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