client.rs

  1use std::cell::RefCell;
  2use std::ops::Deref;
  3use std::rc::{Rc, Weak};
  4use std::time::{Duration, Instant};
  5
  6use calloop::generic::{FdWrapper, Generic};
  7use calloop::{EventLoop, LoopHandle, RegistrationToken};
  8use collections::HashMap;
  9use copypasta::x11_clipboard::{Clipboard, Primary, X11ClipboardContext};
 10use copypasta::ClipboardProvider;
 11
 12use util::ResultExt;
 13use x11rb::connection::{Connection, RequestConnection};
 14use x11rb::cursor;
 15use x11rb::errors::ConnectionError;
 16use x11rb::protocol::randr::ConnectionExt as _;
 17use x11rb::protocol::xinput::{ConnectionExt, ScrollClass};
 18use x11rb::protocol::xkb::ConnectionExt as _;
 19use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _};
 20use x11rb::protocol::{randr, render, xinput, xkb, xproto, Event};
 21use x11rb::resource_manager::Database;
 22use x11rb::xcb_ffi::XCBConnection;
 23use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
 24use xkbcommon::xkb as xkbc;
 25
 26use crate::platform::linux::LinuxClient;
 27use crate::platform::{LinuxCommon, PlatformWindow};
 28use crate::{
 29    modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, CursorStyle, DisplayId,
 30    Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput, Point, ScrollDelta,
 31    Size, TouchPhase, WindowParams, X11Window,
 32};
 33
 34use super::{
 35    super::{open_uri_internal, SCROLL_LINES},
 36    X11Display, X11WindowStatePtr, XcbAtoms,
 37};
 38use super::{button_from_mask, button_of_key, modifiers_from_state};
 39use crate::platform::linux::is_within_click_distance;
 40use crate::platform::linux::platform::DOUBLE_CLICK_INTERVAL;
 41
 42pub(crate) struct WindowRef {
 43    window: X11WindowStatePtr,
 44    refresh_event_token: RegistrationToken,
 45}
 46
 47impl Deref for WindowRef {
 48    type Target = X11WindowStatePtr;
 49
 50    fn deref(&self) -> &Self::Target {
 51        &self.window
 52    }
 53}
 54
 55pub struct X11ClientState {
 56    pub(crate) loop_handle: LoopHandle<'static, X11Client>,
 57    pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,
 58
 59    pub(crate) last_click: Instant,
 60    pub(crate) last_location: Point<Pixels>,
 61    pub(crate) current_count: usize,
 62
 63    pub(crate) scale_factor: f32,
 64
 65    pub(crate) xcb_connection: Rc<XCBConnection>,
 66    pub(crate) x_root_index: usize,
 67    pub(crate) resource_database: Database,
 68    pub(crate) atoms: XcbAtoms,
 69    pub(crate) windows: HashMap<xproto::Window, WindowRef>,
 70    pub(crate) focused_window: Option<xproto::Window>,
 71    pub(crate) xkb: xkbc::State,
 72
 73    pub(crate) cursor_handle: cursor::Handle,
 74    pub(crate) cursor_styles: HashMap<xproto::Window, CursorStyle>,
 75    pub(crate) cursor_cache: HashMap<CursorStyle, xproto::Cursor>,
 76
 77    pub(crate) scroll_class_data: Vec<xinput::DeviceClassDataScroll>,
 78    pub(crate) scroll_x: Option<f32>,
 79    pub(crate) scroll_y: Option<f32>,
 80
 81    pub(crate) common: LinuxCommon,
 82    pub(crate) clipboard: X11ClipboardContext<Clipboard>,
 83    pub(crate) primary: X11ClipboardContext<Primary>,
 84}
 85
 86#[derive(Clone)]
 87pub struct X11ClientStatePtr(pub Weak<RefCell<X11ClientState>>);
 88
 89impl X11ClientStatePtr {
 90    pub fn drop_window(&self, x_window: u32) {
 91        let client = X11Client(self.0.upgrade().expect("client already dropped"));
 92        let mut state = client.0.borrow_mut();
 93
 94        if let Some(window_ref) = state.windows.remove(&x_window) {
 95            state.loop_handle.remove(window_ref.refresh_event_token);
 96        }
 97
 98        state.cursor_styles.remove(&x_window);
 99
100        if state.windows.is_empty() {
101            state.common.signal.stop();
102        }
103    }
104}
105
106#[derive(Clone)]
107pub(crate) struct X11Client(Rc<RefCell<X11ClientState>>);
108
109impl X11Client {
110    pub(crate) fn new() -> Self {
111        let event_loop = EventLoop::try_new().unwrap();
112
113        let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
114
115        let handle = event_loop.handle();
116
117        handle.insert_source(main_receiver, |event, _, _: &mut X11Client| {
118            if let calloop::channel::Event::Msg(runnable) = event {
119                runnable.run();
120            }
121        });
122
123        let (xcb_connection, x_root_index) = XCBConnection::connect(None).unwrap();
124        xcb_connection
125            .prefetch_extension_information(xkb::X11_EXTENSION_NAME)
126            .unwrap();
127        xcb_connection
128            .prefetch_extension_information(randr::X11_EXTENSION_NAME)
129            .unwrap();
130        xcb_connection
131            .prefetch_extension_information(render::X11_EXTENSION_NAME)
132            .unwrap();
133        xcb_connection
134            .prefetch_extension_information(xinput::X11_EXTENSION_NAME)
135            .unwrap();
136
137        let xinput_version = xcb_connection
138            .xinput_xi_query_version(2, 0)
139            .unwrap()
140            .reply()
141            .unwrap();
142        assert!(
143            xinput_version.major_version >= 2,
144            "XInput Extension v2 not supported."
145        );
146
147        let master_device_query = xcb_connection
148            .xinput_xi_query_device(1_u16)
149            .unwrap()
150            .reply()
151            .unwrap();
152        let scroll_class_data = master_device_query
153            .infos
154            .iter()
155            .find(|info| info.type_ == xinput::DeviceType::MASTER_POINTER)
156            .unwrap()
157            .classes
158            .iter()
159            .filter_map(|class| class.data.as_scroll())
160            .map(|class| *class)
161            .collect::<Vec<_>>();
162
163        let atoms = XcbAtoms::new(&xcb_connection).unwrap();
164        let xkb = xcb_connection
165            .xkb_use_extension(XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION)
166            .unwrap();
167
168        let atoms = atoms.reply().unwrap();
169        let xkb = xkb.reply().unwrap();
170        let events = xkb::EventType::STATE_NOTIFY;
171        xcb_connection
172            .xkb_select_events(
173                xkb::ID::USE_CORE_KBD.into(),
174                0u8.into(),
175                events,
176                0u8.into(),
177                0u8.into(),
178                &xkb::SelectEventsAux::new(),
179            )
180            .unwrap();
181        assert!(xkb.supported);
182
183        let xkb_state = {
184            let xkb_context = xkbc::Context::new(xkbc::CONTEXT_NO_FLAGS);
185            let xkb_device_id = xkbc::x11::get_core_keyboard_device_id(&xcb_connection);
186            let xkb_keymap = xkbc::x11::keymap_new_from_device(
187                &xkb_context,
188                &xcb_connection,
189                xkb_device_id,
190                xkbc::KEYMAP_COMPILE_NO_FLAGS,
191            );
192            xkbc::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id)
193        };
194
195        let screen = xcb_connection.setup().roots.get(x_root_index).unwrap();
196
197        // Values from `Database::GET_RESOURCE_DATABASE`
198        let resource_manager = xcb_connection
199            .get_property(
200                false,
201                screen.root,
202                xproto::AtomEnum::RESOURCE_MANAGER,
203                xproto::AtomEnum::STRING,
204                0,
205                100_000_000,
206            )
207            .unwrap();
208        let resource_manager = resource_manager.reply().unwrap();
209
210        // todo(linux): read hostname
211        let resource_database = Database::new_from_default(&resource_manager, "HOSTNAME".into());
212
213        let scale_factor = resource_database
214            .get_value("Xft.dpi", "Xft.dpi")
215            .ok()
216            .flatten()
217            .map(|dpi: f32| dpi / 96.0)
218            .unwrap_or(1.0);
219
220        let cursor_handle = cursor::Handle::new(&xcb_connection, x_root_index, &resource_database)
221            .unwrap()
222            .reply()
223            .unwrap();
224
225        let clipboard = X11ClipboardContext::<Clipboard>::new().unwrap();
226        let primary = X11ClipboardContext::<Primary>::new().unwrap();
227
228        let xcb_connection = Rc::new(xcb_connection);
229
230        // Safety: Safe if xcb::Connection always returns a valid fd
231        let fd = unsafe { FdWrapper::new(Rc::clone(&xcb_connection)) };
232
233        handle
234            .insert_source(
235                Generic::new_with_error::<ConnectionError>(
236                    fd,
237                    calloop::Interest::READ,
238                    calloop::Mode::Level,
239                ),
240                {
241                    let xcb_connection = xcb_connection.clone();
242                    move |_readiness, _, client| {
243                        while let Some(event) = xcb_connection.poll_for_event()? {
244                            client.handle_event(event);
245                        }
246                        Ok(calloop::PostAction::Continue)
247                    }
248                },
249            )
250            .expect("Failed to initialize x11 event source");
251
252        X11Client(Rc::new(RefCell::new(X11ClientState {
253            event_loop: Some(event_loop),
254            loop_handle: handle,
255            common,
256            last_click: Instant::now(),
257            last_location: Point::new(px(0.0), px(0.0)),
258            current_count: 0,
259            scale_factor,
260
261            xcb_connection,
262            x_root_index,
263            resource_database,
264            atoms,
265            windows: HashMap::default(),
266            focused_window: None,
267            xkb: xkb_state,
268
269            cursor_handle,
270            cursor_styles: HashMap::default(),
271            cursor_cache: HashMap::default(),
272
273            scroll_class_data,
274            scroll_x: None,
275            scroll_y: None,
276
277            clipboard,
278            primary,
279        })))
280    }
281
282    fn get_window(&self, win: xproto::Window) -> Option<X11WindowStatePtr> {
283        let state = self.0.borrow();
284        state
285            .windows
286            .get(&win)
287            .map(|window_reference| window_reference.window.clone())
288    }
289
290    fn handle_event(&self, event: Event) -> Option<()> {
291        match event {
292            Event::ClientMessage(event) => {
293                let window = self.get_window(event.window)?;
294                let [atom, ..] = event.data.as_data32();
295                let mut state = self.0.borrow_mut();
296
297                if atom == state.atoms.WM_DELETE_WINDOW {
298                    // window "x" button clicked by user
299                    if window.should_close() {
300                        let window_ref = state.windows.remove(&event.window)?;
301                        state.loop_handle.remove(window_ref.refresh_event_token);
302                        // Rest of the close logic is handled in drop_window()
303                    }
304                }
305            }
306            Event::ConfigureNotify(event) => {
307                let bounds = Bounds {
308                    origin: Point {
309                        x: event.x.into(),
310                        y: event.y.into(),
311                    },
312                    size: Size {
313                        width: event.width.into(),
314                        height: event.height.into(),
315                    },
316                };
317                let window = self.get_window(event.window)?;
318                window.configure(bounds);
319            }
320            Event::Expose(event) => {
321                let window = self.get_window(event.window)?;
322                window.refresh();
323            }
324            Event::FocusIn(event) => {
325                let window = self.get_window(event.event)?;
326                window.set_focused(true);
327                self.0.borrow_mut().focused_window = Some(event.event);
328            }
329            Event::FocusOut(event) => {
330                let window = self.get_window(event.event)?;
331                window.set_focused(false);
332                self.0.borrow_mut().focused_window = None;
333            }
334            Event::XkbStateNotify(event) => {
335                let mut state = self.0.borrow_mut();
336                state.xkb.update_mask(
337                    event.base_mods.into(),
338                    event.latched_mods.into(),
339                    event.locked_mods.into(),
340                    0,
341                    0,
342                    event.locked_group.into(),
343                );
344                let modifiers = Modifiers::from_xkb(&state.xkb);
345                let focused_window_id = state.focused_window?;
346                drop(state);
347
348                let focused_window = self.get_window(focused_window_id)?;
349                focused_window.handle_input(PlatformInput::ModifiersChanged(
350                    ModifiersChangedEvent { modifiers },
351                ));
352            }
353            Event::KeyPress(event) => {
354                let window = self.get_window(event.event)?;
355                let mut state = self.0.borrow_mut();
356
357                let modifiers = modifiers_from_state(event.state);
358                let keystroke = {
359                    let code = event.detail.into();
360                    let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
361                    state.xkb.update_key(code, xkbc::KeyDirection::Down);
362                    let keysym = state.xkb.key_get_one_sym(code);
363                    if keysym.is_modifier_key() {
364                        return Some(());
365                    }
366                    keystroke
367                };
368
369                drop(state);
370                window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
371                    keystroke,
372                    is_held: false,
373                }));
374            }
375            Event::KeyRelease(event) => {
376                let window = self.get_window(event.event)?;
377                let mut state = self.0.borrow_mut();
378
379                let modifiers = modifiers_from_state(event.state);
380                let keystroke = {
381                    let code = event.detail.into();
382                    let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
383                    state.xkb.update_key(code, xkbc::KeyDirection::Up);
384                    let keysym = state.xkb.key_get_one_sym(code);
385                    if keysym.is_modifier_key() {
386                        return Some(());
387                    }
388                    keystroke
389                };
390                drop(state);
391                window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke }));
392            }
393            Event::XinputButtonPress(event) => {
394                let window = self.get_window(event.event)?;
395                let mut state = self.0.borrow_mut();
396
397                let modifiers = modifiers_from_xinput_info(event.mods);
398                let position = point(
399                    px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
400                    px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
401                );
402                if let Some(button) = button_of_key(event.detail.try_into().unwrap()) {
403                    let click_elapsed = state.last_click.elapsed();
404
405                    if click_elapsed < DOUBLE_CLICK_INTERVAL
406                        && is_within_click_distance(state.last_location, position)
407                    {
408                        state.current_count += 1;
409                    } else {
410                        state.current_count = 1;
411                    }
412
413                    state.last_click = Instant::now();
414                    state.last_location = position;
415                    let current_count = state.current_count;
416
417                    drop(state);
418                    window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
419                        button,
420                        position,
421                        modifiers,
422                        click_count: current_count,
423                        first_mouse: false,
424                    }));
425                } else {
426                    log::warn!("Unknown button press: {event:?}");
427                }
428            }
429            Event::XinputButtonRelease(event) => {
430                let window = self.get_window(event.event)?;
431                let state = self.0.borrow();
432                let modifiers = modifiers_from_xinput_info(event.mods);
433                let position = point(
434                    px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
435                    px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
436                );
437                if let Some(button) = button_of_key(event.detail.try_into().unwrap()) {
438                    let click_count = state.current_count;
439                    drop(state);
440                    window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
441                        button,
442                        position,
443                        modifiers,
444                        click_count,
445                    }));
446                }
447            }
448            Event::XinputMotion(event) => {
449                let window = self.get_window(event.event)?;
450                let state = self.0.borrow();
451                let pressed_button = button_from_mask(event.button_mask[0]);
452                let position = point(
453                    px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
454                    px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
455                );
456                drop(state);
457                let modifiers = modifiers_from_xinput_info(event.mods);
458
459                let axisvalues = event
460                    .axisvalues
461                    .iter()
462                    .map(|axisvalue| fp3232_to_f32(*axisvalue))
463                    .collect::<Vec<_>>();
464
465                if event.valuator_mask[0] & 3 != 0 {
466                    window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
467                        position,
468                        pressed_button,
469                        modifiers,
470                    }));
471                }
472
473                let mut valuator_idx = 0;
474                let scroll_class_data = self.0.borrow().scroll_class_data.clone();
475                for shift in 0..32 {
476                    if (event.valuator_mask[0] >> shift) & 1 == 0 {
477                        continue;
478                    }
479
480                    for scroll_class in &scroll_class_data {
481                        if scroll_class.scroll_type == xinput::ScrollType::HORIZONTAL
482                            && scroll_class.number == shift
483                        {
484                            let new_scroll = axisvalues[valuator_idx]
485                                / fp3232_to_f32(scroll_class.increment)
486                                * SCROLL_LINES as f32;
487                            let old_scroll = self.0.borrow().scroll_x;
488                            self.0.borrow_mut().scroll_x = Some(new_scroll);
489
490                            if let Some(old_scroll) = old_scroll {
491                                let delta_scroll = old_scroll - new_scroll;
492                                window.handle_input(PlatformInput::ScrollWheel(
493                                    crate::ScrollWheelEvent {
494                                        position,
495                                        delta: ScrollDelta::Lines(Point::new(delta_scroll, 0.0)),
496                                        modifiers,
497                                        touch_phase: TouchPhase::default(),
498                                    },
499                                ));
500                            }
501                        } else if scroll_class.scroll_type == xinput::ScrollType::VERTICAL
502                            && scroll_class.number == shift
503                        {
504                            // the `increment` is the valuator delta equivalent to one positive unit of scrolling. Here that means SCROLL_LINES lines.
505                            let new_scroll = axisvalues[valuator_idx]
506                                / fp3232_to_f32(scroll_class.increment)
507                                * SCROLL_LINES as f32;
508                            let old_scroll = self.0.borrow().scroll_y;
509                            self.0.borrow_mut().scroll_y = Some(new_scroll);
510
511                            if let Some(old_scroll) = old_scroll {
512                                let delta_scroll = old_scroll - new_scroll;
513                                window.handle_input(PlatformInput::ScrollWheel(
514                                    crate::ScrollWheelEvent {
515                                        position,
516                                        delta: ScrollDelta::Lines(Point::new(0.0, delta_scroll)),
517                                        modifiers,
518                                        touch_phase: TouchPhase::default(),
519                                    },
520                                ));
521                            }
522                        }
523                    }
524
525                    valuator_idx += 1;
526                }
527            }
528            Event::XinputLeave(event) => {
529                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)
530                self.0.borrow_mut().scroll_y = None;
531
532                let window = self.get_window(event.event)?;
533                let state = self.0.borrow();
534                let pressed_button = button_from_mask(event.buttons[0]);
535                let position = point(
536                    px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
537                    px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
538                );
539                let modifiers = modifiers_from_xinput_info(event.mods);
540                drop(state);
541
542                window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
543                    pressed_button,
544                    position,
545                    modifiers,
546                }));
547            }
548            _ => {}
549        };
550
551        Some(())
552    }
553}
554
555impl LinuxClient for X11Client {
556    fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
557        f(&mut self.0.borrow_mut().common)
558    }
559
560    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
561        let state = self.0.borrow();
562        let setup = state.xcb_connection.setup();
563        setup
564            .roots
565            .iter()
566            .enumerate()
567            .filter_map(|(root_id, _)| {
568                Some(Rc::new(X11Display::new(&state.xcb_connection, root_id)?)
569                    as Rc<dyn PlatformDisplay>)
570            })
571            .collect()
572    }
573
574    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
575        let state = self.0.borrow();
576
577        Some(Rc::new(
578            X11Display::new(&state.xcb_connection, state.x_root_index)
579                .expect("There should always be a root index"),
580        ))
581    }
582
583    fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
584        let state = self.0.borrow();
585
586        Some(Rc::new(X11Display::new(
587            &state.xcb_connection,
588            id.0 as usize,
589        )?))
590    }
591
592    fn open_window(
593        &self,
594        _handle: AnyWindowHandle,
595        params: WindowParams,
596    ) -> Box<dyn PlatformWindow> {
597        let mut state = self.0.borrow_mut();
598        let x_window = state.xcb_connection.generate_id().unwrap();
599
600        let window = X11Window::new(
601            X11ClientStatePtr(Rc::downgrade(&self.0)),
602            state.common.foreground_executor.clone(),
603            params,
604            &state.xcb_connection,
605            state.x_root_index,
606            x_window,
607            &state.atoms,
608            state.scale_factor,
609        );
610
611        let screen_resources = state
612            .xcb_connection
613            .randr_get_screen_resources(x_window)
614            .unwrap()
615            .reply()
616            .expect("Could not find available screens");
617
618        let mode = screen_resources
619            .crtcs
620            .iter()
621            .find_map(|crtc| {
622                let crtc_info = state
623                    .xcb_connection
624                    .randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME)
625                    .ok()?
626                    .reply()
627                    .ok()?;
628
629                screen_resources
630                    .modes
631                    .iter()
632                    .find(|m| m.id == crtc_info.mode)
633            })
634            .expect("Unable to find screen refresh rate");
635
636        let refresh_event_token = state
637            .loop_handle
638            .insert_source(calloop::timer::Timer::immediate(), {
639                let refresh_duration = mode_refresh_rate(mode);
640                move |mut instant, (), client| {
641                    let state = client.0.borrow_mut();
642                    state
643                        .xcb_connection
644                        .send_event(
645                            false,
646                            x_window,
647                            xproto::EventMask::EXPOSURE,
648                            xproto::ExposeEvent {
649                                response_type: xproto::EXPOSE_EVENT,
650                                sequence: 0,
651                                window: x_window,
652                                x: 0,
653                                y: 0,
654                                width: 0,
655                                height: 0,
656                                count: 1,
657                            },
658                        )
659                        .unwrap();
660                    let _ = state.xcb_connection.flush().unwrap();
661                    // Take into account that some frames have been skipped
662                    let now = time::Instant::now();
663                    while instant < now {
664                        instant += refresh_duration;
665                    }
666                    calloop::timer::TimeoutAction::ToInstant(instant)
667                }
668            })
669            .expect("Failed to initialize refresh timer");
670
671        let window_ref = WindowRef {
672            window: window.0.clone(),
673            refresh_event_token,
674        };
675
676        state.windows.insert(x_window, window_ref);
677        Box::new(window)
678    }
679
680    fn set_cursor_style(&self, style: CursorStyle) {
681        let mut state = self.0.borrow_mut();
682        let Some(focused_window) = state.focused_window else {
683            return;
684        };
685        let current_style = state
686            .cursor_styles
687            .get(&focused_window)
688            .unwrap_or(&CursorStyle::Arrow);
689        if *current_style == style {
690            return;
691        }
692
693        let cursor = match state.cursor_cache.get(&style) {
694            Some(cursor) => *cursor,
695            None => {
696                let cursor = state
697                    .cursor_handle
698                    .load_cursor(&state.xcb_connection, &style.to_icon_name())
699                    .expect("failed to load cursor");
700                state.cursor_cache.insert(style, cursor);
701                cursor
702            }
703        };
704
705        state.cursor_styles.insert(focused_window, style);
706        state
707            .xcb_connection
708            .change_window_attributes(
709                focused_window,
710                &ChangeWindowAttributesAux {
711                    cursor: Some(cursor),
712                    ..Default::default()
713                },
714            )
715            .expect("failed to change window cursor");
716    }
717
718    fn open_uri(&self, uri: &str) {
719        open_uri_internal(uri, None);
720    }
721
722    fn write_to_primary(&self, item: crate::ClipboardItem) {
723        self.0.borrow_mut().primary.set_contents(item.text);
724    }
725
726    fn write_to_clipboard(&self, item: crate::ClipboardItem) {
727        self.0.borrow_mut().clipboard.set_contents(item.text);
728    }
729
730    fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
731        self.0
732            .borrow_mut()
733            .primary
734            .get_contents()
735            .ok()
736            .map(|text| crate::ClipboardItem {
737                text,
738                metadata: None,
739            })
740    }
741
742    fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
743        self.0
744            .borrow_mut()
745            .clipboard
746            .get_contents()
747            .ok()
748            .map(|text| crate::ClipboardItem {
749                text,
750                metadata: None,
751            })
752    }
753
754    fn run(&self) {
755        let mut event_loop = self
756            .0
757            .borrow_mut()
758            .event_loop
759            .take()
760            .expect("App is already running");
761
762        event_loop.run(None, &mut self.clone(), |_| {}).log_err();
763    }
764}
765
766// Adatpted from:
767// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
768pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
769    let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
770    let micros = 1_000_000_000 / millihertz;
771    log::info!("Refreshing at {} micros", micros);
772    Duration::from_micros(micros)
773}
774
775fn fp3232_to_f32(value: xinput::Fp3232) -> f32 {
776    value.integral as f32 + value.frac as f32 / u32::MAX as f32
777}