Skip to main content

hid_descriptor/
lib.rs

1//! # Tools for building USB HID report descriptors.
2//!
3//! This is a utility for generating a binary USB HID report descriptor from Rust code.
4//! It can be used from `build.rs` so that the descriptor is generated at compile time,
5//! and the descriptor bytes can be included in the code, with no runtime cost.
6//!
7//! Alternatively, you can edit one of the example binaries, and copy the resulting
8//! descriptor file into your project.
9//!
10//! See [Device Class Definition for Human Interface Devices](https://www.usb.org/document-library/device-class-definition-hid-111)
11//! for a detailed description of HID report descriptors.
12//!
13//! # Example
14//! ```no_run
15//! // `build.rs`
16//! # use std::path::PathBuf;
17//! use hid_descriptor::*;
18//! use hid_types::id::{IoFlags, KnownCollectionType, KnownUsagePage};
19//! use hid_types::id::usage::{generic_desktop, keyboard_keypad};
20//!
21//! fn main() {
22//!     println!("cargo:rerun-if-changed=build.rs");
23//!     let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
24//!     let descriptor_path = out_dir.join("descriptor.bin");
25//!     println!("cargo:rerun-if-changed={}", descriptor_path.display());
26//!
27//!     let bytes = [
28//!         usage_page(KnownUsagePage::GenericDesktop),
29//!         usage(generic_desktop::KnownUsage::Gamepad),
30//!         collection(KnownCollectionType::Application),
31//!         usage_page(KnownUsagePage::Button),
32//!         usage_minimum(1u16),
33//!         usage_maximum(8u16),
34//!         logical_minimum(0),
35//!         logical_maximum(1),
36//!         report_size(1),
37//!         report_count(8),
38//!         input(IoFlags::data().variable()),
39//!         end_collection(),
40//!     ];
41//!     write_to_file(bytes, descriptor_path).unwrap();
42//! }
43//! ```
44//!
45//! ```ignore
46//! //! In your crate, compile in the descriptor bytes.
47//! static GAMEPAD_DESCRIPTOR: &[u8] =
48//!     include_bytes!(concat!(env!("OUT_DIR"), "/descriptor.bin"));
49//! ```
50
51#![warn(clippy::print_stderr, clippy::print_stdout, clippy::dbg_macro)]
52#![warn(clippy::todo)]
53#![warn(missing_docs)]
54
55// Re-export everything from the `hid_types` crate, so that callers
56// only need to track one direct dependency.
57pub use hid_types;
58
59// Re-export the EncodeItem trait because that module isn't public.
60pub use encode::EncodeItem;
61
62use hid_types::id::{CollectionType, InputFlags, IoFlags, Unit, UnitExponent, UsagePage};
63use hid_types::item::usage::{ExtendedUsage, Usage};
64use hid_types::item::{Global, Item, Local, Main};
65use tinyvec::TinyVec;
66
67use std::path::Path;
68
69mod encode;
70
71/// A variable-length short chunk of descriptor bytes.
72pub type DescriptorBytes = TinyVec<[u8; 4]>;
73
74/// Encode a sequence of items into a binary HID report descriptor.
75pub fn encode_items(items: &[Item]) -> Vec<u8> {
76    items.iter().flat_map(|item| item.encode()).collect()
77}
78
79/// Create a __Usage Page__.
80pub fn usage_page(usage_page: impl Into<UsagePage>) -> DescriptorBytes {
81    let usage_page: UsagePage = usage_page.into();
82    Global::UsagePage(usage_page).encode()
83}
84
85/// Create a __Collection__.
86pub fn collection(value: impl Into<CollectionType>) -> DescriptorBytes {
87    Main::Collection(value.into()).encode()
88}
89
90/// Create an __End Collection__.
91pub fn end_collection() -> DescriptorBytes {
92    Main::EndCollection.encode()
93}
94
95/// Create a __Logical Minimum__.
96pub fn logical_minimum(value: i32) -> DescriptorBytes {
97    Global::LogicalMinimum(value).encode()
98}
99
100/// Create a __Logical Maximum__.
101pub fn logical_maximum(value: i32) -> DescriptorBytes {
102    Global::LogicalMaximum(value).encode()
103}
104
105/// Create a __Physical Minimum__.
106pub fn physical_minimum(value: i32) -> DescriptorBytes {
107    Global::PhysicalMinimum(value).encode()
108}
109
110/// Create a __Physical Maximum__.
111pub fn physical_maximum(value: i32) -> DescriptorBytes {
112    Global::PhysicalMaximum(value).encode()
113}
114
115/// Create a __Unit__.
116pub fn unit(unit: impl Into<Unit>) -> DescriptorBytes {
117    let unit: Unit = unit.into();
118    Global::Unit(unit).encode()
119}
120
121/// Create a __Unit Exponent__.
122pub fn unit_exponent(unit_exponent: impl Into<UnitExponent>) -> DescriptorBytes {
123    let unit: UnitExponent = unit_exponent.into();
124    Global::UnitExponent(unit).encode()
125}
126
127/// Create a __Report ID__.
128///
129/// See [Device Class Definition for Human Interface Devices (HID)](https://www.usb.org/document-library/device-class-definition-hid-111),
130/// sections 5.6 and 6.2.2.7.
131///
132/// > Report ID items are used to indicate which data fields are represented in each report
133/// > structure. A Report ID item tag assigns a 1-byte identification prefix to each
134/// > report transfer. If no Report ID item tags are present in the Report descriptor, it
135/// > can be assumed that only one Input, Output, and Feature report structure exists
136/// > and together they represent all of the device’s data.
137pub fn report_id(value: u8) -> DescriptorBytes {
138    Global::ReportId(value).encode()
139}
140
141/// Create a __Report Size__ value.
142pub fn report_size(value: u32) -> DescriptorBytes {
143    Global::ReportSize(value).encode()
144}
145
146/// Create a __Report Count__ value.
147pub fn report_count(value: u32) -> DescriptorBytes {
148    Global::ReportCount(value).encode()
149}
150
151/// Create a __Push__.
152pub fn push() -> DescriptorBytes {
153    Global::Push.encode()
154}
155
156/// Create a __Pop__.
157pub fn pop() -> DescriptorBytes {
158    Global::Pop.encode()
159}
160
161/// Create an __Input__ item.
162pub fn input(flags: impl Into<IoFlags>) -> DescriptorBytes {
163    let flags = flags.into();
164    Main::Input(InputFlags::from_unchecked(flags)).encode()
165}
166
167/// Create an __Output__ item.
168pub fn output(flags: impl Into<IoFlags>) -> DescriptorBytes {
169    let flags = flags.into();
170    Main::Output(flags.into()).encode()
171}
172
173/// Create a __Feature__ item.
174pub fn feature(flags: impl Into<IoFlags>) -> DescriptorBytes {
175    let flags = flags.into();
176    Main::Feature(flags.into()).encode()
177}
178
179/// Create a __Usage__ value.
180///
181/// Note: this does not implement the "extended usage" format which also contains a usage page.
182pub fn usage(value: impl Into<u16>) -> DescriptorBytes {
183    let usage = Usage::without_page(value.into());
184    Local::Usage(usage).encode()
185}
186
187/// Create an extended __Usage__ value with a __Usage Page__ attached.
188pub fn extended_usage(usage_page: impl Into<UsagePage>, usage: impl Into<u16>) -> DescriptorBytes {
189    let extended_usage: ExtendedUsage = ExtendedUsage::new(usage_page.into(), usage.into());
190    Local::ExtendedUsage(extended_usage).encode()
191}
192
193/// Create a __Usage Minimum__ value.
194///
195/// Note: this does not implement the "extended usage" format which also contains a usage page.
196pub fn usage_minimum(value: impl Into<u16>) -> DescriptorBytes {
197    let value = value.into();
198    Local::UsageMinimum(value.into()).encode()
199}
200
201/// Create a __Usage Maximum__ value.
202///
203/// Note: this does not implement the "extended usage" format which also contains a usage page.
204pub fn usage_maximum(value: impl Into<u16>) -> DescriptorBytes {
205    let value = value.into();
206    Local::UsageMaximum(value.into()).encode()
207}
208
209/// Create a __Designator Index__.
210pub fn designator_index(value: u32) -> DescriptorBytes {
211    Local::DesignatorIndex(value).encode()
212}
213
214/// Create a __Designator Minimum__.
215pub fn designator_minimum(value: u32) -> DescriptorBytes {
216    Local::DesignatorMinimum(value).encode()
217}
218
219/// Create a __Designator Maximum__.
220pub fn designator_maximum(value: u32) -> DescriptorBytes {
221    Local::DesignatorMaximum(value).encode()
222}
223
224/// Create a __String Index__.
225pub fn string_index(value: u32) -> DescriptorBytes {
226    Local::StringIndex(value).encode()
227}
228
229/// Create a __String Minimum__.
230pub fn string_minimum(value: u32) -> DescriptorBytes {
231    Local::StringMinimum(value).encode()
232}
233
234/// Create a __String Maximum__.
235pub fn string_maximum(value: u32) -> DescriptorBytes {
236    Local::StringMaximum(value).encode()
237}
238
239/// Create a __Delimiter__.
240pub fn string_(value: bool) -> DescriptorBytes {
241    Local::Delimiter(value).encode()
242}
243
244/// Concatenate a bunch of descriptor bytes into one iterator.
245///
246/// This is just a more descriptive name for `.into_iter().flatten()`.
247pub fn concatenate_bytes(
248    items: impl IntoIterator<Item = impl IntoIterator<Item = u8>>,
249) -> impl Iterator<Item = u8> {
250    items.into_iter().flatten()
251}
252
253/// Write a binary encoded descriptor to a file.
254///
255/// This function just concatenates a list of byte vectors into a `Vec<u8>` and then writes them.
256/// It's easy to write your own version of this function-- just peek at the source code.
257pub fn write_to_file(
258    items: impl IntoIterator<Item = impl IntoIterator<Item = u8>>,
259    path: impl AsRef<Path>,
260) -> std::io::Result<()> {
261    let descriptor: Vec<u8> = items.into_iter().flatten().collect();
262    std::fs::write(path, descriptor)
263}
264
265#[cfg(test)]
266mod tests {
267    use hid_types::id::{KnownCollectionType, KnownUsagePage};
268
269    use super::*;
270
271    #[test]
272    fn test_usage_page() {
273        let bytes = usage_page(KnownUsagePage::GenericDesktop);
274        assert_eq!(bytes.as_slice(), &[0x05, 0x01]);
275
276        let bytes = usage_page(UsagePage::Reserved(0xFF00));
277        assert_eq!(bytes.as_slice(), &[0x06, 0x00, 0xFF]);
278
279        // Usage Page values are defined to be unsigned, so it's safe to shorten the data to one byte.
280        let bytes = usage_page(UsagePage::Reserved(0x00FF));
281        assert_eq!(bytes.as_slice(), &[0x05, 0xFF]);
282    }
283
284    #[test]
285    fn concatenate() {
286        use hid_types::id::usage::generic_desktop::KnownUsage as Usage;
287
288        let bytes = [
289            usage_page(KnownUsagePage::GenericDesktop),
290            usage(Usage::Joystick),
291            collection(KnownCollectionType::Application),
292            usage(Usage::Pointer),
293            collection(KnownCollectionType::Physical),
294            usage(Usage::X),
295            usage(Usage::Y),
296            logical_minimum(-1),
297            logical_maximum(255),
298            report_size(8),
299            report_count(2),
300            input(IoFlags::data().variable()),
301            end_collection(),
302            usage_page(KnownUsagePage::Button),
303            usage_minimum(1u16),
304            usage_maximum(8u16),
305            logical_minimum(0),
306            logical_maximum(1),
307            report_size(1),
308            report_count(8),
309            input(IoFlags::data().variable()),
310            end_collection(),
311        ];
312        let desc: Vec<u8> = concatenate_bytes(bytes).collect();
313        assert_eq!(
314            desc,
315            &[
316                0x05, 0x01, // usage page
317                0x09, 0x04, // usage
318                0xA1, 0x01, // collection(application)
319                0x09, 0x01, // usage
320                0xa1, 0x00, // collection(physical)
321                0x09, 0x30, // X
322                0x09, 0x31, // Y
323                0x15, 0xFF, // logical minimum
324                0x26, 0xFF, 0x00, // logical maximum
325                0x75, 0x08, // report size
326                0x95, 0x02, // report count
327                0x81, 0x02, // input
328                0xc0, // end collection
329                0x05, 0x09, // usage page (button)
330                0x19, 0x01, // usage minimum
331                0x29, 0x08, // usage maximum
332                0x15, 0x00, // logical minimum
333                0x25, 0x01, // logical maximum
334                0x75, 0x01, // report size
335                0x95, 0x08, // report count
336                0x81, 0x02, // input
337                0xc0, // end collection
338            ]
339        );
340    }
341}