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. Load more
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. } }