ref:71189aecc12f35b3ab81ba9306bf69fc1447a31f

chore(deps): bump libvirtualhid (#5585)

SHA: 71189aecc12f35b3ab81ba9306bf69fc1447a31f
Author: Dave Lane <42013603+ReenigneArcher@users.noreply.github.com>
Date: 2026-09-06 01:44
Parents: 2975396
15 files changed +224 -21
Type
CMakeLists.txt +4 −0
@@ -9,6 +9,10 @@
DESCRIPTION "Self-hosted game stream host for Moonlight"
HOMEPAGE_URL "https://app.lizardbyte.dev/Sunshine")
# Minimum separately installed Virtual HID Driver version supported by this build.
set(LIBVIRTUALHID_MINIMUM_VERSION "2026.905.2300.20")
list(APPEND SUNSHINE_DEFINITIONS LIBVIRTUALHID_MINIMUM_VERSION="${LIBVIRTUALHID_MINIMUM_VERSION}")
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(NOT DEFINED CMAKE_CUDA_STANDARD)
docs/getting_started.md +1 −3
@@ -539,9 +539,7 @@
Input keyboard and mouse plus 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.829.2338.54` 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.
Sunshine requires Virtual HID Driver version `2026.905.2300.20` or newer.
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
docs/troubleshooting.md +1 −3
@@ -308,9 +308,7 @@
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.829.2338.54` 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.
Sunshine requires Virtual HID Driver version `2026.905.2300.20` or newer.
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
src/confighttp.cpp +11 −9
@@ -175,7 +175,7 @@
*/
constexpr auto CSRF_TOKEN_LIFETIME = std::chrono::hours(1); // Tokens valid for 1 hour
constexpr auto LIBVIRTUALHID_MINIMUM_VERSION = "2026.829.2338.54"sv; ///< Minimum supported libvirtualhid driver version. // NOSONAR(cpp:S1313): not an IP address
constexpr std::string_view libvirtualhid_minimum_version = LIBVIRTUALHID_MINIMUM_VERSION; ///< Minimum supported libvirtualhid driver version.
constexpr auto VIGEMBUS_MINIMUM_VERSION = "1.17.0.0"sv; ///< Minimum supported ViGEmBus fallback driver version. // NOSONAR(cpp:S1313): not an IP address
/**
@@ -231,7 +231,12 @@
return parts;
}
bool is_driver_version_development(std::string_view version) {
const auto version_parts = parse_driver_version(version);
return version_parts && version_parts->size() >= 3U && (*version_parts)[0] == 0U && (*version_parts)[1] == 0U;
}
bool is_driver_version_supported(std::string_view version, std::string_view minimum_version) {
if (minimum_version.empty() || is_driver_version_development(version)) {
if (minimum_version.empty()) {
return true;
}
@@ -242,10 +247,6 @@
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;
@@ -266,6 +267,7 @@
output_tree["version"] = version;
output_tree["minimum_version"] = minimum_version_text;
output_tree["supported_versions"] = minimum_version.empty() ? "Any" : std::format(">= {}", minimum_version_text);
output_tree["development_version"] = installed && is_driver_version_development(version);
output_tree["version_compatible"] = installed && is_driver_version_supported(version, minimum_version);
return output_tree;
@@ -1886,7 +1888,7 @@
#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(driver_detected, version_str, libvirtualhid_minimum_version);
bool requires_installed_driver = true;
std::string backend_name;
std::string runtime_error_message;
@@ -1897,7 +1899,7 @@
const auto &capabilities = runtime->capabilities();
backend_name = capabilities.backend_name;
requires_installed_driver = capabilities.requires_installed_driver;
output_tree = build_driver_status(driver_detected || 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();
@@ -1909,7 +1911,7 @@
output_tree["error"] = runtime_error_message;
}
#else
auto output_tree = build_driver_status(false, "", LIBVIRTUALHID_MINIMUM_VERSION);
auto output_tree = build_driver_status(false, "", libvirtualhid_minimum_version);
output_tree["error"] = "libvirtualhid driver status is only available on Windows";
output_tree["backend_name"] = "";
output_tree["requires_installed_driver"] = false;
src/confighttp.h +13 −2
@@ -103,11 +103,22 @@
void savePin(const resp_https_t &response, const req_https_t &request);
/**
* @brief Check whether a detected driver version identifies a development build.
*
* Numeric versions beginning with `0.0` and containing at least three
* components are development builds.
*
* @param version Detected driver version.
* @return True when the version identifies a development build.
*/
bool is_driver_version_development(std::string_view version);
/**
* @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. Development versions beginning
* require a fully numeric dotted version string. Development versions whose
* first three components are `0.0.0` are always accepted.
* with `0.0` are always accepted.
*
* @param version Detected driver version.
* @param minimum_version Minimum supported driver version, or empty for any version.
src/main.cpp +1 −0
@@ -453,6 +453,7 @@
BOOST_LOG(info) << "Starting system tray"sv;
#ifdef _WIN32
system_tray::prepare_tray_virtualhid_license();
system_tray::prepare_tray_virtualhid_driver();
// TODO: Windows has a weird bug where when running as a service and on the first Windows boot,
// the tray icon would not appear even though Sunshine is running correctly otherwise.
// Restarting the service would allow the icon to appear normally.
src/system_tray.cpp +54 −0
@@ -144,5 +144,15 @@
static std::array<std::string, 11> menu_text;
return menu_text;
}
/**
* @brief Access storage for the Virtual HID Driver compatibility notification.
*
* @return Persistent string storage backing the tray notification pointer.
*/
std::string &virtualhid_driver_notification_text_storage() {
static std::string notification_text;
return notification_text;
}
#endif
} // namespace
@@ -318,6 +328,7 @@
tray.notification_cb = nullptr;
#ifdef _WIN32
virtualhid_license_menu_text_storage() = {};
virtualhid_driver_notification_text_storage().clear();
virtualhid_license_menu = initial_virtualhid_license_menu();
#endif
}
@@ -485,5 +496,48 @@
void prepare_tray_virtualhid_license() {
const auto result = lvh::get_license_status();
update_tray_virtualhid_license(result.license, !result.license.licensed());
}
void update_tray_virtualhid_driver(
const bool installed,
const std::string_view version,
const bool version_compatible,
const std::string_view supported_versions
) {
if (!installed || version_compatible) {
return;
}
const std::scoped_lock lock(tray_state_mutex());
clear_tray_notification();
const auto displayed_version = version.empty() ? "unknown" : std::format("v{}", version);
auto &notification_text = virtualhid_driver_notification_text_storage();
notification_text = std::format(
"Installed Virtual HID Driver {} is not supported by this version of Sunshine. Supported versions: {}. Restart Sunshine after updating. Click for instructions.",
displayed_version,
supported_versions
);
tray.notification_title = "Update Virtual HID Driver";
tray.notification_text = notification_text.c_str();
tray.notification_icon = tray.allIconPaths[4];
tray.notification_cb = []() {
launch_ui("/troubleshooting#virtualhid");
};
BOOST_LOG(warning) << notification_text;
if (tray_initialized_state().load()) {
tray_update(&tray);
}
}
void prepare_tray_virtualhid_driver() {
const auto status = confighttp::get_virtualhid_driver_status();
update_tray_virtualhid_driver(
status.value("installed", false),
status.value("version", std::string {}),
status.value("version_compatible", false),
status.value("supported_versions", std::string {})
);
}
#endif
src/system_tray.h +23 −0
@@ -6,6 +6,7 @@
// standard includes
#include <string>
#include <string_view>
#ifdef _WIN32
namespace lvh {
@@ -127,6 +128,28 @@
* @brief Query the Virtual HID Driver license and prepare the startup tray state.
*/
void prepare_tray_virtualhid_license();
/**
* @brief Show an update notification for an unsupported Virtual HID Driver.
*
* Existing notifications are preserved when the driver is absent or supported.
*
* @param installed Whether the driver is installed.
* @param version Installed driver version.
* @param version_compatible Whether Sunshine supports the installed version.
* @param supported_versions User-visible supported version range.
*/
void update_tray_virtualhid_driver(
bool installed,
std::string_view version,
bool version_compatible,
std::string_view supported_versions
);
/**
* @brief Query the Virtual HID Driver version and prepare its startup notification.
*/
void prepare_tray_virtualhid_driver();
#endif
/**
src_assets/common/assets/web/index.html +12 −1
@@ -29,7 +29,7 @@
</div>
<!-- libvirtualhid Warning -->
<div class="alert alert-warning my-4" v-if="platform === 'windows' && virtualhid && (!virtualhid.installed || !virtualhid.version_compatible)">
<div class="alert alert-warning my-4" v-if="platform === 'windows' && virtualhid && (!virtualhid.installed || (!virtualhid.version_compatible && !virtualhid.development_version))">
<div>
<div class="d-flex align-items-center mb-3">
<alert-triangle :size="32" class="icon-lg me-3"></alert-triangle>
@@ -48,5 +48,16 @@
<wrench :size="18" class="icon"></wrench>
{{ $t('index.fix_now') }}
</a>
</div>
</div>
<!-- libvirtualhid Development Build Notice -->
<div class="alert alert-info my-4" v-if="platform === 'windows' && virtualhid && virtualhid.development_version">
<div class="d-flex align-items-center">
<info :size="32" class="icon-lg me-3"></info>
<div>
<p class="mb-1"><strong>{{ $t('index.virtualhid_development_title') }}</strong></p>
<p class="mb-0">{{ $t('index.virtualhid_development_desc') }}</p>
</div>
</div>
</div>
src_assets/common/assets/web/public/assets/locale/en.json +2 −0
@@ -454,6 +454,8 @@
"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_development_desc": "Thank you for using a development build of Virtual HID Driver. Your testing helps make Virtual HID Driver better!",
"virtualhid_development_title": "Virtual HID Driver Development Build",
"virtualhid_license_required_desc": "Virtual HID Driver adds a Raw Input keyboard and 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.",
tests/CMakeLists.txt +2 −0
@@ -174,6 +174,8 @@
"src/config.cpp"
"src_assets/common/assets/web/config.html"
"docs/configuration.md"
"docs/getting_started.md"
"docs/troubleshooting.md"
"src_assets/common/assets/web/public/assets/locale/en.json"
"src_assets/common/assets/web/configs/tabs/General.vue"
"src_assets/linux/misc/60-sunshine.rules"
tests/integration/test_virtualhid_version_consistency.cpp +41 −0
@@ -1,0 +1,41 @@
/**
* @file tests/integration/test_virtualhid_version_consistency.cpp
* @brief Tests for the shared Virtual HID Driver minimum-version contract.
*/
// test includes
#include "../tests_common.h"
// standard includes
#include <array>
#include <format>
#include <string>
#include <string_view>
// local includes
#include "src/file_handler.h"
namespace {
/**
* @brief Documentation pages that publish the minimum Virtual HID Driver version.
*/
constexpr std::array<std::string_view, 2> virtualhid_version_documents {
"docs/getting_started.md",
"docs/troubleshooting.md",
};
} // namespace
TEST(VirtualHidVersionConsistencyTest, DocumentationMatchesConfiguredMinimum) {
const auto expected_text = std::format(
"Sunshine requires Virtual HID Driver version `{}` or newer",
LIBVIRTUALHID_MINIMUM_VERSION
);
for (const auto path : virtualhid_version_documents) {
const auto path_string = std::format("{}/{}", SUNSHINE_TEST_BIN_DIR, path);
const auto content = file_handler::read_file(path_string.c_str());
ASSERT_FALSE(content.empty()) << "Unable to read " << path;
EXPECT_NE(content.find(expected_text), std::string::npos)
<< path << " must use the CMake-configured Virtual HID Driver minimum " << LIBVIRTUALHID_MINIMUM_VERSION;
}
}
tests/unit/test_confighttp.cpp +18 −3
@@ -1469,10 +1469,20 @@
ASSERT_FALSE(confighttp::is_driver_version_supported("1.17.", "1.17.0.0"));
}
// Test: numeric 0.0.x development versions are identified consistently
TEST(ConfigHttpDriverStatusTest, IsDriverVersionDevelopment_RecognizesZeroMajorAndMinor) {
ASSERT_TRUE(confighttp::is_driver_version_development("0.0.0"));
ASSERT_TRUE(confighttp::is_driver_version_development("0.0.0.42"));
ASSERT_TRUE(confighttp::is_driver_version_development("0.0.1.0"));
ASSERT_FALSE(confighttp::is_driver_version_development("0.0"));
ASSERT_FALSE(confighttp::is_driver_version_development("0.1.0"));
ASSERT_FALSE(confighttp::is_driver_version_development("development"));
}
// 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_TRUE(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.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
}
@@ -1485,5 +1495,6 @@
ASSERT_EQ(status["version"].get<std::string>(), "1.17.0.0");
ASSERT_EQ(status["minimum_version"].get<std::string>(), "1.17.0.0");
ASSERT_EQ(status["supported_versions"].get<std::string>(), ">= 1.17.0.0");
ASSERT_FALSE(status["development_version"].get<bool>());
ASSERT_TRUE(status["version_compatible"].get<bool>());
}
@@ -1494,5 +1505,6 @@
ASSERT_FALSE(status["installed"].get<bool>());
ASSERT_EQ(status["supported_versions"].get<std::string>(), "Any");
ASSERT_FALSE(status["development_version"].get<bool>());
ASSERT_FALSE(status["version_compatible"].get<bool>());
}
@@ -1502,5 +1514,6 @@
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["development_version"].get<bool>());
ASSERT_FALSE(status["version_compatible"].get<bool>());
}
@@ -1511,5 +1524,6 @@
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["development_version"].get<bool>());
ASSERT_TRUE(status["version_compatible"].get<bool>());
}
@@ -1519,10 +1533,11 @@
EXPECT_TRUE(virtualhid.contains("installed"));
EXPECT_TRUE(virtualhid.contains("version"));
EXPECT_TRUE(virtualhid.contains("version_compatible"));
EXPECT_TRUE(virtualhid.contains("development_version"));
EXPECT_TRUE(virtualhid.contains("backend_name"));
EXPECT_TRUE(virtualhid.contains("requires_installed_driver"));
EXPECT_EQ(virtualhid["minimum_version"].get<std::string>(), LIBVIRTUALHID_MINIMUM_VERSION);
EXPECT_EQ(virtualhid["supported_versions"].get<std::string>(), std::format(">= {}", LIBVIRTUALHID_MINIMUM_VERSION));
EXPECT_EQ(virtualhid["minimum_version"].get<std::string>(), "2026.829.2338.54"); // NOSONAR(cpp:S1313): not an IP address
EXPECT_EQ(virtualhid["supported_versions"].get<std::string>(), ">= 2026.829.2338.54"); // NOSONAR(cpp:S1313): not an IP address
const auto vigembus = confighttp::get_vigembus_driver_status();
EXPECT_TRUE(vigembus.contains("installed"));
tests/unit/test_system_tray.cpp +41 −0
@@ -366,6 +366,47 @@
EXPECT_STREQ(license_menu[3].text, "Machine activations: Not reported");
}
TEST_F(SystemTrayTest, NotifiesWhenVirtualHidDriverIsUnsupported) {
const auto supported_versions = std::format(">= {}", LIBVIRTUALHID_MINIMUM_VERSION);
system_tray::update_tray_virtualhid_driver(true, "2026.829.2338.54", false, supported_versions); // NOSONAR(cpp:S1313): not an IP address
const auto &tray_data = system_tray::tray_data_for_testing();
const auto expected_notification = std::format(
"Installed Virtual HID Driver v2026.829.2338.54 is not supported by this version of Sunshine. Supported versions: {}. Restart Sunshine after updating. Click for instructions.",
supported_versions
);
EXPECT_STREQ(tray_data.notification_title, "Update Virtual HID Driver");
EXPECT_STREQ(tray_data.notification_text, expected_notification.c_str());
EXPECT_STREQ(tray_data.notification_icon, tray_data.allIconPaths[4]);
EXPECT_NE(tray_data.notification_cb, nullptr);
system_tray::resolve_tray_icon_paths_for_testing();
EXPECT_STREQ(tray_data.notification_icon, tray_data.allIconPaths[4]);
}
TEST_F(SystemTrayTest, PreservesExistingNotificationWhenVirtualHidDriverIsUsable) {
lvh::LicenseStatus license;
license.service_available = true;
license.state = lvh::LicenseState::unlicensed;
system_tray::update_tray_virtualhid_license(license, true);
const auto &tray_data = system_tray::tray_data_for_testing();
const auto *notification_title = tray_data.notification_title;
const auto *notification_text = tray_data.notification_text;
const auto notification_callback = tray_data.notification_cb;
const auto supported_versions = std::format(">= {}", LIBVIRTUALHID_MINIMUM_VERSION);
system_tray::update_tray_virtualhid_driver(false, "", false, supported_versions);
EXPECT_EQ(tray_data.notification_title, notification_title);
EXPECT_EQ(tray_data.notification_text, notification_text);
EXPECT_EQ(tray_data.notification_cb, notification_callback);
system_tray::update_tray_virtualhid_driver(true, "0.0.1", true, supported_versions);
EXPECT_EQ(tray_data.notification_title, notification_title);
EXPECT_EQ(tray_data.notification_text, notification_text);
EXPECT_EQ(tray_data.notification_cb, notification_callback);
}
/**
* @brief License-state fixture for the Windows Virtual HID Driver tray menu.
*/
third-party/libvirtualhid +0 −0