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