use core::str;use std::cell::RefCell;use std::collections::{BTreeMap, HashSet};use std::ops::Deref;use std::path::PathBuf;use std::rc::{Rc, Weak};use std::time::{Duration, Instant}; use calloop::generic::{FdWrapper, Generic};use calloop::{EventLoop, LoopHandle, RegistrationToken}; use anyhow::Context as _;use collections::HashMap;use http_client::Url;use smallvec::SmallVec;use util::ResultExt; use x11rb::connection::{Connection, RequestConnection};use x11rb::cursor;use x11rb::errors::ConnectionError;use x11rb::protocol::randr::ConnectionExt as _;use x11rb::protocol::xinput::ConnectionExt;use x11rb::protocol::xkb::ConnectionExt as _;use x11rb::protocol::xproto::{ AtomEnum, ChangeWindowAttributesAux, ClientMessageData, ClientMessageEvent, ConnectionExt as _, EventMask, KeyPressEvent,};use x11rb::protocol::{randr, render, xinput, xkb, xproto, Event};use x11rb::resource_manager::Database;use x11rb::wrapper::ConnectionExt as _;use x11rb::xcb_ffi::XCBConnection;use xim::{x11rb::X11rbClient, Client};use xim::{AttributeName, InputStyle};use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};use xkbcommon::xkb::{self as xkbc, LayoutIndex, ModMask}; use crate::platform::linux::LinuxClient;use crate::platform::{LinuxCommon, PlatformWindow};use crate::{ modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle, DisplayId, FileDropEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, Pixels, Platform, PlatformDisplay, PlatformInput, Point, RequestFrameOptions, ScaledPixels, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,}; use super::{ button_or_scroll_from_event_detail, get_valuator_axis_index, modifiers_from_state, pressed_button_from_mask, ButtonOrScroll, ScrollDirection,};use super::{X11Display, X11WindowStatePtr, XcbAtoms};use super::{XimCallbackEvent, XimHandler};use crate::platform::linux::platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES};use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};use crate::platform::linux::{ get_xkb_compose_state, is_within_click_distance, open_uri_internal, reveal_path_internal,}; /// 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; pub(crate) struct WindowRef { window: X11WindowStatePtr, refresh_event_token: RegistrationToken,} 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 }} #[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, Clone)]struct XKBStateNotiy { depressed_layout: LayoutIndex, latched_layout: LayoutIndex, locked_layout: LayoutIndex,} #[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) 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, previous_xkb_state: XKBStateNotiy, pub(crate) ximc: Option<X11rbClient<Rc<XCBConnection>>>, pub(crate) xim_handler: Option<XimHandler>, pub modifiers: Modifiers, 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, xproto::Cursor>, pointer_device_states: BTreeMap<xinput::DeviceId, PointerDeviceState>, pub(crate) common: LinuxCommon, pub(crate) clipboard: x11_clipboard::Clipboard, pub(crate) clipboard_item: Option<ClipboardItem>, pub(crate) xdnd_state: Xdnd,} #[derive(Clone)]pub struct X11ClientStatePtr(pub Weak<RefCell<X11ClientState>>); impl X11ClientStatePtr { fn get_client(&self) -> X11Client { X11Client(self.0.upgrade().expect("client already dropped")) } pub fn drop_window(&self, x_window: u32) { let client = self.get_client(); let mut state = client.0.borrow_mut(); if let Some(window_ref) = state.windows.remove(&x_window) { state.loop_handle.remove(window_ref.refresh_event_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); if state.windows.is_empty() { state.common.signal.stop(); } } pub fn update_ime_position(&self, bounds: Bounds<ScaledPixels>) { let client = self.get_client(); let mut state = client.0.borrow_mut(); if state.composing || state.ximc.is_none() { return; } let mut ximc = state.ximc.take().unwrap(); let xim_handler = state.xim_handler.take().unwrap(); 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(bounds.origin.x + bounds.size.width) as i16, y: u32::from(bounds.origin.y + 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(Rc<RefCell<X11ClientState>>); impl X11Client { pub(crate) fn new() -> Self { let event_loop = EventLoop::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 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(|_| { runnable.run(); }); } } }) .unwrap(); let (xcb_connection, x_root_index) = XCBConnection::connect(None).unwrap(); xcb_connection .prefetch_extension_information(xkb::X11_EXTENSION_NAME) .unwrap(); xcb_connection .prefetch_extension_information(randr::X11_EXTENSION_NAME) .unwrap(); xcb_connection .prefetch_extension_information(render::X11_EXTENSION_NAME) .unwrap(); xcb_connection .prefetch_extension_information(xinput::X11_EXTENSION_NAME) .unwrap(); // Announce to X server that XInput up to 2.1 is supported. To increase this to 2.2 and // beyond, support for touch events would need to be added. let xinput_version = xcb_connection .xinput_xi_query_version(2, 1) .unwrap() .reply() .unwrap(); // XInput 1.x is not supported. assert!( xinput_version.major_version >= 2, "XInput version >= 2 required." ); let pointer_device_states = get_new_pointer_device_states(&xcb_connection, &BTreeMap::new()); let atoms = XcbAtoms::new(&xcb_connection).unwrap().reply().unwrap(); 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 = xcb_connection .xkb_use_extension(XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION) .unwrap() .reply() .unwrap(); let events = xkb::EventType::STATE_NOTIFY | xkb::EventType::MAP_NOTIFY | xkb::EventType::NEW_KEYBOARD_NOTIFY; xcb_connection .xkb_select_events( xkb::ID::USE_CORE_KBD.into(), 0u8.into(), events, 0u8.into(), 0u8.into(), &xkb::SelectEventsAux::new(), ) .unwrap(); assert!(xkb.supported); 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 resource_database = x11rb::resource_manager::new_from_default(&xcb_connection).unwrap(); let scale_factor = resource_database .get_value("Xft.dpi", "Xft.dpi") .ok() .flatten() .map(|dpi: f32| dpi / 96.0) .unwrap_or(1.0); let cursor_handle = cursor::Handle::new(&xcb_connection, x_root_index, &resource_database) .unwrap() .reply() .unwrap(); let clipboard = x11_clipboard::Clipboard::new().unwrap(); 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) } }, ) .expect("Failed to initialize x11 event source"); 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 &mut client.0.borrow_mut().windows { window.window.set_appearance(appearance); } } XDPEvent::CursorTheme(_) | XDPEvent::CursorSize(_) => { // noop, X11 manages this for us. } } }) .unwrap(); X11Client(Rc::new(RefCell::new(X11ClientState { modifiers: Modifiers::default(), event_loop: Some(event_loop), loop_handle: handle, common, last_click: Instant::now(), last_mouse_button: None, last_location: Point::new(px(0.0), px(0.0)), current_count: 0, scale_factor, xkb_context, xcb_connection, xkb_device_id, client_side_decorations_supported, x_root_index, _resource_database: resource_database, atoms, windows: HashMap::default(), mouse_focused_window: None, keyboard_focused_window: None, xkb: xkb_state, previous_xkb_state: XKBStateNotiy::default(), ximc, xim_handler, compose_state, pre_edit_text: None, pre_key_char_down: None, composing: false, cursor_handle, cursor_styles: HashMap::default(), cursor_cache: HashMap::default(), pointer_device_states, clipboard, clipboard_item: None, xdnd_state: Xdnd::default(), }))) } pub fn process_x11_events( &self, xcb_connection: &XCBConnection, ) -> Result<(), EventHandlerError> { loop { let mut events = Vec::new(); let mut windows_to_refresh = HashSet::new(); let mut last_key_release = None; let mut last_key_press: Option<KeyPressEvent> = None; loop { match xcb_connection.poll_for_event() { Ok(Some(event)) => { match event { Event::Expose(expose_event) => { windows_to_refresh.insert(expose_event.window); } Event::KeyRelease(_) => { last_key_release = Some(event); } Event::KeyPress(key_press) => { if let Some(last_press) = last_key_press.as_ref() { if last_press.detail == key_press.detail { continue; } } if let Some(Event::KeyRelease(key_release)) = last_key_release.take() { // We ignore that last KeyRelease if it's too close to this KeyPress, // suggesting that it's auto-generated by X11 as a key-repeat event. if key_release.detail != key_press.detail || key_press.time.saturating_sub(key_release.time) > 20 {Lines 1–500 of 1898. Load more
use core::str; use std::cell::RefCell; use std::collections::{BTreeMap, HashSet}; use std::ops::Deref; use std::path::PathBuf; use std::rc::{Rc, Weak}; use std::time::{Duration, Instant}; use calloop::generic::{FdWrapper, Generic}; use calloop::{EventLoop, LoopHandle, RegistrationToken}; use anyhow::Context as _; use collections::HashMap; use http_client::Url; use smallvec::SmallVec; use util::ResultExt; use x11rb::connection::{Connection, RequestConnection}; use x11rb::cursor; use x11rb::errors::ConnectionError; use x11rb::protocol::randr::ConnectionExt as _; use x11rb::protocol::xinput::ConnectionExt; use x11rb::protocol::xkb::ConnectionExt as _; use x11rb::protocol::xproto::{ AtomEnum, ChangeWindowAttributesAux, ClientMessageData, ClientMessageEvent, ConnectionExt as _, EventMask, KeyPressEvent, }; use x11rb::protocol::{randr, render, xinput, xkb, xproto, Event}; use x11rb::resource_manager::Database; use x11rb::wrapper::ConnectionExt as _; use x11rb::xcb_ffi::XCBConnection; use xim::{x11rb::X11rbClient, Client}; use xim::{AttributeName, InputStyle}; use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION}; use xkbcommon::xkb::{self as xkbc, LayoutIndex, ModMask}; use crate::platform::linux::LinuxClient; use crate::platform::{LinuxCommon, PlatformWindow}; use crate::{ modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle, DisplayId, FileDropEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, Pixels, Platform, PlatformDisplay, PlatformInput, Point, RequestFrameOptions, ScaledPixels, ScrollDelta, Size, TouchPhase, WindowParams, X11Window, }; use super::{ button_or_scroll_from_event_detail, get_valuator_axis_index, modifiers_from_state, pressed_button_from_mask, ButtonOrScroll, ScrollDirection, }; use super::{X11Display, X11WindowStatePtr, XcbAtoms}; use super::{XimCallbackEvent, XimHandler}; use crate::platform::linux::platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES}; use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource}; use crate::platform::linux::{ get_xkb_compose_state, is_within_click_distance, open_uri_internal, reveal_path_internal, }; /// 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; pub(crate) struct WindowRef { window: X11WindowStatePtr, refresh_event_token: RegistrationToken, } 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 } } #[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, Clone)] struct XKBStateNotiy { depressed_layout: LayoutIndex, latched_layout: LayoutIndex, locked_layout: LayoutIndex, } #[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) 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, previous_xkb_state: XKBStateNotiy, pub(crate) ximc: Option<X11rbClient<Rc<XCBConnection>>>, pub(crate) xim_handler: Option<XimHandler>, pub modifiers: Modifiers, 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, xproto::Cursor>, pointer_device_states: BTreeMap<xinput::DeviceId, PointerDeviceState>, pub(crate) common: LinuxCommon, pub(crate) clipboard: x11_clipboard::Clipboard, pub(crate) clipboard_item: Option<ClipboardItem>, pub(crate) xdnd_state: Xdnd, } #[derive(Clone)] pub struct X11ClientStatePtr(pub Weak<RefCell<X11ClientState>>); impl X11ClientStatePtr { fn get_client(&self) -> X11Client { X11Client(self.0.upgrade().expect("client already dropped")) } pub fn drop_window(&self, x_window: u32) { let client = self.get_client(); let mut state = client.0.borrow_mut(); if let Some(window_ref) = state.windows.remove(&x_window) { state.loop_handle.remove(window_ref.refresh_event_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); if state.windows.is_empty() { state.common.signal.stop(); } } pub fn update_ime_position(&self, bounds: Bounds<ScaledPixels>) { let client = self.get_client(); let mut state = client.0.borrow_mut(); if state.composing || state.ximc.is_none() { return; } let mut ximc = state.ximc.take().unwrap(); let xim_handler = state.xim_handler.take().unwrap(); 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(bounds.origin.x + bounds.size.width) as i16, y: u32::from(bounds.origin.y + 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(Rc<RefCell<X11ClientState>>); impl X11Client { pub(crate) fn new() -> Self { let event_loop = EventLoop::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 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(|_| { runnable.run(); }); } } }) .unwrap(); let (xcb_connection, x_root_index) = XCBConnection::connect(None).unwrap(); xcb_connection .prefetch_extension_information(xkb::X11_EXTENSION_NAME) .unwrap(); xcb_connection .prefetch_extension_information(randr::X11_EXTENSION_NAME) .unwrap(); xcb_connection .prefetch_extension_information(render::X11_EXTENSION_NAME) .unwrap(); xcb_connection .prefetch_extension_information(xinput::X11_EXTENSION_NAME) .unwrap(); // Announce to X server that XInput up to 2.1 is supported. To increase this to 2.2 and // beyond, support for touch events would need to be added. let xinput_version = xcb_connection .xinput_xi_query_version(2, 1) .unwrap() .reply() .unwrap(); // XInput 1.x is not supported. assert!( xinput_version.major_version >= 2, "XInput version >= 2 required." ); let pointer_device_states = get_new_pointer_device_states(&xcb_connection, &BTreeMap::new()); let atoms = XcbAtoms::new(&xcb_connection).unwrap().reply().unwrap(); 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 = xcb_connection .xkb_use_extension(XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION) .unwrap() .reply() .unwrap(); let events = xkb::EventType::STATE_NOTIFY | xkb::EventType::MAP_NOTIFY | xkb::EventType::NEW_KEYBOARD_NOTIFY; xcb_connection .xkb_select_events( xkb::ID::USE_CORE_KBD.into(), 0u8.into(), events, 0u8.into(), 0u8.into(), &xkb::SelectEventsAux::new(), ) .unwrap(); assert!(xkb.supported); 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 resource_database = x11rb::resource_manager::new_from_default(&xcb_connection).unwrap(); let scale_factor = resource_database .get_value("Xft.dpi", "Xft.dpi") .ok() .flatten() .map(|dpi: f32| dpi / 96.0) .unwrap_or(1.0); let cursor_handle = cursor::Handle::new(&xcb_connection, x_root_index, &resource_database) .unwrap() .reply() .unwrap(); let clipboard = x11_clipboard::Clipboard::new().unwrap(); 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) } }, ) .expect("Failed to initialize x11 event source"); 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 &mut client.0.borrow_mut().windows { window.window.set_appearance(appearance); } } XDPEvent::CursorTheme(_) | XDPEvent::CursorSize(_) => { // noop, X11 manages this for us. } } }) .unwrap(); X11Client(Rc::new(RefCell::new(X11ClientState { modifiers: Modifiers::default(), event_loop: Some(event_loop), loop_handle: handle, common, last_click: Instant::now(), last_mouse_button: None, last_location: Point::new(px(0.0), px(0.0)), current_count: 0, scale_factor, xkb_context, xcb_connection, xkb_device_id, client_side_decorations_supported, x_root_index, _resource_database: resource_database, atoms, windows: HashMap::default(), mouse_focused_window: None, keyboard_focused_window: None, xkb: xkb_state, previous_xkb_state: XKBStateNotiy::default(), ximc, xim_handler, compose_state, pre_edit_text: None, pre_key_char_down: None, composing: false, cursor_handle, cursor_styles: HashMap::default(), cursor_cache: HashMap::default(), pointer_device_states, clipboard, clipboard_item: None, xdnd_state: Xdnd::default(), }))) } pub fn process_x11_events( &self, xcb_connection: &XCBConnection, ) -> Result<(), EventHandlerError> { loop { let mut events = Vec::new(); let mut windows_to_refresh = HashSet::new(); let mut last_key_release = None; let mut last_key_press: Option<KeyPressEvent> = None; loop { match xcb_connection.poll_for_event() { Ok(Some(event)) => { match event { Event::Expose(expose_event) => { windows_to_refresh.insert(expose_event.window); } Event::KeyRelease(_) => { last_key_release = Some(event); } Event::KeyPress(key_press) => { if let Some(last_press) = last_key_press.as_ref() { if last_press.detail == key_press.detail { continue; } } if let Some(Event::KeyRelease(key_release)) = last_key_release.take() { // We ignore that last KeyRelease if it's too close to this KeyPress, // suggesting that it's auto-generated by X11 as a key-repeat event. if key_release.detail != key_press.detail || key_press.time.saturating_sub(key_release.time) > 20 {