From 78d53ffb105108c9b6bcdde84dd1ef5272f62197 Mon Sep 17 00:00:00 2001 From: Ani <115020168+drawbyperpetual@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:16:19 +0200 Subject: [PATCH] [PTRun]Fixed unstable startup position after moving to PerMonitorV2 (#33784) ## Summary of the Pull Request Fixes an issue where PowerToys Run can sometimes start up in an inconvenient position in a multi-monitor / multi-DPI setup. --- .../Helper/WindowsInteropHelper.cs | 36 +++++++------ .../launcher/PowerLauncher/MainWindow.xaml.cs | 50 +++++++++---------- 2 files changed, 41 insertions(+), 45 deletions(-) diff --git a/src/modules/launcher/PowerLauncher/Helper/WindowsInteropHelper.cs b/src/modules/launcher/PowerLauncher/Helper/WindowsInteropHelper.cs index 612476daff..1832bbbb8b 100644 --- a/src/modules/launcher/PowerLauncher/Helper/WindowsInteropHelper.cs +++ b/src/modules/launcher/PowerLauncher/Helper/WindowsInteropHelper.cs @@ -10,7 +10,6 @@ using System.Windows; using System.Windows.Forms; using System.Windows.Interop; using System.Windows.Media; -using Point = System.Windows.Point; namespace PowerLauncher.Helper { @@ -188,30 +187,29 @@ namespace PowerLauncher.Helper _ = NativeMethods.SetWindowLong(hwnd, GWL_EX_STYLE, NativeMethods.GetWindowLong(hwnd, GWL_EX_STYLE) | WS_EX_TOOLWINDOW); } - /// - /// 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 - public static Point TransformPixelsToDIP(Visual visual, double unitX, double unitY) + public static void MoveToScreenCenter(Window window, Screen screen) { - Matrix matrix; - var source = PresentationSource.FromVisual(visual); - if (source != null) + var workingArea = screen.WorkingArea; + var matrix = GetCompositionTarget(window).TransformFromDevice; + var dpiX = matrix.M11; + var dpiY = matrix.M22; + + window.Left = (dpiX * workingArea.Left) + (((dpiX * workingArea.Width) - window.Width) / 2); + window.Top = (dpiY * workingArea.Top) + (((dpiY * workingArea.Height) - window.Height) / 2); + } + + private static CompositionTarget GetCompositionTarget(Visual visual) + { + var presentationSource = PresentationSource.FromVisual(visual); + if (presentationSource != null) { - matrix = source.CompositionTarget.TransformFromDevice; + return presentationSource.CompositionTarget; } else { - using (var src = new HwndSource(default)) - { - matrix = src.CompositionTarget.TransformFromDevice; - } + using var hwndSource = new HwndSource(default); + return hwndSource.CompositionTarget; } - - return new Point((int)(matrix.M11 * unitX), (int)(matrix.M22 * unitY)); } [StructLayout(LayoutKind.Sequential)] diff --git a/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs b/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs index 2fa2699953..600af8dc6d 100644 --- a/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs +++ b/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs @@ -411,8 +411,8 @@ namespace PowerLauncher private void InitializePosition() { - Top = WindowTop(); - Left = WindowLeft(); + MoveToDesiredPosition(); + _settings.WindowTop = Top; _settings.WindowLeft = Left; } @@ -434,11 +434,31 @@ namespace PowerLauncher } else { - Top = WindowTop(); - Left = WindowLeft(); + MoveToDesiredPosition(); } } + private void MoveToDesiredPosition() + { + // Hack: After switching to PerMonitorV2, this operation seems to require a three-step operation + // to ensure a stable position: First move to top-left of desired screen, then centralize twice. + // More straightforward ways of doing this don't seem to work well for unclear reasons, but possibly related to + // https://github.com/dotnet/wpf/issues/4127 + // In any case, there does not seem to be any big practical downside to doing it this way. As a bonus, it can be + // done in pure WPF without any native calls and without too much DPI-based fiddling. + // In terms of the hack itself, removing any of these three steps seems to fail in certain scenarios only, + // so be careful with testing! + var desiredScreen = GetScreen(); + + // Move to top-left of desired screen. + Top = desiredScreen.WorkingArea.Top; + Left = desiredScreen.WorkingArea.Left; + + // Centralize twice. + WindowsInteropHelper.MoveToScreenCenter(this, desiredScreen); + WindowsInteropHelper.MoveToScreenCenter(this, desiredScreen); + } + private void OnLocationChanged(object sender, EventArgs e) { if (_settings.RememberLastLaunchLocation) @@ -448,28 +468,6 @@ namespace PowerLauncher } } - /// - /// Calculates X co-ordinate of main window top left corner. - /// - /// X co-ordinate of main window top left corner - private double WindowLeft() - { - var screen = GetScreen(); - var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); - var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0); - var left = ((dip2.X - ActualWidth) / 2) + dip1.X; - return left; - } - - private double WindowTop() - { - var screen = GetScreen(); - var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y); - var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height); - var top = ((dip2.Y - SearchBox.ActualHeight) / 4) + dip1.Y; - return top; - } - private Screen GetScreen() { ManagedCommon.StartupPosition position = _settings.StartupPosition;