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>
8.8 KiB
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.rsDS_*constants) — same bytes, same VID/PID, so Windows + games recognize it as a DualSense. - Userspace bridge: a
DualSenseManager-shaped struct mirroring the Linux one (sameRichInput→ report0x01packing, sameHidOutputparsing from report0x02), talking to the driver over an IOCTL/pipe instead of/dev/uhid. - Packaging: vendor + sign the
.sys/.inf/.catand install via the existingpackaging/windows/sudovdamachinery (nefconc.exe+ aninstall-*.ps1, bundled in the Innosetup.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)
- VHF specifics: confirm VHF is the right/current mechanism (vs. a newer HID-injection API);
exact API (
VhfCreate/VhfStart/VhfReadReportSubmit/the output-reportEvtVhf…WriteReportcallback); KMDF-only or UMDF-capable; minimum Windows version; the MSvhidmini/VHF sample. - 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?
- Recognition: does a VHF device with VID
054C/PID0CE6+ the DualSense descriptor get recognized as a DualSense by Windows.Gaming.Input / GameInput / Steam Input / native-DS5 games — including adaptive triggers via the0x02output report? - 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?
- HidHide: needed at all on a (usually headless) host, or only when a physical pad is present?
Recommended plan
- 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.
- If VHF (or a vendorable driver) is confirmed feasible: build Option A — driver + Windows
DualSenseManager+ un-gatePadBackend::DualSense, reusing the inputtino descriptor and the existingHidOutputplane (no protocol/client/ABI change), packaged via the SudoVDA path. - Keep Xbox 360 as-is and treat ViGEm DS4 only as an optional fallback (Option C), never as the DualSense answer.