client.rs

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