client.rs

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