Skip to content
Open
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
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ project(Sunshine VERSION 0.0.0
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)
Expand Down
4 changes: 1 addition & 3 deletions docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -539,9 +539,7 @@ Sunshine uses libvirtualhid for virtual input on Windows. You must install the
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
Expand Down
4 changes: 1 addition & 3 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,7 @@ Input keyboard and mouse plus full virtual gamepad support. ViGEmBus is detected
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
Expand Down
20 changes: 11 additions & 9 deletions src/confighttp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ namespace confighttp {
*/
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

/**
Expand Down Expand Up @@ -231,8 +231,13 @@ namespace confighttp {
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()) {
if (minimum_version.empty() || is_driver_version_development(version)) {
return true;
}

Expand All @@ -242,10 +247,6 @@ 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 All @@ -266,6 +267,7 @@ namespace confighttp {
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;
Expand Down Expand Up @@ -1886,7 +1888,7 @@ namespace confighttp {
#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;
Expand All @@ -1897,7 +1899,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(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();
Expand All @@ -1909,7 +1911,7 @@ namespace confighttp {
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;
Expand Down
15 changes: 13 additions & 2 deletions src/confighttp.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,23 @@ namespace confighttp {
*/
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 whose
* first three components are `0.0.0` are always accepted.
* require a fully numeric dotted version string. Development versions beginning
* with `0.0` are always accepted.
*
* @param version Detected driver version.
* @param minimum_version Minimum supported driver version, or empty for any version.
Expand Down
1 change: 1 addition & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ int main(int argc, char *argv[]) {
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.
Expand Down
54 changes: 54 additions & 0 deletions src/system_tray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,16 @@ namespace system_tray {
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

Expand Down Expand Up @@ -318,6 +328,7 @@ namespace system_tray {
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
}
Expand Down Expand Up @@ -486,6 +497,49 @@ namespace system_tray {
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

/**
Expand Down
23 changes: 23 additions & 0 deletions src/system_tray.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

// standard includes
#include <string>
#include <string_view>

#ifdef _WIN32
namespace lvh {
Expand Down Expand Up @@ -127,6 +128,28 @@ namespace system_tray {
* @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

/**
Expand Down
13 changes: 12 additions & 1 deletion 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' && 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>
Expand All @@ -51,6 +51,17 @@ <h1 class="my-4">{{ $t('index.welcome') }}</h1>
</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>

<div class="alert alert-warning my-4" v-if="platform === 'windows' && controllerEnabled && virtualhid && vigembus && !virtualhid.installed && vigembus.installed">
<div class="d-flex align-items-center">
<alert-triangle :size="32" class="icon-lg me-3"></alert-triangle>
Expand Down
2 changes: 2 additions & 0 deletions src_assets/common/assets/web/public/assets/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ set(INTEGRATION_TEST_FILES
"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"
Expand Down
41 changes: 41 additions & 0 deletions tests/integration/test_virtualhid_version_consistency.cpp
Original file line number Diff line number Diff line change
@@ -0,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;
}
}
Loading
Loading