96a35ca84c
ci / rust (push) Failing after 29s
ci / web (push) Failing after 35s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 3s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 18s
ci / docs-site (push) Failing after 38s
apple / swift (push) Successful in 1m15s
docker / deploy-docs (push) Successful in 17s
New crate crates/punktfunk-client-linux (binary punktfunk-client), the native Linux client on the Option A architecture (2026-06-12 research): - GTK4/libadwaita shell linking punktfunk-core directly (no C ABI): mDNS host list, TOFU fingerprint prompt, SPAKE2 PIN pairing dialog, preferences (mode/bitrate/gamepad/shortcut capture), stats overlay, --connect host[:port] for scripting. - Video: FFmpeg software HEVC decode (LOW_DELAY, slice threads) -> RGBA -> GdkMemoryTexture inside GtkGraphicsOffload (the dmabuf subsurface path lights up when VAAPI lands; black-background keeps fullscreen scanout-eligible). - Audio: Opus -> PipeWire playback stream, the host virtual-mic's adaptive jitter ring inverted. - Input: keyboard as the exact inverse of the host VK table (evdev keycodes, layout-independent; unit-tested), absolute mouse through the Contain-fit transform, WHEEL_DELTA(120) scroll, compositor shortcut inhibition while streaming, Ctrl+Alt+Shift+Q release chord, F11 fullscreen. SDL3 gamepad capture (single pad-0 model) + rumble and DualSense lightbar feedback on the same thread. - Session pump owns video+audio pulls; the gamepad thread owns rumble+hidout — possible because NativeClient's plane receivers are now mutexed, making it Sync (Arc-shared, compiler-verified per-plane contract instead of the ABI's manual assertion). - Linux-gated deps + a stub main keep cargo build --workspace green on macOS. Validated live against serve --native on this box: 1920x1080@60, locked 60 fps, capture->decoded p50 ~6.4 ms (software decode, debug build). Teardown keys off AdwNavigationPage::hidden — NavigationView push fires a transient unmap/map cycle that must not end the session. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
204 lines
6.6 KiB
Rust
204 lines
6.6 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`). GTK hands us the hardware keycode, which on
|
|
//! Wayland (and X11) is the evdev code + 8 — so this table is the exact inverse of the
|
|
//! host's, keyed on evdev codes. Layout-independent by construction: positional keys map
|
|
//! positionally, exactly what a game expects.
|
|
|
|
/// Map a Linux evdev key code 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 evdev_to_vk(evdev: u16) -> Option<u8> {
|
|
Some(match evdev {
|
|
// --- Navigation / editing / whitespace ---
|
|
14 => 0x08, // KEY_BACKSPACE -> VK_BACK
|
|
15 => 0x09, // KEY_TAB -> VK_TAB
|
|
28 => 0x0D, // KEY_ENTER -> VK_RETURN
|
|
119 => 0x13, // KEY_PAUSE -> VK_PAUSE
|
|
58 => 0x14, // KEY_CAPSLOCK -> VK_CAPITAL
|
|
1 => 0x1B, // KEY_ESC -> VK_ESCAPE
|
|
57 => 0x20, // KEY_SPACE -> VK_SPACE
|
|
104 => 0x21, // KEY_PAGEUP -> VK_PRIOR
|
|
109 => 0x22, // KEY_PAGEDOWN -> VK_NEXT
|
|
107 => 0x23, // KEY_END -> VK_END
|
|
102 => 0x24, // KEY_HOME -> VK_HOME
|
|
105 => 0x25, // KEY_LEFT -> VK_LEFT
|
|
103 => 0x26, // KEY_UP -> VK_UP
|
|
106 => 0x27, // KEY_RIGHT -> VK_RIGHT
|
|
108 => 0x28, // KEY_DOWN -> VK_DOWN
|
|
99 => 0x2C, // KEY_SYSRQ -> VK_SNAPSHOT
|
|
110 => 0x2D, // KEY_INSERT -> VK_INSERT
|
|
111 => 0x2E, // KEY_DELETE -> VK_DELETE
|
|
|
|
// --- Digit row (KEY_1..KEY_9 are 2..10, KEY_0 is 11) ---
|
|
11 => 0x30,
|
|
2 => 0x31,
|
|
3 => 0x32,
|
|
4 => 0x33,
|
|
5 => 0x34,
|
|
6 => 0x35,
|
|
7 => 0x36,
|
|
8 => 0x37,
|
|
9 => 0x38,
|
|
10 => 0x39,
|
|
|
|
// --- Letters (evdev order is QWERTY rows, not alphabetical) ---
|
|
30 => 0x41, // A
|
|
48 => 0x42, // B
|
|
46 => 0x43, // C
|
|
32 => 0x44, // D
|
|
18 => 0x45, // E
|
|
33 => 0x46, // F
|
|
34 => 0x47, // G
|
|
35 => 0x48, // H
|
|
23 => 0x49, // I
|
|
36 => 0x4A, // J
|
|
37 => 0x4B, // K
|
|
38 => 0x4C, // L
|
|
50 => 0x4D, // M
|
|
49 => 0x4E, // N
|
|
24 => 0x4F, // O
|
|
25 => 0x50, // P
|
|
16 => 0x51, // Q
|
|
19 => 0x52, // R
|
|
31 => 0x53, // S
|
|
20 => 0x54, // T
|
|
22 => 0x55, // U
|
|
47 => 0x56, // V
|
|
17 => 0x57, // W
|
|
45 => 0x58, // X
|
|
21 => 0x59, // Y
|
|
44 => 0x5A, // Z
|
|
|
|
// --- Meta / context-menu ---
|
|
125 => 0x5B, // KEY_LEFTMETA -> VK_LWIN
|
|
126 => 0x5C, // KEY_RIGHTMETA -> VK_RWIN
|
|
127 => 0x5D, // KEY_COMPOSE -> VK_APPS
|
|
|
|
// --- Numpad ---
|
|
82 => 0x60, // KP0
|
|
79 => 0x61,
|
|
80 => 0x62,
|
|
81 => 0x63,
|
|
75 => 0x64,
|
|
76 => 0x65,
|
|
77 => 0x66,
|
|
71 => 0x67,
|
|
72 => 0x68,
|
|
73 => 0x69, // KP9
|
|
55 => 0x6A, // KEY_KPASTERISK -> VK_MULTIPLY
|
|
78 => 0x6B, // KEY_KPPLUS -> VK_ADD
|
|
96 => 0x6C, // KEY_KPENTER -> VK_SEPARATOR
|
|
74 => 0x6D, // KEY_KPMINUS -> VK_SUBTRACT
|
|
83 => 0x6E, // KEY_KPDOT -> VK_DECIMAL
|
|
98 => 0x6F, // KEY_KPSLASH -> VK_DIVIDE
|
|
|
|
// --- Function keys ---
|
|
59 => 0x70, // F1
|
|
60 => 0x71,
|
|
61 => 0x72,
|
|
62 => 0x73,
|
|
63 => 0x74,
|
|
64 => 0x75,
|
|
65 => 0x76,
|
|
66 => 0x77,
|
|
67 => 0x78,
|
|
68 => 0x79, // F10
|
|
87 => 0x7A, // F11
|
|
88 => 0x7B, // F12
|
|
|
|
// --- Locks ---
|
|
69 => 0x90, // KEY_NUMLOCK -> VK_NUMLOCK
|
|
70 => 0x91, // KEY_SCROLLLOCK -> VK_SCROLL
|
|
|
|
// --- Left/right modifiers (specific VKs; the host maps both generics here too) ---
|
|
42 => 0xA0, // KEY_LEFTSHIFT -> VK_LSHIFT
|
|
54 => 0xA1, // KEY_RIGHTSHIFT -> VK_RSHIFT
|
|
29 => 0xA2, // KEY_LEFTCTRL -> VK_LCONTROL
|
|
97 => 0xA3, // KEY_RIGHTCTRL -> VK_RCONTROL
|
|
56 => 0xA4, // KEY_LEFTALT -> VK_LMENU
|
|
100 => 0xA5, // KEY_RIGHTALT -> VK_RMENU
|
|
|
|
// --- OEM punctuation (US-layout positions) ---
|
|
39 => 0xBA, // KEY_SEMICOLON -> VK_OEM_1
|
|
13 => 0xBB, // KEY_EQUAL -> VK_OEM_PLUS
|
|
51 => 0xBC, // KEY_COMMA -> VK_OEM_COMMA
|
|
12 => 0xBD, // KEY_MINUS -> VK_OEM_MINUS
|
|
52 => 0xBE, // KEY_DOT -> VK_OEM_PERIOD
|
|
53 => 0xBF, // KEY_SLASH -> VK_OEM_2
|
|
41 => 0xC0, // KEY_GRAVE -> VK_OEM_3
|
|
26 => 0xDB, // KEY_LEFTBRACE -> VK_OEM_4
|
|
43 => 0xDC, // KEY_BACKSLASH -> VK_OEM_5
|
|
27 => 0xDD, // KEY_RIGHTBRACE -> VK_OEM_6
|
|
40 => 0xDE, // KEY_APOSTROPHE -> VK_OEM_7
|
|
86 => 0xE2, // KEY_102ND -> VK_OEM_102
|
|
|
|
_ => return None,
|
|
})
|
|
}
|
|
|
|
/// Map a GTK/GDK mouse button number to the GameStream button id the wire expects
|
|
/// (1=left, 2=middle, 3=right, 4=X1, 5=X2). GDK reports back/forward as 8/9.
|
|
pub fn gdk_button_to_gs(button: u32) -> Option<u32> {
|
|
Some(match button {
|
|
1 => 1,
|
|
2 => 2,
|
|
3 => 3,
|
|
8 => 4,
|
|
9 => 5,
|
|
_ => return None,
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
/// The table must be the exact inverse of the host's `vk_to_evdev` for every key the
|
|
/// host knows (modulo the generic-modifier VKs, which collapse onto the same evdev
|
|
/// codes as the specific left-hand ones).
|
|
#[test]
|
|
fn roundtrips_through_the_host_table() {
|
|
// Mirror of the host's table (inject::vk_to_evdev), generic modifiers excluded.
|
|
let host_pairs: &[(u8, u16)] = &[
|
|
(0x08, 14),
|
|
(0x09, 15),
|
|
(0x0D, 28),
|
|
(0x13, 119),
|
|
(0x14, 58),
|
|
(0x1B, 1),
|
|
(0x20, 57),
|
|
(0x21, 104),
|
|
(0x22, 109),
|
|
(0x23, 107),
|
|
(0x24, 102),
|
|
(0x25, 105),
|
|
(0x26, 103),
|
|
(0x27, 106),
|
|
(0x28, 108),
|
|
(0x2C, 99),
|
|
(0x2D, 110),
|
|
(0x2E, 111),
|
|
(0x30, 11),
|
|
(0x31, 2),
|
|
(0x39, 10),
|
|
(0x41, 30),
|
|
(0x5A, 44),
|
|
(0x5B, 125),
|
|
(0x60, 82),
|
|
(0x69, 73),
|
|
(0x70, 59),
|
|
(0x7B, 88),
|
|
(0x90, 69),
|
|
(0xA0, 42),
|
|
(0xA5, 100),
|
|
(0xBA, 39),
|
|
(0xE2, 86),
|
|
];
|
|
for &(vk, evdev) in host_pairs {
|
|
assert_eq!(evdev_to_vk(evdev), Some(vk), "evdev {evdev}");
|
|
}
|
|
assert_eq!(evdev_to_vk(113), None); // KEY_MUTE — not in the wire contract
|
|
}
|
|
}
|