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