client.rs

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