1use std::sync::{Mutex, RwLock};
20
21use x11rb::connection::Connection;
22use x11rb::protocol::xproto::{
23 ConnectionExt as _, GetKeyboardMappingReply, Keycode, Screen, Window, BUTTON_PRESS_EVENT,
24 BUTTON_RELEASE_EVENT, KEY_PRESS_EVENT, KEY_RELEASE_EVENT, MOTION_NOTIFY_EVENT,
25};
26use x11rb::protocol::xtest::ConnectionExt as _;
27use x11rb::rust_connection::RustConnection;
28
29use xa11y_core::input::{InputProvider, Key, MouseButton, Point, ScrollDelta};
30use xa11y_core::{Error, Result};
31
32const XK_SHIFT_L: u32 = 0xffe1;
35const XK_CONTROL_L: u32 = 0xffe3;
36const XK_ALT_L: u32 = 0xffe9;
37const XK_SUPER_L: u32 = 0xffeb;
38const XK_RETURN: u32 = 0xff0d;
39const XK_ESCAPE: u32 = 0xff1b;
40const XK_BACKSPACE: u32 = 0xff08;
41const XK_TAB: u32 = 0xff09;
42const XK_DELETE: u32 = 0xffff;
43const XK_INSERT: u32 = 0xff63;
44const XK_UP: u32 = 0xff52;
45const XK_DOWN: u32 = 0xff54;
46const XK_LEFT: u32 = 0xff51;
47const XK_RIGHT: u32 = 0xff53;
48const XK_HOME: u32 = 0xff50;
49const XK_END: u32 = 0xff57;
50const XK_PAGE_UP: u32 = 0xff55;
51const XK_PAGE_DOWN: u32 = 0xff56;
52const XK_F1: u32 = 0xffbe;
53struct Keymap {
59 min_keycode: u8,
60 syms_per_code: u8,
61 syms: Vec<u32>,
62}
63
64impl Keymap {
65 fn from_reply(reply: GetKeyboardMappingReply, min_keycode: u8) -> Self {
66 Self {
67 min_keycode,
68 syms_per_code: reply.keysyms_per_keycode,
69 syms: reply.keysyms,
70 }
71 }
72
73 fn lookup(&self, keysym: u32) -> Option<(Keycode, bool)> {
76 let per = self.syms_per_code as usize;
77 if per == 0 {
78 return None;
79 }
80 for (code_index, chunk) in self.syms.chunks(per).enumerate() {
81 if chunk.first() == Some(&keysym) {
85 return Some((self.min_keycode + code_index as u8, false));
86 }
87 if per >= 2 && chunk.get(1) == Some(&keysym) {
88 return Some((self.min_keycode + code_index as u8, true));
89 }
90 }
91 None
92 }
93}
94
95pub struct LinuxInputProvider {
97 conn: Mutex<RustConnection>,
99 root: Window,
100 keymap: RwLock<Keymap>,
101}
102
103impl LinuxInputProvider {
104 pub fn new() -> Result<Self> {
110 let display_set = std::env::var_os("DISPLAY").is_some();
111 let wayland = std::env::var_os("WAYLAND_DISPLAY").is_some();
112 if !display_set {
113 let feature = if wayland {
114 "input simulation on Wayland (X11 DISPLAY not set)".to_string()
115 } else {
116 "input simulation (no X11 DISPLAY)".to_string()
117 };
118 return Err(Error::Unsupported { feature });
119 }
120
121 let (conn, screen_num) = RustConnection::connect(None).map_err(platform)?;
122 let setup = conn.setup().clone();
123 let screen: &Screen = setup
124 .roots
125 .get(screen_num)
126 .ok_or_else(|| platform_msg("X server reported no screens"))?;
127 let root = screen.root;
128
129 let min_keycode = setup.min_keycode;
130 let max_keycode = setup.max_keycode;
131 if max_keycode < min_keycode {
132 return Err(platform_msg("X server reported an empty keycode range"));
133 }
134 let count = max_keycode - min_keycode + 1;
135 let reply = conn
136 .get_keyboard_mapping(min_keycode, count)
137 .map_err(platform)?
138 .reply()
139 .map_err(platform)?;
140 let keymap = Keymap::from_reply(reply, min_keycode);
141
142 Ok(Self {
143 conn: Mutex::new(conn),
144 root,
145 keymap: RwLock::new(keymap),
146 })
147 }
148
149 fn with_conn<F, R>(&self, f: F) -> Result<R>
150 where
151 F: FnOnce(&RustConnection) -> Result<R>,
152 {
153 let guard = self.conn.lock().unwrap_or_else(|e| e.into_inner());
154 f(&guard)
155 }
156
157 fn send(&self, type_: u8, detail: u8, x: i16, y: i16) -> Result<()> {
158 self.with_conn(|conn| {
159 conn.xtest_fake_input(type_, detail, 0, self.root, x, y, 0)
160 .map_err(platform)?
161 .check()
162 .map_err(platform)?;
163 conn.flush().map_err(platform)?;
164 Ok(())
165 })
166 }
167
168 fn key_event(&self, keysym: u32, press: bool) -> Result<()> {
169 let (keycode, _shift) = {
170 let map = self.keymap.read().unwrap_or_else(|e| e.into_inner());
171 map.lookup(keysym).ok_or_else(|| Error::Unsupported {
172 feature: format!("keysym 0x{keysym:04x} has no keycode in the current X layout"),
173 })?
174 };
175 let type_ = if press {
176 KEY_PRESS_EVENT
177 } else {
178 KEY_RELEASE_EVENT
179 };
180 self.send(type_, keycode, 0, 0)
181 }
182
183 fn button_event(&self, button: u8, press: bool) -> Result<()> {
184 let type_ = if press {
185 BUTTON_PRESS_EVENT
186 } else {
187 BUTTON_RELEASE_EVENT
188 };
189 self.send(type_, button, 0, 0)
190 }
191
192 fn keysym_for(&self, key: &Key) -> Result<u32> {
195 let keysym = match key {
196 Key::Shift => XK_SHIFT_L,
197 Key::Ctrl => XK_CONTROL_L,
198 Key::Alt => XK_ALT_L,
199 Key::Meta => XK_SUPER_L,
200 Key::Enter => XK_RETURN,
201 Key::Escape => XK_ESCAPE,
202 Key::Backspace => XK_BACKSPACE,
203 Key::Tab => XK_TAB,
204 Key::Space => 0x0020,
205 Key::Delete => XK_DELETE,
206 Key::Insert => XK_INSERT,
207 Key::ArrowUp => XK_UP,
208 Key::ArrowDown => XK_DOWN,
209 Key::ArrowLeft => XK_LEFT,
210 Key::ArrowRight => XK_RIGHT,
211 Key::Home => XK_HOME,
212 Key::End => XK_END,
213 Key::PageUp => XK_PAGE_UP,
214 Key::PageDown => XK_PAGE_DOWN,
215 Key::F(n) => {
216 if *n < 1 || *n > 24 {
217 return Err(Error::InvalidActionData {
218 message: format!("F{n} is out of range (1..=24)"),
219 });
220 }
221 XK_F1 + (*n as u32 - 1)
222 }
223 Key::Char(c) => char_keysym(*c),
224 };
225 Ok(keysym)
226 }
227}
228
229fn char_keysym(c: char) -> u32 {
232 let cp = c as u32;
233 if (0x20..=0x7e).contains(&cp) {
234 cp
235 } else {
236 0x0100_0000 | cp
237 }
238}
239
240fn platform<E: std::fmt::Display>(e: E) -> Error {
241 Error::Platform {
242 code: -1,
243 message: e.to_string(),
244 }
245}
246
247fn platform_msg(msg: &str) -> Error {
248 Error::Platform {
249 code: -1,
250 message: msg.to_string(),
251 }
252}
253
254fn clamp_coord(v: i32) -> i16 {
257 v.clamp(i16::MIN as i32, i16::MAX as i32) as i16
258}
259
260impl InputProvider for LinuxInputProvider {
261 fn pointer_move(&self, to: Point) -> Result<()> {
262 self.send(MOTION_NOTIFY_EVENT, 0, clamp_coord(to.x), clamp_coord(to.y))
264 }
265
266 fn pointer_down(&self, button: MouseButton) -> Result<()> {
267 self.button_event(button_number(button), true)
268 }
269
270 fn pointer_up(&self, button: MouseButton) -> Result<()> {
271 self.button_event(button_number(button), false)
272 }
273
274 fn pointer_click(&self, at: Point, button: MouseButton, count: u32) -> Result<()> {
275 if count == 0 {
276 return Ok(());
277 }
278 self.pointer_move(at)?;
279 let btn = button_number(button);
280 for _ in 0..count {
281 self.button_event(btn, true)?;
282 self.button_event(btn, false)?;
283 }
284 Ok(())
285 }
286
287 fn pointer_scroll(&self, at: Point, delta: ScrollDelta) -> Result<()> {
288 self.pointer_move(at)?;
289 for _ in 0..delta.dy.abs() {
296 let btn = if delta.dy > 0 { 5 } else { 4 };
297 self.button_event(btn, true)?;
298 self.button_event(btn, false)?;
299 }
300 for _ in 0..delta.dx.abs() {
301 let btn = if delta.dx > 0 { 7 } else { 6 };
302 self.button_event(btn, true)?;
303 self.button_event(btn, false)?;
304 }
305 Ok(())
306 }
307
308 fn key_down(&self, key: &Key) -> Result<()> {
309 let keysym = self.keysym_for(key)?;
310 self.key_event(keysym, true)
311 }
312
313 fn key_up(&self, key: &Key) -> Result<()> {
314 let keysym = self.keysym_for(key)?;
315 self.key_event(keysym, false)
316 }
317
318 fn type_text(&self, text: &str) -> Result<()> {
319 for c in text.chars() {
324 let keysym = char_keysym(c);
325 let (keycode, needs_shift) = {
326 let map = self.keymap.read().unwrap_or_else(|e| e.into_inner());
327 match map.lookup(keysym) {
328 Some(v) => v,
329 None => {
330 return Err(Error::Unsupported {
331 feature: format!(
332 "character '{c}' (keysym 0x{keysym:04x}) has no keycode \
333 in the current X keyboard layout"
334 ),
335 });
336 }
337 }
338 };
339 if needs_shift {
340 self.key_event(XK_SHIFT_L, true)?;
341 }
342 self.send(KEY_PRESS_EVENT, keycode, 0, 0)?;
343 self.send(KEY_RELEASE_EVENT, keycode, 0, 0)?;
344 if needs_shift {
345 self.key_event(XK_SHIFT_L, false)?;
346 }
347 }
348 Ok(())
349 }
350}
351
352fn button_number(button: MouseButton) -> u8 {
353 match button {
354 MouseButton::Left => 1,
355 MouseButton::Middle => 2,
356 MouseButton::Right => 3,
357 }
358}