mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-08-10 00:52:16 +00:00
Compare commits
110 Commits
v2025.118.
...
ci/homebre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cf5e8a51c | ||
|
|
2acc57e8d6 | ||
|
|
3d731a21e6 | ||
|
|
e9b39e4c15 | ||
|
|
e81a3f029f | ||
|
|
c435d0fe56 | ||
|
|
a846ea909a | ||
|
|
0a942437e2 | ||
|
|
808a862952 | ||
|
|
a70cf5e12e | ||
|
|
8ef838bada | ||
|
|
7b6818ab4a | ||
|
|
3de3c299b2 | ||
|
|
e9bce254fd | ||
|
|
e807769f6a | ||
|
|
bdf15f5277 | ||
|
|
326592d08c | ||
|
|
f82923e5c5 | ||
|
|
c6f36474ba | ||
|
|
1df4c89026 | ||
|
|
65b0217a90 | ||
|
|
e58a437e6b | ||
|
|
f76ad0d238 | ||
|
|
4f2603ff63 | ||
|
|
5529850c10 | ||
|
|
57b5b296da | ||
|
|
b43a9b8efb | ||
|
|
86d7663927 | ||
|
|
64c3862248 | ||
|
|
955c0fd8e7 | ||
|
|
4f3d50a2ba | ||
|
|
39916a7516 | ||
|
|
d2fbe1c7b4 | ||
|
|
498e9cfa61 | ||
|
|
6c1407847f | ||
|
|
ccce4ed6f4 | ||
|
|
7d75e4b75f | ||
|
|
3e41770d44 | ||
|
|
fc9b548edd | ||
|
|
7ca2721739 | ||
|
|
5f9fe26df3 | ||
|
|
f921ae45d4 | ||
|
|
f65bb842ff | ||
|
|
c4d991c8f7 | ||
|
|
f0d563ecbb | ||
|
|
2cd4b1b3ad | ||
|
|
937615c8e6 | ||
|
|
e6fbd24001 | ||
|
|
5da5293471 | ||
|
|
873939a7b2 | ||
|
|
7c8113ded8 | ||
|
|
310f5b8c1c | ||
|
|
0262a77c9a | ||
|
|
643fdee8d3 | ||
|
|
fe5812b008 | ||
|
|
791580954f | ||
|
|
6efd41d3f7 | ||
|
|
1fd5534541 | ||
|
|
a344e6cc69 | ||
|
|
ceea97479b | ||
|
|
bd359b21d6 | ||
|
|
67d649621c | ||
|
|
848558d80e | ||
|
|
7cda5fa925 | ||
|
|
fd9f10f730 | ||
|
|
a594b6434b | ||
|
|
f7190f53ff | ||
|
|
e3079da132 | ||
|
|
385471fb0a | ||
|
|
3c6374fa7a | ||
|
|
ecd0f9b5dc | ||
|
|
fa22227fca | ||
|
|
9aaa40c3ca | ||
|
|
849ad1c176 | ||
|
|
782b3827d7 | ||
|
|
e45b0b95fe | ||
|
|
ffccc1af9b | ||
|
|
41d9a58560 | ||
|
|
4f62944a7c | ||
|
|
3a88ddc639 | ||
|
|
ff0ed25e47 | ||
|
|
ac2fc48288 | ||
|
|
d777b72421 | ||
|
|
f99d3af0a1 | ||
|
|
265a00793c | ||
|
|
dbba364ed7 | ||
|
|
997093986d | ||
|
|
6efc687036 | ||
|
|
a995e578fa | ||
|
|
257a102127 | ||
|
|
5b36357133 | ||
|
|
ce28e36a47 | ||
|
|
23e131439f | ||
|
|
24cce3a666 | ||
|
|
0631472533 | ||
|
|
a5c791658e | ||
|
|
a513acc16b | ||
|
|
64544e7960 | ||
|
|
2a31ee5422 | ||
|
|
8263d8976f | ||
|
|
eb6916ef34 | ||
|
|
5af21bde88 | ||
|
|
31866fde35 | ||
|
|
299b12347f | ||
|
|
2ac87fdc36 | ||
|
|
a88c01f3b8 | ||
|
|
f5b923c406 | ||
|
|
3048e6fe20 | ||
|
|
c2420427b1 | ||
|
|
f57aee9025 |
@@ -6,27 +6,34 @@
|
||||
# Generated from CLion C/C++ Code Style settings
|
||||
BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -2
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
AlignConsecutiveAssignments: None
|
||||
AlignEscapedNewlines: DontAlign
|
||||
AlignOperands: Align
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowAllConstructorInitializersOnNextLine: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: Always
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: None
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlignTrailingComments: false
|
||||
AlwaysBreakAfterReturnType: All
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: MultiLine
|
||||
BreakBeforeBraces: Custom
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BracedInitializerIndentWidth: 2
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterExternBlock: true
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
@@ -36,39 +43,75 @@ BraceWrapping:
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: true
|
||||
BreakArrays: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeTernaryOperators: false
|
||||
BreakConstructorInitializers: AfterColon
|
||||
BreakInheritanceList: AfterColon
|
||||
ColumnLimit: 0
|
||||
CompactNamespaces: false
|
||||
ContinuationIndentWidth: 2
|
||||
Cpp11BracedListStyle: true
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: Always
|
||||
ExperimentalAutoDetectBinPacking: true
|
||||
FixNamespaceComments: true
|
||||
IncludeBlocks: Regroup
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: true
|
||||
IndentCaseLabels: true
|
||||
IndentExternBlock: Indent
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: BeforeHash
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: true
|
||||
InsertBraces: true
|
||||
InsertNewlineAtEOF: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
LineEnding: LF
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: All
|
||||
ObjCBinPackProtocolList: Never
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PackConstructorInitializers: Never
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 1
|
||||
PenaltyBreakString: 1
|
||||
PenaltyBreakFirstLessLess: 0
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 100000000
|
||||
PointerAlignment: Right
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: true
|
||||
RemoveBracesLLVM: false
|
||||
RemoveSemicolon: false
|
||||
SeparateDefinitionBlocks: Always
|
||||
SortIncludes: CaseInsensitive
|
||||
SortUsingDeclarations: Lexicographic
|
||||
SpaceAfterCStyleCast: true
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: true
|
||||
SpaceBeforeCtorInitializerColon: false
|
||||
SpaceBeforeInheritanceColon: false
|
||||
SpaceBeforeJsonColon: false
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: Never
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInLineCommentPrefix:
|
||||
Maximum: 3
|
||||
Minimum: 1
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
TabWidth: 2
|
||||
Cpp11BracedListStyle: false
|
||||
UseTab: Never
|
||||
|
||||
@@ -4,10 +4,31 @@ 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-boost"
|
||||
"mingw-w64-ucrt-x86_64-cmake"
|
||||
"mingw-w64-ucrt-x86_64-cppwinrt"
|
||||
"mingw-w64-ucrt-x86_64-curl-winssl"
|
||||
@@ -21,7 +42,8 @@ dependencies=(
|
||||
"mingw-w64-ucrt-x86_64-opus"
|
||||
"mingw-w64-ucrt-x86_64-toolchain"
|
||||
)
|
||||
pacman -S --noconfirm "${dependencies[@]}"
|
||||
|
||||
pacman -Syu --noconfirm --ignore="$(IFS=,; echo "${broken_deps[*]}")" "${dependencies[@]}"
|
||||
|
||||
# build
|
||||
mkdir -p build
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
# 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=(
|
||||
"boost"
|
||||
"cmake"
|
||||
"miniupnpc"
|
||||
"ninja"
|
||||
|
||||
12
.github/ISSUE_TEMPLATE/config.yml
vendored
12
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -5,9 +5,15 @@
|
||||
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Discussions
|
||||
url: https://github.com/orgs/LizardByte/discussions
|
||||
about: Community discussions
|
||||
- name: Questions
|
||||
url: https://github.com/orgs/LizardByte/discussions
|
||||
about: Ask questions
|
||||
- name: Feature Requests
|
||||
url: https://github.com/orgs/LizardByte/discussions
|
||||
about: Request new features
|
||||
- name: Support Center
|
||||
url: https://app.lizardbyte.dev/support
|
||||
about: Official LizardByte support
|
||||
- name: Discussions
|
||||
url: https://github.com/orgs/LizardByte/discussions
|
||||
about: Community discussions, questions, and feature requests
|
||||
|
||||
378
.github/workflows/CI.yml
vendored
378
.github/workflows/CI.yml
vendored
@@ -1,12 +1,19 @@
|
||||
---
|
||||
name: CI
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- master
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
push:
|
||||
branches: [master]
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
@@ -17,7 +24,6 @@ jobs:
|
||||
github_env:
|
||||
name: GitHub Env Debug
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Dump github context
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
@@ -34,6 +40,8 @@ jobs:
|
||||
release_generate_release_notes: ${{ steps.setup_release.outputs.release_generate_release_notes }}
|
||||
release_tag: ${{ steps.setup_release.outputs.release_tag }}
|
||||
release_version: ${{ steps.setup_release.outputs.release_version }}
|
||||
permissions:
|
||||
contents: write # read does not work to check squash and merge details
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -41,50 +49,29 @@ jobs:
|
||||
|
||||
- name: Setup Release
|
||||
id: setup_release
|
||||
uses: LizardByte/setup-release-action@v2025.102.14715
|
||||
uses: LizardByte/setup-release-action@v2025.426.225
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
setup_flatpak_matrix:
|
||||
name: Setup Flatpak Matrix
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set release details
|
||||
id: flatpak_matrix
|
||||
# https://www.cynkra.com/blog/2020-12-23-dynamic-gha
|
||||
run: |
|
||||
# determine which architectures to build
|
||||
if [[ "${{ github.event_name }}" == "push" ]]; then
|
||||
matrix=$((
|
||||
echo '{ "arch" : ["x86_64", "aarch64"] }'
|
||||
) | jq -c .)
|
||||
else
|
||||
matrix=$((
|
||||
echo '{ "arch" : ["x86_64"] }'
|
||||
) | jq -c .)
|
||||
fi
|
||||
|
||||
echo $matrix
|
||||
echo $matrix | jq .
|
||||
echo "matrix=$matrix" >> $GITHUB_OUTPUT
|
||||
|
||||
outputs:
|
||||
matrix: ${{ steps.flatpak_matrix.outputs.matrix }}
|
||||
|
||||
build_linux_flatpak:
|
||||
name: Linux Flatpak
|
||||
env:
|
||||
APP_ID: dev.lizardbyte.app.Sunshine
|
||||
NODE_VERSION: "20"
|
||||
PLATFORM_VERSION: "23.08"
|
||||
name: Linux Flatpak
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [setup_release, setup_flatpak_matrix]
|
||||
needs: setup_release
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false # false to test all, true to fail entire job if any fail
|
||||
matrix: ${{fromJson(needs.setup_flatpak_matrix.outputs.matrix)}}
|
||||
|
||||
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
|
||||
@@ -106,12 +93,10 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: |
|
||||
npm install --package-lock-only
|
||||
run: npm install --package-lock-only
|
||||
|
||||
- name: Debug package-lock.json
|
||||
run: |
|
||||
cat package-lock.json
|
||||
run: cat package-lock.json
|
||||
|
||||
- name: Setup python
|
||||
id: python
|
||||
@@ -126,8 +111,7 @@ jobs:
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y \
|
||||
cmake \
|
||||
flatpak \
|
||||
qemu-user-static
|
||||
flatpak
|
||||
|
||||
sudo su $(whoami) -c "flatpak --user remote-add --if-not-exists flathub \
|
||||
https://flathub.org/repo/flathub.flatpakrepo"
|
||||
@@ -143,12 +127,10 @@ jobs:
|
||||
|
||||
- name: flatpak node generator
|
||||
# https://github.com/flatpak/flatpak-builder-tools/blob/master/node/README.md
|
||||
run: |
|
||||
flatpak-node-generator npm package-lock.json
|
||||
run: flatpak-node-generator npm package-lock.json
|
||||
|
||||
- name: Debug generated-sources.json
|
||||
run: |
|
||||
cat generated-sources.json
|
||||
run: cat generated-sources.json
|
||||
|
||||
- name: Cache Flatpak build
|
||||
uses: actions/cache@v4
|
||||
@@ -193,8 +175,7 @@ jobs:
|
||||
|
||||
- name: Debug Manifest
|
||||
working-directory: build
|
||||
run: |
|
||||
cat ${APP_ID}.yml
|
||||
run: cat ${APP_ID}.yml
|
||||
|
||||
- name: Build Linux Flatpak
|
||||
working-directory: build
|
||||
@@ -227,56 +208,27 @@ jobs:
|
||||
- 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 \
|
||||
manifest ${APP_ID}.yml > _flatpak-lint-exceptions_manifest.json || true
|
||||
--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 \
|
||||
repo repo > _flatpak-lint-exceptions_repo.json || true
|
||||
|
||||
checks=(manifest repo)
|
||||
exit_code=0
|
||||
|
||||
# check if files are equal
|
||||
for check in "${checks[@]}"; do
|
||||
echo "Validating $check"
|
||||
|
||||
# load baseline and result files
|
||||
baseline="${{ github.workspace }}/packaging/linux/flatpak/flatpak-lint-baseline_${check}.json"
|
||||
result="_flatpak-lint-exceptions_${check}.json"
|
||||
|
||||
# Extract errors from both JSON files
|
||||
readarray -t result_errors < <(jq -r '.errors[]' "$result")
|
||||
readarray -t baseline_errors < <(jq -r '.errors[]' "$baseline")
|
||||
|
||||
# Loop through result errors and check against baseline errors
|
||||
for error in "${result_errors[@]}"; do
|
||||
if printf '%s\n' "${baseline_errors[@]}" | grep -q -F "$error"; then
|
||||
echo "::warning:: '$error'"
|
||||
else
|
||||
echo "::error:: '$error'"
|
||||
exit_code=1
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# if exit code is not 0, print results
|
||||
if [ $exit_code -ne 0 ]; then
|
||||
echo "Manifest lint results:"
|
||||
cat _flatpak-lint-exceptions_manifest.json
|
||||
echo "Repo lint results:"
|
||||
cat _flatpak-lint-exceptions_repo.json
|
||||
fi
|
||||
|
||||
# exit with the correct code
|
||||
exit $exit_code
|
||||
--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' }}
|
||||
if: matrix.arch == 'x86_64'
|
||||
run: |
|
||||
mkdir -p flathub/modules
|
||||
cp ./build/generated-sources.json ./flathub/
|
||||
@@ -296,10 +248,11 @@ jobs:
|
||||
with:
|
||||
name: sunshine-linux-flatpak-${{ matrix.arch }}
|
||||
path: artifacts/
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Create/Update GitHub Release
|
||||
if: ${{ needs.setup_release.outputs.publish_release == 'true' }}
|
||||
uses: LizardByte/create-release-action@v2025.102.13208
|
||||
if: needs.setup_release.outputs.publish_release == 'true'
|
||||
uses: LizardByte/create-release-action@v2025.426.1549
|
||||
with:
|
||||
allowUpdates: true
|
||||
body: ${{ needs.setup_release.outputs.release_body }}
|
||||
@@ -312,7 +265,7 @@ jobs:
|
||||
build_linux:
|
||||
name: Linux ${{ matrix.type }}
|
||||
runs-on: ubuntu-${{ matrix.dist }}
|
||||
needs: [setup_release]
|
||||
needs: setup_release
|
||||
strategy:
|
||||
fail-fast: false # false to test all, true to fail entire job if any fail
|
||||
matrix:
|
||||
@@ -320,7 +273,6 @@ jobs:
|
||||
- type: AppImage
|
||||
EXTRA_ARGS: '--appimage-build'
|
||||
dist: 22.04
|
||||
|
||||
steps:
|
||||
- name: Maximize build space
|
||||
uses: easimon/maximize-build-space@v10
|
||||
@@ -395,14 +347,13 @@ jobs:
|
||||
--ubuntu-test-repo ${{ matrix.EXTRA_ARGS }}
|
||||
|
||||
- name: Set AppImage Version
|
||||
if: |
|
||||
matrix.type == 'AppImage'
|
||||
if: matrix.type == 'AppImage'
|
||||
run: |
|
||||
version=${{ needs.setup_release.outputs.release_tag }}
|
||||
echo "VERSION=${version}" >> $GITHUB_ENV
|
||||
|
||||
- name: Package Linux - AppImage
|
||||
if: ${{ matrix.type == 'AppImage' }}
|
||||
if: matrix.type == 'AppImage'
|
||||
working-directory: build
|
||||
run: |
|
||||
# install sunshine to the DESTDIR
|
||||
@@ -447,7 +398,7 @@ jobs:
|
||||
rm -rf ./build/cuda
|
||||
|
||||
- name: Verify AppImage
|
||||
if: ${{ matrix.type == 'AppImage' }}
|
||||
if: matrix.type == 'AppImage'
|
||||
run: |
|
||||
wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage
|
||||
chmod +x appimagelint-x86_64.AppImage
|
||||
@@ -459,6 +410,7 @@ jobs:
|
||||
with:
|
||||
name: sunshine-linux-${{ matrix.type }}-${{ matrix.dist }}
|
||||
path: artifacts/
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Install test deps
|
||||
run: |
|
||||
@@ -479,12 +431,14 @@ jobs:
|
||||
Xvfb ${DISPLAY} -screen 0 1024x768x24 &
|
||||
sleep 5 # give Xvfb time to start
|
||||
|
||||
./test_sunshine --gtest_color=yes
|
||||
./test_sunshine --gtest_color=yes --gtest_output=xml:test_results.xml
|
||||
|
||||
- name: Generate gcov report
|
||||
# any except canceled or skipped
|
||||
if: always() && (steps.test.outcome == 'success' || steps.test.outcome == 'failure')
|
||||
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
|
||||
@@ -496,6 +450,22 @@ jobs:
|
||||
--xml-pretty \
|
||||
-o coverage.xml
|
||||
|
||||
- name: Upload test results to Codecov
|
||||
# any except canceled or skipped
|
||||
if: >-
|
||||
always() &&
|
||||
(steps.test.outcome == 'success' || steps.test.outcome == 'failure') &&
|
||||
startsWith(github.repository, 'LizardByte/')
|
||||
uses: codecov/test-results-action@v1
|
||||
with:
|
||||
disable_search: true
|
||||
fail_ci_if_error: true
|
||||
files: ./build/tests/test_results.xml
|
||||
flags: ${{ runner.os }}
|
||||
handle_no_reports_found: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
|
||||
- name: Upload coverage
|
||||
# any except canceled or skipped
|
||||
if: >-
|
||||
@@ -512,8 +482,8 @@ jobs:
|
||||
verbose: true
|
||||
|
||||
- name: Create/Update GitHub Release
|
||||
if: ${{ needs.setup_release.outputs.publish_release == 'true' }}
|
||||
uses: LizardByte/create-release-action@v2025.102.13208
|
||||
if: needs.setup_release.outputs.publish_release == 'true'
|
||||
uses: LizardByte/create-release-action@v2025.426.1549
|
||||
with:
|
||||
allowUpdates: true
|
||||
body: ${{ needs.setup_release.outputs.release_body }}
|
||||
@@ -524,7 +494,8 @@ jobs:
|
||||
token: ${{ secrets.GH_BOT_TOKEN }}
|
||||
|
||||
build_homebrew:
|
||||
needs: [setup_release]
|
||||
name: Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }})
|
||||
needs: setup_release
|
||||
strategy:
|
||||
fail-fast: false # false to test all, true to fail entire job if any fail
|
||||
matrix:
|
||||
@@ -540,9 +511,7 @@ jobs:
|
||||
- os_version: "latest" # this job will only configure the formula for release, no validation
|
||||
os_name: "ubuntu"
|
||||
release: true
|
||||
name: Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }})
|
||||
runs-on: ${{ matrix.os_name }}-${{ matrix.os_version }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -554,20 +523,18 @@ jobs:
|
||||
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/pydoc3'
|
||||
rm '/usr/local/bin/pydoc3.12'
|
||||
rm '/usr/local/bin/pydoc3.13'
|
||||
rm '/usr/local/bin/python3'
|
||||
rm '/usr/local/bin/python3-config'
|
||||
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 python
|
||||
|
||||
- name: Setup python
|
||||
id: python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Configure formula
|
||||
run: |
|
||||
# variables for formula
|
||||
@@ -626,16 +593,15 @@ jobs:
|
||||
cat ./homebrew/sunshine.rb
|
||||
|
||||
- name: Upload Artifacts
|
||||
if: ${{ matrix.release }}
|
||||
if: matrix.release
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sunshine-homebrew
|
||||
path: homebrew/
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Setup Xvfb
|
||||
if: |
|
||||
matrix.release != true &&
|
||||
runner.os == 'Linux'
|
||||
if: matrix.release != true && runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y \
|
||||
@@ -648,9 +614,8 @@ jobs:
|
||||
|
||||
- name: Validate Homebrew Formula
|
||||
id: test
|
||||
if: |
|
||||
matrix.release != true
|
||||
uses: LizardByte/homebrew-release-action@v2024.1115.14934
|
||||
if: matrix.release != true
|
||||
uses: LizardByte/homebrew-release-action@v2025.506.15440
|
||||
with:
|
||||
formula_file: ${{ github.workspace }}/homebrew/sunshine.rb
|
||||
git_email: ${{ secrets.GH_BOT_EMAIL }}
|
||||
@@ -659,52 +624,56 @@ jobs:
|
||||
token: ${{ secrets.GH_BOT_TOKEN }}
|
||||
validate: true
|
||||
|
||||
- name: Generate gcov 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')
|
||||
id: test_report
|
||||
run: |
|
||||
# if linux
|
||||
if [ "${{ runner.os }}" == "Linux" ]; then
|
||||
prefix="/tmp"
|
||||
else
|
||||
prefix="/private/tmp"
|
||||
fi
|
||||
- name: Debug upload gcda artifact
|
||||
if: always() && matrix.release != true
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: homebrew-gcda-${{ matrix.os_name }}-${{ matrix.os_version }}
|
||||
path: ${{ steps.test.outputs.buildpath }}/build/tests/CMakeFiles/test_sunshine.dir/__/src/audio.cpp.gcda
|
||||
|
||||
brew_dir=$(find ${prefix} -type d -name "sunshine-*" -maxdepth 1 2>/dev/null)
|
||||
cp -rf $brew_dir/build/ ./build/
|
||||
cd build
|
||||
- name: Debug homebrew buildpath
|
||||
if: always() && matrix.release != true
|
||||
run: |
|
||||
# print the build path
|
||||
echo "Build path: ${{ steps.test.outputs.buildpath }}"
|
||||
echo "contents:"
|
||||
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
|
||||
# print the test path
|
||||
echo "----"
|
||||
echo "Test path: ${{ steps.test.outputs.testpath }}"
|
||||
echo "contents:"
|
||||
ls -Ra
|
||||
|
||||
- name: Upload test results to Codecov
|
||||
# any except canceled or skipped
|
||||
if: >-
|
||||
always() &&
|
||||
matrix.release != true &&
|
||||
(steps.test.outcome == 'success' || steps.test.outcome == 'failure') &&
|
||||
startsWith(github.repository, 'LizardByte/')
|
||||
uses: codecov/test-results-action@v1
|
||||
with:
|
||||
disable_search: true
|
||||
fail_ci_if_error: true
|
||||
files: ${{ steps.test.outputs.testpath }}/test_results.xml
|
||||
flags: ${{ matrix.os_name }}-${{ matrix.os_version }} (Homebrew)
|
||||
handle_no_reports_found: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
|
||||
- name: Upload coverage
|
||||
# any except canceled or skipped
|
||||
# TODO: enable this once coverage report is fixed
|
||||
if: false
|
||||
# if: >-
|
||||
# always() &&
|
||||
# matrix.release != true &&
|
||||
# (steps.test_report.outcome == 'success') &&
|
||||
# startsWith(github.repository, 'LizardByte/')
|
||||
if: >-
|
||||
always() &&
|
||||
matrix.release != true &&
|
||||
(steps.test_report.outcome == 'success') &&
|
||||
startsWith(github.repository, 'LizardByte/')
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
disable_search: true
|
||||
fail_ci_if_error: true
|
||||
files: ./build/coverage.xml
|
||||
files: ${{ steps.test.outputs.testpath }}/build/coverage.xml
|
||||
flags: ${{ matrix.os_name }}-${{ matrix.os_version }} (Homebrew)
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
@@ -713,7 +682,7 @@ jobs:
|
||||
if: >-
|
||||
matrix.release &&
|
||||
needs.setup_release.outputs.publish_release == 'true'
|
||||
uses: LizardByte/create-release-action@v2025.102.13208
|
||||
uses: LizardByte/create-release-action@v2025.426.1549
|
||||
with:
|
||||
allowUpdates: true
|
||||
artifacts: '${{ github.workspace }}/homebrew/*'
|
||||
@@ -727,8 +696,7 @@ jobs:
|
||||
- name: Patch homebrew formula
|
||||
# create beta version of the formula
|
||||
# don't run this on macOS, as the sed command fails
|
||||
if: >-
|
||||
matrix.release
|
||||
if: matrix.release
|
||||
run: |
|
||||
# variables
|
||||
formula_file="homebrew/sunshine-beta.rb"
|
||||
@@ -749,7 +717,7 @@ jobs:
|
||||
github.repository_owner == 'LizardByte' &&
|
||||
matrix.release &&
|
||||
needs.setup_release.outputs.publish_release == 'true'
|
||||
uses: LizardByte/homebrew-release-action@v2024.1115.14934
|
||||
uses: LizardByte/homebrew-release-action@v2025.503.165455
|
||||
with:
|
||||
formula_file: ${{ github.workspace }}/homebrew/sunshine-beta.rb
|
||||
git_email: ${{ secrets.GH_BOT_EMAIL }}
|
||||
@@ -760,9 +728,8 @@ jobs:
|
||||
|
||||
build_win:
|
||||
name: Windows
|
||||
needs: setup_release
|
||||
runs-on: windows-2019
|
||||
needs: [setup_release]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -874,28 +841,59 @@ jobs:
|
||||
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: ucrt64
|
||||
update: true
|
||||
install: >-
|
||||
git
|
||||
mingw-w64-ucrt-x86_64-boost
|
||||
mingw-w64-ucrt-x86_64-cmake
|
||||
mingw-w64-ucrt-x86_64-cppwinrt
|
||||
mingw-w64-ucrt-x86_64-curl-winssl
|
||||
mingw-w64-ucrt-x86_64-graphviz
|
||||
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
|
||||
wget
|
||||
|
||||
- name: Update Windows dependencies
|
||||
env:
|
||||
gcc_version: "14.2.0-3"
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
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-graphviz"
|
||||
"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[@]}"
|
||||
|
||||
- name: Install Doxygen
|
||||
# GCC compiled doxygen has issues when running graphviz
|
||||
env:
|
||||
@@ -943,6 +941,7 @@ jobs:
|
||||
env:
|
||||
BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||
BUILD_VERSION: ${{ needs.setup_release.outputs.release_tag }}
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
COMMIT: ${{ needs.setup_release.outputs.release_commit }}
|
||||
run: |
|
||||
mkdir -p build
|
||||
@@ -977,12 +976,14 @@ jobs:
|
||||
shell: msys2 {0}
|
||||
working-directory: build/tests
|
||||
run: |
|
||||
./test_sunshine.exe --gtest_color=yes
|
||||
./test_sunshine.exe --gtest_color=yes --gtest_output=xml:test_results.xml
|
||||
|
||||
- name: Generate gcov report
|
||||
# any except canceled or skipped
|
||||
if: always() && (steps.test.outcome == 'success' || steps.test.outcome == 'failure')
|
||||
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: |
|
||||
@@ -995,6 +996,22 @@ jobs:
|
||||
--xml-pretty \
|
||||
-o coverage.xml
|
||||
|
||||
- name: Upload test results to Codecov
|
||||
# any except canceled or skipped
|
||||
if: >-
|
||||
always() &&
|
||||
(steps.test.outcome == 'success' || steps.test.outcome == 'failure') &&
|
||||
startsWith(github.repository, 'LizardByte/')
|
||||
uses: codecov/test-results-action@v1
|
||||
with:
|
||||
disable_search: true
|
||||
fail_ci_if_error: true
|
||||
files: ./build/tests/test_results.xml
|
||||
flags: ${{ runner.os }}
|
||||
handle_no_reports_found: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
|
||||
- name: Upload coverage
|
||||
# any except canceled or skipped
|
||||
if: >-
|
||||
@@ -1028,10 +1045,11 @@ jobs:
|
||||
with:
|
||||
name: sunshine-windows
|
||||
path: artifacts/
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Create/Update GitHub Release
|
||||
if: ${{ needs.setup_release.outputs.publish_release == 'true' }}
|
||||
uses: LizardByte/create-release-action@v2025.102.13208
|
||||
if: needs.setup_release.outputs.publish_release == 'true'
|
||||
uses: LizardByte/create-release-action@v2025.426.1549
|
||||
with:
|
||||
allowUpdates: true
|
||||
body: ${{ needs.setup_release.outputs.release_body }}
|
||||
|
||||
4
.github/workflows/ci-copr.yml
vendored
4
.github/workflows/ci-copr.yml
vendored
@@ -1,5 +1,7 @@
|
||||
---
|
||||
name: CI Copr
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -26,7 +28,7 @@ jobs:
|
||||
github_org_owner: LizardByte
|
||||
copr_ownername: lizardbyte
|
||||
auto_update_package: true
|
||||
job_timeout: 60
|
||||
job_timeout: 90
|
||||
secrets:
|
||||
COPR_BETA_WEBHOOK_TOKEN: ${{ secrets.COPR_BETA_WEBHOOK_TOKEN }}
|
||||
COPR_STABLE_WEBHOOK_TOKEN: ${{ secrets.COPR_STABLE_WEBHOOK_TOKEN }}
|
||||
|
||||
56
.github/workflows/ci-docker.yml
vendored
56
.github/workflows/ci-docker.yml
vendored
@@ -1,5 +1,5 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# This workflow is centrally managed in https://github.com/<organization>/.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.
|
||||
|
||||
@@ -19,13 +19,20 @@
|
||||
# GitHub runner.
|
||||
|
||||
name: CI Docker
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- master
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
push:
|
||||
branches: [master]
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
@@ -97,10 +104,9 @@ jobs:
|
||||
solution: ${{ steps.find_dotnet.outputs.solution }}
|
||||
|
||||
setup_release:
|
||||
if: ${{ needs.check_dockerfiles.outputs.dockerfiles }}
|
||||
name: Setup Release
|
||||
needs:
|
||||
- check_dockerfiles
|
||||
if: needs.check_dockerfiles.outputs.dockerfiles
|
||||
needs: check_dockerfiles
|
||||
outputs:
|
||||
publish_release: ${{ steps.setup_release.outputs.publish_release }}
|
||||
release_body: ${{ steps.setup_release.outputs.release_body }}
|
||||
@@ -108,6 +114,8 @@ jobs:
|
||||
release_generate_release_notes: ${{ steps.setup_release.outputs.release_generate_release_notes }}
|
||||
release_tag: ${{ steps.setup_release.outputs.release_tag }}
|
||||
release_version: ${{ steps.setup_release.outputs.release_version }}
|
||||
permissions:
|
||||
contents: write # read does not work to check squash and merge details
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -115,23 +123,24 @@ jobs:
|
||||
|
||||
- name: Setup Release
|
||||
id: setup_release
|
||||
uses: LizardByte/setup-release-action@v2025.102.14715
|
||||
uses: LizardByte/setup-release-action@v2025.426.225
|
||||
with:
|
||||
dotnet: ${{ needs.check_dockerfiles.outputs.dotnet }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
docker:
|
||||
needs: [check_dockerfiles, setup_release]
|
||||
if: ${{ needs.check_dockerfiles.outputs.dockerfiles }}
|
||||
runs-on: ubuntu-latest
|
||||
name: Docker${{ matrix.tag }}
|
||||
if: needs.check_dockerfiles.outputs.dockerfiles
|
||||
needs:
|
||||
- check_dockerfiles
|
||||
- setup_release
|
||||
permissions:
|
||||
packages: write
|
||||
contents: write
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.check_dockerfiles.outputs.matrix) }}
|
||||
name: Docker${{ matrix.tag }}
|
||||
|
||||
steps:
|
||||
- name: Maximize build space
|
||||
uses: easimon/maximize-build-space@v10
|
||||
@@ -256,14 +265,14 @@ jobs:
|
||||
Docker-buildx${{ matrix.tag }}-
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
if: ${{ needs.setup_release.outputs.publish_release == 'true' }} # PRs do not have access to secrets
|
||||
if: needs.setup_release.outputs.publish_release == 'true' # PRs do not have access to secrets
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
|
||||
- name: Log in to the Container registry
|
||||
if: ${{ needs.setup_release.outputs.publish_release == 'true' }} # PRs do not have access to secrets
|
||||
if: needs.setup_release.outputs.publish_release == 'true' # PRs do not have access to secrets
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@@ -271,7 +280,7 @@ jobs:
|
||||
password: ${{ secrets.GH_BOT_TOKEN }}
|
||||
|
||||
- name: Build artifacts
|
||||
if: ${{ steps.prepare.outputs.artifacts == 'true' }}
|
||||
if: steps.prepare.outputs.artifacts == 'true'
|
||||
id: build_artifacts
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
@@ -314,7 +323,7 @@ jobs:
|
||||
no-cache-filters: ${{ steps.prepare.outputs.no_cache_filters }}
|
||||
|
||||
- name: Arrange Artifacts
|
||||
if: ${{ steps.prepare.outputs.artifacts == 'true' }}
|
||||
if: steps.prepare.outputs.artifacts == 'true'
|
||||
working-directory: artifacts
|
||||
run: |
|
||||
# debug directory
|
||||
@@ -336,15 +345,18 @@ jobs:
|
||||
rm -f ./provenance.json
|
||||
|
||||
- name: Upload Artifacts
|
||||
if: ${{ steps.prepare.outputs.artifacts == 'true' }}
|
||||
if: steps.prepare.outputs.artifacts == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Docker${{ matrix.tag }}
|
||||
path: artifacts/
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Create/Update GitHub Release
|
||||
if: ${{ needs.setup_release.outputs.publish_release == 'true' && steps.prepare.outputs.artifacts == 'true' }}
|
||||
uses: LizardByte/create-release-action@v2025.102.13208
|
||||
if: >
|
||||
needs.setup_release.outputs.publish_release == 'true' &&
|
||||
steps.prepare.outputs.artifacts == 'true'
|
||||
uses: LizardByte/create-release-action@v2025.426.1549
|
||||
with:
|
||||
allowUpdates: true
|
||||
artifacts: "*artifacts/*"
|
||||
@@ -356,7 +368,9 @@ jobs:
|
||||
token: ${{ secrets.GH_BOT_TOKEN }}
|
||||
|
||||
- name: Update Docker Hub Description
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
if: >
|
||||
github.event_name == 'push' &&
|
||||
github.ref == 'refs/heads/master'
|
||||
uses: peter-evans/dockerhub-description@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
|
||||
90
.github/workflows/codeql.yml
vendored
90
.github/workflows/codeql.yml
vendored
@@ -1,17 +1,21 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# This workflow is centrally managed in https://github.com/<organization>/.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.
|
||||
|
||||
# This workflow will analyze all supported languages in the repository using CodeQL Analysis.
|
||||
|
||||
name: "CodeQL"
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches: ["master"]
|
||||
branches:
|
||||
- master
|
||||
schedule:
|
||||
- cron: '00 12 * * 0' # every Sunday at 12:00 UTC
|
||||
|
||||
@@ -22,14 +26,17 @@ concurrency:
|
||||
jobs:
|
||||
languages:
|
||||
name: Get language matrix
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.lang.outputs.result }}
|
||||
continue: ${{ steps.continue.outputs.result }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get repo languages
|
||||
uses: actions/github-script@v7
|
||||
id: lang
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
// CodeQL supports ['cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift']
|
||||
@@ -51,32 +58,62 @@ jobs:
|
||||
"include": []
|
||||
}
|
||||
|
||||
// Track languages we've already added to avoid duplicates
|
||||
const addedLanguages = new Set()
|
||||
|
||||
// Check if workflow files exist to determine if we should add actions language
|
||||
const fs = require('fs');
|
||||
const hasYmlFiles = fs.existsSync('.github/workflows') &&
|
||||
fs.readdirSync('.github/workflows').some(file => file.endsWith('.yml') || file.endsWith('.yaml'));
|
||||
|
||||
// Add actions language if workflow files exist
|
||||
if (hasYmlFiles) {
|
||||
console.log('Found GitHub Actions workflow files. Adding actions to the matrix.');
|
||||
matrix['include'].push({
|
||||
"category": "/language:actions",
|
||||
"language": "actions",
|
||||
"name": "actions",
|
||||
"os": "ubuntu-latest"
|
||||
});
|
||||
}
|
||||
|
||||
for (let [key, value] of Object.entries(response.data)) {
|
||||
// remap language
|
||||
if (remap_languages[key.toLowerCase()]) {
|
||||
console.log(`Remapping language: ${key} to ${remap_languages[key.toLowerCase()]}`)
|
||||
key = remap_languages[key.toLowerCase()]
|
||||
}
|
||||
if (supported_languages.includes(key.toLowerCase())) {
|
||||
console.log(`Found supported language: ${key}`)
|
||||
|
||||
const normalizedKey = key.toLowerCase()
|
||||
|
||||
if (supported_languages.includes(normalizedKey) && !addedLanguages.has(normalizedKey)) {
|
||||
// Mark this language as added
|
||||
addedLanguages.add(normalizedKey)
|
||||
|
||||
console.log(`Found supported language: ${normalizedKey}`)
|
||||
let osList = ['ubuntu-latest'];
|
||||
if (key.toLowerCase() === 'swift') {
|
||||
if (normalizedKey === 'swift') {
|
||||
osList = ['macos-latest'];
|
||||
} else if (key.toLowerCase() === 'cpp') {
|
||||
// TODO: update macos to latest after the below issue is resolved
|
||||
// https://github.com/github/codeql-action/issues/2266
|
||||
osList = ['macos-13', 'ubuntu-latest', 'windows-latest'];
|
||||
} else if (normalizedKey === 'cpp') {
|
||||
osList = ['macos-latest', 'ubuntu-latest', 'windows-latest'];
|
||||
}
|
||||
for (let os of osList) {
|
||||
// set name for matrix
|
||||
if (osList.length == 1) {
|
||||
name = key.toLowerCase()
|
||||
} else {
|
||||
name = `${key.toLowerCase()}, ${os}`
|
||||
let name = osList.length === 1 ? normalizedKey : `${normalizedKey}, ${os}`
|
||||
|
||||
// set category for matrix
|
||||
let category = `/language:${normalizedKey}`
|
||||
if (normalizedKey === 'cpp') {
|
||||
category = `/language:cpp-${os.split('-')[0]}`
|
||||
}
|
||||
|
||||
// add to matrix
|
||||
matrix['include'].push({"language": key.toLowerCase(), "os": os, "name": name})
|
||||
matrix['include'].push({
|
||||
"category": category,
|
||||
"language": normalizedKey,
|
||||
"name": name,
|
||||
"os": os
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,8 +124,8 @@ jobs:
|
||||
return matrix
|
||||
|
||||
- name: Continue
|
||||
uses: actions/github-script@v7
|
||||
id: continue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
// if matrix['include'] is an empty list return false, otherwise true
|
||||
@@ -102,24 +139,22 @@ jobs:
|
||||
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.name }})
|
||||
if: ${{ needs.languages.outputs.continue == 'true' }}
|
||||
if: needs.languages.outputs.continue == 'true'
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.os == 'windows-latest' && 'msys2 {0}' || 'bash' }}
|
||||
env:
|
||||
GITHUB_CODEQL_BUILD: true
|
||||
needs: [languages]
|
||||
runs-on: ${{ matrix.os || 'ubuntu-latest' }}
|
||||
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
|
||||
needs: languages
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
runs-on: ${{ matrix.os || 'ubuntu-latest' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.languages.outputs.matrix) }}
|
||||
|
||||
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
|
||||
steps:
|
||||
- name: Maximize build space
|
||||
if: >-
|
||||
@@ -167,8 +202,7 @@ jobs:
|
||||
- third-party
|
||||
|
||||
# Pre autobuild
|
||||
# create a file named .codeql-prebuild-${{ matrix.language }}.sh in the root of your repository
|
||||
# create a file named .codeql-build-${{ matrix.language }}.sh in the root of your repository
|
||||
# create a file named .codeql-prebuild-${{ matrix.language }}-${{ runner.os }}.sh in the root of your repository
|
||||
- name: Prebuild
|
||||
id: prebuild
|
||||
run: |
|
||||
@@ -187,7 +221,7 @@ jobs:
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
category: "${{ matrix.category }}"
|
||||
output: sarif-results
|
||||
upload: failure-only
|
||||
|
||||
@@ -204,6 +238,7 @@ jobs:
|
||||
- name: Upload SARIF
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
category: "${{ matrix.category }}"
|
||||
sarif_file: sarif-results/${{ matrix.language }}.sarif
|
||||
|
||||
- name: Upload loc as a Build Artifact
|
||||
@@ -211,4 +246,5 @@ jobs:
|
||||
with:
|
||||
name: sarif-results-${{ matrix.language }}-${{ runner.os }}
|
||||
path: sarif-results
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
18
.github/workflows/common-lint.yml
vendored
18
.github/workflows/common-lint.yml
vendored
@@ -1,16 +1,22 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# This workflow is centrally managed in https://github.com/<organization>/.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.
|
||||
|
||||
# Common linting.
|
||||
|
||||
name: common lint
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- master
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
|
||||
concurrency:
|
||||
group: "${{ github.workflow }}-${{ github.ref }}"
|
||||
@@ -77,9 +83,10 @@ jobs:
|
||||
|
||||
- name: C++ - Clang format lint
|
||||
if: always() && steps.cpp_files.outputs.found_files
|
||||
uses: DoozyX/clang-format-lint-action@v0.18
|
||||
uses: DoozyX/clang-format-lint-action@v0.20
|
||||
with:
|
||||
source: ${{ steps.cpp_files.outputs.found_files }}
|
||||
clangFormatVersion: '20'
|
||||
extensions: 'c,cpp,h,hpp,m,mm'
|
||||
style: file
|
||||
inplace: false
|
||||
@@ -263,5 +270,4 @@ jobs:
|
||||
|
||||
- name: YAML - log
|
||||
if: always() && steps.yamllint.outcome == 'failure'
|
||||
run: |
|
||||
cat "${{ steps.yamllint.outputs.logfile }}" >> $GITHUB_STEP_SUMMARY
|
||||
run: cat "${{ steps.yamllint.outputs.logfile }}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
11
.github/workflows/issues.yml
vendored
11
.github/workflows/issues.yml
vendored
@@ -1,17 +1,22 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# This workflow is centrally managed in https://github.com/<organization>/.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.
|
||||
|
||||
# Label and un-label actions using `../label-actions.yml`.
|
||||
|
||||
name: Issues
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled, unlabeled]
|
||||
types:
|
||||
- labeled
|
||||
- unlabeled
|
||||
discussion:
|
||||
types: [labeled, unlabeled]
|
||||
types:
|
||||
- labeled
|
||||
- unlabeled
|
||||
|
||||
jobs:
|
||||
label:
|
||||
|
||||
13
.github/workflows/localize.yml
vendored
13
.github/workflows/localize.yml
vendored
@@ -1,10 +1,13 @@
|
||||
---
|
||||
name: localize
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths: # prevents workflow from running unless these files change
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '.github/workflows/localize.yml'
|
||||
- 'src/**'
|
||||
- 'locale/sunshine.po'
|
||||
@@ -54,7 +57,7 @@ jobs:
|
||||
python ./scripts/_locale.py --extract
|
||||
|
||||
- name: git diff
|
||||
if: ${{ env.new_file == 'false' }}
|
||||
if: env.new_file == 'false'
|
||||
run: |
|
||||
# disable the pager
|
||||
git config --global pager.diff false
|
||||
@@ -68,7 +71,9 @@ jobs:
|
||||
|
||||
- name: git reset
|
||||
# only run if a single line changed (date/time) and file already existed
|
||||
if: ${{ env.git_diff == '1 1 locale/sunshine.po' && env.new_file == 'false' }}
|
||||
if: >-
|
||||
env.git_diff == '1 1 locale/sunshine.po' &&
|
||||
env.new_file == 'false'
|
||||
run: |
|
||||
git reset --hard
|
||||
|
||||
|
||||
33
.github/workflows/release-notifier-moonlight.yml
vendored
33
.github/workflows/release-notifier-moonlight.yml
vendored
@@ -1,5 +1,6 @@
|
||||
---
|
||||
name: Release Notifications (Moonlight)
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
release:
|
||||
@@ -8,20 +9,30 @@ on:
|
||||
|
||||
jobs:
|
||||
discord:
|
||||
if: >-
|
||||
startsWith(github.repository, 'LizardByte/') &&
|
||||
!github.event.release.prerelease &&
|
||||
!github.event.release.draft
|
||||
if: github.repository_owner == 'LizardByte'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: discord
|
||||
uses: sarisia/actions-status-discord@v1 # https://github.com/sarisia/actions-status-discord
|
||||
- name: Check if latest GitHub release
|
||||
id: check-release
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_RELEASE_WEBHOOK_MOONLIGHT }}
|
||||
script: |
|
||||
const latestRelease = await github.rest.repos.getLatestRelease({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo
|
||||
});
|
||||
|
||||
core.setOutput('isLatestRelease', latestRelease.data.tag_name === context.payload.release.tag_name);
|
||||
|
||||
- name: discord
|
||||
if: steps.check-release.outputs.isLatestRelease == 'true'
|
||||
uses: sarisia/actions-status-discord@v1
|
||||
with:
|
||||
avatar_url: ${{ vars.ORG_LOGO_URL }}256
|
||||
color: 0x${{ vars.COLOR_HEX_GREEN }}
|
||||
description: ${{ github.event.release.body }}
|
||||
nodetail: true
|
||||
nofail: false
|
||||
username: ${{ secrets.DISCORD_USERNAME }}
|
||||
avatar_url: ${{ secrets.ORG_LOGO_URL }}
|
||||
title: ${{ github.event.repository.name }} ${{ github.ref_name }} Released
|
||||
description: ${{ github.event.release.body }}
|
||||
color: 0xFF4500
|
||||
username: ${{ secrets.DISCORD_USERNAME }}
|
||||
webhook: ${{ secrets.DISCORD_RELEASE_WEBHOOK_MOONLIGHT }}
|
||||
|
||||
18
.github/workflows/release-notifier.yml
vendored
18
.github/workflows/release-notifier.yml
vendored
@@ -1,11 +1,13 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# This workflow is centrally managed in https://github.com/<organization>/.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:
|
||||
@@ -14,8 +16,8 @@ on:
|
||||
|
||||
jobs:
|
||||
update-blog:
|
||||
if: >-
|
||||
github.repository_owner == 'LizardByte'
|
||||
name: Update blog
|
||||
if: github.repository_owner == 'LizardByte'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check topics
|
||||
@@ -41,8 +43,7 @@ jobs:
|
||||
|
||||
- name: Check if latest GitHub release
|
||||
id: check-release
|
||||
if: >-
|
||||
steps.check-label.outputs.hasTopic == 'true'
|
||||
if: steps.check-label.outputs.hasTopic == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
@@ -80,13 +81,14 @@ jobs:
|
||||
month_day=$(printf "%04d" "$month_day")
|
||||
|
||||
# create the filename
|
||||
file_name="_posts/releases/${year}-${month_day:0:2}-${month_day:2:2}-v${semver}.md"
|
||||
file_name="_posts/releases/${repo_lower}/${year}-${month_day:0:2}-${month_day:2:2}-v${semver}.md"
|
||||
mkdir -p "$(dirname "${file_name}")"
|
||||
|
||||
# create jekyll blog post
|
||||
echo "---" > "${file_name}"
|
||||
echo "layout: release" >> "${file_name}"
|
||||
echo "title: ${{ github.event.repository.name }} ${tag_name} Released" >> "${file_name}"
|
||||
echo "release-tag: ${tag_name}" >> "${file_name}"
|
||||
echo "gh-repo: ${{ github.repository }}" >> "${file_name}"
|
||||
echo "gh-badge: [follow, fork, star]" >> "${file_name}"
|
||||
echo "tags: [release, ${repo_lower}]" >> "${file_name}"
|
||||
@@ -127,9 +129,7 @@ jobs:
|
||||
steps.check-label.outputs.hasTopic == 'true' &&
|
||||
steps.check-release.outputs.isLatestRelease == 'true'
|
||||
run: |
|
||||
gh \
|
||||
pr \
|
||||
merge \
|
||||
gh pr merge \
|
||||
--auto \
|
||||
--delete-branch \
|
||||
--repo "LizardByte/LizardByte.github.io" \
|
||||
|
||||
10
.github/workflows/update-changelog.yml
vendored
10
.github/workflows/update-changelog.yml
vendored
@@ -1,15 +1,20 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# This workflow is centrally managed in https://github.com/<organization>/.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.
|
||||
|
||||
# Update changelog on release events.
|
||||
|
||||
name: Update changelog
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created, edited, deleted]
|
||||
types:
|
||||
- created
|
||||
- edited
|
||||
- deleted
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
@@ -18,6 +23,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
update-changelog:
|
||||
name: Update Changelog
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(!github.event.release.prerelease && !github.event.release.draft)
|
||||
|
||||
22
.github/workflows/update-docs.yml
vendored
22
.github/workflows/update-docs.yml
vendored
@@ -1,19 +1,23 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# This workflow is centrally managed in https://github.com/<organization>/.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.
|
||||
|
||||
# Use the `rtd` repository label to identify repositories that should trigger have this workflow.
|
||||
# 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]
|
||||
types:
|
||||
- created
|
||||
- edited
|
||||
- deleted
|
||||
|
||||
concurrency:
|
||||
group: "${{ github.workflow }}-${{ github.event.release.tag_name }}"
|
||||
@@ -73,17 +77,23 @@ jobs:
|
||||
- name: Update RTD project
|
||||
# changing the default branch in readthedocs makes "latest" point to that branch/tag
|
||||
# we can also update other properties like description, etc.
|
||||
if: >-
|
||||
steps.check.outputs.isLatestRelease == 'true'
|
||||
if: steps.check.outputs.isLatestRelease == 'true'
|
||||
run: |
|
||||
json_body=$(jq -n \
|
||||
--arg default_branch "${TAG}" \
|
||||
--arg description "${{ github.event.repository.description }}" \
|
||||
'{default_branch: $default_branch}')
|
||||
|
||||
# change the default branch to the latest release
|
||||
curl \
|
||||
-X PATCH \
|
||||
-H "Authorization: Token ${RTD_TOKEN}" \
|
||||
https://readthedocs.org/api/v3/projects/${RTD_SLUG}/ \
|
||||
-H "Content-Type: application/json" \
|
||||
https://readthedocs.org/api/v3/projects/${RTD_SLUG}/ \
|
||||
-d "$json_body"
|
||||
|
||||
# trigger a build for the latest version
|
||||
curl \
|
||||
-X POST \
|
||||
-H "Authorization: Token ${RTD_TOKEN}" \
|
||||
https://readthedocs.org/api/v3/projects/${RTD_SLUG}/versions/latest/builds/
|
||||
|
||||
47
.github/workflows/update-flathub-repo.yml
vendored
47
.github/workflows/update-flathub-repo.yml
vendored
@@ -1,15 +1,20 @@
|
||||
---
|
||||
# This action is a candidate to centrally manage in https://github.com/<organization>/.github/
|
||||
# If more Flathub applications are developed, consider moving this action to the organization's .github repository,
|
||||
# using the `flathub-pkg` repository label to identify repositories that should trigger this workflow.
|
||||
# This workflow is centrally managed in https://github.com/<organization>/.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]
|
||||
types:
|
||||
- released
|
||||
|
||||
concurrency:
|
||||
group: "${{ github.workflow }}-${{ github.event.release.tag_name }}"
|
||||
@@ -19,14 +24,13 @@ jobs:
|
||||
update-flathub-repo:
|
||||
env:
|
||||
FLATHUB_PKG: dev.lizardbyte.app.${{ github.event.repository.name }}
|
||||
if: >-
|
||||
github.repository_owner == 'LizardByte'
|
||||
if: github.repository_owner == 'LizardByte'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if flathub repo
|
||||
id: check-label
|
||||
env:
|
||||
TOPIC: flathub-pkg
|
||||
id: check-label
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
@@ -46,8 +50,7 @@ jobs:
|
||||
|
||||
- name: Check if latest GitHub release
|
||||
id: check-release
|
||||
if: >-
|
||||
steps.check-label.outputs.hasTopic == 'true'
|
||||
if: steps.check-label.outputs.hasTopic == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
@@ -104,7 +107,7 @@ jobs:
|
||||
if: >-
|
||||
steps.check-label.outputs.hasTopic == 'true' &&
|
||||
steps.check-release.outputs.isLatestRelease == 'true'
|
||||
uses: robinraju/release-downloader@v1.11
|
||||
uses: robinraju/release-downloader@v1.12
|
||||
with:
|
||||
repository: "${{ github.repository }}"
|
||||
tag: "${{ github.event.release.tag_name }}"
|
||||
@@ -114,7 +117,7 @@ jobs:
|
||||
out-file-path: "flathub/${{ env.FLATHUB_PKG }}"
|
||||
extract: true
|
||||
|
||||
- name: Delete arhive
|
||||
- name: Delete archive
|
||||
if: >-
|
||||
steps.check-label.outputs.hasTopic == 'true' &&
|
||||
steps.check-release.outputs.isLatestRelease == 'true'
|
||||
@@ -143,9 +146,9 @@ jobs:
|
||||
BEGIN { replaced = 0 }
|
||||
/<release version=.*>/ {
|
||||
if (!replaced) {
|
||||
print "<release version=\"" version "\" date=\"" date "\">"
|
||||
print "<description><p>" changelog "</p></description>"
|
||||
print "</release>"
|
||||
print " <release version=\"" version "\" date=\"" date "\">"
|
||||
print " <description><p>" changelog "</p></description>"
|
||||
print " </release>"
|
||||
replaced = 1
|
||||
}
|
||||
}
|
||||
@@ -172,6 +175,7 @@ jobs:
|
||||
git checkout $main_commit
|
||||
|
||||
- name: Create/Update Pull Request
|
||||
id: create-pr
|
||||
if: >-
|
||||
steps.check-label.outputs.hasTopic == 'true' &&
|
||||
steps.check-release.outputs.isLatestRelease == 'true' &&
|
||||
@@ -185,3 +189,18 @@ jobs:
|
||||
delete-branch: true
|
||||
title: "chore: Update ${{ env.FLATHUB_PKG }} to ${{ github.event.release.tag_name }}"
|
||||
body: ${{ github.event.release.body }}
|
||||
|
||||
- name: Automerge PR
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
|
||||
if: >-
|
||||
steps.check-label.outputs.hasTopic == 'true' &&
|
||||
steps.check-release.outputs.isLatestRelease == 'true' &&
|
||||
fromJson(steps.download.outputs.downloaded_files)[0]
|
||||
run: |
|
||||
gh pr merge \
|
||||
--auto \
|
||||
--delete-branch \
|
||||
--repo "flathub/${{ env.FLATHUB_PKG }}" \
|
||||
--squash \
|
||||
"${{ steps.create-pr.outputs.pull-request-number }}"
|
||||
|
||||
23
.github/workflows/update-homebrew-release.yml
vendored
23
.github/workflows/update-homebrew-release.yml
vendored
@@ -1,15 +1,20 @@
|
||||
---
|
||||
# This action is a candidate to centrally manage in https://github.com/<organization>/.github/
|
||||
# If more Homebrew applications are developed, consider moving this action to the organization's .github repository,
|
||||
# using the `homebrew-pkg` repository label to identify repositories that should trigger this workflow.
|
||||
# This workflow is centrally managed in https://github.com/<organization>/.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 release
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
types:
|
||||
- released
|
||||
|
||||
concurrency:
|
||||
group: "${{ github.workflow }}-${{ github.event.release.tag_name }}"
|
||||
@@ -17,14 +22,13 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
update-homebrew-release:
|
||||
if: >-
|
||||
github.repository_owner == 'LizardByte'
|
||||
if: github.repository_owner == 'LizardByte'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if Homebrew repo
|
||||
id: check-label
|
||||
env:
|
||||
TOPIC: homebrew-pkg
|
||||
id: check-label
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
@@ -44,9 +48,8 @@ jobs:
|
||||
|
||||
- name: Download release asset
|
||||
id: download
|
||||
if: >-
|
||||
steps.check-label.outputs.hasTopic == 'true'
|
||||
uses: robinraju/release-downloader@v1.11
|
||||
if: steps.check-label.outputs.hasTopic == 'true'
|
||||
uses: robinraju/release-downloader@v1.12
|
||||
with:
|
||||
repository: "${{ github.repository }}"
|
||||
tag: "${{ github.event.release.tag_name }}"
|
||||
|
||||
43
.github/workflows/update-pacman-repo.yml
vendored
43
.github/workflows/update-pacman-repo.yml
vendored
@@ -1,15 +1,20 @@
|
||||
---
|
||||
# This action is a candidate to centrally manage in https://github.com/<organization>/.github/
|
||||
# If more pacman packages are developed, consider moving this action to the organization's .github repository,
|
||||
# using the `pacman-pkg` repository label to identify repositories that should trigger have this workflow.
|
||||
# This workflow is centrally managed in https://github.com/<organization>/.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]
|
||||
types:
|
||||
- released
|
||||
|
||||
concurrency:
|
||||
group: "${{ github.workflow }}-${{ github.event.release.tag_name }}"
|
||||
@@ -17,14 +22,13 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
update-homebrew-release:
|
||||
if: >-
|
||||
github.repository_owner == 'LizardByte'
|
||||
if: github.repository_owner == 'LizardByte'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if pacman repo
|
||||
id: check-label
|
||||
env:
|
||||
TOPIC: pacman-pkg
|
||||
id: check-label
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
@@ -77,7 +81,7 @@ jobs:
|
||||
if: >-
|
||||
steps.check-label.outputs.hasTopic == 'true' &&
|
||||
steps.check-release.outputs.isLatestRelease == 'true'
|
||||
uses: robinraju/release-downloader@v1.11
|
||||
uses: robinraju/release-downloader@v1.12
|
||||
with:
|
||||
repository: "${{ github.repository }}"
|
||||
tag: "${{ github.event.release.tag_name }}"
|
||||
@@ -87,7 +91,16 @@ jobs:
|
||||
out-file-path: "pkgbuilds/${{ steps.prep.outputs.pkg_name }}"
|
||||
extract: true
|
||||
|
||||
- name: Remove pkg.tar.gz
|
||||
if: >-
|
||||
steps.check-label.outputs.hasTopic == 'true' &&
|
||||
steps.check-release.outputs.isLatestRelease == 'true' &&
|
||||
fromJson(steps.download.outputs.downloaded_files)[0]
|
||||
run: |
|
||||
rm -f "pkgbuilds/${{ steps.prep.outputs.pkg_name }}"
|
||||
|
||||
- name: Create/Update Pull Request
|
||||
id: create-pr
|
||||
if: >-
|
||||
steps.check-label.outputs.hasTopic == 'true' &&
|
||||
steps.check-release.outputs.isLatestRelease == 'true' &&
|
||||
@@ -105,3 +118,17 @@ jobs:
|
||||
labels: |
|
||||
auto-approve
|
||||
auto-merge
|
||||
|
||||
- name: Automerge PR
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
|
||||
if: >-
|
||||
steps.check-label.outputs.hasTopic == 'true' &&
|
||||
steps.check-release.outputs.isLatestRelease == 'true' &&
|
||||
fromJson(steps.download.outputs.downloaded_files)[0]
|
||||
run: |
|
||||
gh pr merge \
|
||||
--auto \
|
||||
--delete-branch \
|
||||
--squash \
|
||||
"${{ steps.create-pr.outputs.pull-request-number }}"
|
||||
|
||||
10
.github/workflows/update-pages.yml
vendored
10
.github/workflows/update-pages.yml
vendored
@@ -1,5 +1,7 @@
|
||||
---
|
||||
name: Build GH-Pages
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -37,11 +39,11 @@ jobs:
|
||||
call-jekyll-build:
|
||||
needs: prep
|
||||
uses: LizardByte/LizardByte.github.io/.github/workflows/jekyll-build.yml@master
|
||||
with:
|
||||
site_artifact: 'prep'
|
||||
target_branch: 'gh-pages'
|
||||
clean_gh_pages: true
|
||||
secrets:
|
||||
GH_BOT_EMAIL: ${{ secrets.GH_BOT_EMAIL }}
|
||||
GH_BOT_NAME: ${{ secrets.GH_BOT_NAME }}
|
||||
GH_BOT_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
|
||||
with:
|
||||
clean_gh_pages: true
|
||||
site_artifact: 'prep'
|
||||
target_branch: 'gh-pages'
|
||||
|
||||
23
.github/workflows/update-winget-release.yml
vendored
23
.github/workflows/update-winget-release.yml
vendored
@@ -1,15 +1,20 @@
|
||||
---
|
||||
# This action is a candidate to centrally manage in https://github.com/<organization>/.github/
|
||||
# If more Winget applications are developed, consider moving this action to the organization's .github repository,
|
||||
# using the `winget-pkg` repository label to identify repositories that should trigger this workflow.
|
||||
# This workflow is centrally managed in https://github.com/<organization>/.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 release
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
types:
|
||||
- released
|
||||
|
||||
concurrency:
|
||||
group: "${{ github.workflow }}-${{ github.event.release.tag_name }}"
|
||||
@@ -17,14 +22,13 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
update-winget-release:
|
||||
if: >-
|
||||
github.repository_owner == 'LizardByte'
|
||||
if: github.repository_owner == 'LizardByte'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if Winget repo
|
||||
id: check-label
|
||||
env:
|
||||
TOPIC: winget-pkg
|
||||
id: check-label
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
@@ -44,9 +48,8 @@ jobs:
|
||||
|
||||
- name: Download release asset
|
||||
id: download
|
||||
if: >-
|
||||
steps.check-label.outputs.hasTopic == 'true'
|
||||
uses: robinraju/release-downloader@v1.11
|
||||
if: steps.check-label.outputs.hasTopic == 'true'
|
||||
uses: robinraju/release-downloader@v1.12
|
||||
with:
|
||||
repository: "${{ github.repository }}"
|
||||
tag: "${{ github.event.release.tag_name }}"
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -44,7 +44,7 @@
|
||||
branch = sdk
|
||||
[submodule "third-party/Simple-Web-Server"]
|
||||
path = third-party/Simple-Web-Server
|
||||
url = https://gitlab.com/eidheim/Simple-Web-Server.git
|
||||
url = https://github.com/LizardByte-infrastructure/Simple-Web-Server.git
|
||||
branch = master
|
||||
[submodule "third-party/TPCircularBuffer"]
|
||||
path = third-party/TPCircularBuffer
|
||||
@@ -60,9 +60,9 @@
|
||||
branch = master
|
||||
[submodule "third-party/wayland-protocols"]
|
||||
path = third-party/wayland-protocols
|
||||
url = https://gitlab.freedesktop.org/wayland/wayland-protocols.git
|
||||
url = https://github.com/LizardByte-infrastructure/wayland-protocols.git
|
||||
branch = main
|
||||
[submodule "third-party/wlr-protocols"]
|
||||
path = third-party/wlr-protocols
|
||||
url = https://gitlab.freedesktop.org/wlroots/wlr-protocols.git
|
||||
url = https://github.com/LizardByte-infrastructure/wlr-protocols.git
|
||||
branch = master
|
||||
|
||||
76
README.md
76
README.md
@@ -5,42 +5,18 @@
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/LizardByte/Sunshine">
|
||||
<img src="https://img.shields.io/github/stars/lizardbyte/sunshine.svg?logo=github&style=for-the-badge" alt="GitHub stars">
|
||||
</a>
|
||||
<a href="https://github.com/LizardByte/Sunshine/releases/latest">
|
||||
<img src="https://img.shields.io/github/downloads/lizardbyte/sunshine/total.svg?style=for-the-badge&logo=github" alt="GitHub Releases">
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/lizardbyte/sunshine">
|
||||
<img src="https://img.shields.io/docker/pulls/lizardbyte/sunshine.svg?style=for-the-badge&logo=docker" alt="Docker">
|
||||
</a>
|
||||
<a href="https://github.com/LizardByte/Sunshine/pkgs/container/sunshine">
|
||||
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fipitio.github.io%2Fbackage%2FLizardByte%2FSunshine%2Fsunshine.json&query=%24.downloads&label=ghcr%20pulls&style=for-the-badge&logo=github" alt="GHCR">
|
||||
</a>
|
||||
<a href="https://flathub.org/apps/dev.lizardbyte.app.Sunshine">
|
||||
<img src="https://img.shields.io/flathub/downloads/dev.lizardbyte.app.Sunshine?style=for-the-badge&logo=flathub" alt="Flathub installs">
|
||||
</a>
|
||||
<a href="https://flathub.org/apps/dev.lizardbyte.app.Sunshine">
|
||||
<img src="https://img.shields.io/flathub/v/dev.lizardbyte.app.Sunshine?style=for-the-badge&logo=flathub" alt="Flathub Version">
|
||||
</a>
|
||||
<a href="https://github.com/microsoft/winget-pkgs/tree/master/manifests/l/LizardByte/Sunshine">
|
||||
<img src="https://img.shields.io/winget/v/LizardByte.Sunshine?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHuSURBVFhH7ZfNTtRQGIYZiMDwN/IrCAqIhMSNKxcmymVwG+5dcDVsWHgDrtxwCYQVl+BChzDEwSnPY+eQ0sxoOz1mQuBNnpyvTdvz9jun5/SrjfxnJUkyQbMEz2ELduF1l0YUA3QyTrMAa2AnPtyOXsELeAYNyKtV2EC3k3lYgTOwg09ghy/BTp7CKBRV844BOpmmMV2+ySb4BmInG7AKY7AHH+EYqqhZo9PPBG/BVDlOizAD/XQFmnoPXzxRQX8M/CCYS48L6RIc4ygGHK9WGg9HZSZMUNRPVwNJGg5Hg2Qgqh4N3FsDsb6EmgYm07iwwvUxstdxJTwgmILf4CfZ6bb5OHANX8GN5x20IVxnG8ge94pt2xpwU3GnCwayF4Q2G2vgFLzHndFzQdk4q77nNfCdwL28qNyMtmEf3A1/QV5FjDiPWo5jrwf8TWZChTlgJvL4F9QL50/A43qVidTvLcuoM2wDQ1+IkgefgUpLcYwMVBqCKNJA2b0gKNocOIITOIef8C/F/CdMbh/GklynsSawKLHS8d9/B1x2LUqsfFyy3TMsWj5A1cLkotDbYO4JjWWZlZEGv8EbOIR1CAVN2eG8W5oNKgxaeC6DmTJjZs7ixUxpznLPLT+v4sXpoMLcLI3mzFSonDXIEI/M3QCIO4YuimBJ/gAAAABJRU5ErkJggg==" alt="Winget Version">
|
||||
</a>
|
||||
<a href="https://gurubase.io/g/sunshine">
|
||||
<img src="https://img.shields.io/badge/Gurubase-Ask%20Guru-ef1a1b?style=for-the-badge&logo=data:image/jpeg;base64,/9j/2wCEAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDIBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/AABEIABgAGAMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AOLqSO3mlilljido4QGkYDIQEgAn05IH41seFo7aS+uRKlrJci2Y2cd2QImlyOGyQPu7sA8ZxXapAlvpThbPRkv7nTQWhDoIZZRc/XaSAOmcZGOnFfP06XMr3P17F5iqE+Tl1uuvf9Lde55dRW74pit4r61EcdtFdG2U3kVqQY0lyeBgkD5duQOASawqykuV2O6jV9rTU0rXLNjf3Om3QubSXy5QCudoYEEYIIOQR7GnahqV3qk6zXk3mOqhFAUKqqOyqAAByeAKqUUXdrFezhz89lfv1+8KKKKRZ//Z" alt="Gurubase">
|
||||
</a>
|
||||
<a href="https://github.com/LizardByte/Sunshine/actions/workflows/CI.yml?query=branch%3Amaster">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/lizardbyte/sunshine/CI.yml.svg?branch=master&label=CI%20build&logo=github&style=for-the-badge" alt="GitHub Workflow Status (CI)">
|
||||
</a>
|
||||
<a href="https://github.com/LizardByte/Sunshine/actions/workflows/localize.yml?query=branch%3Amaster">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/lizardbyte/sunshine/localize.yml.svg?branch=master&label=localize%20build&logo=github&style=for-the-badge" alt="GitHub Workflow Status (localize)">
|
||||
</a>
|
||||
<a href="https://docs.lizardbyte.dev/projects/sunshine">
|
||||
<img src="https://img.shields.io/readthedocs/sunshinestream.svg?label=Docs&style=for-the-badge&logo=readthedocs" alt="Read the Docs">
|
||||
</a>
|
||||
<a href="https://codecov.io/gh/LizardByte/Sunshine">
|
||||
<img src="https://img.shields.io/codecov/c/gh/LizardByte/Sunshine?token=SMGXQ5NVMJ&style=for-the-badge&logo=codecov&label=codecov" alt="Codecov">
|
||||
</a>
|
||||
<a href="https://github.com/LizardByte/Sunshine"><img src="https://img.shields.io/github/stars/lizardbyte/sunshine.svg?logo=github&style=for-the-badge" alt="GitHub stars"></a>
|
||||
<a href="https://github.com/LizardByte/Sunshine/releases/latest"><img src="https://img.shields.io/github/downloads/lizardbyte/sunshine/total.svg?style=for-the-badge&logo=github" alt="GitHub Releases"></a>
|
||||
<a href="https://hub.docker.com/r/lizardbyte/sunshine"><img src="https://img.shields.io/docker/pulls/lizardbyte/sunshine.svg?style=for-the-badge&logo=docker" alt="Docker"></a>
|
||||
<a href="https://github.com/LizardByte/Sunshine/pkgs/container/sunshine"><img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fipitio.github.io%2Fbackage%2FLizardByte%2FSunshine%2Fsunshine.json&query=%24.downloads&label=ghcr%20pulls&style=for-the-badge&logo=github" alt="GHCR"></a>
|
||||
<a href="https://flathub.org/apps/dev.lizardbyte.app.Sunshine"><img src="https://img.shields.io/flathub/downloads/dev.lizardbyte.app.Sunshine?style=for-the-badge&logo=flathub" alt="Flathub installs"></a>
|
||||
<a href="https://flathub.org/apps/dev.lizardbyte.app.Sunshine"><img src="https://img.shields.io/flathub/v/dev.lizardbyte.app.Sunshine?style=for-the-badge&logo=flathub" alt="Flathub Version"></a>
|
||||
<a href="https://github.com/microsoft/winget-pkgs/tree/master/manifests/l/LizardByte/Sunshine"><img src="https://img.shields.io/winget/v/LizardByte.Sunshine?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHuSURBVFhH7ZfNTtRQGIYZiMDwN/IrCAqIhMSNKxcmymVwG+5dcDVsWHgDrtxwCYQVl+BChzDEwSnPY+eQ0sxoOz1mQuBNnpyvTdvz9jun5/SrjfxnJUkyQbMEz2ELduF1l0YUA3QyTrMAa2AnPtyOXsELeAYNyKtV2EC3k3lYgTOwg09ghy/BTp7CKBRV844BOpmmMV2+ySb4BmInG7AKY7AHH+EYqqhZo9PPBG/BVDlOizAD/XQFmnoPXzxRQX8M/CCYS48L6RIc4ygGHK9WGg9HZSZMUNRPVwNJGg5Hg2Qgqh4N3FsDsb6EmgYm07iwwvUxstdxJTwgmILf4CfZ6bb5OHANX8GN5x20IVxnG8ge94pt2xpwU3GnCwayF4Q2G2vgFLzHndFzQdk4q77nNfCdwL28qNyMtmEf3A1/QV5FjDiPWo5jrwf8TWZChTlgJvL4F9QL50/A43qVidTvLcuoM2wDQ1+IkgefgUpLcYwMVBqCKNJA2b0gKNocOIITOIef8C/F/CdMbh/GklynsSawKLHS8d9/B1x2LUqsfFyy3TMsWj5A1cLkotDbYO4JjWWZlZEGv8EbOIR1CAVN2eG8W5oNKgxaeC6DmTJjZs7ixUxpznLPLT+v4sXpoMLcLI3mzFSonDXIEI/M3QCIO4YuimBJ/gAAAABJRU5ErkJggg==" alt="Winget Version"></a>
|
||||
<a href="https://gurubase.io/g/sunshine"><img src="https://img.shields.io/badge/Gurubase-Ask%20Guru-ef1a1b?style=for-the-badge&logo=data:image/jpeg;base64,/9j/2wCEAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDIBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/AABEIABgAGAMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AOLqSO3mlilljido4QGkYDIQEgAn05IH41seFo7aS+uRKlrJci2Y2cd2QImlyOGyQPu7sA8ZxXapAlvpThbPRkv7nTQWhDoIZZRc/XaSAOmcZGOnFfP06XMr3P17F5iqE+Tl1uuvf9Lde55dRW74pit4r61EcdtFdG2U3kVqQY0lyeBgkD5duQOASawqykuV2O6jV9rTU0rXLNjf3Om3QubSXy5QCudoYEEYIIOQR7GnahqV3qk6zXk3mOqhFAUKqqOyqAAByeAKqUUXdrFezhz89lfv1+8KKKKRZ//Z" alt="Gurubase"></a>
|
||||
<a href="https://github.com/LizardByte/Sunshine/actions/workflows/CI.yml?query=branch%3Amaster"><img src="https://img.shields.io/github/actions/workflow/status/lizardbyte/sunshine/CI.yml.svg?branch=master&label=CI%20build&logo=github&style=for-the-badge" alt="GitHub Workflow Status (CI)"></a>
|
||||
<a href="https://github.com/LizardByte/Sunshine/actions/workflows/localize.yml?query=branch%3Amaster"><img src="https://img.shields.io/github/actions/workflow/status/lizardbyte/sunshine/localize.yml.svg?branch=master&label=localize%20build&logo=github&style=for-the-badge" alt="GitHub Workflow Status (localize)"></a>
|
||||
<a href="https://docs.lizardbyte.dev/projects/sunshine"><img src="https://img.shields.io/readthedocs/sunshinestream.svg?label=Docs&style=for-the-badge&logo=readthedocs" alt="Read the Docs"></a>
|
||||
<a href="https://codecov.io/gh/LizardByte/Sunshine"><img src="https://img.shields.io/codecov/c/gh/LizardByte/Sunshine?token=SMGXQ5NVMJ&style=for-the-badge&logo=codecov&label=codecov" alt="Codecov"></a>
|
||||
</div>
|
||||
|
||||
## ℹ️ About
|
||||
@@ -188,6 +164,34 @@ LizardByte has the full documentation hosted on [Read the Docs](https://docs.liz
|
||||
|
||||
Our support methods are listed in our [LizardByte Docs](https://docs.lizardbyte.dev/latest/about/support.html).
|
||||
|
||||
## 💲 Sponsors and Supporters
|
||||
|
||||
<p align="center">
|
||||
<a href="https://app.lizardbyte.dev" aria-label="Sponsor LizardByte">
|
||||
<img src='https://raw.githubusercontent.com/LizardByte/contributors/refs/heads/dist/sponsors.svg'/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## 👥 Contributors
|
||||
|
||||
Thank you to all the contributors who have helped make Sunshine better!
|
||||
|
||||
### GitHub
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/LizardByte/Sunshine" aria-label="GitHub">
|
||||
<img src='https://raw.githubusercontent.com/LizardByte/contributors/refs/heads/dist/github.Sunshine.svg'/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
### CrowdIn
|
||||
|
||||
<p align="center">
|
||||
<a href="https://translate.lizardbyte.dev" aria-label="CrowdIn">
|
||||
<img src='https://raw.githubusercontent.com/LizardByte/contributors/refs/heads/dist/crowdin.606145.svg'/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div class="section_buttons">
|
||||
|
||||
| Previous | Next |
|
||||
|
||||
@@ -149,6 +149,7 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
enet
|
||||
libdisplaydevice::display_device
|
||||
nlohmann_json::nlohmann_json
|
||||
opus
|
||||
${FFMPEG_LIBRARIES}
|
||||
${Boost_LIBRARIES}
|
||||
|
||||
@@ -137,7 +137,8 @@ if(WAYLAND_FOUND)
|
||||
endif()
|
||||
|
||||
GEN_WAYLAND("${WAYLAND_PROTOCOLS_DIR}" "unstable/xdg-output" xdg-output-unstable-v1)
|
||||
GEN_WAYLAND("${CMAKE_SOURCE_DIR}/third-party/wlr-protocols" "unstable" wlr-export-dmabuf-unstable-v1)
|
||||
GEN_WAYLAND("${WAYLAND_PROTOCOLS_DIR}" "unstable/linux-dmabuf" linux-dmabuf-unstable-v1)
|
||||
GEN_WAYLAND("${CMAKE_SOURCE_DIR}/third-party/wlr-protocols" "unstable" wlr-screencopy-unstable-v1)
|
||||
|
||||
include_directories(
|
||||
SYSTEM
|
||||
@@ -145,7 +146,7 @@ if(WAYLAND_FOUND)
|
||||
${CMAKE_BINARY_DIR}/generated-src
|
||||
)
|
||||
|
||||
list(APPEND PLATFORM_LIBRARIES ${WAYLAND_LIBRARIES})
|
||||
list(APPEND PLATFORM_LIBRARIES ${WAYLAND_LIBRARIES} gbm)
|
||||
list(APPEND PLATFORM_TARGET_FILES
|
||||
"${CMAKE_SOURCE_DIR}/src/platform/linux/wlgrab.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/platform/linux/wayland.h"
|
||||
@@ -198,29 +199,33 @@ if(${SUNSHINE_ENABLE_TRAY})
|
||||
list(APPEND PLATFORM_TARGET_FILES "${CMAKE_SOURCE_DIR}/third-party/tray/src/tray_linux.c")
|
||||
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${APPINDICATOR_LIBRARIES} ${LIBNOTIFY_LIBRARIES})
|
||||
endif()
|
||||
|
||||
# flatpak icons must be prefixed with the app id or they will not be included in the flatpak
|
||||
if(${SUNSHINE_BUILD_FLATPAK})
|
||||
set(SUNSHINE_TRAY_PREFIX "${PROJECT_FQDN}")
|
||||
else()
|
||||
set(SUNSHINE_TRAY_PREFIX "sunshine")
|
||||
endif()
|
||||
list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_TRAY_PREFIX="${SUNSHINE_TRAY_PREFIX}")
|
||||
else()
|
||||
set(SUNSHINE_TRAY 0)
|
||||
message(STATUS "Tray icon disabled")
|
||||
endif()
|
||||
|
||||
if(${SUNSHINE_USE_LEGACY_INPUT}) # TODO: Remove this legacy option after the next stable release
|
||||
list(APPEND PLATFORM_TARGET_FILES "${CMAKE_SOURCE_DIR}/src/platform/linux/input/legacy_input.cpp")
|
||||
else()
|
||||
# These need to be set before adding the inputtino subdirectory in order for them to be picked up
|
||||
set(LIBEVDEV_CUSTOM_INCLUDE_DIR "${EVDEV_INCLUDE_DIR}")
|
||||
set(LIBEVDEV_CUSTOM_LIBRARY "${EVDEV_LIBRARY}")
|
||||
# These need to be set before adding the inputtino subdirectory in order for them to be picked up
|
||||
set(LIBEVDEV_CUSTOM_INCLUDE_DIR "${EVDEV_INCLUDE_DIR}")
|
||||
set(LIBEVDEV_CUSTOM_LIBRARY "${EVDEV_LIBRARY}")
|
||||
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/inputtino")
|
||||
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES inputtino::libinputtino)
|
||||
file(GLOB_RECURSE INPUTTINO_SOURCES
|
||||
${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.h
|
||||
${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.cpp)
|
||||
list(APPEND PLATFORM_TARGET_FILES ${INPUTTINO_SOURCES})
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/inputtino")
|
||||
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES inputtino::libinputtino)
|
||||
file(GLOB_RECURSE INPUTTINO_SOURCES
|
||||
${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.h
|
||||
${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.cpp)
|
||||
list(APPEND PLATFORM_TARGET_FILES ${INPUTTINO_SOURCES})
|
||||
|
||||
# build libevdev before the libinputtino target
|
||||
if(EXTERNAL_PROJECT_LIBEVDEV_USED)
|
||||
add_dependencies(libinputtino libevdev)
|
||||
endif()
|
||||
# build libevdev before the libinputtino target
|
||||
if(EXTERNAL_PROJECT_LIBEVDEV_USED)
|
||||
add_dependencies(libinputtino libevdev)
|
||||
endif()
|
||||
|
||||
# AppImage and Flatpak
|
||||
|
||||
@@ -38,7 +38,7 @@ if(NOT DEFINED SUNSHINE_ICON_PATH)
|
||||
set(SUNSHINE_ICON_PATH "${CMAKE_SOURCE_DIR}/sunshine.ico")
|
||||
endif()
|
||||
|
||||
configure_file("${CMAKE_SOURCE_DIR}/src/platform/windows/windows.rs.in" windows.rc @ONLY)
|
||||
configure_file("${CMAKE_SOURCE_DIR}/src/platform/windows/windows.rc.in" windows.rc @ONLY)
|
||||
|
||||
set(PLATFORM_TARGET_FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/windows.rc"
|
||||
@@ -76,7 +76,6 @@ list(PREPEND PLATFORM_LIBRARIES
|
||||
libstdc++.a
|
||||
libwinpthread.a
|
||||
minhook::minhook
|
||||
nlohmann_json::nlohmann_json
|
||||
ntdll
|
||||
setupapi
|
||||
shlwapi
|
||||
|
||||
@@ -3,26 +3,43 @@
|
||||
#
|
||||
include_guard(GLOBAL)
|
||||
|
||||
set(BOOST_VERSION 1.86)
|
||||
set(BOOST_VERSION "1.87.0")
|
||||
set(BOOST_COMPONENTS
|
||||
filesystem
|
||||
locale
|
||||
log
|
||||
program_options
|
||||
system) # system is not used by Sunshine, but by Simple-Web-Server, added here for convenience
|
||||
system
|
||||
)
|
||||
# system is not used by Sunshine, but by Simple-Web-Server, added here for convenience
|
||||
|
||||
# algorithm, preprocessor, scope, and uuid are not used by Sunshine, but by libdisplaydevice, added here for convenience
|
||||
if(WIN32)
|
||||
list(APPEND BOOST_COMPONENTS
|
||||
algorithm
|
||||
preprocessor
|
||||
scope
|
||||
uuid
|
||||
)
|
||||
endif()
|
||||
|
||||
if(BOOST_USE_STATIC)
|
||||
set(Boost_USE_STATIC_LIBS ON) # cmake-lint: disable=C0103
|
||||
endif()
|
||||
|
||||
find_package(Boost CONFIG ${BOOST_VERSION} COMPONENTS ${BOOST_COMPONENTS})
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.30")
|
||||
cmake_policy(SET CMP0167 NEW) # Get BoostConfig.cmake from upstream
|
||||
endif()
|
||||
find_package(Boost CONFIG ${BOOST_VERSION} EXACT COMPONENTS ${BOOST_COMPONENTS})
|
||||
if(NOT Boost_FOUND)
|
||||
message(STATUS "Boost v${BOOST_VERSION}.x package not found in the system. Falling back to FetchContent.")
|
||||
message(STATUS "Boost v${BOOST_VERSION} package not found in the system. Falling back to FetchContent.")
|
||||
include(FetchContent)
|
||||
|
||||
# Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24:
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
|
||||
cmake_policy(SET CMP0135 NEW)
|
||||
cmake_policy(SET CMP0135 NEW) # Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24
|
||||
endif()
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.31.0")
|
||||
cmake_policy(SET CMP0174 NEW) # Handle empty variables
|
||||
endif()
|
||||
|
||||
# more components required for compiling boost targets
|
||||
@@ -36,12 +53,9 @@ if(NOT Boost_FOUND)
|
||||
set(BOOST_ENABLE_CMAKE ON)
|
||||
|
||||
# Limit boost to the required libraries only
|
||||
set(BOOST_INCLUDE_LIBRARIES
|
||||
${BOOST_COMPONENTS})
|
||||
set(BOOST_URL
|
||||
"https://github.com/boostorg/boost/releases/download/boost-1.86.0/boost-1.86.0-cmake.tar.xz")
|
||||
set(BOOST_HASH
|
||||
"MD5=D02759931CEDC02ADED80402906C5EB6")
|
||||
set(BOOST_INCLUDE_LIBRARIES ${BOOST_COMPONENTS})
|
||||
set(BOOST_URL "https://github.com/boostorg/boost/releases/download/boost-${BOOST_VERSION}/boost-${BOOST_VERSION}-cmake.tar.xz") # cmake-lint: disable=C0301
|
||||
set(BOOST_HASH "SHA256=7da75f171837577a52bbf217e17f8ea576c7c246e4594d617bfde7fafd408be5")
|
||||
|
||||
if(CMAKE_VERSION VERSION_LESS "3.24.0")
|
||||
FetchContent_Declare(
|
||||
@@ -72,7 +86,7 @@ if(NOT Boost_FOUND)
|
||||
|
||||
set(Boost_FOUND TRUE) # cmake-lint: disable=C0103
|
||||
set(Boost_INCLUDE_DIRS # cmake-lint: disable=C0103
|
||||
"$<BUILD_INTERFACE:${Boost_SOURCE_DIR}/libs/headers/include>;$<INSTALL_INTERFACE:include/boost-1_85>")
|
||||
"$<BUILD_INTERFACE:${Boost_SOURCE_DIR}/libs/headers/include>")
|
||||
|
||||
if(WIN32)
|
||||
# Windows build is failing to create .h file in this directory
|
||||
|
||||
@@ -16,6 +16,7 @@ add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/Simple-Web-Server")
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/libdisplaydevice")
|
||||
|
||||
# common dependencies
|
||||
include("${CMAKE_MODULE_PATH}/dependencies/nlohmann_json.cmake")
|
||||
find_package(OpenSSL REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
25
cmake/dependencies/nlohmann_json.cmake
Normal file
25
cmake/dependencies/nlohmann_json.cmake
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
# Loads the nlohmann_json library giving the priority to the system package first, with a fallback to FetchContent.
|
||||
#
|
||||
include_guard(GLOBAL)
|
||||
|
||||
find_package(nlohmann_json 3.11 QUIET GLOBAL)
|
||||
if(NOT nlohmann_json_FOUND)
|
||||
message(STATUS "nlohmann_json v3.11.x package not found in the system. Falling back to FetchContent.")
|
||||
include(FetchContent)
|
||||
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
|
||||
cmake_policy(SET CMP0135 NEW) # Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24
|
||||
endif()
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.31.0")
|
||||
cmake_policy(SET CMP0174 NEW) # Handle empty variables
|
||||
endif()
|
||||
|
||||
FetchContent_Declare(
|
||||
json
|
||||
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
|
||||
URL_HASH MD5=c23a33f04786d85c29fda8d16b5f0efd
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP
|
||||
)
|
||||
FetchContent_MakeAvailable(json)
|
||||
endif()
|
||||
@@ -1,8 +1,5 @@
|
||||
# windows specific dependencies
|
||||
|
||||
# nlohmann_json
|
||||
find_package(nlohmann_json CONFIG 3.11 REQUIRED)
|
||||
|
||||
# Make sure MinHook is installed
|
||||
find_library(MINHOOK_LIBRARY libMinHook.a REQUIRED)
|
||||
find_path(MINHOOK_INCLUDE_DIR MinHook.h PATH_SUFFIXES include REQUIRED)
|
||||
|
||||
@@ -100,15 +100,31 @@ endif()
|
||||
|
||||
# tray icon
|
||||
if(${SUNSHINE_TRAY} STREQUAL 1)
|
||||
install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
|
||||
RENAME "sunshine-tray.svg")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-playing.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-pausing.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-locked.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
|
||||
if(NOT ${SUNSHINE_BUILD_FLATPAK})
|
||||
install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
|
||||
RENAME "sunshine-tray.svg")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-playing.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-pausing.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-locked.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
|
||||
else()
|
||||
# flatpak icons must be prefixed with the app id or they will not be included in the flatpak
|
||||
install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
|
||||
RENAME "${PROJECT_FQDN}-tray.svg")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-playing.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
|
||||
RENAME "${PROJECT_FQDN}-playing.svg")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-pausing.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
|
||||
RENAME "${PROJECT_FQDN}-pausing.svg")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-locked.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
|
||||
RENAME "${PROJECT_FQDN}-locked.svg")
|
||||
endif()
|
||||
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "\
|
||||
${CPACK_DEBIAN_PACKAGE_DEPENDS}, \
|
||||
@@ -128,15 +144,8 @@ else()
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.desktop"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications"
|
||||
RENAME "${PROJECT_FQDN}.desktop")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine_kms.desktop"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications"
|
||||
RENAME "${PROJECT_FQDN}_kms.desktop")
|
||||
endif()
|
||||
if(${SUNSHINE_BUILD_FLATPAK})
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine_terminal.desktop"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications"
|
||||
RENAME "${PROJECT_FQDN}_terminal.desktop")
|
||||
elseif(NOT ${SUNSHINE_BUILD_APPIMAGE})
|
||||
if(NOT ${SUNSHINE_BUILD_APPIMAGE} AND NOT ${SUNSHINE_BUILD_FLATPAK})
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine_terminal.desktop"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
|
||||
endif()
|
||||
|
||||
@@ -20,6 +20,9 @@ install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/service/"
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/migration/"
|
||||
DESTINATION "scripts"
|
||||
COMPONENT assets)
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/path/"
|
||||
DESTINATION "scripts"
|
||||
COMPONENT assets)
|
||||
|
||||
# Configurable options for the service
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/autostart/"
|
||||
@@ -64,6 +67,7 @@ SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS
|
||||
IfSilent +2 0
|
||||
ExecShell 'open' 'https://docs.lizardbyte.dev/projects/sunshine'
|
||||
nsExec::ExecToLog 'icacls \\\"$INSTDIR\\\" /reset'
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\update-path.bat\\\" add'
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\migrate-config.bat\\\"'
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\add-firewall-rule.bat\\\"'
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\install-gamepad.bat\\\"'
|
||||
@@ -78,7 +82,7 @@ set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS
|
||||
"${CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS}
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\delete-firewall-rule.bat\\\"'
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\uninstall-service.bat\\\"'
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\sunshine.exe\\\" --restore-nvprefs-undo'
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe\\\" --restore-nvprefs-undo'
|
||||
MessageBox MB_YESNO|MB_ICONQUESTION \
|
||||
'Do you want to remove Virtual Gamepad?' \
|
||||
/SD IDNO IDNO NoGamepad
|
||||
@@ -88,16 +92,18 @@ set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS
|
||||
'Do you want to remove $INSTDIR (this includes the configuration, cover images, and settings)?' \
|
||||
/SD IDNO IDNO NoDelete
|
||||
RMDir /r \\\"$INSTDIR\\\"; skipped if no
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\update-path.bat\\\" remove'
|
||||
NoDelete:
|
||||
")
|
||||
|
||||
# Adding an option for the start menu
|
||||
set(CPACK_NSIS_MODIFY_PATH "OFF")
|
||||
set(CPACK_NSIS_MODIFY_PATH OFF)
|
||||
set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
|
||||
# This will be shown on the installed apps Windows settings
|
||||
set(CPACK_NSIS_INSTALLED_ICON_NAME "${CMAKE_PROJECT_NAME}.exe")
|
||||
set(CPACK_NSIS_CREATE_ICONS_EXTRA
|
||||
"${CPACK_NSIS_CREATE_ICONS_EXTRA}
|
||||
SetOutPath '\$INSTDIR'
|
||||
CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${CMAKE_PROJECT_NAME}.lnk' \
|
||||
'\$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe' '--shortcut'
|
||||
")
|
||||
|
||||
@@ -8,10 +8,10 @@ elseif (UNIX)
|
||||
endif()
|
||||
|
||||
if(SUNSHINE_BUILD_FLATPAK)
|
||||
set(SUNSHINE_SERVICE_START_COMMAND "ExecStart=${PROJECT_FQDN}")
|
||||
set(SUNSHINE_SERVICE_START_COMMAND "ExecStart=flatpak run --command=sunshine ${PROJECT_FQDN}")
|
||||
set(SUNSHINE_SERVICE_STOP_COMMAND "ExecStop=flatpak kill ${PROJECT_FQDN}")
|
||||
else()
|
||||
set(SUNSHINE_SERVICE_START_COMMAND "ExecStart=${SUNSHINE_EXECUTABLE_PATH}")
|
||||
set(SUNSHINE_SERVICE_STOP_COMMAND "")
|
||||
endif()
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
@@ -65,6 +65,4 @@ elseif(UNIX) # Linux
|
||||
"Enable building wayland specific code." ON)
|
||||
option(SUNSHINE_ENABLE_X11
|
||||
"Enable X11 grab if available." ON)
|
||||
option(SUNSHINE_USE_LEGACY_INPUT # TODO: Remove this legacy option after the next stable release
|
||||
"Use the legacy virtual input implementation." OFF)
|
||||
endif()
|
||||
|
||||
@@ -10,14 +10,12 @@ if(APPLE)
|
||||
endif()
|
||||
elseif(UNIX)
|
||||
# configure the .desktop file
|
||||
set(SUNSHINE_DESKTOP_ICON "sunshine.svg")
|
||||
set(SUNSHINE_DESKTOP_ICON "sunshine")
|
||||
if(${SUNSHINE_BUILD_APPIMAGE})
|
||||
configure_file(packaging/linux/AppImage/sunshine.desktop sunshine.desktop @ONLY)
|
||||
elseif(${SUNSHINE_BUILD_FLATPAK})
|
||||
set(SUNSHINE_DESKTOP_ICON "${PROJECT_FQDN}.svg")
|
||||
set(SUNSHINE_DESKTOP_ICON "${PROJECT_FQDN}")
|
||||
configure_file(packaging/linux/flatpak/sunshine.desktop sunshine.desktop @ONLY)
|
||||
configure_file(packaging/linux/flatpak/sunshine_kms.desktop sunshine_kms.desktop @ONLY)
|
||||
configure_file(packaging/linux/sunshine_terminal.desktop sunshine_terminal.desktop @ONLY)
|
||||
configure_file(packaging/linux/flatpak/${PROJECT_FQDN}.metainfo.xml
|
||||
${PROJECT_FQDN}.metainfo.xml @ONLY)
|
||||
else()
|
||||
|
||||
@@ -49,6 +49,7 @@ INPUT = ../README.md \
|
||||
legal.md \
|
||||
configuration.md \
|
||||
app_examples.md \
|
||||
awesome_sunshine.md \
|
||||
guides.md \
|
||||
performance_tuning.md \
|
||||
api.md \
|
||||
|
||||
38
docs/api.md
38
docs/api.md
@@ -12,17 +12,23 @@ basic authentication with the admin username and password.
|
||||
## GET /api/apps
|
||||
@copydoc confighttp::getApps()
|
||||
|
||||
## GET /api/logs
|
||||
@copydoc confighttp::getLogs()
|
||||
|
||||
## POST /api/apps
|
||||
@copydoc confighttp::saveApp()
|
||||
|
||||
## POST /api/apps/close
|
||||
@copydoc confighttp::closeApp()
|
||||
|
||||
## DELETE /api/apps/{index}
|
||||
@copydoc confighttp::deleteApp()
|
||||
|
||||
## POST /api/covers/upload
|
||||
@copydoc confighttp::uploadCover()
|
||||
## GET /api/clients/list
|
||||
@copydoc confighttp::getClients()
|
||||
|
||||
## POST /api/clients/unpair
|
||||
@copydoc confighttp::unpair()
|
||||
|
||||
## POST /api/clients/unpair-all
|
||||
@copydoc confighttp::unpairAll()
|
||||
|
||||
## GET /api/config
|
||||
@copydoc confighttp::getConfig()
|
||||
@@ -33,11 +39,11 @@ basic authentication with the admin username and password.
|
||||
## POST /api/config
|
||||
@copydoc confighttp::saveConfig()
|
||||
|
||||
## POST /api/restart
|
||||
@copydoc confighttp::restart()
|
||||
## POST /api/covers/upload
|
||||
@copydoc confighttp::uploadCover()
|
||||
|
||||
## POST /api/reset-display-device-persistence
|
||||
@copydoc confighttp::resetDisplayDevicePersistence()
|
||||
## GET /api/logs
|
||||
@copydoc confighttp::getLogs()
|
||||
|
||||
## POST /api/password
|
||||
@copydoc confighttp::savePassword()
|
||||
@@ -45,17 +51,11 @@ basic authentication with the admin username and password.
|
||||
## POST /api/pin
|
||||
@copydoc confighttp::savePin()
|
||||
|
||||
## POST /api/clients/unpair-all
|
||||
@copydoc confighttp::unpairAll()
|
||||
## POST /api/reset-display-device-persistence
|
||||
@copydoc confighttp::resetDisplayDevicePersistence()
|
||||
|
||||
## POST /api/clients/unpair
|
||||
@copydoc confighttp::unpair()
|
||||
|
||||
## GET /api/clients/list
|
||||
@copydoc confighttp::listClients()
|
||||
|
||||
## POST /api/apps/close
|
||||
@copydoc confighttp::closeApp()
|
||||
## POST /api/restart
|
||||
@copydoc confighttp::restart()
|
||||
|
||||
<div class="section_buttons">
|
||||
|
||||
|
||||
@@ -23,25 +23,28 @@ process is killed.}
|
||||
|
||||
@tabs{
|
||||
@tab{Linux | <!-- -->
|
||||
\| Field \| Value \|
|
||||
\|-------------------\|-----------------------------------------------------\|
|
||||
\| Application Name \| @code{}Steam Big Picture@endcode \|
|
||||
\| Detached Commands \| @code{}setsid steam steam://open/bigpicture@endcode \|
|
||||
\| Image \| @code{}steam.png@endcode \|
|
||||
\| Field \| Value \|
|
||||
\|------------------------------\|------------------------------------------------------\|
|
||||
\| Application Name \| @code{}Steam Big Picture@endcode \|
|
||||
\| Command Preporations -> Undo \| @code{}setsid steam steam://close/bigpicture@endcode \|
|
||||
\| Detached Commands \| @code{}setsid steam steam://open/bigpicture@endcode \|
|
||||
\| Image \| @code{}steam.png@endcode \|
|
||||
}
|
||||
@tab{macOS | <!-- -->
|
||||
\| Field \| Value \|
|
||||
\|-------------------\|---------------------------------------------------\|
|
||||
\| Application Name \| @code{}Steam Big Picture@endcode \|
|
||||
\| Detached Commands \| @code{}open steam steam://open/bigpicture@endcode \|
|
||||
\| Image \| @code{}steam.png@endcode \|
|
||||
\| Field \| Value \|
|
||||
\|------------------------------\|------------------------------------------------\|
|
||||
\| Application Name \| @code{}Steam Big Picture@endcode \|
|
||||
\| Command Preporations -> Undo \| @code{}open steam://close/bigpicture@endcode \|
|
||||
\| Detached Commands \| @code{}open steam://open/bigpicture@endcode \|
|
||||
\| Image \| @code{}steam.png@endcode \|
|
||||
}
|
||||
@tab{Windows | <!-- -->
|
||||
\| Field \| Value \|
|
||||
\|-------------------\|----------------------------------------\|
|
||||
\| Application Name \| @code{}Steam Big Picture@endcode \|
|
||||
\| Detached Commands \| @code{}steam://open/bigpicture@endcode \|
|
||||
\| Image \| @code{}steam.png@endcode \|
|
||||
\| Field \| Value \|
|
||||
\|------------------------------\|-------------------------------------------\|
|
||||
\| Application Name \| @code{}Steam Big Picture@endcode \|
|
||||
\| Command Preporations -> Undo \| @code{}steam://close/bigpicture@endcode \|
|
||||
\| Detached Commands \| @code{}steam://open/bigpicture@endcode \|
|
||||
\| Image \| @code{}steam.png@endcode \|
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +213,7 @@ xrandr --output ${display_output} --primary --mode ${mode_alias} --pos 0x0 --rot
|
||||
```
|
||||
}
|
||||
|
||||
###### Wayland
|
||||
###### Wayland (wlroots, e.g. hyprland)
|
||||
|
||||
| Prep Step | Command |
|
||||
|-----------|------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
@@ -219,17 +222,30 @@ xrandr --output ${display_output} --primary --mode ${mode_alias} --pos 0x0 --rot
|
||||
|
||||
@hint{`wlr-xrandr` only works with wlroots-based compositors.}
|
||||
|
||||
###### Gnome (Wayland, X11)
|
||||
###### Gnome (X11)
|
||||
|
||||
| Prep Step | Command |
|
||||
|-----------|---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Do | @code{}sh -c "xrandr --output HDMI-1 --mode ${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT} --rate ${SUNSHINE_CLIENT_FPS}"@endcode |
|
||||
| Undo | @code{}xrandr --output HDMI-1 --mode 3840x2160 --rate 120@endcode |
|
||||
|
||||
The commands above are valid for an X11 session but won't work for
|
||||
Wayland. In that case `xrandr` must be replaced by [gnome-randr.py](https://gitlab.com/Oschowa/gnome-randr).
|
||||
This script is intended as a drop-in replacement with the same syntax. (It can be saved in
|
||||
`/usr/local/bin` and needs to be made executable.)
|
||||
###### Gnome (Wayland)
|
||||
|
||||
| Prep Step | Command |
|
||||
|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Do | @code{}sh -c "displayconfig-mutter set --connector HDMI-1 --resolution ${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT} --refresh-rate ${SUNSHINE_CLIENT_FPS} --hdr ${SUNSHINE_CLIENT_HDR}"@endcode |
|
||||
| Undo | @code{}displayconfig-mutter set --connector HDMI-1 --resolution 3840x2160 --refresh-rate 120 --hdr false@endcode |
|
||||
|
||||
Installation instructions for displayconfig-mutter can be [found here](https://github.com/eaglesemanation/displayconfig-mutter). Alternatives include
|
||||
[gnome-randr-rust](https://github.com/maxwellainatchi/gnome-randr-rust) and [gnome-randr.py](https://gitlab.com/Oschowa/gnome-randr), but both of those are
|
||||
unmaintained and do not support newer Mutter features such as HDR and VRR.
|
||||
|
||||
@hint{HDR support has been added to Gnome 48, to check if your display supports it you can run this:
|
||||
```
|
||||
displayconfig-mutter list
|
||||
```
|
||||
If it doesn't, then remove ``--hdr`` flag from both ``Do`` and ``Undo`` steps.
|
||||
}
|
||||
|
||||
###### KDE Plasma (Wayland, X11)
|
||||
|
||||
@@ -301,22 +317,19 @@ administrative privileges. Simply enable the elevated option in the WEB UI, or a
|
||||
This is an option for both prep-cmd and regular commands and will launch the process with the current user without a
|
||||
UAC prompt.
|
||||
|
||||
@note{It is important to write the values "true" and "false" as string values, not as the typical true/false
|
||||
values in most JSON.}
|
||||
|
||||
**Example**
|
||||
```json
|
||||
{
|
||||
"name": "Game With AntiCheat that Requires Admin",
|
||||
"output": "",
|
||||
"cmd": "ping 127.0.0.1",
|
||||
"exclude-global-prep-cmd": "false",
|
||||
"elevated": "true",
|
||||
"exclude-global-prep-cmd": false,
|
||||
"elevated": true,
|
||||
"prep-cmd": [
|
||||
{
|
||||
"do": "powershell.exe -command \"Start-Streaming\"",
|
||||
"undo": "powershell.exe -command \"Stop-Streaming\"",
|
||||
"elevated": "false"
|
||||
"elevated": false
|
||||
}
|
||||
],
|
||||
"image-path": ""
|
||||
@@ -325,9 +338,9 @@ values in most JSON.}
|
||||
|
||||
<div class="section_buttons">
|
||||
|
||||
| Previous | Next |
|
||||
|:----------------------------------|--------------------:|
|
||||
| [Configuration](configuration.md) | [Guides](guides.md) |
|
||||
| Previous | Next |
|
||||
|:----------------------------------|----------------------------------------:|
|
||||
| [Configuration](configuration.md) | [Awesome-Sunshine](awesome_sunshine.md) |
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
23
docs/awesome_sunshine.md
Normal file
23
docs/awesome_sunshine.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Awesome-Sunshine
|
||||
|
||||
@htmlonly
|
||||
<script type="module" src="https://md-block.verou.me/md-block.js"></script>
|
||||
<md-block
|
||||
hlinks=""
|
||||
hmin="2"
|
||||
src="https://raw.githubusercontent.com/LizardByte/awesome-sunshine/master/README.md">
|
||||
</md-block>
|
||||
@endhtmlonly
|
||||
|
||||
<div class="section_buttons">
|
||||
|
||||
| Previous | Next |
|
||||
|:--------------------------------|--------------------:|
|
||||
| [App Examples](app_examples.md) | [Guides](guides.md) |
|
||||
|
||||
</div>
|
||||
|
||||
<details style="display: none;">
|
||||
<summary></summary>
|
||||
[TOC]
|
||||
</details>
|
||||
@@ -92,7 +92,6 @@ dependencies=(
|
||||
"mingw-w64-ucrt-x86_64-graphviz" # Optional, for docs
|
||||
"mingw-w64-ucrt-x86_64-MinHook"
|
||||
"mingw-w64-ucrt-x86_64-miniupnpc"
|
||||
"mingw-w64-ucrt-x86_64-nlohmann-json"
|
||||
"mingw-w64-ucrt-x86_64-nodejs"
|
||||
"mingw-w64-ucrt-x86_64-nsis"
|
||||
"mingw-w64-ucrt-x86_64-onevpl"
|
||||
|
||||
@@ -225,7 +225,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
|
||||
<tr>
|
||||
<td>Example</td>
|
||||
<td colspan="2">@code{}
|
||||
global_prep_cmd = [{"do":"nircmd.exe setdisplay 1280 720 32 144","undo":"nircmd.exe setdisplay 2560 1440 32 144"}]
|
||||
global_prep_cmd = [{"do":"nircmd.exe setdisplay 1280 720 32 144","elevated":true,"undo":"nircmd.exe setdisplay 2560 1440 32 144"}]
|
||||
@endcode</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -770,6 +770,29 @@ editing the `conf` file in a text editor. Use the examples as reference.
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### stream_audio
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td colspan="2">
|
||||
Whether to stream audio or not. Disabling this can be useful for streaming headless displays as second monitors.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Default</td>
|
||||
<td colspan="2">@code{}
|
||||
enabled
|
||||
@endcode</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Example</td>
|
||||
<td colspan="2">@code{}
|
||||
stream_audio = disabled
|
||||
@endcode</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### install_steam_audio_drivers
|
||||
|
||||
<table>
|
||||
@@ -974,7 +997,9 @@ editing the `conf` file in a text editor. Use the examples as reference.
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Default</td>
|
||||
<td colspan="2">@code{}verify_only@endcode</td>
|
||||
<td colspan="2">@code{}
|
||||
disabled
|
||||
@endcode</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Example</td>
|
||||
@@ -1154,14 +1179,15 @@ editing the `conf` file in a text editor. Use the examples as reference.
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### dd_wa_hdr_toggle
|
||||
### dd_wa_hdr_toggle_delay
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td colspan="2">
|
||||
When using virtual display device as for streaming, it might display incorrect (high-contrast) color.
|
||||
With this option enabled, Sunshine will try to mitigate this issue.
|
||||
When using virtual display device (VDD) for streaming, it might incorrectly display HDR color. Sunshine can try to mitigate this issue, by turning HDR off and then on again.<br>
|
||||
If the value is set to 0, the workaround is disabled (default). If the value is between 0 and 3000 milliseconds, Sunshine will turn off HDR, wait for the specified amount of time and then turn HDR on again. The recommended delay time is around 500 milliseconds in most cases.<br>
|
||||
DO NOT use this workaround unless you actually have issues with HDR as it directly impacts stream start time!
|
||||
@note{This option works independently of [dd_hdr_option](#dd_hdr_option)}
|
||||
@note{Applies to Windows only.}
|
||||
</td>
|
||||
@@ -1169,13 +1195,13 @@ editing the `conf` file in a text editor. Use the examples as reference.
|
||||
<tr>
|
||||
<td>Default</td>
|
||||
<td colspan="2">@code{}
|
||||
disabled
|
||||
0
|
||||
@endcode</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Example</td>
|
||||
<td colspan="2">@code{}
|
||||
dd_wa_hdr_toggle = enabled
|
||||
dd_wa_hdr_toggle_delay = 500
|
||||
@endcode</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -1203,6 +1229,31 @@ editing the `conf` file in a text editor. Use the examples as reference.
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
### dd_config_revert_on_disconnect
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td colspan="2">
|
||||
When enabled, display configuration is reverted upon disconnect of all clients instead of app close or last session termination.
|
||||
This can be useful for returning to physical usage of the host machine without closing the active app.
|
||||
@warning{Some applications may not function properly when display configuration is changed while active.}
|
||||
@note{Applies to Windows only.}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Default</td>
|
||||
<td colspan="2">@code{}disabled@endcode</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Example</td>
|
||||
<td colspan="2">@code{}
|
||||
dd_config_revert_on_disconnect = enabled
|
||||
@endcode</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### dd_mode_remapping
|
||||
|
||||
<table>
|
||||
@@ -1289,6 +1340,29 @@ editing the `conf` file in a text editor. Use the examples as reference.
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### max_bitrate
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td colspan="2">
|
||||
The maximum bitrate (in Kbps) that Sunshine will encode the stream at. If set to 0, it will always use the bitrate requested by Moonlight.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Default</td>
|
||||
<td colspan="2">@code{}
|
||||
0
|
||||
@endcode</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Example</td>
|
||||
<td colspan="2">@code{}
|
||||
max_bitrate = 5000
|
||||
@endcode</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### min_fps_factor
|
||||
|
||||
<table>
|
||||
@@ -1896,7 +1970,8 @@ editing the `conf` file in a text editor. Use the examples as reference.
|
||||
</tr>
|
||||
<tr>
|
||||
<td>wlr</td>
|
||||
<td>Capture for wlroots based Wayland compositors via DMA-BUF.
|
||||
<td>Capture for wlroots based Wayland compositors via wlr-screencopy-unstable-v1. It is possible to capture
|
||||
virtual displays in e.g. Hyprland using this method.
|
||||
@note{Applies to Linux only.}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
Read our contribution guide in our organization level
|
||||
[docs](https://docs.lizardbyte.dev/latest/developers/contributing.html).
|
||||
|
||||
## Recommended Tools
|
||||
|
||||
| Tool | Description |
|
||||
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| <a href="https://www.jetbrains.com/clion/"><img src="https://resources.jetbrains.com/storage/products/company/brand/logos/CLion_icon.svg" width="30" height="30"></a><br>CLion | Recommended IDE for C++ development. Free licenses available for open source developers through the [JetBrains Open Source Program](https://www.jetbrains.com/community/opensource/). |
|
||||
|
||||
## Project Patterns
|
||||
|
||||
### Web UI
|
||||
|
||||
@@ -30,7 +30,9 @@ See [Docker](../DOCKER_README.md) for more information.
|
||||
|
||||
CUDA is used for NVFBC capture.
|
||||
|
||||
@tip{See [CUDA GPUS](https://developer.nvidia.com/cuda-gpus) to cross-reference Compute Capability to your GPU.}
|
||||
@tip{See [CUDA GPUS](https://developer.nvidia.com/cuda-gpus) to cross-reference Compute Capability to your GPU.
|
||||
The table below applies to packages provided by LizardByte. If you use an official LizardByte package then you do not
|
||||
need to install CUDA.}
|
||||
|
||||
<table>
|
||||
<caption>CUDA Compatibility</caption>
|
||||
@@ -55,7 +57,7 @@ CUDA is used for NVFBC capture.
|
||||
<tr>
|
||||
<td rowspan="1">12.0.0</td>
|
||||
<td rowspan="2">525.60.13</td>
|
||||
<td rowspan="4">50;52;60;61;62;70;72;75;80;86;87;89;90</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>
|
||||
@@ -68,7 +70,12 @@ CUDA is used for NVFBC capture.
|
||||
<td>sunshine_{arch}.flatpak</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sunshine (copr)</td>
|
||||
<td>Sunshine (copr - Fedora 40/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>
|
||||
</table>
|
||||
|
||||
@@ -335,8 +342,6 @@ recommended for most users. No support will be provided!}
|
||||
scripts/uninstall-service.bat
|
||||
```
|
||||
|
||||
To uninstall, delete the extracted directory which contains the `sunshine.exe` file.
|
||||
|
||||
## Initial Setup
|
||||
After installation, some initial setup is required.
|
||||
|
||||
@@ -489,6 +494,7 @@ All shortcuts start with `Ctrl+Alt+Shift`, just like Moonlight.
|
||||
instead it simply starts a stream. If you removed it and would like to get it back, just add a new application with
|
||||
the name "Desktop" and "desktop.png" as the image path.
|
||||
* For the Linux flatpak you must prepend commands with `flatpak-spawn --host`.
|
||||
* If inputs (mouse, keyboard, gamepads...) aren't working after connecting, add the user running sunshine to the `input` group.
|
||||
|
||||
### HDR Support
|
||||
Streaming HDR content is officially supported on Windows hosts and experimentally supported for Linux hosts.
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# Guides
|
||||
|
||||
@admonition{Community | A collection of guides written by the community is available on our
|
||||
[blog](https://lizardbyte.com/blog).
|
||||
[blog](https://app.lizardbyte.dev/blog).
|
||||
Feel free to contribute your own tips and trips by making a PR to
|
||||
[LizardByte.github.io](https://github.com/LizardByte/LizardByte.github.io).}
|
||||
|
||||
<div class="section_buttons">
|
||||
|
||||
| Previous | Next |
|
||||
|:--------------------------------|--------------------------------------------:|
|
||||
| [App Examples](app_examples.md) | [Performance Tuning](performance_tuning.md) |
|
||||
| Previous | Next |
|
||||
|:----------------------------------------|--------------------------------------------:|
|
||||
| [Awesome-Sunshine](awesome_sunshine.md) | [Performance Tuning](performance_tuning.md) |
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -118,6 +118,16 @@ system. You may also want to enable decoders, however that is not required for S
|
||||
```
|
||||
}
|
||||
|
||||
### Input not working
|
||||
After installation, the `udev` rules need to be reloaded. Our post-install script tries to do this for you
|
||||
automatically, but if it fails you may need to restart your system.
|
||||
|
||||
If the input is still not working, you may need to add your user to the `input` group.
|
||||
|
||||
```bash
|
||||
sudo usermod -aG input $USER
|
||||
```
|
||||
|
||||
@note{Other build options are listed in the
|
||||
[meson options](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/meson_options.txt) file.}
|
||||
|
||||
|
||||
25
gh-pages-template/.readthedocs.yaml
Normal file
25
gh-pages-template/.readthedocs.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
version: 2
|
||||
|
||||
build:
|
||||
os: ubuntu-24.04
|
||||
tools:
|
||||
ruby: "3.3"
|
||||
apt_packages:
|
||||
- 7zip
|
||||
- jq
|
||||
jobs:
|
||||
install:
|
||||
- |
|
||||
mkdir -p "./tmp"
|
||||
branch="master"
|
||||
base_url="https://raw.githubusercontent.com/LizardByte/LizardByte.github.io"
|
||||
url="${base_url}/refs/heads/${branch}/scripts/readthedocs_build.sh"
|
||||
curl -sSL -o "./tmp/readthedocs_build.sh" "${url}"
|
||||
chmod +x "./tmp/readthedocs_build.sh"
|
||||
build:
|
||||
html:
|
||||
- "./tmp/readthedocs_build.sh"
|
||||
@@ -56,7 +56,7 @@ ext-js:
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon text-white">
|
||||
<img class="icon" src="https://moonlight-stream.org/images/moonlight.svg" alt="Moonlight">
|
||||
<img class="icon" src="https://moonlight-stream.org/images/moonlight.svg" alt="Moonlight"/>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<h5 class="fw-bolder mb-0">Moonlight Support</h5>
|
||||
@@ -163,7 +163,7 @@ ext-js:
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon text-white">
|
||||
<i class="fa-fw fa-2x fab fa-android"></i>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/android.svg" alt="Android"/>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<h5 class="fw-bolder mb-0">
|
||||
@@ -182,7 +182,7 @@ ext-js:
|
||||
<a href="https://play.google.com/store/apps/details?id=com.limelight" target="_blank">
|
||||
<img alt="Get it on Google Play"
|
||||
src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png"
|
||||
height="60">
|
||||
height="60"/>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
@@ -190,14 +190,14 @@ ext-js:
|
||||
<img alt="Available at Amazon Appstore"
|
||||
src="https://images-na.ssl-images-amazon.com/images/G/01/mobile-apps/devportal2/res/images/amazon-appstore-badge-english-black.png"
|
||||
height="60"
|
||||
style="padding: 10px;">
|
||||
style="padding: 10px;"/>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://f-droid.org/packages/com.limelight" target="_blank">
|
||||
<img alt="Get it on F-Droid"
|
||||
src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
height="60">
|
||||
height="60"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -210,7 +210,7 @@ ext-js:
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon text-white">
|
||||
<i class="fa-fw fa-2x fab fa-chrome"></i>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/chromewebstore.svg" alt="Chrome Web Store"/>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<h5 class="fw-bolder mb-0">
|
||||
@@ -229,7 +229,7 @@ ext-js:
|
||||
<a href="https://chrome.google.com/webstore/detail/moonlight-game-streaming/gemamigbbenahjlfnmlfdjhdnkpbkfjj" target="_blank" class="btn btn-outline-light">
|
||||
<img alt="Available in the Chrome Web Store"
|
||||
src="https://developer.chrome.com/static/docs/webstore/branding/image/206x58-chrome-web-043497a3d766e.png"
|
||||
height="30">
|
||||
height="30"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -242,7 +242,8 @@ ext-js:
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon text-white">
|
||||
<i class="fa-fw fa-2x fab fa-apple"></i>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/ios.svg" alt="iOS"/>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/appletv.svg" alt="Apple TV"/>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<h5 class="fw-bolder mb-0">
|
||||
@@ -261,14 +262,14 @@ ext-js:
|
||||
<a href="https://apps.apple.com/us/app/moonlight-game-streaming/id1000551566" target="_blank">
|
||||
<img alt="Download on the App Store"
|
||||
src="https://developer.apple.com/assets/elements/badges/download-on-the-app-store.svg"
|
||||
height="40">
|
||||
height="40"/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="pb-3">
|
||||
<a href="https://apps.apple.com/us/app/moonlight-game-streaming/id1000551566" target="_blank">
|
||||
<img alt="Download on Apple TV"
|
||||
src="https://developer.apple.com/app-store/marketing/guidelines/images/badge-download-on-apple-tv.svg"
|
||||
height="40">
|
||||
height="40"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -281,10 +282,10 @@ ext-js:
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon text-white">
|
||||
<i class="fa-fw fa-2x fab fa-linux"></i>
|
||||
<i class="fa-fw fa-2x fab fa-apple"></i>
|
||||
<i class="fa-fw fa-2x fab fa-windows"></i>
|
||||
<i class="fa-fw fa-2x fab fa-steam"></i>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/linux.svg" alt="Linux"/>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/macos.svg" alt="macOS"/>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/icons/windows.svg" alt="Windows"/>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/steam.svg" alt="Steam"/>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<h5 class="fw-bolder mb-0">
|
||||
@@ -314,7 +315,7 @@ ext-js:
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon text-white">
|
||||
<i class="fa-fw fa-2x fas fa-microchip"></i>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/raspberrypi.svg" alt="Raspberry Pi"/>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<h5 class="fw-bolder mb-0">
|
||||
@@ -344,7 +345,7 @@ ext-js:
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon text-white">
|
||||
<i class="fa-fw fa-2x fab fa-xbox"></i>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/icons/xbox.svg" alt="Xbox"/>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<h5 class="fw-bolder mb-0">
|
||||
@@ -363,7 +364,7 @@ ext-js:
|
||||
<a href="https://apps.microsoft.com/store/detail/moonlight-uwp/9MW1BS08ZBTH" target="_blank">
|
||||
<img alt="Get it from Microsoft"
|
||||
src="https://get.microsoft.com/images/en-us%20dark.svg"
|
||||
height="40">
|
||||
height="40"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -376,7 +377,7 @@ ext-js:
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon text-white">
|
||||
<i class="fa-fw fa-2x fab fa-playstation"></i>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/playstationvita.svg" alt="PlayStation Vita"/>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<h5 class="fw-bolder mb-0">
|
||||
@@ -406,12 +407,16 @@ ext-js:
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon text-white">
|
||||
<i class="fa-fw fa-2x fas fa-code"></i>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/icons/nintendo-switch.svg" alt="Nintendo Switch"/>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/android.svg" alt="Android"/>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/appletv.svg" alt="Apple TV"/>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/ios.svg" alt="iOS"/>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/macos.svg" alt="macOS"/>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<h5 class="fw-bolder mb-0">
|
||||
<a href="https://github.com/XITRIX/Moonlight-Switch" target="_blank" class="text-white text-decoration-none">
|
||||
Nintendo Switch
|
||||
Moonlight Switch
|
||||
</a>
|
||||
</h5>
|
||||
</div>
|
||||
@@ -436,7 +441,7 @@ ext-js:
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon text-white">
|
||||
<i class="fa-fw fa-2x fas fa-code"></i>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v12/icons/wiiu.svg" alt="Wii U"/>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<h5 class="fw-bolder mb-0">
|
||||
@@ -466,7 +471,7 @@ ext-js:
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon text-white">
|
||||
<i class="fa-fw fa-2x fas fa-code"></i>
|
||||
<img class="invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/lg.svg" alt="LG webOS TV"/>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<h5 class="fw-bolder mb-0">
|
||||
@@ -514,7 +519,7 @@ ext-js:
|
||||
</div>
|
||||
<div class="card-footer p-3 px-4">
|
||||
<a class="btn btn-outline-light me-3 mb-3" href="https://docs.lizardbyte.dev/projects/sunshine" target="_blank">
|
||||
<i class="fa-fw fas fa-book"></i>
|
||||
<img class="icon-sm invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/readthedocs.svg" alt="ReadTheDocs"/>
|
||||
Read the Docs
|
||||
</a>
|
||||
</div>
|
||||
@@ -537,27 +542,37 @@ ext-js:
|
||||
</div>
|
||||
<div class="card-footer p-3 px-4">
|
||||
<a class="latest-button btn btn-outline-light me-3 mb-3 d-none" href="https://github.com/LizardByte/Sunshine/releases/latest" target="_blank">
|
||||
<i class="fa-fw fab fa-github"></i>
|
||||
Latest: <span id="latest-version"></span>
|
||||
<img class="icon-sm invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/github.svg" alt="GitHub"/>
|
||||
<img class="icon-sm invert" src="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/icons/windows.svg" alt="Windows"/>
|
||||
<img class="icon-sm invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/debian.svg" alt="Debian"/>
|
||||
<img class="icon-sm invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/ubuntu.svg" alt="Ubuntu"/>
|
||||
<img class="icon-sm invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/flatpak.svg" alt="Flatpak"/>
|
||||
<img class="icon-sm invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/linux.svg" alt="AppImage"/>
|
||||
Latest: <span id="latest-version" class="crowdin-ignore"></span>
|
||||
</a>
|
||||
<a class="beta-button btn btn-outline-light me-3 mb-3 d-none" href="#" target="_blank">
|
||||
<i class="fa-fw fas fa-flask"></i>
|
||||
Beta: <span id="beta-version"></span>
|
||||
<i class="fa-fw fa-lg fas fa-flask"></i>
|
||||
<img class="icon-sm invert" src="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/icons/windows.svg" alt="Windows"/>
|
||||
<img class="icon-sm invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/debian.svg" alt="Debian"/>
|
||||
<img class="icon-sm invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/ubuntu.svg" alt="Ubuntu"/>
|
||||
<img class="icon-sm invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/flatpak.svg" alt="Flatpak"/>
|
||||
<img class="icon-sm invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/linux.svg" alt="AppImage"/>
|
||||
Beta: <span id="beta-version" class="crowdin-ignore"></span>
|
||||
</a>
|
||||
<a class="btn btn-outline-light me-3 mb-3" href="https://github.com/LizardByte/pacman-repo" target="_blank">
|
||||
<i class="fa-fw fab fa-linux"></i>
|
||||
ArchLinux
|
||||
<img class="icon-sm invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/archlinux.svg" alt="Arch Linux"/>
|
||||
Arch Linux
|
||||
</a>
|
||||
<a class="btn btn-outline-light me-3 mb-3" href="https://hub.docker.com/r/lizardbyte/sunshine" target="_blank">
|
||||
<i class="fa-fw fab fa-docker"></i>
|
||||
<img class="icon-sm invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/docker.svg" alt="Docker"/>
|
||||
Docker
|
||||
</a>
|
||||
<a class="btn btn-outline-light me-3 mb-3" href="https://flathub.org/apps/dev.lizardbyte.app.Sunshine" target="_blank">
|
||||
<i class="fa-fw fab fa-linux"></i>
|
||||
<img class="icon-sm invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/flathub.svg" alt="Flathub"/>
|
||||
Flathub
|
||||
</a>
|
||||
<a class="btn btn-outline-light me-3 mb-3" href="https://github.com/LizardByte/homebrew-homebrew" target="_blank">
|
||||
<i class="fa-fw fas fa-beer-mug-empty"></i>
|
||||
<img class="icon-sm invert" src="https://cdn.jsdelivr.net/npm/simple-icons@v14/icons/homebrew.svg" alt="Homebrew"/>
|
||||
Homebrew
|
||||
</a>
|
||||
</div>
|
||||
@@ -577,16 +592,21 @@ ext-js:
|
||||
// Filter the releases to get only the stable releases
|
||||
const stableReleases = data.filter(release => !release.prerelease);
|
||||
|
||||
const latestButton = document.querySelector('.latest-button');
|
||||
const latestVersion = document.querySelector('#latest-version');
|
||||
const betaButton = document.querySelector('.beta-button');
|
||||
const betaVersion = document.querySelector('#beta-version');
|
||||
|
||||
// If there are no stable releases, hide the latest download button
|
||||
if (stableReleases.length === 0) {
|
||||
document.querySelector('.latest-button').classList.add('d-none');
|
||||
latestButton.classList.add('d-none');
|
||||
} else {
|
||||
// Show the latest download button
|
||||
document.querySelector('.latest-button').classList.remove('d-none');
|
||||
latestButton.classList.remove('d-none');
|
||||
|
||||
// Get the latest stable release
|
||||
const latestStableRelease = stableReleases[0];
|
||||
document.querySelector('#latest-version').textContent = latestStableRelease.tag_name;
|
||||
latestVersion.textContent = latestStableRelease.tag_name;
|
||||
|
||||
// If there is a pre-release, update the href attribute of the anchor tag
|
||||
if (preReleases.length > 0) {
|
||||
@@ -598,16 +618,16 @@ ext-js:
|
||||
|
||||
// If the pre-release is newer, update the href attribute of the anchor tag
|
||||
if (preReleaseDate > stableReleaseDate) {
|
||||
document.querySelector('.beta-button').href = latestPreRelease.html_url;
|
||||
document.querySelector('#beta-version').textContent = latestPreRelease.tag_name;
|
||||
document.querySelector('.beta-button').classList.remove('d-none');
|
||||
betaButton.href = latestPreRelease.html_url;
|
||||
betaVersion.textContent = latestPreRelease.tag_name;
|
||||
betaButton.classList.remove('d-none');
|
||||
} else {
|
||||
// If the pre-release is older, hide the button
|
||||
document.querySelector('.beta-button').classList.add('d-none');
|
||||
betaButton.classList.add('d-none');
|
||||
}
|
||||
} else {
|
||||
// If there is no pre-release, hide the button
|
||||
document.querySelector('.beta-button').classList.add('d-none');
|
||||
betaButton.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,14 +8,15 @@
|
||||
"serve": "serve ./tests/fixtures/http --no-port-switching"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lizardbyte/shared-web": "2024.921.191855",
|
||||
"@lizardbyte/shared-web": "2025.326.11214",
|
||||
"vue": "3.5.13",
|
||||
"vue-i18n": "11.0.1"
|
||||
"vue-i18n": "11.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@codecov/vite-plugin": "1.9.0",
|
||||
"@vitejs/plugin-vue": "4.6.2",
|
||||
"serve": "14.2.3",
|
||||
"vite": "4.5.2",
|
||||
"vite": "4.5.9",
|
||||
"vite-plugin-ejs": "1.6.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ Source0: tarball.tar.gz
|
||||
|
||||
# BuildRequires: boost-devel >= 1.86.0
|
||||
BuildRequires: cmake >= 3.25.0
|
||||
BuildRequires: gcc
|
||||
BuildRequires: gcc-c++
|
||||
BuildRequires: libayatana-appindicator3-devel
|
||||
BuildRequires: libcap-devel
|
||||
BuildRequires: libcurl-devel
|
||||
@@ -37,6 +35,7 @@ BuildRequires: libXrandr-devel
|
||||
BuildRequires: libXtst-devel
|
||||
BuildRequires: git
|
||||
BuildRequires: mesa-libGL-devel
|
||||
BuildRequires: mesa-libgbm-devel
|
||||
BuildRequires: miniupnpc-devel
|
||||
BuildRequires: npm
|
||||
BuildRequires: numactl-devel
|
||||
@@ -54,11 +53,22 @@ BuildRequires: which
|
||||
BuildRequires: xorg-x11-server-Xvfb
|
||||
|
||||
# Conditional BuildRequires for cuda-gcc based on Fedora version
|
||||
%if 0%{?fedora} >= 40
|
||||
# this package conflicts with gcc on f39
|
||||
BuildRequires: cuda-gcc-c++
|
||||
%if 0%{?fedora} >= 40 && 0%{?fedora} <= 41
|
||||
BuildRequires: gcc13
|
||||
BuildRequires: gcc13-c++
|
||||
%global gcc_version 13
|
||||
%global cuda_version 12.6.3
|
||||
%global cuda_build 560.35.05
|
||||
%elif %{?fedora} >= 42
|
||||
BuildRequires: gcc14
|
||||
BuildRequires: gcc14-c++
|
||||
%global gcc_version 14
|
||||
%global cuda_version 12.8.1
|
||||
%global cuda_build 570.124.06
|
||||
%endif
|
||||
|
||||
%global cuda_dir %{_builddir}/cuda
|
||||
|
||||
Requires: libcap >= 2.22
|
||||
Requires: libcurl >= 7.0
|
||||
Requires: libdrm > 2.4.97
|
||||
@@ -88,20 +98,14 @@ ls -a %{_builddir}/Sunshine
|
||||
%autopatch -p1
|
||||
|
||||
%build
|
||||
# exit on error
|
||||
set -e
|
||||
|
||||
# Detect the architecture and Fedora version
|
||||
architecture=$(uname -m)
|
||||
fedora_version=%{fedora}
|
||||
|
||||
cuda_supported_architectures=("x86_64" "aarch64")
|
||||
|
||||
# set cuda_version based on Fedora version
|
||||
case "$fedora_version" in
|
||||
*)
|
||||
cuda_version="12.6.3"
|
||||
cuda_build="560.35.05"
|
||||
;;
|
||||
esac
|
||||
|
||||
# prepare CMAKE args
|
||||
cmake_args=(
|
||||
"-B=%{_builddir}/Sunshine/build"
|
||||
@@ -121,27 +125,23 @@ cmake_args=(
|
||||
"-DSUNSHINE_PUBLISHER_ISSUE_URL=https://app.lizardbyte.dev/support"
|
||||
)
|
||||
|
||||
export CC=gcc-%{gcc_version}
|
||||
export CXX=g++-%{gcc_version}
|
||||
|
||||
function install_cuda() {
|
||||
# check if we need to install cuda
|
||||
if [ -f "%{_builddir}/cuda/bin/nvcc" ]; then
|
||||
if [ -f "%{cuda_dir}/bin/nvcc" ]; then
|
||||
echo "cuda already installed"
|
||||
return
|
||||
fi
|
||||
|
||||
if [ "$fedora_version" -ge 40 ]; then
|
||||
# update environment variables for CUDA, necessary when using cuda-gcc-c++
|
||||
export NVCC_PREPEND_FLAGS='-ccbin /usr/bin/g++-13'
|
||||
export PATH=/usr/bin/cuda:"%{_builddir}/cuda/bin:${PATH}"
|
||||
export LD_LIBRARY_PATH="%{_builddir}/cuda/lib64:${LD_LIBRARY_PATH}"
|
||||
fi
|
||||
|
||||
local cuda_prefix="https://developer.download.nvidia.com/compute/cuda/"
|
||||
local cuda_suffix=""
|
||||
if [ "$architecture" == "aarch64" ]; then
|
||||
local cuda_suffix="_sbsa"
|
||||
fi
|
||||
|
||||
local url="${cuda_prefix}${cuda_version}/local_installers/cuda_${cuda_version}_${cuda_build}_linux${cuda_suffix}.run"
|
||||
local url="${cuda_prefix}%{cuda_version}/local_installers/cuda_%{cuda_version}_%{cuda_build}_linux${cuda_suffix}.run"
|
||||
echo "cuda url: ${url}"
|
||||
wget \
|
||||
"$url" \
|
||||
@@ -157,23 +157,31 @@ function install_cuda() {
|
||||
--override \
|
||||
--silent \
|
||||
--toolkit \
|
||||
--toolkitpath="%{_builddir}/cuda"
|
||||
--toolkitpath="%{cuda_dir}"
|
||||
rm "%{_builddir}/cuda.run"
|
||||
|
||||
# we need to patch math_functions.h on fedora 42
|
||||
# see https://forums.developer.nvidia.com/t/error-exception-specification-is-incompatible-for-cospi-sinpi-cospif-sinpif-with-glibc-2-41/323591/3
|
||||
if [ "%{?fedora}" -eq 42 ]; then
|
||||
echo "Original math_functions.h:"
|
||||
find "%{cuda_dir}" -name math_functions.h -exec cat {} \;
|
||||
|
||||
# Apply the patch
|
||||
patch -p2 \
|
||||
--backup \
|
||||
--directory="%{cuda_dir}" \
|
||||
--verbose \
|
||||
< "%{_builddir}/Sunshine/packaging/linux/fedora/patches/f42/${architecture}/01-math_functions.patch"
|
||||
fi
|
||||
}
|
||||
|
||||
# we need to clear these flags to avoid linkage errors with cuda-gcc-c++
|
||||
export CFLAGS=""
|
||||
export CXXFLAGS=""
|
||||
export FFLAGS=""
|
||||
export FCFLAGS=""
|
||||
export LDFLAGS=""
|
||||
export CC=gcc-13
|
||||
export CXX=g++-13
|
||||
|
||||
if [ -n "$cuda_version" ] && [[ " ${cuda_supported_architectures[@]} " =~ " ${architecture} " ]]; then
|
||||
if [ -n "%{cuda_version}" ] && [[ " ${cuda_supported_architectures[@]} " =~ " ${architecture} " ]]; then
|
||||
install_cuda
|
||||
cmake_args+=("-DSUNSHINE_ENABLE_CUDA=ON")
|
||||
cmake_args+=("-DCMAKE_CUDA_COMPILER:PATH=%{_builddir}/cuda/bin/nvcc")
|
||||
cmake_args+=("-DCMAKE_CUDA_COMPILER:PATH=%{cuda_dir}/bin/nvcc")
|
||||
cmake_args+=("-DCMAKE_CUDA_HOST_COMPILER=gcc-%{gcc_version}")
|
||||
else
|
||||
cmake_args+=("-DSUNSHINE_ENABLE_CUDA=OFF")
|
||||
fi
|
||||
|
||||
# setup the version
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
diff '--color=auto' -ur a/cuda/targets/sbsa-linux/include/crt/math_functions.h b/cuda/targets/sbsa-linux/include/crt/math_functions.h
|
||||
--- a/cuda/targets/sbsa-linux/include/crt/math_functions.h 2024-08-23 00:25:39.000000000 +0200
|
||||
+++ b/cuda/targets/sbsa-linux/include/crt/math_functions.h 2025-02-17 01:19:44.270292640 +0100
|
||||
@@ -2553,7 +2553,7 @@
|
||||
*
|
||||
* \note_accuracy_double
|
||||
*/
|
||||
-extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ double sinpi(double x);
|
||||
+extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ double sinpi(double x) noexcept (true);
|
||||
/**
|
||||
* \ingroup CUDA_MATH_SINGLE
|
||||
* \brief Calculate the sine of the input argument
|
||||
@@ -2576,7 +2576,7 @@
|
||||
*
|
||||
* \note_accuracy_single
|
||||
*/
|
||||
-extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ float sinpif(float x);
|
||||
+extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ float sinpif(float x) noexcept (true);
|
||||
/**
|
||||
* \ingroup CUDA_MATH_DOUBLE
|
||||
* \brief Calculate the cosine of the input argument
|
||||
@@ -2598,7 +2598,7 @@
|
||||
*
|
||||
* \note_accuracy_double
|
||||
*/
|
||||
-extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ double cospi(double x);
|
||||
+extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ double cospi(double x) noexcept (true);
|
||||
/**
|
||||
* \ingroup CUDA_MATH_SINGLE
|
||||
* \brief Calculate the cosine of the input argument
|
||||
@@ -2620,7 +2620,7 @@
|
||||
*
|
||||
* \note_accuracy_single
|
||||
*/
|
||||
-extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ float cospif(float x);
|
||||
+extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ float cospif(float x) noexcept (true);
|
||||
/**
|
||||
* \ingroup CUDA_MATH_DOUBLE
|
||||
* \brief Calculate the sine and cosine of the first input argument
|
||||
@@ -0,0 +1,39 @@
|
||||
diff '--color=auto' -ur a/cuda/targets/x86_64-linux/include/crt/math_functions.h b/cuda/targets/x86_64-linux/include/crt/math_functions.h
|
||||
--- a/cuda/targets/x86_64-linux/include/crt/math_functions.h 2024-08-23 00:25:39.000000000 +0200
|
||||
+++ b/cuda/targets/x86_64-linux/include/crt/math_functions.h 2025-02-17 01:19:44.270292640 +0100
|
||||
@@ -2553,7 +2553,7 @@
|
||||
*
|
||||
* \note_accuracy_double
|
||||
*/
|
||||
-extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ double sinpi(double x);
|
||||
+extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ double sinpi(double x) noexcept (true);
|
||||
/**
|
||||
* \ingroup CUDA_MATH_SINGLE
|
||||
* \brief Calculate the sine of the input argument
|
||||
@@ -2576,7 +2576,7 @@
|
||||
*
|
||||
* \note_accuracy_single
|
||||
*/
|
||||
-extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ float sinpif(float x);
|
||||
+extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ float sinpif(float x) noexcept (true);
|
||||
/**
|
||||
* \ingroup CUDA_MATH_DOUBLE
|
||||
* \brief Calculate the cosine of the input argument
|
||||
@@ -2598,7 +2598,7 @@
|
||||
*
|
||||
* \note_accuracy_double
|
||||
*/
|
||||
-extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ double cospi(double x);
|
||||
+extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ double cospi(double x) noexcept (true);
|
||||
/**
|
||||
* \ingroup CUDA_MATH_SINGLE
|
||||
* \brief Calculate the cosine of the input argument
|
||||
@@ -2620,7 +2620,7 @@
|
||||
*
|
||||
* \note_accuracy_single
|
||||
*/
|
||||
-extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ float cospif(float x);
|
||||
+extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ float cospif(float x) noexcept (true);
|
||||
/**
|
||||
* \ingroup CUDA_MATH_DOUBLE
|
||||
* \brief Calculate the sine and cosine of the first input argument
|
||||
@@ -1,13 +1,22 @@
|
||||
# Overview
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/LizardByte/Sunshine/master/sunshine.png" />
|
||||
<h1 align="center">Sunshine</h1>
|
||||
<h4 align="center">Self-hosted game stream host for Moonlight.</h4>
|
||||
</div>
|
||||
|
||||
[](https://flathub.org/apps/dev.lizardbyte.app.Sunshine)
|
||||
[](https://flathub.org/apps/dev.lizardbyte.app.Sunshine)
|
||||
<div align="center">
|
||||
<a href="https://flathub.org/apps/dev.lizardbyte.app.Sunshine"><img src="https://img.shields.io/flathub/downloads/dev.lizardbyte.app.Sunshine?style=for-the-badge&logo=flathub" alt="Flathub installs"></a>
|
||||
<a href="https://flathub.org/apps/dev.lizardbyte.app.Sunshine"><img src="https://img.shields.io/flathub/v/dev.lizardbyte.app.Sunshine?style=for-the-badge&logo=flathub" alt="Flathub Version"></a>
|
||||
</div>
|
||||
|
||||
LizardByte has the full documentation hosted on [Read the Docs](https://docs.lizardbyte.dev/projects/sunshine).
|
||||
|
||||
## About
|
||||
## ℹ️ About
|
||||
|
||||
Sunshine is a self-hosted game stream host for Moonlight.
|
||||
|
||||
LizardByte has the full documentation hosted on [Read the Docs](https://docs.lizardbyte.dev/projects/sunshine)
|
||||
|
||||
* [Stable](https://docs.lizardbyte.dev/projects/sunshine/latest/)
|
||||
* [Beta](https://docs.lizardbyte.dev/projects/sunshine/master/)
|
||||
|
||||
This repo is synced from the upstream [Sunshine](https://github.com/LizardByte/Sunshine) repo.
|
||||
Please report issues and contribute to the upstream repo.
|
||||
|
||||
Submodule packaging/linux/flatpak/deps/flatpak-builder-tools updated: a1eb29c5f3...bf91cb0bee
Submodule packaging/linux/flatpak/deps/shared-modules updated: f5d368a31d...1f8e591b26
@@ -29,14 +29,18 @@
|
||||
</p>
|
||||
|
||||
<p>NOTE: Sunshine requires additional installation steps.</p>
|
||||
<p>flatpak run --command=additional-install.sh @PROJECT_FQDN@</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>sudo -i PULSE_SERVER=unix:/run/user/$(id -u $whoami)/pulse/native flatpak run @PROJECT_FQDN@</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="1970-01-01"></release>
|
||||
<release version="@PROJECT_VERSION@" date="1990-01-01"></release>
|
||||
</releases>
|
||||
|
||||
<developer_name>LizardByte</developer_name>
|
||||
|
||||
8
packaging/linux/flatpak/exceptions.json
Normal file
8
packaging/linux/flatpak/exceptions.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"dev.lizardbyte.app.Sunshine": [
|
||||
"appstream-missing-screenshots",
|
||||
"appstream-screenshots-not-mirrored-in-ostree",
|
||||
"external-gitmodule-url-found",
|
||||
"finish-args-flatpak-spawn-access"
|
||||
]
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"errors": [
|
||||
"finish-args-flatpak-spawn-access"
|
||||
],
|
||||
"info": [
|
||||
"finish-args-flatpak-spawn-access: finish-args has a talk-name access for org.freedesktop.Flatpak"
|
||||
],
|
||||
"message": "Please consult the documentation at https://docs.flathub.org/docs/for-app-authors/linter"
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"errors": [
|
||||
"appstream-missing-screenshots",
|
||||
"finish-args-flatpak-spawn-access"
|
||||
],
|
||||
"info": [
|
||||
"appstream-missing-screenshots: Catalogue file has no screenshots. Please check if screenshot URLs are reachable",
|
||||
"finish-args-flatpak-spawn-access: finish-args has a talk-name access for org.freedesktop.Flatpak"
|
||||
],
|
||||
"message": "Please consult the documentation at https://docs.flathub.org/docs/for-app-authors/linter"
|
||||
}
|
||||
@@ -9,8 +9,8 @@
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/boostorg/boost/releases/download/boost-1.86.0/boost-1.86.0-cmake.tar.xz",
|
||||
"sha256": "2c5ec5edcdff47ff55e27ed9560b0a0b94b07bd07ed9928b476150e16b0efc57"
|
||||
"url": "https://github.com/boostorg/boost/releases/download/boost-1.87.0/boost-1.87.0-cmake.tar.xz",
|
||||
"sha256": "7da75f171837577a52bbf217e17f8ea576c7c246e4594d617bfde7fafd408be5"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,14 +11,15 @@
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://gitlab.freedesktop.org/xorg/xserver/-/archive/xorg-server-21.1.13/xserver-xorg-server-21.1.13.tar.bz2",
|
||||
"sha256": "ee2bf6d65f4b111ce86ca817c3327dc1e70d9c958aa16876f2820caf7bf7cffa",
|
||||
"type": "git",
|
||||
"url": "https://github.com/LizardByte-infrastructure/xserver.git",
|
||||
"tag": "xorg-server-21.1.13",
|
||||
"commit": "be2767845d6ed3c6dbd25a151051294d0908a995",
|
||||
"x-checker-data": {
|
||||
"type": "anitya",
|
||||
"project-id": 5250,
|
||||
"stable-only": true,
|
||||
"url-template": "https://gitlab.freedesktop.org/xorg/xserver/-/archive/xorg-server-$version/xserver-xorg-server-$version.tar.bz2"
|
||||
"tag-template": "xorg-server-$version"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -32,14 +33,15 @@
|
||||
"buildsystem": "meson",
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://gitlab.freedesktop.org/xorg/lib/libxcvt/-/archive/libxcvt-0.1.2/libxcvt-libxcvt-0.1.2.tar.bz2",
|
||||
"sha256": "590e5a6da87ace7aa7857026b207a2c4d378620035441e20ea97efedd15d6d4a",
|
||||
"type": "git",
|
||||
"url": "https://github.com/LizardByte-infrastructure/libxcvt.git",
|
||||
"tag": "libxcvt-0.1.2",
|
||||
"commit": "d9ca87eea9eecddaccc3a77227bcb3acf84e89df",
|
||||
"x-checker-data": {
|
||||
"type": "anitya",
|
||||
"project-id": 235147,
|
||||
"stable-only": true,
|
||||
"url-template": "https://gitlab.freedesktop.org/xorg/lib/libxcvt/-/archive/libxcvt-$version/libxcvt-libxcvt-$version.tar.bz2"
|
||||
"tag-template": "libxcvt-$version"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -48,14 +50,32 @@
|
||||
"name": "libXmu",
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://xorg.freedesktop.org/archive/individual/lib/libXmu-1.2.1.tar.gz",
|
||||
"sha256": "bf0902583dd1123856c11e0a5085bd3c6e9886fbbd44954464975fd7d52eb599",
|
||||
"type": "git",
|
||||
"url": "https://github.com/LizardByte-infrastructure/libxmu.git",
|
||||
"tag": "libXmu-1.2.1",
|
||||
"commit": "792f80402ee06ce69bca3a8f2a84295999c3a170",
|
||||
"x-checker-data": {
|
||||
"type": "anitya",
|
||||
"project-id": 1785,
|
||||
"stable-only": true,
|
||||
"url-template": "https://xorg.freedesktop.org/archive/individual/lib/libXmu-$version.tar.gz"
|
||||
"tag-template": "libXmu-$version"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "font-util",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/LizardByte-infrastructure/font-util.git",
|
||||
"tag": "font-util-1.4.1",
|
||||
"commit": "b5ca142f81a6f14eddb23be050291d1c25514777",
|
||||
"x-checker-data": {
|
||||
"type": "anitya",
|
||||
"project-id": 15055,
|
||||
"stable-only": true,
|
||||
"tag-template": "font-util-$version"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -64,14 +84,15 @@
|
||||
"name": "libfontenc",
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://xorg.freedesktop.org/archive/individual/lib/libfontenc-1.1.8.tar.xz",
|
||||
"sha256": "7b02c3d405236e0d86806b1de9d6868fe60c313628b38350b032914aa4fd14c6",
|
||||
"type": "git",
|
||||
"url": "https://github.com/LizardByte-infrastructure/libfontenc.git",
|
||||
"tag": "libfontenc-1.1.8",
|
||||
"commit": "92a85fda2acb4e14ec0b2f6d8fe3eaf2b687218c",
|
||||
"x-checker-data": {
|
||||
"type": "anitya",
|
||||
"project-id": 1613,
|
||||
"stable-only": true,
|
||||
"url-template": "https://xorg.freedesktop.org/archive/individual/lib/libfontenc-$version.tar.xz"
|
||||
"tag-template": "libfontenc-$version"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -95,34 +116,19 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "font-util",
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://xorg.freedesktop.org/archive/individual/font/font-util-1.4.1.tar.gz",
|
||||
"sha256": "f029ae80cdd75d89bee7f7af61c21e07982adfb9f72344a158b99f91f77ef5ed",
|
||||
"x-checker-data": {
|
||||
"type": "anitya",
|
||||
"project-id": 15055,
|
||||
"stable-only": true,
|
||||
"url-template": "https://xorg.freedesktop.org/archive/individual/font/font-util-$version.tar.gz"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "xvfb-libXfont2",
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://xorg.freedesktop.org/archive/individual/lib/libXfont2-2.0.6.tar.gz",
|
||||
"sha256": "a944df7b6837c8fa2067f6a5fc25d89b0acc4011cd0bc085106a03557fb502fc",
|
||||
"type": "git",
|
||||
"url": "https://github.com/LizardByte-infrastructure/libxfont.git",
|
||||
"tag": "libXfont2-2.0.6",
|
||||
"commit": "d54aaf2483df6a1f98fadc09004157e657b7f73e",
|
||||
"x-checker-data": {
|
||||
"type": "anitya",
|
||||
"project-id": 17165,
|
||||
"stable-only": true,
|
||||
"url-template": "https://xorg.freedesktop.org/archive/individual/lib/libXfont2-$version.tar.gz"
|
||||
"tag-template": "libXfont2-$version"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -131,14 +137,15 @@
|
||||
"name": "xvfb-xauth",
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://gitlab.freedesktop.org/xorg/app/xauth/-/archive/xauth-1.1.1/xauth-xauth-1.1.3.tar.bz2",
|
||||
"sha256": "3cee16ebe9de0e85c62513f6d6353710407c8ebb1f855b18d03807c27d38a215",
|
||||
"type": "git",
|
||||
"url": "https://github.com/LizardByte-infrastructure/xauth.git",
|
||||
"tag": "xauth-1.1.3",
|
||||
"commit": "c29eef23683f0e3575a3c60d9314de8156fbe2c2",
|
||||
"x-checker-data": {
|
||||
"type": "anitya",
|
||||
"project-id": 5253,
|
||||
"stable-only": true,
|
||||
"url-template": "https://gitlab.freedesktop.org/xorg/app/xauth/-/archive/xauth-1.1.1/xauth-xauth-$version.tar.bz2"
|
||||
"tag-template": "xauth-$version"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -8,4 +8,4 @@ echo Sunshine User Service has been removed.
|
||||
|
||||
# Udev rule
|
||||
flatpak-spawn --host pkexec sh -c "rm /etc/udev/rules.d/60-sunshine.rules"
|
||||
echo Mouse permission removed. Restart computer to take effect.
|
||||
echo Input rules removed. Restart computer to take effect.
|
||||
|
||||
11
packaging/linux/flatpak/scripts/sunshine.sh
Normal file
11
packaging/linux/flatpak/scripts/sunshine.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
PORT=47990
|
||||
|
||||
if ! curl -k https://localhost:$PORT > /dev/null 2>&1; then
|
||||
(sleep 3 && xdg-open https://localhost:$PORT) &
|
||||
exec sunshine "$@"
|
||||
else
|
||||
echo "Sunshine is already running, opening the web interface..."
|
||||
xdg-open https://localhost:$PORT
|
||||
fi
|
||||
@@ -1,20 +1,9 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=@PROJECT_NAME@
|
||||
Exec=@PROJECT_FQDN@
|
||||
Version=1.0
|
||||
Categories=AudioVideo;Network;RemoteAccess;
|
||||
Comment=@PROJECT_DESCRIPTION@
|
||||
Exec=sunshine.sh
|
||||
Icon=@SUNSHINE_DESKTOP_ICON@
|
||||
Keywords=gamestream;stream;moonlight;remote play;
|
||||
Categories=AudioVideo;Network;RemoteAccess;
|
||||
Actions=RunInTerminal;KMS;
|
||||
|
||||
[Desktop Action RunInTerminal]
|
||||
Name=Run in Terminal
|
||||
Icon=application-x-executable
|
||||
Exec=gio launch @CMAKE_INSTALL_FULL_DATAROOTDIR@/applications/@PROJECT_FQDN@_terminal.desktop
|
||||
|
||||
[Desktop Action KMS]
|
||||
Name=Run in Terminal (KMS)
|
||||
Icon=application-x-executable
|
||||
Exec=gio launch @CMAKE_INSTALL_FULL_DATAROOTDIR@/applications/@PROJECT_FQDN@_kms.desktop
|
||||
Name=@PROJECT_NAME@
|
||||
Type=Application
|
||||
Version=1.0
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=@PROJECT_NAME@ (KMS)
|
||||
Exec=sudo -i PULSE_SERVER=unix:$(pactl info | awk '/Server String/{print$3}') flatpak run @PROJECT_FQDN@
|
||||
Terminal=true
|
||||
Type=Application
|
||||
NoDisplay=true
|
||||
@@ -29,16 +29,35 @@ class @PROJECT_NAME@ < Formula
|
||||
depends_on "cmake" => :build
|
||||
depends_on "doxygen" => :build
|
||||
depends_on "graphviz" => :build
|
||||
depends_on "ninja" => :build
|
||||
depends_on "node" => :build
|
||||
depends_on "pkg-config" => :build
|
||||
depends_on "gcovr" => :test
|
||||
depends_on "curl"
|
||||
depends_on "miniupnpc"
|
||||
depends_on "openssl"
|
||||
depends_on "opus"
|
||||
depends_on "boost" => :recommended
|
||||
depends_on "icu4c" => :recommended
|
||||
|
||||
on_linux do
|
||||
# the "build" dependencies are for libayatana-appindicator
|
||||
depends_on "at-spi2-core" => :build
|
||||
depends_on "cairo" => :build
|
||||
depends_on "fontconfig" => :build
|
||||
depends_on "freetype" => :build
|
||||
depends_on "fribidi" => :build
|
||||
depends_on "gettext" => :build
|
||||
depends_on "gobject-introspection" => :build
|
||||
depends_on "graphite2" => :build
|
||||
depends_on "gtk+3" => :build
|
||||
depends_on "harfbuzz" => :build
|
||||
depends_on "intltool" => :build
|
||||
depends_on "libepoxy" => :build
|
||||
depends_on "libxdamage" => :build
|
||||
depends_on "libxkbcommon" => :build
|
||||
depends_on "pango" => :build
|
||||
depends_on "perl" => :build
|
||||
depends_on "pixman" => :build
|
||||
depends_on "avahi"
|
||||
depends_on "libcap"
|
||||
depends_on "libdrm"
|
||||
@@ -52,10 +71,133 @@ class @PROJECT_NAME@ < Formula
|
||||
depends_on "libxinerama"
|
||||
depends_on "libxrandr"
|
||||
depends_on "libxtst"
|
||||
depends_on "mesa"
|
||||
depends_on "numactl"
|
||||
depends_on "pulseaudio"
|
||||
depends_on "systemd"
|
||||
depends_on "wayland"
|
||||
|
||||
# resources that do not have brew packages
|
||||
resource "libayatana-appindicator" do
|
||||
url "https://github.com/AyatanaIndicators/libayatana-appindicator/archive/refs/tags/0.5.94.tar.gz"
|
||||
sha256 "884a6bc77994c0b58c961613ca4c4b974dc91aa0f804e70e92f38a542d0d0f90"
|
||||
end
|
||||
|
||||
resource "libdbusmenu" do
|
||||
url "https://launchpad.net/libdbusmenu/16.04/16.04.0/+download/libdbusmenu-16.04.0.tar.gz"
|
||||
sha256 "b9cc4a2acd74509435892823607d966d424bd9ad5d0b00938f27240a1bfa878a"
|
||||
|
||||
patch 'From 729546c51806a1b3ea6cb6efb7a115b1baa811f1 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Stefan=20Br=C3=BCns?= <stefan.bruens@rwth-aachen.de>
|
||||
Date: Mon, 18 Nov 2019 19:58:53 +0100
|
||||
Subject: [PATCH 1/1] Fix HAVE_VALGRIND AM_CONDITIONAL
|
||||
|
||||
The AM_CONDITIONAL should also be run with --disable-tests, otherwise
|
||||
HAVE_VALGRIND is undefined.
|
||||
---
|
||||
configure | 4 ++--
|
||||
configure.ac | 2 +-
|
||||
2 files changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/configure b/configure
|
||||
index 831a3bb..8913b9b 100644
|
||||
--- a/configure
|
||||
+++ b/configure
|
||||
@@ -14801,6 +14801,8 @@ else
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
||||
$as_echo "yes" >&6; }
|
||||
have_valgrind=yes
|
||||
+fi
|
||||
+
|
||||
fi
|
||||
if test "x$have_valgrind" = "xyes"; then
|
||||
HAVE_VALGRIND_TRUE=
|
||||
@@ -14811,8 +14813,6 @@ else
|
||||
fi
|
||||
|
||||
|
||||
-fi
|
||||
-
|
||||
|
||||
|
||||
|
||||
diff --git a/configure.ac b/configure.ac
|
||||
index ace54d1..cbd38a6 100644
|
||||
--- a/configure.ac
|
||||
+++ b/configure.ac
|
||||
@@ -120,8 +120,8 @@ PKG_CHECK_MODULES(DBUSMENUTESTS, json-glib-1.0 >= $JSON_GLIB_REQUIRED_VERSION
|
||||
[have_tests=yes]
|
||||
)
|
||||
PKG_CHECK_MODULES(DBUSMENUTESTSVALGRIND, valgrind, have_valgrind=yes, have_valgrind=no)
|
||||
-AM_CONDITIONAL([HAVE_VALGRIND], [test "x$have_valgrind" = "xyes"])
|
||||
])
|
||||
+AM_CONDITIONAL([HAVE_VALGRIND], [test "x$have_valgrind" = "xyes"])
|
||||
|
||||
AC_SUBST(DBUSMENUTESTS_CFLAGS)
|
||||
AC_SUBST(DBUSMENUTESTS_LIBS)
|
||||
--
|
||||
2.46.2
|
||||
|
||||
|
||||
'
|
||||
end
|
||||
|
||||
resource "ayatana-ido" do
|
||||
url "https://github.com/AyatanaIndicators/ayatana-ido/archive/refs/tags/0.10.4.tar.gz"
|
||||
sha256 "bd59abd5f1314e411d0d55ce3643e91cef633271f58126be529de5fb71c5ab38"
|
||||
|
||||
patch 'From 8a09e6ad33c58c017c0c8fd756da036fc39428ea Mon Sep 17 00:00:00 2001
|
||||
From: Alexander Koskovich <akoskovich@pm.me>
|
||||
Date: Sun, 29 Sep 2024 13:47:54 -0400
|
||||
Subject: [PATCH 1/1] Make introspection configurable
|
||||
|
||||
---
|
||||
CMakeLists.txt | 1 +
|
||||
src/CMakeLists.txt | 4 ++++
|
||||
2 files changed, 5 insertions(+)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 0e13fcd..f3e9ec0 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -12,6 +12,7 @@ endif(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
option(ENABLE_TESTS "Enable all tests and checks" OFF)
|
||||
option(ENABLE_COVERAGE "Enable coverage reports (includes enabling all tests and checks)" OFF)
|
||||
option(ENABLE_WERROR "Treat all build warnings as errors" OFF)
|
||||
+option(ENABLE_INTROSPECTION "Enable introspection" ON)
|
||||
|
||||
if(ENABLE_COVERAGE)
|
||||
set(ENABLE_TESTS ON)
|
||||
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
|
||||
index 5b3638d..aca9481 100644
|
||||
--- a/src/CMakeLists.txt
|
||||
+++ b/src/CMakeLists.txt
|
||||
@@ -108,6 +108,8 @@ install(TARGETS "ayatana-ido3-0.4" LIBRARY DESTINATION "${CMAKE_INSTALL_FULL_LIB
|
||||
|
||||
# AyatanaIdo3-0.4.gir
|
||||
|
||||
+if (ENABLE_INTROSPECTION)
|
||||
+
|
||||
find_package(GObjectIntrospection REQUIRED QUIET)
|
||||
|
||||
if (INTROSPECTION_FOUND)
|
||||
@@ -183,3 +185,5 @@ if (INTROSPECTION_FOUND)
|
||||
endif ()
|
||||
|
||||
endif ()
|
||||
+
|
||||
+endif ()
|
||||
--
|
||||
2.46.2
|
||||
|
||||
|
||||
'
|
||||
end
|
||||
|
||||
resource "libayatana-indicator" do
|
||||
url "https://github.com/AyatanaIndicators/libayatana-indicator/archive/refs/tags/0.9.4.tar.gz"
|
||||
sha256 "a18d3c682e29afd77db24366f8475b26bda22b0e16ff569a2ec71cd6eb4eac95"
|
||||
end
|
||||
end
|
||||
|
||||
def install
|
||||
@@ -65,12 +207,12 @@ class @PROJECT_NAME@ < Formula
|
||||
|
||||
args = %W[
|
||||
-DBUILD_WERROR=ON
|
||||
-DCMAKE_CXX_STANDARD=20
|
||||
-DCMAKE_INSTALL_PREFIX=#{prefix}
|
||||
-DHOMEBREW_ALLOW_FETCHCONTENT=ON
|
||||
-DOPENSSL_ROOT_DIR=#{Formula["openssl"].opt_prefix}
|
||||
-DSUNSHINE_ASSETS_DIR=sunshine/assets
|
||||
-DSUNSHINE_BUILD_HOMEBREW=ON
|
||||
-DSUNSHINE_ENABLE_TRAY=OFF
|
||||
-DSUNSHINE_PUBLISHER_NAME='LizardByte'
|
||||
-DSUNSHINE_PUBLISHER_WEBSITE='https://app.lizardbyte.dev'
|
||||
-DSUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support'
|
||||
@@ -105,16 +247,69 @@ class @PROJECT_NAME@ < Formula
|
||||
end
|
||||
|
||||
args << "-DCUDA_FAIL_ON_MISSING=OFF" if OS.linux?
|
||||
args << "-DSUNSHINE_ENABLE_TRAY=OFF" if OS.mac?
|
||||
|
||||
system "cmake", "-S", ".", "-B", "build", *std_cmake_args, *args
|
||||
# Handle system tray on Linux
|
||||
if OS.linux?
|
||||
# Build and install libayatana components
|
||||
|
||||
cd "build" do
|
||||
system "make"
|
||||
system "make", "install"
|
||||
# Build libdbusmenu
|
||||
resource("libdbusmenu").stage do
|
||||
system "./configure",
|
||||
"--prefix=#{prefix}",
|
||||
"--with-gtk=3",
|
||||
"--disable-dumper",
|
||||
"--disable-static",
|
||||
"--disable-tests",
|
||||
"--disable-gtk-doc",
|
||||
"--enable-introspection=no",
|
||||
"--disable-vala"
|
||||
system "make", "install"
|
||||
end
|
||||
|
||||
bin.install "tests/test_sunshine"
|
||||
# Build ayatana-ido
|
||||
resource("ayatana-ido").stage do
|
||||
system "cmake", "-S", ".", "-B", "build", "-G", "Ninja",
|
||||
"-DCMAKE_INSTALL_PREFIX=#{prefix}",
|
||||
"-DENABLE_INTROSPECTION=OFF",
|
||||
*std_cmake_args
|
||||
system "ninja", "-C", "build"
|
||||
system "ninja", "-C", "build", "install"
|
||||
end
|
||||
|
||||
# Build libayatana-indicator
|
||||
resource("libayatana-indicator").stage do
|
||||
ENV.append_path "PKG_CONFIG_PATH", "#{lib}/pkgconfig"
|
||||
ENV.append "LDFLAGS", "-L#{lib}"
|
||||
|
||||
system "cmake", "-S", ".", "-B", "build", "-G", "Ninja",
|
||||
"-DCMAKE_INSTALL_PREFIX=#{prefix}",
|
||||
*std_cmake_args
|
||||
system "ninja", "-C", "build"
|
||||
system "ninja", "-C", "build", "install"
|
||||
end
|
||||
|
||||
# Build libayatana-appindicator
|
||||
resource("libayatana-appindicator").stage do
|
||||
system "cmake", "-S", ".", "-B", "build", "-G", "Ninja",
|
||||
"-DCMAKE_INSTALL_PREFIX=#{prefix}",
|
||||
"-DENABLE_BINDINGS_MONO=OFF",
|
||||
"-DENABLE_BINDINGS_VALA=OFF",
|
||||
"-DENABLE_GTKDOC=OFF",
|
||||
*std_cmake_args
|
||||
system "ninja", "-C", "build"
|
||||
system "ninja", "-C", "build", "install"
|
||||
end
|
||||
end
|
||||
|
||||
system "cmake", "-S", ".", "-B", "build", "-G", "Unix Makefiles",
|
||||
*std_cmake_args,
|
||||
*args
|
||||
|
||||
system "make", "-C", "build"
|
||||
system "make", "-C", "build", "install"
|
||||
bin.install "build/tests/test_sunshine"
|
||||
|
||||
# codesign the binary on intel macs
|
||||
system "codesign", "-s", "-", "--force", "--deep", bin/"sunshine" if OS.mac? && Hardware::CPU.intel?
|
||||
|
||||
@@ -157,6 +352,23 @@ class @PROJECT_NAME@ < Formula
|
||||
system bin/"sunshine", "--version"
|
||||
|
||||
# run the test suite
|
||||
system bin/"test_sunshine", "--gtest_color=yes"
|
||||
system bin/"test_sunshine", "--gtest_color=yes", "--gtest_output=xml:test_results.xml"
|
||||
assert_path_exists testpath/"test_results.xml"
|
||||
|
||||
# create gcovr report
|
||||
if ENV["HOMEBREW_BUILDPATH"]
|
||||
|
||||
cd File.join(ENV["HOMEBREW_BUILDPATH"], "build") do
|
||||
system "gcovr", ".",
|
||||
"-r", "../src",
|
||||
"--exclude-noncode-lines",
|
||||
"--exclude-throw-branches",
|
||||
"--exclude-unreachable-branches",
|
||||
"--verbose",
|
||||
"--xml-pretty",
|
||||
"-o=coverage.xml"
|
||||
end
|
||||
assert_path_exists File.join(ENV["HOMEBREW_BUILDPATH"], "build", "coverage.xml")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
156
scripts/linux_build.sh
Normal file → Executable file
156
scripts/linux_build.sh
Normal file → Executable file
@@ -3,6 +3,7 @@ set -e
|
||||
|
||||
# Default value for arguments
|
||||
appimage_build=0
|
||||
num_processors=$(nproc)
|
||||
publisher_name="Third Party Publisher"
|
||||
publisher_website=""
|
||||
publisher_issue_url="https://app.lizardbyte.dev/support"
|
||||
@@ -27,6 +28,7 @@ Options:
|
||||
-h, --help Display this help message.
|
||||
-s, --sudo-off Disable sudo command.
|
||||
--appimage-build Compile for AppImage, this will not create the AppImage, just the executable.
|
||||
--num-processors The number of processors to use for compilation. Default is the value of 'nproc'.
|
||||
--publisher-name The name of the publisher (not developer) of the application.
|
||||
--publisher-website The URL of the publisher's website.
|
||||
--publisher-issue-url The URL of the publisher's support site or issue tracker.
|
||||
@@ -53,6 +55,9 @@ while getopts ":hs-:" opt; do
|
||||
appimage_build=1
|
||||
skip_libva=1
|
||||
;;
|
||||
num-processors=*)
|
||||
num_processors="${OPTARG#*=}"
|
||||
;;
|
||||
publisher-name=*)
|
||||
publisher_name="${OPTARG#*=}"
|
||||
;;
|
||||
@@ -85,7 +90,53 @@ shift $((OPTIND -1))
|
||||
# dependencies array to build out
|
||||
dependencies=()
|
||||
|
||||
function add_debain_based_deps() {
|
||||
function add_arch_deps() {
|
||||
dependencies+=(
|
||||
'avahi'
|
||||
'base-devel'
|
||||
'cmake'
|
||||
'curl'
|
||||
"gcc${gcc_version}"
|
||||
"gcc${gcc_version}-libs"
|
||||
'git'
|
||||
'libayatana-appindicator'
|
||||
'libcap'
|
||||
'libdrm'
|
||||
'libevdev'
|
||||
'libmfx'
|
||||
'libnotify'
|
||||
'libpulse'
|
||||
'libva'
|
||||
'libx11'
|
||||
'libxcb'
|
||||
'libxfixes'
|
||||
'libxrandr'
|
||||
'libxtst'
|
||||
'miniupnpc'
|
||||
'ninja'
|
||||
'nodejs'
|
||||
'npm'
|
||||
'numactl'
|
||||
'openssl'
|
||||
'opus'
|
||||
'udev'
|
||||
'wayland'
|
||||
)
|
||||
|
||||
if [ "$skip_libva" == 0 ]; then
|
||||
dependencies+=(
|
||||
"libva" # VA-API
|
||||
)
|
||||
fi
|
||||
|
||||
if [ "$skip_cuda" == 0 ]; then
|
||||
dependencies+=(
|
||||
"cuda" # VA-API
|
||||
)
|
||||
fi
|
||||
}
|
||||
|
||||
function add_debian_based_deps() {
|
||||
dependencies+=(
|
||||
"bison" # required if we need to compile doxygen
|
||||
"build-essential"
|
||||
@@ -100,6 +151,7 @@ function add_debain_based_deps() {
|
||||
"libcurl4-openssl-dev"
|
||||
"libdrm-dev" # KMS
|
||||
"libevdev-dev"
|
||||
"libgbm-dev"
|
||||
"libminiupnpc-dev"
|
||||
"libnotify-dev"
|
||||
"libnuma-dev"
|
||||
@@ -128,8 +180,8 @@ function add_debain_based_deps() {
|
||||
fi
|
||||
}
|
||||
|
||||
function add_debain_deps() {
|
||||
add_debain_based_deps
|
||||
function add_debian_deps() {
|
||||
add_debian_based_deps
|
||||
dependencies+=(
|
||||
"libayatana-appindicator3-dev"
|
||||
)
|
||||
@@ -141,7 +193,7 @@ function add_ubuntu_deps() {
|
||||
${sudo_cmd} add-apt-repository ppa:ubuntu-toolchain-r/test -y
|
||||
fi
|
||||
|
||||
add_debain_based_deps
|
||||
add_debian_based_deps
|
||||
dependencies+=(
|
||||
"libappindicator3-dev"
|
||||
)
|
||||
@@ -151,8 +203,8 @@ function add_fedora_deps() {
|
||||
dependencies+=(
|
||||
"cmake"
|
||||
"doxygen"
|
||||
"gcc"
|
||||
"g++"
|
||||
"gcc${gcc_version}"
|
||||
"gcc${gcc_version}-c++"
|
||||
"git"
|
||||
"graphviz"
|
||||
"libappindicator-gtk3-devel"
|
||||
@@ -170,6 +222,7 @@ function add_fedora_deps() {
|
||||
"libXrandr-devel" # X11
|
||||
"libXtst-devel" # X11
|
||||
"mesa-libGL-devel"
|
||||
"mesa-libgbm-devel"
|
||||
"miniupnpc-devel"
|
||||
"ninja-build"
|
||||
"npm"
|
||||
@@ -191,9 +244,15 @@ function add_fedora_deps() {
|
||||
}
|
||||
|
||||
function install_cuda() {
|
||||
nvcc_path=$(command -v nvcc 2>/dev/null) || true
|
||||
if [ -n "$nvcc_path" ]; then
|
||||
echo "found system cuda"
|
||||
return
|
||||
fi
|
||||
# check if we need to install cuda
|
||||
if [ -f "${build_dir}/cuda/bin/nvcc" ]; then
|
||||
echo "cuda already installed"
|
||||
nvcc_path="${build_dir}/cuda/bin/nvcc"
|
||||
echo "found local cuda"
|
||||
return
|
||||
fi
|
||||
|
||||
@@ -230,11 +289,13 @@ function install_cuda() {
|
||||
chmod a+x "${build_dir}/cuda.run"
|
||||
"${build_dir}/cuda.run" --silent --toolkit --toolkitpath="${build_dir}/cuda" --no-opengl-libs --no-man-page --no-drm
|
||||
rm "${build_dir}/cuda.run"
|
||||
nvcc_path="${build_dir}/cuda/bin/nvcc"
|
||||
}
|
||||
|
||||
function check_version() {
|
||||
local package_name=$1
|
||||
local min_version=$2
|
||||
local max_version=$3
|
||||
local installed_version
|
||||
|
||||
echo "Checking if $package_name is installed and at least version $min_version"
|
||||
@@ -243,6 +304,8 @@ function check_version() {
|
||||
installed_version=$(dpkg -s "$package_name" 2>/dev/null | grep '^Version:' | awk '{print $2}')
|
||||
elif [ "$distro" == "fedora" ]; then
|
||||
installed_version=$(rpm -q --queryformat '%{VERSION}' "$package_name" 2>/dev/null)
|
||||
elif [ "$distro" == "arch" ]; then
|
||||
installed_version=$(pacman -Q "$package_name" | awk '{print $2}' )
|
||||
else
|
||||
echo "Unsupported Distro"
|
||||
return 1
|
||||
@@ -253,11 +316,12 @@ function check_version() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$(printf '%s\n' "$installed_version" "$min_version" | sort -V | head -n1)" = "$min_version" ]; then
|
||||
echo "$package_name version $installed_version is at least $min_version"
|
||||
if [[ "$(printf '%s\n' "$installed_version" "$min_version" | sort -V | head -n1)" = "$min_version" ]] && \
|
||||
[[ "$(printf '%s\n' "$installed_version" "$max_version" | sort -V | head -n1)" = "$installed_version" ]]; then
|
||||
echo "Installed version is within range"
|
||||
return 0
|
||||
else
|
||||
echo "$package_name version $installed_version is less than $min_version"
|
||||
echo "$package_name version $installed_version is out of range"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
@@ -296,13 +360,15 @@ function run_install() {
|
||||
# Update the package list
|
||||
$package_update_command
|
||||
|
||||
if [ "$distro" == "debian" ]; then
|
||||
add_debain_deps
|
||||
if [ "$distro" == "arch" ]; then
|
||||
add_arch_deps
|
||||
elif [ "$distro" == "debian" ]; then
|
||||
add_debian_deps
|
||||
elif [ "$distro" == "ubuntu" ]; then
|
||||
add_ubuntu_deps
|
||||
elif [ "$distro" == "fedora" ]; then
|
||||
add_fedora_deps
|
||||
${sudo_cmd} dnf group install "Development Tools" -y
|
||||
${sudo_cmd} dnf group install "$dev_tools_group" -y
|
||||
fi
|
||||
|
||||
# Install the dependencies
|
||||
@@ -320,8 +386,11 @@ function run_install() {
|
||||
"gcc-ranlib"
|
||||
)
|
||||
|
||||
# update alternatives for gcc and g++ if a debian based distro
|
||||
if [ "$distro" == "debian" ] || [ "$distro" == "ubuntu" ]; then
|
||||
#set gcc version based on distros
|
||||
if [ "$distro" == "arch" ]; then
|
||||
export CC=gcc-14
|
||||
export CXX=g++-14
|
||||
elif [ "$distro" == "debian" ] || [ "$distro" == "ubuntu" ]; then
|
||||
for file in "${gcc_alternative_files[@]}"; do
|
||||
file_path="/etc/alternatives/$file"
|
||||
if [ -e "$file_path" ]; then
|
||||
@@ -340,7 +409,7 @@ function run_install() {
|
||||
# compile cmake if the version is too low
|
||||
cmake_min="3.25.0"
|
||||
target_cmake_version="3.30.1"
|
||||
if ! check_version "cmake" "$cmake_min"; then
|
||||
if ! check_version "cmake" "$cmake_min" "inf"; then
|
||||
cmake_prefix="https://github.com/Kitware/CMake/releases/download/v"
|
||||
if [ "$architecture" == "x86_64" ]; then
|
||||
cmake_arch="x86_64"
|
||||
@@ -358,7 +427,8 @@ function run_install() {
|
||||
# compile doxygen if version is too low
|
||||
doxygen_min="1.10.0"
|
||||
_doxygen_min="1_10_0"
|
||||
if ! check_version "doxygen" "$doxygen_min"; then
|
||||
doxygen_max="1.12.0"
|
||||
if ! check_version "doxygen" "$doxygen_min" "$doxygen_max"; then
|
||||
if [ "${SUNSHINE_COMPILE_DOXYGEN}" == "true" ]; then
|
||||
echo "Compiling doxygen"
|
||||
doxygen_url="https://github.com/doxygen/doxygen/releases/download/Release_${_doxygen_min}/doxygen-${doxygen_min}.src.tar.gz"
|
||||
@@ -367,10 +437,10 @@ function run_install() {
|
||||
tar -xzf "${build_dir}/doxygen.tar.gz"
|
||||
cd "doxygen-${doxygen_min}"
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -G="Ninja" -B="build" -S="."
|
||||
ninja -C "build"
|
||||
ninja -C "build" -j"${num_processors}"
|
||||
ninja -C "build" install
|
||||
else
|
||||
echo "Doxygen version too low, skipping docs"
|
||||
echo "Doxygen version not in range, skipping docs"
|
||||
cmake_args+=("-DBUILD_DOCS=OFF")
|
||||
fi
|
||||
fi
|
||||
@@ -386,10 +456,10 @@ function run_install() {
|
||||
fi
|
||||
|
||||
# run the cuda install
|
||||
if [ -n "$cuda_version" ] && [ "$skip_cuda" == 0 ]; then
|
||||
if [ "$skip_cuda" == 0 ]; then
|
||||
install_cuda
|
||||
cmake_args+=("-DSUNSHINE_ENABLE_CUDA=ON")
|
||||
cmake_args+=("-DCMAKE_CUDA_COMPILER:PATH=${build_dir}/cuda/bin/nvcc")
|
||||
cmake_args+=("-DCMAKE_CUDA_COMPILER:PATH=$nvcc_path")
|
||||
fi
|
||||
|
||||
# Cmake stuff here
|
||||
@@ -429,7 +499,15 @@ function run_install() {
|
||||
|
||||
# Determine the OS and call the appropriate function
|
||||
cat /etc/os-release
|
||||
if grep -q "Debian GNU/Linux 12 (bookworm)" /etc/os-release; then
|
||||
|
||||
if grep -q "Arch Linux" /etc/os-release; then
|
||||
distro="arch"
|
||||
version=""
|
||||
package_update_command="${sudo_cmd} pacman -Syu --noconfirm"
|
||||
package_install_command="${sudo_cmd} pacman -Sy --needed"
|
||||
nvm_node=0
|
||||
gcc_version="14"
|
||||
elif grep -q "Debian GNU/Linux 12 (bookworm)" /etc/os-release; then
|
||||
distro="debian"
|
||||
version="12"
|
||||
package_update_command="${sudo_cmd} apt-get update"
|
||||
@@ -438,24 +516,36 @@ if grep -q "Debian GNU/Linux 12 (bookworm)" /etc/os-release; then
|
||||
cuda_build="525.60.13"
|
||||
gcc_version="12"
|
||||
nvm_node=0
|
||||
elif grep -q "PLATFORM_ID=\"platform:f39\"" /etc/os-release; then
|
||||
distro="fedora"
|
||||
version="39"
|
||||
package_update_command="${sudo_cmd} dnf update -y"
|
||||
package_install_command="${sudo_cmd} dnf install -y"
|
||||
cuda_version="12.4.0"
|
||||
cuda_build="550.54.14"
|
||||
gcc_version="13"
|
||||
nvm_node=0
|
||||
elif grep -q "PLATFORM_ID=\"platform:f40\"" /etc/os-release; then
|
||||
distro="fedora"
|
||||
version="40"
|
||||
package_update_command="${sudo_cmd} dnf update -y"
|
||||
package_install_command="${sudo_cmd} dnf install -y"
|
||||
cuda_version=
|
||||
cuda_build=
|
||||
cuda_version=12.6.3
|
||||
cuda_build=560.35.05
|
||||
gcc_version="13"
|
||||
nvm_node=0
|
||||
dev_tools_group="Development Tools"
|
||||
elif grep -q "PLATFORM_ID=\"platform:f41\"" /etc/os-release; then
|
||||
distro="fedora"
|
||||
version="41"
|
||||
package_update_command="${sudo_cmd} dnf update -y"
|
||||
package_install_command="${sudo_cmd} dnf install -y"
|
||||
cuda_version=12.6.3
|
||||
cuda_build=560.35.05
|
||||
gcc_version="13"
|
||||
nvm_node=0
|
||||
dev_tools_group="development-tools"
|
||||
elif grep -q "PLATFORM_ID=\"platform:f42\"" /etc/os-release; then
|
||||
distro="fedora"
|
||||
version="42"
|
||||
package_update_command="${sudo_cmd} dnf update -y"
|
||||
package_install_command="${sudo_cmd} dnf install -y"
|
||||
cuda_version=12.8.1
|
||||
cuda_build=570.124.06
|
||||
gcc_version="14"
|
||||
nvm_node=0
|
||||
dev_tools_group="development-tools"
|
||||
elif grep -q "Ubuntu 22.04" /etc/os-release; then
|
||||
distro="ubuntu"
|
||||
version="22.04"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
Babel==2.16.0
|
||||
clang-format
|
||||
Babel==2.17.0
|
||||
clang-format==20.*
|
||||
|
||||
@@ -7,12 +7,12 @@ directories = [
|
||||
'src',
|
||||
'tests',
|
||||
'tools',
|
||||
os.path.join('third-party', 'glad'),
|
||||
os.path.join('third-party', 'nvfbc'),
|
||||
]
|
||||
file_types = [
|
||||
'cpp',
|
||||
'cu',
|
||||
'h',
|
||||
'hpp',
|
||||
'm',
|
||||
'mm'
|
||||
]
|
||||
|
||||
@@ -2,16 +2,18 @@
|
||||
* @file src/audio.cpp
|
||||
* @brief Definitions for audio capture and encoding.
|
||||
*/
|
||||
// standard includes
|
||||
#include <thread>
|
||||
|
||||
// lib includes
|
||||
#include <opus/opus_multistream.h>
|
||||
|
||||
#include "platform/common.h"
|
||||
|
||||
// local includes
|
||||
#include "audio.h"
|
||||
#include "config.h"
|
||||
#include "globals.h"
|
||||
#include "logging.h"
|
||||
#include "platform/common.h"
|
||||
#include "thread_safe.h"
|
||||
#include "utility.h"
|
||||
|
||||
@@ -20,15 +22,11 @@ namespace audio {
|
||||
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
|
||||
using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<float>>>;
|
||||
|
||||
static int
|
||||
start_audio_control(audio_ctx_t &ctx);
|
||||
static void
|
||||
stop_audio_control(audio_ctx_t &);
|
||||
static void
|
||||
apply_surround_params(opus_stream_config_t &stream, const stream_params_t ¶ms);
|
||||
static int start_audio_control(audio_ctx_t &ctx);
|
||||
static void stop_audio_control(audio_ctx_t &);
|
||||
static void apply_surround_params(opus_stream_config_t &stream, const stream_params_t ¶ms);
|
||||
|
||||
int
|
||||
map_stream(int channels, bool quality);
|
||||
int map_stream(int channels, bool quality);
|
||||
|
||||
constexpr auto SAMPLE_RATE = 48000;
|
||||
|
||||
@@ -85,8 +83,7 @@ namespace audio {
|
||||
},
|
||||
};
|
||||
|
||||
void
|
||||
encodeThread(sample_queue_t samples, config_t config, void *channel_data) {
|
||||
void encodeThread(sample_queue_t samples, config_t config, void *channel_data) {
|
||||
auto packets = mail::man->queue<packet_t>(mail::audio_packets);
|
||||
auto stream = stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
|
||||
if (config.flags[config_t::CUSTOM_SURROUND_PARAMS]) {
|
||||
@@ -96,14 +93,15 @@ namespace audio {
|
||||
// Encoding takes place on this thread
|
||||
platf::adjust_thread_priority(platf::thread_priority_e::high);
|
||||
|
||||
opus_t opus { opus_multistream_encoder_create(
|
||||
opus_t opus {opus_multistream_encoder_create(
|
||||
stream.sampleRate,
|
||||
stream.channelCount,
|
||||
stream.streams,
|
||||
stream.coupledStreams,
|
||||
stream.mapping,
|
||||
OPUS_APPLICATION_RESTRICTED_LOWDELAY,
|
||||
nullptr) };
|
||||
nullptr
|
||||
)};
|
||||
|
||||
opus_multistream_encoder_ctl(opus.get(), OPUS_SET_BITRATE(stream.bitrate));
|
||||
opus_multistream_encoder_ctl(opus.get(), OPUS_SET_VBR(0));
|
||||
@@ -114,7 +112,7 @@ namespace audio {
|
||||
|
||||
auto frame_size = config.packetDuration * stream.sampleRate / 1000;
|
||||
while (auto sample = samples->pop()) {
|
||||
buffer_t packet { 1400 };
|
||||
buffer_t packet {1400};
|
||||
|
||||
int bytes = opus_multistream_encode_float(opus.get(), sample->data(), frame_size, std::begin(packet), packet.size());
|
||||
if (bytes < 0) {
|
||||
@@ -129,9 +127,12 @@ namespace audio {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
capture(safe::mail_t mail, config_t config, void *channel_data) {
|
||||
void capture(safe::mail_t mail, config_t config, void *channel_data) {
|
||||
auto shutdown_event = mail->event<bool>(mail::shutdown);
|
||||
if (!config::audio.stream) {
|
||||
shutdown_event->view();
|
||||
return;
|
||||
}
|
||||
auto stream = stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
|
||||
if (config.flags[config_t::CUSTOM_SURROUND_PARAMS]) {
|
||||
apply_surround_params(stream, config.customStreamParams);
|
||||
@@ -204,7 +205,7 @@ namespace audio {
|
||||
platf::adjust_thread_priority(platf::thread_priority_e::critical);
|
||||
|
||||
auto samples = std::make_shared<sample_queue_t::element_type>(30);
|
||||
std::thread thread { encodeThread, samples, config, channel_data };
|
||||
std::thread thread {encodeThread, samples, config, channel_data};
|
||||
|
||||
auto fg = util::fail_guard([&]() {
|
||||
samples->stop();
|
||||
@@ -243,14 +244,12 @@ namespace audio {
|
||||
}
|
||||
}
|
||||
|
||||
audio_ctx_ref_t
|
||||
get_audio_ctx_ref() {
|
||||
static auto control_shared { safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control) };
|
||||
audio_ctx_ref_t get_audio_ctx_ref() {
|
||||
static auto control_shared {safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control)};
|
||||
return control_shared.ref();
|
||||
}
|
||||
|
||||
bool
|
||||
is_audio_ctx_sink_available(const audio_ctx_t &ctx) {
|
||||
bool is_audio_ctx_sink_available(const audio_ctx_t &ctx) {
|
||||
if (!ctx.control) {
|
||||
return false;
|
||||
}
|
||||
@@ -263,8 +262,7 @@ namespace audio {
|
||||
return ctx.control->is_sink_available(sink);
|
||||
}
|
||||
|
||||
int
|
||||
map_stream(int channels, bool quality) {
|
||||
int map_stream(int channels, bool quality) {
|
||||
int shift = quality ? 1 : 0;
|
||||
switch (channels) {
|
||||
case 2:
|
||||
@@ -277,8 +275,7 @@ namespace audio {
|
||||
return STEREO;
|
||||
}
|
||||
|
||||
int
|
||||
start_audio_control(audio_ctx_t &ctx) {
|
||||
int start_audio_control(audio_ctx_t &ctx) {
|
||||
auto fg = util::fail_guard([]() {
|
||||
BOOST_LOG(warning) << "There will be no audio"sv;
|
||||
});
|
||||
@@ -305,8 +302,7 @@ namespace audio {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
stop_audio_control(audio_ctx_t &ctx) {
|
||||
void stop_audio_control(audio_ctx_t &ctx) {
|
||||
// restore audio-sink if applicable
|
||||
if (!ctx.restore_sink) {
|
||||
return;
|
||||
@@ -320,8 +316,7 @@ namespace audio {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
apply_surround_params(opus_stream_config_t &stream, const stream_params_t ¶ms) {
|
||||
void apply_surround_params(opus_stream_config_t &stream, const stream_params_t ¶ms) {
|
||||
stream.channelCount = params.channelCount;
|
||||
stream.streams = params.streams;
|
||||
stream.coupledStreams = params.coupledStreams;
|
||||
|
||||
@@ -71,8 +71,7 @@ namespace audio {
|
||||
using packet_t = std::pair<void *, buffer_t>;
|
||||
using audio_ctx_ref_t = safe::shared_t<audio_ctx_t>::ptr_t;
|
||||
|
||||
void
|
||||
capture(safe::mail_t mail, config_t config, void *channel_data);
|
||||
void capture(safe::mail_t mail, config_t config, void *channel_data);
|
||||
|
||||
/**
|
||||
* @brief Get the reference to the audio context.
|
||||
@@ -84,8 +83,7 @@ namespace audio {
|
||||
* audio_ctx_ref_t audio = get_audio_ctx_ref()
|
||||
* @examples_end
|
||||
*/
|
||||
audio_ctx_ref_t
|
||||
get_audio_ctx_ref();
|
||||
audio_ctx_ref_t get_audio_ctx_ref();
|
||||
|
||||
/**
|
||||
* @brief Check if the audio sink held by audio context is available.
|
||||
@@ -101,6 +99,5 @@ namespace audio {
|
||||
* return false;
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
is_audio_ctx_sink_available(const audio_ctx_t &ctx);
|
||||
bool is_audio_ctx_sink_available(const audio_ctx_t &ctx);
|
||||
} // namespace audio
|
||||
|
||||
36
src/cbs.cpp
36
src/cbs.cpp
@@ -3,6 +3,7 @@
|
||||
* @brief Definitions for FFmpeg Coded Bitstream API.
|
||||
*/
|
||||
extern "C" {
|
||||
// lib includes
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavcodec/cbs_h264.h>
|
||||
#include <libavcodec/cbs_h265.h>
|
||||
@@ -10,14 +11,15 @@ extern "C" {
|
||||
#include <libavutil/pixdesc.h>
|
||||
}
|
||||
|
||||
// local includes
|
||||
#include "cbs.h"
|
||||
#include "logging.h"
|
||||
#include "utility.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace cbs {
|
||||
void
|
||||
close(CodedBitstreamContext *c) {
|
||||
void close(CodedBitstreamContext *c) {
|
||||
ff_cbs_close(&c);
|
||||
}
|
||||
|
||||
@@ -36,8 +38,7 @@ namespace cbs {
|
||||
std::fill_n((std::uint8_t *) this, sizeof(*this), 0);
|
||||
}
|
||||
|
||||
frag_t &
|
||||
operator=(frag_t &&o) {
|
||||
frag_t &operator=(frag_t &&o) {
|
||||
std::copy((std::uint8_t *) &o, (std::uint8_t *) (&o + 1), (std::uint8_t *) this);
|
||||
|
||||
o.data = nullptr;
|
||||
@@ -53,12 +54,11 @@ namespace cbs {
|
||||
}
|
||||
};
|
||||
|
||||
util::buffer_t<std::uint8_t>
|
||||
write(cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) {
|
||||
util::buffer_t<std::uint8_t> write(cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) {
|
||||
cbs::frag_t frag;
|
||||
auto err = ff_cbs_insert_unit_content(&frag, -1, nal, uh, nullptr);
|
||||
if (err < 0) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
|
||||
BOOST_LOG(error) << "Could not insert NAL unit SPS: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return {};
|
||||
@@ -66,29 +66,27 @@ namespace cbs {
|
||||
|
||||
err = ff_cbs_write_fragment_data(cbs_ctx.get(), &frag);
|
||||
if (err < 0) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
|
||||
BOOST_LOG(error) << "Could not write fragment data: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// frag.data_size * 8 - frag.data_bit_padding == bits in fragment
|
||||
util::buffer_t<std::uint8_t> data { frag.data_size };
|
||||
util::buffer_t<std::uint8_t> data {frag.data_size};
|
||||
std::copy_n(frag.data, frag.data_size, std::begin(data));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
util::buffer_t<std::uint8_t>
|
||||
write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
|
||||
util::buffer_t<std::uint8_t> write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
|
||||
cbs::ctx_t cbs_ctx;
|
||||
ff_cbs_init(&cbs_ctx, codec_id, nullptr);
|
||||
|
||||
return write(cbs_ctx, nal, uh, codec_id);
|
||||
}
|
||||
|
||||
h264_t
|
||||
make_sps_h264(const AVCodecContext *avctx, const AVPacket *packet) {
|
||||
h264_t make_sps_h264(const AVCodecContext *avctx, const AVPacket *packet) {
|
||||
cbs::ctx_t ctx;
|
||||
if (ff_cbs_init(&ctx, AV_CODEC_ID_H264, nullptr)) {
|
||||
return {};
|
||||
@@ -98,7 +96,7 @@ namespace cbs {
|
||||
|
||||
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
|
||||
if (err < 0) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
|
||||
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return {};
|
||||
@@ -144,8 +142,7 @@ namespace cbs {
|
||||
};
|
||||
}
|
||||
|
||||
hevc_t
|
||||
make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) {
|
||||
hevc_t make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) {
|
||||
cbs::ctx_t ctx;
|
||||
if (ff_cbs_init(&ctx, AV_CODEC_ID_H265, nullptr)) {
|
||||
return {};
|
||||
@@ -155,7 +152,7 @@ namespace cbs {
|
||||
|
||||
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
|
||||
if (err < 0) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
|
||||
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return {};
|
||||
@@ -222,8 +219,7 @@ namespace cbs {
|
||||
* It then checks if the SPS->VUI (Video Usability Information) is present in the active SPS of the packet.
|
||||
* This is done for both H264 and H265 codecs.
|
||||
*/
|
||||
bool
|
||||
validate_sps(const AVPacket *packet, int codec_id) {
|
||||
bool validate_sps(const AVPacket *packet, int codec_id) {
|
||||
cbs::ctx_t ctx;
|
||||
if (ff_cbs_init(&ctx, (AVCodecID) codec_id, nullptr)) {
|
||||
return false;
|
||||
@@ -233,7 +229,7 @@ namespace cbs {
|
||||
|
||||
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
|
||||
if (err < 0) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
|
||||
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return false;
|
||||
|
||||
10
src/cbs.h
10
src/cbs.h
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// local includes
|
||||
#include "utility.h"
|
||||
|
||||
struct AVPacket;
|
||||
@@ -25,10 +26,8 @@ namespace cbs {
|
||||
nal_t sps;
|
||||
};
|
||||
|
||||
hevc_t
|
||||
make_sps_hevc(const AVCodecContext *ctx, const AVPacket *packet);
|
||||
h264_t
|
||||
make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet);
|
||||
hevc_t make_sps_hevc(const AVCodecContext *ctx, const AVPacket *packet);
|
||||
h264_t make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet);
|
||||
|
||||
/**
|
||||
* @brief Validates the Sequence Parameter Set (SPS) of a given packet.
|
||||
@@ -36,6 +35,5 @@ namespace cbs {
|
||||
* @param codec_id The ID of the codec used (either AV_CODEC_ID_H264 or AV_CODEC_ID_H265).
|
||||
* @return True if the SPS->VUI is present in the active SPS of the packet, false otherwise.
|
||||
*/
|
||||
bool
|
||||
validate_sps(const AVPacket *packet, int codec_id);
|
||||
bool validate_sps(const AVPacket *packet, int codec_id);
|
||||
} // namespace cbs
|
||||
|
||||
399
src/config.cpp
399
src/config.cpp
@@ -2,6 +2,7 @@
|
||||
* @file src/config.cpp
|
||||
* @brief Definitions for the configuration of Sunshine.
|
||||
*/
|
||||
// standard includes
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
@@ -11,21 +12,22 @@
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
// lib includes
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
// local includes
|
||||
#include "config.h"
|
||||
#include "entry_handler.h"
|
||||
#include "file_handler.h"
|
||||
#include "logging.h"
|
||||
#include "nvhttp.h"
|
||||
#include "platform/common.h"
|
||||
#include "rtsp.h"
|
||||
#include "utility.h"
|
||||
|
||||
#include "platform/common.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
@@ -43,15 +45,21 @@ using namespace std::literals;
|
||||
#define CERTIFICATE_FILE CA_DIR "/cacert.pem"
|
||||
|
||||
#define APPS_JSON_PATH platf::appdata().string() + "/apps.json"
|
||||
|
||||
namespace config {
|
||||
|
||||
namespace nv {
|
||||
|
||||
nvenc::nvenc_two_pass
|
||||
twopass_from_view(const std::string_view &preset) {
|
||||
if (preset == "disabled") return nvenc::nvenc_two_pass::disabled;
|
||||
if (preset == "quarter_res") return nvenc::nvenc_two_pass::quarter_resolution;
|
||||
if (preset == "full_res") return nvenc::nvenc_two_pass::full_resolution;
|
||||
nvenc::nvenc_two_pass twopass_from_view(const std::string_view &preset) {
|
||||
if (preset == "disabled") {
|
||||
return nvenc::nvenc_two_pass::disabled;
|
||||
}
|
||||
if (preset == "quarter_res") {
|
||||
return nvenc::nvenc_two_pass::quarter_resolution;
|
||||
}
|
||||
if (preset == "full_res") {
|
||||
return nvenc::nvenc_two_pass::full_resolution;
|
||||
}
|
||||
BOOST_LOG(warning) << "config: unknown nvenc_twopass value: " << preset;
|
||||
return nvenc::nvenc_two_pass::quarter_resolution;
|
||||
}
|
||||
@@ -178,11 +186,11 @@ namespace config {
|
||||
cavlc = AMF_VIDEO_ENCODER_CALV ///< CAVLC
|
||||
};
|
||||
|
||||
template <class T>
|
||||
std::optional<int>
|
||||
quality_from_view(const std::string_view &quality_type, const std::optional<int>(&original)) {
|
||||
template<class T>
|
||||
std::optional<int> quality_from_view(const std::string_view &quality_type, const std::optional<int>(&original)) {
|
||||
#define _CONVERT_(x) \
|
||||
if (quality_type == #x##sv) return (int) T::x
|
||||
if (quality_type == #x##sv) \
|
||||
return (int) T::x
|
||||
_CONVERT_(balanced);
|
||||
_CONVERT_(quality);
|
||||
_CONVERT_(speed);
|
||||
@@ -190,11 +198,11 @@ namespace config {
|
||||
return original;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::optional<int>
|
||||
rc_from_view(const std::string_view &rc, const std::optional<int>(&original)) {
|
||||
template<class T>
|
||||
std::optional<int> rc_from_view(const std::string_view &rc, const std::optional<int>(&original)) {
|
||||
#define _CONVERT_(x) \
|
||||
if (rc == #x##sv) return (int) T::x
|
||||
if (rc == #x##sv) \
|
||||
return (int) T::x
|
||||
_CONVERT_(cbr);
|
||||
_CONVERT_(cqp);
|
||||
_CONVERT_(vbr_latency);
|
||||
@@ -203,11 +211,11 @@ namespace config {
|
||||
return original;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::optional<int>
|
||||
usage_from_view(const std::string_view &usage, const std::optional<int>(&original)) {
|
||||
template<class T>
|
||||
std::optional<int> usage_from_view(const std::string_view &usage, const std::optional<int>(&original)) {
|
||||
#define _CONVERT_(x) \
|
||||
if (usage == #x##sv) return (int) T::x
|
||||
if (usage == #x##sv) \
|
||||
return (int) T::x
|
||||
_CONVERT_(lowlatency);
|
||||
_CONVERT_(lowlatency_high_quality);
|
||||
_CONVERT_(transcoding);
|
||||
@@ -217,11 +225,16 @@ namespace config {
|
||||
return original;
|
||||
}
|
||||
|
||||
int
|
||||
coder_from_view(const std::string_view &coder) {
|
||||
if (coder == "auto"sv) return _auto;
|
||||
if (coder == "cabac"sv || coder == "ac"sv) return cabac;
|
||||
if (coder == "cavlc"sv || coder == "vlc"sv) return cavlc;
|
||||
int coder_from_view(const std::string_view &coder) {
|
||||
if (coder == "auto"sv) {
|
||||
return _auto;
|
||||
}
|
||||
if (coder == "cabac"sv || coder == "ac"sv) {
|
||||
return cabac;
|
||||
}
|
||||
if (coder == "cavlc"sv || coder == "vlc"sv) {
|
||||
return cavlc;
|
||||
}
|
||||
|
||||
return _auto;
|
||||
}
|
||||
@@ -244,10 +257,10 @@ namespace config {
|
||||
disabled = false ///< Disabled
|
||||
};
|
||||
|
||||
std::optional<int>
|
||||
preset_from_view(const std::string_view &preset) {
|
||||
std::optional<int> preset_from_view(const std::string_view &preset) {
|
||||
#define _CONVERT_(x) \
|
||||
if (preset == #x##sv) return x
|
||||
if (preset == #x##sv) \
|
||||
return x
|
||||
_CONVERT_(veryslow);
|
||||
_CONVERT_(slower);
|
||||
_CONVERT_(slow);
|
||||
@@ -259,11 +272,16 @@ namespace config {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<int>
|
||||
coder_from_view(const std::string_view &coder) {
|
||||
if (coder == "auto"sv) return _auto;
|
||||
if (coder == "cabac"sv || coder == "ac"sv) return disabled;
|
||||
if (coder == "cavlc"sv || coder == "vlc"sv) return enabled;
|
||||
std::optional<int> coder_from_view(const std::string_view &coder) {
|
||||
if (coder == "auto"sv) {
|
||||
return _auto;
|
||||
}
|
||||
if (coder == "cabac"sv || coder == "ac"sv) {
|
||||
return disabled;
|
||||
}
|
||||
if (coder == "cavlc"sv || coder == "vlc"sv) {
|
||||
return enabled;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -277,32 +295,40 @@ namespace config {
|
||||
cavlc ///< CAVLC
|
||||
};
|
||||
|
||||
int
|
||||
coder_from_view(const std::string_view &coder) {
|
||||
if (coder == "auto"sv) return _auto;
|
||||
if (coder == "cabac"sv || coder == "ac"sv) return cabac;
|
||||
if (coder == "cavlc"sv || coder == "vlc"sv) return cavlc;
|
||||
int coder_from_view(const std::string_view &coder) {
|
||||
if (coder == "auto"sv) {
|
||||
return _auto;
|
||||
}
|
||||
if (coder == "cabac"sv || coder == "ac"sv) {
|
||||
return cabac;
|
||||
}
|
||||
if (coder == "cavlc"sv || coder == "vlc"sv) {
|
||||
return cavlc;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
allow_software_from_view(const std::string_view &software) {
|
||||
if (software == "allowed"sv || software == "forced") return 1;
|
||||
int allow_software_from_view(const std::string_view &software) {
|
||||
if (software == "allowed"sv || software == "forced") {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
force_software_from_view(const std::string_view &software) {
|
||||
if (software == "forced") return 1;
|
||||
int force_software_from_view(const std::string_view &software) {
|
||||
if (software == "forced") {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
rt_from_view(const std::string_view &rt) {
|
||||
if (rt == "disabled" || rt == "off" || rt == "0") return 0;
|
||||
int rt_from_view(const std::string_view &rt) {
|
||||
if (rt == "disabled" || rt == "off" || rt == "0") {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -310,10 +336,10 @@ namespace config {
|
||||
} // namespace vt
|
||||
|
||||
namespace sw {
|
||||
int
|
||||
svtav1_preset_from_view(const std::string_view &preset) {
|
||||
int svtav1_preset_from_view(const std::string_view &preset) {
|
||||
#define _CONVERT_(x, y) \
|
||||
if (preset == #x##sv) return y
|
||||
if (preset == #x##sv) \
|
||||
return y
|
||||
_CONVERT_(veryslow, 1);
|
||||
_CONVERT_(slower, 2);
|
||||
_CONVERT_(slow, 4);
|
||||
@@ -329,10 +355,10 @@ namespace config {
|
||||
} // namespace sw
|
||||
|
||||
namespace dd {
|
||||
video_t::dd_t::config_option_e
|
||||
config_option_from_view(const std::string_view value) {
|
||||
video_t::dd_t::config_option_e config_option_from_view(const std::string_view value) {
|
||||
#define _CONVERT_(x) \
|
||||
if (value == #x##sv) return video_t::dd_t::config_option_e::x
|
||||
if (value == #x##sv) \
|
||||
return video_t::dd_t::config_option_e::x
|
||||
_CONVERT_(disabled);
|
||||
_CONVERT_(verify_only);
|
||||
_CONVERT_(ensure_active);
|
||||
@@ -342,10 +368,10 @@ namespace config {
|
||||
return video_t::dd_t::config_option_e::disabled; // Default to this if value is invalid
|
||||
}
|
||||
|
||||
video_t::dd_t::resolution_option_e
|
||||
resolution_option_from_view(const std::string_view value) {
|
||||
video_t::dd_t::resolution_option_e resolution_option_from_view(const std::string_view value) {
|
||||
#define _CONVERT_2_ARG_(str, val) \
|
||||
if (value == #str##sv) return video_t::dd_t::resolution_option_e::val
|
||||
if (value == #str##sv) \
|
||||
return video_t::dd_t::resolution_option_e::val
|
||||
#define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
|
||||
_CONVERT_(disabled);
|
||||
_CONVERT_2_ARG_(auto, automatic);
|
||||
@@ -355,10 +381,10 @@ namespace config {
|
||||
return video_t::dd_t::resolution_option_e::disabled; // Default to this if value is invalid
|
||||
}
|
||||
|
||||
video_t::dd_t::refresh_rate_option_e
|
||||
refresh_rate_option_from_view(const std::string_view value) {
|
||||
video_t::dd_t::refresh_rate_option_e refresh_rate_option_from_view(const std::string_view value) {
|
||||
#define _CONVERT_2_ARG_(str, val) \
|
||||
if (value == #str##sv) return video_t::dd_t::refresh_rate_option_e::val
|
||||
if (value == #str##sv) \
|
||||
return video_t::dd_t::refresh_rate_option_e::val
|
||||
#define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
|
||||
_CONVERT_(disabled);
|
||||
_CONVERT_2_ARG_(auto, automatic);
|
||||
@@ -368,10 +394,10 @@ namespace config {
|
||||
return video_t::dd_t::refresh_rate_option_e::disabled; // Default to this if value is invalid
|
||||
}
|
||||
|
||||
video_t::dd_t::hdr_option_e
|
||||
hdr_option_from_view(const std::string_view value) {
|
||||
video_t::dd_t::hdr_option_e hdr_option_from_view(const std::string_view value) {
|
||||
#define _CONVERT_2_ARG_(str, val) \
|
||||
if (value == #str##sv) return video_t::dd_t::hdr_option_e::val
|
||||
if (value == #str##sv) \
|
||||
return video_t::dd_t::hdr_option_e::val
|
||||
#define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
|
||||
_CONVERT_(disabled);
|
||||
_CONVERT_2_ARG_(auto, automatic);
|
||||
@@ -380,22 +406,17 @@ namespace config {
|
||||
return video_t::dd_t::hdr_option_e::disabled; // Default to this if value is invalid
|
||||
}
|
||||
|
||||
video_t::dd_t::mode_remapping_t
|
||||
mode_remapping_from_view(const std::string_view value) {
|
||||
const auto parse_entry_list { [](const auto &entry_list, auto &output_field) {
|
||||
video_t::dd_t::mode_remapping_t mode_remapping_from_view(const std::string_view value) {
|
||||
const auto parse_entry_list {[](const auto &entry_list, auto &output_field) {
|
||||
for (auto &[_, entry] : entry_list) {
|
||||
auto requested_resolution = entry.template get_optional<std::string>("requested_resolution"s);
|
||||
auto requested_fps = entry.template get_optional<std::string>("requested_fps"s);
|
||||
auto final_resolution = entry.template get_optional<std::string>("final_resolution"s);
|
||||
auto final_refresh_rate = entry.template get_optional<std::string>("final_refresh_rate"s);
|
||||
|
||||
output_field.push_back(video_t::dd_t::mode_remapping_entry_t {
|
||||
requested_resolution.value_or(""),
|
||||
requested_fps.value_or(""),
|
||||
final_resolution.value_or(""),
|
||||
final_refresh_rate.value_or("") });
|
||||
output_field.push_back(video_t::dd_t::mode_remapping_entry_t {requested_resolution.value_or(""), requested_fps.value_or(""), final_resolution.value_or(""), final_refresh_rate.value_or("")});
|
||||
}
|
||||
} };
|
||||
}};
|
||||
|
||||
// We need to add a wrapping object to make it valid JSON, otherwise ptree cannot parse it.
|
||||
std::stringstream json_stream;
|
||||
@@ -471,23 +492,26 @@ namespace config {
|
||||
{}, // output_name
|
||||
|
||||
{
|
||||
video_t::dd_t::config_option_e::verify_only, // configuration_option
|
||||
video_t::dd_t::config_option_e::disabled, // configuration_option
|
||||
video_t::dd_t::resolution_option_e::automatic, // resolution_option
|
||||
{}, // manual_resolution
|
||||
video_t::dd_t::refresh_rate_option_e::automatic, // refresh_rate_option
|
||||
{}, // manual_refresh_rate
|
||||
video_t::dd_t::hdr_option_e::automatic, // hdr_option
|
||||
3s, // config_revert_delay
|
||||
{}, // config_revert_on_disconnect
|
||||
{}, // mode_remapping
|
||||
{} // wa
|
||||
}, // display_device
|
||||
|
||||
1 // min_fps_factor
|
||||
1, // min_fps_factor
|
||||
0 // max_bitrate
|
||||
};
|
||||
|
||||
audio_t audio {
|
||||
{}, // audio_sink
|
||||
{}, // virtual_sink
|
||||
true, // stream audio
|
||||
true, // install_steam_drivers
|
||||
};
|
||||
|
||||
@@ -515,13 +539,13 @@ namespace config {
|
||||
|
||||
input_t input {
|
||||
{
|
||||
{ 0x10, 0xA0 },
|
||||
{ 0x11, 0xA2 },
|
||||
{ 0x12, 0xA4 },
|
||||
{0x10, 0xA0},
|
||||
{0x11, 0xA2},
|
||||
{0x12, 0xA4},
|
||||
},
|
||||
-1ms, // back_button_timeout
|
||||
500ms, // key_repeat_delay
|
||||
std::chrono::duration<double> { 1 / 24.9 }, // key_repeat_period
|
||||
std::chrono::duration<double> {1 / 24.9}, // key_repeat_period
|
||||
|
||||
{
|
||||
platf::supported_gamepads(nullptr).front().name.data(),
|
||||
@@ -556,23 +580,19 @@ namespace config {
|
||||
{}, // prep commands
|
||||
};
|
||||
|
||||
bool
|
||||
endline(char ch) {
|
||||
bool endline(char ch) {
|
||||
return ch == '\r' || ch == '\n';
|
||||
}
|
||||
|
||||
bool
|
||||
space_tab(char ch) {
|
||||
bool space_tab(char ch) {
|
||||
return ch == ' ' || ch == '\t';
|
||||
}
|
||||
|
||||
bool
|
||||
whitespace(char ch) {
|
||||
bool whitespace(char ch) {
|
||||
return space_tab(ch) || endline(ch);
|
||||
}
|
||||
|
||||
std::string
|
||||
to_string(const char *begin, const char *end) {
|
||||
std::string to_string(const char *begin, const char *end) {
|
||||
std::string result;
|
||||
|
||||
KITTY_WHILE_LOOP(auto pos = begin, pos != end, {
|
||||
@@ -587,9 +607,8 @@ namespace config {
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class It>
|
||||
It
|
||||
skip_list(It skipper, It end) {
|
||||
template<class It>
|
||||
It skip_list(It skipper, It end) {
|
||||
int stack = 1;
|
||||
while (skipper != end && stack) {
|
||||
if (*skipper == '[') {
|
||||
@@ -608,7 +627,7 @@ namespace config {
|
||||
std::pair<
|
||||
std::string_view::const_iterator,
|
||||
std::optional<std::pair<std::string, std::string>>>
|
||||
parse_option(std::string_view::const_iterator begin, std::string_view::const_iterator end) {
|
||||
parse_option(std::string_view::const_iterator begin, std::string_view::const_iterator end) {
|
||||
begin = std::find_if_not(begin, end, whitespace);
|
||||
auto endl = std::find_if(begin, end, endline);
|
||||
auto endc = std::find(begin, endl, '#');
|
||||
@@ -629,20 +648,24 @@ namespace config {
|
||||
// Lists might contain newlines
|
||||
if (*begin_val == '[') {
|
||||
endl = skip_list(begin_val + 1, end);
|
||||
if (endl == end) {
|
||||
std::cout << "Warning: Config option ["sv << to_string(begin, end_name) << "] Missing ']'"sv;
|
||||
|
||||
// Check if we reached the end of the file without finding a closing bracket
|
||||
// We know we have a valid closing bracket if:
|
||||
// 1. We didn't reach the end, or
|
||||
// 2. We reached the end but the last character was the matching closing bracket
|
||||
if (endl == end && end == begin_val + 1) {
|
||||
BOOST_LOG(warning) << "config: Missing ']' in config option: " << to_string(begin, end_name);
|
||||
return std::make_pair(endl, std::nullopt);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(
|
||||
endl,
|
||||
std::make_pair(to_string(begin, end_name), to_string(begin_val, endl)));
|
||||
std::make_pair(to_string(begin, end_name), to_string(begin_val, endl))
|
||||
);
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string>
|
||||
parse_config(const std::string_view &file_content) {
|
||||
std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content) {
|
||||
std::unordered_map<std::string, std::string> vars;
|
||||
|
||||
auto pos = std::begin(file_content);
|
||||
@@ -667,8 +690,7 @@ namespace config {
|
||||
return vars;
|
||||
}
|
||||
|
||||
void
|
||||
string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
|
||||
void string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
|
||||
auto it = vars.find(name);
|
||||
if (it == std::end(vars)) {
|
||||
return;
|
||||
@@ -679,9 +701,8 @@ namespace config {
|
||||
vars.erase(it);
|
||||
}
|
||||
|
||||
template <typename T, typename F>
|
||||
void
|
||||
generic_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, T &input, F &&f) {
|
||||
template<typename T, typename F>
|
||||
void generic_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, T &input, F &&f) {
|
||||
std::string tmp;
|
||||
string_f(vars, name, tmp);
|
||||
if (!tmp.empty()) {
|
||||
@@ -689,8 +710,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
string_restricted_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input, const std::vector<std::string_view> &allowed_vals) {
|
||||
void string_restricted_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input, const std::vector<std::string_view> &allowed_vals) {
|
||||
std::string temp;
|
||||
string_f(vars, name, temp);
|
||||
|
||||
@@ -702,8 +722,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, fs::path &input) {
|
||||
void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, fs::path &input) {
|
||||
// appdata needs to be retrieved once only
|
||||
static auto appdata = platf::appdata();
|
||||
|
||||
@@ -727,8 +746,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
|
||||
void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
|
||||
fs::path temp = input;
|
||||
|
||||
path_f(vars, name, temp);
|
||||
@@ -736,8 +754,7 @@ namespace config {
|
||||
input = temp.string();
|
||||
}
|
||||
|
||||
void
|
||||
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
|
||||
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
|
||||
auto it = vars.find(name);
|
||||
|
||||
if (it == std::end(vars)) {
|
||||
@@ -754,16 +771,14 @@ namespace config {
|
||||
// If that integer is in hexadecimal
|
||||
if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
|
||||
input = util::from_hex<int>(val.substr(2));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
input = util::from_view(val);
|
||||
}
|
||||
|
||||
vars.erase(it);
|
||||
}
|
||||
|
||||
void
|
||||
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input) {
|
||||
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input) {
|
||||
auto it = vars.find(name);
|
||||
|
||||
if (it == std::end(vars)) {
|
||||
@@ -780,17 +795,15 @@ namespace config {
|
||||
// If that integer is in hexadecimal
|
||||
if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
|
||||
input = util::from_hex<int>(val.substr(2));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
input = util::from_view(val);
|
||||
}
|
||||
|
||||
vars.erase(it);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void
|
||||
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, F &&f) {
|
||||
template<class F>
|
||||
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, F &&f) {
|
||||
std::string tmp;
|
||||
string_f(vars, name, tmp);
|
||||
if (!tmp.empty()) {
|
||||
@@ -798,9 +811,8 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void
|
||||
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input, F &&f) {
|
||||
template<class F>
|
||||
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input, F &&f) {
|
||||
std::string tmp;
|
||||
string_f(vars, name, tmp);
|
||||
if (!tmp.empty()) {
|
||||
@@ -808,8 +820,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
int_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, const std::pair<int, int> &range) {
|
||||
void int_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, const std::pair<int, int> &range) {
|
||||
int temp = input;
|
||||
|
||||
int_f(vars, name, temp);
|
||||
@@ -820,9 +831,10 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
to_bool(std::string &boolean) {
|
||||
std::for_each(std::begin(boolean), std::end(boolean), [](char ch) { return (char) std::tolower(ch); });
|
||||
bool to_bool(std::string &boolean) {
|
||||
std::for_each(std::begin(boolean), std::end(boolean), [](char ch) {
|
||||
return (char) std::tolower(ch);
|
||||
});
|
||||
|
||||
return boolean == "true"sv ||
|
||||
boolean == "yes"sv ||
|
||||
@@ -832,8 +844,7 @@ namespace config {
|
||||
(std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean));
|
||||
}
|
||||
|
||||
void
|
||||
bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, bool &input) {
|
||||
void bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, bool &input) {
|
||||
std::string tmp;
|
||||
string_f(vars, name, tmp);
|
||||
|
||||
@@ -844,8 +855,7 @@ namespace config {
|
||||
input = to_bool(tmp);
|
||||
}
|
||||
|
||||
void
|
||||
double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) {
|
||||
void double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) {
|
||||
std::string tmp;
|
||||
string_f(vars, name, tmp);
|
||||
|
||||
@@ -863,8 +873,7 @@ namespace config {
|
||||
input = val;
|
||||
}
|
||||
|
||||
void
|
||||
double_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input, const std::pair<double, double> &range) {
|
||||
void double_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input, const std::pair<double, double> &range) {
|
||||
double temp = input;
|
||||
|
||||
double_f(vars, name, temp);
|
||||
@@ -875,8 +884,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
list_string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &input) {
|
||||
void list_string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &input) {
|
||||
std::string string;
|
||||
string_f(vars, name, string);
|
||||
|
||||
@@ -900,15 +908,12 @@ namespace config {
|
||||
while (pos < std::cend(string)) {
|
||||
if (*pos == '[') {
|
||||
pos = skip_list(pos + 1, std::cend(string)) + 1;
|
||||
}
|
||||
else if (*pos == ']') {
|
||||
} else if (*pos == ']') {
|
||||
break;
|
||||
}
|
||||
else if (*pos == ',') {
|
||||
} else if (*pos == ',') {
|
||||
input.emplace_back(begin, pos);
|
||||
pos = begin = std::find_if_not(pos + 1, std::cend(string), whitespace);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
@@ -918,8 +923,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
list_prep_cmd_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<prep_cmd_t> &input) {
|
||||
void list_prep_cmd_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<prep_cmd_t> &input) {
|
||||
std::string string;
|
||||
string_f(vars, name, string);
|
||||
|
||||
@@ -945,8 +949,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
list_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<int> &input) {
|
||||
void list_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<int> &input) {
|
||||
std::vector<std::string> list;
|
||||
list_string_f(vars, name, list);
|
||||
|
||||
@@ -972,22 +975,20 @@ namespace config {
|
||||
// If the integer is a hexadecimal
|
||||
if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
|
||||
tmp = util::from_hex<int>(val.substr(2));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
tmp = util::from_view(val);
|
||||
}
|
||||
input.emplace_back(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
map_int_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::unordered_map<int, int> &input) {
|
||||
void map_int_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::unordered_map<int, int> &input) {
|
||||
std::vector<int> list;
|
||||
list_int_f(vars, name, list);
|
||||
|
||||
// The list needs to be a multiple of 2
|
||||
if (list.size() % 2) {
|
||||
std::cout << "Warning: expected "sv << name << " to have a multiple of two elements --> not "sv << list.size() << std::endl;
|
||||
BOOST_LOG(warning) << "config: expected "sv << name << " to have a multiple of two elements --> not "sv << list.size();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1000,8 +1001,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
apply_flags(const char *line) {
|
||||
int apply_flags(const char *line) {
|
||||
int ret = 0;
|
||||
while (*line != '\0') {
|
||||
switch (*line) {
|
||||
@@ -1018,7 +1018,7 @@ namespace config {
|
||||
config::sunshine.flags[config::flag::UPNP].flip();
|
||||
break;
|
||||
default:
|
||||
std::cout << "Warning: Unrecognized flag: ["sv << *line << ']' << std::endl;
|
||||
BOOST_LOG(warning) << "config: Unrecognized flag: ["sv << *line << ']' << std::endl;
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
@@ -1028,8 +1028,7 @@ namespace config {
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::string_view> &
|
||||
get_supported_gamepad_options() {
|
||||
std::vector<std::string_view> &get_supported_gamepad_options() {
|
||||
const auto options = platf::supported_gamepads(nullptr);
|
||||
static std::vector<std::string_view> opts {};
|
||||
opts.reserve(options.size());
|
||||
@@ -1039,19 +1038,19 @@ namespace config {
|
||||
return opts;
|
||||
}
|
||||
|
||||
void
|
||||
apply_config(std::unordered_map<std::string, std::string> &&vars) {
|
||||
void apply_config(std::unordered_map<std::string, std::string> &&vars) {
|
||||
if (!fs::exists(stream.file_apps.c_str())) {
|
||||
fs::copy_file(SUNSHINE_ASSETS_DIR "/apps.json", stream.file_apps);
|
||||
}
|
||||
|
||||
for (auto &[name, val] : vars) {
|
||||
std::cout << "["sv << name << "] -- ["sv << val << ']' << std::endl;
|
||||
BOOST_LOG(info) << "config: '"sv << name << "' = "sv << val;
|
||||
modified_config_settings[name] = val;
|
||||
}
|
||||
|
||||
int_f(vars, "qp", video.qp);
|
||||
int_between_f(vars, "hevc_mode", video.hevc_mode, { 0, 3 });
|
||||
int_between_f(vars, "av1_mode", video.av1_mode, { 0, 3 });
|
||||
int_between_f(vars, "hevc_mode", video.hevc_mode, {0, 3});
|
||||
int_between_f(vars, "av1_mode", video.av1_mode, {0, 3});
|
||||
int_f(vars, "min_threads", video.min_threads);
|
||||
string_f(vars, "sw_preset", video.sw.sw_preset);
|
||||
if (!video.sw.sw_preset.empty()) {
|
||||
@@ -1059,8 +1058,8 @@ namespace config {
|
||||
}
|
||||
string_f(vars, "sw_tune", video.sw.sw_tune);
|
||||
|
||||
int_between_f(vars, "nvenc_preset", video.nv.quality_preset, { 1, 7 });
|
||||
int_between_f(vars, "nvenc_vbv_increase", video.nv.vbv_percentage_increase, { 0, 400 });
|
||||
int_between_f(vars, "nvenc_preset", video.nv.quality_preset, {1, 7});
|
||||
int_between_f(vars, "nvenc_vbv_increase", video.nv.vbv_percentage_increase, {0, 400});
|
||||
bool_f(vars, "nvenc_spatial_aq", video.nv.adaptive_quantization);
|
||||
generic_f(vars, "nvenc_twopass", video.nv.two_pass, nv::twopass_from_view);
|
||||
bool_f(vars, "nvenc_h264_cavlc", video.nv.h264_cavlc);
|
||||
@@ -1131,15 +1130,21 @@ namespace config {
|
||||
generic_f(vars, "dd_hdr_option", video.dd.hdr_option, dd::hdr_option_from_view);
|
||||
{
|
||||
int value = -1;
|
||||
int_between_f(vars, "dd_config_revert_delay", value, { 0, std::numeric_limits<int>::max() });
|
||||
int_between_f(vars, "dd_config_revert_delay", value, {0, std::numeric_limits<int>::max()});
|
||||
if (value >= 0) {
|
||||
video.dd.config_revert_delay = std::chrono::milliseconds { value };
|
||||
video.dd.config_revert_delay = std::chrono::milliseconds {value};
|
||||
}
|
||||
}
|
||||
bool_f(vars, "dd_config_revert_on_disconnect", video.dd.config_revert_on_disconnect);
|
||||
generic_f(vars, "dd_mode_remapping", video.dd.mode_remapping, dd::mode_remapping_from_view);
|
||||
bool_f(vars, "dd_wa_hdr_toggle", video.dd.wa.hdr_toggle);
|
||||
{
|
||||
int value = 0;
|
||||
int_between_f(vars, "dd_wa_hdr_toggle_delay", value, {0, 3000});
|
||||
video.dd.wa.hdr_toggle_delay = std::chrono::milliseconds {value};
|
||||
}
|
||||
|
||||
int_between_f(vars, "min_fps_factor", video.min_fps_factor, { 1, 3 });
|
||||
int_between_f(vars, "min_fps_factor", video.min_fps_factor, {1, 3});
|
||||
int_f(vars, "max_bitrate", video.max_bitrate);
|
||||
|
||||
path_f(vars, "pkey", nvhttp.pkey);
|
||||
path_f(vars, "cert", nvhttp.cert);
|
||||
@@ -1156,21 +1161,22 @@ namespace config {
|
||||
|
||||
string_f(vars, "audio_sink", audio.sink);
|
||||
string_f(vars, "virtual_sink", audio.virtual_sink);
|
||||
bool_f(vars, "stream_audio", audio.stream);
|
||||
bool_f(vars, "install_steam_audio_drivers", audio.install_steam_drivers);
|
||||
|
||||
string_restricted_f(vars, "origin_web_ui_allowed", nvhttp.origin_web_ui_allowed, { "pc"sv, "lan"sv, "wan"sv });
|
||||
string_restricted_f(vars, "origin_web_ui_allowed", nvhttp.origin_web_ui_allowed, {"pc"sv, "lan"sv, "wan"sv});
|
||||
|
||||
int to = -1;
|
||||
int_between_f(vars, "ping_timeout", to, { -1, std::numeric_limits<int>::max() });
|
||||
int_between_f(vars, "ping_timeout", to, {-1, std::numeric_limits<int>::max()});
|
||||
if (to != -1) {
|
||||
stream.ping_timeout = std::chrono::milliseconds(to);
|
||||
}
|
||||
|
||||
int_between_f(vars, "lan_encryption_mode", stream.lan_encryption_mode, { 0, 2 });
|
||||
int_between_f(vars, "wan_encryption_mode", stream.wan_encryption_mode, { 0, 2 });
|
||||
int_between_f(vars, "lan_encryption_mode", stream.lan_encryption_mode, {0, 2});
|
||||
int_between_f(vars, "wan_encryption_mode", stream.wan_encryption_mode, {0, 2});
|
||||
|
||||
path_f(vars, "file_apps", stream.file_apps);
|
||||
int_between_f(vars, "fec_percentage", stream.fec_percentage, { 1, 255 });
|
||||
int_between_f(vars, "fec_percentage", stream.fec_percentage, {1, 255});
|
||||
|
||||
map_int_int_f(vars, "keybindings"s, input.keybindings);
|
||||
|
||||
@@ -1187,20 +1193,20 @@ namespace config {
|
||||
int_f(vars, "back_button_timeout", to);
|
||||
|
||||
if (to > std::numeric_limits<int>::min()) {
|
||||
input.back_button_timeout = std::chrono::milliseconds { to };
|
||||
input.back_button_timeout = std::chrono::milliseconds {to};
|
||||
}
|
||||
|
||||
double repeat_frequency { 0 };
|
||||
double_between_f(vars, "key_repeat_frequency", repeat_frequency, { 0, std::numeric_limits<double>::max() });
|
||||
double repeat_frequency {0};
|
||||
double_between_f(vars, "key_repeat_frequency", repeat_frequency, {0, std::numeric_limits<double>::max()});
|
||||
|
||||
if (repeat_frequency > 0) {
|
||||
config::input.key_repeat_period = std::chrono::duration<double> { 1 / repeat_frequency };
|
||||
config::input.key_repeat_period = std::chrono::duration<double> {1 / repeat_frequency};
|
||||
}
|
||||
|
||||
to = -1;
|
||||
int_f(vars, "key_repeat_delay", to);
|
||||
if (to >= 0) {
|
||||
input.key_repeat_delay = std::chrono::milliseconds { to };
|
||||
input.key_repeat_delay = std::chrono::milliseconds {to};
|
||||
}
|
||||
|
||||
string_restricted_f(vars, "gamepad"s, input.gamepad, get_supported_gamepad_options());
|
||||
@@ -1220,10 +1226,10 @@ namespace config {
|
||||
bool_f(vars, "notify_pre_releases", sunshine.notify_pre_releases);
|
||||
|
||||
int port = sunshine.port;
|
||||
int_between_f(vars, "port"s, port, { 1024 + nvhttp::PORT_HTTPS, 65535 - rtsp_stream::RTSP_SETUP_PORT });
|
||||
int_between_f(vars, "port"s, port, {1024 + nvhttp::PORT_HTTPS, 65535 - rtsp_stream::RTSP_SETUP_PORT});
|
||||
sunshine.port = (std::uint16_t) port;
|
||||
|
||||
string_restricted_f(vars, "address_family", sunshine.address_family, { "ipv4"sv, "both"sv });
|
||||
string_restricted_f(vars, "address_family", sunshine.address_family, {"ipv4"sv, "both"sv});
|
||||
|
||||
bool upnp = false;
|
||||
bool_f(vars, "upnp"s, upnp);
|
||||
@@ -1259,26 +1265,19 @@ namespace config {
|
||||
if (!log_level_string.empty()) {
|
||||
if (log_level_string == "verbose"sv) {
|
||||
sunshine.min_log_level = 0;
|
||||
}
|
||||
else if (log_level_string == "debug"sv) {
|
||||
} else if (log_level_string == "debug"sv) {
|
||||
sunshine.min_log_level = 1;
|
||||
}
|
||||
else if (log_level_string == "info"sv) {
|
||||
} else if (log_level_string == "info"sv) {
|
||||
sunshine.min_log_level = 2;
|
||||
}
|
||||
else if (log_level_string == "warning"sv) {
|
||||
} else if (log_level_string == "warning"sv) {
|
||||
sunshine.min_log_level = 3;
|
||||
}
|
||||
else if (log_level_string == "error"sv) {
|
||||
} else if (log_level_string == "error"sv) {
|
||||
sunshine.min_log_level = 4;
|
||||
}
|
||||
else if (log_level_string == "fatal"sv) {
|
||||
} else if (log_level_string == "fatal"sv) {
|
||||
sunshine.min_log_level = 5;
|
||||
}
|
||||
else if (log_level_string == "none"sv) {
|
||||
} else if (log_level_string == "none"sv) {
|
||||
sunshine.min_log_level = 6;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// accept digit directly
|
||||
auto val = log_level_string[0];
|
||||
if (val >= '0' && val < '7') {
|
||||
@@ -1301,8 +1300,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
parse(int argc, char *argv[]) {
|
||||
int parse(int argc, char *argv[]) {
|
||||
std::unordered_map<std::string, std::string> cmd_vars;
|
||||
#ifdef _WIN32
|
||||
bool shortcut_launch = false;
|
||||
@@ -1319,8 +1317,7 @@ namespace config {
|
||||
#ifdef _WIN32
|
||||
else if (line == "--shortcut"sv) {
|
||||
shortcut_launch = true;
|
||||
}
|
||||
else if (line == "--shortcut-admin"sv) {
|
||||
} else if (line == "--shortcut-admin"sv) {
|
||||
service_admin_launch = true;
|
||||
}
|
||||
#endif
|
||||
@@ -1336,15 +1333,13 @@ namespace config {
|
||||
logging::print_help(*argv);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
auto line_end = line + strlen(line);
|
||||
|
||||
auto pos = std::find(line, line_end, '=');
|
||||
if (pos == line_end) {
|
||||
sunshine.config_file = line;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
TUPLE_EL(var, 1, parse_option(line, line_end));
|
||||
if (!var) {
|
||||
logging::print_help(*argv);
|
||||
@@ -1370,7 +1365,7 @@ namespace config {
|
||||
|
||||
// Create empty config file if it does not exist
|
||||
if (!fs::exists(sunshine.config_file)) {
|
||||
std::ofstream { sunshine.config_file };
|
||||
std::ofstream {sunshine.config_file};
|
||||
}
|
||||
|
||||
// Read config file
|
||||
@@ -1385,11 +1380,9 @@ namespace config {
|
||||
// the path is incorrect or inaccessible.
|
||||
apply_config(std::move(vars));
|
||||
config_loaded = true;
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error &err) {
|
||||
} catch (const std::filesystem::filesystem_error &err) {
|
||||
BOOST_LOG(fatal) << "Failed to apply config: "sv << err.what();
|
||||
}
|
||||
catch (const boost::filesystem::filesystem_error &err) {
|
||||
} catch (const boost::filesystem::filesystem_error &err) {
|
||||
BOOST_LOG(fatal) << "Failed to apply config: "sv << err.what();
|
||||
}
|
||||
|
||||
@@ -1419,7 +1412,7 @@ namespace config {
|
||||
// Always return 1 to ensure Sunshine doesn't start normally
|
||||
return 1;
|
||||
}
|
||||
else if (shortcut_launch) {
|
||||
if (shortcut_launch) {
|
||||
if (!service_ctrl::is_service_running()) {
|
||||
// If the service isn't running, relaunch ourselves as admin to start it
|
||||
WCHAR executable[MAX_PATH];
|
||||
@@ -1434,7 +1427,7 @@ namespace config {
|
||||
shell_exec_info.nShow = SW_NORMAL;
|
||||
if (!ShellExecuteExW(&shell_exec_info)) {
|
||||
auto winerr = GetLastError();
|
||||
std::cout << "Error: ShellExecuteEx() failed:"sv << winerr << std::endl;
|
||||
BOOST_LOG(error) << "Failed executing shell command: " << winerr << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
31
src/config.h
31
src/config.h
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
@@ -11,9 +12,13 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
// local includes
|
||||
#include "nvenc/nvenc_config.h"
|
||||
|
||||
namespace config {
|
||||
// track modified config options
|
||||
inline std::unordered_map<std::string, std::string> modified_config_settings;
|
||||
|
||||
struct video_t {
|
||||
// ffmpeg params
|
||||
int qp; // higher == more compression and less quality
|
||||
@@ -22,6 +27,7 @@ namespace config {
|
||||
int av1_mode;
|
||||
|
||||
int min_threads; // Minimum number of threads/slices for CPU encoding
|
||||
|
||||
struct {
|
||||
std::string sw_preset;
|
||||
std::string sw_tune;
|
||||
@@ -81,7 +87,7 @@ namespace config {
|
||||
|
||||
struct dd_t {
|
||||
struct workarounds_t {
|
||||
bool hdr_toggle; ///< Specify whether to apply HDR high-contrast color workaround.
|
||||
std::chrono::milliseconds hdr_toggle_delay; ///< Specify whether to apply HDR high-contrast color workaround and what delay to use.
|
||||
};
|
||||
|
||||
enum class config_option_e {
|
||||
@@ -129,16 +135,19 @@ namespace config {
|
||||
std::string manual_refresh_rate; ///< Manual refresh rate in case `refresh_rate_option == refresh_rate_option_e::manual`.
|
||||
hdr_option_e hdr_option;
|
||||
std::chrono::milliseconds config_revert_delay; ///< Time to wait until settings are reverted (after stream ends/app exists).
|
||||
bool config_revert_on_disconnect; ///< Specify whether to revert display configuration on client disconnect.
|
||||
mode_remapping_t mode_remapping;
|
||||
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
|
||||
};
|
||||
|
||||
struct audio_t {
|
||||
std::string sink;
|
||||
std::string virtual_sink;
|
||||
bool stream;
|
||||
bool install_steam_drivers;
|
||||
};
|
||||
|
||||
@@ -204,17 +213,25 @@ namespace config {
|
||||
CONST_PIN, ///< Use "universal" pin
|
||||
FLAG_SIZE ///< Number of flags
|
||||
};
|
||||
}
|
||||
} // namespace flag
|
||||
|
||||
struct prep_cmd_t {
|
||||
prep_cmd_t(std::string &&do_cmd, std::string &&undo_cmd, bool &&elevated):
|
||||
do_cmd(std::move(do_cmd)), undo_cmd(std::move(undo_cmd)), elevated(std::move(elevated)) {}
|
||||
do_cmd(std::move(do_cmd)),
|
||||
undo_cmd(std::move(undo_cmd)),
|
||||
elevated(std::move(elevated)) {
|
||||
}
|
||||
|
||||
explicit prep_cmd_t(std::string &&do_cmd, bool &&elevated):
|
||||
do_cmd(std::move(do_cmd)), elevated(std::move(elevated)) {}
|
||||
do_cmd(std::move(do_cmd)),
|
||||
elevated(std::move(elevated)) {
|
||||
}
|
||||
|
||||
std::string do_cmd;
|
||||
std::string undo_cmd;
|
||||
bool elevated;
|
||||
};
|
||||
|
||||
struct sunshine_t {
|
||||
std::string locale;
|
||||
int min_log_level;
|
||||
@@ -248,8 +265,6 @@ namespace config {
|
||||
extern input_t input;
|
||||
extern sunshine_t sunshine;
|
||||
|
||||
int
|
||||
parse(int argc, char *argv[]);
|
||||
std::unordered_map<std::string, std::string>
|
||||
parse_config(const std::string_view &file_content);
|
||||
int parse(int argc, char *argv[]);
|
||||
std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content);
|
||||
} // namespace config
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,34 +4,34 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
// standard includes
|
||||
#include <string>
|
||||
|
||||
// local includes
|
||||
#include "thread_safe.h"
|
||||
|
||||
#define WEB_DIR SUNSHINE_ASSETS_DIR "/web/"
|
||||
|
||||
namespace confighttp {
|
||||
constexpr auto PORT_HTTPS = 1;
|
||||
void
|
||||
start();
|
||||
void start();
|
||||
} // namespace confighttp
|
||||
|
||||
// mime types map
|
||||
const std::map<std::string, std::string> mime_types = {
|
||||
{ "css", "text/css" },
|
||||
{ "gif", "image/gif" },
|
||||
{ "htm", "text/html" },
|
||||
{ "html", "text/html" },
|
||||
{ "ico", "image/x-icon" },
|
||||
{ "jpeg", "image/jpeg" },
|
||||
{ "jpg", "image/jpeg" },
|
||||
{ "js", "application/javascript" },
|
||||
{ "json", "application/json" },
|
||||
{ "png", "image/png" },
|
||||
{ "svg", "image/svg+xml" },
|
||||
{ "ttf", "font/ttf" },
|
||||
{ "txt", "text/plain" },
|
||||
{ "woff2", "font/woff2" },
|
||||
{ "xml", "text/xml" },
|
||||
{"css", "text/css"},
|
||||
{"gif", "image/gif"},
|
||||
{"htm", "text/html"},
|
||||
{"html", "text/html"},
|
||||
{"ico", "image/x-icon"},
|
||||
{"jpeg", "image/jpeg"},
|
||||
{"jpg", "image/jpeg"},
|
||||
{"js", "application/javascript"},
|
||||
{"json", "application/json"},
|
||||
{"png", "image/png"},
|
||||
{"svg", "image/svg+xml"},
|
||||
{"ttf", "font/ttf"},
|
||||
{"txt", "text/plain"},
|
||||
{"woff2", "font/woff2"},
|
||||
{"xml", "text/xml"},
|
||||
};
|
||||
|
||||
140
src/crypto.cpp
140
src/crypto.cpp
@@ -2,29 +2,33 @@
|
||||
* @file src/crypto.cpp
|
||||
* @brief Definitions for cryptography functions.
|
||||
*/
|
||||
#include "crypto.h"
|
||||
// lib includes
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
|
||||
// local includes
|
||||
#include "crypto.h"
|
||||
|
||||
namespace crypto {
|
||||
using asn1_string_t = util::safe_ptr<ASN1_STRING, ASN1_STRING_free>;
|
||||
|
||||
cert_chain_t::cert_chain_t():
|
||||
_certs {}, _cert_ctx { X509_STORE_CTX_new() } {}
|
||||
void
|
||||
cert_chain_t::add(x509_t &&cert) {
|
||||
x509_store_t x509_store { X509_STORE_new() };
|
||||
_certs {},
|
||||
_cert_ctx {X509_STORE_CTX_new()} {
|
||||
}
|
||||
|
||||
void cert_chain_t::add(x509_t &&cert) {
|
||||
x509_store_t x509_store {X509_STORE_new()};
|
||||
|
||||
X509_STORE_add_cert(x509_store.get(), cert.get());
|
||||
_certs.emplace_back(std::make_pair(std::move(cert), std::move(x509_store)));
|
||||
}
|
||||
void
|
||||
cert_chain_t::clear() {
|
||||
|
||||
void cert_chain_t::clear() {
|
||||
_certs.clear();
|
||||
}
|
||||
|
||||
static int
|
||||
openssl_verify_cb(int ok, X509_STORE_CTX *ctx) {
|
||||
static int openssl_verify_cb(int ok, X509_STORE_CTX *ctx) {
|
||||
int err_code = X509_STORE_CTX_get_error(ctx);
|
||||
|
||||
switch (err_code) {
|
||||
@@ -52,8 +56,7 @@ namespace crypto {
|
||||
* @param cert The certificate to verify.
|
||||
* @return nullptr if the certificate is valid, otherwise an error string.
|
||||
*/
|
||||
const char *
|
||||
cert_chain_t::verify(x509_t::element_type *cert) {
|
||||
const char *cert_chain_t::verify(x509_t::element_type *cert) {
|
||||
int err_code = 0;
|
||||
for (auto &[_, x509_store] : _certs) {
|
||||
auto fg = util::fail_guard([this]() {
|
||||
@@ -86,8 +89,7 @@ namespace crypto {
|
||||
|
||||
namespace cipher {
|
||||
|
||||
static int
|
||||
init_decrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
|
||||
static int init_decrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
|
||||
ctx.reset(EVP_CIPHER_CTX_new());
|
||||
|
||||
if (!ctx) {
|
||||
@@ -110,8 +112,7 @@ namespace crypto {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
init_encrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
|
||||
static int init_encrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
|
||||
ctx.reset(EVP_CIPHER_CTX_new());
|
||||
|
||||
// Gen 7 servers use 128-bit AES ECB
|
||||
@@ -131,8 +132,7 @@ namespace crypto {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
init_encrypt_cbc(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
|
||||
static int init_encrypt_cbc(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
|
||||
ctx.reset(EVP_CIPHER_CTX_new());
|
||||
|
||||
// Gen 7 servers use 128-bit AES ECB
|
||||
@@ -145,8 +145,7 @@ namespace crypto {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
gcm_t::decrypt(const std::string_view &tagged_cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv) {
|
||||
int gcm_t::decrypt(const std::string_view &tagged_cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv) {
|
||||
if (!decrypt_ctx && init_decrypt_gcm(decrypt_ctx, &key, iv, padding)) {
|
||||
return -1;
|
||||
}
|
||||
@@ -185,8 +184,7 @@ namespace crypto {
|
||||
* The function handles the creation and initialization of the encryption context, and manages the encryption process.
|
||||
* The resulting ciphertext and the GCM tag are written into the tagged_cipher buffer.
|
||||
*/
|
||||
int
|
||||
gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tag, std::uint8_t *ciphertext, aes_t *iv) {
|
||||
int gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tag, std::uint8_t *ciphertext, aes_t *iv) {
|
||||
if (!encrypt_ctx && init_encrypt_gcm(encrypt_ctx, &key, iv, padding)) {
|
||||
return -1;
|
||||
}
|
||||
@@ -216,14 +214,12 @@ namespace crypto {
|
||||
return update_outlen + final_outlen;
|
||||
}
|
||||
|
||||
int
|
||||
gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv) {
|
||||
int gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv) {
|
||||
// This overload handles the common case of [GCM tag][cipher text] buffer layout
|
||||
return encrypt(plaintext, tagged_cipher, tagged_cipher + tag_size, iv);
|
||||
}
|
||||
|
||||
int
|
||||
ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
|
||||
int ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
|
||||
auto fg = util::fail_guard([this]() {
|
||||
EVP_CIPHER_CTX_reset(decrypt_ctx.get());
|
||||
});
|
||||
@@ -250,8 +246,7 @@ namespace crypto {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
ecb_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
|
||||
int ecb_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
|
||||
auto fg = util::fail_guard([this]() {
|
||||
EVP_CIPHER_CTX_reset(encrypt_ctx.get());
|
||||
});
|
||||
@@ -284,8 +279,7 @@ namespace crypto {
|
||||
* The function handles the creation and initialization of the encryption context, and manages the encryption process.
|
||||
* The resulting ciphertext is written into the cipher buffer.
|
||||
*/
|
||||
int
|
||||
cbc_t::encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv) {
|
||||
int cbc_t::encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv) {
|
||||
if (!encrypt_ctx && init_encrypt_cbc(encrypt_ctx, &key, iv, padding)) {
|
||||
return -1;
|
||||
}
|
||||
@@ -311,18 +305,20 @@ namespace crypto {
|
||||
}
|
||||
|
||||
ecb_t::ecb_t(const aes_t &key, bool padding):
|
||||
cipher_t { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_new(), key, padding } {}
|
||||
cipher_t {EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_new(), key, padding} {
|
||||
}
|
||||
|
||||
cbc_t::cbc_t(const aes_t &key, bool padding):
|
||||
cipher_t { nullptr, nullptr, key, padding } {}
|
||||
cipher_t {nullptr, nullptr, key, padding} {
|
||||
}
|
||||
|
||||
gcm_t::gcm_t(const crypto::aes_t &key, bool padding):
|
||||
cipher_t { nullptr, nullptr, key, padding } {}
|
||||
cipher_t {nullptr, nullptr, key, padding} {
|
||||
}
|
||||
|
||||
} // namespace cipher
|
||||
|
||||
aes_t
|
||||
gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
|
||||
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
|
||||
aes_t key(16);
|
||||
|
||||
std::string salt_pin;
|
||||
@@ -338,16 +334,14 @@ namespace crypto {
|
||||
return key;
|
||||
}
|
||||
|
||||
sha256_t
|
||||
hash(const std::string_view &plaintext) {
|
||||
sha256_t hash(const std::string_view &plaintext) {
|
||||
sha256_t hsh;
|
||||
EVP_Digest(plaintext.data(), plaintext.size(), hsh.data(), nullptr, EVP_sha256(), nullptr);
|
||||
return hsh;
|
||||
}
|
||||
|
||||
x509_t
|
||||
x509(const std::string_view &x) {
|
||||
bio_t io { BIO_new(BIO_s_mem()) };
|
||||
x509_t x509(const std::string_view &x) {
|
||||
bio_t io {BIO_new(BIO_s_mem())};
|
||||
|
||||
BIO_write(io.get(), x.data(), x.size());
|
||||
|
||||
@@ -357,9 +351,8 @@ namespace crypto {
|
||||
return p;
|
||||
}
|
||||
|
||||
pkey_t
|
||||
pkey(const std::string_view &k) {
|
||||
bio_t io { BIO_new(BIO_s_mem()) };
|
||||
pkey_t pkey(const std::string_view &k) {
|
||||
bio_t io {BIO_new(BIO_s_mem())};
|
||||
|
||||
BIO_write(io.get(), k.data(), k.size());
|
||||
|
||||
@@ -369,40 +362,36 @@ namespace crypto {
|
||||
return p;
|
||||
}
|
||||
|
||||
std::string
|
||||
pem(x509_t &x509) {
|
||||
bio_t bio { BIO_new(BIO_s_mem()) };
|
||||
std::string pem(x509_t &x509) {
|
||||
bio_t bio {BIO_new(BIO_s_mem())};
|
||||
|
||||
PEM_write_bio_X509(bio.get(), x509.get());
|
||||
BUF_MEM *mem_ptr;
|
||||
BIO_get_mem_ptr(bio.get(), &mem_ptr);
|
||||
|
||||
return { mem_ptr->data, mem_ptr->length };
|
||||
return {mem_ptr->data, mem_ptr->length};
|
||||
}
|
||||
|
||||
std::string
|
||||
pem(pkey_t &pkey) {
|
||||
bio_t bio { BIO_new(BIO_s_mem()) };
|
||||
std::string pem(pkey_t &pkey) {
|
||||
bio_t bio {BIO_new(BIO_s_mem())};
|
||||
|
||||
PEM_write_bio_PrivateKey(bio.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr);
|
||||
BUF_MEM *mem_ptr;
|
||||
BIO_get_mem_ptr(bio.get(), &mem_ptr);
|
||||
|
||||
return { mem_ptr->data, mem_ptr->length };
|
||||
return {mem_ptr->data, mem_ptr->length};
|
||||
}
|
||||
|
||||
std::string_view
|
||||
signature(const x509_t &x) {
|
||||
std::string_view signature(const x509_t &x) {
|
||||
// X509_ALGOR *_ = nullptr;
|
||||
|
||||
const ASN1_BIT_STRING *asn1 = nullptr;
|
||||
X509_get0_signature(&asn1, nullptr, x.get());
|
||||
|
||||
return { (const char *) asn1->data, (std::size_t) asn1->length };
|
||||
return {(const char *) asn1->data, (std::size_t) asn1->length};
|
||||
}
|
||||
|
||||
std::string
|
||||
rand(std::size_t bytes) {
|
||||
std::string rand(std::size_t bytes) {
|
||||
std::string r;
|
||||
r.resize(bytes);
|
||||
|
||||
@@ -411,9 +400,8 @@ namespace crypto {
|
||||
return r;
|
||||
}
|
||||
|
||||
std::vector<uint8_t>
|
||||
sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
|
||||
md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||
std::vector<uint8_t> sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
|
||||
md_ctx_t ctx {EVP_MD_CTX_create()};
|
||||
|
||||
if (EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, (EVP_PKEY *) pkey.get()) != 1) {
|
||||
return {};
|
||||
@@ -436,10 +424,9 @@ namespace crypto {
|
||||
return digest;
|
||||
}
|
||||
|
||||
creds_t
|
||||
gen_creds(const std::string_view &cn, std::uint32_t key_bits) {
|
||||
x509_t x509 { X509_new() };
|
||||
pkey_ctx_t ctx { EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr) };
|
||||
creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits) {
|
||||
x509_t x509 {X509_new()};
|
||||
pkey_ctx_t ctx {EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr)};
|
||||
pkey_t pkey;
|
||||
|
||||
EVP_PKEY_keygen_init(ctx.get());
|
||||
@@ -449,7 +436,7 @@ namespace crypto {
|
||||
X509_set_version(x509.get(), 2);
|
||||
|
||||
// Generate a real serial number to avoid SEC_ERROR_REUSED_ISSUER_AND_SERIAL with Firefox
|
||||
bignum_t serial { BN_new() };
|
||||
bignum_t serial {BN_new()};
|
||||
BN_rand(serial.get(), 159, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY); // 159 bits to fit in 20 bytes in DER format
|
||||
BN_set_negative(serial.get(), 0); // Serial numbers must be positive
|
||||
BN_to_ASN1_INTEGER(serial.get(), X509_get_serialNumber(x509.get()));
|
||||
@@ -459,8 +446,8 @@ namespace crypto {
|
||||
X509_gmtime_adj(X509_get_notBefore(x509.get()), 0);
|
||||
X509_gmtime_adj(X509_get_notAfter(x509.get()), 20 * year);
|
||||
#else
|
||||
asn1_string_t not_before { ASN1_STRING_dup(X509_get0_notBefore(x509.get())) };
|
||||
asn1_string_t not_after { ASN1_STRING_dup(X509_get0_notAfter(x509.get())) };
|
||||
asn1_string_t not_before {ASN1_STRING_dup(X509_get0_notBefore(x509.get()))};
|
||||
asn1_string_t not_after {ASN1_STRING_dup(X509_get0_notAfter(x509.get()))};
|
||||
|
||||
X509_gmtime_adj(not_before.get(), 0);
|
||||
X509_gmtime_adj(not_after.get(), 20 * year);
|
||||
@@ -472,26 +459,22 @@ namespace crypto {
|
||||
X509_set_pubkey(x509.get(), pkey.get());
|
||||
|
||||
auto name = X509_get_subject_name(x509.get());
|
||||
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
|
||||
(const std::uint8_t *) cn.data(), cn.size(),
|
||||
-1, 0);
|
||||
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const std::uint8_t *) cn.data(), cn.size(), -1, 0);
|
||||
|
||||
X509_set_issuer_name(x509.get(), name);
|
||||
X509_sign(x509.get(), pkey.get(), EVP_sha256());
|
||||
|
||||
return { pem(x509), pem(pkey) };
|
||||
return {pem(x509), pem(pkey)};
|
||||
}
|
||||
|
||||
std::vector<uint8_t>
|
||||
sign256(const pkey_t &pkey, const std::string_view &data) {
|
||||
std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data) {
|
||||
return sign(pkey, data, EVP_sha256());
|
||||
}
|
||||
|
||||
bool
|
||||
verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
|
||||
bool verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
|
||||
auto pkey = X509_get0_pubkey(x509.get());
|
||||
|
||||
md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||
md_ctx_t ctx {EVP_MD_CTX_create()};
|
||||
|
||||
if (EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pkey) != 1) {
|
||||
return false;
|
||||
@@ -508,18 +491,15 @@ namespace crypto {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) {
|
||||
bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) {
|
||||
return verify(x509, data, signature, EVP_sha256());
|
||||
}
|
||||
|
||||
void
|
||||
md_ctx_destroy(EVP_MD_CTX *ctx) {
|
||||
void md_ctx_destroy(EVP_MD_CTX *ctx) {
|
||||
EVP_MD_CTX_destroy(ctx);
|
||||
}
|
||||
|
||||
std::string
|
||||
rand_alphabet(std::size_t bytes, const std::string_view &alphabet) {
|
||||
std::string rand_alphabet(std::size_t bytes, const std::string_view &alphabet) {
|
||||
auto value = rand(bytes);
|
||||
|
||||
for (std::size_t i = 0; i != value.size(); ++i) {
|
||||
|
||||
85
src/crypto.h
85
src/crypto.h
@@ -4,12 +4,16 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <array>
|
||||
|
||||
// lib includes
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
// local includes
|
||||
#include "utility.h"
|
||||
|
||||
namespace crypto {
|
||||
@@ -18,8 +22,7 @@ namespace crypto {
|
||||
std::string pkey;
|
||||
};
|
||||
|
||||
void
|
||||
md_ctx_destroy(EVP_MD_CTX *);
|
||||
void md_ctx_destroy(EVP_MD_CTX *);
|
||||
|
||||
using sha256_t = std::array<std::uint8_t, SHA256_DIGEST_LENGTH>;
|
||||
|
||||
@@ -39,50 +42,33 @@ namespace crypto {
|
||||
* @param plaintext
|
||||
* @return The SHA-256 hash of the plaintext.
|
||||
*/
|
||||
sha256_t
|
||||
hash(const std::string_view &plaintext);
|
||||
sha256_t hash(const std::string_view &plaintext);
|
||||
|
||||
aes_t
|
||||
gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin);
|
||||
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin);
|
||||
x509_t x509(const std::string_view &x);
|
||||
pkey_t pkey(const std::string_view &k);
|
||||
std::string pem(x509_t &x509);
|
||||
std::string pem(pkey_t &pkey);
|
||||
|
||||
x509_t
|
||||
x509(const std::string_view &x);
|
||||
pkey_t
|
||||
pkey(const std::string_view &k);
|
||||
std::string
|
||||
pem(x509_t &x509);
|
||||
std::string
|
||||
pem(pkey_t &pkey);
|
||||
std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data);
|
||||
bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature);
|
||||
|
||||
std::vector<uint8_t>
|
||||
sign256(const pkey_t &pkey, const std::string_view &data);
|
||||
bool
|
||||
verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature);
|
||||
creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits);
|
||||
|
||||
creds_t
|
||||
gen_creds(const std::string_view &cn, std::uint32_t key_bits);
|
||||
std::string_view signature(const x509_t &x);
|
||||
|
||||
std::string_view
|
||||
signature(const x509_t &x);
|
||||
|
||||
std::string
|
||||
rand(std::size_t bytes);
|
||||
std::string
|
||||
rand_alphabet(std::size_t bytes,
|
||||
const std::string_view &alphabet = std::string_view { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-" });
|
||||
std::string rand(std::size_t bytes);
|
||||
std::string rand_alphabet(std::size_t bytes, const std::string_view &alphabet = std::string_view {"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-"});
|
||||
|
||||
class cert_chain_t {
|
||||
public:
|
||||
KITTY_DECL_CONSTR(cert_chain_t)
|
||||
|
||||
void
|
||||
add(x509_t &&cert);
|
||||
void add(x509_t &&cert);
|
||||
|
||||
void
|
||||
clear();
|
||||
void clear();
|
||||
|
||||
const char *
|
||||
verify(x509_t::element_type *cert);
|
||||
const char *verify(x509_t::element_type *cert);
|
||||
|
||||
private:
|
||||
std::vector<std::pair<x509_t, x509_store_t>> _certs;
|
||||
@@ -91,8 +77,8 @@ namespace crypto {
|
||||
|
||||
namespace cipher {
|
||||
constexpr std::size_t tag_size = 16;
|
||||
constexpr std::size_t
|
||||
round_to_pkcs7_padded(std::size_t size) {
|
||||
|
||||
constexpr std::size_t round_to_pkcs7_padded(std::size_t size) {
|
||||
return ((size + 15) / 16) * 16;
|
||||
}
|
||||
|
||||
@@ -110,23 +96,19 @@ namespace crypto {
|
||||
public:
|
||||
ecb_t() = default;
|
||||
ecb_t(ecb_t &&) noexcept = default;
|
||||
ecb_t &
|
||||
operator=(ecb_t &&) noexcept = default;
|
||||
ecb_t &operator=(ecb_t &&) noexcept = default;
|
||||
|
||||
ecb_t(const aes_t &key, bool padding = true);
|
||||
|
||||
int
|
||||
encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher);
|
||||
int
|
||||
decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
|
||||
int encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher);
|
||||
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
|
||||
};
|
||||
|
||||
class gcm_t: public cipher_t {
|
||||
public:
|
||||
gcm_t() = default;
|
||||
gcm_t(gcm_t &&) noexcept = default;
|
||||
gcm_t &
|
||||
operator=(gcm_t &&) noexcept = default;
|
||||
gcm_t &operator=(gcm_t &&) noexcept = default;
|
||||
|
||||
gcm_t(const crypto::aes_t &key, bool padding = true);
|
||||
|
||||
@@ -138,8 +120,7 @@ namespace crypto {
|
||||
* @param iv The initialization vector to be used for the encryption.
|
||||
* @return The total length of the ciphertext and GCM tag. Returns -1 in case of an error.
|
||||
*/
|
||||
int
|
||||
encrypt(const std::string_view &plaintext, std::uint8_t *tag, std::uint8_t *ciphertext, aes_t *iv);
|
||||
int encrypt(const std::string_view &plaintext, std::uint8_t *tag, std::uint8_t *ciphertext, aes_t *iv);
|
||||
|
||||
/**
|
||||
* @brief Encrypts the plaintext using AES GCM mode.
|
||||
@@ -149,19 +130,16 @@ namespace crypto {
|
||||
* @param iv The initialization vector to be used for the encryption.
|
||||
* @return The total length of the ciphertext and GCM tag written into tagged_cipher. Returns -1 in case of an error.
|
||||
*/
|
||||
int
|
||||
encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv);
|
||||
int encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv);
|
||||
|
||||
int
|
||||
decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv);
|
||||
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv);
|
||||
};
|
||||
|
||||
class cbc_t: public cipher_t {
|
||||
public:
|
||||
cbc_t() = default;
|
||||
cbc_t(cbc_t &&) noexcept = default;
|
||||
cbc_t &
|
||||
operator=(cbc_t &&) noexcept = default;
|
||||
cbc_t &operator=(cbc_t &&) noexcept = default;
|
||||
|
||||
cbc_t(const crypto::aes_t &key, bool padding = true);
|
||||
|
||||
@@ -173,8 +151,7 @@ namespace crypto {
|
||||
* @param iv The initialization vector to be used for the encryption.
|
||||
* @return The total length of the ciphertext written into cipher. Returns -1 in case of an error.
|
||||
*/
|
||||
int
|
||||
encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv);
|
||||
int encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv);
|
||||
};
|
||||
} // namespace cipher
|
||||
} // namespace crypto
|
||||
|
||||
@@ -29,15 +29,15 @@
|
||||
|
||||
namespace display_device {
|
||||
namespace {
|
||||
constexpr std::chrono::milliseconds DEFAULT_RETRY_INTERVAL { 5000 };
|
||||
constexpr std::chrono::milliseconds DEFAULT_RETRY_INTERVAL {5000};
|
||||
|
||||
/**
|
||||
* @brief A global for the settings manager interface and other settings whose lifetime is managed by `display_device::init(...)`.
|
||||
*/
|
||||
struct {
|
||||
std::mutex mutex {};
|
||||
std::chrono::milliseconds config_revert_delay { 0 };
|
||||
std::unique_ptr<RetryScheduler<SettingsManagerInterface>> sm_instance { nullptr };
|
||||
std::chrono::milliseconds config_revert_delay {0};
|
||||
std::unique_ptr<RetryScheduler<SettingsManagerInterface>> sm_instance {nullptr};
|
||||
} DD_DATA;
|
||||
|
||||
/**
|
||||
@@ -49,8 +49,7 @@ namespace display_device {
|
||||
*/
|
||||
class sunshine_audio_context_t: public AudioContextInterface {
|
||||
public:
|
||||
[[nodiscard]] bool
|
||||
capture() override {
|
||||
[[nodiscard]] bool capture() override {
|
||||
return context_scheduler.execute([](auto &audio_context) {
|
||||
// Explicitly releasing the context first in case it was not release yet so that it can be potentially cleaned up.
|
||||
audio_context = boost::none;
|
||||
@@ -61,8 +60,7 @@ namespace display_device {
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isCaptured() const override {
|
||||
[[nodiscard]] bool isCaptured() const override {
|
||||
return context_scheduler.execute([](const auto &audio_context) {
|
||||
if (audio_context) {
|
||||
// In case we still have context we need to check whether it was released or not.
|
||||
@@ -74,8 +72,7 @@ namespace display_device {
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
release() override {
|
||||
void release() override {
|
||||
context_scheduler.schedule([](auto &audio_context, auto &stop_token) {
|
||||
if (audio_context) {
|
||||
audio_context->released = true;
|
||||
@@ -93,7 +90,7 @@ namespace display_device {
|
||||
audio_context = boost::none;
|
||||
stop_token.requestStop();
|
||||
},
|
||||
SchedulerOptions { .m_sleep_durations = { 2s } });
|
||||
SchedulerOptions {.m_sleep_durations = {2s}});
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -102,20 +99,20 @@ namespace display_device {
|
||||
* @brief A reference to the audio context that will automatically extend the audio session.
|
||||
* @note It is auto-initialized here for convenience.
|
||||
*/
|
||||
decltype(audio::get_audio_ctx_ref()) audio_ctx_ref { audio::get_audio_ctx_ref() };
|
||||
decltype(audio::get_audio_ctx_ref()) audio_ctx_ref {audio::get_audio_ctx_ref()};
|
||||
|
||||
/**
|
||||
* @brief Will be set to true if the capture was released, but we still have to keep the context around, because the device is not available.
|
||||
*/
|
||||
bool released { false };
|
||||
bool released {false};
|
||||
|
||||
/**
|
||||
* @brief How many times to check if the audio sink is available before giving up.
|
||||
*/
|
||||
int retry_counter { 15 };
|
||||
int retry_counter {15};
|
||||
};
|
||||
|
||||
RetryScheduler<boost::optional<audio_context_t>> context_scheduler { std::make_unique<boost::optional<audio_context_t>>(boost::none) };
|
||||
RetryScheduler<boost::optional<audio_context_t>> context_scheduler {std::make_unique<boost::optional<audio_context_t>>(boost::none)};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -124,9 +121,8 @@ namespace display_device {
|
||||
* @param value String to be converted
|
||||
* @return Parsed unsigned integer.
|
||||
*/
|
||||
unsigned int
|
||||
stou(const std::string &value) {
|
||||
unsigned long result { std::stoul(value) };
|
||||
unsigned int stou(const std::string &value) {
|
||||
unsigned long result {std::stoul(value)};
|
||||
if (result > std::numeric_limits<unsigned int>::max()) {
|
||||
throw std::out_of_range("stou");
|
||||
}
|
||||
@@ -151,10 +147,9 @@ namespace display_device {
|
||||
* }
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
parse_resolution_string(const std::string &input, std::optional<Resolution> &output) {
|
||||
const std::string trimmed_input { boost::algorithm::trim_copy(input) };
|
||||
const std::regex resolution_regex { R"(^(\d+)x(\d+)$)" };
|
||||
bool parse_resolution_string(const std::string &input, std::optional<Resolution> &output) {
|
||||
const std::string trimmed_input {boost::algorithm::trim_copy(input)};
|
||||
const std::regex resolution_regex {R"(^(\d+)x(\d+)$)"};
|
||||
|
||||
if (std::smatch match; std::regex_match(trimmed_input, match, resolution_regex)) {
|
||||
try {
|
||||
@@ -163,16 +158,13 @@ namespace display_device {
|
||||
stou(match[2].str())
|
||||
};
|
||||
return true;
|
||||
}
|
||||
catch (const std::out_of_range &) {
|
||||
} catch (const std::out_of_range &) {
|
||||
BOOST_LOG(error) << "Failed to parse resolution string " << trimmed_input << " (number out of range).";
|
||||
}
|
||||
catch (const std::exception &err) {
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG(error) << "Failed to parse resolution string " << trimmed_input << ":\n"
|
||||
<< err.what();
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (trimmed_input.empty()) {
|
||||
output = std::nullopt;
|
||||
return true;
|
||||
@@ -203,16 +195,17 @@ namespace display_device {
|
||||
* }
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
parse_refresh_rate_string(const std::string &input, std::optional<FloatingPoint> &output, const bool allow_decimal_point = true) {
|
||||
static const auto is_zero { [](const auto &character) { return character == '0'; } };
|
||||
const std::string trimmed_input { boost::algorithm::trim_copy(input) };
|
||||
const std::regex refresh_rate_regex { allow_decimal_point ? R"(^(\d+)(?:\.(\d+))?$)" : R"(^(\d+)$)" };
|
||||
bool parse_refresh_rate_string(const std::string &input, std::optional<FloatingPoint> &output, const bool allow_decimal_point = true) {
|
||||
static const auto is_zero {[](const auto &character) {
|
||||
return character == '0';
|
||||
}};
|
||||
const std::string trimmed_input {boost::algorithm::trim_copy(input)};
|
||||
const std::regex refresh_rate_regex {allow_decimal_point ? R"(^(\d+)(?:\.(\d+))?$)" : R"(^(\d+)$)"};
|
||||
|
||||
if (std::smatch match; std::regex_match(trimmed_input, match, refresh_rate_regex)) {
|
||||
try {
|
||||
// Here we are trimming zeros from the string to possibly reduce out of bounds case
|
||||
std::string trimmed_match_1 { boost::algorithm::trim_left_copy_if(match[1].str(), is_zero) };
|
||||
std::string trimmed_match_1 {boost::algorithm::trim_left_copy_if(match[1].str(), is_zero)};
|
||||
if (trimmed_match_1.empty()) {
|
||||
trimmed_match_1 = "0"s; // Just in case ALL the string is full of zeros, we want to leave one
|
||||
}
|
||||
@@ -230,33 +223,29 @@ namespace display_device {
|
||||
// denominator = 1000
|
||||
|
||||
// We are essentially removing the decimal point here: 59.995 -> 59995
|
||||
const std::string numerator_str { trimmed_match_1 + trimmed_match_2 };
|
||||
const auto numerator { stou(numerator_str) };
|
||||
const std::string numerator_str {trimmed_match_1 + trimmed_match_2};
|
||||
const auto numerator {stou(numerator_str)};
|
||||
|
||||
// Here we are counting decimal places and calculating denominator: 10^decimal_places
|
||||
const auto denominator { static_cast<unsigned int>(std::pow(10, trimmed_match_2.size())) };
|
||||
const auto denominator {static_cast<unsigned int>(std::pow(10, trimmed_match_2.size()))};
|
||||
|
||||
output = Rational { numerator, denominator };
|
||||
}
|
||||
else {
|
||||
output = Rational {numerator, denominator};
|
||||
} else {
|
||||
// We do not have a decimal point, just a valid number.
|
||||
// For example:
|
||||
// 60:
|
||||
// numerator = 60
|
||||
// denominator = 1
|
||||
output = Rational { stou(trimmed_match_1), 1 };
|
||||
output = Rational {stou(trimmed_match_1), 1};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (const std::out_of_range &) {
|
||||
} catch (const std::out_of_range &) {
|
||||
BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << " (number out of range).";
|
||||
}
|
||||
catch (const std::exception &err) {
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << ":\n"
|
||||
<< err.what();
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (trimmed_input.empty()) {
|
||||
output = std::nullopt;
|
||||
return true;
|
||||
@@ -279,8 +268,7 @@ namespace display_device {
|
||||
* const auto device_prep_option = parse_device_prep_option(video_config);
|
||||
* @examples_end
|
||||
*/
|
||||
std::optional<SingleDisplayConfiguration::DevicePreparation>
|
||||
parse_device_prep_option(const config::video_t &video_config) {
|
||||
std::optional<SingleDisplayConfiguration::DevicePreparation> parse_device_prep_option(const config::video_t &video_config) {
|
||||
using enum config::video_t::dd_t::config_option_e;
|
||||
using enum SingleDisplayConfiguration::DevicePreparation;
|
||||
|
||||
@@ -315,44 +303,42 @@ namespace display_device {
|
||||
* const bool success = parse_resolution_option(video_config, *launch_session, config);
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
parse_resolution_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
|
||||
bool parse_resolution_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
|
||||
using resolution_option_e = config::video_t::dd_t::resolution_option_e;
|
||||
|
||||
switch (video_config.dd.resolution_option) {
|
||||
case resolution_option_e::automatic: {
|
||||
if (!session.enable_sops) {
|
||||
BOOST_LOG(warning) << R"(Sunshine is configured to change resolution automatically, but the "Optimize game settings" is not set in the client! Resolution will not be changed.)";
|
||||
}
|
||||
else if (session.width >= 0 && session.height >= 0) {
|
||||
config.m_resolution = Resolution {
|
||||
static_cast<unsigned int>(session.width),
|
||||
static_cast<unsigned int>(session.height)
|
||||
};
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(error) << "Resolution provided by client session config is invalid: " << session.width << "x" << session.height;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case resolution_option_e::manual: {
|
||||
if (!session.enable_sops) {
|
||||
BOOST_LOG(warning) << R"(Sunshine is configured to change resolution manually, but the "Optimize game settings" is not set in the client! Resolution will not be changed.)";
|
||||
}
|
||||
else {
|
||||
if (!parse_resolution_string(video_config.dd.manual_resolution, config.m_resolution)) {
|
||||
BOOST_LOG(error) << "Failed to parse manual resolution string!";
|
||||
case resolution_option_e::automatic:
|
||||
{
|
||||
if (!session.enable_sops) {
|
||||
BOOST_LOG(warning) << R"(Sunshine is configured to change resolution automatically, but the "Optimize game settings" is not set in the client! Resolution will not be changed.)";
|
||||
} else if (session.width >= 0 && session.height >= 0) {
|
||||
config.m_resolution = Resolution {
|
||||
static_cast<unsigned int>(session.width),
|
||||
static_cast<unsigned int>(session.height)
|
||||
};
|
||||
} else {
|
||||
BOOST_LOG(error) << "Resolution provided by client session config is invalid: " << session.width << "x" << session.height;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case resolution_option_e::manual:
|
||||
{
|
||||
if (!session.enable_sops) {
|
||||
BOOST_LOG(warning) << R"(Sunshine is configured to change resolution manually, but the "Optimize game settings" is not set in the client! Resolution will not be changed.)";
|
||||
} else {
|
||||
if (!parse_resolution_string(video_config.dd.manual_resolution, config.m_resolution)) {
|
||||
BOOST_LOG(error) << "Failed to parse manual resolution string!";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.m_resolution) {
|
||||
BOOST_LOG(error) << "Manual resolution must be specified!";
|
||||
return false;
|
||||
if (!config.m_resolution) {
|
||||
BOOST_LOG(error) << "Manual resolution must be specified!";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case resolution_option_e::disabled:
|
||||
break;
|
||||
}
|
||||
@@ -375,33 +361,33 @@ namespace display_device {
|
||||
* const bool success = parse_refresh_rate_option(video_config, *launch_session, config);
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
parse_refresh_rate_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
|
||||
bool parse_refresh_rate_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
|
||||
using refresh_rate_option_e = config::video_t::dd_t::refresh_rate_option_e;
|
||||
|
||||
switch (video_config.dd.refresh_rate_option) {
|
||||
case refresh_rate_option_e::automatic: {
|
||||
if (session.fps >= 0) {
|
||||
config.m_refresh_rate = Rational { static_cast<unsigned int>(session.fps), 1 };
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(error) << "FPS value provided by client session config is invalid: " << session.fps;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case refresh_rate_option_e::manual: {
|
||||
if (!parse_refresh_rate_string(video_config.dd.manual_refresh_rate, config.m_refresh_rate)) {
|
||||
BOOST_LOG(error) << "Failed to parse manual refresh rate string!";
|
||||
return false;
|
||||
case refresh_rate_option_e::automatic:
|
||||
{
|
||||
if (session.fps >= 0) {
|
||||
config.m_refresh_rate = Rational {static_cast<unsigned int>(session.fps), 1};
|
||||
} else {
|
||||
BOOST_LOG(error) << "FPS value provided by client session config is invalid: " << session.fps;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case refresh_rate_option_e::manual:
|
||||
{
|
||||
if (!parse_refresh_rate_string(video_config.dd.manual_refresh_rate, config.m_refresh_rate)) {
|
||||
BOOST_LOG(error) << "Failed to parse manual refresh rate string!";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.m_refresh_rate) {
|
||||
BOOST_LOG(error) << "Manual refresh rate must be specified!";
|
||||
return false;
|
||||
if (!config.m_refresh_rate) {
|
||||
BOOST_LOG(error) << "Manual refresh rate must be specified!";
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case refresh_rate_option_e::disabled:
|
||||
break;
|
||||
}
|
||||
@@ -422,8 +408,7 @@ namespace display_device {
|
||||
* const auto hdr_option = parse_hdr_option(video_config, *launch_session);
|
||||
* @examples_end
|
||||
*/
|
||||
std::optional<HdrState>
|
||||
parse_hdr_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
|
||||
std::optional<HdrState> parse_hdr_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
|
||||
using hdr_option_e = config::video_t::dd_t::hdr_option_e;
|
||||
|
||||
switch (video_config.dd.hdr_option) {
|
||||
@@ -450,11 +435,10 @@ namespace display_device {
|
||||
* @param video_config User's video related configuration.
|
||||
* @returns Enum value if remapping can be performed, null optional if remapping shall be skipped.
|
||||
*/
|
||||
std::optional<remapping_type_e>
|
||||
determine_remapping_type(const config::video_t &video_config) {
|
||||
std::optional<remapping_type_e> determine_remapping_type(const config::video_t &video_config) {
|
||||
using dd_t = config::video_t::dd_t;
|
||||
const bool auto_resolution { video_config.dd.resolution_option == dd_t::resolution_option_e::automatic };
|
||||
const bool auto_refresh_rate { video_config.dd.refresh_rate_option == dd_t::refresh_rate_option_e::automatic };
|
||||
const bool auto_resolution {video_config.dd.resolution_option == dd_t::resolution_option_e::automatic};
|
||||
const bool auto_refresh_rate {video_config.dd.refresh_rate_option == dd_t::refresh_rate_option_e::automatic};
|
||||
|
||||
if (auto_resolution && auto_refresh_rate) {
|
||||
return remapping_type_e::mixed;
|
||||
@@ -486,8 +470,7 @@ namespace display_device {
|
||||
* @param type Remapping type to check.
|
||||
* @returns True if resolution is to be mapped, false otherwise.
|
||||
*/
|
||||
bool
|
||||
is_resolution_mapped(const remapping_type_e type) {
|
||||
bool is_resolution_mapped(const remapping_type_e type) {
|
||||
return type == remapping_type_e::resolution_only || type == remapping_type_e::mixed;
|
||||
}
|
||||
|
||||
@@ -496,8 +479,7 @@ namespace display_device {
|
||||
* @param type Remapping type to check.
|
||||
* @returns True if FPS is to be mapped, false otherwise.
|
||||
*/
|
||||
bool
|
||||
is_fps_mapped(const remapping_type_e type) {
|
||||
bool is_fps_mapped(const remapping_type_e type) {
|
||||
return type == remapping_type_e::refresh_rate_only || type == remapping_type_e::mixed;
|
||||
}
|
||||
|
||||
@@ -507,17 +489,16 @@ namespace display_device {
|
||||
* @param type Specify which entry fields should be parsed.
|
||||
* @returns Parsed structure or null optional if a necessary field could not be parsed.
|
||||
*/
|
||||
std::optional<parsed_remapping_entry_t>
|
||||
parse_remapping_entry(const config::video_t::dd_t::mode_remapping_entry_t &entry, const remapping_type_e type) {
|
||||
std::optional<parsed_remapping_entry_t> parse_remapping_entry(const config::video_t::dd_t::mode_remapping_entry_t &entry, const remapping_type_e type) {
|
||||
parsed_remapping_entry_t result {};
|
||||
|
||||
if (is_resolution_mapped(type) && (!parse_resolution_string(entry.requested_resolution, result.requested_resolution) ||
|
||||
!parse_resolution_string(entry.final_resolution, result.final_resolution))) {
|
||||
!parse_resolution_string(entry.final_resolution, result.final_resolution))) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (is_fps_mapped(type) && (!parse_refresh_rate_string(entry.requested_fps, result.requested_fps, false) ||
|
||||
!parse_refresh_rate_string(entry.final_refresh_rate, result.final_refresh_rate))) {
|
||||
!parse_refresh_rate_string(entry.final_refresh_rate, result.final_refresh_rate))) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -539,14 +520,13 @@ namespace display_device {
|
||||
* const bool success = remap_display_mode_if_needed(video_config, *launch_session, config);
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
remap_display_mode_if_needed(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
|
||||
const auto remapping_type { determine_remapping_type(video_config) };
|
||||
bool remap_display_mode_if_needed(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
|
||||
const auto remapping_type {determine_remapping_type(video_config)};
|
||||
if (!remapping_type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto &remapping_list { [&]() {
|
||||
const auto &remapping_list {[&]() {
|
||||
using enum remapping_type_e;
|
||||
|
||||
switch (*remapping_type) {
|
||||
@@ -558,7 +538,7 @@ namespace display_device {
|
||||
default:
|
||||
return video_config.dd.mode_remapping.mixed;
|
||||
}
|
||||
}() };
|
||||
}()};
|
||||
|
||||
if (remapping_list.empty()) {
|
||||
BOOST_LOG(debug) << "No values are available for display mode remapping.";
|
||||
@@ -566,9 +546,9 @@ namespace display_device {
|
||||
}
|
||||
BOOST_LOG(debug) << "Trying to remap display modes...";
|
||||
|
||||
const auto entry_to_string { [type = *remapping_type](const config::video_t::dd_t::mode_remapping_entry_t &entry) {
|
||||
const bool mapping_resolution { is_resolution_mapped(type) };
|
||||
const bool mapping_fps { is_fps_mapped(type) };
|
||||
const auto entry_to_string {[type = *remapping_type](const config::video_t::dd_t::mode_remapping_entry_t &entry) {
|
||||
const bool mapping_resolution {is_resolution_mapped(type)};
|
||||
const bool mapping_fps {is_fps_mapped(type)};
|
||||
|
||||
// clang-format off
|
||||
return (mapping_resolution ? " - requested resolution: "s + entry.requested_resolution + "\n" : "") +
|
||||
@@ -576,10 +556,10 @@ namespace display_device {
|
||||
(mapping_resolution ? " - final resolution: "s + entry.final_resolution + "\n" : "") +
|
||||
(mapping_fps ? " - final refresh rate: "s + entry.final_refresh_rate : "");
|
||||
// clang-format on
|
||||
} };
|
||||
}};
|
||||
|
||||
for (const auto &entry : remapping_list) {
|
||||
const auto parsed_entry { parse_remapping_entry(entry, *remapping_type) };
|
||||
const auto parsed_entry {parse_remapping_entry(entry, *remapping_type)};
|
||||
if (!parsed_entry) {
|
||||
BOOST_LOG(error) << "Failed to parse remapping entry from:\n"
|
||||
<< entry_to_string(entry);
|
||||
@@ -632,16 +612,18 @@ namespace display_device {
|
||||
* @param video_config User's video related configuration.
|
||||
* @return An interface or nullptr if the OS does not support the interface.
|
||||
*/
|
||||
std::unique_ptr<SettingsManagerInterface>
|
||||
make_settings_manager([[maybe_unused]] const std::filesystem::path &persistence_filepath, [[maybe_unused]] const config::video_t &video_config) {
|
||||
std::unique_ptr<SettingsManagerInterface> make_settings_manager([[maybe_unused]] const std::filesystem::path &persistence_filepath, [[maybe_unused]] const config::video_t &video_config) {
|
||||
#ifdef _WIN32
|
||||
return std::make_unique<SettingsManager>(
|
||||
std::make_shared<WinDisplayDevice>(std::make_shared<WinApiLayer>()),
|
||||
std::make_shared<sunshine_audio_context_t>(),
|
||||
std::make_unique<PersistentState>(
|
||||
std::make_shared<FileSettingsPersistence>(persistence_filepath)),
|
||||
std::make_shared<FileSettingsPersistence>(persistence_filepath)
|
||||
),
|
||||
WinWorkarounds {
|
||||
.m_hdr_blank_delay = video_config.dd.wa.hdr_toggle ? std::make_optional(500ms) : std::nullopt });
|
||||
.m_hdr_blank_delay = video_config.dd.wa.hdr_toggle_delay != std::chrono::milliseconds::zero() ? std::make_optional(video_config.dd.wa.hdr_toggle_delay) : std::nullopt
|
||||
}
|
||||
);
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
@@ -660,17 +642,16 @@ namespace display_device {
|
||||
* @brief Reverts the configuration based on the provided option.
|
||||
* @note This is function does not lock mutex.
|
||||
*/
|
||||
void
|
||||
revert_configuration_unlocked(const revert_option_e option) {
|
||||
void revert_configuration_unlocked(const revert_option_e option) {
|
||||
if (!DD_DATA.sm_instance) {
|
||||
// Platform is not supported, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: by default the executor function is immediately executed in the calling thread. With delay, we want to avoid that.
|
||||
SchedulerOptions scheduler_option { .m_sleep_durations = { DEFAULT_RETRY_INTERVAL } };
|
||||
SchedulerOptions scheduler_option {.m_sleep_durations = {DEFAULT_RETRY_INTERVAL}};
|
||||
if (option == revert_option_e::try_indefinitely_with_delay && DD_DATA.config_revert_delay > std::chrono::milliseconds::zero()) {
|
||||
scheduler_option.m_sleep_durations = { DD_DATA.config_revert_delay, DEFAULT_RETRY_INTERVAL };
|
||||
scheduler_option.m_sleep_durations = {DD_DATA.config_revert_delay, DEFAULT_RETRY_INTERVAL};
|
||||
scheduler_option.m_execution = SchedulerOptions::Execution::ScheduledOnly;
|
||||
}
|
||||
|
||||
@@ -681,17 +662,21 @@ namespace display_device {
|
||||
return;
|
||||
}
|
||||
|
||||
auto available_devices { [&settings_iface]() {
|
||||
const auto devices { settings_iface.enumAvailableDevices() };
|
||||
auto available_devices {[&settings_iface]() {
|
||||
const auto devices {settings_iface.enumAvailableDevices()};
|
||||
std::set<std::string> parsed_devices;
|
||||
|
||||
std::transform(
|
||||
std::begin(devices), std::end(devices),
|
||||
std::begin(devices),
|
||||
std::end(devices),
|
||||
std::inserter(parsed_devices, std::end(parsed_devices)),
|
||||
[](const auto &device) { return device.m_device_id + " - " + device.m_friendly_name; });
|
||||
[](const auto &device) {
|
||||
return device.m_device_id + " - " + device.m_friendly_name;
|
||||
}
|
||||
);
|
||||
|
||||
return parsed_devices;
|
||||
}() };
|
||||
}()};
|
||||
if (available_devices == tried_out_devices) {
|
||||
BOOST_LOG(debug) << "Skipping reverting configuration, because no newly added/removed devices were detected since last check. Currently available devices:\n"
|
||||
<< toJson(available_devices);
|
||||
@@ -699,11 +684,10 @@ namespace display_device {
|
||||
}
|
||||
|
||||
using enum SettingsManagerInterface::RevertResult;
|
||||
if (const auto result { settings_iface.revertSettings() }; result == Ok) {
|
||||
if (const auto result {settings_iface.revertSettings()}; result == Ok) {
|
||||
stop_token.requestStop();
|
||||
return;
|
||||
}
|
||||
else if (result == ApiTemporarilyUnavailable) {
|
||||
} else if (result == ApiTemporarilyUnavailable) {
|
||||
// Do nothing and retry next time
|
||||
return;
|
||||
}
|
||||
@@ -713,13 +697,12 @@ namespace display_device {
|
||||
<< toJson(available_devices);
|
||||
tried_out_devices.swap(available_devices);
|
||||
},
|
||||
scheduler_option);
|
||||
scheduler_option);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<platf::deinit_t>
|
||||
init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config) {
|
||||
std::lock_guard lock { DD_DATA.mutex };
|
||||
std::unique_ptr<platf::deinit_t> init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config) {
|
||||
std::lock_guard lock {DD_DATA.mutex};
|
||||
// We can support re-init without any issues, however we should make sure to clean up first!
|
||||
revert_configuration_unlocked(revert_option_e::try_once);
|
||||
DD_DATA.config_revert_delay = video_config.dd.config_revert_delay;
|
||||
@@ -727,10 +710,12 @@ namespace display_device {
|
||||
|
||||
// If we fail to create settings manager, this means platform is not supported, and
|
||||
// we will need to provided error-free pass-trough in other methods
|
||||
if (auto settings_manager { make_settings_manager(persistence_filepath, video_config) }) {
|
||||
if (auto settings_manager {make_settings_manager(persistence_filepath, video_config)}) {
|
||||
DD_DATA.sm_instance = std::make_unique<RetryScheduler<SettingsManagerInterface>>(std::move(settings_manager));
|
||||
|
||||
const auto available_devices { DD_DATA.sm_instance->execute([](auto &settings_iface) { return settings_iface.enumAvailableDevices(); }) };
|
||||
const auto available_devices {DD_DATA.sm_instance->execute([](auto &settings_iface) {
|
||||
return settings_iface.enumAvailableDevices();
|
||||
})};
|
||||
BOOST_LOG(info) << "Currently available display devices:\n"
|
||||
<< toJson(available_devices);
|
||||
|
||||
@@ -742,44 +727,44 @@ namespace display_device {
|
||||
class deinit_t: public platf::deinit_t {
|
||||
public:
|
||||
~deinit_t() override {
|
||||
std::lock_guard lock { DD_DATA.mutex };
|
||||
std::lock_guard lock {DD_DATA.mutex};
|
||||
try {
|
||||
// This may throw if used incorrectly. At the moment this will not happen, however
|
||||
// in case some unforeseen changes are made that could raise an exception,
|
||||
// we definitely don't want this to happen in destructor. Especially in the
|
||||
// deinit_t where the outcome does not really matter.
|
||||
revert_configuration_unlocked(revert_option_e::try_once);
|
||||
}
|
||||
catch (std::exception &err) {
|
||||
} catch (std::exception &err) {
|
||||
BOOST_LOG(fatal) << err.what();
|
||||
}
|
||||
|
||||
DD_DATA.sm_instance = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
return std::make_unique<deinit_t>();
|
||||
}
|
||||
|
||||
std::string
|
||||
map_output_name(const std::string &output_name) {
|
||||
std::lock_guard lock { DD_DATA.mutex };
|
||||
std::string map_output_name(const std::string &output_name) {
|
||||
std::lock_guard lock {DD_DATA.mutex};
|
||||
if (!DD_DATA.sm_instance) {
|
||||
// Fallback to giving back the output name if the platform is not supported.
|
||||
return output_name;
|
||||
}
|
||||
|
||||
return DD_DATA.sm_instance->execute([&output_name](auto &settings_iface) { return settings_iface.getDisplayName(output_name); });
|
||||
return DD_DATA.sm_instance->execute([&output_name](auto &settings_iface) {
|
||||
return settings_iface.getDisplayName(output_name);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
|
||||
const auto result { parse_configuration(video_config, session) };
|
||||
if (const auto *parsed_config { std::get_if<SingleDisplayConfiguration>(&result) }; parsed_config) {
|
||||
void configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
|
||||
const auto result {parse_configuration(video_config, session)};
|
||||
if (const auto *parsed_config {std::get_if<SingleDisplayConfiguration>(&result)}; parsed_config) {
|
||||
configure_display(*parsed_config);
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto *disabled { std::get_if<configuration_disabled_tag_t>(&result) }; disabled) {
|
||||
if (const auto *disabled {std::get_if<configuration_disabled_tag_t>(&result)}; disabled) {
|
||||
revert_configuration();
|
||||
return;
|
||||
}
|
||||
@@ -788,9 +773,8 @@ namespace display_device {
|
||||
// want to revert active configuration in case we have any
|
||||
}
|
||||
|
||||
void
|
||||
configure_display(const SingleDisplayConfiguration &config) {
|
||||
std::lock_guard lock { DD_DATA.mutex };
|
||||
void configure_display(const SingleDisplayConfiguration &config) {
|
||||
std::lock_guard lock {DD_DATA.mutex};
|
||||
if (!DD_DATA.sm_instance) {
|
||||
// Platform is not supported, nothing to do.
|
||||
return;
|
||||
@@ -803,18 +787,16 @@ namespace display_device {
|
||||
stop_token.requestStop();
|
||||
}
|
||||
},
|
||||
{ .m_sleep_durations = { DEFAULT_RETRY_INTERVAL } });
|
||||
{.m_sleep_durations = {DEFAULT_RETRY_INTERVAL}});
|
||||
}
|
||||
|
||||
void
|
||||
revert_configuration() {
|
||||
std::lock_guard lock { DD_DATA.mutex };
|
||||
void revert_configuration() {
|
||||
std::lock_guard lock {DD_DATA.mutex};
|
||||
revert_configuration_unlocked(revert_option_e::try_indefinitely_with_delay);
|
||||
}
|
||||
|
||||
bool
|
||||
reset_persistence() {
|
||||
std::lock_guard lock { DD_DATA.mutex };
|
||||
bool reset_persistence() {
|
||||
std::lock_guard lock {DD_DATA.mutex};
|
||||
if (!DD_DATA.sm_instance) {
|
||||
// Platform is not supported, assume success.
|
||||
return true;
|
||||
@@ -828,9 +810,20 @@ namespace display_device {
|
||||
});
|
||||
}
|
||||
|
||||
std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration>
|
||||
parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
|
||||
const auto device_prep { parse_device_prep_option(video_config) };
|
||||
EnumeratedDeviceList enumerate_devices() {
|
||||
std::lock_guard lock {DD_DATA.mutex};
|
||||
if (!DD_DATA.sm_instance) {
|
||||
// Platform is not supported.
|
||||
return {};
|
||||
}
|
||||
|
||||
return DD_DATA.sm_instance->execute([](auto &settings_iface) {
|
||||
return settings_iface.enumAvailableDevices();
|
||||
});
|
||||
}
|
||||
|
||||
std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration> parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
|
||||
const auto device_prep {parse_device_prep_option(video_config)};
|
||||
if (!device_prep) {
|
||||
return configuration_disabled_tag_t {};
|
||||
}
|
||||
|
||||
@@ -4,18 +4,22 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// lib includes
|
||||
#include <display_device/types.h>
|
||||
// standard includes
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
// lib includes
|
||||
#include <display_device/types.h>
|
||||
|
||||
// forward declarations
|
||||
namespace platf {
|
||||
class deinit_t;
|
||||
}
|
||||
|
||||
namespace config {
|
||||
struct video_t;
|
||||
}
|
||||
|
||||
namespace rtsp_stream {
|
||||
struct launch_session_t;
|
||||
}
|
||||
@@ -32,8 +36,7 @@ namespace display_device {
|
||||
* const auto init_guard { init("/my/persitence/file.state", video_config) };
|
||||
* @examples_end
|
||||
*/
|
||||
[[nodiscard]] std::unique_ptr<platf::deinit_t>
|
||||
init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config);
|
||||
[[nodiscard]] std::unique_ptr<platf::deinit_t> init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config);
|
||||
|
||||
/**
|
||||
* @brief Map the output name to a specific display.
|
||||
@@ -45,8 +48,7 @@ namespace display_device {
|
||||
* const auto mapped_name_custom { map_output_name("{some-device-id}") };
|
||||
* @examples_end
|
||||
*/
|
||||
[[nodiscard]] std::string
|
||||
map_output_name(const std::string &output_name);
|
||||
[[nodiscard]] std::string map_output_name(const std::string &output_name);
|
||||
|
||||
/**
|
||||
* @brief Configure the display device based on the user configuration and the session information.
|
||||
@@ -62,8 +64,7 @@ namespace display_device {
|
||||
* configure_display(video_config, *launch_session);
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
|
||||
void configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
|
||||
|
||||
/**
|
||||
* @brief Configure the display device using the provided configuration.
|
||||
@@ -83,8 +84,7 @@ namespace display_device {
|
||||
* configure_display(valid_config);
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
configure_display(const SingleDisplayConfiguration &config);
|
||||
void configure_display(const SingleDisplayConfiguration &config);
|
||||
|
||||
/**
|
||||
* @brief Revert the display configuration and restore the previous state.
|
||||
@@ -96,8 +96,7 @@ namespace display_device {
|
||||
* revert_configuration();
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
revert_configuration();
|
||||
void revert_configuration();
|
||||
|
||||
/**
|
||||
* @brief Reset the persistence and currently held initial display state.
|
||||
@@ -111,16 +110,25 @@ namespace display_device {
|
||||
* The user then accepts that Sunshine is not able to restore the state and "agrees" to
|
||||
* do it manually.
|
||||
*
|
||||
* @return
|
||||
* @note Whether the function succeeds or fails, the any of the scheduled "retries" from
|
||||
* @return True if persistence was reset, false otherwise.
|
||||
* @note Whether the function succeeds or fails, any of the scheduled "retries" from
|
||||
* other methods will be stopped to not interfere with the user actions.
|
||||
*
|
||||
* @examples
|
||||
* const auto result = reset_persistence();
|
||||
* @examples_end
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
reset_persistence();
|
||||
[[nodiscard]] bool reset_persistence();
|
||||
|
||||
/**
|
||||
* @brief Enumerate the available devices.
|
||||
* @return A list of devices.
|
||||
*
|
||||
* @examples
|
||||
* const auto devices = enumerate_devices();
|
||||
* @examples_end
|
||||
*/
|
||||
[[nodiscard]] EnumeratedDeviceList enumerate_devices();
|
||||
|
||||
/**
|
||||
* @brief A tag structure indicating that configuration parsing has failed.
|
||||
@@ -150,6 +158,5 @@ namespace display_device {
|
||||
* }
|
||||
* @examples_end
|
||||
*/
|
||||
[[nodiscard]] std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration>
|
||||
parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
|
||||
[[nodiscard]] std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration> parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
|
||||
} // namespace display_device
|
||||
|
||||
@@ -26,21 +26,18 @@ extern "C" {
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
void
|
||||
launch_ui() {
|
||||
void launch_ui() {
|
||||
std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS));
|
||||
platf::open_url(url);
|
||||
}
|
||||
|
||||
void
|
||||
launch_ui_with_path(std::string path) {
|
||||
void launch_ui_with_path(std::string path) {
|
||||
std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + path;
|
||||
platf::open_url(url);
|
||||
}
|
||||
|
||||
namespace args {
|
||||
int
|
||||
creds(const char *name, int argc, char *argv[]) {
|
||||
int creds(const char *name, int argc, char *argv[]) {
|
||||
if (argc < 2 || argv[0] == "help"sv || argv[1] == "help"sv) {
|
||||
help(name);
|
||||
}
|
||||
@@ -50,21 +47,18 @@ namespace args {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
help(const char *name) {
|
||||
int help(const char *name) {
|
||||
logging::print_help(name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
version() {
|
||||
int version() {
|
||||
// version was already logged at startup
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int
|
||||
restore_nvprefs_undo() {
|
||||
int restore_nvprefs_undo() {
|
||||
if (nvprefs_instance.load()) {
|
||||
nvprefs_instance.restore_from_and_delete_undo_file_if_exists();
|
||||
nvprefs_instance.unload();
|
||||
@@ -78,8 +72,7 @@ namespace lifetime {
|
||||
char **argv;
|
||||
std::atomic_int desired_exit_code;
|
||||
|
||||
void
|
||||
exit_sunshine(int exit_code, bool async) {
|
||||
void exit_sunshine(int exit_code, bool async) {
|
||||
// Store the exit code of the first exit_sunshine() call
|
||||
int zero = 0;
|
||||
desired_exit_code.compare_exchange_strong(zero, exit_code);
|
||||
@@ -94,8 +87,7 @@ namespace lifetime {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
debug_trap() {
|
||||
void debug_trap() {
|
||||
#ifdef _WIN32
|
||||
DebugBreak();
|
||||
#else
|
||||
@@ -103,22 +95,19 @@ namespace lifetime {
|
||||
#endif
|
||||
}
|
||||
|
||||
char **
|
||||
get_argv() {
|
||||
char **get_argv() {
|
||||
return argv;
|
||||
}
|
||||
} // namespace lifetime
|
||||
|
||||
void
|
||||
log_publisher_data() {
|
||||
void log_publisher_data() {
|
||||
BOOST_LOG(info) << "Package Publisher: "sv << SUNSHINE_PUBLISHER_NAME;
|
||||
BOOST_LOG(info) << "Publisher Website: "sv << SUNSHINE_PUBLISHER_WEBSITE;
|
||||
BOOST_LOG(info) << "Get support: "sv << SUNSHINE_PUBLISHER_ISSUE_URL;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
bool
|
||||
is_gamestream_enabled() {
|
||||
bool is_gamestream_enabled() {
|
||||
DWORD enabled;
|
||||
DWORD size = sizeof(enabled);
|
||||
return RegGetValueW(
|
||||
@@ -128,7 +117,8 @@ is_gamestream_enabled() {
|
||||
RRF_RT_REG_DWORD,
|
||||
nullptr,
|
||||
&enabled,
|
||||
&size) == ERROR_SUCCESS &&
|
||||
&size
|
||||
) == ERROR_SUCCESS &&
|
||||
enabled != 0;
|
||||
}
|
||||
|
||||
@@ -168,8 +158,7 @@ namespace service_ctrl {
|
||||
/**
|
||||
* @brief Asynchronously starts the Sunshine service.
|
||||
*/
|
||||
bool
|
||||
start_service() {
|
||||
bool start_service() {
|
||||
if (!service_handle) {
|
||||
return false;
|
||||
}
|
||||
@@ -189,8 +178,7 @@ namespace service_ctrl {
|
||||
* @brief Query the service status.
|
||||
* @param status The SERVICE_STATUS struct to populate.
|
||||
*/
|
||||
bool
|
||||
query_service_status(SERVICE_STATUS &status) {
|
||||
bool query_service_status(SERVICE_STATUS &status) {
|
||||
if (!service_handle) {
|
||||
return false;
|
||||
}
|
||||
@@ -209,9 +197,8 @@ namespace service_ctrl {
|
||||
SC_HANDLE service_handle = NULL;
|
||||
};
|
||||
|
||||
bool
|
||||
is_service_running() {
|
||||
service_controller sc { SERVICE_QUERY_STATUS };
|
||||
bool is_service_running() {
|
||||
service_controller sc {SERVICE_QUERY_STATUS};
|
||||
|
||||
SERVICE_STATUS status;
|
||||
if (!sc.query_service_status(status)) {
|
||||
@@ -221,9 +208,8 @@ namespace service_ctrl {
|
||||
return status.dwCurrentState == SERVICE_RUNNING;
|
||||
}
|
||||
|
||||
bool
|
||||
start_service() {
|
||||
service_controller sc { SERVICE_QUERY_STATUS | SERVICE_START };
|
||||
bool start_service() {
|
||||
service_controller sc {SERVICE_QUERY_STATUS | SERVICE_START};
|
||||
|
||||
std::cout << "Starting Sunshine..."sv;
|
||||
|
||||
@@ -247,8 +233,7 @@ namespace service_ctrl {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
wait_for_ui_ready() {
|
||||
bool wait_for_ui_ready() {
|
||||
std::cout << "Waiting for Web UI to be ready...";
|
||||
|
||||
// Wait up to 30 seconds for the web UI to start
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
* launch_ui();
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
launch_ui();
|
||||
void launch_ui();
|
||||
|
||||
/**
|
||||
* @brief Launch the Web UI at a specific endpoint.
|
||||
@@ -27,8 +26,7 @@ launch_ui();
|
||||
* launch_ui_with_path("/pin");
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
launch_ui_with_path(std::string path);
|
||||
void launch_ui_with_path(std::string path);
|
||||
|
||||
/**
|
||||
* @brief Functions for handling command line arguments.
|
||||
@@ -43,8 +41,7 @@ namespace args {
|
||||
* creds("sunshine", 2, {"new_username", "new_password"});
|
||||
* @examples_end
|
||||
*/
|
||||
int
|
||||
creds(const char *name, int argc, char *argv[]);
|
||||
int creds(const char *name, int argc, char *argv[]);
|
||||
|
||||
/**
|
||||
* @brief Print help to stdout, then exit.
|
||||
@@ -53,8 +50,7 @@ namespace args {
|
||||
* help("sunshine");
|
||||
* @examples_end
|
||||
*/
|
||||
int
|
||||
help(const char *name);
|
||||
int help(const char *name);
|
||||
|
||||
/**
|
||||
* @brief Print the version to stdout, then exit.
|
||||
@@ -62,8 +58,7 @@ namespace args {
|
||||
* version();
|
||||
* @examples_end
|
||||
*/
|
||||
int
|
||||
version();
|
||||
int version();
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
@@ -75,8 +70,7 @@ namespace args {
|
||||
* restore_nvprefs_undo();
|
||||
* @examples_end
|
||||
*/
|
||||
int
|
||||
restore_nvprefs_undo();
|
||||
int restore_nvprefs_undo();
|
||||
#endif
|
||||
} // namespace args
|
||||
|
||||
@@ -92,35 +86,30 @@ namespace lifetime {
|
||||
* @param exit_code The exit code to return from main().
|
||||
* @param async Specifies whether our termination will be non-blocking.
|
||||
*/
|
||||
void
|
||||
exit_sunshine(int exit_code, bool async);
|
||||
void exit_sunshine(int exit_code, bool async);
|
||||
|
||||
/**
|
||||
* @brief Breaks into the debugger or terminates Sunshine if no debugger is attached.
|
||||
*/
|
||||
void
|
||||
debug_trap();
|
||||
void debug_trap();
|
||||
|
||||
/**
|
||||
* @brief Get the argv array passed to main().
|
||||
*/
|
||||
char **
|
||||
get_argv();
|
||||
char **get_argv();
|
||||
} // namespace lifetime
|
||||
|
||||
/**
|
||||
* @brief Log the publisher metadata provided from CMake.
|
||||
*/
|
||||
void
|
||||
log_publisher_data();
|
||||
void log_publisher_data();
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
* @brief Check if NVIDIA's GameStream software is running.
|
||||
* @return `true` if GameStream is enabled, `false` otherwise.
|
||||
*/
|
||||
bool
|
||||
is_gamestream_enabled();
|
||||
bool is_gamestream_enabled();
|
||||
|
||||
/**
|
||||
* @brief Namespace for controlling the Sunshine service model on Windows.
|
||||
@@ -132,8 +121,7 @@ namespace service_ctrl {
|
||||
* is_service_running();
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
is_service_running();
|
||||
bool is_service_running();
|
||||
|
||||
/**
|
||||
* @brief Start the service and wait for startup to complete.
|
||||
@@ -141,8 +129,7 @@ namespace service_ctrl {
|
||||
* start_service();
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
start_service();
|
||||
bool start_service();
|
||||
|
||||
/**
|
||||
* @brief Wait for the UI to be ready after Sunshine startup.
|
||||
@@ -150,7 +137,6 @@ namespace service_ctrl {
|
||||
* wait_for_ui_ready();
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
wait_for_ui_ready();
|
||||
bool wait_for_ui_ready();
|
||||
} // namespace service_ctrl
|
||||
#endif
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
#include "logging.h"
|
||||
|
||||
namespace file_handler {
|
||||
std::string
|
||||
get_parent_directory(const std::string &path) {
|
||||
std::string get_parent_directory(const std::string &path) {
|
||||
// remove any trailing path separators
|
||||
std::string trimmed_path = path;
|
||||
while (!trimmed_path.empty() && trimmed_path.back() == '/') {
|
||||
@@ -24,8 +23,7 @@ namespace file_handler {
|
||||
return p.parent_path().string();
|
||||
}
|
||||
|
||||
bool
|
||||
make_directory(const std::string &path) {
|
||||
bool make_directory(const std::string &path) {
|
||||
// first, check if the directory already exists
|
||||
if (std::filesystem::exists(path)) {
|
||||
return true;
|
||||
@@ -34,19 +32,17 @@ namespace file_handler {
|
||||
return std::filesystem::create_directories(path);
|
||||
}
|
||||
|
||||
std::string
|
||||
read_file(const char *path) {
|
||||
std::string read_file(const char *path) {
|
||||
if (!std::filesystem::exists(path)) {
|
||||
BOOST_LOG(debug) << "Missing file: " << path;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::ifstream in(path);
|
||||
return std::string { (std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>() };
|
||||
return std::string {(std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>()};
|
||||
}
|
||||
|
||||
int
|
||||
write_file(const char *path, const std::string_view &contents) {
|
||||
int write_file(const char *path, const std::string_view &contents) {
|
||||
std::ofstream out(path);
|
||||
|
||||
if (!out.is_open()) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
@@ -18,8 +19,7 @@ namespace file_handler {
|
||||
* std::string parent_dir = get_parent_directory("path/to/file");
|
||||
* @examples_end
|
||||
*/
|
||||
std::string
|
||||
get_parent_directory(const std::string &path);
|
||||
std::string get_parent_directory(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief Make a directory.
|
||||
@@ -29,8 +29,7 @@ namespace file_handler {
|
||||
* bool dir_created = make_directory("path/to/directory");
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
make_directory(const std::string &path);
|
||||
bool make_directory(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief Read a file to string.
|
||||
@@ -40,8 +39,7 @@ namespace file_handler {
|
||||
* std::string contents = read_file("path/to/file");
|
||||
* @examples_end
|
||||
*/
|
||||
std::string
|
||||
read_file(const char *path);
|
||||
std::string read_file(const char *path);
|
||||
|
||||
/**
|
||||
* @brief Writes a file.
|
||||
@@ -52,6 +50,5 @@ namespace file_handler {
|
||||
* int write_status = write_file("path/to/file", "file contents");
|
||||
* @examples_end
|
||||
*/
|
||||
int
|
||||
write_file(const char *path, const std::string_view &contents);
|
||||
int write_file(const char *path, const std::string_view &contents);
|
||||
} // namespace file_handler
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* @file globals.cpp
|
||||
* @brief Definitions for globally accessible variables and functions.
|
||||
*/
|
||||
// local includes
|
||||
#include "globals.h"
|
||||
|
||||
safe::mail_t mail::man;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// local includes
|
||||
#include "entry_handler.h"
|
||||
#include "thread_pool.h"
|
||||
|
||||
@@ -31,9 +32,9 @@ extern nvprefs::nvprefs_interface nvprefs_instance;
|
||||
* @brief Handles process-wide communication.
|
||||
*/
|
||||
namespace mail {
|
||||
#define MAIL(x) \
|
||||
#define MAIL(x) \
|
||||
constexpr auto x = std::string_view { \
|
||||
#x \
|
||||
#x \
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,22 +4,21 @@
|
||||
*/
|
||||
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
|
||||
|
||||
#include "process.h"
|
||||
|
||||
// standard includes
|
||||
#include <filesystem>
|
||||
#include <utility>
|
||||
|
||||
// lib includes
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/asio/ssl/context_base.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/xml_parser.hpp>
|
||||
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <Simple-Web-Server/server_http.hpp>
|
||||
#include <Simple-Web-Server/server_https.hpp>
|
||||
#include <boost/asio/ssl/context_base.hpp>
|
||||
#include <curl/curl.h>
|
||||
|
||||
// local includes
|
||||
#include "config.h"
|
||||
#include "crypto.h"
|
||||
#include "file_handler.h"
|
||||
@@ -28,6 +27,7 @@
|
||||
#include "network.h"
|
||||
#include "nvhttp.h"
|
||||
#include "platform/common.h"
|
||||
#include "process.h"
|
||||
#include "rtsp.h"
|
||||
#include "utility.h"
|
||||
#include "uuid.h"
|
||||
@@ -37,16 +37,13 @@ namespace http {
|
||||
namespace fs = std::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
int
|
||||
reload_user_creds(const std::string &file);
|
||||
bool
|
||||
user_creds_exist(const std::string &file);
|
||||
int reload_user_creds(const std::string &file);
|
||||
bool user_creds_exist(const std::string &file);
|
||||
|
||||
std::string unique_id;
|
||||
net::net_e origin_web_ui_allowed;
|
||||
|
||||
int
|
||||
init() {
|
||||
int init() {
|
||||
bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE];
|
||||
origin_web_ui_allowed = net::from_enum_string(config::nvhttp.origin_web_ui_allowed);
|
||||
|
||||
@@ -57,29 +54,25 @@ namespace http {
|
||||
config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string();
|
||||
}
|
||||
|
||||
if (!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) {
|
||||
if (create_creds(config::nvhttp.pkey, config::nvhttp.cert)) {
|
||||
return -1;
|
||||
}
|
||||
if ((!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) &&
|
||||
create_creds(config::nvhttp.pkey, config::nvhttp.cert)) {
|
||||
return -1;
|
||||
}
|
||||
if (user_creds_exist(config::sunshine.credentials_file)) {
|
||||
if (reload_user_creds(config::sunshine.credentials_file)) return -1;
|
||||
}
|
||||
else {
|
||||
if (!user_creds_exist(config::sunshine.credentials_file)) {
|
||||
BOOST_LOG(info) << "Open the Web UI to set your new username and password and getting started";
|
||||
} else if (reload_user_creds(config::sunshine.credentials_file)) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
save_user_creds(const std::string &file, const std::string &username, const std::string &password, bool run_our_mouth) {
|
||||
int save_user_creds(const std::string &file, const std::string &username, const std::string &password, bool run_our_mouth) {
|
||||
pt::ptree outputTree;
|
||||
|
||||
if (fs::exists(file)) {
|
||||
try {
|
||||
pt::read_json(file, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(error) << "Couldn't read user credentials: "sv << e.what();
|
||||
return -1;
|
||||
}
|
||||
@@ -91,8 +84,7 @@ namespace http {
|
||||
outputTree.put("password", util::hex(crypto::hash(password + salt)).to_string());
|
||||
try {
|
||||
pt::write_json(file, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(error) << "error writing to the credentials file, perhaps try this again as an administrator? Details: "sv << e.what();
|
||||
return -1;
|
||||
}
|
||||
@@ -101,8 +93,7 @@ namespace http {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
user_creds_exist(const std::string &file) {
|
||||
bool user_creds_exist(const std::string &file) {
|
||||
if (!fs::exists(file)) {
|
||||
return false;
|
||||
}
|
||||
@@ -113,32 +104,28 @@ namespace http {
|
||||
return inputTree.find("username") != inputTree.not_found() &&
|
||||
inputTree.find("password") != inputTree.not_found() &&
|
||||
inputTree.find("salt") != inputTree.not_found();
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(error) << "validating user credentials: "sv << e.what();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int
|
||||
reload_user_creds(const std::string &file) {
|
||||
int reload_user_creds(const std::string &file) {
|
||||
pt::ptree inputTree;
|
||||
try {
|
||||
pt::read_json(file, inputTree);
|
||||
config::sunshine.username = inputTree.get<std::string>("username");
|
||||
config::sunshine.password = inputTree.get<std::string>("password");
|
||||
config::sunshine.salt = inputTree.get<std::string>("salt");
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(error) << "loading user credentials: "sv << e.what();
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
create_creds(const std::string &pkey, const std::string &cert) {
|
||||
int create_creds(const std::string &pkey, const std::string &cert) {
|
||||
fs::path pkey_path = pkey;
|
||||
fs::path cert_path = cert;
|
||||
|
||||
@@ -172,18 +159,14 @@ namespace http {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fs::permissions(pkey_path,
|
||||
fs::perms::owner_read | fs::perms::owner_write,
|
||||
fs::perm_options::replace, err_code);
|
||||
fs::permissions(pkey_path, fs::perms::owner_read | fs::perms::owner_write, fs::perm_options::replace, err_code);
|
||||
|
||||
if (err_code) {
|
||||
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message();
|
||||
return -1;
|
||||
}
|
||||
|
||||
fs::permissions(cert_path,
|
||||
fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write,
|
||||
fs::perm_options::replace, err_code);
|
||||
fs::permissions(cert_path, fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write, fs::perm_options::replace, err_code);
|
||||
|
||||
if (err_code) {
|
||||
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message();
|
||||
@@ -193,21 +176,15 @@ namespace http {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
download_file(const std::string &url, const std::string &file) {
|
||||
CURL *curl = curl_easy_init();
|
||||
if (curl) {
|
||||
// sonar complains about weak ssl and tls versions
|
||||
// ideally, the setopts should go after the early returns; however sonar cannot detect the fix
|
||||
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
|
||||
}
|
||||
else {
|
||||
bool download_file(const std::string &url, const std::string &file, long ssl_version) {
|
||||
// sonar complains about weak ssl and tls versions; however sonar cannot detect the fix
|
||||
CURL *curl = curl_easy_init(); // NOSONAR
|
||||
if (!curl) {
|
||||
BOOST_LOG(error) << "Couldn't create CURL instance";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string file_dir = file_handler::get_parent_directory(file);
|
||||
if (!file_handler::make_directory(file_dir)) {
|
||||
if (std::string file_dir = file_handler::get_parent_directory(file); !file_handler::make_directory(file_dir)) {
|
||||
BOOST_LOG(error) << "Couldn't create directory ["sv << file_dir << ']';
|
||||
curl_easy_cleanup(curl);
|
||||
return false;
|
||||
@@ -220,6 +197,7 @@ namespace http {
|
||||
return false;
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_SSLVERSION, ssl_version); // NOSONAR
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
|
||||
@@ -234,20 +212,16 @@ namespace http {
|
||||
return result == CURLE_OK;
|
||||
}
|
||||
|
||||
std::string
|
||||
url_escape(const std::string &url) {
|
||||
CURL *curl = curl_easy_init();
|
||||
char *string = curl_easy_escape(curl, url.c_str(), url.length());
|
||||
std::string url_escape(const std::string &url) {
|
||||
char *string = curl_easy_escape(nullptr, url.c_str(), static_cast<int>(url.length()));
|
||||
std::string result(string);
|
||||
curl_free(string);
|
||||
curl_easy_cleanup(curl);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string
|
||||
url_get_host(const std::string &url) {
|
||||
std::string url_get_host(const std::string &url) {
|
||||
CURLU *curlu = curl_url();
|
||||
curl_url_set(curlu, CURLUPART_URL, url.c_str(), url.length());
|
||||
curl_url_set(curlu, CURLUPART_URL, url.c_str(), static_cast<unsigned int>(url.length()));
|
||||
char *host;
|
||||
if (curl_url_get(curlu, CURLUPART_HOST, &host, 0) != CURLUE_OK) {
|
||||
curl_url_cleanup(curlu);
|
||||
@@ -258,5 +232,4 @@ namespace http {
|
||||
curl_url_cleanup(curlu);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace http
|
||||
|
||||
@@ -4,30 +4,28 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// lib includes
|
||||
#include <curl/curl.h>
|
||||
|
||||
// local includes
|
||||
#include "network.h"
|
||||
#include "thread_safe.h"
|
||||
|
||||
namespace http {
|
||||
|
||||
int
|
||||
init();
|
||||
int
|
||||
create_creds(const std::string &pkey, const std::string &cert);
|
||||
int
|
||||
save_user_creds(
|
||||
int init();
|
||||
int create_creds(const std::string &pkey, const std::string &cert);
|
||||
int save_user_creds(
|
||||
const std::string &file,
|
||||
const std::string &username,
|
||||
const std::string &password,
|
||||
bool run_our_mouth = false);
|
||||
bool run_our_mouth = false
|
||||
);
|
||||
|
||||
int
|
||||
reload_user_creds(const std::string &file);
|
||||
bool
|
||||
download_file(const std::string &url, const std::string &file);
|
||||
std::string
|
||||
url_escape(const std::string &url);
|
||||
std::string
|
||||
url_get_host(const std::string &url);
|
||||
int reload_user_creds(const std::string &file);
|
||||
bool download_file(const std::string &url, const std::string &file, long ssl_version = CURL_SSLVERSION_TLSv1_2);
|
||||
std::string url_escape(const std::string &url);
|
||||
std::string url_get_host(const std::string &url);
|
||||
|
||||
extern std::string unique_id;
|
||||
extern net::net_e origin_web_ui_allowed;
|
||||
|
||||
323
src/input.cpp
323
src/input.cpp
@@ -2,13 +2,13 @@
|
||||
* @file src/input.cpp
|
||||
* @brief Definitions for gamepad, keyboard, and mouse input handling.
|
||||
*/
|
||||
// define uint32_t for <moonlight-common-c/src/Input.h>
|
||||
#include <cstdint>
|
||||
extern "C" {
|
||||
#include <moonlight-common-c/src/Input.h>
|
||||
#include <moonlight-common-c/src/Limelight.h>
|
||||
}
|
||||
|
||||
// standard includes
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
@@ -16,6 +16,10 @@ extern "C" {
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
// lib includes
|
||||
#include <boost/endian/buffers.hpp>
|
||||
|
||||
// local includes
|
||||
#include "config.h"
|
||||
#include "globals.h"
|
||||
#include "input.h"
|
||||
@@ -24,14 +28,13 @@ extern "C" {
|
||||
#include "thread_pool.h"
|
||||
#include "utility.h"
|
||||
|
||||
#include <boost/endian/buffers.hpp>
|
||||
|
||||
// Win32 WHEEL_DELTA constant
|
||||
#ifndef WHEEL_DELTA
|
||||
#define WHEEL_DELTA 120
|
||||
#endif
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace input {
|
||||
|
||||
constexpr auto MAX_GAMEPADS = std::min((std::size_t) platf::MAX_GAMEPADS, sizeof(std::int16_t) * 8);
|
||||
@@ -54,9 +57,8 @@ namespace input {
|
||||
UP ///< Button is up
|
||||
};
|
||||
|
||||
template <std::size_t N>
|
||||
int
|
||||
alloc_id(std::bitset<N> &gamepad_mask) {
|
||||
template<std::size_t N>
|
||||
int alloc_id(std::bitset<N> &gamepad_mask) {
|
||||
for (int x = 0; x < gamepad_mask.size(); ++x) {
|
||||
if (!gamepad_mask[x]) {
|
||||
gamepad_mask[x] = true;
|
||||
@@ -67,23 +69,22 @@ namespace input {
|
||||
return -1;
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
void
|
||||
free_id(std::bitset<N> &gamepad_mask, int id) {
|
||||
template<std::size_t N>
|
||||
void free_id(std::bitset<N> &gamepad_mask, int id) {
|
||||
gamepad_mask[id] = false;
|
||||
}
|
||||
|
||||
typedef uint32_t key_press_id_t;
|
||||
key_press_id_t
|
||||
make_kpid(uint16_t vk, uint8_t flags) {
|
||||
|
||||
key_press_id_t make_kpid(uint16_t vk, uint8_t flags) {
|
||||
return (key_press_id_t) vk << 8 | flags;
|
||||
}
|
||||
uint16_t
|
||||
vk_from_kpid(key_press_id_t kpid) {
|
||||
|
||||
uint16_t vk_from_kpid(key_press_id_t kpid) {
|
||||
return kpid >> 8;
|
||||
}
|
||||
uint8_t
|
||||
flags_from_kpid(key_press_id_t kpid) {
|
||||
|
||||
uint8_t flags_from_kpid(key_press_id_t kpid) {
|
||||
return kpid & 0xFF;
|
||||
}
|
||||
|
||||
@@ -92,8 +93,7 @@ namespace input {
|
||||
* @param f Netfloat value.
|
||||
* @return The native endianness float value.
|
||||
*/
|
||||
float
|
||||
from_netfloat(netfloat f) {
|
||||
float from_netfloat(netfloat f) {
|
||||
return boost::endian::endian_load<float, sizeof(float), boost::endian::order::little>(f);
|
||||
}
|
||||
|
||||
@@ -104,8 +104,7 @@ namespace input {
|
||||
* @param max The maximum value for clamping.
|
||||
* @return Clamped native endianess float value.
|
||||
*/
|
||||
float
|
||||
from_clamped_netfloat(netfloat f, float min, float max) {
|
||||
float from_clamped_netfloat(netfloat f, float min, float max) {
|
||||
return std::clamp(from_netfloat(f), min, max);
|
||||
}
|
||||
|
||||
@@ -116,16 +115,21 @@ namespace input {
|
||||
static platf::input_t platf_input;
|
||||
static std::bitset<platf::MAX_GAMEPADS> gamepadMask {};
|
||||
|
||||
void
|
||||
free_gamepad(platf::input_t &platf_input, int id) {
|
||||
void free_gamepad(platf::input_t &platf_input, int id) {
|
||||
platf::gamepad_update(platf_input, id, platf::gamepad_state_t {});
|
||||
platf::free_gamepad(platf_input, id);
|
||||
|
||||
free_id(gamepadMask, id);
|
||||
}
|
||||
|
||||
struct gamepad_t {
|
||||
gamepad_t():
|
||||
gamepad_state {}, back_timeout_id {}, id { -1 }, back_button_state { button_state_e::NONE } {}
|
||||
gamepad_state {},
|
||||
back_timeout_id {},
|
||||
id {-1},
|
||||
back_button_state {button_state_e::NONE} {
|
||||
}
|
||||
|
||||
~gamepad_t() {
|
||||
if (id >= 0) {
|
||||
task_pool.push([id = this->id]() {
|
||||
@@ -158,16 +162,18 @@ namespace input {
|
||||
|
||||
input_t(
|
||||
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event,
|
||||
platf::feedback_queue_t feedback_queue):
|
||||
platf::feedback_queue_t feedback_queue
|
||||
):
|
||||
shortcutFlags {},
|
||||
gamepads(MAX_GAMEPADS),
|
||||
client_context { platf::allocate_client_input_context(platf_input) },
|
||||
touch_port_event { std::move(touch_port_event) },
|
||||
feedback_queue { std::move(feedback_queue) },
|
||||
client_context {platf::allocate_client_input_context(platf_input)},
|
||||
touch_port_event {std::move(touch_port_event)},
|
||||
feedback_queue {std::move(feedback_queue)},
|
||||
mouse_left_button_timeout {},
|
||||
touch_port { { 0, 0, 0, 0 }, 0, 0, 1.0f },
|
||||
touch_port {{0, 0, 0, 0}, 0, 0, 1.0f},
|
||||
accumulated_vscroll_delta {},
|
||||
accumulated_hscroll_delta {} {}
|
||||
accumulated_hscroll_delta {} {
|
||||
}
|
||||
|
||||
// Keep track of alt+ctrl+shift key combo
|
||||
int shortcutFlags;
|
||||
@@ -194,8 +200,7 @@ namespace input {
|
||||
* @param keyCode The VKEY code
|
||||
* @return 0 if no shortcut applied, > 0 if shortcut applied.
|
||||
*/
|
||||
inline int
|
||||
apply_shortcut(short keyCode) {
|
||||
inline int apply_shortcut(short keyCode) {
|
||||
constexpr auto VK_F1 = 0x70;
|
||||
constexpr auto VK_F13 = 0x7C;
|
||||
|
||||
@@ -215,8 +220,7 @@ namespace input {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
print(PNV_REL_MOUSE_MOVE_PACKET packet) {
|
||||
void print(PNV_REL_MOUSE_MOVE_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin relative mouse move packet--"sv << std::endl
|
||||
<< "deltaX ["sv << util::endian::big(packet->deltaX) << ']' << std::endl
|
||||
@@ -224,8 +228,7 @@ namespace input {
|
||||
<< "--end relative mouse move packet--"sv;
|
||||
}
|
||||
|
||||
void
|
||||
print(PNV_ABS_MOUSE_MOVE_PACKET packet) {
|
||||
void print(PNV_ABS_MOUSE_MOVE_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin absolute mouse move packet--"sv << std::endl
|
||||
<< "x ["sv << util::endian::big(packet->x) << ']' << std::endl
|
||||
@@ -235,8 +238,7 @@ namespace input {
|
||||
<< "--end absolute mouse move packet--"sv;
|
||||
}
|
||||
|
||||
void
|
||||
print(PNV_MOUSE_BUTTON_PACKET packet) {
|
||||
void print(PNV_MOUSE_BUTTON_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin mouse button packet--"sv << std::endl
|
||||
<< "action ["sv << util::hex(packet->header.magic).to_string_view() << ']' << std::endl
|
||||
@@ -244,24 +246,21 @@ namespace input {
|
||||
<< "--end mouse button packet--"sv;
|
||||
}
|
||||
|
||||
void
|
||||
print(PNV_SCROLL_PACKET packet) {
|
||||
void print(PNV_SCROLL_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin mouse scroll packet--"sv << std::endl
|
||||
<< "scrollAmt1 ["sv << util::endian::big(packet->scrollAmt1) << ']' << std::endl
|
||||
<< "--end mouse scroll packet--"sv;
|
||||
}
|
||||
|
||||
void
|
||||
print(PSS_HSCROLL_PACKET packet) {
|
||||
void print(PSS_HSCROLL_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin mouse hscroll packet--"sv << std::endl
|
||||
<< "scrollAmount ["sv << util::endian::big(packet->scrollAmount) << ']' << std::endl
|
||||
<< "--end mouse hscroll packet--"sv;
|
||||
}
|
||||
|
||||
void
|
||||
print(PNV_KEYBOARD_PACKET packet) {
|
||||
void print(PNV_KEYBOARD_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin keyboard packet--"sv << std::endl
|
||||
<< "keyAction ["sv << util::hex(packet->header.magic).to_string_view() << ']' << std::endl
|
||||
@@ -271,8 +270,7 @@ namespace input {
|
||||
<< "--end keyboard packet--"sv;
|
||||
}
|
||||
|
||||
void
|
||||
print(PNV_UNICODE_PACKET packet) {
|
||||
void print(PNV_UNICODE_PACKET packet) {
|
||||
std::string text(packet->text, util::endian::big(packet->header.size) - sizeof(packet->header.magic));
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin unicode packet--"sv << std::endl
|
||||
@@ -280,8 +278,7 @@ namespace input {
|
||||
<< "--end unicode packet--"sv;
|
||||
}
|
||||
|
||||
void
|
||||
print(PNV_MULTI_CONTROLLER_PACKET packet) {
|
||||
void print(PNV_MULTI_CONTROLLER_PACKET packet) {
|
||||
// Moonlight spams controller packet even when not necessary
|
||||
BOOST_LOG(verbose)
|
||||
<< "--begin controller packet--"sv << std::endl
|
||||
@@ -301,8 +298,7 @@ namespace input {
|
||||
* @brief Prints a touch packet.
|
||||
* @param packet The touch packet.
|
||||
*/
|
||||
void
|
||||
print(PSS_TOUCH_PACKET packet) {
|
||||
void print(PSS_TOUCH_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin touch packet--"sv << std::endl
|
||||
<< "eventType ["sv << util::hex(packet->eventType).to_string_view() << ']' << std::endl
|
||||
@@ -320,8 +316,7 @@ namespace input {
|
||||
* @brief Prints a pen packet.
|
||||
* @param packet The pen packet.
|
||||
*/
|
||||
void
|
||||
print(PSS_PEN_PACKET packet) {
|
||||
void print(PSS_PEN_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin pen packet--"sv << std::endl
|
||||
<< "eventType ["sv << util::hex(packet->eventType).to_string_view() << ']' << std::endl
|
||||
@@ -341,8 +336,7 @@ namespace input {
|
||||
* @brief Prints a controller arrival packet.
|
||||
* @param packet The controller arrival packet.
|
||||
*/
|
||||
void
|
||||
print(PSS_CONTROLLER_ARRIVAL_PACKET packet) {
|
||||
void print(PSS_CONTROLLER_ARRIVAL_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin controller arrival packet--"sv << std::endl
|
||||
<< "controllerNumber ["sv << (uint32_t) packet->controllerNumber << ']' << std::endl
|
||||
@@ -356,8 +350,7 @@ namespace input {
|
||||
* @brief Prints a controller touch packet.
|
||||
* @param packet The controller touch packet.
|
||||
*/
|
||||
void
|
||||
print(PSS_CONTROLLER_TOUCH_PACKET packet) {
|
||||
void print(PSS_CONTROLLER_TOUCH_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin controller touch packet--"sv << std::endl
|
||||
<< "controllerNumber ["sv << (uint32_t) packet->controllerNumber << ']' << std::endl
|
||||
@@ -373,8 +366,7 @@ namespace input {
|
||||
* @brief Prints a controller motion packet.
|
||||
* @param packet The controller motion packet.
|
||||
*/
|
||||
void
|
||||
print(PSS_CONTROLLER_MOTION_PACKET packet) {
|
||||
void print(PSS_CONTROLLER_MOTION_PACKET packet) {
|
||||
BOOST_LOG(verbose)
|
||||
<< "--begin controller motion packet--"sv << std::endl
|
||||
<< "controllerNumber ["sv << util::hex(packet->controllerNumber).to_string_view() << ']' << std::endl
|
||||
@@ -389,8 +381,7 @@ namespace input {
|
||||
* @brief Prints a controller battery packet.
|
||||
* @param packet The controller battery packet.
|
||||
*/
|
||||
void
|
||||
print(PSS_CONTROLLER_BATTERY_PACKET packet) {
|
||||
void print(PSS_CONTROLLER_BATTERY_PACKET packet) {
|
||||
BOOST_LOG(verbose)
|
||||
<< "--begin controller battery packet--"sv << std::endl
|
||||
<< "controllerNumber ["sv << util::hex(packet->controllerNumber).to_string_view() << ']' << std::endl
|
||||
@@ -399,8 +390,7 @@ namespace input {
|
||||
<< "--end controller battery packet--"sv;
|
||||
}
|
||||
|
||||
void
|
||||
print(void *payload) {
|
||||
void print(void *payload) {
|
||||
auto header = (PNV_INPUT_HEADER) payload;
|
||||
|
||||
switch (util::endian::little(header->magic)) {
|
||||
@@ -451,8 +441,7 @@ namespace input {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PNV_REL_MOUSE_MOVE_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_REL_MOUSE_MOVE_PACKET packet) {
|
||||
if (!config::input.mouse) {
|
||||
return;
|
||||
}
|
||||
@@ -468,8 +457,7 @@ namespace input {
|
||||
* @param size The size of the client's surface containing the value.
|
||||
* @return The host-relative coordinate pair if a touchport is available.
|
||||
*/
|
||||
std::optional<std::pair<float, float>>
|
||||
client_to_touchport(std::shared_ptr<input_t> &input, const std::pair<float, float> &val, const std::pair<float, float> &size) {
|
||||
std::optional<std::pair<float, float>> client_to_touchport(std::shared_ptr<input_t> &input, const std::pair<float, float> &val, const std::pair<float, float> &size) {
|
||||
auto &touch_port_event = input->touch_port_event;
|
||||
auto &touch_port = input->touch_port;
|
||||
if (touch_port_event->peek()) {
|
||||
@@ -492,7 +480,7 @@ namespace input {
|
||||
x = std::clamp(x, offsetX, (size.first * scalarX) - offsetX);
|
||||
y = std::clamp(y, offsetY, (size.second * scalarY) - offsetY);
|
||||
|
||||
return std::pair { (x - offsetX) * touch_port.scalar_inv, (y - offsetY) * touch_port.scalar_inv };
|
||||
return std::pair {(x - offsetX) * touch_port.scalar_inv, (y - offsetY) * touch_port.scalar_inv};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -502,8 +490,7 @@ namespace input {
|
||||
* @param scalar The scalar cartesian coordinate pair.
|
||||
* @return The scaled radial coordinate.
|
||||
*/
|
||||
float
|
||||
multiply_polar_by_cartesian_scalar(float r, float angle, const std::pair<float, float> &scalar) {
|
||||
float multiply_polar_by_cartesian_scalar(float r, float angle, const std::pair<float, float> &scalar) {
|
||||
// Convert polar to cartesian coordinates
|
||||
float x = r * std::cos(angle);
|
||||
float y = r * std::sin(angle);
|
||||
@@ -516,8 +503,7 @@ namespace input {
|
||||
return std::sqrt(std::pow(x, 2) + std::pow(y, 2));
|
||||
}
|
||||
|
||||
std::pair<float, float>
|
||||
scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar) {
|
||||
std::pair<float, float> scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar) {
|
||||
// If the rotation is unknown, we'll just scale both axes equally by using
|
||||
// a 45-degree angle for our scaling calculations
|
||||
float angle = rotation == LI_ROT_UNKNOWN ? (M_PI / 4) : (rotation * (M_PI / 180));
|
||||
@@ -527,11 +513,10 @@ namespace input {
|
||||
float minor = val.second != 0.0f ? val.second : val.first;
|
||||
|
||||
// The minor axis is perpendicular to major axis so the angle must be rotated by 90 degrees
|
||||
return { multiply_polar_by_cartesian_scalar(major, angle, scalar), multiply_polar_by_cartesian_scalar(minor, angle + (M_PI / 2), scalar) };
|
||||
return {multiply_polar_by_cartesian_scalar(major, angle, scalar), multiply_polar_by_cartesian_scalar(minor, angle + (M_PI / 2), scalar)};
|
||||
}
|
||||
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET packet) {
|
||||
if (!config::input.mouse) {
|
||||
return;
|
||||
}
|
||||
@@ -554,22 +539,23 @@ namespace input {
|
||||
auto width = (float) util::endian::big(packet->width);
|
||||
auto height = (float) util::endian::big(packet->height);
|
||||
|
||||
auto tpcoords = client_to_touchport(input, { x, y }, { width, height });
|
||||
auto tpcoords = client_to_touchport(input, {x, y}, {width, height});
|
||||
if (!tpcoords) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &touch_port = input->touch_port;
|
||||
platf::touch_port_t abs_port {
|
||||
touch_port.offset_x, touch_port.offset_y,
|
||||
touch_port.env_width, touch_port.env_height
|
||||
touch_port.offset_x,
|
||||
touch_port.offset_y,
|
||||
touch_port.env_width,
|
||||
touch_port.env_height
|
||||
};
|
||||
|
||||
platf::abs_mouse(platf_input, abs_port, tpcoords->first, tpcoords->second);
|
||||
}
|
||||
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PNV_MOUSE_BUTTON_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_MOUSE_BUTTON_PACKET packet) {
|
||||
if (!config::input.mouse) {
|
||||
return;
|
||||
}
|
||||
@@ -617,7 +603,8 @@ namespace input {
|
||||
}
|
||||
if (
|
||||
button == BUTTON_RIGHT && !release &&
|
||||
input->mouse_left_button_timeout > DISABLE_LEFT_BUTTON_DELAY) {
|
||||
input->mouse_left_button_timeout > DISABLE_LEFT_BUTTON_DELAY
|
||||
) {
|
||||
platf::button_mouse(platf_input, BUTTON_RIGHT, false);
|
||||
platf::button_mouse(platf_input, BUTTON_RIGHT, true);
|
||||
|
||||
@@ -629,8 +616,7 @@ namespace input {
|
||||
platf::button_mouse(platf_input, button, release);
|
||||
}
|
||||
|
||||
short
|
||||
map_keycode(short keycode) {
|
||||
short map_keycode(short keycode) {
|
||||
auto it = config::input.keybindings.find(keycode);
|
||||
if (it != std::end(config::input.keybindings)) {
|
||||
return it->second;
|
||||
@@ -642,16 +628,14 @@ namespace input {
|
||||
/**
|
||||
* @brief Update flags for keyboard shortcut combo's
|
||||
*/
|
||||
inline void
|
||||
update_shortcutFlags(int *flags, short keyCode, bool release) {
|
||||
inline void update_shortcutFlags(int *flags, short keyCode, bool release) {
|
||||
switch (keyCode) {
|
||||
case VKEY_SHIFT:
|
||||
case VKEY_LSHIFT:
|
||||
case VKEY_RSHIFT:
|
||||
if (release) {
|
||||
*flags &= ~input_t::SHIFT;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
*flags |= input_t::SHIFT;
|
||||
}
|
||||
break;
|
||||
@@ -660,8 +644,7 @@ namespace input {
|
||||
case VKEY_RCONTROL:
|
||||
if (release) {
|
||||
*flags &= ~input_t::CTRL;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
*flags |= input_t::CTRL;
|
||||
}
|
||||
break;
|
||||
@@ -670,16 +653,14 @@ namespace input {
|
||||
case VKEY_RMENU:
|
||||
if (release) {
|
||||
*flags &= ~input_t::ALT;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
*flags |= input_t::ALT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
is_modifier(uint16_t keyCode) {
|
||||
bool is_modifier(uint16_t keyCode) {
|
||||
switch (keyCode) {
|
||||
case VKEY_SHIFT:
|
||||
case VKEY_LSHIFT:
|
||||
@@ -696,8 +677,7 @@ namespace input {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
send_key_and_modifiers(uint16_t key_code, bool release, uint8_t flags, uint8_t synthetic_modifiers) {
|
||||
void send_key_and_modifiers(uint16_t key_code, bool release, uint8_t flags, uint8_t synthetic_modifiers) {
|
||||
if (!release) {
|
||||
// Press any synthetic modifiers required for this key
|
||||
if (synthetic_modifiers & MODIFIER_SHIFT) {
|
||||
@@ -727,8 +707,7 @@ namespace input {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
repeat_key(uint16_t key_code, uint8_t flags, uint8_t synthetic_modifiers) {
|
||||
void repeat_key(uint16_t key_code, uint8_t flags, uint8_t synthetic_modifiers) {
|
||||
// If key no longer pressed, stop repeating
|
||||
if (!key_press[make_kpid(key_code, flags)]) {
|
||||
key_press_repeat_id = nullptr;
|
||||
@@ -740,8 +719,7 @@ namespace input {
|
||||
key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code, flags, synthetic_modifiers).task_id;
|
||||
}
|
||||
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
|
||||
if (!config::input.keyboard) {
|
||||
return;
|
||||
}
|
||||
@@ -780,13 +758,11 @@ namespace input {
|
||||
if (config::input.key_repeat_delay.count() > 0) {
|
||||
key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_delay, keyCode, packet->flags, synthetic_modifiers).task_id;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Already released
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!release) {
|
||||
} else if (!release) {
|
||||
// Already pressed down key
|
||||
return;
|
||||
}
|
||||
@@ -803,16 +779,14 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param packet The scroll packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PNV_SCROLL_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_SCROLL_PACKET packet) {
|
||||
if (!config::input.mouse) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config::input.high_resolution_scrolling) {
|
||||
platf::scroll(platf_input, util::endian::big(packet->scrollAmt1));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
input->accumulated_vscroll_delta += util::endian::big(packet->scrollAmt1);
|
||||
auto full_ticks = input->accumulated_vscroll_delta / WHEEL_DELTA;
|
||||
if (full_ticks) {
|
||||
@@ -828,16 +802,14 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param packet The scroll packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PSS_HSCROLL_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PSS_HSCROLL_PACKET packet) {
|
||||
if (!config::input.mouse) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config::input.high_resolution_scrolling) {
|
||||
platf::hscroll(platf_input, util::endian::big(packet->scrollAmount));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
input->accumulated_hscroll_delta += util::endian::big(packet->scrollAmount);
|
||||
auto full_ticks = input->accumulated_hscroll_delta / WHEEL_DELTA;
|
||||
if (full_ticks) {
|
||||
@@ -848,8 +820,7 @@ namespace input {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
passthrough(PNV_UNICODE_PACKET packet) {
|
||||
void passthrough(PNV_UNICODE_PACKET packet) {
|
||||
if (!config::input.keyboard) {
|
||||
return;
|
||||
}
|
||||
@@ -863,8 +834,7 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param packet The controller arrival packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_ARRIVAL_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_ARRIVAL_PACKET packet) {
|
||||
if (!config::input.controller) {
|
||||
return;
|
||||
}
|
||||
@@ -891,7 +861,7 @@ namespace input {
|
||||
}
|
||||
|
||||
// Allocate a new gamepad
|
||||
if (platf::alloc_gamepad(platf_input, { id, packet->controllerNumber }, arrival, input->feedback_queue)) {
|
||||
if (platf::alloc_gamepad(platf_input, {id, packet->controllerNumber}, arrival, input->feedback_queue)) {
|
||||
free_id(gamepadMask, id);
|
||||
return;
|
||||
}
|
||||
@@ -904,25 +874,23 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param packet The touch packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PSS_TOUCH_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PSS_TOUCH_PACKET packet) {
|
||||
if (!config::input.mouse) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the client normalized coordinates to touchport coordinates
|
||||
auto coords = client_to_touchport(input,
|
||||
{ from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f,
|
||||
from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f },
|
||||
{ 65535.f, 65535.f });
|
||||
auto coords = client_to_touchport(input, {from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f, from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f}, {65535.f, 65535.f});
|
||||
if (!coords) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &touch_port = input->touch_port;
|
||||
platf::touch_port_t abs_port {
|
||||
touch_port.offset_x, touch_port.offset_y,
|
||||
touch_port.env_width, touch_port.env_height
|
||||
touch_port.offset_x,
|
||||
touch_port.offset_y,
|
||||
touch_port.env_width,
|
||||
touch_port.env_height
|
||||
};
|
||||
|
||||
// Renormalize the coordinates
|
||||
@@ -937,10 +905,11 @@ namespace input {
|
||||
|
||||
// Normalize the contact area based on the touchport
|
||||
auto contact_area = scale_client_contact_area(
|
||||
{ from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f,
|
||||
from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f },
|
||||
{from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f,
|
||||
from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f},
|
||||
rotation,
|
||||
{ abs_port.width / 65535.f, abs_port.height / 65535.f });
|
||||
{abs_port.width / 65535.f, abs_port.height / 65535.f}
|
||||
);
|
||||
|
||||
platf::touch_input_t touch {
|
||||
packet->eventType,
|
||||
@@ -961,25 +930,23 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param packet The pen packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PSS_PEN_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PSS_PEN_PACKET packet) {
|
||||
if (!config::input.mouse) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the client normalized coordinates to touchport coordinates
|
||||
auto coords = client_to_touchport(input,
|
||||
{ from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f,
|
||||
from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f },
|
||||
{ 65535.f, 65535.f });
|
||||
auto coords = client_to_touchport(input, {from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f, from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f}, {65535.f, 65535.f});
|
||||
if (!coords) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &touch_port = input->touch_port;
|
||||
platf::touch_port_t abs_port {
|
||||
touch_port.offset_x, touch_port.offset_y,
|
||||
touch_port.env_width, touch_port.env_height
|
||||
touch_port.offset_x,
|
||||
touch_port.offset_y,
|
||||
touch_port.env_width,
|
||||
touch_port.env_height
|
||||
};
|
||||
|
||||
// Renormalize the coordinates
|
||||
@@ -994,10 +961,11 @@ namespace input {
|
||||
|
||||
// Normalize the contact area based on the touchport
|
||||
auto contact_area = scale_client_contact_area(
|
||||
{ from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f,
|
||||
from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f },
|
||||
{from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f,
|
||||
from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f},
|
||||
rotation,
|
||||
{ abs_port.width / 65535.f, abs_port.height / 65535.f });
|
||||
{abs_port.width / 65535.f, abs_port.height / 65535.f}
|
||||
);
|
||||
|
||||
platf::pen_input_t pen {
|
||||
packet->eventType,
|
||||
@@ -1020,8 +988,7 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param packet The controller touch packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_TOUCH_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_TOUCH_PACKET packet) {
|
||||
if (!config::input.controller) {
|
||||
return;
|
||||
}
|
||||
@@ -1038,7 +1005,7 @@ namespace input {
|
||||
}
|
||||
|
||||
platf::gamepad_touch_t touch {
|
||||
{ gamepad.id, packet->controllerNumber },
|
||||
{gamepad.id, packet->controllerNumber},
|
||||
packet->eventType,
|
||||
util::endian::little(packet->pointerId),
|
||||
from_clamped_netfloat(packet->x, 0.0f, 1.0f),
|
||||
@@ -1054,8 +1021,7 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param packet The controller motion packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_MOTION_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_MOTION_PACKET packet) {
|
||||
if (!config::input.controller) {
|
||||
return;
|
||||
}
|
||||
@@ -1072,7 +1038,7 @@ namespace input {
|
||||
}
|
||||
|
||||
platf::gamepad_motion_t motion {
|
||||
{ gamepad.id, packet->controllerNumber },
|
||||
{gamepad.id, packet->controllerNumber},
|
||||
packet->motionType,
|
||||
from_netfloat(packet->x),
|
||||
from_netfloat(packet->y),
|
||||
@@ -1087,8 +1053,7 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param packet The controller battery packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_BATTERY_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_BATTERY_PACKET packet) {
|
||||
if (!config::input.controller) {
|
||||
return;
|
||||
}
|
||||
@@ -1105,7 +1070,7 @@ namespace input {
|
||||
}
|
||||
|
||||
platf::gamepad_battery_t battery {
|
||||
{ gamepad.id, packet->controllerNumber },
|
||||
{gamepad.id, packet->controllerNumber},
|
||||
packet->batteryState,
|
||||
packet->batteryPercentage
|
||||
};
|
||||
@@ -1113,8 +1078,7 @@ namespace input {
|
||||
platf::gamepad_battery(platf_input, battery);
|
||||
}
|
||||
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET packet) {
|
||||
if (!config::input.controller) {
|
||||
return;
|
||||
}
|
||||
@@ -1135,14 +1099,13 @@ namespace input {
|
||||
return;
|
||||
}
|
||||
|
||||
if (platf::alloc_gamepad(platf_input, { id, (uint8_t) packet->controllerNumber }, {}, input->feedback_queue)) {
|
||||
if (platf::alloc_gamepad(platf_input, {id, (uint8_t) packet->controllerNumber}, {}, input->feedback_queue)) {
|
||||
free_id(gamepadMask, id);
|
||||
return;
|
||||
}
|
||||
|
||||
gamepad.id = id;
|
||||
}
|
||||
else if (!(packet->activeGamepadMask & (1 << packet->controllerNumber)) && gamepad.id >= 0) {
|
||||
} else if (!(packet->activeGamepadMask & (1 << packet->controllerNumber)) && gamepad.id >= 0) {
|
||||
// If this is the final event for a gamepad being removed, free the gamepad and return.
|
||||
free_gamepad(platf_input, gamepad.id);
|
||||
gamepad.id = -1;
|
||||
@@ -1219,8 +1182,7 @@ namespace input {
|
||||
|
||||
gamepad.back_timeout_id = task_pool.pushDelayed(std::move(f), config::input.back_button_timeout).task_id;
|
||||
}
|
||||
}
|
||||
else if (gamepad.back_timeout_id) {
|
||||
} else if (gamepad.back_timeout_id) {
|
||||
task_pool.cancel(gamepad.back_timeout_id);
|
||||
gamepad.back_timeout_id = nullptr;
|
||||
}
|
||||
@@ -1243,8 +1205,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PNV_REL_MOUSE_MOVE_PACKET dest, PNV_REL_MOUSE_MOVE_PACKET src) {
|
||||
batch_result_e batch(PNV_REL_MOUSE_MOVE_PACKET dest, PNV_REL_MOUSE_MOVE_PACKET src) {
|
||||
short deltaX, deltaY;
|
||||
|
||||
// Batching is safe as long as the result doesn't overflow a 16-bit integer
|
||||
@@ -1267,8 +1228,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PNV_ABS_MOUSE_MOVE_PACKET dest, PNV_ABS_MOUSE_MOVE_PACKET src) {
|
||||
batch_result_e batch(PNV_ABS_MOUSE_MOVE_PACKET dest, PNV_ABS_MOUSE_MOVE_PACKET src) {
|
||||
// Batching must only happen if the reference width and height don't change
|
||||
if (dest->width != src->width || dest->height != src->height) {
|
||||
return batch_result_e::terminate_batch;
|
||||
@@ -1285,8 +1245,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PNV_SCROLL_PACKET dest, PNV_SCROLL_PACKET src) {
|
||||
batch_result_e batch(PNV_SCROLL_PACKET dest, PNV_SCROLL_PACKET src) {
|
||||
short scrollAmt;
|
||||
|
||||
// Batching is safe as long as the result doesn't overflow a 16-bit integer
|
||||
@@ -1306,8 +1265,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PSS_HSCROLL_PACKET dest, PSS_HSCROLL_PACKET src) {
|
||||
batch_result_e batch(PSS_HSCROLL_PACKET dest, PSS_HSCROLL_PACKET src) {
|
||||
short scrollAmt;
|
||||
|
||||
// Batching is safe as long as the result doesn't overflow a 16-bit integer
|
||||
@@ -1326,8 +1284,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PNV_MULTI_CONTROLLER_PACKET dest, PNV_MULTI_CONTROLLER_PACKET src) {
|
||||
batch_result_e batch(PNV_MULTI_CONTROLLER_PACKET dest, PNV_MULTI_CONTROLLER_PACKET src) {
|
||||
// Do not allow batching if the active controllers change
|
||||
if (dest->activeGamepadMask != src->activeGamepadMask) {
|
||||
return batch_result_e::terminate_batch;
|
||||
@@ -1355,8 +1312,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PSS_TOUCH_PACKET dest, PSS_TOUCH_PACKET src) {
|
||||
batch_result_e batch(PSS_TOUCH_PACKET dest, PSS_TOUCH_PACKET src) {
|
||||
// Only batch hover or move events
|
||||
if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
|
||||
dest->eventType != LI_TOUCH_EVENT_HOVER) {
|
||||
@@ -1390,8 +1346,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PSS_PEN_PACKET dest, PSS_PEN_PACKET src) {
|
||||
batch_result_e batch(PSS_PEN_PACKET dest, PSS_PEN_PACKET src) {
|
||||
// Only batch hover or move events
|
||||
if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
|
||||
dest->eventType != LI_TOUCH_EVENT_HOVER) {
|
||||
@@ -1424,8 +1379,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PSS_CONTROLLER_TOUCH_PACKET dest, PSS_CONTROLLER_TOUCH_PACKET src) {
|
||||
batch_result_e batch(PSS_CONTROLLER_TOUCH_PACKET dest, PSS_CONTROLLER_TOUCH_PACKET src) {
|
||||
// Only batch hover or move events
|
||||
if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
|
||||
dest->eventType != LI_TOUCH_EVENT_HOVER) {
|
||||
@@ -1465,8 +1419,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PSS_CONTROLLER_MOTION_PACKET dest, PSS_CONTROLLER_MOTION_PACKET src) {
|
||||
batch_result_e batch(PSS_CONTROLLER_MOTION_PACKET dest, PSS_CONTROLLER_MOTION_PACKET src) {
|
||||
// We can only batch entries for the same controller, but allow batching attempts to continue
|
||||
// in case we have more packets for this controller later in the queue.
|
||||
if (dest->controllerNumber != src->controllerNumber) {
|
||||
@@ -1489,8 +1442,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PNV_INPUT_HEADER dest, PNV_INPUT_HEADER src) {
|
||||
batch_result_e batch(PNV_INPUT_HEADER dest, PNV_INPUT_HEADER src) {
|
||||
// We can only batch if the packet types are the same
|
||||
if (dest->magic != src->magic) {
|
||||
return batch_result_e::terminate_batch;
|
||||
@@ -1526,8 +1478,7 @@ namespace input {
|
||||
* @brief Called on a thread pool thread to process an input message.
|
||||
* @param input The input context pointer.
|
||||
*/
|
||||
void
|
||||
passthrough_next_message(std::shared_ptr<input_t> input) {
|
||||
void passthrough_next_message(std::shared_ptr<input_t> input) {
|
||||
// 'entry' backs the 'payload' pointer, so they must remain in scope together
|
||||
std::vector<uint8_t> entry;
|
||||
PNV_INPUT_HEADER payload;
|
||||
@@ -1558,12 +1509,10 @@ namespace input {
|
||||
if (batch_result == batch_result_e::terminate_batch) {
|
||||
// Stop batching
|
||||
break;
|
||||
}
|
||||
else if (batch_result == batch_result_e::batched) {
|
||||
} else if (batch_result == batch_result_e::batched) {
|
||||
// Erase this entry since it was batched
|
||||
i = input->input_queue.erase(i);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// We couldn't batch this entry, but try to batch later entries.
|
||||
i++;
|
||||
}
|
||||
@@ -1627,8 +1576,7 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param input_data The input message.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lg(input->input_queue_lock);
|
||||
input->input_queue.push_back(std::move(input_data));
|
||||
@@ -1636,8 +1584,7 @@ namespace input {
|
||||
task_pool.push(passthrough_next_message, input);
|
||||
}
|
||||
|
||||
void
|
||||
reset(std::shared_ptr<input_t> &input) {
|
||||
void reset(std::shared_ptr<input_t> &input) {
|
||||
task_pool.cancel(key_press_repeat_id);
|
||||
task_pool.cancel(input->mouse_left_button_timeout);
|
||||
|
||||
@@ -1668,15 +1615,13 @@ namespace input {
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<platf::deinit_t>
|
||||
init() {
|
||||
[[nodiscard]] std::unique_ptr<platf::deinit_t> init() {
|
||||
platf_input = platf::input();
|
||||
|
||||
return std::make_unique<deinit_t>();
|
||||
}
|
||||
|
||||
bool
|
||||
probe_gamepads() {
|
||||
bool probe_gamepads() {
|
||||
auto input = static_cast<platf::input_t *>(platf_input.get());
|
||||
const auto gamepads = platf::supported_gamepads(input);
|
||||
for (auto &gamepad : gamepads) {
|
||||
@@ -1687,18 +1632,18 @@ namespace input {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<input_t>
|
||||
alloc(safe::mail_t mail) {
|
||||
std::shared_ptr<input_t> alloc(safe::mail_t mail) {
|
||||
auto input = std::make_shared<input_t>(
|
||||
mail->event<input::touch_port_t>(mail::touch_port),
|
||||
mail->queue<platf::gamepad_feedback_msg_t>(mail::gamepad_feedback));
|
||||
mail->queue<platf::gamepad_feedback_msg_t>(mail::gamepad_feedback)
|
||||
);
|
||||
|
||||
// Workaround to ensure new frames will be captured when a client connects
|
||||
task_pool.pushDelayed([]() {
|
||||
platf::move_mouse(platf_input, 1, 1);
|
||||
platf::move_mouse(platf_input, -1, -1);
|
||||
},
|
||||
100ms);
|
||||
100ms);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
26
src/input.h
26
src/input.h
@@ -4,29 +4,25 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <functional>
|
||||
|
||||
// local includes
|
||||
#include "platform/common.h"
|
||||
#include "thread_safe.h"
|
||||
|
||||
namespace input {
|
||||
struct input_t;
|
||||
|
||||
void
|
||||
print(void *input);
|
||||
void
|
||||
reset(std::shared_ptr<input_t> &input);
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data);
|
||||
void print(void *input);
|
||||
void reset(std::shared_ptr<input_t> &input);
|
||||
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data);
|
||||
|
||||
[[nodiscard]] std::unique_ptr<platf::deinit_t>
|
||||
init();
|
||||
[[nodiscard]] std::unique_ptr<platf::deinit_t> init();
|
||||
|
||||
bool
|
||||
probe_gamepads();
|
||||
bool probe_gamepads();
|
||||
|
||||
std::shared_ptr<input_t>
|
||||
alloc(safe::mail_t mail);
|
||||
std::shared_ptr<input_t> alloc(safe::mail_t mail);
|
||||
|
||||
struct touch_port_t: public platf::touch_port_t {
|
||||
int env_width, env_height;
|
||||
@@ -36,8 +32,7 @@ namespace input {
|
||||
|
||||
float scalar_inv;
|
||||
|
||||
explicit
|
||||
operator bool() const {
|
||||
explicit operator bool() const {
|
||||
return width != 0 && height != 0 && env_width != 0 && env_height != 0;
|
||||
}
|
||||
};
|
||||
@@ -49,6 +44,5 @@ namespace input {
|
||||
* @param scalar The scalar cartesian coordinate pair.
|
||||
* @return The major and minor axis pair.
|
||||
*/
|
||||
std::pair<float, float>
|
||||
scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar);
|
||||
std::pair<float, float> scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar);
|
||||
} // namespace input
|
||||
|
||||
@@ -47,15 +47,13 @@ namespace logging {
|
||||
deinit();
|
||||
}
|
||||
|
||||
void
|
||||
deinit() {
|
||||
void deinit() {
|
||||
log_flush();
|
||||
bl::core::get()->remove_sink(sink);
|
||||
sink.reset();
|
||||
}
|
||||
|
||||
void
|
||||
formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os) {
|
||||
void formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os) {
|
||||
constexpr const char *message = "Message";
|
||||
constexpr const char *severity = "Severity";
|
||||
|
||||
@@ -90,7 +88,8 @@ namespace logging {
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - std::chrono::time_point_cast<std::chrono::seconds>(now));
|
||||
now - std::chrono::time_point_cast<std::chrono::seconds>(now)
|
||||
);
|
||||
|
||||
auto t = std::chrono::system_clock::to_time_t(now);
|
||||
auto lt = *std::localtime(&t);
|
||||
@@ -99,8 +98,7 @@ namespace logging {
|
||||
<< log_type << view.attribute_values()[message].extract<std::string>();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::unique_ptr<deinit_t>
|
||||
init(int min_log_level, const std::string &log_file) {
|
||||
[[nodiscard]] std::unique_ptr<deinit_t> init(int min_log_level, const std::string &log_file) {
|
||||
if (sink) {
|
||||
// Deinitialize the logging system before reinitializing it. This can probably only ever be hit in tests.
|
||||
deinit();
|
||||
@@ -112,7 +110,7 @@ namespace logging {
|
||||
sink = boost::make_shared<text_sink>();
|
||||
|
||||
#ifndef SUNSHINE_TESTS
|
||||
boost::shared_ptr<std::ostream> stream { &std::cout, boost::null_deleter() };
|
||||
boost::shared_ptr<std::ostream> stream {&std::cout, boost::null_deleter()};
|
||||
sink->locked_backend()->add_stream(stream);
|
||||
#endif
|
||||
sink->locked_backend()->add_stream(boost::make_shared<std::ofstream>(log_file));
|
||||
@@ -127,12 +125,10 @@ namespace logging {
|
||||
return std::make_unique<deinit_t>();
|
||||
}
|
||||
|
||||
void
|
||||
setup_av_logging(int min_log_level) {
|
||||
void setup_av_logging(int min_log_level) {
|
||||
if (min_log_level >= 1) {
|
||||
av_log_set_level(AV_LOG_QUIET);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
av_log_set_level(AV_LOG_DEBUG);
|
||||
}
|
||||
av_log_set_callback([](void *ptr, int level, const char *fmt, va_list vl) {
|
||||
@@ -144,28 +140,23 @@ namespace logging {
|
||||
// We print AV_LOG_FATAL at the error level. FFmpeg prints things as fatal that
|
||||
// are expected in some cases, such as lack of codec support or similar things.
|
||||
BOOST_LOG(error) << buffer;
|
||||
}
|
||||
else if (level <= AV_LOG_WARNING) {
|
||||
} else if (level <= AV_LOG_WARNING) {
|
||||
BOOST_LOG(warning) << buffer;
|
||||
}
|
||||
else if (level <= AV_LOG_INFO) {
|
||||
} else if (level <= AV_LOG_INFO) {
|
||||
BOOST_LOG(info) << buffer;
|
||||
}
|
||||
else if (level <= AV_LOG_VERBOSE) {
|
||||
} else if (level <= AV_LOG_VERBOSE) {
|
||||
// AV_LOG_VERBOSE is less verbose than AV_LOG_DEBUG
|
||||
BOOST_LOG(debug) << buffer;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(verbose) << buffer;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
setup_libdisplaydevice_logging(int min_log_level) {
|
||||
constexpr int min_level { static_cast<int>(display_device::Logger::LogLevel::verbose) };
|
||||
constexpr int max_level { static_cast<int>(display_device::Logger::LogLevel::fatal) };
|
||||
const auto log_level { static_cast<display_device::Logger::LogLevel>(std::min(std::max(min_level, min_log_level), max_level)) };
|
||||
void setup_libdisplaydevice_logging(int min_log_level) {
|
||||
constexpr int min_level {static_cast<int>(display_device::Logger::LogLevel::verbose)};
|
||||
constexpr int max_level {static_cast<int>(display_device::Logger::LogLevel::fatal)};
|
||||
const auto log_level {static_cast<display_device::Logger::LogLevel>(std::min(std::max(min_level, min_log_level), max_level))};
|
||||
|
||||
display_device::Logger::get().setLogLevel(log_level);
|
||||
display_device::Logger::get().setCustomCallback([](const display_device::Logger::LogLevel level, const std::string &message) {
|
||||
@@ -192,15 +183,13 @@ namespace logging {
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
log_flush() {
|
||||
void log_flush() {
|
||||
if (sink) {
|
||||
sink->flush();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
print_help(const char *name) {
|
||||
void print_help(const char *name) {
|
||||
std::cout
|
||||
<< "Usage: "sv << name << " [options] [/path/to/configuration_file] [--cmd]"sv << std::endl
|
||||
<< " Any configurable option can be overwritten with: \"name=value\""sv << std::endl
|
||||
@@ -220,13 +209,11 @@ namespace logging {
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
std::string
|
||||
bracket(const std::string &input) {
|
||||
std::string bracket(const std::string &input) {
|
||||
return "["s + input + "]"s;
|
||||
}
|
||||
|
||||
std::wstring
|
||||
bracket(const std::wstring &input) {
|
||||
std::wstring bracket(const std::wstring &input) {
|
||||
return L"["s + input + L"]"s;
|
||||
}
|
||||
|
||||
|
||||
103
src/logging.h
103
src/logging.h
@@ -41,11 +41,9 @@ namespace logging {
|
||||
* deinit();
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
deinit();
|
||||
void deinit();
|
||||
|
||||
void
|
||||
formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os);
|
||||
void formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os);
|
||||
|
||||
/**
|
||||
* @brief Initialize the logging system.
|
||||
@@ -56,22 +54,19 @@ namespace logging {
|
||||
* log_init(2, "sunshine.log");
|
||||
* @examples_end
|
||||
*/
|
||||
[[nodiscard]] std::unique_ptr<deinit_t>
|
||||
init(int min_log_level, const std::string &log_file);
|
||||
[[nodiscard]] std::unique_ptr<deinit_t> init(int min_log_level, const std::string &log_file);
|
||||
|
||||
/**
|
||||
* @brief Setup AV logging.
|
||||
* @param min_log_level The log level.
|
||||
*/
|
||||
void
|
||||
setup_av_logging(int min_log_level);
|
||||
void setup_av_logging(int min_log_level);
|
||||
|
||||
/**
|
||||
* @brief Setup logging for libdisplaydevice.
|
||||
* @param min_log_level The log level.
|
||||
*/
|
||||
void
|
||||
setup_libdisplaydevice_logging(int min_log_level);
|
||||
void setup_libdisplaydevice_logging(int min_log_level);
|
||||
|
||||
/**
|
||||
* @brief Flush the log.
|
||||
@@ -79,8 +74,7 @@ namespace logging {
|
||||
* log_flush();
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
log_flush();
|
||||
void log_flush();
|
||||
|
||||
/**
|
||||
* @brief Print help to stdout.
|
||||
@@ -89,8 +83,7 @@ namespace logging {
|
||||
* print_help("sunshine");
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
print_help(const char *name);
|
||||
void print_help(const char *name);
|
||||
|
||||
/**
|
||||
* @brief A helper class for tracking and logging numerical values across a period of time
|
||||
@@ -105,28 +98,24 @@ namespace logging {
|
||||
* // [2024:01:01:12:00:00]: Debug: Test time value (min/max/avg): 1ms/3ms/2.00ms
|
||||
* @examples_end
|
||||
*/
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
class min_max_avg_periodic_logger {
|
||||
public:
|
||||
min_max_avg_periodic_logger(boost::log::sources::severity_logger<int> &severity,
|
||||
std::string_view message,
|
||||
std::string_view units,
|
||||
std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
|
||||
min_max_avg_periodic_logger(boost::log::sources::severity_logger<int> &severity, std::string_view message, std::string_view units, std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
|
||||
severity(severity),
|
||||
message(message),
|
||||
units(units),
|
||||
interval(interval_in_seconds),
|
||||
enabled(config::sunshine.min_log_level <= severity.default_severity()) {}
|
||||
enabled(config::sunshine.min_log_level <= severity.default_severity()) {
|
||||
}
|
||||
|
||||
void
|
||||
collect_and_log(const T &value) {
|
||||
void collect_and_log(const T &value) {
|
||||
if (enabled) {
|
||||
auto print_info = [&](const T &min_value, const T &max_value, double avg_value) {
|
||||
auto f = stat_trackers::two_digits_after_decimal();
|
||||
if constexpr (std::is_floating_point_v<T>) {
|
||||
BOOST_LOG(severity.get()) << message << " (min/max/avg): " << f % min_value << units << "/" << f % max_value << units << "/" << f % avg_value << units;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(severity.get()) << message << " (min/max/avg): " << min_value << units << "/" << max_value << units << "/" << f % avg_value << units;
|
||||
}
|
||||
};
|
||||
@@ -134,18 +123,19 @@ namespace logging {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
collect_and_log(std::function<T()> func) {
|
||||
if (enabled) collect_and_log(func());
|
||||
void collect_and_log(std::function<T()> func) {
|
||||
if (enabled) {
|
||||
collect_and_log(func());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
reset() {
|
||||
if (enabled) tracker.reset();
|
||||
void reset() {
|
||||
if (enabled) {
|
||||
tracker.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
is_enabled() const {
|
||||
bool is_enabled() const {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@@ -175,40 +165,41 @@ namespace logging {
|
||||
*/
|
||||
class time_delta_periodic_logger {
|
||||
public:
|
||||
time_delta_periodic_logger(boost::log::sources::severity_logger<int> &severity,
|
||||
std::string_view message,
|
||||
std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
|
||||
logger(severity, message, "ms", interval_in_seconds) {}
|
||||
|
||||
void
|
||||
first_point(const std::chrono::steady_clock::time_point &point) {
|
||||
if (logger.is_enabled()) point1 = point;
|
||||
time_delta_periodic_logger(boost::log::sources::severity_logger<int> &severity, std::string_view message, std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
|
||||
logger(severity, message, "ms", interval_in_seconds) {
|
||||
}
|
||||
|
||||
void
|
||||
first_point_now() {
|
||||
if (logger.is_enabled()) first_point(std::chrono::steady_clock::now());
|
||||
void first_point(const std::chrono::steady_clock::time_point &point) {
|
||||
if (logger.is_enabled()) {
|
||||
point1 = point;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
second_point_and_log(const std::chrono::steady_clock::time_point &point) {
|
||||
void first_point_now() {
|
||||
if (logger.is_enabled()) {
|
||||
first_point(std::chrono::steady_clock::now());
|
||||
}
|
||||
}
|
||||
|
||||
void second_point_and_log(const std::chrono::steady_clock::time_point &point) {
|
||||
if (logger.is_enabled()) {
|
||||
logger.collect_and_log(std::chrono::duration<double, std::milli>(point - point1).count());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
second_point_now_and_log() {
|
||||
if (logger.is_enabled()) second_point_and_log(std::chrono::steady_clock::now());
|
||||
void second_point_now_and_log() {
|
||||
if (logger.is_enabled()) {
|
||||
second_point_and_log(std::chrono::steady_clock::now());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
reset() {
|
||||
if (logger.is_enabled()) logger.reset();
|
||||
void reset() {
|
||||
if (logger.is_enabled()) {
|
||||
logger.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
is_enabled() const {
|
||||
bool is_enabled() const {
|
||||
return logger.is_enabled();
|
||||
}
|
||||
|
||||
@@ -222,15 +213,13 @@ namespace logging {
|
||||
* @param input Input string.
|
||||
* @return Enclosed string.
|
||||
*/
|
||||
std::string
|
||||
bracket(const std::string &input);
|
||||
std::string bracket(const std::string &input);
|
||||
|
||||
/**
|
||||
* @brief Enclose string in square brackets.
|
||||
* @param input Input string.
|
||||
* @return Enclosed string.
|
||||
*/
|
||||
std::wstring
|
||||
bracket(const std::wstring &input);
|
||||
std::wstring bracket(const std::wstring &input);
|
||||
|
||||
} // namespace logging
|
||||
|
||||
67
src/main.cpp
67
src/main.cpp
@@ -30,31 +30,37 @@ extern "C" {
|
||||
using namespace std::literals;
|
||||
|
||||
std::map<int, std::function<void()>> signal_handlers;
|
||||
void
|
||||
on_signal_forwarder(int sig) {
|
||||
|
||||
void on_signal_forwarder(int sig) {
|
||||
signal_handlers.at(sig)();
|
||||
}
|
||||
|
||||
template <class FN>
|
||||
void
|
||||
on_signal(int sig, FN &&fn) {
|
||||
template<class FN>
|
||||
void on_signal(int sig, FN &&fn) {
|
||||
signal_handlers.emplace(sig, std::forward<FN>(fn));
|
||||
|
||||
std::signal(sig, on_signal_forwarder);
|
||||
}
|
||||
|
||||
std::map<std::string_view, std::function<int(const char *name, int argc, char **argv)>> cmd_to_func {
|
||||
{ "creds"sv, [](const char *name, int argc, char **argv) { return args::creds(name, argc, argv); } },
|
||||
{ "help"sv, [](const char *name, int argc, char **argv) { return args::help(name); } },
|
||||
{ "version"sv, [](const char *name, int argc, char **argv) { return args::version(); } },
|
||||
{"creds"sv, [](const char *name, int argc, char **argv) {
|
||||
return args::creds(name, argc, argv);
|
||||
}},
|
||||
{"help"sv, [](const char *name, int argc, char **argv) {
|
||||
return args::help(name);
|
||||
}},
|
||||
{"version"sv, [](const char *name, int argc, char **argv) {
|
||||
return args::version();
|
||||
}},
|
||||
#ifdef _WIN32
|
||||
{ "restore-nvprefs-undo"sv, [](const char *name, int argc, char **argv) { return args::restore_nvprefs_undo(); } },
|
||||
{"restore-nvprefs-undo"sv, [](const char *name, int argc, char **argv) {
|
||||
return args::restore_nvprefs_undo();
|
||||
}},
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
LRESULT CALLBACK
|
||||
SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||
LRESULT CALLBACK SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||
switch (uMsg) {
|
||||
case WM_CLOSE:
|
||||
DestroyWindow(hwnd);
|
||||
@@ -62,19 +68,19 @@ SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
case WM_ENDSESSION: {
|
||||
// Terminate ourselves with a blocking exit call
|
||||
std::cout << "Received WM_ENDSESSION"sv << std::endl;
|
||||
lifetime::exit_sunshine(0, false);
|
||||
return 0;
|
||||
}
|
||||
case WM_ENDSESSION:
|
||||
{
|
||||
// Terminate ourselves with a blocking exit call
|
||||
std::cout << "Received WM_ENDSESSION"sv << std::endl;
|
||||
lifetime::exit_sunshine(0, false);
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
WINAPI BOOL
|
||||
ConsoleCtrlHandler(DWORD type) {
|
||||
WINAPI BOOL ConsoleCtrlHandler(DWORD type) {
|
||||
if (type == CTRL_CLOSE_EVENT) {
|
||||
BOOST_LOG(info) << "Console closed handler called";
|
||||
lifetime::exit_sunshine(0, false);
|
||||
@@ -83,8 +89,7 @@ ConsoleCtrlHandler(DWORD type) {
|
||||
}
|
||||
#endif
|
||||
|
||||
int
|
||||
main(int argc, char *argv[]) {
|
||||
int main(int argc, char *argv[]) {
|
||||
lifetime::argv = argv;
|
||||
|
||||
task_pool_util::TaskPool::task_id_t force_shutdown = nullptr;
|
||||
@@ -101,6 +106,7 @@ main(int argc, char *argv[]) {
|
||||
|
||||
mail::man = std::make_shared<safe::mail_raw_t>();
|
||||
|
||||
// parse config file
|
||||
if (config::parse(argc, argv)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -118,6 +124,12 @@ main(int argc, char *argv[]) {
|
||||
// Log publisher metadata
|
||||
log_publisher_data();
|
||||
|
||||
// Log modified_config_settings
|
||||
for (auto &[name, val] : config::modified_config_settings) {
|
||||
BOOST_LOG(info) << "config: '"sv << name << "' = "sv << val;
|
||||
}
|
||||
config::modified_config_settings.clear();
|
||||
|
||||
if (!config::sunshine.cmd.name.empty()) {
|
||||
auto fn = cmd_to_func.find(config::sunshine.cmd.name);
|
||||
if (fn == std::end(cmd_to_func)) {
|
||||
@@ -188,7 +200,8 @@ main(int argc, char *argv[]) {
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
nullptr
|
||||
);
|
||||
|
||||
session_monitor_hwnd_promise.set_value(wnd);
|
||||
|
||||
@@ -216,12 +229,10 @@ main(int argc, char *argv[]) {
|
||||
if (session_monitor_join_thread_future.wait_for(1s) == std::future_status::ready) {
|
||||
session_monitor_thread.join();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(warning) << "session_monitor_join_thread_future reached timeout";
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(warning) << "session_monitor_hwnd_future reached timeout";
|
||||
}
|
||||
|
||||
@@ -324,8 +335,8 @@ main(int argc, char *argv[]) {
|
||||
return lifetime::desired_exit_code;
|
||||
}
|
||||
|
||||
std::thread httpThread { nvhttp::start };
|
||||
std::thread configThread { confighttp::start };
|
||||
std::thread httpThread {nvhttp::start};
|
||||
std::thread configThread {confighttp::start};
|
||||
|
||||
#ifdef _WIN32
|
||||
// If we're using the default port and GameStream is enabled, warn the user
|
||||
|
||||
@@ -12,5 +12,4 @@
|
||||
* main(1, const char* args[] = {"sunshine", nullptr});
|
||||
* @examples_end
|
||||
*/
|
||||
int
|
||||
main(int argc, char *argv[]);
|
||||
int main(int argc, char *argv[]);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
@@ -14,7 +15,7 @@ namespace move_by_copy_util {
|
||||
* When a copy is made, it moves the object
|
||||
* This allows you to move an object when a move can't be done.
|
||||
*/
|
||||
template <class T>
|
||||
template<class T>
|
||||
class MoveByCopy {
|
||||
public:
|
||||
typedef T move_type;
|
||||
@@ -24,7 +25,8 @@ namespace move_by_copy_util {
|
||||
|
||||
public:
|
||||
explicit MoveByCopy(move_type &&to_move):
|
||||
_to_move(std::move(to_move)) {}
|
||||
_to_move(std::move(to_move)) {
|
||||
}
|
||||
|
||||
MoveByCopy(MoveByCopy &&other) = default;
|
||||
|
||||
@@ -32,11 +34,9 @@ namespace move_by_copy_util {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
MoveByCopy &
|
||||
operator=(MoveByCopy &&other) = default;
|
||||
MoveByCopy &operator=(MoveByCopy &&other) = default;
|
||||
|
||||
MoveByCopy &
|
||||
operator=(const MoveByCopy &other) {
|
||||
MoveByCopy &operator=(const MoveByCopy &other) {
|
||||
this->_to_move = std::move(const_cast<MoveByCopy &>(other)._to_move);
|
||||
|
||||
return *this;
|
||||
@@ -47,16 +47,14 @@ namespace move_by_copy_util {
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
MoveByCopy<T>
|
||||
cmove(T &movable) {
|
||||
template<class T>
|
||||
MoveByCopy<T> cmove(T &movable) {
|
||||
return MoveByCopy<T>(std::move(movable));
|
||||
}
|
||||
|
||||
// Do NOT use this unless you are absolutely certain the object to be moved is no longer used by the caller
|
||||
template <class T>
|
||||
MoveByCopy<T>
|
||||
const_cmove(const T &movable) {
|
||||
template<class T>
|
||||
MoveByCopy<T> const_cmove(const T &movable) {
|
||||
return MoveByCopy<T>(std::move(const_cast<T &>(movable)));
|
||||
}
|
||||
} // namespace move_by_copy_util
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
* @file src/network.cpp
|
||||
* @brief Definitions for networking related functions.
|
||||
*/
|
||||
#include "network.h"
|
||||
#include "config.h"
|
||||
#include "logging.h"
|
||||
#include "utility.h"
|
||||
// standard includes
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
// local includes
|
||||
#include "config.h"
|
||||
#include "logging.h"
|
||||
#include "network.h"
|
||||
#include "utility.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace ip = boost::asio::ip;
|
||||
@@ -33,8 +36,7 @@ namespace net {
|
||||
ip::make_network_v6("fe80::/64"sv),
|
||||
};
|
||||
|
||||
net_e
|
||||
from_enum_string(const std::string_view &view) {
|
||||
net_e from_enum_string(const std::string_view &view) {
|
||||
if (view == "wan") {
|
||||
return WAN;
|
||||
}
|
||||
@@ -45,8 +47,7 @@ namespace net {
|
||||
return PC;
|
||||
}
|
||||
|
||||
net_e
|
||||
from_address(const std::string_view &view) {
|
||||
net_e from_address(const std::string_view &view) {
|
||||
auto addr = normalize_address(ip::make_address(view));
|
||||
|
||||
if (addr.is_v6()) {
|
||||
@@ -61,8 +62,7 @@ namespace net {
|
||||
return LAN;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
for (auto &range : pc_ips_v4) {
|
||||
if (range.hosts().find(addr.to_v4()) != range.hosts().end()) {
|
||||
return PC;
|
||||
@@ -79,8 +79,7 @@ namespace net {
|
||||
return WAN;
|
||||
}
|
||||
|
||||
std::string_view
|
||||
to_enum_string(net_e net) {
|
||||
std::string_view to_enum_string(net_e net) {
|
||||
switch (net) {
|
||||
case PC:
|
||||
return "pc"sv;
|
||||
@@ -94,8 +93,7 @@ namespace net {
|
||||
return "wan"sv;
|
||||
}
|
||||
|
||||
af_e
|
||||
af_from_enum_string(const std::string_view &view) {
|
||||
af_e af_from_enum_string(const std::string_view &view) {
|
||||
if (view == "ipv4") {
|
||||
return IPV4;
|
||||
}
|
||||
@@ -107,8 +105,7 @@ namespace net {
|
||||
return BOTH;
|
||||
}
|
||||
|
||||
std::string_view
|
||||
af_to_any_address_string(af_e af) {
|
||||
std::string_view af_to_any_address_string(af_e af) {
|
||||
switch (af) {
|
||||
case IPV4:
|
||||
return "0.0.0.0"sv;
|
||||
@@ -120,8 +117,7 @@ namespace net {
|
||||
return "::"sv;
|
||||
}
|
||||
|
||||
boost::asio::ip::address
|
||||
normalize_address(boost::asio::ip::address address) {
|
||||
boost::asio::ip::address normalize_address(boost::asio::ip::address address) {
|
||||
// Convert IPv6-mapped IPv4 addresses into regular IPv4 addresses
|
||||
if (address.is_v6()) {
|
||||
auto v6 = address.to_v6();
|
||||
@@ -133,37 +129,31 @@ namespace net {
|
||||
return address;
|
||||
}
|
||||
|
||||
std::string
|
||||
addr_to_normalized_string(boost::asio::ip::address address) {
|
||||
std::string addr_to_normalized_string(boost::asio::ip::address address) {
|
||||
return normalize_address(address).to_string();
|
||||
}
|
||||
|
||||
std::string
|
||||
addr_to_url_escaped_string(boost::asio::ip::address address) {
|
||||
std::string addr_to_url_escaped_string(boost::asio::ip::address address) {
|
||||
address = normalize_address(address);
|
||||
if (address.is_v6()) {
|
||||
std::stringstream ss;
|
||||
ss << '[' << address.to_string() << ']';
|
||||
return ss.str();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return address.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
encryption_mode_for_address(boost::asio::ip::address address) {
|
||||
int encryption_mode_for_address(boost::asio::ip::address address) {
|
||||
auto nettype = net::from_address(address.to_string());
|
||||
if (nettype == net::net_e::PC || nettype == net::net_e::LAN) {
|
||||
return config::stream.lan_encryption_mode;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return config::stream.wan_encryption_mode;
|
||||
}
|
||||
}
|
||||
|
||||
host_t
|
||||
host_create(af_e af, ENetAddress &addr, std::uint16_t port) {
|
||||
host_t host_create(af_e af, ENetAddress &addr, std::uint16_t port) {
|
||||
static std::once_flag enet_init_flag;
|
||||
std::call_once(enet_init_flag, []() {
|
||||
enet_initialize();
|
||||
@@ -174,7 +164,7 @@ namespace net {
|
||||
enet_address_set_port(&addr, port);
|
||||
|
||||
// Maximum of 128 clients, which should be enough for anyone
|
||||
auto host = host_t { enet_host_create(af == IPV4 ? AF_INET : AF_INET6, &addr, 128, 0, 0, 0) };
|
||||
auto host = host_t {enet_host_create(af == IPV4 ? AF_INET : AF_INET6, &addr, 128, 0, 0, 0)};
|
||||
|
||||
// Enable opportunistic QoS tagging (automatically disables if the network appears to drop tagged packets)
|
||||
enet_socket_set_option(host->socket, ENET_SOCKOPT_QOS, 1);
|
||||
@@ -182,8 +172,7 @@ namespace net {
|
||||
return host;
|
||||
}
|
||||
|
||||
void
|
||||
free_host(ENetHost *host) {
|
||||
void free_host(ENetHost *host) {
|
||||
std::for_each(host->peers, host->peers + host->peerCount, [](ENetPeer &peer_ref) {
|
||||
ENetPeer *peer = &peer_ref;
|
||||
|
||||
@@ -195,10 +184,9 @@ namespace net {
|
||||
enet_host_destroy(host);
|
||||
}
|
||||
|
||||
std::uint16_t
|
||||
map_port(int port) {
|
||||
std::uint16_t map_port(int port) {
|
||||
// calculate the port from the config port
|
||||
auto mapped_port = (std::uint16_t)((int) config::sunshine.port + port);
|
||||
auto mapped_port = (std::uint16_t) ((int) config::sunshine.port + port);
|
||||
|
||||
// Ensure port is in the range of 1024-65535
|
||||
if (mapped_port < 1024 || mapped_port > 65535) {
|
||||
@@ -213,10 +201,9 @@ namespace net {
|
||||
* @param hostname The hostname to use for instance name generation.
|
||||
* @return Hostname-based instance name or "Sunshine" if hostname is invalid.
|
||||
*/
|
||||
std::string
|
||||
mdns_instance_name(const std::string_view &hostname) {
|
||||
std::string mdns_instance_name(const std::string_view &hostname) {
|
||||
// Start with the unmodified hostname
|
||||
std::string instancename { hostname.data(), hostname.size() };
|
||||
std::string instancename {hostname.data(), hostname.size()};
|
||||
|
||||
// Truncate to 63 characters per RFC 6763 section 7.2.
|
||||
if (instancename.size() > 63) {
|
||||
@@ -227,8 +214,7 @@ namespace net {
|
||||
// Replace any spaces with dashes
|
||||
if (instancename[i] == ' ') {
|
||||
instancename[i] = '-';
|
||||
}
|
||||
else if (!std::isalnum(instancename[i]) && instancename[i] != '-') {
|
||||
} else if (!std::isalnum(instancename[i]) && instancename[i] != '-') {
|
||||
// Stop at the first invalid character
|
||||
instancename.resize(i);
|
||||
break;
|
||||
|
||||
@@ -4,18 +4,19 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
// lib includes
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include <enet/enet.h>
|
||||
|
||||
// local includes
|
||||
#include "utility.h"
|
||||
|
||||
namespace net {
|
||||
void
|
||||
free_host(ENetHost *host);
|
||||
void free_host(ENetHost *host);
|
||||
|
||||
/**
|
||||
* @brief Map a specified port based on the base port.
|
||||
@@ -26,8 +27,7 @@ namespace net {
|
||||
* @examples_end
|
||||
* @todo Ensure port is not already in use by another application.
|
||||
*/
|
||||
std::uint16_t
|
||||
map_port(int port);
|
||||
std::uint16_t map_port(int port);
|
||||
|
||||
using host_t = util::safe_ptr<ENetHost, free_host>;
|
||||
using peer_t = ENetPeer *;
|
||||
@@ -44,32 +44,26 @@ namespace net {
|
||||
BOTH ///< IPv4 and IPv6
|
||||
};
|
||||
|
||||
net_e
|
||||
from_enum_string(const std::string_view &view);
|
||||
std::string_view
|
||||
to_enum_string(net_e net);
|
||||
net_e from_enum_string(const std::string_view &view);
|
||||
std::string_view to_enum_string(net_e net);
|
||||
|
||||
net_e
|
||||
from_address(const std::string_view &view);
|
||||
net_e from_address(const std::string_view &view);
|
||||
|
||||
host_t
|
||||
host_create(af_e af, ENetAddress &addr, std::uint16_t port);
|
||||
host_t host_create(af_e af, ENetAddress &addr, std::uint16_t port);
|
||||
|
||||
/**
|
||||
* @brief Get the address family enum value from a string.
|
||||
* @param view The config option value.
|
||||
* @return The address family enum value.
|
||||
*/
|
||||
af_e
|
||||
af_from_enum_string(const std::string_view &view);
|
||||
af_e af_from_enum_string(const std::string_view &view);
|
||||
|
||||
/**
|
||||
* @brief Get the wildcard binding address for a given address family.
|
||||
* @param af Address family.
|
||||
* @return Normalized address.
|
||||
*/
|
||||
std::string_view
|
||||
af_to_any_address_string(af_e af);
|
||||
std::string_view af_to_any_address_string(af_e af);
|
||||
|
||||
/**
|
||||
* @brief Convert an address to a normalized form.
|
||||
@@ -77,8 +71,7 @@ namespace net {
|
||||
* @param address The address to normalize.
|
||||
* @return Normalized address.
|
||||
*/
|
||||
boost::asio::ip::address
|
||||
normalize_address(boost::asio::ip::address address);
|
||||
boost::asio::ip::address normalize_address(boost::asio::ip::address address);
|
||||
|
||||
/**
|
||||
* @brief Get the given address in normalized string form.
|
||||
@@ -86,8 +79,7 @@ namespace net {
|
||||
* @param address The address to normalize.
|
||||
* @return Normalized address in string form.
|
||||
*/
|
||||
std::string
|
||||
addr_to_normalized_string(boost::asio::ip::address address);
|
||||
std::string addr_to_normalized_string(boost::asio::ip::address address);
|
||||
|
||||
/**
|
||||
* @brief Get the given address in a normalized form for the host portion of a URL.
|
||||
@@ -95,22 +87,19 @@ namespace net {
|
||||
* @param address The address to normalize and escape.
|
||||
* @return Normalized address in URL-escaped string.
|
||||
*/
|
||||
std::string
|
||||
addr_to_url_escaped_string(boost::asio::ip::address address);
|
||||
std::string addr_to_url_escaped_string(boost::asio::ip::address address);
|
||||
|
||||
/**
|
||||
* @brief Get the encryption mode for the given remote endpoint address.
|
||||
* @param address The address used to look up the desired encryption mode.
|
||||
* @return The WAN or LAN encryption mode, based on the provided address.
|
||||
*/
|
||||
int
|
||||
encryption_mode_for_address(boost::asio::ip::address address);
|
||||
int encryption_mode_for_address(boost::asio::ip::address address);
|
||||
|
||||
/**
|
||||
* @brief Returns a string for use as the instance name for mDNS.
|
||||
* @param hostname The hostname to use for instance name generation.
|
||||
* @return Hostname-based instance name or "Sunshine" if hostname is invalid.
|
||||
*/
|
||||
std::string
|
||||
mdns_instance_name(const std::string_view &hostname);
|
||||
std::string mdns_instance_name(const std::string_view &hostname);
|
||||
} // namespace net
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,161 +1,155 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_base.h
|
||||
* @brief Declarations for abstract platform-agnostic base of standalone NVENC encoder.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "nvenc_colorspace.h"
|
||||
#include "nvenc_config.h"
|
||||
#include "nvenc_encoded_frame.h"
|
||||
|
||||
#include "src/logging.h"
|
||||
#include "src/video.h"
|
||||
|
||||
#include <ffnvcodec/nvEncodeAPI.h>
|
||||
|
||||
/**
|
||||
* @brief Standalone NVENC encoder
|
||||
*/
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief Abstract platform-agnostic base of standalone NVENC encoder.
|
||||
* Derived classes perform platform-specific operations.
|
||||
*/
|
||||
class nvenc_base {
|
||||
public:
|
||||
/**
|
||||
* @param device_type Underlying device type used by derived class.
|
||||
*/
|
||||
explicit nvenc_base(NV_ENC_DEVICE_TYPE device_type);
|
||||
virtual ~nvenc_base();
|
||||
|
||||
nvenc_base(const nvenc_base &) = delete;
|
||||
nvenc_base &
|
||||
operator=(const nvenc_base &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Create the encoder.
|
||||
* @param config NVENC encoder configuration.
|
||||
* @param client_config Stream configuration requested by the client.
|
||||
* @param colorspace YUV colorspace.
|
||||
* @param buffer_format Platform-agnostic input surface format.
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
bool
|
||||
create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format);
|
||||
|
||||
/**
|
||||
* @brief Destroy the encoder.
|
||||
* Derived classes classes call it in the destructor.
|
||||
*/
|
||||
void
|
||||
destroy_encoder();
|
||||
|
||||
/**
|
||||
* @brief Encode the next frame using platform-specific input surface.
|
||||
* @param frame_index Frame index that uniquely identifies the frame.
|
||||
* Afterwards serves as parameter for `invalidate_ref_frames()`.
|
||||
* No restrictions on the first frame index, but later frame indexes must be subsequent.
|
||||
* @param force_idr Whether to encode frame as forced IDR.
|
||||
* @return Encoded frame.
|
||||
*/
|
||||
nvenc_encoded_frame
|
||||
encode_frame(uint64_t frame_index, bool force_idr);
|
||||
|
||||
/**
|
||||
* @brief Perform reference frame invalidation (RFI) procedure.
|
||||
* @param first_frame First frame index of the invalidation range.
|
||||
* @param last_frame Last frame index of the invalidation range.
|
||||
* @return `true` on success, `false` on error.
|
||||
* After error next frame must be encoded with `force_idr = true`.
|
||||
*/
|
||||
bool
|
||||
invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Required. Used for loading NvEnc library and setting `nvenc` variable with `NvEncodeAPICreateInstance()`.
|
||||
* Called during `create_encoder()` if `nvenc` variable is not initialized.
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
virtual bool
|
||||
init_library() = 0;
|
||||
|
||||
/**
|
||||
* @brief Required. Used for creating outside-facing input surface,
|
||||
* registering this surface with `nvenc->nvEncRegisterResource()` and setting `registered_input_buffer` variable.
|
||||
* Called during `create_encoder()`.
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
virtual bool
|
||||
create_and_register_input_buffer() = 0;
|
||||
|
||||
/**
|
||||
* @brief Optional. Override if you must perform additional operations on the registered input surface in the beginning of `encode_frame()`.
|
||||
* Typically used for interop copy.
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
virtual bool
|
||||
synchronize_input_buffer() { return true; }
|
||||
|
||||
/**
|
||||
* @brief Optional. Override if you want to create encoder in async mode.
|
||||
* In this case must also set `async_event_handle` variable.
|
||||
* @param timeout_ms Wait timeout in milliseconds
|
||||
* @return `true` on success, `false` on timeout or error
|
||||
*/
|
||||
virtual bool
|
||||
wait_for_async_event(uint32_t timeout_ms) { return false; }
|
||||
|
||||
bool
|
||||
nvenc_failed(NVENCSTATUS status);
|
||||
|
||||
/**
|
||||
* @brief This function returns the corresponding struct version for the minimum API required by the codec.
|
||||
* @details Reducing the struct versions maximizes driver compatibility by avoiding needless API breaks.
|
||||
* @param version The raw structure version from `NVENCAPI_STRUCT_VERSION()`.
|
||||
* @param v11_struct_version Optionally specifies the struct version to use with v11 SDK major versions.
|
||||
* @param v12_struct_version Optionally specifies the struct version to use with v12 SDK major versions.
|
||||
* @return A suitable struct version for the active codec.
|
||||
*/
|
||||
uint32_t
|
||||
min_struct_version(uint32_t version, uint32_t v11_struct_version = 0, uint32_t v12_struct_version = 0);
|
||||
|
||||
const NV_ENC_DEVICE_TYPE device_type;
|
||||
|
||||
void *encoder = nullptr;
|
||||
|
||||
struct {
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_UNDEFINED;
|
||||
uint32_t ref_frames_in_dpb = 0;
|
||||
bool rfi = false;
|
||||
} encoder_params;
|
||||
|
||||
std::string last_nvenc_error_string;
|
||||
|
||||
// Derived classes set these variables
|
||||
void *device = nullptr; ///< Platform-specific handle of encoding device.
|
||||
///< Should be set in constructor or `init_library()`.
|
||||
std::shared_ptr<NV_ENCODE_API_FUNCTION_LIST> nvenc; ///< Function pointers list produced by `NvEncodeAPICreateInstance()`.
|
||||
///< Should be set in `init_library()`.
|
||||
NV_ENC_REGISTERED_PTR registered_input_buffer = nullptr; ///< Platform-specific input surface registered with `NvEncRegisterResource()`.
|
||||
///< Should be set in `create_and_register_input_buffer()`.
|
||||
void *async_event_handle = nullptr; ///< (optional) Platform-specific handle of event object event.
|
||||
///< Can be set in constructor or `init_library()`, must override `wait_for_async_event()`.
|
||||
|
||||
private:
|
||||
NV_ENC_OUTPUT_PTR output_bitstream = nullptr;
|
||||
uint32_t minimum_api_version = 0;
|
||||
|
||||
struct {
|
||||
uint64_t last_encoded_frame_index = 0;
|
||||
bool rfi_needs_confirmation = false;
|
||||
std::pair<uint64_t, uint64_t> last_rfi_range;
|
||||
logging::min_max_avg_periodic_logger<double> frame_size_logger = { debug, "NvEnc: encoded frame sizes in kB", "" };
|
||||
} encoder_state;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
/**
|
||||
* @file src/nvenc/nvenc_base.h
|
||||
* @brief Declarations for abstract platform-agnostic base of standalone NVENC encoder.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// lib includes
|
||||
#include <ffnvcodec/nvEncodeAPI.h>
|
||||
|
||||
// local includes
|
||||
#include "nvenc_colorspace.h"
|
||||
#include "nvenc_config.h"
|
||||
#include "nvenc_encoded_frame.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/video.h"
|
||||
|
||||
/**
|
||||
* @brief Standalone NVENC encoder
|
||||
*/
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief Abstract platform-agnostic base of standalone NVENC encoder.
|
||||
* Derived classes perform platform-specific operations.
|
||||
*/
|
||||
class nvenc_base {
|
||||
public:
|
||||
/**
|
||||
* @param device_type Underlying device type used by derived class.
|
||||
*/
|
||||
explicit nvenc_base(NV_ENC_DEVICE_TYPE device_type);
|
||||
virtual ~nvenc_base();
|
||||
|
||||
nvenc_base(const nvenc_base &) = delete;
|
||||
nvenc_base &operator=(const nvenc_base &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Create the encoder.
|
||||
* @param config NVENC encoder configuration.
|
||||
* @param client_config Stream configuration requested by the client.
|
||||
* @param colorspace YUV colorspace.
|
||||
* @param buffer_format Platform-agnostic input surface format.
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
bool create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format);
|
||||
|
||||
/**
|
||||
* @brief Destroy the encoder.
|
||||
* Derived classes classes call it in the destructor.
|
||||
*/
|
||||
void destroy_encoder();
|
||||
|
||||
/**
|
||||
* @brief Encode the next frame using platform-specific input surface.
|
||||
* @param frame_index Frame index that uniquely identifies the frame.
|
||||
* Afterwards serves as parameter for `invalidate_ref_frames()`.
|
||||
* No restrictions on the first frame index, but later frame indexes must be subsequent.
|
||||
* @param force_idr Whether to encode frame as forced IDR.
|
||||
* @return Encoded frame.
|
||||
*/
|
||||
nvenc_encoded_frame encode_frame(uint64_t frame_index, bool force_idr);
|
||||
|
||||
/**
|
||||
* @brief Perform reference frame invalidation (RFI) procedure.
|
||||
* @param first_frame First frame index of the invalidation range.
|
||||
* @param last_frame Last frame index of the invalidation range.
|
||||
* @return `true` on success, `false` on error.
|
||||
* After error next frame must be encoded with `force_idr = true`.
|
||||
*/
|
||||
bool invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Required. Used for loading NvEnc library and setting `nvenc` variable with `NvEncodeAPICreateInstance()`.
|
||||
* Called during `create_encoder()` if `nvenc` variable is not initialized.
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
virtual bool init_library() = 0;
|
||||
|
||||
/**
|
||||
* @brief Required. Used for creating outside-facing input surface,
|
||||
* registering this surface with `nvenc->nvEncRegisterResource()` and setting `registered_input_buffer` variable.
|
||||
* Called during `create_encoder()`.
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
virtual bool create_and_register_input_buffer() = 0;
|
||||
|
||||
/**
|
||||
* @brief Optional. Override if you must perform additional operations on the registered input surface in the beginning of `encode_frame()`.
|
||||
* Typically used for interop copy.
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
virtual bool synchronize_input_buffer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Optional. Override if you want to create encoder in async mode.
|
||||
* In this case must also set `async_event_handle` variable.
|
||||
* @param timeout_ms Wait timeout in milliseconds
|
||||
* @return `true` on success, `false` on timeout or error
|
||||
*/
|
||||
virtual bool wait_for_async_event(uint32_t timeout_ms) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool nvenc_failed(NVENCSTATUS status);
|
||||
|
||||
/**
|
||||
* @brief This function returns the corresponding struct version for the minimum API required by the codec.
|
||||
* @details Reducing the struct versions maximizes driver compatibility by avoiding needless API breaks.
|
||||
* @param version The raw structure version from `NVENCAPI_STRUCT_VERSION()`.
|
||||
* @param v11_struct_version Optionally specifies the struct version to use with v11 SDK major versions.
|
||||
* @param v12_struct_version Optionally specifies the struct version to use with v12 SDK major versions.
|
||||
* @return A suitable struct version for the active codec.
|
||||
*/
|
||||
uint32_t min_struct_version(uint32_t version, uint32_t v11_struct_version = 0, uint32_t v12_struct_version = 0);
|
||||
|
||||
const NV_ENC_DEVICE_TYPE device_type;
|
||||
|
||||
void *encoder = nullptr;
|
||||
|
||||
struct {
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_UNDEFINED;
|
||||
uint32_t ref_frames_in_dpb = 0;
|
||||
bool rfi = false;
|
||||
} encoder_params;
|
||||
|
||||
std::string last_nvenc_error_string;
|
||||
|
||||
// Derived classes set these variables
|
||||
void *device = nullptr; ///< Platform-specific handle of encoding device.
|
||||
///< Should be set in constructor or `init_library()`.
|
||||
std::shared_ptr<NV_ENCODE_API_FUNCTION_LIST> nvenc; ///< Function pointers list produced by `NvEncodeAPICreateInstance()`.
|
||||
///< Should be set in `init_library()`.
|
||||
NV_ENC_REGISTERED_PTR registered_input_buffer = nullptr; ///< Platform-specific input surface registered with `NvEncRegisterResource()`.
|
||||
///< Should be set in `create_and_register_input_buffer()`.
|
||||
void *async_event_handle = nullptr; ///< (optional) Platform-specific handle of event object event.
|
||||
///< Can be set in constructor or `init_library()`, must override `wait_for_async_event()`.
|
||||
|
||||
private:
|
||||
NV_ENC_OUTPUT_PTR output_bitstream = nullptr;
|
||||
uint32_t minimum_api_version = 0;
|
||||
|
||||
struct {
|
||||
uint64_t last_encoded_frame_index = 0;
|
||||
bool rfi_needs_confirmation = false;
|
||||
std::pair<uint64_t, uint64_t> last_rfi_range;
|
||||
logging::min_max_avg_periodic_logger<double> frame_size_logger = {debug, "NvEnc: encoded frame sizes in kB", ""};
|
||||
} encoder_state;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_colorspace.h
|
||||
* @brief Declarations for NVENC YUV colorspace.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <ffnvcodec/nvEncodeAPI.h>
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief YUV colorspace and color range.
|
||||
*/
|
||||
struct nvenc_colorspace_t {
|
||||
NV_ENC_VUI_COLOR_PRIMARIES primaries;
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC tranfer_function;
|
||||
NV_ENC_VUI_MATRIX_COEFFS matrix;
|
||||
bool full_range;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
/**
|
||||
* @file src/nvenc/nvenc_colorspace.h
|
||||
* @brief Declarations for NVENC YUV colorspace.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// lib includes
|
||||
#include <ffnvcodec/nvEncodeAPI.h>
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief YUV colorspace and color range.
|
||||
*/
|
||||
struct nvenc_colorspace_t {
|
||||
NV_ENC_VUI_COLOR_PRIMARIES primaries;
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC tranfer_function;
|
||||
NV_ENC_VUI_MATRIX_COEFFS matrix;
|
||||
bool full_range;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_config.h
|
||||
* @brief Declarations for NVENC encoder configuration.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
enum class nvenc_two_pass {
|
||||
disabled, ///< Single pass, the fastest and no extra vram
|
||||
quarter_resolution, ///< Larger motion vectors being caught, faster and uses less extra vram
|
||||
full_resolution, ///< Better overall statistics, slower and uses more extra vram
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief NVENC encoder configuration.
|
||||
*/
|
||||
struct nvenc_config {
|
||||
// Quality preset from 1 to 7, higher is slower
|
||||
int quality_preset = 1;
|
||||
|
||||
// Use optional preliminary pass for better motion vectors, bitrate distribution and stricter VBV(HRD), uses CUDA cores
|
||||
nvenc_two_pass two_pass = nvenc_two_pass::quarter_resolution;
|
||||
|
||||
// Percentage increase of VBV/HRD from the default single frame, allows low-latency variable bitrate
|
||||
int vbv_percentage_increase = 0;
|
||||
|
||||
// Improves fades compression, uses CUDA cores
|
||||
bool weighted_prediction = false;
|
||||
|
||||
// Allocate more bitrate to flat regions since they're visually more perceptible, uses CUDA cores
|
||||
bool adaptive_quantization = false;
|
||||
|
||||
// Don't use QP below certain value, limits peak image quality to save bitrate
|
||||
bool enable_min_qp = false;
|
||||
|
||||
// Min QP value for H.264 when enable_min_qp is selected
|
||||
unsigned min_qp_h264 = 19;
|
||||
|
||||
// Min QP value for HEVC when enable_min_qp is selected
|
||||
unsigned min_qp_hevc = 23;
|
||||
|
||||
// Min QP value for AV1 when enable_min_qp is selected
|
||||
unsigned min_qp_av1 = 23;
|
||||
|
||||
// Use CAVLC entropy coding in H.264 instead of CABAC, not relevant and here for historical reasons
|
||||
bool h264_cavlc = false;
|
||||
|
||||
// Add filler data to encoded frames to stay at target bitrate, mainly for testing
|
||||
bool insert_filler_data = false;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
/**
|
||||
* @file src/nvenc/nvenc_config.h
|
||||
* @brief Declarations for NVENC encoder configuration.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
enum class nvenc_two_pass {
|
||||
disabled, ///< Single pass, the fastest and no extra vram
|
||||
quarter_resolution, ///< Larger motion vectors being caught, faster and uses less extra vram
|
||||
full_resolution, ///< Better overall statistics, slower and uses more extra vram
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief NVENC encoder configuration.
|
||||
*/
|
||||
struct nvenc_config {
|
||||
// Quality preset from 1 to 7, higher is slower
|
||||
int quality_preset = 1;
|
||||
|
||||
// Use optional preliminary pass for better motion vectors, bitrate distribution and stricter VBV(HRD), uses CUDA cores
|
||||
nvenc_two_pass two_pass = nvenc_two_pass::quarter_resolution;
|
||||
|
||||
// Percentage increase of VBV/HRD from the default single frame, allows low-latency variable bitrate
|
||||
int vbv_percentage_increase = 0;
|
||||
|
||||
// Improves fades compression, uses CUDA cores
|
||||
bool weighted_prediction = false;
|
||||
|
||||
// Allocate more bitrate to flat regions since they're visually more perceptible, uses CUDA cores
|
||||
bool adaptive_quantization = false;
|
||||
|
||||
// Don't use QP below certain value, limits peak image quality to save bitrate
|
||||
bool enable_min_qp = false;
|
||||
|
||||
// Min QP value for H.264 when enable_min_qp is selected
|
||||
unsigned min_qp_h264 = 19;
|
||||
|
||||
// Min QP value for HEVC when enable_min_qp is selected
|
||||
unsigned min_qp_hevc = 23;
|
||||
|
||||
// Min QP value for AV1 when enable_min_qp is selected
|
||||
unsigned min_qp_av1 = 23;
|
||||
|
||||
// Use CAVLC entropy coding in H.264 instead of CABAC, not relevant and here for historical reasons
|
||||
bool h264_cavlc = false;
|
||||
|
||||
// Add filler data to encoded frames to stay at target bitrate, mainly for testing
|
||||
bool insert_filler_data = false;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user