1use std::sync::{Mutex, RwLock};
29
30use x11rb::connection::Connection;
31use x11rb::protocol::xproto::{
32 ConnectionExt as _, GetKeyboardMappingReply, Keycode, Screen, Window, BUTTON_PRESS_EVENT,
33 BUTTON_RELEASE_EVENT, KEY_PRESS_EVENT, KEY_RELEASE_EVENT, MOTION_NOTIFY_EVENT,
34};
35use x11rb::protocol::xtest::ConnectionExt as _;
36use x11rb::rust_connection::RustConnection;
37
38use xa11y_core::input::{InputProvider, Key, MouseButton, Point, ScrollDelta};
39use xa11y_core::{Error, Result};
40
41use crate::wayland_input::WaylandInputBackend;
42
43pub(crate) const XK_SHIFT_L: u32 = 0xffe1;
47pub(crate) const XK_CONTROL_L: u32 = 0xffe3;
48pub(crate) const XK_ALT_L: u32 = 0xffe9;
49pub(crate) const XK_SUPER_L: u32 = 0xffeb;
50const XK_RETURN: u32 = 0xff0d;
51const XK_ESCAPE: u32 = 0xff1b;
52const XK_BACKSPACE: u32 = 0xff08;
53const XK_TAB: u32 = 0xff09;
54const XK_DELETE: u32 = 0xffff;
55const XK_INSERT: u32 = 0xff63;
56const XK_UP: u32 = 0xff52;
57const XK_DOWN: u32 = 0xff54;
58const XK_LEFT: u32 = 0xff51;
59const XK_RIGHT: u32 = 0xff53;
60const XK_HOME: u32 = 0xff50;
61const XK_END: u32 = 0xff57;
62const XK_PAGE_UP: u32 = 0xff55;
63const XK_PAGE_DOWN: u32 = 0xff56;
64const XK_F1: u32 = 0xffbe;
65pub(crate) fn key_to_keysym(key: &Key) -> Result<u32> {
73 let keysym = match key {
74 Key::Shift => XK_SHIFT_L,
75 Key::Ctrl => XK_CONTROL_L,
76 Key::Alt => XK_ALT_L,
77 Key::Meta => XK_SUPER_L,
78 Key::Enter => XK_RETURN,
79 Key::Escape => XK_ESCAPE,
80 Key::Backspace => XK_BACKSPACE,
81 Key::Tab => XK_TAB,
82 Key::Space => 0x0020,
83 Key::Delete => XK_DELETE,
84 Key::Insert => XK_INSERT,
85 Key::ArrowUp => XK_UP,
86 Key::ArrowDown => XK_DOWN,
87 Key::ArrowLeft => XK_LEFT,
88 Key::ArrowRight => XK_RIGHT,
89 Key::Home => XK_HOME,
90 Key::End => XK_END,
91 Key::PageUp => XK_PAGE_UP,
92 Key::PageDown => XK_PAGE_DOWN,
93 Key::F(n) => {
94 if *n < 1 || *n > 24 {
95 return Err(Error::InvalidActionData {
96 message: format!("F{n} is out of range (1..=24)"),
97 });
98 }
99 XK_F1 + (*n as u32 - 1)
100 }
101 Key::Char(c) => char_keysym(*c),
102 };
103 Ok(keysym)
104}
105
106pub(crate) fn char_keysym(c: char) -> u32 {
109 let cp = c as u32;
110 if (0x20..=0x7e).contains(&cp) {
111 cp
112 } else {
113 0x0100_0000 | cp
114 }
115}
116
117struct Keymap {
121 min_keycode: u8,
122 syms_per_code: u8,
123 syms: Vec<u32>,
124}
125
126impl Keymap {
127 fn from_reply(reply: GetKeyboardMappingReply, min_keycode: u8) -> Self {
128 Self {
129 min_keycode,
130 syms_per_code: reply.keysyms_per_keycode,
131 syms: reply.keysyms,
132 }
133 }
134
135 fn lookup(&self, keysym: u32) -> Option<(Keycode, bool)> {
138 let per = self.syms_per_code as usize;
139 if per == 0 {
140 return None;
141 }
142 for (code_index, chunk) in self.syms.chunks(per).enumerate() {
143 if chunk.first() == Some(&keysym) {
147 return Some((self.min_keycode + code_index as u8, false));
148 }
149 if per >= 2 && chunk.get(1) == Some(&keysym) {
150 return Some((self.min_keycode + code_index as u8, true));
151 }
152 }
153 None
154 }
155}
156
157pub(crate) struct X11InputBackend {
159 conn: Mutex<RustConnection>,
161 root: Window,
162 keymap: RwLock<Keymap>,
163}
164
165impl X11InputBackend {
166 fn new() -> Result<Self> {
167 let (conn, screen_num) = RustConnection::connect(None).map_err(platform)?;
168 let setup = conn.setup().clone();
169 let screen: &Screen = setup
170 .roots
171 .get(screen_num)
172 .ok_or_else(|| platform_msg("X server reported no screens"))?;
173 let root = screen.root;
174
175 let min_keycode = setup.min_keycode;
176 let max_keycode = setup.max_keycode;
177 if max_keycode < min_keycode {
178 return Err(platform_msg("X server reported an empty keycode range"));
179 }
180 let count = max_keycode - min_keycode + 1;
181 let reply = conn
182 .get_keyboard_mapping(min_keycode, count)
183 .map_err(platform)?
184 .reply()
185 .map_err(platform)?;
186 let keymap = Keymap::from_reply(reply, min_keycode);
187
188 Ok(Self {
189 conn: Mutex::new(conn),
190 root,
191 keymap: RwLock::new(keymap),
192 })
193 }
194
195 fn with_conn<F, R>(&self, f: F) -> Result<R>
196 where
197 F: FnOnce(&RustConnection) -> Result<R>,
198 {
199 let guard = self.conn.lock().unwrap_or_else(|e| e.into_inner());
200 f(&guard)
201 }
202
203 fn send(&self, type_: u8, detail: u8, x: i16, y: i16) -> Result<()> {
204 self.with_conn(|conn| {
205 conn.xtest_fake_input(type_, detail, 0, self.root, x, y, 0)
206 .map_err(platform)?
207 .check()
208 .map_err(platform)?;
209 conn.flush().map_err(platform)?;
210 Ok(())
211 })
212 }
213
214 fn key_event(&self, keysym: u32, press: bool) -> Result<()> {
215 let (keycode, _shift) = {
216 let map = self.keymap.read().unwrap_or_else(|e| e.into_inner());
217 map.lookup(keysym).ok_or_else(|| Error::Unsupported {
218 feature: format!("keysym 0x{keysym:04x} has no keycode in the current X layout"),
219 })?
220 };
221 let type_ = if press {
222 KEY_PRESS_EVENT
223 } else {
224 KEY_RELEASE_EVENT
225 };
226 self.send(type_, keycode, 0, 0)
227 }
228
229 fn button_event(&self, button: u8, press: bool) -> Result<()> {
230 let type_ = if press {
231 BUTTON_PRESS_EVENT
232 } else {
233 BUTTON_RELEASE_EVENT
234 };
235 self.send(type_, button, 0, 0)
236 }
237
238 fn pointer_move(&self, to: Point) -> Result<()> {
239 self.send(MOTION_NOTIFY_EVENT, 0, clamp_coord(to.x), clamp_coord(to.y))
241 }
242
243 fn pointer_down(&self, button: MouseButton) -> Result<()> {
244 self.button_event(button_number(button), true)
245 }
246
247 fn pointer_up(&self, button: MouseButton) -> Result<()> {
248 self.button_event(button_number(button), false)
249 }
250
251 fn pointer_click(&self, at: Point, button: MouseButton, count: u32) -> Result<()> {
252 if count == 0 {
253 return Ok(());
254 }
255 self.pointer_move(at)?;
256 let btn = button_number(button);
257 for _ in 0..count {
258 self.button_event(btn, true)?;
259 self.button_event(btn, false)?;
260 }
261 Ok(())
262 }
263
264 fn pointer_scroll(&self, at: Point, delta: ScrollDelta) -> Result<()> {
265 self.pointer_move(at)?;
266 for _ in 0..delta.dy.abs() {
271 let btn = if delta.dy > 0 { 5 } else { 4 };
272 self.button_event(btn, true)?;
273 self.button_event(btn, false)?;
274 }
275 for _ in 0..delta.dx.abs() {
276 let btn = if delta.dx > 0 { 7 } else { 6 };
277 self.button_event(btn, true)?;
278 self.button_event(btn, false)?;
279 }
280 Ok(())
281 }
282
283 fn key_down(&self, key: &Key) -> Result<()> {
284 let keysym = key_to_keysym(key)?;
285 self.key_event(keysym, true)
286 }
287
288 fn key_up(&self, key: &Key) -> Result<()> {
289 let keysym = key_to_keysym(key)?;
290 self.key_event(keysym, false)
291 }
292
293 fn type_text(&self, text: &str) -> Result<()> {
294 for c in text.chars() {
299 let keysym = char_keysym(c);
300 let (keycode, needs_shift) = {
301 let map = self.keymap.read().unwrap_or_else(|e| e.into_inner());
302 match map.lookup(keysym) {
303 Some(v) => v,
304 None => {
305 return Err(Error::Unsupported {
306 feature: format!(
307 "character '{c}' (keysym 0x{keysym:04x}) has no keycode \
308 in the current X keyboard layout"
309 ),
310 });
311 }
312 }
313 };
314 if needs_shift {
315 self.key_event(XK_SHIFT_L, true)?;
316 }
317 self.send(KEY_PRESS_EVENT, keycode, 0, 0)?;
318 self.send(KEY_RELEASE_EVENT, keycode, 0, 0)?;
319 if needs_shift {
320 self.key_event(XK_SHIFT_L, false)?;
321 }
322 }
323 Ok(())
324 }
325}
326
327pub struct LinuxInputProvider {
330 backend: InputBackend,
331}
332
333enum InputBackend {
334 X11(Box<X11InputBackend>),
335 Wayland(Box<WaylandInputBackend>),
336}
337
338impl LinuxInputProvider {
339 pub fn new() -> Result<Self> {
347 let display_set = std::env::var_os("DISPLAY").is_some();
348
349 if display_set {
350 let x11 = X11InputBackend::new()?;
351 Ok(Self {
352 backend: InputBackend::X11(Box::new(x11)),
353 })
354 } else {
355 let wl = WaylandInputBackend::new()?;
356 Ok(Self {
357 backend: InputBackend::Wayland(Box::new(wl)),
358 })
359 }
360 }
361}
362
363impl InputProvider for LinuxInputProvider {
364 fn pointer_move(&self, to: Point) -> Result<()> {
365 match &self.backend {
366 InputBackend::X11(x) => x.pointer_move(to),
367 InputBackend::Wayland(w) => w.pointer_move(to),
368 }
369 }
370
371 fn pointer_down(&self, button: MouseButton) -> Result<()> {
372 match &self.backend {
373 InputBackend::X11(x) => x.pointer_down(button),
374 InputBackend::Wayland(w) => w.pointer_down(button),
375 }
376 }
377
378 fn pointer_up(&self, button: MouseButton) -> Result<()> {
379 match &self.backend {
380 InputBackend::X11(x) => x.pointer_up(button),
381 InputBackend::Wayland(w) => w.pointer_up(button),
382 }
383 }
384
385 fn pointer_click(&self, at: Point, button: MouseButton, count: u32) -> Result<()> {
386 match &self.backend {
387 InputBackend::X11(x) => x.pointer_click(at, button, count),
388 InputBackend::Wayland(w) => w.pointer_click(at, button, count),
389 }
390 }
391
392 fn pointer_scroll(&self, at: Point, delta: ScrollDelta) -> Result<()> {
393 match &self.backend {
394 InputBackend::X11(x) => x.pointer_scroll(at, delta),
395 InputBackend::Wayland(w) => w.pointer_scroll(at, delta),
396 }
397 }
398
399 fn key_down(&self, key: &Key) -> Result<()> {
400 match &self.backend {
401 InputBackend::X11(x) => x.key_down(key),
402 InputBackend::Wayland(w) => w.key_down(key),
403 }
404 }
405
406 fn key_up(&self, key: &Key) -> Result<()> {
407 match &self.backend {
408 InputBackend::X11(x) => x.key_up(key),
409 InputBackend::Wayland(w) => w.key_up(key),
410 }
411 }
412
413 fn type_text(&self, text: &str) -> Result<()> {
414 match &self.backend {
415 InputBackend::X11(x) => x.type_text(text),
416 InputBackend::Wayland(w) => w.type_text(text),
417 }
418 }
419}
420
421pub(crate) fn platform<E: std::fmt::Display>(e: E) -> Error {
422 Error::Platform {
423 code: -1,
424 message: e.to_string(),
425 }
426}
427
428pub(crate) fn platform_msg(msg: &str) -> Error {
429 Error::Platform {
430 code: -1,
431 message: msg.to_string(),
432 }
433}
434
435fn clamp_coord(v: i32) -> i16 {
438 v.clamp(i16::MIN as i32, i16::MAX as i32) as i16
439}
440
441fn button_number(button: MouseButton) -> u8 {
442 match button {
443 MouseButton::Left => 1,
444 MouseButton::Middle => 2,
445 MouseButton::Right => 3,
446 }
447}