feat(gamepad): SC2 Puck-dongle passthrough with the native 28DE:1304 topology
Community-contributed round 5 of the Steam Controller 2 passthrough, reviewed + verified. A Puck-captured pad now presents the dongle's real seven-interface identity (CDC pair, four controller HID slots, management HID) instead of relabelling its reports as a wired 1302 — Steam's Puck feature dances (wireless_transport / esb/bond / 0xB4 slot status) get capture-shaped answers, and the wired identity's canned replies are corrected to the real captures (attribute count, string-attr framing, 0xF2 firmware info, bcdDevice nibble encoding). - new wire pref 10 = SteamController2Puck (Hello/Welcome byte; older peers degrade to Auto), selected by the Android capture link when the transport is a dongle, or by VID/PID in the degraded InputDevice path - TRITON_RDESC is now the captured numbered descriptor (mouse/keyboard lizard collections + per-id vendor reports); unnumbered framing made hidraw mangle feature report 2 and Steam eventually closed the device - interrupt-IN now queues sparse reports (battery/RSSI/wireless edges) instead of keeping latest-only, so a 250 Hz state packet can no longer erase them before the USB/IP poll observes them; EP0 SET_REPORT is split by wValue report type (OUTPUT parsed for rumble vs FEATURE) - vendored usbip-sim: config attributes/max-power, IAD prefix + BOS descriptor support, correct BCD minor.patch encoding (Deck's 0x0300/ 0x0200 values are nibble-zero, so its bytes are unchanged), and full-speed interrupt pacing in ms (was 8 kHz from the HS formula) - Triton feedback is serviced at 1 kHz while an SC2 backend exists so Steam's trackpad haptic writes reach the client unbatched Verified: clippy -D warnings + 319 host tests green on Linux, core wire tests green, Android kit/app compile + unit tests green. On-glass Puck retest owed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -272,9 +272,14 @@ impl TritonTransport {
|
||||
/// Open the best Steam-visible SC2 transport: **usbip (`vhci_hcd`) → UHID.** Steam is confirmed
|
||||
/// (on-glass 2026-07-15) to ignore the UHID leg, so reaching the fallback means the pad exists as
|
||||
/// hidraw only — flagged loudly, with the vhci_hcd remedy in the log.
|
||||
fn open_transport(idx: u8) -> Result<TritonTransport> {
|
||||
fn open_transport(idx: u8, puck: bool) -> Result<TritonTransport> {
|
||||
if crate::inject::steam_usbip::usbip_preferred() {
|
||||
match crate::inject::triton_usbip::TritonUsbip::open(idx) {
|
||||
let opened = if puck {
|
||||
crate::inject::triton_usbip::TritonUsbip::open_puck(idx)
|
||||
} else {
|
||||
crate::inject::triton_usbip::TritonUsbip::open(idx)
|
||||
};
|
||||
match opened {
|
||||
Ok(u) => return Ok(TritonTransport::Usbip(u)),
|
||||
Err(e) => {
|
||||
tracing::warn!(error = %format!("{e:#}"), "usbip SC2 unavailable — falling back to UHID")
|
||||
@@ -294,7 +299,15 @@ fn open_transport(idx: u8) -> Result<TritonTransport> {
|
||||
/// The Triton-specific half of the shared stateful manager (see [`PadProto`]): raw mirroring
|
||||
/// with the typed fallback, and the raw-forwarding service pass.
|
||||
#[derive(Default)]
|
||||
pub struct TritonProto;
|
||||
pub struct TritonProto {
|
||||
puck: bool,
|
||||
}
|
||||
|
||||
impl TritonProto {
|
||||
pub fn puck() -> Self {
|
||||
Self { puck: true }
|
||||
}
|
||||
}
|
||||
|
||||
impl PadProto for TritonProto {
|
||||
type Pad = TritonTransport;
|
||||
@@ -304,7 +317,7 @@ impl PadProto for TritonProto {
|
||||
const CREATE_HINT: &'static str = "";
|
||||
|
||||
fn open(&mut self, idx: u8) -> Result<TritonTransport> {
|
||||
open_transport(idx)
|
||||
open_transport(idx, self.puck)
|
||||
}
|
||||
|
||||
fn neutral(&self) -> TritonState {
|
||||
|
||||
Reference in New Issue
Block a user