1use std::{rc::Rc, sync::Arc};
2
3use parking_lot::Mutex;
4use xcb::{x, Xid as _};
5use xkbcommon::xkb;
6
7use collections::{HashMap, HashSet};
8
9use crate::platform::linux::client::Client;
10use crate::platform::{
11 LinuxPlatformInner, PlatformWindow, X11Display, X11Window, X11WindowState, XcbAtoms,
12};
13use crate::{
14 AnyWindowHandle, Bounds, DisplayId, PlatformDisplay, PlatformInput, Point, ScrollDelta, Size,
15 TouchPhase, WindowOptions,
16};
17
18pub(crate) struct X11ClientState {
19 pub(crate) windows: HashMap<x::Window, Rc<X11WindowState>>,
20 xkb: xkbcommon::xkb::State,
21}
22
23pub(crate) struct X11Client {
24 platform_inner: Rc<LinuxPlatformInner>,
25 xcb_connection: Arc<xcb::Connection>,
26 x_root_index: i32,
27 atoms: XcbAtoms,
28 state: Mutex<X11ClientState>,
29}
30
31impl X11Client {
32 pub(crate) fn new(
33 inner: Rc<LinuxPlatformInner>,
34 xcb_connection: Arc<xcb::Connection>,
35 x_root_index: i32,
36 atoms: XcbAtoms,
37 ) -> Self {
38 let xkb_context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
39 let xkb_device_id = xkb::x11::get_core_keyboard_device_id(&xcb_connection);
40 let xkb_keymap = xkb::x11::keymap_new_from_device(
41 &xkb_context,
42 &xcb_connection,
43 xkb_device_id,
44 xkb::KEYMAP_COMPILE_NO_FLAGS,
45 );
46 let xkb_state =
47 xkb::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id);
48
49 Self {
50 platform_inner: inner,
51 xcb_connection,
52 x_root_index,
53 atoms,
54 state: Mutex::new(X11ClientState {
55 windows: HashMap::default(),
56 xkb: xkb_state,
57 }),
58 }
59 }
60
61 fn get_window(&self, win: x::Window) -> Rc<X11WindowState> {
62 let state = self.state.lock();
63 Rc::clone(&state.windows[&win])
64 }
65}
66
67impl Client for X11Client {
68 fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
69 on_finish_launching();
70 let mut windows_to_refresh = HashSet::<x::Window>::default();
71 while !self.platform_inner.state.lock().quit_requested {
72 // We prioritize work in the following order:
73 // 1. input events from X11
74 // 2. runnables for the main thread
75 // 3. drawing/presentation
76 let event = if let Some(event) = self.xcb_connection.poll_for_event().unwrap() {
77 event
78 } else if let Ok(runnable) = self.platform_inner.main_receiver.try_recv() {
79 runnable.run();
80 continue;
81 } else if let Some(x_window) = windows_to_refresh.iter().next().cloned() {
82 windows_to_refresh.remove(&x_window);
83 let window = self.get_window(x_window);
84 window.refresh();
85 window.request_refresh();
86 continue;
87 } else {
88 profiling::scope!("Wait for event");
89 self.xcb_connection.wait_for_event().unwrap()
90 };
91
92 match event {
93 xcb::Event::X(x::Event::ClientMessage(ev)) => {
94 if let x::ClientMessageData::Data32([atom, ..]) = ev.data() {
95 if atom == self.atoms.wm_del_window.resource_id() {
96 windows_to_refresh.remove(&ev.window());
97 // window "x" button clicked by user, we gracefully exit
98 let window = self.state.lock().windows.remove(&ev.window()).unwrap();
99 window.destroy();
100 let state = self.state.lock();
101 self.platform_inner.state.lock().quit_requested |=
102 state.windows.is_empty();
103 }
104 }
105 }
106 xcb::Event::X(x::Event::Expose(ev)) => {
107 windows_to_refresh.insert(ev.window());
108 }
109 xcb::Event::X(x::Event::ConfigureNotify(ev)) => {
110 let bounds = Bounds {
111 origin: Point {
112 x: ev.x().into(),
113 y: ev.y().into(),
114 },
115 size: Size {
116 width: ev.width().into(),
117 height: ev.height().into(),
118 },
119 };
120 self.get_window(ev.window()).configure(bounds)
121 }
122 xcb::Event::Present(xcb::present::Event::CompleteNotify(ev)) => {
123 windows_to_refresh.insert(ev.window());
124 }
125 xcb::Event::Present(xcb::present::Event::IdleNotify(_ev)) => {}
126 xcb::Event::X(x::Event::FocusIn(ev)) => {
127 let window = self.get_window(ev.event());
128 window.set_focused(true);
129 }
130 xcb::Event::X(x::Event::FocusOut(ev)) => {
131 let window = self.get_window(ev.event());
132 window.set_focused(false);
133 }
134 xcb::Event::X(x::Event::KeyPress(ev)) => {
135 let window = self.get_window(ev.event());
136 let modifiers = super::modifiers_from_state(ev.state());
137 let keystroke = {
138 let code = ev.detail().into();
139 let mut state = self.state.lock();
140 let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
141 state.xkb.update_key(code, xkb::KeyDirection::Down);
142 keystroke
143 };
144
145 window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
146 keystroke,
147 is_held: false,
148 }));
149 }
150 xcb::Event::X(x::Event::KeyRelease(ev)) => {
151 let window = self.get_window(ev.event());
152 let modifiers = super::modifiers_from_state(ev.state());
153 let keystroke = {
154 let code = ev.detail().into();
155 let mut state = self.state.lock();
156 let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
157 state.xkb.update_key(code, xkb::KeyDirection::Up);
158 keystroke
159 };
160
161 window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke }));
162 }
163 xcb::Event::X(x::Event::ButtonPress(ev)) => {
164 let window = self.get_window(ev.event());
165 let modifiers = super::modifiers_from_state(ev.state());
166 let position =
167 Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
168 if let Some(button) = super::button_of_key(ev.detail()) {
169 window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
170 button,
171 position,
172 modifiers,
173 click_count: 1,
174 }));
175 } else if ev.detail() >= 4 && ev.detail() <= 5 {
176 // https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11
177 let delta_x = if ev.detail() == 4 { 1.0 } else { -1.0 };
178 window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
179 position,
180 delta: ScrollDelta::Lines(Point::new(0.0, delta_x)),
181 modifiers,
182 touch_phase: TouchPhase::default(),
183 }));
184 } else {
185 log::warn!("Unknown button press: {ev:?}");
186 }
187 }
188 xcb::Event::X(x::Event::ButtonRelease(ev)) => {
189 let window = self.get_window(ev.event());
190 let modifiers = super::modifiers_from_state(ev.state());
191 let position =
192 Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
193 if let Some(button) = super::button_of_key(ev.detail()) {
194 window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
195 button,
196 position,
197 modifiers,
198 click_count: 1,
199 }));
200 }
201 }
202 xcb::Event::X(x::Event::MotionNotify(ev)) => {
203 let window = self.get_window(ev.event());
204 let pressed_button = super::button_from_state(ev.state());
205 let position =
206 Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
207 let modifiers = super::modifiers_from_state(ev.state());
208 window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
209 pressed_button,
210 position,
211 modifiers,
212 }));
213 }
214 xcb::Event::X(x::Event::LeaveNotify(ev)) => {
215 let window = self.get_window(ev.event());
216 let pressed_button = super::button_from_state(ev.state());
217 let position =
218 Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
219 let modifiers = super::modifiers_from_state(ev.state());
220 window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
221 pressed_button,
222 position,
223 modifiers,
224 }));
225 }
226 _ => {}
227 }
228 }
229
230 if let Some(ref mut fun) = self.platform_inner.callbacks.lock().quit {
231 fun();
232 }
233 }
234
235 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
236 let setup = self.xcb_connection.get_setup();
237 setup
238 .roots()
239 .enumerate()
240 .map(|(root_id, _)| {
241 Rc::new(X11Display::new(&self.xcb_connection, root_id as i32))
242 as Rc<dyn PlatformDisplay>
243 })
244 .collect()
245 }
246
247 fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
248 Some(Rc::new(X11Display::new(&self.xcb_connection, id.0 as i32)))
249 }
250
251 fn open_window(
252 &self,
253 _handle: AnyWindowHandle,
254 options: WindowOptions,
255 ) -> Box<dyn PlatformWindow> {
256 let x_window = self.xcb_connection.generate_id();
257
258 let window_ptr = Rc::new(X11WindowState::new(
259 options,
260 &self.xcb_connection,
261 self.x_root_index,
262 x_window,
263 &self.atoms,
264 ));
265 window_ptr.request_refresh();
266
267 self.state
268 .lock()
269 .windows
270 .insert(x_window, Rc::clone(&window_ptr));
271 Box::new(X11Window(window_ptr))
272 }
273}