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