9c8fa9340c
apple / swift (push) Failing after 40s
audit / cargo-audit (push) Failing after 1m12s
windows-msix / package (push) Successful in 1m37s
windows / build (push) Successful in 1m14s
android / android (push) Successful in 4m48s
ci / web (push) Successful in 27s
ci / rust (push) Successful in 4m21s
ci / docs-site (push) Successful in 31s
ci / bench (push) Successful in 4m39s
decky / build-publish (push) Successful in 11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 19s
deb / build-publish (push) Successful in 6m3s
flatpak / build-publish (push) Successful in 4m13s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m15s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m16s
docker / deploy-docs (push) Successful in 18s
Two bodies of work in one commit (the rename moved files the fixes also touched). Naming/structure cleanup (pre-launch): - Host modules m3.rs->punktfunk1.rs, m0.rs->spike.rs; CLI m3-host->punktfunk1-host, m0->spike; bare `punktfunk-host` now prints help. Types M3Options/M3Source-> Punktfunk1Options/Punktfunk1Source. - Clients consolidated out of crates/ into clients/: punktfunk-client-rs-> clients/probe (crate punktfunk-probe), client-linux->clients/linux, client-windows->clients/windows, punktfunk-android->clients/android/native (crate punktfunk-client-android; kept [lib] name=punktfunk_android so the JNI contract is unchanged). crates/ now holds only core + host. - Milestone codes M0-M4 purged from code/CLI/CLAUDE.md/README/docs/docs-site, kept only in docs/implementation-plan.md. docs/m2-plan.md-> docs/gamestream-host-plan.md. CI/gradle/flatpak paths updated. Client loss-recovery (video froze and never recovered after a brief drop): - Export punktfunk_connection_frames_dropped through the C ABI (the core already tracked it for the client keyframe-recovery loop; it was never reachable from the ABI clients). Regenerated punktfunk_core.h. - Apple (StreamPump + Stage2Pipeline) and Android (decode.rs) now poll frames_dropped and request a keyframe when it climbs -- the same loss-driven recovery Linux/Windows already had. Under infinite GOP the decoder silently conceals reference-missing frames, so the decode-error trigger rarely fires. Apple rumble robustness (worked then went spotty -- DualSense + Xbox): - Add CHHapticEngine stopped/reset handlers (rebuild on app background / audio interruption / server reset) and drop the permanent `broken` latch on a transient drive failure; latch only when the controller truly has no haptics. - Surface swallowed SDL set_rumble errors on Linux/Windows + diagnostic logging. Verified: cargo build/clippy/fmt --workspace, C-ABI harness, header drift. Not runnable on this box (verify in CI): Gitea workflows, gradle/Android, flatpak, Swift/decky. Co-Authored-By: Claude Opus 4.8 (1M context) <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
|
|
}
|
|
}
|