//! Shared HID mapping helpers used by both the client and server crates. pub type KeyboardHidReport = [u8; 8]; /// Map a printable character to a USB HID usage plus modifier byte. /// /// Inputs: a Unicode scalar value that should be typed through the HID gadget. /// Outputs: `Some((usage, modifiers))` for supported ASCII characters, or /// `None` when the character cannot be represented by the current keyboard map. /// Why: server-side paste injection and client-side keymap tests must agree on /// the exact HID encoding so they do not drift apart over time. #[must_use] pub fn char_to_usage(c: char) -> Option<(u8, u8)> { let shift = 0x02; // left shift in HID modifier byte match c { 'a'..='z' => Some((0x04 + (c as u8 - b'a'), 0)), 'A'..='Z' => Some((0x04 + (c as u8 - b'A'), shift)), '1'..='9' => Some((0x1E + (c as u8 - b'1'), 0)), '0' => Some((0x27, 0)), '!' => Some((0x1E, shift)), '@' => Some((0x1F, shift)), '#' => Some((0x20, shift)), '$' => Some((0x21, shift)), '%' => Some((0x22, shift)), '^' => Some((0x23, shift)), '&' => Some((0x24, shift)), '*' => Some((0x25, shift)), '(' => Some((0x26, shift)), ')' => Some((0x27, shift)), '-' => Some((0x2D, 0)), '_' => Some((0x2D, shift)), '=' => Some((0x2E, 0)), '+' => Some((0x2E, shift)), '[' => Some((0x2F, 0)), '{' => Some((0x2F, shift)), ']' => Some((0x30, 0)), '}' => Some((0x30, shift)), '\\' => Some((0x31, 0)), '|' => Some((0x31, shift)), ';' => Some((0x33, 0)), ':' => Some((0x33, shift)), '\'' => Some((0x34, 0)), '"' => Some((0x34, shift)), '`' => Some((0x35, 0)), '~' => Some((0x35, shift)), ',' => Some((0x36, 0)), '<' => Some((0x36, shift)), '.' => Some((0x37, 0)), '>' => Some((0x37, shift)), '/' => Some((0x38, 0)), '?' => Some((0x38, shift)), ' ' => Some((0x2C, 0)), '\n' | '\r' => Some((0x28, 0)), '\t' => Some((0x2B, 0)), _ => None, } } /// Append the HID report sequence needed to type a character. /// /// Inputs: an output buffer plus the character that should be typed. /// Outputs: `true` when the character is supported and reports were appended. /// Why: some targets miss uppercase or shifted characters when they arrive as a /// single modifier+usage pulse, so we emit a more human-like modifier press, /// key press, key release, and modifier release sequence. pub fn append_char_reports(out: &mut Vec, c: char) -> bool { let Some((usage, mods)) = char_to_usage(c) else { return false; }; if mods != 0 { out.push([mods, 0, 0, 0, 0, 0, 0, 0]); out.push([mods, 0, usage, 0, 0, 0, 0, 0]); out.push([mods, 0, 0, 0, 0, 0, 0, 0]); out.push([0; 8]); } else { out.push([0, 0, usage, 0, 0, 0, 0, 0]); out.push([0; 8]); } true } #[cfg(test)] mod tests { use super::{append_char_reports, char_to_usage}; #[test] fn char_to_usage_maps_letters_numbers_and_shifted_symbols() { assert_eq!(char_to_usage('a'), Some((0x04, 0))); assert_eq!(char_to_usage('Z'), Some((0x1D, 0x02))); assert_eq!(char_to_usage('0'), Some((0x27, 0))); assert_eq!(char_to_usage('9'), Some((0x26, 0))); assert_eq!(char_to_usage(' '), Some((0x2C, 0))); assert_eq!(char_to_usage('\n'), Some((0x28, 0))); assert_eq!(char_to_usage('\t'), Some((0x2B, 0))); assert_eq!(char_to_usage('{'), Some((0x2F, 0x02))); assert_eq!(char_to_usage('~'), Some((0x35, 0x02))); assert_eq!(char_to_usage('?'), Some((0x38, 0x02))); } #[test] fn char_to_usage_rejects_unsupported_chars() { assert_eq!(char_to_usage('é'), None); assert_eq!(char_to_usage('\u{2603}'), None); } #[test] fn append_char_reports_expands_shifted_chars_into_four_steps() { let mut reports = Vec::new(); assert!(append_char_reports(&mut reports, 'A')); assert_eq!(reports.len(), 4); assert_eq!(reports[0], [0x02, 0, 0, 0, 0, 0, 0, 0]); assert_eq!(reports[1], [0x02, 0, 0x04, 0, 0, 0, 0, 0]); assert_eq!(reports[2], [0x02, 0, 0, 0, 0, 0, 0, 0]); assert_eq!(reports[3], [0; 8]); } #[test] fn append_char_reports_keeps_unshifted_chars_as_press_and_release() { let mut reports = Vec::new(); assert!(append_char_reports(&mut reports, 'a')); assert_eq!(reports.len(), 2); assert_eq!(reports[0], [0, 0, 0x04, 0, 0, 0, 0, 0]); assert_eq!(reports[1], [0; 8]); } #[test] fn append_char_reports_rejects_unsupported_chars() { let mut reports = Vec::new(); assert!(!append_char_reports(&mut reports, '🙂')); assert!(reports.is_empty()); } }