Skip to main content

netget/server/usb/keyboard/
handler.rs

1//! USB HID keyboard handler.
2//!
3//! Wraps `usbip::hid::UsbHidKeyboardHandler` rather than replacing it: the crate's key-down /
4//! key-up state machine on the interrupt IN endpoint is exactly right, and reimplementing it
5//! would be a second copy to keep in step. Everything on **endpoint 0** is handled here
6//! instead, for two reasons.
7//!
8//! **1. LED output reports.** A host tells a keyboard about Caps/Num/Scroll Lock with a class
9//! `SET_REPORT` on the control endpoint (the device has no OUT endpoint). The crate's handler
10//! never sees it, which is why `usb_keyboard_led_status` was a declared event with no emit site
11//! for its entire existence — advertised to the model, unable to fire. This intercepts it and
12//! reports the LED byte on a channel; `mod.rs` turns that into the event.
13//!
14//! **2. The crate panics on anything else.** `UsbHidKeyboardHandler::handle_urb` ends its
15//! control arm with `unimplemented!("hid request {:?}", setup)`, and `handle_urb` is called from
16//! a tokio worker inside the USB/IP session task. A host issuing `GET_PROTOCOL`, `GET_IDLE`, a
17//! `GET_REPORT`, or the very `SET_REPORT` above would take the connection down with a panic
18//! rather than get an answer — and `SET_REPORT` is not exotic: pressing Caps Lock sends one.
19//! Unknown control requests are answered empty here, because returning an error aborts the
20//! USB/IP session for the whole device and a host probing an optional request must not
21//! disconnect the keyboard.
22
23#[cfg(all(unix, feature = "libusb"))]
24use tokio::sync::mpsc::UnboundedSender;
25#[cfg(all(unix, feature = "libusb"))]
26use tracing::{debug, trace, warn};
27#[cfg(all(unix, feature = "libusb"))]
28use usbip::hid::{UsbHidKeyboardHandler, UsbHidKeyboardReport};
29
30/// HID descriptor type codes (HID 1.11 §7.1).
31#[cfg(all(unix, feature = "libusb"))]
32mod hid_descriptor_type {
33    pub const REPORT: u8 = 0x22;
34}
35
36/// The LED byte a host writes with `SET_REPORT`, decoded (HID Usage Tables, LED page).
37#[cfg(all(unix, feature = "libusb"))]
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub struct LedState {
40    pub raw: u8,
41}
42
43#[cfg(all(unix, feature = "libusb"))]
44impl LedState {
45    pub fn num_lock(&self) -> bool {
46        self.raw & 0x01 != 0
47    }
48    pub fn caps_lock(&self) -> bool {
49        self.raw & 0x02 != 0
50    }
51    pub fn scroll_lock(&self) -> bool {
52        self.raw & 0x04 != 0
53    }
54    pub fn compose(&self) -> bool {
55        self.raw & 0x08 != 0
56    }
57    pub fn kana(&self) -> bool {
58        self.raw & 0x10 != 0
59    }
60}
61
62/// A virtual USB HID keyboard that also reports LED changes.
63#[cfg(all(unix, feature = "libusb"))]
64pub struct NetGetKeyboardHandler {
65    inner: UsbHidKeyboardHandler,
66    /// Where LED changes are posted for the connection task. Unbounded because
67    /// `UnboundedSender::send` is synchronous, and this is reached from `handle_urb`, which
68    /// cannot await — the same seam `usb/msc` uses for sector transfers.
69    led_tx: Option<UnboundedSender<LedState>>,
70    /// Whether the last report handed out held a key down and still needs its release.
71    key_is_down: bool,
72    /// Last state reported, so an unchanged repeat does not raise an event. A host re-sends the
73    /// full LED byte on every change to *any* LED, and X11 in particular re-asserts it
74    /// periodically; without this the model gets woken by a stream of identical events.
75    last_led: Option<LedState>,
76}
77
78#[cfg(all(unix, feature = "libusb"))]
79impl std::fmt::Debug for NetGetKeyboardHandler {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        f.debug_struct("NetGetKeyboardHandler")
82            .field("pending_key_events", &self.inner.pending_key_events.len())
83            .field("last_led", &self.last_led)
84            .finish()
85    }
86}
87
88#[cfg(all(unix, feature = "libusb"))]
89impl Default for NetGetKeyboardHandler {
90    fn default() -> Self {
91        Self::new()
92    }
93}
94
95#[cfg(all(unix, feature = "libusb"))]
96impl NetGetKeyboardHandler {
97    pub fn new() -> Self {
98        Self {
99            inner: UsbHidKeyboardHandler::new_keyboard(),
100            led_tx: None,
101            key_is_down: false,
102            last_led: None,
103        }
104    }
105
106    /// Attach the channel LED changes are reported on.
107    pub fn with_led_events(mut self, led_tx: UnboundedSender<LedState>) -> Self {
108        self.led_tx = Some(led_tx);
109        self
110    }
111
112    /// Queue a report to be delivered on the next interrupt-IN URB.
113    pub fn queue(&mut self, report: UsbHidKeyboardReport) {
114        self.inner.pending_key_events.push_back(report);
115    }
116
117    /// How many reports are still waiting (tests and diagnostics).
118    #[allow(dead_code)]
119    pub fn pending_len(&self) -> usize {
120        self.inner.pending_key_events.len()
121    }
122
123    /// The last LED state the host set, if it has set one.
124    #[allow(dead_code)]
125    pub fn led_state(&self) -> Option<LedState> {
126        self.last_led
127    }
128
129    fn report_descriptor(&self) -> Vec<u8> {
130        self.inner.report_descriptor.clone()
131    }
132
133    /// The next 8-byte input report to hand the host: a key-down, then its release.
134    ///
135    /// This is the crate's state machine reimplemented for one reason: the crate answers a
136    /// release with `vec![0; 6]`. A boot-protocol keyboard report is **8 bytes** (modifier,
137    /// reserved, six key slots), and a short release is not the report a host is decoding — it
138    /// is a different message with the same intent, which a strict HID parser reads as
139    /// malformed rather than as "all keys up".
140    fn next_input_report(&mut self) -> Vec<u8> {
141        if self.key_is_down {
142            self.key_is_down = false;
143            trace!("USB keyboard: key up");
144            return vec![0u8; 8];
145        }
146
147        match self.inner.pending_key_events.pop_front() {
148            Some(report) => {
149                let mut out = Vec::with_capacity(8);
150                out.push(report.modifier);
151                out.push(0); // reserved
152                out.extend_from_slice(&report.keys);
153                self.key_is_down = true;
154                trace!(
155                    "USB keyboard: key down modifier={:#04x} keys={:?}",
156                    report.modifier,
157                    report.keys
158                );
159                out
160            }
161            // Nothing queued. An empty URB response is how this transport says "no report".
162            None => Vec::new(),
163        }
164    }
165
166    /// Record an LED byte and report it if it changed.
167    fn set_leds(&mut self, raw: u8) {
168        let state = LedState { raw };
169        if self.last_led == Some(state) {
170            trace!("USB keyboard LED report unchanged ({:#04x})", raw);
171            return;
172        }
173        self.last_led = Some(state);
174        debug!(
175            "USB keyboard LEDs: num={} caps={} scroll={} (raw {:#04x})",
176            state.num_lock(),
177            state.caps_lock(),
178            state.scroll_lock(),
179            raw
180        );
181        if let Some(ref tx) = self.led_tx {
182            // The connection task may already be gone; that is not an error here.
183            let _ = tx.send(state);
184        }
185    }
186}
187
188#[cfg(all(unix, feature = "libusb"))]
189impl usbip::UsbInterfaceHandler for NetGetKeyboardHandler {
190    fn handle_urb(
191        &mut self,
192        interface: &usbip::UsbInterface,
193        ep: usbip::UsbEndpoint,
194        transfer_buffer_length: u32,
195        setup: usbip::SetupPacket,
196        req: &[u8],
197    ) -> std::result::Result<Vec<u8>, std::io::Error> {
198        if !ep.is_ep0() {
199            let _ = (interface, transfer_buffer_length, req);
200            if let usbip::Direction::In = ep.direction() {
201                return Ok(self.next_input_report());
202            }
203            // A boot keyboard has no OUT endpoint; LEDs arrive as SET_REPORT on ep0.
204            return Ok(vec![]);
205        }
206
207        match (setup.request_type, setup.request) {
208            // GET_DESCRIPTOR (device-to-host, standard, interface)
209            (0b1000_0001, 0x06) => {
210                let desc_type = (setup.value >> 8) as u8;
211                if desc_type == hid_descriptor_type::REPORT {
212                    Ok(self.report_descriptor())
213                } else {
214                    warn!(
215                        "USB keyboard: unsupported descriptor type {:#04x}",
216                        desc_type
217                    );
218                    Ok(vec![])
219                }
220            }
221
222            // SET_REPORT (host-to-device, class, interface). wValue high byte 0x02 is an
223            // Output report, which for a keyboard is the LED byte.
224            (0b0010_0001, 0x09) => {
225                let report_type = (setup.value >> 8) as u8;
226                if report_type == 0x02 {
227                    match req.first() {
228                        Some(&leds) => self.set_leds(leds),
229                        None => warn!("USB keyboard SET_REPORT(Output) carried no data"),
230                    }
231                } else {
232                    debug!(
233                        "USB keyboard SET_REPORT of type {:#04x} ignored",
234                        report_type
235                    );
236                }
237                Ok(vec![])
238            }
239
240            // GET_REPORT (device-to-host, class, interface): the current LED byte.
241            (0b1010_0001, 0x01) => Ok(vec![self.last_led.map_or(0, |s| s.raw)]),
242
243            // GET_IDLE / GET_PROTOCOL (device-to-host, class, interface)
244            (0b1010_0001, 0x02) => Ok(vec![0]), // report protocol
245            (0b1010_0001, 0x03) => Ok(vec![0]), // idle: indefinite
246
247            // SET_IDLE / SET_PROTOCOL (host-to-device, class, interface)
248            (0b0010_0001, 0x0A) | (0b0010_0001, 0x0B) => Ok(vec![]),
249
250            _ => {
251                // Empty rather than an error: an error aborts the USB/IP session for the whole
252                // device, and the crate's own handler panicked here.
253                debug!(
254                    "USB keyboard: unhandled control request type={:#04x} request={:#04x}",
255                    setup.request_type, setup.request
256                );
257                Ok(vec![])
258            }
259        }
260    }
261
262    fn get_class_specific_descriptor(&self) -> Vec<u8> {
263        self.inner.get_class_specific_descriptor()
264    }
265
266    fn as_any(&mut self) -> &mut dyn std::any::Any {
267        self
268    }
269}