Skip to content

Commit 8157a68

Browse files
authored
feat(core): allow listening to event loop events & prevent window close (#2131)
1 parent d69b1cf commit 8157a68

File tree

12 files changed

+169
-56
lines changed

12 files changed

+169
-56
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"tauri": patch
3+
"tauri-runtime": patch
4+
"tauri-runtime-wry": patch
5+
---
6+
7+
Allow preventing window close when the user requests it.

.changes/app-callback.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Add `App#run` method with callback argument (event loop event handler).

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

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1490,6 +1490,11 @@ fn handle_event_loop(
14901490
#[cfg(feature = "system-tray")]
14911491
tray_context,
14921492
} = context;
1493+
if *control_flow == ControlFlow::Exit {
1494+
return RunIteration {
1495+
webview_count: webviews.len(),
1496+
};
1497+
}
14931498
*control_flow = ControlFlow::Wait;
14941499

14951500
match event {
@@ -1562,14 +1567,26 @@ fn handle_event_loop(
15621567
}
15631568
match event {
15641569
WryWindowEvent::CloseRequested => {
1565-
on_window_close(
1566-
callback,
1567-
window_id,
1568-
&mut webviews,
1569-
control_flow,
1570-
#[cfg(feature = "menu")]
1571-
menu_event_listeners.clone(),
1572-
);
1570+
let (tx, rx) = channel();
1571+
if let Some(w) = webviews.get(&window_id) {
1572+
callback(RunEvent::CloseRequested {
1573+
label: w.label.clone(),
1574+
signal_tx: tx,
1575+
});
1576+
if let Ok(true) = rx.try_recv() {
1577+
} else {
1578+
on_window_close(
1579+
callback,
1580+
window_id,
1581+
&mut webviews,
1582+
control_flow,
1583+
#[cfg(target_os = "linux")]
1584+
window_event_listeners,
1585+
#[cfg(feature = "menu")]
1586+
menu_event_listeners.clone(),
1587+
);
1588+
}
1589+
}
15731590
}
15741591
// we also resize the webview on `Moved` to fix https://github.com/tauri-apps/tauri/issues/1911
15751592
WryWindowEvent::Resized(_) | WryWindowEvent::Moved(_) => {
@@ -1666,22 +1683,13 @@ fn handle_event_loop(
16661683
WindowMessage::Show => window.set_visible(true),
16671684
WindowMessage::Hide => window.set_visible(false),
16681685
WindowMessage::Close => {
1669-
for handler in window_event_listeners
1670-
.lock()
1671-
.unwrap()
1672-
.get(&window.id())
1673-
.unwrap()
1674-
.lock()
1675-
.unwrap()
1676-
.values()
1677-
{
1678-
handler(&WindowEvent::CloseRequested);
1679-
}
16801686
on_window_close(
16811687
callback,
16821688
id,
16831689
&mut webviews,
16841690
control_flow,
1691+
#[cfg(target_os = "linux")]
1692+
window_event_listeners,
16851693
#[cfg(feature = "menu")]
16861694
menu_event_listeners.clone(),
16871695
);
@@ -1851,6 +1859,7 @@ fn on_window_close<'a>(
18511859
window_id: WindowId,
18521860
webviews: &mut MutexGuard<'a, HashMap<WindowId, WebviewWrapper>>,
18531861
control_flow: &mut ControlFlow,
1862+
#[cfg(target_os = "linux")] window_event_listeners: &WindowEventListeners,
18541863
#[cfg(feature = "menu")] menu_event_listeners: MenuEventListeners,
18551864
) {
18561865
if let Some(webview) = webviews.remove(&window_id) {
@@ -1862,6 +1871,21 @@ fn on_window_close<'a>(
18621871
*control_flow = ControlFlow::Exit;
18631872
callback(RunEvent::Exit);
18641873
}
1874+
// TODO: tao does not fire the destroyed event properly
1875+
#[cfg(target_os = "linux")]
1876+
{
1877+
for handler in window_event_listeners
1878+
.lock()
1879+
.unwrap()
1880+
.get(&window_id)
1881+
.unwrap()
1882+
.lock()
1883+
.unwrap()
1884+
.values()
1885+
{
1886+
handler(&WindowEvent::Destroyed);
1887+
}
1888+
}
18651889
}
18661890

18671891
fn center_window(window: &Window) -> Result<()> {

core/tauri-runtime/src/lib.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
77
#![cfg_attr(doc_cfg, feature(doc_cfg))]
88

9-
use std::{fmt::Debug, hash::Hash, path::PathBuf};
9+
use std::{fmt::Debug, hash::Hash, path::PathBuf, sync::mpsc::Sender};
1010

1111
use serde::{Deserialize, Serialize};
1212
use tauri_utils::assets::Assets;
@@ -189,9 +189,17 @@ impl Icon {
189189
}
190190

191191
/// Event triggered on the event loop run.
192+
#[non_exhaustive]
192193
pub enum RunEvent {
193194
/// Event loop is exiting.
194195
Exit,
196+
/// Window close was requested by the user.
197+
CloseRequested {
198+
/// The window label.
199+
label: String,
200+
/// A signal sender. If a `true` value is emitted, the window won't be closed.
201+
signal_tx: Sender<bool>,
202+
},
195203
/// Window closed.
196204
WindowClose(String),
197205
}

core/tauri/src/api/dialog.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ impl FileDialogBuilder {
6565
}
6666

6767
/// Response for the ask dialog
68+
#[derive(Debug, Clone, PartialEq, Eq)]
6869
pub enum AskResponse {
6970
/// User confirmed.
7071
Yes,

core/tauri/src/app.rs

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ use crate::{
2525

2626
use tauri_utils::PackageInfo;
2727

28-
use std::{collections::HashMap, path::PathBuf, sync::Arc};
28+
use std::{
29+
collections::HashMap,
30+
path::PathBuf,
31+
sync::{mpsc::Sender, Arc},
32+
};
2933

3034
#[cfg(feature = "menu")]
3135
use crate::runtime::menu::Menu;
@@ -45,6 +49,33 @@ pub(crate) type GlobalWindowEventListener<P> = Box<dyn Fn(GlobalWindowEvent<P>)
4549
type SystemTrayEventListener<P> =
4650
Box<dyn Fn(&AppHandle<P>, tray::SystemTrayEvent<<P as Params>::SystemTrayMenuId>) + Send + Sync>;
4751

52+
/// Api exposed on the `CloseRequested` event.
53+
pub struct CloseRequestApi(Sender<bool>);
54+
55+
impl CloseRequestApi {
56+
/// Prevents the window from being closed.
57+
pub fn prevent_close(&self) {
58+
self.0.send(true).unwrap();
59+
}
60+
}
61+
62+
/// An application event, triggered from the event loop.
63+
#[non_exhaustive]
64+
pub enum Event<P: Params> {
65+
/// Event loop is exiting.
66+
Exit,
67+
/// Window close was requested by the user.
68+
#[non_exhaustive]
69+
CloseRequested {
70+
/// The window label.
71+
label: P::Label,
72+
/// Event API.
73+
api: CloseRequestApi,
74+
},
75+
/// Window closed.
76+
WindowClosed(P::Label),
77+
}
78+
4879
crate::manager::default_args! {
4980
/// A menu event that was triggered on a window.
5081
#[cfg(feature = "menu")]
@@ -271,6 +302,42 @@ impl<P: Params> App<P> {
271302
self.handle.clone()
272303
}
273304

305+
/// Runs the application.
306+
pub fn run<F: Fn(&AppHandle<P>, Event<P>) + 'static>(mut self, callback: F) {
307+
let app_handle = self.handle();
308+
let manager = self.manager.clone();
309+
self.runtime.take().unwrap().run(move |event| match event {
310+
RunEvent::Exit => {
311+
#[cfg(shell_execute)]
312+
{
313+
crate::api::process::kill_children();
314+
}
315+
#[cfg(all(windows, feature = "system-tray"))]
316+
{
317+
let _ = app_handle.remove_system_tray();
318+
}
319+
callback(&app_handle, Event::Exit);
320+
}
321+
_ => {
322+
on_event_loop_event(&event, &manager);
323+
callback(
324+
&app_handle,
325+
match event {
326+
RunEvent::Exit => Event::Exit,
327+
RunEvent::CloseRequested { label, signal_tx } => Event::CloseRequested {
328+
label: label.parse().unwrap_or_else(|_| unreachable!()),
329+
api: CloseRequestApi(signal_tx),
330+
},
331+
RunEvent::WindowClose(label) => {
332+
Event::WindowClosed(label.parse().unwrap_or_else(|_| unreachable!()))
333+
}
334+
_ => unimplemented!(),
335+
},
336+
);
337+
}
338+
});
339+
}
340+
274341
/// Runs a iteration of the runtime event loop and immediately return.
275342
///
276343
/// Note that when using this API, app cleanup is not automatically done.
@@ -297,7 +364,7 @@ impl<P: Params> App<P> {
297364
.runtime
298365
.as_mut()
299366
.unwrap()
300-
.run_iteration(move |event| on_event_loop_event(event, &manager))
367+
.run_iteration(move |event| on_event_loop_event(&event, &manager))
301368
}
302369
}
303370

@@ -855,28 +922,12 @@ where
855922

856923
/// Runs the configured Tauri application.
857924
pub fn run(self, context: Context<A>) -> crate::Result<()> {
858-
let mut app = self.build(context)?;
859-
#[cfg(all(windows, feature = "system-tray"))]
860-
let app_handle = app.handle();
861-
let manager = app.manager.clone();
862-
app.runtime.take().unwrap().run(move |event| match event {
863-
RunEvent::Exit => {
864-
#[cfg(shell_execute)]
865-
{
866-
crate::api::process::kill_children();
867-
}
868-
#[cfg(all(windows, feature = "system-tray"))]
869-
{
870-
let _ = app_handle.remove_system_tray();
871-
}
872-
}
873-
_ => on_event_loop_event(event, &manager),
874-
});
925+
self.build(context)?.run(|_, _| {});
875926
Ok(())
876927
}
877928
}
878929

879-
fn on_event_loop_event<P: Params>(event: RunEvent, manager: &WindowManager<P>) {
930+
fn on_event_loop_event<P: Params>(event: &RunEvent, manager: &WindowManager<P>) {
880931
if let RunEvent::WindowClose(label) = event {
881932
manager.on_window_close(label);
882933
}

core/tauri/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ pub type Result<T> = std::result::Result<T, Error>;
5959
pub type SyncTask = Box<dyn FnOnce() + Send>;
6060

6161
use crate::{
62-
event::{Event, EventHandler},
62+
event::{Event as EmittedEvent, EventHandler},
6363
runtime::window::PendingWindow,
6464
};
6565
use serde::Serialize;
@@ -83,7 +83,7 @@ pub use {
8383
config::{Config, WindowUrl},
8484
PackageInfo,
8585
},
86-
self::app::{App, AppHandle, Builder, GlobalWindowEvent},
86+
self::app::{App, AppHandle, Builder, CloseRequestApi, Event, GlobalWindowEvent},
8787
self::hooks::{
8888
Invoke, InvokeError, InvokeHandler, InvokeMessage, InvokeResolver, InvokeResponse, OnPageLoad,
8989
PageLoadPayload, SetupHook,
@@ -283,15 +283,15 @@ pub trait Manager<P: Params>: sealed::ManagerBase<P> {
283283
/// Listen to a global event.
284284
fn listen_global<E: Into<P::Event>, F>(&self, event: E, handler: F) -> EventHandler
285285
where
286-
F: Fn(Event) + Send + 'static,
286+
F: Fn(EmittedEvent) + Send + 'static,
287287
{
288288
self.manager().listen(event.into(), None, handler)
289289
}
290290

291291
/// Listen to a global event only once.
292292
fn once_global<E: Into<P::Event>, F>(&self, event: E, handler: F) -> EventHandler
293293
where
294-
F: Fn(Event) + Send + 'static,
294+
F: Fn(EmittedEvent) + Send + 'static,
295295
{
296296
self.manager().once(event.into(), None, handler)
297297
}

core/tauri/src/manager.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,7 @@ impl<P: Params> WindowManager<P> {
753753
window
754754
}
755755

756-
pub(crate) fn on_window_close(&self, label: String) {
756+
pub(crate) fn on_window_close(&self, label: &str) {
757757
self
758758
.windows_lock()
759759
.remove(&label.parse().unwrap_or_else(|_| panic!("bad label")));
@@ -886,6 +886,14 @@ fn on_window_event<P: Params>(
886886
.unwrap_or_else(|_| panic!("unhandled event")),
887887
Some(()),
888888
)?;
889+
}
890+
WindowEvent::Destroyed => {
891+
window.emit(
892+
&WINDOW_DESTROYED_EVENT
893+
.parse()
894+
.unwrap_or_else(|_| panic!("unhandled event")),
895+
Some(()),
896+
)?;
889897
let label = window.label();
890898
for window in manager.inner.windows.lock().unwrap().values() {
891899
window.eval(&format!(
@@ -894,12 +902,6 @@ fn on_window_event<P: Params>(
894902
))?;
895903
}
896904
}
897-
WindowEvent::Destroyed => window.emit(
898-
&WINDOW_DESTROYED_EVENT
899-
.parse()
900-
.unwrap_or_else(|_| panic!("unhandled event")),
901-
Some(()),
902-
)?,
903905
WindowEvent::Focused(focused) => window.emit(
904906
&if *focused {
905907
WINDOW_FOCUS_EVENT

examples/api/public/build/bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/api/public/build/bundle.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)