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