1use std::{rc::Rc, sync::Arc};
2
3use parking_lot::Mutex;
4use xcb::{x, Xid as _};
5use xkbcommon::xkb;
6
7use collections::HashMap;
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 //Note: here and below, don't keep the lock() open when calling
71 // into window functions as they may invoke callbacks that need
72 // to immediately access the platform (self).
73 while !self.platform_inner.state.lock().quit_requested {
74 let event = self.xcb_connection.wait_for_event().unwrap();
75 match event {
76 xcb::Event::X(x::Event::ClientMessage(ev)) => {
77 if let x::ClientMessageData::Data32([atom, ..]) = ev.data() {
78 if atom == self.atoms.wm_del_window.resource_id() {
79 // window "x" button clicked by user, we gracefully exit
80 let window = self.state.lock().windows.remove(&ev.window()).unwrap();
81 window.destroy();
82 let state = self.state.lock();
83 self.platform_inner.state.lock().quit_requested |=
84 state.windows.is_empty();
85 }
86 }
87 }
88 xcb::Event::X(x::Event::Expose(ev)) => {
89 self.get_window(ev.window()).refresh();
90 }
91 xcb::Event::X(x::Event::ConfigureNotify(ev)) => {
92 let bounds = Bounds {
93 origin: Point {
94 x: ev.x().into(),
95 y: ev.y().into(),
96 },
97 size: Size {
98 width: ev.width().into(),
99 height: ev.height().into(),
100 },
101 };
102 self.get_window(ev.window()).configure(bounds)
103 }
104 xcb::Event::Present(xcb::present::Event::CompleteNotify(ev)) => {
105 let window = self.get_window(ev.window());
106 window.refresh();
107 window.request_refresh();
108 }
109 xcb::Event::Present(xcb::present::Event::IdleNotify(_ev)) => {}
110 xcb::Event::X(x::Event::FocusIn(ev)) => {
111 let window = self.get_window(ev.event());
112 window.set_focused(true);
113 }
114 xcb::Event::X(x::Event::FocusOut(ev)) => {
115 let window = self.get_window(ev.event());
116 window.set_focused(false);
117 }
118 xcb::Event::X(x::Event::KeyPress(ev)) => {
119 let window = self.get_window(ev.event());
120 let modifiers = super::modifiers_from_state(ev.state());
121 let key = {
122 let code = ev.detail().into();
123 let mut state = self.state.lock();
124 let key = state.xkb.key_get_utf8(code);
125 state.xkb.update_key(code, xkb::KeyDirection::Down);
126 key
127 };
128 window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
129 keystroke: crate::Keystroke {
130 modifiers,
131 key,
132 ime_key: None,
133 },
134 is_held: false,
135 }));
136 }
137 xcb::Event::X(x::Event::KeyRelease(ev)) => {
138 let window = self.get_window(ev.event());
139 let modifiers = super::modifiers_from_state(ev.state());
140 let key = {
141 let code = ev.detail().into();
142 let mut state = self.state.lock();
143 let key = state.xkb.key_get_utf8(code);
144 state.xkb.update_key(code, xkb::KeyDirection::Up);
145 key
146 };
147 window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent {
148 keystroke: crate::Keystroke {
149 modifiers,
150 key,
151 ime_key: None,
152 },
153 }));
154 }
155 xcb::Event::X(x::Event::ButtonPress(ev)) => {
156 let window = self.get_window(ev.event());
157 let modifiers = super::modifiers_from_state(ev.state());
158 let position =
159 Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
160 if let Some(button) = super::button_of_key(ev.detail()) {
161 window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
162 button,
163 position,
164 modifiers,
165 click_count: 1,
166 }));
167 } else if ev.detail() >= 4 && ev.detail() <= 5 {
168 // https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11
169 let delta_x = if ev.detail() == 4 { 1.0 } else { -1.0 };
170 window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
171 position,
172 delta: ScrollDelta::Lines(Point::new(0.0, delta_x)),
173 modifiers,
174 touch_phase: TouchPhase::default(),
175 }));
176 } else {
177 log::warn!("Unknown button press: {ev:?}");
178 }
179 }
180 xcb::Event::X(x::Event::ButtonRelease(ev)) => {
181 let window = self.get_window(ev.event());
182 let modifiers = super::modifiers_from_state(ev.state());
183 let position =
184 Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
185 if let Some(button) = super::button_of_key(ev.detail()) {
186 window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
187 button,
188 position,
189 modifiers,
190 click_count: 1,
191 }));
192 }
193 }
194 xcb::Event::X(x::Event::MotionNotify(ev)) => {
195 let window = self.get_window(ev.event());
196 let pressed_button = super::button_from_state(ev.state());
197 let position =
198 Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
199 let modifiers = super::modifiers_from_state(ev.state());
200 window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
201 pressed_button,
202 position,
203 modifiers,
204 }));
205 }
206 xcb::Event::X(x::Event::LeaveNotify(ev)) => {
207 let window = self.get_window(ev.event());
208 let pressed_button = super::button_from_state(ev.state());
209 let position =
210 Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
211 let modifiers = super::modifiers_from_state(ev.state());
212 window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
213 pressed_button,
214 position,
215 modifiers,
216 }));
217 }
218 _ => {}
219 }
220
221 if let Ok(runnable) = self.platform_inner.main_receiver.try_recv() {
222 runnable.run();
223 }
224 }
225
226 if let Some(ref mut fun) = self.platform_inner.callbacks.lock().quit {
227 fun();
228 }
229 }
230 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
231 let setup = self.xcb_connection.get_setup();
232 setup
233 .roots()
234 .enumerate()
235 .map(|(root_id, _)| {
236 Rc::new(X11Display::new(&self.xcb_connection, root_id as i32))
237 as Rc<dyn PlatformDisplay>
238 })
239 .collect()
240 }
241 fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
242 Some(Rc::new(X11Display::new(&self.xcb_connection, id.0 as i32)))
243 }
244
245 fn open_window(
246 &self,
247 _handle: AnyWindowHandle,
248 options: WindowOptions,
249 ) -> Box<dyn PlatformWindow> {
250 let x_window = self.xcb_connection.generate_id();
251
252 let window_ptr = Rc::new(X11WindowState::new(
253 options,
254 &self.xcb_connection,
255 self.x_root_index,
256 x_window,
257 &self.atoms,
258 ));
259 window_ptr.request_refresh();
260
261 self.state
262 .lock()
263 .windows
264 .insert(x_window, Rc::clone(&window_ptr));
265 Box::new(X11Window(window_ptr))
266 }
267}