diff --git a/.changes/exit-requested-event.md b/.changes/exit-requested-event.md new file mode 100644 index 000000000..c8cea2d5e --- /dev/null +++ b/.changes/exit-requested-event.md @@ -0,0 +1,7 @@ +--- +"tauri": patch +"tauri-runtime": patch +"tauri-runtime-wry": patch +--- + +Add `ExitRequested` event that allows preventing the app from exiting when all windows are closed, and an `AppHandle.exit()` function to exit the app manually. diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index a949223c1..d29435661 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -13,8 +13,8 @@ use tauri_runtime::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, DetachedWindow, PendingWindow, WindowEvent, }, - ClipboardManager, Dispatch, Error, GlobalShortcutManager, Icon, Result, RunEvent, RunIteration, - Runtime, RuntimeHandle, UserAttentionType, + ClipboardManager, Dispatch, Error, ExitRequestedEventAction, GlobalShortcutManager, Icon, Result, + RunEvent, RunIteration, Runtime, RuntimeHandle, UserAttentionType, }; #[cfg(feature = "menu")] @@ -2063,8 +2063,16 @@ fn on_window_close<'a>( callback(RunEvent::WindowClose(webview.label)); } if windows.is_empty() { - *control_flow = ControlFlow::Exit; - callback(RunEvent::Exit); + let (tx, rx) = channel(); + callback(RunEvent::ExitRequested { tx }); + + let recv = rx.try_recv(); + let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent)); + + if !should_prevent { + *control_flow = ControlFlow::Exit; + callback(RunEvent::Exit); + } } // TODO: tao does not fire the destroyed event properly #[cfg(target_os = "linux")] diff --git a/core/tauri-runtime/src/lib.rs b/core/tauri-runtime/src/lib.rs index 7f25180a7..faa3ca0d5 100644 --- a/core/tauri-runtime/src/lib.rs +++ b/core/tauri-runtime/src/lib.rs @@ -170,6 +170,10 @@ impl Icon { pub enum RunEvent { /// Event loop is exiting. Exit, + /// Event loop is about to exit + ExitRequested { + tx: Sender, + }, /// Window close was requested by the user. CloseRequested { /// The window label. @@ -181,6 +185,13 @@ pub enum RunEvent { WindowClose(String), } +/// Action to take when the event loop is about to exit +#[derive(Debug)] +pub enum ExitRequestedEventAction { + /// Prevent the event loop from exiting + Prevent, +} + /// A system tray event. pub enum SystemTrayEvent { MenuItemClick(u16), diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 301b22e4a..dee7876fd 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -15,7 +15,7 @@ use crate::{ runtime::{ webview::{CustomProtocol, WebviewAttributes, WindowBuilder}, window::{PendingWindow, WindowEvent}, - Dispatch, RunEvent, Runtime, + Dispatch, ExitRequestedEventAction, RunEvent, Runtime, }, sealed::{ManagerBase, RuntimeOrDispatch}, Context, Invoke, InvokeError, Manager, StateManager, Window, @@ -47,7 +47,19 @@ pub(crate) type GlobalWindowEventListener = Box) #[cfg(feature = "system-tray")] type SystemTrayEventListener = Box, tray::SystemTrayEvent) + Send + Sync>; +/// Api exposed on the `ExitRequested` event. +#[derive(Debug)] +pub struct ExitRequestApi(Sender); + +impl ExitRequestApi { + /// Prevents the app from exiting + pub fn prevent_exit(&self) { + self.0.send(ExitRequestedEventAction::Prevent).unwrap(); + } +} + /// Api exposed on the `CloseRequested` event. +#[derive(Debug)] pub struct CloseRequestApi(Sender); impl CloseRequestApi { @@ -58,10 +70,17 @@ impl CloseRequestApi { } /// An application event, triggered from the event loop. +#[derive(Debug)] #[non_exhaustive] pub enum Event { /// Event loop is exiting. Exit, + /// The app is about to exit + #[non_exhaustive] + ExitRequested { + /// Event API + api: ExitRequestApi, + }, /// Window close was requested by the user. #[non_exhaustive] CloseRequested { @@ -223,6 +242,23 @@ impl AppHandle { .register(plugin); Ok(()) } + + /// Exits the app + pub fn exit(&self, exit_code: i32) { + std::process::exit(exit_code); + } + + /// Runs necessary cleanup tasks before exiting the process + fn cleanup_before_exit(&self) { + #[cfg(shell_execute)] + { + crate::api::process::kill_children(); + } + #[cfg(all(windows, feature = "system-tray"))] + { + let _ = self.remove_system_tray(); + } + } } impl Manager for AppHandle {} @@ -356,14 +392,7 @@ impl App { let manager = self.manager.clone(); self.runtime.take().unwrap().run(move |event| match event { RunEvent::Exit => { - #[cfg(shell_execute)] - { - crate::api::process::kill_children(); - } - #[cfg(all(windows, feature = "system-tray"))] - { - let _ = app_handle.remove_system_tray(); - } + app_handle.cleanup_before_exit(); callback(&app_handle, Event::Exit); } _ => { @@ -372,6 +401,9 @@ impl App { &app_handle, match event { RunEvent::Exit => Event::Exit, + RunEvent::ExitRequested { tx } => Event::ExitRequested { + api: ExitRequestApi(tx), + }, RunEvent::CloseRequested { label, signal_tx } => Event::CloseRequested { label: label.parse().unwrap_or_else(|_| unreachable!()), api: CloseRequestApi(signal_tx),