client.rs

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