From 8f8298d7f36111f77126be90a0520861942df438 Mon Sep 17 00:00:00 2001 From: Haobo Gu Date: Wed, 31 Jan 2024 21:25:37 +0800 Subject: [PATCH] feat(rmk): use composite report for mouse/media/system control Signed-off-by: Haobo Gu --- README.md | 4 +- boards/nrf52840/Cargo.lock | 1 + boards/rp2040/Cargo.lock | 1 + boards/stm32f4/Cargo.lock | 1 + boards/stm32h7/Cargo.lock | 1 + rmk/Cargo.toml | 1 + rmk/src/keyboard.rs | 72 +++++++++++++------- rmk/src/lib.rs | 4 +- rmk/src/usb.rs | 16 +++-- rmk/src/usb/descriptor.rs | 134 ++++++++++++++++++++++++++----------- 10 files changed, 159 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index d7e4b890..0274d019 100644 --- a/README.md +++ b/README.md @@ -93,11 +93,11 @@ A lot of todos at the list, any contributions are welcomed :) - [x] vial support - [x] eeprom - [x] project template +- [ ] mouse key - [ ] keyboard macro -- [ ] wireless - [ ] encoder - [ ] RGB -- [ ] cli tools +- [ ] wireless ## License diff --git a/boards/nrf52840/Cargo.lock b/boards/nrf52840/Cargo.lock index 20c9a6cd..737a2835 100644 --- a/boards/nrf52840/Cargo.lock +++ b/boards/nrf52840/Cargo.lock @@ -950,6 +950,7 @@ dependencies = [ "log", "num_enum", "packed_struct", + "ssmarshal", "static_cell", "usbd-hid", ] diff --git a/boards/rp2040/Cargo.lock b/boards/rp2040/Cargo.lock index 40febff9..07ca7612 100644 --- a/boards/rp2040/Cargo.lock +++ b/boards/rp2040/Cargo.lock @@ -1321,6 +1321,7 @@ dependencies = [ "log", "num_enum 0.7.2", "packed_struct", + "ssmarshal", "static_cell", "usbd-hid", ] diff --git a/boards/stm32f4/Cargo.lock b/boards/stm32f4/Cargo.lock index c2e382c7..89ba06cd 100644 --- a/boards/stm32f4/Cargo.lock +++ b/boards/stm32f4/Cargo.lock @@ -887,6 +887,7 @@ dependencies = [ "log", "num_enum", "packed_struct", + "ssmarshal", "static_cell", "usbd-hid", ] diff --git a/boards/stm32h7/Cargo.lock b/boards/stm32h7/Cargo.lock index bc6eaa05..20c907e8 100644 --- a/boards/stm32h7/Cargo.lock +++ b/boards/stm32h7/Cargo.lock @@ -887,6 +887,7 @@ dependencies = [ "log", "num_enum", "packed_struct", + "ssmarshal", "static_cell", "usbd-hid", ] diff --git a/rmk/Cargo.toml b/rmk/Cargo.toml index 6f1873fd..0f9fe892 100644 --- a/rmk/Cargo.toml +++ b/rmk/Cargo.toml @@ -23,6 +23,7 @@ embassy-usb = { version = "0.1", features = [ embassy-sync = { version = "0.5", features = ["defmt"] } embassy-futures = { version = "0.1", features = ["defmt"] } usbd-hid = { version = "0.6.1", features = ["defmt"] } +ssmarshal = { version = "1.0", default-features = false } defmt = "0.3" log = "0.4" static_cell = "2" diff --git a/rmk/src/keyboard.rs b/rmk/src/keyboard.rs index 136b0e39..69114705 100644 --- a/rmk/src/keyboard.rs +++ b/rmk/src/keyboard.rs @@ -3,15 +3,15 @@ use crate::{ keycode::{KeyCode, ModifierCombination}, keymap::KeyMap, matrix::{KeyState, Matrix}, - usb::descriptor::{MyKeyboardReport, ViaReport}, + usb::descriptor::{CompositeReport, CompositeReportType, ViaReport}, }; use core::{cell::RefCell, convert::Infallible}; -use defmt::{error, warn}; +use defmt::{debug, error, warn}; use embassy_time::Timer; use embassy_usb::{class::hid::HidReaderWriter, driver::Driver}; use embedded_hal::digital::{InputPin, OutputPin}; use embedded_storage::nor_flash::NorFlash; -use usbd_hid::descriptor::{KeyboardReport, MediaKeyboardReport, SystemControlReport}; +use usbd_hid::descriptor::KeyboardReport; #[derive(Debug)] pub struct KeyboardUsbConfig<'a> { @@ -74,16 +74,13 @@ pub(crate) struct Keyboard< /// Keyboard internal hid report buf report: KeyboardReport, - /// Media internal report - media_report: MyKeyboardReport, - - /// System control internal report - system_control_report: SystemControlReport, + /// Internal composite report: mouse + media(consumer) + system control + other_report: CompositeReport, /// Via report via_report: ViaReport, - /// Should send a new report? + /// Should send a new keyboard report? need_send_key_report: bool, /// Should send a consumer control report? @@ -91,6 +88,9 @@ pub(crate) struct Keyboard< /// Should send a system control report? need_send_system_control_report: bool, + + /// Should send a mouse report? + need_send_mouse_report: bool, } impl< @@ -119,8 +119,7 @@ impl< leds: 0, keycodes: [0; 6], }, - media_report: MyKeyboardReport::default(), - system_control_report: SystemControlReport { usage_id: 0 }, + other_report: CompositeReport::default(), via_report: ViaReport { input_data: [0; 32], output_data: [0; 32], @@ -128,6 +127,7 @@ impl< need_send_key_report: false, need_send_consumer_control_report: false, need_send_system_control_report: false, + need_send_mouse_report: false, } } @@ -193,21 +193,43 @@ impl< } } - pub(crate) async fn send_media_report<'d, D: Driver<'d>>( + pub(crate) async fn send_other_report<'d, D: Driver<'d>>( &mut self, - hid_interface: &mut HidReaderWriter<'d, D, 1, 8>, + hid_interface: &mut HidReaderWriter<'d, D, 1, 9>, ) { if self.need_send_consumer_control_report { - let mut buf: [u8; 9] = [0; 9]; - // Report id for media(consumer) - buf[0] = 0x02; - match hid_interface.write_serialize(&self.media_report).await { - Ok(()) => {} - Err(e) => error!("Send media(consumer control) report error: {}", e), - }; - self.media_report.usage_id = 0; + self.serialize_and_send_composite_report(hid_interface, CompositeReportType::Consumer) + .await; + self.other_report.media_usage_id = 0; self.need_send_consumer_control_report = false; } + + if self.need_send_system_control_report { + self.serialize_and_send_composite_report(hid_interface, CompositeReportType::System) + .await; + self.other_report.system_usage_id = 0; + self.need_send_system_control_report = false; + } + } + + async fn serialize_and_send_composite_report<'d, D: Driver<'d>>( + &mut self, + hid_interface: &mut HidReaderWriter<'d, D, 1, 9>, + report_type: CompositeReportType, + ) { + let mut buf: [u8; 9] = [0; 9]; + // Prepend report id + buf[0] = report_type as u8; + match self.other_report.serialize(&mut buf[1..], report_type) { + Ok(_) => { + debug!("Sending other report: {=[u8]:#X}", buf); + match hid_interface.write(&buf).await { + Ok(()) => {} + Err(e) => error!("Send other report error: {}", e), + }; + } + Err(_) => error!("Serialize other report error"), + } } /// Main keyboard task, it scans matrix, processes active keys @@ -367,10 +389,10 @@ impl< if key.is_consumer() { if key_state.pressed { let media_key = key.as_consumer_control_usage_id(); - self.media_report.usage_id = media_key as u16; + self.other_report.media_usage_id = media_key as u16; self.need_send_consumer_control_report = true; } else { - self.media_report.usage_id = 0; + self.other_report.media_usage_id = 0; self.need_send_consumer_control_report = true; } } @@ -381,11 +403,11 @@ impl< if key.is_system() { if key_state.pressed { if let Some(system_key) = key.as_system_control_usage_id() { - self.system_control_report.usage_id = system_key as u8; + self.other_report.system_usage_id = system_key as u8; self.need_send_system_control_report = true; } } else { - self.system_control_report.usage_id = 0; + self.other_report.system_usage_id = 0; self.need_send_system_control_report = true; } } diff --git a/rmk/src/lib.rs b/rmk/src/lib.rs index 1b0e82b4..16ce0a80 100644 --- a/rmk/src/lib.rs +++ b/rmk/src/lib.rs @@ -89,7 +89,7 @@ pub async fn initialize_keyboard_with_config_and_run< loop { let _ = keyboard.keyboard_task().await; keyboard.send_report(&mut usb_device.keyboard_hid).await; - keyboard.send_media_report(&mut usb_device.other_hid).await; + keyboard.send_other_report(&mut usb_device.other_hid).await; } }; @@ -135,7 +135,7 @@ pub async fn initialize_keyboard_and_run< loop { let _ = keyboard.keyboard_task().await; keyboard.send_report(&mut usb_device.keyboard_hid).await; - keyboard.send_media_report(&mut usb_device.other_hid).await; + keyboard.send_other_report(&mut usb_device.other_hid).await; } }; diff --git a/rmk/src/usb.rs b/rmk/src/usb.rs index 703bede9..2c39be63 100644 --- a/rmk/src/usb.rs +++ b/rmk/src/usb.rs @@ -1,3 +1,5 @@ +pub(crate) mod descriptor; + use core::sync::atomic::{AtomicBool, Ordering}; use defmt::info; use embassy_usb::{ @@ -9,13 +11,13 @@ use embassy_usb::{ use static_cell::StaticCell; use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; -pub(crate) mod descriptor; - -use crate::{keyboard::KeyboardUsbConfig, usb::descriptor::{MyKeyboardReport, ViaReport}}; +use crate::{ + keyboard::KeyboardUsbConfig, + usb::descriptor::{CompositeReport, ViaReport}, +}; static SUSPENDED: AtomicBool = AtomicBool::new(false); -// TODO: Use a composite hid device for Keyboard + Mouse + System control + Consumer control // In this case, report id should be used. // The keyboard usb device should have 3 hid instances: // 1. Boot keyboard: 1 endpoint in @@ -24,7 +26,7 @@ static SUSPENDED: AtomicBool = AtomicBool::new(false); pub(crate) struct KeyboardUsbDevice<'d, D: Driver<'d>> { pub(crate) device: UsbDevice<'d, D>, pub(crate) keyboard_hid: HidReaderWriter<'d, D, 1, 8>, - pub(crate) other_hid: HidReaderWriter<'d, D, 1, 8>, + pub(crate) other_hid: HidReaderWriter<'d, D, 1, 9>, pub(crate) via_hid: HidReaderWriter<'d, D, 32, 32>, } @@ -76,13 +78,13 @@ impl> KeyboardUsbDevice<'static, D> { ); let other_hid_config = Config { - report_descriptor: MyKeyboardReport::desc(), + report_descriptor: CompositeReport::desc(), request_handler: Some(&request_handler), poll_ms: 60, max_packet_size: 64, }; static OTHER_HID_STATE: StaticCell = StaticCell::new(); - let other_hid: HidReaderWriter<'_, D, 1, 8> = HidReaderWriter::new( + let other_hid: HidReaderWriter<'_, D, 1, 9> = HidReaderWriter::new( &mut builder, OTHER_HID_STATE.init(State::new()), other_hid_config, diff --git a/rmk/src/usb/descriptor.rs b/rmk/src/usb/descriptor.rs index ea40ef58..95b116cc 100644 --- a/rmk/src/usb/descriptor.rs +++ b/rmk/src/usb/descriptor.rs @@ -1,5 +1,7 @@ -use serde::ser::SerializeStruct; -use usbd_hid::descriptor::generator_prelude::*; +use ssmarshal::serialize; +use usbd_hid::descriptor::{ + generator_prelude::*, MediaKeyboardReport, MouseReport, SystemControlReport, +}; #[gen_hid_descriptor( (collection = APPLICATION, usage_page = 0xFF60, usage = 0x61) = { @@ -16,58 +18,110 @@ pub(crate) struct ViaReport { pub(crate) output_data: [u8; 32], } -// TODO: Composite hid report -// KeyboardReport describes a report and its companion descriptor that can be -// used to send keyboard button presses to a host and receive the status of the -// keyboard LEDs. +/// Predefined report ids for composite hid report. +/// Should be same with `#[gen_hid_descriptor]` +/// DO NOT EDIT +#[repr(u8)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] + +pub(crate) enum CompositeReportType { + None = 0x00, + Mouse = 0x01, + Consumer = 0x02, + System = 0x03, +} + +impl CompositeReportType { + fn from_u8(report_id: u8) -> Self { + match report_id { + 0x01 => Self::Mouse, + 0x02 => Self::Consumer, + 0x03 => Self::System, + _ => Self::None, + } + } +} + +/// A composite hid report which contains mouse, consumer, system reports. +/// Report id is used to distinguish from them. #[gen_hid_descriptor( - (report_id = 0x01, 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; - }; - (usage_min = 0x00, usage_max = 0xFF) = { - #[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; + (report_id = 0x01, collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = MOUSE) = { + (collection = PHYSICAL, usage = POINTER) = { + (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; + }; + }; }; }, (report_id = 0x02, collection = APPLICATION, usage_page = CONSUMER, usage = CONSUMER_CONTROL) = { (usage_page = CONSUMER, usage_min = 0x00, usage_max = 0x514) = { - #[item_settings data,array,absolute,not_null] usage_id=input; + #[item_settings data,array,absolute,not_null] media_usage_id=input; }; }, (report_id = 0x03, collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = SYSTEM_CONTROL) = { (usage_min = 0x81, usage_max = 0xB7, logical_min = 1) = { - #[item_settings data,array,absolute,not_null] usage_id=input; + #[item_settings data,array,absolute,not_null] system_usage_id=input; }; } )] -#[allow(dead_code)] #[derive(Default)] -pub struct MyKeyboardReport { - pub(crate) modifier: u8, - pub(crate) reserved: u8, - pub(crate) leds: u8, - pub(crate) keycodes: [u8; 6], - pub(crate) usage_id: u16, +pub struct CompositeReport { + pub(crate) buttons: u8, + pub(crate) x: i8, + pub(crate) y: i8, + pub(crate) wheel: i8, // Scroll down (negative) or up (positive) this many units + pub(crate) pan: i8, // Scroll left (negative) or right (positive) this many units + pub(crate) media_usage_id: u16, + pub(crate) system_usage_id: u8, } -impl MyKeyboardReport { - fn serialize_to_media(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut s = serializer.serialize_struct("OtherKeyboardReport", 2)?; - s.serialize_field("name", &self.leds)?; - // s.serialize_field("age", &self.age)?; - // s.serialize_field("phones", &self.phones)?; - s.end() - // serializer.serialize_bool(true) +impl CompositeReport { + pub(crate) fn serialize( + &self, + mut data: &mut [u8], + report_type: CompositeReportType, + ) -> Result { + // TODO: Optimize it + // Use usbd-hid's report to do serialization, but not so efficient. + match report_type { + CompositeReportType::None => Ok(0), + CompositeReportType::Mouse => { + let mouse_report = MouseReport { + buttons: self.buttons, + x: self.x, + y: self.y, + wheel: self.wheel, + pan: self.pan, + }; + Ok(serialize(&mut data, &mouse_report)?) + } + CompositeReportType::Consumer => { + let consumer_report = MediaKeyboardReport { + usage_id: self.media_usage_id, + }; + Ok(serialize(&mut data, &consumer_report)?) + } + CompositeReportType::System => { + let system_report = SystemControlReport { + usage_id: self.system_usage_id as u8, + }; + Ok(serialize(&mut data, &system_report)?) + } + } } } - -// impl AsInputReport for MyKeyboardReport {}