use core::future::Future;
use core::sync::atomic::Ordering;
use embassy_usb::class::hid::ReadError;
use embassy_usb::driver::EndpointError;
use rmk_types::connection::ConnectionType;
use rmk_types::led_indicator::LedIndicator;
#[cfg(feature = "rynk")]
use rmk_types::protocol::rynk::RYNK_HID_REPORT_SIZE;
use serde::Serialize;
use usbd_hid::descriptor::generator_prelude::*;
use usbd_hid::descriptor::{AsInputReport, MediaKeyboardReport, MouseReport, SystemControlReport};
use crate::event::{LedIndicatorEvent, publish_event};
use crate::keyboard::LOCK_LED_STATES;
#[gen_hid_descriptor(
(collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = KEYBOARD) = {
(usage_page = KEYBOARD, usage_min = 0xE0, usage_max = 0xE7) = {
#[packed_bits = 8] #[item_settings(data,variable,absolute)] modifier=input;
};
(logical_min = 0,) = {
#[item_settings(constant,variable,absolute)] reserved=input;
};
(usage_page = LEDS, usage_min = 0x01, usage_max = 0x05) = {
#[packed_bits = 5] #[item_settings(data,variable,absolute)] leds=output;
};
(usage_page = KEYBOARD, usage_min = 0x00, usage_max = 0xDD) = {
#[item_settings(data,array,absolute)] keycodes=input;
};
}
)]
#[allow(dead_code)]
#[derive(Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct KeyboardReport {
pub modifier: u8, pub reserved: u8,
pub leds: u8, pub keycodes: [u8; 6],
}
#[gen_hid_descriptor(
(collection = APPLICATION, usage_page = 0xFF60, usage = 0x61) = {
(usage = 0x62, logical_min = 0x0) = {
#[item_settings(data,variable,absolute)] input_data=input;
};
(usage = 0x63, logical_min = 0x0) = {
#[item_settings(data,variable,absolute)] output_data=output;
};
}
)]
#[derive(Default)]
pub struct ViaReport {
pub(crate) input_data: [u8; 32],
pub(crate) output_data: [u8; 32],
}
#[cfg(feature = "rynk")]
#[gen_hid_descriptor(
(collection = APPLICATION, usage_page = 0xFF14, usage = 0x61) = {
(usage = 0x62, logical_min = 0x0) = {
#[item_settings(data,variable,absolute)] input_data=input;
};
(usage = 0x63, logical_min = 0x0) = {
#[item_settings(data,variable,absolute)] output_data=output;
};
}
)]
#[derive(Default)]
pub struct RynkHidReport {
pub(crate) input_data: [u8; 32],
pub(crate) output_data: [u8; 32],
}
#[cfg(feature = "rynk")]
const _: () = core::assert!(
RYNK_HID_REPORT_SIZE == 32,
"RynkHidReport literal length must equal RYNK_HID_REPORT_SIZE"
);
#[repr(u8)]
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Serialize)]
pub enum CompositeReportType {
#[default]
None = 0x00,
Keyboard = 0x01,
Mouse = 0x02,
Media = 0x03,
System = 0x04,
}
#[cfg(feature = "steno")]
#[gen_hid_descriptor(
(collection = LOGICAL, usage_page = 0xFF50, usage = 0x4C56) = {
(report_id = 0x50, usage_page = 0x0A, usage_min = 0x0, usage_max = 0x3F, logical_min = 0x0) = {
#[packed_bits = 64] #[item_settings(data,variable,absolute)] keys=input;
};
}
)]
#[derive(Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct StenoReport {
pub keys: [u8; 8],
}
#[cfg(feature = "steno")]
impl usbd_hid::descriptor::AsInputReport for StenoReport {
fn serialize(&self, buffer: &mut [u8]) -> Result<usize, usbd_hid::descriptor::BufferOverflow> {
if buffer.len() < 9 {
return Err(usbd_hid::descriptor::BufferOverflow);
}
buffer[0] = rmk_types::steno::PLOVER_HID_REPORT_ID;
buffer[1..9].copy_from_slice(&self.keys);
Ok(9)
}
}
#[cfg(all(test, feature = "steno"))]
mod steno_tests {
use usbd_hid::descriptor::SerializedDescriptor;
use super::StenoReport;
#[test]
fn descriptor_advertises_plover_identifiers() {
let desc = StenoReport::desc();
fn contains(haystack: &[u8], needle: &[u8]) -> bool {
haystack.windows(needle.len()).any(|w| w == needle)
}
assert!(contains(desc, &[0x06, 0x50, 0xff]), "missing UsagePage 0xFF50");
assert!(contains(desc, &[0x0a, 0x56, 0x4c]), "missing Usage 0x4C56");
assert!(contains(desc, &[0xa1, 0x02]), "missing Logical collection");
assert!(contains(desc, &[0x85, 0x50]), "missing ReportID 0x50");
assert!(contains(desc, &[0x75, 0x01]), "missing ReportSize 1");
assert!(contains(desc, &[0x95, 0x40]), "missing ReportCount 64");
assert!(contains(desc, &[0x05, 0x0a]), "missing Ordinal UsagePage");
assert!(contains(desc, &[0x19, 0x00]), "missing UsageMin 0");
assert!(contains(desc, &[0x29, 0x3f]), "missing UsageMax 63");
}
}
#[gen_hid_descriptor(
(collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = MOUSE) = {
(collection = PHYSICAL, usage = POINTER) = {
(report_id = 0x02,) = {
(usage_page = BUTTON, usage_min = BUTTON_1, usage_max = BUTTON_8) = {
#[packed_bits = 8] #[item_settings(data,variable,absolute)] buttons=input;
};
(usage_page = GENERIC_DESKTOP,) = {
(usage = X,) = {
#[item_settings(data,variable,relative)] x=input;
};
(usage = Y,) = {
#[item_settings(data,variable,relative)] y=input;
};
(usage = WHEEL,) = {
#[item_settings(data,variable,relative)] wheel=input;
};
};
(usage_page = CONSUMER,) = {
(usage = AC_PAN,) = {
#[item_settings(data,variable,relative)] pan=input;
};
};
};
};
},
(collection = APPLICATION, usage_page = CONSUMER, usage = CONSUMER_CONTROL) = {
(report_id = 0x03,) = {
(usage_page = CONSUMER, usage_min = 0x00, usage_max = 0x514) = {
#[item_settings(data,array,absolute,not_null)] media_usage_id=input;
}
};
},
(collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = SYSTEM_CONTROL) = {
(report_id = 0x04,) = {
(usage_min = 0x01, usage_max = 0xB7, logical_min = 1) = {
#[item_settings(data,array,absolute,not_null)] system_usage_id=input;
};
};
}
)]
#[derive(Default, Serialize)]
pub struct CompositeReport {
pub(crate) buttons: u8, pub(crate) x: i8,
pub(crate) y: i8,
pub(crate) wheel: i8, pub(crate) pan: i8, pub(crate) media_usage_id: u16,
pub(crate) system_usage_id: u8,
}
#[cfg(feature = "_ble")]
#[gen_hid_descriptor(
(collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = KEYBOARD) = {
(report_id = 0x01,) = {
(usage_page = KEYBOARD, usage_min = 0xE0, usage_max = 0xE7) = {
#[packed_bits = 8] #[item_settings(data,variable,absolute)] modifier=input;
};
(logical_min = 0,) = {
#[item_settings(constant,variable,absolute)] reserved=input;
};
(usage_page = LEDS, usage_min = 0x01, usage_max = 0x05) = {
#[packed_bits = 5] #[item_settings(data,variable,absolute)] leds=output;
};
(usage_page = KEYBOARD, usage_min = 0x00, usage_max = 0xDD) = {
#[item_settings(data,array,absolute)] keycodes=input;
};
};
},
(collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = MOUSE) = {
(collection = PHYSICAL, usage = POINTER) = {
(report_id = 0x02,) = {
(usage_page = BUTTON, usage_min = BUTTON_1, usage_max = BUTTON_8) = {
#[packed_bits = 8] #[item_settings(data,variable,absolute)] buttons=input;
};
(usage_page = GENERIC_DESKTOP,) = {
(usage = X,) = {
#[item_settings(data,variable,relative)] x=input;
};
(usage = Y,) = {
#[item_settings(data,variable,relative)] y=input;
};
(usage = WHEEL,) = {
#[item_settings(data,variable,relative)] wheel=input;
};
};
(usage_page = CONSUMER,) = {
(usage = AC_PAN,) = {
#[item_settings(data,variable,relative)] pan=input;
};
};
};
};
},
(collection = APPLICATION, usage_page = CONSUMER, usage = CONSUMER_CONTROL) = {
(report_id = 0x03,) = {
(usage_page = CONSUMER, usage_min = 0x00, usage_max = 0x514) = {
#[item_settings(data,array,absolute,not_null)] media_usage_id=input;
}
};
},
(collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = SYSTEM_CONTROL) = {
(report_id = 0x04,) = {
(usage_min = 0x01, usage_max = 0xB7, logical_min = 1) = {
#[item_settings(data,array,absolute,not_null)] system_usage_id=input;
};
};
}
)]
#[allow(dead_code)]
#[derive(Default)]
pub struct BleCompositeReport {
pub(crate) modifier: u8,
pub(crate) reserved: u8,
pub(crate) leds: u8,
pub(crate) keycodes: [u8; 6],
pub(crate) buttons: u8,
pub(crate) x: i8,
pub(crate) y: i8,
pub(crate) wheel: i8,
pub(crate) pan: i8,
pub(crate) media_usage_id: u16,
pub(crate) system_usage_id: u8,
}
#[cfg(all(test, feature = "_ble"))]
mod ble_report_map_tests {
use usbd_hid::descriptor::SerializedDescriptor;
use super::BleCompositeReport;
#[test]
fn ble_report_map_matches_service_definition() {
let desc = BleCompositeReport::desc();
fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack.windows(needle.len()).position(|w| w == needle)
}
assert_eq!(desc.len(), 178, "update HidService's report_map size on change");
let keyboard = find(desc, &[0x09, 0x06]).expect("missing Usage Keyboard");
for report_id in 1u8..=4 {
let id = find(desc, &[0x85, report_id]).unwrap_or_else(|| panic!("missing ReportID {report_id}"));
if report_id == 0x01 {
assert!(keyboard < id, "keyboard collection must own ReportID 1");
}
}
}
}
#[derive(Debug, Clone)]
pub enum Report {
KeyboardReport(KeyboardReport),
MouseReport(MouseReport),
MediaKeyboardReport(MediaKeyboardReport),
SystemControlReport(SystemControlReport),
#[cfg(feature = "steno")]
StenoReport(StenoReport),
}
impl AsInputReport for Report {
fn serialize(&self, buffer: &mut [u8]) -> Result<usize, usbd_hid::descriptor::BufferOverflow> {
match self {
Report::KeyboardReport(r) => r.serialize(buffer),
Report::MouseReport(r) => r.serialize(buffer),
Report::MediaKeyboardReport(r) => r.serialize(buffer),
Report::SystemControlReport(r) => r.serialize(buffer),
#[cfg(feature = "steno")]
Report::StenoReport(r) => r.serialize(buffer),
}
}
}
#[derive(PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum HidError {
UsbReadError(ReadError),
UsbEndpointError(EndpointError),
ReportSerializeError,
BleError,
}
pub trait HidWriterTrait {
type ReportType: AsInputReport;
fn write_report(&mut self, report: &Self::ReportType) -> impl Future<Output = Result<usize, HidError>>;
}
pub trait HidReaderTrait {
type ReportType;
fn read_report(&mut self) -> impl Future<Output = Result<Self::ReportType, HidError>>;
}
pub(crate) async fn run_led_reader<R: HidReaderTrait<ReportType = LedIndicator>>(
reader: &mut R,
kind: ConnectionType,
) -> ! {
loop {
match reader.read_report().await {
Ok(led_indicator) => {
info!("Got led indicator");
if crate::state::active_transport() == Some(kind) {
LOCK_LED_STATES.store(led_indicator.into_bits(), Ordering::Relaxed);
publish_event(LedIndicatorEvent::new(led_indicator));
}
}
Err(e) => {
debug!("Read HID LED indicator error: {:?}", e);
embassy_time::Timer::after_millis(1000).await;
}
}
}
}