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