Files
punktfunk/crates/punktfunk-client-windows/src/keymap.rs
T
enricobuehlerandClaude Opus 4.8 bdd6c7f751 feat(windows-client): winit + D3D11 present, WASAPI render, input — builds live on MSVC
Builds on the prior headless scaffold (which was committed but never VM-built — its
audio.rs had two non-compiling wasapi calls). This makes the whole crate build + clippy
+ fmt + test green on x86_64-pc-windows-msvc and adds the windowed client.

- Fix audio.rs: `DeviceEnumerator::new()?.get_default_device(...)` (the free fn doesn't
  exist) and the 3-arg `write_to_device` (wasapi 0.23). WASAPI shared-mode event-driven
  render + mic capture now compile and link.
- present.rs: D3D11 renderer with WARP fallback (GPU-less dev box), runtime-compiled
  fullscreen-triangle shaders, dynamic RGBA video-texture upload, Contain-fit letterbox
  draw, and a flip-model swapchain on the window HWND.
- app.rs: winit 0.30 ApplicationHandler — present loop + Moonlight-style click-to-capture
  input (keyboard via the physical-KeyCode→VK keymap, absolute mouse, wheel, F11), held
  state flushed on release/focus-loss.
- keymap.rs: winit physical KeyCode → Windows VK (layout-independent positional mapping,
  the analogue of the Linux client's evdev table).
- main.rs: windowed default + `--headless` counting mode, `--discover` (mDNS list),
  `--pair PIN` (SPAKE2 ceremony), `--pin HEX`/known-host/TOFU trust, settings-backed
  CLI defaults.

UI decision: winit + raw D3D11 (the bootstrap doc's sanctioned fallback), confirmed by a
research pass — windows-rs "Reactor" ships no SwapChainPanel / SetSwapChain escape hatch,
so it can't host the presenter; winit+WARP validates on the GPU-less VM. Native-chrome
host-list/settings GUI + D3D11VA hardware decode + 10-bit/HDR present are follow-ups.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:59:40 +00:00

163 lines
4.9 KiB
Rust

//! Local key/button codes → the punktfunk input wire contract.
//!
//! The wire carries Windows Virtual-Key codes (the GameStream convention; the host maps them
//! back with `inject::vk_to_evdev`). On Windows the VK is the *native* source — but winit
//! hands us a layout-independent **physical** `KeyCode` (`KeyA` is always the QWERTY-A
//! position), which is exactly what a game wants: positional keys map positionally regardless
//! of the user's keyboard layout. This table is that physical-position → VK mapping, the
//! direct analogue of the Linux client's evdev table.
use winit::keyboard::KeyCode;
/// Map a winit physical `KeyCode` to the Windows VK code the host expects. `None` = a key the
/// wire contract doesn't cover (media keys etc.) — drop it rather than guess.
pub fn keycode_to_vk(code: KeyCode) -> Option<u8> {
use KeyCode::*;
Some(match code {
// --- Navigation / editing / whitespace ---
Backspace => 0x08,
Tab => 0x09,
Enter => 0x0D,
Pause => 0x13,
CapsLock => 0x14,
Escape => 0x1B,
Space => 0x20,
PageUp => 0x21,
PageDown => 0x22,
End => 0x23,
Home => 0x24,
ArrowLeft => 0x25,
ArrowUp => 0x26,
ArrowRight => 0x27,
ArrowDown => 0x28,
PrintScreen => 0x2C,
Insert => 0x2D,
Delete => 0x2E,
// --- Digit row ---
Digit0 => 0x30,
Digit1 => 0x31,
Digit2 => 0x32,
Digit3 => 0x33,
Digit4 => 0x34,
Digit5 => 0x35,
Digit6 => 0x36,
Digit7 => 0x37,
Digit8 => 0x38,
Digit9 => 0x39,
// --- Letters ---
KeyA => 0x41,
KeyB => 0x42,
KeyC => 0x43,
KeyD => 0x44,
KeyE => 0x45,
KeyF => 0x46,
KeyG => 0x47,
KeyH => 0x48,
KeyI => 0x49,
KeyJ => 0x4A,
KeyK => 0x4B,
KeyL => 0x4C,
KeyM => 0x4D,
KeyN => 0x4E,
KeyO => 0x4F,
KeyP => 0x50,
KeyQ => 0x51,
KeyR => 0x52,
KeyS => 0x53,
KeyT => 0x54,
KeyU => 0x55,
KeyV => 0x56,
KeyW => 0x57,
KeyX => 0x58,
KeyY => 0x59,
KeyZ => 0x5A,
// --- Meta / context-menu ---
SuperLeft => 0x5B, // VK_LWIN
SuperRight => 0x5C, // VK_RWIN
ContextMenu => 0x5D,
// --- Numpad ---
Numpad0 => 0x60,
Numpad1 => 0x61,
Numpad2 => 0x62,
Numpad3 => 0x63,
Numpad4 => 0x64,
Numpad5 => 0x65,
Numpad6 => 0x66,
Numpad7 => 0x67,
Numpad8 => 0x68,
Numpad9 => 0x69,
NumpadMultiply => 0x6A,
NumpadAdd => 0x6B,
NumpadEnter => 0x6C, // VK_SEPARATOR (matches the Linux client's KP_ENTER mapping)
NumpadSubtract => 0x6D,
NumpadDecimal => 0x6E,
NumpadDivide => 0x6F,
// --- Function keys ---
F1 => 0x70,
F2 => 0x71,
F3 => 0x72,
F4 => 0x73,
F5 => 0x74,
F6 => 0x75,
F7 => 0x76,
F8 => 0x77,
F9 => 0x78,
F10 => 0x79,
F11 => 0x7A,
F12 => 0x7B,
// --- Locks ---
NumLock => 0x90,
ScrollLock => 0x91,
// --- Left/right modifiers ---
ShiftLeft => 0xA0,
ShiftRight => 0xA1,
ControlLeft => 0xA2,
ControlRight => 0xA3,
AltLeft => 0xA4,
AltRight => 0xA5,
// --- OEM punctuation (US-layout positions) ---
Semicolon => 0xBA, // VK_OEM_1
Equal => 0xBB, // VK_OEM_PLUS
Comma => 0xBC, // VK_OEM_COMMA
Minus => 0xBD, // VK_OEM_MINUS
Period => 0xBE, // VK_OEM_PERIOD
Slash => 0xBF, // VK_OEM_2
Backquote => 0xC0, // VK_OEM_3
BracketLeft => 0xDB, // VK_OEM_4
Backslash => 0xDC, // VK_OEM_5
BracketRight => 0xDD, // VK_OEM_6
Quote => 0xDE, // VK_OEM_7
IntlBackslash => 0xE2, // VK_OEM_102 (the 102nd key)
_ => return None,
})
}
#[cfg(test)]
mod tests {
use super::*;
/// Spot-check positions against the Windows VK constants the host's `vk_to_evdev` knows.
#[test]
fn maps_known_positions() {
assert_eq!(keycode_to_vk(KeyCode::KeyA), Some(0x41));
assert_eq!(keycode_to_vk(KeyCode::KeyZ), Some(0x5A));
assert_eq!(keycode_to_vk(KeyCode::Digit0), Some(0x30));
assert_eq!(keycode_to_vk(KeyCode::Escape), Some(0x1B));
assert_eq!(keycode_to_vk(KeyCode::F11), Some(0x7A));
assert_eq!(keycode_to_vk(KeyCode::ShiftLeft), Some(0xA0));
assert_eq!(keycode_to_vk(KeyCode::IntlBackslash), Some(0xE2));
assert_eq!(keycode_to_vk(KeyCode::Numpad9), Some(0x69));
// A key outside the wire contract is dropped, not guessed.
assert_eq!(keycode_to_vk(KeyCode::AudioVolumeMute), None);
}
}