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