Skip to main content

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//! - **`defmt`** Add support for defmt library.
12//! - **`usbd-hid`** Add support for converting between the usbd-hid library KeyboardReport.
13//! - **`embassy-usb-host`** Add support for converting between the embassy-usb-host library KeyboardReport.
14//!
15//! # Example Usage
16//!
17//! ```toml
18//! [dependencies]
19//! key-mapping = "0.7"
20//! ```
21//!
22//! ```rust,editable
23//! use key_mapping::Keyboard;
24//!
25//! fn main() {
26//!     let dom_code = "KeyA";
27//!     let usage_id = Keyboard::US.dom_key_to_usage_id(dom_code).unwrap();
28//!
29//!     assert_eq!(0x04, *usage_id);
30//! }
31//! ```
32
33#![forbid(unsafe_code)]
34#![cfg_attr(not(feature = "std"), no_std)]
35#[cfg(feature = "serde")]
36use serde::{Deserialize, Serialize};
37
38include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
39
40pub const MODIFIER_CODE_CTRL: u8 = 1;
41pub const MODIFIER_CODE_SHIFT: u8 = 2;
42pub const MODIFIER_CODE_ALT: u8 = 4;
43pub const MODIFIER_CODE_META: u8 = 8;
44
45/// Keyboard layouts, used to convert between key-code types.
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
47#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
48#[cfg_attr(feature = "defmt", derive(defmt::Format))]
49pub enum Keyboard {
50    /// US keyboard layout *(default)*
51    #[default]
52    US,
53    /// UK keyboard layout
54    UK,
55}
56
57impl Keyboard {
58    /// Convert key-code into a hid usage id, using the given keyboard layout.
59    /// Uses a performant O(1) operation.
60    pub fn dom_key_to_usage_id(&self, key_code: &str) -> Option<&u8> {
61        match self {
62            Self::US => DOM_KEYS_US.get(key_code),
63            Self::UK => DOM_KEYS_UK.get(key_code),
64        }
65    }
66}
67
68/// Keyboard key type.
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
71#[cfg_attr(feature = "defmt", derive(defmt::Format))]
72pub enum MappedKeyType {
73    Special,
74    Modifier,
75    Printable,
76    Whitespace,
77    Navigation,
78    Editing,
79    Ui,
80    Device,
81    Function,
82    Numeric,
83}
84
85/// A single mapped keyboard key.
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
88#[cfg_attr(feature = "defmt", derive(defmt::Format))]
89pub struct MappedKey<'a> {
90    /// HID usage-id for keyboard key
91    pub usage_id: u8,
92    /// The DOM key representation
93    pub dom_key: &'a str,
94    /// Machine friendly key name
95    pub prefix: &'a str,
96    /// Human friendly key name
97    pub visual: &'a str,
98    /// The type of key
99    pub key_type: MappedKeyType,
100}
101
102/// A keyboard report, could be used for making key press/release events,
103/// Defaults to no keys or modifiers.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
106#[cfg_attr(feature = "defmt", derive(defmt::Format))]
107pub struct KeyboardReport<const N: usize = 6> {
108    /// Keys included in action, represented as usage-ids
109    #[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
110    pub keys: [Keys; N],
111    /// Whether ALT is held
112    pub alt: bool,
113    /// Whether CTRL is held
114    pub ctrl: bool,
115    /// Whether SHIFT is held
116    pub shift: bool,
117    /// Whether META is held
118    pub meta: bool,
119}
120
121impl Default for KeyboardReport {
122    fn default() -> Self {
123        Self {
124            keys: [
125                Keys::None,
126                Keys::None,
127                Keys::None,
128                Keys::None,
129                Keys::None,
130                Keys::None,
131            ],
132            alt: Default::default(),
133            ctrl: Default::default(),
134            shift: Default::default(),
135            meta: Default::default(),
136        }
137    }
138}
139
140#[cfg(feature = "usbd-hid")]
141impl From<KeyboardReport> for usbd_hid::descriptor::KeyboardReport {
142    fn from(value: KeyboardReport) -> Self {
143        let mut keycodes = [0; 6];
144        for (i, v) in value.keys.into_iter().map(|v| v as u8).enumerate() {
145            keycodes[i] = v;
146        }
147        Self {
148            modifier: value.get_modifer_code(),
149            reserved: 0,
150            leds: 0,
151            keycodes,
152        }
153    }
154}
155
156#[cfg(feature = "usbd-hid")]
157impl From<usbd_hid::descriptor::KeyboardReport> for KeyboardReport {
158    fn from(value: usbd_hid::descriptor::KeyboardReport) -> Self {
159        let mut keys = [Keys::None; 6];
160        for (i, v) in value.keycodes.into_iter().enumerate() {
161            keys[i] = v.try_into().unwrap_or(Keys::None);
162        }
163        Self {
164            keys,
165            alt: value.modifier & MODIFIER_CODE_ALT != 0,
166            ctrl: value.modifier & MODIFIER_CODE_CTRL != 0,
167            shift: value.modifier & MODIFIER_CODE_SHIFT != 0,
168            meta: value.modifier & MODIFIER_CODE_META != 0,
169        }
170    }
171}
172
173#[cfg(feature = "embassy-usb-host")]
174impl From<KeyboardReport> for embassy_usb_host::class::hid::KeyboardReport {
175    fn from(value: KeyboardReport) -> Self {
176        let mut keycodes = [0; 6];
177        for (i, v) in value.keys.into_iter().map(|v| v as u8).enumerate() {
178            keycodes[i] = v;
179        }
180        Self {
181            modifiers: value.get_modifer_code(),
182            keycodes,
183        }
184    }
185}
186
187#[cfg(feature = "embassy-usb-host")]
188impl From<embassy_usb_host::class::hid::KeyboardReport> for KeyboardReport {
189    fn from(value: embassy_usb_host::class::hid::KeyboardReport) -> Self {
190        let mut keys = [Keys::None; 6];
191        for (i, v) in value.keycodes.into_iter().enumerate() {
192            keys[i] = v.try_into().unwrap_or(Keys::None);
193        }
194        Self {
195            keys,
196            alt: value.alt(),
197            ctrl: value.ctrl(),
198            shift: value.shift(),
199            meta: value.gui(),
200        }
201    }
202}
203
204#[cfg(feature = "embassy-usb-host")]
205impl From<KeyboardReport> for embassy_usb_host::class::kbd::KeyStatusUpdate {
206    fn from(value: KeyboardReport) -> Self {
207        let mut keycodes = [None; 6];
208        for (i, v) in value.keys.into_iter().map(|v| v as u8).enumerate() {
209            keycodes[i] = core::num::NonZeroU8::new(v);
210        }
211        Self {
212            modifiers: value.get_modifer_code(),
213            reserved: 0,
214            keypress: keycodes,
215        }
216    }
217}
218
219#[cfg(feature = "embassy-usb-host")]
220impl From<embassy_usb_host::class::kbd::KeyStatusUpdate> for KeyboardReport {
221    fn from(value: embassy_usb_host::class::kbd::KeyStatusUpdate) -> Self {
222        let mut keys = [Keys::None; 6];
223        for (i, v) in value.keypress.into_iter().enumerate() {
224            keys[i] = v
225                .map(|v| v.get())
226                .unwrap_or(0)
227                .try_into()
228                .unwrap_or(Keys::None);
229        }
230        Self {
231            keys,
232            alt: value.modifiers & MODIFIER_CODE_ALT != 0,
233            ctrl: value.modifiers & MODIFIER_CODE_CTRL != 0,
234            shift: value.modifiers & MODIFIER_CODE_SHIFT != 0,
235            meta: value.modifiers & MODIFIER_CODE_META != 0,
236        }
237    }
238}
239
240impl KeyboardReport {
241    /// Get the modifiers as their code representation
242    pub fn get_modifer_code(&self) -> u8 {
243        let mut result = 0;
244        if self.ctrl {
245            result |= MODIFIER_CODE_CTRL;
246        }
247        if self.shift {
248            result |= MODIFIER_CODE_SHIFT;
249        }
250        if self.alt {
251            result |= MODIFIER_CODE_ALT;
252        }
253        if self.meta {
254            result |= MODIFIER_CODE_META;
255        }
256        result
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use crate::{Keyboard, Keys, MAPPED_KEYS, MappedKey, MappedKeyType};
263
264    #[test]
265    fn dom_key_to_hid() {
266        assert_eq!(0x04, *Keyboard::US.dom_key_to_usage_id("KeyA").unwrap());
267        assert_eq!(
268            0x31,
269            *Keyboard::US.dom_key_to_usage_id("Backslash").unwrap()
270        );
271        assert_eq!(
272            0x32,
273            *Keyboard::UK.dom_key_to_usage_id("Backslash").unwrap()
274        );
275    }
276
277    #[test]
278    fn u8_key_to_key() {
279        assert_eq!(Keys::try_from(0x04), Ok(Keys::A));
280        assert_eq!(Keys::try_from(0xff), Err(()));
281    }
282
283    #[test]
284    fn usage_id_to_mapping() {
285        assert_eq!(
286            MAPPED_KEYS.get(&0x04),
287            Some(&MappedKey {
288                usage_id: 0x04,
289                dom_key: "KeyA",
290                prefix: "A",
291                visual: "A",
292                key_type: MappedKeyType::Printable,
293            })
294        );
295    }
296}