fix(inject/host/linux): re-assert absolute gamepad button state each frame (G8)
The uinput gamepad backend emitted only XOR-changed button edges while advancing `prev_buttons` unconditionally. Because `emit()` is best-effort (a full kernel queue silently drops the write), a dropped EV_KEY edge was never re-synced — the button stayed stuck (pressed-not-released, or vice versa) until it next toggled. The axes never had this problem: they re-emit their absolute value every frame. Re-assert every mapped button's absolute state each frame, exactly like the axes, and drop the now-unused `prev_buttons` field. Restating an unchanged key is free downstream: the kernel input core discards an EV_KEY whose value already matches the device's current state (no duplicate event reaches consumers, and BTN_* keys don't autorepeat). The `emit()` "next frame re-syncs state" comment is now honest for buttons too. Verified on the Linux host build (.21): cargo clippy -D warnings clean (no dead-field warning), full punktfunk-host suite 277 passed / 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -266,7 +266,6 @@ struct Effect {
|
|||||||
/// One virtual X-Box-360 pad backed by a uinput device.
|
/// One virtual X-Box-360 pad backed by a uinput device.
|
||||||
pub struct VirtualPad {
|
pub struct VirtualPad {
|
||||||
fd: OwnedFd,
|
fd: OwnedFd,
|
||||||
prev_buttons: u32,
|
|
||||||
effects: HashMap<i16, Effect>,
|
effects: HashMap<i16, Effect>,
|
||||||
next_effect_id: i16,
|
next_effect_id: i16,
|
||||||
gain: u32,
|
gain: u32,
|
||||||
@@ -370,7 +369,6 @@ impl VirtualPad {
|
|||||||
|
|
||||||
Ok(VirtualPad {
|
Ok(VirtualPad {
|
||||||
fd,
|
fd,
|
||||||
prev_buttons: 0,
|
|
||||||
effects: HashMap::new(),
|
effects: HashMap::new(),
|
||||||
next_effect_id: 0,
|
next_effect_id: 0,
|
||||||
gain: 0xFFFF,
|
gain: 0xFFFF,
|
||||||
@@ -413,15 +411,17 @@ impl VirtualPad {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply one decoded frame: button transitions, axes, D-pad hat, one SYN_REPORT.
|
/// Apply one decoded frame: button state, axes, D-pad hat, one SYN_REPORT.
|
||||||
pub fn apply(&mut self, f: &GamepadFrame) {
|
pub fn apply(&mut self, f: &GamepadFrame) {
|
||||||
let changed = self.prev_buttons ^ f.buttons;
|
// Re-assert every mapped button's absolute state each frame — exactly like the axes below —
|
||||||
|
// instead of only writing XOR-changed edges. `emit` is best-effort (a full kernel queue drops
|
||||||
|
// the write), so an edge-only scheme would strand a dropped press/release until that button
|
||||||
|
// next toggles; re-asserting re-syncs it on the following frame. Restating an unchanged key is
|
||||||
|
// free downstream: the kernel input core discards an EV_KEY whose value already matches the
|
||||||
|
// device's current state (no duplicate event reaches consumers, and BTN_* keys don't autorepeat).
|
||||||
for (bit, key) in BUTTON_MAP {
|
for (bit, key) in BUTTON_MAP {
|
||||||
if changed & bit != 0 {
|
|
||||||
self.emit(EV_KEY, key, ((f.buttons & bit) != 0) as i32);
|
self.emit(EV_KEY, key, ((f.buttons & bit) != 0) as i32);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
self.prev_buttons = f.buttons;
|
|
||||||
|
|
||||||
// Moonlight: +Y = up; evdev: +Y = down → negate (i32 math avoids -(-32768) overflow).
|
// Moonlight: +Y = up; evdev: +Y = down → negate (i32 math avoids -(-32768) overflow).
|
||||||
self.emit(EV_ABS, ABS_X, f.ls_x as i32);
|
self.emit(EV_ABS, ABS_X, f.ls_x as i32);
|
||||||
|
|||||||
Reference in New Issue
Block a user