system_tray.rs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. // Copyright 2019-2023 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. pub use tauri_runtime::{
  5. menu::{
  6. Menu, MenuEntry, MenuItem, MenuUpdate, Submenu, SystemTrayMenu, SystemTrayMenuEntry,
  7. SystemTrayMenuItem, TrayHandle,
  8. },
  9. Icon, SystemTrayEvent,
  10. };
  11. use wry::application::event_loop::EventLoopWindowTarget;
  12. pub use wry::application::{
  13. event::TrayEvent,
  14. event_loop::EventLoopProxy,
  15. menu::{
  16. ContextMenu as WryContextMenu, CustomMenuItem as WryCustomMenuItem, MenuItem as WryMenuItem,
  17. },
  18. system_tray::Icon as WryTrayIcon,
  19. TrayId as WryTrayId,
  20. };
  21. #[cfg(target_os = "macos")]
  22. pub use wry::application::platform::macos::{
  23. CustomMenuItemExtMacOS, SystemTrayBuilderExtMacOS, SystemTrayExtMacOS,
  24. };
  25. use wry::application::system_tray::{SystemTray as WrySystemTray, SystemTrayBuilder};
  26. use crate::{send_user_message, Context, Error, Message, Result, TrayId, TrayMessage};
  27. use tauri_runtime::{menu::MenuHash, SystemTray, UserEvent};
  28. use std::{
  29. collections::HashMap,
  30. fmt,
  31. sync::{Arc, Mutex},
  32. };
  33. pub type GlobalSystemTrayEventHandler = Box<dyn Fn(TrayId, &SystemTrayEvent) + Send>;
  34. pub type GlobalSystemTrayEventListeners = Arc<Mutex<Vec<Arc<GlobalSystemTrayEventHandler>>>>;
  35. pub type SystemTrayEventHandler = Box<dyn Fn(&SystemTrayEvent) + Send>;
  36. pub type SystemTrayEventListeners = Arc<Mutex<Vec<Arc<SystemTrayEventHandler>>>>;
  37. pub type SystemTrayItems = Arc<Mutex<HashMap<u16, WryCustomMenuItem>>>;
  38. #[derive(Clone, Default)]
  39. pub struct TrayContext {
  40. pub tray: Arc<Mutex<Option<WrySystemTray>>>,
  41. pub listeners: SystemTrayEventListeners,
  42. pub items: SystemTrayItems,
  43. }
  44. impl fmt::Debug for TrayContext {
  45. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  46. f.debug_struct("TrayContext")
  47. .field("items", &self.items)
  48. .finish()
  49. }
  50. }
  51. #[derive(Clone, Default)]
  52. pub struct SystemTrayManager {
  53. pub trays: Arc<Mutex<HashMap<TrayId, TrayContext>>>,
  54. pub global_listeners: GlobalSystemTrayEventListeners,
  55. }
  56. impl fmt::Debug for SystemTrayManager {
  57. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  58. f.debug_struct("SystemTrayManager")
  59. .field("trays", &self.trays)
  60. .finish()
  61. }
  62. }
  63. /// Wrapper around a [`wry::application::system_tray::Icon`] that can be created from an [`WindowIcon`].
  64. pub struct TrayIcon(pub(crate) WryTrayIcon);
  65. impl TryFrom<Icon> for TrayIcon {
  66. type Error = Error;
  67. fn try_from(icon: Icon) -> std::result::Result<Self, Self::Error> {
  68. WryTrayIcon::from_rgba(icon.rgba, icon.width, icon.height)
  69. .map(Self)
  70. .map_err(crate::icon_err)
  71. }
  72. }
  73. pub fn create_tray<T>(
  74. id: WryTrayId,
  75. system_tray: SystemTray,
  76. event_loop: &EventLoopWindowTarget<T>,
  77. ) -> crate::Result<(WrySystemTray, HashMap<u16, WryCustomMenuItem>)> {
  78. let icon = TrayIcon::try_from(system_tray.icon.expect("tray icon not set"))?;
  79. let mut items = HashMap::new();
  80. #[allow(unused_mut)]
  81. let mut builder = SystemTrayBuilder::new(
  82. icon.0,
  83. system_tray
  84. .menu
  85. .map(|menu| to_wry_context_menu(&mut items, menu)),
  86. )
  87. .with_id(id);
  88. #[cfg(target_os = "macos")]
  89. {
  90. builder = builder
  91. .with_icon_as_template(system_tray.icon_as_template)
  92. .with_menu_on_left_click(system_tray.menu_on_left_click);
  93. if let Some(title) = system_tray.title {
  94. builder = builder.with_title(&title);
  95. }
  96. }
  97. if let Some(tooltip) = system_tray.tooltip {
  98. builder = builder.with_tooltip(&tooltip);
  99. }
  100. let tray = builder
  101. .build(event_loop)
  102. .map_err(|e| Error::SystemTray(Box::new(e)))?;
  103. Ok((tray, items))
  104. }
  105. #[derive(Debug, Clone)]
  106. pub struct SystemTrayHandle<T: UserEvent> {
  107. pub(crate) context: Context<T>,
  108. pub(crate) id: TrayId,
  109. pub(crate) proxy: EventLoopProxy<super::Message<T>>,
  110. }
  111. impl<T: UserEvent> TrayHandle for SystemTrayHandle<T> {
  112. fn set_icon(&self, icon: Icon) -> Result<()> {
  113. self
  114. .proxy
  115. .send_event(Message::Tray(self.id, TrayMessage::UpdateIcon(icon)))
  116. .map_err(|_| Error::FailedToSendMessage)
  117. }
  118. fn set_menu(&self, menu: SystemTrayMenu) -> Result<()> {
  119. self
  120. .proxy
  121. .send_event(Message::Tray(self.id, TrayMessage::UpdateMenu(menu)))
  122. .map_err(|_| Error::FailedToSendMessage)
  123. }
  124. fn update_item(&self, id: u16, update: MenuUpdate) -> Result<()> {
  125. self
  126. .proxy
  127. .send_event(Message::Tray(self.id, TrayMessage::UpdateItem(id, update)))
  128. .map_err(|_| Error::FailedToSendMessage)
  129. }
  130. #[cfg(target_os = "macos")]
  131. fn set_icon_as_template(&self, is_template: bool) -> tauri_runtime::Result<()> {
  132. self
  133. .proxy
  134. .send_event(Message::Tray(
  135. self.id,
  136. TrayMessage::UpdateIconAsTemplate(is_template),
  137. ))
  138. .map_err(|_| Error::FailedToSendMessage)
  139. }
  140. #[cfg(target_os = "macos")]
  141. fn set_title(&self, title: &str) -> tauri_runtime::Result<()> {
  142. self
  143. .proxy
  144. .send_event(Message::Tray(
  145. self.id,
  146. TrayMessage::UpdateTitle(title.to_owned()),
  147. ))
  148. .map_err(|_| Error::FailedToSendMessage)
  149. }
  150. fn set_tooltip(&self, tooltip: &str) -> Result<()> {
  151. self
  152. .proxy
  153. .send_event(Message::Tray(
  154. self.id,
  155. TrayMessage::UpdateTooltip(tooltip.to_owned()),
  156. ))
  157. .map_err(|_| Error::FailedToSendMessage)
  158. }
  159. fn destroy(&self) -> Result<()> {
  160. let (tx, rx) = std::sync::mpsc::channel();
  161. send_user_message(
  162. &self.context,
  163. Message::Tray(self.id, TrayMessage::Destroy(tx)),
  164. )?;
  165. rx.recv().unwrap()?;
  166. Ok(())
  167. }
  168. }
  169. impl From<SystemTrayMenuItem> for crate::MenuItemWrapper {
  170. fn from(item: SystemTrayMenuItem) -> Self {
  171. match item {
  172. SystemTrayMenuItem::Separator => Self(WryMenuItem::Separator),
  173. _ => unimplemented!(),
  174. }
  175. }
  176. }
  177. pub fn to_wry_context_menu(
  178. custom_menu_items: &mut HashMap<MenuHash, WryCustomMenuItem>,
  179. menu: SystemTrayMenu,
  180. ) -> WryContextMenu {
  181. let mut tray_menu = WryContextMenu::new();
  182. for item in menu.items {
  183. match item {
  184. SystemTrayMenuEntry::CustomItem(c) => {
  185. #[allow(unused_mut)]
  186. let mut item = tray_menu.add_item(crate::MenuItemAttributesWrapper::from(&c).0);
  187. #[cfg(target_os = "macos")]
  188. if let Some(native_image) = c.native_image {
  189. item.set_native_image(crate::NativeImageWrapper::from(native_image).0);
  190. }
  191. custom_menu_items.insert(c.id, item);
  192. }
  193. SystemTrayMenuEntry::NativeItem(i) => {
  194. tray_menu.add_native_item(crate::MenuItemWrapper::from(i).0);
  195. }
  196. SystemTrayMenuEntry::Submenu(submenu) => {
  197. tray_menu.add_submenu(
  198. &submenu.title,
  199. submenu.enabled,
  200. to_wry_context_menu(custom_menu_items, submenu.inner),
  201. );
  202. }
  203. }
  204. }
  205. tray_menu
  206. }