2839 lines · 104.9 KB · Rust
RawDownload
use std::{    cell::{RefCell, RefMut},    hash::Hash,    os::fd::{AsRawFd, BorrowedFd},    path::PathBuf,    rc::{Rc, Weak},    time::{Duration, Instant},}; use ashpd::WindowIdentifier;use calloop::{    EventLoop, LoopHandle,    timer::{TimeoutAction, Timer},};use calloop_wayland_source::WaylandSource;use collections::HashMap;use filedescriptor::Pipe;use gpui_util::ResultExt as _;use http_client::Url;use smallvec::SmallVec;use wayland_backend::client::ObjectId;use wayland_backend::protocol::WEnum;use wayland_client::event_created_child;use wayland_client::globals::{GlobalList, GlobalListContents, registry_queue_init};use wayland_client::protocol::wl_callback::{self, WlCallback};use wayland_client::protocol::wl_data_device_manager::DndAction;use wayland_client::protocol::wl_data_offer::WlDataOffer;use wayland_client::protocol::wl_pointer::AxisSource;use wayland_client::protocol::{    wl_data_device, wl_data_device_manager, wl_data_offer, wl_data_source, wl_output, wl_region,};use wayland_client::{    Connection, Dispatch, Proxy, QueueHandle, delegate_noop,    protocol::{        wl_buffer, wl_compositor, wl_keyboard, wl_pointer, wl_registry, wl_seat, wl_shm,        wl_shm_pool, wl_surface,    },};use wayland_protocols::wp::pointer_gestures::zv1::client::{    zwp_pointer_gesture_pinch_v1, zwp_pointer_gestures_v1,};use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::{    self, ZwpPrimarySelectionOfferV1,};use wayland_protocols::wp::primary_selection::zv1::client::{    zwp_primary_selection_device_manager_v1, zwp_primary_selection_device_v1,    zwp_primary_selection_source_v1,};use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_v3::{    ContentHint, ContentPurpose,};use wayland_protocols::wp::text_input::zv3::client::{    zwp_text_input_manager_v3, zwp_text_input_v3,};use wayland_protocols::wp::viewporter::client::{wp_viewport, wp_viewporter};use wayland_protocols::xdg::activation::v1::client::{xdg_activation_token_v1, xdg_activation_v1};use wayland_protocols::xdg::decoration::zv1::client::{    zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1,};use wayland_protocols::xdg::shell::client::{    xdg_popup, xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base,};use wayland_protocols::xdg::system_bell::v1::client::xdg_system_bell_v1;use wayland_protocols::{    wp::cursor_shape::v1::client::{wp_cursor_shape_device_v1, wp_cursor_shape_manager_v1},    xdg::dialog::v1::client::xdg_wm_dialog_v1::{self, XdgWmDialogV1},};use wayland_protocols::{    wp::fractional_scale::v1::client::{wp_fractional_scale_manager_v1, wp_fractional_scale_v1},    xdg::dialog::v1::client::xdg_dialog_v1::XdgDialogV1,};use wayland_protocols_plasma::blur::client::{org_kde_kwin_blur, org_kde_kwin_blur_manager};use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1};use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1;use xkbcommon::xkb::{self, KEYMAP_COMPILE_NO_FLAGS, Keycode}; use super::{    display::WaylandDisplay,    window::{ImeInput, WaylandWindowStatePtr},}; use crate::linux::{    DOUBLE_CLICK_INTERVAL, LinuxClient, LinuxCommon, LinuxKeyboardLayout, PIPE_READ_TIMEOUT,    SCROLL_LINES, capslock_from_xkb, cursor_style_to_icon_names, get_xkb_compose_state,    is_within_click_distance, keystroke_from_xkb, keystroke_underlying_dead_key,    modifiers_from_xkb, open_uri_internal, read_fd_with_timeout, reveal_path_internal,    wayland::{        clipboard::{Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPES},        cursor::Cursor,        serial::{Serial, SerialKind, SerialTracker},        to_shape,        window::WaylandWindow,    },    xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},};use gpui::{    AnyWindowHandle, Bounds, Capslock, CursorStyle, DevicePixels, DisplayId, FileDropEvent,    ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent,    MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection,    Pixels, PlatformDisplay, PlatformInput, PlatformKeyboardLayout, PlatformWindow, Point,    ScrollDelta, ScrollWheelEvent, SharedString, Size, TouchPhase, WindowButtonLayout, WindowKind,    WindowParams, point, profiler, px, size,};use gpui_wgpu::{CompositorGpuHint, GpuContext};use wayland_protocols::wp::linux_dmabuf::zv1::client::{    zwp_linux_dmabuf_feedback_v1, zwp_linux_dmabuf_v1,}; /// Used to convert evdev scancode to xkb scancodeconst MIN_KEYCODE: u32 = 8; const UNKNOWN_KEYBOARD_LAYOUT_NAME: SharedString = SharedString::new_static("unknown");const XDG_ACTIVATION_TOKEN_ENV_VAR: &str = "XDG_ACTIVATION_TOKEN"; #[derive(Clone, Copy, Debug, PartialEq, Eq)]struct ImeCursorRectangle {    x: i32,    y: i32,    width: i32,    height: i32,} impl From<Bounds<Pixels>> for ImeCursorRectangle {    fn from(bounds: Bounds<Pixels>) -> Self {        Self {            x: bounds.origin.x.as_f32() as i32,            y: bounds.origin.y.as_f32() as i32,            width: bounds.size.width.as_f32() as i32,            height: bounds.size.height.as_f32() as i32,        }    }} trait ImeCursorRectangleSink {    fn set_ime_cursor_rectangle(&self, x: i32, y: i32, width: i32, height: i32);    fn commit_ime_state(&self);} impl ImeCursorRectangleSink for zwp_text_input_v3::ZwpTextInputV3 {    fn set_ime_cursor_rectangle(&self, x: i32, y: i32, width: i32, height: i32) {        self.set_cursor_rectangle(x, y, width, height);    }     fn commit_ime_state(&self) {        self.commit();    }} fn set_ime_cursor_rectangle(    text_input: &impl ImeCursorRectangleSink,    cursor_rectangle: ImeCursorRectangle,) {    text_input.set_ime_cursor_rectangle(        cursor_rectangle.x,        cursor_rectangle.y,        cursor_rectangle.width,        cursor_rectangle.height,    );} fn update_ime_cursor_rectangle(    text_input: &impl ImeCursorRectangleSink,    last_ime_cursor_rectangle: &mut Option<ImeCursorRectangle>,    bounds: Bounds<Pixels>,) {    let cursor_rectangle = ImeCursorRectangle::from(bounds);    if *last_ime_cursor_rectangle == Some(cursor_rectangle) {        return;    }     *last_ime_cursor_rectangle = Some(cursor_rectangle);    set_ime_cursor_rectangle(text_input, cursor_rectangle);    text_input.commit_ime_state();} fn set_ime_cursor_rectangle_after_done(    text_input: &impl ImeCursorRectangleSink,    last_ime_cursor_rectangle: &mut Option<ImeCursorRectangle>,    bounds: Bounds<Pixels>,    should_commit: bool,) {    if should_commit {        update_ime_cursor_rectangle(text_input, last_ime_cursor_rectangle, bounds);    } else {        set_ime_cursor_rectangle(text_input, ImeCursorRectangle::from(bounds));    }} fn take_startup_activation_token_from_environment() -> Option<String> {    let startup_activation_token = std::env::var(XDG_ACTIVATION_TOKEN_ENV_VAR)        .ok()        .filter(|token| !token.is_empty());    // The token must be removed from the environment so it isn't inherited by child    // processes we spawn, per the xdg-activation spec: https://wayland.app/protocols/xdg-activation-v1    // SAFETY: This runs during Wayland platform initialization before GPUI starts    // concurrent environment access or spawning child processes.    unsafe { std::env::remove_var(XDG_ACTIVATION_TOKEN_ENV_VAR) };    startup_activation_token} #[derive(Clone)]pub struct Globals {    pub qh: QueueHandle<WaylandClientStatePtr>,    pub activation: Option<xdg_activation_v1::XdgActivationV1>,    pub compositor: wl_compositor::WlCompositor,    pub cursor_shape_manager: Option<wp_cursor_shape_manager_v1::WpCursorShapeManagerV1>,    pub data_device_manager: Option<wl_data_device_manager::WlDataDeviceManager>,    pub primary_selection_manager:        Option<zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1>,    pub wm_base: xdg_wm_base::XdgWmBase,    pub shm: wl_shm::WlShm,    pub seat: wl_seat::WlSeat,    pub viewporter: Option<wp_viewporter::WpViewporter>,    pub fractional_scale_manager:        Option<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1>,    pub decoration_manager: Option<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1>,    pub layer_shell: Option<zwlr_layer_shell_v1::ZwlrLayerShellV1>,    pub blur_manager: Option<org_kde_kwin_blur_manager::OrgKdeKwinBlurManager>,    pub text_input_manager: Option<zwp_text_input_manager_v3::ZwpTextInputManagerV3>,    pub gesture_manager: Option<zwp_pointer_gestures_v1::ZwpPointerGesturesV1>,    pub dialog: Option<xdg_wm_dialog_v1::XdgWmDialogV1>,    pub system_bell: Option<xdg_system_bell_v1::XdgSystemBellV1>,    pub executor: ForegroundExecutor,} impl Globals {    fn new(        globals: GlobalList,        executor: ForegroundExecutor,        qh: QueueHandle<WaylandClientStatePtr>,        seat: wl_seat::WlSeat,    ) -> Self {        let dialog_v = XdgWmDialogV1::interface().version;        Globals {            activation: globals.bind(&qh, 1..=1, ()).ok(),            compositor: globals                .bind(                    &qh,                    wl_surface::REQ_SET_BUFFER_SCALE_SINCE                        ..=wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE,                    (),                )                .unwrap(),            cursor_shape_manager: globals.bind(&qh, 1..=1, ()).ok(),            data_device_manager: globals                .bind(                    &qh,                    WL_DATA_DEVICE_MANAGER_VERSION..=WL_DATA_DEVICE_MANAGER_VERSION,                    (),                )                .ok(),            primary_selection_manager: globals.bind(&qh, 1..=1, ()).ok(),            shm: globals.bind(&qh, 1..=1, ()).unwrap(),            seat,            wm_base: globals.bind(&qh, 1..=5, ()).unwrap(),            viewporter: globals.bind(&qh, 1..=1, ()).ok(),            fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(),            decoration_manager: globals.bind(&qh, 1..=1, ()).ok(),            layer_shell: globals.bind(&qh, 1..=5, ()).ok(),            blur_manager: globals.bind(&qh, 1..=1, ()).ok(),            text_input_manager: globals.bind(&qh, 1..=1, ()).ok(),            gesture_manager: globals.bind(&qh, 1..=3, ()).ok(),            dialog: globals.bind(&qh, dialog_v..=dialog_v, ()).ok(),            system_bell: globals.bind(&qh, 1..=1, ()).ok(),            executor,            qh,        }    }} #[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]pub struct InProgressOutput {    name: Option<String>,    scale: Option<i32>,    position: Option<Point<DevicePixels>>,    size: Option<Size<DevicePixels>>,    subpixel: Option<wl_output::Subpixel>,} impl InProgressOutput {    fn complete(&self) -> Option<Output> {        if let Some((position, size)) = self.position.zip(self.size) {            let scale = self.scale.unwrap_or(1);            Some(Output {                name: self.name.clone(),                scale,                bounds: Bounds::new(position, size),                subpixel: self.subpixel,            })        } else {            None        }    }} #[derive(Debug, Clone, Eq, PartialEq, Hash)]pub struct Output {    pub name: Option<String>,    pub scale: i32,    pub bounds: Bounds<DevicePixels>,    pub subpixel: Option<wl_output::Subpixel>,} pub(crate) struct WaylandClientState {    serial_tracker: SerialTracker,    globals: Globals,    pub gpu_context: GpuContext,    pub compositor_gpu: Option<CompositorGpuHint>,    wl_seat: wl_seat::WlSeat, // TODO: Multi seat support    wl_pointer: Option<wl_pointer::WlPointer>,    pinch_gesture: Option<zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1>,    pinch_scale: f32,    wl_keyboard: Option<wl_keyboard::WlKeyboard>,    cursor_shape_device: Option<wp_cursor_shape_device_v1::WpCursorShapeDeviceV1>,    data_device: Option<wl_data_device::WlDataDevice>,    primary_selection: Option<zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1>,    text_input: Option<zwp_text_input_v3::ZwpTextInputV3>,    pre_edit_text: Option<String>,    ime_pre_edit: Option<String>,    composing: bool,    last_ime_cursor_rectangle: Option<ImeCursorRectangle>,    // Surface to Window mapping    windows: HashMap<ObjectId, WaylandWindowStatePtr>,    // Output to scale mapping    outputs: HashMap<ObjectId, Output>,    in_progress_outputs: HashMap<ObjectId, InProgressOutput>,    wl_outputs: HashMap<ObjectId, wl_output::WlOutput>,    keyboard_layout: LinuxKeyboardLayout,    keymap_state: Option<xkb::State>,    compose_state: Option<xkb::compose::State>,    drag: DragState,    click: ClickState,    repeat: KeyRepeat,    pub modifiers: Modifiers,    pub capslock: Capslock,    axis_source: AxisSource,    pub mouse_location: Option<Point<Pixels>>,    continuous_scroll_delta: Option<Point<Pixels>>,    discrete_scroll_delta: Option<Point<f32>>,    vertical_modifier: f32,    horizontal_modifier: f32,    scroll_event_received: bool,    enter_token: Option<()>,    button_pressed: Option<MouseButton>,    mouse_focused_window: Option<WaylandWindowStatePtr>,    keyboard_focused_window: Option<WaylandWindowStatePtr>,    loop_handle: LoopHandle<'static, WaylandClientStatePtr>,    cursor_style: Option<CursorStyle>,    cursor_hidden_window: Option<WaylandWindowStatePtr>,    clipboard: Clipboard,    data_offers: Vec<DataOffer<WlDataOffer>>,    primary_data_offer: Option<DataOffer<ZwpPrimarySelectionOfferV1>>,    cursor: Cursor,    pending_activation: Option<PendingActivation>,    startup_activation_token: Option<String>,    event_loop: Option<EventLoop<'static, WaylandClientStatePtr>>,    pub common: LinuxCommon,    ime_enabled: Option<bool>,} pub struct DragState {    data_offer: Option<wl_data_offer::WlDataOffer>,    window: Option<WaylandWindowStatePtr>,    position: Point<Pixels>,} pub struct ClickState {    last_mouse_button: Option<MouseButton>,    last_click: Instant,    last_location: Point<Pixels>,    current_count: usize,} pub(crate) struct KeyRepeat {    characters_per_second: u32,    delay: Duration,    current_id: u64,    current_keycode: Option<xkb::Keycode>,} pub(crate) enum PendingActivation {    /// URI to open in the web browser.    Uri(String),    /// Path to open in the file explorer.    Path(PathBuf),    /// A window from ourselves to raise.    Window(ObjectId),} impl WaylandClientState {    fn consume_startup_activation_token(&mut self, surface: &wl_surface::WlSurface) {        let Some(startup_activation_token) = self.startup_activation_token.take() else {            return;        };        let Some(activation) = self.globals.activation.as_ref() else {            return;        };        activation.activate(startup_activation_token, surface);    }} /// This struct is required to conform to Rust's orphan rules, so we can dispatch on the state but hand the/// window to GPUI.#[derive(Clone)]pub struct WaylandClientStatePtr(Weak<RefCell<WaylandClientState>>); impl WaylandClientStatePtr {    pub fn get_client(&self) -> Rc<RefCell<WaylandClientState>> {        self.0            .upgrade()            .expect("The pointer should always be valid when dispatching in wayland")    }     pub fn get_serial(&self, kind: SerialKind) -> Serial {        self.0.upgrade().unwrap().borrow().serial_tracker.get(kind)    }     pub fn set_pending_activation(&self, window: ObjectId) {        self.0.upgrade().unwrap().borrow_mut().pending_activation =            Some(PendingActivation::Window(window));    }     pub fn enable_ime(&self) {        let client = self.get_client();        let mut state = client.borrow_mut();        state.ime_enabled = Some(true);        state.last_ime_cursor_rectangle = None;        let Some(text_input) = state.text_input.take() else {            return;        };         text_input.enable();        text_input.set_content_type(ContentHint::None, ContentPurpose::Normal);        let mut cursor_rectangle = None;        if let Some(window) = state.keyboard_focused_window.clone() {            drop(state);            if let Some(area) = window.get_ime_area() {                let area = ImeCursorRectangle::from(area);                set_ime_cursor_rectangle(&text_input, area);                cursor_rectangle = Some(area);            }            state = client.borrow_mut();        }        text_input.commit();        state.last_ime_cursor_rectangle = cursor_rectangle;        state.text_input = Some(text_input);    }     pub fn disable_ime(&self) {        let client = self.get_client();        let mut state = client.borrow_mut();        state.ime_enabled = Some(false);        state.composing = false;        if let Some(text_input) = &state.text_input {            text_input.disable();            text_input.commit();        }    }     pub fn ime_enabled(&self) -> Option<bool> {        let client = self.get_client();        client.borrow().ime_enabled    }     pub fn update_ime_position(&self, bounds: Bounds<Pixels>) {        let client = self.get_client();        let mut state = client.borrow_mut();        if state.pre_edit_text.is_some() {            return;        }        let Some(text_input) = state.text_input.clone() else {            return;        };        update_ime_cursor_rectangle(&text_input, &mut state.last_ime_cursor_rectangle, bounds);    }     pub fn handle_keyboard_layout_change(&self) {        let client = self.get_client();        let mut state = client.borrow_mut();        let changed = if let Some(keymap_state) = &state.keymap_state {            let layout_idx = keymap_state.serialize_layout(xkbcommon::xkb::STATE_LAYOUT_EFFECTIVE);            let keymap = keymap_state.get_keymap();            let layout_name = keymap.layout_get_name(layout_idx);            let changed = layout_name != state.keyboard_layout.name();            if changed {                state.keyboard_layout = LinuxKeyboardLayout::new(layout_name.to_string().into());            }            changed        } else {            let changed = &UNKNOWN_KEYBOARD_LAYOUT_NAME != state.keyboard_layout.name();            if changed {                state.keyboard_layout = LinuxKeyboardLayout::new(UNKNOWN_KEYBOARD_LAYOUT_NAME);            }            changed        };         if changed && let Some(mut callback) = state.common.callbacks.keyboard_layout_change.take()        {            drop(state);            callback();

Lines 1–500 of 2839.