From db9584b7244002b2bef15186b649481068b39e8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Mestre?= Date: Tue, 19 May 2026 15:49:53 -0300 Subject: [PATCH 1/2] feat(windows): add active monitor refresh rate detection - Add DisplayHelper to query supported monitor refresh rates via Win32 APIs. - Fetch the exact monitor hosting the Avalonia main window using MonitorFromWindow and EnumDisplaySettings. - Implement GetCompatibleRefreshRate to automatically select optimal refresh rates (exact matches or multiples) for NTSC/PAL targets. - Add safe fallbacks to the highest available refresh rate to prevent crashes on non-standard displays. --- UI/Utilities/DisplayHelper.cs | 145 ++++++++++++++++++++++++++ UI/ViewModels/VideoConfigViewModel.cs | 17 +++ 2 files changed, 162 insertions(+) create mode 100644 UI/Utilities/DisplayHelper.cs diff --git a/UI/Utilities/DisplayHelper.cs b/UI/Utilities/DisplayHelper.cs new file mode 100644 index 000000000..38df635f6 --- /dev/null +++ b/UI/Utilities/DisplayHelper.cs @@ -0,0 +1,145 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Mesen.Utilities +{ + public class DisplayHelper + { + [DllImport("user32.dll")] + private static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern bool EnumDisplaySettings(string? lpszDeviceName, int iModeNum, ref DEVMODE lpDevMode); + + private const uint MONITOR_DEFAULTTONEAREST = 2; + + [StructLayout(LayoutKind.Sequential)] + private struct RECT + { + public int left, top, right, bottom; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct MONITORINFOEX + { + public int cbSize; + public RECT rcMonitor; + public RECT rcWork; + public int dwFlags; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string szDevice; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct DEVMODE + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string dmDeviceName; + + public short dmSpecVersion; + public short dmDriverVersion; + public short dmSize; + public short dmDriverExtra; + public int dmFields; + public int dmPositionX; + public int dmPositionY; + public int dmDisplayOrientation; + public int dmDisplayFixedOutput; + public short dmColor; + public short dmDuplex; + public short dmYResolution; + public short dmTTOption; + public short dmCollate; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string dmFormName; + + public short dmLogPixels; + public int dmBitsPerPel; + public int dmPelsWidth; + public int dmPelsHeight; + public int dmDisplayFlags; + public int dmDisplayFrequency; + public int dmICMMethod; + public int dmICMIntent; + public int dmMediaType; + public int dmDitherType; + public int dmReserved1; + public int dmReserved2; + public int dmPanningWidth; + public int dmPanningHeight; + } + + /// + /// Returns an array of supported refresh rates for the monitor where the main Avalonia window currently resides. + /// Assumes the caller has already verified this is executing on a Windows environment. + /// + public static UInt32[] GetRefreshRatesForCurrentMonitor() + { + IntPtr hwnd = IntPtr.Zero; + + // 1. Safely grab the Avalonia Main Window handle + if(Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { + var window = desktop.MainWindow; + if(window != null) { + var platformHandle = window.TryGetPlatformHandle(); + if(platformHandle != null) { + hwnd = platformHandle.Handle; + } + } + } + + string? deviceName = null; + + // 2. If we successfully got the window handle, find out which monitor it's on + if(hwnd != IntPtr.Zero) { + IntPtr hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + MONITORINFOEX monitorInfo = new MONITORINFOEX(); + monitorInfo.cbSize = Marshal.SizeOf(); + + if(GetMonitorInfo(hMonitor, ref monitorInfo)) { + deviceName = monitorInfo.szDevice; + } + } + + // 3. Query the refresh rates (passing 'null' as deviceName falls back to primary display) + HashSet refreshRates = new HashSet(); + DEVMODE vDevMode = new DEVMODE(); + vDevMode.dmSize = (short)Marshal.SizeOf(); + + int modeIndex = 0; + while(EnumDisplaySettings(deviceName, modeIndex, ref vDevMode)) { + if(vDevMode.dmDisplayFrequency > 0) { + refreshRates.Add((UInt32)vDevMode.dmDisplayFrequency); + } + + modeIndex++; + } + + UInt32[] result = refreshRates.ToArray(); + Array.Sort(result); + return result; + } + + /// + /// Returns the lowest refresh rate that is equal to or a multiple of . + /// Falls back to the highest available rate if no compatible rate is found, or if is 0. + /// + public static UInt32 GetCompatibleRefreshRate(UInt32[] availableRates, UInt32 targetRate) + { + if (targetRate == 0) return availableRates.LastOrDefault(); + + var compatibleRate = availableRates.FirstOrDefault(rate => rate % targetRate == 0); + + return compatibleRate == 0 ? availableRates.LastOrDefault() : compatibleRate; + } + } +} diff --git a/UI/ViewModels/VideoConfigViewModel.cs b/UI/ViewModels/VideoConfigViewModel.cs index d2cff1911..109de1555 100644 --- a/UI/ViewModels/VideoConfigViewModel.cs +++ b/UI/ViewModels/VideoConfigViewModel.cs @@ -55,6 +55,23 @@ public VideoConfigViewModel() //Exclusive fullscreen is only supported on Windows currently IsWindows = OperatingSystem.IsWindows(); + // Detect available refresh rates on Windows and ensure the configured refresh rates are valid + if(IsWindows) { + UInt32[] monitorSupportedRefreshRates = DisplayHelper.GetRefreshRatesForCurrentMonitor(); + + if(monitorSupportedRefreshRates.Length > 0) { + AvailableRefreshRates = monitorSupportedRefreshRates; + + if(!AvailableRefreshRates.Contains(Config.ExclusiveFullscreenRefreshRatePal)) { + Config.ExclusiveFullscreenRefreshRatePal = DisplayHelper.GetCompatibleRefreshRate(AvailableRefreshRates, Config.ExclusiveFullscreenRefreshRatePal); + } + + if(!AvailableRefreshRates.Contains(Config.ExclusiveFullscreenRefreshRateNtsc)) { + Config.ExclusiveFullscreenRefreshRateNtsc = DisplayHelper.GetCompatibleRefreshRate(AvailableRefreshRates, Config.ExclusiveFullscreenRefreshRateNtsc); + } + } + } + //MacOS only supports the software renderer IsMacOs = OperatingSystem.IsMacOS(); From 3d5ccaa853da98230a82b22658a22b6086addc2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Mestre?= Date: Wed, 20 May 2026 00:33:22 -0300 Subject: [PATCH 2/2] style: apply dotnet format to DisplayHelper --- UI/Utilities/DisplayHelper.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/UI/Utilities/DisplayHelper.cs b/UI/Utilities/DisplayHelper.cs index 38df635f6..27129dfd4 100644 --- a/UI/Utilities/DisplayHelper.cs +++ b/UI/Utilities/DisplayHelper.cs @@ -128,17 +128,17 @@ public static UInt32[] GetRefreshRatesForCurrentMonitor() Array.Sort(result); return result; } - + /// /// Returns the lowest refresh rate that is equal to or a multiple of . /// Falls back to the highest available rate if no compatible rate is found, or if is 0. /// public static UInt32 GetCompatibleRefreshRate(UInt32[] availableRates, UInt32 targetRate) { - if (targetRate == 0) return availableRates.LastOrDefault(); - + if(targetRate == 0) return availableRates.LastOrDefault(); + var compatibleRate = availableRates.FirstOrDefault(rate => rate % targetRate == 0); - + return compatibleRate == 0 ? availableRates.LastOrDefault() : compatibleRate; } }