├── .gitignore ├── descriptors ├── Cargo.toml └── src │ └── lib.rs ├── README.md ├── Cargo.toml ├── macros ├── Cargo.toml └── src │ ├── packer.rs │ ├── item.rs │ ├── spec.rs │ └── lib.rs ├── .github └── workflows │ ├── test.yml │ ├── release-crates.yml │ └── check.yml ├── LICENSE └── src ├── lib.rs ├── hid_class.rs └── descriptor.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /macros/target 3 | **/*.rs.bk 4 | .#* 5 | Cargo.lock 6 | bin/*.after 7 | bin/*.before 8 | bin/*.o 9 | target/ 10 | -------------------------------------------------------------------------------- /descriptors/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["twitchyliquid64"] 3 | categories = ["embedded", "no-std"] 4 | description = "Low-level, wire-format enums/bitfields used in HID descriptors" 5 | keywords = ["no-std", "hid"] 6 | license = "MIT OR Apache-2.0" 7 | name = "usbd-hid-descriptors" 8 | version = "0.9.0" 9 | edition = "2021" 10 | 11 | [dependencies] 12 | bitfield = "~0.14" 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # usbd-hid 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/usbd-hid.svg)](https://crates.io/crates/usbd-hid) [![usbd-hid](https://docs.rs/usbd-hid/badge.svg)](https://docs.rs/usbd-hid) 4 | 5 | USB HID implementation for [usb-device](https://crates.io/crates/usb-device). 6 | 7 | Also implements a cheeky procedural macro for generating HID descriptors. 8 | 9 | Examples: 10 | 11 | - [twitching usb mouse](https://github.com/atsamd-rs/atsamd/blob/master/boards/itsybitsy_m0/examples/twitching_usb_mouse.rs) 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "macros", 4 | "descriptors", 5 | ] 6 | 7 | [package] 8 | name = "usbd-hid" 9 | description = "A HID class for use with usb-device." 10 | version = "0.9.0" 11 | keywords = ["hid", "no-std", "usb-device"] 12 | license = "MIT OR Apache-2.0" 13 | authors = ["twitchyliquid64"] 14 | edition = "2021" 15 | repository = "https://github.com/twitchyliquid64/usbd-hid" 16 | 17 | 18 | [dependencies] 19 | defmt = { version = "0.3", optional = true } 20 | usb-device = "0.3.0" 21 | usbd-hid-macros = { path = "macros", version = "0.9.0" } 22 | 23 | 24 | [features] 25 | # Add defmt Format support enums and structs 26 | defmt = ["dep:defmt", "usb-device/defmt"] 27 | -------------------------------------------------------------------------------- /macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["twitchyliquid64"] 3 | categories = ["embedded", "no-std"] 4 | description = "Internal crate: contains procedural macros for HID descriptors. Use the `usbd-hid` crate instead, these macros are re-exported there." 5 | keywords = ["no-std", "usb-device"] 6 | license = "MIT OR Apache-2.0" 7 | name = "usbd-hid-macros" 8 | version = "0.9.0" 9 | edition = "2021" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | byteorder = { version = "1.5", default-features = false } 16 | proc-macro2 = "1.0" 17 | quote = "1.0" 18 | serde = { version = "1.0", default-features = false } 19 | usbd-hid-descriptors = { path = "../descriptors", version = "0.9.0" } 20 | hashbrown = "0.13" 21 | log = "0.4" 22 | 23 | [dependencies.syn] 24 | features = ["extra-traits", "full", "visit"] 25 | version = "1.0" 26 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | jobs: 4 | native: 5 | runs-on: ${{ matrix.os }} 6 | strategy: 7 | matrix: 8 | os: [ubuntu-latest, windows-latest, macOS-latest] 9 | steps: 10 | - name: Checkout sources 11 | uses: actions/checkout@v2 12 | 13 | - name: Install Rust stable 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: stable 18 | override: true 19 | 20 | - name: Run cargo test - no features 21 | uses: actions-rs/cargo@v1 22 | with: 23 | command: test 24 | args: --all --no-default-features 25 | 26 | - name: Run cargo test - all features 27 | if: runner.os != 'Windows' 28 | uses: actions-rs/cargo@v1 29 | with: 30 | command: test 31 | args: --all --all-features 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 rightsholder 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /.github/workflows/release-crates.yml: -------------------------------------------------------------------------------- 1 | name: Release crates 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | release_desc: 6 | description: 'Release descriptor crate (yes/no)' 7 | required: true 8 | default: 'no' 9 | release_macro: 10 | description: 'Release macro crate (yes/no)' 11 | required: true 12 | default: 'yes' 13 | 14 | jobs: 15 | release-crates: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Set up Rust 19 | uses: hecrj/setup-rust-action@v1 20 | - uses: actions/checkout@v2 21 | 22 | - name: Login 23 | run: cargo login ${CRATES_IO_TOKEN} 24 | env: 25 | CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} 26 | 27 | - name: Release descriptor crate 28 | if: github.event.inputs.release_desc == 'yes' 29 | shell: bash 30 | run: | 31 | set -ex 32 | (cd "descriptors" && cargo publish ); 33 | 34 | - name: Release macro crate 35 | if: github.event.inputs.release_macro == 'yes' 36 | shell: bash 37 | run: | 38 | set -ex 39 | (cd "macros" && cargo publish ); 40 | 41 | - name: Release usbd-hid crate 42 | shell: bash 43 | run: | 44 | set -ex 45 | 46 | # Install random crate to force update of the registry 47 | cargo install lazy_static || true 48 | 49 | cargo publish 50 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Rust Checks 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | toolchain: stable 14 | override: true 15 | - uses: actions-rs/cargo@v1 16 | with: 17 | command: check 18 | - uses: actions-rs/cargo@v1 19 | with: 20 | command: check 21 | args: --features defmt 22 | 23 | build: 24 | name: Build 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v1 28 | - uses: actions-rs/toolchain@v1 29 | with: 30 | toolchain: stable 31 | override: true 32 | - uses: actions-rs/cargo@v1 33 | with: 34 | command: build 35 | - uses: actions-rs/cargo@v1 36 | with: 37 | command: build 38 | args: --features defmt 39 | 40 | fmt: 41 | name: Rustfmt 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v1 45 | - uses: actions-rs/toolchain@v1 46 | with: 47 | toolchain: stable 48 | override: true 49 | - run: rustup component add rustfmt 50 | - uses: actions-rs/cargo@v1 51 | with: 52 | command: fmt 53 | args: -- --check 54 | 55 | clippy: 56 | name: Clippy 57 | runs-on: ubuntu-latest 58 | steps: 59 | - uses: actions/checkout@v1 60 | - uses: actions-rs/toolchain@v1 61 | with: 62 | toolchain: stable 63 | override: true 64 | - run: rustup component add clippy 65 | - uses: actions-rs/cargo@v1 66 | with: 67 | command: clippy 68 | args: -- -D warnings 69 | - uses: actions-rs/cargo@v1 70 | with: 71 | command: clippy 72 | args: --features defmt -- -D warnings 73 | 74 | doc: 75 | name: Doc Check 76 | runs-on: ubuntu-latest 77 | steps: 78 | - uses: actions/checkout@v1 79 | - uses: actions-rs/toolchain@v1 80 | with: 81 | toolchain: stable 82 | override: true 83 | - uses: actions-rs/cargo@v1 84 | with: 85 | command: doc 86 | -------------------------------------------------------------------------------- /descriptors/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use bitfield::bitfield; 4 | 5 | /// GlobalItemKind describes global item tags as described in section 6.2.2.7 6 | /// 'Report Descriptor' of the spec, version 1.11. 7 | #[repr(u8)] 8 | #[allow(unused)] 9 | #[derive(Copy, Debug, Clone, Eq, PartialEq)] 10 | pub enum GlobalItemKind { 11 | UsagePage = 0, 12 | LogicalMin = 1, 13 | LogicalMax = 2, 14 | PhysicalMin = 3, 15 | PhysicalMax = 4, 16 | UnitExponent = 5, 17 | Unit = 6, 18 | ReportSize = 7, 19 | ReportID = 8, 20 | ReportCount = 9, 21 | } 22 | 23 | impl From for u8 { 24 | fn from(kind: GlobalItemKind) -> u8 { 25 | kind as u8 26 | } 27 | } 28 | 29 | /// LocalItemKind describes local item tags as described in section 6.2.2.8 30 | /// 'Local Items' of the spec, version 1.11. 31 | #[repr(u8)] 32 | #[allow(unused)] 33 | #[derive(Copy, Debug, Clone, Eq, PartialEq)] 34 | pub enum LocalItemKind { 35 | Usage = 0, 36 | UsageMin = 1, 37 | UsageMax = 2, 38 | DesignatorIdx = 3, 39 | DesignatorMin = 4, 40 | DesignatorMax = 5, 41 | StringIdx = 7, 42 | StringMin = 8, 43 | StringMax = 9, 44 | Delimiter = 10, 45 | } 46 | 47 | impl From for u8 { 48 | fn from(kind: LocalItemKind) -> u8 { 49 | kind as u8 50 | } 51 | } 52 | 53 | /// MainItemKind describes main item tags as described in section 6.2.2.4 54 | /// 'Report Descriptor' of the spec, version 1.11. 55 | #[repr(u8)] 56 | #[allow(unused)] 57 | #[derive(Copy, Debug, Default, Clone, Eq, PartialEq)] 58 | pub enum MainItemKind { 59 | #[default] 60 | Input = 0b1000, 61 | Output = 0b1001, 62 | Feature = 0b1011, 63 | Collection = 0b1010, 64 | EndCollection = 0b1100, 65 | } 66 | 67 | impl From for u8 { 68 | fn from(kind: MainItemKind) -> u8 { 69 | kind as u8 70 | } 71 | } 72 | 73 | impl From<&str> for MainItemKind { 74 | fn from(s: &str) -> Self { 75 | match s { 76 | "feature" => MainItemKind::Feature, 77 | "output" => MainItemKind::Output, 78 | "collection" => MainItemKind::Collection, 79 | "ecollection" => MainItemKind::EndCollection, 80 | "input" => MainItemKind::Input, 81 | _ => MainItemKind::Input, 82 | } 83 | } 84 | } 85 | 86 | /// ItemType describes types of items as described in section 6.2.2.7 87 | /// 'Report Descriptor' of the spec, version 1.11. 88 | #[repr(u8)] 89 | #[allow(unused)] 90 | #[derive(Copy, Debug, Default, Clone, Eq, PartialEq)] 91 | pub enum ItemType { 92 | #[default] 93 | Main = 0, 94 | Global = 1, 95 | Local = 2, 96 | } 97 | 98 | impl From for u8 { 99 | fn from(kind: ItemType) -> u8 { 100 | kind as u8 101 | } 102 | } 103 | 104 | bitfield! { 105 | /// MainItemSetting describes the bits which configure invariants on a MainItem. 106 | #[derive(Clone,Debug)] 107 | pub struct MainItemSetting(u8); 108 | pub is_constant, set_constant: 0; 109 | pub is_variable, set_variable: 1; 110 | pub is_relative, set_relative: 2; 111 | pub is_wrap, set_wrap: 3; 112 | pub is_non_linear, set_non_linear: 4; 113 | pub has_no_preferred_state, set_no_preferred_state: 5; 114 | pub has_null_state, set_has_null_state: 6; 115 | pub volatile, set_volatile: 7; 116 | } 117 | 118 | bitfield! { 119 | /// ItemPrefix describes the 1 byte prefix describing an item in a descriptor. 120 | pub struct ItemPrefix(u8); 121 | impl Debug; 122 | pub byte_count, set_byte_count: 1, 0; 123 | pub typ, set_type: 3, 2; 124 | pub tag, set_tag: 7, 4; 125 | } 126 | -------------------------------------------------------------------------------- /macros/src/packer.rs: -------------------------------------------------------------------------------- 1 | extern crate usbd_hid_descriptors; 2 | use usbd_hid_descriptors::*; 3 | 4 | use alloc::vec::Vec; 5 | 6 | use proc_macro2::TokenStream; 7 | use quote::quote; 8 | use syn::{parse, Result}; 9 | 10 | use crate::item::*; 11 | use crate::spec::*; 12 | 13 | pub fn uses_report_ids(spec: &Spec) -> bool { 14 | match spec { 15 | Spec::MainItem(_) => false, 16 | Spec::Collection(c) => { 17 | for s in c.fields.values() { 18 | if uses_report_ids(s) { 19 | return true; 20 | } 21 | } 22 | c.report_id.is_some() 23 | } 24 | } 25 | } 26 | 27 | pub fn gen_serializer(fields: Vec, typ: MainItemKind) -> Result { 28 | let mut elems: Vec = Vec::new(); 29 | 30 | let mut offset = 0; 31 | for field in fields { 32 | if field.descriptor_item.kind != typ { 33 | continue; 34 | } 35 | let ident = &field.ident; 36 | 37 | let report_size = field.descriptor_item.report_size; 38 | let report_count = field.descriptor_item.report_count; 39 | let rc = match report_size { 40 | 1 => { 41 | // bitfield, stored in a u8 / u16 / u32 42 | elems.push(write_value(field.bit_width, offset, quote!(#ident))); 43 | offset += field.bit_width / 8; 44 | Ok(()) 45 | } 46 | 8 => { 47 | // u8 / i8 48 | if report_count == 1 { 49 | elems.push(write_value(8, offset, quote!(#ident))); 50 | offset += 1; 51 | } else { 52 | // byte array 53 | for i in 0..report_count as usize { 54 | elems.push(write_value(8, offset, quote!(#ident[#i]))); 55 | offset += 1; 56 | } 57 | } 58 | Ok(()) 59 | } 60 | 16 | 32 => { 61 | // u16 / i16 / u32 / i32 62 | if report_count == 1 { 63 | elems.push(write_value(report_size as usize, offset, quote!(#ident))); 64 | offset += report_size as usize / 8; 65 | Ok(()) 66 | } else { 67 | Err(parse::Error::new( 68 | field.ident.span(), 69 | "Arrays of 16/32bit fields not supported", 70 | )) 71 | } 72 | } 73 | _ => Err(parse::Error::new( 74 | field.ident.span(), 75 | "Unsupported report size for serialization", 76 | )), 77 | }; 78 | 79 | rc?; 80 | } 81 | 82 | Ok(quote!({ 83 | if #offset > buffer.len() { 84 | return Err(usbd_hid::descriptor::BufferOverflow); 85 | } 86 | #( #elems; )* 87 | Ok(#offset) 88 | })) 89 | } 90 | 91 | fn write_value(bit_width: usize, offset: usize, ident: TokenStream) -> TokenStream { 92 | if bit_width == 8 { 93 | quote!(buffer[#offset] = self.#ident as u8) 94 | } else if bit_width == 16 { 95 | quote!( 96 | let [v1, v2] = self.#ident.to_le_bytes(); 97 | buffer[#offset] = v1; 98 | buffer[#offset + 1] = v2; 99 | ) 100 | } else if bit_width == 32 { 101 | quote!( 102 | let [v1, v2, v3, v4] = (self.#ident as u32).to_le_bytes(); 103 | buffer[#offset] = v1; 104 | buffer[#offset + 1] = v2; 105 | buffer[#offset + 2] = v3; 106 | buffer[#offset + 3] = v4; 107 | ) 108 | } else { 109 | unreachable!("Earlier diagnostic check catches this") 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /macros/src/item.rs: -------------------------------------------------------------------------------- 1 | extern crate usbd_hid_descriptors; 2 | 3 | use crate::spec::*; 4 | use alloc::{ 5 | format, 6 | string::{String, ToString}, 7 | }; 8 | use syn::{parse, Expr, ExprLit, Field, Fields, Ident, Lit, Result, Type, TypePath}; 9 | use usbd_hid_descriptors::*; 10 | 11 | // MainItem describes all the mandatory data points of a Main item. 12 | #[derive(Debug, Default, Clone)] 13 | pub struct MainItem { 14 | pub kind: MainItemKind, 15 | pub logical_minimum: isize, 16 | pub logical_maximum: isize, 17 | pub report_count: u16, 18 | pub report_size: u16, 19 | pub padding_bits: Option, 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | pub struct ReportUnaryField { 24 | pub bit_width: usize, 25 | pub descriptor_item: MainItem, 26 | pub ident: Ident, 27 | } 28 | 29 | /// analyze_field constructs a main item from an item spec & field. 30 | pub fn analyze_field(field: Field, ft: Type, item: &ItemSpec) -> Result { 31 | let (p, size) = parse_type(&field, ft)?; 32 | 33 | if p.path.segments.len() != 1 { 34 | return Err(parse::Error::new( 35 | field.ident.unwrap().span(), 36 | "`#[gen_hid_descriptor]` internal error when unwrapping type", 37 | )); 38 | } 39 | let type_ident = p.path.segments[0].ident.clone(); 40 | 41 | let type_str = type_ident.to_string(); 42 | let (sign, size_str) = type_str.as_str().split_at(1); 43 | let bit_width = size_str.parse(); 44 | let type_setter: Option = match sign { 45 | "u" => Some(set_unsigned_unary_item), 46 | "i" => Some(set_signed_unary_item), 47 | &_ => None, 48 | }; 49 | 50 | if bit_width.is_err() || type_setter.is_none() { 51 | return Err(parse::Error::new( 52 | type_ident.span(), 53 | "`#[gen_hid_descriptor]` type not supported", 54 | )); 55 | } 56 | let bit_width = bit_width.unwrap(); 57 | 58 | if bit_width >= 64 { 59 | return Err(parse::Error::new( 60 | type_ident.span(), 61 | // TODO: not sure if this is a limitation of the HID protocol or just this crate. 62 | "`#[gen_hid_descriptor]` integer >= 64 bits is not supported in usbd_hid", 63 | )); 64 | } 65 | 66 | let mut output = unary_item(field.ident.clone().unwrap(), item.kind, bit_width); 67 | 68 | if let Some(want_bits) = item.want_bits { 69 | // bitpack 70 | output.descriptor_item.logical_minimum = 0; 71 | output.descriptor_item.logical_maximum = 1; 72 | output.descriptor_item.report_count = want_bits; 73 | output.descriptor_item.report_size = 1; 74 | let width = output.bit_width * size; 75 | if width < want_bits as usize { 76 | return Err(parse::Error::new( 77 | field.ident.unwrap().span(), 78 | format!( 79 | "`#[gen_hid_descriptor]` not enough space, missing {} bit(s)", 80 | want_bits as usize - width 81 | ), 82 | )); 83 | } 84 | let remaining_bits = width as u16 - want_bits; 85 | if remaining_bits > 0 { 86 | output.descriptor_item.padding_bits = Some(remaining_bits); 87 | } 88 | } else { 89 | // array of reports 90 | type_setter.unwrap()(&mut output, bit_width); 91 | output.descriptor_item.report_count *= size as u16; 92 | } 93 | 94 | Ok(output) 95 | } 96 | 97 | fn parse_type(field: &Field, ft: Type) -> Result<(TypePath, usize)> { 98 | match ft { 99 | Type::Array(a) => { 100 | let mut size: usize = 0; 101 | 102 | if let Expr::Lit(ExprLit { 103 | lit: Lit::Int(lit), .. 104 | }) = a.len 105 | { 106 | if let Ok(num) = lit.base10_parse::() { 107 | size = num; 108 | } 109 | } 110 | if size == 0 { 111 | Err(parse::Error::new( 112 | field.ident.as_ref().unwrap().span(), 113 | "`#[gen_hid_descriptor]` array has invalid length", 114 | )) 115 | } else { 116 | Ok((parse_type(field, *a.elem)?.0, size)) 117 | } 118 | } 119 | Type::Path(p) => Ok((p, 1)), 120 | _ => Err(parse::Error::new( 121 | field.ident.as_ref().unwrap().span(), 122 | "`#[gen_hid_descriptor]` cannot handle field type", 123 | )), 124 | } 125 | } 126 | 127 | fn set_signed_unary_item(out: &mut ReportUnaryField, bit_width: usize) { 128 | let bound = 2u32.pow((bit_width - 1) as u32) as isize - 1; 129 | out.descriptor_item.logical_minimum = -bound; 130 | out.descriptor_item.logical_maximum = bound; 131 | } 132 | 133 | fn set_unsigned_unary_item(out: &mut ReportUnaryField, bit_width: usize) { 134 | out.descriptor_item.logical_minimum = 0; 135 | out.descriptor_item.logical_maximum = 2u32.pow(bit_width as u32) as isize - 1; 136 | } 137 | 138 | fn unary_item(id: Ident, kind: MainItemKind, bit_width: usize) -> ReportUnaryField { 139 | ReportUnaryField { 140 | ident: id, 141 | bit_width, 142 | descriptor_item: MainItem { 143 | kind, 144 | logical_minimum: 0, 145 | logical_maximum: 0, 146 | report_count: 1, 147 | report_size: bit_width as u16, 148 | padding_bits: None, 149 | }, 150 | } 151 | } 152 | 153 | pub fn field_decl(fields: &Fields, name: String) -> Field { 154 | for field in fields { 155 | let ident = field.ident.clone().unwrap().to_string(); 156 | if ident == name { 157 | return field.clone(); 158 | } 159 | } 160 | panic!( 161 | "internal error: could not find field {} which should exist", 162 | name 163 | ) 164 | } 165 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! HID report descriptor generation & USB HID class implementation 2 | //! 3 | //! This crate implements components necessary to build a USB HID device. This 4 | //! includes generation of the report descriptor, serialization of input reports, 5 | //! and communicating with a host that implements USB HID. 6 | #![no_std] 7 | 8 | pub use usb_device::{Result, UsbError}; 9 | pub mod descriptor; 10 | pub mod hid_class; 11 | 12 | // Allow gen_hid_descriptor macro to access usbd_hid types from within usbd_hid itself, 13 | // while retaining the ability to run the macro in user crates as well. 14 | extern crate self as usbd_hid; 15 | 16 | #[cfg(test)] 17 | #[allow(unused_imports)] 18 | mod tests { 19 | use crate::descriptor::{generator_prelude::*, CtapReport}; 20 | use crate::descriptor::{KeyboardReport, MouseReport, SystemControlReport}; 21 | 22 | fn serialize(buf: &mut [u8], report: T) -> &[u8] { 23 | let size = report.serialize(buf).unwrap(); 24 | &buf[..size] 25 | } 26 | 27 | // This should generate this descriptor: 28 | // 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) 29 | // 0x09, 0x01, // Usage (0x01) 30 | // 0xA1, 0x01, // Collection (Application) 31 | // 0x15, 0x00, // Logical Minimum (0) 32 | // 0x26, 0xFF, 0x00, // Logical Maximum (255) 33 | // 0x75, 0x08, // Report Size (8) 34 | // 0x95, 0x01, // Report Count (1) 35 | // 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 36 | // 0x27, 0xFF, 0xFF, 0x00, 0x00, // Logical Maximum (65534) 37 | // 0x75, 0x10, // Report Size (16) 38 | // 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 39 | // 0xC1, // End Collection 40 | #[gen_hid_descriptor( 41 | (collection = 0x01, usage = 0x01, usage_page = 0xff00) = { 42 | f1=input; 43 | f2=output; 44 | } 45 | )] 46 | #[allow(dead_code)] 47 | struct CustomUnaryUnsignedFrame { 48 | f1: u8, 49 | f2: u16, 50 | } 51 | 52 | #[test] 53 | fn test_custom_unsigned() { 54 | let expected = &[ 55 | 6u8, 0u8, 255u8, 9u8, 1u8, 161u8, 1u8, 21u8, 0u8, 38u8, 255u8, 0u8, 117u8, 8u8, 149u8, 56 | 1u8, 129u8, 2u8, 39u8, 255u8, 255u8, 0u8, 0u8, 117u8, 16u8, 145u8, 2u8, 192u8, 57 | ]; 58 | assert_eq!(CustomUnaryUnsignedFrame::desc(), expected); 59 | } 60 | 61 | #[test] 62 | fn test_custom_unsigned_serialize() { 63 | let expected = &[1]; 64 | let report = CustomUnaryUnsignedFrame { f1: 1, f2: 2 }; 65 | let mut buf = [0; 64]; 66 | let result = serialize(&mut buf, report); 67 | assert_eq!(result, expected); 68 | } 69 | 70 | // This should generate this descriptor: 71 | // 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) 72 | // 0x09, 0x01, // Usage (0x01) 73 | // 0xA1, 0x01, // Collection (Application) 74 | // 0x17, 0x81, 0xFF, 0xFF, 0xFF, // Logical Minimum (-128) 75 | // 0x25, 0x7F, // Logical Maximum (127) 76 | // 0x75, 0x08, // Report Size (8) 77 | // 0x95, 0x01, // Report Count (1) 78 | // 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 79 | // 0x17, 0x01, 0x80, 0xFF, 0xFF, // Logical Minimum (-32768) 80 | // 0x26, 0xFF, 0x7F, // Logical Maximum (32767) 81 | // 0x75, 0x10, // Report Size (16) 82 | // 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 83 | // 0xC0, // End Collection 84 | #[gen_hid_descriptor( 85 | (collection = 0x01, usage = 0x01, usage_page = 0xff00) = { 86 | f1=input; 87 | f2=output; 88 | } 89 | )] 90 | #[allow(dead_code)] 91 | struct CustomUnarySignedFrame { 92 | f1: i8, 93 | f2: i16, 94 | } 95 | 96 | #[test] 97 | fn test_custom_signed() { 98 | let expected = &[ 99 | 6u8, 0u8, 255u8, 9u8, 1u8, 161u8, 1u8, 23u8, 129u8, 255u8, 255u8, 255u8, 37u8, 127u8, 100 | 117u8, 8u8, 149u8, 1u8, 129u8, 2u8, 23u8, 1u8, 128u8, 255u8, 255u8, 38u8, 255u8, 127u8, 101 | 117u8, 16u8, 145u8, 2u8, 192u8, 102 | ]; 103 | assert_eq!(CustomUnarySignedFrame::desc()[0..32], expected[0..32]); 104 | } 105 | 106 | #[test] 107 | fn test_custom_signed_serialize() { 108 | let expected = &[1]; 109 | let report = CustomUnarySignedFrame { f1: 1, f2: 2 }; 110 | let mut buf = [0; 64]; 111 | let result = serialize(&mut buf, report); 112 | assert_eq!(result, expected); 113 | } 114 | 115 | #[gen_hid_descriptor( 116 | (report_id = 0x01,) = { 117 | f1=input 118 | }, 119 | (report_id = 0x02,) = { 120 | f2=input 121 | }, 122 | )] 123 | #[allow(dead_code)] 124 | struct CustomMultiReport { 125 | f1: u8, 126 | f2: u8, 127 | } 128 | 129 | #[test] 130 | fn test_custom_reports() { 131 | let expected: &[u8] = &[ 132 | 133, 1, 21, 0, 38, 255, 0, 117, 8, 149, 1, 129, 2, 133, 2, 129, 2, 133 | ]; 134 | assert_eq!(CustomMultiReport::desc(), expected); 135 | } 136 | 137 | // This should generate the following descriptor: 138 | // 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) 139 | // 0x09, 0x01, // Usage (0x01) 140 | // 0xA1, 0x01, // Collection (Application) 141 | // 0x15, 0x00, // Logical Minimum (0) 142 | // 0x26, 0xFF, 0x00, // Logical Maximum (255) 143 | // 0x75, 0x08, // Report Size (8) 144 | // 0x95, 0x20, // Report Count (32) 145 | // 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 146 | // 0xC0, // End Collection 147 | #[gen_hid_descriptor( 148 | (collection = 0x01, usage = 0x01, usage_page = 0xff00) = { 149 | buff=input; 150 | } 151 | )] 152 | #[allow(dead_code)] 153 | struct CustomArray { 154 | buff: [u8; 32], 155 | } 156 | 157 | #[test] 158 | fn test_array() { 159 | let expected: &[u8] = &[ 160 | 6, 0, 255, 9, 1, 161, 1, 21, 0, 38, 255, 0, 117, 8, 149, 32, 129, 2, 192, 161 | ]; 162 | assert_eq!(CustomArray::desc(), expected); 163 | } 164 | 165 | #[test] 166 | fn test_array_serialize() { 167 | let expected = [ 168 | 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 169 | 4, 4, 4, 170 | ]; 171 | let report = CustomArray { buff: [4; 32] }; 172 | let mut buf = [0; 64]; 173 | let result = serialize(&mut buf, report); 174 | assert_eq!(result, expected); 175 | } 176 | 177 | #[gen_hid_descriptor( 178 | (collection = APPLICATION, usage_page = VENDOR_DEFINED_START, usage = 0x01) = { 179 | (usage_min = BUTTON_1, usage_max = BUTTON_3) = { 180 | #[item_settings data,variable,relative] f1=input; 181 | }; 182 | } 183 | )] 184 | #[allow(dead_code)] 185 | struct CustomConst { 186 | f1: u8, 187 | } 188 | 189 | #[test] 190 | fn test_custom_const() { 191 | let expected = &[ 192 | 6u8, 0u8, 255u8, 9u8, 1u8, 161u8, 1u8, 25u8, 1u8, 41u8, 3u8, 21u8, 0u8, 38u8, 255u8, 193 | 0u8, 117u8, 8u8, 149u8, 1u8, 129u8, 6u8, 192u8, 194 | ]; 195 | assert_eq!(CustomConst::desc(), expected); 196 | } 197 | 198 | #[test] 199 | fn test_custom_const_serialize() { 200 | let expected = &[1]; 201 | let report = CustomConst { f1: 1 }; 202 | let mut buf = [0; 64]; 203 | let result = serialize(&mut buf, report); 204 | assert_eq!(result, expected); 205 | } 206 | 207 | // This should generate the following descriptor: 208 | // 0x85, 0x01, // Report ID (1) 209 | // 0x15, 0x00, // Logical Minimum (0) 210 | // 0x25, 0x01, // Logical Maximum (1) 211 | // 0x75, 0x01, // Report Size (1) 212 | // 0x95, 0x03, // Report Count (3) 213 | // 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 214 | // 0x95, 0x05, // Report Count (5) 215 | // 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 216 | // 0x95, 0x09, // Report Count (9) 217 | // 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 218 | // 0x95, 0x07, // Report Count (7) 219 | // 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 220 | // 0x95, 0x14, // Report Count (20) 221 | // 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 222 | // 0x95, 0x04, // Report Count (4) 223 | // 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 224 | #[gen_hid_descriptor( 225 | (report_id = 0x01,) = { 226 | #[packed_bits 3] f1=input; 227 | #[packed_bits 9] f2=input; 228 | #[packed_bits 20] f3=input; 229 | } 230 | )] 231 | #[allow(dead_code)] 232 | struct CustomPackedBits { 233 | f1: u8, 234 | f2: u16, 235 | f3: [u8; 3], 236 | } 237 | 238 | #[test] 239 | fn test_custom_packed_bits() { 240 | let expected = &[ 241 | 133u8, 1u8, 21u8, 0u8, 37u8, 1u8, 117u8, 1u8, 149u8, 3u8, 129u8, 2u8, 149u8, 5u8, 242 | 129u8, 3u8, 149u8, 9u8, 129u8, 2u8, 149u8, 7u8, 129u8, 3u8, 149u8, 20u8, 129u8, 2u8, 243 | 149u8, 4u8, 129u8, 3u8, 244 | ]; 245 | assert_eq!(CustomPackedBits::desc(), expected); 246 | } 247 | 248 | #[test] 249 | fn test_mouse_descriptor() { 250 | let expected = &[ 251 | 5u8, 1u8, 9u8, 2u8, 161u8, 1u8, 9u8, 1u8, 161u8, 0u8, 5u8, 9u8, 25u8, 1u8, 41u8, 8u8, 252 | 21u8, 0u8, 37u8, 1u8, 117u8, 1u8, 149u8, 8u8, 129u8, 2u8, 5u8, 1u8, 9u8, 48u8, 23u8, 253 | 129u8, 255u8, 255u8, 255u8, 37u8, 127u8, 117u8, 8u8, 149u8, 1u8, 129u8, 6u8, 9u8, 49u8, 254 | 129u8, 6u8, 9u8, 56u8, 129u8, 6u8, 5u8, 12u8, 10u8, 56u8, 2u8, 129u8, 6u8, 192u8, 255 | 192u8, 256 | ]; 257 | assert_eq!(MouseReport::desc()[0..32], expected[0..32]); 258 | } 259 | 260 | #[test] 261 | fn test_mouse_serialize() { 262 | let expected = &[1, 2, 3, 4, 5]; 263 | let report = MouseReport { 264 | buttons: 1, 265 | x: 2, 266 | y: 3, 267 | wheel: 4, 268 | pan: 5, 269 | }; 270 | let mut buf = [0; 64]; 271 | let result = serialize(&mut buf, report); 272 | assert_eq!(result, expected); 273 | } 274 | 275 | #[test] 276 | fn test_keyboard_descriptor() { 277 | let expected = &[ 278 | 0x05, 0x01, // Usage Page (Generic Desktop) 279 | 0x09, 0x06, // Usage (Keyboard) 280 | 0xa1, 0x01, // Collection (Application) 281 | 0x05, 0x07, // Usage Page (Key Codes) 282 | 0x19, 0xe0, // Usage Minimum (224) 283 | 0x29, 0xe7, // Usage Maximum (231) 284 | 0x15, 0x00, // Logical Minimum (0) 285 | 0x25, 0x01, // Logical Maximum (1) 286 | 0x75, 0x01, // Report Size (1) 287 | 0x95, 0x08, // Report count (8) 288 | 0x81, 0x02, // Input (Data, Variable, Absolute) 289 | 0x19, 0x00, // Usage Minimum (0) 290 | 0x29, 0xFF, // Usage Maximum (255) 291 | 0x26, 0xFF, 0x00, // Logical Maximum (255) 292 | 0x75, 0x08, // Report Size (8) 293 | 0x95, 0x01, // Report Count (1) 294 | 0x81, 0x03, // Input (Const, Variable, Absolute) 295 | 0x05, 0x08, // Usage Page (LEDs) 296 | 0x19, 0x01, // Usage Minimum (1) 297 | 0x29, 0x05, // Usage Maximum (5) 298 | 0x25, 0x01, // Logical Maximum (1) 299 | 0x75, 0x01, // Report Size (1) 300 | 0x95, 0x05, // Report Count (5) 301 | 0x91, 0x02, // Output (Data, Variable, Absolute) 302 | 0x95, 0x03, // Report Count (3) 303 | 0x91, 0x03, // Output (Constant, Variable, Absolute) 304 | 0x05, 0x07, // Usage Page (Key Codes) 305 | 0x19, 0x00, // Usage Minimum (0) 306 | 0x29, 0xDD, // Usage Maximum (221) 307 | 0x26, 0xFF, 0x00, // Logical Maximum (255) 308 | 0x75, 0x08, // Report Size (8) 309 | 0x95, 0x06, // Report Count (6) 310 | 0x81, 0x00, // Input (Data, Array, Absolute) 311 | 0xc0, // End Collection 312 | ]; 313 | assert_eq!(KeyboardReport::desc(), expected); 314 | } 315 | 316 | #[test] 317 | fn test_keyboard_serialize() { 318 | let expected = &[1, 2, 4, 5, 6, 7, 8, 9]; 319 | let report = KeyboardReport { 320 | modifier: 1, 321 | reserved: 2, 322 | leds: 3, 323 | keycodes: [4, 5, 6, 7, 8, 9], 324 | }; 325 | let mut buf = [0; 64]; 326 | let result = serialize(&mut buf, report); 327 | assert_eq!(result, expected); 328 | } 329 | 330 | #[test] 331 | fn test_system_control_descriptor() { 332 | let expected = &[ 333 | 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) 334 | 0x09, 0x80, // Usage (Sys Control) 335 | 0xA1, 0x01, // Collection (Application) 336 | 0x19, 0x81, // Usage Minimum (Sys Power Down) 337 | 0x29, 0xB7, // Usage Maximum (Sys Display LCD Autoscale) 338 | 0x15, 0x01, // Logical Minimum (1) 339 | 0x26, 0xFF, 0x00, // Logical Maximum (255) 340 | 0x75, 0x08, // Report Size (8) 341 | 0x95, 0x01, // Report Count (1) 342 | 0x81, 343 | 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) 344 | 0xC0, // End Collection 345 | ]; 346 | assert_eq!(SystemControlReport::desc(), expected); 347 | } 348 | 349 | #[test] 350 | fn test_system_control_serialize() { 351 | let expected = &[4]; 352 | let report = SystemControlReport { usage_id: 4 }; 353 | let mut buf = [0; 64]; 354 | let result = serialize(&mut buf, report); 355 | assert_eq!(result, expected); 356 | } 357 | 358 | #[test] 359 | fn test_ctap_serialize() { 360 | let expected = &[ 361 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 362 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 363 | 1, 1, 1, 1, 1, 1, 364 | ]; 365 | let report = CtapReport { 366 | data_in: [1; 64], 367 | data_out: [2; 64], 368 | }; 369 | let mut buf = [0; 64]; 370 | let result = serialize(&mut buf, report); 371 | assert_eq!(result, expected); 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /macros/src/spec.rs: -------------------------------------------------------------------------------- 1 | extern crate usbd_hid_descriptors; 2 | 3 | use quote::quote; 4 | use syn::parse::{Parse, ParseStream}; 5 | use syn::punctuated::Punctuated; 6 | use syn::{parse, Attribute, Expr, ExprAssign, ExprPath, Path, Result, Token}; 7 | use syn::{Block, ExprBlock, ExprLit, ExprTuple, Lit, Stmt}; 8 | 9 | use alloc::{ 10 | borrow::ToOwned, 11 | format, 12 | string::{String, ToString}, 13 | vec, 14 | vec::Vec, 15 | }; 16 | use hashbrown::HashMap; 17 | use syn::spanned::Spanned; 18 | use syn::visit::Visit; 19 | use usbd_hid_descriptors::*; 20 | 21 | // Spec describes an item within a HID report. 22 | #[derive(Debug, Clone)] 23 | pub enum Spec { 24 | MainItem(ItemSpec), 25 | Collection(GroupSpec), 26 | } 27 | 28 | // ItemQuirks describes minor settings which can be tweaked for 29 | // compatibility. 30 | #[derive(Debug, Clone, Default, Copy)] 31 | pub struct ItemQuirks { 32 | pub allow_short_form: bool, 33 | } 34 | 35 | // ItemSpec describes settings that apply to a single field. 36 | #[derive(Debug, Clone, Default)] 37 | pub struct ItemSpec { 38 | pub kind: MainItemKind, 39 | pub quirks: ItemQuirks, 40 | pub settings: Option, 41 | pub want_bits: Option, 42 | } 43 | 44 | /// GroupSpec keeps track of consecutive fields with shared global 45 | /// parameters. Fields are configured based on the attributes 46 | /// used in the procedural macro's invocation. 47 | #[derive(Debug, Clone, Default)] 48 | pub struct GroupSpec { 49 | pub fields: HashMap, 50 | pub field_order: Vec, 51 | 52 | pub report_id: Option, 53 | pub usage_page: Option, 54 | pub collection: Option, 55 | pub logical_min: Option, 56 | pub unit_exponent: Option, 57 | 58 | // Local items 59 | pub usage: Vec, 60 | pub usage_min: Option, 61 | pub usage_max: Option, 62 | } 63 | 64 | impl GroupSpec { 65 | pub fn set_item( 66 | &mut self, 67 | name: String, 68 | item_kind: MainItemKind, 69 | settings: Option, 70 | bits: Option, 71 | quirks: ItemQuirks, 72 | ) { 73 | if let Some(field) = self.fields.get_mut(&name) { 74 | if let Spec::MainItem(field) = field { 75 | field.kind = item_kind; 76 | field.settings = settings; 77 | field.want_bits = bits; 78 | } 79 | } else { 80 | self.fields.insert( 81 | name.clone(), 82 | Spec::MainItem(ItemSpec { 83 | kind: item_kind, 84 | settings, 85 | want_bits: bits, 86 | quirks, 87 | }), 88 | ); 89 | self.field_order.push(name); 90 | } 91 | } 92 | 93 | pub fn add_nested_group(&mut self, ng: GroupSpec) { 94 | let name = (0..self.fields.len() + 1).map(|_| "_").collect::(); 95 | self.fields.insert(name.clone(), Spec::Collection(ng)); 96 | self.field_order.push(name); 97 | } 98 | 99 | pub fn get(&self, name: String) -> Option<&Spec> { 100 | self.fields.get(&name) 101 | } 102 | 103 | pub fn try_set_attr(&mut self, input: ParseStream, name: String, val: u32) -> Result<()> { 104 | match name.as_str() { 105 | "report_id" => { 106 | self.report_id = Some(val); 107 | Ok(()) 108 | } 109 | "usage_page" => { 110 | self.usage_page = Some(val); 111 | Ok(()) 112 | } 113 | "collection" => { 114 | self.collection = Some(val); 115 | Ok(()) 116 | } 117 | "unit_exponent" => { 118 | self.unit_exponent = Some(val); 119 | Ok(()) 120 | } 121 | // Local items. 122 | "usage" => { 123 | self.usage.push(val); 124 | Ok(()) 125 | } 126 | "usage_min" => { 127 | self.usage_min = Some(val); 128 | Ok(()) 129 | } 130 | "usage_max" => { 131 | self.usage_max = Some(val); 132 | Ok(()) 133 | } 134 | "logical_min" => { 135 | self.logical_min = Some(val); 136 | Ok(()) 137 | } 138 | _ => Err(parse::Error::new( 139 | input.span(), 140 | format!( 141 | "`#[gen_hid_descriptor]` unknown group spec key: {}", 142 | name.clone() 143 | ), 144 | )), 145 | } 146 | } 147 | } 148 | 149 | impl IntoIterator for GroupSpec { 150 | type Item = String; 151 | type IntoIter = vec::IntoIter; 152 | 153 | fn into_iter(self) -> Self::IntoIter { 154 | self.field_order.into_iter() 155 | } 156 | } 157 | 158 | pub fn try_resolve_constant(key_name: String, path: String) -> Option { 159 | match (key_name.as_str(), path.as_str()) { 160 | ("collection", "PHYSICAL") => Some(0x0), 161 | ("collection", "APPLICATION") => Some(0x1), 162 | ("collection", "LOGICAL") => Some(0x2), 163 | ("collection", "REPORT") => Some(0x3), 164 | ("collection", "NAMED_ARRAY") => Some(0x4), 165 | ("collection", "USAGE_SWITCH") => Some(0x5), 166 | ("collection", "USAGE_MODIFIER") => Some(0x06), 167 | 168 | ("usage_page", "UNDEFINED") => Some(0x00), 169 | ("usage_page", "GENERIC_DESKTOP") => Some(0x01), 170 | ("usage_page", "SIMULATION_CONTROLS") => Some(0x02), 171 | ("usage_page", "VR_CONTROLS") => Some(0x03), 172 | ("usage_page", "SPORT_CONTROLS") => Some(0x04), 173 | ("usage_page", "GAME_CONTROLS") => Some(0x05), 174 | ("usage_page", "GENERIC_DEVICE_CONTROLS") => Some(0x06), 175 | ("usage_page", "KEYBOARD") => Some(0x07), 176 | ("usage_page", "LEDS") => Some(0x08), 177 | ("usage_page", "BUTTON") => Some(0x09), 178 | ("usage_page", "ORDINAL") => Some(0x0A), 179 | ("usage_page", "TELEPHONY") => Some(0x0B), 180 | ("usage_page", "CONSUMER") => Some(0x0C), 181 | ("usage_page", "DIGITIZER") => Some(0x0D), 182 | ("usage_page", "ALPHANUMERIC_DISPLAY") => Some(0x14), 183 | ("usage_page", "SENSOR") => Some(0x20), 184 | ("usage_page", "BARCODE_SCANNER") => Some(0x8C), 185 | ("usage_page", "FIDO_ALLIANCE") => Some(0xF1D0), 186 | ("usage_page", "VENDOR_DEFINED_START") => Some(0xFF00), 187 | ("usage_page", "VENDOR_DEFINED_END") => Some(0xFFFF), 188 | 189 | // Desktop usage_page usage ID's. 190 | ("usage", "POINTER") => Some(0x01), 191 | ("usage", "MOUSE") => Some(0x02), 192 | ("usage", "JOYSTICK") => Some(0x04), 193 | ("usage", "GAMEPAD") => Some(0x05), 194 | ("usage", "KEYBOARD") => Some(0x06), 195 | ("usage", "KEYPAD") => Some(0x07), 196 | ("usage", "MULTI_AXIS_CONTROLLER") => Some(0x08), 197 | ("usage", "X") | ("usage_min", "X") | ("usage_max", "X") => Some(0x30), 198 | ("usage", "Y") | ("usage_min", "Y") | ("usage_max", "Y") => Some(0x31), 199 | ("usage", "Z") | ("usage_min", "Z") | ("usage_max", "Z") => Some(0x32), 200 | ("usage", "WHEEL") => Some(0x38), 201 | ("usage", "SYSTEM_CONTROL") => Some(0x80), 202 | 203 | // LED usage_page usage ID's. 204 | ("usage", "NUM_LOCK") => Some(0x01), 205 | ("usage", "CAPS_LOCK") => Some(0x02), 206 | ("usage", "SCROLL_LOCK") => Some(0x03), 207 | ("usage", "POWER") => Some(0x06), 208 | ("usage", "SHIFT") => Some(0x07), 209 | ("usage", "MUTE") => Some(0x09), 210 | ("usage", "RING") => Some(0x18), 211 | 212 | // Button usage_page usage ID's. 213 | ("usage", "BUTTON_NONE") => Some(0x00), 214 | ("usage", "BUTTON_1") | ("usage_min", "BUTTON_1") => Some(0x01), 215 | ("usage", "BUTTON_2") => Some(0x02), 216 | ("usage", "BUTTON_3") | ("usage_max", "BUTTON_3") => Some(0x03), 217 | ("usage", "BUTTON_4") | ("usage_max", "BUTTON_4") => Some(0x04), 218 | ("usage", "BUTTON_5") => Some(0x05), 219 | ("usage", "BUTTON_6") => Some(0x06), 220 | ("usage", "BUTTON_7") => Some(0x07), 221 | ("usage", "BUTTON_8") | ("usage_max", "BUTTON_8") => Some(0x08), 222 | 223 | // Alpha-numeric display usage_page usage ID's. 224 | ("usage", "CLEAR_DISPLAY") => Some(0x25), 225 | ("usage", "DISPLAY_ENABLE") => Some(0x26), 226 | ("usage", "CHARACTER_REPORT") => Some(0x2B), 227 | ("usage", "CHARACTER_DATA") => Some(0x2C), 228 | 229 | // Consumer usage 230 | ("usage", "CONSUMER_CONTROL") => Some(0x01), 231 | ("usage", "NUMERIC_KEYPAD") => Some(0x02), 232 | ("usage", "PROGRAMMABLE_BUTTONS") => Some(0x03), 233 | ("usage", "MICROPHONE") => Some(0x04), 234 | ("usage", "HEADPHONE") => Some(0x05), 235 | ("usage", "GRAPHIC_EQUALIZER") => Some(0x06), 236 | ("usage", "AC_PAN") => Some(0x0238), 237 | 238 | // sensor power states 239 | ("usage", "SENSOR_POWER_STATE") => Some(0x0319), 240 | ("usage", "SENSOR_POWER_STATE_UNDEFINED") => Some(0x0850), 241 | ("usage", "SENSOR_POWER_STATE_D0_FULL_POWER") => Some(0x0851), 242 | ("usage", "SENSOR_POWER_STATE_D1_LOW_POWER") => Some(0x0852), 243 | ("usage", "SENSOR_POWER_STATE_D2_STANDBY_WITH_WAKE") => Some(0x0853), 244 | ("usage", "SENSOR_POWER_STATE_D3_SLEEP_WITH_WAKE") => Some(0x0854), 245 | ("usage", "SENSOR_POWER_STATE_D4_POWER_OFF") => Some(0x0855), 246 | 247 | // FIDO Alliance usage_page 248 | ("usage", "U2F_AUTHENTICATOR_DEVICE") => Some(0x1), 249 | ("usage", "INPUT_REPORT_DATA") => Some(0x20), 250 | ("usage", "OUTPUT_REPORT_DATA") => Some(0x21), 251 | 252 | (_, _) => None, 253 | } 254 | } 255 | 256 | fn parse_group_spec(input: ParseStream, field: Expr) -> Result { 257 | let mut collection_attrs: Vec<(String, u32)> = vec![]; 258 | 259 | if let Expr::Assign(ExprAssign { left, .. }) = field.clone() { 260 | if let Expr::Tuple(ExprTuple { elems, .. }) = *left { 261 | for elem in elems { 262 | let group_attr = maybe_parse_kv_lhs(elem.clone()); 263 | if group_attr.is_none() || group_attr.clone().unwrap().len() != 1 { 264 | return Err(parse::Error::new( 265 | input.span(), 266 | "`#[gen_hid_descriptor]` group spec key can only have a single element", 267 | )); 268 | } 269 | let group_attr = group_attr.unwrap()[0].clone(); 270 | 271 | let mut val: Option = None; 272 | if let Expr::Assign(ExprAssign { right, .. }) = elem { 273 | if let Expr::Lit(ExprLit { lit, .. }) = *right { 274 | if let Lit::Int(lit) = lit { 275 | if let Ok(num) = lit.base10_parse::() { 276 | val = Some(num); 277 | } 278 | } 279 | } else if let Expr::Path(ExprPath { 280 | path: Path { segments, .. }, 281 | .. 282 | }) = *right 283 | { 284 | val = try_resolve_constant( 285 | group_attr.clone(), 286 | quote! { #segments }.to_string(), 287 | ); 288 | if val.is_none() { 289 | return Err(parse::Error::new( 290 | input.span(), 291 | format!( 292 | "`#[gen_hid_descriptor]` unrecognized constant: {}", 293 | quote! { #segments } 294 | ), 295 | )); 296 | } 297 | } 298 | } 299 | if val.is_none() { 300 | return Err(parse::Error::new(input.span(), "`#[gen_hid_descriptor]` group spec attribute value must be a numeric literal or recognized constant")); 301 | } 302 | collection_attrs.push((group_attr, val.unwrap())); 303 | } 304 | } 305 | } 306 | if collection_attrs.is_empty() { 307 | return Err(parse::Error::new( 308 | input.span(), 309 | "`#[gen_hid_descriptor]` group spec lhs must contain value pairs", 310 | )); 311 | } 312 | let mut out = GroupSpec { 313 | ..Default::default() 314 | }; 315 | for (key, val) in collection_attrs { 316 | out.try_set_attr(input, key, val)?; 317 | } 318 | 319 | // Match out the item kind on the right of the equals. 320 | if let Expr::Assign(ExprAssign { right, .. }) = field { 321 | if let Expr::Block(ExprBlock { 322 | block: Block { stmts, .. }, 323 | .. 324 | }) = *right 325 | { 326 | for stmt in stmts { 327 | if let Stmt::Expr(e) = stmt { 328 | out.from_field(input, e)?; 329 | } else if let Stmt::Semi(e, _) = stmt { 330 | out.from_field(input, e)?; 331 | } else { 332 | return Err(parse::Error::new(input.span(), "`#[gen_hid_descriptor]` group spec body can only contain semicolon-separated fields")); 333 | } 334 | } 335 | } else { 336 | return Err(parse::Error::new( 337 | right.span(), 338 | "`#[gen_hid_descriptor]` group spec rhs must be a block (did you miss a `,`)", 339 | )); 340 | }; 341 | }; 342 | Ok(out) 343 | } 344 | 345 | /// maybe_parse_kv_lhs returns a vector of :: separated idents. 346 | fn maybe_parse_kv_lhs(field: Expr) -> Option> { 347 | if let Expr::Assign(ExprAssign { left, .. }) = field { 348 | if let Expr::Path(ExprPath { 349 | path: Path { segments, .. }, 350 | .. 351 | }) = *left 352 | { 353 | let mut out: Vec = vec![]; 354 | for s in segments { 355 | out.push(s.ident.to_string()); 356 | } 357 | return Some(out); 358 | } 359 | } 360 | None 361 | } 362 | 363 | fn parse_item_attrs(attrs: Vec) -> (Option, Option, ItemQuirks) { 364 | let mut out: MainItemSetting = MainItemSetting(0); 365 | let mut had_settings: bool = false; 366 | let mut packed_bits: Option = None; 367 | let mut quirks: ItemQuirks = ItemQuirks { 368 | ..Default::default() 369 | }; 370 | 371 | for attr in attrs { 372 | match attr.path.segments[0].ident.to_string().as_str() { 373 | "packed_bits" => { 374 | for tok in attr.tokens { 375 | if let proc_macro2::TokenTree::Literal(lit) = tok { 376 | if let Ok(num) = lit.to_string().parse::() { 377 | packed_bits = Some(num); 378 | break; 379 | } 380 | } 381 | } 382 | if packed_bits.is_none() { 383 | log::warn!("bitfield attribute specified but failed to read number of bits from token!"); 384 | } 385 | } 386 | 387 | "item_settings" => { 388 | had_settings = true; 389 | for setting in attr.tokens { 390 | if let proc_macro2::TokenTree::Ident(id) = setting { 391 | match id.to_string().as_str() { 392 | "constant" => out.set_constant(true), 393 | "data" => out.set_constant(false), 394 | 395 | "variable" => out.set_variable(true), 396 | "array" => out.set_variable(false), 397 | 398 | "relative" => out.set_relative(true), 399 | "absolute" => out.set_relative(false), 400 | 401 | "wrap" => out.set_wrap(true), 402 | "no_wrap" => out.set_wrap(false), 403 | 404 | "non_linear" => out.set_non_linear(true), 405 | "linear" => out.set_non_linear(false), 406 | 407 | "no_preferred" => out.set_no_preferred_state(true), 408 | "preferred" => out.set_no_preferred_state(false), 409 | 410 | "null" => out.set_has_null_state(true), 411 | "not_null" => out.set_has_null_state(false), 412 | 413 | "volatile" => out.set_volatile(true), 414 | "not_volatile" => out.set_volatile(false), 415 | p => log::warn!("Unknown item_settings parameter: {p}"), 416 | } 417 | } 418 | } 419 | } 420 | 421 | "quirks" => { 422 | for setting in attr.tokens { 423 | if let proc_macro2::TokenTree::Ident(id) = setting { 424 | match id.to_string().as_str() { 425 | "allow_short" => quirks.allow_short_form = true, 426 | p => log::warn!("Unknown item_settings parameter: {p}"), 427 | } 428 | } 429 | } 430 | } 431 | 432 | p => log::warn!("Unknown item attribute: {p}"), 433 | } 434 | } 435 | 436 | if had_settings { 437 | return (Some(out), packed_bits, quirks); 438 | } 439 | (None, packed_bits, quirks) 440 | } 441 | 442 | // maybe_parse_kv tries to parse an expression like 'blah=blah'. 443 | #[allow(clippy::type_complexity)] 444 | fn maybe_parse_kv( 445 | field: Expr, 446 | ) -> Option<( 447 | String, 448 | String, 449 | Option, 450 | Option, 451 | ItemQuirks, 452 | )> { 453 | // Match out the identifier on the left of the equals. 454 | let name: String; 455 | if let Some(lhs) = maybe_parse_kv_lhs(field.clone()) { 456 | if lhs.len() != 1 { 457 | return None; 458 | } 459 | name = lhs[0].clone(); 460 | } else { 461 | return None; 462 | } 463 | 464 | // Decode item settings. 465 | let item_settings = if let Some(attrs) = AttributeCollector::all(&field) { 466 | parse_item_attrs(attrs) 467 | } else { 468 | (None, None, ItemQuirks::default()) 469 | }; 470 | 471 | // Match out the item kind on the right of the equals. 472 | let mut val: Option = None; 473 | if let Expr::Assign(ExprAssign { right, .. }) = field { 474 | if let Expr::Path(ExprPath { 475 | path: Path { segments, .. }, 476 | .. 477 | }) = *right 478 | { 479 | val = Some(segments[0].ident.clone().to_string()); 480 | } 481 | }; 482 | val.as_ref()?; 483 | 484 | Some(( 485 | name, 486 | val.unwrap(), 487 | item_settings.0, 488 | item_settings.1, 489 | item_settings.2, 490 | )) 491 | } 492 | 493 | struct AttributeCollector(Vec); 494 | 495 | impl AttributeCollector { 496 | fn new() -> Self { 497 | Self(vec![]) 498 | } 499 | 500 | /// Recursively finds all Attributes contained by an Expr. 501 | /// Returns None when no attributes are found. 502 | pub fn all(expr: &'_ Expr) -> Option> { 503 | let mut visitor = Self::new(); 504 | visitor.visit_expr(expr); 505 | if visitor.0.is_empty() { 506 | None 507 | } else { 508 | Some(visitor.0) 509 | } 510 | } 511 | } 512 | 513 | impl<'ast> Visit<'ast> for AttributeCollector { 514 | fn visit_attribute(&mut self, node: &'ast Attribute) { 515 | self.0.push(node.to_owned()); 516 | } 517 | } 518 | 519 | impl Parse for GroupSpec { 520 | fn parse(input: ParseStream) -> Result { 521 | let mut out = GroupSpec { 522 | ..Default::default() 523 | }; 524 | let fields: Punctuated = input.parse_terminated(Expr::parse)?; 525 | if fields.is_empty() { 526 | return Err(parse::Error::new( 527 | input.span(), 528 | "`#[gen_hid_descriptor]` expected information about the HID report", 529 | )); 530 | } 531 | for field in fields { 532 | out.from_field(input, field)?; 533 | } 534 | Ok(out) 535 | } 536 | } 537 | 538 | impl GroupSpec { 539 | #[allow(clippy::wrong_self_convention)] 540 | fn from_field(&mut self, input: ParseStream, field: Expr) -> Result<()> { 541 | if let Some(i) = maybe_parse_kv(field.clone()) { 542 | let (name, item_kind, settings, bits, quirks) = i; 543 | self.set_item(name, item_kind.as_str().into(), settings, bits, quirks); 544 | return Ok(()); 545 | }; 546 | match parse_group_spec(input, field) { 547 | Err(e) => return Err(e), 548 | Ok(g) => self.add_nested_group(g), 549 | }; 550 | Ok(()) 551 | } 552 | } 553 | -------------------------------------------------------------------------------- /macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Internal implementation details of usbd-hid. 2 | #![no_std] 3 | 4 | extern crate alloc; 5 | extern crate proc_macro; 6 | extern crate usbd_hid_descriptors; 7 | 8 | use alloc::{boxed::Box, vec, vec::Vec}; 9 | use proc_macro::TokenStream; 10 | use proc_macro2::Span; 11 | use quote::quote; 12 | use syn::punctuated::Punctuated; 13 | use syn::token::Bracket; 14 | use syn::{parse, parse_macro_input, Expr, Fields, ItemStruct}; 15 | use syn::{Pat, PatSlice, Result}; 16 | 17 | use byteorder::{ByteOrder, LittleEndian}; 18 | use usbd_hid_descriptors::*; 19 | 20 | mod spec; 21 | use spec::*; 22 | mod item; 23 | use item::*; 24 | mod packer; 25 | use packer::{gen_serializer, uses_report_ids}; 26 | 27 | /// Attribute to generate a HID descriptor & serialization code 28 | /// 29 | /// You are expected to provide two inputs to this generator: 30 | /// 31 | /// - A struct of named fields (which follows the `gen_hid_descriptor` attribute) 32 | /// - A specially-formatted section describing the properties of the descriptor (this 33 | /// section must be provided as arguments to the `gen_hid_descriptor()` attribute) 34 | /// 35 | /// The generated HID descriptor will be available as a `&[u8]` by calling 36 | /// `YourStructType::desc()`. `YourStructType` also now implements `SerializedDescriptor`. 37 | /// 38 | /// As long as a descriptor describes only input or output types, and a report ID is 39 | /// not used, the wire format for transmitting and recieving the data described by the 40 | /// descriptor is simply the packed representation of the struct itself. 41 | /// Where report ID's are used anywhere in the descriptor, you must prepend the relevant 42 | /// report ID to the packed representation of the struct prior to transmission. 43 | /// 44 | /// If inputs and outputs are mixed within the same HID descriptor, then only the struct 45 | /// fields used in that direction can be present in a payload being transmitted in that 46 | /// direction. 47 | /// 48 | /// If report ID's are not used, input (device-to-host) serialization code is generated 49 | /// automatically, and is represented by the implementation of the `AsInputReport` trait. 50 | /// 51 | /// # Examples 52 | /// 53 | /// - Custom 32-octet array, sent from device to host 54 | /// 55 | /// ```ignore 56 | /// #[gen_hid_descriptor( 57 | /// (collection = APPLICATION, usage_page = VENDOR_DEFINED_START, usage = 0x01) = { 58 | /// buff=input; 59 | /// } 60 | /// )] 61 | /// struct CustomInputReport { 62 | /// buff: [u8; 32], 63 | /// } 64 | /// ``` 65 | /// 66 | /// - Custom input / output, sent in either direction 67 | /// 68 | /// ```ignore 69 | /// #[gen_hid_descriptor( 70 | /// (collection = APPLICATION, usage_page = VENDOR_DEFINED_START, usage = 0x01) = { 71 | /// input_buffer=input; 72 | /// output_buffer=output; 73 | /// } 74 | /// )] 75 | /// struct CustomBidirectionalReport { 76 | /// input_buffer: [u8; 32], 77 | /// output_buffer: [u8; 32], 78 | /// } 79 | /// ``` 80 | /// 81 | /// Because both inputs and outputs are used, the data format when sending / recieving is the 82 | /// 32 bytes in the relevant direction, **NOT** the full 64 bytes contained within the struct. 83 | /// 84 | /// - Packed bitfields 85 | /// 86 | /// ```ignore 87 | /// #[gen_hid_descriptor( 88 | /// (report_id = 0x01,) = { 89 | /// #[packed_bits 3] f1=input; 90 | /// #[packed_bits 9] f2=input; 91 | /// } 92 | /// )] 93 | /// struct CustomPackedBits { 94 | /// f1: u8, 95 | /// f2: u16, 96 | /// } 97 | /// ``` 98 | /// 99 | /// Because the `#[packed_bits]` sub-attribute was used, the two input fields specified are 100 | /// interpreted as packed bits. As such, `f1` describes 3 boolean inputs, and `f2` describes 101 | /// 9 boolean inputs. Padding constants are automatically generated. 102 | /// 103 | /// The `#[packed_bits ]` feature is intended to be used for describing button presses. 104 | /// 105 | /// - Customizing the settings on a report item 106 | /// 107 | /// ```ignore 108 | /// #[gen_hid_descriptor( 109 | /// (collection = APPLICATION, usage_page = VENDOR_DEFINED_START, usage = 0x01) = { 110 | /// (usage_min = X, usage_max = Y) = { 111 | /// #[item_settings data,variable,relative] x=input; 112 | /// #[item_settings data,variable,relative] y=input; 113 | /// }; 114 | /// } 115 | /// )] 116 | /// struct CustomCoords { 117 | /// x: i8, 118 | /// y: i8, 119 | /// } 120 | /// ``` 121 | /// 122 | /// The above example describes a report which sends X & Y co-ordinates. As indicated in 123 | /// the `#[item_settings]` sub-attribute, the individual inputs are described as: 124 | /// 125 | /// - Datapoints (`data`) - as opposed to constant 126 | /// - Variable (`variable`) - as opposed to an array 127 | /// - Relative (`relative`) - as opposed to absolute 128 | /// 129 | /// # Supported struct types 130 | /// 131 | /// The struct following the attribute must consist entirely of named fields, using 132 | /// only types enumerated below, or fixed-size arrays of the types enumerated below. 133 | /// 134 | /// - u8 / i8 135 | /// - u16 / i16 136 | /// - u32 / i32 137 | /// 138 | /// `LOGICAL_MINIMUM` & `LOGICAL_MAXIMUM` are automatically set in the descriptor, based 139 | /// on the type & whether `#[packed_bits]` was set on the field or not. 140 | /// 141 | /// # Descriptor format 142 | /// 143 | /// The parameters of the HID descriptor should be provided as arguments to the attribute. 144 | /// The arguments should follow the basic form: 145 | /// 146 | /// ```ignore 147 | /// #[gen_hid_descriptor( 148 | /// OR ; 149 | /// OR ; 150 | /// ... 151 | /// OR 152 | /// )] 153 | /// ``` 154 | /// 155 | /// ## `collection-spec`: 156 | /// 157 | /// ```text 158 | /// (parameter = , ...) = { 159 | /// OR ; 160 | /// ... 161 | /// } 162 | /// ``` 163 | /// 164 | /// Note: All collection specs must end in a semicolon, except the top-level one. 165 | /// 166 | /// Note: Parameters are a tuple, so make sure you have a trailing comma if you only have one 167 | /// parameter. 168 | /// 169 | /// The valid parameters are `collection`, `usage_page`, `usage`, `usage_min`, `usage_max`, 170 | /// `unit_exponent`, and `report_id`. 171 | /// These simply configure parameters that apply to contained items in the report. 172 | /// Use of the `collection` parameter automatically creates a collection feature for all items 173 | /// which are contained within it, and other parameters specified in the same collection-spec 174 | /// apply to the collection, not directly to the elements of the collection (ie: defining a 175 | /// collection + a usage generates a descriptor where the usage is set on the collection, not the 176 | /// items contained within the collection). 177 | /// 178 | /// ## `item-spec`: 179 | /// 180 | /// ```ignore 181 | /// #[packed_bits ] #[item_settings ,...] =input OR output; 182 | /// ``` 183 | /// 184 | /// The two sub-attributes are both optional. 185 | /// 186 | /// - `fieldname` refers to the name of a field within the struct. All fields must be specified. 187 | /// - `input` fields are sent in reports from device to host. `output` fields are sent in reports 188 | /// from host to device. This matches the terminology used in the USB & HID specifications. 189 | /// - `packed_bits` configures the field as a set of `num_items` booleans rather than a number. 190 | /// If the number of packed bits is less than the natural bit width of the field, the 191 | /// remaining most-significant bits are set as constants within the report and are not used. 192 | /// `packed_bits` is typically used to implement buttons. 193 | /// - `item_settings` describes settings on the input/output item, as enumerated in section 194 | /// 6.2.2.5 of the [HID specification, version 1.11](https://www.usb.org/sites/default/files/documents/hid1_11.pdf). 195 | /// By default, all items are configured as `(Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)`. 196 | /// 197 | /// ## Quirks 198 | /// 199 | /// By default generated descriptors are such to maximize compatibility. To change this 200 | /// behaviour, you can use a `#[quirks ]` attribute on the relevant input/output 201 | /// item. 202 | /// For now, the only quirk is `#[quirks allow_short]`, which allows global features to be 203 | /// serialized in a 1 byte form. This is disabled by default as the Windows HID parser 204 | /// considers it invalid. 205 | #[proc_macro_attribute] 206 | pub fn gen_hid_descriptor(args: TokenStream, input: TokenStream) -> TokenStream { 207 | let decl = parse_macro_input!(input as ItemStruct); 208 | let spec = parse_macro_input!(args as GroupSpec); 209 | let ident = decl.ident.clone(); 210 | 211 | // Error if the struct doesn't name its fields. 212 | match decl.fields { 213 | Fields::Named(_) => (), 214 | _ => { 215 | return parse::Error::new( 216 | ident.span(), 217 | "`#[gen_hid_descriptor]` type must name fields", 218 | ) 219 | .to_compile_error() 220 | .into() 221 | } 222 | }; 223 | 224 | let do_serialize = !uses_report_ids(&Spec::Collection(spec.clone())); 225 | 226 | let output = match compile_descriptor(spec, &decl.fields) { 227 | Ok(d) => d, 228 | Err(e) => return e.to_compile_error().into(), 229 | }; 230 | let (descriptor, fields) = output; 231 | 232 | let mut out = quote! { 233 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 234 | #[repr(C, packed)] 235 | #decl 236 | 237 | impl SerializedDescriptor for #ident { 238 | fn desc() -> &'static[u8] { 239 | &#descriptor 240 | } 241 | } 242 | }; 243 | 244 | if do_serialize { 245 | let input_serializer = match gen_serializer(fields, MainItemKind::Input) { 246 | Ok(s) => s, 247 | Err(e) => return e.to_compile_error().into(), 248 | }; 249 | 250 | out = quote! { 251 | #out 252 | 253 | impl AsInputReport for #ident { 254 | fn serialize(&self, buffer: &mut [u8]) -> Result 255 | { 256 | #input_serializer 257 | } 258 | } 259 | }; 260 | } 261 | 262 | TokenStream::from(out) 263 | } 264 | 265 | fn compile_descriptor( 266 | spec: GroupSpec, 267 | fields: &Fields, 268 | ) -> Result<(PatSlice, Vec)> { 269 | let mut compiler = DescCompilation { 270 | ..Default::default() 271 | }; 272 | let mut elems = Punctuated::new(); 273 | compiler.emit_group(&mut elems, &spec, fields)?; 274 | 275 | Ok(( 276 | PatSlice { 277 | attrs: vec![], 278 | elems, 279 | bracket_token: Bracket { 280 | span: Span::call_site(), 281 | }, 282 | }, 283 | compiler.report_fields(), 284 | )) 285 | } 286 | 287 | #[derive(Default)] 288 | struct DescCompilation { 289 | logical_minimum: Option, 290 | logical_maximum: Option, 291 | report_size: Option, 292 | report_count: Option, 293 | processed_fields: Vec, 294 | } 295 | 296 | impl DescCompilation { 297 | fn report_fields(&self) -> Vec { 298 | self.processed_fields.clone() 299 | } 300 | 301 | fn emit( 302 | &self, 303 | elems: &mut Punctuated, 304 | prefix: &mut ItemPrefix, 305 | buf: [u8; 4], 306 | signed: bool, 307 | ) { 308 | // println!("buf: {:?}", buf); 309 | if buf[1..4] == [0, 0, 0] && !(signed && buf[0] == 255) { 310 | prefix.set_byte_count(1); 311 | elems.push(byte_literal(prefix.0)); 312 | elems.push(byte_literal(buf[0])); 313 | } else if buf[2..4] == [0, 0] && !(signed && buf[1] == 255) { 314 | prefix.set_byte_count(2); 315 | elems.push(byte_literal(prefix.0)); 316 | elems.push(byte_literal(buf[0])); 317 | elems.push(byte_literal(buf[1])); 318 | } else { 319 | prefix.set_byte_count(3); 320 | elems.push(byte_literal(prefix.0)); 321 | elems.push(byte_literal(buf[0])); 322 | elems.push(byte_literal(buf[1])); 323 | elems.push(byte_literal(buf[2])); 324 | elems.push(byte_literal(buf[3])); 325 | } 326 | // println!("emitted {} data bytes", prefix.byte_count()); 327 | } 328 | 329 | fn emit_item( 330 | &self, 331 | elems: &mut Punctuated, 332 | typ: u8, 333 | kind: u8, 334 | num: isize, 335 | signed: bool, 336 | allow_short_form: bool, 337 | ) { 338 | let mut prefix = ItemPrefix(0); 339 | prefix.set_tag(kind); 340 | prefix.set_type(typ); 341 | 342 | // TODO: Support long tags. 343 | 344 | // Section 6.2.2.4: An Input item could have a data size of zero (0) 345 | // bytes. In this case the value of each data bit for the item can be 346 | // assumed to be zero. This is functionally identical to using a item 347 | // tag that specifies a 4-byte data item followed by four zero bytes. 348 | let allow_short = typ == ItemType::Main.into() && kind == MainItemKind::Input.into(); 349 | if allow_short_form && allow_short && num == 0 { 350 | prefix.set_byte_count(0); 351 | elems.push(byte_literal(prefix.0)); 352 | return; 353 | } 354 | 355 | let mut buf = [0; 4]; 356 | LittleEndian::write_i32(&mut buf, num as i32); 357 | self.emit(elems, &mut prefix, buf, signed); 358 | } 359 | 360 | fn handle_globals( 361 | &mut self, 362 | elems: &mut Punctuated, 363 | item: MainItem, 364 | quirks: ItemQuirks, 365 | ) { 366 | if self.logical_minimum.is_none() || self.logical_minimum.unwrap() != item.logical_minimum { 367 | self.emit_item( 368 | elems, 369 | ItemType::Global.into(), 370 | GlobalItemKind::LogicalMin.into(), 371 | item.logical_minimum, 372 | true, 373 | quirks.allow_short_form, 374 | ); 375 | self.logical_minimum = Some(item.logical_minimum); 376 | } 377 | if self.logical_maximum.is_none() || self.logical_maximum.unwrap() != item.logical_maximum { 378 | self.emit_item( 379 | elems, 380 | ItemType::Global.into(), 381 | GlobalItemKind::LogicalMax.into(), 382 | item.logical_maximum, 383 | true, 384 | quirks.allow_short_form, 385 | ); 386 | self.logical_maximum = Some(item.logical_maximum); 387 | } 388 | if self.report_size.is_none() || self.report_size.unwrap() != item.report_size { 389 | self.emit_item( 390 | elems, 391 | ItemType::Global.into(), 392 | GlobalItemKind::ReportSize.into(), 393 | item.report_size as isize, 394 | true, 395 | quirks.allow_short_form, 396 | ); 397 | self.report_size = Some(item.report_size); 398 | } 399 | if self.report_count.is_none() || self.report_count.unwrap() != item.report_count { 400 | self.emit_item( 401 | elems, 402 | ItemType::Global.into(), 403 | GlobalItemKind::ReportCount.into(), 404 | item.report_count as isize, 405 | true, 406 | quirks.allow_short_form, 407 | ); 408 | self.report_count = Some(item.report_count); 409 | } 410 | } 411 | 412 | fn emit_field( 413 | &mut self, 414 | elems: &mut Punctuated, 415 | i: &ItemSpec, 416 | item: MainItem, 417 | ) { 418 | self.handle_globals(elems, item.clone(), i.quirks); 419 | let item_data = match &i.settings { 420 | Some(s) => s.0 as isize, 421 | None => 0x02, // 0x02 = Data,Var,Abs 422 | }; 423 | self.emit_item( 424 | elems, 425 | ItemType::Main.into(), 426 | item.kind.into(), 427 | item_data, 428 | true, 429 | i.quirks.allow_short_form, 430 | ); 431 | 432 | if let Some(padding) = item.padding_bits { 433 | // Make another item of type constant to carry the remaining bits. 434 | let padding = MainItem { 435 | report_size: 1, 436 | report_count: padding, 437 | ..item 438 | }; 439 | self.handle_globals(elems, padding, i.quirks); 440 | 441 | let mut const_settings = MainItemSetting(0); 442 | const_settings.set_constant(true); 443 | const_settings.set_variable(true); 444 | self.emit_item( 445 | elems, 446 | ItemType::Main.into(), 447 | item.kind.into(), 448 | const_settings.0 as isize, 449 | true, 450 | i.quirks.allow_short_form, 451 | ); 452 | } 453 | } 454 | 455 | fn emit_group( 456 | &mut self, 457 | elems: &mut Punctuated, 458 | spec: &GroupSpec, 459 | fields: &Fields, 460 | ) -> Result<()> { 461 | // println!("GROUP: {:?}", spec); 462 | 463 | if let Some(usage_page) = spec.usage_page { 464 | self.emit_item( 465 | elems, 466 | ItemType::Global.into(), 467 | GlobalItemKind::UsagePage.into(), 468 | usage_page as isize, 469 | false, 470 | false, 471 | ); 472 | } 473 | for usage in &spec.usage { 474 | self.emit_item( 475 | elems, 476 | ItemType::Local.into(), 477 | LocalItemKind::Usage.into(), 478 | *usage as isize, 479 | false, 480 | false, 481 | ); 482 | } 483 | if let Some(usage_min) = spec.usage_min { 484 | self.emit_item( 485 | elems, 486 | ItemType::Local.into(), 487 | LocalItemKind::UsageMin.into(), 488 | usage_min as isize, 489 | false, 490 | false, 491 | ); 492 | } 493 | if let Some(usage_max) = spec.usage_max { 494 | self.emit_item( 495 | elems, 496 | ItemType::Local.into(), 497 | LocalItemKind::UsageMax.into(), 498 | usage_max as isize, 499 | false, 500 | false, 501 | ); 502 | } 503 | if let Some(report_id) = spec.report_id { 504 | self.emit_item( 505 | elems, 506 | ItemType::Global.into(), 507 | GlobalItemKind::ReportID.into(), 508 | report_id as isize, 509 | false, 510 | false, 511 | ); 512 | } 513 | if let Some(collection) = spec.collection { 514 | self.emit_item( 515 | elems, 516 | ItemType::Main.into(), 517 | MainItemKind::Collection.into(), 518 | collection as isize, 519 | false, 520 | false, 521 | ); 522 | } 523 | if let Some(logical_minimum) = spec.logical_min { 524 | // Set to 0 to indicate that we've already set the default 525 | // See handle_globals 526 | self.logical_minimum = Some(0); 527 | self.emit_item( 528 | elems, 529 | ItemType::Global.into(), 530 | GlobalItemKind::LogicalMin.into(), 531 | logical_minimum as isize, 532 | false, 533 | false, 534 | ); 535 | } 536 | if let Some(unit_exponent) = spec.unit_exponent { 537 | self.emit_item( 538 | elems, 539 | ItemType::Global.into(), 540 | GlobalItemKind::UnitExponent.into(), 541 | unit_exponent as isize, 542 | false, 543 | false, 544 | ); 545 | } 546 | 547 | for name in spec.clone() { 548 | let f = spec.get(name.clone()).unwrap(); 549 | match f { 550 | Spec::MainItem(i) => { 551 | let d = field_decl(fields, name); 552 | match analyze_field(d.clone(), d.ty, i) { 553 | Ok(item) => { 554 | self.processed_fields.push(item.clone()); 555 | self.emit_field(elems, i, item.descriptor_item) 556 | } 557 | Err(e) => return Err(e), 558 | } 559 | } 560 | Spec::Collection(g) => { 561 | self.emit_group(elems, g, fields)?; 562 | } 563 | } 564 | } 565 | 566 | if spec.collection.is_some() { 567 | // Close collection. 568 | elems.push(byte_literal(0xc0)); 569 | } 570 | Ok(()) 571 | } 572 | } 573 | 574 | fn byte_literal(lit: u8) -> Pat { 575 | // print!("{:x} ", lit); 576 | // println!(); 577 | Pat::Lit(syn::PatLit { 578 | attrs: vec![], 579 | expr: Box::new(Expr::Lit(syn::ExprLit { 580 | attrs: vec![], 581 | lit: syn::Lit::Byte(syn::LitByte::new(lit, Span::call_site())), 582 | })), 583 | }) 584 | } 585 | -------------------------------------------------------------------------------- /src/hid_class.rs: -------------------------------------------------------------------------------- 1 | //! Implements HID functionality for a usb-device device. 2 | use usb_device::class_prelude::*; 3 | use usb_device::Result; 4 | 5 | use crate::descriptor::AsInputReport; 6 | use crate::descriptor::BufferOverflow; 7 | 8 | const USB_CLASS_HID: u8 = 0x03; 9 | 10 | // HID 11 | const HID_DESC_DESCTYPE_HID: u8 = 0x21; 12 | const HID_DESC_DESCTYPE_HID_REPORT: u8 = 0x22; 13 | const HID_DESC_SPEC_1_10: [u8; 2] = [0x10, 0x01]; 14 | 15 | /// Requests the set idle rate from the device 16 | /// See (7.2.3): 17 | const HID_REQ_GET_IDLE: u8 = 0x02; 18 | 19 | /// Requests device to not send a particular report until a new event occurs 20 | /// or the specified amount of time passes. 21 | /// See (7.2.4): 22 | const HID_REQ_SET_IDLE: u8 = 0x0a; 23 | 24 | /// Requests the active protocol on the device (boot or report) 25 | /// See (7.2.5): 26 | const HID_REQ_GET_PROTOCOL: u8 = 0x03; 27 | 28 | /// Switches the device between boot and report protocols. Devices must default 29 | /// to report protocol, it is the reponsibility of the host to set the device 30 | /// to boot protocol (NOTE: Sadly many OSs, BIOSs and bootloaders do not adhere 31 | /// to the USB spec here). 32 | /// See (7.2.6): 33 | const HID_REQ_SET_PROTOCOL: u8 = 0x0b; 34 | 35 | /// Allows a host to receive a report via the Control pipe 36 | /// See (7.2.1): 37 | const HID_REQ_GET_REPORT: u8 = 0x01; 38 | 39 | /// Allows the host to send a report to the device via the Control pipe 40 | /// See (7.2.2): 41 | const HID_REQ_SET_REPORT: u8 = 0x09; 42 | 43 | /// See CONTROL_BUF_LEN from usb-device.git src/control_pipe.rs 44 | /// Will need to revisit how this is set once usb-device has true HiSpeed USB support. 45 | const CONTROL_BUF_LEN: usize = 128; 46 | 47 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 48 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 49 | pub enum ReportType { 50 | Input = 1, 51 | Output = 2, 52 | Feature = 3, 53 | Reserved, 54 | } 55 | 56 | impl From for ReportType { 57 | fn from(rt: u8) -> ReportType { 58 | match rt { 59 | 1 => ReportType::Input, 60 | 2 => ReportType::Output, 61 | 3 => ReportType::Feature, 62 | _ => ReportType::Reserved, 63 | } 64 | } 65 | } 66 | 67 | #[derive(Copy, Clone, Debug)] 68 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 69 | pub struct ReportInfo { 70 | pub report_type: ReportType, 71 | pub report_id: u8, 72 | pub len: usize, 73 | } 74 | 75 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 76 | struct Report { 77 | info: ReportInfo, 78 | buf: [u8; CONTROL_BUF_LEN], 79 | } 80 | 81 | /// List of official USB HID country codes 82 | /// See (6.2.1): 83 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 84 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 85 | #[repr(u8)] 86 | pub enum HidCountryCode { 87 | NotSupported = 0, 88 | Arabic = 1, 89 | Belgian = 2, 90 | CanadianBilingual = 3, 91 | CanadianFrench = 4, 92 | CzechRepublic = 5, 93 | Danish = 6, 94 | Finnish = 7, 95 | French = 8, 96 | German = 9, 97 | Greek = 10, 98 | Hebrew = 11, 99 | Hungary = 12, 100 | InternationalISO = 13, 101 | Italian = 14, 102 | JapanKatakana = 15, 103 | Korean = 16, 104 | LatinAmerica = 17, 105 | NetherlandsDutch = 18, 106 | Norwegian = 19, 107 | PersianFarsi = 20, 108 | Poland = 21, 109 | Portuguese = 22, 110 | Russia = 23, 111 | Slovakia = 24, 112 | Spanish = 25, 113 | Swedish = 26, 114 | SwissFrench = 27, 115 | SwissGerman = 28, 116 | Switzerland = 29, 117 | Taiwan = 30, 118 | TurkishQ = 31, 119 | UK = 32, 120 | US = 33, 121 | Yugoslavia = 34, 122 | TurkishF = 35, 123 | } 124 | 125 | /// Used to enable Boot mode descriptors for Mouse and Keyboard devices. 126 | /// See (4.2): 127 | /// Boot mode descriptors are fixed and must follow a strict format. 128 | /// See (Appendix F): 129 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 130 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 131 | #[repr(u8)] 132 | pub enum HidSubClass { 133 | NoSubClass = 0, 134 | Boot = 1, 135 | } 136 | 137 | /// Defines fixed packet format 138 | /// Only used if HidSubClass::Boot(1) is set 139 | /// See (4.3): 140 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 141 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 142 | #[repr(u8)] 143 | pub enum HidProtocol { 144 | Generic = 0, 145 | Keyboard = 1, 146 | Mouse = 2, 147 | } 148 | 149 | /// Get/Set Protocol mapping 150 | /// See (7.2.5 and 7.2.6): 151 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 152 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 153 | #[repr(u8)] 154 | pub enum HidProtocolMode { 155 | Boot = 0, 156 | Report = 1, 157 | } 158 | 159 | impl From for HidProtocolMode { 160 | fn from(mode: u8) -> HidProtocolMode { 161 | if mode == HidProtocolMode::Boot as u8 { 162 | HidProtocolMode::Boot 163 | } else { 164 | HidProtocolMode::Report 165 | } 166 | } 167 | } 168 | 169 | /// It is often necessary to override OS behavior in order to get around OS (and application) level 170 | /// bugs. Forcing either Boot mode (6KRO) and Report mode (NKRO) are often necessary for NKRO 171 | /// compatible keyboards. Mice that support boot mode are not common and generally only useful for 172 | /// legacy OSs. 173 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 174 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 175 | pub enum ProtocolModeConfig { 176 | /// Allows the host to define boot or report mode. Defaults to report mode. 177 | DefaultBehavior, 178 | /// Forces protocol mode to boot mode 179 | ForceBoot, 180 | /// Forces protocol mode to report mode 181 | ForceReport, 182 | } 183 | 184 | /// Used to define specialized HID device settings 185 | /// Most commonly used to setup Boot Mode (6KRO) or Report Mode (NKRO) keyboards. 186 | /// Some OSs will also respect the HID locale setting of the keyboard to help choose the OS 187 | /// keyboard layout. 188 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 189 | pub struct HidClassSettings { 190 | pub subclass: HidSubClass, 191 | pub protocol: HidProtocol, 192 | pub config: ProtocolModeConfig, 193 | pub locale: HidCountryCode, 194 | } 195 | 196 | impl Default for HidClassSettings { 197 | fn default() -> Self { 198 | Self { 199 | subclass: HidSubClass::NoSubClass, 200 | protocol: HidProtocol::Generic, 201 | config: ProtocolModeConfig::DefaultBehavior, 202 | locale: HidCountryCode::NotSupported, 203 | } 204 | } 205 | } 206 | 207 | /// HIDClass provides an interface to declare, read & write HID reports. 208 | /// 209 | /// Users are expected to provide the report descriptor, as well as pack 210 | /// and unpack reports which are read or staged for transmission. 211 | pub struct HIDClass<'a, B: UsbBus> { 212 | if_num: InterfaceNumber, 213 | /// Low-latency OUT buffer 214 | out_ep: Option>, 215 | /// Low-latency IN buffer 216 | in_ep: Option>, 217 | report_descriptor: &'static [u8], 218 | /// Control endpoint alternative OUT buffer (always used for setting feature reports) 219 | /// See: 7.2.1 and 7.2.2 220 | set_report_buf: Option, 221 | /// Used only by Keyboard and Mouse to define BIOS (Boot) mode vs Normal (Report) mode. 222 | /// This is used to switch between 6KRO (boot) and NKRO (report) endpoints. 223 | /// Boot mode configured endpoints may not parse the hid descriptor and expect an exact 224 | /// hid packet format. By default a device should start in normal (report) mode and the host 225 | /// must request the boot mode protocol if it requires it. 226 | /// 227 | /// If a device does not request boot mode, this is a host bug. For convenience this API allows 228 | /// manually setting the protocol. 229 | /// See Section 7.2.6 230 | protocol: Option, 231 | settings: HidClassSettings, 232 | } 233 | 234 | fn determine_protocol_setting(settings: &HidClassSettings) -> Option { 235 | if settings.protocol == HidProtocol::Keyboard || settings.protocol == HidProtocol::Mouse { 236 | match settings.config { 237 | ProtocolModeConfig::DefaultBehavior | ProtocolModeConfig::ForceReport => { 238 | Some(HidProtocolMode::Report) 239 | } 240 | ProtocolModeConfig::ForceBoot => Some(HidProtocolMode::Boot), 241 | } 242 | } else { 243 | None 244 | } 245 | } 246 | 247 | impl HIDClass<'_, B> { 248 | /// Creates a new HIDClass with the provided UsbBus & HID report descriptor. 249 | /// 250 | /// poll_ms configures how frequently the host should poll for reading/writing 251 | /// HID reports. A lower value means better throughput & latency, at the expense 252 | /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for 253 | /// high performance uses, and a value of 255 is good for best-effort usecases. 254 | /// 255 | /// This allocates two endpoints (IN and OUT). 256 | /// See new_ep_in (IN endpoint only) and new_ep_out (OUT endpoint only) to only create a single 257 | /// endpoint. 258 | /// 259 | /// See new_with_settings() if you need to define protocol or locale settings for a IN/OUT 260 | /// HID interface. 261 | pub fn new<'a>( 262 | alloc: &'a UsbBusAllocator, 263 | report_descriptor: &'static [u8], 264 | poll_ms: u8, 265 | ) -> HIDClass<'a, B> { 266 | let settings = HidClassSettings::default(); 267 | HIDClass { 268 | if_num: alloc.interface(), 269 | out_ep: Some(alloc.interrupt(64, poll_ms)), 270 | in_ep: Some(alloc.interrupt(64, poll_ms)), 271 | report_descriptor, 272 | set_report_buf: None, 273 | protocol: determine_protocol_setting(&settings), 274 | settings, 275 | } 276 | } 277 | 278 | /// Same as new() but includes a settings field. 279 | /// The settings field is used to define both locale and protocol settings of the HID 280 | /// device (needed for HID keyboard and Mice). 281 | pub fn new_with_settings<'a>( 282 | alloc: &'a UsbBusAllocator, 283 | report_descriptor: &'static [u8], 284 | poll_ms: u8, 285 | settings: HidClassSettings, 286 | ) -> HIDClass<'a, B> { 287 | HIDClass { 288 | if_num: alloc.interface(), 289 | out_ep: Some(alloc.interrupt(64, poll_ms)), 290 | in_ep: Some(alloc.interrupt(64, poll_ms)), 291 | report_descriptor, 292 | set_report_buf: None, 293 | protocol: determine_protocol_setting(&settings), 294 | settings, 295 | } 296 | } 297 | 298 | /// Creates a new HIDClass with the provided UsbBus & HID report descriptor. 299 | /// See new() for more details. 300 | /// Please use new_ep_in_with_settings() if you are creating a keyboard or mouse. 301 | pub fn new_ep_in<'a>( 302 | alloc: &'a UsbBusAllocator, 303 | report_descriptor: &'static [u8], 304 | poll_ms: u8, 305 | ) -> HIDClass<'a, B> { 306 | let settings = HidClassSettings::default(); 307 | HIDClass { 308 | if_num: alloc.interface(), 309 | out_ep: None, 310 | in_ep: Some(alloc.interrupt(64, poll_ms)), 311 | report_descriptor, 312 | set_report_buf: None, 313 | protocol: determine_protocol_setting(&settings), 314 | settings, 315 | } 316 | } 317 | 318 | /// Same as new_ep_in() but includes a settings field. 319 | /// The settings field is used to define both locale and protocol settings of the HID 320 | /// device (needed for HID keyboard and Mice). 321 | pub fn new_ep_in_with_settings<'a>( 322 | alloc: &'a UsbBusAllocator, 323 | report_descriptor: &'static [u8], 324 | poll_ms: u8, 325 | settings: HidClassSettings, 326 | ) -> HIDClass<'a, B> { 327 | HIDClass { 328 | if_num: alloc.interface(), 329 | out_ep: None, 330 | in_ep: Some(alloc.interrupt(64, poll_ms)), 331 | report_descriptor, 332 | set_report_buf: None, 333 | protocol: determine_protocol_setting(&settings), 334 | settings, 335 | } 336 | } 337 | 338 | /// Creates a new HIDClass with the provided UsbBus & HID report descriptor. 339 | /// See new() for more details. 340 | /// Please use new_ep_out_with_settings if you need the settings field. 341 | pub fn new_ep_out<'a>( 342 | alloc: &'a UsbBusAllocator, 343 | report_descriptor: &'static [u8], 344 | poll_ms: u8, 345 | ) -> HIDClass<'a, B> { 346 | let settings = HidClassSettings::default(); 347 | HIDClass { 348 | if_num: alloc.interface(), 349 | out_ep: Some(alloc.interrupt(64, poll_ms)), 350 | in_ep: None, 351 | report_descriptor, 352 | set_report_buf: None, 353 | protocol: determine_protocol_setting(&settings), 354 | settings, 355 | } 356 | } 357 | 358 | /// Same as new_ep_out() but includes a settings field. 359 | /// This should be uncommon (non-standard), but is included for completeness as there 360 | /// may be cases where setting the locale is useful. 361 | pub fn new_ep_out_with_settings<'a>( 362 | alloc: &'a UsbBusAllocator, 363 | report_descriptor: &'static [u8], 364 | poll_ms: u8, 365 | settings: HidClassSettings, 366 | ) -> HIDClass<'a, B> { 367 | HIDClass { 368 | if_num: alloc.interface(), 369 | out_ep: Some(alloc.interrupt(64, poll_ms)), 370 | in_ep: None, 371 | report_descriptor, 372 | set_report_buf: None, 373 | protocol: determine_protocol_setting(&settings), 374 | settings, 375 | } 376 | } 377 | 378 | /// Tries to write an input report by serializing the given report structure. 379 | /// A BufferOverflow error is returned if the serialized report is greater than 380 | /// 64 bytes in size. 381 | pub fn push_input(&self, r: &IR) -> Result { 382 | // Do not push data if protocol settings do not match (only for keyboard and mouse) 383 | match self.settings.protocol { 384 | HidProtocol::Keyboard | HidProtocol::Mouse => { 385 | if let Some(protocol) = self.protocol { 386 | if (protocol == HidProtocolMode::Report 387 | && self.settings.subclass != HidSubClass::NoSubClass) 388 | || (protocol == HidProtocolMode::Boot 389 | && self.settings.subclass != HidSubClass::Boot) 390 | { 391 | return Err(UsbError::InvalidState); 392 | } 393 | } 394 | } 395 | _ => {} 396 | } 397 | 398 | if let Some(ep) = &self.in_ep { 399 | let mut buff: [u8; 64] = [0; 64]; 400 | let size = match r.serialize(&mut buff) { 401 | Ok(l) => l, 402 | Err(BufferOverflow) => return Err(UsbError::BufferOverflow), 403 | }; 404 | ep.write(&buff[0..size]) 405 | } else { 406 | Err(UsbError::InvalidEndpoint) 407 | } 408 | } 409 | 410 | /// Tries to write an input (device-to-host) report from the given raw bytes. 411 | /// Data is expected to be a valid HID report for INPUT items. If report ID's 412 | /// were used in the descriptor, the report ID corresponding to this report 413 | /// must be be present before the contents of the report. 414 | pub fn push_raw_input(&self, data: &[u8]) -> Result { 415 | // Do not push data if protocol settings do not match (only for keyboard and mouse) 416 | match self.settings.protocol { 417 | HidProtocol::Keyboard | HidProtocol::Mouse => { 418 | if let Some(protocol) = self.protocol { 419 | if (protocol == HidProtocolMode::Report 420 | && self.settings.subclass != HidSubClass::NoSubClass) 421 | || (protocol == HidProtocolMode::Boot 422 | && self.settings.subclass != HidSubClass::Boot) 423 | { 424 | return Err(UsbError::InvalidState); 425 | } 426 | } 427 | } 428 | _ => {} 429 | } 430 | 431 | if let Some(ep) = &self.in_ep { 432 | ep.write(data) 433 | } else { 434 | Err(UsbError::InvalidEndpoint) 435 | } 436 | } 437 | 438 | /// Tries to read an output (host-to-device) report as raw bytes. Data 439 | /// is expected to be sized appropriately to contain any valid HID report 440 | /// for OUTPUT items, including the report ID prefix if report IDs are used. 441 | pub fn pull_raw_output(&self, data: &mut [u8]) -> Result { 442 | if let Some(ep) = &self.out_ep { 443 | ep.read(data) 444 | } else { 445 | Err(UsbError::InvalidEndpoint) 446 | } 447 | } 448 | 449 | /// Tries to read an incoming SET_REPORT report as raw bytes. 450 | /// Unlike OUT endpoints, report IDs are not prefixed in the buffer. Use the returned tuple 451 | /// instead to determine the buffer's usage. 452 | /// 453 | /// The most common usage of pull_raw_report is for keyboard lock LED status if an OUT endpoint 454 | /// is not defined. It is not necessary to call this function if you're not going to be using 455 | /// SET_REPORT functionality. 456 | pub fn pull_raw_report(&mut self, data: &mut [u8]) -> Result { 457 | let info = match &self.set_report_buf { 458 | Some(set_report_buf) => { 459 | let info = set_report_buf.info; 460 | 461 | // Make sure the given buffer is large enough for the stored report 462 | if data.len() < info.len { 463 | return Err(UsbError::BufferOverflow); 464 | } 465 | 466 | // Copy buffer 467 | data[..info.len].copy_from_slice(&set_report_buf.buf[..info.len]); 468 | info 469 | } 470 | None => { 471 | return Err(UsbError::WouldBlock); 472 | } 473 | }; 474 | 475 | // Clear the report 476 | self.set_report_buf = None; 477 | Ok(info) 478 | } 479 | 480 | /// Retrieves the currently set device protocol 481 | /// This is equivalent to the USB HID GET_PROTOCOL request 482 | /// See (7.2.5): 483 | pub fn get_protocol_mode(&self) -> Result { 484 | // Protocol mode only has meaning if Keyboard or Mouse Protocol is set 485 | match self.settings.protocol { 486 | HidProtocol::Keyboard | HidProtocol::Mouse => {} 487 | _ => { 488 | return Err(UsbError::Unsupported); 489 | } 490 | } 491 | 492 | if let Some(protocol) = self.protocol { 493 | Ok(protocol) 494 | } else { 495 | Err(UsbError::InvalidState) 496 | } 497 | } 498 | 499 | /// Forcibly sets the device protocol 500 | /// This is equivalent to the USB HID SET_PROTOCOL request. 501 | /// NOTE: If the OS does not support the new mode, the device may no longer work correctly. 502 | /// See (7.2.6): 503 | /// 504 | /// If either, ForceBoot or ForceReport are set in config, the mode argument is ignored. 505 | /// In addition, if ForceBoot or ForceReport are set, then any SET_PROTOCOL requests are also 506 | /// ignored. 507 | pub fn set_protocol_mode( 508 | &mut self, 509 | mode: HidProtocolMode, 510 | config: ProtocolModeConfig, 511 | ) -> Result<()> { 512 | // Protocol mode only has meaning if Keyboard or Mouse Protocol is set 513 | match self.settings.protocol { 514 | HidProtocol::Keyboard | HidProtocol::Mouse => {} 515 | _ => { 516 | return Err(UsbError::Unsupported); 517 | } 518 | } 519 | 520 | // Update the protocol setting behavior and update the protocol mode 521 | match config { 522 | ProtocolModeConfig::DefaultBehavior => self.protocol = Some(mode), 523 | ProtocolModeConfig::ForceBoot => { 524 | self.protocol = Some(HidProtocolMode::Boot); 525 | } 526 | ProtocolModeConfig::ForceReport => { 527 | self.protocol = Some(HidProtocolMode::Report); 528 | } 529 | } 530 | self.settings.config = config; 531 | Ok(()) 532 | } 533 | } 534 | 535 | impl UsbClass for HIDClass<'_, B> { 536 | fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<()> { 537 | writer.interface( 538 | self.if_num, 539 | USB_CLASS_HID, 540 | self.settings.subclass as u8, 541 | self.settings.protocol as u8, 542 | )?; 543 | 544 | // HID descriptor 545 | writer.write( 546 | HID_DESC_DESCTYPE_HID, 547 | &[ 548 | // HID Class spec version 549 | HID_DESC_SPEC_1_10[0], 550 | HID_DESC_SPEC_1_10[1], 551 | self.settings.locale as u8, 552 | // Number of following descriptors 553 | 1, 554 | // We have a HID report descriptor the host should read 555 | HID_DESC_DESCTYPE_HID_REPORT, 556 | // HID report descriptor size, 557 | (self.report_descriptor.len() & 0xFF) as u8, 558 | ((self.report_descriptor.len() >> 8) & 0xFF) as u8, 559 | ], 560 | )?; 561 | 562 | if let Some(ep) = &self.out_ep { 563 | writer.endpoint(ep)?; 564 | } 565 | if let Some(ep) = &self.in_ep { 566 | writer.endpoint(ep)?; 567 | } 568 | Ok(()) 569 | } 570 | 571 | // Handle control requests to the host. 572 | fn control_in(&mut self, xfer: ControlIn) { 573 | let req = xfer.request(); 574 | 575 | // Bail out if its not relevant to our interface. 576 | if req.index != u8::from(self.if_num) as u16 { 577 | return; 578 | } 579 | 580 | match (req.request_type, req.request) { 581 | (control::RequestType::Standard, control::Request::GET_DESCRIPTOR) => { 582 | match (req.value >> 8) as u8 { 583 | HID_DESC_DESCTYPE_HID_REPORT => { 584 | xfer.accept_with_static(self.report_descriptor).ok(); 585 | } 586 | HID_DESC_DESCTYPE_HID => { 587 | let buf = &[ 588 | // Length of buf inclusive of size prefix 589 | 9, 590 | // Descriptor type 591 | HID_DESC_DESCTYPE_HID, 592 | // HID Class spec version 593 | HID_DESC_SPEC_1_10[0], 594 | HID_DESC_SPEC_1_10[1], 595 | self.settings.locale as u8, 596 | // Number of following descriptors 597 | 1, 598 | // We have a HID report descriptor the host should read 599 | HID_DESC_DESCTYPE_HID_REPORT, 600 | // HID report descriptor size, 601 | (self.report_descriptor.len() & 0xFF) as u8, 602 | ((self.report_descriptor.len() >> 8) & 0xFF) as u8, 603 | ]; 604 | xfer.accept_with(buf).ok(); 605 | } 606 | _ => {} 607 | } 608 | } 609 | (control::RequestType::Class, HID_REQ_GET_REPORT) => { 610 | // To support GET_REPORT correctly each request must be serviced immediately. 611 | // This complicates the current API and may require a standing copy of each 612 | // of the possible IN reports (as well as any FEATURE reports as well). 613 | // For most projects, GET_REPORT won't be necessary so until a project comes along 614 | // with a need for it, I think it's safe to leave unsupported. 615 | // See: https://www.usb.org/sites/default/files/documents/hid1_11.pdf 7.2.1 616 | xfer.reject().ok(); // Not supported for now 617 | } 618 | (control::RequestType::Class, HID_REQ_GET_IDLE) => { 619 | // XXX (HaaTa): As a note for future readers 620 | // GET/SET_IDLE tends to be rather buggy on the host side 621 | // macOS is known to set SET_IDLE for keyboards but most other OSs do not. 622 | // I haven't had much success in the past trying to enable GET/SET_IDLE for 623 | // macOS (it seems to expose other bugs in the macOS hid stack). 624 | // The interesting part is that SET_IDLE is not called for official Apple 625 | // keyboards. So beyond getting 100% compliance from the USB compliance tools 626 | // IDLE is useless (at least with respect to keyboards). Modern USB host 627 | // controllers should never have a problem keeping up with slow HID devices. 628 | // 629 | // To implement this correctly it would require integration with higher-level 630 | // functions to handle report expiry. 631 | // See https://www.usb.org/sites/default/files/documents/hid1_11.pdf 7.2.4 632 | // 633 | // Each Report ID can be configured independently. 634 | xfer.reject().ok(); // Not supported for now 635 | } 636 | (control::RequestType::Class, HID_REQ_GET_PROTOCOL) => { 637 | // Only accept in supported configurations 638 | if let Some(protocol) = self.protocol { 639 | xfer.accept_with(&[protocol as u8]).ok(); 640 | } else { 641 | xfer.reject().ok(); 642 | } 643 | } 644 | _ => {} 645 | } 646 | } 647 | 648 | // Handle a control request from the host. 649 | fn control_out(&mut self, xfer: ControlOut) { 650 | let req = xfer.request(); 651 | 652 | // Bail out if its not relevant to our interface. 653 | if !(req.recipient == control::Recipient::Interface 654 | && req.index == u8::from(self.if_num) as u16) 655 | { 656 | return; 657 | } 658 | 659 | match req.request { 660 | HID_REQ_SET_IDLE => { 661 | xfer.accept().ok(); 662 | } 663 | HID_REQ_SET_PROTOCOL => { 664 | // Only accept in supported configurations 665 | if let Some(_protocol) = self.protocol { 666 | // Only set if configured to 667 | if self.settings.config == ProtocolModeConfig::DefaultBehavior { 668 | self.protocol = Some(((req.value & 0xFF) as u8).into()); 669 | } 670 | xfer.accept().ok(); 671 | } else { 672 | xfer.reject().ok(); 673 | } 674 | } 675 | HID_REQ_SET_REPORT => { 676 | let report_type = ((req.value >> 8) as u8).into(); 677 | let report_id = (req.value & 0xFF) as u8; 678 | let len = req.length as usize; 679 | 680 | // Validate that the incoming data isn't too large for the buffer 681 | if len > CONTROL_BUF_LEN { 682 | self.set_report_buf = None; 683 | xfer.reject().ok(); 684 | } else { 685 | let mut buf: [u8; CONTROL_BUF_LEN] = [0; CONTROL_BUF_LEN]; 686 | buf[..len].copy_from_slice(&xfer.data()[..len]); 687 | 688 | // Overwrite previous buffer even if unused 689 | self.set_report_buf = Some(Report { 690 | info: ReportInfo { 691 | report_type, 692 | report_id, 693 | len, 694 | }, 695 | buf, 696 | }); 697 | xfer.accept().ok(); 698 | } 699 | } 700 | _ => { 701 | xfer.reject().ok(); 702 | } 703 | } 704 | } 705 | } 706 | -------------------------------------------------------------------------------- /src/descriptor.rs: -------------------------------------------------------------------------------- 1 | //! Implements generation of HID report descriptors as well as common reports 2 | extern crate usbd_hid_macros; 3 | 4 | pub use usbd_hid_macros::gen_hid_descriptor; 5 | 6 | /// Report types where serialized HID report descriptors are available. 7 | pub trait SerializedDescriptor { 8 | fn desc() -> &'static [u8]; 9 | } 10 | 11 | /// Report types which serialize into input reports, ready for transmission. 12 | pub trait AsInputReport { 13 | fn serialize(&self, buffer: &mut [u8]) -> Result; 14 | } 15 | 16 | #[derive(Debug)] 17 | pub struct BufferOverflow; 18 | 19 | /// Prelude for modules which use the `gen_hid_descriptor` macro. 20 | pub mod generator_prelude { 21 | pub use crate::descriptor::{AsInputReport, SerializedDescriptor}; 22 | pub use usbd_hid_macros::gen_hid_descriptor; 23 | } 24 | 25 | /// MouseReport describes a report and its companion descriptor than can be used 26 | /// to send mouse movements and button presses to a host. 27 | #[gen_hid_descriptor( 28 | (collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = MOUSE) = { 29 | (collection = PHYSICAL, usage = POINTER) = { 30 | (usage_page = BUTTON, usage_min = BUTTON_1, usage_max = BUTTON_8) = { 31 | #[packed_bits 8] #[item_settings data,variable,absolute] buttons=input; 32 | }; 33 | (usage_page = GENERIC_DESKTOP,) = { 34 | (usage = X,) = { 35 | #[item_settings data,variable,relative] x=input; 36 | }; 37 | (usage = Y,) = { 38 | #[item_settings data,variable,relative] y=input; 39 | }; 40 | (usage = WHEEL,) = { 41 | #[item_settings data,variable,relative] wheel=input; 42 | }; 43 | }; 44 | (usage_page = CONSUMER,) = { 45 | (usage = AC_PAN,) = { 46 | #[item_settings data,variable,relative] pan=input; 47 | }; 48 | }; 49 | }; 50 | } 51 | )] 52 | #[allow(dead_code)] 53 | pub struct MouseReport { 54 | pub buttons: u8, 55 | pub x: i8, 56 | pub y: i8, 57 | pub wheel: i8, // Scroll down (negative) or up (positive) this many units 58 | pub pan: i8, // Scroll left (negative) or right (positive) this many units 59 | } 60 | 61 | /// KeyboardReport describes a report and its companion descriptor that can be 62 | /// used to send keyboard button presses to a host and receive the status of the 63 | /// keyboard LEDs. 64 | #[gen_hid_descriptor( 65 | (collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = KEYBOARD) = { 66 | (usage_page = KEYBOARD, usage_min = 0xE0, usage_max = 0xE7) = { 67 | #[packed_bits 8] #[item_settings data,variable,absolute] modifier=input; 68 | }; 69 | (usage_min = 0x00, usage_max = 0xFF) = { 70 | #[item_settings constant,variable,absolute] reserved=input; 71 | }; 72 | (usage_page = LEDS, usage_min = 0x01, usage_max = 0x05) = { 73 | #[packed_bits 5] #[item_settings data,variable,absolute] leds=output; 74 | }; 75 | (usage_page = KEYBOARD, usage_min = 0x00, usage_max = 0xDD) = { 76 | #[item_settings data,array,absolute] keycodes=input; 77 | }; 78 | } 79 | )] 80 | #[allow(dead_code)] 81 | pub struct KeyboardReport { 82 | pub modifier: u8, 83 | pub reserved: u8, 84 | pub leds: u8, 85 | pub keycodes: [u8; 6], 86 | } 87 | 88 | impl KeyboardReport { 89 | pub const fn default() -> Self { 90 | Self { 91 | modifier: 0, 92 | reserved: 0, 93 | leds: 0, 94 | keycodes: [0u8; 6], 95 | } 96 | } 97 | } 98 | 99 | /// KeyboardUsage describes the key codes to be used in implementing a USB keyboard. 100 | /// 101 | /// The usage type of all key codes is Selectors, except for the modifier keys 102 | /// Keyboard Left Control to Keyboard Right GUI which are Dynamic Flags. 103 | /// 104 | /// Reference: (Section 10, page 88) 105 | #[repr(u8)] 106 | #[allow(unused)] 107 | #[non_exhaustive] 108 | #[derive(Copy, Debug, Clone, Eq, PartialEq)] 109 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 110 | pub enum KeyboardUsage { 111 | // 0x00: Reserved 112 | /// Keyboard ErrorRollOver (Footnote 1) 113 | KeyboardErrorRollOver = 0x01, 114 | /// Keyboard POSTFail (Footnote 1) 115 | KeyboardPOSTFail = 0x02, 116 | /// Keyboard ErrorUndefined (Footnote 1) 117 | KeyboardErrorUndefined = 0x03, 118 | /// Keyboard a and A (Footnote 2) 119 | KeyboardAa = 0x04, 120 | /// Keyboard b and B 121 | KeyboardBb = 0x05, 122 | /// Keyboard c and C (Footnote 2) 123 | KeyboardCc = 0x06, 124 | /// Keyboard d and D 125 | KeyboardDd = 0x07, 126 | /// Keyboard e and E 127 | KeyboardEe = 0x08, 128 | /// Keyboard f and F 129 | KeyboardFf = 0x09, 130 | /// Keyboard g and G 131 | KeyboardGg = 0x0A, 132 | /// Keyboard h and H 133 | KeyboardHh = 0x0B, 134 | /// Keyboard i and I 135 | KeyboardIi = 0x0C, 136 | /// Keyboard j and J 137 | KeyboardJj = 0x0D, 138 | /// Keyboard k and K 139 | KeyboardKk = 0x0E, 140 | /// Keyboard l and L 141 | KeyboardLl = 0x0F, 142 | /// Keyboard m and M (Footnote 2) 143 | KeyboardMm = 0x10, 144 | /// Keyboard n and N 145 | KeyboardNn = 0x11, 146 | /// Keyboard o and O (Footnote 2) 147 | KeyboardOo = 0x12, 148 | /// Keyboard p and P (Footnote 2) 149 | KeyboardPp = 0x13, 150 | /// Keyboard q and Q (Footnote 2) 151 | KeyboardQq = 0x14, 152 | /// Keyboard r and R 153 | KeyboardRr = 0x15, 154 | /// Keyboard s and S 155 | KeyboardSs = 0x16, 156 | /// Keyboard t and T 157 | KeyboardTt = 0x17, 158 | /// Keyboard u and U 159 | KeyboardUu = 0x18, 160 | /// Keyboard v and V 161 | KeyboardVv = 0x19, 162 | /// Keyboard w and W (Footnote 2) 163 | KeyboardWw = 0x1A, 164 | /// Keyboard x and X (Footnote 2) 165 | KeyboardXx = 0x1B, 166 | /// Keyboard y and Y (Footnote 2) 167 | KeyboardYy = 0x1C, 168 | /// Keyboard z and Z (Footnote 2) 169 | KeyboardZz = 0x1D, 170 | /// Keyboard 1 and ! (Footnote 2) 171 | Keyboard1Exclamation = 0x1E, 172 | /// Keyboard 2 and @ (Footnote 2) 173 | Keyboard2At = 0x1F, 174 | /// Keyboard 3 and # (Footnote 2) 175 | Keyboard3Hash = 0x20, 176 | /// Keyboard 4 and $ (Footnote 2) 177 | Keyboard4Dollar = 0x21, 178 | /// Keyboard 5 and % (Footnote 2) 179 | Keyboard5Percent = 0x22, 180 | /// Keyboard 6 and ^ (Footnote 2) 181 | Keyboard6Caret = 0x23, 182 | /// Keyboard 7 and & (Footnote 2) 183 | Keyboard7Ampersand = 0x24, 184 | /// Keyboard 8 and * (Footnote 2) 185 | Keyboard8Asterisk = 0x25, 186 | /// Keyboard 9 and ( (Footnote 2) 187 | Keyboard9OpenParens = 0x26, 188 | /// Keyboard 0 and ) (Footnote 2) 189 | Keyboard0CloseParens = 0x27, 190 | /// Keyboard Return (ENTER) (Footnote 3) 191 | /// 192 | /// (Footnote 3): Keyboard Enter and Keypad Enter generate different Usage codes. 193 | KeyboardEnter = 0x28, 194 | /// Keyboard ESCAPE 195 | KeyboardEscape = 0x29, 196 | /// Keyboard DELETE (Backspace) (Footnote 4) 197 | KeyboardBackspace = 0x2A, 198 | /// Keyboard Tab 199 | KeyboardTab = 0x2B, 200 | /// Keyboard Spacebar 201 | KeyboardSpacebar = 0x2C, 202 | /// Keyboard - and _ (Footnote 2) 203 | KeyboardDashUnderscore = 0x2D, 204 | /// Keyboard = and + (Footnote 2) 205 | KeyboardEqualPlus = 0x2E, 206 | /// Keyboard [ and { (Footnote 2) 207 | KeyboardOpenBracketBrace = 0x2F, 208 | /// Keyboard ] and } (Footnote 2) 209 | KeyboardCloseBracketBrace = 0x30, 210 | /// Keyboard \ and | 211 | KeyboardBackslashBar = 0x31, 212 | /// Keyboard Non-US # and (Footnote 5) 213 | KeyboardNonUSHash = 0x32, 214 | /// Keyboard ; and : (Footnote 2) 215 | KeyboardSemiColon = 0x33, 216 | /// Keyboard ' and " (Footnote 2) 217 | KeyboardSingleDoubleQuote = 0x34, 218 | /// Keyboard ` and ~ (Footnote 2) 219 | KeyboardBacktickTilde = 0x35, 220 | /// Keyboard , and < (Footnote 2) 221 | KeyboardCommaLess = 0x36, 222 | /// Keyboard . and > (Footnote 2) 223 | KeyboardPeriodGreater = 0x37, 224 | /// Keyboard / and ? (Footnote 2) 225 | KeyboardSlashQuestion = 0x38, 226 | /// Keyboard Caps Lock (Footnote 6) 227 | KeyboardCapsLock = 0x39, 228 | /// Keyboard F1 229 | KeyboardF1 = 0x3A, 230 | /// Keyboard F2 231 | KeyboardF2 = 0x3B, 232 | /// Keyboard F3 233 | KeyboardF3 = 0x3C, 234 | /// Keyboard F4 235 | KeyboardF4 = 0x3D, 236 | /// Keyboard F5 237 | KeyboardF5 = 0x3E, 238 | /// Keyboard F6 239 | KeyboardF6 = 0x3F, 240 | /// Keyboard F7 241 | KeyboardF7 = 0x40, 242 | /// Keyboard F8 243 | KeyboardF8 = 0x41, 244 | /// Keyboard F9 245 | KeyboardF9 = 0x42, 246 | /// Keyboard F10 247 | KeyboardF10 = 0x43, 248 | /// Keyboard F11 249 | KeyboardF11 = 0x44, 250 | /// Keyboard F12 251 | KeyboardF12 = 0x45, 252 | /// Keyboard PrintScreen (Footnote 7) 253 | KeyboardPrintScreen = 0x46, 254 | /// Keyboard ScrollLock (Footnote 6) 255 | KeyboardScrollLock = 0x47, 256 | /// Keyboard Pause (Footnote 7) 257 | KeyboardPause = 0x48, 258 | /// Keyboard Insert (Footnote 7) 259 | KeyboardInsert = 0x49, 260 | /// Keyboard Home (Footnote 7) 261 | KeyboardHome = 0x4A, 262 | /// Keyboard PageUp (Footnote 7) 263 | KeyboardPageUp = 0x4B, 264 | /// Keyboard Delete Forward (Footnote 7) (Footnote 8) 265 | KeyboardDelete = 0x4C, 266 | /// Keyboard End (Footnote 7) 267 | KeyboardEnd = 0x4D, 268 | /// Keyboard PageDown (Footnote 7) 269 | KeyboardPageDown = 0x4E, 270 | /// Keyboard RightArrow (Footnote 7) 271 | KeyboardRightArrow = 0x4F, 272 | /// Keyboard LeftArrow (Footnote 7) 273 | KeyboardLeftArrow = 0x50, 274 | /// Keyboard DownArrow (Footnote 7) 275 | KeyboardDownArrow = 0x51, 276 | /// Keyboard UpArrow (Footnote 7) 277 | KeyboardUpArrow = 0x52, 278 | /// Keypad Num Lock and Clear (Footnote 6) 279 | KeypadNumLock = 0x53, 280 | /// Keypad / (Footnote 7) 281 | KeypadDivide = 0x54, 282 | /// Keypad * 283 | KeypadMultiply = 0x55, 284 | /// Keypad - 285 | KeypadMinus = 0x56, 286 | /// Keypad + 287 | KeypadPlus = 0x57, 288 | /// Keypad ENTER (Footnote 3) 289 | KeypadEnter = 0x58, 290 | /// Keypad 1 and End 291 | Keypad1End = 0x59, 292 | /// Keypad 2 and DownArrow 293 | Keypad2DownArrow = 0x5A, 294 | /// Keypad 3 and PageDown 295 | Keypad3PageDown = 0x5B, 296 | /// Keypad 4 and LeftArrow 297 | Keypad4LeftArrow = 0x5C, 298 | /// Keypad 5 299 | Keypad5 = 0x5D, 300 | /// Keypad 6 and RightArrow 301 | Keypad6RightArrow = 0x5E, 302 | /// Keypad 7 and Home 303 | Keypad7Home = 0x5F, 304 | /// Keypad 8 and UpArrow 305 | Keypad8UpArrow = 0x60, 306 | /// Keypad 9 and PageUp 307 | Keypad9PageUp = 0x61, 308 | /// Keypad 0 and Insert 309 | Keypad0Insert = 0x62, 310 | /// Keypad . and Delete 311 | KeypadPeriodDelete = 0x63, 312 | /// Keyboard Non-US \ and | (Footnote 9) (Footnote 10) 313 | KeyboardNonUSSlash = 0x64, 314 | /// Keyboard Application (Footnote 11) 315 | KeyboardApplication = 0x65, 316 | /// Keyboard Power (Footnote 1) 317 | KeyboardPower = 0x66, 318 | /// Keypad = 319 | KeypadEqual = 0x67, 320 | /// Keyboard F13 321 | KeyboardF13 = 0x68, 322 | /// Keyboard F14 323 | KeyboardF14 = 0x69, 324 | /// Keyboard F15 325 | KeyboardF15 = 0x6A, 326 | /// Keyboard F16 327 | KeyboardF16 = 0x6B, 328 | /// Keyboard F17 329 | KeyboardF17 = 0x6C, 330 | /// Keyboard F18 331 | KeyboardF18 = 0x6D, 332 | /// Keyboard F19 333 | KeyboardF19 = 0x6E, 334 | /// Keyboard F20 335 | KeyboardF20 = 0x6F, 336 | /// Keyboard F21 337 | KeyboardF21 = 0x70, 338 | /// Keyboard F22 339 | KeyboardF22 = 0x71, 340 | /// Keyboard F23 341 | KeyboardF23 = 0x72, 342 | /// Keyboard F24 343 | KeyboardF24 = 0x73, 344 | /// Keyboard Execute 345 | KeyboardExecute = 0x74, 346 | /// Keyboard Help 347 | KeyboardHelp = 0x75, 348 | /// Keyboard Menu 349 | KeyboardMenu = 0x76, 350 | /// Keyboard Select 351 | KeyboardSelect = 0x77, 352 | /// Keyboard Stop 353 | KeyboardStop = 0x78, 354 | /// Keyboard Again 355 | KeyboardAgain = 0x79, 356 | /// Keyboard Undo 357 | KeyboardUndo = 0x7A, 358 | /// Keyboard Cut 359 | KeyboardCut = 0x7B, 360 | /// Keyboard Copy 361 | KeyboardCopy = 0x7C, 362 | /// Keyboard Paste 363 | KeyboardPaste = 0x7D, 364 | /// Keyboard Find 365 | KeyboardFind = 0x7E, 366 | /// Keyboard Mute 367 | KeyboardMute = 0x7F, 368 | /// Keyboard Volume Up 369 | KeyboardVolumeUp = 0x80, 370 | /// Keyboard Volume Down 371 | KeyboardVolumeDown = 0x81, 372 | /// Keyboad Locking Caps Lock (Footnote 12) 373 | KeyboardLockingCapsLock = 0x82, 374 | /// Keyboad Locking Num Lock (Footnote 12) 375 | KeyboardLockingNumLock = 0x83, 376 | /// Keyboad Locking Scroll Lock (Footnote 12) 377 | KeyboardLockingScrollLock = 0x84, 378 | /// Keypad Comma (Footnote 13) 379 | KeypadComma = 0x85, 380 | /// Keypad Equal Sign (Footnote 14) 381 | KeypadEqualSign = 0x86, 382 | /// Keyboard International1 (Footnote 15) (Footnote 16) 383 | KeyboardInternational1 = 0x87, 384 | /// Keyboard International2 (Footnote 17) 385 | KeyboardInternational2 = 0x88, 386 | /// Keyboard International3 (Footnote 18) 387 | KeyboardInternational3 = 0x89, 388 | /// Keyboard International4 (Footnote 19) 389 | KeyboardInternational4 = 0x8A, 390 | /// Keyboard International5 (Footnote 20) 391 | KeyboardInternational5 = 0x8B, 392 | /// Keyboard International6 (Footnote 21) 393 | KeyboardInternational6 = 0x8C, 394 | /// Keyboard International7 (Footnote 22) 395 | KeyboardInternational7 = 0x8D, 396 | /// Keyboard International8 (Footnote 23) 397 | KeyboardInternational8 = 0x8E, 398 | /// Keyboard International9 (Footnote 23) 399 | KeyboardInternational9 = 0x8F, 400 | /// Keyboard LANG1 (Footnote 24) 401 | KeyboardLANG1 = 0x90, 402 | /// Keyboard LANG2 (Footnote 25) 403 | KeyboardLANG2 = 0x91, 404 | /// Keyboard LANG3 (Footnote 26) 405 | KeyboardLANG3 = 0x92, 406 | /// Keyboard LANG4 (Footnote 27) 407 | KeyboardLANG4 = 0x93, 408 | /// Keyboard LANG5 (Footnote 28) 409 | KeyboardLANG5 = 0x94, 410 | /// Keyboard LANG6 (Footnote 29) 411 | KeyboardLANG6 = 0x95, 412 | /// Keyboard LANG7 (Footnote 29) 413 | KeyboardLANG7 = 0x96, 414 | /// Keyboard LANG8 (Footnote 29) 415 | KeyboardLANG8 = 0x97, 416 | /// Keyboard LANG9 (Footnote 29) 417 | KeyboardLANG9 = 0x98, 418 | /// Keyboard Alternate Erase (Footnote 30) 419 | KeyboardAlternateErase = 0x99, 420 | /// Keyboard SysReq/Attention (Footnote 7) 421 | KeyboardSysReqAttention = 0x9A, 422 | /// Keyboard Cancel 423 | KeyboardCancel = 0x9B, 424 | /// Keyboard Clear 425 | KeyboardClear = 0x9C, 426 | /// Keyboard Prior 427 | KeyboardPrior = 0x9D, 428 | /// Keyboard Return 429 | KeyboardReturn = 0x9E, 430 | /// Keyboard Separator 431 | KeyboardSeparator = 0x9F, 432 | /// Keyboard Out 433 | KeyboardOut = 0xA0, 434 | /// Keyboard Oper 435 | KeyboardOper = 0xA1, 436 | /// Keyboard Clear/Again 437 | KeyboardClearAgain = 0xA2, 438 | /// Keyboard CrSel/Props 439 | KeyboardCrSelProps = 0xA3, 440 | /// Keyboard ExSel 441 | KeyboardExSel = 0xA4, 442 | // 0xA5-0xAF: Reserved 443 | /// Keypad 00 444 | Keypad00 = 0xB0, 445 | /// Keypad 000 446 | Keypad000 = 0xB1, 447 | /// Thousands Separator (Footnote 31) 448 | ThousandsSeparator = 0xB2, 449 | /// Decimal Separator (Footnote 31) 450 | DecimalSeparator = 0xB3, 451 | /// Currency Unit (Footnote 32) 452 | CurrencyUnit = 0xB4, 453 | /// Currency Sub-unit (Footnote 32) 454 | CurrencySubunit = 0xB5, 455 | /// Keypad ( 456 | KeypadOpenParens = 0xB6, 457 | /// Keypad ) 458 | KeypadCloseParens = 0xB7, 459 | /// Keypad { 460 | KeypadOpenBrace = 0xB8, 461 | /// Keypad } 462 | KeypadCloseBrace = 0xB9, 463 | /// Keypad Tab 464 | KeypadTab = 0xBA, 465 | /// Keypad Backspace 466 | KeypadBackspace = 0xBB, 467 | /// Keypad A 468 | KeypadA = 0xBC, 469 | /// Keypad B 470 | KeypadB = 0xBD, 471 | /// Keypad C 472 | KeypadC = 0xBE, 473 | /// Keypad D 474 | KeypadD = 0xBF, 475 | /// Keypad E 476 | KeypadE = 0xC0, 477 | /// Keypad F 478 | KeypadF = 0xC1, 479 | /// Keypad XOR 480 | KeypadBitwiseXor = 0xC2, 481 | /// Keypad ^ 482 | KeypadLogicalXor = 0xC3, 483 | /// Keypad % 484 | KeypadModulo = 0xC4, 485 | /// Keypad < 486 | KeypadLeftShift = 0xC5, 487 | /// Keypad > 488 | KeypadRightShift = 0xC6, 489 | /// Keypad & 490 | KeypadBitwiseAnd = 0xC7, 491 | /// Keypad && 492 | KeypadLogicalAnd = 0xC8, 493 | /// Keypad | 494 | KeypadBitwiseOr = 0xC9, 495 | /// Keypad || 496 | KeypadLogicalOr = 0xCA, 497 | /// Keypad : 498 | KeypadColon = 0xCB, 499 | /// Keypad # 500 | KeypadHash = 0xCC, 501 | /// Keypad Space 502 | KeypadSpace = 0xCD, 503 | /// Keypad @ 504 | KeypadAt = 0xCE, 505 | /// Keypad ! 506 | KeypadExclamation = 0xCF, 507 | /// Keypad Memory Store 508 | KeypadMemoryStore = 0xD0, 509 | /// Keypad Memory Recall 510 | KeypadMemoryRecall = 0xD1, 511 | /// Keypad Memory Clear 512 | KeypadMemoryClear = 0xD2, 513 | /// Keypad Memory Add 514 | KeypadMemoryAdd = 0xD3, 515 | /// Keypad Memory Subtract 516 | KeypadMemorySubtract = 0xD4, 517 | /// Keypad Memory Multiply 518 | KeypadMemoryMultiply = 0xD5, 519 | /// Keypad Memory Divice 520 | KeypadMemoryDivide = 0xD6, 521 | /// Keypad +/- 522 | KeypadPositiveNegative = 0xD7, 523 | /// Keypad Clear 524 | KeypadClear = 0xD8, 525 | /// Keypad Clear Entry 526 | KeypadClearEntry = 0xD9, 527 | /// Keypad Binary 528 | KeypadBinary = 0xDA, 529 | /// Keypad Octal 530 | KeypadOctal = 0xDB, 531 | /// Keypad Decimal 532 | KeypadDecimal = 0xDC, 533 | /// Keypad Hexadecimal 534 | KeypadHexadecimal = 0xDD, 535 | // 0xDE-0xDF: Reserved 536 | /// Keyboard LeftControl 537 | KeyboardLeftControl = 0xE0, 538 | /// Keyboard LeftShift 539 | KeyboardLeftShift = 0xE1, 540 | /// Keyboard LeftAlt 541 | KeyboardLeftAlt = 0xE2, 542 | /// Keyboard LeftGUI (Footnote 11) (Footnote 33) 543 | KeyboardLeftGUI = 0xE3, 544 | /// Keyboard RightControl 545 | KeyboardRightControl = 0xE4, 546 | /// Keyboard RightShift 547 | KeyboardRightShift = 0xE5, 548 | /// Keyboard RightAlt 549 | KeyboardRightAlt = 0xE6, 550 | /// Keyboard RightGUI (Footnote 11) (Footnote 34) 551 | KeyboardRightGUI = 0xE7, 552 | /// Reserved keyboard values (used for all reserved / invalid values) 553 | Reserved = 0xE8, 554 | // 0xE8-0xFF: Reserved 555 | } 556 | 557 | impl From for KeyboardUsage { 558 | fn from(k: u8) -> Self { 559 | match k { 560 | 0x01 => Self::KeyboardErrorRollOver, 561 | 0x02 => Self::KeyboardPOSTFail, 562 | 0x03 => Self::KeyboardErrorUndefined, 563 | 0x04 => Self::KeyboardAa, 564 | 0x05 => Self::KeyboardBb, 565 | 0x06 => Self::KeyboardCc, 566 | 0x07 => Self::KeyboardDd, 567 | 0x08 => Self::KeyboardEe, 568 | 0x09 => Self::KeyboardFf, 569 | 0x0A => Self::KeyboardGg, 570 | 0x0B => Self::KeyboardHh, 571 | 0x0C => Self::KeyboardIi, 572 | 0x0D => Self::KeyboardJj, 573 | 0x0E => Self::KeyboardKk, 574 | 0x0F => Self::KeyboardLl, 575 | 0x10 => Self::KeyboardMm, 576 | 0x11 => Self::KeyboardNn, 577 | 0x12 => Self::KeyboardOo, 578 | 0x13 => Self::KeyboardPp, 579 | 0x14 => Self::KeyboardQq, 580 | 0x15 => Self::KeyboardRr, 581 | 0x16 => Self::KeyboardSs, 582 | 0x17 => Self::KeyboardTt, 583 | 0x18 => Self::KeyboardUu, 584 | 0x19 => Self::KeyboardVv, 585 | 0x1A => Self::KeyboardWw, 586 | 0x1B => Self::KeyboardXx, 587 | 0x1C => Self::KeyboardYy, 588 | 0x1D => Self::KeyboardZz, 589 | 0x1E => Self::Keyboard1Exclamation, 590 | 0x1F => Self::Keyboard2At, 591 | 0x20 => Self::Keyboard3Hash, 592 | 0x21 => Self::Keyboard4Dollar, 593 | 0x22 => Self::Keyboard5Percent, 594 | 0x23 => Self::Keyboard6Caret, 595 | 0x24 => Self::Keyboard7Ampersand, 596 | 0x25 => Self::Keyboard8Asterisk, 597 | 0x26 => Self::Keyboard9OpenParens, 598 | 0x27 => Self::Keyboard0CloseParens, 599 | 0x28 => Self::KeyboardEnter, 600 | 0x29 => Self::KeyboardEscape, 601 | 0x2A => Self::KeyboardBackspace, 602 | 0x2B => Self::KeyboardTab, 603 | 0x2C => Self::KeyboardSpacebar, 604 | 0x2D => Self::KeyboardDashUnderscore, 605 | 0x2E => Self::KeyboardEqualPlus, 606 | 0x2F => Self::KeyboardOpenBracketBrace, 607 | 0x30 => Self::KeyboardCloseBracketBrace, 608 | 0x31 => Self::KeyboardBackslashBar, 609 | 0x32 => Self::KeyboardNonUSHash, 610 | 0x33 => Self::KeyboardSemiColon, 611 | 0x34 => Self::KeyboardSingleDoubleQuote, 612 | 0x35 => Self::KeyboardBacktickTilde, 613 | 0x36 => Self::KeyboardCommaLess, 614 | 0x37 => Self::KeyboardPeriodGreater, 615 | 0x38 => Self::KeyboardSlashQuestion, 616 | 0x39 => Self::KeyboardCapsLock, 617 | 0x3A => Self::KeyboardF1, 618 | 0x3B => Self::KeyboardF2, 619 | 0x3C => Self::KeyboardF3, 620 | 0x3D => Self::KeyboardF4, 621 | 0x3E => Self::KeyboardF5, 622 | 0x3F => Self::KeyboardF6, 623 | 0x40 => Self::KeyboardF7, 624 | 0x41 => Self::KeyboardF8, 625 | 0x42 => Self::KeyboardF9, 626 | 0x43 => Self::KeyboardF10, 627 | 0x44 => Self::KeyboardF11, 628 | 0x45 => Self::KeyboardF12, 629 | 0x46 => Self::KeyboardPrintScreen, 630 | 0x47 => Self::KeyboardScrollLock, 631 | 0x48 => Self::KeyboardPause, 632 | 0x49 => Self::KeyboardInsert, 633 | 0x4A => Self::KeyboardHome, 634 | 0x4B => Self::KeyboardPageUp, 635 | 0x4C => Self::KeyboardDelete, 636 | 0x4D => Self::KeyboardEnd, 637 | 0x4E => Self::KeyboardPageDown, 638 | 0x4F => Self::KeyboardRightArrow, 639 | 0x50 => Self::KeyboardLeftArrow, 640 | 0x51 => Self::KeyboardDownArrow, 641 | 0x52 => Self::KeyboardUpArrow, 642 | 0x53 => Self::KeypadNumLock, 643 | 0x54 => Self::KeypadDivide, 644 | 0x55 => Self::KeypadMultiply, 645 | 0x56 => Self::KeypadMinus, 646 | 0x57 => Self::KeypadPlus, 647 | 0x58 => Self::KeypadEnter, 648 | 0x59 => Self::Keypad1End, 649 | 0x5A => Self::Keypad2DownArrow, 650 | 0x5B => Self::Keypad3PageDown, 651 | 0x5C => Self::Keypad4LeftArrow, 652 | 0x5D => Self::Keypad5, 653 | 0x5E => Self::Keypad6RightArrow, 654 | 0x5F => Self::Keypad7Home, 655 | 0x60 => Self::Keypad8UpArrow, 656 | 0x61 => Self::Keypad9PageUp, 657 | 0x62 => Self::Keypad0Insert, 658 | 0x63 => Self::KeypadPeriodDelete, 659 | 0x64 => Self::KeyboardNonUSSlash, 660 | 0x65 => Self::KeyboardApplication, 661 | 0x66 => Self::KeyboardPower, 662 | 0x67 => Self::KeypadEqual, 663 | 0x68 => Self::KeyboardF13, 664 | 0x69 => Self::KeyboardF14, 665 | 0x6A => Self::KeyboardF15, 666 | 0x6B => Self::KeyboardF16, 667 | 0x6C => Self::KeyboardF17, 668 | 0x6D => Self::KeyboardF18, 669 | 0x6E => Self::KeyboardF19, 670 | 0x6F => Self::KeyboardF20, 671 | 0x70 => Self::KeyboardF21, 672 | 0x71 => Self::KeyboardF22, 673 | 0x72 => Self::KeyboardF23, 674 | 0x73 => Self::KeyboardF24, 675 | 0x74 => Self::KeyboardExecute, 676 | 0x75 => Self::KeyboardHelp, 677 | 0x76 => Self::KeyboardMenu, 678 | 0x77 => Self::KeyboardSelect, 679 | 0x78 => Self::KeyboardStop, 680 | 0x79 => Self::KeyboardAgain, 681 | 0x7A => Self::KeyboardUndo, 682 | 0x7B => Self::KeyboardCut, 683 | 0x7C => Self::KeyboardCopy, 684 | 0x7D => Self::KeyboardPaste, 685 | 0x7E => Self::KeyboardFind, 686 | 0x7F => Self::KeyboardMute, 687 | 0x80 => Self::KeyboardVolumeUp, 688 | 0x81 => Self::KeyboardVolumeDown, 689 | 0x82 => Self::KeyboardLockingCapsLock, 690 | 0x83 => Self::KeyboardLockingNumLock, 691 | 0x84 => Self::KeyboardLockingScrollLock, 692 | 0x85 => Self::KeypadComma, 693 | 0x86 => Self::KeypadEqualSign, 694 | 0x87 => Self::KeyboardInternational1, 695 | 0x88 => Self::KeyboardInternational2, 696 | 0x89 => Self::KeyboardInternational3, 697 | 0x8A => Self::KeyboardInternational4, 698 | 0x8B => Self::KeyboardInternational5, 699 | 0x8C => Self::KeyboardInternational6, 700 | 0x8D => Self::KeyboardInternational7, 701 | 0x8E => Self::KeyboardInternational8, 702 | 0x8F => Self::KeyboardInternational9, 703 | 0x90 => Self::KeyboardLANG1, 704 | 0x91 => Self::KeyboardLANG2, 705 | 0x92 => Self::KeyboardLANG3, 706 | 0x93 => Self::KeyboardLANG4, 707 | 0x94 => Self::KeyboardLANG5, 708 | 0x95 => Self::KeyboardLANG6, 709 | 0x96 => Self::KeyboardLANG7, 710 | 0x97 => Self::KeyboardLANG8, 711 | 0x98 => Self::KeyboardLANG9, 712 | 0x99 => Self::KeyboardAlternateErase, 713 | 0x9A => Self::KeyboardSysReqAttention, 714 | 0x9B => Self::KeyboardCancel, 715 | 0x9C => Self::KeyboardClear, 716 | 0x9D => Self::KeyboardPrior, 717 | 0x9E => Self::KeyboardReturn, 718 | 0x9F => Self::KeyboardSeparator, 719 | 0xA0 => Self::KeyboardOut, 720 | 0xA1 => Self::KeyboardOper, 721 | 0xA2 => Self::KeyboardClearAgain, 722 | 0xA3 => Self::KeyboardCrSelProps, 723 | 0xA4 => Self::KeyboardExSel, 724 | 0xB0 => Self::Keypad00, 725 | 0xB1 => Self::Keypad000, 726 | 0xB2 => Self::ThousandsSeparator, 727 | 0xB3 => Self::DecimalSeparator, 728 | 0xB4 => Self::CurrencyUnit, 729 | 0xB5 => Self::CurrencySubunit, 730 | 0xB6 => Self::KeypadOpenParens, 731 | 0xB7 => Self::KeypadCloseParens, 732 | 0xB8 => Self::KeypadOpenBrace, 733 | 0xB9 => Self::KeypadCloseBrace, 734 | 0xBA => Self::KeypadTab, 735 | 0xBB => Self::KeypadBackspace, 736 | 0xBC => Self::KeypadA, 737 | 0xBD => Self::KeypadB, 738 | 0xBE => Self::KeypadC, 739 | 0xBF => Self::KeypadD, 740 | 0xC0 => Self::KeypadE, 741 | 0xC1 => Self::KeypadF, 742 | 0xC2 => Self::KeypadBitwiseXor, 743 | 0xC3 => Self::KeypadLogicalXor, 744 | 0xC4 => Self::KeypadModulo, 745 | 0xC5 => Self::KeypadLeftShift, 746 | 0xC6 => Self::KeypadRightShift, 747 | 0xC7 => Self::KeypadBitwiseAnd, 748 | 0xC8 => Self::KeypadLogicalAnd, 749 | 0xC9 => Self::KeypadBitwiseOr, 750 | 0xCA => Self::KeypadLogicalOr, 751 | 0xCB => Self::KeypadColon, 752 | 0xCC => Self::KeypadHash, 753 | 0xCD => Self::KeypadSpace, 754 | 0xCE => Self::KeypadAt, 755 | 0xCF => Self::KeypadExclamation, 756 | 0xD0 => Self::KeypadMemoryStore, 757 | 0xD1 => Self::KeypadMemoryRecall, 758 | 0xD2 => Self::KeypadMemoryClear, 759 | 0xD3 => Self::KeypadMemoryAdd, 760 | 0xD4 => Self::KeypadMemorySubtract, 761 | 0xD5 => Self::KeypadMemoryMultiply, 762 | 0xD6 => Self::KeypadMemoryDivide, 763 | 0xD7 => Self::KeypadPositiveNegative, 764 | 0xD8 => Self::KeypadClear, 765 | 0xD9 => Self::KeypadClearEntry, 766 | 0xDA => Self::KeypadBinary, 767 | 0xDB => Self::KeypadOctal, 768 | 0xDC => Self::KeypadDecimal, 769 | 0xDD => Self::KeypadHexadecimal, 770 | 0xE0 => Self::KeyboardLeftControl, 771 | 0xE1 => Self::KeyboardLeftShift, 772 | 0xE2 => Self::KeyboardLeftAlt, 773 | 0xE3 => Self::KeyboardLeftGUI, 774 | 0xE4 => Self::KeyboardRightControl, 775 | 0xE5 => Self::KeyboardRightShift, 776 | 0xE6 => Self::KeyboardRightAlt, 777 | 0xE7 => Self::KeyboardRightGUI, 778 | _ => Self::Reserved, 779 | } 780 | } 781 | } 782 | 783 | /// MediaKeyboardReport describes a report and descriptor that can be used to 784 | /// send consumer control commands to the host. 785 | /// 786 | /// This is commonly used for sending media player for keyboards with media player 787 | /// keys, but can be used for all sorts of Consumer Page functionality. 788 | /// 789 | /// Reference: 790 | /// 791 | #[gen_hid_descriptor( 792 | (collection = APPLICATION, usage_page = CONSUMER, usage = CONSUMER_CONTROL) = { 793 | (usage_page = CONSUMER, usage_min = 0x00, usage_max = 0x514) = { 794 | #[item_settings data,array,absolute,not_null] usage_id=input; 795 | }; 796 | } 797 | )] 798 | #[allow(dead_code)] 799 | pub struct MediaKeyboardReport { 800 | pub usage_id: u16, 801 | } 802 | 803 | /// Media player usage ids that can be used in MediaKeyboardReport 804 | #[non_exhaustive] 805 | #[repr(u16)] 806 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 807 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 808 | pub enum MediaKey { 809 | Zero = 0x00, 810 | Play = 0xB0, 811 | Pause = 0xB1, 812 | Record = 0xB2, 813 | NextTrack = 0xB5, 814 | PrevTrack = 0xB6, 815 | Stop = 0xB7, 816 | RandomPlay = 0xB9, 817 | Repeat = 0xBC, 818 | PlayPause = 0xCD, 819 | Mute = 0xE2, 820 | VolumeIncrement = 0xE9, 821 | VolumeDecrement = 0xEA, 822 | Reserved = 0xEB, 823 | } 824 | 825 | impl From for u16 { 826 | fn from(mk: MediaKey) -> u16 { 827 | mk as u16 828 | } 829 | } 830 | 831 | impl From for MediaKey { 832 | fn from(k: u8) -> Self { 833 | match k { 834 | 0x00 => Self::Zero, 835 | 0xB0 => Self::Play, 836 | 0xB1 => Self::Pause, 837 | 0xB2 => Self::Record, 838 | 0xB5 => Self::NextTrack, 839 | 0xB6 => Self::PrevTrack, 840 | 0xB7 => Self::Stop, 841 | 0xB9 => Self::RandomPlay, 842 | 0xBC => Self::Repeat, 843 | 0xCD => Self::PlayPause, 844 | 0xE2 => Self::Mute, 845 | 0xE9 => Self::VolumeIncrement, 846 | 0xEA => Self::VolumeDecrement, 847 | _ => Self::Reserved, 848 | } 849 | } 850 | } 851 | 852 | impl From for MediaKey { 853 | fn from(k: u16) -> Self { 854 | (k as u8).into() 855 | } 856 | } 857 | 858 | /// SystemControlReport describes a report and descriptor that can be used to 859 | /// send system control commands to the host. 860 | /// 861 | /// This is commonly used to enter sleep mode, power down, hibernate, etc. 862 | /// 863 | /// Reference: 864 | /// 865 | /// NOTE: For Windows compatibility usage_min should start at 0x81 866 | /// NOTE: For macOS scrollbar compatibility, logical minimum should start from 1 867 | /// (scrollbars disappear if logical_min is set to 0) 868 | #[gen_hid_descriptor( 869 | (collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = SYSTEM_CONTROL) = { 870 | (usage_min = 0x81, usage_max = 0xB7, logical_min = 1) = { 871 | #[item_settings data,array,absolute,not_null] usage_id=input; 872 | }; 873 | } 874 | )] 875 | #[allow(dead_code)] 876 | pub struct SystemControlReport { 877 | pub usage_id: u8, 878 | } 879 | 880 | /// System control usage ids to use with SystemControlReport 881 | #[non_exhaustive] 882 | #[repr(u8)] 883 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 884 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 885 | pub enum SystemControlKey { 886 | PowerDown = 0x81, 887 | Sleep = 0x82, 888 | WakeUp = 0x83, 889 | ContextMenu = 0x84, 890 | MainMenu = 0x85, 891 | AppMenu = 0x86, 892 | MenuHelp = 0x87, 893 | MenuExit = 0x88, 894 | MenuSelect = 0x89, 895 | MenuRight = 0x8A, 896 | MenuLeft = 0x8B, 897 | MenuUp = 0x8C, 898 | MenuDown = 0x8D, 899 | ColdRestart = 0x8E, 900 | WarmRestart = 0x8F, 901 | DpadUp = 0x90, 902 | DpadDown = 0x91, 903 | DpadRight = 0x92, 904 | DpadLeft = 0x93, 905 | SystemFunctionShift = 0x97, 906 | SystemFunctionShiftLock = 0x98, 907 | SystemDismissNotification = 0x9A, 908 | SystemDoNotDisturb = 0x9B, 909 | Dock = 0xA0, 910 | Undock = 0xA1, 911 | Setup = 0xA2, 912 | Break = 0xA3, 913 | DebuggerBreak = 0xA4, 914 | ApplicationBreak = 0xA5, 915 | ApplicationDebuggerBreak = 0xA6, 916 | SpeakerMute = 0xA7, 917 | Hibernate = 0xA8, 918 | DisplayInvert = 0xB0, 919 | DisplayInternal = 0xB1, 920 | DisplayExternal = 0xB2, 921 | DisplayBoth = 0xB3, 922 | DisplayDual = 0xB4, 923 | DisplayToggleInternalExternal = 0xB5, 924 | DisplaySwapPrimarySecondary = 0xB6, 925 | DisplayLcdAutoscale = 0xB7, 926 | // Use this reserved value to represent all reserved keys / invalid values 927 | Reserved = 0xB8, 928 | } 929 | 930 | impl From for u8 { 931 | fn from(sck: SystemControlKey) -> u8 { 932 | sck as u8 933 | } 934 | } 935 | 936 | impl From for SystemControlKey { 937 | fn from(k: u8) -> Self { 938 | match k { 939 | 0x81 => Self::PowerDown, 940 | 0x82 => Self::Sleep, 941 | 0x83 => Self::WakeUp, 942 | 0x84 => Self::ContextMenu, 943 | 0x85 => Self::MainMenu, 944 | 0x86 => Self::AppMenu, 945 | 0x87 => Self::MenuHelp, 946 | 0x88 => Self::MenuExit, 947 | 0x89 => Self::MenuSelect, 948 | 0x8A => Self::MenuRight, 949 | 0x8B => Self::MenuLeft, 950 | 0x8C => Self::MenuUp, 951 | 0x8D => Self::MenuDown, 952 | 0x8E => Self::ColdRestart, 953 | 0x8F => Self::WarmRestart, 954 | 0x90 => Self::DpadUp, 955 | 0x91 => Self::DpadDown, 956 | 0x92 => Self::DpadRight, 957 | 0x93 => Self::DpadLeft, 958 | 0x97 => Self::SystemFunctionShift, 959 | 0x98 => Self::SystemFunctionShiftLock, 960 | 0x9A => Self::SystemDismissNotification, 961 | 0x9B => Self::SystemDoNotDisturb, 962 | 0xA0 => Self::Dock, 963 | 0xA1 => Self::Undock, 964 | 0xA2 => Self::Setup, 965 | 0xA3 => Self::Break, 966 | 0xA4 => Self::DebuggerBreak, 967 | 0xA5 => Self::ApplicationBreak, 968 | 0xA6 => Self::ApplicationDebuggerBreak, 969 | 0xA7 => Self::SpeakerMute, 970 | 0xA8 => Self::Hibernate, 971 | 0xB0 => Self::DisplayInvert, 972 | 0xB1 => Self::DisplayInternal, 973 | 0xB2 => Self::DisplayExternal, 974 | 0xB3 => Self::DisplayBoth, 975 | 0xB4 => Self::DisplayDual, 976 | 0xB5 => Self::DisplayToggleInternalExternal, 977 | 0xB6 => Self::DisplaySwapPrimarySecondary, 978 | 0xB7 => Self::DisplayLcdAutoscale, 979 | _ => Self::Reserved, 980 | } 981 | } 982 | } 983 | 984 | /// CtapReport describes a report and its companion descriptor that can be 985 | /// used to present a FIDO-compatible authenticator device to the host. 986 | #[gen_hid_descriptor( 987 | (collection = APPLICATION, usage_page = FIDO_ALLIANCE, usage = U2F_AUTHENTICATOR_DEVICE) = { 988 | (usage = INPUT_REPORT_DATA, logical_min = 0x0) = { 989 | #[item_settings data,variable,absolute] data_in=input; 990 | }; 991 | (usage = OUTPUT_REPORT_DATA, logical_min = 0x0) = { 992 | #[item_settings data,variable,absolute] data_out=output; 993 | }; 994 | } 995 | )] 996 | #[allow(dead_code)] 997 | pub struct CtapReport { 998 | pub data_in: [u8; 64], 999 | pub data_out: [u8; 64], 1000 | } 1001 | --------------------------------------------------------------------------------