key_mapping/
lib.rs

1#![doc(html_playground_url = "https://play.rust-lang.org/")]
2//! key-mapping library allows for keyboard key code conversion between systems such as the DOM and
3//! HID usage-ids. With Rust `[no_std]` support.
4//!
5//! # Features
6//!
7//! Extra functionality is behind optional features to optimize compile time and binary size.
8//!
9//! - **`std`** *(enabled by default)* - Add support for Rust's libstd types.
10//! - **`serde`** Add support for `serde` de/serializing library.
11//! - **`usbd-hid`** Add support for converting between the usbd-hid library KeyboardReport.
12//!
13//! # Example Usage
14//!
15//! ```toml
16//! [dependencies]
17//! key-mapping = "0.4"
18//! ```
19//!
20//! ```rust,editable
21//! use key_mapping::Keyboard;
22//!
23//! fn main() {
24//!     let dom_code = "KeyA";
25//!     let usage_id = Keyboard::US.dom_key_to_usage_id(dom_code).unwrap();
26//!
27//!     assert_eq!(0x04, *usage_id);
28//! }
29//! ```
30
31#![forbid(unsafe_code)]
32#![cfg_attr(not(feature = "std"), no_std)]
33#[cfg(feature = "serde")]
34use serde::{Deserialize, Serialize};
35
36include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
37
38pub const MODIFIER_CODE_CTRL: u8 = 1;
39pub const MODIFIER_CODE_SHIFT: u8 = 2;
40pub const MODIFIER_CODE_ALT: u8 = 4;
41pub const MODIFIER_CODE_META: u8 = 8;
42
43/// Keyboard layouts, used to convert between key-code types.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
45#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
46pub enum Keyboard {
47    /// US keyboard layout *(default)*
48    #[default]
49    US,
50    /// UK keyboard layout
51    UK,
52}
53
54impl Keyboard {
55    /// Convert key-code into a hid usage id, using the given keyboard layout.
56    /// Uses a performant O(1) operation.
57    pub fn dom_key_to_usage_id(&self, key_code: &str) -> Option<&u8> {
58        match self {
59            Self::US => DOM_KEYS_US.get(key_code),
60            Self::UK => DOM_KEYS_UK.get(key_code),
61        }
62    }
63}
64
65/// A single mapped keyboard key.
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
68pub struct MappedKey<'a> {
69    /// HID usage-id for keyboard key
70    pub usage_id: u8,
71    /// The DOM key representation
72    pub dom_key: &'a str,
73    /// Machine friendly key name
74    pub prefix: &'a str,
75    /// Human friendly key name
76    pub visual: &'a str,
77}
78
79/// A keyboard action, could be used for making key press/release events,
80/// Defaults to no keys or modifiers.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
83pub struct KeyboardAction {
84    /// Keys included in action, represented as usage-ids
85    pub keys: [Keys; 6],
86    /// Whether ALT is held
87    pub alt: bool,
88    /// Whether CTRL is held
89    pub ctrl: bool,
90    /// Whether SHIFT is held
91    pub shift: bool,
92    /// Whether META is held
93    pub meta: bool,
94}
95
96impl Default for KeyboardAction {
97    fn default() -> Self {
98        Self {
99            keys: [
100                Keys::None,
101                Keys::None,
102                Keys::None,
103                Keys::None,
104                Keys::None,
105                Keys::None,
106            ],
107            alt: Default::default(),
108            ctrl: Default::default(),
109            shift: Default::default(),
110            meta: Default::default(),
111        }
112    }
113}
114
115#[cfg(feature = "usbd-hid")]
116impl From<KeyboardAction> for usbd_hid::descriptor::KeyboardReport {
117    fn from(value: KeyboardAction) -> Self {
118        let mut keycodes = [0; 6];
119        for (i, v) in value.keys.into_iter().map(|v| v as u8).enumerate() {
120            keycodes[i] = v;
121        }
122        Self {
123            modifier: value.get_modifer_code(),
124            reserved: 0,
125            leds: 0,
126            keycodes,
127        }
128    }
129}
130
131impl KeyboardAction {
132    /// Get the modifiers as their code representation
133    pub fn get_modifer_code(&self) -> u8 {
134        let mut result = 0;
135        if self.ctrl {
136            result |= MODIFIER_CODE_CTRL;
137        }
138        if self.shift {
139            result |= MODIFIER_CODE_SHIFT;
140        }
141        if self.alt {
142            result |= MODIFIER_CODE_ALT;
143        }
144        if self.meta {
145            result |= MODIFIER_CODE_META;
146        }
147        result
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use crate::{Keyboard, Keys, MappedKey, MAPPED_KEYS};
154
155    #[test]
156    fn dom_key_to_hid() {
157        assert_eq!(0x04, *Keyboard::US.dom_key_to_usage_id("KeyA").unwrap());
158        assert_eq!(
159            0x31,
160            *Keyboard::US.dom_key_to_usage_id("Backslash").unwrap()
161        );
162        assert_eq!(
163            0x32,
164            *Keyboard::UK.dom_key_to_usage_id("Backslash").unwrap()
165        );
166    }
167
168    #[test]
169    fn u8_key_to_key() {
170        assert_eq!(Keys::try_from(0x04), Ok(Keys::A));
171        assert_eq!(Keys::try_from(0xff), Err(()));
172    }
173
174    #[test]
175    fn usage_id_to_mapping() {
176        assert_eq!(
177            MAPPED_KEYS.get(&0x04),
178            Some(&MappedKey {
179                usage_id: 0x04,
180                dom_key: "KeyA",
181                prefix: "A",
182                visual: "A",
183            })
184        );
185    }
186}