mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix: route macOS quit through safe teardown (#1841)
This commit is contained in:
@@ -31,6 +31,8 @@ mod webview_apis;
|
|||||||
mod whatsapp_scanner;
|
mod whatsapp_scanner;
|
||||||
mod window_state;
|
mod window_state;
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use tauri::menu::{PredefinedMenuItem, Submenu};
|
||||||
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||||
use tauri::WindowEvent;
|
use tauri::WindowEvent;
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
@@ -54,6 +56,12 @@ use objc2_app_kit::{NSPanel, NSWindowCollectionBehavior, NSWindowStyleMask};
|
|||||||
// CEF is the only runtime; alias kept so command handlers thread the runtime generic uniformly.
|
// CEF is the only runtime; alias kept so command handlers thread the runtime generic uniformly.
|
||||||
pub(crate) type AppRuntime = tauri::Cef;
|
pub(crate) type AppRuntime = tauri::Cef;
|
||||||
|
|
||||||
|
static EARLY_TEARDOWN_RAN: std::sync::atomic::AtomicBool =
|
||||||
|
std::sync::atomic::AtomicBool::new(false);
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
const APP_QUIT_MENU_ID: &str = "app_quit";
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn core_rpc_url() -> String {
|
fn core_rpc_url() -> String {
|
||||||
crate::core_rpc::core_rpc_url_value()
|
crate::core_rpc::core_rpc_url_value()
|
||||||
@@ -972,6 +980,69 @@ fn show_main_window(app: &AppHandle<AppRuntime>) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn macos_app_menu(app: &AppHandle<AppRuntime>) -> tauri::Result<Menu<AppRuntime>> {
|
||||||
|
let about = PredefinedMenuItem::about(app, None, None)?;
|
||||||
|
let hide = PredefinedMenuItem::hide(app, None)?;
|
||||||
|
let hide_others = PredefinedMenuItem::hide_others(app, None)?;
|
||||||
|
let show_all = PredefinedMenuItem::show_all(app, None)?;
|
||||||
|
let quit = MenuItem::with_id(
|
||||||
|
app,
|
||||||
|
APP_QUIT_MENU_ID,
|
||||||
|
"Quit OpenHuman",
|
||||||
|
true,
|
||||||
|
Some("CmdOrCtrl+Q"),
|
||||||
|
)?;
|
||||||
|
let app_sep_1 = PredefinedMenuItem::separator(app)?;
|
||||||
|
let app_sep_2 = PredefinedMenuItem::separator(app)?;
|
||||||
|
let app_menu = Submenu::with_items(
|
||||||
|
app,
|
||||||
|
"OpenHuman",
|
||||||
|
true,
|
||||||
|
&[
|
||||||
|
&about,
|
||||||
|
&app_sep_1,
|
||||||
|
&hide,
|
||||||
|
&hide_others,
|
||||||
|
&show_all,
|
||||||
|
&app_sep_2,
|
||||||
|
&quit,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let undo = PredefinedMenuItem::undo(app, None)?;
|
||||||
|
let redo = PredefinedMenuItem::redo(app, None)?;
|
||||||
|
let cut = PredefinedMenuItem::cut(app, None)?;
|
||||||
|
let copy = PredefinedMenuItem::copy(app, None)?;
|
||||||
|
let paste = PredefinedMenuItem::paste(app, None)?;
|
||||||
|
let select_all = PredefinedMenuItem::select_all(app, None)?;
|
||||||
|
let edit_sep_1 = PredefinedMenuItem::separator(app)?;
|
||||||
|
let edit_sep_2 = PredefinedMenuItem::separator(app)?;
|
||||||
|
let edit_menu = Submenu::with_items(
|
||||||
|
app,
|
||||||
|
"Edit",
|
||||||
|
true,
|
||||||
|
&[
|
||||||
|
&undo,
|
||||||
|
&redo,
|
||||||
|
&edit_sep_1,
|
||||||
|
&cut,
|
||||||
|
©,
|
||||||
|
&paste,
|
||||||
|
&edit_sep_2,
|
||||||
|
&select_all,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let close = PredefinedMenuItem::close_window(app, None)?;
|
||||||
|
let minimize = PredefinedMenuItem::minimize(app, None)?;
|
||||||
|
let fullscreen = PredefinedMenuItem::fullscreen(app, None)?;
|
||||||
|
let window_menu = Submenu::with_items(app, "Window", true, &[&close, &minimize, &fullscreen])?;
|
||||||
|
|
||||||
|
Menu::with_items(app, &[&app_menu, &edit_menu, &window_menu])
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn setup_tray(app: &AppHandle<AppRuntime>) -> tauri::Result<()> {
|
fn setup_tray(app: &AppHandle<AppRuntime>) -> tauri::Result<()> {
|
||||||
let _ = app;
|
let _ = app;
|
||||||
@@ -1206,6 +1277,18 @@ fn perform_early_teardown_sync(app_handle: &AppHandle<AppRuntime>) {
|
|||||||
log::info!("[app] perform_early_teardown_sync — early teardown complete");
|
log::info!("[app] perform_early_teardown_sync — early teardown complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn perform_early_teardown_sync_once(app_handle: &AppHandle<AppRuntime>, reason: &str) {
|
||||||
|
if EARLY_TEARDOWN_RAN.swap(true, std::sync::atomic::Ordering::SeqCst) {
|
||||||
|
log::info!(
|
||||||
|
"[app] perform_early_teardown_sync_once — already ran, skipping reason={reason}"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("[app] perform_early_teardown_sync_once — reason={reason}");
|
||||||
|
perform_early_teardown_sync(app_handle);
|
||||||
|
}
|
||||||
|
|
||||||
/// Shared early teardown logic before CEF's shutdown to prevent races and zombie processes.
|
/// Shared early teardown logic before CEF's shutdown to prevent races and zombie processes.
|
||||||
/// Asynchronous version to be called from async Tauri commands (e.g. `restart_app`, updates).
|
/// Asynchronous version to be called from async Tauri commands (e.g. `restart_app`, updates).
|
||||||
async fn perform_early_teardown_async(app_handle: &AppHandle<AppRuntime>) {
|
async fn perform_early_teardown_async(app_handle: &AppHandle<AppRuntime>) {
|
||||||
@@ -1229,7 +1312,7 @@ async fn perform_early_teardown_async(app_handle: &AppHandle<AppRuntime>) {
|
|||||||
/// Explicitly winds down CEF and Tauri before an app.exit(0)
|
/// Explicitly winds down CEF and Tauri before an app.exit(0)
|
||||||
fn shutdown_app_sync(app_handle: &AppHandle<AppRuntime>, exit_code: i32) {
|
fn shutdown_app_sync(app_handle: &AppHandle<AppRuntime>, exit_code: i32) {
|
||||||
log::info!("[app] shutdown_app_sync — starting early teardown");
|
log::info!("[app] shutdown_app_sync — starting early teardown");
|
||||||
perform_early_teardown_sync(app_handle);
|
perform_early_teardown_sync_once(app_handle, "shutdown_app_sync");
|
||||||
log::info!("[app] shutdown_app_sync — early teardown complete, exiting");
|
log::info!("[app] shutdown_app_sync — early teardown complete, exiting");
|
||||||
app_handle.exit(exit_code);
|
app_handle.exit(exit_code);
|
||||||
}
|
}
|
||||||
@@ -1705,6 +1788,20 @@ pub fn run() {
|
|||||||
tauri::Builder::<tauri::Cef>::new().command_line_args::<&str, &str>(args)
|
tauri::Builder::<tauri::Cef>::new().command_line_args::<&str, &str>(args)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let builder = builder
|
||||||
|
// Use an app-owned Quit item for Cmd+Q instead of the native
|
||||||
|
// predefined Quit action. The predefined path calls
|
||||||
|
// NSApplication::terminate, which reaches CEF shutdown before
|
||||||
|
// OpenHuman's child-webview/core teardown can run.
|
||||||
|
.menu(macos_app_menu)
|
||||||
|
.on_menu_event(|app, event| {
|
||||||
|
if event.id().as_ref() == APP_QUIT_MENU_ID {
|
||||||
|
log::info!("[app-menu] action=quit");
|
||||||
|
shutdown_app_sync(app, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let builder = builder
|
let builder = builder
|
||||||
// Single-instance guard — MUST be the first plugin registered so the
|
// Single-instance guard — MUST be the first plugin registered so the
|
||||||
// secondary-process exit path triggers before any other plugin setup
|
// secondary-process exit path triggers before any other plugin setup
|
||||||
@@ -2518,7 +2615,7 @@ pub fn run() {
|
|||||||
// do not wait — that would block the main thread
|
// do not wait — that would block the main thread
|
||||||
// and starve CEF's UI loop. The kernel reaps the
|
// and starve CEF's UI loop. The kernel reaps the
|
||||||
// child after Tauri exits.
|
// child after Tauri exits.
|
||||||
perform_early_teardown_sync(app_handle);
|
perform_early_teardown_sync_once(app_handle, "exit_requested");
|
||||||
}
|
}
|
||||||
RunEvent::Exit => {
|
RunEvent::Exit => {
|
||||||
log::info!("[app] RunEvent::Exit — cef::shutdown follows");
|
log::info!("[app] RunEvent::Exit — cef::shutdown follows");
|
||||||
|
|||||||
Reference in New Issue
Block a user