From df61a2f03e7438f74a52bd9613500ce4955e52d5 Mon Sep 17 00:00:00 2001
From: 59xa <59xa@proton.me>
Date: Sun, 22 Feb 2026 16:44:18 -0600
Subject: [PATCH 01/11] Update dependencies
---
DynamicWin/DynamicWin.csproj | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/DynamicWin/DynamicWin.csproj b/DynamicWin/DynamicWin.csproj
index 21210b7..49f52fe 100644
--- a/DynamicWin/DynamicWin.csproj
+++ b/DynamicWin/DynamicWin.csproj
@@ -41,14 +41,15 @@
-
+
-
-
-
+
+
+
+
From 29a9eefefc84fed1947ca9db3a1f115227d8ffb9 Mon Sep 17 00:00:00 2001
From: 59xa <59xa@proton.me>
Date: Sun, 1 Mar 2026 01:05:00 -0600
Subject: [PATCH 02/11] Mark canary build
---
DynamicWin/Main/App.xaml.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/DynamicWin/Main/App.xaml.cs b/DynamicWin/Main/App.xaml.cs
index 8c6b4fd..f72dfb1 100644
--- a/DynamicWin/Main/App.xaml.cs
+++ b/DynamicWin/Main/App.xaml.cs
@@ -20,8 +20,8 @@ public partial class DynamicWinMain : System.Windows.Application
public static MMDevice? defaultDevice;
public static MMDevice? defaultMicrophone;
- public static string Version => "v1.6.1r";
- public static Channel ReleaseStream => Channel.Release;
+ public static string Version => "v1.6.2b1";
+ public static Channel ReleaseStream => Channel.Canary;
public static Architecture ProcessArchitecture => RuntimeInformation.ProcessArchitecture;
[STAThread]
From ab1a3726a7cb7f7c2f00d11c99e056f64ab4ad87 Mon Sep 17 00:00:00 2001
From: 59xa <59xa@proton.me>
Date: Sun, 1 Mar 2026 01:21:11 -0600
Subject: [PATCH 03/11] Add setting for ReduceWorkingArea
---
DynamicWin/Main/Settings.cs | 6 ++++++
DynamicWin/UI/Menu/Menus/SettingsMenu.cs | 22 +++++++++++++++++++++-
2 files changed, 27 insertions(+), 1 deletion(-)
diff --git a/DynamicWin/Main/Settings.cs b/DynamicWin/Main/Settings.cs
index e9a1f5f..3e18d95 100644
--- a/DynamicWin/Main/Settings.cs
+++ b/DynamicWin/Main/Settings.cs
@@ -28,6 +28,7 @@ public class Settings
private static int activeScreenIndex;
private static int releaseStream;
private static bool allowAutomaticUpdates = true;
+ private static bool reduceWorkingArea;
public static IslandObject.IslandMode IslandMode { get => islandMode; set => islandMode = value; }
public static bool AllowBlur { get => allowBlur; set => allowBlur = value; }
@@ -42,6 +43,7 @@ public class Settings
public static int ScreenIndex { get => activeScreenIndex; set => activeScreenIndex = value; }
public static int ReleaseStream { get => releaseStream; set => releaseStream = value; }
public static bool AllowAutomaticUpdates { get => allowAutomaticUpdates; set => allowAutomaticUpdates = value; }
+ public static bool ReduceWorkingArea { get => reduceWorkingArea; set => reduceWorkingArea = value; }
public static List smallWidgetsLeft;
public static List smallWidgetsRight;
@@ -77,6 +79,8 @@ public static void InitializeSettings()
AllowAutomaticUpdates = SaveManager.Contains("settings.AllowAutomaticUpdates") ? (bool)SaveManager.Get("settings.AllowAutomaticUpdates") : true;
+ ReduceWorkingArea = SaveManager.Contains("settings.ReduceWorkingArea") ? (bool)SaveManager.Get("settings.ReduceWorkingArea") : true;
+
Theme = (int)((Int64)SaveManager.Get("settings.theme"));
ScreenIndex = (int)((Int64)SaveManager.Get("settings.screenindex"));
ReleaseStream = SaveManager.Contains("settings.ReleaseStream") ? (int)((Int64)SaveManager.Get("settings.ReleaseStream")) : 0;
@@ -137,6 +141,7 @@ public static void InitializeSettings()
// default automatic updates enabled
AllowAutomaticUpdates = true;
+ ReduceWorkingArea = true;
Theme = 0;
@@ -186,6 +191,7 @@ public static void Save()
SaveManager.Add("settings.ReleaseStream", ReleaseStream);
SaveManager.Add("settings.AllowAutomaticUpdates", AllowAutomaticUpdates);
+ SaveManager.Add("settings.ReduceWorkingArea", ReduceWorkingArea);
SaveManager.Add("settings.theme", Theme);
SaveManager.Add("settings.screenindex", ScreenIndex);
diff --git a/DynamicWin/UI/Menu/Menus/SettingsMenu.cs b/DynamicWin/UI/Menu/Menus/SettingsMenu.cs
index 5f5c297..0434f17 100644
--- a/DynamicWin/UI/Menu/Menus/SettingsMenu.cs
+++ b/DynamicWin/UI/Menu/Menus/SettingsMenu.cs
@@ -52,6 +52,7 @@ void SaveAndBack()
Settings.ToggleHomeMenuShadow = toggleHomeMenuShadow.IsChecked;
Settings.RunOnStartup = runOnStartup.IsChecked;
Settings.AllowAutomaticUpdates = allowAutomaticUpdates.IsChecked;
+ Settings.ReduceWorkingArea = reduceWorkingArea.IsChecked;
// Save the selected default big menu mode
if (bigMenuModeSelector != null)
@@ -88,12 +89,13 @@ void SaveAndBack()
DWCheckbox antiAliasing;
DWCheckbox runOnStartup;
DWCheckbox allowAutomaticUpdates;
+ DWCheckbox reduceWorkingArea;
DWCheckbox toggleIslandShadow;
DWCheckbox toggleHomeMenuShadow;
DWCheckbox toggleHighRefreshRate;
DWCheckbox limitRefreshRateWhenIdle;
- DWText refreshRateDisclaimer1, refreshRateDisclaimer2, limitRefreshRateDisclaimer1, limitRefreshRateDisclaimer2;
+ DWText refreshRateDisclaimer1, refreshRateDisclaimer2, limitRefreshRateDisclaimer1, limitRefreshRateDisclaimer2, workingAreaDisclaimer1, workingAreaDisclaimer2;
UIObject bottomMask;
@@ -132,6 +134,24 @@ public override List InitializeMenu(IslandObject island)
objects.Add(islandMode);
}
+ workingAreaDisclaimer1 = new DWText(island, "Renders the interface to its minimum height and width possible, also improves performance.", new Vec2(25, 0), UIAlignment.TopLeft);
+ workingAreaDisclaimer1.Font = Res.SFProRegular;
+ workingAreaDisclaimer1.TextSize = 12;
+ workingAreaDisclaimer1.Anchor.X = 0;
+
+ workingAreaDisclaimer2 = new DWText(island, "Enabling this setting may prevent the interface from being placed correctly at the top.", new Vec2(25, 0), UIAlignment.TopLeft);
+ workingAreaDisclaimer2.Font = Res.SFProRegular;
+ workingAreaDisclaimer2.TextSize = 12;
+ workingAreaDisclaimer2.Anchor.X = 0;
+
+ objects.Add(workingAreaDisclaimer1);
+ objects.Add(workingAreaDisclaimer2);
+
+ reduceWorkingArea = new DWCheckbox(island, "Reduce working area", new Vec2(25, 0), new Vec2(25, 25), () => { }, UIAlignment.TopLeft);
+ reduceWorkingArea.IsChecked = Settings.ReduceWorkingArea;
+ reduceWorkingArea.Anchor.X = 0;
+ objects.Add(reduceWorkingArea);
+
allowBlur = new DWCheckbox(island, "Toggle blur", new Vec2(25, 0), new Vec2(25, 25), () => { }, UIAlignment.TopLeft);
allowBlur.IsChecked = Settings.AllowBlur;
allowBlur.Anchor.X = 0;
From 285c7b324023cd59979b6be8a22666e0c2e38739 Mon Sep 17 00:00:00 2001
From: 59xa <59xa@proton.me>
Date: Sun, 1 Mar 2026 01:51:20 -0600
Subject: [PATCH 04/11] Implement working area reduction logic
---
DynamicWin/Main/Settings.cs | 31 +++++++++++++++++++++++-
DynamicWin/Utils/WindowPositionHelper.cs | 22 +++++++++++++----
2 files changed, 47 insertions(+), 6 deletions(-)
diff --git a/DynamicWin/Main/Settings.cs b/DynamicWin/Main/Settings.cs
index 3e18d95..73db682 100644
--- a/DynamicWin/Main/Settings.cs
+++ b/DynamicWin/Main/Settings.cs
@@ -43,7 +43,36 @@ public class Settings
public static int ScreenIndex { get => activeScreenIndex; set => activeScreenIndex = value; }
public static int ReleaseStream { get => releaseStream; set => releaseStream = value; }
public static bool AllowAutomaticUpdates { get => allowAutomaticUpdates; set => allowAutomaticUpdates = value; }
- public static bool ReduceWorkingArea { get => reduceWorkingArea; set => reduceWorkingArea = value; }
+ public static bool ReduceWorkingArea
+ {
+ get => reduceWorkingArea;
+ set
+ {
+ reduceWorkingArea = value;
+
+ try
+ {
+ if (Application.Current != null)
+ {
+ Application.Current.Dispatcher.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (MainForm.Instance != null)
+ WindowPositionHelper.CenterWindowOnMonitor(MainForm.Instance, ScreenIndex);
+ }
+ catch { }
+ }));
+ }
+ else
+ {
+ if (MainForm.Instance != null)
+ WindowPositionHelper.CenterWindowOnMonitor(MainForm.Instance, ScreenIndex);
+ }
+ }
+ catch { }
+ }
+ }
public static List smallWidgetsLeft;
public static List smallWidgetsRight;
diff --git a/DynamicWin/Utils/WindowPositionHelper.cs b/DynamicWin/Utils/WindowPositionHelper.cs
index 0ea8235..f05819c 100644
--- a/DynamicWin/Utils/WindowPositionHelper.cs
+++ b/DynamicWin/Utils/WindowPositionHelper.cs
@@ -1,3 +1,4 @@
+using DynamicWin.Main;
using System;
using System.Windows;
using System.Windows.Forms;
@@ -15,6 +16,8 @@ public static void CenterWindowOnMonitor(Window window, int monitorIndex)
var screen = screens[clampedIndex];
var bounds = screen.Bounds;
+ double windowWidth = window is { ActualWidth: > 0 } ? window.ActualWidth : window.Width;
+
// Get DPI scaling for the target monitor
double dpiX = 96.0, dpiY = 96.0;
var source = PresentationSource.FromVisual(window);
@@ -35,12 +38,21 @@ public static void CenterWindowOnMonitor(Window window, int monitorIndex)
double scaleX = dpiX / 96.0;
double scaleY = dpiY / 96.0;
- // Aggressively place window at the very top and full width of the physical screen (ignoring taskbar)
var screenBounds = screen.Bounds;
- window.Left = screenBounds.Left / scaleX;
- window.Top = screenBounds.Top / scaleY;
- window.Width = screenBounds.Width / scaleX;
- window.Height = screenBounds.Height / scaleY;
+ double targetLeft = (bounds.Left + (bounds.Width - windowWidth * scaleX) / 2.0) / scaleX;
+ double targetTop = screenBounds.Top / scaleY;
+
+ const double epsilon = 1.0;
+
+ if (double.IsNaN(window.Left) || Math.Abs(window.Left - targetLeft) > epsilon)
+ window.Left = targetLeft;
+
+ if (double.IsNaN(window.Top) || Math.Abs(window.Top - targetTop) > epsilon)
+ window.Top = targetTop;
+
+ double desiredHeight = !Settings.ReduceWorkingArea ? screenBounds.Height / scaleY : 500.0;
+ if (double.IsNaN(window.Height) || Math.Abs(window.Height - desiredHeight) > epsilon)
+ window.Height = desiredHeight;
}
}
}
From 5fd28d0a3415b959f509fe6c51b0c29c80b0ba5f Mon Sep 17 00:00:00 2001
From: 59xa <59xa@proton.me>
Date: Sun, 1 Mar 2026 13:18:31 -0600
Subject: [PATCH 05/11] Implement logic for toggling media display when idle
---
.../UI/Widgets/Small/MediaThumbnailWidget.cs | 58 +++++++-
.../UI/Widgets/Small/SmallVisualiserWidget.cs | 93 +++++++++++-
DynamicWin/Utils/MediaThumbnailService.cs | 140 +++++++++++++++++-
3 files changed, 281 insertions(+), 10 deletions(-)
diff --git a/DynamicWin/UI/Widgets/Small/MediaThumbnailWidget.cs b/DynamicWin/UI/Widgets/Small/MediaThumbnailWidget.cs
index afdd02e..c567929 100644
--- a/DynamicWin/UI/Widgets/Small/MediaThumbnailWidget.cs
+++ b/DynamicWin/UI/Widgets/Small/MediaThumbnailWidget.cs
@@ -1,4 +1,4 @@
-using DynamicWin.Utils;
+using DynamicWin.Utils;
using DynamicWin.UI.UIElements;
using SkiaSharp;
using System;
@@ -52,6 +52,9 @@ public class MediaThumbnailWidget : SmallWidgetBase
public MediaThumbnailWidget(UIObject? parent, Vec2 position, UIAlignment alignment = UIAlignment.TopCenter) : base(parent, position, alignment)
{
+ // Ensure shared setting loaded
+ try { RegisterSmallVisualiserWidgetSettings.SharedMediaSettings.Load(); } catch { }
+
MediaThumbnailService.Instance.ThumbnailChanged += OnThumbnailChanged;
// Try to initialise from service canonical bitmap (fast path)
@@ -70,7 +73,23 @@ public MediaThumbnailWidget(UIObject? parent, Vec2 position, UIAlignment alignme
thumbnailBitmap = bmp;
try { currentBitmapFingerprint = BitmapUtils.GetBitmapFingerprint(bmp); } catch { currentBitmapFingerprint = null; }
hasMedia = true;
- collapseProgress = 1f;
+
+ // Respect shared hide-when-idle setting: if enabled and media has been paused for longer than 30 seconds, start collapsed
+ bool hideWhenIdle = false;
+ try { hideWhenIdle = RegisterSmallVisualiserWidgetSettings.SharedMediaSettings.HideMediaWhenIdle; } catch { hideWhenIdle = false; }
+ if (hideWhenIdle)
+ {
+ try
+ {
+ bool pausedLong = MediaThumbnailService.Instance.IsPausedLongerThan(TimeSpan.FromSeconds(30));
+ collapseProgress = pausedLong ? 0f : 1f;
+ }
+ catch { collapseProgress = 1f; }
+ }
+ else
+ {
+ collapseProgress = 1f;
+ }
}
}
catch { }
@@ -88,10 +107,41 @@ private void OnThumbnailChanged(object? sender, MediaChangedEventArgs e)
// Adopt metadata immediately and ensure widget expanded when media exists
lock (mediaLock)
{
+ // If shared setting requests hiding media when idle, only expand when playback active
+ bool hideWhenIdle = false;
+ try { hideWhenIdle = RegisterSmallVisualiserWidgetSettings.SharedMediaSettings.HideMediaWhenIdle; } catch { hideWhenIdle = false; }
+
if (e.Media != null)
{
hasMedia = true;
- BeginInvokeUI(() => StartCollapseOrExpand(true));
+
+ // If hideWhenIdle is enabled, determine expand state based on playback status and pause duration
+ if (hideWhenIdle)
+ {
+ try
+ {
+ var status = DynamicWin.Utils.MediaThumbnailService.Instance?.LastPlaybackStatus;
+ bool playing = status.HasValue && status.Value == Windows.Media.Control.GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing;
+
+ // If not playing, check if paused longer than 30 seconds
+ bool shouldExpand = playing;
+ if (!playing)
+ {
+ bool pausedLong = MediaThumbnailService.Instance.IsPausedLongerThan(TimeSpan.FromSeconds(30));
+ shouldExpand = !pausedLong;
+ }
+
+ BeginInvokeUI(() => StartCollapseOrExpand(shouldExpand));
+ }
+ catch
+ {
+ BeginInvokeUI(() => StartCollapseOrExpand(true));
+ }
+ }
+ else
+ {
+ BeginInvokeUI(() => StartCollapseOrExpand(true));
+ }
}
else
{
@@ -250,7 +300,7 @@ public override void Update(float deltaTime)
bool isPaused = false;
try
{
- var status = DynamicWin.Utils.MediaThumbnailService.Instance?.LastPlaybackStatus;
+ var status = MediaThumbnailService.Instance?.LastPlaybackStatus;
if (status.HasValue)
{
isPaused = status.Value != Windows.Media.Control.GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing;
diff --git a/DynamicWin/UI/Widgets/Small/SmallVisualiserWidget.cs b/DynamicWin/UI/Widgets/Small/SmallVisualiserWidget.cs
index 4005543..5b996f3 100644
--- a/DynamicWin/UI/Widgets/Small/SmallVisualiserWidget.cs
+++ b/DynamicWin/UI/Widgets/Small/SmallVisualiserWidget.cs
@@ -39,6 +39,34 @@ class RegisterSmallVisualiserWidgetSettings : IRegisterableSetting
public static SmallVisualiserSave saveData;
+ // Shared setting between visualiser and media thumbnail widgets
+ public static class SharedMediaSettings
+ {
+ public const string SettingKey = "Setting.HideMediaWhenIdle";
+ public static bool HideMediaWhenIdle = false;
+
+ public static void Load()
+ {
+ try
+ {
+ if (SaveManager.Contains(SettingKey))
+ {
+ HideMediaWhenIdle = (bool)SaveManager.Get(SettingKey);
+ }
+ else
+ {
+ HideMediaWhenIdle = false;
+ }
+ }
+ catch { HideMediaWhenIdle = false; }
+ }
+
+ public static void Save()
+ {
+ try { SaveManager.Add(SettingKey, HideMediaWhenIdle); } catch { }
+ }
+ }
+
public struct SmallVisualiserSave
{
public bool displayDotWhenIdle;
@@ -47,7 +75,7 @@ public struct SmallVisualiserSave
}
///
- /// Loads the visualizer settings from persistent storage or initializes them with default values if no saved
+ /// Loads the visualiser settings from persistent storage or initialises them with default values if no saved
/// settings are found.
///
/// This method attempts to retrieve previously saved settings using the current setting
@@ -68,6 +96,9 @@ public void LoadSettings()
useThumbnailBackground = true
};
}
+
+ // Load shared setting
+ SharedMediaSettings.Load();
}
///
@@ -79,6 +110,9 @@ public void LoadSettings()
public void SaveSettings()
{
SaveManager.Add(SettingID, JsonConvert.SerializeObject(saveData));
+
+ // Persist shared setting
+ SharedMediaSettings.Save();
}
///
@@ -96,7 +130,9 @@ public List SettingsObjects()
var displayDotWhenIdle = new DWCheckbox(null, "Display visualiser dots when idle", new Vec2(25, 0), new Vec2(25, 25), null, UIAlignment.TopLeft);
var enableColourTransition = new DWCheckbox(null, "Enable visualiser colour transitioning", new Vec2(25, 0), new Vec2(25, 25), null, UIAlignment.TopLeft);
var useThumbnailBackground = new DWCheckbox(null, "Use media thumbnail as background", new Vec2(25, 0), new Vec2(25, 25), null, UIAlignment.TopLeft);
- var thumbnailDisclaimer = new DWText(null, "By enabling this option, colour transitioning will be bypassed.", new Vec2(25, 0), UIAlignment.TopLeft);
+ var thumbnailDisclaimer = new DWText(null, "Colour transition and media thumbnail options are mutually exclusive.", new Vec2(25, 0), UIAlignment.TopLeft);
+ var hideMediaWhenIdle = new DWCheckbox(null, "Hide media thumbnail when idle (shared)", new Vec2(25, 0), new Vec2(25, 25), null, UIAlignment.TopLeft);
+ var hideMediaDisclaimer = new DWText(null, "Hides media thumbnail and visualiser after 30 seconds when paused.", new Vec2(25, 0), UIAlignment.TopLeft);
displayDotWhenIdle.clickCallback += () =>
{
@@ -106,26 +142,51 @@ public List SettingsObjects()
enableColourTransition.clickCallback += () =>
{
saveData.enableColourTransition = enableColourTransition.IsChecked;
+
+ if (enableColourTransition.IsChecked)
+ {
+ // If enabling colour transition, disable thumbnail background
+ saveData.useThumbnailBackground = false;
+ useThumbnailBackground.IsChecked = false;
+ }
};
useThumbnailBackground.clickCallback += () =>
{
saveData.useThumbnailBackground = useThumbnailBackground.IsChecked;
+
+ if (useThumbnailBackground.IsChecked)
+ {
+ // If enabling thumbnail background, disable colour transition
+ saveData.enableColourTransition = false;
+ enableColourTransition.IsChecked = false;
+ }
+ };
+
+ hideMediaWhenIdle.clickCallback += () =>
+ {
+ SharedMediaSettings.HideMediaWhenIdle = hideMediaWhenIdle.IsChecked;
+ // Save immediately so other widget instances can read it
+ SharedMediaSettings.Save();
};
displayDotWhenIdle.IsChecked = saveData.displayDotWhenIdle;
enableColourTransition.IsChecked = saveData.enableColourTransition;
useThumbnailBackground.IsChecked = saveData.useThumbnailBackground;
+ hideMediaWhenIdle.IsChecked = SharedMediaSettings.HideMediaWhenIdle;
displayDotWhenIdle.Anchor.X = 0;
enableColourTransition.Anchor.X = 0;
thumbnailDisclaimer.Anchor.X = 0;
useThumbnailBackground.Anchor.X = 0;
+ hideMediaWhenIdle.Anchor.X = 0;
objects.Add(displayDotWhenIdle);
- objects.Add(enableColourTransition);
objects.Add(thumbnailDisclaimer);
+ objects.Add(enableColourTransition);
objects.Add(useThumbnailBackground);
+ objects.Add(hideMediaDisclaimer);
+ objects.Add(hideMediaWhenIdle);
return objects;
}
@@ -185,11 +246,33 @@ private async void OnServiceThumbnailChanged(object? sender, MediaChangedEventAr
try
{
var tl = await MediaInfo.FetchCurrentTimelineAsync().ConfigureAwait(false);
- bool shouldExpand = tl != null && tl.PlaybackStatus != GlobalSystemMediaTransportControlsSessionPlaybackStatus.Closed;
hasMedia = true;
- targetExpanded = shouldExpand;
+ // Evaluate shared setting: if HideMediaWhenIdle is enabled and media has been paused for >= 1 minute, collapse
+ bool hideWhenIdle = RegisterSmallVisualiserWidgetSettings.SharedMediaSettings.HideMediaWhenIdle;
+ bool shouldExpand;
+
+ if (hideWhenIdle)
+ {
+ // If timeline is present and playing, expand; otherwise if paused longer than threshold, collapse
+ if (tl != null && tl.PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing)
+ {
+ shouldExpand = true;
+ }
+ else
+ {
+ // If paused longer than threshold -> collapse
+ bool pausedLong = MediaThumbnailService.Instance.IsPausedLongerThan(TimeSpan.FromSeconds(30));
+ shouldExpand = !pausedLong;
+ }
+ }
+ else
+ {
+ shouldExpand = tl != null && tl.PlaybackStatus != GlobalSystemMediaTransportControlsSessionPlaybackStatus.Closed;
+ }
+
+ targetExpanded = shouldExpand;
BeginInvokeUI(() => StartCollapseOrExpand(shouldExpand));
}
catch
diff --git a/DynamicWin/Utils/MediaThumbnailService.cs b/DynamicWin/Utils/MediaThumbnailService.cs
index 5ccc70e..5d42efa 100644
--- a/DynamicWin/Utils/MediaThumbnailService.cs
+++ b/DynamicWin/Utils/MediaThumbnailService.cs
@@ -115,6 +115,25 @@ public event EventHandler? ThumbnailChanged
// Add playback status tracking for widgets
private GlobalSystemMediaTransportControlsSessionPlaybackStatus? _lastPlaybackStatus = null;
public GlobalSystemMediaTransportControlsSessionPlaybackStatus? LastPlaybackStatus => _lastPlaybackStatus;
+ // Track when playback transitioned from playing to a non-playing state
+ private DateTime? lastPlaybackNotPlayingAt = null;
+ // Whether we've observed playback status at least once since service started
+ private bool playbackStateInitialized = false;
+ // Track if we've already notified about being paused for longer than 30 seconds
+ private bool hasNotifiedPausedLongThreshold = false;
+
+ ///
+ /// Returns true if media has been in a non-playing state for at least the provided duration.
+ ///
+ public bool IsPausedLongerThan(TimeSpan duration)
+ {
+ if (lastPlaybackNotPlayingAt == null) return false;
+ try
+ {
+ return (DateTime.UtcNow - lastPlaybackNotPlayingAt.Value) >= duration;
+ }
+ catch { return false; }
+ }
private MediaThumbnailService() { }
@@ -230,7 +249,54 @@ private async Task FetchAndUpdateAsync(bool forceRefreshThumbnail = false)
playbackStatus = timeline.PlaybackStatus;
}
catch { }
- _lastPlaybackStatus = playbackStatus;
+ // Update last playback status and record when it became non-playing
+ var previousPlaybackStatus = _lastPlaybackStatus;
+ try
+ {
+ _lastPlaybackStatus = playbackStatus;
+
+ // If this is the first observed playback state since service start, treat a non-playing
+ // state as having been not-playing for longer than the idle threshold so widgets that
+ // should hide on startup will collapse immediately. Subsequent non-playing transitions
+ // use the normal timestamping behaviour (mark time when transition from playing occurs).
+ if (!playbackStateInitialized)
+ {
+ playbackStateInitialized = true;
+ if (playbackStatus.HasValue && playbackStatus.Value == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing)
+ {
+ lastPlaybackNotPlayingAt = null;
+ }
+ else
+ {
+ // Mark as having been not-playing for a while so startup widgets can hide immediately
+ lastPlaybackNotPlayingAt = DateTime.UtcNow.AddSeconds(-31);
+ }
+ }
+ else
+ {
+ if (playbackStatus.HasValue && playbackStatus.Value == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing)
+ {
+ lastPlaybackNotPlayingAt = null;
+ hasNotifiedPausedLongThreshold = false;
+ }
+ else
+ {
+ if (previousPlaybackStatus.HasValue && previousPlaybackStatus.Value == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing)
+ {
+ // Just transitioned from playing to not-playing
+ lastPlaybackNotPlayingAt = DateTime.UtcNow;
+ hasNotifiedPausedLongThreshold = false;
+ }
+ else if (!previousPlaybackStatus.HasValue && playbackStatus.HasValue && playbackStatus.Value != GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing)
+ {
+ // Started already not-playing (fallback)
+ lastPlaybackNotPlayingAt = DateTime.UtcNow;
+ hasNotifiedPausedLongThreshold = false;
+ }
+ }
+ }
+ }
+ catch { _lastPlaybackStatus = playbackStatus; }
// If there is no media, clear all cached metadata and thumbnail, and notify subscribers immediately
if (media == null)
@@ -278,6 +344,51 @@ private async Task FetchAndUpdateAsync(bool forceRefreshThumbnail = false)
if (!metadataChanged && lastBytes != null && !forceRefreshThumbnail)
{
+ // Before returning, check if playback status change or pause threshold crossing must be notified
+ try
+ {
+ if (playbackStatus != null && lastMedia != null)
+ {
+ bool statusChanged = !Equals(previousPlaybackStatus, playbackStatus);
+ bool isPausedLong = IsPausedLongerThan(TimeSpan.FromSeconds(30));
+ bool shouldNotify = false;
+
+ // Check if playback status changed
+ if (statusChanged)
+ {
+ shouldNotify = true;
+ }
+ // Check if 30-second pause threshold was crossed
+ else if (isPausedLong && !hasNotifiedPausedLongThreshold)
+ {
+ // Just crossed into "paused long" territory
+ hasNotifiedPausedLongThreshold = true;
+ shouldNotify = true;
+ }
+ else if (!isPausedLong && hasNotifiedPausedLongThreshold)
+ {
+ // Transitioned back to "not paused long" (resumed playing)
+ hasNotifiedPausedLongThreshold = false;
+ shouldNotify = true;
+ }
+
+ if (shouldNotify)
+ {
+ _thumbnailChanged?.Invoke(this, new MediaChangedEventArgs(
+ new Media { Title = lastMedia.Title, Artist = lastMedia.Artist },
+ lastBytes
+ ));
+
+ List> snap3;
+ lock (listLock) { snap3 = new List>(legacyListeners); }
+ foreach (var l in snap3)
+ {
+ try { l(lastMedia); } catch { }
+ }
+ }
+ }
+ }
+ catch { }
return;
}
@@ -328,6 +439,33 @@ private async Task FetchAndUpdateAsync(bool forceRefreshThumbnail = false)
ThumbnailData = lastBytes
};
+ // If playback status changed during this fetch, notify subscribers so widgets can re-evaluate
+ try
+ {
+ if (playbackStatus != null && lastMedia != null)
+ {
+ // Check if playback status changed
+ bool statusChanged = !Equals(previousPlaybackStatus, playbackStatus);
+
+ // If playback status changed, notify subscribers
+ if (statusChanged)
+ {
+ _thumbnailChanged?.Invoke(this, new MediaChangedEventArgs(
+ new Media { Title = lastMedia.Title, Artist = lastMedia.Artist },
+ lastBytes
+ ));
+
+ List> snap2;
+ lock (listLock) { snap2 = new List>(legacyListeners); }
+ foreach (var l in snap2)
+ {
+ try { l(lastMedia); } catch { }
+ }
+ }
+ }
+ }
+ catch { }
+
bool bytesChanged = false;
if (newFingerprint.HasValue || prevFingerprint.HasValue)
{
From 7314018c7c9b71d9aa605c7b447a8899376de654 Mon Sep 17 00:00:00 2001
From: 59xa <59xa@proton.me>
Date: Sun, 1 Mar 2026 13:35:28 -0600
Subject: [PATCH 06/11] Increase thumbnail scale to 0.8f when paused
---
DynamicWin/UI/UIElements/Custom/MediaPlayer.cs | 4 ++--
DynamicWin/UI/Widgets/Small/MediaThumbnailWidget.cs | 11 ++++++-----
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/DynamicWin/UI/UIElements/Custom/MediaPlayer.cs b/DynamicWin/UI/UIElements/Custom/MediaPlayer.cs
index 2d0aeb8..0a80d74 100644
--- a/DynamicWin/UI/UIElements/Custom/MediaPlayer.cs
+++ b/DynamicWin/UI/UIElements/Custom/MediaPlayer.cs
@@ -1360,7 +1360,7 @@ public override void Draw(SKCanvas canvas)
int save = canvas.Save();
// Shrink and dim thumbnail if paused, animated
- float thumbScale = 0.6f + 0.4f * thumbnailAnim; // 0.6 (paused) to 1.0 (playing)
+ float thumbScale = 0.8f + 0.2f * thumbnailAnim; // 0.8 (paused) to 1.0 (playing)
float dimAlpha = (1f - thumbnailAnim) * 120f; // 0 (playing) to 120 (paused)
float centerX = thumbRect.MidX;
float centerY = thumbRect.MidY;
@@ -1547,7 +1547,7 @@ public override void Draw(SKCanvas canvas)
paint.IsAntialias = Settings.AntiAliasing;
paint.Color = GetColor(timelineTextColor).Value();
paint.TextSize = timelineTextSize;
- paint.Typeface = Resources.Res.SFProRegular;
+ paint.Typeface = Res.SFProRegular;
float timelineTextY = barY + timelineHeight + timelineTextSize - 10f;
float leftX = barX - timelineSidePadding + 4f;
diff --git a/DynamicWin/UI/Widgets/Small/MediaThumbnailWidget.cs b/DynamicWin/UI/Widgets/Small/MediaThumbnailWidget.cs
index c567929..ed43dd7 100644
--- a/DynamicWin/UI/Widgets/Small/MediaThumbnailWidget.cs
+++ b/DynamicWin/UI/Widgets/Small/MediaThumbnailWidget.cs
@@ -6,6 +6,7 @@
using System.Threading;
using System.Diagnostics;
using DynamicWin.Resources;
+using DynamicWin.Main;
namespace DynamicWin.UI.Widgets.Small
{
@@ -368,7 +369,7 @@ public override void Draw(SKCanvas canvas)
float flipScale = animator.GetFlipScale();
bool doFlip = animator.IsFlipping;
- float thumbScale = 0.6f + 0.4f * thumbnailAnim;
+ float thumbScale = 0.8f + 0.2f * thumbnailAnim;
float dimAlpha = (1f - thumbnailAnim) * 120f;
float centerX = thumbRect.MidX;
float centerY = thumbRect.MidY;
@@ -383,9 +384,9 @@ public override void Draw(SKCanvas canvas)
var localRect = SKRect.Create(-thumbRect.Width / 2f, -thumbRect.Height / 2f, thumbRect.Width, thumbRect.Height);
var localPath = BuildSuperellipsePath(localRect, 7f, 1f);
canvas.Save();
- canvas.ClipPath(localPath, antialias: true);
+ canvas.ClipPath(localPath, antialias: Settings.AntiAliasing);
var paint = GetPaint();
- paint.IsAntialias = true;
+ paint.IsAntialias = Settings.AntiAliasing;
paint.ImageFilter = animator.BlurAmount > 0f ? SKImageFilter.CreateBlur(animator.BlurAmount, animator.BlurAmount) : null;
if (bmp != null)
{
@@ -411,9 +412,9 @@ public override void Draw(SKCanvas canvas)
canvas.Translate(centerX, centerY);
canvas.Scale(thumbScale, thumbScale);
canvas.Translate(-centerX, -centerY);
- canvas.ClipPath(path, antialias: true);
+ canvas.ClipPath(path, antialias: Settings.AntiAliasing);
var paint = GetPaint();
- paint.IsAntialias = true;
+ paint.IsAntialias = Settings.AntiAliasing;
paint.ImageFilter = animator.BlurAmount > 0f ? SKImageFilter.CreateBlur(animator.BlurAmount, animator.BlurAmount) : null;
if (bmp != null)
{
From 5a2bffbeca19b15d8c0d7ee95080039c50ea4aef Mon Sep 17 00:00:00 2001
From: 59xa <59xa@proton.me>
Date: Sun, 1 Mar 2026 14:33:12 -0600
Subject: [PATCH 07/11] Downgrade SkiaSharp dependency as it caused large file
sizes
---
DynamicWin/DynamicWin.csproj | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/DynamicWin/DynamicWin.csproj b/DynamicWin/DynamicWin.csproj
index 49f52fe..546109e 100644
--- a/DynamicWin/DynamicWin.csproj
+++ b/DynamicWin/DynamicWin.csproj
@@ -46,9 +46,8 @@
-
-
-
+
+
From 68a2be337309d75425c0ed714c45ee8fb481dc29 Mon Sep 17 00:00:00 2001
From: 59xa <59xa@proton.me>
Date: Tue, 14 Apr 2026 16:22:10 -0600
Subject: [PATCH 08/11] Update visualiser to react closer to native iOS
---
DynamicWin/Utils/AudioVisualiser.cs | 58 +++++++++++++++++++++--------
1 file changed, 42 insertions(+), 16 deletions(-)
diff --git a/DynamicWin/Utils/AudioVisualiser.cs b/DynamicWin/Utils/AudioVisualiser.cs
index a20a01f..af217d8 100644
--- a/DynamicWin/Utils/AudioVisualiser.cs
+++ b/DynamicWin/Utils/AudioVisualiser.cs
@@ -39,7 +39,7 @@ public class AudioVisualiser : UIObject
private float[] targetHeights; // Re-use per-frame to avoid allocations
- private float[] bandBalance = new float[] { 1f, 0.75f, 1.15f, 1.10f, 1.25f, 1.35f };
+ private float[] bandBalance = new float[] { 1f, 1f, 1.15f, 1.10f, 1.25f, 1.35f };
private WasapiLoopbackCapture capture;
private readonly object fftLock = new object();
@@ -96,7 +96,7 @@ public class AudioVisualiser : UIObject
private bool enableDotWhenLow = true;
public bool EnableDotWhenLow { get => enableDotWhenLow; set => enableDotWhenLow = value; }
public float BlurAmount { get; set; } = 0f;
- public float BarSpacing { get; set; } = 1f;
+ public float BarSpacing { get; set; } = 1.5f;
// Initialise class
public AudioVisualiser(UIObject? parent, Vec2 position, Vec2 size, UIAlignment alignment = UIAlignment.TopRight, Col Primary = null, Col Secondary = null) : base(parent, position, size, alignment)
@@ -189,7 +189,7 @@ private void InitBarBinMapping(float sampleRate)
}
double minFreq = 20.0; // 20Hz - human hearing start
- double maxFreq = 12000.0; // 12kHz - frequency end
+ double maxFreq = 6000.0; // 12kHz - frequency end
// Use Logarithmic scale for natural frequency distribution
double logMin = Math.Log10(minFreq);
@@ -329,7 +329,7 @@ public override void Update(float deltaTime)
// Bar 0: Sub (1.2x) -> Strongest
// Bar 1: Bass (0.8x) -> Dipped to let bar 0 lead
// Bar 5: Highs (2.5x) -> Extreme boost for hi-hat visibility
- float[] barWeights = { 1.0f, 0.85f, 1.15f, 1.5f, 2.0f, 2.5f };
+ float[] barWeights = { 1.0f, 0.9f, 1.15f, 1.5f, 2.0f, 2.5f };
for (int i = 0; i < barCount; i++)
{
@@ -577,22 +577,48 @@ public override void Draw(SKCanvas canvas)
float rawHeight = barHeight[i] * visualBoost;
float dynamicHeight = rawHeight * height * 0.8f;
- float bH = dynamicHeight;
+ float activity = Math.Clamp(barHeight[i], 0f, 1f);
- // Handle dot clamping
- if (EnableDotWhenLow)
- {
- // The dot should be a perfect circle/square, so its height equals its width
- // Clamp the height so it never gets smaller than the dot
- bH = Math.Max(dotHeight, dynamicHeight);
- }
+ float center = (barCount - 1) / 2f;
+ float distFromCenter = MathF.Abs(i - center) / center;
+ float centerBoost = 1f - distFromCenter;
+
+ float midEmphasis = 1f - MathF.Abs(activity - 0.5f) * 2f;
+ midEmphasis = MathF.Pow(midEmphasis, 1.5f);
+
+ float minScale = 0.7f;
+ float baseScale = activity;
+
+ float breathStrength = 0.15f + (0.15f * centerBoost);
+
+ float time = (float)Stopwatch.GetTimestamp() / Stopwatch.Frequency;
+
+ float phaseOffset = i * 0.6f;
+
+ float lag = distFromCenter * 0.08f;
+ float delayedActivity = Math.Clamp(activity - lag, 0f, 1f);
+
+ float scale = minScale + (1f - minScale) * delayedActivity;
+ scale += midEmphasis * breathStrength;
+
+ float pulse = MathF.Sin(time * 4f + phaseOffset) * (0.03f + 0.02f * centerBoost) * midEmphasis;
+ scale += pulse;
+
+ scale = Math.Clamp(scale, minScale, 1.1f);
+
+ float scaledWidth = barWidth2 * scale;
+ float scaledDotHeight = dotHeight * scale;
+
+ float bH = EnableDotWhenLow
+ ? Math.Max(scaledDotHeight, dynamicHeight)
+ : dynamicHeight;
- // Positioning
- float x = Position.X + i * (barWidth2 + spacing2);
+ float xBase = Position.X + i * (barWidth2 + spacing2);
+ float x = xBase + (barWidth2 - scaledWidth) / 2f;
float barTopY = centerY - bH / 2;
- var rect = SKRect.Create(x, barTopY, barWidth2, bH);
- var roundRect = new SKRoundRect(rect, barWidth2 / 2, barWidth2 / 2);
+ var rect = SKRect.Create(x, barTopY, scaledWidth, bH);
+ var roundRect = new SKRoundRect(rect, scaledWidth / 2, scaledWidth / 2);
// Color logic: dot must stay at the "Secondary" color until it starts growing
// We calculate a 'colorActivity' based on how much it has grown past the dot
From a59d5e364d3ac0155fec15c8526abc43e7fde392 Mon Sep 17 00:00:00 2001
From: 59xa <59xa@proton.me>
Date: Tue, 14 Apr 2026 16:24:29 -0600
Subject: [PATCH 09/11] Bump
---
DynamicWin/Main/App.xaml.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/DynamicWin/Main/App.xaml.cs b/DynamicWin/Main/App.xaml.cs
index f72dfb1..25c46fd 100644
--- a/DynamicWin/Main/App.xaml.cs
+++ b/DynamicWin/Main/App.xaml.cs
@@ -20,7 +20,7 @@ public partial class DynamicWinMain : System.Windows.Application
public static MMDevice? defaultDevice;
public static MMDevice? defaultMicrophone;
- public static string Version => "v1.6.2b1";
+ public static string Version => "v1.6.2b2";
public static Channel ReleaseStream => Channel.Canary;
public static Architecture ProcessArchitecture => RuntimeInformation.ProcessArchitecture;
From c4a035fbad285fafee962341b60422cf64d284f7 Mon Sep 17 00:00:00 2001
From: 59xa <59xa@proton.me>
Date: Tue, 14 Apr 2026 17:31:07 -0600
Subject: [PATCH 10/11] Change misleading text from "Reduce working area" to
"Keep interface always topmost"
---
DynamicWin/Main/Settings.cs | 8 ++++----
DynamicWin/UI/Menu/Menus/SettingsMenu.cs | 13 +++++++------
DynamicWin/Utils/WindowPositionHelper.cs | 2 +-
3 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/DynamicWin/Main/Settings.cs b/DynamicWin/Main/Settings.cs
index 73db682..cc0d231 100644
--- a/DynamicWin/Main/Settings.cs
+++ b/DynamicWin/Main/Settings.cs
@@ -43,7 +43,7 @@ public class Settings
public static int ScreenIndex { get => activeScreenIndex; set => activeScreenIndex = value; }
public static int ReleaseStream { get => releaseStream; set => releaseStream = value; }
public static bool AllowAutomaticUpdates { get => allowAutomaticUpdates; set => allowAutomaticUpdates = value; }
- public static bool ReduceWorkingArea
+ public static bool AlwaysTopmost
{
get => reduceWorkingArea;
set
@@ -108,7 +108,7 @@ public static void InitializeSettings()
AllowAutomaticUpdates = SaveManager.Contains("settings.AllowAutomaticUpdates") ? (bool)SaveManager.Get("settings.AllowAutomaticUpdates") : true;
- ReduceWorkingArea = SaveManager.Contains("settings.ReduceWorkingArea") ? (bool)SaveManager.Get("settings.ReduceWorkingArea") : true;
+ AlwaysTopmost = SaveManager.Contains("settings.ReduceWorkingArea") ? (bool)SaveManager.Get("settings.ReduceWorkingArea") : true;
Theme = (int)((Int64)SaveManager.Get("settings.theme"));
ScreenIndex = (int)((Int64)SaveManager.Get("settings.screenindex"));
@@ -170,7 +170,7 @@ public static void InitializeSettings()
// default automatic updates enabled
AllowAutomaticUpdates = true;
- ReduceWorkingArea = true;
+ AlwaysTopmost = true;
Theme = 0;
@@ -220,7 +220,7 @@ public static void Save()
SaveManager.Add("settings.ReleaseStream", ReleaseStream);
SaveManager.Add("settings.AllowAutomaticUpdates", AllowAutomaticUpdates);
- SaveManager.Add("settings.ReduceWorkingArea", ReduceWorkingArea);
+ SaveManager.Add("settings.ReduceWorkingArea", AlwaysTopmost);
SaveManager.Add("settings.theme", Theme);
SaveManager.Add("settings.screenindex", ScreenIndex);
diff --git a/DynamicWin/UI/Menu/Menus/SettingsMenu.cs b/DynamicWin/UI/Menu/Menus/SettingsMenu.cs
index 0434f17..1801c0a 100644
--- a/DynamicWin/UI/Menu/Menus/SettingsMenu.cs
+++ b/DynamicWin/UI/Menu/Menus/SettingsMenu.cs
@@ -17,6 +17,7 @@
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
+using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Navigation;
using System.Xml.Linq;
@@ -52,7 +53,7 @@ void SaveAndBack()
Settings.ToggleHomeMenuShadow = toggleHomeMenuShadow.IsChecked;
Settings.RunOnStartup = runOnStartup.IsChecked;
Settings.AllowAutomaticUpdates = allowAutomaticUpdates.IsChecked;
- Settings.ReduceWorkingArea = reduceWorkingArea.IsChecked;
+ Settings.AlwaysTopmost = alwaysTopmost.IsChecked;
// Save the selected default big menu mode
if (bigMenuModeSelector != null)
@@ -89,7 +90,7 @@ void SaveAndBack()
DWCheckbox antiAliasing;
DWCheckbox runOnStartup;
DWCheckbox allowAutomaticUpdates;
- DWCheckbox reduceWorkingArea;
+ DWCheckbox alwaysTopmost;
DWCheckbox toggleIslandShadow;
DWCheckbox toggleHomeMenuShadow;
DWCheckbox toggleHighRefreshRate;
@@ -147,10 +148,10 @@ public override List InitializeMenu(IslandObject island)
objects.Add(workingAreaDisclaimer1);
objects.Add(workingAreaDisclaimer2);
- reduceWorkingArea = new DWCheckbox(island, "Reduce working area", new Vec2(25, 0), new Vec2(25, 25), () => { }, UIAlignment.TopLeft);
- reduceWorkingArea.IsChecked = Settings.ReduceWorkingArea;
- reduceWorkingArea.Anchor.X = 0;
- objects.Add(reduceWorkingArea);
+ alwaysTopmost = new DWCheckbox(island, $"Keep interface always topmost", new Vec2(25, 0), new Vec2(25, 25), () => { }, UIAlignment.TopLeft);
+ alwaysTopmost.IsChecked = Settings.AlwaysTopmost;
+ alwaysTopmost.Anchor.X = 0;
+ objects.Add(alwaysTopmost);
allowBlur = new DWCheckbox(island, "Toggle blur", new Vec2(25, 0), new Vec2(25, 25), () => { }, UIAlignment.TopLeft);
allowBlur.IsChecked = Settings.AllowBlur;
diff --git a/DynamicWin/Utils/WindowPositionHelper.cs b/DynamicWin/Utils/WindowPositionHelper.cs
index f05819c..0b10db6 100644
--- a/DynamicWin/Utils/WindowPositionHelper.cs
+++ b/DynamicWin/Utils/WindowPositionHelper.cs
@@ -50,7 +50,7 @@ public static void CenterWindowOnMonitor(Window window, int monitorIndex)
if (double.IsNaN(window.Top) || Math.Abs(window.Top - targetTop) > epsilon)
window.Top = targetTop;
- double desiredHeight = !Settings.ReduceWorkingArea ? screenBounds.Height / scaleY : 500.0;
+ double desiredHeight = !Settings.AlwaysTopmost ? screenBounds.Height / scaleY : 500.0;
if (double.IsNaN(window.Height) || Math.Abs(window.Height - desiredHeight) > epsilon)
window.Height = desiredHeight;
}
From 8c049bb09ed215e620afb633806044e58d9a4bfd Mon Sep 17 00:00:00 2001
From: 59xa <59xa@proton.me>
Date: Tue, 14 Apr 2026 17:31:41 -0600
Subject: [PATCH 11/11] Bump
---
DynamicWin/Main/App.xaml.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/DynamicWin/Main/App.xaml.cs b/DynamicWin/Main/App.xaml.cs
index 25c46fd..463c46e 100644
--- a/DynamicWin/Main/App.xaml.cs
+++ b/DynamicWin/Main/App.xaml.cs
@@ -20,7 +20,7 @@ public partial class DynamicWinMain : System.Windows.Application
public static MMDevice? defaultDevice;
public static MMDevice? defaultMicrophone;
- public static string Version => "v1.6.2b2";
+ public static string Version => "v1.6.2b3";
public static Channel ReleaseStream => Channel.Canary;
public static Architecture ProcessArchitecture => RuntimeInformation.ProcessArchitecture;