1use std::cell::RefCell;
26use std::collections::HashMap;
27use std::thread;
28use std::time::Duration;
29
30use x11rb::connection::Connection;
31use x11rb::protocol::xfixes::ConnectionExt as XfixesExt;
32use x11rb::protocol::xproto::{
33 ConnectionExt as XprotoExt, ImageFormat, BUTTON_PRESS_EVENT, BUTTON_RELEASE_EVENT,
34 KEY_PRESS_EVENT, KEY_RELEASE_EVENT, MOTION_NOTIFY_EVENT,
35};
36use x11rb::protocol::xtest::ConnectionExt as XtestExt;
37use x11rb::rust_connection::RustConnection;
38
39use crate::computer_use::{encode_png_rgba, CuBackend, CuButton};
40
41const MAX_SCROLL_CLICKS: i32 = 100;
44
45const KEYSYM_ISO_LEVEL3_SHIFT: u32 = 0xfe03;
46const KEYSYM_MODE_SWITCH: u32 = 0xff7e;
47
48struct ServerKeymap {
50 by_sym: HashMap<u32, (u32, u32)>,
54 altgr_keycode: u32,
58}
59
60pub struct CuX11Backend {
61 conn: RustConnection,
62 root: u32,
63 has_xfixes: bool,
64 reverse_keymap: RefCell<Option<ServerKeymap>>,
68}
69
70impl CuX11Backend {
71 pub fn connect() -> Result<Self, String> {
74 let (conn, screen_num) =
75 x11rb::connect(None).map_err(|e| format!("X11 connect failed: {e}"))?;
76 let root = conn.setup().roots[screen_num].root;
77 conn.xtest_get_version(2, 2)
78 .map_err(|e| format!("xtest_get_version: {e}"))?
79 .reply()
80 .map_err(|e| format!("XTEST unavailable: {e}"))?;
81 let has_xfixes = conn
82 .xfixes_query_version(5, 0)
83 .ok()
84 .and_then(|c| c.reply().ok())
85 .is_some();
86 Ok(Self { conn, root, has_xfixes, reverse_keymap: RefCell::new(None) })
87 }
88
89 fn build_reverse_keymap(&self) -> ServerKeymap {
98 let mut km = ServerKeymap { by_sym: HashMap::new(), altgr_keycode: 0 };
99 let setup = self.conn.setup();
100 let (lo, hi) = (setup.min_keycode, setup.max_keycode);
101 let Some(reply) = self
102 .conn
103 .get_keyboard_mapping(lo, hi - lo + 1)
104 .ok()
105 .and_then(|c| c.reply().ok())
106 else {
107 return km;
108 };
109 let per = reply.keysyms_per_keycode as usize;
110 if per == 0 {
111 return km;
112 }
113 let keycode_of = |wanted: u32| {
114 reply
115 .keysyms
116 .chunks_exact(per)
117 .position(|syms| syms.contains(&wanted))
118 .map(|i| lo as u32 + i as u32)
119 };
120 km.altgr_keycode = keycode_of(KEYSYM_ISO_LEVEL3_SHIFT)
121 .or_else(|| keycode_of(KEYSYM_MODE_SWITCH))
122 .unwrap_or(0);
123 let columns: [(usize, u32); 4] = [(0, 0), (1, 1), (4, 2), (5, 3)];
125 for (col, level) in columns {
126 if col >= per || (level & 2 != 0 && km.altgr_keycode == 0) {
127 continue;
128 }
129 for (i, syms) in reply.keysyms.chunks_exact(per).enumerate() {
130 let sym = syms[col];
131 if sym != 0 {
132 km.by_sym.entry(sym).or_insert((lo as u32 + i as u32, level));
133 }
134 }
135 }
136 km
137 }
138
139 fn fake_input(&self, kind: u8, detail: u8, root: u32, x: i16, y: i16) {
142 let _ = self
143 .conn
144 .xtest_fake_input(kind, detail, x11rb::CURRENT_TIME, root, x, y, 0)
145 .map(|c| c.ignore_error());
146 let _ = self.conn.flush();
147 }
148
149 fn root_geometry(&self) -> Result<(u16, u16), String> {
150 let geo = self
151 .conn
152 .get_geometry(self.root)
153 .map_err(|e| format!("get_geometry: {e}"))?
154 .reply()
155 .map_err(|e| format!("get_geometry reply: {e}"))?;
156 Ok((geo.width, geo.height))
157 }
158
159 fn sync(&self) -> Result<(), String> {
162 self.conn
163 .get_input_focus()
164 .map_err(|e| format!("sync: {e}"))?
165 .reply()
166 .map_err(|e| format!("sync reply: {e}"))?;
167 Ok(())
168 }
169
170 fn type_with_transient_binds(
179 &self,
180 keysyms: &[u32],
181 seq: &mut dyn FnMut(&HashMap<u32, u32>),
182 ) -> Result<(), String> {
183 let setup = self.conn.setup();
184 let (lo, hi) = (setup.min_keycode, setup.max_keycode);
185 let mut chosen: Vec<u32> = Vec::with_capacity(keysyms.len());
186 let (span_lo, count, per, bound_syms) = {
187 self.conn
188 .grab_server()
189 .map_err(|e| format!("grab_server: {e}"))?;
190 let _guard = ServerGrabGuard { conn: &self.conn };
191 let reply = self
194 .conn
195 .get_keyboard_mapping(lo, hi - lo + 1)
196 .map_err(|e| format!("get_keyboard_mapping: {e}"))?
197 .reply()
198 .map_err(|e| format!("get_keyboard_mapping reply: {e}"))?;
199 let per = reply.keysyms_per_keycode as usize;
200 if per == 0 {
201 return Err("empty keymap".to_string());
202 }
203 for (i, syms) in reply.keysyms.chunks_exact(per).enumerate().rev() {
209 if syms.iter().all(|&s| s == 0) {
210 chosen.push(lo as u32 + i as u32);
211 if chosen.len() == keysyms.len() {
212 break;
213 }
214 }
215 }
216 if chosen.len() < keysyms.len() {
217 return Err(format!(
218 "only {} spare keycodes for {} unresolved keysyms",
219 chosen.len(),
220 keysyms.len()
221 ));
222 }
223 let span_lo = *chosen.last().unwrap();
230 let span_hi = chosen[0];
231 let base = ((span_lo - lo as u32) as usize) * per;
232 let end = ((span_hi - lo as u32) as usize + 1) * per;
233 let mut bound_syms = reply.keysyms[base..end].to_vec();
234 let mut map = HashMap::new();
235 for (&sym, &kc) in keysyms.iter().zip(chosen.iter()) {
236 let off = ((kc - span_lo) as usize) * per;
237 bound_syms[off] = sym;
238 if per > 1 {
239 bound_syms[off + 1] = sym;
240 }
241 map.insert(sym, kc);
242 }
243 let count = (span_hi - span_lo + 1) as u8;
244 self.conn
245 .change_keyboard_mapping(count, span_lo as u8, per as u8, &bound_syms)
246 .map_err(|e| format!("change_keyboard_mapping: {e}"))?
247 .check()
248 .map_err(|e| format!("change_keyboard_mapping check: {e}"))?;
249 self.sync()?;
252 seq(&map);
253 (span_lo, count, per, bound_syms)
254 };
257 thread::sleep(TRANSIENT_BIND_SETTLE);
260 if let Err(e) = self.restore_transient_binds(span_lo, count, per, &bound_syms, &chosen) {
261 eprintln!("[ComputerUse] transient keysym restore failed: {e}");
262 }
263 Ok(())
264 }
265
266 fn restore_transient_binds(
272 &self,
273 span_lo: u32,
274 count: u8,
275 per: usize,
276 bound_syms: &[u32],
277 chosen: &[u32],
278 ) -> Result<(), String> {
279 self.conn
280 .grab_server()
281 .map_err(|e| format!("grab_server: {e}"))?;
282 let _guard = ServerGrabGuard { conn: &self.conn };
283 let reply = self
284 .conn
285 .get_keyboard_mapping(span_lo as u8, count)
286 .map_err(|e| format!("get_keyboard_mapping: {e}"))?
287 .reply()
288 .map_err(|e| format!("get_keyboard_mapping reply: {e}"))?;
289 let cur_per = reply.keysyms_per_keycode as usize;
290 if cur_per == 0 || per == 0 {
291 return Err("empty keymap".to_string());
292 }
293 let mut restore = reply.keysyms.clone();
294 let mut changed = false;
295 for &kc in chosen {
296 let sym = bound_syms[((kc - span_lo) as usize) * per];
297 let cur = &mut restore[((kc - span_lo) as usize) * cur_per..][..cur_per];
298 let still_ours =
302 cur.contains(&sym) && cur.iter().all(|&s| s == 0 || s == sym);
303 if still_ours {
304 cur.fill(0);
305 changed = true;
306 }
307 }
308 if changed {
309 self.conn
310 .change_keyboard_mapping(count, span_lo as u8, cur_per as u8, &restore)
311 .map_err(|e| format!("change_keyboard_mapping: {e}"))?
312 .check()
313 .map_err(|e| format!("change_keyboard_mapping check: {e}"))?;
314 }
315 Ok(())
316 }
317}
318
319const TRANSIENT_BIND_SETTLE: Duration = Duration::from_millis(50);
324
325struct ServerGrabGuard<'a> {
329 conn: &'a RustConnection,
330}
331
332impl Drop for ServerGrabGuard<'_> {
333 fn drop(&mut self) {
334 let _ = self.conn.ungrab_server().map(|c| c.ignore_error());
335 let _ = self.conn.flush();
336 }
337}
338
339impl Drop for CuX11Backend {
340 fn drop(&mut self) {
344 if let Ok(cookie) = self.conn.get_input_focus() {
345 let _ = cookie.reply();
346 }
347 }
348}
349
350impl CuBackend for CuX11Backend {
351 fn name(&self) -> &'static str {
352 "x11"
353 }
354
355 fn fb_size(&self) -> Result<(i32, i32), String> {
356 let (w, h) = self.root_geometry()?;
357 Ok((w as i32, h as i32))
358 }
359
360 fn key(&self, scancode: u32, pressed: bool) {
361 if scancode > u8::MAX as u32 {
362 return;
363 }
364 let kind = if pressed { KEY_PRESS_EVENT } else { KEY_RELEASE_EVENT };
365 self.fake_input(kind, scancode as u8, x11rb::NONE, 0, 0);
366 }
367
368 fn mouse_move(&self, x: f64, y: f64) {
369 self.fake_input(
371 MOTION_NOTIFY_EVENT,
372 0,
373 self.root,
374 x.round() as i16,
375 y.round() as i16,
376 );
377 }
378
379 fn button(&self, btn: CuButton, pressed: bool) {
380 let detail = match btn {
381 CuButton::Left => 1,
382 CuButton::Middle => 2,
383 CuButton::Right => 3,
384 };
385 let kind = if pressed { BUTTON_PRESS_EVENT } else { BUTTON_RELEASE_EVENT };
386 self.fake_input(kind, detail, x11rb::NONE, 0, 0);
387 }
388
389 fn scroll(&self, dx: f64, dy: f64) {
390 let emit = |button: u8, clicks: i32| {
393 for _ in 0..clicks.min(MAX_SCROLL_CLICKS) {
394 self.fake_input(BUTTON_PRESS_EVENT, button, x11rb::NONE, 0, 0);
395 self.fake_input(BUTTON_RELEASE_EVENT, button, x11rb::NONE, 0, 0);
396 }
397 };
398 let vy = dy.round() as i32;
399 let vx = dx.round() as i32;
400 if vy != 0 {
401 emit(if vy < 0 { 4 } else { 5 }, vy.abs());
402 }
403 if vx != 0 {
404 emit(if vx < 0 { 6 } else { 7 }, vx.abs());
405 }
406 }
407
408 fn screenshot_png(&self, display: u32) -> Result<Vec<u8>, String> {
409 if display != 0 {
411 return Err(format!("Unknown display: {display}"));
412 }
413 let (w, h) = self.root_geometry()?;
414 let img = self
415 .conn
416 .get_image(ImageFormat::Z_PIXMAP, self.root, 0, 0, w, h, !0u32)
417 .map_err(|e| format!("get_image: {e}"))?
418 .reply()
419 .map_err(|e| format!("get_image reply: {e}"))?;
420 let mut data = img.data;
421 let expected = w as usize * h as usize * 4;
422 if data.len() != expected {
423 return Err(format!(
424 "unexpected image size {} for {}x{} (only 32-bpp roots are supported)",
425 data.len(), w, h
426 ));
427 }
428 if self.has_xfixes
430 && let Some(c) = self
431 .conn
432 .xfixes_get_cursor_image()
433 .ok()
434 .and_then(|c| c.reply().ok())
435 && c.width > 0 && c.height > 0 {
436 let (img_x, img_y) =
437 super::cursor_image_origin(c.x, c.y, c.xhot, c.yhot, 0, 0);
438 super::overlay_cursor(
439 &mut data,
440 w as usize * 4,
441 w as i32,
442 h as i32,
443 c.width as i32,
444 c.height as i32,
445 &c.cursor_image,
446 img_x,
447 img_y,
448 );
449 }
450 for px in data.chunks_exact_mut(4) {
453 px.swap(0, 2);
454 px[3] = 0xFF;
455 }
456 encode_png_rgba(&data, w as u32, h as u32)
457 }
458
459 fn cursor_pos(&self) -> Result<(f64, f64), String> {
460 let ptr = self
461 .conn
462 .query_pointer(self.root)
463 .map_err(|e| format!("query_pointer: {e}"))?
464 .reply()
465 .map_err(|e| format!("query_pointer reply: {e}"))?;
466 Ok((ptr.root_x as f64, ptr.root_y as f64))
467 }
468
469 fn resolve_keysyms(&self, keysyms: &[u32]) -> Vec<(u32, u32)> {
470 let mut cached = self.reverse_keymap.borrow_mut();
471 let km = cached.get_or_insert_with(|| self.build_reverse_keymap());
472 keysyms
473 .iter()
474 .map(|sym| km.by_sym.get(sym).copied().unwrap_or((0, 0)))
475 .collect()
476 }
477
478 fn altgr_keycode(&self) -> u32 {
479 let mut cached = self.reverse_keymap.borrow_mut();
480 cached.get_or_insert_with(|| self.build_reverse_keymap()).altgr_keycode
481 }
482
483 fn with_transient_keysyms(&self, keysyms: &[u32], seq: &mut dyn FnMut(&HashMap<u32, u32>)) {
484 let mut unique: Vec<u32> = Vec::with_capacity(keysyms.len());
486 for &s in keysyms {
487 if s != 0 && !unique.contains(&s) {
488 unique.push(s);
489 }
490 }
491 if unique.is_empty() {
492 seq(&HashMap::new());
493 return;
494 }
495 if let Err(e) = self.type_with_transient_binds(&unique, seq) {
496 eprintln!("[ComputerUse] transient keysym bind failed ({e}); typing without it");
497 seq(&HashMap::new());
498 }
499 }
500}