windows_helpers/
win32_app.rs

1#![cfg(feature = "win32_app")]
2
3//! Helpers to simplify some tedious aspects of a Win32 application.
4//!
5//! They allow you to combine them with the regular Windows API, and so can be undermined, which is why they haven't been implemented hiding underlying complexity too much. E.g., a `Window` could be destoyed via its `HWND` while the struct continues to exist.
6//!
7//! The types automatically free resources they allocated on drop, but you have to pay attention to the correct drop order, e.g., in structs. Since fields are dropped from top to bottom, specify the higher-level resources last.
8//!
9//! Activate the feature `windows_<version>_win32_app` (available from `windows` v0.52 onwards).
10
11pub mod error;
12pub mod msg_loop;
13pub mod tray_icon;
14pub mod window;
15
16mod app;
17
18pub use app::*;
19
20#[cfg(all(test, feature = "windows_latest_compatible_all"))]
21mod tests {
22    use super::{
23        error::{try_or_quit_now, try_or_set_app_error, try_then_favor_app_error},
24        tray_icon::SimplifiedTrayIconMsg,
25        window::translate_timer_msg,
26        AppLike, InvisibleWindowAppHelper,
27    };
28    use crate::{
29        cell::ReentrantRefCell,
30        core::CheckNumberError,
31        win32_app::{
32            msg_loop,
33            tray_icon::{BalloonIcon, TrayIcon},
34        },
35        windows, Null, ResGuard,
36    };
37    use anyhow::anyhow;
38    use std::rc::Rc;
39    use windows::Win32::{
40        Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, WPARAM},
41        UI::WindowsAndMessaging::{
42            DestroyWindow, KillTimer, LoadIconW, PostQuitMessage, SetTimer, HICON, IDI_APPLICATION,
43            WM_APP, WM_CREATE, WM_DESTROY, WM_TIMER,
44        },
45    };
46
47    #[ignore]
48    #[test]
49    fn tray_icon_app() -> anyhow::Result<()> {
50        try_then_favor_app_error(|| -> anyhow::Result<()> {
51            let (_app_helper, _app) = App::new()?;
52            msg_loop::run()?;
53
54            Ok(())
55        })
56        .map_err(|e| anyhow!(e))
57    }
58
59    struct App {
60        tray_icon: TrayIcon,
61        _tray_h_icon: ResGuard<HICON>,
62    }
63
64    impl App {
65        const TRAY_ICON_MSG: u32 = WM_APP;
66        const TIMER_ID: usize = 1;
67    }
68
69    impl<'a> AppLike<InvisibleWindowAppHelper<'a>> for App {
70        fn new() -> windows::core::Result<(
71            InvisibleWindowAppHelper<'a>,
72            Rc<ReentrantRefCell<Option<Self>>>,
73        )> {
74            Ok(unsafe { InvisibleWindowAppHelper::make_app()? })
75        }
76
77        fn startup_wnd_proc(
78            hwnd: HWND,
79            msg_id: u32,
80            _wparam: WPARAM,
81            _lparam: LPARAM,
82        ) -> (Option<Self>, Option<LRESULT>) {
83            if msg_id == WM_CREATE {
84                let app = try_or_set_app_error(|| -> windows::core::Result<_> {
85                    let mut tray_icon = TrayIcon::with_primary_id(hwnd, Some(Self::TRAY_ICON_MSG))?;
86
87                    let tray_h_icon = ResGuard::with_acq_and_destroy_icon(|| unsafe {
88                        LoadIconW(HINSTANCE::NULL, IDI_APPLICATION)
89                    })?;
90                    unsafe { tray_icon.set_icon(*tray_h_icon)? };
91
92                    tray_icon.show(true)?;
93
94                    unsafe {
95                        SetTimer(hwnd, Self::TIMER_ID, 1500 /*ms*/, None)
96                    }
97                    .nonzero_or_win32_err()?;
98
99                    Ok(Self {
100                        tray_icon,
101                        _tray_h_icon: tray_h_icon,
102                    })
103                });
104
105                if let Some(app) = app {
106                    (Some(app), Some(LRESULT(0)))
107                } else {
108                    // Make window creation fail.
109                    (None, Some(LRESULT(-1)))
110                }
111            } else {
112                (None, None)
113            }
114        }
115
116        fn wnd_proc(
117            &mut self,
118            hwnd: HWND,
119            msg_id: u32,
120            wparam: WPARAM,
121            lparam: LPARAM,
122        ) -> Option<LRESULT> {
123            match msg_id {
124                WM_TIMER => {
125                    let msg = unsafe { translate_timer_msg(wparam, lparam) };
126
127                    if msg.timer_id == Self::TIMER_ID {
128                        try_or_quit_now(|| -> windows::core::Result<()> {
129                            unsafe { KillTimer(hwnd, Self::TIMER_ID)? };
130
131                            self.tray_icon.show_balloon(
132                                BalloonIcon::User,
133                                Some("Title"),
134                                "This is the notification message.",
135                                false,
136                                true,
137                                true,
138                            )?;
139
140                            self.tray_icon.set_tooltip(Some("Click the icon to exit"))?;
141
142                            Ok(())
143                        });
144                    }
145
146                    Some(LRESULT(0))
147                }
148                Self::TRAY_ICON_MSG => {
149                    match self
150                        .tray_icon
151                        .simplifying_translate_window_msg(wparam, lparam)
152                    {
153                        SimplifiedTrayIconMsg::Activated => {
154                            try_or_quit_now(|| -> windows::core::Result<_> {
155                                self.tray_icon.delete()?;
156                                self.reenter_wnd_proc(|_| unsafe { DestroyWindow(hwnd) })
157                            });
158                            Some(LRESULT(0))
159                        }
160                        _ => None,
161                    }
162                }
163                WM_DESTROY => {
164                    unsafe { PostQuitMessage(0) };
165                    Some(LRESULT(0))
166                }
167                _ => None,
168            }
169        }
170    }
171}