Merge remote-tracking branch 'sunshine/master'

This commit is contained in:
Yukino Song
2025-09-27 00:37:24 +08:00
104 changed files with 3939 additions and 898 deletions

View File

@@ -69,7 +69,6 @@ IndentWrappedFunctionNames: true
InsertBraces: true
InsertNewlineAtEOF: true
KeepEmptyLinesAtTheStartOfBlocks: false
LineEnding: LF
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
ObjCBinPackProtocolList: Never

11
.github/copilot-instructions.md vendored Normal file
View File

@@ -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.

View File

@@ -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 ()

View File

@@ -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()

View File

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

View File

@@ -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"

View File

@@ -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()

View File

@@ -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 "\

View File

@@ -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 \

View File

@@ -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)

View File

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

View File

@@ -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 <<EOF > /entrypoint.sh
cat <<EOF > 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"]

View File

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

View File

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

View File

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

View File

@@ -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)**

View File

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

View File

@@ -261,6 +261,29 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr>
</table>
### system_tray
<table>
<tr>
<td>Description</td>
<td colspan="2">
Show icon in system tray and display desktop notifications
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}
enabled
@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
system_tray = enabled
@endcode</td>
</tr>
</table>
## Input
### controller
@@ -416,6 +439,30 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr>
</table>
### ds5_inputtino_randomize_mac
<table>
<tr>
<td>Description</td>
<td colspan="2">
Randomize the MAC-Address for the generated virtual controller.
@hint{Only applies on linux for gamepads created as PS5-style controllers}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}
enabled
@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
ds5_inputtino_randomize_mac = enabled
@endcode</td>
</tr>
</table>
### back_button_timeout
<table>

View File

@@ -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.
</template>
```
@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.

View File

@@ -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.
<div class="section_buttons">

View File

@@ -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.
<table>
<caption>CUDA Compatibility</caption>
@@ -43,9 +47,9 @@ need to install CUDA.}
<th>Package</th>
</tr>
<tr>
<td rowspan="3">11.8.0</td>
<td rowspan="3">450.80.02</td>
<td rowspan="3">35;50;52;60;61;62;70;72;75;80;86;87;89;90</td>
<td rowspan="8">12.9.1</td>
<td rowspan="8">575.57.08</td>
<td rowspan="8">50;52;60;61;62;70;72;75;80;86;87;89;90;100;101;103;120;121</td>
<td>sunshine.AppImage</td>
</tr>
<tr>
@@ -55,33 +59,26 @@ need to install CUDA.}
<td>sunshine-ubuntu-24.04-{arch}.deb</td>
</tr>
<tr>
<td rowspan="1">12.0.0</td>
<td rowspan="1">525.60.13</td>
<td rowspan="5">50;52;60;61;62;70;72;75;80;86;87;89;90</td>
<td>sunshine-debian-bookworm-{arch}.deb</td>
<td>sunshine-debian-trixie-{arch}.deb</td>
</tr>
<tr>
<td rowspan="2">12.6.2</td>
<td rowspan="2">560.35.03</td>
<td>sunshine_{arch}.flatpak</td>
</tr>
<tr>
<td>Sunshine (copr - Fedora 41)</td>
</tr>
<tr>
<td rowspan="1">12.8.1</td>
<td rowspan="1">570.124.06</td>
<td>Sunshine (copr - Fedora 42)</td>
</tr>
<tr>
<td rowspan="1">12.9.1</td>
<td rowspan="1">575.57.08</td>
<td>sunshine.pkg.tar.zst</td>
</tr>
</table>
#### 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 <a href="ms-settings:installed-apps">here</a> 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 <directory of conf file>/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 <user>@<ip_address> '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).

View File

@@ -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).

View File

@@ -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)

View File

@@ -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 <NIC> 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 3060% 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.

View File

@@ -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"
}
}

View File

@@ -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
}

View File

@@ -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"
}

View File

@@ -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
}

View File

@@ -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 <<EOF
uhid
EOF
%post
# Note: this is copied from the postinst script
# Load uhid (DS5 emulation)
echo "Loading uhid kernel module for DS5 emulation."
modprobe uhid
# Check if we're in an rpm-ostree environment
if [ ! -x "$(command -v rpm-ostree)" ]; then
echo "Not in an rpm-ostree environment, proceeding with post install steps."
@@ -238,10 +237,6 @@ else
echo "rpm-ostree environment detected, skipping post install steps. Restart to apply the changes."
fi
%preun
# Remove modules-load configuration
rm -f /usr/lib/modules-load.d/uhid.conf
%files
# Executables
%caps(cap_sys_admin+p) %{_bindir}/sunshine
@@ -254,7 +249,7 @@ rm -f /usr/lib/modules-load.d/uhid.conf
%{_udevrulesdir}/*-sunshine.rules
# Modules-load configuration
%{_modulesloaddir}/uhid.conf
%{_modulesloaddir}/*-sunshine.conf
# Desktop entries
%{_datadir}/applications/*.desktop

View File

@@ -19,8 +19,8 @@
"only-arches": [
"x86_64"
],
"url": "https://developer.download.nvidia.com/compute/cuda/12.6.2/local_installers/cuda_12.6.2_560.35.03_linux.run",
"sha256": "3729a89cb58f7ca6a46719cff110d6292aec7577585a8d71340f0dbac54fb237",
"url": "https://developer.download.nvidia.com/compute/cuda/12.9.1/local_installers/cuda_12.9.1_575.57.08_linux.run",
"sha256": "0f6d806ddd87230d2adbe8a6006a9d20144fdbda9de2d6acc677daa5d036417a",
"dest-filename": "cuda.run"
},
{
@@ -28,8 +28,8 @@
"only-arches": [
"aarch64"
],
"url": "https://developer.download.nvidia.com/compute/cuda/12.6.2/local_installers/cuda_12.6.2_560.35.03_linux_sbsa.run",
"sha256": "2249408848b705c18b9eadfb5161b52e4e36fcc5753647329cce93db141e5466",
"url": "https://developer.download.nvidia.com/compute/cuda/12.9.1/local_installers/cuda_12.9.1_575.57.08_linux_sbsa.run",
"sha256": "64f47ab791a76b6889702425e0755385f5fa216c5a9f061875c7deed5f08cdb6",
"dest-filename": "cuda.run"
}
]

View File

@@ -2,6 +2,7 @@
"name": "miniupnpc",
"buildsystem": "cmake-ninja",
"builddir": true,
"subdir": "miniupnpc",
"config-opts": [
"-DCMAKE_BUILD_TYPE=RelWithDebInfo",
"-DUPNPC_BUILD_STATIC=OFF",
@@ -11,15 +12,16 @@
],
"sources": [
{
"url": "https://miniupnp.tuxfamily.org/files/miniupnpc-2.3.3.tar.gz",
"sha256": "d52a0afa614ad6c088cc9ddff1ae7d29c8c595ac5fdd321170a05f41e634bd1a",
"type": "git",
"url": "https://github.com/miniupnp/miniupnp.git",
"tag": "miniupnpc_2_3_3",
"commit": "bf4215a7574f88aa55859db9db00e3ae58cf42d6",
"x-checker-data": {
"type": "anitya",
"project-id": 1986,
"stable-only": true,
"url-template": "https://miniupnp.tuxfamily.org/files/miniupnpc-$version.tar.gz"
},
"type": "archive"
"tag-template": "miniupnpc_${version0}_${version1}_${version2}"
}
}
],
"cleanup": [

View File

@@ -6,6 +6,12 @@ cp "/app/share/sunshine/systemd/user/sunshine.service" "$HOME/.config/systemd/us
echo "Sunshine User Service has been installed."
echo "Use [systemctl --user enable sunshine] once to autostart Sunshine on login."
# Load uhid (DS5 emulation)
UHID=$(cat /app/share/sunshine/modules-load.d/60-sunshine.conf)
echo "Enabling DS5 emulation."
flatpak-spawn --host pkexec sh -c "echo '$UHID' > /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."

View File

@@ -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."

View File

@@ -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,

View File

@@ -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"

View File

@@ -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);

View File

@@ -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_cmd_t> prep_cmds;
std::vector<prep_cmd_t> state_cmds;
std::vector<server_cmd_t> server_cmds;

View File

@@ -8,6 +8,7 @@
// standard includes
#include <filesystem>
#include <format>
#include <fstream>
#include <set>
#include <sstream>

View File

@@ -4,6 +4,7 @@
*/
// standard includes
#include <csignal>
#include <format>
#include <iostream>
#include <thread>
@@ -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<std::string> &path) {
std::string url = std::format("https://localhost:{}", static_cast<int>(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() {

View File

@@ -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<std::string> &path = std::nullopt);
/**
* @brief Functions for handling command line arguments.

View File

@@ -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<safe::event_t<bool>> &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<bool>(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;

View File

@@ -5,6 +5,9 @@
// this include
#include "nvenc_base.h"
// standard includes
#include <format>
// 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";

View File

@@ -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<NV_ENCODE_API_FUNCTION_LIST>();
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;

View File

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

View File

@@ -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 = [&]<typename T>(T &location, auto symbol) -> bool {
location = (T) GetProcAddress(cuda_functions.dll, symbol);
return location != nullptr;

View File

@@ -56,7 +56,7 @@ namespace nvenc {
autopop_context push_context();
HMODULE dll = NULL;
HMODULE dll = nullptr;
const ID3D11DevicePtr d3d_device;
ID3D11Texture2DPtr d3d_input_texture;

View File

@@ -7,6 +7,7 @@
// standard includes
#include <filesystem>
#include <format>
#include <string>
#include <utility>
#include <string>
@@ -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.<xmlattr>.status_code", 400);
tree.put(
"root.<xmlattr>.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.<xmlattr>.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<int>(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.<xmlattr>.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<int>(net::map_port(rtsp_stream::RTSP_SETUP_PORT))
)
);
tree.put("root.resume", 1);
rtsp_stream::launch_session_raise(launch_session);

View File

@@ -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<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) {
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
return ::cuda::make_avcodec_encode_device(width, height, true);
}

View File

@@ -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 {

View File

@@ -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();

View File

@@ -4,6 +4,7 @@
*/
// standard includes
#include <fcntl.h>
#include <format>
#include <sstream>
#include <string>
@@ -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};

View File

@@ -4,8 +4,11 @@
*/
#define INITGUID
// standard includes
#include <format>
// platform includes
#include <audioclient.h>
#include <Audioclient.h>
#include <avrt.h>
#include <mmdeviceapi.h>
#include <newdev.h>
@@ -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<int>(waveformat.Samples.wValidBitsPerSample), static_cast<int>(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<int>(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 {

View File

@@ -12,7 +12,7 @@
#include <dxgi.h>
#include <dxgi1_6.h>
#include <Unknwn.h>
#include <winrt/Windows.Graphics.Capture.h>
#include <winrt/windows.graphics.capture.h>
// local includes
#include "src/platform/common.h"

View File

@@ -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",

View File

@@ -7,7 +7,7 @@
// platform includes
#include <d3dcompiler.h>
#include <directxmath.h>
#include <DirectXMath.h>
extern "C" {
#include <libavcodec/avcodec.h>
@@ -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

View File

@@ -8,7 +8,7 @@
// Gross hack to work around MINGW-packages#22160
#define ____FIReference_1_boolean_INTERFACE_DEFINED__
#include <windows.graphics.capture.interop.h>
#include <Windows.Graphics.Capture.Interop.h>
#include <winrt/windows.foundation.h>
#include <winrt/windows.foundation.metadata.h>
#include <winrt/windows.graphics.directx.direct3d11.h>

View File

@@ -5,7 +5,7 @@
#define WINVER 0x0A00
// platform includes
#include <windows.h>
#include <Windows.h>
// standard includes
#include <cmath>
@@ -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++;

View File

@@ -28,13 +28,13 @@
#include <iphlpapi.h>
#include <iterator>
#include <timeapi.h>
#include <userenv.h>
#include <winsock2.h>
#include <windows.h>
#include <winuser.h>
#include <UserEnv.h>
#include <WinSock2.h>
#include <Windows.h>
#include <WinUser.h>
#include <wlanapi.h>
#include <ws2tcpip.h>
#include <wtsapi32.h>
#include <WS2tcpip.h>
#include <WtsApi32.h>
#include <sddl.h>
// 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<high_precision_timer> create_high_precision_timer() {

View File

@@ -9,7 +9,7 @@
#include <string_view>
// platform includes
#include <windows.h>
#include <Windows.h>
#include <winnt.h>
namespace platf {

View File

@@ -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) {

View File

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

View File

@@ -15,7 +15,7 @@
namespace {
std::map<const char *, void *> interfaces;
HMODULE dll = NULL;
HMODULE dll = nullptr;
template<typename Func, typename... Args>
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;
}

View File

@@ -7,8 +7,8 @@
// platform includes
// disable clang-format header reordering
// clang-format off
#include <windows.h>
#include <aclapi.h>
#include <Windows.h>
#include <AclAPI.h>
// 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;
}
};

View File

@@ -51,7 +51,7 @@ namespace nvprefs {
std::optional<undo_file_t> 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> 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

View File

@@ -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 <winsock2.h>
#include <windows.h>
#include <WinSock2.h>
#include <Windows.h>
// clang-format on
#include <windns.h>
#include <WinDNS.h>
#include <winerror.h>
// local includes

View File

@@ -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";

View File

@@ -12,6 +12,7 @@ extern "C" {
// standard includes
#include <array>
#include <cctype>
#include <format>
#include <set>
#include <unordered_map>
#include <utility>
@@ -897,7 +898,7 @@ namespace rtsp_stream {
session_option.next = &port_option;
// Moonlight merely requires 'server_port=<port>'
auto port_value = "server_port=" + std::to_string(port);
auto port_value = std::format("server_port={}", static_cast<int>(port));
port_option.option = const_cast<char *>("Transport");
port_option.content = port_value.data();

View File

@@ -34,8 +34,12 @@
#endif
// standard includes
#include <atomic>
#include <chrono>
#include <csignal>
#include <format>
#include <string>
#include <thread>
// lib includes
#include <boost/filesystem.hpp>
@@ -56,9 +60,14 @@ using namespace std::literals;
// system_tray namespace
namespace system_tray {
static std::atomic<bool> 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

View File

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

View File

@@ -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;
}

View File

@@ -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",

View File

@@ -69,6 +69,7 @@ onMounted(() => {
<option value="en_US">English, US</option>
<option value="es">Español (Spanish)</option>
<option value="fr">Français (French)</option>
<option value="hu">Magyar (Hungarian)</option>
<option value="it">Italiano (Italian)</option>
<option value="ja">日本語 (Japanese)</option>
<option value="ko">한국어 (Korean)</option>
@@ -79,6 +80,7 @@ onMounted(() => {
<option value="sv">svenska (Swedish)</option>
<option value="tr">Türkçe (Turkish)</option>
<option value="uk">Українська (Ukranian)</option>
<option value="vi">Tiếng Việt (Vietnamese)</option>
<option value="zh">简体中文 (Chinese Simplified)</option>
<option value="zh_TW">繁體中文 (Chinese Traditional)</option>
</select>
@@ -95,17 +97,17 @@ onMounted(() => {
<!-- Log Level -->
<div class="mb-3">
<label for="min_log_level" class="form-label">{{ $t('config.log_level') }}</label>
<label for="min_log_level" class="form-label">{{ $t('config.min_log_level') }}</label>
<select id="min_log_level" class="form-select" v-model="config.min_log_level">
<option value="0">{{ $t('config.log_level_0') }}</option>
<option value="1">{{ $t('config.log_level_1') }}</option>
<option value="2">{{ $t('config.log_level_2') }}</option>
<option value="3">{{ $t('config.log_level_3') }}</option>
<option value="4">{{ $t('config.log_level_4') }}</option>
<option value="5">{{ $t('config.log_level_5') }}</option>
<option value="6">{{ $t('config.log_level_6') }}</option>
<option value="0">{{ $t('config.min_log_level_0') }}</option>
<option value="1">{{ $t('config.min_log_level_1') }}</option>
<option value="2">{{ $t('config.min_log_level_2') }}</option>
<option value="3">{{ $t('config.min_log_level_3') }}</option>
<option value="4">{{ $t('config.min_log_level_4') }}</option>
<option value="5">{{ $t('config.min_log_level_5') }}</option>
<option value="6">{{ $t('config.min_log_level_6') }}</option>
</select>
<div class="form-text">{{ $t('config.log_level_desc') }}</div>
<div class="form-text">{{ $t('config.min_log_level_desc') }}</div>
</div>
<!-- Global Prep/State Commands -->
@@ -234,6 +236,14 @@ onMounted(() => {
v-model="config.notify_pre_releases"
default="false"
></Checkbox>
<!-- Enable system tray -->
<Checkbox class="mb-3"
id="system_tray"
locale-prefix="config"
v-model="config.system_tray"
default="true"
></Checkbox>
</div>
</template>

View File

@@ -45,28 +45,28 @@ const config = ref(props.config)
<!-- Additional options based on gamepad type -->
<template v-if="config.controller === 'enabled'">
<template v-if="config.gamepad === 'ds4' || (config.gamepad === 'auto' && platform === 'windows')">
<template v-if="config.gamepad === 'ds4' || config.gamepad === 'ds5' || (config.gamepad === 'auto' && platform !== 'macos')">
<div class="mb-3 accordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" type="button" data-bs-toggle="collapse"
data-bs-target="#panelsStayOpen-collapseOne">
{{ $t(config.gamepad === 'ds4' ? 'config.gamepad_ds4_manual' : 'config.gamepad_auto') }}
{{ $t(config.gamepad === 'ds4' ? 'config.gamepad_ds4_manual' : (config.gamepad === 'ds5' ? 'config.gamepad_ds5_manual' : 'config.gamepad_auto')) }}
</button>
</h2>
<div id="panelsStayOpen-collapseOne" class="accordion-collapse collapse show"
aria-labelledby="panelsStayOpen-headingOne">
<div class="accordion-body">
<!-- Auto options (Windows only) -->
<template v-if="config.gamepad === 'auto'">
<!-- DS4 motion -->
<!-- Automatic detection options (for Windows and Linux) -->
<template v-if="config.gamepad === 'auto' && (platform === 'windows' || platform === 'linux')">
<!-- Gamepad with motion-capability as DS4(Windows)/DS5(Linux) -->
<Checkbox class="mb-3"
id="motion_as_ds4"
locale-prefix="config"
v-model="config.motion_as_ds4"
default="true"
></Checkbox>
<!-- DS4 touchpad -->
<!-- Gamepad with touch-capability as DS4(Windows)/DS5(Linux) -->
<Checkbox class="mb-3"
id="touchpad_as_ds4"
locale-prefix="config"
@@ -74,9 +74,8 @@ const config = ref(props.config)
default="true"
></Checkbox>
</template>
<!-- DS4 options (all platforms) -->
<template v-if="config.gamepad === 'ds4'">
<!-- DS4 back button as touchpad click -->
<!-- DS4 option: DS4 back button as touchpad click (on Automatic: Windows only) -->
<template v-if="config.gamepad === 'ds4' || (config.gamepad === 'auto' && platform === 'windows')">
<Checkbox class="mb-3"
id="ds4_back_as_touchpad_click"
locale-prefix="config"
@@ -84,6 +83,15 @@ const config = ref(props.config)
default="true"
></Checkbox>
</template>
<!-- DS5 Option: Controller MAC randomization (on Automatic: Linux only) -->
<template v-if="config.gamepad === 'ds5' || (config.gamepad === 'auto' && platform === 'linux')">
<Checkbox class="mb-3"
id="ds5_inputtino_randomize_mac"
locale-prefix="config"
v-model="config.ds5_inputtino_randomize_mac"
default="true"
></Checkbox>
</template>
</div>
</div>
</div>

View File

@@ -68,7 +68,7 @@ function addRemappingEntry() {
<!-- Configuration option -->
<div class="mb-3">
<label for="dd_configuration_option" class="form-label">
{{ $t('config.dd_config_label') }}
{{ $t('config.dd_configuration_option') }}
</label>
<select id="dd_configuration_option" class="form-select" v-model="config.dd_configuration_option">
<option value="disabled">{{ $t('_common.disabled_def') }}</option>
@@ -97,7 +97,7 @@ function addRemappingEntry() {
<!-- Manual resolution -->
<div class="mt-2 ps-4" v-if="config.dd_resolution_option === 'manual'">
<div class="form-text">
{{ $t('config.dd_resolution_option_manual_desc') }}
{{ $t('config.dd_manual_resolution') }}
</div>
<input type="text" class="form-control" id="dd_manual_resolution" placeholder="2560x1440"
v-model="config.dd_manual_resolution" />
@@ -118,7 +118,7 @@ function addRemappingEntry() {
<!-- Manual refresh rate -->
<div class="mt-2 ps-4" v-if="config.dd_refresh_rate_option === 'manual'">
<div class="form-text">
{{ $t('config.dd_refresh_rate_option_manual_desc') }}
{{ $t('config.dd_manual_refresh_rate') }}
</div>
<input type="text" class="form-control" id="dd_manual_refresh_rate" placeholder="59.9558"
v-model="config.dd_manual_refresh_rate" />

View File

@@ -155,7 +155,7 @@
"dd_config_ensure_active": "Automaticky aktivovat displej",
"dd_config_ensure_only_display": "Deaktivovat další displeje a aktivovat pouze zadaný displej",
"dd_config_ensure_primary": "Automaticky aktivovat displej a učinit jej primárním displejem",
"dd_config_label": "Konfigurace zařízení",
"dd_configuration_option": "Konfigurace zařízení",
"dd_config_revert_delay": "Zpoždění vrácení konfigurace",
"dd_config_revert_delay_desc": "Dodatečná prodleva v milisekundách, která má být vyčkána před vrácením konfigurace, pokud byla aplikace zavřena nebo poslední relace ukončena. Hlavním účelem je zajistit plynulejší přechod při rychlém přepínání mezi aplikacemi.",
"dd_config_revert_on_disconnect": "Vrácení konfigurace při odpojení",
@@ -164,6 +164,8 @@
"dd_hdr_option": "HDR",
"dd_hdr_option_auto": "Zapnout/vypnout HDR režim podle požadavku klienta (výchozí)",
"dd_hdr_option_disabled": "Neměnit nastavení HDR",
"dd_manual_refresh_rate": "Manuální obnovovací frekvence",
"dd_manual_resolution": "Manuální rozlišení",
"dd_mode_remapping": "Přemapování režimu zobrazení",
"dd_mode_remapping_add": "Přidat položku pro nové mapování",
"dd_mode_remapping_desc_1": "Určete položky pro nové mapování pro změnu požadovaného rozlišení a/nebo obnovení frekvence na jiné hodnoty.",
@@ -182,12 +184,10 @@
"dd_refresh_rate_option_auto": "Použít hodnotu FPS zadanou klientem (výchozí)",
"dd_refresh_rate_option_disabled": "Neměnit obnovovací frekvenci",
"dd_refresh_rate_option_manual": "Použít ručně zadanou obnovovací frekvenci",
"dd_refresh_rate_option_manual_desc": "Zadejte obnovovací frekvenci, která se má použít",
"dd_resolution_option": "Rozlišení",
"dd_resolution_option_auto": "Použít rozlišení poskytované klientem (výchozí)",
"dd_resolution_option_disabled": "Neměnit rozlišení",
"dd_resolution_option_manual": "Použít ručně zadané rozlišení",
"dd_resolution_option_manual_desc": "Zadejte rozlišení, které má být použito",
"dd_resolution_option_ogs_desc": "Aby tato funkce fungovala, musí být v klientovi Moonlight povolena možnost \"Optimalizovat nastavení hry\".",
"dd_wa_hdr_toggle_delay_desc_1": "Při použití virtuálního zobrazovacího zařízení (VDD) pro streamování může dojít k nesprávnému zobrazení barev HDR. Sunshine se může pokusit tento problém zmírnit vypnutím a opětovným zapnutím HDR.",
"dd_wa_hdr_toggle_delay_desc_2": "Pokud je hodnota nastavena na 0, je obcházení zakázáno (výchozí nastavení). Pokud je hodnota v rozmezí 0 až 3000 milisekund, Sunshine vypne HDR, počká zadanou dobu a poté HDR opět zapne. Doporučená doba zpoždění je ve většině případů přibližně 500 milisekund.",
@@ -195,6 +195,8 @@
"dd_wa_hdr_toggle_delay": "Řešení pro HDR s vysokým kontrastem",
"ds4_back_as_touchpad_click": "Namapovat Zpět/Vybrat na klepnutí touchpadu",
"ds4_back_as_touchpad_click_desc": "Při vynucení emulace DS4 namapujte funkci Zpět/Vybrat na klepnutí touchpadu",
"ds5_inputtino_randomize_mac": "Náhodné nastavení virtuálního ovladače MAC",
"ds5_inputtino_randomize_mac_desc": "Při registraci řadiče použijte náhodný MAC místo MAC založeného na interním indexu řadiče, aby nedošlo k promíchání konfiguračních nastavení různých řadičů při jejich výměně na straně klienta.",
"encoder": "Vynutit specifický enkodér",
"encoder_desc": "Vynutit konkrétní kodér, jinak Sunshine vybere nejlepší dostupnou možnost. Poznámka: Pokud v systému Windows zadáte hardwarový kodér, musí odpovídat grafickému procesoru, ke kterému je displej připojen.",
"encoder_software": "Software",
@@ -213,6 +215,7 @@
"gamepad_ds4": "DS4 (PS4)",
"gamepad_ds4_manual": "Možnosti výběru DS4",
"gamepad_ds5": "DS5 (PS5)",
"gamepad_ds5_manual": "Možnosti výběru DS5",
"gamepad_switch": "Nintendo Pro (Switch)",
"gamepad_manual": "Možnosti manuálního ovládání DS4",
"gamepad_x360": "X360 (Xbox 360)",
@@ -235,6 +238,7 @@
"key_repeat_frequency_desc": "Jak často se klávesy opakují každou sekundu. Tato nastavitelná možnost podporuje desetinná čísla.",
"key_rightalt_to_key_win": "Mapování pravé klávesy Alt na klávesu Windows",
"key_rightalt_to_key_win_desc": "Je možné, že klávesa Windows nelze odeslat přímo z aplikace Moonlight. V takových případech může být užitečné přimět Sunshine, aby si myslel, že pravý Alt je klávesa Windows",
"keybindings": "Klávesové zkratky",
"keyboard": "Povolit vstupu z klávesnice",
"keyboard_desc": "Umožňuje hostům ovládat hostitelský systém pomocí klávesnice",
"lan_encryption_mode": "Režim šifrování LAN",
@@ -243,72 +247,73 @@
"lan_encryption_mode_desc": "Určuje, kdy bude šifrování použito při streamování přes místní síť. Šifrování může snížit výkon streamování, zejména u méně výkonných hostitelů a klientů.",
"locale": "Místní prostředí",
"locale_desc": "Místní jazyk používaný pro uživatelské rozhraní Sunshine.",
"log_level": "Úroveň logu",
"log_level_0": "Verbose",
"log_level_1": "Debug",
"log_level_2": "Info",
"log_level_3": "Varování",
"log_level_4": "Chyba",
"log_level_5": "Fatální",
"log_level_6": "Nic",
"log_level_desc": "Minimální úroveň logu vypisovaná do standardního výstupu",
"log_path": "Cesta k logu",
"log_path_desc": "Soubor, ve kterém jsou uloženy aktuální logy Sunshine.",
"max_bitrate": "Maximální bitrate",
"max_bitrate_desc": "Maximální bitrate (v Kb/s), kterým bude Sunshine kódovat datový tok. Pokud je nastaven na 0, bude vždy použit bitrate požadovaný aplikací Moonlight.",
"minimum_fps_target": "Minimální cílová hodnota FPS",
"minimum_fps_target_desc": "Nejnižší efektivní FPS, kterého může stream dosáhnout. Hodnota 0 je považována za zhruba polovinu FPS streamu. Pokud streamujete obsah s 24 nebo 30 snímky za sekundu, doporučuje se nastavení 20.",
"min_log_level": "Úroveň logu",
"min_log_level_0": "Verbose",
"min_log_level_1": "Ladit",
"min_log_level_2": "Info",
"min_log_level_3": "Varování",
"min_log_level_4": "Chyba",
"min_log_level_5": "Kritická chyba",
"min_log_level_6": "Nic",
"min_log_level_desc": "Minimální úroveň protokolu vypisovaná do standardního výstupu",
"min_threads": "Minimální počet vláken CPU",
"min_threads_desc": "Zvýšení této hodnoty mírně snižuje efektivitu kódování, ale tento kompromis se obvykle vyplatí, protože získáte více jader procesoru pro kódování. Ideální hodnota je nejnižší hodnota, která dokáže spolehlivě enkódovat při požadovaném nastavení streamování na vašem hardwaru.",
"misc": "Různé možnosti",
"motion_as_ds4": "Emulovat gamepad DS4, pokud klientský gamepad hlásí přítomnost pohybových senzorů",
"motion_as_ds4_desc": "Je-li zakázáno, nebudou při výběru typu gamepadu brány v úvahu snímače pohybu.",
"motion_as_ds4_desc": "Pokud je vypnuto, nebudou při výběru typu gamepadu brány v úvahu snímače pohybu.",
"mouse": "Povolit vstup myši",
"mouse_desc": "Umožňuje hostům ovládat systém pomocí myši",
"native_pen_touch": "Nativní Peněžení/Dotkněte se podpory",
"native_pen_touch_desc": "Pokud je povoleno, Sunshine projde nativní per/dotyk od klientů Moonlight událostí. To může být užitečné pro vypnutí starších aplikací bez nativní podpory pen/dotyku.",
"native_pen_touch": "Nativní podpora pera/dotyku",
"native_pen_touch_desc": "Je-li tato funkce povolena, bude Sunshine předávat nativní události pera/dotyku z klientů Moonlight. To může být užitečné vypnout pro starší aplikace bez nativní podpory pera/dotyku.",
"notify_pre_releases": "Oznámení před vydáním",
"notify_pre_releases_desc": "Zda mají být informovány o nových předběžných verzích Sunshine",
"notify_pre_releases_desc": "Zda chcete být informováni o nových předběžných verzích Sunshine",
"nvenc_h264_cavlc": "Preferovat CAVLC před CABAC v H.264",
"nvenc_h264_cavlc_desc": "Jednoduchá forma entropizace. CAVLC potřebuje asi o 10 % více bitratu ve stejné kvalitě. Je relevantní pouze pro opravdu staré dekódování zařízení.",
"nvenc_h264_cavlc_desc": "Jednodušší forma entropického kódování. CAVLC potřebuje pro stejnou kvalitu přibližně o 10 % vyšší datový tok. Má význam pouze pro opravdu stará dekódovací zařízení.",
"nvenc_latency_over_power": "Preferovat nižší latenci kódování před úsporami energie",
"nvenc_latency_over_power_desc": "Sluneční požadavky vyžadují maximální rychlost GPU hodin při streamování, aby se snížila latence kódování. Vypnutí se nedoporučuje, protože to může vést k výraznému zvýšení latence kódování.",
"nvenc_opengl_vulkan_on_dxgi": "Současný OpenGL/Vulkan nad DXGI",
"nvenc_opengl_vulkan_on_dxgi_desc": "Sluneční neschopný zachytit programy OpenGL a Vulkan při plném snímku, pokud nejsou přítomny na vrcholu DXGI. Toto je systémové nastavení, které je vráceno při ukončení slunečního programu.",
"nvenc_latency_over_power_desc": "Sunshine požaduje maximální taktovací frekvenci GPU při streamování, aby se snížila latence kódování. Jeho vypnutí se nedoporučuje, protože může vést k výraznému zvýšení latence enkódování.",
"nvenc_opengl_vulkan_on_dxgi": "Prezentovat OpenGL/Vulkan na vrchu DXGI",
"nvenc_opengl_vulkan_on_dxgi_desc": "Sunshine nedokáže zachytit programy OpenGL a Vulkan na celou obrazovku s plnou snímkovou frekvencí, pokud nejsou prezentovány nad DXGI. Jedná se o celosystémové nastavení, které se při ukončení programu Sunshine vrátí zpět.",
"nvenc_preset": "Předvolba výkonu",
"nvenc_preset_1": "(nejrychlejší, výchozí)",
"nvenc_preset_7": "(nejmenší)",
"nvenc_preset_desc": "Vyšší čísla zlepšují kompresi (kvalita při dané bitové rychlosti) za cenu zvýšeného zpoždění kódování. Změnu doporučujeme pouze v případě, že je omezena sítí nebo dekodérem, jinak lze dosáhnout podobného efektu zvýšením bitrate.",
"nvenc_realtime_hags": "Použít prioritu v reálném čase v hardwarově akcelerovaném plánování",
"nvenc_realtime_hags_desc": "V současné době mohou ovladače NVIDIA zmrazit v enkodéru, pokud je HAGS povoleno, je použita priorita v reálném čase a využití VRAM je blízko maximu. Zakázání této možnosti snižuje prioritu na vysokou úroveň, vyhýbá se zmrazení za cenu snížení výkonu zachytávání při vysoké zátěži.",
"nvenc_spatial_aq": "Spatial AQ",
"nvenc_spatial_aq_desc": "Přiřadit vyšší hodnoty QP plochým oblastem videa. Doporučeno povolit při streamování při nižších bitech.",
"nvenc_twopass": "Režim obousměrného průjezdu",
"nvenc_twopass_desc": "Přidá předběžné kódování. To umožňuje detekovat více vektorů pohybu, lépe distribuovat bitrate napříč rámcem a přesněji dodržovat limity bitratu. Vypnutí se nedoporučuje, protože to může způsobit občasné překročení bitratu a následnou ztrátu paketů.",
"nvenc_preset_7": "(nejpomalejší)",
"nvenc_preset_desc": "Vyšší čísla zlepšují kompresi (kvalitu při daném datovém toku) za cenu zvýšené latence kódování. Doporučuje se měnit pouze v případě omezení ze strany sítě nebo dekodéru, jinak lze podobného efektu dosáhnout zvýšením datového toku.",
"nvenc_realtime_hags": "Použít prioritu reálného času v hardwarově akcelerovaném plánování gpu",
"nvenc_realtime_hags_desc": "V současné době mohou ovladače NVIDIA v enkodéru zamrznout, pokud je povolena funkce HAGS, je použita priorita reálného času a využití VRAM se blíží maximu. Zakázáním této možnosti se priorita sníží na vysokou, čímž se zamrznutí obejde za cenu snížení výkonu snímání při velm zatížení GPU.",
"nvenc_spatial_aq": "Prostorové AQ",
"nvenc_spatial_aq_desc": "Přiřadit vyšší hodnoty QP plochým oblastem videa. Doporučuje se povolit při streamování s nižšími datovými toky.",
"nvenc_twopass": "Dvouprůchodový režim",
"nvenc_twopass_desc": "Přidá předběžný průchod kódování. To umožňuje detekovat více vektorů pohybu, lépe rozložit datový tok napříč snímkem a přísněji dodržovat limity datového toku. Vypnutí se nedoporučuje, protože to může vést k občasnému překročení datového toku a následné ztrátě paketů.",
"nvenc_twopass_disabled": "Zakázáno (nejrychlejší, nedoporučeno)",
"nvenc_twopass_full_res": "Úplné rozlišení (pomalejší)",
"nvenc_twopass_quarter_res": "Čtvrtletní rozlišení (rychlejší, výchozí)",
"nvenc_vbv_increase": "Zvýšení procenta jednoho snímku VBV/HRD",
"nvenc_vbv_increase_desc": "Ve výchozím nastavení používá sluneční záření jednosnímkový VBV/HRD, což znamená, že se neočekává, že by žádná velikost zakódovaného video snímku překročila požadovanou bitrate dělenou požadovanou frekvencí snímku. zmírnění tohoto omezení může být prospěšné a fungovat jako variabilní bitrate s nízkou latencí, ale může také vést ke ztrátě paketů, pokud síť nemá mezipaměnnou mezipaměť pro zvládání výkyvů bitratů. Maximální přípustná hodnota je 400, což odpovídá 5x zvýšenému limitu horní velikosti zakódovaného video snímku.",
"origin_web_ui_allowed": "Origin Web UI povoleno",
"origin_web_ui_allowed_desc": "Původ adresy vzdáleného koncového bodu, které není odepřen přístup k webovému uživatelskému rozhraní",
"origin_web_ui_allowed_lan": "Přístup k webovému uživatelskému rozhraní mohou mít pouze uživatelé LAN",
"origin_web_ui_allowed_pc": "Pouze localhost může přistupovat k webovému rozhraní",
"origin_web_ui_allowed_wan": "Kdokoli může přistupovat k webovému rozhraní",
"output_name_desc_unix": "Při spuštění pomocí slunečního svitu byste měli vidět seznam rozpoznaných displejů. Poznámka: Je třeba použít id hodnotu uvnitř závorky. Níže je příklad; skutečný výstup lze nalézt v záložce řešení problémů.",
"output_name_desc_windows": "Ručně zadejte id zobrazovacího zařízení pro zachycení. Pokud je odpojen, primární obrazovka je zachycena. Poznámka: Pokud jste zadali GPU výše, musí být tento displej připojen k grafické kartě. Při spouštění přes Sunshine byste měli vidět seznam detekovaných displejů. Níže je příklad; skutečný výstup lze nalézt v záložce Řešení problémů.",
"output_name_unix": "Zobrazit číslo",
"output_name_windows": "Zobrazit ID zařízení",
"ping_timeout": "Časový limit Ping",
"ping_timeout_desc": "Jak dlouho čekat v milisekundách na data z Měsíčního světla před vypnutím proudu",
"nvenc_twopass_quarter_res": "Čtvrtinové rozlišení (rychlejší, výchozí)",
"nvenc_vbv_increase": "Procentuální nárůst VBV/HRD v jednom snímku",
"nvenc_vbv_increase_desc": "Ve výchozím nastavení používá Sunshine jednosnímkové VBV/HRD, což znamená, že velikost kódovaného videosnímku nesmí překročit požadovaný datový tok dělený požadovanou snímkovou frekvencí. Uvolnění tohoto omezení může být přínosné a fungovat jako proměnný datový tok s nízkou latencí, ale může také vést ke ztrátě paketů, pokud síť nemá vyrovnávací paměť, která by zvládla skokové nárůsty datového toku. Maximální přijatelná hodnota je 400, což odpovídá 5x zvýšenému hornímu limitu velikosti kódovaného videosnímku.",
"origin_web_ui_allowed": "Povolené webové rozhraní Origin",
"origin_web_ui_allowed_desc": "Původ adresy vzdáleného koncového bodu, kterému není odepřen přístup k webovému uživatelskému rozhraní",
"origin_web_ui_allowed_lan": "K webovému uživatelskému rozhraní mohou přistupovat pouze osoby v síti LAN",
"origin_web_ui_allowed_pc": "K webovému uživatelskému rozhraní může přistupovat pouze localhost",
"origin_web_ui_allowed_wan": "K webovému uživatelskému rozhraní může přistupovat kdokoli",
"output_name": "Zobrazit ID",
"output_name_desc_unix": "Při spuštění Sunshine by se měl zobrazit seznam zjištěných displejů. Poznámka: Musíte použít hodnotu id uvnitř závorky. Níže je uveden příklad; skutečný výstup naleznete na kartě Odstraňování problémů.",
"output_name_desc_windows": "Ruční zadání id zobrazovacího zařízení, které se má použít pro zachycení. Pokud není nastaveno, zachytí se primární displej. Poznámka: Pokud jste výše zadali grafický procesor, musí být tento displej připojen k tomuto grafickému procesoru. Během spuštění Sunshine by se měl zobrazit seznam zjištěných displejů. Níže je uveden příklad; skutečný výstup naleznete na kartě Odstraňování problémů.",
"ping_timeout": "Časový limit příchozího pingu",
"ping_timeout_desc": "Jak dlouho v milisekundách čekat na data z moonlight před vypnutím datového toku",
"pkey": "Soukromý klíč",
"pkey_desc": "Soukromý klíč používaný pro párování webových UI a Moonlight klientů. Pro nejlepší kompatibilitu by měl být soukromý klíč RSA-2048.",
"port": "Přístav",
"port_alert_1": "Sluneční svaz nemůže používat přístavy pod 1024!",
"port_alert_2": "Přístavy nad 65535 nejsou k dispozici!",
"port_desc": "Nastavte rodinu přístavů používaných sunshine",
"port_http_port_note": "Použijte tento port pro připojení k měsíčnímu světlu.",
"pkey_desc": "Soukromý klíč používaný pro párování webového uživatelského rozhraní a klienta Moonlight. Pro zajištění nejlepší kompatibility by to měl být soukromý klíč RSA-2048.",
"port": "Port",
"port_alert_1": "Sunshine nemůže používat porty nižší než 1024!",
"port_alert_2": "Porty nad 65535 nejsou k dispozici!",
"port_desc": "Nastave rodiny portů, které používá Sunshine",
"port_http_port_note": "Tento port slouží k připojení k Moonlight.",
"port_note": "Poznámka",
"port_port": "Přístav",
"port_protocol": "Protocol",
"port_port": "Port",
"port_protocol": "Protokol",
"port_tcp": "TCP",
"port_udp": "UDP",
"port_warning": "Vystavení webového uživatelského rozhraní na internet je bezpečnostní riziko! Pokračujte na vlastní nebezpečí!",
@@ -350,6 +355,8 @@
"sw_tune_grain": "zrno - zachovává strukturu zrna ve starém zrnitém filmovém materiálu",
"sw_tune_stillimage": "stillimage -- dobré pro prezentační obsah",
"sw_tune_zerolatency": "zerolatency -- vhodné pro rychlé kódování a streamování s nízkou latencí (výchozí)",
"system_tray": "Povolit systémovou lištu",
"system_tray_desc": "Zobrazit ikonu v systémové liště a zobrazit oznámení na ploše",
"touchpad_as_ds4": "Emulovat gamepad DS4, pokud klientský gamepad hlásí přítomnost touchpadu",
"touchpad_as_ds4_desc": "Pokud je vypnuto, nebude při výběru typu gamepadu zohledněna přítomnost touchpadu.",
"upnp": "UPnP",
@@ -360,8 +367,8 @@
"virtual_sink_desc": "Ručně zadejte virtuální zvukové zařízení, které chcete použít. Pokud není nastaveno, je zařízení vybráno automaticky. Důrazně doporučujeme ponechat toto pole prázdné, chcete-li použít automatický výběr zařízení!",
"virtual_sink_placeholder": "Streamovací reproduktory služby Steam",
"vt_coder": "VideoToolbox Coder",
"vt_realtime": "Video Toolbox v reálném čase enkódování",
"vt_software": "Softwarové kódování video nástrojů",
"vt_realtime": "VideoToolbox Kódování v reálném čase",
"vt_software": "Kódování softwaru VideoToolbox",
"vt_software_allowed": "Povoleno",
"vt_software_forced": "Vynucené",
"wan_encryption_mode": "Režim šifrování WAN",
@@ -370,16 +377,16 @@
"wan_encryption_mode_desc": "Určuje, kdy bude šifrování použito při streamování přes internet. Šifrování může snížit streamovací výkon, zejména u méně výkonných hostitelů a klientů."
},
"index": {
"description": "Sluneční stream je hostitelem pro Měsíční světlo.",
"description": "Sunshine je samostatný hostitel herního streamu pro Moonlight.",
"download": "Stáhnout",
"installed_version_not_stable": "Používáte předverzi Sunshine. Můžete zaznamenat chyby nebo jiné problémy. Prosím nahlaste všechny problémy, se kterými se setkáte. Děkujeme, že jste pomohli udělat sunshine lepší software!",
"installed_version_not_stable": "Používáte předběžnou verzi Sunshine. Mohou se vyskytnout chyby nebo jiné problémy. Nahlaste prosím všechny problémy, se kterými se setkáte. Děkujeme, že pomáháte vytvořit lepší software Sunshine!",
"loading_latest": "Načítání nejnovější verze...",
"new_pre_release": "Je k dispozici nová verze před-vydání!",
"new_pre_release": "K dispozici je nová verze před vydáním!",
"new_stable": "K dispozici je nová stabilní verze!",
"startup_errors": "<b>Pozor!</b> Sunshine detekoval tyto chyby během spuštění. <b>STRONGLY RECOMMEND</b> je opraví před vysíláním.",
"version_dirty": "Děkujeme, že jste pomohli udělat sunshine lepší software!",
"startup_errors": "<b>Pozor!</b> Sunshine zjistil tyto chyby při spuštění. <b>DŮRAZNĚ DOPORUČUJEME</b> je před streamováním opravit.",
"version_dirty": "Děkujeme, že pomáháte vylepšovat software Sunshine!",
"version_latest": "Používáte nejnovější verzi Sunshine",
"welcome": "Ahoj, sunshine!"
"welcome": "Ahoj, Sunshine!"
},
"navbar": {
"applications": "Aplikace",
@@ -399,29 +406,29 @@
"new_creds": "Nové přihlašovací údaje",
"new_username_desc": "Pokud není zadáno, uživatelské jméno se nezmění",
"password_change": "Změna hesla",
"success_msg": "Heslo bylo úspěšně změněno! Tato stránka se brzy obnoví, váš prohlížeč vás požádá o nové přihlašovací údaje."
"success_msg": "Heslo bylo úspěšně změněno! Tato stránka se brzy znovu načte a prohlížeč vás požádá o zadání nových přihlašovacích údajů."
},
"pin": {
"device_name": "Název zařízení",
"pair_failure": "Spárování se nezdařilo: Zkontrolujte, zda je PIN správně zadán",
"pair_success": "Úspěch! Prosím, zkontrolujte Moonlight pro pokračování",
"pin_pairing": "PIN Pairing",
"send": "Poslat",
"warning_msg": "Ujistěte se, že máte přístup k klientovi, se kterým spárujete. Tento software může poskytnout vašemu počítači úplnou kontrolu, takže buďte opatrní!"
"pin_pairing": "Párování PIN",
"send": "Odeslat",
"warning_msg": "Ujistěte se, že máte přístup ke klientovi, se kterým se párujete. Tento software může dát počítači úplnou kontrolu, proto buďte opatrní!"
},
"resource_card": {
"github_discussions": "GitHub Discussions",
"github_discussions": "Diskuse na GitHubu",
"legal": "Právní předpisy",
"legal_desc": "Pokračováním v používání tohoto softwaru souhlasíte s podmínkami v následujících dokumentech.",
"legal_desc": "Dalším používáním tohoto softwaru souhlasíte s podmínkami uvedenými v následujících dokumentech.",
"license": "Licence",
"lizardbyte_website": "Webové stránky LizardByte",
"resources": "Zdroje",
"resources_desc": "Zdroje pro sunnity!",
"resources_desc": "Zdroje pro Sunshine!",
"third_party_notice": "Oznámení třetí strany"
},
"troubleshooting": {
"dd_reset": "Obnovit nastavení trvalého zobrazení zařízení",
"dd_reset_desc": "Pokud se sluneční svaz zasekne o obnovení změněného nastavení zobrazovacího zařízení, můžete obnovit nastavení a pokračovat v obnově stavu displeje ručně.",
"dd_reset": "Obnovení nastavení zařízení s trvalým displejem",
"dd_reset_desc": "Pokud se Sunshine zasekne při pokusu o obnovení změněných nastavení zobrazovacího zařízení, můžete obnovit nastavení a pokračovat v obnovení stavu displeje ručně.",
"dd_reset_error": "Chyba při obnovení perzistence!",
"dd_reset_success": "Úspěch resetování perzistence!",
"force_close": "Vynutit zavření",

View File

@@ -0,0 +1,462 @@
{
"_common": {
"apply": "Alkalmazás",
"auto": "Automatikus",
"autodetect": "Automatikus felismerés (ajánlott)",
"beta": "(béta)",
"cancel": "Mégse",
"disabled": "Kikapcsolva",
"disabled_def": "Kikapcsolva (alapértelmezett)",
"disabled_def_cbox": "Alapértelmezett: nincs bejelölve",
"dismiss": "Eltüntetés",
"do_cmd": "Do parancs",
"elevated": "Megemelt",
"enabled": "Bekapcsolva",
"enabled_def": "Bekapcsolva (alapértelmezett)",
"enabled_def_cbox": "Alapértelmezett: bekapcsolva",
"error": "Hiba!",
"note": "Megjegyzés:",
"password": "Jelszó",
"run_as": "Futtatás rendszergazdaként",
"save": "Mentés",
"see_more": "Lásd még",
"success": "Siker!",
"undo_cmd": "Parancs visszavonása",
"username": "Felhasználónév",
"warning": "Figyelem!"
},
"apps": {
"actions": "Tevékenységek",
"add_cmds": "Parancsok hozzáadása",
"add_new": "Új hozzáadása",
"app_name": "Alkalmazás neve",
"app_name_desc": "Alkalmazás neve, a Moonlight-on feltüntetett módon",
"applications_desc": "Az alkalmazások csak a kliensgép újraindításakor frissülnek",
"applications_title": "Alkalmazások",
"auto_detach": "Folytassa a streaminget, ha az alkalmazás gyorsan kilép",
"auto_detach_desc": "Ez megpróbálja automatikusan felismerni az olyan indító típusú alkalmazásokat, amelyek gyorsan bezáródnak egy másik program vagy saját példányuk elindítása után. Ha egy indító típusú alkalmazást észlel, azt leválasztott alkalmazásként kezeli.",
"cmd": "Parancs",
"cmd_desc": "A fő alkalmazás elindítása. Ha üres, akkor nem indul alkalmazás.",
"cmd_note": "Ha a parancs futtatható fájljának elérési útvonala szóközöket tartalmaz, akkor idézőjelek közé kell zárnia.",
"cmd_prep_desc": "Az alkalmazás előtt/után futtatandó parancsok listája. Ha bármelyik előkészítő parancs sikertelen, az alkalmazás elindítása megszakad.",
"cmd_prep_name": "Parancsnoki előkészületek",
"covers_found": "Fedelek találtak",
"delete": "Törlés",
"detached_cmds": "Leválasztott parancsok",
"detached_cmds_add": "Önálló parancs hozzáadása",
"detached_cmds_desc": "A háttérben futtatandó parancsok listája.",
"detached_cmds_note": "Ha a parancs futtatható fájljának elérési útvonala szóközöket tartalmaz, akkor idézőjelek közé kell zárnia.",
"edit": "Szerkesztés",
"env_app_id": "Alkalmazás azonosítója",
"env_app_name": "Alkalmazás neve",
"env_client_audio_config": "A kliensgép által kért hangkonfiguráció (2.0/5.1/7.1)",
"env_client_enable_sops": "Az ügyfél kérte a játék optimalizálásának lehetőségét az optimális streaminghez (igaz/hamis).",
"env_client_fps": "A kliensgép által kért FPS (egész szám)",
"env_client_gcmap": "A kért gamepad maszk, bset/bitfield formátumban (int)",
"env_client_hdr": "A kliensgép engedélyezi a HDR-t (igaz/hamis)",
"env_client_height": "A kliensgép által kért magasság (egész szám)",
"env_client_host_audio": "A kliensgép a gazdagép hangját kérte (igaz/hamis)",
"env_client_width": "A kliensgép által kért szélesség (egész szám)",
"env_displayplacer_example": "Példa - displayplacer a Resolution Automation számára:",
"env_qres_example": "Példa - QRes a felbontás automatizálásához:",
"env_qres_path": "qres útvonal",
"env_var_name": "Var neve",
"env_vars_about": "A környezeti változókról",
"env_vars_desc": "Alapértelmezés szerint minden parancs megkapja ezeket a környezeti változókat:",
"env_xrandr_example": "Példa - Xrandr a felbontás automatizálásához:",
"exit_timeout": "Kilépési időkorlát",
"exit_timeout_desc": "A kilépésre vonatkozó kérés esetén az alkalmazás összes folyamatának méltóságteljes kilépésére várandó másodpercek száma. Ha nincs megadva, az alapértelmezett érték 5 másodpercig várakozik. Ha 0-ra van állítva, az alkalmazás azonnal befejeződik.",
"find_cover": "Fedezet keresése",
"global_prep_desc": "A globális előkészítő parancsok végrehajtásának engedélyezése/letiltása az alkalmazás számára.",
"global_prep_name": "Globális előkészítő parancsok",
"image": "Kép",
"image_desc": "Az ügyfélnek küldött alkalmazás ikon/kép/kép elérési útvonala. A képnek PNG fájlnak kell lennie. Ha nincs megadva, a Sunshine alapértelmezett dobozképet küld.",
"loading": "Betöltés...",
"name": "Név",
"output_desc": "Az a fájl, ahol a parancs kimenete tárolódik, ha nincs megadva, a kimenetet figyelmen kívül hagyjuk.",
"output_name": "Kimenet",
"run_as_desc": "Erre olyan alkalmazásoknál lehet szükség, amelyek megfelelő futtatásához rendszergazdai engedélyek szükségesek.",
"wait_all": "Folytassa a streaminget, amíg az összes alkalmazásfolyamat ki nem lép",
"wait_all_desc": "A streaming addig folytatódik, amíg az alkalmazás által indított összes folyamat le nem zárul. Ha nincs bejelölve, a streaming leáll, amikor az alkalmazás kezdeti folyamata kilép, még akkor is, ha más alkalmazásfolyamatok még futnak.",
"working_dir": "Munkakönyvtár",
"working_dir_desc": "A folyamatnak átadandó munkakönyvtár. Egyes alkalmazások például a munkakönyvtárat használják a konfigurációs fájlok keresésére. Ha nincs megadva, a Sunshine alapértelmezés szerint a parancs szülő könyvtárát fogja használni."
},
"config": {
"adapter_name": "Adapter neve",
"adapter_name_desc_linux_1": "Kézzel adja meg a rögzítéshez használt GPU-t.",
"adapter_name_desc_linux_2": "az összes VAAPI-képes eszköz megtalálása",
"adapter_name_desc_linux_3": "A ``renderD129``-t helyettesítsük a fenti eszközzel, hogy felsoroljuk az eszköz nevét és képességeit. Ahhoz, hogy a Sunshine támogassa, legalább a következőkkel kell rendelkeznie:",
"adapter_name_desc_windows": "Kézzel adja meg a rögzítéshez használt GPU-t. Ha nincs megadva, a GPU automatikusan kiválasztásra kerül. A GPU automatikus kiválasztásához javasoljuk, hogy hagyja üresen ezt a mezőt! Megjegyzés: Ehhez a GPU-hoz csatlakoztatott és bekapcsolt kijelzőnek kell lennie. A megfelelő értékeket a következő paranccsal találhatja meg:",
"adapter_name_placeholder_windows": "Radeon RX 580 sorozat",
"add": "Hozzáadás",
"address_family": "Címcsalád",
"address_family_both": "IPv4+IPv6",
"address_family_desc": "A Sunshine által használt címcsalád beállítása",
"address_family_ipv4": "Csak IPv4",
"always_send_scancodes": "Mindig küldjön kódokat",
"always_send_scancodes_desc": "A scan-kódok küldése javítja a kompatibilitást a játékokkal és alkalmazásokkal, de bizonyos, nem amerikai angol billentyűzetkiosztást használó ügyfeleknél helytelen billentyűzetbevitelt eredményezhet. Engedélyezze, ha a billentyűzetbevitel egyáltalán nem működik bizonyos alkalmazásokban. Tiltja le, ha az ügyfél billentyűi rossz bevitelt generálnak a hoszton.",
"amd_coder": "AMF kódoló (H264)",
"amd_coder_desc": "Lehetővé teszi az entrópia kódolás kiválasztását a minőség vagy a kódolási sebesség előtérbe helyezéséhez. Csak H.264.",
"amd_enforce_hrd": "AMF hipotetikus referencia dekóder (HRD) végrehajtása",
"amd_enforce_hrd_desc": "Növeli az árfolyam-szabályozásra vonatkozó korlátozásokat, hogy megfeleljen a HRD-modell követelményeinek. Ez nagymértékben csökkenti a bitráta-túlcsordulást, de bizonyos kártyákon kódolási leleteket vagy csökkent minőséget okozhat.",
"amd_preanalysis": "AMF előelemzés",
"amd_preanalysis_desc": "Ez lehetővé teszi a sebesség-szabályozás előzetes elemzését, ami növelheti a minőséget a megnövekedett kódolási késleltetés rovására.",
"amd_quality": "AMF minőség",
"amd_quality_balanced": "balanced -- kiegyensúlyozott (alapértelmezett)",
"amd_quality_desc": "Ez szabályozza a kódolási sebesség és a minőség közötti egyensúlyt.",
"amd_quality_group": "AMF minőségi beállítások",
"amd_quality_quality": "minőség -- a minőség előnyben részesítése",
"amd_quality_speed": "sebesség -- a sebesség előnyben részesítése",
"amd_rc": "AMF sebesség vezérlés",
"amd_rc_cbr": "cbr -- állandó bitráta (ajánlott, ha a HRD engedélyezve van)",
"amd_rc_cqp": "cqp -- állandó qp üzemmód",
"amd_rc_desc": "Ez vezérli a sebességszabályozási módszert, hogy biztosítsa, hogy nem lépjük túl az ügyfél bitrátájának célértékét. A 'cqp' nem alkalmas bitráta-célzásra, és a 'vbr_latency'-n kívül más opciók is a HRD Enforcementtől függenek, hogy segítsenek a bitráta-túllépések korlátozásában.",
"amd_rc_group": "AMF Rate Control beállítások",
"amd_rc_vbr_latency": "vbr_latency -- késleltetéskorlátozott változó bitráta (ajánlott, ha a HRD le van tiltva; alapértelmezett)",
"amd_rc_vbr_peak": "vbr_peak -- csúcsértékkel korlátozott változó bitráta",
"amd_usage": "AMF használat",
"amd_usage_desc": "Ez állítja be az alap kódolási profilt. Az alább bemutatott összes opció felülírja a használati profil egy részhalmazát, de vannak további rejtett beállítások, amelyek máshol nem konfigurálhatók.",
"amd_usage_lowlatency": "lowlatency - alacsony késleltetés (leggyorsabb)",
"amd_usage_lowlatency_high_quality": "lowlatency_high_quality - alacsony késleltetés, magas minőség (gyors)",
"amd_usage_transcoding": "transzkódolás -- transzkódolás (leglassabb)",
"amd_usage_ultralowlatency": "ultralowlatency - ultra alacsony késleltetés (leggyorsabb; alapértelmezett)",
"amd_usage_webcam": "webcam -- webkamera (lassú)",
"amd_vbaq": "AMF Variancia alapú adaptív kvantálás (VBAQ)",
"amd_vbaq_desc": "Az emberi vizuális rendszer jellemzően kevésbé érzékeny az erősen texturált területeken megjelenő műalkotásokra. A VBAQ módban a pixelvariancia a térbeli textúrák összetettségének jelzésére szolgál, lehetővé téve a kódoló számára, hogy több bitet rendeljen a simább területekhez. E funkció engedélyezése bizonyos tartalmak esetében javítja a szubjektív vizuális minőséget.",
"apply_note": "Kattintson az 'Alkalmaz' gombra a Sunshine újraindításához és a módosítások alkalmazásához. Ezzel minden futó munkamenet megszűnik.",
"audio_sink": "Audio Sink",
"audio_sink_desc_linux": "Az Audio Loopbackhez használt hangkimenet neve. Ha nem adja meg ezt a változót, a pulseaudio az alapértelmezett monitor eszközt fogja kiválasztani. A hangelnyelő nevét bármelyik parancs segítségével megtudhatja:",
"audio_sink_desc_macos": "Az Audio Loopbackhez használt hangkimenet neve. A Sunshine a rendszer korlátai miatt csak a macOS rendszerben érheti el a mikrofonokat. Rendszerhang streameléséhez Soundflower vagy BlackHole használatával.",
"audio_sink_desc_windows": "Kézzel adja meg a rögzítendő hangeszközöket. Ha nincs megadva, az eszköz automatikusan kiválasztásra kerül. Az automatikus eszközkiválasztás használatához erősen ajánlott üresen hagyni ezt a mezőt! Ha több azonos nevű audioeszközzel rendelkezik, az eszközazonosítót a következő paranccsal kaphatja meg:",
"audio_sink_placeholder_macos": "BlackHole 2ch",
"audio_sink_placeholder_windows": "Hangszórók (nagy felbontású hangeszköz)",
"av1_mode": "AV1 támogatás",
"av1_mode_0": "A Sunshine az AV1 támogatását a kódoló képességei alapján hirdeti (ajánlott)",
"av1_mode_1": "A Sunshine nem fogja hirdetni az AV1 támogatását",
"av1_mode_2": "A Sunshine az AV1 Main 8-bites profil támogatását fogja hirdetni",
"av1_mode_3": "A Sunshine az AV1 Main 8-bites és 10-bites (HDR) profilok támogatását hirdeti meg",
"av1_mode_desc": "Lehetővé teszi az ügyfél számára, hogy AV1 Main 8 bites vagy 10 bites videófolyamokat kérjen. Az AV1 kódolása CPU-igényesebb, ezért ennek engedélyezése csökkentheti a teljesítményt szoftveres kódolás esetén.",
"back_button_timeout": "Home/Guide gomb Emulációs időkorlát",
"back_button_timeout_desc": "Ha a Back/Select gombot a megadott számú milliszekundumig lenyomva tartja, a Home/Guide gomb megnyomása utánozódik. Ha < 0 értékre van beállítva (alapértelmezett), a Back/Select gomb nyomva tartása nem emulálja a Home/Guide gombot.",
"capture": "Speciális rögzítési módszer kikényszerítése",
"capture_desc": "Automatikus üzemmódban a Sunshine az elsőt használja, amelyik működik. Az NvFBC javított nvidia illesztőprogramokat igényel.",
"cert": "Tanúsítvány",
"cert_desc": "A webes felhasználói felület és a Moonlight-ügyfél párosításához használt tanúsítvány. A legjobb kompatibilitás érdekében ennek RSA-2048-as nyilvános kulccsal kell rendelkeznie.",
"channels": "Maximális csatlakoztatott ügyfelek",
"channels_desc_1": "A Sunshine lehetővé teszi, hogy egyetlen streaming munkamenetet egyszerre több ügyfél is használhasson.",
"channels_desc_2": "Néhány hardveres kódolónak lehetnek olyan korlátai, amelyek több adatfolyam esetén csökkentik a teljesítményt.",
"coder_cabac": "cabac -- kontextus adaptív bináris aritmetikai kódolás - magasabb minőség",
"coder_cavlc": "cavlc -- kontextus adaptív változó hosszúságú kódolás - gyorsabb dekódolás",
"configuration": "Konfiguráció",
"controller": "Gamepad bemenet engedélyezése",
"controller_desc": "Lehetővé teszi a vendégek számára, hogy gamepaddal / kontrollerrel irányítsák a gazdarendszert.",
"credentials_file": "Hitelesítési fájl",
"credentials_file_desc": "Tárolja a felhasználónevet/jelszót a Sunshine állapotfájljától elkülönítve.",
"dd_config_ensure_active": "A kijelző automatikus aktiválása",
"dd_config_ensure_only_display": "Más kijelzők kikapcsolása és csak a megadott kijelző aktiválása",
"dd_config_ensure_primary": "A kijelző automatikus aktiválása és elsődleges kijelzővé tétele",
"dd_configuration_option": "Eszköz konfigurációja",
"dd_config_revert_delay": "Config revert késleltetés",
"dd_config_revert_delay_desc": "Kiegészítő késleltetés milliszekundumban, amelyet a konfiguráció visszaállítása előtt várni kell, ha az alkalmazás bezárásra került vagy az utolsó munkamenet befejeződött. Fő célja, hogy simább átmenetet biztosítson az alkalmazások közötti gyors váltáskor.",
"dd_config_revert_on_disconnect": "Konfiguráció visszaállítása a kapcsolat megszakításakor",
"dd_config_revert_on_disconnect_desc": "A konfiguráció visszaállítása az összes ügyfél kapcsolatának megszakításakor az alkalmazás bezárása vagy az utolsó munkamenet befejezése helyett.",
"dd_config_verify_only": "Ellenőrizze, hogy a kijelző engedélyezve van-e",
"dd_hdr_option": "HDR",
"dd_hdr_option_auto": "A HDR üzemmód be-/kikapcsolása az ügyfél kérésének megfelelően (alapértelmezett).",
"dd_hdr_option_disabled": "Ne módosítsa a HDR beállításokat",
"dd_manual_refresh_rate": "Kézi frissítési gyakoriság",
"dd_manual_resolution": "Kézi felbontás",
"dd_mode_remapping": "Megjelenítési mód átképzése",
"dd_mode_remapping_add": "Remapping bejegyzés hozzáadása",
"dd_mode_remapping_desc_1": "Adjon meg remapping bejegyzéseket a kért felbontás és/vagy frissítési sebesség más értékekre történő módosításához.",
"dd_mode_remapping_desc_2": "A lista felülről lefelé halad, és az első találatot használja fel.",
"dd_mode_remapping_desc_3": "A \"Requested\" mezők üresen hagyhatók, hogy megfeleljenek bármely kért értéknek.",
"dd_mode_remapping_desc_4_final_values_mixed": "Legalább egy \"Végleges\" mezőt meg kell adni. A meg nem adott felbontás vagy frissítési sebesség nem fog megváltozni.",
"dd_mode_remapping_desc_4_final_values_non_mixed": "A \"Final\" mezőt meg kell adni, és nem lehet üres.",
"dd_mode_remapping_desc_5_sops_mixed_only": "A \"Játékbeállítások optimalizálása\" opciónak engedélyezve kell lennie a Moonlight kliensben, különben a felbontási mezőkkel rendelkező bejegyzések kihagyásra kerülnek.",
"dd_mode_remapping_desc_5_sops_resolution_only": "A \"Játékbeállítások optimalizálása\" opciónak engedélyezve kell lennie a Moonlight kliensben, különben a leképezés kihagyásra kerül.",
"dd_mode_remapping_final_refresh_rate": "Végső frissítési gyakoriság",
"dd_mode_remapping_final_resolution": "Végső felbontás",
"dd_mode_remapping_requested_fps": "Kért FPS",
"dd_mode_remapping_requested_resolution": "Kért felbontás",
"dd_options_header": "Speciális kijelzőeszköz beállítások",
"dd_refresh_rate_option": "Frissítési gyakoriság",
"dd_refresh_rate_option_auto": "Az ügyfél által megadott FPS-érték használata (alapértelmezett)",
"dd_refresh_rate_option_disabled": "Ne változtassa meg a frissítési gyakoriságot",
"dd_refresh_rate_option_manual": "Kézzel megadott frissítési sebesség használata",
"dd_resolution_option": "Felbontás",
"dd_resolution_option_auto": "A kliensgép által megadott felbontás használata (alapértelmezett)",
"dd_resolution_option_disabled": "Ne változtassa meg a felbontást",
"dd_resolution_option_manual": "Kézzel megadott felbontás használata",
"dd_resolution_option_ogs_desc": "A \"Játékbeállítások optimalizálása\" opciónak engedélyezve kell lennie a Moonlight kliensben, hogy ez működjön.",
"dd_wa_hdr_toggle_delay_desc_1": "Ha virtuális megjelenítő eszközt (VDD) használ a streaminghez, előfordulhat, hogy a HDR színt helytelenül jeleníti meg. A Sunshine megpróbálhatja enyhíteni ezt a problémát a HDR kikapcsolásával, majd újra bekapcsolásával.",
"dd_wa_hdr_toggle_delay_desc_2": "Ha az érték 0-ra van állítva, a megoldás le van tiltva (alapértelmezett). Ha az érték 0 és 3000 milliszekundum között van, a Sunshine kikapcsolja a HDR-t, vár a megadott ideig, majd újra bekapcsolja a HDR-t. Az ajánlott késleltetési idő a legtöbb esetben 500 milliszekundum körül van.",
"dd_wa_hdr_toggle_delay_desc_3": "NE használja ezt a megoldást, kivéve, ha valóban problémái vannak a HDR-rel, mivel ez közvetlenül befolyásolja a stream indulási idejét!",
"dd_wa_hdr_toggle_delay": "Nagy kontrasztú megoldás HDR esetén",
"ds4_back_as_touchpad_click": "Vissza/kiválasztás az érintőpadra kattintás",
"ds4_back_as_touchpad_click_desc": "A DS4 emuláció kényszerítésekor a Vissza/Választás gombot a Touchpad kattintáshoz kell rendelni.",
"ds5_inputtino_randomize_mac": "Virtuális vezérlő MAC véletlenszerűvé tétele",
"ds5_inputtino_randomize_mac_desc": "A vezérlő regisztrációjakor a vezérlők belső indexén alapuló MAC helyett véletlenszerű MAC-et használ, hogy elkerülje a különböző vezérlők konfigurációs beállításainak keveredését, amikor a vezérlők ügyféloldalon cserélődnek.",
"encoder": "Egy adott kódoló kényszerítése",
"encoder_desc": "Kényszeríts egy adott kódolót, különben a Sunshine a legjobb elérhető opciót fogja kiválasztani. Megjegyzés: Ha Windows alatt hardveres kódolót adsz meg, annak meg kell egyeznie azzal a GPU-val, amelyhez a kijelző csatlakoztatva van.",
"encoder_software": "Szoftveres",
"external_ip": "Külső IP",
"external_ip_desc": "Ha nincs megadva külső IP-cím, a Sunshine automatikusan felismeri a külső IP-címet.",
"fec_percentage": "FEC százalékos aránya",
"fec_percentage_desc": "A hibajavító csomagok százalékos aránya adatcsomagonként az egyes videóképkockákban. A magasabb értékek több hálózati csomagveszteséget korrigálhatnak, de ennek ára a sávszélesség-használat növekedése.",
"ffmpeg_auto": "auto -- hagyja, hogy az ffmpeg döntsön (alapértelmezett)",
"file_apps": "Alkalmazások fájl",
"file_apps_desc": "Az a fájl, amelyben a Sunshine aktuális alkalmazásai tárolódnak.",
"file_state": "State fájl",
"file_state_desc": "A fájl, ahol a Sunshine aktuális állapota tárolódik",
"gamepad": "Emulált gamepad típus",
"gamepad_auto": "Automatikus kiválasztási lehetőségek",
"gamepad_desc": "Válassza ki, hogy milyen típusú gamepadot szeretne emulálni a gazdán.",
"gamepad_ds4": "DS4 (PS4)",
"gamepad_ds4_manual": "DS4 kiválasztási lehetőségek",
"gamepad_ds5": "DS5 (PS5)",
"gamepad_ds5_manual": "DS5 kiválasztási lehetőségek",
"gamepad_switch": "Nintendo Pro (Switch)",
"gamepad_manual": "Kézi DS4 opciók",
"gamepad_x360": "X360 (Xbox 360)",
"gamepad_xone": "XOne (Xbox One)",
"global_prep_cmd": "Parancsnoki előkészületek",
"global_prep_cmd_desc": "Konfigurálja a bármely alkalmazás futtatása előtt vagy után végrehajtandó parancsok listáját. Ha a megadott előkészítő parancsok bármelyike sikertelen, az alkalmazás indítási folyamata megszakad.",
"hevc_mode": "HEVC támogatás",
"hevc_mode_0": "A Sunshine a HEVC támogatását a kódoló képességei alapján fogja hirdetni (ajánlott)",
"hevc_mode_1": "A Sunshine nem fogja hirdetni a HEVC támogatását",
"hevc_mode_2": "A Sunshine a HEVC fő profil támogatását fogja hirdetni",
"hevc_mode_3": "A Sunshine a HEVC Main és Main10 (HDR) profilok támogatását fogja hirdetni",
"hevc_mode_desc": "Lehetővé teszi az ügyfél számára, hogy HEVC Main vagy HEVC Main10 videófolyamokat kérjen. A HEVC kódolása CPU-igényesebb, ezért ennek engedélyezése csökkentheti a teljesítményt szoftveres kódolás esetén.",
"high_resolution_scrolling": "Nagy felbontású görgetés támogatása",
"high_resolution_scrolling_desc": "Ha engedélyezve van, a Sunshine átadja a Moonlight-ügyfelek nagy felbontású görgetési eseményeit. Ezt hasznos lehet letiltani olyan régebbi alkalmazások esetében, amelyek túl gyorsan görgetnek a nagy felbontású görgetési eseményekkel.",
"install_steam_audio_drivers": "Steam audió illesztőprogramok telepítése",
"install_steam_audio_drivers_desc": "Ha a Steam telepítve van, ez automatikusan telepíti a Steam Streaming Speakers illesztőprogramot az 5.1/7.1 surround hangzás és a host hang elnémításának támogatásához.",
"key_repeat_delay": "Kulcsismétlés késleltetése",
"key_repeat_delay_desc": "Szabályozza, hogy a billentyűk milyen gyorsan ismétlődjenek. A kezdeti késleltetés milliszekundumban a billentyűk ismétlése előtt.",
"key_repeat_frequency": "Kulcs Ismétlési gyakoriság",
"key_repeat_frequency_desc": "Milyen gyakran ismétlődnek a billentyűk másodpercenként. Ez a konfigurálható opció támogatja a tizedesjegyeket.",
"key_rightalt_to_key_win": "A jobb Alt billentyű Windows billentyűhöz való hozzárendelése",
"key_rightalt_to_key_win_desc": "Előfordulhat, hogy a Windows-kulcsot nem tudja közvetlenül a Moonlightból elküldeni. Ezekben az esetekben hasznos lehet, ha a Sunshine úgy gondolja, hogy a jobb Alt billentyű a Windows billentyű.",
"keybindings": "Billentyűzetkötések",
"keyboard": "Billentyűzetbemenet engedélyezése",
"keyboard_desc": "Lehetővé teszi a vendégek számára, hogy a billentyűzettel irányítsák a gazdarendszert.",
"lan_encryption_mode": "LAN titkosítási mód",
"lan_encryption_mode_1": "Engedélyezve a támogatott kliensgépeknél",
"lan_encryption_mode_2": "Minden kliensgépnek kötelező",
"lan_encryption_mode_desc": "Ez határozza meg, hogy a helyi hálózaton keresztüli streaming során mikor kerül sor titkosításra. A titkosítás csökkentheti a streaming teljesítményét, különösen a kisebb teljesítményű hosztokon és klienseken.",
"locale": "Területi beállítás",
"locale_desc": "A Sunshine felhasználói felületén használt területi beállítás.",
"log_path": "Naplófájl elérési útvonal",
"log_path_desc": "Az a fájl, amelyben a Sunshine aktuális naplói tárolódnak.",
"max_bitrate": "Maximális bitráta",
"max_bitrate_desc": "A maximális bitráta (Kbps-ban), amellyel a Sunshine kódolni fogja a streamet. Ha 0-ra van állítva, mindig a Moonlight által kért bitrátát fogja használni.",
"minimum_fps_target": "Minimális FPS cél",
"minimum_fps_target_desc": "A legalacsonyabb effektív FPS, amelyet egy stream elérhet. A 0 értéket a folyam FPS-értékének nagyjából felének tekintjük. A 20-as beállítás ajánlott, ha 24 vagy 30fps sebességű tartalmat streamel.",
"min_log_level": "Napló szint",
"min_log_level_0": "Bővebben",
"min_log_level_1": "Debug",
"min_log_level_2": "Info",
"min_log_level_3": "Figyelmeztetés",
"min_log_level_4": "Hiba",
"min_log_level_5": "Végzetes",
"min_log_level_6": "Nincs",
"min_log_level_desc": "A minimális naplózási szint, amely a szabványos kimenetre kerül kiírásra",
"min_threads": "Minimális CPU szálszám",
"min_threads_desc": "Az érték növelése kissé csökkenti a kódolás hatékonyságát, de a kompromisszum általában megéri, ha több CPU-magot használhatunk a kódoláshoz. Az ideális érték az a legalacsonyabb érték, amely megbízhatóan kódol a kívánt streaming-beállítások mellett a hardveren.",
"misc": "Egyéb lehetőségek",
"motion_as_ds4": "DS4 gamepad emulálása, ha a kliens gamepad mozgásérzékelők jelenlétét jelzi.",
"motion_as_ds4_desc": "Ha letiltja, a mozgásérzékelőket nem veszi figyelembe a játékvezérlő típusának kiválasztásakor.",
"mouse": "Egérbemenet engedélyezése",
"mouse_desc": "Lehetővé teszi a vendégek számára, hogy az egérrel irányítsák a gazdarendszert.",
"native_pen_touch": "Natív toll/érintés támogatás",
"native_pen_touch_desc": "Ha engedélyezve van, a Sunshine átadja a Moonlight-ügyfelek natív toll/érintés eseményeit. Ezt hasznos lehet letiltani a régebbi, natív toll/érintés támogatással nem rendelkező alkalmazások esetében.",
"notify_pre_releases": "Kiadás előtti értesítések",
"notify_pre_releases_desc": "Akar-e értesítést kapni a Sunshine új, kiadás előtti verzióiról?",
"nvenc_h264_cavlc": "A CAVLC előnyben részesítése a CABAC-kal szemben a H.264-ben",
"nvenc_h264_cavlc_desc": "Az entrópia kódolás egyszerűbb formája. A CAVLC-nek körülbelül 10%-kal több bitrátára van szüksége ugyanahhoz a minőséghez. Csak nagyon régi dekódoló eszközök esetében releváns.",
"nvenc_latency_over_power": "Az alacsonyabb kódolási késleltetés előnyben részesítése az energiatakarékossággal szemben",
"nvenc_latency_over_power_desc": "A Sunshine a maximális GPU-órajelsebességet kéri streamelés közben, hogy csökkentse a kódolási késleltetést. Ennek kikapcsolása nem ajánlott, mivel ez jelentősen megnövekedett kódolási késleltetéshez vezethet.",
"nvenc_opengl_vulkan_on_dxgi": "OpenGL/Vulkan bemutatása a DXGI tetején",
"nvenc_opengl_vulkan_on_dxgi_desc": "A Sunshine nem képes teljes képernyős OpenGL és Vulkan programokat teljes képkocka sebességgel rögzíteni, hacsak nem a DXGI tetején vannak jelen. Ez egy rendszerszintű beállítás, amely a Sunshine programból való kilépéskor visszaáll.",
"nvenc_preset": "Előre beállított teljesítmény",
"nvenc_preset_1": "(leggyorsabb, alapértelmezett)",
"nvenc_preset_7": "(leglassabb)",
"nvenc_preset_desc": "A nagyobb számok javítják a tömörítést (minőséget adott bitráta mellett) a megnövekedett kódolási késleltetés árán. Csak akkor ajánlott változtatni, ha a hálózat vagy a dekóder korlátozza, egyébként hasonló hatás érhető el a bitráta növelésével.",
"nvenc_realtime_hags": "Valós idejű prioritás használata hardveres gyorsított gpu ütemezésben",
"nvenc_realtime_hags_desc": "Jelenleg az NVIDIA illesztőprogramok lefagyhatnak a kódolóban, ha a HAGS engedélyezve van, valós idejű prioritást használnak, és a VRAM kihasználtsága közel van a maximumhoz. Ennek az opciónak a letiltása a prioritást magasra csökkenti, így elkerülhető a lefagyás, ami a GPU nagy terhelésénél a rögzítési teljesítmény csökkenésének árán történik.",
"nvenc_spatial_aq": "Térbeli AQ",
"nvenc_spatial_aq_desc": "A videó lapos régióihoz magasabb QP-értékeket rendelhet. Ajánlott engedélyezni alacsonyabb bitráta mellett történő streamelés esetén.",
"nvenc_twopass": "Kétszeri átjárási mód",
"nvenc_twopass_desc": "Előzetes kódolási lépés hozzáadása. Ez lehetővé teszi több mozgásvektor felismerését, a bitráta jobb elosztását a képkockán belül, és a bitráta-határértékek szigorúbb betartását. A kikapcsolása nem ajánlott, mivel ez esetenként bitráta-túllépéshez és későbbi csomagvesztéshez vezethet.",
"nvenc_twopass_disabled": "Kikapcsolva (leggyorsabb, nem ajánlott)",
"nvenc_twopass_full_res": "Teljes felbontás (lassabb)",
"nvenc_twopass_quarter_res": "Negyedes felbontás (gyorsabb, alapértelmezett)",
"nvenc_vbv_increase": "Egyetlen képkocka VBV/HRD százalékos növekedése",
"nvenc_vbv_increase_desc": "Alapértelmezés szerint a sunshine egykockás VBV/HRD-t használ, ami azt jelenti, hogy a kódolt videóképkocka mérete várhatóan nem haladja meg a kért bitrátát osztva a kért képkocka sebességgel. Ennek a korlátozásnak az enyhítése előnyös lehet, és alacsony késleltetésű változó bitrátaként működhet, de csomagvesztéshez is vezethet, ha a hálózatnak nincs pufferterülete a bitráta-csúcsok kezeléséhez. A maximálisan elfogadott érték 400, ami megfelel az 5x megnövelt kódolt videóképkocka felső mérethatárának.",
"origin_web_ui_allowed": "Origin webes felhasználói felület Engedélyezve",
"origin_web_ui_allowed_desc": "A webes felhasználói felülethez való hozzáférést nem tiltó távoli végpontcím eredete",
"origin_web_ui_allowed_lan": "Csak a LAN-ban lévők férhetnek hozzá a webes felhasználói felülethez",
"origin_web_ui_allowed_pc": "Csak a localhost férhet hozzá a webes felhasználói felülethez",
"origin_web_ui_allowed_wan": "Bárki hozzáférhet a webes felhasználói felülethez",
"output_name": "Kijelző azonosító",
"output_name_desc_unix": "A Sunshine indítása során meg kell jelenítenie az észlelt kijelzők listáját. Megjegyzés: A zárójelben lévő id értéket kell használnia. Az alábbiakban egy példa látható; a tényleges kimenet a Hibaelhárítás lapon található.",
"output_name_desc_windows": "A rögzítéshez használt kijelzőeszköz azonosítójának manuális megadása. Ha nincs megadva, az elsődleges kijelzőt rögzíti a rendszer. Megjegyzés: Ha fentebb GPU-t adott meg, akkor ennek a kijelzőnek ahhoz a GPU-hoz kell csatlakoznia. A Sunshine indítása során meg kell jelenítenie az észlelt kijelzők listáját. Az alábbiakban egy példa látható; a tényleges kimenet a Hibaelhárítás lapon található.",
"ping_timeout": "Ping időkorlát",
"ping_timeout_desc": "Mennyi ideig kell várni milliszekundumban a holdfénytől érkező adatokra a folyam leállítása előtt.",
"pkey": "Privát kulcs",
"pkey_desc": "A webes felhasználói felület és a Moonlight-ügyfél párosításához használt titkos kulcs. A legjobb kompatibilitás érdekében ez egy RSA-2048-as magánkulcs kell, hogy legyen.",
"port": "Port",
"port_alert_1": "A Sunshine nem használhat 1024 alatti portokat!",
"port_alert_2": "A 65535 feletti portok nem elérhetőek!",
"port_desc": "A Sunshine által használt portok családjának beállítása",
"port_http_port_note": "Ezt a portot használja a Moonlighthoz való csatlakozáshoz.",
"port_note": "Megjegyzés",
"port_port": "Port",
"port_protocol": "Protokoll",
"port_tcp": "TCP",
"port_udp": "UDP",
"port_warning": "A webes felhasználói felület internetre való kitettsége biztonsági kockázatot jelent! Csak saját felelősségre!",
"port_web_ui": "Webes felhasználói felület",
"qp": "Kvantálási paraméter",
"qp_desc": "Előfordulhat, hogy egyes eszközök nem támogatják a konstans bitsebességet. Ezeknél az eszközöknél a QP-t használják helyette. A magasabb érték nagyobb tömörítést, de kevesebb minőséget jelent.",
"qsv_coder": "QuickSync kódoló (H264)",
"qsv_preset": "QuickSync Előbeállítás",
"qsv_preset_fast": "fast (alacsony minőség)",
"qsv_preset_faster": "faster (alacsonyabb minőség)",
"qsv_preset_medium": "medium (alapértelmezett)",
"qsv_preset_slow": "slow (jó minőség)",
"qsv_preset_slower": "slower (jobb minőség)",
"qsv_preset_slowest": "slowest (legjobb minőség)",
"qsv_preset_veryfast": "fastest (legalacsonyabb minőség)",
"qsv_slow_hevc": "Lassú HEVC kódolás engedélyezése",
"qsv_slow_hevc_desc": "Ez lehetővé teheti a HEVC kódolást a régebbi Intel GPU-kon, ami a GPU nagyobb kihasználtsága és rosszabb teljesítménye árán érhető el.",
"restart_note": "A Sunshine újraindul, hogy alkalmazza a változtatásokat.",
"stream_audio": "Stream Audio",
"stream_audio_desc": "A hang streamelés vagy sem. Ennek kikapcsolása hasznos lehet fej nélküli kijelzők második monitorként történő streameléséhez.",
"sunshine_name": "Sunshine-név",
"sunshine_name_desc": "A Holdfény által megjelenített név. Ha nincs megadva, a számítógép hostnevét használja a rendszer",
"sw_preset": "SW előbeállítások",
"sw_preset_desc": "A kódolási sebesség (kódolt képkockák másodpercenként) és a tömörítési hatékonyság (minőség a bitfolyamban lévő bitenként) közötti kompromisszum optimalizálása. Alapértelmezés szerint szupergyors.",
"sw_preset_fast": "fast",
"sw_preset_faster": "faster",
"sw_preset_medium": "medium",
"sw_preset_slow": "slow",
"sw_preset_slower": "slower",
"sw_preset_superfast": "superfast (alapértelmezett)",
"sw_preset_ultrafast": "ultrafast",
"sw_preset_veryfast": "veryfast",
"sw_preset_veryslow": "veryslow",
"sw_tune": "SW Tune",
"sw_tune_animation": "animáció -- jó rajzfilmekhez; nagyobb deblockingot és több referencia képkockát használ",
"sw_tune_desc": "Hangolási lehetőségek, amelyek az előbeállítás után kerülnek alkalmazásra. Alapértelmezett beállítása nulla lappangási idő.",
"sw_tune_fastdecode": "fastdecode -- gyorsabb dekódolást tesz lehetővé bizonyos szűrők kikapcsolásával",
"sw_tune_film": "film -- kiváló minőségű filmtartalom esetén; csökkenti a deblockingot",
"sw_tune_grain": "grain -- megőrzi a szemcseszerkezetet a régi, szemcsés filmanyagban",
"sw_tune_stillimage": "stillimage -- jó diavetítésszerű tartalomhoz",
"sw_tune_zerolatency": "zerolatency -- gyors kódoláshoz és alacsony késleltetésű streaminghez jó (alapértelmezett)",
"system_tray": "Rendszertálca engedélyezése",
"system_tray_desc": "ikon megjelenítése a tálcán és asztali értesítések megjelenítése",
"touchpad_as_ds4": "DS4 gamepad emulálása, ha a kliensgép gamepad érintőtábla jelenlétét jelzi",
"touchpad_as_ds4_desc": "Ha letiltja, az érintőpad jelenlétét nem veszi figyelembe a rendszer a gamepad típusának kiválasztásakor.",
"upnp": "UPnP",
"upnp_desc": "Porttovábbítás automatikus konfigurálása az interneten keresztüli streaminghez",
"vaapi_strict_rc_buffer": "A H.264/HEVC képkocka-bitráta korlátok szigorú betartása AMD GPU-kon",
"vaapi_strict_rc_buffer_desc": "Ha engedélyezi ezt a beállítást, elkerülheti a hálózaton keresztül történő képkocka kiesést a jelenetváltások során, de a videó minősége csökkenhet mozgás közben.",
"virtual_sink": "Virtuális mosogató",
"virtual_sink_desc": "Kézzel adja meg a használni kívánt virtuális audioeszközt. Ha nincs megadva, az eszköz automatikusan kiválasztásra kerül. Az automatikus eszközkiválasztás használatához erősen ajánlott üresen hagyni ezt a mezőt!",
"virtual_sink_placeholder": "Steam streaming hangszórók",
"vt_coder": "VideoToolbox kódoló",
"vt_realtime": "VideoToolbox valós idejű kódolás",
"vt_software": "VideoToolbox szoftveres kódolás",
"vt_software_allowed": "Engedélyezett",
"vt_software_forced": "Kényszerített",
"wan_encryption_mode": "WAN titkosítási mód",
"wan_encryption_mode_1": "Engedélyezve a támogatott kliensgépeken (alapértelmezett)",
"wan_encryption_mode_2": "Minden kliensgép számára kötelező",
"wan_encryption_mode_desc": "Ez határozza meg, hogy az interneten keresztüli streaming során mikor kerül sor titkosításra. A titkosítás csökkentheti a streaming teljesítményét, különösen a kisebb teljesítményű hosztokon és klienseken."
},
"index": {
"description": "A Sunshine a Moonlight saját szervezésű játékstream hostja.",
"download": "Letöltés",
"installed_version_not_stable": "Ön a Sunshine kiadás előtti verzióját futtatja. Előfordulhatnak hibák vagy egyéb problémák. Kérjük, jelentse a felmerülő problémákat. Köszönjük, hogy segít a Sunshine jobbá tételében!",
"loading_latest": "Legújabb kiadás betöltése...",
"new_pre_release": "Elérhető egy új előzetes verzió!",
"new_stable": "Elérhető egy új stabil verzió!",
"startup_errors": "<b>Figyelem!</b> A Sunshine ezeket a hibákat észlelte az indítás során. <b>KIFEJEZETTEN AJÁNLJUK</b> ezek kijavítását a streamelés előtt.",
"version_dirty": "Köszönjük, hogy segítesz a Sunshine jobb szoftverré tételében!",
"version_latest": "A Sunshine legújabb verzióját futtatod",
"welcome": "Helló, Sunshine!"
},
"navbar": {
"applications": "Alkalmazások",
"configuration": "Konfiguráció",
"home": "Home",
"password": "Jelszó módosítása",
"pin": "PIN",
"theme_auto": "Auto",
"theme_dark": "Sötét",
"theme_light": "Világos",
"toggle_theme": "Téma",
"troubleshoot": "Hibaelhárítás"
},
"password": {
"confirm_password": "Jelszó megerősítése",
"current_creds": "Jelenlegi megbízólevelek",
"new_creds": "Új megbízólevelek",
"new_username_desc": "Ha nincs megadva, a felhasználónév nem fog változni.",
"password_change": "Jelszó módosítása",
"success_msg": "A jelszó módosítása sikeresen megtörtént! Ez az oldal hamarosan újratöltődik, a böngésző kérni fogja az új hitelesítő adatokat."
},
"pin": {
"device_name": "Eszköz neve",
"pair_failure": "A párosítás sikertelen: Ellenőrizze, hogy a PIN kód helyesen lett-e beírva",
"pair_success": "Siker! Kérjük, ellenőrizze a Holdfényt a folytatáshoz",
"pin_pairing": "PIN párosítás",
"send": "Küldés",
"warning_msg": "Győződjön meg róla, hogy hozzáférése van a párosított ügyfélhez. Ez a szoftver teljes irányítást adhat a számítógépének, ezért legyen óvatos!"
},
"resource_card": {
"github_discussions": "GitHub Discussions",
"legal": "Jogi",
"legal_desc": "A szoftver további használatával Ön elfogadja a következő dokumentumokban foglalt feltételeket.",
"license": "Licenc",
"lizardbyte_website": "LizardByte honlapja",
"resources": "Források",
"resources_desc": "Források a Sunshine-hoz!",
"third_party_notice": "Harmadik fél közleménye"
},
"troubleshooting": {
"dd_reset": "Tartós kijelzőkészülék beállításainak visszaállítása",
"dd_reset_desc": "Ha a Sunshine elakad a módosított kijelzőeszköz-beállítások visszaállítása során, akkor visszaállíthatja a beállításokat, és manuálisan folytathatja a kijelző állapotának visszaállítását.",
"dd_reset_error": "Hiba a perzisztencia visszaállítása közben!",
"dd_reset_success": "Sikeres visszaállítása kitartás!",
"force_close": "Bezárás erőltetése",
"force_close_desc": "Ha a Moonlight panaszt tesz egy jelenleg futó alkalmazás miatt, az alkalmazás kényszerített bezárása megoldja a problémát.",
"force_close_error": "Hiba az alkalmazás bezárásakor",
"force_close_success": "Az alkalmazás bezárása sikeres!",
"logs": "Naplók",
"logs_desc": "A Sunshine által feltöltött naplók megtekintése",
"logs_find": "Keresés...",
"restart_sunshine": "Sunshine újraindítása",
"restart_sunshine_desc": "Ha a Sunshine nem működik megfelelően, próbálja meg újraindítani. Ez megszakítja a futó munkameneteket.",
"restart_sunshine_success": "A Sunshine újraindul",
"troubleshooting": "Hibaelhárítás",
"unpair_all": "Unpair All",
"unpair_all_error": "Hiba a párosítás feloldásakor",
"unpair_all_success": "Minden eszköz nincs párosítva.",
"unpair_desc": "Távolítsa el a párosított eszközöket. Az aktív munkamenettel rendelkező, különállóan nem párosított eszközök továbbra is kapcsolatban maradnak, de nem tudnak munkamenetet indítani vagy folytatni.",
"unpair_single_no_devices": "Nincsenek párosított eszközök.",
"unpair_single_success": "Előfordulhat azonban, hogy az eszköz(ök) még mindig aktív munkamenetben van(nak). A fenti \"Force Close\" (Bezárás kikényszerítése) gomb segítségével fejezze be a nyitott munkameneteket.",
"unpair_single_unknown": "Ismeretlen kliens",
"unpair_title": "Eszközök párosításának feloldása"
},
"welcome": {
"confirm_password": "Jelszó megerősítése",
"create_creds": "Mielőtt elkezdené, új felhasználónevet és jelszót kell létrehoznia a webes felhasználói felülethez való hozzáféréshez.",
"create_creds_alert": "A Sunshine webes felhasználói felületének eléréséhez az alábbi hitelesítő adatokra van szükség. Tartsa őket biztonságban, mivel soha többé nem fogja látni őket!",
"greeting": "Üdvözlünk a Sunshine-ban!",
"login": "Bejelentkezés",
"welcome_success": "Ez az oldal hamarosan újratöltődik, és a böngésző kérni fogja az új hitelesítő adatokat."
}
}

View File

@@ -0,0 +1,462 @@
{
"_common": {
"apply": "Áp dụng",
"auto": "Tự động",
"autodetect": "Phát hiện tự động (đề xuất)",
"beta": "(phiên bản thử nghiệm)",
"cancel": "Hủy",
"disabled": "Tắt",
"disabled_def": "Tắt (mặc định)",
"disabled_def_cbox": "Mặc định: không bật",
"dismiss": "Bỏ qua",
"do_cmd": "Thực hiện lệnh",
"elevated": "Nâng cao",
"enabled": "Đã bật",
"enabled_def": "Bật (mặc định)",
"enabled_def_cbox": "Mặc định: đã bật",
"error": "Lỗi!",
"note": "Lưu ý:",
"password": "Mật khẩu",
"run_as": "Chạy với quyền Admin",
"save": "Lưu",
"see_more": "Xem thêm",
"success": "Thành công!",
"undo_cmd": "Hủy lệnh",
"username": "Tên đăng nhập",
"warning": "Cảnh báo!"
},
"apps": {
"actions": "Hành động",
"add_cmds": "Thêm lệnh",
"add_new": "Thêm mới",
"app_name": "Tên ứng dụng",
"app_name_desc": "Tên ứng dụng, hiển thị trên Moonlight",
"applications_desc": "Ứng dụng chỉ được làm mới khi Client được khởi động lại.",
"applications_title": "Ứng dụng",
"auto_detach": "Không ngắt stream nếu ứng dụng thoát trong thời gian ngắn",
"auto_detach_desc": "Tùy chọn này sẽ cố gắng tự động nhận diện các ứng dụng dạng launcher, thường thoát ngay sau khi mở một chương trình khác hoặc một phiên bản khác của chính nó.\nKhi phát hiện launcher như vậy, hệ thống sẽ xử lý nó như một ứng dụng tách biệt để tránh ngắt kết nối stream.",
"cmd": "Lệnh",
"cmd_desc": "Ứng dụng chính để khởi động. Nếu để trống, sẽ không khởi động ứng dụng nào.",
"cmd_note": "Nếu đường dẫn đến tệp thực thi lệnh chứa khoảng trắng (space), bạn phải đặt nó trong dấu ngoặc kép.",
"cmd_prep_desc": "Danh sách các lệnh sẽ được chạy trước hoặc sau ứng dụng này. Nếu bất kỳ lệnh chuẩn bị nào bị lỗi, quá trình khởi chạy ứng dụng sẽ bị hủy.",
"cmd_prep_name": "Chuẩn bị lệnh",
"covers_found": "Bìa đã tìm thấy",
"delete": "Xóa",
"detached_cmds": "Lệnh độc lập",
"detached_cmds_add": "Thêm lệnh tách rời",
"detached_cmds_desc": "Danh sách các lệnh cần chạy ở chế độ nền.",
"detached_cmds_note": "Nếu đường dẫn đến tệp thực thi lệnh chứa khoảng trắng (space), bạn phải đặt nó trong dấu ngoặc kép.",
"edit": "Chỉnh sửa",
"env_app_id": "ID ứng dụng",
"env_app_name": "Tên ứng dụng",
"env_client_audio_config": "Cấu hình âm thanh được yêu cầu bởi client (2.0/5.1/7.1)",
"env_client_enable_sops": "Client yêu cầu tùy chọn tối ưu hóa trò chơi cho việc streaming tối ưu (có/không)",
"env_client_fps": "Tốc độ khung hình mỗi giây (FPS) mà client yêu cầu (số nguyên)",
"env_client_gcmap": "The requested gamepad mask, in a bitset/bitfield format (int)",
"env_client_hdr": "HDR được kích hoạt bởi client (true/false)",
"env_client_height": "Chiều cao do client yêu cầu (số nguyên)",
"env_client_host_audio": "Client yêu cầu âm thanh từ host (có/không)",
"env_client_width": "Chiều rộng được yêu cầu bởi client (số nguyên)",
"env_displayplacer_example": "Ví dụ - Công cụ hiển thị cho độ phân giải động:",
"env_qres_example": "Ví dụ - QRes cho độ phân giải động:",
"env_qres_path": "Đường dẫn qres",
"env_var_name": "Tên biến",
"env_vars_about": "Về biến môi trường",
"env_vars_desc": "Tất cả các lệnh đều được gán các biến môi trường sau theo mặc định:",
"env_xrandr_example": "Ví dụ - Xrandr cho độ phân giải động:",
"exit_timeout": "Thời gian chờ thoát",
"exit_timeout_desc": "Số giây chờ đợi cho tất cả các tiến trình của ứng dụng thoát ra một cách trơn tru khi được yêu cầu thoát. Nếu không được thiết lập, giá trị mặc định là chờ tối đa 5 giây. Nếu được thiết lập thành 0, ứng dụng sẽ bị kết thúc ngay lập tức.",
"find_cover": "Tìm chỗ trú ẩn",
"global_prep_desc": "Bật/Tắt việc thực thi các lệnh chuẩn bị toàn cầu cho ứng dụng này.",
"global_prep_name": "Lệnh chuẩn bị toàn cầu",
"image": "Hình ảnh",
"image_desc": "Đường dẫn đến biểu tượng/hình ảnh sẽ được gửi đến client. Hình ảnh phải là file PNG. Nếu không được thiết lập, Sunshine sẽ gửi hình ảnh mặc định.",
"loading": "Đang tải...",
"name": "Tên",
"output_desc": "Tệp chứa kết quả đầu ra của lệnh. Nếu không được chỉ định, kết quả đầu ra sẽ bị bỏ qua.",
"output_name": "Đầu ra",
"run_as_desc": "Điều này có thể cần thiết cho một số ứng dụng yêu cầu quyền quản trị viên (Administrator) để hoạt động đúng cách.",
"wait_all": "Tiếp tục streaming cho đến khi tất cả các tiến trình của ứng dụng kết thúc",
"wait_all_desc": "Quá trình streaming này sẽ tiếp tục cho đến khi tất cả các tiến trình được ứng dụng khởi chạy đã kết thúc. Khi tùy chọn này không được chọn, stream sẽ dừng lại khi tiến trình chính của ứng dụng kết thúc, ngay cả khi các tiến trình khác của ứng dụng vẫn đang chạy.",
"working_dir": "Thư mục làm việc",
"working_dir_desc": "Thư mục làm việc cần được truyền vào quá trình. Ví dụ, một số ứng dụng sử dụng thư mục làm việc để tìm kiếm các tệp cấu hình. Nếu không được thiết lập, Sunshine sẽ mặc định sử dụng thư mục cha của lệnh."
},
"config": {
"adapter_name": "Adapter Name",
"adapter_name_desc_linux_1": "Chọn GPU cụ thể để sử dụng cho quá trình capture.",
"adapter_name_desc_linux_2": "Tìm tất cả các thiết bị hỗ trợ VAAPI",
"adapter_name_desc_linux_3": "Thay thế ``renderD129`` bằng thiết bị từ trên để liệt kê tên và khả năng của thiết bị. Để được hỗ trợ bởi Sunshine, thiết bị cần phải có ít nhất:",
"adapter_name_desc_windows": "Chỉ định GPU cụ thể để sử dụng cho quá trình capture Nếu không được thiết lập, GPU sẽ được chọn tự động. Chúng tôi khuyến nghị để để trống trường này để sử dụng tính năng chọn GPU tự động! Lưu ý: GPU này phải có màn hình kết nối và đang bật nguồn. Các giá trị phù hợp có thể được tìm thấy bằng cách sử dụng lệnh sau:",
"adapter_name_placeholder_windows": "Radeon RX 580 Series",
"add": "Thêm",
"address_family": "Kiểu địa chỉ mạng",
"address_family_both": "IPv4 và IPv6",
"address_family_desc": "Đặt loại địa chỉ mạng được sử dụng bởi Sunshine",
"address_family_ipv4": "Chỉ hỗ trợ IPv4",
"always_send_scancodes": "Luôn gửi mã quét",
"always_send_scancodes_desc": "Gửi mã quét (scancodes) giúp tăng tính tương thích với các trò chơi và ứng dụng, nhưng có thể dẫn đến nhập liệu bàn phím không chính xác từ một số client không sử dụng bố cục bàn phím tiếng Anh Mỹ. Bật tùy chọn này nếu nhập liệu bàn phím không hoạt động trong một số ứng dụng. Tắt tùy chọn này nếu các phím trên client tạo ra nhập liệu sai trên host.",
"amd_coder": "Mã hóa AMF (H264)",
"amd_coder_desc": "Cho phép bạn chọn mã hóa entropy để ưu tiên chất lượng hoặc tốc độ mã hóa. Chỉ hỗ trợ H.264.",
"amd_enforce_hrd": "Thiết bị giải mã tham chiếu giả định AMF (HRD)",
"amd_enforce_hrd_desc": "Tăng cường các giới hạn kiểm soát tốc độ để đáp ứng yêu cầu của mô hình HRD. Điều này giúp giảm đáng kể hiện tượng tràn bitrate, nhưng có thể gây ra các lỗi mã hóa hoặc giảm chất lượng trên một số thẻ.",
"amd_preanalysis": "Phân tích tiền xử lý AMF",
"amd_preanalysis_desc": "Điều này cho phép thực hiện phân tích trước để kiểm soát tốc độ, có thể cải thiện chất lượng nhưng đồng thời làm tăng độ trễ mã hóa.",
"amd_quality": "Chất lượng AMF",
"amd_quality_balanced": "cân bằng -- cân bằng (mặc định)",
"amd_quality_desc": "Điều này điều chỉnh sự cân bằng giữa tốc độ encode và chất lượng.",
"amd_quality_group": "Cài đặt chất lượng AMF",
"amd_quality_quality": "chất lượng -- ưu tiên chất lượng",
"amd_quality_speed": "Tốc độ -- Ưu tiên tốc độ",
"amd_rc": "Kiểm soát tốc độ AMF",
"amd_rc_cbr": "cbr -- Tốc độ bit cố định (được khuyến nghị nếu HRD được bật)",
"amd_rc_cqp": "cqp -- Chế độ QP cố định",
"amd_rc_desc": "Điều này kiểm soát phương pháp điều chỉnh tốc độ để đảm bảo chúng ta không vượt quá mục tiêu bitrate của khách hàng. 'cqp' không phù hợp cho việc điều chỉnh bitrate, và các tùy chọn khác ngoài 'vbr_latency' phụ thuộc vào HRD Enforcement để giúp hạn chế việc vượt quá bitrate.",
"amd_rc_group": "Cài đặt kiểm soát tốc độ AMF",
"amd_rc_vbr_latency": "vbr_latency -- Tốc độ bit biến đổi có giới hạn độ trễ (được khuyến nghị nếu HRD bị vô hiệu hóa; mặc định)",
"amd_rc_vbr_peak": "vbr_peak -- Tốc độ bit biến đổi có giới hạn đỉnh",
"amd_usage": "Sử dụng AMF",
"amd_usage_desc": "Điều này thiết lập cấu hình encode cơ bản. Tất cả các tùy chọn được trình bày bên dưới sẽ ghi đè lên một phần của cấu hình sử dụng, nhưng có thêm các thiết lập ẩn được áp dụng mà không thể cấu hình ở nơi khác.",
"amd_usage_lowlatency": "lowlatency - độ trễ thấp (nhanh nhất)",
"amd_usage_lowlatency_high_quality": "lowlatency_high_quality - độ trễ thấp, chất lượng cao (nhanh)",
"amd_usage_transcoding": "Chuyển mã -- Chuyển mã (chậm nhất)",
"amd_usage_ultralowlatency": "ultralowlatency - độ trễ cực thấp (nhanh nhất; mặc định)",
"amd_usage_webcam": "webcam -- webcam (chậm)",
"amd_vbaq": "Cơ chế nén thích nghi theo mức độ thay đổi hình ảnh (VBAQ) của AMF",
"amd_vbaq_desc": "Hệ thống thị giác của con người thường ít nhạy cảm hơn với các hiện tượng nhiễu trong các vùng có kết cấu phức tạp. Trong chế độ VBAQ, độ biến thiên của pixel được sử dụng để chỉ ra độ phức tạp của kết cấu không gian, cho phép bộ mã hóa phân bổ nhiều bit hơn cho các vùng mịn hơn. Kích hoạt tính năng này mang lại cải thiện về chất lượng hình ảnh chủ quan với một số nội dung.",
"apply_note": "Nhấp vào 'Áp dụng' để khởi động lại Sunshine và áp dụng các thay đổi. Điều này sẽ kết thúc tất cả các phiên đang chạy.",
"audio_sink": "Bộ thu âm thanh",
"audio_sink_desc_linux": "Tên của thiết bị âm thanh được sử dụng cho vòng lặp âm thanh (Audio Loopback). Nếu bạn không chỉ định biến này, pulseaudio sẽ chọn thiết bị monitor mặc định. Bạn có thể tìm tên của thiết bị âm thanh bằng một trong hai lệnh sau:",
"audio_sink_desc_macos": "Tên của thiết bị đầu ra âm thanh được sử dụng cho Audio Loopback. Sunshine chỉ có thể truy cập micro trên macOS do hạn chế của hệ thống. Để phát âm thanh hệ thống thông qua Soundflower hoặc BlackHole.",
"audio_sink_desc_windows": "Chọn thủ công thiết bị âm thanh cụ thể để ghi âm. Nếu không được thiết lập, thiết bị sẽ được chọn tự động. Chúng tôi khuyến nghị mạnh mẽ để để trống trường này để sử dụng tính năng chọn thiết bị tự động! Nếu bạn có nhiều thiết bị âm thanh có tên giống nhau, bạn có thể lấy ID thiết bị bằng cách sử dụng lệnh sau:",
"audio_sink_placeholder_macos": "Lỗ đen 2ch",
"audio_sink_placeholder_windows": "Loa (Thiết bị âm thanh độ nét cao)",
"av1_mode": "Hỗ trợ AV1",
"av1_mode_0": "Sunshine sẽ quảng cáo hỗ trợ cho AV1 dựa trên khả năng của bộ mã hóa (được khuyến nghị)",
"av1_mode_1": "Sunshine sẽ không quảng cáo hỗ trợ cho AV1.",
"av1_mode_2": "Sunshine sẽ quảng cáo hỗ trợ cho AV1 Main 8-bit profile.",
"av1_mode_3": "Sunshine sẽ quảng cáo hỗ trợ cho các cấu hình AV1 Main 8-bit và 10-bit (HDR).",
"av1_mode_desc": "Cho phép khách hàng yêu cầu luồng video AV1 Main 8-bit hoặc 10-bit. AV1 đòi hỏi nhiều tài nguyên CPU hơn để mã hóa, do đó việc kích hoạt tính năng này có thể làm giảm hiệu suất khi sử dụng mã hóa phần mềm.",
"back_button_timeout": "Thời gian chờ cho nút Home/Hướng dẫn",
"back_button_timeout_desc": "Nếu nút Back/Select được giữ nhấn trong số mili giây đã chỉ định, thao tác nhấn nút Home/Guide sẽ được mô phỏng. Nếu giá trị được đặt nhỏ hơn 0 (mặc định), việc giữ nút Back/Select sẽ không mô phỏng thao tác nhấn nút Home/Guide.",
"capture": "Buộc sử dụng phương pháp capture cụ thể",
"capture_desc": "Ở chế độ tự động, Sunshine sẽ sử dụng trình điều khiển đầu tiên hoạt động. NvFBC yêu cầu trình điều khiển NVIDIA đã được vá.",
"cert": "Chứng chỉ",
"cert_desc": "Chứng chỉ được sử dụng cho việc ghép nối giao diện người dùng web (web UI) và ứng dụng Moonlight. Để đảm bảo tương thích tốt nhất, chứng chỉ này nên sử dụng khóa công khai RSA-2048.",
"channels": "Số lượng khách hàng kết nối tối đa",
"channels_desc_1": "Ánh sáng mặt trời cho phép một phiên phát trực tuyến duy nhất được chia sẻ đồng thời với nhiều khách hàng.",
"channels_desc_2": "Một số bộ mã hóa phần cứng có thể có các hạn chế làm giảm hiệu suất khi xử lý nhiều luồng.",
"coder_cabac": "cabac -- Mã hóa nhị phân thích ứng theo ngữ cảnh - Chất lượng cao hơn",
"coder_cavlc": "cavlc -- Mã hóa độ dài biến đổi thích ứng với ngữ cảnh - Giải mã nhanh hơn",
"configuration": "Cấu hình",
"controller": "Bật điều khiển bằng gamepad",
"controller_desc": "Cho phép khách điều khiển hệ thống chủ bằng gamepad / bộ điều khiển.",
"credentials_file": "Tệp thông tin xác thực",
"credentials_file_desc": "Lưu tên người dùng/mật khẩu riêng biệt với tệp trạng thái của Sunshine.",
"dd_config_ensure_active": "Bật màn hình tự động",
"dd_config_ensure_only_display": "Tắt các màn hình khác và chỉ kích hoạt màn hình đã chỉ định.",
"dd_config_ensure_primary": "Kích hoạt màn hình tự động và thiết lập nó làm màn hình chính.",
"dd_configuration_option": "Cấu hình thiết bị",
"dd_config_revert_delay": "Thời gian trễ khôi phục cấu hình",
"dd_config_revert_delay_desc": "Thời gian trễ bổ sung (tính bằng mili giây) để chờ trước khi khôi phục cấu hình khi ứng dụng đã bị đóng hoặc phiên làm việc cuối cùng đã kết thúc. Mục đích chính là cung cấp quá trình chuyển đổi mượt mà hơn khi chuyển đổi nhanh giữa các ứng dụng.",
"dd_config_revert_on_disconnect": "Khôi phục cài đặt gốc khi ngắt kết nối",
"dd_config_revert_on_disconnect_desc": "Khôi phục cấu hình khi tất cả các client ngắt kết nối thay vì khi ứng dụng đóng hoặc phiên làm việc cuối cùng kết thúc.",
"dd_config_verify_only": "Kiểm tra xem màn hình đã được bật chưa.",
"dd_hdr_option": "HDR",
"dd_hdr_option_auto": "Bật/tắt chế độ HDR theo yêu cầu của khách hàng (mặc định)",
"dd_hdr_option_disabled": "Không thay đổi cài đặt HDR.",
"dd_manual_refresh_rate": "Tốc độ làm mới thủ công",
"dd_manual_resolution": "Giải quyết thủ công",
"dd_mode_remapping": "Chuyển đổi chế độ hiển thị",
"dd_mode_remapping_add": "Thêm mục remapping",
"dd_mode_remapping_desc_1": "Chỉ định các mục remapping để thay đổi độ phân giải và/hoặc tần số làm mới yêu cầu sang các giá trị khác.",
"dd_mode_remapping_desc_2": "Danh sách được duyệt từ trên xuống dưới và kết quả khớp đầu tiên được sử dụng.",
"dd_mode_remapping_desc_3": "Các trường \"Yêu cầu\" có thể để trống để phù hợp với bất kỳ giá trị nào được yêu cầu.",
"dd_mode_remapping_desc_4_final_values_mixed": "Phải chỉ định ít nhất một trường \"Final\". Độ phân giải hoặc tần số làm mới không được chỉ định sẽ không được thay đổi.",
"dd_mode_remapping_desc_4_final_values_non_mixed": "Trường \"Final\" phải được chỉ định và không được để trống.",
"dd_mode_remapping_desc_5_sops_mixed_only": "Tùy chọn \"Tối ưu hóa cài đặt trò chơi\" phải được bật trong ứng dụng Moonlight, nếu không các mục có trường độ phân giải được chỉ định sẽ bị bỏ qua.",
"dd_mode_remapping_desc_5_sops_resolution_only": "Tùy chọn \"Tối ưu hóa cài đặt trò chơi\" phải được bật trong ứng dụng Moonlight, nếu không quá trình ánh xạ sẽ bị bỏ qua.",
"dd_mode_remapping_final_refresh_rate": "Tần số làm mới cuối cùng",
"dd_mode_remapping_final_resolution": "Quyết định cuối cùng",
"dd_mode_remapping_requested_fps": "Tốc độ khung hình yêu cầu (FPS)",
"dd_mode_remapping_requested_resolution": "Giải pháp được yêu cầu",
"dd_options_header": "Các tùy chọn hiển thị nâng cao",
"dd_refresh_rate_option": "Tần số làm mới",
"dd_refresh_rate_option_auto": "Sử dụng giá trị FPS do khách hàng cung cấp (mặc định)",
"dd_refresh_rate_option_disabled": "Không thay đổi tần số làm mới.",
"dd_refresh_rate_option_manual": "Sử dụng tần số làm mới được nhập thủ công",
"dd_resolution_option": "Quyết định",
"dd_resolution_option_auto": "Sử dụng độ phân giải do khách hàng cung cấp (mặc định)",
"dd_resolution_option_disabled": "Không thay đổi độ phân giải",
"dd_resolution_option_manual": "Sử dụng độ phân giải được nhập thủ công",
"dd_resolution_option_ogs_desc": "Tùy chọn \"Tối ưu hóa cài đặt trò chơi\" phải được bật trên ứng dụng Moonlight để tính năng này hoạt động.",
"dd_wa_hdr_toggle_delay_desc_1": "Khi sử dụng thiết bị hiển thị ảo (VDD) cho việc phát trực tuyến, màu HDR có thể hiển thị không chính xác. Sunshine có thể thử khắc phục vấn đề này bằng cách tắt HDR và sau đó bật lại.",
"dd_wa_hdr_toggle_delay_desc_2": "Nếu giá trị được đặt là 0, tính năng khắc phục sự cố sẽ bị vô hiệu hóa (mặc định). Nếu giá trị nằm trong khoảng từ 0 đến 3000 mili giây, Sunshine sẽ tắt HDR, chờ trong khoảng thời gian đã chỉ định và sau đó bật HDR lại. Thời gian chờ khuyến nghị là khoảng 500 mili giây trong hầu hết các trường hợp.",
"dd_wa_hdr_toggle_delay_desc_3": "KHÔNG sử dụng giải pháp tạm thời này trừ khi bạn thực sự gặp vấn đề với HDR, vì nó ảnh hưởng trực tiếp đến thời gian bắt đầu phát trực tiếp!",
"dd_wa_hdr_toggle_delay": "Giải pháp thay thế cho HDR có độ tương phản cao",
"ds4_back_as_touchpad_click": "Quay lại bản đồ/Chọn bằng cách nhấp chuột vào bàn di chuột",
"ds4_back_as_touchpad_click_desc": "Khi ép buộc mô phỏng DS4, gán nút Back/Select cho thao tác nhấp chuột trên bàn di chuột.",
"ds5_inputtino_randomize_mac": "Ngẫu nhiên hóa địa chỉ MAC của bộ điều khiển ảo",
"ds5_inputtino_randomize_mac_desc": "Khi đăng ký bộ điều khiển, hãy sử dụng một địa chỉ MAC ngẫu nhiên thay vì địa chỉ dựa trên chỉ số nội bộ của bộ điều khiển để tránh trộn lẫn các thiết lập cấu hình của các bộ điều khiển khác nhau khi chúng được hoán đổi trên phía client.",
"encoder": "Bắt buộc sử dụng bộ mã hóa cụ thể",
"encoder_desc": "Buộc sử dụng bộ mã hóa cụ thể, nếu không Sunshine sẽ tự động chọn tùy chọn tốt nhất có sẵn. Lưu ý: Nếu bạn chỉ định bộ mã hóa phần cứng trên Windows, nó phải trùng khớp với GPU mà màn hình được kết nối.",
"encoder_software": "Phần mềm",
"external_ip": "Địa chỉ IP bên ngoài",
"external_ip_desc": "Nếu không được cung cấp địa chỉ IP bên ngoài, Sunshine sẽ tự động phát hiện địa chỉ IP bên ngoài.",
"fec_percentage": "Tỷ lệ phần trăm FEC",
"fec_percentage_desc": "Tỷ lệ gói tin sửa lỗi trên mỗi gói tin dữ liệu trong mỗi khung hình video. Giá trị cao hơn có thể bù đắp cho việc mất gói tin mạng nhiều hơn, nhưng đổi lại sẽ làm tăng sử dụng băng thông.",
"ffmpeg_auto": "Tự động -- để ffmpeg quyết định (mặc định)",
"file_apps": "Tệp ứng dụng",
"file_apps_desc": "Thư mục chứa các ứng dụng hiện tại của Sunshine.",
"file_state": "Tệp của Nhà nước",
"file_state_desc": "Tệp chứa trạng thái hiện tại của Sunshine",
"gamepad": "Loại bộ điều khiển trò chơi mô phỏng",
"gamepad_auto": "Tùy chọn chọn tự động",
"gamepad_desc": "Chọn loại gamepad muốn mô phỏng trên máy chủ.",
"gamepad_ds4": "DS4 (PlayStation 4)",
"gamepad_ds4_manual": "Các tùy chọn lựa chọn cho DS4",
"gamepad_ds5": "DS5 (PS5)",
"gamepad_ds5_manual": "Các tùy chọn lựa chọn cho DS5",
"gamepad_switch": "Nintendo Pro (Switch)",
"gamepad_manual": "Các tùy chọn DS4 thủ công",
"gamepad_x360": "X360 (Xbox 360)",
"gamepad_xone": "XOne (Xbox One)",
"global_prep_cmd": "Chuẩn bị lệnh",
"global_prep_cmd_desc": "Cấu hình danh sách các lệnh cần thực thi trước hoặc sau khi chạy bất kỳ ứng dụng nào. Nếu bất kỳ lệnh chuẩn bị nào trong danh sách bị thất bại, quá trình khởi chạy ứng dụng sẽ bị hủy bỏ.",
"hevc_mode": "Hỗ trợ HEVC",
"hevc_mode_0": "Sunshine sẽ quảng cáo hỗ trợ cho HEVC dựa trên khả năng của bộ mã hóa (được khuyến nghị)",
"hevc_mode_1": "Sunshine sẽ không quảng cáo hỗ trợ cho HEVC.",
"hevc_mode_2": "Sunshine sẽ quảng cáo hỗ trợ cho HEVC Main profile.",
"hevc_mode_3": "Sunshine sẽ quảng cáo hỗ trợ cho các cấu hình HEVC Main và Main10 (HDR).",
"hevc_mode_desc": "Cho phép khách hàng yêu cầu luồng video HEVC Main hoặc HEVC Main10. HEVC đòi hỏi nhiều tài nguyên CPU hơn khi mã hóa, do đó việc kích hoạt tính năng này có thể làm giảm hiệu suất khi sử dụng mã hóa phần mềm.",
"high_resolution_scrolling": "Hỗ trợ cuộn với độ phân giải cao",
"high_resolution_scrolling_desc": "Khi được bật, Sunshine sẽ truyền các sự kiện cuộn có độ phân giải cao từ các ứng dụng Moonlight. Tính năng này có thể hữu ích để tắt cho các ứng dụng cũ có tốc độ cuộn quá nhanh khi sử dụng sự kiện cuộn có độ phân giải cao.",
"install_steam_audio_drivers": "Cài đặt trình điều khiển âm thanh Steam",
"install_steam_audio_drivers_desc": "Nếu Steam đã được cài đặt, trình điều khiển loa phát trực tuyến Steam sẽ được cài đặt tự động để hỗ trợ âm thanh vòm 5.1/7.1 và tắt âm thanh của ứng dụng chủ.",
"key_repeat_delay": "Thời gian trễ lặp lại phím",
"key_repeat_delay_desc": "Điều chỉnh tốc độ lặp lại của các phím. Thời gian trễ ban đầu (tính bằng mili giây) trước khi các phím bắt đầu lặp lại.",
"key_repeat_frequency": "Tần suất lặp lại phím",
"key_repeat_frequency_desc": "Tần suất lặp lại của các phím mỗi giây. Tùy chọn này có thể điều chỉnh và hỗ trợ số thập phân.",
"key_rightalt_to_key_win": "Gán phím Alt bên phải cho phím Windows",
"key_rightalt_to_key_win_desc": "Có thể bạn không thể gửi phím Windows từ Moonlight trực tiếp. Trong trường hợp đó, có thể hữu ích khi làm cho Sunshine nghĩ rằng phím Alt bên phải là phím Windows.",
"keybindings": "Phím tắt",
"keyboard": "Bật nhập liệu bằng bàn phím",
"keyboard_desc": "Cho phép khách truy cập điều khiển hệ thống chủ thông qua bàn phím.",
"lan_encryption_mode": "Chế độ mã hóa mạng LAN",
"lan_encryption_mode_1": "Đã kích hoạt cho các khách hàng được hỗ trợ",
"lan_encryption_mode_2": "Yêu cầu đối với tất cả khách hàng",
"lan_encryption_mode_desc": "Điều này xác định thời điểm mã hóa sẽ được sử dụng khi truyền phát qua mạng nội bộ của bạn. Mã hóa có thể làm giảm hiệu suất truyền phát, đặc biệt là trên các máy chủ và thiết bị khách có cấu hình yếu.",
"locale": "Vùng",
"locale_desc": "Ngôn ngữ giao diện người dùng được sử dụng cho Sunshine.",
"log_path": "Đường dẫn tệp nhật ký",
"log_path_desc": "Tệp chứa các bản ghi hiện tại của Sunshine.",
"max_bitrate": "Tốc độ bit tối đa",
"max_bitrate_desc": "Tốc độ bit tối đa (đơn vị Kbps) mà Sunshine sẽ mã hóa luồng. Nếu đặt thành 0, nó sẽ luôn sử dụng tốc độ bit được yêu cầu bởi Moonlight.",
"minimum_fps_target": "Mục tiêu FPS tối thiểu",
"minimum_fps_target_desc": "Tốc độ khung hình hiệu quả thấp nhất mà luồng có thể đạt được. Giá trị 0 được coi là khoảng một nửa tốc độ khung hình của luồng. Nên thiết lập giá trị 20 nếu bạn phát nội dung có tốc độ khung hình 24 hoặc 30fps.",
"min_log_level": "Mức độ ghi nhật ký",
"min_log_level_0": "Chi tiết",
"min_log_level_1": "Gỡ lỗi",
"min_log_level_2": "Thông tin",
"min_log_level_3": "Cảnh báo",
"min_log_level_4": "Lỗi",
"min_log_level_5": "Chết người",
"min_log_level_6": "Không có",
"min_log_level_desc": "Mức ghi nhật ký tối thiểu được in ra tiêu chuẩn đầu ra.",
"min_threads": "Số luồng CPU tối thiểu",
"min_threads_desc": "Tăng giá trị một chút sẽ làm giảm hiệu suất mã hóa, nhưng sự đánh đổi này thường đáng giá để tận dụng thêm các lõi CPU cho quá trình mã hóa. Giá trị lý tưởng là giá trị thấp nhất có thể mã hóa một cách đáng tin cậy ở cài đặt phát trực tuyến mong muốn trên phần cứng của bạn.",
"misc": "Các tùy chọn khác",
"motion_as_ds4": "Mô phỏng tay cầm DS4 nếu tay cầm của client báo cáo có cảm biến chuyển động.",
"motion_as_ds4_desc": "Nếu bị vô hiệu hóa, cảm biến chuyển động sẽ không được tính đến trong quá trình chọn loại gamepad.",
"mouse": "Bật nhập liệu chuột",
"mouse_desc": "Cho phép khách truy cập điều khiển hệ thống chủ bằng chuột.",
"native_pen_touch": "Hỗ trợ bút cảm ứng và chạm gốc",
"native_pen_touch_desc": "Khi được bật, Sunshine sẽ truyền các sự kiện bút/chạm gốc từ các ứng dụng Moonlight. Tính năng này có thể hữu ích để tắt cho các ứng dụng cũ không hỗ trợ bút/chạm gốc.",
"notify_pre_releases": "Thông báo trước khi phát hành",
"notify_pre_releases_desc": "Có muốn nhận thông báo về các phiên bản thử nghiệm mới của Sunshine không?",
"nvenc_h264_cavlc": "Ưu tiên CAVLC hơn CABAC trong H.264",
"nvenc_h264_cavlc_desc": "Hình thức đơn giản hơn của mã hóa entropy. CAVLC cần khoảng 10% băng thông bit cao hơn để đạt được chất lượng tương đương. Chỉ áp dụng cho các thiết bị giải mã rất cũ.",
"nvenc_latency_over_power": "Ưu tiên độ trễ mã hóa thấp hơn so với tiết kiệm năng lượng.",
"nvenc_latency_over_power_desc": "Sunshine yêu cầu tốc độ đồng hồ GPU tối đa khi phát trực tiếp để giảm độ trễ mã hóa. Việc tắt tính năng này không được khuyến nghị vì có thể dẫn đến độ trễ mã hóa tăng đáng kể.",
"nvenc_opengl_vulkan_on_dxgi": "Hiển thị OpenGL/Vulkan trên nền DXGI",
"nvenc_opengl_vulkan_on_dxgi_desc": "Ánh sáng mặt trời không thể ghi lại các chương trình OpenGL và Vulkan toàn màn hình ở tốc độ khung hình đầy đủ trừ khi chúng được hiển thị trên DXGI. Đây là cài đặt hệ thống và sẽ được khôi phục lại sau khi chương trình Ánh sáng mặt trời kết thúc.",
"nvenc_preset": "Cài đặt sẵn hiệu suất",
"nvenc_preset_1": "(nhanh nhất, mặc định)",
"nvenc_preset_7": "(chậm nhất)",
"nvenc_preset_desc": "Các giá trị cao hơn cải thiện tỷ lệ nén (chất lượng ở cùng bitrate) nhưng làm tăng độ trễ mã hóa. Nên thay đổi chỉ khi bị giới hạn bởi mạng hoặc bộ giải mã, nếu không, hiệu quả tương tự có thể đạt được bằng cách tăng bitrate.",
"nvenc_realtime_hags": "Sử dụng ưu tiên thời gian thực trong lịch trình GPU được tăng tốc phần cứng.",
"nvenc_realtime_hags_desc": "Hiện tại, trình điều khiển NVIDIA có thể bị treo trong trình mã hóa khi tùy chọn HAGS được bật, ưu tiên thời gian thực được sử dụng và sử dụng VRAM gần đạt mức tối đa. Tắt tùy chọn này sẽ hạ ưu tiên xuống mức cao, tránh tình trạng treo nhưng làm giảm hiệu suất ghi hình khi GPU đang hoạt động nặng.",
"nvenc_spatial_aq": "Chất lượng không khí theo không gian",
"nvenc_spatial_aq_desc": "Gán giá trị QP cao hơn cho các vùng phẳng trong video. Được khuyến nghị bật khi phát trực tuyến ở tốc độ bit thấp.",
"nvenc_twopass": "Chế độ hai lần quét",
"nvenc_twopass_desc": "Thêm bước mã hóa sơ bộ. Điều này cho phép phát hiện nhiều vector chuyển động hơn, phân phối bitrate đều hơn trong khung hình và tuân thủ nghiêm ngặt hơn các giới hạn bitrate. Không nên tắt tính năng này vì có thể dẫn đến việc vượt quá bitrate tạm thời và mất gói dữ liệu sau đó.",
"nvenc_twopass_disabled": "Tắt (nhanh nhất, không được khuyến nghị)",
"nvenc_twopass_full_res": "Độ phân giải cao (chậm hơn)",
"nvenc_twopass_quarter_res": "Độ phân giải theo quý (nhanh hơn, mặc định)",
"nvenc_vbv_increase": "Tỷ lệ phần trăm tăng của VBV/HRD trong một khung hình",
"nvenc_vbv_increase_desc": "Theo mặc định, Sunshine sử dụng VBV/HRD một khung hình, có nghĩa là kích thước khung hình video đã mã hóa không được vượt quá tỷ lệ bit yêu cầu chia cho tần số khung hình yêu cầu. Nới lỏng hạn chế này có thể mang lại lợi ích và hoạt động như bitrate biến đổi độ trễ thấp, nhưng cũng có thể dẫn đến mất gói nếu mạng không có dung lượng đệm đủ để xử lý các đỉnh bitrate. Giá trị tối đa được chấp nhận là 400, tương ứng với giới hạn kích thước khung hình video đã mã hóa tăng gấp 5 lần.",
"origin_web_ui_allowed": "Giao diện người dùng web gốc được phép",
"origin_web_ui_allowed_desc": "Nguồn gốc của địa chỉ điểm cuối từ xa không bị từ chối truy cập vào giao diện người dùng web (Web UI).",
"origin_web_ui_allowed_lan": "Chỉ những người trong mạng LAN mới có thể truy cập giao diện người dùng web.",
"origin_web_ui_allowed_pc": "Chỉ máy chủ cục bộ (localhost) mới có thể truy cập giao diện người dùng web (Web UI).",
"origin_web_ui_allowed_wan": "Bất kỳ ai cũng có thể truy cập giao diện người dùng web (Web UI).",
"output_name": "ID hiển thị",
"output_name_desc_unix": "Trong quá trình khởi động Sunshine, bạn sẽ thấy danh sách các màn hình được phát hiện. Lưu ý: Bạn cần sử dụng giá trị ID bên trong dấu ngoặc đơn. Dưới đây là một ví dụ; kết quả thực tế có thể được tìm thấy trong tab Khắc phục sự cố.",
"output_name_desc_windows": "Chỉ định thủ công ID thiết bị hiển thị để sử dụng cho việc ghi hình. Nếu không được thiết lập, thiết bị hiển thị chính sẽ được ghi hình. Lưu ý: Nếu bạn đã chỉ định GPU ở trên, thiết bị hiển thị này phải được kết nối với GPU đó. Trong quá trình khởi động Sunshine, bạn sẽ thấy danh sách các thiết bị hiển thị được phát hiện. Dưới đây là một ví dụ; kết quả thực tế có thể được tìm thấy trong tab Khắc phục sự cố.",
"ping_timeout": "Thời gian chờ ping",
"ping_timeout_desc": "Thời gian chờ (tính bằng mili giây) trước khi ngừng truyền dữ liệu từ Moonlight.",
"pkey": "Khóa riêng",
"pkey_desc": "Khóa riêng tư được sử dụng cho việc ghép nối giữa giao diện web và ứng dụng Moonlight. Để đảm bảo tương thích tốt nhất, khóa riêng tư này nên là khóa RSA-2048.",
"port": "Cảng",
"port_alert_1": "Sunshine không thể sử dụng các cổng dưới 1024!",
"port_alert_2": "Các cổng trên 65535 không khả dụng!",
"port_desc": "Đặt nhóm cổng được sử dụng bởi Sunshine",
"port_http_port_note": "Sử dụng cổng này để kết nối với Moonlight.",
"port_note": "Lưu ý",
"port_port": "Cảng",
"port_protocol": "Quy trình",
"port_tcp": "Giao thức truyền tải liên kết (TCP)",
"port_udp": "UDP (Giao thức dữ liệu không định hướng)",
"port_warning": "Việc phơi bày giao diện người dùng web (Web UI) ra internet là một rủi ro bảo mật! Hãy tiếp tục với rủi ro của riêng bạn!",
"port_web_ui": "Giao diện người dùng web",
"qp": "Tham số lượng tử hóa",
"qp_desc": "Một số thiết bị có thể không hỗ trợ Tốc độ bit cố định (Constant Bit Rate). Đối với những thiết bị này, QP (Quality Profile) sẽ được sử dụng thay thế. Giá trị cao hơn có nghĩa là nén nhiều hơn, nhưng chất lượng sẽ thấp hơn.",
"qsv_coder": "QuickSync Coder (H.264)",
"qsv_preset": "Cài đặt nhanh QuickSync",
"qsv_preset_fast": "Nhanh (chất lượng thấp)",
"qsv_preset_faster": "nhanh hơn (chất lượng thấp hơn)",
"qsv_preset_medium": "Trung bình (mặc định)",
"qsv_preset_slow": "chậm (chất lượng tốt)",
"qsv_preset_slower": "chậm hơn (chất lượng tốt hơn)",
"qsv_preset_slowest": "chậm nhất (chất lượng tốt nhất)",
"qsv_preset_veryfast": "nhanh nhất (chất lượng thấp nhất)",
"qsv_slow_hevc": "Cho phép mã hóa HEVC chậm",
"qsv_slow_hevc_desc": "Điều này có thể cho phép mã hóa HEVC trên các GPU Intel cũ hơn, nhưng sẽ làm tăng sử dụng GPU và giảm hiệu suất.",
"restart_note": "Sunshine đang khởi động lại để áp dụng các thay đổi.",
"stream_audio": "Phát trực tiếp âm thanh",
"stream_audio_desc": "Có nên phát âm thanh hay không. Tắt tính năng này có thể hữu ích khi phát video trên các màn hình không có giao diện người dùng (headless displays) như màn hình phụ.",
"sunshine_name": "Tên Ánh Dương",
"sunshine_name_desc": "Tên hiển thị bởi Moonlight. Nếu không được chỉ định, tên máy chủ của PC sẽ được sử dụng.",
"sw_preset": "Cài đặt sẵn cho SW",
"sw_preset_desc": "Tối ưu hóa sự cân bằng giữa tốc độ mã hóa (số khung hình được mã hóa mỗi giây) và hiệu quả nén (chất lượng trên mỗi bit trong luồng bit). Mặc định là siêu nhanh.",
"sw_preset_fast": "nhanh",
"sw_preset_faster": "nhanh hơn",
"sw_preset_medium": "trung bình",
"sw_preset_slow": "chậm",
"sw_preset_slower": "chậm hơn",
"sw_preset_superfast": "siêu nhanh (mặc định)",
"sw_preset_ultrafast": "siêu nhanh",
"sw_preset_veryfast": "rất nhanh",
"sw_preset_veryslow": "rất chậm",
"sw_tune": "Điều chỉnh phần mềm",
"sw_tune_animation": "Hoạt hình -- phù hợp cho phim hoạt hình; sử dụng thuật toán giảm nhiễu cao hơn và nhiều khung tham chiếu hơn.",
"sw_tune_desc": "Các tùy chọn điều chỉnh, được áp dụng sau khi thiết lập trước. Mặc định là zerolatency.",
"sw_tune_fastdecode": "fastdecode -- cho phép giải mã nhanh hơn bằng cách vô hiệu hóa một số bộ lọc.",
"sw_tune_film": "Phim -- dùng cho nội dung phim chất lượng cao; giảm hiện tượng vỡ khối.",
"sw_tune_grain": "hạt -- giữ nguyên cấu trúc hạt trong vật liệu phim cũ, có hạt.",
"sw_tune_stillimage": "Hình ảnh tĩnh -- Phù hợp cho nội dung dạng trình chiếu.",
"sw_tune_zerolatency": "zerolatency -- phù hợp cho mã hóa nhanh và phát trực tuyến có độ trễ thấp (mặc định)",
"system_tray": "Bật khay hệ thống",
"system_tray_desc": "Hiển thị biểu tượng trong khay hệ thống và hiển thị thông báo trên màn hình desktop.",
"touchpad_as_ds4": "Mô phỏng tay cầm DS4 nếu tay cầm của client báo có bàn di chuột.",
"touchpad_as_ds4_desc": "Nếu tính năng này bị tắt, sự hiện diện của bàn di chuột sẽ không được xem xét trong quá trình chọn loại gamepad.",
"upnp": "UPnP (Tự động phát hiện và chia sẻ thiết bị)",
"upnp_desc": "Tự động cấu hình chuyển tiếp cổng để phát trực tuyến qua Internet.",
"vaapi_strict_rc_buffer": "Thực thi nghiêm ngặt giới hạn tốc độ khung hình cho H.264/HEVC trên GPU AMD.",
"vaapi_strict_rc_buffer_desc": "Bật tùy chọn này có thể tránh tình trạng mất khung hình trên mạng trong quá trình chuyển cảnh, nhưng chất lượng video có thể bị giảm trong quá trình chuyển động.",
"virtual_sink": "Bồn rửa ảo",
"virtual_sink_desc": "Chỉ định thủ công thiết bị âm thanh ảo để sử dụng. Nếu không được thiết lập, thiết bị sẽ được chọn tự động. Chúng tôi khuyến nghị mạnh mẽ để để trống trường này để sử dụng tính năng chọn thiết bị tự động!",
"virtual_sink_placeholder": "Loa phát trực tuyến Steam",
"vt_coder": "VideoToolbox Coder",
"vt_realtime": "VideoToolbox Mã hóa thời gian thực",
"vt_software": "Phần mềm VideoToolbox cho mã hóa video",
"vt_software_allowed": "Được phép",
"vt_software_forced": "Bắt buộc",
"wan_encryption_mode": "Chế độ mã hóa WAN",
"wan_encryption_mode_1": "Được kích hoạt cho các khách hàng được hỗ trợ (mặc định)",
"wan_encryption_mode_2": "Yêu cầu đối với tất cả khách hàng",
"wan_encryption_mode_desc": "Điều này xác định thời điểm mã hóa sẽ được sử dụng khi truyền phát qua Internet. Mã hóa có thể làm giảm hiệu suất truyền phát, đặc biệt là trên các máy chủ và thiết bị khách có cấu hình yếu."
},
"index": {
"description": "Sunshine là một nền tảng phát trực tiếp game tự chủ cho Moonlight.",
"download": "Tải xuống",
"installed_version_not_stable": "Bạn đang sử dụng phiên bản thử nghiệm của Sunshine. Bạn có thể gặp phải lỗi hoặc các vấn đề khác. Vui lòng báo cáo bất kỳ vấn đề nào bạn gặp phải. Cảm ơn bạn đã giúp Sunshine trở thành phần mềm tốt hơn!",
"loading_latest": "Đang tải phiên bản mới nhất...",
"new_pre_release": "Phiên bản thử nghiệm mới đã có sẵn!",
"new_stable": "Phiên bản ổn định mới đã có sẵn!",
"startup_errors": "<b>Lưu ý!</b> Sunshine đã phát hiện các lỗi sau đây trong quá trình khởi động. Chúng tôi <b>KHUYẾN NGHỊ MẠNH MẼ bạn</b> khắc phục các lỗi này trước khi bắt đầu phát trực tuyến.",
"version_dirty": "Cảm ơn bạn đã giúp Sunshine trở thành phần mềm tốt hơn!",
"version_latest": "Bạn đang sử dụng phiên bản mới nhất của Sunshine.",
"welcome": "Chào nắng!"
},
"navbar": {
"applications": "Ứng dụng",
"configuration": "Cấu hình",
"home": "Trang chủ",
"password": "Thay đổi mật khẩu",
"pin": "Mã PIN",
"theme_auto": "Tự động",
"theme_dark": "Tối",
"theme_light": "Ánh sáng",
"toggle_theme": "Chủ đề",
"troubleshoot": "Khắc phục sự cố"
},
"password": {
"confirm_password": "Xác nhận mật khẩu",
"current_creds": "Chứng chỉ hiện tại",
"new_creds": "Chứng chỉ mới",
"new_username_desc": "Nếu không được chỉ định, tên người dùng sẽ không thay đổi.",
"password_change": "Thay đổi mật khẩu",
"success_msg": "Mật khẩu đã được thay đổi thành công! Trang này sẽ được tải lại trong giây lát, trình duyệt của bạn sẽ yêu cầu bạn nhập thông tin đăng nhập mới."
},
"pin": {
"device_name": "Tên thiết bị",
"pair_failure": "Kết nối không thành công: Vui lòng kiểm tra xem mã PIN đã được nhập chính xác chưa.",
"pair_success": "Thành công! Vui lòng kiểm tra Moonlight để tiếp tục.",
"pin_pairing": "Kết nối PIN",
"send": "Gửi",
"warning_msg": "Đảm bảo bạn có quyền truy cập vào máy tính mà bạn đang kết nối. Phần mềm này có thể cho phép kiểm soát hoàn toàn máy tính của bạn, vì vậy hãy cẩn thận!"
},
"resource_card": {
"github_discussions": "Thảo luận trên GitHub",
"legal": "Pháp lý",
"legal_desc": "Bằng cách tiếp tục sử dụng phần mềm này, bạn đồng ý với các điều khoản và điều kiện được quy định trong các tài liệu sau đây.",
"license": "Giấy phép",
"lizardbyte_website": "Trang web LizardByte",
"resources": "Tài nguyên",
"resources_desc": "Tài nguyên cho Ánh nắng!",
"third_party_notice": "Thông báo từ bên thứ ba"
},
"troubleshooting": {
"dd_reset": "Đặt lại cài đặt thiết bị hiển thị cố định",
"dd_reset_desc": "Nếu Sunshine gặp sự cố khi cố gắng khôi phục cài đặt thiết bị hiển thị đã thay đổi, bạn có thể đặt lại cài đặt và tiếp tục khôi phục trạng thái hiển thị thủ công.",
"dd_reset_error": "Lỗi xảy ra trong quá trình khôi phục trạng thái lưu trữ!",
"dd_reset_success": "Thành công trong việc thiết lập lại sự kiên trì!",
"force_close": "Buộc đóng ứng dụng",
"force_close_desc": "Nếu Moonlight báo lỗi về một ứng dụng đang chạy, việc buộc đóng ứng dụng đó sẽ khắc phục sự cố.",
"force_close_error": "Lỗi khi đóng ứng dụng",
"force_close_success": "Đơn đăng ký đã được đóng thành công!",
"logs": "Nhật ký",
"logs_desc": "Xem các bản ghi được tải lên bởi Sunshine",
"logs_find": "Tìm...",
"restart_sunshine": "Khởi động lại Sunshine",
"restart_sunshine_desc": "Nếu Sunshine không hoạt động đúng cách, bạn có thể thử khởi động lại ứng dụng. Điều này sẽ kết thúc tất cả các phiên đang chạy.",
"restart_sunshine_success": "Sunshine đang khởi động lại.",
"troubleshooting": "Khắc phục sự cố",
"unpair_all": "Bỏ ghép tất cả",
"unpair_all_error": "Lỗi khi hủy ghép nối",
"unpair_all_success": "Tất cả các thiết bị đã ngắt kết nối.",
"unpair_desc": "Hãy ngắt kết nối các thiết bị đã ghép nối. Các thiết bị đã ghép nối nhưng đang có phiên hoạt động sẽ vẫn kết nối, nhưng không thể bắt đầu hoặc tiếp tục phiên.",
"unpair_single_no_devices": "Không có thiết bị nào được ghép đôi.",
"unpair_single_success": "Tuy nhiên, thiết bị (các thiết bị) có thể vẫn đang trong phiên hoạt động. Nhấn nút 'Buộc đóng' ở trên để kết thúc tất cả các phiên đang mở.",
"unpair_single_unknown": "Khách hàng không xác định",
"unpair_title": "Ngắt kết nối thiết bị"
},
"welcome": {
"confirm_password": "Xác nhận mật khẩu",
"create_creds": "Trước khi bắt đầu, chúng tôi cần bạn tạo một tên người dùng và mật khẩu mới để truy cập vào giao diện người dùng web (Web UI).",
"create_creds_alert": "Các thông tin đăng nhập sau đây là cần thiết để truy cập giao diện người dùng web của Sunshine. Hãy giữ chúng an toàn, vì bạn sẽ không bao giờ thấy chúng nữa!",
"greeting": "Chào mừng đến với Sunshine!",
"login": "Đăng nhập",
"welcome_success": "Trang này sẽ được tải lại trong giây lát, trình duyệt của bạn sẽ yêu cầu bạn nhập lại thông tin đăng nhập."
}
}

View File

@@ -18,7 +18,7 @@
"note": "請注意:",
"password": "密碼",
"run_as": "以系統管理員身份執行",
"save": "節省",
"save": "儲存",
"see_more": "查看更多資訊",
"success": "成功!",
"undo_cmd": "復原指令",
@@ -89,9 +89,9 @@
"adapter_name_desc_windows": "手動指定用於擷取的 GPU。如果未設定系統會自動選擇 GPU。我們強烈建議保持此欄位為空以使用自動 GPU 選擇!注意:此 GPU 必須已連接顯示器並開啟電源。可以使用以下指令來查找適當的值:",
"adapter_name_placeholder_windows": "Radeon RX 580 系列",
"add": "新增",
"address_family": "地址家庭",
"address_family": "位址族群",
"address_family_both": "IPv4+IPv6",
"address_family_desc": "設定 Sunshine 使用的位址族",
"address_family_desc": "設定 Sunshine 使用的位址族",
"address_family_ipv4": "僅 IPv4",
"always_send_scancodes": "永遠傳送掃描碼",
"always_send_scancodes_desc": "傳送掃描碼可以增強與遊戲和應用程式的相容性,但可能會導致某些未使用美式英語鍵盤佈局的用戶端輸入錯誤。若某些應用程式的鍵盤輸入完全無效,請啟用此選項。若用戶端的鍵盤輸入在主機端產生錯誤輸入,則請停用此選項。",
@@ -110,7 +110,7 @@
"amd_rc": "AMF 速率控制",
"amd_rc_cbr": "cbr—固定位元率如果啟用 HRD建議使用",
"amd_rc_cqp": "cqp—常數 qp 模式",
"amd_rc_desc": "這個選項控制了速率控制方法,確保不超過戶端的位元率目標。'cqp' 不適用於位元率目標設定,除了 'vbr_latency' 外,其他選項依賴 HRD 強制執行來幫助限制位元率溢出。",
"amd_rc_desc": "這個選項控制了速率控制方法,確保不超過戶端的位元率目標。'cqp' 不適用於位元率目標設定,除了 'vbr_latency' 外,其他選項依賴 HRD 強制執行來幫助限制位元率溢出。",
"amd_rc_group": "AMF 速率控制設定",
"amd_rc_vbr_latency": "vbr_latency—受延遲限制的可變位元率如果停用 HRD建議使用此選項預設",
"amd_rc_vbr_peak": "vbr_peak—峰值受限的可變位元率",
@@ -140,8 +140,8 @@
"back_button_timeout_desc": "如果按住 Back/Select 按鈕達到指定的毫秒數,系統會模擬 Home/Guide 按鈕的按下動作。若設定為小於 0預設值則按住 Back/Select 按鈕不會模擬 Home/Guide 按鈕。",
"capture": "強制使用特定的擷取方式",
"capture_desc": "在自動模式下Sunshine 會使用第一個有效的驅動程式。NvFBC 需要已修補的 nvidia 驅動程式。",
"cert": "證",
"cert_desc": "用於網頁 UI 和 Moonlight 戶端配對的私鑰。為了確保最佳相容性,使用 RSA-2048 鑰。",
"cert": "證",
"cert_desc": "用於 Web UI 和 Moonlight 戶端配對的憑證。為了確保最佳相容性,建議使用 RSA-2048 鑰。",
"channels": "最大連線用戶端數量",
"channels_desc_1": "Sunshine 可讓單一串流工作階段同時與多個裝置共享。",
"channels_desc_2": "某些硬體編碼器可能會因多重串流而受到性能限制。",
@@ -155,7 +155,7 @@
"dd_config_ensure_active": "自動啟用顯示器",
"dd_config_ensure_only_display": "停用其他顯示器,並只啟用指定的顯示器",
"dd_config_ensure_primary": "自動啟用顯示器並設定為主要顯示器",
"dd_config_label": "裝置設定",
"dd_configuration_option": "裝置組態",
"dd_config_revert_delay": "設定回復延遲",
"dd_config_revert_delay_desc": "當應用程式關閉或最後一個工作階段結束時,將會額外等待的延遲時間再恢復設定,以毫秒為單位。這樣做的主要目的是讓在快速切換應用程式時能夠更順暢。",
"dd_config_revert_on_disconnect": "斷線時恢復設定",
@@ -164,6 +164,8 @@
"dd_hdr_option": "HDR",
"dd_hdr_option_auto": "根據用戶端的要求開啟/關閉 HDR 模式(預設值)",
"dd_hdr_option_disabled": "不更改 HDR 設定",
"dd_manual_refresh_rate": "手動更新率",
"dd_manual_resolution": "手動解析度",
"dd_mode_remapping": "顯示模式重新映射",
"dd_mode_remapping_add": "新增重新映射項目",
"dd_mode_remapping_desc_1": "指定重新映射項目以將請求的解析度和/或更新率更改為其他值。",
@@ -182,19 +184,19 @@
"dd_refresh_rate_option_auto": "使用用戶端提供的 FPS 值(預設)",
"dd_refresh_rate_option_disabled": "不變更更新率",
"dd_refresh_rate_option_manual": "使用手動輸入的更新率",
"dd_refresh_rate_option_manual_desc": "請輸入要使用的更新率",
"dd_resolution_option": "解析度",
"dd_resolution_option_auto": "使用戶端提供的解析度(預設)",
"dd_resolution_option_auto": "使用戶端提供的解析度(預設)",
"dd_resolution_option_disabled": "不更改解析度",
"dd_resolution_option_manual": "使用手動輸入的解析度",
"dd_resolution_option_manual_desc": "請輸入要使用的解析度",
"dd_resolution_option_ogs_desc": "必須在 Moonlight 客戶端啟用「最佳化遊戲設定」選項,才能讓這個功能正常運作。",
"dd_resolution_option_ogs_desc": "必須在 Moonlight 用戶端啟用「最佳化遊戲設定」選項,才能讓這個功能正常運作。",
"dd_wa_hdr_toggle_delay_desc_1": "使用虛擬顯示裝置 (VDD) 進行串流時,可能會不正確顯示 HDR 顏色。陽光可以嘗試關閉 HDR然後再開啟以減少此問題。",
"dd_wa_hdr_toggle_delay_desc_2": "如果該值設為 0則會停用變通預設。如果值介於 0 和 3000 毫秒之間Sunshine 會關閉 HDR等待指定的時間然後再開啟 HDR。在大多數情況下建議的延遲時間約為 500 毫秒。",
"dd_wa_hdr_toggle_delay_desc_3": "除非您真的有 HDR 問題,否則請勿使用此變通技術,因為它會直接影響串流的啟動時間!",
"dd_wa_hdr_toggle_delay": "HDR 的高對比度解決方案",
"ds4_back_as_touchpad_click": "地圖 Back/Select 至觸控板點選",
"ds4_back_as_touchpad_click_desc": "當強制啟用 DS4 模擬時,將返回/選擇按鈕映射為觸控板點擊",
"ds5_inputtino_randomize_mac": "隨機化虛擬控制器 MAC",
"ds5_inputtino_randomize_mac_desc": "控制器註冊時,使用隨機 MAC 而非控制器內部索引,以避免在用戶端交換控制器時混淆不同控制器的組態設定。",
"encoder": "強制指定編碼器",
"encoder_desc": "強制指定特定的編碼器,否則 Sunshine 將選擇最佳的可用選項。注意:如果您在 Windows 上指定硬體編碼器,則必須與顯示器連接的 GPU 符合。",
"encoder_software": "軟體",
@@ -213,6 +215,7 @@
"gamepad_ds4": "DS4 (PS4)",
"gamepad_ds4_manual": "DS4 選擇選項",
"gamepad_ds5": "DS5 (PS5)",
"gamepad_ds5_manual": "DS5 選擇選項",
"gamepad_switch": "Nintendo Pro (Switch)",
"gamepad_manual": "手動 DS4 選項",
"gamepad_x360": "X360 (Xbox 360)",
@@ -235,31 +238,34 @@
"key_repeat_frequency_desc": "每秒按鍵重複的頻率。此選項支援小數點。",
"key_rightalt_to_key_win": "將右 Alt 鍵映射為 Windows 鍵",
"key_rightalt_to_key_win_desc": "Moonlight 可能無法直接發送 Windows 鍵。在這種情況下,讓 Sunshine 認為右 Alt 鍵是 Windows 鍵可能會很有用",
"keybindings": "鍵盤綁定",
"keyboard": "啟用鍵盤輸入",
"keyboard_desc": "允許訪客使用鍵盤控制主機系統",
"lan_encryption_mode": "區域網路加密模式",
"lan_encryption_mode_1": "當用戶端支援時啟用",
"lan_encryption_mode_2": "所有用戶端都需要",
"lan_encryption_mode_desc": "這會決定在本地網路上進行串流時何時使用加密。加密可能會降低串流效能,特別是在較不強大的主機和戶端上。",
"lan_encryption_mode_desc": "這會決定在本地網路上進行串流時何時使用加密。加密可能會降低串流效能,特別是在較不強大的主機和戶端上。",
"locale": "語系",
"locale_desc": "Sunshine 使用的使用者介面語言設定。",
"log_level": "日誌層級",
"log_level_0": "詳細",
"log_level_1": "除錯",
"log_level_2": "資訊",
"log_level_3": "警告",
"log_level_4": "錯誤",
"log_level_5": "嚴重錯誤",
"log_level_6": "無",
"log_level_desc": "列印到標準輸出的最小日誌層級",
"log_path": "記錄檔路徑",
"log_path_desc": "儲存目前 Sunshine 記錄的檔案。",
"max_bitrate": "最大位元率",
"max_bitrate_desc": "Sunshine 會以最大位元率(單位為 Kbps來編碼串流。如果設為0則會使用Moonlight所要求的位元率。",
"min_threads": "最低 CPU 線程數",
"minimum_fps_target": "最低 FPS 目標",
"minimum_fps_target_desc": "串流可達到的最低有效 FPS。0 的值會被視為串流 FPS 的一半左右。如果您串流 24 或 30fps 的內容,建議設定為 20。",
"min_log_level": "日誌層級",
"min_log_level_0": "繁體",
"min_log_level_1": "除錯",
"min_log_level_2": "資訊",
"min_log_level_3": "警告",
"min_log_level_4": "錯誤",
"min_log_level_5": "致命",
"min_log_level_6": "無",
"min_log_level_desc": "列印到標準輸出的最小記錄層級",
"min_threads": "最低 CPU 執行緒數",
"min_threads_desc": "增加該值會稍微降低編碼效率,但為了能使用更多 CPU 核心進行編碼,這樣的折衷通常是值得的。理想的值是在您的硬體上,能以您所需的串流設定進行可靠編碼的最低值。",
"misc": "其他選項",
"motion_as_ds4": "如果戶端的遊戲手把報告有運動感應器,則會模擬 DS4 遊戲手把",
"motion_as_ds4": "如果戶端的遊戲手把報告有運動感應器,則會模擬 DS4 遊戲手把",
"motion_as_ds4_desc": "如果禁用,則在選擇遊戲手把類型時不會考慮運動感應器的存在。",
"mouse": "啟用滑鼠輸入",
"mouse_desc": "允許訪客使用滑鼠控制主機系統",
@@ -288,31 +294,30 @@
"nvenc_twopass_quarter_res": "四分之一解析度(更快,預設值)",
"nvenc_vbv_increase": "單幅 VBV/HRD 百分比增加",
"nvenc_vbv_increase_desc": "預設 sunshine 使用單幀 VBV/HRD這表示任何編碼視訊幀大小都不會超過要求的位元率除以要求的幀速率。放寬此限制可能有益並可作為低延遲的可變位元率但如果網路沒有緩衝空間處理比特率峰值也可能導致封包遺失。可接受的最大值是 400相當於 5 倍增加的編碼視訊畫格上限。",
"origin_web_ui_allowed": "允許使用 Origin 網頁 UI",
"origin_web_ui_allowed_desc": "用於網頁 UI 和 Moonlight 用戶端配對的憑證。為了確保最佳相容性,建議使用 RSA-2048 公開金鑰。",
"origin_web_ui_allowed_lan": "只有區域網路中的人可以存取網頁 UI",
"origin_web_ui_allowed_pc": "只有 localhost 可以存取網頁 UI",
"origin_web_ui_allowed_wan": "任何人都可以存取網頁 UI",
"origin_web_ui_allowed": "允許存取 Web UI 的來源",
"origin_web_ui_allowed_desc": "未被拒絕存取 Web UI 的遠端端點位址來源",
"origin_web_ui_allowed_lan": "只有區域網路中的人可以存取 Web UI",
"origin_web_ui_allowed_pc": "只有 localhost 可以存取 Web UI",
"origin_web_ui_allowed_wan": "任何人都可以存取 Web UI",
"output_name": "顯示 ID",
"output_name_desc_unix": "在 Sunshine 啟動時,您應該會看到檢測到的顯示器清單。請注意:需要使用括弧內的 ID 值。以下是範例,實際輸出可以在「故障排除」分頁中找到。",
"output_name_desc_windows": "手動指定要用於擷取的顯示器設備 ID。如果未設定則擷取主要顯示器。注意如果您在上方指定了 GPU則此顯示器必須連接到該 GPU。在 Sunshine 啟動時,您應該會看到檢測到的顯示器清單。以下是範例,實際輸出可以在「故障排除」分頁中找到。",
"output_name_unix": "顯示號碼",
"output_name_windows": "顯示裝置 ID",
"ping_timeout": "Ping 逾時",
"ping_timeout_desc": "在關閉串流前,等待來自 Moonlight 的資料,以毫秒為單位",
"pkey": "私人密碼匙",
"pkey_desc": "用於網頁 UI 和 Moonlight 戶端配對的私鑰。為了確保最佳相容性,建議使用 RSA-2048 私鑰。",
"pkey": "私人金鑰",
"pkey_desc": "用於 Web UI 和 Moonlight 戶端配對的私鑰。為了確保最佳相容性,建議使用 RSA-2048 私鑰。",
"port": "連接埠",
"port_alert_1": "Sunshine 不能使用低於 1024 的連接埠!",
"port_alert_2": "65535 以上的連接埠無法使用!",
"port_desc": "設定 Sunshine 使用的連接埠系列",
"port_desc": "設定 Sunshine 使用的連接埠範圍",
"port_http_port_note": "使用此連接埠與 Moonlight 連線。",
"port_note": "注意事項",
"port_port": "連接埠",
"port_protocol": "通訊協定",
"port_tcp": "TCP",
"port_udp": "UDP",
"port_warning": "將網頁 UI 暴露於網際網路存在安全風險!請自行承擔風險!",
"port_web_ui": "網頁 UI",
"port_warning": "將 Web UI 暴露於網際網路存在安全風險!請自行承擔風險!",
"port_web_ui": "Web UI",
"qp": "量化參數",
"qp_desc": "某些裝置可能不支援 Constant Bit Rate。對於這些裝置會使用 QP 來取代。值越高表示壓縮越多,但品質越低。",
"qsv_coder": "QuickSync 編碼器H264",
@@ -350,6 +355,8 @@
"sw_tune_grain": "grain—保留老電影畫面的顆粒結構",
"sw_tune_stillimage": "stillimage—適用於類似投影片的內容。",
"sw_tune_zerolatency": "zerolatency—適合快速編碼和低延遲串流預設值",
"system_tray": "啟用系統匣",
"system_tray_desc": "在系統匣中顯示圖示,並顯示桌面通知",
"touchpad_as_ds4": "當用戶端的遊戲手把報告存在觸控板時,模擬 DS4 遊戲手把",
"touchpad_as_ds4_desc": "若停用,選擇遊戲手把類型時將不會考慮觸控板的存在。",
"upnp": "UPnP",
@@ -366,7 +373,7 @@
"vt_software_forced": "強制",
"wan_encryption_mode": "WAN 加密模式",
"wan_encryption_mode_1": "對支援的用戶端啟用(預設)",
"wan_encryption_mode_2": "所有客戶都需要",
"wan_encryption_mode_2": "所有用戶端都需要",
"wan_encryption_mode_desc": "這會決定在網際網路上串流時,何時會使用加密。加密可能會降低串流效能,尤其是在效能較低的主機和用戶端上。"
},
"index": {
@@ -446,8 +453,8 @@
},
"welcome": {
"confirm_password": "確認密碼",
"create_creds": "在開始之前,我們需要您建立新的使用者名稱和密碼,以便存取網頁 UI。",
"create_creds_alert": "以下的憑證是存取 Sunshine 網頁介面所需的。請妥善保管,因為您將無法再查看這些憑證!",
"create_creds": "在開始之前,我們需要您建立新的使用者名稱和密碼,以便存取 Web UI。",
"create_creds_alert": "存取 Sunshine 的 Web UI 需要以下憑證。請妥善保管,因為您將無法再查看這些憑證!",
"greeting": "歡迎來到 Sunshine",
"login": "登入",
"welcome_success": "此頁面將很快重新載入,您的瀏覽器會要求您提供新的憑證"

View File

@@ -0,0 +1,2 @@
# Sunshine needs uhid for DS5 emulation
uhid

View File

@@ -5,7 +5,7 @@ KERNEL=="uinput", SUBSYSTEM=="misc", OPTIONS+="static_node=uinput", GROUP="input
KERNEL=="uhid", GROUP="input", MODE="0660", TAG+="uaccess"
# Joypads
KERNEL=="hidraw*" ATTRS{name}=="Sunshine PS5 (virtual) pad" GROUP="input", MODE="0660", TAG+="uaccess"
KERNEL=="hidraw*", ATTRS{name}=="Sunshine PS5 (virtual) pad", GROUP="input", MODE="0660", TAG+="uaccess"
SUBSYSTEMS=="input", ATTRS{name}=="Sunshine X-Box One (virtual) pad", GROUP="input", MODE="0660", TAG+="uaccess"
SUBSYSTEMS=="input", ATTRS{name}=="Sunshine gamepad (virtual) motion sensors", GROUP="input", MODE="0660", TAG+="uaccess"
SUBSYSTEMS=="input", ATTRS{name}=="Sunshine Nintendo (virtual) pad", GROUP="input", MODE="0660", TAG+="uaccess"

View File

@@ -1,5 +1,9 @@
#!/bin/sh
# Load uhid (DS5 emulation)
echo "Loading uhid kernel module for DS5 emulation."
modprobe uhid
# Check if we're in an rpm-ostree environment
if [ ! -x "$(command -v rpm-ostree)" ]; then
echo "Not in an rpm-ostree environment, proceeding with post install steps."

View File

@@ -50,7 +50,7 @@ if exist "%OLD_DIR%\covers\" (
move "%OLD_DIR%\covers" "%NEW_DIR%\"
rem Fix apps.json image path values that point at the old covers directory
powershell -c "(Get-Content '%NEW_DIR%\apps.json').replace('.\/covers\/', '.\/config\/covers\/') | Set-Content '%NEW_DIR%\apps.json'"
powershell -NoProfile -c "(Get-Content '%NEW_DIR%\apps.json').replace('.\/covers\/', '.\/config\/covers\/') | Set-Content '%NEW_DIR%\apps.json'"
)
)

View File

@@ -58,7 +58,7 @@ if exist "%SERVICE_CONFIG_FILE%" (
echo Setting service start type set to: [!SERVICE_START_TYPE!]
rem Run the sc command to create/reconfigure the service
sc %SC_CMD% %SERVICE_NAME% binPath= "%SERVICE_BIN%" start= %SERVICE_START_TYPE% DisplayName= "Apollo Service"
sc %SC_CMD% %SERVICE_NAME% binPath= "\"%SERVICE_BIN%\"" start= %SERVICE_START_TYPE% DisplayName= "Apollo Service"
rem Set the description of the service
sc description %SERVICE_NAME% "Apollo is a self-hosted game stream host for Moonlight."

View File

@@ -37,6 +37,16 @@ set(TEST_DEFINITIONS) # list will be appended as needed
# this indicates we're building tests in case sunshine needs to adjust some code or add private tests
list(APPEND TEST_DEFINITIONS SUNSHINE_TESTS)
list(APPEND TEST_DEFINITIONS SUNSHINE_SOURCE_DIR="${CMAKE_SOURCE_DIR}")
list(APPEND TEST_DEFINITIONS SUNSHINE_TEST_BIN_DIR="${CMAKE_CURRENT_BINARY_DIR}")
if(NOT WIN32)
find_package(Udev 255) # we need 255+ for udevadm verify
message(STATUS "UDEV_FOUND: ${UDEV_FOUND}")
if(UDEV_FOUND)
list(APPEND TEST_DEFINITIONS UDEVADM_EXECUTABLE="${UDEVADM_EXECUTABLE}")
endif()
endif()
file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS
${CMAKE_SOURCE_DIR}/tests/*.h
@@ -52,10 +62,45 @@ add_executable(${PROJECT_NAME}
${TEST_SOURCES}
${SUNSHINE_SOURCES})
# Copy files needed for config consistency tests to build directory
# This ensures both CLI and CLion can access the same files relative to the test executable
# Using configure_file ensures files are copied when they change between builds
set(INTEGRATION_TEST_FILES
"src/config.cpp"
"src_assets/common/assets/web/config.html"
"docs/configuration.md"
"src_assets/common/assets/web/public/assets/locale/en.json"
"src_assets/common/assets/web/configs/tabs/General.vue"
"src_assets/linux/misc/60-sunshine.rules"
)
foreach(file ${INTEGRATION_TEST_FILES})
configure_file(
"${CMAKE_SOURCE_DIR}/${file}"
"${CMAKE_CURRENT_BINARY_DIR}/${file}"
COPYONLY
)
endforeach()
# Copy all locale files for locale consistency tests
# Use a custom command to properly handle both adding and removing files
set(LOCALE_SRC_DIR "${CMAKE_SOURCE_DIR}/src_assets/common/assets/web/public/assets/locale")
set(LOCALE_DST_DIR "${CMAKE_CURRENT_BINARY_DIR}/src_assets/common/assets/web/public/assets/locale")
add_custom_target(sync_locale_files ALL
COMMAND ${CMAKE_COMMAND} -E rm -rf "${LOCALE_DST_DIR}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${LOCALE_DST_DIR}"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${LOCALE_SRC_DIR}" "${LOCALE_DST_DIR}"
COMMENT "Synchronizing locale files for tests"
VERBATIM
)
foreach(dep ${SUNSHINE_TARGET_DEPENDENCIES})
add_dependencies(${PROJECT_NAME} ${dep}) # compile these before sunshine
endforeach()
# Ensure locale files are synchronized before building the test executable
add_dependencies(${PROJECT_NAME} sync_locale_files)
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 23)
target_link_libraries(${PROJECT_NAME}
${SUNSHINE_EXTERNAL_LIBRARIES}

View File

@@ -0,0 +1,667 @@
/**
* @file tests/integration/test_config_consistency.cpp
* @brief Test configuration consistency across all configuration files
*/
#include "../tests_common.h"
// standard includes
#include <algorithm>
#include <format>
#include <fstream>
#include <map>
#include <ranges>
#include <regex>
#include <set>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>
// local includes
#include "src/file_handler.h"
class ConfigConsistencyTest: public ::testing::Test {
protected:
void SetUp() override {
// Define the expected mapping between documentation sections and UI tabs
expectedDocToTabMapping = {
{"General", "general"},
{"Input", "input"},
{"Audio/Video", "av"},
{"Network", "network"},
{"Config Files", "files"},
{"Advanced", "advanced"},
{"NVIDIA NVENC Encoder", "nv"},
{"Intel QuickSync Encoder", "qsv"},
{"AMD AMF Encoder", "amd"},
{"VideoToolbox Encoder", "vt"},
{"VA-API Encoder", "vaapi"},
{"Software Encoder", "sw"}
};
}
// Extract config options from config.cpp - the authoritative source
static std::set<std::string, std::less<>> extractConfigCppOptions() {
std::set<std::string, std::less<>> options;
std::string content = file_handler::read_file("src/config.cpp");
// Regex patterns to match different config option types in config.cpp
const std::vector patterns = {
std::regex(R"DELIM((?:string_f|path_f|string_restricted_f)\s*\(\s*vars\s*,\s*"([^"]+)")DELIM"),
std::regex(R"DELIM((?:int_f|int_between_f)\s*\(\s*vars\s*,\s*"([^"]+)")DELIM"),
std::regex(R"DELIM(bool_f\s*\(\s*vars\s*,\s*"([^"]+)")DELIM"),
std::regex(R"DELIM((?:double_f|double_between_f)\s*\(\s*vars\s*,\s*"([^"]+)")DELIM"),
std::regex(R"DELIM(generic_f\s*\(\s*vars\s*,\s*"([^"]+)")DELIM"),
std::regex(R"DELIM(list_prep_cmd_f\s*\(\s*vars\s*,\s*"([^"]+)")DELIM"),
std::regex(R"DELIM(map_int_int_f\s*\(\s*vars\s*,\s*"([^"]+)")DELIM")
};
for (const auto &pattern : patterns) {
std::sregex_iterator iter(content.begin(), content.end(), pattern);
for (std::sregex_iterator end; iter != end; ++iter) {
std::string optionName = (*iter)[1].str();
options.insert(optionName);
}
}
return options;
}
// Helper function to find brace boundaries
static size_t findClosingBrace(const std::string &content, const size_t start) {
size_t pos = start + 1;
int braceLevel = 1;
while (pos < content.length() && braceLevel > 0) {
if (content[pos] == '{') {
braceLevel++;
} else if (content[pos] == '}') {
braceLevel--;
}
pos++;
}
return pos - 1;
}
// Helper function to extract tab ID from a tab object
static std::string extractTabId(const std::string &tabObject) {
const std::regex idPattern(R"DELIM(id:\s*"([^"]+)")DELIM");
if (std::smatch idMatch; std::regex_search(tabObject, idMatch, idPattern)) {
return idMatch[1].str();
}
return "";
}
// Helper function to find and extract tabs content from HTML
static std::string extractTabsContent(const std::string &content) {
const size_t tabsStart = content.find("tabs: [");
if (tabsStart == std::string::npos) {
return "";
}
// Find the end of the tab array
size_t pos = tabsStart + 7; // Skip "tabs: ["
int bracketLevel = 1;
size_t tabsEnd = pos;
while (pos < content.length() && bracketLevel > 0) {
if (content[pos] == '[') {
bracketLevel++;
} else if (content[pos] == ']') {
bracketLevel--;
}
tabsEnd = pos;
pos++;
}
return content.substr(tabsStart + 7, tabsEnd - tabsStart - 7);
}
// Helper function to extract options from a tab object (generic version)
template<typename Container>
static void extractOptionsFromTabGeneric(const std::string &tabObject, Container &container) {
const std::string tabId = extractTabId(tabObject);
if (tabId.empty()) {
return;
}
const size_t optionsStart = tabObject.find("options:");
if (optionsStart == std::string::npos) {
return;
}
const size_t optStart = tabObject.find('{', optionsStart);
if (optStart == std::string::npos) {
return;
}
const size_t optEnd = findClosingBrace(tabObject, optStart);
std::string optionsSection = tabObject.substr(optStart + 1, optEnd - optStart - 1);
// Extract option names
const std::regex optionPattern(R"DELIM("([^"]+)":\s*)DELIM");
std::sregex_iterator optionIter(optionsSection.begin(), optionsSection.end(), optionPattern);
for (const std::sregex_iterator optionEnd; optionIter != optionEnd; ++optionIter) {
std::string optionName = (*optionIter)[1].str();
// Use if constexpr to handle different container types
if constexpr (std::is_same_v<Container, std::map<std::string, std::string, std::less<>>>) {
container[optionName] = tabId;
} else if constexpr (std::is_same_v<Container, std::map<std::string, std::vector<std::string>, std::less<>>>) {
container[tabId].push_back(optionName);
}
}
}
// Helper function to process tab objects from tabs content
template<typename Container>
static void processTabObjects(const std::string &tabsContent, Container &container) {
size_t tabPos = 0;
while (tabPos < tabsContent.length()) {
const size_t objStart = tabsContent.find('{', tabPos);
if (objStart == std::string::npos) {
break;
}
const size_t objEnd = findClosingBrace(tabsContent, objStart);
std::string tabObject = tabsContent.substr(objStart, objEnd - objStart + 1);
extractOptionsFromTabGeneric(tabObject, container);
tabPos = objEnd + 1;
}
}
// Helper function to trim whitespace from string
static void trimWhitespace(std::string &str) {
str.erase(str.find_last_not_of(" \t\r\n") + 1);
}
// Helper function to extract option name from the Markdown line
static std::string extractOptionFromMarkdownLine(const std::string &line) {
const std::regex optionPattern(R"(^### ([^#\r\n]+))");
if (std::smatch optionMatch; std::regex_search(line, optionMatch, optionPattern)) {
std::string optionName = optionMatch[1].str();
trimWhitespace(optionName);
return optionName;
}
return "";
}
// Extract config options from config.html
static std::map<std::string, std::string, std::less<>> extractConfigHtmlOptions() {
std::map<std::string, std::string, std::less<>> options;
const std::string content = file_handler::read_file("src_assets/common/assets/web/config.html");
const std::string tabsContent = extractTabsContent(content);
if (tabsContent.empty()) {
return options;
}
processTabObjects(tabsContent, options);
return options;
}
// Helper function to extract options from a single tab object (now using generic function)
static void extractOptionsFromTab(const std::string &tabObject, std::map<std::string, std::vector<std::string>, std::less<>> &optionsByTab) {
extractOptionsFromTabGeneric(tabObject, optionsByTab);
}
// Extract config options from config.html with order preserved
static std::map<std::string, std::vector<std::string>, std::less<>> extractConfigHtmlOptionsWithOrder() {
std::map<std::string, std::vector<std::string>, std::less<>> optionsByTab;
const std::string content = file_handler::read_file("src_assets/common/assets/web/config.html");
const std::string tabsContent = extractTabsContent(content);
if (tabsContent.empty()) {
return optionsByTab;
}
processTabObjects(tabsContent, optionsByTab);
return optionsByTab;
}
// Helper function to process markdown line for section headers
static bool processSectionHeader(const std::string &line, std::string &currentSection) {
const std::regex sectionPattern(R"(^## ([^#\r\n]+))");
if (std::smatch sectionMatch; std::regex_search(line, sectionMatch, sectionPattern)) {
currentSection = sectionMatch[1].str();
trimWhitespace(currentSection);
return true;
}
return false;
}
// Helper function to process markdown line for option headers
static bool processOptionHeader(const std::string &line, const std::string_view currentSection, std::map<std::string, std::string, std::less<>> &options) {
if (currentSection.empty()) {
return false;
}
if (const std::string optionName = extractOptionFromMarkdownLine(line); !optionName.empty()) {
options[optionName] = currentSection;
return true;
}
return false;
}
// Extract config options from configuration.md
static std::map<std::string, std::string, std::less<>> extractConfigMdOptions() {
std::map<std::string, std::string, std::less<>> options;
const std::string content = file_handler::read_file("docs/configuration.md");
std::istringstream stream(content);
std::string line;
std::string currentSection;
while (std::getline(stream, line)) {
if (processSectionHeader(line, currentSection)) {
continue;
}
processOptionHeader(line, currentSection, options);
}
return options;
}
// Helper function to process markdown option line for order-preserved extraction
static void processMarkdownOptionLine(const std::string &line, const std::string &currentSection, std::map<std::string, std::vector<std::string>, std::less<>> &optionsBySection) {
if (currentSection.empty()) {
return;
}
if (const std::string optionName = extractOptionFromMarkdownLine(line); !optionName.empty()) {
optionsBySection[currentSection].push_back(optionName);
}
}
// Extract config options from configuration.md with order preserved
static std::map<std::string, std::vector<std::string>, std::less<>> extractConfigMdOptionsWithOrder() {
std::map<std::string, std::vector<std::string>, std::less<>> optionsBySection;
const std::string content = file_handler::read_file("docs/configuration.md");
std::istringstream stream(content);
std::string line;
std::string currentSection;
while (std::getline(stream, line)) {
if (processSectionHeader(line, currentSection)) {
continue;
}
processMarkdownOptionLine(line, currentSection, optionsBySection);
}
return optionsBySection;
}
// Helper function to find the config section end
static size_t findConfigSectionEnd(const std::string &content, size_t configStart) {
size_t braceCount = 1;
size_t configEnd = configStart;
while (configStart < content.length() && braceCount > 0) {
if (content[configStart] == '{') {
braceCount++;
} else if (content[configStart] == '}') {
braceCount--;
}
configEnd = configStart;
configStart++;
}
return configEnd;
}
// Helper function to extract keys from a config section
static void extractKeysFromConfigSection(const std::string_view configSection, std::set<std::string, std::less<>> &options) {
const std::regex keyPattern(R"DELIM("([^"]+)":\s*)DELIM");
std::string configStr(configSection);
std::sregex_iterator iter(configStr.begin(), configStr.end(), keyPattern);
for (const std::sregex_iterator end; iter != end; ++iter) {
options.insert((*iter)[1].str());
}
}
// Extract config options from en.json
static std::set<std::string, std::less<>> extractEnJsonConfigOptions() {
std::set<std::string, std::less<>> options;
const std::string content = file_handler::read_file("src_assets/common/assets/web/public/assets/locale/en.json");
// Look for the config section
const std::regex configSectionPattern(R"DELIM("config":\s*\{)DELIM");
std::smatch match;
if (!std::regex_search(content, match, configSectionPattern)) {
return options;
}
// Find the config section and extract keys
const size_t configStart = match.position() + match.length();
const size_t configEnd = findConfigSectionEnd(content, configStart);
const std::string configSection = content.substr(configStart, configEnd - configStart);
extractKeysFromConfigSection(configSection, options);
return options;
}
std::map<std::string, std::string, std::less<>> expectedDocToTabMapping;
// Helper function to check if an option exists in HTML options
static bool isOptionInHtml(const std::string &option, const std::map<std::string, std::string, std::less<>> &htmlOptions) {
return htmlOptions.contains(option);
}
// Helper function to check if an option exists in MD options
static bool isOptionInMd(const std::string &option, const std::map<std::string, std::string, std::less<>> &mdOptions) {
return mdOptions.contains(option);
}
// Helper function to validate option existence across files
static void validateOptionExistence(const std::string &option, const std::map<std::string, std::string, std::less<>> &htmlOptions, const std::map<std::string, std::string, std::less<>> &mdOptions, const std::set<std::string, std::less<>> &jsonOptions, std::vector<std::string> &missingFromFiles) {
if (!isOptionInHtml(option, htmlOptions)) {
missingFromFiles.push_back(std::format("config.html missing: {}", option));
}
if (!isOptionInMd(option, mdOptions)) {
missingFromFiles.push_back(std::format("configuration.md missing: {}", option));
}
if (!jsonOptions.contains(option)) {
missingFromFiles.push_back(std::format("en.json missing: {}", option));
}
}
// Helper function to check tab correspondence with documentation sections
static void checkTabCorrespondence(const std::string &tab, const std::map<std::string, std::string, std::less<>> &expectedDocToTabMapping, const std::set<std::string, std::less<>> &mdSections, std::vector<std::string> &inconsistencies) {
bool found = false;
for (const auto &[docSection, expectedTab] : expectedDocToTabMapping) {
if (expectedTab != tab) {
continue;
}
if (!mdSections.contains(docSection)) {
inconsistencies.push_back(std::format("Tab '{}' maps to doc section '{}' but section not found", tab, docSection));
}
found = true;
break;
}
if (!found) {
inconsistencies.push_back(std::format("Tab '{}' has no corresponding documentation section", tab));
}
}
// Helper function to check if a test fake option is found in missing files
static void checkTestDummyDetection(const std::vector<std::string> &missingFromFiles, const std::string &testDummyOption, bool &foundMissingDummyInHtml, bool &foundMissingDummyInMd, bool &foundMissingDummyInJson) {
for (const auto &missing : missingFromFiles) {
if (!missing.contains(testDummyOption)) {
continue;
}
if (missing.contains("config.html")) {
foundMissingDummyInHtml = true;
}
if (missing.contains("configuration.md")) {
foundMissingDummyInMd = true;
}
if (missing.contains("en.json")) {
foundMissingDummyInJson = true;
}
}
}
// Helper function to create comma-separated string from vector
static std::string buildCommaSeparatedString(const std::vector<std::string> &options) {
std::string result;
for (size_t i = 0; i < options.size(); ++i) {
if (i > 0) {
result += ", ";
}
result += options[i];
}
return result;
}
};
TEST_F(ConfigConsistencyTest, AllConfigOptionsExistInAllFiles) {
const auto cppOptions = extractConfigCppOptions();
const auto htmlOptions = extractConfigHtmlOptions();
const auto mdOptions = extractConfigMdOptions();
const auto jsonOptions = extractEnJsonConfigOptions();
// Options that are internal/special and shouldn't be in UI/docs
const std::set<std::string, std::less<>> internalOptions = {
"flags" // Internal config flags, not user-configurable
};
std::vector<std::string> missingFromFiles;
// Check that all config.cpp options exist in other files (except internal ones)
for (const auto &option : cppOptions) {
if (internalOptions.contains(option)) {
continue; // Skip internal options
}
validateOptionExistence(option, htmlOptions, mdOptions, jsonOptions, missingFromFiles);
}
if (!missingFromFiles.empty()) {
std::string errorMsg = "Config options missing from files:\n";
for (const auto &missing : missingFromFiles) {
errorMsg += std::format(" {}\n", missing);
}
FAIL() << errorMsg;
}
}
TEST_F(ConfigConsistencyTest, ConfigTabsMatchDocumentationSections) {
auto htmlOptions = extractConfigHtmlOptions();
auto mdOptions = extractConfigMdOptions();
// Get unique tabs and sections
std::set<std::string, std::less<>> htmlTabs;
std::set<std::string, std::less<>> mdSections;
for (const auto &tab : htmlOptions | std::views::values) {
htmlTabs.insert(tab);
}
for (const auto &section : mdOptions | std::views::values) {
mdSections.insert(section);
}
std::vector<std::string> inconsistencies;
// Check that each HTML tab has a corresponding documentation section
for (const auto &tab : htmlTabs) {
checkTabCorrespondence(tab, expectedDocToTabMapping, mdSections, inconsistencies);
}
// Check that each documentation section has a corresponding HTML tab
for (const auto &section : mdSections) {
if (!expectedDocToTabMapping.contains(section)) {
inconsistencies.push_back(std::format("Documentation section '{}' has no corresponding UI tab", section));
}
}
if (!inconsistencies.empty()) {
std::string errorMsg = "Tab/Section mapping inconsistencies:\n";
for (const auto &inconsistency : inconsistencies) {
errorMsg += std::format(" {}\n", inconsistency);
}
FAIL() << errorMsg;
}
}
TEST_F(ConfigConsistencyTest, ConfigOptionsInSameOrderWithinSections) {
// Extract options with order preserved
auto htmlOptionsByTab = extractConfigHtmlOptionsWithOrder();
auto mdOptionsBySection = extractConfigMdOptionsWithOrder();
std::vector<std::string> orderInconsistencies;
// Compare order for each tab/section pair
for (const auto &[docSection, tabId] : expectedDocToTabMapping) {
if (!htmlOptionsByTab.contains(tabId) || !mdOptionsBySection.contains(docSection)) {
continue; // Skip if either tab or section doesn't exist
}
const auto &htmlOrder = htmlOptionsByTab.at(tabId);
const auto &mdOrder = mdOptionsBySection.at(docSection);
// Find options that exist in both HTML and MD for this section
std::vector<std::string> commonOptions;
for (const auto &option : htmlOrder) {
if (std::ranges::find(mdOrder, option) != mdOrder.end()) {
commonOptions.push_back(option);
}
}
// Filter MD order to only include common options in the same order they appear in MD
std::vector<std::string> mdOrderFiltered;
for (const auto &option : mdOrder) {
if (std::ranges::find(commonOptions, option) != commonOptions.end()) {
mdOrderFiltered.push_back(option);
}
}
// Compare the order of common options
if (commonOptions != mdOrderFiltered && !commonOptions.empty() && !mdOrderFiltered.empty()) {
// Create readable string representations of the option lists
std::string htmlOrderStr = buildCommaSeparatedString(commonOptions);
std::string mdOrderStr = buildCommaSeparatedString(mdOrderFiltered);
std::string detailMsg = std::format(
"Section '{}' (tab '{}') has different option order:\n"
" HTML order: [{}]\n"
" MD order: [{}]",
docSection,
tabId,
htmlOrderStr,
mdOrderStr
);
orderInconsistencies.push_back(detailMsg);
}
}
if (!orderInconsistencies.empty()) {
std::string errorMsg = "Config option order inconsistencies:\n";
for (const auto &inconsistency : orderInconsistencies) {
errorMsg += std::format(" {}\n", inconsistency);
}
FAIL() << errorMsg;
}
}
TEST_F(ConfigConsistencyTest, DummyConfigOptionsDoNotExist) {
const auto cppOptions = extractConfigCppOptions();
const auto htmlOptions = extractConfigHtmlOptions();
const auto mdOptions = extractConfigMdOptions();
const auto jsonOptions = extractEnJsonConfigOptions();
// List of fake config options that should NOT exist in any files
const std::vector<std::string> dummyOptions = {
"dummy_config_option",
"nonexistent_setting",
"fake_config_parameter",
"test_dummy_option",
"invalid_config_key"
};
std::vector<std::string> unexpectedlyFound;
// Check that none of the fake options exist in any of the config files
for (const auto &dummyOption : dummyOptions) {
if (cppOptions.contains(dummyOption)) {
unexpectedlyFound.push_back(std::format("config.cpp contains dummy option: {}", dummyOption));
}
if (htmlOptions.contains(dummyOption)) {
unexpectedlyFound.push_back(std::format("config.html contains dummy option: {}", dummyOption));
}
if (mdOptions.contains(dummyOption)) {
unexpectedlyFound.push_back(std::format("configuration.md contains dummy option: {}", dummyOption));
}
if (jsonOptions.contains(dummyOption)) {
unexpectedlyFound.push_back(std::format("en.json contains dummy option: {}", dummyOption));
}
}
// This test should pass (i.e., no fake options should be found)
// If any fake options are found, it indicates a problem with the test data
if (!unexpectedlyFound.empty()) {
std::string errorMsg = "Dummy config options unexpectedly found in files:\n";
for (const auto &found : unexpectedlyFound) {
errorMsg += std::format(" {}\n", found);
}
FAIL() << errorMsg;
}
}
TEST_F(ConfigConsistencyTest, TestFrameworkDetectsMissingOptions) {
const auto cppOptions = extractConfigCppOptions();
const auto htmlOptions = extractConfigHtmlOptions();
const auto mdOptions = extractConfigMdOptions();
const auto jsonOptions = extractEnJsonConfigOptions();
// Add a fake option to the cpp options to simulate a missing option scenario
std::set<std::string, std::less<>> modifiedCppOptions = cppOptions;
const std::string testDummyOption = "test_framework_validation_option";
modifiedCppOptions.insert(testDummyOption);
// Options that are internal/special and shouldn't be in UI/docs
std::set<std::string, std::less<>> internalOptions = {
"flags" // Internal config flags, not user-configurable
};
std::vector<std::string> missingFromFiles;
// Check that the fake option is detected as missing from other files
for (const auto &option : modifiedCppOptions) {
if (internalOptions.contains(option)) {
continue; // Skip internal options
}
if (!htmlOptions.contains(option)) {
missingFromFiles.push_back(std::format("config.html missing: {}", option));
}
if (!mdOptions.contains(option)) {
missingFromFiles.push_back(std::format("configuration.md missing: {}", option));
}
if (!jsonOptions.contains(option)) {
missingFromFiles.push_back(std::format("en.json missing: {}", option));
}
}
// Verify that the test framework detected the missing fake option
bool foundMissingDummyInHtml = false;
bool foundMissingDummyInMd = false;
bool foundMissingDummyInJson = false;
checkTestDummyDetection(missingFromFiles, testDummyOption, foundMissingDummyInHtml, foundMissingDummyInMd, foundMissingDummyInJson);
// The test framework should have detected the fake option as missing from all files
EXPECT_TRUE(foundMissingDummyInHtml) << "Test framework failed to detect missing option in config.html";
EXPECT_TRUE(foundMissingDummyInMd) << "Test framework failed to detect missing option in configuration.md";
EXPECT_TRUE(foundMissingDummyInJson) << "Test framework failed to detect missing option in en.json";
// Verify we have at least 3 missing entries (one for each file type)
EXPECT_GE(missingFromFiles.size(), 3) << "Test framework should detect missing dummy option in all three file types";
}

View File

@@ -0,0 +1,194 @@
/**
* @file tests/integration/test_external_commands.cpp
* @brief Integration tests for running external commands with platform-specific validation
*/
#include "../tests_common.h"
// standard includes
#include <format>
#include <string>
#include <tuple>
#include <vector>
// lib includes
#include <boost/process/v1.hpp>
// local includes
#include "src/platform/common.h"
// Test data structure for parameterized testing
struct ExternalCommandTestData {
std::string command;
std::string platform; // "windows", "linux", "macos", or "all"
bool should_succeed;
std::string description;
std::string working_directory; // Optional: if empty, uses SUNSHINE_SOURCE_DIR
bool xfail_condition = false; // Optional: condition for expected failure
std::string xfail_reason = ""; // Optional: reason for expected failure
// Constructor with xfail parameters
ExternalCommandTestData(std::string cmd, std::string plat, const bool succeed, std::string desc, std::string work_dir = "", const bool xfail_cond = false, std::string xfail_rsn = ""):
command(std::move(cmd)),
platform(std::move(plat)),
should_succeed(succeed),
description(std::move(desc)),
working_directory(std::move(work_dir)),
xfail_condition(xfail_cond),
xfail_reason(std::move(xfail_rsn)) {}
};
class ExternalCommandTest: public ::testing::TestWithParam<ExternalCommandTestData> {
protected:
void SetUp() override {
if constexpr (IS_WINDOWS) {
current_platform = "windows";
} else if constexpr (IS_MACOS) {
current_platform = "macos";
} else if constexpr (IS_LINUX) {
current_platform = "linux";
}
}
[[nodiscard]] bool shouldRunOnCurrentPlatform(const std::string_view &test_platform) const {
return test_platform == "all" || test_platform == current_platform;
}
// Helper function to run a command using the existing process infrastructure
static std::pair<int, std::string> runCommand(const std::string &cmd, const std::string_view &working_dir) {
const auto env = boost::this_process::environment();
// Determine the working directory: use the provided working_dir or fall back to SUNSHINE_SOURCE_DIR
boost::filesystem::path effective_working_dir;
if (!working_dir.empty()) {
effective_working_dir = working_dir;
} else {
// Use SUNSHINE_SOURCE_DIR CMake definition as the default working directory
effective_working_dir = SUNSHINE_SOURCE_DIR;
}
std::error_code ec;
// Create a temporary file to capture output
const auto temp_file = std::tmpfile();
if (!temp_file) {
return {-1, "Failed to create temporary file for output"};
}
// Run the command using the existing platf::run_command function
auto child = platf::run_command(
false, // not elevated
false, // not interactive
cmd,
effective_working_dir,
env,
temp_file,
ec,
nullptr // no process group
);
if (ec) {
std::fclose(temp_file);
return {-1, std::format("Failed to start command: {}", ec.message())};
}
// Wait for the command to complete
child.wait();
int exit_code = child.exit_code();
// Read the output from the temporary file
std::rewind(temp_file);
std::string output;
std::array<char, 1024> buffer {};
while (std::fgets(buffer.data(), static_cast<int>(buffer.size()), temp_file)) {
// std::string constructor automatically handles null-terminated strings
output += std::string(buffer.data());
}
std::fclose(temp_file);
return {exit_code, output};
}
public:
std::string current_platform;
};
// Test case implementation
TEST_P(ExternalCommandTest, RunExternalCommand) {
const auto &[command, platform, should_succeed, description, working_directory, xfail_condition, xfail_reason] = GetParam();
// Skip test if not for the current platform
if (!shouldRunOnCurrentPlatform(platform)) {
GTEST_SKIP() << "Test not applicable for platform: " << current_platform;
}
// Use the xfail condition and reason from test data
XFAIL_IF(xfail_condition, xfail_reason);
BOOST_LOG(info) << "Running external command test: " << description;
BOOST_LOG(debug) << "Command: " << command;
auto [exit_code, output] = runCommand(command, working_directory);
BOOST_LOG(debug) << "Command exit code: " << exit_code;
if (!output.empty()) {
BOOST_LOG(debug) << "Command output: " << output;
}
if (should_succeed) {
HANDLE_XFAIL_ASSERT_EQ(exit_code, 0, std::format("Command should have succeeded but failed with exit code {}\nOutput: {}", std::to_string(exit_code), output));
} else {
HANDLE_XFAIL_ASSERT_NE(exit_code, 0, std::format("Command should have failed but succeeded\nOutput: {}", output));
}
}
// Platform-specific command strings
constexpr auto SIMPLE_COMMAND = IS_WINDOWS ? "where cmd" : "which sh";
#ifdef UDEVADM_EXECUTABLE
#define UDEV_TESTS \
ExternalCommandTestData { \
std::format("{} verify {}/src_assets/linux/misc/60-sunshine.rules", UDEVADM_EXECUTABLE, SUNSHINE_TEST_BIN_DIR), \
"linux", \
true, \
"Test udev rules file" \
},
#else
#define UDEV_TESTS
#endif
// Test data
INSTANTIATE_TEST_SUITE_P(
ExternalCommands,
ExternalCommandTest,
::testing::Values(
UDEV_TESTS
// Cross-platform tests with xfail on Windows CI
ExternalCommandTestData {
SIMPLE_COMMAND,
"all",
true,
"Simple command test",
"", // working_directory
IS_WINDOWS, // xfail_condition
"Simple command test fails on Windows CI environment" // xfail_reason
},
// Cross-platform failing test
ExternalCommandTestData {
"non_existent_command_12345",
"all",
false,
"Test command that should fail"
}
),
[](const ::testing::TestParamInfo<ExternalCommandTestData> &info) {
// Generate test names from a description
std::string name = info.param.description;
// Replace spaces and special characters with underscores for valid test names
std::replace_if(name.begin(), name.end(), [](char c) {
return !std::isalnum(c);
},
'_');
return name;
}
);

View File

@@ -0,0 +1,357 @@
/**
* @file tests/integration/test_locale_consistency.cpp
* @brief Test locale consistency across configuration files and locale JSON files
*/
#include "../tests_common.h"
// standard includes
#include <filesystem>
#include <format>
#include <fstream>
#include <functional>
#include <map>
#include <regex>
#include <set>
#include <string>
#include <vector>
// lib includes
#include <nlohmann/json.hpp>
// local includes
#include "src/file_handler.h"
namespace fs = std::filesystem;
class LocaleConsistencyTest: public ::testing::Test {
protected:
// Extract locale options from config.cpp
static std::set<std::string, std::less<>> extractConfigCppLocales() {
std::set<std::string, std::less<>> locales;
const std::string content = file_handler::read_file("src/config.cpp");
// Find the string_restricted_f call for locale
const std::regex localeSection(R"(string_restricted_f\s*\(\s*vars\s*,\s*"locale"[^}]*\{([^}]*)\})");
if (std::smatch match; std::regex_search(content, match, localeSection)) {
const std::string localeList = match[1].str();
// Extract individual locale codes
const std::regex localePattern(R"delimiter("([^"]+)"sv)delimiter");
std::sregex_iterator iter(localeList.begin(), localeList.end(), localePattern);
for (const std::sregex_iterator end; iter != end; ++iter) {
locales.insert((*iter)[1].str());
}
}
return locales;
}
// Extract locale options from General.vue
static std::map<std::string, std::string, std::less<>> extractGeneralVueLocales() {
std::map<std::string, std::string, std::less<>> locales;
const std::string content = file_handler::read_file("src_assets/common/assets/web/configs/tabs/General.vue");
// Find the locale select section specifically
const std::regex localeSelectPattern("id=\"locale\"[^>]*>([^<]*(?:<option[^>]*>[^<]*</option>[^<]*)*)</select>");
if (std::smatch selectMatch; std::regex_search(content, selectMatch, localeSelectPattern)) {
const std::string localeSection = selectMatch[1].str();
// Extract option elements with locale codes and display names from the locale section
const std::regex optionPattern(R"delimiter(<option\s+value="([^"]+)">([^<]+)</option>)delimiter");
std::sregex_iterator iter(localeSection.begin(), localeSection.end(), optionPattern);
for (const std::sregex_iterator end; iter != end; ++iter) {
const std::string localeCode = (*iter)[1].str();
const std::string displayName = (*iter)[2].str();
locales[localeCode] = displayName;
}
}
return locales;
}
// Get available locale JSON files
static std::set<std::string, std::less<>> getAvailableLocaleFiles() {
std::set<std::string, std::less<>> locales;
const std::filesystem::path localeDir = "src_assets/common/assets/web/public/assets/locale";
if (!fs::exists(localeDir)) {
return locales;
}
for (const auto &entry : fs::directory_iterator(localeDir)) {
if (entry.is_regular_file() && entry.path().extension() == ".json") {
const std::string filename = entry.path().stem().string();
locales.insert(filename);
}
}
return locales;
}
// Helper function to check if a locale JSON file is valid using nlohmann/json
static bool isValidLocaleFile(const std::string &localeCode) {
const std::string filePath = std::format("src_assets/common/assets/web/public/assets/locale/{}.json", localeCode);
if (!fs::exists(filePath)) {
return false;
}
try {
const std::string content = file_handler::read_file(filePath.c_str());
// Parse JSON using nlohmann/json to validate it's properly formatted
const nlohmann::json localeJson = nlohmann::json::parse(content);
// Basic validation - should be a JSON object with some content
return localeJson.is_object() && !localeJson.empty();
} catch (const nlohmann::json::parse_error &) {
return false;
}
}
};
TEST_F(LocaleConsistencyTest, AllLocaleFilesHaveConfigCppEntries) {
const auto configLocales = extractConfigCppLocales();
const auto localeFiles = getAvailableLocaleFiles();
std::vector<std::string> missingFromConfig;
// Check that every locale file has a corresponding entry in config.cpp
for (const auto &localeFile : localeFiles) {
if (!configLocales.contains(localeFile)) {
missingFromConfig.push_back(localeFile);
}
}
if (!missingFromConfig.empty()) {
std::string errorMsg = "Locale files missing from config.cpp:\n";
for (const auto &missing : missingFromConfig) {
errorMsg += std::format(" {}.json\n", missing);
}
FAIL() << errorMsg;
}
}
TEST_F(LocaleConsistencyTest, AllLocaleFilesHaveGeneralVueEntries) {
const auto vueLocales = extractGeneralVueLocales();
const auto localeFiles = getAvailableLocaleFiles();
std::vector<std::string> missingFromVue;
// Check that every locale file has a corresponding entry in General.vue
for (const auto &localeFile : localeFiles) {
if (!vueLocales.contains(localeFile)) {
missingFromVue.push_back(localeFile);
}
}
if (!missingFromVue.empty()) {
std::string errorMsg = "Locale files missing from General.vue:\n";
for (const auto &missing : missingFromVue) {
errorMsg += std::format(" {}.json\n", missing);
}
FAIL() << errorMsg;
}
}
TEST_F(LocaleConsistencyTest, AllConfigCppLocalesHaveFiles) {
const auto configLocales = extractConfigCppLocales();
const auto localeFiles = getAvailableLocaleFiles();
std::vector<std::string> missingFiles;
// Check that every config.cpp locale has a corresponding JSON file
for (const auto &configLocale : configLocales) {
if (!localeFiles.contains(configLocale)) {
missingFiles.push_back(configLocale);
}
}
if (!missingFiles.empty()) {
std::string errorMsg = "config.cpp locales missing JSON files:\n";
for (const auto &missing : missingFiles) {
errorMsg += std::format(" {}.json\n", missing);
}
FAIL() << errorMsg;
}
}
TEST_F(LocaleConsistencyTest, AllGeneralVueLocalesHaveFiles) {
const auto vueLocales = extractGeneralVueLocales();
const auto localeFiles = getAvailableLocaleFiles();
std::vector<std::string> missingFiles;
// Check that every General.vue locale has a corresponding JSON file
for (const auto &vueLocale : vueLocales | std::views::keys) {
if (!localeFiles.contains(vueLocale)) {
missingFiles.push_back(vueLocale);
}
}
if (!missingFiles.empty()) {
std::string errorMsg = "General.vue locales missing JSON files:\n";
for (const auto &missing : missingFiles) {
errorMsg += std::format(" {}.json\n", missing);
}
FAIL() << errorMsg;
}
}
TEST_F(LocaleConsistencyTest, ConfigCppAndGeneralVueLocalesMatch) {
const auto configLocales = extractConfigCppLocales();
const auto vueLocales = extractGeneralVueLocales();
std::vector<std::string> configOnlyLocales;
std::vector<std::string> vueOnlyLocales;
// Find locales in config.cpp but not in General.vue
for (const auto &configLocale : configLocales) {
if (!vueLocales.contains(configLocale)) {
configOnlyLocales.push_back(configLocale);
}
}
// Find locales in General.vue but not in config.cpp
for (const auto &vueLocale : vueLocales | std::views::keys) {
if (!configLocales.contains(vueLocale)) {
vueOnlyLocales.push_back(vueLocale);
}
}
std::string errorMsg;
if (!configOnlyLocales.empty()) {
errorMsg += "Locales in config.cpp but not in General.vue:\n";
for (const auto &locale : configOnlyLocales) {
errorMsg += std::format(" {}\n", locale);
}
}
if (!vueOnlyLocales.empty()) {
errorMsg += "Locales in General.vue but not in config.cpp:\n";
for (const auto &locale : vueOnlyLocales) {
errorMsg += std::format(" {}\n", locale);
}
}
if (!errorMsg.empty()) {
FAIL() << errorMsg;
}
}
TEST_F(LocaleConsistencyTest, AllLocaleFilesAreValid) {
const auto localeFiles = getAvailableLocaleFiles();
std::vector<std::string> invalidFiles;
// Check that all locale files are valid JSON
for (const auto &localeFile : localeFiles) {
if (!isValidLocaleFile(localeFile)) {
invalidFiles.push_back(localeFile);
}
}
if (!invalidFiles.empty()) {
std::string errorMsg = "Invalid locale files found:\n";
for (const auto &invalid : invalidFiles) {
errorMsg += std::format(" {}.json\n", invalid);
}
FAIL() << errorMsg;
}
}
TEST_F(LocaleConsistencyTest, LocaleDisplayNamesAreConsistent) {
const auto vueLocales = extractGeneralVueLocales();
const auto localeFiles = getAvailableLocaleFiles();
std::vector<std::string> inconsistentDisplayNames;
// Check that all locales in General.vue have corresponding JSON files
for (const auto &[localeCode, displayName] : vueLocales) {
if (!localeFiles.contains(localeCode)) {
inconsistentDisplayNames.push_back(
std::format("{}: has display name '{}' but no corresponding JSON file exists", localeCode, displayName)
);
}
}
// Also check that locale files that exist have entries in General.vue
for (const auto &localeFile : localeFiles) {
if (!vueLocales.contains(localeFile)) {
inconsistentDisplayNames.push_back(
std::format("{}: has JSON file but no display name in General.vue", localeFile)
);
}
}
if (!inconsistentDisplayNames.empty()) {
std::string errorMsg = "Locale display name inconsistencies found:\n";
for (const auto &inconsistent : inconsistentDisplayNames) {
errorMsg += std::format(" {}\n", inconsistent);
}
FAIL() << errorMsg;
}
}
TEST_F(LocaleConsistencyTest, NoOrphanedLocaleReferences) {
const auto configLocales = extractConfigCppLocales();
const auto vueLocales = extractGeneralVueLocales();
const auto localeFiles = getAvailableLocaleFiles();
std::vector<std::string> orphanedReferences;
// Check for locale references that don't have corresponding files
for (const auto &configLocale : configLocales) {
if (!localeFiles.contains(configLocale)) {
orphanedReferences.push_back(std::format("config.cpp references missing file: {}.json", configLocale));
}
}
for (const auto &vueLocale : vueLocales | std::views::keys) {
if (!localeFiles.contains(vueLocale)) {
orphanedReferences.push_back(std::format("General.vue references missing file: {}.json", vueLocale));
}
}
if (!orphanedReferences.empty()) {
std::string errorMsg = "Orphaned locale references found:\n";
for (const auto &orphaned : orphanedReferences) {
errorMsg += std::format(" {}\n", orphaned);
}
FAIL() << errorMsg;
}
}
TEST_F(LocaleConsistencyTest, TestFrameworkDetectsLocaleInconsistencies) {
// Test the framework by simulating a missing locale scenario
const std::string testLocale = "test_framework_validation_locale";
auto configLocales = extractConfigCppLocales();
auto vueLocales = extractGeneralVueLocales();
const auto localeFiles = getAvailableLocaleFiles();
// Add a fake locale to config to simulate a missing file
configLocales.insert(testLocale);
std::vector<std::string> missingFiles;
for (const auto &configLocale : configLocales) {
if (!localeFiles.contains(configLocale)) {
missingFiles.push_back(configLocale);
}
}
// Verify the test framework detects the missing fake locale
bool foundMissingTestLocale = false;
for (const auto &missing : missingFiles) {
if (missing == testLocale) {
foundMissingTestLocale = true;
break;
}
}
EXPECT_TRUE(foundMissingTestLocale) << "Test framework failed to detect missing locale file";
EXPECT_GE(missingFiles.size(), 1) << "Test framework should detect at least the fake missing locale";
}

View File

@@ -8,6 +8,124 @@
#include <src/logging.h>
#include <src/platform/common.h>
// XFail/XPass pattern implementation (similar to pytest)
namespace test_utils {
/**
* @brief Marks a test as expected to fail
* @param condition The condition under which the test is expected to fail
* @param reason The reason why the test is expected to fail
*/
struct XFailMarker {
bool should_xfail;
std::string reason;
XFailMarker(bool condition, std::string reason):
should_xfail(condition),
reason(std::move(reason)) {}
};
/**
* @brief Helper function to handle xfail logic
* @param marker The XFailMarker containing condition and reason
* @param test_passed Whether the test actually passed
*/
inline void handleXFail(const XFailMarker &marker, bool test_passed) {
if (marker.should_xfail) {
if (test_passed) {
// XPass: Test was expected to fail but passed
const std::string message = "XPASS: Test unexpectedly passed (expected to fail: " + marker.reason + ")";
BOOST_LOG(warning) << message;
GTEST_SKIP() << "XPASS: Test unexpectedly passed (expected to fail: " << marker.reason << ")";
} else {
// XFail: Test failed as expected
const std::string message = "XFAIL: Test failed as expected (" + marker.reason + ")";
BOOST_LOG(info) << message;
GTEST_SKIP() << "XFAIL: " << marker.reason;
}
}
// If not marked as xfail, let the test result stand as normal
}
/**
* @brief Check if two values are equal without failing the test
* @param actual The actual value
* @param expected The expected value
* @param message Optional message to include
* @return true if values are equal, false otherwise
*/
template<typename T1, typename T2>
inline bool checkEqual(const T1 &actual, const T2 &expected, const std::string &message = "") {
bool result = (actual == expected);
if (!message.empty()) {
BOOST_LOG(debug) << "Assertion check: " << message << " - " << (result ? "PASSED" : "FAILED");
}
return result;
}
/**
* @brief Check if two values are not equal without failing the test
* @param actual The actual value
* @param expected The expected value
* @param message Optional message to include
* @return true if values are not equal, false otherwise
*/
template<typename T1, typename T2>
inline bool checkNotEqual(const T1 &actual, const T2 &expected, const std::string &message = "") {
const bool result = (actual != expected);
if (!message.empty()) {
BOOST_LOG(debug) << "Assertion check: " << message << " - " << (result ? "PASSED" : "FAILED");
}
return result;
}
} // namespace test_utils
// Convenience macros for xfail testing
#define XFAIL_IF(condition, reason) \
test_utils::XFailMarker xfail_marker((condition), (reason))
#define HANDLE_XFAIL_ASSERT_EQ(actual, expected, message) \
do { \
if (xfail_marker.should_xfail) { \
/* For xfail tests, check the assertion without failing */ \
bool test_passed = test_utils::checkEqual((actual), (expected), (message)); \
test_utils::handleXFail(xfail_marker, test_passed); \
} else { \
/* Run the normal GTest assertion if not marked as xfail */ \
EXPECT_EQ((actual), (expected)) << (message); \
} \
} while (0)
#define HANDLE_XFAIL_ASSERT_NE(actual, expected, message) \
do { \
if (xfail_marker.should_xfail) { \
/* For xfail tests, check the assertion without failing */ \
bool test_passed = test_utils::checkNotEqual((actual), (expected), (message)); \
test_utils::handleXFail(xfail_marker, test_passed); \
} else { \
/* Run the normal GTest assertion if not marked as xfail */ \
EXPECT_NE((actual), (expected)) << (message); \
} \
} while (0)
// Platform detection macros for convenience
#ifdef _WIN32
#define IS_WINDOWS true
#else
#define IS_WINDOWS false
#endif
#ifdef __linux__
#define IS_LINUX true
#else
#define IS_LINUX false
#endif
#ifdef __APPLE__
#define IS_MACOS true
#else
#define IS_MACOS false
#endif
struct PlatformTestSuite: testing::Test {
static void SetUpTestSuite() {
ASSERT_FALSE(platf_deinit);

View File

@@ -18,7 +18,7 @@ struct AudioTest: PlatformTestSuite, testing::WithParamInterface<std::tuple<std:
safe::mail_t m_mail;
};
constexpr std::bitset<config_t::MAX_FLAGS> config_flags(int flag = -1) {
constexpr std::bitset<config_t::MAX_FLAGS> config_flags(const int flag = -1) {
std::bitset<3> result = std::bitset<config_t::MAX_FLAGS>();
if (flag >= 0) {
result.set(flag);
@@ -42,22 +42,21 @@ INSTANTIATE_TEST_SUITE_P(
TEST_P(AudioTest, TestEncode) {
std::thread timer([&] {
// Terminate the audio capture after 5 seconds.
std::this_thread::sleep_for(5s);
auto shutdown_event = m_mail->event<bool>(mail::shutdown);
auto audio_packets = m_mail->queue<packet_t>(mail::audio_packets);
// Terminate the audio capture after 100 ms
std::this_thread::sleep_for(100ms);
const auto shutdown_event = m_mail->event<bool>(mail::shutdown);
const auto audio_packets = m_mail->queue<packet_t>(mail::audio_packets);
shutdown_event->raise(true);
audio_packets->stop();
});
std::thread capture([&] {
auto packets = m_mail->queue<packet_t>(mail::audio_packets);
auto shutdown_event = m_mail->event<bool>(mail::shutdown);
while (auto packet = packets->pop()) {
const auto packets = m_mail->queue<packet_t>(mail::audio_packets);
const auto shutdown_event = m_mail->event<bool>(mail::shutdown);
while (const auto packet = packets->pop()) {
if (shutdown_event->peek()) {
break;
}
auto packet_data = packet->second;
if (packet_data.size() == 0) {
if (auto packet_data = packet->second; packet_data.size() == 0) {
FAIL() << "Empty packet data";
}
}

View File

@@ -4,6 +4,7 @@
*/
#include "../tests_common.h"
#include <format>
#include <src/config.h>
#include <src/display_device.h>
#include <src/rtsp.h>
@@ -473,7 +474,7 @@ namespace {
} else {
const auto [manual_res] = std::get<manual_value_t<res_t>>(input_res);
video_config.dd.resolution_option = manual;
video_config.dd.manual_resolution = std::to_string(manual_res.m_width) + "x"s + std::to_string(manual_res.m_height);
video_config.dd.manual_resolution = std::format("{}x{}", static_cast<int>(manual_res.m_width), static_cast<int>(manual_res.m_height));
}
}

View File

@@ -4,6 +4,7 @@
*/
#include "../tests_common.h"
#include <format>
#include <src/file_handler.h>
struct FileHandlerParentDirectoryTest: testing::TestWithParam<std::tuple<std::string, std::string>> {};
@@ -79,13 +80,13 @@ Hey, hey, hey!
TEST_P(FileHandlerTests, WriteFileTest) {
auto [fileNum, content] = GetParam();
std::string fileName = "write_file_test_" + std::to_string(fileNum) + ".txt";
const std::string fileName = std::format("write_file_test_{}.txt", fileNum);
EXPECT_EQ(file_handler::write_file(fileName.c_str(), content), 0);
}
TEST_P(FileHandlerTests, ReadFileTest) {
auto [fileNum, content] = GetParam();
std::string fileName = "write_file_test_" + std::to_string(fileNum) + ".txt";
const std::string fileName = std::format("write_file_test_{}.txt", fileNum);
EXPECT_EQ(file_handler::read_file(fileName.c_str()), content);
}

View File

@@ -5,6 +5,7 @@
#include "../tests_common.h"
#include "../tests_log_checker.h"
#include <format>
#include <random>
#include <src/logging.h>
@@ -39,7 +40,7 @@ TEST_P(LogLevelsTest, PutMessage) {
std::random_device rand_dev;
std::mt19937_64 rand_gen(rand_dev());
auto test_message = std::to_string(rand_gen()) + std::to_string(rand_gen());
auto test_message = std::format("{}{}", rand_gen(), rand_gen());
BOOST_LOG(logger) << test_message;
ASSERT_TRUE(log_checker::line_contains(log_file, test_message));

Some files were not shown because too many files have changed in this diff Show More