windows_helpers/
win32_app.rs1#![cfg(feature = "win32_app")]
2
3pub 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 , 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 (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}