1940 lines · 71.1 KB · Rust
RawDownload
use core::str;use std::{    cell::RefCell,    collections::{BTreeMap, HashSet},    ops::Deref,    path::PathBuf,    rc::{Rc, Weak},    time::{Duration, Instant},}; use calloop::{    generic::{FdWrapper, Generic},    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},    cursor,    errors::ConnectionError,    protocol::randr::ConnectionExt as _,    protocol::xinput::ConnectionExt,    protocol::xkb::ConnectionExt as _,    protocol::xproto::{        AtomEnum, ChangeWindowAttributesAux, ClientMessageData, ClientMessageEvent,        ConnectionExt as _, EventMask, KeyPressEvent,    },    protocol::{randr, render, xinput, xkb, xproto, Event},    resource_manager::Database,    wrapper::ConnectionExt as _,    xcb_ffi::XCBConnection,};use xim::{x11rb::X11rbClient, AttributeName, Client, 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, STATE_LAYOUT_EFFECTIVE}; 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::{    blade::BladeContext,    linux::{        get_xkb_compose_state, is_within_click_distance, open_uri_internal,        platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES},        reveal_path_internal,        xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},        LinuxClient,    },    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,}; /// 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,     gpu_context: BladeContext,     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,    // TODO: Can the other updates to `modifiers` be removed so that this is unnecessary?    pub last_modifiers_changed_event: 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 gpu_context = BladeContext::new().expect("Unable to init GPU context");         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(),            last_modifiers_changed_event: 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,            gpu_context,            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(_) => {

Lines 1–500 of 1940.