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
This commit is contained in:
John Tur authored and GitHub committed 2026-08-24 02:59:15 +00:00
1 parent a86d40eda5
commit 8a6aaa73ef
10 files changed
+79 -16

No files matched your search

+11
View File
@@ -182,6 +182,7 @@ pub struct CrashServer {
active_gpu: Mutex<Option<system_specs::GpuSpecs>>,
user_info: Mutex<Option<UserInfo>>,
abort_message_location: Mutex<Option<AbortMessageLocation>>,
shutdown: Arc<AtomicBool>,
has_connection: Arc<AtomicBool>,
logs_dir: PathBuf,
}
@@ -255,6 +256,11 @@ pub fn set_user_info(crash_client: &Arc<Client>, 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<Client>) {
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,
+13 -2
View File
@@ -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
}
}
}
}));
+1 -1
View File
@@ -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<dyn FnMut()>);
fn on_quit(&self, callback: Box<dyn FnMut() -> bool>);
fn on_reopen(&self, callback: Box<dyn FnMut()>);
fn on_system_wake(&self, callback: Box<dyn FnMut()>);
+1 -1
View File
@@ -454,7 +454,7 @@ impl Platform for TestPlatform {
unimplemented!()
}
fn on_quit(&self, _callback: Box<dyn FnMut()>) {}
fn on_quit(&self, _callback: Box<dyn FnMut() -> bool>) {}
fn on_reopen(&self, _callback: Box<dyn FnMut()>) {
unimplemented!()
+1 -1
View File
@@ -172,7 +172,7 @@ impl Platform for VisualTestPlatform {
self.platform.open_with_system(path)
}
fn on_quit(&self, _callback: Box<dyn FnMut()>) {}
fn on_quit(&self, _callback: Box<dyn FnMut() -> bool>) {}
fn on_reopen(&self, _callback: Box<dyn FnMut()>) {}
+2 -2
View File
@@ -106,7 +106,7 @@ pub(crate) trait LinuxClient {
#[derive(Default)]
pub(crate) struct PlatformHandlers {
pub(crate) open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
pub(crate) quit: Option<Box<dyn FnMut()>>,
pub(crate) quit: Option<Box<dyn FnMut() -> bool>>,
pub(crate) reopen: Option<Box<dyn FnMut()>>,
pub(crate) app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
pub(crate) will_open_app_menu: Option<Box<dyn FnMut()>>,
@@ -547,7 +547,7 @@ impl<P: LinuxClient + 'static> Platform for LinuxPlatform<P> {
.detach();
}
fn on_quit(&self, callback: Box<dyn FnMut()>) {
fn on_quit(&self, callback: Box<dyn FnMut() -> bool>) {
self.inner.with_common(|common| {
common.callbacks.quit = Some(callback);
});
+2 -2
View File
@@ -178,7 +178,7 @@ pub(crate) struct MacPlatformState {
on_thermal_state_change: Option<Box<dyn FnMut()>>,
on_system_wake: Option<Box<dyn FnMut()>>,
system_wake_observer_registered: bool,
quit: Option<Box<dyn FnMut()>>,
quit: Option<Box<dyn FnMut() -> bool>>,
menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
will_open_menu: Option<Box<dyn FnMut()>>,
@@ -940,7 +940,7 @@ impl Platform for MacPlatform {
.detach();
}
fn on_quit(&self, callback: Box<dyn FnMut()>) {
fn on_quit(&self, callback: Box<dyn FnMut() -> bool>) {
self.0.lock().quit = Some(callback);
}
+2 -2
View File
@@ -106,7 +106,7 @@ impl std::error::Error for WebWindowError {}
#[derive(Default)]
struct WebPlatformCallbacks {
open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
quit: Option<Box<dyn FnMut()>>,
quit: Option<Box<dyn FnMut() -> bool>>,
reopen: Option<Box<dyn FnMut()>>,
app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
will_open_app_menu: Option<Box<dyn FnMut()>>,
@@ -458,7 +458,7 @@ impl Platform for WebPlatform {
fn open_with_system(&self, _path: &Path) {}
fn on_quit(&self, callback: Box<dyn FnMut()>) {
fn on_quit(&self, callback: Box<dyn FnMut() -> bool>) {
self.callbacks.borrow_mut().quit = Some(callback);
}
+17
View File
@@ -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<isize> {
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<isize> {
let origin = logical_point(
lparam.signed_loword() as f32,
+29 -5
View File
@@ -79,7 +79,7 @@ pub(crate) struct WindowsPlatformState {
#[derive(Default)]
struct PlatformCallbacks {
open_urls: Cell<Option<Box<dyn FnMut(Vec<String>)>>>,
quit: Cell<Option<Box<dyn FnMut()>>>,
quit: Cell<Option<Box<dyn FnMut() -> bool>>>,
reopen: Cell<Option<Box<dyn FnMut()>>>,
app_menu_action: Cell<Option<Box<dyn FnMut(&dyn Action)>>>,
will_open_app_menu: Cell<Option<Box<dyn FnMut()>>>,
@@ -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<dyn FnMut()>) {
fn on_quit(&self, callback: Box<dyn FnMut() -> 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<isize> {
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");