Files
punktfunk/crates/pf-presenter/src/keymap_sdl.rs
T
enricobuehler d6647b9183 feat(clients/windows): port the Vulkan session client to Windows — session-always
The punktfunk-session Vulkan client (clients/linux-session, now clients/session)
builds and runs on Windows; the WinUI shell spawns it for every stream. Verified
live: 10-bit HEVC via Vulkan Video on both AMD (iGPU) and NVIDIA, 5120x1440 at
130 fps / 8 ms end-to-end on the RTX 4090.

- pf-ffvk: Windows bindgen branch (FFMPEG_DIR + PF_FFVK_VULKAN_INCLUDE, no
  pkg-config); provisioning fetches Vulkan-Headers (pinned v1.4.309).
- pf-client-core: builds on Windows — WASAPI audio (audio_wasapi.rs, cfg-swapped
  via #[path], same surface as the PipeWire twin), VAAPI/dmabuf gated inline
  (chain = vulkan -> software), trust reads the WinUI shell's %APPDATA% stores
  (parity tests pin both serialized shapes), Settings gains adapter/hdr_enabled
  (serde-defaulted; Linux stores unaffected).
- pf-presenter: builds on Windows — dmabuf module Linux-gated; SDL keyboard grab
  while captured (Alt+Tab/Win reach the host); pick_device ranks discrete over
  integrated (device 0 was the iGPU on hybrid boxes — the silent footgun) and
  honors PUNKTFUNK_VK_ADAPTER (the Settings GPU pick, exported by the session).
- run loop: block in one SDL wait woken by input AND decoded frames (a per-
  session forwarder pushes a FrameWake user event) instead of a 1 ms poll —
  measured 111%% -> 5%% of a core (NVIDIA), 86%% -> 3.5%% (AMD), stats unchanged.
  The pump's decode-fence wait became once-per-window sampling (no per-frame
  pipeline stall; the stat now shows true backlog).
- pf-console-ui: builds on Windows (skia-safe msvc prebuilts); font lookup falls
  through fontconfig aliases to concrete DirectWrite families (Consolas/Segoe UI)
  — browse/coverflow works, verified against a live host.
- WinUI shell: session-always via new src/spawn.rs (GTK spawn.rs port —
  CREATE_NO_WINDOW, stdout contract, kill handle); the Stream screen is a status
  card (chips + stage lines from the child's stats). The legacy in-process
  D3D11VA path stays behind Settings "Streaming engine" / PUNKTFUNK_BUILTIN_
  STREAM=1 as the A/B baseline until Phase 8 deletes it. SessionParams.video_caps
  makes the HDR toggle real.
- clients/linux-session renamed to clients/session (builds for both OSes).
- CI/MSIX: both workflows build/test both bins with widened path filters; the
  MSIX ships punktfunk-session.exe. ARM64 session builds --no-default-features
  (rust-skia has no aarch64-pc-windows-msvc prebuilts; flip when it does).

A/B on this box (5120x1440 HEVC vs home-worker-5): NVIDIA Vulkan 130 fps / 8 ms
e2e / 1.6 ms decode — clearly better than the built-in path. The AMD iGPU VCN
saturates at ~52 fps where its own D3D11VA does ~70 — Adrenalin Vulkan decode is
slower on APU silicon; discrete RDNA validation gates Phase 8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 23:21:36 +02:00

204 lines
5.6 KiB
Rust

//! 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<u8> {
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<u32> {
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,
})
}
// Linux-only: the reference table it cross-checks (pf_client_core::keymap, evdev-keyed)
// only exists there. The SDL table under test is itself cross-platform.
#[cfg(all(test, target_os = "linux"))]
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
}
}