diff --git a/.gitignore b/.gitignore
index a293100e615..9a1728ebbd7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -304,4 +304,5 @@ Output-Performance.txt
# vscode
.vscode
-.history
\ No newline at end of file
+.history
+/.copilot
diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs
index 635a433540f..da8b5cb19d1 100644
--- a/Flow.Launcher.Infrastructure/Win32Helper.cs
+++ b/Flow.Launcher.Infrastructure/Win32Helper.cs
@@ -303,13 +303,15 @@ public static unsafe bool IsForegroundWindowFullscreen()
#region Pixel to DIP
+ private const double DefaultDpi = 96d;
+
///
- /// Transforms pixels to Device Independent Pixels used by WPF
+ /// Transforms pixels to Device Independent Pixels used by WPF.
///
- /// current window, required to get presentation source
- /// horizontal position in pixels
- /// vertical position in pixels
- /// point containing device independent pixels
+ /// Current window, required to get presentation source.
+ /// Horizontal position in pixels.
+ /// Vertical position in pixels.
+ /// Point containing device independent pixels.
public static Point TransformPixelsToDIP(Visual visual, double unitX, double unitY)
{
Matrix matrix;
@@ -324,7 +326,7 @@ public static Point TransformPixelsToDIP(Visual visual, double unitX, double uni
matrix = src.CompositionTarget.TransformFromDevice;
}
- return new Point((int)(matrix.M11 * unitX), (int)(matrix.M22 * unitY));
+ return new Point(matrix.M11 * unitX, matrix.M22 * unitY);
}
#endregion
diff --git a/Flow.Launcher.Plugin/SharedModels/MonitorInfo.cs b/Flow.Launcher.Plugin/SharedModels/MonitorInfo.cs
index 6808c9c9104..c5373232e5a 100644
--- a/Flow.Launcher.Plugin/SharedModels/MonitorInfo.cs
+++ b/Flow.Launcher.Plugin/SharedModels/MonitorInfo.cs
@@ -148,7 +148,7 @@ internal unsafe MonitorInfo(HMONITOR monitor, RECT* rect)
public string Name { get; }
///
- /// Gets the display monitor rectangle, expressed in virtual-screen coordinates.
+ /// Gets the display monitor rectangle, expressed in virtual-screen coordinates and physical pixels.
///
///
/// If the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values.
@@ -156,13 +156,32 @@ internal unsafe MonitorInfo(HMONITOR monitor, RECT* rect)
public Rect Bounds { get; }
///
- /// Gets the work area rectangle of the display monitor, expressed in virtual-screen coordinates.
+ /// Gets the work area rectangle of the display monitor, expressed in virtual-screen coordinates and physical pixels.
///
///
/// If the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values.
///
public Rect WorkingArea { get; }
+ ///
+ /// Transforms physical pixel coordinates on this monitor to WPF device-independent pixels.
+ ///
+ public Point TransformPixelsToDIP(double unitX, double unitY)
+ {
+ var (scaleX, scaleY) = GetDipScale();
+ return new Point(unitX / scaleX, unitY / scaleY);
+ }
+
+ ///
+ /// Transforms a physical pixel rectangle on this monitor to WPF device-independent pixels.
+ ///
+ public Rect TransformPixelsToDIP(Rect rect)
+ {
+ var topLeft = TransformPixelsToDIP(rect.Left, rect.Top);
+ var bottomRight = TransformPixelsToDIP(rect.Right, rect.Bottom);
+ return new Rect(topLeft, bottomRight);
+ }
+
///
/// Gets if the monitor is the primary display monitor.
///
@@ -171,6 +190,24 @@ internal unsafe MonitorInfo(HMONITOR monitor, RECT* rect)
///
public override string ToString() => $"{Name} {Bounds.Width}x{Bounds.Height}";
+ private (double ScaleX, double ScaleY) GetDipScale()
+ {
+ if (GetDpiForMonitor(_monitor, MonitorDpiType.EffectiveDpi, out var dpiX, out var dpiY) == 0 && dpiX != 0 && dpiY != 0)
+ {
+ return (dpiX / 96, dpiY / 96);
+ }
+
+ return (1d, 1d);
+ }
+
+ [DllImport("Shcore.dll")]
+ private static extern int GetDpiForMonitor(HMONITOR hmonitor, MonitorDpiType dpiType, out uint dpiX, out uint dpiY);
+
+ private enum MonitorDpiType
+ {
+ EffectiveDpi = 0,
+ }
+
private static unsafe bool GetMonitorInfo(HMONITOR hMonitor, ref MONITORINFOEXW lpmi)
{
fixed (MONITORINFOEXW* lpmiLocal = &lpmi)
diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs
index 60517db9736..e09f9fbb309 100644
--- a/Flow.Launcher/MainWindow.xaml.cs
+++ b/Flow.Launcher/MainWindow.xaml.cs
@@ -395,8 +395,7 @@ private void OnLocationChanged(object sender, EventArgs e)
if (IsLoaded)
{
- _settings.WindowLeft = Left;
- _settings.WindowTop = Top;
+ SaveWindowPositionAndDisplayMetrics();
}
}
@@ -407,8 +406,7 @@ private async void OnDeactivated(object sender, EventArgs e)
return;
}
- _settings.WindowLeft = Left;
- _settings.WindowTop = Top;
+ SaveWindowPositionAndDisplayMetrics();
_viewModel.ClockPanelOpacity = 0.0;
_viewModel.SearchIconOpacity = 0.0;
@@ -537,7 +535,7 @@ private void OnMouseDown(object sender, MouseButtonEventArgs e)
// Current monitor information
var screen = MonitorInfo.GetNearestDisplayMonitor(new WindowInteropHelper(this).Handle);
var workingArea = screen.WorkingArea;
- var screenLeftTop = Win32Helper.TransformPixelsToDIP(this, workingArea.X, workingArea.Y);
+ var screenLeftTop = screen.TransformPixelsToDIP(workingArea.X, workingArea.Y);
// Switch to Normal state
WindowState = WindowState.Normal;
@@ -897,10 +895,8 @@ void InitializePositionInner()
{
var previousScreenWidth = _settings.PreviousScreenWidth;
var previousScreenHeight = _settings.PreviousScreenHeight;
- GetDpi(out var previousDpiX, out var previousDpiY);
-
- _settings.PreviousScreenWidth = SystemParameters.VirtualScreenWidth;
- _settings.PreviousScreenHeight = SystemParameters.VirtualScreenHeight;
+ var previousDpiX = _settings.PreviousDpiX;
+ var previousDpiY = _settings.PreviousDpiY;
GetDpi(out var currentDpiX, out var currentDpiY);
if (previousScreenWidth != 0 && previousScreenHeight != 0 &&
@@ -910,11 +906,13 @@ void InitializePositionInner()
previousDpiX != currentDpiX || previousDpiY != currentDpiY))
{
AdjustPositionForResolutionChange();
- return;
}
-
- Left = _settings.WindowLeft;
- Top = _settings.WindowTop;
+ else
+ {
+ Left = _settings.WindowLeft;
+ Top = _settings.WindowTop;
+ }
+ SaveCurrentDisplayMetrics(currentDpiX, currentDpiY);
}
else
{
@@ -938,12 +936,11 @@ void InitializePositionInner()
Top = VerticalTop(screen);
break;
case SearchWindowAligns.Custom:
- var customLeft = Win32Helper.TransformPixelsToDIP(this,
- screen.WorkingArea.X + _settings.CustomWindowLeft, 0);
- var customTop = Win32Helper.TransformPixelsToDIP(this, 0,
+ var customPosition = screen.TransformPixelsToDIP(
+ screen.WorkingArea.X + _settings.CustomWindowLeft,
screen.WorkingArea.Y + _settings.CustomWindowTop);
- Left = customLeft.X;
- Top = customTop.Y;
+ Left = customPosition.X;
+ Top = customPosition.Y;
break;
}
}
@@ -954,19 +951,15 @@ private void AdjustPositionForResolutionChange()
{
var screenWidth = SystemParameters.VirtualScreenWidth;
var screenHeight = SystemParameters.VirtualScreenHeight;
- GetDpi(out var currentDpiX, out var currentDpiY);
var previousLeft = _settings.WindowLeft;
var previousTop = _settings.WindowTop;
- GetDpi(out var previousDpiX, out var previousDpiY);
var widthRatio = screenWidth / _settings.PreviousScreenWidth;
var heightRatio = screenHeight / _settings.PreviousScreenHeight;
- var dpiXRatio = currentDpiX / previousDpiX;
- var dpiYRatio = currentDpiY / previousDpiY;
- var newLeft = previousLeft * widthRatio * dpiXRatio;
- var newTop = previousTop * heightRatio * dpiYRatio;
+ var newLeft = previousLeft * widthRatio;
+ var newTop = previousTop * heightRatio;
var screenLeft = SystemParameters.VirtualScreenLeft;
var screenTop = SystemParameters.VirtualScreenTop;
@@ -994,6 +987,22 @@ private void GetDpi(out double dpiX, out double dpiY)
}
}
+ private void SaveWindowPositionAndDisplayMetrics()
+ {
+ _settings.WindowLeft = Left;
+ _settings.WindowTop = Top;
+ GetDpi(out var dpiX, out var dpiY);
+ SaveCurrentDisplayMetrics(dpiX, dpiY);
+ }
+
+ private void SaveCurrentDisplayMetrics(double dpiX, double dpiY)
+ {
+ _settings.PreviousScreenWidth = SystemParameters.VirtualScreenWidth;
+ _settings.PreviousScreenHeight = SystemParameters.VirtualScreenHeight;
+ _settings.PreviousDpiX = dpiX;
+ _settings.PreviousDpiY = dpiY;
+ }
+
private MonitorInfo SelectedScreen()
{
MonitorInfo screen;
@@ -1025,38 +1034,38 @@ private MonitorInfo SelectedScreen()
private double HorizonCenter(MonitorInfo screen)
{
- var dip1 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
- var dip2 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0);
+ var dip1 = screen.TransformPixelsToDIP(screen.WorkingArea.X, 0);
+ var dip2 = screen.TransformPixelsToDIP(screen.WorkingArea.Width, 0);
var left = (dip2.X - ActualWidth) / 2 + dip1.X;
return left;
}
private double VerticalCenter(MonitorInfo screen)
{
- var dip1 = Win32Helper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
- var dip2 = Win32Helper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height);
+ var dip1 = screen.TransformPixelsToDIP(0, screen.WorkingArea.Y);
+ var dip2 = screen.TransformPixelsToDIP(0, screen.WorkingArea.Height);
var top = (dip2.Y - QueryTextBox.ActualHeight) / 4 + dip1.Y;
return top;
}
private double HorizonRight(MonitorInfo screen)
{
- var dip1 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
- var dip2 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0);
+ var dip1 = screen.TransformPixelsToDIP(screen.WorkingArea.X, 0);
+ var dip2 = screen.TransformPixelsToDIP(screen.WorkingArea.Width, 0);
var left = (dip1.X + dip2.X - ActualWidth) - 10;
return left;
}
private double HorizonLeft(MonitorInfo screen)
{
- var dip1 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
+ var dip1 = screen.TransformPixelsToDIP(screen.WorkingArea.X, 0);
var left = dip1.X + 10;
return left;
}
private double VerticalTop(MonitorInfo screen)
{
- var dip1 = Win32Helper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
+ var dip1 = screen.TransformPixelsToDIP(0, screen.WorkingArea.Y);
var top = dip1.Y + 10;
return top;
}
@@ -1185,7 +1194,7 @@ private void WindowAnimation()
iconsb.Children.Add(IconMotion);
iconsb.Children.Add(IconOpacity);
- _settings.WindowLeft = Left;
+ SaveWindowPositionAndDisplayMetrics();
_isArrowKeyPressed = false;
clocksb.Begin(ClockPanel);
@@ -1449,23 +1458,23 @@ private void InitializeDialogJumpPosition()
var result = Win32Helper.GetWindowRect(_viewModel.DialogWindowHandle, out var window);
if (!result) return;
+ var monitor = MonitorInfo.GetNearestDisplayMonitor(_viewModel.DialogWindowHandle);
+ var dipWindow = monitor.TransformPixelsToDIP(window);
+
// Move window below the bottom of the dialog and keep it center
- Top = VerticalBottom(window);
- Left = HorizonCenter(window);
+ Top = VerticalBottom(dipWindow);
+ Left = HorizonCenter(dipWindow);
}
private double HorizonCenter(Rect window)
{
- var dip1 = Win32Helper.TransformPixelsToDIP(this, window.X, 0);
- var dip2 = Win32Helper.TransformPixelsToDIP(this, window.Width, 0);
- var left = (dip2.X - ActualWidth) / 2 + dip1.X;
+ var left = (window.Width - ActualWidth) / 2 + window.X;
return left;
}
private double VerticalBottom(Rect window)
{
- var dip1 = Win32Helper.TransformPixelsToDIP(this, 0, window.Bottom);
- return dip1.Y;
+ return window.Bottom;
}
#endregion
diff --git a/Flow.Launcher/SettingWindow.xaml.cs b/Flow.Launcher/SettingWindow.xaml.cs
index 054c60c8a7e..f15fd73e08b 100644
--- a/Flow.Launcher/SettingWindow.xaml.cs
+++ b/Flow.Launcher/SettingWindow.xaml.cs
@@ -179,8 +179,8 @@ private void SetWindowPosition(double top, double left)
// Ensure window does not exceed screen boundaries
top = Math.Max(top, SystemParameters.VirtualScreenTop);
left = Math.Max(left, SystemParameters.VirtualScreenLeft);
- top = Math.Min(top, SystemParameters.VirtualScreenHeight - ActualHeight);
- left = Math.Min(left, SystemParameters.VirtualScreenWidth - ActualWidth);
+ top = Math.Min(top, SystemParameters.VirtualScreenTop + SystemParameters.VirtualScreenHeight - ActualHeight);
+ left = Math.Min(left, SystemParameters.VirtualScreenLeft + SystemParameters.VirtualScreenWidth - ActualWidth);
Top = top;
Left = left;
@@ -191,30 +191,26 @@ private void AdjustWindowPosition(ref double top, ref double left)
// Adjust window position if it exceeds screen boundaries
top = Math.Max(top, SystemParameters.VirtualScreenTop);
left = Math.Max(left, SystemParameters.VirtualScreenLeft);
- top = Math.Min(top, SystemParameters.VirtualScreenHeight - ActualHeight);
- left = Math.Min(left, SystemParameters.VirtualScreenWidth - ActualWidth);
+ top = Math.Min(top, SystemParameters.VirtualScreenTop + SystemParameters.VirtualScreenHeight - ActualHeight);
+ left = Math.Min(left, SystemParameters.VirtualScreenLeft + SystemParameters.VirtualScreenWidth - ActualWidth);
}
private static bool IsPositionValid(double top, double left)
{
- foreach (var screen in MonitorInfo.GetDisplayMonitors())
- {
- var workingArea = screen.WorkingArea;
-
- if (left >= workingArea.Left && left < workingArea.Right &&
- top >= workingArea.Top && top < workingArea.Bottom)
- {
- return true;
- }
- }
- return false;
+ // Use SystemParameters (DIP units) to match the coordinate system of Window.Top/Left.
+ // MonitorInfo.WorkingArea uses physical pixels which can differ from DIP units when DPI
+ // scaling is active, leading to incorrect results on high-DPI or mixed-DPI setups.
+ return left >= SystemParameters.VirtualScreenLeft &&
+ left < SystemParameters.VirtualScreenLeft + SystemParameters.VirtualScreenWidth &&
+ top >= SystemParameters.VirtualScreenTop &&
+ top < SystemParameters.VirtualScreenTop + SystemParameters.VirtualScreenHeight;
}
private double WindowLeft()
{
var screen = MonitorInfo.GetCursorDisplayMonitor();
- var dip1 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
- var dip2 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0);
+ var dip1 = screen.TransformPixelsToDIP(screen.WorkingArea.X, 0);
+ var dip2 = screen.TransformPixelsToDIP(screen.WorkingArea.Width, 0);
var left = (dip2.X - ActualWidth) / 2 + dip1.X;
return left;
}
@@ -222,8 +218,8 @@ private double WindowLeft()
private double WindowTop()
{
var screen = MonitorInfo.GetCursorDisplayMonitor();
- var dip1 = Win32Helper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
- var dip2 = Win32Helper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height);
+ var dip1 = screen.TransformPixelsToDIP(0, screen.WorkingArea.Y);
+ var dip2 = screen.TransformPixelsToDIP(0, screen.WorkingArea.Height);
var top = (dip2.Y - ActualHeight) / 2 + dip1.Y - 20;
return top;
}