fix(host/windows): layout-correct keyboard injection - semantic vs positional VKs

First-party punktfunk clients send US-positional VKs (the physical key's
US-layout VK), GameStream/Moonlight clients send layout-semantic VKs
(Sunshine's model). The SendInput injector previously resolved everything
through the SYSTEM service's layout - on a German host that is the y/z swap
and u-umlaut-on-o-umlaut scramble. GameStream ingest now tags its key events
KEY_FLAG_SEMANTIC_VK (stripped from punktfunk/1 wire events so a network
client can't flip the convention); the injector maps semantic VKs under the
foreground app's layout and positional VKs through a fixed scancode table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 16:24:04 +02:00
parent 019f2677a7
commit 2c416a4bff
4 changed files with 203 additions and 28 deletions
+12 -1
View File
@@ -1015,8 +1015,19 @@ async fn serve_session(
if rich_tx.send(rich).is_err() {
break;
}
} else if let Some(ev) = InputEvent::decode(&d) {
} else if let Some(mut ev) = InputEvent::decode(&d) {
input_count += 1;
// Wire hygiene: KEY_FLAG_SEMANTIC_VK is an in-process tag (GameStream ingest
// only) — strip it from network events so a client can't flip the host's
// key-decoding convention. Other kinds keep flags verbatim (MouseMoveAbs packs
// its reference extent there).
if matches!(
ev.kind,
punktfunk_core::input::InputKind::KeyDown
| punktfunk_core::input::InputKind::KeyUp
) {
ev.flags &= !crate::inject::KEY_FLAG_SEMANTIC_VK;
}
if input_tx.send(ev).is_err() {
break;
}