diff --git a/Cargo.lock b/Cargo.lock index 7121a8a..5bea059 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,11 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "arc-swap" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" + [[package]] name = "arrayref" version = "0.3.6" @@ -148,6 +154,17 @@ dependencies = [ "wasi 0.9.0+wasi-snapshot-preview1", ] +[[package]] +name = "hidapi" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6ffb97f2ec5835ec73bcea5256fc2cd57a13c5958230778ef97f11900ba661" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -299,6 +316,26 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "signal-hook" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035" +dependencies = [ + "arc-swap", + "libc", +] + [[package]] name = "strum" version = "0.8.0" @@ -320,7 +357,9 @@ name = "surface-dial-daemon" version = "0.1.0" dependencies = [ "evdev-rs", + "hidapi", "notify-rust", + "signal-hook", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ead7b7b..084e703 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,6 @@ edition = "2018" [dependencies] # master includes a PR that implements `Send` for `Device` and `UInputDevice` evdev-rs = { git = "https://github.com/ndesh26/evdev-rs.git" } +hidapi = { version = "1.2.3", default-features = false, features = ["linux-shared-hidraw"] } notify-rust = "4" +signal-hook = "0.1.16" diff --git a/README.md b/README.md index 8922b70..a884156 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ A Linux userspace controller for the [Microsoft Surface Dial](https://www.micros - Uses the [`evdev`](https://en.wikipedia.org/wiki/Evdev) API + `libevdev` to read events from the surface dial. - Uses `libevdev` to fake input via `/dev/uinput` (for keypresses / media controls) +- Uses `hidapi` to configure dial sensitivity + haptics **DISCLAIMER: This is WIP software!** @@ -34,33 +35,42 @@ It would be cool to create some sort of GUI overlay (similar to the Windows one) - [x] Scrolling / Zooming - [ ] \(meta\) Specify modes via config file(s) - [ ] Dynamically switch between operating modes - - _currently required re-compiling the daemon_ - - [ ] Context-sensitive (based on currently open application) - - [ ] Using `surface-dial-cli` application + - _currently requires re-compiling the daemon_ - [ ] Using some-sort of on-device mechanism (e.g: long-press) -- [ ] Haptic Feedback - - This is tough one, as it doesn't seem like the kernel driver exposes any haptic feedback interface... + - [ ] Using `surface-dial-cli` application + - [ ] Context-sensitive (based on currently open application) +- [x] Haptic Feedback + - https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/radial-controller-protocol-implementation + - https://www.usb.org/sites/default/files/hutrr63b_-_haptics_page_redline_0.pdf + - https://www.usb.org/sites/default/files/hut1_21.pdf + - _This was tricky to figure out, but in the end, it was surprisingly straightforward! Big thanks to [Geo](https://www.linkedin.com/in/geo-palakunnel-57718245/) for pointing me in the right direction!_ - [x] Desktop Notifications - [x] On Launch - [x] When switching between sub-modes (e.g: scroll/zoom) Feel free to contribute new features! -## Building +## Dependencies Building `surface-dial-daemon` requires the following: +- Linux Kernel 4.19 or higher - A fairly recent version of the Rust compiler - `libevdev` +- `hidapi` -If `libevdev` is not installed, the `evdev_rs` Rust library will try to build it from source, which may require other bits of build tooling. As such, it's recommended to install `libevdev` if it's available through your distribution. +You can install Rust through [`rustup`](https://rustup.rs/). + +Unless you're a cool hackerman, the easiest way to get `libevdev` and `hidapi` is via your distro's package manager. ```bash # e.g: on ubuntu -sudo apt install libevdev-dev +sudo apt install libevdev-dev libhidapi-dev ``` -Otherwise, `surface-dial-daemon` uses the bog-standard `cargo build` flow. +## Building + +`surface-dial-daemon` uses the bog-standard `cargo build` flow. ```bash cargo build -p surface-dial-daemon --release diff --git a/notes/HID_Report_Descriptor.txt b/notes/HID_Report_Descriptor.txt index 544e171..aa2a2ef 100644 --- a/notes/HID_Report_Descriptor.txt +++ b/notes/HID_Report_Descriptor.txt @@ -1,234 +1,234 @@ -# extracted using hid-example.c + https://eleccelerator.com/usbdescreqparser/ +# extracted using cat /sys/bus/hid/devices/*:045E:091B.*/report_descriptor | hidrd-convert -o spec -0x05, 0x01, // Usage Page (Generic Desktop Ctrls) -0x09, 0x0E, // Usage (0x0E) -0xA1, 0x01, // Collection (Application) -0x85, 0x01, // Report ID (1) -0x05, 0x0D, // Usage Page (Digitizer) -0x09, 0x21, // Usage (Puck) -0xA1, 0x02, // Collection (Logical) -0x15, 0x00, // Logical Minimum (0) -0x25, 0x01, // Logical Maximum (1) -0x75, 0x01, // Report Size (1) -0x95, 0x01, // Report Count (1) -0xA1, 0x00, // Collection (Physical) -0x05, 0x09, // Usage Page (Button) -0x09, 0x01, // Usage (0x01) -0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) -0x05, 0x0D, // Usage Page (Digitizer) -0x09, 0x33, // Usage (Touch) -0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) -0x95, 0x06, // Report Count (6) -0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) -0xA1, 0x02, // Collection (Logical) -0x05, 0x01, // Usage Page (Generic Desktop Ctrls) -0x09, 0x37, // Usage (Dial) -0x16, 0x01, 0x80, // Logical Minimum (-32767) -0x26, 0xFF, 0x7F, // Logical Maximum (32767) -0x75, 0x10, // Report Size (16) -0x95, 0x01, // Report Count (1) -0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) -0x35, 0x00, // Physical Minimum (0) -0x46, 0x10, 0x0E, // Physical Maximum (3600) -0x15, 0x00, // Logical Minimum (0) -0x26, 0x10, 0x0E, // Logical Maximum (3600) -0x09, 0x48, // Usage (0x48) -0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) -0x45, 0x00, // Physical Maximum (0) -0xC0, // End Collection -0x55, 0x0E, // Unit Exponent (-2) -0x65, 0x11, // Unit (System: SI Linear, Length: Centimeter) -0x46, 0x00, 0x00, // Physical Maximum (0) -0x26, 0x00, 0x00, // Logical Maximum (0) -0x09, 0x30, // Usage (X) -0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) -0x09, 0x31, // Usage (Y) -0x46, 0x00, 0x00, // Physical Maximum (0) -0x26, 0x00, 0x00, // Logical Maximum (0) -0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) -0x05, 0x0D, // Usage Page (Digitizer) -0x09, 0x48, // Usage (0x48) -0x15, 0x3A, // Logical Minimum (58) -0x25, 0x3A, // Logical Maximum (58) -0x75, 0x08, // Report Size (8) -0x55, 0x0F, // Unit Exponent (-1) -0x35, 0x3A, // Physical Minimum (58) -0x45, 0x3A, // Physical Maximum (58) -0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) -0x55, 0x00, // Unit Exponent (0) -0x65, 0x00, // Unit (None) -0x35, 0x00, // Physical Minimum (0) -0x45, 0x00, // Physical Maximum (0) -0x05, 0x0E, // Usage Page (Reserved 0x0E) -0x09, 0x01, // Usage (0x01) -0xA1, 0x02, // Collection (Logical) -0x15, 0x00, // Logical Minimum (0) -0x26, 0xFF, 0x00, // Logical Maximum (255) -0x09, 0x24, // Usage (0x24) -0xB1, 0x42, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State,Non-volatile) -0x09, 0x24, // Usage (0x24) -0x91, 0x42, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State,Non-volatile) -0x15, 0x01, // Logical Minimum (1) -0x25, 0x07, // Logical Maximum (7) -0x09, 0x20, // Usage (0x20) -0xB1, 0x42, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State,Non-volatile) -0x09, 0x21, // Usage (0x21) -0x91, 0x42, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State,Non-volatile) -0x25, 0x0A, // Logical Maximum (10) -0x09, 0x28, // Usage (0x28) -0xB1, 0x42, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State,Non-volatile) -0x75, 0x10, // Report Size (16) -0x26, 0xD0, 0x07, // Logical Maximum (2000) -0x09, 0x25, // Usage (0x25) -0xB1, 0x42, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State,Non-volatile) -0x09, 0x25, // Usage (0x25) -0x91, 0x42, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State,Non-volatile) -0x85, 0x02, // Report ID (2) -0x75, 0x20, // Report Size (32) -0x17, 0x37, 0x00, 0x01, 0x00, // Logical Minimum (65590) -0x27, 0x37, 0x00, 0x01, 0x00, // Logical Maximum (65590) -0x09, 0x22, // Usage (0x22) -0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) -0x09, 0x11, // Usage (0x11) -0xA1, 0x02, // Collection (Logical) -0x05, 0x0A, // Usage Page (Ordinal) -0x95, 0x03, // Report Count (3) -0x09, 0x03, // Usage (0x03) -0x09, 0x04, // Usage (0x04) -0x09, 0x05, // Usage (0x05) -0x75, 0x08, // Report Size (8) -0x15, 0x00, // Logical Minimum (0) -0x25, 0xFF, // Logical Maximum (-1) -0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) -0xC0, // End Collection -0x05, 0x0E, // Usage Page (Reserved 0x0E) -0x09, 0x10, // Usage (0x10) -0xA1, 0x02, // Collection (Logical) -0x05, 0x0A, // Usage Page (Ordinal) -0x95, 0x01, // Report Count (1) -0x15, 0x03, // Logical Minimum (3) -0x25, 0x03, // Logical Maximum (3) -0x36, 0x03, 0x10, // Physical Minimum (4099) -0x46, 0x03, 0x10, // Physical Maximum (4099) -0x09, 0x03, // Usage (0x03) -0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) -0x15, 0x04, // Logical Minimum (4) -0x25, 0x04, // Logical Maximum (4) -0x36, 0x04, 0x10, // Physical Minimum (4100) -0x46, 0x04, 0x10, // Physical Maximum (4100) -0x09, 0x04, // Usage (0x04) -0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) -0x15, 0x05, // Logical Minimum (5) -0x25, 0x05, // Logical Maximum (5) -0x36, 0x04, 0x10, // Physical Minimum (4100) -0x46, 0x04, 0x10, // Physical Maximum (4100) -0x09, 0x05, // Usage (0x05) -0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) -0x35, 0x00, // Physical Minimum (0) -0x45, 0x00, // Physical Maximum (0) -0xC0, // End Collection -0xC0, // End Collection -0xC0, // End Collection -0xC0, // End Collection -0xC0, // End Collection -0x06, 0x07, 0xFF, // Usage Page (Vendor Defined 0xFF07) -0x09, 0x70, // Usage (0x70) -0xA1, 0x01, // Collection (Application) -0x85, 0x30, // Report ID (48) -0x15, 0x00, // Logical Minimum (0) -0x25, 0xFF, // Logical Maximum (-1) -0x95, 0x01, // Report Count (1) -0x75, 0x08, // Report Size (8) -0x09, 0x00, // Usage (0x00) -0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) -0xC0, // End Collection -0x09, 0x71, // Usage (0x71) -0xA1, 0x01, // Collection (Application) -0x15, 0x00, // Logical Minimum (0) -0x25, 0xFF, // Logical Maximum (-1) -0x75, 0x08, // Report Size (8) -0x95, 0x48, // Report Count (72) -0x85, 0x2A, // Report ID (42) -0x09, 0xC6, // Usage (0xC6) -0x82, 0x02, 0x01, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Buffered Bytes) -0x09, 0xC7, // Usage (0xC7) -0x92, 0x02, 0x01, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile,Buffered Bytes) -0x95, 0x34, // Report Count (52) -0x09, 0xC8, // Usage (0xC8) -0xB2, 0x03, 0x01, // Feature (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile,Buffered Bytes) -0x85, 0x2B, // Report ID (43) -0x09, 0xC9, // Usage (0xC9) -0x82, 0x02, 0x01, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Buffered Bytes) -0x09, 0xCA, // Usage (0xCA) -0x92, 0x02, 0x01, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile,Buffered Bytes) -0x09, 0xCB, // Usage (0xCB) -0xB2, 0x02, 0x01, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile,Buffered Bytes) -0x17, 0x00, 0x00, 0x00, 0x80, // Logical Minimum (-2147483649) -0x27, 0xFF, 0xFF, 0xFF, 0x7F, // Logical Maximum (2147483646) -0x75, 0x20, // Report Size (32) -0x95, 0x04, // Report Count (4) -0x85, 0x2C, // Report ID (44) -0x19, 0xCC, // Usage Minimum (0xCC) -0x29, 0xCF, // Usage Maximum (0xCF) -0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) -0x95, 0x04, // Report Count (4) -0x85, 0x2D, // Report ID (45) -0x19, 0xD8, // Usage Minimum (0xD8) -0x29, 0xDB, // Usage Maximum (0xDB) -0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) -0x95, 0x04, // Report Count (4) -0x19, 0xDC, // Usage Minimum (0xDC) -0x29, 0xDF, // Usage Maximum (0xDF) -0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) -0x19, 0xE0, // Usage Minimum (0xE0) -0x29, 0xE3, // Usage Maximum (0xE3) -0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) -0x85, 0x2E, // Report ID (46) -0x19, 0xE4, // Usage Minimum (0xE4) -0x29, 0xE7, // Usage Maximum (0xE7) -0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) -0x19, 0xE8, // Usage Minimum (0xE8) -0x29, 0xEB, // Usage Maximum (0xEB) -0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) -0x95, 0x0B, // Report Count (11) -0x19, 0xEC, // Usage Minimum (0xEC) -0x29, 0xEF, // Usage Maximum (0xEF) -0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) -0x95, 0x04, // Report Count (4) -0x85, 0x2F, // Report ID (47) -0x19, 0xF0, // Usage Minimum (0xF0) -0x29, 0xF3, // Usage Maximum (0xF3) -0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) -0x19, 0xF4, // Usage Minimum (0xF4) -0x29, 0xF7, // Usage Maximum (0xF7) -0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) -0x19, 0xF8, // Usage Minimum (0xF8) -0x29, 0xFB, // Usage Maximum (0xFB) -0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) -0xC0, // End Collection -0x05, 0x01, // Usage Page (Generic Desktop Ctrls) -0x09, 0x80, // Usage (Sys Control) -0xA1, 0x01, // Collection (Application) -0x85, 0x32, // Report ID (50) -0x09, 0x82, // Usage (Sys Sleep) -0x09, 0x83, // Usage (Sys Wake Up) -0x15, 0x00, // Logical Minimum (0) -0x25, 0x01, // Logical Maximum (1) -0x95, 0x02, // Report Count (2) -0x75, 0x01, // Report Size (1) -0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) -0x95, 0x06, // Report Count (6) -0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) -0xC0, // End Collection -0x09, 0x72, // Usage (0x72) -0xA1, 0x01, // Collection (Application) -0x85, 0x31, // Report ID (49) -0x95, 0x0A, // Report Count (10) -0x75, 0x08, // Report Size (8) -0x15, 0x00, // Logical Minimum (0) -0x25, 0xFF, // Logical Maximum (-1) -0x09, 0xC6, // Usage (0xC6) -0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) -0x09, 0xC7, // Usage (0xC7) -0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) -0xC0, // End Collection +Usage Page (Desktop), ; Generic desktop controls (01h) +Usage (0Eh), +Collection (Application), + Report ID (1), + Usage Page (Digitizer), ; Digitizer (0Dh) + Usage (Puck), ; Puck (21h, logical collection) + Collection (Logical), + Logical Minimum (0), + Logical Maximum (1), + Report Size (1), + Report Count (1), + Collection (Physical), + Usage Page (Button), ; Button (09h) + Usage (01h), + Input (Variable), + Usage Page (Digitizer), ; Digitizer (0Dh) + Usage (Touch), ; Touch (33h, momentary control) + Input (Variable), + Report Count (6), + Input (Constant, Variable), + Collection (Logical), + Usage Page (Desktop), ; Generic desktop controls (01h) + Usage (Dial), ; Dial (37h, dynamic value) + Logical Minimum (-32767), + Logical Maximum (32767), + Report Size (16), + Report Count (1), + Input (Variable, Relative), + Physical Minimum (0), + Physical Maximum (3600), + Logical Minimum (0), + Logical Maximum (3600), + Usage (Resolution Multiplier), ; Resolution multiplier (48h, dynamic value) + Feature (Variable), + Physical Maximum (0), + End Collection, + Unit Exponent (14), + Unit (Centimeter), + Physical Maximum (0), + Logical Maximum (0), + Usage (X), ; X (30h, dynamic value) + Input (Variable, Null State), + Usage (Y), ; Y (31h, dynamic value) + Physical Maximum (0), + Logical Maximum (0), + Input (Variable, Null State), + Usage Page (Digitizer), ; Digitizer (0Dh) + Usage (Width), ; Width (48h, dynamic value) + Logical Minimum (58), + Logical Maximum (58), + Report Size (8), + Unit Exponent (15), + Physical Minimum (58), + Physical Maximum (58), + Input (Constant, Variable), + Unit Exponent (0), + Unit, + Physical Minimum (0), + Physical Maximum (0), + Usage Page (0Eh), ; 0Eh, reserved + Usage (01h), + Collection (Logical), + Logical Minimum (0), + Logical Maximum (255), + Usage (24h), + Feature (Variable, Null State), + Usage (24h), + Output (Variable, Null State), + Logical Minimum (1), + Logical Maximum (7), + Usage (20h), + Feature (Variable, Null State), + Usage (21h), + Output (Variable, Null State), + Logical Maximum (10), + Usage (28h), + Feature (Variable, Null State), + Report Size (16), + Logical Maximum (2000), + Usage (25h), + Feature (Variable, Null State), + Usage (25h), + Output (Variable, Null State), + Report ID (2), + Report Size (32), + Logical Minimum (65591), + Logical Maximum (65591), + Usage (22h), + Feature (Variable), + Usage (11h), + Collection (Logical), + Usage Page (Ordinal), ; Ordinal (0Ah) + Report Count (3), + Usage (03h), + Usage (04h), + Usage (05h), + Report Size (8), + Logical Minimum (0), + Logical Maximum (-1), + Feature (Variable), + End Collection, + Usage Page (0Eh), ; 0Eh, reserved + Usage (10h), + Collection (Logical), + Usage Page (Ordinal), ; Ordinal (0Ah) + Report Count (1), + Logical Minimum (3), + Logical Maximum (3), + Physical Minimum (4099), + Physical Maximum (4099), + Usage (03h), + Feature (Variable), + Logical Minimum (4), + Logical Maximum (4), + Physical Minimum (4100), + Physical Maximum (4100), + Usage (04h), + Feature (Variable), + Logical Minimum (5), + Logical Maximum (5), + Physical Minimum (4100), + Physical Maximum (4100), + Usage (05h), + Feature (Variable), + Physical Minimum (0), + Physical Maximum (0), + End Collection, + End Collection, + End Collection, + End Collection, +End Collection, +Usage Page (FF07h), ; FF07h, vendor-defined +Usage (70h), +Collection (Application), + Report ID (48), + Logical Minimum (0), + Logical Maximum (-1), + Report Count (1), + Report Size (8), + Usage (00h), + Output (Variable), +End Collection, +Usage (71h), +Collection (Application), + Logical Minimum (0), + Logical Maximum (-1), + Report Size (8), + Report Count (72), + Report ID (42), + Usage (C6h), + Input (Variable, Buffered Bytes), + Usage (C7h), + Output (Variable, Buffered Bytes), + Report Count (52), + Usage (C8h), + Feature (Constant, Variable, Buffered Bytes), + Report ID (43), + Usage (C9h), + Input (Variable, Buffered Bytes), + Usage (CAh), + Output (Variable, Buffered Bytes), + Usage (CBh), + Feature (Variable, Buffered Bytes), + Logical Minimum (-2147483648), + Logical Maximum (2147483647), + Report Size (32), + Report Count (4), + Report ID (44), + Usage Minimum (CCh), + Usage Maximum (CFh), + Input (Variable), + Report Count (4), + Report ID (45), + Usage Minimum (D8h), + Usage Maximum (DBh), + Input (Variable), + Report Count (4), + Usage Minimum (DCh), + Usage Maximum (DFh), + Output (Variable), + Usage Minimum (E0h), + Usage Maximum (E3h), + Feature (Variable), + Report ID (46), + Usage Minimum (E4h), + Usage Maximum (E7h), + Input (Variable), + Usage Minimum (E8h), + Usage Maximum (EBh), + Output (Variable), + Report Count (11), + Usage Minimum (ECh), + Usage Maximum (EFh), + Feature (Variable), + Report Count (4), + Report ID (47), + Usage Minimum (F0h), + Usage Maximum (F3h), + Input (Variable), + Usage Minimum (F4h), + Usage Maximum (F7h), + Output (Variable), + Usage Minimum (F8h), + Usage Maximum (FBh), + Feature (Variable), +End Collection, +Usage Page (Desktop), ; Generic desktop controls (01h) +Usage (Sys Control), ; System control (80h, application collection) +Collection (Application), + Report ID (50), + Usage (Sys Sleep), ; System sleep (82h, one-shot control) + Usage (Sys Wake Up), ; System wake up (83h, one-shot control) + Logical Minimum (0), + Logical Maximum (1), + Report Count (2), + Report Size (1), + Input (Variable), + Report Count (6), + Input (Constant, Variable), +End Collection, +Usage (72h), +Collection (Application), + Report ID (49), + Report Count (10), + Report Size (8), + Logical Minimum (0), + Logical Maximum (-1), + Usage (C6h), + Input (Variable), + Usage (C7h), + Output (Variable), +End Collection diff --git a/notes/descriptor.c b/notes/descriptor.c new file mode 100644 index 0000000..1d0c8d3 --- /dev/null +++ b/notes/descriptor.c @@ -0,0 +1,621 @@ +// generated by https://github.com/abend0c1/hidrdd + +//-------------------------------------------------------------------------------- +// Decoded Application Collection +//-------------------------------------------------------------------------------- + +/* +05 01 (GLOBAL) USAGE_PAGE 0x0001 Generic Desktop Page +09 0E (LOCAL) USAGE 0x0001000E System Multi-axis Controller (Application Collection) +A1 01 (MAIN) COLLECTION 0x01 Application (Usage=0x0001000E: Page=Generic Desktop Page, Usage=System Multi-axis Controller, Type=Application Collection) +85 01 (GLOBAL) REPORT_ID 0x01 (1) +05 0D (GLOBAL) USAGE_PAGE 0x000D Digitizer Device Page +09 21 (LOCAL) USAGE 0x000D0021 Puck (Logical Collection) +A1 02 (MAIN) COLLECTION 0x02 Logical (Usage=0x000D0021: Page=Digitizer Device Page, Usage=Puck, Type=Logical Collection) +15 00 (GLOBAL) LOGICAL_MINIMUM 0x00 (0) <-- Info: Consider replacing 15 00 with 14 +25 01 (GLOBAL) LOGICAL_MAXIMUM 0x01 (1) +75 01 (GLOBAL) REPORT_SIZE 0x01 (1) Number of bits per field +95 01 (GLOBAL) REPORT_COUNT 0x01 (1) Number of fields +A1 00 (MAIN) COLLECTION 0x00 Physical (Usage=0x0: Page=, Usage=, Type=) <-- Error: COLLECTION must be preceded by a known USAGE <-- Warning: USAGE type should be CP (Physical Collection) +05 09 (GLOBAL) USAGE_PAGE 0x0009 Button Page +09 01 (LOCAL) USAGE 0x00090001 Button 1 Primary/trigger (Selector, On/Off Control, Momentary Control, or One Shot Control) +81 02 (MAIN) INPUT 0x00000002 (1 field x 1 bit) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +05 0D (GLOBAL) USAGE_PAGE 0x000D Digitizer Device Page +09 33 (LOCAL) USAGE 0x000D0033 Touch (Momentary Control) +81 02 (MAIN) INPUT 0x00000002 (1 field x 1 bit) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +95 06 (GLOBAL) REPORT_COUNT 0x06 (6) Number of fields +81 03 (MAIN) INPUT 0x00000003 (6 fields x 1 bit) 1=Constant 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +A1 02 (MAIN) COLLECTION 0x02 Logical (Usage=0x0: Page=, Usage=, Type=) <-- Error: COLLECTION must be preceded by a known USAGE <-- Warning: USAGE type should be CL (Logical Collection) +05 01 (GLOBAL) USAGE_PAGE 0x0001 Generic Desktop Page +09 37 (LOCAL) USAGE 0x00010037 Dial (Dynamic Value) +16 0180 (GLOBAL) LOGICAL_MINIMUM 0x8001 (-32767) +26 FF7F (GLOBAL) LOGICAL_MAXIMUM 0x7FFF (32767) +75 10 (GLOBAL) REPORT_SIZE 0x10 (16) Number of bits per field +95 01 (GLOBAL) REPORT_COUNT 0x01 (1) Number of fields +81 06 (MAIN) INPUT 0x00000006 (1 field x 16 bits) 0=Data 1=Variable 1=Relative 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +35 00 (GLOBAL) PHYSICAL_MINIMUM 0x00 (0) <-- Info: Consider replacing 35 00 with 34 +46 100E (GLOBAL) PHYSICAL_MAXIMUM 0x0E10 (3600) +15 00 (GLOBAL) LOGICAL_MINIMUM 0x00 (0) <-- Info: Consider replacing 15 00 with 14 +26 100E (GLOBAL) LOGICAL_MAXIMUM 0x0E10 (3600) +09 48 (LOCAL) USAGE 0x00010048 Resolution Multiplier (Dynamic Value) +B1 02 (MAIN) FEATURE 0x00000002 (1 field x 16 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +45 00 (GLOBAL) PHYSICAL_MAXIMUM 0x00 (0) <-- Info: Consider replacing 45 00 with 44 +C0 (MAIN) END_COLLECTION Logical +55 0E (GLOBAL) UNIT_EXPONENT 0x0E (Unit Value x 10⁻²) +65 11 (GLOBAL) UNIT 0x11 Distance in metres [1 cm units] (1=System=SI Linear, 1=Length=Centimetre) +46 0000 (GLOBAL) PHYSICAL_MAXIMUM 0x0000 (0) <-- Redundant: PHYSICAL_MAXIMUM is already 0 <-- Info: Consider replacing 46 0000 with 44 +26 0000 (GLOBAL) LOGICAL_MAXIMUM 0x0000 (0) <-- Info: Consider replacing 26 0000 with 24 +09 30 (LOCAL) USAGE 0x00010030 X (Dynamic Value) +81 42 (MAIN) INPUT 0x00000042 (1 field x 16 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 1=Null 0=NonVolatile 0=Bitmap +09 31 (LOCAL) USAGE 0x00010031 Y (Dynamic Value) +46 0000 (GLOBAL) PHYSICAL_MAXIMUM 0x0000 (0) <-- Redundant: PHYSICAL_MAXIMUM is already 0 <-- Info: Consider replacing 46 0000 with 44 +26 0000 (GLOBAL) LOGICAL_MAXIMUM 0x0000 (0) <-- Redundant: LOGICAL_MAXIMUM is already 0 <-- Info: Consider replacing 26 0000 with 24 +81 42 (MAIN) INPUT 0x00000042 (1 field x 16 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 1=Null 0=NonVolatile 0=Bitmap +05 0D (GLOBAL) USAGE_PAGE 0x000D Digitizer Device Page +09 48 (LOCAL) USAGE 0x000D0048 Width (Dynamic Value) +15 3A (GLOBAL) LOGICAL_MINIMUM 0x3A (58) +25 3A (GLOBAL) LOGICAL_MAXIMUM 0x3A (58) +75 08 (GLOBAL) REPORT_SIZE 0x08 (8) Number of bits per field +55 0F (GLOBAL) UNIT_EXPONENT 0x0F (Unit Value x 10⁻¹) +35 3A (GLOBAL) PHYSICAL_MINIMUM 0x3A (58) +45 3A (GLOBAL) PHYSICAL_MAXIMUM 0x3A (58) +81 03 (MAIN) INPUT 0x00000003 (1 field x 8 bits) 1=Constant 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +55 00 (GLOBAL) UNIT_EXPONENT 0x00 (Unit Value x 10⁰) <-- Info: Consider replacing 55 00 with 54 +65 00 (GLOBAL) UNIT 0x00 No unit (0=None) <-- Info: Consider replacing 65 00 with 64 +35 00 (GLOBAL) PHYSICAL_MINIMUM 0x00 (0) <-- Info: Consider replacing 35 00 with 34 +45 00 (GLOBAL) PHYSICAL_MAXIMUM 0x00 (0) <-- Info: Consider replacing 45 00 with 44 +05 0E (GLOBAL) USAGE_PAGE 0x000E Haptics Page +09 01 (LOCAL) USAGE 0x000E0001 Simple Haptic Controller (Application or Logical Collection) +A1 02 (MAIN) COLLECTION 0x02 Logical (Usage=0x000E0001: Page=Haptics Page, Usage=Simple Haptic Controller, Type=Application or Logical Collection) +15 00 (GLOBAL) LOGICAL_MINIMUM 0x00 (0) <-- Info: Consider replacing 15 00 with 14 +26 FF00 (GLOBAL) LOGICAL_MAXIMUM 0x00FF (255) +09 24 (LOCAL) USAGE 0x000E0024 Repeat Count (Dynamic Value) +B1 42 (MAIN) FEATURE 0x00000042 (1 field x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 1=Null 0=NonVolatile 0=Bitmap +09 24 (LOCAL) USAGE 0x000E0024 Repeat Count (Dynamic Value) +91 42 (MAIN) OUTPUT 0x00000042 (1 field x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 1=Null 0=NonVolatile 0=Bitmap +15 01 (GLOBAL) LOGICAL_MINIMUM 0x01 (1) +25 07 (GLOBAL) LOGICAL_MAXIMUM 0x07 (7) +09 20 (LOCAL) USAGE 0x000E0020 Auto Trigger (Dynamic Value) +B1 42 (MAIN) FEATURE 0x00000042 (1 field x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 1=Null 0=NonVolatile 0=Bitmap +09 21 (LOCAL) USAGE 0x000E0021 Manual Trigger (Dynamic Value) +91 42 (MAIN) OUTPUT 0x00000042 (1 field x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 1=Null 0=NonVolatile 0=Bitmap +25 0A (GLOBAL) LOGICAL_MAXIMUM 0x0A (10) +09 28 (LOCAL) USAGE 0x000E0028 Waveform Cutoff Time (Static Value) +B1 42 (MAIN) FEATURE 0x00000042 (1 field x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 1=Null 0=NonVolatile 0=Bitmap +75 10 (GLOBAL) REPORT_SIZE 0x10 (16) Number of bits per field +26 D007 (GLOBAL) LOGICAL_MAXIMUM 0x07D0 (2000) +09 25 (LOCAL) USAGE 0x000E0025 Retrigger Period (Dynamic Value) +B1 42 (MAIN) FEATURE 0x00000042 (1 field x 16 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 1=Null 0=NonVolatile 0=Bitmap +09 25 (LOCAL) USAGE 0x000E0025 Retrigger Period (Dynamic Value) +91 42 (MAIN) OUTPUT 0x00000042 (1 field x 16 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 1=Null 0=NonVolatile 0=Bitmap +85 02 (GLOBAL) REPORT_ID 0x02 (2) +75 20 (GLOBAL) REPORT_SIZE 0x20 (32) Number of bits per field +17 37000100 (GLOBAL) LOGICAL_MINIMUM 0x00010037 (65591) +27 37000100 (GLOBAL) LOGICAL_MAXIMUM 0x00010037 (65591) +09 22 (LOCAL) USAGE 0x000E0022 Auto Trigger Associated Control (Static Value) +B1 02 (MAIN) FEATURE 0x00000002 (1 field x 32 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +09 11 (LOCAL) USAGE 0x000E0011 Duration List (Named Array Collection) +A1 02 (MAIN) COLLECTION 0x02 Logical (Usage=0x000E0011: Page=Haptics Page, Usage=Duration List, Type=Named Array Collection) <-- Warning: USAGE type should be CL (Logical Collection) +05 0A (GLOBAL) USAGE_PAGE 0x000A Ordinal Page +95 03 (GLOBAL) REPORT_COUNT 0x03 (3) Number of fields +09 03 (LOCAL) USAGE 0x000A0003 Instance 3 (Usage Modifier Collection) +09 04 (LOCAL) USAGE 0x000A0004 Instance 4 (Usage Modifier Collection) +09 05 (LOCAL) USAGE 0x000A0005 Instance 5 (Usage Modifier Collection) +75 08 (GLOBAL) REPORT_SIZE 0x08 (8) Number of bits per field +15 00 (GLOBAL) LOGICAL_MINIMUM 0x00 (0) <-- Info: Consider replacing 15 00 with 14 +25 FF (GLOBAL) LOGICAL_MAXIMUM 0xFF (-1) +B1 02 (MAIN) FEATURE 0x00000002 (3 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap <-- Error: LOGICAL_MAXIMUM (-1) is less than LOGICAL_MINIMUM (0) +C0 (MAIN) END_COLLECTION Logical +05 0E (GLOBAL) USAGE_PAGE 0x000E Haptics Page +09 10 (LOCAL) USAGE 0x000E0010 Waveform List (Named Array Collection) +A1 02 (MAIN) COLLECTION 0x02 Logical (Usage=0x000E0010: Page=Haptics Page, Usage=Waveform List, Type=Named Array Collection) <-- Warning: USAGE type should be CL (Logical Collection) +05 0A (GLOBAL) USAGE_PAGE 0x000A Ordinal Page +95 01 (GLOBAL) REPORT_COUNT 0x01 (1) Number of fields +15 03 (GLOBAL) LOGICAL_MINIMUM 0x03 (3) +25 03 (GLOBAL) LOGICAL_MAXIMUM 0x03 (3) +36 0310 (GLOBAL) PHYSICAL_MINIMUM 0x1003 (4099) +46 0310 (GLOBAL) PHYSICAL_MAXIMUM 0x1003 (4099) +09 03 (LOCAL) USAGE 0x000A0003 Instance 3 (Usage Modifier Collection) +B1 02 (MAIN) FEATURE 0x00000002 (1 field x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +15 04 (GLOBAL) LOGICAL_MINIMUM 0x04 (4) +25 04 (GLOBAL) LOGICAL_MAXIMUM 0x04 (4) +36 0410 (GLOBAL) PHYSICAL_MINIMUM 0x1004 (4100) +46 0410 (GLOBAL) PHYSICAL_MAXIMUM 0x1004 (4100) +09 04 (LOCAL) USAGE 0x000A0004 Instance 4 (Usage Modifier Collection) +B1 02 (MAIN) FEATURE 0x00000002 (1 field x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +15 05 (GLOBAL) LOGICAL_MINIMUM 0x05 (5) +25 05 (GLOBAL) LOGICAL_MAXIMUM 0x05 (5) +36 0410 (GLOBAL) PHYSICAL_MINIMUM 0x1004 (4100) <-- Redundant: PHYSICAL_MINIMUM is already 4100 +46 0410 (GLOBAL) PHYSICAL_MAXIMUM 0x1004 (4100) <-- Redundant: PHYSICAL_MAXIMUM is already 4100 +09 05 (LOCAL) USAGE 0x000A0005 Instance 5 (Usage Modifier Collection) +B1 02 (MAIN) FEATURE 0x00000002 (1 field x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +35 00 (GLOBAL) PHYSICAL_MINIMUM 0x00 (0) <-- Info: Consider replacing 35 00 with 34 +45 00 (GLOBAL) PHYSICAL_MAXIMUM 0x00 (0) <-- Info: Consider replacing 45 00 with 44 +C0 (MAIN) END_COLLECTION Logical +C0 (MAIN) END_COLLECTION Logical +C0 (MAIN) END_COLLECTION Physical +C0 (MAIN) END_COLLECTION Logical +C0 (MAIN) END_COLLECTION Application +*/ + +//-------------------------------------------------------------------------------- +// Generic Desktop Page featureReport 01 (Device <-> Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x01 (1) + // Collection: CA:SystemMulti-axisController CL:Puck CP: CL: + uint16_t GD_SystemMultiaxisControllerPuckResolutionMultiplier; // Usage 0x00010048: Resolution Multiplier, Value = 0 to 3600, Physical = Value + // Collection: CA:SystemMulti-axisController CL:Puck CP: CL:SimpleHapticController + uint8_t HAP_SystemMultiaxisControllerPuckSimpleHapticControllerRepeatCount; // Usage 0x000E0024: Repeat Count, Value = 0 to 255 + uint8_t HAP_SystemMultiaxisControllerPuckSimpleHapticControllerAutoTrigger; // Usage 0x000E0020: Auto Trigger, Value = 1 to 7 + uint8_t HAP_SystemMultiaxisControllerPuckSimpleHapticControllerWaveformCutoffTime; // Usage 0x000E0028: Waveform Cutoff Time, Value = 1 to 10 + uint16_t HAP_SystemMultiaxisControllerPuckSimpleHapticControllerRetriggerPeriod; // Usage 0x000E0025: Retrigger Period, Value = 1 to 2000 +} featureReport01_t; + + +//-------------------------------------------------------------------------------- +// Haptics Page featureReport 02 (Device <-> Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x02 (2) + // Collection: CA:SystemMulti-axisController CL:Puck CP: CL:SimpleHapticController + uint32_t HAP_SystemMultiaxisControllerPuckSimpleHapticControllerAutoTriggerAssociatedControl; // Usage 0x000E0022: Auto Trigger Associated Control, Value = 65591 to 65591 + // Collection: CA:SystemMulti-axisController CL:Puck CP: CL:SimpleHapticController CL:DurationList + uint8_t ORD_SystemMultiaxisControllerPuckSimpleHapticControllerDurationListInstance3; // Usage 0x000A0003: Instance 3, Value = 0 to -1 + uint8_t ORD_SystemMultiaxisControllerPuckSimpleHapticControllerDurationListInstance4; // Usage 0x000A0004: Instance 4, Value = 0 to -1 + uint8_t ORD_SystemMultiaxisControllerPuckSimpleHapticControllerDurationListInstance5; // Usage 0x000A0005: Instance 5, Value = 0 to -1 + // Collection: CA:SystemMulti-axisController CL:Puck CP: CL:SimpleHapticController CL:WaveformList + uint8_t ORD_SystemMultiaxisControllerPuckSimpleHapticControllerWaveformListInstance3; // Usage 0x000A0003: Instance 3, Value = 3 to 3, Physical = ((Value - 3) x 0 / 0 + 4099) + uint8_t ORD_SystemMultiaxisControllerPuckSimpleHapticControllerWaveformListInstance4; // Usage 0x000A0004: Instance 4, Value = 4 to 4, Physical = ((Value - 4) x 0 / 0 + 4100) + uint8_t ORD_SystemMultiaxisControllerPuckSimpleHapticControllerWaveformListInstance5; // Usage 0x000A0005: Instance 5, Value = 5 to 5, Physical = ((Value - 5) x 0 / 0 + 4100) +} featureReport02_t; + + +//-------------------------------------------------------------------------------- +// Button Page inputReport 01 (Device --> Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x01 (1) + // Collection: CA:SystemMulti-axisController CL:Puck CP: + uint8_t BTN_SystemMultiaxisControllerPuckButton1 : 1; // Usage 0x00090001: Button 1 Primary/trigger, Value = 0 to 1 + uint8_t DIG_SystemMultiaxisControllerPuckTouch : 1; // Usage 0x000D0033: Touch, Value = 0 to 1 + uint8_t : 1; // Pad + uint8_t : 1; // Pad + uint8_t : 1; // Pad + uint8_t : 1; // Pad + uint8_t : 1; // Pad + uint8_t : 1; // Pad + // Collection: CA:SystemMulti-axisController CL:Puck CP: CL: + int16_t GD_SystemMultiaxisControllerPuckDial; // Usage 0x00010037: Dial, Value = -32767 to 32767 + // Collection: CA:SystemMulti-axisController CL:Puck CP: + uint16_t GD_SystemMultiaxisControllerPuckX; // Usage 0x00010030: X, Value = 0 to 0, Physical = Value x 0 / 0 in 10⁻⁴ m units + uint16_t GD_SystemMultiaxisControllerPuckY; // Usage 0x00010031: Y, Value = 0 to 0, Physical = Value x 0 / 0 in 10⁻⁴ m units + uint8_t DIG_SystemMultiaxisControllerPuckWidth; // Usage 0x000D0048: Width, Value = 58 to 58, Physical = ((Value - 58) x 0 / 0 + 58) in 10⁻³ m units +} inputReport01_t; + + +//-------------------------------------------------------------------------------- +// Haptics Page outputReport 01 (Device <-- Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x01 (1) + // Collection: CA:SystemMulti-axisController CL:Puck CP: CL:SimpleHapticController + uint8_t HAP_SystemMultiaxisControllerPuckSimpleHapticControllerRepeatCount; // Usage 0x000E0024: Repeat Count, Value = 0 to 255 + uint8_t HAP_SystemMultiaxisControllerPuckSimpleHapticControllerManualTrigger; // Usage 0x000E0021: Manual Trigger, Value = 1 to 7 + uint16_t HAP_SystemMultiaxisControllerPuckSimpleHapticControllerRetriggerPeriod; // Usage 0x000E0025: Retrigger Period, Value = 1 to 2000 +} outputReport01_t; + + +//-------------------------------------------------------------------------------- +// Decoded Application Collection +//-------------------------------------------------------------------------------- + +/* +06 07FF (GLOBAL) USAGE_PAGE 0xFF07 Vendor-defined +09 70 (LOCAL) USAGE 0xFF070070 <-- Warning: Undocumented usage (document it by inserting 0070 into file FF07.conf) <-- Error: Usage Modifier COLLECTION item (A1 06) expected for USAGE 0x000A0005 Instance 5 (Usage Modifier Collection) +A1 01 (MAIN) COLLECTION 0x01 Application (Usage=0xFF070070: Page=Vendor-defined, Usage=, Type=) <-- Error: COLLECTION must be preceded by a known USAGE +85 30 (GLOBAL) REPORT_ID 0x30 (48) '0' +15 00 (GLOBAL) LOGICAL_MINIMUM 0x00 (0) <-- Info: Consider replacing 15 00 with 14 +25 FF (GLOBAL) LOGICAL_MAXIMUM 0xFF (-1) +95 01 (GLOBAL) REPORT_COUNT 0x01 (1) Number of fields <-- Redundant: REPORT_COUNT is already 1 +75 08 (GLOBAL) REPORT_SIZE 0x08 (8) Number of bits per field <-- Redundant: REPORT_SIZE is already 8 +09 00 (LOCAL) USAGE 0xFF070000 <-- Info: Consider replacing 09 00 with 08 +91 02 (MAIN) OUTPUT 0x00000002 (1 field x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap <-- Error: LOGICAL_MAXIMUM (-1) is less than LOGICAL_MINIMUM (0) +C0 (MAIN) END_COLLECTION Application +*/ + +//-------------------------------------------------------------------------------- +// Vendor-defined outputReport 30 (Device <-- Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x30 (48) '0' + // Collection: CA: + uint8_t VEN_0000; // Usage 0xFF070000: , Value = 0 to -1 +} outputReport30_t; + + +//-------------------------------------------------------------------------------- +// Decoded Application Collection +//-------------------------------------------------------------------------------- + +/* +09 71 (LOCAL) USAGE 0xFF070071 <-- Warning: Undocumented usage (document it by inserting 0071 into file FF07.conf) +A1 01 (MAIN) COLLECTION 0x01 Application (Usage=0xFF070071: Page=Vendor-defined, Usage=, Type=) <-- Error: COLLECTION must be preceded by a known USAGE +15 00 (GLOBAL) LOGICAL_MINIMUM 0x00 (0) <-- Redundant: LOGICAL_MINIMUM is already 0 <-- Info: Consider replacing 15 00 with 14 +25 FF (GLOBAL) LOGICAL_MAXIMUM 0xFF (-1) <-- Redundant: LOGICAL_MAXIMUM is already -1 +75 08 (GLOBAL) REPORT_SIZE 0x08 (8) Number of bits per field <-- Redundant: REPORT_SIZE is already 8 +95 48 (GLOBAL) REPORT_COUNT 0x48 (72) Number of fields +85 2A (GLOBAL) REPORT_ID 0x2A (42) +09 C6 (LOCAL) USAGE 0xFF0700C6 <-- Warning: Undocumented usage (document it by inserting 00C6 into file FF07.conf) +82 0201 (MAIN) INPUT 0x00000102 (72 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 1=Buffer <-- Error: LOGICAL_MAXIMUM (-1) is less than LOGICAL_MINIMUM (0) +09 C7 (LOCAL) USAGE 0xFF0700C7 <-- Warning: Undocumented usage (document it by inserting 00C7 into file FF07.conf) +92 0201 (MAIN) OUTPUT 0x00000102 (72 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 1=Buffer <-- Error: LOGICAL_MAXIMUM (-1) is less than LOGICAL_MINIMUM (0) +95 34 (GLOBAL) REPORT_COUNT 0x34 (52) Number of fields +09 C8 (LOCAL) USAGE 0xFF0700C8 <-- Warning: Undocumented usage (document it by inserting 00C8 into file FF07.conf) +B2 0301 (MAIN) FEATURE 0x00000103 (52 fields x 8 bits) 1=Constant 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 1=Buffer +85 2B (GLOBAL) REPORT_ID 0x2B (43) +09 C9 (LOCAL) USAGE 0xFF0700C9 <-- Warning: Undocumented usage (document it by inserting 00C9 into file FF07.conf) +82 0201 (MAIN) INPUT 0x00000102 (52 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 1=Buffer <-- Error: LOGICAL_MAXIMUM (-1) is less than LOGICAL_MINIMUM (0) +09 CA (LOCAL) USAGE 0xFF0700CA <-- Warning: Undocumented usage (document it by inserting 00CA into file FF07.conf) +92 0201 (MAIN) OUTPUT 0x00000102 (52 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 1=Buffer <-- Error: LOGICAL_MAXIMUM (-1) is less than LOGICAL_MINIMUM (0) +09 CB (LOCAL) USAGE 0xFF0700CB <-- Warning: Undocumented usage (document it by inserting 00CB into file FF07.conf) +B2 0201 (MAIN) FEATURE 0x00000102 (52 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 1=Buffer <-- Error: LOGICAL_MAXIMUM (-1) is less than LOGICAL_MINIMUM (0) +17 00000080 (GLOBAL) LOGICAL_MINIMUM 0x80000000 (-2147483648) +27 FFFFFF7F (GLOBAL) LOGICAL_MAXIMUM 0x7FFFFFFF (2147483647) +75 20 (GLOBAL) REPORT_SIZE 0x20 (32) Number of bits per field +95 04 (GLOBAL) REPORT_COUNT 0x04 (4) Number of fields +85 2C (GLOBAL) REPORT_ID 0x2C (44) +19 CC (LOCAL) USAGE_MINIMUM 0xFF0700CC <-- Warning: Undocumented usage (document it by inserting 00CC into file FF07.conf) +29 CF (LOCAL) USAGE_MAXIMUM 0xFF0700CF <-- Warning: Undocumented usage (document it by inserting 00CF into file FF07.conf) +81 02 (MAIN) INPUT 0x00000002 (4 fields x 32 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +95 04 (GLOBAL) REPORT_COUNT 0x04 (4) Number of fields <-- Redundant: REPORT_COUNT is already 4 +85 2D (GLOBAL) REPORT_ID 0x2D (45) +19 D8 (LOCAL) USAGE_MINIMUM 0xFF0700D8 <-- Warning: Undocumented usage (document it by inserting 00D8 into file FF07.conf) +29 DB (LOCAL) USAGE_MAXIMUM 0xFF0700DB <-- Warning: Undocumented usage (document it by inserting 00DB into file FF07.conf) +81 02 (MAIN) INPUT 0x00000002 (4 fields x 32 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +95 04 (GLOBAL) REPORT_COUNT 0x04 (4) Number of fields <-- Redundant: REPORT_COUNT is already 4 +19 DC (LOCAL) USAGE_MINIMUM 0xFF0700DC <-- Warning: Undocumented usage (document it by inserting 00DC into file FF07.conf) +29 DF (LOCAL) USAGE_MAXIMUM 0xFF0700DF <-- Warning: Undocumented usage (document it by inserting 00DF into file FF07.conf) +91 02 (MAIN) OUTPUT 0x00000002 (4 fields x 32 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +19 E0 (LOCAL) USAGE_MINIMUM 0xFF0700E0 <-- Warning: Undocumented usage (document it by inserting 00E0 into file FF07.conf) +29 E3 (LOCAL) USAGE_MAXIMUM 0xFF0700E3 <-- Warning: Undocumented usage (document it by inserting 00E3 into file FF07.conf) +B1 02 (MAIN) FEATURE 0x00000002 (4 fields x 32 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +85 2E (GLOBAL) REPORT_ID 0x2E (46) +19 E4 (LOCAL) USAGE_MINIMUM 0xFF0700E4 <-- Warning: Undocumented usage (document it by inserting 00E4 into file FF07.conf) +29 E7 (LOCAL) USAGE_MAXIMUM 0xFF0700E7 <-- Warning: Undocumented usage (document it by inserting 00E7 into file FF07.conf) +81 02 (MAIN) INPUT 0x00000002 (4 fields x 32 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +19 E8 (LOCAL) USAGE_MINIMUM 0xFF0700E8 <-- Warning: Undocumented usage (document it by inserting 00E8 into file FF07.conf) +29 EB (LOCAL) USAGE_MAXIMUM 0xFF0700EB <-- Warning: Undocumented usage (document it by inserting 00EB into file FF07.conf) +91 02 (MAIN) OUTPUT 0x00000002 (4 fields x 32 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +95 0B (GLOBAL) REPORT_COUNT 0x0B (11) Number of fields +19 EC (LOCAL) USAGE_MINIMUM 0xFF0700EC <-- Warning: Undocumented usage (document it by inserting 00EC into file FF07.conf) +29 EF (LOCAL) USAGE_MAXIMUM 0xFF0700EF <-- Warning: Undocumented usage (document it by inserting 00EF into file FF07.conf) +B1 02 (MAIN) FEATURE 0x00000002 (11 fields x 32 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +95 04 (GLOBAL) REPORT_COUNT 0x04 (4) Number of fields +85 2F (GLOBAL) REPORT_ID 0x2F (47) +19 F0 (LOCAL) USAGE_MINIMUM 0xFF0700F0 <-- Warning: Undocumented usage (document it by inserting 00F0 into file FF07.conf) +29 F3 (LOCAL) USAGE_MAXIMUM 0xFF0700F3 <-- Warning: Undocumented usage (document it by inserting 00F3 into file FF07.conf) +81 02 (MAIN) INPUT 0x00000002 (4 fields x 32 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +19 F4 (LOCAL) USAGE_MINIMUM 0xFF0700F4 <-- Warning: Undocumented usage (document it by inserting 00F4 into file FF07.conf) +29 F7 (LOCAL) USAGE_MAXIMUM 0xFF0700F7 <-- Warning: Undocumented usage (document it by inserting 00F7 into file FF07.conf) +91 02 (MAIN) OUTPUT 0x00000002 (4 fields x 32 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +19 F8 (LOCAL) USAGE_MINIMUM 0xFF0700F8 <-- Warning: Undocumented usage (document it by inserting 00F8 into file FF07.conf) +29 FB (LOCAL) USAGE_MAXIMUM 0xFF0700FB <-- Warning: Undocumented usage (document it by inserting 00FB into file FF07.conf) +B1 02 (MAIN) FEATURE 0x00000002 (4 fields x 32 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +C0 (MAIN) END_COLLECTION Application +*/ + +//-------------------------------------------------------------------------------- +// Vendor-defined featureReport 2A (Device <-> Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x2A (42) + // Collection: CA: + uint8_t VEN_00C8[52]; // Usage 0xFF0700C8: , Value = 0 to -1 +} featureReport2A_t; + + +//-------------------------------------------------------------------------------- +// Vendor-defined featureReport 2B (Device <-> Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x2B (43) + // Collection: CA: + uint8_t VEN_00CB[52]; // Usage 0xFF0700CB: , Value = 0 to -1 +} featureReport2B_t; + + +//-------------------------------------------------------------------------------- +// Vendor-defined featureReport 2D (Device <-> Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x2D (45) + // Collection: CA: + int32_t VEN_00E0; // Usage 0xFF0700E0: , Value = -2147483648 to 2147483647 + int32_t VEN_00E1; // Usage 0xFF0700E1: , Value = -2147483648 to 2147483647 + int32_t VEN_00E2; // Usage 0xFF0700E2: , Value = -2147483648 to 2147483647 + int32_t VEN_00E3; // Usage 0xFF0700E3: , Value = -2147483648 to 2147483647 +} featureReport2D_t; + + +//-------------------------------------------------------------------------------- +// Vendor-defined featureReport 2E (Device <-> Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x2E (46) + // Collection: CA: + int32_t VEN_00EC; // Usage 0xFF0700EC: , Value = -2147483648 to 2147483647 + int32_t VEN_00ED; // Usage 0xFF0700ED: , Value = -2147483648 to 2147483647 + int32_t VEN_00EE; // Usage 0xFF0700EE: , Value = -2147483648 to 2147483647 + int32_t VEN_00EF[8]; // Usage 0xFF0700EF: , Value = -2147483648 to 2147483647 +} featureReport2E_t; + + +//-------------------------------------------------------------------------------- +// Vendor-defined featureReport 2F (Device <-> Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x2F (47) + // Collection: CA: + int32_t VEN_00F8; // Usage 0xFF0700F8: , Value = -2147483648 to 2147483647 + int32_t VEN_00F9; // Usage 0xFF0700F9: , Value = -2147483648 to 2147483647 + int32_t VEN_00FA; // Usage 0xFF0700FA: , Value = -2147483648 to 2147483647 + int32_t VEN_00FB; // Usage 0xFF0700FB: , Value = -2147483648 to 2147483647 +} featureReport2F_t; + + +//-------------------------------------------------------------------------------- +// Vendor-defined inputReport 2A (Device --> Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x2A (42) + // Collection: CA: + uint8_t VEN_00C6[72]; // Usage 0xFF0700C6: , Value = 0 to -1 +} inputReport2A_t; + + +//-------------------------------------------------------------------------------- +// Vendor-defined inputReport 2B (Device --> Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x2B (43) + // Collection: CA: + uint8_t VEN_00C9[52]; // Usage 0xFF0700C9: , Value = 0 to -1 +} inputReport2B_t; + + +//-------------------------------------------------------------------------------- +// Vendor-defined inputReport 2C (Device --> Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x2C (44) + // Collection: CA: + int32_t VEN_00CC; // Usage 0xFF0700CC: , Value = -2147483648 to 2147483647 + int32_t VEN_00CD; // Usage 0xFF0700CD: , Value = -2147483648 to 2147483647 + int32_t VEN_00CE; // Usage 0xFF0700CE: , Value = -2147483648 to 2147483647 + int32_t VEN_00CF; // Usage 0xFF0700CF: , Value = -2147483648 to 2147483647 +} inputReport2C_t; + + +//-------------------------------------------------------------------------------- +// Vendor-defined inputReport 2D (Device --> Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x2D (45) + // Collection: CA: + int32_t VEN_00D8; // Usage 0xFF0700D8: , Value = -2147483648 to 2147483647 + int32_t VEN_00D9; // Usage 0xFF0700D9: , Value = -2147483648 to 2147483647 + int32_t VEN_00DA; // Usage 0xFF0700DA: , Value = -2147483648 to 2147483647 + int32_t VEN_00DB; // Usage 0xFF0700DB: , Value = -2147483648 to 2147483647 +} inputReport2D_t; + + +//-------------------------------------------------------------------------------- +// Vendor-defined inputReport 2E (Device --> Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x2E (46) + // Collection: CA: + int32_t VEN_00E4; // Usage 0xFF0700E4: , Value = -2147483648 to 2147483647 + int32_t VEN_00E5; // Usage 0xFF0700E5: , Value = -2147483648 to 2147483647 + int32_t VEN_00E6; // Usage 0xFF0700E6: , Value = -2147483648 to 2147483647 + int32_t VEN_00E7; // Usage 0xFF0700E7: , Value = -2147483648 to 2147483647 +} inputReport2E_t; + + +//-------------------------------------------------------------------------------- +// Vendor-defined inputReport 2F (Device --> Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x2F (47) + // Collection: CA: + int32_t VEN_00F0; // Usage 0xFF0700F0: , Value = -2147483648 to 2147483647 + int32_t VEN_00F1; // Usage 0xFF0700F1: , Value = -2147483648 to 2147483647 + int32_t VEN_00F2; // Usage 0xFF0700F2: , Value = -2147483648 to 2147483647 + int32_t VEN_00F3; // Usage 0xFF0700F3: , Value = -2147483648 to 2147483647 +} inputReport2F_t; + + +//-------------------------------------------------------------------------------- +// Vendor-defined outputReport 2A (Device <-- Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x2A (42) + // Collection: CA: + uint8_t VEN_00C7[72]; // Usage 0xFF0700C7: , Value = 0 to -1 +} outputReport2A_t; + + +//-------------------------------------------------------------------------------- +// Vendor-defined outputReport 2B (Device <-- Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x2B (43) + // Collection: CA: + uint8_t VEN_00CA[52]; // Usage 0xFF0700CA: , Value = 0 to -1 +} outputReport2B_t; + + +//-------------------------------------------------------------------------------- +// Vendor-defined outputReport 2D (Device <-- Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x2D (45) + // Collection: CA: + int32_t VEN_00DC; // Usage 0xFF0700DC: , Value = -2147483648 to 2147483647 + int32_t VEN_00DD; // Usage 0xFF0700DD: , Value = -2147483648 to 2147483647 + int32_t VEN_00DE; // Usage 0xFF0700DE: , Value = -2147483648 to 2147483647 + int32_t VEN_00DF; // Usage 0xFF0700DF: , Value = -2147483648 to 2147483647 +} outputReport2D_t; + + +//-------------------------------------------------------------------------------- +// Vendor-defined outputReport 2E (Device <-- Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x2E (46) + // Collection: CA: + int32_t VEN_00E8; // Usage 0xFF0700E8: , Value = -2147483648 to 2147483647 + int32_t VEN_00E9; // Usage 0xFF0700E9: , Value = -2147483648 to 2147483647 + int32_t VEN_00EA; // Usage 0xFF0700EA: , Value = -2147483648 to 2147483647 + int32_t VEN_00EB; // Usage 0xFF0700EB: , Value = -2147483648 to 2147483647 +} outputReport2E_t; + + +//-------------------------------------------------------------------------------- +// Vendor-defined outputReport 2F (Device <-- Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x2F (47) + // Collection: CA: + int32_t VEN_00F4; // Usage 0xFF0700F4: , Value = -2147483648 to 2147483647 + int32_t VEN_00F5; // Usage 0xFF0700F5: , Value = -2147483648 to 2147483647 + int32_t VEN_00F6; // Usage 0xFF0700F6: , Value = -2147483648 to 2147483647 + int32_t VEN_00F7; // Usage 0xFF0700F7: , Value = -2147483648 to 2147483647 +} outputReport2F_t; + + +//-------------------------------------------------------------------------------- +// Decoded Application Collection +//-------------------------------------------------------------------------------- + +/* +05 01 (GLOBAL) USAGE_PAGE 0x0001 Generic Desktop Page +09 80 (LOCAL) USAGE 0x00010080 System Control (Application Collection) +A1 01 (MAIN) COLLECTION 0x01 Application (Usage=0x00010080: Page=Generic Desktop Page, Usage=System Control, Type=Application Collection) +85 32 (GLOBAL) REPORT_ID 0x32 (50) '2' +09 82 (LOCAL) USAGE 0x00010082 System Sleep (One Shot Control) +09 83 (LOCAL) USAGE 0x00010083 System Wake Up (One Shot Control) +15 00 (GLOBAL) LOGICAL_MINIMUM 0x00 (0) <-- Info: Consider replacing 15 00 with 14 +25 01 (GLOBAL) LOGICAL_MAXIMUM 0x01 (1) +95 02 (GLOBAL) REPORT_COUNT 0x02 (2) Number of fields +75 01 (GLOBAL) REPORT_SIZE 0x01 (1) Number of bits per field +81 02 (MAIN) INPUT 0x00000002 (2 fields x 1 bit) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +95 06 (GLOBAL) REPORT_COUNT 0x06 (6) Number of fields +81 03 (MAIN) INPUT 0x00000003 (6 fields x 1 bit) 1=Constant 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap +C0 (MAIN) END_COLLECTION Application +*/ + +//-------------------------------------------------------------------------------- +// Generic Desktop Page inputReport 32 (Device --> Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x32 (50) '2' + // Collection: CA:SystemControl + uint8_t GD_SystemControlSystemSleep : 1; // Usage 0x00010082: System Sleep, Value = 0 to 1 + uint8_t GD_SystemControlSystemWakeUp : 1; // Usage 0x00010083: System Wake Up, Value = 0 to 1 + uint8_t : 1; // Pad + uint8_t : 1; // Pad + uint8_t : 1; // Pad + uint8_t : 1; // Pad + uint8_t : 1; // Pad + uint8_t : 1; // Pad +} inputReport32_t; + + +//-------------------------------------------------------------------------------- +// Decoded Application Collection +//-------------------------------------------------------------------------------- + +/* +09 72 (LOCAL) USAGE 0x00010072 <-- Warning: Undocumented usage (document it by inserting 0072 into file 0001.conf) +A1 01 (MAIN) COLLECTION 0x01 Application (Usage=0x00010072: Page=Generic Desktop Page, Usage=, Type=) <-- Error: COLLECTION must be preceded by a known USAGE <-- Warning: USAGE type should be CA (Application Collection) +85 31 (GLOBAL) REPORT_ID 0x31 (49) '1' +95 0A (GLOBAL) REPORT_COUNT 0x0A (10) Number of fields +75 08 (GLOBAL) REPORT_SIZE 0x08 (8) Number of bits per field +15 00 (GLOBAL) LOGICAL_MINIMUM 0x00 (0) <-- Redundant: LOGICAL_MINIMUM is already 0 <-- Info: Consider replacing 15 00 with 14 +25 FF (GLOBAL) LOGICAL_MAXIMUM 0xFF (-1) +09 C6 (LOCAL) USAGE 0x000100C6 Wireless Radio Button (On/Off Control) +81 02 (MAIN) INPUT 0x00000002 (10 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap <-- Error: LOGICAL_MAXIMUM (-1) is less than LOGICAL_MINIMUM (0) +09 C7 (LOCAL) USAGE 0x000100C7 Wireless Radio LED (On/Off Control) +91 02 (MAIN) OUTPUT 0x00000002 (10 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap <-- Error: LOGICAL_MAXIMUM (-1) is less than LOGICAL_MINIMUM (0) +C0 (MAIN) END_COLLECTION Application +*/ + +//-------------------------------------------------------------------------------- +// Generic Desktop Page inputReport 31 (Device --> Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x31 (49) '1' + // Collection: CA: + uint8_t GD_WirelessRadioButton[10]; // Usage 0x000100C6: Wireless Radio Button, Value = 0 to -1 +} inputReport31_t; + + +//-------------------------------------------------------------------------------- +// Generic Desktop Page outputReport 31 (Device <-- Host) +//-------------------------------------------------------------------------------- + +typedef struct +{ + uint8_t reportId; // Report ID = 0x31 (49) '1' + // Collection: CA: + uint8_t GD_WirelessRadioLed[10]; // Usage 0x000100C7: Wireless Radio LED, Value = 0 to -1 +} outputReport31_t; + diff --git a/src/common.rs b/src/common.rs index 8c55803..1f8d4c1 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,38 +1,3 @@ -pub enum DialDir { - Left, - Right, -} - -pub struct ThresholdHelper { - sensitivity: i32, - pos: i32, -} - -impl ThresholdHelper { - pub fn new(sensitivity: i32) -> ThresholdHelper { - ThresholdHelper { - sensitivity, - pos: 0, - } - } - - pub fn update(&mut self, delta: i32) -> Option { - self.pos += delta; - - if self.pos > self.sensitivity { - self.pos -= self.sensitivity; - return Some(DialDir::Right); - } - - if self.pos < -self.sensitivity { - self.pos += self.sensitivity; - return Some(DialDir::Left); - } - - None - } -} - use notify_rust::error::Result as NotifyResult; use notify_rust::{Hint, Notification, NotificationHandle, Timeout}; diff --git a/src/controller/controls/dpad.rs b/src/controller/controls/dpad.rs index 694bfe1..0560f43 100644 --- a/src/controller/controls/dpad.rs +++ b/src/controller/controls/dpad.rs @@ -4,6 +4,7 @@ use std::thread::JoinHandle; use std::time::Duration; use crate::controller::ControlMode; +use crate::dial_device::DialHaptics; use crate::fake_input::FakeInput; use crate::DynResult; @@ -106,12 +107,6 @@ impl Drop for DPad { } } -impl Default for DPad { - fn default() -> Self { - Self::new() - } -} - impl DPad { pub fn new() -> DPad { let (msg_tx, msg_rx) = mpsc::channel(); @@ -128,17 +123,22 @@ impl DPad { } impl ControlMode for DPad { - fn on_btn_press(&mut self) -> DynResult<()> { + fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()> { + haptics.set_mode(false, Some(3600))?; + Ok(()) + } + + fn on_btn_press(&mut self, _: &DialHaptics) -> DynResult<()> { eprintln!("space"); self.fake_input.key_click(&[EV_KEY::KEY_SPACE])?; Ok(()) } - fn on_btn_release(&mut self) -> DynResult<()> { + fn on_btn_release(&mut self, _: &DialHaptics) -> DynResult<()> { Ok(()) } - fn on_dial(&mut self, delta: i32) -> DynResult<()> { + fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> DynResult<()> { self.msg.send(Msg::Delta(delta))?; Ok(()) } diff --git a/src/controller/controls/media.rs b/src/controller/controls/media.rs index b50ca3d..f993fe7 100644 --- a/src/controller/controls/media.rs +++ b/src/controller/controls/media.rs @@ -1,47 +1,44 @@ -use crate::common::{DialDir, ThresholdHelper}; use crate::controller::ControlMode; +use crate::dial_device::DialHaptics; use crate::fake_input::FakeInput; use crate::DynResult; use evdev_rs::enums::EV_KEY; pub struct Media { - thresh: ThresholdHelper, - fake_input: FakeInput, } impl Media { - pub fn new(sensitivity: i32) -> Media { + pub fn new() -> Media { Media { - thresh: ThresholdHelper::new(sensitivity), - fake_input: FakeInput::new(), } } } impl ControlMode for Media { - fn on_btn_press(&mut self) -> DynResult<()> { + fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()> { + haptics.set_mode(false, Some(36))?; Ok(()) } - fn on_btn_release(&mut self) -> DynResult<()> { + fn on_btn_press(&mut self, _: &DialHaptics) -> DynResult<()> { + Ok(()) + } + + fn on_btn_release(&mut self, _: &DialHaptics) -> DynResult<()> { self.fake_input.key_click(&[EV_KEY::KEY_PLAYPAUSE])?; Ok(()) } - fn on_dial(&mut self, delta: i32) -> DynResult<()> { - match self.thresh.update(delta) { - Some(DialDir::Left) => { - eprintln!("next song"); - self.fake_input.key_click(&[EV_KEY::KEY_NEXTSONG])?; - } - Some(DialDir::Right) => { - eprintln!("last song"); - self.fake_input.key_click(&[EV_KEY::KEY_PREVIOUSSONG])?; - } - None => {} + fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> DynResult<()> { + if delta > 0 { + eprintln!("last song"); + self.fake_input.key_click(&[EV_KEY::KEY_PREVIOUSSONG])?; + } else { + eprintln!("next song"); + self.fake_input.key_click(&[EV_KEY::KEY_NEXTSONG])?; } Ok(()) } diff --git a/src/controller/controls/mod.rs b/src/controller/controls/mod.rs index c06cb95..dad577d 100644 --- a/src/controller/controls/mod.rs +++ b/src/controller/controls/mod.rs @@ -1,9 +1,11 @@ mod dpad; mod media; +mod null; mod scroll_zoom; mod volume; pub use self::dpad::*; pub use self::media::*; +pub use self::null::*; pub use self::scroll_zoom::*; pub use self::volume::*; diff --git a/src/controller/controls/null.rs b/src/controller/controls/null.rs new file mode 100644 index 0000000..02d38a3 --- /dev/null +++ b/src/controller/controls/null.rs @@ -0,0 +1,30 @@ +use crate::controller::ControlMode; +use crate::dial_device::DialHaptics; +use crate::DynResult; + +pub struct Null {} + +impl Null { + pub fn new() -> Null { + Null {} + } +} + +impl ControlMode for Null { + fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()> { + haptics.set_mode(false, Some(0))?; + Ok(()) + } + + fn on_btn_press(&mut self, _haptics: &DialHaptics) -> DynResult<()> { + Ok(()) + } + + fn on_btn_release(&mut self, _haptics: &DialHaptics) -> DynResult<()> { + Ok(()) + } + + fn on_dial(&mut self, _haptics: &DialHaptics, _delta: i32) -> DynResult<()> { + Ok(()) + } +} diff --git a/src/controller/controls/scroll_zoom.rs b/src/controller/controls/scroll_zoom.rs index 815b0a5..9273888 100644 --- a/src/controller/controls/scroll_zoom.rs +++ b/src/controller/controls/scroll_zoom.rs @@ -1,21 +1,20 @@ -use crate::common::{action_notification, DialDir, ThresholdHelper}; +use crate::common::action_notification; use crate::controller::ControlMode; +use crate::dial_device::DialHaptics; use crate::fake_input::{FakeInput, ScrollStep}; use crate::DynResult; use evdev_rs::enums::EV_KEY; pub struct ScrollZoom { - thresh: ThresholdHelper, zoom: bool, fake_input: FakeInput, } impl ScrollZoom { - pub fn new(sensitivity: i32) -> ScrollZoom { + pub fn new() -> ScrollZoom { ScrollZoom { - thresh: ThresholdHelper::new(sensitivity), zoom: false, fake_input: FakeInput::new(), @@ -23,46 +22,55 @@ impl ScrollZoom { } } +const ZOOM_SENSITIVITY: u16 = 36; +const SCROLL_SENSITIVITY: u16 = 90; + impl ControlMode for ScrollZoom { - fn on_btn_press(&mut self) -> DynResult<()> { + fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()> { + haptics.set_mode(false, Some(SCROLL_SENSITIVITY))?; Ok(()) } - fn on_btn_release(&mut self) -> DynResult<()> { + fn on_btn_press(&mut self, _: &DialHaptics) -> DynResult<()> { + Ok(()) + } + + fn on_btn_release(&mut self, haptics: &DialHaptics) -> DynResult<()> { self.zoom = !self.zoom; + haptics.buzz(1)?; + if self.zoom { action_notification("Zoom Mode", "zoom-in")?; + haptics.set_mode(false, Some(ZOOM_SENSITIVITY))?; } else { action_notification("ScrollZoom Mode", "input-mouse")?; + haptics.set_mode(false, Some(SCROLL_SENSITIVITY))?; } Ok(()) } - fn on_dial(&mut self, delta: i32) -> DynResult<()> { - match self.thresh.update(delta) { - None => {} - Some(DialDir::Left) => { - if self.zoom { - eprintln!("zoom out"); - self.fake_input - .key_click(&[EV_KEY::KEY_LEFTCTRL, EV_KEY::KEY_MINUS])?; - } else { - eprintln!("scroll up"); - self.fake_input.scroll_step(ScrollStep::Up)?; - } + fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> DynResult<()> { + if delta > 0 { + if self.zoom { + eprintln!("zoom in"); + self.fake_input + .key_click(&[EV_KEY::KEY_LEFTCTRL, EV_KEY::KEY_EQUAL])?; + } else { + eprintln!("scroll down"); + self.fake_input.scroll_step(ScrollStep::Down)?; } - Some(DialDir::Right) => { - if self.zoom { - eprintln!("zoom in"); - self.fake_input - .key_click(&[EV_KEY::KEY_LEFTCTRL, EV_KEY::KEY_EQUAL])?; - } else { - eprintln!("scroll down"); - self.fake_input.scroll_step(ScrollStep::Down)?; - } + } else { + if self.zoom { + eprintln!("zoom out"); + self.fake_input + .key_click(&[EV_KEY::KEY_LEFTCTRL, EV_KEY::KEY_MINUS])?; + } else { + eprintln!("scroll up"); + self.fake_input.scroll_step(ScrollStep::Up)?; } } + Ok(()) } } diff --git a/src/controller/controls/volume.rs b/src/controller/controls/volume.rs index af4081d..89274e6 100644 --- a/src/controller/controls/volume.rs +++ b/src/controller/controls/volume.rs @@ -1,53 +1,51 @@ -use crate::common::{DialDir, ThresholdHelper}; use crate::controller::ControlMode; +use crate::dial_device::DialHaptics; use crate::fake_input::FakeInput; use crate::DynResult; use evdev_rs::enums::EV_KEY; pub struct Volume { - thresh: ThresholdHelper, - fake_input: FakeInput, } impl Volume { - pub fn new(sensitivity: i32) -> Volume { + pub fn new() -> Volume { Volume { - thresh: ThresholdHelper::new(sensitivity), - fake_input: FakeInput::new(), } } } impl ControlMode for Volume { - fn on_btn_press(&mut self) -> DynResult<()> { + fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()> { + haptics.set_mode(false, Some(36 * 2))?; + Ok(()) + } + + fn on_btn_press(&mut self, _: &DialHaptics) -> DynResult<()> { // TODO: support double-click to mute Ok(()) } - fn on_btn_release(&mut self) -> DynResult<()> { + fn on_btn_release(&mut self, _: &DialHaptics) -> DynResult<()> { eprintln!("play/pause"); // self.fake_input.mute()? self.fake_input.key_click(&[EV_KEY::KEY_PLAYPAUSE])?; Ok(()) } - fn on_dial(&mut self, delta: i32) -> DynResult<()> { - match self.thresh.update(delta) { - Some(DialDir::Left) => { - eprintln!("volume down"); - self.fake_input - .key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEDOWN])? - } - Some(DialDir::Right) => { - eprintln!("volume up"); - self.fake_input - .key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEUP])? - } - None => {} + fn on_dial(&mut self, _: &DialHaptics, delta: i32) -> DynResult<()> { + if delta > 0 { + eprintln!("volume up"); + self.fake_input + .key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEUP])?; + } else { + eprintln!("volume down"); + self.fake_input + .key_click(&[EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_VOLUMEDOWN])?; } + Ok(()) } } diff --git a/src/controller/mod.rs b/src/controller/mod.rs index e549bd3..fba7b2c 100644 --- a/src/controller/mod.rs +++ b/src/controller/mod.rs @@ -1,13 +1,14 @@ use crate::DynResult; -use crate::dial_device::{DialDevice, DialEventKind}; +use crate::dial_device::{DialDevice, DialEventKind, DialHaptics}; pub mod controls; pub trait ControlMode { - fn on_btn_press(&mut self) -> DynResult<()>; - fn on_btn_release(&mut self) -> DynResult<()>; - fn on_dial(&mut self, delta: i32) -> DynResult<()>; + fn on_start(&mut self, haptics: &DialHaptics) -> DynResult<()>; + fn on_btn_press(&mut self, haptics: &DialHaptics) -> DynResult<()>; + fn on_btn_release(&mut self, haptics: &DialHaptics) -> DynResult<()>; + fn on_dial(&mut self, haptics: &DialHaptics, delta: i32) -> DynResult<()>; } pub struct DialController { @@ -26,16 +27,20 @@ impl DialController { } pub fn run(&mut self) -> DynResult<()> { + let haptics = self.device.haptics(); + + self.mode.on_start(haptics)?; + loop { let evt = self.device.next_event()?; - // TODO: press and hold + rotate to switch between modes + // TODO: press and hold (+ rotate?) to switch between modes match evt.kind { DialEventKind::Ignored => {} - DialEventKind::ButtonPress => self.mode.on_btn_press()?, - DialEventKind::ButtonRelease => self.mode.on_btn_release()?, - DialEventKind::Dial(delta) => self.mode.on_dial(delta)?, + DialEventKind::ButtonPress => self.mode.on_btn_press(haptics)?, + DialEventKind::ButtonRelease => self.mode.on_btn_release(haptics)?, + DialEventKind::Dial(delta) => self.mode.on_dial(haptics, delta)?, } } } diff --git a/src/dial_device.rs b/src/dial_device.rs index e0731ba..029d15d 100644 --- a/src/dial_device.rs +++ b/src/dial_device.rs @@ -2,6 +2,7 @@ use std::fs; use std::time::Duration; use evdev_rs::{Device, InputEvent}; +use hidapi::{HidApi, HidDevice}; use crate::error::Error; @@ -9,6 +10,7 @@ pub struct DialDevice { // TODO: explore what the control channel can be used for... _control: Device, axis: Device, + haptics: DialHaptics, } #[derive(Debug)] @@ -30,6 +32,7 @@ impl DialDevice { let mut control = None; let mut axis = None; + // discover the evdev devices for e in fs::read_dir("/dev/input/").map_err(Error::OpenDevInputDir)? { let e = e.map_err(Error::OpenDevInputDir)?; if !e.file_name().to_str().unwrap().starts_with("event") { @@ -62,6 +65,7 @@ impl DialDevice { Ok(DialDevice { _control: control.ok_or(Error::MissingDial)?, axis: axis.ok_or(Error::MissingDial)?, + haptics: DialHaptics::new()?, }) } @@ -79,6 +83,10 @@ impl DialDevice { Ok(event) } + + pub fn haptics(&self) -> &DialHaptics { + &self.haptics + } } impl DialEvent { @@ -110,3 +118,67 @@ impl DialEvent { Some(evt) } } + +pub struct DialHaptics { + hid_device: HidDevice, +} + +impl DialHaptics { + fn new() -> Result { + let api = HidApi::new().map_err(Error::HidError)?; + let hid_device = api.open(0x045e, 0x091b).map_err(|_| Error::MissingDial)?; + + // let mut buf = [0; 256]; + + // buf[0] = 1; + // let len = device + // .get_feature_report(&mut buf) + // .map_err(Error::HidError)?; + // eprintln!("1: {:02x?}", &buf[..len]); + + // buf[0] = 2; + // let len = device + // .get_feature_report(&mut buf) + // .map_err(Error::HidError)?; + // eprintln!("2: {:02x?}", &buf[..len]); + + Ok(DialHaptics { hid_device }) + } + + /// `steps` should be a value between 0 and 3600, which corresponds to the + /// number of subdivisions the dial should use. If left unspecified, this + /// defaults to 36 (an arbitrary choice that "feels good" most of the time) + pub fn set_mode(&self, haptics: bool, steps: Option) -> Result<(), Error> { + let steps = steps.unwrap_or(36); + assert!(steps <= 3600); + + let steps_lo = steps & 0xff; + let steps_hi = (steps >> 8) & 0xff; + + let mut buf = [0; 8]; + buf[0] = 1; + buf[1] = steps_lo as u8; // steps + buf[2] = steps_hi as u8; // steps + buf[3] = 0x00; // Repeat Count + buf[4] = if haptics { 0x03 } else { 0x02 }; // auto trigger + buf[5] = 0x00; // Waveform Cutoff Time + buf[6] = 0x00; // retrigger period + buf[7] = 0x00; // retrigger period + self.hid_device + .send_feature_report(&buf[..8]) + .map_err(Error::HidError)?; + + Ok(()) + } + + pub fn buzz(&self, repeat: u8) -> Result<(), Error> { + let mut buf = [0; 5]; + buf[0] = 0x01; // Report ID + buf[1] = repeat; // RepeatCount + buf[2] = 0x03; // ManualTrigger + buf[3] = 0x00; // RetriggerPeriod (lo) + buf[4] = 0x00; // RetriggerPeriod (hi) + self.hid_device.write(&buf).map_err(Error::HidError)?; + Ok(()) + } +} diff --git a/src/error.rs b/src/error.rs index 8fd86e7..e30ffff 100644 --- a/src/error.rs +++ b/src/error.rs @@ -7,6 +7,7 @@ use evdev_rs::InputEvent; pub enum Error { OpenDevInputDir(io::Error), OpenEventFile(std::path::PathBuf, io::Error), + HidError(hidapi::HidError), MissingDial, MultipleDials, UnexpectedEvt(InputEvent), @@ -19,6 +20,7 @@ impl fmt::Display for Error { match self { Error::OpenDevInputDir(e) => write!(f, "Could not open /dev/input directory: {}", e), Error::OpenEventFile(path, e) => write!(f, "Could not open {:?}: {}", path, e), + Error::HidError(e) => write!(f, "HID API Error: {}", e), Error::MissingDial => write!(f, "Could not find the Surface Dial"), Error::MultipleDials => write!(f, "Found multiple dials"), Error::UnexpectedEvt(evt) => write!(f, "Unexpected event: {:?}", evt), diff --git a/src/main.rs b/src/main.rs index a1852e4..eea415c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![allow(clippy::collapsible_if, clippy::new_without_default)] + mod common; pub mod controller; mod dial_device; @@ -10,6 +12,9 @@ use crate::controller::DialController; use crate::dial_device::DialDevice; use crate::error::Error; +use notify_rust::{Hint, Notification, Timeout}; +use signal_hook::{iterator::Signals, SIGINT, SIGTERM}; + fn main() { if let Err(e) = true_main() { println!("{}", e); @@ -22,11 +27,29 @@ fn true_main() -> DynResult<()> { let dial = DialDevice::new()?; println!("Found the dial."); - common::action_notification("Active!", "input-mouse")?; + std::thread::spawn(move || { + let active_notification = Notification::new() + .hint(Hint::Resident(true)) + .hint(Hint::Category("device".into())) + .timeout(Timeout::Never) + .summary("Surface Dial") + .body("Active!") + .icon("input-mouse") + .show() + .expect("failed to send notification"); - let default_mode = Box::new(controller::controls::ScrollZoom::new(30)); - // let default_mode = Box::new(controller::controls::Volume::new(30)); - // let default_mode = Box::new(controller::controls::Media::new(50)); + let signals = Signals::new(&[SIGTERM, SIGINT]).unwrap(); + for sig in signals.forever() { + eprintln!("received signal {:?}", sig); + active_notification.close(); + std::process::exit(1); + } + }); + + // let default_mode = Box::new(controller::controls::Null::new()); + let default_mode = Box::new(controller::controls::ScrollZoom::new()); + // let default_mode = Box::new(controller::controls::Volume::new()); + // let default_mode = Box::new(controller::controls::Media::new()); // let default_mode = Box::new(controller::controls::DPad::new()); let mut controller = DialController::new(dial, default_mode);