diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index 6356add1..9cf527ef 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -32,7 +32,9 @@ body:
id: description
attributes:
label: Describe the Bug
- description: A clear and concise description of the bug, list the reproduction steps.
+ description: |
+ A clear and concise description of the bug, list the reproduction steps.
+ :warning: Errors in log messages are NOT bugs. Read the message and fix what it's telling you. :warning:
validations:
required: true
- type: textarea
@@ -52,6 +54,7 @@ body:
description: What version operating system are you running the software on?
options:
- Docker
+ - FreeBSD
- Linux
- macOS
- Windows
@@ -75,15 +78,6 @@ body:
- other, n/a
validations:
required: true
- - type: input
- id: version
- attributes:
- label: Sunshine commit or version
- description: |
- Use `sunshine --verison` to get the version, or get the version from web UI.
- Please don't just copy the latest commit from our repo, if that's not the commit you're actually using.
- validations:
- required: true
- type: dropdown
id: package_type
attributes:
@@ -103,10 +97,10 @@ body:
- Linux - solus (Third Party)
- Linux - Unraid (Third Party)
- macOS - Homebrew
- - macOS - Portfile
- Windows - Chocolatey (Third Party)
- - Windows - installer (recommended)
- - Windows - portable (not recommended)
+ - Windows - exe installer
+ - Windows - msi installer (recommended)
+ - Windows - portable (NOT recommended)
- Windows - Scoop (Third Party)
- Windows - Winget
- other (not listed)
@@ -154,19 +148,11 @@ body:
- NvFBC (Linux)
- wlroots (Linux)
- X11 (Linux)
+ - XDG Portal Grab (Linux)
- Desktop Duplication API (Windows)
- Windows.Graphics.Capture (Windows)
validations:
required: false
- - type: textarea
- id: config
- attributes:
- label: Config
- description: |
- Please copy and paste your config (`sunshine.conf`) file.
- render: Shell
- validations:
- required: false
- type: textarea
id: apps
attributes:
@@ -179,10 +165,20 @@ body:
- type: textarea
id: logs
attributes:
- label: Relevant log output
+ label: Log output
description: |
- Please copy and paste any relevant log output. This will be automatically formatted into code,
- so no need for backticks.
+ Copy and paste logs from web-ui troubleshooting page.
+ This will be automatically formatted into code, so no need for backticks.
+ :warning: If full logs are not provided, the issue will be closed! :warning:
render: shell
validations:
- required: true
+ required: false
+ - type: input
+ id: logs_link
+ attributes:
+ label: Online logs
+ description: |
+ If logs are too long to include in the field above,
+ create a [gist](https://gist.github.com/) of the logs and paste the link here.
+ validations:
+ required: false
diff --git a/.github/workflows/ci-bundle.yml b/.github/workflows/ci-bundle.yml
index 60a57c60..a6642de6 100644
--- a/.github/workflows/ci-bundle.yml
+++ b/.github/workflows/ci-bundle.yml
@@ -23,6 +23,10 @@ jobs:
- name: Install npm dependencies
run: npm install --ignore-scripts
+ - name: Debug install
+ if: always()
+ run: cat "${HOME}/.npm/_logs/*-debug-0.log" || true
+
- name: Build
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml
index e869692a..a6a21ae8 100644
--- a/.github/workflows/ci-windows.yml
+++ b/.github/workflows/ci-windows.yml
@@ -29,6 +29,11 @@ jobs:
arch: x86_64
msystem: ucrt64
toolchain: ucrt-x86_64
+ - name: Windows-ARM64
+ os: windows-11-arm
+ arch: aarch64
+ msystem: clangarm64
+ toolchain: clang-aarch64
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -46,7 +51,9 @@ jobs:
- name: Update Windows dependencies
env:
- MSYSTEM: ${{ matrix.msystem }}
+ # MSYSTEM is a built-in environment variable of MSYS2.
+ # Do not use this environment variable name.
+ MATRIX_MSYSTEM: ${{ matrix.msystem }}
TOOLCHAIN: ${{ matrix.toolchain }}
shell: msys2 {0}
run: |
@@ -62,17 +69,22 @@ jobs:
"mingw-w64-${TOOLCHAIN}-curl-winssl"
"mingw-w64-${TOOLCHAIN}-gcc"
"mingw-w64-${TOOLCHAIN}-graphviz"
- "mingw-w64-${TOOLCHAIN}-MinHook"
"mingw-w64-${TOOLCHAIN}-miniupnpc"
"mingw-w64-${TOOLCHAIN}-nlohmann-json"
- "mingw-w64-${TOOLCHAIN}-nodejs"
- "mingw-w64-${TOOLCHAIN}-nsis"
"mingw-w64-${TOOLCHAIN}-onevpl"
"mingw-w64-${TOOLCHAIN}-openssl"
"mingw-w64-${TOOLCHAIN}-opus"
"mingw-w64-${TOOLCHAIN}-toolchain"
)
+ if [[ "${MATRIX_MSYSTEM}" == "ucrt64" ]]; then
+ dependencies+=(
+ "mingw-w64-${TOOLCHAIN}-MinHook"
+ "mingw-w64-${TOOLCHAIN}-nsis"
+ "mingw-w64-${TOOLCHAIN}-nodejs"
+ )
+ fi
+
# do not modify below this line
ignore_packages=()
@@ -83,7 +95,7 @@ jobs:
tarball="${pkg}-${version}-any.pkg.tar.zst"
# download working version
- wget "https://repo.msys2.org/mingw/${MSYSTEM}/${tarball}"
+ wget "https://repo.msys2.org/mingw/${MATRIX_MSYSTEM}/${tarball}"
tarballs="${tarballs} ${tarball}"
done
@@ -129,6 +141,33 @@ jobs:
# Clean up
Remove-Item -Path doxygen-setup.exe
+ - name: Setup dotnet # needed for wix
+ uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
+ with:
+ dotnet-version: '10.x'
+
+ - name: Setup NodeJS
+ # Clang compiled NodeJS has issues when running rollup webpack
+ if: matrix.msystem != 'ucrt64'
+ uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ with:
+ node-version: 'lts/*'
+
+ - name: NodeJS Path
+ if: matrix.msystem != 'ucrt64'
+ shell: pwsh
+ run: |
+ # get NodeJS PATH
+ $NODEJS_BINARY_PATH = (Get-Command node).Source
+ $NODEJS_PATH = Split-Path -Path "$NODEJS_BINARY_PATH" -Parent
+
+ # setup environment variables
+ echo "NODEJS_PATH=$NODEJS_PATH" >> $env:GITHUB_ENV
+
+ # step output
+ echo "nodejs-path=$NODEJS_PATH"
+ echo "nodejs-path=$NODEJS_PATH" >> $env:GITHUB_OUTPUT
+
- name: Setup python
id: setup-python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
@@ -149,10 +188,19 @@ jobs:
- name: Build Windows
shell: msys2 {0}
env:
+ # MSYSTEM is a built-in environment variable of MSYS2.
+ # Do not use this environment variable name.
+ MATRIX_MSYSTEM: ${{ matrix.msystem }}
BRANCH: ${{ github.head_ref || github.ref_name }}
BUILD_VERSION: ${{ inputs.release_version }}
COMMIT: ${{ inputs.release_commit }}
run: |
+ # setup NodeJS PATH
+ if [[ "${MATRIX_MSYSTEM}" != "ucrt64" ]]; then
+ NODEJS_PATH=$(cygpath "$NODEJS_PATH")
+ export PATH="$PATH:$NODEJS_PATH"
+ fi
+
mkdir -p build
cmake \
-B build \
@@ -176,12 +224,24 @@ jobs:
# package
cpack -G NSIS
+ cpack -G WIX
cpack -G ZIP
# move
mv ./cpack_artifacts/Sunshine.exe ../artifacts/Sunshine-${{ matrix.name }}-installer.exe
+ mv ./cpack_artifacts/Sunshine.msi ../artifacts/Sunshine-${{ matrix.name }}-installer.msi
mv ./cpack_artifacts/Sunshine.zip ../artifacts/Sunshine-${{ matrix.name }}-portable.zip
+ - name: Debug nsis
+ if: always()
+ shell: msys2 {0}
+ run: cat ./build/cpack_artifacts/_CPack_Packages/win64/NSIS/NSISOutput.log || true
+
+ - name: Debug wix
+ if: always()
+ shell: msys2 {0}
+ run: cat ./build/cpack_artifacts/_CPack_Packages/win64/WIX/wix.log || true
+
- name: Run tests
id: test
shell: msys2 {0}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3094d7f7..071461a1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -181,6 +181,9 @@ jobs:
- name: Windows-AMD64
coverage: true
pr: true
+ - name: Windows-ARM64
+ coverage: true
+ pr: true
steps:
- name: Should run
id: should_run
@@ -202,18 +205,7 @@ jobs:
name: coverage-${{ matrix.name }}
path: _coverage
- - name: Upload test results
- if: steps.should_run.outputs.SHOULD_RUN == 'true'
- uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1
- with:
- disable_search: true
- fail_ci_if_error: true
- files: ./_coverage/tests/test_results.xml
- flags: ${{ matrix.name }}
- token: ${{ secrets.CODECOV_TOKEN }}
- verbose: true
-
- - name: Upload coverage
+ - name: Upload test coverage
if: |
steps.should_run.outputs.SHOULD_RUN == 'true' &&
matrix.coverage != false
@@ -222,6 +214,19 @@ jobs:
disable_search: true
fail_ci_if_error: true
files: ./_coverage/coverage.xml
+ report_type: coverage
+ flags: ${{ matrix.name }}
+ token: ${{ secrets.CODECOV_TOKEN }}
+ verbose: true
+
+ - name: Upload test results
+ if: steps.should_run.outputs.SHOULD_RUN == 'true'
+ uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
+ with:
+ disable_search: true
+ fail_ci_if_error: true
+ files: ./_coverage/tests/test_results.xml
+ report_type: test_results
flags: ${{ matrix.name }}
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true
diff --git a/cmake/compile_definitions/windows.cmake b/cmake/compile_definitions/windows.cmake
index c1a2c95f..57af5e2a 100644
--- a/cmake/compile_definitions/windows.cmake
+++ b/cmake/compile_definitions/windows.cmake
@@ -9,6 +9,13 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
# gcc complains about misleading indentation in some mingw includes
list(APPEND SUNSHINE_COMPILE_OPTIONS -Wno-misleading-indentation)
+# Disable warnings for Windows ARM64
+if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64")
+ list(APPEND SUNSHINE_COMPILE_OPTIONS -Wno-dll-attribute-on-redeclaration) # Boost
+ list(APPEND SUNSHINE_COMPILE_OPTIONS -Wno-unknown-warning-option) # ViGEmClient
+ list(APPEND SUNSHINE_COMPILE_OPTIONS -Wno-unused-variable) # Boost
+endif()
+
# see gcc bug 98723
add_definitions(-DUSE_BOOST_REGEX)
diff --git a/cmake/dependencies/windows.cmake b/cmake/dependencies/windows.cmake
index 3faad7df..a9e5630d 100644
--- a/cmake/dependencies/windows.cmake
+++ b/cmake/dependencies/windows.cmake
@@ -1,9 +1,35 @@
# windows specific dependencies
-# Make sure MinHook is installed
-find_library(MINHOOK_LIBRARY libMinHook.a REQUIRED)
-find_path(MINHOOK_INCLUDE_DIR MinHook.h PATH_SUFFIXES include REQUIRED)
+# MinHook setup - use installed minhook for AMD64, otherwise download minhook-detours for ARM64
+if(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64")
+ # Make sure MinHook is installed for x86/x64
+ find_library(MINHOOK_LIBRARY libMinHook.a REQUIRED)
+ find_path(MINHOOK_INCLUDE_DIR MinHook.h PATH_SUFFIXES include REQUIRED)
-add_library(minhook::minhook STATIC IMPORTED)
-set_property(TARGET minhook::minhook PROPERTY IMPORTED_LOCATION ${MINHOOK_LIBRARY})
-target_include_directories(minhook::minhook INTERFACE ${MINHOOK_INCLUDE_DIR})
+ add_library(minhook::minhook STATIC IMPORTED)
+ set_property(TARGET minhook::minhook PROPERTY IMPORTED_LOCATION ${MINHOOK_LIBRARY})
+ target_include_directories(minhook::minhook INTERFACE ${MINHOOK_INCLUDE_DIR})
+else()
+ # Download pre-built minhook-detours for ARM64
+ message(STATUS "Downloading minhook-detours pre-built binaries for ARM64")
+ include(FetchContent)
+
+ FetchContent_Declare(
+ minhook-detours
+ URL https://github.com/m417z/minhook-detours/releases/download/v1.0.6/minhook-detours-1.0.6.zip
+ URL_HASH SHA256=E719959D824511E27395A82AEDA994CAAD53A67EE5894BA5FC2F4BF1FA41E38E
+ )
+ FetchContent_MakeAvailable(minhook-detours)
+
+ # Create imported library for the pre-built DLL
+ set(_MINHOOK_DLL
+ "${minhook-detours_SOURCE_DIR}/Release/minhook-detours.ARM64.Release.dll"
+ CACHE INTERNAL "Path to minhook-detours DLL")
+ add_library(minhook::minhook SHARED IMPORTED GLOBAL)
+ set_property(TARGET minhook::minhook PROPERTY IMPORTED_LOCATION "${_MINHOOK_DLL}")
+ set_property(TARGET minhook::minhook PROPERTY IMPORTED_IMPLIB
+ "${minhook-detours_SOURCE_DIR}/Release/minhook-detours.ARM64.Release.lib")
+ set_target_properties(minhook::minhook PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES "${minhook-detours_SOURCE_DIR}/src"
+ )
+endif()
diff --git a/cmake/packaging/windows.cmake b/cmake/packaging/windows.cmake
index 069fd851..9d75f7a6 100644
--- a/cmake/packaging/windows.cmake
+++ b/cmake/packaging/windows.cmake
@@ -4,6 +4,11 @@ install(TARGETS sunshine RUNTIME DESTINATION "." COMPONENT application)
# Hardening: include zlib1.dll (loaded via LoadLibrary() in openssl's libcrypto.a)
install(FILES "${ZLIB}" DESTINATION "." COMPONENT application)
+# ARM64: include minhook-detours DLL (shared library for ARM64)
+if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64" AND DEFINED _MINHOOK_DLL)
+ install(FILES "${_MINHOOK_DLL}" DESTINATION "." COMPONENT application)
+endif()
+
# ViGEmBus installer
set(VIGEMBUS_INSTALLER "${CMAKE_BINARY_DIR}/scripts/vigembus_installer.exe")
set(VIGEMBUS_DOWNLOAD_URL_1 "https://github.com/nefarius/ViGEmBus/releases/download")
@@ -28,6 +33,9 @@ install(TARGETS audio-info RUNTIME DESTINATION "tools" COMPONENT audio)
install(TARGETS sunshinesvc RUNTIME DESTINATION "tools" COMPONENT application)
# Mandatory scripts
+install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/sunshine-setup.ps1"
+ DESTINATION "scripts"
+ COMPONENT assets)
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/service/"
DESTINATION "scripts"
COMPONENT assets)
diff --git a/cmake/packaging/windows_nsis.cmake b/cmake/packaging/windows_nsis.cmake
index 6644a17d..7c413b9f 100644
--- a/cmake/packaging/windows_nsis.cmake
+++ b/cmake/packaging/windows_nsis.cmake
@@ -3,36 +3,42 @@
set(CPACK_NSIS_INSTALLED_ICON_NAME "${PROJECT__DIR}\\\\${PROJECT_EXE}")
+# Enable detailed logging only on AMD64
+if(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64")
+ set(NSIS_LOGSET_COMMAND "LogSet on")
+else()
+ set(NSIS_LOGSET_COMMAND "")
+endif()
+
# Extra install commands
-# Restores permissions on the install directory
-# Migrates config files from the root into the new config folder
-# Install service
+# Runs the main setup script which handles all installation tasks
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS
"${CPACK_NSIS_EXTRA_INSTALL_COMMANDS}
- IfSilent +2 0
- ExecShell 'open' 'https://docs.lizardbyte.dev/projects/sunshine'
- nsExec::ExecToLog 'icacls \\\"$INSTDIR\\\" /reset'
- 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 '\\\"$INSTDIR\\\\scripts\\\\install-service.bat\\\"'
- nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\autostart-service.bat\\\"'
- NoController:
+ ${NSIS_LOGSET_COMMAND}
+ IfSilent +3 0
+ nsExec::ExecToLog \
+ 'powershell -ExecutionPolicy Bypass \
+ -File \\\"$INSTDIR\\\\scripts\\\\sunshine-setup.ps1\\\" -Action install'
+ Goto +2
+ nsExec::ExecToLog \
+ 'powershell -ExecutionPolicy Bypass \
+ -File \\\"$INSTDIR\\\\scripts\\\\sunshine-setup.ps1\\\" -Action install -Silent'
+ install_done:
")
# Extra uninstall commands
-# Uninstall service
+# Runs the main setup script which handles all uninstallation tasks
set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS
"${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'
+ ${NSIS_LOGSET_COMMAND}
+ nsExec::ExecToLog \
+ 'powershell -ExecutionPolicy Bypass \
+ -File \\\"$INSTDIR\\\\scripts\\\\sunshine-setup.ps1\\\" -Action uninstall'
MessageBox MB_YESNO|MB_ICONQUESTION \
- 'Do you want to remove $INSTDIR (this includes the configuration, cover images, and settings)?' \
- /SD IDNO IDNO NoDelete
- RMDir /r \\\"$INSTDIR\\\"; skipped if no
- nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\update-path.bat\\\" remove'
- NoDelete:
+ 'Do you want to remove $INSTDIR (this includes the configuration, cover images, and settings)?' \
+ /SD IDNO IDNO no_delete
+ RMDir /r \\\"$INSTDIR\\\"; skipped if no
+ no_delete:
")
# Adding an option for the start menu
diff --git a/cmake/packaging/windows_wix.cmake b/cmake/packaging/windows_wix.cmake
index bc956182..4a2f4d03 100644
--- a/cmake/packaging/windows_wix.cmake
+++ b/cmake/packaging/windows_wix.cmake
@@ -1,4 +1,97 @@
# WIX Packaging
# see options at: https://cmake.org/cmake/help/latest/cpack_gen/wix.html
-# TODO: Replace nsis with wix
+# find dotnet
+find_program(DOTNET_EXECUTABLE dotnet HINTS "C:/Program Files/dotnet")
+
+if(NOT DOTNET_EXECUTABLE)
+ message(WARNING "Dotnet executable not found, skipping WiX packaging.")
+ return()
+endif()
+
+set(CPACK_WIX_VERSION 4)
+set(WIX_VERSION 4.0.4)
+set(WIX_UI_VERSION 4.0.4) # extension versioning is independent of the WiX version
+set(WIX_BUILD_PARENT_DIRECTORY "${CMAKE_BINARY_DIR}/wix_packaging")
+set(WIX_BUILD_DIRECTORY "${CPACK_PACKAGE_DIRECTORY}/_CPack_Packages/win64/WIX")
+
+# Download and install WiX tools locally in the build directory
+set(WIX_TOOL_PATH "${CMAKE_BINARY_DIR}/.wix")
+file(MAKE_DIRECTORY ${WIX_TOOL_PATH})
+
+# Install WiX locally using dotnet
+execute_process(
+ COMMAND ${DOTNET_EXECUTABLE} tool install --tool-path ${WIX_TOOL_PATH} wix --version ${WIX_VERSION}
+ ERROR_VARIABLE WIX_INSTALL_OUTPUT
+ RESULT_VARIABLE WIX_INSTALL_RESULT
+)
+
+if(NOT WIX_INSTALL_RESULT EQUAL 0)
+ message(FATAL_ERROR "Failed to install WiX tools locally.
+ WiX packaging may not work correctly, error: ${WIX_INSTALL_OUTPUT}")
+endif()
+
+# Install WiX UI Extension
+execute_process(
+ COMMAND "${WIX_TOOL_PATH}/wix" extension add WixToolset.UI.wixext/${WIX_UI_VERSION}
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ ERROR_VARIABLE WIX_UI_INSTALL_OUTPUT
+ RESULT_VARIABLE WIX_UI_INSTALL_RESULT
+)
+
+if(NOT WIX_UI_INSTALL_RESULT EQUAL 0)
+ message(FATAL_ERROR "Failed to install WiX UI extension, error: ${WIX_UI_INSTALL_OUTPUT}")
+endif()
+
+# Install WiX Util Extension
+execute_process(
+ COMMAND "${WIX_TOOL_PATH}/wix" extension add WixToolset.Util.wixext/${WIX_UI_VERSION}
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ ERROR_VARIABLE WIX_UTIL_INSTALL_OUTPUT
+ RESULT_VARIABLE WIX_UTIL_INSTALL_RESULT
+)
+
+if(NOT WIX_UTIL_INSTALL_RESULT EQUAL 0)
+ message(FATAL_ERROR "Failed to install WiX Util extension, error: ${WIX_UTIL_INSTALL_OUTPUT}")
+endif()
+
+# Set WiX-specific variables
+set(CPACK_WIX_ROOT "${WIX_TOOL_PATH}")
+set(CPACK_WIX_UPGRADE_GUID "512A3D1B-BE16-401B-A0D1-59BBA3942FB8")
+
+# Installer metadata
+set(CPACK_WIX_HELP_LINK "https://docs.lizardbyte.dev/projects/sunshine/latest/md_docs_2getting__started.html")
+set(CPACK_WIX_PRODUCT_ICON "${SUNSHINE_ICON_PATH}")
+set(CPACK_WIX_PRODUCT_URL "${CMAKE_PROJECT_HOMEPAGE_URL}")
+set(CPACK_WIX_PROGRAM_MENU_FOLDER "LizardByte")
+
+set(CPACK_WIX_EXTENSIONS
+ "WixToolset.UI.wixext"
+ "WixToolset.Util.wixext"
+)
+
+message(STATUS "cpack package directory: ${CPACK_PACKAGE_DIRECTORY}")
+
+# copy custom wxs files to the build directory
+file(COPY "${CMAKE_CURRENT_LIST_DIR}/wix_resources/"
+ DESTINATION "${WIX_BUILD_PARENT_DIRECTORY}/")
+
+set(CPACK_WIX_EXTRA_SOURCES
+ "${WIX_BUILD_PARENT_DIRECTORY}/sunshine-installer.wxs"
+)
+set(CPACK_WIX_PATCH_FILE
+ "${WIX_BUILD_PARENT_DIRECTORY}/patch.xml"
+)
+
+# Copy root LICENSE and rename to have .txt extension
+file(COPY "${CMAKE_SOURCE_DIR}/LICENSE"
+ DESTINATION "${CMAKE_BINARY_DIR}")
+file(RENAME "${CMAKE_BINARY_DIR}/LICENSE" "${CMAKE_BINARY_DIR}/LICENSE.txt")
+set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_BINARY_DIR}/LICENSE.txt") # cpack will covert this to an RTF if it is txt
+
+# https://cmake.org/cmake/help/latest/cpack_gen/wix.html#variable:CPACK_WIX_ARCHITECTURE
+if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64")
+ set(CPACK_WIX_ARCHITECTURE "arm64")
+else()
+ set(CPACK_WIX_ARCHITECTURE "x64")
+endif()
diff --git a/cmake/packaging/wix_resources/patch.xml b/cmake/packaging/wix_resources/patch.xml
new file mode 100644
index 00000000..e85c3581
--- /dev/null
+++ b/cmake/packaging/wix_resources/patch.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/cmake/packaging/wix_resources/sunshine-installer.wxs b/cmake/packaging/wix_resources/sunshine-installer.wxs
new file mode 100644
index 00000000..36f92969
--- /dev/null
+++ b/cmake/packaging/wix_resources/sunshine-installer.wxs
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cmake/prep/build_version.cmake b/cmake/prep/build_version.cmake
index 7c10dde2..91209922 100644
--- a/cmake/prep/build_version.cmake
+++ b/cmake/prep/build_version.cmake
@@ -56,11 +56,11 @@ else()
if(NOT GIT_DESCRIBE_ERROR_CODE)
MESSAGE("Sunshine Branch: ${GIT_DESCRIBE_BRANCH}")
if(NOT GIT_DESCRIBE_BRANCH STREQUAL "master")
- set(PROJECT_VERSION ${PROJECT_VERSION}.${GIT_DESCRIBE_VERSION})
+ set(PROJECT_VERSION ${PROJECT_VERSION}-${GIT_DESCRIBE_VERSION})
MESSAGE("Sunshine Version: ${GIT_DESCRIBE_VERSION}")
endif()
if(GIT_IS_DIRTY)
- set(PROJECT_VERSION ${PROJECT_VERSION}.dirty)
+ set(PROJECT_VERSION ${PROJECT_VERSION}-dirty)
MESSAGE("Git tree is dirty!")
endif()
else()
diff --git a/docs/Doxyfile b/docs/Doxyfile
index 8549dbba..36196192 100644
--- a/docs/Doxyfile
+++ b/docs/Doxyfile
@@ -31,7 +31,7 @@ PROJECT_NAME = Sunshine
# project specific settings
DOT_GRAPH_MAX_NODES = 60
-# IMAGE_PATH = ../docs/images
+IMAGE_PATH = ../docs/images
PREDEFINED += SUNSHINE_BUILD_WAYLAND
PREDEFINED += SUNSHINE_TRAY=1
diff --git a/docs/building.md b/docs/building.md
index 67b2066c..4a3ee6c2 100644
--- a/docs/building.md
+++ b/docs/building.md
@@ -126,36 +126,60 @@ sudo port install "${dependencies[@]}"
```
#### Windows
-First you need to install [MSYS2](https://www.msys2.org), then startup "MSYS2 UCRT64" and execute the following
-commands.
+
+> [!WARNING]
+> Cross-compilation is not supported on Windows. You must build on the target architecture.
+
+First, you need to install [MSYS2](https://www.msys2.org).
+
+For AMD64 startup "MSYS2 UCRT64" (or for ARM64 startup "MSYS2 CLANGARM64") then execute the following commands.
##### Update all packages
```bash
pacman -Syu
```
+##### Set toolchain variable
+For UCRT64:
+```bash
+export TOOLCHAIN="ucrt-x86_64"
+```
+
+For CLANGARM64:
+```bash
+export TOOLCHAIN="clang-aarch64"
+```
+
##### Install dependencies
```bash
dependencies=(
"git"
- "mingw-w64-ucrt-x86_64-boost" # Optional
- "mingw-w64-ucrt-x86_64-cmake"
- "mingw-w64-ucrt-x86_64-cppwinrt"
- "mingw-w64-ucrt-x86_64-curl-winssl"
- "mingw-w64-ucrt-x86_64-doxygen" # Optional, for docs... better to install official Doxygen
- "mingw-w64-ucrt-x86_64-graphviz" # Optional, for docs
- "mingw-w64-ucrt-x86_64-MinHook"
- "mingw-w64-ucrt-x86_64-miniupnpc"
- "mingw-w64-ucrt-x86_64-nodejs"
- "mingw-w64-ucrt-x86_64-nsis"
- "mingw-w64-ucrt-x86_64-onevpl"
- "mingw-w64-ucrt-x86_64-openssl"
- "mingw-w64-ucrt-x86_64-opus"
- "mingw-w64-ucrt-x86_64-toolchain"
+ "mingw-w64-${TOOLCHAIN}-boost" # Optional
+ "mingw-w64-${TOOLCHAIN}-cmake"
+ "mingw-w64-${TOOLCHAIN}-cppwinrt"
+ "mingw-w64-${TOOLCHAIN}-curl-winssl"
+ "mingw-w64-${TOOLCHAIN}-doxygen" # Optional, for docs... better to install official Doxygen
+ "mingw-w64-${TOOLCHAIN}-graphviz" # Optional, for docs
+ "mingw-w64-${TOOLCHAIN}-miniupnpc"
+ "mingw-w64-${TOOLCHAIN}-onevpl"
+ "mingw-w64-${TOOLCHAIN}-openssl"
+ "mingw-w64-${TOOLCHAIN}-opus"
+ "mingw-w64-${TOOLCHAIN}-toolchain"
)
+if [[ "${MSYSTEM}" == "UCRT64" ]]; then
+ dependencies+=(
+ "mingw-w64-${TOOLCHAIN}-MinHook"
+ "mingw-w64-${TOOLCHAIN}-nodejs"
+ "mingw-w64-${TOOLCHAIN}-nsis"
+ )
+fi
pacman -S "${dependencies[@]}"
```
+To create a WiX installer, you also need to install [.NET](https://dotnet.microsoft.com/download).
+
+For ARM64: To build frontend, you also need to install [Node.JS](https://nodejs.org/en/download)
+
### Clone
Ensure [git](https://git-scm.com) is installed on your system, then clone the repository using the following command:
@@ -198,9 +222,12 @@ ninja -C build
```}
}}
@tab{Windows | @tabs{
- @tab{Installer | ```bash
+ @tab{NSIS Installer | ```bash
cpack -G NSIS --config ./build/CPackConfig.cmake
```}
+ @tab{WiX Installer | ```bash
+ cpack -G WIX --config ./build/CPackConfig.cmake
+ ```}
@tab{Portable | ```bash
cpack -G ZIP --config ./build/CPackConfig.cmake
```}
diff --git a/docs/getting_started.md b/docs/getting_started.md
index d2afb2b9..5daccc04 100644
--- a/docs/getting_started.md
+++ b/docs/getting_started.md
@@ -100,22 +100,10 @@ CUDA is used for NVFBC capture.
> [!CAUTION]
> Use distro-specific packages instead of the AppImage if they are available.
+> AppImage does not support KMS capture.
-According to AppImageLint the supported distro matrix of the AppImage is below.
-
-- ✖ Debian bullseye
-- ✔ Debian bookworm
-- ✔ Debian trixie
-- ✔ Debian sid
-- ✔ Ubuntu plucky
-- ✔ Ubuntu noble
-- ✔ Ubuntu jammy
-- ✖ Ubuntu focal
-- ✖ Ubuntu bionic
-- ✖ Ubuntu xenial
-- ✖ Ubuntu trusty
-- ✖ Rocky Linux 8
-- ✖ Rocky Linux 9
+> [!NOTE]
+> The AppImage is built on Ubuntu 22.04, which requires `glibc 2.35` or newer and `libstdc++ 3.4.11` or newer.
##### Install
1. Download [sunshine.AppImage](https://github.com/LizardByte/Sunshine/releases/latest/download/sunshine.AppImage)
@@ -214,6 +202,12 @@ sudo dnf remove sunshine
```
##### Install (Copr)
+
+> [!IMPORTANT]
+> Stable builds are only available if the Sunshine release was made after the Fedora version release.
+> Because of this, it is often recommended to use the beta copr; however, you do not need to regularly update.
+> This could lead to annoyances in rare cases where there may be a breaking change.
+
1. Enable copr repository.
```bash
sudo dnf copr enable lizardbyte/stable
@@ -238,6 +232,7 @@ sudo dnf remove Sunshine
> [!CAUTION]
> Use distro-specific packages instead of the Flatpak if they are available.
+> Flatpak does not support KMS capture.
Using this package requires that you have [Flatpak](https://flatpak.org/setup) installed.
@@ -305,6 +300,9 @@ brew install sunshine
brew uninstall sunshine
```
+> [!TIP]
+> For beta you can replace `sunshine` with `sunshine-beta` in the above commands.
+
### macOS
> [!IMPORTANT]
@@ -315,6 +313,8 @@ This package requires that you have [Homebrew](https://docs.brew.sh/Installation
##### Install
```bash
+brew update
+brew upgrade
brew tap LizardByte/homebrew
brew install sunshine
```
@@ -329,10 +329,31 @@ brew uninstall sunshine
### Windows
+> [!NOTE]
+> Sunshine supports ARM64 on Windows; however, this should be considered experimental. This version does not properly
+> support GPU scheduling and any hardware acceleration.
+
#### Installer (recommended)
-1. Download and install
- [Sunshine-Windows-AMD64-installer.exe](https://github.com/LizardByte/Sunshine/releases/latest/download/Sunshine-Windows-AMD64-installer.exe)
+> [!CAUTION]
+> The msi installer is preferred moving forward. Before using a different type of installer, you should manually
+> uninstall the previous installation.
+
+1. Download and install based on your architecture:
+
+ | Architecture | Installer |
+ |-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
+ | AMD64/x64 (Intel/AMD) | [Sunshine-Windows-AMD64-installer.msi](https://github.com/LizardByte/Sunshine/releases/latest/download/Sunshine-Windows-AMD64-installer.msi) |
+ | AMD64/x64 (Intel/AMD) | [Sunshine-Windows-AMD64-installer.exe](https://github.com/LizardByte/Sunshine/releases/latest/download/Sunshine-Windows-AMD64-installer.exe) |
+ | ARM64 | [Sunshine-Windows-ARM64-installer.msi](https://github.com/LizardByte/Sunshine/releases/latest/download/Sunshine-Windows-ARM64-installer.msi) |
+ | ARM64 | [Sunshine-Windows-ARM64-installer.exe](https://github.com/LizardByte/Sunshine/releases/latest/download/Sunshine-Windows-ARM64-installer.exe) |
+
+> [!TIP]
+> Installer logs can be found in the following locations.
+> | File | log paths |
+> | ---- | --------- |
+> | .exe | `%%PROGRAMFILES%/Sunshine/install.log` (AMD64 only)
`%%TEMP%/Sunshine/logs/install/` |
+> | .msi | `%%TEMP%/Sunshine/logs/install/` |
> [!CAUTION]
> You should carefully select or unselect the options you want to install. Do not blindly install or
@@ -347,8 +368,13 @@ overflow menu. Different versions of Windows may provide slightly different step
> By using this package instead of the installer, performance will be reduced. This package is not
> recommended for most users. No support will be provided!
-1. Download and extract
- [Sunshine-Windows-AMD64-portable.zip](https://github.com/LizardByte/Sunshine/releases/latest/download/Sunshine-Windows-AMD64-portable.zip)
+1. Download and extract based on your architecture:
+
+ | Architecture | Installer |
+ |-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
+ | AMD64/x64 (Intel/AMD) | [Sunshine-Windows-AMD64-portable.zip](https://github.com/LizardByte/Sunshine/releases/latest/download/Sunshine-Windows-AMD64-portable.zip) |
+ | ARM64 | [Sunshine-Windows-ARM64-portable.zip](https://github.com/LizardByte/Sunshine/releases/latest/download/Sunshine-Windows-ARM64-portable.zip) |
+
2. Open command prompt as administrator
3. Firewall rules
@@ -435,6 +461,8 @@ In order for virtual gamepads to work, you must install ViGEmBus. You can do thi
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
@@ -455,9 +483,8 @@ sunshine /sunshine.conf
```
> [!NOTE]
-> You do not need to specify a config file. If no config file is entered, the default location will be used.
-
-> [!TIP]
+> This step is optional, you do not need to specify a config file.
+> If no config file is entered, the default location will be used.
> The configuration file specified will be created if it doesn't exist.
### Start Sunshine over SSH (Linux/X11)
@@ -493,16 +520,27 @@ by default. You may replace *localhost* with your internal ip address.
> [!CAUTION]
> If running for the first time, make sure to note the username and password that you created.
-1. Add games and applications.
-2. Adjust any configuration settings as needed.
-3. In Moonlight, you may need to add the PC manually.
-4. When Moonlight requests for you insert the pin:
+1. Change the web-ui to your desired theme, using the dropdown menu in the navbar.
+ 
+2. Add games and applications.
+ 
+3. Adjust any configuration settings as needed. You can search for options in the search bar.
+ 
+4. Find Moonlight clients and other tools for Sunshine in the `Featured Apps` tab.
+ 
+5. In Moonlight, you may need to add the PC manually.
+6. When Moonlight requests for you insert the pin:
- - Login to the web ui
+ - Login to the web-ui
- Go to "PIN" in the Navbar
- - Type in your PIN and press Enter, you should get a Success Message
+ - Type in your PIN and press `Enter`, and enter a name of your choosing for the device.
+ You should get a Success Message!
- In Moonlight, select one of the Applications listed
+7. If you run into issues, logs are available in the `Troubleshooting` tab.
+ You can navigate through each warning/error message for clues to the issue.
+ 
+
### Arguments
To get a list of available arguments, run the following command.
diff --git a/docs/images/applications.png b/docs/images/applications.png
new file mode 100644
index 00000000..42f7605f
Binary files /dev/null and b/docs/images/applications.png differ
diff --git a/docs/images/configuration-search.png b/docs/images/configuration-search.png
new file mode 100644
index 00000000..c9c573ec
Binary files /dev/null and b/docs/images/configuration-search.png differ
diff --git a/docs/images/featured-apps.png b/docs/images/featured-apps.png
new file mode 100644
index 00000000..489ad710
Binary files /dev/null and b/docs/images/featured-apps.png differ
diff --git a/docs/images/split-themes.png b/docs/images/split-themes.png
new file mode 100644
index 00000000..22b42e02
Binary files /dev/null and b/docs/images/split-themes.png differ
diff --git a/docs/images/troubleshooting-logs.png b/docs/images/troubleshooting-logs.png
new file mode 100644
index 00000000..ea57c7bc
Binary files /dev/null and b/docs/images/troubleshooting-logs.png differ
diff --git a/docs/images/vigembus-installer.png b/docs/images/vigembus-installer.png
new file mode 100644
index 00000000..fb68d5e0
Binary files /dev/null and b/docs/images/vigembus-installer.png differ
diff --git a/src/config.cpp b/src/config.cpp
index e5d58f3e..cc7b704b 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -187,7 +187,7 @@ namespace config {
};
template
- std::optional quality_from_view(const std::string_view &quality_type, const std::optional(&original)) {
+ ::std::optional quality_from_view(const ::std::string_view &quality_type, const ::std::optional(&original)) {
#define _CONVERT_(x) \
if (quality_type == #x##sv) \
return (int) T::x
@@ -199,7 +199,7 @@ namespace config {
}
template
- std::optional rc_from_view(const std::string_view &rc, const std::optional(&original)) {
+ ::std::optional rc_from_view(const ::std::string_view &rc, const ::std::optional(&original)) {
#define _CONVERT_(x) \
if (rc == #x##sv) \
return (int) T::x
@@ -212,7 +212,7 @@ namespace config {
}
template
- std::optional usage_from_view(const std::string_view &usage, const std::optional(&original)) {
+ ::std::optional usage_from_view(const ::std::string_view &usage, const ::std::optional(&original)) {
#define _CONVERT_(x) \
if (usage == #x##sv) \
return (int) T::x
@@ -225,7 +225,7 @@ namespace config {
return original;
}
- int coder_from_view(const std::string_view &coder) {
+ int coder_from_view(const ::std::string_view &coder) {
if (coder == "auto"sv) {
return _auto;
}
@@ -556,8 +556,8 @@ namespace config {
true, // client gamepads with touchpads are emulated as DS4
true, // ds5_inputtino_randomize_mac
- true, // keyboard enabled
- true, // mouse enabled
+ false, // keyboard disabled (Parsec-style default)
+ false, // mouse disabled (Parsec-style default)
true, // controller enabled
true, // always send scancodes
true, // high resolution scrolling
diff --git a/src/main.cpp b/src/main.cpp
index 20a733d8..b4117450 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -383,7 +383,7 @@ int main(int argc, char *argv[]) {
BOOST_LOG(info) << "Starting system tray"sv;
#ifdef _WIN32
// TODO: Windows has a weird bug where when running as a service and on the first Windows boot,
- // he tray icon would not appear even though Sunshine is running correctly otherwise.
+ // the tray icon would not appear even though Sunshine is running correctly otherwise.
// Restarting the service would allow the icon to appear normally.
// For now we will keep the Windows tray icon on a separate thread.
// Ideally, we would run the system tray on the main thread for all platforms.
diff --git a/src/nvenc/nvenc_d3d11_on_cuda.h b/src/nvenc/nvenc_d3d11_on_cuda.h
index 102e1809..80aeb9ed 100644
--- a/src/nvenc/nvenc_d3d11_on_cuda.h
+++ b/src/nvenc/nvenc_d3d11_on_cuda.h
@@ -56,7 +56,6 @@ namespace nvenc {
autopop_context push_context();
- HMODULE dll = nullptr;
const ID3D11DevicePtr d3d_device;
ID3D11Texture2DPtr d3d_input_texture;
diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp
index e8bcc37f..c0012dd6 100644
--- a/src/platform/linux/kmsgrab.cpp
+++ b/src/platform/linux/kmsgrab.cpp
@@ -1661,9 +1661,14 @@ namespace platf {
if (!fb->handles[0]) {
BOOST_LOG(error) << "Couldn't get handle for DRM Framebuffer ["sv << plane->fb_id << "]: Probably not permitted"sv;
BOOST_LOG((config::video.capture == "kms") ? fatal : error)
- << "If you installed from AppImage or Flatpak, KMS capture is not supported.\n"sv
+#if defined(SUNSHINE_BUILD_FLATPAK) || defined(SUNSHINE_BUILD_APPIMAGE)
+ << "AppImage and Flatpak do not support KMS capture. Use another capture method."sv;
+#else
+ << "You must use the 'sunshine-kms' service instead of the 'sunshine' service for KMS capture.\n"sv
<< "Please refer to the official documentation:\n"sv
- << "https://docs.lizardbyte.dev/projects/sunshine/latest/md_docs_2getting__started.html#linux"sv;
+ << " stable: https://docs.lizardbyte.dev/projects/sunshine/latest/md_docs_2getting__started.html#linux-1"sv
+ << " beta: https://docs.lizardbyte.dev/projects/sunshine/master/md_docs_2getting__started.html#linux-1"sv;
+#endif
break;
}
diff --git a/src/platform/linux/portalgrab.cpp b/src/platform/linux/portalgrab.cpp
index 4c27357d..c2c604f5 100644
--- a/src/platform/linux/portalgrab.cpp
+++ b/src/platform/linux/portalgrab.cpp
@@ -736,8 +736,9 @@ namespace portal {
struct spa_buffer *buf;
buf = stream_data.current_buffer->buffer;
if (buf->datas[0].chunk->size != 0) {
+ const auto img_descriptor = static_cast(img);
+ img_descriptor->frame_timestamp = std::chrono::steady_clock::now();
if (buf->datas[0].type == SPA_DATA_DmaBuf) {
- const auto img_descriptor = static_cast(img);
img_descriptor->sd.width = stream_data.format.info.raw.size.width;
img_descriptor->sd.height = stream_data.format.info.raw.size.height;
img_descriptor->sd.modifier = stream_data.format.info.raw.modifier;
diff --git a/src/platform/windows/audio.cpp b/src/platform/windows/audio.cpp
index d7d4680e..b26f91a8 100644
--- a/src/platform/windows/audio.cpp
+++ b/src/platform/windows/audio.cpp
@@ -32,14 +32,14 @@ DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b
#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64)
#define STEAM_DRIVER_SUBDIR L"x64"
-#else
- #warning No known Steam audio driver for this architecture
#endif
namespace {
constexpr auto SAMPLE_RATE = 48000;
+#ifdef STEAM_DRIVER_SUBDIR
constexpr auto STEAM_AUDIO_DRIVER_PATH = L"%CommonProgramFiles(x86)%\\Steam\\drivers\\Windows10\\" STEAM_DRIVER_SUBDIR L"\\SteamStreamingSpeakers.inf";
+#endif
constexpr auto waveformat_mask_stereo = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp
index c6b694f0..52a87c78 100644
--- a/src/platform/windows/display_vram.cpp
+++ b/src/platform/windows/display_vram.cpp
@@ -1907,6 +1907,12 @@ namespace platf::dxgi {
if (!boost::algorithm::ends_with(name, "_nvenc")) {
return false;
}
+ } else if (adapter_desc.VendorId == 0x4D4F4351 || // Qualcomm (QCOM as MOQC reversed)
+ adapter_desc.VendorId == 0x5143) { // Qualcomm alternate ID
+ // If it's not a MediaFoundation encoder, it's not compatible with a Qualcomm GPU
+ if (!boost::algorithm::ends_with(name, "_mf")) {
+ return false;
+ }
} else {
BOOST_LOG(warning) << "Unknown GPU vendor ID: " << util::hex(adapter_desc.VendorId).to_string_view();
}
diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp
index 85273609..533e3790 100644
--- a/src/platform/windows/input.cpp
+++ b/src/platform/windows/input.cpp
@@ -10,6 +10,7 @@
// standard includes
#include
#include
+#include
// lib includes
#include
@@ -1134,9 +1135,9 @@ namespace platf {
void unicode(input_t &input, char *utf8, int size) {
// We can do no worse than one UTF-16 character per byte of UTF-8
- WCHAR wide[size];
+ std::vector wide(size);
- int chars = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8, size, wide, size);
+ int chars = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8, size, wide.data(), size);
if (chars <= 0) {
return;
}
diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp
index 5a4ea26e..4736a4c7 100644
--- a/src/platform/windows/misc.cpp
+++ b/src/platform/windows/misc.cpp
@@ -9,6 +9,7 @@
#include
#include
#include
+#include
// lib includes
#include
@@ -1383,7 +1384,7 @@ namespace platf {
auto const max_bufs_per_msg = send_info.payload_buffers.size() + (send_info.headers ? 1 : 0);
- WSABUF bufs[(send_info.headers ? send_info.block_count : 1) * max_bufs_per_msg];
+ std::vector bufs((send_info.headers ? send_info.block_count : 1) * max_bufs_per_msg);
DWORD bufcount = 0;
if (send_info.headers) {
// Interleave buffers for headers and payloads
@@ -1409,7 +1410,7 @@ namespace platf {
}
}
- msg.lpBuffers = bufs;
+ msg.lpBuffers = bufs.data();
msg.dwBufferCount = bufcount;
msg.dwFlags = 0;
diff --git a/src/platform/windows/windows.rc b/src/platform/windows/windows.rc
index 8f1c2bf7..417fcf0d 100644
--- a/src/platform/windows/windows.rc
+++ b/src/platform/windows/windows.rc
@@ -41,4 +41,4 @@ BEGIN
END
END
-SuperDuperAmazing ICON DISCARDABLE PROJECT_ICON_PATH
+SuperDuperAmazing ICON DISCARDABLE TOSTRING(PROJECT_ICON_PATH)
diff --git a/src/video.cpp b/src/video.cpp
index 0290fd95..7487e127 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -300,6 +300,7 @@ namespace video {
ALWAYS_REPROBE = 1 << 9, ///< This is an encoder of last resort and we want to aggressively probe for a better one
YUV444_SUPPORT = 1 << 10, ///< Encoder may support 4:4:4 chroma sampling depending on hardware
ASYNC_TEARDOWN = 1 << 11, ///< Encoder supports async teardown on a different thread
+ FIXED_GOP_SIZE = 1 << 12, ///< Use fixed small GOP size (encoder doesn't support on-demand IDR frames)
};
class avcodec_encode_session_t: public encode_session_t {
@@ -825,6 +826,63 @@ namespace video {
},
PARALLEL_ENCODING
};
+
+ encoder_t mediafoundation {
+ "mediafoundation"sv,
+ std::make_unique(
+ AV_HWDEVICE_TYPE_D3D11VA,
+ AV_HWDEVICE_TYPE_NONE,
+ AV_PIX_FMT_D3D11,
+ AV_PIX_FMT_NV12, // SDR 4:2:0 8-bit (only format Qualcomm supports)
+ AV_PIX_FMT_NONE, // No HDR - Qualcomm MF only supports 8-bit
+ AV_PIX_FMT_NONE, // No YUV444 SDR
+ AV_PIX_FMT_NONE, // No YUV444 HDR
+ dxgi_init_avcodec_hardware_input_buffer
+ ),
+ {
+ // Common options for AV1 - Qualcomm MF encoder
+ {
+ {"hw_encoding"s, 1},
+ {"rate_control"s, "cbr"s},
+ {"scenario"s, "display_remoting"s},
+ },
+ {}, // SDR-specific options
+ {}, // HDR-specific options
+ {}, // YUV444 SDR-specific options
+ {}, // YUV444 HDR-specific options
+ {}, // Fallback options
+ "av1_mf"s,
+ },
+ {
+ // Common options for HEVC - Qualcomm MF encoder
+ {
+ {"hw_encoding"s, 1},
+ {"rate_control"s, "cbr"s},
+ {"scenario"s, "display_remoting"s},
+ },
+ {}, // SDR-specific options
+ {}, // HDR-specific options
+ {}, // YUV444 SDR-specific options
+ {}, // YUV444 HDR-specific options
+ {}, // Fallback options
+ "hevc_mf"s,
+ },
+ {
+ // Common options for H.264 - Qualcomm MF encoder
+ {
+ {"hw_encoding"s, 1},
+ {"rate_control"s, "cbr"s},
+ {"scenario"s, "display_remoting"s},
+ },
+ {}, // SDR-specific options
+ {}, // HDR-specific options
+ {}, // YUV444 SDR-specific options
+ {}, // YUV444 HDR-specific options
+ {}, // Fallback options
+ "h264_mf"s,
+ },
+ PARALLEL_ENCODING | FIXED_GOP_SIZE // MF encoder doesn't support on-demand IDR frames
+ };
#endif
encoder_t software {
@@ -1031,6 +1089,7 @@ namespace video {
#ifdef _WIN32
&quicksync,
&amdvce,
+ &mediafoundation,
#endif
#if defined(__linux__) || defined(linux) || defined(__linux) || defined(__FreeBSD__)
&vaapi,
@@ -1566,11 +1625,17 @@ namespace video {
ctx->max_b_frames = 0;
// Use an infinite GOP length since I-frames are generated on demand
- ctx->gop_size = encoder.flags & LIMITED_GOP_SIZE ?
- std::numeric_limits::max() :
- std::numeric_limits::max();
-
- ctx->keyint_min = std::numeric_limits::max();
+ // Exception: encoders with FIXED_GOP_SIZE flag don't support on-demand IDR
+ if (encoder.flags & FIXED_GOP_SIZE) {
+ // Fixed GOP for encoders that don't support on-demand IDR (e.g. Media Foundation)
+ ctx->gop_size = 120; // ~2 seconds at 60 FPS - larger to reduce oversized IDR frame frequency
+ ctx->keyint_min = 120;
+ } else {
+ ctx->gop_size = encoder.flags & LIMITED_GOP_SIZE ?
+ std::numeric_limits::max() :
+ std::numeric_limits::max();
+ ctx->keyint_min = std::numeric_limits::max();
+ }
// Some client decoders have limits on the number of reference frames
if (config.numRefFrames) {
diff --git a/src/video.h b/src/video.h
index 8dbf76e2..eb8bb46f 100644
--- a/src/video.h
+++ b/src/video.h
@@ -220,6 +220,7 @@ namespace video {
#ifdef _WIN32
extern encoder_t amdvce;
extern encoder_t quicksync;
+ extern encoder_t mediafoundation;
#endif
#if defined(__linux__) || defined(linux) || defined(__linux) || defined(__FreeBSD__)
diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html
index 222fba0e..1bc7cdb6 100644
--- a/src_assets/common/assets/web/config.html
+++ b/src_assets/common/assets/web/config.html
@@ -203,12 +203,12 @@
"touchpad_as_ds4": "enabled",
"ds5_inputtino_randomize_mac": "enabled",
"back_button_timeout": -1,
- "keyboard": "enabled",
+ "keyboard": "disabled",
"key_repeat_delay": 500,
"key_repeat_frequency": 24.9,
"always_send_scancodes": "enabled",
"key_rightalt_to_key_win": "disabled",
- "mouse": "enabled",
+ "mouse": "disabled",
"high_resolution_scrolling": "enabled",
"native_pen_touch": "enabled",
"keybindings": "[0x10,0xA0,0x11,0xA2,0x12,0xA4]", // todo: add this to UI
diff --git a/src_assets/common/assets/web/configs/tabs/Inputs.vue b/src_assets/common/assets/web/configs/tabs/Inputs.vue
index 7fa76a20..64792b07 100644
--- a/src_assets/common/assets/web/configs/tabs/Inputs.vue
+++ b/src_assets/common/assets/web/configs/tabs/Inputs.vue
@@ -118,7 +118,7 @@ const config = ref(props.config)
id="keyboard"
locale-prefix="config"
v-model="config.keyboard"
- default="true"
+ default="false"
>
@@ -161,7 +161,7 @@ const config = ref(props.config)
id="mouse"
locale-prefix="config"
v-model="config.mouse"
- default="true"
+ default="false"
>
diff --git a/src_assets/windows/misc/sunshine-setup.ps1 b/src_assets/windows/misc/sunshine-setup.ps1
new file mode 100644
index 00000000..f0015f0e
--- /dev/null
+++ b/src_assets/windows/misc/sunshine-setup.ps1
@@ -0,0 +1,674 @@
+# Sunshine Setup Script
+# This script orchestrates the installation and uninstallation of Sunshine
+# Usage: sunshine-setup.ps1 -Action [install|uninstall] [-Silent]
+
+param(
+ [Parameter(Mandatory=$false)]
+ [ValidateSet(
+ "install",
+ "uninstall"
+ )]
+ [string]$Action,
+
+ [Parameter(Mandatory=$false)]
+ [switch]$Silent
+)
+
+# Constants
+$DocsUrl = "https://docs.lizardbyte.dev/projects/sunshine"
+
+# Set preference variables for output streams
+$InformationPreference = 'Continue'
+
+# Function to write output to both console (with color/stream) and log file (without color)
+function Write-LogMessage {
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '',
+ Justification='Write-Host is required for colored output')]
+ param(
+ [Parameter(Mandatory=$true)]
+ [AllowEmptyString()]
+ [string]$Message,
+
+ [Parameter(Mandatory=$false)]
+ [ValidateSet(
+ 'Debug',
+ 'Error',
+ 'Information',
+ 'Step',
+ 'Success',
+ 'Verbose',
+ 'Warning'
+ )]
+ [string]$Level = 'Information',
+
+ [Parameter(Mandatory=$false)]
+ [ValidateSet(
+ 'Black',
+ 'Blue',
+ 'Cyan',
+ 'DarkGray',
+ 'Gray',
+ 'Green',
+ 'Magenta',
+ 'Red',
+ 'White',
+ 'Yellow'
+ )]
+ [string]$Color = $null,
+
+ [Parameter(Mandatory=$false)]
+ [switch]$NoTimestamp,
+
+ [Parameter(Mandatory=$false)]
+ [switch]$NoLogFile
+ )
+
+ # Map levels to colors and output streams
+ $levelConfig = @{
+ 'Debug' = @{ DefaultColor = 'DarkGray'; Stream = 'Debug'; Emoji = ''; LogLevel = 'DEBUG' }
+ 'Error' = @{ DefaultColor = 'Red'; Stream = 'Error'; Emoji = '✗'; LogLevel = 'ERROR' }
+ 'Information' = @{ DefaultColor = $null; Stream = 'Host'; Emoji = ''; LogLevel = 'INFO' }
+ 'Step' = @{ DefaultColor = 'Cyan'; Stream = 'Host'; Emoji = '==>'; LogLevel = 'INFO' }
+ 'Success' = @{ DefaultColor = 'Green'; Stream = 'Host'; Emoji = '✓'; LogLevel = 'INFO' }
+ 'Verbose' = @{ DefaultColor = 'DarkGray'; Stream = 'Verbose'; Emoji = ''; LogLevel = 'VERBOSE' }
+ 'Warning' = @{ DefaultColor = 'Yellow'; Stream = 'Warning'; Emoji = '⚠'; LogLevel = 'WARN' }
+ }
+
+ $config = $levelConfig[$Level]
+
+ # Use custom color if specified, otherwise use default color for the level
+ $displayColor = if ($Color) { $Color } else { $config.DefaultColor }
+
+ # Write to appropriate output stream with color
+ switch ($config.Stream) {
+ 'Debug' {
+ Write-Debug $Message
+ }
+ 'Error' {
+ Write-Error $Message
+ }
+ 'Host' {
+ if ($null -ne $displayColor) {
+ Write-Host "$($config.Emoji) $Message" -ForegroundColor $displayColor
+ } else {
+ Write-Host "$($config.Emoji) $Message"
+ }
+ }
+ 'Information' {
+ Write-Information $Message
+ }
+ 'Verbose' {
+ Write-Verbose $Message
+ }
+ 'Warning' {
+ Write-Warning $Message
+ }
+ default {
+ Write-Information $Message
+ }
+ }
+
+ # Write to log file without color codes (only if LogPath exists and not disabled)
+ if ($script:LogPath -and -not $NoLogFile) {
+ try {
+ # Format log entry with timestamp and level
+ if ($NoTimestamp) {
+ $logEntry = $Message
+ } else {
+ $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
+ $logEntry = "[$timestamp] [$($config.LogLevel)] $Message"
+ }
+
+ $logEntry | Out-File `
+ -FilePath $script:LogPath `
+ -Append `
+ -Encoding UTF8
+ } catch {
+ # Avoid infinite recursion - use Write-Verbose directly
+ Write-Verbose "Could not write to log file: $($_.Exception.Message)"
+ }
+ }
+}
+
+# Function to print a separator bar
+function Write-Bar {
+ param(
+ [string]$Level = 'Information',
+ [int]$Length = 63,
+ [string]$Color = $null,
+ [switch]$NoTimestamp
+ )
+ $bar = "=" * $Length
+ if ($Color) {
+ Write-LogMessage -Message $bar -Level $Level -Color $Color -NoTimestamp:$NoTimestamp
+ } else {
+ Write-LogMessage -Message $bar -Level $Level -NoTimestamp:$NoTimestamp
+ }
+}
+
+# Function to print text framed by bars
+function Write-FramedText {
+ param(
+ [string]$Message,
+ [string]$Level = 'Information',
+ [int]$BarLength = 63,
+ [string]$Color = $null,
+ [switch]$NoTimestamp,
+ [switch]$NoCenter
+ )
+
+ # Center the message if NoCenter is not specified
+ $displayMessage = $Message
+ if (-not $NoCenter) {
+ $messageLength = $Message.Trim().Length
+
+ if ($messageLength -lt $BarLength) {
+ $totalPadding = $BarLength - $messageLength
+ $leftPadding = [Math]::Floor($totalPadding / 2)
+ $displayMessage = (' ' * $leftPadding) + $Message.Trim()
+ } else {
+ $displayMessage = $Message.Trim()
+ }
+ }
+
+ if ($Color) {
+ Write-Bar -Level $Level -Length $BarLength -Color $Color -NoTimestamp:$NoTimestamp
+ Write-LogMessage -Message $displayMessage -Level $Level -Color $Color -NoTimestamp:$NoTimestamp
+ Write-Bar -Level $Level -Length $BarLength -Color $Color -NoTimestamp:$NoTimestamp
+ } else {
+ Write-Bar -Level $Level -Length $BarLength -NoTimestamp:$NoTimestamp
+ Write-LogMessage -Message $displayMessage -Level $Level -NoTimestamp:$NoTimestamp
+ Write-Bar -Level $Level -Length $BarLength -NoTimestamp:$NoTimestamp
+ }
+}
+
+# Function to write to log file (helper function)
+function Write-LogFile {
+ param(
+ [string[]]$Lines
+ )
+ if ($script:LogPath) {
+ try {
+ foreach ($line in $Lines) {
+ $line | Out-File `
+ -FilePath $script:LogPath `
+ -Append `
+ -Encoding UTF8
+ }
+ } catch {
+ Write-Warning "Failed to write to log file: $($_.Exception.Message)"
+ }
+ }
+}
+
+# If Action is not provided, prompt the user
+if (-not $Action) {
+ Write-Information ""
+ Write-FramedText -Message "🔅 Sunshine Setup Script" -Level "Information" -Color "Cyan"
+ Write-Information ""
+ Write-LogMessage -Message "Please select an action:" -Level "Information" -Color "Yellow"
+ Write-LogMessage -Message " 1. Install Sunshine" -Level "Information" -Color "Green"
+ Write-LogMessage -Message " 2. Uninstall Sunshine" -Level "Information" -Color "Red"
+ Write-Information ""
+
+ $validChoice = $false
+ while (-not $validChoice) {
+ $choice = Read-Host "Enter your choice (1 or 2)"
+
+ switch ($choice) {
+ "1" {
+ $Action = "install"
+ $validChoice = $true
+ }
+ "2" {
+ $Action = "uninstall"
+ $validChoice = $true
+ }
+ default {
+ Write-Warning "Invalid choice. Please select 1 or 2."
+ Write-Information ""
+ }
+ }
+ }
+ Write-Information ""
+}
+
+# Check if running as administrator, if not, relaunch with elevation
+$currentPrincipal = New-Object `
+ Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
+$isAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+
+if (-not $isAdmin) {
+ Write-Warning "This script requires administrator privileges. Relaunching with elevation..."
+
+ # Build the argument list for the elevated process
+ $arguments = "-ExecutionPolicy Bypass -File `"$($MyInvocation.MyCommand.Path)`" -Action $Action"
+ if ($Silent) {
+ $arguments += " -Silent"
+ }
+
+ try {
+ # Relaunch the script with elevation
+ Start-Process powershell.exe -Verb RunAs -ArgumentList $arguments -Wait
+ exit $LASTEXITCODE
+ } catch {
+ Write-Error "Failed to elevate privileges: $($_.Exception.Message)"
+ exit 1
+ }
+}
+
+# Get the script directory and root directory
+$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+$RootDir = Split-Path -Parent $ScriptDir
+
+# Set up transcript logging
+$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
+$logDir = Join-Path $env:TEMP "Sunshine\logs\$Action"
+$LogPath = Join-Path $logDir "${timestamp}.log"
+
+# Ensure the log directory exists
+if (-not (Test-Path $logDir)) {
+ New-Item -ItemType Directory -Path $logDir -Force | Out-Null
+}
+
+# Store LogPath in script scope for logging functions
+$script:LogPath = $LogPath
+
+# Function to execute a batch script if it exists
+function Invoke-ScriptIfExist {
+ param(
+ [string]$ScriptPath,
+ [string]$Arguments = "",
+ [string]$Description = "",
+ [string]$Emoji = "🔧"
+ )
+
+ if ($Description) {
+ Write-LogMessage -Message "$Emoji $Description" -Level "Step"
+ }
+
+ if (Test-Path $ScriptPath) {
+ Write-LogMessage -Message "Executing: $ScriptPath $Arguments" -Level "Information"
+
+ # Capture output to suppress it from console but log it
+ $stdoutFile = [System.IO.Path]::GetTempFileName()
+ $stderrFile = [System.IO.Path]::GetTempFileName()
+
+ try {
+ if ($Arguments -ne "") {
+ $process = Start-Process `
+ -FilePath $ScriptPath `
+ -ArgumentList $Arguments `
+ -Wait `
+ -PassThru `
+ -NoNewWindow `
+ -RedirectStandardOutput $stdoutFile `
+ -RedirectStandardError $stderrFile
+ } else {
+ $process = Start-Process `
+ -FilePath $ScriptPath `
+ -Wait `
+ -PassThru `
+ -NoNewWindow `
+ -RedirectStandardOutput $stdoutFile `
+ -RedirectStandardError $stderrFile
+ }
+
+ # Log and display the output
+ if (Test-Path $stdoutFile) {
+ $output = Get-Content $stdoutFile -Raw -ErrorAction SilentlyContinue
+ if ($output) {
+ # Display output with indentation
+ $output -split "`r?`n" | ForEach-Object {
+ if ($_.Trim()) {
+ Write-LogMessage -Message " $_" -Level "Information" -Color "DarkGray"
+ }
+ }
+ }
+ }
+ if (Test-Path $stderrFile) {
+ $errors = Get-Content $stderrFile -Raw -ErrorAction SilentlyContinue
+ if ($errors) {
+ # Display errors with indentation
+ $errors -split "`r?`n" | ForEach-Object {
+ if ($_.Trim()) {
+ Write-LogMessage -Message " $_" -Level "Warning"
+ }
+ }
+ }
+ }
+
+ if ($process.ExitCode -ne 0) {
+ Write-LogMessage -Message " ⚠ Script exited with code $($process.ExitCode): $ScriptPath" -Level "Warning"
+ return $process.ExitCode
+ } else {
+ Write-LogMessage -Message " ✓ Done" -Level "Success"
+ return 0
+ }
+ } finally {
+ # Clean up temp files
+ if (Test-Path $stdoutFile) {
+ Remove-Item $stdoutFile -Force -ErrorAction SilentlyContinue
+ }
+ if (Test-Path $stderrFile) {
+ Remove-Item $stderrFile -Force -ErrorAction SilentlyContinue
+ }
+ }
+ } else {
+ Write-LogMessage -Message " ⓘ Skipped (script not found)" -Level "Information" -Color "DarkGray"
+ return 0
+ }
+}
+
+# Function to execute sunshine.exe with arguments if it exists
+function Invoke-SunshineIfExist {
+ param(
+ [string]$Arguments,
+ [string]$Description = "",
+ [string]$Emoji = "🔧"
+ )
+
+ if ($Description) {
+ Write-LogMessage -Message "$Emoji $Description" -Level "Step"
+ }
+
+ $SunshinePath = Join-Path $RootDir "sunshine.exe"
+
+ if (Test-Path $SunshinePath) {
+ Write-LogMessage -Message "Executing: $SunshinePath $Arguments" -Level "Information"
+
+ # Capture output to suppress it from console but log it
+ $stdoutFile = [System.IO.Path]::GetTempFileName()
+ $stderrFile = [System.IO.Path]::GetTempFileName()
+
+ try {
+ $process = Start-Process `
+ -FilePath $SunshinePath `
+ -ArgumentList $Arguments `
+ -Wait `
+ -PassThru `
+ -NoNewWindow `
+ -RedirectStandardOutput $stdoutFile `
+ -RedirectStandardError $stderrFile
+
+ # Log and display the output
+ if (Test-Path $stdoutFile) {
+ $output = Get-Content $stdoutFile -Raw -ErrorAction SilentlyContinue
+ if ($output) {
+ # Display output with indentation
+ $output -split "`r?`n" | ForEach-Object {
+ if ($_.Trim()) {
+ Write-LogMessage -Message " $_" -Level "Information" -Color "DarkGray"
+ }
+ }
+ }
+ }
+ if (Test-Path $stderrFile) {
+ $errors = Get-Content $stderrFile -Raw -ErrorAction SilentlyContinue
+ if ($errors) {
+ # Display errors with indentation
+ $errors -split "`r?`n" | ForEach-Object {
+ if ($_.Trim()) {
+ Write-LogMessage -Message " $_" -Level "Warning"
+ }
+ }
+ }
+ }
+
+ if ($process.ExitCode -ne 0) {
+ Write-LogMessage -Message " ⚠ Sunshine exited with code $($process.ExitCode)" -Level "Warning"
+ return $process.ExitCode
+ } else {
+ Write-LogMessage -Message " ✓ Done" -Level "Success"
+ return 0
+ }
+ } finally {
+ # Clean up temp files
+ if (Test-Path $stdoutFile) {
+ Remove-Item $stdoutFile -Force -ErrorAction SilentlyContinue
+ }
+ if (Test-Path $stderrFile) {
+ Remove-Item $stderrFile -Force -ErrorAction SilentlyContinue
+ }
+ }
+ } else {
+ Write-LogMessage -Message " ⓘ Skipped (executable not found)" -Level "Information" -Color "DarkGray"
+ return 0
+ }
+}
+
+# Main script logic
+Write-Information ""
+
+if ($Action -eq "install") {
+ Write-FramedText `
+ -Message "🔅 Sunshine Installation Script" `
+ -Level "Information" `
+ -Color "Yellow"
+ Write-Information ""
+
+ $totalSteps = 6
+ $currentStep = 0
+
+ # Reset permissions on the install directory
+ $currentStep++
+ Write-Progress `
+ -Activity "Installing Sunshine" `
+ -Status "Resetting permissions on installation directory" `
+ -PercentComplete (($currentStep / $totalSteps) * 100)
+ Write-LogMessage -Message "🔐 Resetting permissions on installation directory" -Level "Step"
+ try {
+ Write-LogMessage -Message "Executing: icacls.exe `"$RootDir`" /reset" -Level "Information"
+
+ # Capture output to suppress it from console but log it
+ $stdoutFile = [System.IO.Path]::GetTempFileName()
+ $stderrFile = [System.IO.Path]::GetTempFileName()
+
+ try {
+ $icaclsProcess = Start-Process `
+ -FilePath "icacls.exe" `
+ -ArgumentList "`"$RootDir`" /reset" `
+ -Wait `
+ -PassThru `
+ -NoNewWindow `
+ -RedirectStandardOutput $stdoutFile `
+ -RedirectStandardError $stderrFile
+
+ # Log and display the output
+ if (Test-Path $stdoutFile) {
+ $output = Get-Content $stdoutFile -Raw -ErrorAction SilentlyContinue
+ if ($output) {
+ # Display output with indentation
+ $output -split "`r?`n" | ForEach-Object {
+ if ($_.Trim()) {
+ Write-LogMessage -Message " $_" -Level "Information" -Color "DarkGray"
+ }
+ }
+ }
+ }
+ if (Test-Path $stderrFile) {
+ $errors = Get-Content $stderrFile -Raw -ErrorAction SilentlyContinue
+ if ($errors) {
+ # Display errors with indentation
+ $errors -split "`r?`n" | ForEach-Object {
+ if ($_.Trim()) {
+ Write-LogMessage -Message " $_" -Level "Warning"
+ }
+ }
+ }
+ }
+
+ if ($icaclsProcess.ExitCode -eq 0) {
+ Write-LogMessage -Message " ✓ Done" -Level "Success"
+ } else {
+ Write-LogMessage -Message " ⚠ Exit code $($icaclsProcess.ExitCode)" -Level "Warning"
+ }
+ } finally {
+ # Clean up temp files
+ if (Test-Path $stdoutFile) {
+ Remove-Item $stdoutFile -Force -ErrorAction SilentlyContinue
+ }
+ if (Test-Path $stderrFile) {
+ Remove-Item $stderrFile -Force -ErrorAction SilentlyContinue
+ }
+ }
+ } catch {
+ Write-LogMessage -Message " ⚠ Failed to reset permissions: $($_.Exception.Message)" -Level "Warning"
+ }
+ Write-Information ""
+
+ # 1. Update PATH (add)
+ $currentStep++
+ Write-Progress `
+ -Activity "Installing Sunshine" `
+ -Status "Updating system PATH" `
+ -PercentComplete (($currentStep / $totalSteps) * 100)
+ $updatePathScript = Join-Path $RootDir "scripts\update-path.bat"
+ Invoke-ScriptIfExist `
+ -ScriptPath $updatePathScript `
+ -Arguments "add" `
+ -Description "Adding Sunshine directories to PATH" `
+ -Emoji "📁"
+ Write-Information ""
+
+ # 2. Migrate configuration
+ $currentStep++
+ Write-Progress `
+ -Activity "Installing Sunshine" `
+ -Status "Migrating configuration" `
+ -PercentComplete (($currentStep / $totalSteps) * 100)
+ $migrateConfigScript = Join-Path $RootDir "scripts\migrate-config.bat"
+ Invoke-ScriptIfExist `
+ -ScriptPath $migrateConfigScript `
+ -Description "Migrating configuration files" `
+ -Emoji "⚙️"
+ Write-Information ""
+
+ # 3. Add firewall rules
+ $currentStep++
+ Write-Progress `
+ -Activity "Installing Sunshine" `
+ -Status "Configuring firewall" `
+ -PercentComplete (($currentStep / $totalSteps) * 100)
+ $addFirewallScript = Join-Path $RootDir "scripts\add-firewall-rule.bat"
+ Invoke-ScriptIfExist `
+ -ScriptPath $addFirewallScript `
+ -Description "Adding firewall rules" `
+ -Emoji "🛡️"
+ Write-Information ""
+
+ # 4. Install service
+ $currentStep++
+ Write-Progress `
+ -Activity "Installing Sunshine" `
+ -Status "Installing service" `
+ -PercentComplete (($currentStep / $totalSteps) * 100)
+ $installServiceScript = Join-Path $RootDir "scripts\install-service.bat"
+ Invoke-ScriptIfExist `
+ -ScriptPath $installServiceScript `
+ -Description "Installing Windows Service" `
+ -Emoji "⚡"
+ Write-Information ""
+
+ # 5. Configure autostart
+ $currentStep++
+ Write-Progress `
+ -Activity "Installing Sunshine" `
+ -Status "Configuring autostart" `
+ -PercentComplete (($currentStep / $totalSteps) * 100)
+ $autostartScript = Join-Path $RootDir "scripts\autostart-service.bat"
+ Invoke-ScriptIfExist `
+ -ScriptPath $autostartScript `
+ -Description "Configuring autostart" `
+ -Emoji "🚀"
+ Write-Information ""
+
+ Write-Progress -Activity "Installing Sunshine" -Completed
+ Write-FramedText -Message "✓ Sunshine installation completed successfully!" -Level "Success"
+
+ # Open documentation in browser (only if not running silently)
+ if (-not $Silent) {
+ Write-Information ""
+ Write-LogMessage `
+ -Message "📖 Opening documentation in your browser: $DocsUrl" `
+ -Level "Step"
+ try {
+ Start-Process $DocsUrl
+ Write-LogMessage -Message " ✓ Done" -Level "Success"
+ } catch {
+ Write-LogMessage `
+ -Message " ⓘ Could not open browser automatically: $($_.Exception.Message)" `
+ -Level "Warning"
+ }
+ }
+
+} elseif ($Action -eq "uninstall") {
+ Write-FramedText `
+ -Message "🗑️ Sunshine Uninstallation Script" `
+ -Level "Information" `
+ -Color "Yellow"
+ Write-Information ""
+
+ $totalSteps = 4
+ $currentStep = 0
+
+ # 1. Delete firewall rules
+ $currentStep++
+ Write-Progress `
+ -Activity "Uninstalling Sunshine" `
+ -Status "Removing firewall rules" `
+ -PercentComplete (($currentStep / $totalSteps) * 100)
+ $deleteFirewallScript = Join-Path $RootDir "scripts\delete-firewall-rule.bat"
+ Invoke-ScriptIfExist `
+ -ScriptPath $deleteFirewallScript `
+ -Description "Removing firewall rules" `
+ -Emoji "🛡️"
+ Write-Information ""
+
+ # 2. Uninstall service
+ $currentStep++
+ Write-Progress `
+ -Activity "Uninstalling Sunshine" `
+ -Status "Uninstalling service" `
+ -PercentComplete (($currentStep / $totalSteps) * 100)
+ $uninstallServiceScript = Join-Path $RootDir "scripts\uninstall-service.bat"
+ Invoke-ScriptIfExist `
+ -ScriptPath $uninstallServiceScript `
+ -Description "Removing Windows Service" `
+ -Emoji "⚡"
+ Write-Information ""
+
+ # 3. Restore NVIDIA preferences
+ $currentStep++
+ Write-Progress `
+ -Activity "Uninstalling Sunshine" `
+ -Status "Restoring NVIDIA settings" `
+ -PercentComplete (($currentStep / $totalSteps) * 100)
+ Invoke-SunshineIfExist `
+ -Arguments "--restore-nvprefs-undo" `
+ -Description "Restoring NVIDIA preferences" `
+ -Emoji "🎮"
+ Write-Information ""
+
+ # 4. Update PATH (remove)
+ $currentStep++
+ Write-Progress `
+ -Activity "Uninstalling Sunshine" `
+ -Status "Cleaning up system PATH" `
+ -PercentComplete (($currentStep / $totalSteps) * 100)
+ $updatePathScript = Join-Path $RootDir "scripts\update-path.bat"
+ Invoke-ScriptIfExist `
+ -ScriptPath $updatePathScript `
+ -Arguments "remove" `
+ -Description "Removing from PATH" `
+ -Emoji "📁"
+ Write-Information ""
+
+ Write-Progress -Activity "Uninstalling Sunshine" -Completed
+ Write-FramedText `
+ -Message "✓ Sunshine uninstallation completed successfully!" `
+ -Level "Success"
+}
+
+Write-Information ""
+exit 0
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 9a570fd3..1d9bdad4 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -163,4 +163,15 @@ if (WIN32)
# prefer static libraries since we're linking statically
# this fixes libcurl linking errors when using non MSYS2 version of CMake
set_target_properties(${PROJECT_NAME} PROPERTIES LINK_SEARCH_START_STATIC 1)
+
+ # Copy minhook-detours DLL to test binary directory for ARM64
+ if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64" AND DEFINED _MINHOOK_DLL)
+ add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different
+ "${_MINHOOK_DLL}"
+ "${CMAKE_CURRENT_BINARY_DIR}"
+ COMMENT "Copying minhook-detours DLL to test binary directory"
+ VERBATIM
+ )
+ endif()
endif ()