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