1#![doc(html_playground_url = "https://play.rust-lang.org/")]
2#![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#[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 #[default]
52 US,
53 UK,
55}
56
57impl Keyboard {
58 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#[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#[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 pub usage_id: u8,
92 pub dom_key: &'a str,
94 pub prefix: &'a str,
96 pub visual: &'a str,
98 pub key_type: MappedKeyType,
100}
101
102#[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 #[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
110 pub keys: [Keys; N],
111 pub alt: bool,
113 pub ctrl: bool,
115 pub shift: bool,
117 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 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}