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