Repository: tauri-apps/tauri
File: crates/tauri/src/tray/mod.rs (Rust)

Code before:
  pub fn id(&self) -> &TrayIconId {
    self.inner.id()
  }

  /// Builds and adds a new [`TrayIcon`] to the system tray.
  pub fn build<M: Manager<R>>(self, manager: &M) -> crate::Result<TrayIcon<R>> {
    let id = self.id().clone();

    struct SafeSend<T>(T);
    unsafe impl<T> Send for SafeSend<T> {}

    // SAFETY:
    // the menu within this builder was created on main thread
    // and will be accessed on the main thread
    let inner = &mut Some(self.inner) as *mut _ as isize;

    let (tx, rx) = std::sync::mpsc::channel();
    let inner = manager
      .app_handle()
      .run_on_main_thread(move || {
        let inner = unsafe { &mut *(inner as *mut Option<tray_icon::TrayIconBuilder>) };

        // SAFETY: will only be accessed on main thread
        let _ = tx.send(inner.take().unwrap().build().map(SafeSend));
      })
      .and_then(|_| rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage))??;

    let icon = TrayIcon {
      id,
      inner: inner.0,
      app_handle: manager.app_handle().clone(),
    };

    icon.register(
      &icon.app_handle,
      self.on_menu_event,
      self.on_tray_icon_event,
    );

    Ok(icon)
  }
}

/// Tray icon struct and associated methods.
///
/// This type is reference-counted and the icon is removed when the last instance is dropped.
///
/// See [TrayIconBuilder] to construct this type.
#[tauri_macros::default_runtime(crate::Wry, wry)]
pub struct TrayIcon<R: Runtime> {
  id: TrayIconId,

Review comment (bug):
The cast just feels wrong to me, maybe something like this?

```suggestion
    struct SafeSendTrayIconBuilder(tray_icon::TrayIconBuilder);
    unsafe impl Send for SafeSendTrayIconBuilder {}
    impl SafeSendTrayIconBuilder {
      fn build(self) -> Result<tray_icon::TrayIcon, tray_icon::Error> {
        self.0.build()
      }
    }

    // SAFETY:
    // the menu within this builder was created on main thread
    // and will be accessed on the main thread
    let inner = SafeSendTrayIconBuilder(self.inner);

    let (tx, rx) = std::sync::mpsc::channel();
    let inner = manager
      .app_handle()
      .run_on_main_thread(move || {
        // SAFETY: will only be accessed on main thread
        let _ = tx.send(inner.build().map(SafeSend));
      })
      .and_then(|_| rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage))??;
```

Code after:
  /// this builder will create.
  pub fn id(&self) -> &TrayIconId {
    self.inner.id()
  }

  /// Builds and adds a new [`TrayIcon`] to the system tray.
  pub fn build<M: Manager<R>>(self, manager: &M) -> crate::Result<TrayIcon<R>> {
    let id = self.id().clone();

    // 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: unsafe_tray.take(),
      app_handle: manager.app_handle().clone(),
    };

    icon.register(
      &icon.app_handle,
      self.on_menu_event,
      self.on_tray_icon_event,
    );

    Ok(icon)
  }
}

/// Tray icon struct and associated methods.
///
/// This type is reference-counted and the icon is removed when the last instance is dropped.
///
/// See [TrayIconBuilder] to construct this type.
#[tauri_macros::default_runtime(crate::Wry, wry)]
pub struct TrayIcon<R: Runtime> {
  id: TrayIconId,
  inner: tray_icon::TrayIcon,
  app_handle: AppHandle<R>,
}