Skip to content

Commit a42e2c7

Browse files
committed
fix(Hardware Support): Fix Dropped Events From Debounce Method
In some rare circumstances the Legion Go S DPad can have some chatter when pressed, emitting a 'down', 'up', and 'down' event in the same event frame. The deck and deck-uhid targets both contain debounce sequences for double events, preventing a "no-op" if a 'down' then 'up' event is hit in the same frame by rescheduling the 'up' event for later, but it doesn't account for what should happen if another 'down' event is hit while the 'up' event is scheduled. In such a case, the second 'down' event becomes a no-op as the state doesn't change, then the deferred 'up' event hits, clearing the state and putting the virtual device out of sync with the physical hardware. Additionally, all of the currently implemented target device scheduled_events() functions fail to check for the elapsed time has passed, instead immediately draining all scheduled events in the next poll. This patch addresses these issues by making the following changes: - Mask the Legion Go S evdev events to reduce the overall processing overhead for duplicated events and lower the chance for chatter. - Dequeue pending 'up' events if a 'down' event is processed before that event's scheduled interval. - Reduce ScheduledNativeEvent timeout on deck and deck-uhid to 8ms (approximately 2 frames) to reduce chatter collision probability. - Check ScheduledNativeEvent.is_ready() for queued events before draining them on all target devices with implemented scheduled_events() functions.
1 parent 62663dd commit a42e2c7

9 files changed

Lines changed: 78 additions & 19 deletions

File tree

rootfs/usr/share/inputplumber/devices/50-legion_go_s.yaml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,13 @@ source_devices:
7979
- group: gamepad
8080
evdev:
8181
vendor_id: "1a86"
82-
product_id: "e310"
83-
name: "{QH Electronics Controller,Legion Go S}"
84-
handler: event*
85-
- group: gamepad
86-
evdev:
87-
vendor_id: "1a86"
88-
product_id: "e311"
82+
product_id: "{e31[01]}"
8983
name: "{QH Electronics Controller,Legion Go S}"
9084
handler: event*
85+
events:
86+
# Block input events, but use device for output events
87+
exclude:
88+
- "*"
9189

9290
# IMU
9391
- group: imu
@@ -116,4 +114,6 @@ options:
116114

117115
# The target input device(s) to emulate by default
118116
target_devices:
119-
- deck-uhid
117+
- xbox-elite
118+
- keyboard
119+
- mouse

src/input/event/native.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,4 +198,9 @@ impl ScheduledNativeEvent {
198198
pub fn is_ready(&self) -> bool {
199199
self.scheduled_time.elapsed() > self.wait_time
200200
}
201+
202+
/// Returns the capability that the scheduled event implements
203+
pub fn as_capability(&self) -> Capability {
204+
self.event.capability.clone()
205+
}
201206
}

src/input/target/dualsense.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1009,7 +1009,13 @@ impl TargetInputDevice for DualSenseDevice {
10091009
if self.queued_events.is_empty() {
10101010
return None;
10111011
}
1012-
Some(self.queued_events.drain(..).collect())
1012+
let (ready, pending): (Vec<_>, Vec<_>) =
1013+
self.queued_events.drain(..).partition(|e| e.is_ready());
1014+
self.queued_events = pending;
1015+
if ready.is_empty() {
1016+
return None;
1017+
}
1018+
Some(ready)
10131019
}
10141020

10151021
fn stop(&mut self) -> Result<(), InputError> {

src/input/target/horipad_steam.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,7 @@ impl HoripadSteamDevice {
313313
report_number: u8,
314314
_report_type: uhid_virt::ReportType,
315315
) -> Result<(), Box<dyn Error>> {
316-
log::debug!(
317-
"Received GetReport request: id: {id}, report_number: {report_number}"
318-
);
316+
log::debug!("Received GetReport request: id: {id}, report_number: {report_number}");
319317
if let Err(e) = self.device.write_get_report_reply(id, 1, vec![]) {
320318
log::warn!("Failed to write get report reply: {:?}", e);
321319
return Err(e.to_string().into());
@@ -372,7 +370,13 @@ impl TargetInputDevice for HoripadSteamDevice {
372370
if self.queued_events.is_empty() {
373371
return None;
374372
}
375-
Some(self.queued_events.drain(..).collect())
373+
let (ready, pending): (Vec<_>, Vec<_>) =
374+
self.queued_events.drain(..).partition(|e| e.is_ready());
375+
self.queued_events = pending;
376+
if ready.is_empty() {
377+
return None;
378+
}
379+
Some(ready)
376380
}
377381

378382
fn stop(&mut self) -> Result<(), InputError> {

src/input/target/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ impl<T: TargetInputDevice + TargetOutputDevice + Send + 'static> TargetDriver<T>
537537
i += 1;
538538
}
539539
for event in ready_events.drain(..) {
540+
log::trace!("Emmiting scheduled event: {event:?}");
540541
let mut implementation = self.implementation.lock().unwrap();
541542
if let Err(e) = implementation.write_event(event.into()) {
542543
log::error!("Error writing event: {e:?}");

src/input/target/steam_deck.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ impl Default for SteamDeckConfig {
7070

7171
// The minimum amount of time that button up events must wait after
7272
// a button down event.
73-
const MIN_FRAME_TIME: Duration = Duration::from_millis(80);
73+
const MIN_FRAME_TIME: Duration = Duration::from_millis(8);
7474

7575
pub struct SteamDeckDevice {
7676
chip_id: [u8; 15],
@@ -932,6 +932,14 @@ impl TargetInputDevice for SteamDeckDevice {
932932
log::trace!("Button down: {cap:?}");
933933
// Keep track of button down events
934934
self.pressed_events.insert(cap.clone(), Instant::now());
935+
// Clear any stale up events for this capability
936+
self.queued_events.retain(|scheduled| {
937+
let found = scheduled.as_capability() == cap;
938+
if found {
939+
log::trace!("Found stale queued release for {cap:?}, Clearing.");
940+
}
941+
!found
942+
});
935943
} else {
936944
log::trace!("Button up: {cap:?}");
937945
// If the event is a button up event, check to
@@ -1009,11 +1017,18 @@ impl TargetInputDevice for SteamDeckDevice {
10091017
])
10101018
}
10111019

1020+
/// Returns any events in the queue up to the [TargetDriver]
10121021
fn scheduled_events(&mut self) -> Option<Vec<ScheduledNativeEvent>> {
10131022
if self.queued_events.is_empty() {
10141023
return None;
10151024
}
1016-
Some(self.queued_events.drain(..).collect())
1025+
let (ready, pending): (Vec<_>, Vec<_>) =
1026+
self.queued_events.drain(..).partition(|e| e.is_ready());
1027+
self.queued_events = pending;
1028+
if ready.is_empty() {
1029+
return None;
1030+
}
1031+
Some(ready)
10171032
}
10181033

10191034
/// Stop the virtual USB read/write threads

src/input/target/steam_deck_uhid.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ use super::{
4747

4848
// The minimum amount of time that button up events must wait after
4949
// a button down event.
50-
const MIN_FRAME_TIME: Duration = Duration::from_millis(80);
50+
const MIN_FRAME_TIME: Duration = Duration::from_millis(8);
5151

5252
pub struct SteamDeckUhidDevice {
5353
chip_id: [u8; 15],
@@ -740,6 +740,14 @@ impl TargetInputDevice for SteamDeckUhidDevice {
740740
log::trace!("Button down: {cap:?}");
741741
// Keep track of button down events
742742
self.pressed_events.insert(cap.clone(), Instant::now());
743+
// Clear any stale up events for this capability
744+
self.queued_events.retain(|scheduled| {
745+
let found = scheduled.as_capability() == cap;
746+
if found {
747+
log::trace!("Found stale queued release for {cap:?}, Clearing.");
748+
}
749+
!found
750+
});
743751
} else {
744752
log::trace!("Button up: {cap:?}");
745753
// If the event is a button up event, check to
@@ -817,11 +825,18 @@ impl TargetInputDevice for SteamDeckUhidDevice {
817825
])
818826
}
819827

828+
/// Returns any events in the queue up to the [TargetDriver]
820829
fn scheduled_events(&mut self) -> Option<Vec<ScheduledNativeEvent>> {
821830
if self.queued_events.is_empty() {
822831
return None;
823832
}
824-
Some(self.queued_events.drain(..).collect())
833+
let (ready, pending): (Vec<_>, Vec<_>) =
834+
self.queued_events.drain(..).partition(|e| e.is_ready());
835+
self.queued_events = pending;
836+
if ready.is_empty() {
837+
return None;
838+
}
839+
Some(ready)
825840
}
826841

827842
fn stop(&mut self) -> Result<(), InputError> {

src/input/target/ultimate2_wireless.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,11 +379,18 @@ impl TargetInputDevice for Ultimate2WirelessDevice {
379379
])
380380
}
381381

382+
/// Returns any events in the queue up to the [TargetDriver]
382383
fn scheduled_events(&mut self) -> Option<Vec<ScheduledNativeEvent>> {
383384
if self.queued_events.is_empty() {
384385
return None;
385386
}
386-
Some(self.queued_events.drain(..).collect())
387+
let (ready, pending): (Vec<_>, Vec<_>) =
388+
self.queued_events.drain(..).partition(|e| e.is_ready());
389+
self.queued_events = pending;
390+
if ready.is_empty() {
391+
return None;
392+
}
393+
Some(ready)
387394
}
388395

389396
fn stop(&mut self) -> Result<(), InputError> {

src/input/target/xpad.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,13 @@ impl TargetInputDevice for XBoxController {
286286
if self.queued_events.is_empty() {
287287
return None;
288288
}
289-
Some(self.queued_events.drain(..).collect())
289+
let (ready, pending): (Vec<_>, Vec<_>) =
290+
self.queued_events.drain(..).partition(|e| e.is_ready());
291+
self.queued_events = pending;
292+
if ready.is_empty() {
293+
return None;
294+
}
295+
Some(ready)
290296
}
291297

292298
/// Clear any local state on the target device.

0 commit comments

Comments
 (0)