client.rs

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