diff --git a/crates/punktfunk-host/src/inject.rs b/crates/punktfunk-host/src/inject.rs
index 8ed2772..0c5d2d8 100644
--- a/crates/punktfunk-host/src/inject.rs
+++ b/crates/punktfunk-host/src/inject.rs
@@ -495,6 +495,12 @@ mod gamepad_raii;
#[cfg(target_os = "linux")]
#[path = "inject/linux/steam_controller.rs"]
pub mod steam_controller;
+/// Linux: virtual Steam Deck via the USB gadget subsystem (`raw_gadget` + `dummy_hcd`) — the only
+/// virtual-Deck transport Steam Input promotes (presents the controller on USB interface 2).
+/// SteamOS-host only (needs `dummy_hcd` + `raw_gadget`).
+#[cfg(target_os = "linux")]
+#[path = "inject/linux/steam_gadget.rs"]
+pub mod steam_gadget;
/// Transport-independent Steam Controller / Steam Deck HID contract (descriptor, byte-exact Deck
/// serializer, XInput/rich mappers, rumble parser), used by the Linux UHID backend ([`steam_controller`]).
#[cfg(target_os = "linux")]
diff --git a/crates/punktfunk-host/src/inject/linux/steam_controller.rs b/crates/punktfunk-host/src/inject/linux/steam_controller.rs
index 7461f02..3d9d4d2 100644
--- a/crates/punktfunk-host/src/inject/linux/steam_controller.rs
+++ b/crates/punktfunk-host/src/inject/linux/steam_controller.rs
@@ -239,8 +239,39 @@ impl Drop for SteamDeckPad {
/// Button/stick frames arrive via [`handle`](Self::handle); the right trackpad + motion via
/// [`apply_rich`](Self::apply_rich); [`pump`](Self::pump) services the kernel handshake + routes
/// rumble back; [`heartbeat`](Self::heartbeat) keeps the pad alive (and drives the mode-entry pulse).
+/// The transport a manager pad drives. UHID is universal but Steam Input won't promote it (a UHID
+/// device has no USB interface number); the USB gadget is SteamOS-only but Steam Input *does* promote
+/// it (it presents the controller on interface 2). Selected per-pad in [`SteamControllerManager::ensure`].
+enum DeckTransport {
+ Uhid(SteamDeckPad),
+ Gadget(crate::inject::steam_gadget::SteamDeckGadget),
+}
+
+impl DeckTransport {
+ fn write_state(&mut self, st: &SteamState) {
+ match self {
+ DeckTransport::Uhid(p) => {
+ let _ = p.write_state(st);
+ }
+ DeckTransport::Gadget(g) => g.write_state(st),
+ }
+ }
+ fn service(&mut self) -> Option<(u16, u16)> {
+ match self {
+ DeckTransport::Uhid(p) => p.service(),
+ DeckTransport::Gadget(g) => g.service().rumble,
+ }
+ }
+ fn in_mode_entry(&self) -> bool {
+ match self {
+ DeckTransport::Uhid(p) => p.in_mode_entry(),
+ DeckTransport::Gadget(_) => false,
+ }
+ }
+}
+
pub struct SteamControllerManager {
- pads: Vec