use std::{ ffi::CStr, sync::{Once, OnceLock}, }; use objc2::{ ffi, runtime::{AnyClass, AnyObject, Imp, Sel}, sel, }; use objc2_app_kit::NSApplicationTerminateReply; use tauri::{AppHandle, Manager}; use crate::{request_app_close, CloseBehaviorState}; static APP_HANDLE: OnceLock = OnceLock::new(); static INSTALL_DOCK_QUIT_HANDLER: Once = Once::new(); pub(crate) fn install_dock_quit_handler(app: &AppHandle) { let _ = APP_HANDLE.set(app.clone()); INSTALL_DOCK_QUIT_HANDLER.call_once(|| { let Some(delegate_class) = AnyClass::get(CStr::from_bytes_with_nul(b"TaoAppDelegateParent\0").expect("valid class name")) else { eprintln!("[WARN] failed to install macOS Dock quit handler: TaoAppDelegateParent not found"); return; }; let imp: Imp = unsafe { std::mem::transmute( application_should_terminate as extern "C-unwind" fn(&AnyObject, Sel, &AnyObject) -> NSApplicationTerminateReply, ) }; let method_types = CStr::from_bytes_with_nul(b"Q@:@\0").expect("valid Objective-C method type encoding"); // Tao's app delegate does not implement applicationShouldTerminate:, so Dock Quit can // bypass Tauri's ExitRequested event. Add the method to Tao's registered delegate class // and route that native quit request through DBX's normal close confirmation flow. let added = unsafe { ffi::class_addMethod( delegate_class as *const AnyClass as *mut AnyClass, sel!(applicationShouldTerminate:), imp, method_types.as_ptr(), ) }; if !added.as_bool() { eprintln!("[WARN] failed to install macOS Dock quit handler: applicationShouldTerminate: already exists"); } }); } extern "C-unwind" fn application_should_terminate( _delegate: &AnyObject, _selector: Sel, _sender: &AnyObject, ) -> NSApplicationTerminateReply { let Some(app) = APP_HANDLE.get() else { return NSApplicationTerminateReply::TerminateNow; }; if app.try_state::().map(|state| state.take_confirmed_exit()).unwrap_or(false) { return NSApplicationTerminateReply::TerminateNow; } request_app_close(app, "quit"); NSApplicationTerminateReply::TerminateCancel }