fix(host): don't count punktfunk's own virtual Deck as a physical Steam controller
apple / swift (push) Successful in 1m7s
android / android (push) Has been cancelled
apple / screenshots (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / bench (push) Has been cancelled
ci / web (push) Has been cancelled
ci / rust (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
windows-host / package (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled

The Steam-conflict gate scanned /sys/bus/hid/devices for non-virtual 28DE
devices, but the usbip/gadget virtual Decks present a REAL USB device (vhci
resolves through vhci_hcd, not /devices/virtual/) — so a just-ended session's
pad still detaching, or a concurrent session's live one, read as "physical
Steam controller attached" and degraded every back-to-back Deck session to
DualSense (observed live on Bazzite). Exclude our pads by their PFDK… serial
(HID_UNIQ), with the vhci_hcd path as belt and braces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-04 11:14:24 +00:00
parent 853e7fe92f
commit 90c2d8b3a0
+16 -1
View File
@@ -1911,6 +1911,13 @@ fn degrade_if_no_uhid(chosen: GamepadPref) -> GamepadPref {
/// two Decks — confirmed conflict-prone on a Deck-as-host (the physical `28DE:1205` + Steam's /// two Decks — confirmed conflict-prone on a Deck-as-host (the physical `28DE:1205` + Steam's
/// `28DE:11FF` XInput output pad are both live). HID device dirs are named `BUS:VID:PID.INST` /// `28DE:11FF` XInput output pad are both live). HID device dirs are named `BUS:VID:PID.INST`
/// (uppercase); a UHID virtual device resolves through `/devices/virtual/…`, a real one does not. /// (uppercase); a UHID virtual device resolves through `/devices/virtual/…`, a real one does not.
///
/// Punktfunk's OWN virtual Decks must never count: the usbip/gadget transports present a real USB
/// device (vhci resolves through `vhci_hcd`, NOT `/devices/virtual/`), so a just-ended session's
/// pad still detaching — or a concurrent session's live one — read as "physical" and degraded
/// every back-to-back Deck session to DualSense (observed live on Bazzite 2026-07-04). Ours are
/// recognizable by the `PFDK…` serial ([`steam_proto::deck_serial`]) in `HID_UNIQ`, with the
/// vhci path as belt and braces.
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn physical_steam_controller_present() -> bool { fn physical_steam_controller_present() -> bool {
let Ok(entries) = std::fs::read_dir("/sys/bus/hid/devices") else { let Ok(entries) = std::fs::read_dir("/sys/bus/hid/devices") else {
@@ -1920,8 +1927,16 @@ fn physical_steam_controller_present() -> bool {
if !e.file_name().to_string_lossy().contains(":28DE:") { if !e.file_name().to_string_lossy().contains(":28DE:") {
return false; return false;
} }
if std::fs::read_to_string(e.path().join("uevent"))
.is_ok_and(|u| u.lines().any(|l| l.starts_with("HID_UNIQ=PFDK")))
{
return false; // one of our own virtual Decks
}
match std::fs::read_link(e.path()) { match std::fs::read_link(e.path()) {
Ok(target) => !target.to_string_lossy().contains("/virtual/"), Ok(target) => {
let t = target.to_string_lossy();
!t.contains("/virtual/") && !t.contains("vhci_hcd")
}
Err(_) => true, Err(_) => true,
} }
}) })