libvirtualhid 105
Cross-platform C++ library for virtual HID devices.
End-User Gamepad Guide

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.

Understand the Input Path

Standard input and controller-specific features must pass through several independent layers:

physical controller -> Moonlight client -> Sunshine -> libvirtualhid -> game
physical controller <- Moonlight client <- Sunshine <- libvirtualhid <- game

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.

Prepare the Physical Controller

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.

Configure Sunshine

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:

Expect Client-Specific Differences

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.

Troubleshoot by Symptom

The host does not see a controller

  1. Confirm that the physical controller works on the client before starting Moonlight.
  2. Confirm that controller input is enabled in Sunshine.
  3. On Windows, check the Virtual HID Driver version and license status on Sunshine's Troubleshooting page.
  4. End and reconnect the stream, then check whether the host operating system sees a newly created controller.
  5. Review the Sunshine log for controller creation, driver, permission, or license errors.

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.

Buttons work but an extended feature does not

  1. Identify the direction of the missing feature. Motion and touch travel from the client to the host; rumble and LEDs travel from the game back to the client.
  2. Check whether the physical-controller vendor documents the feature for the client operating system and USB or Bluetooth connection being used.
  3. Check the client-specific observations above and the issue tracker for that Moonlight client.
  4. Confirm that Sunshine selected a virtual profile that represents the feature. A game seeing an Xbox controller will not gain PlayStation motion or adaptive-trigger support.
  5. Test with a game or tool known to use that exact feature. Standard rumble is not a valid test for Xbox Impulse Triggers or DualSense adaptive triggers.
  6. If Steam is involved, test once with Steam Input enabled and once with it disabled. Record which path works instead of treating Steam calibration or remapping as a driver fix.

The controller works in Steam but not in a game

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.

PlayStation gyro, light bar, or rumble does not work in Steam

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.

Report a Compatibility Problem

Include enough information to identify which layer failed:

  • Moonlight client name and exact version.
  • Client device, operating-system version, and whether the controller uses USB, Bluetooth, a wireless adapter, or a built-in connection.
  • Physical controller model and firmware version.
  • Sunshine version, host operating system, and selected virtual-gamepad profile.
  • Virtual HID Driver version on Windows.
  • Game or test tool, whether Steam Input is enabled, and whether standard input works.
  • The exact missing feature and its direction, such as Switch Pro motion to the host or Xbox Impulse Triggers back to the client.
  • Relevant Sunshine logs and a comparison with another Moonlight client, when available.

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.