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}