mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
fix(app): ask before quitting from the Dock or a logout on macOS
The Dock's Quit, the system's app menu and a logout terminated the app at once: the application delegate didn't answer applicationShouldTerminate:, so none of the exit, running-process or unsaved-changes prompts showed. It now cancels that termination and quits the way Cmd+Q does, with the same prompts; cancelling one keeps the app running.
This commit is contained in:
@@ -174,6 +174,8 @@ pub fn run() {
|
||||
app.manage(windows);
|
||||
app.manage(application_focus::ApplicationFocus::default());
|
||||
#[cfg(target_os = "macos")]
|
||||
macos::intercept_system_quit(app.handle());
|
||||
#[cfg(target_os = "macos")]
|
||||
application_focus::observe(app.handle());
|
||||
app.manage(menu_state);
|
||||
if let Some(paths) = setup_opens.lock().take() {
|
||||
@@ -254,7 +256,11 @@ fn window_state_flags() -> tauri_plugin_window_state::StateFlags {
|
||||
|
||||
/// Routes IDE menu items to the focused window's frontend (`executeAction`).
|
||||
fn on_menu_event(app: &tauri::AppHandle, event: tauri::menu::MenuEvent) {
|
||||
let id = event.id().as_ref();
|
||||
dispatch_menu_action(app, event.id().as_ref());
|
||||
}
|
||||
|
||||
/// Runs menu item `id` as if it were chosen: in the focused window's frontend, or here without one.
|
||||
pub(crate) fn dispatch_menu_action(app: &tauri::AppHandle, id: &str) {
|
||||
let Some(route) = menu::route(&menu::spec(menu::Os::current()), id) else { return };
|
||||
match windows::target_window(app) {
|
||||
Some(w) => {
|
||||
|
||||
@@ -202,6 +202,43 @@ fn present(fullscreen: bool) {
|
||||
NSApplication::sharedApplication(mtm).setPresentationOptions(options);
|
||||
}
|
||||
|
||||
type ShouldTerminate = unsafe extern "C-unwind" fn(*mut AnyObject, Sel, *mut AnyObject) -> usize;
|
||||
|
||||
/// `NSApplicationTerminateReply`.
|
||||
const TERMINATE_CANCEL: usize = 0;
|
||||
const TERMINATE_NOW: usize = 1;
|
||||
|
||||
/// The Dock's Quit, the system's app menu and a logout ask the application delegate whether it
|
||||
/// may terminate; tao's delegate doesn't answer, so AppKit terminated at once, past the exit,
|
||||
/// running-process and unsaved-changes prompts. Answer it: cancel, and quit like Cmd+Q does.
|
||||
pub fn intercept_system_quit(app: &AppHandle) {
|
||||
let _ = APP.set(app.clone());
|
||||
let Some(mtm) = MainThreadMarker::new() else { return };
|
||||
let application = objc2_app_kit::NSApplication::sharedApplication(mtm);
|
||||
unsafe {
|
||||
let delegate: *mut AnyObject = objc2::msg_send![&*application, delegate];
|
||||
if delegate.is_null() {
|
||||
return;
|
||||
}
|
||||
let class = objc2::ffi::object_getClass(delegate.cast()) as *mut AnyClass;
|
||||
let imp: Imp = std::mem::transmute(should_terminate as ShouldTerminate);
|
||||
let added = objc2::ffi::class_addMethod(class, objc2::sel!(applicationShouldTerminate:), imp, c"Q@:@".as_ptr());
|
||||
if !added.as_bool() {
|
||||
tracing::warn!("could not handle applicationShouldTerminate:");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C-unwind" fn should_terminate(_this: *mut AnyObject, _selector: Sel, _sender: *mut AnyObject) -> usize {
|
||||
let Some(app) = APP.get() else { return TERMINATE_NOW };
|
||||
// Our own quit exits through the event loop; a system quit during it goes ahead.
|
||||
if app.try_state::<WindowsState>().is_none_or(|w| w.quitting()) {
|
||||
return TERMINATE_NOW;
|
||||
}
|
||||
crate::dispatch_menu_action(app, "app.quit");
|
||||
TERMINATE_CANCEL
|
||||
}
|
||||
|
||||
type ToggleFullScreen = unsafe extern "C-unwind" fn(*mut AnyObject, Sel, *mut AnyObject);
|
||||
|
||||
static NATIVE_TOGGLE_FULLSCREEN: std::sync::OnceLock<ToggleFullScreen> = std::sync::OnceLock::new();
|
||||
|
||||
Reference in New Issue
Block a user