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:
@@ -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, ¤t);
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user