Skip to content

Commit

Permalink
[PTRun]Fixed unstable startup position after moving to PerMonitorV2 (m…
Browse files Browse the repository at this point in the history
…icrosoft#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.
  • Loading branch information
drawbyperpetual authored Jul 18, 2024
1 parent 7d8af7b commit 78d53ff
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 45 deletions.
36 changes: 17 additions & 19 deletions src/modules/launcher/PowerLauncher/Helper/WindowsInteropHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using System.Windows.Forms;
using System.Windows.Interop;
using System.Windows.Media;
using Point = System.Windows.Point;

namespace PowerLauncher.Helper
{
Expand Down Expand Up @@ -188,30 +187,29 @@ internal static void SetToolWindowStyle(Window win)
_ = NativeMethods.SetWindowLong(hwnd, GWL_EX_STYLE, NativeMethods.GetWindowLong(hwnd, GWL_EX_STYLE) | WS_EX_TOOLWINDOW);
}

/// <summary>
/// Transforms pixels to Device Independent Pixels used by WPF
/// </summary>
/// <param name="visual">current window, required to get presentation source</param>
/// <param name="unitX">horizontal position in pixels</param>
/// <param name="unitY">vertical position in pixels</param>
/// <returns>point containing device independent pixels</returns>
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)]
Expand Down
50 changes: 24 additions & 26 deletions src/modules/launcher/PowerLauncher/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,8 @@ private void OnMouseDown(object sender, MouseButtonEventArgs e)

private void InitializePosition()
{
Top = WindowTop();
Left = WindowLeft();
MoveToDesiredPosition();

_settings.WindowTop = Top;
_settings.WindowLeft = Left;
}
Expand All @@ -434,11 +434,31 @@ private void UpdatePosition()
}
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)
Expand All @@ -448,28 +468,6 @@ private void OnLocationChanged(object sender, EventArgs e)
}
}

/// <summary>
/// Calculates X co-ordinate of main window top left corner.
/// </summary>
/// <returns>X co-ordinate of main window top left corner</returns>
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;
Expand Down

0 comments on commit 78d53ff

Please sign in to comment.