Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmake/compile_definitions/windows.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ set_target_properties(sunshine_rc_object PROPERTIES
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
"${CMAKE_SOURCE_DIR}/src/platform/windows/publish.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/windows/misc.h"
Expand Down
11 changes: 5 additions & 6 deletions cmake/packaging/windows.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ install(TARGETS sunshine RUNTIME DESTINATION "." COMPONENT application)
install(FILES "${ZLIB}" DESTINATION "." COMPONENT application)

# 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
"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}
SHOW_PROGRESS
EXPECTED_HASH SHA256=155c50f1eec07bdc28d2f61a3e3c2c6c132fee7328412de224695f89143316bc
Expand Down Expand Up @@ -45,9 +47,6 @@ install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/autostart/"
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/firewall/"
DESTINATION "scripts"
COMPONENT firewall)
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/gamepad/"
DESTINATION "scripts"
COMPONENT gamepad)

# Sunshine assets
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/assets/"
Expand Down Expand Up @@ -107,7 +106,7 @@ set(CPACK_COMPONENT_FIREWALL_GROUP "Scripts")

# gamepad scripts
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")

# include specific packaging
Expand Down
10 changes: 0 additions & 10 deletions cmake/packaging/windows_nsis.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\update-path.bat\\\" add'
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\migrate-config.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\\\\autostart-service.bat\\\"'
NoController:
Expand All @@ -29,14 +27,6 @@ set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\delete-firewall-rule.bat\\\"'
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\uninstall-service.bat\\\"'
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 \
'Do you want to remove $INSTDIR (this includes the configuration, cover images, and settings)?' \
/SD IDNO IDNO NoDelete
Expand Down
3 changes: 2 additions & 1 deletion cmake/targets/windows.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ find_library(ZLIB ZLIB1)
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
$<TARGET_OBJECTS:sunshine_rc_object>
Windowsapp.lib
Wtsapi32.lib)
Wtsapi32.lib
version.lib)
6 changes: 6 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ basic authentication with the admin username and password.
## POST /api/restart
@copydoc confighttp::restart()

## GET /api/vigembus/status
@copydoc confighttp::getViGEmBusStatus()

## POST /api/vigembus/install
@copydoc confighttp::installViGEmBus()

<div class="section_buttons">

| Previous | Next |
Expand Down
21 changes: 6 additions & 15 deletions docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -369,21 +369,7 @@ overflow menu. Different versions of Windows may provide slightly different step
scripts/delete-firewall-rule.bat
```

4. Virtual Gamepad Support

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
4. Windows service

Install:
```bash
Expand Down Expand Up @@ -465,6 +451,11 @@ Sunshine can only access microphones on macOS due to system limitations. To stre
> [!CAUTION]
> 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

### Basic usage
Expand Down
7 changes: 6 additions & 1 deletion docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,12 @@ launchctl load -w /Library/LaunchAgents/org.freedesktop.dbus-session.plist
## Windows

### 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
Since Sunshine runs as a service on Windows, it may not have the same level of access that your regular user account
Expand Down
135 changes: 135 additions & 0 deletions src/confighttp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
#include <Simple-Web-Server/crypto.hpp>
#include <Simple-Web-Server/server_https.hpp>

#ifdef _WIN32
#include "platform/windows/misc.h"

#include <vector>
#include <Windows.h>
#endif

// local includes
#include "config.h"
#include "confighttp.h"
Expand Down Expand Up @@ -1171,6 +1178,132 @@ namespace confighttp {
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() {
platf::set_thread_name("confighttp");
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
Expand Down Expand Up @@ -1209,6 +1342,8 @@ namespace confighttp {
server.resource["^/api/configLocale$"]["GET"] = getLocale;
server.resource["^/api/restart$"]["POST"] = restart;
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/apps/([0-9]+)$"]["DELETE"] = deleteApp;
server.resource["^/api/clients/unpair-all$"]["POST"] = unpairAll;
Expand Down
27 changes: 27 additions & 0 deletions src/platform/windows/misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1761,4 +1761,31 @@ namespace platf {
std::unique_ptr<high_precision_timer> create_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
10 changes: 10 additions & 0 deletions src/platform/windows/misc.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

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

// platform includes
Expand All @@ -19,4 +21,12 @@ namespace platf {
int64_t qpc_counter();

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
Loading
Loading