From 2f214532d9604e3fcd44a8e4a99e141fef1c9760 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Mon, 13 Jul 2026 22:05:13 +0200 Subject: [PATCH] fix(inject/host/windows): eager-create the XUSB pad on Arrival + refresh last_active (G10) The XUSB manager's `handle` dropped `GamepadEvent::Arrival` via a `let else`, so the GameStream path never created the pad until the first `State` and missed the first XInput poll. Match on the event and `ensure` eagerly on Arrival, mirroring the DualSense backend. Also refresh `last_active` on create and unplug so a freshly-created pad's residual-rumble idle clock starts fresh rather than inheriting a stale Instant (which could force off a legitimate rumble at once). Verified: Windows .173 `cargo clippy -p punktfunk-host --all-targets -- -D warnings` (green). Co-Authored-By: Claude Opus 4.8 --- .../src/inject/windows/gamepad_windows.rs | 63 ++++++++++--------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/crates/punktfunk-host/src/inject/windows/gamepad_windows.rs b/crates/punktfunk-host/src/inject/windows/gamepad_windows.rs index 10cc8638..5537676a 100644 --- a/crates/punktfunk-host/src/inject/windows/gamepad_windows.rs +++ b/crates/punktfunk-host/src/inject/windows/gamepad_windows.rs @@ -282,6 +282,7 @@ impl GamepadManager { ); self.pads[idx] = Some(p); self.last_rumble[idx] = (0, 0); + self.last_active[idx] = Instant::now(); self.gate.on_success(); } Err(e) => { @@ -292,35 +293,41 @@ impl GamepadManager { } pub fn handle(&mut self, ev: &GamepadEvent) { - let GamepadEvent::State(f) = ev else { - return; // Arrival metadata — the pad is created lazily on the first State - }; - let idx = f.index.max(0) as usize; - if idx >= MAX_PADS { - return; - } - // Unplugs: drop any allocated pad whose mask bit cleared. - for (i, slot) in self.pads.iter_mut().enumerate() { - if slot.is_some() && f.active_mask & (1 << i) == 0 { - tracing::info!(index = i, "controller unplugged (Xbox 360/Windows)"); - *slot = None; - self.last_rumble[i] = (0, 0); + match ev { + GamepadEvent::Arrival { index, kind, .. } => { + tracing::info!(index, kind, "controller arrival (Xbox 360/Windows)"); + self.ensure(*index as usize); + } + GamepadEvent::State(f) => { + let idx = f.index.max(0) as usize; + if idx >= MAX_PADS { + return; + } + // Unplugs: drop any allocated pad whose mask bit cleared. + for (i, slot) in self.pads.iter_mut().enumerate() { + if slot.is_some() && f.active_mask & (1 << i) == 0 { + tracing::info!(index = i, "controller unplugged (Xbox 360/Windows)"); + *slot = None; + self.last_rumble[i] = (0, 0); + self.last_active[i] = Instant::now(); + } + } + if f.active_mask & (1 << idx) == 0 { + return; + } + self.ensure(idx); + if let Some(pad) = self.pads[idx].as_mut() { + pad.write_state( + (f.buttons & 0xffff) as u16, + f.left_trigger, + f.right_trigger, + f.ls_x, + f.ls_y, + f.rs_x, + f.rs_y, + ); + } } - } - if f.active_mask & (1 << idx) == 0 { - return; - } - self.ensure(idx); - if let Some(pad) = self.pads[idx].as_mut() { - pad.write_state( - (f.buttons & 0xffff) as u16, - f.left_trigger, - f.right_trigger, - f.ls_x, - f.ls_y, - f.rs_x, - f.rs_y, - ); } }