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