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 Controls::*,
11 HiDpi::*,
12 Input::{Ime::*, KeyboardAndMouse::*},
13 WindowsAndMessaging::*,
14 },
15};
16
17use crate::*;
18
19pub(crate) const CURSOR_STYLE_CHANGED: u32 = WM_USER + 1;
20pub(crate) const CLOSE_ONE_WINDOW: u32 = WM_USER + 2;
21
22const SIZE_MOVE_LOOP_TIMER_ID: usize = 1;
23const AUTO_HIDE_TASKBAR_THICKNESS_PX: i32 = 1;
24
25pub(crate) fn handle_msg(
26 handle: HWND,
27 msg: u32,
28 wparam: WPARAM,
29 lparam: LPARAM,
30 state_ptr: Rc<WindowsWindowStatePtr>,
31) -> LRESULT {
32 let handled = match msg {
33 WM_ACTIVATE => handle_activate_msg(handle, wparam, state_ptr),
34 WM_CREATE => handle_create_msg(handle, state_ptr),
35 WM_MOVE => handle_move_msg(handle, lparam, state_ptr),
36 WM_SIZE => handle_size_msg(wparam, lparam, state_ptr),
37 WM_ENTERSIZEMOVE | WM_ENTERMENULOOP => handle_size_move_loop(handle),
38 WM_EXITSIZEMOVE | WM_EXITMENULOOP => handle_size_move_loop_exit(handle),
39 WM_TIMER => handle_timer_msg(handle, wparam, state_ptr),
40 WM_NCCALCSIZE => handle_calc_client_size(handle, wparam, lparam, state_ptr),
41 WM_DPICHANGED => handle_dpi_changed_msg(handle, wparam, lparam, state_ptr),
42 WM_DISPLAYCHANGE => handle_display_change_msg(handle, state_ptr),
43 WM_NCHITTEST => handle_hit_test_msg(handle, msg, wparam, lparam, state_ptr),
44 WM_PAINT => handle_paint_msg(handle, state_ptr),
45 WM_CLOSE => handle_close_msg(state_ptr),
46 WM_DESTROY => handle_destroy_msg(handle, state_ptr),
47 WM_MOUSEMOVE => handle_mouse_move_msg(handle, lparam, wparam, state_ptr),
48 WM_MOUSELEAVE => handle_mouse_leave_msg(state_ptr),
49 WM_NCMOUSEMOVE => handle_nc_mouse_move_msg(handle, lparam, state_ptr),
50 WM_NCMOUSELEAVE => handle_nc_mouse_leave_msg(state_ptr),
51 WM_NCLBUTTONDOWN => {
52 handle_nc_mouse_down_msg(handle, MouseButton::Left, wparam, lparam, state_ptr)
53 }
54 WM_NCRBUTTONDOWN => {
55 handle_nc_mouse_down_msg(handle, MouseButton::Right, wparam, lparam, state_ptr)
56 }
57 WM_NCMBUTTONDOWN => {
58 handle_nc_mouse_down_msg(handle, MouseButton::Middle, wparam, lparam, state_ptr)
59 }
60 WM_NCLBUTTONUP => {
61 handle_nc_mouse_up_msg(handle, MouseButton::Left, wparam, lparam, state_ptr)
62 }
63 WM_NCRBUTTONUP => {
64 handle_nc_mouse_up_msg(handle, MouseButton::Right, wparam, lparam, state_ptr)
65 }
66 WM_NCMBUTTONUP => {
67 handle_nc_mouse_up_msg(handle, MouseButton::Middle, wparam, lparam, state_ptr)
68 }
69 WM_LBUTTONDOWN => handle_mouse_down_msg(handle, MouseButton::Left, lparam, state_ptr),
70 WM_RBUTTONDOWN => handle_mouse_down_msg(handle, MouseButton::Right, lparam, state_ptr),
71 WM_MBUTTONDOWN => handle_mouse_down_msg(handle, MouseButton::Middle, lparam, state_ptr),
72 WM_XBUTTONDOWN => {
73 handle_xbutton_msg(handle, wparam, lparam, handle_mouse_down_msg, state_ptr)
74 }
75 WM_LBUTTONUP => handle_mouse_up_msg(handle, MouseButton::Left, lparam, state_ptr),
76 WM_RBUTTONUP => handle_mouse_up_msg(handle, MouseButton::Right, lparam, state_ptr),
77 WM_MBUTTONUP => handle_mouse_up_msg(handle, MouseButton::Middle, lparam, state_ptr),
78 WM_XBUTTONUP => handle_xbutton_msg(handle, wparam, lparam, handle_mouse_up_msg, state_ptr),
79 WM_MOUSEWHEEL => handle_mouse_wheel_msg(handle, wparam, lparam, state_ptr),
80 WM_MOUSEHWHEEL => handle_mouse_horizontal_wheel_msg(handle, wparam, lparam, state_ptr),
81 WM_SYSKEYDOWN => handle_syskeydown_msg(wparam, lparam, state_ptr),
82 WM_SYSKEYUP => handle_syskeyup_msg(wparam, state_ptr),
83 WM_SYSCOMMAND => handle_system_command(wparam, state_ptr),
84 WM_KEYDOWN => handle_keydown_msg(wparam, lparam, state_ptr),
85 WM_KEYUP => handle_keyup_msg(wparam, state_ptr),
86 WM_CHAR => handle_char_msg(wparam, lparam, state_ptr),
87 WM_IME_STARTCOMPOSITION => handle_ime_position(handle, state_ptr),
88 WM_IME_COMPOSITION => handle_ime_composition(handle, lparam, state_ptr),
89 WM_SETCURSOR => handle_set_cursor(lparam, state_ptr),
90 WM_SETTINGCHANGE => handle_system_settings_changed(handle, state_ptr),
91 WM_DWMCOLORIZATIONCOLORCHANGED => handle_system_theme_changed(state_ptr),
92 CURSOR_STYLE_CHANGED => handle_cursor_changed(lparam, state_ptr),
93 _ => None,
94 };
95 if let Some(n) = handled {
96 LRESULT(n)
97 } else {
98 unsafe { DefWindowProcW(handle, msg, wparam, lparam) }
99 }
100}
101
102fn handle_move_msg(
103 handle: HWND,
104 lparam: LPARAM,
105 state_ptr: Rc<WindowsWindowStatePtr>,
106) -> Option<isize> {
107 let mut lock = state_ptr.state.borrow_mut();
108 let origin = logical_point(
109 lparam.signed_loword() as f32,
110 lparam.signed_hiword() as f32,
111 lock.scale_factor,
112 );
113 lock.origin = origin;
114 let size = lock.logical_size;
115 let center_x = origin.x.0 + size.width.0 / 2.;
116 let center_y = origin.y.0 + size.height.0 / 2.;
117 let monitor_bounds = lock.display.bounds();
118 if center_x < monitor_bounds.left().0
119 || center_x > monitor_bounds.right().0
120 || center_y < monitor_bounds.top().0
121 || center_y > monitor_bounds.bottom().0
122 {
123 // center of the window may have moved to another monitor
124 let monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) };
125 // minimize the window can trigger this event too, in this case,
126 // monitor is invalid, we do nothing.
127 if !monitor.is_invalid() && lock.display.handle != monitor {
128 // we will get the same monitor if we only have one
129 lock.display = WindowsDisplay::new_with_handle(monitor);
130 }
131 }
132 if let Some(mut callback) = lock.callbacks.moved.take() {
133 drop(lock);
134 callback();
135 state_ptr.state.borrow_mut().callbacks.moved = Some(callback);
136 }
137 Some(0)
138}
139
140fn handle_size_msg(
141 wparam: WPARAM,
142 lparam: LPARAM,
143 state_ptr: Rc<WindowsWindowStatePtr>,
144) -> Option<isize> {
145 let mut lock = state_ptr.state.borrow_mut();
146
147 // Don't resize the renderer when the window is minimized, but record that it was minimized so
148 // that on restore the swap chain can be recreated via `update_drawable_size_even_if_unchanged`.
149 if wparam.0 == SIZE_MINIMIZED as usize {
150 lock.restore_from_minimized = lock.callbacks.request_frame.take();
151 return Some(0);
152 }
153
154 let width = lparam.loword().max(1) as i32;
155 let height = lparam.hiword().max(1) as i32;
156 let new_size = size(DevicePixels(width), DevicePixels(height));
157 let scale_factor = lock.scale_factor;
158 if lock.restore_from_minimized.is_some() {
159 lock.renderer
160 .update_drawable_size_even_if_unchanged(new_size);
161 lock.callbacks.request_frame = lock.restore_from_minimized.take();
162 } else {
163 lock.renderer.update_drawable_size(new_size);
164 }
165 let new_size = new_size.to_pixels(scale_factor);
166 lock.logical_size = new_size;
167 if let Some(mut callback) = lock.callbacks.resize.take() {
168 drop(lock);
169 callback(new_size, scale_factor);
170 state_ptr.state.borrow_mut().callbacks.resize = Some(callback);
171 }
172 Some(0)
173}
174
175fn handle_size_move_loop(handle: HWND) -> Option<isize> {
176 unsafe {
177 let ret = SetTimer(handle, SIZE_MOVE_LOOP_TIMER_ID, USER_TIMER_MINIMUM, None);
178 if ret == 0 {
179 log::error!(
180 "unable to create timer: {}",
181 std::io::Error::last_os_error()
182 );
183 }
184 }
185 None
186}
187
188fn handle_size_move_loop_exit(handle: HWND) -> Option<isize> {
189 unsafe {
190 KillTimer(handle, SIZE_MOVE_LOOP_TIMER_ID).log_err();
191 }
192 None
193}
194
195fn handle_timer_msg(
196 handle: HWND,
197 wparam: WPARAM,
198 state_ptr: Rc<WindowsWindowStatePtr>,
199) -> Option<isize> {
200 if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID {
201 for runnable in state_ptr.main_receiver.drain() {
202 runnable.run();
203 }
204 handle_paint_msg(handle, state_ptr)
205 } else {
206 None
207 }
208}
209
210fn handle_paint_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
211 let mut lock = state_ptr.state.borrow_mut();
212 if let Some(mut request_frame) = lock.callbacks.request_frame.take() {
213 drop(lock);
214 request_frame(Default::default());
215 state_ptr.state.borrow_mut().callbacks.request_frame = Some(request_frame);
216 }
217 unsafe { ValidateRect(handle, None).ok().log_err() };
218 Some(0)
219}
220
221fn handle_close_msg(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
222 let mut lock = state_ptr.state.borrow_mut();
223 if let Some(mut callback) = lock.callbacks.should_close.take() {
224 drop(lock);
225 let should_close = callback();
226 state_ptr.state.borrow_mut().callbacks.should_close = Some(callback);
227 if should_close {
228 None
229 } else {
230 Some(0)
231 }
232 } else {
233 None
234 }
235}
236
237fn handle_destroy_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
238 let callback = {
239 let mut lock = state_ptr.state.borrow_mut();
240 lock.callbacks.close.take()
241 };
242 if let Some(callback) = callback {
243 callback();
244 }
245 unsafe {
246 PostMessageW(
247 None,
248 CLOSE_ONE_WINDOW,
249 WPARAM(state_ptr.validation_number),
250 LPARAM(handle.0 as isize),
251 )
252 .log_err();
253 }
254 Some(0)
255}
256
257fn handle_mouse_move_msg(
258 handle: HWND,
259 lparam: LPARAM,
260 wparam: WPARAM,
261 state_ptr: Rc<WindowsWindowStatePtr>,
262) -> Option<isize> {
263 let mut lock = state_ptr.state.borrow_mut();
264 if !lock.hovered {
265 lock.hovered = true;
266 unsafe {
267 TrackMouseEvent(&mut TRACKMOUSEEVENT {
268 cbSize: std::mem::size_of::<TRACKMOUSEEVENT>() as u32,
269 dwFlags: TME_LEAVE,
270 hwndTrack: handle,
271 dwHoverTime: HOVER_DEFAULT,
272 })
273 .log_err()
274 };
275 if let Some(mut callback) = lock.callbacks.hovered_status_change.take() {
276 drop(lock);
277 callback(true);
278 state_ptr.state.borrow_mut().callbacks.hovered_status_change = Some(callback);
279 }
280 } else {
281 drop(lock);
282 }
283
284 let mut lock = state_ptr.state.borrow_mut();
285 if let Some(mut callback) = lock.callbacks.input.take() {
286 let scale_factor = lock.scale_factor;
287 drop(lock);
288 let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
289 flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
290 flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),
291 flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),
292 flags if flags.contains(MK_XBUTTON1) => {
293 Some(MouseButton::Navigate(NavigationDirection::Back))
294 }
295 flags if flags.contains(MK_XBUTTON2) => {
296 Some(MouseButton::Navigate(NavigationDirection::Forward))
297 }
298 _ => None,
299 };
300 let x = lparam.signed_loword() as f32;
301 let y = lparam.signed_hiword() as f32;
302 let event = MouseMoveEvent {
303 position: logical_point(x, y, scale_factor),
304 pressed_button,
305 modifiers: current_modifiers(),
306 };
307 let result = if callback(PlatformInput::MouseMove(event)).default_prevented {
308 Some(0)
309 } else {
310 Some(1)
311 };
312 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
313 return result;
314 }
315 Some(1)
316}
317
318fn handle_nc_mouse_leave_msg(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
319 let mut lock = state_ptr.state.borrow_mut();
320 lock.hovered = false;
321 if let Some(mut callback) = lock.callbacks.hovered_status_change.take() {
322 drop(lock);
323 callback(false);
324 state_ptr.state.borrow_mut().callbacks.hovered_status_change = Some(callback);
325 }
326
327 Some(0)
328}
329
330fn handle_mouse_leave_msg(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
331 let mut lock = state_ptr.state.borrow_mut();
332 lock.hovered = false;
333 if let Some(mut callback) = lock.callbacks.hovered_status_change.take() {
334 drop(lock);
335 callback(false);
336 state_ptr.state.borrow_mut().callbacks.hovered_status_change = Some(callback);
337 }
338
339 Some(0)
340}
341
342fn handle_syskeydown_msg(
343 wparam: WPARAM,
344 lparam: LPARAM,
345 state_ptr: Rc<WindowsWindowStatePtr>,
346) -> Option<isize> {
347 // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
348 // shortcuts.
349 let keystroke = parse_syskeydown_msg_keystroke(wparam)?;
350 let mut func = state_ptr.state.borrow_mut().callbacks.input.take()?;
351 let event = KeyDownEvent {
352 keystroke,
353 is_held: lparam.0 & (0x1 << 30) > 0,
354 };
355 let result = if !func(PlatformInput::KeyDown(event)).propagate {
356 state_ptr.state.borrow_mut().system_key_handled = true;
357 Some(0)
358 } else {
359 None
360 };
361 state_ptr.state.borrow_mut().callbacks.input = Some(func);
362
363 result
364}
365
366fn handle_syskeyup_msg(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
367 // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
368 // shortcuts.
369 let keystroke = parse_syskeydown_msg_keystroke(wparam)?;
370 let mut func = state_ptr.state.borrow_mut().callbacks.input.take()?;
371 let event = KeyUpEvent { keystroke };
372 let result = if func(PlatformInput::KeyUp(event)).default_prevented {
373 Some(0)
374 } else {
375 Some(1)
376 };
377 state_ptr.state.borrow_mut().callbacks.input = Some(func);
378
379 result
380}
381
382fn handle_keydown_msg(
383 wparam: WPARAM,
384 lparam: LPARAM,
385 state_ptr: Rc<WindowsWindowStatePtr>,
386) -> Option<isize> {
387 let Some(keystroke_or_modifier) = parse_keydown_msg_keystroke(wparam) else {
388 return Some(1);
389 };
390 let mut lock = state_ptr.state.borrow_mut();
391 let Some(mut func) = lock.callbacks.input.take() else {
392 return Some(1);
393 };
394 drop(lock);
395
396 let event = match keystroke_or_modifier {
397 KeystrokeOrModifier::Keystroke(keystroke) => PlatformInput::KeyDown(KeyDownEvent {
398 keystroke,
399 is_held: lparam.0 & (0x1 << 30) > 0,
400 }),
401 KeystrokeOrModifier::Modifier(modifiers) => {
402 PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers })
403 }
404 };
405
406 let result = if func(event).default_prevented {
407 Some(0)
408 } else {
409 Some(1)
410 };
411 state_ptr.state.borrow_mut().callbacks.input = Some(func);
412
413 result
414}
415
416fn handle_keyup_msg(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
417 let Some(keystroke_or_modifier) = parse_keydown_msg_keystroke(wparam) else {
418 return Some(1);
419 };
420 let mut lock = state_ptr.state.borrow_mut();
421 let Some(mut func) = lock.callbacks.input.take() else {
422 return Some(1);
423 };
424 drop(lock);
425
426 let event = match keystroke_or_modifier {
427 KeystrokeOrModifier::Keystroke(keystroke) => PlatformInput::KeyUp(KeyUpEvent { keystroke }),
428 KeystrokeOrModifier::Modifier(modifiers) => {
429 PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers })
430 }
431 };
432
433 let result = if func(event).default_prevented {
434 Some(0)
435 } else {
436 Some(1)
437 };
438 state_ptr.state.borrow_mut().callbacks.input = Some(func);
439
440 result
441}
442
443fn handle_char_msg(
444 wparam: WPARAM,
445 lparam: LPARAM,
446 state_ptr: Rc<WindowsWindowStatePtr>,
447) -> Option<isize> {
448 let Some(keystroke) = parse_char_msg_keystroke(wparam) else {
449 return Some(1);
450 };
451 let mut lock = state_ptr.state.borrow_mut();
452 let Some(mut func) = lock.callbacks.input.take() else {
453 return Some(1);
454 };
455 drop(lock);
456 let key_char = keystroke.key_char.clone();
457 let event = KeyDownEvent {
458 keystroke,
459 is_held: lparam.0 & (0x1 << 30) > 0,
460 };
461 let dispatch_event_result = func(PlatformInput::KeyDown(event));
462 state_ptr.state.borrow_mut().callbacks.input = Some(func);
463
464 if dispatch_event_result.default_prevented || !dispatch_event_result.propagate {
465 return Some(0);
466 }
467 let Some(ime_char) = key_char else {
468 return Some(1);
469 };
470 with_input_handler(&state_ptr, |input_handler| {
471 input_handler.replace_text_in_range(None, &ime_char);
472 });
473
474 Some(0)
475}
476
477fn handle_mouse_down_msg(
478 handle: HWND,
479 button: MouseButton,
480 lparam: LPARAM,
481 state_ptr: Rc<WindowsWindowStatePtr>,
482) -> Option<isize> {
483 unsafe { SetCapture(handle) };
484 let mut lock = state_ptr.state.borrow_mut();
485 if let Some(mut callback) = lock.callbacks.input.take() {
486 let x = lparam.signed_loword() as f32;
487 let y = lparam.signed_hiword() as f32;
488 let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
489 let click_count = lock.click_state.update(button, physical_point);
490 let scale_factor = lock.scale_factor;
491 drop(lock);
492
493 let event = MouseDownEvent {
494 button,
495 position: logical_point(x, y, scale_factor),
496 modifiers: current_modifiers(),
497 click_count,
498 first_mouse: false,
499 };
500 let result = if callback(PlatformInput::MouseDown(event)).default_prevented {
501 Some(0)
502 } else {
503 Some(1)
504 };
505 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
506
507 result
508 } else {
509 Some(1)
510 }
511}
512
513fn handle_mouse_up_msg(
514 _handle: HWND,
515 button: MouseButton,
516 lparam: LPARAM,
517 state_ptr: Rc<WindowsWindowStatePtr>,
518) -> Option<isize> {
519 unsafe { ReleaseCapture().log_err() };
520 let mut lock = state_ptr.state.borrow_mut();
521 if let Some(mut callback) = lock.callbacks.input.take() {
522 let x = lparam.signed_loword() as f32;
523 let y = lparam.signed_hiword() as f32;
524 let click_count = lock.click_state.current_count;
525 let scale_factor = lock.scale_factor;
526 drop(lock);
527
528 let event = MouseUpEvent {
529 button,
530 position: logical_point(x, y, scale_factor),
531 modifiers: current_modifiers(),
532 click_count,
533 };
534 let result = if callback(PlatformInput::MouseUp(event)).default_prevented {
535 Some(0)
536 } else {
537 Some(1)
538 };
539 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
540
541 result
542 } else {
543 Some(1)
544 }
545}
546
547fn handle_xbutton_msg(
548 handle: HWND,
549 wparam: WPARAM,
550 lparam: LPARAM,
551 handler: impl Fn(HWND, MouseButton, LPARAM, Rc<WindowsWindowStatePtr>) -> Option<isize>,
552 state_ptr: Rc<WindowsWindowStatePtr>,
553) -> Option<isize> {
554 let nav_dir = match wparam.hiword() {
555 XBUTTON1 => NavigationDirection::Back,
556 XBUTTON2 => NavigationDirection::Forward,
557 _ => return Some(1),
558 };
559 handler(handle, MouseButton::Navigate(nav_dir), lparam, state_ptr)
560}
561
562fn handle_mouse_wheel_msg(
563 handle: HWND,
564 wparam: WPARAM,
565 lparam: LPARAM,
566 state_ptr: Rc<WindowsWindowStatePtr>,
567) -> Option<isize> {
568 let modifiers = current_modifiers();
569 let mut lock = state_ptr.state.borrow_mut();
570 if let Some(mut callback) = lock.callbacks.input.take() {
571 let scale_factor = lock.scale_factor;
572 let wheel_scroll_amount = match modifiers.shift {
573 true => lock.system_settings.mouse_wheel_settings.wheel_scroll_chars,
574 false => lock.system_settings.mouse_wheel_settings.wheel_scroll_lines,
575 };
576 drop(lock);
577 let wheel_distance =
578 (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_amount as f32;
579 let mut cursor_point = POINT {
580 x: lparam.signed_loword().into(),
581 y: lparam.signed_hiword().into(),
582 };
583 unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
584 let event = ScrollWheelEvent {
585 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
586 delta: ScrollDelta::Lines(match modifiers.shift {
587 true => Point {
588 x: wheel_distance,
589 y: 0.0,
590 },
591 false => Point {
592 y: wheel_distance,
593 x: 0.0,
594 },
595 }),
596 modifiers: current_modifiers(),
597 touch_phase: TouchPhase::Moved,
598 };
599 let result = if callback(PlatformInput::ScrollWheel(event)).default_prevented {
600 Some(0)
601 } else {
602 Some(1)
603 };
604 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
605
606 result
607 } else {
608 Some(1)
609 }
610}
611
612fn handle_mouse_horizontal_wheel_msg(
613 handle: HWND,
614 wparam: WPARAM,
615 lparam: LPARAM,
616 state_ptr: Rc<WindowsWindowStatePtr>,
617) -> Option<isize> {
618 let mut lock = state_ptr.state.borrow_mut();
619 if let Some(mut callback) = lock.callbacks.input.take() {
620 let scale_factor = lock.scale_factor;
621 let wheel_scroll_chars = lock.system_settings.mouse_wheel_settings.wheel_scroll_chars;
622 drop(lock);
623 let wheel_distance =
624 (-wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_chars as f32;
625 let mut cursor_point = POINT {
626 x: lparam.signed_loword().into(),
627 y: lparam.signed_hiword().into(),
628 };
629 unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
630 let event = ScrollWheelEvent {
631 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
632 delta: ScrollDelta::Lines(Point {
633 x: wheel_distance,
634 y: 0.0,
635 }),
636 modifiers: current_modifiers(),
637 touch_phase: TouchPhase::Moved,
638 };
639 let result = if callback(PlatformInput::ScrollWheel(event)).default_prevented {
640 Some(0)
641 } else {
642 Some(1)
643 };
644 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
645
646 result
647 } else {
648 Some(1)
649 }
650}
651
652fn retrieve_caret_position(state_ptr: &Rc<WindowsWindowStatePtr>) -> Option<POINT> {
653 with_input_handler_and_scale_factor(state_ptr, |input_handler, scale_factor| {
654 let caret_range = input_handler.selected_text_range(false)?;
655 let caret_position = input_handler.bounds_for_range(caret_range.range)?;
656 Some(POINT {
657 // logical to physical
658 x: (caret_position.origin.x.0 * scale_factor) as i32,
659 y: (caret_position.origin.y.0 * scale_factor) as i32
660 + ((caret_position.size.height.0 * scale_factor) as i32 / 2),
661 })
662 })
663}
664
665fn handle_ime_position(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
666 unsafe {
667 let ctx = ImmGetContext(handle);
668
669 let Some(caret_position) = retrieve_caret_position(&state_ptr) else {
670 return Some(0);
671 };
672 {
673 let config = COMPOSITIONFORM {
674 dwStyle: CFS_POINT,
675 ptCurrentPos: caret_position,
676 ..Default::default()
677 };
678 ImmSetCompositionWindow(ctx, &config as _).ok().log_err();
679 }
680 {
681 let config = CANDIDATEFORM {
682 dwStyle: CFS_CANDIDATEPOS,
683 ptCurrentPos: caret_position,
684 ..Default::default()
685 };
686 ImmSetCandidateWindow(ctx, &config as _).ok().log_err();
687 }
688 ImmReleaseContext(handle, ctx).ok().log_err();
689 Some(0)
690 }
691}
692
693fn handle_ime_composition(
694 handle: HWND,
695 lparam: LPARAM,
696 state_ptr: Rc<WindowsWindowStatePtr>,
697) -> Option<isize> {
698 let ctx = unsafe { ImmGetContext(handle) };
699 let result = handle_ime_composition_inner(ctx, lparam, state_ptr);
700 unsafe { ImmReleaseContext(handle, ctx).ok().log_err() };
701 result
702}
703
704fn handle_ime_composition_inner(
705 ctx: HIMC,
706 lparam: LPARAM,
707 state_ptr: Rc<WindowsWindowStatePtr>,
708) -> Option<isize> {
709 let mut ime_input = None;
710 if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
711 let (comp_string, string_len) = parse_ime_compostion_string(ctx)?;
712 with_input_handler(&state_ptr, |input_handler| {
713 input_handler.replace_and_mark_text_in_range(
714 None,
715 &comp_string,
716 Some(string_len..string_len),
717 );
718 })?;
719 ime_input = Some(comp_string);
720 }
721 if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
722 let comp_string = &ime_input?;
723 let caret_pos = retrieve_composition_cursor_position(ctx);
724 with_input_handler(&state_ptr, |input_handler| {
725 input_handler.replace_and_mark_text_in_range(
726 None,
727 comp_string,
728 Some(caret_pos..caret_pos),
729 );
730 })?;
731 }
732 if lparam.0 as u32 & GCS_RESULTSTR.0 > 0 {
733 let comp_result = parse_ime_compostion_result(ctx)?;
734 with_input_handler(&state_ptr, |input_handler| {
735 input_handler.replace_text_in_range(None, &comp_result);
736 })?;
737 return Some(0);
738 }
739 // currently, we don't care other stuff
740 None
741}
742
743/// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
744fn handle_calc_client_size(
745 handle: HWND,
746 wparam: WPARAM,
747 lparam: LPARAM,
748 state_ptr: Rc<WindowsWindowStatePtr>,
749) -> Option<isize> {
750 if !state_ptr.hide_title_bar || state_ptr.state.borrow().is_fullscreen() || wparam.0 == 0 {
751 return None;
752 }
753
754 let is_maximized = state_ptr.state.borrow().is_maximized();
755 let insets = get_client_area_insets(handle, is_maximized, state_ptr.windows_version);
756 // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure
757 let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS;
758 let mut requested_client_rect = unsafe { &mut ((*params).rgrc) };
759
760 requested_client_rect[0].left += insets.left;
761 requested_client_rect[0].top += insets.top;
762 requested_client_rect[0].right -= insets.right;
763 requested_client_rect[0].bottom -= insets.bottom;
764
765 // Fix auto hide taskbar not showing. This solution is based on the approach
766 // used by Chrome. However, it may result in one row of pixels being obscured
767 // in our client area. But as Chrome says, "there seems to be no better solution."
768 if is_maximized {
769 if let Some(ref taskbar_position) = state_ptr
770 .state
771 .borrow()
772 .system_settings
773 .auto_hide_taskbar_position
774 {
775 // Fot the auto-hide taskbar, adjust in by 1 pixel on taskbar edge,
776 // so the window isn't treated as a "fullscreen app", which would cause
777 // the taskbar to disappear.
778 match taskbar_position {
779 AutoHideTaskbarPosition::Left => {
780 requested_client_rect[0].left += AUTO_HIDE_TASKBAR_THICKNESS_PX
781 }
782 AutoHideTaskbarPosition::Top => {
783 requested_client_rect[0].top += AUTO_HIDE_TASKBAR_THICKNESS_PX
784 }
785 AutoHideTaskbarPosition::Right => {
786 requested_client_rect[0].right -= AUTO_HIDE_TASKBAR_THICKNESS_PX
787 }
788 AutoHideTaskbarPosition::Bottom => {
789 requested_client_rect[0].bottom -= AUTO_HIDE_TASKBAR_THICKNESS_PX
790 }
791 }
792 }
793 }
794
795 Some(0)
796}
797
798fn handle_activate_msg(
799 handle: HWND,
800 wparam: WPARAM,
801 state_ptr: Rc<WindowsWindowStatePtr>,
802) -> Option<isize> {
803 let activated = wparam.loword() > 0;
804 if state_ptr.hide_title_bar {
805 if let Some(titlebar_rect) = state_ptr.state.borrow().get_titlebar_rect().log_err() {
806 unsafe {
807 InvalidateRect(handle, Some(&titlebar_rect), FALSE)
808 .ok()
809 .log_err()
810 };
811 }
812 }
813 let this = state_ptr.clone();
814 state_ptr
815 .executor
816 .spawn(async move {
817 let mut lock = this.state.borrow_mut();
818 if let Some(mut cb) = lock.callbacks.active_status_change.take() {
819 drop(lock);
820 cb(activated);
821 this.state.borrow_mut().callbacks.active_status_change = Some(cb);
822 }
823 })
824 .detach();
825
826 None
827}
828
829fn handle_create_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
830 if state_ptr.hide_title_bar {
831 notify_frame_changed(handle);
832 Some(0)
833 } else {
834 None
835 }
836}
837
838fn handle_dpi_changed_msg(
839 handle: HWND,
840 wparam: WPARAM,
841 lparam: LPARAM,
842 state_ptr: Rc<WindowsWindowStatePtr>,
843) -> Option<isize> {
844 let new_dpi = wparam.loword() as f32;
845 let mut lock = state_ptr.state.borrow_mut();
846 lock.scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
847 lock.border_offset.update(handle).log_err();
848 drop(lock);
849
850 let rect = unsafe { &*(lparam.0 as *const RECT) };
851 let width = rect.right - rect.left;
852 let height = rect.bottom - rect.top;
853 // this will emit `WM_SIZE` and `WM_MOVE` right here
854 // even before this function returns
855 // the new size is handled in `WM_SIZE`
856 unsafe {
857 SetWindowPos(
858 handle,
859 None,
860 rect.left,
861 rect.top,
862 width,
863 height,
864 SWP_NOZORDER | SWP_NOACTIVATE,
865 )
866 .context("unable to set window position after dpi has changed")
867 .log_err();
868 }
869
870 Some(0)
871}
872
873/// The following conditions will trigger this event:
874/// 1. The monitor on which the window is located goes offline or changes resolution.
875/// 2. Another monitor goes offline, is plugged in, or changes resolution.
876///
877/// In either case, the window will only receive information from the monitor on which
878/// it is located.
879///
880/// For example, in the case of condition 2, where the monitor on which the window is
881/// located has actually changed nothing, it will still receive this event.
882fn handle_display_change_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
883 // NOTE:
884 // Even the `lParam` holds the resolution of the screen, we just ignore it.
885 // Because WM_DPICHANGED, WM_MOVE, WM_SIZE will come first, window reposition and resize
886 // are handled there.
887 // So we only care about if monitor is disconnected.
888 let previous_monitor = state_ptr.as_ref().state.borrow().display;
889 if WindowsDisplay::is_connected(previous_monitor.handle) {
890 // we are fine, other display changed
891 return None;
892 }
893 // display disconnected
894 // in this case, the OS will move our window to another monitor, and minimize it.
895 // we deminimize the window and query the monitor after moving
896 unsafe {
897 let _ = ShowWindow(handle, SW_SHOWNORMAL);
898 };
899 let new_monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) };
900 // all monitors disconnected
901 if new_monitor.is_invalid() {
902 log::error!("No monitor detected!");
903 return None;
904 }
905 let new_display = WindowsDisplay::new_with_handle(new_monitor);
906 state_ptr.as_ref().state.borrow_mut().display = new_display;
907 Some(0)
908}
909
910fn handle_hit_test_msg(
911 handle: HWND,
912 msg: u32,
913 wparam: WPARAM,
914 lparam: LPARAM,
915 state_ptr: Rc<WindowsWindowStatePtr>,
916) -> Option<isize> {
917 if !state_ptr.is_movable {
918 return None;
919 }
920 if !state_ptr.hide_title_bar {
921 return None;
922 }
923
924 // default handler for resize areas
925 let hit = unsafe { DefWindowProcW(handle, msg, wparam, lparam) };
926 if matches!(
927 hit.0 as u32,
928 HTNOWHERE
929 | HTRIGHT
930 | HTLEFT
931 | HTTOPLEFT
932 | HTTOP
933 | HTTOPRIGHT
934 | HTBOTTOMRIGHT
935 | HTBOTTOM
936 | HTBOTTOMLEFT
937 ) {
938 return Some(hit.0);
939 }
940
941 if state_ptr.state.borrow().is_fullscreen() {
942 return Some(HTCLIENT as _);
943 }
944
945 let dpi = unsafe { GetDpiForWindow(handle) };
946 let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
947
948 let mut cursor_point = POINT {
949 x: lparam.signed_loword().into(),
950 y: lparam.signed_hiword().into(),
951 };
952 unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
953 if !state_ptr.state.borrow().is_maximized() && cursor_point.y >= 0 && cursor_point.y <= frame_y
954 {
955 return Some(HTTOP as _);
956 }
957
958 let titlebar_rect = state_ptr.state.borrow().get_titlebar_rect();
959 if let Ok(titlebar_rect) = titlebar_rect {
960 if cursor_point.y < titlebar_rect.bottom {
961 let caption_btn_width = (state_ptr.state.borrow().caption_button_width().0
962 * state_ptr.state.borrow().scale_factor) as i32;
963 if cursor_point.x >= titlebar_rect.right - caption_btn_width {
964 return Some(HTCLOSE as _);
965 } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 {
966 return Some(HTMAXBUTTON as _);
967 } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 {
968 return Some(HTMINBUTTON as _);
969 }
970
971 return Some(HTCAPTION as _);
972 }
973 }
974
975 Some(HTCLIENT as _)
976}
977
978fn handle_nc_mouse_move_msg(
979 handle: HWND,
980 lparam: LPARAM,
981 state_ptr: Rc<WindowsWindowStatePtr>,
982) -> Option<isize> {
983 if !state_ptr.hide_title_bar {
984 return None;
985 }
986
987 let mut lock = state_ptr.state.borrow_mut();
988 if !lock.hovered {
989 lock.hovered = true;
990 unsafe {
991 TrackMouseEvent(&mut TRACKMOUSEEVENT {
992 cbSize: std::mem::size_of::<TRACKMOUSEEVENT>() as u32,
993 dwFlags: TME_LEAVE | TME_NONCLIENT,
994 hwndTrack: handle,
995 dwHoverTime: HOVER_DEFAULT,
996 })
997 .log_err()
998 };
999 if let Some(mut callback) = lock.callbacks.hovered_status_change.take() {
1000 drop(lock);
1001 callback(true);
1002 state_ptr.state.borrow_mut().callbacks.hovered_status_change = Some(callback);
1003 }
1004 } else {
1005 drop(lock);
1006 }
1007
1008 let mut lock = state_ptr.state.borrow_mut();
1009 if let Some(mut callback) = lock.callbacks.input.take() {
1010 let scale_factor = lock.scale_factor;
1011 drop(lock);
1012 let mut cursor_point = POINT {
1013 x: lparam.signed_loword().into(),
1014 y: lparam.signed_hiword().into(),
1015 };
1016 unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
1017 let event = MouseMoveEvent {
1018 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1019 pressed_button: None,
1020 modifiers: current_modifiers(),
1021 };
1022 let result = if callback(PlatformInput::MouseMove(event)).default_prevented {
1023 Some(0)
1024 } else {
1025 Some(1)
1026 };
1027 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
1028
1029 result
1030 } else {
1031 None
1032 }
1033}
1034
1035fn handle_nc_mouse_down_msg(
1036 handle: HWND,
1037 button: MouseButton,
1038 wparam: WPARAM,
1039 lparam: LPARAM,
1040 state_ptr: Rc<WindowsWindowStatePtr>,
1041) -> Option<isize> {
1042 if !state_ptr.hide_title_bar {
1043 return None;
1044 }
1045
1046 let mut lock = state_ptr.state.borrow_mut();
1047 if let Some(mut callback) = lock.callbacks.input.take() {
1048 let scale_factor = lock.scale_factor;
1049 let mut cursor_point = POINT {
1050 x: lparam.signed_loword().into(),
1051 y: lparam.signed_hiword().into(),
1052 };
1053 unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
1054 let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
1055 let click_count = lock.click_state.update(button, physical_point);
1056 drop(lock);
1057 let event = MouseDownEvent {
1058 button,
1059 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1060 modifiers: current_modifiers(),
1061 click_count,
1062 first_mouse: false,
1063 };
1064 let result = if callback(PlatformInput::MouseDown(event)).default_prevented {
1065 Some(0)
1066 } else {
1067 None
1068 };
1069 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
1070
1071 if result.is_some() {
1072 return result;
1073 }
1074 } else {
1075 drop(lock);
1076 };
1077
1078 // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
1079 if button == MouseButton::Left {
1080 match wparam.0 as u32 {
1081 HTMINBUTTON => state_ptr.state.borrow_mut().nc_button_pressed = Some(HTMINBUTTON),
1082 HTMAXBUTTON => state_ptr.state.borrow_mut().nc_button_pressed = Some(HTMAXBUTTON),
1083 HTCLOSE => state_ptr.state.borrow_mut().nc_button_pressed = Some(HTCLOSE),
1084 _ => return None,
1085 };
1086 Some(0)
1087 } else {
1088 None
1089 }
1090}
1091
1092fn handle_nc_mouse_up_msg(
1093 handle: HWND,
1094 button: MouseButton,
1095 wparam: WPARAM,
1096 lparam: LPARAM,
1097 state_ptr: Rc<WindowsWindowStatePtr>,
1098) -> Option<isize> {
1099 if !state_ptr.hide_title_bar {
1100 return None;
1101 }
1102
1103 let mut lock = state_ptr.state.borrow_mut();
1104 if let Some(mut callback) = lock.callbacks.input.take() {
1105 let scale_factor = lock.scale_factor;
1106 drop(lock);
1107 let mut cursor_point = POINT {
1108 x: lparam.signed_loword().into(),
1109 y: lparam.signed_hiword().into(),
1110 };
1111 unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
1112 let event = MouseUpEvent {
1113 button,
1114 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1115 modifiers: current_modifiers(),
1116 click_count: 1,
1117 };
1118 let result = if callback(PlatformInput::MouseUp(event)).default_prevented {
1119 Some(0)
1120 } else {
1121 None
1122 };
1123 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
1124 if result.is_some() {
1125 return result;
1126 }
1127 } else {
1128 drop(lock);
1129 }
1130
1131 let last_pressed = state_ptr.state.borrow_mut().nc_button_pressed.take();
1132 if button == MouseButton::Left && last_pressed.is_some() {
1133 let last_button = last_pressed.unwrap();
1134 let mut handled = false;
1135 match wparam.0 as u32 {
1136 HTMINBUTTON => {
1137 if last_button == HTMINBUTTON {
1138 unsafe { ShowWindowAsync(handle, SW_MINIMIZE).ok().log_err() };
1139 handled = true;
1140 }
1141 }
1142 HTMAXBUTTON => {
1143 if last_button == HTMAXBUTTON {
1144 if state_ptr.state.borrow().is_maximized() {
1145 unsafe { ShowWindowAsync(handle, SW_NORMAL).ok().log_err() };
1146 } else {
1147 unsafe { ShowWindowAsync(handle, SW_MAXIMIZE).ok().log_err() };
1148 }
1149 handled = true;
1150 }
1151 }
1152 HTCLOSE => {
1153 if last_button == HTCLOSE {
1154 unsafe {
1155 PostMessageW(handle, WM_CLOSE, WPARAM::default(), LPARAM::default())
1156 .log_err()
1157 };
1158 handled = true;
1159 }
1160 }
1161 _ => {}
1162 };
1163 if handled {
1164 return Some(0);
1165 }
1166 }
1167
1168 None
1169}
1170
1171fn handle_cursor_changed(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
1172 state_ptr.state.borrow_mut().current_cursor = HCURSOR(lparam.0 as _);
1173 Some(0)
1174}
1175
1176fn handle_set_cursor(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
1177 if matches!(
1178 lparam.loword() as u32,
1179 HTLEFT | HTRIGHT | HTTOP | HTTOPLEFT | HTTOPRIGHT | HTBOTTOM | HTBOTTOMLEFT | HTBOTTOMRIGHT
1180 ) {
1181 return None;
1182 }
1183 unsafe { SetCursor(state_ptr.state.borrow().current_cursor) };
1184 Some(1)
1185}
1186
1187fn handle_system_settings_changed(
1188 handle: HWND,
1189 state_ptr: Rc<WindowsWindowStatePtr>,
1190) -> Option<isize> {
1191 let mut lock = state_ptr.state.borrow_mut();
1192 let display = lock.display;
1193 // system settings
1194 lock.system_settings.update(display);
1195 // mouse double click
1196 lock.click_state.system_update();
1197 // window border offset
1198 lock.border_offset.update(handle).log_err();
1199 drop(lock);
1200 // Force to trigger WM_NCCALCSIZE event to ensure that we handle auto hide
1201 // taskbar correctly.
1202 notify_frame_changed(handle);
1203 Some(0)
1204}
1205
1206fn handle_system_command(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
1207 if wparam.0 == SC_KEYMENU as usize {
1208 let mut lock = state_ptr.state.borrow_mut();
1209 if lock.system_key_handled {
1210 lock.system_key_handled = false;
1211 return Some(0);
1212 }
1213 }
1214 None
1215}
1216
1217fn handle_system_theme_changed(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
1218 let mut callback = state_ptr
1219 .state
1220 .borrow_mut()
1221 .callbacks
1222 .appearance_changed
1223 .take()?;
1224 callback();
1225 state_ptr.state.borrow_mut().callbacks.appearance_changed = Some(callback);
1226 Some(0)
1227}
1228
1229fn parse_syskeydown_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
1230 let modifiers = current_modifiers();
1231 if !modifiers.alt {
1232 // on Windows, F10 can trigger this event, not just the alt key
1233 // and we just don't care about F10
1234 return None;
1235 }
1236
1237 let vk_code = wparam.loword();
1238
1239 let key = match VIRTUAL_KEY(vk_code) {
1240 VK_BACK => "backspace",
1241 VK_RETURN => "enter",
1242 VK_TAB => "tab",
1243 VK_UP => "up",
1244 VK_DOWN => "down",
1245 VK_RIGHT => "right",
1246 VK_LEFT => "left",
1247 VK_HOME => "home",
1248 VK_END => "end",
1249 VK_PRIOR => "pageup",
1250 VK_NEXT => "pagedown",
1251 VK_BROWSER_BACK => "back",
1252 VK_BROWSER_FORWARD => "forward",
1253 VK_ESCAPE => "escape",
1254 VK_INSERT => "insert",
1255 VK_DELETE => "delete",
1256 _ => return basic_vkcode_to_string(vk_code, modifiers),
1257 }
1258 .to_owned();
1259
1260 Some(Keystroke {
1261 modifiers,
1262 key,
1263 key_char: None,
1264 })
1265}
1266
1267enum KeystrokeOrModifier {
1268 Keystroke(Keystroke),
1269 Modifier(Modifiers),
1270}
1271
1272fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option<KeystrokeOrModifier> {
1273 let vk_code = wparam.loword();
1274
1275 let modifiers = current_modifiers();
1276
1277 let key = match VIRTUAL_KEY(vk_code) {
1278 VK_BACK => "backspace",
1279 VK_RETURN => "enter",
1280 VK_TAB => "tab",
1281 VK_UP => "up",
1282 VK_DOWN => "down",
1283 VK_RIGHT => "right",
1284 VK_LEFT => "left",
1285 VK_HOME => "home",
1286 VK_END => "end",
1287 VK_PRIOR => "pageup",
1288 VK_NEXT => "pagedown",
1289 VK_BROWSER_BACK => "back",
1290 VK_BROWSER_FORWARD => "forward",
1291 VK_ESCAPE => "escape",
1292 VK_INSERT => "insert",
1293 VK_DELETE => "delete",
1294 _ => {
1295 if is_modifier(VIRTUAL_KEY(vk_code)) {
1296 return Some(KeystrokeOrModifier::Modifier(modifiers));
1297 }
1298
1299 if modifiers.control || modifiers.alt {
1300 let basic_key = basic_vkcode_to_string(vk_code, modifiers);
1301 if let Some(basic_key) = basic_key {
1302 return Some(KeystrokeOrModifier::Keystroke(basic_key));
1303 }
1304 }
1305
1306 if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 {
1307 let offset = vk_code - VK_F1.0;
1308 return Some(KeystrokeOrModifier::Keystroke(Keystroke {
1309 modifiers,
1310 key: format!("f{}", offset + 1),
1311 key_char: None,
1312 }));
1313 };
1314 return None;
1315 }
1316 }
1317 .to_owned();
1318
1319 Some(KeystrokeOrModifier::Keystroke(Keystroke {
1320 modifiers,
1321 key,
1322 key_char: None,
1323 }))
1324}
1325
1326fn parse_char_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
1327 let first_char = char::from_u32((wparam.0 as u16).into())?;
1328 if first_char.is_control() {
1329 None
1330 } else {
1331 let mut modifiers = current_modifiers();
1332 // for characters that use 'shift' to type it is expected that the
1333 // shift is not reported if the uppercase/lowercase are the same and instead only the key is reported
1334 if first_char.to_ascii_uppercase() == first_char.to_ascii_lowercase() {
1335 modifiers.shift = false;
1336 }
1337 let key = match first_char {
1338 ' ' => "space".to_string(),
1339 first_char => first_char.to_lowercase().to_string(),
1340 };
1341 Some(Keystroke {
1342 modifiers,
1343 key,
1344 key_char: Some(first_char.to_string()),
1345 })
1346 }
1347}
1348
1349fn parse_ime_compostion_string(ctx: HIMC) -> Option<(String, usize)> {
1350 unsafe {
1351 let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0);
1352 if string_len >= 0 {
1353 let mut buffer = vec![0u8; string_len as usize + 2];
1354 ImmGetCompositionStringW(
1355 ctx,
1356 GCS_COMPSTR,
1357 Some(buffer.as_mut_ptr() as _),
1358 string_len as _,
1359 );
1360 let wstring = std::slice::from_raw_parts::<u16>(
1361 buffer.as_mut_ptr().cast::<u16>(),
1362 string_len as usize / 2,
1363 );
1364 let string = String::from_utf16_lossy(wstring);
1365 Some((string, string_len as usize / 2))
1366 } else {
1367 None
1368 }
1369 }
1370}
1371
1372#[inline]
1373fn retrieve_composition_cursor_position(ctx: HIMC) -> usize {
1374 unsafe { ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0) as usize }
1375}
1376
1377fn parse_ime_compostion_result(ctx: HIMC) -> Option<String> {
1378 unsafe {
1379 let string_len = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, None, 0);
1380 if string_len >= 0 {
1381 let mut buffer = vec![0u8; string_len as usize + 2];
1382 ImmGetCompositionStringW(
1383 ctx,
1384 GCS_RESULTSTR,
1385 Some(buffer.as_mut_ptr() as _),
1386 string_len as _,
1387 );
1388 let wstring = std::slice::from_raw_parts::<u16>(
1389 buffer.as_mut_ptr().cast::<u16>(),
1390 string_len as usize / 2,
1391 );
1392 let string = String::from_utf16_lossy(wstring);
1393 Some(string)
1394 } else {
1395 None
1396 }
1397 }
1398}
1399
1400fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
1401 let mapped_code = unsafe { MapVirtualKeyW(code as u32, MAPVK_VK_TO_CHAR) };
1402
1403 let key = match mapped_code {
1404 0 => None,
1405 raw_code => char::from_u32(raw_code),
1406 }?
1407 .to_ascii_lowercase();
1408
1409 let key = if matches!(code as u32, 112..=135) {
1410 format!("f{key}")
1411 } else {
1412 key.to_string()
1413 };
1414
1415 Some(Keystroke {
1416 modifiers,
1417 key,
1418 key_char: None,
1419 })
1420}
1421
1422#[inline]
1423fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool {
1424 unsafe { GetKeyState(vkey.0 as i32) < 0 }
1425}
1426
1427fn is_modifier(virtual_key: VIRTUAL_KEY) -> bool {
1428 matches!(
1429 virtual_key,
1430 VK_CONTROL | VK_MENU | VK_SHIFT | VK_LWIN | VK_RWIN
1431 )
1432}
1433
1434#[inline]
1435pub(crate) fn current_modifiers() -> Modifiers {
1436 Modifiers {
1437 control: is_virtual_key_pressed(VK_CONTROL),
1438 alt: is_virtual_key_pressed(VK_MENU),
1439 shift: is_virtual_key_pressed(VK_SHIFT),
1440 platform: is_virtual_key_pressed(VK_LWIN) || is_virtual_key_pressed(VK_RWIN),
1441 function: false,
1442 }
1443}
1444
1445fn get_client_area_insets(
1446 handle: HWND,
1447 is_maximized: bool,
1448 windows_version: WindowsVersion,
1449) -> RECT {
1450 // For maximized windows, Windows outdents the window rect from the screen's client rect
1451 // by `frame_thickness` on each edge, meaning `insets` must contain `frame_thickness`
1452 // on all sides (including the top) to avoid the client area extending onto adjacent
1453 // monitors.
1454 //
1455 // For non-maximized windows, things become complicated:
1456 //
1457 // - On Windows 10
1458 // The top inset must be zero, since if there is any nonclient area, Windows will draw
1459 // a full native titlebar outside the client area. (This doesn't occur in the maximized
1460 // case.)
1461 //
1462 // - On Windows 11
1463 // The top inset is calculated using an empirical formula that I derived through various
1464 // tests. Without this, the top 1-2 rows of pixels in our window would be obscured.
1465 let dpi = unsafe { GetDpiForWindow(handle) };
1466 let frame_thickness = get_frame_thickness(dpi);
1467 let top_insets = if is_maximized {
1468 frame_thickness
1469 } else {
1470 match windows_version {
1471 WindowsVersion::Win10 => 0,
1472 WindowsVersion::Win11 => (dpi as f32 / USER_DEFAULT_SCREEN_DPI as f32).round() as i32,
1473 }
1474 };
1475 RECT {
1476 left: frame_thickness,
1477 top: top_insets,
1478 right: frame_thickness,
1479 bottom: frame_thickness,
1480 }
1481}
1482
1483// there is some additional non-visible space when talking about window
1484// borders on Windows:
1485// - SM_CXSIZEFRAME: The resize handle.
1486// - SM_CXPADDEDBORDER: Additional border space that isn't part of the resize handle.
1487fn get_frame_thickness(dpi: u32) -> i32 {
1488 let resize_frame_thickness = unsafe { GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) };
1489 let padding_thickness = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
1490 resize_frame_thickness + padding_thickness
1491}
1492
1493fn notify_frame_changed(handle: HWND) {
1494 unsafe {
1495 SetWindowPos(
1496 handle,
1497 None,
1498 0,
1499 0,
1500 0,
1501 0,
1502 SWP_FRAMECHANGED
1503 | SWP_NOACTIVATE
1504 | SWP_NOCOPYBITS
1505 | SWP_NOMOVE
1506 | SWP_NOOWNERZORDER
1507 | SWP_NOREPOSITION
1508 | SWP_NOSENDCHANGING
1509 | SWP_NOSIZE
1510 | SWP_NOZORDER,
1511 )
1512 .log_err();
1513 }
1514}
1515
1516fn with_input_handler<F, R>(state_ptr: &Rc<WindowsWindowStatePtr>, f: F) -> Option<R>
1517where
1518 F: FnOnce(&mut PlatformInputHandler) -> R,
1519{
1520 let mut input_handler = state_ptr.state.borrow_mut().input_handler.take()?;
1521 let result = f(&mut input_handler);
1522 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
1523 Some(result)
1524}
1525
1526fn with_input_handler_and_scale_factor<F, R>(
1527 state_ptr: &Rc<WindowsWindowStatePtr>,
1528 f: F,
1529) -> Option<R>
1530where
1531 F: FnOnce(&mut PlatformInputHandler, f32) -> Option<R>,
1532{
1533 let mut lock = state_ptr.state.borrow_mut();
1534 let mut input_handler = lock.input_handler.take()?;
1535 let scale_factor = lock.scale_factor;
1536 drop(lock);
1537 let result = f(&mut input_handler, scale_factor);
1538 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
1539 result
1540}