From 2621b6e6b1bc54303bb908ce9d698074fb58c68d Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Wed, 15 Jul 2026 11:22:16 +0200 Subject: [PATCH] feat(core,host,android): Steam Controller 2 as-is passthrough to Linux hosts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 2026 Steam Controller (Valve "Ibex" / SDL "Triton") captured on an Android client is passed through AS-IS: the host presents a virtual pad with the real wired identity (28DE:1302) and mirrors the physical pad's raw HID reports, so Steam on the host drives it over hidraw exactly like the real thing — trackpads, gyro, paddles, and its rumble/settings writes flow back onto the physical controller. Protocol ground truth: SDL's Valve-maintained SDL_hidapi_steam_triton.c + steam/controller_structs.h. Core: - GamepadPref::SteamController2 (wire byte 9; names steamcontroller2/ sc2/ibex) + PUNKTFUNK_GAMEPAD_STEAMCONTROLLER2 in the C ABI. - Raw HID planes: RichInput::HidReport (0xCC/0x04, client→host input reports verbatim, Copy fixed-64 body) and HidOutput::HidRaw (0xCD/0x05, host→client feature/output writes for replay). Best-effort is sound by the device protocol's own design (rumble re-sent every ~40 ms, settings every ~3 s — losses self-heal); HidRaw bypasses hidout dedup for exactly that reason. Host (Linux): - triton_proto.rs + steam_controller2.rs: Triton2Manager UHID backend — no kernel driver binds the PID (hidraw only; Steam Input is the consumer), raw mirroring with a typed-fallback 0x42 synthesizer until the first raw report, SET_REPORT ack + raw forward, canned GET_REPORT serial reply, rumble also parsed onto the universal 0xCA plane (phone mirror). Rides the uhid + 28DE-conflict degrades; UHID promotion by Steam is flagged in the creation log (usbip transport is the known follow-up if Steam ignores Interface:-1 devices for Triton too). Android: - Sc2UsbLink (wired/Puck: vendor-interface claim detaches the OS driver, interrupt read loop, lizard-off on the watchdog cadence, raw replay via interrupt-OUT / SET_REPORT with hidapi report-id framing) and Sc2BleLink (Valve vendor GATT service, notify subscribe machine, 0x45 re-framing, HIGH connection priority). - Sc2Capture orchestrator: raw plane + typed mirror (exit chord + host degrade paths keep working) on a GamepadRouter external slot; raw return path via GamepadFeedback.onHidRaw. - nativeSendPadHidReport JNI (direct ByteBuffer, no per-report copy), hidout raw decode, usb-host/BLUETOOTH_CONNECT manifest bits, opt-out settings toggle, StreamScreen engagement incl. the USB permission flow. Verified: core 149 + host 312 tests green on Linux (.21), on-box uhid smoke creates/mirrors/tears down the virtual 28DE:1302, C ABI harness round-trips, Android compileDebugKotlin green. On-glass with the real controller owed. Co-Authored-By: Claude Fable 5 --- .../android/app/src/main/AndroidManifest.xml | 8 + .../main/kotlin/io/unom/punktfunk/Settings.kt | 12 + .../io/unom/punktfunk/SettingsScreen.kt | 8 + .../kotlin/io/unom/punktfunk/StreamScreen.kt | 56 +++ .../kotlin/io/unom/punktfunk/kit/Gamepad.kt | 11 + .../io/unom/punktfunk/kit/GamepadFeedback.kt | 26 +- .../io/unom/punktfunk/kit/GamepadRouter.kt | 90 ++++- .../io/unom/punktfunk/kit/NativeBridge.kt | 17 +- .../io/unom/punktfunk/kit/Sc2BleLink.kt | 241 ++++++++++++ .../io/unom/punktfunk/kit/Sc2Capture.kt | 161 ++++++++ .../kotlin/io/unom/punktfunk/kit/Sc2Device.kt | 151 ++++++++ .../io/unom/punktfunk/kit/Sc2UsbLink.kt | 223 +++++++++++ clients/android/native/src/feedback.rs | 15 + clients/android/native/src/session/input.rs | 43 ++- .../Connection/PunktfunkConnection.swift | 7 + crates/pf-client-core/src/gamepad.rs | 12 +- crates/punktfunk-core/src/abi.rs | 29 +- crates/punktfunk-core/src/config.rs | 25 +- crates/punktfunk-core/src/quic/datagram.rs | 64 ++++ crates/punktfunk-core/src/quic/tests.rs | 58 ++- crates/punktfunk-host/src/inject.rs | 11 + .../src/inject/linux/steam_controller2.rs | 353 ++++++++++++++++++ .../src/inject/proto/dualsense_proto.rs | 7 + .../src/inject/proto/steam_proto.rs | 2 + .../src/inject/proto/triton_proto.rs | 306 +++++++++++++++ .../punktfunk-host/src/inject/uhid_manager.rs | 3 +- crates/punktfunk-host/src/punktfunk1.rs | 48 ++- docs-site/content/docs/configuration.md | 2 +- include/punktfunk_core.h | 28 ++ 29 files changed, 1977 insertions(+), 40 deletions(-) create mode 100644 clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/Sc2BleLink.kt create mode 100644 clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/Sc2Capture.kt create mode 100644 clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/Sc2Device.kt create mode 100644 clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/Sc2UsbLink.kt create mode 100644 crates/punktfunk-host/src/inject/linux/steam_controller2.rs create mode 100644 crates/punktfunk-host/src/inject/proto/triton_proto.rs diff --git a/clients/android/app/src/main/AndroidManifest.xml b/clients/android/app/src/main/AndroidManifest.xml index fa4034f1..de2e7a4a 100644 --- a/clients/android/app/src/main/AndroidManifest.xml +++ b/clients/android/app/src/main/AndroidManifest.xml @@ -27,6 +27,10 @@ + + @@ -40,6 +44,10 @@ ethernet-only boxes declare no wifi (discovery/WifiLock are best-effort hedges there). --> + + +