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