//! SDL scancodes / mouse buttons → the punktfunk input wire contract. //! //! Same VK codes as `pf_client_core::keymap` (the wire carries Windows Virtual-Keys, the //! GameStream convention), keyed on SDL scancodes instead of evdev codes: SDL normalizes //! the platform keycode to its USB-HID-derived scancode table, so this stays //! layout-independent exactly like the evdev table. Coverage mirrors `keymap::evdev_to_vk` //! one-to-one — a key the wire contract doesn't cover (media keys etc.) returns `None` //! and is dropped rather than guessed. use sdl3::keyboard::Scancode; /// Map an SDL scancode to the Windows VK code the host expects. pub fn scancode_to_vk(sc: Scancode) -> Option { use Scancode as S; Some(match sc { // --- Navigation / editing / whitespace --- S::Backspace => 0x08, S::Tab => 0x09, S::Return => 0x0D, S::Pause => 0x13, S::CapsLock => 0x14, S::Escape => 0x1B, S::Space => 0x20, S::PageUp => 0x21, S::PageDown => 0x22, S::End => 0x23, S::Home => 0x24, S::Left => 0x25, S::Up => 0x26, S::Right => 0x27, S::Down => 0x28, S::PrintScreen => 0x2C, S::Insert => 0x2D, S::Delete => 0x2E, // --- Digit row --- S::_0 => 0x30, S::_1 => 0x31, S::_2 => 0x32, S::_3 => 0x33, S::_4 => 0x34, S::_5 => 0x35, S::_6 => 0x36, S::_7 => 0x37, S::_8 => 0x38, S::_9 => 0x39, // --- Letters --- S::A => 0x41, S::B => 0x42, S::C => 0x43, S::D => 0x44, S::E => 0x45, S::F => 0x46, S::G => 0x47, S::H => 0x48, S::I => 0x49, S::J => 0x4A, S::K => 0x4B, S::L => 0x4C, S::M => 0x4D, S::N => 0x4E, S::O => 0x4F, S::P => 0x50, S::Q => 0x51, S::R => 0x52, S::S => 0x53, S::T => 0x54, S::U => 0x55, S::V => 0x56, S::W => 0x57, S::X => 0x58, S::Y => 0x59, S::Z => 0x5A, // --- Meta / context-menu --- S::LGui => 0x5B, S::RGui => 0x5C, S::Application => 0x5D, // --- Numpad --- S::Kp0 => 0x60, S::Kp1 => 0x61, S::Kp2 => 0x62, S::Kp3 => 0x63, S::Kp4 => 0x64, S::Kp5 => 0x65, S::Kp6 => 0x66, S::Kp7 => 0x67, S::Kp8 => 0x68, S::Kp9 => 0x69, S::KpMultiply => 0x6A, S::KpPlus => 0x6B, // KP-Enter → VK_SEPARATOR mirrors the evdev table (KEY_KPENTER → 0x6C). S::KpEnter => 0x6C, S::KpMinus => 0x6D, S::KpPeriod => 0x6E, S::KpDivide => 0x6F, // --- Function keys --- S::F1 => 0x70, S::F2 => 0x71, S::F3 => 0x72, S::F4 => 0x73, S::F5 => 0x74, S::F6 => 0x75, S::F7 => 0x76, S::F8 => 0x77, S::F9 => 0x78, S::F10 => 0x79, S::F11 => 0x7A, S::F12 => 0x7B, // --- Locks --- S::NumLockClear => 0x90, S::ScrollLock => 0x91, // --- Left/right modifiers --- S::LShift => 0xA0, S::RShift => 0xA1, S::LCtrl => 0xA2, S::RCtrl => 0xA3, S::LAlt => 0xA4, S::RAlt => 0xA5, // --- OEM punctuation (US-layout positions) --- S::Semicolon => 0xBA, S::Equals => 0xBB, S::Comma => 0xBC, S::Minus => 0xBD, S::Period => 0xBE, S::Slash => 0xBF, S::Grave => 0xC0, S::LeftBracket => 0xDB, S::Backslash => 0xDC, S::RightBracket => 0xDD, S::Apostrophe => 0xDE, S::NonUsBackslash => 0xE2, _ => return None, }) } /// SDL mouse button → the GameStream button id the wire expects (1=left, 2=middle, /// 3=right, 4=X1, 5=X2) — the SDL twin of `keymap::gdk_button_to_gs`. pub fn mouse_button_to_gs(b: sdl3::mouse::MouseButton) -> Option { use sdl3::mouse::MouseButton as B; Some(match b { B::Left => 1, B::Middle => 2, B::Right => 3, B::X1 => 4, B::X2 => 5, _ => return None, }) } #[cfg(test)] mod tests { use super::*; use pf_client_core::keymap::evdev_to_vk; /// Both tables feed the same wire: for every key the evdev table knows, the SDL /// scancode of the same physical key must map to the same VK. #[test] fn agrees_with_the_evdev_table() { use Scancode as S; let same_key: &[(Scancode, u16)] = &[ (S::Backspace, 14), (S::Tab, 15), (S::Return, 28), (S::Escape, 1), (S::Space, 57), (S::PageUp, 104), (S::Left, 105), (S::_0, 11), (S::_1, 2), (S::_9, 10), (S::A, 30), (S::Q, 16), (S::Z, 44), (S::LGui, 125), (S::Kp0, 82), (S::Kp9, 73), (S::KpEnter, 96), (S::F1, 59), (S::F11, 87), (S::F12, 88), (S::NumLockClear, 69), (S::LShift, 42), (S::RAlt, 100), (S::Semicolon, 39), (S::Grave, 41), (S::NonUsBackslash, 86), ]; for &(sc, ev) in same_key { assert_eq!(scancode_to_vk(sc), evdev_to_vk(ev), "scancode {sc:?}"); } assert_eq!(scancode_to_vk(Scancode::Mute), None); // not in the wire contract } }