tray_icon/lib.rs
1// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5#![allow(clippy::uninlined_format_args)]
6
7//! tray-icon lets you create tray icons for desktop applications.
8//!
9//! # Platforms supported:
10//!
11//! - Windows
12//! - macOS
13//! - Linux (gtk Only)
14//!
15//! # Platform-specific notes:
16//!
17//! - On Windows and Linux, an event loop must be running on the thread, on Windows, a win32 event loop and on Linux, a gtk event loop. It doesn't need to be the main thread but you have to create the tray icon on the same thread as the event loop.
18//! - On macOS, an event loop must be running on the main thread so you also need to create the tray icon on the main thread. You must make sure that the event loop is already running and not just created before creating a TrayIcon to prevent issues with fullscreen apps. In Winit for example the earliest you can create icons is on [`StartCause::Init`](https://docs.rs/winit/latest/winit/event/enum.StartCause.html#variant.Init).
19//!
20//! # Dependencies (Linux Only)
21//!
22//! On Linux, `gtk`, `libxdo` is used to make the predfined `Copy`, `Cut`, `Paste` and `SelectAll` menu items work and `libappindicator` or `libayatnat-appindicator` are used to create the tray icon, so make sure to install them on your system.
23//!
24//! #### Arch Linux / Manjaro:
25//!
26//! ```sh
27//! pacman -S gtk3 xdotool libappindicator-gtk3 #or libayatana-appindicator
28//! ```
29//!
30//! #### Debian / Ubuntu:
31//!
32//! ```sh
33//! sudo apt install libgtk-3-dev libxdo-dev libappindicator3-dev #or libayatana-appindicator3-dev
34//! ```
35//!
36//! # Examples
37//!
38//! #### Create a tray icon without a menu.
39//!
40//! ```no_run
41//! use tray_icon::{TrayIconBuilder, Icon};
42//!
43//! # let icon = Icon::from_rgba(Vec::new(), 0, 0).unwrap();
44//! let tray_icon = TrayIconBuilder::new()
45//! .with_tooltip("system-tray - tray icon library!")
46//! .with_icon(icon)
47//! .build()
48//! .unwrap();
49//! ```
50//!
51//! #### Create a tray icon with a menu.
52//!
53//! ```no_run
54//! use tray_icon::{TrayIconBuilder, menu::Menu,Icon};
55//!
56//! # let icon = Icon::from_rgba(Vec::new(), 0, 0).unwrap();
57//! let tray_menu = Menu::new();
58//! let tray_icon = TrayIconBuilder::new()
59//! .with_menu(Box::new(tray_menu))
60//! .with_tooltip("system-tray - tray icon library!")
61//! .with_icon(icon)
62//! .build()
63//! .unwrap();
64//! ```
65//!
66//! # Processing tray events
67//!
68//! You can use [`TrayIconEvent::receiver`] to get a reference to the [`TrayIconEventReceiver`]
69//! which you can use to listen to events when a click happens on the tray icon
70//! ```no_run
71//! use tray_icon::TrayIconEvent;
72//!
73//! if let Ok(event) = TrayIconEvent::receiver().try_recv() {
74//! println!("{:?}", event);
75//! }
76//! ```
77//!
78//! You can also listen for the menu events using [`MenuEvent::receiver`](crate::menu::MenuEvent::receiver) to get events for the tray context menu.
79//!
80//! ```no_run
81//! use tray_icon::{TrayIconEvent, menu::MenuEvent};
82//!
83//! if let Ok(event) = TrayIconEvent::receiver().try_recv() {
84//! println!("tray event: {:?}", event);
85//! }
86//!
87//! if let Ok(event) = MenuEvent::receiver().try_recv() {
88//! println!("menu event: {:?}", event);
89//! }
90//! ```
91//!
92//! ### Note for [winit] or [tao] users:
93//!
94//! You should use [`TrayIconEvent::set_event_handler`] and forward
95//! the tray icon events to the event loop by using [`EventLoopProxy`]
96//! so that the event loop is awakened on each tray icon event.
97//! Same can be done for menu events using [`MenuEvent::set_event_handler`].
98//!
99//! ```no_run
100//! # use winit::event_loop::EventLoop;
101//! enum UserEvent {
102//! TrayIconEvent(tray_icon::TrayIconEvent),
103//! MenuEvent(tray_icon::menu::MenuEvent)
104//! }
105//!
106//! let event_loop = EventLoop::<UserEvent>::with_user_event().build().unwrap();
107//!
108//! let proxy = event_loop.create_proxy();
109//! tray_icon::TrayIconEvent::set_event_handler(Some(move |event| {
110//! proxy.send_event(UserEvent::TrayIconEvent(event));
111//! }));
112//!
113//! let proxy = event_loop.create_proxy();
114//! tray_icon::menu::MenuEvent::set_event_handler(Some(move |event| {
115//! proxy.send_event(UserEvent::MenuEvent(event));
116//! }));
117//! ```
118//!
119//! [`EventLoopProxy`]: https://docs.rs/winit/latest/winit/event_loop/struct.EventLoopProxy.html
120//! [winit]: https://docs.rs/winit
121//! [tao]: https://docs.rs/tao
122
123use std::{
124 cell::RefCell,
125 path::{Path, PathBuf},
126 rc::Rc,
127};
128
129use counter::Counter;
130use crossbeam_channel::{unbounded, Receiver, Sender};
131use once_cell::sync::{Lazy, OnceCell};
132
133mod counter;
134mod error;
135mod icon;
136mod platform_impl;
137mod tray_icon_id;
138
139pub use self::error::*;
140pub use self::icon::{BadIcon, Icon};
141pub use self::tray_icon_id::TrayIconId;
142
143/// Re-export of [muda](::muda) crate and used for tray context menu.
144pub mod menu {
145 pub use muda::*;
146}
147pub use muda::dpi;
148
149static COUNTER: Counter = Counter::new();
150
151/// Attributes to use when creating a tray icon.
152pub struct TrayIconAttributes {
153 /// Tray icon tooltip
154 ///
155 /// ## Platform-specific:
156 ///
157 /// - **Linux:** Unsupported.
158 pub tooltip: Option<String>,
159
160 /// Tray menu
161 ///
162 /// ## Platform-specific:
163 ///
164 /// - **Linux**: once a menu is set, it cannot be removed.
165 pub menu: Option<Box<dyn menu::ContextMenu>>,
166
167 /// Tray icon
168 ///
169 /// ## Platform-specific:
170 ///
171 /// - **Linux:** Sometimes the icon won't be visible unless a menu is set.
172 /// Setting an empty [`Menu`](crate::menu::Menu) is enough.
173 pub icon: Option<Icon>,
174
175 /// Tray icon temp dir path. **Linux only**.
176 pub temp_dir_path: Option<PathBuf>,
177
178 /// Use the icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.
179 pub icon_is_template: bool,
180
181 /// Whether to show the tray menu on left click or not, default is `true`.
182 ///
183 /// ## Platform-specific:
184 ///
185 /// - **Linux:** Unsupported.
186 pub menu_on_left_click: bool,
187
188 /// Whether to show the tray menu on right click or not, default is `true`.
189 ///
190 /// ## Platform-specific:
191 ///
192 /// - **Linux:** Unsupported.
193 pub menu_on_right_click: bool,
194
195 /// Tray icon title.
196 ///
197 /// ## Platform-specific
198 ///
199 /// - **Linux:** The title will not be shown unless there is an icon
200 /// as well. The title is useful for numerical and other frequently
201 /// updated information. In general, it shouldn't be shown unless a
202 /// user requests it as it can take up a significant amount of space
203 /// on the user's panel. This may not be shown in all visualizations.
204 /// - **Windows:** Unsupported.
205 pub title: Option<String>,
206}
207
208impl Default for TrayIconAttributes {
209 fn default() -> Self {
210 Self {
211 tooltip: None,
212 menu: None,
213 icon: None,
214 temp_dir_path: None,
215 icon_is_template: false,
216 menu_on_left_click: true,
217 menu_on_right_click: true,
218 title: None,
219 }
220 }
221}
222
223/// [`TrayIcon`] builder struct and associated methods.
224#[derive(Default)]
225pub struct TrayIconBuilder {
226 id: TrayIconId,
227 attrs: TrayIconAttributes,
228}
229
230impl TrayIconBuilder {
231 /// Creates a new [`TrayIconBuilder`] with default [`TrayIconAttributes`].
232 ///
233 /// See [`TrayIcon::new`] for more info.
234 pub fn new() -> Self {
235 Self {
236 id: TrayIconId::new_unique(),
237 attrs: TrayIconAttributes::default(),
238 }
239 }
240
241 /// Sets the unique id to build the tray icon with.
242 pub fn with_id<I: Into<TrayIconId>>(mut self, id: I) -> Self {
243 self.id = id.into();
244 self
245 }
246
247 /// Set the a menu for this tray icon.
248 ///
249 /// ## Platform-specific:
250 ///
251 /// - **Linux**: once a menu is set, it cannot be removed or replaced but you can change its content.
252 pub fn with_menu(mut self, menu: Box<dyn menu::ContextMenu>) -> Self {
253 self.attrs.menu = Some(menu);
254 self
255 }
256
257 /// Set an icon for this tray icon.
258 ///
259 /// ## Platform-specific:
260 ///
261 /// - **Linux:** Sometimes the icon won't be visible unless a menu is set.
262 /// Setting an empty [`Menu`](crate::menu::Menu) is enough.
263 pub fn with_icon(mut self, icon: Icon) -> Self {
264 self.attrs.icon = Some(icon);
265 self
266 }
267
268 /// Set a tooltip for this tray icon.
269 ///
270 /// ## Platform-specific:
271 ///
272 /// - **Linux:** Unsupported.
273 pub fn with_tooltip<S: AsRef<str>>(mut self, s: S) -> Self {
274 self.attrs.tooltip = Some(s.as_ref().to_string());
275 self
276 }
277
278 /// Set the tray icon title.
279 ///
280 /// ## Platform-specific
281 ///
282 /// - **Linux:** The title will not be shown unless there is an icon
283 /// as well. The title is useful for numerical and other frequently
284 /// updated information. In general, it shouldn't be shown unless a
285 /// user requests it as it can take up a significant amount of space
286 /// on the user's panel. This may not be shown in all visualizations.
287 /// - **Windows:** Unsupported.
288 pub fn with_title<S: AsRef<str>>(mut self, title: S) -> Self {
289 self.attrs.title.replace(title.as_ref().to_string());
290 self
291 }
292
293 /// Set tray icon temp dir path. **Linux only**.
294 ///
295 /// On Linux, we need to write the icon to the disk and usually it will
296 /// be `$XDG_RUNTIME_DIR/tray-icon` or `$TEMP/tray-icon`.
297 pub fn with_temp_dir_path<P: AsRef<Path>>(mut self, s: P) -> Self {
298 self.attrs.temp_dir_path = Some(s.as_ref().to_path_buf());
299 self
300 }
301
302 /// Use the icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.
303 pub fn with_icon_as_template(mut self, is_template: bool) -> Self {
304 self.attrs.icon_is_template = is_template;
305 self
306 }
307
308 /// Whether to show the tray menu on left click or not, default is `true`.
309 ///
310 /// ## Platform-specific:
311 ///
312 /// - **Linux:** Unsupported.
313 pub fn with_menu_on_left_click(mut self, enable: bool) -> Self {
314 self.attrs.menu_on_left_click = enable;
315 self
316 }
317
318 /// Whether to show the tray menu on right click or not, default is `true`.
319 ///
320 /// ## Platform-specific:
321 ///
322 /// - **Linux:** Unsupported.
323 pub fn with_menu_on_right_click(mut self, enable: bool) -> Self {
324 self.attrs.menu_on_right_click = enable;
325 self
326 }
327
328 /// Access the unique id that will be assigned to the tray icon
329 /// this builder will create.
330 pub fn id(&self) -> &TrayIconId {
331 &self.id
332 }
333
334 /// Builds and adds a new [`TrayIcon`] to the system tray.
335 pub fn build(self) -> Result<TrayIcon> {
336 TrayIcon::with_id(self.id, self.attrs)
337 }
338}
339
340/// Tray icon struct and associated methods.
341///
342/// This type is reference-counted and the icon is removed when the last instance is dropped.
343#[derive(Clone)]
344pub struct TrayIcon {
345 id: TrayIconId,
346 tray: Rc<RefCell<platform_impl::TrayIcon>>,
347}
348
349impl TrayIcon {
350 /// Builds and adds a new tray icon to the system tray.
351 ///
352 /// ## Platform-specific:
353 ///
354 /// - **Linux:** Sometimes the icon won't be visible unless a menu is set.
355 /// Setting an empty [`Menu`](crate::menu::Menu) is enough.
356 pub fn new(attrs: TrayIconAttributes) -> Result<Self> {
357 let id = TrayIconId::new_unique();
358 Ok(Self {
359 tray: Rc::new(RefCell::new(platform_impl::TrayIcon::new(
360 id.clone(),
361 attrs,
362 )?)),
363 id,
364 })
365 }
366
367 /// Builds and adds a new tray icon to the system tray with the specified Id.
368 ///
369 /// See [`TrayIcon::new`] for more info.
370 pub fn with_id<I: Into<TrayIconId>>(id: I, attrs: TrayIconAttributes) -> Result<Self> {
371 let id = id.into();
372 Ok(Self {
373 tray: Rc::new(RefCell::new(platform_impl::TrayIcon::new(
374 id.clone(),
375 attrs,
376 )?)),
377 id,
378 })
379 }
380
381 /// Returns the id associated with this tray icon.
382 pub fn id(&self) -> &TrayIconId {
383 &self.id
384 }
385
386 /// Set new tray icon. If `None` is provided, it will remove the icon.
387 pub fn set_icon(&self, icon: Option<Icon>) -> Result<()> {
388 self.tray.borrow_mut().set_icon(icon)
389 }
390
391 /// Set new tray menu.
392 ///
393 /// ## Platform-specific:
394 ///
395 /// - **Linux**: once a menu is set it cannot be removed so `None` has no effect
396 pub fn set_menu(&self, menu: Option<Box<dyn menu::ContextMenu>>) {
397 self.tray.borrow_mut().set_menu(menu)
398 }
399
400 /// Sets the tooltip for this tray icon.
401 ///
402 /// ## Platform-specific:
403 ///
404 /// - **Linux:** Unsupported
405 pub fn set_tooltip<S: AsRef<str>>(&self, tooltip: Option<S>) -> Result<()> {
406 self.tray.borrow_mut().set_tooltip(tooltip)
407 }
408
409 /// Sets the tooltip for this tray icon.
410 ///
411 /// ## Platform-specific:
412 ///
413 /// - **Linux:** The title will not be shown unless there is an icon
414 /// as well. The title is useful for numerical and other frequently
415 /// updated information. In general, it shouldn't be shown unless a
416 /// user requests it as it can take up a significant amount of space
417 /// on the user's panel. This may not be shown in all visualizations.
418 /// - **Windows:** Unsupported
419 pub fn set_title<S: AsRef<str>>(&self, title: Option<S>) {
420 self.tray.borrow_mut().set_title(title)
421 }
422
423 /// Show or hide this tray icon
424 pub fn set_visible(&self, visible: bool) -> Result<()> {
425 self.tray.borrow_mut().set_visible(visible)
426 }
427
428 /// Sets the tray icon temp dir path. **Linux only**.
429 ///
430 /// On Linux, we need to write the icon to the disk and usually it will
431 /// be `$XDG_RUNTIME_DIR/tray-icon` or `$TEMP/tray-icon`.
432 pub fn set_temp_dir_path<P: AsRef<Path>>(&self, path: Option<P>) {
433 #[cfg(any(
434 target_os = "linux",
435 target_os = "dragonfly",
436 target_os = "freebsd",
437 target_os = "netbsd",
438 target_os = "openbsd"
439 ))]
440 self.tray.borrow_mut().set_temp_dir_path(path);
441 #[cfg(not(any(
442 target_os = "linux",
443 target_os = "dragonfly",
444 target_os = "freebsd",
445 target_os = "netbsd",
446 target_os = "openbsd"
447 )))]
448 let _ = path;
449 }
450
451 /// Set the current icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.
452 pub fn set_icon_as_template(&self, is_template: bool) {
453 #[cfg(target_os = "macos")]
454 self.tray.borrow_mut().set_icon_as_template(is_template);
455 #[cfg(not(target_os = "macos"))]
456 let _ = is_template;
457 }
458
459 pub fn set_icon_with_as_template(&self, icon: Option<Icon>, is_template: bool) -> Result<()> {
460 #[cfg(target_os = "macos")]
461 return self
462 .tray
463 .borrow_mut()
464 .set_icon_with_as_template(icon, is_template);
465 #[cfg(not(target_os = "macos"))]
466 {
467 let _ = icon;
468 let _ = is_template;
469 Ok(())
470 }
471 }
472
473 /// Disable or enable showing the tray menu on left click.
474 ///
475 /// ## Platform-specific:
476 ///
477 /// - **Linux:** Unsupported.
478 pub fn set_show_menu_on_left_click(&self, enable: bool) {
479 #[cfg(any(target_os = "macos", target_os = "windows"))]
480 self.tray.borrow_mut().set_show_menu_on_left_click(enable);
481 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
482 let _ = enable;
483 }
484
485 /// Disable or enable showing the tray menu on right click.
486 ///
487 /// ## Platform-specific:
488 ///
489 /// - **Linux:** Unsupported.
490 pub fn set_show_menu_on_right_click(&self, enable: bool) {
491 #[cfg(any(target_os = "macos", target_os = "windows"))]
492 self.tray.borrow_mut().set_show_menu_on_right_click(enable);
493 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
494 let _ = enable;
495 }
496
497 /// Manually show the tray menu at the current cursor position.
498 ///
499 /// This is useful when you want to control when the menu is displayed,
500 /// for example after updating menu items dynamically.
501 ///
502 /// ## Platform-specific:
503 ///
504 /// - **Linux:** Unsupported.
505 pub fn show_menu(&self) {
506 #[cfg(any(target_os = "macos", target_os = "windows"))]
507 self.tray.borrow().show_menu();
508 }
509
510 /// Get tray icon rect.
511 ///
512 /// ## Platform-specific:
513 ///
514 /// - **Linux**: Unsupported.
515 pub fn rect(&self) -> Option<Rect> {
516 self.tray.borrow().rect()
517 }
518
519 /// Get the tray icon's underlying [window handle](windows_sys::Win32::Foundation::HWND) **Windows only**.
520 ///
521 /// This window handle is valid as long as the tray icon.
522 #[cfg(windows)]
523 pub fn window_handle(&self) -> windows_sys::Win32::Foundation::HWND {
524 self.tray.borrow().hwnd()
525 }
526
527 /// Get the tray icon's underlying [NSStatusItem](objc2_app_kit::NSStatusItem) **macOS only**.
528 ///
529 /// Returns `None` if the status item is not available.
530 #[cfg(target_os = "macos")]
531 pub fn ns_status_item(&self) -> Option<objc2::rc::Retained<objc2_app_kit::NSStatusItem>> {
532 self.tray.borrow().ns_status_item().cloned()
533 }
534
535 /// Get the tray icon's underlying [AppIndicator](libappindicator::AppIndicator) **Linux only**.
536 ///
537 /// # Safety
538 ///
539 /// The returned pointer is valid as long as the `TrayIcon` is.
540 #[cfg(all(unix, not(target_os = "macos")))]
541 pub unsafe fn app_indicator(&self) -> *const libappindicator::AppIndicator {
542 self.tray.borrow().app_indicator() as *const _
543 }
544}
545
546/// Describes a tray icon event.
547///
548/// ## Platform-specific:
549///
550/// - **Linux**: Unsupported. The event is not emmited even though the icon is shown
551/// and will still show a context menu on right click.
552#[derive(Debug, Clone)]
553#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
554#[cfg_attr(feature = "serde", serde(tag = "type"))]
555#[non_exhaustive]
556pub enum TrayIconEvent {
557 /// A click happened on the tray icon.
558 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
559 Click {
560 /// Id of the tray icon which triggered this event.
561 id: TrayIconId,
562 /// Physical Position of this event.
563 position: dpi::PhysicalPosition<f64>,
564 /// Position and size of the tray icon.
565 rect: Rect,
566 /// Mouse button that triggered this event.
567 button: MouseButton,
568 /// Mouse button state when this event was triggered.
569 button_state: MouseButtonState,
570 },
571 /// A double click happened on the tray icon. **Windows Only**
572 DoubleClick {
573 /// Id of the tray icon which triggered this event.
574 id: TrayIconId,
575 /// Physical Position of this event.
576 position: dpi::PhysicalPosition<f64>,
577 /// Position and size of the tray icon.
578 rect: Rect,
579 /// Mouse button that triggered this event.
580 button: MouseButton,
581 },
582 /// The mouse entered the tray icon region.
583 Enter {
584 /// Id of the tray icon which triggered this event.
585 id: TrayIconId,
586 /// Physical Position of this event.
587 position: dpi::PhysicalPosition<f64>,
588 /// Position and size of the tray icon.
589 rect: Rect,
590 },
591 /// The mouse moved over the tray icon region.
592 Move {
593 /// Id of the tray icon which triggered this event.
594 id: TrayIconId,
595 /// Physical Position of this event.
596 position: dpi::PhysicalPosition<f64>,
597 /// Position and size of the tray icon.
598 rect: Rect,
599 },
600 /// The mouse left the tray icon region.
601 Leave {
602 /// Id of the tray icon which triggered this event.
603 id: TrayIconId,
604 /// Physical Position of this event.
605 position: dpi::PhysicalPosition<f64>,
606 /// Position and size of the tray icon.
607 rect: Rect,
608 },
609}
610
611/// Describes the mouse button state.
612#[derive(Clone, Copy, PartialEq, Eq, Debug)]
613#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
614#[derive(Default)]
615pub enum MouseButtonState {
616 #[default]
617 Up,
618 Down,
619}
620
621/// Describes which mouse button triggered the event..
622#[derive(Clone, Copy, PartialEq, Eq, Debug)]
623#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
624#[derive(Default)]
625pub enum MouseButton {
626 #[default]
627 Left,
628 Right,
629 Middle,
630}
631
632/// Describes a rectangle including position (x - y axis) and size.
633#[derive(Debug, PartialEq, Clone, Copy)]
634#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
635pub struct Rect {
636 pub size: dpi::PhysicalSize<u32>,
637 pub position: dpi::PhysicalPosition<f64>,
638}
639
640impl Default for Rect {
641 fn default() -> Self {
642 Self {
643 size: dpi::PhysicalSize::new(0, 0),
644 position: dpi::PhysicalPosition::new(0., 0.),
645 }
646 }
647}
648
649/// A reciever that could be used to listen to tray events.
650pub type TrayIconEventReceiver = Receiver<TrayIconEvent>;
651type TrayIconEventHandler = Box<dyn Fn(TrayIconEvent) + Send + Sync + 'static>;
652
653static TRAY_CHANNEL: Lazy<(Sender<TrayIconEvent>, TrayIconEventReceiver)> = Lazy::new(unbounded);
654static TRAY_EVENT_HANDLER: OnceCell<Option<TrayIconEventHandler>> = OnceCell::new();
655
656impl TrayIconEvent {
657 /// Returns the id of the tray icon which triggered this event.
658 pub fn id(&self) -> &TrayIconId {
659 match self {
660 TrayIconEvent::Click { id, .. } => id,
661 TrayIconEvent::DoubleClick { id, .. } => id,
662 TrayIconEvent::Enter { id, .. } => id,
663 TrayIconEvent::Move { id, .. } => id,
664 TrayIconEvent::Leave { id, .. } => id,
665 }
666 }
667
668 /// Gets a reference to the event channel's [`TrayIconEventReceiver`]
669 /// which can be used to listen for tray events.
670 ///
671 /// ## Note
672 ///
673 /// This will not receive any events if [`TrayIconEvent::set_event_handler`] has been called with a `Some` value.
674 pub fn receiver<'a>() -> &'a TrayIconEventReceiver {
675 &TRAY_CHANNEL.1
676 }
677
678 /// Set a handler to be called for new events. Useful for implementing custom event sender.
679 ///
680 /// ## Note
681 ///
682 /// Calling this function with a `Some` value,
683 /// will not send new events to the channel associated with [`TrayIconEvent::receiver`]
684 pub fn set_event_handler<F: Fn(TrayIconEvent) + Send + Sync + 'static>(f: Option<F>) {
685 if let Some(f) = f {
686 let _ = TRAY_EVENT_HANDLER.set(Some(Box::new(f)));
687 } else {
688 let _ = TRAY_EVENT_HANDLER.set(None);
689 }
690 }
691
692 #[allow(unused)]
693 pub(crate) fn send(event: TrayIconEvent) {
694 if let Some(handler) = TRAY_EVENT_HANDLER.get_or_init(|| None) {
695 handler(event);
696 } else {
697 let _ = TRAY_CHANNEL.0.send(event);
698 }
699 }
700}
701
702#[cfg(test)]
703mod tests {
704
705 #[cfg(feature = "serde")]
706 #[test]
707 fn it_serializes() {
708 use super::*;
709 let event = TrayIconEvent::Click {
710 button: MouseButton::Left,
711 button_state: MouseButtonState::Down,
712 id: TrayIconId::new("id"),
713 position: dpi::PhysicalPosition::default(),
714 rect: Rect::default(),
715 };
716
717 let value = serde_json::to_value(&event).unwrap();
718 assert_eq!(
719 value,
720 serde_json::json!({
721 "type": "Click",
722 "button": "Left",
723 "buttonState": "Down",
724 "id": "id",
725 "position": {
726 "x": 0.0,
727 "y": 0.0,
728 },
729 "rect": {
730 "size": {
731 "width": 0,
732 "height": 0,
733 },
734 "position": {
735 "x": 0.0,
736 "y": 0.0,
737 },
738 }
739 })
740 )
741 }
742}