tray-icon icon indicating copy to clipboard operation
tray-icon copied to clipboard

Crash on macOS

Open ssrlive opened this issue 3 months ago • 4 comments

Error info

➜  iced-demo git:(master)  /Users/xyz/.vscode/extensions/vadimcn.vscode-lldb-1.11.5/adapter/codelldb terminal-agent --connect=52222 
Assertion failed: (CGAtomicGet(&is_initialized)), function CGSConnectionByID, file CGSConnection.mm, line 419.

The call stack is

__pthread_kill (@__pthread_kill:6)
pthread_kill (@pthread_kill:65)
abort (@abort:40)
__assert_rtn (@err:3)
CGSConnectionByID (@CGSConnectionByID:132)
SLSRegisterConnectionNotifyProc (@SLSRegisterConnectionNotifyProc:19)
+[NSCGSStatusItem addNavigationChangedNotificationHandler:] (@+[NSCGSStatusItem addNavigationChangedNotificationHandler:]:32)
__53+[NSStatusItem _registerNotificationHandlersIfNeeded]_block_invoke (@__53+[NSStatusItem _registerNotificationHandlersIfNeeded]_block_invoke:12)
_dispatch_client_callout (@_dispatch_client_callout:8)
_dispatch_once_callout (@_dispatch_once_callout:11)
-[NSStatusItem _initWithStatusBar:length:priority:systemInsertOrder:] (@-[NSStatusItem _initWithStatusBar:length:priority:systemInsertOrder:]:27)
-[NSStatusBar _statusItemWithLength:withPriority:] (@-[NSStatusBar _statusItemWithLength:withPriority:]:22)
<(A,) as objc2::encode::EncodeArguments>::__invoke (/Users/xyz/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/objc2-0.6.2/src/encode.rs:433)
objc2::runtime::message_receiver::msg_send_primitive::send (/Users/xyz/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/objc2-0.6.2/src/runtime/message_receiver.rs:172)
objc2::runtime::message_receiver::MessageReceiver::send_message (/Users/xyz/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/objc2-0.6.2/src/runtime/message_receiver.rs:432)
<MethodFamily as objc2::__macro_helpers::msg_send_retained::MsgSend<Receiver,Return>>::send_message (/Users/xyz/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/objc2-0.6.2/src/__macro_helpers/msg_send_retained.rs:35)
objc2_app_kit::generated::__NSStatusBar::NSStatusBar::statusItemWithLength (/Users/xyz/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/objc2-0.6.2/src/macros/extern_methods.rs:238)
tray_icon::platform_impl::platform::TrayIcon::create (/Users/xyz/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tray-icon-0.21.1/src/platform_impl/macos/mod.rs:55)
tray_icon::platform_impl::platform::TrayIcon::new (/Users/xyz/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tray-icon-0.21.1/src/platform_impl/macos/mod.rs:36)
tray_icon::TrayIcon::new (/Users/xyz/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tray-icon-0.21.1/src/lib.rs:341)
iced_demo::main::{{closure}} (/Users/xyz/Desktop/iced-demo/src/main.rs:133)
iced_demo::main (/Users/xyz/Desktop/iced-demo/src/main.rs:138)
core::ops::function::FnOnce::call_once (/Users/xyz/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/ops/function.rs:250)
std::sys::backtrace::__rust_begin_short_backtrace (/Users/xyz/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:152)
std::rt::lang_start::{{closure}} (/Users/xyz/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/std/src/rt.rs:206)
core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once (@std::rt::lang_start_internal:219)
std::panicking::catch_unwind::do_call (@std::rt::lang_start_internal:217)
std::panicking::catch_unwind (@std::rt::lang_start_internal:217)
std::panic::catch_unwind (@std::rt::lang_start_internal:217)
std::rt::lang_start_internal::{{closure}} (@std::rt::lang_start_internal:217)
std::panicking::catch_unwind::do_call (@std::rt::lang_start_internal:14)
std::panicking::catch_unwind (@std::rt::lang_start_internal:14)
std::panic::catch_unwind (@std::rt::lang_start_internal:14)
std::rt::lang_start_internal (@std::rt::lang_start_internal:14)
std::rt::lang_start (/Users/xyz/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/std/src/rt.rs:205)
main (@main:10)
start (@start:439)
Image

ssrlive avatar Sep 13 '25 05:09 ssrlive

Thanks for the report, which macOS version are you running?

FabianLars avatar Sep 13 '25 07:09 FabianLars

➜  iced-demo git:(master) uname -a
Darwin 23.3.0 Darwin Kernel Version 23.3.0: Wed Dec 20 21:28:58 PST 2023; root:xnu-10002.81.5~7/RELEASE_X86_64 x86_64

ssrlive avatar Sep 13 '25 08:09 ssrlive

Now I make this changes

diff --git a/src/lib.rs b/src/lib.rs
index 5865a3c..c12b257 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -121,9 +121,8 @@
 //! [tao]: https://docs.rs/tao
 
 use std::{
-    cell::RefCell,
     path::{Path, PathBuf},
-    rc::Rc,
+    sync::{Arc, Mutex},
 };
 
 use counter::Counter;
@@ -325,9 +324,12 @@ impl TrayIconBuilder {
 #[derive(Clone)]
 pub struct TrayIcon {
     id: TrayIconId,
-    tray: Rc<RefCell<platform_impl::TrayIcon>>,
+    tray: std::sync::Arc<std::sync::Mutex<platform_impl::TrayIcon>>,
 }
 
+unsafe impl Send for TrayIcon {}
+unsafe impl Sync for TrayIcon {}
+
 impl TrayIcon {
     /// Builds and adds a new tray icon to the system tray.
     ///
@@ -338,10 +340,7 @@ impl TrayIcon {
     pub fn new(attrs: TrayIconAttributes) -> Result<Self> {
         let id = TrayIconId(COUNTER.next().to_string());
         Ok(Self {
-            tray: Rc::new(RefCell::new(platform_impl::TrayIcon::new(
-                id.clone(),
-                attrs,
-            )?)),
+            tray: Arc::new(Mutex::new(platform_impl::TrayIcon::new(id.clone(), attrs)?)),
             id,
         })
     }
@@ -352,10 +351,7 @@ impl TrayIcon {
     pub fn with_id<I: Into<TrayIconId>>(id: I, attrs: TrayIconAttributes) -> Result<Self> {
         let id = id.into();
         Ok(Self {
-            tray: Rc::new(RefCell::new(platform_impl::TrayIcon::new(
-                id.clone(),
-                attrs,
-            )?)),
+            tray: Arc::new(Mutex::new(platform_impl::TrayIcon::new(id.clone(), attrs)?)),
             id,
         })
     }
@@ -367,7 +363,7 @@ impl TrayIcon {
 
     /// Set new tray icon. If `None` is provided, it will remove the icon.
     pub fn set_icon(&self, icon: Option<Icon>) -> Result<()> {
-        self.tray.borrow_mut().set_icon(icon)
+        self.tray.lock().unwrap().set_icon(icon)
     }
 
     /// Set new tray menu.
@@ -376,7 +372,7 @@ impl TrayIcon {
     ///
     /// - **Linux**: once a menu is set it cannot be removed so `None` has no effect
     pub fn set_menu(&self, menu: Option<Box<dyn menu::ContextMenu>>) {
-        self.tray.borrow_mut().set_menu(menu)
+        self.tray.lock().unwrap().set_menu(menu);
     }
 
     /// Sets the tooltip for this tray icon.
@@ -385,7 +381,7 @@ impl TrayIcon {
     ///
     /// - **Linux:** Unsupported
     pub fn set_tooltip<S: AsRef<str>>(&self, tooltip: Option<S>) -> Result<()> {
-        self.tray.borrow_mut().set_tooltip(tooltip)
+        self.tray.lock().unwrap().set_tooltip(tooltip)
     }
 
     /// Sets the tooltip for this tray icon.
@@ -399,12 +395,12 @@ impl TrayIcon {
     ///   on the user's panel.  This may not be shown in all visualizations.
     /// - **Windows:** Unsupported
     pub fn set_title<S: AsRef<str>>(&self, title: Option<S>) {
-        self.tray.borrow_mut().set_title(title)
+        self.tray.lock().unwrap().set_title(title)
     }
 
     /// Show or hide this tray icon
     pub fn set_visible(&self, visible: bool) -> Result<()> {
-        self.tray.borrow_mut().set_visible(visible)
+        self.tray.lock().unwrap().set_visible(visible)
     }
 
     /// Sets the tray icon temp dir path. **Linux only**.
@@ -413,7 +409,7 @@ impl TrayIcon {
     /// be `$XDG_RUNTIME_DIR/tray-icon` or `$TEMP/tray-icon`.
     pub fn set_temp_dir_path<P: AsRef<Path>>(&self, path: Option<P>) {
         #[cfg(target_os = "linux")]
-        self.tray.borrow_mut().set_temp_dir_path(path);
+        self.tray.lock().unwrap().set_temp_dir_path(path);
         #[cfg(not(target_os = "linux"))]
         let _ = path;
     }
@@ -421,7 +417,7 @@ impl TrayIcon {
     /// Set the current icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.
     pub fn set_icon_as_template(&self, is_template: bool) {
         #[cfg(target_os = "macos")]
-        self.tray.borrow_mut().set_icon_as_template(is_template);
+        self.tray.lock().unwrap().set_icon_as_template(is_template);
         #[cfg(not(target_os = "macos"))]
         let _ = is_template;
     }
@@ -430,7 +426,8 @@ impl TrayIcon {
         #[cfg(target_os = "macos")]
         return self
             .tray
-            .borrow_mut()
+            .lock()
+            .unwrap()
             .set_icon_with_as_template(icon, is_template);
         #[cfg(not(target_os = "macos"))]
         {
@@ -447,7 +444,10 @@ impl TrayIcon {
     /// - **Linux:** Unsupported.
     pub fn set_show_menu_on_left_click(&self, enable: bool) {
         #[cfg(any(target_os = "macos", target_os = "windows"))]
-        self.tray.borrow_mut().set_show_menu_on_left_click(enable);
+        self.tray
+            .lock()
+            .unwrap()
+            .set_show_menu_on_left_click(enable);
         #[cfg(not(any(target_os = "macos", target_os = "windows")))]
         let _ = enable;
     }
@@ -458,7 +458,7 @@ impl TrayIcon {
     ///
     /// - **Linux**: Unsupported.
     pub fn rect(&self) -> Option<Rect> {
-        self.tray.borrow().rect()
+        self.tray.lock().unwrap().rect()
     }
 
     /// Get the tray icon's underlying [window handle](windows_sys::Win32::Foundation::HWND) **Windows only**.
@@ -466,7 +466,7 @@ impl TrayIcon {
     /// This window handle is valid as long as the tray icon.
     #[cfg(windows)]
     pub fn window_handle(&self) -> windows_sys::Win32::Foundation::HWND {
-        self.tray.borrow().hwnd()
+        self.tray.lock().unwrap().window_handle()
     }
 
     /// Get the tray icon's underlying [NSStatusItem](objc2_app_kit::NSStatusItem) **macOS only**.
@@ -474,7 +474,7 @@ impl TrayIcon {
     /// Returns `None` if the status item is not available.
     #[cfg(target_os = "macos")]
     pub fn ns_status_item(&self) -> Option<objc2::rc::Retained<objc2_app_kit::NSStatusItem>> {
-        self.tray.borrow().ns_status_item().cloned()
+        self.tray.lock().unwrap().ns_status_item().cloned()
     }
 
     /// Get the tray icon's underlying [AppIndicator](libappindicator::AppIndicator) **Linux only**.
@@ -484,7 +484,7 @@ impl TrayIcon {
     /// The returned pointer is valid as long as the `TrayIcon` is.
     #[cfg(all(unix, not(target_os = "macos")))]
     pub unsafe fn app_indicator(&self) -> *const libappindicator::AppIndicator {
-        self.tray.borrow().app_indicator() as *const _
+        self.tray.lock().unwrap().app_indicator() as *const _
     }
 }

ssrlive avatar Sep 13 '25 11:09 ssrlive

There may be a lifecycle or initialization race between the iced::application run loop and the tray-icon's NSStatusItem.

I try deferring the creation of the tray_icon to the iced::application update or view function (initializing it only once). This ensures that iced has initialized the GUI environment on the main thread.

And I call it like this. now it work fine for me.


static TRAY_ICON_HANDLE: std::sync::OnceLock<Arc<Mutex<tray_icon::TrayIcon>>> = std::sync::OnceLock::new();

fn update(state: &mut AppState, message: Message) {
    TRAY_ICON_HANDLE.get_or_init(|| {
        let tray_icon = tray_icon::TrayIcon::new(attrs).expect("Failed to create tray icon");
        Arc::new(Mutex::new(tray_icon))
    });

   // ...
}

ssrlive avatar Sep 13 '25 11:09 ssrlive