use std::{ cell::{RefCell, RefMut}, hash::Hash, os::fd::{AsRawFd, BorrowedFd}, path::PathBuf, rc::{Rc, Weak}, time::{Duration, Instant},}; use calloop::{ timer::{TimeoutAction, Timer}, EventLoop, LoopHandle,};use calloop_wayland_source::WaylandSource;use collections::HashMap;use filedescriptor::Pipe; use http_client::Url;use smallvec::SmallVec;use util::ResultExt;use wayland_backend::client::ObjectId;use wayland_backend::protocol::WEnum;use wayland_client::event_created_child;use wayland_client::globals::{registry_queue_init, GlobalList, GlobalListContents};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::{ delegate_noop, protocol::{ wl_buffer, wl_compositor, wl_keyboard, wl_pointer, wl_registry, wl_seat, wl_shm, wl_shm_pool, wl_surface, }, Connection, Dispatch, Proxy, QueueHandle,};use wayland_protocols::wp::cursor_shape::v1::client::{ wp_cursor_shape_device_v1, wp_cursor_shape_manager_v1,};use wayland_protocols::wp::fractional_scale::v1::client::{ wp_fractional_scale_manager_v1, wp_fractional_scale_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_surface, xdg_toplevel, xdg_wm_base};use wayland_protocols_plasma::blur::client::{org_kde_kwin_blur, org_kde_kwin_blur_manager};use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1;use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS}; use super::display::WaylandDisplay;use super::window::{ImeInput, WaylandWindowStatePtr}; use crate::platform::linux::{ get_xkb_compose_state, is_within_click_distance, open_uri_internal, read_fd, reveal_path_internal, wayland::{ clipboard::{Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPE}, cursor::Cursor, serial::{SerialKind, SerialTracker}, window::WaylandWindow, }, xdg_desktop_portal::{Event as XDPEvent, XDPEventSource}, LinuxClient,};use crate::platform::{blade::BladeContext, PlatformWindow};use crate::{ point, px, size, AnyWindowHandle, Bounds, CursorStyle, DevicePixels, DisplayId, FileDropEvent, ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, LinuxCommon, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScaledPixels, ScrollDelta, ScrollWheelEvent, Size, TouchPhase, WindowParams, DOUBLE_CLICK_INTERVAL, SCROLL_LINES,}; /// Used to convert evdev scancode to xkb scancodeconst MIN_KEYCODE: u32 = 8; #[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 blur_manager: Option<org_kde_kwin_blur_manager::OrgKdeKwinBlurManager>, pub text_input_manager: Option<zwp_text_input_manager_v3::ZwpTextInputManagerV3>, pub executor: ForegroundExecutor,} impl Globals { fn new( globals: GlobalList, executor: ForegroundExecutor, qh: QueueHandle<WaylandClientStatePtr>, seat: wl_seat::WlSeat, ) -> Self { 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, 2..=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(), blur_manager: globals.bind(&qh, 1..=1, ()).ok(), text_input_manager: 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>>,} 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), }) } else { None } }} #[derive(Debug, Clone, Eq, PartialEq, Hash)]pub struct Output { pub name: Option<String>, pub scale: i32, pub bounds: Bounds<DevicePixels>,} pub(crate) struct WaylandClientState { serial_tracker: SerialTracker, globals: Globals, gpu_context: BladeContext, wl_seat: wl_seat::WlSeat, // TODO: Multi seat support wl_pointer: Option<wl_pointer::WlPointer>, 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, // Surface to Window mapping windows: HashMap<ObjectId, WaylandWindowStatePtr>, // Output to scale mapping outputs: HashMap<ObjectId, Output>, in_progress_outputs: HashMap<ObjectId, InProgressOutput>, keymap_state: Option<xkb::State>, compose_state: Option<xkb::compose::State>, drag: DragState, click: ClickState, repeat: KeyRepeat, pub modifiers: Modifiers, 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>, clipboard: Clipboard, data_offers: Vec<DataOffer<WlDataOffer>>, primary_data_offer: Option<DataOffer<ZwpPrimarySelectionOfferV1>>, cursor: Cursor, pending_activation: Option<PendingActivation>, event_loop: Option<EventLoop<'static, WaylandClientStatePtr>>, common: LinuxCommon,} 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),} /// 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) -> u32 { 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(); let Some(mut text_input) = state.text_input.take() else { return; }; text_input.enable(); text_input.set_content_type(ContentHint::None, ContentPurpose::Normal); if let Some(window) = state.keyboard_focused_window.clone() { drop(state); if let Some(area) = window.get_ime_area() { text_input.set_cursor_rectangle( area.origin.x.0 as i32, area.origin.y.0 as i32, area.size.width.0 as i32, area.size.height.0 as i32, ); } state = client.borrow_mut(); } text_input.commit(); state.text_input = Some(text_input); } pub fn disable_ime(&self) { let client = self.get_client(); let mut state = client.borrow_mut(); state.composing = false; if let Some(text_input) = &state.text_input { text_input.disable(); text_input.commit(); } } pub fn update_ime_position(&self, bounds: Bounds<ScaledPixels>) { let client = self.get_client(); let mut state = client.borrow_mut(); if state.composing || state.text_input.is_none() || state.pre_edit_text.is_some() { return; } let text_input = state.text_input.as_ref().unwrap(); text_input.set_cursor_rectangle( bounds.origin.x.0 as i32, bounds.origin.y.0 as i32, bounds.size.width.0 as i32, bounds.size.height.0 as i32, ); text_input.commit(); } pub fn drop_window(&self, surface_id: &ObjectId) { let mut client = self.get_client(); let mut state = client.borrow_mut(); let closed_window = state.windows.remove(surface_id).unwrap(); if let Some(window) = state.mouse_focused_window.take() { if !window.ptr_eq(&closed_window) { state.mouse_focused_window = Some(window); } } if let Some(window) = state.keyboard_focused_window.take() { if !window.ptr_eq(&closed_window) { state.keyboard_focused_window = Some(window); } } if state.windows.is_empty() { state.common.signal.stop(); } }} #[derive(Clone)]pub struct WaylandClient(Rc<RefCell<WaylandClientState>>); impl Drop for WaylandClient { fn drop(&mut self) { let mut state = self.0.borrow_mut(); state.windows.clear(); if let Some(wl_pointer) = &state.wl_pointer { wl_pointer.release(); } if let Some(cursor_shape_device) = &state.cursor_shape_device { cursor_shape_device.destroy(); } if let Some(data_device) = &state.data_device { data_device.release(); } if let Some(text_input) = &state.text_input { text_input.destroy(); } }} const WL_DATA_DEVICE_MANAGER_VERSION: u32 = 3; fn wl_seat_version(version: u32) -> u32 { // We rely on the wl_pointer.frame event const WL_SEAT_MIN_VERSION: u32 = 5; const WL_SEAT_MAX_VERSION: u32 = 9; if version < WL_SEAT_MIN_VERSION { panic!( "wl_seat below required version: {} < {}", version, WL_SEAT_MIN_VERSION ); } version.clamp(WL_SEAT_MIN_VERSION, WL_SEAT_MAX_VERSION)} fn wl_output_version(version: u32) -> u32 { const WL_OUTPUT_MIN_VERSION: u32 = 2; const WL_OUTPUT_MAX_VERSION: u32 = 4; if version < WL_OUTPUT_MIN_VERSION { panic!( "wl_output below required version: {} < {}", version, WL_OUTPUT_MIN_VERSION ); } version.clamp(WL_OUTPUT_MIN_VERSION, WL_OUTPUT_MAX_VERSION)} impl WaylandClient { pub(crate) fn new() -> Self { let conn = Connection::connect_to_env().unwrap(); let (globals, mut event_queue) = registry_queue_init::<WaylandClientStatePtr>(&conn).unwrap(); let qh = event_queue.handle(); let mut seat: Option<wl_seat::WlSeat> = None; #[allow(clippy::mutable_key_type)] let mut in_progress_outputs = HashMap::default(); globals.contents().with_list(|list| { for global in list { match &global.interface[..] { "wl_seat" => { seat = Some(globals.registry().bind::<wl_seat::WlSeat, _, _>( global.name, wl_seat_version(global.version), &qh, (), )); } "wl_output" => { let output = globals.registry().bind::<wl_output::WlOutput, _, _>( global.name, wl_output_version(global.version), &qh, (), ); in_progress_outputs.insert(output.id(), InProgressOutput::default()); } _ => {} } } }); let event_loop = EventLoop::<WaylandClientStatePtr>::try_new().unwrap(); 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 WaylandClientStatePtr| { if let calloop::channel::Event::Msg(runnable) = event { handle.insert_idle(|_| { runnable.run(); }); } } }) .unwrap(); let gpu_context = BladeContext::new().expect("Unable to init GPU context"); let seat = seat.unwrap(); let globals = Globals::new( globals, common.foreground_executor.clone(), qh.clone(), seat.clone(), ); let data_device = globals .data_device_manager .as_ref() .map(|data_device_manager| data_device_manager.get_data_device(&seat, &qh, ())); let primary_selection = globals .primary_selection_manager .as_ref() .map(|primary_selection_manager| primary_selection_manager.get_device(&seat, &qh, ())); let mut cursor = Cursor::new(&conn, &globals, 24); handle .insert_source(XDPEventSource::new(&common.background_executor), { move |event, _, client| match event { XDPEvent::WindowAppearance(appearance) => { if let Some(client) = client.0.upgrade() { let mut client = client.borrow_mut(); client.common.appearance = appearance; for (_, window) in &mut client.windows { window.set_appearance(appearance); } } }Lines 1–500 of 2097. Load more
use std::{ cell::{RefCell, RefMut}, hash::Hash, os::fd::{AsRawFd, BorrowedFd}, path::PathBuf, rc::{Rc, Weak}, time::{Duration, Instant}, }; use calloop::{ timer::{TimeoutAction, Timer}, EventLoop, LoopHandle, }; use calloop_wayland_source::WaylandSource; use collections::HashMap; use filedescriptor::Pipe; use http_client::Url; use smallvec::SmallVec; use util::ResultExt; use wayland_backend::client::ObjectId; use wayland_backend::protocol::WEnum; use wayland_client::event_created_child; use wayland_client::globals::{registry_queue_init, GlobalList, GlobalListContents}; 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::{ delegate_noop, protocol::{ wl_buffer, wl_compositor, wl_keyboard, wl_pointer, wl_registry, wl_seat, wl_shm, wl_shm_pool, wl_surface, }, Connection, Dispatch, Proxy, QueueHandle, }; use wayland_protocols::wp::cursor_shape::v1::client::{ wp_cursor_shape_device_v1, wp_cursor_shape_manager_v1, }; use wayland_protocols::wp::fractional_scale::v1::client::{ wp_fractional_scale_manager_v1, wp_fractional_scale_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_surface, xdg_toplevel, xdg_wm_base}; use wayland_protocols_plasma::blur::client::{org_kde_kwin_blur, org_kde_kwin_blur_manager}; use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1; use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS}; use super::display::WaylandDisplay; use super::window::{ImeInput, WaylandWindowStatePtr}; use crate::platform::linux::{ get_xkb_compose_state, is_within_click_distance, open_uri_internal, read_fd, reveal_path_internal, wayland::{ clipboard::{Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPE}, cursor::Cursor, serial::{SerialKind, SerialTracker}, window::WaylandWindow, }, xdg_desktop_portal::{Event as XDPEvent, XDPEventSource}, LinuxClient, }; use crate::platform::{blade::BladeContext, PlatformWindow}; use crate::{ point, px, size, AnyWindowHandle, Bounds, CursorStyle, DevicePixels, DisplayId, FileDropEvent, ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, LinuxCommon, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScaledPixels, ScrollDelta, ScrollWheelEvent, Size, TouchPhase, WindowParams, DOUBLE_CLICK_INTERVAL, SCROLL_LINES, }; /// Used to convert evdev scancode to xkb scancode const MIN_KEYCODE: u32 = 8; #[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 blur_manager: Option<org_kde_kwin_blur_manager::OrgKdeKwinBlurManager>, pub text_input_manager: Option<zwp_text_input_manager_v3::ZwpTextInputManagerV3>, pub executor: ForegroundExecutor, } impl Globals { fn new( globals: GlobalList, executor: ForegroundExecutor, qh: QueueHandle<WaylandClientStatePtr>, seat: wl_seat::WlSeat, ) -> Self { 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, 2..=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(), blur_manager: globals.bind(&qh, 1..=1, ()).ok(), text_input_manager: 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>>, } 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), }) } else { None } } } #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Output { pub name: Option<String>, pub scale: i32, pub bounds: Bounds<DevicePixels>, } pub(crate) struct WaylandClientState { serial_tracker: SerialTracker, globals: Globals, gpu_context: BladeContext, wl_seat: wl_seat::WlSeat, // TODO: Multi seat support wl_pointer: Option<wl_pointer::WlPointer>, 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, // Surface to Window mapping windows: HashMap<ObjectId, WaylandWindowStatePtr>, // Output to scale mapping outputs: HashMap<ObjectId, Output>, in_progress_outputs: HashMap<ObjectId, InProgressOutput>, keymap_state: Option<xkb::State>, compose_state: Option<xkb::compose::State>, drag: DragState, click: ClickState, repeat: KeyRepeat, pub modifiers: Modifiers, 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>, clipboard: Clipboard, data_offers: Vec<DataOffer<WlDataOffer>>, primary_data_offer: Option<DataOffer<ZwpPrimarySelectionOfferV1>>, cursor: Cursor, pending_activation: Option<PendingActivation>, event_loop: Option<EventLoop<'static, WaylandClientStatePtr>>, common: LinuxCommon, } 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), } /// 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) -> u32 { 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(); let Some(mut text_input) = state.text_input.take() else { return; }; text_input.enable(); text_input.set_content_type(ContentHint::None, ContentPurpose::Normal); if let Some(window) = state.keyboard_focused_window.clone() { drop(state); if let Some(area) = window.get_ime_area() { text_input.set_cursor_rectangle( area.origin.x.0 as i32, area.origin.y.0 as i32, area.size.width.0 as i32, area.size.height.0 as i32, ); } state = client.borrow_mut(); } text_input.commit(); state.text_input = Some(text_input); } pub fn disable_ime(&self) { let client = self.get_client(); let mut state = client.borrow_mut(); state.composing = false; if let Some(text_input) = &state.text_input { text_input.disable(); text_input.commit(); } } pub fn update_ime_position(&self, bounds: Bounds<ScaledPixels>) { let client = self.get_client(); let mut state = client.borrow_mut(); if state.composing || state.text_input.is_none() || state.pre_edit_text.is_some() { return; } let text_input = state.text_input.as_ref().unwrap(); text_input.set_cursor_rectangle( bounds.origin.x.0 as i32, bounds.origin.y.0 as i32, bounds.size.width.0 as i32, bounds.size.height.0 as i32, ); text_input.commit(); } pub fn drop_window(&self, surface_id: &ObjectId) { let mut client = self.get_client(); let mut state = client.borrow_mut(); let closed_window = state.windows.remove(surface_id).unwrap(); if let Some(window) = state.mouse_focused_window.take() { if !window.ptr_eq(&closed_window) { state.mouse_focused_window = Some(window); } } if let Some(window) = state.keyboard_focused_window.take() { if !window.ptr_eq(&closed_window) { state.keyboard_focused_window = Some(window); } } if state.windows.is_empty() { state.common.signal.stop(); } } } #[derive(Clone)] pub struct WaylandClient(Rc<RefCell<WaylandClientState>>); impl Drop for WaylandClient { fn drop(&mut self) { let mut state = self.0.borrow_mut(); state.windows.clear(); if let Some(wl_pointer) = &state.wl_pointer { wl_pointer.release(); } if let Some(cursor_shape_device) = &state.cursor_shape_device { cursor_shape_device.destroy(); } if let Some(data_device) = &state.data_device { data_device.release(); } if let Some(text_input) = &state.text_input { text_input.destroy(); } } } const WL_DATA_DEVICE_MANAGER_VERSION: u32 = 3; fn wl_seat_version(version: u32) -> u32 { // We rely on the wl_pointer.frame event const WL_SEAT_MIN_VERSION: u32 = 5; const WL_SEAT_MAX_VERSION: u32 = 9; if version < WL_SEAT_MIN_VERSION { panic!( "wl_seat below required version: {} < {}", version, WL_SEAT_MIN_VERSION ); } version.clamp(WL_SEAT_MIN_VERSION, WL_SEAT_MAX_VERSION) } fn wl_output_version(version: u32) -> u32 { const WL_OUTPUT_MIN_VERSION: u32 = 2; const WL_OUTPUT_MAX_VERSION: u32 = 4; if version < WL_OUTPUT_MIN_VERSION { panic!( "wl_output below required version: {} < {}", version, WL_OUTPUT_MIN_VERSION ); } version.clamp(WL_OUTPUT_MIN_VERSION, WL_OUTPUT_MAX_VERSION) } impl WaylandClient { pub(crate) fn new() -> Self { let conn = Connection::connect_to_env().unwrap(); let (globals, mut event_queue) = registry_queue_init::<WaylandClientStatePtr>(&conn).unwrap(); let qh = event_queue.handle(); let mut seat: Option<wl_seat::WlSeat> = None; #[allow(clippy::mutable_key_type)] let mut in_progress_outputs = HashMap::default(); globals.contents().with_list(|list| { for global in list { match &global.interface[..] { "wl_seat" => { seat = Some(globals.registry().bind::<wl_seat::WlSeat, _, _>( global.name, wl_seat_version(global.version), &qh, (), )); } "wl_output" => { let output = globals.registry().bind::<wl_output::WlOutput, _, _>( global.name, wl_output_version(global.version), &qh, (), ); in_progress_outputs.insert(output.id(), InProgressOutput::default()); } _ => {} } } }); let event_loop = EventLoop::<WaylandClientStatePtr>::try_new().unwrap(); 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 WaylandClientStatePtr| { if let calloop::channel::Event::Msg(runnable) = event { handle.insert_idle(|_| { runnable.run(); }); } } }) .unwrap(); let gpu_context = BladeContext::new().expect("Unable to init GPU context"); let seat = seat.unwrap(); let globals = Globals::new( globals, common.foreground_executor.clone(), qh.clone(), seat.clone(), ); let data_device = globals .data_device_manager .as_ref() .map(|data_device_manager| data_device_manager.get_data_device(&seat, &qh, ())); let primary_selection = globals .primary_selection_manager .as_ref() .map(|primary_selection_manager| primary_selection_manager.get_device(&seat, &qh, ())); let mut cursor = Cursor::new(&conn, &globals, 24); handle .insert_source(XDPEventSource::new(&common.background_executor), { move |event, _, client| match event { XDPEvent::WindowAppearance(appearance) => { if let Some(client) = client.0.upgrade() { let mut client = client.borrow_mut(); client.common.appearance = appearance; for (_, window) in &mut client.windows { window.set_appearance(appearance); } } }