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 if lparam.0 == 0 {
683 // Japanese IME may send this message with lparam = 0, which indicates that
684 // there is no composition string.
685 with_input_handler(&state_ptr, |input_handler| {
686 input_handler.replace_text_in_range(None, "");
687 })?;
688 return Some(0);
689 }
690 let mut ime_input = None;
691 if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
692 let comp_string = parse_ime_compostion_string(ctx)?;
693 with_input_handler(&state_ptr, |input_handler| {
694 input_handler.replace_and_mark_text_in_range(None, &comp_string, None);
695 })?;
696 ime_input = Some(comp_string);
697 }
698 if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
699 let comp_string = &ime_input?;
700 let caret_pos = retrieve_composition_cursor_position(ctx);
701 with_input_handler(&state_ptr, |input_handler| {
702 input_handler.replace_and_mark_text_in_range(
703 None,
704 comp_string,
705 Some(caret_pos..caret_pos),
706 );
707 })?;
708 }
709 if lparam.0 as u32 & GCS_RESULTSTR.0 > 0 {
710 let comp_result = parse_ime_compostion_result(ctx)?;
711 with_input_handler(&state_ptr, |input_handler| {
712 input_handler.replace_text_in_range(None, &comp_result);
713 })?;
714 return Some(0);
715 }
716 // currently, we don't care other stuff
717 None
718}
719
720/// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
721fn handle_calc_client_size(
722 handle: HWND,
723 wparam: WPARAM,
724 lparam: LPARAM,
725 state_ptr: Rc<WindowsWindowStatePtr>,
726) -> Option<isize> {
727 if !state_ptr.hide_title_bar || state_ptr.state.borrow().is_fullscreen() || wparam.0 == 0 {
728 return None;
729 }
730
731 let is_maximized = state_ptr.state.borrow().is_maximized();
732 let insets = get_client_area_insets(handle, is_maximized, state_ptr.windows_version);
733 // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure
734 let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS;
735 let mut requested_client_rect = unsafe { &mut ((*params).rgrc) };
736
737 requested_client_rect[0].left += insets.left;
738 requested_client_rect[0].top += insets.top;
739 requested_client_rect[0].right -= insets.right;
740 requested_client_rect[0].bottom -= insets.bottom;
741
742 // Fix auto hide taskbar not showing. This solution is based on the approach
743 // used by Chrome. However, it may result in one row of pixels being obscured
744 // in our client area. But as Chrome says, "there seems to be no better solution."
745 if is_maximized {
746 if let Some(ref taskbar_position) = state_ptr
747 .state
748 .borrow()
749 .system_settings
750 .auto_hide_taskbar_position
751 {
752 // Fot the auto-hide taskbar, adjust in by 1 pixel on taskbar edge,
753 // so the window isn't treated as a "fullscreen app", which would cause
754 // the taskbar to disappear.
755 match taskbar_position {
756 AutoHideTaskbarPosition::Left => {
757 requested_client_rect[0].left += AUTO_HIDE_TASKBAR_THICKNESS_PX
758 }
759 AutoHideTaskbarPosition::Top => {
760 requested_client_rect[0].top += AUTO_HIDE_TASKBAR_THICKNESS_PX
761 }
762 AutoHideTaskbarPosition::Right => {
763 requested_client_rect[0].right -= AUTO_HIDE_TASKBAR_THICKNESS_PX
764 }
765 AutoHideTaskbarPosition::Bottom => {
766 requested_client_rect[0].bottom -= AUTO_HIDE_TASKBAR_THICKNESS_PX
767 }
768 }
769 }
770 }
771
772 Some(0)
773}
774
775fn handle_activate_msg(
776 handle: HWND,
777 wparam: WPARAM,
778 state_ptr: Rc<WindowsWindowStatePtr>,
779) -> Option<isize> {
780 let activated = wparam.loword() > 0;
781 if state_ptr.hide_title_bar {
782 if let Some(titlebar_rect) = state_ptr.state.borrow().get_titlebar_rect().log_err() {
783 unsafe {
784 InvalidateRect(Some(handle), Some(&titlebar_rect), false)
785 .ok()
786 .log_err()
787 };
788 }
789 }
790 let this = state_ptr.clone();
791 state_ptr
792 .executor
793 .spawn(async move {
794 let mut lock = this.state.borrow_mut();
795 if let Some(mut func) = lock.callbacks.active_status_change.take() {
796 drop(lock);
797 func(activated);
798 this.state.borrow_mut().callbacks.active_status_change = Some(func);
799 }
800 })
801 .detach();
802
803 None
804}
805
806fn handle_create_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
807 if state_ptr.hide_title_bar {
808 notify_frame_changed(handle);
809 Some(0)
810 } else {
811 None
812 }
813}
814
815fn handle_dpi_changed_msg(
816 handle: HWND,
817 wparam: WPARAM,
818 lparam: LPARAM,
819 state_ptr: Rc<WindowsWindowStatePtr>,
820) -> Option<isize> {
821 let new_dpi = wparam.loword() as f32;
822 let mut lock = state_ptr.state.borrow_mut();
823 lock.scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
824 lock.border_offset.update(handle).log_err();
825 drop(lock);
826
827 let rect = unsafe { &*(lparam.0 as *const RECT) };
828 let width = rect.right - rect.left;
829 let height = rect.bottom - rect.top;
830 // this will emit `WM_SIZE` and `WM_MOVE` right here
831 // even before this function returns
832 // the new size is handled in `WM_SIZE`
833 unsafe {
834 SetWindowPos(
835 handle,
836 None,
837 rect.left,
838 rect.top,
839 width,
840 height,
841 SWP_NOZORDER | SWP_NOACTIVATE,
842 )
843 .context("unable to set window position after dpi has changed")
844 .log_err();
845 }
846
847 Some(0)
848}
849
850/// The following conditions will trigger this event:
851/// 1. The monitor on which the window is located goes offline or changes resolution.
852/// 2. Another monitor goes offline, is plugged in, or changes resolution.
853///
854/// In either case, the window will only receive information from the monitor on which
855/// it is located.
856///
857/// For example, in the case of condition 2, where the monitor on which the window is
858/// located has actually changed nothing, it will still receive this event.
859fn handle_display_change_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
860 // NOTE:
861 // Even the `lParam` holds the resolution of the screen, we just ignore it.
862 // Because WM_DPICHANGED, WM_MOVE, WM_SIZE will come first, window reposition and resize
863 // are handled there.
864 // So we only care about if monitor is disconnected.
865 let previous_monitor = state_ptr.state.borrow().display;
866 if WindowsDisplay::is_connected(previous_monitor.handle) {
867 // we are fine, other display changed
868 return None;
869 }
870 // display disconnected
871 // in this case, the OS will move our window to another monitor, and minimize it.
872 // we deminimize the window and query the monitor after moving
873 unsafe {
874 let _ = ShowWindow(handle, SW_SHOWNORMAL);
875 };
876 let new_monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) };
877 // all monitors disconnected
878 if new_monitor.is_invalid() {
879 log::error!("No monitor detected!");
880 return None;
881 }
882 let new_display = WindowsDisplay::new_with_handle(new_monitor);
883 state_ptr.state.borrow_mut().display = new_display;
884 Some(0)
885}
886
887fn handle_hit_test_msg(
888 handle: HWND,
889 msg: u32,
890 wparam: WPARAM,
891 lparam: LPARAM,
892 state_ptr: Rc<WindowsWindowStatePtr>,
893) -> Option<isize> {
894 if !state_ptr.is_movable {
895 return None;
896 }
897 if !state_ptr.hide_title_bar {
898 return None;
899 }
900
901 // default handler for resize areas
902 let hit = unsafe { DefWindowProcW(handle, msg, wparam, lparam) };
903 if matches!(
904 hit.0 as u32,
905 HTNOWHERE
906 | HTRIGHT
907 | HTLEFT
908 | HTTOPLEFT
909 | HTTOP
910 | HTTOPRIGHT
911 | HTBOTTOMRIGHT
912 | HTBOTTOM
913 | HTBOTTOMLEFT
914 ) {
915 return Some(hit.0);
916 }
917
918 if state_ptr.state.borrow().is_fullscreen() {
919 return Some(HTCLIENT as _);
920 }
921
922 let dpi = unsafe { GetDpiForWindow(handle) };
923 let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
924
925 let mut cursor_point = POINT {
926 x: lparam.signed_loword().into(),
927 y: lparam.signed_hiword().into(),
928 };
929 unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
930 if !state_ptr.state.borrow().is_maximized() && cursor_point.y >= 0 && cursor_point.y <= frame_y
931 {
932 return Some(HTTOP as _);
933 }
934
935 let titlebar_rect = state_ptr.state.borrow().get_titlebar_rect();
936 if let Ok(titlebar_rect) = titlebar_rect {
937 if cursor_point.y < titlebar_rect.bottom {
938 let caption_btn_width = (state_ptr.state.borrow().caption_button_width().0
939 * state_ptr.state.borrow().scale_factor) as i32;
940 if cursor_point.x >= titlebar_rect.right - caption_btn_width {
941 return Some(HTCLOSE as _);
942 } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 {
943 return Some(HTMAXBUTTON as _);
944 } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 {
945 return Some(HTMINBUTTON as _);
946 }
947
948 return Some(HTCAPTION as _);
949 }
950 }
951
952 Some(HTCLIENT as _)
953}
954
955fn handle_nc_mouse_move_msg(
956 handle: HWND,
957 lparam: LPARAM,
958 state_ptr: Rc<WindowsWindowStatePtr>,
959) -> Option<isize> {
960 if !state_ptr.hide_title_bar {
961 return None;
962 }
963
964 start_tracking_mouse(handle, &state_ptr, TME_LEAVE | TME_NONCLIENT);
965
966 let mut lock = state_ptr.state.borrow_mut();
967 let mut func = lock.callbacks.input.take()?;
968 let scale_factor = lock.scale_factor;
969 drop(lock);
970
971 let mut cursor_point = POINT {
972 x: lparam.signed_loword().into(),
973 y: lparam.signed_hiword().into(),
974 };
975 unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
976 let input = PlatformInput::MouseMove(MouseMoveEvent {
977 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
978 pressed_button: None,
979 modifiers: current_modifiers(),
980 });
981 let handled = !func(input).propagate;
982 state_ptr.state.borrow_mut().callbacks.input = Some(func);
983
984 if handled { Some(0) } else { None }
985}
986
987fn handle_nc_mouse_down_msg(
988 handle: HWND,
989 button: MouseButton,
990 wparam: WPARAM,
991 lparam: LPARAM,
992 state_ptr: Rc<WindowsWindowStatePtr>,
993) -> Option<isize> {
994 if !state_ptr.hide_title_bar {
995 return None;
996 }
997
998 let mut lock = state_ptr.state.borrow_mut();
999 if let Some(mut func) = lock.callbacks.input.take() {
1000 let scale_factor = lock.scale_factor;
1001 let mut cursor_point = POINT {
1002 x: lparam.signed_loword().into(),
1003 y: lparam.signed_hiword().into(),
1004 };
1005 unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
1006 let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
1007 let click_count = lock.click_state.update(button, physical_point);
1008 drop(lock);
1009
1010 let input = PlatformInput::MouseDown(MouseDownEvent {
1011 button,
1012 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1013 modifiers: current_modifiers(),
1014 click_count,
1015 first_mouse: false,
1016 });
1017 let result = func(input.clone());
1018 let handled = !result.propagate || result.default_prevented;
1019 state_ptr.state.borrow_mut().callbacks.input = Some(func);
1020
1021 if handled {
1022 return Some(0);
1023 }
1024 } else {
1025 drop(lock);
1026 };
1027
1028 // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
1029 if button == MouseButton::Left {
1030 match wparam.0 as u32 {
1031 HTMINBUTTON => state_ptr.state.borrow_mut().nc_button_pressed = Some(HTMINBUTTON),
1032 HTMAXBUTTON => state_ptr.state.borrow_mut().nc_button_pressed = Some(HTMAXBUTTON),
1033 HTCLOSE => state_ptr.state.borrow_mut().nc_button_pressed = Some(HTCLOSE),
1034 _ => return None,
1035 };
1036 Some(0)
1037 } else {
1038 None
1039 }
1040}
1041
1042fn handle_nc_mouse_up_msg(
1043 handle: HWND,
1044 button: MouseButton,
1045 wparam: WPARAM,
1046 lparam: LPARAM,
1047 state_ptr: Rc<WindowsWindowStatePtr>,
1048) -> Option<isize> {
1049 if !state_ptr.hide_title_bar {
1050 return None;
1051 }
1052
1053 let mut lock = state_ptr.state.borrow_mut();
1054 if let Some(mut func) = lock.callbacks.input.take() {
1055 let scale_factor = lock.scale_factor;
1056 drop(lock);
1057
1058 let mut cursor_point = POINT {
1059 x: lparam.signed_loword().into(),
1060 y: lparam.signed_hiword().into(),
1061 };
1062 unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
1063 let input = PlatformInput::MouseUp(MouseUpEvent {
1064 button,
1065 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1066 modifiers: current_modifiers(),
1067 click_count: 1,
1068 });
1069 let handled = !func(input).propagate;
1070 state_ptr.state.borrow_mut().callbacks.input = Some(func);
1071
1072 if handled {
1073 return Some(0);
1074 }
1075 } else {
1076 drop(lock);
1077 }
1078
1079 let last_pressed = state_ptr.state.borrow_mut().nc_button_pressed.take();
1080 if button == MouseButton::Left && last_pressed.is_some() {
1081 let handled = match (wparam.0 as u32, last_pressed.unwrap()) {
1082 (HTMINBUTTON, HTMINBUTTON) => {
1083 unsafe { ShowWindowAsync(handle, SW_MINIMIZE).ok().log_err() };
1084 true
1085 }
1086 (HTMAXBUTTON, HTMAXBUTTON) => {
1087 if state_ptr.state.borrow().is_maximized() {
1088 unsafe { ShowWindowAsync(handle, SW_NORMAL).ok().log_err() };
1089 } else {
1090 unsafe { ShowWindowAsync(handle, SW_MAXIMIZE).ok().log_err() };
1091 }
1092 true
1093 }
1094 (HTCLOSE, HTCLOSE) => {
1095 unsafe {
1096 PostMessageW(Some(handle), WM_CLOSE, WPARAM::default(), LPARAM::default())
1097 .log_err()
1098 };
1099 true
1100 }
1101 _ => false,
1102 };
1103 if handled {
1104 return Some(0);
1105 }
1106 }
1107
1108 None
1109}
1110
1111fn handle_cursor_changed(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
1112 let mut state = state_ptr.state.borrow_mut();
1113 let had_cursor = state.current_cursor.is_some();
1114
1115 state.current_cursor = if lparam.0 == 0 {
1116 None
1117 } else {
1118 Some(HCURSOR(lparam.0 as _))
1119 };
1120
1121 if had_cursor != state.current_cursor.is_some() {
1122 unsafe { SetCursor(state.current_cursor) };
1123 }
1124
1125 Some(0)
1126}
1127
1128fn handle_set_cursor(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
1129 if matches!(
1130 lparam.loword() as u32,
1131 HTLEFT | HTRIGHT | HTTOP | HTTOPLEFT | HTTOPRIGHT | HTBOTTOM | HTBOTTOMLEFT | HTBOTTOMRIGHT
1132 ) {
1133 return None;
1134 }
1135 unsafe {
1136 SetCursor(state_ptr.state.borrow().current_cursor);
1137 };
1138 Some(1)
1139}
1140
1141fn handle_system_settings_changed(
1142 handle: HWND,
1143 lparam: LPARAM,
1144 state_ptr: Rc<WindowsWindowStatePtr>,
1145) -> Option<isize> {
1146 let mut lock = state_ptr.state.borrow_mut();
1147 let display = lock.display;
1148 // system settings
1149 lock.system_settings.update(display);
1150 // mouse double click
1151 lock.click_state.system_update();
1152 // window border offset
1153 lock.border_offset.update(handle).log_err();
1154 drop(lock);
1155
1156 // lParam is a pointer to a string that indicates the area containing the system parameter
1157 // that was changed.
1158 let parameter = PCWSTR::from_raw(lparam.0 as _);
1159 if unsafe { !parameter.is_null() && !parameter.is_empty() } {
1160 if let Some(parameter_string) = unsafe { parameter.to_string() }.log_err() {
1161 log::info!("System settings changed: {}", parameter_string);
1162 match parameter_string.as_str() {
1163 "ImmersiveColorSet" => {
1164 handle_system_theme_changed(handle, state_ptr);
1165 }
1166 _ => {}
1167 }
1168 }
1169 }
1170
1171 // Force to trigger WM_NCCALCSIZE event to ensure that we handle auto hide
1172 // taskbar correctly.
1173 notify_frame_changed(handle);
1174 Some(0)
1175}
1176
1177fn handle_system_command(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
1178 if wparam.0 == SC_KEYMENU as usize {
1179 let mut lock = state_ptr.state.borrow_mut();
1180 if lock.system_key_handled {
1181 lock.system_key_handled = false;
1182 return Some(0);
1183 }
1184 }
1185 None
1186}
1187
1188fn handle_system_theme_changed(
1189 handle: HWND,
1190 state_ptr: Rc<WindowsWindowStatePtr>,
1191) -> Option<isize> {
1192 let mut callback = state_ptr
1193 .state
1194 .borrow_mut()
1195 .callbacks
1196 .appearance_changed
1197 .take()?;
1198 callback();
1199 state_ptr.state.borrow_mut().callbacks.appearance_changed = Some(callback);
1200 configure_dwm_dark_mode(handle);
1201 Some(0)
1202}
1203
1204fn handle_input_language_changed(
1205 lparam: LPARAM,
1206 state_ptr: Rc<WindowsWindowStatePtr>,
1207) -> Option<isize> {
1208 let thread = state_ptr.main_thread_id_win32;
1209 let validation = state_ptr.validation_number;
1210 unsafe {
1211 PostThreadMessageW(thread, WM_INPUTLANGCHANGE, WPARAM(validation), lparam).log_err();
1212 }
1213 Some(0)
1214}
1215
1216fn handle_key_event<F>(
1217 wparam: WPARAM,
1218 lparam: LPARAM,
1219 state: &mut WindowsWindowState,
1220 f: F,
1221) -> Option<PlatformInput>
1222where
1223 F: FnOnce(Keystroke) -> PlatformInput,
1224{
1225 state.suppress_next_char_msg = false;
1226 let virtual_key = VIRTUAL_KEY(wparam.loword());
1227 let mut modifiers = current_modifiers();
1228
1229 match virtual_key {
1230 VK_PROCESSKEY => {
1231 // IME composition
1232 None
1233 }
1234 VK_SHIFT | VK_CONTROL | VK_MENU | VK_LWIN | VK_RWIN => {
1235 if state
1236 .last_reported_modifiers
1237 .is_some_and(|prev_modifiers| prev_modifiers == modifiers)
1238 {
1239 return None;
1240 }
1241 state.last_reported_modifiers = Some(modifiers);
1242 Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent {
1243 modifiers,
1244 }))
1245 }
1246 vkey => {
1247 let keystroke = parse_normal_key(vkey, lparam, modifiers)?;
1248 Some(f(keystroke))
1249 }
1250 }
1251}
1252
1253fn parse_immutable(vkey: VIRTUAL_KEY) -> Option<String> {
1254 Some(
1255 match vkey {
1256 VK_SPACE => "space",
1257 VK_BACK => "backspace",
1258 VK_RETURN => "enter",
1259 VK_TAB => "tab",
1260 VK_UP => "up",
1261 VK_DOWN => "down",
1262 VK_RIGHT => "right",
1263 VK_LEFT => "left",
1264 VK_HOME => "home",
1265 VK_END => "end",
1266 VK_PRIOR => "pageup",
1267 VK_NEXT => "pagedown",
1268 VK_BROWSER_BACK => "back",
1269 VK_BROWSER_FORWARD => "forward",
1270 VK_ESCAPE => "escape",
1271 VK_INSERT => "insert",
1272 VK_DELETE => "delete",
1273 VK_APPS => "menu",
1274 VK_F1 => "f1",
1275 VK_F2 => "f2",
1276 VK_F3 => "f3",
1277 VK_F4 => "f4",
1278 VK_F5 => "f5",
1279 VK_F6 => "f6",
1280 VK_F7 => "f7",
1281 VK_F8 => "f8",
1282 VK_F9 => "f9",
1283 VK_F10 => "f10",
1284 VK_F11 => "f11",
1285 VK_F12 => "f12",
1286 VK_F13 => "f13",
1287 VK_F14 => "f14",
1288 VK_F15 => "f15",
1289 VK_F16 => "f16",
1290 VK_F17 => "f17",
1291 VK_F18 => "f18",
1292 VK_F19 => "f19",
1293 VK_F20 => "f20",
1294 VK_F21 => "f21",
1295 VK_F22 => "f22",
1296 VK_F23 => "f23",
1297 VK_F24 => "f24",
1298 _ => return None,
1299 }
1300 .to_string(),
1301 )
1302}
1303
1304fn parse_normal_key(
1305 vkey: VIRTUAL_KEY,
1306 lparam: LPARAM,
1307 mut modifiers: Modifiers,
1308) -> Option<Keystroke> {
1309 let mut key_char = None;
1310 let key = parse_immutable(vkey).or_else(|| {
1311 let scan_code = lparam.hiword() & 0xFF;
1312 key_char = generate_key_char(
1313 vkey,
1314 scan_code as u32,
1315 modifiers.control,
1316 modifiers.shift,
1317 modifiers.alt,
1318 );
1319 get_keystroke_key(vkey, scan_code as u32, &mut modifiers)
1320 })?;
1321 Some(Keystroke {
1322 modifiers,
1323 key,
1324 key_char,
1325 })
1326}
1327
1328fn parse_ime_compostion_string(ctx: HIMC) -> Option<String> {
1329 unsafe {
1330 let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0);
1331 if string_len >= 0 {
1332 let mut buffer = vec![0u8; string_len as usize + 2];
1333 ImmGetCompositionStringW(
1334 ctx,
1335 GCS_COMPSTR,
1336 Some(buffer.as_mut_ptr() as _),
1337 string_len as _,
1338 );
1339 let wstring = std::slice::from_raw_parts::<u16>(
1340 buffer.as_mut_ptr().cast::<u16>(),
1341 string_len as usize / 2,
1342 );
1343 Some(String::from_utf16_lossy(wstring))
1344 } else {
1345 None
1346 }
1347 }
1348}
1349
1350#[inline]
1351fn retrieve_composition_cursor_position(ctx: HIMC) -> usize {
1352 unsafe { ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0) as usize }
1353}
1354
1355fn parse_ime_compostion_result(ctx: HIMC) -> Option<String> {
1356 unsafe {
1357 let string_len = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, None, 0);
1358 if string_len >= 0 {
1359 let mut buffer = vec![0u8; string_len as usize + 2];
1360 ImmGetCompositionStringW(
1361 ctx,
1362 GCS_RESULTSTR,
1363 Some(buffer.as_mut_ptr() as _),
1364 string_len as _,
1365 );
1366 let wstring = std::slice::from_raw_parts::<u16>(
1367 buffer.as_mut_ptr().cast::<u16>(),
1368 string_len as usize / 2,
1369 );
1370 let string = String::from_utf16_lossy(wstring);
1371 Some(string)
1372 } else {
1373 None
1374 }
1375 }
1376}
1377
1378#[inline]
1379fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool {
1380 unsafe { GetKeyState(vkey.0 as i32) < 0 }
1381}
1382
1383#[inline]
1384pub(crate) fn current_modifiers() -> Modifiers {
1385 Modifiers {
1386 control: is_virtual_key_pressed(VK_CONTROL),
1387 alt: is_virtual_key_pressed(VK_MENU),
1388 shift: is_virtual_key_pressed(VK_SHIFT),
1389 platform: is_virtual_key_pressed(VK_LWIN) || is_virtual_key_pressed(VK_RWIN),
1390 function: false,
1391 }
1392}
1393
1394fn get_client_area_insets(
1395 handle: HWND,
1396 is_maximized: bool,
1397 windows_version: WindowsVersion,
1398) -> RECT {
1399 // For maximized windows, Windows outdents the window rect from the screen's client rect
1400 // by `frame_thickness` on each edge, meaning `insets` must contain `frame_thickness`
1401 // on all sides (including the top) to avoid the client area extending onto adjacent
1402 // monitors.
1403 //
1404 // For non-maximized windows, things become complicated:
1405 //
1406 // - On Windows 10
1407 // The top inset must be zero, since if there is any nonclient area, Windows will draw
1408 // a full native titlebar outside the client area. (This doesn't occur in the maximized
1409 // case.)
1410 //
1411 // - On Windows 11
1412 // The top inset is calculated using an empirical formula that I derived through various
1413 // tests. Without this, the top 1-2 rows of pixels in our window would be obscured.
1414 let dpi = unsafe { GetDpiForWindow(handle) };
1415 let frame_thickness = get_frame_thickness(dpi);
1416 let top_insets = if is_maximized {
1417 frame_thickness
1418 } else {
1419 match windows_version {
1420 WindowsVersion::Win10 => 0,
1421 WindowsVersion::Win11 => (dpi as f32 / USER_DEFAULT_SCREEN_DPI as f32).round() as i32,
1422 }
1423 };
1424 RECT {
1425 left: frame_thickness,
1426 top: top_insets,
1427 right: frame_thickness,
1428 bottom: frame_thickness,
1429 }
1430}
1431
1432// there is some additional non-visible space when talking about window
1433// borders on Windows:
1434// - SM_CXSIZEFRAME: The resize handle.
1435// - SM_CXPADDEDBORDER: Additional border space that isn't part of the resize handle.
1436fn get_frame_thickness(dpi: u32) -> i32 {
1437 let resize_frame_thickness = unsafe { GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) };
1438 let padding_thickness = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
1439 resize_frame_thickness + padding_thickness
1440}
1441
1442fn notify_frame_changed(handle: HWND) {
1443 unsafe {
1444 SetWindowPos(
1445 handle,
1446 None,
1447 0,
1448 0,
1449 0,
1450 0,
1451 SWP_FRAMECHANGED
1452 | SWP_NOACTIVATE
1453 | SWP_NOCOPYBITS
1454 | SWP_NOMOVE
1455 | SWP_NOOWNERZORDER
1456 | SWP_NOREPOSITION
1457 | SWP_NOSENDCHANGING
1458 | SWP_NOSIZE
1459 | SWP_NOZORDER,
1460 )
1461 .log_err();
1462 }
1463}
1464
1465fn start_tracking_mouse(
1466 handle: HWND,
1467 state_ptr: &Rc<WindowsWindowStatePtr>,
1468 flags: TRACKMOUSEEVENT_FLAGS,
1469) {
1470 let mut lock = state_ptr.state.borrow_mut();
1471 if !lock.hovered {
1472 lock.hovered = true;
1473 unsafe {
1474 TrackMouseEvent(&mut TRACKMOUSEEVENT {
1475 cbSize: std::mem::size_of::<TRACKMOUSEEVENT>() as u32,
1476 dwFlags: flags,
1477 hwndTrack: handle,
1478 dwHoverTime: HOVER_DEFAULT,
1479 })
1480 .log_err()
1481 };
1482 if let Some(mut callback) = lock.callbacks.hovered_status_change.take() {
1483 drop(lock);
1484 callback(true);
1485 state_ptr.state.borrow_mut().callbacks.hovered_status_change = Some(callback);
1486 }
1487 }
1488}
1489
1490fn with_input_handler<F, R>(state_ptr: &Rc<WindowsWindowStatePtr>, f: F) -> Option<R>
1491where
1492 F: FnOnce(&mut PlatformInputHandler) -> R,
1493{
1494 let mut lock = state_ptr.state.borrow_mut();
1495 if lock.suppress_next_char_msg {
1496 return None;
1497 }
1498 let mut input_handler = lock.input_handler.take()?;
1499 drop(lock);
1500 let result = f(&mut input_handler);
1501 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
1502 Some(result)
1503}
1504
1505fn with_input_handler_and_scale_factor<F, R>(
1506 state_ptr: &Rc<WindowsWindowStatePtr>,
1507 f: F,
1508) -> Option<R>
1509where
1510 F: FnOnce(&mut PlatformInputHandler, f32) -> Option<R>,
1511{
1512 let mut lock = state_ptr.state.borrow_mut();
1513 if lock.suppress_next_char_msg {
1514 return None;
1515 }
1516 let mut input_handler = lock.input_handler.take()?;
1517 let scale_factor = lock.scale_factor;
1518 drop(lock);
1519 let result = f(&mut input_handler, scale_factor);
1520 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
1521 result
1522}