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