From 658e5f5d1dc1bd970ae572a42447448d064a7fee Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Sun, 16 Mar 2025 21:04:22 +1100 Subject: [PATCH] feat: introduce `App::run_return` (#12668) * Introduce `run_return` * Fix compile error * Clone web_context * Refactor to Result API * Fix clippy * Impl mock runtime * Make it desktop-only * Add changelog entry * Fix compile error * Make it semver compatible * Extend changelog entry * Undo semver-hack * Reduce diff * Remove unnecessary mut * Make it take `self` by value * Reduce diff * Undo diff hack * Make everything cfg(desktop) * Rename vars to reduce diff * Fix clippy * Extract make_event_handler * Reduce diff * Deprecate `App::run_return` * Update changelog * Fix compile errors * Accept reference * Create event handler first * Update example * Update manifest * Fix example * Fix example docs * Call `setup` only upon Ready * Update changelog entry * Update docs * Update changelog * Add platform-specific note * update docs * run_return on mobile * Apply suggestions from code review * remove change file --------- Co-authored-by: Lucas Nogueira --- .changes/introduce-run-return.md | 9 ++ crates/tauri-runtime-wry/src/lib.rs | 90 ++++++++++++------- crates/tauri-runtime/src/lib.rs | 3 + crates/tauri/Cargo.toml | 4 +- crates/tauri/src/app.rs | 64 +++++++++++++ crates/tauri/src/test/mock_runtime.rs | 6 ++ examples/run-iteration/README.md | 3 - examples/run-return/README.md | 3 + .../{run-iteration => run-return}/index.html | 0 .../{run-iteration => run-return}/main.rs | 21 ++--- .../tauri.conf.json | 2 +- 11 files changed, 155 insertions(+), 50 deletions(-) create mode 100644 .changes/introduce-run-return.md delete mode 100644 examples/run-iteration/README.md create mode 100644 examples/run-return/README.md rename examples/{run-iteration => run-return}/index.html (100%) rename examples/{run-iteration => run-return}/main.rs (51%) rename examples/{run-iteration => run-return}/tauri.conf.json (95%) diff --git a/.changes/introduce-run-return.md b/.changes/introduce-run-return.md new file mode 100644 index 000000000..c241275f6 --- /dev/null +++ b/.changes/introduce-run-return.md @@ -0,0 +1,9 @@ +--- +tauri: 'minor:feat' +tauri-runtime: 'minor:feat' +tauri-runtime-wry: 'minor:feat' +--- + +Add `App::run_return` function. Contrary to `App::run`, this will **not** exit the process but instead return the requested exit-code. This allows the host app to perform further cleanup after Tauri has exited. `App::run_return` is not available on iOS and fallbacks to the regular `App::run` functionality. + +The `App::run_iteration` function is deprecated as part of this because calling it in a loop - as suggested by the name - will cause a busy-loop. diff --git a/crates/tauri-runtime-wry/src/lib.rs b/crates/tauri-runtime-wry/src/lib.rs index a5ead7282..570e75505 100644 --- a/crates/tauri-runtime-wry/src/lib.rs +++ b/crates/tauri-runtime-wry/src/lib.rs @@ -2917,39 +2917,51 @@ impl Runtime for Wry { }); } - fn run) + 'static>(self, mut callback: F) { - let windows = self.context.main_thread.windows.clone(); - let window_id_map = self.context.window_id_map.clone(); - let web_context = self.context.main_thread.web_context; - let plugins = self.context.plugins.clone(); + fn run) + 'static>(self, callback: F) { + let event_handler = make_event_handler(&self, callback); - #[cfg(feature = "tracing")] - let active_tracing_spans = self.context.main_thread.active_tracing_spans.clone(); - let proxy = self.event_loop.create_proxy(); + self.event_loop.run(event_handler) + } - self.event_loop.run(move |event, event_loop, control_flow| { - for p in plugins.lock().unwrap().iter_mut() { - let prevent_default = p.on_event( - &event, - event_loop, - &proxy, - control_flow, - EventLoopIterationContext { - callback: &mut callback, - window_id_map: window_id_map.clone(), - windows: windows.clone(), - #[cfg(feature = "tracing")] - active_tracing_spans: active_tracing_spans.clone(), - }, - &web_context, - ); - if prevent_default { - return; - } - } - handle_event_loop( - event, + #[cfg(not(target_os = "ios"))] + fn run_return) + 'static>(mut self, callback: F) -> i32 { + use tao::platform::run_return::EventLoopExtRunReturn; + + let event_handler = make_event_handler(&self, callback); + + self.event_loop.run_return(event_handler) + } + + #[cfg(target_os = "ios")] + fn run_return) + 'static>(mut self, callback: F) -> i32 { + self.run(callback); + 0 + } +} + +fn make_event_handler( + runtime: &Wry, + mut callback: F, +) -> impl FnMut(Event<'_, Message>, &EventLoopWindowTarget>, &mut ControlFlow) +where + T: UserEvent, + F: FnMut(RunEvent) + 'static, +{ + let windows = runtime.context.main_thread.windows.clone(); + let window_id_map = runtime.context.window_id_map.clone(); + let web_context = runtime.context.main_thread.web_context.clone(); + let plugins = runtime.context.plugins.clone(); + + #[cfg(feature = "tracing")] + let active_tracing_spans = runtime.context.main_thread.active_tracing_spans.clone(); + let proxy = runtime.event_loop.create_proxy(); + + move |event, event_loop, control_flow| { + for p in plugins.lock().unwrap().iter_mut() { + let prevent_default = p.on_event( + &event, event_loop, + &proxy, control_flow, EventLoopIterationContext { callback: &mut callback, @@ -2958,8 +2970,24 @@ impl Runtime for Wry { #[cfg(feature = "tracing")] active_tracing_spans: active_tracing_spans.clone(), }, + &web_context, ); - }) + if prevent_default { + return; + } + } + handle_event_loop( + event, + event_loop, + control_flow, + EventLoopIterationContext { + callback: &mut callback, + window_id_map: window_id_map.clone(), + windows: windows.clone(), + #[cfg(feature = "tracing")] + active_tracing_spans: active_tracing_spans.clone(), + }, + ); } } diff --git a/crates/tauri-runtime/src/lib.rs b/crates/tauri-runtime/src/lib.rs index c19dff933..053b0e14d 100644 --- a/crates/tauri-runtime/src/lib.rs +++ b/crates/tauri-runtime/src/lib.rs @@ -458,6 +458,9 @@ pub trait Runtime: Debug + Sized + 'static { #[cfg(desktop)] fn run_iteration) + 'static>(&mut self, callback: F); + /// Equivalent to [`Runtime::run`] but returns the exit code instead of exiting the process. + fn run_return) + 'static>(self, callback: F) -> i32; + /// Run the webview runtime. fn run) + 'static>(self, callback: F); } diff --git a/crates/tauri/Cargo.toml b/crates/tauri/Cargo.toml index 94962ee3f..d6f92c109 100644 --- a/crates/tauri/Cargo.toml +++ b/crates/tauri/Cargo.toml @@ -230,8 +230,8 @@ name = "multiwindow" path = "../../examples/multiwindow/main.rs" [[example]] -name = "run-iteration" -path = "../../examples/run-iteration/main.rs" +name = "run-return" +path = "../../examples/run-return/main.rs" [[example]] name = "splashscreen" diff --git a/crates/tauri/src/app.rs b/crates/tauri/src/app.rs index a77d5854a..b3fcd9894 100644 --- a/crates/tauri/src/app.rs +++ b/crates/tauri/src/app.rs @@ -1139,6 +1139,13 @@ impl App { /// Runs the application. /// + /// This function never returns. When the application finishes, the process is exited directly using [`std::process::exit`]. + /// See [`run_return`](Self::run_return) if you need to run code after the application event loop exits. + /// + /// # Panics + /// + /// This function will panic if the setup-function supplied in [`Builder::setup`] fails. + /// /// # Examples /// ```,no_run /// let app = tauri::Builder::default() @@ -1179,6 +1186,60 @@ impl App { }); } + /// Runs the application, returning its intended exit code. + /// + /// ## Platform-specific + /// + /// - **iOS**: Unsupported. The application will fallback to [`run`](Self::run). + /// + /// # Panics + /// + /// This function will panic if the setup-function supplied in [`Builder::setup`] fails. + /// + /// # Examples + /// ```,no_run + /// let app = tauri::Builder::default() + /// // on an actual app, remove the string argument + /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) + /// .expect("error while building tauri application"); + /// let exit_code = app + /// .run_return(|_app_handle, event| match event { + /// tauri::RunEvent::ExitRequested { api, .. } => { + /// api.prevent_exit(); + /// } + /// _ => {} + /// }); + /// + /// std::process::exit(exit_code); + /// ``` + pub fn run_return, RunEvent) + 'static>(mut self, mut callback: F) -> i32 { + let manager = self.manager.clone(); + let app_handle = self.handle().clone(); + + self + .runtime + .take() + .unwrap() + .run_return(move |event| match event { + RuntimeRunEvent::Ready => { + if let Err(e) = setup(&mut self) { + panic!("Failed to setup app: {e}"); + } + let event = on_event_loop_event(&app_handle, RuntimeRunEvent::Ready, &manager); + callback(&app_handle, event); + } + RuntimeRunEvent::Exit => { + let event = on_event_loop_event(&app_handle, RuntimeRunEvent::Exit, &manager); + callback(&app_handle, event); + app_handle.cleanup_before_exit(); + } + _ => { + let event = on_event_loop_event(&app_handle, event, &manager); + callback(&app_handle, event); + } + }) + } + /// Runs an iteration of the runtime event loop and immediately return. /// /// Note that when using this API, app cleanup is not automatically done. @@ -1202,6 +1263,9 @@ impl App { /// } /// ``` #[cfg(desktop)] + #[deprecated( + note = "When called in a loop (as suggested by the name), this function will busy-loop. To re-gain control of control flow after the app has exited, use `App::run_return` instead." + )] pub fn run_iteration, RunEvent) + 'static>(&mut self, mut callback: F) { let manager = self.manager.clone(); let app_handle = self.handle().clone(); diff --git a/crates/tauri/src/test/mock_runtime.rs b/crates/tauri/src/test/mock_runtime.rs index 78e44193b..981c60acb 100644 --- a/crates/tauri/src/test/mock_runtime.rs +++ b/crates/tauri/src/test/mock_runtime.rs @@ -1230,6 +1230,12 @@ impl Runtime for MockRuntime { ))] fn run_iteration)>(&mut self, callback: F) {} + fn run_return) + 'static>(self, callback: F) -> i32 { + self.run(callback); + + 0 + } + fn run) + 'static>(self, mut callback: F) { self.is_running.store(true, Ordering::Relaxed); callback(RunEvent::Ready); diff --git a/examples/run-iteration/README.md b/examples/run-iteration/README.md deleted file mode 100644 index 81a408271..000000000 --- a/examples/run-iteration/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Run Iteration Example - -To execute run the following on the root directory of the repository: `cargo run --example run-iteration`. diff --git a/examples/run-return/README.md b/examples/run-return/README.md new file mode 100644 index 000000000..919577049 --- /dev/null +++ b/examples/run-return/README.md @@ -0,0 +1,3 @@ +# Run Return Example + +To execute run the following on the root directory of the repository: `cargo run --example run-return`. diff --git a/examples/run-iteration/index.html b/examples/run-return/index.html similarity index 100% rename from examples/run-iteration/index.html rename to examples/run-return/index.html diff --git a/examples/run-iteration/main.rs b/examples/run-return/main.rs similarity index 51% rename from examples/run-iteration/main.rs rename to examples/run-return/main.rs index 666de49fe..93f5dcd51 100644 --- a/examples/run-iteration/main.rs +++ b/examples/run-return/main.rs @@ -4,23 +4,18 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use tauri::Manager; - fn main() { - let mut app = tauri::Builder::default() + let app = tauri::Builder::default() .build(tauri::generate_context!( - "../../examples/run-iteration/tauri.conf.json" + "../../examples/run-return/tauri.conf.json" )) .expect("error while building tauri application"); - loop { - app.run_iteration(|_app, _event| { - //println!("{:?}", _event); - }); + let exit_code = app.run_return(|_app, _event| { + //println!("{:?}", _event); + }); - if app.webview_windows().is_empty() { - app.cleanup_before_exit(); - break; - } - } + println!("I run after exit"); + + std::process::exit(exit_code); } diff --git a/examples/run-iteration/tauri.conf.json b/examples/run-return/tauri.conf.json similarity index 95% rename from examples/run-iteration/tauri.conf.json rename to examples/run-return/tauri.conf.json index 80851c7ae..fb3eaf94d 100644 --- a/examples/run-iteration/tauri.conf.json +++ b/examples/run-return/tauri.conf.json @@ -1,6 +1,6 @@ { "$schema": "../../crates/tauri-schema-generator/schemas/config.schema.json", - "productName": "RunIteration", + "productName": "RunReturn", "version": "0.1.0", "identifier": "com.tauri.dev", "build": {