feat(core): create system tray at runtime, closes #2278 (#4862)

This commit is contained in:
Lucas Fernandes Nogueira
2022-08-09 14:43:51 -03:00
committed by GitHub
parent fd992c547e
commit 4d063ae9ee
15 changed files with 957 additions and 384 deletions

7
.changes/runtime-tray.md Normal file
View File

@@ -0,0 +1,7 @@
---
"tauri": minor
"tauri-runtime": minor
"tauri-runtime-wry": minor
---
Added APIs to create a system tray at runtime.

5
.changes/tray-destroy.md Normal file
View File

@@ -0,0 +1,5 @@
---
"tauri": minor
---
Added the `SystemTrayHandle::destroy` method.

View File

@@ -8,7 +8,7 @@ members = [
"core/tauri-utils",
"core/tauri-build",
"core/tauri-codegen",
# integration tests
"core/tests/restart",
"core/tests/app-updater"

View File

@@ -30,16 +30,11 @@ use webview2_com::FocusChangedEventHandler;
use windows::Win32::{Foundation::HWND, System::WinRT::EventRegistrationToken};
#[cfg(target_os = "macos")]
use wry::application::platform::macos::WindowBuilderExtMacOS;
#[cfg(all(feature = "system-tray", target_os = "macos"))]
use wry::application::platform::macos::{SystemTrayBuilderExtMacOS, SystemTrayExtMacOS};
#[cfg(target_os = "linux")]
use wry::application::platform::unix::{WindowBuilderExtUnix, WindowExtUnix};
#[cfg(windows)]
use wry::application::platform::windows::{WindowBuilderExtWindows, WindowExtWindows};
#[cfg(all(desktop, feature = "system-tray"))]
use wry::application::system_tray::{SystemTray as WrySystemTray, SystemTrayBuilder};
use tauri_utils::{config::WindowConfig, debug_eprintln, Theme};
use uuid::Uuid;
use wry::{
@@ -91,7 +86,6 @@ use std::{
HashMap, HashSet,
},
fmt,
marker::PhantomData,
ops::Deref,
path::PathBuf,
sync::{
@@ -104,6 +98,8 @@ use std::{
pub type WebviewId = u64;
type IpcHandler = dyn Fn(&Window, String) + 'static;
type FileDropHandler = dyn Fn(&Window, WryFileDropEvent) -> bool + 'static;
#[cfg(all(desktop, feature = "system-tray"))]
pub use tauri_runtime::TrayId;
#[cfg(desktop)]
mod webview;
@@ -173,7 +169,6 @@ fn send_user_message<T: UserEvent>(context: &Context<T>, message: Message<T>) ->
&context.main_thread.window_target,
message,
UserMessageContext {
marker: &PhantomData,
webview_id_map: context.webview_id_map.clone(),
#[cfg(all(desktop, feature = "global-shortcut"))]
global_shortcut_manager: context.main_thread.global_shortcut_manager.clone(),
@@ -181,7 +176,7 @@ fn send_user_message<T: UserEvent>(context: &Context<T>, message: Message<T>) ->
clipboard_manager: context.main_thread.clipboard_manager.clone(),
windows: context.main_thread.windows.clone(),
#[cfg(all(desktop, feature = "system-tray"))]
tray_context: &context.main_thread.tray_context,
system_tray_manager: context.main_thread.system_tray_manager.clone(),
},
&context.main_thread.web_context,
);
@@ -256,7 +251,7 @@ pub struct DispatcherMainThreadContext<T: UserEvent> {
pub clipboard_manager: Arc<Mutex<Clipboard>>,
pub windows: Arc<Mutex<HashMap<WebviewId, WindowWrapper>>>,
#[cfg(all(desktop, feature = "system-tray"))]
pub tray_context: TrayContext,
system_tray_manager: SystemTrayManager,
}
// SAFETY: we ensure this type is only used on the main thread.
@@ -1086,7 +1081,8 @@ pub enum TrayMessage {
UpdateIcon(Icon),
#[cfg(target_os = "macos")]
UpdateIconAsTemplate(bool),
Close,
Create(SystemTray, Sender<Result<()>>),
Destroy,
}
pub type CreateWebviewClosure<T> = Box<
@@ -1098,7 +1094,7 @@ pub enum Message<T: 'static> {
Window(WebviewId, WindowMessage),
Webview(WebviewId, WebviewMessage),
#[cfg(all(desktop, feature = "system-tray"))]
Tray(TrayMessage),
Tray(TrayId, TrayMessage),
CreateWebview(WebviewId, CreateWebviewClosure<T>),
CreateWindow(
WebviewId,
@@ -1117,7 +1113,7 @@ impl<T: UserEvent> Clone for Message<T> {
match self {
Self::Webview(i, m) => Self::Webview(*i, m.clone()),
#[cfg(all(desktop, feature = "system-tray"))]
Self::Tray(m) => Self::Tray(m.clone()),
Self::Tray(i, m) => Self::Tray(*i, m.clone()),
#[cfg(all(desktop, feature = "global-shortcut"))]
Self::GlobalShortcut(m) => Self::GlobalShortcut(m.clone()),
#[cfg(feature = "clipboard")]
@@ -1525,23 +1521,6 @@ impl<T: UserEvent> Dispatch<T> for WryDispatcher<T> {
}
}
#[cfg(all(desktop, feature = "system-tray"))]
#[derive(Clone, Default)]
pub struct TrayContext {
tray: Arc<Mutex<Option<Arc<Mutex<WrySystemTray>>>>>,
listeners: SystemTrayEventListeners,
items: SystemTrayItems,
}
#[cfg(all(desktop, feature = "system-tray"))]
impl fmt::Debug for TrayContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TrayContext")
.field("items", &self.items)
.finish()
}
}
#[derive(Clone)]
enum WindowHandle {
Webview(Arc<WebView>),
@@ -1650,7 +1629,10 @@ impl<T: UserEvent> fmt::Debug for Wry<T> {
.field("web_context", &self.context.main_thread.web_context);
#[cfg(all(desktop, feature = "system-tray"))]
d.field("tray_context", &self.context.main_thread.tray_context);
d.field(
"system_tray_manager",
&self.context.main_thread.system_tray_manager,
);
#[cfg(all(desktop, feature = "global-shortcut"))]
#[cfg(feature = "global-shortcut")]
@@ -1741,9 +1723,22 @@ impl<T: UserEvent> RuntimeHandle<T> for WryHandle<T> {
send_user_message(&self.context, Message::Task(Box::new(f)))
}
#[cfg(all(windows, feature = "system-tray"))]
fn remove_system_tray(&self) -> Result<()> {
send_user_message(&self.context, Message::Tray(TrayMessage::Close))
#[cfg(all(desktop, feature = "system-tray"))]
fn system_tray(
&self,
system_tray: SystemTray,
) -> Result<<Self::Runtime as Runtime<T>>::TrayHandler> {
let id = system_tray.id;
let (tx, rx) = channel();
send_user_message(
&self.context,
Message::Tray(id, TrayMessage::Create(system_tray, tx)),
)?;
rx.recv().unwrap()?;
Ok(SystemTrayHandle {
id,
proxy: self.context.proxy.clone(),
})
}
fn raw_display_handle(&self) -> RawDisplayHandle {
@@ -1766,7 +1761,7 @@ impl<T: UserEvent> Wry<T> {
let webview_id_map = WebviewIdStore::default();
#[cfg(all(desktop, feature = "system-tray"))]
let tray_context = TrayContext::default();
let system_tray_manager = Default::default();
let context = Context {
webview_id_map,
@@ -1781,7 +1776,7 @@ impl<T: UserEvent> Wry<T> {
clipboard_manager,
windows,
#[cfg(all(desktop, feature = "system-tray"))]
tray_context,
system_tray_manager,
},
};
@@ -1906,50 +1901,45 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
}
#[cfg(all(desktop, feature = "system-tray"))]
fn system_tray(&self, system_tray: SystemTray) -> Result<Self::TrayHandler> {
let icon = TrayIcon::try_from(system_tray.icon.expect("tray icon not set"))?;
let mut items = HashMap::new();
#[allow(unused_mut)]
let mut tray_builder = SystemTrayBuilder::new(
icon.0,
system_tray
.menu
.map(|menu| to_wry_context_menu(&mut items, menu)),
);
#[cfg(target_os = "macos")]
{
tray_builder = tray_builder
.with_icon_as_template(system_tray.icon_as_template)
.with_menu_on_left_click(system_tray.menu_on_left_click);
fn system_tray(&self, mut system_tray: SystemTray) -> Result<Self::TrayHandler> {
let id = system_tray.id;
let mut listeners = Vec::new();
if let Some(l) = system_tray.on_event.take() {
listeners.push(Arc::new(l));
}
let tray = tray_builder
.build(&self.event_loop)
.map_err(|e| Error::SystemTray(Box::new(e)))?;
*self.context.main_thread.tray_context.items.lock().unwrap() = items;
*self.context.main_thread.tray_context.tray.lock().unwrap() = Some(Arc::new(Mutex::new(tray)));
let (tray, items) = create_tray(WryTrayId(id), system_tray, &self.event_loop)?;
self
.context
.main_thread
.system_tray_manager
.trays
.lock()
.unwrap()
.insert(
id,
TrayContext {
tray: Arc::new(Mutex::new(Some(tray))),
listeners: Arc::new(Mutex::new(listeners)),
items: Arc::new(Mutex::new(items)),
},
);
Ok(SystemTrayHandle {
id,
proxy: self.event_loop.create_proxy(),
})
}
#[cfg(all(desktop, feature = "system-tray"))]
fn on_system_tray_event<F: Fn(&SystemTrayEvent) + Send + 'static>(&mut self, f: F) -> Uuid {
let id = Uuid::new_v4();
fn on_system_tray_event<F: Fn(TrayId, &SystemTrayEvent) + Send + 'static>(&mut self, f: F) {
self
.context
.main_thread
.tray_context
.listeners
.system_tray_manager
.global_listeners
.lock()
.unwrap()
.insert(id, Arc::new(Box::new(f)));
id
.push(Arc::new(Box::new(f)));
}
#[cfg(target_os = "macos")]
@@ -1972,7 +1962,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
let web_context = &self.context.main_thread.web_context;
let plugins = &mut self.plugins;
#[cfg(all(desktop, feature = "system-tray"))]
let tray_context = self.context.main_thread.tray_context.clone();
let system_tray_manager = self.context.main_thread.system_tray_manager.clone();
#[cfg(all(desktop, feature = "global-shortcut"))]
let global_shortcut_manager = self.context.main_thread.global_shortcut_manager.clone();
@@ -2010,7 +2000,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
#[cfg(feature = "clipboard")]
clipboard_manager: clipboard_manager.clone(),
#[cfg(all(desktop, feature = "system-tray"))]
tray_context: &tray_context,
system_tray_manager: system_tray_manager.clone(),
},
web_context,
);
@@ -2034,7 +2024,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
#[cfg(feature = "clipboard")]
clipboard_manager: clipboard_manager.clone(),
#[cfg(all(desktop, feature = "system-tray"))]
tray_context: &tray_context,
system_tray_manager: system_tray_manager.clone(),
},
web_context,
);
@@ -2050,7 +2040,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
let mut plugins = self.plugins;
#[cfg(all(desktop, feature = "system-tray"))]
let tray_context = self.context.main_thread.tray_context;
let system_tray_manager = self.context.main_thread.system_tray_manager;
#[cfg(all(desktop, feature = "global-shortcut"))]
let global_shortcut_manager = self.context.main_thread.global_shortcut_manager.clone();
@@ -2080,7 +2070,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
#[cfg(feature = "clipboard")]
clipboard_manager: clipboard_manager.clone(),
#[cfg(all(desktop, feature = "system-tray"))]
tray_context: &tray_context,
system_tray_manager: system_tray_manager.clone(),
},
&web_context,
);
@@ -2103,7 +2093,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
#[cfg(feature = "clipboard")]
clipboard_manager: clipboard_manager.clone(),
#[cfg(all(desktop, feature = "system-tray"))]
tray_context: &tray_context,
system_tray_manager: system_tray_manager.clone(),
},
&web_context,
);
@@ -2122,12 +2112,10 @@ pub struct EventLoopIterationContext<'a, T: UserEvent> {
#[cfg(feature = "clipboard")]
pub clipboard_manager: Arc<Mutex<Clipboard>>,
#[cfg(all(desktop, feature = "system-tray"))]
pub tray_context: &'a TrayContext,
pub system_tray_manager: SystemTrayManager,
}
struct UserMessageContext<'a> {
#[allow(dead_code)]
marker: &'a PhantomData<()>,
struct UserMessageContext {
webview_id_map: WebviewIdStore,
#[cfg(all(desktop, feature = "global-shortcut"))]
global_shortcut_manager: Arc<Mutex<WryShortcutManager>>,
@@ -2135,17 +2123,16 @@ struct UserMessageContext<'a> {
clipboard_manager: Arc<Mutex<Clipboard>>,
windows: Arc<Mutex<HashMap<WebviewId, WindowWrapper>>>,
#[cfg(all(desktop, feature = "system-tray"))]
tray_context: &'a TrayContext,
system_tray_manager: SystemTrayManager,
}
fn handle_user_message<T: UserEvent>(
event_loop: &EventLoopWindowTarget<Message<T>>,
message: Message<T>,
context: UserMessageContext<'_>,
context: UserMessageContext,
web_context: &WebContextStore,
) -> RunIteration {
let UserMessageContext {
marker: _,
webview_id_map,
#[cfg(all(desktop, feature = "global-shortcut"))]
global_shortcut_manager,
@@ -2153,7 +2140,7 @@ fn handle_user_message<T: UserEvent>(
clipboard_manager,
windows,
#[cfg(all(desktop, feature = "system-tray"))]
tray_context,
system_tray_manager,
} = context;
match message {
Message::Task(task) => task(),
@@ -2455,49 +2442,74 @@ fn handle_user_message<T: UserEvent>(
}
#[cfg(all(desktop, feature = "system-tray"))]
Message::Tray(tray_message) => match tray_message {
TrayMessage::UpdateItem(menu_id, update) => {
let mut tray = tray_context.items.as_ref().lock().unwrap();
let item = tray.get_mut(&menu_id).expect("menu item not found");
match update {
MenuUpdate::SetEnabled(enabled) => item.set_enabled(enabled),
MenuUpdate::SetTitle(title) => item.set_title(&title),
MenuUpdate::SetSelected(selected) => item.set_selected(selected),
Message::Tray(tray_id, tray_message) => {
let mut trays = system_tray_manager.trays.lock().unwrap();
if let TrayMessage::Create(tray, tx) = tray_message {
match create_tray(WryTrayId(tray_id), tray, event_loop) {
Ok((tray, items)) => {
trays.insert(
tray_id,
TrayContext {
tray: Arc::new(Mutex::new(Some(tray))),
listeners: Default::default(),
items: Arc::new(Mutex::new(items)),
},
);
tx.send(Ok(())).unwrap();
}
Err(e) => {
tx.send(Err(e)).unwrap();
}
}
} else if let Some(tray_context) = trays.get(&tray_id) {
match tray_message {
TrayMessage::UpdateItem(menu_id, update) => {
let mut tray = tray_context.items.as_ref().lock().unwrap();
let item = tray.get_mut(&menu_id).expect("menu item not found");
match update {
MenuUpdate::SetEnabled(enabled) => item.set_enabled(enabled),
MenuUpdate::SetTitle(title) => item.set_title(&title),
MenuUpdate::SetSelected(selected) => item.set_selected(selected),
#[cfg(target_os = "macos")]
MenuUpdate::SetNativeImage(image) => {
item.set_native_image(NativeImageWrapper::from(image).0)
}
}
}
TrayMessage::UpdateMenu(menu) => {
if let Some(tray) = &mut *tray_context.tray.lock().unwrap() {
let mut items = HashMap::new();
tray.set_menu(&to_wry_context_menu(&mut items, menu));
*tray_context.items.lock().unwrap() = items;
}
}
TrayMessage::UpdateIcon(icon) => {
if let Some(tray) = &mut *tray_context.tray.lock().unwrap() {
if let Ok(icon) = TrayIcon::try_from(icon) {
tray.set_icon(icon.0);
}
}
}
#[cfg(target_os = "macos")]
MenuUpdate::SetNativeImage(image) => {
item.set_native_image(NativeImageWrapper::from(image).0)
TrayMessage::UpdateIconAsTemplate(is_template) => {
if let Some(tray) = &mut *tray_context.tray.lock().unwrap() {
tray.set_icon_as_template(is_template);
}
}
TrayMessage::Create(_tray, _tx) => {
// already handled
}
TrayMessage::Destroy => {
*tray_context.tray.lock().unwrap() = None;
tray_context.listeners.lock().unwrap().clear();
tray_context.items.lock().unwrap().clear();
}
}
}
TrayMessage::UpdateMenu(menu) => {
if let Some(tray) = &*tray_context.tray.lock().unwrap() {
let mut items = HashMap::new();
tray
.lock()
.unwrap()
.set_menu(&to_wry_context_menu(&mut items, menu));
*tray_context.items.lock().unwrap() = items;
}
}
TrayMessage::UpdateIcon(icon) => {
if let Some(tray) = &*tray_context.tray.lock().unwrap() {
if let Ok(icon) = TrayIcon::try_from(icon) {
tray.lock().unwrap().set_icon(icon.0);
}
}
}
#[cfg(target_os = "macos")]
TrayMessage::UpdateIconAsTemplate(is_template) => {
if let Some(tray) = &*tray_context.tray.lock().unwrap() {
tray.lock().unwrap().set_icon_as_template(is_template);
}
}
TrayMessage::Close => {
*tray_context.tray.lock().unwrap() = None;
tray_context.listeners.lock().unwrap().clear();
tray_context.items.lock().unwrap().clear();
}
},
}
#[cfg(all(desktop, feature = "global-shortcut"))]
Message::GlobalShortcut(message) => {
handle_global_shortcut_message(message, &global_shortcut_manager)
@@ -2531,7 +2543,7 @@ fn handle_event_loop<T: UserEvent>(
#[cfg(feature = "clipboard")]
clipboard_manager,
#[cfg(all(desktop, feature = "system-tray"))]
tray_context,
system_tray_manager,
} = context;
if *control_flow != ControlFlow::Exit {
*control_flow = ControlFlow::Wait;
@@ -2612,13 +2624,40 @@ fn handle_event_loop<T: UserEvent>(
..
} => {
let event = SystemTrayEvent::MenuItemClick(menu_id.0);
let listeners = tray_context.listeners.lock().unwrap().clone();
for handler in listeners.values() {
handler(&event);
let trays = system_tray_manager.trays.lock().unwrap();
let trays_iter = trays.iter();
let (mut listeners, mut tray_id) = (None, 0);
for (id, tray_context) in trays_iter {
let has_menu = {
let items = tray_context.items.lock().unwrap();
items.contains_key(&menu_id.0)
};
if has_menu {
listeners.replace(tray_context.listeners.clone());
tray_id = *id;
break;
}
}
drop(trays);
if let Some(listeners) = listeners {
let listeners = listeners.lock().unwrap();
let handlers = listeners.iter();
for handler in handlers {
handler(&event);
}
let global_listeners = system_tray_manager.global_listeners.lock().unwrap();
let global_listeners_iter = global_listeners.iter();
for global_listener in global_listeners_iter {
global_listener(tray_id, &event);
}
}
}
#[cfg(all(desktop, feature = "system-tray"))]
Event::TrayEvent {
id,
bounds,
event,
position: _cursor_position,
@@ -2634,10 +2673,13 @@ fn handle_event_loop<T: UserEvent>(
// default to left click
_ => SystemTrayEvent::LeftClick { position, size },
};
let listeners = tray_context.listeners.lock().unwrap();
let handlers = listeners.values();
for handler in handlers {
handler(&event);
let trays = system_tray_manager.trays.lock().unwrap();
if let Some(tray_context) = trays.get(&id.0) {
let listeners = tray_context.listeners.lock().unwrap();
let iter = listeners.iter();
for handler in iter {
handler(&event);
}
}
}
Event::WindowEvent {
@@ -2715,7 +2757,6 @@ fn handle_event_loop<T: UserEvent>(
event_loop,
message,
UserMessageContext {
marker: &PhantomData,
webview_id_map,
#[cfg(all(desktop, feature = "global-shortcut"))]
global_shortcut_manager,
@@ -2723,7 +2764,7 @@ fn handle_event_loop<T: UserEvent>(
clipboard_manager,
windows,
#[cfg(all(desktop, feature = "system-tray"))]
tray_context,
system_tray_manager,
},
web_context,
);

View File

@@ -9,6 +9,7 @@ pub use tauri_runtime::{
},
Icon, SystemTrayEvent,
};
use wry::application::event_loop::EventLoopWindowTarget;
pub use wry::application::{
event::TrayEvent,
event_loop::EventLoopProxy,
@@ -16,26 +17,62 @@ pub use wry::application::{
ContextMenu as WryContextMenu, CustomMenuItem as WryCustomMenuItem, MenuItem as WryMenuItem,
},
system_tray::Icon as WryTrayIcon,
TrayId as WryTrayId,
};
#[cfg(target_os = "macos")]
pub use wry::application::platform::macos::CustomMenuItemExtMacOS;
pub use wry::application::platform::macos::{
CustomMenuItemExtMacOS, SystemTrayBuilderExtMacOS, SystemTrayExtMacOS,
};
use crate::{Error, Message, Result, TrayMessage};
use wry::application::system_tray::{SystemTray as WrySystemTray, SystemTrayBuilder};
use tauri_runtime::{menu::MenuHash, UserEvent};
use crate::{Error, Message, Result, TrayId, TrayMessage};
use uuid::Uuid;
use tauri_runtime::{menu::MenuHash, SystemTray, UserEvent};
use std::{
collections::HashMap,
fmt,
sync::{Arc, Mutex},
};
pub type GlobalSystemTrayEventHandler = Box<dyn Fn(TrayId, &SystemTrayEvent) + Send>;
pub type GlobalSystemTrayEventListeners = Arc<Mutex<Vec<Arc<GlobalSystemTrayEventHandler>>>>;
pub type SystemTrayEventHandler = Box<dyn Fn(&SystemTrayEvent) + Send>;
pub type SystemTrayEventListeners = Arc<Mutex<HashMap<Uuid, Arc<SystemTrayEventHandler>>>>;
pub type SystemTrayEventListeners = Arc<Mutex<Vec<Arc<SystemTrayEventHandler>>>>;
pub type SystemTrayItems = Arc<Mutex<HashMap<u16, WryCustomMenuItem>>>;
#[derive(Clone, Default)]
pub struct TrayContext {
pub tray: Arc<Mutex<Option<WrySystemTray>>>,
pub listeners: SystemTrayEventListeners,
pub items: SystemTrayItems,
}
impl fmt::Debug for TrayContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TrayContext")
.field("items", &self.items)
.finish()
}
}
#[derive(Clone, Default)]
pub struct SystemTrayManager {
pub trays: Arc<Mutex<HashMap<TrayId, TrayContext>>>,
pub global_listeners: GlobalSystemTrayEventListeners,
}
impl fmt::Debug for SystemTrayManager {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SystemTrayManager")
.field("trays", &self.trays)
.finish()
}
}
/// Wrapper around a [`wry::application::system_tray::Icon`] that can be created from an [`WindowIcon`].
pub struct TrayIcon(pub(crate) WryTrayIcon);
@@ -48,8 +85,39 @@ impl TryFrom<Icon> for TrayIcon {
}
}
pub fn create_tray<T>(
id: WryTrayId,
system_tray: SystemTray,
event_loop: &EventLoopWindowTarget<T>,
) -> crate::Result<(WrySystemTray, HashMap<u16, WryCustomMenuItem>)> {
let icon = TrayIcon::try_from(system_tray.icon.expect("tray icon not set"))?;
let mut items = HashMap::new();
#[allow(unused_mut)]
let mut builder = SystemTrayBuilder::new(
icon.0,
system_tray
.menu
.map(|menu| to_wry_context_menu(&mut items, menu)),
)
.with_id(id);
#[cfg(target_os = "macos")]
{
builder = builder.with_icon_as_template(system_tray.icon_as_template)
}
let tray = builder
.build(event_loop)
.map_err(|e| Error::SystemTray(Box::new(e)))?;
Ok((tray, items))
}
#[derive(Debug, Clone)]
pub struct SystemTrayHandle<T: UserEvent> {
pub(crate) id: TrayId,
pub(crate) proxy: EventLoopProxy<super::Message<T>>,
}
@@ -57,28 +125,39 @@ impl<T: UserEvent> TrayHandle for SystemTrayHandle<T> {
fn set_icon(&self, icon: Icon) -> Result<()> {
self
.proxy
.send_event(Message::Tray(TrayMessage::UpdateIcon(icon)))
.send_event(Message::Tray(self.id, TrayMessage::UpdateIcon(icon)))
.map_err(|_| Error::FailedToSendMessage)
}
fn set_menu(&self, menu: SystemTrayMenu) -> Result<()> {
self
.proxy
.send_event(Message::Tray(TrayMessage::UpdateMenu(menu)))
.send_event(Message::Tray(self.id, TrayMessage::UpdateMenu(menu)))
.map_err(|_| Error::FailedToSendMessage)
}
fn update_item(&self, id: u16, update: MenuUpdate) -> Result<()> {
self
.proxy
.send_event(Message::Tray(TrayMessage::UpdateItem(id, update)))
.send_event(Message::Tray(self.id, TrayMessage::UpdateItem(id, update)))
.map_err(|_| Error::FailedToSendMessage)
}
#[cfg(target_os = "macos")]
fn set_icon_as_template(&self, is_template: bool) -> tauri_runtime::Result<()> {
self
.proxy
.send_event(Message::Tray(TrayMessage::UpdateIconAsTemplate(
is_template,
)))
.send_event(Message::Tray(
self.id,
TrayMessage::UpdateIconAsTemplate(is_template),
))
.map_err(|_| Error::FailedToSendMessage)
}
fn destroy(&self) -> Result<()> {
self
.proxy
.send_event(Message::Tray(self.id, TrayMessage::Destroy))
.map_err(|_| Error::FailedToSendMessage)
}
}

View File

@@ -32,6 +32,7 @@ http = "0.2.4"
http-range = "0.1.4"
infer = "0.7"
raw-window-handle = "0.5"
rand = "0.8"
[target."cfg(windows)".dependencies]
webview2-com = "0.16.0"

View File

@@ -34,16 +34,71 @@ use crate::http::{
InvalidUri,
};
#[cfg(all(desktop, feature = "system-tray"))]
use std::fmt;
pub type TrayId = u16;
pub type TrayEventHandler = dyn Fn(&SystemTrayEvent) + Send + 'static;
#[cfg(all(desktop, feature = "system-tray"))]
#[non_exhaustive]
#[derive(Debug, Default)]
pub struct SystemTray {
pub id: TrayId,
pub icon: Option<Icon>,
pub menu: Option<menu::SystemTrayMenu>,
#[cfg(target_os = "macos")]
pub icon_as_template: bool,
#[cfg(target_os = "macos")]
pub menu_on_left_click: bool,
pub on_event: Option<Box<TrayEventHandler>>,
}
#[cfg(all(desktop, feature = "system-tray"))]
impl fmt::Debug for SystemTray {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("SystemTray");
d.field("id", &self.id)
.field("icon", &self.icon)
.field("menu", &self.menu);
#[cfg(target_os = "macos")]
{
d.field("icon_as_template", &self.icon_as_template)
.field("menu_on_left_click", &self.menu_on_left_click);
}
d.finish()
}
}
#[cfg(all(desktop, feature = "system-tray"))]
impl Clone for SystemTray {
fn clone(&self) -> Self {
Self {
id: self.id,
icon: self.icon.clone(),
menu: self.menu.clone(),
on_event: None,
#[cfg(target_os = "macos")]
icon_as_template: self.icon_as_template,
#[cfg(target_os = "macos")]
menu_on_left_click: self.menu_on_left_click,
}
}
}
#[cfg(all(desktop, feature = "system-tray"))]
impl Default for SystemTray {
fn default() -> Self {
Self {
id: rand::random(),
icon: None,
menu: None,
#[cfg(target_os = "macos")]
icon_as_template: false,
#[cfg(target_os = "macos")]
menu_on_left_click: false,
on_event: None,
}
}
}
#[cfg(all(desktop, feature = "system-tray"))]
@@ -57,6 +112,13 @@ impl SystemTray {
self.menu.as_ref()
}
/// Sets the tray id.
#[must_use]
pub fn with_id(mut self, id: TrayId) -> Self {
self.id = id;
self
}
/// Sets the tray icon.
#[must_use]
pub fn with_icon(mut self, icon: Icon) -> Self {
@@ -86,6 +148,12 @@ impl SystemTray {
self.menu.replace(menu);
self
}
#[must_use]
pub fn on_event<F: Fn(&SystemTrayEvent) + Send + 'static>(mut self, f: F) -> Self {
self.on_event.replace(Box::new(f));
self
}
}
/// Type of user attention requested on a window.
@@ -261,9 +329,13 @@ pub trait RuntimeHandle<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'st
/// Run a task on the main thread.
fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()>;
#[cfg(all(windows, feature = "system-tray"))]
#[cfg_attr(doc_cfg, doc(cfg(all(windows, feature = "system-tray"))))]
fn remove_system_tray(&self) -> Result<()>;
/// Adds an icon to the system tray with the specified menu items.
#[cfg(all(desktop, feature = "system-tray"))]
#[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "system-tray"))))]
fn system_tray(
&self,
system_tray: SystemTray,
) -> Result<<Self::Runtime as Runtime<T>>::TrayHandler>;
fn raw_display_handle(&self) -> RawDisplayHandle;
}
@@ -348,7 +420,7 @@ pub trait Runtime<T: UserEvent>: Debug + Sized + 'static {
/// Registers a system tray event handler.
#[cfg(all(desktop, feature = "system-tray"))]
#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
fn on_system_tray_event<F: Fn(&SystemTrayEvent) + Send + 'static>(&mut self, f: F) -> Uuid;
fn on_system_tray_event<F: Fn(TrayId, &SystemTrayEvent) + Send + 'static>(&mut self, f: F);
/// Sets the activation policy for the application. It is set to `NSApplicationActivationPolicyRegular` by default.
#[cfg(target_os = "macos")]

View File

@@ -152,6 +152,7 @@ pub trait TrayHandle: fmt::Debug + Clone + Send + Sync {
fn update_item(&self, id: u16, update: MenuUpdate) -> crate::Result<()>;
#[cfg(target_os = "macos")]
fn set_icon_as_template(&self, is_template: bool) -> crate::Result<()>;
fn destroy(&self) -> crate::Result<()>;
}
/// A window menu.

View File

@@ -2329,9 +2329,7 @@ impl Default for UpdaterConfig {
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct SystemTrayConfig {
/// Path to the icon to use on the system tray.
///
/// It is forced to be a `.png` file on Linux and macOS, and a `.ico` file on Windows.
/// Path to the default icon to use on the system tray.
#[serde(alias = "icon-path")]
pub icon_path: PathBuf,
/// A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.

View File

@@ -47,8 +47,6 @@ use std::{
use crate::runtime::menu::{Menu, MenuId, MenuIdRef};
use crate::runtime::RuntimeHandle;
#[cfg(all(desktop, feature = "system-tray"))]
use crate::runtime::SystemTrayEvent as RuntimeSystemTrayEvent;
#[cfg(updater)]
use crate::updater;
@@ -326,8 +324,6 @@ pub struct AppHandle<R: Runtime> {
global_shortcut_manager: R::GlobalShortcutManager,
#[cfg(feature = "clipboard")]
clipboard_manager: R::ClipboardManager,
#[cfg(all(desktop, feature = "system-tray"))]
tray_handle: Option<tray::SystemTrayHandle<R>>,
/// The updater configuration.
#[cfg(updater)]
pub(crate) updater_settings: UpdaterSettings,
@@ -379,8 +375,6 @@ impl<R: Runtime> Clone for AppHandle<R> {
global_shortcut_manager: self.global_shortcut_manager.clone(),
#[cfg(feature = "clipboard")]
clipboard_manager: self.clipboard_manager.clone(),
#[cfg(all(desktop, feature = "system-tray"))]
tray_handle: self.tray_handle.clone(),
#[cfg(updater)]
updater_settings: self.updater_settings.clone(),
}
@@ -403,13 +397,6 @@ impl<R: Runtime> AppHandle<R> {
.map_err(Into::into)
}
/// Removes the system tray.
#[cfg(all(windows, feature = "system-tray"))]
#[cfg_attr(doc_cfg, doc(cfg(all(windows, feature = "system-tray"))))]
fn remove_system_tray(&self) -> crate::Result<()> {
self.runtime_handle.remove_system_tray().map_err(Into::into)
}
/// Adds a Tauri application plugin.
/// This function can be used to register a plugin that is loaded dynamically e.g. after login.
/// For plugins that are created when the app is started, prefer [`Builder::plugin`].
@@ -513,7 +500,9 @@ impl<R: Runtime> AppHandle<R> {
}
#[cfg(all(windows, feature = "system-tray"))]
{
let _ = self.remove_system_tray();
for tray in self.manager().trays().values() {
let _ = tray.destroy();
}
}
}
}
@@ -545,8 +534,6 @@ pub struct App<R: Runtime> {
global_shortcut_manager: R::GlobalShortcutManager,
#[cfg(feature = "clipboard")]
clipboard_manager: R::ClipboardManager,
#[cfg(all(desktop, feature = "system-tray"))]
tray_handle: Option<tray::SystemTrayHandle<R>>,
handle: AppHandle<R>,
}
@@ -607,14 +594,72 @@ macro_rules! shared_app_impl {
updater::builder(self.app_handle())
}
/// Gets a handle to the first system tray.
///
/// Prefer [`Self::tray_handle_by_id`] when multiple system trays are created.
///
/// # Examples
/// ```
/// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu};
///
/// tauri::Builder::default()
/// .setup(|app| {
/// let app_handle = app.handle();
/// SystemTray::new()
/// .with_menu(
/// SystemTrayMenu::new()
/// .add_item(CustomMenuItem::new("quit", "Quit"))
/// .add_item(CustomMenuItem::new("open", "Open"))
/// )
/// .on_event(move |event| {
/// let tray_handle = app_handle.tray_handle();
/// })
/// .build(app)?;
/// Ok(())
/// });
/// ```
#[cfg(all(desktop, feature = "system-tray"))]
#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
/// Gets a handle handle to the system tray.
pub fn tray_handle(&self) -> tray::SystemTrayHandle<R> {
self
.tray_handle
.clone()
.expect("tray not configured; use the `Builder#system_tray` API first.")
.manager()
.trays()
.values()
.next()
.cloned()
.expect("tray not configured; use the `Builder#system_tray`, `App#system_tray` or `AppHandle#system_tray` APIs first.")
}
/// Gets a handle to a system tray by its id.
///
/// ```
/// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu};
///
/// tauri::Builder::default()
/// .setup(|app| {
/// let app_handle = app.handle();
/// let tray_id = "my-tray";
/// SystemTray::new()
/// .with_id(tray_id)
/// .with_menu(
/// SystemTrayMenu::new()
/// .add_item(CustomMenuItem::new("quit", "Quit"))
/// .add_item(CustomMenuItem::new("open", "Open"))
/// )
/// .on_event(move |event| {
/// let tray_handle = app_handle.tray_handle_by_id(tray_id).unwrap();
/// })
/// .build(app)?;
/// Ok(())
/// });
/// ```
#[cfg(all(desktop, feature = "system-tray"))]
#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
pub fn tray_handle_by_id(&self, id: &str) -> Option<tray::SystemTrayHandle<R>> {
self
.manager()
.get_tray(id)
}
/// The path resolver for the application.
@@ -672,7 +717,7 @@ impl<R: Runtime> App<R> {
/// Sets the activation policy for the application. It is set to `NSApplicationActivationPolicyRegular` by default.
///
/// # Examples
/// ```rust,no_run
/// ```,no_run
/// let mut app = tauri::Builder::default()
/// // on an actual app, remove the string argument
/// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
@@ -695,7 +740,7 @@ impl<R: Runtime> App<R> {
///
/// # Examples
///
/// ```rust,no_run
/// ```
/// tauri::Builder::default()
/// .setup(|app| {
/// let matches = app.get_cli_matches()?;
@@ -714,7 +759,7 @@ impl<R: Runtime> App<R> {
/// Runs the application.
///
/// # Examples
/// ```rust,no_run
/// ```,no_run
/// let app = tauri::Builder::default()
/// // on an actual app, remove the string argument
/// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
@@ -752,7 +797,7 @@ impl<R: Runtime> App<R> {
/// Additionally, the cleanup calls [AppHandle#remove_system_tray](`AppHandle#method.remove_system_tray`) (Windows only).
///
/// # Examples
/// ```rust,no_run
/// ```no_run
/// let mut app = tauri::Builder::default()
/// // on an actual app, remove the string argument
/// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
@@ -831,7 +876,7 @@ impl<R: Runtime> App<R> {
/// Builds a Tauri application.
///
/// # Examples
/// ```rust,no_run
/// ```,no_run
/// tauri::Builder::default()
/// // on an actual app, remove the string argument
/// .run(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
@@ -941,7 +986,7 @@ impl<R: Runtime> Builder<R> {
/// Defines the JS message handler callback.
///
/// # Examples
/// ```rust,no_run
/// ```
/// #[tauri::command]
/// fn command_1() -> String {
/// return "hello world".to_string();
@@ -980,7 +1025,7 @@ impl<R: Runtime> Builder<R> {
/// Defines the setup hook.
///
/// # Examples
/// ```rust,no_run
/// ```
/// use tauri::Manager;
/// tauri::Builder::default()
/// .setup(|app| {
@@ -1076,7 +1121,7 @@ impl<R: Runtime> Builder<R> {
///
/// Since the managed state is global and must be [`Send`] + [`Sync`], mutations can only happen through interior mutability:
///
/// ```rust,no_run
/// ```,no_run
/// use std::{collections::HashMap, sync::Mutex};
/// use tauri::State;
/// // here we use Mutex to achieve interior mutability
@@ -1111,7 +1156,7 @@ impl<R: Runtime> Builder<R> {
///
/// # Examples
///
/// ```rust,no_run
/// ```,no_run
/// use tauri::State;
///
/// struct MyInt(isize);
@@ -1149,7 +1194,21 @@ impl<R: Runtime> Builder<R> {
self
}
/// Adds the icon configured on `tauri.conf.json` to the system tray with the specified menu items.
/// Sets the given system tray to be built before the app runs.
///
/// Prefer the [`SystemTray#method.build`] method to create the tray at runtime instead.
///
/// # Examples
/// ```
/// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu};
///
/// tauri::Builder::default()
/// .system_tray(SystemTray::new().with_menu(
/// SystemTrayMenu::new()
/// .add_item(CustomMenuItem::new("quit", "Quit"))
/// .add_item(CustomMenuItem::new("open", "Open"))
/// ));
/// ```
#[cfg(all(desktop, feature = "system-tray"))]
#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
#[must_use]
@@ -1161,7 +1220,7 @@ impl<R: Runtime> Builder<R> {
/// Sets the menu to use on all windows.
///
/// # Examples
/// ```rust,no_run
/// ```
/// use tauri::{MenuEntry, Submenu, MenuItem, Menu, CustomMenuItem};
///
/// tauri::Builder::default()
@@ -1185,7 +1244,7 @@ impl<R: Runtime> Builder<R> {
/// Registers a menu event handler for all windows.
///
/// # Examples
/// ```rust,no_run
/// ```
/// use tauri::{Menu, MenuEntry, Submenu, CustomMenuItem, api, Manager};
/// tauri::Builder::default()
/// .menu(Menu::with_items([
@@ -1225,7 +1284,7 @@ impl<R: Runtime> Builder<R> {
/// Registers a window event handler for all windows.
///
/// # Examples
/// ```rust,no_run
/// ```
/// tauri::Builder::default()
/// .on_window_event(|event| match event.event() {
/// tauri::WindowEvent::Focused(focused) => {
@@ -1248,13 +1307,15 @@ impl<R: Runtime> Builder<R> {
/// Registers a system tray event handler.
///
/// Prefer the [`SystemTray#method.on_event`] method when creating a tray at runtime instead.
///
/// # Examples
/// ```rust,no_run
/// use tauri::Manager;
/// ```
/// use tauri::{Manager, SystemTrayEvent};
/// tauri::Builder::default()
/// .on_system_tray_event(|app, event| match event {
/// // show window with id "main" when the tray is left clicked
/// tauri::SystemTrayEvent::LeftClick { .. } => {
/// SystemTrayEvent::LeftClick { .. } => {
/// let window = app.get_window("main").unwrap();
/// window.show().unwrap();
/// window.set_focus().unwrap();
@@ -1313,7 +1374,7 @@ impl<R: Runtime> Builder<R> {
///
/// - Use a macOS Universal binary target name:
///
/// ```no_run
/// ```
/// let mut builder = tauri::Builder::default();
/// #[cfg(target_os = "macos")]
/// {
@@ -1323,7 +1384,7 @@ impl<R: Runtime> Builder<R> {
///
/// - Append debug information to the target:
///
/// ```no_run
/// ```
/// let kind = if cfg!(debug_assertions) { "debug" } else { "release" };
/// tauri::Builder::default()
/// .updater_target(format!("{}-{}", tauri::updater::target().unwrap(), kind));
@@ -1331,7 +1392,7 @@ impl<R: Runtime> Builder<R> {
///
/// - Use the platform's target triple:
///
/// ```no_run
/// ```
/// tauri::Builder::default()
/// .updater_target(tauri::utils::platform::target_triple().unwrap());
/// ```
@@ -1349,18 +1410,6 @@ impl<R: Runtime> Builder<R> {
self.menu = Some(Menu::os_default(&context.package_info().name));
}
#[cfg(all(desktop, feature = "system-tray"))]
let system_tray_icon = context.system_tray_icon.clone();
#[cfg(all(feature = "system-tray", target_os = "macos"))]
let (system_tray_icon_as_template, system_tray_menu_on_left_click) = context
.config
.tauri
.system_tray
.as_ref()
.map(|t| (t.icon_as_template, t.menu_on_left_click))
.unwrap_or_default();
#[cfg(shell_scope)]
let shell_scope = context.shell_scope.clone();
@@ -1418,8 +1467,6 @@ impl<R: Runtime> Builder<R> {
global_shortcut_manager: global_shortcut_manager.clone(),
#[cfg(feature = "clipboard")]
clipboard_manager: clipboard_manager.clone(),
#[cfg(all(desktop, feature = "system-tray"))]
tray_handle: None,
handle: AppHandle {
runtime_handle,
manager,
@@ -1427,8 +1474,6 @@ impl<R: Runtime> Builder<R> {
global_shortcut_manager,
#[cfg(feature = "clipboard")]
clipboard_manager,
#[cfg(all(desktop, feature = "system-tray"))]
tray_handle: None,
#[cfg(updater)]
updater_settings: self.updater_settings,
},
@@ -1481,77 +1526,25 @@ impl<R: Runtime> Builder<R> {
}
#[cfg(all(desktop, feature = "system-tray"))]
if let Some(system_tray) = self.system_tray {
let mut ids = HashMap::new();
if let Some(menu) = system_tray.menu() {
tray::get_menu_ids(&mut ids, menu);
{
if let Some(tray) = self.system_tray {
tray.build(&app)?;
}
let tray_icon = if let Some(icon) = system_tray.icon {
Some(icon)
} else if let Some(tray_icon) = system_tray_icon {
Some(tray_icon.try_into()?)
} else {
None
};
let mut tray = tray::SystemTray::new()
.with_icon(tray_icon.expect("tray icon not found; please configure it on tauri.conf.json"));
if let Some(menu) = system_tray.menu {
tray = tray.with_menu(menu);
}
#[cfg(target_os = "macos")]
let tray = tray
.with_icon_as_template(system_tray_icon_as_template)
.with_menu_on_left_click(system_tray_menu_on_left_click);
let tray_handler = app
.runtime
.as_ref()
.unwrap()
.system_tray(tray.into())
.expect("failed to run tray");
let tray_handle = tray::SystemTrayHandle {
ids: Arc::new(std::sync::Mutex::new(ids)),
inner: tray_handler,
};
let ids = tray_handle.ids.clone();
app.tray_handle.replace(tray_handle.clone());
app.handle.tray_handle.replace(tray_handle);
for listener in self.system_tray_event_listeners {
let app_handle = app.handle();
let ids = ids.clone();
let listener = Arc::new(std::sync::Mutex::new(listener));
app
.runtime
.as_mut()
.unwrap()
.on_system_tray_event(move |event| {
let app_handle = app_handle.clone();
let event = match event {
RuntimeSystemTrayEvent::MenuItemClick(id) => tray::SystemTrayEvent::MenuItemClick {
id: ids.lock().unwrap().get(id).unwrap().clone(),
},
RuntimeSystemTrayEvent::LeftClick { position, size } => {
tray::SystemTrayEvent::LeftClick {
position: *position,
size: *size,
}
}
RuntimeSystemTrayEvent::RightClick { position, size } => {
tray::SystemTrayEvent::RightClick {
position: *position,
size: *size,
}
}
RuntimeSystemTrayEvent::DoubleClick { position, size } => {
tray::SystemTrayEvent::DoubleClick {
position: *position,
size: *size,
}
}
};
let listener = listener.clone();
listener.lock().unwrap()(&app_handle, event);
.on_system_tray_event(move |tray_id, event| {
if let Some((tray_id, tray)) = app_handle.manager().get_tray_by_runtime_id(tray_id) {
let app_handle = app_handle.clone();
let event = tray::SystemTrayEvent::from_runtime_event(event, tray_id, &tray.ids);
let listener = listener.clone();
listener.lock().unwrap()(&app_handle, event);
}
});
}
}

View File

@@ -8,18 +8,26 @@ pub use crate::{
MenuHash, MenuId, MenuIdRef, MenuUpdate, SystemTrayMenu, SystemTrayMenuEntry, TrayHandle,
},
window::dpi::{PhysicalPosition, PhysicalSize},
RuntimeHandle, SystemTrayEvent as RuntimeSystemTrayEvent,
},
Icon, Runtime,
};
use crate::{sealed::RuntimeOrDispatch, Manager};
use rand::distributions::{Alphanumeric, DistString};
use tauri_macros::default_runtime;
use tauri_runtime::TrayId;
use tauri_utils::debug_eprintln;
use std::{
collections::HashMap,
collections::{hash_map::DefaultHasher, HashMap},
fmt,
hash::{Hash, Hasher},
sync::{Arc, Mutex},
};
type TrayEventHandler = dyn Fn(SystemTrayEvent) + Send + Sync + 'static;
pub(crate) fn get_menu_ids(map: &mut HashMap<MenuHash, MenuId>, menu: &SystemTrayMenu) {
for item in &menu.items {
match item {
@@ -33,9 +41,11 @@ pub(crate) fn get_menu_ids(map: &mut HashMap<MenuHash, MenuId>, menu: &SystemTra
}
/// Represents a System Tray instance.
#[derive(Debug, Default)]
#[derive(Clone)]
#[non_exhaustive]
pub struct SystemTray {
/// The tray identifier. Defaults to a random string.
pub id: String,
/// The tray icon.
pub icon: Option<tauri_runtime::Icon>,
/// The tray menu.
@@ -46,10 +56,62 @@ pub struct SystemTray {
/// Whether the menu should appear when the tray receives a left click. Defaults to `true`
#[cfg(target_os = "macos")]
pub menu_on_left_click: bool,
on_event: Option<Arc<TrayEventHandler>>,
// TODO: icon_as_template and menu_on_left_click should be an Option instead :(
#[cfg(target_os = "macos")]
menu_on_left_click_set: bool,
#[cfg(target_os = "macos")]
icon_as_template_set: bool,
}
impl fmt::Debug for SystemTray {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("SystemTray");
d.field("id", &self.id)
.field("icon", &self.icon)
.field("menu", &self.menu);
#[cfg(target_os = "macos")]
{
d.field("icon_as_template", &self.icon_as_template)
.field("menu_on_left_click", &self.menu_on_left_click);
}
d.finish()
}
}
impl Default for SystemTray {
fn default() -> Self {
Self {
id: Alphanumeric.sample_string(&mut rand::thread_rng(), 16),
icon: None,
menu: None,
on_event: None,
#[cfg(target_os = "macos")]
icon_as_template: false,
#[cfg(target_os = "macos")]
menu_on_left_click: false,
#[cfg(target_os = "macos")]
icon_as_template_set: false,
#[cfg(target_os = "macos")]
menu_on_left_click_set: false,
}
}
}
impl SystemTray {
/// Creates a new system tray that only renders an icon.
///
/// # Examples
///
/// ```
/// use tauri::SystemTray;
///
/// tauri::Builder::default()
/// .setup(|app| {
/// let tray_handle = SystemTray::new().build(app)?;
/// Ok(())
/// });
/// ```
pub fn new() -> Self {
Default::default()
}
@@ -58,7 +120,43 @@ impl SystemTray {
self.menu.as_ref()
}
/// Sets the tray icon.
/// Sets the tray identifier, used to retrieve its handle and to identify a tray event source.
///
/// # Examples
///
/// ```
/// use tauri::SystemTray;
///
/// tauri::Builder::default()
/// .setup(|app| {
/// let tray_handle = SystemTray::new()
/// .with_id("tray-id")
/// .build(app)?;
/// Ok(())
/// });
/// ```
#[must_use]
pub fn with_id<I: Into<String>>(mut self, id: I) -> Self {
self.id = id.into();
self
}
/// Sets the tray [`Icon`].
///
/// # Examples
///
/// ```
/// use tauri::{Icon, SystemTray};
///
/// tauri::Builder::default()
/// .setup(|app| {
/// let tray_handle = SystemTray::new()
/// // dummy and invalid Rgba icon; see the Icon documentation for more information
/// .with_icon(Icon::Rgba { rgba: Vec::new(), width: 0, height: 0 })
/// .build(app)?;
/// Ok(())
/// });
/// ```
#[must_use]
pub fn with_icon<I: TryInto<tauri_runtime::Icon>>(mut self, icon: I) -> Self
where
@@ -79,50 +177,230 @@ impl SystemTray {
///
/// Images you mark as template images should consist of only black and clear colors.
/// You can use the alpha channel in the image to adjust the opacity of black content.
///
/// # Examples
///
/// ```
/// use tauri::SystemTray;
///
/// tauri::Builder::default()
/// .setup(|app| {
/// let mut tray_builder = SystemTray::new();
/// #[cfg(target_os = "macos")]
/// {
/// tray_builder = tray_builder.with_icon_as_template(true);
/// }
/// let tray_handle = tray_builder.build(app)?;
/// Ok(())
/// });
/// ```
#[cfg(target_os = "macos")]
#[must_use]
pub fn with_icon_as_template(mut self, is_template: bool) -> Self {
self.icon_as_template_set = true;
self.icon_as_template = is_template;
self
}
/// Sets whether the menu should appear when the tray receives a left click. Defaults to `true`.
///
/// # Examples
///
/// ```
/// use tauri::SystemTray;
///
/// tauri::Builder::default()
/// .setup(|app| {
/// let mut tray_builder = SystemTray::new();
/// #[cfg(target_os = "macos")]
/// {
/// tray_builder = tray_builder.with_menu_on_left_click(false);
/// }
/// let tray_handle = tray_builder.build(app)?;
/// Ok(())
/// });
/// ```
#[cfg(target_os = "macos")]
#[must_use]
pub fn with_menu_on_left_click(mut self, menu_on_left_click: bool) -> Self {
self.menu_on_left_click_set = true;
self.menu_on_left_click = menu_on_left_click;
self
}
/// Sets the event listener for this system tray.
///
/// # Examples
///
/// ```
/// use tauri::{Icon, Manager, SystemTray, SystemTrayEvent};
///
/// tauri::Builder::default()
/// .setup(|app| {
/// let handle = app.handle();
/// let id = "tray-id";
/// SystemTray::new()
/// .with_id(id)
/// .on_event(move |event| {
/// let tray_handle = handle.tray_handle_by_id(id).unwrap();
/// match event {
/// // show window with id "main" when the tray is left clicked
/// SystemTrayEvent::LeftClick { .. } => {
/// let window = handle.get_window("main").unwrap();
/// window.show().unwrap();
/// window.set_focus().unwrap();
/// }
/// _ => {}
/// }
/// })
/// .build(app)?;
/// Ok(())
/// });
/// ```
#[must_use]
pub fn on_event<F: Fn(SystemTrayEvent) + Send + Sync + 'static>(mut self, f: F) -> Self {
self.on_event.replace(Arc::new(f));
self
}
/// Sets the menu to show when the system tray is right clicked.
///
/// # Examples
///
/// ```
/// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu};
///
/// tauri::Builder::default()
/// .setup(|app| {
/// let tray_handle = SystemTray::new()
/// .with_menu(
/// SystemTrayMenu::new()
/// .add_item(CustomMenuItem::new("quit", "Quit"))
/// .add_item(CustomMenuItem::new("open", "Open"))
/// )
/// .build(app)?;
/// Ok(())
/// });
/// ```
#[must_use]
pub fn with_menu(mut self, menu: SystemTrayMenu) -> Self {
self.menu.replace(menu);
self
}
}
impl From<SystemTray> for tauri_runtime::SystemTray {
fn from(tray: SystemTray) -> Self {
let mut t = tauri_runtime::SystemTray::new();
if let Some(i) = tray.icon {
t = t.with_icon(i);
/// Builds and shows the system tray.
///
/// # Examples
///
/// ```
/// use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu};
///
/// tauri::Builder::default()
/// .setup(|app| {
/// let tray_handle = SystemTray::new()
/// .with_menu(
/// SystemTrayMenu::new()
/// .add_item(CustomMenuItem::new("quit", "Quit"))
/// .add_item(CustomMenuItem::new("open", "Open"))
/// )
/// .build(app)?;
///
/// tray_handle.get_item("quit").set_enabled(false);
/// Ok(())
/// });
/// ```
pub fn build<R: Runtime, M: Manager<R>>(
mut self,
manager: &M,
) -> crate::Result<SystemTrayHandle<R>> {
let mut ids = HashMap::new();
if let Some(menu) = self.menu() {
get_menu_ids(&mut ids, menu);
}
let ids = Arc::new(Mutex::new(ids));
if self.icon.is_none() {
if let Some(tray_icon) = &manager.manager().inner.tray_icon {
self = self.with_icon(tray_icon.clone());
}
}
#[cfg(target_os = "macos")]
{
if !self.icon_as_template_set {
self.icon_as_template = manager
.config()
.tauri
.system_tray
.as_ref()
.map_or(false, |t| t.icon_as_template);
}
if !self.menu_on_left_click_set {
self.menu_on_left_click = manager
.config()
.tauri
.system_tray
.as_ref()
.map_or(false, |t| t.menu_on_left_click);
}
}
if let Some(menu) = tray.menu {
t = t.with_menu(menu);
let tray_id = self.id.clone();
let mut runtime_tray = tauri_runtime::SystemTray::new();
runtime_tray = runtime_tray.with_id(hash(&self.id));
if let Some(i) = self.icon {
runtime_tray = runtime_tray.with_icon(i);
}
if let Some(menu) = self.menu {
runtime_tray = runtime_tray.with_menu(menu);
}
if let Some(on_event) = self.on_event {
let ids_ = ids.clone();
let tray_id_ = tray_id.clone();
runtime_tray = runtime_tray.on_event(move |event| {
on_event(SystemTrayEvent::from_runtime_event(
event,
tray_id_.clone(),
&ids_,
))
});
}
#[cfg(target_os = "macos")]
{
t = t.with_icon_as_template(tray.icon_as_template);
t = t.with_menu_on_left_click(tray.menu_on_left_click);
runtime_tray = runtime_tray.with_icon_as_template(self.icon_as_template);
runtime_tray = runtime_tray.with_menu_on_left_click(self.menu_on_left_click);
}
t
let id = runtime_tray.id;
let tray_handler = match manager.runtime() {
RuntimeOrDispatch::Runtime(r) => r.system_tray(runtime_tray),
RuntimeOrDispatch::RuntimeHandle(h) => h.system_tray(runtime_tray),
RuntimeOrDispatch::Dispatch(_) => manager
.app_handle()
.runtime_handle
.system_tray(runtime_tray),
}?;
let tray_handle = SystemTrayHandle {
id,
ids,
inner: tray_handler,
};
manager.manager().attach_tray(tray_id, tray_handle.clone());
Ok(tray_handle)
}
}
fn hash(id: &str) -> MenuHash {
let mut hasher = DefaultHasher::new();
id.hash(&mut hasher);
hasher.finish() as MenuHash
}
/// System tray event.
#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
#[non_exhaustive]
@@ -130,6 +408,8 @@ pub enum SystemTrayEvent {
/// Tray context menu item was clicked.
#[non_exhaustive]
MenuItemClick {
/// The tray id.
tray_id: String,
/// The id of the menu item.
id: MenuId,
},
@@ -140,6 +420,8 @@ pub enum SystemTrayEvent {
/// - **Linux:** Unsupported
#[non_exhaustive]
LeftClick {
/// The tray id.
tray_id: String,
/// The position of the tray icon.
position: PhysicalPosition<f64>,
/// The size of the tray icon.
@@ -153,6 +435,8 @@ pub enum SystemTrayEvent {
/// - **macOS:** `Ctrl` + `Left click` fire this event.
#[non_exhaustive]
RightClick {
/// The tray id.
tray_id: String,
/// The position of the tray icon.
position: PhysicalPosition<f64>,
/// The size of the tray icon.
@@ -166,6 +450,8 @@ pub enum SystemTrayEvent {
///
#[non_exhaustive]
DoubleClick {
/// The tray id.
tray_id: String,
/// The position of the tray icon.
position: PhysicalPosition<f64>,
/// The size of the tray icon.
@@ -173,10 +459,41 @@ pub enum SystemTrayEvent {
},
}
impl SystemTrayEvent {
pub(crate) fn from_runtime_event(
event: &RuntimeSystemTrayEvent,
tray_id: String,
menu_ids: &Arc<Mutex<HashMap<u16, String>>>,
) -> Self {
match event {
RuntimeSystemTrayEvent::MenuItemClick(id) => Self::MenuItemClick {
tray_id,
id: menu_ids.lock().unwrap().get(id).unwrap().clone(),
},
RuntimeSystemTrayEvent::LeftClick { position, size } => Self::LeftClick {
tray_id,
position: *position,
size: *size,
},
RuntimeSystemTrayEvent::RightClick { position, size } => Self::RightClick {
tray_id,
position: *position,
size: *size,
},
RuntimeSystemTrayEvent::DoubleClick { position, size } => Self::DoubleClick {
tray_id,
position: *position,
size: *size,
},
}
}
}
/// A handle to a system tray. Allows updating the context menu items.
#[default_runtime(crate::Wry, wry)]
#[derive(Debug)]
pub struct SystemTrayHandle<R: Runtime> {
pub(crate) id: TrayId,
pub(crate) ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
pub(crate) inner: R::TrayHandler,
}
@@ -184,6 +501,7 @@ pub struct SystemTrayHandle<R: Runtime> {
impl<R: Runtime> Clone for SystemTrayHandle<R> {
fn clone(&self) -> Self {
Self {
id: self.id,
ids: self.ids.clone(),
inner: self.inner.clone(),
}
@@ -245,6 +563,11 @@ impl<R: Runtime> SystemTrayHandle<R> {
.set_icon_as_template(is_template)
.map_err(Into::into)
}
/// Destroys this system tray.
pub fn destroy(&self) -> crate::Result<()> {
self.inner.destroy().map_err(Into::into)
}
}
impl<R: Runtime> SystemTrayMenuItemHandle<R> {

View File

@@ -196,6 +196,8 @@ fn replace_csp_nonce(
#[default_runtime(crate::Wry, wry)]
pub struct InnerWindowManager<R: Runtime> {
windows: Mutex<HashMap<String, Window<R>>>,
#[cfg(all(desktop, feature = "system-tray"))]
pub(crate) trays: Mutex<HashMap<String, crate::SystemTrayHandle<R>>>,
pub(crate) plugins: Mutex<PluginStore<R>>,
listeners: Listeners,
pub(crate) state: Arc<StateManager>,
@@ -210,6 +212,7 @@ pub struct InnerWindowManager<R: Runtime> {
assets: Arc<dyn Assets>,
pub(crate) default_window_icon: Option<Icon>,
pub(crate) app_icon: Option<Vec<u8>>,
pub(crate) tray_icon: Option<Icon>,
package_info: PackageInfo,
/// The webview protocols protocols available to all windows.
@@ -236,6 +239,7 @@ impl<R: Runtime> fmt::Debug for InnerWindowManager<R> {
.field("config", &self.config)
.field("default_window_icon", &self.default_window_icon)
.field("app_icon", &self.app_icon)
.field("tray_icon", &self.tray_icon)
.field("package_info", &self.package_info)
.field("menu", &self.menu)
.field("pattern", &self.pattern)
@@ -300,6 +304,8 @@ impl<R: Runtime> WindowManager<R> {
Self {
inner: Arc::new(InnerWindowManager {
windows: Mutex::default(),
#[cfg(all(desktop, feature = "system-tray"))]
trays: Default::default(),
plugins: Mutex::new(plugins),
listeners: Listeners::default(),
state: Arc::new(state),
@@ -309,6 +315,7 @@ impl<R: Runtime> WindowManager<R> {
assets: context.assets,
default_window_icon: context.default_window_icon,
app_icon: context.app_icon,
tray_icon: context.system_tray_icon,
package_info: context.package_info,
pattern: context.pattern,
uri_scheme_protocols,
@@ -1289,6 +1296,33 @@ impl<R: Runtime> WindowManager<R> {
}
}
/// Tray APIs
#[cfg(all(desktop, feature = "system-tray"))]
impl<R: Runtime> WindowManager<R> {
pub fn get_tray(&self, id: &str) -> Option<crate::SystemTrayHandle<R>> {
self.inner.trays.lock().unwrap().get(id).cloned()
}
pub fn trays(&self) -> HashMap<String, crate::SystemTrayHandle<R>> {
self.inner.trays.lock().unwrap().clone()
}
pub fn attach_tray(&self, id: String, tray: crate::SystemTrayHandle<R>) {
self.inner.trays.lock().unwrap().insert(id, tray);
}
pub fn get_tray_by_runtime_id(&self, id: u16) -> Option<(String, crate::SystemTrayHandle<R>)> {
let trays = self.inner.trays.lock().unwrap();
let iter = trays.iter();
for (tray_id, tray) in iter {
if tray.id == id {
return Some((tray_id.clone(), tray.clone()));
}
}
None
}
}
fn on_window_event<R: Runtime>(
window: &Window<R>,
manager: &WindowManager<R>,

View File

@@ -18,7 +18,7 @@ use tauri_runtime::{
#[cfg(all(desktop, feature = "system-tray"))]
use tauri_runtime::{
menu::{SystemTrayMenu, TrayHandle},
SystemTray, SystemTrayEvent,
SystemTray, SystemTrayEvent, TrayId,
};
use tauri_utils::{config::WindowConfig, Theme};
use uuid::Uuid;
@@ -80,10 +80,13 @@ impl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {
unimplemented!()
}
#[cfg(all(windows, feature = "system-tray"))]
#[cfg_attr(doc_cfg, doc(cfg(all(windows, feature = "system-tray"))))]
fn remove_system_tray(&self) -> Result<()> {
Ok(())
#[cfg(all(desktop, feature = "system-tray"))]
#[cfg_attr(doc_cfg, doc(cfg(all(desktop, feature = "system-tray"))))]
fn system_tray(
&self,
system_tray: SystemTray,
) -> Result<<Self::Runtime as Runtime<T>>::TrayHandler> {
unimplemented!()
}
fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle {
@@ -531,6 +534,10 @@ impl TrayHandle for MockTrayHandler {
fn set_icon_as_template(&self, is_template: bool) -> Result<()> {
Ok(())
}
fn destroy(&self) -> Result<()> {
Ok(())
}
}
#[derive(Debug, Clone)]
@@ -636,9 +643,7 @@ impl<T: UserEvent> Runtime<T> for MockRuntime {
#[cfg(all(desktop, feature = "system-tray"))]
#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
fn on_system_tray_event<F: Fn(&SystemTrayEvent) + Send + 'static>(&mut self, f: F) -> Uuid {
Uuid::new_v4()
}
fn on_system_tray_event<F: Fn(TrayId, &SystemTrayEvent) + Send + 'static>(&mut self, f: F) {}
#[cfg(target_os = "macos")]
#[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]

View File

@@ -3037,9 +3037,9 @@ dependencies = [
[[package]]
name = "tao"
version = "0.13.1"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6901eece433f5ce79a60c2660e204d8b2ff4f76668db9aedb8ae6b2c5a57ae43"
checksum = "8ad691ca9fca6c2c76c09ffcddf6ae6593fba65d95477cf31780910ed272f5b8"
dependencies = [
"bitflags",
"cairo-rs",
@@ -3217,6 +3217,7 @@ dependencies = [
"http",
"http-range",
"infer 0.7.0",
"rand 0.8.5",
"raw-window-handle",
"serde",
"serde_json",

View File

@@ -31,7 +31,10 @@ struct Reply {
fn main() {
#[allow(unused_mut)]
let mut builder = tauri::Builder::default()
.setup(|app| {
.setup(move |app| {
#[cfg(desktop)]
create_tray(app)?;
#[allow(unused_mut)]
let mut window_builder = WindowBuilder::new(app, "main", WindowUrl::default())
.title("Tauri API Validation")
@@ -100,99 +103,6 @@ fn main() {
builder = builder.menu(tauri::Menu::os_default("Tauri API Validation"));
}
#[cfg(desktop)]
{
let tray_menu1 = SystemTrayMenu::new()
.add_item(CustomMenuItem::new("toggle", "Toggle"))
.add_item(CustomMenuItem::new("new", "New window"))
.add_item(CustomMenuItem::new("icon_1", "Tray Icon 1"))
.add_item(CustomMenuItem::new("icon_2", "Tray Icon 2"))
.add_item(CustomMenuItem::new("switch_menu", "Switch Menu"))
.add_item(CustomMenuItem::new("exit_app", "Quit"));
let tray_menu2 = SystemTrayMenu::new()
.add_item(CustomMenuItem::new("toggle", "Toggle"))
.add_item(CustomMenuItem::new("new", "New window"))
.add_item(CustomMenuItem::new("switch_menu", "Switch Menu"))
.add_item(CustomMenuItem::new("exit_app", "Quit"));
let is_menu1 = AtomicBool::new(true);
builder = builder
.system_tray(SystemTray::new().with_menu(tray_menu1.clone()))
.on_system_tray_event(move |app, event| match event {
SystemTrayEvent::LeftClick {
position: _,
size: _,
..
} => {
let window = app.get_window("main").unwrap();
window.show().unwrap();
window.set_focus().unwrap();
}
SystemTrayEvent::MenuItemClick { id, .. } => {
let item_handle = app.tray_handle().get_item(&id);
match id.as_str() {
"exit_app" => {
// exit the app
app.exit(0);
}
"toggle" => {
let window = app.get_window("main").unwrap();
let new_title = if window.is_visible().unwrap() {
window.hide().unwrap();
"Show"
} else {
window.show().unwrap();
"Hide"
};
item_handle.set_title(new_title).unwrap();
}
"new" => {
WindowBuilder::new(app, "new", WindowUrl::App("index.html".into()))
.title("Tauri")
.build()
.unwrap();
}
"icon_1" => {
#[cfg(target_os = "macos")]
app.tray_handle().set_icon_as_template(true).unwrap();
app
.tray_handle()
.set_icon(tauri::Icon::Raw(
include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec(),
))
.unwrap();
}
"icon_2" => {
#[cfg(target_os = "macos")]
app.tray_handle().set_icon_as_template(true).unwrap();
app
.tray_handle()
.set_icon(tauri::Icon::Raw(
include_bytes!("../../../.icons/icon.ico").to_vec(),
))
.unwrap();
}
"switch_menu" => {
let flag = is_menu1.load(Ordering::Relaxed);
app
.tray_handle()
.set_menu(if flag {
tray_menu2.clone()
} else {
tray_menu1.clone()
})
.unwrap();
is_menu1.store(!flag, Ordering::Relaxed);
}
_ => {}
}
}
_ => {}
});
}
#[allow(unused_mut)]
let mut app = builder
.invoke_handler(tauri::generate_handler![
@@ -259,3 +169,106 @@ fn main() {
_ => {}
})
}
#[cfg(desktop)]
fn create_tray(app: &tauri::App) -> tauri::Result<()> {
let tray_menu1 = SystemTrayMenu::new()
.add_item(CustomMenuItem::new("toggle", "Toggle"))
.add_item(CustomMenuItem::new("new", "New window"))
.add_item(CustomMenuItem::new("icon_1", "Tray Icon 1"))
.add_item(CustomMenuItem::new("icon_2", "Tray Icon 2"))
.add_item(CustomMenuItem::new("switch_menu", "Switch Menu"))
.add_item(CustomMenuItem::new("exit_app", "Quit"))
.add_item(CustomMenuItem::new("destroy", "Destroy"));
let tray_menu2 = SystemTrayMenu::new()
.add_item(CustomMenuItem::new("toggle", "Toggle"))
.add_item(CustomMenuItem::new("new", "New window"))
.add_item(CustomMenuItem::new("switch_menu", "Switch Menu"))
.add_item(CustomMenuItem::new("exit_app", "Quit"))
.add_item(CustomMenuItem::new("destroy", "Destroy"));
let is_menu1 = AtomicBool::new(true);
let handle = app.handle();
let tray_id = "my-tray".to_string();
SystemTray::new()
.with_id(&tray_id)
.with_menu(tray_menu1.clone())
.on_event(move |event| {
let tray_handle = handle.tray_handle_by_id(&tray_id).unwrap();
match event {
SystemTrayEvent::LeftClick {
position: _,
size: _,
..
} => {
let window = handle.get_window("main").unwrap();
window.show().unwrap();
window.set_focus().unwrap();
}
SystemTrayEvent::MenuItemClick { id, .. } => {
let item_handle = tray_handle.get_item(&id);
match id.as_str() {
"exit_app" => {
// exit the app
handle.exit(0);
}
"destroy" => {
tray_handle.destroy().unwrap();
}
"toggle" => {
let window = handle.get_window("main").unwrap();
let new_title = if window.is_visible().unwrap() {
window.hide().unwrap();
"Show"
} else {
window.show().unwrap();
"Hide"
};
item_handle.set_title(new_title).unwrap();
}
"new" => {
WindowBuilder::new(&handle, "new", WindowUrl::App("index.html".into()))
.title("Tauri")
.build()
.unwrap();
}
"icon_1" => {
#[cfg(target_os = "macos")]
tray_handle.set_icon_as_template(true).unwrap();
tray_handle
.set_icon(tauri::Icon::Raw(
include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec(),
))
.unwrap();
}
"icon_2" => {
#[cfg(target_os = "macos")]
tray_handle.set_icon_as_template(true).unwrap();
tray_handle
.set_icon(tauri::Icon::Raw(
include_bytes!("../../../.icons/icon.ico").to_vec(),
))
.unwrap();
}
"switch_menu" => {
let flag = is_menu1.load(Ordering::Relaxed);
tray_handle
.set_menu(if flag {
tray_menu2.clone()
} else {
tray_menu1.clone()
})
.unwrap();
is_menu1.store(!flag, Ordering::Relaxed);
}
_ => {}
}
}
_ => {}
}
})
.build(app)
.map(|_| ())
}