1#![doc(html_playground_url = "https://play.rust-lang.org/")]
2#![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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
46#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
47pub enum Keyboard {
48 #[default]
50 US,
51 UK,
53}
54
55impl Keyboard {
56 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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
85pub struct MappedKey<'a> {
86 pub usage_id: u8,
88 pub dom_key: &'a str,
90 pub prefix: &'a str,
92 pub visual: &'a str,
94 pub key_type: MappedKeyType,
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
102pub struct KeyboardReport<const N: usize = 6> {
103 #[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
105 pub keys: [Keys; N],
106 pub alt: bool,
108 pub ctrl: bool,
110 pub shift: bool,
112 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 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}