1use std::{
4 io,
5 ptr,
6};
7
8use windows::Win32::Foundation::{
9 HWND,
10 LPARAM,
11 LRESULT,
12 WPARAM,
13};
14use windows::Win32::UI::Shell::NIN_SELECT;
15use windows::Win32::UI::WindowsAndMessaging::{
16 DefWindowProcW,
17 GetMessagePos,
18 HMENU,
19 PostMessageW,
20 SIZE_MINIMIZED,
21 WM_APP,
22 WM_CLOSE,
23 WM_COMMAND,
24 WM_CONTEXTMENU,
25 WM_DESTROY,
26 WM_MENUCOMMAND,
27 WM_SIZE,
28 WM_TIMER,
29};
30
31use crate::internal::windows_missing::{
32 GET_X_LPARAM,
33 GET_Y_LPARAM,
34 HIWORD,
35 LOWORD,
36 NIN_KEYSELECT,
37};
38use crate::internal::{
39 ResultExt,
40 catch_unwind_and_abort,
41};
42use crate::ui::menu::MenuHandle;
43use crate::ui::{
44 Point,
45 WindowHandle,
46};
47
48#[derive(Clone, PartialEq, Debug)]
49pub struct ListenerMessage {
50 pub window_handle: WindowHandle,
51 pub variant: ListenerMessageVariant,
52}
53
54impl ListenerMessage {
55 fn from_known_raw_message(
56 raw_message: RawMessage,
57 window_handle: WindowHandle,
58 ) -> Option<Self> {
59 let variant = match raw_message.message {
60 value if value >= WM_APP && value <= WM_APP + (u32::from(u8::MAX)) => {
61 ListenerMessageVariant::CustomUserMessage(CustomUserMessage {
62 message_id: (raw_message.message - WM_APP)
63 .try_into()
64 .expect("Message ID should be in u8 range"),
65 w_param: raw_message.w_param.0,
66 l_param: raw_message.l_param.0,
67 })
68 .into()
69 }
70 RawMessage::ID_NOTIFICATION_ICON_MSG => {
71 let icon_id = HIWORD(
72 u32::try_from(raw_message.l_param.0).expect("Icon ID conversion failed"),
73 );
74 let event_code: u32 = LOWORD(
75 u32::try_from(raw_message.l_param.0).expect("Event code conversion failed"),
76 )
77 .into();
78 let xy_coords = {
79 let raw_position = unsafe { GetMessagePos() };
85 get_param_xy_coords(raw_position)
86 };
87 match event_code {
88 NIN_SELECT | NIN_KEYSELECT => {
90 ListenerMessageVariant::NotificationIconSelect { icon_id, xy_coords }.into()
91 }
92 WM_CONTEXTMENU => {
94 ListenerMessageVariant::NotificationIconContextSelect { icon_id, xy_coords }
95 .into()
96 }
97 _ => None,
98 }
99 }
100 WM_COMMAND if HIWORD(u32::try_from(raw_message.w_param.0).unwrap()) == 0 => {
101 ListenerMessageVariant::MenuCommand {
103 selected_item_id: u32::from(LOWORD(
104 u32::try_from(raw_message.w_param.0).unwrap(),
105 )),
106 }
107 .into()
108 }
109 WM_MENUCOMMAND => {
110 let menu_handle = MenuHandle::from_maybe_null(HMENU(
111 ptr::with_exposed_provenance_mut(raw_message.l_param.0.cast_unsigned()),
112 ))
113 .expect("Menu handle should not be null here");
114 let selected_item_id = menu_handle
115 .get_item_id(raw_message.w_param.0.try_into().unwrap())
116 .unwrap();
117 ListenerMessageVariant::MenuCommand { selected_item_id }.into()
118 }
119 WM_SIZE => {
120 if raw_message.w_param.0 == SIZE_MINIMIZED.try_into().unwrap() {
121 ListenerMessageVariant::WindowMinimized.into()
122 } else {
123 None
124 }
125 }
126 WM_TIMER => ListenerMessageVariant::Timer {
127 timer_id: raw_message.w_param.0,
128 }
129 .into(),
130 WM_CLOSE => ListenerMessageVariant::WindowClose.into(),
131 WM_DESTROY => {
132 window_handle
134 .set_menu(None)
135 .unwrap_or_default_and_print_error();
136 ListenerMessageVariant::WindowDestroy.into()
137 }
138 _ => None,
139 };
140 variant.map(|variant| ListenerMessage {
141 window_handle,
142 variant,
143 })
144 }
145}
146
147#[derive(Clone, PartialEq, Debug)]
148pub enum ListenerMessageVariant {
149 MenuCommand {
150 selected_item_id: u32,
151 },
152 WindowMinimized,
153 WindowClose,
154 WindowDestroy,
155 NotificationIconSelect {
156 icon_id: u16,
157 xy_coords: Point,
158 },
159 NotificationIconContextSelect {
160 icon_id: u16,
161 xy_coords: Point,
162 },
163 Timer {
164 timer_id: usize,
165 },
166 CustomUserMessage(CustomUserMessage),
170}
171
172#[derive(Copy, Clone, Default, Debug)]
174pub enum ListenerAnswer {
175 #[default]
177 CallDefaultHandler,
178 StopMessageProcessing,
180}
181
182impl ListenerAnswer {
183 fn to_raw_lresult(self) -> Option<LRESULT> {
184 match self {
185 ListenerAnswer::CallDefaultHandler => None,
186 ListenerAnswer::StopMessageProcessing => Some(LRESULT(0)),
187 }
188 }
189}
190
191pub(crate) type WmlOpaqueClosure<'a> = Box<dyn FnMut(&ListenerMessage) -> ListenerAnswer + 'a>;
192
193#[derive(Copy, Clone, Debug)]
194pub(crate) struct RawMessage {
195 pub(crate) message: u32,
196 pub(crate) w_param: WPARAM,
197 pub(crate) l_param: LPARAM,
198}
199
200impl RawMessage {
201 const STR_MSG_RANGE_START: u32 = 0xC000;
206
207 pub(crate) const ID_WINDOW_PROC_MSG: u32 = Self::STR_MSG_RANGE_START - 1;
208 pub(crate) const ID_APP_WAKEUP_MSG: u32 = Self::STR_MSG_RANGE_START - 2;
209 pub(crate) const ID_NOTIFICATION_ICON_MSG: u32 = Self::STR_MSG_RANGE_START - 3;
210
211 pub(crate) fn post_to_queue(&self, window: Option<WindowHandle>) -> io::Result<()> {
215 unsafe {
216 PostMessageW(
217 window.map(Into::into),
218 self.message,
219 self.w_param,
220 self.l_param,
221 )?;
222 }
223 Ok(())
224 }
225
226 fn post_window_proc_message(listener_message: ListenerMessage) -> io::Result<()> {
227 let ptr_usize = Box::into_raw(Box::new(listener_message)).expose_provenance();
228 let window_proc_message = RawMessage {
229 message: Self::ID_WINDOW_PROC_MSG,
230 w_param: WPARAM(ptr_usize),
231 l_param: LPARAM(0),
232 };
233 window_proc_message.post_to_queue(None)
234 }
235
236 #[expect(dead_code)]
237 fn post_loop_wakeup_message() -> io::Result<()> {
238 let wakeup_message = RawMessage {
239 message: Self::ID_APP_WAKEUP_MSG,
240 w_param: WPARAM(0),
241 l_param: LPARAM(0),
242 };
243 wakeup_message.post_to_queue(None)
244 }
245}
246
247impl From<CustomUserMessage> for RawMessage {
248 fn from(custom_message: CustomUserMessage) -> Self {
249 RawMessage {
250 message: WM_APP + u32::from(custom_message.message_id),
251 w_param: WPARAM(custom_message.w_param),
252 l_param: LPARAM(custom_message.l_param),
253 }
254 }
255}
256
257#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
258pub struct CustomUserMessage {
259 pub message_id: u8,
260 pub w_param: usize,
261 pub l_param: isize,
262}
263
264pub(crate) unsafe extern "system" fn generic_window_proc(
265 h_wnd: HWND,
266 message: u32,
267 w_param: WPARAM,
268 l_param: LPARAM,
269) -> LRESULT {
270 let call = move || {
271 let window = WindowHandle::from_maybe_null(h_wnd)
272 .expect("Window handle given to window procedure should never be NULL");
273
274 let raw_message = RawMessage {
275 message,
276 w_param,
277 l_param,
278 };
279
280 let listener_message = ListenerMessage::from_known_raw_message(raw_message, window);
281 let listener_result = unsafe { window.get_user_data_ptr::<WmlOpaqueClosure>() }.and_then(
284 |mut listener_ptr| {
285 if let Some(known_listener_message) = &listener_message {
286 (unsafe { listener_ptr.as_mut().as_mut() })(known_listener_message)
287 .to_raw_lresult()
288 } else {
289 ListenerAnswer::default().to_raw_lresult()
290 }
291 },
292 );
293 if let Some(known_listener_message) = listener_message {
294 RawMessage::post_window_proc_message(known_listener_message)
298 .expect("Cannot send internal window procedure message");
299 }
300
301 if let Some(l_result) = listener_result {
302 l_result
303 } else {
304 unsafe { DefWindowProcW(h_wnd, message, w_param, l_param) }
305 }
306 };
307 catch_unwind_and_abort(call)
308}
309
310fn get_param_xy_coords(param: u32) -> Point {
311 let param = LPARAM(param.try_into().unwrap());
312 Point {
313 x: GET_X_LPARAM(param),
314 y: GET_Y_LPARAM(param),
315 }
316}