Skip to content

Fix Windows multi-monitor window restore via WINDOWPLACEMENT - #8331

Open
Dvow wants to merge 5 commits into
emilk:mainfrom
Dvow:fix/windows-windowplacement-restore
Open

Fix Windows multi-monitor window restore via WINDOWPLACEMENT#8331
Dvow wants to merge 5 commits into
emilk:mainfrom
Dvow:fix/windows-windowplacement-restore

Conversation

@Dvow

@Dvow Dvow commented Jul 21, 2026

Copy link
Copy Markdown

Summary

Persisting logical window position/size is not enough on Windows. On mixed-DPI and multi-monitor setups, winit-based restore can land on the wrong monitor, grow across restarts, fail to restore maximized state correctly (especially on a secondary display), forget Aero Snap size, and flash an empty frame on first show.

This change saves and restores the native Win32 WINDOWPLACEMENT blob. On create we apply only the restored position, scaled with GetDpiForMonitor for the target rect, and intentionally skip with_inner_size so create DPI and placement DPI cannot compound. Placement is applied twice while the window is still hidden (SW_HIDE) so WM_DPICHANGED can settle before the first paint. Maximized restore additionally sizes to the monitor work area before show.

First show is DWM-cloaked, then revealed around the first present (glow uncloaks in the same frame; wgpu needs one extra redraw), and window-geometry persistence is deferred until after that reveal so mid-restore geometry is never written back. When a restored placement is present, centering and the old clamp/size paths are skipped so they cannot fight Win32 restore.

Aero Snap is handled explicitly: GetWindowPlacement keeps the pre-snap restore rect in rcNormalPosition, so when the window is not min/max and the visible size differs, we store the visible GetWindowRect converted screen→workspace (wxWidgets-style size compare with tolerance) instead.

Non-Windows keeps the existing winit path with a plain set_visible on first show. windows-sys is linked on Windows only.

Persisting logical pos/size breaks on mixed-DPI setups and loses the
true restore rect when maximized or Aero-snapped. Capture Win32
WINDOWPLACEMENT, apply it twice with SW_HIDE before the first paint
(so WM_DPICHANGED settles), create with position only using
GetDpiForMonitor (never with_inner_size), cloak until after the first
present, and defer geometry persist until reveal settles.

When not max/min, prefer the visible GetWindowRect (screen→workspace)
if width/height differs from rcNormalPosition (wxWidgets snap rule),
so Aero Snap size is remembered without DPI jitter creep.

Non-Windows paths stay on winit with a plain set_visible; only Windows
links windows-sys / DWM cloak.
@github-actions

Copy link
Copy Markdown

Preview is being built...

Preview will be available at https://egui-pr-preview.github.io/pr/8331-fix/windows-windowplacement-restore

View snapshot changes at kitdiff

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant