From 8a6aaa73ef1e2117f483f2a0a36d7f4c0edcb1e4 Mon Sep 17 00:00:00 2001 From: John Tur Date: Mon, 24 Aug 2026 02:59:15 +0000 Subject: [PATCH] gpui: Support Windows Restart Manager shutdown (#62987) Handle the `WM_QUERYENDSESSION` and `WM_ENDSESSION` messages, which are used by Windows when shutting down an application. For example, Restart Manager uses this mechanism to gracefully close applications (e.g. when installing an update to the application). Release Notes: - N/A --- crates/crashes/src/crashes.rs | 11 ++++++++ crates/gpui/src/app.rs | 15 ++++++++-- crates/gpui/src/platform.rs | 2 +- crates/gpui/src/platform/test/platform.rs | 2 +- crates/gpui/src/platform/visual_test.rs | 2 +- crates/gpui_linux/src/linux/platform.rs | 4 +-- crates/gpui_macos/src/platform.rs | 4 +-- crates/gpui_web/src/platform.rs | 4 +-- crates/gpui_windows/src/events.rs | 17 ++++++++++++ crates/gpui_windows/src/platform.rs | 34 +++++++++++++++++++---- 10 files changed, 79 insertions(+), 16 deletions(-) diff --git a/crates/crashes/src/crashes.rs b/crates/crashes/src/crashes.rs index 5d3fc954d3..959c676b4e 100644 --- a/crates/crashes/src/crashes.rs +++ b/crates/crashes/src/crashes.rs @@ -182,6 +182,7 @@ pub struct CrashServer { active_gpu: Mutex>, user_info: Mutex>, abort_message_location: Mutex>, + shutdown: Arc, has_connection: Arc, logs_dir: PathBuf, } @@ -255,6 +256,11 @@ pub fn set_user_info(crash_client: &Arc, info: UserInfo) { send_crash_server_message(crash_client, CrashServerMessage::UserInfo(info)); } +/// Requests an orderly exit from the crash-handler sidecar. +pub fn shutdown_crash_handler(crash_client: &Arc) { + send_crash_server_message(crash_client, CrashServerMessage::Shutdown); +} + #[derive(Serialize, Deserialize, Debug)] enum CrashServerMessage { Init(InitCrashHandler), @@ -262,6 +268,7 @@ enum CrashServerMessage { GPUInfo(GpuSpecs), UserInfo(UserInfo), AbortMessageLocation(AbortMessageLocation), + Shutdown, } /// glibc records the diagnostic it prints just before aborting (malloc integrity @@ -464,6 +471,9 @@ impl minidumper::ServerHandler for CrashServer { CrashServerMessage::AbortMessageLocation(location) => { self.abort_message_location.lock().replace(location); } + CrashServerMessage::Shutdown => { + self.shutdown.store(true, Ordering::SeqCst); + } } } @@ -668,6 +678,7 @@ pub fn crash_server(socket: &Path, logs_dir: PathBuf) { panic_info: Mutex::default(), user_info: Mutex::default(), abort_message_location: Mutex::default(), + shutdown: shutdown.clone(), has_connection, active_gpu: Mutex::default(), logs_dir, diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 08f4be87f5..0c58c55e8a 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -918,8 +918,19 @@ impl App { platform.on_quit(Box::new({ let cx = Rc::downgrade(&app); move || { - if let Some(cx) = cx.upgrade() { - cx.borrow_mut().shutdown(); + let Some(cx) = cx.upgrade() else { + return true; + }; + match cx.try_borrow_mut() { + Ok(mut cx) => { + cx.shutdown(); + true + } + Err(_) => { + // Quit was requested while the AppCell was borrowed, so we can't shut down synchronously. + // The platform decides how to proceed. + false + } } } })); diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index ab4b500825..bdafe8cf56 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -200,7 +200,7 @@ pub trait Platform: 'static { fn reveal_path(&self, path: &Path); fn open_with_system(&self, path: &Path); - fn on_quit(&self, callback: Box); + fn on_quit(&self, callback: Box bool>); fn on_reopen(&self, callback: Box); fn on_system_wake(&self, callback: Box); diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 3982190f5c..86c2297d84 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -454,7 +454,7 @@ impl Platform for TestPlatform { unimplemented!() } - fn on_quit(&self, _callback: Box) {} + fn on_quit(&self, _callback: Box bool>) {} fn on_reopen(&self, _callback: Box) { unimplemented!() diff --git a/crates/gpui/src/platform/visual_test.rs b/crates/gpui/src/platform/visual_test.rs index 3ff561b26a..042a757f22 100644 --- a/crates/gpui/src/platform/visual_test.rs +++ b/crates/gpui/src/platform/visual_test.rs @@ -172,7 +172,7 @@ impl Platform for VisualTestPlatform { self.platform.open_with_system(path) } - fn on_quit(&self, _callback: Box) {} + fn on_quit(&self, _callback: Box bool>) {} fn on_reopen(&self, _callback: Box) {} diff --git a/crates/gpui_linux/src/linux/platform.rs b/crates/gpui_linux/src/linux/platform.rs index 7e8ec2224e..53dec5dca9 100644 --- a/crates/gpui_linux/src/linux/platform.rs +++ b/crates/gpui_linux/src/linux/platform.rs @@ -106,7 +106,7 @@ pub(crate) trait LinuxClient { #[derive(Default)] pub(crate) struct PlatformHandlers { pub(crate) open_urls: Option)>>, - pub(crate) quit: Option>, + pub(crate) quit: Option bool>>, pub(crate) reopen: Option>, pub(crate) app_menu_action: Option>, pub(crate) will_open_app_menu: Option>, @@ -547,7 +547,7 @@ impl Platform for LinuxPlatform

{ .detach(); } - fn on_quit(&self, callback: Box) { + fn on_quit(&self, callback: Box bool>) { self.inner.with_common(|common| { common.callbacks.quit = Some(callback); }); diff --git a/crates/gpui_macos/src/platform.rs b/crates/gpui_macos/src/platform.rs index 3ec54e8452..c1d857c78a 100644 --- a/crates/gpui_macos/src/platform.rs +++ b/crates/gpui_macos/src/platform.rs @@ -178,7 +178,7 @@ pub(crate) struct MacPlatformState { on_thermal_state_change: Option>, on_system_wake: Option>, system_wake_observer_registered: bool, - quit: Option>, + quit: Option bool>>, menu_command: Option>, validate_menu_command: Option bool>>, will_open_menu: Option>, @@ -940,7 +940,7 @@ impl Platform for MacPlatform { .detach(); } - fn on_quit(&self, callback: Box) { + fn on_quit(&self, callback: Box bool>) { self.0.lock().quit = Some(callback); } diff --git a/crates/gpui_web/src/platform.rs b/crates/gpui_web/src/platform.rs index 6e6f9c0c77..a546173198 100644 --- a/crates/gpui_web/src/platform.rs +++ b/crates/gpui_web/src/platform.rs @@ -106,7 +106,7 @@ impl std::error::Error for WebWindowError {} #[derive(Default)] struct WebPlatformCallbacks { open_urls: Option)>>, - quit: Option>, + quit: Option bool>>, reopen: Option>, app_menu_action: Option>, will_open_app_menu: Option>, @@ -458,7 +458,7 @@ impl Platform for WebPlatform { fn open_with_system(&self, _path: &Path) {} - fn on_quit(&self, callback: Box) { + fn on_quit(&self, callback: Box bool>) { self.callbacks.borrow_mut().quit = Some(callback); } diff --git a/crates/gpui_windows/src/events.rs b/crates/gpui_windows/src/events.rs index 1bd18493d0..e700cb0fc5 100644 --- a/crates/gpui_windows/src/events.rs +++ b/crates/gpui_windows/src/events.rs @@ -28,6 +28,7 @@ pub(crate) const WM_GPUI_FORCE_UPDATE_WINDOW: u32 = WM_USER + 5; pub(crate) const WM_GPUI_KEYBOARD_LAYOUT_CHANGED: u32 = WM_USER + 6; pub(crate) const WM_GPUI_GPU_DEVICE_LOST: u32 = WM_USER + 7; pub(crate) const WM_GPUI_KEYDOWN: u32 = WM_USER + 8; +pub(crate) const WM_GPUI_END_SESSION: u32 = WM_USER + 9; const SIZE_MOVE_LOOP_TIMER_ID: usize = 1; @@ -108,6 +109,8 @@ impl WindowsWindowInner { WM_PAINT => self.handle_paint_msg(handle), WM_CLOSE => self.handle_close_msg(), WM_DESTROY => self.handle_destroy_msg(handle), + WM_QUERYENDSESSION => Some(1), + WM_ENDSESSION => self.handle_end_session_msg(wparam), WM_MOUSEMOVE => self.handle_mouse_move_msg(handle, lparam, wparam), WM_MOUSELEAVE | WM_NCMOUSELEAVE => self.handle_mouse_leave_msg(), WM_NCMOUSEMOVE => self.handle_nc_mouse_move_msg(handle, lparam), @@ -170,6 +173,20 @@ impl WindowsWindowInner { } } + fn handle_end_session_msg(&self, wparam: WPARAM) -> Option { + if wparam.0 != 0 { + unsafe { + SendMessageW( + self.platform_window_handle, + WM_GPUI_END_SESSION, + Some(WPARAM(self.validation_number)), + None, + ); + } + } + Some(0) + } + fn handle_move_msg(&self, handle: HWND, lparam: LPARAM) -> Option { let origin = logical_point( lparam.signed_loword() as f32, diff --git a/crates/gpui_windows/src/platform.rs b/crates/gpui_windows/src/platform.rs index 0c0f18a0ee..9147ce51f2 100644 --- a/crates/gpui_windows/src/platform.rs +++ b/crates/gpui_windows/src/platform.rs @@ -79,7 +79,7 @@ pub(crate) struct WindowsPlatformState { #[derive(Default)] struct PlatformCallbacks { open_urls: Cell)>>>, - quit: Cell>>, + quit: Cell bool>>>, reopen: Cell>>, app_menu_action: Cell>>, will_open_app_menu: Cell>>, @@ -460,8 +460,12 @@ impl Platform for WindowsPlatform { } } - self.inner - .with_callback(|callbacks| &callbacks.quit, |callback| callback()); + self.inner.with_callback( + |callbacks| &callbacks.quit, + |callback| { + callback(); + }, + ); } fn quit(&self) { @@ -667,7 +671,7 @@ impl Platform for WindowsPlatform { .detach(); } - fn on_quit(&self, callback: Box) { + fn on_quit(&self, callback: Box bool>) { self.inner.state.callbacks.quit.set(Some(callback)); } @@ -997,7 +1001,8 @@ impl WindowsPlatformInner { | WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD | WM_GPUI_DOCK_MENU_ACTION | WM_GPUI_KEYBOARD_LAYOUT_CHANGED - | WM_GPUI_GPU_DEVICE_LOST => self.handle_gpui_events(msg, wparam, lparam), + | WM_GPUI_GPU_DEVICE_LOST + | WM_GPUI_END_SESSION => self.handle_gpui_events(msg, wparam, lparam), WM_POWERBROADCAST => self.handle_power_broadcast(wparam), _ => None, }; @@ -1022,10 +1027,29 @@ impl WindowsPlatformInner { WM_GPUI_DOCK_MENU_ACTION => self.handle_dock_action_event(lparam.0 as _), WM_GPUI_KEYBOARD_LAYOUT_CHANGED => self.handle_keyboard_layout_change(), WM_GPUI_GPU_DEVICE_LOST => self.handle_device_lost(lparam), + WM_GPUI_END_SESSION => self.handle_end_session(), _ => unreachable!(), } } + fn handle_end_session(&self) -> Option { + let mut shutdown_completed = false; + self.with_callback( + |callbacks| &callbacks.quit, + |callback| shutdown_completed = callback(), + ); + log::logger().flush(); + if shutdown_completed { + std::process::exit(0); + } + + // Shutdown couldn't run synchronously, since the AppCell is already borrowed. + // Windows may terminate the application as soon as we return from this handler, but if we post a WM_QUIT message now, + // we may get to gracefully shut down the app before we're terminated by the OS. + unsafe { PostQuitMessage(0) }; + Some(0) + } + fn close_one_window(&self, target_window: HWND) -> bool { let Some(all_windows) = self.raw_window_handles.upgrade() else { log::error!("Failed to upgrade raw window handles");