1756 lines · 62.5 KB · Rust
RawDownload
use std::{cell::Cell, rc::Rc, sync::atomic::Ordering}; use anyhow::Context as _;use gpui_util::ResultExt;use windows::{    Win32::{        Foundation::*,        Graphics::Gdi::*,        System::SystemServices::*,        UI::{            Controls::*,            HiDpi::*,            Input::{Ime::*, KeyboardAndMouse::*},            WindowsAndMessaging::*,        },    },    core::PCWSTR,}; use crate::*;use gpui::*; pub(crate) const WM_GPUI_CURSOR_STYLE_CHANGED: u32 = WM_USER + 1;pub(crate) const WM_GPUI_CLOSE_ONE_WINDOW: u32 = WM_USER + 2;pub(crate) const WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD: u32 = WM_USER + 3;pub(crate) const WM_GPUI_DOCK_MENU_ACTION: u32 = WM_USER + 4;pub(crate) const WM_GPUI_FORCE_UPDATE_WINDOW: u32 = WM_USER + 5;pub(crate) const WM_GPUI_KEYBOARD_LAYOUT_CHANGED: u32 = WM_USER + 6;pub(crate) const WM_GPUI_GPU_DEVICE_LOST: u32 = WM_USER + 7;pub(crate) const WM_GPUI_KEYDOWN: u32 = WM_USER + 8; const SIZE_MOVE_LOOP_TIMER_ID: usize = 1; /// Coordinates window draws on the UI thread. Owned by the platform and/// shared with every window (like `WindowsPlatformState::cursor_visible`),/// because the coordination is inherently cross-window: while window A is/// drawing, a re-entrant paint request for window B must be deferred.pub(crate) struct DrawCoordinator {    /// Whether some window is currently inside `draw_window`. Win32 can    /// re-enter the window procedure while a draw is in progress (e.g.    /// cross-thread `SendMessage` dispatch during message pumping, or modal    /// message loops entered by COM calls), and drawing re-entrantly would    /// nest GPUI draws. Nested draws are wasted work whose output is    /// immediately redrawn, so we defer them instead.    drawing: Cell<bool>,} impl DrawCoordinator {    pub(crate) fn new() -> Self {        Self {            drawing: Cell::new(false),        }    }     fn try_begin_draw(&self) -> Option<DrawWindowGuard<'_>> {        // This only covers the `draw_window` span (which extends past the GPUI        // draw, through presentation and IME updates). Requests that arrive        // re-entrantly during GPUI-initiated draws (e.g. key dispatch or        // opening a window draws synchronously) are deferred by GPUI's        // `on_request_frame` callback itself, which no-ops in that case.        if self.drawing.get() {            None        } else {            self.drawing.set(true);            Some(DrawWindowGuard { coordinator: self })        }    }} struct DrawWindowGuard<'a> {    coordinator: &'a DrawCoordinator,} impl Drop for DrawWindowGuard<'_> {    fn drop(&mut self) {        self.coordinator.drawing.set(false);    }} impl WindowsWindowInner {    pub(crate) fn handle_msg(        self: &Rc<Self>,        handle: HWND,        msg: u32,        wparam: WPARAM,        lparam: LPARAM,    ) -> LRESULT {        let handled = match msg {            // `DefWindowProc` answers `MA_NOACTIVATE` for a left click on `HTCAPTION`.            // The activation is only triggered when `DefWindowProc` handles the following `WM_NCLBUTTONDOWN`.            // The GPUI event is dispatched in between, so a click handler runs while `active_window` is still            // whichever window was active before the click. If that handler consumes the            // press, `DefWindowProc` never sees it, so the window is never activated at all.            // So, let's eagerly activate the window.            WM_MOUSEACTIVATE => Some(MA_ACTIVATE as isize),            WM_ACTIVATE => self.handle_activate_msg(wparam),            WM_CREATE => self.handle_create_msg(handle),            WM_MOVE => self.handle_move_msg(handle, lparam),            WM_SIZE => self.handle_size_msg(wparam, lparam),            WM_GETMINMAXINFO => self.handle_get_min_max_info_msg(lparam),            WM_ENTERSIZEMOVE | WM_ENTERMENULOOP => self.handle_size_move_loop(handle),            WM_EXITSIZEMOVE | WM_EXITMENULOOP => self.handle_size_move_loop_exit(handle),            WM_TIMER => self.handle_timer_msg(handle, wparam),            WM_NCCALCSIZE => self.handle_calc_client_size(handle, wparam, lparam),            WM_DPICHANGED => self.handle_dpi_changed_msg(handle, wparam, lparam),            WM_DISPLAYCHANGE => self.handle_display_change_msg(handle),            WM_NCHITTEST => self.handle_hit_test_msg(handle, lparam),            WM_PAINT => self.handle_paint_msg(handle),            WM_CLOSE => self.handle_close_msg(),            WM_DESTROY => self.handle_destroy_msg(handle),            WM_MOUSEMOVE => self.handle_mouse_move_msg(handle, lparam, wparam),            WM_MOUSELEAVE | WM_NCMOUSELEAVE => self.handle_mouse_leave_msg(),            WM_NCMOUSEMOVE => self.handle_nc_mouse_move_msg(handle, lparam),            // Treat double click as a second single click, since we track the double clicks ourselves.            // If you don't interact with any elements, this will fall through to the windows default            // behavior of toggling whether the window is maximized.            WM_NCLBUTTONDBLCLK | WM_NCLBUTTONDOWN => {                self.handle_nc_mouse_down_msg(handle, MouseButton::Left, wparam, lparam)            }            WM_NCRBUTTONDOWN => {                self.handle_nc_mouse_down_msg(handle, MouseButton::Right, wparam, lparam)            }            WM_NCMBUTTONDOWN => {                self.handle_nc_mouse_down_msg(handle, MouseButton::Middle, wparam, lparam)            }            WM_NCLBUTTONUP => {                self.handle_nc_mouse_up_msg(handle, MouseButton::Left, wparam, lparam)            }            WM_NCRBUTTONUP => {                self.handle_nc_mouse_up_msg(handle, MouseButton::Right, wparam, lparam)            }            WM_NCMBUTTONUP => {                self.handle_nc_mouse_up_msg(handle, MouseButton::Middle, wparam, lparam)            }            WM_LBUTTONDOWN => self.handle_mouse_down_msg(handle, MouseButton::Left, lparam),            WM_RBUTTONDOWN => self.handle_mouse_down_msg(handle, MouseButton::Right, lparam),            WM_MBUTTONDOWN => self.handle_mouse_down_msg(handle, MouseButton::Middle, lparam),            WM_XBUTTONDOWN => {                self.handle_xbutton_msg(handle, wparam, lparam, Self::handle_mouse_down_msg)            }            WM_LBUTTONUP => self.handle_mouse_up_msg(handle, MouseButton::Left, lparam),            WM_RBUTTONUP => self.handle_mouse_up_msg(handle, MouseButton::Right, lparam),            WM_MBUTTONUP => self.handle_mouse_up_msg(handle, MouseButton::Middle, lparam),            WM_XBUTTONUP => {                self.handle_xbutton_msg(handle, wparam, lparam, Self::handle_mouse_up_msg)            }            WM_MOUSEWHEEL => self.handle_mouse_wheel_msg(handle, wparam, lparam),            WM_MOUSEHWHEEL => self.handle_mouse_horizontal_wheel_msg(handle, wparam, lparam),            WM_SYSKEYUP => self.handle_syskeyup_msg(wparam, lparam),            WM_KEYUP => self.handle_keyup_msg(wparam, lparam),            WM_GPUI_KEYDOWN => self.handle_keydown_msg(wparam, lparam),            WM_CHAR => self.handle_char_msg(wparam),            WM_IME_STARTCOMPOSITION => self.handle_ime_position(handle),            WM_IME_COMPOSITION => self.handle_ime_composition(handle, lparam),            WM_SETCURSOR => self.handle_set_cursor(handle, lparam),            WM_SETTINGCHANGE => self.handle_system_settings_changed(handle, wparam, lparam),            WM_INPUTLANGCHANGE => self.handle_input_language_changed(),            WM_SHOWWINDOW => self.handle_window_visibility_changed(handle, wparam),            WM_GPUI_CURSOR_STYLE_CHANGED => self.handle_cursor_changed(lparam),            WM_GPUI_FORCE_UPDATE_WINDOW => self.draw_window(handle, true),            WM_GPUI_GPU_DEVICE_LOST => self.handle_device_lost(lparam),            DM_POINTERHITTEST => self.handle_dm_pointer_hit_test(wparam),            WM_GETOBJECT => self.handle_wm_getobject(wparam, lparam),            _ => None,        };        if let Some(n) = handled {            LRESULT(n)        } else {            unsafe { DefWindowProcW(handle, msg, wparam, lparam) }        }    }     fn handle_move_msg(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {        let origin = logical_point(            lparam.signed_loword() as f32,            lparam.signed_hiword() as f32,            self.state.scale_factor.get(),        );        self.state.origin.set(origin);        let size = self.state.logical_size.get();        let center_x = origin.x.as_f32() + size.width.as_f32() / 2.;        let center_y = origin.y.as_f32() + size.height.as_f32() / 2.;        let monitor_bounds = self.state.display.get().bounds();        if center_x < monitor_bounds.left().as_f32()            || center_x > monitor_bounds.right().as_f32()            || center_y < monitor_bounds.top().as_f32()            || center_y > monitor_bounds.bottom().as_f32()        {            // center of the window may have moved to another monitor            let monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) };            // minimize the window can trigger this event too, in this case,            // monitor is invalid, we do nothing.            if !monitor.is_invalid() && self.state.display.get().handle != monitor {                // we will get the same monitor if we only have one                self.state.display.set(WindowsDisplay::new(                    WindowsDisplay::display_id_for_monitor(monitor),                )?);            }        }        if let Some(mut callback) = self.state.callbacks.moved.take() {            callback();            self.state.callbacks.moved.set(Some(callback));        }        Some(0)    }     fn handle_get_min_max_info_msg(&self, lparam: LPARAM) -> Option<isize> {        let min_size = self.state.min_size?;        let scale_factor = self.state.scale_factor.get();        let boarder_offset = &self.state.border_offset;         unsafe {            let minmax_info = &mut *(lparam.0 as *mut MINMAXINFO);            minmax_info.ptMinTrackSize.x = min_size.width.scale(scale_factor).as_f32() as i32                + boarder_offset.width_offset.get();            minmax_info.ptMinTrackSize.y = min_size.height.scale(scale_factor).as_f32() as i32                + boarder_offset.height_offset.get();        }        Some(0)    }     fn handle_size_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {        // Don't resize the renderer when the window is minimized, but record that it was minimized so        // that on restore the swap chain can be recreated via `update_drawable_size_even_if_unchanged`.        if wparam.0 == SIZE_MINIMIZED as usize {            self.state                .restore_from_minimized                .set(self.state.callbacks.request_frame.take());            return Some(0);        }         let width = lparam.loword().max(1) as i32;        let height = lparam.hiword().max(1) as i32;        let new_size = size(DevicePixels(width), DevicePixels(height));         let scale_factor = self.state.scale_factor.get();        let mut should_resize_renderer = false;        if let Some(restore_from_minimized) = self.state.restore_from_minimized.take() {            self.state                .callbacks                .request_frame                .set(Some(restore_from_minimized));        } else {            should_resize_renderer = true;        }         self.handle_size_change(new_size, scale_factor, should_resize_renderer);        Some(0)    }     fn handle_size_change(        &self,        device_size: Size<DevicePixels>,        scale_factor: f32,        should_resize_renderer: bool,    ) {        let new_logical_size = device_size.to_pixels(scale_factor);         self.state.logical_size.set(new_logical_size);        if should_resize_renderer            && let Err(e) = self.state.renderer.borrow_mut().resize(device_size)        {            log::error!("Failed to resize renderer, invalidating devices: {}", e);            self.state                .invalidate_devices                .store(true, std::sync::atomic::Ordering::Release);        }        if let Some(mut callback) = self.state.callbacks.resize.take() {            callback(new_logical_size, scale_factor);            self.state.callbacks.resize.set(Some(callback));        }    }     fn handle_size_move_loop(&self, handle: HWND) -> Option<isize> {        unsafe {            let ret = SetTimer(                Some(handle),                SIZE_MOVE_LOOP_TIMER_ID,                USER_TIMER_MINIMUM,                None,            );            if ret == 0 {                log::error!(                    "unable to create timer: {}",                    std::io::Error::last_os_error()                );            }        }        None    }     fn handle_size_move_loop_exit(&self, handle: HWND) -> Option<isize> {        unsafe {            KillTimer(Some(handle), SIZE_MOVE_LOOP_TIMER_ID).log_err();        }        None    }     fn handle_timer_msg(&self, handle: HWND, wparam: WPARAM) -> Option<isize> {        if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID {            let mut runnables = self.main_receiver.clone().try_iter();            while let Some(Ok(runnable)) = runnables.next() {                WindowsDispatcher::execute_runnable(runnable);            }            self.handle_paint_msg(handle)        } else {            None        }    }     fn handle_paint_msg(&self, handle: HWND) -> Option<isize> {        self.draw_window(handle, false)    }     fn handle_close_msg(&self) -> Option<isize> {        let mut callback = self.state.callbacks.should_close.take()?;        let should_close = callback();        self.state.callbacks.should_close.set(Some(callback));        if should_close { None } else { Some(0) }    }     fn handle_destroy_msg(&self, handle: HWND) -> Option<isize> {        let callback = { self.state.callbacks.close.take() };        // Re-enable parent window if this was a modal dialog        if let Some(parent_hwnd) = self.parent_hwnd {            unsafe {                let _ = EnableWindow(parent_hwnd, true);                let _ = SetForegroundWindow(parent_hwnd);            }        }         if let Some(callback) = callback {            callback();        }        unsafe {            PostMessageW(                Some(self.platform_window_handle),                WM_GPUI_CLOSE_ONE_WINDOW,                WPARAM(self.validation_number),                LPARAM(handle.0 as isize),            )            .log_err();        }        Some(0)    }     fn handle_mouse_move_msg(&self, handle: HWND, lparam: LPARAM, wparam: WPARAM) -> Option<isize> {        self.start_tracking_mouse(handle, TME_LEAVE);        self.restore_cursor_after_hide();         let Some(mut func) = self.state.callbacks.input.take() else {            return Some(1);        };        let scale_factor = self.state.scale_factor.get();         let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {            flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),            flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),            flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),            flags if flags.contains(MK_XBUTTON1) => {                Some(MouseButton::Navigate(NavigationDirection::Back))            }            flags if flags.contains(MK_XBUTTON2) => {                Some(MouseButton::Navigate(NavigationDirection::Forward))            }            _ => None,        };        let x = lparam.signed_loword() as f32;        let y = lparam.signed_hiword() as f32;        let input = PlatformInput::MouseMove(MouseMoveEvent {            position: logical_point(x, y, scale_factor),            pressed_button,            modifiers: current_modifiers(),        });        let handled = !func(input).propagate;        self.state.callbacks.input.set(Some(func));         if handled { Some(0) } else { Some(1) }    }     fn handle_mouse_leave_msg(&self) -> Option<isize> {        self.state.hovered.set(false);        // The next window's `WM_SETCURSOR` picks its own cursor, so we just clear        // the flag for tight `is_cursor_visible()` semantics.        self.state.cursor_visible.store(true, Ordering::Relaxed);        if let Some(mut callback) = self.state.callbacks.hovered_status_change.take() {            callback(false);            self.state                .callbacks                .hovered_status_change                .set(Some(callback));        }         Some(0)    }     fn handle_syskeyup_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {        let input = handle_key_event(wparam, lparam, &self.state, |keystroke, _| {            PlatformInput::KeyUp(KeyUpEvent { keystroke })        })?;        let mut func = self.state.callbacks.input.take()?;         func(input);        self.state.callbacks.input.set(Some(func));         // Always return 0 to indicate that the message was handled, so we could properly handle `ModifiersChanged` event.        Some(0)    }     // It's a known bug that you can't trigger `ctrl-shift-0`. See:    // https://superuser.com/questions/1455762/ctrl-shift-number-key-combination-has-stopped-working-for-a-few-numbers    fn handle_keydown_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {        let Some(input) = handle_key_event(            wparam,            lparam,            &self.state,            |keystroke, prefer_character_input| {                PlatformInput::KeyDown(KeyDownEvent {                    keystroke,                    is_held: lparam.0 & (0x1 << 30) > 0,                    prefer_character_input,                })            },        ) else {            return Some(1);        };         let Some(mut func) = self.state.callbacks.input.take() else {            return Some(1);        };         let handled = !func(input).propagate;         self.state.callbacks.input.set(Some(func));         if handled { Some(0) } else { Some(1) }    }     fn handle_keyup_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {        let Some(input) = handle_key_event(wparam, lparam, &self.state, |keystroke, _| {            PlatformInput::KeyUp(KeyUpEvent { keystroke })        }) else {            return Some(1);        };         let Some(mut func) = self.state.callbacks.input.take() else {            return Some(1);        };         let handled = !func(input).propagate;        self.state.callbacks.input.set(Some(func));         if handled { Some(0) } else { Some(1) }    }     fn handle_char_msg(&self, wparam: WPARAM) -> Option<isize> {        let input = self.parse_char_message(wparam)?;        self.with_input_handler(|input_handler| {            input_handler.replace_text_in_range(None, &input);        });         Some(0)    }     fn handle_mouse_down_msg(        &self,        handle: HWND,        button: MouseButton,        lparam: LPARAM,    ) -> Option<isize> {        unsafe { SetCapture(handle) };         let Some(mut func) = self.state.callbacks.input.take() else {            return Some(1);        };        let x = lparam.signed_loword();        let y = lparam.signed_hiword();        let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));        let click_count = self.state.click_state.update(button, physical_point);        let scale_factor = self.state.scale_factor.get();         let input = PlatformInput::MouseDown(MouseDownEvent {            button,            position: logical_point(x as f32, y as f32, scale_factor),            modifiers: current_modifiers(),            click_count,            first_mouse: false,        });        let handled = !func(input).propagate;        self.state.callbacks.input.set(Some(func));         if handled { Some(0) } else { Some(1) }    }     fn handle_mouse_up_msg(        &self,        _handle: HWND,        button: MouseButton,        lparam: LPARAM,    ) -> Option<isize> {

Lines 1–500 of 1756.