|
libvirtualhid 105
Cross-platform C++ library for virtual HID devices.
|
This page is for people using an application that embeds libvirtualhid, such as Sunshine. You do not normally run or configure libvirtualhid directly. The streaming host uses it to create the virtual controller that Windows, Linux, FreeBSD, Steam, and games see.
Standard input and controller-specific features must pass through several independent layers:
Buttons, sticks, triggers, touch, motion, and battery state travel toward the host. Rumble, Xbox Impulse Triggers, DualSense adaptive-trigger effects, and LEDs travel back toward the client. A feature works end to end only when every layer in its direction supports it.
The capabilities advertised by a libvirtualhid profile describe what the host-side virtual controller can represent. They do not guarantee that a Moonlight client can read the feature from the physical controller, transmit it, or play feedback on the client device. The game and any compatibility layer, such as Steam Input, must support the feature too.
Before troubleshooting the stream, update the controller firmware and confirm that ordinary buttons and sticks work on the client device. Use the controller manufacturer's instructions:
A controller working locally proves only the physical controller-to-client part of the path. It does not prove that an extended feature is implemented by that Moonlight client.
Use the current Sunshine and Virtual HID Driver versions recommended by the Sunshine release you installed. On Windows, the Virtual HID Driver must be installed and have a valid machine license before Sunshine can create a driver-backed controller. After installing or updating the driver, restart Windows.
In Sunshine's Web UI, confirm that controller input is enabled and review the selected virtual gamepad under Configuration > Input. auto lets Sunshine choose a host-side profile from the features reported by the client. Selecting a profile manually changes what the game sees; it cannot add data that the client did not send.
Restart Sunshine, then reconnect the stream after changing the profile or updating the client, host, or driver. Sunshine creates the virtual gamepad for the streaming session.
See the Sunshine documentation for the current host-specific setup:
Moonlight is available as several clients with platform-specific input implementations. Their controller behavior can differ because the client platform, operating-system input APIs, controller connection, and Moonlight implementation expose different capabilities. Results from one client device should not be treated as proof for another, even when both run Android or use the same physical controller.
These observations are snapshots, not a permanent compatibility guarantee. The feature rows include the controller capabilities relevant to streaming, including manufacturer-specific features that are not yet implemented end to end. A check mark means that the complete path was observed working, while Not tested means that there is not yet a result to publish; it does not mean that the feature is unsupported.
| Feature | Moonlight Xbox | Moonlight Qt | Moonlight Android | Moonlight iOS |
|---|---|---|---|---|
| Xbox 360 | ||||
| Standard buttons, sticks, and D-pad | ✅ | ✅ | ✅ | ✅ |
| Analog trigger input (0 to 1) | ✅ | ✅ | ✅ | ✅ |
| Basic rumble | ✅ | ✅ | ✅ | ✅ |
| Xbox One | ||||
| Standard buttons, sticks, and D-pad | ✅ | ✅ | ✅ | ✅ |
| Analog trigger input (0 to 1) | ✅ | ✅ | ✅ | ✅ |
| Basic rumble | ✅ | ✅ | ✅ | ✅ |
| Impulse Triggers | ✅ | ✅ | ❌ | ❌ |
| Battery state | ❌4 | ❌4 | ❌4 | ❌4 |
| Xbox Series | ||||
| Standard buttons, sticks, and D-pad | ✅ | ✅ | ✅ | ✅ |
| Analog trigger input (0 to 1) | ✅ | ✅ | ✅ | ✅ |
| Basic rumble | ✅ | ✅ | ✅ | ✅ |
| Impulse Triggers | ✅ | ✅ | ❌ | ❌ |
| Battery state | ❌ | ❌ | ❌ | ❌ |
| Share button | ❌3 | ❌3 | ❌3 | ❌3 |
| DualShock 4 | ||||
| Standard buttons, sticks, and D-pad | N/A | ✅ | ✅ | ✅ |
| Analog trigger input (0 to 1) | N/A | ✅ | ✅ | ✅ |
| Basic rumble | N/A | ✅ | ✅5 | ✅ |
| Motion/gyro | N/A | ✅ | ✅1 | ❌ |
| Touchpad position | N/A | ✅ | ❌2 | ❌ |
| Touchpad click | N/A | ✅ | ❌2 | ❌ |
| Light bar (RGB/player color) | N/A | ✅ | ✅6 | ❌ |
| Battery state | N/A | ❌ | ❌ | ❌ |
| DualSense | ||||
| Standard buttons, sticks, and D-pad | N/A | ✅ | ✅ | ✅ |
| Analog trigger input (0 to 1) | N/A | ✅ | ✅ | ✅ |
| Basic rumble | N/A | ✅ | ✅5 | ✅ |
| Motion/gyro | N/A | ✅ | ✅1 | ❌ |
| Touchpad position | N/A | ✅ | ❌2 | ❌ |
| Touchpad click | N/A | ✅ | ❌2 | ❌ |
| Light bar (RGB) | N/A | ✅ | ✅6 | ❌ |
| Battery state | N/A | ❌ | ❌ | ❌ |
| Adaptive triggers | N/A | Not tested | ❌2 | ❌ |
| Player indicator | N/A | ❌7 | ❌7 | ❌ |
| MUTE button | N/A | ❌8 | ❌8 | ❌ |
| MUTE button LED | N/A | ❌7 | ❌7 | ❌ |
| Nintendo Switch Pro Controller | ||||
| Standard buttons, sticks, and D-pad | N/A | ✅ | ✅ | ✅ |
| Digital trigger input (0 or 1) | N/A | ✅ | ✅ | ✅ |
| Basic rumble | N/A | ✅ | ❌ | ❌ |
| Motion/gyro | N/A | ❌9 | ❌9 | ❌9 |
| Battery state | N/A | ❌ | ❌ | ❌ |
| Player LED | N/A | ❌7 | ❌7 | ❌ |
| Capture button | N/A | ✅ | ❌10 | ❌ |
1 Moonlight Android exposes its gamepad-motion option on Android 12 or later. The option must be enabled, and the Android device must expose the controller's motion sensors. Available motion settings can therefore differ between Android devices. See the Moonlight Android implementation.
2 Native PlayStation touchpad forwarding depends on how Android exposes the controller. DualSense support requires Android 12 or later. Android may expose the touchpad as a mouse instead of a native touchpad; in that case Moonlight can forward the click as a mouse button but not as a virtual-gamepad touchpad click, and the touchpad controls the pointer. Leave Gamepad touchpad as mouse disabled when native touchpad forwarding is available. See Sony's Android requirements and the Moonlight Android touchpad handling. Sony also documents on the same page that adaptive triggers are not compatible with Android-based mobile devices.
3 This is a known Windows-host gap, not a Moonlight Xbox limitation. See GitHub issue #106.
4 This is a known libvirtualhid profile gap. See GitHub issue #107.
5 Rumble availability on Android depends on how the Android device exposes the controller's vibration motors. Moonlight uses the per-device vibration APIs available on Android 12 or later when compatible motors are present, with a legacy fallback on some devices. In the tested PlayStation setup, Steam did not begin sending rumble until its controller-calibration page had been opened. See the Moonlight Android vibration handling.
6 Moonlight Android queries a controller's RGB light through the lights API available on Android 12 or later. It worked on the tested newer Android devices but was unavailable on NVIDIA Shield running Android 11. See the Moonlight Android RGB-light detection.
7 Host-controlled player indication is not currently forwarded end to end. DualSense player-indicator and MUTE-button LED support is proposed in libvirtualhid pull request #97 and Sunshine pull request #5537. Those changes do not add Switch Pro Player LED forwarding.
8 The DualSense MUTE button is an input separate from its LED. It was not observed working through these clients; the open LED pull requests referenced in note 7 do not add MUTE-button input support.
9 Switch Pro motion was observed not working through Moonlight Qt or Moonlight Android to Sunshine on Windows. No open issue was found for this specific failure.
10 On the tested Sunshine Windows host, the Switch Pro Capture button sent by Moonlight Android was exposed as the A button instead of Capture.
Analog trigger input reports intermediate values between 0 and 1. Switch Pro ZL/ZR input is digital and reports only 0 or 1. Trigger input is also separate from feedback: basic rumble, Xbox Impulse Triggers, and DualSense adaptive triggers are distinct features. One working does not imply that the others work. Likewise, a client may forward motion while omitting battery or LED data.
On Windows, joy.cpl is useful for checking ordinary buttons, sticks, and triggers. Browser testers, Steam, and individual games use different controller APIs and mappings, so do not use any one of them as the only compatibility test.
The game may support a different controller API or profile than Steam. Check the game's controller requirements, try Steam Input both enabled and disabled, and verify that the Sunshine virtual-gamepad selection matches a controller the game supports. Disconnect unused host-side controllers if the game always opens the first controller slot.
Steam may need a one-time gyro calibration before its controller tester or Steam Input fully initializes a virtual DualShock 4 or DualSense controller's gyro, light bar, and rumble. Open Steam's controller settings and complete the gyro calibration, then test the features again. This has only been observed with Steam's handling of virtual DualShock 4 and DualSense controllers and may be a Steam bug rather than a remaining controller-protocol failure. See ValveSoftware/steam-for-linux issue #13435 for a related DualSense rumble report.
If Steam repeatedly treats the virtual controller as a new device, disabling Sunshine's Randomize virtual controller MAC option may help it retain the controller's calibration and settings. Restart Sunshine and reconnect the stream after changing the option. A stable MAC can cause different physical controllers that reuse the same client controller slot to share Steam's per-controller settings.
Include enough information to identify which layer failed:
Report client capture or playback problems to the relevant Moonlight client. Report streaming-session mapping or forwarding problems to Sunshine. Report a libvirtualhid issue when the same host-side virtual profile can be reproduced without Moonlight and Sunshine, or when Sunshine logs show the expected data reaching the library but the virtual device reports it incorrectly.
The Moonlight setup guide links the official clients and their support resources.