From 59cb250e99fc90d376f5f7cc73aaec656e8830b1 Mon Sep 17 00:00:00 2001 From: Mega Mind <146339422+M3gA-Mind@users.noreply.github.com> Date: Mon, 13 Jul 2026 21:39:08 +0530 Subject: [PATCH] fix(window): persist window geometry on normal quit, not just restart (#4810) (#4817) --- app/src-tauri/src/lib.rs | 25 +++++++++++++++++++++++++ app/src-tauri/src/window_state.rs | 22 ++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/app/src-tauri/src/lib.rs b/app/src-tauri/src/lib.rs index ddbebc220..a0d2094e3 100644 --- a/app/src-tauri/src/lib.rs +++ b/app/src-tauri/src/lib.rs @@ -3946,6 +3946,19 @@ pub fn run() { "[window] close requested on main window — hiding to tray" ); api.prevent_close(); + // Persist geometry now, while the window handle is still + // reachable. On Windows the hide below is a raw SW_HIDE on the + // OS frame, after which `get_webview_window("main")` returns + // `None` until the window is shown again (#1607). If the user + // then picks tray "Quit" while hidden, the ExitRequested save + // finds no window and nothing is persisted, so the next launch + // falls back to the default geometry (#4810). Saving here + // captures the last on-screen size/position before it becomes + // unreachable; ExitRequested still saves for the shown-window + // quit paths (`save_main` is best-effort and idempotent). + if let Some(window) = app_handle.get_webview_window("main") { + window_state::save_main(&window); + } // Hide the OS top-level Chrome_WidgetWin_1 frame via // EnumWindows + SW_HIDE — full hide-to-tray as PR #1548 // intended. `window.hide()` and `window.minimize()` through @@ -3966,6 +3979,18 @@ pub fn run() { } } RunEvent::ExitRequested { .. } => { + // Persist the main window's geometry on every clean quit + // (Cmd+Q, tray "Quit", dock quit, or the frontend + // `app_quit` command) so the next launch restores the + // user's size + position. Previously `save_main` ran only + // on the identity-flip `restart_app` path (#900): a normal + // quit never saved, so the window always reopened at the + // default small centered size (#4810). `save_main` is + // best-effort and idempotent — safe to also run here even + // though `restart_app` saves before it triggers exit. + if let Some(window) = app_handle.get_webview_window("main") { + window_state::save_main(&window); + } // Run our cleanup BEFORE CEF's own Exit handler does // `close_all_windows() → cef::shutdown()`. Doing this in // RunEvent::Exit instead races CEF's teardown and the diff --git a/app/src-tauri/src/window_state.rs b/app/src-tauri/src/window_state.rs index f3494110e..462daf211 100644 --- a/app/src-tauri/src/window_state.rs +++ b/app/src-tauri/src/window_state.rs @@ -378,6 +378,28 @@ fn clamp_to_work_area( mod tests { use super::*; + #[test] + fn window_state_toml_roundtrips() { + // `save_main` serializes this record to `window_state.toml` and + // `restore_main` parses it back on the next launch. The whole + // save-on-quit → restore-on-launch feature (#4810) hinges on this + // record surviving the round trip byte-for-byte, including a + // negative x from a monitor left of the primary. Guards the + // on-disk contract against an accidental serde/rename change. + let state = WindowState { + x: -1920, + y: 100, + width: 1280, + height: 800, + }; + let raw = toml::to_string_pretty(&state).expect("serialize"); + let parsed: WindowState = toml::from_str(&raw).expect("parse"); + assert_eq!( + (parsed.x, parsed.y, parsed.width, parsed.height), + (state.x, state.y, state.width, state.height) + ); + } + fn wa(x: i32, y: i32, width: u32, height: u32) -> WorkArea { WorkArea { x,