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