feat(gamepad): Windows Steam Deck backend — Steam-Input-promoted UMDF virtual Deck
windows-drivers / probe-and-proto (push) Successful in 18s
windows / build (aarch64-pc-windows-msvc) (push) Has been cancelled
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Has been cancelled
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Has been cancelled
windows-host / package (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
release / apple (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (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
decky / build-publish (push) Has been cancelled
deb / build-publish (push) Has been cancelled
ci / web (push) Successful in 51s
windows-drivers / driver-build (push) Successful in 1m51s
ci / docs-site (push) Successful in 54s
ci / bench (push) Successful in 5m20s
arch / build-publish (push) Successful in 13m32s
android / android (push) Successful in 15m7s
ci / rust (push) Successful in 21m11s
apple / swift (push) Successful in 4m59s
apple / screenshots (push) Successful in 20m53s

The N4 GO verdict, productized. GamepadPref::SteamDeck on a Windows host
now builds a real virtual Deck instead of folding to DualSense: games
get native Deck glyphs + both trackpads + gyro + all four back grips
through Steam Input's own remapping.

- steam_deck_windows.rs: DeckWinPad/DeckWinProto/SteamDeckWindowsManager
  over the sealed shm channel, sharing the whole Linux Deck codec
  (steam_proto now compiles on Windows too — it was already pure). The
  SwDevice identity carries usb_mi: Some(2): the &MI_02 hardware-id
  token hidclass mirrors into the HID child and Steam parses as the
  wired controller interface — the promotion gate.
- Driver: DEVTYPE_STEAMDECK (3) graduates from the spike — SET_FEATURE
  0xEB rumble / 0x8F haptic pulses are republished to the host through
  the output slot (report-id-0 prefixed, so parse_steam_output sees the
  Linux wire shape), and the 0xAE/GET_STRING serial + 0x83 unit id are
  per-pad (read from the section's pad_index; PFDK<unit-id> matches
  steam_proto::deck_serial).
- Router: SteamDeck arms in the Windows Pads paths; pick_gamepad flips
  SteamDeck-if-windows -> SteamDeck (the DualSense fold retires);
  dualsense-windows-test grows --deck.

ON-GLASS VALIDATED on .173 (rebuilt signed driver 9.9.0714.12xx
installed, Steam live): the manager-created pad (index 1) enumerates
with per-pad serial PFDK50460001, Steam logs Interface: 2 ->
'!! Steam controller device opened' -> 'Steam Controller reserving
XInput slot 0' -> PollState 2 (actively polling our cycling input
frames) -> mapping activated; clean teardown on exit. Rumble round-trip
through a real game remains an on-glass debt (nothing sent 0xEB during
the idle hold).

Known gap vs Linux: no physical-Steam-controller conflict degrade on
Windows yet (degrade_steam_on_conflict is Linux-only — /sys scan); a
Windows equivalent needs SetupDi enumeration and is deferred.

Verified: .21 clippy -D warnings + 304/0 tests + fmt --all; .133 clippy
-D warnings + the WDK driver-workspace check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 12:36:26 +02:00
parent 4201851c7f
commit d8e8529cd7
11 changed files with 353 additions and 53 deletions
+31 -10
View File
@@ -1754,7 +1754,8 @@ const INJECTOR_REOPEN_BACKOFF: std::time::Duration = std::time::Duration::from_s
/// two identities), the XUSB companion driver (classic XInput) on Windows.
/// - DualSense / DualSense Edge / DualShock 4 — Linux UHID `hid-playstation`, or the Windows UMDF
/// minidriver (device-type 0/2/1).
/// - Steam Deck — Linux UHID `hid-steam`.
/// - Steam Deck — Linux UHID `hid-steam` (or usbip/gadget), or the Windows UMDF minidriver
/// (device-type 3, Steam-Input-promoted).
///
/// [`resolve_pad_kind`] folds any kind a platform can't build into one it can, so this never
/// constructs a manager the build lacks.
@@ -1787,6 +1788,8 @@ struct Pads {
dualsense_edge_win: Option<crate::inject::dualsense_edge_windows::DualSenseEdgeWindowsManager>,
#[cfg(target_os = "windows")]
dualshock4_win: Option<crate::inject::dualshock4_windows::DualShock4WindowsManager>,
#[cfg(target_os = "windows")]
steamdeck_win: Option<crate::inject::steam_deck_windows::SteamDeckWindowsManager>,
}
impl Pads {
@@ -1822,6 +1825,8 @@ impl Pads {
dualsense_edge_win: None,
#[cfg(target_os = "windows")]
dualshock4_win: None,
#[cfg(target_os = "windows")]
steamdeck_win: None,
}
}
@@ -1924,6 +1929,11 @@ impl Pads {
crate::inject::dualshock4_windows::DualShock4WindowsManager::new,
)
.handle(ev),
#[cfg(target_os = "windows")]
GamepadPref::SteamDeck => self
.steamdeck_win
.get_or_insert_with(crate::inject::steam_deck_windows::SteamDeckWindowsManager::new)
.handle(ev),
_ => self
.xbox360
.get_or_insert_with(crate::inject::gamepad::GamepadManager::new)
@@ -2006,6 +2016,12 @@ impl Pads {
m.apply_rich(rich)
}
}
#[cfg(target_os = "windows")]
GamepadPref::SteamDeck => {
if let Some(m) = &mut self.steamdeck_win {
m.apply_rich(rich)
}
}
_ => {}
}
}
@@ -2057,6 +2073,9 @@ impl Pads {
if let Some(m) = &mut self.dualshock4_win {
m.pump(&mut rumble, &mut hidout);
}
if let Some(m) = &mut self.steamdeck_win {
m.pump(&mut rumble, &mut hidout);
}
}
}
@@ -2099,6 +2118,9 @@ impl Pads {
if let Some(m) = &mut self.dualshock4_win {
m.heartbeat(gap);
}
if let Some(m) = &mut self.steamdeck_win {
m.heartbeat(gap);
}
}
}
}
@@ -2783,11 +2805,10 @@ fn pick_gamepad(pref: GamepadPref, env: Option<&str>, linux: bool, windows: bool
// are the N4 spike).
GamepadPref::SteamDeck if linux => GamepadPref::SteamDeck,
GamepadPref::SteamController if linux => GamepadPref::SteamController,
// No virtual Deck on Windows (M7) — fold to DualSense, the closest rich pad: its
// backend keeps gyro + trackpads + pad-click alive (the Deck's dual pads split the
// DualSense touchpad left/right per DsState::apply_rich). Folding to Xbox360 dropped
// all of that silently.
GamepadPref::SteamDeck if windows => GamepadPref::DualSense,
// Windows virtual Deck: the UMDF device-type-3 identity, Steam-Input-promoted via the
// MI_02 hardware-id synthesis (gamepad-new-types N4) — native Deck glyphs + trackpads +
// gyro + back grips, replacing the old fold to DualSense.
GamepadPref::SteamDeck if windows => GamepadPref::SteamDeck,
// DualSense Edge: Linux UHID hid-playstation / Windows UMDF (device-type 2) — the plain
// DualSense plus native back/Fn buttons, so the wire paddles stop hitting the fold/drop
// policy. Degrades to Xbox360 elsewhere like its siblings.
@@ -5373,11 +5394,11 @@ mod tests {
assert_eq!(pick_gamepad(Auto, Some("series"), true, false), XboxOne);
assert_eq!(pick_gamepad(XboxOne, None, false, true), Xbox360);
// Steam Deck: native on Linux; folds to DualSense on Windows (keeps gyro + trackpads
// via the UMDF backend — Xbox360 would drop the whole rich plane); Xbox360 elsewhere.
// Steam Deck: native on Linux (UHID/usbip/gadget) AND Windows (UMDF device-type 3,
// Steam-Input-promoted via MI_02 — gamepad-new-types N4); Xbox360 elsewhere.
assert_eq!(pick_gamepad(SteamDeck, None, true, false), SteamDeck);
assert_eq!(pick_gamepad(SteamDeck, None, false, true), DualSense);
assert_eq!(pick_gamepad(Auto, Some("deck"), false, true), DualSense);
assert_eq!(pick_gamepad(SteamDeck, None, false, true), SteamDeck);
assert_eq!(pick_gamepad(Auto, Some("deck"), false, true), SteamDeck);
assert_eq!(pick_gamepad(SteamDeck, None, false, false), Xbox360);
// Classic Steam Controller: native on Linux (UHID hid-steam); Xbox360 elsewhere.
assert_eq!(