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