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    /// Tray icon title.
189    ///
190    /// ## Platform-specific
191    ///
192    /// - **Linux:** The title will not be shown unless there is an icon
193    ///   as well.  The title is useful for numerical and other frequently
194    ///   updated information.  In general, it shouldn't be shown unless a
195    ///   user requests it as it can take up a significant amount of space
196    ///   on the user's panel.  This may not be shown in all visualizations.
197    /// - **Windows:** Unsupported.
198    pub title: Option<String>,
199}
200
201impl Default for TrayIconAttributes {
202    fn default() -> Self {
203        Self {
204            tooltip: None,
205            menu: None,
206            icon: None,
207            temp_dir_path: None,
208            icon_is_template: false,
209            menu_on_left_click: true,
210            title: None,
211        }
212    }
213}
214
215/// [`TrayIcon`] builder struct and associated methods.
216#[derive(Default)]
217pub struct TrayIconBuilder {
218    id: TrayIconId,
219    attrs: TrayIconAttributes,
220}
221
222impl TrayIconBuilder {
223    /// Creates a new [`TrayIconBuilder`] with default [`TrayIconAttributes`].
224    ///
225    /// See [`TrayIcon::new`] for more info.
226    pub fn new() -> Self {
227        Self {
228            id: TrayIconId(COUNTER.next().to_string()),
229            attrs: TrayIconAttributes::default(),
230        }
231    }
232
233    /// Sets the unique id to build the tray icon with.
234    pub fn with_id<I: Into<TrayIconId>>(mut self, id: I) -> Self {
235        self.id = id.into();
236        self
237    }
238
239    /// Set the a menu for this tray icon.
240    ///
241    /// ## Platform-specific:
242    ///
243    /// - **Linux**: once a menu is set, it cannot be removed or replaced but you can change its content.
244    pub fn with_menu(mut self, menu: Box<dyn menu::ContextMenu>) -> Self {
245        self.attrs.menu = Some(menu);
246        self
247    }
248
249    /// Set an icon for this tray icon.
250    ///
251    /// ## Platform-specific:
252    ///
253    /// - **Linux:** Sometimes the icon won't be visible unless a menu is set.
254    ///   Setting an empty [`Menu`](crate::menu::Menu) is enough.
255    pub fn with_icon(mut self, icon: Icon) -> Self {
256        self.attrs.icon = Some(icon);
257        self
258    }
259
260    /// Set a tooltip for this tray icon.
261    ///
262    /// ## Platform-specific:
263    ///
264    /// - **Linux:** Unsupported.
265    pub fn with_tooltip<S: AsRef<str>>(mut self, s: S) -> Self {
266        self.attrs.tooltip = Some(s.as_ref().to_string());
267        self
268    }
269
270    /// Set the tray icon title.
271    ///
272    /// ## Platform-specific
273    ///
274    /// - **Linux:** The title will not be shown unless there is an icon
275    ///   as well.  The title is useful for numerical and other frequently
276    ///   updated information.  In general, it shouldn't be shown unless a
277    ///   user requests it as it can take up a significant amount of space
278    ///   on the user's panel.  This may not be shown in all visualizations.
279    /// - **Windows:** Unsupported.
280    pub fn with_title<S: AsRef<str>>(mut self, title: S) -> Self {
281        self.attrs.title.replace(title.as_ref().to_string());
282        self
283    }
284
285    /// Set tray icon temp dir path. **Linux only**.
286    ///
287    /// On Linux, we need to write the icon to the disk and usually it will
288    /// be `$XDG_RUNTIME_DIR/tray-icon` or `$TEMP/tray-icon`.
289    pub fn with_temp_dir_path<P: AsRef<Path>>(mut self, s: P) -> Self {
290        self.attrs.temp_dir_path = Some(s.as_ref().to_path_buf());
291        self
292    }
293
294    /// Use the icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.
295    pub fn with_icon_as_template(mut self, is_template: bool) -> Self {
296        self.attrs.icon_is_template = is_template;
297        self
298    }
299
300    /// Whether to show the tray menu on left click or not, default is `true`.
301    ///
302    /// ## Platform-specific:
303    ///
304    /// - **Linux:** Unsupported.
305    pub fn with_menu_on_left_click(mut self, enable: bool) -> Self {
306        self.attrs.menu_on_left_click = enable;
307        self
308    }
309
310    /// Access the unique id that will be assigned to the tray icon
311    /// this builder will create.
312    pub fn id(&self) -> &TrayIconId {
313        &self.id
314    }
315
316    /// Builds and adds a new [`TrayIcon`] to the system tray.
317    pub fn build(self) -> Result<TrayIcon> {
318        TrayIcon::with_id(self.id, self.attrs)
319    }
320}
321
322/// Tray icon struct and associated methods.
323///
324/// This type is reference-counted and the icon is removed when the last instance is dropped.
325#[derive(Clone)]
326pub struct TrayIcon {
327    id: TrayIconId,
328    tray: Rc<RefCell<platform_impl::TrayIcon>>,
329}
330
331impl TrayIcon {
332    /// Builds and adds a new tray icon to the system tray.
333    ///
334    /// ## Platform-specific:
335    ///
336    /// - **Linux:** Sometimes the icon won't be visible unless a menu is set.
337    ///   Setting an empty [`Menu`](crate::menu::Menu) is enough.
338    pub fn new(attrs: TrayIconAttributes) -> Result<Self> {
339        let id = TrayIconId(COUNTER.next().to_string());
340        Ok(Self {
341            tray: Rc::new(RefCell::new(platform_impl::TrayIcon::new(
342                id.clone(),
343                attrs,
344            )?)),
345            id,
346        })
347    }
348
349    /// Builds and adds a new tray icon to the system tray with the specified Id.
350    ///
351    /// See [`TrayIcon::new`] for more info.
352    pub fn with_id<I: Into<TrayIconId>>(id: I, attrs: TrayIconAttributes) -> Result<Self> {
353        let id = id.into();
354        Ok(Self {
355            tray: Rc::new(RefCell::new(platform_impl::TrayIcon::new(
356                id.clone(),
357                attrs,
358            )?)),
359            id,
360        })
361    }
362
363    /// Returns the id associated with this tray icon.
364    pub fn id(&self) -> &TrayIconId {
365        &self.id
366    }
367
368    /// Set new tray icon. If `None` is provided, it will remove the icon.
369    pub fn set_icon(&self, icon: Option<Icon>) -> Result<()> {
370        self.tray.borrow_mut().set_icon(icon)
371    }
372
373    /// Set new tray menu.
374    ///
375    /// ## Platform-specific:
376    ///
377    /// - **Linux**: once a menu is set it cannot be removed so `None` has no effect
378    pub fn set_menu(&self, menu: Option<Box<dyn menu::ContextMenu>>) {
379        self.tray.borrow_mut().set_menu(menu)
380    }
381
382    /// Sets the tooltip for this tray icon.
383    ///
384    /// ## Platform-specific:
385    ///
386    /// - **Linux:** Unsupported
387    pub fn set_tooltip<S: AsRef<str>>(&self, tooltip: Option<S>) -> Result<()> {
388        self.tray.borrow_mut().set_tooltip(tooltip)
389    }
390
391    /// Sets the tooltip for this tray icon.
392    ///
393    /// ## Platform-specific:
394    ///
395    /// - **Linux:** The title will not be shown unless there is an icon
396    ///   as well.  The title is useful for numerical and other frequently
397    ///   updated information.  In general, it shouldn't be shown unless a
398    ///   user requests it as it can take up a significant amount of space
399    ///   on the user's panel.  This may not be shown in all visualizations.
400    /// - **Windows:** Unsupported
401    pub fn set_title<S: AsRef<str>>(&self, title: Option<S>) {
402        self.tray.borrow_mut().set_title(title)
403    }
404
405    /// Show or hide this tray icon
406    pub fn set_visible(&self, visible: bool) -> Result<()> {
407        self.tray.borrow_mut().set_visible(visible)
408    }
409
410    /// Sets the tray icon temp dir path. **Linux only**.
411    ///
412    /// On Linux, we need to write the icon to the disk and usually it will
413    /// be `$XDG_RUNTIME_DIR/tray-icon` or `$TEMP/tray-icon`.
414    pub fn set_temp_dir_path<P: AsRef<Path>>(&self, path: Option<P>) {
415        #[cfg(target_os = "linux")]
416        self.tray.borrow_mut().set_temp_dir_path(path);
417        #[cfg(not(target_os = "linux"))]
418        let _ = path;
419    }
420
421    /// Set the current icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.
422    pub fn set_icon_as_template(&self, is_template: bool) {
423        #[cfg(target_os = "macos")]
424        self.tray.borrow_mut().set_icon_as_template(is_template);
425        #[cfg(not(target_os = "macos"))]
426        let _ = is_template;
427    }
428
429    pub fn set_icon_with_as_template(&self, icon: Option<Icon>, is_template: bool) -> Result<()> {
430        #[cfg(target_os = "macos")]
431        return self
432            .tray
433            .borrow_mut()
434            .set_icon_with_as_template(icon, is_template);
435        #[cfg(not(target_os = "macos"))]
436        {
437            let _ = icon;
438            let _ = is_template;
439            Ok(())
440        }
441    }
442
443    /// Disable or enable showing the tray menu on left click.
444    ///
445    /// ## Platform-specific:
446    ///
447    /// - **Linux:** Unsupported.
448    pub fn set_show_menu_on_left_click(&self, enable: bool) {
449        #[cfg(any(target_os = "macos", target_os = "windows"))]
450        self.tray.borrow_mut().set_show_menu_on_left_click(enable);
451        #[cfg(not(any(target_os = "macos", target_os = "windows")))]
452        let _ = enable;
453    }
454
455    /// Get tray icon rect.
456    ///
457    /// ## Platform-specific:
458    ///
459    /// - **Linux**: Unsupported.
460    pub fn rect(&self) -> Option<Rect> {
461        self.tray.borrow().rect()
462    }
463
464    /// Get the tray icon's underlying [window handle](windows_sys::Win32::Foundation::HWND) **Windows only**.
465    ///
466    /// This window handle is valid as long as the tray icon.
467    #[cfg(windows)]
468    pub fn window_handle(&self) -> windows_sys::Win32::Foundation::HWND {
469        self.tray.borrow().hwnd()
470    }
471
472    /// Get the tray icon's underlying [NSStatusItem](objc2_app_kit::NSStatusItem) **macOS only**.
473    ///
474    /// Returns `None` if the status item is not available.
475    #[cfg(target_os = "macos")]
476    pub fn ns_status_item(&self) -> Option<objc2::rc::Retained<objc2_app_kit::NSStatusItem>> {
477        self.tray.borrow().ns_status_item().cloned()
478    }
479
480    /// Get the tray icon's underlying [AppIndicator](libappindicator::AppIndicator) **Linux only**.
481    ///
482    /// # Safety
483    ///
484    /// The returned pointer is valid as long as the `TrayIcon` is.
485    #[cfg(all(unix, not(target_os = "macos")))]
486    pub unsafe fn app_indicator(&self) -> *const libappindicator::AppIndicator {
487        self.tray.borrow().app_indicator() as *const _
488    }
489}
490
491/// Describes a tray icon event.
492///
493/// ## Platform-specific:
494///
495/// - **Linux**: Unsupported. The event is not emmited even though the icon is shown
496///   and will still show a context menu on right click.
497#[derive(Debug, Clone)]
498#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
499#[cfg_attr(feature = "serde", serde(tag = "type"))]
500#[non_exhaustive]
501pub enum TrayIconEvent {
502    /// A click happened on the tray icon.
503    #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
504    Click {
505        /// Id of the tray icon which triggered this event.
506        id: TrayIconId,
507        /// Physical Position of this event.
508        position: dpi::PhysicalPosition<f64>,
509        /// Position and size of the tray icon.
510        rect: Rect,
511        /// Mouse button that triggered this event.
512        button: MouseButton,
513        /// Mouse button state when this event was triggered.
514        button_state: MouseButtonState,
515    },
516    /// A double click happened on the tray icon. **Windows Only**
517    DoubleClick {
518        /// Id of the tray icon which triggered this event.
519        id: TrayIconId,
520        /// Physical Position of this event.
521        position: dpi::PhysicalPosition<f64>,
522        /// Position and size of the tray icon.
523        rect: Rect,
524        /// Mouse button that triggered this event.
525        button: MouseButton,
526    },
527    /// The mouse entered the tray icon region.
528    Enter {
529        /// Id of the tray icon which triggered this event.
530        id: TrayIconId,
531        /// Physical Position of this event.
532        position: dpi::PhysicalPosition<f64>,
533        /// Position and size of the tray icon.
534        rect: Rect,
535    },
536    /// The mouse moved over the tray icon region.
537    Move {
538        /// Id of the tray icon which triggered this event.
539        id: TrayIconId,
540        /// Physical Position of this event.
541        position: dpi::PhysicalPosition<f64>,
542        /// Position and size of the tray icon.
543        rect: Rect,
544    },
545    /// The mouse left the tray icon region.
546    Leave {
547        /// Id of the tray icon which triggered this event.
548        id: TrayIconId,
549        /// Physical Position of this event.
550        position: dpi::PhysicalPosition<f64>,
551        /// Position and size of the tray icon.
552        rect: Rect,
553    },
554}
555
556/// Describes the mouse button state.
557#[derive(Clone, Copy, PartialEq, Eq, Debug)]
558#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
559pub enum MouseButtonState {
560    Up,
561    Down,
562}
563
564impl Default for MouseButtonState {
565    fn default() -> Self {
566        Self::Up
567    }
568}
569
570/// Describes which mouse button triggered the event..
571#[derive(Clone, Copy, PartialEq, Eq, Debug)]
572#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
573pub enum MouseButton {
574    Left,
575    Right,
576    Middle,
577}
578
579impl Default for MouseButton {
580    fn default() -> Self {
581        Self::Left
582    }
583}
584
585/// Describes a rectangle including position (x - y axis) and size.
586#[derive(Debug, PartialEq, Clone, Copy)]
587#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
588pub struct Rect {
589    pub size: dpi::PhysicalSize<u32>,
590    pub position: dpi::PhysicalPosition<f64>,
591}
592
593impl Default for Rect {
594    fn default() -> Self {
595        Self {
596            size: dpi::PhysicalSize::new(0, 0),
597            position: dpi::PhysicalPosition::new(0., 0.),
598        }
599    }
600}
601
602/// A reciever that could be used to listen to tray events.
603pub type TrayIconEventReceiver = Receiver<TrayIconEvent>;
604type TrayIconEventHandler = Box<dyn Fn(TrayIconEvent) + Send + Sync + 'static>;
605
606static TRAY_CHANNEL: Lazy<(Sender<TrayIconEvent>, TrayIconEventReceiver)> = Lazy::new(unbounded);
607static TRAY_EVENT_HANDLER: OnceCell<Option<TrayIconEventHandler>> = OnceCell::new();
608
609impl TrayIconEvent {
610    /// Returns the id of the tray icon which triggered this event.
611    pub fn id(&self) -> &TrayIconId {
612        match self {
613            TrayIconEvent::Click { id, .. } => id,
614            TrayIconEvent::DoubleClick { id, .. } => id,
615            TrayIconEvent::Enter { id, .. } => id,
616            TrayIconEvent::Move { id, .. } => id,
617            TrayIconEvent::Leave { id, .. } => id,
618        }
619    }
620
621    /// Gets a reference to the event channel's [`TrayIconEventReceiver`]
622    /// which can be used to listen for tray events.
623    ///
624    /// ## Note
625    ///
626    /// This will not receive any events if [`TrayIconEvent::set_event_handler`] has been called with a `Some` value.
627    pub fn receiver<'a>() -> &'a TrayIconEventReceiver {
628        &TRAY_CHANNEL.1
629    }
630
631    /// Set a handler to be called for new events. Useful for implementing custom event sender.
632    ///
633    /// ## Note
634    ///
635    /// Calling this function with a `Some` value,
636    /// will not send new events to the channel associated with [`TrayIconEvent::receiver`]
637    pub fn set_event_handler<F: Fn(TrayIconEvent) + Send + Sync + 'static>(f: Option<F>) {
638        if let Some(f) = f {
639            let _ = TRAY_EVENT_HANDLER.set(Some(Box::new(f)));
640        } else {
641            let _ = TRAY_EVENT_HANDLER.set(None);
642        }
643    }
644
645    #[allow(unused)]
646    pub(crate) fn send(event: TrayIconEvent) {
647        if let Some(handler) = TRAY_EVENT_HANDLER.get_or_init(|| None) {
648            handler(event);
649        } else {
650            let _ = TRAY_CHANNEL.0.send(event);
651        }
652    }
653}
654
655#[cfg(test)]
656mod tests {
657
658    #[cfg(feature = "serde")]
659    #[test]
660    fn it_serializes() {
661        use super::*;
662        let event = TrayIconEvent::Click {
663            button: MouseButton::Left,
664            button_state: MouseButtonState::Down,
665            id: TrayIconId::new("id"),
666            position: dpi::PhysicalPosition::default(),
667            rect: Rect::default(),
668        };
669
670        let value = serde_json::to_value(&event).unwrap();
671        assert_eq!(
672            value,
673            serde_json::json!({
674                "type": "Click",
675                "button": "Left",
676                "buttonState": "Down",
677                "id": "id",
678                "position": {
679                    "x": 0.0,
680                    "y": 0.0,
681                },
682                "rect": {
683                    "size": {
684                        "width": 0,
685                        "height": 0,
686                    },
687                    "position": {
688                        "x": 0.0,
689                        "y": 0.0,
690                    },
691                }
692            })
693        )
694    }
695}