Skip to main content

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}