diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp index 519650177..05cd9799e 100644 --- a/windows/runner/flutter_window.cpp +++ b/windows/runner/flutter_window.cpp @@ -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(&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;