Make application DPI aware (Closes #1369) (#1381)

Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
This commit is contained in:
2026-02-08 06:44:33 -08:00
parent ea0a7a1edb
commit 3ca5117875
3 changed files with 96 additions and 4 deletions
@@ -11,4 +11,6 @@ public sealed class ScreenManagerOptions
public double Y { get; set; }
public double Width { get; set; }
public double Height { get; set; }
public int DpiX { get; set; } = 96;
public int DpiY { get; set; } = 96;
}
@@ -24,6 +24,8 @@ internal sealed class ScreenManager(
ILogger<ScreenManager> logger
) : IScreenManager, IHostedService
{
private const int DefaultDpi = 96;
private readonly PhotinoWindow photinoWindow = photinoWindow.ThrowIfNull();
private readonly IOptionsProvider optionsProvider = optionsProvider.ThrowIfNull();
private readonly IOptionsMonitor<ScreenManagerOptions> liveUpdateableOptions =
@@ -70,11 +72,15 @@ internal sealed class ScreenManager(
this.photinoWindow.Height
);
var currentDpi = GetDpiForPosition(position.Left, position.Top);
var options = this.liveUpdateableOptions.CurrentValue;
options.X = position.Left;
options.Y = position.Top;
options.Width = position.Width;
options.Height = position.Height;
options.DpiX = currentDpi.X;
options.DpiY = currentDpi.Y;
this.optionsProvider.SaveOption(options);
}
@@ -85,6 +91,8 @@ internal sealed class ScreenManager(
options.Y = 0;
options.Width = 0;
options.Height = 0;
options.DpiX = DefaultDpi;
options.DpiY = DefaultDpi;
this.optionsProvider.SaveOption(options);
}
@@ -112,11 +120,12 @@ internal sealed class ScreenManager(
public Rectangle GetSavedPosition()
{
var savedOptions = this.liveUpdateableOptions.CurrentValue;
var savedPosition = new Rectangle(
(int)this.liveUpdateableOptions.CurrentValue.X,
(int)this.liveUpdateableOptions.CurrentValue.Y,
(int)this.liveUpdateableOptions.CurrentValue.Width,
(int)this.liveUpdateableOptions.CurrentValue.Height
(int)savedOptions.X,
(int)savedOptions.Y,
(int)savedOptions.Width,
(int)savedOptions.Height
);
if (
@@ -139,9 +148,74 @@ internal sealed class ScreenManager(
);
}
// Adjust position based on DPI scaling changes
var savedDpiX = savedOptions.DpiX > 0 ? savedOptions.DpiX : DefaultDpi;
var savedDpiY = savedOptions.DpiY > 0 ? savedOptions.DpiY : DefaultDpi;
var currentDpi = GetDpiForPosition(savedPosition.Left, savedPosition.Top);
if (savedDpiX != currentDpi.X || savedDpiY != currentDpi.Y)
{
var scaleX = (double)currentDpi.X / savedDpiX;
var scaleY = (double)currentDpi.Y / savedDpiY;
savedPosition = new Rectangle(
(int)(savedPosition.X * scaleX),
(int)(savedPosition.Y * scaleY),
(int)(savedPosition.Width * scaleX),
(int)(savedPosition.Height * scaleY)
);
}
// Validate that the position is within visible screen bounds
savedPosition = this.EnsurePositionIsOnScreen(savedPosition);
return savedPosition;
}
private Rectangle EnsurePositionIsOnScreen(Rectangle position)
{
var screens = this.Screens.ToList();
if (screens.Count == 0)
{
return position;
}
// Check if the window center is within any screen
var centerX = position.Left + (position.Width / 2);
var centerY = position.Top + (position.Height / 2);
var isOnScreen = screens.Any(s =>
centerX >= s.Size.Left && centerX <= s.Size.Right &&
centerY >= s.Size.Top && centerY <= s.Size.Bottom);
if (isOnScreen)
{
return position;
}
// Window is off-screen, move to the first available screen
var firstScreen = screens.First();
return new Rectangle(
firstScreen.Size.X + (firstScreen.Size.Width / 4),
firstScreen.Size.Y + (firstScreen.Size.Height / 4),
Math.Min(position.Width, firstScreen.Size.Width / 2),
Math.Min(position.Height, firstScreen.Size.Height / 2)
);
}
private static Point GetDpiForPosition(int x, int y)
{
var point = new NativeMethods.POINT { X = x, Y = y };
var monitor = NativeMethods.MonitorFromPoint(point, NativeMethods.MONITOR_DEFAULTTONEAREST);
if (monitor != IntPtr.Zero &&
NativeMethods.GetDpiForMonitor(monitor, NativeMethods.MonitorDpiType.MDT_EFFECTIVE_DPI, out var dpiX, out var dpiY) == 0)
{
return new Point(dpiX, dpiY);
}
return new Point(DefaultDpi, DefaultDpi);
}
private static IntPtr? GetMainWindowHandle()
{
var process = Process.GetProcessesByName("gw").FirstOrDefault();
+16
View File
@@ -87,6 +87,14 @@ public static class NativeMethods
DWMWCP_ROUNDSMALL = 3
}
public enum MonitorDpiType
{
MDT_EFFECTIVE_DPI = 0,
MDT_ANGULAR_DPI = 1,
MDT_RAW_DPI = 2,
MDT_DEFAULT = MDT_EFFECTIVE_DPI
}
[Flags]
public enum ThreadAccess : uint
{
@@ -703,4 +711,12 @@ public static class NativeMethods
[DllImport("user32.dll")]
public static extern bool GetMonitorInfo(nint hMonitor, ref MONITORINFO lpmi);
[DllImport("shcore.dll")]
public static extern int GetDpiForMonitor(IntPtr hMonitor, MonitorDpiType dpiType, out int dpiX, out int dpiY);
[DllImport("user32.dll")]
public static extern IntPtr MonitorFromPoint(POINT pt, uint dwFlags);
}