kbd_evdev/forwarder.rs
1//! uinput virtual device for event forwarding.
2//!
3//! In grab mode, unmatched key events are re-emitted through a virtual
4//! device so they reach applications normally.
5//!
6//! Synthetic action emission is planned but is not implemented yet.
7//! Unlike tools such as keyd, this crate currently needs only a keyboard
8//! virtual device.
9
10use kbd::key::Key;
11use kbd::key_state::KeyTransition;
12
13use crate::convert::KbdKeyExt;
14use crate::error::Error;
15
16/// Name of the virtual device we create, used for self-detection.
17pub(crate) const VIRTUAL_DEVICE_NAME: &str = "kbd-virtual-keyboard";
18
19/// Sink for forwarding key events through a virtual device.
20///
21/// The engine uses this trait to forward unmatched events in grab mode.
22/// Test utilities and alternate implementations can provide a different sink
23/// with the same interface.
24pub trait ForwardSink: Send {
25 /// Forward a single key event through the virtual device.
26 ///
27 /// # Errors
28 ///
29 /// Returns [`Error::Uinput`] if the underlying `write` to the virtual
30 /// device fails.
31 fn forward_key(&mut self, key: Key, transition: KeyTransition) -> Result<(), Error>;
32}
33
34/// Maximum key code we register with uinput.
35const MAX_FORWARDABLE_KEY_CODE: u16 = 767;
36
37/// Virtual uinput device for forwarding unmatched key events in grab mode.
38///
39/// Creates a virtual keyboard device via `/dev/uinput`. Unmatched events
40/// are re-emitted through this device so they reach applications normally.
41pub struct UinputForwarder {
42 device: evdev::uinput::VirtualDevice,
43}
44
45impl UinputForwarder {
46 /// Create a new virtual keyboard device via `/dev/uinput`.
47 ///
48 /// The device is named `kbd-virtual-keyboard` and supports all key
49 /// codes up to code 767. [`DeviceManager`](crate::devices::DeviceManager)
50 /// automatically skips this device during discovery to prevent
51 /// feedback loops.
52 ///
53 /// # Errors
54 ///
55 /// Returns [`Error::Uinput`] if `/dev/uinput` cannot be opened or
56 /// the virtual device cannot be created (e.g., missing permissions).
57 pub fn new() -> Result<Self, Error> {
58 let mut keys = evdev::AttributeSet::<evdev::KeyCode>::new();
59 for code in 0..=MAX_FORWARDABLE_KEY_CODE {
60 keys.insert(evdev::KeyCode::new(code));
61 }
62
63 let device = evdev::uinput::VirtualDevice::builder()
64 .map_err(Error::Uinput)?
65 .name(VIRTUAL_DEVICE_NAME)
66 .with_keys(&keys)
67 .map_err(Error::Uinput)?
68 .build()
69 .map_err(Error::Uinput)?;
70
71 Ok(Self { device })
72 }
73}
74
75impl ForwardSink for UinputForwarder {
76 fn forward_key(&mut self, key: Key, transition: KeyTransition) -> Result<(), Error> {
77 let key_code = key.to_key_code();
78 let value = match transition {
79 KeyTransition::Press => 1,
80 KeyTransition::Release => 0,
81 KeyTransition::Repeat => 2,
82 _ => return Ok(()),
83 };
84
85 let event = evdev::InputEvent::new(evdev::EventType::KEY.0, key_code.code(), value);
86 self.device.emit(&[event]).map_err(Error::Uinput)
87 }
88}
89
90// TODO: emit_key() — produce a synthetic key event (for remapping/actions)
91
92/// Test utilities for the forwarder — recording forwarder for assertions.
93///
94/// Gated behind the `testing` feature flag. Enable it in downstream
95/// `[dev-dependencies]` to use `RecordingForwarder` in your own tests.
96#[cfg(any(test, feature = "testing"))]
97pub mod testing {
98 use std::sync::Arc;
99 use std::sync::Mutex;
100
101 use kbd::key::Key;
102 use kbd::key_state::KeyTransition;
103
104 use super::ForwardSink;
105 use crate::error::Error;
106
107 /// Shared buffer for inspecting forwarded events in tests.
108 pub type ForwardedEvents = Arc<Mutex<Vec<(Key, KeyTransition)>>>;
109
110 /// A forwarder that records forwarded events for test assertions.
111 pub struct RecordingForwarder {
112 events: ForwardedEvents,
113 }
114
115 impl RecordingForwarder {
116 /// Create a new recording forwarder and return the shared event buffer.
117 #[must_use]
118 pub fn new() -> (Self, ForwardedEvents) {
119 let events = Arc::new(Mutex::new(Vec::new()));
120 (
121 Self {
122 events: Arc::clone(&events),
123 },
124 events,
125 )
126 }
127 }
128
129 impl ForwardSink for RecordingForwarder {
130 fn forward_key(&mut self, key: Key, transition: KeyTransition) -> Result<(), Error> {
131 self.events.lock().unwrap().push((key, transition));
132 Ok(())
133 }
134 }
135}