1use std::rc::Rc;
2
3use ::util::ResultExt;
4use anyhow::Context;
5use windows::Win32::{
6 Foundation::*,
7 Graphics::Gdi::*,
8 System::SystemServices::*,
9 UI::{
10 HiDpi::*,
11 Input::{Ime::*, KeyboardAndMouse::*},
12 WindowsAndMessaging::*,
13 },
14};
15
16use crate::*;
17
18pub(crate) const CURSOR_STYLE_CHANGED: u32 = WM_USER + 1;
19pub(crate) const CLOSE_ONE_WINDOW: u32 = WM_USER + 2;
20
21const SIZE_MOVE_LOOP_TIMER_ID: usize = 1;
22
23pub(crate) fn handle_msg(
24 handle: HWND,
25 msg: u32,
26 wparam: WPARAM,
27 lparam: LPARAM,
28 state_ptr: Rc<WindowsWindowStatePtr>,
29) -> LRESULT {
30 let handled = match msg {
31 WM_ACTIVATE => handle_activate_msg(handle, wparam, state_ptr),
32 WM_CREATE => handle_create_msg(handle, state_ptr),
33 WM_MOVE => handle_move_msg(handle, lparam, state_ptr),
34 WM_SIZE => handle_size_msg(lparam, state_ptr),
35 WM_ENTERSIZEMOVE | WM_ENTERMENULOOP => handle_size_move_loop(handle),
36 WM_EXITSIZEMOVE | WM_EXITMENULOOP => handle_size_move_loop_exit(handle),
37 WM_TIMER => handle_timer_msg(handle, wparam, state_ptr),
38 WM_NCCALCSIZE => handle_calc_client_size(handle, wparam, lparam, state_ptr),
39 WM_DPICHANGED => handle_dpi_changed_msg(handle, wparam, lparam, state_ptr),
40 WM_DISPLAYCHANGE => handle_display_change_msg(handle, state_ptr),
41 WM_NCHITTEST => handle_hit_test_msg(handle, msg, wparam, lparam, state_ptr),
42 WM_PAINT => handle_paint_msg(handle, state_ptr),
43 WM_CLOSE => handle_close_msg(state_ptr),
44 WM_DESTROY => handle_destroy_msg(handle, state_ptr),
45 WM_MOUSEMOVE => handle_mouse_move_msg(lparam, wparam, state_ptr),
46 WM_NCMOUSEMOVE => handle_nc_mouse_move_msg(handle, lparam, state_ptr),
47 WM_NCLBUTTONDOWN => {
48 handle_nc_mouse_down_msg(handle, MouseButton::Left, wparam, lparam, state_ptr)
49 }
50 WM_NCRBUTTONDOWN => {
51 handle_nc_mouse_down_msg(handle, MouseButton::Right, wparam, lparam, state_ptr)
52 }
53 WM_NCMBUTTONDOWN => {
54 handle_nc_mouse_down_msg(handle, MouseButton::Middle, wparam, lparam, state_ptr)
55 }
56 WM_NCLBUTTONUP => {
57 handle_nc_mouse_up_msg(handle, MouseButton::Left, wparam, lparam, state_ptr)
58 }
59 WM_NCRBUTTONUP => {
60 handle_nc_mouse_up_msg(handle, MouseButton::Right, wparam, lparam, state_ptr)
61 }
62 WM_NCMBUTTONUP => {
63 handle_nc_mouse_up_msg(handle, MouseButton::Middle, wparam, lparam, state_ptr)
64 }
65 WM_LBUTTONDOWN => handle_mouse_down_msg(MouseButton::Left, lparam, state_ptr),
66 WM_RBUTTONDOWN => handle_mouse_down_msg(MouseButton::Right, lparam, state_ptr),
67 WM_MBUTTONDOWN => handle_mouse_down_msg(MouseButton::Middle, lparam, state_ptr),
68 WM_XBUTTONDOWN => handle_xbutton_msg(wparam, lparam, handle_mouse_down_msg, state_ptr),
69 WM_LBUTTONUP => handle_mouse_up_msg(MouseButton::Left, lparam, state_ptr),
70 WM_RBUTTONUP => handle_mouse_up_msg(MouseButton::Right, lparam, state_ptr),
71 WM_MBUTTONUP => handle_mouse_up_msg(MouseButton::Middle, lparam, state_ptr),
72 WM_XBUTTONUP => handle_xbutton_msg(wparam, lparam, handle_mouse_up_msg, state_ptr),
73 WM_MOUSEWHEEL => handle_mouse_wheel_msg(handle, wparam, lparam, state_ptr),
74 WM_MOUSEHWHEEL => handle_mouse_horizontal_wheel_msg(handle, wparam, lparam, state_ptr),
75 WM_SYSKEYDOWN => handle_syskeydown_msg(wparam, lparam, state_ptr),
76 WM_SYSKEYUP => handle_syskeyup_msg(wparam, state_ptr),
77 WM_KEYDOWN => handle_keydown_msg(wparam, lparam, state_ptr),
78 WM_KEYUP => handle_keyup_msg(wparam, state_ptr),
79 WM_CHAR => handle_char_msg(wparam, lparam, state_ptr),
80 WM_IME_STARTCOMPOSITION => handle_ime_position(handle, state_ptr),
81 WM_IME_COMPOSITION => handle_ime_composition(handle, lparam, state_ptr),
82 WM_SETCURSOR => handle_set_cursor(lparam, state_ptr),
83 WM_SETTINGCHANGE => handle_system_settings_changed(state_ptr),
84 CURSOR_STYLE_CHANGED => handle_cursor_changed(lparam, state_ptr),
85 _ => None,
86 };
87 if let Some(n) = handled {
88 LRESULT(n)
89 } else {
90 unsafe { DefWindowProcW(handle, msg, wparam, lparam) }
91 }
92}
93
94fn handle_move_msg(
95 handle: HWND,
96 lparam: LPARAM,
97 state_ptr: Rc<WindowsWindowStatePtr>,
98) -> Option<isize> {
99 let x = lparam.signed_loword() as f32;
100 let y = lparam.signed_hiword() as f32;
101 let mut lock = state_ptr.state.borrow_mut();
102 lock.origin = point(px(x), px(y));
103 let size = lock.physical_size;
104 let center_x = x + size.width.0 / 2.;
105 let center_y = y + size.height.0 / 2.;
106 let monitor_bounds = lock.display.bounds();
107 if center_x < monitor_bounds.left().0
108 || center_x > monitor_bounds.right().0
109 || center_y < monitor_bounds.top().0
110 || center_y > monitor_bounds.bottom().0
111 {
112 // center of the window may have moved to another monitor
113 let monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) };
114 // minimize the window can trigger this event too, in this case,
115 // monitor is invalid, we do nothing.
116 if !monitor.is_invalid() && lock.display.handle != monitor {
117 // we will get the same monitor if we only have one
118 lock.display = WindowsDisplay::new_with_handle(monitor);
119 }
120 }
121 if let Some(mut callback) = lock.callbacks.moved.take() {
122 drop(lock);
123 callback();
124 state_ptr.state.borrow_mut().callbacks.moved = Some(callback);
125 }
126 Some(0)
127}
128
129fn handle_size_msg(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
130 let width = lparam.loword().max(1) as i32;
131 let height = lparam.hiword().max(1) as i32;
132 let mut lock = state_ptr.state.borrow_mut();
133 let new_size = size(DevicePixels(width), DevicePixels(height));
134 let scale_factor = lock.scale_factor;
135 lock.renderer.update_drawable_size(new_size);
136 let new_size = new_size.to_pixels(lock.scale_factor);
137 lock.physical_size = new_size;
138 if let Some(mut callback) = lock.callbacks.resize.take() {
139 drop(lock);
140 callback(new_size, scale_factor);
141 state_ptr.state.borrow_mut().callbacks.resize = Some(callback);
142 }
143 Some(0)
144}
145
146fn handle_size_move_loop(handle: HWND) -> Option<isize> {
147 unsafe {
148 let ret = SetTimer(handle, SIZE_MOVE_LOOP_TIMER_ID, USER_TIMER_MINIMUM, None);
149 if ret == 0 {
150 log::error!(
151 "unable to create timer: {}",
152 std::io::Error::last_os_error()
153 );
154 }
155 }
156 None
157}
158
159fn handle_size_move_loop_exit(handle: HWND) -> Option<isize> {
160 unsafe {
161 KillTimer(handle, SIZE_MOVE_LOOP_TIMER_ID).log_err();
162 }
163 None
164}
165
166fn handle_timer_msg(
167 handle: HWND,
168 wparam: WPARAM,
169 state_ptr: Rc<WindowsWindowStatePtr>,
170) -> Option<isize> {
171 if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID {
172 handle_paint_msg(handle, state_ptr)
173 } else {
174 None
175 }
176}
177
178fn handle_paint_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
179 let mut lock = state_ptr.state.borrow_mut();
180 if let Some(mut request_frame) = lock.callbacks.request_frame.take() {
181 drop(lock);
182 request_frame();
183 state_ptr.state.borrow_mut().callbacks.request_frame = Some(request_frame);
184 }
185 unsafe { ValidateRect(handle, None).ok().log_err() };
186 Some(0)
187}
188
189fn handle_close_msg(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
190 let mut lock = state_ptr.state.borrow_mut();
191 if let Some(mut callback) = lock.callbacks.should_close.take() {
192 drop(lock);
193 let should_close = callback();
194 state_ptr.state.borrow_mut().callbacks.should_close = Some(callback);
195 if should_close {
196 None
197 } else {
198 Some(0)
199 }
200 } else {
201 None
202 }
203}
204
205fn handle_destroy_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
206 let callback = {
207 let mut lock = state_ptr.state.borrow_mut();
208 lock.callbacks.close.take()
209 };
210 if let Some(callback) = callback {
211 callback();
212 }
213 unsafe {
214 PostMessageW(None, CLOSE_ONE_WINDOW, None, LPARAM(handle.0)).log_err();
215 }
216 Some(0)
217}
218
219fn handle_mouse_move_msg(
220 lparam: LPARAM,
221 wparam: WPARAM,
222 state_ptr: Rc<WindowsWindowStatePtr>,
223) -> Option<isize> {
224 let mut lock = state_ptr.state.borrow_mut();
225 if let Some(mut callback) = lock.callbacks.input.take() {
226 let scale_factor = lock.scale_factor;
227 drop(lock);
228 let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
229 flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
230 flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),
231 flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),
232 flags if flags.contains(MK_XBUTTON1) => {
233 Some(MouseButton::Navigate(NavigationDirection::Back))
234 }
235 flags if flags.contains(MK_XBUTTON2) => {
236 Some(MouseButton::Navigate(NavigationDirection::Forward))
237 }
238 _ => None,
239 };
240 let x = lparam.signed_loword() as f32;
241 let y = lparam.signed_hiword() as f32;
242 let event = MouseMoveEvent {
243 position: logical_point(x, y, scale_factor),
244 pressed_button,
245 modifiers: current_modifiers(),
246 };
247 let result = if callback(PlatformInput::MouseMove(event)).default_prevented {
248 Some(0)
249 } else {
250 Some(1)
251 };
252 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
253 return result;
254 }
255 Some(1)
256}
257
258fn handle_syskeydown_msg(
259 wparam: WPARAM,
260 lparam: LPARAM,
261 state_ptr: Rc<WindowsWindowStatePtr>,
262) -> Option<isize> {
263 // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
264 // shortcuts.
265 let Some(keystroke) = parse_syskeydown_msg_keystroke(wparam) else {
266 return None;
267 };
268 let mut lock = state_ptr.state.borrow_mut();
269 let Some(mut func) = lock.callbacks.input.take() else {
270 return None;
271 };
272 drop(lock);
273 let event = KeyDownEvent {
274 keystroke,
275 is_held: lparam.0 & (0x1 << 30) > 0,
276 };
277 let result = if func(PlatformInput::KeyDown(event)).default_prevented {
278 Some(0)
279 } else {
280 None
281 };
282 state_ptr.state.borrow_mut().callbacks.input = Some(func);
283
284 result
285}
286
287fn handle_syskeyup_msg(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
288 // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
289 // shortcuts.
290 let Some(keystroke) = parse_syskeydown_msg_keystroke(wparam) else {
291 return None;
292 };
293 let mut lock = state_ptr.state.borrow_mut();
294 let Some(mut func) = lock.callbacks.input.take() else {
295 return None;
296 };
297 drop(lock);
298 let event = KeyUpEvent { keystroke };
299 let result = if func(PlatformInput::KeyUp(event)).default_prevented {
300 Some(0)
301 } else {
302 Some(1)
303 };
304 state_ptr.state.borrow_mut().callbacks.input = Some(func);
305
306 result
307}
308
309fn handle_keydown_msg(
310 wparam: WPARAM,
311 lparam: LPARAM,
312 state_ptr: Rc<WindowsWindowStatePtr>,
313) -> Option<isize> {
314 let Some(keystroke_or_modifier) = parse_keydown_msg_keystroke(wparam) else {
315 return Some(1);
316 };
317 let mut lock = state_ptr.state.borrow_mut();
318 let Some(mut func) = lock.callbacks.input.take() else {
319 return Some(1);
320 };
321 drop(lock);
322
323 let event = match keystroke_or_modifier {
324 KeystrokeOrModifier::Keystroke(keystroke) => PlatformInput::KeyDown(KeyDownEvent {
325 keystroke,
326 is_held: lparam.0 & (0x1 << 30) > 0,
327 }),
328 KeystrokeOrModifier::Modifier(modifiers) => {
329 PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers })
330 }
331 };
332
333 let result = if func(event).default_prevented {
334 Some(0)
335 } else {
336 Some(1)
337 };
338 state_ptr.state.borrow_mut().callbacks.input = Some(func);
339
340 result
341}
342
343fn handle_keyup_msg(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
344 let Some(keystroke_or_modifier) = parse_keydown_msg_keystroke(wparam) else {
345 return Some(1);
346 };
347 let mut lock = state_ptr.state.borrow_mut();
348 let Some(mut func) = lock.callbacks.input.take() else {
349 return Some(1);
350 };
351 drop(lock);
352
353 let event = match keystroke_or_modifier {
354 KeystrokeOrModifier::Keystroke(keystroke) => PlatformInput::KeyUp(KeyUpEvent { keystroke }),
355 KeystrokeOrModifier::Modifier(modifiers) => {
356 PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers })
357 }
358 };
359
360 let result = if func(event).default_prevented {
361 Some(0)
362 } else {
363 Some(1)
364 };
365 state_ptr.state.borrow_mut().callbacks.input = Some(func);
366
367 result
368}
369
370fn handle_char_msg(
371 wparam: WPARAM,
372 lparam: LPARAM,
373 state_ptr: Rc<WindowsWindowStatePtr>,
374) -> Option<isize> {
375 let Some(keystroke) = parse_char_msg_keystroke(wparam) else {
376 return Some(1);
377 };
378 let mut lock = state_ptr.state.borrow_mut();
379 let Some(mut func) = lock.callbacks.input.take() else {
380 return Some(1);
381 };
382 drop(lock);
383 let ime_key = keystroke.ime_key.clone();
384 let event = KeyDownEvent {
385 keystroke,
386 is_held: lparam.0 & (0x1 << 30) > 0,
387 };
388
389 let dispatch_event_result = func(PlatformInput::KeyDown(event));
390 let mut lock = state_ptr.state.borrow_mut();
391 lock.callbacks.input = Some(func);
392 if dispatch_event_result.default_prevented || !dispatch_event_result.propagate {
393 return Some(0);
394 }
395 let Some(ime_char) = ime_key else {
396 return Some(1);
397 };
398 let Some(mut input_handler) = lock.input_handler.take() else {
399 return Some(1);
400 };
401 drop(lock);
402 input_handler.replace_text_in_range(None, &ime_char);
403 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
404
405 Some(0)
406}
407
408fn handle_mouse_down_msg(
409 button: MouseButton,
410 lparam: LPARAM,
411 state_ptr: Rc<WindowsWindowStatePtr>,
412) -> Option<isize> {
413 let mut lock = state_ptr.state.borrow_mut();
414 if let Some(mut callback) = lock.callbacks.input.take() {
415 let x = lparam.signed_loword() as f32;
416 let y = lparam.signed_hiword() as f32;
417 let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
418 let click_count = lock.click_state.update(button, physical_point);
419 let scale_factor = lock.scale_factor;
420 drop(lock);
421
422 let event = MouseDownEvent {
423 button,
424 position: logical_point(x, y, scale_factor),
425 modifiers: current_modifiers(),
426 click_count,
427 first_mouse: false,
428 };
429 let result = if callback(PlatformInput::MouseDown(event)).default_prevented {
430 Some(0)
431 } else {
432 Some(1)
433 };
434 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
435
436 result
437 } else {
438 Some(1)
439 }
440}
441
442fn handle_mouse_up_msg(
443 button: MouseButton,
444 lparam: LPARAM,
445 state_ptr: Rc<WindowsWindowStatePtr>,
446) -> Option<isize> {
447 let mut lock = state_ptr.state.borrow_mut();
448 if let Some(mut callback) = lock.callbacks.input.take() {
449 let x = lparam.signed_loword() as f32;
450 let y = lparam.signed_hiword() as f32;
451 let click_count = lock.click_state.current_count;
452 let scale_factor = lock.scale_factor;
453 drop(lock);
454
455 let event = MouseUpEvent {
456 button,
457 position: logical_point(x, y, scale_factor),
458 modifiers: current_modifiers(),
459 click_count,
460 };
461 let result = if callback(PlatformInput::MouseUp(event)).default_prevented {
462 Some(0)
463 } else {
464 Some(1)
465 };
466 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
467
468 result
469 } else {
470 Some(1)
471 }
472}
473
474fn handle_xbutton_msg(
475 wparam: WPARAM,
476 lparam: LPARAM,
477 handler: impl Fn(MouseButton, LPARAM, Rc<WindowsWindowStatePtr>) -> Option<isize>,
478 state_ptr: Rc<WindowsWindowStatePtr>,
479) -> Option<isize> {
480 let nav_dir = match wparam.hiword() {
481 XBUTTON1 => NavigationDirection::Back,
482 XBUTTON2 => NavigationDirection::Forward,
483 _ => return Some(1),
484 };
485 handler(MouseButton::Navigate(nav_dir), lparam, state_ptr)
486}
487
488fn handle_mouse_wheel_msg(
489 handle: HWND,
490 wparam: WPARAM,
491 lparam: LPARAM,
492 state_ptr: Rc<WindowsWindowStatePtr>,
493) -> Option<isize> {
494 let mut lock = state_ptr.state.borrow_mut();
495 if let Some(mut callback) = lock.callbacks.input.take() {
496 let scale_factor = lock.scale_factor;
497 let wheel_scroll_lines = lock.system_settings.mouse_wheel_settings.wheel_scroll_lines;
498 drop(lock);
499 let wheel_distance =
500 (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_lines as f32;
501 let mut cursor_point = POINT {
502 x: lparam.signed_loword().into(),
503 y: lparam.signed_hiword().into(),
504 };
505 unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
506 let event = ScrollWheelEvent {
507 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
508 delta: ScrollDelta::Lines(Point {
509 x: 0.0,
510 y: wheel_distance,
511 }),
512 modifiers: current_modifiers(),
513 touch_phase: TouchPhase::Moved,
514 };
515 let result = if callback(PlatformInput::ScrollWheel(event)).default_prevented {
516 Some(0)
517 } else {
518 Some(1)
519 };
520 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
521
522 result
523 } else {
524 Some(1)
525 }
526}
527
528fn handle_mouse_horizontal_wheel_msg(
529 handle: HWND,
530 wparam: WPARAM,
531 lparam: LPARAM,
532 state_ptr: Rc<WindowsWindowStatePtr>,
533) -> Option<isize> {
534 let mut lock = state_ptr.state.borrow_mut();
535 if let Some(mut callback) = lock.callbacks.input.take() {
536 let scale_factor = lock.scale_factor;
537 let wheel_scroll_chars = lock.system_settings.mouse_wheel_settings.wheel_scroll_chars;
538 drop(lock);
539 let wheel_distance =
540 (-wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_chars as f32;
541 let mut cursor_point = POINT {
542 x: lparam.signed_loword().into(),
543 y: lparam.signed_hiword().into(),
544 };
545 unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
546 let event = ScrollWheelEvent {
547 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
548 delta: ScrollDelta::Lines(Point {
549 x: wheel_distance,
550 y: 0.0,
551 }),
552 modifiers: current_modifiers(),
553 touch_phase: TouchPhase::Moved,
554 };
555 let result = if callback(PlatformInput::ScrollWheel(event)).default_prevented {
556 Some(0)
557 } else {
558 Some(1)
559 };
560 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
561
562 result
563 } else {
564 Some(1)
565 }
566}
567
568fn handle_ime_position(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
569 unsafe {
570 let mut lock = state_ptr.state.borrow_mut();
571 let ctx = ImmGetContext(handle);
572 let Some(mut input_handler) = lock.input_handler.take() else {
573 return Some(1);
574 };
575 let scale_factor = lock.scale_factor;
576 drop(lock);
577
578 let Some(caret_range) = input_handler.selected_text_range() else {
579 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
580 return Some(0);
581 };
582 let caret_position = input_handler.bounds_for_range(caret_range).unwrap();
583 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
584 let config = CANDIDATEFORM {
585 dwStyle: CFS_CANDIDATEPOS,
586 // logical to physical
587 ptCurrentPos: POINT {
588 x: (caret_position.origin.x.0 * scale_factor) as i32,
589 y: (caret_position.origin.y.0 * scale_factor) as i32
590 + ((caret_position.size.height.0 * scale_factor) as i32 / 2),
591 },
592 ..Default::default()
593 };
594 ImmSetCandidateWindow(ctx, &config as _).ok().log_err();
595 ImmReleaseContext(handle, ctx).ok().log_err();
596 Some(0)
597 }
598}
599
600fn handle_ime_composition(
601 handle: HWND,
602 lparam: LPARAM,
603 state_ptr: Rc<WindowsWindowStatePtr>,
604) -> Option<isize> {
605 let mut ime_input = None;
606 if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
607 let Some((string, string_len)) = parse_ime_compostion_string(handle) else {
608 return None;
609 };
610 let mut lock = state_ptr.state.borrow_mut();
611 let Some(mut input_handler) = lock.input_handler.take() else {
612 return None;
613 };
614 drop(lock);
615 input_handler.replace_and_mark_text_in_range(None, string.as_str(), Some(0..string_len));
616 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
617 ime_input = Some(string);
618 }
619 if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
620 let Some(ref comp_string) = ime_input else {
621 return None;
622 };
623 let caret_pos = retrieve_composition_cursor_position(handle);
624 let mut lock = state_ptr.state.borrow_mut();
625 let Some(mut input_handler) = lock.input_handler.take() else {
626 return None;
627 };
628 drop(lock);
629 input_handler.replace_and_mark_text_in_range(None, comp_string, Some(0..caret_pos));
630 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
631 }
632 if lparam.0 as u32 & GCS_RESULTSTR.0 > 0 {
633 let Some(comp_result) = parse_ime_compostion_result(handle) else {
634 return None;
635 };
636 let mut lock = state_ptr.state.borrow_mut();
637 let Some(mut input_handler) = lock.input_handler.take() else {
638 return Some(1);
639 };
640 drop(lock);
641 input_handler.replace_text_in_range(None, &comp_result);
642 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
643 return Some(0);
644 }
645 // currently, we don't care other stuff
646 None
647}
648
649/// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
650fn handle_calc_client_size(
651 handle: HWND,
652 wparam: WPARAM,
653 lparam: LPARAM,
654 state_ptr: Rc<WindowsWindowStatePtr>,
655) -> Option<isize> {
656 if !state_ptr.hide_title_bar || state_ptr.state.borrow().is_fullscreen() {
657 return None;
658 }
659
660 if wparam.0 == 0 {
661 return None;
662 }
663
664 let dpi = unsafe { GetDpiForWindow(handle) };
665
666 let frame_x = unsafe { GetSystemMetricsForDpi(SM_CXFRAME, dpi) };
667 let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
668 let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
669
670 // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure
671 let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS;
672 let mut requested_client_rect = unsafe { &mut ((*params).rgrc) };
673
674 requested_client_rect[0].right -= frame_x + padding;
675 requested_client_rect[0].left += frame_x + padding;
676 requested_client_rect[0].bottom -= frame_y + padding;
677
678 Some(0)
679}
680
681fn handle_activate_msg(
682 handle: HWND,
683 wparam: WPARAM,
684 state_ptr: Rc<WindowsWindowStatePtr>,
685) -> Option<isize> {
686 let activated = wparam.loword() > 0;
687 if state_ptr.hide_title_bar {
688 if let Some(titlebar_rect) = state_ptr.state.borrow().get_titlebar_rect().log_err() {
689 unsafe {
690 InvalidateRect(handle, Some(&titlebar_rect), FALSE)
691 .ok()
692 .log_err()
693 };
694 }
695 }
696 let this = state_ptr.clone();
697 state_ptr
698 .executor
699 .spawn(async move {
700 let mut lock = this.state.borrow_mut();
701 if let Some(mut cb) = lock.callbacks.active_status_change.take() {
702 drop(lock);
703 cb(activated);
704 this.state.borrow_mut().callbacks.active_status_change = Some(cb);
705 }
706 })
707 .detach();
708
709 None
710}
711
712fn handle_create_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
713 let mut size_rect = RECT::default();
714 unsafe { GetWindowRect(handle, &mut size_rect).log_err() };
715
716 let width = size_rect.right - size_rect.left;
717 let height = size_rect.bottom - size_rect.top;
718
719 if state_ptr.hide_title_bar {
720 unsafe {
721 SetWindowPos(
722 handle,
723 None,
724 size_rect.left,
725 size_rect.top,
726 width,
727 height,
728 SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE,
729 )
730 .log_err()
731 };
732 }
733
734 Some(0)
735}
736
737fn handle_dpi_changed_msg(
738 handle: HWND,
739 wparam: WPARAM,
740 lparam: LPARAM,
741 state_ptr: Rc<WindowsWindowStatePtr>,
742) -> Option<isize> {
743 let new_dpi = wparam.loword() as f32;
744 state_ptr.state.borrow_mut().scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
745
746 let rect = unsafe { &*(lparam.0 as *const RECT) };
747 let width = rect.right - rect.left;
748 let height = rect.bottom - rect.top;
749 // this will emit `WM_SIZE` and `WM_MOVE` right here
750 // even before this function returns
751 // the new size is handled in `WM_SIZE`
752 unsafe {
753 SetWindowPos(
754 handle,
755 None,
756 rect.left,
757 rect.top,
758 width,
759 height,
760 SWP_NOZORDER | SWP_NOACTIVATE,
761 )
762 .context("unable to set window position after dpi has changed")
763 .log_err();
764 }
765
766 Some(0)
767}
768
769/// The following conditions will trigger this event:
770/// 1. The monitor on which the window is located goes offline or changes resolution.
771/// 2. Another monitor goes offline, is plugged in, or changes resolution.
772///
773/// In either case, the window will only receive information from the monitor on which
774/// it is located.
775///
776/// For example, in the case of condition 2, where the monitor on which the window is
777/// located has actually changed nothing, it will still receive this event.
778fn handle_display_change_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
779 // NOTE:
780 // Even the `lParam` holds the resolution of the screen, we just ignore it.
781 // Because WM_DPICHANGED, WM_MOVE, WM_SIEZ will come first, window reposition and resize
782 // are handled there.
783 // So we only care about if monitor is disconnected.
784 let previous_monitor = state_ptr.as_ref().state.borrow().display;
785 if WindowsDisplay::is_connected(previous_monitor.handle) {
786 // we are fine, other display changed
787 return None;
788 }
789 // display disconnected
790 // in this case, the OS will move our window to another monitor, and minimize it.
791 // we deminimize the window and query the monitor after moving
792 unsafe {
793 let _ = ShowWindow(handle, SW_SHOWNORMAL);
794 };
795 let new_monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) };
796 // all monitors disconnected
797 if new_monitor.is_invalid() {
798 log::error!("No monitor detected!");
799 return None;
800 }
801 let new_display = WindowsDisplay::new_with_handle(new_monitor);
802 state_ptr.as_ref().state.borrow_mut().display = new_display;
803 Some(0)
804}
805
806fn handle_hit_test_msg(
807 handle: HWND,
808 msg: u32,
809 wparam: WPARAM,
810 lparam: LPARAM,
811 state_ptr: Rc<WindowsWindowStatePtr>,
812) -> Option<isize> {
813 if !state_ptr.hide_title_bar {
814 return None;
815 }
816
817 // default handler for resize areas
818 let hit = unsafe { DefWindowProcW(handle, msg, wparam, lparam) };
819 if matches!(
820 hit.0 as u32,
821 HTNOWHERE
822 | HTRIGHT
823 | HTLEFT
824 | HTTOPLEFT
825 | HTTOP
826 | HTTOPRIGHT
827 | HTBOTTOMRIGHT
828 | HTBOTTOM
829 | HTBOTTOMLEFT
830 ) {
831 return Some(hit.0);
832 }
833
834 if state_ptr.state.borrow().is_fullscreen() {
835 return Some(HTCLIENT as _);
836 }
837
838 let dpi = unsafe { GetDpiForWindow(handle) };
839 let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
840 let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
841
842 let mut cursor_point = POINT {
843 x: lparam.signed_loword().into(),
844 y: lparam.signed_hiword().into(),
845 };
846 unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
847 if cursor_point.y > 0 && cursor_point.y < frame_y + padding {
848 return Some(HTTOP as _);
849 }
850
851 let titlebar_rect = state_ptr.state.borrow().get_titlebar_rect();
852 if let Ok(titlebar_rect) = titlebar_rect {
853 if cursor_point.y < titlebar_rect.bottom {
854 let caption_btn_width = (state_ptr.state.borrow().caption_button_width().0
855 * state_ptr.state.borrow().scale_factor) as i32;
856 if cursor_point.x >= titlebar_rect.right - caption_btn_width {
857 return Some(HTCLOSE as _);
858 } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 {
859 return Some(HTMAXBUTTON as _);
860 } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 {
861 return Some(HTMINBUTTON as _);
862 }
863
864 return Some(HTCAPTION as _);
865 }
866 }
867
868 Some(HTCLIENT as _)
869}
870
871fn handle_nc_mouse_move_msg(
872 handle: HWND,
873 lparam: LPARAM,
874 state_ptr: Rc<WindowsWindowStatePtr>,
875) -> Option<isize> {
876 if !state_ptr.hide_title_bar {
877 return None;
878 }
879
880 let mut lock = state_ptr.state.borrow_mut();
881 if let Some(mut callback) = lock.callbacks.input.take() {
882 let scale_factor = lock.scale_factor;
883 drop(lock);
884 let mut cursor_point = POINT {
885 x: lparam.signed_loword().into(),
886 y: lparam.signed_hiword().into(),
887 };
888 unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
889 let event = MouseMoveEvent {
890 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
891 pressed_button: None,
892 modifiers: current_modifiers(),
893 };
894 let result = if callback(PlatformInput::MouseMove(event)).default_prevented {
895 Some(0)
896 } else {
897 Some(1)
898 };
899 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
900
901 result
902 } else {
903 None
904 }
905}
906
907fn handle_nc_mouse_down_msg(
908 handle: HWND,
909 button: MouseButton,
910 wparam: WPARAM,
911 lparam: LPARAM,
912 state_ptr: Rc<WindowsWindowStatePtr>,
913) -> Option<isize> {
914 if !state_ptr.hide_title_bar {
915 return None;
916 }
917
918 let mut lock = state_ptr.state.borrow_mut();
919 let result = if let Some(mut callback) = lock.callbacks.input.take() {
920 let scale_factor = lock.scale_factor;
921 let mut cursor_point = POINT {
922 x: lparam.signed_loword().into(),
923 y: lparam.signed_hiword().into(),
924 };
925 unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
926 let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
927 let click_count = lock.click_state.update(button, physical_point);
928 drop(lock);
929 let event = MouseDownEvent {
930 button,
931 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
932 modifiers: current_modifiers(),
933 click_count,
934 first_mouse: false,
935 };
936 let result = if callback(PlatformInput::MouseDown(event)).default_prevented {
937 Some(0)
938 } else {
939 None
940 };
941 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
942
943 result
944 } else {
945 None
946 };
947
948 // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
949 result.or_else(|| matches!(wparam.0 as u32, HTMINBUTTON | HTMAXBUTTON | HTCLOSE).then_some(0))
950}
951
952fn handle_nc_mouse_up_msg(
953 handle: HWND,
954 button: MouseButton,
955 wparam: WPARAM,
956 lparam: LPARAM,
957 state_ptr: Rc<WindowsWindowStatePtr>,
958) -> Option<isize> {
959 if !state_ptr.hide_title_bar {
960 return None;
961 }
962
963 let mut lock = state_ptr.state.borrow_mut();
964 if let Some(mut callback) = lock.callbacks.input.take() {
965 let scale_factor = lock.scale_factor;
966 drop(lock);
967 let mut cursor_point = POINT {
968 x: lparam.signed_loword().into(),
969 y: lparam.signed_hiword().into(),
970 };
971 unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
972 let event = MouseUpEvent {
973 button,
974 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
975 modifiers: current_modifiers(),
976 click_count: 1,
977 };
978 let result = if callback(PlatformInput::MouseUp(event)).default_prevented {
979 Some(0)
980 } else {
981 None
982 };
983 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
984 if result.is_some() {
985 return result;
986 }
987 } else {
988 drop(lock);
989 }
990
991 if button == MouseButton::Left {
992 match wparam.0 as u32 {
993 HTMINBUTTON => unsafe {
994 ShowWindowAsync(handle, SW_MINIMIZE).ok().log_err();
995 },
996 HTMAXBUTTON => unsafe {
997 if state_ptr.state.borrow().is_maximized() {
998 ShowWindowAsync(handle, SW_NORMAL).ok().log_err();
999 } else {
1000 ShowWindowAsync(handle, SW_MAXIMIZE).ok().log_err();
1001 }
1002 },
1003 HTCLOSE => unsafe {
1004 PostMessageW(handle, WM_CLOSE, WPARAM::default(), LPARAM::default()).log_err();
1005 },
1006 _ => return None,
1007 };
1008 return Some(0);
1009 }
1010
1011 None
1012}
1013
1014fn handle_cursor_changed(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
1015 state_ptr.state.borrow_mut().current_cursor = HCURSOR(lparam.0);
1016 Some(0)
1017}
1018
1019fn handle_set_cursor(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
1020 if matches!(
1021 lparam.loword() as u32,
1022 HTLEFT | HTRIGHT | HTTOP | HTTOPLEFT | HTTOPRIGHT | HTBOTTOM | HTBOTTOMLEFT | HTBOTTOMRIGHT
1023 ) {
1024 return None;
1025 }
1026 unsafe { SetCursor(state_ptr.state.borrow().current_cursor) };
1027 Some(1)
1028}
1029
1030fn handle_system_settings_changed(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
1031 let mut lock = state_ptr.state.borrow_mut();
1032 // mouse wheel
1033 lock.system_settings.mouse_wheel_settings.update();
1034 Some(0)
1035}
1036
1037fn parse_syskeydown_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
1038 let modifiers = current_modifiers();
1039 if !modifiers.alt {
1040 // on Windows, F10 can trigger this event, not just the alt key
1041 // and we just don't care about F10
1042 return None;
1043 }
1044
1045 let vk_code = wparam.loword();
1046 let basic_key = basic_vkcode_to_string(vk_code, modifiers);
1047 if basic_key.is_some() {
1048 return basic_key;
1049 }
1050
1051 let key = match VIRTUAL_KEY(vk_code) {
1052 VK_BACK => Some("backspace"),
1053 VK_RETURN => Some("enter"),
1054 VK_TAB => Some("tab"),
1055 VK_UP => Some("up"),
1056 VK_DOWN => Some("down"),
1057 VK_RIGHT => Some("right"),
1058 VK_LEFT => Some("left"),
1059 VK_HOME => Some("home"),
1060 VK_END => Some("end"),
1061 VK_PRIOR => Some("pageup"),
1062 VK_NEXT => Some("pagedown"),
1063 VK_ESCAPE => Some("escape"),
1064 VK_INSERT => Some("insert"),
1065 _ => None,
1066 };
1067
1068 if let Some(key) = key {
1069 Some(Keystroke {
1070 modifiers,
1071 key: key.to_string(),
1072 ime_key: None,
1073 })
1074 } else {
1075 None
1076 }
1077}
1078
1079enum KeystrokeOrModifier {
1080 Keystroke(Keystroke),
1081 Modifier(Modifiers),
1082}
1083
1084fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option<KeystrokeOrModifier> {
1085 let vk_code = wparam.loword();
1086
1087 let modifiers = current_modifiers();
1088
1089 if is_modifier(VIRTUAL_KEY(vk_code)) {
1090 return Some(KeystrokeOrModifier::Modifier(modifiers));
1091 }
1092
1093 if modifiers.control || modifiers.alt {
1094 let basic_key = basic_vkcode_to_string(vk_code, modifiers);
1095 if let Some(basic_key) = basic_key {
1096 return Some(KeystrokeOrModifier::Keystroke(basic_key));
1097 }
1098 }
1099
1100 if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 {
1101 let offset = vk_code - VK_F1.0;
1102 return Some(KeystrokeOrModifier::Keystroke(Keystroke {
1103 modifiers,
1104 key: format!("f{}", offset + 1),
1105 ime_key: None,
1106 }));
1107 }
1108
1109 let key = match VIRTUAL_KEY(vk_code) {
1110 VK_BACK => Some("backspace"),
1111 VK_RETURN => Some("enter"),
1112 VK_TAB => Some("tab"),
1113 VK_UP => Some("up"),
1114 VK_DOWN => Some("down"),
1115 VK_RIGHT => Some("right"),
1116 VK_LEFT => Some("left"),
1117 VK_HOME => Some("home"),
1118 VK_END => Some("end"),
1119 VK_PRIOR => Some("pageup"),
1120 VK_NEXT => Some("pagedown"),
1121 VK_ESCAPE => Some("escape"),
1122 VK_INSERT => Some("insert"),
1123 VK_DELETE => Some("delete"),
1124 _ => None,
1125 };
1126
1127 if let Some(key) = key {
1128 Some(KeystrokeOrModifier::Keystroke(Keystroke {
1129 modifiers,
1130 key: key.to_string(),
1131 ime_key: None,
1132 }))
1133 } else {
1134 None
1135 }
1136}
1137
1138fn parse_char_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
1139 let src = [wparam.0 as u16];
1140 let Ok(first_char) = char::decode_utf16(src).collect::<Vec<_>>()[0] else {
1141 return None;
1142 };
1143 if first_char.is_control() {
1144 None
1145 } else {
1146 let mut modifiers = current_modifiers();
1147 // for characters that use 'shift' to type it is expected that the
1148 // shift is not reported if the uppercase/lowercase are the same and instead only the key is reported
1149 if first_char.to_lowercase().to_string() == first_char.to_uppercase().to_string() {
1150 modifiers.shift = false;
1151 }
1152 let key = match first_char {
1153 ' ' => "space".to_string(),
1154 first_char => first_char.to_lowercase().to_string(),
1155 };
1156 Some(Keystroke {
1157 modifiers,
1158 key,
1159 ime_key: Some(first_char.to_string()),
1160 })
1161 }
1162}
1163
1164fn parse_ime_compostion_string(handle: HWND) -> Option<(String, usize)> {
1165 unsafe {
1166 let ctx = ImmGetContext(handle);
1167 let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0);
1168 let result = if string_len >= 0 {
1169 let mut buffer = vec![0u8; string_len as usize + 2];
1170 ImmGetCompositionStringW(
1171 ctx,
1172 GCS_COMPSTR,
1173 Some(buffer.as_mut_ptr() as _),
1174 string_len as _,
1175 );
1176 let wstring = std::slice::from_raw_parts::<u16>(
1177 buffer.as_mut_ptr().cast::<u16>(),
1178 string_len as usize / 2,
1179 );
1180 let string = String::from_utf16_lossy(wstring);
1181 Some((string, string_len as usize / 2))
1182 } else {
1183 None
1184 };
1185 ImmReleaseContext(handle, ctx).ok().log_err();
1186 result
1187 }
1188}
1189
1190fn retrieve_composition_cursor_position(handle: HWND) -> usize {
1191 unsafe {
1192 let ctx = ImmGetContext(handle);
1193 let ret = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0);
1194 ImmReleaseContext(handle, ctx).ok().log_err();
1195 ret as usize
1196 }
1197}
1198
1199fn parse_ime_compostion_result(handle: HWND) -> Option<String> {
1200 unsafe {
1201 let ctx = ImmGetContext(handle);
1202 let string_len = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, None, 0);
1203 let result = if string_len >= 0 {
1204 let mut buffer = vec![0u8; string_len as usize + 2];
1205 ImmGetCompositionStringW(
1206 ctx,
1207 GCS_RESULTSTR,
1208 Some(buffer.as_mut_ptr() as _),
1209 string_len as _,
1210 );
1211 let wstring = std::slice::from_raw_parts::<u16>(
1212 buffer.as_mut_ptr().cast::<u16>(),
1213 string_len as usize / 2,
1214 );
1215 let string = String::from_utf16_lossy(wstring);
1216 Some(string)
1217 } else {
1218 None
1219 };
1220 ImmReleaseContext(handle, ctx).ok().log_err();
1221 result
1222 }
1223}
1224
1225fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
1226 match code {
1227 // VK_0 - VK_9
1228 48..=57 => Some(Keystroke {
1229 modifiers,
1230 key: format!("{}", code - VK_0.0),
1231 ime_key: None,
1232 }),
1233 // VK_A - VK_Z
1234 65..=90 => Some(Keystroke {
1235 modifiers,
1236 key: format!("{}", (b'a' + code as u8 - VK_A.0 as u8) as char),
1237 ime_key: None,
1238 }),
1239 // VK_F1 - VK_F24
1240 112..=135 => Some(Keystroke {
1241 modifiers,
1242 key: format!("f{}", code - VK_F1.0 + 1),
1243 ime_key: None,
1244 }),
1245 // OEM3: `/~, OEM_MINUS: -/_, OEM_PLUS: =/+, ...
1246 _ => {
1247 if let Some(key) = oemkey_vkcode_to_string(code) {
1248 Some(Keystroke {
1249 modifiers,
1250 key,
1251 ime_key: None,
1252 })
1253 } else {
1254 None
1255 }
1256 }
1257 }
1258}
1259
1260fn oemkey_vkcode_to_string(code: u16) -> Option<String> {
1261 match code {
1262 186 => Some(";".to_string()), // VK_OEM_1
1263 187 => Some("=".to_string()), // VK_OEM_PLUS
1264 188 => Some(",".to_string()), // VK_OEM_COMMA
1265 189 => Some("-".to_string()), // VK_OEM_MINUS
1266 190 => Some(".".to_string()), // VK_OEM_PERIOD
1267 // https://kbdlayout.info/features/virtualkeys/VK_ABNT_C1
1268 191 | 193 => Some("/".to_string()), // VK_OEM_2 VK_ABNT_C1
1269 192 => Some("`".to_string()), // VK_OEM_3
1270 219 => Some("[".to_string()), // VK_OEM_4
1271 220 => Some("\\".to_string()), // VK_OEM_5
1272 221 => Some("]".to_string()), // VK_OEM_6
1273 222 => Some("'".to_string()), // VK_OEM_7
1274 _ => None,
1275 }
1276}
1277
1278#[inline]
1279fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool {
1280 unsafe { GetKeyState(vkey.0 as i32) < 0 }
1281}
1282
1283fn is_modifier(virtual_key: VIRTUAL_KEY) -> bool {
1284 matches!(
1285 virtual_key,
1286 VK_CONTROL | VK_MENU | VK_SHIFT | VK_LWIN | VK_RWIN
1287 )
1288}
1289
1290#[inline]
1291fn current_modifiers() -> Modifiers {
1292 Modifiers {
1293 control: is_virtual_key_pressed(VK_CONTROL),
1294 alt: is_virtual_key_pressed(VK_MENU),
1295 shift: is_virtual_key_pressed(VK_SHIFT),
1296 platform: is_virtual_key_pressed(VK_LWIN) || is_virtual_key_pressed(VK_RWIN),
1297 function: false,
1298 }
1299}