fix(windows): restore window placement onto a live monitor without a blank flash

The remembered window placement restored invisible when its monitor was
gone (undocked laptop, powered-off TV), briefly flashed a blank window at
the restored spot before the first Flutter frame, and could lose the
maximized state: the exit path hides the window before a multi-second
teardown, so a debounced save landing in that gap recorded SW_HIDE, and a
window closed while minimized-from-maximized restored as a normal window.

Validate the saved rect against current monitors and keep only the size on
a miss, apply the placement with SW_HIDE so the first-frame callback stays
the single show, skip persisting while the window is hidden, and honor
WPF_RESTORETOMAXIMIZED when deciding to relaunch maximized.
This commit is contained in:
edde746
2026-08-23 10:57:42 +02:00
parent b4b36c7e18
commit f2bf43a8bc
+37 -6
View File
@@ -35,12 +35,19 @@ static void WriteWindowPlacement(const WINDOWPLACEMENT& wp) {
}
static void SaveWindowPlacement(HWND hwnd) {
// Never persist a hidden window: the exit path hides the window before its
// multi-second teardown, and a save landing in that gap would record
// SW_HIDE over the user's real show state.
if (!IsWindowVisible(hwnd)) return;
WINDOWPLACEMENT wp{};
wp.length = sizeof(wp);
if (!GetWindowPlacement(hwnd, &wp)) return;
WriteWindowPlacement(wp);
}
// Loads the saved WINDOWPLACEMENT and applies it while keeping the window
// hidden; the first-frame callback in OnCreate performs the single show.
// Returns whether the window should be shown maximized.
static bool LoadWindowPlacement(HWND hwnd) {
HKEY hKey;
if (RegOpenKeyExW(HKEY_CURRENT_USER, kWindowPlacementKey, 0, KEY_READ, &hKey) != ERROR_SUCCESS) return false;
@@ -53,10 +60,34 @@ static bool LoadWindowPlacement(HWND hwnd) {
if (RegQueryValueExW(hKey, kWindowPlacementValue, nullptr, nullptr, reinterpret_cast<BYTE*>(&wp), &size) ==
ERROR_SUCCESS &&
size == sizeof(wp)) {
// Prevent restoring as minimized
if (wp.showCmd == SW_SHOWMINIMIZED) wp.showCmd = SW_SHOWNORMAL;
// A window minimized away from the maximized state stores SW_SHOWMINIMIZED
// plus WPF_RESTORETOMAXIMIZED; both spellings mean "maximized" on relaunch.
wasMaximized = wp.showCmd == SW_SHOWMAXIMIZED || (wp.flags & WPF_RESTORETOMAXIMIZED) != 0;
// The saved monitor may be gone (undocked laptop, powered-off TV).
// rcNormalPosition is in workspace coordinates — screen coordinates offset
// by the primary work area — so convert before testing against monitors.
// On a miss, keep the size but fall back to the default creation position
// so the window never restores invisible.
RECT workArea{};
SystemParametersInfoW(SPI_GETWORKAREA, 0, &workArea, 0);
RECT screenRect = wp.rcNormalPosition;
OffsetRect(&screenRect, workArea.left, workArea.top);
if (MonitorFromRect(&screenRect, MONITOR_DEFAULTTONULL) == nullptr) {
RECT current{};
GetWindowRect(hwnd, &current);
const LONG width = wp.rcNormalPosition.right - wp.rcNormalPosition.left;
const LONG height = wp.rcNormalPosition.bottom - wp.rcNormalPosition.top;
wp.rcNormalPosition.left = current.left - workArea.left;
wp.rcNormalPosition.top = current.top - workArea.top;
wp.rcNormalPosition.right = wp.rcNormalPosition.left + width;
wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + height;
}
// Apply hidden. A visible showCmd here would display a blank window at
// the restored spot before Flutter has rendered anything.
wp.showCmd = SW_HIDE;
SetWindowPlacement(hwnd, &wp);
wasMaximized = (wp.showCmd == SW_SHOWMAXIMIZED);
}
RegCloseKey(hKey);
@@ -155,9 +186,9 @@ FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam
flutter_controller_->engine()->ReloadSystemFonts();
break;
case WM_WINDOWPOSCHANGED:
// Don't persist placement while in fullscreen or mid-toggle — the rect
// would overwrite the user's real window position.
if (!is_fullscreen_ && !g_suppressPlacementSave) {
// Don't persist placement while fullscreen, mid-toggle, or hidden — the
// rect would overwrite the user's real window position or show state.
if (!is_fullscreen_ && !g_suppressPlacementSave && IsWindowVisible(hwnd)) {
DebounceSaveWindowPlacement(hwnd);
}
break;