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}