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 <lucas@tauri.app>
This commit is contained in:
Thomas Eizinger
2025-03-16 21:04:22 +11:00
committed by GitHub
parent 7930dde85c
commit 658e5f5d1d
11 changed files with 155 additions and 50 deletions

View File

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

View File

@@ -2917,39 +2917,51 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
});
}
fn run<F: FnMut(RunEvent<T>) + '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<F: FnMut(RunEvent<T>) + '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<F: FnMut(RunEvent<T>) + '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<F: FnMut(RunEvent<T>) + 'static>(mut self, callback: F) -> i32 {
self.run(callback);
0
}
}
fn make_event_handler<T, F>(
runtime: &Wry<T>,
mut callback: F,
) -> impl FnMut(Event<'_, Message<T>>, &EventLoopWindowTarget<Message<T>>, &mut ControlFlow)
where
T: UserEvent,
F: FnMut(RunEvent<T>) + '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<T: UserEvent> Runtime<T> for Wry<T> {
#[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(),
},
);
}
}

View File

@@ -458,6 +458,9 @@ pub trait Runtime<T: UserEvent>: Debug + Sized + 'static {
#[cfg(desktop)]
fn run_iteration<F: FnMut(RunEvent<T>) + 'static>(&mut self, callback: F);
/// Equivalent to [`Runtime::run`] but returns the exit code instead of exiting the process.
fn run_return<F: FnMut(RunEvent<T>) + 'static>(self, callback: F) -> i32;
/// Run the webview runtime.
fn run<F: FnMut(RunEvent<T>) + 'static>(self, callback: F);
}

View File

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

View File

@@ -1139,6 +1139,13 @@ impl<R: Runtime> App<R> {
/// 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<R: Runtime> App<R> {
});
}
/// 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<F: FnMut(&AppHandle<R>, 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<R: Runtime> App<R> {
/// }
/// ```
#[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<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(&mut self, mut callback: F) {
let manager = self.manager.clone();
let app_handle = self.handle().clone();

View File

@@ -1230,6 +1230,12 @@ impl<T: UserEvent> Runtime<T> for MockRuntime {
))]
fn run_iteration<F: FnMut(RunEvent<T>)>(&mut self, callback: F) {}
fn run_return<F: FnMut(RunEvent<T>) + 'static>(self, callback: F) -> i32 {
self.run(callback);
0
}
fn run<F: FnMut(RunEvent<T>) + 'static>(self, mut callback: F) {
self.is_running.store(true, Ordering::Relaxed);
callback(RunEvent::Ready);

View File

@@ -1,3 +0,0 @@
# Run Iteration Example
To execute run the following on the root directory of the repository: `cargo run --example run-iteration`.

View File

@@ -0,0 +1,3 @@
# Run Return Example
To execute run the following on the root directory of the repository: `cargo run --example run-return`.

View File

@@ -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);
}

View File

@@ -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": {