Skip to content

feat(input/windows): add WinUHid backend for native DualSense emulation#4948

Closed
hkirste wants to merge 5 commits into
LizardByte:masterfrom
hkirste:feat/winuhid-dualsense-windows
Closed

feat(input/windows): add WinUHid backend for native DualSense emulation#4948
hkirste wants to merge 5 commits into
LizardByte:masterfrom
hkirste:feat/winuhid-dualsense-windows

Conversation

@hkirste
Copy link
Copy Markdown

@hkirste hkirste commented Apr 4, 2026

Description

Add a WinUHid-based gamepad backend for native DualSense (PS5) controller emulation on Windows, alongside the existing ViGEmBus backend.

When the WinUHid driver is installed, Sunshine creates virtual DualSense controllers instead of DS4 via ViGEmBus, enabling:

  • Adaptive trigger effects flowing from games back to the Moonlight client
  • Games and Steam detecting a real DualSense (VID 054C / PID 0CE6)
  • Gyro/accel passthrough without inverse-calibration hacks
  • Native 1920x1080 touchpad resolution (was 1920x943 on DS4)
  • RGB lightbar feedback forwarded to client (was discarded on DS4 path)
  • Player indicator LEDs forwarded to client (protocol extension 0x5504)
  • Mic mute LED state forwarded to client: off, on, pulse (protocol extension 0x5505)
  • Firmware version passthrough from physical controller via TLV metadata on arrival packet, preventing firmware update prompts

ViGEmBus remains as automatic fallback when WinUHid is not installed. All WinUHid code is guarded by #ifdef SUNSHINE_WINUHID — zero risk to existing functionality.

Architecture:

  • winuhid_t manager class parallel to existing vigem_t
  • winuhid_gamepad_context_t holds per-gamepad state and PS5 input report
  • Six WinUHid callbacks (rumble, lightbar LED, player LED, mic LED, adaptive triggers) push feedback onto the existing feedback_queue
  • TLV metadata extensions on the controller arrival packet carry firmware info from client to host
  • No changes needed to stream.cpp protocol handling — uses the same Sunshine extension mechanism (0x55xx) as existing features

Dependencies:

  • cgutman/WinUHid added as submodule (UMDF driver using Microsoft's VHF framework, by the Moonlight author)
  • MinGW WRL compatibility shim at third-party/WinUHid-compat/ for cross-compiler support

Client requirements:

  • Adaptive trigger feedback requires moonlight-qt with DualSense adaptive trigger support (commit d9c7a24, merged 2025-04-02). Stable v6.1.0 does NOT include this — a nightly build or v6.2+ is needed.
  • Player LED, mic LED, and firmware passthrough require additional moonlight-qt and moonlight-common-c changes (PRs pending).
  • Rumble, lightbar RGB, and gyro/accel work on v6.1.0+.

Cross-project changes (PRs pending):

  • cgutman/WinUHid — firmware passthrough support, mic LED callback, feature report stubs
  • moonlight-stream/moonlight-common-c — TLV metadata on controller arrival, player/mic LED protocol extensions
  • moonlight-stream/moonlight-qt — firmware read from physical DualSense, player/mic LED forwarding via SDL

Testing done:

  • Build verified with SUNSHINE_ENABLE_WINUHID=ON and OFF (MinGW g++ 15.2)
  • Sunshine detects WinUHid driver at runtime ("WinUHid driver detected (interface version 1)")
  • Virtual DualSense creation, input passthrough (sticks, buttons, triggers, touchpad, gyro over Bluetooth), and feedback forwarding (rumble + adaptive triggers felt on physical controller) all verified end-to-end
  • Player LED and mic LED state forwarded to physical controller verified end-to-end
  • Firmware version from physical DualSense (connected via Bluetooth) successfully passed through to virtual DualSense on host, verified via HID feature report 0x20 read
  • Graceful fallback to ViGEmBus when WinUHid driver not installed
  • Graceful fallback to default firmware version when client doesn't provide firmware metadata

Screenshot

Issues Fixed or Closed

Roadmap Issues

Type of Change

  • feat: New feature (non-breaking change which adds functionality)
  • fix: Bug fix (non-breaking change which fixes an issue)
  • docs: Documentation only changes
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semicolons, etc.)
  • refactor: Code change that neither fixes a bug nor adds a feature
  • perf: Code change that improves performance
  • test: Adding missing tests or correcting existing tests
  • build: Changes that affect the build system or external dependencies
  • ci: Changes to CI configuration files and scripts
  • chore: Other changes that don't modify src or test files
  • revert: Reverts a previous commit
  • BREAKING CHANGE: Introduces a breaking change (can be combined with any type above)

Checklist

  • Code follows the style guidelines of this project
  • Code has been self-reviewed
  • Code has been commented, particularly in hard-to-understand areas
  • Code docstring/documentation-blocks for new or existing methods/components have been added or updated
  • Unit tests have been added or updated for any new or modified functionality

AI Usage

  • None: No AI tools were used in creating this PR
  • Light: AI provided minor assistance (formatting, simple suggestions)
  • Moderate: AI helped with code generation or debugging specific parts

hkirste added 4 commits April 1, 2026 20:52
Add cgutman/WinUHid as a submodule. WinUHid is a UMDF driver using
Microsoft's VHF (Virtual HID Framework) that enables user-mode
applications to create virtual HID devices. It provides ready-made
DualSense (PS5), DualShock 4, and Xbox One controller emulation
with full feature support including adaptive triggers, motion
sensors, touchpad, and lightbar control.
- Add SUNSHINE_ENABLE_WINUHID CMake option (ON by default)
- Compile WinUHid and WinUHidPS5 sources as static (WINUHID_STATIC)
- Add MinGW-compatible WRL shim for Wrappers::Event/FileHandle
  (WinUHid uses MSVC WRL for RAII handle wrappers; this shim
  provides equivalent types for MinGW builds)
- Rename DllMain per-file via -D to avoid duplicate symbol errors
- Define SUNSHINE_WINUHID preprocessor guard for conditional code
Add a WinUHid-based gamepad backend alongside the existing ViGEmBus
backend. When the WinUHid driver is installed, Sunshine can now
create virtual DualSense (PS5) controllers that games recognize
natively, enabling features that were impossible with DS4 emulation:

- Adaptive trigger effects flow from games back to the Moonlight
  client's physical DualSense controller
- RGB lightbar color feedback is now forwarded (was discarded)
- Motion sensor data passes through without inverse-calibration
  hacks (WinUHid provides proper calibration in feature reports)
- Touchpad uses native 1920x1080 DualSense resolution (was 1920x943)
- Games and Steam detect the controller as a real DualSense

Architecture:
- winuhid_t manager class parallel to vigem_t
- winuhid_gamepad_context_t holds per-gamepad state and PS5 report
- Four WinUHid callbacks (rumble, lightbar, player LED, adaptive
  triggers) push feedback onto the existing feedback_queue
- No changes needed to stream.cpp — the adaptive trigger and LED
  wire protocol support already existed from the Linux DS5 work
- Backend selection: WinUHid preferred for PS-type controllers,
  ViGEm fallback for Xbox 360 or when WinUHid is not installed
- All WinUHid code guarded by #ifdef SUNSHINE_WINUHID

Adds "ds5" to supported_gamepads when WinUHid is available.
- Make WINUHID_STATIC a global compile definition instead of per-file
  (fixes dllimport errors when GCC processes WinUHid header includes)
- Remove redundant #define WINUHID_STATIC from input.cpp
- Rename local variable 'info' to 'gp_info' to avoid collision with
  Boost.Log's BOOST_LOG(info) macro
- Add noexcept move constructor/assignment to winuhid_gamepad_context_t
- Replace C-style void* casts with static_cast
- Replace raw new/delete with std::unique_ptr for winuhid_t and vigem_t
- Extract should_use_ds5() helper to reduce alloc_gamepad complexity
- Extract winuhid_handle_touch() to reduce gamepad_touch complexity
- Replace redundant type declarations with auto
- Split multi-variable declarations into separate statements
- Remove comment that SonarCloud flagged as commented-out code
- Use named parameters instead of (void) casts for unused args
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 4, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
12 New issues
12 New Code Smells (required ≤ 0)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@ReenigneArcher
Copy link
Copy Markdown
Member

WinUHID is not ready to be used yet. There are no drivers built/released that can be installed easily by Sunshine users.

Due to this the build needs to be incorporated into Sunshine's build process. I have a PR open in WinUHID that will make this slightly easier.

Until that is merged there is not much point in this PR. And since I'm working on the same exact feature, although I'm completely replacing vigembus... I will close this.

@hkirste
Copy link
Copy Markdown
Author

hkirste commented Apr 4, 2026

Thanks for the context. Couple of things worth noting:

This implementation is fully tested end-to-end. Input passthrough over Bluetooth, adaptive trigger effects on real hardware, rumble, LED, clean ViGEmBus fallback. The code side is solid.

On driver distribution, totally fair point. If you're planning to build the UMDF driver as part of Sunshine's build, heads up: it needs the full WDK toolchain (stampinf for INF macros, WPP preprocessing, UMDF version stamping). Building it outside a windows-latest CI environment was painful. The user-mode library compiles fine with MinGW though.

Happy to have this branch used as reference for the input mapping, feedback callbacks, and the MinGW WRL compatibility shim. Let me know if there's a good way to plug into what you're doing.

@ReenigneArcher
Copy link
Copy Markdown
Member

Appologies if my first comment was short and didn't explain things in a lot of detail. We definitely need to migrate away from vigembus, so it's not that this is closed because I'm not planning to have this at all.

For reference: cgutman/WinUHid#2

On driver distribution, totally fair point. If you're planning to build the UMDF driver as part of Sunshine's build

It's not that I want to build the driver as part of Sunshine's build process. It would be ideal if it was pre-built and we could just consume it like we do for vigembus. Currently it's not available though, and for sure 99% of windows users are never going to know how to build a driver on their own, lol.

My other changes in Sunshine I have kept local for now.

Building it outside a windows-latest CI environment was painful. The user-mode library compiles fine with MinGW though.

Might be good to submit a PR documenting the process to WinUHid.

Does the driver need to be signed? I am assuming it does, and if so what kind of signing is required? OV or EV? I think just OV for user mode, but I am not 100% sure.

Happy to have this branch used as reference for the input mapping, feedback callbacks, and the MinGW WRL compatibility shim. Let me know if there's a good way to plug into what you're doing.

Once there is a distributable driver, we can definitely resume this. Maybe we need a mini repo to build and distribute the driver. If OV signing is okay, I'd be happy to setup a build directory if you'd be okay will helping out with the initial workflow? Or should it just be a fork? cgutman is a top contributor here, and obviously the primary Moonlight maintainer, so I definitely don't want to step on their toes.

We can discuss further on discord if you'd like.

@hkirste
Copy link
Copy Markdown
Author

hkirste commented Apr 4, 2026

No worries, makes sense now.

On signing: OV is enough for UMDF. During testing I got it working with just a self-signed cert in the TrustedPublisher store, Secure Boot on, no test signing needed. So an OV cert would be more than sufficient for distribution. No EV required, that's only for kernel-mode drivers.

A mini repo for the driver build+sign+package makes total sense. I went through the entire build process manually and can document the exact steps. The main pain points were: stampinf for INF macro expansion ($ARCH$, $UMDFVERSION$), the WPP tracing stub (WinUHid.tmh doesn't exist without the WDK preprocessor), UMDF library version matching the target OS, and getting the NuGet WDK package (Microsoft.Windows.WDK.x64) wired up correctly. Once you know the steps it's straightforward to automate in a GitHub Actions workflow on windows-latest.

Happy to help set up that workflow. I'll ping you on Discord, username is hkirste.

@cgutman
Copy link
Copy Markdown
Collaborator

cgutman commented Apr 4, 2026

It's not that I want to build the driver as part of Sunshine's build process. It would be ideal if it was pre-built and we could just consume it like we do for vigembus. Currently it's not available though, and for sure 99% of windows users are never going to know how to build a driver on their own, lol.

Yep, my goal is a prebuilt MSI like Vigembus was. However, we currently have a signing problem that is blocking this.

Does the driver need to be signed? I am assuming it does, and if so what kind of signing is required? OV or EV? I think just OV for user mode, but I am not 100% sure.

It does. Unfortunately for full compatibility with all architectures, it needs to be signed through the WHQL process since it's a driver. That means having an EV certificate (and an official legal entity).

Since WinUHid is a UMDF driver rather than a driver that loads into kernel mode, you can technically get away with OV for x86 and x64, since they are grandfathered into to lower code integrity requirements for UMDF. However, this strategy doesn't work for ARM64.

@ReenigneArcher
Copy link
Copy Markdown
Member

@cgutman thanks for the info! Do you have OV signing? I think it would be nice to continue with x86_64 even if we have to continue falling back to vigembus on ARM64 for now.

@cgutman
Copy link
Copy Markdown
Collaborator

cgutman commented Apr 9, 2026

I do, though I've got to see if I remember the PIN for my HSM 😬

In any case, I will take a look at WinUHid again in a week or so. I need to remember where I left it last year.

@lurebat
Copy link
Copy Markdown

lurebat commented Apr 29, 2026

@cgutman @hkirste

For now I created (to be honest, vibed) a fork:

https://github.com/lurebat/WinUHid

It:

  • Merges in all open PRs
  • Adds README.md files
  • Adds just task runner
  • Adds easy install/uninstall scripts for the driver
  • Adds a test web app

So it will be easier to use and play around with, to hopefully make this happen.

I can also open PRs to the original, I just didn't want to by default since it's vibe coded.
If you'll accept some of it, even if it just the docs or code that doesn't touches the core cpp I would make a PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants