diff --git a/.clang-format b/.clang-format index d6da5d94..782d1575 100644 --- a/.clang-format +++ b/.clang-format @@ -69,7 +69,6 @@ IndentWrappedFunctionNames: true InsertBraces: true InsertNewlineAtEOF: true KeepEmptyLinesAtTheStartOfBlocks: false -LineEnding: LF MaxEmptyLinesToKeep: 1 NamespaceIndentation: All ObjCBinPackProtocolList: Never diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..7b056d2a --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,11 @@ +On Windows we use msys2 and ucrt64 to compile. +You need to prefix commands with `C:\msys64\msys2_shell.cmd -defterm -here -no-start -ucrt64 -c`. + +Prefix build directories with `cmake-build-`. + +The test executable is named `test_sunshine` and will be located inside the `tests` directory within +the build directory. + +The project uses gtest as a test framework. + +Always follow the style guidelines defined in .clang-format for c/c++ code. diff --git a/cmake/FindSystemd.cmake b/cmake/FindSystemd.cmake index c41ca64d..ee5c70d3 100644 --- a/cmake/FindSystemd.cmake +++ b/cmake/FindSystemd.cmake @@ -4,6 +4,7 @@ # SYSTEMD_FOUND - system has systemd # SYSTEMD_USER_UNIT_INSTALL_DIR - the systemd system unit install directory # SYSTEMD_SYSTEM_UNIT_INSTALL_DIR - the systemd user unit install directory +# SYSTEMD_MODULES_LOAD_DIR - the systemd modules-load.d directory IF (NOT WIN32) @@ -14,20 +15,21 @@ IF (NOT WIN32) if (SYSTEMD_FOUND) execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} - --variable=systemduserunitdir systemd + --variable=systemd_user_unit_dir systemd + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE SYSTEMD_USER_UNIT_INSTALL_DIR) - string(REGEX REPLACE "[ \t\n]+" "" SYSTEMD_USER_UNIT_INSTALL_DIR - "${SYSTEMD_USER_UNIT_INSTALL_DIR}") - execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} - --variable=systemdsystemunitdir systemd + --variable=systemd_system_unit_dir systemd + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE SYSTEMD_SYSTEM_UNIT_INSTALL_DIR) - string(REGEX REPLACE "[ \t\n]+" "" SYSTEMD_SYSTEM_UNIT_INSTALL_DIR - "${SYSTEMD_SYSTEM_UNIT_INSTALL_DIR}") + execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} + --variable=modules_load_dir systemd + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE SYSTEMD_MODULES_LOAD_DIR) - mark_as_advanced(SYSTEMD_USER_UNIT_INSTALL_DIR SYSTEMD_SYSTEM_UNIT_INSTALL_DIR) + mark_as_advanced(SYSTEMD_USER_UNIT_INSTALL_DIR SYSTEMD_SYSTEM_UNIT_INSTALL_DIR SYSTEMD_MODULES_LOAD_DIR) endif () diff --git a/cmake/FindUdev.cmake b/cmake/FindUdev.cmake index 8343f791..35b03ce7 100644 --- a/cmake/FindUdev.cmake +++ b/cmake/FindUdev.cmake @@ -3,26 +3,50 @@ # # UDEV_FOUND - system has udev # UDEV_RULES_INSTALL_DIR - the udev rules install directory +# UDEVADM_EXECUTABLE - path to udevadm executable +# UDEV_VERSION - version of udev/systemd -IF (NOT WIN32) - +if(NOT WIN32) find_package(PkgConfig QUIET) if(PKG_CONFIG_FOUND) pkg_check_modules(UDEV "udev") endif() - if (UDEV_FOUND) - execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} - --variable=udevdir udev - OUTPUT_VARIABLE UDEV_RULES_INSTALL_DIR) + if(UDEV_FOUND) + if(UDEV_VERSION) + message(STATUS "Found udev/systemd version: ${UDEV_VERSION}") + else() + message(WARNING "Could not determine udev/systemd version") + set(UDEV_VERSION "0") + endif() - string(REGEX REPLACE "[ \t\n]+" "" UDEV_RULES_INSTALL_DIR - "${UDEV_RULES_INSTALL_DIR}") + execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} + --variable=udev_dir udev + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE UDEV_RULES_INSTALL_DIR) set(UDEV_RULES_INSTALL_DIR "${UDEV_RULES_INSTALL_DIR}/rules.d") mark_as_advanced(UDEV_RULES_INSTALL_DIR) - endif () + # Check if udevadm is available + find_program(UDEVADM_EXECUTABLE udevadm + PATHS /usr/bin /bin /usr/sbin /sbin + DOC "Path to udevadm executable") + mark_as_advanced(UDEVADM_EXECUTABLE) -ENDIF () + # Handle version requirements + if(Udev_FIND_VERSION) + if(UDEV_VERSION VERSION_LESS Udev_FIND_VERSION) + set(UDEV_FOUND FALSE) + if(Udev_FIND_REQUIRED) + message(FATAL_ERROR "Udev version ${UDEV_VERSION} less than required version ${Udev_FIND_VERSION}") + else() + message(STATUS "Udev version ${UDEV_VERSION} less than required version ${Udev_FIND_VERSION}") + endif() + else() + message(STATUS "Udev version ${UDEV_VERSION} meets requirement (>= ${Udev_FIND_VERSION})") + endif() + endif() + endif() +endif() diff --git a/cmake/compile_definitions/linux.cmake b/cmake/compile_definitions/linux.cmake index c019af67..1130ed97 100644 --- a/cmake/compile_definitions/linux.cmake +++ b/cmake/compile_definitions/linux.cmake @@ -21,45 +21,32 @@ if(${SUNSHINE_ENABLE_CUDA}) message(STATUS "CUDA Compiler Version: ${CMAKE_CUDA_COMPILER_VERSION}") set(CMAKE_CUDA_ARCHITECTURES "") - # https://tech.amikelive.com/node-930/cuda-compatibility-of-nvidia-display-gpu-drivers/ - if(CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 6.5) - list(APPEND CMAKE_CUDA_ARCHITECTURES 10) - elseif(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 6.5) - list(APPEND CMAKE_CUDA_ARCHITECTURES 50 52) + # https://docs.nvidia.com/cuda/archive/12.0.0/cuda-compiler-driver-nvcc/index.html + if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0) + list(APPEND CMAKE_CUDA_ARCHITECTURES 75 80 86 87 89 90) + else() + message(FATAL_ERROR + "Sunshine requires a minimum CUDA Compiler version of 12.0. + Found version: ${CMAKE_CUDA_COMPILER_VERSION}" + ) endif() - if(CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 7.0) - list(APPEND CMAKE_CUDA_ARCHITECTURES 11) - elseif(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER 7.6) - list(APPEND CMAKE_CUDA_ARCHITECTURES 60 61 62) + # https://docs.nvidia.com/cuda/archive/12.8.0/cuda-compiler-driver-nvcc/index.html + if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 12.8) + list(APPEND CMAKE_CUDA_ARCHITECTURES 100 101 120) endif() - # https://docs.nvidia.com/cuda/archive/9.2/cuda-compiler-driver-nvcc/index.html - if(CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 9.0) - list(APPEND CMAKE_CUDA_ARCHITECTURES 20) - elseif(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 9.0) - list(APPEND CMAKE_CUDA_ARCHITECTURES 70) + # https://docs.nvidia.com/cuda/archive/12.9.0/cuda-compiler-driver-nvcc/index.html + if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 12.9) + list(APPEND CMAKE_CUDA_ARCHITECTURES 103 121) endif() - # https://docs.nvidia.com/cuda/archive/10.0/cuda-compiler-driver-nvcc/index.html - if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 10.0) - list(APPEND CMAKE_CUDA_ARCHITECTURES 72 75) - endif() - - # https://docs.nvidia.com/cuda/archive/11.0/cuda-compiler-driver-nvcc/index.html - if(CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 11.0) - list(APPEND CMAKE_CUDA_ARCHITECTURES 30) - elseif(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 11.0) - list(APPEND CMAKE_CUDA_ARCHITECTURES 80) - endif() - - # https://docs.nvidia.com/cuda/archive/11.8.0/cuda-compiler-driver-nvcc/index.html - if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 11.8) - list(APPEND CMAKE_CUDA_ARCHITECTURES 86 87 89 90) - endif() - - if(CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 12.0) - list(APPEND CMAKE_CUDA_ARCHITECTURES 35) + # https://docs.nvidia.com/cuda/archive/13.0.0/cuda-compiler-driver-nvcc/index.html + if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 13.0) + list(REMOVE_ITEM CMAKE_CUDA_ARCHITECTURES 101) + list(APPEND CMAKE_CUDA_ARCHITECTURES 110) + else() + list(APPEND CMAKE_CUDA_ARCHITECTURES 50 52 53 60 61 62 70 72) endif() # sort the architectures diff --git a/cmake/compile_definitions/macos.cmake b/cmake/compile_definitions/macos.cmake index fb33d3bf..448ad65e 100644 --- a/cmake/compile_definitions/macos.cmake +++ b/cmake/compile_definitions/macos.cmake @@ -28,9 +28,6 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES set(APPLE_PLIST_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/Info.plist") -# todo - tray is not working on macos -set(SUNSHINE_TRAY 0) - set(PLATFORM_TARGET_FILES "${CMAKE_SOURCE_DIR}/src/platform/macos/av_audio.h" "${CMAKE_SOURCE_DIR}/src/platform/macos/av_audio.m" diff --git a/cmake/dependencies/common.cmake b/cmake/dependencies/common.cmake index c92b4777..97319be6 100644 --- a/cmake/dependencies/common.cmake +++ b/cmake/dependencies/common.cmake @@ -51,10 +51,10 @@ if(NOT DEFINED FFMPEG_PREPARED_BINARIES) endif() set(FFMPEG_LIBRARIES "${FFMPEG_PREPARED_BINARIES}/lib/libavcodec.a" + "${FFMPEG_PREPARED_BINARIES}/lib/libswscale.a" "${FFMPEG_PREPARED_BINARIES}/lib/libavutil.a" "${FFMPEG_PREPARED_BINARIES}/lib/libcbs.a" "${FFMPEG_PREPARED_BINARIES}/lib/libSvtAv1Enc.a" - "${FFMPEG_PREPARED_BINARIES}/lib/libswscale.a" "${FFMPEG_PREPARED_BINARIES}/lib/libx264.a" "${FFMPEG_PREPARED_BINARIES}/lib/libx265.a" ${HDR10_PLUS_LIBRARY} @@ -62,9 +62,9 @@ if(NOT DEFINED FFMPEG_PREPARED_BINARIES) else() set(FFMPEG_LIBRARIES "${FFMPEG_PREPARED_BINARIES}/lib/libavcodec.a" + "${FFMPEG_PREPARED_BINARIES}/lib/libswscale.a" "${FFMPEG_PREPARED_BINARIES}/lib/libavutil.a" "${FFMPEG_PREPARED_BINARIES}/lib/libcbs.a" - "${FFMPEG_PREPARED_BINARIES}/lib/libswscale.a" ${FFMPEG_PLATFORM_LIBRARIES}) endif() diff --git a/cmake/packaging/linux.cmake b/cmake/packaging/linux.cmake index 24d084a7..e59663bf 100644 --- a/cmake/packaging/linux.cmake +++ b/cmake/packaging/linux.cmake @@ -14,6 +14,8 @@ file(CREATE_LINK "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/assets/shaders" if(${SUNSHINE_BUILD_APPIMAGE} OR ${SUNSHINE_BUILD_FLATPAK}) install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/60-sunshine.rules" DESTINATION "${SUNSHINE_ASSETS_DIR}/udev/rules.d") + install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/60-sunshine.conf" + DESTINATION "${SUNSHINE_ASSETS_DIR}/modules-load.d") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "${SUNSHINE_ASSETS_DIR}/systemd/user") else() @@ -27,6 +29,8 @@ else() if(SYSTEMD_FOUND) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "${SYSTEMD_USER_UNIT_INSTALL_DIR}") + install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/60-sunshine.conf" + DESTINATION "${SYSTEMD_MODULES_LOAD_DIR}") endif() endif() @@ -42,6 +46,7 @@ set(CPACK_RPM_USER_FILELIST "%caps(cap_sys_admin+p) ${SUNSHINE_EXECUTABLE_PATH}" set(CPACK_DEB_COMPONENT_INSTALL ON) set(CPACK_DEBIAN_PACKAGE_DEPENDS "\ ${CPACK_DEB_PLATFORM_PACKAGE_DEPENDS} \ + debianutils, \ libcap2, \ libcurl4, \ libdrm2, \ @@ -70,7 +75,8 @@ set(CPACK_RPM_PACKAGE_REQUIRES "\ miniupnpc >= 2.2.4, \ numactl-libs >= 2.0.14, \ openssl >= 3.0.2, \ - pulseaudio-libs >= 10.0") + pulseaudio-libs >= 10.0, \ + which >= 2.21") if(NOT BOOST_USE_STATIC) set(CPACK_DEBIAN_PACKAGE_DEPENDS "\ diff --git a/cmake/packaging/windows_nsis.cmake b/cmake/packaging/windows_nsis.cmake index 50e43749..8b37d658 100644 --- a/cmake/packaging/windows_nsis.cmake +++ b/cmake/packaging/windows_nsis.cmake @@ -17,7 +17,7 @@ SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\migrate-config.bat\\\"' nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\add-firewall-rule.bat\\\"' nsExec::ExecToLog \ - 'powershell.exe -ExecutionPolicy Bypass -File \\\"$INSTDIR\\\\scripts\\\\install-gamepad.ps1\\\"' + '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: @@ -34,7 +34,8 @@ set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS 'Do you want to remove Virtual Gamepad?' \ /SD IDNO IDNO NoGamepad nsExec::ExecToLog \ - 'powershell.exe -ExecutionPolicy Bypass -File \\\"$INSTDIR\\\\scripts\\\\uninstall-gamepad.ps1\\\"'; \ + 'powershell.exe -NoProfile -ExecutionPolicy Bypass -File \ + \\\"$INSTDIR\\\\scripts\\\\uninstall-gamepad.ps1\\\"'; \ skipped if no NoGamepad: MessageBox MB_YESNO|MB_ICONQUESTION \ diff --git a/cmake/prep/options.cmake b/cmake/prep/options.cmake index 8438d775..4c679aaa 100644 --- a/cmake/prep/options.cmake +++ b/cmake/prep/options.cmake @@ -16,7 +16,7 @@ option(BUILD_WERROR "Enable -Werror flag." OFF) # if this option is set, the build will exit after configuring special package configuration files option(SUNSHINE_CONFIGURE_ONLY "Configure special files only, then exit." OFF) -option(SUNSHINE_ENABLE_TRAY "Enable system tray icon. This option will be ignored on macOS." ON) +option(SUNSHINE_ENABLE_TRAY "Enable system tray icon." ON) option(SUNSHINE_SYSTEM_WAYLAND_PROTOCOLS "Use system installation of wayland-protocols rather than the submodule." OFF) diff --git a/docker/archlinux.dockerfile b/docker/archlinux.dockerfile index 63a9be63..6d23160c 100644 --- a/docker/archlinux.dockerfile +++ b/docker/archlinux.dockerfile @@ -17,21 +17,11 @@ pacman -Syu --disable-download-timeout --noconfirm pacman -Scc --noconfirm _DEPS -FROM sunshine-base AS sunshine-build - -ARG BRANCH -ARG BUILD_VERSION -ARG COMMIT -ARG CLONE_URL -# note: BUILD_VERSION may be blank - -ENV BRANCH=${BRANCH} -ENV BUILD_VERSION=${BUILD_VERSION} -ENV COMMIT=${COMMIT} -ENV CLONE_URL=${CLONE_URL} +FROM sunshine-base AS sunshine-deps SHELL ["/bin/bash", "-o", "pipefail", "-c"] +# Install dependencies first - this layer will be cached RUN <<_SETUP #!/bin/bash set -e @@ -55,6 +45,26 @@ pacman -Syu --disable-download-timeout --needed --noconfirm \ pacman -Scc --noconfirm _SETUP +FROM sunshine-deps AS sunshine-build + +ARG BRANCH +ARG BUILD_VERSION +ARG COMMIT +ARG CLONE_URL +# note: BUILD_VERSION may be blank + +ENV BRANCH=${BRANCH} +ENV BUILD_VERSION=${BUILD_VERSION} +ENV COMMIT=${COMMIT} +ENV CLONE_URL=${CLONE_URL} + +# PKGBUILD options +ENV _use_cuda=true +ENV _run_unit_tests=true +ENV _support_headless_testing=true + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + # Setup builder user USER builder diff --git a/docker/clion-toolchain.dockerfile b/docker/clion-toolchain.dockerfile index f5080d18..23464190 100644 --- a/docker/clion-toolchain.dockerfile +++ b/docker/clion-toolchain.dockerfile @@ -3,8 +3,8 @@ # platforms: linux/amd64 # platforms_pr: linux/amd64 # no-cache-filters: toolchain-base,toolchain -ARG BASE=ubuntu -ARG TAG=22.04 +ARG BASE=debian +ARG TAG=trixie-slim FROM ${BASE}:${TAG} AS toolchain-base ENV DEBIAN_FRONTEND=noninteractive @@ -25,11 +25,11 @@ set -e apt-get update -y apt-get install -y --no-install-recommends \ build-essential \ - cmake=3.22.* \ + cmake=3.31.* \ ca-certificates \ doxygen \ - gcc=4:11.2.* \ - g++=4:11.2.* \ + gcc=4:14.2.* \ + g++=4:14.2.* \ gdb \ git \ graphviz \ @@ -38,6 +38,7 @@ apt-get install -y --no-install-recommends \ libcurl4-openssl-dev \ libdrm-dev \ libevdev-dev \ + libgbm-dev \ libminiupnpc-dev \ libnotify-dev \ libnuma-dev \ @@ -53,26 +54,20 @@ apt-get install -y --no-install-recommends \ libxfixes-dev \ libxrandr-dev \ libxtst-dev \ + npm \ udev \ wget \ x11-xserver-utils \ xvfb apt-get clean rm -rf /var/lib/apt/lists/* - -# Install Node -wget --max-redirect=0 -qO- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash -source "$HOME/.nvm/nvm.sh" -nvm install node -nvm use node -nvm alias default node _DEPS # install cuda WORKDIR /build/cuda # versions: https://developer.nvidia.com/cuda-toolkit-archive -ENV CUDA_VERSION="11.8.0" -ENV CUDA_BUILD="520.61.05" +ENV CUDA_VERSION="12.9.1" +ENV CUDA_BUILD="575.57.08" RUN <<_INSTALL_CUDA #!/bin/bash set -e @@ -83,18 +78,19 @@ if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then fi url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run" echo "cuda url: ${url}" -wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run -chmod a+x ./cuda.run -./cuda.run --silent --toolkit --toolkitpath=/usr/local --no-opengl-libs --no-man-page --no-drm -rm ./cuda.run +tmpfile="/tmp/cuda.run" +wget "$url" --progress=bar:force:noscroll --show-progress -O "$tmpfile" +chmod a+x "${tmpfile}" +"${tmpfile}" --silent --toolkit --toolkitpath=/usr/local --no-opengl-libs --no-man-page --no-drm +rm -f "${tmpfile}" _INSTALL_CUDA -WORKDIR / -# Write a shell script that starts Xvfb and then runs a shell +WORKDIR /toolchain +# Create a shell script that starts Xvfb and then runs a shell RUN <<_ENTRYPOINT #!/bin/bash set -e -cat < /entrypoint.sh +cat < entrypoint.sh #!/bin/bash Xvfb ${DISPLAY} -screen 0 1024x768x24 & if [ "\$#" -eq 0 ]; then @@ -105,11 +101,11 @@ fi EOF # Make the script executable -chmod +x /entrypoint.sh +chmod +x entrypoint.sh # Note about CLion echo "ATTENTION: CLion will override the entrypoint, you can disable this in the toolchain settings" _ENTRYPOINT # Use the shell script as the entrypoint -ENTRYPOINT ["/entrypoint.sh"] +ENTRYPOINT ["/toolchain/entrypoint.sh"] diff --git a/docker/debian-bookworm.dockerfile b/docker/debian-trixie.dockerfile similarity index 74% rename from docker/debian-bookworm.dockerfile rename to docker/debian-trixie.dockerfile index ffa68b7c..78d8637e 100644 --- a/docker/debian-bookworm.dockerfile +++ b/docker/debian-trixie.dockerfile @@ -4,12 +4,34 @@ # platforms_pr: linux/amd64 # no-cache-filters: sunshine-base,artifacts,sunshine ARG BASE=debian -ARG TAG=bookworm +ARG TAG=trixie FROM ${BASE}:${TAG} AS sunshine-base ENV DEBIAN_FRONTEND=noninteractive -FROM sunshine-base AS sunshine-build +FROM sunshine-base AS sunshine-deps + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# Copy only the build script and necessary files first for better layer caching +WORKDIR /build/sunshine/ +COPY --link scripts/linux_build.sh ./scripts/linux_build.sh +COPY --link packaging/linux/patches/ ./packaging/linux/patches/ + +# Install dependencies first - this layer will be cached +RUN <<_DEPS +#!/bin/bash +set -e +chmod +x ./scripts/linux_build.sh +./scripts/linux_build.sh \ + --step=deps \ + --cuda-patches \ + --sudo-off +apt-get clean +rm -rf /var/lib/apt/lists/* +_DEPS + +FROM sunshine-deps AS sunshine-build ARG BRANCH ARG BUILD_VERSION @@ -20,24 +42,31 @@ ENV BRANCH=${BRANCH} ENV BUILD_VERSION=${BUILD_VERSION} ENV COMMIT=${COMMIT} -SHELL ["/bin/bash", "-o", "pipefail", "-c"] - -# copy repository -WORKDIR /build/sunshine/ +# Now copy the full repository COPY --link .. . -# cmake and cpack +# Configure, validate, build and package RUN <<_BUILD #!/bin/bash set -e -chmod +x ./scripts/linux_build.sh ./scripts/linux_build.sh \ + --step=cmake \ --publisher-name='LizardByte' \ --publisher-website='https://app.lizardbyte.dev' \ --publisher-issue-url='https://app.lizardbyte.dev/support' \ --sudo-off -apt-get clean -rm -rf /var/lib/apt/lists/* + +./scripts/linux_build.sh \ + --step=validation \ + --sudo-off + +./scripts/linux_build.sh \ + --step=build \ + --sudo-off + +./scripts/linux_build.sh \ + --step=package \ + --sudo-off _BUILD # run tests diff --git a/docker/ubuntu-22.04.dockerfile b/docker/ubuntu-22.04.dockerfile index 06032175..1693367c 100644 --- a/docker/ubuntu-22.04.dockerfile +++ b/docker/ubuntu-22.04.dockerfile @@ -9,7 +9,28 @@ FROM ${BASE}:${TAG} AS sunshine-base ENV DEBIAN_FRONTEND=noninteractive -FROM sunshine-base AS sunshine-build +FROM sunshine-base AS sunshine-deps + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# Copy only the build script first for better layer caching +WORKDIR /build/sunshine/ +COPY --link scripts/linux_build.sh ./scripts/linux_build.sh + +# Install dependencies first - this layer will be cached +RUN <<_DEPS +#!/bin/bash +set -e +chmod +x ./scripts/linux_build.sh +./scripts/linux_build.sh \ + --step=deps \ + --ubuntu-test-repo \ + --sudo-off +apt-get clean +rm -rf /var/lib/apt/lists/* +_DEPS + +FROM sunshine-deps AS sunshine-build ARG BRANCH ARG BUILD_VERSION @@ -20,24 +41,31 @@ ENV BRANCH=${BRANCH} ENV BUILD_VERSION=${BUILD_VERSION} ENV COMMIT=${COMMIT} -SHELL ["/bin/bash", "-o", "pipefail", "-c"] - -# copy repository -WORKDIR /build/sunshine/ +# Now copy the full repository COPY --link .. . -# cmake and cpack +# Configure, validate, build and package RUN <<_BUILD #!/bin/bash set -e -chmod +x ./scripts/linux_build.sh ./scripts/linux_build.sh \ + --step=cmake \ --publisher-name='LizardByte' \ --publisher-website='https://app.lizardbyte.dev' \ --publisher-issue-url='https://app.lizardbyte.dev/support' \ --sudo-off -apt-get clean -rm -rf /var/lib/apt/lists/* + +./scripts/linux_build.sh \ + --step=validation \ + --sudo-off + +./scripts/linux_build.sh \ + --step=build \ + --sudo-off + +./scripts/linux_build.sh \ + --step=package \ + --sudo-off _BUILD # run tests diff --git a/docker/ubuntu-24.04.dockerfile b/docker/ubuntu-24.04.dockerfile index e11f18b2..6c0f82fc 100644 --- a/docker/ubuntu-24.04.dockerfile +++ b/docker/ubuntu-24.04.dockerfile @@ -9,7 +9,27 @@ FROM ${BASE}:${TAG} AS sunshine-base ENV DEBIAN_FRONTEND=noninteractive -FROM sunshine-base AS sunshine-build +FROM sunshine-base AS sunshine-deps + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# Copy only the build script first for better layer caching +WORKDIR /build/sunshine/ +COPY --link scripts/linux_build.sh ./scripts/linux_build.sh + +# Install dependencies first - this layer will be cached +RUN <<_DEPS +#!/bin/bash +set -e +chmod +x ./scripts/linux_build.sh +./scripts/linux_build.sh \ + --step=deps \ + --sudo-off +apt-get clean +rm -rf /var/lib/apt/lists/* +_DEPS + +FROM sunshine-deps AS sunshine-build ARG BRANCH ARG BUILD_VERSION @@ -20,24 +40,31 @@ ENV BRANCH=${BRANCH} ENV BUILD_VERSION=${BUILD_VERSION} ENV COMMIT=${COMMIT} -SHELL ["/bin/bash", "-o", "pipefail", "-c"] - -# copy repository -WORKDIR /build/sunshine/ +# Now copy the full repository COPY --link .. . -# cmake and cpack +# Configure, validate, build and package RUN <<_BUILD #!/bin/bash set -e -chmod +x ./scripts/linux_build.sh ./scripts/linux_build.sh \ + --step=cmake \ --publisher-name='LizardByte' \ --publisher-website='https://app.lizardbyte.dev' \ --publisher-issue-url='https://app.lizardbyte.dev/support' \ --sudo-off -apt-get clean -rm -rf /var/lib/apt/lists/* + +./scripts/linux_build.sh \ + --step=validation \ + --sudo-off + +./scripts/linux_build.sh \ + --step=build \ + --sudo-off + +./scripts/linux_build.sh \ + --step=package \ + --sudo-off _BUILD # run tests diff --git a/docs/app_examples.md b/docs/app_examples.md index 782681fd..85f70337 100644 --- a/docs/app_examples.md +++ b/docs/app_examples.md @@ -2,10 +2,12 @@ Since not all applications behave the same, we decided to create some examples to help you get started adding games and applications to Sunshine. -@attention{Throughout these examples, any fields not shown are left blank. You can enhance your experience by -adding an image or a log file (via the `Output` field).} +> [!TIP] +> Throughout these examples, any fields not shown are left blank. You can enhance your experience by +> adding an image or a log file (via the `Output` field). -@note{When a working directory is not specified, it defaults to the folder where the target application resides.} +> [!WARNING] +> When a working directory is not specified, it defaults to the folder where the target application resides. ## Common Examples @@ -18,8 +20,10 @@ adding an image or a log file (via the `Output` field).} | Image | @code{}desktop.png@endcode | ### Steam Big Picture -@note{Steam is launched as a detached command because Steam starts with a process that self updates itself and the original -process is killed.} + +> [!NOTE] +> Steam is launched as a detached command because Steam starts with a process that self updates itself and the original +> process is killed. @tabs{ @tab{Linux | @@ -49,7 +53,9 @@ process is killed.} } ### Epic Game Store game -@note{Using URI method will be the most consistent between various games.} + +> [!NOTE] +> Using the URI method will be the most consistent between various games. #### URI @@ -84,7 +90,9 @@ process is killed.} } ### Steam game -@note{Using URI method will be the most consistent between various games.} + +> [!NOTE] +> Using the URI method will be the most consistent between various games. #### URI @@ -169,49 +177,49 @@ process is killed.} | Do | @code{}sh -c "xrandr --output HDMI-1 --mode ${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT} --rate ${SUNSHINE_CLIENT_FPS}"@endcode | | Undo | @code{}xrandr --output HDMI-1 --mode 3840x2160 --rate 120@endcode | -@hint{The above only works if the xrandr mode already exists. You will need to create new modes to stream to macOS -and iOS devices, since they use non-standard resolutions. - -You can update the ``Do`` command to this: -```bash -bash -c "${HOME}/scripts/set-custom-res.sh \"${SUNSHINE_CLIENT_WIDTH}\" \"${SUNSHINE_CLIENT_HEIGHT}\" \"${SUNSHINE_CLIENT_FPS}\"" -``` - -The `set-custom-res.sh` will have this content: -```bash -#!/bin/bash -set -e - -# Get params and set any defaults -width=${1:-1920} -height=${2:-1080} -refresh_rate=${3:-60} - -# You may need to adjust the scaling differently so the UI/text isn't too small / big -scale=${4:-0.55} - -# Get the name of the active display -display_output=$(xrandr | grep " connected" | awk '{ print $1 }') - -# Get the modeline info from the 2nd row in the cvt output -modeline=$(cvt ${width} ${height} ${refresh_rate} | awk 'FNR == 2') -xrandr_mode_str=${modeline//Modeline \"*\" /} -mode_alias="${width}x${height}" - -echo "xrandr setting new mode ${mode_alias} ${xrandr_mode_str}" -xrandr --newmode ${mode_alias} ${xrandr_mode_str} -xrandr --addmode ${display_output} ${mode_alias} - -# Reset scaling -xrandr --output ${display_output} --scale 1 - -# Apply new xrandr mode -xrandr --output ${display_output} --primary --mode ${mode_alias} --pos 0x0 --rotate normal --scale ${scale} - -# Optional reset your wallpaper to fit to new resolution -# xwallpaper --zoom /path/to/wallpaper.png -``` -} +> [!TIP] +> The above only works if the xrandr mode already exists. You will need to create new modes to stream to macOS +> and iOS devices, since they use non-standard resolutions. +> +> You can update the ``Do`` command to this: +> ```bash +> bash -c "${HOME}/scripts/set-custom-res.sh \"${SUNSHINE_CLIENT_WIDTH}\" \"${SUNSHINE_CLIENT_HEIGHT}\" \"${SUNSHINE_CLIENT_FPS}\"" +> ``` +> +> The `set-custom-res.sh` will have this content: +> ```bash +> #!/bin/bash +> set -e +> +> # Get params and set any defaults +> width=${1:-1920} +> height=${2:-1080} +> refresh_rate=${3:-60} +> +> # You may need to adjust the scaling differently so the UI/text isn't too small / big +> scale=${4:-0.55} +> +> # Get the name of the active display +> display_output=$(xrandr | grep " connected" | awk '{ print $1 }') +> +> # Get the modeline info from the 2nd row in the cvt output +> modeline=$(cvt ${width} ${height} ${refresh_rate} | awk 'FNR == 2') +> xrandr_mode_str=${modeline//Modeline \"*\" /} +> mode_alias="${width}x${height}" +> +> echo "xrandr setting new mode ${mode_alias} ${xrandr_mode_str}" +> xrandr --newmode ${mode_alias} ${xrandr_mode_str} +> xrandr --addmode ${display_output} ${mode_alias} +> +> # Reset scaling +> xrandr --output ${display_output} --scale 1 +> +> # Apply new xrandr mode +> xrandr --output ${display_output} --primary --mode ${mode_alias} --pos 0x0 --rotate normal --scale ${scale} +> +> # Optional reset your wallpaper to fit to new resolution +> # xwallpaper --zoom /path/to/wallpaper.png +> ``` ###### Wayland (wlroots, e.g. hyprland) @@ -220,7 +228,8 @@ xrandr --output ${display_output} --primary --mode ${mode_alias} --pos 0x0 --rot | Do | @code{}sh -c "wlr-xrandr --output HDMI-1 --mode \"${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}@${SUNSHINE_CLIENT_FPS}Hz\""@endcode | | Undo | @code{}wlr-xrandr --output HDMI-1 --mode 3840x2160@120Hz@endcode | -@hint{`wlr-xrandr` only works with wlroots-based compositors.} +> [!TIP] +> `wlr-xrandr` only works with wlroots-based compositors. ###### Gnome (X11) @@ -240,12 +249,12 @@ Installation instructions for displayconfig-mutter can be [found here](https://g [gnome-randr-rust](https://github.com/maxwellainatchi/gnome-randr-rust) and [gnome-randr.py](https://gitlab.com/Oschowa/gnome-randr), but both of those are unmaintained and do not support newer Mutter features such as HDR and VRR. -@hint{HDR support has been added to Gnome 48, to check if your display supports it you can run this: -``` -displayconfig-mutter list -``` -If it doesn't, then remove ``--hdr`` flag from both ``Do`` and ``Undo`` steps. -} +> [!TIP] +> HDR support has been added to Gnome 48, to check if your display supports it, you can run this: +> ``` +> displayconfig-mutter list +> ``` +> If it doesn't, then remove ``--hdr`` flag from both ``Do`` and ``Undo`` steps. ###### KDE Plasma (Wayland, X11) @@ -254,22 +263,22 @@ If it doesn't, then remove ``--hdr`` flag from both ``Do`` and ``Undo`` steps. | Do | @code{}sh -c "kscreen-doctor output.HDMI-A-1.mode.${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}@${SUNSHINE_CLIENT_FPS}"@endcode | | Undo | @code{}kscreen-doctor output.HDMI-A-1.mode.3840x2160@120@endcode | -@attention{The names of your displays will differ between X11 and Wayland. -Be sure to use the correct name, depending on your session manager. -e.g. On X11, the monitor may be called ``HDMI-A-0``, but on Wayland, it may be called ``HDMI-A-1``. -} +> [!CAUTION] +> The names of your displays will differ between X11 and Wayland. +> Be sure to use the correct name, depending on your session manager. +> e.g., On X11, the monitor may be called ``HDMI-A-0``, but on Wayland, it may be called ``HDMI-A-1``. -@hint{Replace ``HDMI-A-1`` with the display name of the monitor you would like to use for Moonlight. -You can list the monitors available to you with: -``` -kscreen-doctor -o -``` - -These will also give you the supported display properties for each monitor. You can select them either by -hard-coding their corresponding number (e.g. ``kscreen-doctor output.HDMI-A1.mode.0``) or using the above -``do`` command to fetch the resolution requested by your Moonlight client -(which has a chance of not being supported by your monitor). -} +> [!TIP] +> Replace ``HDMI-A-1`` with the display name of the monitor you would like to use for Moonlight. +> You can list the monitors available to you with: +> ``` +> kscreen-doctor -o +> ``` +> +> These will also give you the supported display properties for each monitor. You can select them either by +> hard-coding their corresponding number (e.g. ``kscreen-doctor output.HDMI-A1.mode.0``) or using the above +> ``do`` command to fetch the resolution requested by your Moonlight client +> (which has a chance of not being supported by your monitor). ###### NVIDIA @@ -281,9 +290,11 @@ hard-coding their corresponding number (e.g. ``kscreen-doctor output.HDMI-A1.mod ##### macOS ###### displayplacer -@note{This example uses the `displayplacer` tool to change the resolution. -This tool can be installed following instructions in their -[GitHub repository](https://github.com/jakehilborn/displayplacer)}. + +> [!NOTE] +> This example uses the `displayplacer` tool to change the resolution. +> This tool can be installed following instructions in their +> [GitHub repository](https://github.com/jakehilborn/displayplacer). | Prep Step | Command | |-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -295,8 +306,10 @@ Sunshine has built-in support for changing the resolution and refresh rate on Wi third-party tool, you can use *QRes* as an example. ###### QRes -@note{This example uses the *QRes* tool to change the resolution and refresh rate. -This tool can be downloaded from their [SourceForge repository](https://sourceforge.net/projects/qres).} + +> [!NOTE] +> This example uses the *QRes* tool to change the resolution and refresh rate. +> This tool can be downloaded from their [SourceForge repository](https://sourceforge.net/projects/qres). | Prep Step | Command | |-----------|---------------------------------------------------------------------------------------------------------------------------| @@ -306,8 +319,10 @@ This tool can be downloaded from their [SourceForge repository](https://sourcefo ### Additional Considerations #### Linux (Flatpak) -@attention{Because Flatpak packages run in a sandboxed environment and do not normally have access to the -host, the Flatpak of Sunshine requires commands to be prefixed with `flatpak-spawn --host`.} + +> [!CAUTION] +> Because Flatpak packages run in a sandboxed environment and do not normally have access to the +> host, the Flatpak of Sunshine requires commands to be prefixed with `flatpak-spawn --host`. #### Windows **Elevating Commands (Windows)** diff --git a/docs/building.md b/docs/building.md index f8e8a01d..ecc9f6b9 100644 --- a/docs/building.md +++ b/docs/building.md @@ -3,6 +3,15 @@ Sunshine binaries are built using [CMake](https://cmake.org) and requires `cmake ## Building Locally +### Compiler +It is recommended to use one of the following compilers: + +| Compiler | Version | +|:------------|:--------| +| GCC | 13+ | +| Clang | 17+ | +| Apple Clang | 15+ | + ### Dependencies #### Linux @@ -16,11 +25,12 @@ Sunshine requires CUDA Toolkit for NVFBC capture. There are two caveats to CUDA: 1. The version installed depends on the version of GCC. 2. The version of CUDA you use will determine compatibility with various GPU generations. - At the time of writing, the recommended version to use is CUDA ~11.8. + At the time of writing, the recommended version to use is CUDA ~12.9. See [CUDA compatibility](https://docs.nvidia.com/deploy/cuda-compatibility/index.html) for more info. -@tip{To install older versions, select the appropriate run file based on your desired CUDA version and architecture -according to [CUDA Toolkit Archive](https://developer.nvidia.com/cuda-toolkit-archive)} +> [!NOTE] +> To install older versions, select the appropriate run file based on your desired CUDA version and architecture +> according to [CUDA Toolkit Archive](https://developer.nvidia.com/cuda-toolkit-archive) #### macOS You can either use [Homebrew](https://brew.sh) or [MacPorts](https://www.macports.org) to install dependencies. @@ -119,8 +129,9 @@ cmake -B build -G Ninja -S . ninja -C build ``` -@tip{Available build options can be found in -[options.cmake](https://github.com/LizardByte/Sunshine/blob/master/cmake/prep/options.cmake).} +> [!TIP] +> Available build options can be found in +> [options.cmake](https://github.com/LizardByte/Sunshine/blob/master/cmake/prep/options.cmake). ### Package diff --git a/docs/configuration.md b/docs/configuration.md index b37578e2..b5377cad 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -261,6 +261,29 @@ editing the `conf` file in a text editor. Use the examples as reference. +### system_tray + + + + + + + + + + + + + + +
Description + Show icon in system tray and display desktop notifications +
Default@code{} + enabled + @endcode
Example@code{} + system_tray = enabled + @endcode
+ ## Input ### controller @@ -416,6 +439,30 @@ editing the `conf` file in a text editor. Use the examples as reference. +### ds5_inputtino_randomize_mac + + + + + + + + + + + + + + +
Description + Randomize the MAC-Address for the generated virtual controller. + @hint{Only applies on linux for gamepads created as PS5-style controllers} +
Default@code{} + enabled + @endcode
Example@code{} + ds5_inputtino_randomize_mac = enabled + @endcode
+ ### back_button_timeout diff --git a/docs/contributing.md b/docs/contributing.md index 95a6e7ab..1575ded5 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -73,13 +73,15 @@ The following is a simple example of how to use it. } ``` - @note{The json keys should be sorted alphabetically. You can use [jsonabc](https://novicelab.org/jsonabc) - to sort the keys.} + > [!NOTE] + > The JSON keys should be sorted alphabetically. You can use [jsonabc](https://novicelab.org/jsonabc) + > to sort the keys. - @attention{Due to the integration with Crowdin, it is important to only add strings to the *en.json* file, - and to not modify any other language files. After the PR is merged, the translations can take place - on [CrowdIn][crowdin-url]. Once the translations are complete, a PR will be made - to merge the translations into Sunshine.} + > [!IMPORTANT] + > Due to the integration with Crowdin, it is important to only add strings to the *en.json* file, + > and to not modify any other language files. After the PR is merged, the translations can take place + > on [CrowdIn][crowdin-url]. Once the translations are complete, a PR will be made + > to merge the translations into Sunshine. * Use the string in the Vue component. ```html @@ -90,8 +92,9 @@ The following is a simple example of how to use it. ``` - @tip{More formatting examples can be found in the - [Vue I18n guide](https://kazupon.github.io/vue-i18n/guide/formatting.html).} + > [!TIP] + > More formatting examples can be found in the + > [Vue I18n guide](https://kazupon.github.io/vue-i18n/guide/formatting.html). ##### C++ @@ -106,11 +109,13 @@ some situations. For example the system tray icon could be localized as it is us std::string msg = boost::locale::translate("Hello world!"); ``` -@tip{More examples can be found in the documentation for -[boost locale](https://www.boost.org/doc/libs/1_70_0/libs/locale/doc/html/messages_formatting.html).} +> [!TIP] +> More examples can be found in the documentation for +> [boost locale](https://www.boost.org/doc/libs/1_70_0/libs/locale/doc/html/messages_formatting.html). -@warning{The below is for information only. Contributors should never include manually updated template files, or -manually compiled language files in Pull Requests.} +> [!WARNING] +> The below is for information only. Contributors should never include manually updated template files, or +> manually compiled language files in Pull Requests. Strings are automatically extracted from the code to the `locale/sunshine.po` template file. The generated file is used by CrowdIn to generate language specific template files. The file is generated using the @@ -135,10 +140,11 @@ required for this, along with the python dependencies in the `./scripts/requirem python ./scripts/_locale.py --compile ``` -@attention{Due to the integration with CrowdIn, it is important to not include any extracted or compiled files in -Pull Requests. The files are automatically generated and updated by the workflow. Once the PR is merged, the -translations can take place on [CrowdIn][crowdin-url]. Once the translations are -complete, a PR will be made to merge the translations into Sunshine.} +> [!IMPORTANT] +> Due to the integration with CrowdIn, it is important to not include any extracted or compiled files in +> Pull Requests. The files are automatically generated and updated by the workflow. Once the PR is merged, the +> translations can take place on [CrowdIn][crowdin-url]. Once the translations are +> complete, a PR will be made to merge the translations into Sunshine. ### Testing @@ -175,8 +181,8 @@ To see all available options, run the tests with the `--help` flag. ./build/tests/test_sunshine --help ``` -@tip{See the googletest [FAQ](https://google.github.io/googletest/faq.html) for more information on how to use -Google Test.} +> [!TIP] +> See the googletest [FAQ](https://google.github.io/googletest/faq.html) for more information on how to use Google Test. We use [gcovr](https://www.gcovr.com) to generate code coverage reports, and [Codecov](https://about.codecov.io) to analyze the reports for all PRs and commits. diff --git a/docs/gamestream_migration.md b/docs/gamestream_migration.md index 8a8699b7..5018ba58 100644 --- a/docs/gamestream_migration.md +++ b/docs/gamestream_migration.md @@ -13,14 +13,15 @@ to a specified directory. If you are using the Moonlight Internet Hosting Tool, you can remove it from your system when you migrate to Sunshine. To stream over the Internet with Sunshine and a UPnP-capable router, enable the UPnP option in the Sunshine Web UI. -@note{Running Sunshine together with versions of the Moonlight Internet Hosting Tool prior to v5.6 will cause UPnP -port forwarding to become unreliable. Either uninstall the tool entirely or update it to v5.6 or later.} +> [!NOTE] +> Running Sunshine together with versions of the Moonlight Internet Hosting Tool prior to v5.6 will cause UPnP +> port forwarding to become unreliable. Either uninstall the tool entirely or update it to v5.6 or later. ## Limitations Sunshine does have some limitations, as compared to Nvidia GameStream. * Automatic game/application list. -* Changing game settings automatically, to optimize streaming. +* Changing game settings automatically to optimize streaming.
diff --git a/docs/getting_started.md b/docs/getting_started.md index f3036239..1aba10b0 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -11,14 +11,17 @@ and release artifacts may be missing when merging changes on a faster cadence. Binaries of Sunshine are created for each release. They are available for Linux, macOS, and Windows. Binaries can be found in the [latest release][latest-release]. -@tip{Some third party packages also exist. -See [Third Party Packages](third_party_packages.md) for more information. -No support will be provided for third party packages!} +> [!NOTE] +> Some third party packages also exist. +> See [Third Party Packages](third_party_packages.md) for more information. +> No support will be provided for third party packages! ## Install ### Docker -@warning{The Docker images are not recommended for most users.} + +> [!WARNING] +> The Docker images are not recommended for most users. Docker images are available on [Dockerhub.io](https://hub.docker.com/repository/docker/lizardbyte/sunshine) and [ghcr.io](https://github.com/orgs/LizardByte/packages?repo_name=sunshine). @@ -30,9 +33,10 @@ See [Docker](../DOCKER_README.md) for more information. CUDA is used for NVFBC capture. -@tip{See [CUDA GPUS](https://developer.nvidia.com/cuda-gpus) to cross-reference Compute Capability to your GPU. -The table below applies to packages provided by LizardByte. If you use an official LizardByte package then you do not -need to install CUDA.} +> [!NOTE] +> See [CUDA GPUS](https://developer.nvidia.com/cuda-gpus) to cross-reference Compute Capability to your GPU. +> The table below applies to packages provided by LizardByte. If you use an official LizardByte package, then you do not +> need to install CUDA.
@@ -43,9 +47,9 @@ need to install CUDA.} - - - + + + @@ -55,33 +59,26 @@ need to install CUDA.} - - - - + - - - - - -
CUDA Compatibility
Package
11.8.0450.80.0235;50;52;60;61;62;70;72;75;80;86;87;89;9012.9.1575.57.0850;52;60;61;62;70;72;75;80;86;87;89;90;100;101;103;120;121 sunshine.AppImage
sunshine-ubuntu-24.04-{arch}.deb
12.0.0525.60.1350;52;60;61;62;70;72;75;80;86;87;89;90sunshine-debian-bookworm-{arch}.debsunshine-debian-trixie-{arch}.deb
12.6.2560.35.03 sunshine_{arch}.flatpak
Sunshine (copr - Fedora 41)
12.8.1570.124.06 Sunshine (copr - Fedora 42)
12.9.1575.57.08 sunshine.pkg.tar.zst
#### AppImage -@caution{Use distro-specific packages instead of the AppImage if they are available.} + +> [!CAUTION] +> Use distro-specific packages instead of the AppImage if they are available. According to AppImageLint the supported distro matrix of the AppImage is below. @@ -89,13 +86,15 @@ According to AppImageLint the supported distro matrix of the AppImage is below. - ✔ Debian bookworm - ✔ Debian trixie - ✔ Debian sid +- ✔ Ubuntu plucky - ✔ Ubuntu noble - ✔ Ubuntu jammy - ✖ Ubuntu focal - ✖ Ubuntu bionic - ✖ Ubuntu xenial - ✖ Ubuntu trusty -- ✖ CentOS 7 +- ✖ Rocky Linux 8 +- ✖ Rocky Linux 9 ##### Install 1. Download [sunshine.AppImage](https://github.com/LizardByte/Sunshine/releases/latest/download/sunshine.AppImage) @@ -120,7 +119,9 @@ According to AppImageLint the supported distro matrix of the AppImage is below. ``` #### ArchLinux -@warning{We do not provide support for any AUR packages.} + +> [!CAUTION] +> Use AUR packages at your own risk. ##### Install Prebuilt Packages Follow the instructions at LizardByte's [pacman-repo](https://github.com/LizardByte/pacman-repo) to add @@ -155,10 +156,12 @@ Download `sunshine-{distro}-{distro-version}-{arch}.deb` and run the following c sudo dpkg -i ./sunshine-{distro}-{distro-version}-{arch}.deb ``` -@note{The `{distro-version}` is the version of the distro we built the package on. The `{arch}` is the -architecture of your operating system.} +> [!NOTE] +> The `{distro-version}` is the version of the distro we built the package on. The `{arch}` is the +> architecture of your operating system. -@tip{You can double-click the deb file to see details about the package and begin installation.} +> [!TIP] +> You can double-click the deb file to see details about the package and begin installation. ##### Uninstall ```bash @@ -166,7 +169,9 @@ sudo apt remove sunshine ``` #### Fedora -@tip{The package name is case-sensitive.} + +> [!TIP] +> The package name is case-sensitive. ##### Install 1. Enable copr repository. @@ -190,13 +195,17 @@ sudo dnf remove Sunshine ``` #### Flatpak -@caution{Use distro-specific packages instead of the Flatpak if they are available.} + +> [!CAUTION] +> Use distro-specific packages instead of the Flatpak if they are available. Using this package requires that you have [Flatpak](https://flatpak.org/setup) installed. ##### Download (local option) 1. Download `sunshine_{arch}.flatpak` and run the following command. - @note{Replace `{arch}` with your system architecture.} + + > [!NOTE] + > Replace `{arch}` with your system architecture. ##### Install (system level) **Flathub** @@ -242,7 +251,9 @@ flatpak uninstall --delete-data dev.lizardbyte.app.Sunshine ``` #### Homebrew -@important{The Homebrew package is experimental on Linux.} + +> [!IMPORTANT] +> The Homebrew package is experimental on Linux. This package requires that you have [Homebrew](https://docs.brew.sh/Installation) installed. @@ -261,7 +272,8 @@ brew uninstall sunshine ### macOS -@important{Sunshine on macOS is experimental. Gamepads do not work.} +> [!IMPORTANT] +> Sunshine on macOS is experimental. Gamepads do not work. #### Homebrew This package requires that you have [Homebrew](https://docs.brew.sh/Installation) installed. @@ -277,7 +289,8 @@ brew install sunshine brew uninstall sunshine ``` -@tip{For beta you can replace `sunshine` with `sunshine-beta` in the above commands.} +> [!TIP] +> For beta you can replace `sunshine` with `sunshine-beta` in the above commands. ### Windows @@ -286,16 +299,18 @@ brew uninstall sunshine 1. Download and install [Sunshine-Windows-AMD64-installer.exe](https://github.com/LizardByte/Sunshine/releases/latest/download/Sunshine-Windows-AMD64-installer.exe) -@attention{You should carefully select or unselect the options you want to install. Do not blindly install or -enable features.} +> [!CAUTION] +> You should carefully select or unselect the options you want to install. Do not blindly install or +> enable features. To uninstall, find Sunshine in the list here and select "Uninstall" from the overflow menu. Different versions of Windows may provide slightly different steps for uninstall. #### Standalone (lite version) -@warning{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!} +> [!WARNING] +> 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) @@ -349,9 +364,13 @@ After installation, some initial setup is required. ### Linux #### KMS Capture -@warning{Capture of most Wayland-based desktop environments will fail unless this step is performed.} -@note{`cap_sys_admin` may as well be root, except you don't need to be root to run the program. This is necessary to -allow Sunshine to use KMS capture.} + +> [!WARNING] +> Capture of most Wayland-based desktop environments will fail unless this step is performed. + +> [!NOTE] +> `cap_sys_admin` may as well be root, except you don't need to be root to run the program. This is necessary to +> allow Sunshine to use KMS capture. ##### Enable ```bash @@ -384,8 +403,11 @@ Sunshine can only access microphones on macOS due to system limitations. To stre [Soundflower](https://github.com/mattingalls/Soundflower) or [BlackHole](https://github.com/ExistentialAudio/BlackHole). -@note{Command Keys are not forwarded by Moonlight. Right Option-Key is mapped to CMD-Key.} -@caution{Gamepads are not currently supported.} +> [!NOTE] +> Command Keys are not forwarded by Moonlight. Right Option-Key is mapped to CMD-Key. + +> [!CAUTION] +> Gamepads are not currently supported. ## Usage @@ -393,8 +415,9 @@ Sunshine can only access microphones on macOS due to system limitations. To stre If Sunshine is not installed/running as a service, then start Sunshine with the following command, unless a start command is listed in the specified package [install](#install) instructions above. -@note{A service is a process that runs in the background. This is the default when installing Sunshine from the -Windows installer. Running multiple instances of Sunshine is not advised.} +> [!NOTE] +> A service is a process that runs in the background. This is the default when installing Sunshine from the +> Windows installer. Running multiple instances of Sunshine is not advised. ```bash sunshine @@ -405,9 +428,11 @@ sunshine 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.} +> [!NOTE] +> You do not need to specify a config file. If no config file is entered, the default location will be used. -@attention{The configuration file specified will be created if it doesn't exist.} +> [!TIP] +> The configuration file specified will be created if it doesn't exist. ### Start Sunshine over SSH (Linux/X11) Assuming you are already logged into the host, you can use this command @@ -424,9 +449,10 @@ be ready. ssh @ 'startx &; export DISPLAY=:0; sunshine' ``` -@tip{You could also utilize the `~/.bash_profile` or `~/.bashrc` files to set up the `DISPLAY` variable.} +> [!TIP] +> You could also use the `~/.bash_profile` or `~/.bashrc` files to set up the `DISPLAY` variable. -@seealso{ See [Remote SSH Headless Setup](https://app.lizardbyte.dev/2023-09-14-remote-ssh-headless-sunshine-setup) +@seealso{See [Remote SSH Headless Setup](https://app.lizardbyte.dev/2023-09-14-remote-ssh-headless-sunshine-setup) on how to set up a headless streaming server without autologin and dummy plugs (X11 + NVidia GPUs)} ### Configuration @@ -434,10 +460,12 @@ on how to set up a headless streaming server without autologin and dummy plugs ( Sunshine is configured via the web ui, which is available on [https://localhost:47990](https://localhost:47990) by default. You may replace *localhost* with your internal ip address. -@attention{Ignore any warning given by your browser about "insecure website". This is due to the SSL certificate -being self-signed.} +> [!NOTE] +> Ignore any warning given by your browser about "insecure website". This is due to the SSL certificate +> being self-signed. -@caution{If running for the first time, make sure to note the username and password that you created.} +> [!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. @@ -453,15 +481,15 @@ being self-signed.} To get a list of available arguments, run the following command. @tabs{ - @tab{ General | @code{.bash} + @tab{ General | ```bash sunshine --help - @endcode } - @tab{ AppImage | @code{.bash} + ```} + @tab{ AppImage | ```bash ./sunshine.AppImage --help - @endcode } - @tab{ Flatpak | @code{.bash} + ```} + @tab{ Flatpak | ```bash flatpak run --command=sunshine dev.lizardbyte.app.Sunshine --help - @endcode } + ```} } ### Shortcuts @@ -513,25 +541,25 @@ Streaming HDR content is officially supported on Windows hosts and experimentall * Some GPUs video encoders can produce lower image quality or encoding performance when streaming in HDR compared to SDR. -* Additional information: +Additional information: - @tabs{ - @tab{ Windows | - - HDR streaming is supported for Intel, AMD, and NVIDIA GPUs that support encoding HEVC Main 10 or AV1 10-bit profiles. - - We recommend calibrating the display by streaming the Windows HDR Calibration app to your client device and saving an HDR calibration profile to use while streaming. - - Older games that use NVIDIA-specific NVAPI HDR rather than native Windows HDR support may not display properly in HDR. - } - - @tab{ Linux | - - HDR streaming is supported for Intel and AMD GPUs that support encoding HEVC Main 10 or AV1 10-bit profiles using VAAPI. - - The KMS capture backend is required for HDR capture. Other capture methods, like NvFBC or X11, do not support HDR. - - You will need a desktop environment with a compositor that supports HDR rendering, such as Gamescope or KDE Plasma 6. - - @seealso{[Arch wiki on HDR Support for Linux](https://wiki.archlinux.org/title/HDR_monitor_support) and - [Reddit Guide for HDR Support for AMD GPUs](https://www.reddit.com/r/linux_gaming/comments/10m2gyx/guide_alpha_test_hdr_on_linux)} - } +@tabs{ + @tab{ Windows | + - HDR streaming is supported for Intel, AMD, and NVIDIA GPUs that support encoding HEVC Main 10 or AV1 10-bit profiles. + - We recommend calibrating the display by streaming the Windows HDR Calibration app to your client device and saving an HDR calibration profile to use while streaming. + - Older games that use NVIDIA-specific NVAPI HDR rather than native Windows HDR support may not display properly in HDR. } +@tab{ Linux | + - HDR streaming is supported for Intel and AMD GPUs that support encoding HEVC Main 10 or AV1 10-bit profiles using VAAPI. + - The KMS capture backend is required for HDR capture. Other capture methods, like NvFBC or X11, do not support HDR. + - You will need a desktop environment with a compositor that supports HDR rendering, such as Gamescope or KDE Plasma 6. + + @seealso{[Arch wiki on HDR Support for Linux](https://wiki.archlinux.org/title/HDR_monitor_support) and + [Reddit Guide for HDR Support for AMD GPUs](https://www.reddit.com/r/linux_gaming/comments/10m2gyx/guide_alpha_test_hdr_on_linux)} + } +} + ### Tutorials and Guides Tutorial videos are available [here](https://www.youtube.com/playlist?list=PLMYr5_xSeuXAbhxYHz86hA1eCDugoxXY0). diff --git a/docs/legal.md b/docs/legal.md index 161791a6..d171389f 100644 --- a/docs/legal.md +++ b/docs/legal.md @@ -1,6 +1,8 @@ # Legal -@attention{This documentation is for informational purposes only and is not intended as legal advice. If you have -any legal questions or concerns about using Sunshine, we recommend consulting with a lawyer.} + +> [!CAUTION] +> This documentation is for informational purposes only and is not intended as legal advice. If you have +> any legal questions or concerns about using Sunshine, we recommend consulting with a lawyer. Sunshine is licensed under the GPL-3.0 license, which allows for free use and modification of the software. The full text of the license can be reviewed [here](https://github.com/LizardByte/Sunshine/blob/master/LICENSE). diff --git a/docs/third_party_packages.md b/docs/third_party_packages.md index 14867299..33b18e14 100644 --- a/docs/third_party_packages.md +++ b/docs/third_party_packages.md @@ -1,6 +1,7 @@ # Third-Party Packages -@danger{These packages are not maintained by LizardByte. Use at your own risk.} +> [!WARNING] +> These packages are not maintained by LizardByte. Use at your own risk. ## Chocolatey [![Chocolatey](https://img.shields.io/badge/dynamic/xml.svg?color=orange&label=chocolatey&style=for-the-badge&prefix=v&query=%2F%2Ftr%5B%40id%3D%27chocolatey%27%5D%2Ftd%5B3%5D%2Fspan%2Fa&url=https%3A%2F%2Frepology.org%2Fproject%2Fsunshine%2Fversions&logo=chocolatey)](https://community.chocolatey.org/packages/sunshine) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 479956bb..b5b211bf 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -20,8 +20,9 @@ If you forgot your credentials to the web UI, try this. } } -@tip{Don't forget to replace `{new-username}` and `{new-password}` with your new credentials. -Do not include the curly braces.} +> [!TIP] +> Remember to replace `{new-username}` and `{new-password}` with your new credentials. +> Do not include the curly braces. ### Unusual Mouse Behavior If you experience unusual mouse behavior, try attaching a physical mouse to the Sunshine host. @@ -32,11 +33,11 @@ Can't access the web UI? 1. Check firewall rules. ### Controller works on Steam but not in games -One trick might be to change Steam settings and check or uncheck the configuration to support Xbox/Playstation +One trick might be to change Steam settings and check or uncheck the configuration to support Xbox/PlayStation controllers and leave only support for Generic controllers. Also, if you have many controllers already directly connected to the host, it might help to disable them so that the -Sunshine provided controller (connected to the guest) is the "first" one. In Linux this can be accomplished on USB +Sunshine-provided controller (connected to the guest) is the "first" one. In Linux this can be achieved on USB devices by finding the device in `/sys/bus/usb/devices/` and writing `0` to the `authorized` file. ### Network performance test @@ -53,7 +54,7 @@ On the Sunshine host `iperf3` is started in server mode: iperf3 -s ``` -On the client device iperf3 is asked to perform a 60-second UDP test in reverse +On the client device iperf3 is asked to perform a 60-second UDP test in a reverse direction (from server to client) at a given bitrate (e.g. 50 Mbps): ```bash @@ -61,31 +62,31 @@ iperf3 -c {HostIpAddress} -t 60 -u -R -b 50M ``` Watch the output on the client for packet loss and jitter values. Both should be -(very) low. Ideally packet loss remains less than 5% and jitter below 1ms. +(very) low. Ideally, packet loss remains less than 5% and jitter below 1 ms. For Android clients use [PingMaster](https://play.google.com/store/apps/details?id=com.appplanex.pingmasternetworktools). For iOS clients use [HE.NET Network Tools](https://apps.apple.com/us/app/he-net-network-tools/id858241710). -If you are testing a remote connection (over the internet) you will need to +If you are testing a remote connection (over the internet), you will need to forward the port 5201 (TCP and UDP) from your host. ### Packet loss (Buffer overrun) If the host PC (running Sunshine) has a much faster connection to the network than the slowest segment of the network path to the client device (running Moonlight), massive packet loss can occur: Sunshine emits its stream in bursts -every 16ms (for 60fps) but those bursts can't be passed on fast enough to the +every 16 ms (for 60 fps), but those bursts can't be passed on fast enough to the client and must be buffered by one of the network devices inbetween. If the bitrate is high enough, these buffers will overflow and data will be discarded. -This can easily happen if e.g. the host has a 2.5 Gbit/s connection and the +This can easily happen if e.g., the host has a 2.5 Gbit/s connection and the client only 1 Gbit/s or Wi-Fi. Similarly, a 1 Gbps host may be too fast for a client having only a 100 Mbps interface. As a workaround the transmission speed of the host NIC can be reduced: 1 Gbps instead of 2.5 or 100 Mbps instead of 1 Gbps. A technically more advanced -solution would be to configure traffic shaping rules at the OS-level, so that +solution would be to configure traffic shaping rules at the OS level, so that only Sunshine's traffic is slowed down. Such a solution on Linux could look like that: @@ -112,7 +113,7 @@ sudo tc filter add dev protocol ip parent 1: prio 1 \ ``` In that way only the Sunshine traffic is limited by 1 Gbit. This is not persistent on reboots. -If you use a different port for the game stream you need to adjust the last command. +If you use a different port for the game stream, you need to adjust the last command. Sunshine versions > 0.23.1 include improved networking code that should alleviate or even solve this issue (without reducing the NIC speed). @@ -120,7 +121,7 @@ alleviate or even solve this issue (without reducing the NIC speed). ### Packet loss (MTU) Although unlikely, some guests might work better with a lower [MTU](https://en.wikipedia.org/wiki/Maximum_transmission_unit) from the host. -For example, a LG TV was found to have 30-60% packet loss when the host had MTU +For example, an LG TV was found to have 30–60% packet loss when the host had MTU set to 1500 and 1472, but 0% packet loss with a MTU of 1428 set in the network card serving the stream (a Linux PC). It's unclear how that helped precisely, so it's a last resort suggestion. @@ -134,19 +135,23 @@ Due to legal concerns, Mesa has disabled hardware decoding and encoding by defau Error: Could not open codec [h264_vaapi]: Function not implemented ``` -If you see the above error in the Sunshine logs, compiling *Mesa* manually, may be required. See the official Mesa3D +If you see the above error in the Sunshine logs, compiling *Mesa* manually may be required. See the official Mesa3D [Compiling and Installing](https://docs.mesa3d.org/install.html) documentation for instructions. -@important{You must re-enable the disabled encoders. You can do so, by passing the following argument to the build -system. You may also want to enable decoders, however that is not required for Sunshine and is not covered here. -```bash --Dvideo-codecs=h264enc,h265enc -``` -} +> [!IMPORTANT] +> You must re-enable the disabled encoders. You can do so by passing the following argument to the build +> system. You may also want to enable decoders, however, that is not required for Sunshine and is not covered here. +> ```bash +> -Dvideo-codecs=h264enc,h265enc +> ``` + +> [!NOTE] +> Other build options are listed in the +> [meson options](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/meson_options.txt) file. ### Input not working After installation, the `udev` rules need to be reloaded. Our post-install script tries to do this for you -automatically, but if it fails you may need to restart your system. +automatically, but if it fails, you may need to restart your system. If the input is still not working, you may need to add your user to the `input` group. @@ -154,9 +159,6 @@ If the input is still not working, you may need to add your user to the `input` sudo usermod -aG input $USER ``` -@note{Other build options are listed in the -[meson options](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/meson_options.txt) file.} - ### KMS Streaming fails If screencasting fails with KMS, you may need to run the following to force unprivileged screencasting. @@ -164,9 +166,10 @@ If screencasting fails with KMS, you may need to run the following to force unpr sudo setcap -r $(readlink -f $(which sunshine)) ``` -@note{The above command will not work with the AppImage or Flatpak packages. Please refer to the -[AppImage setup](md_docs_2getting__started.html#appimage) or -[Flatpak setup](md_docs_2getting__started.html#flatpak) for more specific instructions.} +> [!NOTE] +> The above command will not work with the AppImage or Flatpak packages. Please refer to the +> [AppImage setup](md_docs_2getting__started.html#appimage) or +> [Flatpak setup](md_docs_2getting__started.html#flatpak) for more specific instructions. ### KMS streaming fails on Nvidia GPUs If KMS screen capture results in a black screen being streamed, you may need to @@ -181,12 +184,12 @@ Consult your distribution's documentation for details on how to do this. (Most often grub is used to load the kernel and set its command line.) ### AMD encoding latency issues -If you notice unexpectedly high encoding latencies (e.g. in Moonlight's +If you notice unexpectedly high encoding latencies (e.g., in Moonlight's performance overlay) or strong fluctuations thereof, your system's Mesa libraries are outdated (<24.2). This is particularly problematic at higher resolutions (4K). -Starting with Mesa-24.2 applications can request a +Starting with Mesa-24.2, applications can request a [low-latency mode](https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/30039) by running them with a special [environment variable](https://docs.mesa3d.org/envvars.html#envvar-AMD_DEBUG): @@ -224,7 +227,7 @@ Verify that you've installed [Nefarius Virtual Gamepad](https://github.com/nefar ### Permission denied Since Sunshine runs as a service on Windows, it may not have the same level of access that your regular user account -has. You may get permission denied errors when attempting to launch a game or application from a non system drive. +has. You may get permission denied errors when attempting to launch a game or application from a non-system drive. You will need to modify the security permissions on your disk. Ensure that user/principal SYSTEM has full permissions on the disk. diff --git a/package.json b/package.json index 39094545..7f93d6a7 100644 --- a/package.json +++ b/package.json @@ -7,16 +7,17 @@ "dev": "vite build --watch", "serve": "serve ./tests/fixtures/http --no-port-switching" }, + "type": "module", "dependencies": { - "@lizardbyte/shared-web": "2025.626.181239", - "vue": "3.5.18", - "vue-i18n": "11.1.11" + "@lizardbyte/shared-web": "2025.922.181114", + "vue": "3.5.22", + "vue-i18n": "11.1.12" }, "devDependencies": { "@codecov/vite-plugin": "1.9.1", - "@vitejs/plugin-vue": "4.6.2", - "serve": "14.2.4", - "vite": "4.5.14", - "vite-plugin-ejs": "1.6.4" + "@vitejs/plugin-vue": "6.0.1", + "serve": "14.2.5", + "vite": "6.3.6", + "vite-plugin-ejs": "1.7.0" } } diff --git a/packaging/linux/AppImage/AppRun b/packaging/linux/AppImage/AppRun index e90ee3a4..c021e425 100644 --- a/packaging/linux/AppImage/AppRun +++ b/packaging/linux/AppImage/AppRun @@ -47,6 +47,8 @@ function install() { # user input rules # shellcheck disable=SC2002 cat "$SUNSHINE_SHARE_HERE/udev/rules.d/60-sunshine.rules" | sudo tee /etc/udev/rules.d/60-sunshine.rules + cat "$SUNSHINE_SHARE_HERE/modules-load.d/60-sunshine.conf" | sudo tee /etc/modules-load.d/60-sunshine.conf + sudo modprobe uhid sudo udevadm control --reload-rules sudo udevadm trigger --property-match=DEVNAME=/dev/uinput sudo udevadm trigger --property-match=DEVNAME=/dev/uhid @@ -65,6 +67,9 @@ function remove() { # remove input rules sudo rm -f /etc/udev/rules.d/60-sunshine.rules + # remove uhid module loading config + sudo rm -f /etc/modules-load.d/60-sunshine.conf + # remove service sudo rm -f ~/.config/systemd/user/sunshine.service } diff --git a/packaging/linux/Arch/PKGBUILD b/packaging/linux/Arch/PKGBUILD index eab48e31..8fc26d9b 100644 --- a/packaging/linux/Arch/PKGBUILD +++ b/packaging/linux/Arch/PKGBUILD @@ -1,6 +1,13 @@ # Edit on github: https://github.com/LizardByte/Sunshine/blob/master/packaging/linux/Arch/PKGBUILD # Reference: https://wiki.archlinux.org/title/PKGBUILD +## options +: "${_run_unit_tests:=false}" # if set to true; unit tests will be executed post build; useful in CI +: "${_support_headless_testing:=false}" +: "${_use_cuda:=detect}" # nvenc + +: "${_commit:=@GITHUB_COMMIT@}" + pkgname='sunshine' pkgver=@PROJECT_VERSION@@SUNSHINE_SUB_VERSION@ pkgrel=1 @@ -33,6 +40,7 @@ depends=( 'openssl' 'opus' 'udev' + 'which' ) makedepends=( @@ -40,7 +48,6 @@ makedepends=( 'appstream-glib' 'cmake' 'desktop-file-utils' - 'cuda' "gcc${_gcc_version}" 'git' 'make' @@ -49,17 +56,45 @@ makedepends=( ) optdepends=( - 'cuda: Nvidia GPU encoding support' 'libva-mesa-driver: AMD GPU encoding support' - 'xorg-server-xvfb: Virtual X server for headless testing' ) provides=() conflicts=() -source=("$pkgname::git+@GITHUB_CLONE_URL@#commit=@GITHUB_COMMIT@") +source=("$pkgname::git+@GITHUB_CLONE_URL@#commit=${_commit}") sha256sums=('SKIP') +# Options Handling +if [[ "${_use_cuda::1}" == "d" ]] && pacman -Qi cuda &> /dev/null; then + _use_cuda=true +fi + +if [[ "${_use_cuda::1}" == "t" ]]; then + optdepends+=( + 'cuda: Nvidia GPU encoding support' + ) +fi + +if [[ "${_support_headless_testing::1}" == "t" ]]; then + optdepends+=( + 'xorg-server-xvfb: Virtual X server for headless testing' + ) +fi + +# Ensure makedepends, checkdepends, optdepends are sorted +if [ -n "${makedepends+x}" ]; then + mapfile -t tmp_array < <(printf '%s\n' "${makedepends[@]}" | sort) + makedepends=("${tmp_array[@]}") + unset tmp_array +fi + +if [ -n "${optdepends+x}" ]; then + mapfile -t tmp_array < <(printf '%s\n' "${optdepends[@]}" | sort) + optdepends=("${tmp_array[@]}") + unset tmp_array +fi + prepare() { cd "$pkgname" git submodule update --recursive --init @@ -68,7 +103,7 @@ prepare() { build() { export BRANCH="@GITHUB_BRANCH@" export BUILD_VERSION="@BUILD_VERSION@" - export COMMIT="@GITHUB_COMMIT@" + export COMMIT="${_commit}" export CC="gcc-${_gcc_version}" export CXX="g++-${_gcc_version}" @@ -76,18 +111,41 @@ build() { export CFLAGS="${CFLAGS/-Werror=format-security/}" export CXXFLAGS="${CXXFLAGS/-Werror=format-security/}" - cmake \ - -S "$pkgname" \ - -B build \ - -Wno-dev \ - -D BUILD_DOCS=OFF \ - -D BUILD_WERROR=ON \ - -D CMAKE_INSTALL_PREFIX=/usr \ - -D SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ - -D SUNSHINE_ASSETS_DIR="share/sunshine" \ - -D SUNSHINE_PUBLISHER_NAME='LizardByte' \ - -D SUNSHINE_PUBLISHER_WEBSITE='https://app.lizardbyte.dev' \ - -D SUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support' + export MAKEFLAGS="${MAKEFLAGS:--j$(nproc)}" + + local _cmake_options=( + -S "$pkgname" + -B build + -Wno-dev + -D BUILD_DOCS=OFF + -D BUILD_WERROR=ON + -D CMAKE_INSTALL_PREFIX=/usr + -D SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine + -D SUNSHINE_ASSETS_DIR="share/sunshine" + -D SUNSHINE_PUBLISHER_NAME='LizardByte' + -D SUNSHINE_PUBLISHER_WEBSITE='https://app.lizardbyte.dev' + -D SUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support' + ) + + if [[ "${_use_cuda::1}" != "t" ]]; then + _cmake_options+=(-DSUNSHINE_ENABLE_CUDA=OFF -DCUDA_FAIL_ON_MISSING=OFF) + else + # If cuda has just been installed, its variables will not be available in the environment + # therefore, set them manually to the expected values on Arch Linux + if [ -z "${CUDA_PATH:-}" ] && pacman -Qi cuda &> /dev/null; then + local _cuda_gcc_version + _cuda_gcc_version="$(LC_ALL=C pacman -Si cuda | grep -Pom1 '^Depends On\s*:.*\bgcc\K[0-9]+\b')" + + export CUDA_PATH=/opt/cuda + export NVCC_CCBIN="/usr/bin/g++-${_cuda_gcc_version}" + fi + fi + + if [[ "${_run_unit_tests::1}" != "t" ]]; then + _cmake_options+=(-DBUILD_TESTS=OFF) + fi + + cmake "${_cmake_options[@]}" appstreamcli validate "build/dev.lizardbyte.app.Sunshine.metainfo.xml" appstream-util validate "build/dev.lizardbyte.app.Sunshine.metainfo.xml" @@ -98,13 +156,19 @@ build() { } check() { - export CC="gcc-${_gcc_version}" - export CXX="g++-${_gcc_version}" + cd "${srcdir}/build" + ./sunshine --version - cd "${srcdir}/build/tests" - ./test_sunshine --gtest_color=yes + if [[ "${_run_unit_tests::1}" == "t" ]]; then + export CC="gcc-${_gcc_version}" + export CXX="g++-${_gcc_version}" + + cd "${srcdir}/build/tests" + ./test_sunshine --gtest_color=yes + fi } package() { + export MAKEFLAGS="${MAKEFLAGS:--j$(nproc)}" make -C build install DESTDIR="$pkgdir" } diff --git a/packaging/linux/Arch/sunshine.install b/packaging/linux/Arch/sunshine.install index 6b274cdf..d7b87737 100644 --- a/packaging/linux/Arch/sunshine.install +++ b/packaging/linux/Arch/sunshine.install @@ -1,5 +1,5 @@ do_setcap() { - setcap cap_sys_admin+p $(readlink -f $(which sunshine)) + setcap cap_sys_admin+p $(readlink -f usr/bin/sunshine) } do_udev_reload() { @@ -13,10 +13,11 @@ do_udev_reload() { post_install() { do_setcap do_udev_reload + modprobe uhid } post_upgrade() { do_setcap do_udev_reload + modprobe uhid } - diff --git a/packaging/linux/fedora/Sunshine.spec b/packaging/linux/fedora/Sunshine.spec index d027b361..44cf39ae 100644 --- a/packaging/linux/fedora/Sunshine.spec +++ b/packaging/linux/fedora/Sunshine.spec @@ -56,22 +56,23 @@ BuildRequires: which BuildRequires: xorg-x11-server-Xvfb # Conditional BuildRequires for cuda-gcc based on Fedora version -%if 0%{?fedora} >= 40 && 0%{?fedora} <= 41 +%if 0%{?fedora} <= 41 BuildRequires: gcc13 BuildRequires: gcc13-c++ %global gcc_version 13 -%global cuda_version 12.6.3 -%global cuda_build 560.35.05 +%global cuda_version 12.9.1 +%global cuda_build 575.57.08 %elif %{?fedora} >= 42 BuildRequires: gcc14 BuildRequires: gcc14-c++ %global gcc_version 14 -%global cuda_version 12.8.1 -%global cuda_build 570.124.06 +%global cuda_version 12.9.1 +%global cuda_build 575.57.08 %endif %global cuda_dir %{_builddir}/cuda +Requires: libayatana-appindicator3 >= 0.5.3 Requires: libcap >= 2.22 Requires: libcurl >= 7.0 Requires: libdrm > 2.4.97 @@ -84,7 +85,7 @@ Requires: miniupnpc >= 2.2.4 Requires: numactl-libs >= 2.0.14 Requires: openssl >= 3.0.2 Requires: pulseaudio-libs >= 10.0 -Requires: libayatana-appindicator3 >= 0.5.3 +Requires: which >= 2.21 %description Self-hosted game stream host for Moonlight. @@ -171,7 +172,7 @@ function install_cuda() { --backup \ --directory="%{cuda_dir}" \ --verbose \ - < "%{_builddir}/Sunshine/packaging/linux/fedora/patches/f42/${architecture}/01-math_functions.patch" + < "%{_builddir}/Sunshine/packaging/linux/patches/${architecture}/01-math_functions.patch" fi } @@ -210,15 +211,13 @@ xvfb-run ./tests/test_sunshine cd %{_builddir}/Sunshine/build %make_install -# Add modules-load configuration -# load the uhid module in initramfs even if it doesn't detect the module as being used during dracut -# which must be run every time a new kernel is installed -install -D -m 0644 /dev/stdin %{buildroot}/usr/lib/modules-load.d/uhid.conf < /etc/modules-load.d/60-sunshine.conf" +flatpak-spawn --host pkexec modprobe uhid + # Udev rule UDEV=$(cat /app/share/sunshine/udev/rules.d/60-sunshine.rules) echo "Configuring mouse permission." diff --git a/packaging/linux/flatpak/scripts/remove-additional-install.sh b/packaging/linux/flatpak/scripts/remove-additional-install.sh index ea2680ef..b3b30149 100644 --- a/packaging/linux/flatpak/scripts/remove-additional-install.sh +++ b/packaging/linux/flatpak/scripts/remove-additional-install.sh @@ -6,6 +6,7 @@ rm "$HOME/.config/systemd/user/sunshine.service" systemctl --user daemon-reload echo "Sunshine User Service has been removed." -# Udev rule +# Remove rules +flatpak-spawn --host pkexec sh -c "rm /etc/modules-load.d/60-sunshine.conf" flatpak-spawn --host pkexec sh -c "rm /etc/udev/rules.d/60-sunshine.rules" echo "Input rules removed. Restart computer to take effect." diff --git a/packaging/linux/fedora/patches/f42/aarch64/01-math_functions.patch b/packaging/linux/patches/aarch64/01-math_functions.patch similarity index 100% rename from packaging/linux/fedora/patches/f42/aarch64/01-math_functions.patch rename to packaging/linux/patches/aarch64/01-math_functions.patch diff --git a/packaging/linux/fedora/patches/f42/x86_64/01-math_functions.patch b/packaging/linux/patches/x86_64/01-math_functions.patch similarity index 100% rename from packaging/linux/fedora/patches/f42/x86_64/01-math_functions.patch rename to packaging/linux/patches/x86_64/01-math_functions.patch diff --git a/packaging/sunshine.rb b/packaging/sunshine.rb index 97eb9fe5..7bed2366 100644 --- a/packaging/sunshine.rb +++ b/packaging/sunshine.rb @@ -39,6 +39,7 @@ class @PROJECT_NAME@ < Formula on_linux do depends_on "avahi" + depends_on "gnu-which" depends_on "libayatana-appindicator" depends_on "libcap" depends_on "libdrm" @@ -59,6 +60,16 @@ class @PROJECT_NAME@ < Formula depends_on "wayland" end + fails_with :clang do + build 1400 + cause "Requires C++23 support" + end + + fails_with :gcc do + version "12" # fails with GCC 12.x and earlier + cause "Requires C++23 support" + end + def install ENV["BRANCH"] = "@GITHUB_BRANCH@" ENV["BUILD_VERSION"] = "@BUILD_VERSION@" @@ -106,7 +117,6 @@ class @PROJECT_NAME@ < Formula end args << "-DCUDA_FAIL_ON_MISSING=OFF" if OS.linux? - args << "-DSUNSHINE_ENABLE_TRAY=OFF" if OS.mac? system "cmake", "-S", ".", "-B", "build", "-G", "Unix Makefiles", *std_cmake_args, diff --git a/scripts/linux_build.sh b/scripts/linux_build.sh index 646619ec..cc1be275 100644 --- a/scripts/linux_build.sh +++ b/scripts/linux_build.sh @@ -1,8 +1,16 @@ #!/bin/bash set -e +# Version requirements - centralized for easy maintenance +cmake_min="3.25.0" +target_cmake_version="3.30.1" +doxygen_min="1.10.0" +_doxygen_min="${doxygen_min//\./_}" # Convert dots to underscores for URL +doxygen_max="1.12.0" + # Default value for arguments appimage_build=0 +cuda_patches=0 num_processors=$(nproc) publisher_name="Third Party Publisher" publisher_website="" @@ -13,6 +21,55 @@ skip_libva=0 skip_package=0 sudo_cmd="sudo" ubuntu_test_repo=0 +step="all" + +# common variables +gcc_alternative_files=( + "gcc" + "g++" + "gcov" + "gcc-ar" + "gcc-ranlib" +) + +# Reusable function to detect nvcc path +function detect_nvcc_path() { + local nvcc_path="" + + # First check for system-installed CUDA + nvcc_path=$(command -v nvcc 2>/dev/null) || true + if [ -n "$nvcc_path" ]; then + echo "$nvcc_path" + return 0 + fi + + # Then check for locally installed CUDA in build directory + if [ -f "${build_dir}/cuda/bin/nvcc" ]; then + echo "${build_dir}/cuda/bin/nvcc" + return 0 + fi + + # No CUDA found + return 1 +} + +# Reusable function to setup NVM environment +function setup_nvm_environment() { + # Only setup NVM if it should be used for this distro + if [ "$nvm_node" == 1 ]; then + # Check if NVM is installed and source it + if [ -f "$HOME/.nvm/nvm.sh" ]; then + # shellcheck source=/dev/null + source "$HOME/.nvm/nvm.sh" + # Use the default node version installed by NVM + nvm use default 2>/dev/null || nvm use node 2>/dev/null || true + echo "Using NVM Node.js version: $(node --version 2>/dev/null || echo 'not available')" + echo "Using NVM npm version: $(npm --version 2>/dev/null || echo 'not available')" + else + echo "NVM not found, using system Node.js if available" + fi + fi +} function _usage() { local exit_code=$1 @@ -28,6 +85,7 @@ Options: -h, --help Display this help message. -s, --sudo-off Disable sudo command. --appimage-build Compile for AppImage, this will not create the AppImage, just the executable. + --cuda-patches Apply cuda patches. --num-processors The number of processors to use for compilation. Default is the value of 'nproc'. --publisher-name The name of the publisher (not developer) of the application. --publisher-website The URL of the publisher's website. @@ -38,6 +96,16 @@ Options: --skip-libva Skip libva installation. This will automatically be enabled if passing --appimage-build. --skip-package Skip creating DEB, or RPM package. --ubuntu-test-repo Install ppa:ubuntu-toolchain-r/test repo on Ubuntu. + --step Which step(s) to run: deps, cmake, validation, build, package, cleanup, or all (default: all) + +Steps: + deps Install dependencies only + cmake Run cmake configure only + validation Run validation commands only + build Build the project only + package Create packages only + cleanup Cleanup alternatives and backups only + all Run all steps (default) EOF exit "$exit_code" @@ -55,6 +123,9 @@ while getopts ":hs-:" opt; do appimage_build=1 skip_libva=1 ;; + cuda-patches) + cuda_patches=1 + ;; num-processors=*) num_processors="${OPTARG#*=}" ;; @@ -73,6 +144,9 @@ while getopts ":hs-:" opt; do skip-package) skip_package=1 ;; sudo-off) sudo_cmd="" ;; ubuntu-test-repo) ubuntu_test_repo=1 ;; + step=*) + step="${OPTARG#*=}" + ;; *) echo "Invalid option: --${OPTARG}" 1>&2 _usage 1 @@ -185,7 +259,15 @@ function add_debian_based_deps() { fi } +function add_test_ppa() { + if [ "$ubuntu_test_repo" == 1 ]; then + $package_install_command "software-properties-common" + ${sudo_cmd} add-apt-repository ppa:ubuntu-toolchain-r/test -y + fi +} + function add_debian_deps() { + add_test_ppa add_debian_based_deps dependencies+=( "libayatana-appindicator3-dev" @@ -193,11 +275,7 @@ function add_debian_deps() { } function add_ubuntu_deps() { - if [ "$ubuntu_test_repo" == 1 ]; then - # allow newer gcc - ${sudo_cmd} add-apt-repository ppa:ubuntu-toolchain-r/test -y - fi - + add_test_ppa add_debian_based_deps dependencies+=( "libappindicator3-dev" @@ -252,15 +330,8 @@ function add_fedora_deps() { } function install_cuda() { - nvcc_path=$(command -v nvcc 2>/dev/null) || true - if [ -n "$nvcc_path" ]; then - echo "found system cuda" - return - fi - # check if we need to install cuda - if [ -f "${build_dir}/cuda/bin/nvcc" ]; then - nvcc_path="${build_dir}/cuda/bin/nvcc" - echo "found local cuda" + # Check if CUDA is already available + if detect_nvcc_path > /dev/null 2>&1; then return fi @@ -297,7 +368,24 @@ function install_cuda() { chmod a+x "${build_dir}/cuda.run" "${build_dir}/cuda.run" --silent --toolkit --toolkitpath="${build_dir}/cuda" --no-opengl-libs --no-man-page --no-drm rm "${build_dir}/cuda.run" - nvcc_path="${build_dir}/cuda/bin/nvcc" + + # run cuda patches + if [ "$cuda_patches" == 1 ]; then + echo "Applying CUDA patches" + local patch_dir="${script_dir}/../packaging/linux/patches/${architecture}" + if [ -d "$patch_dir" ]; then + for patch in "$patch_dir"/*.patch; do + echo "Applying patch: $patch" + patch -p2 \ + --backup \ + --directory="${build_dir}/cuda" \ + --verbose \ + < "$patch" + done + else + echo "No patches found for architecture: $architecture" + fi + fi } function check_version() { @@ -334,38 +422,8 @@ if [[ "$(printf '%s\n' "$installed_version" "$min_version" | sort -V | head -n1) fi } -function run_install() { - # prepare CMAKE args - cmake_args=( - "-B=build" - "-G=Ninja" - "-S=." - "-DBUILD_WERROR=ON" - "-DCMAKE_BUILD_TYPE=Release" - "-DCMAKE_INSTALL_PREFIX=/usr" - "-DSUNSHINE_ASSETS_DIR=share/sunshine" - "-DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine" - "-DSUNSHINE_ENABLE_WAYLAND=ON" - "-DSUNSHINE_ENABLE_X11=ON" - "-DSUNSHINE_ENABLE_DRM=ON" - "-DBUILD_TESTS=false" - "-DBUILD_DOCS=false" - ) - - if [ "$appimage_build" == 1 ]; then - cmake_args+=("-DSUNSHINE_BUILD_APPIMAGE=ON") - fi - - # Publisher metadata - if [ -n "$publisher_name" ]; then - cmake_args+=("-DSUNSHINE_PUBLISHER_NAME='${publisher_name}'") - fi - if [ -n "$publisher_website" ]; then - cmake_args+=("-DSUNSHINE_PUBLISHER_WEBSITE='${publisher_website}'") - fi - if [ -n "$publisher_issue_url" ]; then - cmake_args+=("-DSUNSHINE_PUBLISHER_ISSUE_URL='${publisher_issue_url}'") - fi +function run_step_deps() { + echo "Running step: Install dependencies" # Update the package list $package_update_command @@ -376,29 +434,17 @@ function run_install() { add_debian_deps elif [ "$distro" == "ubuntu" ]; then add_ubuntu_deps - elif [ "$distro" == "fedora" ] && [ "$version" == "41" ]; then - add_fedora_deps - ${sudo_cmd} dnf group install "development-tools" -y - elif [ "$distro" == "fedora" ] && [ "$version" <= "40" ]; then + elif [ "$distro" == "fedora" ]; then add_fedora_deps ${sudo_cmd} dnf group install "$dev_tools_group" -y fi - # Install the dependencies $package_install_command "${dependencies[@]}" # reload the environment # shellcheck source=/dev/null - # source ~/.bashrc - - gcc_alternative_files=( - "gcc" - "g++" - "gcov" - "gcc-ar" - "gcc-ranlib" - ) + source ~/.bashrc #set gcc version based on distros if [ "$distro" == "arch" ]; then @@ -421,8 +467,6 @@ function run_install() { fi # compile cmake if the version is too low - cmake_min="3.25.0" - target_cmake_version="3.30.1" if ! check_version "cmake" "$cmake_min" "inf"; then cmake_prefix="https://github.com/Kitware/CMake/releases/download/v" if [ "$architecture" == "x86_64" ]; then @@ -439,9 +483,6 @@ function run_install() { fi # compile doxygen if version is too low - doxygen_min="1.10.0" - _doxygen_min="1_10_0" - doxygen_max="1.12.0" if ! check_version "doxygen" "$doxygen_min" "$doxygen_max"; then if [ "${SUNSHINE_COMPILE_DOXYGEN}" == "true" ]; then echo "Compiling doxygen" @@ -457,7 +498,7 @@ function run_install() { popd else echo "Doxygen version not in range, skipping docs" - cmake_args+=("-DBUILD_DOCS=OFF") + # Note: cmake_args will be set in cmake step fi fi @@ -476,8 +517,64 @@ function run_install() { # run the cuda install if [ "$skip_cuda" == 0 ]; then install_cuda + fi +} + +function run_step_cmake() { + echo "Running step: CMake configure" + + # Setup NVM environment if needed (for web UI builds) + setup_nvm_environment + + # Detect CUDA path using the reusable function + nvcc_path="" + if [ "$skip_cuda" == 0 ]; then + nvcc_path=$(detect_nvcc_path) + fi + + # prepare CMAKE args + cmake_args=( + "-B=build" + "-G=Ninja" + "-S=." + "-DBUILD_WERROR=ON" + "-DCMAKE_BUILD_TYPE=Release" + "-DCMAKE_INSTALL_PREFIX=/usr" + "-DSUNSHINE_ASSETS_DIR=share/sunshine" + "-DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine" + "-DSUNSHINE_ENABLE_WAYLAND=ON" + "-DSUNSHINE_ENABLE_X11=ON" + "-DSUNSHINE_ENABLE_DRM=ON" + ) + + if [ "$appimage_build" == 1 ]; then + cmake_args+=("-DSUNSHINE_BUILD_APPIMAGE=ON") + fi + + # Publisher metadata + if [ -n "$publisher_name" ]; then + cmake_args+=("-DSUNSHINE_PUBLISHER_NAME='${publisher_name}'") + fi + if [ -n "$publisher_website" ]; then + cmake_args+=("-DSUNSHINE_PUBLISHER_WEBSITE='${publisher_website}'") + fi + if [ -n "$publisher_issue_url" ]; then + cmake_args+=("-DSUNSHINE_PUBLISHER_ISSUE_URL='${publisher_issue_url}'") + fi + + # Handle doxygen docs flag + if ! check_version "doxygen" "$doxygen_min" "$doxygen_max"; then + if [ "${SUNSHINE_COMPILE_DOXYGEN}" != "true" ]; then + cmake_args+=("-DBUILD_DOCS=OFF") + fi + fi + + # Handle CUDA + if [ "$skip_cuda" == 0 ]; then cmake_args+=("-DSUNSHINE_ENABLE_CUDA=ON") - cmake_args+=("-DCMAKE_CUDA_COMPILER:PATH=$nvcc_path") + if [ -n "$nvcc_path" ]; then + cmake_args+=("-DCMAKE_CUDA_COMPILER:PATH=$nvcc_path") + fi else cmake_args+=("-DSUNSHINE_ENABLE_CUDA=OFF") fi @@ -487,6 +584,10 @@ function run_install() { echo "cmake args:" echo "${cmake_args[@]}" cmake "${cmake_args[@]}" +} + +function run_step_validation() { + echo "Running step: Validation" # Run appstream validation, etc. appstreamcli validate "build/dev.lizardbyte.app.Sunshine.metainfo.xml" @@ -495,9 +596,20 @@ function run_install() { if [ "$appimage_build" == 0 ]; then desktop-file-validate "build/dev.lizardbyte.app.Sunshine.terminal.desktop" fi +} + +function run_step_build() { + echo "Running step: Build" + + # Setup NVM environment if needed (for web UI builds) + setup_nvm_environment # Build the project ninja -C "build" +} + +function run_step_package() { + echo "Running step: Package" # Create the package if [ "$skip_package" == 0 ]; then @@ -507,6 +619,10 @@ function run_install() { cpack -G RPM --config ./build/CPackConfig.cmake fi fi +} + +function run_step_cleanup() { + echo "Running step: Cleanup" if [ "$skip_cleanup" == 0 ]; then # Restore the original gcc alternatives @@ -527,6 +643,42 @@ function run_install() { fi } +function run_install() { + case "$step" in + deps) + run_step_deps + ;; + cmake) + run_step_cmake + ;; + validation) + run_step_validation + ;; + build) + run_step_build + ;; + package) + run_step_package + ;; + cleanup) + run_step_cleanup + ;; + all) + run_step_deps + run_step_cmake + run_step_validation + run_step_build + run_step_package + run_step_cleanup + ;; + *) + echo "Invalid step: $step" + echo "Valid steps are: deps, cmake, validation, build, package, cleanup, all" + exit 1 + ;; + esac +} + # Determine the OS and call the appropriate function cat /etc/os-release @@ -542,27 +694,26 @@ elif grep -q "Debian GNU/Linux 12 (bookworm)" /etc/os-release; then version="12" package_update_command="${sudo_cmd} apt-get update" package_install_command="${sudo_cmd} apt-get install -y" - cuda_version="12.0.0" - cuda_build="525.60.13" - gcc_version="12" - nvm_node=0 -elif grep -q "PLATFORM_ID=\"platform:f40\"" /etc/os-release; then - distro="fedora" - version="40" - package_update_command="${sudo_cmd} dnf update -y" - package_install_command="${sudo_cmd} dnf install -y" - cuda_version=12.6.3 - cuda_build=560.35.05 + cuda_version="12.9.1" + cuda_build="575.57.08" gcc_version="13" nvm_node=0 - dev_tools_group="Development Tools" +elif grep -q "Debian GNU/Linux 13 (trixie)" /etc/os-release; then + distro="debian" + version="13" + package_update_command="${sudo_cmd} apt-get update" + package_install_command="${sudo_cmd} apt-get install -y" + cuda_version="12.9.1" + cuda_build="575.57.08" + gcc_version="14" + nvm_node=0 elif grep -q "PLATFORM_ID=\"platform:f41\"" /etc/os-release; then distro="fedora" version="41" package_update_command="${sudo_cmd} dnf update -y" package_install_command="${sudo_cmd} dnf install -y" - cuda_version=12.6.3 - cuda_build=560.35.05 + cuda_version="12.9.1" + cuda_build="575.57.08" gcc_version="13" nvm_node=0 dev_tools_group="development-tools" @@ -571,8 +722,8 @@ elif grep -q "PLATFORM_ID=\"platform:f42\"" /etc/os-release; then version="42" package_update_command="${sudo_cmd} dnf update -y" package_install_command="${sudo_cmd} dnf install -y" - cuda_version=12.8.1 - cuda_build=570.124.06 + cuda_version="12.9.1" + cuda_build="575.57.08" gcc_version="14" nvm_node=0 dev_tools_group="development-tools" @@ -581,27 +732,27 @@ elif grep -q "Ubuntu 22.04" /etc/os-release; then version="22.04" package_update_command="${sudo_cmd} apt-get update" package_install_command="${sudo_cmd} apt-get install -y" - cuda_version="11.8.0" - cuda_build="520.61.05" - gcc_version="11" + cuda_version="12.9.1" + cuda_build="575.57.08" + gcc_version="13" nvm_node=1 elif grep -q "Ubuntu 24.04" /etc/os-release; then distro="ubuntu" version="24.04" package_update_command="${sudo_cmd} apt-get update" package_install_command="${sudo_cmd} apt-get install -y" - cuda_version="11.8.0" - cuda_build="520.61.05" - gcc_version="11" - nvm_node=0 + cuda_version="12.9.1" + cuda_build="575.57.08" + gcc_version="14" + nvm_node=1 elif grep -q "Ubuntu 25.04" /etc/os-release; then distro="ubuntu" version="25.04" package_update_command="${sudo_cmd} apt-get update" package_install_command="${sudo_cmd} apt-get install -y" - cuda_version="11.8.0" - cuda_build="520.61.05" - gcc_version="11" + cuda_version="12.9.1" + cuda_build="575.57.08" + gcc_version="14" nvm_node=0 else echo "Unsupported Distro or Version" diff --git a/src/config.cpp b/src/config.cpp index f30596d5..6892e302 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -566,6 +566,7 @@ namespace config { true, // back as touchpad click enabled (manual DS4 only) true, // client gamepads with motion events are emulated as DS4 true, // client gamepads with touchpads are emulated as DS4 + true, // ds5_inputtino_randomize_mac true, // keyboard enabled true, // mouse enabled @@ -596,6 +597,7 @@ namespace config { platf::appdata().string() + "/sunshine.log", // log file false, // notify_pre_releases false, // legacy_ordering + true, // system_tray {}, // prep commands {}, // state commands {}, // server commands @@ -1279,6 +1281,7 @@ namespace config { bool_f(vars, "ds4_back_as_touchpad_click", input.ds4_back_as_touchpad_click); bool_f(vars, "motion_as_ds4", input.motion_as_ds4); bool_f(vars, "touchpad_as_ds4", input.touchpad_as_ds4); + bool_f(vars, "ds5_inputtino_randomize_mac", input.ds5_inputtino_randomize_mac); bool_f(vars, "mouse", input.mouse); bool_f(vars, "keyboard", input.keyboard); @@ -1290,6 +1293,7 @@ namespace config { bool_f(vars, "native_pen_touch", input.native_pen_touch); bool_f(vars, "enable_input_only_mode", input.enable_input_only_mode); + bool_f(vars, "system_tray", sunshine.system_tray); bool_f(vars, "hide_tray_controls", sunshine.hide_tray_controls); bool_f(vars, "enable_pairing", sunshine.enable_pairing); bool_f(vars, "enable_discovery", sunshine.enable_discovery); @@ -1320,6 +1324,7 @@ namespace config { "en_US"sv, // English (US) "es"sv, // Spanish "fr"sv, // French + "hu"sv, // Hungarian "it"sv, // Italian "ja"sv, // Japanese "ko"sv, // Korean @@ -1330,6 +1335,7 @@ namespace config { "sv"sv, // Swedish "tr"sv, // Turkish "uk"sv, // Ukrainian + "vi"sv, // Vietnamese "zh"sv, // Chinese "zh_TW"sv, // Chinese (Traditional) }); @@ -1497,7 +1503,7 @@ namespace config { if (!service_ctrl::is_service_running()) { // If the service isn't running, relaunch ourselves as admin to start it WCHAR executable[MAX_PATH]; - GetModuleFileNameW(NULL, executable, ARRAYSIZE(executable)); + GetModuleFileNameW(nullptr, executable, ARRAYSIZE(executable)); SHELLEXECUTEINFOW shell_exec_info {}; shell_exec_info.cbSize = sizeof(shell_exec_info); diff --git a/src/config.h b/src/config.h index bec1ac95..a6d7c833 100644 --- a/src/config.h +++ b/src/config.h @@ -202,6 +202,7 @@ namespace config { bool ds4_back_as_touchpad_click; bool motion_as_ds4; bool touchpad_as_ds4; + bool ds5_inputtino_randomize_mac; bool keyboard; bool mouse; @@ -283,6 +284,7 @@ namespace config { std::string log_file; bool notify_pre_releases; bool legacy_ordering; + bool system_tray; std::vector prep_cmds; std::vector state_cmds; std::vector server_cmds; diff --git a/src/confighttp.cpp b/src/confighttp.cpp index e270dd76..d53ac653 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -8,6 +8,7 @@ // standard includes #include +#include #include #include #include diff --git a/src/entry_handler.cpp b/src/entry_handler.cpp index c5810898..686fe73d 100644 --- a/src/entry_handler.cpp +++ b/src/entry_handler.cpp @@ -4,6 +4,7 @@ */ // standard includes #include +#include #include #include @@ -25,13 +26,11 @@ extern "C" { using namespace std::literals; -void launch_ui() { - std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS)); - platf::open_url(url); -} - -void launch_ui_with_path(std::string path) { - std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + path; +void launch_ui(const std::optional &path) { + std::string url = std::format("https://localhost:{}", static_cast(net::map_port(confighttp::PORT_HTTPS))); + if (path) { + url += *path; + } platf::open_url(url); } @@ -194,8 +193,8 @@ namespace service_ctrl { } private: - SC_HANDLE scm_handle = NULL; - SC_HANDLE service_handle = NULL; + SC_HANDLE scm_handle = nullptr; + SC_HANDLE service_handle = nullptr; }; bool is_service_running() { diff --git a/src/entry_handler.h b/src/entry_handler.h index a83fed29..8d5a03e7 100644 --- a/src/entry_handler.h +++ b/src/entry_handler.h @@ -14,19 +14,13 @@ /** * @brief Launch the Web UI. + * @param path Optional path to append to the base URL. * @examples * launch_ui(); + * launch_ui("/pin"); * @examples_end */ -void launch_ui(); - -/** - * @brief Launch the Web UI at a specific endpoint. - * @examples - * launch_ui_with_path("/pin"); - * @examples_end - */ -void launch_ui_with_path(std::string path); +void launch_ui(const std::optional &path = std::nullopt); /** * @brief Functions for handling command line arguments. diff --git a/src/main.cpp b/src/main.cpp index 5b64431a..73209c72 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -96,6 +96,45 @@ WINAPI BOOL ConsoleCtrlHandler(DWORD type) { } #endif +#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1 +constexpr bool tray_is_enabled = true; +#else +constexpr bool tray_is_enabled = false; +#endif + +void mainThreadLoop(const std::shared_ptr> &shutdown_event) { + bool run_loop = false; + + // Conditions that would require the main thread event loop +#ifndef _WIN32 + run_loop = tray_is_enabled; // On Windows, tray runs in separate thread, so no main loop needed for tray +#endif + + if (!run_loop) { + BOOST_LOG(info) << "No main thread features enabled, skipping event loop"sv; + return; + } + + // Main thread event loop + BOOST_LOG(info) << "Starting main loop"sv; + while (true) { + if (shutdown_event->peek()) { + BOOST_LOG(info) << "Shutdown event detected, breaking main loop"sv; + if (tray_is_enabled && config::sunshine.system_tray) { + system_tray::end_tray(); + } + break; + } + + if (tray_is_enabled) { + system_tray::process_tray_events(); + } + + // Sleep to avoid busy waiting + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } +} + int main(int argc, char *argv[]) { lifetime::argv = argv; @@ -167,7 +206,7 @@ int main(int argc, char *argv[]) { BOOST_LOG(error) << "Display device session failed to initialize"sv; } -#ifdef WIN32 +#ifdef _WIN32 // Modify relevant NVIDIA control panel settings if the system has corresponding gpu if (nvprefs_instance.load()) { // Restore global settings to the undo file left by improper termination of sunshine.exe @@ -196,7 +235,7 @@ int main(int argc, char *argv[]) { wnd_class.lpszClassName = "SunshineSessionMonitorClass"; wnd_class.lpfnWndProc = SessionMonitorWindowProc; if (!RegisterClassA(&wnd_class)) { - session_monitor_hwnd_promise.set_value(NULL); + session_monitor_hwnd_promise.set_value(nullptr); BOOST_LOG(error) << "Failed to register session monitor window class"sv << std::endl; return; } @@ -256,11 +295,6 @@ int main(int argc, char *argv[]) { task_pool.start(1); -#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1 - // create tray thread and detach it - system_tray::run_tray(); -#endif - // Create signal handler after logging has been initialized auto shutdown_event = mail::man->event(mail::shutdown); on_signal(SIGINT, [&force_shutdown, &display_device_deinit_guard, shutdown_event]() { @@ -405,7 +439,23 @@ int main(int argc, char *argv[]) { } #endif - // Wait for shutdown + if (tray_is_enabled && config::sunshine.system_tray) { + 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. + // 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. + system_tray::init_tray_threaded(); +#else + system_tray::init_tray(); +#endif + } + + mainThreadLoop(shutdown_event); + + // Wait for shutdown, this is not necessary when we're using the main event loop shutdown_event->view(); httpThread.join(); @@ -415,17 +465,17 @@ int main(int argc, char *argv[]) { task_pool.stop(); task_pool.join(); - // stop system tray -#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1 - system_tray::end_tray(); -#endif - -#ifdef WIN32 +#ifdef _WIN32 // Restore global NVIDIA control panel settings if (nvprefs_instance.owning_undo_file() && nvprefs_instance.load()) { nvprefs_instance.restore_global_profile(); nvprefs_instance.unload(); } + + // Stop the threaded tray if it was started + if (tray_is_enabled && config::sunshine.system_tray) { + system_tray::end_tray_threaded(); + } #endif return lifetime::desired_exit_code; diff --git a/src/nvenc/nvenc_base.cpp b/src/nvenc/nvenc_base.cpp index bcd12ca1..431b7de2 100644 --- a/src/nvenc/nvenc_base.cpp +++ b/src/nvenc/nvenc_base.cpp @@ -5,6 +5,9 @@ // this include #include "nvenc_base.h" +// standard includes +#include + // local includes #include "src/config.h" #include "src/logging.h" @@ -427,7 +430,7 @@ namespace nvenc { extra += " two-pass"; } if (config.vbv_percentage_increase > 0 && get_encoder_cap(NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) { - extra += " vbv+" + std::to_string(config.vbv_percentage_increase); + extra += std::format(" vbv+{}", config.vbv_percentage_increase); } if (encoder_params.rfi) { extra += " rfi"; @@ -439,7 +442,7 @@ namespace nvenc { extra += " spatial-aq"; } if (enc_config.rcParams.enableMinQP) { - extra += " qpmin=" + std::to_string(enc_config.rcParams.minQP.qpInterP); + extra += std::format(" qpmin={}", enc_config.rcParams.minQP.qpInterP); } if (config.insert_filler_data) { extra += " filler-data"; diff --git a/src/nvenc/nvenc_d3d11.cpp b/src/nvenc/nvenc_d3d11.cpp index 74670acd..1b749f92 100644 --- a/src/nvenc/nvenc_d3d11.cpp +++ b/src/nvenc/nvenc_d3d11.cpp @@ -12,13 +12,13 @@ namespace nvenc { nvenc_d3d11::nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type): nvenc_base(device_type) { - async_event_handle = CreateEvent(NULL, FALSE, FALSE, NULL); + async_event_handle = CreateEvent(nullptr, FALSE, FALSE, nullptr); } nvenc_d3d11::~nvenc_d3d11() { if (dll) { FreeLibrary(dll); - dll = NULL; + dll = nullptr; } if (async_event_handle) { CloseHandle(async_event_handle); @@ -36,7 +36,7 @@ namespace nvenc { constexpr auto dll_name = "nvEncodeAPI.dll"; #endif - if ((dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) { + if ((dll = LoadLibraryEx(dll_name, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32))) { if (auto create_instance = (decltype(NvEncodeAPICreateInstance) *) GetProcAddress(dll, "NvEncodeAPICreateInstance")) { auto new_nvenc = std::make_unique(); new_nvenc->version = min_struct_version(NV_ENCODE_API_FUNCTION_LIST_VER); @@ -55,7 +55,7 @@ namespace nvenc { if (dll) { FreeLibrary(dll); - dll = NULL; + dll = nullptr; } return false; diff --git a/src/nvenc/nvenc_d3d11.h b/src/nvenc/nvenc_d3d11.h index efacb607..19facee0 100644 --- a/src/nvenc/nvenc_d3d11.h +++ b/src/nvenc/nvenc_d3d11.h @@ -39,7 +39,7 @@ namespace nvenc { bool wait_for_async_event(uint32_t timeout_ms) override; private: - HMODULE dll = NULL; + HMODULE dll = nullptr; }; } // namespace nvenc diff --git a/src/nvenc/nvenc_d3d11_on_cuda.cpp b/src/nvenc/nvenc_d3d11_on_cuda.cpp index 02436e69..b915b329 100644 --- a/src/nvenc/nvenc_d3d11_on_cuda.cpp +++ b/src/nvenc/nvenc_d3d11_on_cuda.cpp @@ -63,7 +63,7 @@ namespace nvenc { constexpr auto dll_name = "nvcuda.dll"; - if ((cuda_functions.dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) { + if ((cuda_functions.dll = LoadLibraryEx(dll_name, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32))) { auto load_function = [&](T &location, auto symbol) -> bool { location = (T) GetProcAddress(cuda_functions.dll, symbol); return location != nullptr; diff --git a/src/nvenc/nvenc_d3d11_on_cuda.h b/src/nvenc/nvenc_d3d11_on_cuda.h index 1c912f50..102e1809 100644 --- a/src/nvenc/nvenc_d3d11_on_cuda.h +++ b/src/nvenc/nvenc_d3d11_on_cuda.h @@ -56,7 +56,7 @@ namespace nvenc { autopop_context push_context(); - HMODULE dll = NULL; + HMODULE dll = nullptr; const ID3D11DevicePtr d3d_device; ID3D11Texture2DPtr d3d_input_texture; diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index e6df1c0b..ef5f6fe8 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -7,6 +7,7 @@ // standard includes #include +#include #include #include #include @@ -163,7 +164,7 @@ namespace nvhttp { std::string get_arg(const args_t &args, const char *name, const char *default_value) { auto it = args.find(name); if (it == std::end(args)) { - if (default_value != NULL) { + if (default_value != nullptr) { return std::string(default_value); } @@ -839,7 +840,7 @@ namespace nvhttp { tree.put("root..status_code", 400); tree.put( "root..status_message", - "Pin must be 4 digits, " + std::to_string(pin.size()) + " provided" + std::format("Pin must be 4 digits, {} provided", pin.size()) ); return false; } @@ -1330,7 +1331,15 @@ namespace nvhttp { } tree.put("root..status_code", 200); - tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT))); + tree.put( + "root.sessionUrl0", + std::format( + "{}{}:{}", + launch_session->rtsp_url_scheme, + net::addr_to_url_escaped_string(request->local_endpoint().address()), + static_cast(net::map_port(rtsp_stream::RTSP_SETUP_PORT)) + ) + ); tree.put("root.gamesession", 1); rtsp_stream::launch_session_raise(launch_session); @@ -1429,7 +1438,15 @@ namespace nvhttp { } tree.put("root..status_code", 200); - tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT))); + tree.put( + "root.sessionUrl0", + std::format( + "{}{}:{}", + launch_session->rtsp_url_scheme, + net::addr_to_url_escaped_string(request->local_endpoint().address()), + static_cast(net::map_port(rtsp_stream::RTSP_SETUP_PORT)) + ) + ); tree.put("root.resume", 1); rtsp_stream::launch_session_raise(launch_session); diff --git a/src/platform/linux/cuda.cpp b/src/platform/linux/cuda.cpp index 3a17b9cf..5b3bb695 100644 --- a/src/platform/linux/cuda.cpp +++ b/src/platform/linux/cuda.cpp @@ -202,7 +202,7 @@ namespace cuda { return sws.load_ram(img, tex.array) || sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(tex), stream.get()); } - int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) { + int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override { if (cuda_t::set_frame(frame, hw_frames_ctx)) { return -1; } @@ -929,7 +929,7 @@ namespace cuda { return platf::capture_e::ok; } - std::unique_ptr make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) { + std::unique_ptr make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override { return ::cuda::make_avcodec_encode_device(width, height, true); } diff --git a/src/platform/linux/cuda.h b/src/platform/linux/cuda.h index c7f5daac..40957581 100644 --- a/src/platform/linux/cuda.h +++ b/src/platform/linux/cuda.h @@ -16,8 +16,8 @@ #include "src/video_colorspace.h" namespace platf { - class avcodec_encode_device_t; - class img_t; + struct avcodec_encode_device_t; + struct img_t; } // namespace platf namespace cuda { diff --git a/src/platform/linux/input/inputtino_gamepad.cpp b/src/platform/linux/input/inputtino_gamepad.cpp index 43814631..7e782b59 100644 --- a/src/platform/linux/input/inputtino_gamepad.cpp +++ b/src/platform/linux/input/inputtino_gamepad.cpp @@ -42,8 +42,15 @@ namespace platf::gamepad { .version = 0x8111}); } - auto create_ds5() { - return inputtino::PS5Joypad::create({.name = "Sunshine PS5 (virtual) pad", .vendor_id = 0x054C, .product_id = 0x0CE6, .version = 0x8111}); + auto create_ds5(int globalIndex) { + std::string device_mac = ""; // Inputtino checks empty() to generate a random MAC + + if (!config::input.ds5_inputtino_randomize_mac && globalIndex >= 0 && globalIndex <= 255) { + // Generate private virtual device MAC based on gamepad globalIndex between 0 (00) and 255 (ff) + device_mac = std::format("02:00:00:00:00:{:02x}", globalIndex); + } + + return inputtino::PS5Joypad::create({.name = "Sunshine PS5 (virtual) pad", .vendor_id = 0x054C, .product_id = 0x0CE6, .version = 0x8111, .device_phys = device_mac, .device_uniq = device_mac}); } int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) { @@ -138,7 +145,7 @@ namespace platf::gamepad { } case DualSenseWired: { - auto ds5 = create_ds5(); + auto ds5 = create_ds5(id.globalIndex); if (ds5) { (*ds5).set_on_rumble(on_rumble_fn); (*ds5).set_on_led([feedback_queue, idx = id.clientRelativeIndex, gamepad](int r, int g, int b) { @@ -267,7 +274,7 @@ namespace platf::gamepad { return gps; } - auto ds5 = create_ds5(); + auto ds5 = create_ds5(-1); // Index -1 will result in a random MAC virtual device, which is fine for probing auto switchPro = create_switch(); auto xOne = create_xbox_one(); diff --git a/src/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp index 88444d10..e0cc7930 100644 --- a/src/platform/linux/vaapi.cpp +++ b/src/platform/linux/vaapi.cpp @@ -4,6 +4,7 @@ */ // standard includes #include +#include #include #include @@ -190,7 +191,7 @@ namespace va { return VAProfileH264High; } else if (ctx->codec_id == AV_CODEC_ID_HEVC) { switch (ctx->profile) { - case FF_PROFILE_HEVC_REXT: + case AV_PROFILE_HEVC_REXT: switch (av_pix_fmt_desc_get(ctx->sw_pix_fmt)->comp[0].depth) { case 10: return VAProfileHEVCMain444_10; @@ -198,16 +199,16 @@ namespace va { return VAProfileHEVCMain444; } break; - case FF_PROFILE_HEVC_MAIN_10: + case AV_PROFILE_HEVC_MAIN_10: return VAProfileHEVCMain10; - case FF_PROFILE_HEVC_MAIN: + case AV_PROFILE_HEVC_MAIN: return VAProfileHEVCMain; } } else if (ctx->codec_id == AV_CODEC_ID_AV1) { switch (ctx->profile) { - case FF_PROFILE_AV1_HIGH: + case AV_PROFILE_AV1_HIGH: return VAProfileAV1Profile1; - case FF_PROFILE_AV1_MAIN: + case AV_PROFILE_AV1_MAIN: return VAProfileAV1Profile0; } } @@ -574,7 +575,7 @@ namespace va { if (!display) { char string[1024]; - auto bytes = readlink(("/proc/self/fd/" + std::to_string(fd)).c_str(), string, sizeof(string)); + auto bytes = readlink(std::format("/proc/self/fd/{}", fd).c_str(), string, sizeof(string)); std::string_view render_device {string, (std::size_t) bytes}; diff --git a/src/platform/windows/audio.cpp b/src/platform/windows/audio.cpp index b6853e4a..69a0b2ff 100644 --- a/src/platform/windows/audio.cpp +++ b/src/platform/windows/audio.cpp @@ -4,8 +4,11 @@ */ #define INITGUID +// standard includes +#include + // platform includes -#include +#include #include #include #include @@ -168,28 +171,27 @@ namespace { waveformat.SubFormat == KSDATAFORMAT_SUBTYPE_PCM ? "S" : "UNKNOWN"; - result += std::to_string(waveformat.Samples.wValidBitsPerSample) + " " + - std::to_string(waveformat.Format.nSamplesPerSec) + " "; + result += std::format("{} {} ", static_cast(waveformat.Samples.wValidBitsPerSample), static_cast(waveformat.Format.nSamplesPerSec)); switch (waveformat.dwChannelMask) { - case (waveformat_mask_stereo): + case waveformat_mask_stereo: result += "2.0"; break; - case (waveformat_mask_surround51_with_backspeakers): + case waveformat_mask_surround51_with_backspeakers: result += "5.1"; break; - case (waveformat_mask_surround51_with_sidespeakers): + case waveformat_mask_surround51_with_sidespeakers: result += "5.1 (sidespeakers)"; break; - case (waveformat_mask_surround71): + case waveformat_mask_surround71: result += "7.1"; break; default: - result += std::to_string(waveformat.Format.nChannels) + " channels (unrecognized)"; + result += std::format("{} channels (unrecognized)", static_cast(waveformat.Format.nChannels)); break; } @@ -375,7 +377,7 @@ namespace platf::audio { *ppvInterface = (IMMNotificationClient *) this; return S_OK; } else { - *ppvInterface = NULL; + *ppvInterface = nullptr; return E_NOINTERFACE; } } @@ -677,7 +679,7 @@ namespace platf::audio { float *sample_buf_pos; int channels; - HANDLE mmcss_task_handle = NULL; + HANDLE mmcss_task_handle = nullptr; }; class audio_control_t: public ::platf::audio_control_t { diff --git a/src/platform/windows/display.h b/src/platform/windows/display.h index 74c4be7c..d508ae97 100644 --- a/src/platform/windows/display.h +++ b/src/platform/windows/display.h @@ -12,7 +12,7 @@ #include #include #include -#include +#include // local includes #include "src/platform/common.h" diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 72ce0b50..dfb0b16e 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -603,12 +603,12 @@ namespace platf::dxgi { LUID val; if (OpenProcessToken(GetCurrentProcess(), flags, &token) && - !!LookupPrivilegeValue(NULL, SE_INC_BASE_PRIORITY_NAME, &val)) { + !!LookupPrivilegeValue(nullptr, SE_INC_BASE_PRIORITY_NAME, &val)) { tp.PrivilegeCount = 1; tp.Privileges[0].Luid = val; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - if (!AdjustTokenPrivileges(token, false, &tp, sizeof(tp), NULL, NULL)) { + if (!AdjustTokenPrivileges(token, false, &tp, sizeof(tp), nullptr, nullptr)) { BOOST_LOG(warning) << "Could not set privilege to increase GPU priority"; } } @@ -926,20 +926,20 @@ namespace platf::dxgi { "DXGI_FORMAT_A8P8", "DXGI_FORMAT_B4G4R4A4_UNORM", - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, "DXGI_FORMAT_P208", "DXGI_FORMAT_V208", diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index 72bccaf7..d87b5df0 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -7,7 +7,7 @@ // platform includes #include -#include +#include extern "C" { #include @@ -1745,7 +1745,7 @@ namespace platf::dxgi { img->data = nullptr; if (img->encoder_texture_handle) { CloseHandle(img->encoder_texture_handle); - img->encoder_texture_handle = NULL; + img->encoder_texture_handle = nullptr; } // Initialize format-dependent fields diff --git a/src/platform/windows/display_wgc.cpp b/src/platform/windows/display_wgc.cpp index 490f10bf..a5ccb83f 100644 --- a/src/platform/windows/display_wgc.cpp +++ b/src/platform/windows/display_wgc.cpp @@ -8,7 +8,7 @@ // Gross hack to work around MINGW-packages#22160 #define ____FIReference_1_boolean_INTERFACE_DEFINED__ -#include +#include #include #include #include diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index 0b330956..81bb72ec 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -5,7 +5,7 @@ #define WINVER 0x0A00 // platform includes -#include +#include // standard includes #include @@ -293,7 +293,7 @@ namespace platf { if (gamepad.repeat_task) { task_pool.cancel(gamepad.repeat_task); - gamepad.repeat_task = 0; + gamepad.repeat_task = nullptr; } if (gamepad.gp && vigem_target_is_attached(gamepad.gp.get())) { @@ -1457,7 +1457,7 @@ namespace platf { // Cancel any pending updates. We will requeue one here when we're finished. if (gamepad.repeat_task) { task_pool.cancel(gamepad.repeat_task); - gamepad.repeat_task = 0; + gamepad.repeat_task = nullptr; } if (gamepad.gp && vigem_target_is_attached(gamepad.gp.get())) { @@ -1603,8 +1603,8 @@ namespace platf { uint16_t y = touch.y * 943; uint8_t touchData[] = { (uint8_t) (x & 0xFF), // Low 8 bits of X - (uint8_t) (((x >> 8) & 0x0F) | ((y & 0x0F) << 4)), // High 4 bits of X and low 4 bits of Y - (uint8_t) (((y >> 4) & 0xFF)) // High 8 bits of Y + (uint8_t) ((x >> 8 & 0x0F) | (y & 0x0F) << 4), // High 4 bits of X and low 4 bits of Y + (uint8_t) (y >> 4 & 0xFF) // High 8 bits of Y }; report.sCurrentTouch.bPacketCounter++; diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index cf04d0f2..ce07c7be 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -28,13 +28,13 @@ #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include -#include -#include +#include +#include #include // clang-format on @@ -128,7 +128,7 @@ namespace platf { std::filesystem::path appdata() { WCHAR sunshine_path[MAX_PATH]; - GetModuleFileNameW(NULL, sunshine_path, _countof(sunshine_path)); + GetModuleFileNameW(nullptr, sunshine_path, _countof(sunshine_path)); return std::filesystem::path {sunshine_path}.remove_filename() / L"config"sv; } @@ -476,16 +476,16 @@ namespace platf { LPPROC_THREAD_ATTRIBUTE_LIST allocate_proc_thread_attr_list(DWORD attribute_count) { SIZE_T size; - InitializeProcThreadAttributeList(NULL, attribute_count, 0, &size); + InitializeProcThreadAttributeList(nullptr, attribute_count, 0, &size); auto list = (LPPROC_THREAD_ATTRIBUTE_LIST) HeapAlloc(GetProcessHeap(), 0, size); - if (list == NULL) { - return NULL; + if (list == nullptr) { + return nullptr; } if (!InitializeProcThreadAttributeList(list, attribute_count, 0, &size)) { HeapFree(GetProcessHeap(), 0, list); - return NULL; + return nullptr; } return list; @@ -584,7 +584,7 @@ namespace platf { // Allocate a process attribute list with space for 2 elements startup_info.lpAttributeList = allocate_proc_thread_attr_list(2); - if (startup_info.lpAttributeList == NULL) { + if (startup_info.lpAttributeList == nullptr) { // If the allocation failed, set ec to an appropriate error code and return the structure ec = std::make_error_code(std::errc::not_enough_memory); return startup_info; @@ -596,7 +596,7 @@ namespace platf { // Populate std handles if the caller gave us a log file to use startup_info.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; - startup_info.StartupInfo.hStdInput = NULL; + startup_info.StartupInfo.hStdInput = nullptr; startup_info.StartupInfo.hStdOutput = log_file_handle; startup_info.StartupInfo.hStdError = log_file_handle; @@ -605,7 +605,7 @@ namespace platf { // // Note: The value we point to here must be valid for the lifetime of the attribute list, // so we need to point into the STARTUPINFO instead of our log_file_variable on the stack. - UpdateProcThreadAttribute(startup_info.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &startup_info.StartupInfo.hStdOutput, sizeof(startup_info.StartupInfo.hStdOutput), NULL, NULL); + UpdateProcThreadAttribute(startup_info.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &startup_info.StartupInfo.hStdOutput, sizeof(startup_info.StartupInfo.hStdOutput), nullptr, nullptr); } if (job) { @@ -613,7 +613,7 @@ namespace platf { // // Note: The value we point to here must be valid for the lifetime of the attribute list, // so we take a HANDLE* instead of just a HANDLE to use the caller's stack storage. - UpdateProcThreadAttribute(startup_info.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_JOB_LIST, job, sizeof(*job), NULL, NULL); + UpdateProcThreadAttribute(startup_info.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_JOB_LIST, job, sizeof(*job), nullptr, nullptr); } return startup_info; @@ -621,11 +621,11 @@ namespace platf { /** * @brief This function overrides HKEY_CURRENT_USER and HKEY_CLASSES_ROOT using the provided token. - * @param token The primary token identifying the user to use, or `NULL` to restore original keys. + * @param token The primary token identifying the user to use, or `nullptr` to restore original keys. * @return `true` if the override or restore operation was successful. */ bool override_per_user_predefined_keys(HANDLE token) { - HKEY user_classes_root = NULL; + HKEY user_classes_root = nullptr; if (token) { auto err = RegOpenUserClassesRoot(token, 0, GENERIC_ALL, &user_classes_root); if (err != ERROR_SUCCESS) { @@ -639,14 +639,14 @@ namespace platf { } }); - HKEY user_key = NULL; + HKEY user_key = nullptr; if (token) { impersonate_current_user(token, [&]() { // RegOpenCurrentUser() doesn't take a token. It assumes we're impersonating the desired user. auto err = RegOpenCurrentUser(GENERIC_ALL, &user_key); if (err != ERROR_SUCCESS) { BOOST_LOG(error) << "Failed to open user key for target user: "sv << err; - user_key = NULL; + user_key = nullptr; } }); if (!user_key) { @@ -668,7 +668,7 @@ namespace platf { err = RegOverridePredefKey(HKEY_CURRENT_USER, user_key); if (err != ERROR_SUCCESS) { BOOST_LOG(error) << "Failed to override HKEY_CURRENT_USER: "sv << err; - RegOverridePredefKey(HKEY_CLASSES_ROOT, NULL); + RegOverridePredefKey(HKEY_CLASSES_ROOT, nullptr); return false; } @@ -737,7 +737,7 @@ namespace platf { * @details This converts URLs and non-executable file paths into a runnable command like ShellExecute(). * @param raw_cmd The raw command provided by the user. * @param working_dir The working directory for the new process. - * @param token The user token currently being impersonated or `NULL` if running as ourselves. + * @param token The user token currently being impersonated or `nullptr` if running as ourselves. * @param creation_flags The creation flags for CreateProcess(), which may be modified by this function. * @return A command string suitable for use by CreateProcess(). */ @@ -814,7 +814,7 @@ namespace platf { } // Reset per-user keys back to the original value - override_per_user_predefined_keys(NULL); + override_per_user_predefined_keys(nullptr); } if (res != S_OK) { @@ -1029,7 +1029,7 @@ namespace platf { ec = impersonate_current_user(user_token, [&]() { std::wstring env_block = create_environment_block(cloned_env); std::wstring wcmd = resolve_command_string(cmd, start_dir, user_token, creation_flags); - ret = CreateProcessAsUserW(user_token, NULL, (LPWSTR) wcmd.c_str(), NULL, NULL, !!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES), creation_flags, env_block.data(), start_dir.empty() ? NULL : start_dir.c_str(), (LPSTARTUPINFOW) &startup_info, &process_info); + ret = CreateProcessAsUserW(user_token, nullptr, (LPWSTR) wcmd.c_str(), nullptr, nullptr, !!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES), creation_flags, env_block.data(), start_dir.empty() ? nullptr : start_dir.c_str(), (LPSTARTUPINFOW) &startup_info, &process_info); }); } // Otherwise, launch the process using CreateProcessW() @@ -1052,8 +1052,8 @@ namespace platf { } std::wstring env_block = create_environment_block(cloned_env); - std::wstring wcmd = resolve_command_string(cmd, start_dir, NULL, creation_flags); - ret = CreateProcessW(NULL, (LPWSTR) wcmd.c_str(), NULL, NULL, !!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES), creation_flags, env_block.data(), start_dir.empty() ? NULL : start_dir.c_str(), (LPSTARTUPINFOW) &startup_info, &process_info); + std::wstring wcmd = resolve_command_string(cmd, start_dir, nullptr, creation_flags); + ret = CreateProcessW(nullptr, (LPWSTR) wcmd.c_str(), nullptr, nullptr, !!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES), creation_flags, env_block.data(), start_dir.empty() ? nullptr : start_dir.c_str(), (LPSTARTUPINFOW) &startup_info, &process_info); } // Use the results of the launch to create a bp::child object @@ -1109,7 +1109,7 @@ namespace platf { static std::once_flag load_wlanapi_once_flag; std::call_once(load_wlanapi_once_flag, []() { // wlanapi.dll is not installed by default on Windows Server, so we load it dynamically - HMODULE wlanapi = LoadLibraryExA("wlanapi.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + HMODULE wlanapi = LoadLibraryExA("wlanapi.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!wlanapi) { BOOST_LOG(debug) << "wlanapi.dll is not available on this OS"sv; return; @@ -1186,7 +1186,7 @@ namespace platf { fn_WlanFreeMemory(wlan_interface_list); } else { fn_WlanCloseHandle(wlan_handle, nullptr); - wlan_handle = NULL; + wlan_handle = nullptr; } } } @@ -1257,7 +1257,7 @@ namespace platf { startup_info.StartupInfo.cb = sizeof(startup_info); WCHAR executable[MAX_PATH]; - if (GetModuleFileNameW(NULL, executable, ARRAYSIZE(executable)) == 0) { + if (GetModuleFileNameW(nullptr, executable, ARRAYSIZE(executable)) == 0) { auto winerr = GetLastError(); BOOST_LOG(fatal) << "Failed to get Sunshine path: "sv << winerr; return; @@ -1277,7 +1277,7 @@ namespace platf { void restart() { // If we're running standalone, we have to respawn ourselves via CreateProcess(). // If we're running from the service, we should just exit and let it respawn us. - if (GetConsoleWindow() != NULL) { + if (GetConsoleWindow() != nullptr) { // Avoid racing with the new process by waiting until we're exiting to start it. atexit(restart_on_exit); } @@ -1595,7 +1595,7 @@ namespace platf { } virtual ~qos_t() { - if (!fn_QOSRemoveSocketFromFlow(qos_handle, (SOCKET) NULL, flow_id, 0)) { + if (!fn_QOSRemoveSocketFromFlow(qos_handle, (SOCKET) nullptr, flow_id, 0)) { auto winerr = GetLastError(); BOOST_LOG(warning) << "QOSRemoveSocketFromFlow() failed: "sv << winerr; } @@ -1627,7 +1627,7 @@ namespace platf { static std::once_flag load_qwave_once_flag; std::call_once(load_qwave_once_flag, []() { // qWAVE is not installed by default on Windows Server, so we load it dynamically - HMODULE qwave = LoadLibraryExA("qwave.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + HMODULE qwave = LoadLibraryExA("qwave.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!qwave) { BOOST_LOG(debug) << "qwave.dll is not available on this OS"sv; return; @@ -1845,11 +1845,11 @@ namespace platf { } operator bool() override { - return timer != NULL; + return timer != nullptr; } private: - HANDLE timer = NULL; + HANDLE timer = nullptr; }; std::unique_ptr create_high_precision_timer() { diff --git a/src/platform/windows/misc.h b/src/platform/windows/misc.h index c996e677..69a5726b 100644 --- a/src/platform/windows/misc.h +++ b/src/platform/windows/misc.h @@ -9,7 +9,7 @@ #include // platform includes -#include +#include #include namespace platf { diff --git a/src/platform/windows/nvprefs/driver_settings.cpp b/src/platform/windows/nvprefs/driver_settings.cpp index 57aa809f..8cefd793 100644 --- a/src/platform/windows/nvprefs/driver_settings.cpp +++ b/src/platform/windows/nvprefs/driver_settings.cpp @@ -60,7 +60,7 @@ namespace nvprefs { void driver_settings_t::destroy() { if (session_handle) { NvAPI_DRS_DestroySession(session_handle); - session_handle = 0; + session_handle = nullptr; } NvAPI_Unload(); } @@ -105,7 +105,7 @@ namespace nvprefs { if (swapchain_data) { NvAPI_Status status; - NvDRSProfileHandle profile_handle = 0; + NvDRSProfileHandle profile_handle = nullptr; status = NvAPI_DRS_GetBaseProfile(session_handle, &profile_handle); if (status != NVAPI_OK) { nvapi_error_message(status); @@ -168,7 +168,7 @@ namespace nvprefs { return true; } - NvDRSProfileHandle profile_handle = 0; + NvDRSProfileHandle profile_handle = nullptr; status = NvAPI_DRS_GetBaseProfile(session_handle, &profile_handle); if (status != NVAPI_OK) { nvapi_error_message(status); @@ -224,7 +224,7 @@ namespace nvprefs { NvAPI_UnicodeString profile_name = {}; fill_nvapi_string(profile_name, sunshine_application_profile_name); - NvDRSProfileHandle profile_handle = 0; + NvDRSProfileHandle profile_handle = nullptr; status = NvAPI_DRS_FindProfileByName(session_handle, profile_name, &profile_handle); if (status != NVAPI_OK) { diff --git a/src/platform/windows/nvprefs/driver_settings.h b/src/platform/windows/nvprefs/driver_settings.h index e4649d1d..f62cf4bd 100644 --- a/src/platform/windows/nvprefs/driver_settings.h +++ b/src/platform/windows/nvprefs/driver_settings.h @@ -36,7 +36,7 @@ namespace nvprefs { bool check_and_modify_application_profile(bool &modified); private: - NvDRSSessionHandle session_handle = 0; + NvDRSSessionHandle session_handle = nullptr; }; } // namespace nvprefs diff --git a/src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp b/src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp index b4c04f6a..d03c6bbb 100644 --- a/src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp +++ b/src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp @@ -15,7 +15,7 @@ namespace { std::map interfaces; - HMODULE dll = NULL; + HMODULE dll = nullptr; template NvAPI_Status call_interface(const char *name, Args... args) { @@ -47,7 +47,7 @@ NvAPI_Initialize() { auto dll_name = "nvapi.dll"; #endif - if ((dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) { + if ((dll = LoadLibraryEx(dll_name, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32))) { if (auto query_interface = (decltype(nvapi_QueryInterface) *) GetProcAddress(dll, "nvapi_QueryInterface")) { for (const auto &item : nvapi_interface_table) { interfaces[item.func] = query_interface(item.id); @@ -64,7 +64,7 @@ NVAPI_INTERFACE NvAPI_Unload() { if (dll) { interfaces.clear(); FreeLibrary(dll); - dll = NULL; + dll = nullptr; } return NVAPI_OK; } diff --git a/src/platform/windows/nvprefs/nvprefs_common.h b/src/platform/windows/nvprefs/nvprefs_common.h index baf4c0fb..61a7a6eb 100644 --- a/src/platform/windows/nvprefs/nvprefs_common.h +++ b/src/platform/windows/nvprefs/nvprefs_common.h @@ -7,8 +7,8 @@ // platform includes // disable clang-format header reordering // clang-format off -#include -#include +#include +#include // clang-format on // local includes @@ -21,7 +21,7 @@ namespace nvprefs { explicit operator bool() const { auto handle = get(); - return handle != NULL && handle != INVALID_HANDLE_VALUE; + return handle != nullptr && handle != INVALID_HANDLE_VALUE; } }; diff --git a/src/platform/windows/nvprefs/undo_file.cpp b/src/platform/windows/nvprefs/undo_file.cpp index ada737c8..477af033 100644 --- a/src/platform/windows/nvprefs/undo_file.cpp +++ b/src/platform/windows/nvprefs/undo_file.cpp @@ -51,7 +51,7 @@ namespace nvprefs { std::optional undo_file_t::open_existing_file(std::filesystem::path file_path, bool &access_denied) { undo_file_t file; - file.file_handle.reset(CreateFileW(file_path.c_str(), GENERIC_READ | DELETE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); + file.file_handle.reset(CreateFileW(file_path.c_str(), GENERIC_READ | DELETE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); if (file.file_handle) { access_denied = false; return file; @@ -64,7 +64,7 @@ namespace nvprefs { std::optional undo_file_t::create_new_file(std::filesystem::path file_path) { undo_file_t file; - file.file_handle.reset(CreateFileW(file_path.c_str(), GENERIC_WRITE | STANDARD_RIGHTS_ALL, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL)); + file.file_handle.reset(CreateFileW(file_path.c_str(), GENERIC_WRITE | STANDARD_RIGHTS_ALL, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr)); if (file.file_handle) { // give GENERIC_READ, GENERIC_WRITE and DELETE permissions to Users group diff --git a/src/platform/windows/publish.cpp b/src/platform/windows/publish.cpp index 999f1939..600253de 100644 --- a/src/platform/windows/publish.cpp +++ b/src/platform/windows/publish.cpp @@ -3,12 +3,12 @@ * @brief Definitions for Windows mDNS service registration. */ // platform includes -// winsock2.h must be included before windows.h +// WinSock2.h must be included before Windows.h // clang-format off -#include -#include +#include +#include // clang-format on -#include +#include #include // local includes diff --git a/src/process.cpp b/src/process.cpp index b8b970b0..d7d4af4b 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -394,7 +394,7 @@ namespace proc { _env["APOLLO_CLIENT_HOST_AUDIO"] = launch_session->host_audio ? "true" : "false"; _env["APOLLO_CLIENT_ENABLE_SOPS"] = launch_session->enable_sops ? "true" : "false"; - int channelCount = launch_session->surround_info & (65535); + int channelCount = launch_session->surround_info & 65535; switch (channelCount) { case 2: _env["SUNSHINE_CLIENT_AUDIO_CONFIGURATION"] = "2.0"; diff --git a/src/rtsp.cpp b/src/rtsp.cpp index c81687de..9f08916a 100644 --- a/src/rtsp.cpp +++ b/src/rtsp.cpp @@ -12,6 +12,7 @@ extern "C" { // standard includes #include #include +#include #include #include #include @@ -897,7 +898,7 @@ namespace rtsp_stream { session_option.next = &port_option; // Moonlight merely requires 'server_port=' - auto port_value = "server_port=" + std::to_string(port); + auto port_value = std::format("server_port={}", static_cast(port)); port_option.option = const_cast("Transport"); port_option.content = port_value.data(); diff --git a/src/system_tray.cpp b/src/system_tray.cpp index fb62e382..caeb4544 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -34,8 +34,12 @@ #endif // standard includes + #include + #include #include + #include #include + #include // lib includes #include @@ -56,9 +60,14 @@ using namespace std::literals; // system_tray namespace namespace system_tray { - static std::atomic tray_initialized = false; + static std::atomic tray_initialized = false; - void tray_open_ui_cb(struct tray_menu *item) { + // Threading variables for all platforms + static std::thread tray_thread; + static std::atomic tray_thread_running = false; + static std::atomic tray_thread_should_exit = false; + + void tray_open_ui_cb([[maybe_unused]] struct tray_menu *item) { BOOST_LOG(info) << "Opening UI from system tray"sv; launch_ui(); } @@ -69,20 +78,20 @@ namespace system_tray { proc::proc.terminate(); } - void tray_reset_display_device_config_cb(struct tray_menu *item) { + void tray_reset_display_device_config_cb([[maybe_unused]] struct tray_menu *item) { BOOST_LOG(info) << "Resetting display device config from system tray"sv; std::ignore = display_device::reset_persistence(); } - void tray_restart_cb(struct tray_menu *item) { + void tray_restart_cb([[maybe_unused]] struct tray_menu *item) { BOOST_LOG(info) << "Restarting from system tray"sv; proc::proc.terminate(); platf::restart(); } - void tray_quit_cb(struct tray_menu *item) { + void tray_quit_cb([[maybe_unused]] struct tray_menu *item) { BOOST_LOG(info) << "Quitting from system tray"sv; proc::proc.terminate(); @@ -90,7 +99,7 @@ namespace system_tray { #ifdef _WIN32 // If we're running in a service, return a special status to // tell it to terminate too, otherwise it will just respawn us. - if (GetConsoleWindow() == NULL) { + if (GetConsoleWindow() == nullptr) { lifetime::exit_sunshine(ERROR_SHUTDOWN_IN_PROGRESS, true); return; } @@ -131,7 +140,7 @@ namespace system_tray { .allIconPaths = {TRAY_ICON, TRAY_ICON_LOCKED, TRAY_ICON_PLAYING, TRAY_ICON_PAUSING}, }; - int system_tray() { + int init_tray() { #ifdef _WIN32 // If we're running as SYSTEM, Explorer.exe will not have permission to open our thread handle // to monitor for thread termination. If Explorer fails to open our thread, our tray icon @@ -197,52 +206,32 @@ namespace system_tray { if (tray_init(&tray) < 0) { BOOST_LOG(warning) << "Failed to create system tray"sv; return 1; - } else { - BOOST_LOG(info) << "System tray created"sv; } + BOOST_LOG(info) << "System tray created"sv; tray_initialized = true; - while (tray_loop(1) == 0) { - BOOST_LOG(debug) << "System tray loop"sv; + return 0; + } + + int process_tray_events() { + if (!tray_initialized) { + return 1; + } + + // Process one iteration of the tray loop with non-blocking mode (0) + if (const int result = tray_loop(0); result != 0) { + BOOST_LOG(warning) << "System tray loop failed"sv; + return result; } return 0; } - void run_tray() { - // create the system tray - #if defined(__APPLE__) || defined(__MACH__) - // macOS requires that UI elements be created on the main thread - // creating tray using dispatch queue does not work, although the code doesn't actually throw any (visible) errors - - // dispatch_async(dispatch_get_main_queue(), ^{ - // system_tray(); - // }); - - BOOST_LOG(info) << "system_tray() is not yet implemented for this platform."sv; - #else // Windows, Linux - // create tray in separate thread - - #ifdef _WIN32 - std::string tmp_str = "Open Apollo (" + config::nvhttp.sunshine_name + ":" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + ")"; - static const std::string title_str = utf8ToAcp(tmp_str); - #else - static const std::string title_str = "Open Apollo (" + config::nvhttp.sunshine_name + ":" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + ")"; - #endif - tray.menu[0].text = title_str.c_str(); - - if (config::sunshine.hide_tray_controls) { - tray.menu[1].text = nullptr; - } - - std::thread tray_thread(system_tray); - tray_thread.detach(); - #endif - } - int end_tray() { - tray_initialized = false; - tray_exit(); + if (tray_initialized) { + tray_initialized = false; + tray_exit(); + } return 0; } @@ -251,10 +240,10 @@ namespace system_tray { return; } - tray.notification_title = NULL; - tray.notification_text = NULL; - tray.notification_cb = NULL; - tray.notification_icon = NULL; + tray.notification_title = nullptr; + tray.notification_text = nullptr; + tray.notification_cb = nullptr; + tray.notification_icon = nullptr; tray.icon = TRAY_ICON_PLAYING; tray_update(&tray); @@ -280,10 +269,10 @@ namespace system_tray { return; } - tray.notification_title = NULL; - tray.notification_text = NULL; - tray.notification_cb = NULL; - tray.notification_icon = NULL; + tray.notification_title = nullptr; + tray.notification_text = nullptr; + tray.notification_cb = nullptr; + tray.notification_icon = nullptr; tray.icon = TRAY_ICON_PAUSING; tray_update(&tray); char msg[256]; @@ -304,10 +293,10 @@ namespace system_tray { return; } - tray.notification_title = NULL; - tray.notification_text = NULL; - tray.notification_cb = NULL; - tray.notification_icon = NULL; + tray.notification_title = nullptr; + tray.notification_text = nullptr; + tray.notification_cb = nullptr; + tray.notification_icon = nullptr; tray.icon = TRAY_ICON; tray_update(&tray); char msg[256]; @@ -358,10 +347,10 @@ namespace system_tray { return; } - tray.notification_title = NULL; - tray.notification_text = NULL; - tray.notification_cb = NULL; - tray.notification_icon = NULL; + tray.notification_title = nullptr; + tray.notification_text = nullptr; + tray.notification_cb = nullptr; + tray.notification_icon = nullptr; tray.icon = TRAY_ICON; tray_update(&tray); tray.icon = TRAY_ICON; @@ -370,7 +359,7 @@ namespace system_tray { tray.notification_icon = TRAY_ICON_LOCKED; tray.tooltip = PROJECT_NAME; tray.notification_cb = []() { - launch_ui_with_path("/pin#PIN"); + launch_ui("/pin#PIN"); }; tray_update(&tray); } @@ -422,6 +411,138 @@ namespace system_tray { tray_update(&tray); } + // Threading functions available on all platforms + static void tray_thread_worker() { + BOOST_LOG(info) << "System tray thread started"sv; + + // Initialize the tray in this thread + if (init_tray() != 0) { + BOOST_LOG(error) << "Failed to initialize tray in thread"sv; + tray_thread_running = false; + return; + } + + tray_thread_running = true; + + // Main tray event loop + while (!tray_thread_should_exit) { + if (process_tray_events() != 0) { + BOOST_LOG(warning) << "Tray event processing failed in thread"sv; + break; + } + + // Sleep to avoid busy waiting + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + // Clean up the tray + end_tray(); + tray_thread_running = false; + BOOST_LOG(info) << "System tray thread ended"sv; + } + + // void run_tray() { + // // create the system tray + // #if defined(__APPLE__) || defined(__MACH__) + // // macOS requires that UI elements be created on the main thread + // // creating tray using dispatch queue does not work, although the code doesn't actually throw any (visible) errors + + // // dispatch_async(dispatch_get_main_queue(), ^{ + // // system_tray(); + // // }); + + // BOOST_LOG(info) << "system_tray() is not yet implemented for this platform."sv; + // #else // Windows, Linux + // // create tray in separate thread + + // #ifdef _WIN32 + // std::string tmp_str = "Open Apollo (" + config::nvhttp.sunshine_name + ":" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + ")"; + // static const std::string title_str = utf8ToAcp(tmp_str); + // #else + // static const std::string title_str = "Open Apollo (" + config::nvhttp.sunshine_name + ":" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + ")"; + // #endif + // tray.menu[0].text = title_str.c_str(); + + // if (config::sunshine.hide_tray_controls) { + // tray.menu[1].text = nullptr; + // } + + // std::thread tray_thread(system_tray); + // tray_thread.detach(); + // #endif + // } + + int init_tray_threaded() { + if (tray_thread_running) { + BOOST_LOG(warning) << "Tray thread is already running"sv; + return 1; + } + + #ifdef _WIN32 + std::string tmp_str = "Open Apollo (" + config::nvhttp.sunshine_name + ":" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + ")"; + static const std::string title_str = utf8ToAcp(tmp_str); + #else + static const std::string title_str = "Open Apollo (" + config::nvhttp.sunshine_name + ":" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + ")"; + #endif + tray.menu[0].text = title_str.c_str(); + + if (config::sunshine.hide_tray_controls) { + tray.menu[1].text = nullptr; + } + + tray_thread_should_exit = false; + + try { + tray_thread = std::thread(tray_thread_worker); + + // Wait for the thread to start and initialize + const auto start_time = std::chrono::steady_clock::now(); + while (!tray_thread_running && !tray_thread_should_exit) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + // Timeout after 10 seconds + if (std::chrono::steady_clock::now() - start_time > std::chrono::seconds(10)) { + BOOST_LOG(error) << "Tray thread initialization timeout"sv; + tray_thread_should_exit = true; + if (tray_thread.joinable()) { + tray_thread.join(); + } + return 1; + } + } + + if (!tray_thread_running) { + BOOST_LOG(error) << "Tray thread failed to start"sv; + if (tray_thread.joinable()) { + tray_thread.join(); + } + return 1; + } + + BOOST_LOG(info) << "System tray thread initialized successfully"sv; + return 0; + } catch (const std::exception &e) { + BOOST_LOG(error) << "Failed to create tray thread: " << e.what(); + return 1; + } + } + + int end_tray_threaded() { + if (!tray_thread_running) { + return 0; + } + + BOOST_LOG(info) << "Stopping system tray thread"sv; + tray_thread_should_exit = true; + + if (tray_thread.joinable()) { + tray_thread.join(); + } + + BOOST_LOG(info) << "System tray thread stopped"sv; + return 0; + } + } // namespace system_tray #ifdef BOOST_PROCESS_VERSION diff --git a/src/system_tray.h b/src/system_tray.h index b39a101a..6574445f 100644 --- a/src/system_tray.h +++ b/src/system_tray.h @@ -12,7 +12,7 @@ namespace system_tray { * @brief Callback for opening the UI from the system tray. * @param item The tray menu item. */ - void tray_open_ui_cb(struct tray_menu *item); + void tray_open_ui_cb([[maybe_unused]] struct tray_menu *item); void tray_force_stop_cb(struct tray_menu *item); @@ -21,32 +21,31 @@ namespace system_tray { * @brief Callback for resetting display device configuration. * @param item The tray menu item. */ - void tray_reset_display_device_config_cb(struct tray_menu *item); + void tray_reset_display_device_config_cb([[maybe_unused]] struct tray_menu *item); /** * @brief Callback for restarting Sunshine from the system tray. * @param item The tray menu item. */ - void tray_restart_cb(struct tray_menu *item); + void tray_restart_cb([[maybe_unused]] struct tray_menu *item); /** * @brief Callback for exiting Sunshine from the system tray. * @param item The tray menu item. */ - void tray_quit_cb(struct tray_menu *item); + void tray_quit_cb([[maybe_unused]] struct tray_menu *item); /** - * @brief Create the system tray. - * @details This function has an endless loop, so it should be run in a separate thread. - * @return 1 if the system tray failed to create, otherwise 0 once the tray has been terminated. + * @brief Initializes the system tray without starting a loop. + * @return 0 if initialization was successful, non-zero otherwise. */ - int system_tray(); + int init_tray(); /** - * @brief Run the system tray with platform specific options. - * @todo macOS requires that UI elements be created on the main thread, so the system tray is not currently implemented for macOS. + * @brief Processes a single tray event iteration. + * @return 0 if processing was successful, non-zero otherwise. */ - int run_tray(); + int process_tray_events(); /** * @brief Exit the system tray. @@ -83,4 +82,15 @@ namespace system_tray { void update_tray_paired(std::string device_name); void update_tray_client_connected(std::string client_name); + /** + * @brief Initializes and runs the system tray in a separate thread. + * @return 0 if initialization was successful, non-zero otherwise. + */ + int init_tray_threaded(); + + /** + * @brief Stops the threaded system tray and waits for the thread to finish. + * @return 0 after stopping the threaded tray. + */ + int end_tray_threaded(); } // namespace system_tray diff --git a/src/video.cpp b/src/video.cpp index 6893137c..93429b77 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -325,6 +325,12 @@ namespace video { avcodec_encode_session_t(avcodec_encode_session_t &&other) noexcept = default; ~avcodec_encode_session_t() { + // Flush any remaining frames in the encoder + if (avcodec_send_frame(avcodec_ctx.get(), nullptr) == 0) { + packet_raw_avcodec pkt; + while (avcodec_receive_packet(avcodec_ctx.get(), pkt.av_packet) == 0); + } + // Order matters here because the context relies on the hwdevice still being valid avcodec_ctx.reset(); device.reset(); @@ -546,7 +552,7 @@ namespace video { {"forced-idr"s, 1}, {"zerolatency"s, 1}, {"surfaces"s, 1}, - {"filler_data"s, false}, + {"cbr_padding"s, false}, {"preset"s, &config::video.nv_legacy.preset}, {"tune"s, NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY}, {"rc"s, NV_ENC_PARAMS_RC_CBR}, @@ -567,6 +573,7 @@ namespace video { {"forced-idr"s, 1}, {"zerolatency"s, 1}, {"surfaces"s, 1}, + {"cbr_padding"s, false}, {"preset"s, &config::video.nv_legacy.preset}, {"tune"s, NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY}, {"rc"s, NV_ENC_PARAMS_RC_CBR}, @@ -592,6 +599,7 @@ namespace video { {"forced-idr"s, 1}, {"zerolatency"s, 1}, {"surfaces"s, 1}, + {"cbr_padding"s, false}, {"preset"s, &config::video.nv_legacy.preset}, {"tune"s, NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY}, {"rc"s, NV_ENC_PARAMS_RC_CBR}, @@ -740,6 +748,7 @@ namespace video { {"filler_data"s, false}, {"forced_idr"s, 1}, {"latency"s, "lowest_latency"s}, + {"async_depth"s, 1}, {"skip_frame"s, 0}, {"log_to_dbg"s, []() { return config::sunshine.min_log_level < 2 ? 1 : 0; @@ -763,6 +772,7 @@ namespace video { {"filler_data"s, false}, {"forced_idr"s, 1}, {"latency"s, 1}, + {"async_depth"s, 1}, {"skip_frame"s, 0}, {"log_to_dbg"s, []() { return config::sunshine.min_log_level < 2 ? 1 : 0; @@ -801,6 +811,7 @@ namespace video { {"filler_data"s, false}, {"forced_idr"s, 1}, {"latency"s, 1}, + {"async_depth"s, 1}, {"frame_skipping"s, 0}, {"log_to_dbg"s, []() { return config::sunshine.min_log_level < 2 ? 1 : 0; @@ -1559,22 +1570,22 @@ namespace video { case 0: // 10-bit h264 encoding is not supported by our streaming protocol assert(!config.dynamicRange); - ctx->profile = (config.chromaSamplingType == 1) ? FF_PROFILE_H264_HIGH_444_PREDICTIVE : FF_PROFILE_H264_HIGH; + ctx->profile = (config.chromaSamplingType == 1) ? AV_PROFILE_H264_HIGH_444_PREDICTIVE : AV_PROFILE_H264_HIGH; break; case 1: if (config.chromaSamplingType == 1) { // HEVC uses the same RExt profile for both 8 and 10 bit YUV 4:4:4 encoding - ctx->profile = FF_PROFILE_HEVC_REXT; + ctx->profile = AV_PROFILE_HEVC_REXT; } else { - ctx->profile = config.dynamicRange ? FF_PROFILE_HEVC_MAIN_10 : FF_PROFILE_HEVC_MAIN; + ctx->profile = config.dynamicRange ? AV_PROFILE_HEVC_MAIN_10 : AV_PROFILE_HEVC_MAIN; } break; case 2: // AV1 supports both 8 and 10 bit encoding with the same Main profile // but YUV 4:4:4 sampling requires High profile - ctx->profile = (config.chromaSamplingType == 1) ? FF_PROFILE_AV1_HIGH : FF_PROFILE_AV1_MAIN; + ctx->profile = (config.chromaSamplingType == 1) ? AV_PROFILE_AV1_HIGH : AV_PROFILE_AV1_MAIN; break; } diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index c0cb4873..1c638b6f 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -152,6 +152,7 @@ "global_state_cmd": [], "server_cmd": [], "notify_pre_releases": "disabled", + "system_tray": "enabled", "hide_tray_controls": "disabled", "enable_pairing": "enabled", "enable_discovery": "enabled", @@ -166,6 +167,7 @@ "ds4_back_as_touchpad_click": "enabled", "motion_as_ds4": "enabled", "touchpad_as_ds4": "enabled", + "ds5_inputtino_randomize_mac": "enabled", "back_button_timeout": -1, "keyboard": "enabled", "key_repeat_delay": 500, @@ -186,6 +188,7 @@ options: { "audio_sink": "", "virtual_sink": "", + "stream_audio": "enabled", "install_steam_audio_drivers": "enabled", "keep_sink_default": "enabled", "auto_capture_sink": "enabled", @@ -199,6 +202,7 @@ "dd_refresh_rate_option": "auto", "dd_manual_refresh_rate": "", "dd_hdr_option": "auto", + "dd_wa_hdr_toggle_delay": 0, "dd_config_revert_delay": 3000, "dd_config_revert_on_disconnect": "disabled", "dd_mode_remapping": {"mixed": [], "resolution_only": [], "refresh_rate_only": []}, @@ -206,7 +210,6 @@ "fallback_mode": "", "headless_mode": "disabled", "double_refreshrate": "disabled", - "dd_wa_hdr_toggle_delay": 0, "max_bitrate": 0, "minimum_fps_target": 0, "isolated_virtual_display_option": "disabled", diff --git a/src_assets/common/assets/web/configs/tabs/General.vue b/src_assets/common/assets/web/configs/tabs/General.vue index 039b7ec4..f7e17588 100644 --- a/src_assets/common/assets/web/configs/tabs/General.vue +++ b/src_assets/common/assets/web/configs/tabs/General.vue @@ -69,6 +69,7 @@ onMounted(() => { + @@ -79,6 +80,7 @@ onMounted(() => { + @@ -95,17 +97,17 @@ onMounted(() => {
- + -
{{ $t('config.log_level_desc') }}
+
{{ $t('config.min_log_level_desc') }}
@@ -234,6 +236,14 @@ onMounted(() => { v-model="config.notify_pre_releases" default="false" > + + + diff --git a/src_assets/common/assets/web/configs/tabs/Inputs.vue b/src_assets/common/assets/web/configs/tabs/Inputs.vue index f92baecb..091f839f 100644 --- a/src_assets/common/assets/web/configs/tabs/Inputs.vue +++ b/src_assets/common/assets/web/configs/tabs/Inputs.vue @@ -45,28 +45,28 @@ const config = ref(props.config)