Skip to content

Commit 658e5f5

Browse files
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 <[email protected]>
1 parent 7930dde commit 658e5f5

File tree

11 files changed

+155
-50
lines changed

11 files changed

+155
-50
lines changed

.changes/introduce-run-return.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
tauri: 'minor:feat'
3+
tauri-runtime: 'minor:feat'
4+
tauri-runtime-wry: 'minor:feat'
5+
---
6+
7+
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.
8+
9+
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.

crates/tauri-runtime-wry/src/lib.rs

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2917,39 +2917,51 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
29172917
});
29182918
}
29192919

2920-
fn run<F: FnMut(RunEvent<T>) + 'static>(self, mut callback: F) {
2921-
let windows = self.context.main_thread.windows.clone();
2922-
let window_id_map = self.context.window_id_map.clone();
2923-
let web_context = self.context.main_thread.web_context;
2924-
let plugins = self.context.plugins.clone();
2920+
fn run<F: FnMut(RunEvent<T>) + 'static>(self, callback: F) {
2921+
let event_handler = make_event_handler(&self, callback);
29252922

2926-
#[cfg(feature = "tracing")]
2927-
let active_tracing_spans = self.context.main_thread.active_tracing_spans.clone();
2928-
let proxy = self.event_loop.create_proxy();
2923+
self.event_loop.run(event_handler)
2924+
}
29292925

2930-
self.event_loop.run(move |event, event_loop, control_flow| {
2931-
for p in plugins.lock().unwrap().iter_mut() {
2932-
let prevent_default = p.on_event(
2933-
&event,
2934-
event_loop,
2935-
&proxy,
2936-
control_flow,
2937-
EventLoopIterationContext {
2938-
callback: &mut callback,
2939-
window_id_map: window_id_map.clone(),
2940-
windows: windows.clone(),
2941-
#[cfg(feature = "tracing")]
2942-
active_tracing_spans: active_tracing_spans.clone(),
2943-
},
2944-
&web_context,
2945-
);
2946-
if prevent_default {
2947-
return;
2948-
}
2949-
}
2950-
handle_event_loop(
2951-
event,
2926+
#[cfg(not(target_os = "ios"))]
2927+
fn run_return<F: FnMut(RunEvent<T>) + 'static>(mut self, callback: F) -> i32 {
2928+
use tao::platform::run_return::EventLoopExtRunReturn;
2929+
2930+
let event_handler = make_event_handler(&self, callback);
2931+
2932+
self.event_loop.run_return(event_handler)
2933+
}
2934+
2935+
#[cfg(target_os = "ios")]
2936+
fn run_return<F: FnMut(RunEvent<T>) + 'static>(mut self, callback: F) -> i32 {
2937+
self.run(callback);
2938+
0
2939+
}
2940+
}
2941+
2942+
fn make_event_handler<T, F>(
2943+
runtime: &Wry<T>,
2944+
mut callback: F,
2945+
) -> impl FnMut(Event<'_, Message<T>>, &EventLoopWindowTarget<Message<T>>, &mut ControlFlow)
2946+
where
2947+
T: UserEvent,
2948+
F: FnMut(RunEvent<T>) + 'static,
2949+
{
2950+
let windows = runtime.context.main_thread.windows.clone();
2951+
let window_id_map = runtime.context.window_id_map.clone();
2952+
let web_context = runtime.context.main_thread.web_context.clone();
2953+
let plugins = runtime.context.plugins.clone();
2954+
2955+
#[cfg(feature = "tracing")]
2956+
let active_tracing_spans = runtime.context.main_thread.active_tracing_spans.clone();
2957+
let proxy = runtime.event_loop.create_proxy();
2958+
2959+
move |event, event_loop, control_flow| {
2960+
for p in plugins.lock().unwrap().iter_mut() {
2961+
let prevent_default = p.on_event(
2962+
&event,
29522963
event_loop,
2964+
&proxy,
29532965
control_flow,
29542966
EventLoopIterationContext {
29552967
callback: &mut callback,
@@ -2958,8 +2970,24 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
29582970
#[cfg(feature = "tracing")]
29592971
active_tracing_spans: active_tracing_spans.clone(),
29602972
},
2973+
&web_context,
29612974
);
2962-
})
2975+
if prevent_default {
2976+
return;
2977+
}
2978+
}
2979+
handle_event_loop(
2980+
event,
2981+
event_loop,
2982+
control_flow,
2983+
EventLoopIterationContext {
2984+
callback: &mut callback,
2985+
window_id_map: window_id_map.clone(),
2986+
windows: windows.clone(),
2987+
#[cfg(feature = "tracing")]
2988+
active_tracing_spans: active_tracing_spans.clone(),
2989+
},
2990+
);
29632991
}
29642992
}
29652993

crates/tauri-runtime/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,9 @@ pub trait Runtime<T: UserEvent>: Debug + Sized + 'static {
458458
#[cfg(desktop)]
459459
fn run_iteration<F: FnMut(RunEvent<T>) + 'static>(&mut self, callback: F);
460460

461+
/// Equivalent to [`Runtime::run`] but returns the exit code instead of exiting the process.
462+
fn run_return<F: FnMut(RunEvent<T>) + 'static>(self, callback: F) -> i32;
463+
461464
/// Run the webview runtime.
462465
fn run<F: FnMut(RunEvent<T>) + 'static>(self, callback: F);
463466
}

crates/tauri/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,8 @@ name = "multiwindow"
230230
path = "../../examples/multiwindow/main.rs"
231231

232232
[[example]]
233-
name = "run-iteration"
234-
path = "../../examples/run-iteration/main.rs"
233+
name = "run-return"
234+
path = "../../examples/run-return/main.rs"
235235

236236
[[example]]
237237
name = "splashscreen"

crates/tauri/src/app.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,6 +1139,13 @@ impl<R: Runtime> App<R> {
11391139

11401140
/// Runs the application.
11411141
///
1142+
/// This function never returns. When the application finishes, the process is exited directly using [`std::process::exit`].
1143+
/// See [`run_return`](Self::run_return) if you need to run code after the application event loop exits.
1144+
///
1145+
/// # Panics
1146+
///
1147+
/// This function will panic if the setup-function supplied in [`Builder::setup`] fails.
1148+
///
11421149
/// # Examples
11431150
/// ```,no_run
11441151
/// let app = tauri::Builder::default()
@@ -1179,6 +1186,60 @@ impl<R: Runtime> App<R> {
11791186
});
11801187
}
11811188

1189+
/// Runs the application, returning its intended exit code.
1190+
///
1191+
/// ## Platform-specific
1192+
///
1193+
/// - **iOS**: Unsupported. The application will fallback to [`run`](Self::run).
1194+
///
1195+
/// # Panics
1196+
///
1197+
/// This function will panic if the setup-function supplied in [`Builder::setup`] fails.
1198+
///
1199+
/// # Examples
1200+
/// ```,no_run
1201+
/// let app = tauri::Builder::default()
1202+
/// // on an actual app, remove the string argument
1203+
/// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
1204+
/// .expect("error while building tauri application");
1205+
/// let exit_code = app
1206+
/// .run_return(|_app_handle, event| match event {
1207+
/// tauri::RunEvent::ExitRequested { api, .. } => {
1208+
/// api.prevent_exit();
1209+
/// }
1210+
/// _ => {}
1211+
/// });
1212+
///
1213+
/// std::process::exit(exit_code);
1214+
/// ```
1215+
pub fn run_return<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(mut self, mut callback: F) -> i32 {
1216+
let manager = self.manager.clone();
1217+
let app_handle = self.handle().clone();
1218+
1219+
self
1220+
.runtime
1221+
.take()
1222+
.unwrap()
1223+
.run_return(move |event| match event {
1224+
RuntimeRunEvent::Ready => {
1225+
if let Err(e) = setup(&mut self) {
1226+
panic!("Failed to setup app: {e}");
1227+
}
1228+
let event = on_event_loop_event(&app_handle, RuntimeRunEvent::Ready, &manager);
1229+
callback(&app_handle, event);
1230+
}
1231+
RuntimeRunEvent::Exit => {
1232+
let event = on_event_loop_event(&app_handle, RuntimeRunEvent::Exit, &manager);
1233+
callback(&app_handle, event);
1234+
app_handle.cleanup_before_exit();
1235+
}
1236+
_ => {
1237+
let event = on_event_loop_event(&app_handle, event, &manager);
1238+
callback(&app_handle, event);
1239+
}
1240+
})
1241+
}
1242+
11821243
/// Runs an iteration of the runtime event loop and immediately return.
11831244
///
11841245
/// Note that when using this API, app cleanup is not automatically done.
@@ -1202,6 +1263,9 @@ impl<R: Runtime> App<R> {
12021263
/// }
12031264
/// ```
12041265
#[cfg(desktop)]
1266+
#[deprecated(
1267+
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."
1268+
)]
12051269
pub fn run_iteration<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(&mut self, mut callback: F) {
12061270
let manager = self.manager.clone();
12071271
let app_handle = self.handle().clone();

crates/tauri/src/test/mock_runtime.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,6 +1230,12 @@ impl<T: UserEvent> Runtime<T> for MockRuntime {
12301230
))]
12311231
fn run_iteration<F: FnMut(RunEvent<T>)>(&mut self, callback: F) {}
12321232

1233+
fn run_return<F: FnMut(RunEvent<T>) + 'static>(self, callback: F) -> i32 {
1234+
self.run(callback);
1235+
1236+
0
1237+
}
1238+
12331239
fn run<F: FnMut(RunEvent<T>) + 'static>(self, mut callback: F) {
12341240
self.is_running.store(true, Ordering::Relaxed);
12351241
callback(RunEvent::Ready);

examples/run-iteration/README.md

Lines changed: 0 additions & 3 deletions
This file was deleted.

examples/run-return/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Run Return Example
2+
3+
To execute run the following on the root directory of the repository: `cargo run --example run-return`.
File renamed without changes.

examples/run-iteration/main.rs renamed to examples/run-return/main.rs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,18 @@
44

55
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
66

7-
use tauri::Manager;
8-
97
fn main() {
10-
let mut app = tauri::Builder::default()
8+
let app = tauri::Builder::default()
119
.build(tauri::generate_context!(
12-
"../../examples/run-iteration/tauri.conf.json"
10+
"../../examples/run-return/tauri.conf.json"
1311
))
1412
.expect("error while building tauri application");
1513

16-
loop {
17-
app.run_iteration(|_app, _event| {
18-
//println!("{:?}", _event);
19-
});
14+
let exit_code = app.run_return(|_app, _event| {
15+
//println!("{:?}", _event);
16+
});
17+
18+
println!("I run after exit");
2019

21-
if app.webview_windows().is_empty() {
22-
app.cleanup_before_exit();
23-
break;
24-
}
25-
}
20+
std::process::exit(exit_code);
2621
}

0 commit comments

Comments
 (0)