usbhidd: P5 slice 2 — keyboard LED sync via SET_REPORT

Add Caps Lock, Num Lock, and Scroll Lock LED synchronization
following Linux 7.1 hid-input.c: hidinput_output_event().

Led state tracking:
  - Caps Lock   (usage 0x39) → toggles bit 1
  - Scroll Lock (usage 0x47) → toggles bit 2
  - Num Lock    (usage 0x53) → toggles bit 0

SET_REPORT (Output) via XhciClientHandle::device_request():
  - PortReqTy::Class, PortReqRecipient::Interface
  - bRequest = 0x09 (SET_REPORT)
  - wValue = (0x02 << 8) | 0x00  (Output report type, report ID 0)
  - wIndex = interface_num
  - Data = 1-byte LED state

The SET_REPORT is sent only when the LED state changes (tracked
with last_led_state sentinel).  A failed SET_REPORT is logged at
warn level but does not block the input loop.

Cross-reference: Linux 7.1
  - drivers/hid/hid-input.c: hidinput_output_event()
  - drivers/hid/usbhid/hid-core.c: usbhid_output_report()
  - HID 1.11 spec §7.2.1: SET_REPORT request

This means USB keyboards with Caps/Num/Scroll Lock LEDs will now
have their LEDs synchronized with the host lock state.
This commit is contained in:
Red Bear OS
2026-07-07 12:16:05 +03:00
parent 5276fcb739
commit f98dd4339f
+34
View File
@@ -324,6 +324,11 @@ fn main() -> Result<()> {
let mut right_shift = false;
let mut last_mouse_pos = (0, 0);
let mut last_buttons = [false, false, false];
// Keyboard LED state (HID boot keyboard output report, 1 byte)
let mut led_state: u8 = 0;
let mut last_led_state: u8 = 0xFF; // force first SET_REPORT
loop {
//TODO: get frequency from device
//TODO: use sleeps when accuracy is better: thread::sleep(time::Duration::from_millis(10));
@@ -399,6 +404,15 @@ fn main() -> Result<()> {
} else if event.usage == 0xE5 {
right_shift = pressed;
}
// Track LED state from lock-key presses (Linux 7.1 hid-input.c)
if pressed {
match event.usage {
0x39 => led_state ^= 0x02, // Caps Lock
0x47 => led_state ^= 0x04, // Scroll Lock
0x53 => led_state ^= 0x01, // Num Lock
_ => {}
}
}
send_key_event(&mut display, event.usage_page, event.usage, pressed);
} else if event.usage_page == UsagePage::Button as u16 {
if event.usage > 0 && event.usage as usize <= buttons.len() {
@@ -495,6 +509,26 @@ fn main() -> Result<()> {
}
}
// Keyboard LED sync: send HID output report when LED state changes.
// Linux 7.1 hid-input.c: hidinput_output_event() → hid_hw_output_report()
// → SET_REPORT (Output) via control transfer.
if led_state != last_led_state {
last_led_state = led_state;
let led_bytes = [led_state];
if let Err(e) = handle.device_request(
xhcid_interface::PortReqTy::Class,
xhcid_interface::PortReqRecipient::Interface,
0x09, // SET_REPORT
(0x02u16) << 8, // Output report type, report ID 0
interface_num as u16,
xhcid_interface::DeviceReqData::Out(unsafe {
std::slice::from_raw_parts(led_bytes.as_ptr(), 1)
}),
) {
log::warn!("usbhidd: SET_REPORT(LED) failed: {}", e);
}
}
// log::trace!("took {}ms", timer.elapsed().as_millis())
}
}