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