Browse Source

fix(tauri-runtime-wry): ensure tray is created when event loop ready (#10611)

Lucas Fernandes Nogueira 1 year ago
parent
commit
c3a90e5c27

+ 5 - 0
.changes/ensure-tray-created-ready.md

@@ -0,0 +1,5 @@
+---
+"tauri-runtime-wry": patch:bug
+---
+
+Ensure system tray is created when the event loop is ready. Menu item modifications are not applied unless it is ready.

+ 77 - 23
core/tauri-runtime-wry/src/lib.rs

@@ -5,7 +5,11 @@
 //! The [`wry`] Tauri [`Runtime`].
 
 use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle};
-use std::{collections::BTreeMap, rc::Rc};
+use std::{
+  collections::BTreeMap,
+  rc::Rc,
+  sync::atomic::{AtomicBool, Ordering},
+};
 use tauri_runtime::{
   http::{header::CONTENT_TYPE, Request as HttpRequest, RequestParts, Response as HttpResponse},
   menu::{AboutMetadata, CustomMenuItem, Menu, MenuEntry, MenuHash, MenuId, MenuItem, MenuUpdate},
@@ -196,6 +200,7 @@ pub struct Context<T: UserEvent> {
   main_thread_id: ThreadId,
   pub proxy: WryEventLoopProxy<Message<T>>,
   main_thread: DispatcherMainThreadContext<T>,
+  pub is_event_loop_ready: Arc<AtomicBool>,
 }
 
 impl<T: UserEvent> Context<T> {
@@ -1835,8 +1840,12 @@ pub struct Wry<T: UserEvent> {
   clipboard_manager_handle: ClipboardManagerWrapper,
 
   event_loop: EventLoop<Message<T>>,
+
+  pending_ready_tasks: RefCell<Vec<PendingReadyTask<T>>>,
 }
 
+type PendingReadyTask<T> = Box<dyn FnOnce(&EventLoopWindowTarget<Message<T>>)>;
+
 impl<T: UserEvent> fmt::Debug for Wry<T> {
   fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
     let mut d = f.debug_struct("Wry");
@@ -1952,6 +1961,8 @@ impl<T: UserEvent> RuntimeHandle<T> for WryHandle<T> {
       context: self.context.clone(),
       id,
       proxy: self.context.proxy.clone(),
+      #[allow(clippy::arc_with_non_send_sync)]
+      pending: PendingSystemTray(Arc::new(RefCell::new(None))),
     })
   }
 
@@ -2006,6 +2017,7 @@ impl<T: UserEvent> Wry<T> {
         #[cfg(feature = "tracing")]
         active_tracing_spans: Default::default(),
       },
+      is_event_loop_ready: Arc::new(AtomicBool::new(false)),
     };
 
     #[cfg(all(desktop, feature = "global-shortcut"))]
@@ -2033,6 +2045,8 @@ impl<T: UserEvent> Wry<T> {
       clipboard_manager_handle,
 
       event_loop,
+
+      pending_ready_tasks: RefCell::new(Vec::new()),
     })
   }
 
@@ -2129,34 +2143,48 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
   }
 
   #[cfg(all(desktop, feature = "system-tray"))]
-  fn system_tray(&self, mut system_tray: SystemTray) -> Result<Self::TrayHandler> {
+  fn system_tray(&self, system_tray: SystemTray) -> Result<Self::TrayHandler> {
+    let system_tray_manager = self.context.main_thread.system_tray_manager.clone();
+
     let id = system_tray.id;
-    let mut listeners = Vec::new();
-    if let Some(l) = system_tray.on_event.take() {
-      #[allow(clippy::arc_with_non_send_sync)]
-      listeners.push(Arc::new(l));
-    }
-    let (tray, items) = create_tray(WryTrayId(id), system_tray, &self.event_loop)?;
+    #[allow(clippy::arc_with_non_send_sync)]
+    let pending = PendingSystemTray(Arc::new(RefCell::new(Some(system_tray))));
+
+    let pending_ = pending.clone();
     self
-      .context
-      .main_thread
-      .system_tray_manager
-      .trays
-      .lock()
-      .unwrap()
-      .insert(
-        id,
-        TrayContext {
-          tray: Arc::new(TrayCell(RefCell::new(Some(tray)))),
-          listeners: Arc::new(TrayListenersCell(RefCell::new(listeners))),
-          items: Arc::new(TrayItemsCell(RefCell::new(items))),
-        },
-      );
+      .pending_ready_tasks
+      .borrow_mut()
+      .push(Box::new(move |event_loop| {
+        if let Some(mut system_tray) = pending_.0.borrow_mut().take() {
+          let mut listeners = Vec::new();
+          if let Some(l) = system_tray.on_event.take() {
+            #[allow(clippy::arc_with_non_send_sync)]
+            listeners.push(Arc::new(l));
+          }
+
+          match create_tray(WryTrayId(id), system_tray, event_loop) {
+            Ok((tray, items)) => {
+              system_tray_manager.trays.lock().unwrap().insert(
+                id,
+                TrayContext {
+                  tray: Arc::new(TrayCell(RefCell::new(Some(tray)))),
+                  listeners: Arc::new(TrayListenersCell(RefCell::new(listeners))),
+                  items: Arc::new(TrayItemsCell(RefCell::new(items))),
+                },
+              );
+            }
+            Err(e) => {
+              eprintln!("failed to build tray: {e}");
+            }
+          }
+        }
+      }));
 
     Ok(SystemTrayHandle {
       context: self.context.clone(),
       id,
-      proxy: self.event_loop.create_proxy(),
+      proxy: self.context.proxy.clone(),
+      pending,
     })
   }
 
@@ -2202,6 +2230,8 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
 
   #[cfg(desktop)]
   fn run_iteration<F: FnMut(RunEvent<T>) + 'static>(&mut self, mut callback: F) -> RunIteration {
+    use std::sync::atomic::Ordering;
+
     use wry::application::platform::run_return::EventLoopExtRunReturn;
     let windows = self.context.main_thread.windows.clone();
     let webview_id_map = self.context.webview_id_map.clone();
@@ -2210,6 +2240,11 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
     #[cfg(all(desktop, feature = "system-tray"))]
     let system_tray_manager = self.context.main_thread.system_tray_manager.clone();
 
+    let is_event_loop_ready = self.context.is_event_loop_ready.clone();
+    is_event_loop_ready.store(false, Ordering::Relaxed);
+
+    let pending_ready_tasks = &self.pending_ready_tasks;
+
     #[cfg(feature = "tracing")]
     let active_tracing_spans = self.context.main_thread.active_tracing_spans.clone();
 
@@ -2248,6 +2283,8 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
               system_tray_manager: system_tray_manager.clone(),
               #[cfg(feature = "tracing")]
               active_tracing_spans: active_tracing_spans.clone(),
+              pending_ready_tasks,
+              is_event_loop_ready: is_event_loop_ready.clone(),
             },
             web_context,
           );
@@ -2272,6 +2309,8 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
             system_tray_manager: system_tray_manager.clone(),
             #[cfg(feature = "tracing")]
             active_tracing_spans: active_tracing_spans.clone(),
+            pending_ready_tasks,
+            is_event_loop_ready: is_event_loop_ready.clone(),
           },
           web_context,
         );
@@ -2284,6 +2323,8 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
     let windows = self.context.main_thread.windows.clone();
     let webview_id_map = self.context.webview_id_map.clone();
     let web_context = self.context.main_thread.web_context.clone();
+    let is_event_loop_ready = self.context.is_event_loop_ready.clone();
+    let pending_ready_tasks = self.pending_ready_tasks;
     let mut plugins = self.plugins;
 
     #[cfg(feature = "tracing")]
@@ -2318,6 +2359,8 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
             system_tray_manager: system_tray_manager.clone(),
             #[cfg(feature = "tracing")]
             active_tracing_spans: active_tracing_spans.clone(),
+            pending_ready_tasks: &pending_ready_tasks,
+            is_event_loop_ready: is_event_loop_ready.clone(),
           },
           &web_context,
         );
@@ -2341,6 +2384,8 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
           system_tray_manager: system_tray_manager.clone(),
           #[cfg(feature = "tracing")]
           active_tracing_spans: active_tracing_spans.clone(),
+          pending_ready_tasks: &pending_ready_tasks,
+          is_event_loop_ready: is_event_loop_ready.clone(),
         },
         &web_context,
       );
@@ -2360,6 +2405,8 @@ pub struct EventLoopIterationContext<'a, T: UserEvent> {
   pub system_tray_manager: SystemTrayManager,
   #[cfg(feature = "tracing")]
   pub active_tracing_spans: ActiveTraceSpanStore,
+  pub is_event_loop_ready: Arc<AtomicBool>,
+  pub pending_ready_tasks: &'a RefCell<Vec<PendingReadyTask<T>>>,
 }
 
 struct UserMessageContext {
@@ -2817,6 +2864,8 @@ fn handle_event_loop<T: UserEvent>(
     system_tray_manager,
     #[cfg(feature = "tracing")]
     active_tracing_spans,
+    is_event_loop_ready,
+    pending_ready_tasks,
   } = context;
   if *control_flow != ControlFlow::Exit {
     *control_flow = ControlFlow::Wait;
@@ -2824,6 +2873,11 @@ fn handle_event_loop<T: UserEvent>(
 
   match event {
     Event::NewEvents(StartCause::Init) => {
+      is_event_loop_ready.store(true, Ordering::Relaxed);
+      for task in pending_ready_tasks.replace(Vec::new()) {
+        task(event_loop);
+      }
+
       callback(RunEvent::Ready);
     }
 

+ 87 - 7
core/tauri-runtime-wry/src/system_tray.rs

@@ -32,7 +32,7 @@ use std::{
   cell::RefCell,
   collections::HashMap,
   fmt,
-  sync::{Arc, Mutex},
+  sync::{atomic::Ordering, Arc, Mutex},
 };
 
 pub type GlobalSystemTrayEventHandler = Box<dyn Fn(TrayId, &SystemTrayEvent) + Send>;
@@ -161,10 +161,18 @@ pub struct SystemTrayHandle<T: UserEvent> {
   pub(crate) context: Context<T>,
   pub(crate) id: TrayId,
   pub(crate) proxy: EventLoopProxy<super::Message<T>>,
+  pub(crate) pending: PendingSystemTray,
 }
 
 impl<T: UserEvent> TrayHandle for SystemTrayHandle<T> {
   fn set_icon(&self, icon: Icon) -> Result<()> {
+    if !self.context.is_event_loop_ready.load(Ordering::Relaxed) {
+      if let Some(pending) = &mut *self.pending.0.borrow_mut() {
+        pending.icon.replace(icon);
+        return Ok(());
+      }
+    }
+
     self
       .proxy
       .send_event(Message::Tray(self.id, TrayMessage::UpdateIcon(icon)))
@@ -172,6 +180,13 @@ impl<T: UserEvent> TrayHandle for SystemTrayHandle<T> {
   }
 
   fn set_menu(&self, menu: SystemTrayMenu) -> Result<()> {
+    if !self.context.is_event_loop_ready.load(Ordering::Relaxed) {
+      if let Some(pending) = &mut *self.pending.0.borrow_mut() {
+        pending.menu.replace(menu);
+        return Ok(());
+      }
+    }
+
     self
       .proxy
       .send_event(Message::Tray(self.id, TrayMessage::UpdateMenu(menu)))
@@ -179,6 +194,35 @@ impl<T: UserEvent> TrayHandle for SystemTrayHandle<T> {
   }
 
   fn update_item(&self, id: u16, update: MenuUpdate) -> Result<()> {
+    if !self.context.is_event_loop_ready.load(Ordering::Relaxed) {
+      if let Some(pending) = &mut *self.pending.0.borrow_mut() {
+        if let Some(menu) = &mut pending.menu {
+          for item in &mut menu.items {
+            if let SystemTrayMenuEntry::CustomItem(item) = item {
+              if item.id == id {
+                match update {
+                  MenuUpdate::SetEnabled(enabled) => {
+                    item.enabled = enabled;
+                  }
+                  MenuUpdate::SetTitle(title) => {
+                    item.title = title;
+                  }
+                  MenuUpdate::SetSelected(selected) => {
+                    item.selected = selected;
+                  }
+                  #[cfg(target_os = "macos")]
+                  MenuUpdate::SetNativeImage(img) => {
+                    item.native_image.replace(img);
+                  }
+                }
+                break;
+              }
+            }
+          }
+        }
+        return Ok(());
+      }
+    }
     self
       .proxy
       .send_event(Message::Tray(self.id, TrayMessage::UpdateItem(id, update)))
@@ -187,6 +231,13 @@ impl<T: UserEvent> TrayHandle for SystemTrayHandle<T> {
 
   #[cfg(target_os = "macos")]
   fn set_icon_as_template(&self, is_template: bool) -> tauri_runtime::Result<()> {
+    if !self.context.is_event_loop_ready.load(Ordering::Relaxed) {
+      if let Some(pending) = &mut *self.pending.0.borrow_mut() {
+        pending.icon_as_template = is_template;
+        return Ok(());
+      }
+    }
+
     self
       .proxy
       .send_event(Message::Tray(
@@ -198,6 +249,13 @@ impl<T: UserEvent> TrayHandle for SystemTrayHandle<T> {
 
   #[cfg(target_os = "macos")]
   fn set_title(&self, title: &str) -> tauri_runtime::Result<()> {
+    if !self.context.is_event_loop_ready.load(Ordering::Relaxed) {
+      if let Some(pending) = &mut *self.pending.0.borrow_mut() {
+        pending.title.replace(title.to_string());
+        return Ok(());
+      }
+    }
+
     self
       .proxy
       .send_event(Message::Tray(
@@ -208,6 +266,13 @@ impl<T: UserEvent> TrayHandle for SystemTrayHandle<T> {
   }
 
   fn set_tooltip(&self, tooltip: &str) -> Result<()> {
+    if !self.context.is_event_loop_ready.load(Ordering::Relaxed) {
+      if let Some(pending) = &mut *self.pending.0.borrow_mut() {
+        pending.tooltip.replace(tooltip.to_string());
+        return Ok(());
+      }
+    }
+
     self
       .proxy
       .send_event(Message::Tray(
@@ -218,12 +283,16 @@ impl<T: UserEvent> TrayHandle for SystemTrayHandle<T> {
   }
 
   fn destroy(&self) -> Result<()> {
-    let (tx, rx) = std::sync::mpsc::channel();
-    send_user_message(
-      &self.context,
-      Message::Tray(self.id, TrayMessage::Destroy(tx)),
-    )?;
-    rx.recv().unwrap()?;
+    if self.context.is_event_loop_ready.load(Ordering::Relaxed)
+      && self.pending.0.borrow_mut().take().is_none()
+    {
+      let (tx, rx) = std::sync::mpsc::channel();
+      send_user_message(
+        &self.context,
+        Message::Tray(self.id, TrayMessage::Destroy(tx)),
+      )?;
+      rx.recv().unwrap()?;
+    }
     Ok(())
   }
 }
@@ -267,3 +336,14 @@ pub fn to_wry_context_menu(
   }
   tray_menu
 }
+
+#[derive(Debug, Clone)]
+pub struct PendingSystemTray(pub Arc<RefCell<Option<SystemTray>>>);
+
+// SAFETY: we ensure this type is only used on the main thread.
+#[allow(clippy::non_send_fields_in_send_ty)]
+unsafe impl Send for PendingSystemTray {}
+
+// SAFETY: we ensure this type is only used on the main thread.
+#[allow(clippy::non_send_fields_in_send_ty)]
+unsafe impl Sync for PendingSystemTray {}