1use std::rc::Rc;
2
3use ::util::ResultExt;
4use anyhow::Context;
5use windows::Win32::{
6 Foundation::*,
7 Graphics::Gdi::*,
8 System::SystemServices::*,
9 UI::{
10 HiDpi::*,
11 Input::{Ime::*, KeyboardAndMouse::*},
12 WindowsAndMessaging::*,
13 },
14};
15
16use crate::*;
17
18pub(crate) const CURSOR_STYLE_CHANGED: u32 = WM_USER + 1;
19pub(crate) const MOUSE_WHEEL_SETTINGS_CHANGED: u32 = WM_USER + 2;
20pub(crate) const MOUSE_WHEEL_SETTINGS_SCROLL_CHARS_CHANGED: isize = 1;
21pub(crate) const MOUSE_WHEEL_SETTINGS_SCROLL_LINES_CHANGED: isize = 2;
22pub(crate) const CLOSE_ONE_WINDOW: u32 = WM_USER + 3;
23const SIZE_MOVE_LOOP_TIMER_ID: usize = 1;
24
25pub(crate) fn handle_msg(
26 handle: HWND,
27 msg: u32,
28 wparam: WPARAM,
29 lparam: LPARAM,
30 state_ptr: Rc<WindowsWindowStatePtr>,
31) -> LRESULT {
32 let handled = match msg {
33 WM_ACTIVATE => handle_activate_msg(handle, wparam, state_ptr),
34 WM_CREATE => handle_create_msg(handle, state_ptr),
35 WM_MOVE => handle_move_msg(handle, lparam, state_ptr),
36 WM_SIZE => handle_size_msg(lparam, state_ptr),
37 WM_ENTERSIZEMOVE | WM_ENTERMENULOOP => handle_size_move_loop(handle),
38 WM_EXITSIZEMOVE | WM_EXITMENULOOP => handle_size_move_loop_exit(handle),
39 WM_TIMER => handle_timer_msg(handle, wparam, state_ptr),
40 WM_NCCALCSIZE => handle_calc_client_size(handle, wparam, lparam, state_ptr),
41 WM_DPICHANGED => handle_dpi_changed_msg(handle, wparam, lparam, state_ptr),
42 WM_NCHITTEST => handle_hit_test_msg(handle, msg, wparam, lparam, state_ptr),
43 WM_PAINT => handle_paint_msg(handle, state_ptr),
44 WM_CLOSE => handle_close_msg(state_ptr),
45 WM_DESTROY => handle_destroy_msg(handle, state_ptr),
46 WM_MOUSEMOVE => handle_mouse_move_msg(lparam, wparam, state_ptr),
47 WM_NCMOUSEMOVE => handle_nc_mouse_move_msg(handle, lparam, state_ptr),
48 WM_NCLBUTTONDOWN => {
49 handle_nc_mouse_down_msg(handle, MouseButton::Left, wparam, lparam, state_ptr)
50 }
51 WM_NCRBUTTONDOWN => {
52 handle_nc_mouse_down_msg(handle, MouseButton::Right, wparam, lparam, state_ptr)
53 }
54 WM_NCMBUTTONDOWN => {
55 handle_nc_mouse_down_msg(handle, MouseButton::Middle, wparam, lparam, state_ptr)
56 }
57 WM_NCLBUTTONUP => {
58 handle_nc_mouse_up_msg(handle, MouseButton::Left, wparam, lparam, state_ptr)
59 }
60 WM_NCRBUTTONUP => {
61 handle_nc_mouse_up_msg(handle, MouseButton::Right, wparam, lparam, state_ptr)
62 }
63 WM_NCMBUTTONUP => {
64 handle_nc_mouse_up_msg(handle, MouseButton::Middle, wparam, lparam, state_ptr)
65 }
66 WM_LBUTTONDOWN => handle_mouse_down_msg(MouseButton::Left, lparam, state_ptr),
67 WM_RBUTTONDOWN => handle_mouse_down_msg(MouseButton::Right, lparam, state_ptr),
68 WM_MBUTTONDOWN => handle_mouse_down_msg(MouseButton::Middle, lparam, state_ptr),
69 WM_XBUTTONDOWN => handle_xbutton_msg(wparam, lparam, handle_mouse_down_msg, state_ptr),
70 WM_LBUTTONUP => handle_mouse_up_msg(MouseButton::Left, lparam, state_ptr),
71 WM_RBUTTONUP => handle_mouse_up_msg(MouseButton::Right, lparam, state_ptr),
72 WM_MBUTTONUP => handle_mouse_up_msg(MouseButton::Middle, lparam, state_ptr),
73 WM_XBUTTONUP => handle_xbutton_msg(wparam, lparam, handle_mouse_up_msg, state_ptr),
74 WM_MOUSEWHEEL => handle_mouse_wheel_msg(handle, wparam, lparam, state_ptr),
75 WM_MOUSEHWHEEL => handle_mouse_horizontal_wheel_msg(handle, wparam, lparam, state_ptr),
76 WM_SYSKEYDOWN => handle_syskeydown_msg(handle, wparam, lparam, state_ptr),
77 WM_SYSKEYUP => handle_syskeyup_msg(handle, wparam, state_ptr),
78 WM_KEYDOWN => handle_keydown_msg(handle, wparam, lparam, state_ptr),
79 WM_KEYUP => handle_keyup_msg(handle, wparam, state_ptr),
80 WM_CHAR => handle_char_msg(handle, wparam, lparam, state_ptr),
81 WM_IME_STARTCOMPOSITION => handle_ime_position(handle, state_ptr),
82 WM_IME_COMPOSITION => handle_ime_composition(handle, lparam, state_ptr),
83 WM_SETCURSOR => handle_set_cursor(lparam, state_ptr),
84 CURSOR_STYLE_CHANGED => handle_cursor_changed(lparam, state_ptr),
85 MOUSE_WHEEL_SETTINGS_CHANGED => handle_mouse_wheel_settings_msg(wparam, lparam, state_ptr),
86 _ => None,
87 };
88 if let Some(n) = handled {
89 LRESULT(n)
90 } else {
91 unsafe { DefWindowProcW(handle, msg, wparam, lparam) }
92 }
93}
94
95fn handle_move_msg(
96 handle: HWND,
97 lparam: LPARAM,
98 state_ptr: Rc<WindowsWindowStatePtr>,
99) -> Option<isize> {
100 let x = lparam.signed_loword() as i32;
101 let y = lparam.signed_hiword() as i32;
102 let mut lock = state_ptr.state.borrow_mut();
103 lock.origin = point(x.into(), y.into());
104 let size = lock.physical_size;
105 let center_x = x + size.width.0 / 2;
106 let center_y = y + size.height.0 / 2;
107 let monitor_bounds = lock.display.bounds();
108 if center_x < monitor_bounds.left().0
109 || center_x > monitor_bounds.right().0
110 || center_y < monitor_bounds.top().0
111 || center_y > monitor_bounds.bottom().0
112 {
113 // center of the window may have moved to another monitor
114 let monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) };
115 if !monitor.is_invalid() && lock.display.handle != monitor {
116 // we will get the same monitor if we only have one
117 lock.display = WindowsDisplay::new_with_handle(monitor);
118 }
119 }
120 if let Some(mut callback) = lock.callbacks.moved.take() {
121 drop(lock);
122 callback();
123 state_ptr.state.borrow_mut().callbacks.moved = Some(callback);
124 }
125 Some(0)
126}
127
128fn handle_size_msg(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
129 let width = lparam.loword().max(1) as i32;
130 let height = lparam.hiword().max(1) as i32;
131 let new_physical_size = size(width.into(), height.into());
132 let mut lock = state_ptr.state.borrow_mut();
133 let scale_factor = lock.scale_factor;
134 lock.physical_size = new_physical_size;
135 lock.renderer.update_drawable_size(Size {
136 width: width as f64,
137 height: height as f64,
138 });
139 if let Some(mut callback) = lock.callbacks.resize.take() {
140 drop(lock);
141 let logical_size = logical_size(new_physical_size, scale_factor);
142 callback(logical_size, scale_factor);
143 state_ptr.state.borrow_mut().callbacks.resize = Some(callback);
144 }
145 Some(0)
146}
147
148fn handle_size_move_loop(handle: HWND) -> Option<isize> {
149 unsafe {
150 let ret = SetTimer(handle, SIZE_MOVE_LOOP_TIMER_ID, USER_TIMER_MINIMUM, None);
151 if ret == 0 {
152 log::error!(
153 "unable to create timer: {}",
154 std::io::Error::last_os_error()
155 );
156 }
157 }
158 None
159}
160
161fn handle_size_move_loop_exit(handle: HWND) -> Option<isize> {
162 unsafe {
163 KillTimer(handle, SIZE_MOVE_LOOP_TIMER_ID).log_err();
164 }
165 None
166}
167
168fn handle_timer_msg(
169 handle: HWND,
170 wparam: WPARAM,
171 state_ptr: Rc<WindowsWindowStatePtr>,
172) -> Option<isize> {
173 if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID {
174 handle_paint_msg(handle, state_ptr)
175 } else {
176 None
177 }
178}
179
180fn handle_paint_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
181 let mut paint_struct = PAINTSTRUCT::default();
182 let _hdc = unsafe { BeginPaint(handle, &mut paint_struct) };
183 let mut lock = state_ptr.state.borrow_mut();
184 if let Some(mut request_frame) = lock.callbacks.request_frame.take() {
185 drop(lock);
186 request_frame();
187 state_ptr.state.borrow_mut().callbacks.request_frame = Some(request_frame);
188 }
189 unsafe { EndPaint(handle, &paint_struct) };
190 Some(0)
191}
192
193fn handle_close_msg(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
194 let mut lock = state_ptr.state.borrow_mut();
195 if let Some(mut callback) = lock.callbacks.should_close.take() {
196 drop(lock);
197 let should_close = callback();
198 state_ptr.state.borrow_mut().callbacks.should_close = Some(callback);
199 if should_close {
200 None
201 } else {
202 Some(0)
203 }
204 } else {
205 None
206 }
207}
208
209fn handle_destroy_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
210 let callback = {
211 let mut lock = state_ptr.state.borrow_mut();
212 lock.callbacks.close.take()
213 };
214 if let Some(callback) = callback {
215 callback();
216 }
217 unsafe {
218 PostMessageW(None, CLOSE_ONE_WINDOW, None, LPARAM(handle.0)).log_err();
219 }
220 Some(0)
221}
222
223fn handle_mouse_move_msg(
224 lparam: LPARAM,
225 wparam: WPARAM,
226 state_ptr: Rc<WindowsWindowStatePtr>,
227) -> Option<isize> {
228 let mut lock = state_ptr.state.borrow_mut();
229 if let Some(mut callback) = lock.callbacks.input.take() {
230 let scale_factor = lock.scale_factor;
231 drop(lock);
232 let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
233 flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
234 flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),
235 flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),
236 flags if flags.contains(MK_XBUTTON1) => {
237 Some(MouseButton::Navigate(NavigationDirection::Back))
238 }
239 flags if flags.contains(MK_XBUTTON2) => {
240 Some(MouseButton::Navigate(NavigationDirection::Forward))
241 }
242 _ => None,
243 };
244 let x = lparam.signed_loword() as f32;
245 let y = lparam.signed_hiword() as f32;
246 let event = MouseMoveEvent {
247 position: logical_point(x, y, scale_factor),
248 pressed_button,
249 modifiers: current_modifiers(),
250 };
251 let result = if callback(PlatformInput::MouseMove(event)).default_prevented {
252 Some(0)
253 } else {
254 Some(1)
255 };
256 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
257 return result;
258 }
259 Some(1)
260}
261
262fn handle_syskeydown_msg(
263 handle: HWND,
264 wparam: WPARAM,
265 lparam: LPARAM,
266 state_ptr: Rc<WindowsWindowStatePtr>,
267) -> Option<isize> {
268 // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
269 // shortcuts.
270 let Some(keystroke) = parse_syskeydown_msg_keystroke(wparam) else {
271 return None;
272 };
273 let mut lock = state_ptr.state.borrow_mut();
274 let Some(mut func) = lock.callbacks.input.take() else {
275 return None;
276 };
277 drop(lock);
278 let event = KeyDownEvent {
279 keystroke,
280 is_held: lparam.0 & (0x1 << 30) > 0,
281 };
282 let result = if func(PlatformInput::KeyDown(event)).default_prevented {
283 invalidate_client_area(handle);
284 Some(0)
285 } else {
286 None
287 };
288 state_ptr.state.borrow_mut().callbacks.input = Some(func);
289
290 result
291}
292
293fn handle_syskeyup_msg(
294 handle: HWND,
295 wparam: WPARAM,
296 state_ptr: Rc<WindowsWindowStatePtr>,
297) -> Option<isize> {
298 // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
299 // shortcuts.
300 let Some(keystroke) = parse_syskeydown_msg_keystroke(wparam) else {
301 return None;
302 };
303 let mut lock = state_ptr.state.borrow_mut();
304 let Some(mut func) = lock.callbacks.input.take() else {
305 return None;
306 };
307 drop(lock);
308 let event = KeyUpEvent { keystroke };
309 let result = if func(PlatformInput::KeyUp(event)).default_prevented {
310 invalidate_client_area(handle);
311 Some(0)
312 } else {
313 Some(1)
314 };
315 state_ptr.state.borrow_mut().callbacks.input = Some(func);
316
317 result
318}
319
320fn handle_keydown_msg(
321 handle: HWND,
322 wparam: WPARAM,
323 lparam: LPARAM,
324 state_ptr: Rc<WindowsWindowStatePtr>,
325) -> Option<isize> {
326 let Some(keystroke) = parse_keydown_msg_keystroke(wparam) else {
327 return Some(1);
328 };
329 let mut lock = state_ptr.state.borrow_mut();
330 let Some(mut func) = lock.callbacks.input.take() else {
331 return Some(1);
332 };
333 drop(lock);
334 let event = KeyDownEvent {
335 keystroke,
336 is_held: lparam.0 & (0x1 << 30) > 0,
337 };
338 let result = if func(PlatformInput::KeyDown(event)).default_prevented {
339 invalidate_client_area(handle);
340 Some(0)
341 } else {
342 Some(1)
343 };
344 state_ptr.state.borrow_mut().callbacks.input = Some(func);
345
346 result
347}
348
349fn handle_keyup_msg(
350 handle: HWND,
351 wparam: WPARAM,
352 state_ptr: Rc<WindowsWindowStatePtr>,
353) -> Option<isize> {
354 let Some(keystroke) = parse_keydown_msg_keystroke(wparam) else {
355 return Some(1);
356 };
357 let mut lock = state_ptr.state.borrow_mut();
358 let Some(mut func) = lock.callbacks.input.take() else {
359 return Some(1);
360 };
361 drop(lock);
362 let event = KeyUpEvent { keystroke };
363 let result = if func(PlatformInput::KeyUp(event)).default_prevented {
364 invalidate_client_area(handle);
365 Some(0)
366 } else {
367 Some(1)
368 };
369 state_ptr.state.borrow_mut().callbacks.input = Some(func);
370
371 result
372}
373
374fn handle_char_msg(
375 handle: HWND,
376 wparam: WPARAM,
377 lparam: LPARAM,
378 state_ptr: Rc<WindowsWindowStatePtr>,
379) -> Option<isize> {
380 let Some(keystroke) = parse_char_msg_keystroke(wparam) else {
381 return Some(1);
382 };
383 let mut lock = state_ptr.state.borrow_mut();
384 let Some(mut func) = lock.callbacks.input.take() else {
385 return Some(1);
386 };
387 drop(lock);
388 let ime_key = keystroke.ime_key.clone();
389 let event = KeyDownEvent {
390 keystroke,
391 is_held: lparam.0 & (0x1 << 30) > 0,
392 };
393
394 let dispatch_event_result = func(PlatformInput::KeyDown(event));
395 let mut lock = state_ptr.state.borrow_mut();
396 lock.callbacks.input = Some(func);
397 if dispatch_event_result.default_prevented || !dispatch_event_result.propagate {
398 invalidate_client_area(handle);
399 return Some(0);
400 }
401 let Some(ime_char) = ime_key else {
402 return Some(1);
403 };
404 let Some(mut input_handler) = lock.input_handler.take() else {
405 return Some(1);
406 };
407 drop(lock);
408 input_handler.replace_text_in_range(None, &ime_char);
409 invalidate_client_area(handle);
410 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
411
412 Some(0)
413}
414
415fn handle_mouse_down_msg(
416 button: MouseButton,
417 lparam: LPARAM,
418 state_ptr: Rc<WindowsWindowStatePtr>,
419) -> Option<isize> {
420 let mut lock = state_ptr.state.borrow_mut();
421 if let Some(mut callback) = lock.callbacks.input.take() {
422 let x = lparam.signed_loword() as f32;
423 let y = lparam.signed_hiword() as f32;
424 let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
425 let click_count = lock.click_state.update(button, physical_point);
426 let scale_factor = lock.scale_factor;
427 drop(lock);
428
429 let event = MouseDownEvent {
430 button,
431 position: logical_point(x, y, scale_factor),
432 modifiers: current_modifiers(),
433 click_count,
434 first_mouse: false,
435 };
436 let result = if callback(PlatformInput::MouseDown(event)).default_prevented {
437 Some(0)
438 } else {
439 Some(1)
440 };
441 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
442
443 result
444 } else {
445 Some(1)
446 }
447}
448
449fn handle_mouse_up_msg(
450 button: MouseButton,
451 lparam: LPARAM,
452 state_ptr: Rc<WindowsWindowStatePtr>,
453) -> Option<isize> {
454 let mut lock = state_ptr.state.borrow_mut();
455 if let Some(mut callback) = lock.callbacks.input.take() {
456 let x = lparam.signed_loword() as f32;
457 let y = lparam.signed_hiword() as f32;
458 let click_count = lock.click_state.current_count;
459 let scale_factor = lock.scale_factor;
460 drop(lock);
461
462 let event = MouseUpEvent {
463 button,
464 position: logical_point(x, y, scale_factor),
465 modifiers: current_modifiers(),
466 click_count,
467 };
468 let result = if callback(PlatformInput::MouseUp(event)).default_prevented {
469 Some(0)
470 } else {
471 Some(1)
472 };
473 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
474
475 result
476 } else {
477 Some(1)
478 }
479}
480
481fn handle_xbutton_msg(
482 wparam: WPARAM,
483 lparam: LPARAM,
484 handler: impl Fn(MouseButton, LPARAM, Rc<WindowsWindowStatePtr>) -> Option<isize>,
485 state_ptr: Rc<WindowsWindowStatePtr>,
486) -> Option<isize> {
487 let nav_dir = match wparam.hiword() {
488 XBUTTON1 => NavigationDirection::Back,
489 XBUTTON2 => NavigationDirection::Forward,
490 _ => return Some(1),
491 };
492 handler(MouseButton::Navigate(nav_dir), lparam, state_ptr)
493}
494
495fn handle_mouse_wheel_msg(
496 handle: HWND,
497 wparam: WPARAM,
498 lparam: LPARAM,
499 state_ptr: Rc<WindowsWindowStatePtr>,
500) -> Option<isize> {
501 let mut lock = state_ptr.state.borrow_mut();
502 if let Some(mut callback) = lock.callbacks.input.take() {
503 let scale_factor = lock.scale_factor;
504 let wheel_scroll_lines = lock.mouse_wheel_settings.wheel_scroll_lines;
505 drop(lock);
506 let wheel_distance =
507 (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_lines as f32;
508 let mut cursor_point = POINT {
509 x: lparam.signed_loword().into(),
510 y: lparam.signed_hiword().into(),
511 };
512 unsafe { ScreenToClient(handle, &mut cursor_point) };
513 let event = ScrollWheelEvent {
514 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
515 delta: ScrollDelta::Lines(Point {
516 x: 0.0,
517 y: wheel_distance,
518 }),
519 modifiers: current_modifiers(),
520 touch_phase: TouchPhase::Moved,
521 };
522 let result = if callback(PlatformInput::ScrollWheel(event)).default_prevented {
523 Some(0)
524 } else {
525 Some(1)
526 };
527 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
528
529 result
530 } else {
531 Some(1)
532 }
533}
534
535fn handle_mouse_horizontal_wheel_msg(
536 handle: HWND,
537 wparam: WPARAM,
538 lparam: LPARAM,
539 state_ptr: Rc<WindowsWindowStatePtr>,
540) -> Option<isize> {
541 let mut lock = state_ptr.state.borrow_mut();
542 if let Some(mut callback) = lock.callbacks.input.take() {
543 let scale_factor = lock.scale_factor;
544 let wheel_scroll_chars = lock.mouse_wheel_settings.wheel_scroll_chars;
545 drop(lock);
546 let wheel_distance =
547 (-wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_chars as f32;
548 let mut cursor_point = POINT {
549 x: lparam.signed_loword().into(),
550 y: lparam.signed_hiword().into(),
551 };
552 unsafe { ScreenToClient(handle, &mut cursor_point) };
553 let event = ScrollWheelEvent {
554 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
555 delta: ScrollDelta::Lines(Point {
556 x: wheel_distance,
557 y: 0.0,
558 }),
559 modifiers: current_modifiers(),
560 touch_phase: TouchPhase::Moved,
561 };
562 let result = if callback(PlatformInput::ScrollWheel(event)).default_prevented {
563 Some(0)
564 } else {
565 Some(1)
566 };
567 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
568
569 result
570 } else {
571 Some(1)
572 }
573}
574
575fn handle_ime_position(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
576 unsafe {
577 let mut lock = state_ptr.state.borrow_mut();
578 let ctx = ImmGetContext(handle);
579 let Some(mut input_handler) = lock.input_handler.take() else {
580 return Some(1);
581 };
582 let scale_factor = lock.scale_factor;
583 drop(lock);
584
585 let Some(caret_range) = input_handler.selected_text_range() else {
586 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
587 return Some(0);
588 };
589 let caret_position = input_handler.bounds_for_range(caret_range).unwrap();
590 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
591 let config = CANDIDATEFORM {
592 dwStyle: CFS_CANDIDATEPOS,
593 // logical to physical
594 ptCurrentPos: POINT {
595 x: (caret_position.origin.x.0 * scale_factor) as i32,
596 y: (caret_position.origin.y.0 * scale_factor) as i32
597 + ((caret_position.size.height.0 * scale_factor) as i32 / 2),
598 },
599 ..Default::default()
600 };
601 ImmSetCandidateWindow(ctx, &config as _);
602 ImmReleaseContext(handle, ctx);
603 Some(0)
604 }
605}
606
607fn handle_ime_composition(
608 handle: HWND,
609 lparam: LPARAM,
610 state_ptr: Rc<WindowsWindowStatePtr>,
611) -> Option<isize> {
612 let mut ime_input = None;
613 if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
614 let Some((string, string_len)) = parse_ime_compostion_string(handle) else {
615 return None;
616 };
617 let mut lock = state_ptr.state.borrow_mut();
618 let Some(mut input_handler) = lock.input_handler.take() else {
619 return None;
620 };
621 drop(lock);
622 input_handler.replace_and_mark_text_in_range(None, string.as_str(), Some(0..string_len));
623 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
624 ime_input = Some(string);
625 }
626 if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
627 let Some(ref comp_string) = ime_input else {
628 return None;
629 };
630 let caret_pos = retrieve_composition_cursor_position(handle);
631 let mut lock = state_ptr.state.borrow_mut();
632 let Some(mut input_handler) = lock.input_handler.take() else {
633 return None;
634 };
635 drop(lock);
636 input_handler.replace_and_mark_text_in_range(None, comp_string, Some(0..caret_pos));
637 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
638 }
639 if lparam.0 as u32 & GCS_RESULTSTR.0 > 0 {
640 let Some(comp_result) = parse_ime_compostion_result(handle) else {
641 return None;
642 };
643 let mut lock = state_ptr.state.borrow_mut();
644 let Some(mut input_handler) = lock.input_handler.take() else {
645 return Some(1);
646 };
647 drop(lock);
648 input_handler.replace_text_in_range(None, &comp_result);
649 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
650 invalidate_client_area(handle);
651 return Some(0);
652 }
653 // currently, we don't care other stuff
654 None
655}
656
657/// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
658fn handle_calc_client_size(
659 handle: HWND,
660 wparam: WPARAM,
661 lparam: LPARAM,
662 state_ptr: Rc<WindowsWindowStatePtr>,
663) -> Option<isize> {
664 if !state_ptr.hide_title_bar || state_ptr.state.borrow().is_fullscreen() {
665 return None;
666 }
667
668 if wparam.0 == 0 {
669 return None;
670 }
671
672 let dpi = unsafe { GetDpiForWindow(handle) };
673
674 let frame_x = unsafe { GetSystemMetricsForDpi(SM_CXFRAME, dpi) };
675 let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
676 let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
677
678 // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure
679 let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS;
680 let mut requested_client_rect = unsafe { &mut ((*params).rgrc) };
681
682 requested_client_rect[0].right -= frame_x + padding;
683 requested_client_rect[0].left += frame_x + padding;
684 requested_client_rect[0].bottom -= frame_y + padding;
685
686 Some(0)
687}
688
689fn handle_activate_msg(
690 handle: HWND,
691 wparam: WPARAM,
692 state_ptr: Rc<WindowsWindowStatePtr>,
693) -> Option<isize> {
694 let activated = wparam.loword() > 0;
695 if state_ptr.hide_title_bar {
696 if let Some(titlebar_rect) = state_ptr.state.borrow().get_titlebar_rect().log_err() {
697 unsafe { InvalidateRect(handle, Some(&titlebar_rect), FALSE) };
698 }
699 }
700 let this = state_ptr.clone();
701 state_ptr
702 .executor
703 .spawn(async move {
704 let mut lock = this.state.borrow_mut();
705 if let Some(mut cb) = lock.callbacks.active_status_change.take() {
706 drop(lock);
707 cb(activated);
708 this.state.borrow_mut().callbacks.active_status_change = Some(cb);
709 }
710 })
711 .detach();
712
713 None
714}
715
716fn handle_create_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
717 let mut size_rect = RECT::default();
718 unsafe { GetWindowRect(handle, &mut size_rect).log_err() };
719
720 let width = size_rect.right - size_rect.left;
721 let height = size_rect.bottom - size_rect.top;
722
723 if state_ptr.hide_title_bar {
724 unsafe {
725 SetWindowPos(
726 handle,
727 None,
728 size_rect.left,
729 size_rect.top,
730 width,
731 height,
732 SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE,
733 )
734 .log_err()
735 };
736 }
737
738 Some(0)
739}
740
741fn handle_dpi_changed_msg(
742 handle: HWND,
743 wparam: WPARAM,
744 lparam: LPARAM,
745 state_ptr: Rc<WindowsWindowStatePtr>,
746) -> Option<isize> {
747 let new_dpi = wparam.loword() as f32;
748 state_ptr.state.borrow_mut().scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
749
750 let rect = unsafe { &*(lparam.0 as *const RECT) };
751 let width = rect.right - rect.left;
752 let height = rect.bottom - rect.top;
753 // this will emit `WM_SIZE` and `WM_MOVE` right here
754 // even before this function returns
755 // the new size is handled in `WM_SIZE`
756 unsafe {
757 SetWindowPos(
758 handle,
759 None,
760 rect.left,
761 rect.top,
762 width,
763 height,
764 SWP_NOZORDER | SWP_NOACTIVATE,
765 )
766 .context("unable to set window position after dpi has changed")
767 .log_err();
768 }
769 invalidate_client_area(handle);
770
771 Some(0)
772}
773
774fn handle_hit_test_msg(
775 handle: HWND,
776 msg: u32,
777 wparam: WPARAM,
778 lparam: LPARAM,
779 state_ptr: Rc<WindowsWindowStatePtr>,
780) -> Option<isize> {
781 if !state_ptr.hide_title_bar {
782 return None;
783 }
784
785 // default handler for resize areas
786 let hit = unsafe { DefWindowProcW(handle, msg, wparam, lparam) };
787 if matches!(
788 hit.0 as u32,
789 HTNOWHERE
790 | HTRIGHT
791 | HTLEFT
792 | HTTOPLEFT
793 | HTTOP
794 | HTTOPRIGHT
795 | HTBOTTOMRIGHT
796 | HTBOTTOM
797 | HTBOTTOMLEFT
798 ) {
799 return Some(hit.0);
800 }
801
802 if state_ptr.state.borrow().is_fullscreen() {
803 return Some(HTCLIENT as _);
804 }
805
806 let dpi = unsafe { GetDpiForWindow(handle) };
807 let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
808 let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
809
810 let mut cursor_point = POINT {
811 x: lparam.signed_loword().into(),
812 y: lparam.signed_hiword().into(),
813 };
814 unsafe { ScreenToClient(handle, &mut cursor_point) };
815 if cursor_point.y > 0 && cursor_point.y < frame_y + padding {
816 return Some(HTTOP as _);
817 }
818
819 let titlebar_rect = state_ptr.state.borrow().get_titlebar_rect();
820 if let Ok(titlebar_rect) = titlebar_rect {
821 if cursor_point.y < titlebar_rect.bottom {
822 let caption_btn_width = (state_ptr.state.borrow().caption_button_width().0
823 * state_ptr.state.borrow().scale_factor) as i32;
824 if cursor_point.x >= titlebar_rect.right - caption_btn_width {
825 return Some(HTCLOSE as _);
826 } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 {
827 return Some(HTMAXBUTTON as _);
828 } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 {
829 return Some(HTMINBUTTON as _);
830 }
831
832 return Some(HTCAPTION as _);
833 }
834 }
835
836 Some(HTCLIENT as _)
837}
838
839fn handle_nc_mouse_move_msg(
840 handle: HWND,
841 lparam: LPARAM,
842 state_ptr: Rc<WindowsWindowStatePtr>,
843) -> Option<isize> {
844 if !state_ptr.hide_title_bar {
845 return None;
846 }
847
848 let mut lock = state_ptr.state.borrow_mut();
849 if let Some(mut callback) = lock.callbacks.input.take() {
850 let scale_factor = lock.scale_factor;
851 drop(lock);
852 let mut cursor_point = POINT {
853 x: lparam.signed_loword().into(),
854 y: lparam.signed_hiword().into(),
855 };
856 unsafe { ScreenToClient(handle, &mut cursor_point) };
857 let event = MouseMoveEvent {
858 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
859 pressed_button: None,
860 modifiers: current_modifiers(),
861 };
862 let result = if callback(PlatformInput::MouseMove(event)).default_prevented {
863 Some(0)
864 } else {
865 Some(1)
866 };
867 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
868
869 result
870 } else {
871 None
872 }
873}
874
875fn handle_nc_mouse_down_msg(
876 handle: HWND,
877 button: MouseButton,
878 wparam: WPARAM,
879 lparam: LPARAM,
880 state_ptr: Rc<WindowsWindowStatePtr>,
881) -> Option<isize> {
882 if !state_ptr.hide_title_bar {
883 return None;
884 }
885
886 let mut lock = state_ptr.state.borrow_mut();
887 let result = if let Some(mut callback) = lock.callbacks.input.take() {
888 let scale_factor = lock.scale_factor;
889 let mut cursor_point = POINT {
890 x: lparam.signed_loword().into(),
891 y: lparam.signed_hiword().into(),
892 };
893 unsafe { ScreenToClient(handle, &mut cursor_point) };
894 let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
895 let click_count = lock.click_state.update(button, physical_point);
896 drop(lock);
897 let event = MouseDownEvent {
898 button,
899 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
900 modifiers: current_modifiers(),
901 click_count,
902 first_mouse: false,
903 };
904 let result = if callback(PlatformInput::MouseDown(event)).default_prevented {
905 Some(0)
906 } else {
907 None
908 };
909 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
910
911 result
912 } else {
913 None
914 };
915
916 // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
917 result.or_else(|| matches!(wparam.0 as u32, HTMINBUTTON | HTMAXBUTTON | HTCLOSE).then_some(0))
918}
919
920fn handle_nc_mouse_up_msg(
921 handle: HWND,
922 button: MouseButton,
923 wparam: WPARAM,
924 lparam: LPARAM,
925 state_ptr: Rc<WindowsWindowStatePtr>,
926) -> Option<isize> {
927 if !state_ptr.hide_title_bar {
928 return None;
929 }
930
931 let mut lock = state_ptr.state.borrow_mut();
932 if let Some(mut callback) = lock.callbacks.input.take() {
933 let scale_factor = lock.scale_factor;
934 drop(lock);
935 let mut cursor_point = POINT {
936 x: lparam.signed_loword().into(),
937 y: lparam.signed_hiword().into(),
938 };
939 unsafe { ScreenToClient(handle, &mut cursor_point) };
940 let event = MouseUpEvent {
941 button,
942 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
943 modifiers: current_modifiers(),
944 click_count: 1,
945 };
946 let result = if callback(PlatformInput::MouseUp(event)).default_prevented {
947 Some(0)
948 } else {
949 None
950 };
951 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
952 if result.is_some() {
953 return result;
954 }
955 } else {
956 drop(lock);
957 }
958
959 if button == MouseButton::Left {
960 match wparam.0 as u32 {
961 HTMINBUTTON => unsafe {
962 ShowWindowAsync(handle, SW_MINIMIZE);
963 },
964 HTMAXBUTTON => unsafe {
965 if state_ptr.state.borrow().is_maximized() {
966 ShowWindowAsync(handle, SW_NORMAL);
967 } else {
968 ShowWindowAsync(handle, SW_MAXIMIZE);
969 }
970 },
971 HTCLOSE => unsafe {
972 PostMessageW(handle, WM_CLOSE, WPARAM::default(), LPARAM::default()).log_err();
973 },
974 _ => return None,
975 };
976 return Some(0);
977 }
978
979 None
980}
981
982fn handle_cursor_changed(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
983 state_ptr.state.borrow_mut().current_cursor = HCURSOR(lparam.0);
984 Some(0)
985}
986
987fn handle_set_cursor(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
988 if matches!(
989 lparam.loword() as u32,
990 HTLEFT | HTRIGHT | HTTOP | HTTOPLEFT | HTTOPRIGHT | HTBOTTOM | HTBOTTOMLEFT | HTBOTTOMRIGHT
991 ) {
992 return None;
993 }
994 unsafe { SetCursor(state_ptr.state.borrow().current_cursor) };
995 Some(1)
996}
997
998fn handle_mouse_wheel_settings_msg(
999 wparam: WPARAM,
1000 lparam: LPARAM,
1001 state_ptr: Rc<WindowsWindowStatePtr>,
1002) -> Option<isize> {
1003 match lparam.0 {
1004 1 => {
1005 state_ptr
1006 .state
1007 .borrow_mut()
1008 .mouse_wheel_settings
1009 .wheel_scroll_chars = wparam.0 as u32
1010 }
1011 2 => {
1012 state_ptr
1013 .state
1014 .borrow_mut()
1015 .mouse_wheel_settings
1016 .wheel_scroll_lines = wparam.0 as u32
1017 }
1018 _ => unreachable!(),
1019 }
1020 Some(0)
1021}
1022
1023fn parse_syskeydown_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
1024 let modifiers = current_modifiers();
1025 if !modifiers.alt {
1026 // on Windows, F10 can trigger this event, not just the alt key
1027 // and we just don't care about F10
1028 return None;
1029 }
1030
1031 let vk_code = wparam.loword();
1032 let basic_key = basic_vkcode_to_string(vk_code, modifiers);
1033 if basic_key.is_some() {
1034 return basic_key;
1035 }
1036
1037 let key = match VIRTUAL_KEY(vk_code) {
1038 VK_BACK => Some("backspace"),
1039 VK_RETURN => Some("enter"),
1040 VK_TAB => Some("tab"),
1041 VK_UP => Some("up"),
1042 VK_DOWN => Some("down"),
1043 VK_RIGHT => Some("right"),
1044 VK_LEFT => Some("left"),
1045 VK_HOME => Some("home"),
1046 VK_END => Some("end"),
1047 VK_PRIOR => Some("pageup"),
1048 VK_NEXT => Some("pagedown"),
1049 VK_ESCAPE => Some("escape"),
1050 VK_INSERT => Some("insert"),
1051 _ => None,
1052 };
1053
1054 if let Some(key) = key {
1055 Some(Keystroke {
1056 modifiers,
1057 key: key.to_string(),
1058 ime_key: None,
1059 })
1060 } else {
1061 None
1062 }
1063}
1064
1065fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
1066 let vk_code = wparam.loword();
1067
1068 let modifiers = current_modifiers();
1069 if modifiers.control || modifiers.alt {
1070 let basic_key = basic_vkcode_to_string(vk_code, modifiers);
1071 if basic_key.is_some() {
1072 return basic_key;
1073 }
1074 }
1075
1076 if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 {
1077 let offset = vk_code - VK_F1.0;
1078 return Some(Keystroke {
1079 modifiers,
1080 key: format!("f{}", offset + 1),
1081 ime_key: None,
1082 });
1083 }
1084
1085 let key = match VIRTUAL_KEY(vk_code) {
1086 VK_BACK => Some("backspace"),
1087 VK_RETURN => Some("enter"),
1088 VK_TAB => Some("tab"),
1089 VK_UP => Some("up"),
1090 VK_DOWN => Some("down"),
1091 VK_RIGHT => Some("right"),
1092 VK_LEFT => Some("left"),
1093 VK_HOME => Some("home"),
1094 VK_END => Some("end"),
1095 VK_PRIOR => Some("pageup"),
1096 VK_NEXT => Some("pagedown"),
1097 VK_ESCAPE => Some("escape"),
1098 VK_INSERT => Some("insert"),
1099 VK_DELETE => Some("delete"),
1100 _ => None,
1101 };
1102
1103 if let Some(key) = key {
1104 Some(Keystroke {
1105 modifiers,
1106 key: key.to_string(),
1107 ime_key: None,
1108 })
1109 } else {
1110 None
1111 }
1112}
1113
1114fn parse_char_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
1115 let src = [wparam.0 as u16];
1116 let Ok(first_char) = char::decode_utf16(src).collect::<Vec<_>>()[0] else {
1117 return None;
1118 };
1119 if first_char.is_control() {
1120 None
1121 } else {
1122 let mut modifiers = current_modifiers();
1123 // for characters that use 'shift' to type it is expected that the
1124 // shift is not reported if the uppercase/lowercase are the same and instead only the key is reported
1125 if first_char.to_lowercase().to_string() == first_char.to_uppercase().to_string() {
1126 modifiers.shift = false;
1127 }
1128 let key = match first_char {
1129 ' ' => "space".to_string(),
1130 first_char => first_char.to_lowercase().to_string(),
1131 };
1132 Some(Keystroke {
1133 modifiers,
1134 key,
1135 ime_key: Some(first_char.to_string()),
1136 })
1137 }
1138}
1139
1140/// mark window client rect to be re-drawn
1141/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-invalidaterect
1142pub(crate) fn invalidate_client_area(handle: HWND) {
1143 unsafe { InvalidateRect(handle, None, FALSE) };
1144}
1145
1146fn parse_ime_compostion_string(handle: HWND) -> Option<(String, usize)> {
1147 unsafe {
1148 let ctx = ImmGetContext(handle);
1149 let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0);
1150 let result = if string_len >= 0 {
1151 let mut buffer = vec![0u8; string_len as usize + 2];
1152 ImmGetCompositionStringW(
1153 ctx,
1154 GCS_COMPSTR,
1155 Some(buffer.as_mut_ptr() as _),
1156 string_len as _,
1157 );
1158 let wstring = std::slice::from_raw_parts::<u16>(
1159 buffer.as_mut_ptr().cast::<u16>(),
1160 string_len as usize / 2,
1161 );
1162 let string = String::from_utf16_lossy(wstring);
1163 Some((string, string_len as usize / 2))
1164 } else {
1165 None
1166 };
1167 ImmReleaseContext(handle, ctx);
1168 result
1169 }
1170}
1171
1172fn retrieve_composition_cursor_position(handle: HWND) -> usize {
1173 unsafe {
1174 let ctx = ImmGetContext(handle);
1175 let ret = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0);
1176 ImmReleaseContext(handle, ctx);
1177 ret as usize
1178 }
1179}
1180
1181fn parse_ime_compostion_result(handle: HWND) -> Option<String> {
1182 unsafe {
1183 let ctx = ImmGetContext(handle);
1184 let string_len = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, None, 0);
1185 let result = if string_len >= 0 {
1186 let mut buffer = vec![0u8; string_len as usize + 2];
1187 ImmGetCompositionStringW(
1188 ctx,
1189 GCS_RESULTSTR,
1190 Some(buffer.as_mut_ptr() as _),
1191 string_len as _,
1192 );
1193 let wstring = std::slice::from_raw_parts::<u16>(
1194 buffer.as_mut_ptr().cast::<u16>(),
1195 string_len as usize / 2,
1196 );
1197 let string = String::from_utf16_lossy(wstring);
1198 Some(string)
1199 } else {
1200 None
1201 };
1202 ImmReleaseContext(handle, ctx);
1203 result
1204 }
1205}
1206
1207fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
1208 match code {
1209 // VK_0 - VK_9
1210 48..=57 => Some(Keystroke {
1211 modifiers,
1212 key: format!("{}", code - VK_0.0),
1213 ime_key: None,
1214 }),
1215 // VK_A - VK_Z
1216 65..=90 => Some(Keystroke {
1217 modifiers,
1218 key: format!("{}", (b'a' + code as u8 - VK_A.0 as u8) as char),
1219 ime_key: None,
1220 }),
1221 // VK_F1 - VK_F24
1222 112..=135 => Some(Keystroke {
1223 modifiers,
1224 key: format!("f{}", code - VK_F1.0 + 1),
1225 ime_key: None,
1226 }),
1227 // OEM3: `/~, OEM_MINUS: -/_, OEM_PLUS: =/+, ...
1228 _ => {
1229 if let Some(key) = oemkey_vkcode_to_string(code) {
1230 Some(Keystroke {
1231 modifiers,
1232 key,
1233 ime_key: None,
1234 })
1235 } else {
1236 None
1237 }
1238 }
1239 }
1240}
1241
1242fn oemkey_vkcode_to_string(code: u16) -> Option<String> {
1243 match code {
1244 186 => Some(";".to_string()), // VK_OEM_1
1245 187 => Some("=".to_string()), // VK_OEM_PLUS
1246 188 => Some(",".to_string()), // VK_OEM_COMMA
1247 189 => Some("-".to_string()), // VK_OEM_MINUS
1248 190 => Some(".".to_string()), // VK_OEM_PERIOD
1249 // https://kbdlayout.info/features/virtualkeys/VK_ABNT_C1
1250 191 | 193 => Some("/".to_string()), // VK_OEM_2 VK_ABNT_C1
1251 192 => Some("`".to_string()), // VK_OEM_3
1252 219 => Some("[".to_string()), // VK_OEM_4
1253 220 => Some("\\".to_string()), // VK_OEM_5
1254 221 => Some("]".to_string()), // VK_OEM_6
1255 222 => Some("'".to_string()), // VK_OEM_7
1256 _ => None,
1257 }
1258}
1259
1260#[inline]
1261fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool {
1262 unsafe { GetKeyState(vkey.0 as i32) < 0 }
1263}
1264
1265#[inline]
1266fn current_modifiers() -> Modifiers {
1267 Modifiers {
1268 control: is_virtual_key_pressed(VK_CONTROL),
1269 alt: is_virtual_key_pressed(VK_MENU),
1270 shift: is_virtual_key_pressed(VK_SHIFT),
1271 platform: is_virtual_key_pressed(VK_LWIN) || is_virtual_key_pressed(VK_RWIN),
1272 function: false,
1273 }
1274}