feat(windows): add ViGEmBus driver management API and UI integration (#4625)
Introduces backend API endpoints for ViGEmBus status and installation, updates Windows build scripts to handle ViGEmBus versioning and installer download, and integrates ViGEmBus status and installation controls into the web UI. Removes legacy PowerShell scripts for gamepad driver management and related NSIS installer commands.
This commit is contained in:
parent
3a12f96a86
commit
7e286b90b6
15 changed files with 360 additions and 72 deletions
|
|
@ -48,6 +48,11 @@ set_target_properties(sunshine_rc_object PROPERTIES
|
||||||
INCLUDE_DIRECTORIES ""
|
INCLUDE_DIRECTORIES ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ViGEmBus version
|
||||||
|
set(VIGEMBUS_PACKAGED_V "1.21.442")
|
||||||
|
set(VIGEMBUS_PACKAGED_V_2 "${VIGEMBUS_PACKAGED_V}.0")
|
||||||
|
list(APPEND SUNSHINE_DEFINITIONS VIGEMBUS_PACKAGED_VERSION="${VIGEMBUS_PACKAGED_V_2}")
|
||||||
|
|
||||||
set(PLATFORM_TARGET_FILES
|
set(PLATFORM_TARGET_FILES
|
||||||
"${CMAKE_SOURCE_DIR}/src/platform/windows/publish.cpp"
|
"${CMAKE_SOURCE_DIR}/src/platform/windows/publish.cpp"
|
||||||
"${CMAKE_SOURCE_DIR}/src/platform/windows/misc.h"
|
"${CMAKE_SOURCE_DIR}/src/platform/windows/misc.h"
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,11 @@ install(TARGETS sunshine RUNTIME DESTINATION "." COMPONENT application)
|
||||||
install(FILES "${ZLIB}" DESTINATION "." COMPONENT application)
|
install(FILES "${ZLIB}" DESTINATION "." COMPONENT application)
|
||||||
|
|
||||||
# ViGEmBus installer
|
# ViGEmBus installer
|
||||||
set(VIGEMBUS_INSTALLER "${CMAKE_BINARY_DIR}/vigembus_installer.exe")
|
set(VIGEMBUS_INSTALLER "${CMAKE_BINARY_DIR}/scripts/vigembus_installer.exe")
|
||||||
|
set(VIGEMBUS_DOWNLOAD_URL_1 "https://github.com/nefarius/ViGEmBus/releases/download")
|
||||||
|
set(VIGEMBUS_DOWNLOAD_URL_2 "v${VIGEMBUS_PACKAGED_V_2}/ViGEmBus_${VIGEMBUS_PACKAGED_V}_x64_x86_arm64.exe")
|
||||||
file(DOWNLOAD
|
file(DOWNLOAD
|
||||||
"https://github.com/nefarius/ViGEmBus/releases/download/v1.21.442.0/ViGEmBus_1.21.442_x64_x86_arm64.exe"
|
"${VIGEMBUS_DOWNLOAD_URL_1}/${VIGEMBUS_DOWNLOAD_URL_2}"
|
||||||
${VIGEMBUS_INSTALLER}
|
${VIGEMBUS_INSTALLER}
|
||||||
SHOW_PROGRESS
|
SHOW_PROGRESS
|
||||||
EXPECTED_HASH SHA256=155c50f1eec07bdc28d2f61a3e3c2c6c132fee7328412de224695f89143316bc
|
EXPECTED_HASH SHA256=155c50f1eec07bdc28d2f61a3e3c2c6c132fee7328412de224695f89143316bc
|
||||||
|
|
@ -45,9 +47,6 @@ install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/autostart/"
|
||||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/firewall/"
|
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/firewall/"
|
||||||
DESTINATION "scripts"
|
DESTINATION "scripts"
|
||||||
COMPONENT firewall)
|
COMPONENT firewall)
|
||||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/gamepad/"
|
|
||||||
DESTINATION "scripts"
|
|
||||||
COMPONENT gamepad)
|
|
||||||
|
|
||||||
# Sunshine assets
|
# Sunshine assets
|
||||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/assets/"
|
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/assets/"
|
||||||
|
|
@ -107,7 +106,7 @@ set(CPACK_COMPONENT_FIREWALL_GROUP "Scripts")
|
||||||
|
|
||||||
# gamepad scripts
|
# gamepad scripts
|
||||||
set(CPACK_COMPONENT_GAMEPAD_DISPLAY_NAME "Virtual Gamepad")
|
set(CPACK_COMPONENT_GAMEPAD_DISPLAY_NAME "Virtual Gamepad")
|
||||||
set(CPACK_COMPONENT_GAMEPAD_DESCRIPTION "Scripts to install and uninstall Virtual Gamepad.")
|
set(CPACK_COMPONENT_GAMEPAD_DESCRIPTION "ViGEmBus installer for virtual gamepad support.")
|
||||||
set(CPACK_COMPONENT_GAMEPAD_GROUP "Scripts")
|
set(CPACK_COMPONENT_GAMEPAD_GROUP "Scripts")
|
||||||
|
|
||||||
# include specific packaging
|
# include specific packaging
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@ SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS
|
||||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\update-path.bat\\\" add'
|
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\update-path.bat\\\" add'
|
||||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\migrate-config.bat\\\"'
|
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\migrate-config.bat\\\"'
|
||||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\add-firewall-rule.bat\\\"'
|
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\add-firewall-rule.bat\\\"'
|
||||||
nsExec::ExecToLog \
|
|
||||||
'powershell.exe -NoProfile -ExecutionPolicy Bypass -File \\\"$INSTDIR\\\\scripts\\\\install-gamepad.ps1\\\"'
|
|
||||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\install-service.bat\\\"'
|
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\install-service.bat\\\"'
|
||||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\autostart-service.bat\\\"'
|
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\autostart-service.bat\\\"'
|
||||||
NoController:
|
NoController:
|
||||||
|
|
@ -29,14 +27,6 @@ set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS
|
||||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\delete-firewall-rule.bat\\\"'
|
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\delete-firewall-rule.bat\\\"'
|
||||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\uninstall-service.bat\\\"'
|
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\uninstall-service.bat\\\"'
|
||||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe\\\" --restore-nvprefs-undo'
|
nsExec::ExecToLog '\\\"$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe\\\" --restore-nvprefs-undo'
|
||||||
MessageBox MB_YESNO|MB_ICONQUESTION \
|
|
||||||
'Do you want to remove Virtual Gamepad?' \
|
|
||||||
/SD IDNO IDNO NoGamepad
|
|
||||||
nsExec::ExecToLog \
|
|
||||||
'powershell.exe -NoProfile -ExecutionPolicy Bypass -File \
|
|
||||||
\\\"$INSTDIR\\\\scripts\\\\uninstall-gamepad.ps1\\\"'; \
|
|
||||||
skipped if no
|
|
||||||
NoGamepad:
|
|
||||||
MessageBox MB_YESNO|MB_ICONQUESTION \
|
MessageBox MB_YESNO|MB_ICONQUESTION \
|
||||||
'Do you want to remove $INSTDIR (this includes the configuration, cover images, and settings)?' \
|
'Do you want to remove $INSTDIR (this includes the configuration, cover images, and settings)?' \
|
||||||
/SD IDNO IDNO NoDelete
|
/SD IDNO IDNO NoDelete
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,5 @@ find_library(ZLIB ZLIB1)
|
||||||
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
|
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
|
||||||
$<TARGET_OBJECTS:sunshine_rc_object>
|
$<TARGET_OBJECTS:sunshine_rc_object>
|
||||||
Windowsapp.lib
|
Windowsapp.lib
|
||||||
Wtsapi32.lib)
|
Wtsapi32.lib
|
||||||
|
version.lib)
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,12 @@ basic authentication with the admin username and password.
|
||||||
## POST /api/restart
|
## POST /api/restart
|
||||||
@copydoc confighttp::restart()
|
@copydoc confighttp::restart()
|
||||||
|
|
||||||
|
## GET /api/vigembus/status
|
||||||
|
@copydoc confighttp::getViGEmBusStatus()
|
||||||
|
|
||||||
|
## POST /api/vigembus/install
|
||||||
|
@copydoc confighttp::installViGEmBus()
|
||||||
|
|
||||||
<div class="section_buttons">
|
<div class="section_buttons">
|
||||||
|
|
||||||
| Previous | Next |
|
| Previous | Next |
|
||||||
|
|
|
||||||
|
|
@ -369,21 +369,7 @@ overflow menu. Different versions of Windows may provide slightly different step
|
||||||
scripts/delete-firewall-rule.bat
|
scripts/delete-firewall-rule.bat
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Virtual Gamepad Support
|
4. Windows service
|
||||||
|
|
||||||
Install:
|
|
||||||
```bash
|
|
||||||
cd /d {path to extracted directory}
|
|
||||||
scripts/install-gamepad.bat
|
|
||||||
```
|
|
||||||
|
|
||||||
Uninstall:
|
|
||||||
```bash
|
|
||||||
cd /d {path to extracted directory}
|
|
||||||
scripts/uninstall-gamepad.bat
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Windows service
|
|
||||||
|
|
||||||
Install:
|
Install:
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -465,6 +451,11 @@ Sunshine can only access microphones on macOS due to system limitations. To stre
|
||||||
> [!CAUTION]
|
> [!CAUTION]
|
||||||
> Gamepads are not currently supported.
|
> Gamepads are not currently supported.
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
In order for virtual gamepads to work, you must install ViGEmBus. You can do this from the troubleshooting tab
|
||||||
|
in the web UI, as long as you are running Sunshine as a service or as an administrator. After installation, it is
|
||||||
|
recommended to restart your computer.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Basic usage
|
### Basic usage
|
||||||
|
|
|
||||||
|
|
@ -223,7 +223,12 @@ launchctl load -w /Library/LaunchAgents/org.freedesktop.dbus-session.plist
|
||||||
## Windows
|
## Windows
|
||||||
|
|
||||||
### No gamepad detected
|
### No gamepad detected
|
||||||
Verify that you've installed [Nefarius Virtual Gamepad](https://github.com/nefarius/ViGEmBus/releases/latest).
|
You must install ViGEmBus to use virtual gamepads. You can install this from the troubleshooting tab of the web UI.
|
||||||
|
|
||||||
|
Alternatively, you can manually install it from
|
||||||
|
[ViGEmBus releases](https://github.com/nefarius/ViGEmBus/releases/latest). You must use version 1.17 or newer.
|
||||||
|
|
||||||
|
After installation, it is recommended to restart your computer.
|
||||||
|
|
||||||
### Permission denied
|
### Permission denied
|
||||||
Since Sunshine runs as a service on Windows, it may not have the same level of access that your regular user account
|
Since Sunshine runs as a service on Windows, it may not have the same level of access that your regular user account
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,13 @@
|
||||||
#include <Simple-Web-Server/crypto.hpp>
|
#include <Simple-Web-Server/crypto.hpp>
|
||||||
#include <Simple-Web-Server/server_https.hpp>
|
#include <Simple-Web-Server/server_https.hpp>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include "platform/windows/misc.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
// local includes
|
// local includes
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "confighttp.h"
|
#include "confighttp.h"
|
||||||
|
|
@ -1171,6 +1178,132 @@ namespace confighttp {
|
||||||
platf::restart();
|
platf::restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get ViGEmBus driver version and installation status.
|
||||||
|
* @param response The HTTP response object.
|
||||||
|
* @param request The HTTP request object.
|
||||||
|
*
|
||||||
|
* @api_examples{/api/vigembus/status| GET| null}
|
||||||
|
*/
|
||||||
|
void getViGEmBusStatus(resp_https_t response, req_https_t request) {
|
||||||
|
if (!authenticate(response, request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
print_req(request);
|
||||||
|
|
||||||
|
nlohmann::json output_tree;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::string version_str;
|
||||||
|
bool installed = false;
|
||||||
|
bool version_compatible = false;
|
||||||
|
|
||||||
|
// Check if ViGEmBus driver exists
|
||||||
|
std::filesystem::path driver_path = std::filesystem::path(std::getenv("SystemRoot") ? std::getenv("SystemRoot") : "C:\\Windows") / "System32" / "drivers" / "ViGEmBus.sys";
|
||||||
|
|
||||||
|
if (std::filesystem::exists(driver_path)) {
|
||||||
|
installed = platf::getFileVersionInfo(driver_path, version_str);
|
||||||
|
if (installed) {
|
||||||
|
// Parse version string to check compatibility (>= 1.17.0.0)
|
||||||
|
std::vector<std::string> version_parts;
|
||||||
|
std::stringstream ss(version_str);
|
||||||
|
std::string part;
|
||||||
|
while (std::getline(ss, part, '.')) {
|
||||||
|
version_parts.push_back(part);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version_parts.size() >= 2) {
|
||||||
|
int major = std::stoi(version_parts[0]);
|
||||||
|
int minor = std::stoi(version_parts[1]);
|
||||||
|
version_compatible = (major > 1) || (major == 1 && minor >= 17);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output_tree["installed"] = installed;
|
||||||
|
output_tree["version"] = version_str;
|
||||||
|
output_tree["version_compatible"] = version_compatible;
|
||||||
|
output_tree["packaged_version"] = VIGEMBUS_PACKAGED_VERSION;
|
||||||
|
#else
|
||||||
|
output_tree["error"] = "ViGEmBus is only available on Windows";
|
||||||
|
output_tree["installed"] = false;
|
||||||
|
output_tree["version"] = "";
|
||||||
|
output_tree["version_compatible"] = false;
|
||||||
|
output_tree["packaged_version"] = "";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
send_response(response, output_tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Install ViGEmBus driver with elevated permissions.
|
||||||
|
* @param response The HTTP response object.
|
||||||
|
* @param request The HTTP request object.
|
||||||
|
*
|
||||||
|
* @api_examples{/api/vigembus/install| POST| null}
|
||||||
|
*/
|
||||||
|
void installViGEmBus(resp_https_t response, req_https_t request) {
|
||||||
|
if (!check_content_type(response, request, "application/json")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!authenticate(response, request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
print_req(request);
|
||||||
|
|
||||||
|
nlohmann::json output_tree;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Get the path to the vigembus installer
|
||||||
|
const std::filesystem::path installer_path = platf::appdata().parent_path() / "scripts" / "vigembus_installer.exe";
|
||||||
|
|
||||||
|
if (!std::filesystem::exists(installer_path)) {
|
||||||
|
output_tree["status"] = false;
|
||||||
|
output_tree["error"] = "ViGEmBus installer not found";
|
||||||
|
send_response(response, output_tree);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the installer with elevated permissions
|
||||||
|
std::error_code ec;
|
||||||
|
boost::filesystem::path working_dir = boost::filesystem::path(installer_path.string()).parent_path();
|
||||||
|
boost::process::v1::environment env = boost::this_process::environment();
|
||||||
|
|
||||||
|
// Run with elevated permissions, non-interactive
|
||||||
|
const std::string install_cmd = std::format("{} /quiet", installer_path.string());
|
||||||
|
auto child = platf::run_command(true, false, install_cmd, working_dir, env, nullptr, ec, nullptr);
|
||||||
|
|
||||||
|
if (ec) {
|
||||||
|
output_tree["status"] = false;
|
||||||
|
output_tree["error"] = "Failed to start installer: " + ec.message();
|
||||||
|
send_response(response, output_tree);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the installer to complete
|
||||||
|
child.wait(ec);
|
||||||
|
|
||||||
|
if (ec) {
|
||||||
|
output_tree["status"] = false;
|
||||||
|
output_tree["error"] = "Installer failed: " + ec.message();
|
||||||
|
} else {
|
||||||
|
int exit_code = child.exit_code();
|
||||||
|
output_tree["status"] = (exit_code == 0);
|
||||||
|
output_tree["exit_code"] = exit_code;
|
||||||
|
if (exit_code != 0) {
|
||||||
|
output_tree["error"] = std::format("Installer exited with code {}", exit_code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
output_tree["status"] = false;
|
||||||
|
output_tree["error"] = "ViGEmBus installation is only available on Windows";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
send_response(response, output_tree);
|
||||||
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
platf::set_thread_name("confighttp");
|
platf::set_thread_name("confighttp");
|
||||||
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
|
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
|
||||||
|
|
@ -1209,6 +1342,8 @@ namespace confighttp {
|
||||||
server.resource["^/api/configLocale$"]["GET"] = getLocale;
|
server.resource["^/api/configLocale$"]["GET"] = getLocale;
|
||||||
server.resource["^/api/restart$"]["POST"] = restart;
|
server.resource["^/api/restart$"]["POST"] = restart;
|
||||||
server.resource["^/api/reset-display-device-persistence$"]["POST"] = resetDisplayDevicePersistence;
|
server.resource["^/api/reset-display-device-persistence$"]["POST"] = resetDisplayDevicePersistence;
|
||||||
|
server.resource["^/api/vigembus/status$"]["GET"] = getViGEmBusStatus;
|
||||||
|
server.resource["^/api/vigembus/install$"]["POST"] = installViGEmBus;
|
||||||
server.resource["^/api/password$"]["POST"] = savePassword;
|
server.resource["^/api/password$"]["POST"] = savePassword;
|
||||||
server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp;
|
server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp;
|
||||||
server.resource["^/api/clients/unpair-all$"]["POST"] = unpairAll;
|
server.resource["^/api/clients/unpair-all$"]["POST"] = unpairAll;
|
||||||
|
|
|
||||||
|
|
@ -1761,4 +1761,31 @@ namespace platf {
|
||||||
std::unique_ptr<high_precision_timer> create_high_precision_timer() {
|
std::unique_ptr<high_precision_timer> create_high_precision_timer() {
|
||||||
return std::make_unique<win32_high_precision_timer>();
|
return std::make_unique<win32_high_precision_timer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool getFileVersionInfo(const std::filesystem::path &file_path, std::string &version_str) {
|
||||||
|
DWORD handle = 0;
|
||||||
|
DWORD size = GetFileVersionInfoSizeW(file_path.wstring().c_str(), &handle);
|
||||||
|
if (size == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BYTE> buffer(size);
|
||||||
|
if (!GetFileVersionInfoW(file_path.wstring().c_str(), handle, size, buffer.data())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
VS_FIXEDFILEINFO *file_info = nullptr;
|
||||||
|
if (UINT file_info_size = 0; !VerQueryValueW(buffer.data(), L"\\", (LPVOID *) &file_info, &file_info_size)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD major = HIWORD(file_info->dwFileVersionMS);
|
||||||
|
DWORD minor = LOWORD(file_info->dwFileVersionMS);
|
||||||
|
DWORD build = HIWORD(file_info->dwFileVersionLS);
|
||||||
|
DWORD revision = LOWORD(file_info->dwFileVersionLS);
|
||||||
|
|
||||||
|
version_str = std::format("{}.{}.{}.{}", major, minor, build, revision);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
} // namespace platf
|
} // namespace platf
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
// standard includes
|
// standard includes
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
// platform includes
|
// platform includes
|
||||||
|
|
@ -19,4 +21,12 @@ namespace platf {
|
||||||
int64_t qpc_counter();
|
int64_t qpc_counter();
|
||||||
|
|
||||||
std::chrono::nanoseconds qpc_time_difference(int64_t performance_counter1, int64_t performance_counter2);
|
std::chrono::nanoseconds qpc_time_difference(int64_t performance_counter1, int64_t performance_counter2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get file version information from a Windows executable or driver file.
|
||||||
|
* @param file_path Path to the file to query.
|
||||||
|
* @param version_str Output parameter for version string in format "major.minor.build.revision".
|
||||||
|
* @return true if version info was successfully extracted, false otherwise.
|
||||||
|
*/
|
||||||
|
bool getFileVersionInfo(const std::filesystem::path &file_path, std::string &version_str);
|
||||||
} // namespace platf
|
} // namespace platf
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,21 @@
|
||||||
</ul>
|
</ul>
|
||||||
<a class="btn btn-danger" href="./troubleshooting#logs">View Logs</a>
|
<a class="btn btn-danger" href="./troubleshooting#logs">View Logs</a>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- ViGEmBus Warning -->
|
||||||
|
<div class="alert alert-warning" v-if="platform === 'windows' && controllerEnabled && vigembus && (!vigembus.installed || !vigembus.version_compatible)">
|
||||||
|
<div style="line-height: 32px;">
|
||||||
|
<i class="fas fa-triangle-exclamation" style="font-size: 32px;margin-right: 0.25em;"></i>
|
||||||
|
<div v-if="!vigembus.installed">
|
||||||
|
<p><strong>{{ $t('index.vigembus_not_installed_title') }}</strong></p>
|
||||||
|
<p>{{ $t('index.vigembus_not_installed_desc') }}</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!vigembus.version_compatible">
|
||||||
|
<p><strong>{{ $t('index.vigembus_outdated_title') }}</strong></p>
|
||||||
|
<p>{{ $t('index.vigembus_outdated_desc', { version: vigembus.version }) }}</p>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-warning" href="./troubleshooting#vigembus">{{ $t('index.fix_now') }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Version -->
|
<!-- Version -->
|
||||||
<div class="card p-2 my-4">
|
<div class="card p-2 my-4">
|
||||||
<div class="card-body" v-if="version">
|
<div class="card-body" v-if="version">
|
||||||
|
|
@ -94,18 +109,32 @@
|
||||||
preReleaseVersion: null,
|
preReleaseVersion: null,
|
||||||
loading: true,
|
loading: true,
|
||||||
logs: null,
|
logs: null,
|
||||||
|
platform: "",
|
||||||
|
controllerEnabled: false,
|
||||||
|
vigembus: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
try {
|
try {
|
||||||
let config = await fetch("./api/config").then((r) => r.json());
|
let config = await fetch("./api/config").then((r) => r.json());
|
||||||
this.notifyPreReleases = config.notify_pre_releases;
|
this.notifyPreReleases = config.notify_pre_releases;
|
||||||
|
this.platform = config.platform;
|
||||||
|
this.controllerEnabled = config.controller !== "disabled";
|
||||||
this.version = new SunshineVersion(null, config.version);
|
this.version = new SunshineVersion(null, config.version);
|
||||||
console.log("Version: ", this.version.version)
|
console.log("Version: ", this.version.version)
|
||||||
this.githubVersion = new SunshineVersion(await fetch("https://api.github.com/repos/LizardByte/Sunshine/releases/latest").then((r) => r.json()), null);
|
this.githubVersion = new SunshineVersion(await fetch("https://api.github.com/repos/LizardByte/Sunshine/releases/latest").then((r) => r.json()), null);
|
||||||
console.log("GitHub Version: ", this.githubVersion.version)
|
console.log("GitHub Version: ", this.githubVersion.version)
|
||||||
this.preReleaseVersion = new SunshineVersion((await fetch("https://api.github.com/repos/LizardByte/Sunshine/releases").then((r) => r.json())).find(release => release.prerelease), null);
|
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)
|
console.log("Pre-Release Version: ", this.preReleaseVersion.version)
|
||||||
|
|
||||||
|
// Fetch ViGEmBus status only on Windows when controller is enabled
|
||||||
|
if (this.platform === 'windows' && this.controllerEnabled) {
|
||||||
|
try {
|
||||||
|
this.vigembus = await fetch("./api/vigembus/status").then((r) => r.json());
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to fetch ViGEmBus status:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -381,6 +381,7 @@
|
||||||
"index": {
|
"index": {
|
||||||
"description": "Sunshine is a self-hosted game stream host for Moonlight.",
|
"description": "Sunshine is a self-hosted game stream host for Moonlight.",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
|
"fix_now": "Fix Now",
|
||||||
"installed_version_not_stable": "You are running a pre-release version of Sunshine. You may experience bugs or other issues. Please report any issues you encounter. Thank you for helping to make Sunshine a better software!",
|
"installed_version_not_stable": "You are running a pre-release version of Sunshine. You may experience bugs or other issues. Please report any issues you encounter. Thank you for helping to make Sunshine a better software!",
|
||||||
"loading_latest": "Loading latest release...",
|
"loading_latest": "Loading latest release...",
|
||||||
"new_pre_release": "A new Pre-Release Version is Available!",
|
"new_pre_release": "A new Pre-Release Version is Available!",
|
||||||
|
|
@ -388,6 +389,10 @@
|
||||||
"startup_errors": "<b>Attention!</b> Sunshine detected these errors during startup. We <b>STRONGLY RECOMMEND</b> fixing them before streaming.",
|
"startup_errors": "<b>Attention!</b> Sunshine detected these errors during startup. We <b>STRONGLY RECOMMEND</b> fixing them before streaming.",
|
||||||
"version_dirty": "Thank you for helping to make Sunshine a better software!",
|
"version_dirty": "Thank you for helping to make Sunshine a better software!",
|
||||||
"version_latest": "You are running the latest version of Sunshine",
|
"version_latest": "You are running the latest version of Sunshine",
|
||||||
|
"vigembus_not_installed_desc": "Virtual gamepad support will not work without the ViGEmBus driver. Click the button below to install it.",
|
||||||
|
"vigembus_not_installed_title": "ViGEmBus Driver Not Installed",
|
||||||
|
"vigembus_outdated_desc": "You are running an outdated version of ViGEmBus (v{version}). Version 1.17 or higher is required for proper gamepad support. Click the button below to update.",
|
||||||
|
"vigembus_outdated_title": "ViGEmBus Driver Outdated",
|
||||||
"welcome": "Hello, Sunshine!"
|
"welcome": "Hello, Sunshine!"
|
||||||
},
|
},
|
||||||
"navbar": {
|
"navbar": {
|
||||||
|
|
@ -451,7 +456,17 @@
|
||||||
"unpair_single_no_devices": "There are no paired devices.",
|
"unpair_single_no_devices": "There are no paired devices.",
|
||||||
"unpair_single_success": "However, the device(s) may still be in an active session. Use the 'Force Close' button above to end any open sessions.",
|
"unpair_single_success": "However, the device(s) may still be in an active session. Use the 'Force Close' button above to end any open sessions.",
|
||||||
"unpair_single_unknown": "Unknown Client",
|
"unpair_single_unknown": "Unknown Client",
|
||||||
"unpair_title": "Unpair Devices"
|
"unpair_title": "Unpair Devices",
|
||||||
|
"vigembus_compatible": "ViGEmBus is installed and compatible.",
|
||||||
|
"vigembus_current_version": "Current Version",
|
||||||
|
"vigembus_desc": "ViGEmBus is required for virtual gamepad support. Install or update the driver if it's missing or outdated (version 1.17 or higher required).",
|
||||||
|
"vigembus_incompatible": "ViGEmBus version is too old. Please install version 1.17 or higher.",
|
||||||
|
"vigembus_install": "ViGEmBus Driver",
|
||||||
|
"vigembus_install_button": "Install ViGEmBus v{version}",
|
||||||
|
"vigembus_install_error": "Failed to install ViGEmBus driver.",
|
||||||
|
"vigembus_install_success": "ViGEmBus driver installed successfully! You may need to restart your computer.",
|
||||||
|
"vigembus_force_reinstall_button": "Force Reinstall ViGEmBus v{version}",
|
||||||
|
"vigembus_not_installed": "ViGEmBus is not installed."
|
||||||
},
|
},
|
||||||
"welcome": {
|
"welcome": {
|
||||||
"confirm_password": "Confirm password",
|
"confirm_password": "Confirm password",
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,45 @@
|
||||||
<Navbar></Navbar>
|
<Navbar></Navbar>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 class="my-4">{{ $t('troubleshooting.troubleshooting') }}</h1>
|
<h1 class="my-4">{{ $t('troubleshooting.troubleshooting') }}</h1>
|
||||||
|
<!-- ViGEmBus Installation -->
|
||||||
|
<div class="card p-2 my-4" v-if="platform === 'windows' && controllerEnabled">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 id="vigembus">{{ $t('troubleshooting.vigembus_install') }}</h2>
|
||||||
|
<br>
|
||||||
|
<p style="white-space: pre-line">{{ $t('troubleshooting.vigembus_desc') }}</p>
|
||||||
|
<div v-if="vigembus.installed && vigembus.version">
|
||||||
|
<p><strong>{{ $t('troubleshooting.vigembus_current_version') }}:</strong> {{ vigembus.version }}</p>
|
||||||
|
<div class="alert alert-success" v-if="vigembus.version_compatible">
|
||||||
|
{{ $t('troubleshooting.vigembus_compatible') }}
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-danger" v-if="!vigembus.version_compatible">
|
||||||
|
{{ $t('troubleshooting.vigembus_incompatible') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-warning" v-else-if="!vigembus.installed">
|
||||||
|
{{ $t('troubleshooting.vigembus_not_installed') }}
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-success" v-if="vigemBusInstallStatus === true">
|
||||||
|
{{ $t('troubleshooting.vigembus_install_success') }}
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-danger" v-if="vigemBusInstallStatus === false">
|
||||||
|
{{ vigemBusInstallError || $t('troubleshooting.vigembus_install_error') }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
:class="vigembus.installed && vigembus.version === vigembus.packaged_version ? 'btn btn-danger' : 'btn btn-primary'"
|
||||||
|
:disabled="vigemBusInstallPressed"
|
||||||
|
@click="installViGEmBus">
|
||||||
|
<template v-if="vigembus.installed && vigembus.version === vigembus.packaged_version">
|
||||||
|
{{ $t('troubleshooting.vigembus_force_reinstall_button', { version: vigembus.packaged_version }) }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ $t('troubleshooting.vigembus_install_button', { version: vigembus.packaged_version }) }}
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Force Close App -->
|
<!-- Force Close App -->
|
||||||
<div class="card p-2 my-4">
|
<div class="card p-2 my-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
@ -169,8 +208,18 @@
|
||||||
restartPressed: false,
|
restartPressed: false,
|
||||||
showApplyMessage: false,
|
showApplyMessage: false,
|
||||||
platform: "",
|
platform: "",
|
||||||
|
controllerEnabled: false,
|
||||||
unpairAllPressed: false,
|
unpairAllPressed: false,
|
||||||
unpairAllStatus: null,
|
unpairAllStatus: null,
|
||||||
|
vigembus: {
|
||||||
|
installed: false,
|
||||||
|
version: '',
|
||||||
|
version_compatible: false,
|
||||||
|
packaged_version: '',
|
||||||
|
},
|
||||||
|
vigemBusInstallPressed: false,
|
||||||
|
vigemBusInstallStatus: null,
|
||||||
|
vigemBusInstallError: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -186,6 +235,11 @@
|
||||||
.then((r) => r.json())
|
.then((r) => r.json())
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
this.platform = r.platform;
|
this.platform = r.platform;
|
||||||
|
this.controllerEnabled = r.controller !== "disabled";
|
||||||
|
// Fetch ViGEmBus status only on Windows when gamepad is enabled
|
||||||
|
if (this.platform === 'windows' && this.controllerEnabled) {
|
||||||
|
this.refreshViGEmBusStatus();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logInterval = setInterval(() => {
|
this.logInterval = setInterval(() => {
|
||||||
|
|
@ -207,7 +261,7 @@
|
||||||
},
|
},
|
||||||
closeApp() {
|
closeApp() {
|
||||||
this.closeAppPressed = true;
|
this.closeAppPressed = true;
|
||||||
fetch("./api/apps/close", {
|
fetch("./api/apps/close", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
|
|
@ -223,7 +277,7 @@
|
||||||
},
|
},
|
||||||
unpairAll() {
|
unpairAll() {
|
||||||
this.unpairAllPressed = true;
|
this.unpairAllPressed = true;
|
||||||
fetch("./api/clients/unpair-all", {
|
fetch("./api/clients/unpair-all", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
|
|
@ -240,12 +294,12 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
unpairSingle(uuid) {
|
unpairSingle(uuid) {
|
||||||
fetch("./api/clients/unpair", {
|
fetch("./api/clients/unpair", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ uuid })
|
body: JSON.stringify({ uuid })
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.showApplyMessage = true;
|
this.showApplyMessage = true;
|
||||||
this.refreshClients();
|
this.refreshClients();
|
||||||
|
|
@ -285,11 +339,11 @@
|
||||||
},
|
},
|
||||||
ddResetPersistence() {
|
ddResetPersistence() {
|
||||||
this.ddResetPressed = true;
|
this.ddResetPressed = true;
|
||||||
fetch("/api/reset-display-device-persistence", {
|
fetch("/api/reset-display-device-persistence", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((r) => r.json())
|
.then((r) => r.json())
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
|
|
@ -300,6 +354,55 @@
|
||||||
}, 5000);
|
}, 5000);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
refreshViGEmBusStatus() {
|
||||||
|
fetch("/api/vigembus/status")
|
||||||
|
.then((r) => r.json())
|
||||||
|
.then((r) => {
|
||||||
|
this.vigembus = {
|
||||||
|
installed: r.installed || false,
|
||||||
|
version: r.version || '',
|
||||||
|
version_compatible: r.version_compatible || false,
|
||||||
|
packaged_version: r.packaged_version,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Failed to fetch ViGEmBus status:", err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
installViGEmBus() {
|
||||||
|
this.vigemBusInstallPressed = true;
|
||||||
|
this.vigemBusInstallStatus = null;
|
||||||
|
this.vigemBusInstallError = null;
|
||||||
|
fetch("/api/vigembus/install", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((r) => r.json())
|
||||||
|
.then((r) => {
|
||||||
|
this.vigemBusInstallPressed = false;
|
||||||
|
this.vigemBusInstallStatus = r.status;
|
||||||
|
if (!r.status && r.error) {
|
||||||
|
this.vigemBusInstallError = r.error;
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
this.vigemBusInstallStatus = null;
|
||||||
|
this.vigemBusInstallError = null;
|
||||||
|
}, 10000);
|
||||||
|
// Refresh status after installation attempt
|
||||||
|
this.refreshViGEmBusStatus();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.vigemBusInstallPressed = false;
|
||||||
|
this.vigemBusInstallStatus = false;
|
||||||
|
this.vigemBusInstallError = err.message;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.vigemBusInstallStatus = null;
|
||||||
|
this.vigemBusInstallError = null;
|
||||||
|
}, 10000);
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
# Check if a compatible version of ViGEmBus is already installed (1.17 or later)
|
|
||||||
try {
|
|
||||||
$vigemBusPath = "$env:SystemRoot\System32\drivers\ViGEmBus.sys"
|
|
||||||
$fileVersion = (Get-Item $vigemBusPath).VersionInfo.FileVersion
|
|
||||||
|
|
||||||
if ($fileVersion -ge [System.Version]"1.17") {
|
|
||||||
Write-Information "The installed version is 1.17 or later, no update needed. Exiting."
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Write-Information "ViGEmBus driver not found or inaccessible, proceeding with installation."
|
|
||||||
}
|
|
||||||
|
|
||||||
# Install Virtual Gamepad
|
|
||||||
$scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
||||||
$installerPath = Join-Path $scriptPath "vigembus_installer.exe"
|
|
||||||
Start-Process `
|
|
||||||
-FilePath $installerPath `
|
|
||||||
-ArgumentList "/passive", "/promptrestart"
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
# Use Get-CimInstance to find and uninstall Virtual Gamepad
|
|
||||||
$product = Get-CimInstance -ClassName Win32_Product -Filter "Name='ViGEm Bus Driver'"
|
|
||||||
if ($product) {
|
|
||||||
Invoke-CimMethod -InputObject $product -MethodName Uninstall
|
|
||||||
Write-Information "ViGEm Bus Driver uninstalled successfully"
|
|
||||||
} else {
|
|
||||||
Write-Warning "ViGEm Bus Driver not found"
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue