diff --git a/.changes/core-listener-emitter-trait.md b/.changes/core-listener-emitter-trait.md new file mode 100644 index 000000000..63cd8932d --- /dev/null +++ b/.changes/core-listener-emitter-trait.md @@ -0,0 +1,15 @@ +--- +"tauri": "patch:breaking" +--- + +Added `Emitter` and `Listener` traits that defines what an emitter or a listener can do, this however comes with a few breaking changes: +- Removed `Manager::listen_any`, use `Listener::listen_any` instead. +- Removed `Manager::once_any`, use `Listener::once_any` instead. +- Removed `Manager::unlisten`, use `Listener::unlisten` instead. +- Removed `Manager::emit`, use `Emitter::emit` instead. +- Removed `Manager::emit_to`, use `Emitter::emit_to` instead. +- Removed `Manager::emit_filter`, use `Emitter::emit_filter` instead. +- Removed `App/AppHandle::listen`, `WebviewWindow::listen`, `Window::listen` and `Webview::listen`, use `Listener::listen` instead. +- Removed `App/AppHandle::once`, `WebviewWindow::once`, `Window::once` and `Webview::once`, use `Listener::once` instead. +- Removed `App/AppHandle::unlisten`, `WebviewWindow::unlisten`, `Window::unlisten` and `Webview::unlisten`, use `Listener::unlisten` instead. + diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 7c363b656..4b76f89de 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -22,8 +22,8 @@ use crate::{ utils::config::Config, utils::Env, webview::PageLoadPayload, - Context, DeviceEventFilter, EventLoopMessage, Manager, Monitor, Runtime, Scopes, StateManager, - Theme, Webview, WebviewWindowBuilder, Window, + Context, DeviceEventFilter, Emitter, EventLoopMessage, Listener, Manager, Monitor, Result, + Runtime, Scopes, StateManager, Theme, Webview, WebviewWindowBuilder, Window, }; #[cfg(desktop)] @@ -42,6 +42,7 @@ use tauri_runtime::{ }; use tauri_utils::PackageInfo; +use serde::Serialize; use std::{ borrow::Cow, collections::HashMap, @@ -66,7 +67,7 @@ pub(crate) type GlobalWebviewEventListener = Box, &WebviewEvent) + Send + Sync>; /// A closure that is run when the Tauri application is setting up. pub type SetupHook = - Box) -> Result<(), Box> + Send>; + Box) -> std::result::Result<(), Box> + Send>; /// A closure that is run every time a page starts or finishes loading. pub type OnPageLoad = dyn Fn(&Webview, &PageLoadPayload<'_>) + Send + Sync + 'static; @@ -328,7 +329,7 @@ impl Clone for AppHandle { impl<'de, R: Runtime> CommandArg<'de, R> for AppHandle { /// Grabs the [`Window`] from the [`CommandItem`] and returns the associated [`AppHandle`]. This will never fail. - fn from_command(command: CommandItem<'de, R>) -> Result { + fn from_command(command: CommandItem<'de, R>) -> std::result::Result { Ok(command.message.webview().window().app_handle) } } @@ -820,14 +821,13 @@ macro_rules! shared_app_impl { } } - /// Event system APIs. - impl $app { + impl Listener for $app { /// Listen to an event on this app. /// /// # Examples /// /// ``` - /// use tauri::Manager; + /// use tauri::Listener; /// /// tauri::Builder::default() /// .setup(|app| { @@ -838,19 +838,29 @@ macro_rules! shared_app_impl { /// Ok(()) /// }); /// ``` - pub fn listen(&self, event: impl Into, handler: F) -> EventId + fn listen(&self, event: impl Into, handler: F) -> EventId where F: Fn(Event) + Send + 'static, { self.manager.listen(event.into(), EventTarget::App, handler) } + /// Listen to an event on this app only once. + /// + /// See [`Self::listen`] for more information. + fn once(&self, event: impl Into, handler: F) -> EventId + where + F: FnOnce(Event) + Send + 'static, + { + self.manager.once(event.into(), EventTarget::App, handler) + } + /// Unlisten to an event on this app. /// /// # Examples /// /// ``` - /// use tauri::Manager; + /// use tauri::Listener; /// /// tauri::Builder::default() /// .setup(|app| { @@ -864,18 +874,82 @@ macro_rules! shared_app_impl { /// Ok(()) /// }); /// ``` - pub fn unlisten(&self, id: EventId) { + fn unlisten(&self, id: EventId) { self.manager.unlisten(id) } + } - /// Listen to an event on this app only once. + impl Emitter for $app { + /// Emits an event to all [targets](EventTarget). /// - /// See [`Self::listen`] for more information. - pub fn once(&self, event: impl Into, handler: F) -> EventId + /// # Examples + /// ``` + /// use tauri::Emitter; + /// + /// #[tauri::command] + /// fn synchronize(app: tauri::AppHandle) { + /// // emits the synchronized event to all webviews + /// app.emit("synchronized", ()); + /// } + /// ``` + fn emit(&self, event: &str, payload: S) -> Result<()> { + self.manager.emit(event, payload) + } + + /// Emits an event to all [targets](EventTarget) matching the given target. + /// + /// # Examples + /// ``` + /// use tauri::{Emitter, EventTarget}; + /// + /// #[tauri::command] + /// fn download(app: tauri::AppHandle) { + /// for i in 1..100 { + /// std::thread::sleep(std::time::Duration::from_millis(150)); + /// // emit a download progress event to all listeners + /// app.emit_to(EventTarget::any(), "download-progress", i); + /// // emit an event to listeners that used App::listen or AppHandle::listen + /// app.emit_to(EventTarget::app(), "download-progress", i); + /// // emit an event to any webview/window/webviewWindow matching the given label + /// app.emit_to("updater", "download-progress", i); // similar to using EventTarget::labeled + /// app.emit_to(EventTarget::labeled("updater"), "download-progress", i); + /// // emit an event to listeners that used WebviewWindow::listen + /// app.emit_to(EventTarget::webview_window("updater"), "download-progress", i); + /// } + /// } + /// ``` + fn emit_to(&self, target: I, event: &str, payload: S) -> Result<()> where - F: FnOnce(Event) + Send + 'static, + I: Into, + S: Serialize + Clone, { - self.manager.once(event.into(), EventTarget::App, handler) + self.manager.emit_to(target, event, payload) + } + + /// Emits an event to all [targets](EventTarget) based on the given filter. + /// + /// # Examples + /// ``` + /// use tauri::{Emitter, EventTarget}; + /// + /// #[tauri::command] + /// fn download(app: tauri::AppHandle) { + /// for i in 1..100 { + /// std::thread::sleep(std::time::Duration::from_millis(150)); + /// // emit a download progress event to the updater window + /// app.emit_filter("download-progress", i, |t| match t { + /// EventTarget::WebviewWindow { label } => label == "main", + /// _ => false, + /// }); + /// } + /// } + /// ``` + fn emit_filter(&self, event: &str, payload: S, filter: F) -> Result<()> + where + S: Serialize + Clone, + F: Fn(&EventTarget) -> bool, + { + self.manager.emit_filter(event, payload, filter) } } }; @@ -1239,7 +1313,7 @@ tauri::Builder::default() #[must_use] pub fn setup(mut self, setup: F) -> Self where - F: FnOnce(&mut App) -> Result<(), Box> + Send + 'static, + F: FnOnce(&mut App) -> std::result::Result<(), Box> + Send + 'static, { self.setup = Box::new(setup); self diff --git a/core/tauri/src/event/plugin.rs b/core/tauri/src/event/plugin.rs index 34b404a4d..3857b0c74 100644 --- a/core/tauri/src/event/plugin.rs +++ b/core/tauri/src/event/plugin.rs @@ -9,8 +9,8 @@ use serde_json::Value as JsonValue; use tauri_runtime::window::is_label_valid; use crate::plugin::{Builder, TauriPlugin}; -use crate::{command, ipc::CallbackFn, EventId, Manager, Result, Runtime}; -use crate::{AppHandle, Webview}; +use crate::{command, ipc::CallbackFn, EventId, Result, Runtime}; +use crate::{AppHandle, Emitter, Webview}; use super::{is_event_name_valid, EventTarget}; diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index eabb02d9d..70c9a15d0 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -539,178 +539,6 @@ pub trait Manager: sealed::ManagerBase { self.manager().package_info() } - /// Listen to an emitted event to any [target](EventTarget). - /// - /// # Examples - /// ``` - /// use tauri::Manager; - /// - /// #[tauri::command] - /// fn synchronize(window: tauri::Window) { - /// // emits the synchronized event to all windows - /// window.emit("synchronized", ()); - /// } - /// - /// tauri::Builder::default() - /// .setup(|app| { - /// app.listen_any("synchronized", |event| { - /// println!("app is in sync"); - /// }); - /// Ok(()) - /// }) - /// .invoke_handler(tauri::generate_handler![synchronize]); - /// ``` - fn listen_any(&self, event: impl Into, handler: F) -> EventId - where - F: Fn(Event) + Send + 'static, - { - self - .manager() - .listen(event.into(), EventTarget::Any, handler) - } - - /// Remove an event listener. - /// - /// # Examples - /// ``` - /// use tauri::Manager; - /// - /// tauri::Builder::default() - /// .setup(|app| { - /// let handle = app.handle().clone(); - /// let handler = app.listen_any("ready", move |event| { - /// println!("app is ready"); - /// - /// // we no longer need to listen to the event - /// // we also could have used `app.once_global` instead - /// handle.unlisten(event.id()); - /// }); - /// - /// // stop listening to the event when you do not need it anymore - /// app.unlisten(handler); - /// - /// - /// Ok(()) - /// }); - /// ``` - fn unlisten(&self, id: EventId) { - self.manager().unlisten(id) - } - - /// Listens once to an emitted event to any [target](EventTarget) . - /// - /// See [`Self::listen_any`] for more information. - fn once_any(&self, event: impl Into, handler: F) -> EventId - where - F: FnOnce(Event) + Send + 'static, - { - self.manager().once(event.into(), EventTarget::Any, handler) - } - - /// Emits an event to all [targets](EventTarget). - /// - /// # Examples - /// ``` - /// use tauri::Manager; - /// - /// #[tauri::command] - /// fn synchronize(app: tauri::AppHandle) { - /// // emits the synchronized event to all webviews - /// app.emit("synchronized", ()); - /// } - /// ``` - #[cfg_attr( - feature = "tracing", - tracing::instrument("app::emit", skip(self, payload)) - )] - fn emit(&self, event: &str, payload: S) -> Result<()> { - self.manager().emit(event, payload) - } - - /// Emits an event to all [targets](EventTarget) matching the given target. - /// - /// # Examples - /// ``` - /// use tauri::{Manager, EventTarget}; - /// - /// #[tauri::command] - /// fn download(app: tauri::AppHandle) { - /// for i in 1..100 { - /// std::thread::sleep(std::time::Duration::from_millis(150)); - /// // emit a download progress event to all listeners - /// app.emit_to(EventTarget::any(), "download-progress", i); - /// // emit an event to listeners that used App::listen or AppHandle::listen - /// app.emit_to(EventTarget::app(), "download-progress", i); - /// // emit an event to any webview/window/webviewWindow matching the given label - /// app.emit_to("updater", "download-progress", i); // similar to using EventTarget::labeled - /// app.emit_to(EventTarget::labeled("updater"), "download-progress", i); - /// // emit an event to listeners that used WebviewWindow::listen - /// app.emit_to(EventTarget::webview_window("updater"), "download-progress", i); - /// } - /// } - /// ``` - #[cfg_attr( - feature = "tracing", - tracing::instrument("app::emit::to", skip(self, target, payload), fields(target)) - )] - fn emit_to(&self, target: I, event: &str, payload: S) -> Result<()> - where - I: Into, - S: Serialize + Clone, - { - let target = target.into(); - #[cfg(feature = "tracing")] - tracing::Span::current().record("target", format!("{target:?}")); - - match target { - // if targeting all, emit to all using emit without filter - EventTarget::Any => self.manager().emit(event, payload), - - // if targeting any label, emit using emit_filter and filter labels - EventTarget::AnyLabel { - label: target_label, - } => self.manager().emit_filter(event, payload, |t| match t { - EventTarget::Window { label } - | EventTarget::Webview { label } - | EventTarget::WebviewWindow { label } => label == &target_label, - _ => false, - }), - - // otherwise match same target - _ => self.manager().emit_filter(event, payload, |t| t == &target), - } - } - - /// Emits an event to all [targets](EventTarget) based on the given filter. - /// - /// # Examples - /// ``` - /// use tauri::{Manager, EventTarget}; - /// - /// #[tauri::command] - /// fn download(app: tauri::AppHandle) { - /// for i in 1..100 { - /// std::thread::sleep(std::time::Duration::from_millis(150)); - /// // emit a download progress event to the updater window - /// app.emit_filter("download-progress", i, |t| match t { - /// EventTarget::WebviewWindow { label } => label == "main", - /// _ => false, - /// }); - /// } - /// } - /// ``` - #[cfg_attr( - feature = "tracing", - tracing::instrument("app::emit::filter", skip(self, payload, filter)) - )] - fn emit_filter(&self, event: &str, payload: S, filter: F) -> Result<()> - where - S: Serialize + Clone, - F: Fn(&EventTarget) -> bool, - { - self.manager().emit_filter(event, payload, filter) - } - /// Fetch a single window from the manager. #[cfg(feature = "unstable")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] @@ -933,6 +761,174 @@ pub trait Manager: sealed::ManagerBase { } } +/// Listen to events. +pub trait Listener: sealed::ManagerBase { + /// Listen to an emitted event on this manager. + /// + /// # Examples + /// ``` + /// use tauri::{Manager, Listener, Emitter}; + /// + /// #[tauri::command] + /// fn synchronize(window: tauri::Window) { + /// // emits the synchronized event to all windows + /// window.emit("synchronized", ()); + /// } + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// app.listen("synchronized", |event| { + /// println!("app is in sync"); + /// }); + /// Ok(()) + /// }) + /// .invoke_handler(tauri::generate_handler![synchronize]); + /// ``` + fn listen(&self, event: impl Into, handler: F) -> EventId + where + F: Fn(Event) + Send + 'static; + + /// Listen to an event on this manager only once. + /// + /// See [`Self::listen`] for more information. + fn once(&self, event: impl Into, handler: F) -> EventId + where + F: FnOnce(Event) + Send + 'static; + + /// Remove an event listener. + /// + /// # Examples + /// ``` + /// use tauri::{Manager, Listener}; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let handle = app.handle().clone(); + /// let handler = app.listen_any("ready", move |event| { + /// println!("app is ready"); + /// + /// // we no longer need to listen to the event + /// // we also could have used `app.once_global` instead + /// handle.unlisten(event.id()); + /// }); + /// + /// // stop listening to the event when you do not need it anymore + /// app.unlisten(handler); + /// + /// + /// Ok(()) + /// }); + /// ``` + fn unlisten(&self, id: EventId); + + /// Listen to an emitted event to any [target](EventTarget). + /// + /// # Examples + /// ``` + /// use tauri::{Manager, Emitter, Listener}; + /// + /// #[tauri::command] + /// fn synchronize(window: tauri::Window) { + /// // emits the synchronized event to all windows + /// window.emit("synchronized", ()); + /// } + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// app.listen_any("synchronized", |event| { + /// println!("app is in sync"); + /// }); + /// Ok(()) + /// }) + /// .invoke_handler(tauri::generate_handler![synchronize]); + /// ``` + fn listen_any(&self, event: impl Into, handler: F) -> EventId + where + F: Fn(Event) + Send + 'static, + { + self + .manager() + .listen(event.into(), EventTarget::Any, handler) + } + + /// Listens once to an emitted event to any [target](EventTarget) . + /// + /// See [`Self::listen_any`] for more information. + fn once_any(&self, event: impl Into, handler: F) -> EventId + where + F: FnOnce(Event) + Send + 'static, + { + self.manager().once(event.into(), EventTarget::Any, handler) + } +} + +/// Emit events. +pub trait Emitter: sealed::ManagerBase { + /// Emits an event to all [targets](EventTarget). + /// + /// # Examples + /// ``` + /// use tauri::Emitter; + /// + /// #[tauri::command] + /// fn synchronize(app: tauri::AppHandle) { + /// // emits the synchronized event to all webviews + /// app.emit("synchronized", ()); + /// } + /// ``` + fn emit(&self, event: &str, payload: S) -> Result<()>; + + /// Emits an event to all [targets](EventTarget) matching the given target. + /// + /// # Examples + /// ``` + /// use tauri::{Emitter, EventTarget}; + /// + /// #[tauri::command] + /// fn download(app: tauri::AppHandle) { + /// for i in 1..100 { + /// std::thread::sleep(std::time::Duration::from_millis(150)); + /// // emit a download progress event to all listeners + /// app.emit_to(EventTarget::any(), "download-progress", i); + /// // emit an event to listeners that used App::listen or AppHandle::listen + /// app.emit_to(EventTarget::app(), "download-progress", i); + /// // emit an event to any webview/window/webviewWindow matching the given label + /// app.emit_to("updater", "download-progress", i); // similar to using EventTarget::labeled + /// app.emit_to(EventTarget::labeled("updater"), "download-progress", i); + /// // emit an event to listeners that used WebviewWindow::listen + /// app.emit_to(EventTarget::webview_window("updater"), "download-progress", i); + /// } + /// } + /// ``` + fn emit_to(&self, target: I, event: &str, payload: S) -> Result<()> + where + I: Into, + S: Serialize + Clone; + + /// Emits an event to all [targets](EventTarget) based on the given filter. + /// + /// # Examples + /// ``` + /// use tauri::{Emitter, EventTarget}; + /// + /// #[tauri::command] + /// fn download(app: tauri::AppHandle) { + /// for i in 1..100 { + /// std::thread::sleep(std::time::Duration::from_millis(150)); + /// // emit a download progress event to the updater window + /// app.emit_filter("download-progress", i, |t| match t { + /// EventTarget::WebviewWindow { label } => label == "main", + /// _ => false, + /// }); + /// } + /// } + /// ``` + fn emit_filter(&self, event: &str, payload: S, filter: F) -> Result<()> + where + S: Serialize + Clone, + F: Fn(&EventTarget) -> bool; +} + /// Prevent implementation details from leaking out of the [`Manager`] trait. pub(crate) mod sealed { use super::Runtime; diff --git a/core/tauri/src/manager/mod.rs b/core/tauri/src/manager/mod.rs index a94ab8a27..8410e9e2b 100644 --- a/core/tauri/src/manager/mod.rs +++ b/core/tauri/src/manager/mod.rs @@ -472,10 +472,6 @@ impl AppManager { self.listeners().listen(event, target, handler) } - pub fn unlisten(&self, id: EventId) { - self.listeners().unlisten(id) - } - pub fn once( &self, event: String, @@ -486,6 +482,33 @@ impl AppManager { self.listeners().once(event, target, handler) } + pub fn unlisten(&self, id: EventId) { + self.listeners().unlisten(id) + } + + #[cfg_attr( + feature = "tracing", + tracing::instrument("app::emit", skip(self, payload)) + )] + pub fn emit(&self, event: &str, payload: S) -> crate::Result<()> { + assert_event_name_is_valid(event); + + #[cfg(feature = "tracing")] + let _span = tracing::debug_span!("emit::run").entered(); + let emit_args = EmitArgs::new(event, payload)?; + + let listeners = self.listeners(); + + listeners.emit_js(self.webview.webviews_lock().values(), event, &emit_args)?; + listeners.emit(emit_args)?; + + Ok(()) + } + + #[cfg_attr( + feature = "tracing", + tracing::instrument("app::emit::filter", skip(self, payload, filter)) + )] pub fn emit_filter(&self, event: &str, payload: S, filter: F) -> crate::Result<()> where S: Serialize + Clone, @@ -511,19 +534,36 @@ impl AppManager { Ok(()) } - pub fn emit(&self, event: &str, payload: S) -> crate::Result<()> { - assert_event_name_is_valid(event); - + #[cfg_attr( + feature = "tracing", + tracing::instrument("app::emit::to", skip(self, target, payload), fields(target)) + )] + pub fn emit_to(&self, target: I, event: &str, payload: S) -> crate::Result<()> + where + I: Into, + S: Serialize + Clone, + { + let target = target.into(); #[cfg(feature = "tracing")] - let _span = tracing::debug_span!("emit::run").entered(); - let emit_args = EmitArgs::new(event, payload)?; + tracing::Span::current().record("target", format!("{target:?}")); - let listeners = self.listeners(); + match target { + // if targeting all, emit to all using emit without filter + EventTarget::Any => self.emit(event, payload), - listeners.emit_js(self.webview.webviews_lock().values(), event, &emit_args)?; - listeners.emit(emit_args)?; + // if targeting any label, emit using emit_filter and filter labels + EventTarget::AnyLabel { + label: target_label, + } => self.emit_filter(event, payload, |t| match t { + EventTarget::Window { label } + | EventTarget::Webview { label } + | EventTarget::WebviewWindow { label } => label == &target_label, + _ => false, + }), - Ok(()) + // otherwise match same target + _ => self.emit_filter(event, payload, |t| t == &target), + } } pub fn get_window(&self, label: &str) -> Option> { @@ -634,7 +674,8 @@ mod test { test::{mock_app, MockRuntime}, webview::WebviewBuilder, window::WindowBuilder, - App, Manager, StateManager, Webview, WebviewWindow, WebviewWindowBuilder, Window, Wry, + App, Emitter, Listener, Manager, StateManager, Webview, WebviewWindow, WebviewWindowBuilder, + Window, Wry, }; use super::AppManager; @@ -780,7 +821,11 @@ mod test { run_emit_test("emit (webview_window)", webview_window, &rx); } - fn run_emit_test>(kind: &str, m: M, rx: &Receiver<(&str, String)>) { + fn run_emit_test + Emitter>( + kind: &str, + m: M, + rx: &Receiver<(&str, String)>, + ) { let mut received = Vec::new(); let payload = "global-payload"; m.emit(TEST_EVENT_NAME, payload).unwrap(); @@ -853,7 +898,7 @@ mod test { ); } - fn run_emit_to_test>( + fn run_emit_to_test + Emitter>( kind: &str, m: &M, window: &Window, diff --git a/core/tauri/src/manager/webview.rs b/core/tauri/src/manager/webview.rs index d1001bbf3..b67dbb86e 100644 --- a/core/tauri/src/manager/webview.rs +++ b/core/tauri/src/manager/webview.rs @@ -25,7 +25,7 @@ use crate::{ pattern::PatternJavascript, sealed::ManagerBase, webview::PageLoadPayload, - AppHandle, EventLoopMessage, EventTarget, Manager, Runtime, Scopes, Webview, Window, + AppHandle, Emitter, EventLoopMessage, EventTarget, Manager, Runtime, Scopes, Webview, Window, }; use super::{ diff --git a/core/tauri/src/manager/window.rs b/core/tauri/src/manager/window.rs index 17c8e3ea7..198805972 100644 --- a/core/tauri/src/manager/window.rs +++ b/core/tauri/src/manager/window.rs @@ -17,8 +17,8 @@ use tauri_runtime::{ }; use crate::{ - app::GlobalWindowEventListener, image::Image, sealed::ManagerBase, AppHandle, EventLoopMessage, - EventTarget, Manager, Runtime, Scopes, Window, WindowEvent, + app::GlobalWindowEventListener, image::Image, sealed::ManagerBase, AppHandle, Emitter, + EventLoopMessage, EventTarget, Manager, Runtime, Scopes, Window, WindowEvent, }; const WINDOW_RESIZED_EVENT: &str = "tauri://resize"; diff --git a/core/tauri/src/webview/mod.rs b/core/tauri/src/webview/mod.rs index 4bfec0480..f19b90ab7 100644 --- a/core/tauri/src/webview/mod.rs +++ b/core/tauri/src/webview/mod.rs @@ -34,7 +34,8 @@ use crate::{ }, manager::{webview::WebviewLabelDef, AppManager}, sealed::{ManagerBase, RuntimeOrDispatch}, - AppHandle, Event, EventId, EventLoopMessage, Manager, ResourceTable, Runtime, Window, + AppHandle, Emitter, Event, EventId, EventLoopMessage, Listener, Manager, ResourceTable, Runtime, + Window, }; use std::{ @@ -1484,8 +1485,7 @@ tauri::Builder::default() } } -/// Event system APIs. -impl Webview { +impl Listener for Webview { /// Listen to an event on this webview. /// /// # Examples @@ -1493,13 +1493,13 @@ impl Webview { feature = "unstable", doc = r####" ``` -use tauri::Manager; +use tauri::{Manager, Listener}; tauri::Builder::default() .setup(|app| { let webview = app.get_webview("main").unwrap(); webview.listen("component-loaded", move |event| { - println!("window just loaded a component"); + println!("webview just loaded a component"); }); Ok(()) @@ -1507,7 +1507,7 @@ tauri::Builder::default() ``` "#### )] - pub fn listen(&self, event: impl Into, handler: F) -> EventId + fn listen(&self, event: impl Into, handler: F) -> EventId where F: Fn(Event) + Send + 'static, { @@ -1520,6 +1520,22 @@ tauri::Builder::default() ) } + /// Listen to an event on this webview only once. + /// + /// See [`Self::listen`] for more information. + fn once(&self, event: impl Into, handler: F) -> EventId + where + F: FnOnce(Event) + Send + 'static, + { + self.manager.once( + event.into(), + EventTarget::Webview { + label: self.label().to_string(), + }, + handler, + ) + } + /// Unlisten to an event on this webview. /// /// # Examples @@ -1527,7 +1543,7 @@ tauri::Builder::default() feature = "unstable", doc = r####" ``` -use tauri::Manager; +use tauri::{Manager, Listener}; tauri::Builder::default() .setup(|app| { @@ -1549,24 +1565,97 @@ tauri::Builder::default() ``` "#### )] - pub fn unlisten(&self, id: EventId) { + fn unlisten(&self, id: EventId) { self.manager.unlisten(id) } +} - /// Listen to an event on this webview only once. +impl Emitter for Webview { + /// Emits an event to all [targets](EventTarget). /// - /// See [`Self::listen`] for more information. - pub fn once(&self, event: impl Into, handler: F) -> EventId + /// # Examples + #[cfg_attr( + feature = "unstable", + doc = r####" +``` +use tauri::Emitter; + +#[tauri::command] +fn synchronize(webview: tauri::Webview) { + // emits the synchronized event to all webviews + webview.emit("synchronized", ()); +} + ``` + "#### + )] + fn emit(&self, event: &str, payload: S) -> crate::Result<()> { + self.manager.emit(event, payload) + } + + /// Emits an event to all [targets](EventTarget) matching the given target. + /// + /// # Examples + #[cfg_attr( + feature = "unstable", + doc = r####" +``` +use tauri::{Emitter, EventTarget}; + +#[tauri::command] +fn download(webview: tauri::Webview) { + for i in 1..100 { + std::thread::sleep(std::time::Duration::from_millis(150)); + // emit a download progress event to all listeners + webview.emit_to(EventTarget::any(), "download-progress", i); + // emit an event to listeners that used App::listen or AppHandle::listen + webview.emit_to(EventTarget::app(), "download-progress", i); + // emit an event to any webview/window/webviewWindow matching the given label + webview.emit_to("updater", "download-progress", i); // similar to using EventTarget::labeled + webview.emit_to(EventTarget::labeled("updater"), "download-progress", i); + // emit an event to listeners that used WebviewWindow::listen + webview.emit_to(EventTarget::webview_window("updater"), "download-progress", i); + } +} +``` +"#### + )] + fn emit_to(&self, target: I, event: &str, payload: S) -> crate::Result<()> where - F: FnOnce(Event) + Send + 'static, + I: Into, + S: Serialize + Clone, { - self.manager.once( - event.into(), - EventTarget::Webview { - label: self.label().to_string(), - }, - handler, - ) + self.manager.emit_to(target, event, payload) + } + + /// Emits an event to all [targets](EventTarget) based on the given filter. + /// + /// # Examples + #[cfg_attr( + feature = "unstable", + doc = r####" +``` +use tauri::{Emitter, EventTarget}; + +#[tauri::command] +fn download(webview: tauri::Webview) { + for i in 1..100 { + std::thread::sleep(std::time::Duration::from_millis(150)); + // emit a download progress event to the updater window + webview.emit_filter("download-progress", i, |t| match t { + EventTarget::WebviewWindow { label } => label == "main", + _ => false, + }); + } +} + ``` + "#### + )] + fn emit_filter(&self, event: &str, payload: S, filter: F) -> crate::Result<()> + where + S: Serialize + Clone, + F: Fn(&EventTarget) -> bool, + { + self.manager.emit_filter(event, payload, filter) } } diff --git a/core/tauri/src/webview/webview_window.rs b/core/tauri/src/webview/webview_window.rs index 1a1ff00aa..791bfd0ac 100644 --- a/core/tauri/src/webview/webview_window.rs +++ b/core/tauri/src/webview/webview_window.rs @@ -14,7 +14,7 @@ use crate::{ event::EventTarget, runtime::dpi::{PhysicalPosition, PhysicalSize}, window::Monitor, - ResourceTable, + Emitter, Listener, ResourceTable, }; #[cfg(desktop)] use crate::{ @@ -26,6 +26,7 @@ use crate::{ UserAttentionType, }, }; +use serde::Serialize; use tauri_utils::config::{WebviewUrl, WindowConfig}; use url::Url; @@ -189,40 +190,34 @@ impl<'a, R: Runtime, M: Manager> WebviewWindowBuilder<'a, R, M> { /// but it might be implemented in the future. **Always** check the request URL. /// /// # Examples - #[cfg_attr( - feature = "unstable", - doc = r####" -```rust,no_run -use tauri::{ - utils::config::{Csp, CspDirectiveSources, WebviewUrl}, - window::WindowBuilder, - webview::WebviewWindowBuilder, -}; -use http::header::HeaderValue; -use std::collections::HashMap; -tauri::Builder::default() - .setup(|app| { - let webview_window = WebviewWindowBuilder::new(app, "core", WebviewUrl::App("index.html".into())) - .on_web_resource_request(|request, response| { - if request.uri().scheme_str() == Some("tauri") { - // if we have a CSP header, Tauri is loading an HTML file - // for this example, let's dynamically change the CSP - if let Some(csp) = response.headers_mut().get_mut("Content-Security-Policy") { - // use the tauri helper to parse the CSP policy to a map - let mut csp_map: HashMap = Csp::Policy(csp.to_str().unwrap().to_string()).into(); - csp_map.entry("script-src".to_string()).or_insert_with(Default::default).push("'unsafe-inline'"); - // use the tauri helper to get a CSP string from the map - let csp_string = Csp::from(csp_map).to_string(); - *csp = HeaderValue::from_str(&csp_string).unwrap(); - } - } - }) - .build()?; - Ok(()) - }); -``` - "#### - )] + /// ```rust,no_run + /// use tauri::{ + /// utils::config::{Csp, CspDirectiveSources, WebviewUrl}, + /// webview::WebviewWindowBuilder, + /// }; + /// use http::header::HeaderValue; + /// use std::collections::HashMap; + /// tauri::Builder::default() + /// .setup(|app| { + /// let webview_window = WebviewWindowBuilder::new(app, "core", WebviewUrl::App("index.html".into())) + /// .on_web_resource_request(|request, response| { + /// if request.uri().scheme_str() == Some("tauri") { + /// // if we have a CSP header, Tauri is loading an HTML file + /// // for this example, let's dynamically change the CSP + /// if let Some(csp) = response.headers_mut().get_mut("Content-Security-Policy") { + /// // use the tauri helper to parse the CSP policy to a map + /// let mut csp_map: HashMap = Csp::Policy(csp.to_str().unwrap().to_string()).into(); + /// csp_map.entry("script-src".to_string()).or_insert_with(Default::default).push("'unsafe-inline'"); + /// // use the tauri helper to get a CSP string from the map + /// let csp_string = Csp::from(csp_map).to_string(); + /// *csp = HeaderValue::from_str(&csp_string).unwrap(); + /// } + /// } + /// }) + /// .build()?; + /// Ok(()) + /// }); + /// ``` pub fn on_web_resource_request< F: Fn(http::Request>, &mut http::Response>) + Send + Sync + 'static, >( @@ -236,30 +231,24 @@ tauri::Builder::default() /// Defines a closure to be executed when the webview navigates to a URL. Returning `false` cancels the navigation. /// /// # Examples - #[cfg_attr( - feature = "unstable", - doc = r####" -```rust,no_run -use tauri::{ - utils::config::{Csp, CspDirectiveSources, WebviewUrl}, - window::WindowBuilder, - webview::WebviewWindowBuilder, -}; -use http::header::HeaderValue; -use std::collections::HashMap; -tauri::Builder::default() - .setup(|app| { - let webview_window = WebviewWindowBuilder::new(app, "core", WebviewUrl::App("index.html".into())) - .on_navigation(|url| { - // allow the production URL or localhost on dev - url.scheme() == "tauri" || (cfg!(dev) && url.host_str() == Some("localhost")) - }) - .build()?; - Ok(()) - }); -``` - "#### - )] + /// ```rust,no_run + /// use tauri::{ + /// utils::config::{Csp, CspDirectiveSources, WebviewUrl}, + /// webview::WebviewWindowBuilder, + /// }; + /// use http::header::HeaderValue; + /// use std::collections::HashMap; + /// tauri::Builder::default() + /// .setup(|app| { + /// let webview_window = WebviewWindowBuilder::new(app, "core", WebviewUrl::App("index.html".into())) + /// .on_navigation(|url| { + /// // allow the production URL or localhost on dev + /// url.scheme() == "tauri" || (cfg!(dev) && url.host_str() == Some("localhost")) + /// }) + /// .build()?; + /// Ok(()) + /// }); + /// ``` pub fn on_navigation bool + Send + 'static>(mut self, f: F) -> Self { self.webview_builder = self.webview_builder.on_navigation(f); self @@ -318,36 +307,30 @@ tauri::Builder::default() /// or [`tauri_runtime::webview::PageLoadEvent::Finished`] when the page finishes loading. /// /// # Examples - #[cfg_attr( - feature = "unstable", - doc = r####" -```rust,no_run -use tauri::{ - utils::config::{Csp, CspDirectiveSources, WebviewUrl}, - window::WindowBuilder, - webview::{PageLoadEvent, WebviewWindowBuilder}, -}; -use http::header::HeaderValue; -use std::collections::HashMap; -tauri::Builder::default() - .setup(|app| { - let webview_window = WebviewWindowBuilder::new(app, "core", WebviewUrl::App("index.html".into())) - .on_page_load(|window, payload| { - match payload.event() { - PageLoadEvent::Started => { - println!("{} finished loading", payload.url()); - } - PageLoadEvent::Finished => { - println!("{} finished loading", payload.url()); - } - } - }) - .build()?; - Ok(()) - }); -``` - "#### - )] + /// ```rust,no_run + /// use tauri::{ + /// utils::config::{Csp, CspDirectiveSources, WebviewUrl}, + /// webview::{PageLoadEvent, WebviewWindowBuilder}, + /// }; + /// use http::header::HeaderValue; + /// use std::collections::HashMap; + /// tauri::Builder::default() + /// .setup(|app| { + /// let webview_window = WebviewWindowBuilder::new(app, "core", WebviewUrl::App("index.html".into())) + /// .on_page_load(|window, payload| { + /// match payload.event() { + /// PageLoadEvent::Started => { + /// println!("{} finished loading", payload.url()); + /// } + /// PageLoadEvent::Finished => { + /// println!("{} finished loading", payload.url()); + /// } + /// } + /// }) + /// .build()?; + /// Ok(()) + /// }); + /// ``` pub fn on_page_load, PageLoadPayload<'_>) + Send + Sync + 'static>( mut self, f: F, @@ -776,32 +759,27 @@ impl<'a, R: Runtime, M: Manager> WebviewWindowBuilder<'a, R, M> { /// /// # Examples /// - #[cfg_attr( - feature = "unstable", - doc = r####" -```rust -use tauri::{WindowBuilder, Runtime}; - -const INIT_SCRIPT: &str = r#" - if (window.location.origin === 'https://tauri.app') { - console.log("hello world from js init script"); - - window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' }; - } -"#; - -fn main() { - tauri::Builder::default() - .setup(|app| { - let webview = tauri::WebviewWindowBuilder::new(app, "label", tauri::WebviewUrl::App("index.html".into())) - .initialization_script(INIT_SCRIPT) - .build()?; - Ok(()) - }); -} -``` - "#### - )] + /// ```rust + /// use tauri::{WebviewWindowBuilder, Runtime}; + /// + /// const INIT_SCRIPT: &str = r#" + /// if (window.location.origin === 'https://tauri.app') { + /// console.log("hello world from js init script"); + /// + /// window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' }; + /// } + /// "#; + /// + /// fn main() { + /// tauri::Builder::default() + /// .setup(|app| { + /// let webview = tauri::WebviewWindowBuilder::new(app, "label", tauri::WebviewUrl::App("index.html".into())) + /// .initialization_script(INIT_SCRIPT) + /// .build()?; + /// Ok(()) + /// }); + /// } + /// ``` #[must_use] pub fn initialization_script(mut self, script: &str) -> Self { self.webview_builder = self.webview_builder.initialization_script(script); @@ -1003,36 +981,33 @@ impl WebviewWindow { /// /// # Examples /// - #[cfg_attr( - feature = "unstable", - doc = r####" -``` -use tauri::menu::{Menu, Submenu, MenuItem}; -tauri::Builder::default() - .setup(|app| { - let handle = app.handle(); - let save_menu_item = MenuItem::new(handle, "Save", true, None::<&str>)?; - let menu = Menu::with_items(handle, &[ - &Submenu::with_items(handle, "File", true, &[ - &save_menu_item, - ])?, - ])?; - let webview_window = tauri::window::WindowBuilder::new(app, "editor") - .menu(menu) - .build() - .unwrap(); - - webview_window.on_menu_event(move |window, event| { - if event.id == save_menu_item.id() { - // save menu item - } - }); - - Ok(()) - }); -``` - "#### - )] + /// ``` + /// use tauri::menu::{Menu, Submenu, MenuItem}; + /// use tauri::{WebviewWindowBuilder, WebviewUrl}; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let handle = app.handle(); + /// let save_menu_item = MenuItem::new(handle, "Save", true, None::<&str>)?; + /// let menu = Menu::with_items(handle, &[ + /// &Submenu::with_items(handle, "File", true, &[ + /// &save_menu_item, + /// ])?, + /// ])?; + /// let webview_window = WebviewWindowBuilder::new(app, "editor", WebviewUrl::default()) + /// .menu(menu) + /// .build() + /// .unwrap(); + /// + /// webview_window.on_menu_event(move |window, event| { + /// if event.id == save_menu_item.id() { + /// // save menu item + /// } + /// }); + /// + /// Ok(()) + /// }); + /// ``` pub fn on_menu_event, crate::menu::MenuEvent) + Send + Sync + 'static>( &self, f: F, @@ -1698,20 +1673,15 @@ impl WebviewWindow { /// /// # Examples /// - #[cfg_attr( - feature = "unstable", - doc = r####" -```rust,no_run -use tauri::Manager; -tauri::Builder::default() - .setup(|app| { - #[cfg(debug_assertions)] - app.get_webview("main").unwrap().open_devtools(); - Ok(()) - }); -``` - "#### - )] + /// ```rust,no_run + /// use tauri::Manager; + /// tauri::Builder::default() + /// .setup(|app| { + /// #[cfg(debug_assertions)] + /// app.get_webview_window("main").unwrap().open_devtools(); + /// Ok(()) + /// }); + /// ``` #[cfg(any(debug_assertions, feature = "devtools"))] #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = "devtools"))))] pub fn open_devtools(&self) { @@ -1729,27 +1699,22 @@ tauri::Builder::default() /// /// # Examples /// - #[cfg_attr( - feature = "unstable", - doc = r####" -```rust,no_run -use tauri::Manager; -tauri::Builder::default() - .setup(|app| { - #[cfg(debug_assertions)] - { - let webview = app.get_webview("main").unwrap(); - webview.open_devtools(); - std::thread::spawn(move || { - std::thread::sleep(std::time::Duration::from_secs(10)); - webview.close_devtools(); - }); - } - Ok(()) - }); -``` - "#### - )] + /// ```rust,no_run + /// use tauri::Manager; + /// tauri::Builder::default() + /// .setup(|app| { + /// #[cfg(debug_assertions)] + /// { + /// let webview = app.get_webview_window("main").unwrap(); + /// webview.open_devtools(); + /// std::thread::spawn(move || { + /// std::thread::sleep(std::time::Duration::from_secs(10)); + /// webview.close_devtools(); + /// }); + /// } + /// Ok(()) + /// }); + /// ``` #[cfg(any(debug_assertions, feature = "devtools"))] #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = "devtools"))))] pub fn close_devtools(&self) { @@ -1767,25 +1732,20 @@ tauri::Builder::default() /// /// # Examples /// - #[cfg_attr( - feature = "unstable", - doc = r####" -```rust,no_run -use tauri::Manager; -tauri::Builder::default() - .setup(|app| { - #[cfg(debug_assertions)] - { - let webview = app.get_webview("main").unwrap(); - if !webview.is_devtools_open() { - webview.open_devtools(); - } - } - Ok(()) - }); -``` - "#### - )] + /// ```rust,no_run + /// use tauri::Manager; + /// tauri::Builder::default() + /// .setup(|app| { + /// #[cfg(debug_assertions)] + /// { + /// let webview = app.get_webview_window("main").unwrap(); + /// if !webview.is_devtools_open() { + /// webview.open_devtools(); + /// } + /// } + /// Ok(()) + /// }); + /// ``` #[cfg(any(debug_assertions, feature = "devtools"))] #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = "devtools"))))] pub fn is_devtools_open(&self) -> bool { @@ -1804,31 +1764,25 @@ tauri::Builder::default() } } -/// Event system APIs. -impl WebviewWindow { +impl Listener for WebviewWindow { /// Listen to an event on this webview window. /// /// # Examples /// - #[cfg_attr( - feature = "unstable", - doc = r####" -``` -use tauri::Manager; - -tauri::Builder::default() - .setup(|app| { - let webview = app.get_webview("main").unwrap(); - webview.listen("component-loaded", move |event| { - println!("window just loaded a component"); - }); - - Ok(()) - }); -``` - "#### - )] - pub fn listen(&self, event: impl Into, handler: F) -> EventId + /// ``` + /// use tauri::{Manager, Listener}; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let webview_window = app.get_webview_window("main").unwrap(); + /// webview_window.listen("component-loaded", move |event| { + /// println!("window just loaded a component"); + /// }); + /// + /// Ok(()) + /// }); + /// ``` + fn listen(&self, event: impl Into, handler: F) -> EventId where F: Fn(Event) + Send + 'static, { @@ -1841,43 +1795,10 @@ tauri::Builder::default() ) } - /// Unlisten to an event on this webview window. - /// - /// # Examples - #[cfg_attr( - feature = "unstable", - doc = r####" -``` -use tauri::Manager; - -tauri::Builder::default() - .setup(|app| { - let webview = app.get_webview("main").unwrap(); - let webview_ = webview.clone(); - let handler = webview.listen("component-loaded", move |event| { - println!("webview just loaded a component"); - - // we no longer need to listen to the event - // we also could have used `webview.once` instead - webview_.unlisten(event.id()); - }); - - // stop listening to the event when you do not need it anymore - webview.unlisten(handler); - - Ok(()) - }); -``` - "#### - )] - pub fn unlisten(&self, id: EventId) { - self.manager().unlisten(id) - } - /// Listen to an event on this window webview only once. /// /// See [`Self::listen`] for more information. - pub fn once(&self, event: impl Into, handler: F) -> EventId + fn once(&self, event: impl Into, handler: F) -> EventId where F: FnOnce(Event) + Send + 'static, { @@ -1889,6 +1810,108 @@ tauri::Builder::default() handler, ) } + + /// Unlisten to an event on this webview window. + /// + /// # Examples + /// ``` + /// use tauri::{Manager, Listener}; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let webview_window = app.get_webview_window("main").unwrap(); + /// let webview_window_ = webview_window.clone(); + /// let handler = webview_window.listen("component-loaded", move |event| { + /// println!("webview_window just loaded a component"); + /// + /// // we no longer need to listen to the event + /// // we also could have used `webview_window.once` instead + /// webview_window_.unlisten(event.id()); + /// }); + /// + /// // stop listening to the event when you do not need it anymore + /// webview_window.unlisten(handler); + /// + /// Ok(()) + /// }); + /// ``` + fn unlisten(&self, id: EventId) { + self.manager().unlisten(id) + } +} + +impl Emitter for WebviewWindow { + /// Emits an event to all [targets](EventTarget). + /// + /// # Examples + /// ``` + /// use tauri::Emitter; + /// + /// #[tauri::command] + /// fn synchronize(window: tauri::WebviewWindow) { + /// // emits the synchronized event to all webviews + /// window.emit("synchronized", ()); + /// } + /// ``` + fn emit(&self, event: &str, payload: S) -> crate::Result<()> { + self.manager().emit(event, payload) + } + + /// Emits an event to all [targets](EventTarget) matching the given target. + /// + /// # Examples + /// ``` + /// use tauri::{Emitter, EventTarget}; + /// + /// #[tauri::command] + /// fn download(window: tauri::WebviewWindow) { + /// for i in 1..100 { + /// std::thread::sleep(std::time::Duration::from_millis(150)); + /// // emit a download progress event to all listeners + /// window.emit_to(EventTarget::any(), "download-progress", i); + /// // emit an event to listeners that used App::listen or AppHandle::listen + /// window.emit_to(EventTarget::app(), "download-progress", i); + /// // emit an event to any webview/window/webviewWindow matching the given label + /// window.emit_to("updater", "download-progress", i); // similar to using EventTarget::labeled + /// window.emit_to(EventTarget::labeled("updater"), "download-progress", i); + /// // emit an event to listeners that used WebviewWindow::listen + /// window.emit_to(EventTarget::webview_window("updater"), "download-progress", i); + /// } + /// } + /// ``` + fn emit_to(&self, target: I, event: &str, payload: S) -> crate::Result<()> + where + I: Into, + S: Serialize + Clone, + { + self.manager().emit_to(target, event, payload) + } + + /// Emits an event to all [targets](EventTarget) based on the given filter. + /// + /// # Examples + /// ``` + /// use tauri::{Emitter, EventTarget}; + /// + /// #[tauri::command] + /// fn download(window: tauri::WebviewWindow) { + /// for i in 1..100 { + /// std::thread::sleep(std::time::Duration::from_millis(150)); + /// // emit a download progress event to the updater window + /// window.emit_filter("download-progress", i, |t| match t { + /// EventTarget::WebviewWindow { label } => label == "main", + /// _ => false, + /// }); + /// } + /// } + /// ``` + fn emit_filter(&self, event: &str, payload: S, filter: F) -> crate::Result<()> + where + S: Serialize + Clone, + F: Fn(&EventTarget) -> bool, + { + self.manager().emit_filter(event, payload, filter) + } } impl Manager for WebviewWindow { diff --git a/core/tauri/src/window/mod.rs b/core/tauri/src/window/mod.rs index 847463ada..a625df2d5 100644 --- a/core/tauri/src/window/mod.rs +++ b/core/tauri/src/window/mod.rs @@ -28,7 +28,8 @@ use crate::{ sealed::{ManagerBase, RuntimeOrDispatch}, utils::config::{WindowConfig, WindowEffectsConfig}, webview::WebviewBuilder, - EventLoopMessage, Manager, ResourceTable, Runtime, Theme, Webview, WindowEvent, + Emitter, EventLoopMessage, Listener, Manager, ResourceTable, Runtime, Theme, Webview, + WindowEvent, }; #[cfg(desktop)] use crate::{ @@ -2019,8 +2020,7 @@ pub struct ProgressBarState { pub progress: Option, } -/// Event system APIs. -impl Window { +impl Listener for Window { /// Listen to an event on this window. /// /// # Examples @@ -2028,7 +2028,7 @@ impl Window { feature = "unstable", doc = r####" ``` -use tauri::Manager; +use tauri::{Manager, Listener}; tauri::Builder::default() .setup(|app| { @@ -2042,7 +2042,7 @@ tauri::Builder::default() ``` "#### )] - pub fn listen(&self, event: impl Into, handler: F) -> EventId + fn listen(&self, event: impl Into, handler: F) -> EventId where F: Fn(Event) + Send + 'static, { @@ -2055,6 +2055,22 @@ tauri::Builder::default() ) } + /// Listen to an event on this window only once. + /// + /// See [`Self::listen`] for more information. + fn once(&self, event: impl Into, handler: F) -> EventId + where + F: FnOnce(Event) + Send + 'static, + { + self.manager.once( + event.into(), + EventTarget::Window { + label: self.label().to_string(), + }, + handler, + ) + } + /// Unlisten to an event on this window. /// /// # Examples @@ -2062,7 +2078,7 @@ tauri::Builder::default() feature = "unstable", doc = r####" ``` -use tauri::Manager; +use tauri::{Manager, Listener}; tauri::Builder::default() .setup(|app| { @@ -2084,24 +2100,97 @@ tauri::Builder::default() ``` "#### )] - pub fn unlisten(&self, id: EventId) { + fn unlisten(&self, id: EventId) { self.manager.unlisten(id) } +} - /// Listen to an event on this window only once. +impl Emitter for Window { + /// Emits an event to all [targets](EventTarget). /// - /// See [`Self::listen`] for more information. - pub fn once(&self, event: impl Into, handler: F) -> EventId + /// # Examples + #[cfg_attr( + feature = "unstable", + doc = r####" +``` +use tauri::Emitter; + +#[tauri::command] +fn synchronize(window: tauri::Window) { + // emits the synchronized event to all webviews + window.emit("synchronized", ()); +} + ``` + "#### + )] + fn emit(&self, event: &str, payload: S) -> crate::Result<()> { + self.manager.emit(event, payload) + } + + /// Emits an event to all [targets](EventTarget) matching the given target. + /// + /// # Examples + #[cfg_attr( + feature = "unstable", + doc = r####" +``` +use tauri::{Emitter, EventTarget}; + +#[tauri::command] +fn download(window: tauri::Window) { + for i in 1..100 { + std::thread::sleep(std::time::Duration::from_millis(150)); + // emit a download progress event to all listeners + window.emit_to(EventTarget::any(), "download-progress", i); + // emit an event to listeners that used App::listen or AppHandle::listen + window.emit_to(EventTarget::app(), "download-progress", i); + // emit an event to any webview/window/webviewWindow matching the given label + window.emit_to("updater", "download-progress", i); // similar to using EventTarget::labeled + window.emit_to(EventTarget::labeled("updater"), "download-progress", i); + // emit an event to listeners that used WebviewWindow::listen + window.emit_to(EventTarget::webview_window("updater"), "download-progress", i); + } +} +``` +"#### + )] + fn emit_to(&self, target: I, event: &str, payload: S) -> crate::Result<()> where - F: FnOnce(Event) + Send + 'static, + I: Into, + S: Serialize + Clone, { - self.manager.once( - event.into(), - EventTarget::Window { - label: self.label().to_string(), - }, - handler, - ) + self.manager.emit_to(target, event, payload) + } + + /// Emits an event to all [targets](EventTarget) based on the given filter. + /// + /// # Examples + #[cfg_attr( + feature = "unstable", + doc = r####" +``` +use tauri::{Emitter, EventTarget}; + +#[tauri::command] +fn download(window: tauri::Window) { + for i in 1..100 { + std::thread::sleep(std::time::Duration::from_millis(150)); + // emit a download progress event to the updater window + window.emit_filter("download-progress", i, |t| match t { + EventTarget::WebviewWindow { label } => label == "main", + _ => false, + }); + } +} + ``` + "#### + )] + fn emit_filter(&self, event: &str, payload: S, filter: F) -> crate::Result<()> + where + S: Serialize + Clone, + F: Fn(&EventTarget) -> bool, + { + self.manager.emit_filter(event, payload, filter) } } diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index d0f7e2688..c284b5214 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -4035,6 +4035,17 @@ dependencies = [ "syn 2.0.63", ] +[[package]] +name = "windows-implement" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + [[package]] name = "windows-interface" version = "0.57.0" @@ -4046,6 +4057,17 @@ dependencies = [ "syn 2.0.63", ] +[[package]] +name = "windows-interface" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + [[package]] name = "windows-result" version = "0.1.1" diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index 0527174fe..ec676bbbe 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -12,7 +12,7 @@ use serde::Serialize; use tauri::{ ipc::Channel, webview::{PageLoadEvent, WebviewWindowBuilder}, - App, AppHandle, Manager, RunEvent, Runtime, WebviewUrl, + App, AppHandle, Emitter, Listener, Manager, RunEvent, Runtime, WebviewUrl, }; use tauri_plugin_sample::{PingRequest, SampleExt}; diff --git a/examples/multiwindow/main.rs b/examples/multiwindow/main.rs index ce7ba5062..e36650293 100644 --- a/examples/multiwindow/main.rs +++ b/examples/multiwindow/main.rs @@ -4,7 +4,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use tauri::{webview::PageLoadEvent, WebviewWindowBuilder}; +use tauri::{webview::PageLoadEvent, Listener, WebviewWindowBuilder}; use tauri_utils::acl::ExecutionContext; fn main() { diff --git a/examples/parent-window/main.rs b/examples/parent-window/main.rs index 0804433e4..c95617e40 100644 --- a/examples/parent-window/main.rs +++ b/examples/parent-window/main.rs @@ -4,7 +4,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use tauri::{webview::PageLoadEvent, WebviewUrl, WebviewWindowBuilder}; +use tauri::{webview::PageLoadEvent, Listener, WebviewUrl, WebviewWindowBuilder}; use tauri_utils::acl::ExecutionContext; fn main() {