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