diff --git a/docs/configuration.md b/docs/configuration.md index 03086488173..d55dc380f82 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1522,9 +1522,9 @@ editing the `conf` file in a text editor. Use the examples as reference. Default - @code{} - (empty - bind to all interfaces) - @endcode + + Empty, binds to all interfaces + Example (IPv4) @@ -1626,10 +1626,10 @@ editing the `conf` file in a text editor. Use the examples as reference. Default - @code{} - (empty - uses built-in defaults: https://localhost, https://127.0.0.1, https://[::1], - with configured UI port variants) - @endcode + + Empty, uses built-in defaults: https://localhost, https://127.0.0.1, https://[::1], + with configured UI port variants + Example @@ -2802,9 +2802,9 @@ editing the `conf` file in a text editor. Use the examples as reference. Default - @code{} - - @endcode + + Empty, uses encoder default. + Example diff --git a/docs/getting_started.md b/docs/getting_started.md index 062875653fd..5f96cb18353 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -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. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 3e0785a5411..c5ece4621f0 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -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. diff --git a/src/confighttp.cpp b/src/confighttp.cpp index ef9f710a9bb..eee5677b526 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -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 @@ 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 /** @@ -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; @@ -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; @@ -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(); @@ -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 &) { diff --git a/src/confighttp.h b/src/confighttp.h index d096836fc53..fb737b3272b 100644 --- a/src/confighttp.h +++ b/src/confighttp.h @@ -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. diff --git a/src/input.cpp b/src/input.cpp index 82b11660e2b..bac39561448 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -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" @@ -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. */ diff --git a/src/input.h b/src/input.h index fc092327728..eda3c713952 100644 --- a/src/input.h +++ b/src/input.h @@ -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. * diff --git a/src/platform/virtualhid_input.cpp b/src/platform/virtualhid_input.cpp index b7aee98299a..351da88cc2b 100644 --- a/src/platform/virtualhid_input.cpp +++ b/src/platform/virtualhid_input.cpp @@ -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); } } diff --git a/src/platform/virtualhid_input.h b/src/platform/virtualhid_input.h index a7cf86b735a..380bdb47f81 100644 --- a/src/platform/virtualhid_input.h +++ b/src/platform/virtualhid_input.h @@ -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 runtime; ///< libvirtualhid runtime. std::unique_ptr keyboard; ///< Shared virtual keyboard. std::unique_ptr mouse; ///< Shared virtual mouse. diff --git a/src/system_tray.cpp b/src/system_tray.cpp index 47a739dc178..81043ec1add 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -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 virtualhid_benefits_menu {{ + static std::array 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 @@ 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; } @@ -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", @@ -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); } @@ -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"); diff --git a/src_assets/common/assets/web/index.html b/src_assets/common/assets/web/index.html index d6e57a40e84..bff44590824 100644 --- a/src_assets/common/assets/web/index.html +++ b/src_assets/common/assets/web/index.html @@ -29,7 +29,7 @@

{{ $t('index.welcome') }}

-
+
@@ -62,7 +62,7 @@

{{ $t('index.welcome') }}

-
+
@@ -222,8 +222,8 @@
{{ githubVersion.release.name }}
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; diff --git a/src_assets/common/assets/web/public/assets/locale/en.json b/src_assets/common/assets/web/public/assets/locale/en.json index 07c659660ea..26a809231d9 100644 --- a/src_assets/common/assets/web/public/assets/locale/en.json +++ b/src_assets/common/assets/web/public/assets/locale/en.json @@ -453,11 +453,11 @@ "startup_errors": "Attention! Sunshine detected these errors during startup. We STRONGLY RECOMMEND 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 full virtual gamepad support. Install the libvirtualhid driver package, then restart Sunshine.", + "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_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 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": "Virtual Input", + "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": "Benefits over ViGEmBus", + "virtualhid_benefits_title": "Virtual HID Driver Benefits", "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", diff --git a/src_assets/common/assets/web/troubleshooting.html b/src_assets/common/assets/web/troubleshooting.html index dde2cc0d483..75e0fa290bd 100644 --- a/src_assets/common/assets/web/troubleshooting.html +++ b/src_assets/common/assets/web/troubleshooting.html @@ -9,7 +9,7 @@

{{ $t('troubleshooting.troubleshooting') }}

- +
@@ -32,6 +32,15 @@

{{ $t('troubleshooting.virtualhid_benefit_gamepads_title') }}

{{ $t('troubleshooting.virtualhid_benefit_gamepads') }}

+
+ +
+

{{ $t('troubleshooting.virtualhid_benefit_mouse_title') }}

+

{{ $t('troubleshooting.virtualhid_benefit_mouse') }}

+
+
-
+

{{ $t('troubleshooting.virtual_gamepad_drivers') }}

@@ -436,6 +445,7 @@

{{ $t('troubleshooting.logs') }}

ExternalLink, Gamepad2, KeyRound, + MousePointer2, RefreshCw, RotateCcw, Search, @@ -461,6 +471,7 @@

{{ $t('troubleshooting.logs') }}

ExternalLink, Gamepad2, KeyRound, + MousePointer2, RefreshCw, RotateCcw, Search, @@ -657,7 +668,7 @@

{{ $t('troubleshooting.logs') }}

}, 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 @@

{{ $t('troubleshooting.logs') }}

.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 @@

{{ $t('troubleshooting.logs') }}

this.refreshDriverReleases(); }, /** - * @brief Refresh the latest stable release metadata for both Windows gamepad drivers. + * @brief Refresh the latest stable release metadata for the Windows virtual input drivers. */ refreshDriverReleases() { this.updateLatestRelease( diff --git a/tests/unit/test_confighttp.cpp b/tests/unit/test_confighttp.cpp index 1f65c1d6f85..eb43564f13e 100644 --- a/tests/unit/test_confighttp.cpp +++ b/tests/unit/test_confighttp.cpp @@ -1338,6 +1338,14 @@ TEST(ConfigHttpDriverStatusTest, IsDriverVersionSupported_InvalidVersion_Returns 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 @@ TEST(ConfigHttpDriverStatusTest, BuildDriverStatus_NotInstalledIsNotCompatible) ASSERT_FALSE(status["version_compatible"].get()); } +// 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()); + ASSERT_FALSE(status["version_compatible"].get()); +} + +// 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(), "2026.823.352.3"); // NOSONAR(cpp:S1313): not an IP address + ASSERT_EQ(status["supported_versions"].get(), ">= 2026.823.352.3"); // NOSONAR(cpp:S1313): not an IP address + ASSERT_TRUE(status["version_compatible"].get()); +} + TEST(ConfigHttpDriverStatusTest, BuildsLiveVirtualInputDriverStatus) { const auto virtualhid = confighttp::get_virtualhid_driver_status(); EXPECT_TRUE(virtualhid.contains("installed")); @@ -1365,6 +1390,8 @@ TEST(ConfigHttpDriverStatusTest, BuildsLiveVirtualInputDriverStatus) { 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(), "2026.823.352.3"); // NOSONAR(cpp:S1313): not an IP address + EXPECT_EQ(virtualhid["supported_versions"].get(), ">= 2026.823.352.3"); // NOSONAR(cpp:S1313): not an IP address const auto vigembus = confighttp::get_vigembus_driver_status(); EXPECT_TRUE(vigembus.contains("installed")); diff --git a/tests/unit/test_input.cpp b/tests/unit/test_input.cpp index f820178caf0..db6e0ee73c8 100644 --- a/tests/unit/test_input.cpp +++ b/tests/unit/test_input.cpp @@ -31,6 +31,7 @@ namespace { 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 @@ namespace { 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 @@ namespace { 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. }; @@ -92,3 +104,15 @@ TEST_F(InputGamepadSessionTest, ReusesGamepadsAcrossPauseAndDestroysThemOnTermin auto replacement = input::alloc(std::make_shared(), 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); +} diff --git a/tests/unit/test_system_tray.cpp b/tests/unit/test_system_tray.cpp index 7961786db8b..1c58f745e8c 100644 --- a/tests/unit/test_system_tray.cpp +++ b/tests/unit/test_system_tray.cpp @@ -84,14 +84,16 @@ namespace { 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, "-"); - 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[3].text, "Actively developed and supported by LizardByte"); + EXPECT_EQ(benefits_menu[3].disabled, 1); + 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 @@ namespace { */ 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 @@ namespace { 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, "Benefits over ViGEmBus"); + EXPECT_STREQ(tray_data.menu[2].submenu[3].text, "Virtual HID Driver Benefits"); 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 @@ TEST_P(UnlicensedVirtualHidTrayTest, PreparesMenuAndStartupNotification) { 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, "Full virtual gamepad support is locked"); + EXPECT_STREQ(license_menu[2].text, "Driver-backed gamepads and Raw Input mouse are 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 @@ TEST_P(UnlicensedVirtualHidTrayTest, PreparesMenuAndStartupNotification) { 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); diff --git a/third-party/libvirtualhid b/third-party/libvirtualhid index 15a37d34ac6..0fcb5d8476d 160000 --- a/third-party/libvirtualhid +++ b/third-party/libvirtualhid @@ -1 +1 @@ -Subproject commit 15a37d34ac6705f23cee7ad4f42695e8000d68cb +Subproject commit 0fcb5d8476d9f54a747a99ca9030d15e26ad5bad