Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:08:43.146Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

events.rs

1751 lines · 63.4 KB · rust
1use std::{cell::Cell, rc::Rc, sync::atomic::Ordering};
2
3use anyhow::Context as _;
4use gpui_util::ResultExt;
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::*;
21use gpui::*;
22
23pub(crate) const WM_GPUI_CURSOR_STYLE_CHANGED: u32 = WM_USER + 1;
24pub(crate) const WM_GPUI_CLOSE_ONE_WINDOW: u32 = WM_USER + 2;
25pub(crate) const WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD: u32 = WM_USER + 3;
26pub(crate) const WM_GPUI_DOCK_MENU_ACTION: u32 = WM_USER + 4;
27pub(crate) const WM_GPUI_FORCE_UPDATE_WINDOW: u32 = WM_USER + 5;
28pub(crate) const WM_GPUI_KEYBOARD_LAYOUT_CHANGED: u32 = WM_USER + 6;
29pub(crate) const WM_GPUI_GPU_DEVICE_LOST: u32 = WM_USER + 7;
30pub(crate) const WM_GPUI_KEYDOWN: u32 = WM_USER + 8;
31
32const SIZE_MOVE_LOOP_TIMER_ID: usize = 1;
33
34/// Coordinates window draws on the UI thread. Owned by the platform and
35/// shared with every window (like `WindowsPlatformState::cursor_visible`),
36/// because the coordination is inherently cross-window: while window A is
37/// drawing, a re-entrant paint request for window B must be deferred.
38pub(crate) struct DrawCoordinator {
39    /// Whether some window is currently inside `draw_window`. Win32 can
40    /// re-enter the window procedure while a draw is in progress (e.g.
41    /// cross-thread `SendMessage` dispatch during message pumping, or modal
42    /// message loops entered by COM calls), and drawing re-entrantly would
43    /// nest GPUI draws. Nested draws are wasted work whose output is
44    /// immediately redrawn, so we defer them instead.
45    drawing: Cell<bool>,
46}
47
48impl DrawCoordinator {
49    pub(crate) fn new() -> Self {
50        Self {
51            drawing: Cell::new(false),
52        }
53    }
54
55    fn try_begin_draw(&self) -> Option<DrawWindowGuard<'_>> {
56        // This only covers the `draw_window` span (which extends past the GPUI
57        // draw, through presentation and IME updates). Requests that arrive
58        // re-entrantly during GPUI-initiated draws (e.g. key dispatch or
59        // opening a window draws synchronously) are deferred by GPUI's
60        // `on_request_frame` callback itself, which no-ops in that case.
61        if self.drawing.get() {
62            None
63        } else {
64            self.drawing.set(true);
65            Some(DrawWindowGuard { coordinator: self })
66        }
67    }
68}
69
70struct DrawWindowGuard<'a> {
71    coordinator: &'a DrawCoordinator,
72}
73
74impl Drop for DrawWindowGuard<'_> {
75    fn drop(&mut self) {
76        self.coordinator.drawing.set(false);
77    }
78}
79
80impl WindowsWindowInner {
81    pub(crate) fn handle_msg(
82        self: &Rc<Self>,
83        handle: HWND,
84        msg: u32,
85        wparam: WPARAM,
86        lparam: LPARAM,
87    ) -> LRESULT {
88        let handled = match msg {
89            // eagerly activate the window, so calls to `active_window` will work correctly
90            WM_MOUSEACTIVATE => {
91                unsafe { SetActiveWindow(handle).ok() };
92                None
93            }
94            WM_ACTIVATE => self.handle_activate_msg(wparam),
95            WM_CREATE => self.handle_create_msg(handle),
96            WM_MOVE => self.handle_move_msg(handle, lparam),
97            WM_SIZE => self.handle_size_msg(wparam, lparam),
98            WM_GETMINMAXINFO => self.handle_get_min_max_info_msg(lparam),
99            WM_ENTERSIZEMOVE | WM_ENTERMENULOOP => self.handle_size_move_loop(handle),
100            WM_EXITSIZEMOVE | WM_EXITMENULOOP => self.handle_size_move_loop_exit(handle),
101            WM_TIMER => self.handle_timer_msg(handle, wparam),
102            WM_NCCALCSIZE => self.handle_calc_client_size(handle, wparam, lparam),
103            WM_DPICHANGED => self.handle_dpi_changed_msg(handle, wparam, lparam),
104            WM_DISPLAYCHANGE => self.handle_display_change_msg(handle),
105            WM_NCHITTEST => self.handle_hit_test_msg(handle, lparam),
106            WM_PAINT => self.handle_paint_msg(handle),
107            WM_CLOSE => self.handle_close_msg(),
108            WM_DESTROY => self.handle_destroy_msg(handle),
109            WM_MOUSEMOVE => self.handle_mouse_move_msg(handle, lparam, wparam),
110            WM_MOUSELEAVE | WM_NCMOUSELEAVE => self.handle_mouse_leave_msg(),
111            WM_NCMOUSEMOVE => self.handle_nc_mouse_move_msg(handle, lparam),
112            // Treat double click as a second single click, since we track the double clicks ourselves.
113            // If you don't interact with any elements, this will fall through to the windows default
114            // behavior of toggling whether the window is maximized.
115            WM_NCLBUTTONDBLCLK | WM_NCLBUTTONDOWN => {
116                self.handle_nc_mouse_down_msg(handle, MouseButton::Left, wparam, lparam)
117            }
118            WM_NCRBUTTONDOWN => {
119                self.handle_nc_mouse_down_msg(handle, MouseButton::Right, wparam, lparam)
120            }
121            WM_NCMBUTTONDOWN => {
122                self.handle_nc_mouse_down_msg(handle, MouseButton::Middle, wparam, lparam)
123            }
124            WM_NCLBUTTONUP => {
125                self.handle_nc_mouse_up_msg(handle, MouseButton::Left, wparam, lparam)
126            }
127            WM_NCRBUTTONUP => {
128                self.handle_nc_mouse_up_msg(handle, MouseButton::Right, wparam, lparam)
129            }
130            WM_NCMBUTTONUP => {
131                self.handle_nc_mouse_up_msg(handle, MouseButton::Middle, wparam, lparam)
132            }
133            WM_LBUTTONDOWN => self.handle_mouse_down_msg(handle, MouseButton::Left, lparam),
134            WM_RBUTTONDOWN => self.handle_mouse_down_msg(handle, MouseButton::Right, lparam),
135            WM_MBUTTONDOWN => self.handle_mouse_down_msg(handle, MouseButton::Middle, lparam),
136            WM_XBUTTONDOWN => {
137                self.handle_xbutton_msg(handle, wparam, lparam, Self::handle_mouse_down_msg)
138            }
139            WM_LBUTTONUP => self.handle_mouse_up_msg(handle, MouseButton::Left, lparam),
140            WM_RBUTTONUP => self.handle_mouse_up_msg(handle, MouseButton::Right, lparam),
141            WM_MBUTTONUP => self.handle_mouse_up_msg(handle, MouseButton::Middle, lparam),
142            WM_XBUTTONUP => {
143                self.handle_xbutton_msg(handle, wparam, lparam, Self::handle_mouse_up_msg)
144            }
145            WM_MOUSEWHEEL => self.handle_mouse_wheel_msg(handle, wparam, lparam),
146            WM_MOUSEHWHEEL => self.handle_mouse_horizontal_wheel_msg(handle, wparam, lparam),
147            WM_SYSKEYUP => self.handle_syskeyup_msg(wparam, lparam),
148            WM_KEYUP => self.handle_keyup_msg(wparam, lparam),
149            WM_GPUI_KEYDOWN => self.handle_keydown_msg(wparam, lparam),
150            WM_CHAR => self.handle_char_msg(wparam),
151            WM_IME_STARTCOMPOSITION => self.handle_ime_position(handle),
152            WM_IME_COMPOSITION => self.handle_ime_composition(handle, lparam),
153            WM_SETCURSOR => self.handle_set_cursor(handle, lparam),
154            WM_SETTINGCHANGE => self.handle_system_settings_changed(handle, wparam, lparam),
155            WM_INPUTLANGCHANGE => self.handle_input_language_changed(),
156            WM_SHOWWINDOW => self.handle_window_visibility_changed(handle, wparam),
157            WM_GPUI_CURSOR_STYLE_CHANGED => self.handle_cursor_changed(lparam),
158            WM_GPUI_FORCE_UPDATE_WINDOW => self.draw_window(handle, true),
159            WM_GPUI_GPU_DEVICE_LOST => self.handle_device_lost(lparam),
160            DM_POINTERHITTEST => self.handle_dm_pointer_hit_test(wparam),
161            WM_GETOBJECT => self.handle_wm_getobject(wparam, lparam),
162            _ => None,
163        };
164        if let Some(n) = handled {
165            LRESULT(n)
166        } else {
167            unsafe { DefWindowProcW(handle, msg, wparam, lparam) }
168        }
169    }
170
171    fn handle_move_msg(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
172        let origin = logical_point(
173            lparam.signed_loword() as f32,
174            lparam.signed_hiword() as f32,
175            self.state.scale_factor.get(),
176        );
177        self.state.origin.set(origin);
178        let size = self.state.logical_size.get();
179        let center_x = origin.x.as_f32() + size.width.as_f32() / 2.;
180        let center_y = origin.y.as_f32() + size.height.as_f32() / 2.;
181        let monitor_bounds = self.state.display.get().bounds();
182        if center_x < monitor_bounds.left().as_f32()
183            || center_x > monitor_bounds.right().as_f32()
184            || center_y < monitor_bounds.top().as_f32()
185            || center_y > monitor_bounds.bottom().as_f32()
186        {
187            // center of the window may have moved to another monitor
188            let monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) };
189            // minimize the window can trigger this event too, in this case,
190            // monitor is invalid, we do nothing.
191            if !monitor.is_invalid() && self.state.display.get().handle != monitor {
192                // we will get the same monitor if we only have one
193                self.state.display.set(WindowsDisplay::new(
194                    WindowsDisplay::display_id_for_monitor(monitor),
195                )?);
196            }
197        }
198        if let Some(mut callback) = self.state.callbacks.moved.take() {
199            callback();
200            self.state.callbacks.moved.set(Some(callback));
201        }
202        Some(0)
203    }
204
205    fn handle_get_min_max_info_msg(&self, lparam: LPARAM) -> Option<isize> {
206        let min_size = self.state.min_size?;
207        let scale_factor = self.state.scale_factor.get();
208        let boarder_offset = &self.state.border_offset;
209
210        unsafe {
211            let minmax_info = &mut *(lparam.0 as *mut MINMAXINFO);
212            minmax_info.ptMinTrackSize.x = min_size.width.scale(scale_factor).as_f32() as i32
213                + boarder_offset.width_offset.get();
214            minmax_info.ptMinTrackSize.y = min_size.height.scale(scale_factor).as_f32() as i32
215                + boarder_offset.height_offset.get();
216        }
217        Some(0)
218    }
219
220    fn handle_size_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
221        // Don't resize the renderer when the window is minimized, but record that it was minimized so
222        // that on restore the swap chain can be recreated via `update_drawable_size_even_if_unchanged`.
223        if wparam.0 == SIZE_MINIMIZED as usize {
224            self.state
225                .restore_from_minimized
226                .set(self.state.callbacks.request_frame.take());
227            return Some(0);
228        }
229
230        let width = lparam.loword().max(1) as i32;
231        let height = lparam.hiword().max(1) as i32;
232        let new_size = size(DevicePixels(width), DevicePixels(height));
233
234        let scale_factor = self.state.scale_factor.get();
235        let mut should_resize_renderer = false;
236        if let Some(restore_from_minimized) = self.state.restore_from_minimized.take() {
237            self.state
238                .callbacks
239                .request_frame
240                .set(Some(restore_from_minimized));
241        } else {
242            should_resize_renderer = true;
243        }
244
245        self.handle_size_change(new_size, scale_factor, should_resize_renderer);
246        Some(0)
247    }
248
249    fn handle_size_change(
250        &self,
251        device_size: Size<DevicePixels>,
252        scale_factor: f32,
253        should_resize_renderer: bool,
254    ) {
255        let new_logical_size = device_size.to_pixels(scale_factor);
256
257        self.state.logical_size.set(new_logical_size);
258        if should_resize_renderer
259            && let Err(e) = self.state.renderer.borrow_mut().resize(device_size)
260        {
261            log::error!("Failed to resize renderer, invalidating devices: {}", e);
262            self.state
263                .invalidate_devices
264                .store(true, std::sync::atomic::Ordering::Release);
265        }
266        if let Some(mut callback) = self.state.callbacks.resize.take() {
267            callback(new_logical_size, scale_factor);
268            self.state.callbacks.resize.set(Some(callback));
269        }
270    }
271
272    fn handle_size_move_loop(&self, handle: HWND) -> Option<isize> {
273        unsafe {
274            let ret = SetTimer(
275                Some(handle),
276                SIZE_MOVE_LOOP_TIMER_ID,
277                USER_TIMER_MINIMUM,
278                None,
279            );
280            if ret == 0 {
281                log::error!(
282                    "unable to create timer: {}",
283                    std::io::Error::last_os_error()
284                );
285            }
286        }
287        None
288    }
289
290    fn handle_size_move_loop_exit(&self, handle: HWND) -> Option<isize> {
291        unsafe {
292            KillTimer(Some(handle), SIZE_MOVE_LOOP_TIMER_ID).log_err();
293        }
294        None
295    }
296
297    fn handle_timer_msg(&self, handle: HWND, wparam: WPARAM) -> Option<isize> {
298        if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID {
299            let mut runnables = self.main_receiver.clone().try_iter();
300            while let Some(Ok(runnable)) = runnables.next() {
301                WindowsDispatcher::execute_runnable(runnable);
302            }
303            self.handle_paint_msg(handle)
304        } else {
305            None
306        }
307    }
308
309    fn handle_paint_msg(&self, handle: HWND) -> Option<isize> {
310        self.draw_window(handle, false)
311    }
312
313    fn handle_close_msg(&self) -> Option<isize> {
314        let mut callback = self.state.callbacks.should_close.take()?;
315        let should_close = callback();
316        self.state.callbacks.should_close.set(Some(callback));
317        if should_close { None } else { Some(0) }
318    }
319
320    fn handle_destroy_msg(&self, handle: HWND) -> Option<isize> {
321        let callback = { self.state.callbacks.close.take() };
322        // Re-enable parent window if this was a modal dialog
323        if let Some(parent_hwnd) = self.parent_hwnd {
324            unsafe {
325                let _ = EnableWindow(parent_hwnd, true);
326                let _ = SetForegroundWindow(parent_hwnd);
327            }
328        }
329
330        if let Some(callback) = callback {
331            callback();
332        }
333        unsafe {
334            PostMessageW(
335                Some(self.platform_window_handle),
336                WM_GPUI_CLOSE_ONE_WINDOW,
337                WPARAM(self.validation_number),
338                LPARAM(handle.0 as isize),
339            )
340            .log_err();
341        }
342        Some(0)
343    }
344
345    fn handle_mouse_move_msg(&self, handle: HWND, lparam: LPARAM, wparam: WPARAM) -> Option<isize> {
346        self.start_tracking_mouse(handle, TME_LEAVE);
347        self.restore_cursor_after_hide();
348
349        let Some(mut func) = self.state.callbacks.input.take() else {
350            return Some(1);
351        };
352        let scale_factor = self.state.scale_factor.get();
353
354        let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
355            flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
356            flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),
357            flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),
358            flags if flags.contains(MK_XBUTTON1) => {
359                Some(MouseButton::Navigate(NavigationDirection::Back))
360            }
361            flags if flags.contains(MK_XBUTTON2) => {
362                Some(MouseButton::Navigate(NavigationDirection::Forward))
363            }
364            _ => None,
365        };
366        let x = lparam.signed_loword() as f32;
367        let y = lparam.signed_hiword() as f32;
368        let input = PlatformInput::MouseMove(MouseMoveEvent {
369            position: logical_point(x, y, scale_factor),
370            pressed_button,
371            modifiers: current_modifiers(),
372        });
373        let handled = !func(input).propagate;
374        self.state.callbacks.input.set(Some(func));
375
376        if handled { Some(0) } else { Some(1) }
377    }
378
379    fn handle_mouse_leave_msg(&self) -> Option<isize> {
380        self.state.hovered.set(false);
381        // The next window's `WM_SETCURSOR` picks its own cursor, so we just clear
382        // the flag for tight `is_cursor_visible()` semantics.
383        self.state.cursor_visible.store(true, Ordering::Relaxed);
384        if let Some(mut callback) = self.state.callbacks.hovered_status_change.take() {
385            callback(false);
386            self.state
387                .callbacks
388                .hovered_status_change
389                .set(Some(callback));
390        }
391
392        Some(0)
393    }
394
395    fn handle_syskeyup_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
396        let input = handle_key_event(wparam, lparam, &self.state, |keystroke, _| {
397            PlatformInput::KeyUp(KeyUpEvent { keystroke })
398        })?;
399        let mut func = self.state.callbacks.input.take()?;
400
401        func(input);
402        self.state.callbacks.input.set(Some(func));
403
404        // Always return 0 to indicate that the message was handled, so we could properly handle `ModifiersChanged` event.
405        Some(0)
406    }
407
408    // It's a known bug that you can't trigger `ctrl-shift-0`. See:
409    // https://superuser.com/questions/1455762/ctrl-shift-number-key-combination-has-stopped-working-for-a-few-numbers
410    fn handle_keydown_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
411        let Some(input) = handle_key_event(
412            wparam,
413            lparam,
414            &self.state,
415            |keystroke, prefer_character_input| {
416                PlatformInput::KeyDown(KeyDownEvent {
417                    keystroke,
418                    is_held: lparam.0 & (0x1 << 30) > 0,
419                    prefer_character_input,
420                })
421            },
422        ) else {
423            return Some(1);
424        };
425
426        let Some(mut func) = self.state.callbacks.input.take() else {
427            return Some(1);
428        };
429
430        let handled = !func(input).propagate;
431
432        self.state.callbacks.input.set(Some(func));
433
434        if handled { Some(0) } else { Some(1) }
435    }
436
437    fn handle_keyup_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
438        let Some(input) = handle_key_event(wparam, lparam, &self.state, |keystroke, _| {
439            PlatformInput::KeyUp(KeyUpEvent { keystroke })
440        }) else {
441            return Some(1);
442        };
443
444        let Some(mut func) = self.state.callbacks.input.take() else {
445            return Some(1);
446        };
447
448        let handled = !func(input).propagate;
449        self.state.callbacks.input.set(Some(func));
450
451        if handled { Some(0) } else { Some(1) }
452    }
453
454    fn handle_char_msg(&self, wparam: WPARAM) -> Option<isize> {
455        let input = self.parse_char_message(wparam)?;
456        self.with_input_handler(|input_handler| {
457            input_handler.replace_text_in_range(None, &input);
458        });
459
460        Some(0)
461    }
462
463    fn handle_mouse_down_msg(
464        &self,
465        handle: HWND,
466        button: MouseButton,
467        lparam: LPARAM,
468    ) -> Option<isize> {
469        unsafe { SetCapture(handle) };
470
471        let Some(mut func) = self.state.callbacks.input.take() else {
472            return Some(1);
473        };
474        let x = lparam.signed_loword();
475        let y = lparam.signed_hiword();
476        let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
477        let click_count = self.state.click_state.update(button, physical_point);
478        let scale_factor = self.state.scale_factor.get();
479
480        let input = PlatformInput::MouseDown(MouseDownEvent {
481            button,
482            position: logical_point(x as f32, y as f32, scale_factor),
483            modifiers: current_modifiers(),
484            click_count,
485            first_mouse: false,
486        });
487        let handled = !func(input).propagate;
488        self.state.callbacks.input.set(Some(func));
489
490        if handled { Some(0) } else { Some(1) }
491    }
492
493    fn handle_mouse_up_msg(
494        &self,
495        _handle: HWND,
496        button: MouseButton,
497        lparam: LPARAM,
498    ) -> Option<isize> {
499        unsafe { ReleaseCapture().log_err() };
500
501        let Some(mut func) = self.state.callbacks.input.take() else {
502            return Some(1);
503        };
504        let x = lparam.signed_loword() as f32;
505        let y = lparam.signed_hiword() as f32;
506        let click_count = self.state.click_state.current_count.get();
507        let scale_factor = self.state.scale_factor.get();
508
509        let input = PlatformInput::MouseUp(MouseUpEvent {
510            button,
511            position: logical_point(x, y, scale_factor),
512            modifiers: current_modifiers(),
513            click_count,
514        });
515        let handled = !func(input).propagate;
516        self.state.callbacks.input.set(Some(func));
517
518        if handled { Some(0) } else { Some(1) }
519    }
520
521    fn handle_xbutton_msg(
522        &self,
523        handle: HWND,
524        wparam: WPARAM,
525        lparam: LPARAM,
526        handler: impl Fn(&Self, HWND, MouseButton, LPARAM) -> Option<isize>,
527    ) -> Option<isize> {
528        let nav_dir = match wparam.hiword() {
529            XBUTTON1 => NavigationDirection::Back,
530            XBUTTON2 => NavigationDirection::Forward,
531            _ => return Some(1),
532        };
533        handler(self, handle, MouseButton::Navigate(nav_dir), lparam)
534    }
535
536    fn handle_mouse_wheel_msg(
537        &self,
538        handle: HWND,
539        wparam: WPARAM,
540        lparam: LPARAM,
541    ) -> Option<isize> {
542        let modifiers = current_modifiers();
543
544        let Some(mut func) = self.state.callbacks.input.take() else {
545            return Some(1);
546        };
547        let scale_factor = self.state.scale_factor.get();
548        let wheel_scroll_amount = match modifiers.shift {
549            true => self
550                .system_settings()
551                .mouse_wheel_settings
552                .wheel_scroll_chars
553                .get(),
554            false => self
555                .system_settings()
556                .mouse_wheel_settings
557                .wheel_scroll_lines
558                .get(),
559        };
560
561        let wheel_distance =
562            (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_amount as f32;
563        let mut cursor_point = POINT {
564            x: lparam.signed_loword().into(),
565            y: lparam.signed_hiword().into(),
566        };
567        unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
568        let input = PlatformInput::ScrollWheel(ScrollWheelEvent {
569            position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
570            delta: ScrollDelta::Lines(match modifiers.shift {
571                true => Point {
572                    x: wheel_distance,
573                    y: 0.0,
574                },
575                false => Point {
576                    y: wheel_distance,
577                    x: 0.0,
578                },
579            }),
580            modifiers,
581            touch_phase: TouchPhase::Moved,
582        });
583        let handled = !func(input).propagate;
584        self.state.callbacks.input.set(Some(func));
585
586        if handled { Some(0) } else { Some(1) }
587    }
588
589    fn handle_mouse_horizontal_wheel_msg(
590        &self,
591        handle: HWND,
592        wparam: WPARAM,
593        lparam: LPARAM,
594    ) -> Option<isize> {
595        let Some(mut func) = self.state.callbacks.input.take() else {
596            return Some(1);
597        };
598        let scale_factor = self.state.scale_factor.get();
599        let wheel_scroll_chars = self
600            .system_settings()
601            .mouse_wheel_settings
602            .wheel_scroll_chars
603            .get();
604
605        let wheel_distance =
606            (-wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_chars as f32;
607        let mut cursor_point = POINT {
608            x: lparam.signed_loword().into(),
609            y: lparam.signed_hiword().into(),
610        };
611        unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
612        let event = PlatformInput::ScrollWheel(ScrollWheelEvent {
613            position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
614            delta: ScrollDelta::Lines(Point {
615                x: wheel_distance,
616                y: 0.0,
617            }),
618            modifiers: current_modifiers(),
619            touch_phase: TouchPhase::Moved,
620        });
621        let handled = !func(event).propagate;
622        self.state.callbacks.input.set(Some(func));
623
624        if handled { Some(0) } else { Some(1) }
625    }
626
627    fn retrieve_caret_position(&self) -> Option<POINT> {
628        self.with_input_handler_and_scale_factor(|input_handler, scale_factor| {
629            let caret_range = input_handler.selected_text_range(false)?;
630            let caret_position = input_handler.bounds_for_range(caret_range.range)?;
631            Some(POINT {
632                // logical to physical
633                x: (caret_position.origin.x.as_f32() * scale_factor) as i32,
634                y: (caret_position.origin.y.as_f32() * scale_factor) as i32
635                    + ((caret_position.size.height.as_f32() * scale_factor) as i32 / 2),
636            })
637        })
638    }
639
640    fn handle_ime_position(&self, handle: HWND) -> Option<isize> {
641        if let Some(caret_position) = self.retrieve_caret_position() {
642            self.update_ime_position(handle, caret_position);
643        }
644        Some(0)
645    }
646
647    pub(crate) fn update_ime_position(&self, handle: HWND, caret_position: POINT) {
648        let Some(ctx) = ImeContext::get(handle) else {
649            return;
650        };
651        unsafe {
652            ImmSetCompositionWindow(
653                *ctx,
654                &COMPOSITIONFORM {
655                    dwStyle: CFS_POINT,
656                    ptCurrentPos: caret_position,
657                    ..Default::default()
658                },
659            )
660            .ok()
661            .log_err();
662
663            ImmSetCandidateWindow(
664                *ctx,
665                &CANDIDATEFORM {
666                    dwStyle: CFS_CANDIDATEPOS,
667                    ptCurrentPos: caret_position,
668                    ..Default::default()
669                },
670            )
671            .ok()
672            .log_err();
673        }
674    }
675
676    fn update_ime_enabled(&self, handle: HWND) {
677        let ime_enabled = self
678            .with_input_handler(|input_handler| input_handler.query_accepts_text_input())
679            .unwrap_or(false);
680        if ime_enabled == self.state.ime_enabled.get() {
681            return;
682        }
683        self.state.ime_enabled.set(ime_enabled);
684        unsafe {
685            if ime_enabled {
686                ImmAssociateContextEx(handle, HIMC::default(), IACE_DEFAULT)
687                    .ok()
688                    .log_err();
689            } else {
690                if let Some(ctx) = ImeContext::get(handle) {
691                    ImmNotifyIME(*ctx, NI_COMPOSITIONSTR, CPS_COMPLETE, 0)
692                        .ok()
693                        .log_err();
694                }
695                ImmAssociateContextEx(handle, HIMC::default(), 0)
696                    .ok()
697                    .log_err();
698            }
699        }
700    }
701
702    fn handle_ime_composition(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
703        let ctx = ImeContext::get(handle)?;
704        self.handle_ime_composition_inner(*ctx, lparam)
705    }
706
707    fn handle_ime_composition_inner(&self, ctx: HIMC, lparam: LPARAM) -> Option<isize> {
708        let lparam = lparam.0 as u32;
709        if lparam == 0 {
710            // Japanese IME may send this message with lparam = 0, which indicates that
711            // there is no composition string.
712            self.with_input_handler(|input_handler| {
713                input_handler.replace_text_in_range(None, "");
714            })?;
715            Some(0)
716        } else {
717            if lparam & GCS_RESULTSTR.0 > 0 {
718                let comp_result = parse_ime_composition_string(ctx, GCS_RESULTSTR)?;
719                self.with_input_handler(|input_handler| {
720                    input_handler
721                        .replace_text_in_range(None, &String::from_utf16_lossy(&comp_result));
722                })?;
723            }
724            if lparam & GCS_COMPSTR.0 > 0 {
725                let comp_string = parse_ime_composition_string(ctx, GCS_COMPSTR)?;
726                let caret_pos =
727                    (!comp_string.is_empty() && lparam & GCS_CURSORPOS.0 > 0).then(|| {
728                        let cursor_pos = retrieve_composition_cursor_position(ctx);
729                        let pos = if should_use_ime_cursor_position(ctx, cursor_pos) {
730                            cursor_pos
731                        } else {
732                            comp_string.len()
733                        };
734                        pos..pos
735                    });
736                self.with_input_handler(|input_handler| {
737                    input_handler.replace_and_mark_text_in_range(
738                        None,
739                        &String::from_utf16_lossy(&comp_string),
740                        caret_pos,
741                    );
742                })?;
743            }
744            if lparam & (GCS_RESULTSTR.0 | GCS_COMPSTR.0) > 0 {
745                return Some(0);
746            }
747
748            // currently, we don't care other stuff
749            None
750        }
751    }
752
753    fn handle_calc_client_size(
754        &self,
755        handle: HWND,
756        wparam: WPARAM,
757        lparam: LPARAM,
758    ) -> Option<isize> {
759        if !self.hide_title_bar || self.state.is_fullscreen() || wparam.0 == 0 {
760            return None;
761        }
762
763        unsafe {
764            let params = lparam.0 as *mut NCCALCSIZE_PARAMS;
765            let saved_top = (*params).rgrc[0].top;
766            let result = DefWindowProcW(handle, WM_NCCALCSIZE, wparam, lparam);
767            (*params).rgrc[0].top = saved_top;
768            if self.state.is_maximized() {
769                let dpi = GetDpiForWindow(handle);
770                (*params).rgrc[0].top += get_frame_thicknessx(dpi);
771            }
772            Some(result.0 as isize)
773        }
774    }
775
776    fn handle_activate_msg(self: &Rc<Self>, wparam: WPARAM) -> Option<isize> {
777        let activated = wparam.loword() > 0;
778
779        let events = self
780            .state
781            .a11y
782            .try_borrow_mut()
783            .ok()
784            .and_then(|mut a11y| a11y.as_mut()?.adapter.update_window_focus_state(activated));
785        if let Some(events) = events {
786            events.raise();
787        }
788
789        let this = self.clone();
790
791        if !activated {
792            this.state.cursor_visible.store(true, Ordering::Relaxed);
793        }
794
795        // When the window is activated (gains focus), reset the modifier tracking state.
796        // This fixes the issue where Alt-Tab away and back leaves stale modifier state
797        // (especially the Alt key) because Windows doesn't always send key-up events to
798        // windows that have lost focus.
799        if activated {
800            this.state.last_reported_modifiers.set(None);
801            this.state.last_reported_capslock.set(None);
802
803            if let Some(mut func) = this.state.callbacks.input.take() {
804                let input = PlatformInput::ModifiersChanged(ModifiersChangedEvent {
805                    modifiers: current_modifiers(),
806                    capslock: current_capslock(),
807                });
808                func(input);
809                this.state.callbacks.input.set(Some(func));
810            }
811        }
812
813        self.executor
814            .spawn(async move {
815                if let Some(mut func) = this.state.callbacks.active_status_change.take() {
816                    func(activated);
817                    this.state.callbacks.active_status_change.set(Some(func));
818                }
819            })
820            .detach();
821
822        None
823    }
824
825    fn handle_wm_getobject(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
826        let result = {
827            let mut a11y = self.state.a11y.borrow_mut();
828            let a11y = a11y.as_mut()?;
829            a11y.adapter.handle_wm_getobject(
830                accesskit_windows::WPARAM(wparam.0),
831                accesskit_windows::LPARAM(lparam.0),
832                &mut a11y.activation_handler,
833            )?
834        };
835        // The borrow above must be dropped before calling `.into()`, because
836        // it calls `UiaReturnRawElementProvider` which may send a nested
837        // `WM_GETOBJECT` back into this window procedure.
838        let lresult: accesskit_windows::LRESULT = result.into();
839        Some(lresult.0)
840    }
841
842    fn handle_create_msg(&self, handle: HWND) -> Option<isize> {
843        if self.hide_title_bar {
844            notify_frame_changed(handle);
845            Some(0)
846        } else {
847            None
848        }
849    }
850
851    fn handle_dpi_changed_msg(
852        &self,
853        handle: HWND,
854        wparam: WPARAM,
855        lparam: LPARAM,
856    ) -> Option<isize> {
857        let new_dpi = wparam.loword() as f32;
858
859        let is_maximized = self.state.is_maximized();
860        let new_scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
861        self.state.scale_factor.set(new_scale_factor);
862        self.state.border_offset.update(handle).log_err();
863
864        self.state
865            .direct_manipulation
866            .set_scale_factor(new_scale_factor);
867
868        if is_maximized {
869            // Get the monitor and its work area at the new DPI
870            let monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONEAREST) };
871            let mut monitor_info: MONITORINFO = unsafe { std::mem::zeroed() };
872            monitor_info.cbSize = std::mem::size_of::<MONITORINFO>() as u32;
873            if unsafe { GetMonitorInfoW(monitor, &mut monitor_info) }.as_bool() {
874                let work_area = monitor_info.rcWork;
875                let width = work_area.right - work_area.left;
876                let height = work_area.bottom - work_area.top;
877
878                // Update the window size to match the new monitor work area
879                // This will trigger WM_SIZE which will handle the size change
880                unsafe {
881                    SetWindowPos(
882                        handle,
883                        None,
884                        work_area.left,
885                        work_area.top,
886                        width,
887                        height,
888                        SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED,
889                    )
890                    .context("unable to set maximized window position after dpi has changed")
891                    .log_err();
892                }
893
894                // SetWindowPos may not send WM_SIZE for maximized windows in some cases,
895                // so we manually update the size to ensure proper rendering
896                let device_size = size(DevicePixels(width), DevicePixels(height));
897                self.handle_size_change(device_size, new_scale_factor, true);
898            }
899        } else {
900            // For non-maximized windows, use the suggested RECT from the system
901            let rect = unsafe { &*(lparam.0 as *const RECT) };
902            let width = rect.right - rect.left;
903            let height = rect.bottom - rect.top;
904            // this will emit `WM_SIZE` and `WM_MOVE` right here
905            // even before this function returns
906            // the new size is handled in `WM_SIZE`
907            unsafe {
908                SetWindowPos(
909                    handle,
910                    None,
911                    rect.left,
912                    rect.top,
913                    width,
914                    height,
915                    SWP_NOZORDER | SWP_NOACTIVATE,
916                )
917                .context("unable to set window position after dpi has changed")
918                .log_err();
919            }
920        }
921
922        Some(0)
923    }
924
925    fn handle_display_change_msg(&self, handle: HWND) -> Option<isize> {
926        let new_monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) };
927        if new_monitor.is_invalid() {
928            log::error!("No monitor detected!");
929            return None;
930        }
931        let new_display = WindowsDisplay::new(WindowsDisplay::display_id_for_monitor(new_monitor))?;
932        self.state.display.set(new_display);
933        Some(0)
934    }
935
936    fn handle_hit_test_msg(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
937        if self.state.is_fullscreen() {
938            return None;
939        }
940
941        let callback = self.state.callbacks.hit_test_window_control.take();
942        let drag_area = if let Some(mut callback) = callback {
943            let area = callback();
944            self.state
945                .callbacks
946                .hit_test_window_control
947                .set(Some(callback));
948            area.and_then(|area| match area {
949                WindowControlArea::Drag if self.is_movable => Some(HTCAPTION as _),
950                WindowControlArea::Drag => None,
951                WindowControlArea::Close => Some(HTCLOSE as _),
952                WindowControlArea::Max if self.is_resizable => Some(HTMAXBUTTON as _),
953                WindowControlArea::Max if self.is_movable => Some(HTCAPTION as _),
954                WindowControlArea::Max => Some(HTNOWHERE as _),
955                WindowControlArea::Min if self.is_minimizable => Some(HTMINBUTTON as _),
956                WindowControlArea::Min if self.is_movable => Some(HTCAPTION as _),
957                WindowControlArea::Min => Some(HTNOWHERE as _),
958            })
959        } else {
960            None
961        };
962
963        if !self.hide_title_bar {
964            // If the OS draws the title bar, we don't need to handle hit test messages.
965            return drag_area;
966        }
967
968        let dpi = unsafe { GetDpiForWindow(handle) };
969        // We do not use the OS title bar, so the default `DefWindowProcW` will only register a 1px edge for resizes
970        // We need to calculate the frame thickness ourselves and do the hit test manually.
971        let frame_y = get_frame_thicknessx(dpi);
972        let frame_x = get_frame_thicknessy(dpi);
973        let mut cursor_point = POINT {
974            x: lparam.signed_loword().into(),
975            y: lparam.signed_hiword().into(),
976        };
977
978        unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
979        if self.is_resizable
980            && !self.state.is_maximized()
981            && 0 <= cursor_point.y
982            && cursor_point.y <= frame_y
983        {
984            // x-axis actually goes from -frame_x to 0
985            return Some(if cursor_point.x <= 0 {
986                HTTOPLEFT
987            } else {
988                let mut rect = Default::default();
989                unsafe { GetWindowRect(handle, &mut rect) }.log_err();
990                // right and bottom bounds of RECT are exclusive, thus `-1`
991                let right = rect.right - rect.left - 1;
992                // the bounds include the padding frames, so accommodate for both of them
993                if right - 2 * frame_x <= cursor_point.x {
994                    HTTOPRIGHT
995                } else {
996                    HTTOP
997                }
998            } as _);
999        }
1000
1001        drag_area
1002    }
1003
1004    fn handle_nc_mouse_move_msg(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
1005        self.start_tracking_mouse(handle, TME_LEAVE | TME_NONCLIENT);
1006        self.restore_cursor_after_hide();
1007
1008        let mut func = self.state.callbacks.input.take()?;
1009        let scale_factor = self.state.scale_factor.get();
1010
1011        let mut cursor_point = POINT {
1012            x: lparam.signed_loword().into(),
1013            y: lparam.signed_hiword().into(),
1014        };
1015        unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
1016        let input = PlatformInput::MouseMove(MouseMoveEvent {
1017            position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1018            pressed_button: None,
1019            modifiers: current_modifiers(),
1020        });
1021        let handled = !func(input).propagate;
1022        self.state.callbacks.input.set(Some(func));
1023
1024        if handled { Some(0) } else { None }
1025    }
1026
1027    fn handle_nc_mouse_down_msg(
1028        &self,
1029        handle: HWND,
1030        button: MouseButton,
1031        wparam: WPARAM,
1032        lparam: LPARAM,
1033    ) -> Option<isize> {
1034        if let Some(mut func) = self.state.callbacks.input.take() {
1035            let scale_factor = self.state.scale_factor.get();
1036            let mut cursor_point = POINT {
1037                x: lparam.signed_loword().into(),
1038                y: lparam.signed_hiword().into(),
1039            };
1040            unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
1041            let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
1042            let click_count = self.state.click_state.update(button, physical_point);
1043
1044            let input = PlatformInput::MouseDown(MouseDownEvent {
1045                button,
1046                position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1047                modifiers: current_modifiers(),
1048                click_count,
1049                first_mouse: false,
1050            });
1051            let handled = !func(input).propagate;
1052            self.state.callbacks.input.set(Some(func));
1053
1054            if handled {
1055                return Some(0);
1056            }
1057        } else {
1058        };
1059
1060        // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
1061        if button == MouseButton::Left {
1062            match wparam.0 as u32 {
1063                HTMINBUTTON => self.state.nc_button_pressed.set(Some(HTMINBUTTON)),
1064                HTMAXBUTTON => self.state.nc_button_pressed.set(Some(HTMAXBUTTON)),
1065                HTCLOSE => self.state.nc_button_pressed.set(Some(HTCLOSE)),
1066                _ => return None,
1067            };
1068            Some(0)
1069        } else {
1070            None
1071        }
1072    }
1073
1074    fn handle_nc_mouse_up_msg(
1075        &self,
1076        handle: HWND,
1077        button: MouseButton,
1078        wparam: WPARAM,
1079        lparam: LPARAM,
1080    ) -> Option<isize> {
1081        if let Some(mut func) = self.state.callbacks.input.take() {
1082            let scale_factor = self.state.scale_factor.get();
1083
1084            let mut cursor_point = POINT {
1085                x: lparam.signed_loword().into(),
1086                y: lparam.signed_hiword().into(),
1087            };
1088            unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
1089            let input = PlatformInput::MouseUp(MouseUpEvent {
1090                button,
1091                position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1092                modifiers: current_modifiers(),
1093                click_count: 1,
1094            });
1095            let handled = !func(input).propagate;
1096            self.state.callbacks.input.set(Some(func));
1097
1098            if handled {
1099                return Some(0);
1100            }
1101        } else {
1102        }
1103
1104        let last_pressed = self.state.nc_button_pressed.take();
1105        if button == MouseButton::Left
1106            && let Some(last_pressed) = last_pressed
1107        {
1108            let handled = match (wparam.0 as u32, last_pressed) {
1109                (HTMINBUTTON, HTMINBUTTON) if self.is_minimizable => {
1110                    unsafe { ShowWindowAsync(handle, SW_MINIMIZE).ok().log_err() };
1111                    true
1112                }
1113                (HTMINBUTTON, HTMINBUTTON) => true,
1114                (HTMAXBUTTON, HTMAXBUTTON) if self.is_resizable => {
1115                    if self.state.is_maximized() {
1116                        unsafe { ShowWindowAsync(handle, SW_NORMAL).ok().log_err() };
1117                    } else {
1118                        unsafe { ShowWindowAsync(handle, SW_MAXIMIZE).ok().log_err() };
1119                    }
1120                    true
1121                }
1122                (HTMAXBUTTON, HTMAXBUTTON) => true,
1123                (HTCLOSE, HTCLOSE) => {
1124                    unsafe {
1125                        PostMessageW(Some(handle), WM_CLOSE, WPARAM::default(), LPARAM::default())
1126                            .log_err()
1127                    };
1128                    true
1129                }
1130                _ => false,
1131            };
1132            if handled {
1133                return Some(0);
1134            }
1135        }
1136
1137        None
1138    }
1139
1140    fn handle_cursor_changed(&self, lparam: LPARAM) -> Option<isize> {
1141        let had_cursor = self.state.current_cursor.get().is_some();
1142
1143        self.state.current_cursor.set(if lparam.0 == 0 {
1144            None
1145        } else {
1146            Some(HCURSOR(lparam.0 as _))
1147        });
1148
1149        if had_cursor != self.state.current_cursor.get().is_some() {
1150            unsafe { SetCursor(self.state.current_cursor.get()) };
1151        }
1152
1153        Some(0)
1154    }
1155
1156    fn handle_set_cursor(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
1157        if unsafe { !IsWindowEnabled(handle).as_bool() }
1158            || matches!(
1159                lparam.loword() as u32,
1160                HTLEFT
1161                    | HTRIGHT
1162                    | HTTOP
1163                    | HTTOPLEFT
1164                    | HTTOPRIGHT
1165                    | HTBOTTOM
1166                    | HTBOTTOMLEFT
1167                    | HTBOTTOMRIGHT
1168            )
1169        {
1170            return None;
1171        }
1172        let cursor = if self.state.cursor_visible.load(Ordering::Relaxed) {
1173            self.state.current_cursor.get()
1174        } else {
1175            None
1176        };
1177        unsafe {
1178            SetCursor(cursor);
1179        };
1180        Some(0)
1181    }
1182
1183    fn handle_system_settings_changed(
1184        &self,
1185        handle: HWND,
1186        wparam: WPARAM,
1187        lparam: LPARAM,
1188    ) -> Option<isize> {
1189        if wparam.0 != 0 {
1190            self.state.click_state.system_update(wparam.0);
1191            self.state.border_offset.update(handle).log_err();
1192            // system settings may emit a window message which wants to take the refcell self.state, so drop it
1193
1194            self.system_settings().update(wparam.0);
1195        } else {
1196            self.handle_system_theme_changed(handle, lparam)?;
1197        };
1198
1199        Some(0)
1200    }
1201
1202    fn handle_system_theme_changed(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
1203        // lParam is a pointer to a string that indicates the area containing the system parameter
1204        // that was changed.
1205        let parameter = PCWSTR::from_raw(lparam.0 as _);
1206        if unsafe { !parameter.is_null() && !parameter.is_empty() }
1207            && let Some(parameter_string) = unsafe { parameter.to_string() }.log_err()
1208        {
1209            log::info!("System settings changed: {}", parameter_string);
1210            if parameter_string.as_str() == "ImmersiveColorSet" {
1211                let new_appearance = system_appearance()
1212                    .context("unable to get system appearance when handling ImmersiveColorSet")
1213                    .log_err()?;
1214
1215                if new_appearance != self.state.appearance.get() {
1216                    self.state.appearance.set(new_appearance);
1217                    let mut callback = self.state.callbacks.appearance_changed.take()?;
1218
1219                    callback();
1220                    self.state.callbacks.appearance_changed.set(Some(callback));
1221                    configure_dwm_dark_mode(handle, new_appearance);
1222                }
1223            }
1224        }
1225        Some(0)
1226    }
1227
1228    fn handle_input_language_changed(&self) -> Option<isize> {
1229        unsafe {
1230            PostMessageW(
1231                Some(self.platform_window_handle),
1232                WM_GPUI_KEYBOARD_LAYOUT_CHANGED,
1233                WPARAM(self.validation_number),
1234                LPARAM(0),
1235            )
1236            .log_err();
1237        }
1238        Some(0)
1239    }
1240
1241    fn handle_window_visibility_changed(&self, handle: HWND, wparam: WPARAM) -> Option<isize> {
1242        if wparam.0 == 1 {
1243            self.draw_window(handle, false);
1244        }
1245        None
1246    }
1247
1248    fn handle_device_lost(&self, lparam: LPARAM) -> Option<isize> {
1249        let devices = lparam.0 as *const DirectXDevices;
1250        let devices = unsafe { &*devices };
1251        if let Err(err) = self
1252            .state
1253            .renderer
1254            .borrow_mut()
1255            .handle_device_lost(&devices)
1256        {
1257            panic!("Device lost: {err}");
1258        }
1259        // Make sure the first `draw_window` after recovery (whether it comes
1260        // from the forced WM_GPUI_FORCE_UPDATE_WINDOW or a stray WM_PAINT in
1261        // between) is treated as a forced render so it both clears
1262        // `skip_draws` and bypasses the view cache.
1263        self.state.force_render_pending.set(true);
1264        Some(0)
1265    }
1266
1267    fn handle_dm_pointer_hit_test(&self, wparam: WPARAM) -> Option<isize> {
1268        self.state.direct_manipulation.on_pointer_hit_test(wparam);
1269        None
1270    }
1271
1272    #[inline]
1273    fn draw_window(&self, handle: HWND, force_render: bool) -> Option<isize> {
1274        let Some(_guard) = self.state.draw_coordinator.try_begin_draw() else {
1275            log::debug!("deferring re-entrant draw of window {handle:?}");
1276            if force_render {
1277                self.state.force_render_pending.set(true);
1278            }
1279            // Validate the region so a nested message pump doesn't keep
1280            // re-dispatching WM_PAINT for the still-invalid region in a busy
1281            // loop until the in-progress draw unwinds. The vsync thread
1282            // re-invalidates every window on each vsync (see
1283            // `begin_vsync_thread`), so the deferred frame still gets drawn,
1284            // at most one vsync late.
1285            unsafe { ValidateRect(Some(handle), None).ok().log_err() };
1286            return Some(0);
1287        };
1288        let mut request_frame = self.state.callbacks.request_frame.take()?;
1289
1290        self.state.direct_manipulation.update();
1291
1292        let events = self.state.direct_manipulation.drain_events();
1293        if !events.is_empty() {
1294            if let Some(mut func) = self.state.callbacks.input.take() {
1295                for event in events {
1296                    func(event);
1297                }
1298                self.state.callbacks.input.set(Some(func));
1299            }
1300        }
1301
1302        let force_render = force_render || self.state.force_render_pending.take();
1303        if force_render {
1304            // Re-enable drawing after a device loss recovery. The forced render
1305            // will rebuild the scene with fresh atlas textures.
1306            self.state.renderer.borrow_mut().mark_drawable();
1307        }
1308        request_frame(RequestFrameOptions {
1309            require_presentation: false,
1310            force_render,
1311        });
1312
1313        self.state.callbacks.request_frame.set(Some(request_frame));
1314        self.update_ime_enabled(handle);
1315        unsafe { ValidateRect(Some(handle), None).ok().log_err() };
1316
1317        Some(0)
1318    }
1319
1320    #[inline]
1321    fn parse_char_message(&self, wparam: WPARAM) -> Option<String> {
1322        let code_point = wparam.loword();
1323
1324        // https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-3/#G2630
1325        match code_point {
1326            0xD800..=0xDBFF => {
1327                // High surrogate, wait for low surrogate
1328                self.state.pending_surrogate.set(Some(code_point));
1329                None
1330            }
1331            0xDC00..=0xDFFF => {
1332                if let Some(high_surrogate) = self.state.pending_surrogate.take() {
1333                    // Low surrogate, combine with pending high surrogate
1334                    String::from_utf16(&[high_surrogate, code_point]).ok()
1335                } else {
1336                    // Invalid low surrogate without a preceding high surrogate
1337                    log::warn!(
1338                        "Received low surrogate without a preceding high surrogate: {code_point:x}"
1339                    );
1340                    None
1341                }
1342            }
1343            _ => {
1344                self.state.pending_surrogate.set(None);
1345                char::from_u32(code_point as u32)
1346                    .filter(|c| !c.is_control())
1347                    .map(|c| c.to_string())
1348            }
1349        }
1350    }
1351
1352    /// Clear the hidden flag and restore the cursor immediately
1353    fn restore_cursor_after_hide(&self) {
1354        if !self.state.cursor_visible.swap(true, Ordering::Relaxed) {
1355            unsafe {
1356                SetCursor(self.state.current_cursor.get());
1357            }
1358        }
1359    }
1360
1361    fn start_tracking_mouse(&self, handle: HWND, flags: TRACKMOUSEEVENT_FLAGS) {
1362        if !self.state.hovered.get() {
1363            self.state.hovered.set(true);
1364            unsafe {
1365                TrackMouseEvent(&mut TRACKMOUSEEVENT {
1366                    cbSize: std::mem::size_of::<TRACKMOUSEEVENT>() as u32,
1367                    dwFlags: flags,
1368                    hwndTrack: handle,
1369                    dwHoverTime: HOVER_DEFAULT,
1370                })
1371                .log_err()
1372            };
1373            if let Some(mut callback) = self.state.callbacks.hovered_status_change.take() {
1374                callback(true);
1375                self.state
1376                    .callbacks
1377                    .hovered_status_change
1378                    .set(Some(callback));
1379            }
1380        }
1381    }
1382
1383    fn with_input_handler<F, R>(&self, f: F) -> Option<R>
1384    where
1385        F: FnOnce(&mut PlatformInputHandler) -> R,
1386    {
1387        let mut input_handler = self.state.input_handler.take()?;
1388        let result = f(&mut input_handler);
1389        self.state.input_handler.set(Some(input_handler));
1390        Some(result)
1391    }
1392
1393    fn with_input_handler_and_scale_factor<F, R>(&self, f: F) -> Option<R>
1394    where
1395        F: FnOnce(&mut PlatformInputHandler, f32) -> Option<R>,
1396    {
1397        let mut input_handler = self.state.input_handler.take()?;
1398        let scale_factor = self.state.scale_factor.get();
1399
1400        let result = f(&mut input_handler, scale_factor);
1401        self.state.input_handler.set(Some(input_handler));
1402        result
1403    }
1404}
1405
1406struct ImeContext {
1407    hwnd: HWND,
1408    himc: HIMC,
1409}
1410
1411impl ImeContext {
1412    fn get(hwnd: HWND) -> Option<Self> {
1413        let himc = unsafe { ImmGetContext(hwnd) };
1414        if himc.is_invalid() {
1415            return None;
1416        }
1417        Some(Self { hwnd, himc })
1418    }
1419}
1420
1421impl std::ops::Deref for ImeContext {
1422    type Target = HIMC;
1423    fn deref(&self) -> &HIMC {
1424        &self.himc
1425    }
1426}
1427
1428impl Drop for ImeContext {
1429    fn drop(&mut self) {
1430        unsafe {
1431            ImmReleaseContext(self.hwnd, self.himc).ok().log_err();
1432        }
1433    }
1434}
1435
1436fn handle_key_event<F>(
1437    wparam: WPARAM,
1438    lparam: LPARAM,
1439    state: &WindowsWindowState,
1440    f: F,
1441) -> Option<PlatformInput>
1442where
1443    F: FnOnce(Keystroke, bool) -> PlatformInput,
1444{
1445    let virtual_key = VIRTUAL_KEY(wparam.loword());
1446    let modifiers = current_modifiers();
1447
1448    match virtual_key {
1449        VK_SHIFT | VK_CONTROL | VK_MENU | VK_LMENU | VK_RMENU | VK_LWIN | VK_RWIN => {
1450            if state
1451                .last_reported_modifiers
1452                .get()
1453                .is_some_and(|prev_modifiers| prev_modifiers == modifiers)
1454            {
1455                return None;
1456            }
1457            state.last_reported_modifiers.set(Some(modifiers));
1458            Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent {
1459                modifiers,
1460                capslock: current_capslock(),
1461            }))
1462        }
1463        VK_PACKET => None,
1464        VK_CAPITAL => {
1465            let capslock = current_capslock();
1466            if state
1467                .last_reported_capslock
1468                .get()
1469                .is_some_and(|prev_capslock| prev_capslock == capslock)
1470            {
1471                return None;
1472            }
1473            state.last_reported_capslock.set(Some(capslock));
1474            Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent {
1475                modifiers,
1476                capslock,
1477            }))
1478        }
1479        vkey => {
1480            let keystroke = parse_normal_key(vkey, lparam, modifiers)?;
1481            Some(f(keystroke.0, keystroke.1))
1482        }
1483    }
1484}
1485
1486fn parse_immutable(vkey: VIRTUAL_KEY) -> Option<String> {
1487    Some(
1488        match vkey {
1489            VK_SPACE => "space",
1490            VK_BACK => "backspace",
1491            VK_RETURN => "enter",
1492            VK_TAB => "tab",
1493            VK_UP => "up",
1494            VK_DOWN => "down",
1495            VK_RIGHT => "right",
1496            VK_LEFT => "left",
1497            VK_HOME => "home",
1498            VK_END => "end",
1499            VK_PRIOR => "pageup",
1500            VK_NEXT => "pagedown",
1501            VK_BROWSER_BACK => "back",
1502            VK_BROWSER_FORWARD => "forward",
1503            VK_ESCAPE => "escape",
1504            VK_INSERT => "insert",
1505            VK_DELETE => "delete",
1506            VK_APPS => "menu",
1507            VK_F1 => "f1",
1508            VK_F2 => "f2",
1509            VK_F3 => "f3",
1510            VK_F4 => "f4",
1511            VK_F5 => "f5",
1512            VK_F6 => "f6",
1513            VK_F7 => "f7",
1514            VK_F8 => "f8",
1515            VK_F9 => "f9",
1516            VK_F10 => "f10",
1517            VK_F11 => "f11",
1518            VK_F12 => "f12",
1519            VK_F13 => "f13",
1520            VK_F14 => "f14",
1521            VK_F15 => "f15",
1522            VK_F16 => "f16",
1523            VK_F17 => "f17",
1524            VK_F18 => "f18",
1525            VK_F19 => "f19",
1526            VK_F20 => "f20",
1527            VK_F21 => "f21",
1528            VK_F22 => "f22",
1529            VK_F23 => "f23",
1530            VK_F24 => "f24",
1531            _ => return None,
1532        }
1533        .to_string(),
1534    )
1535}
1536
1537fn parse_normal_key(
1538    vkey: VIRTUAL_KEY,
1539    lparam: LPARAM,
1540    mut modifiers: Modifiers,
1541) -> Option<(Keystroke, bool)> {
1542    let (key_char, prefer_character_input) = process_key(vkey, lparam.hiword());
1543
1544    let key = parse_immutable(vkey).or_else(|| {
1545        let scan_code = lparam.hiword() & 0xFF;
1546        get_keystroke_key(vkey, scan_code as u32, &mut modifiers)
1547    })?;
1548
1549    Some((
1550        Keystroke {
1551            modifiers,
1552            key,
1553            key_char,
1554        },
1555        prefer_character_input,
1556    ))
1557}
1558
1559fn process_key(vkey: VIRTUAL_KEY, scan_code: u16) -> (Option<String>, bool) {
1560    let mut keyboard_state = [0u8; 256];
1561    unsafe {
1562        if GetKeyboardState(&mut keyboard_state).is_err() {
1563            return (None, false);
1564        }
1565    }
1566
1567    let mut buffer_c = [0u16; 8];
1568    let result_c = unsafe {
1569        ToUnicode(
1570            vkey.0 as u32,
1571            scan_code as u32,
1572            Some(&keyboard_state),
1573            &mut buffer_c,
1574            0x4,
1575        )
1576    };
1577
1578    if result_c == 0 {
1579        return (None, false);
1580    }
1581
1582    let c = &buffer_c[..result_c.unsigned_abs() as usize];
1583    let key_char = String::from_utf16(c)
1584        .ok()
1585        .filter(|s| !s.is_empty() && !s.chars().next().unwrap().is_control());
1586
1587    if result_c < 0 {
1588        return (key_char, true);
1589    }
1590
1591    if key_char.is_none() {
1592        return (None, false);
1593    }
1594
1595    // Workaround for some bug that makes the compiler think keyboard_state is still zeroed out
1596    let keyboard_state = std::hint::black_box(keyboard_state);
1597    let ctrl_down = (keyboard_state[VK_CONTROL.0 as usize] & 0x80) != 0;
1598    let alt_down = (keyboard_state[VK_MENU.0 as usize] & 0x80) != 0;
1599    let win_down = (keyboard_state[VK_LWIN.0 as usize] & 0x80) != 0
1600        || (keyboard_state[VK_RWIN.0 as usize] & 0x80) != 0;
1601
1602    let has_modifiers = ctrl_down || alt_down || win_down;
1603    if !has_modifiers {
1604        return (key_char, false);
1605    }
1606
1607    let mut state_no_modifiers = keyboard_state;
1608    state_no_modifiers[VK_CONTROL.0 as usize] = 0;
1609    state_no_modifiers[VK_LCONTROL.0 as usize] = 0;
1610    state_no_modifiers[VK_RCONTROL.0 as usize] = 0;
1611    state_no_modifiers[VK_MENU.0 as usize] = 0;
1612    state_no_modifiers[VK_LMENU.0 as usize] = 0;
1613    state_no_modifiers[VK_RMENU.0 as usize] = 0;
1614    state_no_modifiers[VK_LWIN.0 as usize] = 0;
1615    state_no_modifiers[VK_RWIN.0 as usize] = 0;
1616
1617    let mut buffer_c_no_modifiers = [0u16; 8];
1618    let result_c_no_modifiers = unsafe {
1619        ToUnicode(
1620            vkey.0 as u32,
1621            scan_code as u32,
1622            Some(&state_no_modifiers),
1623            &mut buffer_c_no_modifiers,
1624            0x4,
1625        )
1626    };
1627
1628    let c_no_modifiers = &buffer_c_no_modifiers[..result_c_no_modifiers.unsigned_abs() as usize];
1629    (
1630        key_char,
1631        result_c != result_c_no_modifiers || c != c_no_modifiers,
1632    )
1633}
1634
1635fn parse_ime_composition_string(ctx: HIMC, comp_type: IME_COMPOSITION_STRING) -> Option<Vec<u16>> {
1636    unsafe {
1637        let string_len = ImmGetCompositionStringW(ctx, comp_type, None, 0);
1638        if string_len >= 0 {
1639            let mut buffer = vec![0u8; string_len as usize + 2];
1640            ImmGetCompositionStringW(
1641                ctx,
1642                comp_type,
1643                Some(buffer.as_mut_ptr() as _),
1644                string_len as _,
1645            );
1646            let wstring = std::slice::from_raw_parts::<u16>(
1647                buffer.as_mut_ptr().cast::<u16>(),
1648                string_len as usize / 2,
1649            );
1650            Some(wstring.to_vec())
1651        } else {
1652            None
1653        }
1654    }
1655}
1656
1657#[inline]
1658fn retrieve_composition_cursor_position(ctx: HIMC) -> usize {
1659    unsafe { ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0) as usize }
1660}
1661
1662fn should_use_ime_cursor_position(ctx: HIMC, cursor_pos: usize) -> bool {
1663    let attrs_size = unsafe { ImmGetCompositionStringW(ctx, GCS_COMPATTR, None, 0) } as usize;
1664    if attrs_size == 0 {
1665        return false;
1666    }
1667
1668    let mut attrs = vec![0u8; attrs_size];
1669    let result = unsafe {
1670        ImmGetCompositionStringW(
1671            ctx,
1672            GCS_COMPATTR,
1673            Some(attrs.as_mut_ptr() as *mut _),
1674            attrs_size as u32,
1675        )
1676    };
1677    if result <= 0 {
1678        return false;
1679    }
1680
1681    // Keep the cursor adjacent to the inserted text by only using the suggested position
1682    // if it's adjacent to unconverted text.
1683    let at_cursor_is_input = cursor_pos < attrs.len() && attrs[cursor_pos] == (ATTR_INPUT as u8);
1684    let before_cursor_is_input = cursor_pos > 0
1685        && (cursor_pos - 1) < attrs.len()
1686        && attrs[cursor_pos - 1] == (ATTR_INPUT as u8);
1687
1688    at_cursor_is_input || before_cursor_is_input
1689}
1690
1691#[inline]
1692fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool {
1693    unsafe { GetKeyState(vkey.0 as i32) < 0 }
1694}
1695
1696#[inline]
1697pub(crate) fn current_modifiers() -> Modifiers {
1698    Modifiers {
1699        control: is_virtual_key_pressed(VK_CONTROL),
1700        alt: is_virtual_key_pressed(VK_MENU),
1701        shift: is_virtual_key_pressed(VK_SHIFT),
1702        platform: is_virtual_key_pressed(VK_LWIN) || is_virtual_key_pressed(VK_RWIN),
1703        function: false,
1704    }
1705}
1706
1707#[inline]
1708pub(crate) fn current_capslock() -> Capslock {
1709    let on = unsafe { GetKeyState(VK_CAPITAL.0 as i32) & 1 } > 0;
1710    Capslock { on }
1711}
1712
1713// there is some additional non-visible space when talking about window
1714// borders on Windows:
1715// - SM_CXSIZEFRAME: The resize handle.
1716// - SM_CXPADDEDBORDER: Additional border space that isn't part of the resize handle.
1717fn get_frame_thicknessx(dpi: u32) -> i32 {
1718    let resize_frame_thickness = unsafe { GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) };
1719    let padding_thickness = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
1720    resize_frame_thickness + padding_thickness
1721}
1722
1723fn get_frame_thicknessy(dpi: u32) -> i32 {
1724    let resize_frame_thickness = unsafe { GetSystemMetricsForDpi(SM_CYSIZEFRAME, dpi) };
1725    let padding_thickness = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
1726    resize_frame_thickness + padding_thickness
1727}
1728
1729fn notify_frame_changed(handle: HWND) {
1730    unsafe {
1731        SetWindowPos(
1732            handle,
1733            None,
1734            0,
1735            0,
1736            0,
1737            0,
1738            SWP_FRAMECHANGED
1739                | SWP_NOACTIVATE
1740                | SWP_NOCOPYBITS
1741                | SWP_NOMOVE
1742                | SWP_NOOWNERZORDER
1743                | SWP_NOREPOSITION
1744                | SWP_NOSENDCHANGING
1745                | SWP_NOSIZE
1746                | SWP_NOZORDER,
1747        )
1748        .log_err();
1749    }
1750}
1751
Served at tenant.openagents/omega Member data and write actions are omitted.