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