Skip to content

Commit 696d77c

Browse files
fix(core): global events now reaches window listeners, closes #4493 (#7163)
Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
1 parent 5d85d09 commit 696d77c

File tree

7 files changed

+226
-17
lines changed

7 files changed

+226
-17
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch:bug
3+
---
4+
5+
Fixes global events not being received on window-specific event listeners.

core/tauri/src/lib.rs

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -611,18 +611,66 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
611611
}
612612

613613
/// Emits a event to all windows.
614+
///
615+
/// Only the webviews receives this event.
616+
/// To trigger Rust listeners, use [`Self::trigger_global`], [`Window::trigger`] or [`Window::emit_and_trigger`].
617+
///
618+
/// # Examples
619+
/// ```
620+
/// use tauri::Manager;
621+
///
622+
/// #[tauri::command]
623+
/// fn synchronize(app: tauri::AppHandle) {
624+
/// // emits the synchronized event to all windows
625+
/// app.emit_all("synchronized", ());
626+
/// }
627+
/// ```
614628
fn emit_all<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {
615629
self.manager().emit_filter(event, None, payload, |_| true)
616630
}
617631

618-
/// Emits an event to a window with the specified label.
632+
/// Emits an event to the window with the specified label.
633+
///
634+
/// # Examples
635+
/// ```
636+
/// use tauri::Manager;
637+
///
638+
/// #[tauri::command]
639+
/// fn download(app: tauri::AppHandle) {
640+
/// for i in 1..100 {
641+
/// std::thread::sleep(std::time::Duration::from_millis(150));
642+
/// // emit a download progress event to the updater window
643+
/// app.emit_to("updater", "download-progress", i);
644+
/// }
645+
/// }
646+
/// ```
619647
fn emit_to<S: Serialize + Clone>(&self, label: &str, event: &str, payload: S) -> Result<()> {
620648
self
621649
.manager()
622650
.emit_filter(event, None, payload, |w| label == w.label())
623651
}
624652

625-
/// Listen to a global event.
653+
/// Listen to a event triggered on any window ([`Window::trigger`] or [`Window::emit_and_trigger`]) or with [`Self::trigger_global`].
654+
///
655+
/// # Examples
656+
/// ```
657+
/// use tauri::Manager;
658+
///
659+
/// #[tauri::command]
660+
/// fn synchronize(window: tauri::Window) {
661+
/// // emits the synchronized event to all windows
662+
/// window.emit_and_trigger("synchronized", ());
663+
/// }
664+
///
665+
/// tauri::Builder::default()
666+
/// .setup(|app| {
667+
/// app.listen_global("synchronized", |event| {
668+
/// println!("app is in sync");
669+
/// });
670+
/// Ok(())
671+
/// })
672+
/// .invoke_handler(tauri::generate_handler![synchronize]);
673+
/// ```
626674
fn listen_global<F>(&self, event: impl Into<String>, handler: F) -> EventHandler
627675
where
628676
F: Fn(Event) + Send + 'static,
@@ -631,19 +679,63 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
631679
}
632680

633681
/// Listen to a global event only once.
682+
///
683+
/// See [`Self::listen_global`] for more information.
634684
fn once_global<F>(&self, event: impl Into<String>, handler: F) -> EventHandler
635685
where
636686
F: FnOnce(Event) + Send + 'static,
637687
{
638688
self.manager().once(event.into(), None, handler)
639689
}
640690

641-
/// Trigger a global event.
691+
/// Trigger a global event to Rust listeners.
692+
/// To send the events to the webview, see [`Self::emit_all`] and [`Self::emit_to`].
693+
/// To trigger listeners registed on an specific window, see [`Window::trigger`].
694+
/// To trigger all listeners, see [`Window::emit_and_trigger`].
695+
///
696+
/// A global event does not have a source or target window attached.
697+
///
698+
/// # Examples
699+
/// ```
700+
/// use tauri::Manager;
701+
///
702+
/// #[tauri::command]
703+
/// fn download(app: tauri::AppHandle) {
704+
/// for i in 1..100 {
705+
/// std::thread::sleep(std::time::Duration::from_millis(150));
706+
/// // emit a download progress event to all listeners registed in Rust
707+
/// app.trigger_global("download-progress", Some(i.to_string()));
708+
/// }
709+
/// }
710+
/// ```
642711
fn trigger_global(&self, event: &str, data: Option<String>) {
643712
self.manager().trigger(event, None, data)
644713
}
645714

646715
/// Remove an event listener.
716+
///
717+
/// # Examples
718+
/// ```
719+
/// use tauri::Manager;
720+
///
721+
/// tauri::Builder::default()
722+
/// .setup(|app| {
723+
/// let handle = app.handle();
724+
/// let handler = app.listen_global("ready", move |event| {
725+
/// println!("app is ready");
726+
///
727+
/// // we no longer need to listen to the event
728+
/// // we also could have used `app.once_global` instead
729+
/// handle.unlisten(event.id());
730+
/// });
731+
///
732+
/// // stop listening to the event when you do not need it anymore
733+
/// app.unlisten(handler);
734+
///
735+
///
736+
/// Ok(())
737+
/// });
738+
/// ```
647739
fn unlisten(&self, handler_id: EventHandler) {
648740
self.manager().unlisten(handler_id)
649741
}

core/tauri/src/manager.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -839,7 +839,7 @@ impl<R: Runtime> WindowManager<R> {
839839
840840
for (let i = listeners.length - 1; i >= 0; i--) {{
841841
const listener = listeners[i]
842-
if (listener.windowLabel === null || listener.windowLabel === eventData.windowLabel) {{
842+
if (listener.windowLabel === null || eventData.windowLabel === null || listener.windowLabel === eventData.windowLabel) {{
843843
eventData.id = listener.id
844844
listener.handler(eventData)
845845
}}

core/tauri/src/window.rs

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1737,6 +1737,24 @@ impl<R: Runtime> Window<R> {
17371737
/// Event system APIs.
17381738
impl<R: Runtime> Window<R> {
17391739
/// Emits an event to both the JavaScript and the Rust listeners.
1740+
///
1741+
/// This API is a combination of [`Self::trigger`] and [`Self::emit`].
1742+
///
1743+
/// # Examples
1744+
/// ```
1745+
/// use tauri::Manager;
1746+
///
1747+
/// #[tauri::command]
1748+
/// fn download(window: tauri::Window) {
1749+
/// window.emit_and_trigger("download-started", ());
1750+
///
1751+
/// for i in 1..100 {
1752+
/// std::thread::sleep(std::time::Duration::from_millis(150));
1753+
/// // emit a download progress event to all listeners
1754+
/// window.emit_and_trigger("download-progress", i);
1755+
/// }
1756+
/// }
1757+
/// ```
17401758
pub fn emit_and_trigger<S: Serialize + Clone>(
17411759
&self,
17421760
event: &str,
@@ -1762,9 +1780,21 @@ impl<R: Runtime> Window<R> {
17621780
Ok(())
17631781
}
17641782

1765-
/// Emits an event to the JavaScript listeners on the current window.
1783+
/// Emits an event to the JavaScript listeners on the current window or globally.
17661784
///
1767-
/// The event is only delivered to listeners that used the `WebviewWindow#listen` method on the @tauri-apps/api `window` module.
1785+
/// # Examples
1786+
/// ```
1787+
/// use tauri::Manager;
1788+
///
1789+
/// #[tauri::command]
1790+
/// fn download(window: tauri::Window) {
1791+
/// for i in 1..100 {
1792+
/// std::thread::sleep(std::time::Duration::from_millis(150));
1793+
/// // emit a download progress event to all listeners registed in the webview
1794+
/// window.emit("download-progress", i);
1795+
/// }
1796+
/// }
1797+
/// ```
17681798
pub fn emit<S: Serialize + Clone>(&self, event: &str, payload: S) -> crate::Result<()> {
17691799
self
17701800
.manager
@@ -1779,6 +1809,21 @@ impl<R: Runtime> Window<R> {
17791809
/// This listener only receives events that are triggered using the
17801810
/// [`trigger`](Window#method.trigger) and [`emit_and_trigger`](Window#method.emit_and_trigger) methods or
17811811
/// the `appWindow.emit` function from the @tauri-apps/api `window` module.
1812+
///
1813+
/// # Examples
1814+
/// ```
1815+
/// use tauri::Manager;
1816+
///
1817+
/// tauri::Builder::default()
1818+
/// .setup(|app| {
1819+
/// let window = app.get_window("main").unwrap();
1820+
/// window.listen("component-loaded", move |event| {
1821+
/// println!("window just loaded a component");
1822+
/// });
1823+
///
1824+
/// Ok(())
1825+
/// });
1826+
/// ```
17821827
pub fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventHandler
17831828
where
17841829
F: Fn(Event) + Send + 'static,
@@ -1788,11 +1833,37 @@ impl<R: Runtime> Window<R> {
17881833
}
17891834

17901835
/// Unlisten to an event on this window.
1836+
///
1837+
/// # Examples
1838+
/// ```
1839+
/// use tauri::Manager;
1840+
///
1841+
/// tauri::Builder::default()
1842+
/// .setup(|app| {
1843+
/// let window = app.get_window("main").unwrap();
1844+
/// let window_ = window.clone();
1845+
/// let handler = window.listen("component-loaded", move |event| {
1846+
/// println!("window just loaded a component");
1847+
///
1848+
/// // we no longer need to listen to the event
1849+
/// // we also could have used `window.once` instead
1850+
/// window_.unlisten(event.id());
1851+
/// });
1852+
///
1853+
/// // stop listening to the event when you do not need it anymore
1854+
/// window.unlisten(handler);
1855+
///
1856+
///
1857+
/// Ok(())
1858+
/// });
1859+
/// ```
17911860
pub fn unlisten(&self, handler_id: EventHandler) {
17921861
self.manager.unlisten(handler_id)
17931862
}
17941863

17951864
/// Listen to an event on this window a single time.
1865+
///
1866+
/// See [`Self::listen`] for more information.
17961867
pub fn once<F>(&self, event: impl Into<String>, handler: F) -> EventHandler
17971868
where
17981869
F: FnOnce(Event) + Send + 'static,
@@ -1801,9 +1872,21 @@ impl<R: Runtime> Window<R> {
18011872
self.manager.once(event.into(), Some(label), handler)
18021873
}
18031874

1804-
/// Triggers an event to the Rust listeners on this window.
1875+
/// Triggers an event to the Rust listeners on this window or global listeners.
18051876
///
1806-
/// The event is only delivered to listeners that used the [`listen`](Window#method.listen) method.
1877+
/// # Examples
1878+
/// ```
1879+
/// use tauri::Manager;
1880+
///
1881+
/// #[tauri::command]
1882+
/// fn download(window: tauri::Window) {
1883+
/// for i in 1..100 {
1884+
/// std::thread::sleep(std::time::Duration::from_millis(150));
1885+
/// // emit a download progress event to all listeners registed on `window` in Rust
1886+
/// window.trigger("download-progress", Some(i.to_string()));
1887+
/// }
1888+
/// }
1889+
/// ```
18071890
pub fn trigger(&self, event: &str, data: Option<String>) {
18081891
let label = self.window.label.clone();
18091892
self.manager.trigger(event, Some(label), data)

tooling/api/docs/js-api.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tooling/api/src/event.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ export enum TauriEvent {
3939
}
4040

4141
/**
42-
* Listen to an event from the backend.
42+
* Listen to an event. The event can be either global or window-specific.
43+
* See {@link Event.windowLabel} to check the event source.
4344
*
4445
* @example
4546
* ```typescript
@@ -67,7 +68,7 @@ async function listen<T>(
6768
}
6869

6970
/**
70-
* Listen to an one-off event from the backend.
71+
* Listen to an one-off event. See {@link listen} for more information.
7172
*
7273
* @example
7374
* ```typescript
@@ -98,7 +99,7 @@ async function once<T>(
9899
}
99100

100101
/**
101-
* Emits an event to the backend.
102+
* Emits an event to the backend and all Tauri windows.
102103
* @example
103104
* ```typescript
104105
* import { emit } from '@tauri-apps/api/event';

tooling/api/src/window.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,10 @@ class WebviewWindowHandle {
326326
}
327327

328328
/**
329-
* Listen to an event emitted by the backend that is tied to the webview window.
329+
* Listen to an event emitted by the backend or webview.
330+
* The event must either be a global event or an event targetting this window.
331+
*
332+
* See {@link WebviewWindow.emit | `emit`} for more information.
330333
*
331334
* @example
332335
* ```typescript
@@ -339,10 +342,11 @@ class WebviewWindowHandle {
339342
* unlisten();
340343
* ```
341344
*
345+
* Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
346+
*
342347
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
343348
* @param handler Event handler.
344349
* @returns A promise resolving to a function to unlisten to the event.
345-
* Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
346350
*/
347351
async listen<T>(
348352
event: EventName,
@@ -359,7 +363,8 @@ class WebviewWindowHandle {
359363
}
360364

361365
/**
362-
* Listen to an one-off event emitted by the backend that is tied to the webview window.
366+
* Listen to an one-off event.
367+
* See {@link WebviewWindow.listen | `listen`} for more information.
363368
*
364369
* @example
365370
* ```typescript
@@ -372,10 +377,11 @@ class WebviewWindowHandle {
372377
* unlisten();
373378
* ```
374379
*
380+
* Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
381+
*
375382
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
376383
* @param handler Event handler.
377384
* @returns A promise resolving to a function to unlisten to the event.
378-
* Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
379385
*/
380386
async once<T>(event: string, handler: EventCallback<T>): Promise<UnlistenFn> {
381387
if (this._handleTauriEvent(event, handler)) {
@@ -389,13 +395,35 @@ class WebviewWindowHandle {
389395
}
390396

391397
/**
392-
* Emits an event to the backend, tied to the webview window.
398+
* Emits an event to the backend and all Tauri windows.
399+
* The event will have this window's {@link WebviewWindow.label | label} as {@link Event.windowLabel | source window label}.
400+
*
393401
* @example
394402
* ```typescript
395403
* import { appWindow } from '@tauri-apps/api/window';
396404
* await appWindow.emit('window-loaded', { loggedIn: true, token: 'authToken' });
397405
* ```
398406
*
407+
* This function can also be used to communicate between windows:
408+
* ```typescript
409+
* import { appWindow } from '@tauri-apps/api/window';
410+
* await appWindow.listen('sync-data', (event) => { });
411+
*
412+
* // on another window...
413+
* import { WebviewWindow } from '@tauri-apps/api/window';
414+
* const otherWindow = WebviewWindow.getByLabel('other')
415+
* await otherWindow.emit('sync-data');
416+
* ```
417+
*
418+
* Global listeners are also triggered:
419+
* ```typescript
420+
* import { appWindow } from '@tauri-apps/api/window';
421+
* import { listen } from '@tauri-apps/api/event';
422+
* await listen('ping', (event) => { });
423+
*
424+
* await appWindow.emit('ping');
425+
* ```
426+
*
399427
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
400428
* @param payload Event payload.
401429
*/

0 commit comments

Comments
 (0)