Merge remote-tracking branch 'sunshine/master'

This commit is contained in:
Yukino Song
2025-07-13 23:16:01 +08:00
93 changed files with 2952 additions and 493 deletions

View File

@@ -1,11 +0,0 @@
# install dependencies for C++ analysis
set -e
chmod +x ./scripts/linux_build.sh
./scripts/linux_build.sh --skip-package --ubuntu-test-repo
# Delete CUDA
rm -rf ./build/cuda
# skip autobuild
echo "skip_autobuild=true" >> "$GITHUB_OUTPUT"

View File

@@ -1,59 +0,0 @@
# install dependencies for C++ analysis
set -e
# update pacman
pacman --noconfirm -Syu
gcc_version="14.2.0-3"
broken_deps=(
"mingw-w64-ucrt-x86_64-gcc"
"mingw-w64-ucrt-x86_64-gcc-libs"
)
tarballs=""
for dep in "${broken_deps[@]}"; do
tarball="${dep}-${gcc_version}-any.pkg.tar.zst"
# download and install working version
wget https://repo.msys2.org/mingw/ucrt64/${tarball}
tarballs="${tarballs} ${tarball}"
done
# install broken dependencies
if [ -n "$tarballs" ]; then
pacman -U --noconfirm ${tarballs}
fi
# install dependencies
dependencies=(
"git"
"mingw-w64-ucrt-x86_64-cmake"
"mingw-w64-ucrt-x86_64-cppwinrt"
"mingw-w64-ucrt-x86_64-curl-winssl"
"mingw-w64-ucrt-x86_64-MinHook"
"mingw-w64-ucrt-x86_64-miniupnpc"
"mingw-w64-ucrt-x86_64-nlohmann-json"
"mingw-w64-ucrt-x86_64-nodejs"
"mingw-w64-ucrt-x86_64-nsis"
"mingw-w64-ucrt-x86_64-onevpl"
"mingw-w64-ucrt-x86_64-openssl"
"mingw-w64-ucrt-x86_64-opus"
"mingw-w64-ucrt-x86_64-toolchain"
)
pacman -Syu --noconfirm --ignore="$(IFS=,; echo "${broken_deps[*]}")" "${dependencies[@]}"
# build
mkdir -p build
cmake \
-B build \
-G Ninja \
-S . \
-DBUILD_DOCS=OFF \
-DBUILD_WERROR=ON
ninja -C build
# skip autobuild
echo "skip_autobuild=true" >> "$GITHUB_OUTPUT"

View File

@@ -1,32 +0,0 @@
# install dependencies for C++ analysis
set -e
# setup homebrew for x86_64
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
eval "$(/usr/local/bin/brew shellenv)"
# install dependencies
dependencies=(
"cmake"
"miniupnpc"
"ninja"
"node"
"openssl@3"
"opus"
"pkg-config"
)
brew install "${dependencies[@]}"
# build
mkdir -p build
cmake \
-B build \
-G Ninja \
-S . \
-DBOOST_USE_STATIC=OFF \
-DBUILD_DOCS=OFF \
-DBUILD_WERROR=ON
ninja -C build
# skip autobuild
echo "skip_autobuild=true" >> "$GITHUB_OUTPUT"

View File

@@ -3,4 +3,5 @@ filename =
*.py
max-line-length = 120
extend-exclude =
.venv/
venv/

View File

@@ -12,3 +12,4 @@ titleAndCommits: false
anyCommit: false
allowMergeCommits: false
allowRevertCommits: false
targetUrl: https://docs.lizardbyte.dev/latest/developers/contributing.html#creating-a-pull-request

30
.github/workflows/_codeql.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
---
# This workflow is centrally managed in https://github.com/LizardByte/.github/
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
# the above-mentioned repo.
name: CodeQL
permissions:
actions: read
contents: read
security-events: write
on:
push:
branches:
- master
pull_request:
branches:
- master
schedule:
- cron: '00 12 * * 0' # every Sunday at 12:00 UTC
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: true
jobs:
call-codeql:
name: CodeQL
uses: LizardByte/.github/.github/workflows/__call-codeql.yml@master
if: ${{ github.repository != 'LizardByte/.github' }}

27
.github/workflows/_common-lint.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
---
# This workflow is centrally managed in https://github.com/LizardByte/.github/
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
# the above-mentioned repo.
name: common lint
permissions:
contents: read
on:
pull_request:
branches:
- master
types:
- opened
- synchronize
- reopened
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: true
jobs:
lint:
name: Common Lint
uses: LizardByte/.github/.github/workflows/__call-common-lint.yml@master
if: ${{ github.repository != 'LizardByte/.github' }}

23
.github/workflows/_release-notifier.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
---
# This workflow is centrally managed in https://github.com/LizardByte/.github/
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
# the above-mentioned repo.
# Create a blog post for a new release and open a PR to the blog repo
name: Release Notifications
permissions:
contents: read
on:
release:
types:
- released # this triggers when a release is published, but does not include pre-releases or drafts
jobs:
update-blog:
name: Update blog
uses: LizardByte/.github/.github/workflows/__call-release-notifier.yml@master
if: github.repository_owner == 'LizardByte'
secrets:
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}

34
.github/workflows/_update-docs.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
---
# This workflow is centrally managed in https://github.com/LizardByte/.github/
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
# the above-mentioned repo.
# To use, add the `rtd` repository label to identify repositories that should trigger this workflow.
# If the project slug is not the repository name, add a repository variable named `READTHEDOCS_SLUG` with the value of
# the ReadTheDocs project slug.
# Update readthedocs on release events.
name: Update docs
permissions: {}
on:
release:
types:
- created
- edited
- deleted
concurrency:
group: "${{ github.workflow }}-${{ github.event.release.tag_name }}"
cancel-in-progress: true
jobs:
update-docs:
name: Update docs
uses: LizardByte/.github/.github/workflows/__call-update-docs.yml@master
if: github.repository_owner == 'LizardByte'
with:
readthedocs_slug: ${{ vars.READTHEDOCS_SLUG }}
secrets:
READTHEDOCS_TOKEN: ${{ secrets.READTHEDOCS_TOKEN }}

View File

@@ -0,0 +1,29 @@
---
# This workflow is centrally managed in https://github.com/LizardByte/.github/
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
# the above-mentioned repo.
# To use, add the `flathub-pkg` repository label to identify repositories that should trigger this workflow.
# Update Flathub on release events.
name: Update Flathub repo
permissions:
contents: read
on:
release:
types:
- released
concurrency:
group: "${{ github.workflow }}-${{ github.event.release.tag_name }}"
cancel-in-progress: true
jobs:
update-flathub-repo:
name: Update Flathub Repo
uses: LizardByte/.github/.github/workflows/__call-update-flathub-repo.yml@master
if: github.repository_owner == 'LizardByte'
secrets:
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}

View File

@@ -0,0 +1,31 @@
---
# This workflow is centrally managed in https://github.com/LizardByte/.github/
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
# the above-mentioned repo.
# To use, add the `homebrew-pkg` repository label to identify repositories that should trigger this workflow.
# Update Homebrew on release events.
name: Update Homebrew repo
permissions:
contents: read
on:
release:
types:
- released
concurrency:
group: "${{ github.workflow }}-${{ github.event.release.tag_name }}"
cancel-in-progress: true
jobs:
update-homebrew-repo:
name: Update Homebrew repo
uses: LizardByte/.github/.github/workflows/__call-update-homebrew-repo.yml@master
if: github.repository_owner == 'LizardByte'
secrets:
GH_EMAIL: ${{ secrets.GH_BOT_EMAIL }}
GH_USERNAME: ${{ secrets.GH_BOT_NAME }}
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}

View File

@@ -0,0 +1,29 @@
---
# This workflow is centrally managed in https://github.com/LizardByte/.github/
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
# the above-mentioned repo.
# To use, add the `pacman-pkg` repository label to identify repositories that should trigger this workflow.
# Update pacman repo on release events.
name: Update pacman repo
permissions:
contents: read
on:
release:
types:
- released
concurrency:
group: "${{ github.workflow }}-${{ github.event.release.tag_name }}"
cancel-in-progress: true
jobs:
update-homebrew-release:
name: Update pacman repo
uses: LizardByte/.github/.github/workflows/__call-update-pacman-repo.yml@master
if: github.repository_owner == 'LizardByte'
secrets:
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}

View File

@@ -0,0 +1,29 @@
---
# This workflow is centrally managed in https://github.com/LizardByte/.github/
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
# the above-mentioned repo.
# To use, add the `winget-pkg` repository label to identify repositories that should trigger this workflow.
# Update Winget on release events.
name: Update Winget repo
permissions:
contents: read
on:
release:
types:
- released
concurrency:
group: "${{ github.workflow }}-${{ github.event.release.tag_name }}"
cancel-in-progress: true
jobs:
update-winget-repo:
name: Update Winget repo
uses: LizardByte/.github/.github/workflows/__call-update-winget-repo.yml@master
if: github.repository_owner == 'LizardByte'
secrets:
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}

213
.github/workflows/ci-flatpak.yml vendored Normal file
View File

@@ -0,0 +1,213 @@
---
name: CI-Flatpak
permissions:
contents: read
on:
workflow_call:
inputs:
release_commit:
required: true
type: string
release_version:
required: true
type: string
jobs:
build_linux_flatpak:
name: ${{ matrix.arch }}
env:
APP_ID: dev.lizardbyte.app.Sunshine
NODE_VERSION: "20"
PLATFORM_VERSION: "23.08"
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- arch: x86_64
runner: ubuntu-22.04
- arch: aarch64
runner: ubuntu-22.04-arm
steps:
- name: Maximize build space
if: matrix.arch == 'x86_64'
uses: easimon/maximize-build-space@v10
with:
root-reserve-mb: 10240
remove-dotnet: 'true'
remove-android: 'true'
remove-haskell: 'true'
remove-codeql: 'true'
remove-docker-images: 'true'
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup node
id: node
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install npm dependencies
run: npm install --package-lock-only
- name: Debug package-lock.json
run: cat package-lock.json
- name: Setup python
id: python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Setup Dependencies Linux Flatpak
run: |
python -m pip install ./packaging/linux/flatpak/deps/flatpak-builder-tools/node
sudo apt-get update -y
sudo apt-get install -y \
cmake \
flatpak
sudo su $(whoami) -c "flatpak --user remote-add --if-not-exists flathub \
https://flathub.org/repo/flathub.flatpakrepo"
sudo su $(whoami) -c "flatpak --user install -y flathub \
org.flatpak.Builder \
org.freedesktop.Platform/${{ matrix.arch }}/${PLATFORM_VERSION} \
org.freedesktop.Sdk/${{ matrix.arch }}/${PLATFORM_VERSION} \
org.freedesktop.Sdk.Extension.node${NODE_VERSION}/${{ matrix.arch }}/${PLATFORM_VERSION} \
"
flatpak run org.flatpak.Builder --version
- name: flatpak node generator
# https://github.com/flatpak/flatpak-builder-tools/blob/master/node/README.md
run: flatpak-node-generator npm package-lock.json
- name: Debug generated-sources.json
run: cat generated-sources.json
- name: Cache Flatpak build
uses: actions/cache@v4
with:
path: ./build/.flatpak-builder
key: flatpak-${{ matrix.arch }}-${{ github.sha }}
restore-keys: |
flatpak-${{ matrix.arch }}-
- name: Configure Flatpak Manifest
run: |
# variables for manifest
branch="${{ github.head_ref }}"
build_version=${{ inputs.release_version }}
commit=${{ inputs.release_commit }}
# check the branch variable
if [ -z "$branch" ]
then
echo "This is a PUSH event"
branch=${{ github.ref_name }}
clone_url=${{ github.event.repository.clone_url }}
else
echo "This is a PR event"
clone_url=${{ github.event.pull_request.head.repo.clone_url }}
fi
echo "Branch: ${branch}"
echo "Commit: ${commit}"
echo "Clone URL: ${clone_url}"
export BRANCH=${branch}
export BUILD_VERSION=${build_version}
export CLONE_URL=${clone_url}
export COMMIT=${commit}
mkdir -p build
mkdir -p artifacts
cmake -DGITHUB_CLONE_URL=${clone_url} \
-B build \
-S . \
-DSUNSHINE_CONFIGURE_FLATPAK_MAN=ON \
-DSUNSHINE_CONFIGURE_ONLY=ON
- name: Debug Manifest
working-directory: build
run: cat ${APP_ID}.yml
- name: Build Linux Flatpak
working-directory: build
run: |
sudo su $(whoami) -c "flatpak run org.flatpak.Builder \
--arch=${{ matrix.arch }} \
--force-clean \
--repo=repo \
--sandbox \
--stop-at=cuda build-sunshine ${APP_ID}.yml"
cp -r .flatpak-builder copy-of-flatpak-builder
sudo su $(whoami) -c "flatpak run org.flatpak.Builder \
--arch=${{ matrix.arch }} \
--force-clean \
--repo=repo \
--sandbox \
build-sunshine ${APP_ID}.yml"
rm -rf .flatpak-builder
mv copy-of-flatpak-builder .flatpak-builder
sudo su $(whoami) -c "flatpak build-bundle \
--arch=${{ matrix.arch }} \
./repo \
../artifacts/sunshine_${{ matrix.arch }}.flatpak ${APP_ID}"
sudo su $(whoami) -c "flatpak build-bundle \
--runtime \
--arch=${{ matrix.arch }} \
./repo \
../artifacts/sunshine_debug_${{ matrix.arch }}.flatpak ${APP_ID}.Debug"
- name: Lint Flatpak
working-directory: build
run: |
exceptions_file="${{ github.workspace }}/packaging/linux/flatpak/exceptions.json"
echo "Linting flatpak manifest"
flatpak run --command=flatpak-builder-lint org.flatpak.Builder \
--exceptions \
--user-exceptions "${exceptions_file}" \
manifest \
${APP_ID}.yml
echo "Linting flatpak repo"
# TODO: add arg
# --mirror-screenshots-url=https://dl.flathub.org/media \
flatpak run --command=flatpak-builder-lint org.flatpak.Builder \
--exceptions \
--user-exceptions "${exceptions_file}" \
repo \
repo
- name: Package Flathub repo archive
# copy files required to generate the Flathub repo
if: matrix.arch == 'x86_64'
run: |
mkdir -p flathub/modules
cp ./build/generated-sources.json ./flathub/
cp ./build/package-lock.json ./flathub/
cp ./build/${APP_ID}.yml ./flathub/
cp ./build/${APP_ID}.metainfo.xml ./flathub/
cp ./packaging/linux/flatpak/README.md ./flathub/
cp ./packaging/linux/flatpak/flathub.json ./flathub/
cp -r ./packaging/linux/flatpak/modules/. ./flathub/modules/
# submodules will need to be handled in the workflow that creates the PR
# create the archive
tar -czf ./artifacts/flathub.tar.gz -C ./flathub .
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: build-Linux-Flatpak-${{ matrix.arch }}
path: artifacts/
if-no-files-found: error

235
.github/workflows/ci-homebrew.yml vendored Normal file
View File

@@ -0,0 +1,235 @@
---
name: CI-Homebrew
permissions:
contents: read
on:
workflow_call:
inputs:
publish_release:
required: true
type: string
release_commit:
required: true
type: string
release_tag:
required: true
type: string
release_version:
required: true
type: string
secrets:
GH_TOKEN:
required: true
GIT_EMAIL:
required: true
GIT_USERNAME:
required: true
jobs:
build_homebrew:
name: ${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}
runs-on: ${{ matrix.os_name }}-${{ matrix.os_version }}
strategy:
fail-fast: false
matrix:
include:
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories
# while GitHub has larger macOS runners, they are not available for our repos :(
- os_version: "13"
os_name: "macos"
- os_version: "14"
os_name: "macos"
- os_version: "15"
os_name: "macos"
- os_version: "latest"
os_name: "ubuntu"
- os_version: "latest" # this job will only configure the formula for release, no validation
os_name: "ubuntu"
release: true
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Fix homebrew python
if: matrix.os_name == 'macos' && matrix.os_version == '13'
run: |
rm '/usr/local/bin/2to3'
rm '/usr/local/bin/2to3-3.12'
rm '/usr/local/bin/idle3'
rm '/usr/local/bin/idle3.12'
rm '/usr/local/bin/idle3.13'
rm '/usr/local/bin/pip3.12'
rm '/usr/local/bin/pip3.13'
rm '/usr/local/bin/pydoc3'
rm '/usr/local/bin/pydoc3.12'
rm '/usr/local/bin/pydoc3.13'
rm '/usr/local/bin/python3'
rm '/usr/local/bin/python3.12'
rm '/usr/local/bin/python3.13'
rm '/usr/local/bin/python3-config'
rm '/usr/local/bin/python3.12-config'
rm '/usr/local/bin/python3.13-config'
brew install python3
- name: Configure formula
run: |
# variables for formula
branch="${{ github.head_ref }}"
build_version=${{ inputs.release_version }}
commit=${{ inputs.release_commit }}
# check the branch variable
if [ -z "$branch" ]
then
echo "This is a PUSH event"
clone_url=${{ github.event.repository.clone_url }}
branch="${{ github.ref_name }}"
default_branch="${{ github.event.repository.default_branch }}"
if [ "${{ matrix.release }}" == "true" ]; then
# we will publish the formula with the release tag
tag="${{ inputs.release_tag }}"
else
tag="${{ github.ref_name }}"
fi
else
echo "This is a PR event"
clone_url=${{ github.event.pull_request.head.repo.clone_url }}
branch="${{ github.event.pull_request.head.ref }}"
default_branch="${{ github.event.pull_request.head.repo.default_branch }}"
tag="${{ github.event.pull_request.head.ref }}"
fi
echo "Branch: ${branch}"
echo "Clone URL: ${clone_url}"
echo "Tag: ${tag}"
export BRANCH=${branch}
export BUILD_VERSION=${build_version}
export CLONE_URL=${clone_url}
export COMMIT=${commit}
export TAG=${tag}
mkdir -p build
cmake \
-B build \
-S . \
-DGITHUB_DEFAULT_BRANCH="${default_branch}" \
-DSUNSHINE_CONFIGURE_HOMEBREW=ON \
-DSUNSHINE_CONFIGURE_ONLY=ON
# copy formula to artifacts
mkdir -p homebrew
cp -f ./build/sunshine.rb ./homebrew/sunshine.rb
# testing
cat ./homebrew/sunshine.rb
- name: Upload Artifacts
if: matrix.release
uses: actions/upload-artifact@v4
with:
name: build-Homebrew
path: homebrew/
if-no-files-found: error
- name: Setup Xvfb
if: matrix.release != true && runner.os == 'Linux'
run: |
sudo apt-get update -y
sudo apt-get install -y \
xvfb
export DISPLAY=:1
Xvfb ${DISPLAY} -screen 0 1024x768x24 &
echo "DISPLAY=${DISPLAY}" >> $GITHUB_ENV
- name: Validate Homebrew Formula
id: test
if: matrix.release != true
uses: LizardByte/actions/actions/release_homebrew@v2025.711.172650
with:
formula_file: ${{ github.workspace }}/homebrew/sunshine.rb
git_email: ${{ secrets.GIT_EMAIL }}
git_username: ${{ secrets.GIT_USERNAME }}
publish: false
token: ${{ secrets.GH_TOKEN }}
validate: true
- name: Setup python
id: python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Generate gcov report
id: test_report
# any except canceled or skipped
# TODO: fix coverage, no .gcno files are being created
# TODO: .gcno files are supposed to be created next to .o files
if: false
# if: >-
# always() &&
# matrix.release != true &&
# (steps.test.outcome == 'success' || steps.test.outcome == 'failure')
run: |
cp -rf ${{ steps.test.outputs.buildpath }}/build/ ./build/
cd build
ls -Ra
${{ steps.python.outputs.python-path }} -m pip install gcovr
${{ steps.python.outputs.python-path }} -m gcovr . -r ../src \
--exclude-noncode-lines \
--exclude-throw-branches \
--exclude-unreachable-branches \
--verbose \
--xml-pretty \
-o coverage.xml
- name: Upload coverage artifact
if: >-
always() &&
matrix.release != true &&
(steps.test.outcome == 'success' || steps.test.outcome == 'failure') &&
startsWith(github.repository, 'LizardByte/')
uses: actions/upload-artifact@v4
with:
name: coverage-Homebrew-${{ matrix.os_name }}-${{ matrix.os_version }}
path: |
build/coverage.xml
${{ steps.test.outputs.testpath }}/test_results.xml
if-no-files-found: error
- name: Patch homebrew formula
# create beta version of the formula
# don't run this on macOS, as the sed command fails
if: matrix.release
run: |
# variables
formula_file="homebrew/sunshine-beta.rb"
# rename the file
mv homebrew/sunshine.rb $formula_file
# update the formula
sed -i 's/class Sunshine < Formula/class SunshineBeta < Formula/' $formula_file
sed -i 's/# conflicts_with/conflicts_with/' $formula_file
# print new file
echo "New formula:"
cat $formula_file
- name: Upload Homebrew Beta Formula
if: >-
github.repository_owner == 'LizardByte' &&
matrix.release &&
inputs.publish_release == 'true'
uses: LizardByte/actions/actions/release_homebrew@v2025.711.172650
with:
formula_file: ${{ github.workspace }}/homebrew/sunshine-beta.rb
git_email: ${{ secrets.GIT_EMAIL }}
git_username: ${{ secrets.GIT_USERNAME }}
publish: true
token: ${{ secrets.GH_TOKEN }}
validate: false

216
.github/workflows/ci-linux.yml vendored Normal file
View File

@@ -0,0 +1,216 @@
---
name: CI-Linux
permissions:
contents: read
on:
workflow_call:
inputs:
release_commit:
required: true
type: string
release_version:
required: true
type: string
jobs:
build_linux:
name: ${{ matrix.name }}
env:
APP_ID: dev.lizardbyte.app.Sunshine
runs-on: ubuntu-${{ matrix.dist }}
strategy:
fail-fast: false
matrix:
include:
- name: AppImage
EXTRA_ARGS: '--appimage-build'
dist: 22.04
steps:
- name: Maximize build space
uses: easimon/maximize-build-space@v10
with:
root-reserve-mb: 30720
remove-dotnet: 'true'
remove-android: 'true'
remove-haskell: 'true'
remove-codeql: 'true'
remove-docker-images: 'true'
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup Dependencies Linux
timeout-minutes: 5
run: |
# create the artifacts directory
mkdir -p artifacts
# allow libfuse2 for appimage on 22.04+
sudo add-apt-repository universe
sudo apt-get install -y \
libdrm-dev \
libfuse2 \
libgl-dev \
libwayland-dev \
libx11-xcb-dev \
libxcb-dri3-dev \
libxfixes-dev
- name: Setup python
id: python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Build latest libva
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
timeout-minutes: 5
run: |
gh release download --archive=tar.gz --repo=intel/libva
tar xzf libva-*.tar.gz && rm libva-*.tar.gz
cd libva-*
./autogen.sh --prefix=/usr --libdir=/usr/lib/x86_64-linux-gnu \
--enable-drm \
--enable-x11 \
--enable-glx \
--enable-wayland \
--without-legacy # emgd, nvctrl, fglrx
make -j $(nproc)
sudo make install
cd .. && rm -rf libva-*
- name: Build Linux
env:
BRANCH: ${{ github.head_ref || github.ref_name }}
BUILD_VERSION: ${{ inputs.release_version }}
COMMIT: ${{ inputs.release_commit }}
run: |
chmod +x ./scripts/linux_build.sh
./scripts/linux_build.sh \
--publisher-name='${{ github.repository_owner }}' \
--publisher-website='https://app.lizardbyte.dev' \
--publisher-issue-url='https://app.lizardbyte.dev/support' \
--skip-cleanup \
--skip-package \
--ubuntu-test-repo ${{ matrix.EXTRA_ARGS }}
- name: Set AppImage Version
if: matrix.name == 'AppImage'
run: |
version=${{ inputs.release_version }}
echo "VERSION=${version}" >> $GITHUB_ENV
- name: Package Linux - AppImage
if: matrix.name == 'AppImage'
working-directory: build
run: |
# install sunshine to the DESTDIR
DESTDIR=AppDir ninja install
# custom AppRun file
cp -f ../packaging/linux/AppImage/AppRun ./AppDir/
chmod +x ./AppDir/AppRun
# variables
DESKTOP_FILE="${DESKTOP_FILE:-${APP_ID}.desktop}"
ICON_FILE="${ICON_FILE:-sunshine.png}"
# AppImage
# https://docs.appimage.org/packaging-guide/index.html
wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
chmod +x linuxdeploy-x86_64.AppImage
# https://github.com/linuxdeploy/linuxdeploy-plugin-gtk
sudo apt-get install libgtk-3-dev librsvg2-dev -y
wget -q https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh
chmod +x linuxdeploy-plugin-gtk.sh
export DEPLOY_GTK_VERSION=3
./linuxdeploy-x86_64.AppImage \
--appdir ./AppDir \
--plugin gtk \
--executable ./sunshine \
--icon-file "../$ICON_FILE" \
--desktop-file "./$DESKTOP_FILE" \
--output appimage
# move
mv Sunshine*.AppImage ../artifacts/sunshine.AppImage
# permissions
chmod +x ../artifacts/sunshine.AppImage
- name: Delete CUDA
# free up space on the runner
run: |
rm -rf ./build/cuda
- name: Verify AppImage
if: matrix.name == 'AppImage'
run: |
wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage
chmod +x appimagelint-x86_64.AppImage
./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage
- name: Install test deps
run: |
sudo apt-get update -y
sudo apt-get install -y \
x11-xserver-utils \
xvfb
# clean apt cache
sudo apt-get clean
sudo rm -rf /var/lib/apt/lists/*
- name: Run tests
id: test
working-directory: build/tests
run: |
export DISPLAY=:1
Xvfb ${DISPLAY} -screen 0 1024x768x24 &
sleep 5 # give Xvfb time to start
./test_sunshine --gtest_color=yes --gtest_output=xml:test_results.xml
- name: Generate gcov report
id: test_report
# any except canceled or skipped
if: >-
always() &&
(steps.test.outcome == 'success' || steps.test.outcome == 'failure')
working-directory: build
run: |
${{ steps.python.outputs.python-path }} -m pip install gcovr
${{ steps.python.outputs.python-path }} -m gcovr . -r ../src \
--exclude-noncode-lines \
--exclude-throw-branches \
--exclude-unreachable-branches \
--verbose \
--xml-pretty \
-o coverage.xml
- name: Upload coverage artifact
if: >-
always() &&
(steps.test_report.outcome == 'success')
uses: actions/upload-artifact@v4
with:
name: coverage-Linux-${{ matrix.name }}
path: |
build/coverage.xml
build/tests/test_results.xml
if-no-files-found: error
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: build-Linux-${{ matrix.name }}
path: artifacts/
if-no-files-found: error

345
.github/workflows/ci-windows.yml vendored Normal file
View File

@@ -0,0 +1,345 @@
---
name: CI-Windows
permissions:
contents: read
on:
workflow_call:
inputs:
release_commit:
required: true
type: string
release_version:
required: true
type: string
secrets:
CODECOV_TOKEN:
required: false
jobs:
build_windows:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
defaults:
run:
shell: msys2 {0}
strategy:
fail-fast: false
matrix:
include:
- name: Windows-AMD64
os: windows-2022
arch: x86_64
msystem: ucrt64
toolchain: ucrt-x86_64
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Prepare tests
id: prepare-tests
if: false # todo: DirectX11 is not available, so even software encoder fails
shell: pwsh
run: |
# function to download and extract a zip file
function DownloadAndExtract {
param (
[string]$Uri,
[string]$OutFile
)
$maxRetries = 5
$retryCount = 0
$success = $false
while (-not $success -and $retryCount -lt $maxRetries) {
$retryCount++
Write-Host "Downloading $Uri to $OutFile, attempt $retryCount of $maxRetries"
try {
Invoke-WebRequest -Uri $Uri -OutFile $OutFile
$success = $true
} catch {
Write-Host "Attempt $retryCount of $maxRetries failed with error: $($_.Exception.Message). Retrying..."
Start-Sleep -Seconds 5
}
}
if (-not $success) {
Write-Host "Failed to download the file after $maxRetries attempts."
exit 1
}
# use .NET to get the base name of the file
$baseName = (Get-Item $OutFile).BaseName
# Extract the zip file
Expand-Archive -Path $OutFile -DestinationPath $baseName
}
# virtual display driver
DownloadAndExtract `
-Uri "https://www.amyuni.com/downloads/usbmmidd_v2.zip" `
-OutFile "usbmmidd_v2.zip"
# install
Set-Location -Path usbmmidd_v2/usbmmidd_v2
./deviceinstaller64 install usbmmidd.inf usbmmidd
# create the virtual display
./deviceinstaller64 enableidd 1
# move up a directory
Set-Location -Path ../..
# install devcon
DownloadAndExtract `
-Uri "https://github.com/Drawbackz/DevCon-Installer/releases/download/1.4-rc/Devcon.Installer.zip" `
-OutFile "Devcon.Installer.zip"
Set-Location -Path Devcon.Installer
# hash needs to match OS version
# https://github.com/Drawbackz/DevCon-Installer/blob/master/devcon_sources.json
Start-Process -FilePath "./Devcon Installer.exe" -Wait -ArgumentList `
'install', `
'-hash', '54004C83EE34F6A55380528A8B29F4C400E61FBB947A19E0AB9E5A193D7D961E', `
'-addpath', `
'-update', `
'-dir', 'C:\Windows\System32'
# disable Hyper-V Video
# https://stackoverflow.com/a/59490940
C:\Windows\System32\devcon.exe disable "VMBUS\{da0a7802-e377-4aac-8e77-0558eb1073f8}"
# move up a directory
Set-Location -Path ..
# multi monitor tool
DownloadAndExtract `
-Uri "http://www.nirsoft.net/utils/multimonitortool-x64.zip" `
-OutFile "multimonitortool.zip"
# enable the virtual display
# http://www.nirsoft.net/utils/multi_monitor_tool.html
Set-Location -Path multimonitortool
# Original Hyper-V is \\.\DISPLAY1, it will recreate itself as \\.\DISPLAY6 (or something higher than 2)
# USB Mobile Monitor Virtual Display is \\.\DISPLAY2
# these don't seem to work if not using runAs
# todo: do they work if not using runAs?
Start-Process powershell -Verb runAs -ArgumentList '-Command ./MultiMonitorTool.exe /enable \\.\DISPLAY2'
Start-Process powershell -Verb runAs -ArgumentList '-Command ./MultiMonitorTool.exe /SetPrimary \\.\DISPLAY2'
# wait a few seconds
Start-Sleep -s 5
# list monitors
./MultiMonitorTool.exe /stext monitor_list.txt
# wait a few seconds
Start-Sleep -s 5
# print the monitor list
Get-Content -Path monitor_list.txt
- name: Setup Dependencies Windows
# if a dependency needs to be pinned, see https://github.com/LizardByte/build-deps/pull/186
uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.msystem }}
update: true
install: >-
wget
- name: Update Windows dependencies
env:
MSYSTEM: ${{ matrix.msystem }}
TOOLCHAIN: ${{ matrix.toolchain }}
shell: msys2 {0}
run: |
# variables
declare -A pinned_deps
# dependencies
dependencies=(
"git"
"mingw-w64-${TOOLCHAIN}-cmake"
"mingw-w64-${TOOLCHAIN}-cppwinrt"
"mingw-w64-${TOOLCHAIN}-curl-winssl"
"mingw-w64-${TOOLCHAIN}-gcc"
"mingw-w64-${TOOLCHAIN}-graphviz"
"mingw-w64-${TOOLCHAIN}-MinHook"
"mingw-w64-${TOOLCHAIN}-miniupnpc"
"mingw-w64-${TOOLCHAIN}-nlohmann-json"
"mingw-w64-${TOOLCHAIN}-nodejs"
"mingw-w64-${TOOLCHAIN}-nsis"
"mingw-w64-${TOOLCHAIN}-onevpl"
"mingw-w64-${TOOLCHAIN}-openssl"
"mingw-w64-${TOOLCHAIN}-opus"
"mingw-w64-${TOOLCHAIN}-toolchain"
)
# do not modify below this line
ignore_packages=()
tarballs=""
for pkg in "${!pinned_deps[@]}"; do
ignore_packages+=("${pkg}")
version="${pinned_deps[$pkg]}"
tarball="${pkg}-${version}-any.pkg.tar.zst"
# download working version
wget "https://repo.msys2.org/mingw/${MSYSTEM}/${tarball}"
tarballs="${tarballs} ${tarball}"
done
# Create the ignore string for pacman
ignore_list=$(IFS=,; echo "${ignore_packages[*]}")
# install pinned dependencies
if [ -n "$tarballs" ]; then
pacman -U --noconfirm ${tarballs}
fi
# Only add --ignore if we have packages to ignore
if [ -n "$ignore_list" ]; then
pacman -Syu --noconfirm --ignore="${ignore_list}" "${dependencies[@]}"
else
pacman -Syu --noconfirm "${dependencies[@]}"
fi
- name: Install Doxygen
# GCC compiled doxygen has issues when running graphviz
env:
DOXYGEN_VERSION: "1.11.0"
shell: pwsh
run: |
# Set version variables
$doxy_ver = $env:DOXYGEN_VERSION
$_doxy_ver = $doxy_ver.Replace(".", "_")
# Download the Doxygen installer
Invoke-WebRequest -Uri `
"https://github.com/doxygen/doxygen/releases/download/Release_${_doxy_ver}/doxygen-${doxy_ver}-setup.exe" `
-OutFile "doxygen-setup.exe"
# Run the installer
Start-Process `
-FilePath .\doxygen-setup.exe `
-ArgumentList `
'/VERYSILENT' `
-Wait `
-NoNewWindow
# Clean up
Remove-Item -Path doxygen-setup.exe
- name: Setup python
id: setup-python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Python Path
id: python-path
shell: msys2 {0}
run: |
# replace backslashes with double backslashes
python_path=$(echo "${{ steps.setup-python.outputs.python-path }}" | sed 's/\\/\\\\/g')
# step output
echo "python-path=${python_path}"
echo "python-path=${python_path}" >> $GITHUB_OUTPUT
- name: Build Windows
shell: msys2 {0}
env:
BRANCH: ${{ github.head_ref || github.ref_name }}
BUILD_VERSION: ${{ inputs.release_version }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
COMMIT: ${{ inputs.release_commit }}
run: |
mkdir -p build
cmake \
-B build \
-G Ninja \
-S . \
-DBUILD_WERROR=ON \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DSUNSHINE_ASSETS_DIR=assets \
-DSUNSHINE_PUBLISHER_NAME='${{ github.repository_owner }}' \
-DSUNSHINE_PUBLISHER_WEBSITE='https://app.lizardbyte.dev' \
-DSUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support'
ninja -C build
- name: Package Windows
shell: msys2 {0}
run: |
mkdir -p artifacts
cd build
# package
cpack -G NSIS
cpack -G ZIP
# move
mv ./cpack_artifacts/Sunshine.exe ../artifacts/Sunshine-${{ matrix.name }}-installer.exe
mv ./cpack_artifacts/Sunshine.zip ../artifacts/Sunshine-${{ matrix.name }}-portable.zip
- name: Run tests
id: test
shell: msys2 {0}
working-directory: build/tests
run: |
./test_sunshine.exe --gtest_color=yes --gtest_output=xml:test_results.xml
- name: Generate gcov report
id: test_report
# any except canceled or skipped
if: always() && (steps.test.outcome == 'success' || steps.test.outcome == 'failure')
shell: msys2 {0}
working-directory: build
run: |
${{ steps.python-path.outputs.python-path }} -m pip install gcovr
${{ steps.python-path.outputs.python-path }} -m gcovr . -r ../src \
--exclude-noncode-lines \
--exclude-throw-branches \
--exclude-unreachable-branches \
--verbose \
--xml-pretty \
-o coverage.xml
- name: Upload coverage artifact
if: >-
always() &&
(steps.test_report.outcome == 'success')
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.name }}
path: |
build/coverage.xml
build/tests/test_results.xml
if-no-files-found: error
- name: Package Windows Debug Info
shell: pwsh
working-directory: build
run: |
# use .dbg file extension for binaries to avoid confusion with real packages
Get-ChildItem -File -Recurse | `
% { Rename-Item -Path $_.PSPath -NewName $_.Name.Replace(".exe",".dbg") }
# save the binaries with debug info
7z -r `
"-xr!CMakeFiles" `
"-xr!cpack_artifacts" `
a "../artifacts/Sunshine-${{ matrix.name }}-debuginfo.7z" "*.dbg"
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: build-${{ matrix.name }}
path: artifacts/
if-no-files-found: error

214
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,214 @@
---
name: CI
permissions:
contents: read
on:
pull_request:
branches:
- master
types:
- opened
- synchronize
- reopened
push:
branches:
- master
workflow_dispatch:
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: true
jobs:
github-env:
name: GitHub Env Debug
uses: LizardByte/.github/.github/workflows/__call-github-env.yml@master
release-setup:
name: Release Setup
outputs:
publish_release: ${{ steps.release-setup.outputs.publish_release }}
release_body: ${{ steps.release-setup.outputs.release_body }}
release_commit: ${{ steps.release-setup.outputs.release_commit }}
release_generate_release_notes: ${{ steps.release-setup.outputs.release_generate_release_notes }}
release_tag: ${{ steps.release-setup.outputs.release_tag }}
release_version: ${{ steps.release-setup.outputs.release_version }}
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Release Setup
id: release-setup
uses: LizardByte/actions/actions/release_setup@v2025.711.172650
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
build-docker:
name: Docker
needs: release-setup
permissions:
contents: read
packages: write
uses: LizardByte/.github/.github/workflows/__call-docker.yml@master
with:
maximize_build_space: true
publish_release: ${{ needs.release-setup.outputs.publish_release }}
release_commit: ${{ needs.release-setup.outputs.release_commit }}
release_tag: ${{ needs.release-setup.outputs.release_tag }}
release_version: ${{ needs.release-setup.outputs.release_version }}
secrets:
DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
DOCKER_HUB_PASSWORD: ${{ secrets.DOCKER_HUB_PASSWORD }}
DOCKER_HUB_ACCESS_TOKEN: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
GH_BOT_NAME: ${{ secrets.GH_BOT_NAME }}
GH_BOT_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-homebrew:
name: Homebrew
needs: release-setup
uses: ./.github/workflows/ci-homebrew.yml
with:
publish_release: ${{ needs.release-setup.outputs.publish_release }}
release_commit: ${{ needs.release-setup.outputs.release_commit }}
release_tag: ${{ needs.release-setup.outputs.release_tag }}
release_version: ${{ needs.release-setup.outputs.release_version }}
secrets:
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
GIT_EMAIL: ${{ secrets.GH_BOT_EMAIL }}
GIT_USERNAME: ${{ secrets.GH_BOT_NAME }}
build-linux:
name: Linux
needs: release-setup
uses: ./.github/workflows/ci-linux.yml
with:
release_commit: ${{ needs.release-setup.outputs.release_commit }}
release_version: ${{ needs.release-setup.outputs.release_version }}
build-linux-copr:
name: Linux Copr
if: github.event_name != 'push' # releases are handled directly in ci-copr.yml
needs: release-setup
uses: ./.github/workflows/ci-copr.yml
secrets:
COPR_BETA_WEBHOOK_TOKEN: ${{ secrets.COPR_BETA_WEBHOOK_TOKEN }}
COPR_STABLE_WEBHOOK_TOKEN: ${{ secrets.COPR_STABLE_WEBHOOK_TOKEN }}
COPR_CLI_CONFIG: ${{ secrets.COPR_CLI_CONFIG }}
build-linux-flatpak:
name: Linux Flatpak
needs: release-setup
uses: ./.github/workflows/ci-flatpak.yml
with:
release_commit: ${{ needs.release-setup.outputs.release_commit }}
release_version: ${{ needs.release-setup.outputs.release_version }}
build-windows:
name: Windows
needs: release-setup
uses: ./.github/workflows/ci-windows.yml
with:
release_commit: ${{ needs.release-setup.outputs.release_commit }}
release_version: ${{ needs.release-setup.outputs.release_version }}
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
coverage:
name: Coverage-${{ matrix.name }}
if: >-
always() &&
!cancelled() &&
startsWith(github.repository, 'LizardByte/')
needs:
- build-linux
- build-linux-flatpak
- build-homebrew
- build-windows
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- name: Linux-AppImage
coverage: true
- name: Homebrew-macos-13
coverage: false
- name: Homebrew-macos-14
coverage: false
- name: Homebrew-macos-15
coverage: false
- name: Homebrew-ubuntu-latest
coverage: false
- name: Windows-AMD64
coverage: true
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download coverage artifact
uses: actions/download-artifact@v4
with:
name: coverage-${{ matrix.name }}
path: _coverage
- name: Upload test results
uses: codecov/test-results-action@v1
with:
disable_search: true
fail_ci_if_error: true
files: ./_coverage/tests/test_results.xml
flags: ${{ matrix.name }}
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true
- name: Upload coverage
uses: codecov/codecov-action@v5
if: matrix.coverage != false
with:
disable_search: true
fail_ci_if_error: true
files: ./_coverage/coverage.xml
flags: ${{ matrix.name }}
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true
release:
name: Release
if:
needs.release-setup.outputs.publish_release == 'true' &&
startsWith(github.repository, 'LizardByte/')
needs:
- release-setup
- build-docker
- build-linux
- build-linux-flatpak
- build-homebrew
- build-windows
runs-on: ubuntu-latest
steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
pattern: build-*
merge-multiple: true
- name: Debug artifacts
run: ls -l artifacts
- name: Create/Update GitHub Release
uses: LizardByte/actions/actions/release_create@v2025.711.172650
with:
allowUpdates: false
body: ${{ needs.release-setup.outputs.release_body }}
generateReleaseNotes: ${{ needs.release-setup.outputs.release_generate_release_notes }}
name: ${{ needs.release-setup.outputs.release_tag }}
prerelease: true
tag: ${{ needs.release-setup.outputs.release_tag }}
token: ${{ secrets.GH_BOT_TOKEN }}
virustotal_api_key: ${{ secrets.VIRUSTOTAL_API_KEY }}

View File

@@ -52,9 +52,6 @@ include_directories(BEFORE SYSTEM "${CMAKE_SOURCE_DIR}/third-party/nv-codec-head
file(GLOB NVENC_SOURCES CONFIGURE_DEPENDS "src/nvenc/*.cpp" "src/nvenc/*.h")
list(APPEND PLATFORM_TARGET_FILES ${NVENC_SOURCES})
configure_file("${CMAKE_SOURCE_DIR}/src/version.h.in" version.h @ONLY)
include_directories(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") # required for importing version.h
set(SUNSHINE_TARGET_FILES
"${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c/src/Input.h"
"${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c/src/Rtsp.h"

View File

@@ -85,18 +85,27 @@ if(CUDA_FOUND)
add_compile_definitions(SUNSHINE_BUILD_CUDA)
endif()
# drm
if(${SUNSHINE_ENABLE_DRM})
# libdrm is required for both DRM (KMS) and Wayland
if(${SUNSHINE_ENABLE_DRM} OR ${SUNSHINE_ENABLE_WAYLAND})
find_package(LIBDRM REQUIRED)
find_package(LIBCAP REQUIRED)
else()
set(LIBDRM_FOUND OFF)
endif()
if(LIBDRM_FOUND)
include_directories(SYSTEM ${LIBDRM_INCLUDE_DIRS})
list(APPEND PLATFORM_LIBRARIES ${LIBDRM_LIBRARIES})
endif()
# drm
if(${SUNSHINE_ENABLE_DRM})
find_package(LIBCAP REQUIRED)
else()
set(LIBCAP_FOUND OFF)
endif()
if(LIBDRM_FOUND AND LIBCAP_FOUND)
add_compile_definitions(SUNSHINE_BUILD_DRM)
include_directories(SYSTEM ${LIBDRM_INCLUDE_DIRS} ${LIBCAP_INCLUDE_DIRS})
list(APPEND PLATFORM_LIBRARIES ${LIBDRM_LIBRARIES} ${LIBCAP_LIBRARIES})
include_directories(SYSTEM ${LIBCAP_INCLUDE_DIRS})
list(APPEND PLATFORM_LIBRARIES ${LIBCAP_LIBRARIES})
list(APPEND PLATFORM_TARGET_FILES
"${CMAKE_SOURCE_DIR}/src/platform/linux/kmsgrab.cpp")
list(APPEND SUNSHINE_DEFINITIONS EGL_NO_X11=1)

View File

@@ -38,10 +38,17 @@ if(NOT DEFINED SUNSHINE_ICON_PATH)
set(SUNSHINE_ICON_PATH "${CMAKE_SOURCE_DIR}/apollo.ico")
endif()
configure_file("${CMAKE_SOURCE_DIR}/src/platform/windows/windows.rc.in" windows.rc @ONLY)
# Create a separate object library for the RC file with minimal includes
add_library(sunshine_rc_object OBJECT "${CMAKE_SOURCE_DIR}/src/platform/windows/windows.rc")
# Set minimal properties for RC compilation - only what's needed for the resource file
# Otherwise compilation can fail due to "line too long" errors
set_target_properties(sunshine_rc_object PROPERTIES
COMPILE_DEFINITIONS "PROJECT_ICON_PATH=${SUNSHINE_ICON_PATH};PROJECT_NAME=${PROJECT_NAME};PROJECT_VENDOR=${SUNSHINE_PUBLISHER_NAME};PROJECT_VERSION=${PROJECT_VERSION};PROJECT_VERSION_MAJOR=${PROJECT_VERSION_MAJOR};PROJECT_VERSION_MINOR=${PROJECT_VERSION_MINOR};PROJECT_VERSION_PATCH=${PROJECT_VERSION_PATCH}" # cmake-lint: disable=C0301
INCLUDE_DIRECTORIES ""
)
set(PLATFORM_TARGET_FILES
"${CMAKE_CURRENT_BINARY_DIR}/windows.rc"
"${CMAKE_SOURCE_DIR}/src/platform/windows/publish.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/windows/misc.h"
"${CMAKE_SOURCE_DIR}/src/platform/windows/misc.cpp"

View File

@@ -3,7 +3,10 @@
# common cpack options
set(CPACK_PACKAGE_NAME ${CMAKE_PROJECT_NAME})
set(CPACK_PACKAGE_VENDOR "SudoMaker")
string(REGEX REPLACE "^v" "" CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) # remove the v prefix if it exists
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
set(CPACK_PACKAGE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/cpack_artifacts)
set(CPACK_PACKAGE_CONTACT "https://www.sudomaker.com")
set(CPACK_PACKAGE_DESCRIPTION ${CMAKE_PROJECT_DESCRIPTION})

View File

@@ -136,26 +136,13 @@ if(${SUNSHINE_TRAY} STREQUAL 1)
endif()
# desktop file
# todo - validate desktop files with `desktop-file-validate`
if(NOT ${SUNSHINE_BUILD_FLATPAK})
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.desktop"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
else()
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.desktop"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications"
RENAME "${PROJECT_FQDN}.desktop")
endif()
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_FQDN}.desktop"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
if(NOT ${SUNSHINE_BUILD_APPIMAGE} AND NOT ${SUNSHINE_BUILD_FLATPAK})
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine_terminal.desktop"
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_FQDN}.terminal.desktop"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
endif()
# metadata file
# todo - validate file with `appstream-util validate-relax`
if(NOT ${SUNSHINE_BUILD_FLATPAK})
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.appdata.xml"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo")
else()
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_FQDN}.metainfo.xml"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo")
endif()
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_FQDN}.metainfo.xml"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo")

View File

@@ -1,18 +1,34 @@
# Set build variables if env variables are defined
# These are used in configured files such as manifests for different packages
if(DEFINED ENV{BRANCH}) # cmake-lint: disable=W0106
set(GITHUB_BRANCH $ENV{BRANCH})
endif()
if(DEFINED ENV{BUILD_VERSION}) # cmake-lint: disable=W0106
set(BUILD_VERSION $ENV{BUILD_VERSION})
endif()
if(DEFINED ENV{CLONE_URL}) # cmake-lint: disable=W0106
set(GITHUB_CLONE_URL $ENV{CLONE_URL})
endif()
if(DEFINED ENV{COMMIT}) # cmake-lint: disable=W0106
set(GITHUB_COMMIT $ENV{COMMIT})
endif()
if(DEFINED ENV{TAG}) # cmake-lint: disable=W0106
set(GITHUB_TAG $ENV{TAG})
endif()
# Check if env vars are defined before attempting to access them, variables will be defined even if blank
if((DEFINED ENV{BRANCH}) AND (DEFINED ENV{BUILD_VERSION}) AND (DEFINED ENV{COMMIT})) # cmake-lint: disable=W0106
if(($ENV{BRANCH} STREQUAL "master") AND (NOT $ENV{BUILD_VERSION} STREQUAL ""))
# If BRANCH is "master" and BUILD_VERSION is not empty, then we are building a master branch
MESSAGE("Got from CI master branch and version $ENV{BUILD_VERSION}")
if((DEFINED ENV{BRANCH}) AND (DEFINED ENV{BUILD_VERSION})) # cmake-lint: disable=W0106
if((DEFINED ENV{BRANCH}) AND (NOT $ENV{BUILD_VERSION} STREQUAL ""))
# If BRANCH is defined and BUILD_VERSION is not empty, then we are building from CI
# If BRANCH is master we are building a push/release build
MESSAGE("Got from CI '$ENV{BRANCH}' branch and version '$ENV{BUILD_VERSION}'")
set(PROJECT_VERSION $ENV{BUILD_VERSION})
string(REGEX REPLACE "^v" "" PROJECT_VERSION ${PROJECT_VERSION}) # remove the v prefix if it exists
set(CMAKE_PROJECT_VERSION ${PROJECT_VERSION}) # cpack will use this to set the binary versions
elseif((DEFINED ENV{BRANCH}) AND (DEFINED ENV{COMMIT}))
# If BRANCH is set but not BUILD_VERSION we are building a PR, we gather only the commit hash
MESSAGE("Got from CI $ENV{BRANCH} branch and commit $ENV{COMMIT}")
set(PROJECT_VERSION ${PROJECT_VERSION}.$ENV{COMMIT})
endif()
else()
# Generate Sunshine Version based of the git tag
# https://github.com/nocnokneo/cmake-git-versioning-example/blob/master/LICENSE
else()
find_package(Git)
if(GIT_EXECUTABLE)
MESSAGE("${CMAKE_SOURCE_DIR}")
@@ -54,3 +70,72 @@ else()
MESSAGE(WARNING ": Git not found, cannot find git version")
endif()
endif()
# set date variables
set(PROJECT_YEAR "1990")
set(PROJECT_MONTH "01")
set(PROJECT_DAY "01")
# Extract year, month, and day (do this AFTER version parsing)
# Note: Cmake doesn't support "{}" regex syntax
if(PROJECT_VERSION MATCHES "^([0-9][0-9][0-9][0-9])\\.([0-9][0-9][0-9][0-9]?)\\.([0-9]+)$")
message("Extracting year and month/day from PROJECT_VERSION: ${PROJECT_VERSION}")
# First capture group is the year
set(PROJECT_YEAR "${CMAKE_MATCH_1}")
# Second capture group contains month and day
set(MONTH_DAY "${CMAKE_MATCH_2}")
# Extract month (first 1-2 digits) and day (last 2 digits)
string(LENGTH "${MONTH_DAY}" MONTH_DAY_LENGTH)
if(MONTH_DAY_LENGTH EQUAL 3)
# Format: MDD (e.g., 703 = month 7, day 03)
string(SUBSTRING "${MONTH_DAY}" 0 1 PROJECT_MONTH)
string(SUBSTRING "${MONTH_DAY}" 1 2 PROJECT_DAY)
elseif(MONTH_DAY_LENGTH EQUAL 4)
# Format: MMDD (e.g., 1203 = month 12, day 03)
string(SUBSTRING "${MONTH_DAY}" 0 2 PROJECT_MONTH)
string(SUBSTRING "${MONTH_DAY}" 2 2 PROJECT_DAY)
endif()
# Ensure month is two digits
if(PROJECT_MONTH LESS 10 AND NOT PROJECT_MONTH MATCHES "^0")
set(PROJECT_MONTH "0${PROJECT_MONTH}")
endif()
# Ensure day is two digits
if(PROJECT_DAY LESS 10 AND NOT PROJECT_DAY MATCHES "^0")
set(PROJECT_DAY "0${PROJECT_DAY}")
endif()
endif()
# Parse PROJECT_VERSION to extract major, minor, and patch components
if(PROJECT_VERSION MATCHES "([0-9]+)\\.([0-9]+)\\.([0-9]+)")
set(PROJECT_VERSION_MAJOR "${CMAKE_MATCH_1}")
set(CMAKE_PROJECT_VERSION_MAJOR "${CMAKE_MATCH_1}")
set(PROJECT_VERSION_MINOR "${CMAKE_MATCH_2}")
set(CMAKE_PROJECT_VERSION_MINOR "${CMAKE_MATCH_2}")
set(PROJECT_VERSION_PATCH "${CMAKE_MATCH_3}")
set(CMAKE_PROJECT_VERSION_PATCH "${CMAKE_MATCH_3}")
endif()
message("PROJECT_NAME: ${PROJECT_NAME}")
message("PROJECT_VERSION: ${PROJECT_VERSION}")
message("PROJECT_VERSION_MAJOR: ${PROJECT_VERSION_MAJOR}")
message("PROJECT_VERSION_MINOR: ${PROJECT_VERSION_MINOR}")
message("PROJECT_VERSION_PATCH: ${PROJECT_VERSION_PATCH}")
message("CMAKE_PROJECT_VERSION: ${CMAKE_PROJECT_VERSION}")
message("CMAKE_PROJECT_VERSION_MAJOR: ${CMAKE_PROJECT_VERSION_MAJOR}")
message("CMAKE_PROJECT_VERSION_MINOR: ${CMAKE_PROJECT_VERSION_MINOR}")
message("CMAKE_PROJECT_VERSION_PATCH: ${CMAKE_PROJECT_VERSION_PATCH}")
message("PROJECT_YEAR: ${PROJECT_YEAR}")
message("PROJECT_MONTH: ${PROJECT_MONTH}")
message("PROJECT_DAY: ${PROJECT_DAY}")
list(APPEND SUNSHINE_DEFINITIONS PROJECT_NAME="${PROJECT_NAME}")
list(APPEND SUNSHINE_DEFINITIONS PROJECT_VERSION="${PROJECT_VERSION}")
list(APPEND SUNSHINE_DEFINITIONS PROJECT_VERSION_MAJOR="${PROJECT_VERSION_MAJOR}")
list(APPEND SUNSHINE_DEFINITIONS PROJECT_VERSION_MINOR="${PROJECT_VERSION_MINOR}")
list(APPEND SUNSHINE_DEFINITIONS PROJECT_VERSION_PATCH="${PROJECT_VERSION_PATCH}")
list(APPEND SUNSHINE_DEFINITIONS PROJECT_VERSION_COMMIT="${GITHUB_COMMIT}")

View File

@@ -12,19 +12,17 @@ elseif(UNIX)
# configure the .desktop file
set(SUNSHINE_DESKTOP_ICON "apollo.svg")
if(${SUNSHINE_BUILD_APPIMAGE})
configure_file(packaging/linux/AppImage/sunshine.desktop sunshine.desktop @ONLY)
configure_file(packaging/linux/AppImage/${PROJECT_FQDN}.desktop ${PROJECT_FQDN}.desktop @ONLY)
elseif(${SUNSHINE_BUILD_FLATPAK})
set(SUNSHINE_DESKTOP_ICON "${PROJECT_FQDN}")
configure_file(packaging/linux/flatpak/sunshine.desktop sunshine.desktop @ONLY)
configure_file(packaging/linux/flatpak/${PROJECT_FQDN}.metainfo.xml
${PROJECT_FQDN}.metainfo.xml @ONLY)
configure_file(packaging/linux/flatpak/${PROJECT_FQDN}.desktop ${PROJECT_FQDN}.desktop @ONLY)
else()
configure_file(packaging/linux/sunshine.desktop sunshine.desktop @ONLY)
configure_file(packaging/linux/sunshine_terminal.desktop sunshine_terminal.desktop @ONLY)
configure_file(packaging/linux/${PROJECT_FQDN}.desktop ${PROJECT_FQDN}.desktop @ONLY)
configure_file(packaging/linux/${PROJECT_FQDN}.terminal.desktop ${PROJECT_FQDN}.terminal.desktop @ONLY)
endif()
# configure metadata file
configure_file(packaging/linux/sunshine.appdata.xml sunshine.appdata.xml @ONLY)
configure_file(packaging/linux/${PROJECT_FQDN}.metainfo.xml ${PROJECT_FQDN}.metainfo.xml @ONLY)
# configure service
configure_file(packaging/linux/sunshine.service.in sunshine.service @ONLY)
@@ -38,8 +36,6 @@ elseif(UNIX)
# configure the flatpak manifest
if(${SUNSHINE_CONFIGURE_FLATPAK_MAN})
configure_file(packaging/linux/flatpak/${PROJECT_FQDN}.yml ${PROJECT_FQDN}.yml @ONLY)
configure_file(packaging/linux/flatpak/${PROJECT_FQDN}.metainfo.xml
${PROJECT_FQDN}.metainfo.xml @ONLY)
file(COPY packaging/linux/flatpak/deps/ DESTINATION ${CMAKE_BINARY_DIR})
file(COPY packaging/linux/flatpak/modules DESTINATION ${CMAKE_BINARY_DIR})
file(COPY generated-sources.json DESTINATION ${CMAKE_BINARY_DIR})

View File

@@ -3,5 +3,6 @@ set_target_properties(sunshine PROPERTIES LINK_SEARCH_START_STATIC 1)
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll")
find_library(ZLIB ZLIB1)
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
$<TARGET_OBJECTS:sunshine_rc_object>
Windowsapp.lib
Wtsapi32.lib)

View File

@@ -17,7 +17,9 @@
"two_letters_code": {
# map non-two letter codes here, left side is crowdin designation, right side is babel designation
"en-GB": "en_GB",
"en-US": "en_US"
"en-US": "en_US",
"pt-BR": "pt_BR",
"zh-TW": "zh_TW"
}
},
"update_option": "update_as_unapproved"

View File

@@ -28,6 +28,7 @@ ARG CLONE_URL
ENV BRANCH=${BRANCH}
ENV BUILD_VERSION=${BUILD_VERSION}
ENV COMMIT=${COMMIT}
ENV CLONE_URL=${CLONE_URL}
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
@@ -68,19 +69,16 @@ WORKDIR /build/sunshine/build
RUN <<_MAKE
#!/bin/bash
set -e
if [[ "${BUILD_VERSION}" == '' ]]; then
sub_version=""
if [[ "${BRANCH}" != "master" ]]; then
sub_version=".r${COMMIT}"
else
sub_version=""
fi
cmake \
-DSUNSHINE_CONFIGURE_ONLY=ON \
-DSUNSHINE_CONFIGURE_PKGBUILD=ON \
-DSUNSHINE_SUB_VERSION="${sub_version}" \
-DGITHUB_CLONE_URL="${CLONE_URL}" \
-DGITHUB_BRANCH=${BRANCH} \
-DGITHUB_BUILD_VERSION=${BUILD_VERSION} \
-DGITHUB_COMMIT="${COMMIT}" \
-DSUNSHINE_CONFIGURE_ONLY=ON \
/build/sunshine
_MAKE
@@ -114,14 +112,13 @@ rm -f /build/sunshine/pkg/sunshine-debug*.pkg.tar.zst
ls -a
_PKGBUILD
FROM scratch AS artifacts
COPY --link --from=sunshine-build /build/sunshine/pkg/sunshine*.pkg.tar.zst /sunshine.pkg.tar.zst
COPY --link --from=sunshine-build /build/sunshine/sunshine.pkg.tar.gz /sunshine.pkg.tar.gz
FROM sunshine-base AS sunshine
COPY --link --from=artifacts /sunshine.pkg.tar.zst /
COPY --link --from=sunshine-build /build/sunshine/pkg/sunshine*.pkg.tar.zst /sunshine.pkg.tar.zst
# artifacts to be extracted in CI
COPY --link --from=sunshine-build /build/sunshine/pkg/sunshine*.pkg.tar.zst /artifacts/sunshine.pkg.tar.zst
COPY --link --from=sunshine-build /build/sunshine/sunshine.pkg.tar.gz /artifacts/sunshine.pkg.tar.gz
# install sunshine
RUN <<_INSTALL_SUNSHINE

View File

@@ -51,16 +51,17 @@ Xvfb ${DISPLAY} -screen 0 1024x768x24 &
./test_sunshine --gtest_color=yes
_TEST
FROM scratch AS artifacts
FROM sunshine-base AS sunshine
ARG BASE
ARG TAG
ARG TARGETARCH
COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb
FROM sunshine-base AS sunshine
# artifacts to be extracted in CI
COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /artifacts/sunshine-${BASE}-${TAG}-${TARGETARCH}.deb
# copy deb from builder
COPY --link --from=artifacts /sunshine*.deb /sunshine.deb
COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine.deb
# install sunshine
RUN <<_INSTALL_SUNSHINE

View File

@@ -51,16 +51,17 @@ Xvfb ${DISPLAY} -screen 0 1024x768x24 &
./test_sunshine --gtest_color=yes
_TEST
FROM scratch AS artifacts
FROM sunshine-base AS sunshine
ARG BASE
ARG TAG
ARG TARGETARCH
COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb
FROM sunshine-base AS sunshine
# artifacts to be extracted in CI
COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /artifacts/sunshine-${BASE}-${TAG}-${TARGETARCH}.deb
# copy deb from builder
COPY --link --from=artifacts /sunshine*.deb /sunshine.deb
COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine.deb
# install sunshine
RUN <<_INSTALL_SUNSHINE

View File

@@ -51,16 +51,17 @@ Xvfb ${DISPLAY} -screen 0 1024x768x24 &
./test_sunshine --gtest_color=yes
_TEST
FROM scratch AS artifacts
FROM sunshine-base AS sunshine
ARG BASE
ARG TAG
ARG TARGETARCH
COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb
FROM sunshine-base AS sunshine
# artifacts to be extracted in CI
COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /artifacts/sunshine-${BASE}-${TAG}-${TARGETARCH}.deb
# copy deb from builder
COPY --link --from=artifacts /sunshine*.deb /sunshine.deb
COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine.deb
# install sunshine
RUN <<_INSTALL_SUNSHINE

View File

@@ -9,7 +9,7 @@ function generateExamples(endpoint, method, body = null) {
}
return {
cURL: `curl -u user:pass -X ${method.trim()} -k https://localhost:47990${endpoint.trim()}${curlBodyString}`,
cURL: `curl -u user:pass -H "Content-Type: application/json" -X ${method.trim()} -k https://localhost:47990${endpoint.trim()}${curlBodyString}`,
Python: `import json
import requests
from requests.auth import HTTPBasicAuth
@@ -30,6 +30,7 @@ requests.${method.trim().toLowerCase()}(
.then(data => console.log(data));`,
PowerShell: `Invoke-RestMethod \`
-SkipCertificateCheck \`
-ContentType 'application/json' \`
-Uri 'https://localhost:47990${endpoint.trim()}' \`
-Method ${method.trim()} \`
-Headers @{Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes('user:pass'))}

View File

@@ -57,10 +57,14 @@ editing the `conf` file in a text editor. Use the examples as reference.
@endcode</td>
</tr>
<tr>
<td rowspan="18">Choices</td>
<td rowspan="20">Choices</td>
<td>bg</td>
<td>Bulgarian</td>
</tr>
<tr>
<td>cs</td>
<td>Czech</td>
</tr>
<tr>
<td>de</td>
<td>German</td>
@@ -129,6 +133,10 @@ editing the `conf` file in a text editor. Use the examples as reference.
<td>zh</td>
<td>Chinese (Simplified)</td>
</tr>
<tr>
<td>zh_TW</td>
<td>Chinese (Traditional)</td>
</tr>
</table>
### sunshine_name
@@ -1384,35 +1392,6 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr>
</table>
### min_fps_factor
<table>
<tr>
<td>Description</td>
<td colspan="2">
Sunshine will use this factor to calculate the minimum time between frames. Increasing this value may help
when streaming mostly static content.
@warning{Higher values will consume more bandwidth.}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}
1
@endcode</td>
</tr>
<tr>
<td>Range</td>
<td colspan="2">1-3</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
min_fps_factor = 1
@endcode</td>
</tr>
</table>
## Network
### upnp

View File

@@ -56,27 +56,28 @@ need to install CUDA.}
</tr>
<tr>
<td rowspan="1">12.0.0</td>
<td rowspan="2">525.60.13</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>
</tr>
<tr>
<td rowspan="1">12.5.1</td>
<td>sunshine.pkg.tar.zst</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 40/41)</td>
<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
@@ -283,7 +284,7 @@ brew uninstall sunshine
#### Installer (recommended)
1. Download and install
[sunshine-windows-installer.exe](https://github.com/LizardByte/Sunshine/releases/latest/download/sunshine-windows-installer.exe)
[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.}
@@ -297,7 +298,7 @@ overflow menu. Different versions of Windows may provide slightly different step
recommended for most users. No support will be provided!}
1. Download and extract
[sunshine-windows-portable.zip](https://github.com/LizardByte/Sunshine/releases/latest/download/sunshine-windows-portable.zip)
[Sunshine-Windows-AMD64-portable.zip](https://github.com/LizardByte/Sunshine/releases/latest/download/Sunshine-Windows-AMD64-portable.zip)
2. Open command prompt as administrator
3. Firewall rules

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

View File

@@ -8,14 +8,14 @@
"serve": "serve ./tests/fixtures/http --no-port-switching"
},
"dependencies": {
"@lizardbyte/shared-web": "2025.326.11214",
"vue": "3.5.14",
"vue-i18n": "11.1.4"
"@lizardbyte/shared-web": "2025.626.181239",
"vue": "3.5.17",
"vue-i18n": "11.1.9"
},
"devDependencies": {
"@codecov/vite-plugin": "1.9.0",
"@codecov/vite-plugin": "1.9.1",
"@vitejs/plugin-vue": "4.6.2",
"serve": "14.2.3",
"serve": "14.2.4",
"vite": "4.5.14",
"vite-plugin-ejs": "1.6.4"
}

View File

@@ -1,13 +1,13 @@
[Desktop Entry]
Type=Application
Name=@PROJECT_NAME@
Exec=sunshine
Version=1.0
Categories=RemoteAccess;Network;
Comment=@PROJECT_DESCRIPTION@
Exec=sunshine
Icon=sunshine
Keywords=gamestream;stream;moonlight;remote play;
Categories=AudioVideo;Network;RemoteAccess;
Name=@PROJECT_NAME@
Terminal=true
Type=Application
Version=1.0
X-AppImage-Arch=x86_64
X-AppImage-Name=sunshine
X-AppImage-Version=@PROJECT_VERSION@
X-AppImage-Arch=x86_64

View File

@@ -10,7 +10,7 @@ url=@PROJECT_HOMEPAGE_URL@
license=('GPL-3.0-only')
install=sunshine.install
_gcc_version=13
_gcc_version=14
depends=(
'avahi'
@@ -36,7 +36,10 @@ depends=(
)
makedepends=(
'appstream'
'appstream-glib'
'cmake'
'desktop-file-utils'
'cuda'
"gcc${_gcc_version}"
'git'
@@ -64,7 +67,7 @@ prepare() {
build() {
export BRANCH="@GITHUB_BRANCH@"
export BUILD_VERSION="@GITHUB_BUILD_VERSION@"
export BUILD_VERSION="@BUILD_VERSION@"
export COMMIT="@GITHUB_COMMIT@"
export CC="gcc-${_gcc_version}"
@@ -86,6 +89,11 @@ build() {
-D SUNSHINE_PUBLISHER_WEBSITE='https://app.lizardbyte.dev' \
-D SUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support'
appstreamcli validate "build/dev.lizardbyte.app.Sunshine.metainfo.xml"
appstream-util validate "build/dev.lizardbyte.app.Sunshine.metainfo.xml"
desktop-file-validate "build/dev.lizardbyte.app.Sunshine.desktop"
desktop-file-validate "build/dev.lizardbyte.app.Sunshine.terminal.desktop"
make -C build
}

View File

@@ -1,15 +1,15 @@
[Desktop Entry]
Type=Application
Name=@PROJECT_NAME@
Exec=/usr/bin/env systemctl start --u sunshine
Version=1.0
Actions=RunInTerminal;
Categories=RemoteAccess;Network;
Comment=@PROJECT_DESCRIPTION@
Exec=/usr/bin/env systemctl start --u sunshine
Icon=@SUNSHINE_DESKTOP_ICON@
Keywords=gamestream;stream;moonlight;remote play;
Categories=AudioVideo;Network;RemoteAccess;
Actions=RunInTerminal;
Name=@PROJECT_NAME@
Type=Application
Version=1.0
[Desktop Action RunInTerminal]
Name=Run in Terminal
Exec=gio launch @CMAKE_INSTALL_FULL_DATAROOTDIR@/applications/@PROJECT_FQDN@.terminal.desktop
Icon=application-x-executable
Exec=gio launch @CMAKE_INSTALL_FULL_DATAROOTDIR@/applications/sunshine_terminal.desktop
Name=Run in Terminal

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<component type="desktop">
<id>@PROJECT_FQDN@</id>
<name>@CMAKE_PROJECT_NAME@</name>
<name>@PROJECT_NAME@</name>
<summary>@PROJECT_BRIEF_DESCRIPTION@</summary>
<metadata_license>CC0-1.0</metadata_license>
@@ -15,39 +15,49 @@
<control>gamepad</control>
</supports>
<url type="bugtracker">https://github.com/LizardByte/Sunshine/issues</url>
<url type="homepage">@PROJECT_HOMEPAGE_URL@</url>
<url type="bugtracker">https://github.com/LizardByte/Sunshine/issues</url>
<url type="faq">https://docs.lizardbyte.dev/projects/sunshine/latest/md_docs_2troubleshooting.html</url>
<url type="help">https://docs.lizardbyte.dev/projects/sunshine</url>
<url type="donation">https://app.lizardbyte.dev/#Donate</url>
<url type="contact">https://app.lizardbyte.dev/support</url>
<url type="translate">https://translate.lizardbyte.dev</url>
<url type="contribute">https://docs.lizardbyte.dev</url>
<url type="vcs-browser">https://github.com/LizardByte/Sunshine</url>
<url type="contact">https://app.lizardbyte.dev/support</url>
<description>
<p>
@PROJECT_LONG_DESCRIPTION@
</p>
<p>NOTE: Sunshine requires additional installation steps.</p>
<p>NOTE: Sunshine requires additional installation steps (Flatpak).</p>
<p>
<code>flatpak run --command=additional-install.sh @PROJECT_FQDN@</code>
</p>
<p>NOTE: Sunshine uses a self-signed certificate. The web browser will report it as not secure, but it is safe.</p>
<p>NOTE: KMS Grab (Optional)</p>
<p>NOTE: KMS Grab (Flatpak)</p>
<p>
<code>sudo -i PULSE_SERVER=unix:/run/user/$(id -u $whoami)/pulse/native flatpak run @PROJECT_FQDN@</code>
</p>
</description>
<releases>
<release version="@PROJECT_VERSION@" date="1990-01-01"></release>
<release version="@PROJECT_VERSION@" date="@PROJECT_YEAR@-@PROJECT_MONTH@-@PROJECT_DAY@">
<description>
<p>
See the full changelog on GitHub
<!-- changelog -->
</p>
</description>
</release>
</releases>
<developer_name>LizardByte</developer_name>
<developer id="dev.lizardbyte">
<name>LizardByte</name>
</developer>
<screenshots>
<screenshot type="default">
<image>https://app.lizardbyte.dev/Sunshine/assets/img/banners/AdobeStock_305732536_1920x1280.jpg</image>
<caption>Sunshine</caption>
<image>https://app.lizardbyte.dev/Sunshine/assets/img/screenshots/01-sunshine-welcome-page.png</image>
<caption>Sunshine welcome page</caption>
</screenshot>
</screenshots>
<content_rating type="oars-1.0">

View File

@@ -15,8 +15,11 @@ License: GPLv3-only
URL: https://github.com/LizardByte/Sunshine
Source0: tarball.tar.gz
BuildRequires: appstream
# BuildRequires: boost-devel >= 1.86.0
BuildRequires: cmake >= 3.25.0
BuildRequires: desktop-file-utils
BuildRequires: libappstream-glib
BuildRequires: libayatana-appindicator3-devel
BuildRequires: libcap-devel
BuildRequires: libcurl-devel
@@ -197,6 +200,11 @@ cmake "${cmake_args[@]}"
make -j$(nproc) -C "%{_builddir}/Sunshine/build"
%check
# validate the metainfo file
appstreamcli validate %{buildroot}%{_metainfodir}/*.metainfo.xml
appstream-util validate %{buildroot}%{_metainfodir}/*.metainfo.xml
desktop-file-validate %{buildroot}%{_datadir}/applications/*.desktop
# run tests
cd %{_builddir}/Sunshine/build
xvfb-run ./tests/test_sunshine
@@ -252,14 +260,14 @@ rm -f /usr/lib/modules-load.d/uhid.conf
%{_modulesloaddir}/uhid.conf
# Desktop entries
%{_datadir}/applications/sunshine*.desktop
%{_datadir}/applications/*.desktop
# Icons
%{_datadir}/icons/hicolor/scalable/apps/apollo.svg
%{_datadir}/icons/hicolor/scalable/status/apollo*.svg
# Metainfo
%{_datadir}/metainfo/sunshine.appdata.xml
%{_datadir}/metainfo/*.metainfo.xml
# Assets
%{_datadir}/sunshine/**

View File

@@ -1,5 +1,5 @@
[Desktop Entry]
Categories=AudioVideo;Network;RemoteAccess;
Categories=RemoteAccess;Network;
Comment=@PROJECT_DESCRIPTION@
Exec=sunshine.sh
Icon=@SUNSHINE_DESKTOP_ICON@

View File

@@ -1,8 +1,9 @@
{
"dev.lizardbyte.app.Sunshine": [
"appstream-missing-screenshots",
"appstream-external-screenshot-url",
"appstream-screenshots-not-mirrored-in-ostree",
"external-gitmodule-url-found",
"finish-args-flatpak-spawn-access"
"finish-args-flatpak-spawn-access",
"finish-args-home-filesystem-access"
]
}

View File

@@ -32,15 +32,12 @@
"sources": [
{
"type": "git",
"url": "https://salsa.debian.org/utopia-team/avahi.git",
"commit": "1412c015d348166d58ea9c192239b00769eae24e",
"tag": "debian/0.8-13",
"url": "https://github.com/avahi/avahi.git",
"commit": "f060abee2807c943821d88839c013ce15db17b58",
"tag": "v0.8",
"x-checker-data": {
"type": "git",
"tag-pattern": "^debian\\/(\\d.+)$",
"versions": {
"<": "0.9"
}
"tag-pattern": "^v([\\d.]+)$"
}
},
{

View File

@@ -5,22 +5,24 @@
"-Ddocumentation=disabled",
"-Dtests=disabled"
],
"cleanup": [
"/bin"
],
"sources": [
{
"type": "git",
"url": "https://salsa.debian.org/debian/libevdev.git",
"commit": "1aa7baa233d6df4cee6a66fbc61bb5ffc8b6e88d",
"tag": "debian/1.13.0+dfsg-1",
"url": "https://github.com/LizardByte-infrastructure/libevdev.git",
"commit": "ac0056961c3332a260db063ab4fccc7747638a1d",
"tag": "libevdev-1.13.4",
"x-checker-data": {
"type": "git",
"tag-pattern": "^debian\\/(\\d.\\d+\\.\\d+)",
"versions": {
"<": "1.13.1"
}
"type": "anitya",
"project-id": 20540,
"stable-only": true,
"tag-template": "libevdev-$version"
}
}
],
"cleanup": [
"/bin",
"/include",
"/lib/pkgconfig",
"/share"
]
}

View File

@@ -11,15 +11,12 @@
"sources": [
{
"type": "git",
"url": "https://salsa.debian.org/gnome-team/libnotify.git",
"commit": "ccf2f62ef0a4b264dd4eff32cab70a3e213ceb1a",
"tag": "debian/0.8.1-1",
"url": "https://github.com/LizardByte-infrastructure/libnotify.git",
"commit": "131aad01ff5f563b4863becbb6ed84dac6e75d5a",
"tag": "0.8.6",
"x-checker-data": {
"type": "git",
"tag-pattern": "^debian\\/(\\d.+)$",
"versions": {
"<": "0.8.2"
}
"tag-pattern": "^([\\d.]+)$"
}
}
]

View File

@@ -1,25 +1,33 @@
{
"name": "miniupnpc",
"buildsystem": "cmake-ninja",
"builddir": true,
"config-opts": [
"-DCMAKE_BUILD_TYPE=Release",
"-DUPNPC_BUILD_SAMPLE=OFF",
"-DCMAKE_BUILD_TYPE=RelWithDebInfo",
"-DUPNPC_BUILD_STATIC=OFF",
"-DUPNPC_BUILD_SHARED=ON",
"-DUPNPC_BUILD_TESTS=OFF"
"-DUPNPC_BUILD_TESTS=OFF",
"-DUPNPC_BUILD_SAMPLE=OFF"
],
"sources": [
{
"type": "git",
"url": "https://salsa.debian.org/miniupnp-team/miniupnpc.git",
"commit": "c5fe3aa794e92a503cecec6a4071eb6d310b4e42",
"tag": "debian/2.2.4-1",
"url": "https://miniupnp.tuxfamily.org/files/miniupnpc-2.3.3.tar.gz",
"sha256": "d52a0afa614ad6c088cc9ddff1ae7d29c8c595ac5fdd321170a05f41e634bd1a",
"x-checker-data": {
"type": "git",
"tag-pattern": "^debian\\/(\\d.+)$",
"versions": {
"<": "2.2.5"
}
}
"type": "anitya",
"project-id": 1986,
"stable-only": true,
"url-template": "https://miniupnp.tuxfamily.org/files/miniupnpc-$version.tar.gz"
},
"type": "archive"
}
],
"cleanup": [
"/share/man",
"/lib/pkgconfig",
"/lib/libminiupnpc.so",
"/lib/cmake",
"/include",
"/bin/external-ip.sh"
]
}

View File

@@ -1,22 +1,24 @@
{
"name": "numactl",
"buildsystem": "autotools",
"cleanup": [
"/bin"
],
"sources": [
{
"type": "git",
"url": "https://salsa.debian.org/debian/numactl.git",
"commit": "640bb34497702f9aaeb8af1b491f32b91d03ec80",
"tag": "debian/2.0.16-1",
"url": "https://github.com/numactl/numactl.git",
"tag": "v2.0.19",
"commit": "3bc85e37d5a30da6790cb7e8bb488bb8f679170f",
"x-checker-data": {
"type": "git",
"tag-pattern": "^debian\\/(\\d.+)$",
"versions": {
"<": "2.0.17"
}
"tag-pattern": "^v([\\d.]+)$"
}
}
],
"rm-configure": true,
"cleanup": [
"/include",
"/lib/pkgconfig",
"/lib/*.a",
"/lib/*.la",
"/lib/*.so",
"/share/man"
]
}

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop">
<id>@PROJECT_NAME@.desktop</id>
<metadata_license>@PROJECT_LICENSE@</metadata_license>
<project_license>@PROJECT_LICENSE@</project_license>
<name>@PROJECT_NAME@</name>
<url type="homepage">@CMAKE_PROJECT_HOMEPAGE_URL@</url>
<summary>@PROJECT_BRIEF_DESCRIPTION@</summary>
<description>
<p>
@PROJECT_LONG_DESCRIPTION@
</p>
</description>
<screenshots>
<screenshot type="default">
<image>https://app.lizardbyte.dev/Sunshine/assets/images/AdobeStock_305732536_1920x1280.jpg</image>
<caption>Sunshine</caption>
</screenshot>
</screenshots>
</component>

View File

@@ -6,7 +6,7 @@ class @PROJECT_NAME@ < Formula
homepage "@PROJECT_HOMEPAGE_URL@"
url "@GITHUB_CLONE_URL@",
tag: "@GITHUB_TAG@"
version "@FORMULA_VERSION@"
version "@BUILD_VERSION@"
license all_of: ["GPL-3.0-only"]
head "@GITHUB_CLONE_URL@", branch: "@GITHUB_DEFAULT_BRANCH@"
@@ -319,31 +319,31 @@ index 5b3638d..aca9481 100644
run [opt_bin/"sunshine", "~/.config/sunshine/sunshine.conf"]
end
def caveats
caveats_message = <<~EOS
Thanks for installing @PROJECT_NAME@!
To get started, review the documentation at:
https://docs.lizardbyte.dev/projects/sunshine
EOS
def post_install
if OS.linux?
caveats_message += <<~EOS
opoo <<~EOS
ATTENTION: To complete installation, you must run the following command:
`sudo #{bin}/postinst`
EOS
end
if OS.mac?
caveats_message += <<~EOS
opoo <<~EOS
Sunshine can only access microphones on macOS due to system limitations.
To stream system audio use "Soundflower" or "BlackHole".
Gamepads are not currently supported on macOS.
EOS
end
end
caveats_message
def caveats
<<~EOS
Thanks for installing @PROJECT_NAME@!
To get started, review the documentation at:
https://docs.lizardbyte.dev/projects/sunshine
EOS
end
test do

View File

@@ -25,6 +25,7 @@ year = datetime.datetime.now().year
# target locales
target_locales = [
'bg', # Bulgarian
'cs', # Czech
'de', # German
'en', # English
'en_GB', # English (United Kingdom)
@@ -42,6 +43,7 @@ target_locales = [
'tr', # Turkish
'uk', # Ukrainian
'zh', # Chinese
'zh_TW', # Chinese (Traditional)
]

View File

@@ -138,9 +138,12 @@ function add_arch_deps() {
function add_debian_based_deps() {
dependencies+=(
"appstream"
"appstream-util"
"bison" # required if we need to compile doxygen
"build-essential"
"cmake"
"desktop-file-utils"
"doxygen"
"flex" # required if we need to compile doxygen
"gcc-${gcc_version}"
@@ -201,13 +204,16 @@ function add_ubuntu_deps() {
function add_fedora_deps() {
dependencies+=(
"appstream"
"cmake"
"desktop-file-utils"
"doxygen"
"gcc${gcc_version}"
"gcc${gcc_version}-c++"
"git"
"graphviz"
"libappindicator-gtk3-devel"
"libappstream-glib"
"libcap-devel"
"libcurl-devel"
"libdrm-devel"
@@ -466,6 +472,8 @@ function run_install() {
install_cuda
cmake_args+=("-DSUNSHINE_ENABLE_CUDA=ON")
cmake_args+=("-DCMAKE_CUDA_COMPILER:PATH=$nvcc_path")
else
cmake_args+=("-DSUNSHINE_ENABLE_CUDA=OFF")
fi
# Cmake stuff here
@@ -473,6 +481,16 @@ function run_install() {
echo "cmake args:"
echo "${cmake_args[@]}"
cmake "${cmake_args[@]}"
# Run appstream validation, etc.
appstreamcli validate "build/dev.lizardbyte.app.Sunshine.metainfo.xml"
appstream-util validate "build/dev.lizardbyte.app.Sunshine.metainfo.xml"
desktop-file-validate "build/dev.lizardbyte.app.Sunshine.desktop"
if [ "$appimage_build" == 0 ]; then
desktop-file-validate "build/dev.lizardbyte.app.Sunshine.terminal.desktop"
fi
# Build the project
ninja -C "build"
# Create the package
@@ -570,6 +588,15 @@ elif grep -q "Ubuntu 24.04" /etc/os-release; then
cuda_build="520.61.05"
gcc_version="11"
nvm_node=0
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"
nvm_node=0
else
echo "Unsupported Distro or Version"
exit 1

View File

@@ -509,7 +509,6 @@ namespace config {
{} // wa
}, // display_device
1, // min_fps_factor
0, // max_bitrate
"1920x1080x60", // fallback_mode
@@ -1195,7 +1194,6 @@ namespace config {
video.dd.wa.hdr_toggle_delay = std::chrono::milliseconds {value};
}
int_between_f(vars, "min_fps_factor", video.min_fps_factor, {1, 3});
int_f(vars, "max_bitrate", video.max_bitrate);
string_f(vars, "fallback_mode", video.fallback_mode);
bool_f(vars, "isolated_virtual_display_option", video.isolated_virtual_display_option);
@@ -1304,6 +1302,7 @@ namespace config {
string_restricted_f(vars, "locale", config::sunshine.locale, {
"bg"sv, // Bulgarian
"cs"sv, // Czech
"de"sv, // German
"en"sv, // English
"en_GB"sv, // English (UK)
@@ -1321,6 +1320,7 @@ namespace config {
"tr"sv, // Turkish
"uk"sv, // Ukrainian
"zh"sv, // Chinese
"zh_TW"sv, // Chinese (Traditional)
});
std::string log_level_string;

View File

@@ -143,7 +143,6 @@ namespace config {
workarounds_t wa;
} dd;
int min_fps_factor; // Minimum fps target, determines minimum frame time
int max_bitrate; // Maximum bitrate, sets ceiling in kbps for bitrate requested from client
std::string fallback_mode;

View File

@@ -38,7 +38,6 @@
#include "process.h"
#include "utility.h"
#include "uuid.h"
#include "version.h"
#ifdef _WIN32
#include "platform/windows/utils.h"
@@ -89,6 +88,8 @@ namespace confighttp {
void send_response(resp_https_t response, const nlohmann::json &output_tree) {
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "application/json");
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
response->write(output_tree.dump(), headers);
}
@@ -106,7 +107,9 @@ namespace confighttp {
tree["status"] = false;
tree["error"] = "Unauthorized";
const SimpleWeb::CaseInsensitiveMultimap headers {
{"Content-Type", "application/json"}
{"Content-Type", "application/json"},
{"X-Frame-Options", "DENY"},
{"Content-Security-Policy", "frame-ancestors 'none';"}
};
response->write(code, tree.dump(), headers);
}
@@ -121,7 +124,9 @@ namespace confighttp {
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- redirecting"sv;
const SimpleWeb::CaseInsensitiveMultimap headers {
{"Location", path}
{"Location", path},
{"X-Frame-Options", "DENY"},
{"Content-Security-Policy", "frame-ancestors 'none';"}
};
response->write(SimpleWeb::StatusCode::redirection_temporary_redirect, headers);
}
@@ -218,6 +223,9 @@ namespace confighttp {
tree["error"] = "Not Found";
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "application/json");
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
response->write(code, tree.dump(), headers);
}
@@ -235,6 +243,9 @@ namespace confighttp {
tree["error"] = error_message;
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "application/json");
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
response->write(code, tree.dump(), headers);
}
@@ -252,10 +263,25 @@ namespace confighttp {
return false;
}
if (requestContentType->second != contentType) {
// Extract the media type part before any parameters (e.g., charset)
std::string actualContentType = requestContentType->second;
size_t semicolonPos = actualContentType.find(';');
if (semicolonPos != std::string::npos) {
actualContentType = actualContentType.substr(0, semicolonPos);
}
// Trim whitespace and convert to lowercase for case-insensitive comparison
boost::algorithm::trim(actualContentType);
boost::algorithm::to_lower(actualContentType);
std::string expectedContentType(contentType);
boost::algorithm::to_lower(expectedContentType);
if (actualContentType != expectedContentType) {
bad_request(response, request, "Content type mismatch");
return false;
}
return true;
return true;
}
@@ -273,9 +299,10 @@ namespace confighttp {
print_req(request);
std::string content = file_handler::read_file(WEB_DIR "index.html");
SimpleWeb::CaseInsensitiveMultimap headers {
{"Content-Type", "text/html; charset=utf-8"}
};
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
response->write(content, headers);
}
@@ -292,9 +319,10 @@ namespace confighttp {
print_req(request);
std::string content = file_handler::read_file(WEB_DIR "pin.html");
SimpleWeb::CaseInsensitiveMultimap headers {
{"Content-Type", "text/html; charset=utf-8"}
};
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
response->write(content, headers);
}
@@ -311,10 +339,11 @@ namespace confighttp {
print_req(request);
std::string content = file_handler::read_file(WEB_DIR "apps.html");
SimpleWeb::CaseInsensitiveMultimap headers {
{"Content-Type", "text/html; charset=utf-8"},
{"Access-Control-Allow-Origin", "https://images.igdb.com/"}
};
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
headers.emplace("Access-Control-Allow-Origin", "https://images.igdb.com/");
response->write(content, headers);
}
@@ -331,9 +360,10 @@ namespace confighttp {
print_req(request);
std::string content = file_handler::read_file(WEB_DIR "clients.html");
SimpleWeb::CaseInsensitiveMultimap headers {
{"Content-Type", "text/html; charset=utf-8"}
};
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
response->write(content, headers);
}
@@ -350,9 +380,10 @@ namespace confighttp {
print_req(request);
std::string content = file_handler::read_file(WEB_DIR "config.html");
SimpleWeb::CaseInsensitiveMultimap headers {
{"Content-Type", "text/html; charset=utf-8"}
};
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
response->write(content, headers);
}
@@ -369,9 +400,10 @@ namespace confighttp {
print_req(request);
std::string content = file_handler::read_file(WEB_DIR "password.html");
SimpleWeb::CaseInsensitiveMultimap headers {
{"Content-Type", "text/html; charset=utf-8"}
};
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
response->write(content, headers);
}
@@ -393,9 +425,10 @@ namespace confighttp {
}
std::string content = file_handler::read_file(WEB_DIR "login.html");
SimpleWeb::CaseInsensitiveMultimap headers {
{"Content-Type", "text/html; charset=utf-8"}
};
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
response->write(content, headers);
}
@@ -413,9 +446,10 @@ namespace confighttp {
}
std::string content = file_handler::read_file(WEB_DIR "welcome.html");
SimpleWeb::CaseInsensitiveMultimap headers {
{"Content-Type", "text/html; charset=utf-8"}
};
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
response->write(content, headers);
}
@@ -432,9 +466,10 @@ namespace confighttp {
print_req(request);
std::string content = file_handler::read_file(WEB_DIR "troubleshooting.html");
SimpleWeb::CaseInsensitiveMultimap headers {
{"Content-Type", "text/html; charset=utf-8"}
};
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
response->write(content, headers);
}
@@ -447,9 +482,10 @@ namespace confighttp {
print_req(request);
std::ifstream in(WEB_DIR "images/apollo.ico", std::ios::binary);
SimpleWeb::CaseInsensitiveMultimap headers {
{"Content-Type", "image/x-icon"}
};
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "image/x-icon");
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
}
@@ -465,9 +501,10 @@ namespace confighttp {
print_req(request);
std::ifstream in(WEB_DIR "images/logo-apollo-45.png", std::ios::binary);
SimpleWeb::CaseInsensitiveMultimap headers {
{"Content-Type", "image/png"}
};
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "image/png");
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
}
@@ -518,6 +555,8 @@ namespace confighttp {
}
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", mimeType->second);
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
std::ifstream in(filePath.string(), std::ios::binary);
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
}
@@ -631,6 +670,9 @@ namespace confighttp {
* @api_examples{/api/apps/close| POST| null}
*/
void closeApp(resp_https_t response, req_https_t request) {
if (!check_content_type(response, request, "application/json")) {
return;
}
if (!authenticate(response, request)) {
return;
}
@@ -936,7 +978,7 @@ namespace confighttp {
* @api_examples{/api/clients/unpair-all| POST| null}
*/
void unpairAll(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) {
if (!validateContentType(response, request, "application/json"sv) || !authenticate(response, request)) {
return;
}
@@ -964,7 +1006,7 @@ namespace confighttp {
nlohmann::json output_tree;
output_tree["status"] = true;
output_tree["platform"] = SUNSHINE_PLATFORM;
output_tree["version"] = PROJECT_VER;
output_tree["version"] = PROJECT_VERSION;
#ifdef _WIN32
output_tree["vdisplayStatus"] = (int)proc::vDisplayDriverStatus;
#endif
@@ -1109,6 +1151,8 @@ namespace confighttp {
contentType += currentCodePageToCharset();
#endif
headers.emplace("Content-Type", contentType);
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
response->write(SimpleWeb::StatusCode::success_ok, content, headers);
}
@@ -1264,7 +1308,7 @@ namespace confighttp {
* @api_examples{/api/reset-display-device-persistence| POST| null}
*/
void resetDisplayDevicePersistence(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) {
if (!validateContentType(response, request, "application/json"sv) || !authenticate(response, request)) {
return;
}
@@ -1283,7 +1327,7 @@ namespace confighttp {
* @api_examples{/api/restart| POST| null}
*/
void restart(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) {
if (!validateContentType(response, request, "application/json"sv) || !authenticate(response, request)) {
return;
}

View File

@@ -16,7 +16,6 @@
#include "logging.h"
#include "network.h"
#include "platform/common.h"
#include "version.h"
extern "C" {
#ifdef _WIN32

View File

@@ -103,6 +103,10 @@ int main(int argc, char *argv[]) {
task_pool_util::TaskPool::task_id_t force_shutdown = nullptr;
#ifdef _WIN32
// Avoid searching the PATH in case a user has configured their system insecurely
// by placing a user-writable directory in the system-wide PATH variable.
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32);
setlocale(LC_ALL, "C");
#endif
@@ -129,7 +133,7 @@ int main(int argc, char *argv[]) {
// logging can begin at this point
// if anything is logged prior to this point, it will appear in stdout, but not in the log viewer in the UI
// the version should be printed to the log before anything else
BOOST_LOG(info) << PROJECT_NAME << " version: " << PROJECT_VER;
BOOST_LOG(info) << PROJECT_NAME << " version: " << PROJECT_VERSION << " commit: " << PROJECT_VERSION_COMMIT;
// Log publisher metadata
log_publisher_data();
@@ -389,6 +393,7 @@ int main(int argc, char *argv[]) {
std::thread httpThread {nvhttp::start};
std::thread configThread {confighttp::start};
std::thread rtspThread {rtsp_stream::start};
#ifdef _WIN32
// If we're using the default port and GameStream is enabled, warn the user
@@ -398,10 +403,12 @@ int main(int argc, char *argv[]) {
}
#endif
rtsp_stream::rtpThread();
// Wait for shutdown
shutdown_event->view();
httpThread.join();
configThread.join();
rtspThread.join();
task_pool.stop();
task_pool.join();

View File

@@ -513,7 +513,7 @@ std::string get_local_ip_for_gateway() {
// UDP GSO on Linux currently only supports sending 64K or 64 segments at a time
size_t seg_index = 0;
const size_t seg_max = 65536 / 1500;
struct iovec iovs[(send_info.headers ? std::min(seg_max, send_info.block_count) : 1) * max_iovs_per_msg] = {};
struct iovec iovs[(send_info.headers ? std::min(seg_max, send_info.block_count) : 1) * max_iovs_per_msg];
auto msg_size = send_info.header_size + send_info.payload_size;
while (seg_index < send_info.block_count) {
int iovlen = 0;
@@ -596,10 +596,11 @@ std::string get_local_ip_for_gateway() {
{
// If GSO is not supported, use sendmmsg() instead.
struct mmsghdr msgs[send_info.block_count] = {};
struct iovec iovs[send_info.block_count * (send_info.headers ? 2 : 1)] = {};
struct mmsghdr msgs[send_info.block_count];
struct iovec iovs[send_info.block_count * (send_info.headers ? 2 : 1)];
int iov_idx = 0;
for (size_t i = 0; i < send_info.block_count; i++) {
msgs[i].msg_len = 0;
msgs[i].msg_hdr.msg_iov = &iovs[iov_idx];
msgs[i].msg_hdr.msg_iovlen = send_info.headers ? 2 : 1;
@@ -617,6 +618,7 @@ std::string get_local_ip_for_gateway() {
msgs[i].msg_hdr.msg_namelen = msg.msg_namelen;
msgs[i].msg_hdr.msg_control = cmbuf.buf;
msgs[i].msg_hdr.msg_controllen = cmbuflen;
msgs[i].msg_hdr.msg_flags = 0;
}
// Call sendmmsg() until all messages are sent
@@ -709,7 +711,7 @@ std::string get_local_ip_for_gateway() {
memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo));
}
struct iovec iovs[2] = {};
struct iovec iovs[2];
int iovlen = 0;
if (send_info.header) {
iovs[iovlen].iov_base = (void *) send_info.header;

View File

@@ -34,7 +34,7 @@
+ (NSString *)getDisplayName:(CGDirectDisplayID)displayID {
for (NSScreen *screen in [NSScreen screens]) {
if (screen.deviceDescription[@"NSScreenNumber"] == [NSNumber numberWithUnsignedInt:displayID]) {
if ([screen.deviceDescription[@"NSScreenNumber"] isEqualToNumber:[NSNumber numberWithUnsignedInt:displayID]]) {
return screen.localizedName;
}
}

View File

@@ -27,10 +27,8 @@ DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x2
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2);
#if defined(__x86_64) || defined(_M_AMD64)
#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64)
#define STEAM_DRIVER_SUBDIR L"x64"
#elif defined(__i386) || defined(_M_IX86)
#define STEAM_DRIVER_SUBDIR L"x86"
#else
#warning No known Steam audio driver for this architecture
#endif

View File

@@ -1,13 +1,15 @@
/**
* @file src/platform/windows/windows.rc.in
* @brief Windows resource file template.
* @note The final `windows.rc` is generated from this file during the CMake build.
* @todo Use CMake definitions directly, instead of configuring this file.
* @file src/platform/windows/windows.rc
* @brief Windows resource file.
*/
#include "winver.h"
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
VS_VERSION_INFO VERSIONINFO
FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0
PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0
FILEVERSION PROJECT_VERSION_MAJOR,PROJECT_VERSION_MINOR,PROJECT_VERSION_PATCH,0
PRODUCTVERSION PROJECT_VERSION_MAJOR,PROJECT_VERSION_MINOR,PROJECT_VERSION_PATCH,0
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
@@ -16,13 +18,13 @@ BEGIN
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "CompanyName", "SudoMaker\0"
VALUE "FileDescription", "Apollo\0"
VALUE "FileVersion", "@PROJECT_VERSION@\0"
VALUE "InternalName", "Apollo\0"
VALUE "LegalCopyright", "https://raw.githubusercontent.com/ClassicOldSong/Apollo/master/LICENSE\0"
VALUE "ProductName", "Apollo\0"
VALUE "ProductVersion", "@PROJECT_VERSION@\0"
VALUE "CompanyName", TOSTRING(PROJECT_VENDOR)
VALUE "FileDescription", TOSTRING(PROJECT_NAME)
VALUE "FileVersion", TOSTRING(PROJECT_VERSION)
VALUE "InternalName", TOSTRING(PROJECT_NAME)
VALUE "ProductName", TOSTRING(PROJECT_NAME)
VALUE "ProductVersion", TOSTRING(PROJECT_VERSION)
VALUE "LegalCopyright", "https://raw.githubusercontent.com/ClassicOldSong/Apollo/master/LICENSE"
END
END
@@ -39,4 +41,4 @@ BEGIN
END
END
SuperDuperAmazing ICON DISCARDABLE "@SUNSHINE_ICON_PATH@"
SuperDuperAmazing ICON DISCARDABLE PROJECT_ICON_PATH

View File

@@ -39,7 +39,7 @@
#define gemm DECORATE_FUNC(gemm, ISA_SUFFIX)
#define invert_mat DECORATE_FUNC(invert_mat, ISA_SUFFIX)
#if defined(__x86_64__) || defined(__i386__)
#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64)
// Compile a variant for SSSE3
#if defined(__clang__)
@@ -122,7 +122,7 @@ reed_solomon_decode_t reed_solomon_decode_fn;
* @details The streaming code will directly invoke these function pointers during encoding.
*/
void reed_solomon_init(void) {
#if defined(__x86_64__) || defined(__i386__)
#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64)
if (__builtin_cpu_supports("avx512f") && __builtin_cpu_supports("avx512bw")) {
reed_solomon_new_fn = reed_solomon_new_avx512;
reed_solomon_release_fn = reed_solomon_release_avx512;

View File

@@ -432,11 +432,6 @@ namespace rtsp_stream {
return 0;
}
template<class T, class X>
void iterate(std::chrono::duration<T, X> timeout) {
io_context.run_one_for(timeout);
}
void handle_msg(tcp::socket &sock, launch_session_t &session, msg_t &&req) {
auto func = _map_cmd_cb.find(req->message.request.command);
if (func != std::end(_map_cmd_cb)) {
@@ -494,15 +489,24 @@ namespace rtsp_stream {
* @param launch_session Streaming session information.
*/
void session_raise(std::shared_ptr<launch_session_t> launch_session) {
auto now = std::chrono::steady_clock::now();
// If a launch event is still pending, don't overwrite it.
if (raised_timeout > now && launch_event.peek()) {
if (launch_event.view(0s)) {
return;
}
raised_timeout = now + config::stream.ping_timeout;
// Raise the new launch session to prepare for the RTSP handshake
launch_event.raise(std::move(launch_session));
// Arm the timer to expire this launch session if the client times out
raised_timer.expires_after(config::stream.ping_timeout);
raised_timer.async_wait([this](const boost::system::error_code &ec) {
if (!ec) {
auto discarded = launch_event.pop(0s);
if (discarded) {
BOOST_LOG(debug) << "Event timeout: "sv << discarded->unique_id;
}
}
});
}
/**
@@ -517,6 +521,7 @@ namespace rtsp_stream {
if (launch_session->id != launch_session_id) {
BOOST_LOG(error) << "Attempted to clear unexpected session: "sv << launch_session_id << " vs "sv << launch_session->id;
} else {
raised_timer.cancel();
launch_event.pop();
}
}
@@ -541,14 +546,6 @@ namespace rtsp_stream {
* @examples_end
*/
void clear(bool all = true) {
// if a launch event timed out --> Remove it.
if (raised_timeout < std::chrono::steady_clock::now()) {
auto discarded = launch_event.pop(0s);
if (discarded) {
BOOST_LOG(debug) << "Event timeout: "sv << discarded->unique_id;
}
}
auto lg = _session_slots.lock();
for (auto i = _session_slots->begin(); i != _session_slots->end();) {
@@ -583,6 +580,26 @@ namespace rtsp_stream {
BOOST_LOG(info) << "New streaming session started [active sessions: "sv << _session_slots->size() << ']';
}
/**
* @brief Runs an iteration of the RTSP server loop
*/
void iterate() {
// If we have a session, we will return to the server loop every
// 500ms to allow session cleanup to happen.
if (session_count() > 0) {
io_context.run_one_for(500ms);
} else {
io_context.run_one();
}
}
/**
* @brief Stop the RTSP server.
*/
void stop() {
acceptor.close();
io_context.stop();
clear();
std::shared_ptr<stream::session_t>
find_session(const std::string_view& uuid) {
auto lg = _session_slots.lock();
@@ -613,10 +630,9 @@ namespace rtsp_stream {
sync_util::sync_t<std::set<std::shared_ptr<stream::session_t>>> _session_slots;
std::chrono::steady_clock::time_point raised_timeout;
boost::asio::io_context io_context;
tcp::acceptor acceptor {io_context};
boost::asio::steady_timer raised_timer {io_context};
std::shared_ptr<socket_t> next_socket;
};
@@ -1163,9 +1179,8 @@ namespace rtsp_stream {
respond(sock, session, &option, 200, "OK", req->sequenceNumber, {});
}
void rtpThread() {
void start() {
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
auto broadcast_shutdown_event = mail::man->event<bool>(mail::broadcast_shutdown);
server.map("OPTIONS"sv, &cmd_option);
server.map("DESCRIBE"sv, &cmd_describe);
@@ -1181,18 +1196,29 @@ namespace rtsp_stream {
return;
}
while (!shutdown_event->peek()) {
server.iterate(std::min(500ms, config::stream.ping_timeout));
std::thread rtsp_thread {[&shutdown_event] {
auto broadcast_shutdown_event = mail::man->event<bool>(mail::broadcast_shutdown);
if (broadcast_shutdown_event->peek()) {
server.clear();
} else {
// cleanup all stopped sessions
server.clear(false);
while (!shutdown_event->peek()) {
server.iterate();
if (broadcast_shutdown_event->peek()) {
server.clear();
} else {
// cleanup all stopped sessions
server.clear(false);
}
}
}
server.clear();
server.clear();
}};
// Wait for shutdown
shutdown_event->view();
// Stop the server and join the server thread
server.stop();
rtsp_thread.join();
}
void print_msg(PRTSP_MESSAGE msg) {

View File

@@ -88,6 +88,8 @@ namespace rtsp_stream {
*/
void terminate_sessions();
void rtpThread();
/**
* @brief Runs the RTSP server loop.
*/
void start();
} // namespace rtsp_stream

View File

@@ -51,7 +51,6 @@
#include "process.h"
#include "network.h"
#include "src/entry_handler.h"
#include "version.h"
using namespace std::literals;

View File

@@ -2,6 +2,9 @@
* @file src/upnp.cpp
* @brief Definitions for UPnP port mapping.
*/
// standard includes
#include <stddef.h> // workaround for type_t error in miniupnpc 2.3.3, see https://github.com/miniupnp/miniupnp/commit/e263ab6f56c382e10fed31347ec68095d691a0e8
// lib includes
#include <miniupnpc/miniupnpc.h>
#include <miniupnpc/upnpcommands.h>

View File

@@ -1,13 +0,0 @@
/**
* @file src/version.h.in
* @brief Version definitions for Sunshine.
* @note The final `version.h` is generated from this file during the CMake build.
* @todo Use CMake definitions directly, instead of configuring this file.
*/
#pragma once
#define PROJECT_NAME "@PROJECT_NAME@"
#define PROJECT_VER "@PROJECT_VERSION@"
#define PROJECT_VER_MAJOR "@PROJECT_VERSION_MAJOR@"
#define PROJECT_VER_MINOR "@PROJECT_VERSION_MINOR@"
#define PROJECT_VER_PATCH "@PROJECT_VERSION_PATCH@"

View File

@@ -775,6 +775,18 @@ namespace video {
{"usage"s, &config::video.amd.amd_usage_hevc},
{"vbaq"s, &config::video.amd.amd_vbaq},
{"enforce_hrd"s, &config::video.amd.amd_enforce_hrd},
{"level"s, [](const config_t &cfg) {
auto size = cfg.width * cfg.height;
// For 4K and below, try to use level 5.1 or 5.2 if possible
if (size <= 8912896) {
if (size * cfg.framerate <= 534773760) {
return "5.1"s;
} else if (size * cfg.framerate <= 1069547520) {
return "5.2"s;
}
}
return "auto"s;
}},
},
{}, // SDR-specific options
{}, // HDR-specific options
@@ -1672,7 +1684,7 @@ namespace video {
ctx->thread_count = ctx->slices;
AVDictionary *options {nullptr};
auto handle_option = [&options](const encoder_t::option_t &option) {
auto handle_option = [&options, &config](const encoder_t::option_t &option) {
std::visit(
util::overloaded {
[&](int v) {
@@ -1686,7 +1698,7 @@ namespace video {
av_dict_set_int(&options, option.name.c_str(), **v, 0);
}
},
[&](std::function<int()> v) {
[&](const std::function<int()> &v) {
av_dict_set_int(&options, option.name.c_str(), v(), 0);
},
[&](const std::string &v) {
@@ -1696,6 +1708,9 @@ namespace video {
if (!v->empty()) {
av_dict_set(&options, option.name.c_str(), v->c_str(), 0);
}
},
[&](const std::function<const std::string(const config_t &cfg)> &v) {
av_dict_set(&options, option.name.c_str(), v(config).c_str(), 0);
}
},
option.value
@@ -1907,8 +1922,8 @@ namespace video {
}
});
// set minimum frame time, avoiding violation of client-requested target framerate
auto minimum_frame_time = std::chrono::milliseconds(1000 / std::min(config.framerate, (config::video.min_fps_factor * 10)));
// set minimum frame time based on client-requested target framerate
auto minimum_frame_time = std::chrono::milliseconds(1000 / config.framerate);
auto frame_threshold = std::chrono::microseconds(1000ms * 1000 / config.encodingFramerate);
BOOST_LOG(debug) << "Minimum frame time set to "sv << minimum_frame_time.count() << "ms, based on min fps factor of "sv << config::video.min_fps_factor << "."sv;
BOOST_LOG(info) << "Frame threshold: "sv << frame_threshold;

View File

@@ -156,7 +156,7 @@ namespace video {
option_t(const option_t &) = default;
std::string name;
std::variant<int, int *, std::optional<int> *, std::function<int()>, std::string, std::string *> value;
std::variant<int, int *, std::optional<int> *, std::function<int()>, std::string, std::string *, std::function<const std::string(const config_t &)>> value;
option_t(std::string &&name, decltype(value) &&value):
name {std::move(name)},

View File

@@ -206,7 +206,6 @@
"headless_mode": "disabled",
"double_refreshrate": "disabled",
"dd_wa_hdr_toggle_delay": 0,
"min_fps_factor": 1,
"max_bitrate": 0,
"isolated_virtual_display_option": "disabled",
},

View File

@@ -62,6 +62,7 @@ onMounted(() => {
<label for="locale" class="form-label">{{ $t('config.locale') }}</label>
<select id="locale" class="form-select" v-model="config.locale">
<option value="bg">Български (Bulgarian)</option>
<option value="cs">Čeština (Czech)</option>
<option value="de">Deutsch (German)</option>
<option value="en">English</option>
<option value="en_GB">English, UK</option>
@@ -79,6 +80,7 @@ onMounted(() => {
<option value="tr">Türkçe (Turkish)</option>
<option value="uk">Українська (Ukranian)</option>
<option value="zh">简体中文 (Chinese Simplified)</option>
<option value="zh_TW">繁體中文 (Chinese Traditional)</option>
</select>
<div class="form-text">{{ $t('config.locale_desc') }}</div>
</div>

View File

@@ -11,13 +11,6 @@ const config = ref(props.config)
</script>
<template>
<!--min_fps_factor-->
<div class="mb-3">
<label for="qp" class="form-label">{{ $t('config.min_fps_factor') }}</label>
<input type="number" min="1" max="3" class="form-control" id="min_fps_factor" placeholder="1" v-model="config.min_fps_factor" />
<div class="form-text">{{ $t('config.min_fps_factor_desc') }}</div>
</div>
<!--max_bitrate-->
<div class="mb-3">
<label for="max_bitrate" class="form-label">{{ $t("config.max_bitrate") }}</label>

View File

@@ -0,0 +1,455 @@
{
"_common": {
"apply": "Použít",
"auto": "Automaticky",
"autodetect": "Automatická detekce (doporučeno)",
"beta": "(beta)",
"cancel": "Zrušit",
"disabled": "Zakázáno",
"disabled_def": "Zakázáno (výchozí)",
"disabled_def_cbox": "Výchozí: nezaškrtnuto",
"dismiss": "Odmítnout",
"do_cmd": "Provést příkaz",
"elevated": "Zvýšené",
"enabled": "Povoleno",
"enabled_def": "Povoleno (výchozí)",
"enabled_def_cbox": "Výchozí: zaškrtnuto",
"error": "Chyba!",
"note": "Pozn.:",
"password": "Heslo",
"run_as": "Spustit jako správce",
"save": "Uložit",
"see_more": "Zobrazit více",
"success": "Úspěch!",
"undo_cmd": "Vrátit příkaz",
"username": "Uživatelské jméno",
"warning": "Varování!"
},
"apps": {
"actions": "Akce",
"add_cmds": "Přidat příkazy",
"add_new": "Přidat nový",
"app_name": "Název aplikace",
"app_name_desc": "Název aplikace, jak je uveden v aplikaci Moonlight",
"applications_desc": "Aplikace se obnovují pouze při restartování klienta",
"applications_title": "Aplikace",
"auto_detach": "Pokračovat ve streamování, pokud se aplikace rychle ukončí",
"auto_detach_desc": "Pokusí se automaticky detekovat aplikace typu launcher, které se po spuštění jiného programu nebo jeho instance rychle zavřou. Pokud je detekována aplikace typu launcher, je považována za odpojenou aplikaci.",
"cmd": "Příkaz",
"cmd_desc": "Hlavní aplikace ke spuštění. Pokud je prázdná, nebude spuštěna žádná aplikace.",
"cmd_note": "Pokud cesta ke spustitelnému příkazu obsahuje mezery, musíte ji uvést v uvozovkách.",
"cmd_prep_desc": "Seznam příkazů, které mají být spuštěny před/po této aplikaci. Pokud některý z přípravných příkazů selže, spuštění aplikace se přeruší.",
"cmd_prep_name": "Příprava příkazů",
"covers_found": "Nalezené obaly",
"delete": "Vymazat",
"detached_cmds": "Oddělené příkazy",
"detached_cmds_add": "Přidat oddělený příkaz",
"detached_cmds_desc": "Seznam příkazů, které mají být spuštěny na pozadí.",
"detached_cmds_note": "Pokud cesta k spustitelnému příkazu obsahuje mezery, musíte ji vložit do uvozovek.",
"edit": "Upravit",
"env_app_id": "ID aplikace",
"env_app_name": "Název aplikace",
"env_client_audio_config": "Nastavení zvuku požadované klientem (2.0/5.1/7.1)",
"env_client_enable_sops": "Klient požádal o možnost optimalizovat hru pro optimální streamování (true/false)",
"env_client_fps": "FPS požadované klientem (int)",
"env_client_gcmap": "Požadovaná maska gamepadu ve formátu bitset/bitfield (int)",
"env_client_hdr": "HDR je povoleno klientem (true/false)",
"env_client_height": "Výška požadovaná klientem (int)",
"env_client_host_audio": "Klient si vyžádal hostitelský zvuk (true/false)",
"env_client_width": "Šířka požadovaná klientem (int)",
"env_displayplacer_example": "Příklad - displayplacer pro automatizaci rozlišení:",
"env_qres_example": "Příklad QR pro automatizaci rozlišení:",
"env_qres_path": "qres cesta",
"env_var_name": "Název Var",
"env_vars_about": "O proměnných prostředí",
"env_vars_desc": "Ve výchozím nastavení získávají tyto proměnné prostředí všechny příkazy:",
"env_xrandr_example": "Příklad - Xrandr pro automatizaci rozlišení:",
"exit_timeout": "Časový limit pro ukončení",
"exit_timeout_desc": "Počet sekund, po které se čeká na elegantní ukončení všech procesů aplikace, když je požadováno ukončení. Pokud není nastaveno, výchozí je čekat až 5 sekund. Je-li nastavena hodnota 0, aplikace bude ukončena okamžitě.",
"find_cover": "Najít obal",
"global_prep_desc": "Povolit/zakázat provádění globálních předběžných příkazů pro tuto aplikaci.",
"global_prep_name": "Globální předběžné příkazy",
"image": "Obrázek",
"image_desc": "Cesta k ikoně/obrázku aplikace, který bude odeslán klientovi. Obrázek musí být ve formátu PNG. Pokud není nastaven, Sunshine odešle výchozí obrázek schránky.",
"loading": "Načítám...",
"name": "Název",
"output_desc": "Soubor, kde je uložen výstup příkazu, pokud není zadán, výstup je ignorován",
"output_name": "Výstup",
"run_as_desc": "To může být nutné u některých aplikací, které ke správnému běhu vyžadují oprávnění správce.",
"wait_all": "Pokračujte ve streamování, dokud se neukončí všechny procesy aplikace",
"wait_all_desc": "Streamování bude pokračovat, dokud nebudou ukončeny všechny procesy spuštěné aplikací. Pokud není zaškrtnuto, streamování se zastaví, jakmile se ukončí počáteční proces aplikace, i když ostatní procesy aplikace stále běží.",
"working_dir": "Pracovní adresář",
"working_dir_desc": "Pracovní adresář, který má být předán procesu. Některé aplikace například používají pracovní adresář k vyhledávání konfiguračních souborů. Pokud není nastaven, bude Sunshine implicitně nastaven na nadřazený adresář příkazu"
},
"config": {
"adapter_name": "Název adaptéru",
"adapter_name_desc_linux_1": "Ruční zadání GPU, která se má použít pro snímání.",
"adapter_name_desc_linux_2": "najít všechna zařízení schopná VAAPI",
"adapter_name_desc_linux_3": "Nahraďte ``renderD129`` zařízením z výše uvedeného seznamu, abyste vypsali název a schopnosti zařízení. Aby bylo zařízení Sunshine podporováno, musí mít minimálně:",
"adapter_name_desc_windows": "Ruční zadání GPU, která se má použít pro snímání. Pokud není nastaveno, je GPU vybrána automaticky. Důrazně doporučujeme ponechat toto pole prázdné, chcete-li použít automatický výběr GPU! Poznámka: Toto GPU musí mít připojený a zapnutý displej. Příslušné hodnoty lze zjistit pomocí následujícího příkazu:",
"adapter_name_placeholder_windows": "Řada Radeon RX 580",
"add": "Přidat",
"address_family": "Rodina adres",
"address_family_both": "IPv4+IPv6",
"address_family_desc": "Nastavení rodiny adres, kterou používá Sunshine",
"address_family_ipv4": "Pouze IPv4",
"always_send_scancodes": "Vždy odesílat Scancody",
"always_send_scancodes_desc": "Odesílání scancodes zvyšuje kompatibilitu s hrami a aplikacemi, ale může mít za následek nesprávný vstup na klávesnici od určitých klientů, kteří nepoužívají americké anglické rozložení klávesnice. Povolit, pokud vstup klávesnice v některých aplikacích vůbec nefunguje. Zakázat, pokud klíče klienta generují nesprávný vstup na hostitele.",
"amd_coder": "AMF kodér (H264)",
"amd_coder_desc": "Umožňuje vybrat entropie kódování pro upřednostnění kvality nebo rychlosti kódování. Pouze H.264.",
"amd_enforce_hrd": "Vymáhání hypotetického referenčního dekodéru (HRD) AMF",
"amd_enforce_hrd_desc": "Zvyšuje omezení regulace rychlosti, aby splňovala požadavky modelu HRD. To výrazně snižuje bitrate přetečení, ale může způsobit kódování artefaktů nebo nižší kvalitu na některých kartách.",
"amd_preanalysis": "Předběžná analýza AMF",
"amd_preanalysis_desc": "To umožňuje předběžnou analýzu kontroly sazeb, která může zvýšit kvalitu na úkor zvýšené latence kódování.",
"amd_quality": "Kvalita AMF",
"amd_quality_balanced": "vyrovnané -- vyvážené (výchozí)",
"amd_quality_desc": "Tím se řídí rovnováha mezi rychlostí kódování a kvalitou.",
"amd_quality_group": "Nastavení kvality AMF",
"amd_quality_quality": "kvalita -- preferovat kvalitu",
"amd_quality_speed": "Rychlost -- preferovat rychlost",
"amd_rc": "Ovládání AMF",
"amd_rc_cbr": "cbr -- konstantní bitrate (doporučujeme pokud je HRD zapnuto)",
"amd_rc_cqp": "cqp -- konstantní qp režim",
"amd_rc_desc": "Tím se řídí metoda řízení rychlosti, aby se zajistilo, že nepřekročíme cílový datový tok klienta. 'cqp' není vhodné pro cílování datového toku a ostatní možnosti kromě 'vbr_latency' závisí na HRD Enforcement, které pomáhají omezit přetečení datového toku.",
"amd_rc_group": "Nastavení řízení AMF",
"amd_rc_vbr_latency": "vbr_latency -- proměnný datový tok s omezenou latencí (doporučeno, pokud je HRD vypnuta; výchozí)",
"amd_rc_vbr_peak": "vbr_peak -- maximální nastavená proměnná bitrate",
"amd_usage": "Využití AMF",
"amd_usage_desc": "Nastaví základní profil kódování. Všechny níže uvedené možnosti přepíší podmnožinu uživatelského profilu, ale existují další skrytá nastavení, která nelze nastavit jinde.",
"amd_usage_lowlatency": "lowlatency - nízká latence (nejrychlejší)",
"amd_usage_lowlatency_high_quality": "lowlatency_high_quality - nízká latence, vysoká kvalita (rychlá)",
"amd_usage_transcoding": "překódování -- překódování (nejpomalejší)",
"amd_usage_ultralowlatency": "ultralowlatence - ultra nízká latence (nejrychlejší; výchozí)",
"amd_usage_webcam": "webová kamera -- webová kamera (pomalá)",
"amd_vbaq": "AMF adaptivní kvantifikace založená na rozptylu (VBAQ)",
"amd_vbaq_desc": "Lidský vizuální systém je obvykle méně citlivý na artefakty ve vysoce strukturovaných oblastech. V režimu VBAQ se rozptyl pixelů používá k označení složitosti prostorových textur, což umožňuje enkodéru přidělit více bitů do plynulejších oblastí. Povolení této funkce vede ke zlepšení subjektivní vizuální kvality s určitým obsahem.",
"apply_note": "Kliknutím na tlačítko \"Použít\" restartujte Sunshine a aplikujte změny. Tím se ukončí všechny spuštěné relace.",
"audio_sink": "Zvukový výřez",
"audio_sink_desc_linux": "Název skluzu zvuku, který se používá pro zvukovou smyčku. Pokud tuto proměnnou nevyberete, pulseaudio zvolí výchozí zařízení pro monitor. Název sklízení zvuku můžete najít pomocí některého příkazu:",
"audio_sink_desc_macos": "Název zvukového výřezu používaný pro Audio Loopback. Sunshine má přístup pouze k mikrofonům na macOS kvůli systémovým omezením. Pro streamování zvuku systému pomocí Soundflower nebo BlackHole.",
"audio_sink_desc_windows": "Ručně zadejte konkrétní zvukové zařízení pro zachycení. Pokud je zařízení odstaveno, je vybráno automaticky. Důrazně doporučujeme ponechat toto pole prázdné pro automatický výběr zařízení! Pokud máte více zvukových zařízení se stejnými jmény, můžete získat ID zařízení pomocí následujícího příkazu:",
"audio_sink_placeholder_macos": "BlackHole 2ch",
"audio_sink_placeholder_windows": "Reproduktory (High Definition Audio Device)",
"av1_mode": "AV1 podpora",
"av1_mode_0": "Sunshine bude inzerovat podporu AV1 na základě schopností kodéru (doporučeno)",
"av1_mode_1": "Sunshine nebude inzerovat podporu AV1",
"av1_mode_2": "Sunshine bude inzerovat podporu hlavního 8bitového profilu AV1",
"av1_mode_3": "Sunshine bude inzerovat podporu hlavních 8bitových a 10bitových profilů AV1 (HDR)",
"av1_mode_desc": "Umožňuje klientovi požádat o AV1 hlavní 8-bitové nebo 10-bitové video streamy. AV1 je intenzivnější na CPU kódování, takže díky tomu může dojít ke snížení výkonu při používání kódování softwaru.",
"back_button_timeout": "Časový limit emulace tlačítka Domů/Návod",
"back_button_timeout_desc": "Pokud je tlačítko Zpět/Výběr podrženo pro zadaný počet milisekund, je emulováno stisknutí tlačítka Domů/Průvodce. Je-li nastaveno na hodnotu < 0 (výchozí), podržením tlačítka Zpět/Výběr nebude tlačítko Domů/Průvodce emulováno.",
"capture": "Vynutit specifickou metodu snímání",
"capture_desc": "V automatickém režimu bude Sunshine používat ten první, který funguje. NvFBC vyžaduje upravené nvidia ovladače.",
"cert": "Osvědčení",
"cert_desc": "Certifikát používaný pro párování webových UI a Moonlight klientů. Pro nejlepší kompatibilitu by měl mít veřejný klíč RSA-2048.",
"channels": "Maximální počet připojených klientů",
"channels_desc_1": "Sluneční svár může umožnit sdílení jediné streamovací relace s více klienty současně.",
"channels_desc_2": "Některé hardwarové enkodéry mohou mít omezení, která snižují výkon s více streamy.",
"coder_cabac": "cabac -- kontextové binární aritmetické kódování vyšší kvalita",
"coder_cavlc": "cavlc -- kontextové adaptivní kódování variabilní délky - rychlejší dekódování",
"configuration": "Konfigurace",
"controller": "Enable Gamepad Input",
"controller_desc": "Umožňuje hostům ovládat hostitelský systém pomocí gamepad / controller",
"credentials_file": "Soubor pověření",
"credentials_file_desc": "Uložit uživatelské jméno/heslo odděleně od souboru se stavem Sunshine.",
"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_config_revert_delay": "Zpoždění nastavení zpětného chodu",
"dd_config_revert_delay_desc": "Další zpoždění v milisekundách čeká na obnovení konfigurace při zavření aplikace nebo ukončení poslední relace. Hlavním účelem je zajistit hladký přechod při rychlém přepínání mezi aplikacemi.",
"dd_config_revert_on_disconnect": "Konfigurace se vrátí po odpojení",
"dd_config_revert_on_disconnect_desc": "Vrátit konfiguraci po odpojení všech klientů namísto zavření aplikace nebo posledního ukončení relace.",
"dd_config_verify_only": "Ověřte, zda je displej povolen",
"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_mode_remapping": "Režim zobrazení přemapování",
"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.",
"dd_mode_remapping_desc_2": "Seznam se iteruje shora dolů a použije se první shoda.",
"dd_mode_remapping_desc_3": "Pole \"Požadováno\" mohou být ponechána prázdná, aby odpovídala libovolné požadované hodnotě.",
"dd_mode_remapping_desc_4_final_values_mixed": "Alespoň jedno pole \"Final\" musí být zadáno. Nespecifikované rozlišení nebo obnovovací frekvence se nezmění.",
"dd_mode_remapping_desc_4_final_values_non_mixed": "Pole \"Final\" musí být zadáno a nesmí být prázdné.",
"dd_mode_remapping_desc_5_sops_mixed_only": "Možnost \"Optimalizovat nastavení hry\" musí být povolena v klientovi Měsíční světlo, jinak budou přeskočeny záznamy s libovolnými vybranými rozlišeními polí.",
"dd_mode_remapping_desc_5_sops_resolution_only": "Možnost \"Optimalizovat nastavení hry\" musí být povolena v klientovi Měsíční světlo, jinak je mapování přeskočeno.",
"dd_mode_remapping_final_refresh_rate": "Konečná obnovovací frekvence",
"dd_mode_remapping_final_resolution": "Konečné řešení",
"dd_mode_remapping_requested_fps": "Požadovaná FPS",
"dd_mode_remapping_requested_resolution": "Požadované rozlišení",
"dd_options_header": "Rozšířené možnosti displeje",
"dd_refresh_rate_option": "Obnovovací frekvence",
"dd_refresh_rate_option_auto": "Použít FPS hodnotu poskytnutou 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 pro použití",
"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": "Možnost \"Optimalizovat nastavení hry\" musí být povolena na klientovi Moonlight aby to fungovalo.",
"dd_wa_hdr_toggle_delay_desc_1": "Při použití virtuálního displeje (VDD) pro vysílání může nesprávně zobrazit barvu HDR. Sluneční svár se může pokusit tento problém zmírnit tím, že vypne HDR a poté znovu vypne.",
"dd_wa_hdr_toggle_delay_desc_2": "Pokud je hodnota nastavena na 0, je práce zakázána (výchozí). Pokud je hodnota mezi 0 a 3000 milisekundami, sunshine vypne HDR, počkejte na stanovený čas a pak znovu zapněte HDR. Doporučená doba zpoždění je ve většině případů přibližně 500 milisekund.",
"dd_wa_hdr_toggle_delay_desc_3": "NEPOUŽÍVEJTE toto fungování, pokud nemáte problémy s HDR protože přímo ovlivňuje počáteční čas!",
"dd_wa_hdr_toggle_delay": "Práce na vysokém kontrastu pro HDR",
"ds4_back_as_touchpad_click": "Mapa zpátky/Vyberte pro klepnutí na Touchpad",
"ds4_back_as_touchpad_click_desc": "Při vynucení emulace DS4 mapa zpět/Vyberte na Touchpad kliknutí",
"encoder": "Vynutit specifický enkodér",
"encoder_desc": "Vynutit konkrétní enkodér, jinak Sunshine vybere nejlepší dostupnou možnost. Poznámka: Pokud zadáte hardwarový enkodér v systému Windows, musí odpovídat GPU, kde je displej připojen.",
"encoder_software": "Programové vybavení",
"external_ip": "Externí IP",
"external_ip_desc": "Pokud není zadána žádná externí IP adresa, Sluneční server automaticky rozpozná externí IP adresu",
"fec_percentage": "Procento FEC",
"fec_percentage_desc": "Procento chyb při opravě paketů na datových paketech v každém video snímku. Vyšší hodnoty mohou opravit větší ztrátu síťových paketů, ale za cenu zvýšení využití šířky pásma.",
"ffmpeg_auto": "auto -- nechat rozhodnutí ffmpeg (výchozí)",
"file_apps": "Soubor aplikací",
"file_apps_desc": "Soubor, kde jsou uloženy aktuální aplikace Sunshine.",
"file_state": "Státní soubor",
"file_state_desc": "Soubor, ve kterém je uložen aktuální stav sunshine",
"gamepad": "Emulovaný typ hry",
"gamepad_auto": "Možnosti automatického výběru",
"gamepad_desc": "Vyberte typ gamepadu, který chcete emulovat na hostiteli",
"gamepad_ds4": "DS4 (PS4)",
"gamepad_ds4_manual": "Možnosti výběru DS4",
"gamepad_ds5": "DS5 (PS5)",
"gamepad_switch": "Nintendo Pro (Switch)",
"gamepad_manual": "Možnosti manuálního DS4",
"gamepad_x360": "X360 (Xbox 360)",
"gamepad_xone": "Xone (Xbox Jedna)",
"global_prep_cmd": "Příprava příkazů",
"global_prep_cmd_desc": "Konfigurace seznamu příkazů, které mají být provedeny před spuštěním jakékoli aplikace nebo po ní. Pokud některý z určených příkazů neuspěje, proces spuštění aplikace bude přerušen.",
"hevc_mode": "Podpora HEVC",
"hevc_mode_0": "Sunshine bude propagovat podporu pro HEVC na základě možností enkodéru (doporučeno)",
"hevc_mode_1": "Sluneční síť nebude propagovat podporu HEVC",
"hevc_mode_2": "Sluneční svaz bude propagovat podporu hlavního profilu HEVC",
"hevc_mode_3": "Sluneční svaz bude propagovat podporu profilů HEVC Main a Main10 (HDR)",
"hevc_mode_desc": "Umožňuje klientovi vyžádat si HEVC Main nebo HEVC Main10 video streamy. HEVC je intenzivnější na CPU kódování, takže povolení může snížit výkon při používání kódování softwaru.",
"high_resolution_scrolling": "Podpora rolování s vysokým rozlišením",
"high_resolution_scrolling_desc": "Pokud je povoleno, sunshine projde událostmi posunu s vysokým rozlišením od klientů Moonight. To může být užitečné pro vypnutí starších aplikací, které se posouvají příliš rychle při posunu s vysokým rozlišením.",
"install_steam_audio_drivers": "Nainstalujte Steam Audio Drivers",
"install_steam_audio_drivers_desc": "Pokud je Steam nainstalován, tak se automaticky nainstaluje ovladač Steam Streaming Speakers pro podporu 5.1/7.1 prostorového zvuku a ztlumení zvuku.",
"key_repeat_delay": "Zpoždění opakování klíče",
"key_repeat_delay_desc": "Ovládejte, jak rychle se budou klíče opakovat. Počáteční zpoždění v milisekundách před opakováním klíčů.",
"key_repeat_frequency": "Frekvence opakování klíče",
"key_repeat_frequency_desc": "Jak často se klíče opakují každou vteřinu. Tato konfigurovatelná volba podporuje desetinná místa.",
"key_rightalt_to_key_win": "Mapovat pravý Alt klíč k klíči Windows",
"key_rightalt_to_key_win_desc": "Může být možné, že z Moonlight nemůžete přímo odeslat klíč pro Windows. V těchto případech může být užitečné udělat sunshine si myslet, že klíč pravý Alt je klíč pro Windows",
"keyboard": "Povolit vstup klávesnice",
"keyboard_desc": "Umožňuje hostům ovládat hostitelský systém pomocí klávesnice",
"lan_encryption_mode": "Režim šifrování LAN",
"lan_encryption_mode_1": "Povoleno pro podporované klienty",
"lan_encryption_mode_2": "Vyžadováno pro všechny klienty",
"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": "Lokální prostředí 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": "Fatal",
"log_level_6": "Nic",
"log_level_desc": "Minimální úroveň logu vytištěná pro standardizaci",
"log_path": "Cesta k logu",
"log_path_desc": "Soubor s aktuálními protokoly sunshine jsou uloženy.",
"max_bitrate": "Maximální bitrate",
"max_bitrate_desc": "Maximální bitrate (v Kbps) zakódovaný sunshine streamem. Je-li nastaveno na 0, bude vždy používat bitrate požadovaný Moonlight.",
"min_threads": "Minimální počet CPU vláken",
"min_threads_desc": "Zvýšení hodnoty mírně snižuje efektivitu kódování, ale tento kompromis obvykle stojí za to získat více jader CPU pro kódování. Ideální hodnota je nejnižší hodnota, která může spolehlivě kódovat v požadovaném nastavení streamu do vašeho hardwaru.",
"misc": "Různé možnosti",
"motion_as_ds4": "Emulovat DS4 gamepad pokud jsou přítomny snímače pohybu",
"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.",
"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.",
"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",
"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_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_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_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",
"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.",
"port_note": "Poznámka",
"port_port": "Přístav",
"port_protocol": "Protocol",
"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čí!",
"port_web_ui": "Web UI",
"qp": "Parametr kvantizace",
"qp_desc": "Některá zařízení nemusí podporovat Constant Bit Rate. Pro tato zařízení se místo toho používá QP. Vyšší hodnota znamená větší kompresi, ale menší kvalitu.",
"qsv_coder": "Kodér QuickSync (H264)",
"qsv_preset": "QuickSync Preset",
"qsv_preset_fast": "rychlá (nízká kvalita)",
"qsv_preset_faster": "rychlejší (nižší kvalita)",
"qsv_preset_medium": "střední (výchozí)",
"qsv_preset_slow": "pomalý (dobrá kvalita)",
"qsv_preset_slower": "pomalejší (lepší kvalita)",
"qsv_preset_slowest": "nejpomalejší (nejlepší kvalita)",
"qsv_preset_veryfast": "nejrychlejší (nejnižší jakost)",
"qsv_slow_hevc": "Povolit pomalé kódování HEVC",
"qsv_slow_hevc_desc": "To může povolit HEVC kódování na starších Intel GPU, za cenu vyšší spotřeby GPU a horšího výkonu.",
"restart_note": "Sluneční brýle se restartuje a aplikuje změny.",
"stream_audio": "Stream zvuk",
"stream_audio_desc": "Zakázání zvuku může být užitečné pro streamování bezhlavých displejů jako druhý monitor.",
"sunshine_name": "Sluneční jméno",
"sunshine_name_desc": "Jméno zobrazené podle měsíčního světla. Není-li zadáno, použije se hostname počítače",
"sw_preset": "SW přednastavení",
"sw_preset_desc": "Optimalizujte kompromis mezi rychlostí kódování (kódované snímky za sekundu) a efektivitou komprese (kvalita na bit v bitovém toku). Výchozí nastavení je superrychlé.",
"sw_preset_fast": "rychlá",
"sw_preset_faster": "rychleji",
"sw_preset_medium": "střední",
"sw_preset_slow": "pomalu",
"sw_preset_slower": "pomalejší",
"sw_preset_superfast": "superfast (výchozí)",
"sw_preset_ultrafast": "ultrafast",
"sw_preset_veryfast": "veryfast",
"sw_preset_veryslow": "veryslow",
"sw_tune": "SW melodie",
"sw_tune_animation": "animace -- dobré pro karikatury; používá vyšší deblokovací a více referenčních rámců",
"sw_tune_desc": "Vyladění možností, které jsou aplikovány po předvolbě. Výchozí nastavení je nula.",
"sw_tune_fastdecode": "fastdecode -- umožňuje rychlejší dekódování vypnutím určitých filtrů",
"sw_tune_film": "film -- používá pro vysoce kvalitní filmový obsah; snižuje odblokování",
"sw_tune_grain": "zrno zachovává strukturu zrn ve starém, zrním materiálu",
"sw_tune_stillimage": "stillimage -- dobré pro slideshow-like obsah",
"sw_tune_zerolatency": "nulová latence -- dobrá pro rychlé kódování a nízká latence streamování (výchozí)",
"touchpad_as_ds4": "Emulovat DS4 gamepad pokud klient nahlásí přítomnost touchpadu",
"touchpad_as_ds4_desc": "Je-li zakázáno, během výběru typu gamepadu nebude brána v úvahu přítomnost touchpadu.",
"upnp": "UPnP",
"upnp_desc": "Automaticky konfigurovat přesměrování portů pro streamování přes Internet",
"vaapi_strict_rc_buffer": "Striktně prosazovat limity bitrate pro H.264/HEVC na AMD GPU.",
"vaapi_strict_rc_buffer_desc": "Povolením této volby se během změn scény vyhnete shození snímků přes síť, ale kvalita videa může být během pohybu snížena.",
"virtual_sink": "Virtuální šinek",
"virtual_sink_desc": "Ručně zadejte virtuální audio zařízení, které má být použito. Pokud je zařízení odstaveno, je vybráno automaticky. Důrazně doporučujeme ponechat toto pole prázdné pro automatický výběr zařízení!",
"virtual_sink_placeholder": "Reproduktory pro streamování přes 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_software_allowed": "Povoleno",
"vt_software_forced": "Vynucené",
"wan_encryption_mode": "Režim šifrování WAN",
"wan_encryption_mode_1": "Povoleno pro podporované klienty (výchozí)",
"wan_encryption_mode_2": "Vyžadováno pro všechny klienty",
"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.",
"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!",
"loading_latest": "Načítání nejnovější verze...",
"new_pre_release": "Je k dispozici nová verze před-vydání!",
"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!",
"version_latest": "Používáte nejnovější verzi Sunshine",
"welcome": "Ahoj, sunshine!"
},
"navbar": {
"applications": "Aplikace",
"configuration": "Konfigurace",
"home": "Domů",
"password": "Změnit heslo",
"pin": "PIN",
"theme_auto": "Automaticky",
"theme_dark": "Tmavý",
"theme_light": "Světlý",
"toggle_theme": "Téma",
"troubleshoot": "Řešení problémů"
},
"password": {
"confirm_password": "Potvrzení hesla",
"current_creds": "Aktuální přihlašovací údaje",
"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."
},
"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í!"
},
"resource_card": {
"github_discussions": "GitHub Discussions",
"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.",
"license": "Licence",
"lizardbyte_website": "Webové stránky LizardByte",
"resources": "Zdroje",
"resources_desc": "Zdroje pro sunnity!",
"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_error": "Chyba při obnovování trvalosti!",
"dd_reset_success": "Úspěch resetování trvalosti!",
"force_close": "Vynutit zavření",
"force_close_desc": "Pokud si Měsíc stěžuje na právě spuštěnou aplikaci, vynucené zavření aplikace by mělo problém vyřešit.",
"force_close_error": "Chyba při zavírání aplikace",
"force_close_success": "Aplikace úspěšně uzavřena!",
"logs": "Logy",
"logs_desc": "Podívejte se na logy nahrané sunshine",
"logs_find": "Najít...",
"restart_sunshine": "Restartovat Sunshine",
"restart_sunshine_desc": "Pokud sluneční svit nefunguje správně, můžete jej zkusit restartovat. To ukončí všechny spuštěné relace.",
"restart_sunshine_success": "Sunshine se restartuje",
"troubleshooting": "Řešení problémů",
"unpair_all": "Zrušit spárování vše",
"unpair_all_error": "Chyba při nepárování",
"unpair_all_success": "Všechna zařízení nejsou spárována.",
"unpair_desc": "Odstranit spárovaná zařízení. Jednotlivě nespárovaná zařízení s aktivní relací zůstanou připojena, ale nemohou spustit nebo obnovit relaci.",
"unpair_single_no_devices": "Neexistují žádná spárovaná zařízení.",
"unpair_single_success": "Zařízení však mohou být stále v aktivní relaci. Použijte tlačítko 'Vynutit zavření' pro ukončení všech otevřených relací.",
"unpair_single_unknown": "Neznámý klient",
"unpair_title": "Zrušit párování"
},
"welcome": {
"confirm_password": "Potvrdit heslo",
"create_creds": "Před spuštěním potřebujeme, abyste vytvořili nové uživatelské jméno a heslo pro přístup k webovému uživatelskému rozhraní.",
"create_creds_alert": "Níže uvedené přihlašovací údaje jsou potřebné pro přístup k webovému rozhraní Sunshine. Uchovávejte je v bezpečí, protože je už nikdy nebudete vidět!",
"greeting": "Vítejte v Sunshine!",
"login": "Přihlásit se",
"welcome_success": "Tato stránka se brzy obnoví, váš prohlížeč vás požádá o nové přihlašovací údaje."
}
}

View File

@@ -337,8 +337,6 @@
"log_path_desc": "The file where the current logs of Apollo are stored.",
"max_bitrate": "Maximum Bitrate",
"max_bitrate_desc": "The maximum bitrate (in Kbps) that Apollo will encode the stream at. If set to 0, it will always use the bitrate requested by Artemis/Moonlight.",
"min_fps_factor": "Minimum FPS Factor",
"min_fps_factor_desc": "Apollo will use this factor to calculate the minimum time between frames. Increasing this value slightly may help when streaming mostly static content. Higher values will consume more bandwidth.",
"min_threads": "Minimum CPU Thread Count",
"min_threads_desc": "Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest value that can reliably encode at your desired streaming settings on your hardware.",
"misc": "Miscellaneous options",

View File

@@ -330,8 +330,6 @@
"log_level_desc": "打印到标准输出的最小日志级别",
"log_path": "日志文件路径",
"log_path_desc": "Apollo 当前日志存储的文件。",
"min_fps_factor": "最小帧率系数",
"min_fps_factor_desc": "Apollo 将使用此系数来计算帧之间的最小时间。当串流的内容主要为静态内容时,稍微增加此值可能会有帮助。更大的值将会使用更大的带宽。",
"min_threads": "最低 CPU 线程数",
"min_threads_desc": "提高该值会略微降低编码效率,但为了获得更多的 CPU 内核用于编码,通常是值得的。理想值是在您的硬件配置上以所需的串流设置进行可靠编码的最低值。",
"misc": "杂项选项",

View File

@@ -0,0 +1,455 @@
{
"_common": {
"apply": "套用",
"auto": "自動",
"autodetect": "自動偵測(建議)",
"beta": "(測試版)",
"cancel": "取消",
"disabled": "已停用",
"disabled_def": "已停用(預設)",
"disabled_def_cbox": "預設值:未勾選",
"dismiss": "關閉",
"do_cmd": "執行指令",
"elevated": "提高權限",
"enabled": "已啟用",
"enabled_def": "已啟用(預設值)",
"enabled_def_cbox": "預設值:已勾選",
"error": "錯誤!",
"note": "請注意:",
"password": "密碼",
"run_as": "以系統管理員身份執行",
"save": "節省",
"see_more": "查看更多資訊",
"success": "成功!",
"undo_cmd": "復原指令",
"username": "使用者名稱",
"warning": "警告!"
},
"apps": {
"actions": "行動",
"add_cmds": "新增指令",
"add_new": "新增",
"app_name": "應用程式名稱",
"app_name_desc": "應用程式名稱(在 Moonlight 上顯示的名稱)",
"applications_desc": "應用程式清單僅在用戶端重新啟動時更新",
"applications_title": "應用程式一覽",
"auto_detach": "即使應用程式瞬間關閉,仍會繼續串流。",
"auto_detach_desc": "此功能會自動偵測那些在啟動其他程式或自身的另一個執行個體後,立即關閉的啟動器類應用程式。一旦偵測到這類應用程式,系統會將其視為獨立運行的應用程式。",
"cmd": "指令",
"cmd_desc": "這是要啟動的主要應用程式。如果留空,則不會啟動任何程式。",
"cmd_note": "如果指令執行檔的路徑有空格,請將路徑放在引號中。",
"cmd_prep_desc": "這是啟動應用程式前後要執行的指令清單。如果任何準備指令執行失敗,應用程式將無法啟動。",
"cmd_prep_name": "指令準備",
"covers_found": "找到封面圖片",
"delete": "刪除",
"detached_cmds": "獨立指令",
"detached_cmds_add": "新增獨立指令",
"detached_cmds_desc": "在背景執行的指令清單。",
"detached_cmds_note": "如果指令執行檔的路徑有空格,請將路徑放在引號中。",
"edit": "編輯",
"env_app_id": "應用程式 ID",
"env_app_name": "應用程式名稱",
"env_client_audio_config": "用戶端要求的音訊設定 (2.0/5.1/7.1)",
"env_client_enable_sops": "用戶端要求將遊戲進行最佳化,以達到最佳串流效果 (true/false)",
"env_client_fps": "用戶端要求的 FPS整數",
"env_client_gcmap": "要求的遊戲手把遮罩,使用位元集合/位元欄位格式 (整數)",
"env_client_hdr": "HDR 由用戶端啟用 (true/false)",
"env_client_height": "用戶端要求的高度(整數)",
"env_client_host_audio": "用戶端要求的主機音訊 (true/false)",
"env_client_width": "用戶端要求的寬度(整數)",
"env_displayplacer_example": "範例 - 用於解析度自動化的 displayplacer",
"env_qres_example": "範例 - 用於解析度自動化的 QRes",
"env_qres_path": "qres 路徑",
"env_var_name": "變數名稱",
"env_vars_about": "關於環境變數",
"env_vars_desc": "所有指令預設會獲得這些環境變數:",
"env_xrandr_example": "範例 - 用於解析度自動化的 Xrandr",
"exit_timeout": "結束逾時設定",
"exit_timeout_desc": "當要求結束時,等待所有應用程式處理程序正常結束的秒數。如果未設定,預設會等待最多 5 秒。如果設為 0應用程式將立即終止。",
"find_cover": "尋找封面圖片",
"global_prep_desc": "啟用/停用此應用程式的全域準備指令執行。",
"global_prep_name": "全域準備指令",
"image": "圖片",
"image_desc": "將傳送至用戶端的應用程式圖示/圖片/圖片路徑。圖片必須是 PNG 格式。如果未設定Sunshine 將傳送預設的盒狀圖示。",
"loading": "載入中…",
"name": "名稱",
"output_desc": "指令輸出的儲存檔案。如果未指定,輸出將被忽略",
"output_name": "輸出",
"run_as_desc": "某些需要管理員權限才能正常運行的應用程式,可能需要這個設定。",
"wait_all": "繼續串流,直到所有應用程式處理程序結束",
"wait_all_desc": "這會繼續串流,直到應用程式啟動的所有處理程序都結束。如果未勾選,當最初的應用程式處理程序結束時,串流就會停止,即使還有其他處理程序在運行。",
"working_dir": "工作目錄",
"working_dir_desc": "這是要傳遞給處理程序的工作目錄。例如有些應用程式會使用這個目錄來搜尋設定檔。如果沒有設定Sunshine 會自動使用指令所在的目錄"
},
"config": {
"adapter_name": "顯示卡名稱",
"adapter_name_desc_linux_1": "手動指定用於擷取的 GPU。",
"adapter_name_desc_linux_2": "找出所有支援 VAAPI 的裝置",
"adapter_name_desc_linux_3": "將 ``renderD129`` 替換為上面提到的裝置,以列出裝置的名稱和功能。要被 Sunshine 支援,裝置至少需要具備以下條件:",
"adapter_name_desc_windows": "手動指定用於擷取的 GPU。如果未設定系統會自動選擇 GPU。我們強烈建議保持此欄位為空以使用自動 GPU 選擇!注意:此 GPU 必須已連接顯示器並開啟電源。可以使用以下指令來查找適當的值:",
"adapter_name_placeholder_windows": "Radeon RX 580 系列",
"add": "新增",
"address_family": "地址家庭",
"address_family_both": "IPv4+IPv6",
"address_family_desc": "設定 Sunshine 使用的位址族",
"address_family_ipv4": "僅 IPv4",
"always_send_scancodes": "永遠傳送掃描碼",
"always_send_scancodes_desc": "傳送掃描碼可以增強與遊戲和應用程式的相容性,但可能會導致某些未使用美式英語鍵盤佈局的用戶端輸入錯誤。若某些應用程式的鍵盤輸入完全無效,請啟用此選項。若用戶端的鍵盤輸入在主機端產生錯誤輸入,則請停用此選項。",
"amd_coder": "AMF 編碼器 (H264)",
"amd_coder_desc": "允許您選擇熵編碼,以優先品質或編碼速度。僅限 H.264。",
"amd_enforce_hrd": "AMF 假想參考解碼器 (HRD) 強制執行",
"amd_enforce_hrd_desc": "增加速率控制的限制,以符合 HRD 模型的要求。這可大幅減少比特率溢出,但可能會在某些卡上造成編碼假象或降低品質。",
"amd_preanalysis": "AMF 預分析",
"amd_preanalysis_desc": "這可進行速率控制預分析,以增加編碼延遲為代價來提高品質。",
"amd_quality": "AMF 品質",
"amd_quality_balanced": "balanced—平衡預設",
"amd_quality_desc": "這可以控制編碼速度和品質之間的平衡。",
"amd_quality_group": "AMF 品質設定",
"amd_quality_quality": "quality—偏好品質",
"amd_quality_speed": "speed—偏好速度",
"amd_rc": "AMF 速率控制",
"amd_rc_cbr": "cbr—固定位元率如果啟用 HRD建議使用",
"amd_rc_cqp": "cqp—常數 qp 模式",
"amd_rc_desc": "這個選項控制了速率控制方法,確保不超過客戶端的位元率目標。'cqp' 不適用於位元率目標設定,除了 'vbr_latency' 外,其他選項依賴 HRD 強制執行來幫助限制位元率溢出。",
"amd_rc_group": "AMF 速率控制設定",
"amd_rc_vbr_latency": "vbr_latency—受延遲限制的可變位元率如果停用 HRD建議使用此選項預設",
"amd_rc_vbr_peak": "vbr_peak—峰值受限的可變位元率",
"amd_usage": "AMF 使用情況",
"amd_usage_desc": "這會設定基本的編碼設定檔。以下顯示的所有選項都會覆蓋部分設定檔的設定,但設定檔還包含其他無法在其他地方調整的隱藏設定。",
"amd_usage_lowlatency": "lowlatency—低延遲最快",
"amd_usage_lowlatency_high_quality": "lowlatency_high_quality—低延遲、高品質快速",
"amd_usage_transcoding": "transcoding—轉碼最慢",
"amd_usage_ultralowlatency": "ultralowlatency—超低延遲最快預設值",
"amd_usage_webcam": "webcam—網路攝影機慢速",
"amd_vbaq": "AMF 基於方差的自適應量化 (VBAQ)",
"amd_vbaq_desc": "人類的視覺系統通常對高度紋理區域中的壓縮失真較不敏感。在 VBAQ 模式下,像素變異用於指示空間紋理的複雜度,使編碼器能夠將更多位元分配給較平滑的區域。啟用此功能可在某些內容上提升主觀視覺品質。",
"apply_note": "點選「套用」以重新啟動 Sunshine 並應用變更。這將終止所有正在進行的工作階段。",
"audio_sink": "音訊水槽",
"audio_sink_desc_linux": "用於 Audio Loopback 的音訊輸出的名稱。如果您沒有指定這個變數pulseaudio 將會選擇預設的監視裝置。您可以使用以下任一指令找出音訊輸出裝置的名稱:",
"audio_sink_desc_macos": "用於音訊環回的音訊槽名稱。由於系統限制Sunshine 只能在 macOS 上存取麥克風。要使用 Soundflower 或 BlackHole 串流系統音訊。",
"audio_sink_desc_windows": "手動指定要擷取的特定音訊裝置。若未設定,系統會自動選擇裝置。我們強烈建議將此欄位留空以使用自動裝置選擇!若您有多個名稱相同的音訊裝置,您可以使用以下指令來取得裝置 ID",
"audio_sink_placeholder_macos": "黑洞 2ch",
"audio_sink_placeholder_windows": "揚聲器(高解析度音訊裝置)",
"av1_mode": "AV1 支援",
"av1_mode_0": "Sunshine 將根據編碼器功能來宣告是否支援 AV1建議",
"av1_mode_1": "Sunshine 不會宣告支援 AV1",
"av1_mode_2": "Sunshine 將宣告支援 AV1 Main 8 位元設定檔",
"av1_mode_3": "Sunshine 將宣告支援 AV1 Main 8 位元和 10 位元 (HDR) 設定檔",
"av1_mode_desc": "允許用戶端要求 AV1 Main 8 位元或 10 位元視訊串流。AV1 的編碼較耗費 CPU因此使用軟體編碼時啟用此功能可能會降低效能。",
"back_button_timeout": "主畫面/導覽按鈕模擬超時",
"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 私鑰。",
"channels": "最大連線用戶端數量",
"channels_desc_1": "Sunshine 可讓單一串流工作階段同時與多個裝置共享。",
"channels_desc_2": "某些硬體編碼器可能會因多重串流而受到性能限制。",
"coder_cabac": "cabac—上下文自適應二元算術編碼更高的品質",
"coder_cavlc": "cavlc—上下文自適應可變長度編碼 - 解碼速度較快",
"configuration": "組態",
"controller": "啟用遊戲手把輸入",
"controller_desc": "允許訪客使用遊戲手把或遊戲控制器操作主機系統。",
"credentials_file": "憑證檔案",
"credentials_file_desc": "將用戶名稱/密碼儲存在與 Sunshine 狀態檔案不同的位置。",
"dd_config_ensure_active": "自動啟用顯示器",
"dd_config_ensure_only_display": "停用其他顯示器,並只啟用指定的顯示器",
"dd_config_ensure_primary": "自動啟用顯示器並設定為主要顯示器",
"dd_config_label": "裝置設定",
"dd_config_revert_delay": "設定回復延遲",
"dd_config_revert_delay_desc": "當應用程式關閉或最後一個工作階段結束時,將會額外等待的延遲時間再恢復設定,以毫秒為單位。這樣做的主要目的是讓在快速切換應用程式時能夠更順暢。",
"dd_config_revert_on_disconnect": "斷線時恢復設定",
"dd_config_revert_on_disconnect_desc": "在所有用戶端斷線時恢復設定,而不是應用程式關閉或最後一個工作階段結束時。",
"dd_config_verify_only": "確認顯示器已啟用",
"dd_hdr_option": "HDR",
"dd_hdr_option_auto": "根據用戶端的要求開啟/關閉 HDR 模式(預設值)",
"dd_hdr_option_disabled": "不更改 HDR 設定",
"dd_mode_remapping": "顯示模式重新映射",
"dd_mode_remapping_add": "新增重新映射項目",
"dd_mode_remapping_desc_1": "指定重新映射項目以將請求的解析度和/或更新率更改為其他值。",
"dd_mode_remapping_desc_2": "清單會從上到下迭代,並使用第一個匹配項目。",
"dd_mode_remapping_desc_3": "「要求的」欄位可以留空,以匹配任何要求的值。",
"dd_mode_remapping_desc_4_final_values_mixed": "必須指定至少一個「最終」欄位。未指定的解析度或更新率將不會被更改。",
"dd_mode_remapping_desc_4_final_values_non_mixed": "必須指定「最終」欄位,且不能為空。",
"dd_mode_remapping_desc_5_sops_mixed_only": "必須在 Moonlight 用戶端啟用「最佳化遊戲設定」選項,否則指定了解析度欄位的項目將被跳過。",
"dd_mode_remapping_desc_5_sops_resolution_only": "必須在 Moonlight 用戶端啟用「最佳化遊戲設定」選項,否則映射將被跳過。",
"dd_mode_remapping_final_refresh_rate": "最終更新率",
"dd_mode_remapping_final_resolution": "最終解析度",
"dd_mode_remapping_requested_fps": "要求的 FPS",
"dd_mode_remapping_requested_resolution": "要求的解析度",
"dd_options_header": "進階顯示裝置選項",
"dd_refresh_rate_option": "更新率",
"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_disabled": "不更改解析度",
"dd_resolution_option_manual": "使用手動輸入的解析度",
"dd_resolution_option_manual_desc": "請輸入要使用的解析度",
"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 模擬時,將返回/選擇按鈕映射為觸控板點擊",
"encoder": "強制指定編碼器",
"encoder_desc": "強制指定特定的編碼器,否則 Sunshine 將選擇最佳的可用選項。注意:如果您在 Windows 上指定硬體編碼器,則必須與顯示器連接的 GPU 符合。",
"encoder_software": "軟體",
"external_ip": "外部 IP",
"external_ip_desc": "如果未提供外部 IP 位址Sunshine 會自動偵測外部 IP 位址。",
"fec_percentage": "FEC 比例",
"fec_percentage_desc": "每個視訊影格中每個資料封包的錯誤修正封包百分比。較高的值可以修正更多的網路封包遺失,但代價是增加頻寬使用量。",
"ffmpeg_auto": "auto—由 ffmpeg 決定(預設)",
"file_apps": "應用程式檔案",
"file_apps_desc": "Sunshine 目前的應用程式所儲存的檔案。",
"file_state": "狀態檔案",
"file_state_desc": "儲存目前 Sunshine 狀態的檔案",
"gamepad": "模擬遊戲手把類型",
"gamepad_auto": "自動選擇選項",
"gamepad_desc": "選擇要在主機上模擬的遊戲手把類型",
"gamepad_ds4": "DS4 (PS4)",
"gamepad_ds4_manual": "DS4 選擇選項",
"gamepad_ds5": "DS5 (PS5)",
"gamepad_switch": "Nintendo Pro (Switch)",
"gamepad_manual": "手動 DS4 選項",
"gamepad_x360": "X360 (Xbox 360)",
"gamepad_xone": "XOne (Xbox One)",
"global_prep_cmd": "指令準備",
"global_prep_cmd_desc": "設定在執行任何應用程式之前或之後執行的指令清單。如果任何指定的準備指令失敗,應用程式的啟動程序將會中止。",
"hevc_mode": "HEVC 支援",
"hevc_mode_0": "Sunshine 將根據編碼器的能力宣告是否支援 HEVC建議",
"hevc_mode_1": "Sunshine 不會宣告支援 HEVC",
"hevc_mode_2": "Sunshine 將宣告支援 HEVC Main 設定檔",
"hevc_mode_3": "Sunshine 會宣告支援 HEVC Main 和 Main10 (HDR) 設定檔",
"hevc_mode_desc": "允許用戶端要求 HEVC Main 或 HEVC Main10 視訊串流。HEVC 的編碼較耗費 CPU因此使用軟體編碼時啟用此功能可能會降低效能。",
"high_resolution_scrolling": "支援高解析度捲動",
"high_resolution_scrolling_desc": "啟用後Sunshine 會傳遞來自 Moonlight 用戶端的高解析度捲動事件。對於某些在高解析度捲動時捲動速度過快的舊應用程式,建議將此選項停用。",
"install_steam_audio_drivers": "安裝 Steam 音訊驅動程式",
"install_steam_audio_drivers_desc": "如果已安裝 Steam這將會自動安裝 Steam Streaming Speakers 驅動程式,以支援 5.1/7.1 環繞音效和主機音訊靜音。",
"key_repeat_delay": "按鍵重複延遲",
"key_repeat_delay_desc": "控制按鍵重複的速度。設定按鍵重複前的初始延遲時間,以毫秒為單位。",
"key_repeat_frequency": "按鍵重複頻率",
"key_repeat_frequency_desc": "每秒按鍵重複的頻率。此選項支援小數點。",
"key_rightalt_to_key_win": "將右 Alt 鍵映射為 Windows 鍵",
"key_rightalt_to_key_win_desc": "Moonlight 可能無法直接發送 Windows 鍵。在這種情況下,讓 Sunshine 認為右 Alt 鍵是 Windows 鍵可能會很有用",
"keyboard": "啟用鍵盤輸入",
"keyboard_desc": "允許訪客使用鍵盤控制主機系統",
"lan_encryption_mode": "區域網路加密模式",
"lan_encryption_mode_1": "當用戶端支援時啟用",
"lan_encryption_mode_2": "所有用戶端都需要",
"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 線程數",
"min_threads_desc": "增加該值會稍微降低編碼效率,但為了能使用更多 CPU 核心進行編碼,這樣的折衷通常是值得的。理想的值是在您的硬體上,能以您所需的串流設定進行可靠編碼的最低值。",
"misc": "其他選項",
"motion_as_ds4": "如果客戶端的遊戲手把報告有運動感應器,則會模擬 DS4 遊戲手把",
"motion_as_ds4_desc": "如果禁用,則在選擇遊戲手把類型時不會考慮運動感應器的存在。",
"mouse": "啟用滑鼠輸入",
"mouse_desc": "允許訪客使用滑鼠控制主機系統",
"native_pen_touch": "原生筆/觸控支援",
"native_pen_touch_desc": "啟用後Sunshine 將從 Moonlight 用戶端傳遞本機筆觸事件。對於沒有原生筆/觸控支援的舊版應用程式來說,停用此功能可能很有用。",
"notify_pre_releases": "發佈前通知",
"notify_pre_releases_desc": "是否接收 Sunshine 的新預發佈版本通知",
"nvenc_h264_cavlc": "在 H.264 中選擇 CAVLC 而非 CABAC",
"nvenc_h264_cavlc_desc": "較簡單的熵編碼形式。CAVLC需要約多10%的位元率才能達到相同的畫質。這僅對非常舊的解碼裝置有影響。",
"nvenc_latency_over_power": "相較於省電,更傾向於較低的編碼延遲",
"nvenc_latency_over_power_desc": "Sunshine會在進行串流時請求最大 GPU 時脈速度,以減少編碼延遲。建議不要停用此選項,因為這樣可能會顯著增加編碼延遲。",
"nvenc_opengl_vulkan_on_dxgi": "在 DXGI 之上呈現 OpenGL/Vulkan",
"nvenc_opengl_vulkan_on_dxgi_desc": "Sunshine 無法以完整幀率捕捉全螢幕的 OpenGL 和 Vulkan 程式,除非它們顯示在 DXGI 之上。這是系統範圍的設定會在Sunshine程式退出時還原。",
"nvenc_preset": "性能預設參數",
"nvenc_preset_1": "(最快,預設值)",
"nvenc_preset_7": "(最慢)",
"nvenc_preset_desc": "數值越高,在增加編碼延遲的情況下,可提高壓縮率(在指定位元率下的品質)。建議僅在受限於網路或解碼器時才進行調整,否則可以透過增加位元率來達成類似的效果。",
"nvenc_realtime_hags": "在硬體加速 GPU 排程中使用即時優先順序",
"nvenc_realtime_hags_desc": "目前NVIDIA 驅動程式在啟用 HAGS、使用即時優先權且 VRAM 使用率接近最大值時,可能會在編碼器中凍結。停用此選項會將優先權降低至高,藉此避開凍結,但在 GPU 重度負載時擷取效能會降低。",
"nvenc_spatial_aq": "Spatial AQ",
"nvenc_spatial_aq_desc": "為視訊的平坦區域指定較高的 QP 值。建議在以較低位元率串流時啟用。",
"nvenc_twopass": "兩次編碼模式",
"nvenc_twopass_desc": "新增初步編碼過程,能夠檢測更多的運動向量,將位元率更均勻地分佈於畫面,並更精確地遵守位元率限制。建議不要停用這個功能,因為停用後可能會偶爾出現位元率超過限制,並導致資料包丟失。",
"nvenc_twopass_disabled": "停用(最快,不建議使用)",
"nvenc_twopass_full_res": "全解析度(較慢)",
"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",
"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 私鑰。",
"port": "連接埠",
"port_alert_1": "Sunshine 不能使用低於 1024 的連接埠!",
"port_alert_2": "65535 以上的連接埠無法使用!",
"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",
"qp": "量化參數",
"qp_desc": "某些裝置可能不支援 Constant Bit Rate。對於這些裝置會使用 QP 來取代。值越高表示壓縮越多,但品質越低。",
"qsv_coder": "QuickSync 編碼器H264",
"qsv_preset": "QuickSync 預設值",
"qsv_preset_fast": "快(低畫質)",
"qsv_preset_faster": "更快(品質較低)",
"qsv_preset_medium": "中(預設值)",
"qsv_preset_slow": "慢(高畫質)",
"qsv_preset_slower": "速度較慢(品質較佳)",
"qsv_preset_slowest": "最慢(最高畫質)",
"qsv_preset_veryfast": "最快(最低畫質)",
"qsv_slow_hevc": "允許慢速 HEVC 編碼",
"qsv_slow_hevc_desc": "這樣可以在較舊的 Intel GPU 上進行 HEVC 編碼,但代價是 GPU 使用量較高,效能較差。",
"restart_note": "Sunshine 正在重新啟動以套用變更。",
"stream_audio": "串流音訊",
"stream_audio_desc": "是否串流音訊。停用此功能對於串流無頭顯示器作為第二台顯示器很有用。",
"sunshine_name": "Sunshine 名稱",
"sunshine_name_desc": "Moonlight 顯示的名稱。如果未指定,則使用 PC 的主機名稱。",
"sw_preset": "SW 預設值",
"sw_preset_desc": "最佳化編碼速度(每秒編碼幀數)與壓縮效率(位元流中每位元的品質)之間的權衡。預設為 superfast。",
"sw_preset_fast": "快速",
"sw_preset_faster": "更快",
"sw_preset_medium": "中等",
"sw_preset_slow": "慢速",
"sw_preset_slower": "更慢",
"sw_preset_superfast": "超快(預設)",
"sw_preset_ultrafast": "超快",
"sw_preset_veryfast": "非常快",
"sw_preset_veryslow": "非常慢速",
"sw_tune": "SW 調音",
"sw_tune_animation": "animation—適用於卡通使用較多的去區塊效應處理和更多的參考影格。",
"sw_tune_desc": "調整選項,在預設值之後套用。預設為 zerolatency。",
"sw_tune_fastdecode": "fastdecode—藉由停用某些過濾器來加快解碼速度",
"sw_tune_film": "film—適用於高品質的電影內容降低去區塊效應處理。",
"sw_tune_grain": "grain—保留老電影畫面的顆粒結構",
"sw_tune_stillimage": "stillimage—適用於類似投影片的內容。",
"sw_tune_zerolatency": "zerolatency—適合快速編碼和低延遲串流預設值",
"touchpad_as_ds4": "當用戶端的遊戲手把報告存在觸控板時,模擬 DS4 遊戲手把",
"touchpad_as_ds4_desc": "若停用,選擇遊戲手把類型時將不會考慮觸控板的存在。",
"upnp": "UPnP",
"upnp_desc": "自動設定透過網際網路串流的連接埠轉發",
"vaapi_strict_rc_buffer": "在 AMD GPU 上嚴格執行 H.264/HEVC 的幀位元率限制",
"vaapi_strict_rc_buffer_desc": "啟用此選項可以避免場景變換時在網路上發生畫面掉幀,但可能會降低畫面移動時的影像品質。",
"virtual_sink": "虛擬音訊輸出",
"virtual_sink_desc": "手動指定要使用的虛擬音訊裝置。如果未設定,則會自動選擇裝置。我們強烈建議將此欄位留空,以使用自動裝置選擇!",
"virtual_sink_placeholder": "Steam 串流喇叭",
"vt_coder": "VideoToolbox 編碼器",
"vt_realtime": "VideoToolbox 即時編碼",
"vt_software": "VideoToolbox 軟體編碼",
"vt_software_allowed": "允許",
"vt_software_forced": "強制",
"wan_encryption_mode": "WAN 加密模式",
"wan_encryption_mode_1": "對支援的用戶端啟用(預設)",
"wan_encryption_mode_2": "所有客戶都需要",
"wan_encryption_mode_desc": "這會決定在網際網路上串流時,何時會使用加密。加密可能會降低串流效能,尤其是在效能較低的主機和用戶端上。"
},
"index": {
"description": "Sunshine 是 Moonlight 的自架遊戲串流主機。",
"download": "下載",
"installed_version_not_stable": "您正在運行的是 Sunshine 的預發行版本。您可能會遇到錯誤或其他問題。請報告您遇到的任何問題。感謝您的協助,讓 Sunshine 成為更好的軟體!",
"loading_latest": "正在載入最新版本…",
"new_pre_release": "提供新的預發行版本!",
"new_stable": "有新的穩定版本可用!",
"startup_errors": "<b>注意!</b> Sunshine 在啟動時檢測到這些錯誤。我們<b>強烈建議</b>在開始串流之前修復它們。",
"version_dirty": "感謝您的協助,讓 Sunshine 成為更好的軟體!",
"version_latest": "您正在執行最新版本的 Sunshine",
"welcome": "你好Sunshine"
},
"navbar": {
"applications": "應用程式",
"configuration": "組態",
"home": "首頁",
"password": "變更密碼",
"pin": "Pin 碼",
"theme_auto": "自動",
"theme_dark": "深色主題",
"theme_light": "淺色主題",
"toggle_theme": "主題",
"troubleshoot": "疑難排解"
},
"password": {
"confirm_password": "確認密碼",
"current_creds": "目前的憑證",
"new_creds": "新憑證",
"new_username_desc": "如果未指定,使用者名稱不會變更",
"password_change": "密碼變更",
"success_msg": "密碼已成功變更!此頁面將很快重新載入,您的瀏覽器將要求輸入新的憑證。"
},
"pin": {
"device_name": "裝置名稱",
"pair_failure": "配對失敗:請確認 PIN 碼是否輸入正確",
"pair_success": "成功!請檢查 Moonlight 來繼續",
"pin_pairing": "PIN 碼配對",
"send": "發送",
"warning_msg": "請確保您可以存取要配對的用戶端。此軟體可讓對方完全控制您的電腦,請小心使用!"
},
"resource_card": {
"github_discussions": "GitHub 討論區",
"legal": "法律資訊",
"legal_desc": "繼續使用本軟體即表示您同意下列文件中的條款和條件。",
"license": "許可證",
"lizardbyte_website": "LizardByte 網站",
"resources": "資源",
"resources_desc": "Sunshine 相關資源!",
"third_party_notice": "第三方通知"
},
"troubleshooting": {
"dd_reset": "重置為既存的顯示裝置設定",
"dd_reset_desc": "如果 Sunshine 在嘗試恢復更改過的顯示裝置設定時卡住,您可以重置設定並手動恢復顯示狀態。",
"dd_reset_error": "重置為既存設定時發生錯誤!",
"dd_reset_success": "成功重置為既存設定!",
"force_close": "強制關閉",
"force_close_desc": "如果 Moonlight 抱怨目前的應用程式已經在執行,強制關閉該應用程式應該可以解決問題。",
"force_close_error": "關閉應用程式時發生錯誤",
"force_close_success": "應用程式已成功結束!",
"logs": "日誌",
"logs_desc": "查看 Sunshine 上傳的日誌",
"logs_find": "尋找…",
"restart_sunshine": "重新啟動 Sunshine",
"restart_sunshine_desc": "如果 Sunshine 沒有正常運作,您可以嘗試重新啟動。這會終止所有正在執行的工作階段。",
"restart_sunshine_success": "Sunshine 正在重新啟動",
"troubleshooting": "疑難排解",
"unpair_all": "全部解除配對",
"unpair_all_error": "解除配對時發生錯誤",
"unpair_all_success": "已取消配對所有裝置。",
"unpair_desc": "刪除已配對的裝置。未配對的裝置若有使用中的工作階段,將保持連線,但無法啟動或恢復工作階段。",
"unpair_single_no_devices": "沒有已配對的裝置。",
"unpair_single_success": "然而,該裝置仍可能處於使用中的工作階段。請使用上方的「強制關閉」按鈕結束任何使用中的工作階段。",
"unpair_single_unknown": "未知的用戶端",
"unpair_title": "解除配對裝置"
},
"welcome": {
"confirm_password": "確認密碼",
"create_creds": "在開始之前,我們需要您建立新的使用者名稱和密碼,以便存取網頁 UI。",
"create_creds_alert": "以下的憑證是存取 Sunshine 網頁介面所需的。請妥善保管,因為您將無法再查看這些憑證!",
"greeting": "歡迎來到 Sunshine",
"login": "登入",
"welcome_success": "此頁面將很快重新載入,您的瀏覽器會要求您提供新的憑證"
}
}

View File

@@ -182,7 +182,9 @@
},
methods: {
refreshLogs() {
fetch("./api/logs", { credentials: 'include' })
fetch("./api/logs", {
credentials: 'include'
})
.then(response => {
// Retrieve the Content-Type header
const contentType = response.headers.get("Content-Type") || "";
@@ -205,7 +207,10 @@
this.closeAppPressed = true;
fetch("./api/apps/close", {
credentials: 'include',
method: 'POST'
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then((r) => r.json())
.then((r) => {
@@ -227,6 +232,9 @@
fetch("./api/restart", {
credentials: 'include',
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then((resp) => {
if (resp.status !== 200) {
@@ -251,6 +259,9 @@
fetch("./api/quit", {
credentials: 'include',
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(() => {
this.serverQuitting = false;
@@ -267,7 +278,10 @@
this.ddResetPressed = true;
fetch("/api/reset-display-device-persistence", {
credentials: 'include',
method: 'POST'
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then((r) => r.json())
.then((r) => {

View File

@@ -11,7 +11,7 @@
<div class="card p-2">
<header>
<h1 class="mb-0">
<img src="./images/logo-apollo-45.png" height="45" alt="">
<img src="./images/logo-sunshine-45.png" height="45" alt="">
{{ $t('welcome.greeting') }}
</h1>
</header>
@@ -66,7 +66,7 @@
success: false,
loading: false,
passwordData: {
newUsername: "apollo",
newUsername: "sunshine",
newPassword: "",
confirmNewPassword: "",
},
@@ -74,17 +74,13 @@
},
methods: {
save() {
if (this.passwordData.newPassword !== this.passwordData.confirmNewPassword) {
this.error = "Password mismatch";
return;
};
this.error = null;
this.loading = true;
fetch("./api/password", {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify(this.passwordData),
}).then((r) => {
this.loading = false;

View File

@@ -15,6 +15,7 @@ target_compile_options(dxgi-info PRIVATE ${SUNSHINE_COMPILE_OPTIONS})
add_executable(audio-info audio.cpp utils.cpp)
set_target_properties(audio-info PROPERTIES CXX_STANDARD 20)
target_link_libraries(audio-info
${Boost_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
ksuser
${PLATFORM_LIBRARIES})

View File

@@ -6,6 +6,7 @@
#include "src/utility.h"
#include "utils.h"
// platform includes
#include <audioclient.h>
#include <codecvt>
#include <iostream>
@@ -14,7 +15,6 @@
#include <roapi.h>
#include <synchapi.h>
#include <io.h>
DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING