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}