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 Some(caret_range) = input_handler.selected_text_range() else {
589 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
590 return Some(0);
591 };
592 let caret_position = input_handler.bounds_for_range(caret_range).unwrap();
593 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
594 let config = CANDIDATEFORM {
595 dwStyle: CFS_CANDIDATEPOS,
596 // logical to physical
597 ptCurrentPos: POINT {
598 x: (caret_position.origin.x.0 * scale_factor) as i32,
599 y: (caret_position.origin.y.0 * scale_factor) as i32
600 + ((caret_position.size.height.0 * scale_factor) as i32 / 2),
601 },
602 ..Default::default()
603 };
604 ImmSetCandidateWindow(ctx, &config as _);
605 ImmReleaseContext(handle, ctx);
606 Some(0)
607 }
608}
609
610fn handle_ime_composition(
611 handle: HWND,
612 lparam: LPARAM,
613 state_ptr: Rc<WindowsWindowStatePtr>,
614) -> Option<isize> {
615 let mut ime_input = None;
616 if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
617 let Some((string, string_len)) = parse_ime_compostion_string(handle) else {
618 return None;
619 };
620 let mut lock = state_ptr.state.borrow_mut();
621 let Some(mut input_handler) = lock.input_handler.take() else {
622 return None;
623 };
624 drop(lock);
625 input_handler.replace_and_mark_text_in_range(None, string.as_str(), Some(0..string_len));
626 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
627 ime_input = Some(string);
628 }
629 if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
630 let Some(ref comp_string) = ime_input else {
631 return None;
632 };
633 let caret_pos = retrieve_composition_cursor_position(handle);
634 let mut lock = state_ptr.state.borrow_mut();
635 let Some(mut input_handler) = lock.input_handler.take() else {
636 return None;
637 };
638 drop(lock);
639 input_handler.replace_and_mark_text_in_range(None, comp_string, Some(0..caret_pos));
640 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
641 }
642 if lparam.0 as u32 & GCS_RESULTSTR.0 > 0 {
643 let Some(comp_result) = parse_ime_compostion_result(handle) else {
644 return None;
645 };
646 let mut lock = state_ptr.state.borrow_mut();
647 let Some(mut input_handler) = lock.input_handler.take() else {
648 return Some(1);
649 };
650 drop(lock);
651 input_handler.replace_text_in_range(None, &comp_result);
652 state_ptr.state.borrow_mut().input_handler = Some(input_handler);
653 invalidate_client_area(handle);
654 return Some(0);
655 }
656 // currently, we don't care other stuff
657 None
658}
659
660/// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
661fn handle_calc_client_size(
662 handle: HWND,
663 wparam: WPARAM,
664 lparam: LPARAM,
665 state_ptr: Rc<WindowsWindowStatePtr>,
666) -> Option<isize> {
667 if !state_ptr.hide_title_bar || state_ptr.state.borrow().is_fullscreen() {
668 return None;
669 }
670
671 if wparam.0 == 0 {
672 return None;
673 }
674
675 let dpi = unsafe { GetDpiForWindow(handle) };
676
677 let frame_x = unsafe { GetSystemMetricsForDpi(SM_CXFRAME, dpi) };
678 let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
679 let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
680
681 // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure
682 let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS;
683 let mut requested_client_rect = unsafe { &mut ((*params).rgrc) };
684
685 requested_client_rect[0].right -= frame_x + padding;
686 requested_client_rect[0].left += frame_x + padding;
687 requested_client_rect[0].bottom -= frame_y + padding;
688
689 Some(0)
690}
691
692fn handle_activate_msg(
693 handle: HWND,
694 wparam: WPARAM,
695 state_ptr: Rc<WindowsWindowStatePtr>,
696) -> Option<isize> {
697 let activated = wparam.loword() > 0;
698 if state_ptr.hide_title_bar {
699 if let Some(titlebar_rect) = state_ptr.state.borrow().get_titlebar_rect().log_err() {
700 unsafe { InvalidateRect(handle, Some(&titlebar_rect), FALSE) };
701 }
702 }
703 let this = state_ptr.clone();
704 state_ptr
705 .executor
706 .spawn(async move {
707 let mut lock = this.state.borrow_mut();
708 if let Some(mut cb) = lock.callbacks.active_status_change.take() {
709 drop(lock);
710 cb(activated);
711 this.state.borrow_mut().callbacks.active_status_change = Some(cb);
712 }
713 })
714 .detach();
715
716 None
717}
718
719fn handle_create_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
720 let mut size_rect = RECT::default();
721 unsafe { GetWindowRect(handle, &mut size_rect).log_err() };
722
723 let width = size_rect.right - size_rect.left;
724 let height = size_rect.bottom - size_rect.top;
725
726 if state_ptr.hide_title_bar {
727 unsafe {
728 SetWindowPos(
729 handle,
730 None,
731 size_rect.left,
732 size_rect.top,
733 width,
734 height,
735 SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE,
736 )
737 .log_err()
738 };
739 }
740
741 Some(0)
742}
743
744fn handle_dpi_changed_msg(
745 handle: HWND,
746 wparam: WPARAM,
747 lparam: LPARAM,
748 state_ptr: Rc<WindowsWindowStatePtr>,
749) -> Option<isize> {
750 let new_dpi = wparam.loword() as f32;
751 state_ptr.state.borrow_mut().scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
752
753 let rect = unsafe { &*(lparam.0 as *const RECT) };
754 let width = rect.right - rect.left;
755 let height = rect.bottom - rect.top;
756 // this will emit `WM_SIZE` and `WM_MOVE` right here
757 // even before this function returns
758 // the new size is handled in `WM_SIZE`
759 unsafe {
760 SetWindowPos(
761 handle,
762 None,
763 rect.left,
764 rect.top,
765 width,
766 height,
767 SWP_NOZORDER | SWP_NOACTIVATE,
768 )
769 .context("unable to set window position after dpi has changed")
770 .log_err();
771 }
772 invalidate_client_area(handle);
773
774 Some(0)
775}
776
777fn handle_hit_test_msg(
778 handle: HWND,
779 msg: u32,
780 wparam: WPARAM,
781 lparam: LPARAM,
782 state_ptr: Rc<WindowsWindowStatePtr>,
783) -> Option<isize> {
784 if !state_ptr.hide_title_bar {
785 return None;
786 }
787
788 // default handler for resize areas
789 let hit = unsafe { DefWindowProcW(handle, msg, wparam, lparam) };
790 if matches!(
791 hit.0 as u32,
792 HTNOWHERE
793 | HTRIGHT
794 | HTLEFT
795 | HTTOPLEFT
796 | HTTOP
797 | HTTOPRIGHT
798 | HTBOTTOMRIGHT
799 | HTBOTTOM
800 | HTBOTTOMLEFT
801 ) {
802 return Some(hit.0);
803 }
804
805 if state_ptr.state.borrow().is_fullscreen() {
806 return Some(HTCLIENT as _);
807 }
808
809 let dpi = unsafe { GetDpiForWindow(handle) };
810 let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
811 let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
812
813 let mut cursor_point = POINT {
814 x: lparam.signed_loword().into(),
815 y: lparam.signed_hiword().into(),
816 };
817 unsafe { ScreenToClient(handle, &mut cursor_point) };
818 if cursor_point.y > 0 && cursor_point.y < frame_y + padding {
819 return Some(HTTOP as _);
820 }
821
822 let titlebar_rect = state_ptr.state.borrow().get_titlebar_rect();
823 if let Ok(titlebar_rect) = titlebar_rect {
824 if cursor_point.y < titlebar_rect.bottom {
825 let caption_btn_width = (state_ptr.state.borrow().caption_button_width().0
826 * state_ptr.state.borrow().scale_factor) as i32;
827 if cursor_point.x >= titlebar_rect.right - caption_btn_width {
828 return Some(HTCLOSE as _);
829 } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 {
830 return Some(HTMAXBUTTON as _);
831 } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 {
832 return Some(HTMINBUTTON as _);
833 }
834
835 return Some(HTCAPTION as _);
836 }
837 }
838
839 Some(HTCLIENT as _)
840}
841
842fn handle_nc_mouse_move_msg(
843 handle: HWND,
844 lparam: LPARAM,
845 state_ptr: Rc<WindowsWindowStatePtr>,
846) -> Option<isize> {
847 if !state_ptr.hide_title_bar {
848 return None;
849 }
850
851 let mut lock = state_ptr.state.borrow_mut();
852 if let Some(mut callback) = lock.callbacks.input.take() {
853 let scale_factor = lock.scale_factor;
854 drop(lock);
855 let mut cursor_point = POINT {
856 x: lparam.signed_loword().into(),
857 y: lparam.signed_hiword().into(),
858 };
859 unsafe { ScreenToClient(handle, &mut cursor_point) };
860 let event = MouseMoveEvent {
861 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
862 pressed_button: None,
863 modifiers: current_modifiers(),
864 };
865 let result = if callback(PlatformInput::MouseMove(event)).default_prevented {
866 Some(0)
867 } else {
868 Some(1)
869 };
870 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
871
872 result
873 } else {
874 None
875 }
876}
877
878fn handle_nc_mouse_down_msg(
879 handle: HWND,
880 button: MouseButton,
881 wparam: WPARAM,
882 lparam: LPARAM,
883 state_ptr: Rc<WindowsWindowStatePtr>,
884) -> Option<isize> {
885 if !state_ptr.hide_title_bar {
886 return None;
887 }
888
889 let mut lock = state_ptr.state.borrow_mut();
890 let result = if let Some(mut callback) = lock.callbacks.input.take() {
891 let scale_factor = lock.scale_factor;
892 let mut cursor_point = POINT {
893 x: lparam.signed_loword().into(),
894 y: lparam.signed_hiword().into(),
895 };
896 unsafe { ScreenToClient(handle, &mut cursor_point) };
897 let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
898 let click_count = lock.click_state.update(button, physical_point);
899 drop(lock);
900 let event = MouseDownEvent {
901 button,
902 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
903 modifiers: current_modifiers(),
904 click_count,
905 first_mouse: false,
906 };
907 let result = if callback(PlatformInput::MouseDown(event)).default_prevented {
908 Some(0)
909 } else {
910 None
911 };
912 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
913
914 result
915 } else {
916 None
917 };
918
919 // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
920 result.or_else(|| matches!(wparam.0 as u32, HTMINBUTTON | HTMAXBUTTON | HTCLOSE).then_some(0))
921}
922
923fn handle_nc_mouse_up_msg(
924 handle: HWND,
925 button: MouseButton,
926 wparam: WPARAM,
927 lparam: LPARAM,
928 state_ptr: Rc<WindowsWindowStatePtr>,
929) -> Option<isize> {
930 if !state_ptr.hide_title_bar {
931 return None;
932 }
933
934 let mut lock = state_ptr.state.borrow_mut();
935 if let Some(mut callback) = lock.callbacks.input.take() {
936 let scale_factor = lock.scale_factor;
937 drop(lock);
938 let mut cursor_point = POINT {
939 x: lparam.signed_loword().into(),
940 y: lparam.signed_hiword().into(),
941 };
942 unsafe { ScreenToClient(handle, &mut cursor_point) };
943 let event = MouseUpEvent {
944 button,
945 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
946 modifiers: current_modifiers(),
947 click_count: 1,
948 };
949 let result = if callback(PlatformInput::MouseUp(event)).default_prevented {
950 Some(0)
951 } else {
952 None
953 };
954 state_ptr.state.borrow_mut().callbacks.input = Some(callback);
955 if result.is_some() {
956 return result;
957 }
958 } else {
959 drop(lock);
960 }
961
962 if button == MouseButton::Left {
963 match wparam.0 as u32 {
964 HTMINBUTTON => unsafe {
965 ShowWindowAsync(handle, SW_MINIMIZE);
966 },
967 HTMAXBUTTON => unsafe {
968 if state_ptr.state.borrow().is_maximized() {
969 ShowWindowAsync(handle, SW_NORMAL);
970 } else {
971 ShowWindowAsync(handle, SW_MAXIMIZE);
972 }
973 },
974 HTCLOSE => unsafe {
975 PostMessageW(handle, WM_CLOSE, WPARAM::default(), LPARAM::default()).log_err();
976 },
977 _ => return None,
978 };
979 return Some(0);
980 }
981
982 None
983}
984
985fn handle_cursor_changed(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
986 state_ptr.state.borrow_mut().current_cursor = HCURSOR(lparam.0);
987 Some(0)
988}
989
990fn handle_set_cursor(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
991 if matches!(
992 lparam.loword() as u32,
993 HTLEFT | HTRIGHT | HTTOP | HTTOPLEFT | HTTOPRIGHT | HTBOTTOM | HTBOTTOMLEFT | HTBOTTOMRIGHT
994 ) {
995 return None;
996 }
997 unsafe { SetCursor(state_ptr.state.borrow().current_cursor) };
998 Some(1)
999}
1000
1001fn handle_mouse_wheel_settings_msg(
1002 wparam: WPARAM,
1003 lparam: LPARAM,
1004 state_ptr: Rc<WindowsWindowStatePtr>,
1005) -> Option<isize> {
1006 match lparam.0 {
1007 1 => {
1008 state_ptr
1009 .state
1010 .borrow_mut()
1011 .mouse_wheel_settings
1012 .wheel_scroll_chars = wparam.0 as u32
1013 }
1014 2 => {
1015 state_ptr
1016 .state
1017 .borrow_mut()
1018 .mouse_wheel_settings
1019 .wheel_scroll_lines = wparam.0 as u32
1020 }
1021 _ => unreachable!(),
1022 }
1023 Some(0)
1024}
1025
1026fn parse_syskeydown_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
1027 let modifiers = current_modifiers();
1028 if !modifiers.alt {
1029 // on Windows, F10 can trigger this event, not just the alt key
1030 // and we just don't care about F10
1031 return None;
1032 }
1033
1034 let vk_code = wparam.loword();
1035 let basic_key = basic_vkcode_to_string(vk_code, modifiers);
1036 if basic_key.is_some() {
1037 return basic_key;
1038 }
1039
1040 let key = match VIRTUAL_KEY(vk_code) {
1041 VK_BACK => Some("backspace"),
1042 VK_RETURN => Some("enter"),
1043 VK_TAB => Some("tab"),
1044 VK_UP => Some("up"),
1045 VK_DOWN => Some("down"),
1046 VK_RIGHT => Some("right"),
1047 VK_LEFT => Some("left"),
1048 VK_HOME => Some("home"),
1049 VK_END => Some("end"),
1050 VK_PRIOR => Some("pageup"),
1051 VK_NEXT => Some("pagedown"),
1052 VK_ESCAPE => Some("escape"),
1053 VK_INSERT => Some("insert"),
1054 _ => None,
1055 };
1056
1057 if let Some(key) = key {
1058 Some(Keystroke {
1059 modifiers,
1060 key: key.to_string(),
1061 ime_key: None,
1062 })
1063 } else {
1064 None
1065 }
1066}
1067
1068fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
1069 let vk_code = wparam.loword();
1070
1071 let modifiers = current_modifiers();
1072 if modifiers.control || modifiers.alt {
1073 let basic_key = basic_vkcode_to_string(vk_code, modifiers);
1074 if basic_key.is_some() {
1075 return basic_key;
1076 }
1077 }
1078
1079 if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 {
1080 let offset = vk_code - VK_F1.0;
1081 return Some(Keystroke {
1082 modifiers,
1083 key: format!("f{}", offset + 1),
1084 ime_key: None,
1085 });
1086 }
1087
1088 let key = match VIRTUAL_KEY(vk_code) {
1089 VK_BACK => Some("backspace"),
1090 VK_RETURN => Some("enter"),
1091 VK_TAB => Some("tab"),
1092 VK_UP => Some("up"),
1093 VK_DOWN => Some("down"),
1094 VK_RIGHT => Some("right"),
1095 VK_LEFT => Some("left"),
1096 VK_HOME => Some("home"),
1097 VK_END => Some("end"),
1098 VK_PRIOR => Some("pageup"),
1099 VK_NEXT => Some("pagedown"),
1100 VK_ESCAPE => Some("escape"),
1101 VK_INSERT => Some("insert"),
1102 VK_DELETE => Some("delete"),
1103 _ => None,
1104 };
1105
1106 if let Some(key) = key {
1107 Some(Keystroke {
1108 modifiers,
1109 key: key.to_string(),
1110 ime_key: None,
1111 })
1112 } else {
1113 None
1114 }
1115}
1116
1117fn parse_char_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
1118 let src = [wparam.0 as u16];
1119 let Ok(first_char) = char::decode_utf16(src).collect::<Vec<_>>()[0] else {
1120 return None;
1121 };
1122 if first_char.is_control() {
1123 None
1124 } else {
1125 let mut modifiers = current_modifiers();
1126 // for characters that use 'shift' to type it is expected that the
1127 // shift is not reported if the uppercase/lowercase are the same and instead only the key is reported
1128 if first_char.to_lowercase().to_string() == first_char.to_uppercase().to_string() {
1129 modifiers.shift = false;
1130 }
1131 let key = match first_char {
1132 ' ' => "space".to_string(),
1133 first_char => first_char.to_lowercase().to_string(),
1134 };
1135 Some(Keystroke {
1136 modifiers,
1137 key,
1138 ime_key: Some(first_char.to_string()),
1139 })
1140 }
1141}
1142
1143/// mark window client rect to be re-drawn
1144/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-invalidaterect
1145pub(crate) fn invalidate_client_area(handle: HWND) {
1146 unsafe { InvalidateRect(handle, None, FALSE) };
1147}
1148
1149fn parse_ime_compostion_string(handle: HWND) -> Option<(String, usize)> {
1150 unsafe {
1151 let ctx = ImmGetContext(handle);
1152 let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0);
1153 let result = if string_len >= 0 {
1154 let mut buffer = vec![0u8; string_len as usize + 2];
1155 ImmGetCompositionStringW(
1156 ctx,
1157 GCS_COMPSTR,
1158 Some(buffer.as_mut_ptr() as _),
1159 string_len as _,
1160 );
1161 let wstring = std::slice::from_raw_parts::<u16>(
1162 buffer.as_mut_ptr().cast::<u16>(),
1163 string_len as usize / 2,
1164 );
1165 let string = String::from_utf16_lossy(wstring);
1166 Some((string, string_len as usize / 2))
1167 } else {
1168 None
1169 };
1170 ImmReleaseContext(handle, ctx);
1171 result
1172 }
1173}
1174
1175fn retrieve_composition_cursor_position(handle: HWND) -> usize {
1176 unsafe {
1177 let ctx = ImmGetContext(handle);
1178 let ret = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0);
1179 ImmReleaseContext(handle, ctx);
1180 ret as usize
1181 }
1182}
1183
1184fn parse_ime_compostion_result(handle: HWND) -> Option<String> {
1185 unsafe {
1186 let ctx = ImmGetContext(handle);
1187 let string_len = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, None, 0);
1188 let result = if string_len >= 0 {
1189 let mut buffer = vec![0u8; string_len as usize + 2];
1190 ImmGetCompositionStringW(
1191 ctx,
1192 GCS_RESULTSTR,
1193 Some(buffer.as_mut_ptr() as _),
1194 string_len as _,
1195 );
1196 let wstring = std::slice::from_raw_parts::<u16>(
1197 buffer.as_mut_ptr().cast::<u16>(),
1198 string_len as usize / 2,
1199 );
1200 let string = String::from_utf16_lossy(wstring);
1201 Some(string)
1202 } else {
1203 None
1204 };
1205 ImmReleaseContext(handle, ctx);
1206 result
1207 }
1208}
1209
1210fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
1211 match code {
1212 // VK_0 - VK_9
1213 48..=57 => Some(Keystroke {
1214 modifiers,
1215 key: format!("{}", code - VK_0.0),
1216 ime_key: None,
1217 }),
1218 // VK_A - VK_Z
1219 65..=90 => Some(Keystroke {
1220 modifiers,
1221 key: format!("{}", (b'a' + code as u8 - VK_A.0 as u8) as char),
1222 ime_key: None,
1223 }),
1224 // VK_F1 - VK_F24
1225 112..=135 => Some(Keystroke {
1226 modifiers,
1227 key: format!("f{}", code - VK_F1.0 + 1),
1228 ime_key: None,
1229 }),
1230 // OEM3: `/~, OEM_MINUS: -/_, OEM_PLUS: =/+, ...
1231 _ => {
1232 if let Some(key) = oemkey_vkcode_to_string(code) {
1233 Some(Keystroke {
1234 modifiers,
1235 key,
1236 ime_key: None,
1237 })
1238 } else {
1239 None
1240 }
1241 }
1242 }
1243}
1244
1245fn oemkey_vkcode_to_string(code: u16) -> Option<String> {
1246 match code {
1247 186 => Some(";".to_string()), // VK_OEM_1
1248 187 => Some("=".to_string()), // VK_OEM_PLUS
1249 188 => Some(",".to_string()), // VK_OEM_COMMA
1250 189 => Some("-".to_string()), // VK_OEM_MINUS
1251 190 => Some(".".to_string()), // VK_OEM_PERIOD
1252 // https://kbdlayout.info/features/virtualkeys/VK_ABNT_C1
1253 191 | 193 => Some("/".to_string()), // VK_OEM_2 VK_ABNT_C1
1254 192 => Some("`".to_string()), // VK_OEM_3
1255 219 => Some("[".to_string()), // VK_OEM_4
1256 220 => Some("\\".to_string()), // VK_OEM_5
1257 221 => Some("]".to_string()), // VK_OEM_6
1258 222 => Some("'".to_string()), // VK_OEM_7
1259 _ => None,
1260 }
1261}
1262
1263#[inline]
1264fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool {
1265 unsafe { GetKeyState(vkey.0 as i32) < 0 }
1266}
1267
1268#[inline]
1269fn current_modifiers() -> Modifiers {
1270 Modifiers {
1271 control: is_virtual_key_pressed(VK_CONTROL),
1272 alt: is_virtual_key_pressed(VK_MENU),
1273 shift: is_virtual_key_pressed(VK_SHIFT),
1274 platform: is_virtual_key_pressed(VK_LWIN) || is_virtual_key_pressed(VK_RWIN),
1275 function: false,
1276 }
1277}