1use std::collections::HashMap;
29use std::io::ErrorKind;
30use std::os::fd::{AsRawFd, OwnedFd};
31use std::sync::atomic::{AtomicBool, Ordering};
32use std::sync::{Arc, Mutex};
33use std::time::{SystemTime, UNIX_EPOCH};
34
35use reis::PendingRequestResult;
36use reis::ei;
37use reis::event::{DeviceCapability, EiEvent, EiEventConverter};
38use reis::handshake::ei_handshake_blocking;
39
40use crate::wayland::keymap::{KeymapPolicy, compile_keymap};
41use crate::wayland::wlclient::wake_pipe;
42
43const EVDEV_OFFSET: u32 = 8;
45
46type Ready = Arc<(Mutex<Option<Result<(), String>>>, std::sync::Condvar)>;
49
50enum Cmd {
52 MotionAbsolute {
53 x: f32,
54 y: f32,
55 },
56 MotionRelative {
57 dx: f32,
58 dy: f32,
59 },
60 Button {
61 button: u32,
62 pressed: bool,
63 },
64 Scroll {
65 dx: f32,
66 dy: f32,
67 finish: bool,
68 },
69 ScrollDiscrete {
70 x: i32,
71 y: i32,
72 },
73 Key {
76 xkb_keycode: u32,
77 keysym: Option<u32>,
78 pressed: bool,
79 },
80}
81
82pub(crate) struct EiInjector {
84 queue: Arc<Mutex<Vec<Cmd>>>,
85 wake: OwnedFd,
86 alive: Arc<AtomicBool>,
87}
88
89impl EiInjector {
90 pub(crate) fn spawn(fd: OwnedFd) -> Result<Self, String> {
94 let stream = std::os::unix::net::UnixStream::from(fd);
95 let context = ei::Context::new(stream).map_err(|e| format!("EIS context: {e}"))?;
96 let (wake_rd, wake_wr) = wake_pipe()?;
97 let queue = Arc::new(Mutex::new(Vec::new()));
98 let alive = Arc::new(AtomicBool::new(true));
99 let ready = Arc::new((
100 Mutex::new(None::<Result<(), String>>),
101 std::sync::Condvar::new(),
102 ));
103
104 let (q, a, r) = (queue.clone(), alive.clone(), ready.clone());
105 std::thread::Builder::new()
106 .name("pf-host-eis".into())
107 .spawn(move || Worker::run(context, wake_rd, q, a, r))
108 .map_err(|e| format!("spawn EIS thread: {e}"))?;
109
110 let (lock, cvar) = &*ready;
111 let mut slot = lock.lock().unwrap();
112 while slot.is_none() {
113 slot = cvar.wait(slot).unwrap();
114 }
115 slot.take().unwrap()?;
116 Ok(Self {
117 queue,
118 wake: wake_wr,
119 alive,
120 })
121 }
122
123 pub(crate) fn alive(&self) -> bool {
124 self.alive.load(Ordering::Relaxed)
125 }
126
127 fn push(&self, cmd: Cmd) {
128 if !self.alive() {
129 return;
130 }
131 self.queue.lock().unwrap().push(cmd);
132 let _ = nix_write(self.wake.as_raw_fd());
133 }
134
135 pub(crate) fn pointer_motion_abs(&self, x: f64, y: f64) {
136 self.push(Cmd::MotionAbsolute {
137 x: x as f32,
138 y: y as f32,
139 });
140 }
141
142 pub(crate) fn pointer_motion(&self, dx: f64, dy: f64) {
143 self.push(Cmd::MotionRelative {
144 dx: dx as f32,
145 dy: dy as f32,
146 });
147 }
148
149 pub(crate) fn pointer_button(&self, button: i32, pressed: bool) {
150 self.push(Cmd::Button {
151 button: button as u32,
152 pressed,
153 });
154 }
155
156 pub(crate) fn pointer_axis(&self, dx: f64, dy: f64, finish: bool) {
157 self.push(Cmd::Scroll {
158 dx: dx as f32,
159 dy: dy as f32,
160 finish,
161 });
162 }
163
164 pub(crate) fn pointer_axis_discrete(&self, axis: u32, steps: i32) {
165 let v = steps * 120;
167 let (x, y) = if axis == 0 { (0, v) } else { (v, 0) };
168 self.push(Cmd::ScrollDiscrete { x, y });
169 }
170
171 pub(crate) fn key(&self, xkb_keycode: u32, keysym: Option<u32>, pressed: bool) {
172 self.push(Cmd::Key {
173 xkb_keycode,
174 keysym,
175 pressed,
176 });
177 }
178}
179
180fn nix_write(fd: i32) -> std::io::Result<()> {
182 let one = [1u8];
183 let n = unsafe { libc::write(fd, one.as_ptr() as *const libc::c_void, 1) };
184 if n < 0 {
185 let e = std::io::Error::last_os_error();
186 if e.kind() != ErrorKind::WouldBlock {
187 return Err(e);
188 }
189 }
190 Ok(())
191}
192
193struct Device {
195 device: ei::Device,
196 resumed: bool,
197 emulating: bool,
198 sequence: u32,
199 pointer: Option<ei::Pointer>,
200 pointer_abs: Option<ei::PointerAbsolute>,
201 button: Option<ei::Button>,
202 scroll: Option<ei::Scroll>,
203 keyboard: Option<ei::Keyboard>,
204 keymap: Option<KeymapPolicy>,
205}
206
207struct Worker {
208 context: ei::Context,
209 converter: EiEventConverter,
210 devices: Vec<Device>,
211 last_serial: u32,
212 mod_refs: HashMap<u32, u32>,
215 pressed: HashMap<u32, (u32, Vec<u32>)>,
218}
219
220impl Worker {
221 fn run(
222 context: ei::Context,
223 wake_rd: OwnedFd,
224 queue: Arc<Mutex<Vec<Cmd>>>,
225 alive: Arc<AtomicBool>,
226 ready: Ready,
227 ) {
228 let resp = match ei_handshake_blocking(
229 &context,
230 "pixelflux",
231 ei::handshake::ContextType::Sender,
232 ) {
233 Ok(resp) => resp,
234 Err(e) => {
235 Self::signal(&ready, Err(format!("EIS handshake: {e}")));
236 alive.store(false, Ordering::Relaxed);
237 return;
238 }
239 };
240 let mut worker = Worker {
241 last_serial: resp.serial,
242 converter: EiEventConverter::new(&context, resp),
243 context,
244 devices: Vec::new(),
245 mod_refs: HashMap::new(),
246 pressed: HashMap::new(),
247 };
248 let _ = worker.context.flush();
249
250 let deadline = std::time::Instant::now() + std::time::Duration::from_millis(1500);
253 let mut reported = false;
254 loop {
255 if !reported && (worker.has(|d| d.resumed) || std::time::Instant::now() >= deadline) {
256 let bound = worker.devices.iter().any(|d| {
257 d.keyboard.is_some() || d.pointer.is_some() || d.pointer_abs.is_some()
258 });
259 if !bound {
260 Self::signal(
261 &ready,
262 Err("EIS session bound no keyboard or pointer".into()),
263 );
264 alive.store(false, Ordering::Relaxed);
265 return;
266 }
267 Self::signal(&ready, Ok(()));
268 reported = true;
269 }
270 let timeout = if reported { -1 } else { 100 };
271 if !worker.poll(wake_rd.as_raw_fd(), timeout) {
272 break;
273 }
274 if worker.pump_socket().is_err() {
275 break;
276 }
277 if reported {
278 worker.drain(&queue);
279 }
280 }
281 alive.store(false, Ordering::Relaxed);
282 if !reported {
283 Self::signal(&ready, Err("EIS session closed before it was ready".into()));
284 }
285 }
286
287 fn signal(ready: &Ready, result: Result<(), String>) {
288 let (lock, cvar) = &**ready;
289 *lock.lock().unwrap() = Some(result);
290 cvar.notify_all();
291 }
292
293 fn has(&self, f: impl Fn(&Device) -> bool) -> bool {
294 self.devices.iter().any(f)
295 }
296
297 fn poll(&self, wake_fd: i32, timeout: i32) -> bool {
300 let mut fds = [
301 libc::pollfd {
302 fd: self.context.as_raw_fd(),
303 events: libc::POLLIN,
304 revents: 0,
305 },
306 libc::pollfd {
307 fd: wake_fd,
308 events: libc::POLLIN,
309 revents: 0,
310 },
311 ];
312 let n = unsafe { libc::poll(fds.as_mut_ptr(), 2, timeout) };
313 if n < 0 {
314 return std::io::Error::last_os_error().kind() == ErrorKind::Interrupted;
315 }
316 let broken = libc::POLLHUP | libc::POLLERR | libc::POLLNVAL;
317 if (fds[0].revents | fds[1].revents) & broken != 0 {
320 return false;
321 }
322 if fds[1].revents & libc::POLLIN != 0 {
323 let mut buf = [0u8; 64];
324 while unsafe { libc::read(wake_fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) }
325 == buf.len() as isize
326 {}
327 }
328 true
329 }
330
331 fn pump_socket(&mut self) -> Result<(), ()> {
333 match self.context.read() {
334 Ok(_) => {}
335 Err(e) if e.kind() == ErrorKind::WouldBlock => return Ok(()),
336 Err(_) => return Err(()),
337 }
338 while let Some(result) = self.context.pending_event() {
339 match result {
340 PendingRequestResult::Request(event) => {
341 if self.converter.handle_event(event).is_err() {
342 return Err(());
343 }
344 }
345 PendingRequestResult::ParseError(_) => return Err(()),
346 PendingRequestResult::InvalidObject(_) => {}
347 }
348 }
349 while let Some(event) = self.converter.next_event() {
350 self.handle(event);
351 }
352 Ok(())
353 }
354
355 fn handle(&mut self, event: EiEvent) {
356 match event {
357 EiEvent::SeatAdded(evt) => {
358 evt.seat.bind_capabilities(
359 DeviceCapability::Pointer
360 | DeviceCapability::PointerAbsolute
361 | DeviceCapability::Keyboard
362 | DeviceCapability::Button
363 | DeviceCapability::Scroll
364 | DeviceCapability::Touch,
365 );
366 let _ = self.context.flush();
367 }
368 EiEvent::DeviceAdded(evt) => {
369 let device = evt.device;
370 let keymap = device
371 .keymap()
372 .and_then(keymap_policy)
373 .filter(|_| device.interface::<ei::Keyboard>().is_some());
374 self.devices.push(Device {
375 pointer: device.interface(),
376 pointer_abs: device.interface(),
377 button: device.interface(),
378 scroll: device.interface(),
379 keyboard: device.interface(),
380 keymap,
381 device: device.device().clone(),
382 resumed: false,
383 emulating: false,
384 sequence: 0,
385 });
386 }
387 EiEvent::DeviceRemoved(evt) => {
388 self.devices.retain(|d| d.device != *evt.device.device());
389 }
390 EiEvent::DevicePaused(evt) => {
391 if let Some(d) = self.find_mut(&evt.device) {
392 d.resumed = false;
393 d.emulating = false;
394 }
395 }
396 EiEvent::DeviceResumed(evt) => {
397 self.last_serial = evt.serial;
398 if let Some(d) = self.find_mut(&evt.device) {
399 d.resumed = true;
400 d.emulating = false;
401 }
402 }
403 EiEvent::KeyboardModifiers(evt) => {
404 self.last_serial = evt.serial;
405 }
406 EiEvent::Disconnected(_) => {
407 self.devices.clear();
408 }
409 _ => {}
410 }
411 }
412
413 fn find_mut(&mut self, device: &reis::event::Device) -> Option<&mut Device> {
414 self.devices
415 .iter_mut()
416 .find(|d| d.device == *device.device())
417 }
418
419 fn drain(&mut self, queue: &Arc<Mutex<Vec<Cmd>>>) {
420 let cmds = std::mem::take(&mut *queue.lock().unwrap());
421 for cmd in cmds {
422 self.emit(cmd);
423 }
424 let _ = self.context.flush();
425 }
426
427 fn emit(&mut self, cmd: Cmd) {
428 match cmd {
429 Cmd::MotionAbsolute { x, y } => {
430 if let Some(i) = self.ready_index(|d| d.pointer_abs.is_some()) {
431 self.start(i);
432 self.devices[i]
433 .pointer_abs
434 .as_ref()
435 .unwrap()
436 .motion_absolute(x, y);
437 self.frame(i);
438 }
439 }
440 Cmd::MotionRelative { dx, dy } => {
441 if let Some(i) = self.ready_index(|d| d.pointer.is_some()) {
442 self.start(i);
443 self.devices[i]
444 .pointer
445 .as_ref()
446 .unwrap()
447 .motion_relative(dx, dy);
448 self.frame(i);
449 }
450 }
451 Cmd::Button { button, pressed } => {
452 if let Some(i) = self.ready_index(|d| d.button.is_some()) {
453 self.start(i);
454 self.devices[i]
455 .button
456 .as_ref()
457 .unwrap()
458 .button(button, button_state(pressed));
459 self.frame(i);
460 }
461 }
462 Cmd::Scroll { dx, dy, finish } => {
463 if let Some(i) = self.ready_index(|d| d.scroll.is_some()) {
464 self.start(i);
465 let scroll = self.devices[i].scroll.as_ref().unwrap();
466 if dx != 0.0 || dy != 0.0 {
467 scroll.scroll(dx, dy);
468 }
469 if finish {
470 scroll.scroll_stop(1, 1, 0);
471 }
472 self.frame(i);
473 }
474 }
475 Cmd::ScrollDiscrete { x, y } => {
476 if let Some(i) = self.ready_index(|d| d.scroll.is_some()) {
477 self.start(i);
478 self.devices[i]
479 .scroll
480 .as_ref()
481 .unwrap()
482 .scroll_discrete(x, y);
483 self.frame(i);
484 }
485 }
486 Cmd::Key {
487 xkb_keycode,
488 keysym,
489 pressed,
490 } => self.emit_key(xkb_keycode, keysym, pressed),
491 }
492 }
493
494 fn emit_key(&mut self, xkb_keycode: u32, keysym: Option<u32>, pressed: bool) {
495 let Some(i) = self.ready_index(|d| d.keyboard.is_some()) else {
496 return;
497 };
498 self.start(i);
499 if pressed {
500 let (evdev, mods) = self.resolve_key(i, xkb_keycode, keysym);
501 let kbd = self.devices[i].keyboard.as_ref().unwrap();
502 for &m in &mods {
503 if *self.mod_refs.entry(m).or_insert(0) == 0 {
504 kbd.key(m, key_state(true));
505 }
506 *self.mod_refs.get_mut(&m).unwrap() += 1;
507 }
508 kbd.key(evdev, key_state(true));
509 self.pressed.insert(xkb_keycode, (evdev, mods));
510 } else if let Some((evdev, mods)) = self.pressed.remove(&xkb_keycode) {
511 let kbd = self.devices[i].keyboard.as_ref().unwrap();
512 kbd.key(evdev, key_state(false));
513 for m in mods.into_iter().rev() {
514 let count = self
515 .mod_refs
516 .get(&m)
517 .copied()
518 .unwrap_or(0)
519 .saturating_sub(1);
520 if count == 0 {
521 self.mod_refs.remove(&m);
522 kbd.key(m, key_state(false));
523 } else {
524 self.mod_refs.insert(m, count);
525 }
526 }
527 }
528 self.frame(i);
529 }
530
531 fn resolve_key(&self, index: usize, xkb_keycode: u32, keysym: Option<u32>) -> (u32, Vec<u32>) {
534 let raw = xkb_keycode.saturating_sub(EVDEV_OFFSET);
535 let (Some(sym), Some(policy)) = (keysym, self.devices[index].keymap.as_ref()) else {
536 return (raw, Vec::new());
537 };
538 match policy.resolve(sym) {
539 Some((kc, level)) => {
540 let mut mods = Vec::new();
541 if level & 1 != 0 {
542 mods.push(policy_shift(policy));
543 }
544 if level & 2 != 0 {
545 mods.push(policy_altgr(policy));
546 }
547 (
548 kc.saturating_sub(EVDEV_OFFSET),
549 mods.into_iter().flatten().collect(),
550 )
551 }
552 None => (raw, Vec::new()),
553 }
554 }
555
556 fn ready_index(&self, want: impl Fn(&Device) -> bool) -> Option<usize> {
558 self.devices.iter().position(|d| d.resumed && want(d))
559 }
560
561 fn start(&mut self, index: usize) {
562 let d = &mut self.devices[index];
563 if !d.emulating {
564 d.device.start_emulating(self.last_serial, d.sequence);
565 d.sequence = d.sequence.wrapping_add(1);
566 d.emulating = true;
567 }
568 }
569
570 fn frame(&mut self, index: usize) {
571 self.devices[index]
572 .device
573 .frame(self.last_serial, now_micros());
574 }
575}
576
577fn policy_shift(policy: &KeymapPolicy) -> Option<u32> {
579 policy
580 .resolve(0xffe1)
581 .map(|(kc, _)| kc.saturating_sub(EVDEV_OFFSET))
582}
583
584fn policy_altgr(policy: &KeymapPolicy) -> Option<u32> {
586 policy
587 .resolve(0xfe03)
588 .map(|(kc, _)| kc.saturating_sub(EVDEV_OFFSET))
589}
590
591fn keymap_policy(keymap: &reis::event::Keymap) -> Option<KeymapPolicy> {
594 let map = unsafe {
595 memmap2::MmapOptions::new()
596 .len(keymap.size as usize)
597 .map(keymap.fd.as_raw_fd())
598 .ok()?
599 };
600 let text = std::ffi::CStr::from_bytes_until_nul(&map)
601 .ok()?
602 .to_str()
603 .ok()?;
604 policy_from_text(text)
605}
606
607fn policy_from_text(text: &str) -> Option<KeymapPolicy> {
609 compile_keymap(text)?;
610 let mut policy = KeymapPolicy::empty();
611 policy.rebuild_base(text.to_string()).then_some(policy)
612}
613
614fn now_micros() -> u64 {
615 SystemTime::now()
616 .duration_since(UNIX_EPOCH)
617 .map(|d| d.as_micros() as u64)
618 .unwrap_or(0)
619}
620
621fn button_state(pressed: bool) -> ei::button::ButtonState {
622 if pressed {
623 ei::button::ButtonState::Press
624 } else {
625 ei::button::ButtonState::Released
626 }
627}
628
629fn key_state(pressed: bool) -> ei::keyboard::KeyState {
630 if pressed {
631 ei::keyboard::KeyState::Press
632 } else {
633 ei::keyboard::KeyState::Released
634 }
635}
636
637#[cfg(test)]
638mod tests {
639 use super::*;
640 use crate::wayland::keymap::compile_rmlvo;
641
642 #[test]
646 fn keyboard_resolves_against_the_host_keymap() {
647 let text = compile_rmlvo("", "", "us", "", "").expect("us keymap");
648 let policy = policy_from_text(&text).expect("policy");
649 let (a_kc, a_level) = policy.resolve(0x61).expect("a resolves");
651 assert_eq!(a_level, 0, "lowercase is unshifted");
652 let (upper_kc, upper_level) = policy.resolve(0x41).expect("A resolves");
654 assert_eq!(upper_kc, a_kc, "same physical key");
655 assert_eq!(upper_level & 1, 1, "uppercase needs Shift");
656 let shift = policy_shift(&policy).expect("shift keycode");
658 assert!(shift >= 1, "a real evdev keycode");
659 assert_eq!(
661 policy.resolve(0x0439),
662 None,
663 "Cyrillic short-i absent from us"
664 );
665 }
666
667 #[test]
672 fn injector_emits_the_expected_eis_protocol() {
673 use reis::eis;
674 use reis::handshake::EisHandshaker;
675 use reis::request::{EisRequest, EisRequestConverter};
676 use std::os::fd::OwnedFd;
677 use std::os::unix::net::UnixStream;
678 use std::time::Duration;
679
680 let (server_sock, client_sock) = UnixStream::pair().unwrap();
681 let seen = Arc::new(Mutex::new(Vec::<String>::new()));
682 let recorded = seen.clone();
683
684 let server = std::thread::spawn(move || {
685 let ctx = eis::Context::new(server_sock).unwrap();
686 let mut handshaker = EisHandshaker::new(&ctx, 1);
687 let resp = 'hs: loop {
688 if ctx.read().is_err() {
689 return;
690 }
691 while let Some(pr) = ctx.pending_request() {
692 if let PendingRequestResult::Request(req) = pr
693 && let Ok(Some(resp)) = handshaker.handle_request(req)
694 {
695 break 'hs resp;
696 }
697 }
698 };
699 let mut converter = EisRequestConverter::new(&ctx, resp, 1);
700 let handle = converter.handle().clone();
701 let seat = handle.add_seat(
702 Some("test"),
703 DeviceCapability::Pointer
704 | DeviceCapability::PointerAbsolute
705 | DeviceCapability::Button
706 | DeviceCapability::Scroll
707 | DeviceCapability::Keyboard,
708 );
709 let _ = handle.flush();
710 let mut device = None;
711 loop {
712 if ctx.read().is_err() {
713 return;
714 }
715 while let Some(pr) = ctx.pending_request() {
716 if let PendingRequestResult::Request(req) = pr {
717 let _ = converter.handle_request(req);
718 }
719 }
720 while let Some(req) = converter.next_request() {
721 let mut log = recorded.lock().unwrap();
722 match req {
723 EisRequest::Bind(b) if device.is_none() => {
724 let dev = seat.add_device(
725 Some("virt"),
726 eis::device::DeviceType::Virtual,
727 b.capabilities,
728 |_| {},
729 );
730 dev.resumed();
731 let _ = handle.flush();
732 device = Some(dev);
733 }
734 EisRequest::DeviceStartEmulating(_) => log.push("start".into()),
735 EisRequest::PointerMotionAbsolute(m) => {
736 log.push(format!(
737 "abs {} {}",
738 m.dx_absolute as i32, m.dy_absolute as i32
739 ));
740 }
741 EisRequest::Button(b) => log.push(format!(
742 "button {} {}",
743 b.button,
744 matches!(b.state, eis::button::ButtonState::Press)
745 )),
746 EisRequest::KeyboardKey(k) => log.push(format!(
747 "key {} {}",
748 k.key,
749 matches!(k.state, eis::keyboard::KeyState::Press)
750 )),
751 EisRequest::ScrollDiscrete(s) => {
752 log.push(format!("scroll {} {}", s.discrete_dx, s.discrete_dy));
753 }
754 _ => {}
755 }
756 }
757 }
758 });
759
760 let injector =
761 EiInjector::spawn(OwnedFd::from(client_sock)).expect("injector binds a device");
762 injector.pointer_motion_abs(400.0, 300.0);
763 injector.pointer_button(0x110, true);
764 injector.pointer_button(0x110, false);
765 injector.key(38, None, true);
766 injector.key(38, None, false);
767 injector.pointer_axis_discrete(0, 1);
768 std::thread::sleep(Duration::from_millis(400));
769 drop(injector);
770 server.join().unwrap();
771
772 let got = seen.lock().unwrap().clone();
773 assert!(
774 got.contains(&"start".to_string()),
775 "start_emulating: {got:?}"
776 );
777 assert!(
778 got.contains(&"abs 400 300".to_string()),
779 "absolute motion: {got:?}"
780 );
781 assert!(
782 got.contains(&"button 272 true".to_string()),
783 "left press: {got:?}"
784 );
785 assert!(
786 got.contains(&"button 272 false".to_string()),
787 "left release: {got:?}"
788 );
789 assert!(
790 got.contains(&"key 30 true".to_string()),
791 "key press: {got:?}"
792 );
793 assert!(
794 got.contains(&"key 30 false".to_string()),
795 "key release: {got:?}"
796 );
797 assert!(
798 got.contains(&"scroll 0 120".to_string()),
799 "discrete scroll: {got:?}"
800 );
801 }
802}