Merge remote-tracking branch 'sunshine/master'
This commit is contained in:
@@ -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
11
.github/copilot-instructions.md
vendored
Normal 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.
|
||||
@@ -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 ()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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 "\
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)**
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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">
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
[](https://community.chocolatey.org/packages/sunshine)
|
||||
|
||||
@@ -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 30–60% packet loss when the host had MTU
|
||||
set to 1500 and 1472, but 0% packet loss with a MTU of 1428 set in the network card
|
||||
serving the stream (a Linux PC). It's unclear how that helped precisely, so it's a last
|
||||
resort suggestion.
|
||||
@@ -134,19 +135,23 @@ Due to legal concerns, Mesa has disabled hardware decoding and encoding by defau
|
||||
Error: Could not open codec [h264_vaapi]: Function not implemented
|
||||
```
|
||||
|
||||
If you see the above error in the Sunshine logs, compiling *Mesa* manually, may be required. See the official Mesa3D
|
||||
If you see the above error in the Sunshine logs, compiling *Mesa* manually may be required. See the official Mesa3D
|
||||
[Compiling and Installing](https://docs.mesa3d.org/install.html) documentation for instructions.
|
||||
|
||||
@important{You must re-enable the disabled encoders. You can do so, by passing the following argument to the build
|
||||
system. You may also want to enable decoders, however that is not required for Sunshine and is not covered here.
|
||||
```bash
|
||||
-Dvideo-codecs=h264enc,h265enc
|
||||
```
|
||||
}
|
||||
> [!IMPORTANT]
|
||||
> You must re-enable the disabled encoders. You can do so by passing the following argument to the build
|
||||
> system. You may also want to enable decoders, however, that is not required for Sunshine and is not covered here.
|
||||
> ```bash
|
||||
> -Dvideo-codecs=h264enc,h265enc
|
||||
> ```
|
||||
|
||||
> [!NOTE]
|
||||
> Other build options are listed in the
|
||||
> [meson options](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/meson_options.txt) file.
|
||||
|
||||
### Input not working
|
||||
After installation, the `udev` rules need to be reloaded. Our post-install script tries to do this for you
|
||||
automatically, but if it fails you may need to restart your system.
|
||||
automatically, but if it fails, you may need to restart your system.
|
||||
|
||||
If the input is still not working, you may need to add your user to the `input` group.
|
||||
|
||||
@@ -154,9 +159,6 @@ If the input is still not working, you may need to add your user to the `input`
|
||||
sudo usermod -aG input $USER
|
||||
```
|
||||
|
||||
@note{Other build options are listed in the
|
||||
[meson options](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/meson_options.txt) file.}
|
||||
|
||||
### KMS Streaming fails
|
||||
If screencasting fails with KMS, you may need to run the following to force unprivileged screencasting.
|
||||
|
||||
@@ -164,9 +166,10 @@ If screencasting fails with KMS, you may need to run the following to force unpr
|
||||
sudo setcap -r $(readlink -f $(which sunshine))
|
||||
```
|
||||
|
||||
@note{The above command will not work with the AppImage or Flatpak packages. Please refer to the
|
||||
[AppImage setup](md_docs_2getting__started.html#appimage) or
|
||||
[Flatpak setup](md_docs_2getting__started.html#flatpak) for more specific instructions.}
|
||||
> [!NOTE]
|
||||
> The above command will not work with the AppImage or Flatpak packages. Please refer to the
|
||||
> [AppImage setup](md_docs_2getting__started.html#appimage) or
|
||||
> [Flatpak setup](md_docs_2getting__started.html#flatpak) for more specific instructions.
|
||||
|
||||
### KMS streaming fails on Nvidia GPUs
|
||||
If KMS screen capture results in a black screen being streamed, you may need to
|
||||
@@ -181,12 +184,12 @@ Consult your distribution's documentation for details on how to do this. (Most
|
||||
often grub is used to load the kernel and set its command line.)
|
||||
|
||||
### AMD encoding latency issues
|
||||
If you notice unexpectedly high encoding latencies (e.g. in Moonlight's
|
||||
If you notice unexpectedly high encoding latencies (e.g., in Moonlight's
|
||||
performance overlay) or strong fluctuations thereof, your system's Mesa
|
||||
libraries are outdated (<24.2). This is particularly problematic at higher
|
||||
resolutions (4K).
|
||||
|
||||
Starting with Mesa-24.2 applications can request a
|
||||
Starting with Mesa-24.2, applications can request a
|
||||
[low-latency mode](https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/30039)
|
||||
by running them with a special
|
||||
[environment variable](https://docs.mesa3d.org/envvars.html#envvar-AMD_DEBUG):
|
||||
@@ -224,7 +227,7 @@ Verify that you've installed [Nefarius Virtual Gamepad](https://github.com/nefar
|
||||
|
||||
### Permission denied
|
||||
Since Sunshine runs as a service on Windows, it may not have the same level of access that your regular user account
|
||||
has. You may get permission denied errors when attempting to launch a game or application from a non system drive.
|
||||
has. You may get permission denied errors when attempting to launch a game or application from a non-system drive.
|
||||
|
||||
You will need to modify the security permissions on your disk. Ensure that user/principal SYSTEM has full
|
||||
permissions on the disk.
|
||||
|
||||
15
package.json
15
package.json
@@ -7,16 +7,17 @@
|
||||
"dev": "vite build --watch",
|
||||
"serve": "serve ./tests/fixtures/http --no-port-switching"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@lizardbyte/shared-web": "2025.626.181239",
|
||||
"vue": "3.5.18",
|
||||
"vue-i18n": "11.1.11"
|
||||
"@lizardbyte/shared-web": "2025.922.181114",
|
||||
"vue": "3.5.22",
|
||||
"vue-i18n": "11.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@codecov/vite-plugin": "1.9.1",
|
||||
"@vitejs/plugin-vue": "4.6.2",
|
||||
"serve": "14.2.4",
|
||||
"vite": "4.5.14",
|
||||
"vite-plugin-ejs": "1.6.4"
|
||||
"@vitejs/plugin-vue": "6.0.1",
|
||||
"serve": "14.2.5",
|
||||
"vite": "6.3.6",
|
||||
"vite-plugin-ejs": "1.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Submodule packaging/linux/flatpak/deps/flatpak-builder-tools updated: 7090720d43...ea92dc22ab
Submodule packaging/linux/flatpak/deps/shared-modules updated: b63062b3cd...231e052557
@@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
// standard includes
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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.
|
||||
|
||||
78
src/main.cpp
78
src/main.cpp
@@ -96,6 +96,45 @@ WINAPI BOOL ConsoleCtrlHandler(DWORD type) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
|
||||
constexpr bool tray_is_enabled = true;
|
||||
#else
|
||||
constexpr bool tray_is_enabled = false;
|
||||
#endif
|
||||
|
||||
void mainThreadLoop(const std::shared_ptr<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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace nvenc {
|
||||
|
||||
autopop_context push_context();
|
||||
|
||||
HMODULE dll = NULL;
|
||||
HMODULE dll = nullptr;
|
||||
const ID3D11DevicePtr d3d_device;
|
||||
ID3D11Texture2DPtr d3d_input_texture;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include <string_view>
|
||||
|
||||
// platform includes
|
||||
#include <windows.h>
|
||||
#include <Windows.h>
|
||||
#include <winnt.h>
|
||||
|
||||
namespace platf {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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 velkém 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": "Nastavení 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ává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í",
|
||||
|
||||
462
src_assets/common/assets/web/public/assets/locale/hu.json
Normal file
462
src_assets/common/assets/web/public/assets/locale/hu.json
Normal 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."
|
||||
}
|
||||
}
|
||||
462
src_assets/common/assets/web/public/assets/locale/vi.json
Normal file
462
src_assets/common/assets/web/public/assets/locale/vi.json
Normal 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."
|
||||
}
|
||||
}
|
||||
@@ -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": "此頁面將很快重新載入,您的瀏覽器會要求您提供新的憑證"
|
||||
|
||||
2
src_assets/linux/misc/60-sunshine.conf
Normal file
2
src_assets/linux/misc/60-sunshine.conf
Normal file
@@ -0,0 +1,2 @@
|
||||
# Sunshine needs uhid for DS5 emulation
|
||||
uhid
|
||||
@@ -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"
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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'"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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}
|
||||
|
||||
667
tests/integration/test_config_consistency.cpp
Normal file
667
tests/integration/test_config_consistency.cpp
Normal 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 ¤tSection) {
|
||||
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 ¤tSection, 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 §ion : 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 §ion : 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";
|
||||
}
|
||||
194
tests/integration/test_external_commands.cpp
Normal file
194
tests/integration/test_external_commands.cpp
Normal 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;
|
||||
}
|
||||
);
|
||||
357
tests/integration/test_locale_consistency.cpp
Normal file
357
tests/integration/test_locale_consistency.cpp
Normal 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";
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
2
third-party/build-deps
vendored
2
third-party/build-deps
vendored
Submodule third-party/build-deps updated: cf5dffaf4c...a9a7f86328
2
third-party/doxyconfig
vendored
2
third-party/doxyconfig
vendored
Submodule third-party/doxyconfig updated: a73f908fb7...1188ef2b96
2
third-party/inputtino
vendored
2
third-party/inputtino
vendored
Submodule third-party/inputtino updated: 83cf70ef33...504f0abc7d
2
third-party/libdisplaydevice
vendored
2
third-party/libdisplaydevice
vendored
Submodule third-party/libdisplaydevice updated: a0674741ec...f31e46d873
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user