▸
docs/configuration.md
+10
−10
@@ -1522,9 +1522,9 @@
</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>
@@ -1626,10 +1626,10 @@
</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>
@@ -2802,9 +2802,9 @@
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}
@endcode</td>
<td colspan="2">
Empty, uses encoder default.
</td>
</tr>
<tr>
<td>Example</td>
▸
docs/getting_started.md
+17
−7
@@ -474,18 +474,28 @@
> 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.
▸
docs/troubleshooting.md
+23
−8
@@ -290,21 +290,36 @@
## 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 driver-backed libvirtualhid
devices, including gamepads and the Raw Input mouse. Follow the warning on the Web UI home page, the startup tray
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.
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
▸
src/confighttp.cpp
+14
−3
@@ -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"
@@ -174,7 +175,7 @@
*/
constexpr auto CSRF_TOKEN_LIFETIME = std::chrono::hours(1); // Tokens valid for 1 hour
constexpr auto LIBVIRTUALHID_MINIMUM_VERSION = "2026.823.352.3"sv; ///< Minimum supported libvirtualhid driver version. // NOSONAR(cpp:S1313): not an IP address
constexpr auto LIBVIRTUALHID_MINIMUM_VERSION = ""sv; ///< Minimum supported libvirtualhid driver version; empty means any version.
constexpr auto VIGEMBUS_MINIMUM_VERSION = "1.17.0.0"sv; ///< Minimum supported ViGEmBus fallback driver version. // NOSONAR(cpp:S1313): not an IP address
/**
@@ -241,6 +242,10 @@
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;
@@ -1805,7 +1810,8 @@
nlohmann::json get_virtualhid_driver_status() {
#ifdef _WIN32
const auto version_str = read_libvirtualhid_driver_version();
const auto driver_detected = !version_str.empty();
auto output_tree = build_driver_status(driver_detected, version_str, LIBVIRTUALHID_MINIMUM_VERSION);
auto output_tree = build_driver_status(false, version_str, LIBVIRTUALHID_MINIMUM_VERSION);
bool requires_installed_driver = true;
std::string backend_name;
std::string runtime_error_message;
@@ -1816,7 +1822,7 @@
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();
@@ -1949,6 +1955,11 @@
#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 &) {
▸
src/confighttp.h
+2
−1
@@ -82,7 +82,8 @@
* @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.
▸
src/input.cpp
+10
−0
@@ -31,5 +31,6 @@
#include "input.h"
#include "logging.h"
#include "platform/common.h"
#include "platform/virtualhid_input.h"
#include "thread_pool.h"
#include "utility.h"
@@ -2087,5 +2088,14 @@
const auto &gamepads = platf::supported_gamepads(std::addressof(platf_input));
return std::ranges::none_of(gamepads, [](const auto &gamepad) {
return gamepad.is_enabled && gamepad.name != "auto";
});
}
void refresh_virtual_mouse() {
dispatch_input_task([]() {
if (platf_input) {
reset_mouse_buttons();
platf::virtualhid::get_input_context(platf_input).refresh_mouse();
}
});
}
▸
src/input.h
+8
−0
@@ -63,6 +63,14 @@
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.
*
* @param mail Mailbox used to exchange messages with worker threads.
▸
src/platform/virtualhid_input.cpp
+17
−10
@@ -433,15 +433,22 @@
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);
}
}
▸
src/platform/virtualhid_input.h
+5
−0
@@ -35,6 +35,11 @@
*/
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.
▸
src/system_tray.cpp
+6
−5
@@ -149,8 +149,9 @@
#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 = "-"},
@@ -251,7 +252,7 @@
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;
}
@@ -434,7 +435,7 @@
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, "Driver-backed gamepads and Raw Input mouse are locked", true);
set_virtualhid_license_menu_item(2, "Full virtual gamepad support is locked", true);
set_virtualhid_license_menu_item(
3,
license.service_available ? "License service: Available" : "License service: Unavailable",
@@ -446,7 +447,7 @@
}
virtualhid_license_menu[separator_index] = {.text = "-"};
const auto benefits_index = separator_index + 3U;
set_virtualhid_license_menu_item(benefits_index, "Virtual HID Driver Benefits", false);
set_virtualhid_license_menu_item(benefits_index, "Benefits over ViGEmBus", 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);
}
@@ -469,7 +470,7 @@
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");
▸
src_assets/common/assets/web/index.html
+4
−4
@@ -29,7 +29,7 @@
</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>
@@ -62,7 +62,7 @@
</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>
@@ -222,8 +222,8 @@
this.preReleaseVersion = new SunshineVersion((await fetch("https://api.github.com/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;
▸
src_assets/common/assets/web/public/assets/locale/en.json
+10
−8
@@ -453,11 +453,11 @@
"startup_errors": "<b>Attention!</b> Sunshine detected these errors during startup. We <b>STRONGLY RECOMMEND</b> fixing them before streaming.",
"version_dirty": "Thank you for helping to make Sunshine a better software!",
"version_latest": "You are running the latest version of Sunshine",
"virtualhid_license_required_desc": "Virtual HID Driver adds Xbox One, Xbox Series, DualSense, Nintendo Switch Pro, and Generic gamepads beyond the limited ViGEmBus fallback. It is actively maintained by the LizardByte team. Activate or manage the license to unlock full support.",
"virtualhid_license_required_desc": "Virtual HID Driver adds a Raw Input mouse plus Xbox One, Xbox Series, DualSense, Nintendo Switch Pro, and Generic gamepads. It is actively maintained by the LizardByte team. Activate or manage the license to unlock driver-backed input.",
"virtualhid_license_required_title": "Virtual HID Driver License Missing",
"virtualhid_missing_vigembus_installed_desc": "ViGEmBus is installed, but without the libvirtualhid driver Sunshine can only offer limited fallback gamepad support.",
"virtualhid_missing_vigembus_installed_title": "Gamepad Support Is Limited",
"virtualhid_not_installed_desc": "Sunshine requires the libvirtualhid Windows driver for a Raw Input mouse and full virtual gamepad support. Install the libvirtualhid driver package, then restart Sunshine.",
"virtualhid_not_installed_desc": "Sunshine requires the libvirtualhid Windows driver for full virtual gamepad support. Install the libvirtualhid driver package, then restart Sunshine.",
"virtualhid_not_installed_title": "libvirtualhid Driver Not Installed",
"virtualhid_outdated_desc": "The installed libvirtualhid driver version (v{version}) is not supported. Supported versions: {supported_versions}.",
"virtualhid_outdated_title": "libvirtualhid Driver Outdated",
@@ -575,32 +575,34 @@
"unpair_single_unknown": "Unknown Client",
"unpair_title": "Unpair Devices",
"vigembus_driver": "ViGEmBus Fallback Driver",
"virtual_gamepad": "Virtual Input",
"virtual_gamepad": "Virtual Gamepad",
"virtual_gamepad_desc": "Sunshine uses libvirtualhid for full virtual gamepad support on Windows. ViGEmBus is detected only as a limited fallback for Xbox 360 and DualShock 4 gamepads.",
"virtual_gamepad_desc": "Sunshine uses libvirtualhid for a driver-backed mouse and full virtual gamepad support on Windows. ViGEmBus is detected only as a limited fallback for Xbox 360 and DualShock 4 gamepads.",
"virtual_gamepad_drivers": "Windows Driver Status",
"virtual_gamepad_drivers_desc": "Installed versions are compared with each project's latest stable GitHub release. Prereleases are ignored.",
"virtualhid_benefit_features": "Supports controller-specific features such as motion, touchpads, LEDs, and adaptive triggers when available.",
"virtualhid_benefit_features_title": "Modern Controller Features",
"virtualhid_benefit_gamepads": "Creates Xbox One, Xbox Series, DualSense, Nintendo Switch Pro, and Generic gamepads in addition to Xbox 360 and DualShock 4.",
"virtualhid_benefit_gamepads_title": "More Gamepad Types",
"virtualhid_benefit_mouse": "Exposes relative movement, buttons, and scrolling as a real HID mouse so games using Raw Input can receive them.",
"virtualhid_benefit_mouse_title": "Raw Input Mouse",
"virtualhid_benefit_maintained": "Actively developed and supported by the LizardByte team instead of relying only on the discontinued ViGEmBus project.",
"virtualhid_benefit_maintained_title": "Actively Supported",
"virtualhid_benefits_title": "Virtual HID Driver Benefits",
"virtualhid_benefits_title": "Benefits over ViGEmBus",
"virtualhid_download": "Download Virtual HID Driver",
"virtualhid_driver": "libvirtualhid Driver",
"virtualhid_license": "Virtual HID Driver License",
"virtualhid_license_activate": "Activate License",
"virtualhid_license_activations": "Machine Activations",
"virtualhid_license_active_devices": "Active Virtual Gamepads",
"virtualhid_license_active_devices": "Active Virtual HID Devices",
"virtualhid_license_buy": "Buy License",
"virtualhid_license_customer": "Customer",
"virtualhid_license_deactivate": "Deactivate This Machine",
"virtualhid_license_desc": "An active license is required before Sunshine can create libvirtualhid gamepads. Activation is handled by the local libvirtualhid service; Sunshine does not save your license key.",
"virtualhid_license_desc": "An active license is required before Sunshine can create driver-backed libvirtualhid devices, including gamepads and the Raw Input mouse. Activation is handled by the local libvirtualhid service; Sunshine does not save your license key.",
"virtualhid_license_key": "License Key",
"virtualhid_license_key_desc": "The key is sent only to the local libvirtualhid service and is cleared from this page after successful activation.",
"virtualhid_license_key_placeholder": "Enter your product key",
"virtualhid_license_machine_activated": "This machine is activated",
"virtualhid_license_machine_activated_desc": "Sunshine can create Virtual HID Driver gamepads while this license remains valid.",
"virtualhid_license_machine_activated_desc": "Sunshine can create Virtual HID Driver gamepads and a Raw Input mouse while this license remains valid.",
"virtualhid_license_manage": "Manage Account and Activations",
"virtualhid_license_not_reported": "Not reported",
"virtualhid_license_plan": "Plan",
▸
src_assets/common/assets/web/troubleshooting.html
+17
−8
@@ -9,7 +9,7 @@
<Navbar></Navbar>
<div id="content" class="container">
<h1 class="my-4">{{ $t('troubleshooting.troubleshooting') }}</h1>
<!-- Virtual gamepad and Virtual HID Driver license -->
<!-- Virtual input driver and license -->
<div class="card my-4 virtual-gamepad-card" v-if="platform === 'windows'">
<div class="card-body">
<header class="virtual-gamepad-hero">
@@ -34,6 +34,15 @@
</article>
<article class="virtual-gamepad-feature">
<div class="virtual-gamepad-feature-icon" aria-hidden="true">
<mouse-pointer-2 :size="21"></mouse-pointer-2>
</div>
<div>
<h3>{{ $t('troubleshooting.virtualhid_benefit_mouse_title') }}</h3>
<p>{{ $t('troubleshooting.virtualhid_benefit_mouse') }}</p>
</div>
</article>
<article class="virtual-gamepad-feature">
<div class="virtual-gamepad-feature-icon" aria-hidden="true">
<sparkles :size="21"></sparkles>
</div>
<div>
@@ -52,7 +61,7 @@
</article>
</div>
<section class="virtual-gamepad-section">
<section class="virtual-gamepad-section" v-if="controllerEnabled">
<div class="virtual-gamepad-section-heading">
<div>
<h3 class="h4 mb-1">{{ $t('troubleshooting.virtual_gamepad_drivers') }}</h3>
@@ -436,6 +445,7 @@
ExternalLink,
Gamepad2,
KeyRound,
MousePointer2,
RefreshCw,
RotateCcw,
Search,
@@ -461,6 +471,7 @@
ExternalLink,
Gamepad2,
KeyRound,
MousePointer2,
RefreshCw,
RotateCcw,
Search,
@@ -657,7 +668,7 @@
},
showVigembus() {
return this.virtualInputStatusLoaded && (!this.virtualhid.installed || this.vigembus.installed);
return this.controllerEnabled && this.virtualInputStatusLoaded && (!this.virtualhid.installed || this.vigembus.installed);
}
},
created() {
@@ -667,11 +678,9 @@
.then((r) => {
this.platform = r.platform;
this.controllerEnabled = r.controller !== "disabled";
// Fetch virtual input driver status only on Windows when gamepad is enabled
if (this.platform === 'windows' && this.controllerEnabled) {
this.refreshDriverInformation();
}
// The Virtual HID Driver also backs relative mouse input when gamepads are disabled.
if (this.platform === 'windows') {
this.refreshDriverInformation();
this.refreshLicenseStatus();
}
});
@@ -821,7 +830,7 @@
this.refreshDriverReleases();
},
/**
* @brief Refresh the latest stable release metadata for the Windows virtual input drivers.
* @brief Refresh the latest stable release metadata for both Windows gamepad drivers.
*/
refreshDriverReleases() {
this.updateLatestRelease(
▸
tests/unit/test_confighttp.cpp
+27
−0
@@ -1338,6 +1338,14 @@
ASSERT_FALSE(confighttp::is_driver_version_supported("1.17.", "1.17.0.0"));
}
// Test: numeric development versions always bypass the production minimum
TEST(ConfigHttpDriverStatusTest, IsDriverVersionSupported_DevelopmentVersionBypassesMinimum) {
ASSERT_TRUE(confighttp::is_driver_version_supported("0.0.0", "2026.823.352.3")); // NOSONAR(cpp:S1313): not IP addresses
ASSERT_TRUE(confighttp::is_driver_version_supported("0.0.0.42", "2026.823.352.3")); // NOSONAR(cpp:S1313): not IP addresses
ASSERT_FALSE(confighttp::is_driver_version_supported("0.0.1.0", "2026.823.352.3")); // NOSONAR(cpp:S1313): not IP addresses
ASSERT_FALSE(confighttp::is_driver_version_supported("0.0", "2026.823.352.3")); // NOSONAR(cpp:S1313): not IP addresses
}
// Test: driver status JSON includes compatibility and supported version metadata
TEST(ConfigHttpDriverStatusTest, BuildDriverStatus_IncludesExpectedFields) {
const auto status = confighttp::build_driver_status(true, "1.17.0.0", "1.17.0.0"); // NOSONAR(cpp:S1313): not an IP address
@@ -1358,6 +1366,23 @@
ASSERT_FALSE(status["version_compatible"].get<bool>());
}
// Test: detected drivers remain installed when their version is too old
TEST(ConfigHttpDriverStatusTest, BuildDriverStatus_OlderDetectedDriverIsInstalledButIncompatible) {
const auto status = confighttp::build_driver_status(true, "2026.820.1844.57", "2026.823.352.3"); // NOSONAR(cpp:S1313): not IP addresses
ASSERT_TRUE(status["installed"].get<bool>());
ASSERT_FALSE(status["version_compatible"].get<bool>());
}
// Test: development status remains compatible while retaining the production floor metadata
TEST(ConfigHttpDriverStatusTest, BuildDriverStatus_DevelopmentVersionIsCompatible) {
const auto status = confighttp::build_driver_status(true, "0.0.0.42", "2026.823.352.3"); // NOSONAR(cpp:S1313): not IP addresses
ASSERT_EQ(status["minimum_version"].get<std::string>(), "2026.823.352.3"); // NOSONAR(cpp:S1313): not an IP address
ASSERT_EQ(status["supported_versions"].get<std::string>(), ">= 2026.823.352.3"); // NOSONAR(cpp:S1313): not an IP address
ASSERT_TRUE(status["version_compatible"].get<bool>());
}
TEST(ConfigHttpDriverStatusTest, BuildsLiveVirtualInputDriverStatus) {
const auto virtualhid = confighttp::get_virtualhid_driver_status();
EXPECT_TRUE(virtualhid.contains("installed"));
@@ -1365,6 +1390,8 @@
EXPECT_TRUE(virtualhid.contains("version_compatible"));
EXPECT_TRUE(virtualhid.contains("backend_name"));
EXPECT_TRUE(virtualhid.contains("requires_installed_driver"));
EXPECT_EQ(virtualhid["minimum_version"].get<std::string>(), "2026.823.352.3"); // NOSONAR(cpp:S1313): not an IP address
EXPECT_EQ(virtualhid["supported_versions"].get<std::string>(), ">= 2026.823.352.3"); // NOSONAR(cpp:S1313): not an IP address
const auto vigembus = confighttp::get_vigembus_driver_status();
EXPECT_TRUE(vigembus.contains("installed"));
▸
tests/unit/test_input.cpp
+24
−0
@@ -31,6 +31,7 @@
ASSERT_TRUE(platform_input);
auto &context = platf::virtualhid::get_input_context(platform_input);
context = platf::virtualhid::input_context_t {lvh::BackendKind::fake};
context_ = &context;
runtime_ = context.runtime.get();
ASSERT_NE(runtime_, nullptr);
input::testing::set_platform_input(std::move(platform_input));
@@ -42,6 +43,7 @@
void TearDown() override {
input::terminate_gamepads();
input::testing::set_platform_input({});
context_ = nullptr;
runtime_ = nullptr;
config::input = std::move(original_input_);
}
@@ -55,7 +57,17 @@
return *runtime_;
}
/**
* @brief Access the shared libvirtualhid input context installed for the test.
*
* @return Fake input context.
*/
platf::virtualhid::input_context_t &context() const {
return *context_;
}
private:
platf::virtualhid::input_context_t *context_ = nullptr; ///< Fake input context installed in the global backend.
lvh::Runtime *runtime_ = nullptr; ///< Fake runtime installed in the global input backend.
config::input_t original_input_; ///< Input configuration restored after each test.
};
@@ -91,4 +103,16 @@
auto replacement = input::alloc(std::make_shared<safe::mail_raw_t>(), session_id);
EXPECT_NE(replacement, resumed);
}
TEST_F(InputGamepadSessionTest, RefreshesSharedMouseAfterLicenseStateChanges) {
ASSERT_NE(context().mouse, nullptr);
const auto original_mouse_id = context().mouse->device_id();
const auto active_devices = runtime().active_device_count();
input::refresh_virtual_mouse();
ASSERT_NE(context().mouse, nullptr);
EXPECT_NE(context().mouse->device_id(), original_mouse_id);
EXPECT_EQ(runtime().active_device_count(), active_devices);
}
▸
tests/unit/test_system_tray.cpp
+12
−10
@@ -84,14 +84,16 @@
ASSERT_NE(benefits_menu, nullptr);
EXPECT_STREQ(benefits_menu[0].text, "Xbox One, Xbox Series, DualSense (DS5), Switch Pro, and Generic");
EXPECT_EQ(benefits_menu[0].disabled, 1);
EXPECT_STREQ(benefits_menu[1].text, "Motion, touchpads, LEDs, and adaptive triggers where supported");
EXPECT_STREQ(benefits_menu[1].text, "Raw Input mouse for relative movement, buttons, and scrolling");
EXPECT_EQ(benefits_menu[1].disabled, 1);
EXPECT_STREQ(benefits_menu[2].text, "Actively developed and supported by LizardByte");
EXPECT_STREQ(benefits_menu[2].text, "Motion, touchpads, LEDs, and adaptive triggers where supported");
EXPECT_EQ(benefits_menu[2].disabled, 1);
EXPECT_STREQ(benefits_menu[3].text, "Actively developed and supported by LizardByte");
EXPECT_EQ(benefits_menu[3].disabled, 1);
EXPECT_STREQ(benefits_menu[3].text, "-");
EXPECT_STREQ(benefits_menu[4].text, "Open License Settings");
EXPECT_NE(benefits_menu[4].cb, nullptr);
EXPECT_EQ(benefits_menu[5].text, nullptr);
EXPECT_STREQ(benefits_menu[4].text, "-");
EXPECT_STREQ(benefits_menu[5].text, "Open License Settings");
EXPECT_NE(benefits_menu[5].cb, nullptr);
EXPECT_EQ(benefits_menu[6].text, nullptr);
}
/**
@@ -102,7 +104,7 @@
*/
void verify_virtualhid_actions_menu(const struct tray_menu *license_menu, std::size_t benefits_index) {
ASSERT_NE(license_menu, nullptr);
EXPECT_STREQ(license_menu[benefits_index].text, "Benefits over ViGEmBus");
EXPECT_STREQ(license_menu[benefits_index].text, "Virtual HID Driver Benefits");
EXPECT_EQ(license_menu[benefits_index].cb, nullptr);
verify_virtualhid_benefits_menu(license_menu[benefits_index].submenu);
EXPECT_STREQ(license_menu[benefits_index + 1U].text, "Download Virtual HID Driver");
@@ -131,7 +133,7 @@
EXPECT_STREQ(tray_data.menu[2].submenu[1].text, "-");
EXPECT_STREQ(tray_data.menu[2].submenu[2].text, "Open License Settings");
EXPECT_NE(tray_data.menu[2].submenu[2].cb, nullptr);
EXPECT_STREQ(tray_data.menu[2].submenu[3].text, "Virtual HID Driver Benefits");
EXPECT_STREQ(tray_data.menu[2].submenu[3].text, "Benefits over ViGEmBus");
EXPECT_EQ(tray_data.menu[2].submenu[3].cb, nullptr);
verify_virtualhid_benefits_menu(tray_data.menu[2].submenu[3].submenu);
EXPECT_STREQ(tray_data.menu[2].submenu[4].text, "Download Virtual HID Driver");
@@ -382,7 +384,7 @@
ASSERT_NE(license_menu, nullptr);
EXPECT_STREQ(license_menu[0].text, std::format("Status: {}", state_label).c_str());
EXPECT_STREQ(license_menu[1].text, state_detail);
EXPECT_STREQ(license_menu[2].text, "Driver-backed gamepads and Raw Input mouse are locked");
EXPECT_STREQ(license_menu[2].text, "Full virtual gamepad support is locked");
EXPECT_STREQ(license_menu[3].text, service_available ? "License service: Available" : "License service: Unavailable");
EXPECT_STREQ(license_menu[4].text, "Activate this machine to use Virtual HID Driver");
EXPECT_STREQ(license_menu[5].text, "-");
@@ -394,7 +396,7 @@
EXPECT_STREQ(tray_data.notification_title, "Activate Virtual HID Driver");
EXPECT_STREQ(
tray_data.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."
);
EXPECT_STREQ(tray_data.notification_icon, tray_data.allIconPaths[4]);
EXPECT_NE(tray_data.notification_cb, nullptr);
▸
third-party/libvirtualhid
+0
−0