diff --git a/.changes/simplify-tag-label-usage.md b/.changes/simplify-tag-label-usage.md new file mode 100644 index 000000000..986a5c604 --- /dev/null +++ b/.changes/simplify-tag-label-usage.md @@ -0,0 +1,5 @@ +--- +"tauri": patch +--- + +Simplify usage of app event and window label types. diff --git a/core/tauri/src/endpoints/event.rs b/core/tauri/src/endpoints/event.rs index 95aec1ba9..95fedacad 100644 --- a/core/tauri/src/endpoints/event.rs +++ b/core/tauri/src/endpoints/event.rs @@ -52,12 +52,12 @@ impl Cmd { }); // dispatch the event to Rust listeners - window.trigger(e.clone(), payload.clone()); + window.trigger(&e, payload.clone()); if let Some(target) = window_label { - window.emit_to(&target, e, payload)?; + window.emit_to(&target, &e, payload)?; } else { - window.emit_all(e, payload)?; + window.emit_all(&e, payload)?; } Ok(().into()) } diff --git a/core/tauri/src/endpoints/window.rs b/core/tauri/src/endpoints/window.rs index 95f168bf2..5b6d6287c 100644 --- a/core/tauri/src/endpoints/window.rs +++ b/core/tauri/src/endpoints/window.rs @@ -99,7 +99,7 @@ struct WindowCreatedEvent { impl Cmd { #[allow(dead_code)] - pub async fn run(self, window: Window) -> crate::Result { + pub async fn run(self, window: Window

) -> crate::Result { if cfg!(not(window_all)) { Err(crate::Error::ApiNotAllowlisted("window > all".to_string())) } else { @@ -114,7 +114,7 @@ impl Cmd { Self::CreateWebview { options } => { let mut window = window; // Panic if the user's `Tag` type decided to return an error while parsing. - let label: M::Label = options.label.parse().unwrap_or_else(|_| { + let label: P::Label = options.label.parse().unwrap_or_else(|_| { panic!( "Window module received unknown window label: {}", options.label @@ -124,13 +124,15 @@ impl Cmd { let url = options.url.clone(); let pending = crate::runtime::window::PendingWindow::with_config(options, label.clone(), url); - window.create_window(pending)?.emit_others_internal( - "tauri://window-created".to_string(), + + window.create_window(pending)?.emit_others( + &crate::runtime::manager::tauri_event::("tauri://window-created"), Some(WindowCreatedEvent { label: label.to_string(), }), )?; } + Self::SetResizable { resizable } => window.set_resizable(resizable)?, Self::SetTitle { title } => window.set_title(&title)?, Self::Maximize => window.maximize()?, diff --git a/core/tauri/src/event.rs b/core/tauri/src/event.rs index 46bd85b49..469ea9a16 100644 --- a/core/tauri/src/event.rs +++ b/core/tauri/src/event.rs @@ -2,8 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::runtime::tag::Tag; +use crate::runtime::tag::{Tag, TagRef}; use std::{ + borrow::Borrow, boxed::Box, collections::HashMap, fmt, @@ -134,7 +135,7 @@ impl Listeners { match action { Pending::Unlisten(id) => self.unlisten(id), Pending::Listen(id, event, handler) => self.listen_(id, event, handler), - Pending::Trigger(event, window, payload) => self.trigger(event, window, payload), + Pending::Trigger(ref event, window, payload) => self.trigger(event, window, payload), } } } @@ -191,12 +192,16 @@ impl Listeners { } /// Triggers the given global event with its payload. - pub(crate) fn trigger(&self, event: Event, window: Option, payload: Option) { + pub(crate) fn trigger(&self, event: &E, window: Option, payload: Option) + where + E: TagRef + ?Sized, + Event: Borrow, + { let mut maybe_pending = false; match self.inner.handlers.try_lock() { - Err(_) => self.insert_pending(Pending::Trigger(event, window, payload)), + Err(_) => self.insert_pending(Pending::Trigger(event.to_owned(), window, payload)), Ok(lock) => { - if let Some(handlers) = lock.get(&event) { + if let Some(handlers) = lock.get(event) { for (&id, handler) in handlers { if window.is_none() || window == handler.window { maybe_pending = true; @@ -280,7 +285,7 @@ mod test { // call listen with e and the event_fn dummy func listeners.listen(e.clone(), None, event_fn); // call on event with e and d. - listeners.trigger(e, None, Some(d)); + listeners.trigger(&e, None, Some(d)); // lock the mutex let l = listeners.inner.handlers.lock().unwrap(); diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 7f10905d0..baf2b9ad8 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -39,7 +39,7 @@ pub type SyncTask = Box; use crate::api::assets::Assets; use crate::api::config::Config; use crate::event::{Event, EventHandler}; -use crate::runtime::tag::Tag; +use crate::runtime::tag::{Tag, TagRef}; use crate::runtime::window::PendingWindow; use crate::runtime::{Dispatch, Runtime}; use serde::Serialize; @@ -51,10 +51,12 @@ pub use { api::config::WindowUrl, hooks::{InvokeHandler, InvokeMessage, OnPageLoad, PageLoadPayload, SetupHook}, runtime::app::{App, Builder}, + runtime::flavors::wry::Wry, runtime::webview::Attributes, runtime::window::export::Window, }; +use std::borrow::Borrow; /// Reads the config file at compile time and generates a [`Context`] based on its content. /// /// The default config file path is a `tauri.conf.json` file inside the Cargo manifest directory of @@ -130,31 +132,39 @@ pub trait Params: sealed::ParamsBase { /// Manages a running application. /// /// TODO: expand these docs -pub trait Manager: sealed::ManagerBase { +pub trait Manager: sealed::ManagerBase

{ /// The [`Config`] the manager was created with. fn config(&self) -> &Config { self.manager().config() } /// Emits a event to all windows. - fn emit_all(&self, event: M::Event, payload: Option) -> Result<()> { + fn emit_all(&self, event: &E, payload: Option) -> Result<()> + where + E: TagRef + ?Sized, + S: Serialize + Clone, + { self.manager().emit_filter(event, payload, |_| true) } /// Emits an event to a window with the specified label. - fn emit_to( + fn emit_to( &self, - label: &M::Label, - event: M::Event, + label: &L, + event: &E, payload: Option, - ) -> Result<()> { + ) -> Result<()> + where + L: TagRef + ?Sized, + E: TagRef + ?Sized, + { self .manager() - .emit_filter(event, payload, |w| w.label() == label) + .emit_filter(event, payload, |w| label == w.label()) } /// Creates a new [`Window`] on the [`Runtime`] and attaches it to the [`Manager`]. - fn create_window(&mut self, pending: PendingWindow) -> Result> { + fn create_window(&mut self, pending: PendingWindow

) -> Result> { use sealed::RuntimeOrDispatch::*; let labels = self.manager().labels().into_iter().collect::>(); @@ -167,23 +177,27 @@ pub trait Manager: sealed::ManagerBase { } /// Listen to a global event. - fn listen_global(&self, event: M::Event, handler: F) -> EventHandler + fn listen_global, F>(&self, event: E, handler: F) -> EventHandler where F: Fn(Event) + Send + 'static, { - self.manager().listen(event, None, handler) + self.manager().listen(event.into(), None, handler) } /// Listen to a global event only once. - fn once_global(&self, event: M::Event, handler: F) -> EventHandler + fn once_global, F>(&self, event: E, handler: F) -> EventHandler where F: Fn(Event) + Send + 'static, { - self.manager().once(event, None, handler) + self.manager().once(event.into(), None, handler) } /// Trigger a global event. - fn trigger_global(&self, event: M::Event, data: Option) { + fn trigger_global(&self, event: &E, data: Option) + where + E: TagRef + ?Sized, + P::Event: Borrow, + { self.manager().trigger(event, None, data) } @@ -193,12 +207,16 @@ pub trait Manager: sealed::ManagerBase { } /// Fetch a single window from the manager. - fn get_window(&self, label: &M::Label) -> Option> { + fn get_window(&self, label: &L) -> Option> + where + L: TagRef + ?Sized, + P::Label: Borrow, + { self.manager().get_window(label) } /// Fetch all managed windows. - fn windows(&self) -> HashMap> { + fn windows(&self) -> HashMap> { self.manager().windows() } } diff --git a/core/tauri/src/runtime/app.rs b/core/tauri/src/runtime/app.rs index 7f2ea231e..5694d22fe 100644 --- a/core/tauri/src/runtime/app.rs +++ b/core/tauri/src/runtime/app.rs @@ -74,7 +74,7 @@ impl App { // invoke the Event `tauri://update` from JS or rust side. main_window.listen( updater::EVENT_CHECK_UPDATE - .parse() + .parse::() .unwrap_or_else(|_| panic!("bad label")), move |_msg| { let window = event_window.clone(); diff --git a/core/tauri/src/runtime/manager.rs b/core/tauri/src/runtime/manager.rs index c0ba55de1..3b8bab6ea 100644 --- a/core/tauri/src/runtime/manager.rs +++ b/core/tauri/src/runtime/manager.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use crate::runtime::tag::TagRef; use crate::{ api::{ assets::Assets, @@ -13,7 +14,7 @@ use crate::{ hooks::{InvokeHandler, InvokeMessage, InvokePayload, OnPageLoad, PageLoadPayload}, plugin::PluginStore, runtime::{ - tag::{tags_to_javascript_array, Tag, ToJavascript}, + tag::{tags_to_javascript_array, Tag, ToJsString}, webview::{Attributes, CustomProtocol, FileDropEvent, FileDropHandler, WebviewRpcHandler}, window::{DetachedWindow, PendingWindow}, Dispatch, Icon, Runtime, @@ -23,6 +24,7 @@ use crate::{ }; use serde::Serialize; use serde_json::Value as JsonValue; +use std::borrow::Borrow; use std::marker::PhantomData; use std::{ borrow::Cow, @@ -33,6 +35,20 @@ use std::{ }; use uuid::Uuid; +/// Parse a string representing an internal tauri event into [`Params::Event`] +/// +/// # Panics +/// +/// This will panic if the `FromStr` implementation of [`Params::Event`] returns an error. +pub(crate) fn tauri_event(tauri_event: &str) -> Event { + tauri_event.parse().unwrap_or_else(|_| { + panic!( + "failed to parse internal tauri event into Params::Event: {}", + tauri_event + ) + }) +} + pub struct InnerWindowManager { windows: Mutex>>, plugins: Mutex>, @@ -168,7 +184,7 @@ impl WindowManager

{ window.__TAURI__.__currentWindow = {{ label: {current_window_label} }} "#, window_labels_array = tags_to_javascript_array(pending_labels)?, - current_window_label = label.to_javascript()?, + current_window_label = label.to_js_string()?, )); if !attributes.has_icon() { @@ -281,14 +297,16 @@ impl WindowManager

{ let window = manager.attach_window(window); let _ = match event { FileDropEvent::Hovered(paths) => { - window.emit_internal("tauri://file-drop".to_string(), Some(paths)) - } - FileDropEvent::Dropped(paths) => { - window.emit_internal("tauri://file-drop-hover".to_string(), Some(paths)) - } - FileDropEvent::Cancelled => { - window.emit_internal("tauri://file-drop-cancelled".to_string(), Some(())) + window.emit_internal(&tauri_event::("tauri://file-drop"), Some(paths)) } + FileDropEvent::Dropped(paths) => window.emit_internal( + &tauri_event::("tauri://file-drop-hover"), + Some(paths), + ), + FileDropEvent::Cancelled => window.emit_internal( + &tauri_event::("tauri://file-drop-cancelled"), + Some(()), + ), }; }); true @@ -474,30 +492,20 @@ impl WindowManager

{ window } - pub fn emit_filter_internal) -> bool>( - &self, - event: String, - payload: Option, - filter: F, - ) -> crate::Result<()> { + + pub fn emit_filter(&self, event: &E, payload: Option, filter: F) -> crate::Result<()> + where + E: TagRef + ?Sized, + S: Serialize + Clone, + F: Fn(&Window

) -> bool, + { self .windows_lock() .values() .filter(|&w| filter(w)) - .try_for_each(|window| window.emit_internal(event.clone(), payload.clone())) - } - pub fn emit_filter) -> bool>( - &self, - event: P::Event, - payload: Option, - filter: F, - ) -> crate::Result<()> { - self - .windows_lock() - .values() - .filter(|&w| filter(w)) - .try_for_each(|window| window.emit(&event, payload.clone())) + .try_for_each(|window| window.emit(event, payload.clone())) } + pub fn labels(&self) -> HashSet { self.windows_lock().keys().cloned().collect() } @@ -510,9 +518,15 @@ impl WindowManager

{ pub fn unlisten(&self, handler_id: EventHandler) { self.inner.listeners.unlisten(handler_id) } - pub fn trigger(&self, event: P::Event, window: Option, data: Option) { + + pub fn trigger(&self, event: &E, window: Option, data: Option) + where + E: TagRef + ?Sized, + P::Event: Borrow, + { self.inner.listeners.trigger(event, window, data) } + pub fn listen( &self, event: P::Event, @@ -563,9 +577,15 @@ impl WindowManager

{ .expect("poisoned salt mutex") .remove(&uuid) } - pub fn get_window(&self, label: &P::Label) -> Option> { + + pub fn get_window(&self, label: &L) -> Option> + where + L: TagRef + ?Sized, + P::Label: Borrow, + { self.windows_lock().get(label).cloned() } + pub fn windows(&self) -> HashMap> { self.windows_lock().clone() } diff --git a/core/tauri/src/runtime/tag.rs b/core/tauri/src/runtime/tag.rs index 726e50e9a..0ea085bfe 100644 --- a/core/tauri/src/runtime/tag.rs +++ b/core/tauri/src/runtime/tag.rs @@ -85,6 +85,20 @@ impl Tag for T where { } +/// A reference to a [`Tag`]. +/// +/// * [`Display`] so that we can still convert this tag to a JavaScript string. +/// * [`ToOwned`] to make sure we can clone it into the owned tag in specific cases. +/// * [`PartialEq`] so that we can compare refs to the owned tags easily. +/// * [`Hash`] + [`Eq`] because we want to be able to use a ref as a key to internal hashmaps. +pub trait TagRef: Display + ToOwned + PartialEq + Eq + Hash {} + +/// Automatically implement [`TagRef`] for all types that fit the requirements. +impl TagRef for R where + R: Display + ToOwned + PartialEq + Eq + Hash + ?Sized +{ +} + /// Private helper to turn [`Tag`] related things into JavaScript, safely. /// /// The main concern is string escaping, so we rely on [`serde_json`] to handle all serialization @@ -93,13 +107,13 @@ impl Tag for T where /// /// We don't want downstream users to implement this trait so that [`Tag`]s cannot be turned into /// invalid JavaScript - regardless of their content. -pub(crate) trait ToJavascript { - fn to_javascript(&self) -> crate::Result; +pub(crate) trait ToJsString { + fn to_js_string(&self) -> crate::Result; } -impl ToJavascript for T { +impl ToJsString for D { /// Turn any [`Tag`] into the JavaScript representation of a string. - fn to_javascript(&self) -> crate::Result { + fn to_js_string(&self) -> crate::Result { Ok(serde_json::to_string(&self.to_string())?) } } diff --git a/core/tauri/src/runtime/window.rs b/core/tauri/src/runtime/window.rs index 22f9572af..2ca01b617 100644 --- a/core/tauri/src/runtime/window.rs +++ b/core/tauri/src/runtime/window.rs @@ -9,7 +9,7 @@ use crate::{ event::{Event, EventHandler}, hooks::{InvokeMessage, InvokePayload, PageLoadPayload}, runtime::{ - tag::ToJavascript, + tag::ToJsString, webview::{FileDropHandler, WebviewRpcHandler}, Dispatch, Runtime, }, @@ -107,7 +107,8 @@ impl PartialEq for DetachedWindow { /// We want to export the runtime related window at the crate root, but not look like a re-export. pub(crate) mod export { use super::*; - use crate::runtime::manager::WindowManager; + use crate::runtime::{manager::WindowManager, tag::TagRef}; + use std::borrow::Borrow; /// A webview window managed by Tauri. /// @@ -195,11 +196,11 @@ pub(crate) mod export { &self.window.label } - pub(crate) fn emit_internal( - &self, - event: E, - payload: Option, - ) -> crate::Result<()> { + pub(crate) fn emit_internal(&self, event: &E, payload: Option) -> crate::Result<()> + where + E: TagRef + ?Sized, + S: Serialize, + { let js_payload = match payload { Some(payload_value) => serde_json::to_value(payload_value)?, None => JsonValue::Null, @@ -208,7 +209,7 @@ pub(crate) mod export { self.eval(&format!( "window['{}']({{event: {}, payload: {}}}, '{}')", self.manager.event_emit_function_name(), - event.to_javascript()?, + event.to_js_string()?, js_payload, self.manager.generate_salt(), ))?; @@ -217,50 +218,47 @@ pub(crate) mod export { } /// Emits an event to the current window. - pub fn emit(&self, event: &P::Event, payload: Option) -> crate::Result<()> { - self.emit_internal(event.clone(), payload) - } - - #[allow(dead_code)] - pub(crate) fn emit_others_internal( - &self, - event: String, - payload: Option, - ) -> crate::Result<()> { - self - .manager - .emit_filter_internal(event, payload, |w| w != self) + pub fn emit(&self, event: &E, payload: Option) -> crate::Result<()> + where + E: TagRef + ?Sized, + S: Serialize, + { + self.emit_internal(event, payload) } /// Emits an event on all windows except this one. - pub fn emit_others( + pub fn emit_others + ?Sized, S: Serialize + Clone>( &self, - event: P::Event, + event: &E, payload: Option, ) -> crate::Result<()> { self.manager.emit_filter(event, payload, |w| w != self) } /// Listen to an event on this window. - pub fn listen(&self, event: P::Event, handler: F) -> EventHandler + pub fn listen, F>(&self, event: E, handler: F) -> EventHandler where F: Fn(Event) + Send + 'static, { let label = self.window.label.clone(); - self.manager.listen(event, Some(label), handler) + self.manager.listen(event.into(), Some(label), handler) } /// Listen to a an event on this window a single time. - pub fn once(&self, event: P::Event, handler: F) -> EventHandler + pub fn once, F>(&self, event: E, handler: F) -> EventHandler where F: Fn(Event) + Send + 'static, { let label = self.window.label.clone(); - self.manager.once(event, Some(label), handler) + self.manager.once(event.into(), Some(label), handler) } /// Triggers an event on this window. - pub fn trigger(&self, event: P::Event, data: Option) { + pub fn trigger(&self, event: &E, data: Option) + where + E: TagRef + ?Sized, + P::Event: Borrow, + { let label = self.window.label.clone(); self.manager.trigger(event, Some(label), data) } diff --git a/core/tauri/src/updater/mod.rs b/core/tauri/src/updater/mod.rs index 4b58d941c..666073a5b 100644 --- a/core/tauri/src/updater/mod.rs +++ b/core/tauri/src/updater/mod.rs @@ -339,6 +339,7 @@ mod error; pub use self::error::Error; +use crate::runtime::manager::tauri_event; use crate::{ api::{ app::restart_application, @@ -434,7 +435,7 @@ pub(crate) fn listener( // Wait to receive the event `"tauri://update"` window.listen( EVENT_CHECK_UPDATE - .parse() + .parse::() .unwrap_or_else(|_| panic!("bad label")), move |_msg| { let window = isolated_window.clone(); @@ -468,9 +469,7 @@ pub(crate) fn listener( // Emit `tauri://update-available` let _ = window.emit( - &EVENT_UPDATE_AVAILABLE - .parse() - .unwrap_or_else(|_| panic!("bad label")), + &tauri_event::(EVENT_UPDATE_AVAILABLE), Some(UpdateManifest { body, date: updater.date.clone(), @@ -481,7 +480,7 @@ pub(crate) fn listener( // Listen for `tauri://update-install` window.once( EVENT_INSTALL_UPDATE - .parse() + .parse::() .unwrap_or_else(|_| panic!("bad label")), move |_msg| { let window = window_isolation.clone(); @@ -524,8 +523,8 @@ pub(crate) fn listener( // Send a status update via `tauri://update-status` event. fn send_status_update(window: Window, status: &str, error: Option) { - let _ = window.emit_internal( - EVENT_STATUS_UPDATE.to_string(), + let _ = window.emit( + &tauri_event::(EVENT_STATUS_UPDATE), Some(StatusEvent { error, status: String::from(status), diff --git a/examples/api/src-tauri/src/main.rs b/examples/api/src-tauri/src/main.rs index e80773f00..177aaf9c0 100644 --- a/examples/api/src-tauri/src/main.rs +++ b/examples/api/src-tauri/src/main.rs @@ -20,14 +20,14 @@ fn main() { tauri::Builder::default() .on_page_load(|window, _| { let window_ = window.clone(); - window.listen("js-event".into(), move |event| { + window.listen("js-event", move |event| { println!("got js-event with message '{:?}'", event.payload()); let reply = Reply { data: "something else".to_string(), }; window_ - .emit(&"rust-event".into(), Some(reply)) + .emit("rust-event", Some(reply)) .expect("failed to emit"); }); })