client.rs

   1use std::cell::RefCell;
   2use std::ffi::OsString;
   3use std::ops::Deref;
   4use std::rc::{Rc, Weak};
   5use std::time::{Duration, Instant};
   6
   7use calloop::generic::{FdWrapper, Generic};
   8use calloop::{EventLoop, LoopHandle, RegistrationToken};
   9
  10use collections::HashMap;
  11use util::ResultExt;
  12
  13use x11rb::connection::{Connection, RequestConnection};
  14use x11rb::cursor;
  15use x11rb::errors::ConnectionError;
  16use x11rb::protocol::xinput::ConnectionExt;
  17use x11rb::protocol::xkb::ConnectionExt as _;
  18use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _};
  19use x11rb::protocol::{randr, render, xinput, xkb, xproto, Event};
  20use x11rb::resource_manager::Database;
  21use x11rb::xcb_ffi::XCBConnection;
  22use xim::{x11rb::X11rbClient, Client};
  23use xim::{AttributeName, InputStyle};
  24use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
  25use xkbcommon::xkb as xkbc;
  26
  27use crate::platform::linux::LinuxClient;
  28use crate::platform::{LinuxCommon, PlatformWindow};
  29use crate::{
  30    modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, CursorStyle, DisplayId,
  31    Keystroke, Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput, Point,
  32    ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
  33};
  34
  35use super::{
  36    super::{open_uri_internal, SCROLL_LINES},
  37    X11Display, X11WindowStatePtr, XcbAtoms,
  38};
  39use super::{button_of_key, modifiers_from_state, pressed_button_from_mask};
  40use super::{XimCallbackEvent, XimHandler};
  41use crate::platform::linux::is_within_click_distance;
  42use crate::platform::linux::platform::DOUBLE_CLICK_INTERVAL;
  43use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
  44
  45pub(super) const XINPUT_MASTER_DEVICE: u16 = 1;
  46
  47pub(crate) struct WindowRef {
  48    pub window: X11WindowStatePtr,
  49    refresh_event_token: RegistrationToken,
  50}
  51
  52impl WindowRef {
  53    pub fn handle(&self) -> AnyWindowHandle {
  54        self.window.state.borrow().handle
  55    }
  56}
  57
  58impl Deref for WindowRef {
  59    type Target = X11WindowStatePtr;
  60
  61    fn deref(&self) -> &Self::Target {
  62        &self.window
  63    }
  64}
  65
  66#[derive(Debug)]
  67#[non_exhaustive]
  68pub enum EventHandlerError {
  69    XCBConnectionError(ConnectionError),
  70    XIMClientError(xim::ClientError),
  71}
  72
  73impl std::error::Error for EventHandlerError {}
  74
  75impl std::fmt::Display for EventHandlerError {
  76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  77        match self {
  78            EventHandlerError::XCBConnectionError(err) => err.fmt(f),
  79            EventHandlerError::XIMClientError(err) => err.fmt(f),
  80        }
  81    }
  82}
  83
  84impl From<ConnectionError> for EventHandlerError {
  85    fn from(err: ConnectionError) -> Self {
  86        EventHandlerError::XCBConnectionError(err)
  87    }
  88}
  89
  90impl From<xim::ClientError> for EventHandlerError {
  91    fn from(err: xim::ClientError) -> Self {
  92        EventHandlerError::XIMClientError(err)
  93    }
  94}
  95
  96pub struct X11ClientState {
  97    pub(crate) loop_handle: LoopHandle<'static, X11Client>,
  98    pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,
  99
 100    pub(crate) last_click: Instant,
 101    pub(crate) last_location: Point<Pixels>,
 102    pub(crate) current_count: usize,
 103
 104    pub(crate) scale_factor: f32,
 105
 106    pub(crate) xcb_connection: Rc<XCBConnection>,
 107    pub(crate) x_root_index: usize,
 108    pub(crate) _resource_database: Database,
 109    pub(crate) atoms: XcbAtoms,
 110    pub(crate) windows: HashMap<xproto::Window, WindowRef>,
 111    pub(crate) focused_window: Option<xproto::Window>,
 112    pub(crate) xkb: xkbc::State,
 113    pub(crate) ximc: Option<X11rbClient<Rc<XCBConnection>>>,
 114    pub(crate) xim_handler: Option<XimHandler>,
 115    pub modifiers: Modifiers,
 116
 117    pub(crate) compose_state: xkbc::compose::State,
 118    pub(crate) pre_edit_text: Option<String>,
 119    pub(crate) composing: bool,
 120    pub(crate) cursor_handle: cursor::Handle,
 121    pub(crate) cursor_styles: HashMap<xproto::Window, CursorStyle>,
 122    pub(crate) cursor_cache: HashMap<CursorStyle, xproto::Cursor>,
 123
 124    pub(crate) scroll_class_data: Vec<xinput::DeviceClassDataScroll>,
 125    pub(crate) scroll_x: Option<f32>,
 126    pub(crate) scroll_y: Option<f32>,
 127
 128    pub(crate) common: LinuxCommon,
 129    pub(crate) clipboard: x11_clipboard::Clipboard,
 130}
 131
 132#[derive(Clone)]
 133pub struct X11ClientStatePtr(pub Weak<RefCell<X11ClientState>>);
 134
 135impl X11ClientStatePtr {
 136    pub fn drop_window(&self, x_window: u32) {
 137        let client = X11Client(self.0.upgrade().expect("client already dropped"));
 138        let mut state = client.0.borrow_mut();
 139
 140        if let Some(window_ref) = state.windows.remove(&x_window) {
 141            state.loop_handle.remove(window_ref.refresh_event_token);
 142        }
 143
 144        state.cursor_styles.remove(&x_window);
 145
 146        if state.windows.is_empty() {
 147            state.common.signal.stop();
 148        }
 149    }
 150}
 151
 152#[derive(Clone)]
 153pub(crate) struct X11Client(Rc<RefCell<X11ClientState>>);
 154
 155impl X11Client {
 156    pub(crate) fn new() -> Self {
 157        let event_loop = EventLoop::try_new().unwrap();
 158
 159        let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
 160
 161        let handle = event_loop.handle();
 162
 163        handle
 164            .insert_source(main_receiver, {
 165                let handle = handle.clone();
 166                move |event, _, _: &mut X11Client| {
 167                    if let calloop::channel::Event::Msg(runnable) = event {
 168                        // Insert the runnables as idle callbacks, so we make sure that user-input and X11
 169                        // events have higher priority and runnables are only worked off after the event
 170                        // callbacks.
 171                        handle.insert_idle(|_| {
 172                            runnable.run();
 173                        });
 174                    }
 175                }
 176            })
 177            .unwrap();
 178
 179        let (xcb_connection, x_root_index) = XCBConnection::connect(None).unwrap();
 180        xcb_connection
 181            .prefetch_extension_information(xkb::X11_EXTENSION_NAME)
 182            .unwrap();
 183        xcb_connection
 184            .prefetch_extension_information(randr::X11_EXTENSION_NAME)
 185            .unwrap();
 186        xcb_connection
 187            .prefetch_extension_information(render::X11_EXTENSION_NAME)
 188            .unwrap();
 189        xcb_connection
 190            .prefetch_extension_information(xinput::X11_EXTENSION_NAME)
 191            .unwrap();
 192
 193        let xinput_version = xcb_connection
 194            .xinput_xi_query_version(2, 0)
 195            .unwrap()
 196            .reply()
 197            .unwrap();
 198        assert!(
 199            xinput_version.major_version >= 2,
 200            "XInput Extension v2 not supported."
 201        );
 202
 203        let master_device_query = xcb_connection
 204            .xinput_xi_query_device(XINPUT_MASTER_DEVICE)
 205            .unwrap()
 206            .reply()
 207            .unwrap();
 208        let scroll_class_data = master_device_query
 209            .infos
 210            .iter()
 211            .find(|info| info.type_ == xinput::DeviceType::MASTER_POINTER)
 212            .unwrap()
 213            .classes
 214            .iter()
 215            .filter_map(|class| class.data.as_scroll())
 216            .map(|class| *class)
 217            .collect::<Vec<_>>();
 218
 219        let atoms = XcbAtoms::new(&xcb_connection).unwrap();
 220        let xkb = xcb_connection
 221            .xkb_use_extension(XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION)
 222            .unwrap();
 223
 224        let atoms = atoms.reply().unwrap();
 225        let xkb = xkb.reply().unwrap();
 226        let events = xkb::EventType::STATE_NOTIFY;
 227        xcb_connection
 228            .xkb_select_events(
 229                xkb::ID::USE_CORE_KBD.into(),
 230                0u8.into(),
 231                events,
 232                0u8.into(),
 233                0u8.into(),
 234                &xkb::SelectEventsAux::new(),
 235            )
 236            .unwrap();
 237        assert!(xkb.supported);
 238
 239        let xkb_context = xkbc::Context::new(xkbc::CONTEXT_NO_FLAGS);
 240        let xkb_state = {
 241            let xkb_device_id = xkbc::x11::get_core_keyboard_device_id(&xcb_connection);
 242            let xkb_keymap = xkbc::x11::keymap_new_from_device(
 243                &xkb_context,
 244                &xcb_connection,
 245                xkb_device_id,
 246                xkbc::KEYMAP_COMPILE_NO_FLAGS,
 247            );
 248            xkbc::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id)
 249        };
 250        let compose_state = {
 251            let locale = std::env::var_os("LC_CTYPE").unwrap_or(OsString::from("C"));
 252            let table = xkbc::compose::Table::new_from_locale(
 253                &xkb_context,
 254                &locale,
 255                xkbc::compose::COMPILE_NO_FLAGS,
 256            )
 257            .log_err()
 258            .unwrap();
 259            xkbc::compose::State::new(&table, xkbc::compose::STATE_NO_FLAGS)
 260        };
 261
 262        let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection).unwrap();
 263
 264        let scale_factor = resource_database
 265            .get_value("Xft.dpi", "Xft.dpi")
 266            .ok()
 267            .flatten()
 268            .map(|dpi: f32| dpi / 96.0)
 269            .unwrap_or(1.0);
 270
 271        let cursor_handle = cursor::Handle::new(&xcb_connection, x_root_index, &resource_database)
 272            .unwrap()
 273            .reply()
 274            .unwrap();
 275
 276        let clipboard = x11_clipboard::Clipboard::new().unwrap();
 277
 278        let xcb_connection = Rc::new(xcb_connection);
 279
 280        let ximc = X11rbClient::init(Rc::clone(&xcb_connection), x_root_index, None).ok();
 281        let xim_handler = if ximc.is_some() {
 282            Some(XimHandler::new())
 283        } else {
 284            None
 285        };
 286
 287        // Safety: Safe if xcb::Connection always returns a valid fd
 288        let fd = unsafe { FdWrapper::new(Rc::clone(&xcb_connection)) };
 289
 290        handle
 291            .insert_source(
 292                Generic::new_with_error::<EventHandlerError>(
 293                    fd,
 294                    calloop::Interest::BOTH,
 295                    calloop::Mode::Level,
 296                ),
 297                {
 298                    let xcb_connection = xcb_connection.clone();
 299                    move |_readiness, _, client| {
 300                        let windows = client
 301                            .0
 302                            .borrow()
 303                            .windows
 304                            .values()
 305                            .map(|window_ref| window_ref.window.clone())
 306                            .collect::<Vec<_>>();
 307
 308                        while let Some(event) = xcb_connection.poll_for_event()? {
 309                            for window in &windows {
 310                                let last_render_at;
 311                                let refresh_rate;
 312                                {
 313                                    let window_state = window.state.borrow();
 314                                    last_render_at = window_state.last_render_at;
 315                                    refresh_rate = window_state.refresh_rate;
 316                                }
 317
 318                                if let Some(last_render_at) = last_render_at {
 319                                    if last_render_at.elapsed() >= refresh_rate {
 320                                        window.refresh();
 321                                    }
 322                                } else {
 323                                    window.refresh();
 324                                }
 325                            }
 326
 327                            let mut state = client.0.borrow_mut();
 328                            if state.ximc.is_none() || state.xim_handler.is_none() {
 329                                drop(state);
 330                                client.handle_event(event);
 331                                continue;
 332                            }
 333
 334                            let mut ximc = state.ximc.take().unwrap();
 335                            let mut xim_handler = state.xim_handler.take().unwrap();
 336                            let xim_connected = xim_handler.connected;
 337                            drop(state);
 338
 339                            let xim_filtered = match ximc.filter_event(&event, &mut xim_handler) {
 340                                Ok(handled) => handled,
 341                                Err(err) => {
 342                                    log::error!("XIMClientError: {}", err);
 343                                    false
 344                                }
 345                            };
 346                            let xim_callback_event = xim_handler.last_callback_event.take();
 347
 348                            let mut state = client.0.borrow_mut();
 349                            state.ximc = Some(ximc);
 350                            state.xim_handler = Some(xim_handler);
 351                            drop(state);
 352
 353                            if let Some(event) = xim_callback_event {
 354                                client.handle_xim_callback_event(event);
 355                            }
 356
 357                            if xim_filtered {
 358                                continue;
 359                            }
 360
 361                            if xim_connected {
 362                                client.xim_handle_event(event);
 363                            } else {
 364                                client.handle_event(event);
 365                            }
 366                        }
 367
 368                        Ok(calloop::PostAction::Continue)
 369                    }
 370                },
 371            )
 372            .expect("Failed to initialize x11 event source");
 373
 374        handle
 375            .insert_source(XDPEventSource::new(&common.background_executor), {
 376                move |event, _, client| match event {
 377                    XDPEvent::WindowAppearance(appearance) => {
 378                        client.with_common(|common| common.appearance = appearance);
 379                        for (_, window) in &mut client.0.borrow_mut().windows {
 380                            window.window.set_appearance(appearance);
 381                        }
 382                    }
 383                    XDPEvent::CursorTheme(_) | XDPEvent::CursorSize(_) => {
 384                        // noop, X11 manages this for us.
 385                    }
 386                }
 387            })
 388            .unwrap();
 389
 390        X11Client(Rc::new(RefCell::new(X11ClientState {
 391            modifiers: Modifiers::default(),
 392            event_loop: Some(event_loop),
 393            loop_handle: handle,
 394            common,
 395            last_click: Instant::now(),
 396            last_location: Point::new(px(0.0), px(0.0)),
 397            current_count: 0,
 398            scale_factor,
 399
 400            xcb_connection,
 401            x_root_index,
 402            _resource_database: resource_database,
 403            atoms,
 404            windows: HashMap::default(),
 405            focused_window: None,
 406            xkb: xkb_state,
 407            ximc,
 408            xim_handler,
 409
 410            compose_state: compose_state,
 411            pre_edit_text: None,
 412            composing: false,
 413
 414            cursor_handle,
 415            cursor_styles: HashMap::default(),
 416            cursor_cache: HashMap::default(),
 417
 418            scroll_class_data,
 419            scroll_x: None,
 420            scroll_y: None,
 421
 422            clipboard,
 423        })))
 424    }
 425
 426    pub fn enable_ime(&self) {
 427        let mut state = self.0.borrow_mut();
 428        if state.ximc.is_none() {
 429            return;
 430        }
 431
 432        let mut ximc = state.ximc.take().unwrap();
 433        let mut xim_handler = state.xim_handler.take().unwrap();
 434        let mut ic_attributes = ximc
 435            .build_ic_attributes()
 436            .push(
 437                AttributeName::InputStyle,
 438                InputStyle::PREEDIT_CALLBACKS
 439                    | InputStyle::STATUS_NOTHING
 440                    | InputStyle::PREEDIT_NONE,
 441            )
 442            .push(AttributeName::ClientWindow, xim_handler.window)
 443            .push(AttributeName::FocusWindow, xim_handler.window);
 444
 445        let window_id = state.focused_window;
 446        drop(state);
 447        if let Some(window_id) = window_id {
 448            let window = self.get_window(window_id).unwrap();
 449            if let Some(area) = window.get_ime_area() {
 450                ic_attributes =
 451                    ic_attributes.nested_list(xim::AttributeName::PreeditAttributes, |b| {
 452                        b.push(
 453                            xim::AttributeName::SpotLocation,
 454                            xim::Point {
 455                                x: u32::from(area.origin.x + area.size.width) as i16,
 456                                y: u32::from(area.origin.y + area.size.height) as i16,
 457                            },
 458                        );
 459                    });
 460            }
 461        }
 462        ximc.create_ic(xim_handler.im_id, ic_attributes.build())
 463            .ok();
 464        state = self.0.borrow_mut();
 465        state.xim_handler = Some(xim_handler);
 466        state.ximc = Some(ximc);
 467    }
 468
 469    pub fn disable_ime(&self) {
 470        let mut state = self.0.borrow_mut();
 471        state.composing = false;
 472        if let Some(mut ximc) = state.ximc.take() {
 473            let xim_handler = state.xim_handler.as_ref().unwrap();
 474            ximc.destroy_ic(xim_handler.im_id, xim_handler.ic_id).ok();
 475            state.ximc = Some(ximc);
 476        }
 477    }
 478
 479    fn get_window(&self, win: xproto::Window) -> Option<X11WindowStatePtr> {
 480        let state = self.0.borrow();
 481        state
 482            .windows
 483            .get(&win)
 484            .filter(|window_reference| !window_reference.window.state.borrow().destroyed)
 485            .map(|window_reference| window_reference.window.clone())
 486    }
 487
 488    fn handle_event(&self, event: Event) -> Option<()> {
 489        match event {
 490            Event::ClientMessage(event) => {
 491                let window = self.get_window(event.window)?;
 492                let [atom, ..] = event.data.as_data32();
 493                let mut state = self.0.borrow_mut();
 494
 495                if atom == state.atoms.WM_DELETE_WINDOW {
 496                    // window "x" button clicked by user
 497                    if window.should_close() {
 498                        // Rest of the close logic is handled in drop_window()
 499                        window.close();
 500                    }
 501                }
 502            }
 503            Event::ConfigureNotify(event) => {
 504                let bounds = Bounds {
 505                    origin: Point {
 506                        x: event.x.into(),
 507                        y: event.y.into(),
 508                    },
 509                    size: Size {
 510                        width: event.width.into(),
 511                        height: event.height.into(),
 512                    },
 513                };
 514                let window = self.get_window(event.window)?;
 515                window.configure(bounds);
 516            }
 517            Event::Expose(event) => {
 518                let window = self.get_window(event.window)?;
 519                window.refresh();
 520            }
 521            Event::FocusIn(event) => {
 522                let window = self.get_window(event.event)?;
 523                window.set_focused(true);
 524                let mut state = self.0.borrow_mut();
 525                state.focused_window = Some(event.event);
 526                drop(state);
 527                self.enable_ime();
 528            }
 529            Event::FocusOut(event) => {
 530                let window = self.get_window(event.event)?;
 531                window.set_focused(false);
 532                let mut state = self.0.borrow_mut();
 533                state.focused_window = None;
 534                state.compose_state.reset();
 535                state.pre_edit_text.take();
 536                drop(state);
 537                self.disable_ime();
 538                window.handle_ime_delete();
 539            }
 540            Event::XkbStateNotify(event) => {
 541                let mut state = self.0.borrow_mut();
 542                state.xkb.update_mask(
 543                    event.base_mods.into(),
 544                    event.latched_mods.into(),
 545                    event.locked_mods.into(),
 546                    0,
 547                    0,
 548                    event.locked_group.into(),
 549                );
 550
 551                let modifiers = Modifiers::from_xkb(&state.xkb);
 552                if state.modifiers == modifiers {
 553                    drop(state);
 554                } else {
 555                    let focused_window_id = state.focused_window?;
 556                    state.modifiers = modifiers;
 557                    drop(state);
 558
 559                    let focused_window = self.get_window(focused_window_id)?;
 560                    focused_window.handle_input(PlatformInput::ModifiersChanged(
 561                        ModifiersChangedEvent { modifiers },
 562                    ));
 563                }
 564            }
 565            Event::KeyPress(event) => {
 566                let window = self.get_window(event.event)?;
 567                let mut state = self.0.borrow_mut();
 568
 569                let modifiers = modifiers_from_state(event.state);
 570                state.modifiers = modifiers;
 571
 572                let keystroke = {
 573                    let code = event.detail.into();
 574                    let mut keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
 575                    state.xkb.update_key(code, xkbc::KeyDirection::Down);
 576                    let keysym = state.xkb.key_get_one_sym(code);
 577                    if keysym.is_modifier_key() {
 578                        return Some(());
 579                    }
 580                    state.compose_state.feed(keysym);
 581                    match state.compose_state.status() {
 582                        xkbc::Status::Composed => {
 583                            state.pre_edit_text.take();
 584                            keystroke.ime_key = state.compose_state.utf8();
 585                            keystroke.key =
 586                                xkbc::keysym_get_name(state.compose_state.keysym().unwrap());
 587                        }
 588                        xkbc::Status::Composing => {
 589                            state.pre_edit_text = state
 590                                .compose_state
 591                                .utf8()
 592                                .or(crate::Keystroke::underlying_dead_key(keysym));
 593                            let pre_edit = state.pre_edit_text.clone().unwrap_or(String::default());
 594                            drop(state);
 595                            window.handle_ime_preedit(pre_edit);
 596                            state = self.0.borrow_mut();
 597                        }
 598                        xkbc::Status::Cancelled => {
 599                            let pre_edit = state.pre_edit_text.take();
 600                            drop(state);
 601                            if let Some(pre_edit) = pre_edit {
 602                                window.handle_ime_commit(pre_edit);
 603                            }
 604                            if let Some(current_key) = Keystroke::underlying_dead_key(keysym) {
 605                                window.handle_ime_preedit(current_key);
 606                            }
 607                            state = self.0.borrow_mut();
 608                            state.compose_state.feed(keysym);
 609                        }
 610                        _ => {}
 611                    }
 612                    keystroke
 613                };
 614                drop(state);
 615                window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
 616                    keystroke,
 617                    is_held: false,
 618                }));
 619            }
 620            Event::KeyRelease(event) => {
 621                let window = self.get_window(event.event)?;
 622                let mut state = self.0.borrow_mut();
 623
 624                let modifiers = modifiers_from_state(event.state);
 625                state.modifiers = modifiers;
 626
 627                let keystroke = {
 628                    let code = event.detail.into();
 629                    let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
 630                    state.xkb.update_key(code, xkbc::KeyDirection::Up);
 631                    let keysym = state.xkb.key_get_one_sym(code);
 632                    if keysym.is_modifier_key() {
 633                        return Some(());
 634                    }
 635                    keystroke
 636                };
 637                drop(state);
 638                window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke }));
 639            }
 640            Event::XinputButtonPress(event) => {
 641                let window = self.get_window(event.event)?;
 642                let mut state = self.0.borrow_mut();
 643
 644                let modifiers = modifiers_from_xinput_info(event.mods);
 645                state.modifiers = modifiers;
 646
 647                let position = point(
 648                    px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
 649                    px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
 650                );
 651
 652                if state.composing && state.ximc.is_some() {
 653                    drop(state);
 654                    self.disable_ime();
 655                    self.enable_ime();
 656                    window.handle_ime_unmark();
 657                    state = self.0.borrow_mut();
 658                } else if let Some(text) = state.pre_edit_text.take() {
 659                    state.compose_state.reset();
 660                    drop(state);
 661                    window.handle_ime_commit(text);
 662                    state = self.0.borrow_mut();
 663                }
 664                if let Some(button) = button_of_key(event.detail.try_into().unwrap()) {
 665                    let click_elapsed = state.last_click.elapsed();
 666
 667                    if click_elapsed < DOUBLE_CLICK_INTERVAL
 668                        && is_within_click_distance(state.last_location, position)
 669                    {
 670                        state.current_count += 1;
 671                    } else {
 672                        state.current_count = 1;
 673                    }
 674
 675                    state.last_click = Instant::now();
 676                    state.last_location = position;
 677                    let current_count = state.current_count;
 678
 679                    drop(state);
 680                    window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
 681                        button,
 682                        position,
 683                        modifiers,
 684                        click_count: current_count,
 685                        first_mouse: false,
 686                    }));
 687                } else {
 688                    log::warn!("Unknown button press: {event:?}");
 689                }
 690            }
 691            Event::XinputButtonRelease(event) => {
 692                let window = self.get_window(event.event)?;
 693                let mut state = self.0.borrow_mut();
 694                let modifiers = modifiers_from_xinput_info(event.mods);
 695                state.modifiers = modifiers;
 696
 697                let position = point(
 698                    px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
 699                    px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
 700                );
 701                if let Some(button) = button_of_key(event.detail.try_into().unwrap()) {
 702                    let click_count = state.current_count;
 703                    drop(state);
 704                    window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
 705                        button,
 706                        position,
 707                        modifiers,
 708                        click_count,
 709                    }));
 710                }
 711            }
 712            Event::XinputMotion(event) => {
 713                let window = self.get_window(event.event)?;
 714                let mut state = self.0.borrow_mut();
 715                let pressed_button = pressed_button_from_mask(event.button_mask[0]);
 716                let position = point(
 717                    px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
 718                    px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
 719                );
 720                let modifiers = modifiers_from_xinput_info(event.mods);
 721                state.modifiers = modifiers;
 722                drop(state);
 723
 724                let axisvalues = event
 725                    .axisvalues
 726                    .iter()
 727                    .map(|axisvalue| fp3232_to_f32(*axisvalue))
 728                    .collect::<Vec<_>>();
 729
 730                if event.valuator_mask[0] & 3 != 0 {
 731                    window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
 732                        position,
 733                        pressed_button,
 734                        modifiers,
 735                    }));
 736                }
 737
 738                let mut valuator_idx = 0;
 739                let scroll_class_data = self.0.borrow().scroll_class_data.clone();
 740                for shift in 0..32 {
 741                    if (event.valuator_mask[0] >> shift) & 1 == 0 {
 742                        continue;
 743                    }
 744
 745                    for scroll_class in &scroll_class_data {
 746                        if scroll_class.scroll_type == xinput::ScrollType::HORIZONTAL
 747                            && scroll_class.number == shift
 748                        {
 749                            let new_scroll = axisvalues[valuator_idx]
 750                                / fp3232_to_f32(scroll_class.increment)
 751                                * SCROLL_LINES as f32;
 752                            let old_scroll = self.0.borrow().scroll_x;
 753                            self.0.borrow_mut().scroll_x = Some(new_scroll);
 754
 755                            if let Some(old_scroll) = old_scroll {
 756                                let delta_scroll = old_scroll - new_scroll;
 757                                window.handle_input(PlatformInput::ScrollWheel(
 758                                    crate::ScrollWheelEvent {
 759                                        position,
 760                                        delta: ScrollDelta::Lines(Point::new(delta_scroll, 0.0)),
 761                                        modifiers,
 762                                        touch_phase: TouchPhase::default(),
 763                                    },
 764                                ));
 765                            }
 766                        } else if scroll_class.scroll_type == xinput::ScrollType::VERTICAL
 767                            && scroll_class.number == shift
 768                        {
 769                            // the `increment` is the valuator delta equivalent to one positive unit of scrolling. Here that means SCROLL_LINES lines.
 770                            let new_scroll = axisvalues[valuator_idx]
 771                                / fp3232_to_f32(scroll_class.increment)
 772                                * SCROLL_LINES as f32;
 773                            let old_scroll = self.0.borrow().scroll_y;
 774                            self.0.borrow_mut().scroll_y = Some(new_scroll);
 775
 776                            if let Some(old_scroll) = old_scroll {
 777                                let delta_scroll = old_scroll - new_scroll;
 778                                window.handle_input(PlatformInput::ScrollWheel(
 779                                    crate::ScrollWheelEvent {
 780                                        position,
 781                                        delta: ScrollDelta::Lines(Point::new(0.0, delta_scroll)),
 782                                        modifiers,
 783                                        touch_phase: TouchPhase::default(),
 784                                    },
 785                                ));
 786                            }
 787                        }
 788                    }
 789
 790                    valuator_idx += 1;
 791                }
 792            }
 793            Event::XinputLeave(event) if event.mode == xinput::NotifyMode::NORMAL => {
 794                self.0.borrow_mut().scroll_x = None; // Set last scroll to `None` so that a large delta isn't created if scrolling is done outside the window (the valuator is global)
 795                self.0.borrow_mut().scroll_y = None;
 796
 797                let window = self.get_window(event.event)?;
 798                let mut state = self.0.borrow_mut();
 799                let pressed_button = pressed_button_from_mask(event.buttons[0]);
 800                let position = point(
 801                    px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
 802                    px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
 803                );
 804                let modifiers = modifiers_from_xinput_info(event.mods);
 805                state.modifiers = modifiers;
 806                drop(state);
 807
 808                window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
 809                    pressed_button,
 810                    position,
 811                    modifiers,
 812                }));
 813            }
 814            _ => {}
 815        };
 816
 817        Some(())
 818    }
 819
 820    fn handle_xim_callback_event(&self, event: XimCallbackEvent) {
 821        match event {
 822            XimCallbackEvent::XimXEvent(event) => {
 823                self.handle_event(event);
 824            }
 825            XimCallbackEvent::XimCommitEvent(window, text) => {
 826                self.xim_handle_commit(window, text);
 827            }
 828            XimCallbackEvent::XimPreeditEvent(window, text) => {
 829                self.xim_handle_preedit(window, text);
 830            }
 831        };
 832    }
 833
 834    fn xim_handle_event(&self, event: Event) -> Option<()> {
 835        match event {
 836            Event::KeyPress(event) | Event::KeyRelease(event) => {
 837                let mut state = self.0.borrow_mut();
 838                let mut ximc = state.ximc.take().unwrap();
 839                let mut xim_handler = state.xim_handler.take().unwrap();
 840                drop(state);
 841                xim_handler.window = event.event;
 842                ximc.forward_event(
 843                    xim_handler.im_id,
 844                    xim_handler.ic_id,
 845                    xim::ForwardEventFlag::empty(),
 846                    &event,
 847                )
 848                .unwrap();
 849                let mut state = self.0.borrow_mut();
 850                state.ximc = Some(ximc);
 851                state.xim_handler = Some(xim_handler);
 852                drop(state);
 853            }
 854            event => {
 855                self.handle_event(event);
 856            }
 857        }
 858        Some(())
 859    }
 860
 861    fn xim_handle_commit(&self, window: xproto::Window, text: String) -> Option<()> {
 862        let window = self.get_window(window).unwrap();
 863        let mut state = self.0.borrow_mut();
 864        state.composing = false;
 865        drop(state);
 866
 867        window.handle_ime_commit(text);
 868        Some(())
 869    }
 870
 871    fn xim_handle_preedit(&self, window: xproto::Window, text: String) -> Option<()> {
 872        let window = self.get_window(window).unwrap();
 873        window.handle_ime_preedit(text);
 874
 875        let mut state = self.0.borrow_mut();
 876        let mut ximc = state.ximc.take().unwrap();
 877        let mut xim_handler = state.xim_handler.take().unwrap();
 878        state.composing = true;
 879        drop(state);
 880
 881        if let Some(area) = window.get_ime_area() {
 882            let ic_attributes = ximc
 883                .build_ic_attributes()
 884                .push(
 885                    xim::AttributeName::InputStyle,
 886                    xim::InputStyle::PREEDIT_CALLBACKS
 887                        | xim::InputStyle::STATUS_NOTHING
 888                        | xim::InputStyle::PREEDIT_POSITION,
 889                )
 890                .push(xim::AttributeName::ClientWindow, xim_handler.window)
 891                .push(xim::AttributeName::FocusWindow, xim_handler.window)
 892                .nested_list(xim::AttributeName::PreeditAttributes, |b| {
 893                    b.push(
 894                        xim::AttributeName::SpotLocation,
 895                        xim::Point {
 896                            x: u32::from(area.origin.x + area.size.width) as i16,
 897                            y: u32::from(area.origin.y + area.size.height) as i16,
 898                        },
 899                    );
 900                })
 901                .build();
 902            ximc.set_ic_values(xim_handler.im_id, xim_handler.ic_id, ic_attributes)
 903                .ok();
 904        }
 905        let mut state = self.0.borrow_mut();
 906        state.ximc = Some(ximc);
 907        state.xim_handler = Some(xim_handler);
 908        drop(state);
 909        Some(())
 910    }
 911}
 912
 913impl LinuxClient for X11Client {
 914    fn compositor_name(&self) -> &'static str {
 915        "X11"
 916    }
 917
 918    fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
 919        f(&mut self.0.borrow_mut().common)
 920    }
 921
 922    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
 923        let state = self.0.borrow();
 924        let setup = state.xcb_connection.setup();
 925        setup
 926            .roots
 927            .iter()
 928            .enumerate()
 929            .filter_map(|(root_id, _)| {
 930                Some(Rc::new(X11Display::new(
 931                    &state.xcb_connection,
 932                    state.scale_factor,
 933                    root_id,
 934                )?) as Rc<dyn PlatformDisplay>)
 935            })
 936            .collect()
 937    }
 938
 939    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
 940        let state = self.0.borrow();
 941
 942        Some(Rc::new(
 943            X11Display::new(
 944                &state.xcb_connection,
 945                state.scale_factor,
 946                state.x_root_index,
 947            )
 948            .expect("There should always be a root index"),
 949        ))
 950    }
 951
 952    fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
 953        let state = self.0.borrow();
 954
 955        Some(Rc::new(X11Display::new(
 956            &state.xcb_connection,
 957            state.scale_factor,
 958            id.0 as usize,
 959        )?))
 960    }
 961
 962    fn open_window(
 963        &self,
 964        handle: AnyWindowHandle,
 965        params: WindowParams,
 966    ) -> anyhow::Result<Box<dyn PlatformWindow>> {
 967        let mut state = self.0.borrow_mut();
 968        let x_window = state.xcb_connection.generate_id().unwrap();
 969
 970        let window = X11Window::new(
 971            handle,
 972            X11ClientStatePtr(Rc::downgrade(&self.0)),
 973            state.common.foreground_executor.clone(),
 974            params,
 975            &state.xcb_connection,
 976            state.x_root_index,
 977            x_window,
 978            &state.atoms,
 979            state.scale_factor,
 980            state.common.appearance,
 981        )?;
 982
 983        let refresh_event_token = state
 984            .loop_handle
 985            .insert_source(calloop::timer::Timer::immediate(), {
 986                let window = window.0.clone();
 987                let refresh_rate = window.state.borrow().refresh_rate;
 988                move |mut instant, (), _| {
 989                    window.refresh();
 990
 991                    // Take into account that some frames have been skipped
 992                    let now = Instant::now();
 993                    while instant < now {
 994                        instant += refresh_rate;
 995                    }
 996                    calloop::timer::TimeoutAction::ToInstant(instant)
 997                }
 998            })
 999            .expect("Failed to initialize refresh timer");
1000
1001        let window_ref = WindowRef {
1002            window: window.0.clone(),
1003            refresh_event_token,
1004        };
1005
1006        state.windows.insert(x_window, window_ref);
1007        Ok(Box::new(window))
1008    }
1009
1010    fn set_cursor_style(&self, style: CursorStyle) {
1011        let mut state = self.0.borrow_mut();
1012        let Some(focused_window) = state.focused_window else {
1013            return;
1014        };
1015        let current_style = state
1016            .cursor_styles
1017            .get(&focused_window)
1018            .unwrap_or(&CursorStyle::Arrow);
1019        if *current_style == style {
1020            return;
1021        }
1022
1023        let cursor = match state.cursor_cache.get(&style) {
1024            Some(cursor) => *cursor,
1025            None => {
1026                let cursor = state
1027                    .cursor_handle
1028                    .load_cursor(&state.xcb_connection, &style.to_icon_name())
1029                    .expect("failed to load cursor");
1030                state.cursor_cache.insert(style, cursor);
1031                cursor
1032            }
1033        };
1034
1035        state.cursor_styles.insert(focused_window, style);
1036        state
1037            .xcb_connection
1038            .change_window_attributes(
1039                focused_window,
1040                &ChangeWindowAttributesAux {
1041                    cursor: Some(cursor),
1042                    ..Default::default()
1043                },
1044            )
1045            .expect("failed to change window cursor");
1046    }
1047
1048    fn open_uri(&self, uri: &str) {
1049        open_uri_internal(uri, None);
1050    }
1051
1052    fn write_to_primary(&self, item: crate::ClipboardItem) {
1053        let state = self.0.borrow_mut();
1054        state
1055            .clipboard
1056            .store(
1057                state.clipboard.setter.atoms.primary,
1058                state.clipboard.setter.atoms.utf8_string,
1059                item.text().as_bytes(),
1060            )
1061            .ok();
1062    }
1063
1064    fn write_to_clipboard(&self, item: crate::ClipboardItem) {
1065        let state = self.0.borrow_mut();
1066        state
1067            .clipboard
1068            .store(
1069                state.clipboard.setter.atoms.clipboard,
1070                state.clipboard.setter.atoms.utf8_string,
1071                item.text().as_bytes(),
1072            )
1073            .ok();
1074    }
1075
1076    fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
1077        let state = self.0.borrow_mut();
1078        state
1079            .clipboard
1080            .load(
1081                state.clipboard.getter.atoms.primary,
1082                state.clipboard.getter.atoms.utf8_string,
1083                state.clipboard.getter.atoms.property,
1084                Duration::from_secs(3),
1085            )
1086            .map(|text| crate::ClipboardItem {
1087                text: String::from_utf8(text).unwrap(),
1088                metadata: None,
1089            })
1090            .ok()
1091    }
1092
1093    fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
1094        let state = self.0.borrow_mut();
1095        state
1096            .clipboard
1097            .load(
1098                state.clipboard.getter.atoms.clipboard,
1099                state.clipboard.getter.atoms.utf8_string,
1100                state.clipboard.getter.atoms.property,
1101                Duration::from_secs(3),
1102            )
1103            .map(|text| crate::ClipboardItem {
1104                text: String::from_utf8(text).unwrap(),
1105                metadata: None,
1106            })
1107            .ok()
1108    }
1109
1110    fn run(&self) {
1111        let mut event_loop = self
1112            .0
1113            .borrow_mut()
1114            .event_loop
1115            .take()
1116            .expect("App is already running");
1117
1118        event_loop.run(None, &mut self.clone(), |_| {}).log_err();
1119    }
1120
1121    fn active_window(&self) -> Option<AnyWindowHandle> {
1122        let state = self.0.borrow();
1123        state.focused_window.and_then(|focused_window| {
1124            state
1125                .windows
1126                .get(&focused_window)
1127                .map(|window| window.handle())
1128        })
1129    }
1130}
1131
1132fn fp3232_to_f32(value: xinput::Fp3232) -> f32 {
1133    value.integral as f32 + value.frac as f32 / u32::MAX as f32
1134}