From 2211ad0b99fb49d6f48c4f2958f570d280d7dc96 Mon Sep 17 00:00:00 2001 From: Dave Lane <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 25 Sep 2026 23:40:12 -0400 Subject: [PATCH 01/10] feat(macos): add gamepad support using libvirtualhid --- README.md | 30 ++-- docs/configuration.md | 23 +-- docs/getting_started.md | 41 +++-- docs/troubleshooting.md | 45 ++++-- src/config.cpp | 22 +-- src/config.h | 5 +- src/confighttp.cpp | 2 +- src/input.cpp | 10 +- src/main.cpp | 3 + src/platform/macos/input.cpp | 13 +- src/platform/virtualhid_input.cpp | 9 +- src/platform/virtualhid_input.h | 6 +- src/platform/windows/input.cpp | 7 +- src/system_tray.cpp | 111 +++++++++---- src/system_tray.h | 18 ++- src_assets/common/assets/web/Home.vue | 28 +++- .../common/assets/web/Troubleshooting.vue | 41 +++-- .../common/assets/web/configs/tabs/Inputs.vue | 73 ++++----- .../assets/web/public/assets/locale/en.json | 70 ++++---- tests/CMakeLists.txt | 2 +- tests/unit/platform/macos/test_av_audio.mm | 14 +- tests/unit/platform/test_virtualhid_input.cpp | 62 +++++++- tests/unit/test_config.cpp | 21 +++ tests/unit/test_system_tray.cpp | 149 ++++++++++++++++-- tests/web/Configuration.test.js | 79 ++++++++++ third-party/libvirtualhid | 2 +- vite.config.js | 2 +- 27 files changed, 641 insertions(+), 247 deletions(-) diff --git a/README.md b/README.md index bc9a6f46455..2ffd99b7aa3 100644 --- a/README.md +++ b/README.md @@ -56,55 +56,57 @@ LizardByte has the full documentation hosted on [Read the Docs](https://docs.liz Generic 🟑1 βœ… - ❌ - βœ… + βœ…2 + βœ…3 DualShock / DS4 (PlayStation 4) 🟑1 βœ… - ❌ - βœ… + βœ…2 + βœ…3 DualSense / DS5 (PlayStation 5) 🟑1 βœ… - ❌ - βœ… + βœ…2 + βœ…3 Nintendo Switch Pro 🟑1 βœ… - ❌ - βœ… + βœ…2 + βœ…3 Xbox 360 🟑1 βœ… - ❌ - βœ… + βœ…2 + βœ…3 Xbox One 🟑1 βœ… - ❌ - βœ… + βœ…2 + βœ…3 Xbox Series 🟑1 βœ… - ❌ - βœ… + βœ…2 + βœ…3 > [!NOTE] > 1 Missing motion, touchpad input, battery state, RGB LEDs, adaptive triggers, and raw HID output reports. +> 2 Requires the separately installed and licensed Virtual HID Broker. +> 3 All profiles are available through the separately installed and licensed Virtual HID Broker. Xbox 360 and DualShock 4 can also use ViGEmBus. diff --git a/docs/configuration.md b/docs/configuration.md index d1b12b3c17a..d30bbb85734 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -319,12 +319,12 @@ supported on the current platform. @@ -340,17 +340,21 @@ supported on the current platform. @endcode - + - + - + - + + + + +
Encoding API
Description - Controls which virtual gamepad drivers Sunshine may use. The Web UI and startup notification continue to - request a choice while this option is not set. If Sunshine detects an active Virtual HID Driver license, - it automatically sets this option to `all` when it is missing. + Controls which virtual gamepad backend Sunshine may use on Windows and macOS. On Windows, the Web UI and + startup notification request a choice while this option is not set. If Sunshine detects an active Virtual + HID Broker license on Windows, it automatically sets this option to `all` when it is missing. @warning{ViGEmBus has limited gamepad features, supports only Xbox 360 and DualShock 4 emulation, and has - reached end of life. Selecting `vigembus` also suppresses Virtual HID Driver startup notifications.} - @note{This option applies only to Windows.} + reached end of life. Selecting `vigembus` also suppresses Virtual HID Broker startup notifications.} + @note{`all` and `vigembus` apply only to Windows. On macOS, an unset value uses Virtual HID Broker.}
ChoicesChoices allPrefer Virtual HID Driver when it is available and licensed, with ViGEmBus as a limited fallback.Windows only: prefer Virtual HID Broker when it is available and licensed, with ViGEmBus as a limited fallback.
virtualhidUse only Virtual HID Driver. An active paid license is required; ViGEmBus fallback is disabled.Use Virtual HID Broker for gamepads. An active paid license is required; Windows ViGEmBus fallback is disabled.
vigembusUse only ViGEmBus for gamepads and hide Virtual HID Driver status and licensing details.Windows only: use ViGEmBus for gamepads and hide Virtual HID Broker status and licensing details.
noneDisable virtual gamepads on Windows and macOS without changing keyboard or mouse input. Suppress broker and driver choice notices.
@@ -361,7 +365,6 @@ supported on the current platform. Description The type of gamepad to emulate on the host. - @note{This option applies to FreeBSD, Linux, and Windows.} @note{When gamepad_driver is `vigembus` on Windows, only auto, x360, and ds4 are available.} diff --git a/docs/getting_started.md b/docs/getting_started.md index 2a3eb12147e..4f583676956 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -400,7 +400,19 @@ brew uninstall sunshine ### macOS > [!IMPORTANT] -> Sunshine on macOS is experimental. Gamepads do not work. +> Virtual gamepads require the separately installed, licensed +> [Virtual HID Broker](https://github.com/LizardByte/libvirtualhid/releases/latest). + +To use gamepads, download the macOS universal libvirtualhid DMG from the link above. Open it and run +**Install libvirtualhid.command** to install Virtual HID Broker. In **System Settings > Privacy & Security > +Device Control and Data Access**, add `/Applications/VirtualHIDBroker.app` and enable it. Restart the broker if +you changed this permission with `sudo launchctl kickstart -k system/dev.lizardbyte.app.libvirtualhid`. +Activate the machine license in Sunshine’s +**Troubleshooting > Virtual HID Broker License** section. In **Configuration > Input**, turn on **Enable Gamepad Input** +and choose the emulated gamepad. The Gamepad Backend setting can be set to **None** to disable gamepads without affecting +keyboard or mouse input. Sunshine’s menu bar **Virtual HID Broker** submenu shows the license status and links +for license management and downloads. Sunshine’s DMG does not contain the broker. Keyboard and mouse input use the +standard macOS synthetic-input permission path. #### DMG @@ -569,15 +581,15 @@ and enter its device name in the [audio_sink](configuration.md#audio_sink) field ### Windows Sunshine supports two virtual gamepad backends on Windows. You can install the -[Virtual HID Driver](https://github.com/LizardByte/libvirtualhid/releases/latest) separately as an optional paid upgrade -for a driver-backed Raw Input keyboard and mouse plus full virtual gamepad support. ViGEmBus remains available as a -limited alternative for Xbox 360 and DualShock 4 gamepads, but it has reached end of life. +[Virtual HID Broker](https://github.com/LizardByte/libvirtualhid/releases/latest) separately as an optional paid upgrade. +Its Windows package includes a broker service and user-mode driver for a Raw Input keyboard and mouse plus full virtual +gamepad support. ViGEmBus remains available as a limited alternative for Xbox 360 and DualShock 4 gamepads, but it has reached end of life. -When Virtual HID Driver is used, Sunshine requires version `2026.914.1218.10` or newer. +When Virtual HID Broker is used, Sunshine requires libvirtualhid version `2026.914.1218.10` or newer. -Compared with the ViGEmBus fallback, Virtual HID Driver can create Xbox One, Xbox Series, DualSense, Nintendo Switch +Compared with the ViGEmBus fallback, Virtual HID Broker 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 +as motion, touchpads, LEDs, and adaptive triggers when supported. Virtual HID Broker is actively developed and supported by the LizardByte team. With a compatible driver and active license, normal key transitions are exposed through a real HID keyboard so @@ -589,15 +601,16 @@ 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 paid machine license for driver-backed devices, including gamepads and the Raw +The Virtual HID Broker requires an active paid machine license for driver-backed devices, including gamepads and the Raw Input keyboard and mouse. Sunshine shows the current license status and actions on the Web UI Troubleshooting page and -in the **Virtual HID Driver** system tray submenu. In **Configuration > Input**, choose whether Sunshine may use **All -Available Drivers**, only **Virtual HID Driver**, or only **ViGEmBus**. Sunshine continues to show the selection prompt -until this setting is saved. If an active Virtual HID Driver license is already present, Sunshine selects **All Available -Drivers** automatically. That policy prefers Virtual HID Driver. When its license is not valid, Sunshine falls back to +in the **Virtual HID Broker** system tray submenu. In **Configuration > Input**, choose whether Sunshine may use **All +Available Drivers**, only **Virtual HID Broker**, or only **ViGEmBus**. Sunshine continues to show the selection prompt +until this setting is saved. If an active Virtual HID Broker license is already present, Sunshine selects **All Available +Drivers** automatically. That policy prefers Virtual HID Broker. When its license is not valid, Sunshine falls back to ViGEmBus for Xbox 360 and DualShock 4 gamepads and to SendInput for keyboard and mouse. Selecting only **ViGEmBus** -suppresses Virtual HID Driver startup notifications and limits the available emulated gamepads to Xbox 360 and -DualShock 4. Sunshine recreates the shared keyboard and mouse after a successful license action, so switching between +suppresses Virtual HID Broker startup notifications and limits the available emulated gamepads to Xbox 360 and +DualShock 4. **None** disables all virtual gamepads and their startup notices without affecting keyboard or mouse +input. Sunshine recreates the shared keyboard and 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 fb3ae3ff7dc..889bea61639 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -306,6 +306,18 @@ Some users have reported stuttering issues when streaming games running within G ## macOS +### No gamepad detected +Confirm **Enable Gamepad Input** is on under **Configuration > Input**. Install +[Virtual HID Broker](https://github.com/LizardByte/libvirtualhid/releases/latest) separately from Sunshine, +using the macOS installer in its universal DMG. Confirm that `/Applications/VirtualHIDBroker.app` is allowed in +**System Settings > Privacy & Security > Device Control and Data Access**, and activate a machine license under +**Troubleshooting > Virtual HID Broker License** in Sunshine. The menu bar **Virtual HID Broker** submenu also +shows license status and links for license management and downloads. If the broker or license is unavailable, the +Troubleshooting page shows the broker response. Sunshine can show a startup notice when gamepad input is enabled +and the broker or license is unavailable. Choose **None** under **Configuration > Input > Gamepad Backend** to disable +virtual gamepads and that notice without affecting keyboard or mouse input. Reconnect the Moonlight session after choosing +a different emulated gamepad profile. + ### Dynamic session lookup failed If you get this error: @@ -321,46 +333,47 @@ launchctl load -w /Library/LaunchAgents/org.freedesktop.dbus-session.plist ### No gamepad detected Sunshine supports two virtual gamepad backends on Windows. You can install the -[Virtual HID Driver](https://github.com/LizardByte/libvirtualhid/releases/latest) separately as an optional paid upgrade -for a driver-backed Raw Input keyboard and mouse plus full virtual gamepad support. ViGEmBus is a limited alternative -for Xbox 360 and DualShock 4 support that has reached end of life. If you use the +[Virtual HID Broker](https://github.com/LizardByte/libvirtualhid/releases/latest) separately as an optional paid upgrade. +Its Windows package includes a broker service and user-mode driver for a Raw Input keyboard and mouse plus full virtual +gamepad support. ViGEmBus is a limited alternative for Xbox 360 and DualShock 4 support that has reached end of life. If you use the [ViGEmBus fallback](https://github.com/nefarius/ViGEmBus/releases/latest), you must use version 1.17 or newer. -When Virtual HID Driver is used, Sunshine requires version `2026.914.1218.10` or newer. +When Virtual HID Broker is used, Sunshine requires libvirtualhid version `2026.914.1218.10` or newer. -Virtual HID Driver adds Xbox One, Xbox Series, DualSense, Nintendo Switch Pro, and Generic gamepads, plus advanced +Virtual HID Broker 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. +ViGEmBus project, Virtual HID Broker is actively developed and supported by the LizardByte team. -An active paid Virtual HID Driver machine license is required before Sunshine can create driver-backed libvirtualhid +An active paid Virtual HID Broker machine license is required before Sunshine can create driver-backed libvirtualhid devices, including gamepads and the Raw Input keyboard and mouse. Use the message on the Web UI home page, the startup -tray notification, or **Get/Manage License** in the **Virtual HID Driver** tray submenu to open the license section on -the Troubleshooting page. In **Configuration > Input**, select **All Available Drivers**, only **Virtual HID Driver**, or +tray notification, or **Get/Manage License** in the **Virtual HID Broker** tray submenu to open the license section on +the Troubleshooting page. In **Configuration > Input**, select **All Available Drivers**, only **Virtual HID Broker**, or only **ViGEmBus**. Sunshine keeps prompting until this setting is saved, but automatically selects **All Available -Drivers** when it detects an existing active Virtual HID Driver license. Whenever the Virtual HID Driver license is not +Drivers** when it detects an existing active Virtual HID Broker license. Whenever the Virtual HID Broker license is not valid, **All Available Drivers** falls back to a compatible ViGEmBus installation for Xbox 360 and DualShock 4 gamepads -and to SendInput for keyboard and mouse. Selecting only **ViGEmBus** suppresses Virtual HID Driver startup notifications -and hides its status and license details from the Troubleshooting page. +and to SendInput for keyboard and mouse. Selecting only **ViGEmBus** suppresses Virtual HID Broker startup notifications +and hides its status and license details from the Troubleshooting page. Selecting **None** disables all virtual +gamepads and suppresses broker and driver choice notices without affecting keyboard or mouse input. After installation, it is recommended to restart your computer. ### Games do not detect keyboard input -With a compatible Virtual HID Driver and active license, Sunshine sends normal key transitions through a real HID +With a compatible Virtual HID Broker and active license, Sunshine sends normal key transitions through a real HID keyboard so games using Raw Input can receive them. Unicode text input and keys outside the supported HID keyboard page continue to use Windows input injection. When the driver-backed keyboard cannot be created because the driver, broker, or license is unavailable, libvirtualhid falls back to SendInput. -Check the Virtual HID Driver version and license sections on the Web UI Troubleshooting page. Sunshine recreates the +Check the libvirtualhid driver version and Virtual HID Broker license on the Web UI Troubleshooting page. Sunshine recreates the shared keyboard and 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. ### Games do not detect mouse input -With a compatible Virtual HID Driver and active license, Sunshine sends relative mouse movement, buttons, and scrolling +With a compatible Virtual HID Broker 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 +Check the libvirtualhid driver version and Virtual HID Broker license on the Web UI Troubleshooting page even when controller input is disabled. The same live refresh used by the keyboard path also switches the mouse between HID and SendInput without requiring a Sunshine restart. diff --git a/src/config.cpp b/src/config.cpp index e0e864735da..15dd6c40b18 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -854,7 +854,7 @@ namespace config { platf::supported_gamepads(nullptr).front().name.data(), platf::supported_gamepads(nullptr).front().name.size(), }, // Default gamepad - {}, // gamepad_driver remains unset until the user chooses a Windows driver policy + {}, // Windows requests a backend choice; macOS defaults to Virtual HID Broker. true, // back as touchpad click enabled for PlayStation-style gamepads true, // client gamepads with motion events use PlayStation-style emulation true, // client gamepads with touchpads use PlayStation-style emulation @@ -1571,14 +1571,17 @@ namespace config { * * @return Platform-supported gamepad backend names accepted by configuration. */ - std::vector &get_supported_gamepad_options() { - const auto options = platf::supported_gamepads(nullptr); - static std::vector opts {}; - opts.reserve(options.size()); - for (auto &opt : options) { - opts.emplace_back(opt.name); - } - return opts; + const std::vector &get_supported_gamepad_options() { + static const auto gamepads = platf::supported_gamepads(nullptr); + static const auto options = []() { + std::vector names; + names.reserve(gamepads.size()); + for (const auto &gamepad : gamepads) { + names.emplace_back(gamepad.name); + } + return names; + }(); + return options; } /** @@ -1828,6 +1831,7 @@ namespace config { GAMEPAD_DRIVER_ALL, GAMEPAD_DRIVER_VIRTUALHID, GAMEPAD_DRIVER_VIGEMBUS, + GAMEPAD_DRIVER_NONE, }); string_restricted_f(vars, "gamepad"s, input.gamepad, get_supported_gamepad_options()); #ifdef _WIN32 diff --git a/src/config.h b/src/config.h index 78615387365..8bd3aa4d533 100644 --- a/src/config.h +++ b/src/config.h @@ -34,8 +34,9 @@ namespace config { constexpr int PACKETSIZE_LARGE = 1456; ///< Default large packet size that avoids common MTU fragmentation. inline constexpr std::string_view GAMEPAD_DRIVER_ALL = "all"; ///< Allow every available Windows virtual gamepad driver. - inline constexpr std::string_view GAMEPAD_DRIVER_VIRTUALHID = "virtualhid"; ///< Allow only Virtual HID Driver for Windows gamepads. + inline constexpr std::string_view GAMEPAD_DRIVER_VIRTUALHID = "virtualhid"; ///< Use Virtual HID Broker for gamepads on Windows or macOS. inline constexpr std::string_view GAMEPAD_DRIVER_VIGEMBUS = "vigembus"; ///< Allow only ViGEmBus for Windows gamepads. + inline constexpr std::string_view GAMEPAD_DRIVER_NONE = "none"; ///< Disable virtual gamepads on Windows and macOS. // track modified config options inline std::unordered_map modified_config_settings; ///< Configuration keys changed during the current parse or UI update. @@ -291,7 +292,7 @@ namespace config { std::chrono::duration key_repeat_period; ///< Interval between repeated keyboard key events. std::string gamepad; ///< Virtual controller profile selected by configuration. - std::string gamepad_driver; ///< Windows virtual gamepad driver policy, or empty until the user chooses one. + std::string gamepad_driver; ///< Virtual gamepad backend policy on Windows and macOS. bool ds4_back_as_touchpad_click; ///< Map Back/Select to touchpad click for PlayStation-style gamepads. bool motion_as_ds4; ///< Prefer PlayStation-style emulation for client gamepads with motion controls. bool touchpad_as_ds4; ///< Prefer PlayStation-style emulation for client gamepads with touchpad input. diff --git a/src/confighttp.cpp b/src/confighttp.cpp index 5bfcf1f2092..253c70e67d0 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -2112,7 +2112,7 @@ namespace confighttp { #ifdef _WIN32 config::select_all_gamepad_drivers_if_licensed(result.license.licensed()); #endif -#if defined(_WIN32) && defined(SUNSHINE_TRAY) && SUNSHINE_TRAY >= 1 +#if (defined(_WIN32) || defined(__APPLE__)) && defined(SUNSHINE_TRAY) && SUNSHINE_TRAY >= 1 system_tray::update_tray_virtualhid_license(result.license, false); #endif #ifdef _WIN32 diff --git a/src/input.cpp b/src/input.cpp index 4c1660835fb..b30352205b8 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -1333,7 +1333,7 @@ namespace input { * @param packet The controller arrival packet. */ void passthrough(std::shared_ptr &input, PSS_CONTROLLER_ARRIVAL_PACKET packet) { - if (!config::input.controller) { + if (!config::input.controller || config::input.gamepad_driver == config::GAMEPAD_DRIVER_NONE) { return; } @@ -1483,7 +1483,7 @@ namespace input { * @param packet The controller touch packet. */ void passthrough(std::shared_ptr &input, PSS_CONTROLLER_TOUCH_PACKET packet) { - if (!config::input.controller) { + if (!config::input.controller || config::input.gamepad_driver == config::GAMEPAD_DRIVER_NONE) { return; } @@ -1516,7 +1516,7 @@ namespace input { * @param packet The controller motion packet. */ void passthrough(std::shared_ptr &input, PSS_CONTROLLER_MOTION_PACKET packet) { - if (!config::input.controller) { + if (!config::input.controller || config::input.gamepad_driver == config::GAMEPAD_DRIVER_NONE) { return; } @@ -1548,7 +1548,7 @@ namespace input { * @param packet The controller battery packet. */ void passthrough(std::shared_ptr &input, PSS_CONTROLLER_BATTERY_PACKET packet) { - if (!config::input.controller) { + if (!config::input.controller || config::input.gamepad_driver == config::GAMEPAD_DRIVER_NONE) { return; } @@ -1579,7 +1579,7 @@ namespace input { * @param packet Protocol packet being processed. */ void passthrough(std::shared_ptr &input, PNV_MULTI_CONTROLLER_PACKET packet) { - if (!config::input.controller) { + if (!config::input.controller || config::input.gamepad_driver == config::GAMEPAD_DRIVER_NONE) { return; } diff --git a/src/main.cpp b/src/main.cpp index 21fb8ef0ec2..1eb8f0f825c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -511,6 +511,9 @@ int main(int argc, char *argv[]) { // Ideally, we would run the system tray on the main thread for all platforms. system_tray::init_tray_threaded(); #else + #ifdef __APPLE__ + system_tray::prepare_tray_virtualhid_license(); + #endif system_tray::init_tray(); #endif } diff --git a/src/platform/macos/input.cpp b/src/platform/macos/input.cpp index 8b257a40053..dbc19cb27c8 100644 --- a/src/platform/macos/input.cpp +++ b/src/platform/macos/input.cpp @@ -12,6 +12,9 @@ #include #include +// lib includes +#include + // local includes #include "src/config.h" #include "src/platform/virtualhid_input.h" @@ -37,7 +40,7 @@ namespace platf { } const auto &capabilities = runtime->capabilities(); - if (capabilities.supports_gamepad && virtualhid::configured_gamepad_supports_controller_extensions()) { + if (config::input.gamepad_driver != config::GAMEPAD_DRIVER_NONE && capabilities.supports_gamepad && lvh::get_license_status().license.licensed() && virtualhid::configured_gamepad_supports_controller_extensions()) { caps |= platform_caps::controller_touch; } if (config::input.native_pen_touch && (capabilities.supports_touchscreen || capabilities.supports_pen_tablet)) { @@ -54,7 +57,13 @@ namespace platf { return gamepads; } - gamepads = virtualhid::supported_gamepads(virtualhid::get_input_context(*input).runtime.get()); + if (config::input.gamepad_driver == config::GAMEPAD_DRIVER_NONE) { + gamepads.clear(); + return gamepads; + } + + const auto licensed = lvh::get_license_status().license.licensed(); + gamepads = virtualhid::supported_gamepads(virtualhid::get_input_context(*input).runtime.get(), false, licensed, true); return gamepads; } diff --git a/src/platform/virtualhid_input.cpp b/src/platform/virtualhid_input.cpp index 362f50af5e0..db710dae6d4 100644 --- a/src/platform/virtualhid_input.cpp +++ b/src/platform/virtualhid_input.cpp @@ -542,14 +542,15 @@ namespace platf::virtualhid { std::vector supported_gamepads( lvh::Runtime *runtime, const bool fallback_vigem_available, - const bool virtualhid_licensed + const bool virtualhid_licensed, + const bool require_license ) { if (!runtime) { return static_supported_gamepads(); } const auto &capabilities = runtime->capabilities(); - const auto license_valid = !capabilities.requires_installed_driver || virtualhid_licensed; + const auto license_valid = (!capabilities.requires_installed_driver && !require_license) || virtualhid_licensed; const auto libvirtualhid_available = capabilities.supports_gamepad && license_valid; std::string reason; if (!capabilities.supports_gamepad) { @@ -582,7 +583,7 @@ namespace platf::virtualhid { const std::string_view gamepad_driver, const bool virtualhid_licensed ) { - return gamepad_driver != config::GAMEPAD_DRIVER_VIGEMBUS && capabilities.supports_gamepad && + return gamepad_driver != config::GAMEPAD_DRIVER_VIGEMBUS && gamepad_driver != config::GAMEPAD_DRIVER_NONE && capabilities.supports_gamepad && (!capabilities.requires_installed_driver || virtualhid_licensed); } @@ -591,7 +592,7 @@ namespace platf::virtualhid { const bool virtualhid_selected, const std::string_view gamepad_driver ) { - if (gamepad_driver == config::GAMEPAD_DRIVER_VIRTUALHID) { + if (gamepad_driver == config::GAMEPAD_DRIVER_VIRTUALHID || gamepad_driver == config::GAMEPAD_DRIVER_NONE) { return false; } if (gamepad_driver == config::GAMEPAD_DRIVER_VIGEMBUS || !virtualhid_selected) { diff --git a/src/platform/virtualhid_input.h b/src/platform/virtualhid_input.h index d6302b25529..967b3bbb246 100644 --- a/src/platform/virtualhid_input.h +++ b/src/platform/virtualhid_input.h @@ -106,13 +106,15 @@ namespace platf::virtualhid { * * @param runtime Runtime to probe. * @param fallback_vigem_available Whether Windows ViGEm fallback can create gamepads. - * @param virtualhid_licensed Whether an installed-driver runtime has a valid license. + * @param virtualhid_licensed Whether the broker has a valid license. + * @param require_license Whether this platform requires a broker license for gamepads. * @return Supported gamepad choices. */ std::vector supported_gamepads( lvh::Runtime *runtime, bool fallback_vigem_available = false, - bool virtualhid_licensed = true + bool virtualhid_licensed = true, + bool require_license = false ); /** diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index 5eac499ed85..54f09f5cc19 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -1274,6 +1274,11 @@ namespace platf { } const auto raw = (input_raw_t *) input->get(); + if (config::input.gamepad_driver == config::GAMEPAD_DRIVER_NONE) { + gps.clear(); + return gps; + } + if (config::input.gamepad_driver == config::GAMEPAD_DRIVER_VIGEMBUS) { gps = vigembus_supported_gamepads(raw->vigem != nullptr); return gps; @@ -1310,7 +1315,7 @@ namespace platf { platform_caps::caps_t get_capabilities() { platform_caps::caps_t caps = 0; - if (virtualhid::configured_gamepad_supports_controller_extensions()) { + if (config::input.gamepad_driver != config::GAMEPAD_DRIVER_NONE && virtualhid::configured_gamepad_supports_controller_extensions()) { caps |= platform_caps::controller_touch; } diff --git a/src/system_tray.cpp b/src/system_tray.cpp index 0a2ae33aa35..773bdac56d6 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -27,7 +27,7 @@ #define TRAY_ICON_LOCKED WEB_DIR "images/sunshine-locked.svg" /** * @def TRAY_ICON_VIRTUALHID - * @brief Path to the Virtual HID Driver notification icon. + * @brief Path to the Virtual HID Broker notification icon. */ #define TRAY_ICON_VIRTUALHID WEB_DIR "images/logo-libvirtualhid.svg" @@ -65,7 +65,7 @@ // lib includes #include #include - #ifdef _WIN32 + #if defined(_WIN32) || defined(__APPLE__) #include #endif @@ -134,9 +134,9 @@ namespace system_tray { } }; - #ifdef _WIN32 + #if defined(_WIN32) || defined(__APPLE__) /** - * @brief Access storage for dynamic Virtual HID Driver license menu labels. + * @brief Access storage for dynamic Virtual HID Broker license menu labels. * * @return Persistent string storage backing the tray menu label pointers. */ @@ -145,8 +145,9 @@ namespace system_tray { return menu_text; } + #ifdef _WIN32 /** - * @brief Access storage for the Virtual HID Driver compatibility notification. + * @brief Access storage for the Virtual HID Broker compatibility notification. * * @return Persistent string storage backing the tray notification pointer. */ @@ -156,12 +157,12 @@ namespace system_tray { } /** - * @brief Access persistent storage for the Virtual HID Driver benefits menu. + * @brief Access persistent storage for the Virtual HID Broker benefits menu. * * The tray C API requires a mutable submenu pointer even though Sunshine * treats these entries as immutable. * - * @return Persistent Virtual HID Driver benefits menu storage. + * @return Persistent Virtual HID Broker benefits menu storage. */ std::array &virtualhid_benefits_menu_storage() { static std::array benefits_menu {{ @@ -173,11 +174,12 @@ namespace system_tray { }}; return benefits_menu; } + #endif #endif } // namespace - #ifdef _WIN32 - constexpr auto LIBVIRTUALHID_RELEASES_URL = "https://github.com/LizardByte/libvirtualhid/releases/latest"sv; ///< Latest Virtual HID Driver release. + #if defined(_WIN32) || defined(__APPLE__) + constexpr auto LIBVIRTUALHID_RELEASES_URL = "https://github.com/LizardByte/libvirtualhid/releases/latest"sv; ///< Latest Virtual HID Broker release. #endif void tray_open_ui_cb([[maybe_unused]] struct tray_menu *item) { @@ -197,14 +199,18 @@ namespace system_tray { platf::open_url("https://www.paypal.com/paypalme/ReenigneArcher"); } - #ifdef _WIN32 + #if defined(_WIN32) || defined(__APPLE__) void tray_virtualhid_license_cb([[maybe_unused]] struct tray_menu *item) { - BOOST_LOG(info) << "Opening Virtual HID Driver license settings from system tray"sv; + BOOST_LOG(info) << "Opening Virtual HID Broker license settings from system tray"sv; + #ifdef _WIN32 launch_ui(config::input.gamepad_driver == config::GAMEPAD_DRIVER_VIGEMBUS ? "/config#gamepad_driver" : "/troubleshooting#virtualhid-license"); + #else + launch_ui("/troubleshooting#virtualhid-license"); + #endif } void tray_virtualhid_download_cb([[maybe_unused]] struct tray_menu *item) { - BOOST_LOG(info) << "Opening Virtual HID Driver download from system tray"sv; + BOOST_LOG(info) << "Opening Virtual HID Broker download from system tray"sv; platf::open_url(std::string {LIBVIRTUALHID_RELEASES_URL}); } #endif @@ -261,9 +267,9 @@ namespace system_tray { lifetime::exit_sunshine(0, true); } - #ifdef _WIN32 + #if defined(_WIN32) || defined(__APPLE__) /** - * @brief Create the initial Virtual HID Driver license submenu. + * @brief Create the initial Virtual HID Broker license submenu. * * @return Menu storage with a checking state, benefits, license settings, and driver download. */ @@ -272,12 +278,16 @@ namespace system_tray { menu[0] = {.text = "Status: Checking", .disabled = 1}; menu[1] = {.text = "-"}; menu[2] = {.text = "Get/Manage License", .cb = tray_virtualhid_license_cb}; - menu[3] = {.text = "Virtual HID Driver Benefits", .submenu = virtualhid_benefits_menu_storage().data()}; - menu[4] = {.text = "Download Virtual HID Driver", .cb = tray_virtualhid_download_cb}; + #ifdef _WIN32 + menu[3] = {.text = "Virtual HID Broker Benefits", .submenu = virtualhid_benefits_menu_storage().data()}; + menu[4] = {.text = "Download Virtual HID Broker", .cb = tray_virtualhid_download_cb}; + #else + menu[3] = {.text = "Download Virtual HID Broker", .cb = tray_virtualhid_download_cb}; + #endif return menu; } - static auto virtualhid_license_menu = initial_virtualhid_license_menu(); ///< Virtual HID Driver license submenu. + static auto virtualhid_license_menu = initial_virtualhid_license_menu(); ///< Virtual HID Broker license submenu. #endif // Tray menu @@ -289,8 +299,8 @@ namespace system_tray { // Tray menu labels currently use the project's English source strings. {.text = "Open Sunshine", .cb = tray_open_ui_cb}, {.text = "-"}, - #ifdef _WIN32 - {.text = "Virtual HID Driver", .submenu = virtualhid_license_menu.data()}, + #if defined(_WIN32) || defined(__APPLE__) + {.text = "Virtual HID Broker", .submenu = virtualhid_license_menu.data()}, {.text = "-"}, #endif {.text = "Donate", @@ -310,7 +320,7 @@ namespace system_tray { {.text = "Quit", .cb = tray_quit_cb}, {.text = nullptr} }, - #ifdef _WIN32 + #if defined(_WIN32) || defined(__APPLE__) .iconPathCount = 5, .allIconPaths = {TRAY_ICON, TRAY_ICON_LOCKED, TRAY_ICON_PLAYING, TRAY_ICON_PAUSING, TRAY_ICON_VIRTUALHID}, #else @@ -336,17 +346,19 @@ namespace system_tray { tray.notification_text = nullptr; tray.notification_title = nullptr; tray.notification_cb = nullptr; - #ifdef _WIN32 + #if defined(_WIN32) || defined(__APPLE__) virtualhid_license_menu_text_storage() = {}; + #ifdef _WIN32 virtualhid_driver_notification_text_storage().clear(); + #endif virtualhid_license_menu = initial_virtualhid_license_menu(); #endif } #endif - #ifdef _WIN32 + #if defined(_WIN32) || defined(__APPLE__) /** - * @brief Return the user-visible label for a Virtual HID Driver license state. + * @brief Return the user-visible label for a Virtual HID Broker license state. * * @param state License state reported by libvirtualhid. * @return Short state label suitable for a tray menu. @@ -398,7 +410,7 @@ namespace system_tray { } /** - * @brief Assign text and behavior to one Virtual HID Driver submenu item. + * @brief Assign text and behavior to one Virtual HID Broker submenu item. * * @tparam Callback Callback type accepted by the tray library. * @param index Submenu index to populate. @@ -423,7 +435,7 @@ namespace system_tray { } /** - * @brief Rebuild the Virtual HID Driver submenu for the latest license state. + * @brief Rebuild the Virtual HID Broker submenu for the latest license state. * * @param license Latest machine license details. */ @@ -452,7 +464,11 @@ namespace system_tray { ); } else { set_virtualhid_license_menu_item(1, std::string {virtualhid_license_state_detail(license.state)}, true); + #ifdef _WIN32 set_virtualhid_license_menu_item(2, "Driver-backed keyboard, mouse, and gamepads are locked", true); + #else + set_virtualhid_license_menu_item(2, "Virtual gamepads are locked", true); + #endif set_virtualhid_license_menu_item( 3, license.service_available ? "License service: Available" : "License service: Unavailable", @@ -461,9 +477,13 @@ namespace system_tray { } virtualhid_license_menu[4] = {.text = "-"}; set_virtualhid_license_menu_item(5, "Get/Manage License", false, tray_virtualhid_license_cb); - set_virtualhid_license_menu_item(6, "Virtual HID Driver Benefits", false); + #ifdef _WIN32 + set_virtualhid_license_menu_item(6, "Virtual HID Broker Benefits", false); virtualhid_license_menu[6].submenu = virtualhid_benefits_menu_storage().data(); - set_virtualhid_license_menu_item(7, "Download Virtual HID Driver", false, tray_virtualhid_download_cb); + set_virtualhid_license_menu_item(7, "Download Virtual HID Broker", false, tray_virtualhid_download_cb); + #else + set_virtualhid_license_menu_item(6, "Download Virtual HID Broker", false, tray_virtualhid_download_cb); + #endif } /** @@ -481,16 +501,17 @@ namespace system_tray { clear_tray_notification(); rebuild_virtualhid_license_menu(license); - if (config::input.gamepad_driver.empty()) { - tray.notification_title = "Choose a Gamepad Driver"; + #ifdef _WIN32 + if (config::input.controller && config::input.gamepad_driver.empty()) { + tray.notification_title = "Choose a Gamepad Backend"; tray.notification_text = - "Choose a driver in Input settings. Virtual HID Driver is a paid upgrade; ViGEmBus is limited and has reached end of life."; + "Choose a gamepad backend in Input settings. Virtual HID Broker is a paid upgrade; ViGEmBus is limited and has reached end of life."; tray.notification_icon = tray.allIconPaths[4]; tray.notification_cb = []() { launch_ui("/config#gamepad_driver"); }; - } else if (config::input.gamepad_driver != config::GAMEPAD_DRIVER_VIGEMBUS && notify_if_unlicensed && !license.licensed()) { - tray.notification_title = "Virtual HID Driver License"; + } else if (config::input.controller && config::input.gamepad_driver != config::GAMEPAD_DRIVER_NONE && config::input.gamepad_driver != config::GAMEPAD_DRIVER_VIGEMBUS && notify_if_unlicensed && !license.licensed()) { + tray.notification_title = "Virtual HID Broker License"; tray.notification_text = "Get or manage a license, or use the limited, end-of-life ViGEmBus driver."; tray.notification_icon = tray.allIconPaths[4]; @@ -498,6 +519,24 @@ namespace system_tray { launch_ui("/troubleshooting#virtualhid-license"); }; } + #else + if (config::input.controller && config::input.gamepad_driver != config::GAMEPAD_DRIVER_NONE && notify_if_unlicensed && !license.licensed()) { + if (license.service_available) { + tray.notification_title = "Virtual HID Broker License"; + tray.notification_text = "Activate a machine license to use virtual gamepads. Click to manage the license."; + tray.notification_cb = []() { + launch_ui("/troubleshooting#virtualhid-license"); + }; + } else { + tray.notification_title = "Virtual HID Broker Is Unavailable"; + tray.notification_text = "Install and start Virtual HID Broker to use virtual gamepads. Click to download."; + tray.notification_cb = []() { + tray_virtualhid_download_cb(nullptr); + }; + } + tray.notification_icon = tray.allIconPaths[4]; + } + #endif if (tray_initialized_state().load()) { tray_update(&tray); @@ -509,13 +548,14 @@ namespace system_tray { update_tray_virtualhid_license(result.license, !result.license.licensed()); } + #ifdef _WIN32 void update_tray_virtualhid_driver( const bool installed, const std::string_view version, const bool version_compatible, const std::string_view supported_versions ) { - if (config::input.gamepad_driver.empty() || config::input.gamepad_driver == config::GAMEPAD_DRIVER_VIGEMBUS || !installed || version_compatible) { + if (config::input.gamepad_driver.empty() || config::input.gamepad_driver == config::GAMEPAD_DRIVER_NONE || config::input.gamepad_driver == config::GAMEPAD_DRIVER_VIGEMBUS || !installed || version_compatible) { return; } @@ -525,11 +565,11 @@ namespace system_tray { const auto displayed_version = version.empty() ? "unknown" : std::format("v{}", version); auto ¬ification_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.", + "Installed libvirtualhid 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_title = "Update Virtual HID Broker"; tray.notification_text = notification_text.c_str(); tray.notification_icon = tray.allIconPaths[4]; tray.notification_cb = []() { @@ -551,6 +591,7 @@ namespace system_tray { status.value("supported_versions", std::string {}) ); } + #endif #endif /** diff --git a/src/system_tray.h b/src/system_tray.h index f0fa019b1fb..dfc2fe42baa 100644 --- a/src/system_tray.h +++ b/src/system_tray.h @@ -8,7 +8,7 @@ #include #include -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) namespace lvh { struct LicenseStatus; } @@ -42,15 +42,15 @@ namespace system_tray { */ void tray_donate_paypal_cb([[maybe_unused]] struct tray_menu *item); -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) /** - * @brief Callback for opening Virtual HID Driver license settings in the Web UI. + * @brief Callback for opening Virtual HID Broker license settings in the Web UI. * @param item The tray menu item. */ void tray_virtualhid_license_cb([[maybe_unused]] struct tray_menu *item); /** - * @brief Callback for opening the latest Virtual HID Driver release. + * @brief Callback for opening the latest Virtual HID Broker release. * @param item The tray menu item. */ void tray_virtualhid_download_cb([[maybe_unused]] struct tray_menu *item); @@ -115,20 +115,21 @@ namespace system_tray { */ void update_tray_require_pin(); -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) /** - * @brief Update the Virtual HID Driver license submenu and optional notification. + * @brief Update the Virtual HID Broker license submenu and optional notification. * * @param license Latest machine license details. - * @param notify_if_unlicensed Whether to notify the user when the machine is not activated and ViGEmBus is not exclusively selected. + * @param notify_if_unlicensed Whether to notify the user when the broker needs a license. */ void update_tray_virtualhid_license(const lvh::LicenseStatus &license, bool notify_if_unlicensed); /** - * @brief Query the Virtual HID Driver license and prepare the startup tray state. + * @brief Query the Virtual HID Broker license and prepare the startup tray state. */ void prepare_tray_virtualhid_license(); + #ifdef _WIN32 /** * @brief Show an update notification for an unsupported Virtual HID Driver. * @@ -151,6 +152,7 @@ namespace system_tray { * @brief Query the Virtual HID Driver version and prepare its startup notification. */ void prepare_tray_virtualhid_driver(); + #endif #endif /** diff --git a/src_assets/common/assets/web/Home.vue b/src_assets/common/assets/web/Home.vue index 125738664ef..17184217d67 100644 --- a/src_assets/common/assets/web/Home.vue +++ b/src_assets/common/assets/web/Home.vue @@ -21,7 +21,7 @@ - +
@@ -197,10 +197,12 @@ } catch (e) { console.error("Failed to fetch virtual input driver status:", e); } + } + if (this.platform === 'windows' || this.platform === 'macos') { try { this.virtualhidLicense = await fetch("./api/virtual-input/license").then((r) => r.json()); } catch (e) { - console.error("Failed to fetch Virtual HID Driver license status:", e); + console.error("Failed to fetch Virtual HID Broker license status:", e); } } } catch (e) { @@ -215,11 +217,23 @@ }, computed: { /** - * Build the single Windows virtual-input message shown on the home page. - * Warnings are reserved for an unusable selected backend, an unsupported - * installed driver, or an installed driver with an invalid license. + * Build the virtual-input message shown on the home page. + * Warn about broker or gamepad driver issues when virtual gamepads are enabled. */ virtualInputNotice() { + if (!this.controllerEnabled || this.gamepadDriver === 'none') { + return null; + } + + if (this.platform === 'macos') { + if (!this.virtualhidLicense || this.virtualhidLicense.licensed) { + return null; + } + return this.virtualhidLicense.service_available + ? this.buildVirtualInputNotice(true, 'index.virtualhid_macos_license_title', [{ key: 'index.virtualhid_macos_license_desc' }]) + : this.buildVirtualInputNotice(true, 'index.virtualhid_broker_unavailable_title', [{ key: 'index.virtualhid_macos_broker_desc' }]); + } + if (this.platform !== 'windows' || !this.virtualhid || !this.vigembus) { return null; } @@ -245,7 +259,7 @@ } if (this.gamepadDriver === 'virtualhid') { - return this.buildVirtualInputNotice(true, 'index.virtualhid_required_title', [{ key: 'index.virtualhid_required_desc' }]); + return this.buildVirtualInputNotice(true, 'index.virtualhid_broker_unavailable_title', [{ key: 'index.virtualhid_required_desc' }]); } if (this.controllerEnabled && !vigembusUsable) { @@ -294,7 +308,7 @@ }, methods: { /** - * Build a home-page notice for the current Windows virtual-input state. + * Build a home-page notice for the current virtual-input state. * * @param {boolean} warning Whether the notice represents an actionable warning. * @param {string} title Localization key for the notice title. diff --git a/src_assets/common/assets/web/Troubleshooting.vue b/src_assets/common/assets/web/Troubleshooting.vue index 1cd1001b882..8c6bab7f2ce 100644 --- a/src_assets/common/assets/web/Troubleshooting.vue +++ b/src_assets/common/assets/web/Troubleshooting.vue @@ -2,8 +2,8 @@

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

- -
+ +