mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-11-28 12:27:16 +03:00
refactor(core): exit() and restart() triggers ExitRequested and Exit (#8708)
* refactor(core): exit() and restart() triggers ExitRequested and Exit * update docs * update doc
This commit is contained in:
parent
9af90ca7f3
commit
00e1567584
5
.changes/refactor-exit.md
Normal file
5
.changes/refactor-exit.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"tauri": patch:breaking
|
||||||
|
---
|
||||||
|
|
||||||
|
`AppHandle::exit` and `AppHandle::restart` now go triggers `RunEvent::ExitRequested` and `RunEvent::Exit` and cannot be executed on the event loop handler.
|
6
.changes/request-exit.md
Normal file
6
.changes/request-exit.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"tauri-runtime": patch:feat
|
||||||
|
"tauri-runtime-wry": patch:feat
|
||||||
|
---
|
||||||
|
|
||||||
|
Added `RuntimeHandle::request_exit` function.
|
@ -1185,6 +1185,7 @@ pub type CreateWebviewClosure = Box<dyn FnOnce(&Window) -> Result<WebviewWrapper
|
|||||||
|
|
||||||
pub enum Message<T: 'static> {
|
pub enum Message<T: 'static> {
|
||||||
Task(Box<dyn FnOnce() + Send>),
|
Task(Box<dyn FnOnce() + Send>),
|
||||||
|
RequestExit(i32),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
Application(ApplicationMessage),
|
Application(ApplicationMessage),
|
||||||
Window(WindowId, WindowMessage),
|
Window(WindowId, WindowMessage),
|
||||||
@ -1969,6 +1970,15 @@ impl<T: UserEvent> RuntimeHandle<T> for WryHandle<T> {
|
|||||||
EventProxy(self.context.proxy.clone())
|
EventProxy(self.context.proxy.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn request_exit(&self, code: i32) -> Result<()> {
|
||||||
|
// NOTE: request_exit cannot use the `send_user_message` function because it accesses the event loop callback
|
||||||
|
self
|
||||||
|
.context
|
||||||
|
.proxy
|
||||||
|
.send_event(Message::RequestExit(code))
|
||||||
|
.map_err(|_| Error::FailedToSendMessage)
|
||||||
|
}
|
||||||
|
|
||||||
// Creates a window by dispatching a message to the event loop.
|
// Creates a window by dispatching a message to the event loop.
|
||||||
// Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock.
|
// Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock.
|
||||||
fn create_window<F: Fn(RawWindow) + Send + 'static>(
|
fn create_window<F: Fn(RawWindow) + Send + 'static>(
|
||||||
@ -2411,6 +2421,7 @@ fn handle_user_message<T: UserEvent>(
|
|||||||
} = context;
|
} = context;
|
||||||
match message {
|
match message {
|
||||||
Message::Task(task) => task(),
|
Message::Task(task) => task(),
|
||||||
|
Message::RequestExit(_code) => panic!("cannot handle RequestExit on the main thread"),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
Message::Application(application_message) => match application_message {
|
Message::Application(application_message) => match application_message {
|
||||||
ApplicationMessage::Show => {
|
ApplicationMessage::Show => {
|
||||||
@ -2949,7 +2960,7 @@ fn handle_event_loop<T: UserEvent>(
|
|||||||
let is_empty = windows.borrow().is_empty();
|
let is_empty = windows.borrow().is_empty();
|
||||||
if is_empty {
|
if is_empty {
|
||||||
let (tx, rx) = channel();
|
let (tx, rx) = channel();
|
||||||
callback(RunEvent::ExitRequested { tx });
|
callback(RunEvent::ExitRequested { code: None, tx });
|
||||||
|
|
||||||
let recv = rx.try_recv();
|
let recv = rx.try_recv();
|
||||||
let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent));
|
let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent));
|
||||||
@ -2980,6 +2991,20 @@ fn handle_event_loop<T: UserEvent>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::UserEvent(message) => match message {
|
Event::UserEvent(message) => match message {
|
||||||
|
Message::RequestExit(code) => {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
callback(RunEvent::ExitRequested {
|
||||||
|
code: Some(code),
|
||||||
|
tx,
|
||||||
|
});
|
||||||
|
|
||||||
|
let recv = rx.try_recv();
|
||||||
|
let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent));
|
||||||
|
|
||||||
|
if !should_prevent {
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::Window(id, WindowMessage::Close) => {
|
Message::Window(id, WindowMessage::Close) => {
|
||||||
on_window_close(id, windows.clone());
|
on_window_close(id, windows.clone());
|
||||||
}
|
}
|
||||||
|
@ -153,6 +153,8 @@ pub enum RunEvent<T: UserEvent> {
|
|||||||
Exit,
|
Exit,
|
||||||
/// Event loop is about to exit
|
/// Event loop is about to exit
|
||||||
ExitRequested {
|
ExitRequested {
|
||||||
|
/// The exit code.
|
||||||
|
code: Option<i32>,
|
||||||
tx: Sender<ExitRequestedEventAction>,
|
tx: Sender<ExitRequestedEventAction>,
|
||||||
},
|
},
|
||||||
/// An event associated with a window.
|
/// An event associated with a window.
|
||||||
@ -204,6 +206,9 @@ pub trait RuntimeHandle<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'st
|
|||||||
/// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop.
|
/// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop.
|
||||||
fn create_proxy(&self) -> <Self::Runtime as Runtime<T>>::EventLoopProxy;
|
fn create_proxy(&self) -> <Self::Runtime as Runtime<T>>::EventLoopProxy;
|
||||||
|
|
||||||
|
/// Requests an exit of the event loop.
|
||||||
|
fn request_exit(&self, code: i32) -> Result<()>;
|
||||||
|
|
||||||
/// Create a new window.
|
/// Create a new window.
|
||||||
fn create_window<F: Fn(RawWindow) + Send + 'static>(
|
fn create_window<F: Fn(RawWindow) + Send + 'static>(
|
||||||
&self,
|
&self,
|
||||||
|
@ -41,7 +41,7 @@ use tauri_runtime::{
|
|||||||
},
|
},
|
||||||
RuntimeInitArgs,
|
RuntimeInitArgs,
|
||||||
};
|
};
|
||||||
use tauri_utils::PackageInfo;
|
use tauri_utils::{debug_eprintln, PackageInfo};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
@ -69,12 +69,17 @@ pub type SetupHook<R> =
|
|||||||
/// A closure that is run every time a page starts or finishes loading.
|
/// A closure that is run every time a page starts or finishes loading.
|
||||||
pub type OnPageLoad<R> = dyn Fn(&Webview<R>, &PageLoadPayload<'_>) + Send + Sync + 'static;
|
pub type OnPageLoad<R> = dyn Fn(&Webview<R>, &PageLoadPayload<'_>) + Send + Sync + 'static;
|
||||||
|
|
||||||
|
/// The exit code on [`RunEvent::ExitRequested`] when [`AppHandle#method.restart`] is called.
|
||||||
|
pub const RESTART_EXIT_CODE: i32 = i32::MAX;
|
||||||
|
|
||||||
/// Api exposed on the `ExitRequested` event.
|
/// Api exposed on the `ExitRequested` event.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ExitRequestApi(Sender<ExitRequestedEventAction>);
|
pub struct ExitRequestApi(Sender<ExitRequestedEventAction>);
|
||||||
|
|
||||||
impl ExitRequestApi {
|
impl ExitRequestApi {
|
||||||
/// Prevents the app from exiting
|
/// Prevents the app from exiting.
|
||||||
|
///
|
||||||
|
/// **Note:** This is ignored when using [`AppHandle#method.restart`].
|
||||||
pub fn prevent_exit(&self) {
|
pub fn prevent_exit(&self) {
|
||||||
self.0.send(ExitRequestedEventAction::Prevent).unwrap();
|
self.0.send(ExitRequestedEventAction::Prevent).unwrap();
|
||||||
}
|
}
|
||||||
@ -171,6 +176,10 @@ pub enum RunEvent {
|
|||||||
/// The app is about to exit
|
/// The app is about to exit
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
ExitRequested {
|
ExitRequested {
|
||||||
|
/// Exit code.
|
||||||
|
/// [`Option::None`] when the exit is requested by user interaction,
|
||||||
|
/// [`Option::Some`] when requested programatically via [`AppHandle#method.exit`] and [`AppHandle#method.restart`].
|
||||||
|
code: Option<i32>,
|
||||||
/// Event API
|
/// Event API
|
||||||
api: ExitRequestApi,
|
api: ExitRequestApi,
|
||||||
},
|
},
|
||||||
@ -365,15 +374,20 @@ impl<R: Runtime> AppHandle<R> {
|
|||||||
self.manager().plugins.lock().unwrap().unregister(plugin)
|
self.manager().plugins.lock().unwrap().unregister(plugin)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Exits the app. This is the same as [`std::process::exit`], but it performs cleanup on this application.
|
/// Exits the app by triggering [`RunEvent::ExitRequested`] and [`RunEvent::Exit`].
|
||||||
pub fn exit(&self, exit_code: i32) {
|
pub fn exit(&self, exit_code: i32) {
|
||||||
self.cleanup_before_exit();
|
if let Err(e) = self.runtime_handle.request_exit(exit_code) {
|
||||||
std::process::exit(exit_code);
|
debug_eprintln!("failed to exit: {}", e);
|
||||||
|
self.cleanup_before_exit();
|
||||||
|
std::process::exit(exit_code);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restarts the app. This is the same as [`crate::process::restart`], but it performs cleanup on this application.
|
/// Restarts the app by triggering [`RunEvent::ExitRequested`] with code [`RESTART_EXIT_CODE`] and [`RunEvent::Exit`]..
|
||||||
pub fn restart(&self) {
|
pub fn restart(&self) {
|
||||||
self.cleanup_before_exit();
|
if self.runtime_handle.request_exit(RESTART_EXIT_CODE).is_err() {
|
||||||
|
self.cleanup_before_exit();
|
||||||
|
}
|
||||||
crate::process::restart(&self.env());
|
crate::process::restart(&self.env());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1718,7 +1732,8 @@ fn on_event_loop_event<R: Runtime>(
|
|||||||
|
|
||||||
let event = match event {
|
let event = match event {
|
||||||
RuntimeRunEvent::Exit => RunEvent::Exit,
|
RuntimeRunEvent::Exit => RunEvent::Exit,
|
||||||
RuntimeRunEvent::ExitRequested { tx } => RunEvent::ExitRequested {
|
RuntimeRunEvent::ExitRequested { code, tx } => RunEvent::ExitRequested {
|
||||||
|
code,
|
||||||
api: ExitRequestApi(tx),
|
api: ExitRequestApi(tx),
|
||||||
},
|
},
|
||||||
RuntimeRunEvent::WindowEvent { label, event } => RunEvent::WindowEvent {
|
RuntimeRunEvent::WindowEvent { label, event } => RunEvent::WindowEvent {
|
||||||
|
@ -118,6 +118,10 @@ impl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {
|
|||||||
EventProxy {}
|
EventProxy {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn request_exit(&self, code: i32) -> Result<()> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new webview window.
|
/// Create a new webview window.
|
||||||
fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(
|
fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(
|
||||||
&self,
|
&self,
|
||||||
@ -1008,7 +1012,7 @@ impl<T: UserEvent> Runtime<T> for MockRuntime {
|
|||||||
let is_empty = self.context.windows.borrow().is_empty();
|
let is_empty = self.context.windows.borrow().is_empty();
|
||||||
if is_empty {
|
if is_empty {
|
||||||
let (tx, rx) = channel();
|
let (tx, rx) = channel();
|
||||||
callback(RunEvent::ExitRequested { tx });
|
callback(RunEvent::ExitRequested { code: None, tx });
|
||||||
|
|
||||||
let recv = rx.try_recv();
|
let recv = rx.try_recv();
|
||||||
let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent));
|
let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent));
|
||||||
|
@ -1569,12 +1569,6 @@ impl<R: Runtime> Window<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Closes this window.
|
/// Closes this window.
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// - Panics if the event loop is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
|
|
||||||
/// - Panics when called on the main thread, usually on the [`run`](crate::App#method.run) closure.
|
|
||||||
///
|
|
||||||
/// You can spawn a task to use the API using [`crate::async_runtime::spawn`] or [`std::thread::spawn`] to prevent the panic.
|
|
||||||
pub fn close(&self) -> crate::Result<()> {
|
pub fn close(&self) -> crate::Result<()> {
|
||||||
self.window.dispatcher.close().map_err(Into::into)
|
self.window.dispatcher.close().map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
@ -153,10 +153,13 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
|
|||||||
|
|
||||||
app.run(move |_app_handle, _event| {
|
app.run(move |_app_handle, _event| {
|
||||||
#[cfg(all(desktop, not(test)))]
|
#[cfg(all(desktop, not(test)))]
|
||||||
if let RunEvent::ExitRequested { api, .. } = &_event {
|
if let RunEvent::ExitRequested { api, code, .. } = &_event {
|
||||||
// Keep the event loop running even if all windows are closed
|
// Keep the event loop running even if all windows are closed
|
||||||
// This allow us to catch tray icon events when there is no window
|
// This allow us to catch tray icon events when there is no window
|
||||||
api.prevent_exit();
|
// if we manually requested an exit (code is Some(_)) we will let it go through
|
||||||
|
if code.is_none() {
|
||||||
|
api.prevent_exit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user