Compare commits

...

87 Commits

Author SHA1 Message Date
ReenigneArcher
483e2259d4 Merge pull request #969 from LizardByte/nightly
v0.19.0
2023-03-29 22:21:15 -04:00
ReenigneArcher
972f726ff9 installer: auto install vigembus (#1100) 2023-03-29 17:15:33 -04:00
ReenigneArcher
f169c6d116 v0.19.0 release prep (#1097) 2023-03-29 10:00:40 -04:00
KuleRucket
70674325ba Fix system tray Open Sunshine (#1095)
Co-authored-by: KuleRucket <luke.d.tucker@gmail.com>
Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
2023-03-28 18:51:10 -04:00
KuleRucket
57a722a3fb Continue in the face of failure (#1094)
Co-authored-by: KuleRucket <luke.d.tucker@gmail.com>
2023-03-28 17:54:46 -04:00
luk1337
1ab1b7920e Don't skip disconnected X11 outputs (#1071) 2023-03-28 16:44:32 -04:00
dependabot[bot]
44f1984af0 Bump third-party/moonlight-common-c from d3cb813 to c9426a6 (#1089)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-28 14:34:59 -04:00
dependabot[bot]
4b2f5befce Bump @fortawesome/fontawesome-free from 6.3.0 to 6.4.0 (#1088)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-28 13:57:49 -04:00
dependabot[bot]
c222e343b0 Bump third-party/miniupnp from 014c9df to e439318 (#1093)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-28 10:53:49 -04:00
luk1337
648df66b98 Fix X11 fallback during init (#1087) 2023-03-28 10:01:11 -04:00
ReenigneArcher
21eb4eb6dd clang: adjust formatting rules (#1015) 2023-03-27 21:45:29 -04:00
pgrunzjr
79cf382cd9 Add missing setup for default global prep command exclusion dropdown (#1085) 2023-03-27 20:55:28 -04:00
ns6089
be74b740f5 Properly handle libcurl flags in CMakeLists.txt on Windows (#1079) 2023-03-27 18:59:35 -04:00
ReenigneArcher
014ca7e8a1 commands: fix prep commands when empty (#1083) 2023-03-27 16:46:25 -04:00
ReenigneArcher
e1fddcc99c versioning: fix dirty commit notifications (#1084) 2023-03-27 15:55:21 -04:00
dependabot[bot]
3791edcec1 Bump third-party/nanors from 395e5ad to e9e242e (#1039)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-27 15:01:06 -04:00
dependabot[bot]
1dfe9ea0f6 Bump third-party/moonlight-common-c from 02f12e4 to c9426a6 (#1038)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-27 13:06:16 -04:00
dependabot[bot]
e18e4f433b Bump furo from 2023.3.23 to 2023.3.27 (#1082)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-27 12:42:27 -04:00
pgrunzjr
8c86baf627 Add support for global prep commands (#977) 2023-03-27 12:02:20 -04:00
Chase Payne
c2fba6f651 Add Support for Safely Elevating Administrator Privileges (#1036) 2023-03-27 10:51:48 -04:00
pgrunzjr
6a914f7016 Execute do/undo commands non-elevated (#1022) 2023-03-27 10:15:35 -04:00
ReenigneArcher
6f02274dc4 docs: suppress epub mimetype warnings (#1081) 2023-03-27 09:30:22 -04:00
Conn O'Griofa
bf24d0e7a6 Flatpak: update to org.freedesktop.Platform 22.08 (#936)
Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
2023-03-26 21:00:41 -04:00
Chase Payne
9b0e0565b1 Nightly Notification Bug Fixes (#1073) 2023-03-26 19:28:57 -04:00
KuleRucket
a487fb31ea Fix compiler warnings due to deprecated elements in C++17 (#1077)
Co-authored-by: KuleRucket <luke.d.tucker@gmail.com>
2023-03-25 21:52:46 -04:00
KuleRucket
c6548f4271 Provide ability to force a capture method via configuration. (#1063)
Co-authored-by: KuleRucket <luke.d.tucker@gmail.com>
2023-03-25 21:26:28 -04:00
luk1337
455155a1c9 Add missing <string> include (#1057) 2023-03-24 19:37:06 -04:00
ReenigneArcher
a0d7250c33 docs: update config defaults (#1070) 2023-03-24 19:00:27 -04:00
KuleRucket
8162d5f0d0 Change text input to select (#1067) 2023-03-24 18:18:14 -04:00
ReenigneArcher
5cdc0accad qodana: use workflow dispatch (#1069) 2023-03-24 17:41:30 -04:00
dependabot[bot]
79991654d0 Bump furo from 2022.12.7 to 2023.3.23 (#1065)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-24 08:14:50 -04:00
ReenigneArcher
c820f0a670 qodana: fix notification conditions (#1053) 2023-03-21 16:41:27 -04:00
ReenigneArcher
744b54ffd4 ci: add qodana (#1052) 2023-03-21 15:37:36 -04:00
Mathias Tillman
a192c504cd Use xdg-desktop-autostart.target for systemd service. (#901) 2023-03-20 10:47:14 -04:00
ReenigneArcher
e20ca7a8f0 tray-icon: execute as user (#1046) 2023-03-17 08:59:13 -04:00
ReenigneArcher
7e9b18458d ui: fix apply settings (#1045) 2023-03-16 11:27:48 -04:00
ReenigneArcher
d85b234f1b docs: add favicon (#1044) 2023-03-15 23:33:42 -04:00
ReenigneArcher
afc6966f10 config: only save non default values (#1023) 2023-03-15 17:05:06 -04:00
ReenigneArcher
014d693112 add tray icon (#1035) 2023-03-15 16:30:18 -04:00
dependabot[bot]
237f21573b Bump third-party/miniupnp from 014c9df to e439318 (#944)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-11 13:16:14 -05:00
ReenigneArcher
27c9c0e521 docs: add wayland resolution example (#1028) 2023-03-11 11:24:31 -05:00
Mark Dietzer
fbe5e2486f Fix startup when /dev/dri doesn't exist (#1027) 2023-03-11 10:27:07 -05:00
LizardByte-bot
21a728ccd2 Bump ffmpeg (#1026) 2023-03-10 13:32:45 -05:00
dependabot[bot]
a72b77de71 Bump third-party/moonlight-common-c from d3cb813 to 02f12e4 (#1003)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-10 12:00:12 -05:00
Brian Kendall
80aa61b6e4 Support compiling for earlier releases of macOS (#960) 2023-03-10 09:53:29 -05:00
ReenigneArcher
6d54356166 logging: change client verified messages to debug (#1020) 2023-03-10 09:05:52 -05:00
ReenigneArcher
dfb5293224 archlinux: disable downlaod timeout (#1024) 2023-03-10 08:19:42 -05:00
Joe
bf4ed899d9 Skip irrelevant submodules when building on Arch (#817)
Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
2023-03-09 22:29:06 -05:00
Elia Zammuto
c29c917474 Versioning improvements (#768)
Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
2023-03-09 18:13:57 -05:00
ReenigneArcher
889b93da2d input: invert default config settings (#1017) 2023-03-08 21:44:11 -05:00
Mathias Tillman
c3f3e1606d Fix linux clang build errors (#879)
Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
2023-03-08 18:05:38 -05:00
ReenigneArcher
8f1465d950 Docs improve source code documentation (#1016) 2023-03-08 17:31:19 -05:00
dependabot[bot]
c89fd83040 Bump third-party/ViGEmClient from 9e842ba to 726404e (#913)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-08 12:03:46 -05:00
dependabot[bot]
91f5d39540 Bump @fortawesome/fontawesome-free from 6.2.1 to 6.3.0 (#900)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-08 11:19:39 -05:00
Mark Dietzer
8227e8f8e5 Add XTest input fallback (#997) 2023-03-08 09:26:06 -05:00
ABeltramo
7d01b50498 feat: implemented unicode input mode (#966)
Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
2023-03-08 08:35:34 -05:00
Brian Kendall
f238cf5303 Add setting for suppressing input from mouse, keyboard, or gamepads (#941) 2023-03-08 07:47:19 -05:00
ReenigneArcher
31885434f2 docs: add doxygen (#1004) 2023-03-07 20:26:03 -05:00
dependabot[bot]
a1e6f441e4 Bump babel from 2.11.0 to 2.12.1 (#991)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-07 16:34:10 -05:00
ReenigneArcher
4ac8c5c8ec docs: improve examples (#1000) 2023-03-06 11:10:40 -05:00
Conn O'Griofa
845749d525 windows: input: activate US keyboard layout for scancode mapping (#975) 2023-02-25 14:08:42 -05:00
ReenigneArcher
9e0c72e45d github: move feature requests to moonlight board (#970) 2023-02-24 09:05:01 -05:00
dependabot[bot]
e229d80466 Bump third-party/moonlight-common-c from 07beb0f to d3cb813 (#968)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-21 09:20:19 -05:00
ReenigneArcher
0dfbcfdbc4 Merge pull request #954 from LizardByte/nightly
v0.18.4
2023-02-20 21:26:37 -05:00
ReenigneArcher
e113ac6918 ci: rename windows artifacts (#953) 2023-02-20 17:39:49 -05:00
ReenigneArcher
77deff12a6 docker: updates to docker images (#942) 2023-02-20 15:03:03 -05:00
ReenigneArcher
e5d5256aed build(deps): add pkg-config for macOS build (#952) 2023-02-18 16:14:52 -05:00
ReenigneArcher
956341930b Merge pull request #929 from LizardByte/nightly
v0.18.3
2023-02-13 21:40:32 -05:00
ReenigneArcher
032680ca9d aur: move docker build (#895) 2023-02-13 15:27:51 -05:00
ReenigneArcher
446825b73d Merge pull request #877 from LizardByte/nightly
v0.18.2
2023-02-13 14:19:40 -05:00
ReenigneArcher
0d100a57a6 changelog: v0.18.2 (#925) 2023-02-13 09:58:10 -05:00
Conn O'Griofa
55a225d21c AMF: add missing encoder tunables (#902) 2023-02-13 09:23:29 -05:00
dependabot[bot]
ae12424279 Bump vedantmgoyal2009/winget-releaser from 1 to 2 (#921)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-13 08:58:06 -05:00
Conn O'Griofa
3c223eb289 CMake: CPack: use correct Boost dependency versions (#920) 2023-02-12 21:03:35 -05:00
ReenigneArcher
9ac1e3dcd6 web: ui: add macos audio sink (#912) 2023-02-09 19:41:52 -05:00
ReenigneArcher
a21e231cae web: api: add mimes type map (#890) 2023-02-08 21:35:02 -05:00
LizardByte-bot
2c4e293e21 Bump ffmpeg (#898)
Co-authored-by: LizardByte-bot <108553330+RetroArcher-bot@users.noreply.github.com>
2023-02-07 16:26:12 -05:00
Conn O'Griofa
1b45b57d07 Linux/VAAPI: implement vaSyncBuffer stub for libva <2.9.0 (#886) 2023-02-07 16:02:42 -05:00
LizardByte-bot
905904960d ci: update global docker (#897) 2023-02-07 14:21:42 -05:00
LizardByte-bot
48559a5876 ci: update global workflows (#896) 2023-02-07 13:52:39 -05:00
Grider
d2461e1908 Fix wayland capture on nvidia for kms (#884) 2023-02-05 18:41:35 -05:00
dependabot[bot]
f8819d32e3 Bump m2r2 from 0.3.3 to 0.3.3.post2 (#861)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-31 21:04:06 -05:00
ReenigneArcher
09dff34105 Merge pull request #866 from LizardByte/nightly
v0.18.1
2023-01-31 20:44:20 -05:00
ReenigneArcher
16e2789197 cmake: v0.18.1 (#874) 2023-01-31 16:43:38 -05:00
ReenigneArcher
08ac580bc6 changelog: v0.18.1 (#873) 2023-01-31 16:02:16 -05:00
Conn O'Griofa
cdfcdf2dc7 CMake: CPack: set Intel MediaSDK as arch-specfic depend (#868) 2023-01-31 08:39:20 -05:00
Conn O'Griofa
fb7c9e22ff CMake: Linux: update dependencies & use dynamic Boost (#864) 2023-01-30 21:29:44 -05:00
198 changed files with 32807 additions and 25370 deletions

View File

@@ -7,7 +7,7 @@
BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: Consecutive
AlignConsecutiveAssignments: false
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
@@ -18,8 +18,9 @@ AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
AlignTrailingComments: false
AlwaysBreakAfterReturnType: All
AlwaysBreakTemplateDeclarations: MultiLine
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
@@ -37,32 +38,32 @@ BraceWrapping:
SplitEmptyRecord: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
BreakConstructorInitializers: AfterColon
BreakInheritanceList: AfterColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 2
IndentCaseLabels: false
IndentPPDirectives: None
IndentCaseLabels: true
IndentPPDirectives: BeforeHash
IndentWidth: 2
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
NamespaceIndentation: None
ObjCSpaceAfterProperty: false
KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Right
ReflowComments: false
SpaceAfterCStyleCast: false
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: Never
SpaceBeforeCtorInitializerColon: false
SpaceBeforeInheritanceColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesBeforeTrailingComments: 2
SpacesInAngles: Never
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false

View File

@@ -1,9 +1,9 @@
# ignore git files
.git*
# ignore hidden files
.*
# do not ignore .git, needed for versioning
!/.git
# ignore repo directories and files
docs/
scripts/

View File

@@ -57,6 +57,8 @@ body:
- macOS
- Windows
- other, n/a
validations:
required: true
- type: input
id: os-version
attributes:
@@ -73,11 +75,13 @@ body:
- 64 bit
- arm
- other, n/a
validations:
required: true
- type: input
id: version
attributes:
label: Sunshine commit or version
placeholder: eg. 0.16.0
placeholder: eg. 0.18.0
validations:
required: true
- type: dropdown
@@ -87,20 +91,28 @@ body:
description: The package you installed
options:
- Linux - AppImage
- Linux - AUR
- Linux - 20.04-deb
- Linux - 22.04-deb
- Linux - AUR (Third Party)
- Linux - deb
- Linux - Docker
- Linux - flatpak
- Linux - nixpkgs (Third Party)
- Linux - PKGBUILD
- Linux - pkg.tar.zst
- Linux - rpm
- Linux - solus (Third Party)
- macOS - dmg
- macOS - Portfile
- macOS - pkg
- Windows - Chocolatey (Third Party)
- Windows - installer
- Windows - portable
- Windows - Scoop (Third Party)
- Windows - Winget (Third Party)
- other (not listed)
- other (self built)
- other (fork of this repo)
validations:
required: true
- type: dropdown
id: graphics_type
attributes:
@@ -111,6 +123,8 @@ body:
- Intel
- Nvidia
- none (software encoding)
validations:
required: true
- type: input
id: graphics_model
attributes:
@@ -135,6 +149,24 @@ body:
placeholder: e.g. PipeWire/KVM/X11/KMS
validations:
required: false
- type: textarea
id: config
attributes:
label: Config
description: |
Please copy and paste your config (`sunshine.conf`) file.
render: Shell
validations:
required: true
- type: textarea
id: apps
attributes:
label: Apps
description: |
If this is an issue with launching a game or app, please copy and paste your `apps.json` file.
render: json
validations:
required: false
- type: textarea
id: logs
attributes:
@@ -142,9 +174,6 @@ body:
description: |
Please copy and paste any relevant log output. This will be automatically formatted into code,
so no need for backticks.
render: Shell
- type: markdown
attributes:
value: |
Make sure to close your issue when it's solved! If you found the solution yourself please comment
so that others benefit from it.
render: shell
validations:
required: true

View File

@@ -9,5 +9,5 @@ contact_links:
url: https://app.lizardbyte.dev/support
about: Official LizardByte support
- name: Feature request
url: https://app.lizardbyte.dev/feedback
about: Share your suggestions or ideas to help us improve
url: https://ideas.moonlight-stream.org
about: Share your suggestions or ideas to help Moonlight and Sunshine improve

View File

@@ -29,7 +29,7 @@ updates:
target-branch: "nightly"
open-pull-requests-limit: 10
- package-ecosystem: "pip"
- package-ecosystem: "nuget"
directory: "/"
schedule:
interval: "daily"
@@ -37,10 +37,18 @@ updates:
target-branch: "nightly"
open-pull-requests-limit: 10
- package-ecosystem: "gitsubmodule"
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "daily"
time: "10:00"
target-branch: "nightly"
open-pull-requests-limit: 10
- package-ecosystem: "gitsubmodule"
directory: "/"
schedule:
interval: "daily"
time: "10:30"
target-branch: "nightly"
open-pull-requests-limit: 10

View File

@@ -46,6 +46,7 @@ jobs:
last_version: ${{ steps.verify_changelog.outputs.latest_release_tag_name }}
release_body: ${{ steps.verify_changelog.outputs.changelog_parser_description }}
# todo - remove this job once versioning is fully automated by cmake
check_versions:
name: Check Versions
runs-on: ubuntu-latest
@@ -147,109 +148,6 @@ jobs:
outputs:
matrix: ${{ steps.flatpak_matrix.outputs.matrix }}
build_linux_aur:
name: Linux AUR
runs-on: ubuntu-latest
needs: setup_release
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Dependencies Linux AUR
run: |
sudo apt-get update -y
sudo apt-get install -y \
cmake
- name: Configure PKGBUILD files
id: prepare
run: |
# variables for manifest
aur_publish=false
aur_pkg=sunshine-dev
sub_version=""
conflicts="'sunshine'"
provides="'sunshine'"
branch=${GITHUB_HEAD_REF}
# check the branch variable
if [ -z "$branch" ]; then
echo "This is a PUSH event"
commit=${{ github.sha }}
clone_url=${{ github.event.repository.clone_url }}
if [[ ${{ github.ref == 'refs/heads/master' }} == true ]]; then
echo "This is a main release event"
aur_publish=true
aur_pkg=sunshine
conflicts=""
provides=""
elif [[ ${{ github.ref == 'refs/heads/nightly' }} == true ]]; then
echo "This is a nightly release event"
sub_version=".r${commit}"
fi
else
echo "This is a PR event"
commit=${{ github.event.pull_request.head.sha }}
clone_url=${{ github.event.pull_request.head.repo.clone_url }}
sub_version=".r${commit}"
fi
echo "Commit: ${commit}"
echo "Clone URL: ${clone_url}"
echo "aur_publish=${aur_publish}" >> $GITHUB_OUTPUT
echo "aur_pkg=${aur_pkg}" >> $GITHUB_OUTPUT
mkdir -p artifacts
mkdir -p build
cd build
cmake -DSUNSHINE_CONFIGURE_AUR=ON \
-DSUNSHINE_AUR_PKG=${aur_pkg} \
-DSUNSHINE_SUB_VERSION=${sub_version} \
-DSUNSHINE_AUR_CONFLICTS=${conflicts} \
-DSUNSHINE_AUR_PROVIDES=${provides} \
-DGITHUB_CLONE_URL=${clone_url} \
-DGITHUB_COMMIT=${commit} \
-DSUNSHINE_CONFIGURE_ONLY=ON \
..
cd ..
mv ./build/PKGBUILD ./artifacts/
- name: Validate package
uses: LizardByte/archlinux-package-action@master
with:
path: artifacts
flags: '--syncdeps --noconfirm'
namcap: true
srcinfo: true
aur: true # workaround mirror problem
- name: Upload Artifacts
if: ${{ always() }}
uses: actions/upload-artifact@v3
with:
name: sunshine-linux-aur
path: artifacts/
- name: Publish AUR package
if: ${{ steps.prepare.outputs.aur_publish == 'true' }}
uses: KSXGitHub/github-actions-deploy-aur@v2.6.0
with:
pkgname: ${{ steps.prepare.outputs.aur_pkg }}
pkgbuild: ./artifacts/PKGBUILD
assets: |
./artifacts/*
commit_username: ${{ secrets.AUR_USERNAME }}
commit_email: ${{ secrets.AUR_EMAIL }}
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
commit_message: Automatic update from GitHub ${{ github.repository }} per ${{ github.ref }}
allow_empty_commits: false
build_linux_flatpak:
name: Linux Flatpak
runs-on: ubuntu-22.04
@@ -264,7 +162,7 @@ jobs:
- name: Setup Dependencies Linux Flatpak
run: |
PLATFORM_VERSION=21.08
PLATFORM_VERSION=22.08
sudo apt-get update -y
sudo apt-get install -y \
@@ -384,6 +282,7 @@ jobs:
sudo apt-get update -y
sudo apt-get install -y \
libboost-filesystem1.71-dev \
libboost-locale1.71-dev \
libboost-log1.71-dev \
libboost-regex1.71-dev \
libboost-thread1.71-dev \
@@ -410,6 +309,7 @@ jobs:
sudo apt-get install -y \
cmake \
libboost-filesystem-dev \
libboost-locale-dev \
libboost-log-dev \
libboost-thread-dev \
libboost-program-options-dev
@@ -419,6 +319,7 @@ jobs:
build-essential \
gcc-10 \
g++-10 \
libappindicator3-dev \
libavdevice-dev \
libcap-dev \
libcurl4-openssl-dev \
@@ -463,6 +364,10 @@ jobs:
sudo rm /root/cuda.run
- name: Build Linux
env:
BRANCH: ${{ github.head_ref || github.ref_name }}
BUILD_VERSION: ${{ needs.check_changelog.outputs.next_version_bare }}
COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
mkdir -p build
mkdir -p artifacts
@@ -580,12 +485,16 @@ jobs:
- name: Setup Dependencies MacOS
run: |
# install dependencies using homebrew
brew install boost cmake curl node opus
brew install boost cmake curl node opus pkg-config
# fix openssl header not found
ln -sf /usr/local/opt/openssl/include/openssl /usr/local/include/openssl
- name: Build MacOS
env:
BRANCH: ${{ github.head_ref || github.ref_name }}
BUILD_VERSION: ${{ needs.check_changelog.outputs.next_version_bare }}
COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
npm install
@@ -691,7 +600,8 @@ jobs:
mkdir build
cd build
cmake -DGITHUB_COMMIT=${commit} \
cmake \
-DGITHUB_COMMIT=${commit} \
-DGITHUB_CLONE_URL=${clone_url} \
-DSUNSHINE_CONFIGURE_PORTFILE=ON \
-DSUNSHINE_CONFIGURE_ONLY=ON \
@@ -742,6 +652,8 @@ jobs:
echo "subportlist=${subportlist}" >> $GITHUB_OUTPUT
- name: Run port lint for all subports
env:
subportlist: ${{ steps.subportlist.outputs.subportlist }}
run: |
set -eu
fail=0
@@ -763,10 +675,10 @@ jobs:
echo "::endgroup::"
done
exit "$fail"
env:
subportlist: ${{ steps.subportlist.outputs.subportlist }}
- name: Build subports
env:
subportlist: ${{ steps.subportlist.outputs.subportlist }}
run: |
set -eu
fail=0
@@ -815,8 +727,6 @@ jobs:
echo "::endgroup::"
done
exit "$fail"
env:
subportlist: ${{ steps.subportlist.outputs.subportlist }}
- name: Package
run: |
@@ -896,6 +806,10 @@ jobs:
- name: Build Windows
shell: msys2 {0}
env:
BRANCH: ${{ github.head_ref || github.ref_name }}
BUILD_VERSION: ${{ needs.check_changelog.outputs.next_version_bare }}
COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
mkdir build
cd build
@@ -916,8 +830,8 @@ jobs:
cpack -G ZIP
# move
mv ./cpack_artifacts/Sunshine.exe ../artifacts/sunshine-windows.exe
mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-windows.zip
mv ./cpack_artifacts/Sunshine.exe ../artifacts/sunshine-windows-installer.exe
mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-windows-portable.zip
- name: Upload Artifacts
uses: actions/upload-artifact@v3
@@ -946,7 +860,7 @@ jobs:
runs-on: windows-latest # the required action can only be run on Windows
steps:
- name: Release to WinGet
uses: vedantmgoyal2009/winget-releaser@v1
uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: LizardByte.Sunshine
release-tag: ${{ needs.setup_release.outputs.release_tag }}

View File

@@ -205,9 +205,20 @@ jobs:
# get branch name
BRANCH=${GITHUB_HEAD_REF}
RELEASE=false
if [ -z "$BRANCH" ]; then
echo "This is a PUSH event"
BRANCH=${{ github.ref_name }}
COMMIT=${{ github.sha }}
CLONE_URL=${{ github.event.repository.clone_url }}
if [[ $BRANCH == "master" ]]; then
RELEASE=true
fi
else
echo "This is a PULL REQUEST event"
COMMIT=${{ github.event.pull_request.head.sha }}
CLONE_URL=${{ github.event.pull_request.head.repo.clone_url }}
fi
# determine to push image to dockerhub and ghcr or not
@@ -220,7 +231,6 @@ jobs:
# setup the tags
REPOSITORY=${{ github.repository }}
BASE_TAG=$(echo $REPOSITORY | tr '[:upper:]' '[:lower:]')
COMMIT=${{ github.sha }}
TAGS="${BASE_TAG}:${COMMIT:0:7}${{ matrix.tag }},ghcr.io/${BASE_TAG}:${COMMIT:0:7}${{ matrix.tag }}"
@@ -259,7 +269,10 @@ jobs:
elif [[ $line == "# artifacts: "* && $ARTIFACTS == "" ]]; then
# echo the line and use `sed` to remove the custom directive
ARTIFACTS=$(echo -e "$line" | sed 's/# artifacts: //')
elif [[ $PLATFORMS != "" && $ARTIFACTS != "" ]]; then
elif [[ $line == "# no-cache-filters: "* && $NO_CACHE_FILTERS == "" ]]; then
# echo the line and use `sed` to remove the custom directive
NO_CACHE_FILTERS=$(echo -e "$line" | sed 's/# no-cache-filters: //')
elif [[ $PLATFORMS != "" && $ARTIFACTS != "" && $NO_CACHE_FILTERS != "" ]]; then
# break while loop once all custom directives are found
break
fi
@@ -277,7 +290,10 @@ jobs:
echo "branch=${BRANCH}" >> $GITHUB_OUTPUT
echo "build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
echo "commit=${COMMIT}" >> $GITHUB_OUTPUT
echo "clone_url=${CLONE_URL}" >> $GITHUB_OUTPUT
echo "release=${RELEASE}" >> $GITHUB_OUTPUT
echo "artifacts=${ARTIFACTS}" >> $GITHUB_OUTPUT
echo "no_cache_filters=${NO_CACHE_FILTERS}" >> $GITHUB_OUTPUT
echo "platforms=${PLATFORMS}" >> $GITHUB_OUTPUT
echo "push=${PUSH}" >> $GITHUB_OUTPUT
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
@@ -315,7 +331,7 @@ jobs:
- name: Build artifacts
if: ${{ steps.prepare.outputs.artifacts == 'true' }}
id: build_artifacts
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
context: ./
file: ${{ matrix.dockerfile }}
@@ -328,13 +344,16 @@ jobs:
BUILD_DATE=${{ steps.prepare.outputs.build_date }}
BUILD_VERSION=${{ needs.check_changelog.outputs.next_version }}
COMMIT=${{ steps.prepare.outputs.commit }}
CLONE_URL=${{ steps.prepare.outputs.clone_url }}
RELEASE=${{ steps.prepare.outputs.release }}
tags: ${{ steps.prepare.outputs.tags }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
no-cache-filters: ${{ steps.prepare.outputs.no_cache_filters }}
- name: Build and push
id: build
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
context: ./
file: ${{ matrix.dockerfile }}
@@ -345,9 +364,12 @@ jobs:
BUILD_DATE=${{ steps.prepare.outputs.build_date }}
BUILD_VERSION=${{ needs.check_changelog.outputs.next_version }}
COMMIT=${{ steps.prepare.outputs.commit }}
CLONE_URL=${{ steps.prepare.outputs.clone_url }}
RELEASE=${{ steps.prepare.outputs.release }}
tags: ${{ steps.prepare.outputs.tags }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
no-cache-filters: ${{ steps.prepare.outputs.no_cache_filters }}
- name: Arrange Artifacts
if: ${{ steps.prepare.outputs.artifacts == 'true' }}
@@ -358,11 +380,14 @@ jobs:
# https://unix.stackexchange.com/a/52816
find ./ -type f -exec mv -t ./ -n '{}' +
# remove provenance file
rm -f ./provenance.json
- name: Upload Artifacts
if: ${{ steps.prepare.outputs.artifacts == 'true' }}
uses: actions/upload-artifact@v3
with:
name: sunshine${{ matrix.tag }}
name: Docker${{ matrix.tag }}
path: artifacts/
- name: Create/Update GitHub Release

288
.github/workflows/ci-qodana.yml vendored Normal file
View File

@@ -0,0 +1,288 @@
---
# This action 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.
name: Qodana
on:
pull_request:
branches: [master, nightly]
types: [opened, synchronize, reopened]
push:
branches: [master, nightly]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
qodana_initial_check:
name: Qodana Initial Check
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Prepare
id: prepare
run: |
# check the branch variable
if [ "${{ github.event_name }}" == "push" ]
then
echo "This is a PUSH event"
# use the branch name
destination=${{ github.ref_name }}
target=${{ github.ref_name }}
else
echo "This is a PR event"
# use the PR number
destination=${{ github.event.pull_request.number }}
target=${{ github.event.pull_request.base.ref }}
fi
echo "checkout_repo=$checkout_repo" >> $GITHUB_OUTPUT
echo "checkout_ref=$checkout_ref" >> $GITHUB_OUTPUT
echo "destination=$destination" >> $GITHUB_OUTPUT
echo "target=$target" >> $GITHUB_OUTPUT
# prepare urls
base=https://${{ github.repository_owner }}.github.io
report_url=${base}/qodana-reports/${{ github.event.repository.name }}/${destination}
echo "report_url=$report_url" >> $GITHUB_OUTPUT
# build matrix
files=$(find . -type f -iname "qodana*.yaml")
echo "files: ${files}"
# do not quote to keep this as a single line
echo files=${files} >> $GITHUB_OUTPUT
MATRIX_COMBINATIONS=""
REPORTS_MARKDOWN=""
for FILE in ${files}; do
# extract the language from file name after `qodana-` and before `.yaml`
language=$(echo $FILE | sed -r -z -e 's/(\.\/)*.*\/(qodana.yaml)/default/gm')
if [[ $language != "default" ]]; then
language=$(echo $FILE | sed -r -z -e 's/(\.\/)*.*qodana-(.*).yaml/\2/gm')
fi
MATRIX_COMBINATIONS="$MATRIX_COMBINATIONS {\"file\": \"$FILE\", \"language\": \"$language\"},"
REPORTS_MARKDOWN="$REPORTS_MARKDOWN <br> - [${language}](${report_url}/${language})"
done
# removes the last character (i.e. comma)
MATRIX_COMBINATIONS=${MATRIX_COMBINATIONS::-1}
# setup matrix for later jobs
matrix=$((
echo "{ \"include\": [$MATRIX_COMBINATIONS] }"
) | jq -c .)
echo $matrix
echo $matrix | jq .
echo "matrix=$matrix" >> $GITHUB_OUTPUT
echo "reports_markdown=$REPORTS_MARKDOWN" >> $GITHUB_OUTPUT
- name: Setup initial notification inputs
id: inputs
if: >-
startsWith(github.event_name, 'pull_request') &&
steps.prepare.outputs.files != ''
run: |
# workflow logs
workflow_url_a=https://github.com/${{ github.repository_owner }}/${{ github.event.repository.name }}
workflow_url=${workflow_url_a}/actions/runs/${{ github.run_id }}
# multiline message
message=$(cat <<- EOF
:warning: **Qodana is checking this PR** :warning:
Live results available [here](${workflow_url})
EOF
)
# escape json control characters
message=$(jq -n --arg message "$message" '$message' | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')
secondary_inputs=$(echo '{
"issue_message": "'"${message}"'",
"issue_message_id": "'"qodana"'",
"issue_number": "'"${{ github.event.number }}"'",
"issue_repo_owner": "'"${{ github.repository_owner }}"'",
"issue_repo_name": "'"${{ github.event.repository.name }}"'"
}' | jq -r .)
#escape json control characters
secondary_inputs=$(jq -n --arg secondary_inputs "$secondary_inputs" '$secondary_inputs' \
| sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')
echo $secondary_inputs
# secondary input as string, not JSON
# todo - change dispatch_ref to master instead of nightly
primary_inputs=$(echo '{
"dispatch_repository": "'"${{ github.repository_owner }}/.github"'",
"dispatch_workflow": "'"dispatch-issue-comment.yml"'",
"dispatch_ref": "'"nightly"'",
"dispatch_inputs": "'"${secondary_inputs}"'"
}' | jq -c .)
echo $primary_inputs
echo $primary_inputs | jq .
echo "primary_inputs=$primary_inputs" >> $GITHUB_OUTPUT
- name: Workflow Dispatch
if: >-
startsWith(github.event_name, 'pull_request') &&
steps.prepare.outputs.files != ''
uses: benc-uk/workflow-dispatch@v1.2.2
continue-on-error: true # this might error if the workflow is not found, but we still want to run the next job
with:
ref: ${{ github.base_ref || github.ref_name }} # base ref for PR and branch name for push
workflow: dispatcher.yml
inputs: ${{ steps.inputs.outputs.primary_inputs }}
token: ${{ github.token }}
outputs:
destination: ${{ steps.prepare.outputs.destination }}
target: ${{ steps.prepare.outputs.target }}
files: ${{ steps.prepare.outputs.files }}
reports_markdown: ${{ steps.prepare.outputs.reports_markdown }}
matrix: ${{ steps.prepare.outputs.matrix }}
qodana:
if: ${{ needs.qodana_initial_check.outputs.files != '' }}
needs: [qodana_initial_check]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.qodana_initial_check.outputs.matrix) }}
name: Qodana-Scan-${{ matrix.language }}
continue-on-error: true
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: recursive
- name: Get baseline
id: baseline
run: |
# check if destination is not an integer
if ! [[ "${{ needs.qodana_initial_check.outputs.destination }}" =~ ^[0-9]+$ ]]
then
echo "Running for a branch update"
echo "baseline_args=" >> $GITHUB_OUTPUT
else
echo "Running for a PR"
sarif_file=qodana.sarif.json
repo=${{ github.event.repository.name }}
target=${{ needs.qodana_initial_check.outputs.target }}
language=${{ matrix.language }}
baseline_file="${repo}/${target}/${language}/results/${sarif_file}"
baseline_file_url="https://lizardbyte.github.io/qodana-reports/${baseline_file}"
# don't fail if file does not exist
wget ${baseline_file_url} || true
# check if file exists
if [ -f ${sarif_file} ]
then
echo "baseline exists"
echo "baseline_args=--baseline,${sarif_file}" >> $GITHUB_OUTPUT
else
echo "baseline does not exist"
echo "baseline_args=" >> $GITHUB_OUTPUT
fi
fi
- name: Rename Qodana config file
id: rename
run: |
# rename the file
if [ "${{ matrix.file }}" != "./qodana.yaml" ]
then
mv -f ${{ matrix.file }} ./qodana.yaml
fi
- name: Qodana
id: qodana
continue-on-error: true # ensure dispatch-qodana job is run
uses: JetBrains/qodana-action@v2022.3.4
with:
additional-cache-hash: ${{ github.ref }}-${{ matrix.language }}
artifact-name: qodana-${{ matrix.language }} # yamllint disable-line rule:line-length
args: '--print-problems,${{ steps.baseline.outputs.baseline_args }}'
pr-mode: false
upload-result: true
use-caches: true
- name: Set output status
id: status
run: |
# check if qodana failed
echo "qodana_status=${{ steps.qodana.outcome }}" >> $GITHUB_OUTPUT
outputs:
qodana_status: ${{ steps.status.outputs.qodana_status }}
dispatch-qodana:
# trigger qodana-reports to download artifacts from the matrix runs
needs: [qodana_initial_check, qodana]
runs-on: ubuntu-latest
name: Dispatch Qodana
if: ${{ needs.qodana_initial_check.outputs.files != '' }}
steps:
- name: Setup qodana publish inputs
id: inputs
run: |
# get the artifacts
artifacts=${{ toJson(steps.artifacts.outputs.result) }}
artifacts=$(echo $artifacts | jq -c .)
# get the target branch
target=${{ needs.qodana_initial_check.outputs.target }}
# get the destination branch
destination=${{ needs.qodana_initial_check.outputs.destination }}
# client payload
secondary_inputs=$(echo '{
"destination": "'"${destination}"'",
"ref": "'"${{ github.ref }}"'",
"repo": "'"${{ github.repository }}"'",
"repo_name": "'"${{ github.event.repository.name }}"'",
"run_id": "'"${{ github.run_id }}"'",
"reports_markdown": "'"${{ needs.qodana_initial_check.outputs.reports_markdown }}"'",
"status": "'"${{ needs.qodana.outputs.qodana_status }}"'"
}' | jq -r .)
#escape json control characters
secondary_inputs=$(jq -n --arg secondary_inputs "$secondary_inputs" '$secondary_inputs' \
| sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')
echo $secondary_inputs
primary_inputs=$(echo '{
"dispatch_repository": "'"${{ github.repository_owner }}/qodana-reports"'",
"dispatch_workflow": "'"dispatch-qodana.yml"'",
"dispatch_ref": "'"master"'",
"dispatch_inputs": "'"$secondary_inputs"'"
}' | jq -c .)
echo $primary_inputs
echo $primary_inputs | jq .
echo "primary_inputs=$primary_inputs" >> $GITHUB_OUTPUT
- name: Workflow Dispatch
uses: benc-uk/workflow-dispatch@v1.2.2
continue-on-error: true # this might error if the workflow is not found, but we don't want to fail the workflow
with:
ref: ${{ github.base_ref || github.ref_name }} # base ref for PR and branch name for push
workflow: dispatcher.yml
inputs: ${{ steps.inputs.outputs.primary_inputs }}
token: ${{ github.token }}

69
.github/workflows/dispatcher.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
---
# This action 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 action receives a dispatch event and passes it through to another repo. This is a workaround to avoid issues
# where fork PRs do not have access to secrets.
name: Dispatcher
on:
workflow_dispatch:
inputs:
dispatch_repository:
description: 'Repository to dispatch to'
required: true
dispatch_workflow:
description: 'Workflow to dispatch to'
required: true
dispatch_ref:
description: 'Ref/branch to dispatch to'
required: true
dispatch_inputs:
description: 'Inputs to send'
required: true
jobs:
dispatcher:
name: Repository Dispatch
runs-on: ubuntu-latest
steps:
- name: Unescape JSON control characters
id: inputs
run: |
# get the inputs
dispatch_inputs=${{ github.event.inputs.dispatch_inputs }}
echo "$dispatch_inputs"
# temporarily replace newlines with a placeholder
dispatch_inputs=$(echo ${dispatch_inputs} | sed 's/\\\\n/_!new_line!_/g')
# remove newline characters
dispatch_inputs=$(echo ${dispatch_inputs} | sed 's/\\n//g')
# replace placeholder with newline
dispatch_inputs=$(echo ${dispatch_inputs} | sed 's/_!new_line!_/\\n/g')
# replace escaped quotes with unescaped quotes
dispatch_inputs=$(echo ${dispatch_inputs} | sed 's/\\"//g')
# debug echo
echo "$dispatch_inputs"
# parse as JSON
dispatch_inputs=$(echo "$dispatch_inputs" | jq -c .)
# debug echo
echo "$dispatch_inputs"
echo "dispatch_inputs=$dispatch_inputs" >> $GITHUB_OUTPUT
- name: Workflow Dispatch
uses: benc-uk/workflow-dispatch@v1.2.2
with:
repo: ${{ github.event.inputs.dispatch_repository }}
ref: ${{ github.event.inputs.dispatch_ref || 'master' }} # default to master if not specified
workflow: ${{ github.event.inputs.dispatch_workflow }}
inputs: ${{ steps.inputs.outputs.dispatch_inputs }}
token: ${{ secrets.GH_BOT_TOKEN || github.token }} # fallback to default token if not specified

4
.gitmodules vendored
View File

@@ -46,3 +46,7 @@
path = third-party/nanors
url = https://github.com/sleepybishop/nanors.git
branch = master
[submodule "third-party/tray"]
path = third-party/tray
url = https://github.com/dmikushin/tray
branch = master

View File

@@ -1,17 +1,96 @@
# Changelog
## [0.19.0] - 2023-03-29
**Breaking**
- (Linux/Flatpak) Moved Flatpak to org.freedesktop.Platform 22.08 and Cuda 12.0.0
This will drop support for Nvidia GPUs with compute capability 3.5
**Added**
- (Input) Added option to suppress input from gamepads, keyboards, or mice
- (Input/Linux) Added unicode support for remote pasting (may not work on all DEs)
- (Input/Linux) Added XTest input fallback
- (UI) Added version notifications to web UI
- (Linux/Windows) Add system tray icon
- (Windows) Added ability to safely elevate commands that fail due to insufficient permissions when running as a service
- (Config) Added global prep commands, and ability to exclude an app from using global prep commands
- (Installer/Windows) Automatically install ViGEmBus if selected
**Changed**
- (Logging) Changed client verified messages to debug to prevent spamming the log
- (Config) Only save non default config values
- (Service/Linux) Use xdg-desktop-autostart for systemd service
- (Linux) Added config option to force capture method
- (Windows) Execute prep command in context of current user
- (Linux) Allow disconnected X11 outputs
**Fixed**
- (Input/Windows) Fix issue where internation keys were not translated correct, and modifier keys appeared stuck
- (Linux) Fixed startup when /dev/dri didn't exist
- (UI) Changes software encoding settings to select menu instead of text input
- (Initialization) Do not terminate upon failure, allowing access to the web UI
**Dependencies**
- Bump third-party/moonlight-common-c from 07beb0f to c9426a6
- Bump babel from 2.11.0 to 2.12.1
- Bump @fortawesome/fontawesome-free from 6.2.1 to 6.4.0
- Bump third-party/ViGEmClient from 9e842ba to 726404e
- Bump ffmpeg
- Bump third-party/miniupnp from 014c9df to e439318
- Bump furo from 2022.12.7 to 2023.3.27
- Bump third-party/nanors from 395e5ad to e9e242e
**Misc**
- (GitHub) Shared feature request board with Moonlight
- (Docs) Improved application examples
- (Docs) Added WIP documentation for source code using Doxygen and Breathe
- (Build) Fix linux clang build errors
- (Build/Archlinux) Skip irrelevant submodules
- (Build/Archlinux) Disable download timeout
- (Build/macOS) Support compiling for earlier releases of macOS
- (Docs) Add favicon
- (Docs) Add missing config default values
- (Build) Fix compiler warnings due to depreciated elements in C++17
- (Build) Fix libcurl link errors
- (Clang) Adjusted formatting rules
## [0.18.4] - 2023-02-20
**Fixed**
- (Linux/AUR) Drop support of AUR package
- (Docker) General enhancements to docker images
## [0.18.3] - 2023-02-13
**Added**
- (Linux) Added PKGBUILD for Archlinux based distros to releases
- (Linux) Added precompiled package for Archlinux based distros to releases
- (Docker) Added archlinux docker image (x86_64 only)
## [0.18.2] - 2023-02-13
**Fixed**
- (Video/KMV/Linux) Fixed wayland capture on Nvidia for KMS
- (Video/Linux) Implement vaSyncBuffer stuf for libva <2.9.0
- (UI) Fix issue where mime type was not being set for node_modules when using a reverse proxy
- (UI/macOS) Added missing audio sink config options
- (Linux) Specify correct Boost dependency versions
- (Video/AMF) Add missing encoder tunables
## [0.18.1] - 2023-01-31
**Fixed**
- (Linux) Fixed missing dependencies for deb and rpm packages
- (Linux) Use dynamic boost
## [0.18.0] - 2023-01-29
Attention, this release contains critical security fixes. Please update as soon as possible. Additionally, we are
encouraging users to change your Sunshine password, especially if you expose the web UI (i.e. port 47790 by default)
to the internet, or have ever uploaded your logs with verbose output to a public resource.
### Added
**Added**
- (Windows) Add support for Intel QuickSync
- (Linux) Added aarch64 deb and rpm packages
- (Windows) Add support for hybrid graphics systems, such as laptops with both integrated and discrete GPUs
- (Linux) Add support for streaming from Steam Deck Gaming Mode
- (Windows) Add HDR support, see https://docs.lizardbyte.dev/projects/sunshine/en/latest/about/usage.html#hdr-support
### Fixed
**Fixed**
- (Network) Refactor code for UPnP port forwarding
- (Video) Enforce 10 FPS encoding frame rate minimum to improve static image quality
- (Linux) deb and rpm packages are now specific to destination distro and version
@@ -35,18 +114,20 @@ location which is problematic when running as a service or on a multi-user syste
as a service, games and applications were launched as SYSTEM. This could lead to issues with save files and other game
settings. In v0.17.0, games now run under your user account without elevated privileges.
### Breaking
**Breaking**
- (Apps) Removed automatic desktop entry (Re-add by adding an empty application named "Desktop" with no commands, "desktop.png" can be added as the image.)
- (Windows) Improved user upgrade experience (Suggest to manually uninstall existing Sunshine version before this upgrade. Do NOT select to remove everything, if prompted. Make a backup of config files before uninstall.)
- (Windows) Move config files to specific directory (files will be migrated automatically if using Windows installer)
- (Dependencies) Fix npm path (breaking change for package maintainers)
### Added
**Added**
- (macOS) Added initial support for arm64 on macOS through Macports portfile
- (Input) Added support for foreign keyboard input
- (Misc) Logs inside the WebUI and log to file
- (UI/Windows) Added an Apply button to configuration page when running as a service
- (Input/Windows) Enable Mouse Keys while streaming for systems with no physical mouse
### Fixed
**Fixed**
- (Video) Improved capture performance
- (Audio) Improved audio bitrate and quality handling
- (Apps/Windows) Fixed PATH environment variable handling
@@ -70,7 +151,8 @@ settings. In v0.17.0, games now run under your user account without elevated pri
- (Service/Windows) Self terminate/restart service if process hangs for 10 seconds
- (Input/Windows) Fix Windows masked cursor blending with GPU encoders
- (Video) Color conversion fixes and BT.2020 support
### Dependencies
**Dependencies**
- Bump ffmpeg from 4.4 to 5.1
- ffmpeg_patches: add amfenc delay/buffering fix
- CBS moved to ffmpeg submodules
@@ -83,15 +165,17 @@ settings. In v0.17.0, games now run under your user account without elevated pri
- Bump @fortawesome/fontawesome-free from 6.2.0 to 6.2.1
## [0.16.0] - 2022-12-13
### Added
**Added**
- Add cover finder
- (Docker) Add arm64 docker image
- (Flatpak) Add installation helper scripts
- (Windows) Add support for Unicode input messages
### Fixed
**Fixed**
- (Linux) Reintroduce Ubuntu 20.04 and 22.04 specific deb packages
- (Linux) Fixed udev and systemd file locations
### Dependencies
**Dependencies**
- Bump babel from 2.10.3 to 2.11.0
- Bump sphinx-copybutton from 0.5.0 to 0.5.1
- Bump KSXGitHub/github-actions-deploy-aur from 2.5.0 to 2.6.0
@@ -99,23 +183,26 @@ settings. In v0.17.0, games now run under your user account without elevated pri
- Update moonlight-common-c
- Use pre-built ffmpeg from LizardByte/build-deps for all sunshine builds (breaking change for third-party package maintainers)
- Bump furo from 2022.9.29 to 2022.12.7
### Misc
**Misc**
- Misc org level workflow updates
- Fix misc typos in docs
- Fix winget release
## [0.15.0] - 2022-10-30
### Added
**Added**
- (Windows) Add firewall rules scripts
- (Windows) Automatically add and remove firewall rules at install/uninstall
- (Windows) Automatically add and remove service at install/uninstall
- (Docker) Official image added
- (Linux) Add aarch64 flatpak package
### Changed
**Changed**
- (Windows/Linux/MacOS) - Move default config and apps file to assets directory
- (MacOS) Bump boost to 1.80 for macport builds
- (Linux) Remove backup and restore of config files
### Fixed
**Fixed**
- (Linux) - Create sunshine config directory if it doesn't exist
- (Linux) Remove portable home and config directories for AppImage
- (Windows) Include service install and uninstall scripts again
@@ -126,27 +213,31 @@ settings. In v0.17.0, games now run under your user account without elevated pri
- (Linux) Fix CUDA RGBA to NV12 conversion
## [0.14.1] - 2022-08-09
### Added
**Added**
- (Linux) Flatpak package added
- (Linux) AUR package automated updates
- (Windows) Winget package automated updates
### Changed
**Changed**
- (General) Moved repo to @LizardByte GitHub org
- (WebUI) Fixed button spacing on home page
- (WebUI) Added Discord WidgetBot Crate
### Fixed
**Fixed**
- (Linux/Mac) Default config and app files now copied to user home directory
- (Windows) Default config and app files now copied to working directory
## [0.14.0] - 2022-06-15
### Added
**Added**
- (Documentation) Added Sphinx documentation available at https://sunshinestream.readthedocs.io/en/latest/
- (Development) Initial support for Localization
- (Linux) Add rpm package as release asset
- (macOS) Add Portfile as release asset
- (Windows) Add DwmFlush() call to improve capture
- (Windows) Add Windows installer
### Fixed
**Fixed**
- (AMD) Fixed hwdevice being destroyed before context
- (Linux) Added missing dependencies to AppImage
- (Linux) Fixed rumble events causing game to freeze
@@ -157,41 +248,44 @@ settings. In v0.17.0, games now run under your user account without elevated pri
- (Stream/Video) AVPacket fix
## [0.13.0] - 2022-02-27
### Added
**Added**
- (macOS) Initial support for macOS (#40)
## [0.12.0] - 2022-02-13
### Added
**Added**
- New command line argument `--version`
- Custom png poster support
### Changed
**Changed**
- Correct software bitrate calculation
- Increase vbv-bufsize to 1/10 of requested bitrate
- Improvements to Web UI
## [0.11.1] - 2021-10-04
### Changed
**Changed**
- (Linux) Fix search path for config file and assets
## [0.11.0] - 2021-10-04
### Added
**Added**
- (Linux) Added support for wlroots based compositors on Wayland.
- (Windows) Added an icon for the executable
### Changed
**Changed**
- Fixed a bug causing segfault when connecting multiple controllers.
- (Linux) Improved NVENC, it now offloads converting images from RGB to NV12
- (Linux) Fixed a bug causes stuttering
## [0.10.1] - 2021-08-21
### Changed
**Changed**
- (Linux) Re-enabled KMS
## [0.10.0] - 2021-08-20
### Added
**Added**
- Added support for Rumble with gamepads.
- Added support for keyboard shortcuts <--- See the README for details.
- (Windows) A very basic script has been added in Sunshine-Windows\tools <-- This will start Sunshine at boot with the highest privileges which is needed to display the login prompt.
### Changed
**Changed**
- Some cosmetic changes to the WebUI.
- The first time the WebUI is opened, it will request the creation of a username/password pair from the user.
- Fixed audio crackling introduced in version 0.8.0
@@ -199,54 +293,58 @@ settings. In v0.17.0, games now run under your user account without elevated pri
- (Windows) Installing from debian package shouldn't overwrite your configuration files anymore. <-- It's recommended that you back up `/etc/sunshine/` before testing this.
## [0.9.0] - 2021-07-11
### Added
**Added**
- Added audio encryption
- (Linux) Added basic NVENC support on Linux
- (Windows) The Windows version can now capture the lock screen and the UAC prompt as long as it's run through `PsExec.exe` https://docs.microsoft.com/en-us/sysinternals/downloads/psexec
### Changed
**Changed**
- Sunshine will now accept expired or not-yet-valid certificates, as long as they are signed properly.
- Fixed compatibility with iOS version of Moonlight
- Drastically reduced chance of being forced to skip error correction due to video frame size
- (Linux) sunshine.service will be installed automatically.
## [0.8.0] - 2021-06-30
### Added
**Added**
- Added mDNS support: Moonlight will automatically find Sunshine.
- Added UPnP support. It's off by default.
## [0.7.7] - 2021-06-24
### Added
**Added**
- (Linux) Added installation package for Debian
### Changed
**Changed**
- Fixed incorrect scaling for absolute mouse coordinates when using multiple monitors.
- Fixed incorrect colors when scaling for software encoder
## [0.7.1] - 2021-06-18
### Changed
**Changed**
- (Linux) Fixed an issue where it was impossible to start sunshine on ubuntu 20.04
## [0.7.0] - 2021-06-16
### Added
**Added**
- Added a Web Manager. Accessible through: https://localhost:47990 or https://<ip of your pc>:47990
- (Linux) Added hardware encoding support for AMD on Linux
### Changed
**Changed**
- (Linux) Moved certificates and saved pairings generated during runtime to .config/sunshine on Linux
## [0.6.0] - 2021-05-26
### Added
**Added**
- Added support for surround audio
### Changed
**Changed**
- Maintain aspect ratio when scaling video
- Fix issue where Sunshine is forced to drop frames when they are too large
## [0.5.0] - 2021-05-13
### Added
**Added**
- Added support for absolute mouse coordinates
- (Linux) Added support for streaming specific monitor on Linux
- (Windows) Added support for AMF on Windows
## [0.4.0] - 2020-05-03
### Changed
**Changed**
- prep-cmd is now optional in apps.json
- Fixed bug causing video artifacts
- Fixed bug preventing Moonlight from closing app on exit
@@ -255,25 +353,25 @@ settings. In v0.17.0, games now run under your user account without elevated pri
- Fixed bug causing crash when monitor has resolution 1366x768
## [0.3.1] - 2020-04-24
### Changed
**Changed**
- Fix a memory leak.
## [0.3.0] - 2020-04-23
### Changed
**Changed**
- Hardware acceleration on NVidia GPU's for Video encoding on Windows
## [0.2.0] - 2020-03-21
### Changed
**Changed**
- Multicasting is now supported: You can set the maximum simultaneous connections with the configurable option: channels
- Configuration variables can be overwritten on the command line: "name=value" --> it can be useful to set min_log_level=debug without modifying the configuration file
- Switches to make testing the pairing mechanism more convenient has been added, see "sunshine --help" for details
## [0.1.1] - 2020-01-30
### Added
**Added**
- (Linux) Added deb package and service for Linux
## [0.1.0] - 2020-01-27
### Added
**Added**
- The first official release for Sunshine!
[0.1.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.1.0
@@ -301,3 +399,8 @@ settings. In v0.17.0, games now run under your user account without elevated pri
[0.16.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.16.0
[0.17.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.17.0
[0.18.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.18.0
[0.18.1]: https://github.com/LizardByte/Sunshine/releases/tag/v0.18.1
[0.18.2]: https://github.com/LizardByte/Sunshine/releases/tag/v0.18.2
[0.18.3]: https://github.com/LizardByte/Sunshine/releases/tag/v0.18.3
[0.18.4]: https://github.com/LizardByte/Sunshine/releases/tag/v0.18.4
[0.19.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.19.0

View File

@@ -1,7 +1,8 @@
cmake_minimum_required(VERSION 3.18)
# `CMAKE_CUDA_ARCHITECTURES` requires 3.18
project(Sunshine VERSION 0.18.0
# todo - set version to 0.0.0 once confident in automated versioning
project(Sunshine VERSION 0.19.0
DESCRIPTION "Sunshine is a self-hosted game stream host for Moonlight."
HOMEPAGE_URL "https://app.lizardbyte.dev")
@@ -10,6 +11,65 @@ and Nvidia GPUs for hardware encoding. Software encoding is also available. You
Moonlight client on a variety of devices. A web UI is provided to allow configuration, and client pairing, from \
your favorite web browser. Pair from the local server or any mobile device.")
# Check if env vars are defined before attempting to access them, variables will be defined even if blank
if((DEFINED ENV{BRANCH}) AND (DEFINED ENV{BUILD_VERSION}) AND (DEFINED ENV{COMMIT})) # cmake-lint: disable=W0106
if(($ENV{BRANCH} STREQUAL "master") AND (NOT $ENV{BUILD_VERSION} STREQUAL ""))
# If BRANCH is "master" and BUILD_VERSION is not empty, then we are building a master branch
MESSAGE("Got from CI master branch and version $ENV{BUILD_VERSION}")
set(PROJECT_VERSION $ENV{BUILD_VERSION})
elseif((DEFINED ENV{BRANCH}) AND (DEFINED ENV{COMMIT}))
# If BRANCH is set but not BUILD_VERSION we are building nightly, we gather only the commit hash
MESSAGE("Got from CI $ENV{BRANCH} branch and commit $ENV{COMMIT}")
set(PROJECT_VERSION ${PROJECT_VERSION}.$ENV{COMMIT})
endif()
# Generate Sunshine Version based of the git tag
# https://github.com/nocnokneo/cmake-git-versioning-example/blob/master/LICENSE
else()
find_package(Git)
if(GIT_EXECUTABLE)
MESSAGE("${CMAKE_CURRENT_SOURCE_DIR}")
get_filename_component(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
#Get current Branch
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD
#WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE GIT_DESCRIBE_BRANCH
RESULT_VARIABLE GIT_DESCRIBE_ERROR_CODE
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Gather current commit
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
#WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE GIT_DESCRIBE_VERSION
RESULT_VARIABLE GIT_DESCRIBE_ERROR_CODE
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Check if Dirty
execute_process(
COMMAND ${GIT_EXECUTABLE} diff --quiet --exit-code
#WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE GIT_IS_DIRTY
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT GIT_DESCRIBE_ERROR_CODE)
MESSAGE("Sunshine Branch: ${GIT_DESCRIBE_BRANCH}")
if(NOT GIT_DESCRIBE_BRANCH STREQUAL "master")
set(PROJECT_VERSION ${PROJECT_VERSION}.${GIT_DESCRIBE_VERSION})
MESSAGE("Sunshine Version: ${GIT_DESCRIBE_VERSION}")
endif()
if(GIT_IS_DIRTY)
set(PROJECT_VERSION ${PROJECT_VERSION}.dirty)
MESSAGE("Git tree is dirty!")
endif()
else()
MESSAGE(ERROR ": Got git error while fetching tags: ${GIT_DESCRIBE_ERROR_CODE}")
endif()
else()
MESSAGE(WARNING ": Git not found, cannot find git version")
endif()
endif()
option(SUNSHINE_CONFIGURE_APPIMAGE "Configuration specific for AppImage." OFF)
option(SUNSHINE_CONFIGURE_AUR "Configure files required for AUR." OFF)
option(SUNSHINE_CONFIGURE_FLATPAK_MAN "Configure manifest file required for Flatpak build." OFF)
@@ -72,23 +132,27 @@ find_package(OpenSSL REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(CURL REQUIRED libcurl)
if(NOT APPLE)
set(Boost_USE_STATIC_LIBS ON) # cmake-lint: disable=C0103
endif()
if(WIN32)
set(Boost_USE_STATIC_LIBS ON) # cmake-lint: disable=C0103
# workaround to prevent link errors against icudata, icui18n
set(Boost_NO_BOOST_CMAKE ON) # cmake-lint: disable=C0103
set(Boost_NO_BOOST_CMAKE ON) # cmake-lint: disable=C0103
endif()
find_package(Boost COMPONENTS log filesystem program_options REQUIRED)
find_package(Boost COMPONENTS locale log filesystem program_options REQUIRED)
list(APPEND SUNSHINE_COMPILE_OPTIONS -Wall -Wno-missing-braces -Wno-maybe-uninitialized -Wno-sign-compare)
# enable system tray, we will disable this later if we cannot find the required package config on linux
set(SUNSHINE_TRAY 1)
if(WIN32)
enable_language(RC)
set(CMAKE_RC_COMPILER windres)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CURL_STATIC_LDFLAGS} ${CURL_STATIC_CFLAGS}")
add_definitions(-DCURL_STATICLIB)
include_directories(${CURL_STATIC_INCLUDE_DIRS})
link_directories(${CURL_STATIC_LIBRARY_DIRS})
add_compile_definitions(SUNSHINE_PLATFORM="windows")
add_subdirectory(tools) # This is temporary, only tools for Windows are needed, for now
@@ -110,6 +174,7 @@ if(WIN32)
src/platform/windows/display_vram.cpp
src/platform/windows/display_ram.cpp
src/platform/windows/audio.cpp
third-party/tray/tray_windows.c
third-party/ViGEmClient/src/ViGEmClient.cpp
third-party/ViGEmClient/include/ViGEm/Client.h
third-party/ViGEmClient/include/ViGEm/Common.h
@@ -149,6 +214,7 @@ elseif(APPLE)
FIND_LIBRARY(APP_SERVICES_LIBRARY ApplicationServices )
FIND_LIBRARY(AV_FOUNDATION_LIBRARY AVFoundation )
FIND_LIBRARY(COCOA Cocoa REQUIRED ) # tray icon
FIND_LIBRARY(CORE_MEDIA_LIBRARY CoreMedia )
FIND_LIBRARY(CORE_VIDEO_LIBRARY CoreVideo )
FIND_LIBRARY(VIDEO_TOOLBOX_LIBRARY VideoToolbox )
@@ -156,6 +222,7 @@ elseif(APPLE)
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
${APP_SERVICES_LIBRARY}
${AV_FOUNDATION_LIBRARY}
${COCOA}
${CORE_MEDIA_LIBRARY}
${CORE_VIDEO_LIBRARY}
${VIDEO_TOOLBOX_LIBRARY}
@@ -175,13 +242,14 @@ elseif(APPLE)
src/platform/macos/display.mm
src/platform/macos/input.cpp
src/platform/macos/microphone.mm
src/platform/macos/misc.cpp
src/platform/macos/misc.mm
src/platform/macos/misc.h
src/platform/macos/nv12_zero_device.cpp
src/platform/macos/nv12_zero_device.h
src/platform/macos/publish.cpp
third-party/TPCircularBuffer/TPCircularBuffer.c
third-party/TPCircularBuffer/TPCircularBuffer.h
third-party/tray/tray_darwin.m
${APPLE_PLIST_FILE})
else()
add_compile_definitions(SUNSHINE_PLATFORM="linux")
@@ -190,6 +258,7 @@ else()
option(SUNSHINE_ENABLE_X11 "Enable X11 grab if available" ON)
option(SUNSHINE_ENABLE_WAYLAND "Enable building wayland specific code" ON)
option(SUNSHINE_ENABLE_CUDA "Enable cuda specific code" ON)
option(SUNSHINE_ENABLE_TRAY "Enable tray icon" ON)
if(${SUNSHINE_ENABLE_X11})
find_package(X11)
@@ -355,10 +424,27 @@ ${CMAKE_BINARY_DIR}/generated-src/${filename}.h")
src/platform/linux/wlgrab.cpp
src/platform/linux/wayland.cpp)
endif()
if(NOT ${X11_FOUND} AND NOT (${LIBDRM_FOUND} AND ${LIBCAP_FOUND}) AND NOT ${WAYLAND_FOUND} AND NOT ${})
if(NOT ${X11_FOUND} AND NOT (${LIBDRM_FOUND} AND ${LIBCAP_FOUND}) AND NOT ${WAYLAND_FOUND})
message(FATAL_ERROR "Couldn't find either x11, wayland, cuda or (libdrm and libcap)")
endif()
# tray icon
if(${SUNSHINE_ENABLE_TRAY})
pkg_check_modules(APPINDICATOR appindicator3-0.1)
if(NOT APPINDICATOR_FOUND)
message(WARNING "Couldn't find appindicator, disabling tray icon")
set(SUNSHINE_TRAY 0)
else()
include_directories(${APPINDICATOR_INCLUDE_DIRS})
link_directories(${APPINDICATOR_LIBRARY_DIRS})
list(APPEND PLATFORM_TARGET_FILES third-party/tray/tray_linux.c)
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${APPINDICATOR_LIBRARIES})
endif()
else()
set(SUNSHINE_TRAY 0)
endif()
list(APPEND PLATFORM_TARGET_FILES
src/platform/linux/publish.cpp
src/platform/linux/vaapi.h
@@ -380,6 +466,7 @@ ${CMAKE_BINARY_DIR}/generated-src/${filename}.h")
third-party/glad/include/glad/egl.h)
list(APPEND PLATFORM_LIBRARIES
Boost::dynamic_linking
dl
evdev
numa
@@ -397,7 +484,7 @@ ${CMAKE_BINARY_DIR}/generated-src/${filename}.h")
configure_file(sunshine.service.in sunshine.service @ONLY)
endif()
configure_file(version.h.in version.h @ONLY)
configure_file(src/version.h.in version.h @ONLY)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
set(SUNSHINE_TARGET_FILES
@@ -407,6 +494,7 @@ set(SUNSHINE_TARGET_FILES
third-party/moonlight-common-c/src/Rtsp.h
third-party/moonlight-common-c/src/RtspParser.c
third-party/moonlight-common-c/src/Video.h
third-party/tray/tray.h
src/upnp.cpp
src/upnp.h
src/cbs.cpp
@@ -440,6 +528,8 @@ set(SUNSHINE_TARGET_FILES
src/network.cpp
src/network.h
src/move_by_copy.h
src/system_tray.cpp
src/system_tray.h
src/task_pool.h
src/thread_pool.h
src/thread_safe.h
@@ -452,6 +542,8 @@ set_source_files_properties(src/upnp.cpp PROPERTIES COMPILE_FLAGS -Wno-pedantic)
set_source_files_properties(third-party/nanors/rs.c
PROPERTIES COMPILE_FLAGS "-include deps/obl/autoshim.h -ftree-vectorize")
list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_TRAY=${SUNSHINE_TRAY})
# Pre-compiled binaries
if(WIN32)
set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/ffmpeg-windows-x86_64")
@@ -469,6 +561,8 @@ else()
else()
set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/ffmpeg-linux-x86_64")
list(APPEND FFMPEG_PLATFORM_LIBRARIES mfx)
set(CPACK_DEB_PLATFORM_PACKAGE_DEPENDS "libmfx1,")
set(CPACK_RPM_PLATFORM_PACKAGE_REQUIRES "intel-mediasdk >= 22.3.0,")
endif()
endif()
set(FFMPEG_INCLUDE_DIRS
@@ -559,6 +653,8 @@ endif()
if(APPLE)
target_link_options(sunshine PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,${APPLE_PLIST_FILE})
# Tell linker to dynamically load these symbols at runtime, in case they're unavailable:
target_link_options(sunshine PRIVATE -Wl,-U,_CGPreflightScreenCaptureAccess -Wl,-U,_CGRequestScreenCaptureAccess)
endif()
foreach(flag IN LISTS SUNSHINE_COMPILE_OPTIONS)
@@ -597,6 +693,7 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
install(TARGETS dxgi-info RUNTIME DESTINATION "tools" COMPONENT dxgi)
install(TARGETS audio-info RUNTIME DESTINATION "tools" COMPONENT audio)
install(TARGETS sunshinesvc RUNTIME DESTINATION "tools" COMPONENT sunshinesvc)
install(TARGETS elevator RUNTIME DESTINATION "tools" COMPONENT elevator)
# Mandatory tools
install(TARGETS ddprobe RUNTIME DESTINATION "tools" COMPONENT application)
@@ -608,6 +705,9 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/service/"
DESTINATION "scripts"
COMPONENT service)
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/vigembus/"
DESTINATION "scripts"
COMPONENT vigembus)
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/migration/"
DESTINATION "scripts"
COMPONENT assets)
@@ -640,11 +740,7 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
nsExec::ExecToLog 'icacls \\\"$INSTDIR\\\\config\\\" /grant:r Users:\\\(OI\\\)\\\(CI\\\)\\\(F\\\)'
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\add-firewall-rule.bat\\\"'
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\install-service.bat\\\"'
MessageBox MB_YESNO|MB_ICONQUESTION \
'Do you want to install ViGEmBus? This is REQUIRED for gamepad support while streaming.' \
/SD IDNO IDNO NoController
ExecShell 'open' 'https://github.com/ViGEm/ViGEmBus/releases/latest'; \
skipped if no
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\install-vigembus.bat\\\"'
NoController:
")
@@ -654,6 +750,11 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
"${CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS}
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\delete-firewall-rule.bat\\\"'
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\uninstall-service.bat\\\"'
MessageBox MB_YESNO|MB_ICONQUESTION \
'Do you want to remove ViGEmBus)?' \
/SD IDNO IDNO NoVigem
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\uninstall-vigembus.bat\\\"'; skipped if no
NoVigem:
MessageBox MB_YESNO|MB_ICONQUESTION \
'Do you want to remove $INSTDIR (this includes the configuration, cover images, and settings)?' \
/SD IDNO IDNO NoDelete
@@ -708,6 +809,12 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
set(CPACK_COMPONENT_AUDIO_DESCRIPTION "CLI tool providing information about sound devices.")
set(CPACK_COMPONENT_AUDIO_GROUP "tools")
# elevation tool
set(CPACK_COMPONENT_ELEVATOR_DISPLAY_NAME "elevator")
set(CPACK_COMPONENT_ELEVATOR_DESCRIPTION "CLI tool that assists with elevating \
commands when permissions have been denied.")
set(CPACK_COMPONENT_ELEVATOR_GROUP "tools")
# display tool
set(CPACK_COMPONENT_DXGI_DISPLAY_NAME "dxgi-info")
set(CPACK_COMPONENT_DXGI_DESCRIPTION "CLI tool providing information about graphics cards and displays.")
@@ -728,6 +835,11 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
set(CPACK_COMPONENT_FIREWALL_DISPLAY_NAME "firewall-scripts")
set(CPACK_COMPONENT_FIREWALL_DESCRIPTION "Scripts to enable or disable firewall rules.")
set(CPACK_COMPONENT_FIREWALL_GROUP "scripts")
# vigembus scripts
set(CPACK_COMPONENT_VIGEMBUS_DISPLAY_NAME "vigembus-scripts")
set(CPACK_COMPONENT_VIGEMBUS_DESCRIPTION "Scripts to install and uninstall ViGEmBus for virtual gamepad support.")
set(CPACK_COMPONENT_VIGEMBUS_GROUP "scripts")
endif()
if(APPLE)
# TODO: bundle doesn't produce a valid .app use cpack -G DragNDrop
@@ -787,36 +899,58 @@ elseif(UNIX)
# Dependencies
set(CPACK_DEB_COMPONENT_INSTALL ON)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "\
libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, \
libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, \
libboost-program-options1.67.0 | libboost-program-options1.71.0 | libboost-program-options1.74.0, \
libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, \
${CPACK_DEB_PLATFORM_PACKAGE_DEPENDS} \
libboost-filesystem${Boost_VERSION}, \
libboost-locale${Boost_VERSION}, \
libboost-log${Boost_VERSION}, \
libboost-program-options${Boost_VERSION}, \
libboost-thread${Boost_VERSION}, \
libcap2, \
libcurl4, \
libdrm2, \
libevdev2, \
libnuma1, \
libopus0, \
libpulse0, \
libxcb-shm0, \
libxcb-xfixes0, \
libxtst6, \
openssl")
libva2, \
libva-drm2, \
libvdpau1, \
libwayland-client0, \
libx11-6, \
openssl | libssl3")
set(CPACK_RPM_PACKAGE_REQUIRES "\
boost-filesystem >= 1.67.0, \
boost-log >= 1.67.0, \
boost-program-options >= 1.67.0, \
boost-thread >= 1.67.0, \
${CPACK_RPM_PLATFORM_PACKAGE_REQUIRES} \
boost-filesystem >= ${Boost_VERSION}, \
boost-locale >= ${Boost_VERSION}, \
boost-log >= ${Boost_VERSION}, \
boost-program-options >= ${Boost_VERSION}, \
boost-thread >= ${Boost_VERSION}, \
libcap >= 2.22, \
libcurl >= 7.0, \
libdrm >= 2.4.97, \
libevdev >= 1.5.6, \
libopusenc >= 0.2.1, \
libxcb >= 1.13, \
libXtst >= 1.2.3, \
openssl >= 1.1, \
libva >= 2.14.0, \
libvdpau >= 1.5, \
libwayland-client >= 1.20.0, \
libX11 >= 1.7.3.1, \
numactl-libs >= 2.0.14, \
openssl >= 3.0.2, \
pulseaudio-libs >= 10.0")
# This should automatically figure out dependencies, doesn't work with the current config
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF)
if(${SUNSHINE_TRAY} STREQUAL 1)
install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg"
DESTINATION "/usr/share/icons")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "\
${CPACK_DEBIAN_PACKAGE_DEPENDS}, \
libappindicator3-1")
set(CPACK_RPM_PACKAGE_REQUIRES "\
${CPACK_RPM_PACKAGE_REQUIRES}, \
libappindicator-gtk3 >= 12.10.0")
endif()
endif()
endif()

View File

@@ -23,8 +23,9 @@ ENTRYPOINT steam && sunshine
- commit hash
### SUNSHINE_OS
Sunshine images are available, based on the following base images.
Sunshine images are available with the following tag suffixes, based on their respective base images.
- `archlinux`
- `debian-bullseye`
- `fedora-36`
- `fedora-37`
@@ -126,9 +127,13 @@ If you want to change the PUID or PGID after the image has been built, it will r
Specifying `lizardbyte/sunshine:latest-<SUNSHINE_OS>` or `ghcr.io/lizardbyte/sunshine:latest-<SUNSHINE_OS>` should
retrieve the correct image for your architecture.
The architectures supported by these images are:
The architectures supported by these images are shown in the table below.
| Architecture | Available |
|:---------------:|:---------:|
| amd64 / x86_64 | |
| arm64 / aarch64 | |
| tag suffix | amd64/x86_64 | arm64/aarch64 |
|-----------------|--------------|---------------|
| archlinux | ✅ | |
| debian-bullseye | ✅ | |
| fedora-36 | ✅ | ✅ |
| fedora-37 | ✅ | ✅ |
| ubuntu-20.04 | ✅ | ✅ |
| ubuntu-22.04 | ✅ | ✅ |

View File

@@ -123,10 +123,6 @@ Stats
:alt: GitHub stars
:target: https://github.com/LizardByte/Sunshine
.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=AUR&style=for-the-badge&query=$.results.0.NumVotes&url=https%3A%2F%2Fapp.lizardbyte.dev%2Funo%2Faur%2Fsunshine.json&logo=archlinux
:alt: AUR votes
:target: https://aur.archlinux.org/packages/sunshine
.. _nvenc support matrix: https://developer.nvidia.com/video-encode-and-decode-gpu-support-matrix-new
.. _obs-amd hardware support: https://github.com/obsproject/obs-amd-encoder/wiki/Hardware-Support
.. _VAAPI hardware support: https://www.intel.com/content/www/us/en/developer/articles/technical/linuxmedia-vaapi.html

144
docker/archlinux.dockerfile Normal file
View File

@@ -0,0 +1,144 @@
# syntax=docker/dockerfile:1.4
# artifacts: true
# platforms: linux/amd64
# archlinux does not have an arm64 base image
# no-cache-filters: sunshine-base,artifacts,sunshine
ARG BASE=archlinux
ARG TAG=base-devel
FROM ${BASE}:${TAG} AS sunshine-base
# install dependencies
RUN <<_DEPS
#!/bin/bash
set -e
pacman -Syu --disable-download-timeout --noconfirm \
archlinux-keyring
_DEPS
# Setup builder user, arch prevents running makepkg as root
RUN useradd -m builder && \
echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers
# makepkg is used in sunshine-build and uploader build stages
FROM sunshine-base as sunshine-build
ARG BRANCH
ARG BUILD_VERSION
ARG COMMIT
ARG CLONE_URL
# note: BUILD_VERSION may be blank
ENV BRANCH=${BRANCH}
ENV BUILD_VERSION=${BUILD_VERSION}
ENV COMMIT=${COMMIT}
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# install dependencies
# cuda, libcap, and libdrm are optional dependencies for PKGBUILD
RUN <<_DEPS
#!/bin/bash
set -e
pacman -Syu --disable-download-timeout --noconfirm \
base-devel \
cmake \
cuda \
git \
libcap \
libdrm \
namcap
_DEPS
# Setup builder user
USER builder
# copy repository
WORKDIR /build/sunshine/
COPY --link .. .
# setup build directory
WORKDIR /build/sunshine/build
# configure PKGBUILD file
RUN <<_MAKE
#!/bin/bash
set -e
if [[ "${BUILD_VERSION}" == '' ]]; then
sub_version=".r${COMMIT}"
else
sub_version=""
fi
cmake \
-DSUNSHINE_CONFIGURE_AUR=ON \
-DSUNSHINE_SUB_VERSION="${sub_version}" \
-DGITHUB_CLONE_URL="${CLONE_URL}" \
-DGITHUB_COMMIT="${COMMIT}" \
-DSUNSHINE_CONFIGURE_ONLY=ON \
/build/sunshine
_MAKE
WORKDIR /build/sunshine/pkg
RUN mv /build/sunshine/build/PKGBUILD .
# namcap and build PKGBUILD file
RUN <<_PKGBUILD
#!/bin/bash
set -e
namcap -i PKGBUILD
makepkg -si --noconfirm
ls -a
_PKGBUILD
FROM scratch as artifacts
COPY --link --from=sunshine-build /build/sunshine/pkg/PKGBUILD /PKGBUILD
COPY --link --from=sunshine-build /build/sunshine/pkg/sunshine*.pkg.tar.zst /sunshine.pkg.tar.zst
FROM sunshine-base as sunshine
# copy from uploader instead of artifacts or uploader stage will not run
COPY --link --from=artifacts /sunshine.pkg.tar.zst /
# install sunshine
RUN <<_INSTALL_SUNSHINE
#!/bin/bash
set -e
pacman -U --disable-download-timeout --noconfirm \
/sunshine.pkg.tar.zst
_INSTALL_SUNSHINE
# network setup
EXPOSE 47984-47990/tcp
EXPOSE 48010
EXPOSE 47998-48000/udp
# setup user
ARG PGID=1000
ENV PGID=${PGID}
ARG PUID=1000
ENV PUID=${PUID}
ENV TZ="UTC"
ARG UNAME=lizard
ENV UNAME=${UNAME}
ENV HOME=/home/$UNAME
# setup user
RUN <<_SETUP_USER
#!/bin/bash
set -e
# first delete the builder user
userdel -r builder
# then create the lizard user
groupadd -f -g "${PGID}" "${UNAME}"
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
mkdir -p ${HOME}/.config/sunshine
ln -s ${HOME}/.config/sunshine /config
chown -R ${UNAME} ${HOME}
_SETUP_USER
USER ${UNAME}
WORKDIR ${HOME}
# entrypoint
ENTRYPOINT ["/usr/bin/sunshine"]

View File

@@ -2,6 +2,7 @@
# artifacts: true
# platforms: linux/amd64,linux/arm64/v8
# platforms_pr: linux/amd64
# no-cache-filters: sunshine-base,artifacts,sunshine
ARG BASE=debian
ARG TAG=bullseye
FROM ${BASE}:${TAG} AS sunshine-base
@@ -13,16 +14,28 @@ FROM sunshine-base as sunshine-build
ARG TARGETPLATFORM
RUN echo "target_platform: ${TARGETPLATFORM}"
ARG BRANCH
ARG BUILD_VERSION
ARG COMMIT
# note: BUILD_VERSION may be blank
ENV BRANCH=${BRANCH}
ENV BUILD_VERSION=${BUILD_VERSION}
ENV COMMIT=${COMMIT}
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# install dependencies
RUN <<_DEPS
#!/bin/bash
set -e
apt-get update -y
apt-get install -y --no-install-recommends \
build-essential=12.9* \
cmake=3.18.4* \
git=1:2.30.2* \
libavdevice-dev=7:4.3.* \
libboost-filesystem-dev=1.74.0* \
libboost-locale-dev=1.74.0* \
libboost-log-dev=1.74.0* \
libboost-program-options-dev=1.74.0* \
libboost-thread-dev=1.74.0* \
@@ -63,6 +76,7 @@ ENV CUDA_BUILD="520.61.05"
# hadolint ignore=SC3010
RUN <<_INSTALL_CUDA
#!/bin/bash
set -e
cuda_prefix="https://developer.download.nvidia.com/compute/cuda/"
cuda_suffix=""
if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then
@@ -78,7 +92,7 @@ _INSTALL_CUDA
# copy repository
WORKDIR /build/sunshine/
COPY .. .
COPY --link .. .
# setup npm dependencies
RUN npm install
@@ -89,6 +103,7 @@ WORKDIR /build/sunshine/build
# cmake and cpack
RUN <<_MAKE
#!/bin/bash
set -e
cmake \
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
-DCMAKE_BUILD_TYPE=Release \
@@ -108,16 +123,17 @@ FROM scratch AS artifacts
ARG BASE
ARG TAG
ARG TARGETARCH
COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb
COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb
FROM sunshine-base as sunshine
# copy deb from builder
COPY --from=artifacts /sunshine*.deb /sunshine.deb
COPY --link --from=artifacts /sunshine*.deb /sunshine.deb
# install sunshine
RUN <<_INSTALL_SUNSHINE
#!/bin/bash
set -e
apt-get update -y
apt-get install -y --no-install-recommends /sunshine.deb
apt-get clean
@@ -142,6 +158,8 @@ ENV HOME=/home/$UNAME
# setup user
RUN <<_SETUP_USER
#!/bin/bash
set -e
groupadd -f -g "${PGID}" "${UNAME}"
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
mkdir -p ${HOME}/.config/sunshine

View File

@@ -2,6 +2,7 @@
# artifacts: true
# platforms: linux/amd64,linux/arm64/v8
# platforms_pr: linux/amd64
# no-cache-filters: sunshine-base,artifacts,sunshine
ARG BASE=fedora
ARG TAG=36
FROM ${BASE}:${TAG} AS sunshine-base
@@ -11,19 +12,30 @@ FROM sunshine-base as sunshine-build
ARG TARGETPLATFORM
RUN echo "target_platform: ${TARGETPLATFORM}"
ARG BRANCH
ARG BUILD_VERSION
ARG COMMIT
# note: BUILD_VERSION may be blank
ENV BRANCH=${BRANCH}
ENV BUILD_VERSION=${BUILD_VERSION}
ENV COMMIT=${COMMIT}
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# install dependencies
# hadolint ignore=DL3041
RUN <<_DEPS
#!/bin/bash
set -e
dnf -y update
dnf -y group install "Development Tools"
dnf -y install \
boost-devel-1.76.0* \
boost-static-1.76.0* \
cmake-3.22.2* \
gcc-12.0.1* \
gcc-c++-12.0.1* \
git-2.39.2* \
libappindicator-gtk3-devel-12.10.0* \
libcap-devel-2.48* \
libcurl-devel-7.82.0* \
libdrm-devel-2.4.110* \
@@ -62,6 +74,7 @@ ENV CUDA_BUILD="525.60.13"
# hadolint ignore=SC3010
RUN <<_INSTALL_CUDA
#!/bin/bash
set -e
cuda_prefix="https://developer.download.nvidia.com/compute/cuda/"
cuda_suffix=""
if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then
@@ -77,7 +90,7 @@ _INSTALL_CUDA
# copy repository
WORKDIR /build/sunshine/
COPY .. .
COPY --link .. .
# setup npm dependencies
RUN npm install
@@ -88,6 +101,7 @@ WORKDIR /build/sunshine/build
# cmake and cpack
RUN <<_MAKE
#!/bin/bash
set -e
cmake \
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
-DCMAKE_BUILD_TYPE=Release \
@@ -107,16 +121,17 @@ FROM scratch AS artifacts
ARG BASE
ARG TAG
ARG TARGETARCH
COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.rpm /sunshine-${BASE}-${TAG}-${TARGETARCH}.rpm
COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.rpm /sunshine-${BASE}-${TAG}-${TARGETARCH}.rpm
FROM sunshine-base as sunshine
# copy deb from builder
COPY --from=artifacts /sunshine*.rpm /sunshine.rpm
COPY --link --from=artifacts /sunshine*.rpm /sunshine.rpm
# install sunshine
RUN <<_INSTALL_SUNSHINE
#!/bin/bash
set -e
dnf -y update
dnf -y install /sunshine.rpm
dnf clean all
@@ -141,6 +156,8 @@ ENV HOME=/home/$UNAME
# setup user
RUN <<_SETUP_USER
#!/bin/bash
set -e
groupadd -f -g "${PGID}" "${UNAME}"
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
mkdir -p ${HOME}/.config/sunshine

View File

@@ -2,6 +2,7 @@
# artifacts: true
# platforms: linux/amd64,linux/arm64/v8
# platforms_pr: linux/amd64
# no-cache-filters: sunshine-base,artifacts,sunshine
ARG BASE=fedora
ARG TAG=37
FROM ${BASE}:${TAG} AS sunshine-base
@@ -11,19 +12,30 @@ FROM sunshine-base as sunshine-build
ARG TARGETPLATFORM
RUN echo "target_platform: ${TARGETPLATFORM}"
ARG BRANCH
ARG BUILD_VERSION
ARG COMMIT
# note: BUILD_VERSION may be blank
ENV BRANCH=${BRANCH}
ENV BUILD_VERSION=${BUILD_VERSION}
ENV COMMIT=${COMMIT}
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# install dependencies
# hadolint ignore=DL3041
RUN <<_DEPS
#!/bin/bash
set -e
dnf -y update
dnf -y group install "Development Tools"
dnf -y install \
boost-devel-1.78.0* \
boost-static-1.78.0* \
cmake-3.24.1* \
gcc-12.2.1* \
gcc-c++-12.2.1* \
git-2.39.2* \
libappindicator-gtk3-devel-12.10.1* \
libcap-devel-2.48* \
libcurl-devel-7.85.0* \
libdrm-devel-2.4.112* \
@@ -62,6 +74,7 @@ ENV CUDA_BUILD="525.60.13"
# hadolint ignore=SC3010
RUN <<_INSTALL_CUDA
#!/bin/bash
set -e
cuda_prefix="https://developer.download.nvidia.com/compute/cuda/"
cuda_suffix=""
if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then
@@ -77,7 +90,7 @@ _INSTALL_CUDA
# copy repository
WORKDIR /build/sunshine/
COPY .. .
COPY --link .. .
# setup npm dependencies
RUN npm install
@@ -88,6 +101,7 @@ WORKDIR /build/sunshine/build
# cmake and cpack
RUN <<_MAKE
#!/bin/bash
set -e
cmake \
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
-DCMAKE_BUILD_TYPE=Release \
@@ -107,16 +121,17 @@ FROM scratch AS artifacts
ARG BASE
ARG TAG
ARG TARGETARCH
COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.rpm /sunshine-${BASE}-${TAG}-${TARGETARCH}.rpm
COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.rpm /sunshine-${BASE}-${TAG}-${TARGETARCH}.rpm
FROM sunshine-base as sunshine
# copy deb from builder
COPY --from=artifacts /sunshine*.rpm /sunshine.rpm
COPY --link --from=artifacts /sunshine*.rpm /sunshine.rpm
# install sunshine
RUN <<_INSTALL_SUNSHINE
#!/bin/bash
set -e
dnf -y update
dnf -y install /sunshine.rpm
dnf clean all
@@ -141,6 +156,8 @@ ENV HOME=/home/$UNAME
# setup user
RUN <<_SETUP_USER
#!/bin/bash
set -e
groupadd -f -g "${PGID}" "${UNAME}"
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
mkdir -p ${HOME}/.config/sunshine

View File

@@ -1,210 +0,0 @@
# syntax=docker/dockerfile:1.4
# artifacts: true
# platforms: linux/amd64,linux/arm64/v8
# platforms_pr: linux/amd64
ARG BASE=ubuntu
ARG TAG=18.04
FROM ${BASE}:${TAG} AS sunshine-base
ENV DEBIAN_FRONTEND=noninteractive
FROM sunshine-base as sunshine-build
ARG TARGETPLATFORM
RUN echo "target_platform: ${TARGETPLATFORM}"
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# install dependencies
RUN <<_DEPS
#!/bin/bash
apt-get update -y
apt-get install -y --no-install-recommends \
software-properties-common=0.96.24.32.18
add-apt-repository ppa:ubuntu-toolchain-r/test
apt-get install -y --no-install-recommends \
bison=2:3.0.4* \
build-essential=12.4* \
gcc-10=10.3.0* \
g++-10=10.3.0* \
libavdevice-dev=7:3.4.* \
libcap-dev=1:2.25* \
libcurl-openssl1.0-dev=7.58.0* \
libdrm-dev=2.4.101* \
libevdev-dev=1.5.8* \
libnuma-dev=2.0.11* \
libopus-dev=1.1.2* \
libpulse-dev=1:11.1* \
libssl1.0-dev=1.0.2* \
libva-dev=2.1.0* \
libvdpau-dev=1.1.1* \
libwayland-dev=1.16.0* \
libx11-dev=2:1.6.4* \
libxcb-shm0-dev=1.13* \
libxcb-xfixes0-dev=1.13* \
libxcb1-dev=1.13* \
libxfixes-dev=1:5.0.3* \
libxrandr-dev=2:1.5.1* \
libxtst-dev=2:1.2.3* \
npm=3.5.2* \
node-gyp=3.6.2* \
nodejs-dev=8.10.0* \
wget=1.19.4*
apt-get clean
rm -rf /var/lib/apt/lists/*
_DEPS
# Update gcc alias
# https://stackoverflow.com/a/70653945/11214013
RUN <<_GCC_ALIAS
#!/bin/bash
update-alternatives --install \
/usr/bin/gcc gcc /usr/bin/gcc-10 100 \
--slave /usr/bin/g++ g++ /usr/bin/g++-10 \
--slave /usr/bin/gcov gcov /usr/bin/gcov-10 \
--slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-10 \
--slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-10
_GCC_ALIAS
# install boost
# cannot install boost for aarch64 using ppa:savoury1/boost-defaults-1.71
# otherwise add the repository and the following packages
# libboost-filesystem1.71-dev=1.71.0* \
# libboost-log1.71-dev=1.71.0* \
# libboost-program-options1.71-dev=1.71.0* \
# libboost-regex1.71-dev=1.71.0* \
# libboost-thread1.71-dev=1.71.0* \
WORKDIR /build/tmp
RUN <<_INSTALL_BOOST
url="https://boostorg.jfrog.io/artifactory/main/release/1.74.0/source/boost_1_74_0.tar.bz2"
wget "${url}" --progress=bar:force:noscroll -q --show-progress -O ./boost.tar.bz2
tar --bzip2 -xf boost.tar.bz2 --directory /build
mv /build/boost_*/ /build/boost
ls -a /build/boost
cd /build/boost
./bootstrap.sh --with-libraries=system,thread,log,program_options && \
./b2 install variant=release link=static,shared runtime-link=shared -j "$(nproc)"
_INSTALL_BOOST
# install cmake
# sunshine requires cmake >= 3.18
WORKDIR /build/cmake
# https://cmake.org/download/
ENV CMAKE_VERSION="3.25.1"
# hadolint ignore=SC3010
RUN <<_INSTALL_CMAKE
#!/bin/bash
cmake_prefix="https://github.com/Kitware/CMake/releases/download/v"
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
cmake_arch="x86_64"
elif [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then
cmake_arch="aarch64"
fi
url="${cmake_prefix}${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-${cmake_arch}.sh"
echo "cmake url: ${url}"
wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cmake.sh
sh ./cmake.sh --prefix=/usr/local --skip-license
cmake --version
_INSTALL_CMAKE
# install cuda
WORKDIR /build/cuda
# versions: https://developer.nvidia.com/cuda-toolkit-archive
ENV CUDA_VERSION="11.8.0"
ENV CUDA_BUILD="520.61.05"
# hadolint ignore=SC3010
RUN <<_INSTALL_CUDA
#!/bin/bash
cuda_prefix="https://developer.download.nvidia.com/compute/cuda/"
cuda_suffix=""
if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then
cuda_suffix="_sbsa"
fi
url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run"
echo "cuda url: ${url}"
wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run
chmod a+x ./cuda.run
./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm
rm ./cuda.run
_INSTALL_CUDA
# todo - install libmfx
# https://github.com/Intel-Media-SDK/MediaSDK
# copy repository
WORKDIR /build/sunshine/
COPY .. .
# setup npm dependencies
RUN npm install
# setup build directory
WORKDIR /build/sunshine/build
# cmake and cpack
RUN <<_MAKE
#!/bin/bash
cmake \
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DSUNSHINE_ASSETS_DIR=share/sunshine \
-DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \
-DSUNSHINE_ENABLE_WAYLAND=ON \
-DSUNSHINE_ENABLE_X11=ON \
-DSUNSHINE_ENABLE_DRM=ON \
-DSUNSHINE_ENABLE_CUDA=ON \
/build/sunshine
make -j "$(nproc)"
cpack -G DEB
_MAKE
FROM scratch AS artifacts
ARG BASE
ARG TAG
ARG TARGETARCH
COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb
FROM sunshine-base as sunshine
# copy deb from builder
COPY --from=artifacts /sunshine*.deb /sunshine.deb
# install sunshine
RUN <<_INSTALL_SUNSHINE
#!/bin/bash
apt-get update -y
apt-get install -y --no-install-recommends /sunshine.deb
apt-get clean
rm -rf /var/lib/apt/lists/*
_INSTALL_SUNSHINE
# network setup
EXPOSE 47984-47990/tcp
EXPOSE 48010
EXPOSE 47998-48000/udp
# setup user
ARG PGID=1000
ENV PGID=${PGID}
ARG PUID=1000
ENV PUID=${PUID}
ENV TZ="UTC"
ARG UNAME=lizard
ENV UNAME=${UNAME}
ENV HOME=/home/$UNAME
# setup user
RUN <<_SETUP_USER
groupadd -f -g "${PGID}" "${UNAME}"
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
mkdir -p ${HOME}/.config/sunshine
ln -s ${HOME}/.config/sunshine /config
chown -R ${UNAME} ${HOME}
_SETUP_USER
USER ${UNAME}
WORKDIR ${HOME}
# entrypoint
ENTRYPOINT ["/usr/bin/sunshine"]

View File

@@ -2,6 +2,7 @@
# artifacts: true
# platforms: linux/amd64,linux/arm64/v8
# platforms_pr: linux/amd64
# no-cache-filters: sunshine-base,artifacts,sunshine
ARG BASE=ubuntu
ARG TAG=20.04
FROM ${BASE}:${TAG} AS sunshine-base
@@ -13,17 +14,30 @@ FROM sunshine-base as sunshine-build
ARG TARGETPLATFORM
RUN echo "target_platform: ${TARGETPLATFORM}"
ARG BRANCH
ARG BUILD_VERSION
ARG COMMIT
# note: BUILD_VERSION may be blank
ENV BRANCH=${BRANCH}
ENV BUILD_VERSION=${BUILD_VERSION}
ENV COMMIT=${COMMIT}
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# install dependencies
RUN <<_DEPS
#!/bin/bash
set -e
apt-get update -y
apt-get install -y --no-install-recommends \
build-essential=12.8* \
gcc-10=10.3.0* \
g++-10=10.3.0* \
git=1:2.25.1* \
libappindicator3-dev=12.10.1* \
libavdevice-dev=7:4.2.* \
libboost-filesystem-dev=1.71.0* \
libboost-locale-dev=1.71.0* \
libboost-log-dev=1.71.0* \
libboost-program-options-dev=1.71.0* \
libboost-thread-dev=1.71.0* \
@@ -60,6 +74,7 @@ _DEPS
# https://stackoverflow.com/a/70653945/11214013
RUN <<_GCC_ALIAS
#!/bin/bash
set -e
update-alternatives --install \
/usr/bin/gcc gcc /usr/bin/gcc-10 100 \
--slave /usr/bin/g++ g++ /usr/bin/g++-10 \
@@ -76,6 +91,7 @@ ENV CMAKE_VERSION="3.25.1"
# hadolint ignore=SC3010
RUN <<_INSTALL_CMAKE
#!/bin/bash
set -e
cmake_prefix="https://github.com/Kitware/CMake/releases/download/v"
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
cmake_arch="x86_64"
@@ -97,6 +113,7 @@ ENV CUDA_BUILD="520.61.05"
# hadolint ignore=SC3010
RUN <<_INSTALL_CUDA
#!/bin/bash
set -e
cuda_prefix="https://developer.download.nvidia.com/compute/cuda/"
cuda_suffix=""
if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then
@@ -112,7 +129,7 @@ _INSTALL_CUDA
# copy repository
WORKDIR /build/sunshine/
COPY .. .
COPY --link .. .
# setup npm dependencies
RUN npm install
@@ -123,6 +140,7 @@ WORKDIR /build/sunshine/build
# cmake and cpack
RUN <<_MAKE
#!/bin/bash
set -e
cmake \
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
-DCMAKE_BUILD_TYPE=Release \
@@ -142,16 +160,17 @@ FROM scratch AS artifacts
ARG BASE
ARG TAG
ARG TARGETARCH
COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb
COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb
FROM sunshine-base as sunshine
# copy deb from builder
COPY --from=artifacts /sunshine*.deb /sunshine.deb
COPY --link --from=artifacts /sunshine*.deb /sunshine.deb
# install sunshine
RUN <<_INSTALL_SUNSHINE
#!/bin/bash
set -e
apt-get update -y
apt-get install -y --no-install-recommends /sunshine.deb
apt-get clean
@@ -176,6 +195,8 @@ ENV HOME=/home/$UNAME
# setup user
RUN <<_SETUP_USER
#!/bin/bash
set -e
groupadd -f -g "${PGID}" "${UNAME}"
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
mkdir -p ${HOME}/.config/sunshine

View File

@@ -2,6 +2,7 @@
# artifacts: true
# platforms: linux/amd64,linux/arm64/v8
# platforms_pr: linux/amd64
# no-cache-filters: sunshine-base,artifacts,sunshine
ARG BASE=ubuntu
ARG TAG=22.04
FROM ${BASE}:${TAG} AS sunshine-base
@@ -13,22 +14,35 @@ FROM sunshine-base as sunshine-build
ARG TARGETPLATFORM
RUN echo "target_platform: ${TARGETPLATFORM}"
ARG BRANCH
ARG BUILD_VERSION
ARG COMMIT
# note: BUILD_VERSION may be blank
ENV BRANCH=${BRANCH}
ENV BUILD_VERSION=${BUILD_VERSION}
ENV COMMIT=${COMMIT}
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# install dependencies
RUN <<_DEPS
#!/bin/bash
set -e
apt-get update -y
apt-get install -y --no-install-recommends \
build-essential=12.9* \
cmake=3.22.1* \
git=1:2.34.1* \
libappindicator3-dev=12.10.1* \
libavdevice-dev=7:4.4.* \
libboost-filesystem-dev=1.74.0* \
libboost-locale-dev=1.74.0* \
libboost-log-dev=1.74.0* \
libboost-program-options-dev=1.74.0* \
libboost-thread-dev=1.74.0* \
libcap-dev=1:2.44* \
libcurl4-openssl-dev=7.81.0* \
libdrm-dev=2.4.110* \
libdrm-dev=2.4.113* \
libevdev-dev=1.12.1* \
libnuma-dev=2.0.14* \
libopus-dev=1.3.1* \
@@ -63,6 +77,7 @@ ENV CUDA_BUILD="520.61.05"
# hadolint ignore=SC3010
RUN <<_INSTALL_CUDA
#!/bin/bash
set -e
cuda_prefix="https://developer.download.nvidia.com/compute/cuda/"
cuda_suffix=""
if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then
@@ -78,7 +93,7 @@ _INSTALL_CUDA
# copy repository
WORKDIR /build/sunshine/
COPY .. .
COPY --link .. .
# setup npm dependencies
RUN npm install
@@ -89,6 +104,7 @@ WORKDIR /build/sunshine/build
# cmake and cpack
RUN <<_MAKE
#!/bin/bash
set -e
cmake \
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
-DCMAKE_BUILD_TYPE=Release \
@@ -108,16 +124,17 @@ FROM scratch AS artifacts
ARG BASE
ARG TAG
ARG TARGETARCH
COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb
COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb
FROM sunshine-base as sunshine
# copy deb from builder
COPY --from=artifacts /sunshine*.deb /sunshine.deb
COPY --link --from=artifacts /sunshine*.deb /sunshine.deb
# install sunshine
RUN <<_INSTALL_SUNSHINE
#!/bin/bash
set -e
apt-get update -y
apt-get install -y --no-install-recommends /sunshine.deb
apt-get clean
@@ -142,6 +159,8 @@ ENV HOME=/home/$UNAME
# setup user
RUN <<_SETUP_USER
#!/bin/bash
set -e
groupadd -f -g "${PGID}" "${UNAME}"
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
mkdir -p ${HOME}/.config/sunshine

2778
docs/Doxyfile Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
furo==2022.12.7
m2r2==0.3.3
breathe==4.35.0
furo==2023.3.27
m2r2==0.3.3.post2
Sphinx==6.1.3
sphinx-copybutton==0.5.1

View File

@@ -7,7 +7,7 @@ Performance Tips
AMD
^^^
In Windows, enabling `Enahanced Sync` in AMD's settings may help reduce the latency by an additional frame. This
In Windows, enabling `Enhanced Sync` in AMD's settings may help reduce the latency by an additional frame. This
applies to `amfenc` and `libx264`.
Nvidia
@@ -107,6 +107,20 @@ log_path
log_path = sunshine.log
global_prep_cmd
^^^^^^^^^^^^^^^
**Description**
A list of commands to be run before/after all applications. If any of the prep-commands fail, starting the application is aborted.
**Default**
``[]``
**Example**
.. code-block:: text
global_prep_cmd = [{"do":"nircmd.exe setdisplay 1280 720 32 144","undo":"nircmd.exe setdisplay 2560 1440 32 144"}]
Controls
--------
@@ -160,7 +174,7 @@ key_repeat_delay
^^^^^^^^^^^^^^^^
**Description**
The initial delay in milliseconds before repeating keys. Controls how fast keys will repeat themselves.
The initial delay, in milliseconds, before repeating keys. Controls how fast keys will repeat themselves.
**Default**
``500``
@@ -179,7 +193,7 @@ key_repeat_frequency
.. Tip:: This configurable option supports decimals.
**Default**
.. Todo:: Unknown
``24.9``
**Example**
.. code-block:: text
@@ -197,7 +211,11 @@ keybindings
.. Hint:: keybindings needs to have a multiple of two elements.
**Default**
None
.. code-block:: text
0x10, 0xA0,
0x11, 0xA2,
0x12, 0xA4
**Example**
.. code-block:: text
@@ -217,7 +235,7 @@ key_rightalt_to_key_win
make Sunshine think the Right Alt key is the Windows key.
**Default**
None
``disabled``
**Example**
.. code-block:: text
@@ -281,13 +299,18 @@ output_name
.. Tip:: To find the name of the appropriate values follow these instructions.
**Linux**
.. code-block:: bash
During Sunshine startup, you should see the list of detected monitors:
xrandr --listmonitors
.. code-block:: text
Example output: ``0: +HDMI-1 1920/518x1200/324+0+0 HDMI-1``
Info: Detecting connected monitors
Info: Detected monitor 0: DVI-D-0, connected: false
Info: Detected monitor 1: HDMI-0, connected: true
Info: Detected monitor 2: DP-0, connected: true
Info: Detected monitor 3: DP-1, connected: false
Info: Detected monitor 4: DVI-D-1, connected: false
You need to use the value before the colon in the output, e.g. ``0``.
You need to use the value before the colon in the output, e.g. ``1``.
.. Todo:: macOS
@@ -322,7 +345,7 @@ fps
fps is supported.
**Default**
.. Todo:: Unknown
``[10, 30, 60, 90, 120]``
**Example**
.. code-block:: text
@@ -339,7 +362,20 @@ resolutions
resolution is supported.
**Default**
.. Todo:: Unknown
.. code-block:: text
[
352x240,
480x360,
858x480,
1280x720,
1920x1080,
2560x1080,
3440x1440,
1920x1200,
3860x2160,
3840x1600,
]
**Example**
.. code-block:: text
@@ -411,6 +447,8 @@ audio_sink
tools\audio-info.exe
.. Tip:: If you want to mute the host speakers, use `virtual_sink`_ instead.
**Default**
Sunshine will select the default audio device.
@@ -439,8 +477,13 @@ virtual_sink
.. Tip:: See `audio_sink`_!
**Default**
.. Todo:: Unknown
.. Tip:: These are some options for virtual sound devices.
- Stream Streaming Speakers (Linux, macOS, Windows)
- To use this option, you must have Steam installed and have used Stream remote play at least once.
- `Virtual Audio Cable <https://vb-audio.com/Cable/>`_ (macOS, Windows)
**Example**
.. code-block:: text
@@ -486,7 +529,7 @@ port
Mic (unused) 48002 UDP +13
================ ============ ===========================
.. Attention:: Custom ports are only allowed on select Moonlight clients.
.. Attention:: Custom ports may not be supported by all Moonlight clients.
**Default**
``47989``
@@ -503,7 +546,7 @@ pkey
The private key. This must be 2048 bits.
**Default**
.. Todo:: Unknown
``credentials/cakey.pem``
**Example**
.. code-block:: text
@@ -517,7 +560,7 @@ cert
The certificate. Must be signed with a 2048 bit key.
**Default**
.. Todo:: Unknown
``credentials/cacert.pem``
**Example**
.. code-block:: text
@@ -597,7 +640,7 @@ upnp
===== ===========
**Default**
``off``
``disabled``
**Example**
.. code-block:: text
@@ -608,7 +651,7 @@ ping_timeout
^^^^^^^^^^^^
**Description**
How long to wait in milliseconds for data from Moonlight before shutting down the stream.
How long to wait, in milliseconds, for data from Moonlight before shutting down the stream.
**Default**
``10000``
@@ -728,6 +771,40 @@ hevc_mode
hevc_mode = 2
capture
^^^^^^^
**Description**
Force specific screen capture method.
.. Caution:: Applies to Linux only.
**Choices**
.. table::
:widths: auto
========= ===========
Value Description
========= ===========
nvfbc Use NVIDIA Frame Buffer Capture to capture direct to GPU memory. This is usually the fastest method for
NVIDIA cards. For GeForce cards it will only work with drivers patched with
`nvidia-patch <https://github.com/keylase/nvidia-patch/>`_
or `nvlax <https://github.com/keylase/nvidia-patch/>`_.
wlr Capture for wlroots based Wayland compositors via DMA-BUF.
kms DRM/KMS screen capture from the kernel. This requires that sunshine has cap_sys_admin capability.
See :ref:`Linux Setup <about/usage:setup>`.
x11 Uses XCB. This is the slowest and most CPU intensive so should be avoided if possible.
========= ===========
**Default**
Automatic. Sunshine will use the first capture method available in the order of the table above.
**Example**
.. code-block:: text
capture = kms
encoder
^^^^^^^
@@ -784,12 +861,10 @@ sw_preset
ultrafast fastest
superfast
veryfast
superfast
faster
fast
medium
slow
slow
slower
veryslow slowest
========= ===========
@@ -1081,6 +1156,68 @@ amd_rc
amd_rc = vbr_latency
amd_usage
^^^^^^^^^
**Description**
The encoder usage profile, used to balance latency with encoding quality.
.. Note:: This option only applies when using amdvce `encoder`_.
**Choices**
.. table::
:widths: auto
=============== ===========
Value Description
=============== ===========
transcoding transcoding (slowest)
webcam webcam (slow)
lowlatency low latency (fast)
ultralowlatency ultra low latency (fastest)
=============== ===========
**Default**
``ultralowlatency``
**Example**
.. code-block:: text
amd_usage = ultralowlatency
amd_preanalysis
^^^^^^^^^^^^^^^
**Description**
Preanalysis can increase encoding quality at the cost of latency.
.. Note:: This option only applies when using amdvce `encoder`_.
**Default**
``disabled``
**Example**
.. code-block:: text
amd_preanalysis = disabled
amd_vbaq
^^^^^^^^
**Description**
Variance Based Adaptive Quantization (VBAQ) can increase subjective visual quality.
.. Note:: This option only applies when using amdvce `encoder`_.
**Default**
``enabled``
**Example**
.. code-block:: text
amd_vbaq = enabled
amd_coder
^^^^^^^^^

View File

@@ -0,0 +1,189 @@
App Examples
============
Since not all applications behave the same, we decided to create some examples to help you get started adding games
and applications to Sunshine.
.. Attention:: Throughout these examples, any fields not shown are left blank. You can enhance your experience by
adding an image or a log file (via the ``Output`` field).
Common Examples
---------------
Desktop
^^^^^^^
+----------------------+-----------------+
| **Field** | **Value** |
+----------------------+-----------------+
| Application Name | ``Desktop`` |
+----------------------+-----------------+
| Image | ``desktop.png`` |
+----------------------+-----------------+
Steam Big Picture
^^^^^^^^^^^^^^^^^
.. Note:: Steam is launched as a detached command because Steam starts with a process that self updates itself and the original
process is killed. Since the original process ends it will not work as a regular command.
+----------------------+------------------------------------------+----------------------------------+-----------------------------------+
| **Field** | **Linux** | **macOS** | **Windows** |
+----------------------+------------------------------------------+----------------------------------+-----------------------------------+
| Application Name | ``Steam Big Picture`` |
+----------------------+------------------------------------------+----------------------------------+-----------------------------------+
| Detached Commands | ``setsid steam steam://open/bigpicture`` | ``open steam://open/bigpicture`` | ``steam steam://open/bigpicture`` |
+----------------------+------------------------------------------+----------------------------------+-----------------------------------+
| Image | ``steam.png`` |
+----------------------+------------------------------------------+----------------------------------+-----------------------------------+
Epic Game Store game
^^^^^^^^^^^^^^^^^^^^
.. Note:: Using URI method will be the most consistent between various games, but does not allow a game to be launched
using the "Command" and therefore the stream will not end when the game ends.
URI (Epic)
""""""""""
+----------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| **Field** | **Windows** |
+----------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Application Name | ``Surviving Mars`` |
+----------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Detached Commands | ``cmd /C "start com.epicgames.launcher://apps/d759128018124dcabb1fbee9bb28e178%3A20729b9176c241f0b617c5723e70ec2d%3AOvenbird?action=launch&silent=true"`` |
+----------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
Binary (Epic w/ working directory)
""""""""""""""""""""""""""""""""""
+----------------------+-----------------------------------------------+
| **Field** | **Windows** |
+----------------------+-----------------------------------------------+
| Application Name | ``Surviving Mars`` |
+----------------------+-----------------------------------------------+
| Command | ``cmd /c "MarsEpic.exe"`` |
+----------------------+-----------------------------------------------+
| Working Directory | ``C:\Program Files\Epic Games\SurvivingMars`` |
+----------------------+-----------------------------------------------+
Binary (Epic w/o working directory)
"""""""""""""""""""""""""""""""""""
+----------------------+--------------------------------------------------------------+
| **Field** | **Windows** |
+----------------------+--------------------------------------------------------------+
| Application Name | ``Surviving Mars`` |
+----------------------+--------------------------------------------------------------+
| Command | ``"C:\Program Files\Epic Games\SurvivingMars\MarsEpic.exe"`` |
+----------------------+--------------------------------------------------------------+
Steam game
^^^^^^^^^^
.. Note:: Using URI method will be the most consistent between various games, but does not allow a game to be launched
using the "Command" and therefore the stream will not end when the game ends.
URI (Steam)
"""""""""""
+----------------------+-------------------------------------------+-----------------------------------+---------------------------------------------+
| **Field** | **Linux** | **macOS** | **Windows** |
+----------------------+-------------------------------------------+-----------------------------------+---------------------------------------------+
| Application Name | ``Surviving Mars`` |
+----------------------+-------------------------------------------+-----------------------------------+---------------------------------------------+
| Detached Commands | ``setsid steam steam://rungameid/464920`` | ``open steam://rungameid/464920`` | ``cmd /C "start steam://rungameid/464920"`` |
+----------------------+-------------------------------------------+-----------------------------------+---------------------------------------------+
Binary (Steam w/ working directory)
"""""""""""""""""""""""""""""""""""
+----------------------+-------------------------+-------------------------+------------------------------------------------------------------+
| **Field** | **Linux** | **macOS** | **Windows** |
+----------------------+-------------------------+-------------------------+------------------------------------------------------------------+
| Application Name | ``Surviving Mars`` |
+----------------------+-------------------------+-------------------------+------------------------------------------------------------------+
| Command | ``MarsSteam`` | ``cmd /c "MarsSteam.exe"`` |
+----------------------+-------------------------+-------------------------+------------------------------------------------------------------+
| Working Directory | ``~/.steam/steam/SteamApps/common/Survivng Mars`` | ``C:\Program Files (x86)\Steam\steamapps\common\Surviving Mars`` |
+----------------------+-------------------------+-------------------------+------------------------------------------------------------------+
Binary (Steam w/o working directory)
""""""""""""""""""""""""""""""""""""
+----------------------+------------------------------+------------------------------+----------------------------------------------------------------------------------+
| **Field** | **Linux** | **macOS** | **Windows** |
+----------------------+------------------------------+------------------------------+----------------------------------------------------------------------------------+
| Application Name | ``Surviving Mars`` |
+----------------------+------------------------------+------------------------------+----------------------------------------------------------------------------------+
| Command | ``~/.steam/steam/SteamApps/common/Survivng Mars/MarsSteam`` | ``"C:\Program Files (x86)\Steam\steamapps\common\Surviving Mars\MarsSteam.exe"`` |
+----------------------+------------------------------+------------------------------+----------------------------------------------------------------------------------+
Linux
-----
Changing Resolution and Refresh Rate (Linux - X11)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+----------------------+--------------------------------------------------------------+
| **Field** | **Value** |
+----------------------+--------------------------------------------------------------+
| Command Preparations | Do: ``xrandr --output HDMI-1 --mode 1920x1080 --rate 60`` |
| +--------------------------------------------------------------+
| | Undo: ``xrandr --output HDMI-1 --mode 3840×2160 --rate 120`` |
+----------------------+--------------------------------------------------------------+
Changing Resolution and Refresh Rate (Linux - Wayland)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+----------------------+-------------------------------------------------------------+
| **Field** | **Value** |
+----------------------+-------------------------------------------------------------+
| Command Preparations | Do: ``wlr-xrandr --output HDMI-1 --mode 1920x1080@60Hz`` |
| +-------------------------------------------------------------+
| | Undo: ``wlr-xrandr --output HDMI-1 --mode 3840×2160@120Hz`` |
+----------------------+-------------------------------------------------------------+
Flatpak
^^^^^^^
.. Attention:: Because Flatpak packages run in a sandboxed environment and do not normally have access to the host,
the Flatpak of Sunshine requires commands to be prefixed with ``flatpak-spawn --host``.
macOS
-----
Changing Resolution and Refresh Rate (macOS)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. Note:: This example uses the `displayplacer` tool to change the resolution.
This tool can be installed following instructions in their
`GitHub repository <https://github.com/jakehilborn/displayplacer>`_.
+----------------------+-----------------------------------------------------------------------------------------------+
| **Field** | **Value** |
+----------------------+-----------------------------------------------------------------------------------------------+
| Command Preparations | Do: ``displayplacer "id:<screenId> res:1920x1080 hz:60 scaling:on origin:(0,0) degree:0"`` |
| +-----------------------------------------------------------------------------------------------+
| | Undo: ``displayplacer "id:<screenId> res:3840x2160 hz:120 scaling:on origin:(0,0) degree:0"`` |
+----------------------+-----------------------------------------------------------------------------------------------+
Windows
-------
Changing Resolution and Refresh Rate (Windows)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. Note:: This example uses the `QRes` tool to change the resolution and refresh rate.
This tool can be downloaded from their `SourceForge repository <https://sourceforge.net/projects/qres/>`_.
+----------------------+----------------------------------------------------+
| **Field** | **Value** |
+----------------------+----------------------------------------------------+
| Command Preparations | Do: ``FullPath\qres.exe /x:1920 /y:1080 /r:60`` |
| +----------------------------------------------------+
| | Undo: ``FullPath\qres.exe /x:3840 /y:2160 /r:120`` |
+----------------------+----------------------------------------------------+
.. Tip:: You can change your host resolution to match the client resolution automatically using the
`Nonary/ResolutionAutomation <https://github.com/Nonary/ResolutionAutomation/>`_ project.

View File

@@ -0,0 +1 @@
.. mdinclude:: ../../../CHANGELOG.md

View File

@@ -1,3 +1 @@
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/DOCKER_README.md
.. mdinclude:: ../../../DOCKER_README.md

View File

@@ -35,9 +35,10 @@ CUDA is used for NVFBC capture.
=========================================== ============== ============== ================================
Package CUDA Version Min Driver CUDA Compute Capabilities
=========================================== ============== ============== ================================
https://aur.archlinux.org/packages/sunshine User dependent User dependent User dependent
PKGBUILD User dependent User dependent User dependent
sunshine.AppImage 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35
sunshine_{arch}.flatpak 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35
sunshine.pkg.tar.zst 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35
sunshine_{arch}.flatpak 12.0.0 525.60.13 50;52;60;61;62;70;75;80;86;90
sunshine-debian-bullseye-{arch}.deb 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35
sunshine-fedora-36-{arch}.rpm 12.0.0 525.60.13 50;52;60;61;62;70;75;80;86;90
sunshine-fedora-37-{arch}.rpm 12.0.0 525.60.13 50;52;60;61;62;70;75;80;86;90
@@ -78,14 +79,13 @@ Uninstall:
./sunshine.AppImage --remove
AUR Package
^^^^^^^^^^^
Archlinux PKGBUILD
^^^^^^^^^^^^^^^^^^
#. Open terminal and run the following code.
.. code-block:: bash
git clone https://aur.archlinux.org/sunshine.git
cd sunshine
wget https://github.com/LizardByte/Sunshine/releases/latest/download/PKGBUILD
makepkg -fi
Uninstall:
@@ -93,6 +93,20 @@ Uninstall:
pacman -R sunshine
Archlinux pkg
^^^^^^^^^^^^^
#. Open terminal and run the following code.
.. code-block:: bash
wget https://github.com/LizardByte/Sunshine/releases/latest/download/sunshine.pkg.tar.zst
pacman -U --noconfirm sunshine.pkg.tar.zst
Uninstall:
.. code-block:: bash
pacman -R sunshine
Debian Package
^^^^^^^^^^^^^^
#. Download ``sunshine-{ubuntu-version}.deb`` and run the following code.
@@ -224,7 +238,7 @@ Windows
Installer
^^^^^^^^^
#. Download and install ``sunshine-windows.exe``
#. Download and install ``sunshine-windows-installer.exe``
.. Attention:: You should carefully select or unselect the options you want to install. Do not blindly install or enable
features.
@@ -234,7 +248,7 @@ menu. Different versions of Windows may provide slightly different steps for uni
Standalone
^^^^^^^^^^
#. Download and extract ``sunshine-windows.zip``
#. Download and extract ``sunshine-windows-portable.zip``
To uninstall, delete the extracted directory which contains the ``sunshine.exe`` file.

View File

@@ -3,10 +3,17 @@ Third Party Packages
.. Danger:: These packages are not maintained by LizardByte. Use at your own risk.
AUR
---
.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=AUR&style=for-the-badge&query=$.results.0.NumVotes&url=https%3A%2F%2Fapp.lizardbyte.dev%2Funo%2Faur%2Fsunshine.json&logo=archlinux
:alt: AUR votes
:target: https://aur.archlinux.org/packages/sunshine
Chocolatey
----------
.. image:: https://img.shields.io/chocolatey/v/Sunshine?style=for-the-badge&logo=chocolatey
.. image:: https://img.shields.io/chocolatey/v/sunshine?style=for-the-badge&logo=chocolatey
:alt: Chocolatey Version
:target: https://community.chocolatey.org/packages/sunshine

View File

@@ -146,8 +146,7 @@ macOS
^^^^^
Sunshine can only access microphones on macOS due to system limitations. To stream system audio use
`Soundflower <https://github.com/mattingalls/Soundflower>`_ or
`BlackHole <https://github.com/ExistentialAudio/BlackHole>`_ and
select their sink as audio device in `sunshine.conf`.
`BlackHole <https://github.com/ExistentialAudio/BlackHole>`_.
.. Note:: Command Keys are not forwarded by Moonlight. Right Option-Key is mapped to CMD-Key.
@@ -207,7 +206,7 @@ Application List
- ``"Variable name":"Variable value"``
- ``apps`` - The list of applications
- Advanced users may want to edit the application list manually. The format is ``json``.
- Example application:
- Example ``json`` application:
.. code-block:: json
{
@@ -249,6 +248,8 @@ Application List
- ``working-dir`` - The working directory to use. If not specified, Sunshine will use the application directory.
- For more examples see :ref:`app examples <about/app_examples:app examples>`.
Considerations
--------------
- When an application is started, if there is an application already running, it will be terminated.
@@ -257,8 +258,9 @@ Considerations
- For example, if you attempt to run ``steam`` as a ``cmd`` instead of ``detached`` the stream will immediately fail.
This is due to the method in which the steam process is executed. Other applications may behave similarly.
- In addition to the apps listed, one app "Desktop" is hardcoded into Sunshine. It does not start an application,
instead it simply starts a stream.
- The "Desktop" app works the same as any other application except it has no commands. It does not start an application,
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``.
HDR Support

View File

@@ -16,6 +16,7 @@ Install Requirements
cmake \
libavdevice-dev \
libboost-filesystem-dev \
libboost-locale-dev \
libboost-log-dev \
libboost-program-options-dev \
libboost-thread-dev \
@@ -54,10 +55,11 @@ Install Requirements
sudo dnf group install "Development Tools" && \
sudo dnf install \
boost-devel \
boost-static \
cmake \
gcc \
gcc-c++ \
intel-mediasdk-devel \ # x86_64 only
libappindicator-gtk3-devel \
libcap-devel \
libcurl-devel \
libdrm-devel \
@@ -80,9 +82,7 @@ Install Requirements
pulseaudio-libs-devel \
rpm-build \ # if you want to build an RPM binary package
wget \ # necessary for cuda install with `run` file
which \ # necessary for cuda install with `run` file
# libmfx-devel is not listed for fedora, this is for x86_64 only
https://kojipkgs.fedoraproject.org//packages/libmfx/1.25/4.el8/x86_64/libmfx-devel-1.25-4.el8.x86_64.rpm
which # necessary for cuda install with `run` file
Ubuntu 20.04
^^^^^^^^^^^^
@@ -95,8 +95,10 @@ Install Requirements
build-essential \
cmake \
g++-10 \
libappindicator3-dev \
libavdevice-dev \
libboost-filesystem-dev \
libboost-locale-dev \
libboost-log-dev \
libboost-thread-dev \
libboost-program-options-dev \
@@ -142,8 +144,10 @@ Install Requirements
sudo apt update && sudo apt install \
build-essential \
cmake \
libappindicator3-dev \
libavdevice-dev \
libboost-filesystem-dev \
libboost-locale-dev \
libboost-log-dev \
libboost-thread-dev \
libboost-program-options-dev \

View File

@@ -8,6 +8,7 @@
from datetime import datetime
import os
import re
import subprocess
# -- Path setup --------------------------------------------------------------
@@ -43,9 +44,11 @@ To use cmake method for obtaining version instead of regex,
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'breathe', # c++ support for sphinx with doxygen
'm2r2', # enable markdown files
'sphinx.ext.autosectionlabel',
'sphinx.ext.todo', # enable to-do sections
'sphinx.ext.graphviz', # enable graphs for breathe
'sphinx.ext.viewcode', # add links to view source code
'sphinx_copybutton', # add a copy button to code blocks
]
@@ -64,13 +67,15 @@ source_suffix = ['.rst', '.md']
# -- Options for HTML output -------------------------------------------------
# images
html_favicon = os.path.join(root_dir, 'src_assets', 'common', 'assets', 'web', 'images', 'favicon.ico')
html_logo = os.path.join(root_dir, 'sunshine.png')
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']
html_logo = os.path.join(root_dir, 'sunshine.png')
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'furo'
@@ -82,4 +87,16 @@ html_theme_options = {
# extension config options
autosectionlabel_prefix_document = True # Make sure the target is unique
breathe_default_project = 'src'
breathe_implementation_filename_extensions = ['.c', '.cc', '.cpp', '.mm']
breathe_order_parameters_first = False
breathe_projects = dict(
src="../build/doxyxml"
)
todo_include_todos = True
subprocess.run('doxygen', shell=True, cwd=source_dir)
# disable epub mimetype warnings
# https://github.com/readthedocs/readthedocs.org/blob/eadf6ac6dc6abc760a91e1cb147cc3c5f37d1ea8/docs/conf.py#L235-L236
suppress_warnings = ["epub.unknown_project_files"]

View File

@@ -14,11 +14,16 @@ Test clang-format locally.
Sphinx
------
Sunshine uses `Sphinx <https://www.sphinx-doc.org/en/master/>`_ for documentation building. Sphinx, along with other
required documentation depencies are included in the `./docs/requirements.txt` file. Python is required to build
required python dependencies are included in the `./docs/requirements.txt` file. Python is required to build
sphinx docs. Installation and setup of python will not be covered here.
Doxygen is used to generate the XML files required by Sphinx. Doxygen can be obtained from
`Doxygen downloads <https://www.doxygen.nl/download.html>`_. Ensure that the `doxygen` executable is in your path.
The config file for Sphinx is `docs/source/conf.py`. This is already included in the repo and should not be modified.
The config file for Doxygen is `docs/Doxyfile`. This is already included in the repo and should not be modified.
Test with Sphinx
.. code-block:: bash

View File

@@ -1,5 +1,3 @@
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/index.rst
Table of Contents
=================
.. include:: toc.rst

View File

@@ -0,0 +1,86 @@
src
===
We are in process of improving the source code documentation. Code should be documented using Doxygen syntax.
Some examples exist in `main.h` and `main.cpp`. In order for documentation within the code to appear in the
rendered docs, the definition of the object must be in a header file, although the documentation itself can (and
should) be in the source file.
Example Documentation Blocks
----------------------------
**file.h**
.. code-block:: cpp
// functions
int main(int argc, char *argv[]);
**file.cpp** (with markdown)
.. code-block:: cpp
/**
* @brief Main application entry point.
* @param argc The number of arguments.
* @param argv The arguments.
*
* EXAMPLES:
* ```cpp
* main(1, const char* args[] = {"hello", "markdown", nullptr});
* ```
*/
int main(int argc, char *argv[]) {
// do stuff
}
**file.cpp** (with ReStructuredText)
.. code-block:: cpp
/**
* @brief Main application entry point.
* @param argc The number of arguments.
* @param argv The arguments.
* @rst
* EXAMPLES:
*
* .. code-block:: cpp
* main(1, const char* args[] = {"hello", "rst", nullptr});
* @endrst
*/
int main(int argc, char *argv[]) {
// do stuff
}
Code
----
.. toctree::
:maxdepth: 2
:caption: src
src/main
src/audio
src/cbs
src/config
src/confighttp
src/crypto
src/httpcommon
src/input
src/move_by_copy
src/network
src/nvhttp
src/process
src/round_robin
src/rtsp
src/stream
src/sync
src/system_tray
src/task_pool
src/thread_pool
src/thread_safe
src/upnp
src/utility
src/uuid
src/video
src/platform

View File

@@ -0,0 +1,4 @@
audio
=====
.. doxygenfile:: audio.h

View File

@@ -0,0 +1,4 @@
cbs
===
.. doxygenfile:: cbs.h

View File

@@ -0,0 +1,4 @@
config
======
.. doxygenfile:: config.h

View File

@@ -0,0 +1,4 @@
confighttp
==========
.. doxygenfile:: confighttp.h

View File

@@ -0,0 +1,4 @@
crypto
======
.. doxygenfile:: crypto.h

View File

@@ -0,0 +1,4 @@
httpcommon
==========
.. doxygenfile:: httpcommon.h

View File

@@ -0,0 +1,4 @@
input
=====
.. doxygenfile:: input.h

View File

@@ -0,0 +1,4 @@
main
====
.. doxygenfile:: main.h

View File

@@ -0,0 +1,4 @@
move_by_copy
============
.. doxygenfile:: move_by_copy.h

View File

@@ -0,0 +1,4 @@
network
=======
.. doxygenfile:: network.h

View File

@@ -0,0 +1,4 @@
nvhttp
======
.. doxygenfile:: nvhttp.h

View File

@@ -0,0 +1,10 @@
platform
========
.. toctree::
:maxdepth: 1
platform/common
platform/linux
platform/macos
platform/windows

View File

@@ -0,0 +1,4 @@
common
======
.. Todo:: Add common.h

View File

@@ -0,0 +1,12 @@
linux
=====
.. toctree::
:maxdepth: 1
linux/cuda
linux/graphics
linux/misc
linux/vaapi
linux/wayland
linux/x11grab

View File

@@ -0,0 +1,4 @@
cuda
====
.. doxygenfile:: platform/linux/cuda.h

View File

@@ -0,0 +1,4 @@
graphics
========
.. Todo:: Add graphics.h

View File

@@ -0,0 +1,4 @@
misc
====
.. Todo:: Add misc.h

View File

@@ -0,0 +1,4 @@
vaapi
=====
.. doxygenfile:: platform/linux/vaapi.h

View File

@@ -0,0 +1,4 @@
wayland
=======
.. doxygenfile:: platform/linux/wayland.h

View File

@@ -0,0 +1,4 @@
x11grab
=======
.. Todo:: Add x11grab.h

View File

@@ -0,0 +1,11 @@
macos
=====
.. toctree::
:maxdepth: 1
macos/av_audio
macos/av_img_t
macos/av_video
macos/misc
macos/nv12_zero_device

View File

@@ -0,0 +1,4 @@
av_audio
========
.. Todo:: Add av_audio.h

View File

@@ -0,0 +1,4 @@
av_img_t
========
.. Todo:: Add av_img_t.h

View File

@@ -0,0 +1,4 @@
av_video
========
.. Todo:: Add av_video.h

View File

@@ -0,0 +1,4 @@
misc
====
.. doxygenfile:: platform/macos/misc.h

View File

@@ -0,0 +1,4 @@
nv12_zero_device
================
.. Todo:: Add nv12_zero_device.h

View File

@@ -0,0 +1,9 @@
windows
=======
.. toctree::
:maxdepth: 1
windows/display
windows/misc
windows/PolicyConfig

View File

@@ -0,0 +1,4 @@
PolicyConfig
============
.. Todo:: Add PolicyConfig.h

View File

@@ -0,0 +1,4 @@
display
=======
.. Todo:: Add display.h

View File

@@ -0,0 +1,4 @@
misc
====
.. doxygenfile:: platform/windows/misc.h

View File

@@ -0,0 +1,4 @@
process
=======
.. doxygenfile:: process.h

View File

@@ -0,0 +1,4 @@
round_robin
===========
.. doxygenfile:: round_robin.h

View File

@@ -0,0 +1,4 @@
rtsp
====
.. doxygenfile:: rtsp.h

View File

@@ -0,0 +1,4 @@
stream
======
.. doxygenfile:: stream.h

View File

@@ -0,0 +1,4 @@
sync
====
.. doxygenfile:: sync.h

View File

@@ -0,0 +1,4 @@
system_tray
===========
.. doxygenfile:: system_tray.h

View File

@@ -0,0 +1,4 @@
tasl_pool
=========
.. doxygenfile:: task_pool.h

View File

@@ -0,0 +1,4 @@
thread_pool
===========
.. doxygenfile:: thread_pool.h

View File

@@ -0,0 +1,4 @@
thread_safe
===========
.. doxygenfile:: thread_safe.h

View File

@@ -0,0 +1,4 @@
upnp
====
.. doxygenfile:: upnp.h

View File

@@ -0,0 +1,4 @@
utility
=======
.. Todo:: Add utility.h

View File

@@ -0,0 +1,4 @@
uuid
====
.. doxygenfile:: uuid.h

View File

@@ -0,0 +1,4 @@
video
=====
.. doxygenfile:: video.h

View File

@@ -7,7 +7,9 @@
about/docker
about/third_party_packages
about/usage
about/app_examples
about/advanced_usage
about/changelog
.. toctree::
:maxdepth: 2
@@ -46,3 +48,9 @@
:caption: Legal
legal/legal
.. toctree::
:maxdepth: 2
:caption: source
source/src

View File

@@ -1,6 +1,6 @@
{
"dependencies": {
"@fortawesome/fontawesome-free": "6.2.1",
"@fortawesome/fontawesome-free": "6.4.0",
"bootstrap": "5.2.3",
"vue": "2.6.12"
}

View File

@@ -1,29 +1,49 @@
# Edit on github: https://github.com/LizardByte/Sunshine/tree/nightly/packaging/linux/aur/PKGBUILD
# Reference: https://wiki.archlinux.org/title/PKGBUILD
pkgname=@SUNSHINE_AUR_PKG@
pkgname='sunshine'
pkgver=@PROJECT_VERSION@@SUNSHINE_SUB_VERSION@
pkgrel=1
pkgdesc="@PROJECT_DESCRIPTION@"
arch=('x86_64' 'i686')
arch=('x86_64' 'aarch64')
url=@PROJECT_HOMEPAGE_URL@
license=('GPL3')
depends=('avahi' 'boost-libs' 'curl' 'libevdev' 'libmfx' 'libpulse' 'libva' 'libvdpau' 'libx11' 'libxcb' 'libxfixes' 'libxrandr' 'libxtst' 'numactl' 'openssl' 'opus' 'udev')
depends=('avahi' 'boost-libs' 'curl' 'libappindicator-gtk3' 'libevdev' 'libmfx' 'libpulse' 'libva' 'libvdpau' 'libx11' 'libxcb' 'libxfixes' 'libxrandr' 'libxtst' 'numactl' 'openssl' 'opus' 'udev')
makedepends=('boost' 'cmake' 'git' 'make' 'nodejs' 'npm')
optdepends=('cuda: NvFBC capture support'
'libcap'
'libdrm')
provides=(@SUNSHINE_AUR_PROVIDES@)
conflicts=(@SUNSHINE_AUR_CONFLICTS@)
provides=('sunshine')
source=("$pkgname::git+@GITHUB_CLONE_URL@#commit=@GITHUB_COMMIT@")
sha256sums=('SKIP')
prepare() {
cd "$pkgname"
git submodule update --recursive --init
# Skip submodules that we don't want
if [[ $CARCH == "x86_64" ]]; then
git -c submodule."ffmpeg-macos-x86_64".update=none \
-c submodule."ffmpeg-windows-x86_64".update=none \
-c submodule."ffmpeg-linux-aarch64".update=none \
-c submodule."ffmpeg-macos-aarch64".update=none \
submodule update --recursive --init
elif [[ $CARCH == "aarch64" ]]; then
git -c submodule."ffmpeg-macos-x86_64".update=none \
-c submodule."ffmpeg-windows-x86_64".update=none \
-c submodule."ffmpeg-linux-x86_64".update=none \
-c submodule."ffmpeg-macos-aarch64".update=none \
submodule update --recursive --init
# It's unlikely that someone could get this far on a system with an incorrect arch, but we should handle it anyway
# Pull linux ffmpeg submodules
else
git -c submodule."ffmpeg-macos-x86_64".update=none \
-c submodule."ffmpeg-windows-x86_64".update=none \
-c submodule."ffmpeg-macos-aarch64".update=none \
submodule update --recursive --init
fi
}
build() {

View File

@@ -1,7 +1,7 @@
---
app-id: dev.lizardbyte.sunshine
runtime: org.freedesktop.Platform
runtime-version: "21.08"
runtime-version: "22.08"
sdk: org.freedesktop.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.node18
@@ -33,8 +33,8 @@ modules:
buildsystem: simple
build-commands:
- cd tools/build && bison -y -d -o src/engine/jamgram.cpp src/engine/jamgram.y
- ./bootstrap.sh --prefix=$FLATPAK_DEST --with-libraries=system,thread,log,program_options || cat bootstrap.log
- ./b2 install variant=release link=static,shared runtime-link=shared cxxflags="$CXXFLAGS" linkflags="$LDFLAGS"
- ./bootstrap.sh --prefix=$FLATPAK_DEST --with-libraries=locale,log,program_options,system,thread
- ./b2 install variant=release link=shared runtime-link=shared cxxflags="$CXXFLAGS" linkflags="$LDFLAGS"
-j $FLATPAK_BUILDER_N_JOBS
sources:
- type: archive
@@ -187,14 +187,14 @@ modules:
- type: file
only-arches:
- x86_64
url: https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05_linux.run
sha256: 9223c4af3aebe4a7bbed9abd9b163b03a1b34b855fbc2b4a0d1b706ac09a5a16
url: https://developer.download.nvidia.com/compute/cuda/12.0.0/local_installers/cuda_12.0.0_525.60.13_linux.run
sha256: 905e9b9516900839fb76064719db752439f38b8cb730b49335d8bd53ddfad392
dest-filename: cuda.run
- type: file
only-arches:
- aarch64
url: https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05_linux_sbsa.run # yamllint disable-line rule:line-length
sha256: e6e9a8d31163c9776b5e313fd7590877c5684e1ecddee741154f95704d4ed27c
url: https://developer.download.nvidia.com/compute/cuda/12.0.0/local_installers/cuda_12.0.0_525.60.13_linux_sbsa.run # yamllint disable-line rule:line-length
sha256: cd13e9c65d4c8f895a968706f46064d536be09f9706bce081cc864b7e4fa4544
dest-filename: cuda.run
- name: sunshine

21
qodana-js.yaml Normal file
View File

@@ -0,0 +1,21 @@
---
version: "1.0"
linter: jetbrains/qodana-js:2023.1-eap
bootstrap: |
# install npm dependencies
npm install
exclude:
- name: All
paths:
- gh-pages
- third-party
failThreshold: 100
include:
- name: CheckDependencyLicenses
profile:
name: qodana.recommended

29
qodana-python.yaml Normal file
View File

@@ -0,0 +1,29 @@
---
version: "1.0"
linter: jetbrains/qodana-python:2023.1-eap
bootstrap: |
# setup python
python3 -m venv /data/cache/venv
source /data/cache/venv/bin/activate
python3 -m pip install -r /data/project/docs/requirements.txt
python3 -m pip install -r /data/project/scripts/requirements.txt
# remove idea directory (No Python interpreter configured for the project)
# https://github.com/JetBrains/Qodana/discussions/134#discussioncomment-4329981
rm -rf .idea
exclude:
- name: All
paths:
- gh-pages
- third-party
failThreshold: 100
include:
- name: CheckDependencyLicenses
profile:
name: qodana.recommended

View File

@@ -1 +1 @@
Babel==2.11.0
Babel==2.12.1

View File

@@ -0,0 +1,40 @@
# standard imports
import os
import subprocess
# variables
directories = [
'src',
'tools',
os.path.join('third-party', 'glad'),
os.path.join('third-party', 'nvfbc'),
os.path.join('third-party', 'wayland-protocols')
]
file_types = [
'cpp',
'h',
'm',
'mm'
]
def clang_format(file: str):
print(f'Formatting {file} ...')
subprocess.run(['clang-format', '-i', file])
def main():
"""
Main entry point.
"""
# walk the directories
for directory in directories:
for root, dirs, files in os.walk(directory):
for file in files:
file_path = os.path.join(root, file)
if os.path.isfile(file_path) and file.rsplit('.')[-1] in file_types:
clang_format(file=file_path)
if __name__ == '__main__':
main()

View File

@@ -11,271 +11,279 @@
#include "utility.h"
namespace audio {
using namespace std::literals;
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<std::int16_t>>>;
using namespace std::literals;
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<std::int16_t>>>;
struct audio_ctx_t {
// We want to change the sink for the first stream only
std::unique_ptr<std::atomic_bool> sink_flag;
struct audio_ctx_t {
// We want to change the sink for the first stream only
std::unique_ptr<std::atomic_bool> sink_flag;
std::unique_ptr<platf::audio_control_t> control;
std::unique_ptr<platf::audio_control_t> control;
bool restore_sink;
platf::sink_t sink;
};
bool restore_sink;
platf::sink_t sink;
};
static int start_audio_control(audio_ctx_t &ctx);
static void stop_audio_control(audio_ctx_t &);
static int
start_audio_control(audio_ctx_t &ctx);
static void
stop_audio_control(audio_ctx_t &);
int map_stream(int channels, bool quality);
int
map_stream(int channels, bool quality);
constexpr auto SAMPLE_RATE = 48000;
constexpr auto SAMPLE_RATE = 48000;
opus_stream_config_t stream_configs[MAX_STREAM_CONFIG] {
{
SAMPLE_RATE,
2,
1,
1,
platf::speaker::map_stereo,
96000,
},
{
SAMPLE_RATE,
2,
1,
1,
platf::speaker::map_stereo,
512000,
},
{
SAMPLE_RATE,
6,
4,
2,
platf::speaker::map_surround51,
256000,
},
{
SAMPLE_RATE,
6,
6,
0,
platf::speaker::map_surround51,
1536000,
},
{
SAMPLE_RATE,
8,
5,
3,
platf::speaker::map_surround71,
450000,
},
{
SAMPLE_RATE,
8,
8,
0,
platf::speaker::map_surround71,
2048000,
},
};
opus_stream_config_t stream_configs[MAX_STREAM_CONFIG] {
{
SAMPLE_RATE,
2,
1,
1,
platf::speaker::map_stereo,
96000,
},
{
SAMPLE_RATE,
2,
1,
1,
platf::speaker::map_stereo,
512000,
},
{
SAMPLE_RATE,
6,
4,
2,
platf::speaker::map_surround51,
256000,
},
{
SAMPLE_RATE,
6,
6,
0,
platf::speaker::map_surround51,
1536000,
},
{
SAMPLE_RATE,
8,
5,
3,
platf::speaker::map_surround71,
450000,
},
{
SAMPLE_RATE,
8,
8,
0,
platf::speaker::map_surround71,
2048000,
},
};
auto control_shared = safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control);
auto control_shared = safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control);
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])];
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])];
// Encoding takes place on this thread
platf::adjust_thread_priority(platf::thread_priority_e::high);
// Encoding takes place on this thread
platf::adjust_thread_priority(platf::thread_priority_e::high);
opus_t opus { opus_multistream_encoder_create(
stream->sampleRate,
stream->channelCount,
stream->streams,
stream->coupledStreams,
stream->mapping,
OPUS_APPLICATION_RESTRICTED_LOWDELAY,
nullptr) };
opus_t opus { opus_multistream_encoder_create(
stream->sampleRate,
stream->channelCount,
stream->streams,
stream->coupledStreams,
stream->mapping,
OPUS_APPLICATION_RESTRICTED_LOWDELAY,
nullptr) };
opus_multistream_encoder_ctl(opus.get(), OPUS_SET_BITRATE(stream->bitrate));
opus_multistream_encoder_ctl(opus.get(), OPUS_SET_VBR(0));
opus_multistream_encoder_ctl(opus.get(), OPUS_SET_BITRATE(stream->bitrate));
opus_multistream_encoder_ctl(opus.get(), OPUS_SET_VBR(0));
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
while(auto sample = samples->pop()) {
buffer_t packet { 1400 };
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
while (auto sample = samples->pop()) {
buffer_t packet { 1400 };
int bytes = opus_multistream_encode(opus.get(), sample->data(), frame_size, std::begin(packet), packet.size());
if(bytes < 0) {
BOOST_LOG(error) << "Couldn't encode audio: "sv << opus_strerror(bytes);
packets->stop();
return;
}
packet.fake_resize(bytes);
packets->raise(channel_data, std::move(packet));
}
}
void capture(safe::mail_t mail, config_t config, void *channel_data) {
auto shutdown_event = mail->event<bool>(mail::shutdown);
auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
auto ref = control_shared.ref();
if(!ref) {
return;
}
auto &control = ref->control;
if(!control) {
shutdown_event->view();
return;
}
// Order of priority:
// 1. Config
// 2. Virtual if available
// 3. Host
std::string *sink = &ref->sink.host;
if(!config::audio.sink.empty()) {
sink = &config::audio.sink;
}
else if(ref->sink.null) {
auto &null = *ref->sink.null;
switch(stream->channelCount) {
case 2:
sink = &null.stereo;
break;
case 6:
sink = &null.surround51;
break;
case 8:
sink = &null.surround71;
break;
}
}
// Only the first to start a session may change the default sink
if(!ref->sink_flag->exchange(true, std::memory_order_acquire)) {
ref->restore_sink = !config.flags[config_t::HOST_AUDIO];
// If the sink is empty (Host has no sink!), definately switch to the virtual.
if(ref->sink.host.empty()) {
if(control->set_sink(*sink)) {
return;
}
}
// If the client requests audio on the host, don't change the default sink
else if(!config.flags[config_t::HOST_AUDIO] && control->set_sink(*sink)) {
return;
}
}
// Capture takes place on this thread
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 };
auto fg = util::fail_guard([&]() {
samples->stop();
thread.join();
shutdown_event->view();
});
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
int samples_per_frame = frame_size * stream->channelCount;
auto mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
if(!mic) {
BOOST_LOG(error) << "Couldn't create audio input"sv;
return;
}
while(!shutdown_event->peek()) {
std::vector<std::int16_t> sample_buffer;
sample_buffer.resize(samples_per_frame);
auto status = mic->sample(sample_buffer);
switch(status) {
case platf::capture_e::ok:
break;
case platf::capture_e::timeout:
continue;
case platf::capture_e::reinit:
mic.reset();
mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
if(!mic) {
BOOST_LOG(error) << "Couldn't re-initialize audio input"sv;
int bytes = opus_multistream_encode(opus.get(), sample->data(), frame_size, std::begin(packet), packet.size());
if (bytes < 0) {
BOOST_LOG(error) << "Couldn't encode audio: "sv << opus_strerror(bytes);
packets->stop();
return;
}
return;
default:
packet.fake_resize(bytes);
packets->raise(channel_data, std::move(packet));
}
}
void
capture(safe::mail_t mail, config_t config, void *channel_data) {
auto shutdown_event = mail->event<bool>(mail::shutdown);
auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
auto ref = control_shared.ref();
if (!ref) {
return;
}
samples->raise(std::move(sample_buffer));
auto &control = ref->control;
if (!control) {
shutdown_event->view();
return;
}
// Order of priority:
// 1. Config
// 2. Virtual if available
// 3. Host
std::string *sink = &ref->sink.host;
if (!config::audio.sink.empty()) {
sink = &config::audio.sink;
}
else if (ref->sink.null) {
auto &null = *ref->sink.null;
switch (stream->channelCount) {
case 2:
sink = &null.stereo;
break;
case 6:
sink = &null.surround51;
break;
case 8:
sink = &null.surround71;
break;
}
}
// Only the first to start a session may change the default sink
if (!ref->sink_flag->exchange(true, std::memory_order_acquire)) {
ref->restore_sink = !config.flags[config_t::HOST_AUDIO];
// If the sink is empty (Host has no sink!), definately switch to the virtual.
if (ref->sink.host.empty()) {
if (control->set_sink(*sink)) {
return;
}
}
// If the client requests audio on the host, don't change the default sink
else if (!config.flags[config_t::HOST_AUDIO] && control->set_sink(*sink)) {
return;
}
}
// Capture takes place on this thread
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 };
auto fg = util::fail_guard([&]() {
samples->stop();
thread.join();
shutdown_event->view();
});
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
int samples_per_frame = frame_size * stream->channelCount;
auto mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
if (!mic) {
BOOST_LOG(error) << "Couldn't create audio input"sv;
return;
}
while (!shutdown_event->peek()) {
std::vector<std::int16_t> sample_buffer;
sample_buffer.resize(samples_per_frame);
auto status = mic->sample(sample_buffer);
switch (status) {
case platf::capture_e::ok:
break;
case platf::capture_e::timeout:
continue;
case platf::capture_e::reinit:
mic.reset();
mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
if (!mic) {
BOOST_LOG(error) << "Couldn't re-initialize audio input"sv;
return;
}
return;
default:
return;
}
samples->raise(std::move(sample_buffer));
}
}
}
int map_stream(int channels, bool quality) {
int shift = quality ? 1 : 0;
switch(channels) {
case 2:
return STEREO + shift;
case 6:
return SURROUND51 + shift;
case 8:
return SURROUND71 + shift;
int
map_stream(int channels, bool quality) {
int shift = quality ? 1 : 0;
switch (channels) {
case 2:
return STEREO + shift;
case 6:
return SURROUND51 + shift;
case 8:
return SURROUND71 + shift;
}
return STEREO;
}
return STEREO;
}
int start_audio_control(audio_ctx_t &ctx) {
auto fg = util::fail_guard([]() {
BOOST_LOG(warning) << "There will be no audio"sv;
});
int
start_audio_control(audio_ctx_t &ctx) {
auto fg = util::fail_guard([]() {
BOOST_LOG(warning) << "There will be no audio"sv;
});
ctx.sink_flag = std::make_unique<std::atomic_bool>(false);
ctx.sink_flag = std::make_unique<std::atomic_bool>(false);
// The default sink has not been replaced yet.
ctx.restore_sink = false;
// The default sink has not been replaced yet.
ctx.restore_sink = false;
if(!(ctx.control = platf::audio_control())) {
if (!(ctx.control = platf::audio_control())) {
return 0;
}
auto sink = ctx.control->sink_info();
if (!sink) {
// Let the calling code know it failed
ctx.control.reset();
return 0;
}
ctx.sink = std::move(*sink);
fg.disable();
return 0;
}
auto sink = ctx.control->sink_info();
if(!sink) {
// Let the calling code know it failed
ctx.control.reset();
return 0;
void
stop_audio_control(audio_ctx_t &ctx) {
// restore audio-sink if applicable
if (!ctx.restore_sink) {
return;
}
const std::string &sink = config::audio.sink.empty() ? ctx.sink.host : config::audio.sink;
if (!sink.empty()) {
// Best effort, it's allowed to fail
ctx.control->set_sink(sink);
}
}
ctx.sink = std::move(*sink);
fg.disable();
return 0;
}
void stop_audio_control(audio_ctx_t &ctx) {
// restore audio-sink if applicable
if(!ctx.restore_sink) {
return;
}
const std::string &sink = config::audio.sink.empty() ? ctx.sink.host : config::audio.sink;
if(!sink.empty()) {
// Best effort, it's allowed to fail
ctx.control->set_sink(sink);
}
}
} // namespace audio
} // namespace audio

View File

@@ -4,44 +4,45 @@
#include "thread_safe.h"
#include "utility.h"
namespace audio {
enum stream_config_e : int {
STEREO,
HIGH_STEREO,
SURROUND51,
HIGH_SURROUND51,
SURROUND71,
HIGH_SURROUND71,
MAX_STREAM_CONFIG
};
struct opus_stream_config_t {
std::int32_t sampleRate;
int channelCount;
int streams;
int coupledStreams;
const std::uint8_t *mapping;
int bitrate;
};
extern opus_stream_config_t stream_configs[MAX_STREAM_CONFIG];
struct config_t {
enum flags_e : int {
HIGH_QUALITY,
HOST_AUDIO,
MAX_FLAGS
enum stream_config_e : int {
STEREO,
HIGH_STEREO,
SURROUND51,
HIGH_SURROUND51,
SURROUND71,
HIGH_SURROUND71,
MAX_STREAM_CONFIG
};
int packetDuration;
int channels;
int mask;
struct opus_stream_config_t {
std::int32_t sampleRate;
int channelCount;
int streams;
int coupledStreams;
const std::uint8_t *mapping;
int bitrate;
};
std::bitset<MAX_FLAGS> flags;
};
extern opus_stream_config_t stream_configs[MAX_STREAM_CONFIG];
using buffer_t = util::buffer_t<std::uint8_t>;
using packet_t = std::pair<void *, buffer_t>;
void capture(safe::mail_t mail, config_t config, void *channel_data);
} // namespace audio
struct config_t {
enum flags_e : int {
HIGH_QUALITY,
HOST_AUDIO,
MAX_FLAGS
};
int packetDuration;
int channels;
int mask;
std::bitset<MAX_FLAGS> flags;
};
using buffer_t = util::buffer_t<std::uint8_t>;
using packet_t = std::pair<void *, buffer_t>;
void
capture(safe::mail_t mail, config_t config, void *channel_data);
} // namespace audio
#endif

View File

@@ -12,289 +12,293 @@ extern "C" {
using namespace std::literals;
namespace cbs {
void close(CodedBitstreamContext *c) {
ff_cbs_close(&c);
}
using ctx_t = util::safe_ptr<CodedBitstreamContext, close>;
class frag_t : public CodedBitstreamFragment {
public:
frag_t(frag_t &&o) {
std::copy((std::uint8_t *)&o, (std::uint8_t *)(&o + 1), (std::uint8_t *)this);
o.data = nullptr;
o.units = nullptr;
};
frag_t() {
std::fill_n((std::uint8_t *)this, sizeof(*this), 0);
void
close(CodedBitstreamContext *c) {
ff_cbs_close(&c);
}
frag_t &operator=(frag_t &&o) {
std::copy((std::uint8_t *)&o, (std::uint8_t *)(&o + 1), (std::uint8_t *)this);
using ctx_t = util::safe_ptr<CodedBitstreamContext, close>;
o.data = nullptr;
o.units = nullptr;
class frag_t: public CodedBitstreamFragment {
public:
frag_t(frag_t &&o) {
std::copy((std::uint8_t *) &o, (std::uint8_t *) (&o + 1), (std::uint8_t *) this);
return *this;
};
o.data = nullptr;
o.units = nullptr;
};
~frag_t() {
if(data || units) {
ff_cbs_fragment_free(this);
frag_t() {
std::fill_n((std::uint8_t *) this, sizeof(*this), 0);
}
}
};
util::buffer_t<std::uint8_t> write(const 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 };
BOOST_LOG(error) << "Could not insert NAL unit SPS: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
frag_t &
operator=(frag_t &&o) {
std::copy((std::uint8_t *) &o, (std::uint8_t *) (&o + 1), (std::uint8_t *) this);
return {};
}
o.data = nullptr;
o.units = nullptr;
err = ff_cbs_write_fragment_data(cbs_ctx.get(), &frag);
if(err < 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 *this;
};
return {};
}
~frag_t() {
if (data || units) {
ff_cbs_fragment_free(this);
}
}
};
// frag.data_size * 8 - frag.data_bit_padding == bits in fragment
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) {
cbs::ctx_t cbs_ctx;
ff_cbs_init(&cbs_ctx, codec_id, nullptr);
return write(cbs_ctx, nal, uh, codec_id);
}
util::buffer_t<std::uint8_t> make_sps_h264(const AVCodecContext *ctx) {
H264RawSPS sps {};
/* b_per_p == ctx->max_b_frames for h264 */
/* desired_b_depth == avoption("b_depth") == 1 */
/* max_b_depth == std::min(av_log2(ctx->b_per_p) + 1, desired_b_depth) ==> 1 */
auto max_b_depth = 1;
auto dpb_frame = ctx->gop_size == 1 ? 0 : 1 + max_b_depth;
auto mb_width = (FFALIGN(ctx->width, 16) / 16) * 16;
auto mb_height = (FFALIGN(ctx->height, 16) / 16) * 16;
sps.nal_unit_header.nal_ref_idc = 3;
sps.nal_unit_header.nal_unit_type = H264_NAL_SPS;
sps.profile_idc = FF_PROFILE_H264_HIGH & 0xFF;
sps.constraint_set1_flag = 1;
if(ctx->level != FF_LEVEL_UNKNOWN) {
sps.level_idc = ctx->level;
}
else {
auto framerate = ctx->framerate;
auto level = ff_h264_guess_level(
sps.profile_idc,
ctx->bit_rate,
framerate.num / framerate.den,
mb_width,
mb_height,
dpb_frame);
if(!level) {
BOOST_LOG(error) << "Could not guess h264 level"sv;
util::buffer_t<std::uint8_t>
write(const 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 };
BOOST_LOG(error) << "Could not insert NAL unit SPS: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {};
}
sps.level_idc = level->level_idc;
err = ff_cbs_write_fragment_data(cbs_ctx.get(), &frag);
if (err < 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 };
std::copy_n(frag.data, frag.data_size, std::begin(data));
return data;
}
sps.seq_parameter_set_id = 0;
sps.chroma_format_idc = 1;
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);
sps.log2_max_frame_num_minus4 = 3; // 4;
sps.pic_order_cnt_type = 0;
sps.log2_max_pic_order_cnt_lsb_minus4 = 0; // 4;
sps.max_num_ref_frames = dpb_frame;
sps.pic_width_in_mbs_minus1 = mb_width / 16 - 1;
sps.pic_height_in_map_units_minus1 = mb_height / 16 - 1;
sps.frame_mbs_only_flag = 1;
sps.direct_8x8_inference_flag = 1;
if(ctx->width != mb_width || ctx->height != mb_height) {
sps.frame_cropping_flag = 1;
sps.frame_crop_left_offset = 0;
sps.frame_crop_top_offset = 0;
sps.frame_crop_right_offset = (mb_width - ctx->width) / 2;
sps.frame_crop_bottom_offset = (mb_height - ctx->height) / 2;
return write(cbs_ctx, nal, uh, codec_id);
}
sps.vui_parameters_present_flag = 1;
util::buffer_t<std::uint8_t>
make_sps_h264(const AVCodecContext *ctx) {
H264RawSPS sps {};
auto &vui = sps.vui;
/* b_per_p == ctx->max_b_frames for h264 */
/* desired_b_depth == avoption("b_depth") == 1 */
/* max_b_depth == std::min(av_log2(ctx->b_per_p) + 1, desired_b_depth) ==> 1 */
auto max_b_depth = 1;
auto dpb_frame = ctx->gop_size == 1 ? 0 : 1 + max_b_depth;
auto mb_width = (FFALIGN(ctx->width, 16) / 16) * 16;
auto mb_height = (FFALIGN(ctx->height, 16) / 16) * 16;
vui.video_format = 5;
vui.colour_description_present_flag = 1;
vui.video_signal_type_present_flag = 1;
vui.video_full_range_flag = ctx->color_range == AVCOL_RANGE_JPEG;
vui.colour_primaries = ctx->color_primaries;
vui.transfer_characteristics = ctx->color_trc;
vui.matrix_coefficients = ctx->colorspace;
sps.nal_unit_header.nal_ref_idc = 3;
sps.nal_unit_header.nal_unit_type = H264_NAL_SPS;
vui.low_delay_hrd_flag = 1 - vui.fixed_frame_rate_flag;
sps.profile_idc = FF_PROFILE_H264_HIGH & 0xFF;
vui.bitstream_restriction_flag = 1;
vui.motion_vectors_over_pic_boundaries_flag = 1;
vui.log2_max_mv_length_horizontal = 15;
vui.log2_max_mv_length_vertical = 15;
vui.max_num_reorder_frames = max_b_depth;
vui.max_dec_frame_buffering = max_b_depth + 1;
sps.constraint_set1_flag = 1;
return write(sps.nal_unit_header.nal_unit_type, (void *)&sps.nal_unit_header, AV_CODEC_ID_H264);
}
if (ctx->level != FF_LEVEL_UNKNOWN) {
sps.level_idc = ctx->level;
}
else {
auto framerate = ctx->framerate;
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 {};
auto level = ff_h264_guess_level(
sps.profile_idc,
ctx->bit_rate,
framerate.num / framerate.den,
mb_width,
mb_height,
dpb_frame);
if (!level) {
BOOST_LOG(error) << "Could not guess h264 level"sv;
return {};
}
sps.level_idc = level->level_idc;
}
sps.seq_parameter_set_id = 0;
sps.chroma_format_idc = 1;
sps.log2_max_frame_num_minus4 = 3; // 4;
sps.pic_order_cnt_type = 0;
sps.log2_max_pic_order_cnt_lsb_minus4 = 0; // 4;
sps.max_num_ref_frames = dpb_frame;
sps.pic_width_in_mbs_minus1 = mb_width / 16 - 1;
sps.pic_height_in_map_units_minus1 = mb_height / 16 - 1;
sps.frame_mbs_only_flag = 1;
sps.direct_8x8_inference_flag = 1;
if (ctx->width != mb_width || ctx->height != mb_height) {
sps.frame_cropping_flag = 1;
sps.frame_crop_left_offset = 0;
sps.frame_crop_top_offset = 0;
sps.frame_crop_right_offset = (mb_width - ctx->width) / 2;
sps.frame_crop_bottom_offset = (mb_height - ctx->height) / 2;
}
sps.vui_parameters_present_flag = 1;
auto &vui = sps.vui;
vui.video_format = 5;
vui.colour_description_present_flag = 1;
vui.video_signal_type_present_flag = 1;
vui.video_full_range_flag = ctx->color_range == AVCOL_RANGE_JPEG;
vui.colour_primaries = ctx->color_primaries;
vui.transfer_characteristics = ctx->color_trc;
vui.matrix_coefficients = ctx->colorspace;
vui.low_delay_hrd_flag = 1 - vui.fixed_frame_rate_flag;
vui.bitstream_restriction_flag = 1;
vui.motion_vectors_over_pic_boundaries_flag = 1;
vui.log2_max_mv_length_horizontal = 15;
vui.log2_max_mv_length_vertical = 15;
vui.max_num_reorder_frames = max_b_depth;
vui.max_dec_frame_buffering = max_b_depth + 1;
return write(sps.nal_unit_header.nal_unit_type, (void *) &sps.nal_unit_header, AV_CODEC_ID_H264);
}
cbs::frag_t frag;
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 {};
}
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
if(err < 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);
cbs::frag_t frag;
return {};
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
if (err < 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 {};
}
auto vps_p = ((CodedBitstreamH265Context *) ctx->priv_data)->active_vps;
auto sps_p = ((CodedBitstreamH265Context *) ctx->priv_data)->active_sps;
H265RawSPS sps { *sps_p };
H265RawVPS vps { *vps_p };
vps.profile_tier_level.general_profile_compatibility_flag[4] = 1;
sps.profile_tier_level.general_profile_compatibility_flag[4] = 1;
auto &vui = sps.vui;
std::memset(&vui, 0, sizeof(vui));
sps.vui_parameters_present_flag = 1;
// skip sample aspect ratio
vui.video_format = 5;
vui.colour_description_present_flag = 1;
vui.video_signal_type_present_flag = 1;
vui.video_full_range_flag = avctx->color_range == AVCOL_RANGE_JPEG;
vui.colour_primaries = avctx->color_primaries;
vui.transfer_characteristics = avctx->color_trc;
vui.matrix_coefficients = avctx->colorspace;
vui.vui_timing_info_present_flag = vps.vps_timing_info_present_flag;
vui.vui_num_units_in_tick = vps.vps_num_units_in_tick;
vui.vui_time_scale = vps.vps_time_scale;
vui.vui_poc_proportional_to_timing_flag = vps.vps_poc_proportional_to_timing_flag;
vui.vui_num_ticks_poc_diff_one_minus1 = vps.vps_num_ticks_poc_diff_one_minus1;
vui.vui_hrd_parameters_present_flag = 0;
vui.bitstream_restriction_flag = 1;
vui.motion_vectors_over_pic_boundaries_flag = 1;
vui.restricted_ref_pic_lists_flag = 1;
vui.max_bytes_per_pic_denom = 0;
vui.max_bits_per_min_cu_denom = 0;
vui.log2_max_mv_length_horizontal = 15;
vui.log2_max_mv_length_vertical = 15;
cbs::ctx_t write_ctx;
ff_cbs_init(&write_ctx, AV_CODEC_ID_H265, nullptr);
return hevc_t {
nal_t {
write(write_ctx, vps.nal_unit_header.nal_unit_type, (void *) &vps.nal_unit_header, AV_CODEC_ID_H265),
write(ctx, vps_p->nal_unit_header.nal_unit_type, (void *) &vps_p->nal_unit_header, AV_CODEC_ID_H265),
},
nal_t {
write(write_ctx, sps.nal_unit_header.nal_unit_type, (void *) &sps.nal_unit_header, AV_CODEC_ID_H265),
write(ctx, sps_p->nal_unit_header.nal_unit_type, (void *) &sps_p->nal_unit_header, AV_CODEC_ID_H265),
},
};
}
util::buffer_t<std::uint8_t>
read_sps_h264(const AVPacket *packet) {
cbs::ctx_t ctx;
if (ff_cbs_init(&ctx, AV_CODEC_ID_H264, nullptr)) {
return {};
}
auto vps_p = ((CodedBitstreamH265Context *)ctx->priv_data)->active_vps;
auto sps_p = ((CodedBitstreamH265Context *)ctx->priv_data)->active_sps;
cbs::frag_t frag;
H265RawSPS sps { *sps_p };
H265RawVPS vps { *vps_p };
int err = ff_cbs_read_packet(ctx.get(), &frag, &*packet);
if (err < 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);
vps.profile_tier_level.general_profile_compatibility_flag[4] = 1;
sps.profile_tier_level.general_profile_compatibility_flag[4] = 1;
return {};
}
auto &vui = sps.vui;
std::memset(&vui, 0, sizeof(vui));
sps.vui_parameters_present_flag = 1;
// skip sample aspect ratio
vui.video_format = 5;
vui.colour_description_present_flag = 1;
vui.video_signal_type_present_flag = 1;
vui.video_full_range_flag = avctx->color_range == AVCOL_RANGE_JPEG;
vui.colour_primaries = avctx->color_primaries;
vui.transfer_characteristics = avctx->color_trc;
vui.matrix_coefficients = avctx->colorspace;
vui.vui_timing_info_present_flag = vps.vps_timing_info_present_flag;
vui.vui_num_units_in_tick = vps.vps_num_units_in_tick;
vui.vui_time_scale = vps.vps_time_scale;
vui.vui_poc_proportional_to_timing_flag = vps.vps_poc_proportional_to_timing_flag;
vui.vui_num_ticks_poc_diff_one_minus1 = vps.vps_num_ticks_poc_diff_one_minus1;
vui.vui_hrd_parameters_present_flag = 0;
vui.bitstream_restriction_flag = 1;
vui.motion_vectors_over_pic_boundaries_flag = 1;
vui.restricted_ref_pic_lists_flag = 1;
vui.max_bytes_per_pic_denom = 0;
vui.max_bits_per_min_cu_denom = 0;
vui.log2_max_mv_length_horizontal = 15;
vui.log2_max_mv_length_vertical = 15;
cbs::ctx_t write_ctx;
ff_cbs_init(&write_ctx, AV_CODEC_ID_H265, nullptr);
return hevc_t {
nal_t {
write(write_ctx, vps.nal_unit_header.nal_unit_type, (void *)&vps.nal_unit_header, AV_CODEC_ID_H265),
write(ctx, vps_p->nal_unit_header.nal_unit_type, (void *)&vps_p->nal_unit_header, AV_CODEC_ID_H265),
},
nal_t {
write(write_ctx, sps.nal_unit_header.nal_unit_type, (void *)&sps.nal_unit_header, AV_CODEC_ID_H265),
write(ctx, sps_p->nal_unit_header.nal_unit_type, (void *)&sps_p->nal_unit_header, AV_CODEC_ID_H265),
},
};
}
util::buffer_t<std::uint8_t> read_sps_h264(const AVPacket *packet) {
cbs::ctx_t ctx;
if(ff_cbs_init(&ctx, AV_CODEC_ID_H264, nullptr)) {
return {};
auto h264 = (H264RawNALUnitHeader *) ((CodedBitstreamH264Context *) ctx->priv_data)->active_sps;
return write(h264->nal_unit_type, (void *) h264, AV_CODEC_ID_H264);
}
cbs::frag_t frag;
int err = ff_cbs_read_packet(ctx.get(), &frag, &*packet);
if(err < 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 {};
h264_t
make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet) {
return h264_t {
make_sps_h264(ctx),
read_sps_h264(packet),
};
}
auto h264 = (H264RawNALUnitHeader *)((CodedBitstreamH264Context *)ctx->priv_data)->active_sps;
return write(h264->nal_unit_type, (void *)h264, AV_CODEC_ID_H264);
}
h264_t make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet) {
return h264_t {
make_sps_h264(ctx),
read_sps_h264(packet),
};
}
bool validate_sps(const AVPacket *packet, int codec_id) {
cbs::ctx_t ctx;
if(ff_cbs_init(&ctx, (AVCodecID)codec_id, nullptr)) {
return false;
}
cbs::frag_t frag;
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
if(err < 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;
}
if(codec_id == AV_CODEC_ID_H264) {
auto h264 = (CodedBitstreamH264Context *)ctx->priv_data;
if(!h264->active_sps->vui_parameters_present_flag) {
bool
validate_sps(const AVPacket *packet, int codec_id) {
cbs::ctx_t ctx;
if (ff_cbs_init(&ctx, (AVCodecID) codec_id, nullptr)) {
return false;
}
return true;
}
cbs::frag_t frag;
return ((CodedBitstreamH265Context *)ctx->priv_data)->active_sps->vui_parameters_present_flag;
}
} // namespace cbs
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
if (err < 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;
}
if (codec_id == AV_CODEC_ID_H264) {
auto h264 = (CodedBitstreamH264Context *) ctx->priv_data;
if (!h264->active_sps->vui_parameters_present_flag) {
return false;
}
return true;
}
return ((CodedBitstreamH265Context *) ctx->priv_data)->active_sps->vui_parameters_present_flag;
}
} // namespace cbs

View File

@@ -8,27 +8,30 @@ struct AVCodecContext;
namespace cbs {
struct nal_t {
util::buffer_t<std::uint8_t> _new;
util::buffer_t<std::uint8_t> old;
};
struct nal_t {
util::buffer_t<std::uint8_t> _new;
util::buffer_t<std::uint8_t> old;
};
struct hevc_t {
nal_t vps;
nal_t sps;
};
struct hevc_t {
nal_t vps;
nal_t sps;
};
struct h264_t {
nal_t sps;
};
struct h264_t {
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);
/**
/**
* Check if SPS->VUI is present
*/
bool validate_sps(const AVPacket *packet, int codec_id);
} // namespace cbs
bool
validate_sps(const AVPacket *packet, int codec_id);
} // namespace cbs
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -9,135 +9,157 @@
#include <vector>
namespace config {
struct video_t {
// ffmpeg params
int qp; // higher == more compression and less quality
struct video_t {
// ffmpeg params
int qp; // higher == more compression and less quality
int hevc_mode;
int hevc_mode;
int min_threads; // Minimum number of threads/slices for CPU encoding
struct {
std::string preset;
std::string tune;
} sw;
int min_threads; // Minimum number of threads/slices for CPU encoding
struct {
std::string sw_preset;
std::string sw_tune;
} sw;
struct {
std::optional<int> preset;
std::optional<int> tune;
std::optional<int> rc;
int coder;
} nv;
struct {
std::optional<int> nv_preset;
std::optional<int> nv_tune;
std::optional<int> nv_rc;
int nv_coder;
} nv;
struct {
std::optional<int> preset;
std::optional<int> cavlc;
} qsv;
struct {
std::optional<int> qsv_preset;
std::optional<int> qsv_cavlc;
} qsv;
struct {
std::optional<int> quality_h264;
std::optional<int> quality_hevc;
std::optional<int> rc_h264;
std::optional<int> rc_hevc;
int coder;
} amd;
struct {
std::optional<int> amd_quality_h264;
std::optional<int> amd_quality_hevc;
std::optional<int> amd_rc_h264;
std::optional<int> amd_rc_hevc;
std::optional<int> amd_usage_h264;
std::optional<int> amd_usage_hevc;
std::optional<int> amd_preanalysis;
std::optional<int> amd_vbaq;
int amd_coder;
} amd;
struct {
int allow_sw;
int require_sw;
int realtime;
int coder;
} vt;
struct {
int vt_allow_sw;
int vt_require_sw;
int vt_realtime;
int vt_coder;
} vt;
std::string encoder;
std::string adapter_name;
std::string output_name;
bool dwmflush;
};
std::string capture;
std::string encoder;
std::string adapter_name;
std::string output_name;
bool dwmflush;
};
struct audio_t {
std::string sink;
std::string virtual_sink;
};
struct audio_t {
std::string sink;
std::string virtual_sink;
};
struct stream_t {
std::chrono::milliseconds ping_timeout;
struct stream_t {
std::chrono::milliseconds ping_timeout;
std::string file_apps;
std::string file_apps;
int fec_percentage;
int fec_percentage;
// max unique instances of video and audio streams
int channels;
};
// max unique instances of video and audio streams
int channels;
};
struct nvhttp_t {
// Could be any of the following values:
// pc|lan|wan
std::string origin_pin_allowed;
std::string origin_web_ui_allowed;
struct nvhttp_t {
// Could be any of the following values:
// pc|lan|wan
std::string origin_pin_allowed;
std::string origin_web_ui_allowed;
std::string pkey; // must be 2048 bits
std::string cert; // must be signed with a key of 2048 bits
std::string pkey; // must be 2048 bits
std::string cert; // must be signed with a key of 2048 bits
std::string sunshine_name;
std::string sunshine_name;
std::string file_state;
std::string file_state;
std::string external_ip;
std::vector<std::string> resolutions;
std::vector<int> fps;
};
std::string external_ip;
std::vector<std::string> resolutions;
std::vector<int> fps;
};
struct input_t {
std::unordered_map<int, int> keybindings;
struct input_t {
std::unordered_map<int, int> keybindings;
std::chrono::milliseconds back_button_timeout;
std::chrono::milliseconds key_repeat_delay;
std::chrono::duration<double> key_repeat_period;
std::chrono::milliseconds back_button_timeout;
std::chrono::milliseconds key_repeat_delay;
std::chrono::duration<double> key_repeat_period;
std::string gamepad;
};
std::string gamepad;
namespace flag {
enum flag_e : std::size_t {
PIN_STDIN = 0, // Read PIN from stdin instead of http
FRESH_STATE, // Do not load or save state
FORCE_VIDEO_HEADER_REPLACE, // force replacing headers inside video data
UPNP, // Try Universal Plug 'n Play
CONST_PIN, // Use "universal" pin
FLAG_SIZE
};
}
bool keyboard;
bool mouse;
bool controller;
};
struct sunshine_t {
int min_log_level;
std::bitset<flag::FLAG_SIZE> flags;
std::string credentials_file;
namespace flag {
enum flag_e : std::size_t {
PIN_STDIN = 0, // Read PIN from stdin instead of http
FRESH_STATE, // Do not load or save state
FORCE_VIDEO_HEADER_REPLACE, // force replacing headers inside video data
UPNP, // Try Universal Plug 'n Play
CONST_PIN, // Use "universal" pin
FLAG_SIZE
};
}
std::string username;
std::string password;
std::string salt;
struct prep_cmd_t {
prep_cmd_t(std::string &&do_cmd, std::string &&undo_cmd):
do_cmd(std::move(do_cmd)), undo_cmd(std::move(undo_cmd)) {}
explicit prep_cmd_t(std::string &&do_cmd):
do_cmd(std::move(do_cmd)) {}
std::string do_cmd;
std::string undo_cmd;
};
std::string config_file;
struct sunshine_t {
int min_log_level;
std::bitset<flag::FLAG_SIZE> flags;
std::string credentials_file;
struct cmd_t {
std::string name;
int argc;
char **argv;
} cmd;
std::string username;
std::string password;
std::string salt;
std::uint16_t port;
std::string log_file;
};
std::string config_file;
extern video_t video;
extern audio_t audio;
extern stream_t stream;
extern nvhttp_t nvhttp;
extern input_t input;
extern sunshine_t sunshine;
struct cmd_t {
std::string name;
int argc;
char **argv;
} cmd;
int parse(int argc, char *argv[]);
std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content);
} // namespace config
std::uint16_t port;
std::string log_file;
std::vector<prep_cmd_t> prep_cmds;
};
extern video_t video;
extern audio_t audio;
extern stream_t stream;
extern nvhttp_t nvhttp;
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);
} // namespace config
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -10,10 +10,29 @@
#define WEB_DIR SUNSHINE_ASSETS_DIR "/web/"
namespace confighttp {
constexpr auto PORT_HTTPS = 1;
void start();
} // namespace confighttp
constexpr auto PORT_HTTPS = 1;
void
start();
} // namespace confighttp
#endif // SUNSHINE_CONFIGHTTP_H
// 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" },
};
#endif // SUNSHINE_CONFIGHTTP_H

View File

@@ -4,486 +4,511 @@
#include <openssl/pem.h>
namespace crypto {
using asn1_string_t = util::safe_ptr<ASN1_STRING, ASN1_STRING_free>;
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() };
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() };
X509_STORE_add_cert(x509_store.get(), cert.get());
_certs.emplace_back(std::make_pair(std::move(cert), std::move(x509_store)));
}
static int openssl_verify_cb(int ok, X509_STORE_CTX *ctx) {
int err_code = X509_STORE_CTX_get_error(ctx);
switch(err_code) {
// FIXME: Checking for X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY is a temporary workaround to get mmonlight-embedded to work on the raspberry pi
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
return 1;
// Expired or not-yet-valid certificates are fine. Sometimes Moonlight is running on embedded devices
// that don't have accurate clocks (or haven't yet synchronized by the time Moonlight first runs).
// This behavior also matches what GeForce Experience does.
case X509_V_ERR_CERT_NOT_YET_VALID:
case X509_V_ERR_CERT_HAS_EXPIRED:
return 1;
default:
return ok;
X509_STORE_add_cert(x509_store.get(), cert.get());
_certs.emplace_back(std::make_pair(std::move(cert), std::move(x509_store)));
}
}
/*
static int
openssl_verify_cb(int ok, X509_STORE_CTX *ctx) {
int err_code = X509_STORE_CTX_get_error(ctx);
switch (err_code) {
// Expired or not-yet-valid certificates are fine. Sometimes Moonlight is running on embedded devices
// that don't have accurate clocks (or haven't yet synchronized by the time Moonlight first runs).
// This behavior also matches what GeForce Experience does.
// FIXME: Checking for X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY is a temporary workaround to get moonlight-embedded to work on the raspberry pi
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
case X509_V_ERR_CERT_NOT_YET_VALID:
case X509_V_ERR_CERT_HAS_EXPIRED:
return 1;
default:
return ok;
}
}
/*
* When certificates from two or more instances of Moonlight have been added to x509_store_t,
* only one of them will be verified by X509_verify_cert, resulting in only a single instance of
* Moonlight to be able to use Sunshine
*
* To circumvent this, x509_store_t instance will be created for each instance of the certificates.
*/
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]() {
X509_STORE_CTX_cleanup(_cert_ctx.get());
});
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]() {
X509_STORE_CTX_cleanup(_cert_ctx.get());
});
X509_STORE_CTX_init(_cert_ctx.get(), x509_store.get(), cert, nullptr);
X509_STORE_CTX_set_verify_cb(_cert_ctx.get(), openssl_verify_cb);
X509_STORE_CTX_init(_cert_ctx.get(), x509_store.get(), cert, nullptr);
X509_STORE_CTX_set_verify_cb(_cert_ctx.get(), openssl_verify_cb);
// We don't care to validate the entire chain for the purposes of client auth.
// Some versions of clients forked from Moonlight Embedded produce client certs
// that OpenSSL doesn't detect as self-signed due to some X509v3 extensions.
X509_STORE_CTX_set_flags(_cert_ctx.get(), X509_V_FLAG_PARTIAL_CHAIN);
// We don't care to validate the entire chain for the purposes of client auth.
// Some versions of clients forked from Moonlight Embedded produce client certs
// that OpenSSL doesn't detect as self-signed due to some X509v3 extensions.
X509_STORE_CTX_set_flags(_cert_ctx.get(), X509_V_FLAG_PARTIAL_CHAIN);
auto err = X509_verify_cert(_cert_ctx.get());
auto err = X509_verify_cert(_cert_ctx.get());
if(err == 1) {
return nullptr;
if (err == 1) {
return nullptr;
}
err_code = X509_STORE_CTX_get_error(_cert_ctx.get());
if (err_code != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && err_code != X509_V_ERR_INVALID_CA) {
return X509_verify_cert_error_string(err_code);
}
}
err_code = X509_STORE_CTX_get_error(_cert_ctx.get());
return X509_verify_cert_error_string(err_code);
}
if(err_code != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && err_code != X509_V_ERR_INVALID_CA) {
return X509_verify_cert_error_string(err_code);
namespace cipher {
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) {
return -1;
}
if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
return -1;
}
if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv->size(), nullptr) != 1) {
return -1;
}
if (EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key->data(), iv->data()) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
return 0;
}
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
if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
return -1;
}
if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv->size(), nullptr) != 1) {
return -1;
}
if (EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr, key->data(), iv->data()) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
return 0;
}
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
if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_cbc(), nullptr, key->data(), iv->data()) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
return 0;
}
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;
}
// Calling with cipher == nullptr results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if (EVP_DecryptInit_ex(decrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
return false;
}
auto cipher = tagged_cipher.substr(tag_size);
auto tag = tagged_cipher.substr(0, tag_size);
plaintext.resize((cipher.size() + 15) / 16 * 16);
int size;
if (EVP_DecryptUpdate(decrypt_ctx.get(), plaintext.data(), &size, (const std::uint8_t *) cipher.data(), cipher.size()) != 1) {
return -1;
}
if (EVP_CIPHER_CTX_ctrl(decrypt_ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char *>(tag.data())) != 1) {
return -1;
}
int len = size;
if (EVP_DecryptFinal_ex(decrypt_ctx.get(), plaintext.data() + size, &len) != 1) {
return -1;
}
plaintext.resize(size + len);
return 0;
}
int
gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv) {
if (!encrypt_ctx && init_encrypt_gcm(encrypt_ctx, &key, iv, padding)) {
return -1;
}
// Calling with cipher == nullptr results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if (EVP_EncryptInit_ex(encrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
return -1;
}
auto tag = tagged_cipher;
auto cipher = tag + tag_size;
int len;
int size = round_to_pkcs7_padded(plaintext.size());
// Encrypt into the caller's buffer
if (EVP_EncryptUpdate(encrypt_ctx.get(), cipher, &size, (const std::uint8_t *) plaintext.data(), plaintext.size()) != 1) {
return -1;
}
// GCM encryption won't ever fill ciphertext here but we have to call it anyway
if (EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher + size, &len) != 1) {
return -1;
}
if (EVP_CIPHER_CTX_ctrl(encrypt_ctx.get(), EVP_CTRL_GCM_GET_TAG, tag_size, tag) != 1) {
return -1;
}
return len + size;
}
int
ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
int len;
auto fg = util::fail_guard([this]() {
EVP_CIPHER_CTX_reset(decrypt_ctx.get());
});
// Gen 7 servers use 128-bit AES ECB
if (EVP_DecryptInit_ex(decrypt_ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(decrypt_ctx.get(), padding);
plaintext.resize((cipher.size() + 15) / 16 * 16);
auto size = (int) plaintext.size();
// Decrypt into the caller's buffer, leaving room for the auth tag to be prepended
if (EVP_DecryptUpdate(decrypt_ctx.get(), plaintext.data(), &size, (const std::uint8_t *) cipher.data(), cipher.size()) != 1) {
return -1;
}
if (EVP_DecryptFinal_ex(decrypt_ctx.get(), plaintext.data(), &len) != 1) {
return -1;
}
plaintext.resize(len + size);
return 0;
}
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());
});
// Gen 7 servers use 128-bit AES ECB
if (EVP_EncryptInit_ex(encrypt_ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(encrypt_ctx.get(), padding);
int len;
cipher.resize((plaintext.size() + 15) / 16 * 16);
auto size = (int) cipher.size();
// Encrypt into the caller's buffer
if (EVP_EncryptUpdate(encrypt_ctx.get(), cipher.data(), &size, (const std::uint8_t *) plaintext.data(), plaintext.size()) != 1) {
return -1;
}
if (EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher.data() + size, &len) != 1) {
return -1;
}
cipher.resize(len + size);
return 0;
}
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;
}
// Calling with cipher == nullptr results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if (EVP_EncryptInit_ex(encrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
return false;
}
int len;
int size = plaintext.size(); // round_to_pkcs7_padded(plaintext.size());
// Encrypt into the caller's buffer
if (EVP_EncryptUpdate(encrypt_ctx.get(), cipher, &size, (const std::uint8_t *) plaintext.data(), plaintext.size()) != 1) {
return -1;
}
if (EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher + size, &len) != 1) {
return -1;
}
return size + len;
}
ecb_t::ecb_t(const aes_t &key, bool 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 } {}
gcm_t::gcm_t(const crypto::aes_t &key, bool 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 key;
std::string salt_pin;
salt_pin.reserve(salt.size() + pin.size());
salt_pin.insert(std::end(salt_pin), std::begin(salt), std::end(salt));
salt_pin.insert(std::end(salt_pin), std::begin(pin), std::end(pin));
auto hsh = hash(salt_pin);
std::copy(std::begin(hsh), std::begin(hsh) + key.size(), std::begin(key));
return key;
}
return X509_verify_cert_error_string(err_code);
}
namespace cipher {
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) {
return -1;
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;
}
if(EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
return -1;
x509_t
x509(const std::string_view &x) {
bio_t io { BIO_new(BIO_s_mem()) };
BIO_write(io.get(), x.data(), x.size());
x509_t p;
PEM_read_bio_X509(io.get(), &p, nullptr, nullptr);
return p;
}
if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv->size(), nullptr) != 1) {
return -1;
pkey_t
pkey(const std::string_view &k) {
bio_t io { BIO_new(BIO_s_mem()) };
BIO_write(io.get(), k.data(), k.size());
pkey_t p = nullptr;
PEM_read_bio_PrivateKey(io.get(), &p, nullptr, nullptr);
return p;
}
if(EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key->data(), iv->data()) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
std::string
pem(x509_t &x509) {
bio_t bio { BIO_new(BIO_s_mem()) };
return 0;
}
PEM_write_bio_X509(bio.get(), x509.get());
BUF_MEM *mem_ptr;
BIO_get_mem_ptr(bio.get(), &mem_ptr);
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
if(EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
return -1;
return { mem_ptr->data, mem_ptr->length };
}
if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv->size(), nullptr) != 1) {
return -1;
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 };
}
if(EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr, key->data(), iv->data()) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
std::string_view
signature(const x509_t &x) {
// X509_ALGOR *_ = nullptr;
return 0;
}
const ASN1_BIT_STRING *asn1 = nullptr;
X509_get0_signature(&asn1, nullptr, x.get());
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
if(EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_cbc(), nullptr, key->data(), iv->data()) != 1) {
return -1;
return { (const char *) asn1->data, (std::size_t) asn1->length };
}
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
std::string
rand(std::size_t bytes) {
std::string r;
r.resize(bytes);
return 0;
}
RAND_bytes((uint8_t *) r.data(), r.size());
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;
return r;
}
// Calling with cipher == nullptr results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if(EVP_DecryptInit_ex(decrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
return false;
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, pkey.get()) != 1) {
return {};
}
if (EVP_DigestSignUpdate(ctx.get(), data.data(), data.size()) != 1) {
return {};
}
std::size_t slen = digest_size;
std::vector<uint8_t> digest;
digest.resize(slen);
if (EVP_DigestSignFinal(ctx.get(), digest.data(), &slen) != 1) {
return {};
}
return digest;
}
auto cipher = tagged_cipher.substr(tag_size);
auto tag = tagged_cipher.substr(0, tag_size);
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;
plaintext.resize((cipher.size() + 15) / 16 * 16);
EVP_PKEY_keygen_init(ctx.get());
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx.get(), key_bits);
EVP_PKEY_keygen(ctx.get(), &pkey);
int size;
if(EVP_DecryptUpdate(decrypt_ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) {
return -1;
}
X509_set_version(x509.get(), 2);
if(EVP_CIPHER_CTX_ctrl(decrypt_ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char *>(tag.data())) != 1) {
return -1;
}
// Generate a real serial number to avoid SEC_ERROR_REUSED_ISSUER_AND_SERIAL with Firefox
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()));
int len = size;
if(EVP_DecryptFinal_ex(decrypt_ctx.get(), plaintext.data() + size, &len) != 1) {
return -1;
}
plaintext.resize(size + len);
return 0;
}
int gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv) {
if(!encrypt_ctx && init_encrypt_gcm(encrypt_ctx, &key, iv, padding)) {
return -1;
}
// Calling with cipher == nullptr results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if(EVP_EncryptInit_ex(encrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
return -1;
}
auto tag = tagged_cipher;
auto cipher = tag + tag_size;
int len;
int size = round_to_pkcs7_padded(plaintext.size());
// Encrypt into the caller's buffer
if(EVP_EncryptUpdate(encrypt_ctx.get(), cipher, &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) {
return -1;
}
// GCM encryption won't ever fill ciphertext here but we have to call it anyway
if(EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher + size, &len) != 1) {
return -1;
}
if(EVP_CIPHER_CTX_ctrl(encrypt_ctx.get(), EVP_CTRL_GCM_GET_TAG, tag_size, tag) != 1) {
return -1;
}
return len + size;
}
int ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
int len;
auto fg = util::fail_guard([this]() {
EVP_CIPHER_CTX_reset(decrypt_ctx.get());
});
// Gen 7 servers use 128-bit AES ECB
if(EVP_DecryptInit_ex(decrypt_ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(decrypt_ctx.get(), padding);
plaintext.resize((cipher.size() + 15) / 16 * 16);
auto size = (int)plaintext.size();
// Decrypt into the caller's buffer, leaving room for the auth tag to be prepended
if(EVP_DecryptUpdate(decrypt_ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) {
return -1;
}
if(EVP_DecryptFinal_ex(decrypt_ctx.get(), plaintext.data(), &len) != 1) {
return -1;
}
plaintext.resize(len + size);
return 0;
}
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());
});
// Gen 7 servers use 128-bit AES ECB
if(EVP_EncryptInit_ex(encrypt_ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(encrypt_ctx.get(), padding);
int len;
cipher.resize((plaintext.size() + 15) / 16 * 16);
auto size = (int)cipher.size();
// Encrypt into the caller's buffer
if(EVP_EncryptUpdate(encrypt_ctx.get(), cipher.data(), &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) {
return -1;
}
if(EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher.data() + size, &len) != 1) {
return -1;
}
cipher.resize(len + size);
return 0;
}
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;
}
// Calling with cipher == nullptr results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if(EVP_EncryptInit_ex(encrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
return false;
}
int len;
int size = plaintext.size(); // round_to_pkcs7_padded(plaintext.size());
// Encrypt into the caller's buffer
if(EVP_EncryptUpdate(encrypt_ctx.get(), cipher, &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) {
return -1;
}
if(EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher + size, &len) != 1) {
return -1;
}
return size + len;
}
ecb_t::ecb_t(const aes_t &key, bool 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 } {}
gcm_t::gcm_t(const crypto::aes_t &key, bool 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 key;
std::string salt_pin;
salt_pin.reserve(salt.size() + pin.size());
salt_pin.insert(std::end(salt_pin), std::begin(salt), std::end(salt));
salt_pin.insert(std::end(salt_pin), std::begin(pin), std::end(pin));
auto hsh = hash(salt_pin);
std::copy(std::begin(hsh), std::begin(hsh) + key.size(), std::begin(key));
return key;
}
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()) };
BIO_write(io.get(), x.data(), x.size());
x509_t p;
PEM_read_bio_X509(io.get(), &p, nullptr, nullptr);
return p;
}
pkey_t pkey(const std::string_view &k) {
bio_t io { BIO_new(BIO_s_mem()) };
BIO_write(io.get(), k.data(), k.size());
pkey_t p = nullptr;
PEM_read_bio_PrivateKey(io.get(), &p, nullptr, nullptr);
return p;
}
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 };
}
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 };
}
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 };
}
std::string rand(std::size_t bytes) {
std::string r;
r.resize(bytes);
RAND_bytes((uint8_t *)r.data(), r.size());
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() };
if(EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, pkey.get()) != 1) {
return {};
}
if(EVP_DigestSignUpdate(ctx.get(), data.data(), data.size()) != 1) {
return {};
}
std::size_t slen = digest_size;
std::vector<uint8_t> digest;
digest.resize(slen);
if(EVP_DigestSignFinal(ctx.get(), digest.data(), &slen) != 1) {
return {};
}
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) };
pkey_t pkey;
EVP_PKEY_keygen_init(ctx.get());
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx.get(), key_bits);
EVP_PKEY_keygen(ctx.get(), &pkey);
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() };
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()));
constexpr auto year = 60 * 60 * 24 * 365;
constexpr auto year = 60 * 60 * 24 * 365;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
X509_gmtime_adj(X509_get_notBefore(x509.get()), 0);
X509_gmtime_adj(X509_get_notAfter(x509.get()), 20 * year);
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);
X509_gmtime_adj(not_before.get(), 0);
X509_gmtime_adj(not_after.get(), 20 * year);
X509_set1_notBefore(x509.get(), not_before.get());
X509_set1_notAfter(x509.get(), not_after.get());
X509_set1_notBefore(x509.get(), not_before.get());
X509_set1_notAfter(x509.get(), not_after.get());
#endif
X509_set_pubkey(x509.get(), pkey.get());
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);
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_set_issuer_name(x509.get(), name);
X509_sign(x509.get(), pkey.get(), EVP_sha256());
X509_set_issuer_name(x509.get(), name);
X509_sign(x509.get(), pkey.get(), EVP_sha256());
return { pem(x509), pem(pkey) };
}
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) {
auto pkey = X509_get_pubkey(x509.get());
md_ctx_t ctx { EVP_MD_CTX_create() };
if(EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pkey) != 1) {
return false;
return { pem(x509), pem(pkey) };
}
if(EVP_DigestVerifyUpdate(ctx.get(), data.data(), data.size()) != 1) {
return false;
std::vector<uint8_t>
sign256(const pkey_t &pkey, const std::string_view &data) {
return sign(pkey, data, EVP_sha256());
}
if(EVP_DigestVerifyFinal(ctx.get(), (const uint8_t *)signature.data(), signature.size()) != 1) {
return false;
bool
verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
auto pkey = X509_get_pubkey(x509.get());
md_ctx_t ctx { EVP_MD_CTX_create() };
if (EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pkey) != 1) {
return false;
}
if (EVP_DigestVerifyUpdate(ctx.get(), data.data(), data.size()) != 1) {
return false;
}
if (EVP_DigestVerifyFinal(ctx.get(), (const uint8_t *) signature.data(), signature.size()) != 1) {
return false;
}
return true;
}
return true;
}
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) {
EVP_MD_CTX_destroy(ctx);
}
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) {
value[i] = alphabet[value[i] % alphabet.length()];
bool
verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) {
return verify(x509, data, signature, EVP_sha256());
}
return value;
}
} // namespace crypto
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) {
auto value = rand(bytes);
for (std::size_t i = 0; i != value.size(); ++i) {
value[i] = alphabet[value[i] % alphabet.length()];
}
return value;
}
} // namespace crypto

View File

@@ -12,124 +12,148 @@
#include "utility.h"
namespace crypto {
struct creds_t {
std::string x509;
std::string pkey;
};
constexpr std::size_t digest_size = 256;
struct creds_t {
std::string x509;
std::string pkey;
};
constexpr std::size_t digest_size = 256;
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>;
using sha256_t = std::array<std::uint8_t, SHA256_DIGEST_LENGTH>;
using aes_t = std::array<std::uint8_t, 16>;
using x509_t = util::safe_ptr<X509, X509_free>;
using x509_store_t = util::safe_ptr<X509_STORE, X509_STORE_free>;
using x509_store_ctx_t = util::safe_ptr<X509_STORE_CTX, X509_STORE_CTX_free>;
using cipher_ctx_t = util::safe_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
using md_ctx_t = util::safe_ptr<EVP_MD_CTX, md_ctx_destroy>;
using bio_t = util::safe_ptr<BIO, BIO_free_all>;
using pkey_t = util::safe_ptr<EVP_PKEY, EVP_PKEY_free>;
using pkey_ctx_t = util::safe_ptr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
using bignum_t = util::safe_ptr<BIGNUM, BN_free>;
using aes_t = std::array<std::uint8_t, 16>;
using x509_t = util::safe_ptr<X509, X509_free>;
using x509_store_t = util::safe_ptr<X509_STORE, X509_STORE_free>;
using x509_store_ctx_t = util::safe_ptr<X509_STORE_CTX, X509_STORE_CTX_free>;
using cipher_ctx_t = util::safe_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
using md_ctx_t = util::safe_ptr<EVP_MD_CTX, md_ctx_destroy>;
using bio_t = util::safe_ptr<BIO, BIO_free_all>;
using pkey_t = util::safe_ptr<EVP_PKEY, EVP_PKEY_free>;
using pkey_ctx_t = util::safe_ptr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
using bignum_t = util::safe_ptr<BIGNUM, BN_free>;
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)
class cert_chain_t {
public:
KITTY_DECL_CONSTR(cert_chain_t)
void add(x509_t &&cert);
void
add(x509_t &&cert);
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;
x509_store_ctx_t _cert_ctx;
};
private:
std::vector<std::pair<x509_t, x509_store_t>> _certs;
x509_store_ctx_t _cert_ctx;
};
namespace cipher {
constexpr std::size_t tag_size = 16;
constexpr std::size_t round_to_pkcs7_padded(std::size_t size) {
return ((size + 15) / 16) * 16;
}
namespace cipher {
constexpr std::size_t tag_size = 16;
constexpr std::size_t
round_to_pkcs7_padded(std::size_t size) {
return ((size + 15) / 16) * 16;
}
class cipher_t {
public:
cipher_ctx_t decrypt_ctx;
cipher_ctx_t encrypt_ctx;
class cipher_t {
public:
cipher_ctx_t decrypt_ctx;
cipher_ctx_t encrypt_ctx;
aes_t key;
aes_t key;
bool padding;
};
bool padding;
};
class ecb_t : public cipher_t {
public:
ecb_t() = default;
ecb_t(ecb_t &&) noexcept = default;
ecb_t &operator=(ecb_t &&) noexcept = default;
class ecb_t: public cipher_t {
public:
ecb_t() = default;
ecb_t(ecb_t &&) noexcept = default;
ecb_t &
operator=(ecb_t &&) noexcept = default;
ecb_t(const aes_t &key, bool padding = true);
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;
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(const crypto::aes_t &key, bool padding = true);
gcm_t(const crypto::aes_t &key, bool padding = true);
/**
/**
* length of cipher must be at least: round_to_pkcs7_padded(plaintext.size()) + crypto::cipher::tag_size
*
* return -1 on error
* return bytes written on success
*/
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;
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(const crypto::aes_t &key, bool padding = true);
cbc_t(const crypto::aes_t &key, bool padding = true);
/**
/**
* length of cipher must be at least: round_to_pkcs7_padded(plaintext.size())
*
* return -1 on error
* return bytes written on success
*/
int encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv);
};
} // namespace cipher
} // namespace crypto
int
encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv);
};
} // namespace cipher
} // namespace crypto
#endif //SUNSHINE_CRYPTO_H
#endif //SUNSHINE_CRYPTO_H

View File

@@ -27,209 +27,219 @@
#include "uuid.h"
namespace http {
using namespace std::literals;
namespace fs = std::filesystem;
namespace pt = boost::property_tree;
using namespace std::literals;
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_pin_allowed;
net::net_e origin_web_ui_allowed;
std::string unique_id;
net::net_e origin_pin_allowed;
net::net_e origin_web_ui_allowed;
int init() {
bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE];
origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed);
origin_web_ui_allowed = net::from_enum_string(config::nvhttp.origin_web_ui_allowed);
int
init() {
bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE];
origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed);
origin_web_ui_allowed = net::from_enum_string(config::nvhttp.origin_web_ui_allowed);
if(clean_slate) {
unique_id = util::uuid_t::generate().string();
auto dir = std::filesystem::temp_directory_path() / "Sunshine"sv;
config::nvhttp.cert = (dir / ("cert-"s + unique_id)).string();
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 (clean_slate) {
unique_id = uuid_util::uuid_t::generate().string();
auto dir = std::filesystem::temp_directory_path() / "Sunshine"sv;
config::nvhttp.cert = (dir / ("cert-"s + unique_id)).string();
config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string();
}
}
if(user_creds_exist(config::sunshine.credentials_file)) {
if(reload_user_creds(config::sunshine.credentials_file)) return -1;
}
else {
BOOST_LOG(info) << "Open the Web UI to set your new username and password and getting started";
}
return 0;
}
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(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) {
if (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 {
BOOST_LOG(info) << "Open the Web UI to set your new username and password and getting started";
}
return 0;
}
if(fs::exists(file)) {
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) {
BOOST_LOG(error) << "Couldn't read user credentials: "sv << e.what();
return -1;
}
}
auto salt = crypto::rand_alphabet(16);
outputTree.put("username", username);
outputTree.put("salt", salt);
outputTree.put("password", util::hex(crypto::hash(password + salt)).to_string());
try {
pt::read_json(file, outputTree);
pt::write_json(file, outputTree);
}
catch(std::exception &e) {
BOOST_LOG(error) << "Couldn't read user credentials: "sv << e.what();
catch (std::exception &e) {
BOOST_LOG(error) << "generating user credentials: "sv << e.what();
return -1;
}
BOOST_LOG(info) << "New credentials have been created"sv;
return 0;
}
auto salt = crypto::rand_alphabet(16);
outputTree.put("username", username);
outputTree.put("salt", salt);
outputTree.put("password", util::hex(crypto::hash(password + salt)).to_string());
try {
pt::write_json(file, outputTree);
}
catch(std::exception &e) {
BOOST_LOG(error) << "generating user credentials: "sv << e.what();
return -1;
}
bool
user_creds_exist(const std::string &file) {
if (!fs::exists(file)) {
return false;
}
BOOST_LOG(info) << "New credentials have been created"sv;
return 0;
}
pt::ptree inputTree;
try {
pt::read_json(file, inputTree);
return inputTree.find("username") != inputTree.not_found() &&
inputTree.find("password") != inputTree.not_found() &&
inputTree.find("salt") != inputTree.not_found();
}
catch (std::exception &e) {
BOOST_LOG(error) << "validating user credentials: "sv << e.what();
}
bool user_creds_exist(const std::string &file) {
if(!fs::exists(file)) {
return false;
}
pt::ptree inputTree;
try {
pt::read_json(file, inputTree);
return inputTree.find("username") != inputTree.not_found() &&
inputTree.find("password") != inputTree.not_found() &&
inputTree.find("salt") != inputTree.not_found();
}
catch(std::exception &e) {
BOOST_LOG(error) << "validating user credentials: "sv << e.what();
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) {
BOOST_LOG(error) << "loading user credentials: "sv << e.what();
return -1;
}
return 0;
}
return false;
}
int
create_creds(const std::string &pkey, const std::string &cert) {
fs::path pkey_path = pkey;
fs::path cert_path = cert;
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) {
BOOST_LOG(error) << "loading user credentials: "sv << e.what();
return -1;
}
return 0;
}
auto creds = crypto::gen_creds("Sunshine Gamestream Host"sv, 2048);
int create_creds(const std::string &pkey, const std::string &cert) {
fs::path pkey_path = pkey;
fs::path cert_path = cert;
auto pkey_dir = pkey_path;
auto cert_dir = cert_path;
pkey_dir.remove_filename();
cert_dir.remove_filename();
auto creds = crypto::gen_creds("Sunshine Gamestream Host"sv, 2048);
std::error_code err_code {};
fs::create_directories(pkey_dir, err_code);
if (err_code) {
BOOST_LOG(error) << "Couldn't create directory ["sv << pkey_dir << "] :"sv << err_code.message();
return -1;
}
auto pkey_dir = pkey_path;
auto cert_dir = cert_path;
pkey_dir.remove_filename();
cert_dir.remove_filename();
fs::create_directories(cert_dir, err_code);
if (err_code) {
BOOST_LOG(error) << "Couldn't create directory ["sv << cert_dir << "] :"sv << err_code.message();
return -1;
}
std::error_code err_code {};
fs::create_directories(pkey_dir, err_code);
if(err_code) {
BOOST_LOG(error) << "Couldn't create directory ["sv << pkey_dir << "] :"sv << err_code.message();
return -1;
if (write_file(pkey.c_str(), creds.pkey)) {
BOOST_LOG(error) << "Couldn't open ["sv << config::nvhttp.pkey << ']';
return -1;
}
if (write_file(cert.c_str(), creds.x509)) {
BOOST_LOG(error) << "Couldn't open ["sv << config::nvhttp.cert << ']';
return -1;
}
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);
if (err_code) {
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message();
return -1;
}
return 0;
}
fs::create_directories(cert_dir, err_code);
if(err_code) {
BOOST_LOG(error) << "Couldn't create directory ["sv << cert_dir << "] :"sv << err_code.message();
return -1;
}
if(write_file(pkey.c_str(), creds.pkey)) {
BOOST_LOG(error) << "Couldn't open ["sv << config::nvhttp.pkey << ']';
return -1;
}
if(write_file(cert.c_str(), creds.x509)) {
BOOST_LOG(error) << "Couldn't open ["sv << config::nvhttp.cert << ']';
return -1;
}
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);
if(err_code) {
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message();
return -1;
}
return 0;
}
bool download_file(const std::string &url, const std::string &file) {
CURL *curl = curl_easy_init();
if(!curl) {
BOOST_LOG(error) << "Couldn't create CURL instance";
return false;
}
FILE *fp = fopen(file.c_str(), "wb");
if(!fp) {
BOOST_LOG(error) << "Couldn't open ["sv << file << ']';
curl_easy_cleanup(curl);
return false;
}
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
bool
download_file(const std::string &url, const std::string &file) {
CURL *curl = curl_easy_init();
if (!curl) {
BOOST_LOG(error) << "Couldn't create CURL instance";
return false;
}
FILE *fp = fopen(file.c_str(), "wb");
if (!fp) {
BOOST_LOG(error) << "Couldn't open ["sv << file << ']';
curl_easy_cleanup(curl);
return false;
}
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
#ifdef _WIN32
curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
#endif
CURLcode result = curl_easy_perform(curl);
if(result != CURLE_OK) {
BOOST_LOG(error) << "Couldn't download ["sv << url << ", code:" << result << ']';
CURLcode result = curl_easy_perform(curl);
if (result != CURLE_OK) {
BOOST_LOG(error) << "Couldn't download ["sv << url << ", code:" << result << ']';
}
curl_easy_cleanup(curl);
fclose(fp);
return result == CURLE_OK;
}
curl_easy_cleanup(curl);
fclose(fp);
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 result(string);
curl_free(string);
curl_easy_cleanup(curl);
return result;
}
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 result(string);
curl_free(string);
curl_easy_cleanup(curl);
return result;
}
std::string url_get_host(const std::string &url) {
CURLU *curlu = curl_url();
curl_url_set(curlu, CURLUPART_URL, url.c_str(), url.length());
char *host;
if(curl_url_get(curlu, CURLUPART_HOST, &host, 0) != CURLUE_OK) {
std::string
url_get_host(const std::string &url) {
CURLU *curlu = curl_url();
curl_url_set(curlu, CURLUPART_URL, url.c_str(), url.length());
char *host;
if (curl_url_get(curlu, CURLUPART_HOST, &host, 0) != CURLUE_OK) {
curl_url_cleanup(curlu);
return "";
}
std::string result(host);
curl_free(host);
curl_url_cleanup(curlu);
return "";
return result;
}
std::string result(host);
curl_free(host);
curl_url_cleanup(curlu);
return result;
}
} // namespace http
} // namespace http

View File

@@ -3,21 +3,28 @@
namespace http {
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);
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);
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);
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_pin_allowed;
extern net::net_e origin_web_ui_allowed;
extern std::string unique_id;
extern net::net_e origin_pin_allowed;
extern net::net_e origin_web_ui_allowed;
} // namespace http
} // namespace http

File diff suppressed because it is too large Load Diff

View File

@@ -9,25 +9,29 @@
#include "thread_safe.h"
namespace input {
struct input_t;
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();
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;
struct touch_port_t : public platf::touch_port_t {
int env_width, env_height;
// Offset x and y coordinates of the client
float client_offsetX, client_offsetY;
// Offset x and y coordinates of the client
float client_offsetX, client_offsetY;
float scalar_inv;
};
} // namespace input
float scalar_inv;
};
} // namespace input
#endif // SUNSHINE_INPUT_H
#endif // SUNSHINE_INPUT_H

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