cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

ci/pipeline-ergonomics
goq / crates / sigil-host / src / input / keymap.rs
keymap.rs 22.7K
1#[cfg(target_os = "linux")]2use input_linux::Key;3#[cfg(any(target_os = "linux", test))]4use sigil_protocol::GamepadState;5#[cfg(any(target_os = "linux", test))]6use sigil_protocol::InputEvent;78#[cfg(any(target_os = "linux", test))]9#[derive(Clone, Copy, Debug, PartialEq, Eq)]10pub(crate) enum InputDeviceClass {11 Pointer,12 Keyboard,13 Gamepad,14}1516#[cfg(any(target_os = "linux", test))]17#[derive(Clone, Copy, Debug, PartialEq, Eq)]18pub(crate) enum PointerReportEvent {19 RelativeX(i32),20 RelativeY(i32),21 Synchronize,22}2324#[cfg(any(target_os = "linux", test))]25/// Emulate an absolute position on the relative-only appliance pointer.26///27/// This is correct only when Gamescope clamps the first report at the surface28/// origin and applies neither acceleration nor scaling to either report. The29/// hardware UAT verifies both the synchronized position and one subsequent30/// relative delta through the Xwayland pointer tracker.31pub(crate) fn pointer_position_sync_report(x: i32, y: i32) -> [PointerReportEvent; 6] {32 [33 PointerReportEvent::RelativeX(sigil_protocol::RELATIVE_POINTER_DELTA_MIN),34 PointerReportEvent::RelativeY(sigil_protocol::RELATIVE_POINTER_DELTA_MIN),35 PointerReportEvent::Synchronize,36 PointerReportEvent::RelativeX(x),37 PointerReportEvent::RelativeY(y),38 PointerReportEvent::Synchronize,39 ]40}4142#[cfg(any(target_os = "linux", test))]43pub(crate) fn input_device_class(event: &InputEvent) -> Option<InputDeviceClass> {44 match event {45 InputEvent::Probe => None,46 InputEvent::MouseMove { .. }47 | InputEvent::MouseMoveRelative { .. }48 | InputEvent::MousePositionSync { .. }49 | InputEvent::MouseClick { .. }50 | InputEvent::MouseDown { .. }51 | InputEvent::MouseUp { .. }52 | InputEvent::MouseScroll { .. } => Some(InputDeviceClass::Pointer),53 InputEvent::KeyDown { .. } | InputEvent::KeyUp { .. } | InputEvent::KeyClick { .. } => {54 Some(InputDeviceClass::Keyboard)55 }56 InputEvent::Gamepad { .. } => Some(InputDeviceClass::Gamepad),57 InputEvent::Text { .. } => None,58 }59}6061#[cfg(any(target_os = "linux", test))]62#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]63pub(crate) enum MappedKey {64 A,65 B,66 C,67 D,68 E,69 F,70 G,71 H,72 I,73 J,74 K,75 L,76 M,77 N,78 O,79 P,80 Q,81 R,82 S,83 T,84 U,85 V,86 W,87 X,88 Y,89 Z,90 Num0,91 Num1,92 Num2,93 Num3,94 Num4,95 Num5,96 Num6,97 Num7,98 Num8,99 Num9,100 Minus,101 Equal,102 LeftBrace,103 RightBrace,104 Backslash,105 Semicolon,106 Apostrophe,107 Grave,108 Comma,109 Dot,110 Slash,111 NonUsBackslash,112 Ro,113 Yen,114 Enter,115 Tab,116 Space,117 Backspace,118 Escape,119 LeftShift,120 RightShift,121 LeftCtrl,122 RightCtrl,123 LeftAlt,124 RightAlt,125 LeftMeta,126 RightMeta,127 Up,128 Down,129 Left,130 Right,131 Home,132 End,133 PageUp,134 PageDown,135 Insert,136 Delete,137 PrintScreen,138 F1,139 F2,140 F3,141 F4,142 F5,143 F6,144 F7,145 F8,146 F9,147 F10,148 F11,149 F12,150}151152#[cfg(any(target_os = "linux", test))]153pub(crate) const ALL_MAPPED_KEYS: &[MappedKey] = &[154 MappedKey::A,155 MappedKey::B,156 MappedKey::C,157 MappedKey::D,158 MappedKey::E,159 MappedKey::F,160 MappedKey::G,161 MappedKey::H,162 MappedKey::I,163 MappedKey::J,164 MappedKey::K,165 MappedKey::L,166 MappedKey::M,167 MappedKey::N,168 MappedKey::O,169 MappedKey::P,170 MappedKey::Q,171 MappedKey::R,172 MappedKey::S,173 MappedKey::T,174 MappedKey::U,175 MappedKey::V,176 MappedKey::W,177 MappedKey::X,178 MappedKey::Y,179 MappedKey::Z,180 MappedKey::Num0,181 MappedKey::Num1,182 MappedKey::Num2,183 MappedKey::Num3,184 MappedKey::Num4,185 MappedKey::Num5,186 MappedKey::Num6,187 MappedKey::Num7,188 MappedKey::Num8,189 MappedKey::Num9,190 MappedKey::Minus,191 MappedKey::Equal,192 MappedKey::LeftBrace,193 MappedKey::RightBrace,194 MappedKey::Backslash,195 MappedKey::Semicolon,196 MappedKey::Apostrophe,197 MappedKey::Grave,198 MappedKey::Comma,199 MappedKey::Dot,200 MappedKey::Slash,201 MappedKey::NonUsBackslash,202 MappedKey::Ro,203 MappedKey::Yen,204 MappedKey::Enter,205 MappedKey::Tab,206 MappedKey::Space,207 MappedKey::Backspace,208 MappedKey::Escape,209 MappedKey::LeftShift,210 MappedKey::RightShift,211 MappedKey::LeftCtrl,212 MappedKey::RightCtrl,213 MappedKey::LeftAlt,214 MappedKey::RightAlt,215 MappedKey::LeftMeta,216 MappedKey::RightMeta,217 MappedKey::Up,218 MappedKey::Down,219 MappedKey::Left,220 MappedKey::Right,221 MappedKey::Home,222 MappedKey::End,223 MappedKey::PageUp,224 MappedKey::PageDown,225 MappedKey::Insert,226 MappedKey::Delete,227 MappedKey::PrintScreen,228 MappedKey::F1,229 MappedKey::F2,230 MappedKey::F3,231 MappedKey::F4,232 MappedKey::F5,233 MappedKey::F6,234 MappedKey::F7,235 MappedKey::F8,236 MappedKey::F9,237 MappedKey::F10,238 MappedKey::F11,239 MappedKey::F12,240];241242#[cfg(any(target_os = "linux", test))]243pub(crate) fn map_key(value: &str) -> Option<MappedKey> {244 match value {245 "KeyA" => Some(MappedKey::A),246 "KeyB" => Some(MappedKey::B),247 "KeyC" => Some(MappedKey::C),248 "KeyD" => Some(MappedKey::D),249 "KeyE" => Some(MappedKey::E),250 "KeyF" => Some(MappedKey::F),251 "KeyG" => Some(MappedKey::G),252 "KeyH" => Some(MappedKey::H),253 "KeyI" => Some(MappedKey::I),254 "KeyJ" => Some(MappedKey::J),255 "KeyK" => Some(MappedKey::K),256 "KeyL" => Some(MappedKey::L),257 "KeyM" => Some(MappedKey::M),258 "KeyN" => Some(MappedKey::N),259 "KeyO" => Some(MappedKey::O),260 "KeyP" => Some(MappedKey::P),261 "KeyQ" => Some(MappedKey::Q),262 "KeyR" => Some(MappedKey::R),263 "KeyS" => Some(MappedKey::S),264 "KeyT" => Some(MappedKey::T),265 "KeyU" => Some(MappedKey::U),266 "KeyV" => Some(MappedKey::V),267 "KeyW" => Some(MappedKey::W),268 "KeyX" => Some(MappedKey::X),269 "KeyY" => Some(MappedKey::Y),270 "KeyZ" => Some(MappedKey::Z),271 "Digit0" => Some(MappedKey::Num0),272 "Digit1" => Some(MappedKey::Num1),273 "Digit2" => Some(MappedKey::Num2),274 "Digit3" => Some(MappedKey::Num3),275 "Digit4" => Some(MappedKey::Num4),276 "Digit5" => Some(MappedKey::Num5),277 "Digit6" => Some(MappedKey::Num6),278 "Digit7" => Some(MappedKey::Num7),279 "Digit8" => Some(MappedKey::Num8),280 "Digit9" => Some(MappedKey::Num9),281 "Minus" => Some(MappedKey::Minus),282 "Equal" => Some(MappedKey::Equal),283 "BracketLeft" => Some(MappedKey::LeftBrace),284 "BracketRight" => Some(MappedKey::RightBrace),285 "Backslash" => Some(MappedKey::Backslash),286 "IntlBackslash" => Some(MappedKey::NonUsBackslash),287 "IntlRo" => Some(MappedKey::Ro),288 "IntlYen" => Some(MappedKey::Yen),289 "Semicolon" => Some(MappedKey::Semicolon),290 "Quote" => Some(MappedKey::Apostrophe),291 "Backquote" => Some(MappedKey::Grave),292 "Comma" => Some(MappedKey::Comma),293 "Period" => Some(MappedKey::Dot),294 "Slash" => Some(MappedKey::Slash),295 "Enter" => Some(MappedKey::Enter),296 "Tab" => Some(MappedKey::Tab),297 "Space" => Some(MappedKey::Space),298 "Backspace" => Some(MappedKey::Backspace),299 "Escape" => Some(MappedKey::Escape),300 "ShiftLeft" => Some(MappedKey::LeftShift),301 "ShiftRight" => Some(MappedKey::RightShift),302 "ControlLeft" => Some(MappedKey::LeftCtrl),303 "ControlRight" => Some(MappedKey::RightCtrl),304 "AltLeft" => Some(MappedKey::LeftAlt),305 "AltRight" => Some(MappedKey::RightAlt),306 "MetaLeft" => Some(MappedKey::LeftMeta),307 "MetaRight" => Some(MappedKey::RightMeta),308 "Shift" => Some(MappedKey::LeftShift),309 "Control" => Some(MappedKey::LeftCtrl),310 "Alt" => Some(MappedKey::LeftAlt),311 "Meta" => Some(MappedKey::LeftMeta),312 "ArrowUp" => Some(MappedKey::Up),313 "ArrowDown" => Some(MappedKey::Down),314 "ArrowLeft" => Some(MappedKey::Left),315 "ArrowRight" => Some(MappedKey::Right),316 "Up" => Some(MappedKey::Up),317 "Down" => Some(MappedKey::Down),318 "Left" => Some(MappedKey::Left),319 "Right" => Some(MappedKey::Right),320 "Home" => Some(MappedKey::Home),321 "End" => Some(MappedKey::End),322 "PageUp" => Some(MappedKey::PageUp),323 "PageDown" => Some(MappedKey::PageDown),324 "Insert" => Some(MappedKey::Insert),325 "Delete" => Some(MappedKey::Delete),326 "PrintScreen" => Some(MappedKey::PrintScreen),327 "F1" => Some(MappedKey::F1),328 "F2" => Some(MappedKey::F2),329 "F3" => Some(MappedKey::F3),330 "F4" => Some(MappedKey::F4),331 "F5" => Some(MappedKey::F5),332 "F6" => Some(MappedKey::F6),333 "F7" => Some(MappedKey::F7),334 "F8" => Some(MappedKey::F8),335 "F9" => Some(MappedKey::F9),336 "F10" => Some(MappedKey::F10),337 "F11" => Some(MappedKey::F11),338 "F12" => Some(MappedKey::F12),339 _ => map_printable_us_key(value),340 }341}342343#[cfg(any(target_os = "linux", test))]344fn map_printable_us_key(value: &str) -> Option<MappedKey> {345 let mut chars = value.chars();346 let character = chars.next()?;347 if chars.next().is_some() || !character.is_ascii() {348 return None;349 }350 Some(match character {351 'a' | 'A' => MappedKey::A,352 'b' | 'B' => MappedKey::B,353 'c' | 'C' => MappedKey::C,354 'd' | 'D' => MappedKey::D,355 'e' | 'E' => MappedKey::E,356 'f' | 'F' => MappedKey::F,357 'g' | 'G' => MappedKey::G,358 'h' | 'H' => MappedKey::H,359 'i' | 'I' => MappedKey::I,360 'j' | 'J' => MappedKey::J,361 'k' | 'K' => MappedKey::K,362 'l' | 'L' => MappedKey::L,363 'm' | 'M' => MappedKey::M,364 'n' | 'N' => MappedKey::N,365 'o' | 'O' => MappedKey::O,366 'p' | 'P' => MappedKey::P,367 'q' | 'Q' => MappedKey::Q,368 'r' | 'R' => MappedKey::R,369 's' | 'S' => MappedKey::S,370 't' | 'T' => MappedKey::T,371 'u' | 'U' => MappedKey::U,372 'v' | 'V' => MappedKey::V,373 'w' | 'W' => MappedKey::W,374 'x' | 'X' => MappedKey::X,375 'y' | 'Y' => MappedKey::Y,376 'z' | 'Z' => MappedKey::Z,377 '0' | ')' => MappedKey::Num0,378 '1' | '!' => MappedKey::Num1,379 '2' | '@' => MappedKey::Num2,380 '3' | '#' => MappedKey::Num3,381 '4' | '$' => MappedKey::Num4,382 '5' | '%' => MappedKey::Num5,383 '6' | '^' => MappedKey::Num6,384 '7' | '&' => MappedKey::Num7,385 '8' | '*' => MappedKey::Num8,386 '9' | '(' => MappedKey::Num9,387 '-' | '_' => MappedKey::Minus,388 '=' | '+' => MappedKey::Equal,389 '[' | '{' => MappedKey::LeftBrace,390 ']' | '}' => MappedKey::RightBrace,391 '\\' | '|' => MappedKey::Backslash,392 ';' | ':' => MappedKey::Semicolon,393 '\'' | '"' => MappedKey::Apostrophe,394 '`' | '~' => MappedKey::Grave,395 ',' | '<' => MappedKey::Comma,396 '.' | '>' => MappedKey::Dot,397 '/' | '?' => MappedKey::Slash,398 ' ' => MappedKey::Space,399 _ => return None,400 })401}402403#[cfg(any(target_os = "linux", test))]404#[derive(Clone, Copy, Debug, PartialEq, Eq)]405pub(crate) enum GamepadButton {406 A,407 B,408 X,409 Y,410 LeftShoulder,411 RightShoulder,412 Back,413 Start,414 Guide,415 LeftStick,416 RightStick,417}418419#[cfg(any(target_os = "linux", test))]420#[derive(Clone, Copy, Debug, PartialEq, Eq)]421pub(crate) enum GamepadAxis {422 LeftX,423 LeftY,424 RightX,425 RightY,426 LeftTrigger,427 RightTrigger,428 DpadX,429 DpadY,430}431432#[cfg(any(target_os = "linux", test))]433#[derive(Clone, Copy, Debug, PartialEq, Eq)]434pub(crate) struct GamepadReport {435 pub(crate) buttons: [(GamepadButton, bool); 11],436 pub(crate) axes: [(GamepadAxis, i32); 8],437}438439#[cfg(any(target_os = "linux", test))]440pub(crate) fn map_gamepad_state(state: &GamepadState) -> GamepadReport {441 GamepadReport {442 buttons: [443 (GamepadButton::A, state.a),444 (GamepadButton::B, state.b),445 (GamepadButton::X, state.x),446 (GamepadButton::Y, state.y),447 (GamepadButton::LeftShoulder, state.left_shoulder),448 (GamepadButton::RightShoulder, state.right_shoulder),449 (GamepadButton::Back, state.back),450 (GamepadButton::Start, state.start),451 (GamepadButton::Guide, state.guide),452 (GamepadButton::LeftStick, state.left_stick),453 (GamepadButton::RightStick, state.right_stick),454 ],455 axes: [456 (GamepadAxis::LeftX, i32::from(state.left_x)),457 (GamepadAxis::LeftY, i32::from(state.left_y)),458 (GamepadAxis::RightX, i32::from(state.right_x)),459 (GamepadAxis::RightY, i32::from(state.right_y)),460 (GamepadAxis::LeftTrigger, i32::from(state.left_trigger)),461 (GamepadAxis::RightTrigger, i32::from(state.right_trigger)),462 (463 GamepadAxis::DpadX,464 i32::from(state.dpad_right) - i32::from(state.dpad_left),465 ),466 (467 GamepadAxis::DpadY,468 i32::from(state.dpad_down) - i32::from(state.dpad_up),469 ),470 ],471 }472}473474#[cfg(target_os = "linux")]475pub(crate) fn linux_key(key: MappedKey) -> Key {476 match key {477 MappedKey::A => Key::A,478 MappedKey::B => Key::B,479 MappedKey::C => Key::C,480 MappedKey::D => Key::D,481 MappedKey::E => Key::E,482 MappedKey::F => Key::F,483 MappedKey::G => Key::G,484 MappedKey::H => Key::H,485 MappedKey::I => Key::I,486 MappedKey::J => Key::J,487 MappedKey::K => Key::K,488 MappedKey::L => Key::L,489 MappedKey::M => Key::M,490 MappedKey::N => Key::N,491 MappedKey::O => Key::O,492 MappedKey::P => Key::P,493 MappedKey::Q => Key::Q,494 MappedKey::R => Key::R,495 MappedKey::S => Key::S,496 MappedKey::T => Key::T,497 MappedKey::U => Key::U,498 MappedKey::V => Key::V,499 MappedKey::W => Key::W,500 MappedKey::X => Key::X,501 MappedKey::Y => Key::Y,502 MappedKey::Z => Key::Z,503 MappedKey::Num0 => Key::Num0,504 MappedKey::Num1 => Key::Num1,505 MappedKey::Num2 => Key::Num2,506 MappedKey::Num3 => Key::Num3,507 MappedKey::Num4 => Key::Num4,508 MappedKey::Num5 => Key::Num5,509 MappedKey::Num6 => Key::Num6,510 MappedKey::Num7 => Key::Num7,511 MappedKey::Num8 => Key::Num8,512 MappedKey::Num9 => Key::Num9,513 MappedKey::Minus => Key::Minus,514 MappedKey::Equal => Key::Equal,515 MappedKey::LeftBrace => Key::LeftBrace,516 MappedKey::RightBrace => Key::RightBrace,517 MappedKey::Backslash => Key::Backslash,518 MappedKey::Semicolon => Key::Semicolon,519 MappedKey::Apostrophe => Key::Apostrophe,520 MappedKey::Grave => Key::Grave,521 MappedKey::Comma => Key::Comma,522 MappedKey::Dot => Key::Dot,523 MappedKey::Slash => Key::Slash,524 MappedKey::NonUsBackslash => Key::NonUsBackslashAndPipe,525 MappedKey::Ro => Key::Ro,526 MappedKey::Yen => Key::Yen,527 MappedKey::Enter => Key::Enter,528 MappedKey::Tab => Key::Tab,529 MappedKey::Space => Key::Space,530 MappedKey::Backspace => Key::Backspace,531 MappedKey::Escape => Key::Esc,532 MappedKey::LeftShift => Key::LeftShift,533 MappedKey::RightShift => Key::RightShift,534 MappedKey::LeftCtrl => Key::LeftCtrl,535 MappedKey::RightCtrl => Key::RightCtrl,536 MappedKey::LeftAlt => Key::LeftAlt,537 MappedKey::RightAlt => Key::RightAlt,538 MappedKey::LeftMeta => Key::LeftMeta,539 MappedKey::RightMeta => Key::RightMeta,540 MappedKey::Up => Key::Up,541 MappedKey::Down => Key::Down,542 MappedKey::Left => Key::Left,543 MappedKey::Right => Key::Right,544 MappedKey::Home => Key::Home,545 MappedKey::End => Key::End,546 MappedKey::PageUp => Key::PageUp,547 MappedKey::PageDown => Key::PageDown,548 MappedKey::Insert => Key::Insert,549 MappedKey::Delete => Key::Delete,550 MappedKey::PrintScreen => Key::Sysrq,551 MappedKey::F1 => Key::F1,552 MappedKey::F2 => Key::F2,553 MappedKey::F3 => Key::F3,554 MappedKey::F4 => Key::F4,555 MappedKey::F5 => Key::F5,556 MappedKey::F6 => Key::F6,557 MappedKey::F7 => Key::F7,558 MappedKey::F8 => Key::F8,559 MappedKey::F9 => Key::F9,560 MappedKey::F10 => Key::F10,561 MappedKey::F11 => Key::F11,562 MappedKey::F12 => Key::F12,563 }564}565566#[cfg(test)]567mod tests {568 use super::*;569570 #[test]571 fn protocol_events_route_to_disjoint_virtual_devices() {572 for event in [573 InputEvent::MouseMove { x: 1, y: 2 },574 InputEvent::MouseMoveRelative { dx: 1, dy: 2 },575 InputEvent::MousePositionSync { x: 10, y: 20 },576 InputEvent::MouseClick { b: 1 },577 InputEvent::MouseDown { b: 2 },578 InputEvent::MouseUp { b: 3 },579 InputEvent::MouseScroll { dx: 1, dy: -1 },580 ] {581 assert_eq!(input_device_class(&event), Some(InputDeviceClass::Pointer));582 }583 for event in [584 InputEvent::KeyDown { k: "a".into() },585 InputEvent::KeyUp { k: "a".into() },586 InputEvent::KeyClick { k: "a".into() },587 ] {588 assert_eq!(input_device_class(&event), Some(InputDeviceClass::Keyboard));589 }590 assert_eq!(591 input_device_class(&InputEvent::Gamepad {592 state: GamepadState::default(),593 }),594 Some(InputDeviceClass::Gamepad)595 );596 assert_eq!(597 input_device_class(&InputEvent::Text {598 s: "ignored".into()599 }),600 None601 );602 assert_eq!(input_device_class(&InputEvent::Probe), None);603 }604605 #[test]606 fn maps_client_named_keys_without_guessing() {607 assert_eq!(map_key("Enter"), Some(MappedKey::Enter));608 assert_eq!(map_key("Control"), Some(MappedKey::LeftCtrl));609 assert_eq!(map_key("PageDown"), Some(MappedKey::PageDown));610 assert_eq!(map_key("F13"), None);611 assert_eq!(map_key("CapsLock"), None);612 assert_eq!(map_key("Numpad1"), None);613 assert_eq!(map_key(""), None);614 }615616 #[test]617 fn maps_bounded_browser_physical_keys_for_games_and_layouts() {618 for (value, expected) in [619 ("KeyQ", MappedKey::Q),620 ("Digit2", MappedKey::Num2),621 ("BracketLeft", MappedKey::LeftBrace),622 ("IntlBackslash", MappedKey::NonUsBackslash),623 ("IntlRo", MappedKey::Ro),624 ("IntlYen", MappedKey::Yen),625 ("ShiftLeft", MappedKey::LeftShift),626 ("ShiftRight", MappedKey::RightShift),627 ("ControlLeft", MappedKey::LeftCtrl),628 ("ControlRight", MappedKey::RightCtrl),629 ("AltLeft", MappedKey::LeftAlt),630 ("AltRight", MappedKey::RightAlt),631 ("MetaLeft", MappedKey::LeftMeta),632 ("MetaRight", MappedKey::RightMeta),633 ("ArrowUp", MappedKey::Up),634 ("Insert", MappedKey::Insert),635 ("PrintScreen", MappedKey::PrintScreen),636 ("F1", MappedKey::F1),637 ("F12", MappedKey::F12),638 ] {639 assert_eq!(map_key(value), Some(expected), "{value}");640 assert!(ALL_MAPPED_KEYS.contains(&expected), "{value}");641 }642 let unique = ALL_MAPPED_KEYS643 .iter()644 .copied()645 .collect::<std::collections::BTreeSet<_>>();646 assert_eq!(unique.len(), ALL_MAPPED_KEYS.len());647 }648649 #[cfg(target_os = "linux")]650 #[test]651 fn extended_physical_keys_translate_to_exact_linux_evdev_codes() {652 for (mapped, expected) in [653 (MappedKey::F1, input_linux::Key::F1),654 (MappedKey::F12, input_linux::Key::F12),655 (MappedKey::Insert, input_linux::Key::Insert),656 (MappedKey::PrintScreen, input_linux::Key::Sysrq),657 (MappedKey::RightShift, input_linux::Key::RightShift),658 (MappedKey::RightCtrl, input_linux::Key::RightCtrl),659 (MappedKey::RightAlt, input_linux::Key::RightAlt),660 (MappedKey::RightMeta, input_linux::Key::RightMeta),661 (662 MappedKey::NonUsBackslash,663 input_linux::Key::NonUsBackslashAndPipe,664 ),665 (MappedKey::Ro, input_linux::Key::Ro),666 (MappedKey::Yen, input_linux::Key::Yen),667 ] {668 assert_eq!(linux_key(mapped), expected);669 }670 }671672 #[test]673 fn maps_us_printable_keys_to_physical_keys() {674 assert_eq!(map_key("a"), Some(MappedKey::A));675 assert_eq!(map_key("A"), Some(MappedKey::A));676 assert_eq!(map_key("!"), Some(MappedKey::Num1));677 assert_eq!(map_key("?"), Some(MappedKey::Slash));678 assert_eq!(map_key("é"), None);679 assert_eq!(map_key("ab"), None);680 }681682 #[test]683 fn pointer_position_sync_uses_two_ordered_relative_reports() {684 assert_eq!(685 pointer_position_sync_report(1_280, 800),686 [687 PointerReportEvent::RelativeX(-32_767),688 PointerReportEvent::RelativeY(-32_767),689 PointerReportEvent::Synchronize,690 PointerReportEvent::RelativeX(1_280),691 PointerReportEvent::RelativeY(800),692 PointerReportEvent::Synchronize,693 ]694 );695 }696697 #[test]698 fn maps_every_gamepad_control_into_one_bounded_report() {699 let state = GamepadState {700 a: true,701 b: true,702 x: true,703 y: true,704 left_shoulder: true,705 right_shoulder: true,706 back: true,707 start: true,708 guide: true,709 left_stick: true,710 right_stick: true,711 dpad_up: true,712 dpad_right: true,713 left_x: -32_767,714 left_y: 32_767,715 right_x: -123,716 right_y: 456,717 left_trigger: 12_345,718 right_trigger: 32_767,719 ..GamepadState::default()720 };721 let report = map_gamepad_state(&state);722 assert_eq!(report.buttons.len(), 11);723 assert!(report.buttons.into_iter().all(|(_, pressed)| pressed));724 assert_eq!(725 report.axes,726 [727 (GamepadAxis::LeftX, -32_767),728 (GamepadAxis::LeftY, 32_767),729 (GamepadAxis::RightX, -123),730 (GamepadAxis::RightY, 456),731 (GamepadAxis::LeftTrigger, 12_345),732 (GamepadAxis::RightTrigger, 32_767),733 (GamepadAxis::DpadX, 1),734 (GamepadAxis::DpadY, -1),735 ]736 );737 }738739 #[test]740 fn neutral_gamepad_report_releases_buttons_and_axes() {741 let report = map_gamepad_state(&GamepadState::default());742 assert!(report.buttons.into_iter().all(|(_, pressed)| !pressed));743 assert!(report.axes.into_iter().all(|(_, value)| value == 0));744 }745}