notify_icon/
lib.rs

1//! # notify-icon-rs
2//!
3//! A safe, ergonomic Rust wrapper around the Windows `Shell_NotifyIcon` API for
4//! managing system tray icons on Windows platforms.
5//!
6//! This library provides a high-level interface to create, modify, and manage
7//! notification icons in the Windows system tray. It handles the complexities
8//! of the underlying Windows API while providing a builder pattern for easy
9//! configuration and Rust-style error handling.
10//!
11//! ## Features
12//!
13//! - **Safe wrapper**: Memory-safe abstraction over the raw Windows API
14//! - **Builder pattern**: Fluent, chainable API for configuring notification
15//!   icons
16//! - **Error handling**: Proper Rust [`Result`] types instead of raw Windows
17//!   error codes
18//! - **UTF-16 handling**: Automatic conversion of Rust strings to
19//!   Windows-compatible UTF-16
20//! - **Version support**: Support for different Windows notification icon
21//!   interface versions
22//! - **Comprehensive functionality**: Support for tooltips, balloon
23//!   notifications, GUIDs, and more
24//!
25//! ## Supported Windows Versions
26//!
27//! This library should supports Windows 95 and later versions, with enhanced
28//! functionality on:
29//! - Windows Vista and later (improved balloon notification behavior)
30//! - Windows 7 and later (additional notification features)
31//!
32//! ## Platform Requirements
33//!
34//! - **Target**: Windows only (`#[cfg(windows)]` should be used when
35//!   integrating)
36//! - **Dependencies**: Requires the `windows` crate for Windows API bindings
37//!
38//! ## Basic Usage
39//!
40//! ```rust,no_run,ignore
41//! use windows::Win32::Foundation::HWND;
42//! use windows::Win32::UI::WindowsAndMessaging::LoadIconW;
43//! use windows::Win32::UI::WindowsAndMessaging::WM_USER;
44//! use notify_icon::NotifyIcon;
45//!
46//! const IDI_APP_ICON: PCWSTR = PCWSTR(101 as *const u16);
47//!
48//! // Create and configure a notification icon
49//! let icon = unsafe { LoadIconW(hinstance, IDI_APP_ICON) }.unwrap_or_default();
50//! let icon = NotifyIcon::new()
51//!     .window_handle(hwnd)                    // Window to receive messages
52//!     .tip("My Application")                  // Tooltip text
53//!     .icon(icon_handle)                      // Icon to display
54//!     .callback_message(WM_USER + 1);        // Message ID for callbacks
55//!
56//! // Add the icon to the system tray
57//! icon.notify_add()?;
58//!
59//! // Later, remove the icon
60//! icon.notify_delete()?;
61//! ```
62//!
63//! ## Advanced Usage
64//!
65//! ### Using GUIDs for Icon Persistence
66//!
67//! ```rust,no_run,ignore
68//! use windows::core::GUID;
69//!
70//! let icon = NotifyIcon::new()
71//!     .window_handle(hwnd)
72//!     .guid(GUID::from_u128(0x12345678_1234_1234_1234_123456789ABC))
73//!     .tip("Persistent Icon")
74//!     .icon(icon_handle);
75//!
76//! icon.notify_add()?;
77//! ```
78//!
79//! ### Setting Interface Version for Enhanced Features
80//!
81//! ```rust,no_run,ignore
82//! use notify_icon::NotifyIcon;
83//!
84//! // Use Windows Vista+ behavior
85//! let icon = NotifyIcon::new()
86//!     .window_handle(hwnd)
87//!     .version(3)  // NOTIFYICON_VERSION_4
88//!     .tip("Modern Icon");
89//!
90//! icon.notify_add()?;
91//! icon.notify_set_version()?;  // Apply the version setting
92//! ```
93//!
94//! ### Modifying Existing Icons
95//!
96//! ```rust,no_run,ignore
97//! // Change the tooltip of an existing icon
98//! let updated_icon = icon.tip("Updated tooltip text");
99//! updated_icon.notify_modify()?;
100//! ```
101//!
102//! ## Message Handling
103//!
104//! When users interact with the notification icon, Windows sends messages to
105//! the specified window. Common message handling pattern:
106//!
107//! ```rust,no_run,ignore
108//! // In your window procedure
109//! match msg {
110//!     WM_USER + 1 => {  // Your callback message
111//!         match lparam {
112//!             WM_LBUTTONUP => {
113//!                 // Handle left click
114//!             },
115//!             WM_RBUTTONUP => {
116//!                 // Handle right click - typically show context menu
117//!             },
118//!             _ => {}
119//!         }
120//!     },
121//!     _ => {}
122//! }
123//! ```
124//!
125//! ## Error Handling
126//!
127//! All notification operations return [`windows::core::Result<()>`].
128//!
129//! ## Thread Safety
130//!
131//! The [`NotifyIcon`] struct is safe to use across threads.
132//!
133//! ## Limitations
134//!
135//! - **Windows only**: This library only works on Windows platforms
136
137use windows::{
138    Win32::{
139        Foundation::{FALSE, HWND},
140        UI::{
141            Shell::{
142                NIF_GUID, NIF_ICON, NIF_MESSAGE, NIF_SHOWTIP, NIF_TIP, NIM_ADD, NIM_DELETE,
143                NIM_MODIFY, NIM_SETFOCUS, NIM_SETVERSION, NOTIFY_ICON_DATA_FLAGS,
144                NOTIFY_ICON_MESSAGE, NOTIFYICONDATAW, Shell_NotifyIconW,
145            },
146            WindowsAndMessaging::HICON,
147        },
148    },
149    core::GUID,
150};
151
152/// A wrapper around the Windows NOTIFYICONDATAW structure for managing system
153/// tray icons in Windows.
154pub struct NotifyIcon {
155    /// Underlying internal data.
156    data: NOTIFYICONDATAW,
157}
158
159impl Default for NotifyIcon {
160    fn default() -> Self {
161        Self {
162            data: NOTIFYICONDATAW {
163                cbSize: std::mem::size_of::<NOTIFYICONDATAW>() as _,
164                ..Default::default()
165            },
166        }
167    }
168}
169
170impl NotifyIcon {
171    /// Creates a new [NotifyIcon] instance with default values.
172    ///
173    /// This is equivalent to calling [`NotifyIcon::default()`].
174    ///
175    /// # Returns
176    ///
177    /// A new [`NotifyIcon`] instance with the [`NOTIFYICONDATAW::cbSize`] field
178    /// properly initialized.
179    pub fn new() -> NotifyIcon {
180        Self::default()
181    }
182
183    /// Sets a flag in the notification icon data structure.
184    ///
185    /// This method uses a bitwise OR operation to add the specified flag to the
186    /// existing flags in the [`NOTIFYICONDATAW::uFlags`].
187    ///
188    /// # Arguments
189    ///
190    /// * `flag` - A [`NOTIFY_ICON_DATA_FLAGS`] value to be added to the current
191    ///   flags
192    ///
193    /// # Returns
194    ///
195    /// Self for method chaining
196    pub fn flag(mut self, flag: NOTIFY_ICON_DATA_FLAGS) -> Self {
197        self.data.uFlags |= flag;
198        self
199    }
200
201    /// Sets the window handle that will receive notification messages.
202    ///
203    /// This method specifies the window that will receive callback messages
204    /// when the user interacts with the notification icon. The window
205    /// handle is required for the notification icon to function properly.
206    ///
207    /// # Arguments
208    ///
209    /// * `handle` - A handle ([HWND]) to the window that will receive
210    ///   notification messages
211    ///
212    /// # Returns
213    ///
214    /// Self for method chaining
215    pub fn window_handle(mut self, handle: HWND) -> Self {
216        self.data.hWnd = handle;
217        self.flag(NIF_MESSAGE)
218    }
219
220    /// Sets the tooltip text for the notification icon.
221    ///
222    /// The tooltip text is displayed when the user hovers over the icon in the
223    /// system tray. The text is converted to UTF-16 format and truncated if
224    /// it exceeds the maximum length. Automatically sets the [NIF_TIP] and
225    /// [NIF_SHOWTIP] flags.
226    ///
227    /// # Arguments
228    ///
229    /// * `s` - The tooltip text as any type that can be converted into a String
230    ///
231    /// # Returns
232    ///
233    /// Self for method chaining
234    pub fn tip(mut self, s: impl Into<String>) -> Self {
235        let s = s.into();
236        let tip_utf16 = s.encode_utf16().chain(Some(0)).collect::<Vec<u16>>();
237        let max_len = self.data.szTip.len() - 1;
238        if tip_utf16.len() <= max_len + 1 {
239            self.data.szTip[..tip_utf16.len()].copy_from_slice(&tip_utf16);
240        } else {
241            self.data.szTip[..max_len].copy_from_slice(&tip_utf16[..max_len]);
242            self.data.szTip[max_len] = 0;
243        }
244        self.flag(NIF_TIP | NIF_SHOWTIP)
245    }
246
247    /// Sets the icon for the notification area.
248    ///
249    /// This method assigns an icon handle to the notification icon and
250    /// automatically sets the [NIF_ICON] flag to indicate that the icon field
251    /// is valid.
252    ///
253    /// # Arguments
254    ///
255    /// * `icon` - An [HICON] handle to the icon to be displayed in the system
256    ///   tray
257    ///
258    /// # Returns
259    ///
260    /// Self for method chaining
261    pub fn icon(mut self, icon: HICON) -> Self {
262        self.data.hIcon = icon;
263        self.flag(NIF_ICON)
264    }
265
266    /// Sets the icon for balloon notifications.
267    ///
268    /// This icon is displayed in balloon tip notifications. The method
269    /// automatically sets the [NIF_ICON] flag to indicate that the balloon
270    /// icon field is valid.
271    ///
272    /// # Arguments
273    ///
274    /// * `icon` - An [HICON] handle to the icon to be displayed in balloon
275    ///   notifications
276    ///
277    /// # Returns
278    ///
279    /// Self for method chaining
280    pub fn balloon_icon(mut self, icon: HICON) -> Self {
281        self.data.hBalloonIcon = icon;
282        self.flag(NIF_ICON)
283    }
284
285    /// Sets the callback message identifier for the notification icon.
286    ///
287    /// When the user interacts with the notification icon (clicks,
288    /// double-clicks, etc.), Windows sends this message to the window
289    /// procedure. Automatically sets the [NIF_MESSAGE] flag.
290    ///
291    /// # Arguments
292    ///
293    /// * `callback_msg` - The message identifier that will be sent to the
294    ///   window procedure
295    ///
296    /// # Returns
297    ///
298    /// Self for method chaining
299    pub fn callback_message(mut self, callback_msg: u32) -> Self {
300        self.data.uCallbackMessage = callback_msg;
301        self.flag(NIF_MESSAGE)
302    }
303
304    /// Sets a GUID for the notification icon.
305    ///
306    /// The GUID provides a unique identifier for the notification icon, which
307    /// can be useful for maintaining icon state across application
308    /// restarts. Automatically sets the [NIF_GUID] flag.
309    ///
310    /// # Arguments
311    ///
312    /// * `guid` - A 128-bit unsigned integer representing the GUID
313    ///
314    /// # Returns
315    ///
316    /// Self for method chaining
317    pub fn guid(mut self, guid: impl Into<GUID>) -> Self {
318        self.data.guidItem = guid.into();
319        self.flag(NIF_GUID)
320    }
321
322    /// Sets the timeout duration for balloon tip notifications.
323    ///
324    /// This value specifies how long the balloon tip should be displayed before
325    /// automatically disappearing. The timeout is specified in milliseconds.
326    ///
327    /// **Note**: This field is deprecated as of Windows Vista. On Vista and
328    /// later, notification display times are based on system accessibility
329    /// settings. This field is only effective on Windows 2000 and Windows
330    /// XP.
331    ///
332    /// The system enforces minimum (10 seconds) and maximum (30 seconds)
333    /// timeout values.
334    ///
335    /// # Arguments
336    ///
337    /// * `timeout` - Timeout duration in milliseconds (only effective on
338    ///   Windows 2000/XP)
339    ///
340    /// # Returns
341    ///
342    /// Self for method chaining
343    pub fn timeout(mut self, timeout: u32) -> Self {
344        self.data.Anonymous.uTimeout = timeout;
345        self
346    }
347
348    /// Sets the version of the Shell notification icon interface to use.
349    ///
350    /// This method specifies which version of the notification icon interface
351    /// should be used, which affects the behavior of certain notification
352    /// features. The version determines whether to use Windows 95-style or
353    /// newer behavior for icon interactions.
354    ///
355    /// **Note**: This field shares the same memory location as `uTimeout` in a
356    /// union. This method should only be used when sending a
357    /// [`NIM_SETVERSION`] message via [`NotifyIcon::notify_set_version`]. For
358    /// balloon notifications, use [`NotifyIcon::timeout`] instead.
359    ///
360    /// Common version values:
361    /// - `0` (`NOTIFYICON_VERSION`): Use Windows 95-style behavior (default)
362    /// - `3` (`NOTIFYICON_VERSION_4`): Use Windows Vista and later behavior
363    /// - `4`: Use Windows 7 and later behavior
364    ///
365    /// # Arguments
366    ///
367    /// * `version` - The Shell notification icon interface version to use
368    ///
369    /// # Returns
370    ///
371    /// Self for method chaining
372    pub fn version(mut self, version: u32) -> Self {
373        self.data.Anonymous.uVersion = version;
374        self
375    }
376
377    /// Sends a notification message to the Windows shell.
378    ///
379    /// This is the core method that communicates with the Windows shell to
380    /// perform operations on the notification icon. It calls the
381    /// [Shell_NotifyIconW] function with the specified message and the
382    /// current icon data.
383    ///
384    /// # Arguments
385    ///
386    /// * `message` - The type of operation to perform (add, delete, modify,
387    ///   etc.)
388    ///
389    /// # Returns
390    ///
391    /// A [`windows::core::Result<()>`] indicating success or failure
392    ///
393    /// # Errors
394    ///
395    /// Returns an error if the Shell_NotifyIconW function fails
396    pub fn notify(&self, message: NOTIFY_ICON_MESSAGE) -> windows::core::Result<()> {
397        (unsafe { Shell_NotifyIconW(message, &self.data) } != FALSE)
398            .then_some(())
399            .ok_or_else(windows::core::Error::from_win32)
400    }
401
402    /// Adds the notification icon to the system tray.
403    ///
404    /// This method sends a [NIM_ADD] message to add the notification icon to
405    /// the notification area. The icon will appear in the system tray.
406    ///
407    /// # Returns
408    ///
409    /// A [`windows::core::Result<()>`] indicating success or failure
410    ///
411    /// # Errors
412    ///
413    /// Returns an error if the add operation fails
414    pub fn notify_add(&self) -> windows::core::Result<()> {
415        self.notify(NIM_ADD)
416    }
417
418    /// Removes the notification icon from the system tray.
419    ///
420    /// This method sends a [NIM_DELETE] message to remove the notification icon
421    /// from the notification area. The icon will disappear from the system
422    /// tray.
423    ///
424    /// # Returns
425    ///
426    /// A [`windows::core::Result<()>`] indicating success or failure
427    ///
428    /// # Errors
429    ///
430    /// Returns an error if the delete operation fails
431    pub fn notify_delete(&self) -> windows::core::Result<()> {
432        self.notify(NIM_DELETE)
433    }
434
435    /// Modifies an existing notification icon in the system tray.
436    ///
437    /// This method sends a [NIM_MODIFY] message to update the properties of an
438    /// existing notification icon. Only the fields that have their
439    /// corresponding flags set will be updated.
440    ///
441    /// # Returns
442    ///
443    /// A [`windows::core::Result<()>`] indicating success or failure
444    ///
445    /// # Errors
446    ///
447    /// Returns an error if the modify operation fails
448    pub fn notify_modify(&self) -> windows::core::Result<()> {
449        self.notify(NIM_MODIFY)
450    }
451
452    /// Sets focus to the notification icon.
453    ///
454    /// This method sends a [NIM_SETFOCUS] message to give focus to the
455    /// notification icon, which can be useful for accessibility purposes.
456    ///
457    /// # Returns
458    ///
459    /// A [`windows::core::Result<()>`] indicating success or failure
460    ///
461    /// # Errors
462    ///
463    /// Returns an error if the set focus operation fails
464    pub fn notify_set_focus(&self) -> windows::core::Result<()> {
465        self.notify(NIM_SETFOCUS)
466    }
467
468    /// Sets the version of the notification icon interface.
469    ///
470    /// This method sends a [NIM_SETVERSION] message to specify which version of
471    /// the notification icon interface to use. This affects the behavior of
472    /// certain notification features.
473    ///
474    /// # Returns
475    ///
476    /// A [`windows::core::Result<()>`] indicating success or failure
477    ///
478    /// # Errors
479    ///
480    /// Returns an error if the set version operation fails
481    pub fn notify_set_version(&self) -> windows::core::Result<()> {
482        self.notify(NIM_SETVERSION)
483    }
484}