From 01409d9d8a49f732b098f95a99a96123ee955ecc Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Sun, 14 Jun 2026 16:14:28 +0000 Subject: [PATCH] fix(host/dualsense): report full battery + log rumble forwarding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two DualSense (UHID) fixes surfaced live on the Bazzite host: - Battery: serialize_state never set the input report's status byte (struct off 52 → r[53]), so hid-playstation read battery capacity 0 and SteamOS warned "low battery" even on a fully-charged pad. Set it to 0x0A (discharging, low nibble 0xA → 100 %) — a virtual pad has no real cell. (Forwarding the client pad's real charge is a later feature.) Regression assert added to the layout test. - Rumble diagnostic: log the silent→active transition when forwarding a buzz on the 0xCA plane, so a live test can tell "host never receives rumble from the game" (Steam Input / parse) apart from "client doesn't render it". Once per buzz, no spam. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/punktfunk-host/src/inject/dualsense.rs | 8 ++++++++ crates/punktfunk-host/src/m3.rs | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/crates/punktfunk-host/src/inject/dualsense.rs b/crates/punktfunk-host/src/inject/dualsense.rs index 2bd170f..dfc296e 100644 --- a/crates/punktfunk-host/src/inject/dualsense.rs +++ b/crates/punktfunk-host/src/inject/dualsense.rs @@ -275,6 +275,11 @@ fn serialize_state(r: &mut [u8; DS_INPUT_REPORT_LEN], st: &DsState, seq: u8, ts: r[28..32].copy_from_slice(&ts.to_le_bytes()); // sensor_timestamp (struct off 27) pack_touch(&mut r[33..37], &st.touch[0]); // touch point 1 (struct off 32) pack_touch(&mut r[37..41], &st.touch[1]); // touch point 2 + // status byte (struct off 52 → r[53]) — hid-playstation reads battery here: low nibble = + // capacity (×10+5 %), high nibble = charging state (0 = discharging). A virtual pad has no + // real cell, so report "discharging, full" (0x0A → 100 %); leaving it 0 makes SteamOS / the + // kernel see ~5 % and warn "low battery". (We don't forward the client pad's real charge yet.) + r[53] = 0x0A; } fn pack_touch(dst: &mut [u8], t: &Touch) { @@ -754,6 +759,9 @@ mod tests { assert_eq!(r[35], 0x61); // x_hi nibble 0x1 | (y & 0xF) << 4 (y=0x356 → 0x6 << 4) assert_eq!(r[36], 0x35); // y >> 4 assert_eq!(r[37] & 0x80, 0x80); // touch point 2 inactive + // status byte (struct off 52): discharging (high nibble 0) + full capacity (low nibble + // 0xA → 100 %), so SteamOS/hid-playstation never reports a false "low battery". + assert_eq!(r[53], 0x0A); } /// The wire touchpad-click bit (Moonlight's extended position) lands in `buttons[2]`. diff --git a/crates/punktfunk-host/src/m3.rs b/crates/punktfunk-host/src/m3.rs index 4633081..766eaf4 100644 --- a/crates/punktfunk-host/src/m3.rs +++ b/crates/punktfunk-host/src/m3.rs @@ -1254,6 +1254,11 @@ fn input_thread( pads.pump( |pad, low, high| { if let Some(s) = rumble_state.get_mut(pad as usize) { + // Log the silent→active transition (once per buzz) so a live test can tell + // "host never gets rumble from the game" apart from "client doesn't render it". + if *s == (0, 0) && (low != 0 || high != 0) { + tracing::info!(pad, low, high, "rumble: forwarding to client (0xCA)"); + } *s = (low, high); rumble_seen[pad as usize] = true; }