2701 lines · 101.8 KB · Rust
RawDownload
use anyhow::{Context as _, anyhow};use ashpd::WindowIdentifier;use calloop::{    EventLoop, LoopHandle, RegistrationToken,    generic::{FdWrapper, Generic},};use collections::HashMap;use core::str;use gpui::{Capslock, TaskTiming, profiler};use http_client::Url;use log::Level;use smallvec::SmallVec;use std::{    cell::RefCell,    collections::{BTreeMap, HashSet},    ops::Deref,    path::PathBuf,    rc::{Rc, Weak},    time::{Duration, Instant},};use util::ResultExt as _; use x11rb::{    connection::{Connection, RequestConnection},    cursor,    errors::ConnectionError,    protocol::randr::ConnectionExt as _,    protocol::xinput::ConnectionExt,    protocol::xkb::ConnectionExt as _,    protocol::xproto::{        AtomEnum, ChangeWindowAttributesAux, ClientMessageData, ClientMessageEvent,        ConnectionExt as _, EventMask, ModMask, Visibility,    },    protocol::{Event, dri3, randr, render, xinput, xkb, xproto},    resource_manager::Database,    wrapper::ConnectionExt as _,    xcb_ffi::XCBConnection,};use xim::{AttributeName, Client, InputStyle, x11rb::X11rbClient};use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};use xkbcommon::xkb::{self as xkbc, STATE_LAYOUT_EFFECTIVE}; use super::{    ButtonOrScroll, ScrollDirection, X11Display, X11WindowStatePtr, XcbAtoms, XimCallbackEvent,    XimHandler, button_or_scroll_from_event_detail, check_reply,    clipboard::{self, Clipboard},    get_reply, get_valuator_axis_index, handle_connection_error, modifiers_from_state,    pressed_button_from_mask, xcb_flush,}; use crate::linux::{    DEFAULT_CURSOR_ICON_NAME, LinuxClient, capslock_from_xkb, cursor_style_to_icon_names,    get_xkb_compose_state, is_within_click_distance, keystroke_from_xkb,    keystroke_underlying_dead_key, log_cursor_icon_warning, modifiers_from_xkb, open_uri_internal,    platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES},    reveal_path_internal,    xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},};use crate::linux::{LinuxCommon, LinuxKeyboardLayout, X11Window, modifiers_from_xinput_info}; use gpui::{    AnyWindowHandle, Bounds, ClipboardItem, CursorStyle, DisplayId, FileDropEvent, Keystroke,    Modifiers, ModifiersChangedEvent, MouseButton, Pixels, PlatformDisplay, PlatformInput,    PlatformKeyboardLayout, PlatformWindow, Point, RequestFrameOptions, ScrollDelta, Size,    TouchPhase, WindowButtonLayout, WindowParams, point, px,};use gpui_wgpu::{CompositorGpuHint, GpuContext}; /// Value for DeviceId parameters which selects all devices.pub(crate) const XINPUT_ALL_DEVICES: xinput::DeviceId = 0; /// Value for DeviceId parameters which selects all device groups. Events that/// occur within the group are emitted by the group itself.////// In XInput 2's interface, these are referred to as "master devices", but that/// terminology is both archaic and unclear.pub(crate) const XINPUT_ALL_DEVICE_GROUPS: xinput::DeviceId = 1; const GPUI_X11_SCALE_FACTOR_ENV: &str = "GPUI_X11_SCALE_FACTOR"; pub(crate) struct WindowRef {    window: X11WindowStatePtr,    refresh_state: Option<RefreshState>,    expose_event_received: bool,    last_visibility: Visibility,    is_mapped: bool,} impl WindowRef {    pub fn handle(&self) -> AnyWindowHandle {        self.window.state.borrow().handle    }} impl Deref for WindowRef {    type Target = X11WindowStatePtr;     fn deref(&self) -> &Self::Target {        &self.window    }} enum RefreshState {    Hidden {        refresh_rate: Duration,    },    PeriodicRefresh {        refresh_rate: Duration,        event_loop_token: RegistrationToken,    },} #[derive(Debug)]#[non_exhaustive]pub enum EventHandlerError {    XCBConnectionError(ConnectionError),    XIMClientError(xim::ClientError),} impl std::error::Error for EventHandlerError {} impl std::fmt::Display for EventHandlerError {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        match self {            EventHandlerError::XCBConnectionError(err) => err.fmt(f),            EventHandlerError::XIMClientError(err) => err.fmt(f),        }    }} impl From<ConnectionError> for EventHandlerError {    fn from(err: ConnectionError) -> Self {        EventHandlerError::XCBConnectionError(err)    }} impl From<xim::ClientError> for EventHandlerError {    fn from(err: xim::ClientError) -> Self {        EventHandlerError::XIMClientError(err)    }} #[derive(Debug, Default)]pub struct Xdnd {    other_window: xproto::Window,    drag_type: u32,    retrieved: bool,    position: Point<Pixels>,} #[derive(Debug)]struct PointerDeviceState {    horizontal: ScrollAxisState,    vertical: ScrollAxisState,} #[derive(Debug, Default)]struct ScrollAxisState {    /// Valuator number for looking up this axis's scroll value.    valuator_number: Option<u16>,    /// Conversion factor from scroll units to lines.    multiplier: f32,    /// Last scroll value for calculating scroll delta.    ///    /// This gets set to `None` whenever it might be invalid - when devices change or when window focus changes.    /// The logic errs on the side of invalidating this, since the consequence is just skipping the delta of one scroll event.    /// The consequence of not invalidating it can be large invalid deltas, which are much more user visible.    scroll_value: Option<f32>,} pub struct X11ClientState {    pub(crate) loop_handle: LoopHandle<'static, X11Client>,    pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,     pub(crate) last_click: Instant,    pub(crate) last_mouse_button: Option<MouseButton>,    pub(crate) last_location: Point<Pixels>,    pub(crate) current_count: usize,    pub(crate) pinch_scale: f32,     pub(crate) gpu_context: GpuContext,    pub(crate) compositor_gpu: Option<CompositorGpuHint>,     pub(crate) scale_factor: f32,     xkb_context: xkbc::Context,    pub(crate) xcb_connection: Rc<XCBConnection>,    xkb_device_id: i32,    client_side_decorations_supported: bool,    pub(crate) x_root_index: usize,    pub(crate) _resource_database: Database,    pub(crate) atoms: XcbAtoms,    pub(crate) windows: HashMap<xproto::Window, WindowRef>,    pub(crate) mouse_focused_window: Option<xproto::Window>,    pub(crate) keyboard_focused_window: Option<xproto::Window>,    pub(crate) xkb: xkbc::State,    keyboard_layout: LinuxKeyboardLayout,    pub(crate) ximc: Option<X11rbClient<Rc<XCBConnection>>>,    pub(crate) xim_handler: Option<XimHandler>,    pub modifiers: Modifiers,    pub capslock: Capslock,    // TODO: Can the other updates to `modifiers` be removed so that this is unnecessary?    // capslock logic was done analog to modifiers    pub last_modifiers_changed_event: Modifiers,    pub last_capslock_changed_event: Capslock,     pub(crate) compose_state: Option<xkbc::compose::State>,    pub(crate) pre_edit_text: Option<String>,    pub(crate) composing: bool,    pub(crate) pre_key_char_down: Option<Keystroke>,    pub(crate) cursor_handle: cursor::Handle,    pub(crate) cursor_styles: HashMap<xproto::Window, CursorStyle>,    pub(crate) cursor_cache: HashMap<CursorStyle, Option<xproto::Cursor>>,     pointer_device_states: BTreeMap<xinput::DeviceId, PointerDeviceState>,     pub(crate) supports_xinput_gestures: bool,     pub(crate) common: LinuxCommon,    pub(crate) clipboard: Clipboard,    pub(crate) clipboard_item: Option<ClipboardItem>,    pub(crate) xdnd_state: Xdnd,} #[derive(Clone)]pub struct X11ClientStatePtr(pub Weak<RefCell<X11ClientState>>); impl X11ClientStatePtr {    pub fn get_client(&self) -> Option<X11Client> {        self.0.upgrade().map(X11Client)    }     pub fn drop_window(&self, x_window: u32) {        let Some(client) = self.get_client() else {            return;        };        let mut state = client.0.borrow_mut();         if let Some(window_ref) = state.windows.remove(&x_window)            && let Some(RefreshState::PeriodicRefresh {                event_loop_token, ..            }) = window_ref.refresh_state        {            state.loop_handle.remove(event_loop_token);        }        if state.mouse_focused_window == Some(x_window) {            state.mouse_focused_window = None;        }        if state.keyboard_focused_window == Some(x_window) {            state.keyboard_focused_window = None;        }        state.cursor_styles.remove(&x_window);    }     pub fn update_ime_position(&self, bounds: Bounds<Pixels>) {        let Some(client) = self.get_client() else {            return;        };        let mut state = client.0.borrow_mut();        if state.composing || state.ximc.is_none() {            return;        }         let Some(mut ximc) = state.ximc.take() else {            log::error!("bug: xim connection not set");            return;        };        let Some(xim_handler) = state.xim_handler.take() else {            log::error!("bug: xim handler not set");            state.ximc = Some(ximc);            return;        };        let scaled_bounds = bounds.scale(state.scale_factor);        let ic_attributes = ximc            .build_ic_attributes()            .push(                xim::AttributeName::InputStyle,                xim::InputStyle::PREEDIT_CALLBACKS,            )            .push(xim::AttributeName::ClientWindow, xim_handler.window)            .push(xim::AttributeName::FocusWindow, xim_handler.window)            .nested_list(xim::AttributeName::PreeditAttributes, |b| {                b.push(                    xim::AttributeName::SpotLocation,                    xim::Point {                        x: u32::from(scaled_bounds.origin.x + scaled_bounds.size.width) as i16,                        y: u32::from(scaled_bounds.origin.y + scaled_bounds.size.height) as i16,                    },                );            })            .build();        let _ = ximc            .set_ic_values(xim_handler.im_id, xim_handler.ic_id, ic_attributes)            .log_err();        state.ximc = Some(ximc);        state.xim_handler = Some(xim_handler);    }} #[derive(Clone)]pub(crate) struct X11Client(pub(crate) Rc<RefCell<X11ClientState>>); impl X11Client {    pub(crate) fn new() -> anyhow::Result<Self> {        let event_loop = EventLoop::try_new()?;         let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());         let handle = event_loop.handle();         handle            .insert_source(main_receiver, {                let handle = handle.clone();                move |event, _, _: &mut X11Client| {                    if let calloop::channel::Event::Msg(runnable) = event {                        // Insert the runnables as idle callbacks, so we make sure that user-input and X11                        // events have higher priority and runnables are only worked off after the event                        // callbacks.                        handle.insert_idle(|_| {                            let start = Instant::now();                            let location = runnable.metadata().location;                            let mut timing = TaskTiming {                                location,                                start,                                end: None,                            };                            profiler::add_task_timing(timing);                             runnable.run();                             let end = Instant::now();                            timing.end = Some(end);                            profiler::add_task_timing(timing);                        });                    }                }            })            .map_err(|err| {                anyhow!("Failed to initialize event loop handling of foreground tasks: {err:?}")            })?;         let (xcb_connection, x_root_index) = XCBConnection::connect(None)?;        xcb_connection.prefetch_extension_information(xkb::X11_EXTENSION_NAME)?;        xcb_connection.prefetch_extension_information(randr::X11_EXTENSION_NAME)?;        xcb_connection.prefetch_extension_information(render::X11_EXTENSION_NAME)?;        xcb_connection.prefetch_extension_information(xinput::X11_EXTENSION_NAME)?;         // Announce to X server that XInput up to 2.4 is supported.        // Version 2.4 is needed for gesture events (GesturePinchBegin/Update/End).        // The server responds with the highest version it supports; if < 2.4,        // we must not request gesture event masks in XISelectEvents.        let xinput_version = get_reply(            || "XInput XiQueryVersion failed",            xcb_connection.xinput_xi_query_version(2, 4),        )?;        assert!(            xinput_version.major_version >= 2,            "XInput version >= 2 required."        );        let supports_xinput_gestures = xinput_version.major_version > 2            || (xinput_version.major_version == 2 && xinput_version.minor_version >= 4);        log::info!(            "XInput version: {}.{}, gesture support: {}",            xinput_version.major_version,            xinput_version.minor_version,            supports_xinput_gestures,        );         let pointer_device_states =            current_pointer_device_states(&xcb_connection, &BTreeMap::new()).unwrap_or_default();         let atoms = XcbAtoms::new(&xcb_connection)            .context("Failed to get XCB atoms")?            .reply()            .context("Failed to get XCB atoms")?;         let root = xcb_connection.setup().roots[0].root;        let compositor_present = check_compositor_present(&xcb_connection, root);        let gtk_frame_extents_supported =            check_gtk_frame_extents_supported(&xcb_connection, &atoms, root);        let client_side_decorations_supported = compositor_present && gtk_frame_extents_supported;        log::info!(            "x11: compositor present: {}, gtk_frame_extents_supported: {}",            compositor_present,            gtk_frame_extents_supported        );         let xkb = get_reply(            || "Failed to initialize XKB extension",            xcb_connection                .xkb_use_extension(XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION),        )?;        assert!(xkb.supported);         let events = xkb::EventType::STATE_NOTIFY            | xkb::EventType::MAP_NOTIFY            | xkb::EventType::NEW_KEYBOARD_NOTIFY;        let map_notify_parts = xkb::MapPart::KEY_TYPES            | xkb::MapPart::KEY_SYMS            | xkb::MapPart::MODIFIER_MAP            | xkb::MapPart::EXPLICIT_COMPONENTS            | xkb::MapPart::KEY_ACTIONS            | xkb::MapPart::KEY_BEHAVIORS            | xkb::MapPart::VIRTUAL_MODS            | xkb::MapPart::VIRTUAL_MOD_MAP;        check_reply(            || "Failed to select XKB events",            xcb_connection.xkb_select_events(                xkb::ID::USE_CORE_KBD.into(),                0u8.into(),                events,                map_notify_parts,                map_notify_parts,                &xkb::SelectEventsAux::new(),            ),        )?;         let xkb_context = xkbc::Context::new(xkbc::CONTEXT_NO_FLAGS);        let xkb_device_id = xkbc::x11::get_core_keyboard_device_id(&xcb_connection);        let xkb_state = {            let xkb_keymap = xkbc::x11::keymap_new_from_device(                &xkb_context,                &xcb_connection,                xkb_device_id,                xkbc::KEYMAP_COMPILE_NO_FLAGS,            );            xkbc::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id)        };        let compose_state = get_xkb_compose_state(&xkb_context);        let layout_idx = xkb_state.serialize_layout(STATE_LAYOUT_EFFECTIVE);        let layout_name = xkb_state            .get_keymap()            .layout_get_name(layout_idx)            .to_string();        let keyboard_layout = LinuxKeyboardLayout::new(layout_name.into());         let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection)            .context("Failed to create resource database")?;        let scale_factor = get_scale_factor(&xcb_connection, &resource_database, x_root_index);        let cursor_handle = cursor::Handle::new(&xcb_connection, x_root_index, &resource_database)            .context("Failed to initialize cursor theme handler")?            .reply()            .context("Failed to initialize cursor theme handler")?;         let clipboard = Clipboard::new().context("Failed to initialize clipboard")?;         let screen = &xcb_connection.setup().roots[x_root_index];        let compositor_gpu = detect_compositor_gpu(&xcb_connection, screen);         let xcb_connection = Rc::new(xcb_connection);         let ximc = X11rbClient::init(Rc::clone(&xcb_connection), x_root_index, None).ok();        let xim_handler = if ximc.is_some() {            Some(XimHandler::new())        } else {            None        };         // Safety: Safe if xcb::Connection always returns a valid fd        let fd = unsafe { FdWrapper::new(Rc::clone(&xcb_connection)) };         handle            .insert_source(                Generic::new_with_error::<EventHandlerError>(                    fd,                    calloop::Interest::READ,                    calloop::Mode::Level,                ),                {                    let xcb_connection = xcb_connection.clone();                    move |_readiness, _, client| {                        client.process_x11_events(&xcb_connection)?;                        Ok(calloop::PostAction::Continue)                    }                },            )            .map_err(|err| anyhow!("Failed to initialize X11 event source: {err:?}"))?;         handle            .insert_source(XDPEventSource::new(&common.background_executor), {                move |event, _, client| match event {                    XDPEvent::WindowAppearance(appearance) => {                        client.with_common(|common| common.appearance = appearance);                        for window in client.0.borrow_mut().windows.values_mut() {                            window.window.set_appearance(appearance);                        }                    }                    XDPEvent::ButtonLayout(layout_str) => {                        let layout = WindowButtonLayout::parse(&layout_str)                            .log_err()                            .unwrap_or_else(WindowButtonLayout::linux_default);                        client.with_common(|common| common.button_layout = layout);                        for window in client.0.borrow_mut().windows.values_mut() {                            window.window.set_button_layout();                        }                    }                    XDPEvent::CursorTheme(_) | XDPEvent::CursorSize(_) => {                        // noop, X11 manages this for us.                    }                }

Lines 1–500 of 2701.