Skip to main content

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}