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 for runnable in state_ptr.main_receiver.drain() {
175 runnable.run();
176 }
177 handle_paint_msg(handle, state_ptr)
178 } else {
179 None
180 }
181}
182
183fn handle_paint_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
184 let mut paint_struct = PAINTSTRUCT::default();
185 let _hdc = unsafe { BeginPaint(handle, &mut paint_struct) };
186 let mut lock = state_ptr.state.borrow_mut();
187 if let Some(mut request_frame) = lock.callbacks.request_frame.take() {
188 drop(lock);
189 request_frame();
190 state_ptr.state.borrow_mut().callbacks.request_frame = Some(request_frame);
191 }
192 unsafe { EndPaint(handle, &paint_struct) };
193 Some(0)
194}
195
196fn handle_close_msg(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
197 let mut lock = state_ptr.state.borrow_mut();
198 if let Some(mut callback) = lock.callbacks.should_close.take() {
199 drop(lock);
200 let should_close = callback();
201 state_ptr.state.borrow_mut().callbacks.should_close = Some(callback);
202 if should_close {
203 None
204 } else {
205 Some(0)
206 }
207 } else {
208 None
209 }
210}
211
212fn handle_destroy_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
213 let callback = {
214 let mut lock = state_ptr.state.borrow_mut();
215 lock.callbacks.close.take()
216 };
217 if let Some(callback) = callback {
218 callback();
219 }
220 unsafe {
221 PostMessageW(None, CLOSE_ONE_WINDOW, None, LPARAM(handle.0)).log_err();
222 }
223 Some(0)
224}
225
226fn handle_mouse_move_msg(
227 lparam: LPARAM,
228 wparam: WPARAM,
229 state_ptr: Rc<WindowsWindowStatePtr>,
230) -> Option<isize> {
231 let mut lock = state_ptr.state.borrow_mut();
232 if let Some(mut callback) = lock.callbacks.input.take() {
233 let scale_factor = lock.scale_factor;
234 drop(lock);
235 let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
236 flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
237 flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),
238 flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),
239 flags if flags.contains(MK_XBUTTON1) => {
240 Some(MouseButton::Navigate(NavigationDirection::Back))
241 }
242 flags if flags.contains(MK_XBUTTON2) => {
243 Some(MouseButton::Navigate(NavigationDirection::Forward))
244 }
245 _ => None,
246 };
247 let x = lparam.signed_loword() as f32;
248 let y = lparam.signed_hiword() as f32;
249 let event = MouseMoveEvent {
250 position: logical_point(x, y, scale_factor),
251 pressed_button,
252 modifiers: current_modifiers(),
253 };
254 let result = if callback(PlatformInput::MouseMove(event)).default_prevented {
255 Some(0)
256 } else {
257 Some(1)
258 };
259 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
260 return result;
261 }
262 Some(1)
263}
264
265fn handle_syskeydown_msg(
266 handle: HWND,
267 wparam: WPARAM,
268 lparam: LPARAM,
269 state_ptr: Rc<WindowsWindowStatePtr>,
270) -> Option<isize> {
271 // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
272 // shortcuts.
273 let Some(keystroke) = parse_syskeydown_msg_keystroke(wparam) else {
274 return None;
275 };
276 let mut lock = state_ptr.state.borrow_mut();
277 let Some(mut func) = lock.callbacks.input.take() else {
278 return None;
279 };
280 drop(lock);
281 let event = KeyDownEvent {
282 keystroke,
283 is_held: lparam.0 & (0x1 << 30) > 0,
284 };
285 let result = if func(PlatformInput::KeyDown(event)).default_prevented {
286 invalidate_client_area(handle);
287 Some(0)
288 } else {
289 None
290 };
291 state_ptr.state.borrow_mut().callbacks.input = Some(func);
292
293 result
294}
295
296fn handle_syskeyup_msg(
297 handle: HWND,
298 wparam: WPARAM,
299 state_ptr: Rc<WindowsWindowStatePtr>,
300) -> Option<isize> {
301 // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
302 // shortcuts.
303 let Some(keystroke) = parse_syskeydown_msg_keystroke(wparam) else {
304 return None;
305 };
306 let mut lock = state_ptr.state.borrow_mut();
307 let Some(mut func) = lock.callbacks.input.take() else {
308 return None;
309 };
310 drop(lock);
311 let event = KeyUpEvent { keystroke };
312 let result = if func(PlatformInput::KeyUp(event)).default_prevented {
313 invalidate_client_area(handle);
314 Some(0)
315 } else {
316 Some(1)
317 };
318 state_ptr.state.borrow_mut().callbacks.input = Some(func);
319
320 result
321}
322
323fn handle_keydown_msg(
324 handle: HWND,
325 wparam: WPARAM,
326 lparam: LPARAM,
327 state_ptr: Rc<WindowsWindowStatePtr>,
328) -> Option<isize> {
329 let Some(keystroke) = parse_keydown_msg_keystroke(wparam) else {
330 return Some(1);
331 };
332 let mut lock = state_ptr.state.borrow_mut();
333 let Some(mut func) = lock.callbacks.input.take() else {
334 return Some(1);
335 };
336 drop(lock);
337 let event = KeyDownEvent {
338 keystroke,
339 is_held: lparam.0 & (0x1 << 30) > 0,
340 };
341 let result = if func(PlatformInput::KeyDown(event)).default_prevented {
342 invalidate_client_area(handle);
343 Some(0)
344 } else {
345 Some(1)
346 };
347 state_ptr.state.borrow_mut().callbacks.input = Some(func);
348
349 result
350}
351
352fn handle_keyup_msg(
353 handle: HWND,
354 wparam: WPARAM,
355 state_ptr: Rc<WindowsWindowStatePtr>,
356) -> Option<isize> {
357 let Some(keystroke) = parse_keydown_msg_keystroke(wparam) else {
358 return Some(1);
359 };
360 let mut lock = state_ptr.state.borrow_mut();
361 let Some(mut func) = lock.callbacks.input.take() else {
362 return Some(1);
363 };
364 drop(lock);
365 let event = KeyUpEvent { keystroke };
366 let result = if func(PlatformInput::KeyUp(event)).default_prevented {
367 invalidate_client_area(handle);
368 Some(0)
369 } else {
370 Some(1)
371 };
372 state_ptr.state.borrow_mut().callbacks.input = Some(func);
373
374 result
375}
376
377fn handle_char_msg(
378 handle: HWND,
379 wparam: WPARAM,
380 lparam: LPARAM,
381 state_ptr: Rc<WindowsWindowStatePtr>,
382) -> Option<isize> {
383 let Some(keystroke) = parse_char_msg_keystroke(wparam) else {
384 return Some(1);
385 };
386 let mut lock = state_ptr.state.borrow_mut();
387 let Some(mut func) = lock.callbacks.input.take() else {
388 return Some(1);
389 };
390 drop(lock);
391 let ime_key = keystroke.ime_key.clone();
392 let event = KeyDownEvent {
393 keystroke,
394 is_held: lparam.0 & (0x1 << 30) > 0,
395 };
396
397 let dispatch_event_result = func(PlatformInput::KeyDown(event));
398 let mut lock = state_ptr.state.borrow_mut();
399 lock.callbacks.input = Some(func);
400 if dispatch_event_result.default_prevented || !dispatch_event_result.propagate {
401 invalidate_client_area(handle);
402 return Some(0);
403 }
404 let Some(ime_char) = ime_key else {
405 return Some(1);
406 };
407 let Some(mut input_handler) = lock.input_handler.take() else {
408 return Some(1);
409 };
410 drop(lock);
411 input_handler.replace_text_in_range(None, &ime_char);
412 invalidate_client_area(handle);
413 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
414
415 Some(0)
416}
417
418fn handle_mouse_down_msg(
419 button: MouseButton,
420 lparam: LPARAM,
421 state_ptr: Rc<WindowsWindowStatePtr>,
422) -> Option<isize> {
423 let mut lock = state_ptr.state.borrow_mut();
424 if let Some(mut callback) = lock.callbacks.input.take() {
425 let x = lparam.signed_loword() as f32;
426 let y = lparam.signed_hiword() as f32;
427 let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
428 let click_count = lock.click_state.update(button, physical_point);
429 let scale_factor = lock.scale_factor;
430 drop(lock);
431
432 let event = MouseDownEvent {
433 button,
434 position: logical_point(x, y, scale_factor),
435 modifiers: current_modifiers(),
436 click_count,
437 first_mouse: false,
438 };
439 let result = if callback(PlatformInput::MouseDown(event)).default_prevented {
440 Some(0)
441 } else {
442 Some(1)
443 };
444 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
445
446 result
447 } else {
448 Some(1)
449 }
450}
451
452fn handle_mouse_up_msg(
453 button: MouseButton,
454 lparam: LPARAM,
455 state_ptr: Rc<WindowsWindowStatePtr>,
456) -> Option<isize> {
457 let mut lock = state_ptr.state.borrow_mut();
458 if let Some(mut callback) = lock.callbacks.input.take() {
459 let x = lparam.signed_loword() as f32;
460 let y = lparam.signed_hiword() as f32;
461 let click_count = lock.click_state.current_count;
462 let scale_factor = lock.scale_factor;
463 drop(lock);
464
465 let event = MouseUpEvent {
466 button,
467 position: logical_point(x, y, scale_factor),
468 modifiers: current_modifiers(),
469 click_count,
470 };
471 let result = if callback(PlatformInput::MouseUp(event)).default_prevented {
472 Some(0)
473 } else {
474 Some(1)
475 };
476 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
477
478 result
479 } else {
480 Some(1)
481 }
482}
483
484fn handle_xbutton_msg(
485 wparam: WPARAM,
486 lparam: LPARAM,
487 handler: impl Fn(MouseButton, LPARAM, Rc<WindowsWindowStatePtr>) -> Option<isize>,
488 state_ptr: Rc<WindowsWindowStatePtr>,
489) -> Option<isize> {
490 let nav_dir = match wparam.hiword() {
491 XBUTTON1 => NavigationDirection::Back,
492 XBUTTON2 => NavigationDirection::Forward,
493 _ => return Some(1),
494 };
495 handler(MouseButton::Navigate(nav_dir), lparam, state_ptr)
496}
497
498fn handle_mouse_wheel_msg(
499 handle: HWND,
500 wparam: WPARAM,
501 lparam: LPARAM,
502 state_ptr: Rc<WindowsWindowStatePtr>,
503) -> Option<isize> {
504 let mut lock = state_ptr.state.borrow_mut();
505 if let Some(mut callback) = lock.callbacks.input.take() {
506 let scale_factor = lock.scale_factor;
507 let wheel_scroll_lines = lock.mouse_wheel_settings.wheel_scroll_lines;
508 drop(lock);
509 let wheel_distance =
510 (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_lines as f32;
511 let mut cursor_point = POINT {
512 x: lparam.signed_loword().into(),
513 y: lparam.signed_hiword().into(),
514 };
515 unsafe { ScreenToClient(handle, &mut cursor_point) };
516 let event = ScrollWheelEvent {
517 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
518 delta: ScrollDelta::Lines(Point {
519 x: 0.0,
520 y: wheel_distance,
521 }),
522 modifiers: current_modifiers(),
523 touch_phase: TouchPhase::Moved,
524 };
525 let result = if callback(PlatformInput::ScrollWheel(event)).default_prevented {
526 Some(0)
527 } else {
528 Some(1)
529 };
530 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
531
532 result
533 } else {
534 Some(1)
535 }
536}
537
538fn handle_mouse_horizontal_wheel_msg(
539 handle: HWND,
540 wparam: WPARAM,
541 lparam: LPARAM,
542 state_ptr: Rc<WindowsWindowStatePtr>,
543) -> Option<isize> {
544 let mut lock = state_ptr.state.borrow_mut();
545 if let Some(mut callback) = lock.callbacks.input.take() {
546 let scale_factor = lock.scale_factor;
547 let wheel_scroll_chars = lock.mouse_wheel_settings.wheel_scroll_chars;
548 drop(lock);
549 let wheel_distance =
550 (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_chars as f32;
551 let mut cursor_point = POINT {
552 x: lparam.signed_loword().into(),
553 y: lparam.signed_hiword().into(),
554 };
555 unsafe { ScreenToClient(handle, &mut cursor_point) };
556 let event = ScrollWheelEvent {
557 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
558 delta: ScrollDelta::Lines(Point {
559 x: wheel_distance,
560 y: 0.0,
561 }),
562 modifiers: current_modifiers(),
563 touch_phase: TouchPhase::Moved,
564 };
565 let result = if callback(PlatformInput::ScrollWheel(event)).default_prevented {
566 Some(0)
567 } else {
568 Some(1)
569 };
570 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
571
572 result
573 } else {
574 Some(1)
575 }
576}
577
578fn handle_ime_position(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
579 unsafe {
580 let mut lock = state_ptr.state.borrow_mut();
581 let ctx = ImmGetContext(handle);
582 let Some(mut input_handler) = lock.input_handler.take() else {
583 return Some(1);
584 };
585 let scale_factor = lock.scale_factor;
586 drop(lock);
587
588 let caret_range = input_handler.selected_text_range().unwrap_or_default();
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}