diff --git a/crates/punktfunk-host/src/gamestream/control.rs b/crates/punktfunk-host/src/gamestream/control.rs index 2b2d2c17..a39bbe34 100644 --- a/crates/punktfunk-host/src/gamestream/control.rs +++ b/crates/punktfunk-host/src/gamestream/control.rs @@ -289,6 +289,14 @@ fn on_receive( let _ = inj_tx.send(ev); }); return; + } else if super::input::is_pointer_magic(&pt) { + // A pointer magic that failed the body parse — a layout mismatch against this + // client, exactly what an on-glass "touch/pen does nothing" needs surfaced. + tracing::warn!( + len = pt.len(), + "gamestream: SS_TOUCH/SS_PEN packet failed to decode (malformed/unexpected layout)" + ); + return; } let events = super::input::decode(&pt); diff --git a/crates/punktfunk-host/src/gamestream/input.rs b/crates/punktfunk-host/src/gamestream/input.rs index 7ad9e4c4..79538734 100644 --- a/crates/punktfunk-host/src/gamestream/input.rs +++ b/crates/punktfunk-host/src/gamestream/input.rs @@ -150,6 +150,18 @@ pub enum SsPointer { Touch(SsTouch), } +/// Whether this control plaintext carries a pointer magic ([`decode_pointer`]'s domain) — +/// lets the caller tell "malformed pointer packet" (worth a warn) apart from "some other +/// message" when `decode_pointer` returns `None`. +pub fn is_pointer_magic(plaintext: &[u8]) -> bool { + plaintext.len() >= 12 + && u16::from_le_bytes([plaintext[0], plaintext[1]]) == INPUT_DATA_TYPE + && matches!( + u32::from_le_bytes([plaintext[8], plaintext[9], plaintext[10], plaintext[11]]), + MAGIC_SS_TOUCH | MAGIC_SS_PEN + ) +} + /// Decode a control plaintext into a pen/touch pointer event, or `None` for every other /// message (the caller then falls through to [`decode`]). Bounds- and sanity-checked like the /// rest of the plane: short bodies and non-finite floats (a forged NaN must never reach the diff --git a/crates/punktfunk-host/src/gamestream/pen.rs b/crates/punktfunk-host/src/gamestream/pen.rs index fc476b46..26fccb31 100644 --- a/crates/punktfunk-host/src/gamestream/pen.rs +++ b/crates/punktfunk-host/src/gamestream/pen.rs @@ -67,6 +67,9 @@ pub struct GsPointer { saw_hover: bool, /// Active forwarded touch ids, for `CANCEL_ALL` replay as per-id ups. touch_ids: Vec, + /// Whether this session's first SS_TOUCH was logged (the one observable breadcrumb an + /// on-glass "touch does nothing" report needs — every later stage logs its own failure). + touch_seen: bool, } impl GsPointer { @@ -80,6 +83,7 @@ impl GsPointer { last: PenSample::default(), saw_hover: false, touch_ids: Vec::new(), + touch_seen: false, } } @@ -199,6 +203,13 @@ impl GsPointer { } fn apply_touch(&mut self, t: &SsTouch, mut sink: impl FnMut(InputEvent)) { + if !self.touch_seen { + self.touch_seen = true; + tracing::info!( + event_type = t.event_type, + "gamestream: touch plane active (first SS_TOUCH from this client)" + ); + } let ev = |kind: InputKind, id: u32, x: f32, y: f32| InputEvent { kind, _pad: [0; 3],