Files
punktfunk/docs/windows-dualsense-scoping.md
T
enricobuehler 450bcf1e7b feat(host): Apollo-backlog hardening — cert gate, NVENC RFI, media QoS, async injector
A pass over the apollo-comparison backlog (re-verified against current code).
Lands four items end-to-end plus a Windows-DualSense scoping doc.

- #5/#92/#26 — GameStream paired-cert allow-list. tls.rs surfaces the verified
  peer cert to handlers (serve_https + PeerCertFingerprint, now shared with the
  mgmt API instead of duplicated); nvhttp gates /launch /resume /applist /cancel
  on AppState.paired and reports a real PairStatus; save_paired writes atomically
  (temp+rename). Closes the "mTLS accepts any client cert" hole. + regression test.

- #6/#51/#19/#22 — NVENC caps query -> reference-frame invalidation. nvenc.rs
  query_caps probes nvEncGetEncodeCaps (max dims / 10-bit / custom-VBV / RFI),
  rejecting over-range modes and degrading 10-bit->8-bit instead of an opaque
  InvalidParam. New Encoder::invalidate_ref_frames (default false -> caller
  keyframes); the Windows NVENC path implements real RFI (multi-ref DPB +
  nvEncInvalidateRefFrames, dedup + IDR-on-overflow). control.rs decodes the
  0x0301 lost-frame range (Apollo's IDX_INVALIDATE_REF_FRAMES) -> AppState.rfi_range
  -> encode loop, falling back to a keyframe. NOTE: the Windows NVENC impl is
  RTX-box/CI-pending (can't compile on Linux); adversarially reviewed vs the SDK.

- #43/#72 — media socket QoS + buffer growth. New punktfunk_core::transport::qos:
  grow_socket_buffers (factored out the native plane's 32MB SO_SNDBUF growth so the
  GameStream sockets reuse it) + set_media_qos (opt-in PUNKTFUNK_DSCP=1: DSCP CS5
  video / CS6 audio + Linux SO_PRIORITY, Apollo's scheme). Wired into UdpTransport
  and the GameStream video/audio sockets. Windows IP_TOS needs qWAVE (follow-up).

- #8/#45 — GameStream input injection off the ENet service thread. on_receive no
  longer injects inline (a slow inject head-blocked ENet keepalive/retransmit); it
  forwards to a dedicated injector thread. The hardened InjectorService moved from
  punktfunk1 into crate::inject (shared by both planes) + a coalesce step that sums
  adjacent relative-mouse/scroll deltas while preserving button/key/abs ordering.

Docs: re-verified apollo-comparison.md status (22 items already done/obsolete since
the snapshot) + windows-dualsense-scoping.md (ViGEm can't emulate a DualSense; real
DS5 on Windows needs a VHF virtual-HID driver — web-research pass pending).

fmt + clippy -D warnings clean; full workspace test suite green; no C-ABI/OpenAPI drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 00:06:30 +00:00

8.8 KiB
Raw Blame History

Windows host — virtual DualSense scoping

Status: scoping (2026-06-20). Decision pending the web-research pass (see Open questions — web search was unavailable when this was written, so the VHF API/signing specifics and the "existing-driver-to-vendor" survey are marked TO-CONFIRM).

TL;DR

Apollo's backlog item #23/#89 ("DS4 ViGEm target on Windows") is the wrong target if the goal is actual DualSense. ViGEmBus emulates only Xbox 360 (XUSB) and DualShock 4 (DS4) — never a DualSense. Because this is a host-side virtual pad, the DualSense-defining features (adaptive triggers, the fine haptic actuators, DS5 identity) can only work end-to-end if the game sees a real DualSense and therefore drives them; a DS4 virtual pad means the game uses its DS4 code path and never emits those commands, so the client's adaptive-trigger rendering is never exercised. ViGEm DS4 structurally cannot deliver adaptive triggers.

The right path is the Windows analog of what the Linux host already does: present a real virtual DualSense HID device (Sony VID 054C / PID 0CE6, the inputtino PS5 report descriptor). On Windows that means a kernel-mode virtual-HID device via the Virtual HID Framework (VHF) — the UHID analog — which is a SudoVDA-class driver effort (vendored + signed, installed by the existing Inno installer).

Why this is the wrong place to copy Apollo

Apollo (and all of Sunshine's lineage) does DualSense only on Linux (inputtino, DualSenseWired). Its Windows input path (src/platform/windows/input.cpp) is ViGEm XUSB_REPORT + DS4_REPORT_EX only — MPS2_TO_DS4_ACCEL motion conversion, inverse-ViGEmBus gyro calibration, DS4 touchpad packing. There is zero VHF / virtual-HID / DualSense code on Apollo's Windows side. So:

  • Copying Apollo on Windows gets us a DS4, with the adaptive-trigger ceiling baked in.
  • There is no in-ecosystem upstream (Sunshine/Apollo/Wolf) that already solved virtual DualSense on Windows to vendor from. This would be novel work for the streaming-host space.

The parity target — and what's already done

The Linux host (crates/punktfunk-host/src/inject/dualsense.rs) creates a UHID device presenting the genuine DualSense descriptor, so the kernel hid-playstation driver binds it and games see a real DualSense — gamepad + motion + touchpad + lightbar/player-LEDs + adaptive triggers. It writes HID input report 0x01 (controller state) and reads HID output report 0x02 (the game's rumble/LED/trigger feedback), which it forwards to the client as punktfunk_core::quic::HidOutput.

Crucially, everything except the host backend is already platform-agnostic and DualSense-complete:

Layer State Where
Protocol planes (rich input 0xCC, rumble 0xCA, HID-output 0xCD) done punktfunk_core::quic
Feedback abstraction (HidOutput::{Led,PlayerLeds,Trigger,…}) done punktfunk_core::quic
Pad-type negotiation (client pref > env > default), GamepadPref::DualSense done punktfunk1.rs::resolve_gamepad
Backend dispatch (enum PadBackend) done; DualSense arm is #[cfg(target_os="linux")] punktfunk1.rs:1229
Clients (capture + adaptive-trigger/lightbar/haptic rendering) done, all platforms clients/*
C-ABI (next_hidout / send_rich_input) done abi.rs
Host virtual-DualSense backend Linux only (UHID) inject/dualsense.rs

So a Windows DualSense backend needs no protocol, client, or C-ABI change. It must only: create a virtual DualSense HID device, translate our pad state → HID input report 0x01, and surface the game's HID output report 0x02 as the same HidOutput events the Linux path already emits. That is a well-bounded host-side addition (driver + a DualSenseManager-shaped userspace bridge + a PadBackend::DualSense Windows arm).

The Windows mechanism — VHF (primary candidate)

Windows has no userspace HID-device creation (unlike Linux UHID), so a real virtual DualSense requires a kernel component. The Microsoft-sanctioned one is the Virtual HID Framework (VHF): a small KMDF driver creates a virtual HID device from an arbitrary report descriptor, submits input reports to the OS, and receives output/feature reports written by applications (our feedback hook). This is the structural twin of /dev/uhid.

Sketch of the integration (TO-CONFIRM details in Open questions):

host process (Rust)  <--IOCTL/named-pipe-->  punktfunk-ds5.sys (KMDF + VHF)  <--HID-->  game / Steam / GameInput
  PadState  ----------- input report 0x01 ----------->  VhfReadReportSubmit
  HidOutput <-- output report 0x02 (write callback) ---  EvtVhf*WriteReport
  • Descriptor reuse: the exact inputtino PS5 descriptor + feature-report replies we already ship for Linux (dualsense.rs DS_* constants) — same bytes, same VID/PID, so Windows + games recognize it as a DualSense.
  • Userspace bridge: a DualSenseManager-shaped struct mirroring the Linux one (same RichInput → report 0x01 packing, same HidOutput parsing from report 0x02), talking to the driver over an IOCTL/pipe instead of /dev/uhid.
  • Packaging: vendor + sign the .sys/.inf/.cat and install via the existing packaging/windows/sudovda machinery (nefconc.exe + an install-*.ps1, bundled in the Inno setup.exe). The precedent is already in the repo.

Effort & risk

Piece Rough size Notes / risk
KMDF + VHF virtual-HID driver large KMDF (kernel) is a higher bar than SudoVDA's UMDF/IddCx; bulk of the work
Driver signing + distribution medium EV cert + Microsoft attestation for production; test-signing for dev; SudoVDA precedent but it's pre-signed/vendored, not built here
Userspace DualSenseManager (Windows) smallmedium Mostly a port of the Linux report packing/parsing; reuses descriptors
PadBackend::DualSense Windows arm + negotiation small Un-gate the existing dispatch for Windows
HidHide-style hiding of a physical pad small (maybe unneeded) Headless host usually has no physical pad; only matters if one is attached

Top risks: (1) a KMDF/VHF driver is real kernel work + signing logistics; (2) whether VHF's output-report callback cleanly surfaces the DualSense 0x02 effect report we need for adaptive triggers; (3) whether games/Steam/Windows.Gaming.Input/GameInput accept a VHF-sourced DualSense the same as a physical one (descriptor + VID/PID should suffice, but unverified on Windows).

Decision matrix

Option Adaptive triggers / DS5 identity Effort When it's right
A. VHF virtual DualSense (parity) full large (kernel driver) the goal — matches the Linux host
B. ViGEm DS4 (interim) never (DS4 ceiling) small quick PS-pad-on-Windows w/ touchpad/motion/lightbar/rumble, no adaptive triggers
C. Hybrid A for DS5 clients, B/Xbox360 fallback A + small belt-and-suspenders once A exists
D. Defer if a higher-ROI item (#9 launch, #7/#18 audio) wins the slot

Xbox 360 (XInput) is already implemented and covers most Windows games regardless.

Open questions — REQUIRES the web-research pass (search was down)

  1. VHF specifics: confirm VHF is the right/current mechanism (vs. a newer HID-injection API); exact API (VhfCreate/VhfStart/VhfReadReportSubmit/the output-report EvtVhf…WriteReport callback); KMDF-only or UMDF-capable; minimum Windows version; the MS vhidmini/VHF sample.
  2. Existing driver to vendor: is there a maintained virtual-HID / virtual-DualSense Windows driver (Nefarius/community) we can vendor like SudoVDA, instead of writing a KMDF driver from scratch?
  3. Recognition: does a VHF device with VID 054C/PID 0CE6 + the DualSense descriptor get recognized as a DualSense by Windows.Gaming.Input / GameInput / Steam Input / native-DS5 games — including adaptive triggers via the 0x02 output report?
  4. Signing/distribution: attestation vs. WHQL for a KMDF driver; can we test-sign for dev and ship an attestation-signed driver via the Inno installer like SudoVDA?
  5. HidHide: needed at all on a (usually headless) host, or only when a physical pad is present?
  1. Web-research pass (when search is back) to close the five questions above — especially #2 (vendor vs. build) and #1 (VHF feasibility + output-report support), which gate the whole effort.
  2. If VHF (or a vendorable driver) is confirmed feasible: build Option A — driver + Windows DualSenseManager + un-gate PadBackend::DualSense, reusing the inputtino descriptor and the existing HidOutput plane (no protocol/client/ABI change), packaged via the SudoVDA path.
  3. Keep Xbox 360 as-is and treat ViGEm DS4 only as an optional fallback (Option C), never as the DualSense answer.