diff --git a/.changes/tray-async-command.md b/.changes/tray-async-command.md new file mode 100644 index 000000000..9e4017819 --- /dev/null +++ b/.changes/tray-async-command.md @@ -0,0 +1,5 @@ +--- +"tauri": "patch:bug" +--- + +Fix tray events not fired for tray icons created inside an async command. diff --git a/crates/tauri/src/lib.rs b/crates/tauri/src/lib.rs index ee9f58f9a..e37df1624 100644 --- a/crates/tauri/src/lib.rs +++ b/crates/tauri/src/lib.rs @@ -1011,6 +1011,15 @@ pub(crate) mod sealed { } } +struct UnsafeSend(T); +unsafe impl Send for UnsafeSend {} + +impl UnsafeSend { + fn take(self) -> T { + self.0 + } +} + #[allow(unused)] macro_rules! run_main_thread { ($handle:ident, $ex:expr) => {{ diff --git a/crates/tauri/src/menu/mod.rs b/crates/tauri/src/menu/mod.rs index 2797a5543..2ae33d971 100644 --- a/crates/tauri/src/menu/mod.rs +++ b/crates/tauri/src/menu/mod.rs @@ -75,7 +75,6 @@ macro_rules! gen_wrappers { app_handle: $crate::AppHandle, } - /// # Safety /// /// We make sure it always runs on the main thread. @@ -96,11 +95,9 @@ macro_rules! gen_wrappers { impl Drop for $inner { fn drop(&mut self) { - struct SafeSend(T); - unsafe impl Send for SafeSend {} - let inner = self.inner.take(); - let inner = SafeSend(inner); + // SAFETY: inner was created on main thread and is being dropped on main thread + let inner = $crate::UnsafeSend(inner); let _ = self.app_handle.run_on_main_thread(move || { drop(inner); }); diff --git a/crates/tauri/src/tray/mod.rs b/crates/tauri/src/tray/mod.rs index 21623ff2b..5c053f52b 100644 --- a/crates/tauri/src/tray/mod.rs +++ b/crates/tauri/src/tray/mod.rs @@ -10,6 +10,7 @@ use crate::app::{GlobalMenuEventListener, GlobalTrayIconEventListener}; use crate::menu::ContextMenu; use crate::menu::MenuEvent; use crate::resources::Resource; +use crate::UnsafeSend; use crate::{ image::Image, menu::run_item_main_thread, AppHandle, Manager, PhysicalPosition, Rect, Runtime, }; @@ -342,10 +343,24 @@ impl TrayIconBuilder { /// Builds and adds a new [`TrayIcon`] to the system tray. pub fn build>(self, manager: &M) -> crate::Result> { let id = self.id().clone(); - let inner = self.inner.build()?; + + // SAFETY: + // the menu within this builder was created on main thread + // and will be accessed on the main thread + let unsafe_builder = UnsafeSend(self.inner); + + let (tx, rx) = std::sync::mpsc::channel(); + let unsafe_tray = manager + .app_handle() + .run_on_main_thread(move || { + // SAFETY: will only be accessed on main thread + let _ = tx.send(unsafe_builder.take().build().map(UnsafeSend)); + }) + .and_then(|_| rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage))??; + let icon = TrayIcon { id, - inner, + inner: unsafe_tray.take(), app_handle: manager.app_handle().clone(), };