winapi_easy/ui/
messaging.rs1use std::io;
4
5use windows::Win32::Foundation::{
6 HWND,
7 LPARAM,
8 LRESULT,
9 WPARAM,
10};
11use windows::Win32::UI::Shell::NIN_SELECT;
12use windows::Win32::UI::WindowsAndMessaging::{
13 DefWindowProcW,
14 GetMessagePos,
15 PostMessageW,
16 HMENU,
17 SIZE_MINIMIZED,
18 WM_APP,
19 WM_CLOSE,
20 WM_CONTEXTMENU,
21 WM_DESTROY,
22 WM_MENUCOMMAND,
23 WM_SIZE,
24};
25
26use crate::internal::catch_unwind_and_abort;
27use crate::internal::windows_missing::*;
28use crate::messaging::ThreadMessageLoop;
29use crate::ui::menu::MenuHandle;
30use crate::ui::{
31 Point,
32 WindowHandle,
33};
34
35#[derive(Copy, Clone, Default, Debug)]
37pub enum ListenerAnswer {
38 #[default]
40 CallDefaultHandler,
41 MessageProcessed,
43}
44
45impl ListenerAnswer {
46 fn to_raw_lresult(self) -> Option<LRESULT> {
47 match self {
48 ListenerAnswer::CallDefaultHandler => None,
49 ListenerAnswer::MessageProcessed => Some(LRESULT(0)),
50 }
51 }
52}
53
54pub trait WindowMessageListener {
66 #[allow(unused_variables)]
68 #[inline(always)]
69 fn handle_menu_command(&self, window: &WindowHandle, selected_item_id: u32) {}
70 #[allow(unused_variables)]
72 #[inline(always)]
73 fn handle_window_minimized(&self, window: &WindowHandle) {}
74 #[allow(unused_variables)]
76 #[inline(always)]
77 fn handle_window_close(&self, window: &WindowHandle) -> ListenerAnswer {
78 Default::default()
79 }
80 #[allow(unused_variables)]
82 #[inline(always)]
83 fn handle_window_destroy(&self, window: &WindowHandle) {}
84 #[allow(unused_variables)]
86 #[inline(always)]
87 fn handle_notification_icon_select(&self, icon_id: u16, xy_coords: Point) {}
88 #[allow(unused_variables)]
90 #[inline(always)]
91 fn handle_notification_icon_context_select(&self, icon_id: u16, xy_coords: Point) {}
92 #[allow(unused_variables)]
94 #[inline(always)]
95 fn handle_custom_user_message(
96 &self,
97 window: &WindowHandle,
98 message_id: u8,
99 w_param: WPARAM,
100 l_param: LPARAM,
101 ) {
102 }
103}
104
105#[derive(Copy, Clone, Default, Debug)]
107pub struct EmptyWindowMessageListener;
108
109impl WindowMessageListener for EmptyWindowMessageListener {}
110
111#[derive(Copy, Clone, Debug)]
112pub(crate) struct RawMessage {
113 pub(crate) message: u32,
114 pub(crate) w_param: WPARAM,
115 pub(crate) l_param: LPARAM,
116}
117
118impl RawMessage {
119 const STR_MSG_RANGE_START: u32 = 0xC000;
124
125 pub(crate) const ID_APP_WAKEUP_MSG: u32 = Self::STR_MSG_RANGE_START - 1;
126 pub(crate) const ID_NOTIFICATION_ICON_MSG: u32 = Self::STR_MSG_RANGE_START - 2;
127
128 pub(crate) fn dispatch_to_message_listener<WML: WindowMessageListener>(
129 self,
130 window: WindowHandle,
131 listener: &WML,
132 ) -> Option<LRESULT> {
133 Self::post_loop_wakeup_message().unwrap();
136 let mut call_message_loop_callback = true;
137 let result = match self.message {
138 value if value >= WM_APP && value <= WM_APP + (u32::from(u8::MAX)) => {
139 listener.handle_custom_user_message(
140 &window,
141 (self.message - WM_APP).try_into().unwrap(),
142 self.w_param,
143 self.l_param,
144 );
145 None
146 }
147 Self::ID_NOTIFICATION_ICON_MSG => {
148 let icon_id =
149 HIWORD(u32::try_from(self.l_param.0).expect("Icon ID conversion failed"));
150 let event_code: u32 =
151 LOWORD(u32::try_from(self.l_param.0).expect("Event code conversion failed"))
152 .into();
153 let xy_coords = {
154 let raw_position = unsafe { GetMessagePos() };
160 get_param_xy_coords(raw_position)
161 };
162 match event_code {
163 NIN_SELECT | NIN_KEYSELECT => {
165 listener.handle_notification_icon_select(icon_id, xy_coords)
166 }
167 WM_CONTEXTMENU => {
169 listener.handle_notification_icon_context_select(icon_id, xy_coords)
170 }
171 _ => (),
172 }
173 None
174 }
175 WM_MENUCOMMAND => {
176 let menu_handle =
177 MenuHandle::from_maybe_null(HMENU(self.l_param.0 as *mut std::ffi::c_void))
178 .expect("Menu handle should not be null here");
179 let item_id = menu_handle
180 .get_item_id(self.w_param.0.try_into().unwrap())
181 .unwrap();
182 listener.handle_menu_command(&window, item_id);
183 None
184 }
185 WM_SIZE => {
186 if self.w_param.0 == SIZE_MINIMIZED.try_into().unwrap() {
187 listener.handle_window_minimized(&window);
188 }
189 None
190 }
191 WM_CLOSE => listener.handle_window_close(&window).to_raw_lresult(),
192 WM_DESTROY => {
193 listener.handle_window_destroy(&window);
194 None
195 }
196 _ => {
197 call_message_loop_callback = false;
198 None
199 }
200 };
201 if call_message_loop_callback {
202 ThreadMessageLoop::ENABLE_CALLBACK_ONCE.with(|x| x.set(true));
203 }
204 result
205 }
206
207 fn post_to_queue(&self, window: Option<&WindowHandle>) -> io::Result<()> {
211 unsafe {
212 PostMessageW(
213 window.map(|x| &x.raw_handle),
214 self.message,
215 self.w_param,
216 self.l_param,
217 )?;
218 }
219 Ok(())
220 }
221
222 fn post_loop_wakeup_message() -> io::Result<()> {
223 let wakeup_message = RawMessage {
224 message: Self::ID_APP_WAKEUP_MSG,
225 w_param: WPARAM(0),
226 l_param: LPARAM(0),
227 };
228 wakeup_message.post_to_queue(None)
229 }
230}
231
232pub(crate) unsafe extern "system" fn generic_window_proc<WML>(
233 h_wnd: HWND,
234 message: u32,
235 w_param: WPARAM,
236 l_param: LPARAM,
237) -> LRESULT
238where
239 WML: WindowMessageListener,
240{
241 let call = move || {
242 let window = WindowHandle::from_maybe_null(h_wnd)
243 .expect("Window handle given to window procedure should never be NULL");
244
245 let raw_message = RawMessage {
246 message,
247 w_param,
248 l_param,
249 };
250
251 let listener_result = window.get_user_data_ptr::<WML>().and_then(|listener_ptr| {
254 raw_message.dispatch_to_message_listener(window, listener_ptr.as_ref())
255 });
256
257 if let Some(l_result) = listener_result {
258 l_result
259 } else {
260 DefWindowProcW(h_wnd, message, w_param, l_param)
261 }
262 };
263 catch_unwind_and_abort(call)
264}
265
266fn get_param_xy_coords(param: u32) -> Point {
267 let param = LPARAM(param.try_into().unwrap());
268 Point {
269 x: GET_X_LPARAM(param),
270 y: GET_Y_LPARAM(param),
271 }
272}