diff --git a/docs/configuration.md b/docs/configuration.md
index e19ddd1544d..5d38c8a9d11 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -352,7 +352,7 @@ consent prompt for directory ACLs, so the Web UI shows setup steps for correctin
@endcode
-
Choices
+
Choices
all
Windows only: prefer Virtual HID Broker when it is available and licensed, with ViGEmBus as a limited fallback.
@@ -364,12 +364,45 @@ consent prompt for directory ACLs, so the Web UI shows setup steps for correctin
vigembus
Windows only: use ViGEmBus for gamepads and hide Virtual HID Broker status and licensing details.
+
+
esp32_bridge
+
Windows only: publish gamepads, keyboard, and mouse through an ESP32-S3 USB bridge plugged into this PC.
+ The bridge enumerates as USB devices, and Virtual HID Broker notices are not shown. It holds two devices: keyboard and mouse share one, which leaves
+ one gamepad, and only one Xbox controller fits. Sunshine selects this automatically on first start when
+ a bridge is attached and no backend has been chosen. The bridge only accepts input from paired hosts:
+ the first time Sunshine connects, press the BOOT button on the bridge to pair it. The pairing secret is
+ stored in `%ProgramData%\libvirtualhid\esp32-bridge`.
+
none
Disable virtual gamepads on Windows and macOS without changing keyboard or mouse input. Suppress broker and driver choice notices.
+### esp32_bridge_port
+
+
+
+
Description
+
+ Serial port of the ESP32-S3 USB bridge when `gamepad_driver` is `esp32_bridge`. Leave empty to find the
+ bridge automatically by its `LVHB-` USB serial number. Plug the bridge in before starting Sunshine.
+
+
+
+
Default
+
@code{}
+ empty (auto-detect)
+ @endcode
+
+
+
Example
+
@code{}
+ esp32_bridge_port = COM5
+ @endcode
+
+
+
### gamepad
diff --git a/src/config.cpp b/src/config.cpp
index 7705aa6491c..11e99d44996 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -879,6 +879,7 @@ namespace config {
true, // client gamepads with motion events use PlayStation-style emulation
true, // client gamepads with touchpads use PlayStation-style emulation
true, // virtualhid_randomize_mac
+ {}, // esp32_bridge_port: auto-detect
true, // keyboard enabled
false, // key_rightalt_to_key_win
@@ -1091,6 +1092,15 @@ namespace config {
return true;
}
+ bool select_esp32_bridge_if_detected(const bool bridge_detected) {
+ if (!bridge_detected || !input.gamepad_driver.empty() || !persist_config_option_if_missing("gamepad_driver", GAMEPAD_DRIVER_ESP32_BRIDGE)) {
+ return false;
+ }
+
+ input.gamepad_driver = GAMEPAD_DRIVER_ESP32_BRIDGE;
+ return true;
+ }
+
/**
* @brief Consume a string setting from the parsed configuration map.
*
@@ -1862,6 +1872,7 @@ namespace config {
GAMEPAD_DRIVER_ALL,
GAMEPAD_DRIVER_VIRTUALHID,
GAMEPAD_DRIVER_VIGEMBUS,
+ GAMEPAD_DRIVER_ESP32_BRIDGE,
GAMEPAD_DRIVER_NONE,
});
string_restricted_f(vars, "gamepad"s, input.gamepad, get_supported_gamepad_options());
@@ -1875,6 +1886,7 @@ namespace config {
bool_f(vars, "motion_as_ds4", input.motion_as_ds4);
bool_f(vars, "touchpad_as_ds4", input.touchpad_as_ds4);
bool_f(vars, "virtualhid_randomize_mac", input.virtualhid_randomize_mac);
+ string_f(vars, "esp32_bridge_port", input.esp32_bridge_port);
bool_f(vars, "mouse", input.mouse);
bool_f(vars, "keyboard", input.keyboard);
diff --git a/src/config.h b/src/config.h
index d13ddfd4e1e..a95b060d86f 100644
--- a/src/config.h
+++ b/src/config.h
@@ -42,6 +42,7 @@ namespace config {
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"; ///< 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_ESP32_BRIDGE = "esp32_bridge"; ///< Publish gamepads, keyboard, and mouse through an ESP32-S3 USB bridge.
inline constexpr std::string_view GAMEPAD_DRIVER_NONE = "none"; ///< Disable virtual gamepads on Windows and macOS.
// track modified config options
@@ -308,6 +309,7 @@ namespace config {
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.
bool virtualhid_randomize_mac; ///< Randomize the libvirtualhid virtual controller MAC address.
+ std::string esp32_bridge_port; ///< ESP32-S3 bridge serial port such as `COM5`; empty auto-detects.
bool keyboard; ///< Enable keyboard input from clients.
bool key_rightalt_to_key_win; ///< Map the client Right Alt key to the Windows key.
@@ -458,4 +460,12 @@ namespace config {
* @return True when the `all` preference was persisted and applied; otherwise, false.
*/
bool select_all_gamepad_drivers_if_licensed(bool virtualhid_licensed);
+
+ /**
+ * @brief Select the ESP32-S3 USB bridge when one is attached and the user has not made a choice.
+ *
+ * @param bridge_detected Whether an ESP32-S3 bridge is attached.
+ * @return True when the `esp32_bridge` preference was persisted and applied; otherwise, false.
+ */
+ bool select_esp32_bridge_if_detected(bool bridge_detected);
} // namespace config
diff --git a/src/confighttp.cpp b/src/confighttp.cpp
index edf1c5758a1..148817f8c31 100644
--- a/src/confighttp.cpp
+++ b/src/confighttp.cpp
@@ -36,6 +36,7 @@
#include "platform/windows/misc.h"
#include "platform/windows/utf_utils.h"
+ #include
#include
#elif defined(__APPLE__)
#include "platform/macos/misc.h"
@@ -2107,16 +2108,22 @@ namespace confighttp {
std::string backend_name;
std::string runtime_error_message;
- try {
- const auto runtime = platf::virtualhid::create_runtime();
- if (runtime) {
- 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, minimum_version);
+ if (platf::virtualhid::esp32_bridge_selected()) {
+ // The input context owns the bridge port; report it without opening a second runtime.
+ backend_name = "esp32_bridge";
+ requires_installed_driver = false;
+ } else {
+ try {
+ const auto runtime = platf::virtualhid::create_runtime();
+ if (runtime) {
+ 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, minimum_version);
+ }
+ } catch (const std::bad_alloc &exception) {
+ runtime_error_message = exception.what();
}
- } catch (const std::bad_alloc &exception) {
- runtime_error_message = exception.what();
}
output_tree["backend_name"] = backend_name;
@@ -2183,6 +2190,47 @@ namespace confighttp {
send_response(response, output_tree);
}
+ /**
+ * @brief Get the connection and pairing state of the ESP32-S3 USB bridge.
+ *
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ *
+ * @api_examples{/api/esp32-bridge/status|:| GET|:| null}
+ */
+ void getEsp32BridgeStatus(const resp_https_t &response, const req_https_t &request) {
+ if (!authenticate(response, request)) {
+ return;
+ }
+
+ print_req(request);
+
+ nlohmann::json output_tree;
+ output_tree["selected"] = config::input.gamepad_driver == config::GAMEPAD_DRIVER_ESP32_BRIDGE;
+#ifdef _WIN32
+ const auto status = lvh::esp32_bridge_status();
+ output_tree["active"] = status.active;
+ output_tree["connected"] = status.connected;
+ output_tree["awaiting_pairing"] = status.awaiting_pairing;
+ output_tree["pairing_required"] = status.pairing_required;
+ output_tree["port"] = status.port;
+ output_tree["device_id"] = status.device_id;
+ output_tree["firmware_version"] = status.firmware_version;
+ // Discovery reads USB serial numbers only; it never opens a port.
+ output_tree["detected"] = !lvh::find_esp32_bridge_ports().empty();
+#else
+ output_tree["active"] = false;
+ output_tree["connected"] = false;
+ output_tree["awaiting_pairing"] = false;
+ output_tree["pairing_required"] = false;
+ output_tree["port"] = "";
+ output_tree["device_id"] = "";
+ output_tree["firmware_version"] = "";
+ output_tree["detected"] = false;
+#endif
+ send_response(response, output_tree);
+ }
+
/**
* @brief Get the current libvirtualhid machine license status.
*
@@ -2511,6 +2559,7 @@ namespace confighttp {
server.resource["^/api/virtual-input/license$"]["GET"] = getVirtualInputLicense;
server.resource["^/api/virtual-input/license$"]["POST"] = updateVirtualInputLicense;
server.resource["^/api/virtual-input/status$"]["GET"] = getVirtualInputStatus;
+ server.resource["^/api/esp32-bridge/status$"]["GET"] = getEsp32BridgeStatus;
// static/dynamic resources
server.resource["^/images/sunshine.ico$"]["GET"] = getFaviconImage;
diff --git a/src/confighttp.h b/src/confighttp.h
index 1b8e9b2ff56..2a452f80d9d 100644
--- a/src/confighttp.h
+++ b/src/confighttp.h
@@ -174,6 +174,14 @@ namespace confighttp {
void getVirtualInputStatus(const resp_https_t &response, const req_https_t &request);
+ /**
+ * @brief Get the connection and pairing state of the ESP32-S3 USB bridge.
+ *
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ */
+ void getEsp32BridgeStatus(const resp_https_t &response, const req_https_t &request);
+
/**
* @brief Return the host's permission statuses to the Web UI.
*
diff --git a/src/main.cpp b/src/main.cpp
index 841e5c8c4c0..6cc662994d9 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -28,6 +28,7 @@
// lib includes
#include
#ifdef _WIN32
+ #include
#include
#endif
@@ -285,6 +286,8 @@ int main(int argc, char *argv[]) {
}
#ifdef _WIN32
+ // Check for an attached bridge before applying the default selection.
+ config::select_esp32_bridge_if_detected(!lvh::find_esp32_bridge_ports().empty());
config::select_all_gamepad_drivers_if_licensed(lvh::get_license_status().license.licensed());
#endif
diff --git a/src/platform/linux/input/virtualhid.cpp b/src/platform/linux/input/virtualhid.cpp
index 88395ab45b8..fde3241d18a 100644
--- a/src/platform/linux/input/virtualhid.cpp
+++ b/src/platform/linux/input/virtualhid.cpp
@@ -20,6 +20,13 @@ using namespace std::literals;
namespace platf {
platform_caps::caps_t get_capabilities() {
platform_caps::caps_t caps = 0;
+ // The bridge port is owned by the input context and has no pen or touchscreen slot.
+ if (virtualhid::esp32_bridge_selected()) {
+ if (virtualhid::configured_gamepad_supports_controller_extensions()) {
+ caps |= platform_caps::controller_touch;
+ }
+ return caps;
+ }
const auto runtime = virtualhid::create_runtime();
if (!runtime) {
return caps;
diff --git a/src/platform/macos/input.cpp b/src/platform/macos/input.cpp
index dbc19cb27c8..dd2249af567 100644
--- a/src/platform/macos/input.cpp
+++ b/src/platform/macos/input.cpp
@@ -34,6 +34,13 @@ namespace platf {
platform_caps::caps_t get_capabilities() {
platform_caps::caps_t caps = 0;
+ // The bridge port is owned by the input context and has no pen or touchscreen slot.
+ if (virtualhid::esp32_bridge_selected()) {
+ if (virtualhid::configured_gamepad_supports_controller_extensions()) {
+ caps |= platform_caps::controller_touch;
+ }
+ return caps;
+ }
const auto runtime = virtualhid::create_runtime();
if (!runtime) {
return caps;
diff --git a/src/platform/virtualhid_input.cpp b/src/platform/virtualhid_input.cpp
index 7e255df5231..4de8dcc217c 100644
--- a/src/platform/virtualhid_input.cpp
+++ b/src/platform/virtualhid_input.cpp
@@ -511,7 +511,7 @@ namespace platf::virtualhid {
} // namespace
input_context_t::input_context_t():
- input_context_t {lvh::BackendKind::platform_default} {}
+ input_context_t {configured_backend()} {}
input_context_t::input_context_t(lvh::BackendKind backend):
runtime {create_runtime(backend)} {
@@ -606,10 +606,22 @@ namespace platf::virtualhid {
}
}
+ bool esp32_bridge_selected() {
+ return config::input.gamepad_driver == config::GAMEPAD_DRIVER_ESP32_BRIDGE;
+ }
+
+ lvh::BackendKind configured_backend() {
+ return esp32_bridge_selected() ? lvh::BackendKind::esp32_bridge : lvh::BackendKind::platform_default;
+ }
+
std::unique_ptr create_runtime(lvh::BackendKind backend) {
lvh::RuntimeOptions options;
options.backend = backend;
options.log_callback = log_virtualhid_message;
+ options.esp32_bridge.port = config::input.esp32_bridge_port;
+ options.esp32_bridge.client_name = "Sunshine";
+ // The bridge is an explicit gamepad_driver choice; never switch to it behind another choice.
+ options.esp32_bridge.autodetect = false;
return lvh::Runtime::create(options);
}
@@ -677,7 +689,7 @@ namespace platf::virtualhid {
const bool virtualhid_selected,
const std::string_view gamepad_driver
) {
- if (gamepad_driver == config::GAMEPAD_DRIVER_VIRTUALHID || gamepad_driver == config::GAMEPAD_DRIVER_NONE) {
+ if (gamepad_driver == config::GAMEPAD_DRIVER_VIRTUALHID || gamepad_driver == config::GAMEPAD_DRIVER_ESP32_BRIDGE || 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 47431963bae..4249439ed31 100644
--- a/src/platform/virtualhid_input.h
+++ b/src/platform/virtualhid_input.h
@@ -103,6 +103,23 @@ namespace platf::virtualhid {
*/
client_context_t &get_client_context(client_input_t *input);
+ /**
+ * @brief Check whether `gamepad_driver = esp32_bridge` is selected.
+ *
+ * The bridge serial port can only be opened once, so in this mode the shared
+ * input context owns the only runtime and capability probes must not create another.
+ *
+ * @return True when the ESP32-S3 bridge carries all virtual input.
+ */
+ bool esp32_bridge_selected();
+
+ /**
+ * @brief Return the backend for the configured gamepad driver.
+ *
+ * @return `esp32_bridge` when the bridge is selected, otherwise `platform_default`.
+ */
+ lvh::BackendKind configured_backend();
+
/**
* @brief Create a platform-default libvirtualhid runtime.
*
diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp
index 73f2acf0383..5b58097e8e1 100644
--- a/src/platform/windows/input.cpp
+++ b/src/platform/windows/input.cpp
@@ -604,7 +604,9 @@ namespace platf {
*/
bool should_allocate_vigembus_gamepad(const bool virtualhid_selected) {
if (!virtualhid::should_try_vigembus_fallback(config::input.gamepad, virtualhid_selected, config::input.gamepad_driver)) {
- if (config::input.gamepad_driver == config::GAMEPAD_DRIVER_VIRTUALHID && !virtualhid_selected) {
+ if (config::input.gamepad_driver == config::GAMEPAD_DRIVER_ESP32_BRIDGE && !virtualhid_selected) {
+ BOOST_LOG(warning) << "ESP32-S3 USB bridge is selected but is not connected or has no free slot; ViGEmBus fallback is disabled by configuration"sv;
+ } else if (config::input.gamepad_driver == config::GAMEPAD_DRIVER_VIRTUALHID && !virtualhid_selected) {
BOOST_LOG(warning) << "Virtual HID Driver is selected but is unavailable or unlicensed; ViGEmBus fallback is disabled by configuration"sv;
} else {
BOOST_LOG(warning) << "libvirtualhid could not create the requested gamepad profile, and ViGEm fallback cannot emulate "sv << config::input.gamepad;
@@ -1300,11 +1302,11 @@ namespace platf {
}
if (!raw->virtualhid.runtime) {
- if (config::input.gamepad_driver == config::GAMEPAD_DRIVER_VIRTUALHID) {
+ if (config::input.gamepad_driver == config::GAMEPAD_DRIVER_VIRTUALHID || config::input.gamepad_driver == config::GAMEPAD_DRIVER_ESP32_BRIDGE) {
gps = virtualhid::static_supported_gamepads();
for (auto &gamepad : gps) {
gamepad.is_enabled = false;
- gamepad.reason_disabled = "gamepads.virtualhid-not-available";
+ gamepad.reason_disabled = config::input.gamepad_driver == config::GAMEPAD_DRIVER_ESP32_BRIDGE ? "gamepads.esp32-bridge-not-available" : "gamepads.virtualhid-not-available";
}
} else {
gps = vigembus_supported_gamepads(raw->vigem != nullptr);
@@ -1314,7 +1316,7 @@ namespace platf {
const auto &capabilities = raw->virtualhid.runtime->capabilities();
const auto licensed = !capabilities.requires_installed_driver || lvh::get_license_status().license.licensed();
- if (const auto use_virtualhid = virtualhid::should_use_gamepad_runtime(capabilities, config::input.gamepad_driver, licensed); !use_virtualhid && config::input.gamepad_driver != config::GAMEPAD_DRIVER_VIRTUALHID) {
+ if (const auto use_virtualhid = virtualhid::should_use_gamepad_runtime(capabilities, config::input.gamepad_driver, licensed); !use_virtualhid && config::input.gamepad_driver != config::GAMEPAD_DRIVER_VIRTUALHID && config::input.gamepad_driver != config::GAMEPAD_DRIVER_ESP32_BRIDGE) {
gps = vigembus_supported_gamepads(raw->vigem != nullptr);
return gps;
}
@@ -1334,7 +1336,8 @@ namespace platf {
caps |= platform_caps::controller_touch;
}
- if (config::input.native_pen_touch) {
+ // The bridge has no pen or touchscreen slot; clients fall back to mouse input.
+ if (config::input.native_pen_touch && !virtualhid::esp32_bridge_selected()) {
const auto runtime = virtualhid::create_runtime();
if (runtime) {
const auto &capabilities = runtime->capabilities();
diff --git a/src/system_tray.cpp b/src/system_tray.cpp
index b553d4f4813..7313bc3344d 100644
--- a/src/system_tray.cpp
+++ b/src/system_tray.cpp
@@ -509,7 +509,7 @@ namespace system_tray {
tray.notification_cb = []() {
launch_ui("/config#gamepad_driver");
};
- } 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()) {
+ } else if (config::input.controller && config::input.gamepad_driver != config::GAMEPAD_DRIVER_NONE && config::input.gamepad_driver != config::GAMEPAD_DRIVER_VIGEMBUS && config::input.gamepad_driver != config::GAMEPAD_DRIVER_ESP32_BRIDGE && 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.";
@@ -557,7 +557,7 @@ namespace system_tray {
return;
}
#ifdef _WIN32
- if (config::input.gamepad_driver.empty() || config::input.gamepad_driver == config::GAMEPAD_DRIVER_VIGEMBUS) {
+ if (config::input.gamepad_driver.empty() || config::input.gamepad_driver == config::GAMEPAD_DRIVER_VIGEMBUS || config::input.gamepad_driver == config::GAMEPAD_DRIVER_ESP32_BRIDGE) {
return;
}
#endif
diff --git a/src_assets/common/assets/web/Home.vue b/src_assets/common/assets/web/Home.vue
index 261e3c6ac62..1404b7beba8 100644
--- a/src_assets/common/assets/web/Home.vue
+++ b/src_assets/common/assets/web/Home.vue
@@ -183,6 +183,7 @@
virtualhid: null,
virtualhidLicense: null,
vigembus: null,
+ esp32Bridge: null,
permissions: [],
}
},
@@ -206,6 +207,11 @@
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)
+ if (this.platform === 'windows' && this.gamepadDriver === 'esp32_bridge') {
+ this.refreshEsp32Bridge();
+ // Poll so the pairing notice clears once BOOT is pressed.
+ this.esp32BridgeInterval = setInterval(() => this.refreshEsp32Bridge(), 3000);
+ }
// Read the broker version on both platforms to identify development builds.
if (this.platform === 'windows' || this.platform === 'macos') {
try {
@@ -235,6 +241,9 @@
}
this.loading = false;
},
+ beforeUnmount() {
+ clearInterval(this.esp32BridgeInterval);
+ },
computed: {
/** Return required permissions that the current platform can check and has not granted. */
missingPermissions() {
@@ -253,6 +262,10 @@
return this.buildMacosVirtualInputNotice();
}
+ if (this.platform === 'windows' && this.gamepadDriver === 'esp32_bridge') {
+ return this.buildEsp32BridgeNotice();
+ }
+
if (this.platform !== 'windows' || !this.virtualhid || !this.vigembus) {
return null;
}
@@ -273,6 +286,11 @@
return this.buildVigembusNotice(vigembusUsable);
}
+ if (this.gamepadDriver === 'esp32_bridge') {
+ // Virtual HID Broker notices do not apply to the bridge.
+ return null;
+ }
+
if (this.virtualhid.installed) {
return this.buildInstalledVirtualhidNotice(vigembusUsable);
}
@@ -350,6 +368,30 @@
* @param {object} options Optional action and destination overrides.
* @returns {object} Notice data consumed by the template.
*/
+ /**
+ * Build the notice for the ESP32-S3 bridge: pairing pending or not connected.
+ *
+ * @returns {object|null} Warning data, or no notice while the bridge is connected.
+ */
+ buildEsp32BridgeNotice() {
+ const bridge = this.esp32Bridge;
+ if (!bridge || bridge.connected) {
+ return null;
+ }
+ if (bridge.awaiting_pairing) {
+ return this.buildVirtualInputNotice(true, 'index.esp32_bridge_pairing_title', [{ key: 'index.esp32_bridge_pairing_desc' }]);
+ }
+ return this.buildVirtualInputNotice(true, 'index.esp32_bridge_disconnected_title', [{
+ key: bridge.active ? 'index.esp32_bridge_disconnected_desc' : 'index.esp32_bridge_missing_desc',
+ }]);
+ },
+ async refreshEsp32Bridge() {
+ try {
+ this.esp32Bridge = await fetch("./api/esp32-bridge/status").then((r) => r.json());
+ } catch (e) {
+ console.error("Failed to fetch ESP32-S3 bridge status:", e);
+ }
+ },
buildVirtualInputNotice(warning, title, messages, options = {}) {
return {
action: options.action || 'index.review_virtual_input',
diff --git a/src_assets/common/assets/web/Troubleshooting.vue b/src_assets/common/assets/web/Troubleshooting.vue
index b1ae7ba0b7e..8c469d5e8db 100644
--- a/src_assets/common/assets/web/Troubleshooting.vue
+++ b/src_assets/common/assets/web/Troubleshooting.vue
@@ -131,6 +131,50 @@
+
+