Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.vs/
.vscode/
/DynamicWin/bin/
/DynamicWin/obj/
/DynamicWinSetup
Expand Down
4 changes: 2 additions & 2 deletions DynamicWin/DynamicWin.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@
<ItemGroup>
<PackageReference Include="CsvHelper" Version="33.1.0" />
<PackageReference Include="FftSharp" Version="2.2.0" />
<PackageReference Include="LibreHardwareMonitorLib" Version="0.9.4" />
<PackageReference Include="LibreHardwareMonitorLib" Version="0.9.6" />
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
<PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="NAudio.Wasapi" Version="2.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="SkiaSharp" Version="3.119.1" />
<PackageReference Include="SkiaSharp.NativeAssets.Win32" Version="3.119.1" />
<PackageReference Include="System.Management" Version="10.0.1" />
<PackageReference Include="System.Management" Version="10.0.3" />
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.6" />
</ItemGroup>

Expand Down
7 changes: 5 additions & 2 deletions DynamicWin/Main/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.1r";
public static string Version => "v1.7.0r";
public static Channel ReleaseStream => Channel.Release;
public static Architecture ProcessArchitecture => RuntimeInformation.ProcessArchitecture;

Expand Down Expand Up @@ -96,6 +96,8 @@ protected override void OnStartup(StartupEventArgs e)
try
{
MediaInfo.Initialize();
// Also initialise the thumbnail service to start event loop
_ = MediaThumbnailService.Instance;
}
catch { }

Expand Down Expand Up @@ -134,6 +136,7 @@ protected override void OnStartup(StartupEventArgs e)

protected override void OnExit(ExitEventArgs e)
{
AppBarHelper.ForceUnregisterLast();
base.OnExit(e);

try
Expand Down Expand Up @@ -200,7 +203,7 @@ private void UpdateWindowPosition()
try
{
if (mainForm == null) return;
WindowPositionHelper.CenterWindowOnMonitor(mainForm, Settings.ScreenIndex);
mainForm.UpdateWindowConfiguration();
}
catch { }
}
Expand Down
172 changes: 122 additions & 50 deletions DynamicWin/Main/MainForm.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using DynamicWin.UI.Menu;
using DynamicWin.UI.Menu.Menus;
using DynamicWin.Utils;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
Expand All @@ -25,6 +26,7 @@ public partial class MainForm : Window

internal Forms.ToolStripMenuItem _settingsTrayItem;

public static IntPtr Handle { get; private set; }

private DateTime _lastRenderTime;
// Target interval driven by monitor refresh rate (set in ctor)
Expand All @@ -35,11 +37,24 @@ public partial class MainForm : Window
// Mouse/motion tracking for idle detection
private System.Windows.Point _lastMousePos = new System.Windows.Point(-1, -1);
private DateTime _lastMouseMoveTime = DateTime.MinValue;
private readonly TimeSpan _idleMouseThreshold = TimeSpan.FromSeconds(1.0);
private readonly TimeSpan _idleMouseThreshold = TimeSpan.FromMilliseconds(180);
private readonly TimeSpan _refreshRatePollInterval = TimeSpan.FromSeconds(2.0);
private int _cachedRefreshRate = 60;
private DateTime _lastRefreshRateCheck = DateTime.MinValue;
private DateTime _renderBurstUntil = DateTime.MinValue;
private const int ContinuousRenderHz = 60;
private const int StaticIdleRenderHz = 8;

// Rendering pause flag (used for suspend/hibernate)
private bool _renderPaused = false;

public void RequestRenderBurst(int milliseconds = 350)
{
var until = DateTime.UtcNow.AddMilliseconds(Math.Max(1, milliseconds));
if (until > _renderBurstUntil)
_renderBurstUntil = until;
}

#region Win32 API Definitions

[DllImport("user32.dll")]
Expand Down Expand Up @@ -76,18 +91,10 @@ public MainForm()
// Initialise mouse tracking
_lastMouseMoveTime = DateTime.UtcNow;

// Compute initial target frame interval from monitor refresh rate
try
{
int refresh = DisplayHelper.GetRefreshRate();
if (refresh <= 0) refresh = 60;
_targetElapsedTime = TimeSpan.FromMilliseconds(1000.0 / refresh);
Debug.WriteLine($"[MAIN FORM] Initial target frame interval: {_targetElapsedTime.TotalMilliseconds} ms ({refresh} Hz)");
}
catch
{
_targetElapsedTime = TimeSpan.FromMilliseconds(16);
}
// Compute initial target frame interval from the configured monitor.
RefreshCachedRefreshRate(DateTime.UtcNow, true);
_targetElapsedTime = TimeSpan.FromMilliseconds(1000.0 / _cachedRefreshRate);
Debug.WriteLine($"[MAIN FORM] Initial target frame interval: {_targetElapsedTime.TotalMilliseconds} ms ({_cachedRefreshRate} Hz)");

CompositionTarget.Rendering += OnRendering;

Expand All @@ -96,6 +103,11 @@ public MainForm()
this.WindowStyle = WindowStyle.None;
this.WindowState = WindowState.Maximized;
this.ResizeMode = ResizeMode.NoResize;
this.SourceInitialized += (s, e) =>
{
Handle = new WindowInteropHelper(this).Handle;
UpdateWindowConfiguration();
};
this.Topmost = true;
this.AllowsTransparency = true;
this.ShowInTaskbar = false;
Expand Down Expand Up @@ -138,7 +150,7 @@ public MainForm()
this.Topmost = true;
};

_trayIcon.ContextMenuStrip.Items.Add("Restart Control", ContextMenuUtils.LoadTrayBitmap("Resources/icons/context/refresh.png"), (x, y) =>
_trayIcon.ContextMenuStrip.Items.Add("Restart control", ContextMenuUtils.LoadTrayBitmap("Resources/icons/context/refresh.png"), (x, y) =>
{
if (RendererMain.Instance != null) RendererMain.Instance.Destroy();
this.Content = new Grid();
Expand All @@ -157,8 +169,9 @@ public MainForm()

_trayIcon.ContextMenuStrip.Items.Add("Exit", ContextMenuUtils.LoadTrayBitmap("Resources/icons/context/exit.png"), (x, y) =>
{
SaveManager.SaveAll();
Process.GetCurrentProcess().Kill();
_trayIcon.Visible = false;
AppBarHelper.UnregisterAppBar(this);
Application.Current.Shutdown();
});

_trayIcon.Visible = true;
Expand Down Expand Up @@ -188,14 +201,27 @@ public void SetMonitor(int monitorIndex)
this.WindowState = WindowState.Normal;
this.ResizeMode = ResizeMode.CanResize;

WindowPositionHelper.CenterWindowOnMonitor(this, clampedIndex);
UpdateWindowConfiguration();
this.ResizeMode = ResizeMode.NoResize;
RefreshCachedRefreshRate(DateTime.UtcNow, true);
}

// Move the window in App.xaml.cs as well
if (System.Windows.Application.Current is DynamicWinMain app)
{
app.MoveToMonitor(clampedIndex);
}
public void UpdateWindowConfiguration()
{
WindowPositionHelper.CenterWindowOnMonitor(this, Settings.ScreenIndex);
ApplyWorkingArea();
}

private void ApplyWorkingArea()
{
if (Settings.ReduceWorkingArea) AppBarHelper.RegisterAppBar(this, 40);
else AppBarHelper.UnregisterAppBar(this);
}

protected override void OnClosing(CancelEventArgs e)
{
AppBarHelper.UnregisterAppBar(this);
base.OnClosing(e);
}

public static int GetMonitorCount()
Expand All @@ -205,6 +231,8 @@ public static int GetMonitorCount()

private void OnRendering(object? sender, EventArgs e)
{
if (_renderPaused) return;

var now = DateTime.UtcNow;

// Track mouse movement to detect idle while hovering the island
Expand All @@ -222,47 +250,92 @@ private void OnRendering(object? sender, EventArgs e)
// Decide refresh rate dynamically based on settings and idle state
try
{
TimeSpan desiredInterval = TimeSpan.FromMilliseconds(16);
RefreshCachedRefreshRate(now);
int displayRefresh = _cachedRefreshRate;
if (displayRefresh <= 0) displayRefresh = 60;

int fullRefreshHz = Settings.ToggleHighRefreshRate
? displayRefresh
: Math.Min(60, displayRefresh);
int targetHz = fullRefreshHz;

if (Settings.ToggleHighRefreshRate)
{
int displayRefresh = DisplayHelper.GetRefreshRate();
if (displayRefresh <= 0) displayRefresh = 60;

int targetHz = displayRefresh;
fullRefreshHz = displayRefresh;
}

if (Settings.LimitRefreshRateWhenIdle)
if (Settings.LimitRefreshRateWhenIdle)
{
bool leftMouseDown = false;
try { leftMouseDown = System.Windows.Input.Mouse.LeftButton == System.Windows.Input.MouseButtonState.Pressed; } catch { }

var renderer = RendererMain.Instance;
bool islandHover = renderer?.MainIsland?.IsHovering == true;
bool inputActive = leftMouseDown ||
now <= _renderBurstUntil ||
(islandHover && (now - _lastMouseMoveTime) <= _idleMouseThreshold);
bool wantsRealtime = renderer?.WantsRealtimeRendering == true;
bool wantsContinuous = renderer?.WantsContinuousRendering == true;

if (inputActive || wantsRealtime)
{
bool islandHover = false;
try
{
islandHover = RendererMain.Instance?.MainIsland?.IsHovering ?? false;
}
catch { }

bool idle = !islandHover ||
((now - _lastMouseMoveTime) > _idleMouseThreshold);

if (idle)
targetHz = 60;
targetHz = fullRefreshHz;
}

desiredInterval = TimeSpan.FromMilliseconds(1000.0 / targetHz);
else if (wantsContinuous)
{
targetHz = Math.Min(ContinuousRenderHz, fullRefreshHz);
}
else
{
targetHz = Math.Min(StaticIdleRenderHz, fullRefreshHz);
}
}
else
{
targetHz = fullRefreshHz;
}

_targetElapsedTime = desiredInterval;
if (!Settings.ToggleHighRefreshRate)
targetHz = Math.Min(targetHz, 60);

targetHz = Math.Max(1, targetHz);
_targetElapsedTime = TimeSpan.FromMilliseconds(1000.0 / targetHz);

if (targetHz >= displayRefresh)
{
_lastRenderTime = now;
onMainFormRender?.Invoke();
return;
}
}
catch { }

var currentTime = DateTime.Now;
if (currentTime - _lastRenderTime >= _targetElapsedTime)
if (now - _lastRenderTime >= _targetElapsedTime)
{
_lastRenderTime = currentTime;
_lastRenderTime = now;

onMainFormRender?.Invoke();
}
}

private void RefreshCachedRefreshRate(DateTime now, bool force = false)
{
if (!force && (now - _lastRefreshRateCheck) < _refreshRatePollInterval)
return;

try
{
int refresh = DisplayHelper.GetRefreshRate(Settings.ScreenIndex);
if (refresh > 0) _cachedRefreshRate = refresh;
}
catch
{
if (_cachedRefreshRate <= 0) _cachedRefreshRate = 60;
}

_lastRefreshRateCheck = now;
}

public bool isDragging = false;

public void OnScroll(object? sender, System.Windows.Input.MouseWheelEventArgs e)
Expand All @@ -281,8 +354,7 @@ public void AddRenderer()

this.Content = parent;

// Ensure the new renderer is called from the centralised, throttled MainForm loop
onMainFormRender += customControl.Frame;
// RendererMain registers itself with the centralised, throttled MainForm loop.
}

// Allow external modules to pause/resume the rendering loop during suspend/hibernate
Expand All @@ -294,7 +366,7 @@ public void PauseRendering()
public void ResumeRendering()
{
_renderPaused = false;
_lastRenderTime = DateTime.Now; // Reset timing to avoid immediate large update
_lastRenderTime = DateTime.UtcNow; // Reset timing to avoid immediate large update
}

public void MainForm_DragEnter(object? sender, DragEventArgs e)
Expand Down Expand Up @@ -436,4 +508,4 @@ private void TrayIcon_MouseUp(object? sender, Forms.MouseEventArgs e)
}
}
}
}
}
Loading
Loading