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}