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