450bcf1e7b
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>
135 lines
8.8 KiB
Markdown
135 lines
8.8 KiB
Markdown
# 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) | small–medium | 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?
|
||
|
||
## Recommended plan
|
||
|
||
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.
|