197 lines
7.8 KiB
Rust
197 lines
7.8 KiB
Rust
// client/src/input/keymap.rs
|
|
|
|
use evdev::KeyCode;
|
|
use lesavka_common::hid;
|
|
|
|
/// Return `Some(usage)` for a known `evdev::KeyCode -> HID usage` mapping.
|
|
///
|
|
/// Inputs: one evdev key code from the local input stack.
|
|
/// Outputs: the matching HID usage byte when the key is part of the supported
|
|
/// keyboard subset, or `None` when the key is intentionally unmapped.
|
|
/// Why: the client keeps the evdev-to-HID mapping in one place so the active
|
|
/// key set can be exercised directly by tests.
|
|
pub fn keycode_to_usage(key: KeyCode) -> Option<u8> {
|
|
match key {
|
|
// --- Letters ------------------------------------------------------
|
|
KeyCode::KEY_A => Some(0x04),
|
|
KeyCode::KEY_B => Some(0x05),
|
|
KeyCode::KEY_C => Some(0x06),
|
|
KeyCode::KEY_D => Some(0x07),
|
|
KeyCode::KEY_E => Some(0x08),
|
|
KeyCode::KEY_F => Some(0x09),
|
|
KeyCode::KEY_G => Some(0x0A),
|
|
KeyCode::KEY_H => Some(0x0B),
|
|
KeyCode::KEY_I => Some(0x0C),
|
|
KeyCode::KEY_J => Some(0x0D),
|
|
KeyCode::KEY_K => Some(0x0E),
|
|
KeyCode::KEY_L => Some(0x0F),
|
|
KeyCode::KEY_M => Some(0x10),
|
|
KeyCode::KEY_N => Some(0x11),
|
|
KeyCode::KEY_O => Some(0x12),
|
|
KeyCode::KEY_P => Some(0x13),
|
|
KeyCode::KEY_Q => Some(0x14),
|
|
KeyCode::KEY_R => Some(0x15),
|
|
KeyCode::KEY_S => Some(0x16),
|
|
KeyCode::KEY_T => Some(0x17),
|
|
KeyCode::KEY_U => Some(0x18),
|
|
KeyCode::KEY_V => Some(0x19),
|
|
KeyCode::KEY_W => Some(0x1A),
|
|
KeyCode::KEY_X => Some(0x1B),
|
|
KeyCode::KEY_Y => Some(0x1C),
|
|
KeyCode::KEY_Z => Some(0x1D),
|
|
|
|
// --- Number row ---------------------------------------------------
|
|
KeyCode::KEY_1 => Some(0x1E),
|
|
KeyCode::KEY_2 => Some(0x1F),
|
|
KeyCode::KEY_3 => Some(0x20),
|
|
KeyCode::KEY_4 => Some(0x21),
|
|
KeyCode::KEY_5 => Some(0x22),
|
|
KeyCode::KEY_6 => Some(0x23),
|
|
KeyCode::KEY_7 => Some(0x24),
|
|
KeyCode::KEY_8 => Some(0x25),
|
|
KeyCode::KEY_9 => Some(0x26),
|
|
KeyCode::KEY_0 => Some(0x27),
|
|
|
|
// --- Common punctuation -------------------------------------------
|
|
KeyCode::KEY_ENTER => Some(0x28),
|
|
KeyCode::KEY_ESC => Some(0x29),
|
|
KeyCode::KEY_BACKSPACE => Some(0x2A),
|
|
KeyCode::KEY_TAB => Some(0x2B),
|
|
KeyCode::KEY_SPACE => Some(0x2C),
|
|
KeyCode::KEY_MINUS => Some(0x2D),
|
|
KeyCode::KEY_EQUAL => Some(0x2E),
|
|
KeyCode::KEY_LEFTBRACE => Some(0x2F),
|
|
KeyCode::KEY_RIGHTBRACE => Some(0x30),
|
|
KeyCode::KEY_BACKSLASH => Some(0x31),
|
|
KeyCode::KEY_SEMICOLON => Some(0x33),
|
|
KeyCode::KEY_APOSTROPHE => Some(0x34),
|
|
KeyCode::KEY_GRAVE => Some(0x35),
|
|
KeyCode::KEY_COMMA => Some(0x36),
|
|
KeyCode::KEY_DOT => Some(0x37),
|
|
KeyCode::KEY_SLASH => Some(0x38),
|
|
|
|
// --- Function keys ------------------------------------------------
|
|
KeyCode::KEY_CAPSLOCK => Some(0x39),
|
|
KeyCode::KEY_F1 => Some(0x3A),
|
|
KeyCode::KEY_F2 => Some(0x3B),
|
|
KeyCode::KEY_F3 => Some(0x3C),
|
|
KeyCode::KEY_F4 => Some(0x3D),
|
|
KeyCode::KEY_F5 => Some(0x3E),
|
|
KeyCode::KEY_F6 => Some(0x3F),
|
|
KeyCode::KEY_F7 => Some(0x40),
|
|
KeyCode::KEY_F8 => Some(0x41),
|
|
KeyCode::KEY_F9 => Some(0x42),
|
|
KeyCode::KEY_F10 => Some(0x43),
|
|
KeyCode::KEY_F11 => Some(0x44),
|
|
KeyCode::KEY_F12 => Some(0x45),
|
|
|
|
// --- Navigation / editing cluster ---------------------------------
|
|
KeyCode::KEY_SYSRQ => Some(0x46), // Print-Screen
|
|
KeyCode::KEY_SCROLLLOCK => Some(0x47),
|
|
KeyCode::KEY_PAUSE => Some(0x48),
|
|
KeyCode::KEY_INSERT => Some(0x49),
|
|
KeyCode::KEY_HOME => Some(0x4A),
|
|
KeyCode::KEY_PAGEUP => Some(0x4B),
|
|
KeyCode::KEY_DELETE => Some(0x4C),
|
|
KeyCode::KEY_END => Some(0x4D),
|
|
KeyCode::KEY_PAGEDOWN => Some(0x4E),
|
|
KeyCode::KEY_RIGHT => Some(0x4F),
|
|
KeyCode::KEY_LEFT => Some(0x50),
|
|
KeyCode::KEY_DOWN => Some(0x51),
|
|
KeyCode::KEY_UP => Some(0x52),
|
|
|
|
// --- Keypad / Num-lock block --------------------------------------
|
|
KeyCode::KEY_NUMLOCK => Some(0x53),
|
|
KeyCode::KEY_KPSLASH => Some(0x54),
|
|
KeyCode::KEY_KPASTERISK => Some(0x55),
|
|
KeyCode::KEY_KPMINUS => Some(0x56),
|
|
KeyCode::KEY_KPPLUS => Some(0x57),
|
|
KeyCode::KEY_KPENTER => Some(0x58),
|
|
KeyCode::KEY_KP1 => Some(0x59),
|
|
KeyCode::KEY_KP2 => Some(0x5A),
|
|
KeyCode::KEY_KP3 => Some(0x5B),
|
|
KeyCode::KEY_KP4 => Some(0x5C),
|
|
KeyCode::KEY_KP5 => Some(0x5D),
|
|
KeyCode::KEY_KP6 => Some(0x5E),
|
|
KeyCode::KEY_KP7 => Some(0x5F),
|
|
KeyCode::KEY_KP8 => Some(0x60),
|
|
KeyCode::KEY_KP9 => Some(0x61),
|
|
KeyCode::KEY_KP0 => Some(0x62),
|
|
KeyCode::KEY_KPDOT => Some(0x63),
|
|
KeyCode::KEY_KPEQUAL => Some(0x67),
|
|
|
|
// --- Misc ---------------------------------------------------------
|
|
KeyCode::KEY_102ND => Some(0x64), // “<>” on ISO boards
|
|
KeyCode::KEY_MENU => Some(0x65), // Application / Compose
|
|
|
|
// We'll handle modifiers (ctrl, shift, alt, meta) in `is_modifier()`
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Return the modifier bit(s) for HID report byte 0.
|
|
///
|
|
/// Inputs: one evdev key code from the local input stack.
|
|
/// Outputs: the HID modifier mask for left/right ctrl, shift, alt, and meta
|
|
/// keys, or `None` when the key is not a modifier.
|
|
/// Why: modifier handling is split from the main usage map so the report
|
|
/// encoder can keep byte 0 separate from the key-code payload.
|
|
pub fn is_modifier(key: KeyCode) -> Option<u8> {
|
|
match key {
|
|
KeyCode::KEY_LEFTCTRL => Some(0x01),
|
|
KeyCode::KEY_LEFTSHIFT => Some(0x02),
|
|
KeyCode::KEY_LEFTALT => Some(0x04),
|
|
KeyCode::KEY_LEFTMETA => Some(0x08),
|
|
KeyCode::KEY_RIGHTCTRL => Some(0x10),
|
|
KeyCode::KEY_RIGHTSHIFT => Some(0x20),
|
|
KeyCode::KEY_RIGHTALT => Some(0x40),
|
|
KeyCode::KEY_RIGHTMETA => Some(0x80),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Map a printable character to `(usage, modifiers)`.
|
|
///
|
|
/// Inputs: one printable character from clipboard or keyboard input.
|
|
/// Outputs: the shared HID mapping used by both the client and the server,
|
|
/// including the shift bit for uppercase and punctuation variants.
|
|
/// Why: printable characters should round-trip through the shared HID helper
|
|
/// so both crates stay aligned on the exact keyboard layout.
|
|
pub fn char_to_usage(c: char) -> Option<(u8, u8)> {
|
|
hid::char_to_usage(c)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{char_to_usage, is_modifier, keycode_to_usage};
|
|
use evdev::KeyCode;
|
|
|
|
#[test]
|
|
fn keycode_to_usage_covers_common_keyboard_blocks() {
|
|
assert_eq!(keycode_to_usage(KeyCode::KEY_A), Some(0x04));
|
|
assert_eq!(keycode_to_usage(KeyCode::KEY_0), Some(0x27));
|
|
assert_eq!(keycode_to_usage(KeyCode::KEY_ENTER), Some(0x28));
|
|
assert_eq!(keycode_to_usage(KeyCode::KEY_F12), Some(0x45));
|
|
assert_eq!(keycode_to_usage(KeyCode::KEY_PAGEUP), Some(0x4B));
|
|
assert_eq!(keycode_to_usage(KeyCode::KEY_KP0), Some(0x62));
|
|
assert_eq!(keycode_to_usage(KeyCode::KEY_MENU), Some(0x65));
|
|
}
|
|
|
|
#[test]
|
|
fn is_modifier_handles_both_sides_of_each_modifier_pair() {
|
|
assert_eq!(is_modifier(KeyCode::KEY_LEFTCTRL), Some(0x01));
|
|
assert_eq!(is_modifier(KeyCode::KEY_LEFTSHIFT), Some(0x02));
|
|
assert_eq!(is_modifier(KeyCode::KEY_RIGHTALT), Some(0x40));
|
|
assert_eq!(is_modifier(KeyCode::KEY_RIGHTMETA), Some(0x80));
|
|
assert_eq!(is_modifier(KeyCode::KEY_A), None);
|
|
}
|
|
|
|
#[test]
|
|
fn char_to_usage_delegates_to_shared_hid_table() {
|
|
assert_eq!(char_to_usage('A'), Some((0x04, 0x02)));
|
|
assert_eq!(char_to_usage('/'), Some((0x38, 0x00)));
|
|
assert_eq!(char_to_usage('?'), Some((0x38, 0x02)));
|
|
assert_eq!(char_to_usage('\t'), Some((0x2B, 0x00)));
|
|
}
|
|
}
|