feat(#2287): Add ExitRequested event to let users prevent app from exiting (#2293)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
Wouter Buckens 2021-08-09 02:12:32 +02:00 committed by GitHub
parent fd80dd97e8
commit 892c63a053
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 71 additions and 13 deletions

View File

@ -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.

View File

@ -13,8 +13,8 @@ use tauri_runtime::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
DetachedWindow, PendingWindow, WindowEvent, DetachedWindow, PendingWindow, WindowEvent,
}, },
ClipboardManager, Dispatch, Error, GlobalShortcutManager, Icon, Result, RunEvent, RunIteration, ClipboardManager, Dispatch, Error, ExitRequestedEventAction, GlobalShortcutManager, Icon, Result,
Runtime, RuntimeHandle, UserAttentionType, RunEvent, RunIteration, Runtime, RuntimeHandle, UserAttentionType,
}; };
#[cfg(feature = "menu")] #[cfg(feature = "menu")]
@ -2063,8 +2063,16 @@ fn on_window_close<'a>(
callback(RunEvent::WindowClose(webview.label)); callback(RunEvent::WindowClose(webview.label));
} }
if windows.is_empty() { if windows.is_empty() {
*control_flow = ControlFlow::Exit; let (tx, rx) = channel();
callback(RunEvent::Exit); 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 // TODO: tao does not fire the destroyed event properly
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]

View File

@ -170,6 +170,10 @@ impl Icon {
pub enum RunEvent { pub enum RunEvent {
/// Event loop is exiting. /// Event loop is exiting.
Exit, Exit,
/// Event loop is about to exit
ExitRequested {
tx: Sender<ExitRequestedEventAction>,
},
/// Window close was requested by the user. /// Window close was requested by the user.
CloseRequested { CloseRequested {
/// The window label. /// The window label.
@ -181,6 +185,13 @@ pub enum RunEvent {
WindowClose(String), 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. /// A system tray event.
pub enum SystemTrayEvent { pub enum SystemTrayEvent {
MenuItemClick(u16), MenuItemClick(u16),

View File

@ -15,7 +15,7 @@ use crate::{
runtime::{ runtime::{
webview::{CustomProtocol, WebviewAttributes, WindowBuilder}, webview::{CustomProtocol, WebviewAttributes, WindowBuilder},
window::{PendingWindow, WindowEvent}, window::{PendingWindow, WindowEvent},
Dispatch, RunEvent, Runtime, Dispatch, ExitRequestedEventAction, RunEvent, Runtime,
}, },
sealed::{ManagerBase, RuntimeOrDispatch}, sealed::{ManagerBase, RuntimeOrDispatch},
Context, Invoke, InvokeError, Manager, StateManager, Window, Context, Invoke, InvokeError, Manager, StateManager, Window,
@ -47,7 +47,19 @@ pub(crate) type GlobalWindowEventListener<R> = Box<dyn Fn(GlobalWindowEvent<R>)
#[cfg(feature = "system-tray")] #[cfg(feature = "system-tray")]
type SystemTrayEventListener<R> = Box<dyn Fn(&AppHandle<R>, tray::SystemTrayEvent) + Send + Sync>; type SystemTrayEventListener<R> = Box<dyn Fn(&AppHandle<R>, tray::SystemTrayEvent) + Send + Sync>;
/// Api exposed on the `ExitRequested` event.
#[derive(Debug)]
pub struct ExitRequestApi(Sender<ExitRequestedEventAction>);
impl ExitRequestApi {
/// Prevents the app from exiting
pub fn prevent_exit(&self) {
self.0.send(ExitRequestedEventAction::Prevent).unwrap();
}
}
/// Api exposed on the `CloseRequested` event. /// Api exposed on the `CloseRequested` event.
#[derive(Debug)]
pub struct CloseRequestApi(Sender<bool>); pub struct CloseRequestApi(Sender<bool>);
impl CloseRequestApi { impl CloseRequestApi {
@ -58,10 +70,17 @@ impl CloseRequestApi {
} }
/// An application event, triggered from the event loop. /// An application event, triggered from the event loop.
#[derive(Debug)]
#[non_exhaustive] #[non_exhaustive]
pub enum Event { pub enum Event {
/// Event loop is exiting. /// Event loop is exiting.
Exit, Exit,
/// The app is about to exit
#[non_exhaustive]
ExitRequested {
/// Event API
api: ExitRequestApi,
},
/// Window close was requested by the user. /// Window close was requested by the user.
#[non_exhaustive] #[non_exhaustive]
CloseRequested { CloseRequested {
@ -223,6 +242,23 @@ impl<R: Runtime> AppHandle<R> {
.register(plugin); .register(plugin);
Ok(()) 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<R: Runtime> Manager<R> for AppHandle<R> {} impl<R: Runtime> Manager<R> for AppHandle<R> {}
@ -356,14 +392,7 @@ impl<R: Runtime> App<R> {
let manager = self.manager.clone(); let manager = self.manager.clone();
self.runtime.take().unwrap().run(move |event| match event { self.runtime.take().unwrap().run(move |event| match event {
RunEvent::Exit => { RunEvent::Exit => {
#[cfg(shell_execute)] app_handle.cleanup_before_exit();
{
crate::api::process::kill_children();
}
#[cfg(all(windows, feature = "system-tray"))]
{
let _ = app_handle.remove_system_tray();
}
callback(&app_handle, Event::Exit); callback(&app_handle, Event::Exit);
} }
_ => { _ => {
@ -372,6 +401,9 @@ impl<R: Runtime> App<R> {
&app_handle, &app_handle,
match event { match event {
RunEvent::Exit => Event::Exit, RunEvent::Exit => Event::Exit,
RunEvent::ExitRequested { tx } => Event::ExitRequested {
api: ExitRequestApi(tx),
},
RunEvent::CloseRequested { label, signal_tx } => Event::CloseRequested { RunEvent::CloseRequested { label, signal_tx } => Event::CloseRequested {
label: label.parse().unwrap_or_else(|_| unreachable!()), label: label.parse().unwrap_or_else(|_| unreachable!()),
api: CloseRequestApi(signal_tx), api: CloseRequestApi(signal_tx),