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