client.rs

  1use std::cell::RefCell;
  2use std::rc::Rc;
  3use std::time::Duration;
  4
  5use collections::HashMap;
  6use copypasta::x11_clipboard::{Clipboard, Primary, X11ClipboardContext};
  7use copypasta::ClipboardProvider;
  8
  9use x11rb::connection::{Connection, RequestConnection};
 10use x11rb::errors::ConnectionError;
 11use x11rb::protocol::randr::ConnectionExt as _;
 12use x11rb::protocol::xkb::ConnectionExt as _;
 13use x11rb::protocol::xproto::ConnectionExt as _;
 14use x11rb::protocol::{randr, xkb, xproto, Event};
 15use x11rb::xcb_ffi::XCBConnection;
 16use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
 17use xkbcommon::xkb as xkbc;
 18
 19use crate::platform::linux::client::Client;
 20use crate::platform::{LinuxPlatformInner, PlatformWindow};
 21use crate::{
 22    AnyWindowHandle, Bounds, CursorStyle, DisplayId, PlatformDisplay, PlatformInput, Point,
 23    ScrollDelta, Size, TouchPhase, WindowParams,
 24};
 25
 26use super::{super::SCROLL_LINES, X11Display, X11Window, X11WindowState, XcbAtoms};
 27use calloop::{
 28    generic::{FdWrapper, Generic},
 29    RegistrationToken,
 30};
 31
 32struct WindowRef {
 33    state: Rc<X11WindowState>,
 34    refresh_event_token: RegistrationToken,
 35}
 36
 37struct X11ClientState {
 38    windows: HashMap<xproto::Window, WindowRef>,
 39    xkb: xkbc::State,
 40    clipboard: Rc<RefCell<X11ClipboardContext<Clipboard>>>,
 41    primary: Rc<RefCell<X11ClipboardContext<Primary>>>,
 42}
 43
 44pub(crate) struct X11Client {
 45    platform_inner: Rc<LinuxPlatformInner>,
 46    xcb_connection: Rc<XCBConnection>,
 47    x_root_index: usize,
 48    atoms: XcbAtoms,
 49    state: RefCell<X11ClientState>,
 50}
 51
 52impl X11Client {
 53    pub(crate) fn new(inner: Rc<LinuxPlatformInner>) -> Rc<Self> {
 54        let (xcb_connection, x_root_index) = XCBConnection::connect(None).unwrap();
 55        xcb_connection
 56            .prefetch_extension_information(xkb::X11_EXTENSION_NAME)
 57            .unwrap();
 58        xcb_connection
 59            .prefetch_extension_information(randr::X11_EXTENSION_NAME)
 60            .unwrap();
 61
 62        let atoms = XcbAtoms::new(&xcb_connection).unwrap();
 63        let xkb = xcb_connection
 64            .xkb_use_extension(XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION)
 65            .unwrap();
 66
 67        let atoms = atoms.reply().unwrap();
 68        let xkb = xkb.reply().unwrap();
 69        assert!(xkb.supported);
 70
 71        let xkb_state = {
 72            let xkb_context = xkbc::Context::new(xkbc::CONTEXT_NO_FLAGS);
 73            let xkb_device_id = xkbc::x11::get_core_keyboard_device_id(&xcb_connection);
 74            let xkb_keymap = xkbc::x11::keymap_new_from_device(
 75                &xkb_context,
 76                &xcb_connection,
 77                xkb_device_id,
 78                xkbc::KEYMAP_COMPILE_NO_FLAGS,
 79            );
 80            xkbc::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id)
 81        };
 82
 83        let clipboard = X11ClipboardContext::<Clipboard>::new().unwrap();
 84        let primary = X11ClipboardContext::<Primary>::new().unwrap();
 85
 86        let xcb_connection = Rc::new(xcb_connection);
 87
 88        let client: Rc<X11Client> = Rc::new(Self {
 89            platform_inner: inner.clone(),
 90            xcb_connection: Rc::clone(&xcb_connection),
 91            x_root_index,
 92            atoms,
 93            state: RefCell::new(X11ClientState {
 94                windows: HashMap::default(),
 95                xkb: xkb_state,
 96                clipboard: Rc::new(RefCell::new(clipboard)),
 97                primary: Rc::new(RefCell::new(primary)),
 98            }),
 99        });
100
101        // Safety: Safe if xcb::Connection always returns a valid fd
102        let fd = unsafe { FdWrapper::new(Rc::clone(&xcb_connection)) };
103
104        inner
105            .loop_handle
106            .insert_source(
107                Generic::new_with_error::<ConnectionError>(
108                    fd,
109                    calloop::Interest::READ,
110                    calloop::Mode::Level,
111                ),
112                {
113                    let client = Rc::clone(&client);
114                    move |_readiness, _, _| {
115                        while let Some(event) = xcb_connection.poll_for_event()? {
116                            client.handle_event(event);
117                        }
118                        Ok(calloop::PostAction::Continue)
119                    }
120                },
121            )
122            .expect("Failed to initialize x11 event source");
123
124        client
125    }
126
127    fn get_window(&self, win: xproto::Window) -> Option<Rc<X11WindowState>> {
128        let state = self.state.borrow();
129        state.windows.get(&win).map(|wr| Rc::clone(&wr.state))
130    }
131
132    fn handle_event(&self, event: Event) -> Option<()> {
133        match event {
134            Event::ClientMessage(event) => {
135                let [atom, ..] = event.data.as_data32();
136                if atom == self.atoms.WM_DELETE_WINDOW {
137                    // window "x" button clicked by user, we gracefully exit
138                    let window_ref = self
139                        .state
140                        .borrow_mut()
141                        .windows
142                        .remove(&event.window)
143                        .unwrap();
144
145                    self.platform_inner
146                        .loop_handle
147                        .remove(window_ref.refresh_event_token);
148                    window_ref.state.destroy();
149
150                    if self.state.borrow().windows.is_empty() {
151                        self.platform_inner.loop_signal.stop();
152                    }
153                }
154            }
155            Event::ConfigureNotify(event) => {
156                let bounds = Bounds {
157                    origin: Point {
158                        x: event.x.into(),
159                        y: event.y.into(),
160                    },
161                    size: Size {
162                        width: event.width.into(),
163                        height: event.height.into(),
164                    },
165                };
166                let window = self.get_window(event.window)?;
167                window.configure(bounds);
168            }
169            Event::Expose(event) => {
170                let window = self.get_window(event.window)?;
171                window.refresh();
172            }
173            Event::FocusIn(event) => {
174                let window = self.get_window(event.event)?;
175                window.set_focused(true);
176            }
177            Event::FocusOut(event) => {
178                let window = self.get_window(event.event)?;
179                window.set_focused(false);
180            }
181            Event::KeyPress(event) => {
182                let window = self.get_window(event.event)?;
183                let modifiers = super::modifiers_from_state(event.state);
184                let keystroke = {
185                    let code = event.detail.into();
186                    let mut state = self.state.borrow_mut();
187                    let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
188                    state.xkb.update_key(code, xkbc::KeyDirection::Down);
189                    keystroke
190                };
191
192                window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
193                    keystroke,
194                    is_held: false,
195                }));
196            }
197            Event::KeyRelease(event) => {
198                let window = self.get_window(event.event)?;
199                let modifiers = super::modifiers_from_state(event.state);
200                let keystroke = {
201                    let code = event.detail.into();
202                    let mut state = self.state.borrow_mut();
203                    let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
204                    state.xkb.update_key(code, xkbc::KeyDirection::Up);
205                    keystroke
206                };
207
208                window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke }));
209            }
210            Event::ButtonPress(event) => {
211                let window = self.get_window(event.event)?;
212                let modifiers = super::modifiers_from_state(event.state);
213                let position =
214                    Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
215                if let Some(button) = super::button_of_key(event.detail) {
216                    window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
217                        button,
218                        position,
219                        modifiers,
220                        click_count: 1,
221                        first_mouse: false,
222                    }));
223                } else if event.detail >= 4 && event.detail <= 5 {
224                    // https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11
225                    let scroll_direction = if event.detail == 4 { 1.0 } else { -1.0 };
226                    let scroll_y = SCROLL_LINES * scroll_direction;
227                    window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
228                        position,
229                        delta: ScrollDelta::Lines(Point::new(0.0, scroll_y as f32)),
230                        modifiers,
231                        touch_phase: TouchPhase::Moved,
232                    }));
233                } else {
234                    log::warn!("Unknown button press: {event:?}");
235                }
236            }
237            Event::ButtonRelease(event) => {
238                let window = self.get_window(event.event)?;
239                let modifiers = super::modifiers_from_state(event.state);
240                let position =
241                    Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
242                if let Some(button) = super::button_of_key(event.detail) {
243                    window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
244                        button,
245                        position,
246                        modifiers,
247                        click_count: 1,
248                    }));
249                }
250            }
251            Event::MotionNotify(event) => {
252                let window = self.get_window(event.event)?;
253                let pressed_button = super::button_from_state(event.state);
254                let position =
255                    Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
256                let modifiers = super::modifiers_from_state(event.state);
257                window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
258                    pressed_button,
259                    position,
260                    modifiers,
261                }));
262            }
263            Event::LeaveNotify(event) => {
264                let window = self.get_window(event.event)?;
265                let pressed_button = super::button_from_state(event.state);
266                let position =
267                    Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
268                let modifiers = super::modifiers_from_state(event.state);
269                window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
270                    pressed_button,
271                    position,
272                    modifiers,
273                }));
274            }
275            _ => {}
276        };
277
278        Some(())
279    }
280}
281
282impl Client for X11Client {
283    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
284        let setup = self.xcb_connection.setup();
285        setup
286            .roots
287            .iter()
288            .enumerate()
289            .filter_map(|(root_id, _)| {
290                Some(Rc::new(X11Display::new(&self.xcb_connection, root_id)?)
291                    as Rc<dyn PlatformDisplay>)
292            })
293            .collect()
294    }
295
296    fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
297        Some(Rc::new(X11Display::new(
298            &self.xcb_connection,
299            id.0 as usize,
300        )?))
301    }
302
303    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
304        Some(Rc::new(
305            X11Display::new(&self.xcb_connection, self.x_root_index)
306                .expect("There should always be a root index"),
307        ))
308    }
309
310    fn open_window(
311        &self,
312        _handle: AnyWindowHandle,
313        options: WindowParams,
314    ) -> Box<dyn PlatformWindow> {
315        let x_window = self.xcb_connection.generate_id().unwrap();
316
317        let window_ptr = Rc::new(X11WindowState::new(
318            options,
319            &self.xcb_connection,
320            self.x_root_index,
321            x_window,
322            &self.atoms,
323        ));
324
325        let screen_resources = self
326            .xcb_connection
327            .randr_get_screen_resources(x_window)
328            .unwrap()
329            .reply()
330            .expect("TODO");
331
332        let mode = screen_resources
333            .crtcs
334            .iter()
335            .find_map(|crtc| {
336                let crtc_info = self
337                    .xcb_connection
338                    .randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME)
339                    .ok()?
340                    .reply()
341                    .ok()?;
342
343                screen_resources
344                    .modes
345                    .iter()
346                    .find(|m| m.id == crtc_info.mode)
347            })
348            .expect("Unable to find screen refresh rate");
349
350        // .expect("Missing screen mode for crtc specified mode id");
351
352        let refresh_event_token = self
353            .platform_inner
354            .loop_handle
355            .insert_source(calloop::timer::Timer::immediate(), {
356                let refresh_duration = mode_refresh_rate(mode);
357                let xcb_connection = Rc::clone(&self.xcb_connection);
358                move |mut instant, (), _| {
359                    xcb_connection
360                        .send_event(
361                            false,
362                            x_window,
363                            xproto::EventMask::EXPOSURE,
364                            xproto::ExposeEvent {
365                                response_type: xproto::EXPOSE_EVENT,
366                                sequence: 0,
367                                window: x_window,
368                                x: 0,
369                                y: 0,
370                                width: 0,
371                                height: 0,
372                                count: 1,
373                            },
374                        )
375                        .unwrap();
376                    let _ = xcb_connection.flush().unwrap();
377                    // Take into account that some frames have been skipped
378                    let now = time::Instant::now();
379                    while instant < now {
380                        instant += refresh_duration;
381                    }
382                    calloop::timer::TimeoutAction::ToInstant(instant)
383                }
384            })
385            .expect("Failed to initialize refresh timer");
386
387        let window_ref = WindowRef {
388            state: Rc::clone(&window_ptr),
389            refresh_event_token,
390        };
391        self.state.borrow_mut().windows.insert(x_window, window_ref);
392        Box::new(X11Window(window_ptr))
393    }
394
395    //todo(linux)
396    fn set_cursor_style(&self, _style: CursorStyle) {}
397
398    fn get_clipboard(&self) -> Rc<RefCell<dyn ClipboardProvider>> {
399        self.state.borrow().clipboard.clone()
400    }
401
402    fn get_primary(&self) -> Rc<RefCell<dyn ClipboardProvider>> {
403        self.state.borrow().primary.clone()
404    }
405}
406
407// Adatpted from:
408// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
409pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
410    let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
411    let micros = 1_000_000_000 / millihertz;
412    log::info!("Refreshing at {} micros", micros);
413    Duration::from_micros(micros)
414}