Skip to content

[Common][PowerDisplay][QuickAccess] Shared flyout positioning helper#47097

Merged
moooyo merged 3 commits intomainfrom
fix/flyout-positioning-helper
Apr 21, 2026
Merged

[Common][PowerDisplay][QuickAccess] Shared flyout positioning helper#47097
moooyo merged 3 commits intomainfrom
fix/flyout-positioning-helper

Conversation

@niels9001
Copy link
Copy Markdown
Collaborator

Summary

Introduces a shared FlyoutWindowHelper in Common.UI.Controls and migrates both PowerDisplay and QuickAccess to it, eliminating two pre-existing flyout positioning bugs and removing duplicated math.

Bugs fixed

1. PowerDisplay flyout overlapped the taskbar at 100% scaling

The previous PowerDisplay-only positioning math anchored to the screen bounds rather than DisplayArea.WorkArea on certain monitor configurations, so the bottom edge of the flyout could land on top of the taskbar.

2. QuickAccess flyout rendered too large / partially off-screen after switching DPI between 150% and 100%

The previous code passed WindowEx.Width/Height into MoveAndResize on every summon. Those properties are not the XAML literals — they are computed live as AppWindow.Size / GetDpiForWindow() * 96. After a system-scaling switch, the runtime size has drifted, that wrong "DIP" value got fed into MoveAndResize, and the destination DPI multiplier scaled it again → wrong size, and the wrong size shifted the bottom-right anchor off-screen.

QuickAccess now caches the XAML design size once at construction (when the values are still trustworthy) and uses the cache as the source of truth.

How the helper works

  • Uses absolute screen coordinates against DisplayArea.WorkArea, so it handles non-primary and negatively-positioned monitors correctly.
  • Performs a 1×1 MoveAndResize "teleport" onto the target display before the final visible-size call. The 1×1 jump may cross a DPI boundary, but it's invisible; the second call sets the real size while the window is already on the destination monitor, so no DPI boundary is crossed for the rendered size and WM_DPICHANGED never fires on a visible window.
  • Exposes overloads for bottom-right anchoring (both flyouts) and centered placement (PowerDisplay's IdentifyWindow).

This teleport-then-size approach matches the technique the original Settings.UI flyout used for years before it was removed.

Cleanup

  • Deletes the PowerDisplay-only DpiSuppressor — its WM_DPICHANGED-suppression code path is now dead because the helper sidesteps the message entirely.
  • The DpiSuppressor class also doubled as a generic WndProc subclass to route WM_HOTKEY into HotkeyService. That piece is preserved as WindowMessageHook in Common.UI.Controls/Window/ since PowerDisplay still needs in-process hotkey handling.

Validation

  • Common.UI.Controls, PowerDisplay, and QuickAccess build clean (x64/Debug).
  • Manual repro:
    • PowerDisplay flyout no longer overlaps taskbar at 100% scaling, on multiple invocations.
    • QuickAccess renders at the correct size and position when switching system scaling between 150% and 100%.
    • PowerDisplay hotkey toggle still works after the DpiSuppressorWindowMessageHook rename.

Introduces FlyoutWindowHelper in Common.UI.Controls and migrates both
PowerDisplay and QuickAccess to it. Fixes two pre-existing flyout bugs:

1. PowerDisplay flyout could overlap the taskbar at 100% scaling, because
   the previous PowerDisplay-only positioning math anchored to the screen
   bounds rather than the work area on certain monitor configurations.

2. QuickAccess flyout rendered with the wrong physical size (and ended up
   partially off-screen) after switching system scaling between 150% and
   100%. The previous code passed WindowEx.Width/Height into MoveAndResize
   on every summon; those properties are computed live from the current
   physical size and current DPI, so they drift across DPI transitions
   and were then double-scaled by MoveAndResize. The window now caches the
   XAML design size once at construction and uses that as the source of
   truth.

The shared helper:
- Uses absolute screen coordinates against DisplayArea.WorkArea so it
  handles non-primary and negatively-positioned monitors correctly.
- Performs a 1x1 'teleport' onto the target display before the final
  MoveAndResize call. This avoids crossing a DPI boundary during the
  visible-size call, so WM_DPICHANGED never fires on the rendered window
  and the previous WndProc-subclass workaround is no longer required.
- Exposes overloads for bottom-right anchoring (used by both flyouts)
  and centered placement (used by PowerDisplay's IdentifyWindow).

Cleanup:
- Removes the PowerDisplay-only DpiSuppressor (the DPI-suppression code
  path is now dead). The WndProc-subclass piece that routed WM_HOTKEY
  into HotkeyService is preserved as a generic WindowMessageHook in
  Common.UI.Controls/Window/, since PowerDisplay still needs in-process
  hotkey handling.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@niels9001 niels9001 added the 0.99 label Apr 19, 2026
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions
Copy link
Copy Markdown

@check-spelling-bot Report

🔴 Please review

See the 📂 files view, the 📜action log, 👼 SARIF report, or 📝 job summary for details.

Unrecognized words (1)

DEFAULTTONEAREST

These words are not needed and should be removed defaulttonearest diu IPREVIEW ITHUMBNAIL LPCFHOOKPROC LUMA MAXDWORD MRT suntimes timespan traies udit VSync

To accept these unrecognized words as correct and remove the previously acknowledged and now absent words, you could run the following commands

... in a clone of the git@github.com:microsoft/PowerToys.git repository
on the fix/flyout-positioning-helper branch (ℹ️ how do I use this?):

curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c/apply.pl' |
perl - 'https://github.com/microsoft/PowerToys/actions/runs/24668186258/attempts/1' &&
git commit -m 'Update check-spelling metadata'

OR

To have the bot accept them for you, comment in the PR quoting the following line:
@check-spelling-bot apply updates.

If the flagged items are 🤯 false positives

If items relate to a ...

  • binary file (or some other file you wouldn't want to check at all).

    Please add a file path to the excludes.txt file matching the containing file.

    File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

    ^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

  • well-formed pattern.

    If you can write a pattern that would match it,
    try adding it to the patterns.txt file.

    Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

    Note that patterns can't match multiline strings.

Copy link
Copy Markdown
Contributor

@moooyo moooyo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, tested on my local env.

@moooyo moooyo merged commit bf00c1b into main Apr 21, 2026
15 checks passed
@LegendaryBlair LegendaryBlair added the Product-Display management Refers to the idea of a display management utility power toy label Apr 21, 2026
@LegendaryBlair LegendaryBlair added this to the PowerToys 0.99 milestone Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

0.99 Product-Display management Refers to the idea of a display management utility power toy

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants