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
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:
@@ -538,10 +538,11 @@ pub mod gamepad {
|
||||
/// `device_type` = DualSense Edge (`VID_054C&PID_0DF2` HID identity — the DualSense report
|
||||
/// codec plus the four native back/Fn button bits).
|
||||
pub const DEVTYPE_DUALSENSE_EDGE: u8 = 2;
|
||||
/// `device_type` = **N4-spike** Steam Deck identity (`VID_28DE&PID_1205`). Exists only for
|
||||
/// the `deck-windows-spike` go/no-go probe (does Steam Input on Windows promote a
|
||||
/// software-devnode HID Deck?) — never stamped by a session.
|
||||
pub const DEVTYPE_STEAMDECK_SPIKE: u8 = 3;
|
||||
/// `device_type` = Steam Deck controller (`VID_28DE&PID_1205` HID identity, the captured
|
||||
/// controller-interface descriptor + the Steam `0x83`/`0xAE` feature contract). Promoted by
|
||||
/// Steam Input on Windows when the devnode's synthesized USB hardware ids carry `&MI_02`
|
||||
/// (the wired controller interface — the N4-spike finding).
|
||||
pub const DEVTYPE_STEAMDECK: u8 = 3;
|
||||
|
||||
/// The value a gamepad driver writes into its section's `driver_proto` field once it attaches —
|
||||
/// the host's positive "driver is alive on this section" signal (health check + version audit).
|
||||
|
||||
@@ -885,9 +885,9 @@ pub const PUNKTFUNK_GAMEPAD_DUALSHOCK4: u32 = 4;
|
||||
/// UHID classic Steam Controller (Valve `28DE:1102`, kernel `hid-steam`): one stick + dual
|
||||
/// trackpads + two grip paddles. Honored only where available (Linux hosts); else Xbox 360.
|
||||
pub const PUNKTFUNK_GAMEPAD_STEAMCONTROLLER: u32 = 5;
|
||||
/// UHID Steam Deck controller (Valve `28DE:1205`, kernel `hid-steam`): full Deck gamepad incl. the
|
||||
/// four back grips, a right trackpad, and the IMU; re-grabbed by Steam Input with native glyphs when
|
||||
/// Steam runs on the host. Honored only where available (Linux hosts); else folds to X-Box 360.
|
||||
/// Steam Deck controller (Valve `28DE:1205`): full Deck gamepad incl. the four back grips, both
|
||||
/// trackpads, and the IMU; re-grabbed by Steam Input with native glyphs when Steam runs on the
|
||||
/// host. Honored on Linux AND Windows hosts; else folds to X-Box 360.
|
||||
pub const PUNKTFUNK_GAMEPAD_STEAMDECK: u32 = 6;
|
||||
/// DualSense Edge (Sony `054C:0DF2`): the DualSense plus two back buttons + two Fn buttons, so a
|
||||
/// client's back paddles land on native slots. Folds to `DUALSENSE` until its backend lands.
|
||||
|
||||
@@ -160,9 +160,10 @@ pub enum GamepadPref {
|
||||
/// trackpads + two grip paddles. The wire right stick drives the right pad; a left-pad contact
|
||||
/// shadows the stick (hardware multiplex). Needs Linux UHID.
|
||||
SteamController,
|
||||
/// UHID Steam Deck controller (Valve `28DE:1205`, kernel `hid-steam`) — full Deck gamepad incl.
|
||||
/// the four back grips (L4/L5/R4/R5), a right trackpad, and the IMU; re-grabbed by Steam Input
|
||||
/// with native glyphs when Steam runs on the host. Needs Linux UHID.
|
||||
/// Steam Deck controller (Valve `28DE:1205`) — full Deck gamepad incl. the four back grips
|
||||
/// (L4/L5/R4/R5), both trackpads, and the IMU; re-grabbed by Steam Input with native glyphs
|
||||
/// when Steam runs on the host. Linux (kernel `hid-steam` via UHID/usbip/gadget) or Windows
|
||||
/// (UMDF minidriver, Steam-Input-promoted).
|
||||
SteamDeck,
|
||||
/// DualSense Edge (Sony `054C:0DF2`, kernel `hid-playstation` ≥ 6.3 / Windows UMDF) — the
|
||||
/// DualSense plus two back buttons + two Fn buttons, so a client's back paddles (Deck grips,
|
||||
|
||||
@@ -527,6 +527,11 @@ pub mod pad_slots;
|
||||
#[cfg(target_os = "linux")]
|
||||
#[path = "inject/linux/steam_controller.rs"]
|
||||
pub mod steam_controller;
|
||||
/// Windows: virtual Steam Deck via the same UMDF minidriver + shared-memory channel
|
||||
/// (device-type 3) — promoted by Steam Input thanks to the `&MI_02` hardware-id synthesis.
|
||||
#[cfg(target_os = "windows")]
|
||||
#[path = "inject/windows/steam_deck_windows.rs"]
|
||||
pub mod steam_deck_windows;
|
||||
/// 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`).
|
||||
@@ -534,8 +539,9 @@ pub mod steam_controller;
|
||||
#[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")]
|
||||
/// serializer, XInput/rich mappers, rumble parser), used by the Linux UHID backend
|
||||
/// ([`steam_controller`]) and the Windows UMDF backend ([`steam_deck_windows`]).
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
#[path = "inject/proto/steam_proto.rs"]
|
||||
pub mod steam_proto;
|
||||
/// Pure fallback-remap policy (Steam-only inputs onto a non-Steam backend) + the Deck motion rescale.
|
||||
|
||||
@@ -494,7 +494,7 @@ pub fn deck_spike_hold(index: u8, secs: u64) -> Result<()> {
|
||||
// SAFETY: base points at SHM_SIZE writable bytes; the OFF_* offsets are in range. Device-type
|
||||
// FIRST, magic LAST — the same publish order the session pads use.
|
||||
unsafe {
|
||||
*base.add(OFF_DEVTYPE) = pf_driver_proto::gamepad::DEVTYPE_STEAMDECK_SPIKE;
|
||||
*base.add(OFF_DEVTYPE) = pf_driver_proto::gamepad::DEVTYPE_STEAMDECK;
|
||||
std::ptr::write_unaligned(base.add(OFF_PAD_INDEX) as *mut u32, index as u32);
|
||||
std::ptr::write_unaligned(base.add(OFF_INPUT) as *mut [u8; 64], neutral);
|
||||
std::ptr::write_unaligned(base as *mut u32, SHM_MAGIC);
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
//! Virtual Steam Deck controller on Windows via the UMDF minidriver — the Windows analogue of
|
||||
//! the Linux UHID Deck ([`super::steam_controller`]'s `SteamProto`), sharing its whole codec
|
||||
//! ([`super::steam_proto`]: the byte-exact `ID_CONTROLLER_DECK_STATE` serializer, the
|
||||
//! `XInput`/rich mappers, the `0xEB` rumble parser).
|
||||
//!
|
||||
//! Transport = the sealed shared-memory channel + a `SwDeviceCreate` devnode (device-type 3),
|
||||
//! like the PS pads — with the promotion lever the N4 spike proved: the synthesized USB
|
||||
//! hardware ids carry **`&MI_02`** (the Deck's wired controller interface), which hidclass
|
||||
//! mirrors into the HID child and hidapi/Steam parse as `bInterfaceNumber`. Steam Input then
|
||||
//! claims the pad exactly like a physical wired Deck (`!! Steam controller device opened`,
|
||||
//! XInput slot reserved — observed live on `.173`), so games get native Deck glyphs +
|
||||
//! trackpads + gyro + back grips through Steam's own remapping.
|
||||
//!
|
||||
//! Feedback: Steam drives Deck rumble (`0xEB`) and trackpad haptic pulses (`0x8F`) via
|
||||
//! SET_FEATURE on the unnumbered report; the driver republishes those into the section's
|
||||
//! output slot (report-id-0 prefixed), where [`parse_steam_output`] reads the exact wire shape
|
||||
//! the Linux path sees. No gamepad-mode entry pulse here — that gate lives in the Linux
|
||||
//! kernel's evdev parser; Steam-on-Windows reads the raw reports directly.
|
||||
|
||||
use super::dualsense_windows::{
|
||||
create_swdevice, SwDeviceProfile, OFF_DEVTYPE, OFF_DRIVER_PROTO, OFF_INPUT, OFF_OUTPUT,
|
||||
OFF_OUT_SEQ, OFF_PAD_INDEX, SHM_MAGIC, SHM_SIZE,
|
||||
};
|
||||
use super::gamepad_raii::PadChannel;
|
||||
use super::steam_proto::{
|
||||
neutral_deck_report, parse_steam_output, serialize_deck_state, SteamState, STEAM_REPORT_LEN,
|
||||
};
|
||||
use crate::inject::uhid_manager::{PadFeedback, PadProto, UhidManager};
|
||||
use anyhow::Result;
|
||||
use punktfunk_core::quic::RichInput;
|
||||
use std::time::Duration;
|
||||
|
||||
/// A single virtual Steam Deck: the `SwDeviceCreate`'d `pf_deck_<index>` devnode plus the sealed
|
||||
/// shared-memory channel. Dropping it removes the devnode and closes both sections.
|
||||
/// `pub`: the type appears as `type Pad` in the `PadProto` impl (a public trait).
|
||||
pub struct DeckWinPad {
|
||||
/// Per-session devnode from SwDeviceCreate, when it succeeds (RAII — `SwDeviceClose` on drop).
|
||||
_sw: Option<super::gamepad_raii::SwDevice>,
|
||||
/// The sealed channel: unnamed DATA section (`PadShm`) + bootstrap mailbox + handle delivery.
|
||||
channel: PadChannel,
|
||||
/// Watches the section's `driver_proto` field and logs attach / never-attached diagnosis.
|
||||
attach: super::gamepad_raii::DriverAttach,
|
||||
seq: u32,
|
||||
last_out_seq: u32,
|
||||
}
|
||||
|
||||
impl DeckWinPad {
|
||||
/// Create the sealed channel, stamp `device_type = Steam Deck` FIRST + the pad index + the
|
||||
/// neutral Deck frame + the magic LAST, then spawn the `pf_deck_<index>` devnode with the
|
||||
/// `MI_02` USB identity Steam's promotion gate requires.
|
||||
fn open(index: u8) -> Result<DeckWinPad> {
|
||||
let boot_name = pf_driver_proto::gamepad::pad_boot_name(index);
|
||||
let mut channel = PadChannel::create(boot_name.clone(), SHM_SIZE)?;
|
||||
let base = channel.data_base();
|
||||
// SAFETY: base points at SHM_SIZE writable bytes; the OFF_* offsets are in range.
|
||||
unsafe {
|
||||
*base.add(OFF_DEVTYPE) = pf_driver_proto::gamepad::DEVTYPE_STEAMDECK;
|
||||
std::ptr::write_unaligned(base.add(OFF_PAD_INDEX) as *mut u32, index as u32);
|
||||
std::ptr::write_unaligned(
|
||||
base.add(OFF_INPUT) as *mut [u8; STEAM_REPORT_LEN],
|
||||
neutral_deck_report(),
|
||||
);
|
||||
std::ptr::write_unaligned(base as *mut u32, SHM_MAGIC);
|
||||
}
|
||||
let inst = format!("pf_deck_{index}");
|
||||
let (hsw, instance_id) = match create_swdevice(&SwDeviceProfile {
|
||||
instance: &inst,
|
||||
container_index: index,
|
||||
hwid: "pf_steamdeck",
|
||||
usb_vid_pid: "VID_28DE&PID_1205",
|
||||
// The wired Deck controller interface — WITHOUT this the HID child carries no MI_
|
||||
// token, hidapi reports interface 0, and Steam never claims the pad (the N4
|
||||
// spike's run-1 failure).
|
||||
usb_mi: Some(2),
|
||||
description: "punktfunk Virtual Steam Deck",
|
||||
}) {
|
||||
Ok((h, i)) => (Some(h), i),
|
||||
Err(e) => {
|
||||
tracing::warn!(error = %format!("{e:#}"), "SwDeviceCreate failed; Steam Deck devnode unavailable");
|
||||
(None, None)
|
||||
}
|
||||
};
|
||||
let _sw = hsw.map(super::gamepad_raii::SwDevice::new);
|
||||
// Bounded eager delivery — the driver must read `device_type = 3` before hidclass asks
|
||||
// it for descriptors, or the pad would enumerate with the default DualSense identity.
|
||||
channel.deliver_eager(Duration::from_millis(1500));
|
||||
Ok(DeckWinPad {
|
||||
_sw,
|
||||
channel,
|
||||
attach: super::gamepad_raii::DriverAttach::new(
|
||||
"pf_steamdeck",
|
||||
"pf_dualsense.inf", // one driver package serves every identity
|
||||
"C:\\Users\\Public\\pfds-driver.log",
|
||||
boot_name,
|
||||
instance_id,
|
||||
),
|
||||
seq: 0,
|
||||
last_out_seq: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Serialize `st` into the Deck state frame and publish it to the section's input slot.
|
||||
fn write_state(&mut self, st: &SteamState) {
|
||||
self.seq = self.seq.wrapping_add(1);
|
||||
let mut r = [0u8; STEAM_REPORT_LEN];
|
||||
serialize_deck_state(&mut r, st, self.seq);
|
||||
// SAFETY: base points at SHM_SIZE bytes; input slot is OFF_INPUT..OFF_INPUT+64.
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(
|
||||
r.as_ptr(),
|
||||
self.channel.data_base().add(OFF_INPUT),
|
||||
r.len(),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
/// Poll the section's output slot; parse a newly-published Steam command (`0xEB` rumble /
|
||||
/// `0x8F` haptic pulse — republished by the driver off SET_FEATURE) into feedback. Also
|
||||
/// ticks the sealed-channel delivery and the driver-attach health watcher.
|
||||
fn service(&mut self) -> Option<(u16, u16)> {
|
||||
self.channel.pump();
|
||||
// SAFETY: base points at SHM_SIZE bytes.
|
||||
let proto = unsafe {
|
||||
std::ptr::read_unaligned(self.channel.data_base().add(OFF_DRIVER_PROTO) as *const u32)
|
||||
};
|
||||
self.attach.observe(proto);
|
||||
// SAFETY: base points at SHM_SIZE bytes.
|
||||
let seq = unsafe {
|
||||
std::ptr::read_unaligned(self.channel.data_base().add(OFF_OUT_SEQ) as *const u32)
|
||||
};
|
||||
if seq == self.last_out_seq {
|
||||
return None;
|
||||
}
|
||||
self.last_out_seq = seq;
|
||||
let mut out = [0u8; 64];
|
||||
// SAFETY: output slot is OFF_OUTPUT..OFF_OUTPUT+64 within the section.
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(
|
||||
self.channel.data_base().add(OFF_OUTPUT),
|
||||
out.as_mut_ptr(),
|
||||
64,
|
||||
)
|
||||
};
|
||||
parse_steam_output(&out).rumble
|
||||
}
|
||||
}
|
||||
|
||||
/// The Windows-Deck half of the shared stateful manager (see [`PadProto`]): the sealed-channel
|
||||
/// open under the promoted Deck identity, the same [`SteamState`] mappers as the Linux backend,
|
||||
/// and the section feedback poll. Lifecycle (slot table, unplug sweep, heartbeat, rumble dedup)
|
||||
/// lives in [`UhidManager`].
|
||||
#[derive(Default)]
|
||||
pub struct DeckWinProto;
|
||||
|
||||
impl PadProto for DeckWinProto {
|
||||
type Pad = DeckWinPad;
|
||||
type State = SteamState;
|
||||
const LABEL: &'static str = "Steam Deck/Windows";
|
||||
const DEVICE: &'static str = "Steam Deck";
|
||||
const CREATE_HINT: &'static str =
|
||||
" (install/repair: punktfunk-host.exe driver install --gamepad)";
|
||||
|
||||
fn open(&mut self, idx: u8) -> Result<DeckWinPad> {
|
||||
let p = DeckWinPad::open(idx)?;
|
||||
tracing::info!(
|
||||
index = idx,
|
||||
"virtual Steam Deck created (Windows UMDF shm channel, MI_02 promoted identity)"
|
||||
);
|
||||
Ok(p)
|
||||
}
|
||||
|
||||
fn neutral(&self) -> SteamState {
|
||||
SteamState::neutral()
|
||||
}
|
||||
|
||||
/// Merge buttons/sticks/triggers, preserving the rich-plane fields (trackpads + motion +
|
||||
/// pad clicks arrive separately and must survive a button-only frame) — identical to the
|
||||
/// Linux `SteamProto::merge_frame`.
|
||||
fn merge_frame(
|
||||
&self,
|
||||
prev: &SteamState,
|
||||
f: &crate::gamestream::gamepad::GamepadFrame,
|
||||
) -> SteamState {
|
||||
use super::steam_proto::btn;
|
||||
let mut s = SteamState::from_gamepad(
|
||||
f.buttons,
|
||||
f.ls_x,
|
||||
f.ls_y,
|
||||
f.rs_x,
|
||||
f.rs_y,
|
||||
f.left_trigger,
|
||||
f.right_trigger,
|
||||
);
|
||||
s.rpad_x = prev.rpad_x;
|
||||
s.rpad_y = prev.rpad_y;
|
||||
s.lpad_x = prev.lpad_x;
|
||||
s.lpad_y = prev.lpad_y;
|
||||
s.gyro = prev.gyro;
|
||||
s.accel = prev.accel;
|
||||
s.buttons |= prev.buttons & (btn::RPAD_TOUCH | btn::LPAD_TOUCH);
|
||||
s.lpad_click = prev.lpad_click;
|
||||
s.rpad_click = prev.rpad_click;
|
||||
s
|
||||
}
|
||||
|
||||
fn apply_rich(&self, st: &mut SteamState, rich: RichInput) {
|
||||
st.apply_rich(rich);
|
||||
}
|
||||
|
||||
fn write_state(&self, pad: &mut DeckWinPad, st: &SteamState) {
|
||||
pad.write_state(st);
|
||||
}
|
||||
|
||||
/// Poll the section for Steam's feedback: motor rumble on the universal 0xCA plane. The
|
||||
/// Deck has no rich host→client feedback plane (no lightbar / adaptive triggers), so
|
||||
/// `hidout` stays empty — parity with the Linux backend.
|
||||
fn service(&self, pad: &mut DeckWinPad, _idx: u8) -> PadFeedback {
|
||||
PadFeedback {
|
||||
rumble: pad.service(),
|
||||
hidout: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// All virtual Steam Deck pads of a Windows session — the analogue of the Linux
|
||||
/// `SteamControllerManager`, with the same method surface (via the shared [`UhidManager`]) as
|
||||
/// the other Windows pad managers.
|
||||
pub type SteamDeckWindowsManager = UhidManager<DeckWinProto>;
|
||||
@@ -429,9 +429,11 @@ fn real_main() -> Result<()> {
|
||||
let xbox = args.iter().any(|a| a == "--xbox");
|
||||
// `--edge` drives the DualSense Edge backend (device_type 2) and additionally holds
|
||||
// the R4/L4 paddles on the pressed beats, so a HID read shows the Edge bits in
|
||||
// report byte 10 (0x80|0x40) next to Cross.
|
||||
// report byte 10 (0x80|0x40) next to Cross. `--deck` drives the Steam Deck backend
|
||||
// (device_type 3, the MI_02-promoted identity) — watch Steam claim it live.
|
||||
let edge = args.iter().any(|a| a == "--edge");
|
||||
let extra_buttons: u32 = if edge {
|
||||
let deck = args.iter().any(|a| a == "--deck");
|
||||
let extra_buttons: u32 = if edge || deck {
|
||||
punktfunk_core::input::gamepad::BTN_PADDLE1
|
||||
| punktfunk_core::input::gamepad::BTN_PADDLE2
|
||||
} else {
|
||||
@@ -535,6 +537,11 @@ fn real_main() -> Result<()> {
|
||||
inject::dualsense_edge_windows::DualSenseEdgeWindowsManager::new(),
|
||||
"DualSense Edge"
|
||||
);
|
||||
} else if deck {
|
||||
drive!(
|
||||
inject::steam_deck_windows::SteamDeckWindowsManager::new(),
|
||||
"Steam Deck"
|
||||
);
|
||||
} else {
|
||||
drive!(
|
||||
inject::dualsense_windows::DualSenseWindowsManager::new(),
|
||||
|
||||
@@ -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!(
|
||||
|
||||
Reference in New Issue
Block a user