Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1522,9 +1522,9 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}
(empty - bind to all interfaces)
@endcode</td>
<td colspan="2">
Empty, binds to all interfaces
</td>
</tr>
<tr>
<td>Example (IPv4)</td>
Expand Down Expand Up @@ -1626,10 +1626,10 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}
(empty - uses built-in defaults: https://localhost, https://127.0.0.1, https://[::1],
with configured UI port variants)
@endcode</td>
<td colspan="2">
Empty, uses built-in defaults: https://localhost, https://127.0.0.1, https://[::1],
with configured UI port variants
</td>
</tr>
<tr>
<td>Example</td>
Expand Down Expand Up @@ -2802,9 +2802,9 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}

@endcode</td>
<td colspan="2">
Empty, uses encoder default.
</td>
</tr>
<tr>
<td>Example</td>
Expand Down
24 changes: 17 additions & 7 deletions docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -474,19 +474,29 @@ and enter its device name in the [audio_sink](configuration.md#audio_sink) field
> Gamepads are not currently supported.

### Windows
Sunshine uses libvirtualhid for virtual gamepads on Windows. You must install the
[Virtual HID Driver](https://github.com/LizardByte/libvirtualhid/releases/latest) separately for full virtual gamepad
support. ViGEmBus is detected only as a limited fallback for Xbox 360 and DualShock 4 gamepads when libvirtualhid is
unavailable.
Sunshine uses libvirtualhid for virtual input on Windows. You must install the
[Virtual HID Driver](https://github.com/LizardByte/libvirtualhid/releases/latest) separately for a driver-backed Raw
Input mouse and full virtual gamepad support. ViGEmBus is detected only as a limited fallback for Xbox 360 and
DualShock 4 gamepads when libvirtualhid is unavailable.

Sunshine requires Virtual HID Driver version `2026.823.352.3` or newer. Earlier releases use incompatible Windows
control and broker protocols and must be upgraded together with Sunshine's embedded libvirtualhid library. Local
development driver builds using a `0.0.0.*` version remain supported.

Compared with the ViGEmBus fallback, Virtual HID Driver can create Xbox One, Xbox Series, DualSense, Nintendo Switch
Pro, and Generic gamepads in addition to Xbox 360 and DualShock 4. It can also expose controller-specific features such
as motion, touchpads, LEDs, and adaptive triggers when supported. Virtual HID Driver is actively developed and
supported by the LizardByte team.

The Virtual HID Driver also requires an active machine license. Sunshine shows the current license status and actions
on the Web UI Troubleshooting page and in the **Virtual HID Driver** system tray submenu. When Sunshine starts on an
unactivated machine, select its tray notification to open the activation and purchase options in the Web UI.
With a compatible driver and active license, relative mouse movement, buttons, and scrolling are exposed as a real HID
mouse so applications using Raw Input can receive them. Absolute mouse positioning continues to use Windows input
injection. When the driver-backed mouse cannot be created, libvirtualhid retains its legacy SendInput fallback.

The Virtual HID Driver requires an active machine license for driver-backed devices, including gamepads and the Raw
Input mouse. Sunshine shows the current license status and actions on the Web UI Troubleshooting page and in the
**Virtual HID Driver** system tray submenu. When Sunshine starts on an unactivated machine, select its tray notification
to open the activation and purchase options in the Web UI. Sunshine recreates the shared mouse after a successful
license action, so switching between the HID and SendInput paths does not require restarting Sunshine.

After installing or updating virtual input drivers, it is recommended to restart your computer.

Expand Down
31 changes: 23 additions & 8 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,22 +290,37 @@ launchctl load -w /Library/LaunchAgents/org.freedesktop.dbus-session.plist
## Windows

### No gamepad detected
Sunshine uses libvirtualhid for virtual gamepads on Windows. Install the
[Virtual HID Driver](https://github.com/LizardByte/libvirtualhid/releases/latest) separately for full virtual gamepad
support. ViGEmBus is detected only as a limited fallback for Xbox 360 and DualShock 4 gamepads when libvirtualhid is
unavailable. If you use the [ViGEmBus fallback](https://github.com/nefarius/ViGEmBus/releases/latest), you must use
version 1.17 or newer.
Sunshine uses libvirtualhid for virtual input on Windows. Install the
[Virtual HID Driver](https://github.com/LizardByte/libvirtualhid/releases/latest) separately for a driver-backed Raw
Input mouse and full virtual gamepad support. ViGEmBus is detected only as a limited fallback for Xbox 360 and
DualShock 4 gamepads when libvirtualhid is unavailable. If you use the
[ViGEmBus fallback](https://github.com/nefarius/ViGEmBus/releases/latest), you must use version 1.17 or newer.

Sunshine requires Virtual HID Driver version `2026.823.352.3` or newer. Earlier releases use incompatible Windows
control and broker protocols. The Troubleshooting page reports an older installed package as unsupported and links
to the current driver release. Local development driver builds using a `0.0.0.*` version remain supported.

Virtual HID Driver adds Xbox One, Xbox Series, DualSense, Nintendo Switch Pro, and Generic gamepads, plus advanced
controller features such as motion, touchpads, LEDs, and adaptive triggers when supported. Unlike the discontinued
ViGEmBus project, Virtual HID Driver is actively developed and supported by the LizardByte team.

An active Virtual HID Driver machine license is required before Sunshine can create libvirtualhid gamepads. Follow
the warning on the Web UI home page, the startup tray notification, or the **Virtual HID Driver** tray submenu to open
the license section on the Troubleshooting page, where you can activate a key or follow the purchase link.
An active Virtual HID Driver machine license is required before Sunshine can create driver-backed libvirtualhid
devices, including gamepads and the Raw Input mouse. Follow the warning on the Web UI home page, the startup tray
notification, or the **Virtual HID Driver** tray submenu to open the license section on the Troubleshooting page, where
you can activate a key or follow the purchase link.

After installation, it is recommended to restart your computer.

### Games do not detect mouse input
With a compatible Virtual HID Driver and active license, Sunshine sends relative mouse movement, buttons, and scrolling
through a real HID device so games using Raw Input can receive them. Absolute positioning still uses Windows input
injection. When the driver-backed mouse cannot be created, libvirtualhid falls back to SendInput; the Windows cursor may
still move even though a game that listens only for Raw Input receives nothing.

Check the Virtual HID Driver version and license sections on the Web UI Troubleshooting page even when controller input
is disabled. Sunshine recreates the shared mouse after a successful license activation, validation, or deactivation, so
you do not need to restart Sunshine merely to switch between the HID and SendInput paths.

### Permission denied
Since Sunshine runs as a service on Windows, it may not have the same level of access that your regular user account
has. You may get permission denied errors when attempting to launch a game or application from a non-system drive.
Expand Down
17 changes: 14 additions & 3 deletions src/confighttp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include "file_handler.h"
#include "globals.h"
#include "httpcommon.h"
#include "input.h"
#include "logging.h"
#include "network.h"
#include "nvhttp.h"
Expand Down Expand Up @@ -174,7 +175,7 @@ namespace confighttp {
*/
constexpr auto CSRF_TOKEN_LIFETIME = std::chrono::hours(1); // Tokens valid for 1 hour

constexpr auto LIBVIRTUALHID_MINIMUM_VERSION = ""sv; ///< Minimum supported libvirtualhid driver version; empty means any version.
constexpr auto LIBVIRTUALHID_MINIMUM_VERSION = "2026.823.352.3"sv; ///< Minimum supported libvirtualhid driver version. // NOSONAR(cpp:S1313): not an IP address
constexpr auto VIGEMBUS_MINIMUM_VERSION = "1.17.0.0"sv; ///< Minimum supported ViGEmBus fallback driver version. // NOSONAR(cpp:S1313): not an IP address

/**
Expand Down Expand Up @@ -241,6 +242,10 @@ namespace confighttp {
return false;
}

if (version_parts->size() >= 3U && (*version_parts)[0] == 0U && (*version_parts)[1] == 0U && (*version_parts)[2] == 0U) {
return true;
}

const auto part_count = std::max(version_parts->size(), minimum_parts->size());
for (std::size_t i = 0; i < part_count; ++i) {
const auto version_part = i < version_parts->size() ? (*version_parts)[i] : 0U;
Expand Down Expand Up @@ -1805,7 +1810,8 @@ namespace confighttp {
nlohmann::json get_virtualhid_driver_status() {
#ifdef _WIN32
const auto version_str = read_libvirtualhid_driver_version();
auto output_tree = build_driver_status(false, version_str, LIBVIRTUALHID_MINIMUM_VERSION);
const auto driver_detected = !version_str.empty();
auto output_tree = build_driver_status(driver_detected, version_str, LIBVIRTUALHID_MINIMUM_VERSION);
bool requires_installed_driver = true;
std::string backend_name;
std::string runtime_error_message;
Expand All @@ -1816,7 +1822,7 @@ namespace confighttp {
const auto &capabilities = runtime->capabilities();
backend_name = capabilities.backend_name;
requires_installed_driver = capabilities.requires_installed_driver;
output_tree = build_driver_status(capabilities.supports_gamepad, version_str, LIBVIRTUALHID_MINIMUM_VERSION);
output_tree = build_driver_status(driver_detected || capabilities.supports_gamepad, version_str, LIBVIRTUALHID_MINIMUM_VERSION);
}
} catch (const std::bad_alloc &exception) {
runtime_error_message = exception.what();
Expand Down Expand Up @@ -1949,6 +1955,11 @@ namespace confighttp {

#if defined(_WIN32) && defined(SUNSHINE_TRAY) && SUNSHINE_TRAY >= 1
system_tray::update_tray_virtualhid_license(result.license, false);
#endif
#ifdef _WIN32
if (result.status.ok()) {
input::refresh_virtual_mouse();
}
#endif
send_response(response, build_virtualhid_license_status(result));
} catch (const nlohmann::json::exception &) {
Expand Down
3 changes: 2 additions & 1 deletion src/confighttp.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ namespace confighttp {
* @brief Check whether a detected driver version satisfies a minimum version.
*
* Empty minimum versions accept any detected version. Non-empty minimum versions
* require a fully numeric dotted version string.
* require a fully numeric dotted version string. Development versions whose
* first three components are `0.0.0` are always accepted.
*
* @param version Detected driver version.
* @param minimum_version Minimum supported driver version, or empty for any version.
Expand Down
10 changes: 10 additions & 0 deletions src/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ extern "C" {
#include "input.h"
#include "logging.h"
#include "platform/common.h"
#include "platform/virtualhid_input.h"
#include "thread_pool.h"
#include "utility.h"

Expand Down Expand Up @@ -2090,6 +2091,15 @@ namespace input {
});
}

void refresh_virtual_mouse() {
dispatch_input_task([]() {
if (platf_input) {
reset_mouse_buttons();
platf::virtualhid::get_input_context(platf_input).refresh_mouse();
}
});
}

/**
* @brief Allocate and initialize platform input state for a stream.
*/
Expand Down
8 changes: 8 additions & 0 deletions src/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ namespace input {
*/
bool probe_gamepads();

/**
* @brief Recreate the shared libvirtualhid mouse after a license-state change.
*
* The work is serialized with streamed input so the mouse backend can switch
* safely between the Windows HID and SendInput paths.
*/
void refresh_virtual_mouse();

/**
* @brief Allocate and initialize platform input state for a stream.
*
Expand Down
27 changes: 17 additions & 10 deletions src/platform/virtualhid_input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -433,16 +433,23 @@ namespace platf::virtualhid {
log_failure("create libvirtualhid keyboard"sv, created.status);
}
}
if (capabilities.supports_mouse) {
lvh::CreateMouseOptions options;
options.profile = lvh::profiles::mouse();
options.stable_id = "sunshine-mouse";
auto created = runtime->create_mouse(options);
if (created) {
mouse = std::move(created.mouse);
} else {
log_failure("create libvirtualhid mouse"sv, created.status);
}
refresh_mouse();
}

void input_context_t::refresh_mouse() {
mouse.reset();
if (!runtime || !runtime->capabilities().supports_mouse) {
return;
}

lvh::CreateMouseOptions options;
options.profile = lvh::profiles::mouse();
options.stable_id = "sunshine-mouse";
auto created = runtime->create_mouse(options);
if (created) {
mouse = std::move(created.mouse);
} else {
log_failure("create libvirtualhid mouse"sv, created.status);
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/platform/virtualhid_input.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ namespace platf::virtualhid {
*/
explicit input_context_t(lvh::BackendKind backend);

/**
* @brief Recreate the shared mouse using the runtime's current driver and license state.
*/
void refresh_mouse();

std::unique_ptr<lvh::Runtime> runtime; ///< libvirtualhid runtime.
std::unique_ptr<lvh::Keyboard> keyboard; ///< Shared virtual keyboard.
std::unique_ptr<lvh::Mouse> mouse; ///< Shared virtual mouse.
Expand Down
11 changes: 6 additions & 5 deletions src/system_tray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,9 @@ namespace system_tray {

#ifdef _WIN32
constexpr auto LIBVIRTUALHID_RELEASES_URL = "https://github.com/LizardByte/libvirtualhid/releases/latest"sv; ///< Latest Virtual HID Driver release.
static std::array<struct tray_menu, 6> virtualhid_benefits_menu {{
static std::array<struct tray_menu, 7> virtualhid_benefits_menu {{
{.text = "Xbox One, Xbox Series, DualSense (DS5), Switch Pro, and Generic", .disabled = 1},
{.text = "Raw Input mouse for relative movement, buttons, and scrolling", .disabled = 1},
{.text = "Motion, touchpads, LEDs, and adaptive triggers where supported", .disabled = 1},
{.text = "Actively developed and supported by LizardByte", .disabled = 1},
{.text = "-"},
Expand Down Expand Up @@ -251,7 +252,7 @@ namespace system_tray {
menu[0] = {.text = "Status: Checking", .disabled = 1};
menu[1] = {.text = "-"};
menu[2] = {.text = "Open License Settings", .cb = tray_virtualhid_license_cb};
menu[3] = {.text = "Benefits over ViGEmBus", .submenu = virtualhid_benefits_menu.data()};
menu[3] = {.text = "Virtual HID Driver Benefits", .submenu = virtualhid_benefits_menu.data()};
menu[4] = {.text = "Download Virtual HID Driver", .cb = tray_virtualhid_download_cb};
return menu;
}
Expand Down Expand Up @@ -434,7 +435,7 @@ namespace system_tray {
set_virtualhid_license_menu_item(6, "Manage License", false, tray_virtualhid_license_cb);
} else {
set_virtualhid_license_menu_item(1, std::string {virtualhid_license_state_detail(license.state)}, true);
set_virtualhid_license_menu_item(2, "Full virtual gamepad support is locked", true);
set_virtualhid_license_menu_item(2, "Driver-backed gamepads and Raw Input mouse are locked", true);
set_virtualhid_license_menu_item(
3,
license.service_available ? "License service: Available" : "License service: Unavailable",
Expand All @@ -446,7 +447,7 @@ namespace system_tray {
}
virtualhid_license_menu[separator_index] = {.text = "-"};
const auto benefits_index = separator_index + 3U;
set_virtualhid_license_menu_item(benefits_index, "Benefits over ViGEmBus", false);
set_virtualhid_license_menu_item(benefits_index, "Virtual HID Driver Benefits", false);
virtualhid_license_menu[benefits_index].submenu = virtualhid_benefits_menu.data();
set_virtualhid_license_menu_item(benefits_index + 1U, "Download Virtual HID Driver", false, tray_virtualhid_download_cb);
}
Expand All @@ -469,7 +470,7 @@ namespace system_tray {
if (notify_if_unlicensed && !license.licensed()) {
tray.notification_title = "Activate Virtual HID Driver";
tray.notification_text =
"Adds Xbox One/Series, DualSense (DS5), Switch Pro, and Generic gamepads beyond ViGEmBus. Actively maintained by LizardByte. Click to activate or buy a license; details remain in the tray menu.";
"Adds a Raw Input mouse plus Xbox One/Series, DualSense (DS5), Switch Pro, and Generic gamepads. Actively maintained by LizardByte. Click to activate or buy a license; details remain in the tray menu.";
tray.notification_icon = tray.allIconPaths[4];
tray.notification_cb = []() {
launch_ui("/troubleshooting#virtualhid-license");
Expand Down
8 changes: 4 additions & 4 deletions src_assets/common/assets/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ <h1 class="my-4">{{ $t('index.welcome') }}</h1>
</div>

<!-- libvirtualhid Warning -->
<div class="alert alert-warning my-4" v-if="platform === 'windows' && controllerEnabled && virtualhid && (!virtualhid.installed || !virtualhid.version_compatible)">
<div class="alert alert-warning my-4" v-if="platform === 'windows' && virtualhid && (!virtualhid.installed || !virtualhid.version_compatible)">
<div>
<div class="d-flex align-items-center mb-3">
<alert-triangle :size="32" class="icon-lg me-3"></alert-triangle>
Expand Down Expand Up @@ -62,7 +62,7 @@ <h1 class="my-4">{{ $t('index.welcome') }}</h1>
</div>

<!-- Virtual HID Driver license warning -->
<div class="alert alert-warning my-4" v-if="platform === 'windows' && controllerEnabled && virtualhidLicense && !virtualhidLicense.licensed">
<div class="alert alert-warning my-4" v-if="platform === 'windows' && virtualhidLicense && !virtualhidLicense.licensed">
<div>
<div class="d-flex align-items-center mb-3">
<alert-triangle :size="32" class="icon-lg me-3"></alert-triangle>
Expand Down Expand Up @@ -222,8 +222,8 @@ <h5 class="mb-0">{{ githubVersion.release.name }}</h5>
this.preReleaseVersion = new SunshineVersion((await fetch("https://github.com/ghapi/repos/LizardByte/Sunshine/releases").then((r) => r.json())).find(release => release.prerelease), null);
console.log("Pre-Release Version: ", this.preReleaseVersion.version)

// Fetch virtual input driver status only on Windows when controller is enabled
if (this.platform === 'windows' && this.controllerEnabled) {
// The Virtual HID Driver also backs relative mouse input when controllers are disabled.
if (this.platform === 'windows') {
try {
const virtualInputStatus = await fetch("./api/virtual-input/status").then((r) => r.json());
this.virtualhid = virtualInputStatus.virtualhid;
Expand Down
Loading
Loading