feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
Vendored
+998
@@ -0,0 +1,998 @@
|
||||
//! Implements generation of HID report descriptors as well as common reports
|
||||
extern crate serde;
|
||||
extern crate usbd_hid_macros;
|
||||
use serde::ser::{Serialize, SerializeTuple, Serializer};
|
||||
|
||||
pub use usbd_hid_macros::gen_hid_descriptor;
|
||||
|
||||
/// Report types where serialized HID report descriptors are available.
|
||||
pub trait SerializedDescriptor {
|
||||
fn desc() -> &'static [u8];
|
||||
}
|
||||
|
||||
/// Report types which serialize into input reports, ready for transmission.
|
||||
pub trait AsInputReport: Serialize {}
|
||||
|
||||
/// Prelude for modules which use the `gen_hid_descriptor` macro.
|
||||
pub mod generator_prelude {
|
||||
pub use crate::descriptor::{AsInputReport, SerializedDescriptor};
|
||||
pub use serde::ser::{Serialize, SerializeTuple, Serializer};
|
||||
pub use usbd_hid_macros::gen_hid_descriptor;
|
||||
}
|
||||
|
||||
/// MouseReport describes a report and its companion descriptor than can be used
|
||||
/// to send mouse movements and button presses to a host.
|
||||
#[gen_hid_descriptor(
|
||||
(collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = MOUSE) = {
|
||||
(collection = PHYSICAL, usage = POINTER) = {
|
||||
(usage_page = BUTTON, usage_min = BUTTON_1, usage_max = BUTTON_8) = {
|
||||
#[packed_bits 8] #[item_settings data,variable,absolute] buttons=input;
|
||||
};
|
||||
(usage_page = GENERIC_DESKTOP,) = {
|
||||
(usage = X,) = {
|
||||
#[item_settings data,variable,relative] x=input;
|
||||
};
|
||||
(usage = Y,) = {
|
||||
#[item_settings data,variable,relative] y=input;
|
||||
};
|
||||
(usage = WHEEL,) = {
|
||||
#[item_settings data,variable,relative] wheel=input;
|
||||
};
|
||||
};
|
||||
(usage_page = CONSUMER,) = {
|
||||
(usage = AC_PAN,) = {
|
||||
#[item_settings data,variable,relative] pan=input;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
)]
|
||||
#[allow(dead_code)]
|
||||
pub struct MouseReport {
|
||||
pub buttons: u8,
|
||||
pub x: i8,
|
||||
pub y: i8,
|
||||
pub wheel: i8, // Scroll down (negative) or up (positive) this many units
|
||||
pub pan: i8, // Scroll left (negative) or right (positive) this many units
|
||||
}
|
||||
|
||||
/// KeyboardReport describes a report and its companion descriptor that can be
|
||||
/// used to send keyboard button presses to a host and receive the status of the
|
||||
/// keyboard LEDs.
|
||||
#[gen_hid_descriptor(
|
||||
(collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = KEYBOARD) = {
|
||||
(usage_page = KEYBOARD, usage_min = 0xE0, usage_max = 0xE7) = {
|
||||
#[packed_bits 8] #[item_settings data,variable,absolute] modifier=input;
|
||||
};
|
||||
(usage_min = 0x00, usage_max = 0xFF) = {
|
||||
#[item_settings constant,variable,absolute] reserved=input;
|
||||
};
|
||||
(usage_page = LEDS, usage_min = 0x01, usage_max = 0x05) = {
|
||||
#[packed_bits 5] #[item_settings data,variable,absolute] leds=output;
|
||||
};
|
||||
(usage_page = KEYBOARD, usage_min = 0x00, usage_max = 0xDD) = {
|
||||
#[item_settings data,array,absolute] keycodes=input;
|
||||
};
|
||||
}
|
||||
)]
|
||||
#[allow(dead_code)]
|
||||
pub struct KeyboardReport {
|
||||
pub modifier: u8,
|
||||
pub reserved: u8,
|
||||
pub leds: u8,
|
||||
pub keycodes: [u8; 6],
|
||||
}
|
||||
|
||||
impl KeyboardReport {
|
||||
pub const fn default() -> Self {
|
||||
Self {
|
||||
modifier: 0,
|
||||
reserved: 0,
|
||||
leds: 0,
|
||||
keycodes: [0u8; 6],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// KeyboardUsage describes the key codes to be used in implementing a USB keyboard.
|
||||
///
|
||||
/// The usage type of all key codes is Selectors, except for the modifier keys
|
||||
/// Keyboard Left Control to Keyboard Right GUI which are Dynamic Flags.
|
||||
///
|
||||
/// Reference: <https://usb.org/sites/default/files/hut1_3_0.pdf> (Section 10, page 88)
|
||||
#[repr(u8)]
|
||||
#[allow(unused)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Copy, Debug, Clone, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum KeyboardUsage {
|
||||
// 0x00: Reserved
|
||||
/// Keyboard ErrorRollOver (Footnote 1)
|
||||
KeyboardErrorRollOver = 0x01,
|
||||
/// Keyboard POSTFail (Footnote 1)
|
||||
KeyboardPOSTFail = 0x02,
|
||||
/// Keyboard ErrorUndefined (Footnote 1)
|
||||
KeyboardErrorUndefined = 0x03,
|
||||
/// Keyboard a and A (Footnote 2)
|
||||
KeyboardAa = 0x04,
|
||||
/// Keyboard b and B
|
||||
KeyboardBb = 0x05,
|
||||
/// Keyboard c and C (Footnote 2)
|
||||
KeyboardCc = 0x06,
|
||||
/// Keyboard d and D
|
||||
KeyboardDd = 0x07,
|
||||
/// Keyboard e and E
|
||||
KeyboardEe = 0x08,
|
||||
/// Keyboard f and F
|
||||
KeyboardFf = 0x09,
|
||||
/// Keyboard g and G
|
||||
KeyboardGg = 0x0A,
|
||||
/// Keyboard h and H
|
||||
KeyboardHh = 0x0B,
|
||||
/// Keyboard i and I
|
||||
KeyboardIi = 0x0C,
|
||||
/// Keyboard j and J
|
||||
KeyboardJj = 0x0D,
|
||||
/// Keyboard k and K
|
||||
KeyboardKk = 0x0E,
|
||||
/// Keyboard l and L
|
||||
KeyboardLl = 0x0F,
|
||||
/// Keyboard m and M (Footnote 2)
|
||||
KeyboardMm = 0x10,
|
||||
/// Keyboard n and N
|
||||
KeyboardNn = 0x11,
|
||||
/// Keyboard o and O (Footnote 2)
|
||||
KeyboardOo = 0x12,
|
||||
/// Keyboard p and P (Footnote 2)
|
||||
KeyboardPp = 0x13,
|
||||
/// Keyboard q and Q (Footnote 2)
|
||||
KeyboardQq = 0x14,
|
||||
/// Keyboard r and R
|
||||
KeyboardRr = 0x15,
|
||||
/// Keyboard s and S
|
||||
KeyboardSs = 0x16,
|
||||
/// Keyboard t and T
|
||||
KeyboardTt = 0x17,
|
||||
/// Keyboard u and U
|
||||
KeyboardUu = 0x18,
|
||||
/// Keyboard v and V
|
||||
KeyboardVv = 0x19,
|
||||
/// Keyboard w and W (Footnote 2)
|
||||
KeyboardWw = 0x1A,
|
||||
/// Keyboard x and X (Footnote 2)
|
||||
KeyboardXx = 0x1B,
|
||||
/// Keyboard y and Y (Footnote 2)
|
||||
KeyboardYy = 0x1C,
|
||||
/// Keyboard z and Z (Footnote 2)
|
||||
KeyboardZz = 0x1D,
|
||||
/// Keyboard 1 and ! (Footnote 2)
|
||||
Keyboard1Exclamation = 0x1E,
|
||||
/// Keyboard 2 and @ (Footnote 2)
|
||||
Keyboard2At = 0x1F,
|
||||
/// Keyboard 3 and # (Footnote 2)
|
||||
Keyboard3Hash = 0x20,
|
||||
/// Keyboard 4 and $ (Footnote 2)
|
||||
Keyboard4Dollar = 0x21,
|
||||
/// Keyboard 5 and % (Footnote 2)
|
||||
Keyboard5Percent = 0x22,
|
||||
/// Keyboard 6 and ^ (Footnote 2)
|
||||
Keyboard6Caret = 0x23,
|
||||
/// Keyboard 7 and & (Footnote 2)
|
||||
Keyboard7Ampersand = 0x24,
|
||||
/// Keyboard 8 and * (Footnote 2)
|
||||
Keyboard8Asterisk = 0x25,
|
||||
/// Keyboard 9 and ( (Footnote 2)
|
||||
Keyboard9OpenParens = 0x26,
|
||||
/// Keyboard 0 and ) (Footnote 2)
|
||||
Keyboard0CloseParens = 0x27,
|
||||
/// Keyboard Return (ENTER) (Footnote 3)
|
||||
///
|
||||
/// (Footnote 3): Keyboard Enter and Keypad Enter generate different Usage codes.
|
||||
KeyboardEnter = 0x28,
|
||||
/// Keyboard ESCAPE
|
||||
KeyboardEscape = 0x29,
|
||||
/// Keyboard DELETE (Backspace) (Footnote 4)
|
||||
KeyboardBackspace = 0x2A,
|
||||
/// Keyboard Tab
|
||||
KeyboardTab = 0x2B,
|
||||
/// Keyboard Spacebar
|
||||
KeyboardSpacebar = 0x2C,
|
||||
/// Keyboard - and _ (Footnote 2)
|
||||
KeyboardDashUnderscore = 0x2D,
|
||||
/// Keyboard = and + (Footnote 2)
|
||||
KeyboardEqualPlus = 0x2E,
|
||||
/// Keyboard [ and { (Footnote 2)
|
||||
KeyboardOpenBracketBrace = 0x2F,
|
||||
/// Keyboard ] and } (Footnote 2)
|
||||
KeyboardCloseBracketBrace = 0x30,
|
||||
/// Keyboard \ and |
|
||||
KeyboardBackslashBar = 0x31,
|
||||
/// Keyboard Non-US # and (Footnote 5)
|
||||
KeyboardNonUSHash = 0x32,
|
||||
/// Keyboard ; and : (Footnote 2)
|
||||
KeyboardSemiColon = 0x33,
|
||||
/// Keyboard ' and " (Footnote 2)
|
||||
KeyboardSingleDoubleQuote = 0x34,
|
||||
/// Keyboard ` and ~ (Footnote 2)
|
||||
KeyboardBacktickTilde = 0x35,
|
||||
/// Keyboard , and < (Footnote 2)
|
||||
KeyboardCommaLess = 0x36,
|
||||
/// Keyboard . and > (Footnote 2)
|
||||
KeyboardPeriodGreater = 0x37,
|
||||
/// Keyboard / and ? (Footnote 2)
|
||||
KeyboardSlashQuestion = 0x38,
|
||||
/// Keyboard Caps Lock (Footnote 6)
|
||||
KeyboardCapsLock = 0x39,
|
||||
/// Keyboard F1
|
||||
KeyboardF1 = 0x3A,
|
||||
/// Keyboard F2
|
||||
KeyboardF2 = 0x3B,
|
||||
/// Keyboard F3
|
||||
KeyboardF3 = 0x3C,
|
||||
/// Keyboard F4
|
||||
KeyboardF4 = 0x3D,
|
||||
/// Keyboard F5
|
||||
KeyboardF5 = 0x3E,
|
||||
/// Keyboard F6
|
||||
KeyboardF6 = 0x3F,
|
||||
/// Keyboard F7
|
||||
KeyboardF7 = 0x40,
|
||||
/// Keyboard F8
|
||||
KeyboardF8 = 0x41,
|
||||
/// Keyboard F9
|
||||
KeyboardF9 = 0x42,
|
||||
/// Keyboard F10
|
||||
KeyboardF10 = 0x43,
|
||||
/// Keyboard F11
|
||||
KeyboardF11 = 0x44,
|
||||
/// Keyboard F12
|
||||
KeyboardF12 = 0x45,
|
||||
/// Keyboard PrintScreen (Footnote 7)
|
||||
KeyboardPrintScreen = 0x46,
|
||||
/// Keyboard ScrollLock (Footnote 6)
|
||||
KeyboardScrollLock = 0x47,
|
||||
/// Keyboard Pause (Footnote 7)
|
||||
KeyboardPause = 0x48,
|
||||
/// Keyboard Insert (Footnote 7)
|
||||
KeyboardInsert = 0x49,
|
||||
/// Keyboard Home (Footnote 7)
|
||||
KeyboardHome = 0x4A,
|
||||
/// Keyboard PageUp (Footnote 7)
|
||||
KeyboardPageUp = 0x4B,
|
||||
/// Keyboard Delete Forward (Footnote 7) (Footnote 8)
|
||||
KeyboardDelete = 0x4C,
|
||||
/// Keyboard End (Footnote 7)
|
||||
KeyboardEnd = 0x4D,
|
||||
/// Keyboard PageDown (Footnote 7)
|
||||
KeyboardPageDown = 0x4E,
|
||||
/// Keyboard RightArrow (Footnote 7)
|
||||
KeyboardRightArrow = 0x4F,
|
||||
/// Keyboard LeftArrow (Footnote 7)
|
||||
KeyboardLeftArrow = 0x50,
|
||||
/// Keyboard DownArrow (Footnote 7)
|
||||
KeyboardDownArrow = 0x51,
|
||||
/// Keyboard UpArrow (Footnote 7)
|
||||
KeyboardUpArrow = 0x52,
|
||||
/// Keypad Num Lock and Clear (Footnote 6)
|
||||
KeypadNumLock = 0x53,
|
||||
/// Keypad / (Footnote 7)
|
||||
KeypadDivide = 0x54,
|
||||
/// Keypad *
|
||||
KeypadMultiply = 0x55,
|
||||
/// Keypad -
|
||||
KeypadMinus = 0x56,
|
||||
/// Keypad +
|
||||
KeypadPlus = 0x57,
|
||||
/// Keypad ENTER (Footnote 3)
|
||||
KeypadEnter = 0x58,
|
||||
/// Keypad 1 and End
|
||||
Keypad1End = 0x59,
|
||||
/// Keypad 2 and DownArrow
|
||||
Keypad2DownArrow = 0x5A,
|
||||
/// Keypad 3 and PageDown
|
||||
Keypad3PageDown = 0x5B,
|
||||
/// Keypad 4 and LeftArrow
|
||||
Keypad4LeftArrow = 0x5C,
|
||||
/// Keypad 5
|
||||
Keypad5 = 0x5D,
|
||||
/// Keypad 6 and RightArrow
|
||||
Keypad6RightArrow = 0x5E,
|
||||
/// Keypad 7 and Home
|
||||
Keypad7Home = 0x5F,
|
||||
/// Keypad 8 and UpArrow
|
||||
Keypad8UpArrow = 0x60,
|
||||
/// Keypad 9 and PageUp
|
||||
Keypad9PageUp = 0x61,
|
||||
/// Keypad 0 and Insert
|
||||
Keypad0Insert = 0x62,
|
||||
/// Keypad . and Delete
|
||||
KeypadPeriodDelete = 0x63,
|
||||
/// Keyboard Non-US \ and | (Footnote 9) (Footnote 10)
|
||||
KeyboardNonUSSlash = 0x64,
|
||||
/// Keyboard Application (Footnote 11)
|
||||
KeyboardApplication = 0x65,
|
||||
/// Keyboard Power (Footnote 1)
|
||||
KeyboardPower = 0x66,
|
||||
/// Keypad =
|
||||
KeypadEqual = 0x67,
|
||||
/// Keyboard F13
|
||||
KeyboardF13 = 0x68,
|
||||
/// Keyboard F14
|
||||
KeyboardF14 = 0x69,
|
||||
/// Keyboard F15
|
||||
KeyboardF15 = 0x6A,
|
||||
/// Keyboard F16
|
||||
KeyboardF16 = 0x6B,
|
||||
/// Keyboard F17
|
||||
KeyboardF17 = 0x6C,
|
||||
/// Keyboard F18
|
||||
KeyboardF18 = 0x6D,
|
||||
/// Keyboard F19
|
||||
KeyboardF19 = 0x6E,
|
||||
/// Keyboard F20
|
||||
KeyboardF20 = 0x6F,
|
||||
/// Keyboard F21
|
||||
KeyboardF21 = 0x70,
|
||||
/// Keyboard F22
|
||||
KeyboardF22 = 0x71,
|
||||
/// Keyboard F23
|
||||
KeyboardF23 = 0x72,
|
||||
/// Keyboard F24
|
||||
KeyboardF24 = 0x73,
|
||||
/// Keyboard Execute
|
||||
KeyboardExecute = 0x74,
|
||||
/// Keyboard Help
|
||||
KeyboardHelp = 0x75,
|
||||
/// Keyboard Menu
|
||||
KeyboardMenu = 0x76,
|
||||
/// Keyboard Select
|
||||
KeyboardSelect = 0x77,
|
||||
/// Keyboard Stop
|
||||
KeyboardStop = 0x78,
|
||||
/// Keyboard Again
|
||||
KeyboardAgain = 0x79,
|
||||
/// Keyboard Undo
|
||||
KeyboardUndo = 0x7A,
|
||||
/// Keyboard Cut
|
||||
KeyboardCut = 0x7B,
|
||||
/// Keyboard Copy
|
||||
KeyboardCopy = 0x7C,
|
||||
/// Keyboard Paste
|
||||
KeyboardPaste = 0x7D,
|
||||
/// Keyboard Find
|
||||
KeyboardFind = 0x7E,
|
||||
/// Keyboard Mute
|
||||
KeyboardMute = 0x7F,
|
||||
/// Keyboard Volume Up
|
||||
KeyboardVolumeUp = 0x80,
|
||||
/// Keyboard Volume Down
|
||||
KeyboardVolumeDown = 0x81,
|
||||
/// Keyboad Locking Caps Lock (Footnote 12)
|
||||
KeyboardLockingCapsLock = 0x82,
|
||||
/// Keyboad Locking Num Lock (Footnote 12)
|
||||
KeyboardLockingNumLock = 0x83,
|
||||
/// Keyboad Locking Scroll Lock (Footnote 12)
|
||||
KeyboardLockingScrollLock = 0x84,
|
||||
/// Keypad Comma (Footnote 13)
|
||||
KeypadComma = 0x85,
|
||||
/// Keypad Equal Sign (Footnote 14)
|
||||
KeypadEqualSign = 0x86,
|
||||
/// Keyboard International1 (Footnote 15) (Footnote 16)
|
||||
KeyboardInternational1 = 0x87,
|
||||
/// Keyboard International2 (Footnote 17)
|
||||
KeyboardInternational2 = 0x88,
|
||||
/// Keyboard International3 (Footnote 18)
|
||||
KeyboardInternational3 = 0x89,
|
||||
/// Keyboard International4 (Footnote 19)
|
||||
KeyboardInternational4 = 0x8A,
|
||||
/// Keyboard International5 (Footnote 20)
|
||||
KeyboardInternational5 = 0x8B,
|
||||
/// Keyboard International6 (Footnote 21)
|
||||
KeyboardInternational6 = 0x8C,
|
||||
/// Keyboard International7 (Footnote 22)
|
||||
KeyboardInternational7 = 0x8D,
|
||||
/// Keyboard International8 (Footnote 23)
|
||||
KeyboardInternational8 = 0x8E,
|
||||
/// Keyboard International9 (Footnote 23)
|
||||
KeyboardInternational9 = 0x8F,
|
||||
/// Keyboard LANG1 (Footnote 24)
|
||||
KeyboardLANG1 = 0x90,
|
||||
/// Keyboard LANG2 (Footnote 25)
|
||||
KeyboardLANG2 = 0x91,
|
||||
/// Keyboard LANG3 (Footnote 26)
|
||||
KeyboardLANG3 = 0x92,
|
||||
/// Keyboard LANG4 (Footnote 27)
|
||||
KeyboardLANG4 = 0x93,
|
||||
/// Keyboard LANG5 (Footnote 28)
|
||||
KeyboardLANG5 = 0x94,
|
||||
/// Keyboard LANG6 (Footnote 29)
|
||||
KeyboardLANG6 = 0x95,
|
||||
/// Keyboard LANG7 (Footnote 29)
|
||||
KeyboardLANG7 = 0x96,
|
||||
/// Keyboard LANG8 (Footnote 29)
|
||||
KeyboardLANG8 = 0x97,
|
||||
/// Keyboard LANG9 (Footnote 29)
|
||||
KeyboardLANG9 = 0x98,
|
||||
/// Keyboard Alternate Erase (Footnote 30)
|
||||
KeyboardAlternateErase = 0x99,
|
||||
/// Keyboard SysReq/Attention (Footnote 7)
|
||||
KeyboardSysReqAttention = 0x9A,
|
||||
/// Keyboard Cancel
|
||||
KeyboardCancel = 0x9B,
|
||||
/// Keyboard Clear
|
||||
KeyboardClear = 0x9C,
|
||||
/// Keyboard Prior
|
||||
KeyboardPrior = 0x9D,
|
||||
/// Keyboard Return
|
||||
KeyboardReturn = 0x9E,
|
||||
/// Keyboard Separator
|
||||
KeyboardSeparator = 0x9F,
|
||||
/// Keyboard Out
|
||||
KeyboardOut = 0xA0,
|
||||
/// Keyboard Oper
|
||||
KeyboardOper = 0xA1,
|
||||
/// Keyboard Clear/Again
|
||||
KeyboardClearAgain = 0xA2,
|
||||
/// Keyboard CrSel/Props
|
||||
KeyboardCrSelProps = 0xA3,
|
||||
/// Keyboard ExSel
|
||||
KeyboardExSel = 0xA4,
|
||||
// 0xA5-0xAF: Reserved
|
||||
/// Keypad 00
|
||||
Keypad00 = 0xB0,
|
||||
/// Keypad 000
|
||||
Keypad000 = 0xB1,
|
||||
/// Thousands Separator (Footnote 31)
|
||||
ThousandsSeparator = 0xB2,
|
||||
/// Decimal Separator (Footnote 31)
|
||||
DecimalSeparator = 0xB3,
|
||||
/// Currency Unit (Footnote 32)
|
||||
CurrencyUnit = 0xB4,
|
||||
/// Currency Sub-unit (Footnote 32)
|
||||
CurrencySubunit = 0xB5,
|
||||
/// Keypad (
|
||||
KeypadOpenParens = 0xB6,
|
||||
/// Keypad )
|
||||
KeypadCloseParens = 0xB7,
|
||||
/// Keypad {
|
||||
KeypadOpenBrace = 0xB8,
|
||||
/// Keypad }
|
||||
KeypadCloseBrace = 0xB9,
|
||||
/// Keypad Tab
|
||||
KeypadTab = 0xBA,
|
||||
/// Keypad Backspace
|
||||
KeypadBackspace = 0xBB,
|
||||
/// Keypad A
|
||||
KeypadA = 0xBC,
|
||||
/// Keypad B
|
||||
KeypadB = 0xBD,
|
||||
/// Keypad C
|
||||
KeypadC = 0xBE,
|
||||
/// Keypad D
|
||||
KeypadD = 0xBF,
|
||||
/// Keypad E
|
||||
KeypadE = 0xC0,
|
||||
/// Keypad F
|
||||
KeypadF = 0xC1,
|
||||
/// Keypad XOR
|
||||
KeypadBitwiseXor = 0xC2,
|
||||
/// Keypad ^
|
||||
KeypadLogicalXor = 0xC3,
|
||||
/// Keypad %
|
||||
KeypadModulo = 0xC4,
|
||||
/// Keypad <
|
||||
KeypadLeftShift = 0xC5,
|
||||
/// Keypad >
|
||||
KeypadRightShift = 0xC6,
|
||||
/// Keypad &
|
||||
KeypadBitwiseAnd = 0xC7,
|
||||
/// Keypad &&
|
||||
KeypadLogicalAnd = 0xC8,
|
||||
/// Keypad |
|
||||
KeypadBitwiseOr = 0xC9,
|
||||
/// Keypad ||
|
||||
KeypadLogicalOr = 0xCA,
|
||||
/// Keypad :
|
||||
KeypadColon = 0xCB,
|
||||
/// Keypad #
|
||||
KeypadHash = 0xCC,
|
||||
/// Keypad Space
|
||||
KeypadSpace = 0xCD,
|
||||
/// Keypad @
|
||||
KeypadAt = 0xCE,
|
||||
/// Keypad !
|
||||
KeypadExclamation = 0xCF,
|
||||
/// Keypad Memory Store
|
||||
KeypadMemoryStore = 0xD0,
|
||||
/// Keypad Memory Recall
|
||||
KeypadMemoryRecall = 0xD1,
|
||||
/// Keypad Memory Clear
|
||||
KeypadMemoryClear = 0xD2,
|
||||
/// Keypad Memory Add
|
||||
KeypadMemoryAdd = 0xD3,
|
||||
/// Keypad Memory Subtract
|
||||
KeypadMemorySubtract = 0xD4,
|
||||
/// Keypad Memory Multiply
|
||||
KeypadMemoryMultiply = 0xD5,
|
||||
/// Keypad Memory Divice
|
||||
KeypadMemoryDivide = 0xD6,
|
||||
/// Keypad +/-
|
||||
KeypadPositiveNegative = 0xD7,
|
||||
/// Keypad Clear
|
||||
KeypadClear = 0xD8,
|
||||
/// Keypad Clear Entry
|
||||
KeypadClearEntry = 0xD9,
|
||||
/// Keypad Binary
|
||||
KeypadBinary = 0xDA,
|
||||
/// Keypad Octal
|
||||
KeypadOctal = 0xDB,
|
||||
/// Keypad Decimal
|
||||
KeypadDecimal = 0xDC,
|
||||
/// Keypad Hexadecimal
|
||||
KeypadHexadecimal = 0xDD,
|
||||
// 0xDE-0xDF: Reserved
|
||||
/// Keyboard LeftControl
|
||||
KeyboardLeftControl = 0xE0,
|
||||
/// Keyboard LeftShift
|
||||
KeyboardLeftShift = 0xE1,
|
||||
/// Keyboard LeftAlt
|
||||
KeyboardLeftAlt = 0xE2,
|
||||
/// Keyboard LeftGUI (Footnote 11) (Footnote 33)
|
||||
KeyboardLeftGUI = 0xE3,
|
||||
/// Keyboard RightControl
|
||||
KeyboardRightControl = 0xE4,
|
||||
/// Keyboard RightShift
|
||||
KeyboardRightShift = 0xE5,
|
||||
/// Keyboard RightAlt
|
||||
KeyboardRightAlt = 0xE6,
|
||||
/// Keyboard RightGUI (Footnote 11) (Footnote 34)
|
||||
KeyboardRightGUI = 0xE7,
|
||||
/// Reserved keyboard values (used for all reserved / invalid values)
|
||||
Reserved = 0xE8,
|
||||
// 0xE8-0xFF: Reserved
|
||||
}
|
||||
|
||||
impl From<u8> for KeyboardUsage {
|
||||
fn from(k: u8) -> Self {
|
||||
match k {
|
||||
0x01 => Self::KeyboardErrorRollOver,
|
||||
0x02 => Self::KeyboardPOSTFail,
|
||||
0x03 => Self::KeyboardErrorUndefined,
|
||||
0x04 => Self::KeyboardAa,
|
||||
0x05 => Self::KeyboardBb,
|
||||
0x06 => Self::KeyboardCc,
|
||||
0x07 => Self::KeyboardDd,
|
||||
0x08 => Self::KeyboardEe,
|
||||
0x09 => Self::KeyboardFf,
|
||||
0x0A => Self::KeyboardGg,
|
||||
0x0B => Self::KeyboardHh,
|
||||
0x0C => Self::KeyboardIi,
|
||||
0x0D => Self::KeyboardJj,
|
||||
0x0E => Self::KeyboardKk,
|
||||
0x0F => Self::KeyboardLl,
|
||||
0x10 => Self::KeyboardMm,
|
||||
0x11 => Self::KeyboardNn,
|
||||
0x12 => Self::KeyboardOo,
|
||||
0x13 => Self::KeyboardPp,
|
||||
0x14 => Self::KeyboardQq,
|
||||
0x15 => Self::KeyboardRr,
|
||||
0x16 => Self::KeyboardSs,
|
||||
0x17 => Self::KeyboardTt,
|
||||
0x18 => Self::KeyboardUu,
|
||||
0x19 => Self::KeyboardVv,
|
||||
0x1A => Self::KeyboardWw,
|
||||
0x1B => Self::KeyboardXx,
|
||||
0x1C => Self::KeyboardYy,
|
||||
0x1D => Self::KeyboardZz,
|
||||
0x1E => Self::Keyboard1Exclamation,
|
||||
0x1F => Self::Keyboard2At,
|
||||
0x20 => Self::Keyboard3Hash,
|
||||
0x21 => Self::Keyboard4Dollar,
|
||||
0x22 => Self::Keyboard5Percent,
|
||||
0x23 => Self::Keyboard6Caret,
|
||||
0x24 => Self::Keyboard7Ampersand,
|
||||
0x25 => Self::Keyboard8Asterisk,
|
||||
0x26 => Self::Keyboard9OpenParens,
|
||||
0x27 => Self::Keyboard0CloseParens,
|
||||
0x28 => Self::KeyboardEnter,
|
||||
0x29 => Self::KeyboardEscape,
|
||||
0x2A => Self::KeyboardBackspace,
|
||||
0x2B => Self::KeyboardTab,
|
||||
0x2C => Self::KeyboardSpacebar,
|
||||
0x2D => Self::KeyboardDashUnderscore,
|
||||
0x2E => Self::KeyboardEqualPlus,
|
||||
0x2F => Self::KeyboardOpenBracketBrace,
|
||||
0x30 => Self::KeyboardCloseBracketBrace,
|
||||
0x31 => Self::KeyboardBackslashBar,
|
||||
0x32 => Self::KeyboardNonUSHash,
|
||||
0x33 => Self::KeyboardSemiColon,
|
||||
0x34 => Self::KeyboardSingleDoubleQuote,
|
||||
0x35 => Self::KeyboardBacktickTilde,
|
||||
0x36 => Self::KeyboardCommaLess,
|
||||
0x37 => Self::KeyboardPeriodGreater,
|
||||
0x38 => Self::KeyboardSlashQuestion,
|
||||
0x39 => Self::KeyboardCapsLock,
|
||||
0x3A => Self::KeyboardF1,
|
||||
0x3B => Self::KeyboardF2,
|
||||
0x3C => Self::KeyboardF3,
|
||||
0x3D => Self::KeyboardF4,
|
||||
0x3E => Self::KeyboardF5,
|
||||
0x3F => Self::KeyboardF6,
|
||||
0x40 => Self::KeyboardF7,
|
||||
0x41 => Self::KeyboardF8,
|
||||
0x42 => Self::KeyboardF9,
|
||||
0x43 => Self::KeyboardF10,
|
||||
0x44 => Self::KeyboardF11,
|
||||
0x45 => Self::KeyboardF12,
|
||||
0x46 => Self::KeyboardPrintScreen,
|
||||
0x47 => Self::KeyboardScrollLock,
|
||||
0x48 => Self::KeyboardPause,
|
||||
0x49 => Self::KeyboardInsert,
|
||||
0x4A => Self::KeyboardHome,
|
||||
0x4B => Self::KeyboardPageUp,
|
||||
0x4C => Self::KeyboardDelete,
|
||||
0x4D => Self::KeyboardEnd,
|
||||
0x4E => Self::KeyboardPageDown,
|
||||
0x4F => Self::KeyboardRightArrow,
|
||||
0x50 => Self::KeyboardLeftArrow,
|
||||
0x51 => Self::KeyboardDownArrow,
|
||||
0x52 => Self::KeyboardUpArrow,
|
||||
0x53 => Self::KeypadNumLock,
|
||||
0x54 => Self::KeypadDivide,
|
||||
0x55 => Self::KeypadMultiply,
|
||||
0x56 => Self::KeypadMinus,
|
||||
0x57 => Self::KeypadPlus,
|
||||
0x58 => Self::KeypadEnter,
|
||||
0x59 => Self::Keypad1End,
|
||||
0x5A => Self::Keypad2DownArrow,
|
||||
0x5B => Self::Keypad3PageDown,
|
||||
0x5C => Self::Keypad4LeftArrow,
|
||||
0x5D => Self::Keypad5,
|
||||
0x5E => Self::Keypad6RightArrow,
|
||||
0x5F => Self::Keypad7Home,
|
||||
0x60 => Self::Keypad8UpArrow,
|
||||
0x61 => Self::Keypad9PageUp,
|
||||
0x62 => Self::Keypad0Insert,
|
||||
0x63 => Self::KeypadPeriodDelete,
|
||||
0x64 => Self::KeyboardNonUSSlash,
|
||||
0x65 => Self::KeyboardApplication,
|
||||
0x66 => Self::KeyboardPower,
|
||||
0x67 => Self::KeypadEqual,
|
||||
0x68 => Self::KeyboardF13,
|
||||
0x69 => Self::KeyboardF14,
|
||||
0x6A => Self::KeyboardF15,
|
||||
0x6B => Self::KeyboardF16,
|
||||
0x6C => Self::KeyboardF17,
|
||||
0x6D => Self::KeyboardF18,
|
||||
0x6E => Self::KeyboardF19,
|
||||
0x6F => Self::KeyboardF20,
|
||||
0x70 => Self::KeyboardF21,
|
||||
0x71 => Self::KeyboardF22,
|
||||
0x72 => Self::KeyboardF23,
|
||||
0x73 => Self::KeyboardF24,
|
||||
0x74 => Self::KeyboardExecute,
|
||||
0x75 => Self::KeyboardHelp,
|
||||
0x76 => Self::KeyboardMenu,
|
||||
0x77 => Self::KeyboardSelect,
|
||||
0x78 => Self::KeyboardStop,
|
||||
0x79 => Self::KeyboardAgain,
|
||||
0x7A => Self::KeyboardUndo,
|
||||
0x7B => Self::KeyboardCut,
|
||||
0x7C => Self::KeyboardCopy,
|
||||
0x7D => Self::KeyboardPaste,
|
||||
0x7E => Self::KeyboardFind,
|
||||
0x7F => Self::KeyboardMute,
|
||||
0x80 => Self::KeyboardVolumeUp,
|
||||
0x81 => Self::KeyboardVolumeDown,
|
||||
0x82 => Self::KeyboardLockingCapsLock,
|
||||
0x83 => Self::KeyboardLockingNumLock,
|
||||
0x84 => Self::KeyboardLockingScrollLock,
|
||||
0x85 => Self::KeypadComma,
|
||||
0x86 => Self::KeypadEqualSign,
|
||||
0x87 => Self::KeyboardInternational1,
|
||||
0x88 => Self::KeyboardInternational2,
|
||||
0x89 => Self::KeyboardInternational3,
|
||||
0x8A => Self::KeyboardInternational4,
|
||||
0x8B => Self::KeyboardInternational5,
|
||||
0x8C => Self::KeyboardInternational6,
|
||||
0x8D => Self::KeyboardInternational7,
|
||||
0x8E => Self::KeyboardInternational8,
|
||||
0x8F => Self::KeyboardInternational9,
|
||||
0x90 => Self::KeyboardLANG1,
|
||||
0x91 => Self::KeyboardLANG2,
|
||||
0x92 => Self::KeyboardLANG3,
|
||||
0x93 => Self::KeyboardLANG4,
|
||||
0x94 => Self::KeyboardLANG5,
|
||||
0x95 => Self::KeyboardLANG6,
|
||||
0x96 => Self::KeyboardLANG7,
|
||||
0x97 => Self::KeyboardLANG8,
|
||||
0x98 => Self::KeyboardLANG9,
|
||||
0x99 => Self::KeyboardAlternateErase,
|
||||
0x9A => Self::KeyboardSysReqAttention,
|
||||
0x9B => Self::KeyboardCancel,
|
||||
0x9C => Self::KeyboardClear,
|
||||
0x9D => Self::KeyboardPrior,
|
||||
0x9E => Self::KeyboardReturn,
|
||||
0x9F => Self::KeyboardSeparator,
|
||||
0xA0 => Self::KeyboardOut,
|
||||
0xA1 => Self::KeyboardOper,
|
||||
0xA2 => Self::KeyboardClearAgain,
|
||||
0xA3 => Self::KeyboardCrSelProps,
|
||||
0xA4 => Self::KeyboardExSel,
|
||||
0xB0 => Self::Keypad00,
|
||||
0xB1 => Self::Keypad000,
|
||||
0xB2 => Self::ThousandsSeparator,
|
||||
0xB3 => Self::DecimalSeparator,
|
||||
0xB4 => Self::CurrencyUnit,
|
||||
0xB5 => Self::CurrencySubunit,
|
||||
0xB6 => Self::KeypadOpenParens,
|
||||
0xB7 => Self::KeypadCloseParens,
|
||||
0xB8 => Self::KeypadOpenBrace,
|
||||
0xB9 => Self::KeypadCloseBrace,
|
||||
0xBA => Self::KeypadTab,
|
||||
0xBB => Self::KeypadBackspace,
|
||||
0xBC => Self::KeypadA,
|
||||
0xBD => Self::KeypadB,
|
||||
0xBE => Self::KeypadC,
|
||||
0xBF => Self::KeypadD,
|
||||
0xC0 => Self::KeypadE,
|
||||
0xC1 => Self::KeypadF,
|
||||
0xC2 => Self::KeypadBitwiseXor,
|
||||
0xC3 => Self::KeypadLogicalXor,
|
||||
0xC4 => Self::KeypadModulo,
|
||||
0xC5 => Self::KeypadLeftShift,
|
||||
0xC6 => Self::KeypadRightShift,
|
||||
0xC7 => Self::KeypadBitwiseAnd,
|
||||
0xC8 => Self::KeypadLogicalAnd,
|
||||
0xC9 => Self::KeypadBitwiseOr,
|
||||
0xCA => Self::KeypadLogicalOr,
|
||||
0xCB => Self::KeypadColon,
|
||||
0xCC => Self::KeypadHash,
|
||||
0xCD => Self::KeypadSpace,
|
||||
0xCE => Self::KeypadAt,
|
||||
0xCF => Self::KeypadExclamation,
|
||||
0xD0 => Self::KeypadMemoryStore,
|
||||
0xD1 => Self::KeypadMemoryRecall,
|
||||
0xD2 => Self::KeypadMemoryClear,
|
||||
0xD3 => Self::KeypadMemoryAdd,
|
||||
0xD4 => Self::KeypadMemorySubtract,
|
||||
0xD5 => Self::KeypadMemoryMultiply,
|
||||
0xD6 => Self::KeypadMemoryDivide,
|
||||
0xD7 => Self::KeypadPositiveNegative,
|
||||
0xD8 => Self::KeypadClear,
|
||||
0xD9 => Self::KeypadClearEntry,
|
||||
0xDA => Self::KeypadBinary,
|
||||
0xDB => Self::KeypadOctal,
|
||||
0xDC => Self::KeypadDecimal,
|
||||
0xDD => Self::KeypadHexadecimal,
|
||||
0xE0 => Self::KeyboardLeftControl,
|
||||
0xE1 => Self::KeyboardLeftShift,
|
||||
0xE2 => Self::KeyboardLeftAlt,
|
||||
0xE3 => Self::KeyboardLeftGUI,
|
||||
0xE4 => Self::KeyboardRightControl,
|
||||
0xE5 => Self::KeyboardRightShift,
|
||||
0xE6 => Self::KeyboardRightAlt,
|
||||
0xE7 => Self::KeyboardRightGUI,
|
||||
_ => Self::Reserved,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// MediaKeyboardReport describes a report and descriptor that can be used to
|
||||
/// send consumer control commands to the host.
|
||||
///
|
||||
/// This is commonly used for sending media player for keyboards with media player
|
||||
/// keys, but can be used for all sorts of Consumer Page functionality.
|
||||
///
|
||||
/// Reference: <https://usb.org/sites/default/files/hut1_2.pdf>
|
||||
///
|
||||
#[gen_hid_descriptor(
|
||||
(collection = APPLICATION, usage_page = CONSUMER, usage = CONSUMER_CONTROL) = {
|
||||
(usage_page = CONSUMER, usage_min = 0x00, usage_max = 0x514) = {
|
||||
#[item_settings data,array,absolute,not_null] usage_id=input;
|
||||
};
|
||||
}
|
||||
)]
|
||||
#[allow(dead_code)]
|
||||
pub struct MediaKeyboardReport {
|
||||
pub usage_id: u16,
|
||||
}
|
||||
|
||||
/// Media player usage ids that can be used in MediaKeyboardReport
|
||||
#[non_exhaustive]
|
||||
#[repr(u16)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum MediaKey {
|
||||
Zero = 0x00,
|
||||
Play = 0xB0,
|
||||
Pause = 0xB1,
|
||||
Record = 0xB2,
|
||||
NextTrack = 0xB5,
|
||||
PrevTrack = 0xB6,
|
||||
Stop = 0xB7,
|
||||
RandomPlay = 0xB9,
|
||||
Repeat = 0xBC,
|
||||
PlayPause = 0xCD,
|
||||
Mute = 0xE2,
|
||||
VolumeIncrement = 0xE9,
|
||||
VolumeDecrement = 0xEA,
|
||||
Reserved = 0xEB,
|
||||
}
|
||||
|
||||
impl From<MediaKey> for u16 {
|
||||
fn from(mk: MediaKey) -> u16 {
|
||||
mk as u16
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for MediaKey {
|
||||
fn from(k: u8) -> Self {
|
||||
match k {
|
||||
0x00 => Self::Zero,
|
||||
0xB0 => Self::Play,
|
||||
0xB1 => Self::Pause,
|
||||
0xB2 => Self::Record,
|
||||
0xB5 => Self::NextTrack,
|
||||
0xB6 => Self::PrevTrack,
|
||||
0xB7 => Self::Stop,
|
||||
0xB9 => Self::RandomPlay,
|
||||
0xBC => Self::Repeat,
|
||||
0xCD => Self::PlayPause,
|
||||
0xE2 => Self::Mute,
|
||||
0xE9 => Self::VolumeIncrement,
|
||||
0xEA => Self::VolumeDecrement,
|
||||
_ => Self::Reserved,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for MediaKey {
|
||||
fn from(k: u16) -> Self {
|
||||
(k as u8).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// SystemControlReport describes a report and descriptor that can be used to
|
||||
/// send system control commands to the host.
|
||||
///
|
||||
/// This is commonly used to enter sleep mode, power down, hibernate, etc.
|
||||
///
|
||||
/// Reference: <https://usb.org/sites/default/files/hut1_2.pdf>
|
||||
///
|
||||
/// NOTE: For Windows compatibility usage_min should start at 0x81
|
||||
/// NOTE: For macOS scrollbar compatibility, logical minimum should start from 1
|
||||
/// (scrollbars disappear if logical_min is set to 0)
|
||||
#[gen_hid_descriptor(
|
||||
(collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = SYSTEM_CONTROL) = {
|
||||
(usage_min = 0x81, usage_max = 0xB7, logical_min = 1) = {
|
||||
#[item_settings data,array,absolute,not_null] usage_id=input;
|
||||
};
|
||||
}
|
||||
)]
|
||||
#[allow(dead_code)]
|
||||
pub struct SystemControlReport {
|
||||
pub usage_id: u8,
|
||||
}
|
||||
|
||||
/// System control usage ids to use with SystemControlReport
|
||||
#[non_exhaustive]
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum SystemControlKey {
|
||||
PowerDown = 0x81,
|
||||
Sleep = 0x82,
|
||||
WakeUp = 0x83,
|
||||
ContextMenu = 0x84,
|
||||
MainMenu = 0x85,
|
||||
AppMenu = 0x86,
|
||||
MenuHelp = 0x87,
|
||||
MenuExit = 0x88,
|
||||
MenuSelect = 0x89,
|
||||
MenuRight = 0x8A,
|
||||
MenuLeft = 0x8B,
|
||||
MenuUp = 0x8C,
|
||||
MenuDown = 0x8D,
|
||||
ColdRestart = 0x8E,
|
||||
WarmRestart = 0x8F,
|
||||
DpadUp = 0x90,
|
||||
DpadDown = 0x91,
|
||||
DpadRight = 0x92,
|
||||
DpadLeft = 0x93,
|
||||
SystemFunctionShift = 0x97,
|
||||
SystemFunctionShiftLock = 0x98,
|
||||
SystemDismissNotification = 0x9A,
|
||||
SystemDoNotDisturb = 0x9B,
|
||||
Dock = 0xA0,
|
||||
Undock = 0xA1,
|
||||
Setup = 0xA2,
|
||||
Break = 0xA3,
|
||||
DebuggerBreak = 0xA4,
|
||||
ApplicationBreak = 0xA5,
|
||||
ApplicationDebuggerBreak = 0xA6,
|
||||
SpeakerMute = 0xA7,
|
||||
Hibernate = 0xA8,
|
||||
DisplayInvert = 0xB0,
|
||||
DisplayInternal = 0xB1,
|
||||
DisplayExternal = 0xB2,
|
||||
DisplayBoth = 0xB3,
|
||||
DisplayDual = 0xB4,
|
||||
DisplayToggleInternalExternal = 0xB5,
|
||||
DisplaySwapPrimarySecondary = 0xB6,
|
||||
DisplayLcdAutoscale = 0xB7,
|
||||
// Use this reserved value to represent all reserved keys / invalid values
|
||||
Reserved = 0xB8,
|
||||
}
|
||||
|
||||
impl From<SystemControlKey> for u8 {
|
||||
fn from(sck: SystemControlKey) -> u8 {
|
||||
sck as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for SystemControlKey {
|
||||
fn from(k: u8) -> Self {
|
||||
match k {
|
||||
0x81 => Self::PowerDown,
|
||||
0x82 => Self::Sleep,
|
||||
0x83 => Self::WakeUp,
|
||||
0x84 => Self::ContextMenu,
|
||||
0x85 => Self::MainMenu,
|
||||
0x86 => Self::AppMenu,
|
||||
0x87 => Self::MenuHelp,
|
||||
0x88 => Self::MenuExit,
|
||||
0x89 => Self::MenuSelect,
|
||||
0x8A => Self::MenuRight,
|
||||
0x8B => Self::MenuLeft,
|
||||
0x8C => Self::MenuUp,
|
||||
0x8D => Self::MenuDown,
|
||||
0x8E => Self::ColdRestart,
|
||||
0x8F => Self::WarmRestart,
|
||||
0x90 => Self::DpadUp,
|
||||
0x91 => Self::DpadDown,
|
||||
0x92 => Self::DpadRight,
|
||||
0x93 => Self::DpadLeft,
|
||||
0x97 => Self::SystemFunctionShift,
|
||||
0x98 => Self::SystemFunctionShiftLock,
|
||||
0x9A => Self::SystemDismissNotification,
|
||||
0x9B => Self::SystemDoNotDisturb,
|
||||
0xA0 => Self::Dock,
|
||||
0xA1 => Self::Undock,
|
||||
0xA2 => Self::Setup,
|
||||
0xA3 => Self::Break,
|
||||
0xA4 => Self::DebuggerBreak,
|
||||
0xA5 => Self::ApplicationBreak,
|
||||
0xA6 => Self::ApplicationDebuggerBreak,
|
||||
0xA7 => Self::SpeakerMute,
|
||||
0xA8 => Self::Hibernate,
|
||||
0xB0 => Self::DisplayInvert,
|
||||
0xB1 => Self::DisplayInternal,
|
||||
0xB2 => Self::DisplayExternal,
|
||||
0xB3 => Self::DisplayBoth,
|
||||
0xB4 => Self::DisplayDual,
|
||||
0xB5 => Self::DisplayToggleInternalExternal,
|
||||
0xB6 => Self::DisplaySwapPrimarySecondary,
|
||||
0xB7 => Self::DisplayLcdAutoscale,
|
||||
_ => Self::Reserved,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CtapReport describes a report and its companion descriptor that can be
|
||||
/// used to present a FIDO-compatible authenticator device to the host.
|
||||
#[gen_hid_descriptor(
|
||||
(collection = APPLICATION, usage_page = FIDO_ALLIANCE, usage = U2F_AUTHENTICATOR_DEVICE) = {
|
||||
(usage = INPUT_REPORT_DATA, logical_min = 0x0) = {
|
||||
#[item_settings data,variable,absolute] data_in=input;
|
||||
};
|
||||
(usage = OUTPUT_REPORT_DATA, logical_min = 0x0) = {
|
||||
#[item_settings data,variable,absolute] data_out=output;
|
||||
};
|
||||
}
|
||||
)]
|
||||
#[allow(dead_code)]
|
||||
pub struct CtapReport {
|
||||
pub data_in: [u8; 64],
|
||||
pub data_out: [u8; 64],
|
||||
}
|
||||
Vendored
+706
@@ -0,0 +1,706 @@
|
||||
//! Implements HID functionality for a usb-device device.
|
||||
use usb_device::class_prelude::*;
|
||||
use usb_device::Result;
|
||||
|
||||
use crate::descriptor::AsInputReport;
|
||||
extern crate ssmarshal;
|
||||
use ssmarshal::serialize;
|
||||
|
||||
const USB_CLASS_HID: u8 = 0x03;
|
||||
|
||||
// HID
|
||||
const HID_DESC_DESCTYPE_HID: u8 = 0x21;
|
||||
const HID_DESC_DESCTYPE_HID_REPORT: u8 = 0x22;
|
||||
const HID_DESC_SPEC_1_10: [u8; 2] = [0x10, 0x01];
|
||||
|
||||
/// Requests the set idle rate from the device
|
||||
/// See (7.2.3): <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||
const HID_REQ_GET_IDLE: u8 = 0x02;
|
||||
|
||||
/// Requests device to not send a particular report until a new event occurs
|
||||
/// or the specified amount of time passes.
|
||||
/// See (7.2.4): <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||
const HID_REQ_SET_IDLE: u8 = 0x0a;
|
||||
|
||||
/// Requests the active protocol on the device (boot or report)
|
||||
/// See (7.2.5): <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||
const HID_REQ_GET_PROTOCOL: u8 = 0x03;
|
||||
|
||||
/// Switches the device between boot and report protocols. Devices must default
|
||||
/// to report protocol, it is the reponsibility of the host to set the device
|
||||
/// to boot protocol (NOTE: Sadly many OSs, BIOSs and bootloaders do not adhere
|
||||
/// to the USB spec here).
|
||||
/// See (7.2.6): <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||
const HID_REQ_SET_PROTOCOL: u8 = 0x0b;
|
||||
|
||||
/// Allows a host to receive a report via the Control pipe
|
||||
/// See (7.2.1): <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||
const HID_REQ_GET_REPORT: u8 = 0x01;
|
||||
|
||||
/// Allows the host to send a report to the device via the Control pipe
|
||||
/// See (7.2.2): <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||
const HID_REQ_SET_REPORT: u8 = 0x09;
|
||||
|
||||
/// See CONTROL_BUF_LEN from usb-device.git src/control_pipe.rs
|
||||
/// Will need to revisit how this is set once usb-device has true HiSpeed USB support.
|
||||
const CONTROL_BUF_LEN: usize = 128;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ReportType {
|
||||
Input = 1,
|
||||
Output = 2,
|
||||
Feature = 3,
|
||||
Reserved,
|
||||
}
|
||||
|
||||
impl From<u8> for ReportType {
|
||||
fn from(rt: u8) -> ReportType {
|
||||
match rt {
|
||||
1 => ReportType::Input,
|
||||
2 => ReportType::Output,
|
||||
3 => ReportType::Feature,
|
||||
_ => ReportType::Reserved,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct ReportInfo {
|
||||
pub report_type: ReportType,
|
||||
pub report_id: u8,
|
||||
pub len: usize,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
struct Report {
|
||||
info: ReportInfo,
|
||||
buf: [u8; CONTROL_BUF_LEN],
|
||||
}
|
||||
|
||||
/// List of official USB HID country codes
|
||||
/// See (6.2.1): <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum HidCountryCode {
|
||||
NotSupported = 0,
|
||||
Arabic = 1,
|
||||
Belgian = 2,
|
||||
CanadianBilingual = 3,
|
||||
CanadianFrench = 4,
|
||||
CzechRepublic = 5,
|
||||
Danish = 6,
|
||||
Finnish = 7,
|
||||
French = 8,
|
||||
German = 9,
|
||||
Greek = 10,
|
||||
Hebrew = 11,
|
||||
Hungary = 12,
|
||||
InternationalISO = 13,
|
||||
Italian = 14,
|
||||
JapanKatakana = 15,
|
||||
Korean = 16,
|
||||
LatinAmerica = 17,
|
||||
NetherlandsDutch = 18,
|
||||
Norwegian = 19,
|
||||
PersianFarsi = 20,
|
||||
Poland = 21,
|
||||
Portuguese = 22,
|
||||
Russia = 23,
|
||||
Slovakia = 24,
|
||||
Spanish = 25,
|
||||
Swedish = 26,
|
||||
SwissFrench = 27,
|
||||
SwissGerman = 28,
|
||||
Switzerland = 29,
|
||||
Taiwan = 30,
|
||||
TurkishQ = 31,
|
||||
UK = 32,
|
||||
US = 33,
|
||||
Yugoslavia = 34,
|
||||
TurkishF = 35,
|
||||
}
|
||||
|
||||
/// Used to enable Boot mode descriptors for Mouse and Keyboard devices.
|
||||
/// See (4.2): <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||
/// Boot mode descriptors are fixed and must follow a strict format.
|
||||
/// See (Appendix F): <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum HidSubClass {
|
||||
NoSubClass = 0,
|
||||
Boot = 1,
|
||||
}
|
||||
|
||||
/// Defines fixed packet format
|
||||
/// Only used if HidSubClass::Boot(1) is set
|
||||
/// See (4.3): <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum HidProtocol {
|
||||
Generic = 0,
|
||||
Keyboard = 1,
|
||||
Mouse = 2,
|
||||
}
|
||||
|
||||
/// Get/Set Protocol mapping
|
||||
/// See (7.2.5 and 7.2.6): <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum HidProtocolMode {
|
||||
Boot = 0,
|
||||
Report = 1,
|
||||
}
|
||||
|
||||
impl From<u8> for HidProtocolMode {
|
||||
fn from(mode: u8) -> HidProtocolMode {
|
||||
if mode == HidProtocolMode::Boot as u8 {
|
||||
HidProtocolMode::Boot
|
||||
} else {
|
||||
HidProtocolMode::Report
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// It is often necessary to override OS behavior in order to get around OS (and application) level
|
||||
/// bugs. Forcing either Boot mode (6KRO) and Report mode (NKRO) are often necessary for NKRO
|
||||
/// compatible keyboards. Mice that support boot mode are not common and generally only useful for
|
||||
/// legacy OSs.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ProtocolModeConfig {
|
||||
/// Allows the host to define boot or report mode. Defaults to report mode.
|
||||
DefaultBehavior,
|
||||
/// Forces protocol mode to boot mode
|
||||
ForceBoot,
|
||||
/// Forces protocol mode to report mode
|
||||
ForceReport,
|
||||
}
|
||||
|
||||
/// Used to define specialized HID device settings
|
||||
/// Most commonly used to setup Boot Mode (6KRO) or Report Mode (NKRO) keyboards.
|
||||
/// Some OSs will also respect the HID locale setting of the keyboard to help choose the OS
|
||||
/// keyboard layout.
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct HidClassSettings {
|
||||
pub subclass: HidSubClass,
|
||||
pub protocol: HidProtocol,
|
||||
pub config: ProtocolModeConfig,
|
||||
pub locale: HidCountryCode,
|
||||
}
|
||||
|
||||
impl Default for HidClassSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
subclass: HidSubClass::NoSubClass,
|
||||
protocol: HidProtocol::Generic,
|
||||
config: ProtocolModeConfig::DefaultBehavior,
|
||||
locale: HidCountryCode::NotSupported,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// HIDClass provides an interface to declare, read & write HID reports.
|
||||
///
|
||||
/// Users are expected to provide the report descriptor, as well as pack
|
||||
/// and unpack reports which are read or staged for transmission.
|
||||
pub struct HIDClass<'a, B: UsbBus> {
|
||||
if_num: InterfaceNumber,
|
||||
/// Low-latency OUT buffer
|
||||
out_ep: Option<EndpointOut<'a, B>>,
|
||||
/// Low-latency IN buffer
|
||||
in_ep: Option<EndpointIn<'a, B>>,
|
||||
report_descriptor: &'static [u8],
|
||||
/// Control endpoint alternative OUT buffer (always used for setting feature reports)
|
||||
/// See: <https://www.usb.org/sites/default/files/documents/hid1_11.pdf> 7.2.1 and 7.2.2
|
||||
set_report_buf: Option<Report>,
|
||||
/// Used only by Keyboard and Mouse to define BIOS (Boot) mode vs Normal (Report) mode.
|
||||
/// This is used to switch between 6KRO (boot) and NKRO (report) endpoints.
|
||||
/// Boot mode configured endpoints may not parse the hid descriptor and expect an exact
|
||||
/// hid packet format. By default a device should start in normal (report) mode and the host
|
||||
/// must request the boot mode protocol if it requires it.
|
||||
///
|
||||
/// If a device does not request boot mode, this is a host bug. For convenience this API allows
|
||||
/// manually setting the protocol.
|
||||
/// See <https://www.usb.org/sites/default/files/hid1_11.pdf> Section 7.2.6
|
||||
protocol: Option<HidProtocolMode>,
|
||||
settings: HidClassSettings,
|
||||
}
|
||||
|
||||
fn determine_protocol_setting(settings: &HidClassSettings) -> Option<HidProtocolMode> {
|
||||
if settings.protocol == HidProtocol::Keyboard || settings.protocol == HidProtocol::Mouse {
|
||||
match settings.config {
|
||||
ProtocolModeConfig::DefaultBehavior | ProtocolModeConfig::ForceReport => {
|
||||
Some(HidProtocolMode::Report)
|
||||
}
|
||||
ProtocolModeConfig::ForceBoot => Some(HidProtocolMode::Boot),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: UsbBus> HIDClass<'_, B> {
|
||||
/// Creates a new HIDClass with the provided UsbBus & HID report descriptor.
|
||||
///
|
||||
/// poll_ms configures how frequently the host should poll for reading/writing
|
||||
/// HID reports. A lower value means better throughput & latency, at the expense
|
||||
/// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for
|
||||
/// high performance uses, and a value of 255 is good for best-effort usecases.
|
||||
///
|
||||
/// This allocates two endpoints (IN and OUT).
|
||||
/// See new_ep_in (IN endpoint only) and new_ep_out (OUT endpoint only) to only create a single
|
||||
/// endpoint.
|
||||
///
|
||||
/// See new_with_settings() if you need to define protocol or locale settings for a IN/OUT
|
||||
/// HID interface.
|
||||
pub fn new<'a>(
|
||||
alloc: &'a UsbBusAllocator<B>,
|
||||
report_descriptor: &'static [u8],
|
||||
poll_ms: u8,
|
||||
) -> HIDClass<'a, B> {
|
||||
let settings = HidClassSettings::default();
|
||||
HIDClass {
|
||||
if_num: alloc.interface(),
|
||||
out_ep: Some(alloc.interrupt(64, poll_ms)),
|
||||
in_ep: Some(alloc.interrupt(64, poll_ms)),
|
||||
report_descriptor,
|
||||
set_report_buf: None,
|
||||
protocol: determine_protocol_setting(&settings),
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as new() but includes a settings field.
|
||||
/// The settings field is used to define both locale and protocol settings of the HID
|
||||
/// device (needed for HID keyboard and Mice).
|
||||
pub fn new_with_settings<'a>(
|
||||
alloc: &'a UsbBusAllocator<B>,
|
||||
report_descriptor: &'static [u8],
|
||||
poll_ms: u8,
|
||||
settings: HidClassSettings,
|
||||
) -> HIDClass<'a, B> {
|
||||
HIDClass {
|
||||
if_num: alloc.interface(),
|
||||
out_ep: Some(alloc.interrupt(64, poll_ms)),
|
||||
in_ep: Some(alloc.interrupt(64, poll_ms)),
|
||||
report_descriptor,
|
||||
set_report_buf: None,
|
||||
protocol: determine_protocol_setting(&settings),
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new HIDClass with the provided UsbBus & HID report descriptor.
|
||||
/// See new() for more details.
|
||||
/// Please use new_ep_in_with_settings() if you are creating a keyboard or mouse.
|
||||
pub fn new_ep_in<'a>(
|
||||
alloc: &'a UsbBusAllocator<B>,
|
||||
report_descriptor: &'static [u8],
|
||||
poll_ms: u8,
|
||||
) -> HIDClass<'a, B> {
|
||||
let settings = HidClassSettings::default();
|
||||
HIDClass {
|
||||
if_num: alloc.interface(),
|
||||
out_ep: None,
|
||||
in_ep: Some(alloc.interrupt(64, poll_ms)),
|
||||
report_descriptor,
|
||||
set_report_buf: None,
|
||||
protocol: determine_protocol_setting(&settings),
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as new_ep_in() but includes a settings field.
|
||||
/// The settings field is used to define both locale and protocol settings of the HID
|
||||
/// device (needed for HID keyboard and Mice).
|
||||
pub fn new_ep_in_with_settings<'a>(
|
||||
alloc: &'a UsbBusAllocator<B>,
|
||||
report_descriptor: &'static [u8],
|
||||
poll_ms: u8,
|
||||
settings: HidClassSettings,
|
||||
) -> HIDClass<'a, B> {
|
||||
HIDClass {
|
||||
if_num: alloc.interface(),
|
||||
out_ep: None,
|
||||
in_ep: Some(alloc.interrupt(64, poll_ms)),
|
||||
report_descriptor,
|
||||
set_report_buf: None,
|
||||
protocol: determine_protocol_setting(&settings),
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new HIDClass with the provided UsbBus & HID report descriptor.
|
||||
/// See new() for more details.
|
||||
/// Please use new_ep_out_with_settings if you need the settings field.
|
||||
pub fn new_ep_out<'a>(
|
||||
alloc: &'a UsbBusAllocator<B>,
|
||||
report_descriptor: &'static [u8],
|
||||
poll_ms: u8,
|
||||
) -> HIDClass<'a, B> {
|
||||
let settings = HidClassSettings::default();
|
||||
HIDClass {
|
||||
if_num: alloc.interface(),
|
||||
out_ep: Some(alloc.interrupt(64, poll_ms)),
|
||||
in_ep: None,
|
||||
report_descriptor,
|
||||
set_report_buf: None,
|
||||
protocol: determine_protocol_setting(&settings),
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as new_ep_out() but includes a settings field.
|
||||
/// This should be uncommon (non-standard), but is included for completeness as there
|
||||
/// may be cases where setting the locale is useful.
|
||||
pub fn new_ep_out_with_settings<'a>(
|
||||
alloc: &'a UsbBusAllocator<B>,
|
||||
report_descriptor: &'static [u8],
|
||||
poll_ms: u8,
|
||||
settings: HidClassSettings,
|
||||
) -> HIDClass<'a, B> {
|
||||
HIDClass {
|
||||
if_num: alloc.interface(),
|
||||
out_ep: Some(alloc.interrupt(64, poll_ms)),
|
||||
in_ep: None,
|
||||
report_descriptor,
|
||||
set_report_buf: None,
|
||||
protocol: determine_protocol_setting(&settings),
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to write an input report by serializing the given report structure.
|
||||
/// A BufferOverflow error is returned if the serialized report is greater than
|
||||
/// 64 bytes in size.
|
||||
pub fn push_input<IR: AsInputReport>(&self, r: &IR) -> Result<usize> {
|
||||
// Do not push data if protocol settings do not match (only for keyboard and mouse)
|
||||
match self.settings.protocol {
|
||||
HidProtocol::Keyboard | HidProtocol::Mouse => {
|
||||
if let Some(protocol) = self.protocol {
|
||||
if (protocol == HidProtocolMode::Report
|
||||
&& self.settings.subclass != HidSubClass::NoSubClass)
|
||||
|| (protocol == HidProtocolMode::Boot
|
||||
&& self.settings.subclass != HidSubClass::Boot)
|
||||
{
|
||||
return Err(UsbError::InvalidState);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(ep) = &self.in_ep {
|
||||
let mut buff: [u8; 64] = [0; 64];
|
||||
let size = match serialize(&mut buff, r) {
|
||||
Ok(l) => l,
|
||||
Err(_) => return Err(UsbError::BufferOverflow),
|
||||
};
|
||||
ep.write(&buff[0..size])
|
||||
} else {
|
||||
Err(UsbError::InvalidEndpoint)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to write an input (device-to-host) report from the given raw bytes.
|
||||
/// Data is expected to be a valid HID report for INPUT items. If report ID's
|
||||
/// were used in the descriptor, the report ID corresponding to this report
|
||||
/// must be be present before the contents of the report.
|
||||
pub fn push_raw_input(&self, data: &[u8]) -> Result<usize> {
|
||||
// Do not push data if protocol settings do not match (only for keyboard and mouse)
|
||||
match self.settings.protocol {
|
||||
HidProtocol::Keyboard | HidProtocol::Mouse => {
|
||||
if let Some(protocol) = self.protocol {
|
||||
if (protocol == HidProtocolMode::Report
|
||||
&& self.settings.subclass != HidSubClass::NoSubClass)
|
||||
|| (protocol == HidProtocolMode::Boot
|
||||
&& self.settings.subclass != HidSubClass::Boot)
|
||||
{
|
||||
return Err(UsbError::InvalidState);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(ep) = &self.in_ep {
|
||||
ep.write(data)
|
||||
} else {
|
||||
Err(UsbError::InvalidEndpoint)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to read an output (host-to-device) report as raw bytes. Data
|
||||
/// is expected to be sized appropriately to contain any valid HID report
|
||||
/// for OUTPUT items, including the report ID prefix if report IDs are used.
|
||||
pub fn pull_raw_output(&self, data: &mut [u8]) -> Result<usize> {
|
||||
if let Some(ep) = &self.out_ep {
|
||||
ep.read(data)
|
||||
} else {
|
||||
Err(UsbError::InvalidEndpoint)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to read an incoming SET_REPORT report as raw bytes.
|
||||
/// Unlike OUT endpoints, report IDs are not prefixed in the buffer. Use the returned tuple
|
||||
/// instead to determine the buffer's usage.
|
||||
///
|
||||
/// The most common usage of pull_raw_report is for keyboard lock LED status if an OUT endpoint
|
||||
/// is not defined. It is not necessary to call this function if you're not going to be using
|
||||
/// SET_REPORT functionality.
|
||||
pub fn pull_raw_report(&mut self, data: &mut [u8]) -> Result<ReportInfo> {
|
||||
let info = match &self.set_report_buf {
|
||||
Some(set_report_buf) => {
|
||||
let info = set_report_buf.info;
|
||||
|
||||
// Make sure the given buffer is large enough for the stored report
|
||||
if data.len() < info.len {
|
||||
return Err(UsbError::BufferOverflow);
|
||||
}
|
||||
|
||||
// Copy buffer
|
||||
data[..info.len].copy_from_slice(&set_report_buf.buf[..info.len]);
|
||||
info
|
||||
}
|
||||
None => {
|
||||
return Err(UsbError::WouldBlock);
|
||||
}
|
||||
};
|
||||
|
||||
// Clear the report
|
||||
self.set_report_buf = None;
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
/// Retrieves the currently set device protocol
|
||||
/// This is equivalent to the USB HID GET_PROTOCOL request
|
||||
/// See (7.2.5): <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||
pub fn get_protocol_mode(&self) -> Result<HidProtocolMode> {
|
||||
// Protocol mode only has meaning if Keyboard or Mouse Protocol is set
|
||||
match self.settings.protocol {
|
||||
HidProtocol::Keyboard | HidProtocol::Mouse => {}
|
||||
_ => {
|
||||
return Err(UsbError::Unsupported);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(protocol) = self.protocol {
|
||||
Ok(protocol)
|
||||
} else {
|
||||
Err(UsbError::InvalidState)
|
||||
}
|
||||
}
|
||||
|
||||
/// Forcibly sets the device protocol
|
||||
/// This is equivalent to the USB HID SET_PROTOCOL request.
|
||||
/// NOTE: If the OS does not support the new mode, the device may no longer work correctly.
|
||||
/// See (7.2.6): <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||
///
|
||||
/// If either, ForceBoot or ForceReport are set in config, the mode argument is ignored.
|
||||
/// In addition, if ForceBoot or ForceReport are set, then any SET_PROTOCOL requests are also
|
||||
/// ignored.
|
||||
pub fn set_protocol_mode(
|
||||
&mut self,
|
||||
mode: HidProtocolMode,
|
||||
config: ProtocolModeConfig,
|
||||
) -> Result<()> {
|
||||
// Protocol mode only has meaning if Keyboard or Mouse Protocol is set
|
||||
match self.settings.protocol {
|
||||
HidProtocol::Keyboard | HidProtocol::Mouse => {}
|
||||
_ => {
|
||||
return Err(UsbError::Unsupported);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the protocol setting behavior and update the protocol mode
|
||||
match config {
|
||||
ProtocolModeConfig::DefaultBehavior => self.protocol = Some(mode),
|
||||
ProtocolModeConfig::ForceBoot => {
|
||||
self.protocol = Some(HidProtocolMode::Boot);
|
||||
}
|
||||
ProtocolModeConfig::ForceReport => {
|
||||
self.protocol = Some(HidProtocolMode::Report);
|
||||
}
|
||||
}
|
||||
self.settings.config = config;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: UsbBus> UsbClass<B> for HIDClass<'_, B> {
|
||||
fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<()> {
|
||||
writer.interface(
|
||||
self.if_num,
|
||||
USB_CLASS_HID,
|
||||
self.settings.subclass as u8,
|
||||
self.settings.protocol as u8,
|
||||
)?;
|
||||
|
||||
// HID descriptor
|
||||
writer.write(
|
||||
HID_DESC_DESCTYPE_HID,
|
||||
&[
|
||||
// HID Class spec version
|
||||
HID_DESC_SPEC_1_10[0],
|
||||
HID_DESC_SPEC_1_10[1],
|
||||
self.settings.locale as u8,
|
||||
// Number of following descriptors
|
||||
1,
|
||||
// We have a HID report descriptor the host should read
|
||||
HID_DESC_DESCTYPE_HID_REPORT,
|
||||
// HID report descriptor size,
|
||||
(self.report_descriptor.len() & 0xFF) as u8,
|
||||
(self.report_descriptor.len() >> 8 & 0xFF) as u8,
|
||||
],
|
||||
)?;
|
||||
|
||||
if let Some(ep) = &self.out_ep {
|
||||
writer.endpoint(ep)?;
|
||||
}
|
||||
if let Some(ep) = &self.in_ep {
|
||||
writer.endpoint(ep)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Handle control requests to the host.
|
||||
fn control_in(&mut self, xfer: ControlIn<B>) {
|
||||
let req = xfer.request();
|
||||
|
||||
// Bail out if its not relevant to our interface.
|
||||
if req.index != u8::from(self.if_num) as u16 {
|
||||
return;
|
||||
}
|
||||
|
||||
match (req.request_type, req.request) {
|
||||
(control::RequestType::Standard, control::Request::GET_DESCRIPTOR) => {
|
||||
match (req.value >> 8) as u8 {
|
||||
HID_DESC_DESCTYPE_HID_REPORT => {
|
||||
xfer.accept_with_static(self.report_descriptor).ok();
|
||||
}
|
||||
HID_DESC_DESCTYPE_HID => {
|
||||
let buf = &[
|
||||
// Length of buf inclusive of size prefix
|
||||
9,
|
||||
// Descriptor type
|
||||
HID_DESC_DESCTYPE_HID,
|
||||
// HID Class spec version
|
||||
HID_DESC_SPEC_1_10[0],
|
||||
HID_DESC_SPEC_1_10[1],
|
||||
self.settings.locale as u8,
|
||||
// Number of following descriptors
|
||||
1,
|
||||
// We have a HID report descriptor the host should read
|
||||
HID_DESC_DESCTYPE_HID_REPORT,
|
||||
// HID report descriptor size,
|
||||
(self.report_descriptor.len() & 0xFF) as u8,
|
||||
(self.report_descriptor.len() >> 8 & 0xFF) as u8,
|
||||
];
|
||||
xfer.accept_with(buf).ok();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
(control::RequestType::Class, HID_REQ_GET_REPORT) => {
|
||||
// To support GET_REPORT correctly each request must be serviced immediately.
|
||||
// This complicates the current API and may require a standing copy of each
|
||||
// of the possible IN reports (as well as any FEATURE reports as well).
|
||||
// For most projects, GET_REPORT won't be necessary so until a project comes along
|
||||
// with a need for it, I think it's safe to leave unsupported.
|
||||
// See: https://www.usb.org/sites/default/files/documents/hid1_11.pdf 7.2.1
|
||||
xfer.reject().ok(); // Not supported for now
|
||||
}
|
||||
(control::RequestType::Class, HID_REQ_GET_IDLE) => {
|
||||
// XXX (HaaTa): As a note for future readers
|
||||
// GET/SET_IDLE tends to be rather buggy on the host side
|
||||
// macOS is known to set SET_IDLE for keyboards but most other OSs do not.
|
||||
// I haven't had much success in the past trying to enable GET/SET_IDLE for
|
||||
// macOS (it seems to expose other bugs in the macOS hid stack).
|
||||
// The interesting part is that SET_IDLE is not called for official Apple
|
||||
// keyboards. So beyond getting 100% compliance from the USB compliance tools
|
||||
// IDLE is useless (at least with respect to keyboards). Modern USB host
|
||||
// controllers should never have a problem keeping up with slow HID devices.
|
||||
//
|
||||
// To implement this correctly it would require integration with higher-level
|
||||
// functions to handle report expiry.
|
||||
// See https://www.usb.org/sites/default/files/documents/hid1_11.pdf 7.2.4
|
||||
//
|
||||
// Each Report ID can be configured independently.
|
||||
xfer.reject().ok(); // Not supported for now
|
||||
}
|
||||
(control::RequestType::Class, HID_REQ_GET_PROTOCOL) => {
|
||||
// Only accept in supported configurations
|
||||
if let Some(protocol) = self.protocol {
|
||||
xfer.accept_with(&[protocol as u8]).ok();
|
||||
} else {
|
||||
xfer.reject().ok();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle a control request from the host.
|
||||
fn control_out(&mut self, xfer: ControlOut<B>) {
|
||||
let req = xfer.request();
|
||||
|
||||
// Bail out if its not relevant to our interface.
|
||||
if !(req.recipient == control::Recipient::Interface
|
||||
&& req.index == u8::from(self.if_num) as u16)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
match req.request {
|
||||
HID_REQ_SET_IDLE => {
|
||||
xfer.accept().ok();
|
||||
}
|
||||
HID_REQ_SET_PROTOCOL => {
|
||||
// Only accept in supported configurations
|
||||
if let Some(_protocol) = self.protocol {
|
||||
// Only set if configured to
|
||||
if self.settings.config == ProtocolModeConfig::DefaultBehavior {
|
||||
self.protocol = Some(((req.value & 0xFF) as u8).into());
|
||||
}
|
||||
xfer.accept().ok();
|
||||
} else {
|
||||
xfer.reject().ok();
|
||||
}
|
||||
}
|
||||
HID_REQ_SET_REPORT => {
|
||||
let report_type = ((req.value >> 8) as u8).into();
|
||||
let report_id = (req.value & 0xFF) as u8;
|
||||
let len = req.length as usize;
|
||||
|
||||
// Validate that the incoming data isn't too large for the buffer
|
||||
if len > CONTROL_BUF_LEN {
|
||||
self.set_report_buf = None;
|
||||
xfer.reject().ok();
|
||||
} else {
|
||||
let mut buf: [u8; CONTROL_BUF_LEN] = [0; CONTROL_BUF_LEN];
|
||||
buf[..len].copy_from_slice(&xfer.data()[..len]);
|
||||
|
||||
// Overwrite previous buffer even if unused
|
||||
self.set_report_buf = Some(Report {
|
||||
info: ReportInfo {
|
||||
report_type,
|
||||
report_id,
|
||||
len,
|
||||
},
|
||||
buf,
|
||||
});
|
||||
xfer.accept().ok();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
xfer.reject().ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+271
@@ -0,0 +1,271 @@
|
||||
//! HID report descriptor generation & USB HID class implementation
|
||||
//!
|
||||
//! This crate implements components necessary to build a USB HID device. This
|
||||
//! includes generation of the report descriptor, serialization of input reports,
|
||||
//! and communicating with a host that implements USB HID.
|
||||
#![no_std]
|
||||
|
||||
pub use usb_device::{Result, UsbError};
|
||||
pub mod descriptor;
|
||||
pub mod hid_class;
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(unused_imports)]
|
||||
mod tests {
|
||||
use crate::descriptor::generator_prelude::*;
|
||||
use crate::descriptor::{KeyboardReport, MouseReport, SystemControlReport};
|
||||
|
||||
// This should generate this descriptor:
|
||||
// 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
|
||||
// 0x09, 0x01, // Usage (0x01)
|
||||
// 0xA1, 0x01, // Collection (Application)
|
||||
// 0x15, 0x00, // Logical Minimum (0)
|
||||
// 0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||
// 0x75, 0x08, // Report Size (8)
|
||||
// 0x95, 0x01, // Report Count (1)
|
||||
// 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
// 0x27, 0xFF, 0xFF, 0x00, 0x00, // Logical Maximum (65534)
|
||||
// 0x75, 0x10, // Report Size (16)
|
||||
// 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
// 0xC1, // End Collection
|
||||
#[gen_hid_descriptor(
|
||||
(collection = 0x01, usage = 0x01, usage_page = 0xff00) = {
|
||||
f1=input;
|
||||
f2=output;
|
||||
}
|
||||
)]
|
||||
#[allow(dead_code)]
|
||||
struct CustomUnaryUnsignedFrame {
|
||||
f1: u8,
|
||||
f2: u16,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_unsigned() {
|
||||
let expected = &[
|
||||
6u8, 0u8, 255u8, 9u8, 1u8, 161u8, 1u8, 21u8, 0u8, 38u8, 255u8, 0u8, 117u8, 8u8, 149u8,
|
||||
1u8, 129u8, 2u8, 39u8, 255u8, 255u8, 0u8, 0u8, 117u8, 16u8, 145u8, 2u8, 192u8,
|
||||
];
|
||||
assert_eq!(CustomUnaryUnsignedFrame::desc(), expected);
|
||||
}
|
||||
|
||||
// This should generate this descriptor:
|
||||
// 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
|
||||
// 0x09, 0x01, // Usage (0x01)
|
||||
// 0xA1, 0x01, // Collection (Application)
|
||||
// 0x17, 0x81, 0xFF, 0xFF, 0xFF, // Logical Minimum (-128)
|
||||
// 0x25, 0x7F, // Logical Maximum (127)
|
||||
// 0x75, 0x08, // Report Size (8)
|
||||
// 0x95, 0x01, // Report Count (1)
|
||||
// 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
// 0x17, 0x01, 0x80, 0xFF, 0xFF, // Logical Minimum (-32768)
|
||||
// 0x26, 0xFF, 0x7F, // Logical Maximum (32767)
|
||||
// 0x75, 0x10, // Report Size (16)
|
||||
// 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
// 0xC0, // End Collection
|
||||
#[gen_hid_descriptor(
|
||||
(collection = 0x01, usage = 0x01, usage_page = 0xff00) = {
|
||||
f1=input;
|
||||
f2=output;
|
||||
}
|
||||
)]
|
||||
#[allow(dead_code)]
|
||||
struct CustomUnarySignedFrame {
|
||||
f1: i8,
|
||||
f2: i16,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_signed() {
|
||||
let expected = &[
|
||||
6u8, 0u8, 255u8, 9u8, 1u8, 161u8, 1u8, 23u8, 129u8, 255u8, 255u8, 255u8, 37u8, 127u8,
|
||||
117u8, 8u8, 149u8, 1u8, 129u8, 2u8, 23u8, 1u8, 128u8, 255u8, 255u8, 38u8, 255u8, 127u8,
|
||||
117u8, 16u8, 145u8, 2u8, 192u8,
|
||||
];
|
||||
assert_eq!(CustomUnarySignedFrame::desc()[0..32], expected[0..32]);
|
||||
}
|
||||
|
||||
#[gen_hid_descriptor(
|
||||
(report_id = 0x01,) = {
|
||||
f1=input
|
||||
},
|
||||
(report_id = 0x02,) = {
|
||||
f2=input
|
||||
},
|
||||
)]
|
||||
#[allow(dead_code)]
|
||||
struct CustomMultiReport {
|
||||
f1: u8,
|
||||
f2: u8,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_reports() {
|
||||
let expected: &[u8] = &[
|
||||
133, 1, 21, 0, 38, 255, 0, 117, 8, 149, 1, 129, 2, 133, 2, 129, 2,
|
||||
];
|
||||
assert_eq!(CustomMultiReport::desc(), expected);
|
||||
}
|
||||
|
||||
// This should generate the following descriptor:
|
||||
// 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
|
||||
// 0x09, 0x01, // Usage (0x01)
|
||||
// 0xA1, 0x01, // Collection (Application)
|
||||
// 0x15, 0x00, // Logical Minimum (0)
|
||||
// 0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||
// 0x75, 0x08, // Report Size (8)
|
||||
// 0x95, 0x20, // Report Count (32)
|
||||
// 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
// 0xC0, // End Collection
|
||||
#[gen_hid_descriptor(
|
||||
(collection = 0x01, usage = 0x01, usage_page = 0xff00) = {
|
||||
buff=input;
|
||||
}
|
||||
)]
|
||||
#[allow(dead_code)]
|
||||
struct CustomArray {
|
||||
buff: [u8; 32],
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_array() {
|
||||
let expected: &[u8] = &[
|
||||
6, 0, 255, 9, 1, 161, 1, 21, 0, 38, 255, 0, 117, 8, 149, 32, 129, 2, 192,
|
||||
];
|
||||
assert_eq!(CustomArray::desc(), expected);
|
||||
}
|
||||
|
||||
#[gen_hid_descriptor(
|
||||
(collection = APPLICATION, usage_page = VENDOR_DEFINED_START, usage = 0x01) = {
|
||||
(usage_min = BUTTON_1, usage_max = BUTTON_3) = {
|
||||
#[item_settings data,variable,relative] f1=input;
|
||||
};
|
||||
}
|
||||
)]
|
||||
#[allow(dead_code)]
|
||||
struct CustomConst {
|
||||
f1: u8,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_const() {
|
||||
let expected = &[
|
||||
6u8, 0u8, 255u8, 9u8, 1u8, 161u8, 1u8, 25u8, 1u8, 41u8, 3u8, 21u8, 0u8, 38u8, 255u8,
|
||||
0u8, 117u8, 8u8, 149u8, 1u8, 129u8, 6u8, 192u8,
|
||||
];
|
||||
assert_eq!(CustomConst::desc(), expected);
|
||||
}
|
||||
|
||||
// This should generate the following descriptor:
|
||||
// 0x85, 0x01, // Report ID (1)
|
||||
// 0x15, 0x00, // Logical Minimum (0)
|
||||
// 0x25, 0x01, // Logical Maximum (1)
|
||||
// 0x75, 0x01, // Report Size (1)
|
||||
// 0x95, 0x03, // Report Count (3)
|
||||
// 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
// 0x95, 0x05, // Report Count (5)
|
||||
// 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
// 0x95, 0x09, // Report Count (9)
|
||||
// 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
// 0x95, 0x07, // Report Count (7)
|
||||
// 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
// 0x95, 0x14, // Report Count (20)
|
||||
// 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
// 0x95, 0x04, // Report Count (4)
|
||||
// 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
#[gen_hid_descriptor(
|
||||
(report_id = 0x01,) = {
|
||||
#[packed_bits 3] f1=input;
|
||||
#[packed_bits 9] f2=input;
|
||||
#[packed_bits 20] f3=input;
|
||||
}
|
||||
)]
|
||||
#[allow(dead_code)]
|
||||
struct CustomPackedBits {
|
||||
f1: u8,
|
||||
f2: u16,
|
||||
f3: [u8; 3],
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_packed_bits() {
|
||||
let expected = &[
|
||||
133u8, 1u8, 21u8, 0u8, 37u8, 1u8, 117u8, 1u8, 149u8, 3u8, 129u8, 2u8, 149u8, 5u8,
|
||||
129u8, 3u8, 149u8, 9u8, 129u8, 2u8, 149u8, 7u8, 129u8, 3u8, 149u8, 20u8, 129u8, 2u8,
|
||||
149u8, 4u8, 129u8, 3u8,
|
||||
];
|
||||
assert_eq!(CustomPackedBits::desc(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mouse_descriptor() {
|
||||
let expected = &[
|
||||
5u8, 1u8, 9u8, 2u8, 161u8, 1u8, 9u8, 1u8, 161u8, 0u8, 5u8, 9u8, 25u8, 1u8, 41u8, 8u8,
|
||||
21u8, 0u8, 37u8, 1u8, 117u8, 1u8, 149u8, 8u8, 129u8, 2u8, 5u8, 1u8, 9u8, 48u8, 23u8,
|
||||
129u8, 255u8, 255u8, 255u8, 37u8, 127u8, 117u8, 8u8, 149u8, 1u8, 129u8, 6u8, 9u8, 49u8,
|
||||
129u8, 6u8, 9u8, 56u8, 129u8, 6u8, 5u8, 12u8, 10u8, 56u8, 2u8, 129u8, 6u8, 192u8,
|
||||
192u8,
|
||||
];
|
||||
assert_eq!(MouseReport::desc()[0..32], expected[0..32]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keyboard_descriptor() {
|
||||
let expected = &[
|
||||
0x05, 0x01, // Usage Page (Generic Desktop)
|
||||
0x09, 0x06, // Usage (Keyboard)
|
||||
0xa1, 0x01, // Collection (Application)
|
||||
0x05, 0x07, // Usage Page (Key Codes)
|
||||
0x19, 0xe0, // Usage Minimum (224)
|
||||
0x29, 0xe7, // Usage Maximum (231)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x95, 0x08, // Report count (8)
|
||||
0x81, 0x02, // Input (Data, Variable, Absolute)
|
||||
0x19, 0x00, // Usage Minimum (0)
|
||||
0x29, 0xFF, // Usage Maximum (255)
|
||||
0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x03, // Input (Const, Variable, Absolute)
|
||||
0x05, 0x08, // Usage Page (LEDs)
|
||||
0x19, 0x01, // Usage Minimum (1)
|
||||
0x29, 0x05, // Usage Maximum (5)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x95, 0x05, // Report Count (5)
|
||||
0x91, 0x02, // Output (Data, Variable, Absolute)
|
||||
0x95, 0x03, // Report Count (3)
|
||||
0x91, 0x03, // Output (Constant, Variable, Absolute)
|
||||
0x05, 0x07, // Usage Page (Key Codes)
|
||||
0x19, 0x00, // Usage Minimum (0)
|
||||
0x29, 0xDD, // Usage Maximum (221)
|
||||
0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x06, // Report Count (6)
|
||||
0x81, 0x00, // Input (Data, Array, Absolute)
|
||||
0xc0, // End Collection
|
||||
];
|
||||
assert_eq!(KeyboardReport::desc(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_control_descriptor() {
|
||||
let expected = &[
|
||||
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||
0x09, 0x80, // Usage (Sys Control)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0x19, 0x81, // Usage Minimum (Sys Power Down)
|
||||
0x29, 0xB7, // Usage Maximum (Sys Display LCD Autoscale)
|
||||
0x15, 0x01, // Logical Minimum (1)
|
||||
0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81,
|
||||
0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0xC0, // End Collection
|
||||
];
|
||||
assert_eq!(SystemControlReport::desc(), expected);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user