use crcbl_core::KeyCode;
use crcbl_core::input::PointerButton;
#[must_use]
pub const fn key_code(evdev: u32) -> Option<KeyCode> {
Some(match evdev {
1 => KeyCode::Escape, 2 => KeyCode::Digit1, 3 => KeyCode::Digit2, 4 => KeyCode::Digit3, 5 => KeyCode::Digit4, 6 => KeyCode::Digit5, 7 => KeyCode::Digit6, 8 => KeyCode::Digit7, 9 => KeyCode::Digit8, 10 => KeyCode::Digit9, 11 => KeyCode::Digit0, 12 => KeyCode::Minus, 13 => KeyCode::Equal, 14 => KeyCode::Backspace, 15 => KeyCode::Tab, 16 => KeyCode::KeyQ, 17 => KeyCode::KeyW, 18 => KeyCode::KeyE, 19 => KeyCode::KeyR, 20 => KeyCode::KeyT, 21 => KeyCode::KeyY, 22 => KeyCode::KeyU, 23 => KeyCode::KeyI, 24 => KeyCode::KeyO, 25 => KeyCode::KeyP, 26 => KeyCode::BracketLeft, 27 => KeyCode::BracketRight, 28 => KeyCode::Enter, 29 => KeyCode::ControlLeft, 30 => KeyCode::KeyA, 31 => KeyCode::KeyS, 32 => KeyCode::KeyD, 33 => KeyCode::KeyF, 34 => KeyCode::KeyG, 35 => KeyCode::KeyH, 36 => KeyCode::KeyJ, 37 => KeyCode::KeyK, 38 => KeyCode::KeyL, 39 => KeyCode::Semicolon, 40 => KeyCode::Quote, 41 => KeyCode::Backquote, 42 => KeyCode::ShiftLeft, 43 => KeyCode::Backslash, 44 => KeyCode::KeyZ, 45 => KeyCode::KeyX, 46 => KeyCode::KeyC, 47 => KeyCode::KeyV, 48 => KeyCode::KeyB, 49 => KeyCode::KeyN, 50 => KeyCode::KeyM, 51 => KeyCode::Comma, 52 => KeyCode::Period, 53 => KeyCode::Slash, 54 => KeyCode::ShiftRight, 55 => KeyCode::NumpadMultiply, 56 => KeyCode::AltLeft, 57 => KeyCode::Space, 58 => KeyCode::CapsLock, 59 => KeyCode::F1, 60 => KeyCode::F2, 61 => KeyCode::F3, 62 => KeyCode::F4, 63 => KeyCode::F5, 64 => KeyCode::F6, 65 => KeyCode::F7, 66 => KeyCode::F8, 67 => KeyCode::F9, 68 => KeyCode::F10, 69 => KeyCode::NumLock, 70 => KeyCode::ScrollLock, 71 => KeyCode::Numpad7, 72 => KeyCode::Numpad8, 73 => KeyCode::Numpad9, 74 => KeyCode::NumpadSubtract, 75 => KeyCode::Numpad4, 76 => KeyCode::Numpad5, 77 => KeyCode::Numpad6, 78 => KeyCode::NumpadAdd, 79 => KeyCode::Numpad1, 80 => KeyCode::Numpad2, 81 => KeyCode::Numpad3, 82 => KeyCode::Numpad0, 83 => KeyCode::NumpadDecimal, 86 => KeyCode::IntlBackslash, 87 => KeyCode::F11, 88 => KeyCode::F12, 89 => KeyCode::IntlRo, 96 => KeyCode::NumpadEnter, 97 => KeyCode::ControlRight, 98 => KeyCode::NumpadDivide, 99 => KeyCode::PrintScreen, 100 => KeyCode::AltRight, 102 => KeyCode::Home, 103 => KeyCode::ArrowUp, 104 => KeyCode::PageUp, 105 => KeyCode::ArrowLeft, 106 => KeyCode::ArrowRight, 107 => KeyCode::End, 108 => KeyCode::ArrowDown, 109 => KeyCode::PageDown, 110 => KeyCode::Insert, 111 => KeyCode::Delete, 119 => KeyCode::Pause, 124 => KeyCode::IntlYen, 125 => KeyCode::SuperLeft, 126 => KeyCode::SuperRight, 127 => KeyCode::ContextMenu, _ => return None,
})
}
const BTN_LEFT: u32 = 0x110;
const BTN_RIGHT: u32 = 0x111;
const BTN_MIDDLE: u32 = 0x112;
const BTN_SIDE: u32 = 0x113;
const BTN_EXTRA: u32 = 0x114;
#[must_use]
pub const fn pointer_button(evdev: u32) -> PointerButton {
match evdev {
BTN_LEFT => PointerButton::Left,
BTN_RIGHT => PointerButton::Right,
BTN_MIDDLE => PointerButton::Middle,
BTN_SIDE => PointerButton::Back,
BTN_EXTRA => PointerButton::Forward,
#[expect(
clippy::cast_possible_truncation,
reason = "evdev button codes live in 0x100..0x200; the truncation \
cannot be reached from a real device"
)]
other => PointerButton::Other(other as u16),
}
}
#[inline]
#[must_use]
pub fn fixed_to_f64(value: i32) -> f64 {
f64::from(value) / 256.0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wasd_and_the_gameplay_keys_map_to_their_evdev_codes() {
assert_eq!(key_code(17), Some(KeyCode::KeyW));
assert_eq!(key_code(30), Some(KeyCode::KeyA));
assert_eq!(key_code(31), Some(KeyCode::KeyS));
assert_eq!(key_code(32), Some(KeyCode::KeyD));
assert_eq!(key_code(57), Some(KeyCode::Space));
assert_eq!(key_code(1), Some(KeyCode::Escape));
assert_eq!(key_code(42), Some(KeyCode::ShiftLeft));
assert_eq!(key_code(54), Some(KeyCode::ShiftRight));
assert_eq!(key_code(125), Some(KeyCode::SuperLeft));
}
#[test]
fn every_named_key_is_reachable_from_some_evdev_code() {
let mut missing: Vec<&'static str> = Vec::new();
for key in KeyCode::ALL {
if !(0..=255).any(|code| key_code(code) == Some(*key)) {
missing.push(key.as_str());
}
}
assert!(missing.is_empty(), "unreachable KeyCodes: {missing:?}");
}
#[test]
fn no_evdev_code_maps_to_two_different_keys() {
let mut seen: Vec<KeyCode> = (0..=255).filter_map(key_code).collect();
let count = seen.len();
seen.sort_unstable();
seen.dedup();
assert_eq!(seen.len(), count, "two evdev codes map to one KeyCode");
assert_eq!(
count,
KeyCode::ALL.len(),
"the table is exactly the vocabulary"
);
}
#[test]
fn unnamed_keys_degrade_to_none_rather_than_to_a_wrong_key() {
for code in [0, 84, 85, 101, 113, 128, 190, 240, u32::MAX] {
assert_eq!(key_code(code), None, "evdev {code}");
}
}
#[test]
fn pointer_buttons_name_the_five_everyone_agrees_on() {
assert_eq!(pointer_button(0x110), PointerButton::Left);
assert_eq!(pointer_button(0x111), PointerButton::Right);
assert_eq!(pointer_button(0x112), PointerButton::Middle);
assert_eq!(pointer_button(0x113), PointerButton::Back);
assert_eq!(pointer_button(0x114), PointerButton::Forward);
assert_eq!(pointer_button(0x115), PointerButton::Other(0x115));
assert_eq!(pointer_button(0x120), PointerButton::Other(0x120));
}
#[test]
fn wl_fixed_keeps_the_sub_pixel_part() {
assert!((fixed_to_f64(256) - 1.0).abs() < f64::EPSILON);
assert!((fixed_to_f64(-256) + 1.0).abs() < f64::EPSILON);
assert!((fixed_to_f64(128) - 0.5).abs() < f64::EPSILON);
assert!((fixed_to_f64(-1) + 0.003_906_25).abs() < f64::EPSILON);
assert!((fixed_to_f64(0)).abs() < f64::EPSILON);
}
}