From 5cd66eca59e5b499080604143fa8e2ad72dd30cd Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Mon, 13 Jul 2026 11:12:47 +0200 Subject: [PATCH] fix(gamepad/apple): stop releasing held guide on concurrent input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `sync()` XOR-diffs the full `GamepadWire.allButtons` set (which includes guide) against `slot.buttons`, but `buttonMask` deliberately omits guide — it's driven separately by the Home handler via `sendGuide`. So while guide was physically held, the first stick/trigger/face-button move made `changed` carry the guide bit and the diff loop emitted a spurious guide-UP (then the real release was swallowed by `sendGuide`'s `guard now != slot.buttons`). Effect: you could not hold PS/guide while doing anything else — e.g. holding guide to keep the host's Steam overlay engaged released it the instant you touched a stick. The Rust reference client folds guide through the same diff as every other button and has no such split. Fix: preserve the current held guide bit through the diff (`buttonMask(g) | (slot.buttons & GamepadWire.guide)`) so guide is never seen as "changed"; `sendGuide` stays the sole toggler and `flush`/`allButtons` still release it on close/deactivation. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Sources/PunktfunkKit/Gamepad/GamepadCapture.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/clients/apple/Sources/PunktfunkKit/Gamepad/GamepadCapture.swift b/clients/apple/Sources/PunktfunkKit/Gamepad/GamepadCapture.swift index f15b02c2..44da56d5 100644 --- a/clients/apple/Sources/PunktfunkKit/Gamepad/GamepadCapture.swift +++ b/clients/apple/Sources/PunktfunkKit/Gamepad/GamepadCapture.swift @@ -257,7 +257,12 @@ public final class GamepadCapture { /// tagged with the slot's wire pad index. private func sync(_ slot: Slot, _ g: GCExtendedGamepad) { guard !suspended else { return } - let newButtons = Self.buttonMask(g) + // guide is driven separately (`sendGuide`, off the Home handler) and deliberately kept out + // of `buttonMask`. Preserve its current held state here so the XOR diff below never sees it + // as "changed" — otherwise the first stick/button move after a guide press would emit a + // spurious guide-UP while the button is still physically held (and drop the bit from + // `slot.buttons`, swallowing the real release too). `flush`/`allButtons` still release it. + let newButtons = Self.buttonMask(g) | (slot.buttons & GamepadWire.guide) let changed = newButtons ^ slot.buttons if changed != 0 { for bit in GamepadWire.allButtons where changed & bit != 0 {