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