Skip to main content

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}