# 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.