From 95ced89d5e235b4df0915fbddb26040d6a00399b Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 16 Dec 2021 10:39:14 -0500 Subject: [PATCH 001/817] Add .github --- .github/ISSUE_TEMPLATE/bug-report.md | 33 ++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 +++++ .github/label-actions.yml | 46 +++++++++++++++++++++++++ .github/pull_request_template.md | 26 +++++++++++++++ .github/workflows/issues-stale.yml | 50 ++++++++++++++++++++++++++++ .github/workflows/issues.yml | 15 +++++++++ .github/workflows/pull-requests.yml | 35 +++++++++++++++++++ 7 files changed, 213 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/label-actions.yml create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/issues-stale.yml create mode 100644 .github/workflows/issues.yml create mode 100644 .github/workflows/pull-requests.yml diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 00000000..bd4d5458 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "" +labels: '' +assignees: '' + +--- + +**Describe the bug** + + +**To Reproduce** + +1. [e.g. Go to '...'] +2. [e.g. Click on '....'] +3. [e.g. Scroll down to '....'] +4. [e.g. See error] + +**Expected behavior** + + +**Screenshots** + + +**Host** + + - OS: [e.g. Windows, Ubuntu] + - Architecture [e.g. 32 bit, 64 bit, arm] + - Version [e.g. 0.11.1] + +**Additional context** + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..07357976 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Github Discussions + url: https://github.com/RetroArcher/Sunshine/discussions + about: General discussion, support, feature requests and more! + - name: Discord support + url: https://discord.com/invite/CGg5JxN + about: Ask question about Sunshine in Discord diff --git a/.github/label-actions.yml b/.github/label-actions.yml new file mode 100644 index 00000000..de9759f7 --- /dev/null +++ b/.github/label-actions.yml @@ -0,0 +1,46 @@ +# Configuration for Label Actions - https://github.com/dessant/label-actions + +added: + comment: > + This feature has been added and will be available in the next release. + +fixed: + comment: > + This issue has been fixed and will be available in the next release. + +invalid:duplicate: + comment: > + :wave: @{issue-author}, this appears to be a duplicate of a pre-existing issue. + close: true + lock: true + unlabel: 'status:awaiting-triage' + +-invalid:duplicate: + reopen: true + unlock: true + +invalid:support: + comment: > + :wave: @{issue-author}, we use the issue tracker exclusively for bug reports. + However, this issue appears to be a support request. Please use our + [Discord Server](https://discord.com/invite/CGg5JxN) to get help. Thanks. + close: true + lock: true + lock-reason: 'off-topic' + unlabel: 'status:awaiting-triage' + +-invalid:support: + reopen: true + unlock: true + +invalid:template-incomplete: + issues: + comment: > + :wave: @{issue-author}, please edit your issue to complete the template with + all the required info. Your issue will be automatically closed in 5 days if + the template is not completed. Thanks. + prs: + comment: > + :wave: @{issue-author}, please edit your PR to complete the template with + all the required info. Your PR will be automatically closed in 5 days if + the template is not completed. Thanks. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..e3bcbfdc --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,26 @@ +## Description + +Please include a summary of the changes. + +### Screenshot + +Include screenshots if the changes are UI-related. + +### Issues Fixed or Closed + +- Fixes #(issue) + +## Type of Change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) + +## Checklist + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have added or updated the documentation blocks for new or existing components diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml new file mode 100644 index 00000000..126af260 --- /dev/null +++ b/.github/workflows/issues-stale.yml @@ -0,0 +1,50 @@ +name: Stale Issues / PRs + +on: + schedule: + - cron: '00 19 * * *' + +jobs: + stale: + name: Check Issues / PRs + runs-on: ubuntu-latest + steps: + - name: Stale + uses: actions/stale@v3 + with: + stale-issue-message: > + This issue is stale because it has been open for 30 days with no activity. + Remove the stale label or comment, otherwise this will be closed in 5 days. + close-issue-message: > + This issue was closed because it has been stalled for 5 days with no activity. + stale-issue-label: 'stale' + exempt-issue-labels: 'added,fixed,type:enhancement,status:awaiting-triage,status:in-progress' + stale-pr-message: > + This PR is stale because it has been open for 30 days with no activity. + Remove the stale label or comment, otherwise this will be closed in 5 days. + close-pr-message: > + This PR was closed because it has been stalled for 5 days with no activity. + stale-pr-label: 'stale' + exempt-pr-labels: 'status:in-progress' + days-before-stale: 30 + days-before-close: 5 + + - name: Invalid Template + uses: actions/stale@v3 + with: + stale-issue-message: > + Invalid issues template. + close-issue-message: > + This issue was closed because the the template was not completed after 5 days. + stale-issue-label: 'invalid:template-incomplete' + skip-stale-issue-message: true + stale-pr-message: > + Invalid PR template. + close-pr-message: > + This PR was closed because the the template was not completed after 5 days. + stale-pr-label: 'invalid:template-incomplete' + exempt-pr-labels: 'status:in-progress' + skip-stale-pr-message: true + only-labels: 'invalid:template-incomplete' + days-before-stale: 0 + days-before-close: 5 diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml new file mode 100644 index 00000000..728cd8c9 --- /dev/null +++ b/.github/workflows/issues.yml @@ -0,0 +1,15 @@ +name: Issues + +on: + issues: + types: [labeled, unlabeled] + +jobs: + label: + name: Label Issues + runs-on: ubuntu-latest + steps: + - name: Label Issues + uses: dessant/label-actions@v2 + with: + github-token: ${{ github.token }} diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml new file mode 100644 index 00000000..a322319e --- /dev/null +++ b/.github/workflows/pull-requests.yml @@ -0,0 +1,35 @@ +name: Pull Requests + +on: + pull_request_target: + types: [opened, synchronize, edited, reopened] + +jobs: + check-branch: + name: Check Pull Request + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v2 + + - name: Branch check + if: ( github.head_ref == 'repo-sync/common-repo-files/default' && github.base_ref == 'master' ) || ( github.head_ref == 'nightly' && github.base_ref == 'master' ) + run: | + echo Base: "$GITHUB_BASE_REF" + echo Head: "$GITHUB_HEAD_REF" + echo "branch=True" >> $GITHUB_ENV + + - name: Comment on Pull Request + uses: mshick/add-pr-comment@v1 + if: github.base_ref != 'nightly' && env.branch != 'True' + with: + message: Pull requests must be made to the `nightly` branch. Thanks. + repo-token: ${{ secrets.GITHUB_TOKEN }} + repo-token-user-login: 'github-actions[bot]' + + - name: Fail Workflow + if: github.base_ref != 'nightly' && env.branch != 'True' + run: | + echo Base: "$GITHUB_BASE_REF" + echo Head: "$GITHUB_HEAD_REF" + exit 1 From caf70d703e2622ac06f3788c1689bd57ea2dd460 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 16 Dec 2021 11:13:19 -0500 Subject: [PATCH 002/817] Update bug-report.md -Add lines for gpu information and capture method --- .github/ISSUE_TEMPLATE/bug-report.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index bd4d5458..4da04b37 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -25,9 +25,13 @@ assignees: '' **Host** - - OS: [e.g. Windows, Ubuntu] - - Architecture [e.g. 32 bit, 64 bit, arm] - - Version [e.g. 0.11.1] + - OS: [e.g. Windows, Linux, Mac... include build/distro details] + - Architecture: [e.g. 32 bit, 64 bit, arm] + - Version: [e.g. 0.11.1] + - GPU Type: [e.g. Intel, AMD, Nvidia] + - GPU Model: + - GPU Driver/Mesa Version: + - Capture method (Linux only): [e.g. PipeWire/KVM/X11] **Additional context** From 8730980ab87b272a8bf54e3674f1fa8fab8bd838 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 16 Dec 2021 12:14:26 -0500 Subject: [PATCH 003/817] Update config.yml -Fix path for discussions url --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 07357976..64fadc7a 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,7 @@ blank_issues_enabled: false contact_links: - name: Github Discussions - url: https://github.com/RetroArcher/Sunshine/discussions + url: https://github.com/SunshineStream/Sunshine/discussions about: General discussion, support, feature requests and more! - name: Discord support url: https://discord.com/invite/CGg5JxN From 1b0978f252fdf6056ea32f8d54e450d3cbde7d64 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 17 Dec 2021 10:55:18 -0500 Subject: [PATCH 004/817] Create sunshine.png --- sunshine.png | Bin 0 -> 14699 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 sunshine.png diff --git a/sunshine.png b/sunshine.png new file mode 100644 index 0000000000000000000000000000000000000000..77f4795673e5bc0e5c828e4b37f26a6d8c05105d GIT binary patch literal 14699 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 tMkvIoS9c?+KHU?(UL Date: Fri, 17 Dec 2021 10:55:20 -0500 Subject: [PATCH 005/817] Create sunshine.desktop --- sunshine.desktop | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 sunshine.desktop diff --git a/sunshine.desktop b/sunshine.desktop new file mode 100644 index 00000000..591af9d4 --- /dev/null +++ b/sunshine.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Name=sunshine +Exec=sunshine +Version=1.0 +Comment=Host for Moonlight Streaming Client +Icon=sunshine +Categories=Utility; +Terminal=true +X-AppImage-Name=sunshine +X-AppImage-Version=1.0 +X-AppImage-Arch=x86_64 From 8478ccca5daa4103ba9ffa14090f35d8a1fc4b34 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 17 Dec 2021 10:55:23 -0500 Subject: [PATCH 006/817] Create pull-requests_build-check.yml --- .../workflows/pull-requests_build-check.yml | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 .github/workflows/pull-requests_build-check.yml diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml new file mode 100644 index 00000000..c6dc90ec --- /dev/null +++ b/.github/workflows/pull-requests_build-check.yml @@ -0,0 +1,91 @@ +name: Build test + +on: + pull_request: + types: [opened, synchronize, edited, reopened] + workflow_dispatch: + +jobs: +# check_branch: +# runs-on: ubuntu-latest +# +# steps: +# - name: Fail Workflow +# if: github.base_ref != 'nightly' +# run: | +# echo Base: "$GITHUB_BASE_REF" +# echo Head: "$GITHUB_HEAD_REF" +# exit 1 + + build_linux: + name: Linux + runs-on: ubuntu-20.04 +# needs: check_branch + + steps: + - uses: actions/checkout@v2 + - name: Setup Dependencies + run: | + mkdir -p artifacts + + sudo apt-get update -y && \ + sudo apt-get --reinstall install -y \ + git wget gcc-10 g++-10 build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev + sudo cp /usr/bin/gcc-10 /usr/bin/gcc && sudo cp /usr/bin/g++-10 /usr/bin/gcc-10 + sudo wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && sudo chmod a+x /root/cuda.run + sudo /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && sudo rm /root/cuda.run + sudo add-apt-repository ppa:savoury1/graphics -y + sudo add-apt-repository ppa:savoury1/multimedia -y + sudo add-apt-repository ppa:savoury1/ffmpeg4 -y + sudo apt-get update -y + sudo apt-get upgrade -y && sudo apt-get dist-upgrade -y + sudo apt-get install ffmpeg -y + - name: Build Sunshine for AppImage + run: | + CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}" + SUNSHINE_EXECUTABLE_PATH="${SUNSHINE_EXECUTABLE_PATH:-/usr/bin/sunshine}" + SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR:-sunshine.AppImage.config}" + + SUNSHINE_ENABLE_WAYLAND=${SUNSHINE_ENABLE_WAYLAND:-ON} + SUNSHINE_ENABLE_X11=${SUNSHINE_ENABLE_X11:-ON} + SUNSHINE_ENABLE_DRM=${SUNSHINE_ENABLE_DRM:-ON} + SUNSHINE_ENABLE_CUDA=${SUNSHINE_ENABLE_CUDA:-ON} + + mkdir -p appimage-build && cd appimage-build + + cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" ".." -DCMAKE_INSTALL_PREFIX=/usr + + make -j ${nproc} DESTDIR=AppDir + - name: Build AppImage + # https://docs.appimage.org/packaging-guide/index.html + run: | + DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" + ICON_FILE="${ICON_FILE:-sunshine.png}" + CONFIG_DIR="${CONFIG_DIR:-sunshine/sunshine.AppImage.config/}" + HOME_DIR="${HOME_DIR:-sunshine/sunshine.AppImage.home/}" + + mkdir -p temp_appimage && cd temp_appimage + + wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage && chmod +x linuxdeploy-x86_64.AppImage + ./linuxdeploy-x86_64.AppImage --appdir AppDir -e ../appimage-build/sunshine -i "../$ICON_FILE" -d "../$DESKTOP_FILE" --output appimage + + mv sunshine*.AppImage sunshine.AppImage + mkdir sunshine && mv sunshine.AppImage sunshine/ + ./sunshine/sunshine.AppImage --appimage-portable-config + ./sunshine/sunshine.AppImage --appimage-portable-home + cp -r ../assets/* "$CONFIG_DIR" + rm -f "$CONFIG_DIR"/apps_windows.json + mkdir -p ./$HOME_DIR/.config/$CONFIG_DIR + cp ./$CONFIG_DIR/apps_linux.json ./$HOME_DIR/.config/$CONFIG_DIR + zip -r ./sunshine_linux.zip ./sunshine/* + + cd .. + mv ./temp_appimage/sunshine_linux.zip ./artifacts/sunshine_linux.zip + - name: Verify AppImage + run: | + wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage && chmod +x appimagelint-x86_64.AppImage && ./appimagelint-x86_64.AppImage .temp_appimage/sunshine/sunshine.AppImage + - name: Upload Artifacts + - uses: actions/upload-artifact@v2 + with: + name: Artifacts + path: artifacts/ From c5b8deff41a6c0dafa36cb001a6a758a6f2de187 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 17 Dec 2021 10:59:55 -0500 Subject: [PATCH 007/817] Update pull-requests_build-check.yml -Fix syntax error --- .github/workflows/pull-requests_build-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index c6dc90ec..e7471a7c 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -85,7 +85,7 @@ jobs: run: | wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage && chmod +x appimagelint-x86_64.AppImage && ./appimagelint-x86_64.AppImage .temp_appimage/sunshine/sunshine.AppImage - name: Upload Artifacts - - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v2 with: name: Artifacts path: artifacts/ From e2d3fef9db2a788a0a363067ad191c663f1b5af6 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 17 Dec 2021 13:30:19 -0500 Subject: [PATCH 008/817] Update pull-requests_build-check.yml -Attempt to fix build step at line 56 -Various tweaks to quoting and directory names --- .../workflows/pull-requests_build-check.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index e7471a7c..ac7bf50b 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -53,21 +53,22 @@ jobs: mkdir -p appimage-build && cd appimage-build - cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" ".." -DCMAKE_INSTALL_PREFIX=/usr + cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" "../" -DCMAKE_INSTALL_PREFIX=/usr make -j ${nproc} DESTDIR=AppDir - name: Build AppImage # https://docs.appimage.org/packaging-guide/index.html run: | + mkdir -p appimage_temp && cd appimage_temp + DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" ICON_FILE="${ICON_FILE:-sunshine.png}" CONFIG_DIR="${CONFIG_DIR:-sunshine/sunshine.AppImage.config/}" HOME_DIR="${HOME_DIR:-sunshine/sunshine.AppImage.home/}" - mkdir -p temp_appimage && cd temp_appimage - wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage && chmod +x linuxdeploy-x86_64.AppImage - ./linuxdeploy-x86_64.AppImage --appdir AppDir -e ../appimage-build/sunshine -i "../$ICON_FILE" -d "../$DESKTOP_FILE" --output appimage + + ./linuxdeploy-x86_64.AppImage --appdir ../AppDir -e ../appimage-build/sunshine -i "../$ICON_FILE" -d "../$DESKTOP_FILE" --output appimage mv sunshine*.AppImage sunshine.AppImage mkdir sunshine && mv sunshine.AppImage sunshine/ @@ -75,15 +76,15 @@ jobs: ./sunshine/sunshine.AppImage --appimage-portable-home cp -r ../assets/* "$CONFIG_DIR" rm -f "$CONFIG_DIR"/apps_windows.json - mkdir -p ./$HOME_DIR/.config/$CONFIG_DIR - cp ./$CONFIG_DIR/apps_linux.json ./$HOME_DIR/.config/$CONFIG_DIR + mkdir -p ./"$HOME_DIR"/.config/"$CONFIG_DIR" + cp ./"$CONFIG_DIR"/apps_linux.json ./"$HOME_DIR"/.config/"$CONFIG_DIR" zip -r ./sunshine_linux.zip ./sunshine/* - cd .. - mv ./temp_appimage/sunshine_linux.zip ./artifacts/sunshine_linux.zip + mv sunshine_linux.zip ../artifacts/ - name: Verify AppImage run: | - wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage && chmod +x appimagelint-x86_64.AppImage && ./appimagelint-x86_64.AppImage .temp_appimage/sunshine/sunshine.AppImage + cd appimage_temp + wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage && chmod +x appimagelint-x86_64.AppImage && ./appimagelint-x86_64.AppImage ./sunshine/sunshine.AppImage - name: Upload Artifacts uses: actions/upload-artifact@v2 with: From 89cfbc6bd3c7a53c5513814f370f243eaa2b394f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 17 Dec 2021 14:15:23 -0500 Subject: [PATCH 009/817] Update pull-requests_build-check.yml -Checkout recursively with submodules --- .github/workflows/pull-requests_build-check.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index ac7bf50b..b94c7740 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -23,7 +23,11 @@ jobs: # needs: check_branch steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: true + - name: Setup Dependencies run: | mkdir -p artifacts @@ -40,7 +44,7 @@ jobs: sudo apt-get update -y sudo apt-get upgrade -y && sudo apt-get dist-upgrade -y sudo apt-get install ffmpeg -y - - name: Build Sunshine for AppImage + - name: Build AppImage run: | CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}" SUNSHINE_EXECUTABLE_PATH="${SUNSHINE_EXECUTABLE_PATH:-/usr/bin/sunshine}" @@ -56,7 +60,7 @@ jobs: cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" "../" -DCMAKE_INSTALL_PREFIX=/usr make -j ${nproc} DESTDIR=AppDir - - name: Build AppImage + - name: Package AppImage # https://docs.appimage.org/packaging-guide/index.html run: | mkdir -p appimage_temp && cd appimage_temp From f272b865cd5e796811cffedb5071a52b57b8e811 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 17 Dec 2021 17:36:11 -0500 Subject: [PATCH 010/817] Update pull-requests_build-check.yml -recursive submodules -only run on PR for master or nightly branch --- .github/workflows/pull-requests_build-check.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index b94c7740..90326429 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -2,6 +2,7 @@ name: Build test on: pull_request: + branches: [master, nightly] types: [opened, synchronize, edited, reopened] workflow_dispatch: @@ -26,7 +27,7 @@ jobs: - name: Checkout uses: actions/checkout@v2 with: - submodules: true + submodules: recursive - name: Setup Dependencies run: | From 502bf8ebf879b40bb81ebcaae7ae68ae195b8862 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 17 Dec 2021 22:00:37 -0500 Subject: [PATCH 011/817] Update pull-requests_build-check.yml -Add deb job with matrix strategy --- .../workflows/pull-requests_build-check.yml | 53 +++++++++++++------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 90326429..5f842283 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -7,21 +7,9 @@ on: workflow_dispatch: jobs: -# check_branch: -# runs-on: ubuntu-latest -# -# steps: -# - name: Fail Workflow -# if: github.base_ref != 'nightly' -# run: | -# echo Base: "$GITHUB_BASE_REF" -# echo Head: "$GITHUB_HEAD_REF" -# exit 1 - - build_linux: - name: Linux + build_appimage: + name: AppImage runs-on: ubuntu-20.04 -# needs: check_branch steps: - name: Checkout @@ -29,7 +17,7 @@ jobs: with: submodules: recursive - - name: Setup Dependencies + - name: Setup Dependencies AppImage run: | mkdir -p artifacts @@ -93,5 +81,38 @@ jobs: - name: Upload Artifacts uses: actions/upload-artifact@v2 with: - name: Artifacts + name: sunshine-AppImage path: artifacts/ + + build_deb: + name: deb + runs-on: ubuntu-20.04 + strategy: + matrix: + distro: [ 2004, 2104, debian ] + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: recursive + + - name: Setup Docker + run: | + mkdir -p artifacts + + cd scripts + ./build-container.sh -f Dockerfile-${{ matrix.distro }} + - name: Build and Package deb + run: | + cd scripts + ./build-sunshine -p -s .. + + cd sunshine-build + mv sunshine.deb sunshine-${{ matrix.distro }}.deb + mv sunshine-${{ matrix.distro }}.deb ../artifacts/ + - name: Upload Artifacts + uses: actions/upload-artifact@v2 + with: + name: sunshine-${{ matrix.distro }} + path: artifacts/ From 47a7c5e27b90118557750dc1876507fb6266e76f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 17 Dec 2021 22:25:22 -0500 Subject: [PATCH 012/817] Fix typo in docker build readme --- .github/workflows/pull-requests_build-check.yml | 2 +- scripts/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 5f842283..a13c6ff5 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -106,7 +106,7 @@ jobs: - name: Build and Package deb run: | cd scripts - ./build-sunshine -p -s .. + ./build-sunshine.sh -p -s .. cd sunshine-build mv sunshine.deb sunshine-${{ matrix.distro }}.deb diff --git a/scripts/README.md b/scripts/README.md index 5c2068d4..16e99ac8 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -32,13 +32,13 @@ cd scripts Then, the sources will be compiled and the debian package generated: ``` -./build-sunshine -p -s .. +./build-sunshine.sh -p -s .. ``` You can run `build-sunshine -p -s ..` again as long as the docker container exists. ``` git pull -./build-sunshine -p -s .. +./build-sunshine.sh -p -s .. ``` Optionally, the docker container can be removed after you're finished: From 69642d2db387a6833494688daa1cce9edb0d8cbb Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 11:03:56 -0500 Subject: [PATCH 013/817] Update pull-requests_build-check.yml --- .../workflows/pull-requests_build-check.yml | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index a13c6ff5..4c340d26 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -79,6 +79,7 @@ jobs: cd appimage_temp wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage && chmod +x appimagelint-x86_64.AppImage && ./appimagelint-x86_64.AppImage ./sunshine/sunshine.AppImage - name: Upload Artifacts + if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v2 with: name: sunshine-AppImage @@ -97,21 +98,28 @@ jobs: with: submodules: recursive - - name: Setup Docker + - name: Build run: | mkdir -p artifacts cd scripts - ./build-container.sh -f Dockerfile-${{ matrix.distro }} - - name: Build and Package deb - run: | - cd scripts - ./build-sunshine.sh -p -s .. + sudo ./build-container.sh -f Dockerfile-${{ matrix.distro }} -n sunshine-${{ matrix.distro }} + + ls cd sunshine-build mv sunshine.deb sunshine-${{ matrix.distro }}.deb mv sunshine-${{ matrix.distro }}.deb ../artifacts/ +# - name: Build and Package deb +# run: | +# cd scripts +# sudo ./build-sunshine.sh -p -n sunshine-${{ matrix.distro }} -s .. +# +# cd sunshine-build +# mv sunshine.deb sunshine-${{ matrix.distro }}.deb +# mv sunshine-${{ matrix.distro }}.deb ../artifacts/ - name: Upload Artifacts + if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v2 with: name: sunshine-${{ matrix.distro }} From 23f9474e9e766f813bd04a7ff863d63eb0c7dead Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 11:34:49 -0500 Subject: [PATCH 014/817] Update pull-requests_build-check.yml --- .github/workflows/pull-requests_build-check.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 4c340d26..869be41c 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -103,11 +103,13 @@ jobs: mkdir -p artifacts cd scripts - sudo ./build-container.sh -f Dockerfile-${{ matrix.distro }} -n sunshine-${{ matrix.distro }} + sudo ./build-container.sh -c compile -f Dockerfile-${{ matrix.distro }} -n sunshine-${{ matrix.distro }} ls + echo "...." + ls .. - cd sunshine-build + cd sunshine-${{ matrix.distro }}-build mv sunshine.deb sunshine-${{ matrix.distro }}.deb mv sunshine-${{ matrix.distro }}.deb ../artifacts/ # - name: Build and Package deb From bbdf9618ea0c1dea5e1691af450e842bc32edc93 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 11:41:06 -0500 Subject: [PATCH 015/817] Update pull-requests_build-check.yml --- .github/workflows/pull-requests_build-check.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 869be41c..85148cd6 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -103,6 +103,7 @@ jobs: mkdir -p artifacts cd scripts + sudo ./build-container.sh -c build -f Dockerfile-${{ matrix.distro }} -n sunshine-${{ matrix.distro }} sudo ./build-container.sh -c compile -f Dockerfile-${{ matrix.distro }} -n sunshine-${{ matrix.distro }} ls From ca8917dd1b59f9742df88b89af2082ccbfc127a8 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 16:31:45 -0500 Subject: [PATCH 016/817] Update pull-requests_build-check.yml -build with -u for non interactive --- .github/workflows/pull-requests_build-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 85148cd6..7a586a10 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -104,7 +104,7 @@ jobs: cd scripts sudo ./build-container.sh -c build -f Dockerfile-${{ matrix.distro }} -n sunshine-${{ matrix.distro }} - sudo ./build-container.sh -c compile -f Dockerfile-${{ matrix.distro }} -n sunshine-${{ matrix.distro }} + sudo ./build-sunshine.sh -p -u -n sunshine-${{ matrix.distro }} -s .. ls echo "...." From 65b9b653d057b19d5eab6c5a48419e3fa82df702 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 16:51:49 -0500 Subject: [PATCH 017/817] Update pull-requests_build-check.yml -Fix command that moves deb package -Separate setup, build, and package steps --- .../workflows/pull-requests_build-check.yml | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 7a586a10..159e2655 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -98,29 +98,20 @@ jobs: with: submodules: recursive - - name: Build + - name: Setup Container deb run: | mkdir -p artifacts cd scripts sudo ./build-container.sh -c build -f Dockerfile-${{ matrix.distro }} -n sunshine-${{ matrix.distro }} + - name: Build deb + run: | + cd scripts sudo ./build-sunshine.sh -p -u -n sunshine-${{ matrix.distro }} -s .. - - ls - echo "...." - ls .. - - cd sunshine-${{ matrix.distro }}-build - mv sunshine.deb sunshine-${{ matrix.distro }}.deb - mv sunshine-${{ matrix.distro }}.deb ../artifacts/ -# - name: Build and Package deb -# run: | -# cd scripts -# sudo ./build-sunshine.sh -p -n sunshine-${{ matrix.distro }} -s .. -# -# cd sunshine-build -# mv sunshine.deb sunshine-${{ matrix.distro }}.deb -# mv sunshine-${{ matrix.distro }}.deb ../artifacts/ + - name: Package deb + run: | + cd scripts + mv ./sunshine-${{ matrix.distro }}-build/sunshine-${{ matrix.distro }}.deb ../artifacts/ - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v2 From b73ddc232b563aa5e2086d60f16bdc3329e0944d Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 17:01:54 -0500 Subject: [PATCH 018/817] Update pull-requests_build-check.yml -Fix permission denied error when moving deb package --- .github/workflows/pull-requests_build-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 159e2655..6cc6d9cb 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -111,7 +111,7 @@ jobs: - name: Package deb run: | cd scripts - mv ./sunshine-${{ matrix.distro }}-build/sunshine-${{ matrix.distro }}.deb ../artifacts/ + sudo mv ./sunshine-${{ matrix.distro }}-build/sunshine-${{ matrix.distro }}.deb ../artifacts/ - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v2 From 256188618985214418cb7fc7f6d16075ee886bb7 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 17:36:39 -0500 Subject: [PATCH 019/817] Update pull-requests_build-check.yml -Add Windows build --- .../workflows/pull-requests_build-check.yml | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 6cc6d9cb..f3c92ded 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -118,3 +118,40 @@ jobs: with: name: sunshine-${{ matrix.distro }} path: artifacts/ + + build_win: + name: Windows + runs-on: windows-2019 + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: recursive + + - name: Setup Windows + run: | + pacman --needed --noconfirm -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make + - name: Build Windows + run: | + mkdir sunshine-windows-build && cd sunshine-windows-build + set PATH=C:\msys64\mingw64\bin + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + mingw32-make -j2 + - name: Package Windows + run: | + cd sunshine-windows-build + Del ..\assets\apps_linux.json + 7z a Sunshine-Windows.zip ..\assets + 7z a Sunshine-Windows.zip sunshine.exe + 7z a Sunshine-Windows.zip tools\dxgi-info.exe + 7z a Sunshine-Windows.zip tools\audio-info.exe + 7z a Sunshine-Windows.zip tools\sunshinesvc.exe + 7z a Sunshine-Windows.zip ..\tools\install-service.bat + 7z a Sunshine-Windows.zip ..\tools\uninstall-service.bat + - name: Upload Artifacts + if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} + uses: actions/upload-artifact@v2 + with: + name: Windows + path: sunshine-windows-build/Sunshine-Windows.zip From 0fa68397b76fc50f23d9a18f43b2dde556eea133 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 17:39:49 -0500 Subject: [PATCH 020/817] Update pull-requests_build-check.yml -Fix path issue for pacman command --- .github/workflows/pull-requests_build-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index f3c92ded..3666ddcc 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -131,7 +131,7 @@ jobs: - name: Setup Windows run: | - pacman --needed --noconfirm -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make + C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make" - name: Build Windows run: | mkdir sunshine-windows-build && cd sunshine-windows-build From 71b54955699f7eff96a1adc379320d4037e762bd Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 17:59:30 -0500 Subject: [PATCH 021/817] Update pull-requests_build-check.yml -Boost debug on --- .github/workflows/pull-requests_build-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 3666ddcc..fbc5e914 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -136,7 +136,7 @@ jobs: run: | mkdir sunshine-windows-build && cd sunshine-windows-build set PATH=C:\msys64\mingw64\bin - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + cmake -DBoost_DEBUG=ON -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. mingw32-make -j2 - name: Package Windows run: | From f6d9061441b8173071729125e485cb4e21f45b89 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 18:16:27 -0500 Subject: [PATCH 022/817] Update pull-requests_build-check.yml -Install boost -Remove boost debug during cmake --- .github/workflows/pull-requests_build-check.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index fbc5e914..77d56345 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -129,6 +129,21 @@ jobs: with: submodules: recursive + - name: Install boost + uses: MarkusJx/install-boost@v2.0.0 + id: install-boost + with: + # REQUIRED: Specify the required boost version + # A list of supported versions can be found here: + # https://github.com/actions/boost-versions/blob/main/versions-manifest.json + boost_version: 1.73.0 + # OPTIONAL: Specify a custom installation location + boost_install_dir: C:\boost + # OPTIONAL: Specify a platform version + platform_version: 2016 + + # NOTE: If a boost version matching all requirements cannot be found, + # this build step will fail - name: Setup Windows run: | C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make" @@ -136,7 +151,7 @@ jobs: run: | mkdir sunshine-windows-build && cd sunshine-windows-build set PATH=C:\msys64\mingw64\bin - cmake -DBoost_DEBUG=ON -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. mingw32-make -j2 - name: Package Windows run: | From 2b0e1fb9dcfc6e50bb92780c595258398d791c7c Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 18:21:12 -0500 Subject: [PATCH 023/817] Update pull-requests_build-check.yml -Fix boost install directory --- .github/workflows/pull-requests_build-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 77d56345..4a2335dc 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -138,7 +138,7 @@ jobs: # https://github.com/actions/boost-versions/blob/main/versions-manifest.json boost_version: 1.73.0 # OPTIONAL: Specify a custom installation location - boost_install_dir: C:\boost + boost_install_dir: 'C:' # OPTIONAL: Specify a platform version platform_version: 2016 From de8cff072d5e59da8773a8da27e11492201aca12 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 18:41:58 -0500 Subject: [PATCH 024/817] Update pull-requests_build-check.yml -Add environment variable BOOST_ROOT --- .github/workflows/pull-requests_build-check.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 4a2335dc..69209a5e 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -148,6 +148,8 @@ jobs: run: | C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make" - name: Build Windows + env: + BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }} run: | mkdir sunshine-windows-build && cd sunshine-windows-build set PATH=C:\msys64\mingw64\bin From c49cbd3c3caeefccc87b8058c240da021d5e38dd Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 19:15:05 -0500 Subject: [PATCH 025/817] Update pull-requests_build-check.yml --- .github/workflows/pull-requests_build-check.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 69209a5e..0d8424cf 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -136,11 +136,11 @@ jobs: # REQUIRED: Specify the required boost version # A list of supported versions can be found here: # https://github.com/actions/boost-versions/blob/main/versions-manifest.json - boost_version: 1.73.0 + boost_version: 1.77.0 # OPTIONAL: Specify a custom installation location - boost_install_dir: 'C:' + # boost_install_dir: 'C:' # OPTIONAL: Specify a platform version - platform_version: 2016 + platform_version: 2019 # NOTE: If a boost version matching all requirements cannot be found, # this build step will fail @@ -153,7 +153,7 @@ jobs: run: | mkdir sunshine-windows-build && cd sunshine-windows-build set PATH=C:\msys64\mingw64\bin - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + cmake -DBOOST_ROOT=${{ steps.install-boost.outputs.BOOST_ROOT }} -DBOOST_INCLUDEDIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include -DBOOST_LIBRARYDIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. mingw32-make -j2 - name: Package Windows run: | From 214478760b647f5e7c1c6992fe996bb38faa5f41 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 19:37:32 -0500 Subject: [PATCH 026/817] Update pull-requests_build-check.yml -Try older version of boost, new version failing with cmake 3.22 --- .github/workflows/pull-requests_build-check.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 0d8424cf..a4d8a283 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -130,17 +130,17 @@ jobs: submodules: recursive - name: Install boost - uses: MarkusJx/install-boost@v2.0.0 + uses: MarkusJx/install-boost@v1.0.1 id: install-boost with: # REQUIRED: Specify the required boost version # A list of supported versions can be found here: # https://github.com/actions/boost-versions/blob/main/versions-manifest.json - boost_version: 1.77.0 - # OPTIONAL: Specify a custom installation location - # boost_install_dir: 'C:' - # OPTIONAL: Specify a platform version - platform_version: 2019 + boost_version: 1.70.0 + # OPTIONAL: Specify a toolset on windows + toolset: msvc14.1 + # OPTIONAL: Specify a custom install location + #boost_install_dir: C:\some_directory # NOTE: If a boost version matching all requirements cannot be found, # this build step will fail From 1039160d3a9f33beba185d1a6752e6872082e86a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 20:57:11 -0500 Subject: [PATCH 027/817] Update pull-requests_build-check.yml -Try boost v1.73.0 built on windows server 2019 --- .github/workflows/pull-requests_build-check.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index a4d8a283..ea7ad4bc 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -130,17 +130,17 @@ jobs: submodules: recursive - name: Install boost - uses: MarkusJx/install-boost@v1.0.1 + uses: MarkusJx/install-boost@v2.0.0 id: install-boost with: # REQUIRED: Specify the required boost version # A list of supported versions can be found here: # https://github.com/actions/boost-versions/blob/main/versions-manifest.json - boost_version: 1.70.0 - # OPTIONAL: Specify a toolset on windows - toolset: msvc14.1 - # OPTIONAL: Specify a custom install location - #boost_install_dir: C:\some_directory + boost_version: 1.73.0 + # OPTIONAL: Specify a custon install location + #boost_install_dir: 'C:' + # OPTIONAL: Specify a platform version + platform_version: 2019 # NOTE: If a boost version matching all requirements cannot be found, # this build step will fail @@ -153,7 +153,8 @@ jobs: run: | mkdir sunshine-windows-build && cd sunshine-windows-build set PATH=C:\msys64\mingw64\bin - cmake -DBOOST_ROOT=${{ steps.install-boost.outputs.BOOST_ROOT }} -DBOOST_INCLUDEDIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include -DBOOST_LIBRARYDIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + #-DBOOST_ROOT=${{ steps.install-boost.outputs.BOOST_ROOT }} -DBOOST_INCLUDEDIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include -DBOOST_LIBRARYDIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib mingw32-make -j2 - name: Package Windows run: | From 9fbfca5699112f604c6b409e3260fb4d8ba0dd6b Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 21:19:23 -0500 Subject: [PATCH 028/817] Update pull-requests_build-check.yml -Try boost 1.72.0 --- .github/workflows/pull-requests_build-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index ea7ad4bc..df3efca3 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -136,7 +136,7 @@ jobs: # REQUIRED: Specify the required boost version # A list of supported versions can be found here: # https://github.com/actions/boost-versions/blob/main/versions-manifest.json - boost_version: 1.73.0 + boost_version: 1.72.0 # OPTIONAL: Specify a custon install location #boost_install_dir: 'C:' # OPTIONAL: Specify a platform version From 2512e7f445ae5f98d4b7e2f5f1e72042c217fce3 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 22:22:27 -0500 Subject: [PATCH 029/817] Update pull-requests_build-check.yml -Testing --- .github/workflows/pull-requests_build-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index df3efca3..02a0b2d8 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -153,7 +153,7 @@ jobs: run: | mkdir sunshine-windows-build && cd sunshine-windows-build set PATH=C:\msys64\mingw64\bin - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + cmake -DBoost_NO_BOOST_CMAKE=ON -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. #-DBOOST_ROOT=${{ steps.install-boost.outputs.BOOST_ROOT }} -DBOOST_INCLUDEDIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include -DBOOST_LIBRARYDIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib mingw32-make -j2 - name: Package Windows From 3f2ee64293792ec39f1607744cba7bb49dd3e93d Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 22:39:07 -0500 Subject: [PATCH 030/817] Update pull-requests_build-check.yml -Testing --- .github/workflows/pull-requests_build-check.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 02a0b2d8..b2a4e65c 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -153,7 +153,8 @@ jobs: run: | mkdir sunshine-windows-build && cd sunshine-windows-build set PATH=C:\msys64\mingw64\bin - cmake -DBoost_NO_BOOST_CMAKE=ON -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + #cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + cmake -DBOOST_INCLUDEDIR=C:\msys64\mingw64\include\boost -DBOOST_LIBRARYDIR=C:\msys64\mingw64\lib -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. #-DBOOST_ROOT=${{ steps.install-boost.outputs.BOOST_ROOT }} -DBOOST_INCLUDEDIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include -DBOOST_LIBRARYDIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib mingw32-make -j2 - name: Package Windows From ed38b7e86c090e8a32d5aff2d39cf4e4c7b0eb04 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 23:35:41 -0500 Subject: [PATCH 031/817] Update pull-requests_build-check.yml -Testing --- .github/workflows/pull-requests_build-check.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index b2a4e65c..09c4c753 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -152,10 +152,11 @@ jobs: BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }} run: | mkdir sunshine-windows-build && cd sunshine-windows-build - set PATH=C:\msys64\mingw64\bin - #cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. - cmake -DBOOST_INCLUDEDIR=C:\msys64\mingw64\include\boost -DBOOST_LIBRARYDIR=C:\msys64\mingw64\lib -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. - #-DBOOST_ROOT=${{ steps.install-boost.outputs.BOOST_ROOT }} -DBOOST_INCLUDEDIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include -DBOOST_LIBRARYDIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib + set PATH=%PATH%;C:\msys64\mingw64\bin + set BOOST_ROOT=${{ steps.install-boost.outputs.BOOST_ROOT }} + set BOOST_INCLUDEDIR=${{ steps.install-boost.outputs.BOOST_ROOT }} + set BOOST_LIBRARYDIR=${{ steps.install-boost.outputs.BOOST_ROOT }}\lib + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. mingw32-make -j2 - name: Package Windows run: | From ff47a13bc3b12b79785d7a14de4bffcdbf138219 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Dec 2021 23:44:02 -0500 Subject: [PATCH 032/817] Update pull-requests_build-check.yml --- .github/workflows/pull-requests_build-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 09c4c753..f8c11834 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -152,7 +152,7 @@ jobs: BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }} run: | mkdir sunshine-windows-build && cd sunshine-windows-build - set PATH=%PATH%;C:\msys64\mingw64\bin + set PATH=C:\msys64\mingw64\bin set BOOST_ROOT=${{ steps.install-boost.outputs.BOOST_ROOT }} set BOOST_INCLUDEDIR=${{ steps.install-boost.outputs.BOOST_ROOT }} set BOOST_LIBRARYDIR=${{ steps.install-boost.outputs.BOOST_ROOT }}\lib From 17770fe130a982c41a93fd174a4cdb68cc21b310 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 00:04:20 -0500 Subject: [PATCH 033/817] Update pull-requests_build-check.yml --- .github/workflows/pull-requests_build-check.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index f8c11834..0b9d5f7b 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -136,9 +136,9 @@ jobs: # REQUIRED: Specify the required boost version # A list of supported versions can be found here: # https://github.com/actions/boost-versions/blob/main/versions-manifest.json - boost_version: 1.72.0 - # OPTIONAL: Specify a custon install location - #boost_install_dir: 'C:' + boost_version: 1.77.0 + # OPTIONAL: Specify a custom install location + boost_install_dir: 'C:' # OPTIONAL: Specify a platform version platform_version: 2019 @@ -153,9 +153,13 @@ jobs: run: | mkdir sunshine-windows-build && cd sunshine-windows-build set PATH=C:\msys64\mingw64\bin - set BOOST_ROOT=${{ steps.install-boost.outputs.BOOST_ROOT }} - set BOOST_INCLUDEDIR=${{ steps.install-boost.outputs.BOOST_ROOT }} - set BOOST_LIBRARYDIR=${{ steps.install-boost.outputs.BOOST_ROOT }}\lib + set BOOST_ROOT=C:\boost + dir C:\boost + echo ----------- + set BOOST_INCLUDEDIR=C:\boost + set BOOST_LIBRARYDIR=C:\boost\lib + dir C:\boost\lib + echo ----------- cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. mingw32-make -j2 - name: Package Windows From 4406f7428b45f706cc4a1533d86262fc78fc668b Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 00:11:15 -0500 Subject: [PATCH 034/817] Update pull-requests_build-check.yml --- .github/workflows/pull-requests_build-check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 0b9d5f7b..c698e741 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -154,11 +154,11 @@ jobs: mkdir sunshine-windows-build && cd sunshine-windows-build set PATH=C:\msys64\mingw64\bin set BOOST_ROOT=C:\boost - dir C:\boost + dir C:\boost\boost echo ----------- set BOOST_INCLUDEDIR=C:\boost set BOOST_LIBRARYDIR=C:\boost\lib - dir C:\boost\lib + dir C:\boost\boost\lib echo ----------- cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. mingw32-make -j2 From a5e2df11ebfa681d6f16b9560f5e2787aa6660e0 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 00:42:34 -0500 Subject: [PATCH 035/817] Update pull-requests_build-check.yml --- .github/workflows/pull-requests_build-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index c698e741..4e870034 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -160,7 +160,7 @@ jobs: set BOOST_LIBRARYDIR=C:\boost\lib dir C:\boost\boost\lib echo ----------- - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + cmake -DBoost_USE_STATIC_LIBS=ON -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. mingw32-make -j2 - name: Package Windows run: | From 23e64f23a84d1518f3bc892de75f6321e2314297 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 10:40:11 -0500 Subject: [PATCH 036/817] Add additional distros --- .../workflows/pull-requests_build-check.yml | 9 +++------ appveyor.yml | 4 ++-- scripts/Dockerfile-Fedora_33 | 13 +++++++++++++ scripts/Dockerfile-Fedora_35 | 14 ++++++++++++++ ...Dockerfile-2004 => Dockerfile-Ubuntu_18_04} | 2 +- scripts/Dockerfile-Ubuntu_20_04 | 18 ++++++++++++++++++ ...Dockerfile-2104 => Dockerfile-Ubuntu_21_04} | 2 +- scripts/Dockerfile-Ubuntu_21_10 | 13 +++++++++++++ 8 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 scripts/Dockerfile-Fedora_33 create mode 100644 scripts/Dockerfile-Fedora_35 rename scripts/{Dockerfile-2004 => Dockerfile-Ubuntu_18_04} (95%) create mode 100644 scripts/Dockerfile-Ubuntu_20_04 rename scripts/{Dockerfile-2104 => Dockerfile-Ubuntu_21_04} (92%) create mode 100644 scripts/Dockerfile-Ubuntu_21_10 diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 4e870034..3eb511ac 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -89,8 +89,9 @@ jobs: name: deb runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: - distro: [ 2004, 2104, debian ] + distro: [ debian, Fedora_33, Fedora_35, Ubuntu_18_04, Ubuntu_20_04, Ubuntu_21_04, Ubuntu_21_10 ] steps: - name: Checkout @@ -154,13 +155,9 @@ jobs: mkdir sunshine-windows-build && cd sunshine-windows-build set PATH=C:\msys64\mingw64\bin set BOOST_ROOT=C:\boost - dir C:\boost\boost - echo ----------- set BOOST_INCLUDEDIR=C:\boost set BOOST_LIBRARYDIR=C:\boost\lib - dir C:\boost\boost\lib - echo ----------- - cmake -DBoost_USE_STATIC_LIBS=ON -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. mingw32-make -j2 - name: Package Windows run: | diff --git a/appveyor.yml b/appveyor.yml index 7a418aaa..40d6b807 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,9 +4,9 @@ services: environment: matrix: - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 - DOCKERFILE: Dockerfile-2004 + DOCKERFILE: Dockerfile-Ubuntu_20_04 - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 - DOCKERFILE: Dockerfile-2104 + DOCKERFILE: Dockerfile-Ubuntu_21_04 - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 DOCKERFILE: Dockerfile-debian - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 diff --git a/scripts/Dockerfile-Fedora_33 b/scripts/Dockerfile-Fedora_33 new file mode 100644 index 00000000..48507f64 --- /dev/null +++ b/scripts/Dockerfile-Fedora_33 @@ -0,0 +1,13 @@ +FROM fedora:33 AS sunshine-fedora_33 + +ARG DEBIAN_FRONTEND=noninteractive +ARG TZ="Europe/London" + +RUN dnf group install "Development Tools" \ + dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm \ + dnf install -y openssl-devel ffmpeg-devel boost-devel boost-static.x86_64 pulseaudio-libs-devel opus-devel libXtst-devel libX11-devel libXfixes-devel libevdev-devel libxcb-devel cmake + +COPY build-private.sh /root/build.sh + + +ENTRYPOINT ["/root/build.sh"] diff --git a/scripts/Dockerfile-Fedora_35 b/scripts/Dockerfile-Fedora_35 new file mode 100644 index 00000000..c311cd65 --- /dev/null +++ b/scripts/Dockerfile-Fedora_35 @@ -0,0 +1,14 @@ +FROM fedora:33 AS sunshine-fedora_33 + +ARG DEBIAN_FRONTEND=noninteractive +ARG TZ="Europe/London" + +RUN dnf group install "Development Tools" \ + dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm \ + dnf install -y openssl-devel ffmpeg-devel boost-devel boost-static.x86_64 pulseaudio-libs-devel opus-devel libXtst-devel libX11-devel libXfixes-devel libevdev-devel libxcb-devel cmake \ + dnf install libXcursor-devel libXrandr-devel libXinerama-devel libXi-devel mesa-libGL-devel + +COPY build-private.sh /root/build.sh + + +ENTRYPOINT ["/root/build.sh"] diff --git a/scripts/Dockerfile-2004 b/scripts/Dockerfile-Ubuntu_18_04 similarity index 95% rename from scripts/Dockerfile-2004 rename to scripts/Dockerfile-Ubuntu_18_04 index fe4dc326..b3d44bbe 100644 --- a/scripts/Dockerfile-2004 +++ b/scripts/Dockerfile-Ubuntu_18_04 @@ -1,4 +1,4 @@ -FROM ubuntu:20.04 AS sunshine-2004 +FROM ubuntu:18.04 AS sunshine-ubuntu_18_04 ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" diff --git a/scripts/Dockerfile-Ubuntu_20_04 b/scripts/Dockerfile-Ubuntu_20_04 new file mode 100644 index 00000000..ea69f455 --- /dev/null +++ b/scripts/Dockerfile-Ubuntu_20_04 @@ -0,0 +1,18 @@ +FROM ubuntu:20.04 AS sunshine-ubuntu_20_04 + +ARG DEBIAN_FRONTEND=noninteractive +ARG TZ="Europe/London" + +RUN apt-get update -y && \ + apt-get install -y \ + git wget gcc-10 g++-10 build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev + +RUN cp /usr/bin/gcc-10 /usr/bin/gcc && cp /usr/bin/g++-10 /usr/bin/gcc-10 + +RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run +RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm /root/cuda.run + +COPY build-private.sh /root/build.sh + + +ENTRYPOINT ["/root/build.sh"] diff --git a/scripts/Dockerfile-2104 b/scripts/Dockerfile-Ubuntu_21_04 similarity index 92% rename from scripts/Dockerfile-2104 rename to scripts/Dockerfile-Ubuntu_21_04 index b8174a36..83c17483 100644 --- a/scripts/Dockerfile-2104 +++ b/scripts/Dockerfile-Ubuntu_21_04 @@ -1,4 +1,4 @@ -FROM ubuntu:21.04 AS sunshine-2104 +FROM ubuntu:21.04 AS sunshine-ubuntu_21_04 ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" diff --git a/scripts/Dockerfile-Ubuntu_21_10 b/scripts/Dockerfile-Ubuntu_21_10 new file mode 100644 index 00000000..a7f65f3b --- /dev/null +++ b/scripts/Dockerfile-Ubuntu_21_10 @@ -0,0 +1,13 @@ +FROM ubuntu:21.10 AS sunshine-ubuntu_21_10 + +ARG DEBIAN_FRONTEND=noninteractive +ARG TZ="Europe/London" + +RUN apt-get update -y && \ + apt-get install -y \ + git build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev nvidia-cuda-dev nvidia-cuda-toolkit + +COPY build-private.sh /root/build.sh + + +ENTRYPOINT ["/root/build.sh"] From 7d51a4bfbfa409bcb179c3eca0a5775b3822d78e Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 10:45:29 -0500 Subject: [PATCH 037/817] change linux names to lowercase --- .github/workflows/pull-requests_build-check.yml | 2 +- appveyor.yml | 4 ++-- scripts/{Dockerfile-Fedora_33 => Dockerfile-fedora_33} | 0 scripts/{Dockerfile-Fedora_35 => Dockerfile-fedora_35} | 0 scripts/{Dockerfile-Ubuntu_18_04 => Dockerfile-ubuntu_18_04} | 0 scripts/{Dockerfile-Ubuntu_20_04 => Dockerfile-ubuntu_20_04} | 0 scripts/{Dockerfile-Ubuntu_21_04 => Dockerfile-ubuntu_21_04} | 0 scripts/{Dockerfile-Ubuntu_21_10 => Dockerfile-ubuntu_21_10} | 0 8 files changed, 3 insertions(+), 3 deletions(-) rename scripts/{Dockerfile-Fedora_33 => Dockerfile-fedora_33} (100%) rename scripts/{Dockerfile-Fedora_35 => Dockerfile-fedora_35} (100%) rename scripts/{Dockerfile-Ubuntu_18_04 => Dockerfile-ubuntu_18_04} (100%) rename scripts/{Dockerfile-Ubuntu_20_04 => Dockerfile-ubuntu_20_04} (100%) rename scripts/{Dockerfile-Ubuntu_21_04 => Dockerfile-ubuntu_21_04} (100%) rename scripts/{Dockerfile-Ubuntu_21_10 => Dockerfile-ubuntu_21_10} (100%) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 3eb511ac..4d360ed6 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -91,7 +91,7 @@ jobs: strategy: fail-fast: false matrix: - distro: [ debian, Fedora_33, Fedora_35, Ubuntu_18_04, Ubuntu_20_04, Ubuntu_21_04, Ubuntu_21_10 ] + distro: [ debian, fedora_33, fedora_35, ubuntu_18_04, ubuntu_20_04, ubuntu_21_04, ubuntu_21_10 ] steps: - name: Checkout diff --git a/appveyor.yml b/appveyor.yml index 40d6b807..7fb08089 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,9 +4,9 @@ services: environment: matrix: - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 - DOCKERFILE: Dockerfile-Ubuntu_20_04 + DOCKERFILE: Dockerfile-ubuntu_20_04 - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 - DOCKERFILE: Dockerfile-Ubuntu_21_04 + DOCKERFILE: Dockerfile-ubuntu_21_04 - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 DOCKERFILE: Dockerfile-debian - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 diff --git a/scripts/Dockerfile-Fedora_33 b/scripts/Dockerfile-fedora_33 similarity index 100% rename from scripts/Dockerfile-Fedora_33 rename to scripts/Dockerfile-fedora_33 diff --git a/scripts/Dockerfile-Fedora_35 b/scripts/Dockerfile-fedora_35 similarity index 100% rename from scripts/Dockerfile-Fedora_35 rename to scripts/Dockerfile-fedora_35 diff --git a/scripts/Dockerfile-Ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 similarity index 100% rename from scripts/Dockerfile-Ubuntu_18_04 rename to scripts/Dockerfile-ubuntu_18_04 diff --git a/scripts/Dockerfile-Ubuntu_20_04 b/scripts/Dockerfile-ubuntu_20_04 similarity index 100% rename from scripts/Dockerfile-Ubuntu_20_04 rename to scripts/Dockerfile-ubuntu_20_04 diff --git a/scripts/Dockerfile-Ubuntu_21_04 b/scripts/Dockerfile-ubuntu_21_04 similarity index 100% rename from scripts/Dockerfile-Ubuntu_21_04 rename to scripts/Dockerfile-ubuntu_21_04 diff --git a/scripts/Dockerfile-Ubuntu_21_10 b/scripts/Dockerfile-ubuntu_21_10 similarity index 100% rename from scripts/Dockerfile-Ubuntu_21_10 rename to scripts/Dockerfile-ubuntu_21_10 From 143ca274f542039208e1f9c7ec5798ab315b90ec Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 11:06:06 -0500 Subject: [PATCH 038/817] Update Dockerfile-fedora_33 --- scripts/Dockerfile-fedora_33 | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/scripts/Dockerfile-fedora_33 b/scripts/Dockerfile-fedora_33 index 48507f64..cf0b138f 100644 --- a/scripts/Dockerfile-fedora_33 +++ b/scripts/Dockerfile-fedora_33 @@ -1,11 +1,9 @@ FROM fedora:33 AS sunshine-fedora_33 -ARG DEBIAN_FRONTEND=noninteractive -ARG TZ="Europe/London" - -RUN dnf group install "Development Tools" \ - dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm \ - dnf install -y openssl-devel ffmpeg-devel boost-devel boost-static.x86_64 pulseaudio-libs-devel opus-devel libXtst-devel libX11-devel libXfixes-devel libevdev-devel libxcb-devel cmake +RUN dnf -y update && \ + dnf -y group install "Development Tools" \ + dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm \ + dnf -y install openssl-devel ffmpeg-devel boost-devel boost-static.x86_64 pulseaudio-libs-devel opus-devel libXtst-devel libX11-devel libXfixes-devel libevdev-devel libxcb-devel cmake COPY build-private.sh /root/build.sh From 7b5ac1c86980cb459a90bb5421e5a76227981620 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 11:06:09 -0500 Subject: [PATCH 039/817] Update Dockerfile-fedora_35 --- scripts/Dockerfile-fedora_35 | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/scripts/Dockerfile-fedora_35 b/scripts/Dockerfile-fedora_35 index c311cd65..ef18d6f0 100644 --- a/scripts/Dockerfile-fedora_35 +++ b/scripts/Dockerfile-fedora_35 @@ -1,12 +1,10 @@ FROM fedora:33 AS sunshine-fedora_33 -ARG DEBIAN_FRONTEND=noninteractive -ARG TZ="Europe/London" - -RUN dnf group install "Development Tools" \ - dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm \ - dnf install -y openssl-devel ffmpeg-devel boost-devel boost-static.x86_64 pulseaudio-libs-devel opus-devel libXtst-devel libX11-devel libXfixes-devel libevdev-devel libxcb-devel cmake \ - dnf install libXcursor-devel libXrandr-devel libXinerama-devel libXi-devel mesa-libGL-devel +RUN dnf -y update && \ + dnf -y group install "Development Tools" \ + dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm \ + dnf -y install openssl-devel ffmpeg-devel boost-devel boost-static.x86_64 pulseaudio-libs-devel opus-devel libXtst-devel libX11-devel libXfixes-devel libevdev-devel libxcb-devel cmake \ + dnf -y install libXcursor-devel libXrandr-devel libXinerama-devel libXi-devel mesa-libGL-devel COPY build-private.sh /root/build.sh From 23c6e455fe0846b4ee0afa75c502e4d4e0e7ebb3 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 11:10:40 -0500 Subject: [PATCH 040/817] Update Dockerfile-ubuntu_18_04 -Fix gcc-10 --- scripts/Dockerfile-ubuntu_18_04 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index b3d44bbe..eec1a4ac 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -5,7 +5,10 @@ ARG TZ="Europe/London" RUN apt-get update -y && \ apt-get install -y \ - git wget gcc-10 g++-10 build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev + git wget g++-10 build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev \ + add-apt-repository ppa:ubuntu-toolchain-r/test && \ + apt-get update -y && \ + apt-get install -y gcc-10 RUN cp /usr/bin/gcc-10 /usr/bin/gcc && cp /usr/bin/g++-10 /usr/bin/gcc-10 From d1ab44912b2564d613db89e117693aeb833c3e0a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 13:23:06 -0500 Subject: [PATCH 041/817] Fix dockerfile --- scripts/Dockerfile-fedora_33 | 4 ++-- scripts/Dockerfile-fedora_35 | 6 +++--- scripts/Dockerfile-ubuntu_18_04 | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/Dockerfile-fedora_33 b/scripts/Dockerfile-fedora_33 index cf0b138f..793e64e4 100644 --- a/scripts/Dockerfile-fedora_33 +++ b/scripts/Dockerfile-fedora_33 @@ -1,8 +1,8 @@ FROM fedora:33 AS sunshine-fedora_33 RUN dnf -y update && \ - dnf -y group install "Development Tools" \ - dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm \ + dnf -y group install "Development Tools" && \ + dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm && \ dnf -y install openssl-devel ffmpeg-devel boost-devel boost-static.x86_64 pulseaudio-libs-devel opus-devel libXtst-devel libX11-devel libXfixes-devel libevdev-devel libxcb-devel cmake COPY build-private.sh /root/build.sh diff --git a/scripts/Dockerfile-fedora_35 b/scripts/Dockerfile-fedora_35 index ef18d6f0..80e39eec 100644 --- a/scripts/Dockerfile-fedora_35 +++ b/scripts/Dockerfile-fedora_35 @@ -1,9 +1,9 @@ FROM fedora:33 AS sunshine-fedora_33 RUN dnf -y update && \ - dnf -y group install "Development Tools" \ - dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm \ - dnf -y install openssl-devel ffmpeg-devel boost-devel boost-static.x86_64 pulseaudio-libs-devel opus-devel libXtst-devel libX11-devel libXfixes-devel libevdev-devel libxcb-devel cmake \ + dnf -y group install "Development Tools" && \ + dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm && \ + dnf -y install openssl-devel ffmpeg-devel boost-devel boost-static.x86_64 pulseaudio-libs-devel opus-devel libXtst-devel libX11-devel libXfixes-devel libevdev-devel libxcb-devel cmake && \ dnf -y install libXcursor-devel libXrandr-devel libXinerama-devel libXi-devel mesa-libGL-devel COPY build-private.sh /root/build.sh diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index eec1a4ac..2941a0b1 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -5,7 +5,7 @@ ARG TZ="Europe/London" RUN apt-get update -y && \ apt-get install -y \ - git wget g++-10 build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev \ + git wget g++-10 build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev && \ add-apt-repository ppa:ubuntu-toolchain-r/test && \ apt-get update -y && \ apt-get install -y gcc-10 From 5ff5d46ba555205986d2bea9487b854a634b2ff5 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 14:41:04 -0500 Subject: [PATCH 042/817] Cleanup dockerfiles --- scripts/Dockerfile-debian | 27 ++++++++++++++++++++++++- scripts/Dockerfile-fedora_33 | 18 +++++++++++++++-- scripts/Dockerfile-fedora_35 | 24 +++++++++++++++++++--- scripts/Dockerfile-ubuntu_18_04 | 35 ++++++++++++++++++++++++++++----- scripts/Dockerfile-ubuntu_20_04 | 27 ++++++++++++++++++++++++- scripts/Dockerfile-ubuntu_21_04 | 27 ++++++++++++++++++++++++- scripts/Dockerfile-ubuntu_21_10 | 27 ++++++++++++++++++++++++- 7 files changed, 171 insertions(+), 14 deletions(-) diff --git a/scripts/Dockerfile-debian b/scripts/Dockerfile-debian index 253a4d45..0444edc7 100644 --- a/scripts/Dockerfile-debian +++ b/scripts/Dockerfile-debian @@ -6,7 +6,32 @@ ARG TZ="Europe/London" RUN echo deb http://deb.debian.org/debian/ bullseye main contrib non-free | tee /etc/apt/sources.list.d/non-free.list RUN apt-get update -y && \ apt-get install -y \ - git build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev nvidia-cuda-dev nvidia-cuda-toolkit + build-essential \ + cmake \ + git \ + libavdevice-dev \ + libboost-thread-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + nvidia-cuda-dev \ + nvidia-cuda-toolkit \ + && \ + apt-get clean && \ + apt-get autoclean COPY build-private.sh /root/build.sh diff --git a/scripts/Dockerfile-fedora_33 b/scripts/Dockerfile-fedora_33 index 793e64e4..03075d04 100644 --- a/scripts/Dockerfile-fedora_33 +++ b/scripts/Dockerfile-fedora_33 @@ -2,8 +2,22 @@ FROM fedora:33 AS sunshine-fedora_33 RUN dnf -y update && \ dnf -y group install "Development Tools" && \ - dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm && \ - dnf -y install openssl-devel ffmpeg-devel boost-devel boost-static.x86_64 pulseaudio-libs-devel opus-devel libXtst-devel libX11-devel libXfixes-devel libevdev-devel libxcb-devel cmake + dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm \ + boost-devel \ + boost-static.x86_64 \ + cmake \ + ffmpeg-devel \ + libevdev-devel \ + libxcb-devel \ + libX11-devel \ + libXfixes-devel \ + libXtst-devel \ + openssl-devel \ + opus-devel \ + pulseaudio-libs-devel \ + && \ + dnf clean all && \ + rm -rf /var/cache/yum COPY build-private.sh /root/build.sh diff --git a/scripts/Dockerfile-fedora_35 b/scripts/Dockerfile-fedora_35 index 80e39eec..04628c09 100644 --- a/scripts/Dockerfile-fedora_35 +++ b/scripts/Dockerfile-fedora_35 @@ -2,9 +2,27 @@ FROM fedora:33 AS sunshine-fedora_33 RUN dnf -y update && \ dnf -y group install "Development Tools" && \ - dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm && \ - dnf -y install openssl-devel ffmpeg-devel boost-devel boost-static.x86_64 pulseaudio-libs-devel opus-devel libXtst-devel libX11-devel libXfixes-devel libevdev-devel libxcb-devel cmake && \ - dnf -y install libXcursor-devel libXrandr-devel libXinerama-devel libXi-devel mesa-libGL-devel + dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm \ + boost-devel \ + boost-static.x86_64 \ + cmake \ + ffmpeg-devel \ + libevdev-devel \ + libxcb-devel \ + libX11-devel \ + libXcursor-devel \ + libXfixes-devel \ + libXinerama-devel \ + libXi-devel \ + libXrandr-devel \ + libXtst-devel \ + mesa-libGL-devel \ + openssl-devel \ + opus-devel \ + pulseaudio-libs-devel \ + && \ + dnf clean all && \ + rm -rf /var/cache/yum COPY build-private.sh /root/build.sh diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 2941a0b1..1b8ffd18 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -3,12 +3,37 @@ FROM ubuntu:18.04 AS sunshine-ubuntu_18_04 ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" -RUN apt-get update -y && \ - apt-get install -y \ - git wget g++-10 build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev && \ - add-apt-repository ppa:ubuntu-toolchain-r/test && \ +RUN add-apt-repository ppa:ubuntu-toolchain-r/test && \ apt-get update -y && \ - apt-get install -y gcc-10 + apt-get install -y \ + build-essential \ + cmake \ + gcc-10 + git \ + g++-10 \ + libavdevice-dev \ + libboost-thread-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + software-properties-common \ + wget \ + && \ + apt-get clean && \ + apt-get autoclean RUN cp /usr/bin/gcc-10 /usr/bin/gcc && cp /usr/bin/g++-10 /usr/bin/gcc-10 diff --git a/scripts/Dockerfile-ubuntu_20_04 b/scripts/Dockerfile-ubuntu_20_04 index ea69f455..75169813 100644 --- a/scripts/Dockerfile-ubuntu_20_04 +++ b/scripts/Dockerfile-ubuntu_20_04 @@ -5,7 +5,32 @@ ARG TZ="Europe/London" RUN apt-get update -y && \ apt-get install -y \ - git wget gcc-10 g++-10 build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev + build-essential \ + cmake \ + git \ + g++-10 \ + libavdevice-dev \ + libboost-thread-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + wget \ + && \ + apt-get clean && \ + apt-get autoclean RUN cp /usr/bin/gcc-10 /usr/bin/gcc && cp /usr/bin/g++-10 /usr/bin/gcc-10 diff --git a/scripts/Dockerfile-ubuntu_21_04 b/scripts/Dockerfile-ubuntu_21_04 index 83c17483..1972e2be 100644 --- a/scripts/Dockerfile-ubuntu_21_04 +++ b/scripts/Dockerfile-ubuntu_21_04 @@ -5,7 +5,32 @@ ARG TZ="Europe/London" RUN apt-get update -y && \ apt-get install -y \ - git build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev nvidia-cuda-dev nvidia-cuda-toolkit + build-essential \ + cmake \ + git \ + libavdevice-dev \ + libboost-thread-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + nvidia-cuda-dev \ + nvidia-cuda-toolkit \ + && \ + apt-get clean && \ + apt-get autoclean COPY build-private.sh /root/build.sh diff --git a/scripts/Dockerfile-ubuntu_21_10 b/scripts/Dockerfile-ubuntu_21_10 index a7f65f3b..d5fab928 100644 --- a/scripts/Dockerfile-ubuntu_21_10 +++ b/scripts/Dockerfile-ubuntu_21_10 @@ -5,7 +5,32 @@ ARG TZ="Europe/London" RUN apt-get update -y && \ apt-get install -y \ - git build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev nvidia-cuda-dev nvidia-cuda-toolkit + build-essential \ + cmake \ + git \ + libavdevice-dev \ + libboost-thread-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + nvidia-cuda-dev \ + nvidia-cuda-toolkit \ + && \ + apt-get clean && \ + apt-get autoclean COPY build-private.sh /root/build.sh From 030269b5969d35f810b7cddb154b14a195de82cb Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 14:55:53 -0500 Subject: [PATCH 043/817] Cleanup dockerfiles --- scripts/Dockerfile-debian | 8 ++++---- scripts/Dockerfile-fedora_33 | 5 ++--- scripts/Dockerfile-fedora_35 | 5 ++--- scripts/Dockerfile-ubuntu_18_04 | 9 ++++----- scripts/Dockerfile-ubuntu_20_04 | 7 +++---- scripts/Dockerfile-ubuntu_21_04 | 7 +++---- scripts/Dockerfile-ubuntu_21_10 | 7 +++---- 7 files changed, 21 insertions(+), 27 deletions(-) diff --git a/scripts/Dockerfile-debian b/scripts/Dockerfile-debian index 0444edc7..fbcffd74 100644 --- a/scripts/Dockerfile-debian +++ b/scripts/Dockerfile-debian @@ -3,9 +3,10 @@ FROM debian:bullseye AS sunshine-debian ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" +SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN echo deb http://deb.debian.org/debian/ bullseye main contrib non-free | tee /etc/apt/sources.list.d/non-free.list RUN apt-get update -y && \ - apt-get install -y \ + apt-get install -y --no-install-recommends \ build-essential \ cmake \ git \ @@ -29,9 +30,8 @@ RUN apt-get update -y && \ libxtst-dev \ nvidia-cuda-dev \ nvidia-cuda-toolkit \ - && \ - apt-get clean && \ - apt-get autoclean + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* COPY build-private.sh /root/build.sh diff --git a/scripts/Dockerfile-fedora_33 b/scripts/Dockerfile-fedora_33 index 03075d04..af5a40be 100644 --- a/scripts/Dockerfile-fedora_33 +++ b/scripts/Dockerfile-fedora_33 @@ -15,9 +15,8 @@ RUN dnf -y update && \ openssl-devel \ opus-devel \ pulseaudio-libs-devel \ - && \ - dnf clean all && \ - rm -rf /var/cache/yum + && dnf clean all \ + && rm -rf /var/cache/yum COPY build-private.sh /root/build.sh diff --git a/scripts/Dockerfile-fedora_35 b/scripts/Dockerfile-fedora_35 index 04628c09..6596f561 100644 --- a/scripts/Dockerfile-fedora_35 +++ b/scripts/Dockerfile-fedora_35 @@ -20,9 +20,8 @@ RUN dnf -y update && \ openssl-devel \ opus-devel \ pulseaudio-libs-devel \ - && \ - dnf clean all && \ - rm -rf /var/cache/yum + && dnf clean all \ + && rm -rf /var/cache/yum COPY build-private.sh /root/build.sh diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 1b8ffd18..2ceac2b5 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -5,10 +5,10 @@ ARG TZ="Europe/London" RUN add-apt-repository ppa:ubuntu-toolchain-r/test && \ apt-get update -y && \ - apt-get install -y \ + apt-get install -y --no-install-recommends \ build-essential \ cmake \ - gcc-10 + gcc-10 \ git \ g++-10 \ libavdevice-dev \ @@ -31,9 +31,8 @@ RUN add-apt-repository ppa:ubuntu-toolchain-r/test && \ libxtst-dev \ software-properties-common \ wget \ - && \ - apt-get clean && \ - apt-get autoclean + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* RUN cp /usr/bin/gcc-10 /usr/bin/gcc && cp /usr/bin/g++-10 /usr/bin/gcc-10 diff --git a/scripts/Dockerfile-ubuntu_20_04 b/scripts/Dockerfile-ubuntu_20_04 index 75169813..e6baea1a 100644 --- a/scripts/Dockerfile-ubuntu_20_04 +++ b/scripts/Dockerfile-ubuntu_20_04 @@ -4,7 +4,7 @@ ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" RUN apt-get update -y && \ - apt-get install -y \ + apt-get install -y --no-install-recommends \ build-essential \ cmake \ git \ @@ -28,9 +28,8 @@ RUN apt-get update -y && \ libxrandr-dev \ libxtst-dev \ wget \ - && \ - apt-get clean && \ - apt-get autoclean + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* RUN cp /usr/bin/gcc-10 /usr/bin/gcc && cp /usr/bin/g++-10 /usr/bin/gcc-10 diff --git a/scripts/Dockerfile-ubuntu_21_04 b/scripts/Dockerfile-ubuntu_21_04 index 1972e2be..a584d6e3 100644 --- a/scripts/Dockerfile-ubuntu_21_04 +++ b/scripts/Dockerfile-ubuntu_21_04 @@ -4,7 +4,7 @@ ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" RUN apt-get update -y && \ - apt-get install -y \ + apt-get install -y --no-install-recommends \ build-essential \ cmake \ git \ @@ -28,9 +28,8 @@ RUN apt-get update -y && \ libxtst-dev \ nvidia-cuda-dev \ nvidia-cuda-toolkit \ - && \ - apt-get clean && \ - apt-get autoclean + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* COPY build-private.sh /root/build.sh diff --git a/scripts/Dockerfile-ubuntu_21_10 b/scripts/Dockerfile-ubuntu_21_10 index d5fab928..dc067231 100644 --- a/scripts/Dockerfile-ubuntu_21_10 +++ b/scripts/Dockerfile-ubuntu_21_10 @@ -4,7 +4,7 @@ ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" RUN apt-get update -y && \ - apt-get install -y \ + apt-get install -y --no-install-recommends \ build-essential \ cmake \ git \ @@ -28,9 +28,8 @@ RUN apt-get update -y && \ libxtst-dev \ nvidia-cuda-dev \ nvidia-cuda-toolkit \ - && \ - apt-get clean && \ - apt-get autoclean + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* COPY build-private.sh /root/build.sh From a622c1591ecd23d20c01a25fcd2a0d7cff8a5ecb Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 15:04:54 -0500 Subject: [PATCH 044/817] Rename job deb to Linux --- .github/workflows/pull-requests_build-check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 4d360ed6..bce55cfe 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -85,8 +85,8 @@ jobs: name: sunshine-AppImage path: artifacts/ - build_deb: - name: deb + build_linux: + name: Linux runs-on: ubuntu-20.04 strategy: fail-fast: false From 95baeed75ed2dfe6d20e42f651ca1adbd71dfa1d Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 15:05:23 -0500 Subject: [PATCH 045/817] Re-order RUN command --- scripts/Dockerfile-ubuntu_18_04 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 2ceac2b5..0ef8a085 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -3,7 +3,9 @@ FROM ubuntu:18.04 AS sunshine-ubuntu_18_04 ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" -RUN add-apt-repository ppa:ubuntu-toolchain-r/test && \ +RUN apt-get update -y && \ + apt-get install -y --no-install-recommends software-properties-common && \ + add-apt-repository ppa:ubuntu-toolchain-r/test && \ apt-get update -y && \ apt-get install -y --no-install-recommends \ build-essential \ @@ -29,7 +31,6 @@ RUN add-apt-repository ppa:ubuntu-toolchain-r/test && \ libxfixes-dev \ libxrandr-dev \ libxtst-dev \ - software-properties-common \ wget \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* From 906870d36f2a8e6a0c297ee6232dd8307f749f3b Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 15:23:11 -0500 Subject: [PATCH 046/817] Back to single line install command -Testing, dockerfiles fail when packages split into multiple lines --- scripts/Dockerfile-fedora_33 | 15 ++------------- scripts/Dockerfile-fedora_35 | 20 ++------------------ 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/scripts/Dockerfile-fedora_33 b/scripts/Dockerfile-fedora_33 index af5a40be..4e2c04de 100644 --- a/scripts/Dockerfile-fedora_33 +++ b/scripts/Dockerfile-fedora_33 @@ -2,19 +2,8 @@ FROM fedora:33 AS sunshine-fedora_33 RUN dnf -y update && \ dnf -y group install "Development Tools" && \ - dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm \ - boost-devel \ - boost-static.x86_64 \ - cmake \ - ffmpeg-devel \ - libevdev-devel \ - libxcb-devel \ - libX11-devel \ - libXfixes-devel \ - libXtst-devel \ - openssl-devel \ - opus-devel \ - pulseaudio-libs-devel \ + dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm && \ + dnf -y install boost-devel boost-static.x86_64 cmake ffmpeg-devel libevdev-devel libxcb-devel libX11-devel libXfixes-devel libXtst-devel openssl-devel opus-devel pulseaudio-libs-devel \ && dnf clean all \ && rm -rf /var/cache/yum diff --git a/scripts/Dockerfile-fedora_35 b/scripts/Dockerfile-fedora_35 index 6596f561..cec2c960 100644 --- a/scripts/Dockerfile-fedora_35 +++ b/scripts/Dockerfile-fedora_35 @@ -2,24 +2,8 @@ FROM fedora:33 AS sunshine-fedora_33 RUN dnf -y update && \ dnf -y group install "Development Tools" && \ - dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm \ - boost-devel \ - boost-static.x86_64 \ - cmake \ - ffmpeg-devel \ - libevdev-devel \ - libxcb-devel \ - libX11-devel \ - libXcursor-devel \ - libXfixes-devel \ - libXinerama-devel \ - libXi-devel \ - libXrandr-devel \ - libXtst-devel \ - mesa-libGL-devel \ - openssl-devel \ - opus-devel \ - pulseaudio-libs-devel \ + dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm && \ + dnf -y install boost-devel boost-static.x86_64 cmake ffmpeg-devel libevdev-devel libxcb-devel libX11-devel libXcursor-devel libXfixes-devel libXinerama-devel libXi-devel libXrandr-devel libXtst-devel mesa-libGL-devel openssl-devel opus-devel pulseaudio-libs-devel \ && dnf clean all \ && rm -rf /var/cache/yum From b41cbc8ab4937578c2977f0960fb7034e20925da Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 15:24:02 -0500 Subject: [PATCH 047/817] Fix fedora version --- scripts/Dockerfile-fedora_35 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Dockerfile-fedora_35 b/scripts/Dockerfile-fedora_35 index cec2c960..58b3f32e 100644 --- a/scripts/Dockerfile-fedora_35 +++ b/scripts/Dockerfile-fedora_35 @@ -1,4 +1,4 @@ -FROM fedora:33 AS sunshine-fedora_33 +FROM fedora:35 AS sunshine-fedora_35 RUN dnf -y update && \ dnf -y group install "Development Tools" && \ From 1e0db7df4e6875012745b73f998cd376963e18da Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 15:39:39 -0500 Subject: [PATCH 048/817] Rename deb to Linux --- .github/workflows/pull-requests_build-check.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index bce55cfe..c62ab5d8 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -99,17 +99,17 @@ jobs: with: submodules: recursive - - name: Setup Container deb + - name: Setup Container run: | mkdir -p artifacts cd scripts sudo ./build-container.sh -c build -f Dockerfile-${{ matrix.distro }} -n sunshine-${{ matrix.distro }} - - name: Build deb + - name: Build Linux run: | cd scripts sudo ./build-sunshine.sh -p -u -n sunshine-${{ matrix.distro }} -s .. - - name: Package deb + - name: Package Linux run: | cd scripts sudo mv ./sunshine-${{ matrix.distro }}-build/sunshine-${{ matrix.distro }}.deb ../artifacts/ From e07279707a81cd0ea381c753d037fee3bdfcbe53 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 15:40:12 -0500 Subject: [PATCH 049/817] Update dockerfiles -Fix spacing --- scripts/Dockerfile-fedora_33 | 13 ++++++++- scripts/Dockerfile-fedora_35 | 20 ++++++++++++-- scripts/Dockerfile-ubuntu_18_04 | 48 ++++++++++++++++----------------- scripts/Dockerfile-ubuntu_20_04 | 46 +++++++++++++++---------------- scripts/Dockerfile-ubuntu_21_04 | 46 +++++++++++++++---------------- scripts/Dockerfile-ubuntu_21_10 | 46 +++++++++++++++---------------- 6 files changed, 123 insertions(+), 96 deletions(-) diff --git a/scripts/Dockerfile-fedora_33 b/scripts/Dockerfile-fedora_33 index 4e2c04de..c0143e8c 100644 --- a/scripts/Dockerfile-fedora_33 +++ b/scripts/Dockerfile-fedora_33 @@ -3,7 +3,18 @@ FROM fedora:33 AS sunshine-fedora_33 RUN dnf -y update && \ dnf -y group install "Development Tools" && \ dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm && \ - dnf -y install boost-devel boost-static.x86_64 cmake ffmpeg-devel libevdev-devel libxcb-devel libX11-devel libXfixes-devel libXtst-devel openssl-devel opus-devel pulseaudio-libs-devel \ + boost-devel \ + boost-static.x86_64 \ + cmake \ + ffmpeg-devel \ + libevdev-devel \ + libxcb-devel \ + libX11-devel \ + libXfixes-devel \ + libXtst-devel \ + openssl-devel \ + opus-devel \ + pulseaudio-libs-devel \ && dnf clean all \ && rm -rf /var/cache/yum diff --git a/scripts/Dockerfile-fedora_35 b/scripts/Dockerfile-fedora_35 index 58b3f32e..2beca2f8 100644 --- a/scripts/Dockerfile-fedora_35 +++ b/scripts/Dockerfile-fedora_35 @@ -2,8 +2,24 @@ FROM fedora:35 AS sunshine-fedora_35 RUN dnf -y update && \ dnf -y group install "Development Tools" && \ - dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm && \ - dnf -y install boost-devel boost-static.x86_64 cmake ffmpeg-devel libevdev-devel libxcb-devel libX11-devel libXcursor-devel libXfixes-devel libXinerama-devel libXi-devel libXrandr-devel libXtst-devel mesa-libGL-devel openssl-devel opus-devel pulseaudio-libs-devel \ + dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm \ + boost-devel \ + boost-static.x86_64 \ + cmake \ + ffmpeg-devel \ + libevdev-devel \ + libxcb-devel \ + libX11-devel \ + libXcursor-devel \ + libXfixes-devel \ + libXinerama-devel \ + libXi-devel \ + libXrandr-devel \ + libXtst-devel \ + mesa-libGL-devel \ + openssl-devel \ + opus-devel \ + pulseaudio-libs-devel \ && dnf clean all \ && rm -rf /var/cache/yum diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 0ef8a085..2c732eb5 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -8,30 +8,30 @@ RUN apt-get update -y && \ add-apt-repository ppa:ubuntu-toolchain-r/test && \ apt-get update -y && \ apt-get install -y --no-install-recommends \ - build-essential \ - cmake \ - gcc-10 \ - git \ - g++-10 \ - libavdevice-dev \ - libboost-thread-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libcap-dev \ - libdrm-dev \ - libevdev-dev \ - libpulse-dev \ - libopus-dev \ - libssl-dev \ - libwayland-dev \ - libx11-dev \ - libxcb-shm0-dev \ - libxcb-xfixes0-dev \ - libxcb1-dev \ - libxfixes-dev \ - libxrandr-dev \ - libxtst-dev \ - wget \ + build-essential \ + cmake \ + gcc-10 \ + git \ + g++-10 \ + libavdevice-dev \ + libboost-thread-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + wget \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/scripts/Dockerfile-ubuntu_20_04 b/scripts/Dockerfile-ubuntu_20_04 index e6baea1a..713726a4 100644 --- a/scripts/Dockerfile-ubuntu_20_04 +++ b/scripts/Dockerfile-ubuntu_20_04 @@ -5,29 +5,29 @@ ARG TZ="Europe/London" RUN apt-get update -y && \ apt-get install -y --no-install-recommends \ - build-essential \ - cmake \ - git \ - g++-10 \ - libavdevice-dev \ - libboost-thread-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libcap-dev \ - libdrm-dev \ - libevdev-dev \ - libpulse-dev \ - libopus-dev \ - libssl-dev \ - libwayland-dev \ - libx11-dev \ - libxcb-shm0-dev \ - libxcb-xfixes0-dev \ - libxcb1-dev \ - libxfixes-dev \ - libxrandr-dev \ - libxtst-dev \ - wget \ + build-essential \ + cmake \ + git \ + g++-10 \ + libavdevice-dev \ + libboost-thread-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + wget \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/scripts/Dockerfile-ubuntu_21_04 b/scripts/Dockerfile-ubuntu_21_04 index a584d6e3..6c644d4e 100644 --- a/scripts/Dockerfile-ubuntu_21_04 +++ b/scripts/Dockerfile-ubuntu_21_04 @@ -5,29 +5,29 @@ ARG TZ="Europe/London" RUN apt-get update -y && \ apt-get install -y --no-install-recommends \ - build-essential \ - cmake \ - git \ - libavdevice-dev \ - libboost-thread-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libcap-dev \ - libdrm-dev \ - libevdev-dev \ - libpulse-dev \ - libopus-dev \ - libssl-dev \ - libwayland-dev \ - libx11-dev \ - libxcb-shm0-dev \ - libxcb-xfixes0-dev \ - libxcb1-dev \ - libxfixes-dev \ - libxrandr-dev \ - libxtst-dev \ - nvidia-cuda-dev \ - nvidia-cuda-toolkit \ + build-essential \ + cmake \ + git \ + libavdevice-dev \ + libboost-thread-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + nvidia-cuda-dev \ + nvidia-cuda-toolkit \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/scripts/Dockerfile-ubuntu_21_10 b/scripts/Dockerfile-ubuntu_21_10 index dc067231..faff3c1b 100644 --- a/scripts/Dockerfile-ubuntu_21_10 +++ b/scripts/Dockerfile-ubuntu_21_10 @@ -5,29 +5,29 @@ ARG TZ="Europe/London" RUN apt-get update -y && \ apt-get install -y --no-install-recommends \ - build-essential \ - cmake \ - git \ - libavdevice-dev \ - libboost-thread-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libcap-dev \ - libdrm-dev \ - libevdev-dev \ - libpulse-dev \ - libopus-dev \ - libssl-dev \ - libwayland-dev \ - libx11-dev \ - libxcb-shm0-dev \ - libxcb-xfixes0-dev \ - libxcb1-dev \ - libxfixes-dev \ - libxrandr-dev \ - libxtst-dev \ - nvidia-cuda-dev \ - nvidia-cuda-toolkit \ + build-essential \ + cmake \ + git \ + libavdevice-dev \ + libboost-thread-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + nvidia-cuda-dev \ + nvidia-cuda-toolkit \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* From c4838424db7dfc651052b52e69f0dfc8dcea842a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 16:02:13 -0500 Subject: [PATCH 050/817] Remove --no-install-recommends option --- scripts/Dockerfile-debian | 48 +++++++++++++++---------------- scripts/Dockerfile-ubuntu_18_04 | 50 ++++++++++++++++----------------- scripts/Dockerfile-ubuntu_20_04 | 48 +++++++++++++++---------------- scripts/Dockerfile-ubuntu_21_04 | 48 +++++++++++++++---------------- scripts/Dockerfile-ubuntu_21_10 | 48 +++++++++++++++---------------- 5 files changed, 121 insertions(+), 121 deletions(-) diff --git a/scripts/Dockerfile-debian b/scripts/Dockerfile-debian index fbcffd74..15f0d656 100644 --- a/scripts/Dockerfile-debian +++ b/scripts/Dockerfile-debian @@ -6,30 +6,30 @@ ARG TZ="Europe/London" SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN echo deb http://deb.debian.org/debian/ bullseye main contrib non-free | tee /etc/apt/sources.list.d/non-free.list RUN apt-get update -y && \ - apt-get install -y --no-install-recommends \ - build-essential \ - cmake \ - git \ - libavdevice-dev \ - libboost-thread-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libcap-dev \ - libdrm-dev \ - libevdev-dev \ - libpulse-dev \ - libopus-dev \ - libssl-dev \ - libwayland-dev \ - libx11-dev \ - libxcb-shm0-dev \ - libxcb-xfixes0-dev \ - libxcb1-dev \ - libxfixes-dev \ - libxrandr-dev \ - libxtst-dev \ - nvidia-cuda-dev \ - nvidia-cuda-toolkit \ + apt-get install -y \ + build-essential \ + cmake \ + git \ + libavdevice-dev \ + libboost-thread-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + nvidia-cuda-dev \ + nvidia-cuda-toolkit \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 2c732eb5..480589b9 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -7,31 +7,31 @@ RUN apt-get update -y && \ apt-get install -y --no-install-recommends software-properties-common && \ add-apt-repository ppa:ubuntu-toolchain-r/test && \ apt-get update -y && \ - apt-get install -y --no-install-recommends \ - build-essential \ - cmake \ - gcc-10 \ - git \ - g++-10 \ - libavdevice-dev \ - libboost-thread-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libcap-dev \ - libdrm-dev \ - libevdev-dev \ - libpulse-dev \ - libopus-dev \ - libssl-dev \ - libwayland-dev \ - libx11-dev \ - libxcb-shm0-dev \ - libxcb-xfixes0-dev \ - libxcb1-dev \ - libxfixes-dev \ - libxrandr-dev \ - libxtst-dev \ - wget \ + apt-get install -y \ + build-essential \ + cmake \ + gcc-10 \ + git \ + g++-10 \ + libavdevice-dev \ + libboost-thread-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + wget \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/scripts/Dockerfile-ubuntu_20_04 b/scripts/Dockerfile-ubuntu_20_04 index 713726a4..878e0f49 100644 --- a/scripts/Dockerfile-ubuntu_20_04 +++ b/scripts/Dockerfile-ubuntu_20_04 @@ -4,30 +4,30 @@ ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" RUN apt-get update -y && \ - apt-get install -y --no-install-recommends \ - build-essential \ - cmake \ - git \ - g++-10 \ - libavdevice-dev \ - libboost-thread-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libcap-dev \ - libdrm-dev \ - libevdev-dev \ - libpulse-dev \ - libopus-dev \ - libssl-dev \ - libwayland-dev \ - libx11-dev \ - libxcb-shm0-dev \ - libxcb-xfixes0-dev \ - libxcb1-dev \ - libxfixes-dev \ - libxrandr-dev \ - libxtst-dev \ - wget \ + apt-get install -y \ + build-essential \ + cmake \ + git \ + g++-10 \ + libavdevice-dev \ + libboost-thread-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + wget \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/scripts/Dockerfile-ubuntu_21_04 b/scripts/Dockerfile-ubuntu_21_04 index 6c644d4e..d5668df4 100644 --- a/scripts/Dockerfile-ubuntu_21_04 +++ b/scripts/Dockerfile-ubuntu_21_04 @@ -4,30 +4,30 @@ ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" RUN apt-get update -y && \ - apt-get install -y --no-install-recommends \ - build-essential \ - cmake \ - git \ - libavdevice-dev \ - libboost-thread-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libcap-dev \ - libdrm-dev \ - libevdev-dev \ - libpulse-dev \ - libopus-dev \ - libssl-dev \ - libwayland-dev \ - libx11-dev \ - libxcb-shm0-dev \ - libxcb-xfixes0-dev \ - libxcb1-dev \ - libxfixes-dev \ - libxrandr-dev \ - libxtst-dev \ - nvidia-cuda-dev \ - nvidia-cuda-toolkit \ + apt-get install -y \ + build-essential \ + cmake \ + git \ + libavdevice-dev \ + libboost-thread-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + nvidia-cuda-dev \ + nvidia-cuda-toolkit \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/scripts/Dockerfile-ubuntu_21_10 b/scripts/Dockerfile-ubuntu_21_10 index faff3c1b..14770625 100644 --- a/scripts/Dockerfile-ubuntu_21_10 +++ b/scripts/Dockerfile-ubuntu_21_10 @@ -4,30 +4,30 @@ ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" RUN apt-get update -y && \ - apt-get install -y --no-install-recommends \ - build-essential \ - cmake \ - git \ - libavdevice-dev \ - libboost-thread-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libcap-dev \ - libdrm-dev \ - libevdev-dev \ - libpulse-dev \ - libopus-dev \ - libssl-dev \ - libwayland-dev \ - libx11-dev \ - libxcb-shm0-dev \ - libxcb-xfixes0-dev \ - libxcb1-dev \ - libxfixes-dev \ - libxrandr-dev \ - libxtst-dev \ - nvidia-cuda-dev \ - nvidia-cuda-toolkit \ + apt-get install -y \ + build-essential \ + cmake \ + git \ + libavdevice-dev \ + libboost-thread-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + nvidia-cuda-dev \ + nvidia-cuda-toolkit \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* From 975c4e6b2624734d0ab0f8a295604491e07ce962 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 16:03:08 -0500 Subject: [PATCH 051/817] Install rpmfusion as separate command --- scripts/Dockerfile-fedora_33 | 25 ++++++++++++------------ scripts/Dockerfile-fedora_35 | 37 ++++++++++++++++++------------------ 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/scripts/Dockerfile-fedora_33 b/scripts/Dockerfile-fedora_33 index c0143e8c..7dad9f57 100644 --- a/scripts/Dockerfile-fedora_33 +++ b/scripts/Dockerfile-fedora_33 @@ -3,18 +3,19 @@ FROM fedora:33 AS sunshine-fedora_33 RUN dnf -y update && \ dnf -y group install "Development Tools" && \ dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm && \ - boost-devel \ - boost-static.x86_64 \ - cmake \ - ffmpeg-devel \ - libevdev-devel \ - libxcb-devel \ - libX11-devel \ - libXfixes-devel \ - libXtst-devel \ - openssl-devel \ - opus-devel \ - pulseaudio-libs-devel \ + dnf -y install \ + boost-devel \ + boost-static.x86_64 \ + cmake \ + ffmpeg-devel \ + libevdev-devel \ + libxcb-devel \ + libX11-devel \ + libXfixes-devel \ + libXtst-devel \ + openssl-devel \ + opus-devel \ + pulseaudio-libs-devel \ && dnf clean all \ && rm -rf /var/cache/yum diff --git a/scripts/Dockerfile-fedora_35 b/scripts/Dockerfile-fedora_35 index 2beca2f8..1b52b5fa 100644 --- a/scripts/Dockerfile-fedora_35 +++ b/scripts/Dockerfile-fedora_35 @@ -2,24 +2,25 @@ FROM fedora:35 AS sunshine-fedora_35 RUN dnf -y update && \ dnf -y group install "Development Tools" && \ - dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm \ - boost-devel \ - boost-static.x86_64 \ - cmake \ - ffmpeg-devel \ - libevdev-devel \ - libxcb-devel \ - libX11-devel \ - libXcursor-devel \ - libXfixes-devel \ - libXinerama-devel \ - libXi-devel \ - libXrandr-devel \ - libXtst-devel \ - mesa-libGL-devel \ - openssl-devel \ - opus-devel \ - pulseaudio-libs-devel \ + dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm && \ + dnf -y install \ + boost-devel \ + boost-static.x86_64 \ + cmake \ + ffmpeg-devel \ + libevdev-devel \ + libxcb-devel \ + libX11-devel \ + libXcursor-devel \ + libXfixes-devel \ + libXinerama-devel \ + libXi-devel \ + libXrandr-devel \ + libXtst-devel \ + mesa-libGL-devel \ + openssl-devel \ + opus-devel \ + pulseaudio-libs-devel \ && dnf clean all \ && rm -rf /var/cache/yum From 7c96ee1e00697c2eccac6960ebd5fb93364a4e9a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 16:22:17 -0500 Subject: [PATCH 052/817] Fix cmake -Add repo for updated cmake --- scripts/Dockerfile-ubuntu_18_04 | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 480589b9..c4701312 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -6,6 +6,7 @@ ARG TZ="Europe/London" RUN apt-get update -y && \ apt-get install -y --no-install-recommends software-properties-common && \ add-apt-repository ppa:ubuntu-toolchain-r/test && \ + add-apt-repository ppa:george-edison55/cmake-3.x && \ apt-get update -y && \ apt-get install -y \ build-essential \ From 7c6fecf13d04f9bec65ac405b3a044c644fd2538 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 16:46:47 -0500 Subject: [PATCH 053/817] Fix cmake --- scripts/Dockerfile-ubuntu_18_04 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index c4701312..5e12f448 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -6,11 +6,9 @@ ARG TZ="Europe/London" RUN apt-get update -y && \ apt-get install -y --no-install-recommends software-properties-common && \ add-apt-repository ppa:ubuntu-toolchain-r/test && \ - add-apt-repository ppa:george-edison55/cmake-3.x && \ apt-get update -y && \ apt-get install -y \ build-essential \ - cmake \ gcc-10 \ git \ g++-10 \ @@ -36,6 +34,13 @@ RUN apt-get update -y && \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +RUN wget https://github.com/Kitware/CMake/releases/download/v3.22.1/cmake-3.22.1.tar.gz && \ + tar -zxvf cmake-3.22.1.tar.gz && \ + cd cmake-3.22.1 && \ + ./bootstrap && \ + make && \ + make install + RUN cp /usr/bin/gcc-10 /usr/bin/gcc && cp /usr/bin/g++-10 /usr/bin/gcc-10 RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run From 75cdac5dbf85118b74d30129a074ce718688258c Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 17:19:01 -0500 Subject: [PATCH 054/817] Update Dockerfile-ubuntu_18_04 -Remove cmake build -Test pipefail --- scripts/Dockerfile-ubuntu_18_04 | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 5e12f448..4a0982b4 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -3,12 +3,14 @@ FROM ubuntu:18.04 AS sunshine-ubuntu_18_04 ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" +SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN apt-get update -y && \ apt-get install -y --no-install-recommends software-properties-common && \ add-apt-repository ppa:ubuntu-toolchain-r/test && \ apt-get update -y && \ apt-get install -y \ build-essential \ + cmake \ gcc-10 \ git \ g++-10 \ @@ -34,13 +36,6 @@ RUN apt-get update -y && \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -RUN wget https://github.com/Kitware/CMake/releases/download/v3.22.1/cmake-3.22.1.tar.gz && \ - tar -zxvf cmake-3.22.1.tar.gz && \ - cd cmake-3.22.1 && \ - ./bootstrap && \ - make && \ - make install - RUN cp /usr/bin/gcc-10 /usr/bin/gcc && cp /usr/bin/g++-10 /usr/bin/gcc-10 RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run From 07b974d63842ddf42006fdac0fd4365863de24d7 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 17:47:35 -0500 Subject: [PATCH 055/817] Update Dockerfile-fedora_33 --- scripts/Dockerfile-fedora_33 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/Dockerfile-fedora_33 b/scripts/Dockerfile-fedora_33 index 7dad9f57..ae7e4959 100644 --- a/scripts/Dockerfile-fedora_33 +++ b/scripts/Dockerfile-fedora_33 @@ -1,5 +1,6 @@ FROM fedora:33 AS sunshine-fedora_33 +SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN dnf -y update && \ dnf -y group install "Development Tools" && \ dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm && \ @@ -12,6 +13,7 @@ RUN dnf -y update && \ libxcb-devel \ libX11-devel \ libXfixes-devel \ + libXrandr-devel \ libXtst-devel \ openssl-devel \ opus-devel \ From 26aff26eb05fd5a183b3fd2929463b6ae45b7c52 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 18:03:50 -0500 Subject: [PATCH 056/817] Update Dockerfile-fedora_35 -Add gcc-c++ package --- scripts/Dockerfile-fedora_35 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/Dockerfile-fedora_35 b/scripts/Dockerfile-fedora_35 index 1b52b5fa..0cde7aa4 100644 --- a/scripts/Dockerfile-fedora_35 +++ b/scripts/Dockerfile-fedora_35 @@ -1,5 +1,6 @@ FROM fedora:35 AS sunshine-fedora_35 +SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN dnf -y update && \ dnf -y group install "Development Tools" && \ dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm && \ @@ -8,6 +9,7 @@ RUN dnf -y update && \ boost-static.x86_64 \ cmake \ ffmpeg-devel \ + gcc-c++ \ libevdev-devel \ libxcb-devel \ libX11-devel \ From c3eabebd915bd5487803caf296385dc928abc180 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 18:35:02 -0500 Subject: [PATCH 057/817] Update Dockerfile-ubuntu_18_04 -Try different cmake repo --- scripts/Dockerfile-ubuntu_18_04 | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 4a0982b4..52a896a9 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -5,7 +5,13 @@ ARG TZ="Europe/London" SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN apt-get update -y && \ - apt-get install -y --no-install-recommends software-properties-common && \ + apt-get install -y \ + apt-transport-https \ + ca-certificates \ + gnupg \ + software-properties-common \ + && wget -qO - https://apt.kitware.com/keys/kitware-archive-latest.asc | sudo apt-key add - && \ + add-apt-repository 'deb https://apt.kitware.com/ubuntu/ bionic main' && \ add-apt-repository ppa:ubuntu-toolchain-r/test && \ apt-get update -y && \ apt-get install -y \ From 85cd54fdfe47a563bfb6fb3fa4ecf7e9290647d4 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 18:37:52 -0500 Subject: [PATCH 058/817] Update Dockerfile-ubuntu_18_04 -Move wget --- scripts/Dockerfile-ubuntu_18_04 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 52a896a9..a66dd134 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -10,7 +10,8 @@ RUN apt-get update -y && \ ca-certificates \ gnupg \ software-properties-common \ - && wget -qO - https://apt.kitware.com/keys/kitware-archive-latest.asc | sudo apt-key add - && \ + wget \ + && wget -qO - https://apt.kitware.com/keys/kitware-archive-latest.asc | apt-key add - && \ add-apt-repository 'deb https://apt.kitware.com/ubuntu/ bionic main' && \ add-apt-repository ppa:ubuntu-toolchain-r/test && \ apt-get update -y && \ @@ -38,7 +39,6 @@ RUN apt-get update -y && \ libxfixes-dev \ libxrandr-dev \ libxtst-dev \ - wget \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* From 83a4440cad6425ff0e75a663eb2221e1b3aa4d2f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 18:53:16 -0500 Subject: [PATCH 059/817] Update Dockerfile-ubuntu_18_04 -Add libboost-regex-dev --- scripts/Dockerfile-ubuntu_18_04 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index a66dd134..bff3432f 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -22,9 +22,10 @@ RUN apt-get update -y && \ git \ g++-10 \ libavdevice-dev \ - libboost-thread-dev \ libboost-filesystem-dev \ libboost-log-dev \ + libboost-regex-dev \ + libboost-thread-dev \ libcap-dev \ libdrm-dev \ libevdev-dev \ From 7ce9d27a67cfcb17da2335f6f6c49f81ed558157 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Dec 2021 19:20:20 -0500 Subject: [PATCH 060/817] Update Dockerfile-ubuntu_18_04 -Add ffmpeg --- scripts/Dockerfile-ubuntu_18_04 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index bff3432f..75662174 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -14,10 +14,14 @@ RUN apt-get update -y && \ && wget -qO - https://apt.kitware.com/keys/kitware-archive-latest.asc | apt-key add - && \ add-apt-repository 'deb https://apt.kitware.com/ubuntu/ bionic main' && \ add-apt-repository ppa:ubuntu-toolchain-r/test && \ + add-apt-repository ppa:savoury1/graphics && \ + add-apt-repository ppa:savoury1/multimedia && \ + add-apt-repository ppa:savoury1/ffmpeg4 && \ apt-get update -y && \ apt-get install -y \ build-essential \ cmake \ + ffmpeg \ gcc-10 \ git \ g++-10 \ From 9350afbe6a8690681ee297145df49ce81f026086 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 20 Dec 2021 15:52:01 -0500 Subject: [PATCH 061/817] Test boost compiled with mingw --- .github/workflows/pull-requests_build-check.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index c62ab5d8..c907c94e 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -131,17 +131,19 @@ jobs: submodules: recursive - name: Install boost - uses: MarkusJx/install-boost@v2.0.0 + #uses: MarkusJx/install-boost@v2.0.0 + uses: MarkusJx/install-boost@boost-mingw id: install-boost with: # REQUIRED: Specify the required boost version # A list of supported versions can be found here: # https://github.com/actions/boost-versions/blob/main/versions-manifest.json - boost_version: 1.77.0 + boost_version: 1.78.0 # OPTIONAL: Specify a custom install location boost_install_dir: 'C:' # OPTIONAL: Specify a platform version platform_version: 2019 + toolset: mingw # NOTE: If a boost version matching all requirements cannot be found, # this build step will fail From 8309ee965ae262b723e3ddd21badf8dd305a32a2 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 20 Dec 2021 17:38:42 -0500 Subject: [PATCH 062/817] Update pull-requests_build-check.yml -Add mingw-w64-libc++ --- .github/workflows/pull-requests_build-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index c907c94e..b6f2205a 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -149,7 +149,7 @@ jobs: # this build step will fail - name: Setup Windows run: | - C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make" + C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost mingw-w64-libc++ git yasm nasm diffutils make" - name: Build Windows env: BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }} From 0db8e634a8cf3ee59fb36195dd2d0270f168d552 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 20 Dec 2021 17:54:13 -0500 Subject: [PATCH 063/817] Update pull-requests_build-check.yml -Fix name of libc++ for windows build --- .github/workflows/pull-requests_build-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index b6f2205a..5e4aacb2 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -149,7 +149,7 @@ jobs: # this build step will fail - name: Setup Windows run: | - C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost mingw-w64-libc++ git yasm nasm diffutils make" + C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost mingw-w64-clang-x86_64-libc++ git yasm nasm diffutils make" - name: Build Windows env: BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }} From 4a1f5194cc0fccdf01182524f9bd44c8db5c4c83 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 20 Dec 2021 18:15:16 -0500 Subject: [PATCH 064/817] Update pull-requests_build-check.yml -Remove libc++ -Try setting compiler to c++17 --- .github/workflows/pull-requests_build-check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 5e4aacb2..47b13d34 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -149,7 +149,7 @@ jobs: # this build step will fail - name: Setup Windows run: | - C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost mingw-w64-clang-x86_64-libc++ git yasm nasm diffutils make" + C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make" - name: Build Windows env: BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }} @@ -159,7 +159,7 @@ jobs: set BOOST_ROOT=C:\boost set BOOST_INCLUDEDIR=C:\boost set BOOST_LIBRARYDIR=C:\boost\lib - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + cmake -DCMAKE_CXX_FLAGS=/std:c++17 -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. mingw32-make -j2 - name: Package Windows run: | From c844290c8108fd1dfc826d417f61a6d44f627578 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 20 Dec 2021 18:26:38 -0500 Subject: [PATCH 065/817] Update pull-requests_build-check.yml --- .github/workflows/pull-requests_build-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 47b13d34..b1613413 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -159,7 +159,7 @@ jobs: set BOOST_ROOT=C:\boost set BOOST_INCLUDEDIR=C:\boost set BOOST_LIBRARYDIR=C:\boost\lib - cmake -DCMAKE_CXX_FLAGS=/std:c++17 -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + cmake -DCMAKE_CXX_STANDARD=17 -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. mingw32-make -j2 - name: Package Windows run: | From 32867d1bbf0557192ab5a780baa0b23f57ae2698 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 20 Dec 2021 21:15:24 -0500 Subject: [PATCH 066/817] Update pull-requests_build-check.yml -Try Windows build with Unix Makefiles --- .../workflows/pull-requests_build-check.yml | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index b1613413..ca99ae20 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -130,36 +130,44 @@ jobs: with: submodules: recursive - - name: Install boost - #uses: MarkusJx/install-boost@v2.0.0 - uses: MarkusJx/install-boost@boost-mingw - id: install-boost - with: - # REQUIRED: Specify the required boost version - # A list of supported versions can be found here: - # https://github.com/actions/boost-versions/blob/main/versions-manifest.json - boost_version: 1.78.0 - # OPTIONAL: Specify a custom install location - boost_install_dir: 'C:' - # OPTIONAL: Specify a platform version - platform_version: 2019 - toolset: mingw - - # NOTE: If a boost version matching all requirements cannot be found, - # this build step will fail +# - name: Install boost +# #uses: MarkusJx/install-boost@v2.0.0 +# uses: MarkusJx/install-boost@boost-mingw +# id: install-boost +# with: +# # REQUIRED: Specify the required boost version +# # A list of supported versions can be found here: +# # https://github.com/actions/boost-versions/blob/main/versions-manifest.json +# boost_version: 1.78.0 +# # OPTIONAL: Specify a custom install location +# boost_install_dir: 'C:' +# # OPTIONAL: Specify a platform version +# platform_version: 2019 +# toolset: mingw +# +# # NOTE: If a boost version matching all requirements cannot be found, +# # this build step will fail - name: Setup Windows run: | C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make" +# - name: Build Windows +# env: +# BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }} +# run: | +# mkdir sunshine-windows-build && cd sunshine-windows-build +# set PATH=C:\msys64\mingw64\bin +# set BOOST_ROOT=C:\boost +# set BOOST_INCLUDEDIR=C:\boost +# set BOOST_LIBRARYDIR=C:\boost\lib +# cmake -DCMAKE_CXX_STANDARD=17 -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. +# mingw32-make -j2 - name: Build Windows env: BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }} run: | mkdir sunshine-windows-build && cd sunshine-windows-build set PATH=C:\msys64\mingw64\bin - set BOOST_ROOT=C:\boost - set BOOST_INCLUDEDIR=C:\boost - set BOOST_LIBRARYDIR=C:\boost\lib - cmake -DCMAKE_CXX_STANDARD=17 -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "Unix Makefiles" .. mingw32-make -j2 - name: Package Windows run: | From bc945df0a7ad311f7772349118ef44df80ce7296 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 20 Dec 2021 21:31:22 -0500 Subject: [PATCH 067/817] Update pull-requests_build-check.yml --- .github/workflows/pull-requests_build-check.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index ca99ae20..1b3f2dcd 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -130,6 +130,23 @@ jobs: with: submodules: recursive + - name: Install boost + #uses: MarkusJx/install-boost@v2.0.0 + uses: MarkusJx/install-boost@boost-mingw + id: install-boost + with: + # REQUIRED: Specify the required boost version + # A list of supported versions can be found here: + # https://github.com/actions/boost-versions/blob/main/versions-manifest.json + boost_version: 1.78.0 + # OPTIONAL: Specify a custom install location + boost_install_dir: 'C:' + # OPTIONAL: Specify a platform version + platform_version: 2019 + toolset: msvc + + # NOTE: If a boost version matching all requirements cannot be found, + # this build step will fail # - name: Install boost # #uses: MarkusJx/install-boost@v2.0.0 # uses: MarkusJx/install-boost@boost-mingw From 18a977fdf1eeb6e5097ff209bf6d857b029dcf45 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 20 Dec 2021 21:38:32 -0500 Subject: [PATCH 068/817] Update pull-requests_build-check.yml --- .../workflows/pull-requests_build-check.yml | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 1b3f2dcd..4288b74c 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -143,27 +143,10 @@ jobs: boost_install_dir: 'C:' # OPTIONAL: Specify a platform version platform_version: 2019 - toolset: msvc + toolset: mingw # NOTE: If a boost version matching all requirements cannot be found, # this build step will fail -# - name: Install boost -# #uses: MarkusJx/install-boost@v2.0.0 -# uses: MarkusJx/install-boost@boost-mingw -# id: install-boost -# with: -# # REQUIRED: Specify the required boost version -# # A list of supported versions can be found here: -# # https://github.com/actions/boost-versions/blob/main/versions-manifest.json -# boost_version: 1.78.0 -# # OPTIONAL: Specify a custom install location -# boost_install_dir: 'C:' -# # OPTIONAL: Specify a platform version -# platform_version: 2019 -# toolset: mingw -# -# # NOTE: If a boost version matching all requirements cannot be found, -# # this build step will fail - name: Setup Windows run: | C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make" From cc4ec1b526b85b3777b5c29a590579d709655527 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 20 Dec 2021 23:00:25 -0500 Subject: [PATCH 069/817] Update Dockerfile-ubuntu_18_04 --- scripts/Dockerfile-ubuntu_18_04 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 75662174..38429460 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -22,9 +22,9 @@ RUN apt-get update -y && \ build-essential \ cmake \ ffmpeg \ - gcc-10 \ + gcc-9 \ git \ - g++-10 \ + g++-9 \ libavdevice-dev \ libboost-filesystem-dev \ libboost-log-dev \ @@ -36,6 +36,7 @@ RUN apt-get update -y && \ libpulse-dev \ libopus-dev \ libssl-dev \ + libstdc++-9 \ libwayland-dev \ libx11-dev \ libxcb-shm0-dev \ @@ -47,7 +48,7 @@ RUN apt-get update -y && \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -RUN cp /usr/bin/gcc-10 /usr/bin/gcc && cp /usr/bin/g++-10 /usr/bin/gcc-10 +RUN cp /usr/bin/gcc-9 /usr/bin/gcc && cp /usr/bin/g++-9 /usr/bin/gcc-9 RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm /root/cuda.run From 8f78b599ae1baf09035c4c46f9ec16137c1443be Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 20 Dec 2021 23:23:26 -0500 Subject: [PATCH 070/817] Use update-alternatives for gcc-10 and g++-10 --- scripts/Dockerfile-ubuntu_18_04 | 7 +++---- scripts/Dockerfile-ubuntu_20_04 | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 38429460..5ee33ae3 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -22,9 +22,9 @@ RUN apt-get update -y && \ build-essential \ cmake \ ffmpeg \ - gcc-9 \ + gcc-10 \ git \ - g++-9 \ + g++-10 \ libavdevice-dev \ libboost-filesystem-dev \ libboost-log-dev \ @@ -36,7 +36,6 @@ RUN apt-get update -y && \ libpulse-dev \ libopus-dev \ libssl-dev \ - libstdc++-9 \ libwayland-dev \ libx11-dev \ libxcb-shm0-dev \ @@ -48,7 +47,7 @@ RUN apt-get update -y && \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -RUN cp /usr/bin/gcc-9 /usr/bin/gcc && cp /usr/bin/g++-9 /usr/bin/gcc-9 +RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm /root/cuda.run diff --git a/scripts/Dockerfile-ubuntu_20_04 b/scripts/Dockerfile-ubuntu_20_04 index 878e0f49..76775708 100644 --- a/scripts/Dockerfile-ubuntu_20_04 +++ b/scripts/Dockerfile-ubuntu_20_04 @@ -31,7 +31,8 @@ RUN apt-get update -y && \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -RUN cp /usr/bin/gcc-10 /usr/bin/gcc && cp /usr/bin/g++-10 /usr/bin/gcc-10 +# RUN cp /usr/bin/gcc-10 /usr/bin/gcc && cp /usr/bin/g++-10 /usr/bin/gcc-10 +RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm /root/cuda.run From 59394e23f41cd3a7924b141fc72af9c5e3764d86 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 20 Dec 2021 23:43:23 -0500 Subject: [PATCH 071/817] Update Dockerfile-ubuntu_18_04 --- scripts/Dockerfile-ubuntu_18_04 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 5ee33ae3..34e075c2 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -55,4 +55,4 @@ RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-m COPY build-private.sh /root/build.sh -ENTRYPOINT ["/root/build.sh"] +ENTRYPOINT ["/root/build.sh" -p] From 279fb8803e1cd26c63482d0af4ee46b052ebe535 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 20 Dec 2021 23:54:46 -0500 Subject: [PATCH 072/817] Update Dockerfile-ubuntu_18_04 --- scripts/Dockerfile-ubuntu_18_04 | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 34e075c2..dfeb039a 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -26,10 +26,7 @@ RUN apt-get update -y && \ git \ g++-10 \ libavdevice-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libboost-regex-dev \ - libboost-thread-dev \ + libboost-all-dev \ libcap-dev \ libdrm-dev \ libevdev-dev \ @@ -55,4 +52,4 @@ RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-m COPY build-private.sh /root/build.sh -ENTRYPOINT ["/root/build.sh" -p] +ENTRYPOINT ["/root/build.sh"] From aeb72cba0240fcd61449fa68e0164ce86437020c Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 21 Dec 2021 00:07:24 -0500 Subject: [PATCH 073/817] Update Dockerfile-ubuntu_18_04 --- scripts/Dockerfile-ubuntu_18_04 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index dfeb039a..5ee33ae3 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -26,7 +26,10 @@ RUN apt-get update -y && \ git \ g++-10 \ libavdevice-dev \ - libboost-all-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libboost-regex-dev \ + libboost-thread-dev \ libcap-dev \ libdrm-dev \ libevdev-dev \ From ea1e6f20a8faedb45095e978cbb44eaed128e491 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 21 Dec 2021 00:07:51 -0500 Subject: [PATCH 074/817] Update Dockerfile-ubuntu_20_04 --- scripts/Dockerfile-ubuntu_20_04 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Dockerfile-ubuntu_20_04 b/scripts/Dockerfile-ubuntu_20_04 index 76775708..ca457942 100644 --- a/scripts/Dockerfile-ubuntu_20_04 +++ b/scripts/Dockerfile-ubuntu_20_04 @@ -10,9 +10,9 @@ RUN apt-get update -y && \ git \ g++-10 \ libavdevice-dev \ - libboost-thread-dev \ libboost-filesystem-dev \ libboost-log-dev \ + libboost-thread-dev \ libcap-dev \ libdrm-dev \ libevdev-dev \ From 2fec2bfc51da0be10c87387e6495c65f7d6e6d46 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 21 Dec 2021 17:37:40 -0500 Subject: [PATCH 075/817] Update pull-requests_build-check.yml -Change version for MarkusJx/install-boost --- .github/workflows/pull-requests_build-check.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 4288b74c..fd8aac4c 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -131,8 +131,7 @@ jobs: submodules: recursive - name: Install boost - #uses: MarkusJx/install-boost@v2.0.0 - uses: MarkusJx/install-boost@boost-mingw + uses: MarkusJx/install-boost@v2.1.0 id: install-boost with: # REQUIRED: Specify the required boost version From a46a14c6ac21ac407c02b56989d38be096168b21 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 21 Dec 2021 18:08:52 -0500 Subject: [PATCH 076/817] Update Dockerfile-ubuntu_18_04 -Test building glibc 2.31 --- scripts/Dockerfile-ubuntu_18_04 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 5ee33ae3..25e5c7a6 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -52,6 +52,14 @@ RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave / RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm /root/cuda.run +RUN mkdir glibc-src && cd glibc-src && \ + wget http://ftp.gnu.org/gnu/libc/glibc-2.31.tar.gz && \ + tar -xvzf glibc-2.31.tar.gz && \ + mkdir build && cd build && \ + ../glibc-src/glibc-2.31/configure --prefix=/usr/glibc && \ + make && \ + make install && \ + COPY build-private.sh /root/build.sh From 8c37fa8d8b547a52eb4c0277f6ea6c3934a5700f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 21 Dec 2021 18:19:38 -0500 Subject: [PATCH 077/817] Update Dockerfile-ubuntu_18_04 --- scripts/Dockerfile-ubuntu_18_04 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 25e5c7a6..416a0843 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -58,7 +58,7 @@ RUN mkdir glibc-src && cd glibc-src && \ mkdir build && cd build && \ ../glibc-src/glibc-2.31/configure --prefix=/usr/glibc && \ make && \ - make install && \ + make install COPY build-private.sh /root/build.sh From b843ab7b97dcc00fc08a5949c4cc5d99ed9d696d Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 21 Dec 2021 18:33:59 -0500 Subject: [PATCH 078/817] Update Dockerfile-ubuntu_18_04 --- scripts/Dockerfile-ubuntu_18_04 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 416a0843..5b3b2de0 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -56,7 +56,7 @@ RUN mkdir glibc-src && cd glibc-src && \ wget http://ftp.gnu.org/gnu/libc/glibc-2.31.tar.gz && \ tar -xvzf glibc-2.31.tar.gz && \ mkdir build && cd build && \ - ../glibc-src/glibc-2.31/configure --prefix=/usr/glibc && \ + ../glibc-2.31/configure --prefix=/usr/glibc && \ make && \ make install From ee513939aab916264efdbb6bd6c0587fe527e8e0 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 21 Dec 2021 18:51:55 -0500 Subject: [PATCH 079/817] Update Dockerfile-ubuntu_18_04 --- scripts/Dockerfile-ubuntu_18_04 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 5b3b2de0..1f873991 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -52,7 +52,8 @@ RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave / RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm /root/cuda.run -RUN mkdir glibc-src && cd glibc-src && \ +RUN apt-get install -y gawk bison && \ + mkdir glibc-src && cd glibc-src && \ wget http://ftp.gnu.org/gnu/libc/glibc-2.31.tar.gz && \ tar -xvzf glibc-2.31.tar.gz && \ mkdir build && cd build && \ From 2baed357f2a39540ce84bff79e1049e12751e509 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 21 Dec 2021 19:10:27 -0500 Subject: [PATCH 080/817] Update Dockerfile-ubuntu_18_04 --- scripts/Dockerfile-ubuntu_18_04 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 1f873991..beb47c36 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -52,8 +52,13 @@ RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave / RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm /root/cuda.run -RUN apt-get install -y gawk bison && \ - mkdir glibc-src && cd glibc-src && \ +RUN apt-get update -y && \ + apt-get install -y \ + gawk \ + bison \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && mkdir glibc-src && cd glibc-src && \ wget http://ftp.gnu.org/gnu/libc/glibc-2.31.tar.gz && \ tar -xvzf glibc-2.31.tar.gz && \ mkdir build && cd build && \ From c910de12ff5ebc77b2fd0ea15a5b8dbbd05f37d8 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 21 Dec 2021 19:34:42 -0500 Subject: [PATCH 081/817] Update Dockerfile-ubuntu_18_04 --- scripts/Dockerfile-ubuntu_18_04 | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index beb47c36..5ee33ae3 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -52,20 +52,6 @@ RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave / RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm /root/cuda.run -RUN apt-get update -y && \ - apt-get install -y \ - gawk \ - bison \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* \ - && mkdir glibc-src && cd glibc-src && \ - wget http://ftp.gnu.org/gnu/libc/glibc-2.31.tar.gz && \ - tar -xvzf glibc-2.31.tar.gz && \ - mkdir build && cd build && \ - ../glibc-2.31/configure --prefix=/usr/glibc && \ - make && \ - make install - COPY build-private.sh /root/build.sh From 41906b6fabbb80829135d2e351f0459875b61604 Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Fri, 24 Dec 2021 11:27:40 +0100 Subject: [PATCH 082/817] Use msys2 for Windows Build --- .../workflows/pull-requests_build-check.yml | 52 +++++++------------ 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index fd8aac4c..1dc15c21 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -129,43 +129,29 @@ jobs: uses: actions/checkout@v2 with: submodules: recursive - - - name: Install boost - uses: MarkusJx/install-boost@v2.1.0 - id: install-boost + - name: MSYS2 Setup + uses: msys2/setup-msys2@v2 with: - # REQUIRED: Specify the required boost version - # A list of supported versions can be found here: - # https://github.com/actions/boost-versions/blob/main/versions-manifest.json - boost_version: 1.78.0 - # OPTIONAL: Specify a custom install location - boost_install_dir: 'C:' - # OPTIONAL: Specify a platform version - platform_version: 2019 - toolset: mingw - - # NOTE: If a boost version matching all requirements cannot be found, - # this build step will fail - - name: Setup Windows - run: | - C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make" -# - name: Build Windows -# env: -# BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }} -# run: | -# mkdir sunshine-windows-build && cd sunshine-windows-build -# set PATH=C:\msys64\mingw64\bin -# set BOOST_ROOT=C:\boost -# set BOOST_INCLUDEDIR=C:\boost -# set BOOST_LIBRARYDIR=C:\boost\lib -# cmake -DCMAKE_CXX_STANDARD=17 -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. -# mingw32-make -j2 + update: true + install: >- + base-devel + git + mingw-w64-x86_64-binutils + mingw-w64-x86_64-openssl + mingw-w64-x86_64-cmake + mingw-w64-x86_64-toolchain + mingw-w64-x86_64-opus + mingw-w64-x86_64-x265 + mingw-w64-x86_64-boost + git + yasm + nasm + diffutils + make - name: Build Windows - env: - BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }} + shell: msys2 {0} run: | mkdir sunshine-windows-build && cd sunshine-windows-build - set PATH=C:\msys64\mingw64\bin cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "Unix Makefiles" .. mingw32-make -j2 - name: Package Windows From 997738816d706e12119750d489bea46103df14a5 Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Fri, 24 Dec 2021 12:19:28 +0100 Subject: [PATCH 083/817] Update pull-requests_build-check.yml --- .github/workflows/pull-requests_build-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/pull-requests_build-check.yml index 1dc15c21..502c0967 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/pull-requests_build-check.yml @@ -152,7 +152,7 @@ jobs: shell: msys2 {0} run: | mkdir sunshine-windows-build && cd sunshine-windows-build - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "Unix Makefiles" .. + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. mingw32-make -j2 - name: Package Windows run: | From 6d2d3eca18f8c3bcd6a3125dfce05758d0f8783e Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Sun, 26 Dec 2021 23:32:34 +0100 Subject: [PATCH 084/817] Manually Imported Upstream PR #296 https://github.com/loki-47-6F-64/sunshine/pull/296 by psyke83 should fix the header issue by compiling --- sunshine/platform/windows/publish.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sunshine/platform/windows/publish.cpp b/sunshine/platform/windows/publish.cpp index d981593d..7fdef3fc 100644 --- a/sunshine/platform/windows/publish.cpp +++ b/sunshine/platform/windows/publish.cpp @@ -25,15 +25,18 @@ using namespace std::literals; #define SV(quote) __SV(quote) extern "C" { +#ifndef __MINGW32__ constexpr auto DNS_REQUEST_PENDING = 9506L; constexpr auto DNS_QUERY_REQUEST_VERSION1 = 0x1; constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1; +#endif #define SERVICE_DOMAIN "local" constexpr auto SERVICE_INSTANCE_NAME = SV(SERVICE_NAME "." SERVICE_TYPE "." SERVICE_DOMAIN); constexpr auto SERVICE_TYPE_DOMAIN = SV(SERVICE_TYPE "." SERVICE_DOMAIN); +#ifndef __MINGW32__ typedef struct _DNS_SERVICE_INSTANCE { LPWSTR pszInstanceName; LPWSTR pszHostName; @@ -53,6 +56,7 @@ typedef struct _DNS_SERVICE_INSTANCE { DWORD dwInterfaceIndex; } DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE; +#endif typedef VOID WINAPI DNS_SERVICE_REGISTER_COMPLETE( _In_ DWORD Status, @@ -61,6 +65,7 @@ typedef VOID WINAPI DNS_SERVICE_REGISTER_COMPLETE( typedef DNS_SERVICE_REGISTER_COMPLETE *PDNS_SERVICE_REGISTER_COMPLETE; +#ifndef __MINGW32__ typedef struct _DNS_SERVICE_CANCEL { PVOID reserved; } DNS_SERVICE_CANCEL, *PDNS_SERVICE_CANCEL; @@ -74,6 +79,7 @@ typedef struct _DNS_SERVICE_REGISTER_REQUEST { HANDLE hCredentials; BOOL unicastEnabled; } DNS_SERVICE_REGISTER_REQUEST, *PDNS_SERVICE_REGISTER_REQUEST; +#endif _FN(_DnsServiceFreeInstance, VOID, (_In_ PDNS_SERVICE_INSTANCE pInstance)); _FN(_DnsServiceDeRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel)); @@ -186,4 +192,4 @@ std::unique_ptr<::platf::deinit_t> start() { return std::make_unique(); } -} // namespace platf::publish \ No newline at end of file +} // namespace platf::publish From f6fd1f7e8480ad91881cc782f5045503c2fd2445 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 10 Jan 2022 21:11:44 -0500 Subject: [PATCH 085/817] Update and rename create_package.yml -Create release on push to master (fails if changelog version matches latest release) -Move windows package to artifacts folder -Use re-usable workflow create_release.yml --- ...sts_build-check.yml => create_package.yml} | 26 ++++++++++++++++--- scripts/Dockerfile-ubuntu_20_04 | 1 - 2 files changed, 22 insertions(+), 5 deletions(-) rename .github/workflows/{pull-requests_build-check.yml => create_package.yml} (89%) diff --git a/.github/workflows/pull-requests_build-check.yml b/.github/workflows/create_package.yml similarity index 89% rename from .github/workflows/pull-requests_build-check.yml rename to .github/workflows/create_package.yml index 502c0967..97cad4bd 100644 --- a/.github/workflows/pull-requests_build-check.yml +++ b/.github/workflows/create_package.yml @@ -1,9 +1,11 @@ -name: Build test +name: Create Package on: pull_request: branches: [master, nightly] types: [opened, synchronize, edited, reopened] + push: + branches: [ master ] workflow_dispatch: jobs: @@ -24,7 +26,7 @@ jobs: sudo apt-get update -y && \ sudo apt-get --reinstall install -y \ git wget gcc-10 g++-10 build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev - sudo cp /usr/bin/gcc-10 /usr/bin/gcc && sudo cp /usr/bin/g++-10 /usr/bin/gcc-10 + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 sudo wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && sudo chmod a+x /root/cuda.run sudo /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && sudo rm /root/cuda.run sudo add-apt-repository ppa:savoury1/graphics -y @@ -84,6 +86,11 @@ jobs: with: name: sunshine-AppImage path: artifacts/ + - name: Create Release + uses: .github/workflows/create_release.yml + secrets: + token: ${{ secrets.GITHUB_TOKEN }} + build_linux: name: Linux @@ -119,6 +126,10 @@ jobs: with: name: sunshine-${{ matrix.distro }} path: artifacts/ + - name: Create Release + uses: .github/workflows/create_release.yml + secrets: + token: ${{ secrets.GITHUB_TOKEN }} build_win: name: Windows @@ -165,9 +176,16 @@ jobs: 7z a Sunshine-Windows.zip tools\sunshinesvc.exe 7z a Sunshine-Windows.zip ..\tools\install-service.bat 7z a Sunshine-Windows.zip ..\tools\uninstall-service.bat + cd .. + mkdir artifacts + move "sunshine-windows-build/Sunshine-Windows.zip" "artifats" - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v2 with: - name: Windows - path: sunshine-windows-build/Sunshine-Windows.zip + name: sunshine-${{ runner.os }} + path: artifacts/ + - name: Create Release + uses: .github/workflows/create_release.yml + secrets: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/scripts/Dockerfile-ubuntu_20_04 b/scripts/Dockerfile-ubuntu_20_04 index ca457942..5a633a10 100644 --- a/scripts/Dockerfile-ubuntu_20_04 +++ b/scripts/Dockerfile-ubuntu_20_04 @@ -31,7 +31,6 @@ RUN apt-get update -y && \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -# RUN cp /usr/bin/gcc-10 /usr/bin/gcc && cp /usr/bin/g++-10 /usr/bin/gcc-10 RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run From 65fdf8f6d19033ea6e03fca4615923e35221ac0a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 10 Jan 2022 21:11:58 -0500 Subject: [PATCH 086/817] Create create_release.yml --- .github/workflows/create_release.yml | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/create_release.yml diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml new file mode 100644 index 00000000..e0c51484 --- /dev/null +++ b/.github/workflows/create_release.yml @@ -0,0 +1,44 @@ +name: Create Release + +on: + workflow_call: + secrets: + token: + required: true + +jobs: + create_release: + runs-on: ubuntu-latest + steps: + - name: Parse Changelog Entry + if: ${{ github.ref == 'refs/heads/master' }} + id: changelog + uses: coditory/changelog-parser@v1 # https://github.com/coditory/changelog-parser + + - name: Get last release + if: ${{ github.ref == 'refs/heads/master' }} + id: last_release + uses: InsonusK/get-latest-release@v1.0.1 # https://github.com/InsonusK/get-latest-release + with: + myToken: ${{ secrets.token }} + exclude_types: "draft|prerelease" + view_top: 1 + + - name: Changelog Version + if: ${{ github.ref == 'refs/heads/master' && ( steps.changelog.outputs.version == steps.last_release.tag_name ) }} + # fail the workflow because the versions match + run: | + echo Changelog Version: "${{ steps.changelog.outputs.version }}" + echo Last Released Version: "${{ steps.last_release.tag_name }}" + exit 1 + + - name: Create/Update GitHub Release + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + uses: ncipollo/release-action@v1 # https://github.com/ncipollo/release-action + with: + name: Release ${{ steps.changelog.outputs.version }} + tag: ${{ steps.changelog.outputs.version }} + artifacts: "./artifacts/*" + token: ${{ secrets.token }} + allowUpdated: true + body: ${{ steps.changelog.outputs.description }} From c579f638fcbb824fb2e5fda34982ae478ffae0c0 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 10 Jan 2022 21:12:04 -0500 Subject: [PATCH 087/817] Create CHANGELOG.md --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..314ecf81 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## [Unreleased] - 2022-01-10 +### Added +-Added something +### Changed +-Fixed something From a97c88c45b1e2d5fffecc5c17de5f7661f39e8f7 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 10 Jan 2022 21:15:34 -0500 Subject: [PATCH 088/817] Update create_package.yml -Fix typo in uses path --- .github/workflows/create_package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/create_package.yml b/.github/workflows/create_package.yml index 97cad4bd..ea39634b 100644 --- a/.github/workflows/create_package.yml +++ b/.github/workflows/create_package.yml @@ -87,7 +87,7 @@ jobs: name: sunshine-AppImage path: artifacts/ - name: Create Release - uses: .github/workflows/create_release.yml + uses: ./.github/workflows/create_release.yml secrets: token: ${{ secrets.GITHUB_TOKEN }} @@ -127,7 +127,7 @@ jobs: name: sunshine-${{ matrix.distro }} path: artifacts/ - name: Create Release - uses: .github/workflows/create_release.yml + uses: ./.github/workflows/create_release.yml secrets: token: ${{ secrets.GITHUB_TOKEN }} @@ -186,6 +186,6 @@ jobs: name: sunshine-${{ runner.os }} path: artifacts/ - name: Create Release - uses: .github/workflows/create_release.yml + uses: ./.github/workflows/create_release.yml secrets: token: ${{ secrets.GITHUB_TOKEN }} From 4fa26244956ebb8dae84bac53bcaf9b0fb48fb98 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 10 Jan 2022 21:17:20 -0500 Subject: [PATCH 089/817] Update create_package.yml -Initialize workflow --- .github/workflows/create_package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create_package.yml b/.github/workflows/create_package.yml index ea39634b..e02abf98 100644 --- a/.github/workflows/create_package.yml +++ b/.github/workflows/create_package.yml @@ -5,7 +5,7 @@ on: branches: [master, nightly] types: [opened, synchronize, edited, reopened] push: - branches: [ master ] + branches: [master, add-build-checks] workflow_dispatch: jobs: From 909e36b80d10a9b109bb904238a334d828de4e47 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 10 Jan 2022 21:20:36 -0500 Subject: [PATCH 090/817] Update create_package.yml -Use with instead of secrets on caller workflow --- .github/workflows/create_package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/create_package.yml b/.github/workflows/create_package.yml index e02abf98..94922e4b 100644 --- a/.github/workflows/create_package.yml +++ b/.github/workflows/create_package.yml @@ -88,7 +88,7 @@ jobs: path: artifacts/ - name: Create Release uses: ./.github/workflows/create_release.yml - secrets: + with: token: ${{ secrets.GITHUB_TOKEN }} @@ -128,7 +128,7 @@ jobs: path: artifacts/ - name: Create Release uses: ./.github/workflows/create_release.yml - secrets: + with: token: ${{ secrets.GITHUB_TOKEN }} build_win: @@ -187,5 +187,5 @@ jobs: path: artifacts/ - name: Create Release uses: ./.github/workflows/create_release.yml - secrets: + with: token: ${{ secrets.GITHUB_TOKEN }} From c2027a5481f3eb6b6b006b19a9e9740c836c7f85 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 10 Jan 2022 21:46:12 -0500 Subject: [PATCH 091/817] Remove token The documentation states "The called workflow is automatically granted access to `github.token` and `secrets.GITHUB_TOKEN`." --- .github/workflows/create_package.yml | 6 ------ .github/workflows/create_release.yml | 7 ++----- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/create_package.yml b/.github/workflows/create_package.yml index 94922e4b..77b40cb1 100644 --- a/.github/workflows/create_package.yml +++ b/.github/workflows/create_package.yml @@ -88,8 +88,6 @@ jobs: path: artifacts/ - name: Create Release uses: ./.github/workflows/create_release.yml - with: - token: ${{ secrets.GITHUB_TOKEN }} build_linux: @@ -128,8 +126,6 @@ jobs: path: artifacts/ - name: Create Release uses: ./.github/workflows/create_release.yml - with: - token: ${{ secrets.GITHUB_TOKEN }} build_win: name: Windows @@ -187,5 +183,3 @@ jobs: path: artifacts/ - name: Create Release uses: ./.github/workflows/create_release.yml - with: - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index e0c51484..c95a689f 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -2,9 +2,6 @@ name: Create Release on: workflow_call: - secrets: - token: - required: true jobs: create_release: @@ -20,7 +17,7 @@ jobs: id: last_release uses: InsonusK/get-latest-release@v1.0.1 # https://github.com/InsonusK/get-latest-release with: - myToken: ${{ secrets.token }} + myToken: ${{ secrets.GITHUB_TOKEN }} exclude_types: "draft|prerelease" view_top: 1 @@ -39,6 +36,6 @@ jobs: name: Release ${{ steps.changelog.outputs.version }} tag: ${{ steps.changelog.outputs.version }} artifacts: "./artifacts/*" - token: ${{ secrets.token }} + token: ${{ secrets.GITHUB_TOKEN }} allowUpdated: true body: ${{ steps.changelog.outputs.description }} From aa3137c0a9a3c738cb8419b66b32dd6ad2214c65 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 11 Jan 2022 00:04:13 -0500 Subject: [PATCH 092/817] Update create_package.yml -Replace create_release.yml with common create_release action --- .github/workflows/create_package.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/create_package.yml b/.github/workflows/create_package.yml index 77b40cb1..f38a11e3 100644 --- a/.github/workflows/create_package.yml +++ b/.github/workflows/create_package.yml @@ -87,7 +87,9 @@ jobs: name: sunshine-AppImage path: artifacts/ - name: Create Release - uses: ./.github/workflows/create_release.yml + uses: SunshineStream/.github/actions/create_release + with: + token: ${{ secrets.GITHUB_TOKEN }} build_linux: @@ -125,7 +127,9 @@ jobs: name: sunshine-${{ matrix.distro }} path: artifacts/ - name: Create Release - uses: ./.github/workflows/create_release.yml + uses: SunshineStream/.github/actions/create_release + with: + token: ${{ secrets.GITHUB_TOKEN }} build_win: name: Windows @@ -182,4 +186,6 @@ jobs: name: sunshine-${{ runner.os }} path: artifacts/ - name: Create Release - uses: ./.github/workflows/create_release.yml + uses: SunshineStream/.github/actions/create_release + with: + token: ${{ secrets.GITHUB_TOKEN }} From 7bfbdd5bc0d059cf6bc4bf9838daa8dac3f33022 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 11 Jan 2022 00:12:54 -0500 Subject: [PATCH 093/817] Update create_package.yml --- .github/workflows/create_package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/create_package.yml b/.github/workflows/create_package.yml index f38a11e3..0c307dde 100644 --- a/.github/workflows/create_package.yml +++ b/.github/workflows/create_package.yml @@ -87,7 +87,7 @@ jobs: name: sunshine-AppImage path: artifacts/ - name: Create Release - uses: SunshineStream/.github/actions/create_release + uses: SunshineStream/actions/create_release with: token: ${{ secrets.GITHUB_TOKEN }} @@ -127,7 +127,7 @@ jobs: name: sunshine-${{ matrix.distro }} path: artifacts/ - name: Create Release - uses: SunshineStream/.github/actions/create_release + uses: SunshineStream/actions/create_release with: token: ${{ secrets.GITHUB_TOKEN }} @@ -186,6 +186,6 @@ jobs: name: sunshine-${{ runner.os }} path: artifacts/ - name: Create Release - uses: SunshineStream/.github/actions/create_release + uses: SunshineStream/actions/create_release with: token: ${{ secrets.GITHUB_TOKEN }} From 51b7dc5b5c00691bc2d46858353805755e8fd362 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 11 Jan 2022 00:14:49 -0500 Subject: [PATCH 094/817] Update create_package.yml --- .github/workflows/create_package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/create_package.yml b/.github/workflows/create_package.yml index 0c307dde..9e73b9c7 100644 --- a/.github/workflows/create_package.yml +++ b/.github/workflows/create_package.yml @@ -87,7 +87,7 @@ jobs: name: sunshine-AppImage path: artifacts/ - name: Create Release - uses: SunshineStream/actions/create_release + uses: SunshineStream/actions/create_release@v0 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -127,7 +127,7 @@ jobs: name: sunshine-${{ matrix.distro }} path: artifacts/ - name: Create Release - uses: SunshineStream/actions/create_release + uses: SunshineStream/actions/create_release@v0 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -186,6 +186,6 @@ jobs: name: sunshine-${{ runner.os }} path: artifacts/ - name: Create Release - uses: SunshineStream/actions/create_release + uses: SunshineStream/actions/create_release@v0 with: token: ${{ secrets.GITHUB_TOKEN }} From 5dfd5d8027858e6ac37957df266d08eaef108877 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 11 Jan 2022 00:46:47 -0500 Subject: [PATCH 095/817] set -e Github not respecting -e in shebang --- scripts/build-container.sh | 3 ++- scripts/build-private.sh | 1 + scripts/build-sunshine.sh | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/build-container.sh b/scripts/build-container.sh index 69a8c99a..5da4784a 100755 --- a/scripts/build-container.sh +++ b/scripts/build-container.sh @@ -1,4 +1,5 @@ -#/bin/bash -e +#!/bin/bash -e +set -e usage() { echo "Usage: $0 [OPTIONS]" diff --git a/scripts/build-private.sh b/scripts/build-private.sh index 1526a754..ade64829 100755 --- a/scripts/build-private.sh +++ b/scripts/build-private.sh @@ -1,4 +1,5 @@ #!/bin/bash -e +set -e CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}" SUNSHINE_EXECUTABLE_PATH="${SUNSHINE_EXECUTABLE_PATH:-/usr/bin/sunshine}" diff --git a/scripts/build-sunshine.sh b/scripts/build-sunshine.sh index b49114bb..e8df86cd 100755 --- a/scripts/build-sunshine.sh +++ b/scripts/build-sunshine.sh @@ -1,4 +1,5 @@ -#/bin/bash -e +#!/bin/bash -e +set -e usage() { echo "Usage: $0" From b1030b6601b7b7de45f9cd40b00b97272f29c3c9 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 11 Jan 2022 01:13:29 -0500 Subject: [PATCH 096/817] Move gen-deb to Dockerfiles --- scripts/Dockerfile-debian | 2 ++ scripts/Dockerfile-ubuntu_18_04 | 2 ++ scripts/Dockerfile-ubuntu_20_04 | 2 ++ scripts/Dockerfile-ubuntu_21_04 | 2 ++ scripts/Dockerfile-ubuntu_21_10 | 2 ++ scripts/build-private.sh | 2 -- 6 files changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/Dockerfile-debian b/scripts/Dockerfile-debian index 15f0d656..1ed73c5f 100644 --- a/scripts/Dockerfile-debian +++ b/scripts/Dockerfile-debian @@ -37,3 +37,5 @@ COPY build-private.sh /root/build.sh ENTRYPOINT ["/root/build.sh"] + +ENTRYPOINT ["/root/sunshine-build/gen-deb"] diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 5ee33ae3..dad4c96b 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -56,3 +56,5 @@ COPY build-private.sh /root/build.sh ENTRYPOINT ["/root/build.sh"] + +ENTRYPOINT ["/root/sunshine-build/gen-deb"] diff --git a/scripts/Dockerfile-ubuntu_20_04 b/scripts/Dockerfile-ubuntu_20_04 index 5a633a10..689c820c 100644 --- a/scripts/Dockerfile-ubuntu_20_04 +++ b/scripts/Dockerfile-ubuntu_20_04 @@ -40,3 +40,5 @@ COPY build-private.sh /root/build.sh ENTRYPOINT ["/root/build.sh"] + +ENTRYPOINT ["/root/sunshine-build/gen-deb"] diff --git a/scripts/Dockerfile-ubuntu_21_04 b/scripts/Dockerfile-ubuntu_21_04 index d5668df4..4beedac6 100644 --- a/scripts/Dockerfile-ubuntu_21_04 +++ b/scripts/Dockerfile-ubuntu_21_04 @@ -35,3 +35,5 @@ COPY build-private.sh /root/build.sh ENTRYPOINT ["/root/build.sh"] + +ENTRYPOINT ["/root/sunshine-build/gen-deb"] diff --git a/scripts/Dockerfile-ubuntu_21_10 b/scripts/Dockerfile-ubuntu_21_10 index 14770625..113d4f70 100644 --- a/scripts/Dockerfile-ubuntu_21_10 +++ b/scripts/Dockerfile-ubuntu_21_10 @@ -35,3 +35,5 @@ COPY build-private.sh /root/build.sh ENTRYPOINT ["/root/build.sh"] + +ENTRYPOINT ["/root/sunshine-build/gen-deb"] diff --git a/scripts/build-private.sh b/scripts/build-private.sh index ade64829..283aeffe 100755 --- a/scripts/build-private.sh +++ b/scripts/build-private.sh @@ -31,5 +31,3 @@ cd /root/sunshine-build cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" "$SUNSHINE_ROOT" make -j ${nproc} - -./gen-deb From 41a30b58269ec7b53cc578396e817cc32118f938 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 11 Jan 2022 01:13:57 -0500 Subject: [PATCH 097/817] Skip packaging/release for Fedora build --- .github/workflows/create_package.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/create_package.yml b/.github/workflows/create_package.yml index 9e73b9c7..1a98d11b 100644 --- a/.github/workflows/create_package.yml +++ b/.github/workflows/create_package.yml @@ -117,16 +117,18 @@ jobs: cd scripts sudo ./build-sunshine.sh -p -u -n sunshine-${{ matrix.distro }} -s .. - name: Package Linux + if: ${{ matrix.distro != 'fedora_33' && matrix.distro != 'fedore_35' }} run: | cd scripts sudo mv ./sunshine-${{ matrix.distro }}-build/sunshine-${{ matrix.distro }}.deb ../artifacts/ - name: Upload Artifacts - if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} + if: ${{ ( matrix.distro != 'fedora_33' && matrix.distro != 'fedore_35' ) && ( github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' ) }} uses: actions/upload-artifact@v2 with: name: sunshine-${{ matrix.distro }} path: artifacts/ - name: Create Release + if: ${{ ( matrix.distro != 'fedora_33' && matrix.distro != 'fedore_35' ) && ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) }} uses: SunshineStream/actions/create_release@v0 with: token: ${{ secrets.GITHUB_TOKEN }} From 95a478e55ecfeabe579cf9c73223cf1566c1448a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 11 Jan 2022 09:45:23 -0500 Subject: [PATCH 098/817] Revert changes --- scripts/Dockerfile-ubuntu_18_04 | 2 -- scripts/Dockerfile-ubuntu_20_04 | 2 -- scripts/Dockerfile-ubuntu_21_10 | 2 -- scripts/build-private.sh | 2 ++ 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index dad4c96b..5ee33ae3 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -56,5 +56,3 @@ COPY build-private.sh /root/build.sh ENTRYPOINT ["/root/build.sh"] - -ENTRYPOINT ["/root/sunshine-build/gen-deb"] diff --git a/scripts/Dockerfile-ubuntu_20_04 b/scripts/Dockerfile-ubuntu_20_04 index 689c820c..5a633a10 100644 --- a/scripts/Dockerfile-ubuntu_20_04 +++ b/scripts/Dockerfile-ubuntu_20_04 @@ -40,5 +40,3 @@ COPY build-private.sh /root/build.sh ENTRYPOINT ["/root/build.sh"] - -ENTRYPOINT ["/root/sunshine-build/gen-deb"] diff --git a/scripts/Dockerfile-ubuntu_21_10 b/scripts/Dockerfile-ubuntu_21_10 index 113d4f70..14770625 100644 --- a/scripts/Dockerfile-ubuntu_21_10 +++ b/scripts/Dockerfile-ubuntu_21_10 @@ -35,5 +35,3 @@ COPY build-private.sh /root/build.sh ENTRYPOINT ["/root/build.sh"] - -ENTRYPOINT ["/root/sunshine-build/gen-deb"] diff --git a/scripts/build-private.sh b/scripts/build-private.sh index 283aeffe..ade64829 100755 --- a/scripts/build-private.sh +++ b/scripts/build-private.sh @@ -31,3 +31,5 @@ cd /root/sunshine-build cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" "$SUNSHINE_ROOT" make -j ${nproc} + +./gen-deb From 0cad1bf6a5f982c2d9770cf89da74e204b21e113 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 11 Jan 2022 09:45:45 -0500 Subject: [PATCH 099/817] Revert changes --- scripts/Dockerfile-debian | 2 -- scripts/Dockerfile-ubuntu_21_04 | 2 -- 2 files changed, 4 deletions(-) diff --git a/scripts/Dockerfile-debian b/scripts/Dockerfile-debian index 1ed73c5f..15f0d656 100644 --- a/scripts/Dockerfile-debian +++ b/scripts/Dockerfile-debian @@ -37,5 +37,3 @@ COPY build-private.sh /root/build.sh ENTRYPOINT ["/root/build.sh"] - -ENTRYPOINT ["/root/sunshine-build/gen-deb"] diff --git a/scripts/Dockerfile-ubuntu_21_04 b/scripts/Dockerfile-ubuntu_21_04 index 4beedac6..d5668df4 100644 --- a/scripts/Dockerfile-ubuntu_21_04 +++ b/scripts/Dockerfile-ubuntu_21_04 @@ -35,5 +35,3 @@ COPY build-private.sh /root/build.sh ENTRYPOINT ["/root/build.sh"] - -ENTRYPOINT ["/root/sunshine-build/gen-deb"] From 4c6a0cdc377c6b1a03a9acff87bcd1144804ede7 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 11 Jan 2022 09:46:18 -0500 Subject: [PATCH 100/817] Package based on strategy matrix --- .github/workflows/create_package.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/create_package.yml b/.github/workflows/create_package.yml index 1a98d11b..6829ce60 100644 --- a/.github/workflows/create_package.yml +++ b/.github/workflows/create_package.yml @@ -98,7 +98,13 @@ jobs: strategy: fail-fast: false matrix: - distro: [ debian, fedora_33, fedora_35, ubuntu_18_04, ubuntu_20_04, ubuntu_21_04, ubuntu_21_10 ] + distro: [ debian, ubuntu_18_04, ubuntu_20_04, ubuntu_21_04, ubuntu_21_10 ] + package: [ -p ] + include: + - distro: fedora_33 + package: '' + - distro: fedora_35 + package: '' steps: - name: Checkout @@ -115,20 +121,20 @@ jobs: - name: Build Linux run: | cd scripts - sudo ./build-sunshine.sh -p -u -n sunshine-${{ matrix.distro }} -s .. + sudo ./build-sunshine.sh ${{ matrix.package }} -u -n sunshine-${{ matrix.distro }} -s .. - name: Package Linux - if: ${{ matrix.distro != 'fedora_33' && matrix.distro != 'fedore_35' }} + if: ${{ matrix.package == '-p' }} run: | cd scripts sudo mv ./sunshine-${{ matrix.distro }}-build/sunshine-${{ matrix.distro }}.deb ../artifacts/ - name: Upload Artifacts - if: ${{ ( matrix.distro != 'fedora_33' && matrix.distro != 'fedore_35' ) && ( github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' ) }} + if: ${{ matrix.package == '-p' && ( github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' ) }} uses: actions/upload-artifact@v2 with: name: sunshine-${{ matrix.distro }} path: artifacts/ - name: Create Release - if: ${{ ( matrix.distro != 'fedora_33' && matrix.distro != 'fedore_35' ) && ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) }} + if: ${{ matrix.package == '-p' && github.event_name == 'push' && github.ref == 'refs/heads/master' }} uses: SunshineStream/actions/create_release@v0 with: token: ${{ secrets.GITHUB_TOKEN }} From 8f99d6cf01b6ad2ecf3b99b6c50270f07445ae47 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 11 Jan 2022 10:01:20 -0500 Subject: [PATCH 101/817] Delete create_release.yml --- .github/workflows/create_release.yml | 41 ---------------------------- 1 file changed, 41 deletions(-) delete mode 100644 .github/workflows/create_release.yml diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml deleted file mode 100644 index c95a689f..00000000 --- a/.github/workflows/create_release.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Create Release - -on: - workflow_call: - -jobs: - create_release: - runs-on: ubuntu-latest - steps: - - name: Parse Changelog Entry - if: ${{ github.ref == 'refs/heads/master' }} - id: changelog - uses: coditory/changelog-parser@v1 # https://github.com/coditory/changelog-parser - - - name: Get last release - if: ${{ github.ref == 'refs/heads/master' }} - id: last_release - uses: InsonusK/get-latest-release@v1.0.1 # https://github.com/InsonusK/get-latest-release - with: - myToken: ${{ secrets.GITHUB_TOKEN }} - exclude_types: "draft|prerelease" - view_top: 1 - - - name: Changelog Version - if: ${{ github.ref == 'refs/heads/master' && ( steps.changelog.outputs.version == steps.last_release.tag_name ) }} - # fail the workflow because the versions match - run: | - echo Changelog Version: "${{ steps.changelog.outputs.version }}" - echo Last Released Version: "${{ steps.last_release.tag_name }}" - exit 1 - - - name: Create/Update GitHub Release - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: ncipollo/release-action@v1 # https://github.com/ncipollo/release-action - with: - name: Release ${{ steps.changelog.outputs.version }} - tag: ${{ steps.changelog.outputs.version }} - artifacts: "./artifacts/*" - token: ${{ secrets.GITHUB_TOKEN }} - allowUpdated: true - body: ${{ steps.changelog.outputs.description }} From 60d63fcd2178c28d310cab935ad04f064db29deb Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 11 Jan 2022 10:01:43 -0500 Subject: [PATCH 102/817] Update create_package.yml -Remove push event for add-build-checks --- .github/workflows/create_package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create_package.yml b/.github/workflows/create_package.yml index 6829ce60..1d7f4a26 100644 --- a/.github/workflows/create_package.yml +++ b/.github/workflows/create_package.yml @@ -5,7 +5,7 @@ on: branches: [master, nightly] types: [opened, synchronize, edited, reopened] push: - branches: [master, add-build-checks] + branches: [master] workflow_dispatch: jobs: From c5d782dac559e6af9a9baf45afe5de093556b599 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 11 Jan 2022 10:18:52 -0500 Subject: [PATCH 103/817] Remove Ubuntu 18.04 from strategy --- .github/workflows/create_package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/create_package.yml b/.github/workflows/create_package.yml index 1d7f4a26..bf1d4dbb 100644 --- a/.github/workflows/create_package.yml +++ b/.github/workflows/create_package.yml @@ -96,11 +96,11 @@ jobs: name: Linux runs-on: ubuntu-20.04 strategy: - fail-fast: false + fail-fast: true # false to test all, true to fail entire job if any fail matrix: - distro: [ debian, ubuntu_18_04, ubuntu_20_04, ubuntu_21_04, ubuntu_21_10 ] + distro: [ debian, ubuntu_20_04, ubuntu_21_04, ubuntu_21_10 ] # removed ubuntu_18_04 for now package: [ -p ] - include: + include: # don't package these - distro: fedora_33 package: '' - distro: fedora_35 From bfda8558ea1425b4af6ef4ddd2d25d1cd2437128 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 11 Jan 2022 10:39:20 -0500 Subject: [PATCH 104/817] Update CHANGELOG.md --- CHANGELOG.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 314ecf81..2a052870 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,12 @@ # Changelog -## [Unreleased] - 2022-01-10 +## [0.0.0] - 2022-01-11 ### Added --Added something +-Github Pull Request Tests +-Github Automated Release +-Github Issue Template +-Github Pull Request Template +-AppImage packaging during PR Test and Release +-Fedora build test during PR Test and Release (no package available yet) ### Changed --Fixed something +-Renamed Dockerfiles From cefcaed3580f40d690a22a05dc69606b9976aa1a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 11 Jan 2022 17:40:58 -0500 Subject: [PATCH 105/817] Update create_package.yml --- .github/workflows/create_package.yml | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/.github/workflows/create_package.yml b/.github/workflows/create_package.yml index bf1d4dbb..d6d43916 100644 --- a/.github/workflows/create_package.yml +++ b/.github/workflows/create_package.yml @@ -9,9 +9,25 @@ on: workflow_dispatch: jobs: + check_changelog: + name: Check Changelog + runs-on: ubuntu-latest + steps: + - name: Verify Changelog + id: verify_changelog + if: ${{ github.ref == 'refs/heads/master' }} + uses: SunshineStream/actions/verify_changelog@v0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + outputs: + next_version: ${{ steps.verify_changelog.outputs.changelog_parser_version }} + last_version: ${{ steps.verify_changelog.outputs.latest_release_tag_name }} + release_body: ${{ steps.verify_changelog.outputs.changelog_parser_description }} + build_appimage: name: AppImage runs-on: ubuntu-20.04 + needs: check_changelog steps: - name: Checkout @@ -87,14 +103,19 @@ jobs: name: sunshine-AppImage path: artifacts/ - name: Create Release + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} uses: SunshineStream/actions/create_release@v0 with: token: ${{ secrets.GITHUB_TOKEN }} + next_version: ${{ needs.check_changelog.outputs.next_version }} + last_version: ${{ needs.check_changelog.outputs.last_version }} + release_body: ${{ needs.check_changelog.outputs.release_body }} build_linux: name: Linux runs-on: ubuntu-20.04 + needs: check_changelog strategy: fail-fast: true # false to test all, true to fail entire job if any fail matrix: @@ -138,10 +159,14 @@ jobs: uses: SunshineStream/actions/create_release@v0 with: token: ${{ secrets.GITHUB_TOKEN }} + next_version: ${{ needs.check_changelog.outputs.next_version }} + last_version: ${{ needs.check_changelog.outputs.last_version }} + release_body: ${{ needs.check_changelog.outputs.release_body }} build_win: name: Windows runs-on: windows-2019 + needs: check_changelog steps: - name: Checkout @@ -194,6 +219,10 @@ jobs: name: sunshine-${{ runner.os }} path: artifacts/ - name: Create Release + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} uses: SunshineStream/actions/create_release@v0 with: token: ${{ secrets.GITHUB_TOKEN }} + next_version: ${{ needs.check_changelog.outputs.next_version }} + last_version: ${{ needs.check_changelog.outputs.last_version }} + release_body: ${{ needs.check_changelog.outputs.release_body }} From a2c45f8de0f788ee50501c5b7a4c502f0523669d Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 11 Jan 2022 18:03:30 -0500 Subject: [PATCH 106/817] Update create_package.yml -Fix typo in Windows build --- .github/workflows/create_package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create_package.yml b/.github/workflows/create_package.yml index d6d43916..81123f06 100644 --- a/.github/workflows/create_package.yml +++ b/.github/workflows/create_package.yml @@ -201,7 +201,7 @@ jobs: - name: Package Windows run: | cd sunshine-windows-build - Del ..\assets\apps_linux.json + del ..\assets\apps_linux.json 7z a Sunshine-Windows.zip ..\assets 7z a Sunshine-Windows.zip sunshine.exe 7z a Sunshine-Windows.zip tools\dxgi-info.exe @@ -211,7 +211,7 @@ jobs: 7z a Sunshine-Windows.zip ..\tools\uninstall-service.bat cd .. mkdir artifacts - move "sunshine-windows-build/Sunshine-Windows.zip" "artifats" + move "sunshine-windows-build\Sunshine-Windows.zip" "artifacts" - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v2 From 8781fbcc204f4f5433ab52c0a14d6be0458fffaf Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 11 Jan 2022 18:35:05 -0500 Subject: [PATCH 107/817] Update create_package.yml - github.base_ref is needed for pull request check --- .github/workflows/create_package.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/create_package.yml b/.github/workflows/create_package.yml index 81123f06..95033fd6 100644 --- a/.github/workflows/create_package.yml +++ b/.github/workflows/create_package.yml @@ -15,7 +15,8 @@ jobs: steps: - name: Verify Changelog id: verify_changelog - if: ${{ github.ref == 'refs/heads/master' }} + if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} + # base_ref for pull request check, ref for push uses: SunshineStream/actions/verify_changelog@v0 with: token: ${{ secrets.GITHUB_TOKEN }} From f2c53a52c20656a210c155ab4980b790a40683ec Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 11 Jan 2022 18:55:58 -0500 Subject: [PATCH 108/817] Update create_package.yml -Verify changelog requires checkout --- .github/workflows/create_package.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/create_package.yml b/.github/workflows/create_package.yml index 95033fd6..1a716929 100644 --- a/.github/workflows/create_package.yml +++ b/.github/workflows/create_package.yml @@ -13,6 +13,9 @@ jobs: name: Check Changelog runs-on: ubuntu-latest steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Verify Changelog id: verify_changelog if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} From 55ac123d036a5a70bc3ac22053692820fac900ae Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 12 Jan 2022 19:01:00 -0500 Subject: [PATCH 109/817] Update history --- CHANGELOG.md | 112 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 104 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a052870..1bb6ca49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,108 @@ # Changelog -## [0.0.0] - 2022-01-11 +## [v0.11.1] - 2020-10-04 +### Changed +- (Linux) Fix search path for config file and assets + +## [v0.11.0] - 2020-10-04 +### Added +- (Linux) Added support for wlroots based compositors on Wayland. +- (Windows) Added an icon for the executable +### 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 + +## [v0.10.1] - 2020-08-21 +### Changed +- (Linux) Re-enabled KMS + +## [v0.10.0] - 2020-08-20 +### 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 +- 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 +- (Linux) VAAPI hardware encoding now works on Intel i7-6700 at least. <-- For the best experience, using ffmpeg version 4.3 or higher is recommended. +- (Windows) Installing from debian package shouldn't overwrite your configuration files anymore. <-- It's recommended that you back up `/etc/sunshine/` before testing this. + +## [v0.9.0] - 2020-07-11 ### Added --Github Pull Request Tests --Github Automated Release --Github Issue Template --Github Pull Request Template --AppImage packaging during PR Test and Release --Fedora build test during PR Test and Release (no package available yet) +- 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 --Renamed Dockerfiles +- 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. + +## [v0.8.0] - 2020-06-30 +### Added +- Added mDNS support: Moonlight will automatically find Sunshine. +- Added UPnP support. It's off by default. + +## [v0.7.7] - 2020-06-24 +### Added +- (Linux) Added installation package for Debian +### Changed +- Fixed incorrect scaling for absolute mouse coordinates when using multiple monitors. +- Fixed incorrect colors when scaling for software encoder + +## [v0.7.1] - 2020-06-18 +### Changed +- (Linux) Fixed an issue where it was impossible to start sunshine on ubuntu 20.04 + +## [v0.7.0] - 2020-06-16 +### Added +- Added a Web Manager. Accessible through: https://localhost:47990 or https://:47990 +- (Linux) Added hardware encoding support for AMD on Linux +### Changed +- (Linux) Moved certificates and saved pairings generated during runtime to .config/sunshine on Linux + +## [v0.6.0] - 2020-05-26 +### Added +- Added support for surround audio +### Changed +- Maintain aspect ratio when scaling video +- Fix issue where Sunshine is forced to drop frames when they are too large + +## [v0.5.0] - 2020-05-13 +### Added +- Added support for absolute mouse coordinates +- (Linux) Added support for streaming specific monitor on Linux +- (Windows) Added support for AMF on Windows + +## [v0.4.0] - 2020-05-03 +### Changed +- prep-cmd is now optional in apps.json +- Fixed bug causing video artifacts +- Fixed bug preventing Moonlight from closing app on exit +- Fixed bug causing preventing keyboard keys from repeating on latest version of Moonlight +- Fixed bug causing segfault when another session of sunshine was already running +- Fixed bug causing crash when monitor has resolution 1366x768 + +## [v0.3.1] - 2020-04-24 +### Changed +- Fix a memory leak. + +## [v0.3.0] - 2020-04-23 +### Changed +- Hardware acceleration on NVidia GPU's for Video encoding on Windows + +## [v0.2.0] - 2020-03-21 +### 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 + +## [v0.1.1] - 2020-01-30 +### Added +- (Linux) Added deb package and service for Linux + +## [v0.1.0] - 2020-01-27 +### Added +- The first official release for Sunshine! From 1b392e3921546c16ac611c52de09f0684aad5d6b Mon Sep 17 00:00:00 2001 From: Alvaro <2722773+alvaromunoz@users.noreply.github.com> Date: Thu, 13 Jan 2022 23:38:32 -0300 Subject: [PATCH 110/817] Update index.html Update repo address --- assets/web/index.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/assets/web/index.html b/assets/web/index.html index 761f16f3..8c483aba 100644 --- a/assets/web/index.html +++ b/assets/web/index.html @@ -3,9 +3,7 @@

Hello, Sunshine!

Sunshine is a Gamestream host for Moonlight

- Official GitHub Repository + Maintained GitHub Repository
From 41044ab97f9228f4cb975daf7f4ce346648873f2 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 15 Jan 2022 00:48:30 -0500 Subject: [PATCH 111/817] Update CHANGELOG.md - changelog parser action fails if "v" is in prefix --- CHANGELOG.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bb6ca49..939cb1ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # Changelog -## [v0.11.1] - 2020-10-04 +## [0.11.1] - 2020-10-04 ### Changed - (Linux) Fix search path for config file and assets -## [v0.11.0] - 2020-10-04 +## [0.11.0] - 2020-10-04 ### Added - (Linux) Added support for wlroots based compositors on Wayland. - (Windows) Added an icon for the executable @@ -13,11 +13,11 @@ - (Linux) Improved NVENC, it now offloads converting images from RGB to NV12 - (Linux) Fixed a bug causes stuttering -## [v0.10.1] - 2020-08-21 +## [0.10.1] - 2020-08-21 ### Changed - (Linux) Re-enabled KMS -## [v0.10.0] - 2020-08-20 +## [0.10.0] - 2020-08-20 ### Added - Added support for Rumble with gamepads. - Added support for keyboard shortcuts <--- See the README for details. @@ -29,7 +29,7 @@ - (Linux) VAAPI hardware encoding now works on Intel i7-6700 at least. <-- For the best experience, using ffmpeg version 4.3 or higher is recommended. - (Windows) Installing from debian package shouldn't overwrite your configuration files anymore. <-- It's recommended that you back up `/etc/sunshine/` before testing this. -## [v0.9.0] - 2020-07-11 +## [0.9.0] - 2020-07-11 ### Added - Added audio encryption - (Linux) Added basic NVENC support on Linux @@ -40,43 +40,43 @@ - Drastically reduced chance of being forced to skip error correction due to video frame size - (Linux) sunshine.service will be installed automatically. -## [v0.8.0] - 2020-06-30 +## [0.8.0] - 2020-06-30 ### Added - Added mDNS support: Moonlight will automatically find Sunshine. - Added UPnP support. It's off by default. -## [v0.7.7] - 2020-06-24 +## [0.7.7] - 2020-06-24 ### Added - (Linux) Added installation package for Debian ### Changed - Fixed incorrect scaling for absolute mouse coordinates when using multiple monitors. - Fixed incorrect colors when scaling for software encoder -## [v0.7.1] - 2020-06-18 +## [0.7.1] - 2020-06-18 ### Changed - (Linux) Fixed an issue where it was impossible to start sunshine on ubuntu 20.04 -## [v0.7.0] - 2020-06-16 +## [0.7.0] - 2020-06-16 ### Added - Added a Web Manager. Accessible through: https://localhost:47990 or https://:47990 - (Linux) Added hardware encoding support for AMD on Linux ### Changed - (Linux) Moved certificates and saved pairings generated during runtime to .config/sunshine on Linux -## [v0.6.0] - 2020-05-26 +## [0.6.0] - 2020-05-26 ### Added - Added support for surround audio ### Changed - Maintain aspect ratio when scaling video - Fix issue where Sunshine is forced to drop frames when they are too large -## [v0.5.0] - 2020-05-13 +## [0.5.0] - 2020-05-13 ### Added - Added support for absolute mouse coordinates - (Linux) Added support for streaming specific monitor on Linux - (Windows) Added support for AMF on Windows -## [v0.4.0] - 2020-05-03 +## [0.4.0] - 2020-05-03 ### Changed - prep-cmd is now optional in apps.json - Fixed bug causing video artifacts @@ -85,24 +85,24 @@ - Fixed bug causing segfault when another session of sunshine was already running - Fixed bug causing crash when monitor has resolution 1366x768 -## [v0.3.1] - 2020-04-24 +## [0.3.1] - 2020-04-24 ### Changed - Fix a memory leak. -## [v0.3.0] - 2020-04-23 +## [0.3.0] - 2020-04-23 ### Changed - Hardware acceleration on NVidia GPU's for Video encoding on Windows -## [v0.2.0] - 2020-03-21 +## [0.2.0] - 2020-03-21 ### 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 -## [v0.1.1] - 2020-01-30 +## [0.1.1] - 2020-01-30 ### Added - (Linux) Added deb package and service for Linux -## [v0.1.0] - 2020-01-27 +## [0.1.0] - 2020-01-27 ### Added - The first official release for Sunshine! From 7306a3468d29f5ec59b0d8d34296ef697675b858 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 15 Jan 2022 19:19:15 -0500 Subject: [PATCH 112/817] Update create_package.yml -Use master branch for verify_changelog action --- .github/workflows/create_package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create_package.yml b/.github/workflows/create_package.yml index 1a716929..74e48f5d 100644 --- a/.github/workflows/create_package.yml +++ b/.github/workflows/create_package.yml @@ -20,7 +20,7 @@ jobs: id: verify_changelog if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} # base_ref for pull request check, ref for push - uses: SunshineStream/actions/verify_changelog@v0 + uses: SunshineStream/actions/verify_changelog@master with: token: ${{ secrets.GITHUB_TOKEN }} outputs: From 41e6344995a8a1623fa220f3230ac465f7e9e26e Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 16 Jan 2022 14:05:18 -0500 Subject: [PATCH 113/817] Update bug-report.yml - Use form instead of template --- .github/ISSUE_TEMPLATE/bug-report.md | 37 ------------ .github/ISSUE_TEMPLATE/bug-report.yml | 82 +++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 37 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug-report.md create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md deleted file mode 100644 index 4da04b37..00000000 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: "" -labels: '' -assignees: '' - ---- - -**Describe the bug** - - -**To Reproduce** - -1. [e.g. Go to '...'] -2. [e.g. Click on '....'] -3. [e.g. Scroll down to '....'] -4. [e.g. See error] - -**Expected behavior** - - -**Screenshots** - - -**Host** - - - OS: [e.g. Windows, Linux, Mac... include build/distro details] - - Architecture: [e.g. 32 bit, 64 bit, arm] - - Version: [e.g. 0.11.1] - - GPU Type: [e.g. Intel, AMD, Nvidia] - - GPU Model: - - GPU Driver/Mesa Version: - - Capture method (Linux only): [e.g. PipeWire/KVM/X11] - -**Additional context** - diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 00000000..a9c38290 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,82 @@ +name: Bug Report +description: Create a bug report to help us improve. +body: + - type: markdown + attributes: + value: > + **THIS IS NOT THE PLACE TO ASK FOR SUPPORT!** + Please use [Github Discussions](https://github.com/SunshineStream/Sunshine/discussions) for support issues. + - type: textarea + id: description + attributes: + label: Describe the Bug + description: A clear and concise description of the bug. + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: A clear and concise description of what you expected to happen. + - type: textarea + id: additional + attributes: + label: Additional Context + description: Add any other context about the bug here. + - type: input + id: os + attributes: + label: Sunshine Host Operating System and Version + placeholder: eg. Windows 10, macOS 10.15, Ubuntu 20.04, etc. + validations: + required: true + - type: input + id: architecture + attributes: + label: Architecture + placeholder: e.g. 32 bit, 64 bit, arm + validations: + required: true + - type: input + id: version + attributes: + label: Sunshine Version + placeholder: eg. 0.11.1 + validations: + required: true + - type: input + id: graphics_type + attributes: + label: GPU Type + description: The type of the installed graphics card. + placeholder: e.g. Intel, AMD, Nvidia + validations: + required: true + - type: input + id: graphics_model + attributes: + label: GPU Model + description: The model of the installed graphics card. + placeholder: e.g. GeForce RTX 2080 SUPER + validations: + required: true + - type: input + id: graphics_driver + attributes: + label: GPU Driver/Mesa Version + description: The driver/mesa version of the installed graphics card. + placeholder: e.g. 497.29 + validations: + required: true + - type: input + id: capture_method + attributes: + label: Capture Method (Linux Only) + description: The driver/mesa version of the installed graphics card. + placeholder: e.g. PipeWire/KVM/X11 + validations: + required: false + - 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. From 1b6bf8fa3461f8cf0d8385cb09798ef2cdef171b Mon Sep 17 00:00:00 2001 From: Conn O'Griofa Date: Wed, 8 Dec 2021 22:24:49 +0000 Subject: [PATCH 114/817] video: use a better vbv-bufsize & correct software bitrate calculation * Increase vbv-bufsize to 1/10 of requested bitrate. The previous value was too low, which was resulting in poor encoding quality and failure to stabilize at the requested bitrate. Setting to 1/10 of bitrate is a good compromise, as it avoids quality loss but also prevents bandwidth spikes far above the bitrate/vbv-maxrate. * With vbv-bufsize set to a more appropriate value, testing shows that the average bitrate vs client-requested bandwidth overshoots by ~20%. Adjust for this scenario in the software encoding case only. --- sunshine/video.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 84038a6f..1a3d51e9 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -930,9 +930,9 @@ std::optional make_session(const encoder_t &encoder, const config_t & } if(video_format[encoder_t::CBR]) { - auto bitrate = config.bitrate * 1000; + auto bitrate = config.bitrate * (hardware ? 1000 : 800); // software bitrate overshoots by ~20% ctx->rc_max_rate = bitrate; - ctx->rc_buffer_size = bitrate / config.framerate; + ctx->rc_buffer_size = bitrate / 10; ctx->bit_rate = bitrate; ctx->rc_min_rate = bitrate; } From 9f707f9015c232a2ded0ed45d5f0b24ccc7ee719 Mon Sep 17 00:00:00 2001 From: Alvaro <2722773+alvaromunoz@users.noreply.github.com> Date: Tue, 18 Jan 2022 11:08:23 -0300 Subject: [PATCH 115/817] Update index.html --- assets/web/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/web/index.html b/assets/web/index.html index 8c483aba..3d5784f0 100644 --- a/assets/web/index.html +++ b/assets/web/index.html @@ -3,7 +3,7 @@

Hello, Sunshine!

Sunshine is a Gamestream host for Moonlight

- Maintained GitHub Repository + Official Website
From 3981ca08404989307f8c52b787e11963f60a7472 Mon Sep 17 00:00:00 2001 From: Christophe Fajardo Date: Tue, 18 Jan 2022 22:06:20 +0100 Subject: [PATCH 116/817] applist image attribute --- assets/apps_windows.json | 3 ++- assets/steam.png | Bin 0 -> 47373 bytes sunshine/nvhttp.cpp | 31 +++++++++++++++++++++++++++++-- sunshine/process.cpp | 13 +++++++++++++ sunshine/process.h | 2 ++ 5 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 assets/steam.png diff --git a/assets/apps_windows.json b/assets/apps_windows.json index f2a2a7ec..3dd599a8 100644 --- a/assets/apps_windows.json +++ b/assets/apps_windows.json @@ -7,7 +7,8 @@ "name":"Steam BigPicture", "output":"steam.txt", - "detached":["steam steam://open/bigpicture"] + "detached":["steam steam://open/bigpicture"], + "image":"steam.png" } ] } diff --git a/assets/steam.png b/assets/steam.png new file mode 100644 index 0000000000000000000000000000000000000000..b9d429004269d152e000cc26680d38ff71de3344 GIT binary patch literal 47373 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

^y>>y-vl%`TW$x23W) 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 Z1F+}O*)YkvJ?4M@+UojhRVubo{~xdJ_5=U` literal 0 HcmV?d00001 diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index f167778a..28ecb8ea 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -761,12 +761,39 @@ void cancel(resp_https_t response, req_https_t request) { } } +#define CHECK_EXPECTED_EXTENTIONS(extention) (extention == "png" || extention == "jpg" || extention == "jpeg") + void appasset(resp_https_t response, req_https_t request) { print_req(request); - std::ifstream in(SUNSHINE_ASSETS_DIR "/box.png"); - response->write(SimpleWeb::StatusCode::success_ok, in); + auto args = request->parse_query_string(); + auto appid = util::from_view(args.at("appid")) - 1; + auto app_image = proc::proc.get_app_image(appid); + + BOOST_LOG(debug) << "/appasset: ["sv << app_image << "] -- image"sv; + + if (app_image.empty()) { + app_image = "steam.png"; + } + auto file_path = SUNSHINE_ASSETS_DIR "/" + app_image; + + BOOST_LOG(debug) << "/appasset: ["sv << file_path << "] -- image path"sv; + // check if files exists + std::error_code code; + auto image_extention = std::filesystem::path(file_path).extension().string(); + image_extention = image_extention.substr(1, image_extention.length() - 1); + if (!std::filesystem::exists(file_path, code) || !CHECK_EXPECTED_EXTENTIONS(image_extention)) { + BOOST_LOG(debug) << "/appasset: ["sv << file_path << "] [extention="sv << image_extention << "] -- file not found"sv; + response->write(SimpleWeb::StatusCode::client_error_not_found); + return; + } + + SimpleWeb::CaseInsensitiveMultimap header; + header.emplace("Content-Type", "image/" + image_extention); + std::ifstream in(file_path); + response->write(SimpleWeb::StatusCode::success_ok, in, header); response->close_connection_after_response = true; + BOOST_LOG(debug) << "/appasset: ["sv << file_path << "] [extention="sv << image_extention << "] -- sent"sv; } void start() { diff --git a/sunshine/process.cpp b/sunshine/process.cpp index f68593c0..2215fa5b 100644 --- a/sunshine/process.cpp +++ b/sunshine/process.cpp @@ -189,6 +189,14 @@ std::vector &proc_t::get_apps() { return _apps; } +std::string proc_t::get_app_image(int app_id) { + if(app_id < 0 || app_id >= _apps.size()) { + BOOST_LOG(error) << "Couldn't find app with ID ["sv << app_id << ']'; + return {}; + } + return _apps[app_id].image; +} + proc_t::~proc_t() { terminate(); } @@ -279,6 +287,7 @@ std::optional parse(const std::string &file_name) { auto output = app_node.get_optional("output"s); auto name = parse_env_val(this_env, app_node.get("name"s)); auto cmd = app_node.get_optional("cmd"s); + auto image = app_node.get_optional("image"s); auto working_dir = app_node.get_optional("working-dir"s); std::vector prep_cmds; @@ -321,6 +330,10 @@ std::optional parse(const std::string &file_name) { ctx.working_dir = parse_env_val(this_env, *working_dir); } + if (image) { + ctx.image = parse_env_val(this_env, *image); + } + ctx.name = std::move(name); ctx.prep_cmds = std::move(prep_cmds); ctx.detached = std::move(detached); diff --git a/sunshine/process.h b/sunshine/process.h index 86016b64..b45e0c04 100644 --- a/sunshine/process.h +++ b/sunshine/process.h @@ -55,6 +55,7 @@ struct ctx_t { std::string cmd; std::string working_dir; std::string output; + std::string image; }; class proc_t { @@ -78,6 +79,7 @@ class proc_t { const std::vector &get_apps() const; std::vector &get_apps(); + std::string get_app_image(int app_id); void terminate(); From dd7736e806d26615900f7b0502a4be574c71cee4 Mon Sep 17 00:00:00 2001 From: Elia Zammuto Date: Wed, 19 Jan 2022 21:27:17 +0100 Subject: [PATCH 117/817] UI Web Fixes --- assets/web/config.html | 17 +++++++++++++++-- assets/web/header.html | 11 +++++++++++ assets/web/pin.html | 4 ++-- assets/web/welcome.html | 5 ++--- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/assets/web/config.html b/assets/web/config.html index 9c33df77..39a2f58f 100644 --- a/assets/web/config.html +++ b/assets/web/config.html @@ -85,7 +85,7 @@

Configuration

-
Automatically configure port forwarding
+
Choose which type of gamepad to Emulate on the host
@@ -186,7 +186,8 @@

Configuration

The display modes advertised by Sunshine
Some versions of Moonlight, such as Moonlight-nx (Switch), rely on this list to ensure that the requested resolutions and fps are - supported. + supported.
+ This setting does not change how the screen stream is sent to Moonlight
@@ -628,6 +629,9 @@

Configuration

placeholder="superfast" v-model="config.sw_preset" /> +
+ Available Values: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow +
@@ -637,6 +641,15 @@

Configuration

placeholder="zerolatency" v-model="config.sw_tune" /> +
+ Available Tune settings:
+ film - use for high quality movie content; lowers deblocking
+ animation - good for cartoons; uses higher deblocking and more reference frames
+ grain - preserves the grain structure in old, grainy film material
+ stillimage - good for slideshow-like content
+ fastdecode - allows faster decoding by disabling certain filters
+ zerolatency - good for fast encoding and low-latency streaming
+
diff --git a/assets/web/header.html b/assets/web/header.html index fba95031..a66117cb 100644 --- a/assets/web/header.html +++ b/assets/web/header.html @@ -54,3 +54,14 @@ + + + + \ No newline at end of file diff --git a/assets/web/pin.html b/assets/web/pin.html index 4b5fb3be..c39f4863 100644 --- a/assets/web/pin.html +++ b/assets/web/pin.html @@ -28,14 +28,14 @@

PIN Pairing

fetch("/api/pin", { method: "POST", body: b }) .then((response) => response.json()) .then((response) => { - if (response.status) { + if (response.status && response.status === "true") { document.querySelector( "#status" ).innerHTML = ``; } else { document.querySelector( "#status" - ).innerHTML = ``; + ).innerHTML = ``; } }); }); diff --git a/assets/web/welcome.html b/assets/web/welcome.html index 374f3b13..7f31f726 100644 --- a/assets/web/welcome.html +++ b/assets/web/welcome.html @@ -2,12 +2,11 @@

Welcome to Sunshine!

- Before Getting Started, write down below these credentials + Before Getting Started, we need you to make a new username and password for accessing the Web UI.

- These Credentials down below are needed to access the rest of the - application.
+ These Credentials down below are needed to access Sunshine's Web UI.
Keep them safe, since you will never see them again!
From 4a50fcafd87df4ae686b785550ee1d581c630b34 Mon Sep 17 00:00:00 2001 From: Christophe Fajardo Date: Thu, 20 Jan 2022 14:31:16 +0100 Subject: [PATCH 118/817] send image as a binary stream --- sunshine/nvhttp.cpp | 20 ++++++-------------- sunshine/process.cpp | 11 +++++++++-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 28ecb8ea..6e549fc9 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -769,31 +769,23 @@ void appasset(resp_https_t response, req_https_t request) { auto args = request->parse_query_string(); auto appid = util::from_view(args.at("appid")) - 1; auto app_image = proc::proc.get_app_image(appid); - - BOOST_LOG(debug) << "/appasset: ["sv << app_image << "] -- image"sv; - if (app_image.empty()) { - app_image = "steam.png"; + app_image = "box.png"; } - auto file_path = SUNSHINE_ASSETS_DIR "/" + app_image; - BOOST_LOG(debug) << "/appasset: ["sv << file_path << "] -- image path"sv; - // check if files exists - std::error_code code; + auto file_path = SUNSHINE_ASSETS_DIR "/" + app_image; auto image_extention = std::filesystem::path(file_path).extension().string(); image_extention = image_extention.substr(1, image_extention.length() - 1); + + std::error_code code; if (!std::filesystem::exists(file_path, code) || !CHECK_EXPECTED_EXTENTIONS(image_extention)) { - BOOST_LOG(debug) << "/appasset: ["sv << file_path << "] [extention="sv << image_extention << "] -- file not found"sv; response->write(SimpleWeb::StatusCode::client_error_not_found); return; } - SimpleWeb::CaseInsensitiveMultimap header; - header.emplace("Content-Type", "image/" + image_extention); - std::ifstream in(file_path); - response->write(SimpleWeb::StatusCode::success_ok, in, header); + std::ifstream in(file_path, std::ios::binary); + response->write(SimpleWeb::StatusCode::success_ok, in); response->close_connection_after_response = true; - BOOST_LOG(debug) << "/appasset: ["sv << file_path << "] [extention="sv << image_extention << "] -- sent"sv; } void start() { diff --git a/sunshine/process.cpp b/sunshine/process.cpp index 2215fa5b..81ee4cd2 100644 --- a/sunshine/process.cpp +++ b/sunshine/process.cpp @@ -189,12 +189,19 @@ std::vector &proc_t::get_apps() { return _apps; } +/// Gets application image from application list. +/// Returns default image if image configuration is not set. std::string proc_t::get_app_image(int app_id) { if(app_id < 0 || app_id >= _apps.size()) { BOOST_LOG(error) << "Couldn't find app with ID ["sv << app_id << ']'; - return {}; + return "box.png"; } - return _apps[app_id].image; + + auto app_image = _apps[app_id].image; + if (app_image.empty()) { + return "box.png"; + } + return app_image; } proc_t::~proc_t() { From 38dcdcba2f03dde1b3122735d45340c8ccf75923 Mon Sep 17 00:00:00 2001 From: Christophe Fajardo Date: Thu, 20 Jan 2022 15:55:06 +0100 Subject: [PATCH 119/817] fully working backend image posters --- assets/apps_linux.json | 3 ++- assets/apps_windows.json | 2 +- assets/steam.png | Bin 47373 -> 0 bytes sunshine/nvhttp.cpp | 19 ++----------------- sunshine/process.cpp | 31 ++++++++++++++++++++++--------- sunshine/process.h | 2 +- 6 files changed, 28 insertions(+), 29 deletions(-) delete mode 100644 assets/steam.png diff --git a/assets/apps_linux.json b/assets/apps_linux.json index 1cfeb3d4..632ec7b7 100644 --- a/assets/apps_linux.json +++ b/assets/apps_linux.json @@ -13,7 +13,8 @@ "name":"Steam BigPicture", "output":"steam.txt", - "detached":["setsid steam steam://open/bigpicture"] + "detached":["setsid steam steam://open/bigpicture"], + "image-path":"./asset/steam.png" } ] } diff --git a/assets/apps_windows.json b/assets/apps_windows.json index 3dd599a8..f894a518 100644 --- a/assets/apps_windows.json +++ b/assets/apps_windows.json @@ -8,7 +8,7 @@ "output":"steam.txt", "detached":["steam steam://open/bigpicture"], - "image":"steam.png" + "image_path":"./asset/steam.png" } ] } diff --git a/assets/steam.png b/assets/steam.png deleted file mode 100644 index b9d429004269d152e000cc26680d38ff71de3344..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47373 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

^y>>y-vl%`TW$x23W) z{X`}VrMQXh6&`&O-Bub6p@nKNLa%b4@L5M*IgSuOIUHF81ld*7-A=(d7AIqx8khSX zQKmn2g(q?Z6-q%fz~br5Cvr;Z_b3=fuVM(L57bc zE*tqO<(To>mmTtfF9rLU)zks~9xM zdvuq#l2_C&%a~YnCdP*eaObu?`yam-{xjNgTxoxW0SyGEXHiiK7qh-K7VAhyFd;D< zJBQg+Z1&!Se z-N2FMX^1I(1?iL)Mksuk(z#*GXujHu?v4UB0fE`Kmg69$qI9lnS$o4Tb-4wQ%_4ET ze>SyH=Mmj`yLFsWm)X5VL}l^V^@H_V)#OChzqLUpWlv#7^s$O8qK?ru7E4Y?eiZMC zKgydNa+7!$q{iAp$+>tn1Pg9S+*W#{ZZUb*Frv(K($;Lo8c6#oEJ6Xy z6bh&NyE);8qR6+Z=fyd8sXE_B-adNqCN_AU)BFq8Tt+lwI-!M+()h&~P(h}FQN_Eb z_)})W6DqcFCCcup5Oa?ENjPF8Ca!3?hJkX8Z;X>%doQGU%M{L2$(8$%A$q}w#A5#I z%%p_2lLWrJd(Z^&N$-hF-9hIHjFB{=HkLX9a==wdOZz&V`bNURDgcT6#+NKx96?K7i zhfze8&9|AjvZV>!BSeIy{p6|*(AP%6Z2hL%0}V{nA1RELW;9srSiSv+RSs#}V{Vri zzCv%LhE-@1h$@fkr-vX$b7`J&hR0p^`?Osjged*RY&+>ROY?4Kp* z&%+~{D@~}GQzG%~Nl*H*m0dFW^LZ}onJravi@{X2*iyk=B3YRfzE^*KgDl!Tntt2< znNiQB30)U>2F!M{>R2zZlbUL|%5yzE^mdK);=_U89vfRLBQ*Z{?98^uZ_*~16*W8p za-t&Hh9g!pzI452ayZYeyB=J;SW+@|s-<7QZ&GP8e8yn+r(r(tX$@k>VWk&HEU?jG zy-rjas%Ii0<^B6&^~x>0UV-x-?Fj`j<`3}gmi8;>Pp*SWI9JD#?<^lge|ot*5el$^ zKeu^Ukjm-+s`&fq$sWC|I=J=Y1^L11U7ukKDGd7xAm}!b5twAY$JNdK;&ntr93l4P z5|<~C+&YHKFD+@fa6GtgPWL2A@RVjF=G5X|ffNOT1u+>@s5oA^BsUmIZn*@_UGyVbsVCxctxJe1 zL6SozVb-Cq^{P6o55(I#R*K#<+Qp}9rd)aOg$l)_ph0)Kk&+r7Y;z$yJjjtqcI)hr zn{uu`nF_@q%qrC+^*HsYza`?g{f)?zSE4smu7-_%2mszPbG#CoS4b<7JdvtC`TH!u zW6L1#&C8$ZB8nOj3os(B*n~KCF##&B@$iRVNFqGyg-&B;3uB%q`uTpAwDq8#6eOJ*lRm2S1G17> zY)h5RqRnm6W*09L)nlt?C$6aeHkR4U;=#U9S6AgWm33{SFYTsrc=_7FX!%oKbse?( z+z`Z3MO~HuRbftAWgX|zD}HvpRZ-!K!k?3EzrJtu{HGN&mDO^~VGX=~(v|JqRKFG%+>THaV(am`f9lnRsi z&v~5hdJx}thrbJa%gDv)6VF7lOh)Y+bV+|20euQxL~XMi;%!IwPsQ_S{JFK4ZR?Gt zC?D349MYvVT#Uq3zkQYMu+91B-mIu_Wm$nB~=5xd#X{$?S3bD=w zEF>v}T~%puzH0thRL92T8BMUANfL$fxdLCE+pG+Ifg-Eh-vd%^`os01(X5{HL?z#c zqU555QoQpX8bkMV+6miCE?EZ6jh(fBwn@&i#6g{~l?vt>IOyLDQ)eerdMCw%cPDjY zeGfcEaoF+{pX&KfFpe{V>q6;+fETT0xa+*$Kah7H{OyNHTmhp71!NZ5|7I{}8_!}C z(ywkTy20@`?=7OoJ@<;k8g)p zEWp@WP0_h6Y3=8VF@gCqjBc`j@ekvU(shAEIHMs0?LO+8Si@#?E_dAVc+*3+DH2^4 z`J_JT6H>J7{(qj9Z!W#eyK(tXV>o)~ z-Y^%6qy24S!Ab=EJ_Wb{<&rIcb@MY~hGdi1xWH`OKyxanR~@<>_|RXsRTBm?6b2)&PoY)~Cy^>G zQk2V{;!z8ysC*%k|tKE@U;eU9Q)qA`MW=7FL}O{cD&xeVG5S^#@Cm>i!!* z0wosGQ*e{n)avFj_3|itX%#M!OgjAfsb`EgSkt*_jV0pyXd!Lbv-!Xc!L0Q0Io$qF zlCBdm>aS}NNFW+yVotL=OZc=zP!zjy5O_rvep%po=|1z}QTEd1XMjG2Ow=%L{c7lZ zV=C7$ok1HVQFTqW=E36u3oHY{87$bX3}=+*J<7-pug+ob`jp~U`AcHpdA2UZJnjiI zTDc5QxLf6CPNkQpThQXdG3EWNt^aY#0e|%mrF-pX2{DR>FTj1+HxgFS$T&NfE3HKR z@;Og_F$d%lkLvV1uP*f1CW$1Hef=CQXfxx9+m&co-%Ex4xp_^3>|)d=Kvo2I`0iSw zA#HkII=7R{F^dCrRlzj}hG^+PF3YoW*Q;1p9F+X)-2PP+Bi8JE0lRxF*XFL9kG6ai zdp-H#NN5E#*u4`O%NAI`Xy^Lw9lel?5~KEfq4e!vjqaVb6^XY+Z1S#RgTWxz!Z0y@ zhTD10dYD&31!+{P8tMn%{>a)mxr{y^4xB>RhH$biW$cM3qLap?M4!9w-}du)s$w~? zg~1p4tPAC-z=j%I9P(VlH*Ukn>g=ZCnV=Ynl7rpC7FPD@CvvYy@@;(NRMH|K z>z=^7KgIMhXvf8C0N?UJn)SdPBSJIAWap$wU;K|T5-M4Qg7Gb1+&tD%5z6gW_-Qss zse1x9rT>|Jcn2HL z+d_GWvxqjN6;C)7iAXq3sIkx|Mk_7K;b9M>4UOv?F=DLITXk1f+J!BNP^|PZFE{uk z5xicqbAnsR#N+G_M?X5UBk9OgZXY=#b;_G6>`WinO6gUEmIy{ekkn)OOGH}3BKZPE5$WF;3D7^B8g9V@wosc&?N*YVG^Lz~wo~3h$I^1S#XMItqwI1505fR}r_6 zXw-f*k*?SGh1X%~cn+U92v?mb&yg0L=X~q6nxt6^u8HqApoh^Au8B%`s7|68!DuqU zA=d0%7^I95*r6EtQAfAgr)N&8nQ7hXemjFiGt(H52j`ld0acHk+!fySD6qL4s*Iz2 ztPcdg9{|*=q3U^e9`do@kgKtbzV_sbaB_$E(u@bOYiKCl=99L;nz)vnM#jWDO<}k< z?a)Udt5017A2JFiwB1YQGxg#M_pdi=3Y$Ox7Qw45+j3C#ni^4}XZN?U1Rn)?pSCxo z!5`U%wQNNU>E8|6eV7%fo8xG$fMZ2kzbDZ%&byDKF`0kE5f;M#8dc6qJ(2~>URyMc zU}emqW&sjr^*?XUjNFTU=OG4IEypO*E*G{`%{Oz)*ZsUo>RG$@>SybY8IbGqn1!lY zM7kR1eAhmpVdmH9mI=@VPI*g->AFn%H`~4Gp#y^poOm^%rXO-jAPwTJ>nqwGm(RHI ziFSvn*mYOv#lN7#CpwvDRcov$emvT1fsVZ*Xv-C?7u}SE zAsD-3oLEG9O{+n?R0b~U*jW|VTW3nWvukUS!JNeARBo{*QfTh!iss*SkJszZ<-54i zw^y&bl- z+&LK4K|CBcc8dlJYvLwhS@8JS@M2cs@HN|?S7K?T=D0V3zS%O^?Rgu>nU%M!y@v~5 zRnEc-F$2;qowvSkAf_(;am0@GAtUjFqUFHj83x%;QiS?n*J?+B?JiIg_=={$OnpDZjgqX#{4EOW8 zM6dOR2C7d~O{jm9#gZA}qhg*OVpnzDe^nL692eVI{Yr6)%ABA4%ak_`xT`_Pxy$TI zZB>Lyc>+c$6R8zmlIZJ-3a_6|XC%$$l_) zxp78THom}sie@#Z-SanAS4e)J9{}Z-SIm_A;Ls*$c)hAOlKtPXObDMxsW~)vRO`G- z#5Li6jMwIM?hU*nHsmA2Ak`>EC{21*Dyakhc(##W(&64LUHAzHcy@;p7lzW~P}@_e z$S{+!=D6g5Pvh5J^)_tkO?-yL9Xb+DO!B0GZEynbs~U+(cXL*#=)%htkGkZ=ryhQ@ zI&}*D{@cI0wXy%vcA_Hc^n-&0_;!<6RBMj81ou@hI;zr63J_L@kgwdQNOk4Zt!n;a z8~#5Cyh&OMycr`EnFxsnw|*c0q3bax;3cPKV$;D|CF*R#BOrjfp1ojv%)j z>_j^9dgNC5+lKsa%*P#nWG8gMTV7TcbK&nY@13_IFEScc+hCY9<+ya6`6HFe%T>{i zs031+U<`#wmfpj=&%a*vN7utJ^bU*9KRdH0VyK-Cln}Bx28HxfINI4H51Jhad!>!? zAQ+?MP9QJsTt!=4?oAuTLTgLer_Me<9uT8WO=-B(o z3jC#Qk|rCDWMAq2O4_mNLk8Hu_LDy9q8^{_Z0HA9FZgl*?TC4nM1i7*{O=T(^KO3J z!{17DhVpRv4}TJz3j5M22O#Fpx)wR+WN6jw0RAp$YQHD%Zy!nCIXt)S3ySw*tz>0O zWQE1D8GIzxMU(FwuXkW!BX;MhcbtEnmj`E`U7q;yZ074woG4z#T)Irg^5Js|t}&(1 zD4)dq-pj3(ef*dYi&e~`^*0dcnOZbj7D40lB$56G!U^_yPCSp`(@Xk`{89U)vE>a6 zJ0+Pz{_xgo3_b#?U$|1o#NAHlW`3!|f`^HN-uu&OSA1owX@5U^Lk&w!kLa85;;mV{ ze-hzN6~T?yY-CD#(|?DzrupmeS;_Synk70&&=1)x4O;S1OVL#ZpV^66cBw$V&3vme zChM9c3p}aVT1w-NR;XD@cWAAYH3Yg(zoqF2*tvZiV~4eGs8G$A@Uizu-Q4SG3q2}r zNe0Mb=ylS4&u*b3Q&RQzxmDlJeK}+HnnM(#wPvbBJ(x%AAK8ia1uh9P zA%waEY~{_4mC6eU0VmABGoQ#5`*|2~{ifVcBiCPEfV@s*O4K|t@lcfF2?AdHtSk@Smaym&$+>c0j+`1XUtoF^(VE7M|E^Z#7=t zMuot0)gx1Wl+lJW894DR=6KzOV;@X_(X`SJ&o^6+TM>h=&pCjhRf4pWG2ey$?|Xlx zr(Tb4XHblfjC49z z^F_UdKCAy#wGnbeq{4LiaGR~o*64vh~GSknzE%(6BP zYNj4rnHN)F(L@)1LQ@iAl9elKa#OGSsYQTD;!}|+Buf@@wQZKudvilj;|BC?q0erSM_&0DKD+VaAjQsgzPv&zpt%> zpJ(^yj+hr{Ai0D*fHA$PHSuqbH7bt;73~kVJPkT(<@CO6tTgW8fhd5{?0Hvqm6P^|y=*#)cV6Si*VB_} zYz2QYq=R^SHQ#!}0aeR4&v(bpUv|&s-k37d*xEudrr+dzZnmeT8V`Ngi`Z`N;yka~ z`Q7gQU`Qza2Qy$j+(KUVEH=PA5|LzFp-WEwpHSU@0s)9o5;}ubqM`t?t&LHm$O)JN>j!-`v9AE&pO7XQ zx8(qUfRjxp^{;&n5@l2pVbdR3TI8TLJtHG#_vSRZMyn;#sDr1G=9IrtVp;+3feEMwtlfaLn&TMl8sN@i2!)XxM1SgoN* z=ox{UBZdoC@Mu9#5_9;e4T^1#!jO?;YuS;6)G8=m=rNdbq zp(LcD2MYWqvlkKtsQO5!)5w9{j2xJtC;;6OnSNu00X9VtuW`V%%a{f3t|9|4Vu94L zgW$Z}>A*0El4`UaL}})oDo>mp3W0T=DudD-yD(O5n<&SBySWxrfq8#Hwn(u9!4_oL zMA|`+^&44$J!`o+d-BrQsll60pW0?{{wNx0H+KkEQ}!R_J6=~Um)03 zixkKIOFNQhqnvLCIohx=wA&>@-wlV$QjiylcSLbF+Clu+Eet>5fY8}+cr1v%x*=AW z0@|)x7<%ne1Mf~5CZt8N))!=dm-`nv*d!4|%JgP&BnWn>^n_ry7`AyK)?Z<`?|7cqWfo>~PxM}+@)#1K<=g&KI_mrP^?4(m>q_v5CUCq(#yJjDh(BqB<1n*s)l zA$~HSZro|+12=rtZ~N`Ynt^Ti0-bqIMwXMzhL!)ZXE?V;fX4HPldb1n1B~=f7$zFO z0Z)*0SId4L*DCYk`%sF&4&>5Tu8tq znS2N_E(jo#}&QM7X9~1!iOKO6avx5p23Ti}qVt@n1K@vL| zZ~-bn0KIbnS(3eVuqRQn9(r0E^r?Xcs?4m>ClBe2y;<_Y#4quR_&lDW2_P$s6v4*j z%FFj>&D`VNXp`F*zXgzlyZ|@5-n&l@Xr6{tyuR`&VN1q~1UNjR;oCtY8e(_J> z_kR3yX9JE+a1K#~p#n(C`a%^yjyxm9>QpwWGJk=g8B~d4Mh!@@D{Fhm{`Gu7a97Ox zv26?nD80iJiv^GBPbrZpNb*AVGX}NEK~gMtWul7Jo)^3jIAe$jya(yk4~KUikAqjy z50Szg3qUH>2BIR0ZC$g#BFRzWXAFn03giH(6dOo2*Ay}Sem*s{de)c7w&0Us2Q-{5 zAJ~%JBQUO5k}gQ;8e6krEc)24kf z0Ey@n3zj3n4rcpC`3fwMbP$2RQmcg6HSeaudi_A*Y*I)eYs#p4D}LCtZ7YYoubw>g z(6mw-_!0w<_C@2b7jZx^(x4D~Tb}69;2;#)RJiF3{7oT=^iWYGI?VAkT6%BzIR^NF zN_lx3fue@K1$(`91#BkWMjXBqm&Tf^h|xhGv35Ym$c`y|9{lZr7?XEPK<1}GWABtk zI=oj{@B&q<=A^-4_u|5wJOZr2v*3wDV*DIawfnA?KTw{ce?K;cpQts%U*wteOpc$} zYOPq{174rPnGNYXpknk>bhh#!(!H??WBxoBpy*R`NY_AT%N}9yU1W)>KO9Oy0Kv?y z0B91*t&!-OB^@59n~S_VC<*c3N4fy_85^V_{f_5PS*!xeU0jHR&5PuyI=!u6P zX@f_%P=4%wJgA6uG63#L+CbLzswqaE3l0GjwhPi|@jCTLo!gQ`{+*BDrfbh33byUg zaw)bN>_%2Kc3a7q*fr~mJUVuRhe)O$yxA0DuE9Ek8ut$$p|g&lX~RAzw&xy&=HeY| z!r(C}uQ^bN?+i%wf>;b1(O*l1;CZTuK7Ks`QA=&)D0E*DLFLOF01Ry17HGAQbEv6% zKL=XtPiq9k-O-XBA=*KW-JW&^q8^Hopv^LrAHmg5UV_Pc@zq%VH88m^#*4U+ChS%O zHI@vtvMk=EnppD6fw=12s)R2YSAg2<$!`HGtq&Zo#*1Wlb-DGxLL)aZz(PK4E;4T2 z0wSNcgBp={3R?2q8G*=%J%ukRRYl`(G|@r7Nru7aV~s?Ycq=-F@J~x>9sz#`1CUKw z7r%jv^c|FsYS-^b&VOYOQ50A^`5dDG&(W}IHkWUy}=!| zH7rX^DKF*j#^8M`qw#Jtq>&C3ATTg1L}cpB0UN5?l17_NDFcdJhUpO`kelkD&^KWX z1d}bxF=7||#M_~#WBwn2-SImlknjXm$aLp81t9+ME#N7$CB_Bpg0OI*wgwJ-tV$vGK?Bk5Hx;hK!AdWZGHxnq{EJlv zv@F-63wCBwIT-ig6F(!=h*@JWZ4@mbK_iN{z0>eLlkMe9%%CToL7w6ReFn~3^WRKlAk;m^)WBa3%FMU0LUHwIDwF_c1I%07Up}3Vo(Tu-CLi1+!YA!RjJ4g{J@7 z+MCO9fW@sb zpkMPc!kj?-PDd^Ya^QL?F*dhU+g@4p2N?=0<4uB$ZG|F=*&@VISg(CnpxE_O0eNq3 zD>VK!2QMS|MY(Z5MgoZ)^|yw=Y2lQ#vr@BBK9)9}jnmOSS%T^bGS6v4&T76Y!dWAv zeVm8usl5R!{cGNM?W=jiBG^Rt$CUB~o0-u-<`l&+rF>?mnGv?a<95xG+KPie$QT8y zlEb@U*kOX2o0$mq`!(_i!;HWLe35fvG_dX#hhSHqp)xGL+o7=pd=A8>#_A`*AI?o# zgP^=5Qo$gokM1)iG-=&nibE)9Dh^r?vw zs!Wd_kF-rpZzwEM7DJ{lAHBT}%@y?9;(tPilRRG(YIpuSWL+u(EE*eiher}^7vJ3j zT5E*sx}ch4x)wldhZHYMVl?yY5|ay{UtE^s8_ASoeCUY8K6q>2i0`*zBVhsL`C>(_ z)!uSoO+-|)8H0<3-rj?xL<$EMkSj%ZqW~769hrByilV0)7QouZRH%&|Cnz~qP!Qko z^mYxHh4khsGv;hRtpf0<#Jyk%1Q1j{O(k-X*MLdy{Tx#$kUM2$?_#C;cAb6_smtxZMk42sa0;i`Dy<>eVJ)k&k%#a<<@A0aS z0r0G@%(zij)%QHy-a`tswL4B*KmB7mMSG(Q06ty2{h*5NvA|sj zzK?=yqj`^g%-kyq@Gw*rrn`_&sfg@ zSebW3i1!b*dFuAnet8EhktW#fV5F}V34IgiWhU0Y}3EqvHWWs)1AKGzrfb7 zF;heV_%FWZwwV2^#X z9~SYshTRFp{dj-^4lm<`1|-vewRW&q$*wbdMgsWZ9qpa0XLT8~4fYLUO}+_)o@EzoqVHU&aamtGac^c9)t!peVBGdoX$jMdm;QsVQ&iJVjzE$Jpd-l9-zhL`+3bA6Qq8I#lmBpLvhJy* z9|Mio_KZ<|nv!BSJ@CK$;QOzzBmVO}>yV2o|KEQI|BvE~U13l8Hy*_A%Vy3Z=>Kk7 Z1F+}O*)YkvJ?4M@+UojhRVubo{~xdJ_5=U` diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 6e549fc9..ddc5dd41 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -761,29 +761,14 @@ void cancel(resp_https_t response, req_https_t request) { } } -#define CHECK_EXPECTED_EXTENTIONS(extention) (extention == "png" || extention == "jpg" || extention == "jpeg") void appasset(resp_https_t response, req_https_t request) { print_req(request); auto args = request->parse_query_string(); - auto appid = util::from_view(args.at("appid")) - 1; - auto app_image = proc::proc.get_app_image(appid); - if (app_image.empty()) { - app_image = "box.png"; - } + auto app_image = proc::proc.get_app_image(util::from_view(args.at("appid"))); - auto file_path = SUNSHINE_ASSETS_DIR "/" + app_image; - auto image_extention = std::filesystem::path(file_path).extension().string(); - image_extention = image_extention.substr(1, image_extention.length() - 1); - - std::error_code code; - if (!std::filesystem::exists(file_path, code) || !CHECK_EXPECTED_EXTENTIONS(image_extention)) { - response->write(SimpleWeb::StatusCode::client_error_not_found); - return; - } - - std::ifstream in(file_path, std::ios::binary); + std::ifstream in(app_image, std::ios::binary); response->write(SimpleWeb::StatusCode::success_ok, in); response->close_connection_after_response = true; } diff --git a/sunshine/process.cpp b/sunshine/process.cpp index 81ee4cd2..324b1364 100644 --- a/sunshine/process.cpp +++ b/sunshine/process.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -189,19 +190,31 @@ std::vector &proc_t::get_apps() { return _apps; } +#define CHECK_EXPECTED_EXTENTIONS(extention) (extention == "png" || extention == "jpg" || extention == "jpeg") + /// Gets application image from application list. /// Returns default image if image configuration is not set. std::string proc_t::get_app_image(int app_id) { - if(app_id < 0 || app_id >= _apps.size()) { + auto app_index = app_id -1; + if(app_index < 0 || app_index >= _apps.size()) { BOOST_LOG(error) << "Couldn't find app with ID ["sv << app_id << ']'; - return "box.png"; + return SUNSHINE_ASSETS_DIR "/box.png"; + } + + auto app_image_path = _apps[app_index].image_path; + if (app_image_path.empty()) { + return SUNSHINE_ASSETS_DIR "/box.png"; } - auto app_image = _apps[app_id].image; - if (app_image.empty()) { - return "box.png"; + auto image_extention = std::filesystem::path(app_image_path).extension().string(); + image_extention = image_extention.substr(1, image_extention.length() - 1); + + std::error_code code; + if (!std::filesystem::exists(app_image_path, code) || !CHECK_EXPECTED_EXTENTIONS(image_extention)) { + return SUNSHINE_ASSETS_DIR "/box.png"; } - return app_image; + + return app_image_path; } proc_t::~proc_t() { @@ -294,7 +307,7 @@ std::optional parse(const std::string &file_name) { auto output = app_node.get_optional("output"s); auto name = parse_env_val(this_env, app_node.get("name"s)); auto cmd = app_node.get_optional("cmd"s); - auto image = app_node.get_optional("image"s); + auto image_path = app_node.get_optional("image-path"s); auto working_dir = app_node.get_optional("working-dir"s); std::vector prep_cmds; @@ -337,8 +350,8 @@ std::optional parse(const std::string &file_name) { ctx.working_dir = parse_env_val(this_env, *working_dir); } - if (image) { - ctx.image = parse_env_val(this_env, *image); + if (image_path) { + ctx.image_path = parse_env_val(this_env, *image_path); } ctx.name = std::move(name); diff --git a/sunshine/process.h b/sunshine/process.h index b45e0c04..2b3fdad8 100644 --- a/sunshine/process.h +++ b/sunshine/process.h @@ -55,7 +55,7 @@ struct ctx_t { std::string cmd; std::string working_dir; std::string output; - std::string image; + std::string image_path; }; class proc_t { From a9bbadf8ad85d30009e60de413646467ac86d18f Mon Sep 17 00:00:00 2001 From: Christophe Fajardo Date: Thu, 20 Jan 2022 16:15:44 +0100 Subject: [PATCH 120/817] WebUI image path configuration added --- assets/web/apps.html | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/assets/web/apps.html b/assets/web/apps.html index c87dbc89..1e72584e 100644 --- a/assets/web/apps.html +++ b/assets/web/apps.html @@ -166,6 +166,22 @@

Applications

If not set, Sunshine will default to the parent directory of the command + +
+ + +
+ Application icon/picture/image path that will be sent to client. + Only full path are working, so no relative path. + If not set, Sunshine will send default box image. +
+
diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 55e83d78..8afdb35a 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -766,11 +766,11 @@ void appasset(resp_https_t response, req_https_t request) { print_req(request); auto args = request->parse_query_string(); - auto [ app_image, image_content_type ] = proc::proc.get_app_image(util::from_view(args.at("appid"))); + auto app_image = proc::proc.get_app_image(util::from_view(args.at("appid"))); std::ifstream in(app_image, std::ios::binary); SimpleWeb::CaseInsensitiveMultimap headers; - headers.emplace("Content-Type", "image/" + image_content_type); + headers.emplace("Content-Type", "image/png"); response->write(SimpleWeb::StatusCode::success_ok, in, headers); response->close_connection_after_response = true; } diff --git a/sunshine/process.cpp b/sunshine/process.cpp index 9e217d10..386c43c7 100644 --- a/sunshine/process.cpp +++ b/sunshine/process.cpp @@ -193,28 +193,28 @@ std::vector &proc_t::get_apps() { /// Gets application image from application list. /// Returns default image if image configuration is not set. /// returns http content-type header compatible image type -std::tuple proc_t::get_app_image(int app_id) { +std::string proc_t::get_app_image(int app_id) { auto app_index = app_id -1; if(app_index < 0 || app_index >= _apps.size()) { BOOST_LOG(error) << "Couldn't find app with ID ["sv << app_id << ']'; - return { SUNSHINE_ASSETS_DIR "/box.png", "png" }; + return SUNSHINE_ASSETS_DIR "/box.png"; } auto app_image_path = _apps[app_index].image_path; if (app_image_path.empty()) { - return { SUNSHINE_ASSETS_DIR "/box.png", "png" }; + return SUNSHINE_ASSETS_DIR "/box.png"; } - auto image_extention = std::filesystem::path(app_image_path).extension().string(); - image_extention = image_extention.substr(1, image_extention.length() - 1); + auto image_extension = std::filesystem::path(app_image_path).extension().string(); + image_extension = image_extension.substr(1, image_extension.length() - 1); std::error_code code; - if (!std::filesystem::exists(app_image_path, code) || !CHECK_EXPECTED_PICTURE_EXTENTIONS(image_extention)) { - return { SUNSHINE_ASSETS_DIR "/box.png", "png" }; + if (!std::filesystem::exists(app_image_path, code) || image_extension != "png") { + return SUNSHINE_ASSETS_DIR "/box.png"; } // return only "content-type" http header compatible image type. - return { app_image_path, image_extention == "jpg" ? "jpeg" : image_extention }; + return app_image_path; } proc_t::~proc_t() { diff --git a/sunshine/process.h b/sunshine/process.h index 00fdbe80..2b3fdad8 100644 --- a/sunshine/process.h +++ b/sunshine/process.h @@ -16,8 +16,6 @@ #include "utility.h" -#define CHECK_EXPECTED_PICTURE_EXTENTIONS(extention) (extention == "png" || extention == "jpg" || extention == "jpeg") - namespace proc { using file_t = util::safe_ptr_v2; @@ -81,7 +79,7 @@ class proc_t { const std::vector &get_apps() const; std::vector &get_apps(); - std::tuple get_app_image(int app_id); + std::string get_app_image(int app_id); void terminate(); From f4344ade53df7d8a8bef7241730640eda028e4d1 Mon Sep 17 00:00:00 2001 From: Christophe Fajardo Date: Tue, 25 Jan 2022 21:13:54 +0100 Subject: [PATCH 131/817] tolower on extension --- sunshine/process.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sunshine/process.cpp b/sunshine/process.cpp index 386c43c7..dc7fee71 100644 --- a/sunshine/process.cpp +++ b/sunshine/process.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "main.h" #include "utility.h" @@ -206,7 +207,7 @@ std::string proc_t::get_app_image(int app_id) { } auto image_extension = std::filesystem::path(app_image_path).extension().string(); - image_extension = image_extension.substr(1, image_extension.length() - 1); + boost::to_lower(image_extension); std::error_code code; if (!std::filesystem::exists(app_image_path, code) || image_extension != "png") { From 59959d639766051a200412ca98ee61fa7dae2b23 Mon Sep 17 00:00:00 2001 From: Christophe Fajardo Date: Tue, 1 Feb 2022 23:14:05 +0100 Subject: [PATCH 132/817] extension bugfix and default app_windows.json image-path fixed --- assets/apps_windows.json | 2 +- sunshine/process.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/apps_windows.json b/assets/apps_windows.json index f894a518..c653d04a 100644 --- a/assets/apps_windows.json +++ b/assets/apps_windows.json @@ -8,7 +8,7 @@ "output":"steam.txt", "detached":["steam steam://open/bigpicture"], - "image_path":"./asset/steam.png" + "image-path":"./asset/steam.png" } ] } diff --git a/sunshine/process.cpp b/sunshine/process.cpp index dc7fee71..f94b84fd 100644 --- a/sunshine/process.cpp +++ b/sunshine/process.cpp @@ -210,7 +210,7 @@ std::string proc_t::get_app_image(int app_id) { boost::to_lower(image_extension); std::error_code code; - if (!std::filesystem::exists(app_image_path, code) || image_extension != "png") { + if (!std::filesystem::exists(app_image_path, code) || image_extension != ".png") { return SUNSHINE_ASSETS_DIR "/box.png"; } From 398bf9819eb9a69cadd2d6cb3a1899b8a742855a Mon Sep 17 00:00:00 2001 From: Christophe Fajardo Date: Wed, 2 Feb 2022 23:02:49 +0100 Subject: [PATCH 133/817] steam.png added --- assets/apps_windows.json | 2 +- assets/steam.png | Bin 0 -> 25012 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 assets/steam.png diff --git a/assets/apps_windows.json b/assets/apps_windows.json index c653d04a..419f2be4 100644 --- a/assets/apps_windows.json +++ b/assets/apps_windows.json @@ -8,7 +8,7 @@ "output":"steam.txt", "detached":["steam steam://open/bigpicture"], - "image-path":"./asset/steam.png" + "image-path":"./assets/steam.png" } ] } diff --git a/assets/steam.png b/assets/steam.png new file mode 100644 index 0000000000000000000000000000000000000000..c7d4a44dcbe524b5eaeec940edb0f30041995343 GIT binary patch literal 25012 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¨x9QO_=pbNR@JY^Ix@*xsRoSmo)5wKn1CL@N!#f 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 Date: Wed, 2 Feb 2022 23:04:25 +0100 Subject: [PATCH 134/817] apps page image path documentation update --- assets/web/apps.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/assets/web/apps.html b/assets/web/apps.html index fb6380ee..524020a1 100644 --- a/assets/web/apps.html +++ b/assets/web/apps.html @@ -177,8 +177,7 @@

Applications

v-model="editForm['image-path']" />
- Application icon/picture/image path that will be sent to client. - Only full path are working, so no relative path. Image must be a PNG. + Application icon/picture/image path that will be sent to client. Image must be a PNG file. If not set, Sunshine will send default box image.
From a9ebdf67851745eefe60042cc4ffee4948bf9e62 Mon Sep 17 00:00:00 2001 From: Christophe Fajardo Date: Thu, 3 Feb 2022 09:58:57 +0100 Subject: [PATCH 135/817] fixed apps_linux typo --- assets/apps_linux.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/apps_linux.json b/assets/apps_linux.json index 632ec7b7..552dabe2 100644 --- a/assets/apps_linux.json +++ b/assets/apps_linux.json @@ -14,7 +14,7 @@ "output":"steam.txt", "detached":["setsid steam steam://open/bigpicture"], - "image-path":"./asset/steam.png" + "image-path":"./assets/steam.png" } ] } From 3b2c70422a7c143eb8c499ad42ecf67b673c8fed Mon Sep 17 00:00:00 2001 From: Christophe Fajardo Date: Thu, 3 Feb 2022 22:02:36 +0100 Subject: [PATCH 136/817] remove double quotes from path --- assets/web/apps.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/web/apps.html b/assets/web/apps.html index 524020a1..9c21fe63 100644 --- a/assets/web/apps.html +++ b/assets/web/apps.html @@ -223,6 +223,7 @@

Applications

index: -1, "prep-cmd": [], detached: [], + "image-path": "" }; this.editForm.index = -1; this.showEditForm = true; @@ -253,6 +254,7 @@

Applications

}); }, save() { + this.editForm["image-path"] = this.editForm["image-path"].toString().replace(/"/g, ''); fetch("/api/apps", { method: "POST", body: JSON.stringify(this.editForm), @@ -272,4 +274,4 @@

Applications

.monospace { font-family: monospace; } - + \ No newline at end of file From cf859b7c132b25994e6e8798d417fae71b98b202 Mon Sep 17 00:00:00 2001 From: Christophe Fajardo Date: Thu, 3 Feb 2022 22:15:13 +0100 Subject: [PATCH 137/817] NOTICE file added with Steam trademarks notice --- CMakeLists.txt | 1 + NOTICE | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 NOTICE diff --git a/CMakeLists.txt b/CMakeLists.txt index 0115a264..7890a5ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -364,6 +364,7 @@ endif() list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR}") list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_CONFIG_DIR="${SUNSHINE_CONFIG_DIR}") list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_DEFAULT_DIR="${SUNSHINE_DEFAULT_DIR}") +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/NOTICE" "NOTICE") add_executable(sunshine ${SUNSHINE_TARGET_FILES}) target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES}) target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS}) diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000..7ad027d7 --- /dev/null +++ b/NOTICE @@ -0,0 +1,3 @@ +©2018 Valve Corporation. Steam and the Steam logo are trademarks and/or +registered trademarks of Valve Corporation in the U.S. and/or other countries. All +rights reserved. \ No newline at end of file From bd8ba660230c4d5d73d5dadc32be4b920c0b9326 Mon Sep 17 00:00:00 2001 From: okkiv <68665660+okkiv@users.noreply.github.com> Date: Fri, 4 Feb 2022 11:53:49 +0100 Subject: [PATCH 138/817] fetch correct sources --- scripts/build-sunshine.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build-sunshine.sh b/scripts/build-sunshine.sh index e8df86cd..df21df3c 100755 --- a/scripts/build-sunshine.sh +++ b/scripts/build-sunshine.sh @@ -26,7 +26,7 @@ absolute_path() { CMAKE_BUILD_TYPE="-e CMAKE_BUILD_TYPE=Release" SUNSHINE_PACKAGE_BUILD=OFF -SUNSHINE_GIT_URL=https://github.com/loki-47-6F-64/sunshine.git +SUNSHINE_GIT_URL=https://github.com/sunshinestream/sunshine.git CONTAINER_NAME=sunshine # Docker will fail if ctrl+c is passed through and the input is not a tty From c3d498e30c1f75fe598e5dabb466a10c8e4c5685 Mon Sep 17 00:00:00 2001 From: okkiv <68665660+okkiv@users.noreply.github.com> Date: Fri, 4 Feb 2022 11:54:20 +0100 Subject: [PATCH 139/817] fetch correct sources --- scripts/build-private.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build-private.sh b/scripts/build-private.sh index ade64829..69446de6 100755 --- a/scripts/build-private.sh +++ b/scripts/build-private.sh @@ -8,7 +8,7 @@ SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR:-/etc/sunshine}" SUNSHINE_ROOT="${SUNSHINE_ROOT:-/root/sunshine}" SUNSHINE_TAG="${SUNSHINE_TAG:-master}" -SUNSHINE_GIT_URL="${SUNSHINE_GIT_URL:-https://github.com/loki-47-6F-64/sunshine.git}" +SUNSHINE_GIT_URL="${SUNSHINE_GIT_URL:-https://github.com/sunshinestream/sunshine.git}" SUNSHINE_ENABLE_WAYLAND=${SUNSHINE_ENABLE_WAYLAND:-ON} From 9f4ad530cad3b26165638df83fc7e762b1219bae Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 5 Feb 2022 18:18:00 -0500 Subject: [PATCH 140/817] Update web ui - Add fontawesome - Add favicon - Add logo - Add support and legal links to index.html - Improve layout of index.html - Improve layout of navbar --- assets/web/header.html | 18 +++-- assets/web/images/favicon.ico | Bin 0 -> 22486 bytes assets/web/images/logo-sunshine-45.png | Bin 0 -> 2115 bytes assets/web/index.html | 31 ++++++-- .../font-awesome.5.15.4.all.min.css | 5 ++ sunshine/confighttp.cpp | 72 ++++++++++++------ 6 files changed, 90 insertions(+), 36 deletions(-) create mode 100644 assets/web/images/favicon.ico create mode 100644 assets/web/images/logo-sunshine-45.png create mode 100644 assets/web/third_party/font-awesome.5.15.4.all.min.css diff --git a/assets/web/header.html b/assets/web/header.html index a66117cb..d017dee0 100644 --- a/assets/web/header.html +++ b/assets/web/header.html @@ -5,6 +5,8 @@ Sunshine + + @@ -16,7 +18,9 @@ style="background-color: #ffc400" >
diff --git a/assets/web/images/favicon.ico b/assets/web/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a934d318df3dda85eea2ce3951f5477589108ace GIT binary patch literal 22486 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 literal 0 HcmV?d00001 diff --git a/assets/web/images/logo-sunshine-45.png b/assets/web/images/logo-sunshine-45.png new file mode 100644 index 0000000000000000000000000000000000000000..a4a62c9672faaeb5eead1967ad95ce1dcf3dfc93 GIT binary patch literal 2115 zcmV-J2)y@+P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D2iQqOK~!i%*Pzh;@82XSU zN$r$Am88gfeB-~1Uv;jOs8G_TCtBbj?uPMN z;AF4^1^9`i)|2QyoFx6~sLi$s3~~^lE2`ltWRb(!Sc$V@6H%+-WPkk5qwaNSQ6Z#F z4%MuIdr0mmT46c%;0u2y;%+)E_;+pCjHY#0@(r<#Uu+1K|5zVLgL0APht}d7dZR5r zyc_j1$fMG;{QIb98Nm14&SoN4r7q6p8q~rBjt7(cDSe;_{xxZHB{jVa_j8f2NooQ>?3vj~TB63`t znluSlV>6lGw+@S%IG?hCQC0A`!`9F+EZ`cGklt;rS0c62p8o(c(UDuK;4{A+Hfyiy z(3+(|CR-an_L^-TZm3D17-_1U&3GNHTdAF$q1UQ}N78i|O;Oasq1{>1Nu~5-yjvUf zwJ!_vrS_@_<~`E&a2^Y<>KuxjCRj=^YONF@O_l4i8N~WF@%UES9_>{fx|5nmhs)*gGYi`BI#E1qp3>Gp_pijwZ5*E97hs8W?# zF?M8%%O&ac3_dBzkmT9>Sf*-77LulGG+|?R9q}fMN~teWA=o%d&Xr?yGYRlLEmtSP zQfY&0BT9Fs`ReM{p9QsDTj>anjqZW{xK;;;v540$+Sh!cSn1jb=?<=SF{)_p+De7_ z0S)M_ukiFED(_^~;j?Y1t|8%QvDmc{(wFE}!Y(X{TAF;#D3u1Yl(``TldxS+hms$r zu`uhUlVL)TAk4GfDCtJ7MbfEEVz-;^RuXFBc`DphPnQ8dc{M&{?+#tw zBE)DfP>QkG#2cEUydzkr2iYMiQ9Ze*SWk=W${}gb4}Yt$$DCx0H~Fzvu3s*gwT&CF znZx%F*D@@z=$X z1D~5mPn_JbiWIuy7v4N*=Qv)5slf`II?+!V$=6bN8{o&LxIrO8(n0)Z*R$A8i*7A) zz|14@yJ@N2Dh13ClFj3(;})i@EX+jQG}3KC(wiV4^(}Z1#{zMUm=v$)+R~Z@Str;A{ckm-fI`ISFw|D*pB-Db~2RPQe z6DD~c^YS7kET1SrX9?+bu3*X3lw@w=?HvBq`BY3c zTlqiBcG~Z}w~}G@BW5!X6mYu+?xfjshXkuUZ8Nuc{?oMAsBW>{t~5BUawdlgd){*h z0x85LiAf%7s#qPJpU=T;{f -
-
-

Hello, Sunshine!

-

Sunshine is a Gamestream host for Moonlight

- Official Website +

Hello, Sunshine!

+

Sunshine is a Gamestream host for Moonlight

+ +
+
+

Resources

+
+

+ Resources for Sunshine! +

+ Official Website + Github Discussions + Sunshine Discord + Moonlight Discord +
+
+ +
+
+

Legal

+
+

+ By continuing to use this software you agree to the terms and conditions in the following documents. +

+ License + Third Party Notice
diff --git a/assets/web/third_party/font-awesome.5.15.4.all.min.css b/assets/web/third_party/font-awesome.5.15.4.all.min.css new file mode 100644 index 00000000..ac76ff19 --- /dev/null +++ b/assets/web/third_party/font-awesome.5.15.4.all.min.css @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +.fa,.fab,.fad,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-bacteria:before{content:"\e059"}.fa-bacterium:before{content:"\e05a"}.fa-bahai:before{content:"\f666"}.fa-balance-scale:before{content:"\f24e"}.fa-balance-scale-left:before{content:"\f515"}.fa-balance-scale-right:before{content:"\f516"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-battle-net:before{content:"\f835"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-biking:before{content:"\f84a"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bootstrap:before{content:"\f836"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before{content:"\f853"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-box-tissue:before{content:"\e05b"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buy-n-large:before{content:"\f8a6"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caravan:before{content:"\f8ff"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudflare:before{content:"\e07d"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-alt:before{content:"\f422"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-cotton-bureau:before{content:"\f89e"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dailymotion:before{content:"\e052"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-deezer:before{content:"\e077"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-disease:before{content:"\f7fa"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edge-legacy:before{content:"\e078"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-evernote:before{content:"\f839"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-alt:before{content:"\f424"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fan:before{content:"\f863"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-faucet:before{content:"\e005"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-firefox-browser:before{content:"\e007"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-pay:before{content:"\e079"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guilded:before{content:"\e07e"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-medical:before{content:"\e05c"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-holding-water:before{content:"\f4c1"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-sparkles:before{content:"\e05d"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-hands-wash:before{content:"\e05e"}.fa-handshake:before{content:"\f2b5"}.fa-handshake-alt-slash:before{content:"\e05f"}.fa-handshake-slash:before{content:"\e060"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-hat-wizard:before{content:"\f6e8"}.fa-hdd:before{content:"\f0a0"}.fa-head-side-cough:before{content:"\e061"}.fa-head-side-cough-slash:before{content:"\e062"}.fa-head-side-mask:before{content:"\e063"}.fa-head-side-virus:before{content:"\e064"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hive:before{content:"\e07f"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hospital-user:before{content:"\f80d"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-house-user:before{content:"\e065"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-ideal:before{content:"\e013"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-innosoft:before{content:"\e080"}.fa-instagram:before{content:"\f16d"}.fa-instagram-square:before{content:"\e055"}.fa-instalod:before{content:"\e081"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-house:before{content:"\e066"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lungs:before{content:"\f604"}.fa-lungs-virus:before{content:"\e067"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-mdb:before{content:"\f8ca"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microblog:before{content:"\e01a"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mixer:before{content:"\e056"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse:before{content:"\f8cc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-octopus-deploy:before{content:"\e082"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-orcid:before{content:"\f8d2"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-arrows:before{content:"\e068"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-perbyte:before{content:"\e083"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-square-alt:before{content:"\f87b"}.fa-phone-volume:before{content:"\f2a0"}.fa-photo-video:before{content:"\f87c"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-square:before{content:"\e01e"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-plane-slash:before{content:"\e069"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pump-medical:before{content:"\e06a"}.fa-pump-soap:before{content:"\e06b"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-record-vinyl:before{content:"\f8d9"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-remove-format:before{content:"\f87d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-rust:before{content:"\e07a"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-shield-virus:before{content:"\e06c"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopify:before{content:"\e057"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sink:before{content:"\e06d"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-soap:before{content:"\e06e"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-down-alt:before{content:"\f884"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-amount-up-alt:before{content:"\f885"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-stopwatch-20:before{content:"\e06f"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-store-alt-slash:before{content:"\e070"}.fa-store-slash:before{content:"\e071"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swift:before{content:"\f8e1"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-symfony:before{content:"\f83d"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-tiktok:before{content:"\e07b"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toilet-paper-slash:before{content:"\e072"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-trailer:before{content:"\e041"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbraco:before{content:"\f8e8"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-uncharted:before{content:"\e084"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-unity:before{content:"\e049"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-unsplash:before{content:"\e07c"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-users-slash:before{content:"\e073"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-vest:before{content:"\e085"}.fa-vest-patches:before{content:"\e086"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-virus:before{content:"\e074"}.fa-virus-slash:before{content:"\e075"}.fa-viruses:before{content:"\e076"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-voicemail:before{content:"\f897"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-watchman-monitoring:before{content:"\e087"}.fa-water:before{content:"\f773"}.fa-wave-square:before{content:"\f83e"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wodu:before{content:"\e088"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.fab,.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} \ No newline at end of file diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index c4440b95..d51cfb50 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -221,6 +221,27 @@ void getTroubleshootingPage(resp_https_t response, req_https_t request) { response->write(header + content); } +void getFaviconImage(resp_https_t response, req_https_t request) { + print_req(request); + + std::string content = read_file(WEB_DIR "images/favicon.ico"); + response->write(content); +} + +void getSunshineLogoImage(resp_https_t response, req_https_t request) { + print_req(request); + + std::string content = read_file(WEB_DIR "images/logo-sunshine-45.png"); + response->write(content); +} + +void getFontAwesomeCss(resp_https_t response, req_https_t request) { + print_req(request); + + std::string content = read_file(WEB_DIR "third_party/font-awesome.5.15.4.all.min.css"); + response->write(content); +} + void getBootstrapCss(resp_https_t response, req_https_t request) { print_req(request); @@ -554,30 +575,33 @@ void start() { ctx->use_certificate_chain_file(config::nvhttp.cert); ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem); https_server_t server { ctx, 0 }; - server.default_resource = not_found; - server.resource["^/$"]["GET"] = getIndexPage; - server.resource["^/pin$"]["GET"] = getPinPage; - server.resource["^/apps$"]["GET"] = getAppsPage; - server.resource["^/clients$"]["GET"] = getClientsPage; - server.resource["^/config$"]["GET"] = getConfigPage; - server.resource["^/password$"]["GET"] = getPasswordPage; - server.resource["^/welcome$"]["GET"] = getWelcomePage; - server.resource["^/troubleshooting$"]["GET"] = getTroubleshootingPage; - server.resource["^/api/pin"]["POST"] = savePin; - server.resource["^/api/apps$"]["GET"] = getApps; - server.resource["^/api/apps$"]["POST"] = saveApp; - server.resource["^/api/config$"]["GET"] = getConfig; - server.resource["^/api/config$"]["POST"] = saveConfig; - server.resource["^/api/password$"]["POST"] = savePassword; - server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; - server.resource["^/api/clients/unpair$"]["POST"] = unpairAll; - server.resource["^/api/apps/close"]["POST"] = closeApp; - server.resource["^/third_party/bootstrap.min.css$"]["GET"] = getBootstrapCss; - server.resource["^/third_party/bootstrap.bundle.min.js$"]["GET"] = getBootstrapJs; - server.resource["^/third_party/vue.js$"]["GET"] = getVueJs; - server.config.reuse_address = true; - server.config.address = "0.0.0.0"s; - server.config.port = port_https; + server.default_resource = not_found; + server.resource["^/$"]["GET"] = getIndexPage; + server.resource["^/pin$"]["GET"] = getPinPage; + server.resource["^/apps$"]["GET"] = getAppsPage; + server.resource["^/clients$"]["GET"] = getClientsPage; + server.resource["^/config$"]["GET"] = getConfigPage; + server.resource["^/password$"]["GET"] = getPasswordPage; + server.resource["^/welcome$"]["GET"] = getWelcomePage; + server.resource["^/troubleshooting$"]["GET"] = getTroubleshootingPage; + server.resource["^/api/pin"]["POST"] = savePin; + server.resource["^/api/apps$"]["GET"] = getApps; + server.resource["^/api/apps$"]["POST"] = saveApp; + server.resource["^/api/config$"]["GET"] = getConfig; + server.resource["^/api/config$"]["POST"] = saveConfig; + server.resource["^/api/password$"]["POST"] = savePassword; + server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; + server.resource["^/api/clients/unpair$"]["POST"] = unpairAll; + server.resource["^/api/apps/close"]["POST"] = closeApp; + server.resource["^/images/favicon.ico$"]["GET"] = getFaviconImage; + server.resource["^/images/logo-sunshine-45.png$"]["GET"] = getSunshineLogoImage; + server.resource["^/third_party/font-awesome.5.15.4.all.min.css$"]["GET"] = getFontAwesomeCss; + server.resource["^/third_party/bootstrap.min.css$"]["GET"] = getBootstrapCss; + server.resource["^/third_party/bootstrap.bundle.min.js$"]["GET"] = getBootstrapJs; + server.resource["^/third_party/vue.js$"]["GET"] = getVueJs; + server.config.reuse_address = true; + server.config.address = "0.0.0.0"s; + server.config.port = port_https; try { server.bind(); From b5896c8f2668086ecf93fee71e0ea23b17827c71 Mon Sep 17 00:00:00 2001 From: Christophe Fajardo Date: Sun, 6 Feb 2022 21:28:03 +0100 Subject: [PATCH 141/817] remove NOTICE from CMakeLists.txt --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7890a5ae..0115a264 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -364,7 +364,6 @@ endif() list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR}") list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_CONFIG_DIR="${SUNSHINE_CONFIG_DIR}") list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_DEFAULT_DIR="${SUNSHINE_DEFAULT_DIR}") -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/NOTICE" "NOTICE") add_executable(sunshine ${SUNSHINE_TARGET_FILES}) target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES}) target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS}) From 37e608a88996c7ec9f6a057e97aefdc0621f297a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 6 Feb 2022 17:23:57 -0500 Subject: [PATCH 142/817] Update CHANGELOG.md --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5203de4b..aac89caf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,13 @@ # Changelog ## [0.12.0] - 2022-01-20 +### Added +- New command line argument `--version` +- Custom png poster support ### Changed -- Increase vbv-bufsize to 1/10 of requested bitrate - Correct software bitrate calculation +- Increase vbv-bufsize to 1/10 of requested bitrate +- Improvements to Web UI ## [0.11.1] - 2021-10-04 ### Changed From b288993b86c4a0a065111e9d91e900d156d33b8d Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 6 Feb 2022 17:32:55 -0500 Subject: [PATCH 143/817] Rename workflow --- .github/workflows/{create_package.yml => CI.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{create_package.yml => CI.yml} (99%) diff --git a/.github/workflows/create_package.yml b/.github/workflows/CI.yml similarity index 99% rename from .github/workflows/create_package.yml rename to .github/workflows/CI.yml index 74e48f5d..4e2c05a3 100644 --- a/.github/workflows/create_package.yml +++ b/.github/workflows/CI.yml @@ -1,4 +1,4 @@ -name: Create Package +name: CI on: pull_request: From a9988cb3466a6beaed5551217af76497a48280e6 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 6 Feb 2022 17:42:20 -0500 Subject: [PATCH 144/817] Update issues-stale.yml - Change time of scheduled trigger --- .github/workflows/issues-stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml index 126af260..02aa6ca8 100644 --- a/.github/workflows/issues-stale.yml +++ b/.github/workflows/issues-stale.yml @@ -2,7 +2,7 @@ name: Stale Issues / PRs on: schedule: - - cron: '00 19 * * *' + - cron: '00 00 * * *' jobs: stale: From b19803564ccd7667dcdf8e66a2d0193c1323f5b6 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 6 Feb 2022 17:42:38 -0500 Subject: [PATCH 145/817] Update issues.yml - Label discussions --- .github/workflows/issues.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml index 728cd8c9..e009fbf2 100644 --- a/.github/workflows/issues.yml +++ b/.github/workflows/issues.yml @@ -1,15 +1,17 @@ -name: Issues +name: Label Actions on: issues: types: [labeled, unlabeled] + discussion: + types: [ labeled, unlabeled ] jobs: label: - name: Label Issues + name: Label Actions runs-on: ubuntu-latest steps: - - name: Label Issues + - name: Label Actions uses: dessant/label-actions@v2 with: github-token: ${{ github.token }} From 89f05711e0437f419710953ab62e7621cc2f74af Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 8 Feb 2022 21:35:00 -0500 Subject: [PATCH 146/817] Use cdn for fontawesome --- assets/web/header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/web/header.html b/assets/web/header.html index d017dee0..59c1eeaf 100644 --- a/assets/web/header.html +++ b/assets/web/header.html @@ -6,7 +6,7 @@ Sunshine - + From a577f76d717f517dd564a92aea309995b8d01688 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 8 Feb 2022 21:35:48 -0500 Subject: [PATCH 147/817] Fix favicon and logo --- sunshine/confighttp.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index d51cfb50..02ad6813 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -224,22 +224,19 @@ void getTroubleshootingPage(resp_https_t response, req_https_t request) { void getFaviconImage(resp_https_t response, req_https_t request) { print_req(request); - std::string content = read_file(WEB_DIR "images/favicon.ico"); - response->write(content); + std::ifstream in(WEB_DIR "images/favicon.ico", std::ios::binary); + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "image/x-icon"); + response->write(SimpleWeb::StatusCode::success_ok, in, headers); } void getSunshineLogoImage(resp_https_t response, req_https_t request) { print_req(request); - std::string content = read_file(WEB_DIR "images/logo-sunshine-45.png"); - response->write(content); -} - -void getFontAwesomeCss(resp_https_t response, req_https_t request) { - print_req(request); - - std::string content = read_file(WEB_DIR "third_party/font-awesome.5.15.4.all.min.css"); - response->write(content); + std::ifstream in(WEB_DIR "images/logo-sunshine-45.png", std::ios::binary); + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "image/png"); + response->write(SimpleWeb::StatusCode::success_ok, in, headers); } void getBootstrapCss(resp_https_t response, req_https_t request) { @@ -595,7 +592,6 @@ void start() { server.resource["^/api/apps/close"]["POST"] = closeApp; server.resource["^/images/favicon.ico$"]["GET"] = getFaviconImage; server.resource["^/images/logo-sunshine-45.png$"]["GET"] = getSunshineLogoImage; - server.resource["^/third_party/font-awesome.5.15.4.all.min.css$"]["GET"] = getFontAwesomeCss; server.resource["^/third_party/bootstrap.min.css$"]["GET"] = getBootstrapCss; server.resource["^/third_party/bootstrap.bundle.min.js$"]["GET"] = getBootstrapJs; server.resource["^/third_party/vue.js$"]["GET"] = getVueJs; From f3470893129198595d86387b4d46f85f7be2a3d5 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 8 Feb 2022 23:08:36 -0500 Subject: [PATCH 148/817] Fix font-awesome stylesheet --- assets/web/header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/web/header.html b/assets/web/header.html index 59c1eeaf..867a39b7 100644 --- a/assets/web/header.html +++ b/assets/web/header.html @@ -6,7 +6,7 @@ Sunshine - + From 830fa6567b766636ec0baf5467b233b1141009fd Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 8 Feb 2022 23:09:10 -0500 Subject: [PATCH 149/817] Fix ico file - All sizes available on separate layers --- assets/web/images/favicon.ico | Bin 22486 -> 120760 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/web/images/favicon.ico b/assets/web/images/favicon.ico index a934d318df3dda85eea2ce3951f5477589108ace..4d07b0852816e4d952accfca4e5703ca7c123412 100644 GIT binary patch literal 120760 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 z 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 literal 22486 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 From 91465ed2577ddcc7fd506a310f122c4e924f19fa Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 9 Feb 2022 19:44:05 -0500 Subject: [PATCH 150/817] Update config.html - Fix some typos - Reformat some tags --- assets/web/config.html | 109 +++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 69 deletions(-) diff --git a/assets/web/config.html b/assets/web/config.html index 39a2f58f..12290d59 100644 --- a/assets/web/config.html +++ b/assets/web/config.html @@ -26,8 +26,7 @@

Configuration

v-model="config.sunshine_name" />
- The name displayed by Moonlight. If not specified, the PC's hostname - is used + The name displayed by Moonlight. If not specified, the PC's hostname is used
@@ -65,8 +64,7 @@

Configuration

- The origin of the remote endpoint address that is not denied access to - Web UI + The origin of the remote endpoint address that is not denied access to Web UI
@@ -98,8 +96,7 @@

Configuration

v-model="config.ping_timeout" />
- 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
@@ -184,9 +181,8 @@

Configuration

The display modes advertised by Sunshine
- Some versions of Moonlight, such as Moonlight-nx (Switch), rely on - this list to ensure that the requested resolutions and fps are - supported.
+ Some versions of Moonlight, such as Moonlight-nx (Switch), rely on this list to ensure that the requested + resolutions and fps are supported.
This setting does not change how the screen stream is sent to Moonlight
@@ -205,10 +201,8 @@

Configuration

- It may be possible that you cannot send the Windows Key from Moonlight - directly.
- In those cases it may be usefull to make Sunshine think the Right Alt - key is the Windows key + It may be possible that you cannot send the Windows Key from Moonlight directly.
+ In those cases it may be useful to make Sunshine think the Right Alt key is the Windows key
@@ -284,12 +278,9 @@

Configuration

/>
The back/select button on the controller.
- On the Shield, the home and powerbutton are not passed to - Moonlight.
- If, after the timeout, the back button is still pressed down, - Home/Guide button press is emulated.
- If back_button_timeout < 0, then the Home/Guide button will not be - emulated
+ On the Shield, the home and power button are not passed to Moonlight.
+ If, after the timeout, the back button is still pressed down, Home/Guide button press is emulated.
+ If back_button_timeout < 0, then the Home/Guide button will not be emulated
@@ -341,8 +332,7 @@

Configuration

/>
The name of the audio sink used for Audio Loopback
- You can find the name of the audio sink using the following - command:
+ You can find the name of the audio sink using the following command:
tools\audio-info.exe
@@ -357,8 +347,7 @@

Configuration

/>
The name of the audio sink used for Audio Loopback
- If you do not specify this variable, pulseaudio will select the - default monitor device.
+ If you do not specify this variable, pulseaudio will select the default monitor device.

You can find the name of the audio sink using either command:
pacmd list-sinks | grep "name:"
@@ -377,9 +366,8 @@

Configuration

v-model="config.virtual_sink" />
- The virtual sink, is the audio device that's virtual (Like Steam - Streaming Speakers), it allows Sunshine to stream audio, while muting - the speakers. + The virtual sink, is the audio device that's virtual (Like Steam Streaming Speakers), it allows Sunshine to + stream audio, while muting the speakers.
@@ -411,7 +399,8 @@

Configuration

You can select the video card you want to stream:
The appropriate values can be found using the following command:
- tools\dxgi-info.exe

+ tools\dxgi-info.exe
+
@@ -431,7 +420,7 @@

Configuration

- +
Configuration placeholder="47989" v-model="config.port" /> -
Set the familly of ports used by Sunshine
+
Set the family of ports used by Sunshine
@@ -477,15 +466,12 @@

Configuration

/>
Minimum number of threads used by ffmpeg to encode the video.
- Increasing the value slightly reduces encoding efficiency, but the - tradeoff is usually
- worth it to gain the use of more CPU cores for encoding. The ideal - value is the lowest
- value that can reliably encode at your desired streaming settings on - your hardware. + Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually
+ worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest
+ value that can reliably encode at your desired streaming settings on your hardware.
- +
- Allows the client to request HEVC Main or HEVC Main10 video - streams.
- HEVC is more CPU-intensive to encode, so enabling this may reduce - performance when using software encoding. + Allows the client to request HEVC Main or HEVC Main10 video streams.
+ HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding.
@@ -521,8 +504,7 @@

Configuration

- Force a specific encoder, otherwise Sunshine will use the first - encoder that is available + Force a specific encoder, otherwise Sunshine will use the first encoder that is available
@@ -536,10 +518,8 @@

Configuration

v-model="config.fec_percentage" />
- Percentage of error correcting packets per data packet in each video - frame.
- Higher values can correct for more network packet loss, but at the - cost of increasing bandwidth usage.
+ Percentage of error correcting packets per data packet in each video frame.
+ Higher values can correct for more network packet loss, but at the cost of increasing bandwidth usage.
The default value of 20 is what GeForce Experience uses.
@@ -554,17 +534,16 @@

Configuration

v-model="config.channels" />
- When multicasting, it could be useful to have different configurations - for each connected Client. For example: + When multicasting, it could be useful to have different configurations for each connected Client. For example:
  • - Clients connected through WAN and LAN have different bitrate - contstraints. + Clients connected through WAN and LAN have different bitrate constraints. +
  • +
  • + Decoders may require different settings for color
  • -
  • Decoders may require different settings for color
- Unlike simply broadcasting to multiple Client, this will generate - distinct video streams.
+ Unlike simply broadcasting to multiple Client, this will generate distinct video streams.
Note, CPU usage increases for each distinct video stream generated
@@ -581,7 +560,7 @@

Configuration

v-model="config.credentials_file" />
- Store Username/Password seperately from Sunshine's state file. + Store Username/Password separately from Sunshine's state file.
@@ -599,8 +578,7 @@

Configuration

- The origin of the remote endpoint address that is not denied for HTTP - method /pin + The origin of the remote endpoint address that is not denied for HTTP method /pin
@@ -614,8 +592,7 @@

Configuration

v-model="config.external_ip" />
- If no external IP address is given, Sunshine will automatically detect - external IP + If no external IP address is given, Sunshine will automatically detect external IP
@@ -680,9 +657,7 @@

Configuration

- + @@ -715,12 +690,8 @@

Configuration

From e239751f50220a961a01bbc4d08cb2a2e58cc345 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 9 Feb 2022 19:44:45 -0500 Subject: [PATCH 151/817] Update sunshine.conf - Rename `amd_preset` to `amd_quality` - Document `key_rightalt_to_key_win` --- assets/sunshine.conf | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/sunshine.conf b/assets/sunshine.conf index d8a1f4b4..ccb20fad 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -91,6 +91,10 @@ # 0x4A, 0x4B # ] +# It may be possible that you cannot send the Windows Key from Moonlight directly. +# In those cases it may be useful to make Sunshine think the Right Alt key is the Windows key +# key_rightalt_to_key_win = enabled + # How long to wait in milliseconds for data from moonlight before shutting down the stream # ping_timeout = 10000 @@ -244,7 +248,7 @@ # speed # balanced ########################## -# amd_preset = balanced +# amd_quality = balanced # ####### rate control ##### # auto -- let ffmpeg decide rate control From 7417430659603fa28ab6c868a1d3cf8388be4487 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 10 Feb 2022 18:32:50 -0500 Subject: [PATCH 152/817] Update .gitignore --- .gitignore | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ad68b535..6fb681e7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,14 @@ cmake-build* *.kdev4 .cache -.idea \ No newline at end of file +.idea + +# Extra FontAwesome files +/assets/web/fonts/fontawesome-free-web/css/*.css +!/assets/web/fonts/fontawesome-free-web/css/*min.css +/assets/web/fonts/fontawesome-free-web/js/ +/assets/web/fonts/fontawesome-free-web/less/ +/assets/web/fonts/fontawesome-free-web/metadata/ +/assets/web/fonts/fontawesome-free-web/scss/ +/assets/web/fonts/fontawesome-free-web/sprites/ +/assets/web/fonts/fontawesome-free-web/svgs/ From 8b423315d09e41547d2310da4f7ab0b8a03f6237 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 10 Feb 2022 18:34:35 -0500 Subject: [PATCH 153/817] Use local fontawesome assets --- .../fonts/fontawesome-free-web/LICENSE.txt | 34 + .../fonts/fontawesome-free-web/attribution.js | 3 + .../fontawesome-free-web/css/all.min.css} | 0 .../fontawesome-free-web/css/brands.min.css | 5 + .../css/fontawesome.min.css | 5 + .../fontawesome-free-web/css/regular.min.css | 5 + .../fontawesome-free-web/css/solid.min.css | 5 + .../css/svg-with-js.min.css | 5 + .../fontawesome-free-web/css/v4-shims.min.css | 5 + .../webfonts/fa-brands-400.eot | Bin 0 -> 134294 bytes .../webfonts/fa-brands-400.svg | 3717 ++++++++++++ .../webfonts/fa-brands-400.ttf | Bin 0 -> 133988 bytes .../webfonts/fa-brands-400.woff | Bin 0 -> 89988 bytes .../webfonts/fa-brands-400.woff2 | Bin 0 -> 76736 bytes .../webfonts/fa-regular-400.eot | Bin 0 -> 34034 bytes .../webfonts/fa-regular-400.svg | 801 +++ .../webfonts/fa-regular-400.ttf | Bin 0 -> 33736 bytes .../webfonts/fa-regular-400.woff | Bin 0 -> 16276 bytes .../webfonts/fa-regular-400.woff2 | Bin 0 -> 13224 bytes .../webfonts/fa-solid-900.eot | Bin 0 -> 203030 bytes .../webfonts/fa-solid-900.svg | 5034 +++++++++++++++++ .../webfonts/fa-solid-900.ttf | Bin 0 -> 202744 bytes .../webfonts/fa-solid-900.woff | Bin 0 -> 101648 bytes .../webfonts/fa-solid-900.woff2 | Bin 0 -> 78268 bytes assets/web/header.html | 2 +- sunshine/confighttp.cpp | 28 + 26 files changed, 9648 insertions(+), 1 deletion(-) create mode 100644 assets/web/fonts/fontawesome-free-web/LICENSE.txt create mode 100644 assets/web/fonts/fontawesome-free-web/attribution.js rename assets/web/{third_party/font-awesome.5.15.4.all.min.css => fonts/fontawesome-free-web/css/all.min.css} (100%) create mode 100644 assets/web/fonts/fontawesome-free-web/css/brands.min.css create mode 100644 assets/web/fonts/fontawesome-free-web/css/fontawesome.min.css create mode 100644 assets/web/fonts/fontawesome-free-web/css/regular.min.css create mode 100644 assets/web/fonts/fontawesome-free-web/css/solid.min.css create mode 100644 assets/web/fonts/fontawesome-free-web/css/svg-with-js.min.css create mode 100644 assets/web/fonts/fontawesome-free-web/css/v4-shims.min.css create mode 100644 assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.eot create mode 100644 assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.svg create mode 100644 assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.ttf create mode 100644 assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff create mode 100644 assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff2 create mode 100644 assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.eot create mode 100644 assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.svg create mode 100644 assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.ttf create mode 100644 assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff create mode 100644 assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff2 create mode 100644 assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.eot create mode 100644 assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.svg create mode 100644 assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.ttf create mode 100644 assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff create mode 100644 assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff2 diff --git a/assets/web/fonts/fontawesome-free-web/LICENSE.txt b/assets/web/fonts/fontawesome-free-web/LICENSE.txt new file mode 100644 index 00000000..f31bef92 --- /dev/null +++ b/assets/web/fonts/fontawesome-free-web/LICENSE.txt @@ -0,0 +1,34 @@ +Font Awesome Free License +------------------------- + +Font Awesome Free is free, open source, and GPL friendly. You can use it for +commercial projects, open source projects, or really almost whatever you want. +Full Font Awesome Free license: https://fontawesome.com/license/free. + +# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/) +In the Font Awesome Free download, the CC BY 4.0 license applies to all icons +packaged as SVG and JS file types. + +# Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL) +In the Font Awesome Free download, the SIL OFL license applies to all icons +packaged as web and desktop font files. + +# Code: MIT License (https://opensource.org/licenses/MIT) +In the Font Awesome Free download, the MIT license applies to all non-font and +non-icon files. + +# Attribution +Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font +Awesome Free files already contain embedded comments with sufficient +attribution, so you shouldn't need to do anything additional when using these +files normally. + +We've kept attribution comments terse, so we ask that you do not actively work +to remove them from files, especially code. They're a great way for folks to +learn about Font Awesome. + +# Brand Icons +All brand icons are trademarks of their respective owners. The use of these +trademarks does not indicate endorsement of the trademark holder by Font +Awesome, nor vice versa. **Please do not use brand logos for any purpose except +to represent the company, product, or service to which they refer.** diff --git a/assets/web/fonts/fontawesome-free-web/attribution.js b/assets/web/fonts/fontawesome-free-web/attribution.js new file mode 100644 index 00000000..741c81f8 --- /dev/null +++ b/assets/web/fonts/fontawesome-free-web/attribution.js @@ -0,0 +1,3 @@ +console.log(`Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com +License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) +`) \ No newline at end of file diff --git a/assets/web/third_party/font-awesome.5.15.4.all.min.css b/assets/web/fonts/fontawesome-free-web/css/all.min.css similarity index 100% rename from assets/web/third_party/font-awesome.5.15.4.all.min.css rename to assets/web/fonts/fontawesome-free-web/css/all.min.css diff --git a/assets/web/fonts/fontawesome-free-web/css/brands.min.css b/assets/web/fonts/fontawesome-free-web/css/brands.min.css new file mode 100644 index 00000000..bdd4a243 --- /dev/null +++ b/assets/web/fonts/fontawesome-free-web/css/brands.min.css @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands";font-weight:400} \ No newline at end of file diff --git a/assets/web/fonts/fontawesome-free-web/css/fontawesome.min.css b/assets/web/fonts/fontawesome-free-web/css/fontawesome.min.css new file mode 100644 index 00000000..bec9b39e --- /dev/null +++ b/assets/web/fonts/fontawesome-free-web/css/fontawesome.min.css @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +.fa,.fab,.fad,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-bacteria:before{content:"\e059"}.fa-bacterium:before{content:"\e05a"}.fa-bahai:before{content:"\f666"}.fa-balance-scale:before{content:"\f24e"}.fa-balance-scale-left:before{content:"\f515"}.fa-balance-scale-right:before{content:"\f516"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-battle-net:before{content:"\f835"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-biking:before{content:"\f84a"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bootstrap:before{content:"\f836"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before{content:"\f853"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-box-tissue:before{content:"\e05b"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buy-n-large:before{content:"\f8a6"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caravan:before{content:"\f8ff"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudflare:before{content:"\e07d"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-alt:before{content:"\f422"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-cotton-bureau:before{content:"\f89e"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dailymotion:before{content:"\e052"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-deezer:before{content:"\e077"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-disease:before{content:"\f7fa"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edge-legacy:before{content:"\e078"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-evernote:before{content:"\f839"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-alt:before{content:"\f424"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fan:before{content:"\f863"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-faucet:before{content:"\e005"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-firefox-browser:before{content:"\e007"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-pay:before{content:"\e079"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guilded:before{content:"\e07e"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-medical:before{content:"\e05c"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-holding-water:before{content:"\f4c1"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-sparkles:before{content:"\e05d"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-hands-wash:before{content:"\e05e"}.fa-handshake:before{content:"\f2b5"}.fa-handshake-alt-slash:before{content:"\e05f"}.fa-handshake-slash:before{content:"\e060"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-hat-wizard:before{content:"\f6e8"}.fa-hdd:before{content:"\f0a0"}.fa-head-side-cough:before{content:"\e061"}.fa-head-side-cough-slash:before{content:"\e062"}.fa-head-side-mask:before{content:"\e063"}.fa-head-side-virus:before{content:"\e064"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hive:before{content:"\e07f"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hospital-user:before{content:"\f80d"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-house-user:before{content:"\e065"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-ideal:before{content:"\e013"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-innosoft:before{content:"\e080"}.fa-instagram:before{content:"\f16d"}.fa-instagram-square:before{content:"\e055"}.fa-instalod:before{content:"\e081"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-house:before{content:"\e066"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lungs:before{content:"\f604"}.fa-lungs-virus:before{content:"\e067"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-mdb:before{content:"\f8ca"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microblog:before{content:"\e01a"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mixer:before{content:"\e056"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse:before{content:"\f8cc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-octopus-deploy:before{content:"\e082"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-orcid:before{content:"\f8d2"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-arrows:before{content:"\e068"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-perbyte:before{content:"\e083"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-square-alt:before{content:"\f87b"}.fa-phone-volume:before{content:"\f2a0"}.fa-photo-video:before{content:"\f87c"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-square:before{content:"\e01e"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-plane-slash:before{content:"\e069"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pump-medical:before{content:"\e06a"}.fa-pump-soap:before{content:"\e06b"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-record-vinyl:before{content:"\f8d9"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-remove-format:before{content:"\f87d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-rust:before{content:"\e07a"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-shield-virus:before{content:"\e06c"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopify:before{content:"\e057"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sink:before{content:"\e06d"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-soap:before{content:"\e06e"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-down-alt:before{content:"\f884"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-amount-up-alt:before{content:"\f885"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-stopwatch-20:before{content:"\e06f"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-store-alt-slash:before{content:"\e070"}.fa-store-slash:before{content:"\e071"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swift:before{content:"\f8e1"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-symfony:before{content:"\f83d"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-tiktok:before{content:"\e07b"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toilet-paper-slash:before{content:"\e072"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-trailer:before{content:"\e041"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbraco:before{content:"\f8e8"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-uncharted:before{content:"\e084"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-unity:before{content:"\e049"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-unsplash:before{content:"\e07c"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-users-slash:before{content:"\e073"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-vest:before{content:"\e085"}.fa-vest-patches:before{content:"\e086"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-virus:before{content:"\e074"}.fa-virus-slash:before{content:"\e075"}.fa-viruses:before{content:"\e076"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-voicemail:before{content:"\f897"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-watchman-monitoring:before{content:"\e087"}.fa-water:before{content:"\f773"}.fa-wave-square:before{content:"\f83e"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wodu:before{content:"\e088"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto} \ No newline at end of file diff --git a/assets/web/fonts/fontawesome-free-web/css/regular.min.css b/assets/web/fonts/fontawesome-free-web/css/regular.min.css new file mode 100644 index 00000000..21881d5a --- /dev/null +++ b/assets/web/fonts/fontawesome-free-web/css/regular.min.css @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-family:"Font Awesome 5 Free";font-weight:400} \ No newline at end of file diff --git a/assets/web/fonts/fontawesome-free-web/css/solid.min.css b/assets/web/fonts/fontawesome-free-web/css/solid.min.css new file mode 100644 index 00000000..acdb46dc --- /dev/null +++ b/assets/web/fonts/fontawesome-free-web/css/solid.min.css @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.fas{font-family:"Font Awesome 5 Free";font-weight:900} \ No newline at end of file diff --git a/assets/web/fonts/fontawesome-free-web/css/svg-with-js.min.css b/assets/web/fonts/fontawesome-free-web/css/svg-with-js.min.css new file mode 100644 index 00000000..101beb83 --- /dev/null +++ b/assets/web/fonts/fontawesome-free-web/css/svg-with-js.min.css @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +.svg-inline--fa,svg:not(:root).svg-inline--fa{overflow:visible}.svg-inline--fa{display:inline-block;font-size:inherit;height:1em;vertical-align:-.125em}.svg-inline--fa.fa-lg{vertical-align:-.225em}.svg-inline--fa.fa-w-1{width:.0625em}.svg-inline--fa.fa-w-2{width:.125em}.svg-inline--fa.fa-w-3{width:.1875em}.svg-inline--fa.fa-w-4{width:.25em}.svg-inline--fa.fa-w-5{width:.3125em}.svg-inline--fa.fa-w-6{width:.375em}.svg-inline--fa.fa-w-7{width:.4375em}.svg-inline--fa.fa-w-8{width:.5em}.svg-inline--fa.fa-w-9{width:.5625em}.svg-inline--fa.fa-w-10{width:.625em}.svg-inline--fa.fa-w-11{width:.6875em}.svg-inline--fa.fa-w-12{width:.75em}.svg-inline--fa.fa-w-13{width:.8125em}.svg-inline--fa.fa-w-14{width:.875em}.svg-inline--fa.fa-w-15{width:.9375em}.svg-inline--fa.fa-w-16{width:1em}.svg-inline--fa.fa-w-17{width:1.0625em}.svg-inline--fa.fa-w-18{width:1.125em}.svg-inline--fa.fa-w-19{width:1.1875em}.svg-inline--fa.fa-w-20{width:1.25em}.svg-inline--fa.fa-pull-left{margin-right:.3em;width:auto}.svg-inline--fa.fa-pull-right{margin-left:.3em;width:auto}.svg-inline--fa.fa-border{height:1.5em}.svg-inline--fa.fa-li{width:2em}.svg-inline--fa.fa-fw{width:1.25em}.fa-layers svg.svg-inline--fa{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.fa-layers{display:inline-block;height:1em;position:relative;text-align:center;vertical-align:-.125em;width:1em}.fa-layers svg.svg-inline--fa{-webkit-transform-origin:center center;transform-origin:center center}.fa-layers-counter,.fa-layers-text{display:inline-block;position:absolute;text-align:center}.fa-layers-text{left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);-webkit-transform-origin:center center;transform-origin:center center}.fa-layers-counter{background-color:#ff253a;border-radius:1em;-webkit-box-sizing:border-box;box-sizing:border-box;color:#fff;height:1.5em;line-height:1;max-width:5em;min-width:1.5em;overflow:hidden;padding:.25em;right:0;text-overflow:ellipsis;top:0;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:top right;transform-origin:top right}.fa-layers-bottom-right{bottom:0;right:0;top:auto;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:bottom right;transform-origin:bottom right}.fa-layers-bottom-left{bottom:0;left:0;right:auto;top:auto;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:bottom left;transform-origin:bottom left}.fa-layers-top-right{right:0;top:0;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:top right;transform-origin:top right}.fa-layers-top-left{left:0;right:auto;top:0;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:top left;transform-origin:top left}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;position:relative;width:2.5em}.fa-stack-1x,.fa-stack-2x{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.svg-inline--fa.fa-stack-1x{height:1em;width:1.25em}.svg-inline--fa.fa-stack-2x{height:2em;width:2.5em}.fa-inverse{color:#fff}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.svg-inline--fa .fa-primary{fill:var(--fa-primary-color,currentColor);opacity:1;opacity:var(--fa-primary-opacity,1)}.svg-inline--fa .fa-secondary{fill:var(--fa-secondary-color,currentColor)}.svg-inline--fa .fa-secondary,.svg-inline--fa.fa-swap-opacity .fa-primary{opacity:.4;opacity:var(--fa-secondary-opacity,.4)}.svg-inline--fa.fa-swap-opacity .fa-secondary{opacity:1;opacity:var(--fa-primary-opacity,1)}.svg-inline--fa mask .fa-primary,.svg-inline--fa mask .fa-secondary{fill:#000}.fad.fa-inverse{color:#fff} \ No newline at end of file diff --git a/assets/web/fonts/fontawesome-free-web/css/v4-shims.min.css b/assets/web/fonts/fontawesome-free-web/css/v4-shims.min.css new file mode 100644 index 00000000..21a0708a --- /dev/null +++ b/assets/web/fonts/fontawesome-free-web/css/v4-shims.min.css @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +.fa.fa-glass:before{content:"\f000"}.fa.fa-meetup{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-star-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-star-o:before{content:"\f005"}.fa.fa-close:before,.fa.fa-remove:before{content:"\f00d"}.fa.fa-gear:before{content:"\f013"}.fa.fa-trash-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-trash-o:before{content:"\f2ed"}.fa.fa-file-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-o:before{content:"\f15b"}.fa.fa-clock-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-clock-o:before{content:"\f017"}.fa.fa-arrow-circle-o-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-arrow-circle-o-down:before{content:"\f358"}.fa.fa-arrow-circle-o-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-arrow-circle-o-up:before{content:"\f35b"}.fa.fa-play-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-play-circle-o:before{content:"\f144"}.fa.fa-repeat:before,.fa.fa-rotate-right:before{content:"\f01e"}.fa.fa-refresh:before{content:"\f021"}.fa.fa-list-alt{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-dedent:before{content:"\f03b"}.fa.fa-video-camera:before{content:"\f03d"}.fa.fa-picture-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-picture-o:before{content:"\f03e"}.fa.fa-photo{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-photo:before{content:"\f03e"}.fa.fa-image{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-image:before{content:"\f03e"}.fa.fa-pencil:before{content:"\f303"}.fa.fa-map-marker:before{content:"\f3c5"}.fa.fa-pencil-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-pencil-square-o:before{content:"\f044"}.fa.fa-share-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-share-square-o:before{content:"\f14d"}.fa.fa-check-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-check-square-o:before{content:"\f14a"}.fa.fa-arrows:before{content:"\f0b2"}.fa.fa-times-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-times-circle-o:before{content:"\f057"}.fa.fa-check-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-check-circle-o:before{content:"\f058"}.fa.fa-mail-forward:before{content:"\f064"}.fa.fa-expand:before{content:"\f424"}.fa.fa-compress:before{content:"\f422"}.fa.fa-eye,.fa.fa-eye-slash{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-warning:before{content:"\f071"}.fa.fa-calendar:before{content:"\f073"}.fa.fa-arrows-v:before{content:"\f338"}.fa.fa-arrows-h:before{content:"\f337"}.fa.fa-bar-chart{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bar-chart:before{content:"\f080"}.fa.fa-bar-chart-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bar-chart-o:before{content:"\f080"}.fa.fa-facebook-square,.fa.fa-twitter-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-gears:before{content:"\f085"}.fa.fa-thumbs-o-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-thumbs-o-up:before{content:"\f164"}.fa.fa-thumbs-o-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-thumbs-o-down:before{content:"\f165"}.fa.fa-heart-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-heart-o:before{content:"\f004"}.fa.fa-sign-out:before{content:"\f2f5"}.fa.fa-linkedin-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-linkedin-square:before{content:"\f08c"}.fa.fa-thumb-tack:before{content:"\f08d"}.fa.fa-external-link:before{content:"\f35d"}.fa.fa-sign-in:before{content:"\f2f6"}.fa.fa-github-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-lemon-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-lemon-o:before{content:"\f094"}.fa.fa-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-square-o:before{content:"\f0c8"}.fa.fa-bookmark-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bookmark-o:before{content:"\f02e"}.fa.fa-facebook,.fa.fa-twitter{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-facebook:before{content:"\f39e"}.fa.fa-facebook-f{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-facebook-f:before{content:"\f39e"}.fa.fa-github{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-credit-card{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-feed:before{content:"\f09e"}.fa.fa-hdd-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hdd-o:before{content:"\f0a0"}.fa.fa-hand-o-right{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-o-right:before{content:"\f0a4"}.fa.fa-hand-o-left{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-o-left:before{content:"\f0a5"}.fa.fa-hand-o-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-o-up:before{content:"\f0a6"}.fa.fa-hand-o-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-o-down:before{content:"\f0a7"}.fa.fa-arrows-alt:before{content:"\f31e"}.fa.fa-group:before{content:"\f0c0"}.fa.fa-chain:before{content:"\f0c1"}.fa.fa-scissors:before{content:"\f0c4"}.fa.fa-files-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-files-o:before{content:"\f0c5"}.fa.fa-floppy-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-floppy-o:before{content:"\f0c7"}.fa.fa-navicon:before,.fa.fa-reorder:before{content:"\f0c9"}.fa.fa-google-plus,.fa.fa-google-plus-square,.fa.fa-pinterest,.fa.fa-pinterest-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-google-plus:before{content:"\f0d5"}.fa.fa-money{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-money:before{content:"\f3d1"}.fa.fa-unsorted:before{content:"\f0dc"}.fa.fa-sort-desc:before{content:"\f0dd"}.fa.fa-sort-asc:before{content:"\f0de"}.fa.fa-linkedin{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-linkedin:before{content:"\f0e1"}.fa.fa-rotate-left:before{content:"\f0e2"}.fa.fa-legal:before{content:"\f0e3"}.fa.fa-dashboard:before,.fa.fa-tachometer:before{content:"\f3fd"}.fa.fa-comment-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-comment-o:before{content:"\f075"}.fa.fa-comments-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-comments-o:before{content:"\f086"}.fa.fa-flash:before{content:"\f0e7"}.fa.fa-clipboard,.fa.fa-paste{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-paste:before{content:"\f328"}.fa.fa-lightbulb-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-lightbulb-o:before{content:"\f0eb"}.fa.fa-exchange:before{content:"\f362"}.fa.fa-cloud-download:before{content:"\f381"}.fa.fa-cloud-upload:before{content:"\f382"}.fa.fa-bell-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bell-o:before{content:"\f0f3"}.fa.fa-cutlery:before{content:"\f2e7"}.fa.fa-file-text-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-text-o:before{content:"\f15c"}.fa.fa-building-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-building-o:before{content:"\f1ad"}.fa.fa-hospital-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hospital-o:before{content:"\f0f8"}.fa.fa-tablet:before{content:"\f3fa"}.fa.fa-mobile-phone:before,.fa.fa-mobile:before{content:"\f3cd"}.fa.fa-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-circle-o:before{content:"\f111"}.fa.fa-mail-reply:before{content:"\f3e5"}.fa.fa-github-alt{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-folder-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-folder-o:before{content:"\f07b"}.fa.fa-folder-open-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-folder-open-o:before{content:"\f07c"}.fa.fa-smile-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-smile-o:before{content:"\f118"}.fa.fa-frown-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-frown-o:before{content:"\f119"}.fa.fa-meh-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-meh-o:before{content:"\f11a"}.fa.fa-keyboard-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-keyboard-o:before{content:"\f11c"}.fa.fa-flag-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-flag-o:before{content:"\f024"}.fa.fa-mail-reply-all:before{content:"\f122"}.fa.fa-star-half-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-star-half-o:before{content:"\f089"}.fa.fa-star-half-empty{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-star-half-empty:before{content:"\f089"}.fa.fa-star-half-full{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-star-half-full:before{content:"\f089"}.fa.fa-code-fork:before{content:"\f126"}.fa.fa-chain-broken:before{content:"\f127"}.fa.fa-shield:before{content:"\f3ed"}.fa.fa-calendar-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-o:before{content:"\f133"}.fa.fa-css3,.fa.fa-html5,.fa.fa-maxcdn{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-ticket:before{content:"\f3ff"}.fa.fa-minus-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-minus-square-o:before{content:"\f146"}.fa.fa-level-up:before{content:"\f3bf"}.fa.fa-level-down:before{content:"\f3be"}.fa.fa-pencil-square:before{content:"\f14b"}.fa.fa-external-link-square:before{content:"\f360"}.fa.fa-compass{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-down:before{content:"\f150"}.fa.fa-toggle-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-toggle-down:before{content:"\f150"}.fa.fa-caret-square-o-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-up:before{content:"\f151"}.fa.fa-toggle-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-toggle-up:before{content:"\f151"}.fa.fa-caret-square-o-right{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-right:before{content:"\f152"}.fa.fa-toggle-right{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-toggle-right:before{content:"\f152"}.fa.fa-eur:before,.fa.fa-euro:before{content:"\f153"}.fa.fa-gbp:before{content:"\f154"}.fa.fa-dollar:before,.fa.fa-usd:before{content:"\f155"}.fa.fa-inr:before,.fa.fa-rupee:before{content:"\f156"}.fa.fa-cny:before,.fa.fa-jpy:before,.fa.fa-rmb:before,.fa.fa-yen:before{content:"\f157"}.fa.fa-rouble:before,.fa.fa-rub:before,.fa.fa-ruble:before{content:"\f158"}.fa.fa-krw:before,.fa.fa-won:before{content:"\f159"}.fa.fa-bitcoin,.fa.fa-btc{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-bitcoin:before{content:"\f15a"}.fa.fa-file-text:before{content:"\f15c"}.fa.fa-sort-alpha-asc:before{content:"\f15d"}.fa.fa-sort-alpha-desc:before{content:"\f881"}.fa.fa-sort-amount-asc:before{content:"\f160"}.fa.fa-sort-amount-desc:before{content:"\f884"}.fa.fa-sort-numeric-asc:before{content:"\f162"}.fa.fa-sort-numeric-desc:before{content:"\f886"}.fa.fa-xing,.fa.fa-xing-square,.fa.fa-youtube,.fa.fa-youtube-play,.fa.fa-youtube-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-youtube-play:before{content:"\f167"}.fa.fa-adn,.fa.fa-bitbucket,.fa.fa-bitbucket-square,.fa.fa-dropbox,.fa.fa-flickr,.fa.fa-instagram,.fa.fa-stack-overflow{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-bitbucket-square:before{content:"\f171"}.fa.fa-tumblr,.fa.fa-tumblr-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-long-arrow-down:before{content:"\f309"}.fa.fa-long-arrow-up:before{content:"\f30c"}.fa.fa-long-arrow-left:before{content:"\f30a"}.fa.fa-long-arrow-right:before{content:"\f30b"}.fa.fa-android,.fa.fa-apple,.fa.fa-dribbble,.fa.fa-foursquare,.fa.fa-gittip,.fa.fa-gratipay,.fa.fa-linux,.fa.fa-skype,.fa.fa-trello,.fa.fa-windows{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-gittip:before{content:"\f184"}.fa.fa-sun-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-sun-o:before{content:"\f185"}.fa.fa-moon-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-moon-o:before{content:"\f186"}.fa.fa-pagelines,.fa.fa-renren,.fa.fa-stack-exchange,.fa.fa-vk,.fa.fa-weibo{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-arrow-circle-o-right{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-arrow-circle-o-right:before{content:"\f35a"}.fa.fa-arrow-circle-o-left{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-arrow-circle-o-left:before{content:"\f359"}.fa.fa-caret-square-o-left{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-left:before{content:"\f191"}.fa.fa-toggle-left{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-toggle-left:before{content:"\f191"}.fa.fa-dot-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-dot-circle-o:before{content:"\f192"}.fa.fa-vimeo-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-try:before,.fa.fa-turkish-lira:before{content:"\f195"}.fa.fa-plus-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-plus-square-o:before{content:"\f0fe"}.fa.fa-openid,.fa.fa-slack,.fa.fa-wordpress{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-bank:before,.fa.fa-institution:before{content:"\f19c"}.fa.fa-mortar-board:before{content:"\f19d"}.fa.fa-delicious,.fa.fa-digg,.fa.fa-drupal,.fa.fa-google,.fa.fa-joomla,.fa.fa-pied-piper-alt,.fa.fa-pied-piper-pp,.fa.fa-reddit,.fa.fa-reddit-square,.fa.fa-stumbleupon,.fa.fa-stumbleupon-circle,.fa.fa-yahoo{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-spoon:before{content:"\f2e5"}.fa.fa-behance,.fa.fa-behance-square,.fa.fa-steam,.fa.fa-steam-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-automobile:before{content:"\f1b9"}.fa.fa-envelope-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-envelope-o:before{content:"\f0e0"}.fa.fa-deviantart,.fa.fa-soundcloud,.fa.fa-spotify{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-file-pdf-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-pdf-o:before{content:"\f1c1"}.fa.fa-file-word-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-word-o:before{content:"\f1c2"}.fa.fa-file-excel-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-excel-o:before{content:"\f1c3"}.fa.fa-file-powerpoint-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-powerpoint-o:before{content:"\f1c4"}.fa.fa-file-image-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-image-o:before{content:"\f1c5"}.fa.fa-file-photo-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-photo-o:before{content:"\f1c5"}.fa.fa-file-picture-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-picture-o:before{content:"\f1c5"}.fa.fa-file-archive-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-archive-o:before{content:"\f1c6"}.fa.fa-file-zip-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-zip-o:before{content:"\f1c6"}.fa.fa-file-audio-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-audio-o:before{content:"\f1c7"}.fa.fa-file-sound-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-sound-o:before{content:"\f1c7"}.fa.fa-file-video-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-video-o:before{content:"\f1c8"}.fa.fa-file-movie-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-movie-o:before{content:"\f1c8"}.fa.fa-file-code-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-code-o:before{content:"\f1c9"}.fa.fa-codepen,.fa.fa-jsfiddle,.fa.fa-vine{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-life-bouy,.fa.fa-life-ring{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-life-bouy:before{content:"\f1cd"}.fa.fa-life-buoy{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-life-buoy:before{content:"\f1cd"}.fa.fa-life-saver{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-life-saver:before{content:"\f1cd"}.fa.fa-support{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-support:before{content:"\f1cd"}.fa.fa-circle-o-notch:before{content:"\f1ce"}.fa.fa-ra,.fa.fa-rebel{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-ra:before{content:"\f1d0"}.fa.fa-resistance{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-resistance:before{content:"\f1d0"}.fa.fa-empire,.fa.fa-ge{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-ge:before{content:"\f1d1"}.fa.fa-git,.fa.fa-git-square,.fa.fa-hacker-news,.fa.fa-y-combinator-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-y-combinator-square:before{content:"\f1d4"}.fa.fa-yc-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-yc-square:before{content:"\f1d4"}.fa.fa-qq,.fa.fa-tencent-weibo,.fa.fa-wechat,.fa.fa-weixin{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-wechat:before{content:"\f1d7"}.fa.fa-send:before{content:"\f1d8"}.fa.fa-paper-plane-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-paper-plane-o:before{content:"\f1d8"}.fa.fa-send-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-send-o:before{content:"\f1d8"}.fa.fa-circle-thin{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-circle-thin:before{content:"\f111"}.fa.fa-header:before{content:"\f1dc"}.fa.fa-sliders:before{content:"\f1de"}.fa.fa-futbol-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-futbol-o:before{content:"\f1e3"}.fa.fa-soccer-ball-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-soccer-ball-o:before{content:"\f1e3"}.fa.fa-slideshare,.fa.fa-twitch,.fa.fa-yelp{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-newspaper-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-newspaper-o:before{content:"\f1ea"}.fa.fa-cc-amex,.fa.fa-cc-discover,.fa.fa-cc-mastercard,.fa.fa-cc-paypal,.fa.fa-cc-stripe,.fa.fa-cc-visa,.fa.fa-google-wallet,.fa.fa-paypal{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-bell-slash-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bell-slash-o:before{content:"\f1f6"}.fa.fa-trash:before{content:"\f2ed"}.fa.fa-copyright{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-eyedropper:before{content:"\f1fb"}.fa.fa-area-chart:before{content:"\f1fe"}.fa.fa-pie-chart:before{content:"\f200"}.fa.fa-line-chart:before{content:"\f201"}.fa.fa-angellist,.fa.fa-ioxhost,.fa.fa-lastfm,.fa.fa-lastfm-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-cc{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-cc:before{content:"\f20a"}.fa.fa-ils:before,.fa.fa-shekel:before,.fa.fa-sheqel:before{content:"\f20b"}.fa.fa-meanpath{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-meanpath:before{content:"\f2b4"}.fa.fa-buysellads,.fa.fa-connectdevelop,.fa.fa-dashcube,.fa.fa-forumbee,.fa.fa-leanpub,.fa.fa-sellsy,.fa.fa-shirtsinbulk,.fa.fa-simplybuilt,.fa.fa-skyatlas{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-diamond{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-diamond:before{content:"\f3a5"}.fa.fa-intersex:before{content:"\f224"}.fa.fa-facebook-official{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-facebook-official:before{content:"\f09a"}.fa.fa-pinterest-p,.fa.fa-whatsapp{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-hotel:before{content:"\f236"}.fa.fa-medium,.fa.fa-viacoin,.fa.fa-y-combinator,.fa.fa-yc{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-yc:before{content:"\f23b"}.fa.fa-expeditedssl,.fa.fa-opencart,.fa.fa-optin-monster{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-battery-4:before,.fa.fa-battery:before{content:"\f240"}.fa.fa-battery-3:before{content:"\f241"}.fa.fa-battery-2:before{content:"\f242"}.fa.fa-battery-1:before{content:"\f243"}.fa.fa-battery-0:before{content:"\f244"}.fa.fa-object-group,.fa.fa-object-ungroup,.fa.fa-sticky-note-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-sticky-note-o:before{content:"\f249"}.fa.fa-cc-diners-club,.fa.fa-cc-jcb{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-clone,.fa.fa-hourglass-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hourglass-o:before{content:"\f254"}.fa.fa-hourglass-1:before{content:"\f251"}.fa.fa-hourglass-2:before{content:"\f252"}.fa.fa-hourglass-3:before{content:"\f253"}.fa.fa-hand-rock-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-rock-o:before{content:"\f255"}.fa.fa-hand-grab-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-grab-o:before{content:"\f255"}.fa.fa-hand-paper-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-paper-o:before{content:"\f256"}.fa.fa-hand-stop-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-stop-o:before{content:"\f256"}.fa.fa-hand-scissors-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-scissors-o:before{content:"\f257"}.fa.fa-hand-lizard-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-lizard-o:before{content:"\f258"}.fa.fa-hand-spock-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-spock-o:before{content:"\f259"}.fa.fa-hand-pointer-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-pointer-o:before{content:"\f25a"}.fa.fa-hand-peace-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-peace-o:before{content:"\f25b"}.fa.fa-registered{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-chrome,.fa.fa-creative-commons,.fa.fa-firefox,.fa.fa-get-pocket,.fa.fa-gg,.fa.fa-gg-circle,.fa.fa-internet-explorer,.fa.fa-odnoklassniki,.fa.fa-odnoklassniki-square,.fa.fa-opera,.fa.fa-safari,.fa.fa-tripadvisor,.fa.fa-wikipedia-w{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-television:before{content:"\f26c"}.fa.fa-500px,.fa.fa-amazon,.fa.fa-contao{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-calendar-plus-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-plus-o:before{content:"\f271"}.fa.fa-calendar-minus-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-minus-o:before{content:"\f272"}.fa.fa-calendar-times-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-times-o:before{content:"\f273"}.fa.fa-calendar-check-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-check-o:before{content:"\f274"}.fa.fa-map-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-map-o:before{content:"\f279"}.fa.fa-commenting:before{content:"\f4ad"}.fa.fa-commenting-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-commenting-o:before{content:"\f4ad"}.fa.fa-houzz,.fa.fa-vimeo{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-vimeo:before{content:"\f27d"}.fa.fa-black-tie,.fa.fa-edge,.fa.fa-fonticons,.fa.fa-reddit-alien{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-credit-card-alt:before{content:"\f09d"}.fa.fa-codiepie,.fa.fa-fort-awesome,.fa.fa-mixcloud,.fa.fa-modx,.fa.fa-product-hunt,.fa.fa-scribd,.fa.fa-usb{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-pause-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-pause-circle-o:before{content:"\f28b"}.fa.fa-stop-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-stop-circle-o:before{content:"\f28d"}.fa.fa-bluetooth,.fa.fa-bluetooth-b,.fa.fa-envira,.fa.fa-gitlab,.fa.fa-wheelchair-alt,.fa.fa-wpbeginner,.fa.fa-wpforms{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-wheelchair-alt:before{content:"\f368"}.fa.fa-question-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-question-circle-o:before{content:"\f059"}.fa.fa-volume-control-phone:before{content:"\f2a0"}.fa.fa-asl-interpreting:before{content:"\f2a3"}.fa.fa-deafness:before,.fa.fa-hard-of-hearing:before{content:"\f2a4"}.fa.fa-glide,.fa.fa-glide-g{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-signing:before{content:"\f2a7"}.fa.fa-first-order,.fa.fa-google-plus-official,.fa.fa-pied-piper,.fa.fa-snapchat,.fa.fa-snapchat-ghost,.fa.fa-snapchat-square,.fa.fa-themeisle,.fa.fa-viadeo,.fa.fa-viadeo-square,.fa.fa-yoast{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-google-plus-official:before{content:"\f2b3"}.fa.fa-google-plus-circle{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-google-plus-circle:before{content:"\f2b3"}.fa.fa-fa,.fa.fa-font-awesome{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-fa:before{content:"\f2b4"}.fa.fa-handshake-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-handshake-o:before{content:"\f2b5"}.fa.fa-envelope-open-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-envelope-open-o:before{content:"\f2b6"}.fa.fa-linode{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-address-book-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-address-book-o:before{content:"\f2b9"}.fa.fa-vcard:before{content:"\f2bb"}.fa.fa-address-card-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-address-card-o:before{content:"\f2bb"}.fa.fa-vcard-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-vcard-o:before{content:"\f2bb"}.fa.fa-user-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-user-circle-o:before{content:"\f2bd"}.fa.fa-user-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-user-o:before{content:"\f007"}.fa.fa-id-badge{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-drivers-license:before{content:"\f2c2"}.fa.fa-id-card-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-id-card-o:before{content:"\f2c2"}.fa.fa-drivers-license-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-drivers-license-o:before{content:"\f2c2"}.fa.fa-free-code-camp,.fa.fa-quora,.fa.fa-telegram{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-thermometer-4:before,.fa.fa-thermometer:before{content:"\f2c7"}.fa.fa-thermometer-3:before{content:"\f2c8"}.fa.fa-thermometer-2:before{content:"\f2c9"}.fa.fa-thermometer-1:before{content:"\f2ca"}.fa.fa-thermometer-0:before{content:"\f2cb"}.fa.fa-bathtub:before,.fa.fa-s15:before{content:"\f2cd"}.fa.fa-window-maximize,.fa.fa-window-restore{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-times-rectangle:before{content:"\f410"}.fa.fa-window-close-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-window-close-o:before{content:"\f410"}.fa.fa-times-rectangle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-times-rectangle-o:before{content:"\f410"}.fa.fa-bandcamp,.fa.fa-eercast,.fa.fa-etsy,.fa.fa-grav,.fa.fa-imdb,.fa.fa-ravelry{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-eercast:before{content:"\f2da"}.fa.fa-snowflake-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-snowflake-o:before{content:"\f2dc"}.fa.fa-superpowers,.fa.fa-wpexplorer{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-cab:before{content:"\f1ba"} \ No newline at end of file diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.eot b/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.eot new file mode 100644 index 0000000000000000000000000000000000000000..cba6c6cce88182cb9374acea956769f87a8b8004 GIT binary patch literal 134294 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
  • -hVoDd!sPW~|*- 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

    I7ktX3;2a z!Q^2NTULA9Xz4VDl090rjrrg9_cz6c)i(8Qv$2EpcW+zpBe`vRw-4}#wqtgI7XBRo z0dRq?Z3QnV7k6}^m!f@_lG?6XccZP>k>QQDrNe3=INQ@_xy8DhLaW_JjEc6M@DQzD zy$hJA!92~Dr8CqO57yO4EN2GcsrB=197<;t2WdRKKiyI7G6S%PB2Q+r6hq{#p7~C=|TC>~U;tsIGqH1r& zq7%ejCauZ$T8*MgVNQTV*sWaHZQw zyH<4{39K0K7Q#)po2Id2G<}@VrO|+n2LPd4FIhEqXlo>Ty;C#UVT4*dU5NvDO0#B$ z4wzAq%FyNr%a*%?@M!yK$rg;3M|O4SgVwKjI=0k26*Dw%W?%k?7zb6=+yN%uO zfrK2;j(cybUbZ9Wu~o4rVgZo=My<AGKpb5{L4TKxcy#Y}%x#4sdxknqF{8HSHj)0V=o2_4IDb>g6rPKK zga2t%)z~%E2HEN9Z;H0N-ECUSzP>0_AVr~>$M-ynhqla4UE8r*JlhN3swtvyy(&U7K*xTKmbjZJ7+gC*Q(0GEcgNjnIQyH^+YMH;`k; ze)2PVurv9jtI+c-msv8pIvl2C*rrFJO{=0tViESP1P~CVKd?3tdtL>idt*mwq6gx= z&s{sUGGH;fX)wwdJw--trv#J_pFaY_<{rLn@Kqld_)+vKF#Q&SFJRRe`-5vD2TVi3 z3Y40L%SK*Bpfgw!EC&dtC_cVt)ixk4Ms>+(BZoNV*|J)VQniLwjLuu$0-VD9HjO6Y zKx{^YA6C|G4P@0L|EkgfT>5H4NPdflEZgh(XdK6^XW&ANel4>JILQ$TH@s;+HtCCv_}s+rwDi%WZ`1=9p0 zw!-MtY~XfzOQ~J0GC0UIU3Le+&?&()JI+qic6Dq%x<;#^8P!&)QSzxi<|TpMUQ~Z- z(=wV`sk5;G3h1MBh59@wF|D3NRZ13+%Lj5ZZ6tjeR<7CwzFskvTE^B|z4l%enTx8_Lc!_V tQ}Nuu$|~3i@UJG&)t*y)k*%w@#|J;^49B;>vGG;4{mxNbf%{wUKLeisFF61J literal 0 HcmV?d00001 diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.svg b/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.svg new file mode 100644 index 00000000..b9881a43 --- /dev/null +++ b/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.svg @@ -0,0 +1,3717 @@ + + + + +Created by FontForge 20201107 at Wed Aug 4 12:25:29 2021 + By Robert Madole +Copyright (c) Font Awesome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.ttf b/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8d75deddae520da95d3cf111f4ccbf3361074292 GIT binary patch literal 133988 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 z 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 OvGs=*>()Ih1OWi|n4ByC literal 0 HcmV?d00001 diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.eot b/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.eot new file mode 100644 index 0000000000000000000000000000000000000000..a4e598936b3eb6ceb0080dccccdcd49bfe95ca98 GIT binary patch literal 34034 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 + + + +Created by FontForge 20201107 at Wed Aug 4 12:25:29 2021 + By Robert Madole +Copyright (c) Font Awesome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.ttf b/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7157aafbacdb095b479ae52f59e28e19ce61d79a GIT binary patch literal 33736 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 b5?7;>Z>>sQQ*PhZo%{_-QW_Bdb7%h#hbB&U literal 0 HcmV?d00001 diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff2 b/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..56328948b3b1bacb23a13af9d727fd75c0343448 GIT binary patch literal 13224 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 literal 0 HcmV?d00001 diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.svg b/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.svg new file mode 100644 index 00000000..00296e95 --- /dev/null +++ b/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.svg @@ -0,0 +1,5034 @@ + + + + +Created by FontForge 20201107 at Wed Aug 4 12:25:29 2021 + By Robert Madole +Copyright (c) Font Awesome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.ttf b/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.ttf new file mode 100644 index 0000000000000000000000000000000000000000..25abf389e22db851b03dd14d87ca10acb8b6b44b GIT binary patch literal 202744 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 R_Uou~eShdq`k`O*{{v{mwVeO} literal 0 HcmV?d00001 diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff b/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff new file mode 100644 index 0000000000000000000000000000000000000000..23ee663443a7c6d2393dbcb713b07c566f40c925 GIT binary patch literal 101648 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

      j< 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%0D?I3ov(fVN| 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 M_kYo)8A$*D0I2Z0cmMzZ literal 0 HcmV?d00001 diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff2 b/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..2217164f0c05a385d7d0d83e030fdbae01e99304 GIT binary patch literal 78268 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'd1Mlv9Dcqdu7tF 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 V=VGRl8m#`%cC;f?eCPlA^&g!Ko?QR{ literal 0 HcmV?d00001 diff --git a/assets/web/header.html b/assets/web/header.html index 867a39b7..336a609d 100644 --- a/assets/web/header.html +++ b/assets/web/header.html @@ -6,7 +6,7 @@ Sunshine - + diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index 02ad6813..f90b6d96 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -239,6 +239,31 @@ void getSunshineLogoImage(resp_https_t response, req_https_t request) { response->write(SimpleWeb::StatusCode::success_ok, in, headers); } +void getFontAwesomeCss(resp_https_t response, req_https_t request) { + print_req(request); + + std::string content = read_file(WEB_DIR "fonts/fontawesome-free-web/css/all.min.css"); + response->write(content); +} + +void getFontAwesomeBrands(resp_https_t response, req_https_t request) { + print_req(request); + + std::ifstream in(WEB_DIR "fonts/fontawesome-free-web/webfonts/fa-brands-400.ttf", std::ios::binary); + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "font/ttf"); + response->write(SimpleWeb::StatusCode::success_ok, in, headers); +} + +void getFontAwesomeSolid(resp_https_t response, req_https_t request) { + print_req(request); + + std::ifstream in(WEB_DIR "fonts/fontawesome-free-web/webfonts/fa-solid-900.ttf", std::ios::binary); + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "font/ttf"); + response->write(SimpleWeb::StatusCode::success_ok, in, headers); +} + void getBootstrapCss(resp_https_t response, req_https_t request) { print_req(request); @@ -594,6 +619,9 @@ void start() { server.resource["^/images/logo-sunshine-45.png$"]["GET"] = getSunshineLogoImage; server.resource["^/third_party/bootstrap.min.css$"]["GET"] = getBootstrapCss; server.resource["^/third_party/bootstrap.bundle.min.js$"]["GET"] = getBootstrapJs; + server.resource["^/fontawesome/css/all.min.css$"]["GET"] = getFontAwesomeCss; + server.resource["^/fontawesome/webfonts/fa-brands-400.ttf$"]["GET"] = getFontAwesomeBrands; + server.resource["^/fontawesome/webfonts/fa-solid-900.ttf$"]["GET"] = getFontAwesomeSolid; server.resource["^/third_party/vue.js$"]["GET"] = getVueJs; server.config.reuse_address = true; server.config.address = "0.0.0.0"s; From 9dd6576b261e9fcf28f91dec7c552b3748093bcb Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 13 Feb 2022 09:25:41 -0500 Subject: [PATCH 154/817] Update CHANGELOG.md - Update v0.12.0 release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aac89caf..f9d58a8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [0.12.0] - 2022-01-20 +## [0.12.0] - 2022-02-13 ### Added - New command line argument `--version` - Custom png poster support From 95302485a000de2b1e49bf65d35ede769bfcef94 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 13 Feb 2022 12:07:10 -0500 Subject: [PATCH 155/817] Improve version verification --- .github/workflows/CI.yml | 71 ++++++++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 17 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 9df7c0d3..a71b34ff 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -25,13 +25,64 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} outputs: next_version: ${{ steps.verify_changelog.outputs.changelog_parser_version }} + next_version_bare: ${{ steps.verify_changelog.outputs.changelog_parser_version_bare }} last_version: ${{ steps.verify_changelog.outputs.latest_release_tag_name }} release_body: ${{ steps.verify_changelog.outputs.changelog_parser_description }} + check_versions: + name: Check Versions + runs-on: ubuntu-latest + needs: check_changelog + if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} + # base_ref for pull request check, ref for push + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Check CMakeLists.txt Version + run: | + version=$(grep -o -E '^project\(Sunshine VERSION [0-9]+\.[0-9]+\.[0-9]+\)' CMakeLists.txt | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') + echo "cmakelists_version=${version}" >> $GITHUB_ENV + - name: Compare CMakeList.txt Version + if: ${{ env.cmakelists_version != needs.check_changelog.outputs.next_version_bare }} + run: | + echo CMakeLists version: "$cmakelists_version" + echo Changelog version: "${{ needs.check_changelog.outputs.next_version_bare }}" + echo Within 'CMakeLists.txt' change "project(Sunshine VERSION $cmakelists_version)" to "project(Sunshine VERSION ${{ needs.check_changelog.outputs.next_version_bare }})" + exit 1 + + - name: Check gen-deb.in Version + run: | + version=$(grep -o -E '^Version: [0-9]+\.[0-9]+\.[0-9]+' gen-deb.in | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') + echo "gendeb_version=${version}" >> $GITHUB_ENV + - name: Compare gen-deb.in Version + if: ${{ env.gendeb_version != needs.check_changelog.outputs.next_version_bare }} + run: | + echo gen-deb.in version: "$gendeb_version" + echo Changelog version: "${{ needs.check_changelog.outputs.next_version_bare }}" + echo Within 'gen-deb.in' change "Version: $gendeb_version" to "Version: ${{ needs.check_changelog.outputs.next_version_bare }}" + exit 1 + + - name: Check sunshine.desktop Versions + run: | + version=$(grep -o -E '^Version=[0-9]+\.[0-9]+\.[0-9]+' sunshine.desktop | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') + version_x=$(grep -o -E '^X-AppImage-Version=[0-9]+\.[0-9]+\.[0-9]+' sunshine.desktop | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') + echo "appimage_version=${version}" >> $GITHUB_ENV + echo "appimagex_version=${version_x}" >> $GITHUB_ENV + - name: Compare sunshine.desktop Versions + if: ${{ ( env.appimage_version != needs.check_changelog.outputs.next_version_bare ) && ( env.appimagex_version != needs.check_changelog.outputs.next_version_bare ) }} + run: | + echo sunshine.desktop Version: "$appimage_version" + echo sunshine.desktop X-AppImage-Version: "$appimagex_version" + echo Changelog version: "${{ needs.check_changelog.outputs.next_version_bare }}" + echo Within 'sunshine.desktop' change "Version=$appimage_version" to "Version=${{ needs.check_changelog.outputs.next_version_bare }}" + echo Within 'sunshine.desktop' change "X-AppImage-Version=$appimagex_version" to "X-AppImage-Version=${{ needs.check_changelog.outputs.next_version_bare }}" + exit 1 + build_appimage: name: AppImage runs-on: ubuntu-20.04 - needs: check_changelog + needs: [check_changelog, check_versions] steps: - name: Checkout @@ -100,19 +151,6 @@ jobs: run: | cd appimage_temp wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage && chmod +x appimagelint-x86_64.AppImage && ./appimagelint-x86_64.AppImage ./sunshine/sunshine.AppImage - - name: Check Version - if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} - # base_ref for pull request check, ref for push - run: | - cd ./appimage_temp/sunshine - version=$(./sunshine.AppImage --version | grep -o -E 'v[0-9]+\.[0-9]+\.[0-9]+') - echo "sunshine_version=${version}" >> $GITHUB_ENV - - name: Compare Versions - if: ${{ ( github.ref == 'refs/heads/master' || github.base_ref == 'master') && ( env.sunshine_version != needs.check_changelog.outputs.next_version ) }} - run: | - echo AppImage version: "$sunshine_version" - echo Changelog version: "${{ needs.check_changelog.outputs.next_version }}" - exit 1 - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v2 @@ -128,11 +166,10 @@ jobs: last_version: ${{ needs.check_changelog.outputs.last_version }} release_body: ${{ needs.check_changelog.outputs.release_body }} - build_linux: name: Linux runs-on: ubuntu-20.04 - needs: check_changelog + needs: [check_changelog, check_versions] strategy: fail-fast: true # false to test all, true to fail entire job if any fail matrix: @@ -183,7 +220,7 @@ jobs: build_win: name: Windows runs-on: windows-2019 - needs: check_changelog + needs: [check_changelog, check_versions] steps: - name: Checkout From 8bb7a634793fd2db0f32b11c700bce73e6cb16c4 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 13 Feb 2022 12:17:04 -0500 Subject: [PATCH 156/817] Fix job needs - Build jobs didn't run if `check_versions` doesn't run (need to ENSURE PR check into master is successful before merging!) --- .github/workflows/CI.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a71b34ff..56d74d72 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -82,7 +82,7 @@ jobs: build_appimage: name: AppImage runs-on: ubuntu-20.04 - needs: [check_changelog, check_versions] + needs: check_changelog steps: - name: Checkout @@ -169,7 +169,7 @@ jobs: build_linux: name: Linux runs-on: ubuntu-20.04 - needs: [check_changelog, check_versions] + needs: check_changelog strategy: fail-fast: true # false to test all, true to fail entire job if any fail matrix: @@ -220,7 +220,7 @@ jobs: build_win: name: Windows runs-on: windows-2019 - needs: [check_changelog, check_versions] + needs: check_changelog steps: - name: Checkout From ec5ea7cffb9cf60df267f2ead01b4e365a3ce424 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 13 Feb 2022 12:45:13 -0500 Subject: [PATCH 157/817] Update CI.yml - Version from .desktop file needs to be 1.0 (this is not the sunshine version) - Setting VERSION environment variable will set AppImage version, otherwise short commit hash will be used --- .github/workflows/CI.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 56d74d72..e9de6a36 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -65,18 +65,14 @@ jobs: - name: Check sunshine.desktop Versions run: | - version=$(grep -o -E '^Version=[0-9]+\.[0-9]+\.[0-9]+' sunshine.desktop | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') - version_x=$(grep -o -E '^X-AppImage-Version=[0-9]+\.[0-9]+\.[0-9]+' sunshine.desktop | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') + version=$(grep -o -E '^X-AppImage-Version=[0-9]+\.[0-9]+\.[0-9]+' sunshine.desktop | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') echo "appimage_version=${version}" >> $GITHUB_ENV - echo "appimagex_version=${version_x}" >> $GITHUB_ENV - name: Compare sunshine.desktop Versions - if: ${{ ( env.appimage_version != needs.check_changelog.outputs.next_version_bare ) && ( env.appimagex_version != needs.check_changelog.outputs.next_version_bare ) }} + if: ${{ env.appimage_version != needs.check_changelog.outputs.next_version_bare }} run: | echo sunshine.desktop Version: "$appimage_version" - echo sunshine.desktop X-AppImage-Version: "$appimagex_version" echo Changelog version: "${{ needs.check_changelog.outputs.next_version_bare }}" - echo Within 'sunshine.desktop' change "Version=$appimage_version" to "Version=${{ needs.check_changelog.outputs.next_version_bare }}" - echo Within 'sunshine.desktop' change "X-AppImage-Version=$appimagex_version" to "X-AppImage-Version=${{ needs.check_changelog.outputs.next_version_bare }}" + echo Within 'sunshine.desktop' change "X-AppImage-Version=$appimage_version" to "X-AppImage-Version=${{ needs.check_changelog.outputs.next_version_bare }}" exit 1 build_appimage: @@ -122,6 +118,11 @@ jobs: cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" "../" -DCMAKE_INSTALL_PREFIX=/usr make -j ${nproc} DESTDIR=AppDir + - name: Set AppImage Version + if: ${{ needs.check_changelog.outputs.next_version_bare != needs.check_changelog.outputs.latest_version }} + run: | + version=${{ needs.check_changelog.outputs.next_version_bare }} + echo "VERSION=${version}" >> $GITHUB_ENV - name: Package AppImage # https://docs.appimage.org/packaging-guide/index.html run: | From 543254691b61ed1b9dd6726464e97331c6d8eb9f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 13 Feb 2022 13:04:17 -0500 Subject: [PATCH 158/817] v0.12.0 --- CMakeLists.txt | 2 +- gen-deb.in | 2 +- sunshine.desktop | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 20751568..2bdc2f08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.0) -project(Sunshine VERSION 0.11.1) +project(Sunshine VERSION 0.12.0) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) diff --git a/gen-deb.in b/gen-deb.in index 509459c2..39d585d4 100755 --- a/gen-deb.in +++ b/gen-deb.in @@ -37,7 +37,7 @@ Package: sunshine Architecture: amd64 Maintainer: @loki Priority: optional -Version: 0.11.1 +Version: 0.12.0 Depends: libssl1.1, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2 Description: Gamestream host for Moonlight EOF diff --git a/sunshine.desktop b/sunshine.desktop index 591af9d4..8773a035 100644 --- a/sunshine.desktop +++ b/sunshine.desktop @@ -8,5 +8,5 @@ Icon=sunshine Categories=Utility; Terminal=true X-AppImage-Name=sunshine -X-AppImage-Version=1.0 +X-AppImage-Version=0.12.0 X-AppImage-Arch=x86_64 From 361e5f7ea7999e907df788980b1093d693e336d6 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 16 Feb 2022 17:50:52 -0500 Subject: [PATCH 159/817] Create dependabot.yml --- .github/dependabot.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..60b85432 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + target-branch: "nightly" + open-pull-requests-limit: 20 From 27d273545459aec5df2eca9811fbb207e1747a41 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 16 Feb 2022 17:51:05 -0500 Subject: [PATCH 160/817] Create clang.yml --- .github/workflows/clang.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/clang.yml diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml new file mode 100644 index 00000000..3b1f05d5 --- /dev/null +++ b/.github/workflows/clang.yml @@ -0,0 +1,23 @@ +name: clang-format-lint + +on: + pull_request: + branches: [master, nightly] + types: [opened, synchronize, edited, reopened] + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Clang format lint + uses: DoozyX/clang-format-lint-action@v0.13 + with: + source: './sunshine' + extensions: 'cpp,h' + clangFormatVersion: 13 + style: file + inplace: False From 2be7790415db4529e43da1dcc26fd38da9e93b8f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 16 Feb 2022 17:58:06 -0500 Subject: [PATCH 161/817] Update clang.yml - Add upload artifacts --- .github/workflows/clang.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index 3b1f05d5..a7f71dce 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -21,3 +21,9 @@ jobs: clangFormatVersion: 13 style: file inplace: False + + - name: Upload Artifacts + uses: actions/upload-artifact@v2 + with: + name: clang-formatted-files + path: sunshine/ From 5163ec93b4410d8f95e2e7c5bad91bc11f6f8c6f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 16 Feb 2022 18:02:05 -0500 Subject: [PATCH 162/817] Update clang.yml --- .github/workflows/clang.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index a7f71dce..6ade2d0b 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -20,7 +20,7 @@ jobs: extensions: 'cpp,h' clangFormatVersion: 13 style: file - inplace: False + inplace: True - name: Upload Artifacts uses: actions/upload-artifact@v2 From 320b691086745b44f3127f4e73b95c546acafc03 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 16 Feb 2022 18:09:23 -0500 Subject: [PATCH 163/817] Update clang.yml - Use job strategy matrix - inplace True allows artifacts to be uploaded; however workflow succeeds even if there are errors - inplace False fails workflow if there are errors --- .github/workflows/clang.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index 6ade2d0b..1031378e 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -7,7 +7,12 @@ on: jobs: lint: + name: Clang Format Lint runs-on: ubuntu-latest + strategy: + fail-fast: false # false to test all, true to fail entire job if any fail + matrix: + inplace: [ True, False ] # removed ubuntu_18_04 for now steps: - name: Checkout @@ -20,10 +25,11 @@ jobs: extensions: 'cpp,h' clangFormatVersion: 13 style: file - inplace: True + inplace: ${{ matrix.inplace }} - name: Upload Artifacts + if: ${{ matrix.inplace == True }} uses: actions/upload-artifact@v2 with: - name: clang-formatted-files + name: sunshine path: sunshine/ From 37edcb1b55dd9a6320cd9d164f11170c20951650 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 16 Feb 2022 18:10:54 -0500 Subject: [PATCH 164/817] Update clang.yml - Fix syntax error --- .github/workflows/clang.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index 1031378e..204a9360 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -28,7 +28,7 @@ jobs: inplace: ${{ matrix.inplace }} - name: Upload Artifacts - if: ${{ matrix.inplace == True }} + if: ${{ matrix.inplace == 'True' }} uses: actions/upload-artifact@v2 with: name: sunshine From f54a32feac37214525937d0f9d59269d0a8b2223 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 16 Feb 2022 18:13:16 -0500 Subject: [PATCH 165/817] Update clang.yml --- .github/workflows/clang.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index 204a9360..d87256bc 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false # false to test all, true to fail entire job if any fail matrix: - inplace: [ True, False ] # removed ubuntu_18_04 for now + inplace: [ true, false ] # removed ubuntu_18_04 for now steps: - name: Checkout @@ -28,7 +28,7 @@ jobs: inplace: ${{ matrix.inplace }} - name: Upload Artifacts - if: ${{ matrix.inplace == 'True' }} + if: ${{ matrix.inplace == true }} uses: actions/upload-artifact@v2 with: name: sunshine From d6183430ef15ab6425f8c51194d15adbdcabc7ad Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 16 Feb 2022 18:23:56 -0500 Subject: [PATCH 166/817] clang lint --- sunshine/confighttp.cpp | 82 ++++++++++++++++--------------- sunshine/httpcommon.cpp | 3 +- sunshine/main.cpp | 2 +- sunshine/nvhttp.cpp | 4 +- sunshine/platform/linux/audio.cpp | 2 +- sunshine/platform/linux/x11grab.h | 2 +- sunshine/platform/windows/misc.h | 4 +- sunshine/process.cpp | 22 +++++---- sunshine/utility.h | 4 +- sunshine/video.cpp | 4 +- 10 files changed, 67 insertions(+), 62 deletions(-) diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index f90b6d96..c21089de 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -93,8 +93,8 @@ bool authenticate(resp_https_t response, req_https_t request) { } //If credentials are shown, redirect the user to a /welcome page - if(config::sunshine.username.empty()){ - send_redirect(response,request,"/welcome"); + if(config::sunshine.username.empty()) { + send_redirect(response, request, "/welcome"); return false; } @@ -202,8 +202,8 @@ void getPasswordPage(resp_https_t response, req_https_t request) { void getWelcomePage(resp_https_t response, req_https_t request) { print_req(request); - if(!config::sunshine.username.empty()){ - send_redirect(response,request,"/"); + if(!config::sunshine.username.empty()) { + send_redirect(response, request, "/"); return; } std::string header = read_file(WEB_DIR "header-no-nav.html"); @@ -496,16 +496,18 @@ void savePassword(resp_https_t response, req_https_t request) { auto newPassword = inputTree.count("newPassword") > 0 ? inputTree.get("newPassword") : ""; auto confirmPassword = inputTree.count("confirmNewPassword") > 0 ? inputTree.get("confirmNewPassword") : ""; if(newUsername.length() == 0) newUsername = username; - if(newUsername.length() == 0){ + if(newUsername.length() == 0) { outputTree.put("status", false); outputTree.put("error", "Invalid Username"); - } else { + } + else { auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string(); if(config::sunshine.username.empty() || (username == config::sunshine.username && hash == config::sunshine.password)) { if(newPassword.empty() || newPassword != confirmPassword) { outputTree.put("status", false); outputTree.put("error", "Password Mismatch"); - } else { + } + else { http::save_user_creds(config::sunshine.credentials_file, newUsername, newPassword); http::reload_user_creds(config::sunshine.credentials_file); outputTree.put("status", true); @@ -555,9 +557,9 @@ void savePin(resp_https_t response, req_https_t request) { } } -void unpairAll(resp_https_t response, req_https_t request){ +void unpairAll(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; - + print_req(request); pt::ptree outputTree; @@ -571,9 +573,9 @@ void unpairAll(resp_https_t response, req_https_t request){ outputTree.put("status", true); } -void closeApp(resp_https_t response, req_https_t request){ +void closeApp(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; - + print_req(request); pt::ptree outputTree; @@ -597,35 +599,35 @@ void start() { ctx->use_certificate_chain_file(config::nvhttp.cert); ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem); https_server_t server { ctx, 0 }; - server.default_resource = not_found; - server.resource["^/$"]["GET"] = getIndexPage; - server.resource["^/pin$"]["GET"] = getPinPage; - server.resource["^/apps$"]["GET"] = getAppsPage; - server.resource["^/clients$"]["GET"] = getClientsPage; - server.resource["^/config$"]["GET"] = getConfigPage; - server.resource["^/password$"]["GET"] = getPasswordPage; - server.resource["^/welcome$"]["GET"] = getWelcomePage; - server.resource["^/troubleshooting$"]["GET"] = getTroubleshootingPage; - server.resource["^/api/pin"]["POST"] = savePin; - server.resource["^/api/apps$"]["GET"] = getApps; - server.resource["^/api/apps$"]["POST"] = saveApp; - server.resource["^/api/config$"]["GET"] = getConfig; - server.resource["^/api/config$"]["POST"] = saveConfig; - server.resource["^/api/password$"]["POST"] = savePassword; - server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; - server.resource["^/api/clients/unpair$"]["POST"] = unpairAll; - server.resource["^/api/apps/close"]["POST"] = closeApp; - server.resource["^/images/favicon.ico$"]["GET"] = getFaviconImage; - server.resource["^/images/logo-sunshine-45.png$"]["GET"] = getSunshineLogoImage; - server.resource["^/third_party/bootstrap.min.css$"]["GET"] = getBootstrapCss; - server.resource["^/third_party/bootstrap.bundle.min.js$"]["GET"] = getBootstrapJs; - server.resource["^/fontawesome/css/all.min.css$"]["GET"] = getFontAwesomeCss; - server.resource["^/fontawesome/webfonts/fa-brands-400.ttf$"]["GET"] = getFontAwesomeBrands; - server.resource["^/fontawesome/webfonts/fa-solid-900.ttf$"]["GET"] = getFontAwesomeSolid; - server.resource["^/third_party/vue.js$"]["GET"] = getVueJs; - server.config.reuse_address = true; - server.config.address = "0.0.0.0"s; - server.config.port = port_https; + server.default_resource = not_found; + server.resource["^/$"]["GET"] = getIndexPage; + server.resource["^/pin$"]["GET"] = getPinPage; + server.resource["^/apps$"]["GET"] = getAppsPage; + server.resource["^/clients$"]["GET"] = getClientsPage; + server.resource["^/config$"]["GET"] = getConfigPage; + server.resource["^/password$"]["GET"] = getPasswordPage; + server.resource["^/welcome$"]["GET"] = getWelcomePage; + server.resource["^/troubleshooting$"]["GET"] = getTroubleshootingPage; + server.resource["^/api/pin"]["POST"] = savePin; + server.resource["^/api/apps$"]["GET"] = getApps; + server.resource["^/api/apps$"]["POST"] = saveApp; + server.resource["^/api/config$"]["GET"] = getConfig; + server.resource["^/api/config$"]["POST"] = saveConfig; + server.resource["^/api/password$"]["POST"] = savePassword; + server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; + server.resource["^/api/clients/unpair$"]["POST"] = unpairAll; + server.resource["^/api/apps/close"]["POST"] = closeApp; + server.resource["^/images/favicon.ico$"]["GET"] = getFaviconImage; + server.resource["^/images/logo-sunshine-45.png$"]["GET"] = getSunshineLogoImage; + server.resource["^/third_party/bootstrap.min.css$"]["GET"] = getBootstrapCss; + server.resource["^/third_party/bootstrap.bundle.min.js$"]["GET"] = getBootstrapJs; + server.resource["^/fontawesome/css/all.min.css$"]["GET"] = getFontAwesomeCss; + server.resource["^/fontawesome/webfonts/fa-brands-400.ttf$"]["GET"] = getFontAwesomeBrands; + server.resource["^/fontawesome/webfonts/fa-solid-900.ttf$"]["GET"] = getFontAwesomeSolid; + server.resource["^/third_party/vue.js$"]["GET"] = getVueJs; + server.config.reuse_address = true; + server.config.address = "0.0.0.0"s; + server.config.port = port_https; try { server.bind(); diff --git a/sunshine/httpcommon.cpp b/sunshine/httpcommon.cpp index 14249456..91074144 100644 --- a/sunshine/httpcommon.cpp +++ b/sunshine/httpcommon.cpp @@ -56,7 +56,8 @@ int init() { } if(user_creds_exist(config::sunshine.credentials_file)) { if(reload_user_creds(config::sunshine.credentials_file)) return -1; - } else { + } + else { BOOST_LOG(info) << "Open the Web UI to set your new username and password and getting started"; } return 0; diff --git a/sunshine/main.cpp b/sunshine/main.cpp index b5126f66..bcab4b3b 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -24,8 +24,8 @@ #include "rtsp.h" #include "thread_pool.h" #include "upnp.h" -#include "video.h" #include "version.h" +#include "video.h" #include "platform/common.h" extern "C" { diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 8afdb35a..5da896e2 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -765,7 +765,7 @@ void cancel(resp_https_t response, req_https_t request) { void appasset(resp_https_t response, req_https_t request) { print_req(request); - auto args = request->parse_query_string(); + auto args = request->parse_query_string(); auto app_image = proc::proc.get_app_image(util::from_view(args.at("appid"))); std::ifstream in(app_image, std::ios::binary); @@ -934,7 +934,7 @@ void start() { tcp.join(); } -void erase_all_clients(){ +void erase_all_clients() { map_id_client.clear(); save_state(); } diff --git a/sunshine/platform/linux/audio.cpp b/sunshine/platform/linux/audio.cpp index facb5f3b..c210da24 100644 --- a/sunshine/platform/linux/audio.cpp +++ b/sunshine/platform/linux/audio.cpp @@ -77,7 +77,7 @@ std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std }); pa_buffer_attr pa_attr = {}; - pa_attr.maxlength = frame_size * 8; + pa_attr.maxlength = frame_size * 8; int status; diff --git a/sunshine/platform/linux/x11grab.h b/sunshine/platform/linux/x11grab.h index 3d2868c8..9fde2664 100644 --- a/sunshine/platform/linux/x11grab.h +++ b/sunshine/platform/linux/x11grab.h @@ -21,7 +21,7 @@ void freeCursorCtx(cursor_ctx_raw_t *ctx); void freeDisplay(_XDisplay *xdisplay); using cursor_ctx_t = util::safe_ptr; -using xdisplay_t = util::safe_ptr<_XDisplay, freeDisplay>; +using xdisplay_t = util::safe_ptr<_XDisplay, freeDisplay>; class cursor_t { public: diff --git a/sunshine/platform/windows/misc.h b/sunshine/platform/windows/misc.h index 4effddf5..4cd8791f 100644 --- a/sunshine/platform/windows/misc.h +++ b/sunshine/platform/windows/misc.h @@ -1,13 +1,13 @@ #ifndef SUNSHINE_WINDOWS_MISC_H #define SUNSHINE_WINDOWS_MISC_H +#include #include #include -#include namespace platf { void print_status(const std::string_view &prefix, HRESULT status); HDESK syncThreadDesktop(); -} +} // namespace platf #endif \ No newline at end of file diff --git a/sunshine/process.cpp b/sunshine/process.cpp index f94b84fd..7fd900c5 100644 --- a/sunshine/process.cpp +++ b/sunshine/process.cpp @@ -6,14 +6,14 @@ #include "process.h" +#include #include #include -#include +#include +#include #include #include -#include -#include #include "main.h" #include "utility.h" @@ -112,9 +112,11 @@ int proc_t::execute(int app_id) { if(proc.cmd.empty()) { BOOST_LOG(debug) << "Executing [Desktop]"sv; placebo = true; - } else { - boost::filesystem::path working_dir = proc.working_dir.empty() ? - boost::filesystem::path(proc.cmd).parent_path() : boost::filesystem::path(proc.working_dir); + } + else { + boost::filesystem::path working_dir = proc.working_dir.empty() ? + boost::filesystem::path(proc.cmd).parent_path() : + boost::filesystem::path(proc.working_dir); if(proc.output.empty() || proc.output == "null"sv) { BOOST_LOG(info) << "Executing: ["sv << proc.cmd << ']'; _process = bp::child(_process_handle, proc.cmd, _env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec); @@ -195,14 +197,14 @@ std::vector &proc_t::get_apps() { /// Returns default image if image configuration is not set. /// returns http content-type header compatible image type std::string proc_t::get_app_image(int app_id) { - auto app_index = app_id -1; + auto app_index = app_id - 1; if(app_index < 0 || app_index >= _apps.size()) { BOOST_LOG(error) << "Couldn't find app with ID ["sv << app_id << ']'; return SUNSHINE_ASSETS_DIR "/box.png"; } auto app_image_path = _apps[app_index].image_path; - if (app_image_path.empty()) { + if(app_image_path.empty()) { return SUNSHINE_ASSETS_DIR "/box.png"; } @@ -210,7 +212,7 @@ std::string proc_t::get_app_image(int app_id) { boost::to_lower(image_extension); std::error_code code; - if (!std::filesystem::exists(app_image_path, code) || image_extension != ".png") { + if(!std::filesystem::exists(app_image_path, code) || image_extension != ".png") { return SUNSHINE_ASSETS_DIR "/box.png"; } @@ -351,7 +353,7 @@ std::optional parse(const std::string &file_name) { ctx.working_dir = parse_env_val(this_env, *working_dir); } - if (image_path) { + if(image_path) { ctx.image_path = parse_env_val(this_env, *image_path); } diff --git a/sunshine/utility.h b/sunshine/utility.h index 0e585e21..59776bf1 100644 --- a/sunshine/utility.h +++ b/sunshine/utility.h @@ -62,8 +62,8 @@ struct argument_type { typedef U type; }; x &operator=(x &&) noexcept = default; \ x(); -#define KITTY_DEFAULT_CONSTR_MOVE(x) \ - x(x &&) noexcept = default; \ +#define KITTY_DEFAULT_CONSTR_MOVE(x) \ + x(x &&) noexcept = default; \ x &operator=(x &&) noexcept = default; #define KITTY_DEFAULT_CONSTR_MOVE_THROW(x) \ diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 1a3d51e9..15807eaa 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -1280,7 +1280,7 @@ void captureThreadSync() { ctx.shutdown_event->raise(true); ctx.join_event->raise(true); } - }); + }); while(encode_run_sync(synced_session_ctxs, ctx) == encode_e::reinit) {} } @@ -1296,7 +1296,7 @@ void capture_async( auto lg = util::fail_guard([&]() { images->stop(); shutdown_event->raise(true); - }); + }); auto ref = capture_thread_async.ref(); if(!ref) { From df3e7c5ca140818b0f92c7d0f866fc0486de1fd8 Mon Sep 17 00:00:00 2001 From: Anselm Busse Date: Thu, 24 Feb 2022 20:58:24 +0100 Subject: [PATCH 167/817] Prepare for ObjectiveC/ObjectiveC++ code This commit modifies the clang-format configuration and workflow to support ObjectiveC and ObjectiveC++ code. Signed-off-by: Anselm Busse --- .clang-format | 1 + .github/workflows/clang.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.clang-format b/.clang-format index 1c31d7ba..d28dcc4e 100644 --- a/.clang-format +++ b/.clang-format @@ -23,6 +23,7 @@ BraceWrapping: AfterEnum: false AfterFunction: false AfterNamespace: false + AfterObjCDeclaration: false AfterUnion: false BeforeCatch: true BeforeElse: true diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index d87256bc..9ac9d06a 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -22,7 +22,7 @@ jobs: uses: DoozyX/clang-format-lint-action@v0.13 with: source: './sunshine' - extensions: 'cpp,h' + extensions: 'cpp,h,m,mm' clangFormatVersion: 13 style: file inplace: ${{ matrix.inplace }} From 2b450839a1ba753cf834d947c7069a7a8cfdef84 Mon Sep 17 00:00:00 2001 From: Anselm Busse Date: Thu, 24 Feb 2022 21:09:00 +0100 Subject: [PATCH 168/817] Initial support for MacOS This commit introduces initial support for MacOS as third major host platform. It relies on the VideoToolbox framework for audio and video processing, which enables hardware accelerated processing of the stream on most platforms. Audio capturing requires third party tools as MacOS does not offer the recording of the audio output like the other platforms do. The commit enables most features offered by Sunshine for MacOS with the big exception of gamepad support. The patch sets was tested by a few volunteers, which allowed to remove some of the early bugs. However, several bugs especially regarding corner cases have probably not surfaced yet. Besides instructions how to build from source, the commit also adds a Portfile that allows a more easy installation. After available on the release branch, a pull request for the Portfile in the MacPorts project is planned. Signed-off-by: Anselm Busse --- .gitmodules | 3 + CMakeLists.txt | 67 ++- Portfile | 48 ++ README.md | 48 ++ assets/apps_mac.json | 6 + assets/info.plist | 12 + assets/sunshine.conf | 28 ++ assets/web/config.html | 35 ++ sunshine/config.cpp | 44 ++ sunshine/config.h | 7 + sunshine/input.cpp | 1 + sunshine/platform/common.h | 2 +- sunshine/platform/macos/av_audio.h | 26 ++ sunshine/platform/macos/av_audio.m | 120 +++++ sunshine/platform/macos/av_img_t.h | 18 + sunshine/platform/macos/av_video.h | 43 ++ sunshine/platform/macos/av_video.m | 184 ++++++++ sunshine/platform/macos/display.mm | 196 ++++++++ sunshine/platform/macos/input.cpp | 465 +++++++++++++++++++ sunshine/platform/macos/microphone.mm | 87 ++++ sunshine/platform/macos/misc.cpp | 161 +++++++ sunshine/platform/macos/misc.h | 16 + sunshine/platform/macos/nv12_zero_device.cpp | 82 ++++ sunshine/platform/macos/nv12_zero_device.h | 29 ++ sunshine/platform/macos/publish.cpp | 429 +++++++++++++++++ sunshine/rtsp.cpp | 2 + sunshine/video.cpp | 36 ++ 27 files changed, 2191 insertions(+), 4 deletions(-) create mode 100644 Portfile create mode 100644 assets/apps_mac.json create mode 100644 assets/info.plist create mode 100644 sunshine/platform/macos/av_audio.h create mode 100644 sunshine/platform/macos/av_audio.m create mode 100644 sunshine/platform/macos/av_img_t.h create mode 100644 sunshine/platform/macos/av_video.h create mode 100644 sunshine/platform/macos/av_video.m create mode 100644 sunshine/platform/macos/display.mm create mode 100644 sunshine/platform/macos/input.cpp create mode 100644 sunshine/platform/macos/microphone.mm create mode 100644 sunshine/platform/macos/misc.cpp create mode 100644 sunshine/platform/macos/misc.h create mode 100644 sunshine/platform/macos/nv12_zero_device.cpp create mode 100644 sunshine/platform/macos/nv12_zero_device.h create mode 100644 sunshine/platform/macos/publish.cpp diff --git a/.gitmodules b/.gitmodules index 39650e86..814f3409 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "third-party/nv-codec-headers"] path = third-party/nv-codec-headers url = https://github.com/FFmpeg/nv-codec-headers +[submodule "sunshine/platform/macos/TPCircularBuffer"] + path = sunshine/platform/macos/TPCircularBuffer + url = https://github.com/michaeltyson/TPCircularBuffer diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bdc2f08..fd624bdb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,22 @@ if(WIN32) PQOS_FLOWID=UINT32* QOS_NON_ADAPTIVE_FLOW=2) endif() +if(APPLE) + macro(ADD_FRAMEWORK fwname appname) + find_library(FRAMEWORK_${fwname} + NAMES ${fwname} + PATHS ${CMAKE_OSX_SYSROOT}/System/Library + PATH_SUFFIXES Frameworks + NO_DEFAULT_PATH) + if( ${FRAMEWORK_${fwname}} STREQUAL FRAMEWORK_${fwname}-NOTFOUND) + MESSAGE(ERROR ": Framework ${fwname} not found") + else() + TARGET_LINK_LIBRARIES(${appname} "${FRAMEWORK_${fwname}}/${fwname}") + MESSAGE(STATUS "Framework ${fwname} found at ${FRAMEWORK_${fwname}}") + endif() + endmacro(ADD_FRAMEWORK) +endif() + add_subdirectory(third-party/moonlight-common-c/enet) add_subdirectory(third-party/Simple-Web-Server) @@ -23,7 +39,9 @@ include_directories(third-party/miniupnp) find_package(Threads REQUIRED) find_package(OpenSSL REQUIRED) -set(Boost_USE_STATIC_LIBS ON) +if(NOT APPLE) + set(Boost_USE_STATIC_LIBS ON) +endif() find_package(Boost COMPONENTS log filesystem REQUIRED) list(APPEND SUNSHINE_COMPILE_OPTIONS -Wall -Wno-missing-braces -Wno-maybe-uninitialized -Wno-sign-compare) @@ -106,6 +124,46 @@ if(WIN32) set_source_files_properties(third-party/ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_DEFINITIONS "UNICODE=1;ERROR_INVALID_DEVICE_OBJECT_PARAMETER=650") set_source_files_properties(third-party/ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_FLAGS "-Wno-unknown-pragmas -Wno-misleading-indentation -Wno-class-memaccess") +elseif(APPLE) + add_compile_definitions(SUNSHINE_PLATFORM="macos") + list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_mac.json") + link_directories(/opt/local/lib) + link_directories(/usr/local/lib) + ADD_DEFINITIONS(-DBOOST_LOG_DYN_LINK) + + find_package(FFmpeg REQUIRED) + FIND_LIBRARY(APP_SERVICES_LIBRARY ApplicationServices ) + FIND_LIBRARY(AV_FOUNDATION_LIBRARY AVFoundation ) + FIND_LIBRARY(CORE_MEDIA_LIBRARY CoreMedia ) + FIND_LIBRARY(CORE_VIDEO_LIBRARY CoreVideo ) + FIND_LIBRARY(FOUNDATION_LIBRARY Foundation ) + list(APPEND SUNSHINE_EXTERNAL_LIBRARIES + ${APP_SERVICES_LIBRARY} + ${AV_FOUNDATION_LIBRARY} + ${CORE_MEDIA_LIBRARY} + ${CORE_VIDEO_LIBRARY} + ${FOUNDATION_LIBRARY}) + + set(PLATFORM_INCLUDE_DIRS + ${Boost_INCLUDE_DIR}) + + set(PLATFORM_TARGET_FILES + sunshine/platform/macos/av_audio.h + sunshine/platform/macos/av_audio.m + sunshine/platform/macos/av_img_t.h + sunshine/platform/macos/av_video.h + sunshine/platform/macos/av_video.m + sunshine/platform/macos/display.mm + sunshine/platform/macos/input.cpp + sunshine/platform/macos/microphone.mm + sunshine/platform/macos/misc.cpp + sunshine/platform/macos/misc.h + sunshine/platform/macos/nv12_zero_device.cpp + sunshine/platform/macos/nv12_zero_device.h + sunshine/platform/macos/publish.cpp + sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.c + sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.h + ${CMAKE_CURRENT_SOURCE_DIR}/assets/Info.plist) else() add_compile_definitions(SUNSHINE_PLATFORM="linux") list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_linux.json") @@ -352,7 +410,6 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES libminiupnpc-static ${CBS_EXTERNAL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} - stdc++fs enet opus ${FFMPEG_LIBRARIES} @@ -368,7 +425,7 @@ list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR}") list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_CONFIG_DIR="${SUNSHINE_CONFIG_DIR}") list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_DEFAULT_DIR="${SUNSHINE_DEFAULT_DIR}") add_executable(sunshine ${SUNSHINE_TARGET_FILES}) -target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES}) +target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES} ${EXTRA_LIBS}) target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS}) set_target_properties(sunshine PROPERTIES CXX_STANDARD 17 VERSION ${PROJECT_VERSION} @@ -380,6 +437,10 @@ if(NOT DEFINED CMAKE_CUDA_STANDARD) set(CMAKE_CUDA_STANDARD_REQUIRED ON) endif() +if(APPLE) + target_link_options(sunshine PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,${CMAKE_CURRENT_SOURCE_DIR}/assets/Info.plist) +endif() + foreach(flag IN LISTS SUNSHINE_COMPILE_OPTIONS) list(APPEND SUNSHINE_COMPILE_OPTIONS_CUDA "$<$:--compiler-options=${flag}>") endforeach() diff --git a/Portfile b/Portfile new file mode 100644 index 00000000..ea751ca8 --- /dev/null +++ b/Portfile @@ -0,0 +1,48 @@ +# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4 + +PortSystem 1.0 +PortGroup cmake 1.1 +PortGroup github 1.0 +PortGroup boost 1.0 + +github.setup abusse sunshine macos-dev +version 20220224 + +categories multimedia +platforms darwin +license GPL-2 +maintainers {outlook.com:anselm.busse} + +fetch.type git +post-fetch { + system -W ${worksrcpath} "${git.cmd} submodule update --init --recursive" +} + +description Sunshine is a Gamestream host for Moonlight +long_description Sunshine is a Gamestream host for Moonlight + +homepage https://github.com/SunshineStream/Sunshine + +depends_lib port:avahi port:ffmpeg port:libopus + + +boost.version 1.76 + +configure.args -DBOOST_ROOT=[boost::install_area] \ + -DSUNSHINE_ASSETS_DIR=${prefix}/etc/sunshine + +cmake.out_of_source yes + +destroot { + xinstall -d -m 755 ${destroot}${prefix}/etc/${name} + xinstall ${worksrcpath}/assets/apps_mac.json ${destroot}${prefix}/etc/${name} + xinstall ${worksrcpath}/assets/box.png ${destroot}${prefix}/etc/${name} + xinstall ${worksrcpath}/assets/sunshine.conf ${destroot}${prefix}/etc/${name} + + xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/web + xinstall {*}[glob ${worksrcpath}/assets/web/*.html] ${destroot}${prefix}/etc/${name}/web + xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/web/third_party + xinstall {*}[glob ${worksrcpath}/assets/web/third_party/*] ${destroot}${prefix}/etc/${name}/web/third_party + + xinstall ${workpath}/build/${name} ${destroot}${prefix}/bin +} diff --git a/README.md b/README.md index a25a3da0..72674eac 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Sunshine is a Gamestream host for Moonlight # Building - [Linux](README.md#linux) +- [MacOS](README.md#macos) - [Windows](README.md#windows-10) ## Linux @@ -108,6 +109,53 @@ It's necessary to allow Sunshine to use KMS - If you use hardware acceleration on Linux using an Intel or an AMD GPU (with VAAPI), you will get tons of [graphical issues](https://github.com/loki-47-6F-64/sunshine/issues/228) if your ffmpeg version is < 4.3. If it is not available in your distribution's repositories, consider using a newer version of your distribution. - Ubuntu started to ship ffmpeg 4.3 starting with groovy (20.10). If you're using an older version, you could use [this PPA](https://launchpad.net/%7Esavoury1/+archive/ubuntu/ffmpeg4) instead of upgrading. **Using PPAs is dangerous and may break your system. Use it at your own risk.** +## macOS + +### Quickstart + +- Install [MacPorts](https://www.macports.org) +- Download the `Portfile` from this repository to `/tmp` +- In a Terminal run `cd /tmp && sudo port install` +- Sunshine configuration is in `/opt/local/etc` +- Run `sunshine` to start the Sunshine server +- You will be asked to grant access to screen recording and your microphone to be able to stream it + +### Manuel Build + +#### Requirements: +macOS Big Sur and Xcode 12.5+: + +Either, using [MacPorts](https://www.macports.org), install the following +``` +sudo port install cmake boost libopus ffmpeg +``` + +Or, using [Homebrew](https://brew.sh), install the follwoing: +``` +brew install boost cmake ffmpeg libopusenc +# if there are issues with an SSL header that is not found: +cd /usr/local/include +ln -s ../opt/openssl/include/openssl . +``` + +#### Compilation: +- `git clone https://github.com/SunshineStream/Sunshine.git --recurse-submodules` +- `cd sunshine && mkdir build && cd build` +- `cmake ..` +- `make -j ${nproc}` + +If cmake fails complaining to find Boost, try to set the path explicitly: `cmake -DBOOST_ROOT=[boost path] ..`, e.g., `cmake -DBOOST_ROOT=/opt/local/libexec/boost/1.76 ..` + +### Setup: +- 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` +- `assets/sunshine.conf` is an example configuration file. Modify it as you see fit, then use it by running: + `sunshine path/to/sunshine.conf` +- `assets/apps.json` is an [example](README.md#application-list) of a list of applications that are started just before running a stream + +### Usage & Limitations: +- Command Keys are not forwarded by Moonlight. Right Option-Key is mapped to CMD-Key. +- Gamepads are not supported + ## Windows 10 ### Requirements: diff --git a/assets/apps_mac.json b/assets/apps_mac.json new file mode 100644 index 00000000..746c69b3 --- /dev/null +++ b/assets/apps_mac.json @@ -0,0 +1,6 @@ +{ + "env":{ + "PATH":"$(PATH):$(HOME)/.local/bin" + }, + "apps":[ ] +} diff --git a/assets/info.plist b/assets/info.plist new file mode 100644 index 00000000..c849435a --- /dev/null +++ b/assets/info.plist @@ -0,0 +1,12 @@ + + + + + CFBundleIdentifier + com.github.sunshinestream.sunshine + CFBundleName + Sunshine + NSMicrophoneUsageDescription + This app requires access to your microphone to stream audio. + + diff --git a/assets/sunshine.conf b/assets/sunshine.conf index ccb20fad..a1e020f0 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -155,6 +155,10 @@ # to stream audio, while muting the speakers. # virtual_sink = {0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4} +# +# !! MacOS only !! +# audio_sink = BlackHole 2ch + # !! Windows only !! # You can select the video card you want to stream: # The appropriate values can be found using the following command: @@ -279,6 +283,30 @@ # VAProfileH264High : VAEntrypointEncSlice # adapter_name = /dev/dri/renderD128 +################################# VideoToolbox ############################### +####### software encoding ########## +# Video Toolbox can be allowed/required to use software encoding instead of +# hardware accelerated encoding. +# auto -- let sunshine decide on encoding +# disabled -- disable software encoding +# allowed -- allow software encoding +# forced -- force software encoding +########################## +# vt_software = auto +# +####### realtime encoding ########## +# Disabling realtime encoding might result in a delayed frame encoding or frame drop +########################## +# vt_realtime = enabled +# +###### h264/hevc entropy ###### +# auto -- let ffmpeg decide the entropy encoding +# cabac +# cavlc +########################## +# vt_coder = auto + + ############################################## # Some configurable parameters, are merely toggles for specific features # The first occurrence turns it on, the second occurence turns it off, the third occurence turns it on again, etc, etc diff --git a/assets/web/config.html b/assets/web/config.html index 12290d59..203e807f 100644 --- a/assets/web/config.html +++ b/assets/web/config.html @@ -712,6 +712,34 @@

      Configuration

      v-model="config.adapter_name" /> + +
      + +
      + + +
      +
      + + +
      +
      + + +
      +
      diff --git a/src_assets/common/config/sunshine.conf b/src_assets/common/config/sunshine.conf index 04d51c2a..58dd670b 100644 --- a/src_assets/common/config/sunshine.conf +++ b/src_assets/common/config/sunshine.conf @@ -1 +1 @@ -# See our documentation at https://sunshinestream.readthedocs.io/en/latest/about/advanced_usage.html +# See our documentation at https://docs.lizardbyte.dev/projects/sunshine diff --git a/src_assets/macos/assets/Info.plist b/src_assets/macos/assets/Info.plist index c849435a..0cd88095 100644 --- a/src_assets/macos/assets/Info.plist +++ b/src_assets/macos/assets/Info.plist @@ -3,7 +3,7 @@ CFBundleIdentifier - com.github.sunshinestream.sunshine + dev.lizardbyte.sunshine CFBundleName Sunshine NSMicrophoneUsageDescription From f850b037bc7ac4dc4ac117f9ae9fdf2e0024d3a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Jul 2022 10:47:41 -0400 Subject: [PATCH 328/817] Bump sphinx from 5.0.2 to 5.1.1 in /scripts (#268) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 5.0.2 to 5.1.1. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/5.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v5.0.2...v5.1.1) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- scripts/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/requirements.txt b/scripts/requirements.txt index 2ed4a470..a572e249 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -1,5 +1,5 @@ Babel==2.10.3 m2r2==0.3.2 -Sphinx==5.0.2 +Sphinx==5.1.1 sphinx-copybutton==0.5.0 sphinx-rtd-theme==1.0.0 From b5fa9bb1be3ac7182084ced1e1cab248a2f27bb0 Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Fri, 29 Jul 2022 19:28:15 -0400 Subject: [PATCH 329/817] ci: update global python (#277) Co-authored-by: LizardByte-bot <108553330+RetroArcher-bot@users.noreply.github.com> --- .flake8 | 6 ++++++ .github/workflows/python-flake8.yml | 30 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 .flake8 create mode 100644 .github/workflows/python-flake8.yml diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..2ea73951 --- /dev/null +++ b/.flake8 @@ -0,0 +1,6 @@ +[flake8] +filename = + *.py +max-line-length = 120 +extend-exclude = + venv/ diff --git a/.github/workflows/python-flake8.yml b/.github/workflows/python-flake8.yml new file mode 100644 index 00000000..8769016f --- /dev/null +++ b/.github/workflows/python-flake8.yml @@ -0,0 +1,30 @@ +# This action is centrally managed in https://github.com//.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: flake8 + +on: + pull_request: + branches: [master, nightly] + types: [opened, synchronize, reopened] + +jobs: + flake8: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 # https://github.com/actions/setup-python + with: + python-version: '3.10' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools flake8 + + - name: Test with flake8 + run: | + python -m flake8 --verbose From a9af8472dfc0ecb41c0689dc1de0ab6cfc58eca2 Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Fri, 29 Jul 2022 21:45:02 -0400 Subject: [PATCH 330/817] ci: update global workflows (#279) --- .github/label-actions.yml | 10 +++--- .github/pr_release_template.md | 24 ++++++++++++++ .github/workflows/auto-create-pr.yml | 33 +++++++++++++++++++ .github/workflows/automerge.yml | 34 ++++++++++++++++++++ .github/workflows/issues-stale.yml | 47 +++++++++++++++------------- .github/workflows/issues.yml | 6 +++- .github/workflows/pull-requests.yml | 9 ++++-- 7 files changed, 135 insertions(+), 28 deletions(-) create mode 100644 .github/pr_release_template.md create mode 100644 .github/workflows/auto-create-pr.yml create mode 100644 .github/workflows/automerge.yml diff --git a/.github/label-actions.yml b/.github/label-actions.yml index de9759f7..1b6d70f1 100644 --- a/.github/label-actions.yml +++ b/.github/label-actions.yml @@ -1,13 +1,15 @@ +# This action is centrally managed in https://github.com//.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. + # Configuration for Label Actions - https://github.com/dessant/label-actions added: comment: > This feature has been added and will be available in the next release. - fixed: comment: > This issue has been fixed and will be available in the next release. - invalid:duplicate: comment: > :wave: @{issue-author}, this appears to be a duplicate of a pre-existing issue. @@ -22,8 +24,8 @@ invalid:duplicate: invalid:support: comment: > :wave: @{issue-author}, we use the issue tracker exclusively for bug reports. - However, this issue appears to be a support request. Please use our - [Discord Server](https://discord.com/invite/CGg5JxN) to get help. Thanks. + However, this issue appears to be a support request. Please use + [Discord](https://docs.lizardbyte.dev/about/support.html#discord) for support issues. Thanks. close: true lock: true lock-reason: 'off-topic' diff --git a/.github/pr_release_template.md b/.github/pr_release_template.md new file mode 100644 index 00000000..7c96c6b6 --- /dev/null +++ b/.github/pr_release_template.md @@ -0,0 +1,24 @@ +## Description + +This PR was created automatically. + + +### Screenshot + + + +### Issues Fixed or Closed + + + + + +## Type of Change +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update (changes to documentation) +- [ ] Repository update (changes to repository files) + +## Changelog Summary + diff --git a/.github/workflows/auto-create-pr.yml b/.github/workflows/auto-create-pr.yml new file mode 100644 index 00000000..ef32f2b5 --- /dev/null +++ b/.github/workflows/auto-create-pr.yml @@ -0,0 +1,33 @@ +# This action is centrally managed in https://github.com//.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: Auto create PR + +on: + pull_request: + types: + - closed + branches: + - 'nightly' + +jobs: + create_pr: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Create Pull Request + uses: repo-sync/pull-request@v2 + with: + source_branch: "" # should be "nightly" as it's the triggering branch + destination_branch: "master" + pr_title: "Pulling ${{ github.ref }} into master" + pr_template: ".github/pr_release_template.md" + pr_assignee: "${{ secrets.GH_BOT_NAME }}" + pr_draft: true + pr_allow_empty: false + github_token: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 00000000..eb1ea1a3 --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,34 @@ +# This action is centrally managed in https://github.com//.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: Automerge PR + +on: + pull_request: + types: + - opened + - synchronize + +jobs: + automerge: + runs-on: ubuntu-latest + concurrency: + group: automerge-${{ github.ref }} + cancel-in-progress: true + + steps: + - name: Automerging + uses: pascalgn/automerge-action@v0.15.3 + env: + BASE_BRANCHES: nightly + GITHUB_TOKEN: ${{ secrets.GH_BOT_TOKEN }} + GITHUB_LOGIN: ${{ secrets.GH_BOT_NAME }} + MERGE_LABELS: "" + MERGE_METHOD: "squash" + MERGE_COMMIT_MESSAGE: "{pullRequest.title} (#{pullRequest.number})" + MERGE_DELETE_BRANCH: true + MERGE_ERROR_FAIL: true + MERGE_FILTER_AUTHOR: ${{ secrets.GH_BOT_NAME }} + MERGE_RETRIES: "60" # 1 hour + MERGE_RETRY_SLEEP: "60000" # 1 minute diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml index 233582bd..57b11129 100644 --- a/.github/workflows/issues-stale.yml +++ b/.github/workflows/issues-stale.yml @@ -1,3 +1,7 @@ +# This action is centrally managed in https://github.com//.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: Stale Issues / PRs on: @@ -6,43 +10,44 @@ on: jobs: stale: - name: Check Issues / PRs + name: Check Stale Issues / PRs runs-on: ubuntu-latest steps: - name: Stale uses: actions/stale@v5 with: - stale-issue-message: > - This issue is stale because it has been open for 30 days with no activity. - Remove the stale label or comment, otherwise this will be closed in 5 days. close-issue-message: > This issue was closed because it has been stalled for 5 days with no activity. - stale-issue-label: 'stale' - exempt-issue-labels: 'added,fixed,type:enhancement,status:awaiting-triage,status:in-progress' - stale-pr-message: > - This PR is stale because it has been open for 30 days with no activity. - Remove the stale label or comment, otherwise this will be closed in 5 days. close-pr-message: > - This PR was closed because it has been stalled for 5 days with no activity. - stale-pr-label: 'stale' - exempt-pr-labels: 'status:in-progress' - days-before-stale: 60 + This PR was closed because it has been stalled for 10 days with no activity. + days-before-stale: 90 days-before-close: 10 + exempt-all-assignees: true + exempt-issue-labels: 'added,fixed' + exempt-pr-labels: 'dependencies,l10n' + stale-issue-label: 'stale' + stale-issue-message: > + This issue is stale because it has been open for 30 days with no activity. + Comment or remove the stale label, otherwise this will be closed in 5 days. + stale-pr-label: 'stale' + stale-pr-message: > + This PR is stale because it has been open for 90 days with no activity. + Comment or remove the stale label, otherwise this will be closed in 10 days. - name: Invalid Template uses: actions/stale@v5 with: - stale-issue-message: > - Invalid issues template. close-issue-message: > This issue was closed because the the template was not completed after 5 days. - stale-issue-label: 'invalid:template-incomplete' - stale-pr-message: > - Invalid PR template. close-pr-message: > This PR was closed because the the template was not completed after 5 days. - stale-pr-label: 'invalid:template-incomplete' - exempt-pr-labels: 'status:in-progress' - only-labels: 'invalid:template-incomplete' days-before-stale: 0 days-before-close: 5 + exempt-pr-labels: 'dependencies,l10n' + only-labels: 'invalid:template-incomplete' + stale-issue-label: 'invalid:template-incomplete' + stale-issue-message: > + Invalid issues template. + stale-pr-label: 'invalid:template-incomplete' + stale-pr-message: > + Invalid PR template. diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml index e009fbf2..9f4a08db 100644 --- a/.github/workflows/issues.yml +++ b/.github/workflows/issues.yml @@ -1,4 +1,8 @@ -name: Label Actions +# This action is centrally managed in https://github.com//.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: Issues on: issues: diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 36f597b9..99543875 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -1,3 +1,7 @@ +# This action is centrally managed in https://github.com//.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: Pull Requests on: @@ -17,5 +21,6 @@ jobs: exclude: nightly # Don't prevent going from nightly -> master change-to: nightly comment: | - Your PR was set to `master`, PRs should be sent to `nightly` - The base branch of this PR has been automatically changed to `nightly`, please check that there are no merge conflicts + Your PR was set to `master`, PRs should be sent to `nightly`. + The base branch of this PR has been automatically changed to `nightly`. + Please check that there are no merge conflicts From 7340f7660bdaf44ccb9ae29195ea99974dd7e1ec Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Fri, 29 Jul 2022 22:29:35 -0400 Subject: [PATCH 331/817] ci: update dependabot (#278) --- .github/dependabot.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 32fce3f3..1285370b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,15 +1,29 @@ +# This action is centrally managed in https://github.com//.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. + version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" + time: "00:00" target-branch: "nightly" - open-pull-requests-limit: 20 + open-pull-requests-limit: 10 + + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" + time: "00:00" + target-branch: "nightly" + open-pull-requests-limit: 10 - package-ecosystem: "pip" - directory: "/scripts" + directory: "/" schedule: interval: "daily" + time: "00:00" target-branch: "nightly" open-pull-requests-limit: 10 From 7451c1bd17c3d5b3adb2959239a4c28d10cf30fe Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Fri, 29 Jul 2022 23:18:06 -0400 Subject: [PATCH 332/817] ci: update global cpp (#280) --- .clang-format | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.clang-format b/.clang-format index d28dcc4e..6944ec3e 100644 --- a/.clang-format +++ b/.clang-format @@ -1,3 +1,7 @@ +# This file is centrally managed in https://github.com//.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. + # Generated from CLion C/C++ Code Style settings BasedOnStyle: LLVM AccessModifierOffset: -2 From ce9934ca52c478996a5626513b2204357c3e93ee Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Sat, 30 Jul 2022 13:44:57 -0400 Subject: [PATCH 333/817] ci: update issue templates (#281) --- .github/ISSUE_TEMPLATE/config.yml | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 25157730..28669539 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,18 @@ +# This action is centrally managed in https://github.com//.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. + blank_issues_enabled: false contact_links: - - name: Github Discussions - url: https://github.com/SunshineStream/Sunshine/discussions - about: General discussion, support, feature requests and more! - name: Discord support - url: https://sunshinestream.github.io/discord_join - about: Ask question about Sunshine in Discord + url: https://docs.lizardbyte.dev/about/support.html#discord + about: Ask questions in Discord + - name: Reddit support + url: https://www.reddit.com/r/LizardByte + about: Get community support on Reddit + - name: Facebook support + url: https://www.facebook.com/groups/lizardbyte + about: Get community support on Facebook + - name: Feature request + url: https://feedback.lizardbyte.dev + about: Share your suggestions or ideas to help us improve From 1439c8d95166609997e30de354d7fe3ebd86e79c Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Sat, 30 Jul 2022 15:14:28 -0400 Subject: [PATCH 334/817] ci: update release notifier (#282) --- .github/workflows/release-notifier.yml | 80 ++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 .github/workflows/release-notifier.yml diff --git a/.github/workflows/release-notifier.yml b/.github/workflows/release-notifier.yml new file mode 100644 index 00000000..6b33643b --- /dev/null +++ b/.github/workflows/release-notifier.yml @@ -0,0 +1,80 @@ +# This action is centrally managed in https://github.com//.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: Release Notifications + +on: + release: + types: [published] + # https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#onevent_nametypes + +jobs: + discord: + runs-on: ubuntu-latest + steps: + - name: discord + uses: sarisia/actions-status-discord@v1 # https://github.com/sarisia/actions-status-discord + with: + webhook: ${{ secrets.DISCORD_RELEASE_WEBHOOK }} + nodetail: true + nofail: false + username: ${{ secrets.DISCORD_USERNAME }} + avatar_url: ${{ secrets.ORG_LOGO_URL }} + title: ${{ github.event.repository.name }} ${{ github.ref_name }} Released + description: ${{ github.event.release.body }} + color: 0xFF4500 + + facebook_group: + runs-on: ubuntu-latest + steps: + - name: facebook-post-action + uses: ReenigneArcher/facebook-post-action@v1 # https://github.com/ReenigneArcher/facebook-post-action + with: + page_id: ${{ secrets.FACEBOOK_GROUP_ID }} + access_token: ${{ secrets.FACEBOOK_ACCESS_TOKEN }} + message: | + ${{ github.event.repository.name }} ${{ github.ref_name }} Released + ${{ github.event.release.body }} + url: ${{ github.event.release.html_url }} + + facebook_page: + runs-on: ubuntu-latest + steps: + - name: facebook-post-action + uses: ReenigneArcher/facebook-post-action@v1 # https://github.com/ReenigneArcher/facebook-post-action + with: + page_id: ${{ secrets.FACEBOOK_PAGE_ID }} + access_token: ${{ secrets.FACEBOOK_ACCESS_TOKEN }} + message: | + ${{ github.event.repository.name }} ${{ github.ref_name }} Released + ${{ github.event.release.body }} + url: ${{ github.event.release.html_url }} + + reddit: + runs-on: ubuntu-latest + steps: + - name: reddit + uses: bluwy/release-for-reddit-action@v1 # https://github.com/bluwy/release-for-reddit-action + with: + username: ${{ secrets.REDDIT_USERNAME }} + password: ${{ secrets.REDDIT_PASSWORD }} + app-id: ${{ secrets.REDDIT_CLIENT_ID }} + app-secret: ${{ secrets.REDDIT_CLIENT_SECRET }} + subreddit: ${{ secrets.REDDIT_SUBREDDIT }} + title: ${{ github.event.repository.name }} ${{ github.ref_name }} Released + url: ${{ github.event.release.html_url }} + flair-id: ${{ secrets.REDDIT_FLAIR_ID }} # https://www.reddit.com/r/>/api/link_flair.json + comment: ${{ github.event.release.body }} + + twitter: + runs-on: ubuntu-latest + steps: + - name: twitter + uses: ethomson/send-tweet-action@v1 # https://github.com/ethomson/send-tweet-action + with: + consumer-key: ${{ secrets.TWITTER_API_KEY }} + consumer-secret: ${{ secrets.TWITTER_API_SECRET }} + access-token: ${{ secrets.TWITTER_ACCESS_TOKEN }} + access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} + status: ${{ github.event.release.html_url }} From 5ac84fd03f61337163142cf76e9be347f41db0fd Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Sat, 30 Jul 2022 21:48:13 -0400 Subject: [PATCH 335/817] ci: update global workflows (#286) --- .github/workflows/automerge.yml | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index eb1ea1a3..da7b4b03 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -11,7 +11,31 @@ on: - synchronize jobs: + autoapprove: + if: > + contains(fromJson('["LizardByte-bot"]'), github.event.pull_request.user.login) && + contains(fromJson('["LizardByte-bot"]'), github.actor) + runs-on: ubuntu-latest + steps: + - name: Autoapproving + uses: hmarr/auto-approve-action@v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Label autoapproved + uses: actions/github-script@v5 + with: + github-token: ${{ secrets.GH_BOT_TOKEN }} + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['autoapproved', 'autoupdate'] + }) + automerge: + needs: [autoapprove] runs-on: ubuntu-latest concurrency: group: automerge-${{ github.ref }} @@ -30,5 +54,5 @@ jobs: MERGE_DELETE_BRANCH: true MERGE_ERROR_FAIL: true MERGE_FILTER_AUTHOR: ${{ secrets.GH_BOT_NAME }} - MERGE_RETRIES: "60" # 1 hour - MERGE_RETRY_SLEEP: "60000" # 1 minute + MERGE_RETRIES: "240" # 1 hour + MERGE_RETRY_SLEEP: "15000" # 15 seconds From 58a71bf3e73fed860485e89092fee2ac222e47fa Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Sun, 31 Jul 2022 20:52:39 -0400 Subject: [PATCH 336/817] Add permission for Flatpak. (#291) * Add permission for Flatpak. Adding --talk-name=org.freedesktop.Flatpak as this is needed to launch applications on the host. Example flatpak-spawn --host sleep 1m. * Alphabetical order --- packaging/linux/flatpak/dev.lizardbyte.sunshine.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index 7dd789b8..9624abb0 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -5,15 +5,16 @@ sdk: org.freedesktop.Sdk command: sunshine separate-locales: false finish-args: - - --share=ipc - - --socket=x11 - - --socket=wayland - - --socket=pulseaudio - - --share=network - --device=all + - --env=PULSE_PROP_media.category=Manager - --persist=.config/sunshine + - --share=ipc + - --share=network + - --socket=pulseaudio + - --socket=wayland + - --socket=x11 - --system-talk-name=org.freedesktop.Avahi - - --env=PULSE_PROP_media.category=Manager + - --talk-name=org.freedesktop.Flatpak cleanup: - /include From 49147694de13ab56489e97cfe86e77d18c2afb45 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 2 Aug 2022 13:20:34 +0000 Subject: [PATCH 337/817] Fix minor typo (#287) Correct 'verfied' to 'verified' Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> --- sunshine/nvhttp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 5da896e2..67f8878c 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -832,7 +832,7 @@ void start() { X509_NAME_oneline(X509_get_subject_name(x509), subject_name, sizeof(subject_name)); - BOOST_LOG(info) << subject_name << " -- "sv << (verified ? "verfied"sv : "denied"sv); + BOOST_LOG(info) << subject_name << " -- "sv << (verified ? "verified"sv : "denied"sv); }); while(add_cert->peek()) { From 7cc5e1345c72d16d0149f578534217007dce1d68 Mon Sep 17 00:00:00 2001 From: Ryan Caezar Itang Date: Wed, 3 Aug 2022 23:31:29 +0800 Subject: [PATCH 338/817] Add Winget Releaser workflow --- .github/workflows/winget.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/winget.yml diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml new file mode 100644 index 00000000..d1cd5166 --- /dev/null +++ b/.github/workflows/winget.yml @@ -0,0 +1,14 @@ +name: Publish to WinGet + +on: + release: + types: [released] + +jobs: + publish: + runs-on: windows-latest + steps: + - uses: vedantmgoyal2009/winget-releaser@latest + with: + identifier: LizardByte.Sunshine + token: ${{ secrets.GH_BOT_TOKEN }} From dc8bda0e1b323dfbfaa4eb1d05e5e1a7d149cffa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Aug 2022 15:13:31 -0400 Subject: [PATCH 339/817] Bump KSXGitHub/github-actions-deploy-aur from 2.3.0 to 2.4.1 (#292) Bumps [KSXGitHub/github-actions-deploy-aur](https://github.com/KSXGitHub/github-actions-deploy-aur) from 2.3.0 to 2.4.1. - [Release notes](https://github.com/KSXGitHub/github-actions-deploy-aur/releases) - [Commits](https://github.com/KSXGitHub/github-actions-deploy-aur/compare/v2.3.0...v2.4.1) --- updated-dependencies: - dependency-name: KSXGitHub/github-actions-deploy-aur dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 649ab696..3dbc3346 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -149,7 +149,7 @@ jobs: - name: Publish AUR package if: ${{ env.aur_publish == 'true' }} - uses: KSXGitHub/github-actions-deploy-aur@v2.3.0 + uses: KSXGitHub/github-actions-deploy-aur@v2.4.1 with: pkgname: ${{ env.aur_pkg }} pkgbuild: ./artifacts/PKGBUILD From b7ef109d95af574b15bb6c4cb87844df0f4868f4 Mon Sep 17 00:00:00 2001 From: sitiom Date: Thu, 4 Aug 2022 03:56:35 +0800 Subject: [PATCH 340/817] Update Winget badge link (#284) Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> --- docs/source/about/third_party_packages.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/about/third_party_packages.rst b/docs/source/about/third_party_packages.rst index 456a201f..5ae51e8b 100644 --- a/docs/source/about/third_party_packages.rst +++ b/docs/source/about/third_party_packages.rst @@ -26,7 +26,7 @@ Winget ------ .. image:: https://img.shields.io/badge/dynamic/xml?color=orange&label=Winget&style=for-the-badge&prefix=v&query=%2F%2Ftr%5B%40id%3D%27winget%27%5D%2Ftd%5B3%5D%2Fspan%2Fa&url=https%3A%2F%2Frepology.org%2Fproject%2Fsunshine%2Fversions&logo=microsoft :alt: Winget Version - :target: https://github.com/microsoft/winget-pkgs/tree/master/manifests/s/SunshineStream/Sunshine + :target: https://github.com/microsoft/winget-pkgs/tree/master/manifests/l/LizardByte/Sunshine Legacy GitHub Repo ------------------ From d38392aea0d12d9ba878d637b55ea43a8fc7f967 Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Fri, 5 Aug 2022 20:47:21 -0400 Subject: [PATCH 341/817] sunshine.conf and apps.json on user's home config dir (#269) --- .../linux/flatpak/dev.lizardbyte.sunshine.yml | 2 +- sunshine/config.cpp | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index 9624abb0..15fdb1f2 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -90,7 +90,7 @@ modules: - /share/ffmpeg/examples sources: - type: archive - url: https://ffmpeg.org/releases/ffmpeg-4.4.2.tar.xz + url: http://archive.ubuntu.com/ubuntu/pool/universe/f/ffmpeg/ffmpeg_4.4.2.orig.tar.xz sha256: af419a7f88adbc56c758ab19b4c708afbcae15ef09606b82b855291f6a6faa93 modules: - name: vmaf diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 21c27e2d..fd887605 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -20,7 +20,7 @@ using namespace std::literals; #define PRIVATE_KEY_FILE CA_DIR "/cakey.pem" #define CERTIFICATE_FILE CA_DIR "/cacert.pem" -#define APPS_JSON_PATH SUNSHINE_CONFIG_DIR "/apps.json" +#define APPS_JSON_PATH platf::appdata().string() + "/apps.json" namespace config { namespace nv { @@ -286,14 +286,14 @@ input_t input { }; sunshine_t sunshine { - 2, // min_log_level - 0, // flags - {}, // User file - {}, // Username - {}, // Password - {}, // Password Salt - SUNSHINE_CONFIG_DIR "/sunshine.conf", // config file - {}, // cmd args + 2, // min_log_level + 0, // flags + {}, // User file + {}, // Username + {}, // Password + {}, // Password Salt + platf::appdata().string() + "/sunshine.conf", // config file + {}, // cmd args 47989, }; From d963bd1daa843f6188e1c586b606aea98d53e432 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 7 Aug 2022 16:34:15 -0400 Subject: [PATCH 342/817] add discord widgetbot crate --- src_assets/common/assets/web/header.html | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src_assets/common/assets/web/header.html b/src_assets/common/assets/web/header.html index 336a609d..3957a994 100644 --- a/src_assets/common/assets/web/header.html +++ b/src_assets/common/assets/web/header.html @@ -68,4 +68,13 @@ .nav-link.active { font-weight: 500; } - \ No newline at end of file + + + + From 15aca474eb0cb5d147505c02d6912db11946df8c Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 7 Aug 2022 16:34:39 -0400 Subject: [PATCH 343/817] fix button formatting --- src_assets/common/assets/web/index.html | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src_assets/common/assets/web/index.html b/src_assets/common/assets/web/index.html index 32e8ed6c..55b41947 100644 --- a/src_assets/common/assets/web/index.html +++ b/src_assets/common/assets/web/index.html @@ -9,9 +9,13 @@

      Resources

      Resources for Sunshine!

      - LizardByte Website - Discord - Github Discussions + @@ -22,8 +26,12 @@

      Legal

      By continuing to use this software you agree to the terms and conditions in the following documents.

      - License - Third Party Notice + From c184f16e28ba0f6d700d99b29d973c481dfd6215 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 7 Aug 2022 16:39:59 -0400 Subject: [PATCH 344/817] fix support link and... - remove AUR `sunshine-git` badge --- README.rst | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 1e1a156e..8d51ff09 100644 --- a/README.rst +++ b/README.rst @@ -48,7 +48,7 @@ Integrations Support --------- -Our support methods are listed in our `LizardByte Docs `_. +Our support methods are listed in our `LizardByte Docs `_. Downloads --------- @@ -61,10 +61,6 @@ Downloads :alt: AUR votes :target: https://aur.archlinux.org/packages/sunshine -.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=AUR-git&style=for-the-badge&query=$.results.0.NumVotes&url=https%3A%2F%2Fapp.lizardbyte.dev%2Funo%2Faur%2Fsunshine-git.json&logo=archlinux - :alt: AUR-git votes - :target: https://aur.archlinux.org/packages/sunshine-git - .. comment image:: https://img.shields.io/docker/pulls/lizardbyte/sunshine?style=for-the-badge&logo=docker :alt: Docker From 094ab4eb1a33fd84c85583b57e90efe5c55f28b1 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 7 Aug 2022 16:46:02 -0400 Subject: [PATCH 345/817] fixed archlinux-package-action --- .github/workflows/CI.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 3dbc3346..ad690428 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -130,9 +130,7 @@ jobs: mv ./build/PKGBUILD ./artifacts/ - name: Validate package - # uses: hapakaien/archlinux-package-action@v2 - # the above action has an issue with the archlinux-keychain - uses: lizardbyte/archlinux-package-action@main + uses: hapakaien/archlinux-package-action@v2.2.0 with: path: artifacts flags: '--syncdeps --noconfirm' From 2afa3a439084ecd821a0c0df0903be1b734c5331 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 7 Aug 2022 17:01:25 -0400 Subject: [PATCH 346/817] Change docs nav header background color --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index f4873f15..5ef4356c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -83,7 +83,7 @@ 'prev_next_buttons_location': 'bottom', 'style_external_links': True, 'vcs_pageview_mode': 'blob', - # 'style_nav_header_background': 'white', + 'style_nav_header_background': '#151515', # Toc options 'collapse_navigation': True, 'sticky_navigation': True, From 4d3c9b0be8c78806e3830daed79f4aa8a77f83ef Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+RetroArcher-bot@users.noreply.github.com> Date: Sun, 7 Aug 2022 23:33:19 +0000 Subject: [PATCH 347/817] ci: update global workflows --- .github/dependabot.yml | 1 + .github/label-actions.yml | 1 + .github/workflows/auto-create-pr.yml | 8 ++--- .github/workflows/automerge.yml | 31 ++++++++++--------- .github/workflows/autoupdate.yml | 32 +++++++++++++++++++ .github/workflows/issues-stale.yml | 1 + .github/workflows/issues.yml | 3 +- .github/workflows/pull-requests.yml | 3 +- .github/workflows/yaml-lint.yml | 46 ++++++++++++++++++++++++++++ 9 files changed, 104 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/autoupdate.yml create mode 100644 .github/workflows/yaml-lint.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1285370b..e3b47ad7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.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. diff --git a/.github/label-actions.yml b/.github/label-actions.yml index 1b6d70f1..6d0a74a2 100644 --- a/.github/label-actions.yml +++ b/.github/label-actions.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.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. diff --git a/.github/workflows/auto-create-pr.yml b/.github/workflows/auto-create-pr.yml index ef32f2b5..ef19e40f 100644 --- a/.github/workflows/auto-create-pr.yml +++ b/.github/workflows/auto-create-pr.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.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. @@ -5,15 +6,12 @@ name: Auto create PR on: - pull_request: - types: - - closed + push: branches: - 'nightly' jobs: create_pr: - if: github.event.pull_request.merged == true runs-on: ubuntu-latest steps: @@ -25,7 +23,7 @@ jobs: with: source_branch: "" # should be "nightly" as it's the triggering branch destination_branch: "master" - pr_title: "Pulling ${{ github.ref }} into master" + pr_title: "Pulling ${{ github.ref_name }} into master" pr_template: ".github/pr_release_template.md" pr_assignee: "${{ secrets.GH_BOT_NAME }}" pr_draft: true diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index da7b4b03..7ff83e0a 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.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. @@ -17,22 +18,22 @@ jobs: contains(fromJson('["LizardByte-bot"]'), github.actor) runs-on: ubuntu-latest steps: - - name: Autoapproving - uses: hmarr/auto-approve-action@v2 - with: - github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Autoapproving + uses: hmarr/auto-approve-action@v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" - - name: Label autoapproved - uses: actions/github-script@v5 - with: - github-token: ${{ secrets.GH_BOT_TOKEN }} - script: | - github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: ['autoapproved', 'autoupdate'] - }) + - name: Label autoapproved + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GH_BOT_TOKEN }} + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['autoapproved', 'autoupdate'] + }) automerge: needs: [autoapprove] diff --git a/.github/workflows/autoupdate.yml b/.github/workflows/autoupdate.yml new file mode 100644 index 00000000..f32e65c7 --- /dev/null +++ b/.github/workflows/autoupdate.yml @@ -0,0 +1,32 @@ +--- +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +# This workflow is designed to work with: +# - automerge workflows + +# It uses GitHub Action that auto-updates pull requests branches, when changes are pushed to their destination branch. +# Auto-updating to the latest destination branch works only in the context of upstream repo and not forks. + +name: autoupdate + +on: + push: + branches: + - 'nightly' + +jobs: + autoupdate-for-bot: + name: Autoupdate autoapproved PR created in the upstream + if: startsWith(github.repository, 'LizardByte/') + runs-on: ubuntu-latest + steps: + - name: Update + uses: docker://chinthakagodawita/autoupdate-action:v1 + env: + GITHUB_TOKEN: '${{ secrets.GH_BOT_TOKEN }}' + PR_FILTER: "labelled" + PR_LABELS: "autoupdate" + PR_READY_STATE: "ready_for_review" + MERGE_CONFLICT_ACTION: "ignore" diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml index 57b11129..225b07de 100644 --- a/.github/workflows/issues-stale.yml +++ b/.github/workflows/issues-stale.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.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. diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml index 9f4a08db..f89975f9 100644 --- a/.github/workflows/issues.yml +++ b/.github/workflows/issues.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.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. @@ -8,7 +9,7 @@ on: issues: types: [labeled, unlabeled] discussion: - types: [ labeled, unlabeled ] + types: [labeled, unlabeled] jobs: label: diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 99543875..0abc26b8 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.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. @@ -18,7 +19,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: target: master - exclude: nightly # Don't prevent going from nightly -> master + exclude: nightly # Don't prevent going from nightly -> master change-to: nightly comment: | Your PR was set to `master`, PRs should be sent to `nightly`. diff --git a/.github/workflows/yaml-lint.yml b/.github/workflows/yaml-lint.yml new file mode 100644 index 00000000..83de6c23 --- /dev/null +++ b/.github/workflows/yaml-lint.yml @@ -0,0 +1,46 @@ +--- +# This action is centrally managed in https://github.com//.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: yaml lint + +on: + pull_request: + branches: [master, nightly] + types: [opened, synchronize, reopened] + +jobs: + yaml-lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: yaml lint + id: yaml-lint + uses: ibiqlik/action-yamllint@v3 + with: + # https://yamllint.readthedocs.io/en/stable/configuration.html#default-configuration + config_data: | + extends: default + rules: + comments: + level: error + line-length: + max: 120 + truthy: + allowed-values: ['true', 'false', 'on'] # GitHub uses "on" for workflow event triggers + check-keys: true + level: error + + - name: Log + run: | + echo ${{ steps.yaml-lint.outputs.logfile }} + + - name: Upload logs + uses: actions/upload-artifact@v2 + if: failure() + with: + name: yamllint-logfile + path: ${{ steps.yaml-lint.outputs.logfile }} From 1d242aed7cac86458c93e9c787a3810335019688 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 7 Aug 2022 20:26:40 -0400 Subject: [PATCH 348/817] fix yaml-lint errors --- .github/ISSUE_TEMPLATE/bug-report.yml | 10 +- .github/ISSUE_TEMPLATE/config.yml | 1 + .github/workflows/CI.yml | 323 ++++++++++-------- .github/workflows/clang.yml | 31 -- .github/workflows/cpp-clang-format-lint.yml | 60 ++++ .github/workflows/localize.yml | 126 +++---- .github/workflows/python-flake8.yml | 1 + .github/workflows/release-notifier.yml | 1 + .github/workflows/winget.yml | 31 +- .readthedocs.yaml | 7 +- crowdin.yml | 7 +- .../linux/flatpak/dev.lizardbyte.sunshine.yml | 15 +- 12 files changed, 348 insertions(+), 265 deletions(-) delete mode 100644 .github/workflows/clang.yml create mode 100644 .github/workflows/cpp-clang-format-lint.yml diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 8e4bfc76..bd21df29 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -1,3 +1,4 @@ +--- name: Bug Report description: Create a bug report to help us improve. body: @@ -5,7 +6,7 @@ body: attributes: value: > **THIS IS NOT THE PLACE TO ASK FOR SUPPORT!** - Please use [Discord](https://docs.lizardbyte.dev/about/support.html#discord) for support issues. + Please use [Discord](https://docs.lizardbyte.dev/en/latest/about/support.html#discord) for support issues. - type: textarea id: description attributes: @@ -90,9 +91,12 @@ body: id: logs attributes: label: Relevant log output - description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + 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. + Make sure to close your issue when it's solved! If you found the solution yourself please comment + so that others benefit from it. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 28669539..ba507895 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.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. diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ad690428..c9e6b0df 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,3 +1,4 @@ +--- name: CI on: @@ -52,7 +53,8 @@ jobs: - name: Check CMakeLists.txt Version run: | - version=$(grep -o -E '^project\(Sunshine VERSION [0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') + version=$(grep -o -E '^project\(Sunshine VERSION [0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt | \ + grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') echo "cmakelists_version=${version}" >> $GITHUB_ENV - name: Compare CMakeList.txt Version @@ -60,7 +62,8 @@ jobs: run: | echo CMakeLists version: "$cmakelists_version" echo Changelog version: "${{ needs.check_changelog.outputs.next_version_bare }}" - echo Within 'CMakeLists.txt' change "project(Sunshine [VERSION $cmakelists_version]" to "project(Sunshine [VERSION ${{ needs.check_changelog.outputs.next_version_bare }}]" + echo Within 'CMakeLists.txt' change "project(Sunshine [VERSION $cmakelists_version]" to \ + "project(Sunshine [VERSION ${{ needs.check_changelog.outputs.next_version_bare }}]" exit 1 build_linux_aur: @@ -76,7 +79,7 @@ jobs: run: | sudo apt-get update -y sudo apt-get install -y \ - cmake + cmake - name: Configure PKGBUILD files run: | @@ -86,47 +89,55 @@ jobs: 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' }} ]]; then - aur_pkg=sunshine - conflicts="" - provides="" - - echo "aur_publish=true" >> $GITHUB_ENV - elif [[ ${{ github.ref == 'refs/heads/nightly' }} ]]; then - aur_pkg=sunshine-git - sub_version=".r${commit}" - - echo "aur_publish=true" >> $GITHUB_ENV - 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 }} - + echo "This is a PUSH event" + commit=${{ github.sha }} + clone_url=${{ github.event.repository.clone_url }} + + if [[ ${{ github.ref == 'refs/heads/master' }} ]]; then + aur_pkg=sunshine + conflicts="" + provides="" + + echo "aur_publish=true" >> $GITHUB_ENV + elif [[ ${{ github.ref == 'refs/heads/nightly' }} ]]; then + aur_pkg=sunshine-git sub_version=".r${commit}" + + echo "aur_publish=true" >> $GITHUB_ENV + 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_pkg=${aur_pkg}" >> $GITHUB_ENV - + 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 .. + 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 @@ -172,42 +183,50 @@ jobs: run: | sudo apt-get update -y sudo apt-get install -y \ - cmake \ - flatpak - sudo su $(whoami) -c 'flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo' - sudo su $(whoami) -c 'flatpak install --user flathub org.flatpak.Builder org.freedesktop.Platform//21.08 org.freedesktop.Sdk//21.08 -y' + cmake \ + flatpak + sudo su $(whoami) -c 'flatpak remote-add --user --if-not-exists flathub \ + https://flathub.org/repo/flathub.flatpakrepo' + sudo su $(whoami) -c 'flatpak install --user flathub \ + org.flatpak.Builder org.freedesktop.Platform//21.08 org.freedesktop.Sdk//21.08 -y' - name: Configure Flatpak Manifest run: | # variables for manifest branch=${GITHUB_HEAD_REF} - + # check the branch variable if [ -z "$branch" ] then - echo "This is a PUSH event" - branch=${{ github.ref_name }} - commit=${{ github.sha }} - clone_url=${{ github.event.repository.clone_url }} + echo "This is a PUSH event" + branch=${{ github.ref_name }} + commit=${{ github.sha }} + clone_url=${{ github.event.repository.clone_url }} else - echo "This is a PR event" - commit=${{ github.event.pull_request.head.sha }} - clone_url=${{ github.event.pull_request.head.repo.clone_url }} + echo "This is a PR event" + commit=${{ github.event.pull_request.head.sha }} + clone_url=${{ github.event.pull_request.head.repo.clone_url }} fi echo "Branch: ${branch}" echo "Commit: ${commit}" echo "Clone URL: ${clone_url}" - + mkdir -p build mkdir -p artifacts - + cd build - cmake -DGITHUB_CLONE_URL=${clone_url} -DGITHUB_BRANCH=${branch} -DGITHUB_COMMIT=${commit} -DSUNSHINE_CONFIGURE_FLATPAK=ON -DSUNSHINE_CONFIGURE_ONLY=ON .. + cmake -DGITHUB_CLONE_URL=${clone_url} \ + -DGITHUB_BRANCH=${branch} \ + -DGITHUB_COMMIT=${commit} \ + -DSUNSHINE_CONFIGURE_FLATPAK=ON \ + -DSUNSHINE_CONFIGURE_ONLY=ON \ + .. - name: Build Linux Flatpak working-directory: build run: | - sudo su $(whoami) -c 'flatpak run org.flatpak.Builder --repo=repo --force-clean build-sunshine dev.lizardbyte.sunshine.yml' + sudo su $(whoami) -c 'flatpak run org.flatpak.Builder --repo=repo --force-clean build-sunshine \ + dev.lizardbyte.sunshine.yml' sudo su $(whoami) -c 'flatpak build-bundle ./repo ../artifacts/sunshine.flatpak dev.lizardbyte.sunshine' - name: Upload Artifacts @@ -232,7 +251,7 @@ jobs: strategy: fail-fast: false # false to test all, true to fail entire job if any fail matrix: - include: # package these differently + include: # package these differently - type: cpack CMAKE_INSTALL_PREFIX: '/usr' SUNSHINE_ASSETS_DIR: 'local/sunshine/assets' @@ -255,57 +274,60 @@ jobs: sudo add-apt-repository ppa:savoury1/ffmpeg4 -y # sudo add-apt-repository ppa:savoury1/boost-defaults-1.71 -y sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y - + sudo apt-get update -y sudo apt-get install -y \ - build-essential \ - cmake \ - gcc-10 \ - git \ - g++-10 \ - libavdevice-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libboost-thread-dev \ - libcap-dev \ - libdrm-dev \ - libevdev-dev \ - libpulse-dev \ - libopus-dev \ - libssl-dev \ - libwayland-dev \ - libx11-dev \ - libxcb-shm0-dev \ - libxcb-xfixes0-dev \ - libxcb1-dev \ - libxfixes-dev \ - libxrandr-dev \ - libxtst-dev \ - wget + build-essential \ + cmake \ + gcc-10 \ + git \ + g++-10 \ + libavdevice-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libboost-thread-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + wget # # Ubuntu 20.04+ packages # libboost-filesystem-dev # libboost-log-dev # libboost-thread-dev - + # # Ubuntu 18.04 packages # libboost-filesystem1.71-dev \ # libboost-log1.71-dev \ # libboost-regex1.71-dev \ # libboost-thread1.71-dev \ - + # clean apt cache sudo apt-get clean sudo rm -rf /var/lib/apt/lists/* - + # Update gcc alias - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 - + sudo update-alternatives --install \ + /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 + # Install CuDA - sudo wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run + sudo wget \ + https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run \ + --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run sudo chmod a+x /root/cuda.run sudo /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm sudo rm /root/cuda.run - + # # Install cmake (necessary for 18.04) # wget https://cmake.org/files/v3.22/cmake-3.22.2-linux-x86_64.sh # chmod +x cmake-3.22.2-linux-x86_64.sh @@ -318,9 +340,19 @@ jobs: run: | mkdir -p build mkdir -p artifacts - + cd build - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${{ matrix.CMAKE_INSTALL_PREFIX }} -DSUNSHINE_ASSETS_DIR=${{ matrix.SUNSHINE_ASSETS_DIR }} -DSUNSHINE_CONFIG_DIR=${{ matrix.SUNSHINE_CONFIG_DIR }} -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine -DSUNSHINE_ENABLE_WAYLAND=ON -DSUNSHINE_ENABLE_X11=ON -DSUNSHINE_ENABLE_DRM=ON -DSUNSHINE_ENABLE_CUDA=ON ${{ matrix.EXTRA_ARGS }} .. + cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=${{ matrix.CMAKE_INSTALL_PREFIX }} \ + -DSUNSHINE_ASSETS_DIR=${{ matrix.SUNSHINE_ASSETS_DIR }} \ + -DSUNSHINE_CONFIG_DIR=${{ matrix.SUNSHINE_CONFIG_DIR }} \ + -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ + -DSUNSHINE_ENABLE_WAYLAND=ON \ + -DSUNSHINE_ENABLE_X11=ON \ + -DSUNSHINE_ENABLE_DRM=ON \ + -DSUNSHINE_ENABLE_CUDA=ON \ + ${{ matrix.EXTRA_ARGS }} \ + .. make -j ${nproc} - name: Package Linux - CPACK @@ -336,7 +368,7 @@ jobs: mv ./cpack_artifacts/Sunshine.rpm ../artifacts/sunshine.rpm - name: Set AppImage Version - if: ${{ matrix.type == 'appimage' && ( needs.check_changelog.outputs.next_version_bare != needs.check_changelog.outputs.latest_version ) }} + if: ${{ matrix.type == 'appimage' && ( needs.check_changelog.outputs.next_version_bare != needs.check_changelog.outputs.latest_version ) }} # yamllint disable-line rule:line-length run: | version=${{ needs.check_changelog.outputs.next_version_bare }} echo "VERSION=${version}" >> $GITHUB_ENV @@ -347,43 +379,44 @@ jobs: run: | # install sunshine to the DESTDIR make install DESTDIR=AppDir - + # portable home and config # todo - this is ugly... we should use a custom AppRun script to take care of this mv ./AppDir${{ matrix.CMAKE_INSTALL_PREFIX }}/sunshine.AppImage.* ../artifacts/ mkdir -p ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_CONFIG_DIR }} - cp ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/apps.json ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_CONFIG_DIR }}/ - + cp ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/apps.json \ + ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_CONFIG_DIR }}/ + # variables DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" ICON_FILE="${ICON_FILE:-sunshine.png}" - + # AppImage # https://docs.appimage.org/packaging-guide/index.html wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage chmod +x linuxdeploy-x86_64.AppImage - + # # https://github.com/linuxdeploy/linuxdeploy-plugin-gtk # sudo apt-get install libgtk-3-dev librsvg2-dev -y # wget https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh # chmod +x linuxdeploy-plugin-gtk.sh # export DEPLOY_GTK_VERSION=3 - + ./linuxdeploy-x86_64.AppImage \ - --appdir ./AppDir \ - --executable ./sunshine \ - --icon-file "../$ICON_FILE" \ - --desktop-file "./$DESKTOP_FILE" \ - --library /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0 \ - --library /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 \ - --library /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0 \ - --output appimage + --appdir ./AppDir \ + --executable ./sunshine \ + --icon-file "../$ICON_FILE" \ + --desktop-file "./$DESKTOP_FILE" \ + --library /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0 \ + --library /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 \ + --library /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0 \ + --output appimage # # add this argument back if using gtk plugin # --plugin gtk \ # move mv Sunshine*.AppImage ../artifacts/sunshine.AppImage - + # permissions chmod +x ../artifacts/sunshine.AppImage @@ -392,9 +425,9 @@ jobs: run: | wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage chmod +x appimagelint-x86_64.AppImage - + # rm -rf ~/.cache/appimagelint/ - + ./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage - name: Archive AppImage @@ -402,7 +435,7 @@ jobs: working-directory: artifacts run: | chmod +x ./sunshine.AppImage - + zip --recurse-paths --move --test ./sunshine-appimage.zip ./* - name: Upload Artifacts @@ -443,7 +476,11 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DSUNSHINE_ASSETS_DIR=local/sunshine/assets -DSUNSHINE_CONFIG_DIR=local/sunshine/config .. + cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DSUNSHINE_ASSETS_DIR=local/sunshine/assets \ + -DSUNSHINE_CONFIG_DIR=local/sunshine/config \ + .. make -j ${nproc} - name: Package MacOS @@ -454,10 +491,10 @@ jobs: # package cpack -G DragNDrop mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-macos-experimental-dragndrop.dmg - + cpack -G Bundle mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-macos-experimental-bundle.dmg - + cpack -G ZIP mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-macos-experimental-archive.zip @@ -476,25 +513,20 @@ jobs: rm -f ./sunshine-macos-experimental-bundle.dmg rm -f ./sunshine-macos-experimental-archive.zip - # no artifacts to release currently -# - name: Create Release -# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} -# uses: LizardByte/.github/actions/create_release@master -# with: -# token: ${{ secrets.GH_BOT_TOKEN }} -# next_version: ${{ needs.check_changelog.outputs.next_version }} -# last_version: ${{ needs.check_changelog.outputs.last_version }} -# release_body: ${{ needs.check_changelog.outputs.release_body }} + ## no artifacts to release currently + # - name: Create Release + # if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + # uses: LizardByte/.github/actions/create_release@master + # with: + # token: ${{ secrets.GH_BOT_TOKEN }} + # next_version: ${{ needs.check_changelog.outputs.next_version }} + # last_version: ${{ needs.check_changelog.outputs.last_version }} + # release_body: ${{ needs.check_changelog.outputs.release_body }} build_mac_port: name: Macports needs: check_changelog runs-on: macos-11 -# runs-on: ${{ matrix.os }} -# strategy: -# fail-fast: false -# matrix: -# os: [ macos-10.15, macos-11, macos-12 ] steps: - name: Checkout @@ -522,42 +554,45 @@ jobs: run: | # variables for Portfile 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 }} + echo "This is a PUSH event" + commit=${{ github.sha }} + clone_url=${{ github.event.repository.clone_url }} else - echo "This is a PR event" - commit=${{ github.event.pull_request.head.sha }} - clone_url=${{ github.event.pull_request.head.repo.clone_url }} + echo "This is a PR event" + commit=${{ github.event.pull_request.head.sha }} + clone_url=${{ github.event.pull_request.head.repo.clone_url }} fi echo "Commit: ${commit}" echo "Clone URL: ${clone_url}" - + mkdir build cd build - cmake -DGITHUB_COMMIT=${commit} -DGITHUB_CLONE_URL=${clone_url} -DSUNSHINE_CONFIGURE_PORTFILE=ON -DSUNSHINE_CONFIGURE_ONLY=ON .. - + cmake -DGITHUB_COMMIT=${commit} \ + -DGITHUB_CLONE_URL=${clone_url} \ + -DSUNSHINE_CONFIGURE_PORTFILE=ON \ + -DSUNSHINE_CONFIGURE_ONLY=ON \ + .. cd .. - + # copy Portfile to artifacts mkdir -p artifacts cp -f ./build/Portfile ./artifacts/ - + # copy Portfile to ports mkdir -p ./ports/multimedia/Sunshine cp -f ./build/Portfile ./ports/multimedia/Sunshine/Portfile - + # testing cat ./artifacts/Portfile - name: Bootstrap MacPorts run: | . ports/.github/workflows/bootstrap.sh - + # Add getopt, mpbb and the MacPorts paths to $PATH for the subsequent steps. echo "/opt/mports/bin" >> $GITHUB_PATH echo "${PWD}/mpbb" >> $GITHUB_PATH @@ -573,12 +608,12 @@ jobs: echo "Listing subports for Sunshine" new_subports=$(mpbb \ - --work-dir /tmp/mpbb \ - list-subports \ - --archive-site= \ - --archive-site-private= \ - --include-deps=no \ - "$port" \ + --work-dir /tmp/mpbb \ + list-subports \ + --archive-site= \ + --archive-site-private= \ + --include-deps=no \ + "$port" \ | tr '\n' ' ') for subport in $new_subports; do echo "$subport" @@ -671,14 +706,14 @@ jobs: work=$(port work sunshine) echo "Sunshine port work directory: ${work}" - + # move components out of port work directory sudo mv ${work}/Sunshine*component.pkg /tmp/ - + # copy artifacts sudo mv ${work}/Sunshine*.pkg ./artifacts/sunshine.pkg sudo mv ${work}/Sunshine*.dmg ./artifacts/sunshine.dmg - + # move components back # sudo mv /tmp/Sunshine*component.pkg ${work}/ @@ -733,7 +768,11 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -DSUNSHINE_CONFIG_DIR=config -G "MinGW Makefiles" .. + cmake -DCMAKE_BUILD_TYPE=Release \ + -DSUNSHINE_ASSETS_DIR=assets \ + -DSUNSHINE_CONFIG_DIR=config \ + -G "MinGW Makefiles" \ + .. mingw32-make -j2 - name: Package Windows diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml deleted file mode 100644 index 3c0a70e6..00000000 --- a/.github/workflows/clang.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: clang-format-lint - -on: - pull_request: - branches: [master, nightly] - types: [opened, synchronize, reopened] - -jobs: - lint: - name: Clang Format Lint - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Clang format lint - uses: DoozyX/clang-format-lint-action@v0.14 - with: - source: './sunshine' - extensions: 'cpp,h,m,mm' - clangFormatVersion: 13 - style: file - inplace: false - - - name: Upload Artifacts - if: failure() - uses: actions/upload-artifact@v3 - with: - name: sunshine - path: sunshine/ diff --git a/.github/workflows/cpp-clang-format-lint.yml b/.github/workflows/cpp-clang-format-lint.yml new file mode 100644 index 00000000..4717f70a --- /dev/null +++ b/.github/workflows/cpp-clang-format-lint.yml @@ -0,0 +1,60 @@ +--- +# This action is centrally managed in https://github.com//.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: Clang Format Lint + +on: + pull_request: + branches: [master, nightly] + types: [opened, synchronize, reopened] + +jobs: + check_src: + name: Check src + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Check + id: check + run: | + if [ -d "./src" ] + then + FOUND=true + else + FOUND=false + fi + + echo "::set-output name=src::${FOUND}" + + outputs: + src: ${{ steps.check.outputs.src }} + + lint: + name: Clang Format Lint + needs: [check_src] + if: ${{ needs.check_src.outputs.src == 'true' }} + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Clang format lint + uses: DoozyX/clang-format-lint-action@v0.14 + with: + source: './src' + extensions: 'cpp,h,m,mm' + clangFormatVersion: 13 + style: file + inplace: false + + - name: Upload Artifacts + if: failure() + uses: actions/upload-artifact@v3 + with: + name: clang-format-fixes + path: src/ diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index d48ab223..df3e69ca 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -1,3 +1,4 @@ +--- name: localize on: @@ -18,76 +19,77 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - - name: Install Python 3.9 - uses: actions/setup-python@v4 # https://github.com/actions/setup-python - with: - python-version: '3.9' + - name: Install Python 3.9 + uses: actions/setup-python@v4 # https://github.com/actions/setup-python + with: + python-version: '3.9' - - name: Set up Python 3.9 Dependencies - run: | - cd ./scripts - python -m pip install --upgrade pip setuptools - python -m pip install -r requirements.txt + - name: Set up Python 3.9 Dependencies + run: | + cd ./scripts + python -m pip install --upgrade pip setuptools + python -m pip install -r requirements.txt - - name: Set up xgettext - run: | - sudo apt-get update -y && \ - sudo apt-get --reinstall install -y \ - gettext + - name: Set up xgettext + run: | + sudo apt-get update -y && \ + sudo apt-get --reinstall install -y \ + gettext - - name: Update Strings - run: | - # first, try to remove existing file as xgettext does not remove unused translations - if [ -f "${{ env.file }}" ]; - then - rm ${{ env.file }} - echo "new_file=false" >> $GITHUB_ENV - else - echo "new_file=true" >> $GITHUB_ENV - fi + - name: Update Strings + run: | + # first, try to remove existing file as xgettext does not remove unused translations + if [ -f "${{ env.file }}" ]; + then + rm ${{ env.file }} + echo "new_file=false" >> $GITHUB_ENV + else + echo "new_file=true" >> $GITHUB_ENV + fi - # extract the new strings - python ./scripts/_locale.py --extract + # extract the new strings + python ./scripts/_locale.py --extract - - name: git diff - if: ${{ env.new_file == 'false' }} - run: | - # disable the pager - git config --global pager.diff false + - name: git diff + if: ${{ env.new_file == 'false' }} + run: | + # disable the pager + git config --global pager.diff false - # print the git diff - git diff locale/sunshine.po + # print the git diff + git diff locale/sunshine.po - # set the variable with minimal output - OUTPUT=$(git diff --numstat locale/sunshine.po) - echo "git_diff=${OUTPUT}" >> $GITHUB_ENV + # set the variable with minimal output + OUTPUT=$(git diff --numstat locale/sunshine.po) + echo "git_diff=${OUTPUT}" >> $GITHUB_ENV - - name: git reset - # only run if a single line changed (date/time) and file already existed - if: ${{ env.git_diff == '1 1 locale/sunshine.po' && env.new_file == 'false' }} - run: | - git reset --hard + - name: git reset + # only run if a single line changed (date/time) and file already existed + # \t in next line is a tab character + if: ${{ env.git_diff == '1\t1\tlocale/sunshine.po' && env.new_file == 'false' }} + run: | + git reset --hard - - name: Create/Update Pull Request - uses: peter-evans/create-pull-request@v4 - with: - add-paths: | - locale/*.po - token: ${{ secrets.GH_BOT_TOKEN }} # must trigger PR tests - commit-message: New localization template - branch: localize/update - delete-branch: true - base: nightly - title: New Babel Updates - body: | - Update report - - Updated with *today's* date - - Auto-generated by [create-pull-request][1] + - name: Create/Update Pull Request + uses: peter-evans/create-pull-request@v4 + with: + add-paths: | + locale/*.po + token: ${{ secrets.GH_BOT_TOKEN }} # must trigger PR tests + commit-message: New localization template + branch: localize/update + delete-branch: true + base: nightly + title: New Babel Updates + body: | + Update report + - Updated with *today's* date + - Auto-generated by [create-pull-request][1] - [1]: https://github.com/peter-evans/create-pull-request - labels: | - babel - l10n + [1]: https://github.com/peter-evans/create-pull-request + labels: | + babel + l10n diff --git a/.github/workflows/python-flake8.yml b/.github/workflows/python-flake8.yml index 8769016f..463fb8a2 100644 --- a/.github/workflows/python-flake8.yml +++ b/.github/workflows/python-flake8.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.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. diff --git a/.github/workflows/release-notifier.yml b/.github/workflows/release-notifier.yml index 6b33643b..7a2c13ea 100644 --- a/.github/workflows/release-notifier.yml +++ b/.github/workflows/release-notifier.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.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. diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml index d1cd5166..0cf5b371 100644 --- a/.github/workflows/winget.yml +++ b/.github/workflows/winget.yml @@ -1,14 +1,17 @@ -name: Publish to WinGet - -on: - release: - types: [released] - -jobs: - publish: - runs-on: windows-latest - steps: - - uses: vedantmgoyal2009/winget-releaser@latest - with: - identifier: LizardByte.Sunshine - token: ${{ secrets.GH_BOT_TOKEN }} +--- +name: Publish to WinGet + +on: + release: + types: [released] + +jobs: + winget-releaser: + name: winget releaser + runs-on: windows-latest + steps: + - name: winget releaser + uses: vedantmgoyal2009/winget-releaser@latest + with: + identifier: LizardByte.Sunshine + token: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 762371f8..156e5624 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,3 +1,4 @@ +--- # .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details @@ -25,9 +26,9 @@ build: # - cmake . ## Include the submodules, required for cmake -#submodules: -# include: all -# recursive: true +# submodules: +# include: all +# recursive: true # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/crowdin.yml b/crowdin.yml index ca9c8c5c..0be504ba 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,3 +1,4 @@ +--- "base_path": "." "base_url": "https://api.crowdin.com" # optional (for Crowdin Enterprise only) "preserve_hierarchy": false # flatten tree on crowdin @@ -6,10 +7,10 @@ "l10n" ] -"files" : [ +"files": [ { - "source" : "/locale/*.po", - "translation" : "/locale/%two_letters_code%/LC_MESSAGES/%original_file_name%", + "source": "/locale/*.po", + "translation": "/locale/%two_letters_code%/LC_MESSAGES/%original_file_name%", "languages_mapping": { "two_letters_code": { # map non-two letter codes here, left side is crowdin designation, right side is babel designation diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index 15fdb1f2..04d3df5a 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -1,3 +1,4 @@ +--- app-id: dev.lizardbyte.sunshine runtime: org.freedesktop.Platform runtime-version: "21.08" @@ -35,7 +36,7 @@ modules: - '*' build-commands: - chmod u+x ./cuda.run - - ./cuda.run --silent --toolkit --toolkitpath=$FLATPAK_DEST/cuda --no-opengl-libs --no-man-page --no-drm --tmpdir=$FLATPAK_BUILDER_BUILDDIR + - ./cuda.run --silent --toolkit --toolkitpath=$FLATPAK_DEST/cuda --no-opengl-libs --no-man-page --no-drm --tmpdir=$FLATPAK_BUILDER_BUILDDIR # yamllint disable-line rule:line-length - rm -r $FLATPAK_DEST/cuda/nsight-systems-2021.3.2 - rm ./cuda.run sources: @@ -48,7 +49,7 @@ modules: - type: file only-arches: - aarch64 - url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux_sbsa.run + url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux_sbsa.run # yamllint disable-line rule:line-length sha256: f2c4a52e06329606c8dfb7c5ea3f4cb4c0b28f9d3fdffeeb734fcc98daf580d8 dest-filename: cuda.run @@ -56,7 +57,7 @@ modules: buildsystem: simple build-commands: - ./bootstrap.sh --prefix=$FLATPAK_DEST --with-libraries=system,thread,log - - ./b2 install variant=release link=static,shared runtime-link=shared cxxflags="$CXXFLAGS" linkflags="$LDFLAGS" -j $FLATPAK_BUILDER_N_JOBS + - ./b2 install variant=release link=static,shared runtime-link=shared cxxflags="$CXXFLAGS" linkflags="$LDFLAGS" -j $FLATPAK_BUILDER_N_JOBS # yamllint disable-line rule:line-length sources: - type: archive url: https://boostorg.jfrog.io/artifactory/main/release/1.79.0/source/boost_1_79_0.tar.bz2 @@ -170,7 +171,7 @@ modules: - /bin sources: - type: archive - url: https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz + url: https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz # yamllint disable-line rule:line-length sha256: 92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb - name: libevdev @@ -210,6 +211,6 @@ modules: - -DSUNSHINE_ENABLE_CUDA=ON sources: - type: git - url: @GITHUB_CLONE_URL@ - branch: @GITHUB_BRANCH@ - commit: @GITHUB_COMMIT@ + url: '@GITHUB_CLONE_URL@' + branch: '@GITHUB_BRANCH@' + commit: '@GITHUB_COMMIT@' From 0de52efdb119959ee60fd8246c5b63b4aebdaf91 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 7 Aug 2022 23:13:19 -0400 Subject: [PATCH 349/817] move TPCircularBuffer submodule --- .gitmodules | 4 ++-- CMakeLists.txt | 4 ++-- sunshine/platform/macos/av_audio.h | 2 +- {sunshine/platform/macos => third-party}/TPCircularBuffer | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename {sunshine/platform/macos => third-party}/TPCircularBuffer (100%) diff --git a/.gitmodules b/.gitmodules index 814f3409..1b60acde 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,6 +13,6 @@ [submodule "third-party/nv-codec-headers"] path = third-party/nv-codec-headers url = https://github.com/FFmpeg/nv-codec-headers -[submodule "sunshine/platform/macos/TPCircularBuffer"] - path = sunshine/platform/macos/TPCircularBuffer +[submodule "third-party/TPCircularBuffer"] + path = third-party/TPCircularBuffer url = https://github.com/michaeltyson/TPCircularBuffer diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e94d53a..0a18d3eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -193,8 +193,8 @@ elseif(APPLE) sunshine/platform/macos/nv12_zero_device.cpp sunshine/platform/macos/nv12_zero_device.h sunshine/platform/macos/publish.cpp - sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.c - sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.h + third-party/TPCircularBuffer/TPCircularBuffer.c + third-party/TPCircularBuffer/TPCircularBuffer.h ${APPLE_PLIST_FILE}) else() add_compile_definitions(SUNSHINE_PLATFORM="linux") diff --git a/sunshine/platform/macos/av_audio.h b/sunshine/platform/macos/av_audio.h index 8c04e5bb..2a0b5a43 100644 --- a/sunshine/platform/macos/av_audio.h +++ b/sunshine/platform/macos/av_audio.h @@ -3,7 +3,7 @@ #import -#include "sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.h" +#include "third-party/TPCircularBuffer/TPCircularBuffer.h" #define kBufferLength 2048 diff --git a/sunshine/platform/macos/TPCircularBuffer b/third-party/TPCircularBuffer similarity index 100% rename from sunshine/platform/macos/TPCircularBuffer rename to third-party/TPCircularBuffer From a4acaf15b0d762c9e291365e379659be1c5d1142 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 7 Aug 2022 23:37:57 -0400 Subject: [PATCH 350/817] move `sunshine` to `src` - this will allow for common cpp workflow files within org --- .github/workflows/localize.yml | 2 +- CMakeLists.txt | 162 +- docs/source/contributing/localization.rst | 2 +- {sunshine => src}/audio.cpp | 0 {sunshine => src}/audio.h | 0 {sunshine => src}/cbs.cpp | 0 {sunshine => src}/cbs.h | 0 {sunshine => src}/config.cpp | 0 {sunshine => src}/config.h | 0 {sunshine => src}/confighttp.cpp | 0 {sunshine => src}/confighttp.h | 0 {sunshine => src}/crypto.cpp | 0 {sunshine => src}/crypto.h | 0 {sunshine => src}/httpcommon.cpp | 0 {sunshine => src}/httpcommon.h | 0 {sunshine => src}/input.cpp | 0 {sunshine => src}/input.h | 0 {sunshine => src}/main.cpp | 0 {sunshine => src}/main.h | 0 {sunshine => src}/move_by_copy.h | 0 {sunshine => src}/network.cpp | 0 {sunshine => src}/network.h | 0 {sunshine => src}/nvhttp.cpp | 0 {sunshine => src}/nvhttp.h | 0 {sunshine => src}/platform/common.h | 4 +- {sunshine => src}/platform/linux/audio.cpp | 8 +- {sunshine => src}/platform/linux/cuda.cpp | 4 +- {sunshine => src}/platform/linux/cuda.cu | 0 {sunshine => src}/platform/linux/cuda.h | 0 {sunshine => src}/platform/linux/graphics.cpp | 2 +- {sunshine => src}/platform/linux/graphics.h | 6 +- {sunshine => src}/platform/linux/input.cpp | 8 +- {sunshine => src}/platform/linux/kmsgrab.cpp | 8 +- {sunshine => src}/platform/linux/misc.cpp | 4 +- {sunshine => src}/platform/linux/misc.h | 2 +- {sunshine => src}/platform/linux/publish.cpp | 8 +- {sunshine => src}/platform/linux/vaapi.cpp | 8 +- {sunshine => src}/platform/linux/vaapi.h | 2 +- {sunshine => src}/platform/linux/wayland.cpp | 8 +- {sunshine => src}/platform/linux/wayland.h | 0 {sunshine => src}/platform/linux/wlgrab.cpp | 4 +- {sunshine => src}/platform/linux/x11grab.cpp | 8 +- {sunshine => src}/platform/linux/x11grab.h | 4 +- {sunshine => src}/platform/macos/av_audio.h | 0 {sunshine => src}/platform/macos/av_audio.m | 0 {sunshine => src}/platform/macos/av_img_t.h | 2 +- {sunshine => src}/platform/macos/av_video.h | 0 {sunshine => src}/platform/macos/av_video.m | 0 {sunshine => src}/platform/macos/display.mm | 12 +- {sunshine => src}/platform/macos/input.cpp | 6 +- .../platform/macos/microphone.mm | 8 +- {sunshine => src}/platform/macos/misc.cpp | 4 +- {sunshine => src}/platform/macos/misc.h | 0 .../platform/macos/nv12_zero_device.cpp | 6 +- .../platform/macos/nv12_zero_device.h | 2 +- {sunshine => src}/platform/macos/publish.cpp | 8 +- .../platform/windows/PolicyConfig.h | 328 +-- {sunshine => src}/platform/windows/audio.cpp | 1976 ++++++++--------- {sunshine => src}/platform/windows/display.h | 354 +-- .../platform/windows/display_base.cpp | 6 +- .../platform/windows/display_ram.cpp | 654 +++--- .../platform/windows/display_vram.cpp | 1774 +++++++-------- {sunshine => src}/platform/windows/input.cpp | 6 +- {sunshine => src}/platform/windows/misc.cpp | 244 +- {sunshine => src}/platform/windows/misc.h | 24 +- .../platform/windows/publish.cpp | 390 ++-- .../platform/windows/windows.rs.in | 0 {sunshine => src}/process.cpp | 0 {sunshine => src}/process.h | 0 {sunshine => src}/round_robin.h | 0 {sunshine => src}/rtsp.cpp | 0 {sunshine => src}/rtsp.h | 0 {sunshine => src}/stream.cpp | 0 {sunshine => src}/stream.h | 0 {sunshine => src}/sync.h | 0 {sunshine => src}/task_pool.h | 0 {sunshine => src}/thread_pool.h | 0 {sunshine => src}/thread_safe.h | 0 {sunshine => src}/upnp.cpp | 0 {sunshine => src}/upnp.h | 0 {sunshine => src}/utility.h | 0 {sunshine => src}/uuid.h | 0 {sunshine => src}/video.cpp | 0 {sunshine => src}/video.h | 0 tools/audio.cpp | 2 +- tools/dxgi.cpp | 2 +- 86 files changed, 3031 insertions(+), 3031 deletions(-) rename {sunshine => src}/audio.cpp (100%) rename {sunshine => src}/audio.h (100%) rename {sunshine => src}/cbs.cpp (100%) rename {sunshine => src}/cbs.h (100%) rename {sunshine => src}/config.cpp (100%) rename {sunshine => src}/config.h (100%) rename {sunshine => src}/confighttp.cpp (100%) rename {sunshine => src}/confighttp.h (100%) rename {sunshine => src}/crypto.cpp (100%) rename {sunshine => src}/crypto.h (100%) rename {sunshine => src}/httpcommon.cpp (100%) rename {sunshine => src}/httpcommon.h (100%) rename {sunshine => src}/input.cpp (100%) rename {sunshine => src}/input.h (100%) rename {sunshine => src}/main.cpp (100%) rename {sunshine => src}/main.h (100%) rename {sunshine => src}/move_by_copy.h (100%) rename {sunshine => src}/network.cpp (100%) rename {sunshine => src}/network.h (100%) rename {sunshine => src}/nvhttp.cpp (100%) rename {sunshine => src}/nvhttp.h (100%) rename {sunshine => src}/platform/common.h (99%) rename {sunshine => src}/platform/linux/audio.cpp (99%) rename {sunshine => src}/platform/linux/cuda.cpp (99%) rename {sunshine => src}/platform/linux/cuda.cu (100%) rename {sunshine => src}/platform/linux/cuda.h (100%) rename {sunshine => src}/platform/linux/graphics.cpp (99%) rename {sunshine => src}/platform/linux/graphics.h (98%) rename {sunshine => src}/platform/linux/input.cpp (99%) rename {sunshine => src}/platform/linux/kmsgrab.cpp (99%) rename {sunshine => src}/platform/linux/misc.cpp (99%) rename {sunshine => src}/platform/linux/misc.h (94%) rename {sunshine => src}/platform/linux/publish.cpp (99%) rename {sunshine => src}/platform/linux/vaapi.cpp (99%) rename {sunshine => src}/platform/linux/vaapi.h (95%) rename {sunshine => src}/platform/linux/wayland.cpp (98%) rename {sunshine => src}/platform/linux/wayland.h (100%) rename {sunshine => src}/platform/linux/wlgrab.cpp (99%) rename {sunshine => src}/platform/linux/x11grab.cpp (99%) rename {sunshine => src}/platform/linux/x11grab.h (94%) rename {sunshine => src}/platform/macos/av_audio.h (100%) rename {sunshine => src}/platform/macos/av_audio.m (100%) rename {sunshine => src}/platform/macos/av_img_t.h (89%) rename {sunshine => src}/platform/macos/av_video.h (100%) rename {sunshine => src}/platform/macos/av_video.m (100%) rename {sunshine => src}/platform/macos/display.mm (96%) rename {sunshine => src}/platform/macos/input.cpp (99%) rename {sunshine => src}/platform/macos/microphone.mm (94%) rename {sunshine => src}/platform/macos/misc.cpp (98%) rename {sunshine => src}/platform/macos/misc.h (100%) rename {sunshine => src}/platform/macos/nv12_zero_device.cpp (95%) rename {sunshine => src}/platform/macos/nv12_zero_device.h (96%) rename {sunshine => src}/platform/macos/publish.cpp (99%) rename {sunshine => src}/platform/windows/PolicyConfig.h (96%) rename {sunshine => src}/platform/windows/audio.cpp (96%) rename {sunshine => src}/platform/windows/display.h (96%) rename {sunshine => src}/platform/windows/display_base.cpp (99%) rename {sunshine => src}/platform/windows/display_ram.cpp (96%) rename {sunshine => src}/platform/windows/display_vram.cpp (96%) rename {sunshine => src}/platform/windows/input.cpp (99%) mode change 100755 => 100644 rename {sunshine => src}/platform/windows/misc.cpp (95%) rename {sunshine => src}/platform/windows/misc.h (95%) rename {sunshine => src}/platform/windows/publish.cpp (92%) rename {sunshine => src}/platform/windows/windows.rs.in (100%) rename {sunshine => src}/process.cpp (100%) rename {sunshine => src}/process.h (100%) rename {sunshine => src}/round_robin.h (100%) rename {sunshine => src}/rtsp.cpp (100%) rename {sunshine => src}/rtsp.h (100%) rename {sunshine => src}/stream.cpp (100%) rename {sunshine => src}/stream.h (100%) rename {sunshine => src}/sync.h (100%) rename {sunshine => src}/task_pool.h (100%) rename {sunshine => src}/thread_pool.h (100%) rename {sunshine => src}/thread_safe.h (100%) rename {sunshine => src}/upnp.cpp (100%) rename {sunshine => src}/upnp.h (100%) rename {sunshine => src}/utility.h (100%) rename {sunshine => src}/uuid.h (100%) rename {sunshine => src}/video.cpp (100%) rename {sunshine => src}/video.h (100%) diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index df3e69ca..79674aa4 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -6,7 +6,7 @@ on: branches: [nightly] paths: # prevents workflow from running unless these files change - '.github/workflows/localize.yml' - - 'sunshine/**' + - 'src/**' - 'locale/sunshine.po' workflow_dispatch: diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a18d3eb..4b5d28e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,18 +100,18 @@ if(WIN32) if(NOT DEFINED SUNSHINE_ICON_PATH) set(SUNSHINE_ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/sunshine.ico") endif() - configure_file(sunshine/platform/windows/windows.rs.in windows.rc @ONLY) + configure_file(src/platform/windows/windows.rs.in windows.rc @ONLY) set(PLATFORM_TARGET_FILES "${CMAKE_CURRENT_BINARY_DIR}/windows.rc" - sunshine/platform/windows/publish.cpp - sunshine/platform/windows/misc.h - sunshine/platform/windows/misc.cpp - sunshine/platform/windows/input.cpp - sunshine/platform/windows/display.h - sunshine/platform/windows/display_base.cpp - sunshine/platform/windows/display_vram.cpp - sunshine/platform/windows/display_ram.cpp - sunshine/platform/windows/audio.cpp + src/platform/windows/publish.cpp + src/platform/windows/misc.h + src/platform/windows/misc.cpp + src/platform/windows/input.cpp + src/platform/windows/display.h + src/platform/windows/display_base.cpp + src/platform/windows/display_vram.cpp + src/platform/windows/display_ram.cpp + src/platform/windows/audio.cpp third-party/ViGEmClient/src/ViGEmClient.cpp third-party/ViGEmClient/include/ViGEm/Client.h third-party/ViGEmClient/include/ViGEm/Common.h @@ -180,19 +180,19 @@ elseif(APPLE) set(APPLE_PLIST_FILE ${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/Info.plist) set(PLATFORM_TARGET_FILES - sunshine/platform/macos/av_audio.h - sunshine/platform/macos/av_audio.m - sunshine/platform/macos/av_img_t.h - sunshine/platform/macos/av_video.h - sunshine/platform/macos/av_video.m - sunshine/platform/macos/display.mm - sunshine/platform/macos/input.cpp - sunshine/platform/macos/microphone.mm - sunshine/platform/macos/misc.cpp - sunshine/platform/macos/misc.h - sunshine/platform/macos/nv12_zero_device.cpp - sunshine/platform/macos/nv12_zero_device.h - sunshine/platform/macos/publish.cpp + src/platform/macos/av_audio.h + src/platform/macos/av_audio.m + src/platform/macos/av_img_t.h + src/platform/macos/av_video.h + src/platform/macos/av_video.m + src/platform/macos/display.mm + src/platform/macos/input.cpp + src/platform/macos/microphone.mm + src/platform/macos/misc.cpp + 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 ${APPLE_PLIST_FILE}) @@ -242,14 +242,14 @@ else() if(X11_FOUND) add_compile_definitions(SUNSHINE_BUILD_X11) include_directories(${X11_INCLUDE_DIR}) - list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/x11grab.cpp) + list(APPEND PLATFORM_TARGET_FILES src/platform/linux/x11grab.cpp) endif() if(CUDA_FOUND) include_directories(third-party/nvfbc) list(APPEND PLATFORM_TARGET_FILES - sunshine/platform/linux/cuda.cu - sunshine/platform/linux/cuda.cpp + src/platform/linux/cuda.cu + src/platform/linux/cuda.cpp third-party/nvfbc/NvFBC.h) add_compile_definitions(SUNSHINE_BUILD_CUDA) @@ -259,7 +259,7 @@ else() add_compile_definitions(SUNSHINE_BUILD_DRM) include_directories(${LIBDRM_INCLUDE_DIRS} ${LIBCAP_INCLUDE_DIRS}) list(APPEND PLATFORM_LIBRARIES ${LIBDRM_LIBRARIES} ${LIBCAP_LIBRARIES}) - list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/kmsgrab.cpp) + list(APPEND PLATFORM_TARGET_FILES src/platform/linux/kmsgrab.cpp) list(APPEND SUNSHINE_DEFINITIONS EGL_NO_X11=1) elseif(LIBDRM_FOUND) message(WARNING "Found libdrm, yet there is no libcap") @@ -301,26 +301,26 @@ else() list(APPEND PLATFORM_LIBRARIES ${WAYLAND_LIBRARIES}) list(APPEND PLATFORM_TARGET_FILES - sunshine/platform/linux/wlgrab.cpp - sunshine/platform/linux/wayland.cpp) + 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 ${}) message(FATAL_ERROR "Couldn't find either x11, wayland, cuda or (libdrm and libcap)") endif() list(APPEND PLATFORM_TARGET_FILES - sunshine/platform/linux/publish.cpp - sunshine/platform/linux/vaapi.h - sunshine/platform/linux/vaapi.cpp - sunshine/platform/linux/cuda.h - sunshine/platform/linux/graphics.h - sunshine/platform/linux/graphics.cpp - sunshine/platform/linux/misc.h - sunshine/platform/linux/misc.cpp - sunshine/platform/linux/audio.cpp - sunshine/platform/linux/input.cpp - sunshine/platform/linux/x11grab.h - sunshine/platform/linux/wayland.h + src/platform/linux/publish.cpp + src/platform/linux/vaapi.h + src/platform/linux/vaapi.cpp + src/platform/linux/cuda.h + src/platform/linux/graphics.h + src/platform/linux/graphics.cpp + src/platform/linux/misc.h + src/platform/linux/misc.cpp + src/platform/linux/audio.cpp + src/platform/linux/input.cpp + src/platform/linux/x11grab.h + src/platform/linux/wayland.h third-party/glad/src/egl.c third-party/glad/src/gl.c third-party/glad/include/EGL/eglplatform.h @@ -356,47 +356,47 @@ 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 - sunshine/upnp.cpp - sunshine/upnp.h - sunshine/cbs.cpp - sunshine/utility.h - sunshine/uuid.h - sunshine/config.h - sunshine/config.cpp - sunshine/main.cpp - sunshine/main.h - sunshine/crypto.cpp - sunshine/crypto.h - sunshine/nvhttp.cpp - sunshine/nvhttp.h - sunshine/httpcommon.cpp - sunshine/httpcommon.h - sunshine/confighttp.cpp - sunshine/confighttp.h - sunshine/rtsp.cpp - sunshine/rtsp.h - sunshine/stream.cpp - sunshine/stream.h - sunshine/video.cpp - sunshine/video.h - sunshine/input.cpp - sunshine/input.h - sunshine/audio.cpp - sunshine/audio.h - sunshine/platform/common.h - sunshine/process.cpp - sunshine/process.h - sunshine/network.cpp - sunshine/network.h - sunshine/move_by_copy.h - sunshine/task_pool.h - sunshine/thread_pool.h - sunshine/thread_safe.h - sunshine/sync.h - sunshine/round_robin.h + src/upnp.cpp + src/upnp.h + src/cbs.cpp + src/utility.h + src/uuid.h + src/config.h + src/config.cpp + src/main.cpp + src/main.h + src/crypto.cpp + src/crypto.h + src/nvhttp.cpp + src/nvhttp.h + src/httpcommon.cpp + src/httpcommon.h + src/confighttp.cpp + src/confighttp.h + src/rtsp.cpp + src/rtsp.h + src/stream.cpp + src/stream.h + src/video.cpp + src/video.h + src/input.cpp + src/input.h + src/audio.cpp + src/audio.h + src/platform/common.h + src/process.cpp + src/process.h + src/network.cpp + src/network.h + src/move_by_copy.h + src/task_pool.h + src/thread_pool.h + src/thread_safe.h + src/sync.h + src/round_robin.h ${PLATFORM_TARGET_FILES}) -set_source_files_properties(sunshine/upnp.cpp PROPERTIES COMPILE_FLAGS -Wno-pedantic) +set_source_files_properties(src/upnp.cpp PROPERTIES COMPILE_FLAGS -Wno-pedantic) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} @@ -414,7 +414,7 @@ string(TOUPPER "x${CMAKE_BUILD_TYPE}" BUILD_TYPE) if("${BUILD_TYPE}" STREQUAL "XDEBUG") list(APPEND SUNSHINE_COMPILE_OPTIONS -O0 -ggdb3) if(WIN32) - set_source_files_properties(sunshine/nvhttp.cpp PROPERTIES COMPILE_FLAGS -O2) + set_source_files_properties(src/nvhttp.cpp PROPERTIES COMPILE_FLAGS -O2) endif() else() add_definitions(-DNDEBUG) diff --git a/docs/source/contributing/localization.rst b/docs/source/contributing/localization.rst index 32ec4a5e..8b04867f 100644 --- a/docs/source/contributing/localization.rst +++ b/docs/source/contributing/localization.rst @@ -67,7 +67,7 @@ any of the following paths are modified. .. code-block:: yaml - - 'sunshine/**' + - 'src/**' When testing locally it may be desirable to manually extract, initialize, update, and compile strings. Python is required for this, along with the python dependencies in the `./scripts/requirements.txt` file. Additionally, diff --git a/sunshine/audio.cpp b/src/audio.cpp similarity index 100% rename from sunshine/audio.cpp rename to src/audio.cpp diff --git a/sunshine/audio.h b/src/audio.h similarity index 100% rename from sunshine/audio.h rename to src/audio.h diff --git a/sunshine/cbs.cpp b/src/cbs.cpp similarity index 100% rename from sunshine/cbs.cpp rename to src/cbs.cpp diff --git a/sunshine/cbs.h b/src/cbs.h similarity index 100% rename from sunshine/cbs.h rename to src/cbs.h diff --git a/sunshine/config.cpp b/src/config.cpp similarity index 100% rename from sunshine/config.cpp rename to src/config.cpp diff --git a/sunshine/config.h b/src/config.h similarity index 100% rename from sunshine/config.h rename to src/config.h diff --git a/sunshine/confighttp.cpp b/src/confighttp.cpp similarity index 100% rename from sunshine/confighttp.cpp rename to src/confighttp.cpp diff --git a/sunshine/confighttp.h b/src/confighttp.h similarity index 100% rename from sunshine/confighttp.h rename to src/confighttp.h diff --git a/sunshine/crypto.cpp b/src/crypto.cpp similarity index 100% rename from sunshine/crypto.cpp rename to src/crypto.cpp diff --git a/sunshine/crypto.h b/src/crypto.h similarity index 100% rename from sunshine/crypto.h rename to src/crypto.h diff --git a/sunshine/httpcommon.cpp b/src/httpcommon.cpp similarity index 100% rename from sunshine/httpcommon.cpp rename to src/httpcommon.cpp diff --git a/sunshine/httpcommon.h b/src/httpcommon.h similarity index 100% rename from sunshine/httpcommon.h rename to src/httpcommon.h diff --git a/sunshine/input.cpp b/src/input.cpp similarity index 100% rename from sunshine/input.cpp rename to src/input.cpp diff --git a/sunshine/input.h b/src/input.h similarity index 100% rename from sunshine/input.h rename to src/input.h diff --git a/sunshine/main.cpp b/src/main.cpp similarity index 100% rename from sunshine/main.cpp rename to src/main.cpp diff --git a/sunshine/main.h b/src/main.h similarity index 100% rename from sunshine/main.h rename to src/main.h diff --git a/sunshine/move_by_copy.h b/src/move_by_copy.h similarity index 100% rename from sunshine/move_by_copy.h rename to src/move_by_copy.h diff --git a/sunshine/network.cpp b/src/network.cpp similarity index 100% rename from sunshine/network.cpp rename to src/network.cpp diff --git a/sunshine/network.h b/src/network.h similarity index 100% rename from sunshine/network.h rename to src/network.h diff --git a/sunshine/nvhttp.cpp b/src/nvhttp.cpp similarity index 100% rename from sunshine/nvhttp.cpp rename to src/nvhttp.cpp diff --git a/sunshine/nvhttp.h b/src/nvhttp.h similarity index 100% rename from sunshine/nvhttp.h rename to src/nvhttp.h diff --git a/sunshine/platform/common.h b/src/platform/common.h similarity index 99% rename from sunshine/platform/common.h rename to src/platform/common.h index ef9a2cf6..60ae9d14 100644 --- a/sunshine/platform/common.h +++ b/src/platform/common.h @@ -11,8 +11,8 @@ #include #include -#include "sunshine/thread_safe.h" -#include "sunshine/utility.h" +#include "src/thread_safe.h" +#include "src/utility.h" struct sockaddr; struct AVFrame; diff --git a/sunshine/platform/linux/audio.cpp b/src/platform/linux/audio.cpp similarity index 99% rename from sunshine/platform/linux/audio.cpp rename to src/platform/linux/audio.cpp index 2770e515..d3ac03f9 100644 --- a/sunshine/platform/linux/audio.cpp +++ b/src/platform/linux/audio.cpp @@ -10,11 +10,11 @@ #include #include -#include "sunshine/platform/common.h" +#include "src/platform/common.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/thread_safe.h" +#include "src/config.h" +#include "src/main.h" +#include "src/thread_safe.h" namespace platf { using namespace std::literals; diff --git a/sunshine/platform/linux/cuda.cpp b/src/platform/linux/cuda.cpp similarity index 99% rename from sunshine/platform/linux/cuda.cpp rename to src/platform/linux/cuda.cpp index c907ae6c..faef891d 100644 --- a/sunshine/platform/linux/cuda.cpp +++ b/src/platform/linux/cuda.cpp @@ -11,8 +11,8 @@ extern "C" { #include "cuda.h" #include "graphics.h" -#include "sunshine/main.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/utility.h" #include "wayland.h" #define SUNSHINE_STRINGVIEW_HELPER(x) x##sv diff --git a/sunshine/platform/linux/cuda.cu b/src/platform/linux/cuda.cu similarity index 100% rename from sunshine/platform/linux/cuda.cu rename to src/platform/linux/cuda.cu diff --git a/sunshine/platform/linux/cuda.h b/src/platform/linux/cuda.h similarity index 100% rename from sunshine/platform/linux/cuda.h rename to src/platform/linux/cuda.h diff --git a/sunshine/platform/linux/graphics.cpp b/src/platform/linux/graphics.cpp similarity index 99% rename from sunshine/platform/linux/graphics.cpp rename to src/platform/linux/graphics.cpp index 8d31e37b..3c0adbda 100644 --- a/sunshine/platform/linux/graphics.cpp +++ b/src/platform/linux/graphics.cpp @@ -1,5 +1,5 @@ #include "graphics.h" -#include "sunshine/video.h" +#include "src/video.h" #include diff --git a/sunshine/platform/linux/graphics.h b/src/platform/linux/graphics.h similarity index 98% rename from sunshine/platform/linux/graphics.h rename to src/platform/linux/graphics.h index 2cc24b01..c566b48f 100644 --- a/sunshine/platform/linux/graphics.h +++ b/src/platform/linux/graphics.h @@ -8,9 +8,9 @@ #include #include "misc.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/utility.h" #define SUNSHINE_STRINGIFY_HELPER(x) #x #define SUNSHINE_STRINGIFY(x) SUNSHINE_STRINGIFY_HELPER(x) diff --git a/sunshine/platform/linux/input.cpp b/src/platform/linux/input.cpp similarity index 99% rename from sunshine/platform/linux/input.cpp rename to src/platform/linux/input.cpp index be923936..c8e5887e 100644 --- a/sunshine/platform/linux/input.cpp +++ b/src/platform/linux/input.cpp @@ -9,11 +9,11 @@ #include #include -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/utility.h" -#include "sunshine/platform/common.h" +#include "src/platform/common.h" // Support older versions #ifndef REL_HWHEEL_HI_RES diff --git a/sunshine/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp similarity index 99% rename from sunshine/platform/linux/kmsgrab.cpp rename to src/platform/linux/kmsgrab.cpp index 28ef656e..3b9257be 100644 --- a/sunshine/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -8,10 +8,10 @@ #include -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/round_robin.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/round_robin.h" +#include "src/utility.h" // Cursor rendering support through x11 #include "graphics.h" diff --git a/sunshine/platform/linux/misc.cpp b/src/platform/linux/misc.cpp similarity index 99% rename from sunshine/platform/linux/misc.cpp rename to src/platform/linux/misc.cpp index 6d7643c0..36896dd9 100644 --- a/sunshine/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -11,8 +11,8 @@ #include "misc.h" #include "vaapi.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" +#include "src/main.h" +#include "src/platform/common.h" #ifdef __GNUC__ #define SUNSHINE_GNUC_EXTENSION __extension__ diff --git a/sunshine/platform/linux/misc.h b/src/platform/linux/misc.h similarity index 94% rename from sunshine/platform/linux/misc.h rename to src/platform/linux/misc.h index 432aa9ed..515087d7 100644 --- a/sunshine/platform/linux/misc.h +++ b/src/platform/linux/misc.h @@ -4,7 +4,7 @@ #include #include -#include "sunshine/utility.h" +#include "src/utility.h" KITTY_USING_MOVE_T(file_t, int, -1, { if(el >= 0) { diff --git a/sunshine/platform/linux/publish.cpp b/src/platform/linux/publish.cpp similarity index 99% rename from sunshine/platform/linux/publish.cpp rename to src/platform/linux/publish.cpp index 825f3740..19cbb3f5 100644 --- a/sunshine/platform/linux/publish.cpp +++ b/src/platform/linux/publish.cpp @@ -3,10 +3,10 @@ #include #include "misc.h" -#include "sunshine/main.h" -#include "sunshine/nvhttp.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/nvhttp.h" +#include "src/platform/common.h" +#include "src/utility.h" using namespace std::literals; diff --git a/sunshine/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp similarity index 99% rename from sunshine/platform/linux/vaapi.cpp rename to src/platform/linux/vaapi.cpp index 37a64cb8..9dcb2dfe 100644 --- a/sunshine/platform/linux/vaapi.cpp +++ b/src/platform/linux/vaapi.cpp @@ -9,10 +9,10 @@ extern "C" { #include "graphics.h" #include "misc.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/config.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/utility.h" using namespace std::literals; diff --git a/sunshine/platform/linux/vaapi.h b/src/platform/linux/vaapi.h similarity index 95% rename from sunshine/platform/linux/vaapi.h rename to src/platform/linux/vaapi.h index ddb5c61e..27c97a7f 100644 --- a/sunshine/platform/linux/vaapi.h +++ b/src/platform/linux/vaapi.h @@ -2,7 +2,7 @@ #define SUNSHINE_VAAPI_H #include "misc.h" -#include "sunshine/platform/common.h" +#include "src/platform/common.h" namespace egl { struct surface_descriptor_t; diff --git a/sunshine/platform/linux/wayland.cpp b/src/platform/linux/wayland.cpp similarity index 98% rename from sunshine/platform/linux/wayland.cpp rename to src/platform/linux/wayland.cpp index 9b65ef8d..dace461c 100644 --- a/sunshine/platform/linux/wayland.cpp +++ b/src/platform/linux/wayland.cpp @@ -4,10 +4,10 @@ #include #include "graphics.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/round_robin.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/round_robin.h" +#include "src/utility.h" #include "wayland.h" extern const wl_interface wl_output_interface; diff --git a/sunshine/platform/linux/wayland.h b/src/platform/linux/wayland.h similarity index 100% rename from sunshine/platform/linux/wayland.h rename to src/platform/linux/wayland.h diff --git a/sunshine/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp similarity index 99% rename from sunshine/platform/linux/wlgrab.cpp rename to src/platform/linux/wlgrab.cpp index c4ab3dbf..31d5cfa0 100644 --- a/sunshine/platform/linux/wlgrab.cpp +++ b/src/platform/linux/wlgrab.cpp @@ -1,6 +1,6 @@ -#include "sunshine/platform/common.h" +#include "src/platform/common.h" -#include "sunshine/main.h" +#include "src/main.h" #include "vaapi.h" #include "wayland.h" diff --git a/sunshine/platform/linux/x11grab.cpp b/src/platform/linux/x11grab.cpp similarity index 99% rename from sunshine/platform/linux/x11grab.cpp rename to src/platform/linux/x11grab.cpp index c9b4beaa..c024f4d8 100644 --- a/sunshine/platform/linux/x11grab.cpp +++ b/src/platform/linux/x11grab.cpp @@ -2,7 +2,7 @@ // Created by loki on 6/21/19. // -#include "sunshine/platform/common.h" +#include "src/platform/common.h" #include @@ -16,9 +16,9 @@ #include #include -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/task_pool.h" +#include "src/config.h" +#include "src/main.h" +#include "src/task_pool.h" #include "cuda.h" #include "graphics.h" diff --git a/sunshine/platform/linux/x11grab.h b/src/platform/linux/x11grab.h similarity index 94% rename from sunshine/platform/linux/x11grab.h rename to src/platform/linux/x11grab.h index 9fde2664..801daad5 100644 --- a/sunshine/platform/linux/x11grab.h +++ b/src/platform/linux/x11grab.h @@ -3,8 +3,8 @@ #include -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/platform/common.h" +#include "src/utility.h" // X11 Display extern "C" struct _XDisplay; diff --git a/sunshine/platform/macos/av_audio.h b/src/platform/macos/av_audio.h similarity index 100% rename from sunshine/platform/macos/av_audio.h rename to src/platform/macos/av_audio.h diff --git a/sunshine/platform/macos/av_audio.m b/src/platform/macos/av_audio.m similarity index 100% rename from sunshine/platform/macos/av_audio.m rename to src/platform/macos/av_audio.m diff --git a/sunshine/platform/macos/av_img_t.h b/src/platform/macos/av_img_t.h similarity index 89% rename from sunshine/platform/macos/av_img_t.h rename to src/platform/macos/av_img_t.h index 7af3cbcc..a7a24935 100644 --- a/sunshine/platform/macos/av_img_t.h +++ b/src/platform/macos/av_img_t.h @@ -1,7 +1,7 @@ #ifndef av_img_t_h #define av_img_t_h -#include "sunshine/platform/common.h" +#include "src/platform/common.h" #include #include diff --git a/sunshine/platform/macos/av_video.h b/src/platform/macos/av_video.h similarity index 100% rename from sunshine/platform/macos/av_video.h rename to src/platform/macos/av_video.h diff --git a/sunshine/platform/macos/av_video.m b/src/platform/macos/av_video.m similarity index 100% rename from sunshine/platform/macos/av_video.m rename to src/platform/macos/av_video.m diff --git a/sunshine/platform/macos/display.mm b/src/platform/macos/display.mm similarity index 96% rename from sunshine/platform/macos/display.mm rename to src/platform/macos/display.mm index f00297c2..1601e3ec 100644 --- a/sunshine/platform/macos/display.mm +++ b/src/platform/macos/display.mm @@ -1,10 +1,10 @@ -#include "sunshine/platform/common.h" -#include "sunshine/platform/macos/av_img_t.h" -#include "sunshine/platform/macos/av_video.h" -#include "sunshine/platform/macos/nv12_zero_device.h" +#include "src/platform/common.h" +#include "src/platform/macos/av_img_t.h" +#include "src/platform/macos/av_video.h" +#include "src/platform/macos/nv12_zero_device.h" -#include "sunshine/config.h" -#include "sunshine/main.h" +#include "src/config.h" +#include "src/main.h" namespace fs = std::filesystem; diff --git a/sunshine/platform/macos/input.cpp b/src/platform/macos/input.cpp similarity index 99% rename from sunshine/platform/macos/input.cpp rename to src/platform/macos/input.cpp index fbea6619..3d9c21a3 100644 --- a/sunshine/platform/macos/input.cpp +++ b/src/platform/macos/input.cpp @@ -2,9 +2,9 @@ #include #include -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/utility.h" // Delay for a double click // FIXME: we probably want to make this configurable diff --git a/sunshine/platform/macos/microphone.mm b/src/platform/macos/microphone.mm similarity index 94% rename from sunshine/platform/macos/microphone.mm rename to src/platform/macos/microphone.mm index 4b651690..cfe9562d 100644 --- a/sunshine/platform/macos/microphone.mm +++ b/src/platform/macos/microphone.mm @@ -1,8 +1,8 @@ -#include "sunshine/platform/common.h" -#include "sunshine/platform/macos/av_audio.h" +#include "src/platform/common.h" +#include "src/platform/macos/av_audio.h" -#include "sunshine/config.h" -#include "sunshine/main.h" +#include "src/config.h" +#include "src/main.h" namespace platf { using namespace std::literals; diff --git a/sunshine/platform/macos/misc.cpp b/src/platform/macos/misc.cpp similarity index 98% rename from sunshine/platform/macos/misc.cpp rename to src/platform/macos/misc.cpp index fdc46688..a7b0a89f 100644 --- a/sunshine/platform/macos/misc.cpp +++ b/src/platform/macos/misc.cpp @@ -6,8 +6,8 @@ #include #include "misc.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" +#include "src/main.h" +#include "src/platform/common.h" using namespace std::literals; namespace fs = std::filesystem; diff --git a/sunshine/platform/macos/misc.h b/src/platform/macos/misc.h similarity index 100% rename from sunshine/platform/macos/misc.h rename to src/platform/macos/misc.h diff --git a/sunshine/platform/macos/nv12_zero_device.cpp b/src/platform/macos/nv12_zero_device.cpp similarity index 95% rename from sunshine/platform/macos/nv12_zero_device.cpp rename to src/platform/macos/nv12_zero_device.cpp index 7e0a4a77..1af0e058 100644 --- a/sunshine/platform/macos/nv12_zero_device.cpp +++ b/src/platform/macos/nv12_zero_device.cpp @@ -1,7 +1,7 @@ -#include "sunshine/platform/macos/nv12_zero_device.h" -#include "sunshine/platform/macos/av_img_t.h" +#include "src/platform/macos/nv12_zero_device.h" +#include "src/platform/macos/av_img_t.h" -#include "sunshine/video.h" +#include "src/video.h" extern "C" { #include "libavutil/imgutils.h" diff --git a/sunshine/platform/macos/nv12_zero_device.h b/src/platform/macos/nv12_zero_device.h similarity index 96% rename from sunshine/platform/macos/nv12_zero_device.h rename to src/platform/macos/nv12_zero_device.h index 847a8f0a..3b74ebcc 100644 --- a/sunshine/platform/macos/nv12_zero_device.h +++ b/src/platform/macos/nv12_zero_device.h @@ -1,7 +1,7 @@ #ifndef vtdevice_h #define vtdevice_h -#include "sunshine/platform/common.h" +#include "src/platform/common.h" namespace platf { diff --git a/sunshine/platform/macos/publish.cpp b/src/platform/macos/publish.cpp similarity index 99% rename from sunshine/platform/macos/publish.cpp rename to src/platform/macos/publish.cpp index cc10cd82..bd943ec9 100644 --- a/sunshine/platform/macos/publish.cpp +++ b/src/platform/macos/publish.cpp @@ -3,10 +3,10 @@ #include #include "misc.h" -#include "sunshine/main.h" -#include "sunshine/nvhttp.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/nvhttp.h" +#include "src/platform/common.h" +#include "src/utility.h" using namespace std::literals; diff --git a/sunshine/platform/windows/PolicyConfig.h b/src/platform/windows/PolicyConfig.h similarity index 96% rename from sunshine/platform/windows/PolicyConfig.h rename to src/platform/windows/PolicyConfig.h index 60f36a68..be18ec6f 100644 --- a/sunshine/platform/windows/PolicyConfig.h +++ b/src/platform/windows/PolicyConfig.h @@ -1,164 +1,164 @@ -// ---------------------------------------------------------------------------- -// PolicyConfig.h -// Undocumented COM-interface IPolicyConfig. -// Use for set default audio render endpoint -// @author EreTIk -// http://eretik.omegahg.com/ -// ---------------------------------------------------------------------------- - - -#pragma once - -#ifdef __MINGW32__ -#undef DEFINE_GUID -#ifdef __cplusplus -#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } -#else -#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } -#endif - -DEFINE_GUID(IID_IPolicyConfig, 0xf8679f50, 0x850a, 0x41cf, 0x9c, 0x72, 0x43, 0x0f, 0x29, 0x02, 0x90, 0xc8); -DEFINE_GUID(CLSID_CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf, 0x0d, 0xe6, 0x3d, 0xf4, 0x0c, 0x2b, 0xc9); - -#endif - -interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig; -class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient; -// ---------------------------------------------------------------------------- -// class CPolicyConfigClient -// {870af99c-171d-4f9e-af0d-e63df40c2bc9} -// -// interface IPolicyConfig -// {f8679f50-850a-41cf-9c72-430f290290c8} -// -// Query interface: -// CComPtr PolicyConfig; -// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigClient)); -// -// @compatible: Windows 7 and Later -// ---------------------------------------------------------------------------- -interface IPolicyConfig : public IUnknown { -public: - virtual HRESULT GetMixFormat( - PCWSTR, - WAVEFORMATEX **); - - virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( - PCWSTR, - INT, - WAVEFORMATEX **); - - virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat( - PCWSTR); - - virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( - PCWSTR, - WAVEFORMATEX *, - WAVEFORMATEX *); - - virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod( - PCWSTR, - INT, - PINT64, - PINT64); - - virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod( - PCWSTR, - PINT64); - - virtual HRESULT STDMETHODCALLTYPE GetShareMode( - PCWSTR, - struct DeviceShareMode *); - - virtual HRESULT STDMETHODCALLTYPE SetShareMode( - PCWSTR, - struct DeviceShareMode *); - - virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( - PCWSTR, - const PROPERTYKEY &, - PROPVARIANT *); - - virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( - PCWSTR, - const PROPERTYKEY &, - PROPVARIANT *); - - virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( - PCWSTR wszDeviceId, - ERole eRole); - - virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( - PCWSTR, - INT); -}; - -interface DECLSPEC_UUID("568b9108-44bf-40b4-9006-86afe5b5a620") IPolicyConfigVista; -class DECLSPEC_UUID("294935CE-F637-4E7C-A41B-AB255460B862") CPolicyConfigVistaClient; -// ---------------------------------------------------------------------------- -// class CPolicyConfigVistaClient -// {294935CE-F637-4E7C-A41B-AB255460B862} -// -// interface IPolicyConfigVista -// {568b9108-44bf-40b4-9006-86afe5b5a620} -// -// Query interface: -// CComPtr PolicyConfig; -// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigVistaClient)); -// -// @compatible: Windows Vista and Later -// ---------------------------------------------------------------------------- -interface IPolicyConfigVista : public IUnknown { -public: - virtual HRESULT GetMixFormat( - PCWSTR, - WAVEFORMATEX **); // not available on Windows 7, use method from IPolicyConfig - - virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( - PCWSTR, - INT, - WAVEFORMATEX **); - - virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( - PCWSTR, - WAVEFORMATEX *, - WAVEFORMATEX *); - - virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod( - PCWSTR, - INT, - PINT64, - PINT64); // not available on Windows 7, use method from IPolicyConfig - - virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod( - PCWSTR, - PINT64); // not available on Windows 7, use method from IPolicyConfig - - virtual HRESULT STDMETHODCALLTYPE GetShareMode( - PCWSTR, - struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig - - virtual HRESULT STDMETHODCALLTYPE SetShareMode( - PCWSTR, - struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig - - virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( - PCWSTR, - const PROPERTYKEY &, - PROPVARIANT *); - - virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( - PCWSTR, - const PROPERTYKEY &, - PROPVARIANT *); - - virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( - PCWSTR wszDeviceId, - ERole eRole); - - virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( - PCWSTR, - INT); // not available on Windows 7, use method from IPolicyConfig -}; - -// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- +// PolicyConfig.h +// Undocumented COM-interface IPolicyConfig. +// Use for set default audio render endpoint +// @author EreTIk +// http://eretik.omegahg.com/ +// ---------------------------------------------------------------------------- + + +#pragma once + +#ifdef __MINGW32__ +#undef DEFINE_GUID +#ifdef __cplusplus +#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } +#else +#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } +#endif + +DEFINE_GUID(IID_IPolicyConfig, 0xf8679f50, 0x850a, 0x41cf, 0x9c, 0x72, 0x43, 0x0f, 0x29, 0x02, 0x90, 0xc8); +DEFINE_GUID(CLSID_CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf, 0x0d, 0xe6, 0x3d, 0xf4, 0x0c, 0x2b, 0xc9); + +#endif + +interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig; +class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient; +// ---------------------------------------------------------------------------- +// class CPolicyConfigClient +// {870af99c-171d-4f9e-af0d-e63df40c2bc9} +// +// interface IPolicyConfig +// {f8679f50-850a-41cf-9c72-430f290290c8} +// +// Query interface: +// CComPtr PolicyConfig; +// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigClient)); +// +// @compatible: Windows 7 and Later +// ---------------------------------------------------------------------------- +interface IPolicyConfig : public IUnknown { +public: + virtual HRESULT GetMixFormat( + PCWSTR, + WAVEFORMATEX **); + + virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( + PCWSTR, + INT, + WAVEFORMATEX **); + + virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat( + PCWSTR); + + virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( + PCWSTR, + WAVEFORMATEX *, + WAVEFORMATEX *); + + virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod( + PCWSTR, + INT, + PINT64, + PINT64); + + virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod( + PCWSTR, + PINT64); + + virtual HRESULT STDMETHODCALLTYPE GetShareMode( + PCWSTR, + struct DeviceShareMode *); + + virtual HRESULT STDMETHODCALLTYPE SetShareMode( + PCWSTR, + struct DeviceShareMode *); + + virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( + PCWSTR, + const PROPERTYKEY &, + PROPVARIANT *); + + virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( + PCWSTR, + const PROPERTYKEY &, + PROPVARIANT *); + + virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( + PCWSTR wszDeviceId, + ERole eRole); + + virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( + PCWSTR, + INT); +}; + +interface DECLSPEC_UUID("568b9108-44bf-40b4-9006-86afe5b5a620") IPolicyConfigVista; +class DECLSPEC_UUID("294935CE-F637-4E7C-A41B-AB255460B862") CPolicyConfigVistaClient; +// ---------------------------------------------------------------------------- +// class CPolicyConfigVistaClient +// {294935CE-F637-4E7C-A41B-AB255460B862} +// +// interface IPolicyConfigVista +// {568b9108-44bf-40b4-9006-86afe5b5a620} +// +// Query interface: +// CComPtr PolicyConfig; +// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigVistaClient)); +// +// @compatible: Windows Vista and Later +// ---------------------------------------------------------------------------- +interface IPolicyConfigVista : public IUnknown { +public: + virtual HRESULT GetMixFormat( + PCWSTR, + WAVEFORMATEX **); // not available on Windows 7, use method from IPolicyConfig + + virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( + PCWSTR, + INT, + WAVEFORMATEX **); + + virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( + PCWSTR, + WAVEFORMATEX *, + WAVEFORMATEX *); + + virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod( + PCWSTR, + INT, + PINT64, + PINT64); // not available on Windows 7, use method from IPolicyConfig + + virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod( + PCWSTR, + PINT64); // not available on Windows 7, use method from IPolicyConfig + + virtual HRESULT STDMETHODCALLTYPE GetShareMode( + PCWSTR, + struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig + + virtual HRESULT STDMETHODCALLTYPE SetShareMode( + PCWSTR, + struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig + + virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( + PCWSTR, + const PROPERTYKEY &, + PROPVARIANT *); + + virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( + PCWSTR, + const PROPERTYKEY &, + PROPVARIANT *); + + virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( + PCWSTR wszDeviceId, + ERole eRole); + + virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( + PCWSTR, + INT); // not available on Windows 7, use method from IPolicyConfig +}; + +// ---------------------------------------------------------------------------- diff --git a/sunshine/platform/windows/audio.cpp b/src/platform/windows/audio.cpp similarity index 96% rename from sunshine/platform/windows/audio.cpp rename to src/platform/windows/audio.cpp index 61df413a..b630f73e 100644 --- a/sunshine/platform/windows/audio.cpp +++ b/src/platform/windows/audio.cpp @@ -1,988 +1,988 @@ -// -// Created by loki on 1/12/20. -// - -#include -#include -#include - -#include - -#include - -#define INITGUID -#include -#undef INITGUID - -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" - -// Must be the last included file -// clang-format off -#include "PolicyConfig.h" -// clang-format on - -DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING -DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING -DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2); - -const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); -const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); -const IID IID_IAudioClient = __uuidof(IAudioClient); -const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); - -using namespace std::literals; -namespace platf::audio { -constexpr auto SAMPLE_RATE = 48000; - -template -void Release(T *p) { - p->Release(); -} - -template -void co_task_free(T *p) { - CoTaskMemFree((LPVOID)p); -} - -using device_enum_t = util::safe_ptr>; -using device_t = util::safe_ptr>; -using collection_t = util::safe_ptr>; -using audio_client_t = util::safe_ptr>; -using audio_capture_t = util::safe_ptr>; -using wave_format_t = util::safe_ptr>; -using wstring_t = util::safe_ptr>; -using handle_t = util::safe_ptr_v2; -using policy_t = util::safe_ptr>; -using prop_t = util::safe_ptr>; - -class co_init_t : public deinit_t { -public: - co_init_t() { - CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY); - } - - ~co_init_t() override { - CoUninitialize(); - } -}; - -class prop_var_t { -public: - prop_var_t() { - PropVariantInit(&prop); - } - - ~prop_var_t() { - PropVariantClear(&prop); - } - - PROPVARIANT prop; -}; - -class audio_pipe_t { -public: - static constexpr auto stereo = 2; - static constexpr auto channels51 = 6; - static constexpr auto channels71 = 8; - - using samples_t = std::vector; - using buf_t = util::buffer_t; - - virtual void to_stereo(samples_t &out, const buf_t &in) = 0; - virtual void to_51(samples_t &out, const buf_t &in) = 0; - virtual void to_71(samples_t &out, const buf_t &in) = 0; -}; - -class mono_t : public audio_pipe_t { -public: - void to_stereo(samples_t &out, const buf_t &in) override { - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end;) { - *sample_out_p++ = *sample_in_pos * 7 / 10; - *sample_out_p++ = *sample_in_pos++ * 7 / 10; - } - } - - void to_51(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { - int left = *sample_in_pos++; - - auto fl = (left * 7 / 10); - - sample_out_p[FRONT_LEFT] = fl; - sample_out_p[FRONT_RIGHT] = fl; - sample_out_p[FRONT_CENTER] = fl * 6; - sample_out_p[LOW_FREQUENCY] = fl / 10; - sample_out_p[BACK_LEFT] = left * 4 / 10; - sample_out_p[BACK_RIGHT] = left * 4 / 10; - } - } - - void to_71(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { - int left = *sample_in_pos++; - - auto fl = (left * 7 / 10); - - sample_out_p[FRONT_LEFT] = fl; - sample_out_p[FRONT_RIGHT] = fl; - sample_out_p[FRONT_CENTER] = fl * 6; - sample_out_p[LOW_FREQUENCY] = fl / 10; - sample_out_p[BACK_LEFT] = left * 4 / 10; - sample_out_p[BACK_RIGHT] = left * 4 / 10; - sample_out_p[SIDE_LEFT] = left * 5 / 10; - sample_out_p[SIDE_RIGHT] = left * 5 / 10; - } - } -}; - -class stereo_t : public audio_pipe_t { -public: - void to_stereo(samples_t &out, const buf_t &in) override { - std::copy_n(std::begin(in), out.size(), std::begin(out)); - } - - void to_51(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { - int left = sample_in_pos[speaker::FRONT_LEFT]; - int right = sample_in_pos[speaker::FRONT_RIGHT]; - - sample_in_pos += 2; - - auto fl = (left * 7 / 10); - auto fr = (right * 7 / 10); - - auto mix = (fl + fr) / 2; - - sample_out_p[FRONT_LEFT] = fl; - sample_out_p[FRONT_RIGHT] = fr; - sample_out_p[FRONT_CENTER] = mix; - sample_out_p[LOW_FREQUENCY] = mix / 2; - sample_out_p[BACK_LEFT] = left * 4 / 10; - sample_out_p[BACK_RIGHT] = right * 4 / 10; - } - } - - void to_71(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { - int left = sample_in_pos[speaker::FRONT_LEFT]; - int right = sample_in_pos[speaker::FRONT_RIGHT]; - - sample_in_pos += 2; - - auto fl = (left * 7 / 10); - auto fr = (right * 7 / 10); - - auto mix = (fl + fr) / 2; - - sample_out_p[FRONT_LEFT] = fl; - sample_out_p[FRONT_RIGHT] = fr; - sample_out_p[FRONT_CENTER] = mix; - sample_out_p[LOW_FREQUENCY] = mix / 2; - sample_out_p[BACK_LEFT] = left * 4 / 10; - sample_out_p[BACK_RIGHT] = right * 4 / 10; - sample_out_p[SIDE_LEFT] = left * 5 / 10; - sample_out_p[SIDE_RIGHT] = right * 5 / 10; - } - } -}; - -class surr51_t : public audio_pipe_t { -public: - void to_stereo(samples_t &out, const buf_t &in) { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += stereo) { - int left {}, right {}; - - left += sample_in_pos[FRONT_LEFT]; - left += sample_in_pos[FRONT_CENTER] * 9 / 10; - left += sample_in_pos[LOW_FREQUENCY] * 3 / 10; - left += sample_in_pos[BACK_LEFT] * 7 / 10; - left += sample_in_pos[BACK_RIGHT] * 3 / 10; - - right += sample_in_pos[FRONT_RIGHT]; - right += sample_in_pos[FRONT_CENTER] * 9 / 10; - right += sample_in_pos[LOW_FREQUENCY] * 3 / 10; - right += sample_in_pos[BACK_LEFT] * 3 / 10; - right += sample_in_pos[BACK_RIGHT] * 7 / 10; - - sample_out_p[0] = left; - sample_out_p[1] = right; - - sample_in_pos += channels51; - } - } - - void to_51(samples_t &out, const buf_t &in) override { - std::copy_n(std::begin(in), out.size(), std::begin(out)); - } - - void to_71(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { - int fl = sample_in_pos[FRONT_LEFT]; - int fr = sample_in_pos[FRONT_RIGHT]; - int bl = sample_in_pos[BACK_LEFT]; - int br = sample_in_pos[BACK_RIGHT]; - - auto mix_l = (fl + bl) / 2; - auto mix_r = (bl + br) / 2; - - sample_out_p[FRONT_LEFT] = fl; - sample_out_p[FRONT_RIGHT] = fr; - sample_out_p[FRONT_CENTER] = sample_in_pos[FRONT_CENTER]; - sample_out_p[LOW_FREQUENCY] = sample_in_pos[LOW_FREQUENCY]; - sample_out_p[BACK_LEFT] = bl; - sample_out_p[BACK_RIGHT] = br; - sample_out_p[SIDE_LEFT] = mix_l; - sample_out_p[SIDE_RIGHT] = mix_r; - - sample_in_pos += channels51; - } - } -}; - -class surr71_t : public audio_pipe_t { -public: - void to_stereo(samples_t &out, const buf_t &in) { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += stereo) { - int left {}, right {}; - - left += sample_in_pos[FRONT_LEFT]; - left += sample_in_pos[FRONT_CENTER] * 9 / 10; - left += sample_in_pos[LOW_FREQUENCY] * 3 / 10; - left += sample_in_pos[BACK_LEFT] * 7 / 10; - left += sample_in_pos[BACK_RIGHT] * 3 / 10; - left += sample_in_pos[SIDE_LEFT]; - - right += sample_in_pos[FRONT_RIGHT]; - right += sample_in_pos[FRONT_CENTER] * 9 / 10; - right += sample_in_pos[LOW_FREQUENCY] * 3 / 10; - right += sample_in_pos[BACK_LEFT] * 3 / 10; - right += sample_in_pos[BACK_RIGHT] * 7 / 10; - right += sample_in_pos[SIDE_RIGHT]; - - sample_out_p[0] = left; - sample_out_p[1] = right; - - sample_in_pos += channels71; - } - } - - void to_51(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { - auto sl = (int)sample_out_p[SIDE_LEFT] * 3 / 10; - auto sr = (int)sample_out_p[SIDE_RIGHT] * 3 / 10; - - sample_out_p[FRONT_LEFT] = sample_in_pos[FRONT_LEFT] + sl; - sample_out_p[FRONT_RIGHT] = sample_in_pos[FRONT_RIGHT] + sr; - sample_out_p[FRONT_CENTER] = sample_in_pos[FRONT_CENTER]; - sample_out_p[LOW_FREQUENCY] = sample_in_pos[LOW_FREQUENCY]; - sample_out_p[BACK_LEFT] = sample_in_pos[BACK_LEFT] + sl; - sample_out_p[BACK_RIGHT] = sample_in_pos[BACK_RIGHT] + sr; - - sample_in_pos += channels71; - } - } - - void to_71(samples_t &out, const buf_t &in) override { - std::copy_n(std::begin(in), out.size(), std::begin(out)); - } -}; - -static std::wstring_convert, wchar_t> converter; -struct format_t { - enum type_e : int { - none, - mono, - stereo, - surr51, - surr71, - } type; - - std::string_view name; - int channels; - int channel_mask; -} formats[] { - { - format_t::mono, - "Mono"sv, - 1, - SPEAKER_FRONT_CENTER, - }, - { - format_t::stereo, - "Stereo"sv, - 2, - SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, - }, - { - format_t::surr51, - "Surround 5.1"sv, - 6, - SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT, - }, - { - format_t::surr71, - "Surround 7.1"sv, - 8, - SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT, - }, -}; - -static format_t surround_51_side_speakers { - format_t::surr51, - "Surround 5.1"sv, - 6, - SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT, -}; - -void set_wave_format(audio::wave_format_t &wave_format, const format_t &format) { - wave_format->nChannels = format.channels; - wave_format->nBlockAlign = wave_format->nChannels * wave_format->wBitsPerSample / 8; - wave_format->nAvgBytesPerSec = wave_format->nSamplesPerSec * wave_format->nBlockAlign; - - if(wave_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { - ((PWAVEFORMATEXTENSIBLE)wave_format.get())->dwChannelMask = format.channel_mask; - } -} - -int init_wave_format(audio::wave_format_t &wave_format, DWORD sample_rate) { - wave_format->wBitsPerSample = 16; - wave_format->nSamplesPerSec = sample_rate; - switch(wave_format->wFormatTag) { - case WAVE_FORMAT_PCM: - break; - case WAVE_FORMAT_IEEE_FLOAT: - break; - case WAVE_FORMAT_EXTENSIBLE: { - auto wave_ex = (PWAVEFORMATEXTENSIBLE)wave_format.get(); - if(IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) { - wave_ex->Samples.wValidBitsPerSample = 16; - wave_ex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - } - - BOOST_LOG(error) << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']'; - } - default: - BOOST_LOG(error) << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']'; - return -1; - }; - - return 0; -} - -audio_client_t make_audio_client(device_t &device, const format_t &format, int sample_rate) { - audio_client_t audio_client; - auto status = device->Activate( - IID_IAudioClient, - CLSCTX_ALL, - nullptr, - (void **)&audio_client); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']'; - - return nullptr; - } - - wave_format_t wave_format; - status = audio_client->GetMixFormat(&wave_format); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; - - return nullptr; - } - - if(init_wave_format(wave_format, sample_rate)) { - return nullptr; - } - set_wave_format(wave_format, format); - - status = audio_client->Initialize( - AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - 0, 0, - wave_format.get(), - nullptr); - - if(status) { - BOOST_LOG(debug) << "Couldn't initialize audio client for ["sv << format.name << "]: [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; - } - - return audio_client; -} - -const wchar_t *no_null(const wchar_t *str) { - return str ? str : L"Unknown"; -} - -format_t::type_e validate_device(device_t &device, int sample_rate) { - for(const auto &format : formats) { - // Ensure WaveFromat is compatible - auto audio_client = make_audio_client(device, format, sample_rate); - - BOOST_LOG(debug) << format.name << ": "sv << (!audio_client ? "unsupported"sv : "supported"sv); - - if(audio_client) { - return format.type; - } - } - - return format_t::none; -} - -device_t default_device(device_enum_t &device_enum) { - device_t device; - HRESULT status; - status = device_enum->GetDefaultAudioEndpoint( - eRender, - eConsole, - &device); - - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't create audio Device [0x"sv << util::hex(status).to_string_view() << ']'; - - return nullptr; - } - - return device; -} - -class mic_wasapi_t : public mic_t { -public: - capture_e sample(std::vector &sample_out) override { - auto sample_size = sample_out.size() / channels_out * channels_in; - while(sample_buf_pos - std::begin(sample_buf) < sample_size) { - //FIXME: Use IAudioClient3 instead of IAudioClient, that would allows for adjusting the latency of the audio samples - auto capture_result = _fill_buffer(); - - if(capture_result != capture_e::ok) { - return capture_result; - } - } - - switch(channels_out) { - case 2: - pipe->to_stereo(sample_out, sample_buf); - break; - case 6: - pipe->to_51(sample_out, sample_buf); - break; - case 8: - pipe->to_71(sample_out, sample_buf); - break; - default: - BOOST_LOG(error) << "converting to ["sv << channels_out << "] channels is not supported"sv; - return capture_e::error; - } - - // The excess samples should be in front of the queue - std::move(&sample_buf[sample_size], sample_buf_pos, std::begin(sample_buf)); - sample_buf_pos -= sample_size; - - return capture_e::ok; - } - - - int init(std::uint32_t sample_rate, std::uint32_t frame_size, std::uint32_t channels_out) { - audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr)); - if(!audio_event) { - BOOST_LOG(error) << "Couldn't create Event handle"sv; - - return -1; - } - - HRESULT status; - - status = CoCreateInstance( - CLSID_MMDeviceEnumerator, - nullptr, - CLSCTX_ALL, - IID_IMMDeviceEnumerator, - (void **)&device_enum); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't create Device Enumerator [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - auto device = default_device(device_enum); - if(!device) { - return -1; - } - - for(auto &format : formats) { - BOOST_LOG(debug) << "Trying audio format ["sv << format.name << ']'; - audio_client = make_audio_client(device, format, sample_rate); - - if(audio_client) { - BOOST_LOG(debug) << "Found audio format ["sv << format.name << ']'; - channels_in = format.channels; - this->channels_out = channels_out; - - switch(channels_in) { - case 1: - pipe = std::make_unique(); - break; - case 2: - pipe = std::make_unique(); - break; - case 6: - pipe = std::make_unique(); - break; - case 8: - pipe = std::make_unique(); - break; - default: - BOOST_LOG(error) << "converting from ["sv << channels_in << "] channels is not supported"sv; - return -1; - } - break; - } - } - - if(!audio_client) { - BOOST_LOG(error) << "Couldn't find supported format for audio"sv; - return -1; - } - - REFERENCE_TIME default_latency; - audio_client->GetDevicePeriod(&default_latency, nullptr); - default_latency_ms = default_latency / 1000; - - std::uint32_t frames; - status = audio_client->GetBufferSize(&frames); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't acquire the number of audio frames [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - // *2 --> needs to fit double - sample_buf = util::buffer_t { std::max(frames, frame_size) * 2 * channels_in }; - sample_buf_pos = std::begin(sample_buf); - - status = audio_client->GetService(IID_IAudioCaptureClient, (void **)&audio_capture); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't initialize audio capture client [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - status = audio_client->SetEventHandle(audio_event.get()); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't set event handle [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - status = audio_client->Start(); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't start recording [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - return 0; - } - - ~mic_wasapi_t() override { - if(audio_client) { - audio_client->Stop(); - } - } - -private: - capture_e _fill_buffer() { - HRESULT status; - - // Total number of samples - struct sample_aligned_t { - std::uint32_t uninitialized; - std::int16_t *samples; - } sample_aligned; - - // number of samples / number of channels - struct block_aligned_t { - std::uint32_t audio_sample_size; - } block_aligned; - - status = WaitForSingleObjectEx(audio_event.get(), default_latency_ms, FALSE); - switch(status) { - case WAIT_OBJECT_0: - break; - case WAIT_TIMEOUT: - return capture_e::timeout; - default: - BOOST_LOG(error) << "Couldn't wait for audio event: [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - std::uint32_t packet_size {}; - for( - status = audio_capture->GetNextPacketSize(&packet_size); - SUCCEEDED(status) && packet_size > 0; - status = audio_capture->GetNextPacketSize(&packet_size)) { - DWORD buffer_flags; - status = audio_capture->GetBuffer( - (BYTE **)&sample_aligned.samples, - &block_aligned.audio_sample_size, - &buffer_flags, - nullptr, nullptr); - - switch(status) { - case S_OK: - break; - case AUDCLNT_E_DEVICE_INVALIDATED: - return capture_e::reinit; - default: - BOOST_LOG(error) << "Couldn't capture audio [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - sample_aligned.uninitialized = std::end(sample_buf) - sample_buf_pos; - auto n = std::min(sample_aligned.uninitialized, block_aligned.audio_sample_size * channels_in); - - if(buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) { - std::fill_n(sample_buf_pos, n, 0); - } - else { - std::copy_n(sample_aligned.samples, n, sample_buf_pos); - } - - sample_buf_pos += n; - - audio_capture->ReleaseBuffer(block_aligned.audio_sample_size); - } - - if(status == AUDCLNT_E_DEVICE_INVALIDATED) { - return capture_e::reinit; - } - - if(FAILED(status)) { - return capture_e::error; - } - - return capture_e::ok; - } - -public: - handle_t audio_event; - - device_enum_t device_enum; - device_t device; - audio_client_t audio_client; - audio_capture_t audio_capture; - - REFERENCE_TIME default_latency_ms; - - util::buffer_t sample_buf; - std::int16_t *sample_buf_pos; - - // out --> our audio output - int channels_out; - // in --> our wasapi input - int channels_in; - - std::unique_ptr pipe; -}; - -class audio_control_t : public ::platf::audio_control_t { -public: - std::optional sink_info() override { - auto virtual_adapter_name = L"Steam Streaming Speakers"sv; - - sink_t sink; - - audio::device_enum_t device_enum; - auto status = CoCreateInstance( - CLSID_MMDeviceEnumerator, - nullptr, - CLSCTX_ALL, - IID_IMMDeviceEnumerator, - (void **)&device_enum); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']'; - - return std::nullopt; - } - - auto device = default_device(device_enum); - if(!device) { - return std::nullopt; - } - - audio::wstring_t wstring; - device->GetId(&wstring); - - sink.host = converter.to_bytes(wstring.get()); - - collection_t collection; - status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']'; - - return std::nullopt; - } - - UINT count; - collection->GetCount(&count); - - std::string virtual_device_id = config::audio.virtual_sink; - for(auto x = 0; x < count; ++x) { - audio::device_t device; - collection->Item(x, &device); - - auto type = validate_device(device, SAMPLE_RATE); - if(type == format_t::none) { - continue; - } - - audio::wstring_t wstring; - device->GetId(&wstring); - - audio::prop_t prop; - device->OpenPropertyStore(STGM_READ, &prop); - - prop_var_t adapter_friendly_name; - prop_var_t device_friendly_name; - prop_var_t device_desc; - - prop->GetValue(PKEY_Device_FriendlyName, &device_friendly_name.prop); - prop->GetValue(PKEY_DeviceInterface_FriendlyName, &adapter_friendly_name.prop); - prop->GetValue(PKEY_Device_DeviceDesc, &device_desc.prop); - - auto adapter_name = no_null((LPWSTR)adapter_friendly_name.prop.pszVal); - BOOST_LOG(verbose) - << L"===== Device ====="sv << std::endl - << L"Device ID : "sv << wstring.get() << std::endl - << L"Device name : "sv << no_null((LPWSTR)device_friendly_name.prop.pszVal) << std::endl - << L"Adapter name : "sv << adapter_name << std::endl - << L"Device description : "sv << no_null((LPWSTR)device_desc.prop.pszVal) << std::endl - << std::endl; - - if(virtual_device_id.empty() && adapter_name == virtual_adapter_name) { - virtual_device_id = converter.to_bytes(wstring.get()); - } - } - - if(!virtual_device_id.empty()) { - sink.null = std::make_optional(sink_t::null_t { - "virtual-"s.append(formats[format_t::stereo - 1].name) + virtual_device_id, - "virtual-"s.append(formats[format_t::surr51 - 1].name) + virtual_device_id, - "virtual-"s.append(formats[format_t::surr71 - 1].name) + virtual_device_id, - }); - } - - return sink; - } - - std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override { - auto mic = std::make_unique(); - - if(mic->init(sample_rate, frame_size, channels)) { - return nullptr; - } - - return mic; - } - - /** - * If the requested sink is a virtual sink, meaning no speakers attached to - * the host, then we can seamlessly set the format to stereo and surround sound. - * - * Any virtual sink detected will be prefixed by: - * virtual-(format name) - * If it doesn't contain that prefix, then the format will not be changed - */ - std::optional set_format(const std::string &sink) { - std::string_view sv { sink.c_str(), sink.size() }; - - format_t::type_e type = format_t::none; - // sink format: - // [virtual-(format name)]device_id - auto prefix = "virtual-"sv; - if(sv.find(prefix) == 0) { - sv = sv.substr(prefix.size(), sv.size() - prefix.size()); - - for(auto &format : formats) { - auto &name = format.name; - if(sv.find(name) == 0) { - type = format.type; - sv = sv.substr(name.size(), sv.size() - name.size()); - - break; - } - } - } - - auto wstring_device_id = converter.from_bytes(sv.data()); - - if(type == format_t::none) { - // wstring_device_id does not contain virtual-(format name) - // It's a simple deviceId, just pass it back - return std::make_optional(std::move(wstring_device_id)); - } - - wave_format_t wave_format; - auto status = policy->GetMixFormat(wstring_device_id.c_str(), &wave_format); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; - - return std::nullopt; - } - - if(init_wave_format(wave_format, SAMPLE_RATE)) { - return std::nullopt; - } - set_wave_format(wave_format, formats[(int)type - 1]); - - WAVEFORMATEXTENSIBLE p {}; - status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p); - - // Surround 5.1 might contain side-{left, right} instead of speaker in the back - // Try again with different speaker mask. - if(status == 0x88890008 && type == format_t::surr51) { - set_wave_format(wave_format, surround_51_side_speakers); - status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p); - } - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't set Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; - - return std::nullopt; - } - - return std::make_optional(std::move(wstring_device_id)); - } - - int set_sink(const std::string &sink) override { - auto wstring_device_id = set_format(sink); - if(!wstring_device_id) { - return -1; - } - - int failure {}; - for(int x = 0; x < (int)ERole_enum_count; ++x) { - auto status = policy->SetDefaultEndpoint(wstring_device_id->c_str(), (ERole)x); - if(status) { - BOOST_LOG(warning) << "Couldn't set ["sv << sink << "] to role ["sv << x << ']'; - - ++failure; - } - } - - return failure; - } - - int init() { - auto status = CoCreateInstance( - CLSID_CPolicyConfigClient, - nullptr, - CLSCTX_ALL, - IID_IPolicyConfig, - (void **)&policy); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't create audio policy config: [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - return 0; - } - - ~audio_control_t() override {} - - policy_t policy; -}; -} // namespace platf::audio - -namespace platf { - -// It's not big enough to justify it's own source file :/ -namespace dxgi { -int init(); -} - -std::unique_ptr audio_control() { - auto control = std::make_unique(); - - if(control->init()) { - return nullptr; - } - - return control; -} - -std::unique_ptr init() { - if(dxgi::init()) { - return nullptr; - } - return std::make_unique(); -} -} // namespace platf +// +// Created by loki on 1/12/20. +// + +#include +#include +#include + +#include + +#include + +#define INITGUID +#include +#undef INITGUID + +#include "src/config.h" +#include "src/main.h" +#include "src/platform/common.h" + +// Must be the last included file +// clang-format off +#include "PolicyConfig.h" +// clang-format on + +DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING +DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING +DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2); + +const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); +const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); +const IID IID_IAudioClient = __uuidof(IAudioClient); +const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); + +using namespace std::literals; +namespace platf::audio { +constexpr auto SAMPLE_RATE = 48000; + +template +void Release(T *p) { + p->Release(); +} + +template +void co_task_free(T *p) { + CoTaskMemFree((LPVOID)p); +} + +using device_enum_t = util::safe_ptr>; +using device_t = util::safe_ptr>; +using collection_t = util::safe_ptr>; +using audio_client_t = util::safe_ptr>; +using audio_capture_t = util::safe_ptr>; +using wave_format_t = util::safe_ptr>; +using wstring_t = util::safe_ptr>; +using handle_t = util::safe_ptr_v2; +using policy_t = util::safe_ptr>; +using prop_t = util::safe_ptr>; + +class co_init_t : public deinit_t { +public: + co_init_t() { + CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY); + } + + ~co_init_t() override { + CoUninitialize(); + } +}; + +class prop_var_t { +public: + prop_var_t() { + PropVariantInit(&prop); + } + + ~prop_var_t() { + PropVariantClear(&prop); + } + + PROPVARIANT prop; +}; + +class audio_pipe_t { +public: + static constexpr auto stereo = 2; + static constexpr auto channels51 = 6; + static constexpr auto channels71 = 8; + + using samples_t = std::vector; + using buf_t = util::buffer_t; + + virtual void to_stereo(samples_t &out, const buf_t &in) = 0; + virtual void to_51(samples_t &out, const buf_t &in) = 0; + virtual void to_71(samples_t &out, const buf_t &in) = 0; +}; + +class mono_t : public audio_pipe_t { +public: + void to_stereo(samples_t &out, const buf_t &in) override { + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end;) { + *sample_out_p++ = *sample_in_pos * 7 / 10; + *sample_out_p++ = *sample_in_pos++ * 7 / 10; + } + } + + void to_51(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { + int left = *sample_in_pos++; + + auto fl = (left * 7 / 10); + + sample_out_p[FRONT_LEFT] = fl; + sample_out_p[FRONT_RIGHT] = fl; + sample_out_p[FRONT_CENTER] = fl * 6; + sample_out_p[LOW_FREQUENCY] = fl / 10; + sample_out_p[BACK_LEFT] = left * 4 / 10; + sample_out_p[BACK_RIGHT] = left * 4 / 10; + } + } + + void to_71(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { + int left = *sample_in_pos++; + + auto fl = (left * 7 / 10); + + sample_out_p[FRONT_LEFT] = fl; + sample_out_p[FRONT_RIGHT] = fl; + sample_out_p[FRONT_CENTER] = fl * 6; + sample_out_p[LOW_FREQUENCY] = fl / 10; + sample_out_p[BACK_LEFT] = left * 4 / 10; + sample_out_p[BACK_RIGHT] = left * 4 / 10; + sample_out_p[SIDE_LEFT] = left * 5 / 10; + sample_out_p[SIDE_RIGHT] = left * 5 / 10; + } + } +}; + +class stereo_t : public audio_pipe_t { +public: + void to_stereo(samples_t &out, const buf_t &in) override { + std::copy_n(std::begin(in), out.size(), std::begin(out)); + } + + void to_51(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { + int left = sample_in_pos[speaker::FRONT_LEFT]; + int right = sample_in_pos[speaker::FRONT_RIGHT]; + + sample_in_pos += 2; + + auto fl = (left * 7 / 10); + auto fr = (right * 7 / 10); + + auto mix = (fl + fr) / 2; + + sample_out_p[FRONT_LEFT] = fl; + sample_out_p[FRONT_RIGHT] = fr; + sample_out_p[FRONT_CENTER] = mix; + sample_out_p[LOW_FREQUENCY] = mix / 2; + sample_out_p[BACK_LEFT] = left * 4 / 10; + sample_out_p[BACK_RIGHT] = right * 4 / 10; + } + } + + void to_71(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { + int left = sample_in_pos[speaker::FRONT_LEFT]; + int right = sample_in_pos[speaker::FRONT_RIGHT]; + + sample_in_pos += 2; + + auto fl = (left * 7 / 10); + auto fr = (right * 7 / 10); + + auto mix = (fl + fr) / 2; + + sample_out_p[FRONT_LEFT] = fl; + sample_out_p[FRONT_RIGHT] = fr; + sample_out_p[FRONT_CENTER] = mix; + sample_out_p[LOW_FREQUENCY] = mix / 2; + sample_out_p[BACK_LEFT] = left * 4 / 10; + sample_out_p[BACK_RIGHT] = right * 4 / 10; + sample_out_p[SIDE_LEFT] = left * 5 / 10; + sample_out_p[SIDE_RIGHT] = right * 5 / 10; + } + } +}; + +class surr51_t : public audio_pipe_t { +public: + void to_stereo(samples_t &out, const buf_t &in) { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += stereo) { + int left {}, right {}; + + left += sample_in_pos[FRONT_LEFT]; + left += sample_in_pos[FRONT_CENTER] * 9 / 10; + left += sample_in_pos[LOW_FREQUENCY] * 3 / 10; + left += sample_in_pos[BACK_LEFT] * 7 / 10; + left += sample_in_pos[BACK_RIGHT] * 3 / 10; + + right += sample_in_pos[FRONT_RIGHT]; + right += sample_in_pos[FRONT_CENTER] * 9 / 10; + right += sample_in_pos[LOW_FREQUENCY] * 3 / 10; + right += sample_in_pos[BACK_LEFT] * 3 / 10; + right += sample_in_pos[BACK_RIGHT] * 7 / 10; + + sample_out_p[0] = left; + sample_out_p[1] = right; + + sample_in_pos += channels51; + } + } + + void to_51(samples_t &out, const buf_t &in) override { + std::copy_n(std::begin(in), out.size(), std::begin(out)); + } + + void to_71(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { + int fl = sample_in_pos[FRONT_LEFT]; + int fr = sample_in_pos[FRONT_RIGHT]; + int bl = sample_in_pos[BACK_LEFT]; + int br = sample_in_pos[BACK_RIGHT]; + + auto mix_l = (fl + bl) / 2; + auto mix_r = (bl + br) / 2; + + sample_out_p[FRONT_LEFT] = fl; + sample_out_p[FRONT_RIGHT] = fr; + sample_out_p[FRONT_CENTER] = sample_in_pos[FRONT_CENTER]; + sample_out_p[LOW_FREQUENCY] = sample_in_pos[LOW_FREQUENCY]; + sample_out_p[BACK_LEFT] = bl; + sample_out_p[BACK_RIGHT] = br; + sample_out_p[SIDE_LEFT] = mix_l; + sample_out_p[SIDE_RIGHT] = mix_r; + + sample_in_pos += channels51; + } + } +}; + +class surr71_t : public audio_pipe_t { +public: + void to_stereo(samples_t &out, const buf_t &in) { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += stereo) { + int left {}, right {}; + + left += sample_in_pos[FRONT_LEFT]; + left += sample_in_pos[FRONT_CENTER] * 9 / 10; + left += sample_in_pos[LOW_FREQUENCY] * 3 / 10; + left += sample_in_pos[BACK_LEFT] * 7 / 10; + left += sample_in_pos[BACK_RIGHT] * 3 / 10; + left += sample_in_pos[SIDE_LEFT]; + + right += sample_in_pos[FRONT_RIGHT]; + right += sample_in_pos[FRONT_CENTER] * 9 / 10; + right += sample_in_pos[LOW_FREQUENCY] * 3 / 10; + right += sample_in_pos[BACK_LEFT] * 3 / 10; + right += sample_in_pos[BACK_RIGHT] * 7 / 10; + right += sample_in_pos[SIDE_RIGHT]; + + sample_out_p[0] = left; + sample_out_p[1] = right; + + sample_in_pos += channels71; + } + } + + void to_51(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { + auto sl = (int)sample_out_p[SIDE_LEFT] * 3 / 10; + auto sr = (int)sample_out_p[SIDE_RIGHT] * 3 / 10; + + sample_out_p[FRONT_LEFT] = sample_in_pos[FRONT_LEFT] + sl; + sample_out_p[FRONT_RIGHT] = sample_in_pos[FRONT_RIGHT] + sr; + sample_out_p[FRONT_CENTER] = sample_in_pos[FRONT_CENTER]; + sample_out_p[LOW_FREQUENCY] = sample_in_pos[LOW_FREQUENCY]; + sample_out_p[BACK_LEFT] = sample_in_pos[BACK_LEFT] + sl; + sample_out_p[BACK_RIGHT] = sample_in_pos[BACK_RIGHT] + sr; + + sample_in_pos += channels71; + } + } + + void to_71(samples_t &out, const buf_t &in) override { + std::copy_n(std::begin(in), out.size(), std::begin(out)); + } +}; + +static std::wstring_convert, wchar_t> converter; +struct format_t { + enum type_e : int { + none, + mono, + stereo, + surr51, + surr71, + } type; + + std::string_view name; + int channels; + int channel_mask; +} formats[] { + { + format_t::mono, + "Mono"sv, + 1, + SPEAKER_FRONT_CENTER, + }, + { + format_t::stereo, + "Stereo"sv, + 2, + SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + }, + { + format_t::surr51, + "Surround 5.1"sv, + 6, + SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT, + }, + { + format_t::surr71, + "Surround 7.1"sv, + 8, + SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT | + SPEAKER_SIDE_LEFT | + SPEAKER_SIDE_RIGHT, + }, +}; + +static format_t surround_51_side_speakers { + format_t::surr51, + "Surround 5.1"sv, + 6, + SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_SIDE_LEFT | + SPEAKER_SIDE_RIGHT, +}; + +void set_wave_format(audio::wave_format_t &wave_format, const format_t &format) { + wave_format->nChannels = format.channels; + wave_format->nBlockAlign = wave_format->nChannels * wave_format->wBitsPerSample / 8; + wave_format->nAvgBytesPerSec = wave_format->nSamplesPerSec * wave_format->nBlockAlign; + + if(wave_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + ((PWAVEFORMATEXTENSIBLE)wave_format.get())->dwChannelMask = format.channel_mask; + } +} + +int init_wave_format(audio::wave_format_t &wave_format, DWORD sample_rate) { + wave_format->wBitsPerSample = 16; + wave_format->nSamplesPerSec = sample_rate; + switch(wave_format->wFormatTag) { + case WAVE_FORMAT_PCM: + break; + case WAVE_FORMAT_IEEE_FLOAT: + break; + case WAVE_FORMAT_EXTENSIBLE: { + auto wave_ex = (PWAVEFORMATEXTENSIBLE)wave_format.get(); + if(IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) { + wave_ex->Samples.wValidBitsPerSample = 16; + wave_ex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + } + + BOOST_LOG(error) << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']'; + } + default: + BOOST_LOG(error) << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']'; + return -1; + }; + + return 0; +} + +audio_client_t make_audio_client(device_t &device, const format_t &format, int sample_rate) { + audio_client_t audio_client; + auto status = device->Activate( + IID_IAudioClient, + CLSCTX_ALL, + nullptr, + (void **)&audio_client); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']'; + + return nullptr; + } + + wave_format_t wave_format; + status = audio_client->GetMixFormat(&wave_format); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; + + return nullptr; + } + + if(init_wave_format(wave_format, sample_rate)) { + return nullptr; + } + set_wave_format(wave_format, format); + + status = audio_client->Initialize( + AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, 0, + wave_format.get(), + nullptr); + + if(status) { + BOOST_LOG(debug) << "Couldn't initialize audio client for ["sv << format.name << "]: [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + return audio_client; +} + +const wchar_t *no_null(const wchar_t *str) { + return str ? str : L"Unknown"; +} + +format_t::type_e validate_device(device_t &device, int sample_rate) { + for(const auto &format : formats) { + // Ensure WaveFromat is compatible + auto audio_client = make_audio_client(device, format, sample_rate); + + BOOST_LOG(debug) << format.name << ": "sv << (!audio_client ? "unsupported"sv : "supported"sv); + + if(audio_client) { + return format.type; + } + } + + return format_t::none; +} + +device_t default_device(device_enum_t &device_enum) { + device_t device; + HRESULT status; + status = device_enum->GetDefaultAudioEndpoint( + eRender, + eConsole, + &device); + + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't create audio Device [0x"sv << util::hex(status).to_string_view() << ']'; + + return nullptr; + } + + return device; +} + +class mic_wasapi_t : public mic_t { +public: + capture_e sample(std::vector &sample_out) override { + auto sample_size = sample_out.size() / channels_out * channels_in; + while(sample_buf_pos - std::begin(sample_buf) < sample_size) { + //FIXME: Use IAudioClient3 instead of IAudioClient, that would allows for adjusting the latency of the audio samples + auto capture_result = _fill_buffer(); + + if(capture_result != capture_e::ok) { + return capture_result; + } + } + + switch(channels_out) { + case 2: + pipe->to_stereo(sample_out, sample_buf); + break; + case 6: + pipe->to_51(sample_out, sample_buf); + break; + case 8: + pipe->to_71(sample_out, sample_buf); + break; + default: + BOOST_LOG(error) << "converting to ["sv << channels_out << "] channels is not supported"sv; + return capture_e::error; + } + + // The excess samples should be in front of the queue + std::move(&sample_buf[sample_size], sample_buf_pos, std::begin(sample_buf)); + sample_buf_pos -= sample_size; + + return capture_e::ok; + } + + + int init(std::uint32_t sample_rate, std::uint32_t frame_size, std::uint32_t channels_out) { + audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr)); + if(!audio_event) { + BOOST_LOG(error) << "Couldn't create Event handle"sv; + + return -1; + } + + HRESULT status; + + status = CoCreateInstance( + CLSID_MMDeviceEnumerator, + nullptr, + CLSCTX_ALL, + IID_IMMDeviceEnumerator, + (void **)&device_enum); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't create Device Enumerator [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + auto device = default_device(device_enum); + if(!device) { + return -1; + } + + for(auto &format : formats) { + BOOST_LOG(debug) << "Trying audio format ["sv << format.name << ']'; + audio_client = make_audio_client(device, format, sample_rate); + + if(audio_client) { + BOOST_LOG(debug) << "Found audio format ["sv << format.name << ']'; + channels_in = format.channels; + this->channels_out = channels_out; + + switch(channels_in) { + case 1: + pipe = std::make_unique(); + break; + case 2: + pipe = std::make_unique(); + break; + case 6: + pipe = std::make_unique(); + break; + case 8: + pipe = std::make_unique(); + break; + default: + BOOST_LOG(error) << "converting from ["sv << channels_in << "] channels is not supported"sv; + return -1; + } + break; + } + } + + if(!audio_client) { + BOOST_LOG(error) << "Couldn't find supported format for audio"sv; + return -1; + } + + REFERENCE_TIME default_latency; + audio_client->GetDevicePeriod(&default_latency, nullptr); + default_latency_ms = default_latency / 1000; + + std::uint32_t frames; + status = audio_client->GetBufferSize(&frames); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't acquire the number of audio frames [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + // *2 --> needs to fit double + sample_buf = util::buffer_t { std::max(frames, frame_size) * 2 * channels_in }; + sample_buf_pos = std::begin(sample_buf); + + status = audio_client->GetService(IID_IAudioCaptureClient, (void **)&audio_capture); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't initialize audio capture client [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + status = audio_client->SetEventHandle(audio_event.get()); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't set event handle [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + status = audio_client->Start(); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't start recording [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + return 0; + } + + ~mic_wasapi_t() override { + if(audio_client) { + audio_client->Stop(); + } + } + +private: + capture_e _fill_buffer() { + HRESULT status; + + // Total number of samples + struct sample_aligned_t { + std::uint32_t uninitialized; + std::int16_t *samples; + } sample_aligned; + + // number of samples / number of channels + struct block_aligned_t { + std::uint32_t audio_sample_size; + } block_aligned; + + status = WaitForSingleObjectEx(audio_event.get(), default_latency_ms, FALSE); + switch(status) { + case WAIT_OBJECT_0: + break; + case WAIT_TIMEOUT: + return capture_e::timeout; + default: + BOOST_LOG(error) << "Couldn't wait for audio event: [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + std::uint32_t packet_size {}; + for( + status = audio_capture->GetNextPacketSize(&packet_size); + SUCCEEDED(status) && packet_size > 0; + status = audio_capture->GetNextPacketSize(&packet_size)) { + DWORD buffer_flags; + status = audio_capture->GetBuffer( + (BYTE **)&sample_aligned.samples, + &block_aligned.audio_sample_size, + &buffer_flags, + nullptr, nullptr); + + switch(status) { + case S_OK: + break; + case AUDCLNT_E_DEVICE_INVALIDATED: + return capture_e::reinit; + default: + BOOST_LOG(error) << "Couldn't capture audio [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + sample_aligned.uninitialized = std::end(sample_buf) - sample_buf_pos; + auto n = std::min(sample_aligned.uninitialized, block_aligned.audio_sample_size * channels_in); + + if(buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) { + std::fill_n(sample_buf_pos, n, 0); + } + else { + std::copy_n(sample_aligned.samples, n, sample_buf_pos); + } + + sample_buf_pos += n; + + audio_capture->ReleaseBuffer(block_aligned.audio_sample_size); + } + + if(status == AUDCLNT_E_DEVICE_INVALIDATED) { + return capture_e::reinit; + } + + if(FAILED(status)) { + return capture_e::error; + } + + return capture_e::ok; + } + +public: + handle_t audio_event; + + device_enum_t device_enum; + device_t device; + audio_client_t audio_client; + audio_capture_t audio_capture; + + REFERENCE_TIME default_latency_ms; + + util::buffer_t sample_buf; + std::int16_t *sample_buf_pos; + + // out --> our audio output + int channels_out; + // in --> our wasapi input + int channels_in; + + std::unique_ptr pipe; +}; + +class audio_control_t : public ::platf::audio_control_t { +public: + std::optional sink_info() override { + auto virtual_adapter_name = L"Steam Streaming Speakers"sv; + + sink_t sink; + + audio::device_enum_t device_enum; + auto status = CoCreateInstance( + CLSID_MMDeviceEnumerator, + nullptr, + CLSCTX_ALL, + IID_IMMDeviceEnumerator, + (void **)&device_enum); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']'; + + return std::nullopt; + } + + auto device = default_device(device_enum); + if(!device) { + return std::nullopt; + } + + audio::wstring_t wstring; + device->GetId(&wstring); + + sink.host = converter.to_bytes(wstring.get()); + + collection_t collection; + status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']'; + + return std::nullopt; + } + + UINT count; + collection->GetCount(&count); + + std::string virtual_device_id = config::audio.virtual_sink; + for(auto x = 0; x < count; ++x) { + audio::device_t device; + collection->Item(x, &device); + + auto type = validate_device(device, SAMPLE_RATE); + if(type == format_t::none) { + continue; + } + + audio::wstring_t wstring; + device->GetId(&wstring); + + audio::prop_t prop; + device->OpenPropertyStore(STGM_READ, &prop); + + prop_var_t adapter_friendly_name; + prop_var_t device_friendly_name; + prop_var_t device_desc; + + prop->GetValue(PKEY_Device_FriendlyName, &device_friendly_name.prop); + prop->GetValue(PKEY_DeviceInterface_FriendlyName, &adapter_friendly_name.prop); + prop->GetValue(PKEY_Device_DeviceDesc, &device_desc.prop); + + auto adapter_name = no_null((LPWSTR)adapter_friendly_name.prop.pszVal); + BOOST_LOG(verbose) + << L"===== Device ====="sv << std::endl + << L"Device ID : "sv << wstring.get() << std::endl + << L"Device name : "sv << no_null((LPWSTR)device_friendly_name.prop.pszVal) << std::endl + << L"Adapter name : "sv << adapter_name << std::endl + << L"Device description : "sv << no_null((LPWSTR)device_desc.prop.pszVal) << std::endl + << std::endl; + + if(virtual_device_id.empty() && adapter_name == virtual_adapter_name) { + virtual_device_id = converter.to_bytes(wstring.get()); + } + } + + if(!virtual_device_id.empty()) { + sink.null = std::make_optional(sink_t::null_t { + "virtual-"s.append(formats[format_t::stereo - 1].name) + virtual_device_id, + "virtual-"s.append(formats[format_t::surr51 - 1].name) + virtual_device_id, + "virtual-"s.append(formats[format_t::surr71 - 1].name) + virtual_device_id, + }); + } + + return sink; + } + + std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override { + auto mic = std::make_unique(); + + if(mic->init(sample_rate, frame_size, channels)) { + return nullptr; + } + + return mic; + } + + /** + * If the requested sink is a virtual sink, meaning no speakers attached to + * the host, then we can seamlessly set the format to stereo and surround sound. + * + * Any virtual sink detected will be prefixed by: + * virtual-(format name) + * If it doesn't contain that prefix, then the format will not be changed + */ + std::optional set_format(const std::string &sink) { + std::string_view sv { sink.c_str(), sink.size() }; + + format_t::type_e type = format_t::none; + // sink format: + // [virtual-(format name)]device_id + auto prefix = "virtual-"sv; + if(sv.find(prefix) == 0) { + sv = sv.substr(prefix.size(), sv.size() - prefix.size()); + + for(auto &format : formats) { + auto &name = format.name; + if(sv.find(name) == 0) { + type = format.type; + sv = sv.substr(name.size(), sv.size() - name.size()); + + break; + } + } + } + + auto wstring_device_id = converter.from_bytes(sv.data()); + + if(type == format_t::none) { + // wstring_device_id does not contain virtual-(format name) + // It's a simple deviceId, just pass it back + return std::make_optional(std::move(wstring_device_id)); + } + + wave_format_t wave_format; + auto status = policy->GetMixFormat(wstring_device_id.c_str(), &wave_format); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; + + return std::nullopt; + } + + if(init_wave_format(wave_format, SAMPLE_RATE)) { + return std::nullopt; + } + set_wave_format(wave_format, formats[(int)type - 1]); + + WAVEFORMATEXTENSIBLE p {}; + status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p); + + // Surround 5.1 might contain side-{left, right} instead of speaker in the back + // Try again with different speaker mask. + if(status == 0x88890008 && type == format_t::surr51) { + set_wave_format(wave_format, surround_51_side_speakers); + status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p); + } + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't set Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; + + return std::nullopt; + } + + return std::make_optional(std::move(wstring_device_id)); + } + + int set_sink(const std::string &sink) override { + auto wstring_device_id = set_format(sink); + if(!wstring_device_id) { + return -1; + } + + int failure {}; + for(int x = 0; x < (int)ERole_enum_count; ++x) { + auto status = policy->SetDefaultEndpoint(wstring_device_id->c_str(), (ERole)x); + if(status) { + BOOST_LOG(warning) << "Couldn't set ["sv << sink << "] to role ["sv << x << ']'; + + ++failure; + } + } + + return failure; + } + + int init() { + auto status = CoCreateInstance( + CLSID_CPolicyConfigClient, + nullptr, + CLSCTX_ALL, + IID_IPolicyConfig, + (void **)&policy); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't create audio policy config: [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + return 0; + } + + ~audio_control_t() override {} + + policy_t policy; +}; +} // namespace platf::audio + +namespace platf { + +// It's not big enough to justify it's own source file :/ +namespace dxgi { +int init(); +} + +std::unique_ptr audio_control() { + auto control = std::make_unique(); + + if(control->init()) { + return nullptr; + } + + return control; +} + +std::unique_ptr init() { + if(dxgi::init()) { + return nullptr; + } + return std::make_unique(); +} +} // namespace platf diff --git a/sunshine/platform/windows/display.h b/src/platform/windows/display.h similarity index 96% rename from sunshine/platform/windows/display.h rename to src/platform/windows/display.h index 6a150135..52faddb7 100644 --- a/sunshine/platform/windows/display.h +++ b/src/platform/windows/display.h @@ -1,177 +1,177 @@ -// -// Created by loki on 4/23/20. -// - -#ifndef SUNSHINE_DISPLAY_H -#define SUNSHINE_DISPLAY_H - -#include -#include -#include -#include -#include -#include - -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" - -namespace platf::dxgi { -extern const char *format_str[]; - -template -void Release(T *dxgi) { - dxgi->Release(); -} - -using factory1_t = util::safe_ptr>; -using dxgi_t = util::safe_ptr>; -using dxgi1_t = util::safe_ptr>; -using device_t = util::safe_ptr>; -using device_ctx_t = util::safe_ptr>; -using adapter_t = util::safe_ptr>; -using output_t = util::safe_ptr>; -using output1_t = util::safe_ptr>; -using dup_t = util::safe_ptr>; -using texture2d_t = util::safe_ptr>; -using texture1d_t = util::safe_ptr>; -using resource_t = util::safe_ptr>; -using multithread_t = util::safe_ptr>; -using vs_t = util::safe_ptr>; -using ps_t = util::safe_ptr>; -using blend_t = util::safe_ptr>; -using input_layout_t = util::safe_ptr>; -using render_target_t = util::safe_ptr>; -using shader_res_t = util::safe_ptr>; -using buf_t = util::safe_ptr>; -using raster_state_t = util::safe_ptr>; -using sampler_state_t = util::safe_ptr>; -using blob_t = util::safe_ptr>; -using depth_stencil_state_t = util::safe_ptr>; -using depth_stencil_view_t = util::safe_ptr>; - -namespace video { -using device_t = util::safe_ptr>; -using ctx_t = util::safe_ptr>; -using processor_t = util::safe_ptr>; -using processor_out_t = util::safe_ptr>; -using processor_in_t = util::safe_ptr>; -using processor_enum_t = util::safe_ptr>; -} // namespace video - -class hwdevice_t; -struct cursor_t { - std::vector img_data; - - DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info; - int x, y; - bool visible; -}; - -class gpu_cursor_t { -public: - gpu_cursor_t() : cursor_view { 0, 0, 0, 0, 0.0f, 1.0f } {}; - void set_pos(LONG rel_x, LONG rel_y, bool visible) { - cursor_view.TopLeftX = rel_x; - cursor_view.TopLeftY = rel_y; - - this->visible = visible; - } - - void set_texture(LONG width, LONG height, texture2d_t &&texture) { - cursor_view.Width = width; - cursor_view.Height = height; - - this->texture = std::move(texture); - } - - texture2d_t texture; - shader_res_t input_res; - - D3D11_VIEWPORT cursor_view; - - bool visible; -}; - -class duplication_t { -public: - dup_t dup; - bool has_frame {}; - bool use_dwmflush {}; - - capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p); - capture_e reset(dup_t::pointer dup_p = dup_t::pointer()); - capture_e release_frame(); - - ~duplication_t(); -}; - -class display_base_t : public display_t { -public: - int init(int framerate, const std::string &display_name); - - std::chrono::nanoseconds delay; - - factory1_t factory; - adapter_t adapter; - output_t output; - device_t device; - device_ctx_t device_ctx; - duplication_t dup; - - DXGI_FORMAT format; - D3D_FEATURE_LEVEL feature_level; - - typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS { - D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE, - D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL, - D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL, - D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL, - D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH, - D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME - } D3DKMT_SCHEDULINGPRIORITYCLASS; - - typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS); -}; - -class display_ram_t : public display_base_t { -public: - capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; - capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); - - - std::shared_ptr alloc_img() override; - int dummy_img(img_t *img) override; - - int init(int framerate, const std::string &display_name); - - cursor_t cursor; - D3D11_MAPPED_SUBRESOURCE img_info; - texture2d_t texture; -}; - -class display_vram_t : public display_base_t, public std::enable_shared_from_this { -public: - capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; - capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); - - std::shared_ptr alloc_img() override; - int dummy_img(img_t *img_base) override; - - int init(int framerate, const std::string &display_name); - - std::shared_ptr make_hwdevice(pix_fmt_e pix_fmt) override; - - sampler_state_t sampler_linear; - - blend_t blend_enable; - blend_t blend_disable; - - ps_t scene_ps; - vs_t scene_vs; - - texture2d_t src; - gpu_cursor_t cursor; -}; -} // namespace platf::dxgi - -#endif +// +// Created by loki on 4/23/20. +// + +#ifndef SUNSHINE_DISPLAY_H +#define SUNSHINE_DISPLAY_H + +#include +#include +#include +#include +#include +#include + +#include "src/platform/common.h" +#include "src/utility.h" + +namespace platf::dxgi { +extern const char *format_str[]; + +template +void Release(T *dxgi) { + dxgi->Release(); +} + +using factory1_t = util::safe_ptr>; +using dxgi_t = util::safe_ptr>; +using dxgi1_t = util::safe_ptr>; +using device_t = util::safe_ptr>; +using device_ctx_t = util::safe_ptr>; +using adapter_t = util::safe_ptr>; +using output_t = util::safe_ptr>; +using output1_t = util::safe_ptr>; +using dup_t = util::safe_ptr>; +using texture2d_t = util::safe_ptr>; +using texture1d_t = util::safe_ptr>; +using resource_t = util::safe_ptr>; +using multithread_t = util::safe_ptr>; +using vs_t = util::safe_ptr>; +using ps_t = util::safe_ptr>; +using blend_t = util::safe_ptr>; +using input_layout_t = util::safe_ptr>; +using render_target_t = util::safe_ptr>; +using shader_res_t = util::safe_ptr>; +using buf_t = util::safe_ptr>; +using raster_state_t = util::safe_ptr>; +using sampler_state_t = util::safe_ptr>; +using blob_t = util::safe_ptr>; +using depth_stencil_state_t = util::safe_ptr>; +using depth_stencil_view_t = util::safe_ptr>; + +namespace video { +using device_t = util::safe_ptr>; +using ctx_t = util::safe_ptr>; +using processor_t = util::safe_ptr>; +using processor_out_t = util::safe_ptr>; +using processor_in_t = util::safe_ptr>; +using processor_enum_t = util::safe_ptr>; +} // namespace video + +class hwdevice_t; +struct cursor_t { + std::vector img_data; + + DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info; + int x, y; + bool visible; +}; + +class gpu_cursor_t { +public: + gpu_cursor_t() : cursor_view { 0, 0, 0, 0, 0.0f, 1.0f } {}; + void set_pos(LONG rel_x, LONG rel_y, bool visible) { + cursor_view.TopLeftX = rel_x; + cursor_view.TopLeftY = rel_y; + + this->visible = visible; + } + + void set_texture(LONG width, LONG height, texture2d_t &&texture) { + cursor_view.Width = width; + cursor_view.Height = height; + + this->texture = std::move(texture); + } + + texture2d_t texture; + shader_res_t input_res; + + D3D11_VIEWPORT cursor_view; + + bool visible; +}; + +class duplication_t { +public: + dup_t dup; + bool has_frame {}; + bool use_dwmflush {}; + + capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p); + capture_e reset(dup_t::pointer dup_p = dup_t::pointer()); + capture_e release_frame(); + + ~duplication_t(); +}; + +class display_base_t : public display_t { +public: + int init(int framerate, const std::string &display_name); + + std::chrono::nanoseconds delay; + + factory1_t factory; + adapter_t adapter; + output_t output; + device_t device; + device_ctx_t device_ctx; + duplication_t dup; + + DXGI_FORMAT format; + D3D_FEATURE_LEVEL feature_level; + + typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS { + D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE, + D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL, + D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL, + D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL, + D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH, + D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME + } D3DKMT_SCHEDULINGPRIORITYCLASS; + + typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS); +}; + +class display_ram_t : public display_base_t { +public: + capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; + capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); + + + std::shared_ptr alloc_img() override; + int dummy_img(img_t *img) override; + + int init(int framerate, const std::string &display_name); + + cursor_t cursor; + D3D11_MAPPED_SUBRESOURCE img_info; + texture2d_t texture; +}; + +class display_vram_t : public display_base_t, public std::enable_shared_from_this { +public: + capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; + capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); + + std::shared_ptr alloc_img() override; + int dummy_img(img_t *img_base) override; + + int init(int framerate, const std::string &display_name); + + std::shared_ptr make_hwdevice(pix_fmt_e pix_fmt) override; + + sampler_state_t sampler_linear; + + blend_t blend_enable; + blend_t blend_disable; + + ps_t scene_ps; + vs_t scene_vs; + + texture2d_t src; + gpu_cursor_t cursor; +}; +} // namespace platf::dxgi + +#endif diff --git a/sunshine/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp similarity index 99% rename from sunshine/platform/windows/display_base.cpp rename to src/platform/windows/display_base.cpp index 9ee9ad97..e6951cce 100644 --- a/sunshine/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -7,9 +7,9 @@ #include "display.h" #include "misc.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" +#include "src/config.h" +#include "src/main.h" +#include "src/platform/common.h" namespace platf { using namespace std::literals; diff --git a/sunshine/platform/windows/display_ram.cpp b/src/platform/windows/display_ram.cpp similarity index 96% rename from sunshine/platform/windows/display_ram.cpp rename to src/platform/windows/display_ram.cpp index 285f0bcb..596d4880 100644 --- a/sunshine/platform/windows/display_ram.cpp +++ b/src/platform/windows/display_ram.cpp @@ -1,327 +1,327 @@ -#include "display.h" -#include "sunshine/main.h" - -namespace platf { -using namespace std::literals; -} - -namespace platf::dxgi { -struct img_t : public ::platf::img_t { - ~img_t() override { - delete[] data; - data = nullptr; - } -}; - -void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) { - int height = cursor.shape_info.Height / 2; - int width = cursor.shape_info.Width; - int pitch = cursor.shape_info.Pitch; - - // img cursor.{x,y} < 0, skip parts of the cursor.img_data - auto cursor_skip_y = -std::min(0, cursor.y); - auto cursor_skip_x = -std::min(0, cursor.x); - - // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data - auto cursor_truncate_y = std::max(0, cursor.y - img.height); - auto cursor_truncate_x = std::max(0, cursor.x - img.width); - - auto cursor_width = width - cursor_skip_x - cursor_truncate_x; - auto cursor_height = height - cursor_skip_y - cursor_truncate_y; - - if(cursor_height > height || cursor_width > width) { - return; - } - - auto img_skip_y = std::max(0, cursor.y); - auto img_skip_x = std::max(0, cursor.x); - - auto cursor_img_data = cursor.img_data.data() + cursor_skip_y * pitch; - - int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); - int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); - - auto pixels_per_byte = width / pitch; - auto bytes_per_row = delta_width / pixels_per_byte; - - auto img_data = (int *)img.data; - for(int i = 0; i < delta_height; ++i) { - auto and_mask = &cursor_img_data[i * pitch]; - auto xor_mask = &cursor_img_data[(i + height) * pitch]; - - auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; - - auto skip_x = cursor_skip_x; - for(int x = 0; x < bytes_per_row; ++x) { - for(auto bit = 0u; bit < 8; ++bit) { - if(skip_x > 0) { - --skip_x; - - continue; - } - - int and_ = *and_mask & (1 << (7 - bit)) ? -1 : 0; - int xor_ = *xor_mask & (1 << (7 - bit)) ? -1 : 0; - - *img_pixel_p &= and_; - *img_pixel_p ^= xor_; - - ++img_pixel_p; - } - - ++and_mask; - ++xor_mask; - } - } -} - -void apply_color_alpha(int *img_pixel_p, int cursor_pixel) { - auto colors_out = (std::uint8_t *)&cursor_pixel; - auto colors_in = (std::uint8_t *)img_pixel_p; - - //TODO: When use of IDXGIOutput5 is implemented, support different color formats - auto alpha = colors_out[3]; - if(alpha == 255) { - *img_pixel_p = cursor_pixel; - } - else { - colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255; - colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255; - colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255; - } -} - -void apply_color_masked(int *img_pixel_p, int cursor_pixel) { - //TODO: When use of IDXGIOutput5 is implemented, support different color formats - auto alpha = ((std::uint8_t *)&cursor_pixel)[3]; - if(alpha == 0xFF) { - *img_pixel_p ^= cursor_pixel; - } - else { - *img_pixel_p = cursor_pixel; - } -} - -void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) { - int height = cursor.shape_info.Height; - int width = cursor.shape_info.Width; - int pitch = cursor.shape_info.Pitch; - - // img cursor.y < 0, skip parts of the cursor.img_data - auto cursor_skip_y = -std::min(0, cursor.y); - auto cursor_skip_x = -std::min(0, cursor.x); - - // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data - auto cursor_truncate_y = std::max(0, cursor.y - img.height); - auto cursor_truncate_x = std::max(0, cursor.x - img.width); - - auto img_skip_y = std::max(0, cursor.y); - auto img_skip_x = std::max(0, cursor.x); - - auto cursor_width = width - cursor_skip_x - cursor_truncate_x; - auto cursor_height = height - cursor_skip_y - cursor_truncate_y; - - if(cursor_height > height || cursor_width > width) { - return; - } - - auto cursor_img_data = (int *)&cursor.img_data[cursor_skip_y * pitch]; - - int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); - int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); - - auto img_data = (int *)img.data; - - for(int i = 0; i < delta_height; ++i) { - auto cursor_begin = &cursor_img_data[i * cursor.shape_info.Width + cursor_skip_x]; - auto cursor_end = &cursor_begin[delta_width]; - - auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; - std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) { - if(masked) { - apply_color_masked(img_pixel_p, cursor_pixel); - } - else { - apply_color_alpha(img_pixel_p, cursor_pixel); - } - ++img_pixel_p; - }); - } -} - -void blend_cursor(const cursor_t &cursor, img_t &img) { - switch(cursor.shape_info.Type) { - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: - blend_cursor_color(cursor, img, false); - break; - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME: - blend_cursor_monochrome(cursor, img); - break; - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: - blend_cursor_color(cursor, img, true); - break; - default: - BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']'; - } -} - -capture_e display_ram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { - auto next_frame = std::chrono::steady_clock::now(); - - while(img) { - auto now = std::chrono::steady_clock::now(); - while(next_frame > now) { - now = std::chrono::steady_clock::now(); - } - next_frame = now + delay; - - auto status = snapshot(img.get(), 1000ms, *cursor); - switch(status) { - case platf::capture_e::reinit: - case platf::capture_e::error: - return status; - case platf::capture_e::timeout: - std::this_thread::sleep_for(1ms); - continue; - case platf::capture_e::ok: - img = snapshot_cb(img); - break; - default: - BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; - return status; - } - } - - return capture_e::ok; -} - -capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { - auto img = (img_t *)img_base; - - HRESULT status; - - DXGI_OUTDUPL_FRAME_INFO frame_info; - - resource_t::pointer res_p {}; - auto capture_status = dup.next_frame(frame_info, timeout, &res_p); - resource_t res { res_p }; - - if(capture_status != capture_e::ok) { - return capture_status; - } - - if(frame_info.PointerShapeBufferSize > 0) { - auto &img_data = cursor.img_data; - - img_data.resize(frame_info.PointerShapeBufferSize); - - UINT dummy; - status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; - - return capture_e::error; - } - } - - if(frame_info.LastMouseUpdateTime.QuadPart) { - cursor.x = frame_info.PointerPosition.Position.x; - cursor.y = frame_info.PointerPosition.Position.y; - cursor.visible = frame_info.PointerPosition.Visible; - } - - // If frame has been updated - if(frame_info.LastPresentTime.QuadPart != 0) { - { - texture2d_t src {}; - status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - //Copy from GPU to CPU - device_ctx->CopyResource(texture.get(), src.get()); - } - - if(img_info.pData) { - device_ctx->Unmap(texture.get(), 0); - img_info.pData = nullptr; - } - - status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']'; - - return capture_e::error; - } - } - - const bool mouse_update = - (frame_info.LastMouseUpdateTime.QuadPart || frame_info.PointerShapeBufferSize > 0) && - (cursor_visible && cursor.visible); - - const bool update_flag = frame_info.LastPresentTime.QuadPart != 0 || mouse_update; - - if(!update_flag) { - return capture_e::timeout; - } - - std::copy_n((std::uint8_t *)img_info.pData, height * img_info.RowPitch, (std::uint8_t *)img->data); - - if(cursor_visible && cursor.visible) { - blend_cursor(cursor, *img); - } - - return capture_e::ok; -} - -std::shared_ptr display_ram_t::alloc_img() { - auto img = std::make_shared(); - - img->pixel_pitch = 4; - img->row_pitch = img_info.RowPitch; - img->width = width; - img->height = height; - img->data = new std::uint8_t[img->row_pitch * height]; - - return img; -} - -int display_ram_t::dummy_img(platf::img_t *img) { - return 0; -} - -int display_ram_t::init(int framerate, const std::string &display_name) { - if(display_base_t::init(framerate, display_name)) { - return -1; - } - - D3D11_TEXTURE2D_DESC t {}; - t.Width = width; - t.Height = height; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_STAGING; - t.Format = format; - t.CPUAccessFlags = D3D11_CPU_ACCESS_READ; - - auto status = device->CreateTexture2D(&t, nullptr, &texture); - - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create texture [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - // map the texture simply to get the pitch and stride - status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to map the texture [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - return 0; -} -} // namespace platf::dxgi +#include "display.h" +#include "src/main.h" + +namespace platf { +using namespace std::literals; +} + +namespace platf::dxgi { +struct img_t : public ::platf::img_t { + ~img_t() override { + delete[] data; + data = nullptr; + } +}; + +void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) { + int height = cursor.shape_info.Height / 2; + int width = cursor.shape_info.Width; + int pitch = cursor.shape_info.Pitch; + + // img cursor.{x,y} < 0, skip parts of the cursor.img_data + auto cursor_skip_y = -std::min(0, cursor.y); + auto cursor_skip_x = -std::min(0, cursor.x); + + // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data + auto cursor_truncate_y = std::max(0, cursor.y - img.height); + auto cursor_truncate_x = std::max(0, cursor.x - img.width); + + auto cursor_width = width - cursor_skip_x - cursor_truncate_x; + auto cursor_height = height - cursor_skip_y - cursor_truncate_y; + + if(cursor_height > height || cursor_width > width) { + return; + } + + auto img_skip_y = std::max(0, cursor.y); + auto img_skip_x = std::max(0, cursor.x); + + auto cursor_img_data = cursor.img_data.data() + cursor_skip_y * pitch; + + int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); + int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); + + auto pixels_per_byte = width / pitch; + auto bytes_per_row = delta_width / pixels_per_byte; + + auto img_data = (int *)img.data; + for(int i = 0; i < delta_height; ++i) { + auto and_mask = &cursor_img_data[i * pitch]; + auto xor_mask = &cursor_img_data[(i + height) * pitch]; + + auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; + + auto skip_x = cursor_skip_x; + for(int x = 0; x < bytes_per_row; ++x) { + for(auto bit = 0u; bit < 8; ++bit) { + if(skip_x > 0) { + --skip_x; + + continue; + } + + int and_ = *and_mask & (1 << (7 - bit)) ? -1 : 0; + int xor_ = *xor_mask & (1 << (7 - bit)) ? -1 : 0; + + *img_pixel_p &= and_; + *img_pixel_p ^= xor_; + + ++img_pixel_p; + } + + ++and_mask; + ++xor_mask; + } + } +} + +void apply_color_alpha(int *img_pixel_p, int cursor_pixel) { + auto colors_out = (std::uint8_t *)&cursor_pixel; + auto colors_in = (std::uint8_t *)img_pixel_p; + + //TODO: When use of IDXGIOutput5 is implemented, support different color formats + auto alpha = colors_out[3]; + if(alpha == 255) { + *img_pixel_p = cursor_pixel; + } + else { + colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255; + colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255; + colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255; + } +} + +void apply_color_masked(int *img_pixel_p, int cursor_pixel) { + //TODO: When use of IDXGIOutput5 is implemented, support different color formats + auto alpha = ((std::uint8_t *)&cursor_pixel)[3]; + if(alpha == 0xFF) { + *img_pixel_p ^= cursor_pixel; + } + else { + *img_pixel_p = cursor_pixel; + } +} + +void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) { + int height = cursor.shape_info.Height; + int width = cursor.shape_info.Width; + int pitch = cursor.shape_info.Pitch; + + // img cursor.y < 0, skip parts of the cursor.img_data + auto cursor_skip_y = -std::min(0, cursor.y); + auto cursor_skip_x = -std::min(0, cursor.x); + + // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data + auto cursor_truncate_y = std::max(0, cursor.y - img.height); + auto cursor_truncate_x = std::max(0, cursor.x - img.width); + + auto img_skip_y = std::max(0, cursor.y); + auto img_skip_x = std::max(0, cursor.x); + + auto cursor_width = width - cursor_skip_x - cursor_truncate_x; + auto cursor_height = height - cursor_skip_y - cursor_truncate_y; + + if(cursor_height > height || cursor_width > width) { + return; + } + + auto cursor_img_data = (int *)&cursor.img_data[cursor_skip_y * pitch]; + + int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); + int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); + + auto img_data = (int *)img.data; + + for(int i = 0; i < delta_height; ++i) { + auto cursor_begin = &cursor_img_data[i * cursor.shape_info.Width + cursor_skip_x]; + auto cursor_end = &cursor_begin[delta_width]; + + auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; + std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) { + if(masked) { + apply_color_masked(img_pixel_p, cursor_pixel); + } + else { + apply_color_alpha(img_pixel_p, cursor_pixel); + } + ++img_pixel_p; + }); + } +} + +void blend_cursor(const cursor_t &cursor, img_t &img) { + switch(cursor.shape_info.Type) { + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: + blend_cursor_color(cursor, img, false); + break; + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME: + blend_cursor_monochrome(cursor, img); + break; + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: + blend_cursor_color(cursor, img, true); + break; + default: + BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']'; + } +} + +capture_e display_ram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { + auto next_frame = std::chrono::steady_clock::now(); + + while(img) { + auto now = std::chrono::steady_clock::now(); + while(next_frame > now) { + now = std::chrono::steady_clock::now(); + } + next_frame = now + delay; + + auto status = snapshot(img.get(), 1000ms, *cursor); + switch(status) { + case platf::capture_e::reinit: + case platf::capture_e::error: + return status; + case platf::capture_e::timeout: + std::this_thread::sleep_for(1ms); + continue; + case platf::capture_e::ok: + img = snapshot_cb(img); + break; + default: + BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; + return status; + } + } + + return capture_e::ok; +} + +capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { + auto img = (img_t *)img_base; + + HRESULT status; + + DXGI_OUTDUPL_FRAME_INFO frame_info; + + resource_t::pointer res_p {}; + auto capture_status = dup.next_frame(frame_info, timeout, &res_p); + resource_t res { res_p }; + + if(capture_status != capture_e::ok) { + return capture_status; + } + + if(frame_info.PointerShapeBufferSize > 0) { + auto &img_data = cursor.img_data; + + img_data.resize(frame_info.PointerShapeBufferSize); + + UINT dummy; + status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; + + return capture_e::error; + } + } + + if(frame_info.LastMouseUpdateTime.QuadPart) { + cursor.x = frame_info.PointerPosition.Position.x; + cursor.y = frame_info.PointerPosition.Position.y; + cursor.visible = frame_info.PointerPosition.Visible; + } + + // If frame has been updated + if(frame_info.LastPresentTime.QuadPart != 0) { + { + texture2d_t src {}; + status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + //Copy from GPU to CPU + device_ctx->CopyResource(texture.get(), src.get()); + } + + if(img_info.pData) { + device_ctx->Unmap(texture.get(), 0); + img_info.pData = nullptr; + } + + status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']'; + + return capture_e::error; + } + } + + const bool mouse_update = + (frame_info.LastMouseUpdateTime.QuadPart || frame_info.PointerShapeBufferSize > 0) && + (cursor_visible && cursor.visible); + + const bool update_flag = frame_info.LastPresentTime.QuadPart != 0 || mouse_update; + + if(!update_flag) { + return capture_e::timeout; + } + + std::copy_n((std::uint8_t *)img_info.pData, height * img_info.RowPitch, (std::uint8_t *)img->data); + + if(cursor_visible && cursor.visible) { + blend_cursor(cursor, *img); + } + + return capture_e::ok; +} + +std::shared_ptr display_ram_t::alloc_img() { + auto img = std::make_shared(); + + img->pixel_pitch = 4; + img->row_pitch = img_info.RowPitch; + img->width = width; + img->height = height; + img->data = new std::uint8_t[img->row_pitch * height]; + + return img; +} + +int display_ram_t::dummy_img(platf::img_t *img) { + return 0; +} + +int display_ram_t::init(int framerate, const std::string &display_name) { + if(display_base_t::init(framerate, display_name)) { + return -1; + } + + D3D11_TEXTURE2D_DESC t {}; + t.Width = width; + t.Height = height; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_STAGING; + t.Format = format; + t.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + + auto status = device->CreateTexture2D(&t, nullptr, &texture); + + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create texture [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + // map the texture simply to get the pitch and stride + status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to map the texture [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + return 0; +} +} // namespace platf::dxgi diff --git a/sunshine/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp similarity index 96% rename from sunshine/platform/windows/display_vram.cpp rename to src/platform/windows/display_vram.cpp index 9a932665..3cf7c977 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -1,888 +1,888 @@ -#include - -#include - -#include -#include - -extern "C" { -#include -#include -} - -#include "display.h" -#include "sunshine/main.h" -#include "sunshine/video.h" - - -#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/directx" -namespace platf { -using namespace std::literals; -} - -static void free_frame(AVFrame *frame) { - av_frame_free(&frame); -} - -using frame_t = util::safe_ptr; - -namespace platf::dxgi { - -template -buf_t make_buffer(device_t::pointer device, const T &t) { - static_assert(sizeof(T) % 16 == 0, "Buffer needs to be aligned on a 16-byte alignment"); - - D3D11_BUFFER_DESC buffer_desc { - sizeof(T), - D3D11_USAGE_IMMUTABLE, - D3D11_BIND_CONSTANT_BUFFER - }; - - D3D11_SUBRESOURCE_DATA init_data { - &t - }; - - buf_t::pointer buf_p; - auto status = device->CreateBuffer(&buffer_desc, &init_data, &buf_p); - if(status) { - BOOST_LOG(error) << "Failed to create buffer: [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; - } - - return buf_t { buf_p }; -} - -blend_t make_blend(device_t::pointer device, bool enable) { - D3D11_BLEND_DESC bdesc {}; - auto &rt = bdesc.RenderTarget[0]; - rt.BlendEnable = enable; - rt.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; - - if(enable) { - rt.BlendOp = D3D11_BLEND_OP_ADD; - rt.BlendOpAlpha = D3D11_BLEND_OP_ADD; - - rt.SrcBlend = D3D11_BLEND_SRC_ALPHA; - rt.DestBlend = D3D11_BLEND_INV_SRC_ALPHA; - - rt.SrcBlendAlpha = D3D11_BLEND_ZERO; - rt.DestBlendAlpha = D3D11_BLEND_ZERO; - } - - blend_t blend; - auto status = device->CreateBlendState(&bdesc, &blend); - if(status) { - BOOST_LOG(error) << "Failed to create blend state: [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; - } - - return blend; -} - -blob_t convert_UV_vs_hlsl; -blob_t convert_UV_ps_hlsl; -blob_t scene_vs_hlsl; -blob_t convert_Y_ps_hlsl; -blob_t scene_ps_hlsl; - -struct img_d3d_t : public platf::img_t { - std::shared_ptr display; - - shader_res_t input_res; - render_target_t scene_rt; - - texture2d_t texture; - - ~img_d3d_t() override = default; -}; - -util::buffer_t make_cursor_image(util::buffer_t &&img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) { - constexpr std::uint32_t black = 0xFF000000; - constexpr std::uint32_t white = 0xFFFFFFFF; - constexpr std::uint32_t transparent = 0; - - switch(shape_info.Type) { - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: - std::for_each((std::uint32_t *)std::begin(img_data), (std::uint32_t *)std::end(img_data), [](auto &pixel) { - if(pixel & 0xFF000000) { - pixel = transparent; - } - }); - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: - return std::move(img_data); - default: - break; - } - - shape_info.Height /= 2; - - util::buffer_t cursor_img { shape_info.Width * shape_info.Height * 4 }; - - auto bytes = shape_info.Pitch * shape_info.Height; - auto pixel_begin = (std::uint32_t *)std::begin(cursor_img); - auto pixel_data = pixel_begin; - auto and_mask = std::begin(img_data); - auto xor_mask = std::begin(img_data) + bytes; - - for(auto x = 0; x < bytes; ++x) { - for(auto c = 7; c >= 0; --c) { - auto bit = 1 << c; - auto color_type = ((*and_mask & bit) ? 1 : 0) + ((*xor_mask & bit) ? 2 : 0); - - switch(color_type) { - case 0: //black - *pixel_data = black; - break; - case 2: //white - *pixel_data = white; - break; - case 1: //transparent - { - *pixel_data = transparent; - - break; - } - case 3: //inverse - { - auto top_p = pixel_data - shape_info.Width; - auto left_p = pixel_data - 1; - auto right_p = pixel_data + 1; - auto bottom_p = pixel_data + shape_info.Width; - - // Get the x coordinate of the pixel - auto column = (pixel_data - pixel_begin) % shape_info.Width != 0; - - if(top_p >= pixel_begin && *top_p == transparent) { - *top_p = black; - } - - if(column != 0 && left_p >= pixel_begin && *left_p == transparent) { - *left_p = black; - } - - if(bottom_p < (std::uint32_t *)std::end(cursor_img)) { - *bottom_p = black; - } - - if(column != shape_info.Width - 1) { - *right_p = black; - } - *pixel_data = white; - } - } - - ++pixel_data; - } - ++and_mask; - ++xor_mask; - } - - return cursor_img; -} - -blob_t compile_shader(LPCSTR file, LPCSTR entrypoint, LPCSTR shader_model) { - blob_t::pointer msg_p = nullptr; - blob_t::pointer compiled_p; - - DWORD flags = D3DCOMPILE_ENABLE_STRICTNESS; - -#ifndef NDEBUG - flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; -#endif - std::wstring_convert, wchar_t> converter; - - auto wFile = converter.from_bytes(file); - auto status = D3DCompileFromFile(wFile.c_str(), nullptr, nullptr, entrypoint, shader_model, flags, 0, &compiled_p, &msg_p); - - if(msg_p) { - BOOST_LOG(warning) << std::string_view { (const char *)msg_p->GetBufferPointer(), msg_p->GetBufferSize() - 1 }; - msg_p->Release(); - } - - if(status) { - BOOST_LOG(error) << "Couldn't compile ["sv << file << "] [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; - } - - return blob_t { compiled_p }; -} - -blob_t compile_pixel_shader(LPCSTR file) { - return compile_shader(file, "main_ps", "ps_5_0"); -} - -blob_t compile_vertex_shader(LPCSTR file) { - return compile_shader(file, "main_vs", "vs_5_0"); -} - -int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format, texture2d_t::pointer tex) { - D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_desc { - format, - D3D11_SRV_DIMENSION_TEXTURE2D - }; - shader_resource_desc.Texture2D.MipLevels = 1; - - auto status = device->CreateShaderResourceView(tex, &shader_resource_desc, &shader_res); - if(status) { - BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - D3D11_RENDER_TARGET_VIEW_DESC render_target_desc { - format, - D3D11_RTV_DIMENSION_TEXTURE2D - }; - - status = device->CreateRenderTargetView(tex, &render_target_desc, &render_target); - if(status) { - BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - return 0; -} - -int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format) { - D3D11_TEXTURE2D_DESC desc {}; - - desc.Width = width; - desc.Height = height; - desc.Format = format; - desc.Usage = D3D11_USAGE_DEFAULT; - desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; - desc.MipLevels = 1; - desc.ArraySize = 1; - desc.SampleDesc.Count = 1; - - texture2d_t tex; - auto status = device->CreateTexture2D(&desc, nullptr, &tex); - if(status) { - BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - return init_rt(device, shader_res, render_target, width, height, format, tex.get()); -} - -class hwdevice_t : public platf::hwdevice_t { -public: - int convert(platf::img_t &img_base) override { - auto &img = (img_d3d_t &)img_base; - - device_ctx_p->IASetInputLayout(input_layout.get()); - - _init_view_port(this->img.width, this->img.height); - device_ctx_p->OMSetRenderTargets(1, &nv12_Y_rt, nullptr); - device_ctx_p->VSSetShader(scene_vs.get(), nullptr, 0); - device_ctx_p->PSSetShader(convert_Y_ps.get(), nullptr, 0); - device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res); - device_ctx_p->Draw(3, 0); - - device_ctx_p->RSSetViewports(1, &outY_view); - device_ctx_p->PSSetShaderResources(0, 1, &img.input_res); - device_ctx_p->Draw(3, 0); - - // Artifacts start appearing on the rendered image if Sunshine doesn't flush - // before rendering on the UV part of the image. - device_ctx_p->Flush(); - - _init_view_port(this->img.width / 2, this->img.height / 2); - device_ctx_p->OMSetRenderTargets(1, &nv12_UV_rt, nullptr); - device_ctx_p->VSSetShader(convert_UV_vs.get(), nullptr, 0); - device_ctx_p->PSSetShader(convert_UV_ps.get(), nullptr, 0); - device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res); - device_ctx_p->Draw(3, 0); - - device_ctx_p->RSSetViewports(1, &outUV_view); - device_ctx_p->PSSetShaderResources(0, 1, &img.input_res); - device_ctx_p->Draw(3, 0); - device_ctx_p->Flush(); - - return 0; - } - - void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override { - switch(colorspace) { - case 5: // SWS_CS_SMPTE170M - color_p = &::video::colors[0]; - break; - case 1: // SWS_CS_ITU709 - color_p = &::video::colors[2]; - break; - case 9: // SWS_CS_BT2020 - default: - BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv; - color_p = &::video::colors[0]; - }; - - if(color_range > 1) { - // Full range - ++color_p; - } - - auto color_matrix = make_buffer((device_t::pointer)data, *color_p); - if(!color_matrix) { - BOOST_LOG(warning) << "Failed to create color matrix"sv; - return; - } - - device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); - this->color_matrix = std::move(color_matrix); - } - - int set_frame(AVFrame *frame) { - this->hwframe.reset(frame); - this->frame = frame; - - auto device_p = (device_t::pointer)data; - - auto out_width = frame->width; - auto out_height = frame->height; - - float in_width = img.display->width; - float in_height = img.display->height; - - // // Ensure aspect ratio is maintained - auto scalar = std::fminf(out_width / in_width, out_height / in_height); - auto out_width_f = in_width * scalar; - auto out_height_f = in_height * scalar; - - // result is always positive - auto offsetX = (out_width - out_width_f) / 2; - auto offsetY = (out_height - out_height_f) / 2; - - outY_view = D3D11_VIEWPORT { offsetX, offsetY, out_width_f, out_height_f, 0.0f, 1.0f }; - outUV_view = D3D11_VIEWPORT { offsetX / 2, offsetY / 2, out_width_f / 2, out_height_f / 2, 0.0f, 1.0f }; - - D3D11_TEXTURE2D_DESC t {}; - t.Width = out_width; - t.Height = out_height; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_DEFAULT; - t.Format = format; - t.BindFlags = D3D11_BIND_RENDER_TARGET; - - auto status = device_p->CreateTexture2D(&t, nullptr, &img.texture); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create render target texture [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - img.width = out_width; - img.height = out_height; - img.data = (std::uint8_t *)img.texture.get(); - img.row_pitch = out_width * 4; - img.pixel_pitch = 4; - - float info_in[16 / sizeof(float)] { 1.0f / (float)out_width }; //aligned to 16-byte - info_scene = make_buffer(device_p, info_in); - - if(!info_in) { - BOOST_LOG(error) << "Failed to create info scene buffer"sv; - return -1; - } - - D3D11_RENDER_TARGET_VIEW_DESC nv12_rt_desc { - DXGI_FORMAT_R8_UNORM, - D3D11_RTV_DIMENSION_TEXTURE2D - }; - - status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_Y_rt); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - nv12_rt_desc.Format = DXGI_FORMAT_R8G8_UNORM; - - status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_UV_rt); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - // Need to have something refcounted - if(!frame->buf[0]) { - frame->buf[0] = av_buffer_allocz(sizeof(AVD3D11FrameDescriptor)); - } - - auto desc = (AVD3D11FrameDescriptor *)frame->buf[0]->data; - desc->texture = (ID3D11Texture2D *)img.data; - desc->index = 0; - - frame->data[0] = img.data; - frame->data[1] = 0; - - frame->linesize[0] = img.row_pitch; - - frame->height = img.height; - frame->width = img.width; - - return 0; - } - - int init( - std::shared_ptr display, device_t::pointer device_p, device_ctx_t::pointer device_ctx_p, - pix_fmt_e pix_fmt) { - - HRESULT status; - - device_p->AddRef(); - data = device_p; - - this->device_ctx_p = device_ctx_p; - - format = (pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010); - status = device_p->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); - if(status) { - BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - status = device_p->CreatePixelShader(convert_Y_ps_hlsl->GetBufferPointer(), convert_Y_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps); - if(status) { - BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - status = device_p->CreatePixelShader(convert_UV_ps_hlsl->GetBufferPointer(), convert_UV_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps); - if(status) { - BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - status = device_p->CreateVertexShader(convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), nullptr, &convert_UV_vs); - if(status) { - BOOST_LOG(error) << "Failed to create convertUV vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - status = device_p->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps); - if(status) { - BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - color_matrix = make_buffer(device_p, ::video::colors[0]); - if(!color_matrix) { - BOOST_LOG(error) << "Failed to create color matrix buffer"sv; - return -1; - } - - D3D11_INPUT_ELEMENT_DESC layout_desc { - "SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 - }; - - status = device_p->CreateInputLayout( - &layout_desc, 1, - convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), - &input_layout); - - img.display = std::move(display); - - // Color the background black, so that the padding for keeping the aspect ratio - // is black - if(img.display->dummy_img(&back_img)) { - BOOST_LOG(warning) << "Couldn't create an image to set background color to black"sv; - return -1; - } - - D3D11_SHADER_RESOURCE_VIEW_DESC desc { - DXGI_FORMAT_B8G8R8A8_UNORM, - D3D11_SRV_DIMENSION_TEXTURE2D - }; - desc.Texture2D.MipLevels = 1; - - status = device_p->CreateShaderResourceView(back_img.texture.get(), &desc, &back_img.input_res); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create input shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - device_ctx_p->IASetInputLayout(input_layout.get()); - device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); - device_ctx_p->VSSetConstantBuffers(0, 1, &info_scene); - - return 0; - } - - ~hwdevice_t() override { - if(data) { - ((ID3D11Device *)data)->Release(); - } - } - -private: - void _init_view_port(float x, float y, float width, float height) { - D3D11_VIEWPORT view { - x, y, - width, height, - 0.0f, 1.0f - }; - - device_ctx_p->RSSetViewports(1, &view); - } - - void _init_view_port(float width, float height) { - _init_view_port(0.0f, 0.0f, width, height); - } - -public: - frame_t hwframe; - - ::video::color_t *color_p; - - buf_t info_scene; - buf_t color_matrix; - - input_layout_t input_layout; - - render_target_t nv12_Y_rt; - render_target_t nv12_UV_rt; - - // The image referenced by hwframe - // The resulting image is stored here. - img_d3d_t img; - - // Clear nv12 render target to black - img_d3d_t back_img; - - vs_t convert_UV_vs; - ps_t convert_UV_ps; - ps_t convert_Y_ps; - ps_t scene_ps; - vs_t scene_vs; - - D3D11_VIEWPORT outY_view; - D3D11_VIEWPORT outUV_view; - - DXGI_FORMAT format; - - device_ctx_t::pointer device_ctx_p; -}; - -capture_e display_vram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { - auto next_frame = std::chrono::steady_clock::now(); - - while(img) { - auto now = std::chrono::steady_clock::now(); - while(next_frame > now) { - now = std::chrono::steady_clock::now(); - } - next_frame = now + delay; - - auto status = snapshot(img.get(), 1000ms, *cursor); - switch(status) { - case platf::capture_e::reinit: - case platf::capture_e::error: - return status; - case platf::capture_e::timeout: - std::this_thread::sleep_for(1ms); - continue; - case platf::capture_e::ok: - img = snapshot_cb(img); - break; - default: - BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; - return status; - } - } - - return capture_e::ok; -} - -capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { - auto img = (img_d3d_t *)img_base; - - HRESULT status; - - DXGI_OUTDUPL_FRAME_INFO frame_info; - - resource_t::pointer res_p {}; - auto capture_status = dup.next_frame(frame_info, timeout, &res_p); - resource_t res { res_p }; - - if(capture_status != capture_e::ok) { - return capture_status; - } - - const bool mouse_update_flag = frame_info.LastMouseUpdateTime.QuadPart != 0 || frame_info.PointerShapeBufferSize > 0; - const bool frame_update_flag = frame_info.AccumulatedFrames != 0 || frame_info.LastPresentTime.QuadPart != 0; - const bool update_flag = mouse_update_flag || frame_update_flag; - - if(!update_flag) { - return capture_e::timeout; - } - - if(frame_info.PointerShapeBufferSize > 0) { - DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info {}; - - util::buffer_t img_data { frame_info.PointerShapeBufferSize }; - - UINT dummy; - status = dup.dup->GetFramePointerShape(img_data.size(), std::begin(img_data), &dummy, &shape_info); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; - - return capture_e::error; - } - - auto cursor_img = make_cursor_image(std::move(img_data), shape_info); - - D3D11_SUBRESOURCE_DATA data { - std::begin(cursor_img), - 4 * shape_info.Width, - 0 - }; - - // Create texture for cursor - D3D11_TEXTURE2D_DESC t {}; - t.Width = shape_info.Width; - t.Height = cursor_img.size() / data.SysMemPitch; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_DEFAULT; - t.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - t.BindFlags = D3D11_BIND_SHADER_RESOURCE; - - texture2d_t texture; - auto status = device->CreateTexture2D(&t, &data, &texture); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create mouse texture [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - D3D11_SHADER_RESOURCE_VIEW_DESC desc { - DXGI_FORMAT_B8G8R8A8_UNORM, - D3D11_SRV_DIMENSION_TEXTURE2D - }; - desc.Texture2D.MipLevels = 1; - - // Free resources before allocating on the next line. - cursor.input_res.reset(); - status = device->CreateShaderResourceView(texture.get(), &desc, &cursor.input_res); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create cursor shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - cursor.set_texture(t.Width, t.Height, std::move(texture)); - } - - if(frame_info.LastMouseUpdateTime.QuadPart) { - cursor.set_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, frame_info.PointerPosition.Visible && cursor_visible); - } - - if(frame_update_flag) { - src.reset(); - status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - } - - device_ctx->CopyResource(img->texture.get(), src.get()); - if(cursor.visible) { - D3D11_VIEWPORT view { - 0.0f, 0.0f, - (float)width, (float)height, - 0.0f, 1.0f - }; - - device_ctx->VSSetShader(scene_vs.get(), nullptr, 0); - device_ctx->PSSetShader(scene_ps.get(), nullptr, 0); - device_ctx->RSSetViewports(1, &view); - device_ctx->OMSetRenderTargets(1, &img->scene_rt, nullptr); - device_ctx->PSSetShaderResources(0, 1, &cursor.input_res); - device_ctx->OMSetBlendState(blend_enable.get(), nullptr, 0xFFFFFFFFu); - device_ctx->RSSetViewports(1, &cursor.cursor_view); - device_ctx->Draw(3, 0); - device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); - } - - return capture_e::ok; -} - -int display_vram_t::init(int framerate, const std::string &display_name) { - if(display_base_t::init(framerate, display_name)) { - return -1; - } - - D3D11_SAMPLER_DESC sampler_desc {}; - sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; - sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; - sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; - sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; - sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER; - sampler_desc.MinLOD = 0; - sampler_desc.MaxLOD = D3D11_FLOAT32_MAX; - - auto status = device->CreateSamplerState(&sampler_desc, &sampler_linear); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create point sampler state [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - status = device->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); - if(status) { - BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - status = device->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps); - if(status) { - BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - blend_enable = make_blend(device.get(), true); - blend_disable = make_blend(device.get(), false); - - if(!blend_disable || !blend_enable) { - return -1; - } - - device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); - device_ctx->PSSetSamplers(0, 1, &sampler_linear); - device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); - - return 0; -} - -std::shared_ptr display_vram_t::alloc_img() { - auto img = std::make_shared(); - - img->pixel_pitch = 4; - img->row_pitch = img->pixel_pitch * width; - img->width = width; - img->height = height; - img->display = shared_from_this(); - - auto dummy_data = std::make_unique(img->row_pitch * height); - D3D11_SUBRESOURCE_DATA data { - dummy_data.get(), - (UINT)img->row_pitch - }; - std::fill_n(dummy_data.get(), img->row_pitch * height, 0); - - D3D11_TEXTURE2D_DESC t {}; - t.Width = width; - t.Height = height; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_DEFAULT; - t.Format = format; - t.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; - - auto status = device->CreateTexture2D(&t, &data, &img->texture); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create img buf texture [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; - } - - if(init_rt(device.get(), img->input_res, img->scene_rt, width, height, format, img->texture.get())) { - return nullptr; - } - - img->data = (std::uint8_t *)img->texture.get(); - - return img; -} - -int display_vram_t::dummy_img(platf::img_t *img_base) { - auto img = (img_d3d_t *)img_base; - - if(img->texture) { - return 0; - } - - img->row_pitch = width * 4; - auto dummy_data = std::make_unique(width * height); - D3D11_SUBRESOURCE_DATA data { - dummy_data.get(), - (UINT)img->row_pitch - }; - std::fill_n(dummy_data.get(), width * height, 0); - - D3D11_TEXTURE2D_DESC t {}; - t.Width = width; - t.Height = height; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_DEFAULT; - t.Format = format; - t.BindFlags = D3D11_BIND_SHADER_RESOURCE; - - dxgi::texture2d_t tex; - auto status = device->CreateTexture2D(&t, &data, &tex); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create dummy texture [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - img->texture = std::move(tex); - img->data = (std::uint8_t *)img->texture.get(); - - return 0; -} - -std::shared_ptr display_vram_t::make_hwdevice(pix_fmt_e pix_fmt) { - if(pix_fmt != platf::pix_fmt_e::nv12) { - BOOST_LOG(error) << "display_vram_t doesn't support pixel format ["sv << from_pix_fmt(pix_fmt) << ']'; - - return nullptr; - } - - auto hwdevice = std::make_shared(); - - auto ret = hwdevice->init( - shared_from_this(), - device.get(), - device_ctx.get(), - pix_fmt); - - if(ret) { - return nullptr; - } - - return hwdevice; -} - -int init() { - BOOST_LOG(info) << "Compiling shaders..."sv; - scene_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/SceneVS.hlsl"); - if(!scene_vs_hlsl) { - return -1; - } - - convert_Y_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertYPS.hlsl"); - if(!convert_Y_ps_hlsl) { - return -1; - } - - convert_UV_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertUVPS.hlsl"); - if(!convert_UV_ps_hlsl) { - return -1; - } - - convert_UV_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/ConvertUVVS.hlsl"); - if(!convert_UV_vs_hlsl) { - return -1; - } - - scene_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ScenePS.hlsl"); - if(!scene_ps_hlsl) { - return -1; - } - BOOST_LOG(info) << "Compiled shaders"sv; - - return 0; -} +#include + +#include + +#include +#include + +extern "C" { +#include +#include +} + +#include "display.h" +#include "src/main.h" +#include "src/video.h" + + +#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/directx" +namespace platf { +using namespace std::literals; +} + +static void free_frame(AVFrame *frame) { + av_frame_free(&frame); +} + +using frame_t = util::safe_ptr; + +namespace platf::dxgi { + +template +buf_t make_buffer(device_t::pointer device, const T &t) { + static_assert(sizeof(T) % 16 == 0, "Buffer needs to be aligned on a 16-byte alignment"); + + D3D11_BUFFER_DESC buffer_desc { + sizeof(T), + D3D11_USAGE_IMMUTABLE, + D3D11_BIND_CONSTANT_BUFFER + }; + + D3D11_SUBRESOURCE_DATA init_data { + &t + }; + + buf_t::pointer buf_p; + auto status = device->CreateBuffer(&buffer_desc, &init_data, &buf_p); + if(status) { + BOOST_LOG(error) << "Failed to create buffer: [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + return buf_t { buf_p }; +} + +blend_t make_blend(device_t::pointer device, bool enable) { + D3D11_BLEND_DESC bdesc {}; + auto &rt = bdesc.RenderTarget[0]; + rt.BlendEnable = enable; + rt.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + + if(enable) { + rt.BlendOp = D3D11_BLEND_OP_ADD; + rt.BlendOpAlpha = D3D11_BLEND_OP_ADD; + + rt.SrcBlend = D3D11_BLEND_SRC_ALPHA; + rt.DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + + rt.SrcBlendAlpha = D3D11_BLEND_ZERO; + rt.DestBlendAlpha = D3D11_BLEND_ZERO; + } + + blend_t blend; + auto status = device->CreateBlendState(&bdesc, &blend); + if(status) { + BOOST_LOG(error) << "Failed to create blend state: [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + return blend; +} + +blob_t convert_UV_vs_hlsl; +blob_t convert_UV_ps_hlsl; +blob_t scene_vs_hlsl; +blob_t convert_Y_ps_hlsl; +blob_t scene_ps_hlsl; + +struct img_d3d_t : public platf::img_t { + std::shared_ptr display; + + shader_res_t input_res; + render_target_t scene_rt; + + texture2d_t texture; + + ~img_d3d_t() override = default; +}; + +util::buffer_t make_cursor_image(util::buffer_t &&img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) { + constexpr std::uint32_t black = 0xFF000000; + constexpr std::uint32_t white = 0xFFFFFFFF; + constexpr std::uint32_t transparent = 0; + + switch(shape_info.Type) { + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: + std::for_each((std::uint32_t *)std::begin(img_data), (std::uint32_t *)std::end(img_data), [](auto &pixel) { + if(pixel & 0xFF000000) { + pixel = transparent; + } + }); + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: + return std::move(img_data); + default: + break; + } + + shape_info.Height /= 2; + + util::buffer_t cursor_img { shape_info.Width * shape_info.Height * 4 }; + + auto bytes = shape_info.Pitch * shape_info.Height; + auto pixel_begin = (std::uint32_t *)std::begin(cursor_img); + auto pixel_data = pixel_begin; + auto and_mask = std::begin(img_data); + auto xor_mask = std::begin(img_data) + bytes; + + for(auto x = 0; x < bytes; ++x) { + for(auto c = 7; c >= 0; --c) { + auto bit = 1 << c; + auto color_type = ((*and_mask & bit) ? 1 : 0) + ((*xor_mask & bit) ? 2 : 0); + + switch(color_type) { + case 0: //black + *pixel_data = black; + break; + case 2: //white + *pixel_data = white; + break; + case 1: //transparent + { + *pixel_data = transparent; + + break; + } + case 3: //inverse + { + auto top_p = pixel_data - shape_info.Width; + auto left_p = pixel_data - 1; + auto right_p = pixel_data + 1; + auto bottom_p = pixel_data + shape_info.Width; + + // Get the x coordinate of the pixel + auto column = (pixel_data - pixel_begin) % shape_info.Width != 0; + + if(top_p >= pixel_begin && *top_p == transparent) { + *top_p = black; + } + + if(column != 0 && left_p >= pixel_begin && *left_p == transparent) { + *left_p = black; + } + + if(bottom_p < (std::uint32_t *)std::end(cursor_img)) { + *bottom_p = black; + } + + if(column != shape_info.Width - 1) { + *right_p = black; + } + *pixel_data = white; + } + } + + ++pixel_data; + } + ++and_mask; + ++xor_mask; + } + + return cursor_img; +} + +blob_t compile_shader(LPCSTR file, LPCSTR entrypoint, LPCSTR shader_model) { + blob_t::pointer msg_p = nullptr; + blob_t::pointer compiled_p; + + DWORD flags = D3DCOMPILE_ENABLE_STRICTNESS; + +#ifndef NDEBUG + flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; +#endif + std::wstring_convert, wchar_t> converter; + + auto wFile = converter.from_bytes(file); + auto status = D3DCompileFromFile(wFile.c_str(), nullptr, nullptr, entrypoint, shader_model, flags, 0, &compiled_p, &msg_p); + + if(msg_p) { + BOOST_LOG(warning) << std::string_view { (const char *)msg_p->GetBufferPointer(), msg_p->GetBufferSize() - 1 }; + msg_p->Release(); + } + + if(status) { + BOOST_LOG(error) << "Couldn't compile ["sv << file << "] [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + return blob_t { compiled_p }; +} + +blob_t compile_pixel_shader(LPCSTR file) { + return compile_shader(file, "main_ps", "ps_5_0"); +} + +blob_t compile_vertex_shader(LPCSTR file) { + return compile_shader(file, "main_vs", "vs_5_0"); +} + +int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format, texture2d_t::pointer tex) { + D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_desc { + format, + D3D11_SRV_DIMENSION_TEXTURE2D + }; + shader_resource_desc.Texture2D.MipLevels = 1; + + auto status = device->CreateShaderResourceView(tex, &shader_resource_desc, &shader_res); + if(status) { + BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + D3D11_RENDER_TARGET_VIEW_DESC render_target_desc { + format, + D3D11_RTV_DIMENSION_TEXTURE2D + }; + + status = device->CreateRenderTargetView(tex, &render_target_desc, &render_target); + if(status) { + BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + return 0; +} + +int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format) { + D3D11_TEXTURE2D_DESC desc {}; + + desc.Width = width; + desc.Height = height; + desc.Format = format; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.SampleDesc.Count = 1; + + texture2d_t tex; + auto status = device->CreateTexture2D(&desc, nullptr, &tex); + if(status) { + BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + return init_rt(device, shader_res, render_target, width, height, format, tex.get()); +} + +class hwdevice_t : public platf::hwdevice_t { +public: + int convert(platf::img_t &img_base) override { + auto &img = (img_d3d_t &)img_base; + + device_ctx_p->IASetInputLayout(input_layout.get()); + + _init_view_port(this->img.width, this->img.height); + device_ctx_p->OMSetRenderTargets(1, &nv12_Y_rt, nullptr); + device_ctx_p->VSSetShader(scene_vs.get(), nullptr, 0); + device_ctx_p->PSSetShader(convert_Y_ps.get(), nullptr, 0); + device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res); + device_ctx_p->Draw(3, 0); + + device_ctx_p->RSSetViewports(1, &outY_view); + device_ctx_p->PSSetShaderResources(0, 1, &img.input_res); + device_ctx_p->Draw(3, 0); + + // Artifacts start appearing on the rendered image if Sunshine doesn't flush + // before rendering on the UV part of the image. + device_ctx_p->Flush(); + + _init_view_port(this->img.width / 2, this->img.height / 2); + device_ctx_p->OMSetRenderTargets(1, &nv12_UV_rt, nullptr); + device_ctx_p->VSSetShader(convert_UV_vs.get(), nullptr, 0); + device_ctx_p->PSSetShader(convert_UV_ps.get(), nullptr, 0); + device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res); + device_ctx_p->Draw(3, 0); + + device_ctx_p->RSSetViewports(1, &outUV_view); + device_ctx_p->PSSetShaderResources(0, 1, &img.input_res); + device_ctx_p->Draw(3, 0); + device_ctx_p->Flush(); + + return 0; + } + + void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override { + switch(colorspace) { + case 5: // SWS_CS_SMPTE170M + color_p = &::video::colors[0]; + break; + case 1: // SWS_CS_ITU709 + color_p = &::video::colors[2]; + break; + case 9: // SWS_CS_BT2020 + default: + BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv; + color_p = &::video::colors[0]; + }; + + if(color_range > 1) { + // Full range + ++color_p; + } + + auto color_matrix = make_buffer((device_t::pointer)data, *color_p); + if(!color_matrix) { + BOOST_LOG(warning) << "Failed to create color matrix"sv; + return; + } + + device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); + this->color_matrix = std::move(color_matrix); + } + + int set_frame(AVFrame *frame) { + this->hwframe.reset(frame); + this->frame = frame; + + auto device_p = (device_t::pointer)data; + + auto out_width = frame->width; + auto out_height = frame->height; + + float in_width = img.display->width; + float in_height = img.display->height; + + // // Ensure aspect ratio is maintained + auto scalar = std::fminf(out_width / in_width, out_height / in_height); + auto out_width_f = in_width * scalar; + auto out_height_f = in_height * scalar; + + // result is always positive + auto offsetX = (out_width - out_width_f) / 2; + auto offsetY = (out_height - out_height_f) / 2; + + outY_view = D3D11_VIEWPORT { offsetX, offsetY, out_width_f, out_height_f, 0.0f, 1.0f }; + outUV_view = D3D11_VIEWPORT { offsetX / 2, offsetY / 2, out_width_f / 2, out_height_f / 2, 0.0f, 1.0f }; + + D3D11_TEXTURE2D_DESC t {}; + t.Width = out_width; + t.Height = out_height; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = format; + t.BindFlags = D3D11_BIND_RENDER_TARGET; + + auto status = device_p->CreateTexture2D(&t, nullptr, &img.texture); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create render target texture [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + img.width = out_width; + img.height = out_height; + img.data = (std::uint8_t *)img.texture.get(); + img.row_pitch = out_width * 4; + img.pixel_pitch = 4; + + float info_in[16 / sizeof(float)] { 1.0f / (float)out_width }; //aligned to 16-byte + info_scene = make_buffer(device_p, info_in); + + if(!info_in) { + BOOST_LOG(error) << "Failed to create info scene buffer"sv; + return -1; + } + + D3D11_RENDER_TARGET_VIEW_DESC nv12_rt_desc { + DXGI_FORMAT_R8_UNORM, + D3D11_RTV_DIMENSION_TEXTURE2D + }; + + status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_Y_rt); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + nv12_rt_desc.Format = DXGI_FORMAT_R8G8_UNORM; + + status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_UV_rt); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + // Need to have something refcounted + if(!frame->buf[0]) { + frame->buf[0] = av_buffer_allocz(sizeof(AVD3D11FrameDescriptor)); + } + + auto desc = (AVD3D11FrameDescriptor *)frame->buf[0]->data; + desc->texture = (ID3D11Texture2D *)img.data; + desc->index = 0; + + frame->data[0] = img.data; + frame->data[1] = 0; + + frame->linesize[0] = img.row_pitch; + + frame->height = img.height; + frame->width = img.width; + + return 0; + } + + int init( + std::shared_ptr display, device_t::pointer device_p, device_ctx_t::pointer device_ctx_p, + pix_fmt_e pix_fmt) { + + HRESULT status; + + device_p->AddRef(); + data = device_p; + + this->device_ctx_p = device_ctx_p; + + format = (pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010); + status = device_p->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); + if(status) { + BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + status = device_p->CreatePixelShader(convert_Y_ps_hlsl->GetBufferPointer(), convert_Y_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps); + if(status) { + BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + status = device_p->CreatePixelShader(convert_UV_ps_hlsl->GetBufferPointer(), convert_UV_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps); + if(status) { + BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + status = device_p->CreateVertexShader(convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), nullptr, &convert_UV_vs); + if(status) { + BOOST_LOG(error) << "Failed to create convertUV vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + status = device_p->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps); + if(status) { + BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + color_matrix = make_buffer(device_p, ::video::colors[0]); + if(!color_matrix) { + BOOST_LOG(error) << "Failed to create color matrix buffer"sv; + return -1; + } + + D3D11_INPUT_ELEMENT_DESC layout_desc { + "SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 + }; + + status = device_p->CreateInputLayout( + &layout_desc, 1, + convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), + &input_layout); + + img.display = std::move(display); + + // Color the background black, so that the padding for keeping the aspect ratio + // is black + if(img.display->dummy_img(&back_img)) { + BOOST_LOG(warning) << "Couldn't create an image to set background color to black"sv; + return -1; + } + + D3D11_SHADER_RESOURCE_VIEW_DESC desc { + DXGI_FORMAT_B8G8R8A8_UNORM, + D3D11_SRV_DIMENSION_TEXTURE2D + }; + desc.Texture2D.MipLevels = 1; + + status = device_p->CreateShaderResourceView(back_img.texture.get(), &desc, &back_img.input_res); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create input shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + device_ctx_p->IASetInputLayout(input_layout.get()); + device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); + device_ctx_p->VSSetConstantBuffers(0, 1, &info_scene); + + return 0; + } + + ~hwdevice_t() override { + if(data) { + ((ID3D11Device *)data)->Release(); + } + } + +private: + void _init_view_port(float x, float y, float width, float height) { + D3D11_VIEWPORT view { + x, y, + width, height, + 0.0f, 1.0f + }; + + device_ctx_p->RSSetViewports(1, &view); + } + + void _init_view_port(float width, float height) { + _init_view_port(0.0f, 0.0f, width, height); + } + +public: + frame_t hwframe; + + ::video::color_t *color_p; + + buf_t info_scene; + buf_t color_matrix; + + input_layout_t input_layout; + + render_target_t nv12_Y_rt; + render_target_t nv12_UV_rt; + + // The image referenced by hwframe + // The resulting image is stored here. + img_d3d_t img; + + // Clear nv12 render target to black + img_d3d_t back_img; + + vs_t convert_UV_vs; + ps_t convert_UV_ps; + ps_t convert_Y_ps; + ps_t scene_ps; + vs_t scene_vs; + + D3D11_VIEWPORT outY_view; + D3D11_VIEWPORT outUV_view; + + DXGI_FORMAT format; + + device_ctx_t::pointer device_ctx_p; +}; + +capture_e display_vram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { + auto next_frame = std::chrono::steady_clock::now(); + + while(img) { + auto now = std::chrono::steady_clock::now(); + while(next_frame > now) { + now = std::chrono::steady_clock::now(); + } + next_frame = now + delay; + + auto status = snapshot(img.get(), 1000ms, *cursor); + switch(status) { + case platf::capture_e::reinit: + case platf::capture_e::error: + return status; + case platf::capture_e::timeout: + std::this_thread::sleep_for(1ms); + continue; + case platf::capture_e::ok: + img = snapshot_cb(img); + break; + default: + BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; + return status; + } + } + + return capture_e::ok; +} + +capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { + auto img = (img_d3d_t *)img_base; + + HRESULT status; + + DXGI_OUTDUPL_FRAME_INFO frame_info; + + resource_t::pointer res_p {}; + auto capture_status = dup.next_frame(frame_info, timeout, &res_p); + resource_t res { res_p }; + + if(capture_status != capture_e::ok) { + return capture_status; + } + + const bool mouse_update_flag = frame_info.LastMouseUpdateTime.QuadPart != 0 || frame_info.PointerShapeBufferSize > 0; + const bool frame_update_flag = frame_info.AccumulatedFrames != 0 || frame_info.LastPresentTime.QuadPart != 0; + const bool update_flag = mouse_update_flag || frame_update_flag; + + if(!update_flag) { + return capture_e::timeout; + } + + if(frame_info.PointerShapeBufferSize > 0) { + DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info {}; + + util::buffer_t img_data { frame_info.PointerShapeBufferSize }; + + UINT dummy; + status = dup.dup->GetFramePointerShape(img_data.size(), std::begin(img_data), &dummy, &shape_info); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; + + return capture_e::error; + } + + auto cursor_img = make_cursor_image(std::move(img_data), shape_info); + + D3D11_SUBRESOURCE_DATA data { + std::begin(cursor_img), + 4 * shape_info.Width, + 0 + }; + + // Create texture for cursor + D3D11_TEXTURE2D_DESC t {}; + t.Width = shape_info.Width; + t.Height = cursor_img.size() / data.SysMemPitch; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + t.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + texture2d_t texture; + auto status = device->CreateTexture2D(&t, &data, &texture); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create mouse texture [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + D3D11_SHADER_RESOURCE_VIEW_DESC desc { + DXGI_FORMAT_B8G8R8A8_UNORM, + D3D11_SRV_DIMENSION_TEXTURE2D + }; + desc.Texture2D.MipLevels = 1; + + // Free resources before allocating on the next line. + cursor.input_res.reset(); + status = device->CreateShaderResourceView(texture.get(), &desc, &cursor.input_res); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create cursor shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + cursor.set_texture(t.Width, t.Height, std::move(texture)); + } + + if(frame_info.LastMouseUpdateTime.QuadPart) { + cursor.set_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, frame_info.PointerPosition.Visible && cursor_visible); + } + + if(frame_update_flag) { + src.reset(); + status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + } + + device_ctx->CopyResource(img->texture.get(), src.get()); + if(cursor.visible) { + D3D11_VIEWPORT view { + 0.0f, 0.0f, + (float)width, (float)height, + 0.0f, 1.0f + }; + + device_ctx->VSSetShader(scene_vs.get(), nullptr, 0); + device_ctx->PSSetShader(scene_ps.get(), nullptr, 0); + device_ctx->RSSetViewports(1, &view); + device_ctx->OMSetRenderTargets(1, &img->scene_rt, nullptr); + device_ctx->PSSetShaderResources(0, 1, &cursor.input_res); + device_ctx->OMSetBlendState(blend_enable.get(), nullptr, 0xFFFFFFFFu); + device_ctx->RSSetViewports(1, &cursor.cursor_view); + device_ctx->Draw(3, 0); + device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); + } + + return capture_e::ok; +} + +int display_vram_t::init(int framerate, const std::string &display_name) { + if(display_base_t::init(framerate, display_name)) { + return -1; + } + + D3D11_SAMPLER_DESC sampler_desc {}; + sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; + sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; + sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; + sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER; + sampler_desc.MinLOD = 0; + sampler_desc.MaxLOD = D3D11_FLOAT32_MAX; + + auto status = device->CreateSamplerState(&sampler_desc, &sampler_linear); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create point sampler state [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + status = device->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); + if(status) { + BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + status = device->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps); + if(status) { + BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + blend_enable = make_blend(device.get(), true); + blend_disable = make_blend(device.get(), false); + + if(!blend_disable || !blend_enable) { + return -1; + } + + device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); + device_ctx->PSSetSamplers(0, 1, &sampler_linear); + device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + + return 0; +} + +std::shared_ptr display_vram_t::alloc_img() { + auto img = std::make_shared(); + + img->pixel_pitch = 4; + img->row_pitch = img->pixel_pitch * width; + img->width = width; + img->height = height; + img->display = shared_from_this(); + + auto dummy_data = std::make_unique(img->row_pitch * height); + D3D11_SUBRESOURCE_DATA data { + dummy_data.get(), + (UINT)img->row_pitch + }; + std::fill_n(dummy_data.get(), img->row_pitch * height, 0); + + D3D11_TEXTURE2D_DESC t {}; + t.Width = width; + t.Height = height; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = format; + t.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + + auto status = device->CreateTexture2D(&t, &data, &img->texture); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create img buf texture [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + if(init_rt(device.get(), img->input_res, img->scene_rt, width, height, format, img->texture.get())) { + return nullptr; + } + + img->data = (std::uint8_t *)img->texture.get(); + + return img; +} + +int display_vram_t::dummy_img(platf::img_t *img_base) { + auto img = (img_d3d_t *)img_base; + + if(img->texture) { + return 0; + } + + img->row_pitch = width * 4; + auto dummy_data = std::make_unique(width * height); + D3D11_SUBRESOURCE_DATA data { + dummy_data.get(), + (UINT)img->row_pitch + }; + std::fill_n(dummy_data.get(), width * height, 0); + + D3D11_TEXTURE2D_DESC t {}; + t.Width = width; + t.Height = height; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = format; + t.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + dxgi::texture2d_t tex; + auto status = device->CreateTexture2D(&t, &data, &tex); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create dummy texture [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + img->texture = std::move(tex); + img->data = (std::uint8_t *)img->texture.get(); + + return 0; +} + +std::shared_ptr display_vram_t::make_hwdevice(pix_fmt_e pix_fmt) { + if(pix_fmt != platf::pix_fmt_e::nv12) { + BOOST_LOG(error) << "display_vram_t doesn't support pixel format ["sv << from_pix_fmt(pix_fmt) << ']'; + + return nullptr; + } + + auto hwdevice = std::make_shared(); + + auto ret = hwdevice->init( + shared_from_this(), + device.get(), + device_ctx.get(), + pix_fmt); + + if(ret) { + return nullptr; + } + + return hwdevice; +} + +int init() { + BOOST_LOG(info) << "Compiling shaders..."sv; + scene_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/SceneVS.hlsl"); + if(!scene_vs_hlsl) { + return -1; + } + + convert_Y_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertYPS.hlsl"); + if(!convert_Y_ps_hlsl) { + return -1; + } + + convert_UV_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertUVPS.hlsl"); + if(!convert_UV_ps_hlsl) { + return -1; + } + + convert_UV_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/ConvertUVVS.hlsl"); + if(!convert_UV_vs_hlsl) { + return -1; + } + + scene_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ScenePS.hlsl"); + if(!scene_ps_hlsl) { + return -1; + } + BOOST_LOG(info) << "Compiled shaders"sv; + + return 0; +} } // namespace platf::dxgi \ No newline at end of file diff --git a/sunshine/platform/windows/input.cpp b/src/platform/windows/input.cpp old mode 100755 new mode 100644 similarity index 99% rename from sunshine/platform/windows/input.cpp rename to src/platform/windows/input.cpp index 88183f4b..9ac01a26 --- a/sunshine/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -5,9 +5,9 @@ #include #include "misc.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" +#include "src/config.h" +#include "src/main.h" +#include "src/platform/common.h" namespace platf { using namespace std::literals; diff --git a/sunshine/platform/windows/misc.cpp b/src/platform/windows/misc.cpp similarity index 95% rename from sunshine/platform/windows/misc.cpp rename to src/platform/windows/misc.cpp index 828e0374..b9d08543 100644 --- a/sunshine/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -1,123 +1,123 @@ -#include -#include -#include - - -// prevent clang format from "optimizing" the header include order -// clang-format off -#include -#include -#include -#include -#include -// clang-format on - -#include "sunshine/main.h" -#include "sunshine/utility.h" - -using namespace std::literals; -namespace platf { -using adapteraddrs_t = util::c_ptr; - -std::filesystem::path appdata() { - return L"."sv; -} - -std::string from_sockaddr(const sockaddr *const socket_address) { - char data[INET6_ADDRSTRLEN]; - - auto family = socket_address->sa_family; - if(family == AF_INET6) { - inet_ntop(AF_INET6, &((sockaddr_in6 *)socket_address)->sin6_addr, data, INET6_ADDRSTRLEN); - } - - if(family == AF_INET) { - inet_ntop(AF_INET, &((sockaddr_in *)socket_address)->sin_addr, data, INET_ADDRSTRLEN); - } - - return std::string { data }; -} - -std::pair from_sockaddr_ex(const sockaddr *const ip_addr) { - char data[INET6_ADDRSTRLEN]; - - auto family = ip_addr->sa_family; - std::uint16_t port; - if(family == AF_INET6) { - inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN); - port = ((sockaddr_in6 *)ip_addr)->sin6_port; - } - - if(family == AF_INET) { - inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data, INET_ADDRSTRLEN); - port = ((sockaddr_in *)ip_addr)->sin_port; - } - - return { port, std::string { data } }; -} - -adapteraddrs_t get_adapteraddrs() { - adapteraddrs_t info { nullptr }; - ULONG size = 0; - - while(GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, info.get(), &size) == ERROR_BUFFER_OVERFLOW) { - info.reset((PIP_ADAPTER_ADDRESSES)malloc(size)); - } - - return info; -} - -std::string get_mac_address(const std::string_view &address) { - adapteraddrs_t info = get_adapteraddrs(); - for(auto adapter_pos = info.get(); adapter_pos != nullptr; adapter_pos = adapter_pos->Next) { - for(auto addr_pos = adapter_pos->FirstUnicastAddress; addr_pos != nullptr; addr_pos = addr_pos->Next) { - if(adapter_pos->PhysicalAddressLength != 0 && address == from_sockaddr(addr_pos->Address.lpSockaddr)) { - std::stringstream mac_addr; - mac_addr << std::hex; - for(int i = 0; i < adapter_pos->PhysicalAddressLength; i++) { - if(i > 0) { - mac_addr << ':'; - } - mac_addr << std::setw(2) << std::setfill('0') << (int)adapter_pos->PhysicalAddress[i]; - } - return mac_addr.str(); - } - } - } - BOOST_LOG(warning) << "Unable to find MAC address for "sv << address; - return "00:00:00:00:00:00"s; -} - -HDESK syncThreadDesktop() { - auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL); - if(!hDesk) { - auto err = GetLastError(); - BOOST_LOG(error) << "Failed to Open Input Desktop [0x"sv << util::hex(err).to_string_view() << ']'; - - return nullptr; - } - - if(!SetThreadDesktop(hDesk)) { - auto err = GetLastError(); - BOOST_LOG(error) << "Failed to sync desktop to thread [0x"sv << util::hex(err).to_string_view() << ']'; - } - - CloseDesktop(hDesk); - - return hDesk; -} - -void print_status(const std::string_view &prefix, HRESULT status) { - char err_string[1024]; - - DWORD bytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, - status, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - err_string, - sizeof(err_string), - nullptr); - - BOOST_LOG(error) << prefix << ": "sv << std::string_view { err_string, bytes }; -} +#include +#include +#include + + +// prevent clang format from "optimizing" the header include order +// clang-format off +#include +#include +#include +#include +#include +// clang-format on + +#include "src/main.h" +#include "src/utility.h" + +using namespace std::literals; +namespace platf { +using adapteraddrs_t = util::c_ptr; + +std::filesystem::path appdata() { + return L"."sv; +} + +std::string from_sockaddr(const sockaddr *const socket_address) { + char data[INET6_ADDRSTRLEN]; + + auto family = socket_address->sa_family; + if(family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6 *)socket_address)->sin6_addr, data, INET6_ADDRSTRLEN); + } + + if(family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in *)socket_address)->sin_addr, data, INET_ADDRSTRLEN); + } + + return std::string { data }; +} + +std::pair from_sockaddr_ex(const sockaddr *const ip_addr) { + char data[INET6_ADDRSTRLEN]; + + auto family = ip_addr->sa_family; + std::uint16_t port; + if(family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN); + port = ((sockaddr_in6 *)ip_addr)->sin6_port; + } + + if(family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data, INET_ADDRSTRLEN); + port = ((sockaddr_in *)ip_addr)->sin_port; + } + + return { port, std::string { data } }; +} + +adapteraddrs_t get_adapteraddrs() { + adapteraddrs_t info { nullptr }; + ULONG size = 0; + + while(GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, info.get(), &size) == ERROR_BUFFER_OVERFLOW) { + info.reset((PIP_ADAPTER_ADDRESSES)malloc(size)); + } + + return info; +} + +std::string get_mac_address(const std::string_view &address) { + adapteraddrs_t info = get_adapteraddrs(); + for(auto adapter_pos = info.get(); adapter_pos != nullptr; adapter_pos = adapter_pos->Next) { + for(auto addr_pos = adapter_pos->FirstUnicastAddress; addr_pos != nullptr; addr_pos = addr_pos->Next) { + if(adapter_pos->PhysicalAddressLength != 0 && address == from_sockaddr(addr_pos->Address.lpSockaddr)) { + std::stringstream mac_addr; + mac_addr << std::hex; + for(int i = 0; i < adapter_pos->PhysicalAddressLength; i++) { + if(i > 0) { + mac_addr << ':'; + } + mac_addr << std::setw(2) << std::setfill('0') << (int)adapter_pos->PhysicalAddress[i]; + } + return mac_addr.str(); + } + } + } + BOOST_LOG(warning) << "Unable to find MAC address for "sv << address; + return "00:00:00:00:00:00"s; +} + +HDESK syncThreadDesktop() { + auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL); + if(!hDesk) { + auto err = GetLastError(); + BOOST_LOG(error) << "Failed to Open Input Desktop [0x"sv << util::hex(err).to_string_view() << ']'; + + return nullptr; + } + + if(!SetThreadDesktop(hDesk)) { + auto err = GetLastError(); + BOOST_LOG(error) << "Failed to sync desktop to thread [0x"sv << util::hex(err).to_string_view() << ']'; + } + + CloseDesktop(hDesk); + + return hDesk; +} + +void print_status(const std::string_view &prefix, HRESULT status) { + char err_string[1024]; + + DWORD bytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + status, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + err_string, + sizeof(err_string), + nullptr); + + BOOST_LOG(error) << prefix << ": "sv << std::string_view { err_string, bytes }; +} } // namespace platf \ No newline at end of file diff --git a/sunshine/platform/windows/misc.h b/src/platform/windows/misc.h similarity index 95% rename from sunshine/platform/windows/misc.h rename to src/platform/windows/misc.h index 4cd8791f..a045d23b 100644 --- a/sunshine/platform/windows/misc.h +++ b/src/platform/windows/misc.h @@ -1,13 +1,13 @@ -#ifndef SUNSHINE_WINDOWS_MISC_H -#define SUNSHINE_WINDOWS_MISC_H - -#include -#include -#include - -namespace platf { -void print_status(const std::string_view &prefix, HRESULT status); -HDESK syncThreadDesktop(); -} // namespace platf - +#ifndef SUNSHINE_WINDOWS_MISC_H +#define SUNSHINE_WINDOWS_MISC_H + +#include +#include +#include + +namespace platf { +void print_status(const std::string_view &prefix, HRESULT status); +HDESK syncThreadDesktop(); +} // namespace platf + #endif \ No newline at end of file diff --git a/sunshine/platform/windows/publish.cpp b/src/platform/windows/publish.cpp similarity index 92% rename from sunshine/platform/windows/publish.cpp rename to src/platform/windows/publish.cpp index 7fdef3fc..92673e1d 100644 --- a/sunshine/platform/windows/publish.cpp +++ b/src/platform/windows/publish.cpp @@ -1,195 +1,195 @@ -#include - -#include - -#include -#include - -#include - -#include "misc.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/network.h" -#include "sunshine/nvhttp.h" -#include "sunshine/platform/common.h" -#include "sunshine/thread_safe.h" - -#define _FN(x, ret, args) \ - typedef ret(*x##_fn) args; \ - static x##_fn x - -using namespace std::literals; - -#define __SV(quote) L##quote##sv -#define SV(quote) __SV(quote) - -extern "C" { -#ifndef __MINGW32__ -constexpr auto DNS_REQUEST_PENDING = 9506L; -constexpr auto DNS_QUERY_REQUEST_VERSION1 = 0x1; -constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1; -#endif - -#define SERVICE_DOMAIN "local" - -constexpr auto SERVICE_INSTANCE_NAME = SV(SERVICE_NAME "." SERVICE_TYPE "." SERVICE_DOMAIN); -constexpr auto SERVICE_TYPE_DOMAIN = SV(SERVICE_TYPE "." SERVICE_DOMAIN); - -#ifndef __MINGW32__ -typedef struct _DNS_SERVICE_INSTANCE { - LPWSTR pszInstanceName; - LPWSTR pszHostName; - - IP4_ADDRESS *ip4Address; - IP6_ADDRESS *ip6Address; - - WORD wPort; - WORD wPriority; - WORD wWeight; - - // Property list - DWORD dwPropertyCount; - - PWSTR *keys; - PWSTR *values; - - DWORD dwInterfaceIndex; -} DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE; -#endif - -typedef VOID WINAPI DNS_SERVICE_REGISTER_COMPLETE( - _In_ DWORD Status, - _In_ PVOID pQueryContext, - _In_ PDNS_SERVICE_INSTANCE pInstance); - -typedef DNS_SERVICE_REGISTER_COMPLETE *PDNS_SERVICE_REGISTER_COMPLETE; - -#ifndef __MINGW32__ -typedef struct _DNS_SERVICE_CANCEL { - PVOID reserved; -} DNS_SERVICE_CANCEL, *PDNS_SERVICE_CANCEL; - -typedef struct _DNS_SERVICE_REGISTER_REQUEST { - ULONG Version; - ULONG InterfaceIndex; - PDNS_SERVICE_INSTANCE pServiceInstance; - PDNS_SERVICE_REGISTER_COMPLETE pRegisterCompletionCallback; - PVOID pQueryContext; - HANDLE hCredentials; - BOOL unicastEnabled; -} DNS_SERVICE_REGISTER_REQUEST, *PDNS_SERVICE_REGISTER_REQUEST; -#endif - -_FN(_DnsServiceFreeInstance, VOID, (_In_ PDNS_SERVICE_INSTANCE pInstance)); -_FN(_DnsServiceDeRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel)); -_FN(_DnsServiceRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel)); -} /* extern "C" */ - -namespace platf::publish { -VOID WINAPI register_cb(DWORD status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) { - auto alarm = (safe::alarm_t::element_type *)pQueryContext; - - auto fg = util::fail_guard([&]() { - if(pInstance) { - _DnsServiceFreeInstance(pInstance); - } - }); - - if(status) { - print_status("register_cb()"sv, status); - alarm->ring(-1); - - return; - } - - alarm->ring(0); -} - -static int service(bool enable) { - auto alarm = safe::make_alarm(); - - std::wstring_convert, wchar_t> converter; - - std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() }; - std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() }; - - auto host = converter.from_bytes(boost::asio::ip::host_name() + ".local"); - - DNS_SERVICE_INSTANCE instance {}; - instance.pszInstanceName = name.data(); - instance.wPort = map_port(nvhttp::PORT_HTTP); - instance.pszHostName = host.data(); - - DNS_SERVICE_REGISTER_REQUEST req {}; - req.Version = DNS_QUERY_REQUEST_VERSION1; - req.pQueryContext = alarm.get(); - req.pServiceInstance = &instance; - req.pRegisterCompletionCallback = register_cb; - - DNS_STATUS status {}; - - if(enable) { - status = _DnsServiceRegister(&req, nullptr); - } - else { - status = _DnsServiceDeRegister(&req, nullptr); - } - - alarm->wait(); - - status = *alarm->status(); - if(status) { - BOOST_LOG(error) << "No mDNS service"sv; - return -1; - } - - return 0; -} - -class deinit_t : public ::platf::deinit_t { -public: - ~deinit_t() override { - if(service(false)) { - std::abort(); - } - - BOOST_LOG(info) << "Unregistered Sunshine Gamestream service"sv; - } -}; - -int load_funcs(HMODULE handle) { - auto fg = util::fail_guard([handle]() { - FreeLibrary(handle); - }); - - _DnsServiceFreeInstance = (_DnsServiceFreeInstance_fn)GetProcAddress(handle, "DnsServiceFreeInstance"); - _DnsServiceDeRegister = (_DnsServiceDeRegister_fn)GetProcAddress(handle, "DnsServiceDeRegister"); - _DnsServiceRegister = (_DnsServiceRegister_fn)GetProcAddress(handle, "DnsServiceRegister"); - - if(!(_DnsServiceFreeInstance && _DnsServiceDeRegister && _DnsServiceRegister)) { - BOOST_LOG(error) << "mDNS service not available in dnsapi.dll"sv; - return -1; - } - - fg.disable(); - return 0; -} - -std::unique_ptr<::platf::deinit_t> start() { - HMODULE handle = LoadLibrary("dnsapi.dll"); - - if(!handle || load_funcs(handle)) { - BOOST_LOG(error) << "Couldn't load dnsapi.dll, You'll need to add PC manually from Moonlight"sv; - return nullptr; - } - - if(service(true)) { - return nullptr; - } - - BOOST_LOG(info) << "Registered Sunshine Gamestream service"sv; - - return std::make_unique(); -} -} // namespace platf::publish +#include + +#include + +#include +#include + +#include + +#include "misc.h" +#include "src/config.h" +#include "src/main.h" +#include "src/network.h" +#include "src/nvhttp.h" +#include "src/platform/common.h" +#include "src/thread_safe.h" + +#define _FN(x, ret, args) \ + typedef ret(*x##_fn) args; \ + static x##_fn x + +using namespace std::literals; + +#define __SV(quote) L##quote##sv +#define SV(quote) __SV(quote) + +extern "C" { +#ifndef __MINGW32__ +constexpr auto DNS_REQUEST_PENDING = 9506L; +constexpr auto DNS_QUERY_REQUEST_VERSION1 = 0x1; +constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1; +#endif + +#define SERVICE_DOMAIN "local" + +constexpr auto SERVICE_INSTANCE_NAME = SV(SERVICE_NAME "." SERVICE_TYPE "." SERVICE_DOMAIN); +constexpr auto SERVICE_TYPE_DOMAIN = SV(SERVICE_TYPE "." SERVICE_DOMAIN); + +#ifndef __MINGW32__ +typedef struct _DNS_SERVICE_INSTANCE { + LPWSTR pszInstanceName; + LPWSTR pszHostName; + + IP4_ADDRESS *ip4Address; + IP6_ADDRESS *ip6Address; + + WORD wPort; + WORD wPriority; + WORD wWeight; + + // Property list + DWORD dwPropertyCount; + + PWSTR *keys; + PWSTR *values; + + DWORD dwInterfaceIndex; +} DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE; +#endif + +typedef VOID WINAPI DNS_SERVICE_REGISTER_COMPLETE( + _In_ DWORD Status, + _In_ PVOID pQueryContext, + _In_ PDNS_SERVICE_INSTANCE pInstance); + +typedef DNS_SERVICE_REGISTER_COMPLETE *PDNS_SERVICE_REGISTER_COMPLETE; + +#ifndef __MINGW32__ +typedef struct _DNS_SERVICE_CANCEL { + PVOID reserved; +} DNS_SERVICE_CANCEL, *PDNS_SERVICE_CANCEL; + +typedef struct _DNS_SERVICE_REGISTER_REQUEST { + ULONG Version; + ULONG InterfaceIndex; + PDNS_SERVICE_INSTANCE pServiceInstance; + PDNS_SERVICE_REGISTER_COMPLETE pRegisterCompletionCallback; + PVOID pQueryContext; + HANDLE hCredentials; + BOOL unicastEnabled; +} DNS_SERVICE_REGISTER_REQUEST, *PDNS_SERVICE_REGISTER_REQUEST; +#endif + +_FN(_DnsServiceFreeInstance, VOID, (_In_ PDNS_SERVICE_INSTANCE pInstance)); +_FN(_DnsServiceDeRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel)); +_FN(_DnsServiceRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel)); +} /* extern "C" */ + +namespace platf::publish { +VOID WINAPI register_cb(DWORD status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) { + auto alarm = (safe::alarm_t::element_type *)pQueryContext; + + auto fg = util::fail_guard([&]() { + if(pInstance) { + _DnsServiceFreeInstance(pInstance); + } + }); + + if(status) { + print_status("register_cb()"sv, status); + alarm->ring(-1); + + return; + } + + alarm->ring(0); +} + +static int service(bool enable) { + auto alarm = safe::make_alarm(); + + std::wstring_convert, wchar_t> converter; + + std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() }; + std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() }; + + auto host = converter.from_bytes(boost::asio::ip::host_name() + ".local"); + + DNS_SERVICE_INSTANCE instance {}; + instance.pszInstanceName = name.data(); + instance.wPort = map_port(nvhttp::PORT_HTTP); + instance.pszHostName = host.data(); + + DNS_SERVICE_REGISTER_REQUEST req {}; + req.Version = DNS_QUERY_REQUEST_VERSION1; + req.pQueryContext = alarm.get(); + req.pServiceInstance = &instance; + req.pRegisterCompletionCallback = register_cb; + + DNS_STATUS status {}; + + if(enable) { + status = _DnsServiceRegister(&req, nullptr); + } + else { + status = _DnsServiceDeRegister(&req, nullptr); + } + + alarm->wait(); + + status = *alarm->status(); + if(status) { + BOOST_LOG(error) << "No mDNS service"sv; + return -1; + } + + return 0; +} + +class deinit_t : public ::platf::deinit_t { +public: + ~deinit_t() override { + if(service(false)) { + std::abort(); + } + + BOOST_LOG(info) << "Unregistered Sunshine Gamestream service"sv; + } +}; + +int load_funcs(HMODULE handle) { + auto fg = util::fail_guard([handle]() { + FreeLibrary(handle); + }); + + _DnsServiceFreeInstance = (_DnsServiceFreeInstance_fn)GetProcAddress(handle, "DnsServiceFreeInstance"); + _DnsServiceDeRegister = (_DnsServiceDeRegister_fn)GetProcAddress(handle, "DnsServiceDeRegister"); + _DnsServiceRegister = (_DnsServiceRegister_fn)GetProcAddress(handle, "DnsServiceRegister"); + + if(!(_DnsServiceFreeInstance && _DnsServiceDeRegister && _DnsServiceRegister)) { + BOOST_LOG(error) << "mDNS service not available in dnsapi.dll"sv; + return -1; + } + + fg.disable(); + return 0; +} + +std::unique_ptr<::platf::deinit_t> start() { + HMODULE handle = LoadLibrary("dnsapi.dll"); + + if(!handle || load_funcs(handle)) { + BOOST_LOG(error) << "Couldn't load dnsapi.dll, You'll need to add PC manually from Moonlight"sv; + return nullptr; + } + + if(service(true)) { + return nullptr; + } + + BOOST_LOG(info) << "Registered Sunshine Gamestream service"sv; + + return std::make_unique(); +} +} // namespace platf::publish diff --git a/sunshine/platform/windows/windows.rs.in b/src/platform/windows/windows.rs.in similarity index 100% rename from sunshine/platform/windows/windows.rs.in rename to src/platform/windows/windows.rs.in diff --git a/sunshine/process.cpp b/src/process.cpp similarity index 100% rename from sunshine/process.cpp rename to src/process.cpp diff --git a/sunshine/process.h b/src/process.h similarity index 100% rename from sunshine/process.h rename to src/process.h diff --git a/sunshine/round_robin.h b/src/round_robin.h similarity index 100% rename from sunshine/round_robin.h rename to src/round_robin.h diff --git a/sunshine/rtsp.cpp b/src/rtsp.cpp similarity index 100% rename from sunshine/rtsp.cpp rename to src/rtsp.cpp diff --git a/sunshine/rtsp.h b/src/rtsp.h similarity index 100% rename from sunshine/rtsp.h rename to src/rtsp.h diff --git a/sunshine/stream.cpp b/src/stream.cpp similarity index 100% rename from sunshine/stream.cpp rename to src/stream.cpp diff --git a/sunshine/stream.h b/src/stream.h similarity index 100% rename from sunshine/stream.h rename to src/stream.h diff --git a/sunshine/sync.h b/src/sync.h similarity index 100% rename from sunshine/sync.h rename to src/sync.h diff --git a/sunshine/task_pool.h b/src/task_pool.h similarity index 100% rename from sunshine/task_pool.h rename to src/task_pool.h diff --git a/sunshine/thread_pool.h b/src/thread_pool.h similarity index 100% rename from sunshine/thread_pool.h rename to src/thread_pool.h diff --git a/sunshine/thread_safe.h b/src/thread_safe.h similarity index 100% rename from sunshine/thread_safe.h rename to src/thread_safe.h diff --git a/sunshine/upnp.cpp b/src/upnp.cpp similarity index 100% rename from sunshine/upnp.cpp rename to src/upnp.cpp diff --git a/sunshine/upnp.h b/src/upnp.h similarity index 100% rename from sunshine/upnp.h rename to src/upnp.h diff --git a/sunshine/utility.h b/src/utility.h similarity index 100% rename from sunshine/utility.h rename to src/utility.h diff --git a/sunshine/uuid.h b/src/uuid.h similarity index 100% rename from sunshine/uuid.h rename to src/uuid.h diff --git a/sunshine/video.cpp b/src/video.cpp similarity index 100% rename from sunshine/video.cpp rename to src/video.cpp diff --git a/sunshine/video.h b/src/video.h similarity index 100% rename from sunshine/video.h rename to src/video.h diff --git a/tools/audio.cpp b/tools/audio.cpp index a130d384..592170b7 100644 --- a/tools/audio.cpp +++ b/tools/audio.cpp @@ -14,7 +14,7 @@ #include -#include "sunshine/utility.h" +#include "src/utility.h" DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING diff --git a/tools/dxgi.cpp b/tools/dxgi.cpp index 9c5f7307..7ecaaa2f 100644 --- a/tools/dxgi.cpp +++ b/tools/dxgi.cpp @@ -7,7 +7,7 @@ #include -#include "sunshine/utility.h" +#include "src/utility.h" using namespace std::literals; namespace dxgi { From 9f87401173c489ba5331fa6e50caddff705fd755 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 8 Aug 2022 22:40:01 -0400 Subject: [PATCH 351/817] fix source directory --- scripts/_locale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/_locale.py b/scripts/_locale.py index b8274aed..d967974e 100644 --- a/scripts/_locale.py +++ b/scripts/_locale.py @@ -18,7 +18,7 @@ script_dir = os.path.dirname(os.path.abspath(__file__)) root_dir = os.path.dirname(script_dir) locale_dir = os.path.join(root_dir, 'locale') -project_dir = os.path.join(root_dir, project_name.lower()) +project_dir = os.path.join(root_dir, 'src') year = datetime.datetime.now().year From db2823993972f04bcfd80d2e5b2b50a28b65cf7f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 8 Aug 2022 22:40:13 -0400 Subject: [PATCH 352/817] v0.14.1 --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1844e355..272709ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [0.14.1] - 2022-08-09 +### Added +- (Linux) Flatpak package added +- (Linux) AUR package automated updates +- (Windows) Winget package automated updates +### Changed +- (General) Moved repo to @LizardByte GitHub org +- (WebUI) Fixed button spacing on home page +- (WebUI) Added Discord WidgetBot Crate +### 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 - (Documentation) Added Sphinx documentation available at https://sunshinestream.readthedocs.io/en/latest/ From a6b8371178eb0c61617020e009d1897c3d9574e0 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 9 Aug 2022 17:20:46 -0400 Subject: [PATCH 353/817] v0.14.1 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b5d28e7..d3e36294 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.0) -project(Sunshine VERSION 0.14.0 +project(Sunshine VERSION 0.14.1 DESCRIPTION "Sunshine is a Gamestream host for Moonlight." HOMEPAGE_URL "https://app.lizardbyte.dev" ) From 911035c71142b9947415f8506020d5dfadcd1798 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 12 Aug 2022 15:25:56 -0400 Subject: [PATCH 354/817] use org level PR template --- .github/pull_request_template.md | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index 6c3bab87..00000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,22 +0,0 @@ -## Description - - -### Screenshot - - -### Issues Fixed or Closed - -- Fixes #(issue) - -## Type of Change - -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - -## Checklist - -- [ ] My code follows the style guidelines of this project -- [ ] I have performed a self-review of my own code -- [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] I have added or updated the docstring/documentation-blocks for new or existing methods/components From 30c178baa1ceb7e94cb7d259a83de8e0adf9b009 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 12 Aug 2022 16:00:19 -0400 Subject: [PATCH 355/817] move default configs to assets directory --- .github/workflows/CI.yml | 11 +++----- CMakeLists.txt | 25 ----------------- packaging/linux/aur/PKGBUILD | 1 - .../linux/flatpak/dev.lizardbyte.sunshine.yml | 1 - packaging/macos/Portfile | 27 +------------------ src/config.cpp | 4 +-- .../common/{config => assets}/sunshine.conf | 0 src_assets/linux/{config => assets}/apps.json | 0 src_assets/macos/{config => assets}/apps.json | 0 .../windows/{config => assets}/apps.json | 0 10 files changed, 6 insertions(+), 63 deletions(-) rename src_assets/common/{config => assets}/sunshine.conf (100%) rename src_assets/linux/{config => assets}/apps.json (100%) rename src_assets/macos/{config => assets}/apps.json (100%) rename src_assets/windows/{config => assets}/apps.json (100%) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index c9e6b0df..a036ecf4 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -255,12 +255,10 @@ jobs: - type: cpack CMAKE_INSTALL_PREFIX: '/usr' SUNSHINE_ASSETS_DIR: 'local/sunshine/assets' - SUNSHINE_CONFIG_DIR: 'local/sunshine/config' EXTRA_ARGS: '' - type: appimage CMAKE_INSTALL_PREFIX: '/usr' SUNSHINE_ASSETS_DIR: 'sunshine.AppImage.config' - SUNSHINE_CONFIG_DIR: 'sunshine.AppImage.home' EXTRA_ARGS: '-DSUNSHINE_CONFIGURE_APPIMAGE=ON' steps: @@ -345,7 +343,6 @@ jobs: cmake -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=${{ matrix.CMAKE_INSTALL_PREFIX }} \ -DSUNSHINE_ASSETS_DIR=${{ matrix.SUNSHINE_ASSETS_DIR }} \ - -DSUNSHINE_CONFIG_DIR=${{ matrix.SUNSHINE_CONFIG_DIR }} \ -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ -DSUNSHINE_ENABLE_WAYLAND=ON \ -DSUNSHINE_ENABLE_X11=ON \ @@ -383,9 +380,9 @@ jobs: # portable home and config # todo - this is ugly... we should use a custom AppRun script to take care of this mv ./AppDir${{ matrix.CMAKE_INSTALL_PREFIX }}/sunshine.AppImage.* ../artifacts/ - mkdir -p ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_CONFIG_DIR }} - cp ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/apps.json \ - ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_CONFIG_DIR }}/ + mkdir -p ../artifacts/${{ matrix.SUNSHINE_ASSETS_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_ASSETS_DIR }} + cp ../artifacts/${{ matrix.SUNSHINE_ASSETS_DIR }}/apps.json \ + ../artifacts/${{ matrix.SUNSHINE_ASSETS_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_ASSETS_DIR }}/ # variables DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" @@ -479,7 +476,6 @@ jobs: cmake -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr \ -DSUNSHINE_ASSETS_DIR=local/sunshine/assets \ - -DSUNSHINE_CONFIG_DIR=local/sunshine/config \ .. make -j ${nproc} @@ -770,7 +766,6 @@ jobs: cd build cmake -DCMAKE_BUILD_TYPE=Release \ -DSUNSHINE_ASSETS_DIR=assets \ - -DSUNSHINE_CONFIG_DIR=config \ -G "MinGW Makefiles" \ .. mingw32-make -j2 diff --git a/CMakeLists.txt b/CMakeLists.txt index d3e36294..823fe1d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -425,13 +425,8 @@ if(NOT SUNSHINE_ASSETS_DIR) set(SUNSHINE_ASSETS_DIR "${CMAKE_CURRENT_BINARY_DIR}/assets") endif() -if(NOT SUNSHINE_CONFIG_DIR) - set(SUNSHINE_CONFIG_DIR "${CMAKE_CURRENT_BINARY_DIR}/config") -endif() - if(UNIX AND CMAKE_INSTALL_PREFIX AND NOT ${SUNSHINE_CONFIGURE_APPIMAGE}) set(SUNSHINE_ASSETS_DIR "${CMAKE_INSTALL_PREFIX}/${SUNSHINE_ASSETS_DIR}") - set(SUNSHINE_CONFIG_DIR "${CMAKE_INSTALL_PREFIX}/${SUNSHINE_CONFIG_DIR}") endif() list(APPEND CBS_EXTERNAL_LIBRARIES @@ -453,7 +448,6 @@ if(NOT WIN32) endif() list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR}") -list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_CONFIG_DIR="${SUNSHINE_CONFIG_DIR}") add_executable(sunshine ${SUNSHINE_TARGET_FILES}) target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES} ${EXTRA_LIBS}) target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS}) @@ -504,12 +498,7 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}" COMPONENT assets) install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}" COMPONENT assets) - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT config) - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT config) - - # set(CPACK_NSIS_MUI_HEADERIMAGE "") # TODO: image should be 150x57 bmp - set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}\\\\sunshine.ico") set(CPACK_NSIS_INSTALLED_ICON_NAME "${PROJECT__DIR}\\\\${PROJECT_EXE}") set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}") # The name of the directory that will be created in C:/Program files/ @@ -545,12 +534,6 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h set(CPACK_COMPONENT_ASSETS_GROUP "${CMAKE_PROJECT_NAME}") set(CPACK_COMPONENT_ASSETS_REQUIRED true) - # config - set(CPACK_COMPONENT_CONFIG_DISPLAY_NAME "Config") - set(CPACK_COMPONENT_CONFIG_DESCRIPTION "Default config and apps.json files.") - set(CPACK_COMPONENT_CONFIG_GROUP "${CMAKE_PROJECT_NAME}") - set(CPACK_COMPONENT_CONFIG_REQUIRED true) - # audio tool set(CPACK_COMPONENT_AUDIO_DISPLAY_NAME "audio-info.exe") set(CPACK_COMPONENT_AUDIO_DESCRIPTION "CLI tool that allows you to get information about sound devices.") @@ -578,10 +561,7 @@ if(APPLE AND SUNSHINE_MACOS_PACKAGE) # TODO set(INSTALL_RUNTIME_DIR "${prefix}/MacOS") install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") install(TARGETS sunshine BUNDLE DESTINATION . COMPONENT Runtime @@ -596,17 +576,12 @@ elseif(UNIX) install(TARGETS sunshine RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") if(APPLE) install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") - install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/misc/uninstall_pkg.sh" DESTINATION "${SUNSHINE_ASSETS_DIR}") else() install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") - install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine.rules" DESTINATION "${CMAKE_INSTALL_LIBDIR}/udev/rules.d") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "${CMAKE_INSTALL_LIBDIR}/systemd/user") diff --git a/packaging/linux/aur/PKGBUILD b/packaging/linux/aur/PKGBUILD index ede1af25..c3e1550b 100644 --- a/packaging/linux/aur/PKGBUILD +++ b/packaging/linux/aur/PKGBUILD @@ -35,7 +35,6 @@ build() { -D SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ -D CMAKE_INSTALL_PREFIX="/usr" \ -D SUNSHINE_ASSETS_DIR="share/sunshine/assets" \ - -D SUNSHINE_CONFIG_DIR="share/sunshine/config" \ -D LIBAVCODEC_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ -D LIBAVCODEC_LIBRARIES=/usr/lib/ffmpeg4.4/libavcodec.so \ -D LIBAVDEVICE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index 04d3df5a..0833b234 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -203,7 +203,6 @@ modules: - -DCMAKE_INSTALL_PREFIX=/app - -DCMAKE_CUDA_COMPILER=/app/cuda/bin/nvcc - -DSUNSHINE_ASSETS_DIR=assets - - -DSUNSHINE_CONFIG_DIR=config - -DSUNSHINE_EXECUTABLE_PATH=/app/bin/sunshine - -DSUNSHINE_ENABLE_WAYLAND=ON - -DSUNSHINE_ENABLE_X11=ON diff --git a/packaging/macos/Portfile b/packaging/macos/Portfile index 8707cedc..86ecc863 100644 --- a/packaging/macos/Portfile +++ b/packaging/macos/Portfile @@ -38,8 +38,7 @@ depends_lib port:avahi \ boost.version 1.76 configure.args -DCMAKE_INSTALL_PREFIX=${prefix} \ - -DSUNSHINE_ASSETS_DIR=etc/sunshine/assets \ - -DSUNSHINE_CONFIG_DIR=etc/sunshine/config + -DSUNSHINE_ASSETS_DIR=etc/sunshine/assets startupitem.create yes startupitem.executable "${prefix}/bin/{$name}" @@ -54,30 +53,6 @@ platform darwin { } } -# destroot not required as cmake install directive handles moving files - -# # Rename files in `destroot` -# post-destroot { -# file rename ${destroot}${prefix}/etc/${name}/config/sunshine.conf ${destroot}${prefix}/etc/${name}/config/sunshine.conf.sample -# file rename ${destroot}${prefix}/etc/${name}/config/apps.json ${destroot}${prefix}/etc/${name}/config/apps.json.sample -# } - -# # Don't overwrite existing preference files -# post-activate { -# if {![file exists ${prefix}/etc/${name}/config/sunshine.conf]} { -# file copy ${destroot}${prefix}/etc/${name}/config/sunshine.conf.sample \ -# ${prefix}/etc/${name}/config/sunshine.conf -# } -# if {![file exists ${prefix}/etc/${name}/config/apps.json]} { -# file copy ${destroot}${prefix}/etc/${name}/config/apps.json.sample \ -# ${prefix}/etc/${name}/config/apps.json -# } -# } - -# disabled not overwriting config files... these are the default config files required by Sunshine -# this did not work with pkg created by macports -# we should always install the default files and user should start sunshine like "sunshine " -# if the file doesn't exist sunshine will copy the default config to that location notes-append "Run @PROJECT_NAME@ by executing 'sunshine ', e.g. 'sunshine ~/sunshine.conf' " notes-append "The config file will be created if it doesn't exist." notes-append "It is recommended to set a location for the apps file in the config." diff --git a/src/config.cpp b/src/config.cpp index fd887605..26064d60 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -702,7 +702,7 @@ int apply_flags(const char *line) { void apply_config(std::unordered_map &&vars) { if(!fs::exists(stream.file_apps.c_str())) { - fs::copy_file(SUNSHINE_CONFIG_DIR "/apps.json", stream.file_apps); + fs::copy_file(SUNSHINE_ASSETS_DIR "/apps.json", stream.file_apps); } for(auto &[name, val] : vars) { @@ -911,7 +911,7 @@ int parse(int argc, char *argv[]) { } if(!fs::exists(sunshine.config_file)) { - fs::copy_file(SUNSHINE_CONFIG_DIR "/sunshine.conf", sunshine.config_file); + fs::copy_file(SUNSHINE_ASSETS_DIR "/sunshine.conf", sunshine.config_file); } auto vars = parse_config(read_file(sunshine.config_file.c_str())); diff --git a/src_assets/common/config/sunshine.conf b/src_assets/common/assets/sunshine.conf similarity index 100% rename from src_assets/common/config/sunshine.conf rename to src_assets/common/assets/sunshine.conf diff --git a/src_assets/linux/config/apps.json b/src_assets/linux/assets/apps.json similarity index 100% rename from src_assets/linux/config/apps.json rename to src_assets/linux/assets/apps.json diff --git a/src_assets/macos/config/apps.json b/src_assets/macos/assets/apps.json similarity index 100% rename from src_assets/macos/config/apps.json rename to src_assets/macos/assets/apps.json diff --git a/src_assets/windows/config/apps.json b/src_assets/windows/assets/apps.json similarity index 100% rename from src_assets/windows/config/apps.json rename to src_assets/windows/assets/apps.json From 7dc8546c256e170b40819b4e62ab691859e02db1 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 12 Aug 2022 16:13:04 -0400 Subject: [PATCH 356/817] remove config backup and restore --- CMakeLists.txt | 8 ++------ src_assets/linux/misc/conffiles | 2 -- src_assets/linux/misc/postinst | 21 --------------------- src_assets/linux/misc/preinst | 9 --------- 4 files changed, 2 insertions(+), 38 deletions(-) delete mode 100644 src_assets/linux/misc/conffiles delete mode 100644 src_assets/linux/misc/preinst diff --git a/CMakeLists.txt b/CMakeLists.txt index 823fe1d6..b49fe045 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -585,12 +585,8 @@ elseif(UNIX) install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine.rules" DESTINATION "${CMAKE_INSTALL_LIBDIR}/udev/rules.d") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "${CMAKE_INSTALL_LIBDIR}/systemd/user") - # Pre and post install - set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA - "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/preinst" - "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst" - "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/conffiles") - set(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/preinst") + # Post install + set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst") set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst") # Dependencies diff --git a/src_assets/linux/misc/conffiles b/src_assets/linux/misc/conffiles deleted file mode 100644 index 4a35822b..00000000 --- a/src_assets/linux/misc/conffiles +++ /dev/null @@ -1,2 +0,0 @@ -/usr/local/sunshine/config/sunshine.conf -/usr/local/sunshine/config/apps.json diff --git a/src_assets/linux/misc/postinst b/src_assets/linux/misc/postinst index a42d3ca5..da617605 100644 --- a/src_assets/linux/misc/postinst +++ b/src_assets/linux/misc/postinst @@ -12,27 +12,6 @@ else echo "Warning: /etc/group not found" fi -if [ -f /usr/local/sunshine/config/sunshine.conf.old ]; then - echo "Restoring old sunshine.conf" - mv /usr/local/sunshine/config/sunshine.conf.old /usr/local/sunshine/config/sunshine.conf -fi - -if [ -f /usr/local/sunshine/config/apps.json.old ]; then - echo "Restoring old apps.json" - mv /usr/local/sunshine/config/apps.json.old /usr/local/sunshine/config/apps.json -fi - -# Update permissions on config files for Web Manager -if [ -f /usr/local/sunshine/config/apps.json ]; then - echo "chmod 666 /etc/sunshine/apps.json" - chmod 666 /usr/local/sunshine/config/apps.json -fi - -if [ -f /usr/local/sunshine/config/sunshine.conf ]; then - echo "chmod 666 /etc/sunshine/sunshine.conf" - chmod 666 /usr/local/sunshine/config/sunshine.conf -fi - # Ensure Sunshine can grab images from KMS path_to_setcap=$(which setcap) if [ -x "$path_to_setcap" ] ; then diff --git a/src_assets/linux/misc/preinst b/src_assets/linux/misc/preinst deleted file mode 100644 index 0522b5d6..00000000 --- a/src_assets/linux/misc/preinst +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -#Store backup for old config files to prevent it from being overwritten -if [ -f /usr/local/sunshine/config/sunshine.conf ]; then - cp /usr/local/sunshine/config/sunshine.conf /usr/local/sunshine/config/sunshine.conf.old -fi - -if [ -f /usr/local/sunshine/config/apps.json ]; then - cp /usr/local/sunshine/config/apps.json /usr/local/sunshine/config/apps.json.old -fi From 9a95ce51322b21f918006955924b855ed5429db2 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 12 Aug 2022 17:28:31 -0400 Subject: [PATCH 357/817] simplify portable config for AppImage --- .github/workflows/CI.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a036ecf4..3b03bcca 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -377,12 +377,9 @@ jobs: # install sunshine to the DESTDIR make install DESTDIR=AppDir - # portable home and config + # portable config (assets directory) # todo - this is ugly... we should use a custom AppRun script to take care of this mv ./AppDir${{ matrix.CMAKE_INSTALL_PREFIX }}/sunshine.AppImage.* ../artifacts/ - mkdir -p ../artifacts/${{ matrix.SUNSHINE_ASSETS_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_ASSETS_DIR }} - cp ../artifacts/${{ matrix.SUNSHINE_ASSETS_DIR }}/apps.json \ - ../artifacts/${{ matrix.SUNSHINE_ASSETS_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_ASSETS_DIR }}/ # variables DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" From 8b0e6a28c25ae36c87f03e09413253b5619e3a4f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 12 Aug 2022 19:26:17 -0400 Subject: [PATCH 358/817] single AppImage file - simplify cmake install prefix and assets directory --- .github/workflows/CI.yml | 30 +++-- CMakeLists.txt | 41 ++++--- packaging/linux/AppImage/AppRun | 110 ++++++++++++++++++ packaging/linux/aur/PKGBUILD | 3 +- .../linux/flatpak/dev.lizardbyte.sunshine.yml | 2 +- src/config.cpp | 2 +- src_assets/common/assets/sunshine.conf | 1 - src_assets/macos/misc/uninstall_pkg.sh | 35 +----- 8 files changed, 155 insertions(+), 69 deletions(-) create mode 100644 packaging/linux/AppImage/AppRun delete mode 100644 src_assets/common/assets/sunshine.conf diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 3b03bcca..c2d5b921 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -253,12 +253,8 @@ jobs: matrix: include: # package these differently - type: cpack - CMAKE_INSTALL_PREFIX: '/usr' - SUNSHINE_ASSETS_DIR: 'local/sunshine/assets' EXTRA_ARGS: '' - type: appimage - CMAKE_INSTALL_PREFIX: '/usr' - SUNSHINE_ASSETS_DIR: 'sunshine.AppImage.config' EXTRA_ARGS: '-DSUNSHINE_CONFIGURE_APPIMAGE=ON' steps: @@ -278,7 +274,6 @@ jobs: build-essential \ cmake \ gcc-10 \ - git \ g++-10 \ libavdevice-dev \ libboost-filesystem-dev \ @@ -341,8 +336,8 @@ jobs: cd build cmake -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=${{ matrix.CMAKE_INSTALL_PREFIX }} \ - -DSUNSHINE_ASSETS_DIR=${{ matrix.SUNSHINE_ASSETS_DIR }} \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DSUNSHINE_ASSETS_DIR=share/sunshine \ -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ -DSUNSHINE_ENABLE_WAYLAND=ON \ -DSUNSHINE_ENABLE_X11=ON \ @@ -377,9 +372,9 @@ jobs: # install sunshine to the DESTDIR make install DESTDIR=AppDir - # portable config (assets directory) - # todo - this is ugly... we should use a custom AppRun script to take care of this - mv ./AppDir${{ matrix.CMAKE_INSTALL_PREFIX }}/sunshine.AppImage.* ../artifacts/ + # custom AppRun file + cp -f ../packaging/linux/AppImage/AppRun ./AppDir/ + chmod +x ./AppDir/AppRun # variables DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" @@ -424,13 +419,13 @@ jobs: ./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage - - name: Archive AppImage - if: ${{ matrix.type == 'appimage' }} - working-directory: artifacts - run: | - chmod +x ./sunshine.AppImage - - zip --recurse-paths --move --test ./sunshine-appimage.zip ./* +# - name: Archive AppImage +# if: ${{ matrix.type == 'appimage' }} +# working-directory: artifacts +# run: | +# chmod +x ./sunshine.AppImage +# +# zip --recurse-paths --move --test ./sunshine-appimage.zip ./* - name: Upload Artifacts uses: actions/upload-artifact@v3 @@ -473,6 +468,7 @@ jobs: cmake -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr \ -DSUNSHINE_ASSETS_DIR=local/sunshine/assets \ + -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ .. make -j ${nproc} diff --git a/CMakeLists.txt b/CMakeLists.txt index b49fe045..7eeac066 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,7 +93,7 @@ if(WIN32) endif() add_compile_definitions(SUNSHINE_PLATFORM="windows") - add_subdirectory(tools) #This is temporary, only tools for Windows are needed, for now + add_subdirectory(tools) # This is temporary, only tools for Windows are needed, for now include_directories(third-party/ViGEmClient/include) @@ -421,14 +421,23 @@ else() list(APPEND SUNSHINE_COMPILE_OPTIONS -O3) endif() +# setup assets directory if(NOT SUNSHINE_ASSETS_DIR) - set(SUNSHINE_ASSETS_DIR "${CMAKE_CURRENT_BINARY_DIR}/assets") + set(SUNSHINE_ASSETS_DIR "assets") endif() - -if(UNIX AND CMAKE_INSTALL_PREFIX AND NOT ${SUNSHINE_CONFIGURE_APPIMAGE}) +if(UNIX) set(SUNSHINE_ASSETS_DIR "${CMAKE_INSTALL_PREFIX}/${SUNSHINE_ASSETS_DIR}") endif() +# use relative assets path for AppImage... maybe for all unix +if(${SUNSHINE_CONFIGURE_APPIMAGE}) + string(REPLACE "${CMAKE_INSTALL_PREFIX}" ".${CMAKE_INSTALL_PREFIX}" SUNSHINE_ASSETS_DIR_DEF ${SUNSHINE_ASSETS_DIR}) +else() + set(SUNSHINE_ASSETS_DIR_DEF "${SUNSHINE_ASSETS_DIR}") +endif() +list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR_DEF}") + + list(APPEND CBS_EXTERNAL_LIBRARIES cbs) @@ -447,7 +456,6 @@ if(NOT WIN32) list(APPEND SUNSHINE_EXTERNAL_LIBRARIES Boost::log) endif() -list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR}") add_executable(sunshine ${SUNSHINE_TARGET_FILES}) target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES} ${EXTRA_LIBS}) target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS}) @@ -501,8 +509,8 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h # set(CPACK_NSIS_MUI_HEADERIMAGE "") # TODO: image should be 150x57 bmp set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}\\\\sunshine.ico") set(CPACK_NSIS_INSTALLED_ICON_NAME "${PROJECT__DIR}\\\\${PROJECT_EXE}") - set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}") # The name of the directory that will be created in C:/Program files/ - string(APPEND CPACK_NSIS_DEFINES "\n RequestExecutionLevel admin") # TODO: Not sure if this is needed but it took me a while to figure out where to put this option so I'm leaving it here + set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}") # The name of the directory that will be created in C:/Program files/ + string(APPEND CPACK_NSIS_DEFINES "\n RequestExecutionLevel admin") # TODO: Not sure if this is needed but it took me a while to figure out where to put this option so I'm leaving it here # Sets permissions on the installed folder so that we can write in it SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS @@ -514,11 +522,11 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h set(CPACK_NSIS_MODIFY_PATH "OFF") # TODO: it asks to add it to the PATH but is not working https://gitlab.kitware.com/cmake/cmake/-/issues/15635 set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".") set(CPACK_NSIS_MUI_FINISHPAGE_RUN "${CMAKE_PROJECT_NAME}.exe") - set(CPACK_NSIS_INSTALLED_ICON_NAME "${CMAKE_PROJECT_NAME}.exe") # This will be shown on the installed apps Windows settings + set(CPACK_NSIS_INSTALLED_ICON_NAME "${CMAKE_PROJECT_NAME}.exe") # This will be shown on the installed apps Windows settings set(CPACK_NSIS_CREATE_ICONS "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${CMAKE_PROJECT_NAME}.lnk' '\$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe'") # Checking for previous installed versions - # set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL "ON") # TODO: doesn't work on my machine when Sunshine is already installed + # set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL "ON") # TODO: doesn't work on my machine when Sunshine is already installed # Setting components groups and dependencies # sunshine binary @@ -556,7 +564,7 @@ if(APPLE) set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/sunshine.icns") # set(CPACK_BUNDLE_STARTUP_COMMAND "${INSTALL_RUNTIME_DIR}/sunshine") endif() -if(APPLE AND SUNSHINE_MACOS_PACKAGE) # TODO +if(APPLE AND SUNSHINE_MACOS_PACKAGE) # TODO set(prefix "${CMAKE_PROJECT_NAME}.app/Contents") set(INSTALL_RUNTIME_DIR "${prefix}/MacOS") @@ -570,7 +578,7 @@ elseif(UNIX) # Installation destination dir set(CPACK_SET_DESTDIR true) if(NOT CMAKE_INSTALL_PREFIX) - set(CMAKE_INSTALL_PREFIX "/usr/local/sunshine") + set(CMAKE_INSTALL_PREFIX "/usr/share/sunshine") endif() install(TARGETS sunshine RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") @@ -582,8 +590,13 @@ elseif(UNIX) install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/misc/uninstall_pkg.sh" DESTINATION "${SUNSHINE_ASSETS_DIR}") else() install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") - install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine.rules" DESTINATION "${CMAKE_INSTALL_LIBDIR}/udev/rules.d") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "${CMAKE_INSTALL_LIBDIR}/systemd/user") + if(${SUNSHINE_CONFIGURE_APPIMAGE}) + install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine.rules" DESTINATION "${SUNSHINE_ASSETS_DIR}/udev/rules.d") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "${SUNSHINE_ASSETS_DIR}/systemd/user") + else() + install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine.rules" DESTINATION "${CMAKE_INSTALL_LIBDIR}/udev/rules.d") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "${CMAKE_INSTALL_LIBDIR}/systemd/user") + endif() # Post install set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst") @@ -593,7 +606,7 @@ elseif(UNIX) set(CPACK_DEB_COMPONENT_INSTALL ON) set(CPACK_DEBIAN_PACKAGE_DEPENDS "openssl, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.1, libavdevice >= 4.3, boost-thread >= 1.67.0, boost-filesystem >= 1.67.0, boost-log >= 1.67.0, pulseaudio-libs >= 10.0, libopusenc >= 0.2.1, libxcb >= 1.13, libXtst >= 1.2.3, libevdev >= 1.5.6, libdrm >= 2.4.97, libcap >= 2.22") - set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # This should automatically figure out dependencies, doesn't work with the current config + set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # This should automatically figure out dependencies, doesn't work with the current config endif() endif() diff --git a/packaging/linux/AppImage/AppRun b/packaging/linux/AppImage/AppRun new file mode 100644 index 00000000..e9f9f3f7 --- /dev/null +++ b/packaging/linux/AppImage/AppRun @@ -0,0 +1,110 @@ +#!/bin/bash + +# custom AppRun for Sunshine AppImage + +# path of the extracted AppRun +HERE="$(dirname "$(readlink -f "${0}")")" +SUNSHINE_PATH=/usr/bin/sunshine +SUNSHINE_BIN_HERE=$HERE/usr/bin/sunshine +SUNSHINE_SHARE_HERE=$HERE/usr/share/sunshine + +# Set APPDIR when running directly from the AppDir: +if [ -z "$APPDIR" ]; then + ARGV0="AppRun" +fi + +cd "$HERE" || exit 1 + +function help() { +echo " + ------------------------------ + Sunshine AppImage package. + ------------------------------ + + sunshine.AppImage options + ------------------------ + + Usage: $ARGV0 --help, -h + ------ # This message + + $ARGV0 --install, -i + # Install input rules sunshine.service files. Restart required. + + $ARGV0 --remove, -r + # Remove input rules sunshine.service files. + + $ARGV0 --appimage-help + # Show available AppImage options + + sunshine options + ---------------- +" +# print sunshine binary help, replacing the sunshine command in usage statement +"$SUNSHINE_BIN_HERE" --help | sed -e "s#$SUNSHINE_BIN_HERE#$ARGV0#g" +} + +function install() { + # user input rules + sudo usermod -a -G input $USER + # shellcheck disable=SC2002 + cat "$SUNSHINE_SHARE_HERE/udev/rules.d/85-sunshine.rules" | sudo tee /etc/udev/85-sunshine.rules + + # sunshine service + mkdir -p ~/.config/systemd/user + cp -r "$SUNSHINE_SHARE_HERE/systemd/user/" ~/.config/systemd/ + # patch service executable path + sed -i -e "s#$SUNSHINE_PATH#$(readlink -f $ARGV0)#g" ~/.config/systemd/user/sunshine.service + + # setcap + sudo setcap cap_sys_admin+p "$(readlink -f "$SUNSHINE_BIN_HERE")" + + while true + do + read -r -p "This installation requires a reboot. Do you want to reboot NOW? [y/n] " input + + case $input in + [yY][eE][sS]|[yY]) + echo "Yes" + sudo reboot now + ;; + [nN][oO]|[nN]) + echo "No" + break + ;; + *) + echo "Invalid input..." + ;; + esac + done +} + +function remove() { + # remove input rules + sudo rm -f /etc/udev/rules.d/85-sunshine.rules + + # remove service + sudo rm -f ~/.config/systemd/user/sunshine.service +} + +# process arguments +if [ "x$1" == "xhelp" ] || [ "x$1" == "x--help" ] || [ "x$1" == "x-h" ] ; then + help + exit $? +fi + +if [ "x$1" == "xinstall" ] || [ "x$1" == "x--install" ] || [ "x$1" == "x-i" ] ; then + install + exit $? +fi + +if [ "x$1" == "xremove" ] || [ "x$1" == "x--remove" ] || [ "x$1" == "x-r" ] ; then + remove + exit $? +fi + +# create config directory if it doesn't exist +# https://github.com/LizardByte/Sunshine/issues/324 +mkdir -p ~/.config/sunshine + +# run sunshine +"$SUNSHINE_BIN_HERE" $@ diff --git a/packaging/linux/aur/PKGBUILD b/packaging/linux/aur/PKGBUILD index c3e1550b..01760384 100644 --- a/packaging/linux/aur/PKGBUILD +++ b/packaging/linux/aur/PKGBUILD @@ -33,8 +33,7 @@ build() { -B build \ -Wno-dev \ -D SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ - -D CMAKE_INSTALL_PREFIX="/usr" \ - -D SUNSHINE_ASSETS_DIR="share/sunshine/assets" \ + -D SUNSHINE_ASSETS_DIR="assets" \ -D LIBAVCODEC_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ -D LIBAVCODEC_LIBRARIES=/usr/lib/ffmpeg4.4/libavcodec.so \ -D LIBAVDEVICE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index 0833b234..0a34d878 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -202,7 +202,7 @@ modules: - -DCMAKE_BUILD_TYPE=Release - -DCMAKE_INSTALL_PREFIX=/app - -DCMAKE_CUDA_COMPILER=/app/cuda/bin/nvcc - - -DSUNSHINE_ASSETS_DIR=assets + - -DSUNSHINE_ASSETS_DIR=share/sunshine - -DSUNSHINE_EXECUTABLE_PATH=/app/bin/sunshine - -DSUNSHINE_ENABLE_WAYLAND=ON - -DSUNSHINE_ENABLE_X11=ON diff --git a/src/config.cpp b/src/config.cpp index 26064d60..7f224e9e 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -911,7 +911,7 @@ int parse(int argc, char *argv[]) { } if(!fs::exists(sunshine.config_file)) { - fs::copy_file(SUNSHINE_ASSETS_DIR "/sunshine.conf", sunshine.config_file); + std::ofstream { sunshine.config_file }; // create empty config file } auto vars = parse_config(read_file(sunshine.config_file.c_str())); diff --git a/src_assets/common/assets/sunshine.conf b/src_assets/common/assets/sunshine.conf deleted file mode 100644 index 58dd670b..00000000 --- a/src_assets/common/assets/sunshine.conf +++ /dev/null @@ -1 +0,0 @@ -# See our documentation at https://docs.lizardbyte.dev/projects/sunshine diff --git a/src_assets/macos/misc/uninstall_pkg.sh b/src_assets/macos/misc/uninstall_pkg.sh index 7e136109..869f33d2 100644 --- a/src_assets/macos/misc/uninstall_pkg.sh +++ b/src_assets/macos/misc/uninstall_pkg.sh @@ -6,41 +6,10 @@ package_name=org.macports.Sunshine echo "Removing files now..." FILES=$(pkgutil --files $package_name --only-files) -remove_config=True -remove_apps=True - for file in ${FILES}; do file="/$file" - remove_current=True - if [[ $file == *sunshine.conf ]]; then - if [[ $remove_config == True ]]; then - while true; do - read -p -r "Do you wish to remove 'sunshine.conf'?" yn - case $yn in - [Yy]* ) echo "removing: $file"; rm -f "$file"; break;; - [Nn]* ) remove_config=False; remove_current=False; break;; - * ) echo "Please answer yes or no.";; - esac - done - fi - fi - if [[ $file == *apps.json ]]; then - if [[ $remove_apps == True ]]; then - while true; do - read -p -r "Do you wish to remove 'apps.conf'?" yn - case $yn in - [Yy]* ) echo "removing: $file"; rm -f "$file"; break;; - [Nn]* ) remove_apps=False; remove_current=False; break;; - * ) echo "Please answer yes or no.";; - esac - done - fi - fi - - if [[ $remove_current == True ]]; then - echo "removing: $file" - rm -f "$file" - fi + echo "removing: $file" + rm -f "$file" done echo "Removing directories now..." From 663a92ce33b62e8d46e97d8859115f227be6ad93 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 14 Aug 2022 20:53:38 -0400 Subject: [PATCH 359/817] install bat scripts on windows --- CMakeLists.txt | 45 +++++++++++++++---- .../windows/misc/service}/install-service.bat | 5 ++- .../misc/service}/uninstall-service.bat | 0 3 files changed, 40 insertions(+), 10 deletions(-) rename {tools => src_assets/windows/misc/service}/install-service.bat (80%) rename {tools => src_assets/windows/misc/service}/uninstall-service.bat (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7eeac066..117880ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -501,8 +501,13 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h # Adding tools 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 sunshinesvc RUNTIME DESTINATION "tools" COMPONENT servicesvc) + # scripts + # install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/firewall/" DESTINATION "scripts" COMPONENT firewall) + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/service/" DESTINATION "scripts" COMPONENT service) + + # Sunshine assets install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}" COMPONENT assets) install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}" COMPONENT assets) @@ -512,11 +517,23 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}") # The name of the directory that will be created in C:/Program files/ string(APPEND CPACK_NSIS_DEFINES "\n RequestExecutionLevel admin") # TODO: Not sure if this is needed but it took me a while to figure out where to put this option so I'm leaving it here + # Extra install commands # Sets permissions on the installed folder so that we can write in it + # Install service SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS} ExecWait 'icacls \\\"$INSTDIR\\\" /grant:r Users:\\\(OI\\\)\\\(CI\\\)\\\(F\\\)' + ExecWait 'if exist ./scripts/install-service.bat ./scripts/install-service.bat' ") + # ExecWait 'if exist ./scripts/add-firewall-rule.bat ./scripts/add-firewall-rule.bat' + + # Extra uninstall commands + # Uninstall service + set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS + "${CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS} + ExecWait `if exist ./scripts/uninstall-service.bat ./scripts/uninstall-service.bat` + ") + # ExecWait 'if exist ./scripts/delete-firewall-rule.bat ./scripts/delete-firewall-rule.bat' # Adding an option for the start menu and PATH set(CPACK_NSIS_MODIFY_PATH "OFF") # TODO: it asks to add it to the PATH but is not working https://gitlab.kitware.com/cmake/cmake/-/issues/15635 @@ -543,19 +560,29 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h set(CPACK_COMPONENT_ASSETS_REQUIRED true) # audio tool - set(CPACK_COMPONENT_AUDIO_DISPLAY_NAME "audio-info.exe") - set(CPACK_COMPONENT_AUDIO_DESCRIPTION "CLI tool that allows you to get information about sound devices.") + set(CPACK_COMPONENT_AUDIO_DISPLAY_NAME "audio-info") + set(CPACK_COMPONENT_AUDIO_DESCRIPTION "CLI tool providing information about sound devices.") set(CPACK_COMPONENT_AUDIO_GROUP "Tools") # display tool - set(CPACK_COMPONENT_DXGI_DISPLAY_NAME "dxgi-info.exe") - set(CPACK_COMPONENT_DXGI_DESCRIPTION "CLI tool that allows you to get information about graphics cards and displays.") + set(CPACK_COMPONENT_DXGI_DISPLAY_NAME "dxgi-info") + set(CPACK_COMPONENT_DXGI_DESCRIPTION "CLI tool providing information about graphics cards and displays.") set(CPACK_COMPONENT_DXGI_GROUP "Tools") - # service tool - set(CPACK_COMPONENT_SUNSHINESVC_DISPLAY_NAME "sunshinesvc.exe") - set(CPACK_COMPONENT_SUNSHINESVC_DESCRIPTION "CLI tool that allows you to enable/disable the Sunshine service.") - set(CPACK_COMPONENT_SUNSHINESVC_GROUP "Tools") + # service + set(CPACK_COMPONENT_SERVICESVC_DISPLAY_NAME "service-service") + set(CPACK_COMPONENT_SERVICESVC_DESCRIPTION "Enable the Sunshine service.") + set(CPACK_COMPONENT_SERVICESVC_GROUP "Tools") + + # service scripts + set(CPACK_COMPONENT_SERVICE_DISPLAY_NAME "service-scripts") + set(CPACK_COMPONENT_SERVICE_DESCRIPTION "Scripts to enable or disable the service.") + set(CPACK_COMPONENT_SERVICE_GROUP "Tools") + + # firewall scripts + # 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 "Tools") endif() if(APPLE) # TODO: bundle doesn't produce a valid .app use cpack -G DragNDrop diff --git a/tools/install-service.bat b/src_assets/windows/misc/service/install-service.bat similarity index 80% rename from tools/install-service.bat rename to src_assets/windows/misc/service/install-service.bat index 5e6fbc22..887c5aa3 100644 --- a/tools/install-service.bat +++ b/src_assets/windows/misc/service/install-service.bat @@ -1,7 +1,10 @@ @echo off +rem Get sunshine root directory +for %%I in ("%~dp0\..") do set "root_dir=%%~fI" + set SERVICE_NAME=sunshinesvc -set SERVICE_BIN="%~dp0\tools\sunshinesvc.exe" +set SERVICE_BIN="%root_dir%\tools\sunshinesvc.exe" set SERVICE_START_TYPE=auto rem Check if sunshinesvc already exists diff --git a/tools/uninstall-service.bat b/src_assets/windows/misc/service/uninstall-service.bat similarity index 100% rename from tools/uninstall-service.bat rename to src_assets/windows/misc/service/uninstall-service.bat From 749bfa89ef370acda47e18829d34c4c5600f37c4 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 15 Aug 2022 17:41:27 -0400 Subject: [PATCH 360/817] cleanup commented out AppImage CI code --- .github/workflows/CI.yml | 17 ----------------- CMakeLists.txt | 25 +++++++++++++------------ 2 files changed, 13 insertions(+), 29 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index c2d5b921..a585edd4 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -106,7 +106,6 @@ jobs: echo "aur_publish=true" >> $GITHUB_ENV elif [[ ${{ github.ref == 'refs/heads/nightly' }} ]]; then - aur_pkg=sunshine-git sub_version=".r${commit}" echo "aur_publish=true" >> $GITHUB_ENV @@ -385,12 +384,6 @@ jobs: wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage chmod +x linuxdeploy-x86_64.AppImage - # # https://github.com/linuxdeploy/linuxdeploy-plugin-gtk - # sudo apt-get install libgtk-3-dev librsvg2-dev -y - # wget https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh - # chmod +x linuxdeploy-plugin-gtk.sh - # export DEPLOY_GTK_VERSION=3 - ./linuxdeploy-x86_64.AppImage \ --appdir ./AppDir \ --executable ./sunshine \ @@ -400,8 +393,6 @@ jobs: --library /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 \ --library /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0 \ --output appimage - # # add this argument back if using gtk plugin - # --plugin gtk \ # move mv Sunshine*.AppImage ../artifacts/sunshine.AppImage @@ -419,14 +410,6 @@ jobs: ./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage -# - name: Archive AppImage -# if: ${{ matrix.type == 'appimage' }} -# working-directory: artifacts -# run: | -# chmod +x ./sunshine.AppImage -# -# zip --recurse-paths --move --test ./sunshine-appimage.zip ./* - - name: Upload Artifacts uses: actions/upload-artifact@v3 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 117880ae..8055dc24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -548,41 +548,42 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h # Setting components groups and dependencies # sunshine binary set(CPACK_COMPONENT_APPLICATION_DISPLAY_NAME "${CMAKE_PROJECT_NAME}") - set(CPACK_COMPONENT_APPLICATION_DESCRIPTION "The main application.") - set(CPACK_COMPONENT_APPLICATION_GROUP "${CMAKE_PROJECT_NAME}") + set(CPACK_COMPONENT_APPLICATION_DESCRIPTION "${CMAKE_PROJECT_NAME} main application.") + set(CPACK_COMPONENT_APPLICATION_GROUP "core") set(CPACK_COMPONENT_APPLICATION_REQUIRED true) set(CPACK_COMPONENT_APPLICATION_DEPENDS assets) # assets - set(CPACK_COMPONENT_ASSETS_DISPLAY_NAME "Assets") + set(CPACK_COMPONENT_ASSETS_DISPLAY_NAME "assets") set(CPACK_COMPONENT_ASSETS_DESCRIPTION "Shaders, default box art, and web ui.") - set(CPACK_COMPONENT_ASSETS_GROUP "${CMAKE_PROJECT_NAME}") + set(CPACK_COMPONENT_ASSETS_GROUP "core") set(CPACK_COMPONENT_ASSETS_REQUIRED true) # audio tool set(CPACK_COMPONENT_AUDIO_DISPLAY_NAME "audio-info") set(CPACK_COMPONENT_AUDIO_DESCRIPTION "CLI tool providing information about sound devices.") - set(CPACK_COMPONENT_AUDIO_GROUP "Tools") + set(CPACK_COMPONENT_AUDIO_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.") - set(CPACK_COMPONENT_DXGI_GROUP "Tools") + set(CPACK_COMPONENT_DXGI_GROUP "tools") # service - set(CPACK_COMPONENT_SERVICESVC_DISPLAY_NAME "service-service") - set(CPACK_COMPONENT_SERVICESVC_DESCRIPTION "Enable the Sunshine service.") - set(CPACK_COMPONENT_SERVICESVC_GROUP "Tools") + set(CPACK_COMPONENT_SERVICESVC_DISPLAY_NAME "servicesvc") + set(CPACK_COMPONENT_SERVICESVC_DESCRIPTION "CLI tool providing ability to enable/disable the Sunshine service.") + set(CPACK_COMPONENT_SERVICESVC_GROUP "tools") # service scripts set(CPACK_COMPONENT_SERVICE_DISPLAY_NAME "service-scripts") - set(CPACK_COMPONENT_SERVICE_DESCRIPTION "Scripts to enable or disable the service.") - set(CPACK_COMPONENT_SERVICE_GROUP "Tools") + set(CPACK_COMPONENT_SERVICE_DESCRIPTION "Scripts to enable/disable the service.") + set(CPACK_COMPONENT_SERVICE_GROUP "scripts") + set(CPACK_COMPONENT_SERVICE_DEPENDS servicesvc) # firewall scripts # 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 "Tools") + # set(CPACK_COMPONENT_FIREWALL_GROUP "scripts") endif() if(APPLE) # TODO: bundle doesn't produce a valid .app use cpack -G DragNDrop From 65b557d00333f6ea12c8d26ea0cb0ae8e6c0af38 Mon Sep 17 00:00:00 2001 From: Ryan Caezar Itang Date: Tue, 16 Aug 2022 11:57:14 +0800 Subject: [PATCH 361/817] Add batch files to add and remove firewall rules --- src_assets/windows/misc/firewall/add-firewall-rule.bat | 8 ++++++++ src_assets/windows/misc/firewall/delete-firewall-rule.bat | 6 ++++++ 2 files changed, 14 insertions(+) create mode 100644 src_assets/windows/misc/firewall/add-firewall-rule.bat create mode 100644 src_assets/windows/misc/firewall/delete-firewall-rule.bat diff --git a/src_assets/windows/misc/firewall/add-firewall-rule.bat b/src_assets/windows/misc/firewall/add-firewall-rule.bat new file mode 100644 index 00000000..8c3e7b9e --- /dev/null +++ b/src_assets/windows/misc/firewall/add-firewall-rule.bat @@ -0,0 +1,8 @@ +@echo off + +set RULE_NAME=Sunshine +set PROGRAM_BIN="%~dp0sunshine.exe" + +rem Add the rule +netsh advfirewall firewall add rule name=%RULE_NAME% dir=in action=allow protocol=tcp program=%PROGRAM_BIN% enable=yes +netsh advfirewall firewall add rule name=%RULE_NAME% dir=in action=allow protocol=udp program=%PROGRAM_BIN% enable=yes diff --git a/src_assets/windows/misc/firewall/delete-firewall-rule.bat b/src_assets/windows/misc/firewall/delete-firewall-rule.bat new file mode 100644 index 00000000..3ab0d4e9 --- /dev/null +++ b/src_assets/windows/misc/firewall/delete-firewall-rule.bat @@ -0,0 +1,6 @@ +@echo off + +set RULE_NAME=Sunshine + +rem Delete the rule +netsh advfirewall firewall delete rule name=%RULE_NAME% From e04ed497a6e43bbd06748113689dc69f2cfa305a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 17 Aug 2022 14:41:04 -0400 Subject: [PATCH 362/817] Enable firewall rules for Windows --- CMakeLists.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8055dc24..63d3325d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -504,7 +504,7 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h install(TARGETS sunshinesvc RUNTIME DESTINATION "tools" COMPONENT servicesvc) # scripts - # install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/firewall/" DESTINATION "scripts" COMPONENT firewall) + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/firewall/" DESTINATION "scripts" COMPONENT firewall) install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/service/" DESTINATION "scripts" COMPONENT service) # Sunshine assets @@ -523,17 +523,17 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS} ExecWait 'icacls \\\"$INSTDIR\\\" /grant:r Users:\\\(OI\\\)\\\(CI\\\)\\\(F\\\)' + ExecWait 'if exist ./scripts/add-firewall-rule.bat ./scripts/add-firewall-rule.bat' ExecWait 'if exist ./scripts/install-service.bat ./scripts/install-service.bat' ") - # ExecWait 'if exist ./scripts/add-firewall-rule.bat ./scripts/add-firewall-rule.bat' # Extra uninstall commands # Uninstall service set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "${CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS} + ExecWait 'if exist ./scripts/delete-firewall-rule.bat ./scripts/delete-firewall-rule.bat' ExecWait `if exist ./scripts/uninstall-service.bat ./scripts/uninstall-service.bat` ") - # ExecWait 'if exist ./scripts/delete-firewall-rule.bat ./scripts/delete-firewall-rule.bat' # Adding an option for the start menu and PATH set(CPACK_NSIS_MODIFY_PATH "OFF") # TODO: it asks to add it to the PATH but is not working https://gitlab.kitware.com/cmake/cmake/-/issues/15635 @@ -581,9 +581,9 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h set(CPACK_COMPONENT_SERVICE_DEPENDS servicesvc) # firewall scripts - # 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") + 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") endif() if(APPLE) # TODO: bundle doesn't produce a valid .app use cpack -G DragNDrop From 6980ee36b3a48d4e08592a40dea9a2bf8b72543d Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 20 Aug 2022 21:12:37 -0400 Subject: [PATCH 363/817] fix windows install extra commands and... - prompt to remove install directory on uninstall - delete start menu icons on uninstall - create appdata folder if it doesn't exist --- CMakeLists.txt | 44 +++++++++++++++++++++++++++++--------------- src/config.cpp | 7 +++++++ 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 63d3325d..ccf4139f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -501,7 +501,7 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h # Adding tools 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 servicesvc) + install(TARGETS sunshinesvc RUNTIME DESTINATION "tools" COMPONENT sunshinesvc) # scripts install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/firewall/" DESTINATION "scripts" COMPONENT firewall) @@ -521,30 +521,44 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h # Sets permissions on the installed folder so that we can write in it # Install service SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS - "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS} - ExecWait 'icacls \\\"$INSTDIR\\\" /grant:r Users:\\\(OI\\\)\\\(CI\\\)\\\(F\\\)' - ExecWait 'if exist ./scripts/add-firewall-rule.bat ./scripts/add-firewall-rule.bat' - ExecWait 'if exist ./scripts/install-service.bat ./scripts/install-service.bat' - ") + "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS} + ExecWait 'icacls \\\"$INSTDIR\\\" /grant:r Users:\\\(OI\\\)\\\(CI\\\)\\\(F\\\)' + ExecWait '\\\"$INSTDIR\\\\scripts\\\\add-firewall-rule.bat\\\"' + ExecWait '\\\"$INSTDIR\\\\scripts\\\\install-service.bat\\\"' + ") # Extra uninstall commands # Uninstall service set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS - "${CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS} - ExecWait 'if exist ./scripts/delete-firewall-rule.bat ./scripts/delete-firewall-rule.bat' - ExecWait `if exist ./scripts/uninstall-service.bat ./scripts/uninstall-service.bat` - ") + "${CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS} + ExecWait '\\\"$INSTDIR\\\\scripts\\\\delete-firewall-rule.bat\\\"' + ExecWait '\\\"$INSTDIR\\\\scripts\\\\uninstall-service.bat\\\"' + MessageBox MB_YESNO|MB_ICONQUESTION 'Do you want to completely remove the directory $INSTDIR and all of its contents?' IDNO NoDelete + RMDir /r \\\"$INSTDIR\\\" ; skipped if no + NoDelete: + ") # Adding an option for the start menu and PATH set(CPACK_NSIS_MODIFY_PATH "OFF") # TODO: it asks to add it to the PATH but is not working https://gitlab.kitware.com/cmake/cmake/-/issues/15635 set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".") set(CPACK_NSIS_MUI_FINISHPAGE_RUN "${CMAKE_PROJECT_NAME}.exe") set(CPACK_NSIS_INSTALLED_ICON_NAME "${CMAKE_PROJECT_NAME}.exe") # This will be shown on the installed apps Windows settings - set(CPACK_NSIS_CREATE_ICONS "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${CMAKE_PROJECT_NAME}.lnk' '\$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe'") + set(CPACK_NSIS_CREATE_ICONS_EXTRA + "${CPACK_NSIS_CREATE_ICONS_EXTRA} + CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${CMAKE_PROJECT_NAME}.lnk' '\$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe' + ") + set(CPACK_NSIS_DELETE_ICONS_EXTRA + "${CPACK_NSIS_DELETE_ICONS_EXTRA} + Delete '\$SMPROGRAMS\\\\$MUI_TEMP\\\\${CMAKE_PROJECT_NAME}.lnk' + ") # Checking for previous installed versions # set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL "ON") # TODO: doesn't work on my machine when Sunshine is already installed + set(CPACK_NSIS_HELP_LINK "https://docs.lizardbyte.dev/projects/sunshine/en/latest/about/installation.html") + set(CPACK_NSIS_URL_INFO_ABOUT "${CMAKE_PROJECT_HOMEPAGE_URL}") + set(CPACK_NSIS_CONTACT "${CMAKE_PROJECT_HOMEPAGE_URL}/support") + # Setting components groups and dependencies # sunshine binary set(CPACK_COMPONENT_APPLICATION_DISPLAY_NAME "${CMAKE_PROJECT_NAME}") @@ -570,15 +584,15 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h set(CPACK_COMPONENT_DXGI_GROUP "tools") # service - set(CPACK_COMPONENT_SERVICESVC_DISPLAY_NAME "servicesvc") - set(CPACK_COMPONENT_SERVICESVC_DESCRIPTION "CLI tool providing ability to enable/disable the Sunshine service.") - set(CPACK_COMPONENT_SERVICESVC_GROUP "tools") + set(CPACK_COMPONENT_SUNSHINESVC_DISPLAY_NAME "sunshinesvc") + set(CPACK_COMPONENT_SUNSHINESVC_DESCRIPTION "CLI tool providing ability to enable/disable the Sunshine service.") + set(CPACK_COMPONENT_SUNSHINESVC_GROUP "tools") # service scripts set(CPACK_COMPONENT_SERVICE_DISPLAY_NAME "service-scripts") set(CPACK_COMPONENT_SERVICE_DESCRIPTION "Scripts to enable/disable the service.") set(CPACK_COMPONENT_SERVICE_GROUP "scripts") - set(CPACK_COMPONENT_SERVICE_DEPENDS servicesvc) + set(CPACK_COMPONENT_SERVICE_DEPENDS sunshinesvc) # firewall scripts set(CPACK_COMPONENT_FIREWALL_DISPLAY_NAME "firewall-scripts") diff --git a/src/config.cpp b/src/config.cpp index 7f224e9e..9d27734b 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "config.h" #include "main.h" @@ -910,6 +911,12 @@ int parse(int argc, char *argv[]) { } } + // create appdata folder if it does not exist + if(!boost::filesystem::exists(platf::appdata().string())) { + boost::filesystem::create_directory(platf::appdata().string()); + } + + // create config file if it does not exist if(!fs::exists(sunshine.config_file)) { std::ofstream { sunshine.config_file }; // create empty config file } From f32387f67ea75cff3a02683d74a1457c462e55d1 Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Wed, 24 Aug 2022 12:20:49 -0400 Subject: [PATCH 364/817] Update boost to 1.80.0 --- packaging/linux/flatpak/dev.lizardbyte.sunshine.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index 04d3df5a..8c7db4b7 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -60,8 +60,8 @@ modules: - ./b2 install variant=release link=static,shared runtime-link=shared cxxflags="$CXXFLAGS" linkflags="$LDFLAGS" -j $FLATPAK_BUILDER_N_JOBS # yamllint disable-line rule:line-length sources: - type: archive - url: https://boostorg.jfrog.io/artifactory/main/release/1.79.0/source/boost_1_79_0.tar.bz2 - sha256: 475d589d51a7f8b3ba2ba4eda022b170e562ca3b760ee922c146b6c65856ef39 + url: https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.tar.bz2 + sha256: 1e19565d82e43bc59209a168f5ac899d3ba471d55c7610c677d4ccf2c9c500c0 - name: ffmpeg config-opts: From 0ac67f13d7e9f7401d0823c84d7332857b40eab7 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 23 Aug 2022 20:08:14 -0400 Subject: [PATCH 365/817] fix assets directory for flatpak and AUR --- .github/workflows/CI.yml | 2 +- CMakeLists.txt | 9 +++++---- packaging/linux/aur/PKGBUILD | 3 ++- packaging/linux/flatpak/dev.lizardbyte.sunshine.yml | 8 ++++---- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a585edd4..6c996446 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -217,7 +217,7 @@ jobs: cmake -DGITHUB_CLONE_URL=${clone_url} \ -DGITHUB_BRANCH=${branch} \ -DGITHUB_COMMIT=${commit} \ - -DSUNSHINE_CONFIGURE_FLATPAK=ON \ + -DSUNSHINE_CONFIGURE_FLATPAK_MAN=ON \ -DSUNSHINE_CONFIGURE_ONLY=ON \ .. diff --git a/CMakeLists.txt b/CMakeLists.txt index ccf4139f..cfbbfa7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,9 +9,10 @@ set(PROJECT_LONG_DESCRIPTION "Sunshine is a self hosted, low latency, cloud gami Intel, and Nvidia GPUs. It is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield. \ Connect to Sunshine from any Moonlight client, available for nearly any device imaginable.") -option(SUNSHINE_CONFIGURE_APPIMAGE "Configure files required for AppImage." OFF) +option(SUNSHINE_CONFIGURE_APPIMAGE "Configuration specific for AppImage." OFF) option(SUNSHINE_CONFIGURE_AUR "Configure files required for AUR." OFF) -option(SUNSHINE_CONFIGURE_FLATPAK "Configure files required for Flatpak." OFF) +option(SUNSHINE_CONFIGURE_FLATPAK_MAN "Configure manifest file required for Flatpak build." OFF) +option(SUNSHINE_CONFIGURE_FLATPAK "Configuration specific for Flatpak." OFF) option(SUNSHINE_CONFIGURE_PORTFILE "Configure macOS Portfile." OFF) option(SUNSHINE_CONFIGURE_ONLY "Configure special files only, then exit." OFF) @@ -19,7 +20,7 @@ if(${SUNSHINE_CONFIGURE_APPIMAGE}) configure_file(packaging/linux/sunshine.desktop sunshine.desktop @ONLY) elseif(${SUNSHINE_CONFIGURE_AUR}) configure_file(packaging/linux/aur/PKGBUILD PKGBUILD @ONLY) -elseif(${SUNSHINE_CONFIGURE_FLATPAK}) +elseif(${SUNSHINE_CONFIGURE_FLATPAK_MAN}) configure_file(packaging/linux/flatpak/dev.lizardbyte.sunshine.yml dev.lizardbyte.sunshine.yml @ONLY) elseif(${SUNSHINE_CONFIGURE_PORTFILE}) configure_file(packaging/macos/Portfile Portfile @ONLY) @@ -632,7 +633,7 @@ elseif(UNIX) install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/misc/uninstall_pkg.sh" DESTINATION "${SUNSHINE_ASSETS_DIR}") else() install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") - if(${SUNSHINE_CONFIGURE_APPIMAGE}) + if(${SUNSHINE_CONFIGURE_APPIMAGE} OR ${SUNSHINE_CONFIGURE_FLATPAK}) install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine.rules" DESTINATION "${SUNSHINE_ASSETS_DIR}/udev/rules.d") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "${SUNSHINE_ASSETS_DIR}/systemd/user") else() diff --git a/packaging/linux/aur/PKGBUILD b/packaging/linux/aur/PKGBUILD index 01760384..1ef6d54b 100644 --- a/packaging/linux/aur/PKGBUILD +++ b/packaging/linux/aur/PKGBUILD @@ -32,8 +32,9 @@ build() { -S "$pkgname" \ -B build \ -Wno-dev \ + -D CMAKE_INSTALL_PREFIX=/usr \ -D SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ - -D SUNSHINE_ASSETS_DIR="assets" \ + -D SUNSHINE_ASSETS_DIR="share/sunshine" \ -D LIBAVCODEC_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ -D LIBAVCODEC_LIBRARIES=/usr/lib/ffmpeg4.4/libavcodec.so \ -D LIBAVDEVICE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index 0a34d878..a61b96b4 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -23,7 +23,7 @@ cleanup: - /lib/pkgconfig - /lib/*.la - /lib/*.a - - /share + - /share/man modules: - name: cuda @@ -88,7 +88,7 @@ modules: # - --extra-ldflags=-L${FLATPAK_DEST}/cuda/lib64 # - --nvccflags="-gencode arch=compute_52,code=sm_52 -O2" cleanup: - - /share/ffmpeg/examples + - /share sources: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/universe/f/ffmpeg/ffmpeg_4.4.2.orig.tar.xz @@ -137,8 +137,7 @@ modules: cleanup: - /bin - /lib/avahi - - /share/applications/*.desktop - - /share/avahi + - /share config-opts: - --with-distro=none - --disable-gobject @@ -208,6 +207,7 @@ modules: - -DSUNSHINE_ENABLE_X11=ON - -DSUNSHINE_ENABLE_DRM=ON - -DSUNSHINE_ENABLE_CUDA=ON + - -DSUNSHINE_CONFIGURE_FLATPAK=ON sources: - type: git url: '@GITHUB_CLONE_URL@' From 83ea433857049e621b3c775afad4adc944038617 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 24 Aug 2022 20:43:46 -0400 Subject: [PATCH 366/817] update docs --- docs/source/about/advanced_usage.rst | 6 +- docs/source/about/installation.rst | 130 ++++++++++++++++++++------ docs/source/about/usage.rst | 10 +- docs/source/troubleshooting/linux.rst | 2 +- 4 files changed, 113 insertions(+), 35 deletions(-) diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index 0d65175e..f205e487 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -21,10 +21,8 @@ location by modifying the configuration file. Value Description ========= =========== Docker /config/ - Linux-aur /usr/share/sunshine/config/ - Linux-deb /usr/local/sunshine/config/ - Linux-rpm /usr/local/sunshine/config/ - macOS /usr/local/sunshine/config/ + Linux ~/.config/sunshine/ + macOS ~/.config/sunshine/ Windows ./config/ ========= =========== diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index f1687b3d..7c7d0f61 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -4,9 +4,12 @@ Installation ============ The recommended method for running Sunshine is to use the `binaries`_ bundled with the `latest release`_. +.. Attention:: Additional setup is required after installation. See + :ref:`Setup `. + Binaries -------- -Binaries of Sunshine are created for each release. They are available for Linux, and Windows. +Binaries of Sunshine are created for each release. They are available for Linux, macOS, and Windows. Binaries can be found in the `latest release`_. .. Tip:: Some third party packages also exist. See @@ -19,7 +22,13 @@ Docker Linux ----- -Follow the instructions for your preferred package type below. +First, follow the instructions for your preferred package type below. + +Then start sunshine with the following command, unless a start command is listed in the specified package. + +.. code-block:: bash + + sunshine AppImage ^^^^^^^^ @@ -41,6 +50,23 @@ According to AppImageLint the AppImage can run on the following distros. - [✖] CentOS 7 #. Download ``sunshine-appimage.zip`` and extract the contents to your home directory. +#. Open terminal and run the following code. + + .. code-block:: bash + + ./sunshine.AppImage --install + +Start: + + .. code-block:: bash + + ./sunshine.AppImage --install && ./sunshine.AppImage + +Uninstall: + + .. code-block:: bash + + ./sunshine.AppImage --remove AUR Package ^^^^^^^^^^^ @@ -48,10 +74,16 @@ AUR Package .. code-block:: bash - git clone https://aur.archlinux.org/sunshine-git.git - cd sunshine-git + git clone https://aur.archlinux.org/sunshine.git + cd sunshine makepkg -fi +Uninstall: + + .. code-block:: bash + + pacman -R sunshine + Debian Package ^^^^^^^^^^^^^^ .. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/pkg:deb?logo=github&style=for-the-badge @@ -65,13 +97,17 @@ Debian Package .. Tip:: You can double click the deb file to see details about the package and begin installation. +Uninstall: + + .. code-block:: bash + + sudo apt remove sunshine + Flatpak Package ^^^^^^^^^^^^^^^ .. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/pkg:flatpak?logo=github&style=for-the-badge :alt: GitHub issues by-label -.. Todo:: This package needs to have CUDA added. - #. Install `Flatpak `_ as required. #. Download ``sunshine.flatpak`` and run the following code. @@ -85,6 +121,18 @@ Flatpak Package flatpak install --user sunshine.flatpak +Start: + + .. code-block:: bash + + flatpak run dev.lizardbyte.sunshine + +Uninstall: + + .. code-block:: bash + + flatpak uninstall --delete-data sunshine.flatpak + RPM Package ^^^^^^^^^^^ .. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/pkg:rpm?logo=github&style=for-the-badge @@ -105,41 +153,62 @@ RPM Package .. Tip:: You can double click the rpm file to see details about the package and begin installation. +Uninstall: + + .. code-block:: bash + + sudo dnf remove sunshine + macOS ----- .. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/os:macos?logo=github&style=for-the-badge :alt: GitHub issues by-label pkg - .. Warning:: The `pkg` does not include runtime dependencies and should be considered experimental. +^^^ +.. Warning:: The `pkg` does not include runtime dependencies and should be considered experimental. + +#. Download the ``sunshine.pkg`` file and install it as normal. + +Uninstall: - #. Download the ``sunshine.pkg`` file and install it as normal. + .. code-block:: bash + + cd /etc/sunshine/assets + uninstall_pkg.sh Portfile - #. Install `MacPorts `_ - #. Update the Macports sources. +^^^^^^^^ +#. Install `MacPorts `_ +#. Update the Macports sources. - .. code-block:: bash + .. code-block:: bash - sudo nano /opt/local/etc/macports/sources.conf + sudo nano /opt/local/etc/macports/sources.conf - Add this line, replacing your username, below the line that starts with ``rsync``. + Add this line, replacing your username, below the line that starts with ``rsync``. - file://Users//ports + file://Users//ports - ``Ctrl+x``, then ``Y`` to exit and save changes. + ``Ctrl+x``, then ``Y`` to exit and save changes. - #. Download the ``Portfile`` to ``~/Downloads`` and run the following code. +#. Download the ``Portfile`` to ``~/Downloads`` and run the following code. - .. code-block:: bash + .. code-block:: bash - mkdir -p ~/ports/multimedia/sunshine - mv ~/Downlaods/Portfile ~/ports/multimedia/sunshine - cd ~/ports - portindex - sudo port install sunshine + mkdir -p ~/ports/multimedia/sunshine + mv ~/Downlaods/Portfile ~/ports/multimedia/sunshine + cd ~/ports + portindex + sudo port install sunshine - #. The first time you start Sunshine, you will be asked to grant access to screen recording and your microphone. +#. The first time you start Sunshine, you will be asked to grant access to screen recording and your microphone. + +Uninstall: + + .. code-block:: bash + + sudo port uninstall sunshine Windows ------- @@ -149,11 +218,18 @@ Windows .. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/os:windows:11?logo=github&style=for-the-badge :alt: GitHub issues by-label -Installed option: - #. Download and install ``sunshine-windows.exe`` +Installer +^^^^^^^^^ +#. Download and install ``sunshine-windows.exe`` + +To uninstall, find Sunshine in the list `here `_ and select "Uninstall" from the overflow +menu. Different versions of Windows may provide slightly different steps for uninstall. + +Standalone +^^^^^^^^^^ +#. Download and extract ``sunshine-windows.zip`` -Standalone option: - #. Download and extract ``sunshine-windows.zip`` +To uninstall, delete the extracted directory which contains the ``sunshine.exe`` file. .. _latest release: https://github.com/LizardByte/Sunshine/releases/latest .. _Dockerhub.io: https://hub.docker.com/repository/docker/lizardbyte/sunshine diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst index 009585ed..1870c703 100644 --- a/docs/source/about/usage.rst +++ b/docs/source/about/usage.rst @@ -56,7 +56,8 @@ Setup Linux ^^^^^ -The deb and rpm packages handle these steps automatically. The AppImage does not, third party packages may not as well. +The deb, rpm, and AppImage packages handle these steps automatically. The flatpak does not, third party packages +also may not. Sunshine needs access to `uinput` to create mouse and gamepad events. @@ -105,7 +106,7 @@ Sunshine needs access to `uinput` to create mouse and gamepad events. aur /usr/bin/sunshine ✔ deb /usr/bin/sunshine ✔ rpm /usr/bin/sunshine ✔ - AppImage ~/sunshine.AppImage ✖ + AppImage ~/sunshine.AppImage ✔ Flatpak flatpak run dev.lizardbyte.sunshine ✖ ======== ============================================== =============== @@ -169,9 +170,11 @@ All shortcuts start with CTRL + ALT + SHIFT, just like Moonlight Application List ---------------- +- Applications should be configured via the web UI. +- A basic understanding of working directories and commands is recommended. - You can use Environment variables in place of values - ``$(HOME)`` will be replaced by the value of ``$HOME`` -- ``$$`` will be replaced by ``$``, e.g. ``$$(HOME)`` will be replaced by ``$(HOME)`` +- ``$$`` will be replaced by ``$``, e.g. ``$$(HOME)`` will be become ``$(HOME)`` - ``env`` - Adds or overwrites Environment variables for the commands/applications run by Sunshine - ``"Variable name":"Variable value"`` - ``apps`` - The list of applications @@ -219,3 +222,4 @@ Considerations - When the application has been shutdown, the stream shuts down as well. - 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. +- For the Linux flatpak you must prepend commands with ``flatpak-spawn --host``. diff --git a/docs/source/troubleshooting/linux.rst b/docs/source/troubleshooting/linux.rst index 5bac8c08..a1c8b4da 100644 --- a/docs/source/troubleshooting/linux.rst +++ b/docs/source/troubleshooting/linux.rst @@ -2,7 +2,7 @@ Linux ===== -If screencasting fails with Wayland, you may need to run the following to force screencasting with X11. +If screencasting fails with KMS, you may need to run the following to force unprivileged screencasting. .. code-block:: bash From 642c4a9ed7c367b4b2abf79bec1076b8a292b749 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 24 Aug 2022 22:29:20 -0400 Subject: [PATCH 367/817] add home directory access for flatpak --- .../linux/flatpak/dev.lizardbyte.sunshine.yml | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index a61b96b4..b3a29eb3 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -6,16 +6,16 @@ sdk: org.freedesktop.Sdk command: sunshine separate-locales: false finish-args: - - --device=all - - --env=PULSE_PROP_media.category=Manager - - --persist=.config/sunshine - - --share=ipc - - --share=network - - --socket=pulseaudio - - --socket=wayland - - --socket=x11 - - --system-talk-name=org.freedesktop.Avahi - - --talk-name=org.freedesktop.Flatpak + - --device=all # access all devices + - --env=PULSE_PROP_media.category=Manager # allow sunshine to manage audio sinks + - --filesystem=home # need to save files in user's home directory + - --share=ipc # required for X11 shared memory extension + - --share=network # access network + - --socket=pulseaudio # play sounds using pulseaudio + - --socket=wayland # show windows using Wayland + - --socket=x11 # show windows using X11 + - --system-talk-name=org.freedesktop.Avahi # talk to avahi on the system bus + - --talk-name=org.freedesktop.Flatpak # talk to flatpak on the session bus cleanup: - /include From 977a4d3d4a39e9d75d023605a335f17b00bd2f7e Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Fri, 26 Aug 2022 18:20:49 -0400 Subject: [PATCH 368/817] Add dma import modifiers EGL ext --- src/platform/linux/graphics.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/linux/graphics.cpp b/src/platform/linux/graphics.cpp index 3c0adbda..59b85522 100644 --- a/src/platform/linux/graphics.cpp +++ b/src/platform/linux/graphics.cpp @@ -365,6 +365,7 @@ display_t make_display(std::variant Date: Fri, 26 Aug 2022 19:04:41 -0400 Subject: [PATCH 369/817] fix image paths --- src/process.cpp | 26 +++++++++++++++++++++++--- src_assets/linux/config/apps.json | 2 +- src_assets/windows/config/apps.json | 2 +- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/process.cpp b/src/process.cpp index 7fd900c5..a1b05e46 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -194,8 +194,9 @@ std::vector &proc_t::get_apps() { } /// Gets application image from application list. +/// Returns image from assets directory if found there. /// Returns default image if image configuration is not set. -/// returns http content-type header compatible image type +/// Returns http content-type header compatible image type. std::string proc_t::get_app_image(int app_id) { auto app_index = app_id - 1; if(app_index < 0 || app_index >= _apps.size()) { @@ -205,18 +206,37 @@ std::string proc_t::get_app_image(int app_id) { auto app_image_path = _apps[app_index].image_path; if(app_image_path.empty()) { + // image is empty, return default box image return SUNSHINE_ASSETS_DIR "/box.png"; } + // get the image extension and convert it to lowercase auto image_extension = std::filesystem::path(app_image_path).extension().string(); boost::to_lower(image_extension); + // return the default box image if extension is not "png" + if(image_extension != ".png") { + return SUNSHINE_ASSETS_DIR "/box.png"; + } + + // check if image is in assets directory + if(std::filesystem::exists(SUNSHINE_ASSETS_DIR + app_image_path)) { + return SUNSHINE_ASSETS_DIR + app_image_path; + } + else if(app_image_path == "./assets/steam.png") { + // handle old default steam image definition + return SUNSHINE_ASSETS_DIR "/steam.png"; + } + + // check if specified image exists std::error_code code; - if(!std::filesystem::exists(app_image_path, code) || image_extension != ".png") { + if(!std::filesystem::exists(app_image_path, code)) { + // return default box image if image does not exist return SUNSHINE_ASSETS_DIR "/box.png"; } - // return only "content-type" http header compatible image type. + // image is a png, and not in assets directory + // return only "content-type" http header compatible image type return app_image_path; } diff --git a/src_assets/linux/config/apps.json b/src_assets/linux/config/apps.json index 552dabe2..4a848bd8 100644 --- a/src_assets/linux/config/apps.json +++ b/src_assets/linux/config/apps.json @@ -14,7 +14,7 @@ "output":"steam.txt", "detached":["setsid steam steam://open/bigpicture"], - "image-path":"./assets/steam.png" + "image-path":"steam.png" } ] } diff --git a/src_assets/windows/config/apps.json b/src_assets/windows/config/apps.json index 419f2be4..379dd2fb 100644 --- a/src_assets/windows/config/apps.json +++ b/src_assets/windows/config/apps.json @@ -8,7 +8,7 @@ "output":"steam.txt", "detached":["steam steam://open/bigpicture"], - "image-path":"./assets/steam.png" + "image-path":"steam.png" } ] } From 43fa4100d2a99a649c1b447bc51f9894211b4f26 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 26 Aug 2022 17:05:12 -0400 Subject: [PATCH 370/817] update docs - add information about various ports - correct typos in macos installation instructions --- docs/source/about/advanced_usage.rst | 23 ++++++++++++++++++++++- docs/source/about/installation.rst | 4 ++-- docs/source/about/usage.rst | 4 ++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index f205e487..b8613850 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -443,7 +443,28 @@ port ^^^^ Description - Set the family of ports used by Sunshine. + Set the family of ports used by Sunshine. Changing this value will offset other ports per the table below. + +.. table:: + :widths: auto + + ================ ============ =========================== + Port Description Default Port Difference from config port + ================ ============ =========================== + HTTPS 47984 TCP -5 + HTTP 47989 TCP 0 + Web 47990 TCP +1 + RTSP 48010 TCP +21 + Video 47998 UDP +9 + Control 47999 UDP +10 + Audio 48000 UDP +11 + tbd 48002 UDP +13 + ================ ============ =========================== + +.. Attention:: Custom ports are only allowed on select Moonlight clients. + +.. Todo:: Determine the function of port 48002 UDP. See + `here `_. Default ``47989`` diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 7c7d0f61..eecdc04f 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -188,7 +188,7 @@ Portfile Add this line, replacing your username, below the line that starts with ``rsync``. - file://Users//ports + ``file:///Users//ports`` ``Ctrl+x``, then ``Y`` to exit and save changes. @@ -197,7 +197,7 @@ Portfile .. code-block:: bash mkdir -p ~/ports/multimedia/sunshine - mv ~/Downlaods/Portfile ~/ports/multimedia/sunshine + mv ~/Downloads/Portfile ~/ports/multimedia/sunshine/ cd ~/ports portindex sudo port install sunshine diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst index 1870c703..f1012192 100644 --- a/docs/source/about/usage.rst +++ b/docs/source/about/usage.rst @@ -39,9 +39,9 @@ Usage Network ------- -Sunshine will be available on port 47990 by default. +The Sunshine user interface will be available on port 47990 by default. -.. Danger:: Do not expose port 47990, or the web ui, to the internet! +.. Warning:: Exposing ports to the internet can be dangerous. Do this at your own risk. Arguments --------- From f7d4f49809ee76b990f3cea3dc4a02e1076d3391 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 28 Aug 2022 15:51:17 -0400 Subject: [PATCH 371/817] fix windows `add-firewall-rule` script --- src_assets/windows/misc/firewall/add-firewall-rule.bat | 9 ++++++++- src_assets/windows/misc/service/install-service.bat | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src_assets/windows/misc/firewall/add-firewall-rule.bat b/src_assets/windows/misc/firewall/add-firewall-rule.bat index 8c3e7b9e..d1a4b2fd 100644 --- a/src_assets/windows/misc/firewall/add-firewall-rule.bat +++ b/src_assets/windows/misc/firewall/add-firewall-rule.bat @@ -1,7 +1,14 @@ @echo off +rem Get sunshine root directory +for %%I in ("%~dp0\..") do set "ROOT_DIR=%%~fI" + set RULE_NAME=Sunshine -set PROGRAM_BIN="%~dp0sunshine.exe" +set PROGRAM_BIN="%ROOT_DIR%\sunshine.exe" + +echo %PROGRAM_BIN% + +pause rem Add the rule netsh advfirewall firewall add rule name=%RULE_NAME% dir=in action=allow protocol=tcp program=%PROGRAM_BIN% enable=yes diff --git a/src_assets/windows/misc/service/install-service.bat b/src_assets/windows/misc/service/install-service.bat index 887c5aa3..af27fec1 100644 --- a/src_assets/windows/misc/service/install-service.bat +++ b/src_assets/windows/misc/service/install-service.bat @@ -1,10 +1,10 @@ @echo off rem Get sunshine root directory -for %%I in ("%~dp0\..") do set "root_dir=%%~fI" +for %%I in ("%~dp0\..") do set "ROOT_DIR=%%~fI" set SERVICE_NAME=sunshinesvc -set SERVICE_BIN="%root_dir%\tools\sunshinesvc.exe" +set SERVICE_BIN="%ROOT_DIR%\tools\sunshinesvc.exe" set SERVICE_START_TYPE=auto rem Check if sunshinesvc already exists From 58ed5ba3ceafda9a67da2946e00509ef421855cb Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 28 Aug 2022 16:42:00 -0400 Subject: [PATCH 372/817] fix typos and comment formatting --- src/audio.cpp | 6 +++--- src/cbs.cpp | 6 +++--- src/cbs.h | 2 +- src/config.cpp | 4 ++-- src/confighttp.cpp | 15 +++++++-------- src/confighttp.h | 4 +--- src/crypto.cpp | 10 ++++------ src/crypto.h | 2 -- src/httpcommon.cpp | 4 ++-- src/httpcommon.h | 2 +- src/input.cpp | 10 ++++------ src/input.h | 4 +--- src/main.cpp | 4 +--- src/main.h | 4 +--- src/network.cpp | 4 +--- src/network.h | 4 +--- src/nvhttp.cpp | 4 +--- src/nvhttp.h | 4 +--- src/platform/linux/kmsgrab.cpp | 2 +- src/process.cpp | 12 +++++------- src/process.h | 4 +--- src/rtsp.cpp | 16 +++++++--------- src/rtsp.h | 4 +--- src/stream.cpp | 17 +++++++---------- src/stream.h | 4 +--- src/sync.h | 4 +--- src/task_pool.h | 2 +- src/thread_pool.h | 2 +- src/thread_safe.h | 10 ++++------ src/upnp.cpp | 2 +- src/upnp.h | 2 +- src/uuid.h | 4 +--- src/video.cpp | 19 ++++--------------- src/video.h | 4 +--- 34 files changed, 73 insertions(+), 128 deletions(-) diff --git a/src/audio.cpp b/src/audio.cpp index 2894d128..0242ba2e 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -75,7 +75,7 @@ auto control_shared = safe::make_shared(start_audio_control, stop_a void encodeThread(sample_queue_t samples, config_t config, void *channel_data) { auto packets = mail::man->queue(mail::audio_packets); - //FIXME: Pick correct opus_stream_config_t based on config.channels + // FIXME: Pick correct opus_stream_config_t based on config.channels auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])]; opus_t opus { opus_multistream_encoder_create( @@ -120,7 +120,7 @@ void encodeThread(sample_queue_t samples, config_t config, void *channel_data) { void capture(safe::mail_t mail, config_t config, void *channel_data) { auto shutdown_event = mail->event(mail::shutdown); - //FIXME: Pick correct opus_stream_config_t based on config.channels + // FIXME: Pick correct opus_stream_config_t based on config.channels auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])]; auto ref = control_shared.ref(); @@ -135,7 +135,7 @@ void capture(safe::mail_t mail, config_t config, void *channel_data) { return; } - // Order of priorty: + // Order of priority: // 1. Config // 2. Virtual if available // 3. Host diff --git a/src/cbs.cpp b/src/cbs.cpp index e4e24571..d50bd195 100644 --- a/src/cbs.cpp +++ b/src/cbs.cpp @@ -124,9 +124,9 @@ util::buffer_t make_sps_h264(const AVCodecContext *ctx) { sps.seq_parameter_set_id = 0; sps.chroma_format_idc = 1; - sps.log2_max_frame_num_minus4 = 3; //4; + 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.log2_max_pic_order_cnt_lsb_minus4 = 0; // 4; sps.max_num_ref_frames = dpb_frame; @@ -297,4 +297,4 @@ bool validate_sps(const AVPacket *packet, int codec_id) { return ((CodedBitstreamH265Context *)ctx->priv_data)->active_sps->vui_parameters_present_flag; } -} // namespace cbs \ No newline at end of file +} // namespace cbs diff --git a/src/cbs.h b/src/cbs.h index dc772dd9..cd989b4a 100644 --- a/src/cbs.h +++ b/src/cbs.h @@ -31,4 +31,4 @@ h264_t make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet); bool validate_sps(const AVPacket *packet, int codec_id); } // namespace cbs -#endif \ No newline at end of file +#endif diff --git a/src/config.cpp b/src/config.cpp index 9d27734b..b7f483ac 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -105,14 +105,14 @@ enum quality_e : int { enum class rc_hevc_e : int { constqp, /**< Constant QP mode */ vbr_latency, /**< Latency Constrained Variable Bitrate */ - vbr_peak, /**< Peak Contrained Variable Bitrate */ + vbr_peak, /**< Peak Constrained Variable Bitrate */ cbr, /**< Constant bitrate mode */ }; enum class rc_h264_e : int { constqp, /**< Constant QP mode */ cbr, /**< Constant bitrate mode */ - vbr_peak, /**< Peak Contrained Variable Bitrate */ + vbr_peak, /**< Peak Constrained Variable Bitrate */ vbr_latency, /**< Latency Constrained Variable Bitrate */ }; diff --git a/src/confighttp.cpp b/src/confighttp.cpp index c21089de..f5f2ce09 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -1,4 +1,3 @@ -// // Created by TheElixZammuto on 2021-05-09. // TODO: Authentication, better handling of routes common to nvhttp, cleanup @@ -92,7 +91,7 @@ bool authenticate(resp_https_t response, req_https_t request) { return false; } - //If credentials are shown, redirect the user to a /welcome page + // If credentials are shown, redirect the user to a /welcome page if(config::sunshine.username.empty()) { send_redirect(response, request, "/welcome"); return false; @@ -314,7 +313,7 @@ void saveApp(resp_https_t response, req_https_t request) { BOOST_LOG(fatal) << config::stream.file_apps; try { - //TODO: Input Validation + // TODO: Input Validation pt::read_json(ss, inputTree); pt::read_json(config::stream.file_apps, fileTree); @@ -335,7 +334,7 @@ void saveApp(resp_https_t response, req_https_t request) { apps_node.push_back(std::make_pair("", inputTree)); } else { - //Unfortuantely Boost PT does not allow to directly edit the array, copy should do the trick + // Unfortunately Boost PT does not allow to directly edit the array, copy should do the trick pt::ptree newApps; int i = 0; for(const auto &kv : apps_node) { @@ -388,7 +387,7 @@ void deleteApp(resp_https_t response, req_https_t request) { return; } else { - //Unfortuantely Boost PT does not allow to directly edit the array, copy should do the trick + // Unfortunately Boost PT does not allow to directly edit the array, copy should do the trick pt::ptree newApps; int i = 0; for(const auto &kv : apps_node) { @@ -452,7 +451,7 @@ void saveConfig(resp_https_t response, req_https_t request) { }); pt::ptree inputTree; try { - //TODO: Input Validation + // TODO: Input Validation pt::read_json(ss, inputTree); for(const auto &kv : inputTree) { std::string value = inputTree.get(kv.first); @@ -488,7 +487,7 @@ void savePassword(resp_https_t response, req_https_t request) { }); try { - //TODO: Input Validation + // TODO: Input Validation pt::read_json(ss, inputTree); auto username = inputTree.count("currentUsername") > 0 ? inputTree.get("currentUsername") : ""; auto newUsername = inputTree.get("newUsername"); @@ -544,7 +543,7 @@ void savePin(resp_https_t response, req_https_t request) { }); try { - //TODO: Input Validation + // TODO: Input Validation pt::read_json(ss, inputTree); std::string pin = inputTree.get("pin"); outputTree.put("status", nvhttp::pin(pin)); diff --git a/src/confighttp.h b/src/confighttp.h index 1e0c4a76..cae32aef 100644 --- a/src/confighttp.h +++ b/src/confighttp.h @@ -1,6 +1,4 @@ -// // Created by loki on 6/3/19. -// #ifndef SUNSHINE_CONFIGHTTP_H #define SUNSHINE_CONFIGHTTP_H @@ -18,4 +16,4 @@ constexpr auto PORT_HTTPS = 1; void start(); } // namespace confighttp -#endif //SUNSHINE_CONFIGHTTP_H +#endif // SUNSHINE_CONFIGHTTP_H diff --git a/src/crypto.cpp b/src/crypto.cpp index 006cf7fb..45b65abc 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -1,13 +1,11 @@ -// // Created by loki on 5/31/19. -// #include "crypto.h" #include namespace crypto { using big_num_t = util::safe_ptr; -//using rsa_t = util::safe_ptr; +// using rsa_t = util::safe_ptr; using asn1_string_t = util::safe_ptr; cert_chain_t::cert_chain_t() : _certs {}, _cert_ctx { X509_STORE_CTX_new() } {} @@ -22,7 +20,7 @@ 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 + // 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; @@ -274,7 +272,7 @@ int cbc_t::encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_ int len; - int size = plaintext.size(); //round_to_pkcs7_padded(plaintext.size()); + 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) { @@ -492,4 +490,4 @@ std::string rand_alphabet(std::size_t bytes, const std::string_view &alphabet) { return value; } -} // namespace crypto \ No newline at end of file +} // namespace crypto diff --git a/src/crypto.h b/src/crypto.h index 7f648311..46259d1a 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -1,6 +1,4 @@ -// // Created by loki on 6/1/19. -// #ifndef SUNSHINE_CRYPTO_H #define SUNSHINE_CRYPTO_H diff --git a/src/httpcommon.cpp b/src/httpcommon.cpp index 91074144..5f1955a6 100644 --- a/src/httpcommon.cpp +++ b/src/httpcommon.cpp @@ -44,7 +44,7 @@ int init() { if(clean_slate) { unique_id = util::uuid_t::generate().string(); - auto dir = std::filesystem::temp_directory_path() / "Sushine"sv; + 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(); } @@ -180,4 +180,4 @@ int create_creds(const std::string &pkey, const std::string &cert) { return 0; } -} // namespace http \ No newline at end of file +} // namespace http diff --git a/src/httpcommon.h b/src/httpcommon.h index 37d8451f..e1a1509a 100644 --- a/src/httpcommon.h +++ b/src/httpcommon.h @@ -16,4 +16,4 @@ extern std::string unique_id; extern net::net_e origin_pin_allowed; extern net::net_e origin_web_ui_allowed; -} // namespace http \ No newline at end of file +} // namespace http diff --git a/src/input.cpp b/src/input.cpp index 050a8687..c40cdef5 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -1,6 +1,4 @@ -// // Created by loki on 6/20/19. -// // define uint32_t for #include @@ -89,7 +87,7 @@ struct gamepad_t { // When emulating the HOME button, we may need to artificially release the back button. // Afterwards, the gamepad state on sunshine won't match the state on Moonlight. - // To prevent Sunshine from sending erronious input data to the active application, + // To prevent Sunshine from sending erroneous input data to the active application, // Sunshine forces the button to be in a specific state until the gamepad state matches that of // Moonlight once more. button_state_e back_button_state; @@ -316,11 +314,11 @@ void passthrough(std::shared_ptr &input, PNV_MOUSE_BUTTON_PACKET packet /*/ * When Moonlight sends mouse input through absolute coordinates, * it's possible that BUTTON_RIGHT is pressed down immediately after releasing BUTTON_LEFT. - * As a result, Sunshine will left click on hyperlinks in the browser before right clicking + * As a result, Sunshine will left-click on hyperlinks in the browser before right-clicking * * This can be solved by delaying BUTTON_LEFT, however, any delay on input is undesirable during gaming * As a compromise, Sunshine will only put delays on BUTTON_LEFT when - * absolute mouse coordinates have been send. + * absolute mouse coordinates have been sent. * * Try to make sure BUTTON_RIGHT gets called before BUTTON_LEFT is released. * @@ -428,7 +426,7 @@ void passthrough(std::shared_ptr &input, PNV_KEYBOARD_PACKET packet) { if(!pressed) { if(!release) { // A new key has been pressed down, we need to check for key combo's - // If a keycombo has been pressed down, don't pass it through + // If a key-combo has been pressed down, don't pass it through if(input->shortcutFlags == input_t::SHORTCUT && apply_shortcut(keyCode) > 0) { return; } diff --git a/src/input.h b/src/input.h index c9c0590e..ce291623 100644 --- a/src/input.h +++ b/src/input.h @@ -1,6 +1,4 @@ -// // Created by loki on 6/20/19. -// #ifndef SUNSHINE_INPUT_H #define SUNSHINE_INPUT_H @@ -32,4 +30,4 @@ struct touch_port_t : public platf::touch_port_t { }; } // namespace input -#endif //SUNSHINE_INPUT_H +#endif // SUNSHINE_INPUT_H diff --git a/src/main.cpp b/src/main.cpp index 0f5581d6..58271f83 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,4 @@ -// // Created by loki on 5/30/19. -// #include "process.h" @@ -289,7 +287,7 @@ int main(int argc, char *argv[]) { upnp_unmap = upnp::start(); }); - //FIXME: Temporary workaround: Simple-Web_server needs to be updated or replaced + // FIXME: Temporary workaround: Simple-Web_server needs to be updated or replaced if(shutdown_event->peek()) { return 0; } diff --git a/src/main.h b/src/main.h index aa9558b3..89c4dbdc 100644 --- a/src/main.h +++ b/src/main.h @@ -1,6 +1,4 @@ -// // Created by loki on 12/22/19. -// #ifndef SUNSHINE_MAIN_H #define SUNSHINE_MAIN_H @@ -54,4 +52,4 @@ MAIL(rumble); } // namespace mail -#endif //SUNSHINE_MAIN_H +#endif // SUNSHINE_MAIN_H diff --git a/src/network.cpp b/src/network.cpp index ccf9e32f..90254899 100644 --- a/src/network.cpp +++ b/src/network.cpp @@ -1,6 +1,4 @@ -// // Created by loki on 12/27/19. -// #include "network.h" #include "utility.h" @@ -112,4 +110,4 @@ void free_host(ENetHost *host) { enet_host_destroy(host); } -} // namespace net \ No newline at end of file +} // namespace net diff --git a/src/network.h b/src/network.h index 88f18a40..bd371841 100644 --- a/src/network.h +++ b/src/network.h @@ -1,6 +1,4 @@ -// // Created by loki on 12/27/19. -// #ifndef SUNSHINE_NETWORK_H #define SUNSHINE_NETWORK_H @@ -32,4 +30,4 @@ net_e from_address(const std::string_view &view); host_t host_create(ENetAddress &addr, std::size_t peers, std::uint16_t port); } // namespace net -#endif //SUNSHINE_NETWORK_H +#endif // SUNSHINE_NETWORK_H diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index 67f8878c..47067d26 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -1,6 +1,4 @@ -// // Created by loki on 6/3/19. -// #define BOOST_BIND_GLOBAL_PLACEHOLDERS @@ -297,7 +295,7 @@ void clientpairingsecret(std::shared_ptr> &add_cer // if hash not correct, probably MITM if(std::memcmp(hash.data(), sess.clienthash.data(), hash.size())) { - //TODO: log + // TODO: log map_id_sess.erase(client.uniqueID); tree.put("root.paired", 0); diff --git a/src/nvhttp.h b/src/nvhttp.h index af077a56..ae96c6db 100644 --- a/src/nvhttp.h +++ b/src/nvhttp.h @@ -1,6 +1,4 @@ -// // Created by loki on 6/3/19. -// #ifndef SUNSHINE_NVHTTP_H #define SUNSHINE_NVHTTP_H @@ -17,4 +15,4 @@ bool pin(std::string pin); void erase_all_clients(); } // namespace nvhttp -#endif //SUNSHINE_NVHTTP_H +#endif // SUNSHINE_NVHTTP_H diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index 3b9257be..04763026 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -167,7 +167,7 @@ static std::uint32_t from_view(const std::string_view &string) { _CONVERT("eDP"sv, eDP); _CONVERT("DSI"sv, DSI); - BOOST_LOG(error) << "Unknown Monitor connector type ["sv << string << "]: Please report this to the Github issue tracker"sv; + BOOST_LOG(error) << "Unknown Monitor connector type ["sv << string << "]: Please report this to the GitHub issue tracker"sv; return DRM_MODE_CONNECTOR_Unknown; } diff --git a/src/process.cpp b/src/process.cpp index 7fd900c5..db9056a8 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -1,6 +1,4 @@ -// // Created by loki on 12/14/19. -// #define BOOST_BIND_GLOBAL_PLACEHOLDERS @@ -47,7 +45,7 @@ int exe(const std::string &cmd, bp::environment &env, file_t &file, std::error_c int proc_t::execute(int app_id) { if(!running() && _app_id != -1) { - // previous process exited on it's own, reset _process_handle + // previous process exited on its own, reset _process_handle _process_handle = bp::group(); _app_id = -1; @@ -73,7 +71,7 @@ int proc_t::execute(int app_id) { } std::error_code ec; - //Executed when returning from function + // Executed when returning from function auto fg = util::fail_guard([&]() { terminate(); }); @@ -193,9 +191,9 @@ std::vector &proc_t::get_apps() { return _apps; } -/// Gets application image from application list. -/// Returns default image if image configuration is not set. -/// returns http content-type header compatible image type +// Gets application image from application list. +// Returns default image if image configuration is not set. +// returns http content-type header compatible image type std::string proc_t::get_app_image(int app_id) { auto app_index = app_id - 1; if(app_index < 0 || app_index >= _apps.size()) { diff --git a/src/process.h b/src/process.h index 2b3fdad8..eab324a7 100644 --- a/src/process.h +++ b/src/process.h @@ -1,6 +1,4 @@ -// // Created by loki on 12/14/19. -// #ifndef SUNSHINE_PROCESS_H #define SUNSHINE_PROCESS_H @@ -105,4 +103,4 @@ std::optional parse(const std::string &file_name); extern proc_t proc; } // namespace proc -#endif //SUNSHINE_PROCESS_H +#endif // SUNSHINE_PROCESS_H diff --git a/src/rtsp.cpp b/src/rtsp.cpp index 983aff96..a2ffa143 100644 --- a/src/rtsp.cpp +++ b/src/rtsp.cpp @@ -1,6 +1,4 @@ -// // Created by loki on 2/2/20. -// #define BOOST_BIND_GLOBAL_PLACEHOLDERS @@ -123,7 +121,7 @@ class socket_t : public std::enable_shared_from_this { socket->read_payload(); }); - auto content_lenght = 0; + auto content_length = 0; for(auto option = req->options; option != nullptr; option = option->next) { if("Content-length"sv == option->option) { BOOST_LOG(debug) << "Found Content-Length: "sv << option->content << " bytes"sv; @@ -133,14 +131,14 @@ class socket_t : public std::enable_shared_from_this { std::string_view content { option->content }; auto begin = std::find_if(std::begin(content), std::end(content), [](auto ch) { return (bool)std::isdigit(ch); }); - content_lenght = util::from_chars(begin, std::end(content)); + content_length = util::from_chars(begin, std::end(content)); break; } } - if(end - socket->crlf >= content_lenght) { - if(end - socket->crlf > content_lenght) { - BOOST_LOG(warning) << "(end - socket->crlf) > content_lenght -- "sv << (std::size_t)(end - socket->crlf) << " > "sv << content_lenght; + if(end - socket->crlf >= content_length) { + if(end - socket->crlf > content_length) { + BOOST_LOG(warning) << "(end - socket->crlf) > content_length -- "sv << (std::size_t)(end - socket->crlf) << " > "sv << content_length; } fg.disable(); @@ -271,7 +269,7 @@ class rtsp_server_t { if(ec) { BOOST_LOG(error) << "Couldn't accept incoming connections: "sv << ec.message(); - //Stop server + // Stop server clear(); return; } @@ -380,7 +378,7 @@ void launch_session_raise(launch_session_t launch_session) { } int session_count() { - // Ensure session_count is up to date + // Ensure session_count is up-to-date server.clear(false); return server.session_count(); diff --git a/src/rtsp.h b/src/rtsp.h index f038a5cf..f295e615 100644 --- a/src/rtsp.h +++ b/src/rtsp.h @@ -1,6 +1,4 @@ -// // Created by loki on 2/2/20. -// #ifndef SUNSHINE_RTSP_H #define SUNSHINE_RTSP_H @@ -27,4 +25,4 @@ void rtpThread(); } // namespace stream -#endif //SUNSHINE_RTSP_H +#endif // SUNSHINE_RTSP_H diff --git a/src/stream.cpp b/src/stream.cpp index 27fbe85a..7a357c12 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -1,6 +1,4 @@ -// // Created by loki on 6/5/19. -// #include "process.h" @@ -240,11 +238,10 @@ struct broadcast_ctx_t { udp::socket video_sock { io }; udp::socket audio_sock { io }; - // This is purely for adminitrative purposes. - // - // It's possible two instances of Moonlight are behind a NAT. - // From Sunshine's point of view, the ip addresses are identical - // We need some way to know what ports are already used for different streams + // This is purely for administrative purposes. + // It's possible two instances of Moonlight are behind a NAT. + // From Sunshine's point of view, the ip addresses are identical + // We need some way to know what ports are already used for different streams util::sync_t>> audio_video_connections; control_server_t control_server; @@ -767,7 +764,7 @@ void controlBroadcastThread(control_server_t *server) { } // Let all remaining connections know the server is shutting down - // reason: gracefull termination + // reason: graceful termination std::uint32_t reason = 0x80030023; control_terminate_t plaintext; @@ -932,7 +929,7 @@ void videoBroadcastThread(udp::socket &sock) { // With a fecpercentage of 255, if payload_new is broken up into more than a 100 data_shards // it will generate greater than DATA_SHARDS_MAX shards. - // Therefore, we start breaking the data up into three seperate fec blocks. + // Therefore, we start breaking the data up into three separate fec blocks. auto multi_fec_threshold = 90 * blocksize; // We can go up to 4 fec blocks, but 3 is plenty @@ -1331,7 +1328,7 @@ void join(session_t &session) { session.audioThread.join(); BOOST_LOG(debug) << "Waiting for control to end..."sv; session.controlEnd.view(); - //Reset input on session stop to avoid stuck repeated keys + // Reset input on session stop to avoid stuck repeated keys BOOST_LOG(debug) << "Resetting Input..."sv; input::reset(session.input); diff --git a/src/stream.h b/src/stream.h index b1e17b0f..8e054aa9 100644 --- a/src/stream.h +++ b/src/stream.h @@ -1,6 +1,4 @@ -// // Created by loki on 6/5/19. -// #ifndef SUNSHINE_STREAM_H #define SUNSHINE_STREAM_H @@ -45,4 +43,4 @@ state_e state(session_t &session); } // namespace session } // namespace stream -#endif //SUNSHINE_STREAM_H +#endif // SUNSHINE_STREAM_H diff --git a/src/sync.h b/src/sync.h index 8d67de2a..019adc9e 100644 --- a/src/sync.h +++ b/src/sync.h @@ -1,6 +1,4 @@ -// // Created by loki on 16-4-19. -// #ifndef SUNSHINE_SYNC_H #define SUNSHINE_SYNC_H @@ -92,4 +90,4 @@ class sync_t { } // namespace util -#endif //T_MAN_SYNC_H +#endif // SUNSHINE_SYNC_H diff --git a/src/task_pool.h b/src/task_pool.h index fc07e8ab..8352289c 100644 --- a/src/task_pool.h +++ b/src/task_pool.h @@ -17,7 +17,7 @@ namespace util { class _ImplBase { public: - //_unique_base_type _this_ptr; + // _unique_base_type _this_ptr; inline virtual ~_ImplBase() = default; diff --git a/src/thread_pool.h b/src/thread_pool.h index 8048e6d0..b26c8e34 100644 --- a/src/thread_pool.h +++ b/src/thread_pool.h @@ -7,7 +7,7 @@ namespace util { /* * Allow threads to execute unhindered - * while keeping full controll over the threads. + * while keeping full control over the threads. */ class ThreadPool : public TaskPool { public: diff --git a/src/thread_safe.h b/src/thread_safe.h index d1541ee9..b14b3234 100644 --- a/src/thread_safe.h +++ b/src/thread_safe.h @@ -1,6 +1,4 @@ -// // Created by loki on 6/10/19. -// #ifndef SUNSHINE_THREAD_SAFE_H #define SUNSHINE_THREAD_SAFE_H @@ -37,7 +35,7 @@ class event_t { _cv.notify_all(); } - // pop and view shoud not be used interchangebly + // pop and view shoud not be used interchangeably status_t pop() { std::unique_lock ul { _lock }; @@ -58,7 +56,7 @@ class event_t { return val; } - // pop and view shoud not be used interchangebly + // pop and view shoud not be used interchangeably template status_t pop(std::chrono::duration delay) { std::unique_lock ul { _lock }; @@ -78,7 +76,7 @@ class event_t { return val; } - // pop and view shoud not be used interchangebly + // pop and view shoud not be used interchangeably const status_t &view() { std::unique_lock ul { _lock }; @@ -508,4 +506,4 @@ inline void cleanup(mail_raw_t *mail) { } } // namespace safe -#endif //SUNSHINE_THREAD_SAFE_H +#endif // SUNSHINE_THREAD_SAFE_H diff --git a/src/upnp.cpp b/src/upnp.cpp index a8fc6f4b..a5a99e86 100644 --- a/src/upnp.cpp +++ b/src/upnp.cpp @@ -181,4 +181,4 @@ std::unique_ptr start() { return std::make_unique(std::move(urls), data, std::move(mappings)); } -} // namespace upnp \ No newline at end of file +} // namespace upnp diff --git a/src/upnp.h b/src/upnp.h index 478d69b1..2ab57df6 100644 --- a/src/upnp.h +++ b/src/upnp.h @@ -7,4 +7,4 @@ namespace upnp { [[nodiscard]] std::unique_ptr start(); } -#endif \ No newline at end of file +#endif diff --git a/src/uuid.h b/src/uuid.h index 6d8abe80..c3a026c0 100644 --- a/src/uuid.h +++ b/src/uuid.h @@ -1,6 +1,4 @@ -// // Created by loki on 8-2-19. -// #ifndef T_MAN_UUID_H #define T_MAN_UUID_H @@ -76,4 +74,4 @@ union uuid_t { } }; } // namespace util -#endif //T_MAN_UUID_H +#endif // T_MAN_UUID_H diff --git a/src/video.cpp b/src/video.cpp index f49cac95..bec77c81 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -1,6 +1,4 @@ -// // Created by loki on 6/6/19. -// #include #include @@ -225,7 +223,7 @@ class swdevice_t : public platf::hwdevice_t { ~swdevice_t() override {} - // Store ownsership when frame is hw_frame + // Store ownership when frame is hw_frame frame_t hw_frame; frame_t sw_frame; @@ -239,7 +237,7 @@ class swdevice_t : public platf::hwdevice_t { enum flag_e { DEFAULT = 0x00, PARALLEL_ENCODING = 0x01, - H264_ONLY = 0x02, // When HEVC is to heavy + H264_ONLY = 0x02, // When HEVC is too heavy LIMITED_GOP_SIZE = 0x04, // Some encoders don't like it when you have an infinite GOP_SIZE. *cough* VAAPI *cough* SINGLE_SLICE_ONLY = 0x08, // Never use multiple slices <-- Older intel iGPU's ruin it for everyone else :P }; @@ -1622,12 +1620,7 @@ bool validate_encoder(encoder_t &encoder) { } int init() { - BOOST_LOG(info) << "//////////////////////////////////////////////////////////////////"sv; - BOOST_LOG(info) << "// //"sv; - BOOST_LOG(info) << "// Testing for available encoders, this may generate errors. //"sv; - BOOST_LOG(info) << "// You can safely ignore those errors. //"sv; - BOOST_LOG(info) << "// //"sv; - BOOST_LOG(info) << "//////////////////////////////////////////////////////////////////"sv; + BOOST_LOG(info) << "// Testing for available encoders, this may generate errors. You can safely ignore those errors. //"sv; KITTY_WHILE_LOOP(auto pos = std::begin(encoders), pos != std::end(encoders), { if( @@ -1643,11 +1636,7 @@ int init() { }) BOOST_LOG(info); - BOOST_LOG(info) << "//////////////////////////////////////////////////////////////"sv; - BOOST_LOG(info) << "// //"sv; - BOOST_LOG(info) << "// Ignore any errors mentioned above, they are not relevant //"sv; - BOOST_LOG(info) << "// //"sv; - BOOST_LOG(info) << "//////////////////////////////////////////////////////////////"sv; + BOOST_LOG(info) << "// Ignore any errors mentioned above, they are not relevant. //"sv; BOOST_LOG(info); if(encoders.empty()) { diff --git a/src/video.h b/src/video.h index 05df4463..6b8d6e57 100644 --- a/src/video.h +++ b/src/video.h @@ -1,6 +1,4 @@ -// // Created by loki on 6/9/19. -// #ifndef SUNSHINE_VIDEO_H #define SUNSHINE_VIDEO_H @@ -84,4 +82,4 @@ void capture( int init(); } // namespace video -#endif //SUNSHINE_VIDEO_H +#endif // SUNSHINE_VIDEO_H From 2298cbbe209ce8fc97f042cd83b10c88c14b7702 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 30 Aug 2022 12:39:12 -0400 Subject: [PATCH 373/817] remove debugging commands from `add-firewall-rule.bat` --- src_assets/windows/misc/firewall/add-firewall-rule.bat | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src_assets/windows/misc/firewall/add-firewall-rule.bat b/src_assets/windows/misc/firewall/add-firewall-rule.bat index d1a4b2fd..81d5dd92 100644 --- a/src_assets/windows/misc/firewall/add-firewall-rule.bat +++ b/src_assets/windows/misc/firewall/add-firewall-rule.bat @@ -6,10 +6,6 @@ for %%I in ("%~dp0\..") do set "ROOT_DIR=%%~fI" set RULE_NAME=Sunshine set PROGRAM_BIN="%ROOT_DIR%\sunshine.exe" -echo %PROGRAM_BIN% - -pause - rem Add the rule netsh advfirewall firewall add rule name=%RULE_NAME% dir=in action=allow protocol=tcp program=%PROGRAM_BIN% enable=yes netsh advfirewall firewall add rule name=%RULE_NAME% dir=in action=allow protocol=udp program=%PROGRAM_BIN% enable=yes From 674a9da1666a8182009176e445db742b04ea6766 Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Sun, 11 Sep 2022 08:34:52 -0400 Subject: [PATCH 374/817] Update boost version in portfile --- packaging/macos/Portfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/macos/Portfile b/packaging/macos/Portfile index 8707cedc..c05b6ea0 100644 --- a/packaging/macos/Portfile +++ b/packaging/macos/Portfile @@ -35,7 +35,7 @@ depends_lib port:avahi \ port:ffmpeg \ port:libopus -boost.version 1.76 +boost.version 1.80 configure.args -DCMAKE_INSTALL_PREFIX=${prefix} \ -DSUNSHINE_ASSETS_DIR=etc/sunshine/assets \ From d4a4096bba4f4aa92098812f684ca6b6e66c72d9 Mon Sep 17 00:00:00 2001 From: Michael Rogers Date: Sat, 2 Jul 2022 19:22:05 -0500 Subject: [PATCH 375/817] Added some sanity checks for no sink being detected. --- src/audio.cpp | 8 +++++++- src/platform/linux/audio.cpp | 27 ++++++++++++++++----------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/audio.cpp b/src/audio.cpp index 2894d128..779dd5a5 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -162,8 +162,14 @@ void capture(safe::mail_t mail, config_t config, void *channel_data) { 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 - if(!config.flags[config_t::HOST_AUDIO] && control->set_sink(*sink)) { + else if(!config.flags[config_t::HOST_AUDIO] && control->set_sink(*sink)) { return; } } diff --git a/src/platform/linux/audio.cpp b/src/platform/linux/audio.cpp index d3ac03f9..ceab3eba 100644 --- a/src/platform/linux/audio.cpp +++ b/src/platform/linux/audio.cpp @@ -343,12 +343,7 @@ class server_t : public audio_control_t { } auto sink_name = get_default_sink_name(); - if(sink_name.empty()) { - BOOST_LOG(warning) << "Couldn't find an active sink"sv; - } - else { - sink.host = sink_name; - } + sink.host = sink_name; if(index.stereo == PA_INVALID_INDEX) { index.stereo = load_null(stereo, speaker::map_stereo, sizeof(speaker::map_stereo)); @@ -380,6 +375,10 @@ class server_t : public audio_control_t { } } + if(sink_name.empty()) { + BOOST_LOG(warning) << "Couldn't find an active default sink. Continuing with virtual audio only."sv; + } + if(nullcount == 3) { sink.null = std::make_optional(sink_t::null_t { stereo, surround51, surround71 }); } @@ -388,8 +387,8 @@ class server_t : public audio_control_t { } std::string get_default_sink_name() { - std::string sink_name = "@DEFAULT_SINK@"s; - auto alarm = safe::make_alarm(); + std::string sink_name; + auto alarm = safe::make_alarm(); cb_simple_t server_f = [&](ctx_t::pointer ctx, const pa_server_info *server_info) { if(!server_info) { @@ -397,7 +396,9 @@ class server_t : public audio_control_t { alarm->ring(-1); } - sink_name = server_info->default_sink_name; + if (server_info->default_sink_name) { + sink_name = server_info->default_sink_name; + } alarm->ring(0); }; @@ -408,8 +409,12 @@ class server_t : public audio_control_t { } std::string get_monitor_name(const std::string &sink_name) { - std::string monitor_name = "@DEFAULT_MONITOR@"s; - auto alarm = safe::make_alarm(); + std::string monitor_name; + auto alarm = safe::make_alarm(); + + if (sink_name.empty()) { + return monitor_name; + } cb_t sink_f = [&](ctx_t::pointer ctx, const pa_sink_info *sink_info, int eol) { if(!sink_info) { From 62af7d255cee642db9df0ae900706c745871e021 Mon Sep 17 00:00:00 2001 From: Michael Rogers Date: Sat, 2 Jul 2022 19:52:27 -0500 Subject: [PATCH 376/817] Clang Format Applied --- src/audio.cpp | 6 +++--- src/platform/linux/audio.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/audio.cpp b/src/audio.cpp index 779dd5a5..3051356f 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -163,11 +163,11 @@ void capture(safe::mail_t mail, config_t config, void *channel_data) { 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)) { + 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; diff --git a/src/platform/linux/audio.cpp b/src/platform/linux/audio.cpp index ceab3eba..531e348a 100644 --- a/src/platform/linux/audio.cpp +++ b/src/platform/linux/audio.cpp @@ -343,7 +343,7 @@ class server_t : public audio_control_t { } auto sink_name = get_default_sink_name(); - sink.host = sink_name; + sink.host = sink_name; if(index.stereo == PA_INVALID_INDEX) { index.stereo = load_null(stereo, speaker::map_stereo, sizeof(speaker::map_stereo)); @@ -396,7 +396,7 @@ class server_t : public audio_control_t { alarm->ring(-1); } - if (server_info->default_sink_name) { + if(server_info->default_sink_name) { sink_name = server_info->default_sink_name; } alarm->ring(0); @@ -412,7 +412,7 @@ class server_t : public audio_control_t { std::string monitor_name; auto alarm = safe::make_alarm(); - if (sink_name.empty()) { + if(sink_name.empty()) { return monitor_name; } From 418d3cc76c9ff79f42c1a266c488d4104f41f4d2 Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Sun, 9 Oct 2022 09:40:19 -0400 Subject: [PATCH 377/817] Change Flatpak sources to jammy --- .../linux/flatpak/dev.lizardbyte.sunshine.yml | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index 2746094a..3ad69f21 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -60,8 +60,10 @@ modules: - ./b2 install variant=release link=static,shared runtime-link=shared cxxflags="$CXXFLAGS" linkflags="$LDFLAGS" -j $FLATPAK_BUILDER_N_JOBS # yamllint disable-line rule:line-length sources: - type: archive - url: https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.tar.bz2 - sha256: 1e19565d82e43bc59209a168f5ac899d3ba471d55c7610c677d4ccf2c9c500c0 + url: http://archive.ubuntu.com/ubuntu/pool/main/b/boost1.74/boost1.74_1.74.0.orig.tar.xz + sha256: 2467be4af625b5ae4b3c93fc7af196a09eba39c11a7338cd9e8b356fa44d2f45 +# url: https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.tar.bz2 +# sha256: 1e19565d82e43bc59209a168f5ac899d3ba471d55c7610c677d4ccf2c9c500c0 - name: ffmpeg config-opts: @@ -109,8 +111,10 @@ modules: - --enable-shared sources: - type: archive - url: https://code.videolan.org/videolan/x264/-/archive/stable/x264-stable.tar.bz2 - sha256: 8fedb184045722d8cc39353099373a5b7350171d0964d01fff8eced21b959b29 + url: http://archive.ubuntu.com/ubuntu/pool/universe/x/x264/x264_0.163.3060+git5db6aa6.orig.tar.gz + sha256: 0c7a5585f1b160c91eab8114d8493e65d08d2dfdc5b5d6ae46bbe5523469df9d +# url: https://code.videolan.org/videolan/x264/-/archive/stable/x264-stable.tar.bz2 +# sha256: 8fedb184045722d8cc39353099373a5b7350171d0964d01fff8eced21b959b29 - name: x265 buildsystem: cmake-ninja builddir: true @@ -120,9 +124,11 @@ modules: - -DENABLE_CLI=OFF sources: - type: archive - url: https://bitbucket.org/multicoreware/x265_git/downloads/x265_3.5.tar.gz + url: http://archive.ubuntu.com/ubuntu/pool/universe/x/x265/x265_3.5.orig.tar.gz sha256: e70a3335cacacbba0b3a20ec6fecd6783932288ebc8163ad74bcc9606477cae8 - - name: ffnvcodec +# url: https://bitbucket.org/multicoreware/x265_git/downloads/x265_3.5.tar.gz +# sha256: e70a3335cacacbba0b3a20ec6fecd6783932288ebc8163ad74bcc9606477cae8 + - name: nv-codec-headers no-autogen: true make-install-args: - PREFIX=${FLATPAK_DEST} @@ -130,8 +136,10 @@ modules: - '*' sources: - type: archive - url: https://github.com/FFmpeg/nv-codec-headers/archive/refs/tags/n11.1.5.1.tar.gz + url: http://archive.ubuntu.com/ubuntu/pool/universe/n/nv-codec-headers/nv-codec-headers_11.1.5.1.orig.tar.gz sha256: d095fbd56aa93772471a323be0ebe65504a0f43f06c76a30b6d25da77b06ae9c +# url: https://github.com/FFmpeg/nv-codec-headers/archive/refs/tags/n11.1.5.1.tar.gz +# sha256: d095fbd56aa93772471a323be0ebe65504a0f43f06c76a30b6d25da77b06ae9c - name: avahi cleanup: @@ -162,16 +170,20 @@ modules: - --disable-xmltoman sources: - type: archive - url: https://avahi.org/download/avahi-0.8.tar.gz + url: http://archive.ubuntu.com/ubuntu/pool/main/a/avahi/avahi_0.8.orig.tar.gz sha256: 060309d7a333d38d951bc27598c677af1796934dbd98e1024e7ad8de798fedda +# url: https://avahi.org/download/avahi-0.8.tar.gz +# sha256: 060309d7a333d38d951bc27598c677af1796934dbd98e1024e7ad8de798fedda modules: - name: libevent cleanup: - /bin sources: - type: archive - url: https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz # yamllint disable-line rule:line-length + url: http://archive.ubuntu.com/ubuntu/pool/main/libe/libevent/libevent_2.1.12-stable.orig.tar.gz sha256: 92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb +# url: https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz # yamllint disable-line rule:line-length +# sha256: 92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb - name: libevdev buildsystem: meson @@ -179,8 +191,10 @@ modules: - /bin sources: - type: archive - url: https://www.freedesktop.org/software/libevdev/libevdev-1.12.1.tar.xz - sha256: 1dbba41bc516d3ca7abc0da5b862efe3ea8a7018fa6e9b97ce9d39401b22426c + url: http://archive.ubuntu.com/ubuntu/pool/main/libe/libevdev/libevdev_1.12.1+dfsg.orig.tar.xz + sha256: a9aadd9c1ac64e47ca88776555ea6d0030d678b518f593082a13354e0d8016db +# url: https://www.freedesktop.org/software/libevdev/libevdev-1.12.1.tar.xz +# sha256: 1dbba41bc516d3ca7abc0da5b862efe3ea8a7018fa6e9b97ce9d39401b22426c modules: - name: libcheck buildsystem: cmake @@ -188,8 +202,10 @@ modules: - /bin sources: - type: archive - url: https://github.com/libcheck/check/archive/refs/tags/0.15.2.tar.gz - sha256: 998d355294bb94072f40584272cf4424571c396c631620ce463f6ea97aa67d2e + url: http://archive.ubuntu.com/ubuntu/pool/universe/c/check/check_0.15.2.orig.tar.gz + sha256: 8451b68ac5d6f3157b24f22eceff575bcf566264f6d78f3852f89d4e08cf42e1 +# url: https://github.com/libcheck/check/archive/refs/tags/0.15.2.tar.gz +# sha256: 998d355294bb94072f40584272cf4424571c396c631620ce463f6ea97aa67d2e - name: sunshine buildsystem: cmake From eedcd49713bdf4a69b229cbdf603f93cdd363e84 Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Mon, 10 Oct 2022 06:56:22 -0400 Subject: [PATCH 378/817] Update dev.lizardbyte.sunshine.yml --- .../linux/flatpak/dev.lizardbyte.sunshine.yml | 37 ++++++------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index 3ad69f21..46b6df70 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -54,18 +54,19 @@ modules: dest-filename: cuda.run - name: boost + disabled: false buildsystem: simple build-commands: - - ./bootstrap.sh --prefix=$FLATPAK_DEST --with-libraries=system,thread,log + - 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 || cat bootstrap.log - ./b2 install variant=release link=static,shared runtime-link=shared cxxflags="$CXXFLAGS" linkflags="$LDFLAGS" -j $FLATPAK_BUILDER_N_JOBS # yamllint disable-line rule:line-length sources: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/main/b/boost1.74/boost1.74_1.74.0.orig.tar.xz sha256: 2467be4af625b5ae4b3c93fc7af196a09eba39c11a7338cd9e8b356fa44d2f45 -# url: https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.tar.bz2 -# sha256: 1e19565d82e43bc59209a168f5ac899d3ba471d55c7610c677d4ccf2c9c500c0 - name: ffmpeg + disabled: false config-opts: - --enable-gpl - --disable-static @@ -96,15 +97,6 @@ modules: url: http://archive.ubuntu.com/ubuntu/pool/universe/f/ffmpeg/ffmpeg_4.4.2.orig.tar.xz sha256: af419a7f88adbc56c758ab19b4c708afbcae15ef09606b82b855291f6a6faa93 modules: - - name: vmaf - buildsystem: meson - subdir: libvmaf - cleanup: - - /bin - sources: - - type: archive - url: https://github.com/Netflix/vmaf/archive/refs/tags/v2.3.1.tar.gz - sha256: 8d60b1ddab043ada25ff11ced821da6e0c37fd7730dd81c24f1fc12be7293ef2 - name: x264 config-opts: - --disable-cli @@ -113,8 +105,7 @@ modules: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/universe/x/x264/x264_0.163.3060+git5db6aa6.orig.tar.gz sha256: 0c7a5585f1b160c91eab8114d8493e65d08d2dfdc5b5d6ae46bbe5523469df9d -# url: https://code.videolan.org/videolan/x264/-/archive/stable/x264-stable.tar.bz2 -# sha256: 8fedb184045722d8cc39353099373a5b7350171d0964d01fff8eced21b959b29 + - name: x265 buildsystem: cmake-ninja builddir: true @@ -126,8 +117,7 @@ modules: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/universe/x/x265/x265_3.5.orig.tar.gz sha256: e70a3335cacacbba0b3a20ec6fecd6783932288ebc8163ad74bcc9606477cae8 -# url: https://bitbucket.org/multicoreware/x265_git/downloads/x265_3.5.tar.gz -# sha256: e70a3335cacacbba0b3a20ec6fecd6783932288ebc8163ad74bcc9606477cae8 + - name: nv-codec-headers no-autogen: true make-install-args: @@ -138,10 +128,9 @@ modules: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/universe/n/nv-codec-headers/nv-codec-headers_11.1.5.1.orig.tar.gz sha256: d095fbd56aa93772471a323be0ebe65504a0f43f06c76a30b6d25da77b06ae9c -# url: https://github.com/FFmpeg/nv-codec-headers/archive/refs/tags/n11.1.5.1.tar.gz -# sha256: d095fbd56aa93772471a323be0ebe65504a0f43f06c76a30b6d25da77b06ae9c - name: avahi + disabled: false cleanup: - /bin - /lib/avahi @@ -172,8 +161,7 @@ modules: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/main/a/avahi/avahi_0.8.orig.tar.gz sha256: 060309d7a333d38d951bc27598c677af1796934dbd98e1024e7ad8de798fedda -# url: https://avahi.org/download/avahi-0.8.tar.gz -# sha256: 060309d7a333d38d951bc27598c677af1796934dbd98e1024e7ad8de798fedda + modules: - name: libevent cleanup: @@ -182,19 +170,18 @@ modules: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/main/libe/libevent/libevent_2.1.12-stable.orig.tar.gz sha256: 92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb -# url: https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz # yamllint disable-line rule:line-length -# sha256: 92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb - name: libevdev buildsystem: meson + config-opts: + - -Ddocumentation=disabled cleanup: - /bin sources: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/main/libe/libevdev/libevdev_1.12.1+dfsg.orig.tar.xz sha256: a9aadd9c1ac64e47ca88776555ea6d0030d678b518f593082a13354e0d8016db -# url: https://www.freedesktop.org/software/libevdev/libevdev-1.12.1.tar.xz -# sha256: 1dbba41bc516d3ca7abc0da5b862efe3ea8a7018fa6e9b97ce9d39401b22426c + modules: - name: libcheck buildsystem: cmake @@ -204,8 +191,6 @@ modules: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/universe/c/check/check_0.15.2.orig.tar.gz sha256: 8451b68ac5d6f3157b24f22eceff575bcf566264f6d78f3852f89d4e08cf42e1 -# url: https://github.com/libcheck/check/archive/refs/tags/0.15.2.tar.gz -# sha256: 998d355294bb94072f40584272cf4424571c396c631620ce463f6ea97aa67d2e - name: sunshine buildsystem: cmake From b40d414346f5c09768a9e45952bec3cf30e16abe Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Mon, 10 Oct 2022 07:14:12 -0400 Subject: [PATCH 379/817] Update CI.yml --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6c996446..c33cb0e2 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -171,7 +171,7 @@ jobs: build_linux_flatpak: name: Linux Flatpak - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: check_changelog steps: From de091570b98707bdf7c2a49fa4089cc90e923f69 Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Mon, 10 Oct 2022 12:07:07 -0400 Subject: [PATCH 380/817] ci: update global workflows (#396) --- .github/dependabot.yml | 8 ++++++++ .github/label-actions.yml | 4 ++-- .github/workflows/issues-stale.yml | 12 +++++++----- .github/workflows/issues.yml | 2 +- .github/workflows/yaml-lint.yml | 4 ++-- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e3b47ad7..b67ea638 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,14 @@ version: 2 updates: + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "daily" + time: "00:00" + target-branch: "nightly" + open-pull-requests-limit: 10 + - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/.github/label-actions.yml b/.github/label-actions.yml index 6d0a74a2..29496018 100644 --- a/.github/label-actions.yml +++ b/.github/label-actions.yml @@ -25,8 +25,8 @@ invalid:duplicate: invalid:support: comment: > :wave: @{issue-author}, we use the issue tracker exclusively for bug reports. - However, this issue appears to be a support request. Please use - [Discord](https://docs.lizardbyte.dev/about/support.html#discord) for support issues. Thanks. + However, this issue appears to be a support request. Please use our + [Support Center](https://app.lizardbyte.dev/support) for support issues. Thanks. close: true lock: true lock-reason: 'off-topic' diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml index 225b07de..7baf4b9a 100644 --- a/.github/workflows/issues-stale.yml +++ b/.github/workflows/issues-stale.yml @@ -15,10 +15,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Stale - uses: actions/stale@v5 + uses: actions/stale@v6 with: close-issue-message: > - This issue was closed because it has been stalled for 5 days with no activity. + This issue was closed because it has been stalled for 10 days with no activity. close-pr-message: > This PR was closed because it has been stalled for 10 days with no activity. days-before-stale: 90 @@ -28,15 +28,16 @@ jobs: exempt-pr-labels: 'dependencies,l10n' stale-issue-label: 'stale' stale-issue-message: > - This issue is stale because it has been open for 30 days with no activity. - Comment or remove the stale label, otherwise this will be closed in 5 days. + This issue is stale because it has been open for 90 days with no activity. + Comment or remove the stale label, otherwise this will be closed in 10 days. stale-pr-label: 'stale' stale-pr-message: > This PR is stale because it has been open for 90 days with no activity. Comment or remove the stale label, otherwise this will be closed in 10 days. + repo-token: ${{ secrets.GH_BOT_TOKEN }} - name: Invalid Template - uses: actions/stale@v5 + uses: actions/stale@v6 with: close-issue-message: > This issue was closed because the the template was not completed after 5 days. @@ -52,3 +53,4 @@ jobs: stale-pr-label: 'invalid:template-incomplete' stale-pr-message: > Invalid PR template. + repo-token: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml index f89975f9..6ba44446 100644 --- a/.github/workflows/issues.yml +++ b/.github/workflows/issues.yml @@ -19,4 +19,4 @@ jobs: - name: Label Actions uses: dessant/label-actions@v2 with: - github-token: ${{ github.token }} + github-token: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/yaml-lint.yml b/.github/workflows/yaml-lint.yml index 83de6c23..f2e07218 100644 --- a/.github/workflows/yaml-lint.yml +++ b/.github/workflows/yaml-lint.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: yaml lint id: yaml-lint @@ -39,7 +39,7 @@ jobs: echo ${{ steps.yaml-lint.outputs.logfile }} - name: Upload logs - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 if: failure() with: name: yamllint-logfile From ac1feb386cd24d473b8606c4384a7aa580e28444 Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Mon, 10 Oct 2022 12:49:59 -0400 Subject: [PATCH 381/817] ci: update global python (#397) --- .flake8 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index 2ea73951..2d028b2d 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,7 @@ [flake8] filename = - *.py + *.py, + *.pys max-line-length = 120 extend-exclude = venv/ From e0f2d3affc8ecd71f1a409e244cc4d51f49048d3 Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Mon, 10 Oct 2022 13:37:52 -0400 Subject: [PATCH 382/817] ci: update issue templates (#398) --- .github/ISSUE_TEMPLATE/config.yml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index ba507895..3371de06 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -5,15 +5,9 @@ blank_issues_enabled: false contact_links: - - name: Discord support - url: https://docs.lizardbyte.dev/about/support.html#discord - about: Ask questions in Discord - - name: Reddit support - url: https://www.reddit.com/r/LizardByte - about: Get community support on Reddit - - name: Facebook support - url: https://www.facebook.com/groups/lizardbyte - about: Get community support on Facebook + - name: Support Center + url: https://app.lizardbyte.dev/support + about: Official LizardByte support - name: Feature request - url: https://feedback.lizardbyte.dev + url: https://app.lizardbyte.dev/feedback about: Share your suggestions or ideas to help us improve From 6800fc00f4c2fd6273f62209431ecb23433dc167 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Oct 2022 17:40:24 +0000 Subject: [PATCH 383/817] Bump sphinx from 5.1.1 to 5.2.3 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 5.1.1 to 5.2.3. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/5.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v5.1.1...v5.2.3) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- scripts/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/requirements.txt b/scripts/requirements.txt index a572e249..55da79d8 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -1,5 +1,5 @@ Babel==2.10.3 m2r2==0.3.2 -Sphinx==5.1.1 +Sphinx==5.2.3 sphinx-copybutton==0.5.0 sphinx-rtd-theme==1.0.0 From 96b3bf30f60696c1a104eeac762ff8d9dad57731 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Oct 2022 18:42:42 +0000 Subject: [PATCH 384/817] Bump KSXGitHub/github-actions-deploy-aur from 2.4.1 to 2.5.0 Bumps [KSXGitHub/github-actions-deploy-aur](https://github.com/KSXGitHub/github-actions-deploy-aur) from 2.4.1 to 2.5.0. - [Release notes](https://github.com/KSXGitHub/github-actions-deploy-aur/releases) - [Commits](https://github.com/KSXGitHub/github-actions-deploy-aur/compare/v2.4.1...v2.5.0) --- updated-dependencies: - dependency-name: KSXGitHub/github-actions-deploy-aur dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index c33cb0e2..5e534a2b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -157,7 +157,7 @@ jobs: - name: Publish AUR package if: ${{ env.aur_publish == 'true' }} - uses: KSXGitHub/github-actions-deploy-aur@v2.4.1 + uses: KSXGitHub/github-actions-deploy-aur@v2.5.0 with: pkgname: ${{ env.aur_pkg }} pkgbuild: ./artifacts/PKGBUILD From 7f53388304c5df5eaf527cb97049594338ee4d41 Mon Sep 17 00:00:00 2001 From: Wouter Bijlsma Date: Mon, 10 Oct 2022 20:25:01 +0200 Subject: [PATCH 385/817] Fix CUDA RGBA to NV12 conversion On linux hosts with Nvidia GPU and CUDA support enabled, a CUDA kernel is used to convert captured RGBA frames to NV12 before encoding. This kernel contained a bug affecting image quality, in particular when rendering high-contrast colored text and sharp lines. See [1] for more information. This commit fixes the format conversion kernel by taking 2x2 RGBA blocks to generate 4 luma (Y) values and 1 chroma (UV) pair, ie: 12 bits per pixel YUV420 (NV12). Previous code incorrectly generated 1 UV pair for every 2 pixels. [1] https://github.com/LizardByte/Sunshine/issues/154 --- src/platform/linux/cuda.cu | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/platform/linux/cuda.cu b/src/platform/linux/cuda.cu index f69be730..1c6169b3 100644 --- a/src/platform/linux/cuda.cu +++ b/src/platform/linux/cuda.cu @@ -160,7 +160,7 @@ __global__ void RGBA_to_NV12( float scale, const viewport_t viewport, const video::color_t *const color_matrix) { int idX = (threadIdx.x + blockDim.x * blockIdx.x) * 2; - int idY = (threadIdx.y + blockDim.y * blockIdx.y); + int idY = (threadIdx.y + blockDim.y * blockIdx.y) * 2; if(idX >= viewport.width) return; if(idY >= viewport.height) return; @@ -171,18 +171,28 @@ __global__ void RGBA_to_NV12( idX += viewport.offsetX; idY += viewport.offsetY; - dstY = dstY + idX + idY * dstPitchY; + uint8_t *dstY0 = dstY + idX + idY * dstPitchY; + uint8_t *dstY1 = dstY + idX + (idY + 1) * dstPitchY; dstUV = dstUV + idX + (idY / 2 * dstPitchUV); - float3 rgb_l = bgra_to_rgb(tex2D(srcImage, x, y)); - float3 rgb_r = bgra_to_rgb(tex2D(srcImage, x + scale, y)); + float3 rgb_lt = bgra_to_rgb(tex2D(srcImage, x, y)); + float3 rgb_rt = bgra_to_rgb(tex2D(srcImage, x + scale, y)); + float3 rgb_lb = bgra_to_rgb(tex2D(srcImage, x, y + scale)); + float3 rgb_rb = bgra_to_rgb(tex2D(srcImage, x + scale, y + scale)); - float2 uv = calcUV((rgb_l + rgb_r) * 0.5f, color_matrix) * 256.0f; + float2 uv_lt = calcUV(rgb_lt, color_matrix) * 256.0f; + float2 uv_rt = calcUV(rgb_rt, color_matrix) * 256.0f; + float2 uv_lb = calcUV(rgb_lb, color_matrix) * 256.0f; + float2 uv_rb = calcUV(rgb_rb, color_matrix) * 256.0f; + + float2 uv = (uv_lt + uv_lb + uv_rt + uv_rb) * 0.25f; dstUV[0] = uv.x; dstUV[1] = uv.y; - dstY[0] = calcY(rgb_l, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visisble - dstY[1] = calcY(rgb_r, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visisble + dstY0[0] = calcY(rgb_lt, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visisble + dstY0[1] = calcY(rgb_rt, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visisble + dstY1[0] = calcY(rgb_lb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visisble + dstY1[1] = calcY(rgb_rb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visisble } int tex_t::copy(std::uint8_t *src, int height, int pitch) { @@ -292,7 +302,7 @@ int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std: int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport) { int threadsX = viewport.width / 2; - int threadsY = viewport.height; + int threadsY = viewport.height / 2; dim3 block(threadsPerBlock); dim3 grid(div_align(threadsX, threadsPerBlock), threadsY); @@ -328,4 +338,4 @@ int sws_t::load_ram(platf::img_t &img, cudaArray_t array) { return CU_CHECK_IGNORE(cudaMemcpy2DToArray(array, 0, 0, img.data, img.row_pitch, img.width * img.pixel_pitch, img.height, cudaMemcpyHostToDevice), "Couldn't copy to cuda array"); } -} // namespace cuda \ No newline at end of file +} // namespace cuda From 25a64bff44e314ebd293c257db453af6b9bb919b Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Wed, 12 Oct 2022 19:01:23 -0400 Subject: [PATCH 386/817] Update Flatpak sources to Kinetic --- packaging/linux/flatpak/dev.lizardbyte.sunshine.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index 46b6df70..0b7e57b5 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -94,8 +94,8 @@ modules: - /share sources: - type: archive - url: http://archive.ubuntu.com/ubuntu/pool/universe/f/ffmpeg/ffmpeg_4.4.2.orig.tar.xz - sha256: af419a7f88adbc56c758ab19b4c708afbcae15ef09606b82b855291f6a6faa93 + url: http://archive.ubuntu.com/ubuntu/pool/universe/f/ffmpeg/ffmpeg_5.1.1.orig.tar.xz + sha256: 95bf3ff8c496511e71e958fb249e663c8c9c3de583c5bebc0f5a9745abbc0435 modules: - name: x264 config-opts: @@ -103,8 +103,8 @@ modules: - --enable-shared sources: - type: archive - url: http://archive.ubuntu.com/ubuntu/pool/universe/x/x264/x264_0.163.3060+git5db6aa6.orig.tar.gz - sha256: 0c7a5585f1b160c91eab8114d8493e65d08d2dfdc5b5d6ae46bbe5523469df9d + url: http://archive.ubuntu.com/ubuntu/pool/universe/x/x264/x264_0.164.3095+gitbaee400.orig.tar.gz + sha256: 8b237e94b08c196a1da22f2f25875f10be4cff3648df4eeff21e00da8f683fc2 - name: x265 buildsystem: cmake-ninja @@ -179,8 +179,8 @@ modules: - /bin sources: - type: archive - url: http://archive.ubuntu.com/ubuntu/pool/main/libe/libevdev/libevdev_1.12.1+dfsg.orig.tar.xz - sha256: a9aadd9c1ac64e47ca88776555ea6d0030d678b518f593082a13354e0d8016db + url: http://archive.ubuntu.com/ubuntu/pool/main/libe/libevdev/libevdev_1.13.0+dfsg.orig.tar.xz + sha256: a882e13ef1dd6bd227318080cabf60fe5af3c06471259d3acfc9dbfb202351a7 modules: - name: libcheck From 264c9272df8d443456e405476983cc8bb41687a4 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 15 Oct 2022 16:37:18 -0400 Subject: [PATCH 387/817] fix string concatenation of image in assets dir --- src/process.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/process.cpp b/src/process.cpp index cd74a801..2b6c1507 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -202,10 +202,11 @@ std::string proc_t::get_app_image(int app_id) { return SUNSHINE_ASSETS_DIR "/box.png"; } + auto default_image = SUNSHINE_ASSETS_DIR "/box.png"; auto app_image_path = _apps[app_index].image_path; if(app_image_path.empty()) { // image is empty, return default box image - return SUNSHINE_ASSETS_DIR "/box.png"; + return default_image; } // get the image extension and convert it to lowercase @@ -214,12 +215,13 @@ std::string proc_t::get_app_image(int app_id) { // return the default box image if extension is not "png" if(image_extension != ".png") { - return SUNSHINE_ASSETS_DIR "/box.png"; + return default_image; } // check if image is in assets directory - if(std::filesystem::exists(SUNSHINE_ASSETS_DIR + app_image_path)) { - return SUNSHINE_ASSETS_DIR + app_image_path; + auto full_image_path = std::filesystem::path(SUNSHINE_ASSETS_DIR) / app_image_path; + if(std::filesystem::exists(full_image_path)) { + return full_image_path.string(); } else if(app_image_path == "./assets/steam.png") { // handle old default steam image definition @@ -230,7 +232,7 @@ std::string proc_t::get_app_image(int app_id) { std::error_code code; if(!std::filesystem::exists(app_image_path, code)) { // return default box image if image does not exist - return SUNSHINE_ASSETS_DIR "/box.png"; + return default_image; } // image is a png, and not in assets directory From 9b66a5a16b38e259c654092d11a83d2d843ef373 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 16 Oct 2022 14:12:48 -0400 Subject: [PATCH 388/817] Bump actions/checkout from 2 to 3 --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 5e534a2b..7ce11790 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -73,7 +73,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup Dependencies Linux AUR run: | From 93315f280eea4abe390b31cce318482675d8afdd Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 16 Oct 2022 14:18:20 -0400 Subject: [PATCH 389/817] Update CI.yml - use `GITHUB_OUTPUT` instead of `set-output` --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 7ce11790..7e6b01a1 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -591,7 +591,7 @@ jobs: echo "$subport" subportlist="$subportlist $subport" done - echo "::set-output name=subportlist::${subportlist}" + echo "subportlist=${subportlist}" >> $GITHUB_OUTPUT - name: Run port lint for all subports run: | From b6ae848bb52b30ff705dac8a1e20abcadedd07c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 01:22:31 +0000 Subject: [PATCH 390/817] Bump sphinx from 5.2.3 to 5.3.0 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 5.2.3 to 5.3.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v5.2.3...v5.3.0) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- scripts/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/requirements.txt b/scripts/requirements.txt index 55da79d8..84c26fe1 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -1,5 +1,5 @@ Babel==2.10.3 m2r2==0.3.2 -Sphinx==5.2.3 +Sphinx==5.3.0 sphinx-copybutton==0.5.0 sphinx-rtd-theme==1.0.0 From 3baace671114b3533010cdd3c57677606fb94fb4 Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Mon, 17 Oct 2022 17:31:12 -0400 Subject: [PATCH 391/817] Reduce build time and cleanup --- .../linux/flatpak/dev.lizardbyte.sunshine.yml | 64 ++++++++----------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index 0b7e57b5..2c31e50b 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -26,33 +26,6 @@ cleanup: - /share/man modules: - - name: cuda - disabled: false - buildsystem: simple - only-arches: - - x86_64 - - aarch64 - cleanup: - - '*' - build-commands: - - chmod u+x ./cuda.run - - ./cuda.run --silent --toolkit --toolkitpath=$FLATPAK_DEST/cuda --no-opengl-libs --no-man-page --no-drm --tmpdir=$FLATPAK_BUILDER_BUILDDIR # yamllint disable-line rule:line-length - - rm -r $FLATPAK_DEST/cuda/nsight-systems-2021.3.2 - - rm ./cuda.run - sources: - - type: file - only-arches: - - x86_64 - url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run - sha256: bbd87ca0e913f837454a796367473513cddef555082e4d86ed9a38659cc81f0a - dest-filename: cuda.run - - type: file - only-arches: - - aarch64 - url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux_sbsa.run # yamllint disable-line rule:line-length - sha256: f2c4a52e06329606c8dfb7c5ea3f4cb4c0b28f9d3fdffeeb734fcc98daf580d8 - dest-filename: cuda.run - - name: boost disabled: false buildsystem: simple @@ -82,14 +55,6 @@ modules: - --enable-libx264 - --enable-libx265 - --enable-nvenc - - --enable-encoder=h264_v4l2m2m - - --enable-encoder=hevc_v4l2m2m - # - --enable-nonfree - # - --enable-cuda-nvcc - # - --enable-libnpp - # - --extra-cflags=-I${FLATPAK_DEST}/cuda/include - # - --extra-ldflags=-L${FLATPAK_DEST}/cuda/lib64 - # - --nvccflags="-gencode arch=compute_52,code=sm_52 -O2" cleanup: - /share sources: @@ -161,7 +126,6 @@ modules: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/main/a/avahi/avahi_0.8.orig.tar.gz sha256: 060309d7a333d38d951bc27598c677af1796934dbd98e1024e7ad8de798fedda - modules: - name: libevent cleanup: @@ -181,7 +145,6 @@ modules: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/main/libe/libevdev/libevdev_1.13.0+dfsg.orig.tar.xz sha256: a882e13ef1dd6bd227318080cabf60fe5af3c06471259d3acfc9dbfb202351a7 - modules: - name: libcheck buildsystem: cmake @@ -192,6 +155,33 @@ modules: url: http://archive.ubuntu.com/ubuntu/pool/universe/c/check/check_0.15.2.orig.tar.gz sha256: 8451b68ac5d6f3157b24f22eceff575bcf566264f6d78f3852f89d4e08cf42e1 + - name: cuda + disabled: false + buildsystem: simple + only-arches: + - x86_64 + - aarch64 + cleanup: + - '*' + build-commands: + - chmod u+x ./cuda.run + - ./cuda.run --silent --toolkit --toolkitpath=$FLATPAK_DEST/cuda --no-opengl-libs --no-man-page --no-drm --tmpdir=$FLATPAK_BUILDER_BUILDDIR # yamllint disable-line rule:line-length + - rm -r $FLATPAK_DEST/cuda/nsight-systems-2021.3.2 + - rm ./cuda.run + sources: + - type: file + only-arches: + - x86_64 + url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run + sha256: bbd87ca0e913f837454a796367473513cddef555082e4d86ed9a38659cc81f0a + dest-filename: cuda.run + - type: file + only-arches: + - aarch64 + url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux_sbsa.run # yamllint disable-line rule:line-length + sha256: f2c4a52e06329606c8dfb7c5ea3f4cb4c0b28f9d3fdffeeb734fcc98daf580d8 + dest-filename: cuda.run + - name: sunshine buildsystem: cmake no-make-install: false From 35cdc2c89bed824459902725147000111f13cde6 Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Mon, 17 Oct 2022 20:28:45 -0400 Subject: [PATCH 392/817] Update dev.lizardbyte.sunshine.yml --- .../linux/flatpak/dev.lizardbyte.sunshine.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index 2c31e50b..cfb8678e 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -47,14 +47,18 @@ modules: - --disable-doc - --disable-programs - --disable-decoders - - --enable-libfontconfig - - --enable-libfreetype - - --enable-libopus - - --enable-libvorbis - - --enable-libvpx - - --enable-libx264 - - --enable-libx265 + - --disable-vdpau + - --disable-audiotoolbox + - --disable-videotoolbox + - --disable-vulkan + - --disable-sdl2 + - --disable-filters + - --disable-avfilter + - --disable-postproc + - --disable-autodetect - --enable-nvenc + - --enable-ffnvcodec + - --enable-vaapi cleanup: - /share sources: From ef2ca538a38e00aece0fde0e72a444d3ebdf77cc Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 19 Oct 2022 09:35:48 -0400 Subject: [PATCH 393/817] use matrix build for flatpak Co-Authored-By: istori1 <107304850+istori1@users.noreply.github.com> --- .github/workflows/CI.yml | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 7e6b01a1..ead43706 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -173,6 +173,13 @@ jobs: name: Linux Flatpak runs-on: ubuntu-22.04 needs: check_changelog + strategy: + fail-fast: false # false to test all, true to fail entire job if any fail + matrix: + arch: ['x86_64', 'aarch64'] + exclude: + # exclude `aarch64` on anything except a release triggering event + - arch: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) && '' || 'aarch64' }} steps: - name: Checkout @@ -183,12 +190,14 @@ jobs: sudo apt-get update -y sudo apt-get install -y \ cmake \ + qemu-user-static \ flatpak - sudo su $(whoami) -c 'flatpak remote-add --user --if-not-exists flathub \ + sudo su $(whoami) -c 'flatpak --user remote-add --if-not-exists flathub \ https://flathub.org/repo/flathub.flatpakrepo' - sudo su $(whoami) -c 'flatpak install --user flathub \ - org.flatpak.Builder org.freedesktop.Platform//21.08 org.freedesktop.Sdk//21.08 -y' - + sudo su $(whoami) -c 'flatpak --user install -y flathub \ + org.flatpak.Builder \ + org.freedesktop.Platform/${{ matrix.arch }}/21.08 \ + org.freedesktop.Sdk/${{ matrix.arch }}/21.08' - name: Configure Flatpak Manifest run: | # variables for manifest @@ -224,14 +233,15 @@ jobs: - name: Build Linux Flatpak working-directory: build run: | - sudo su $(whoami) -c 'flatpak run org.flatpak.Builder --repo=repo --force-clean build-sunshine \ - dev.lizardbyte.sunshine.yml' - sudo su $(whoami) -c 'flatpak build-bundle ./repo ../artifacts/sunshine.flatpak dev.lizardbyte.sunshine' + sudo su $(whoami) -c 'flatpak run org.flatpak.Builder --arch=${{ matrix.arch }} --repo=repo --force-clean \ + build-sunshine dev.lizardbyte.sunshine.yml' + sudo su $(whoami) -c 'flatpak build-bundle --arch=${{ matrix.arch }} ./repo \ + ../artifacts/sunshine_${{ matrix.arch }}.flatpak dev.lizardbyte.sunshine' - name: Upload Artifacts uses: actions/upload-artifact@v3 with: - name: sunshine-linux-flatpak + name: sunshine-linux-flatpak-${{ matrix.arch }} path: artifacts/ - name: Create Release From 87f01fc0b86e0ad564b7773138aa225e5d107234 Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Wed, 19 Oct 2022 17:29:27 -0400 Subject: [PATCH 394/817] Update dev.lizardbyte.sunshine.yml --- packaging/linux/flatpak/dev.lizardbyte.sunshine.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index cfb8678e..105caddc 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -32,7 +32,8 @@ modules: 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 || cat bootstrap.log - - ./b2 install variant=release link=static,shared runtime-link=shared cxxflags="$CXXFLAGS" linkflags="$LDFLAGS" -j $FLATPAK_BUILDER_N_JOBS # yamllint disable-line rule:line-length + - ./b2 install variant=release link=static,shared runtime-link=shared cxxflags="$CXXFLAGS" linkflags="$LDFLAGS" + -j $FLATPAK_BUILDER_N_JOBS sources: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/main/b/boost1.74/boost1.74_1.74.0.orig.tar.xz @@ -140,6 +141,7 @@ modules: sha256: 92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb - name: libevdev + disabled: false buildsystem: meson config-opts: - -Ddocumentation=disabled @@ -169,7 +171,8 @@ modules: - '*' build-commands: - chmod u+x ./cuda.run - - ./cuda.run --silent --toolkit --toolkitpath=$FLATPAK_DEST/cuda --no-opengl-libs --no-man-page --no-drm --tmpdir=$FLATPAK_BUILDER_BUILDDIR # yamllint disable-line rule:line-length + - ./cuda.run --silent --toolkit --toolkitpath=$FLATPAK_DEST/cuda --no-opengl-libs --no-man-page --no-drm + --tmpdir=$FLATPAK_BUILDER_BUILDDIR - rm -r $FLATPAK_DEST/cuda/nsight-systems-2021.3.2 - rm ./cuda.run sources: @@ -187,6 +190,7 @@ modules: dest-filename: cuda.run - name: sunshine + disabled: false buildsystem: cmake no-make-install: false builddir: true From 9f0af0f8ae22a37a2351b5d9a0ad8d7d93f85fb6 Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Wed, 19 Oct 2022 19:34:36 -0400 Subject: [PATCH 395/817] ci: update global workflows (#417) --- .github/workflows/automerge.yml | 4 ++-- .github/workflows/yaml-lint.yml | 35 +++++++++++++++++++++++---------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 7ff83e0a..0ac82a7a 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Autoapproving - uses: hmarr/auto-approve-action@v2 + uses: hmarr/auto-approve-action@v3 with: github-token: "${{ secrets.GITHUB_TOKEN }}" @@ -44,7 +44,7 @@ jobs: steps: - name: Automerging - uses: pascalgn/automerge-action@v0.15.3 + uses: pascalgn/automerge-action@v0.15.5 env: BASE_BRANCHES: nightly GITHUB_TOKEN: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/yaml-lint.yml b/.github/workflows/yaml-lint.yml index f2e07218..5623c2dc 100644 --- a/.github/workflows/yaml-lint.yml +++ b/.github/workflows/yaml-lint.yml @@ -17,6 +17,24 @@ jobs: - name: Checkout uses: actions/checkout@v3 + - name: Find additional files + id: find-files + run: | + # space separated list of files + FILES=.clang-format + + # empty placeholder + FOUND="" + + for FILE in ${FILES}; do + if [ -f "$FILE" ] + then + FOUND="$FOUND $FILE" + fi + done + + echo "found=${FOUND}" >> $GITHUB_OUTPUT + - name: yaml lint id: yaml-lint uses: ibiqlik/action-yamllint@v3 @@ -30,17 +48,14 @@ jobs: line-length: max: 120 truthy: - allowed-values: ['true', 'false', 'on'] # GitHub uses "on" for workflow event triggers + # GitHub uses "on" for workflow event triggers + # .clang-format file has options of "Yes" "No" that will be caught by this, so changed to "warning" + allowed-values: ['true', 'false', 'on'] check-keys: true - level: error + level: warning + file_or_dir: . ${{ steps.find-files.outputs.found }} - name: Log - run: | - echo ${{ steps.yaml-lint.outputs.logfile }} - - - name: Upload logs - uses: actions/upload-artifact@v3 if: failure() - with: - name: yamllint-logfile - path: ${{ steps.yaml-lint.outputs.logfile }} + run: | + cat "${{ steps.yaml-lint.outputs.logfile }}" >> $GITHUB_STEP_SUMMARY From cd89808a21e203c58f2ac5132acb03bd9c576e6a Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Wed, 19 Oct 2022 20:18:25 -0400 Subject: [PATCH 396/817] ci: update global cpp (#421) --- .clang-format | 7 ++++--- .github/workflows/cpp-clang-format-lint.yml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.clang-format b/.clang-format index 6944ec3e..9f868d75 100644 --- a/.clang-format +++ b/.clang-format @@ -1,3 +1,4 @@ +--- # This file is centrally managed in https://github.com//.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. @@ -6,7 +7,7 @@ BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: DontAlign -AlignConsecutiveAssignments: true +AlignConsecutiveAssignments: Consecutive AlignOperands: Align AllowAllArgumentsOnNextLine: false AllowAllConstructorInitializersOnNextLine: false @@ -14,7 +15,7 @@ AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: Always AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All -AllowShortIfStatementsOnASingleLine: Always +AllowShortIfStatementsOnASingleLine: WithoutElse AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: true AlwaysBreakAfterReturnType: None @@ -62,7 +63,7 @@ SpaceBeforeParens: Never SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 -SpacesInAngles: false +SpacesInAngles: Never SpacesInCStyleCastParentheses: false SpacesInContainerLiterals: false SpacesInParentheses: false diff --git a/.github/workflows/cpp-clang-format-lint.yml b/.github/workflows/cpp-clang-format-lint.yml index 4717f70a..cc42ea10 100644 --- a/.github/workflows/cpp-clang-format-lint.yml +++ b/.github/workflows/cpp-clang-format-lint.yml @@ -28,7 +28,7 @@ jobs: FOUND=false fi - echo "::set-output name=src::${FOUND}" + echo "src=${FOUND}" >> $GITHUB_OUTPUT outputs: src: ${{ steps.check.outputs.src }} From e2bb1a720ac19dd00a6e8942f7a82e7d2149ca9e Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Thu, 20 Oct 2022 10:34:29 -0400 Subject: [PATCH 397/817] Adding back CPU encoding (#419) --- packaging/linux/flatpak/dev.lizardbyte.sunshine.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index 105caddc..d2c5af4d 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -60,6 +60,8 @@ modules: - --enable-nvenc - --enable-ffnvcodec - --enable-vaapi + - --enable-libx264 + - --enable-libx265 cleanup: - /share sources: From 49afefc43f92d09eeee69c6c9baca2cc6f9f3836 Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Thu, 20 Oct 2022 13:37:34 -0400 Subject: [PATCH 398/817] Export Debug Bundle [Flatpak] (#422) --- .github/workflows/CI.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ead43706..33300441 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -237,6 +237,8 @@ jobs: build-sunshine dev.lizardbyte.sunshine.yml' sudo su $(whoami) -c 'flatpak build-bundle --arch=${{ matrix.arch }} ./repo \ ../artifacts/sunshine_${{ matrix.arch }}.flatpak dev.lizardbyte.sunshine' + sudo su $(whoami) -c 'flatpak build-bundle --runtime --arch=${{ matrix.arch }} ./repo \ + ../artifacts/sunshine_debug_${{ matrix.arch }}.flatpak dev.lizardbyte.sunshine.Debug' - name: Upload Artifacts uses: actions/upload-artifact@v3 From b5ec178cd6fb7145833a4d4d212f4b322fb2b3ba Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 20 Oct 2022 20:08:19 -0400 Subject: [PATCH 399/817] add flatpak-builder cache --- .github/workflows/CI.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 33300441..cd0e5c5c 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -198,6 +198,15 @@ jobs: org.flatpak.Builder \ org.freedesktop.Platform/${{ matrix.arch }}/21.08 \ org.freedesktop.Sdk/${{ matrix.arch }}/21.08' + + - name: Cache Flatpak build + uses: actions/cache@v3 + with: + path: ./build/.flatpak-builder + key: flatpak-${{ matrix.arch }}-${{ github.sha }} + restore-keys: | + flatpak-${{ matrix.arch }}- + - name: Configure Flatpak Manifest run: | # variables for manifest From 272368b59ce45d4a1de07b5f03ccd456ad9cb24c Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Fri, 21 Oct 2022 19:28:36 -0400 Subject: [PATCH 400/817] Do not cache cuda module --- .github/workflows/CI.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index cd0e5c5c..f212aaed 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -242,8 +242,13 @@ jobs: - name: Build Linux Flatpak working-directory: build run: | + sudo su $(whoami) -c 'flatpak run org.flatpak.Builder --arch=${{ matrix.arch }} --repo=repo --force-clean \ + --stop-at=cuda build-sunshine dev.lizardbyte.sunshine.yml' + cp -r .flatpak-builder copy-of-flatpak-builder sudo su $(whoami) -c 'flatpak run org.flatpak.Builder --arch=${{ matrix.arch }} --repo=repo --force-clean \ build-sunshine dev.lizardbyte.sunshine.yml' + rm -r .flatpak-builder + mv copy-of-flatpak-builder .flatpak-builder sudo su $(whoami) -c 'flatpak build-bundle --arch=${{ matrix.arch }} ./repo \ ../artifacts/sunshine_${{ matrix.arch }}.flatpak dev.lizardbyte.sunshine' sudo su $(whoami) -c 'flatpak build-bundle --runtime --arch=${{ matrix.arch }} ./repo \ From cc0ac47f2967ccaf2942545aba0883fe8e52e1a9 Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Fri, 21 Oct 2022 19:40:54 -0400 Subject: [PATCH 401/817] Update CI.yml --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f212aaed..2d367286 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -247,7 +247,7 @@ jobs: cp -r .flatpak-builder copy-of-flatpak-builder sudo su $(whoami) -c 'flatpak run org.flatpak.Builder --arch=${{ matrix.arch }} --repo=repo --force-clean \ build-sunshine dev.lizardbyte.sunshine.yml' - rm -r .flatpak-builder + rm -rf .flatpak-builder mv copy-of-flatpak-builder .flatpak-builder sudo su $(whoami) -c 'flatpak build-bundle --arch=${{ matrix.arch }} ./repo \ ../artifacts/sunshine_${{ matrix.arch }}.flatpak dev.lizardbyte.sunshine' From 88d67277f6d37a7ef3777a0c0ba4176cc2c543f7 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 23 Oct 2022 13:21:00 -0400 Subject: [PATCH 402/817] update localize workflow - fix git diff comparison - add current date to created PR --- .github/workflows/localize.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index 79674aa4..2c57c114 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -62,17 +62,20 @@ jobs: # print the git diff git diff locale/sunshine.po - # set the variable with minimal output - OUTPUT=$(git diff --numstat locale/sunshine.po) + # set the variable with minimal output, replacing `\t` with ` ` + OUTPUT=$(git diff --numstat locale/sunshine.po | sed -e "s#\t# #g") echo "git_diff=${OUTPUT}" >> $GITHUB_ENV - name: git reset # only run if a single line changed (date/time) and file already existed - # \t in next line is a tab character - if: ${{ env.git_diff == '1\t1\tlocale/sunshine.po' && env.new_file == 'false' }} + if: ${{ env.git_diff == '1 1 locale/sunshine.po' && env.new_file == 'false' }} run: | git reset --hard + - name: Get current date + id: date + run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + - name: Create/Update Pull Request uses: peter-evans/create-pull-request@v4 with: @@ -86,7 +89,7 @@ jobs: title: New Babel Updates body: | Update report - - Updated with *today's* date + - Updated ${{ steps.date.outputs.date }} - Auto-generated by [create-pull-request][1] [1]: https://github.com/peter-evans/create-pull-request From db55ff8ea18fcbbe7995d07e08dd29a585f63c8d Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Mon, 24 Oct 2022 07:05:13 -0400 Subject: [PATCH 403/817] Remove libcheck and disable x265 --- packaging/linux/flatpak/dev.lizardbyte.sunshine.yml | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index d2c5af4d..7737936d 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -61,7 +61,7 @@ modules: - --enable-ffnvcodec - --enable-vaapi - --enable-libx264 - - --enable-libx265 + # - --enable-libx265 cleanup: - /share sources: @@ -79,6 +79,7 @@ modules: sha256: 8b237e94b08c196a1da22f2f25875f10be4cff3648df4eeff21e00da8f683fc2 - name: x265 + disabled: true buildsystem: cmake-ninja builddir: true subdir: source @@ -147,21 +148,13 @@ modules: buildsystem: meson config-opts: - -Ddocumentation=disabled + - -Dtests=disabled cleanup: - /bin sources: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/main/libe/libevdev/libevdev_1.13.0+dfsg.orig.tar.xz sha256: a882e13ef1dd6bd227318080cabf60fe5af3c06471259d3acfc9dbfb202351a7 - modules: - - name: libcheck - buildsystem: cmake - cleanup: - - /bin - sources: - - type: archive - url: http://archive.ubuntu.com/ubuntu/pool/universe/c/check/check_0.15.2.orig.tar.gz - sha256: 8451b68ac5d6f3157b24f22eceff575bcf566264f6d78f3852f89d4e08cf42e1 - name: cuda disabled: false From 166f9f73e0b63d1794c9bbdcf2a673020caf6573 Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Mon, 24 Oct 2022 17:47:19 -0400 Subject: [PATCH 404/817] Enabled x264 and removed libevent --- packaging/linux/flatpak/dev.lizardbyte.sunshine.yml | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index 7737936d..d9e64a13 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -61,7 +61,7 @@ modules: - --enable-ffnvcodec - --enable-vaapi - --enable-libx264 - # - --enable-libx265 + - --enable-libx265 cleanup: - /share sources: @@ -79,7 +79,7 @@ modules: sha256: 8b237e94b08c196a1da22f2f25875f10be4cff3648df4eeff21e00da8f683fc2 - name: x265 - disabled: true + disabled: false buildsystem: cmake-ninja builddir: true subdir: source @@ -130,18 +130,11 @@ modules: - --disable-doxygen-html - --disable-manpages - --disable-xmltoman + - --disable-libevent sources: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/main/a/avahi/avahi_0.8.orig.tar.gz sha256: 060309d7a333d38d951bc27598c677af1796934dbd98e1024e7ad8de798fedda - modules: - - name: libevent - cleanup: - - /bin - sources: - - type: archive - url: http://archive.ubuntu.com/ubuntu/pool/main/libe/libevent/libevent_2.1.12-stable.orig.tar.gz - sha256: 92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb - name: libevdev disabled: false From 4769a9348b1b57d90c965eaeb5f5a06942430eaf Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 25 Oct 2022 10:38:03 -0400 Subject: [PATCH 405/817] Create release-notifier-moonlight.yml --- .../workflows/release-notifier-moonlight.yml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/release-notifier-moonlight.yml diff --git a/.github/workflows/release-notifier-moonlight.yml b/.github/workflows/release-notifier-moonlight.yml new file mode 100644 index 00000000..9ac896f3 --- /dev/null +++ b/.github/workflows/release-notifier-moonlight.yml @@ -0,0 +1,22 @@ +--- +name: Release Notifications (Moonlight) + +on: + release: + types: [published] + +jobs: + discord: + runs-on: ubuntu-latest + steps: + - name: discord + uses: sarisia/actions-status-discord@v1 # https://github.com/sarisia/actions-status-discord + with: + webhook: ${{ secrets.DISCORD_RELEASE_WEBHOOK_MOONLIGHT }} + nodetail: true + nofail: false + username: ${{ secrets.DISCORD_USERNAME }} + avatar_url: ${{ secrets.ORG_LOGO_URL }} + title: ${{ github.event.repository.name }} ${{ github.ref_name }} Released + description: ${{ github.event.release.body }} + color: 0xFF4500 From a043dfcf6c65c7ca7e284932fa00d84e169b397a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 26 Oct 2022 14:53:27 -0400 Subject: [PATCH 406/817] Update apps.json - reformat `apps.json` files - use `sed` to add required prefixes for flatpak build --- .../linux/flatpak/dev.lizardbyte.sunshine.yml | 8 ++++ src_assets/linux/assets/apps.json | 40 ++++++++++--------- src_assets/macos/assets/apps.json | 8 ++-- src_assets/windows/assets/apps.json | 25 ++++++------ 4 files changed, 47 insertions(+), 34 deletions(-) diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index d9e64a13..7bb4cd1b 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -200,3 +200,11 @@ modules: url: '@GITHUB_CLONE_URL@' branch: '@GITHUB_BRANCH@' commit: '@GITHUB_COMMIT@' + post-install: + # use `sed` to update apps.json with prefixes required for flatpak + # -r (regex) + # -z (handle new lines) https://linuxhint.com/sed-replace-newline-with-space + # `/gm` global and multiline + - sed -r -z -i -e + 's/("((do)|(undo)|(cmd)|(detached))"\s*:\s*\[?\n*\s*")(.*")/\1flatpak-spawn --host \7/gm' + /app/share/sunshine/apps.json diff --git a/src_assets/linux/assets/apps.json b/src_assets/linux/assets/apps.json index 4a848bd8..927c7028 100644 --- a/src_assets/linux/assets/apps.json +++ b/src_assets/linux/assets/apps.json @@ -1,20 +1,24 @@ { - "env":{ - "PATH":"$(PATH):$(HOME)/.local/bin" - }, - "apps":[ - { - "name":"Low Res Desktop", - "prep-cmd":[ - { "do":"xrandr --output HDMI-1 --mode 1920x1080", "undo":"xrandr --output HDMI-1 --mode 1920x1200" } - ] - }, - { - "name":"Steam BigPicture", - - "output":"steam.txt", - "detached":["setsid steam steam://open/bigpicture"], - "image-path":"steam.png" - } - ] + "env": { + "PATH": "$(PATH):$(HOME)/.local/bin" + }, + "apps": [ + { + "name": "Low Res Desktop", + "prep-cmd": [ + { + "do": "xrandr --output HDMI-1 --mode 1920x1080", + "undo": "xrandr --output HDMI-1 --mode 1920x1200" + } + ] + }, + { + "name": "Steam BigPicture", + "output": "steam.txt", + "detached": [ + "setsid steam steam://open/bigpicture" + ], + "image-path": "steam.png" + } + ] } diff --git a/src_assets/macos/assets/apps.json b/src_assets/macos/assets/apps.json index 746c69b3..cb1c548a 100644 --- a/src_assets/macos/assets/apps.json +++ b/src_assets/macos/assets/apps.json @@ -1,6 +1,6 @@ { - "env":{ - "PATH":"$(PATH):$(HOME)/.local/bin" - }, - "apps":[ ] + "env": { + "PATH": "$(PATH):$(HOME)/.local/bin" + }, + "apps": [] } diff --git a/src_assets/windows/assets/apps.json b/src_assets/windows/assets/apps.json index 379dd2fb..de4847c3 100644 --- a/src_assets/windows/assets/apps.json +++ b/src_assets/windows/assets/apps.json @@ -1,14 +1,15 @@ { - "env":{ - "PATH":"$(PATH);C:\\Program Files (x86)\\Steam" - }, - "apps":[ - { - "name":"Steam BigPicture", - - "output":"steam.txt", - "detached":["steam steam://open/bigpicture"], - "image-path":"steam.png" - } - ] + "env": { + "PATH": "$(PATH);C:\\Program Files (x86)\\Steam" + }, + "apps": [ + { + "name": "Steam BigPicture", + "output": "steam.txt", + "detached": [ + "steam steam://open/bigpicture" + ], + "image-path": "steam.png" + } + ] } From dcdd716a57032d1e8197ef6c86b497bc24317d57 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 27 Oct 2022 21:51:24 -0400 Subject: [PATCH 407/817] update docker (#325) * update docker * remove legacy docker builds * update docker documentation * update docker build Co-authored-by: ABeltramo --- .docker_platforms | 1 + .dockerignore | 16 +++ .github/workflows/ci-docker.yml | 203 +++++++++++++++++++++++++++++ DOCKER_README.md | 89 ++++++------- Dockerfile | 92 +++++++++++++ docs/source/about/docker.rst | 2 - docs/source/about/installation.rst | 5 +- docs/source/building/linux.rst | 77 +---------- scripts/Dockerfile-debian | 40 ------ scripts/Dockerfile-fedora_33 | 32 ----- scripts/Dockerfile-fedora_35 | 36 ----- scripts/Dockerfile-ubuntu_18_04 | 63 --------- scripts/Dockerfile-ubuntu_20_04 | 46 ------- scripts/Dockerfile-ubuntu_21_04 | 40 ------ scripts/Dockerfile-ubuntu_21_10 | 39 ------ scripts/build-container.sh | 179 ------------------------- scripts/build-private.sh | 48 ------- scripts/build-sunshine.sh | 132 ------------------- 18 files changed, 360 insertions(+), 780 deletions(-) create mode 100644 .docker_platforms create mode 100644 .dockerignore create mode 100644 .github/workflows/ci-docker.yml create mode 100644 Dockerfile delete mode 100644 scripts/Dockerfile-debian delete mode 100644 scripts/Dockerfile-fedora_33 delete mode 100644 scripts/Dockerfile-fedora_35 delete mode 100644 scripts/Dockerfile-ubuntu_18_04 delete mode 100644 scripts/Dockerfile-ubuntu_20_04 delete mode 100644 scripts/Dockerfile-ubuntu_21_04 delete mode 100644 scripts/Dockerfile-ubuntu_21_10 delete mode 100755 scripts/build-container.sh delete mode 100755 scripts/build-private.sh delete mode 100755 scripts/build-sunshine.sh diff --git a/.docker_platforms b/.docker_platforms new file mode 100644 index 00000000..11a8121d --- /dev/null +++ b/.docker_platforms @@ -0,0 +1 @@ +linux/amd64 \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..fb14701d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,16 @@ +# ignore git files +.git* + +# ignore hidden files +.* + +# ignore repo directories and files +docs/ +packaging/ +scripts/ +tools/ +crowdin.yml + +# ignore dev directories +build/ +venv/ diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml new file mode 100644 index 00000000..962bb74c --- /dev/null +++ b/.github/workflows/ci-docker.yml @@ -0,0 +1,203 @@ +--- +# This action is centrally managed in https://github.com//.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: CI Docker + +on: + pull_request: + branches: [master, nightly] + types: [opened, synchronize, reopened] + push: + branches: [master, nightly] + workflow_dispatch: + +jobs: + check_dockerfile: + name: Check Dockerfile + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Check + id: check + run: | + if [ -f "./Dockerfile" ] + then + FOUND=true + else + FOUND=false + fi + + echo "dockerfile=${FOUND}" >> $GITHUB_OUTPUT + + outputs: + dockerfile: ${{ steps.check.outputs.dockerfile }} + + lint_dockerfile: + name: Lint Dockerfile + needs: [check_dockerfile] + if: ${{ needs.check_dockerfile.outputs.dockerfile == 'true' }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Hadolint + id: hadolint + uses: hadolint/hadolint-action@v2.1.0 + with: + dockerfile: ./Dockerfile + ignore: DL3008,DL3013,DL3016,DL3018,DL3028,DL3059 + output-file: ./hadolint.log + verbose: true + + - name: Log + if: failure() + run: | + echo "Hadolint outcome: ${{ steps.hadolint.outcome }}" >> $GITHUB_STEP_SUMMARY + cat "./hadolint.log" >> $GITHUB_STEP_SUMMARY + + check_changelog: + name: Check Changelog + needs: [check_dockerfile] + if: ${{ needs.check_dockerfile.outputs.dockerfile == 'true' }} + runs-on: ubuntu-latest + steps: + - name: Checkout + if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} + uses: actions/checkout@v3 + + - name: Verify Changelog + id: verify_changelog + if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} + # base_ref for pull request check, ref for push + uses: LizardByte/.github/actions/verify_changelog@master + with: + token: ${{ secrets.GITHUB_TOKEN }} + outputs: + next_version: ${{ steps.verify_changelog.outputs.changelog_parser_version }} + + docker: + name: Docker + needs: [check_dockerfile, check_changelog] + if: ${{ needs.check_dockerfile.outputs.dockerfile == 'true' }} + runs-on: ubuntu-latest + permissions: + packages: write + contents: write + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Prepare + id: prepare + env: + NEXT_VERSION: ${{ needs.check_changelog.outputs.next_version }} + run: | + # get branch name + BRANCH=${GITHUB_HEAD_REF} + + if [ -z "$BRANCH" ] + then + echo "This is a PUSH event" + BRANCH=${{ github.ref_name }} + fi + + # determine to push image to dockerhub and ghcr or not + if [[ $GITHUB_EVENT_NAME == "push" ]]; then + PUSH=true + else + PUSH=false + fi + + # setup the tags + REPOSITORY=${{ github.repository }} + BASE_TAG=$(echo $REPOSITORY | tr '[:upper:]' '[:lower:]') + COMMIT=${{ github.sha }} + + TAGS="${BASE_TAG}:${COMMIT:0:7},ghcr.io/${BASE_TAG}:${COMMIT:0:7}" + + if [[ $GITHUB_REF == refs/heads/master ]]; then + TAGS="${TAGS},${BASE_TAG}:latest,ghcr.io/${BASE_TAG}:latest" + TAGS="${TAGS},${BASE_TAG}:master,ghcr.io/${BASE_TAG}:master" + elif [[ $GITHUB_REF == refs/heads/nightly ]]; then + TAGS="${TAGS},${BASE_TAG}:nightly,ghcr.io/${BASE_TAG}:nightly" + else + TAGS="${TAGS},${BASE_TAG}:test,ghcr.io/${BASE_TAG}:test" + fi + + if [[ ${NEXT_VERSION} != "" ]]; then + TAGS="${TAGS},${BASE_TAG}:${NEXT_VERSION},ghcr.io/${BASE_TAG}:${NEXT_VERSION}" + fi + + # read the platforms from `.docker_platforms` + PLATFORMS=$(<.docker_platforms) + + echo "branch=${BRANCH}" >> $GITHUB_OUTPUT + echo "build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT + echo "commit=${COMMIT}" >> $GITHUB_OUTPUT + echo "platforms=${PLATFORMS}" >> $GITHUB_OUTPUT + echo "push=${PUSH}" >> $GITHUB_OUTPUT + echo "tags=${TAGS}" >> $GITHUB_OUTPUT + + - name: Set Up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + id: buildx + + - name: Cache Docker Layers + uses: actions/cache@v3 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Log in to Docker Hub + if: ${{ steps.prepare.outputs.push == 'true' }} # PRs do not have access to secrets + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + + - name: Log in to the Container registry + if: ${{ steps.prepare.outputs.push == 'true' }} # PRs do not have access to secrets + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ secrets.GH_BOT_NAME }} + password: ${{ secrets.GH_BOT_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v3 + with: + context: ./ + file: ./Dockerfile + push: ${{ steps.prepare.outputs.push }} + platforms: ${{ steps.prepare.outputs.platforms }} + build-args: | + BRANCH=${{ steps.prepare.outputs.branch }} + BUILD_DATE=${{ steps.prepare.outputs.build_date }} + BUILD_VERSION=${{ needs.check_changelog.outputs.next_version }} + COMMIT=${{ steps.prepare.outputs.commit }} + tags: ${{ steps.prepare.outputs.tags }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache + + - name: Update Docker Hub Description + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + uses: peter-evans/dockerhub-description@v3 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_PASSWORD }} # token is not currently supported + repository: ${{ env.BASE_TAG }} + short-description: ${{ github.event.repository.description }} + readme-filepath: ./DOCKER_README.md diff --git a/DOCKER_README.md b/DOCKER_README.md index a2647e00..87dbc672 100644 --- a/DOCKER_README.md +++ b/DOCKER_README.md @@ -1,44 +1,50 @@ # Docker -## Using docker run +## Build your own containers +This image provides a method for you to easily use the latest Sunshine release in your own docker projects. It is not +intended to use as a standalone container at this point, and should be considered experimental. + +```dockerfile +FROM lizardbyte/sunshine + +# install Steam, Wayland, etc. + +ENTRYPOINT steam && sunshine +``` + +## Where used +This is a list of docker projects using Sunshine. Something missing? Let us know about it! + +- [Games on Whales](https://games-on-whales.github.io) + +## Port and Volume mappings +Examples are below of the required mappings. The configuration file will be saved to `/config` in the container. + +### Using docker run Create and run the container (substitute your ``): ```bash docker run -d \ - --name=sunshine \ + --name= \ --restart=unless-stopped - -v :/config \ -e PUID= \ -e PGID= \ -e TZ= \ + -v :/config \ -p 47984-47990:47984-47990/tcp \ -p 48010:48010 \ -p 47998-48000:47998-48000/udp \ - lizardbyte/sunshine + ``` -To update the container it must be removed and recreated: - -```bash -# Stop the container -docker stop sunshine -# Remove the container -docker rm sunshine -# Pull the latest update -docker pull lizardbyte/sunshine -# Run the container with the same parameters as before -docker run -d ... -``` - -## Using docker-compose - +### Using docker-compose Create a `docker-compose.yml` file with the following contents (substitute your ``): ```yaml version: '3' services: - sunshine: - image: lizardbyte/sunshine + : + image: container_name: sunshine restart: unless-stopped volumes: @@ -48,26 +54,12 @@ services: - PGID= - TZ= ports: - - 47984-47990:47984-47990/tcp - - 48010:48010 - - 47998-48000:47998-48000/udp + - "47984-47990:47984-47990/tcp" + - "48010:48010" + - "47998-48000:47998-48000/udp" ``` -Create and start the container (run the command from the same folder as your `docker-compose.yml` file): - -```bash -docker-compose up -d -``` - -To update the container: -```bash -# Pull the latest update -docker-compose pull -# Update and restart the container -docker-compose up -d -``` - -## Parameters +### Parameters You must substitute the `` with your own settings. Parameters are split into two halves separated by a colon. The left side represents the host and the right side the @@ -79,16 +71,17 @@ port `47990` (e.g. `http://:47990`). The internal port must be `47990`, (e.g. `-p 8080:47990`). All the ports listed in the `docker run` and `docker-compose` examples are required. -| Parameter | Function | Example Value | Required | -| --------------------------- | -------------------- | ------------------- | -------- | -| `-p :47990` | Web UI Port | `47990` | True | -| `-v :/config` | Volume mapping | `/home/sunshine` | True | -| `-e PUID=` | User ID | `1001` | False | -| `-e PGID=` | Group ID | `1001` | False | -| `-e TZ=` | Lookup TZ value [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) | `America/New_York` | True | +| Parameter | Function | Example Value | Required | +|-----------------------------|---------------------------|--------------------|----------| +| `-p :47990` | Web UI Port | `47990` | True | +| `-v :/config` | Volume mapping | `/home/sunshine` | True | +| `-e PUID=` | User ID | `1001` | False | +| `-e PGID=` | Group ID | `1001` | False | +| `-e TZ=` | Lookup TZ value [here][1] | `America/New_York` | False | -### User / Group Identifiers: +[1]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +#### User / Group Identifiers: When using data volumes (-v flags) permissions issues can arise between the host OS and the container. To avoid this issue you can specify the user PUID and group PGID. Ensure the data volume directory on the host is owned by the same user you specify. @@ -99,3 +92,5 @@ In this instance `PUID=1001` and `PGID=1001`. To find yours use id user as below $ id dockeruser uid=1001(dockeruser) gid=1001(dockergroup) groups=1001(dockergroup) ``` + +If you want to change the PUID or PGID after the image has been built, it will require rebuilding the image. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..b493b0d4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,92 @@ +FROM ubuntu:22.04 AS sunshine-base + +ARG DEBIAN_FRONTEND=noninteractive + +FROM sunshine-base as sunshine-build + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +RUN apt-get update -y \ + && apt-get install -y --no-install-recommends \ + build-essential=12.9* \ + cmake=3.22.1* \ + libavdevice-dev=7:4.4.* \ + libboost-filesystem-dev=1.74.0* \ + libboost-log-dev=1.74.0* \ + libboost-thread-dev=1.74.0* \ + libcap-dev=1:2.44* \ + libdrm-dev=2.4.110* \ + libevdev-dev=1.12.1* \ + libpulse-dev=1:15.99.1* \ + libopus-dev=1.3.1* \ + libssl-dev=3.0.2* \ + libwayland-dev=1.20.0* \ + libx11-dev=2:1.7.5* \ + libxcb-shm0-dev=1.14* \ + libxcb-xfixes0-dev=1.14* \ + libxcb1-dev=1.14* \ + libxfixes-dev=1:6.0.0* \ + libxrandr-dev=2:1.5.2* \ + libxtst-dev=2:1.2.3* \ + nvidia-cuda-dev=11.5.1* \ + nvidia-cuda-toolkit=11.5.1* \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# copy repository +WORKDIR /root/sunshine-build/ +COPY . . + +# setup build directory +WORKDIR /root/sunshine-build/build + +# cmake and cpack +RUN cmake -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 \ + /root/sunshine-build \ + && make -j "$(nproc)" \ + && cpack -G DEB + +FROM sunshine-base as sunshine + +# copy deb from builder +COPY --from=sunshine-build /root/sunshine-build/build/cpack_artifacts/Sunshine.deb /sunshine.deb + +# install sunshine +RUN apt-get update -y \ + && apt-get install -y --no-install-recommends /sunshine.deb \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# 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 + +RUN 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} + +USER ${UNAME} +WORKDIR ${HOME} + +# entrypoint +ENTRYPOINT ["/usr/bin/sunshine"] diff --git a/docs/source/about/docker.rst b/docs/source/about/docker.rst index 8a597e26..77e4e208 100644 --- a/docs/source/about/docker.rst +++ b/docs/source/about/docker.rst @@ -1,5 +1,3 @@ :github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/DOCKER_README.md -.. Todo:: This is a planned feature. Currently no Dockerfile or image exists for Sunshine. - .. mdinclude:: ../../../DOCKER_README.md diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index eecdc04f..621e771f 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -17,8 +17,9 @@ Binaries can be found in the `latest release`_. Docker ------ -.. Todo:: Docker images of Sunshine are planned to be included in the future. - They will be available on `Dockerhub.io`_ and `ghcr.io`_. +Docker images are available on `Dockerhub.io`_ and `ghcr.io`_. + +See :ref:`Docker ` for additional information. Linux ----- diff --git a/docs/source/building/linux.rst b/docs/source/building/linux.rst index c841ad61..b2b4bfc7 100644 --- a/docs/source/building/linux.rst +++ b/docs/source/building/linux.rst @@ -5,8 +5,6 @@ Linux Requirements ------------ -.. Danger:: Installing these dependencies may break your distribution. It is recommended to build in a virtual machine - or to use the `Dockerfile builds`_ located in the `./scripts` directory. Debian Bullseye ^^^^^^^^^^^^^^^ @@ -18,7 +16,6 @@ Install Requirements sudo apt update && sudo apt install \ build-essential \ cmake \ - git \ libavdevice-dev \ libboost-filesystem-dev \ libboost-log-dev \ @@ -97,7 +94,6 @@ Install Requirements build-essential \ cmake \ gcc-10 \ - git \ g++-10 \ libavdevice-dev \ libboost-filesystem1.71-dev \ @@ -150,7 +146,6 @@ Install Requirements sudo apt update && sudo apt install \ build-essential \ cmake \ - git \ g++-10 \ libavdevice-dev \ libboost-filesystem-dev \ @@ -183,9 +178,9 @@ Install CuDA wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O ./cuda.run && chmod a+x ./cuda.run ./cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm ./cuda.run -Ubuntu 21.10 +Ubuntu 22.04 ^^^^^^^^^^^^ -End of Life: July 2022 +End of Life: April 2027 Install Requirements .. code-block:: bash @@ -193,7 +188,6 @@ Install Requirements sudo apt update && sudo apt install \ build-essential \ cmake \ - git \ libavdevice-dev \ libboost-filesystem-dev \ libboost-log-dev \ @@ -215,12 +209,6 @@ Install Requirements nvidia-cuda-dev \ # Cuda, NvFBC nvidia-cuda-toolkit \ # Cuda, NvFBC -Ubuntu 22.04 -^^^^^^^^^^^^ -End of Life: April 2027 - -.. Todo:: Create Ubuntu 22.04 Dockerfile and complete this documentation. - Build ----- .. Attention:: Ensure you are in the build directory created during the clone step earlier before continuing. @@ -230,7 +218,7 @@ Debian based OSes cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 .. -Red Hat based Oses +Red Hat based OSes .. code-block:: bash cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ .. @@ -241,62 +229,3 @@ Finally make -j ${nproc} cpack -G DEB # optionally, create a deb package cpack -G RPM # optionally, create a rpm package - -Dockerfile Builds ------------------ -You may wish to simply build sunshine from source, without bloating your OS with development files. -There are scripts located in the ``./scripts`` directory that will create docker images that have the necessary -packages. As a result, removing the development files after you're done is a single command away. -These scripts use docker under the hood, as such, they can only be used to compile the Linux version - -.. Todo:: Publish the Dockerfiles to Dockerhub and ghcr. - -Requirements - Install `Docker `_ - -Instructions - #. :ref:`Clone `. Sunshine. - #. Select the desired Dockerfile from the ``./scripts`` directory. - - Available Files: - .. code-block:: text - - Dockerfile-debian - Dockerfile-fedora_33 # end of life - Dockerfile-fedora_35 - Dockerfile-ubuntu_18_04 - Dockerfile-ubuntu_20_04 - Dockerfile-ubuntu_21_04 # end of life - Dockerfile-ubuntu_21_10 - - #. Execute - - .. code-block:: bash - - cd scripts # move to the scripts directory - ./build-container.sh -f Dockerfile- # create the container (replace the "") - ./build-sunshine.sh -p -s .. # compile and build sunshine - - #. Updating - - .. code-block:: bash - - git pull # pull the latest changes from github - ./build-sunshine.sh -p -s .. # compile and build sunshine - - #. Optionally, delete the container - .. code-block:: bash - - ./build-container.sh -c delete - - #. Install the resulting package - - Debian - .. code-block:: bash - - sudo apt install -f sunshine-build/sunshine.deb - - Red Hat - .. code-block:: bash - - sudo dnf install sunshine-build/sunshine.rpm diff --git a/scripts/Dockerfile-debian b/scripts/Dockerfile-debian deleted file mode 100644 index fc77eb8e..00000000 --- a/scripts/Dockerfile-debian +++ /dev/null @@ -1,40 +0,0 @@ -FROM debian:bullseye AS sunshine-debian - -# Debian Bullseye end of life is TBD - -ARG DEBIAN_FRONTEND=noninteractive -ARG TZ="Europe/London" - -SHELL ["/bin/bash", "-o", "pipefail", "-c"] -RUN echo deb http://deb.debian.org/debian/ bullseye main contrib non-free | tee /etc/apt/sources.list.d/non-free.list -RUN apt-get update -y && \ - apt-get install -y \ - build-essential \ - cmake \ - git \ - libavdevice-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libboost-thread-dev \ - libcap-dev \ - libdrm-dev \ - libevdev-dev \ - libpulse-dev \ - libopus-dev \ - libssl-dev \ - libwayland-dev \ - libx11-dev \ - libxcb-shm0-dev \ - libxcb-xfixes0-dev \ - libxcb1-dev \ - libxfixes-dev \ - libxrandr-dev \ - libxtst-dev \ - nvidia-cuda-dev \ - nvidia-cuda-toolkit \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# Entrypoint -COPY build-private.sh /root/build.sh -ENTRYPOINT ["/root/build.sh", "-deb"] diff --git a/scripts/Dockerfile-fedora_33 b/scripts/Dockerfile-fedora_33 deleted file mode 100644 index 320c6a67..00000000 --- a/scripts/Dockerfile-fedora_33 +++ /dev/null @@ -1,32 +0,0 @@ -FROM fedora:33 AS sunshine-fedora_33 - -# Fedora 33 end of life is November 2021 -# This file remains for reference only - -SHELL ["/bin/bash", "-o", "pipefail", "-c"] -RUN dnf -y update && \ - dnf -y group install "Development Tools" && \ - dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm && \ - dnf -y install \ - boost-devel \ - boost-static.x86_64 \ - cmake \ - ffmpeg-devel \ - libevdev-devel \ - libxcb-devel \ - libX11-devel \ - libXfixes-devel \ - libXrandr-devel \ - libXtst-devel \ - openssl-devel \ - opus-devel \ - pulseaudio-libs-devel \ - libcap-devel \ - libdrm-devel \ - rpm-build \ - && dnf clean all \ - && rm -rf /var/cache/yum - -# Entrypoint -COPY build-private.sh /root/build.sh -ENTRYPOINT ["/root/build.sh", "-rpm"] diff --git a/scripts/Dockerfile-fedora_35 b/scripts/Dockerfile-fedora_35 deleted file mode 100644 index 18f5bf53..00000000 --- a/scripts/Dockerfile-fedora_35 +++ /dev/null @@ -1,36 +0,0 @@ -FROM fedora:35 AS sunshine-fedora_35 - -# Fedora 35 end of life is TBD - -SHELL ["/bin/bash", "-o", "pipefail", "-c"] -RUN dnf -y update && \ - dnf -y group install "Development Tools" && \ - dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm && \ - dnf -y install \ - boost-devel \ - boost-static.x86_64 \ - cmake \ - ffmpeg-devel \ - gcc-c++ \ - libevdev-devel \ - libX11-devel \ - libxcb-devel \ - libXcursor-devel \ - libXfixes-devel \ - libXinerama-devel \ - libXi-devel \ - libXrandr-devel \ - libXtst-devel \ - mesa-libGL-devel \ - openssl-devel \ - opus-devel \ - pulseaudio-libs-devel \ - libcap-devel \ - libdrm-devel \ - rpm-build \ - && dnf clean all \ - && rm -rf /var/cache/yum - -# Entrypoint -COPY build-private.sh /root/build.sh -ENTRYPOINT ["/root/build.sh", "-rpm"] diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 deleted file mode 100644 index 6ad3e8c0..00000000 --- a/scripts/Dockerfile-ubuntu_18_04 +++ /dev/null @@ -1,63 +0,0 @@ -FROM ubuntu:18.04 AS sunshine-ubuntu_18_04 - -# Ubuntu 18.04 end of life is April 2028 - -ARG DEBIAN_FRONTEND=noninteractive -ARG TZ="Europe/London" - -SHELL ["/bin/bash", "-o", "pipefail", "-c"] -RUN apt-get update -y && \ - apt-get install -y \ - software-properties-common \ - && add-apt-repository ppa:savoury1/graphics && \ - add-apt-repository ppa:savoury1/multimedia && \ - add-apt-repository ppa:savoury1/ffmpeg4 && \ - add-apt-repository ppa:savoury1/boost-defaults-1.71 && \ - add-apt-repository ppa:ubuntu-toolchain-r/test && \ - apt-get update -y && \ - apt-get install -y \ - build-essential \ - cmake \ - gcc-10 \ - git \ - g++-10 \ - libavdevice-dev \ - libboost-filesystem1.71-dev \ - libboost-log1.71-dev \ - libboost-regex1.71-dev \ - libboost-thread1.71-dev \ - libcap-dev \ - libdrm-dev \ - libevdev-dev \ - libpulse-dev \ - libopus-dev \ - libssl-dev \ - libwayland-dev \ - libx11-dev \ - libxcb-shm0-dev \ - libxcb-xfixes0-dev \ - libxcb1-dev \ - libxfixes-dev \ - libxrandr-dev \ - libxtst-dev \ - wget \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# Update gcc alias -RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 - -# Install CuDA -RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run -RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm /root/cuda.run - -# Install cmake -ADD https://cmake.org/files/v3.22/cmake-3.22.2-linux-x86_64.sh /cmake-3.22.2-linux-x86_64.sh -RUN mkdir /opt/cmake -RUN sh /cmake-3.22.2-linux-x86_64.sh --prefix=/opt/cmake --skip-license -RUN ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake -RUN cmake --version - -# Entrypoint -COPY build-private.sh /root/build.sh -ENTRYPOINT ["/root/build.sh", "-deb"] diff --git a/scripts/Dockerfile-ubuntu_20_04 b/scripts/Dockerfile-ubuntu_20_04 deleted file mode 100644 index 44e897a7..00000000 --- a/scripts/Dockerfile-ubuntu_20_04 +++ /dev/null @@ -1,46 +0,0 @@ -FROM ubuntu:20.04 AS sunshine-ubuntu_20_04 - -# Ubuntu 20.04 end of life is April 2030 - -ARG DEBIAN_FRONTEND=noninteractive -ARG TZ="Europe/London" - -SHELL ["/bin/bash", "-o", "pipefail", "-c"] -RUN apt-get update -y && \ - apt-get install -y \ - build-essential \ - cmake \ - git \ - g++-10 \ - libavdevice-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libboost-thread-dev \ - libcap-dev \ - libdrm-dev \ - libevdev-dev \ - libpulse-dev \ - libopus-dev \ - libssl-dev \ - libwayland-dev \ - libx11-dev \ - libxcb-shm0-dev \ - libxcb-xfixes0-dev \ - libxcb1-dev \ - libxfixes-dev \ - libxrandr-dev \ - libxtst-dev \ - wget \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# Update gcc alias -RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 - -# Install CuDA -RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run -RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm /root/cuda.run - -# Entrypoint -COPY build-private.sh /root/build.sh -ENTRYPOINT ["/root/build.sh", "-deb"] diff --git a/scripts/Dockerfile-ubuntu_21_04 b/scripts/Dockerfile-ubuntu_21_04 deleted file mode 100644 index 012845a5..00000000 --- a/scripts/Dockerfile-ubuntu_21_04 +++ /dev/null @@ -1,40 +0,0 @@ -FROM ubuntu:21.04 AS sunshine-ubuntu_21_04 - -# Ubuntu 21.04 end of life is January 2022 -# This file remains for reference only - -ARG DEBIAN_FRONTEND=noninteractive -ARG TZ="Europe/London" - -SHELL ["/bin/bash", "-o", "pipefail", "-c"] -RUN apt-get update -y && \ - apt-get install -y \ - build-essential \ - cmake \ - git \ - libavdevice-dev \ - libboost-thread-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libcap-dev \ - libdrm-dev \ - libevdev-dev \ - libpulse-dev \ - libopus-dev \ - libssl-dev \ - libwayland-dev \ - libx11-dev \ - libxcb-shm0-dev \ - libxcb-xfixes0-dev \ - libxcb1-dev \ - libxfixes-dev \ - libxrandr-dev \ - libxtst-dev \ - nvidia-cuda-dev \ - nvidia-cuda-toolkit \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# Entrypoint -COPY build-private.sh /root/build.sh -ENTRYPOINT ["/root/build.sh", "-deb"] diff --git a/scripts/Dockerfile-ubuntu_21_10 b/scripts/Dockerfile-ubuntu_21_10 deleted file mode 100644 index 6be49dbe..00000000 --- a/scripts/Dockerfile-ubuntu_21_10 +++ /dev/null @@ -1,39 +0,0 @@ -FROM ubuntu:21.10 AS sunshine-ubuntu_21_10 - -# Ubuntu 21.10 end of life is July 2022 - -ARG DEBIAN_FRONTEND=noninteractive -ARG TZ="Europe/London" - -SHELL ["/bin/bash", "-o", "pipefail", "-c"] -RUN apt-get update -y && \ - apt-get install -y \ - build-essential \ - cmake \ - git \ - libavdevice-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libboost-thread-dev \ - libcap-dev \ - libdrm-dev \ - libevdev-dev \ - libpulse-dev \ - libopus-dev \ - libssl-dev \ - libwayland-dev \ - libx11-dev \ - libxcb-shm0-dev \ - libxcb-xfixes0-dev \ - libxcb1-dev \ - libxfixes-dev \ - libxrandr-dev \ - libxtst-dev \ - nvidia-cuda-dev \ - nvidia-cuda-toolkit \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# Entrypoint -COPY build-private.sh /root/build.sh -ENTRYPOINT ["/root/build.sh", "-deb"] diff --git a/scripts/build-container.sh b/scripts/build-container.sh deleted file mode 100755 index 5da4784a..00000000 --- a/scripts/build-container.sh +++ /dev/null @@ -1,179 +0,0 @@ -#!/bin/bash -e -set -e - -usage() { - echo "Usage: $0 [OPTIONS]" - echo " -c: command --> default [build]" - echo " | delete --> Delete the container, Dockerfile isn't mandatory" - echo " | build --> Build the container, Dockerfile is mandatory" - echo " | compile --> Builds the container, then compiles it. Dockerfile is mandatory" - echo "" - echo " -s: path: The path to the source for compilation" - echo " -n: name: Docker container name --> default [sunshine]" - echo " --> all: Build/Compile/Delete all available docker containers" - echo " -f: Dockerfile: The name of the docker file" -} - -# Attempt to turn relative paths into absolute paths -absolute_path() { - RELATIVE_PATH=$1 - if which realpath >/dev/null 2>/dev/null - then - RELATIVE_PATH=$(realpath $RELATIVE_PATH) - else - echo "Warning: realpath is not installed on your system, ensure [$1] is absolute" - fi - - RETURN=$RELATIVE_PATH -} - -CONTAINER_NAME=sunshine -COMMAND=BUILD - -build_container() { - CONTAINER_NAME=$1 - DOCKER_FILE=$2 - - if [ ! -f "$DOCKER_FILE" ] - then - echo "Error: $DOCKER_FILE doesn't exist" - exit 7 - fi - - echo "docker build . -t $CONTAINER_NAME -f $DOCKER_FILE" - docker build . -t "$CONTAINER_NAME" -f "$DOCKER_FILE" -} - -delete() { - CONTAINER_NAME_UPPER=$(echo "$CONTAINER_NAME" | tr '[:lower:]' '[:upper:]') - if [ "$CONTAINER_NAME_UPPER" = "ALL" ] - then - shopt -s nullglob - for file in $(find . -maxdepth 1 -iname "Dockerfile-*" -type f) - do - CURRENT_CONTAINER="sunshine-$(echo $file | cut -c 14-)" - - if docker inspect "$CURRENT_CONTAINER" > /dev/null 2> /dev/null - then - echo "docker rmi $CURRENT_CONTAINER" - docker rmi "$CURRENT_CONTAINER" - fi - done - shopt -u nullglob #revert nullglob back to it's normal default state - else - if docker inspect "$CONTAINER_NAME" > /dev/null 2> /dev/null - then - echo "docker rmi $CONTAINER_NAME" - docker rmi $CONTAINER_NAME - fi - fi -} - -build() { - CONTAINER_NAME_UPPER=$(echo "$CONTAINER_NAME" | tr '[:lower:]' '[:upper:]') - if [ "$CONTAINER_NAME_UPPER" = "ALL" ] - then - shopt -s nullglob - for file in $(find . -maxdepth 1 -iname "Dockerfile-*" -type f) - do - CURRENT_CONTAINER="sunshine-$(echo $file | cut -c 14-)" - build_container "$CURRENT_CONTAINER" "$file" - done - shopt -u nullglob #revert nullglob back to it's normal default state - else - if [[ -z "$DOCKER_FILE" ]] - then - echo "Error: if container name isn't equal to 'all', you need to specify the Dockerfile" - exit 6 - fi - - build_container "$CONTAINER_NAME" "$DOCKER_FILE" - fi -} - -abort() { - echo "$1" - exit 10 -} - -compile() { - CONTAINER_NAME_UPPER=$(echo "$CONTAINER_NAME" | tr '[:lower:]' '[:upper:]') - if [ "$CONTAINER_NAME_UPPER" = "ALL" ] - then - shopt -s nullglob - - # If any docker container doesn't exist, we cannot compile all of them - for file in $(find . -maxdepth 1 -iname "Dockerfile-*" -type f) - do - CURRENT_CONTAINER="sunshine-$(echo $file | cut -c 14-)" - - # If container doesn't exist --> abort. - docker inspect "$CURRENT_CONTAINER" > /dev/null 2> /dev/null || abort "Error: container image [$CURRENT_CONTAINER] doesn't exist" - done - - for file in $(find . -maxdepth 1 -iname "Dockerfile-*" -type f) - do - CURRENT_CONTAINER="sunshine-$(echo $file | cut -c 14-)" - - echo "$PWD/build-sunshine.sh -p -n $CURRENT_CONTAINER $SUNSHINE_SOURCES" - "$PWD/build-sunshine.sh" -p -n "$CURRENT_CONTAINER" $SUNSHINE_SOURCES - done - shopt -u nullglob #revert nullglob back to it's normal default state - else - # If container exists - if docker inspect "$CONTAINER_NAME" > /dev/null 2> /dev/null - then - echo "$PWD/build-sunshine.sh -p -n $CONTAINER_NAME $SUNSHINE_SOURCES" - "$PWD/build-sunshine.sh" -p -n "$CONTAINER_NAME" $SUNSHINE_SOURCES - else - echo "Error: container image [$CONTAINER_NAME] doesn't exist" - exit 9 - fi - fi -} - -while getopts ":c:hn:f:s:" arg; do - case ${arg} in - s) - SUNSHINE_SOURCES="-s $OPTARG" - ;; - c) - COMMAND=$(echo $OPTARG | tr '[:lower:]' '[:upper:]') - ;; - n) - echo "Container name: $OPTARG" - CONTAINER_NAME="$OPTARG" - ;; - f) - echo "Using Dockerfile [$OPTARG]" - DOCKER_FILE="$OPTARG" - ;; - h) - usage - exit 0 - ;; - esac -done - -echo "$0 set to $(echo $COMMAND | tr '[:upper:]' '[:lower:]')" - -if [ "$COMMAND" = "BUILD" ] -then - echo "Start building..." - delete - build - echo "Done." -elif [ "$COMMAND" = "COMPILE" ] -then - echo "Start compiling..." - compile - echo "Done." -elif [ "$COMMAND" = "DELETE" ] -then - echo "Start deleting..." - delete - echo "Done." -else - echo "Unknown command [$(echo $COMMAND | tr '[:upper:]' '[:lower:]')]" - exit 4 -fi diff --git a/scripts/build-private.sh b/scripts/build-private.sh deleted file mode 100755 index 80c7d6bc..00000000 --- a/scripts/build-private.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash -e -set -e - -CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}" -SUNSHINE_EXECUTABLE_PATH="${SUNSHINE_EXECUTABLE_PATH:-/usr/bin/sunshine}" -SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR:-/etc/sunshine}" - - -SUNSHINE_ROOT="${SUNSHINE_ROOT:-/root/sunshine}" -SUNSHINE_TAG="${SUNSHINE_TAG:-master}" -SUNSHINE_GIT_URL="${SUNSHINE_GIT_URL:-https://github.com/lizardbyte/sunshine.git}" - - -SUNSHINE_ENABLE_WAYLAND=${SUNSHINE_ENABLE_WAYLAND:-ON} -SUNSHINE_ENABLE_X11=${SUNSHINE_ENABLE_X11:-ON} -SUNSHINE_ENABLE_DRM=${SUNSHINE_ENABLE_DRM:-ON} -SUNSHINE_ENABLE_CUDA=${SUNSHINE_ENABLE_CUDA:-ON} - -# For debugging, it would be usefull to have the sources on the host. -if [[ ! -d "$SUNSHINE_ROOT" ]] -then - git clone --depth 1 --branch "$SUNSHINE_TAG" "$SUNSHINE_GIT_URL" --recurse-submodules "$SUNSHINE_ROOT" -fi - -if [[ ! -d /root/sunshine-build ]] -then - mkdir -p /root/sunshine-build -fi -cd /root/sunshine-build - -cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" "$SUNSHINE_ROOT" - -make -j ${nproc} - -# Get preferred package format -if [ "$1" == "-rpm" ] -then - echo "Packaging in .rpm format." - ./gen-rpm -d -elif [ "$1" == "-deb" ] -then - echo "Packaging in .deb format." - ./gen-deb -else - echo "Preferred packaging not specified." - echo "Use -deb or -rpm to specify preferred package format." - exit 1 -fi diff --git a/scripts/build-sunshine.sh b/scripts/build-sunshine.sh deleted file mode 100755 index 42843752..00000000 --- a/scripts/build-sunshine.sh +++ /dev/null @@ -1,132 +0,0 @@ -#!/bin/bash -e -set -e - -usage() { - echo "Usage: $0" - echo " -d: Generate a debug build" - echo " -p: Generate a linux package" - echo " -e: Extension of package... i.e. 'deb', 'rpm' --> default [deb]" - echo " -u: The input device is not a TTY" - echo " -n name: Docker container name --> default [sunshine]" - echo " -s path/to/sources/sunshine: Use local sources instead of a git repository" - echo " -c path/to/cmake/binary/dir: Store cmake output on host OS" -} - -# Attempt to turn relative paths into absolute paths -absolute_path() { - RELATIVE_PATH=$1 - if which realpath >/dev/null 2>/dev/null - then - RELATIVE_PATH=$(realpath $RELATIVE_PATH) - else - echo "Warning: realpath is not installed on your system, ensure [$1] is absolute" - fi - - RETURN=$RELATIVE_PATH -} - -CMAKE_BUILD_TYPE="-e CMAKE_BUILD_TYPE=Release" -SUNSHINE_PACKAGE_BUILD=OFF -SUNSHINE_PACKAGE_EXTENSION=deb -SUNSHINE_GIT_URL=https://github.com/lizardbyte/sunshine.git -CONTAINER_NAME=sunshine - -# Docker will fail if ctrl+c is passed through and the input is not a tty -DOCKER_INTERACTIVE=-ti - -while getopts ":dpuhc:e:s:n:" arg; do - case ${arg} in - u) - echo "Input device is not a TTY" - USERNAME="$USER" - unset DOCKER_INTERACTIVE - ;; - d) - echo "Creating debug build" - CMAKE_BUILD_TYPE="-e CMAKE_BUILD_TYPE=Debug" - ;; - p) - echo "Creating package build" - SUNSHINE_PACKAGE_BUILD=ON - SUNSHINE_ASSETS_DIR="-e SUNSHINE_ASSETS_DIR=/etc/sunshine" - SUNSHINE_EXECUTABLE_PATH="-e SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine" - ;; - e) - echo "Defining package extension: $OPTARG" - if [ "$OPTARG" == "deb" ] - then - SUNSHINE_PACKAGE_EXTENSION=$OPTARG - echo "Package extension: deb" - elif [ "$OPTARG" == "rpm" ] - then - SUNSHINE_PACKAGE_EXTENSION=$OPTARG - echo "Package extension: rpm" - else - echo "Package extension not supported: $OPTARG" - echo "Falling back to default package extension: $SUNSHINE_PACKAGE_EXTENSION" - fi - ;; - s) - absolute_path "$OPTARG" - OPTARG="$RETURN" - echo "Using sources from $OPTARG" - SUNSHINE_ROOT="-v $OPTARG:/root/sunshine" - ;; - c) - [ "$USERNAME" == "" ] && USERNAME=$(logname) - - absolute_path "$OPTARG" - OPTARG="$RETURN" - - echo "Using $OPTARG as cmake binary dir" - if [[ ! -d $OPTARG ]] - then - echo "cmake binary dir doesn't exist, a new one will be created." - mkdir -p "$OPTARG" - [ "$USERNAME" == "$USER"] || chown $USERNAME:$USERNAME "$OPTARG" - fi - - CMAKE_ROOT="-v $OPTARG:/root/sunshine-build" - ;; - n) - echo "Container name: $OPTARG" - CONTAINER_NAME=$OPTARG - ;; - h) - usage - exit 0 - ;; - esac -done - -[ "$USERNAME" = "" ] && USERNAME=$(logname) - -BUILD_DIR="$PWD/$CONTAINER_NAME-build" -[ "$SUNSHINE_ASSETS_DIR" = "" ] && SUNSHINE_ASSETS_DIR="-e SUNSHINE_ASSETS_DIR=$BUILD_DIR/assets" -[ "$SUNSHINE_EXECUTABLE_PATH" = "" ] && SUNSHINE_EXECUTABLE_PATH="-e SUNSHINE_EXECUTABLE_PATH=$BUILD_DIR/sunshine" - -echo "docker run $DOCKER_INTERACTIVE --privileged $SUNSHINE_ROOT $CMAKE_ROOT $SUNSHINE_ASSETS_DIR $SUNSHINE_EXECUTABLE_PATH $CMAKE_BUILD_TYPE --name $CONTAINER_NAME $CONTAINER_NAME" -docker run $DOCKER_INTERACTIVE --privileged $SUNSHINE_ROOT $CMAKE_ROOT $SUNSHINE_ASSETS_DIR $SUNSHINE_EXECUTABLE_PATH $CMAKE_BUILD_TYPE --name $CONTAINER_NAME $CONTAINER_NAME - -exit_code=$? - -if [ $exit_code -eq 0 ] -then - mkdir -p $BUILD_DIR - case $SUNSHINE_PACKAGE_BUILD in - ON) - echo "Downloading package to: $BUILD_DIR/$CONTAINER_NAME.$SUNSHINE_PACKAGE_EXTENSION" - docker cp $CONTAINER_NAME:/root/sunshine-build/package-$SUNSHINE_PACKAGE_EXTENSION/sunshine.$SUNSHINE_PACKAGE_EXTENSION "$BUILD_DIR/$CONTAINER_NAME.$SUNSHINE_PACKAGE_EXTENSION" - ;; - *) - echo "Downloading binary and assets to: $BUILD_DIR" - docker cp $CONTAINER_NAME:/root/sunshine/assets "$BUILD_DIR" - docker cp $CONTAINER_NAME:/root/sunshine-build/sunshine "$BUILD_DIR" - ;; - esac - echo "chown --recursive $USERNAME:$USERNAME $BUILD_DIR" - chown --recursive $USERNAME:$USERNAME "$BUILD_DIR" -fi - -echo "Removing docker container $CONTAINER_NAME" -docker rm $CONTAINER_NAME From 548eeb8889b160a696aa8f23077496c3125a201c Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 28 Oct 2022 12:20:16 -0400 Subject: [PATCH 408/817] update docs --- .github/ISSUE_TEMPLATE/bug-report.yml | 64 ++++++++++++++++++++----- README.rst | 20 +++++--- docs/source/about/installation.rst | 10 +++- docs/source/about/usage.rst | 14 +----- docs/source/troubleshooting/general.rst | 9 ++++ 5 files changed, 83 insertions(+), 34 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index bd21df29..9e685501 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -6,7 +6,22 @@ body: attributes: value: > **THIS IS NOT THE PLACE TO ASK FOR SUPPORT!** - Please use [Discord](https://docs.lizardbyte.dev/en/latest/about/support.html#discord) for support issues. + Please use our [Support Center](https://app.lizardbyte.dev/support) for support issues. + Non actionable bug reports will be locked and closed! + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. + options: + - label: I have searched the existing issues + required: true + - type: checkboxes + attributes: + label: Is your issue described in the documentation? + description: Please read our [documentation](https://docs.lizardbyte.dev/projects/sunshine) + options: + - label: I have read the documentation + required: true - type: textarea id: description attributes: @@ -30,10 +45,11 @@ body: label: Host Operating System description: What version operating system are you running the software on? options: + - Docker - Linux - macOS - Windows - - other + - other, n/a - type: input id: os-version attributes: @@ -41,28 +57,50 @@ body: description: Provide the version of the operating system. Additionally a build number would be helpful. validations: required: true - - type: input + - type: dropdown id: os-architecture attributes: label: Architecture - placeholder: e.g. 32 bit, 64 bit, arm - validations: - required: true + options: + - 32 bit + - 64 bit + - arm + - other, n/a - type: input id: version attributes: - label: Sunshine Version + label: Sunshine commit or version placeholder: eg. 0.14.0 validations: required: true - - type: input + - type: dropdown + id: package_type + attributes: + label: Package + description: The package you installed + options: + - Linux - AppImage + - Linux - deb + - Linux - flatpak + - Linux - rpm + - macOS - dmg + - macOS - Portfile + - macOS - pkg + - Windows - installer + - Windows - portable + - other (not listed) + - other (self built) + - other (fork of this repo) + - type: dropdown id: graphics_type attributes: label: GPU Type description: The type of the installed graphics card. - placeholder: e.g. Intel, AMD, Nvidia - validations: - required: true + options: + - AMD + - Intel + - Nvidia + - none (software encoding) - type: input id: graphics_model attributes: @@ -83,8 +121,8 @@ body: id: capture_method attributes: label: Capture Method (Linux Only) - description: The driver/mesa version of the installed graphics card. - placeholder: e.g. PipeWire/KVM/X11 + description: If on Linux, the capture method being used. + placeholder: e.g. PipeWire/KVM/X11/KMS validations: required: false - type: textarea diff --git a/README.rst b/README.rst index 8d51ff09..4f7a5102 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ Overview ======== -LizardByte has the full documentation hosted on `Read the Docs `_. +LizardByte has the full documentation hosted on `Read the Docs `_. About ----- @@ -48,7 +48,8 @@ Integrations Support --------- -Our support methods are listed in our `LizardByte Docs `_. +Our support methods are listed in our +`LizardByte Docs `_. Downloads --------- @@ -57,11 +58,16 @@ Downloads :alt: GitHub Releases :target: https://github.com/LizardByte/Sunshine/releases/latest +.. image:: https://img.shields.io/docker/pulls/lizardbyte/sunshine?style=for-the-badge&logo=docker + :alt: Docker + :target: https://hub.docker.com/r/lizardbyte/sunshine + +Stats +------ +.. image:: https://img.shields.io/github/stars/lizardbyte/sunshine?logo=github&style=for-the-badge + :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 - -.. comment - image:: https://img.shields.io/docker/pulls/lizardbyte/sunshine?style=for-the-badge&logo=docker - :alt: Docker - :target: https://hub.docker.com/r/lizardbyte/sunshine diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 621e771f..5c9f2194 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -124,9 +124,15 @@ Flatpak Package Start: - .. code-block:: bash + X11 and NVFBC capture (X11 Only) + .. code-block:: bash + + flatpak run dev.lizardbyte.sunshine + + KMS capture (Wayland & X11) + .. code-block:: bash - flatpak run dev.lizardbyte.sunshine + sudo -i PULSE_SERVER=unix:$(pactl info | awk '/Server String/{print$3}') flatpak run dev.lizardbyte.sunshine Uninstall: diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst index f1012192..0e3c87a4 100644 --- a/docs/source/about/usage.rst +++ b/docs/source/about/usage.rst @@ -67,20 +67,10 @@ Sunshine needs access to `uinput` to create mouse and gamepad events. sudo usermod -a -G input $USER #. Create `udev` rules. - .. code-block:: bash - - sudo nano /etc/udev/rules.d/85-sunshine.rules - - Input the following contents. - .. code-block:: - KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput" - - Save the file and exit: - - #. ``CTRL+X`` to start exit. - #. ``Y`` to save modifications. + echo 'KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput"' | \ + sudo tee /etc/udev/rules.d/85-sunshine-input.rules #. Optionally, configure autostart service - filename: ``~/.config/systemd/user/sunshine.service`` diff --git a/docs/source/troubleshooting/general.rst b/docs/source/troubleshooting/general.rst index 865214db..6c480720 100644 --- a/docs/source/troubleshooting/general.rst +++ b/docs/source/troubleshooting/general.rst @@ -11,3 +11,12 @@ If you forgot your credentials to the web UI, try this. Can't access the web UI? #. Check firefall rules. + +NvFBC, NvENC, or general issues with Nvidia graphics card. + + - Consume grade Nvidia cards are software limited to a specific number of encodes. See + `Video Encode and Decode GPU Support Matrix `_ + for more info. + - You can usually bypass the restriction with a driver patch. See Keylase's + `Linux `_ + or `Windows `_ patches for more guidance. From d0ce0a67f2cd05722b7f6ac8b28bd46b6604c3d0 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 28 Oct 2022 15:44:27 -0400 Subject: [PATCH 409/817] fix winget releaser --- .github/workflows/CI.yml | 14 ++++++++++++++ .github/workflows/winget.yml | 17 ----------------- 2 files changed, 14 insertions(+), 17 deletions(-) delete mode 100644 .github/workflows/winget.yml diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2d367286..8eee5358 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -800,3 +800,17 @@ jobs: next_version: ${{ needs.check_changelog.outputs.next_version }} last_version: ${{ needs.check_changelog.outputs.last_version }} release_body: ${{ needs.check_changelog.outputs.release_body }} + + release-winget: + name: Release to WinGet + needs: build_win + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + runs-on: windows-latest # the required action can only be run on Windows + steps: + - name: Release to WinGet + uses: vedantmgoyal2009/winget-releaser@v1 + with: + identifier: LizardByte.Sunshine + release-tag: ${{ needs.check_changelog.outputs.next_version }} + installers-regex: '\.exe$' # only .exe files + token: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml deleted file mode 100644 index 0cf5b371..00000000 --- a/.github/workflows/winget.yml +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: Publish to WinGet - -on: - release: - types: [released] - -jobs: - winget-releaser: - name: winget releaser - runs-on: windows-latest - steps: - - name: winget releaser - uses: vedantmgoyal2009/winget-releaser@latest - with: - identifier: LizardByte.Sunshine - token: ${{ secrets.GH_BOT_TOKEN }} From 6e9aac3b8375763e3aca65b7b228348120809b4a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 28 Oct 2022 17:04:33 -0400 Subject: [PATCH 410/817] add nightly release --- .github/workflows/CI.yml | 138 +++++++++++++++++++++++++++------------ 1 file changed, 98 insertions(+), 40 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 8eee5358..41b8bd12 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -6,7 +6,7 @@ on: branches: [master, nightly] types: [opened, synchronize, reopened] push: - branches: [master] + branches: [master, nightly] workflow_dispatch: jobs: @@ -26,6 +26,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout + if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} uses: actions/checkout@v3 - name: Verify Changelog @@ -66,10 +67,47 @@ jobs: "project(Sunshine [VERSION ${{ needs.check_changelog.outputs.next_version_bare }}]" exit 1 + setup_release: + name: Setup Release + needs: check_changelog + runs-on: ubuntu-latest + steps: + - name: Set release details + id: release_details + run: | + # determine to create a release or not + if [[ $GITHUB_EVENT_NAME == "push" ]]; then + RELEASE=true + else + RELEASE=false + fi + + # set the release tag + if [[ $GITHUB_REF == refs/heads/master ]]; then + TAG="${{ needs.check_changelog.outputs.next_version }}" + RELEASE_BODY="${{ needs.check_changelog.outputs.release_body }}" + PRE_RELEASE="false" + elif [[ $GITHUB_REF == refs/heads/nightly ]]; then + TAG="nightly" + RELEASE_BODY="automated nightly release\nupdated: $(date -u +'%Y-%m-%dT%H:%M:%SZ')" + PRE_RELEASE="true" + fi + + echo "create_release=${RELEASE}" >> $GITHUB_OUTPUT + echo "release_tag=${TAG}" >> $GITHUB_OUTPUT + echo "release_body=${RELEASE_BODY}" >> $GITHUB_OUTPUT + echo "pre_release=${PRE_RELEASE}" >> $GITHUB_OUTPUT + + outputs: + create_release: ${{ steps.release_details.outputs.create_release }} + release_tag: ${{ steps.release_details.outputs.release_tag }} + release_body: ${{ steps.release_details.outputs.release_body }} + pre_release: ${{ steps.release_details.outputs.pre_release }} + build_linux_aur: name: Linux AUR runs-on: ubuntu-latest - needs: check_changelog + needs: setup_release steps: - name: Checkout @@ -172,7 +210,7 @@ jobs: build_linux_flatpak: name: Linux Flatpak runs-on: ubuntu-22.04 - needs: check_changelog + needs: setup_release strategy: fail-fast: false # false to test all, true to fail entire job if any fail matrix: @@ -260,19 +298,23 @@ jobs: name: sunshine-linux-flatpak-${{ matrix.arch }} path: artifacts/ - - name: Create Release - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: LizardByte/.github/actions/create_release@master + - name: Create/Update GitHub Release + if: ${{ needs.setup_release.outputs.create_release == 'true' }} + uses: ncipollo/release-action@v1 with: + name: ${{ needs.setup_release.outputs.release_tag }} + tag: ${{ needs.setup_release.outputs.release_tag }} + artifacts: "*artifacts/*" token: ${{ secrets.GH_BOT_TOKEN }} - next_version: ${{ needs.check_changelog.outputs.next_version }} - last_version: ${{ needs.check_changelog.outputs.last_version }} - release_body: ${{ needs.check_changelog.outputs.release_body }} + allowUpdates: true + body: ${{ needs.setup_release.outputs.release_body }} + discussionCategory: announcements + prerelease: ${{ needs.setup_release.outputs.pre_release }} build_linux: name: Linux runs-on: ubuntu-20.04 - needs: check_changelog + needs: [check_changelog, setup_release] strategy: fail-fast: false # false to test all, true to fail entire job if any fail matrix: @@ -385,7 +427,7 @@ jobs: mv ./cpack_artifacts/Sunshine.rpm ../artifacts/sunshine.rpm - name: Set AppImage Version - if: ${{ matrix.type == 'appimage' && ( needs.check_changelog.outputs.next_version_bare != needs.check_changelog.outputs.latest_version ) }} # yamllint disable-line rule:line-length + if: ${{ matrix.type == 'appimage' && ( needs.check_changelog.outputs.next_version_bare != needs.check_changelog.outputs.last_version ) }} # yamllint disable-line rule:line-length run: | version=${{ needs.check_changelog.outputs.next_version_bare }} echo "VERSION=${version}" >> $GITHUB_ENV @@ -442,19 +484,23 @@ jobs: name: sunshine-linux-${{ matrix.type }} path: artifacts/ - - name: Create Release - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: LizardByte/.github/actions/create_release@master + - name: Create/Update GitHub Release + if: ${{ needs.setup_release.outputs.create_release == 'true' }} + uses: ncipollo/release-action@v1 with: + name: ${{ needs.setup_release.outputs.release_tag }} + tag: ${{ needs.setup_release.outputs.release_tag }} + artifacts: "*artifacts/*" token: ${{ secrets.GH_BOT_TOKEN }} - next_version: ${{ needs.check_changelog.outputs.next_version }} - last_version: ${{ needs.check_changelog.outputs.last_version }} - release_body: ${{ needs.check_changelog.outputs.release_body }} + allowUpdates: true + body: ${{ needs.setup_release.outputs.release_body }} + discussionCategory: announcements + prerelease: ${{ needs.setup_release.outputs.pre_release }} build_mac: name: MacOS runs-on: macos-11 - needs: check_changelog + needs: setup_release steps: - name: Checkout @@ -512,18 +558,22 @@ jobs: rm -f ./sunshine-macos-experimental-archive.zip ## no artifacts to release currently - # - name: Create Release - # if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - # uses: LizardByte/.github/actions/create_release@master + # - name: Create/Update GitHub Release + # if: ${{ needs.setup_release.outputs.create_release == 'true' }} + # uses: ncipollo/release-action@v1 # with: + # name: ${{ needs.setup_release.outputs.release_tag }} + # tag: ${{ needs.setup_release.outputs.release_tag }} + # artifacts: "*artifacts/*" # token: ${{ secrets.GH_BOT_TOKEN }} - # next_version: ${{ needs.check_changelog.outputs.next_version }} - # last_version: ${{ needs.check_changelog.outputs.last_version }} - # release_body: ${{ needs.check_changelog.outputs.release_body }} + # allowUpdates: true + # body: ${{ needs.setup_release.outputs.release_body }} + # discussionCategory: announcements + # prerelease: ${{ needs.setup_release.outputs.pre_release }} build_mac_port: name: Macports - needs: check_changelog + needs: setup_release runs-on: macos-11 steps: @@ -721,19 +771,23 @@ jobs: name: sunshine-macports path: artifacts/ - - name: Create Release - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: LizardByte/.github/actions/create_release@master + - name: Create/Update GitHub Release + if: ${{ needs.setup_release.outputs.create_release == 'true' }} + uses: ncipollo/release-action@v1 with: + name: ${{ needs.setup_release.outputs.release_tag }} + tag: ${{ needs.setup_release.outputs.release_tag }} + artifacts: "*artifacts/*" token: ${{ secrets.GH_BOT_TOKEN }} - next_version: ${{ needs.check_changelog.outputs.next_version }} - last_version: ${{ needs.check_changelog.outputs.last_version }} - release_body: ${{ needs.check_changelog.outputs.release_body }} + allowUpdates: true + body: ${{ needs.setup_release.outputs.release_body }} + discussionCategory: announcements + prerelease: ${{ needs.setup_release.outputs.pre_release }} build_win: name: Windows runs-on: windows-2019 - needs: check_changelog + needs: setup_release steps: - name: Checkout @@ -792,25 +846,29 @@ jobs: name: sunshine-windows path: artifacts/ - - name: Create Release - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: LizardByte/.github/actions/create_release@master + - name: Create/Update GitHub Release + if: ${{ needs.setup_release.outputs.create_release == 'true' }} + uses: ncipollo/release-action@v1 with: + name: ${{ needs.setup_release.outputs.release_tag }} + tag: ${{ needs.setup_release.outputs.release_tag }} + artifacts: "*artifacts/*" token: ${{ secrets.GH_BOT_TOKEN }} - next_version: ${{ needs.check_changelog.outputs.next_version }} - last_version: ${{ needs.check_changelog.outputs.last_version }} - release_body: ${{ needs.check_changelog.outputs.release_body }} + allowUpdates: true + body: ${{ needs.setup_release.outputs.release_body }} + discussionCategory: announcements + prerelease: ${{ needs.setup_release.outputs.pre_release }} release-winget: name: Release to WinGet needs: build_win - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + if: ${{ needs.setup_release.outputs.create_release == 'true' && github.ref == 'refs/heads/master' }} runs-on: windows-latest # the required action can only be run on Windows steps: - name: Release to WinGet uses: vedantmgoyal2009/winget-releaser@v1 with: identifier: LizardByte.Sunshine - release-tag: ${{ needs.check_changelog.outputs.next_version }} + release-tag: ${{ needs.setup_release.outputs.release_tag }} installers-regex: '\.exe$' # only .exe files token: ${{ secrets.GH_BOT_TOKEN }} From cf5460bd8002da1b6b61cafd2f4abfe66747892a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 28 Oct 2022 20:46:46 -0400 Subject: [PATCH 411/817] update nightly releases - use github sha for nightly tag - specifically provide commit to release action - build aarch64 flatpak on nightly release --- .github/workflows/CI.yml | 48 ++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 41b8bd12..1cabb7e7 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -83,18 +83,23 @@ jobs: fi # set the release tag + COMMIT=${{ github.sha }} if [[ $GITHUB_REF == refs/heads/master ]]; then TAG="${{ needs.check_changelog.outputs.next_version }}" + RELEASE_NAME="${{ needs.check_changelog.outputs.next_version }}" RELEASE_BODY="${{ needs.check_changelog.outputs.release_body }}" PRE_RELEASE="false" elif [[ $GITHUB_REF == refs/heads/nightly ]]; then - TAG="nightly" + TAG="${COMMIT:0:7}" + RELEASE_NAME="nightly" RELEASE_BODY="automated nightly release\nupdated: $(date -u +'%Y-%m-%dT%H:%M:%SZ')" PRE_RELEASE="true" fi echo "create_release=${RELEASE}" >> $GITHUB_OUTPUT echo "release_tag=${TAG}" >> $GITHUB_OUTPUT + echo "release_commit=${COMMIT}" >> $GITHUB_OUTPUT + echo "release_name=${RELEASE_NAME}" >> $GITHUB_OUTPUT echo "release_body=${RELEASE_BODY}" >> $GITHUB_OUTPUT echo "pre_release=${PRE_RELEASE}" >> $GITHUB_OUTPUT @@ -217,7 +222,7 @@ jobs: arch: ['x86_64', 'aarch64'] exclude: # exclude `aarch64` on anything except a release triggering event - - arch: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) && '' || 'aarch64' }} + - arch: ${{ needs.setup_release.outputs.create_release == 'true' && '' || 'aarch64' }} steps: - name: Checkout @@ -302,8 +307,9 @@ jobs: if: ${{ needs.setup_release.outputs.create_release == 'true' }} uses: ncipollo/release-action@v1 with: - name: ${{ needs.setup_release.outputs.release_tag }} + name: ${{ needs.setup_release.outputs.release_name }} tag: ${{ needs.setup_release.outputs.release_tag }} + commit: ${{ needs.setup_release.outputs.release_commit }} artifacts: "*artifacts/*" token: ${{ secrets.GH_BOT_TOKEN }} allowUpdates: true @@ -488,8 +494,9 @@ jobs: if: ${{ needs.setup_release.outputs.create_release == 'true' }} uses: ncipollo/release-action@v1 with: - name: ${{ needs.setup_release.outputs.release_tag }} + name: ${{ needs.setup_release.outputs.release_name }} tag: ${{ needs.setup_release.outputs.release_tag }} + commit: ${{ needs.setup_release.outputs.release_commit }} artifacts: "*artifacts/*" token: ${{ secrets.GH_BOT_TOKEN }} allowUpdates: true @@ -557,19 +564,20 @@ jobs: rm -f ./sunshine-macos-experimental-bundle.dmg rm -f ./sunshine-macos-experimental-archive.zip - ## no artifacts to release currently - # - name: Create/Update GitHub Release - # if: ${{ needs.setup_release.outputs.create_release == 'true' }} - # uses: ncipollo/release-action@v1 - # with: - # name: ${{ needs.setup_release.outputs.release_tag }} - # tag: ${{ needs.setup_release.outputs.release_tag }} - # artifacts: "*artifacts/*" - # token: ${{ secrets.GH_BOT_TOKEN }} - # allowUpdates: true - # body: ${{ needs.setup_release.outputs.release_body }} - # discussionCategory: announcements - # prerelease: ${{ needs.setup_release.outputs.pre_release }} +# # no artifacts to release currently +# - name: Create/Update GitHub Release +# if: ${{ needs.setup_release.outputs.create_release == 'true' }} +# uses: ncipollo/release-action@v1 +# with: +# name: ${{ needs.setup_release.outputs.release_name }} +# tag: ${{ needs.setup_release.outputs.release_tag }} +# commit: ${{ needs.setup_release.outputs.release_commit }} +# artifacts: "*artifacts/*" +# token: ${{ secrets.GH_BOT_TOKEN }} +# allowUpdates: true +# body: ${{ needs.setup_release.outputs.release_body }} +# discussionCategory: announcements +# prerelease: ${{ needs.setup_release.outputs.pre_release }} build_mac_port: name: Macports @@ -775,8 +783,9 @@ jobs: if: ${{ needs.setup_release.outputs.create_release == 'true' }} uses: ncipollo/release-action@v1 with: - name: ${{ needs.setup_release.outputs.release_tag }} + name: ${{ needs.setup_release.outputs.release_name }} tag: ${{ needs.setup_release.outputs.release_tag }} + commit: ${{ needs.setup_release.outputs.release_commit }} artifacts: "*artifacts/*" token: ${{ secrets.GH_BOT_TOKEN }} allowUpdates: true @@ -850,8 +859,9 @@ jobs: if: ${{ needs.setup_release.outputs.create_release == 'true' }} uses: ncipollo/release-action@v1 with: - name: ${{ needs.setup_release.outputs.release_tag }} + name: ${{ needs.setup_release.outputs.release_name }} tag: ${{ needs.setup_release.outputs.release_tag }} + commit: ${{ needs.setup_release.outputs.release_commit }} artifacts: "*artifacts/*" token: ${{ secrets.GH_BOT_TOKEN }} allowUpdates: true From 2183f8eb0f3f002cb7195d154a879d0d0ceebfa2 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 28 Oct 2022 22:08:23 -0400 Subject: [PATCH 412/817] fix `setup_release` outputs --- .github/workflows/CI.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 1cabb7e7..80bc3c19 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -92,7 +92,7 @@ jobs: elif [[ $GITHUB_REF == refs/heads/nightly ]]; then TAG="${COMMIT:0:7}" RELEASE_NAME="nightly" - RELEASE_BODY="automated nightly release\nupdated: $(date -u +'%Y-%m-%dT%H:%M:%SZ')" + RELEASE_BODY="automated nightly release - $(date -u +'%Y-%m-%dT%H:%M:%SZ')" PRE_RELEASE="true" fi @@ -106,6 +106,8 @@ jobs: outputs: create_release: ${{ steps.release_details.outputs.create_release }} release_tag: ${{ steps.release_details.outputs.release_tag }} + release_commit: ${{ steps.release_details.outputs.release_commit }} + release_name: ${{ steps.release_details.outputs.release_name }} release_body: ${{ steps.release_details.outputs.release_body }} pre_release: ${{ steps.release_details.outputs.pre_release }} From 5f3a4666317c023bbc6df1d084ea517b68e43397 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 29 Oct 2022 16:52:03 -0400 Subject: [PATCH 413/817] update nightly release builds --- .github/workflows/CI.yml | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 80bc3c19..653641d4 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -90,9 +90,9 @@ jobs: RELEASE_BODY="${{ needs.check_changelog.outputs.release_body }}" PRE_RELEASE="false" elif [[ $GITHUB_REF == refs/heads/nightly ]]; then - TAG="${COMMIT:0:7}" + TAG="nightly-dev" RELEASE_NAME="nightly" - RELEASE_BODY="automated nightly release - $(date -u +'%Y-%m-%dT%H:%M:%SZ')" + RELEASE_BODY="automated nightly release - $(date -u +'%Y-%m-%dT%H:%M:%SZ') - ${COMMIT}" PRE_RELEASE="true" fi @@ -111,6 +111,32 @@ jobs: release_body: ${{ steps.release_details.outputs.release_body }} pre_release: ${{ steps.release_details.outputs.pre_release }} + setup_flatpak_matrix: + name: Setup Flatpak Matrix + runs-on: ubuntu-latest + steps: + - name: Set release details + id: flatpak_matrix + # https://www.cynkra.com/blog/2020-12-23-dynamic-gha + run: | + # determine which architectures to build + if [[ $GITHUB_EVENT_NAME == "push" ]]; then + matrix=$(( + echo '{ "arch" : ["x86_64", "aarch64"] }' + ) | jq -c .) + else + matrix=$(( + echo '{ "arch" : ["x86_64"] }' + ) | jq -c .) + fi + + echo $matrix + echo $matrix | jq . + echo "matrix=$matrix" >> $GITHUB_OUTPUT + + outputs: + matrix: ${{ steps.flatpak_matrix.outputs.matrix }} + build_linux_aur: name: Linux AUR runs-on: ubuntu-latest @@ -217,14 +243,10 @@ jobs: build_linux_flatpak: name: Linux Flatpak runs-on: ubuntu-22.04 - needs: setup_release + needs: [setup_release, setup_flatpak_matrix] strategy: fail-fast: false # false to test all, true to fail entire job if any fail - matrix: - arch: ['x86_64', 'aarch64'] - exclude: - # exclude `aarch64` on anything except a release triggering event - - arch: ${{ needs.setup_release.outputs.create_release == 'true' && '' || 'aarch64' }} + matrix: ${{fromJson(needs.setup_flatpak_matrix.outputs.matrix)}} steps: - name: Checkout From 3cab7e106791c9def031fa1631931d012359983d Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 29 Oct 2022 22:14:16 -0400 Subject: [PATCH 414/817] change docs theme to `furo` --- .readthedocs.yaml | 4 +- README.rst | 22 +- docs/requirements.txt | 4 + docs/source/about/advanced_usage.rst | 309 ++++++++++----------- docs/source/about/installation.rst | 36 +-- docs/source/about/third_party_packages.rst | 14 +- docs/source/about/usage.rst | 76 ++--- docs/source/building/build.rst | 3 - docs/source/building/linux.rst | 2 - docs/source/building/macos.rst | 11 +- docs/source/building/windows.rst | 18 +- docs/source/conf.py | 18 +- docs/source/contributing/contributing.rst | 38 +-- docs/source/contributing/localization.rst | 39 ++- docs/source/contributing/testing.rst | 10 +- docs/source/troubleshooting/general.rst | 17 +- docs/source/troubleshooting/linux.rst | 3 - docs/source/troubleshooting/macos.rst | 23 +- docs/source/troubleshooting/windows.rst | 3 - scripts/requirements.txt | 4 - 20 files changed, 277 insertions(+), 377 deletions(-) create mode 100644 docs/requirements.txt diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 156e5624..586666c2 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,7 +10,7 @@ version: 2 build: os: ubuntu-20.04 tools: - python: "3.9" + python: "3.10" ## apt packages required packages to run cmake on sunshine, note that additional packages are required # apt_packages: @@ -41,5 +41,5 @@ formats: all python: install: - - requirements: ./scripts/requirements.txt + - requirements: ./docs/requirements.txt system_packages: true diff --git a/README.rst b/README.rst index 4f7a5102..f7853dee 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,3 @@ -:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/README.rst - Overview ======== LizardByte has the full documentation hosted on `Read the Docs `_. @@ -13,18 +11,18 @@ Connect to Sunshine from any Moonlight client, available for nearly any device i These are the advantages of Sunshine over GeForce Experience. - - FOSS (Free and Open Source Software) - - Multi-platform +- FOSS (Free and Open Source Software) +- Multi-platform - - Linux - - macOS - - Windows + - Linux + - macOS + - Windows - - Pair over web ui - - Supports AMD, Intel, and Nvidia GPUs for encoding - - Supports software encoding - - Supports streaming to multiple clients - - Web UI for configuration +- Pair over web ui +- Supports AMD, Intel, and Nvidia GPUs for encoding +- Supports software encoding +- Supports streaming to multiple clients +- Web UI for configuration Integrations ------------ diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..fc950e03 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,4 @@ +furo==2022.9.29 +m2r2==0.3.3 +Sphinx==5.3.0 +sphinx-copybutton==0.5.0 diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index b8613850..2ab43a85 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -1,5 +1,3 @@ -:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/about/advanced_usage.rst - Advanced Usage ============== Sunshine will work with the default settings for most users. In some cases you may want to configure Sunshine further. @@ -26,7 +24,7 @@ location by modifying the configuration file. Windows ./config/ ========= =========== -Example +**Example** .. code-block:: bash sunshine ~/sunshine_config.conf @@ -41,13 +39,13 @@ General sunshine_name ^^^^^^^^^^^^^ -Description +**Description** The name displayed by Moonlight -Default +**Default** PC hostname -Example +**Example** .. code-block:: text sunshine_name = Sunshine @@ -55,7 +53,7 @@ Example min_log_level ^^^^^^^^^^^^^ -Description +**Description** The minimum log level printed to standard out. **Choices** @@ -75,10 +73,10 @@ Description none no logging ======= =========== -Default +**Default** ``info`` -Example +**Example** .. code-block:: text min_log_level = info @@ -89,7 +87,7 @@ Controls gamepad ^^^^^^^ -Description +**Description** The type of gamepad to emulate on the host. .. Caution:: Applies to Windows only. @@ -106,10 +104,10 @@ Description ds4 dualshock controller (PS4) ===== =========== -Default +**Default** ``x360`` -Example +**Example** .. code-block:: text gamepad = x360 @@ -117,17 +115,17 @@ Example back_button_timeout ^^^^^^^^^^^^^^^^^^^ -Description +**Description** If, after the timeout, the back/select button is still pressed down, Home/Guide button press is emulated. On Nvidia Shield, the home and power button are not passed to Moonlight. .. Tip:: If back_button_timeout < 0, then the Home/Guide button will not be emulated. -Default +**Default** ``2000`` -Example +**Example** .. code-block:: text back_button_timeout = 2000 @@ -135,13 +133,13 @@ Example key_repeat_delay ^^^^^^^^^^^^^^^^ -Description +**Description** The initial delay in milliseconds before repeating keys. Controls how fast keys will repeat themselves. -Default +**Default** ``500`` -Example +**Example** .. code-block:: text key_repeat_delay = 500 @@ -149,15 +147,15 @@ Example key_repeat_frequency ^^^^^^^^^^^^^^^^^^^^ -Description +**Description** How often keys repeat every second. .. Tip:: This configurable option supports decimals. -Default +**Default** .. Todo:: Unknown -Example +**Example** .. code-block:: text key_repeat_frequency = 24.9 @@ -165,17 +163,17 @@ Example keybindings ^^^^^^^^^^^ -Description +**Description** Sometimes it may be useful to map keybindings. Wayland won't allow clients to capture the Win Key for example. .. Tip:: See `virtual key codes `_ .. Hint:: keybindings needs to have a multiple of two elements. -Default +**Default** None -Example +**Example** .. code-block:: text keybindings = [ @@ -188,14 +186,14 @@ Example key_rightalt_to_key_win ^^^^^^^^^^^^^^^^^^^^^^^ -Description +**Description** It may be possible that you cannot send the Windows Key from Moonlight directly. In those cases it may be useful to make Sunshine think the Right Alt key is the Windows key. -Default +**Default** None -Example +**Example** .. code-block:: text key_rightalt_to_key_win = enabled @@ -206,12 +204,12 @@ Display adapter_name ^^^^^^^^^^^^ -Description +**Description** Select the video card you want to stream. .. Tip:: To find the name of the appropriate values follow these instructions. - Linux + VA-API + **Linux + VA-API** Unlike with `amdvce` and `nvenc`, it doesn't matter if video encoding is done on a different GPU. .. code-block:: bash @@ -227,23 +225,23 @@ Description .. Todo:: macOS - Windows + **Windows** .. code-block:: batch tools\dxgi-info.exe -Default +**Default** Sunshine will select the default video card. -Examples - Linux +**Examples** + **Linux** .. code-block:: text adapter_name = /dev/dri/renderD128 .. Todo:: macOS - Windows + **Windows** .. code-block:: text adapter_name = Radeon RX 580 Series @@ -251,12 +249,12 @@ Examples output_name ^^^^^^^^^^^ -Description +**Description** Select the display number you want to stream. .. Tip:: To find the name of the appropriate values follow these instructions. - Linux + **Linux** .. code-block:: bash xrandr --listmonitors @@ -267,23 +265,23 @@ Description .. Todo:: macOS - Windows + **Windows** .. code-block:: batch tools\dxgi-info.exe -Default +**Default** Sunshine will select the default display. -Examples - Linux +**Examples** + **Linux** .. code-block:: text output_name = 0 .. Todo:: macOS - Windows + **Windows** .. code-block:: text output_name = \\.\DISPLAY1 @@ -291,16 +289,16 @@ Examples fps ^^^ -Description +**Description** The fps modes advertised by Sunshine. .. Note:: Some versions of Moonlight, such as Moonlight-nx (Switch), rely on this list to ensure that the requested fps is supported. -Default +**Default** .. Todo:: Unknown -Example +**Example** .. code-block:: text fps = [10, 30, 60, 90, 120] @@ -308,16 +306,16 @@ Example resolutions ^^^^^^^^^^^ -Description +**Description** The resolutions advertised by Sunshine. .. Note:: Some versions of Moonlight, such as Moonlight-nx (Switch), rely on this list to ensure that the requested resolution is supported. -Default +**Default** .. Todo:: Unknown -Example +**Example** .. code-block:: text resolutions = [ @@ -336,22 +334,20 @@ Example dwmflush ^^^^^^^^ -Description +**Description** Invoke DwmFlush() to sync screen capture to the Windows presentation interval. .. Caution:: Applies to Windows only. Alleviates visual stuttering during mouse movement. If enabled, this feature will automatically deactivate if the client framerate exceeds the host monitor's current refresh rate. -Default +**Default** ``enabled`` -Examples - - Windows - .. code-block:: text +**Example** + .. code-block:: text - dwmflush = enabled + dwmflush = enabled Audio ----- @@ -359,48 +355,48 @@ Audio audio_sink ^^^^^^^^^^ -Description +**Description** The name of the audio sink used for audio loopback. .. Tip:: To find the name of the audio sink follow these instructions. - Linux + pulseaudio + **Linux + pulseaudio** .. code-block:: bash pacmd list-sinks | grep "name:" - Linux + pipewire + **Linux + pipewire** .. code-block:: bash pactl info | grep Source # in some causes you'd need to use the `Sink` device, if `Source` doesn't work, so try: pactl info | grep Sink - macOS + **macOS** Sunshine can only access microphones on macOS due to system limitations. To stream system audio use `Soundflower `_ or `BlackHole `_. - Windows + **Windows** .. code-block:: batch tools\audio-info.exe -Default +**Default** Sunshine will select the default audio device. -Examples - Linux +**Examples** + **Linux** .. code-block:: text audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo - macOS + **macOS** .. code-block:: text audio_sink = BlackHole 2ch - Windows + **Windows** .. code-block:: text audio_sink = {0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B} @@ -408,16 +404,16 @@ Examples virtual_sink ^^^^^^^^^^^^ -Description +**Description** The audio device that's virtual, like Steam Streaming Speakers. This allows Sunshine to stream audio, while muting the speakers. .. Tip:: See `audio_sink`_! -Default +**Default** .. Todo:: Unknown -Example +**Example** .. code-block:: text virtual_sink = {0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4} @@ -428,13 +424,13 @@ Network external_ip ^^^^^^^^^^^ -Description +**Description** If no external IP address is given, Sunshine will attempt to automatically detect external ip-address. -Default +**Default** Automatic -Example +**Example** .. code-block:: text external_ip = 123.456.789.12 @@ -442,7 +438,7 @@ Example port ^^^^ -Description +**Description** Set the family of ports used by Sunshine. Changing this value will offset other ports per the table below. .. table:: @@ -458,18 +454,15 @@ Description Video 47998 UDP +9 Control 47999 UDP +10 Audio 48000 UDP +11 - tbd 48002 UDP +13 + Mic (unused) 48002 UDP +13 ================ ============ =========================== .. Attention:: Custom ports are only allowed on select Moonlight clients. -.. Todo:: Determine the function of port 48002 UDP. See - `here `_. - -Default +**Default** ``47989`` -Example +**Example** .. code-block:: text port = 47989 @@ -477,13 +470,13 @@ Example pkey ^^^^ -Description +**Description** The private key. This must be 2048 bits. -Default +**Default** .. Todo:: Unknown -Example +**Example** .. code-block:: text pkey = /dir/pkey.pem @@ -491,13 +484,13 @@ Example cert ^^^^ -Description +**Description** The certificate. Must be signed with a 2048 bit key. -Default +**Default** .. Todo:: Unknown -Example +**Example** .. code-block:: text cert = /dir/cert.pem @@ -505,7 +498,7 @@ Example origin_pin_allowed ^^^^^^^^^^^^^^^^^^ -Description +**Description** The origin of the remote endpoint address that is not denied for HTTP method /pin. **Choices** @@ -521,10 +514,10 @@ Description wan Anyone may access /pin ===== =========== -Default +**Default** ``pc`` -Example +**Example** .. code-block:: text origin_pin_allowed = pc @@ -532,7 +525,7 @@ Example origin_web_ui_allowed ^^^^^^^^^^^^^^^^^^^^^ -Description +**Description** The origin of the remote endpoint address that is not denied for HTTPS Web UI. **Choices** @@ -548,10 +541,10 @@ Description wan Anyone may access the web ui ===== =========== -Default +**Default** ``lan`` -Example +**Example** .. code-block:: text origin_web_ui_allowed = lan @@ -559,7 +552,7 @@ Example upnp ^^^^ -Description +**Description** Sunshine will attempt to open ports for streaming over the internet. **Choices** @@ -574,10 +567,10 @@ Description off disable UPnP ===== =========== -Default +**Default** ``off`` -Example +**Example** .. code-block:: text upnp = on @@ -585,13 +578,13 @@ Example ping_timeout ^^^^^^^^^^^^ -Description +**Description** How long to wait in milliseconds for data from Moonlight before shutting down the stream. -Default +**Default** ``10000`` -Example +**Example** .. code-block:: text ping_timeout = 10000 @@ -602,22 +595,22 @@ Encoding channels ^^^^^^^^ -Description +**Description** This will generate distinct video streams, unlike simply broadcasting to multiple Clients. When multicasting, it could be useful to have different configurations for each connected Client. For instance: - - Clients connected through WAN and LAN have different bitrate constraints. - - Decoders may require different settings for color. + - Clients connected through WAN and LAN have different bitrate constraints. + - Decoders may require different settings for color. .. Warning:: CPU usage increases for each distinct video stream generated. -Default +**Default** ``1`` -Example +**Example** .. code-block:: text channels = 1 @@ -625,18 +618,18 @@ Example fec_percentage ^^^^^^^^^^^^^^ -Description +**Description** Percentage of error correcting packets per data packet in each video frame. .. Warning:: Higher values can correct for more network packet loss, but at the cost of increasing bandwidth usage. -Default +**Default** ``20`` -Range +**Range** ``1-255`` -Example +**Example** .. code-block:: text fec_percentage = 20 @@ -644,15 +637,15 @@ Example qp ^^ -Description +**Description** Quantitization Parameter. Some devices don't support Constant Bit Rate. For those devices, QP is used instead. .. Warning:: Higher value means more compression, but less quality. -Default +**Default** ``28`` -Example +**Example** .. code-block:: text qp = 28 @@ -660,17 +653,17 @@ Example min_threads ^^^^^^^^^^^ -Description +**Description** Minimum number of threads used by ffmpeg to encode the video. .. Note:: Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest value that can reliably encode at your desired streaming settings on your hardware. -Default +**Default** ``1`` -Example +**Example** .. code-block:: text min_threads = 1 @@ -678,7 +671,7 @@ Example hevc_mode ^^^^^^^^^ -Description +**Description** Allows the client to request HEVC Main or HEVC Main10 video streams. .. Warning:: HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software @@ -698,10 +691,10 @@ Description 3 advertise support for HEVC Main and Main10 (HDR) profiles ===== =========== -Default +**Default** ``0`` -Example +**Example** .. code-block:: text hevc_mode = 2 @@ -709,7 +702,7 @@ Example encoder ^^^^^^^ -Description +**Description** Force a specific encoder. **Choices** @@ -725,10 +718,10 @@ Description software Encoding occurs on the CPU ======== =========== -Default +**Default** Sunshine will use the first encoder that is available. -Example +**Example** .. code-block:: text encoder = nvenc @@ -736,7 +729,7 @@ Example sw_preset ^^^^^^^^^ -Description +**Description** The encoder preset to use. .. Note:: This option only applies when using software `encoder`_. @@ -771,10 +764,10 @@ Description veryslow slowest ========= =========== -Default +**Default** ``superfast`` -Example +**Example** .. code-block:: text sw_preset = superfast @@ -782,7 +775,7 @@ Example sw_tune ^^^^^^^ -Description +**Description** The tuning preset to use. .. Note:: This option only applies when using software `encoder`_. @@ -807,10 +800,10 @@ Description zerolatency good for fast encoding and low-latency streaming =========== =========== -Default +**Default** ``zerolatency`` -Example +**Example** .. code-block:: text sw_tune = zerolatency @@ -818,7 +811,7 @@ Example nv_preset ^^^^^^^^^ -Description +**Description** The encoder preset to use. .. Note:: This option only applies when using nvenc `encoder`_. @@ -845,10 +838,10 @@ Description losslesshp lossless, high performance ========== =========== -Default +**Default** ``llhq`` -Example +**Example** .. code-block:: text nv_preset = llhq @@ -856,7 +849,7 @@ Example nv_rc ^^^^^ -Description +**Description** The encoder rate control. .. Note:: This option only applies when using nvenc `encoder`_. @@ -880,10 +873,10 @@ Description vbr_hq variable bitrate, high quality ========== =========== -Default +**Default** ``auto`` -Example +**Example** .. code-block:: text nv_rc = auto @@ -891,7 +884,7 @@ Example nv_coder ^^^^^^^^ -Description +**Description** The entropy encoding to use. .. Note:: This option only applies when using nvenc `encoder`_. @@ -909,10 +902,10 @@ Description cavlc ========== =========== -Default +**Default** ``auto`` -Example +**Example** .. code-block:: text nv_coder = auto @@ -920,7 +913,7 @@ Example amd_quality ^^^^^^^^^^^ -Description +**Description** The encoder preset to use. .. Note:: This option only applies when using amdvce `encoder`_. @@ -938,10 +931,10 @@ Description balanced balance performance and speed ========== =========== -Default +**Default** ``balanced`` -Example +**Example** .. code-block:: text amd_quality = balanced @@ -949,7 +942,7 @@ Example amd_rc ^^^^^^ -Description +**Description** The encoder rate control. .. Note:: This option only applies when using amdvce `encoder`_. @@ -971,10 +964,10 @@ Description vbr_peak variable bitrate, peak constrained =========== =========== -Default +**Default** ``auto`` -Example +**Example** .. code-block:: text amd_rc = auto @@ -982,7 +975,7 @@ Example amd_coder ^^^^^^^^^ -Description +**Description** The entropy encoding to use. .. Note:: This option only applies when using nvenc `encoder`_. @@ -1000,10 +993,10 @@ Description cavlc ========== =========== -Default +**Default** ``auto`` -Example +**Example** .. code-block:: text amd_coder = auto @@ -1011,7 +1004,7 @@ Example vt_software ^^^^^^^^^^^ -Description +**Description** Force Video Toolbox to use software encoding. .. Note:: This option only applies when using macOS. @@ -1030,10 +1023,10 @@ Description forced force software encoding ========== =========== -Default +**Default** ``auto`` -Example +**Example** .. code-block:: text vt_software = auto @@ -1041,17 +1034,17 @@ Example vt_realtime ^^^^^^^^^^^ -Description +**Description** Realtime encoding. .. Note:: This option only applies when using macOS. .. Warning:: Disabling realtime encoding might result in a delayed frame encoding or frame drop. -Default +**Default** ``enabled`` -Example +**Example** .. code-block:: text vt_realtime = enabled @@ -1059,7 +1052,7 @@ Example vt_coder ^^^^^^^^ -Description +**Description** The entropy encoding to use. .. Note:: This option only applies when using macOS. @@ -1077,10 +1070,10 @@ Description cavlc ========== =========== -Default +**Default** ``auto`` -Example +**Example** .. code-block:: text vt_coder = auto @@ -1091,14 +1084,14 @@ Advanced file_apps ^^^^^^^^^ -Description +**Description** The application configuration file path. The file contains a json formatted list of applications that can be started by Moonlight. -Default +**Default** OS and package dependent -Example +**Example** .. code-block:: text file_apps = apps.json @@ -1106,13 +1099,13 @@ Example file_state ^^^^^^^^^^ -Description +**Description** The file where current state of Sunshine is stored. -Default +**Default** ``sunshine_state.json`` -Example +**Example** .. code-block:: text file_state = sunshine_state.json @@ -1120,13 +1113,13 @@ Example credentials_file ^^^^^^^^^^^^^^^^ -Description +**Description** The file where user credentials for the UI are stored. -Default +**Default** ``sunshine_state.json`` -Example +**Example** .. code-block:: text credentials_file = sunshine_state.json diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 5c9f2194..9f65ba59 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -1,5 +1,3 @@ -:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/about/installation.rst - Installation ============ The recommended method for running Sunshine is to use the `binaries`_ bundled with the `latest release`_. @@ -36,19 +34,19 @@ AppImage .. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/pkg:appimage?logo=github&style=for-the-badge :alt: GitHub issues by-label -According to AppImageLint the AppImage can run on the following distros. +According to AppImageLint the supported distro matrix of the AppImage is below. - - [✖] Debian oldstable (buster) - - [✔] Debian stable (bullseye) - - [✔] Debian testing (bookworm) - - [✔] Debian unstable (sid) - - [✔] Ubuntu jammy - - [✔] Ubuntu impish - - [✔] Ubuntu focal - - [✖] Ubuntu bionic - - [✖] Ubuntu xenial - - [✖] Ubuntu trusty - - [✖] CentOS 7 +- [✖] Debian oldstable (buster) +- [✔] Debian stable (bullseye) +- [✔] Debian testing (bookworm) +- [✔] Debian unstable (sid) +- [✔] Ubuntu jammy +- [✔] Ubuntu impish +- [✔] Ubuntu focal +- [✖] Ubuntu bionic +- [✖] Ubuntu xenial +- [✖] Ubuntu trusty +- [✖] CentOS 7 #. Download ``sunshine-appimage.zip`` and extract the contents to your home directory. #. Open terminal and run the following code. @@ -58,13 +56,11 @@ According to AppImageLint the AppImage can run on the following distros. ./sunshine.AppImage --install Start: - .. code-block:: bash ./sunshine.AppImage --install && ./sunshine.AppImage Uninstall: - .. code-block:: bash ./sunshine.AppImage --remove @@ -80,7 +76,6 @@ AUR Package makepkg -fi Uninstall: - .. code-block:: bash pacman -R sunshine @@ -99,7 +94,6 @@ Debian Package .. Tip:: You can double click the deb file to see details about the package and begin installation. Uninstall: - .. code-block:: bash sudo apt remove sunshine @@ -123,7 +117,6 @@ Flatpak Package flatpak install --user sunshine.flatpak Start: - X11 and NVFBC capture (X11 Only) .. code-block:: bash @@ -135,7 +128,6 @@ Start: sudo -i PULSE_SERVER=unix:$(pactl info | awk '/Server String/{print$3}') flatpak run dev.lizardbyte.sunshine Uninstall: - .. code-block:: bash flatpak uninstall --delete-data sunshine.flatpak @@ -161,7 +153,6 @@ RPM Package .. Tip:: You can double click the rpm file to see details about the package and begin installation. Uninstall: - .. code-block:: bash sudo dnf remove sunshine @@ -178,7 +169,6 @@ pkg #. Download the ``sunshine.pkg`` file and install it as normal. Uninstall: - .. code-block:: bash cd /etc/sunshine/assets @@ -194,7 +184,6 @@ Portfile sudo nano /opt/local/etc/macports/sources.conf Add this line, replacing your username, below the line that starts with ``rsync``. - ``file:///Users//ports`` ``Ctrl+x``, then ``Y`` to exit and save changes. @@ -212,7 +201,6 @@ Portfile #. The first time you start Sunshine, you will be asked to grant access to screen recording and your microphone. Uninstall: - .. code-block:: bash sudo port uninstall sunshine diff --git a/docs/source/about/third_party_packages.rst b/docs/source/about/third_party_packages.rst index 5ae51e8b..ad8979dc 100644 --- a/docs/source/about/third_party_packages.rst +++ b/docs/source/about/third_party_packages.rst @@ -1,5 +1,3 @@ -:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/about/third_party_packages.rst - Third Party Packages ==================== @@ -15,6 +13,12 @@ Chocolatey .. image:: https://img.shields.io/chocolatey/dt/sunshine?style=for-the-badge&logo=chocolatey :alt: Chocolatey +nixpkgs +------- +.. image:: https://img.shields.io/badge/dynamic/xml?color=orange&label=nixpkgs&style=for-the-badge&prefix=v&query=%2F%2Ftr%5B%40id%3D%27nix_unstable%27%5D%2Ftd%5B3%5D%2Fspan%2Fa&url=https%3A%2F%2Frepology.org%2Fproject%2Fsunshine%2Fversions&logo=nixos + :alt: nixpgs Version + :target: https://github.com/NixOS/nixpkgs/blob/master/pkgs/servers/sunshine/default.nix + Scoop ----- @@ -22,6 +26,12 @@ Scoop :alt: Scoop Version (extras bucket) :target: https://scoop.sh/#/apps?s=0&d=1&o=true&q=sunshine +Solus +----- +.. image:: https://img.shields.io/badge/dynamic/xml?color=orange&label=Solus&style=for-the-badge&prefix=v&query=%2F%2Ftr%5B%40id%3D%27solus%27%5D%2Ftd%5B3%5D%2Fspan%2Fa&url=https%3A%2F%2Frepology.org%2Fproject%2Fsunshine%2Fversions&logo=solus + :alt: Solus Version + :target: https://dev.getsol.us/source/sunshine + Winget ------ .. image:: https://img.shields.io/badge/dynamic/xml?color=orange&label=Winget&style=for-the-badge&prefix=v&query=%2F%2Ftr%5B%40id%3D%27winget%27%5D%2Ftd%5B3%5D%2Fspan%2Fa&url=https%3A%2F%2Frepology.org%2Fproject%2Fsunshine%2Fversions&logo=microsoft diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst index 0e3c87a4..81cfcdcd 100644 --- a/docs/source/about/usage.rst +++ b/docs/source/about/usage.rst @@ -1,5 +1,3 @@ -:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/about/usage.rst - Usage ===== #. See the `setup`_ section for your specific OS. @@ -12,6 +10,7 @@ Usage .. Tip:: If using the Linux AppImage, replace ``sunshine`` with ``./sunshine.AppImage`` #. Configure Sunshine in the web ui + The web ui is available on `https://localhost:47990 `_ by default. You may replace `localhost` with your internal ip address. @@ -20,14 +19,14 @@ Usage .. Caution:: If running for the first time, make sure to note the username and password Sunshine showed to you, since you cannot get back later! - Add games and applications. - This can be configured in the web ui. + **Add games and applications.** + This can be configured in the web ui. - .. Note:: Additionally, apps can be configured manually. `src_assets//config/apps.json` is an example of a - list of applications that are started just before running a stream. This is the directory within the GitHub - repo. + .. Note:: Additionally, apps can be configured manually. `src_assets//config/apps.json` is an example of a + list of applications that are started just before running a stream. This is the directory within the GitHub + repo. - .. Attention:: Application list is not fully supported on macOS + .. Attention:: Application list is not fully supported on macOS #. In Moonlight, you may need to add the PC manually. #. When Moonlight request you insert the correct pin on sunshine: @@ -46,7 +45,6 @@ The Sunshine user interface will be available on port 47990 by default. Arguments --------- To get a list of available arguments run the following: - .. code-block:: bash sunshine --help @@ -56,7 +54,7 @@ Setup Linux ^^^^^ -The deb, rpm, and AppImage packages handle these steps automatically. The flatpak does not, third party packages +The `deb`, `rpm`, and `AppImage` packages handle these steps automatically. The flatpak does not, third party packages also may not. Sunshine needs access to `uinput` to create mouse and gamepad events. @@ -73,9 +71,9 @@ Sunshine needs access to `uinput` to create mouse and gamepad events. sudo tee /etc/udev/rules.d/85-sunshine-input.rules #. Optionally, configure autostart service - - filename: ``~/.config/systemd/user/sunshine.service`` - - contents: + - filename: ``~/.config/systemd/user/sunshine.service`` + - contents: .. code-block:: [Unit] @@ -100,12 +98,12 @@ Sunshine needs access to `uinput` to create mouse and gamepad events. Flatpak flatpak run dev.lizardbyte.sunshine ✖ ======== ============================================== =============== - Start once + **Start once** .. code-block:: bash systemctl --user start sunshine - Start on boot + **Start on boot** .. code-block:: bash systemctl --user enable sunshine @@ -114,12 +112,12 @@ Sunshine needs access to `uinput` to create mouse and gamepad events. .. Note:: ``cap_sys_admin`` may as well be root, except you don't need to be root to run it. It is necessary to allow Sunshine to use KMS. - Enable + **Enable** .. code-block:: bash sudo setcap cap_sys_admin+p $(readlink -f $(which sunshine)) - Disable + **Disable** .. code-block:: bash sudo setcap -r $(readlink -f $(which sunshine)) @@ -141,8 +139,7 @@ select their sink as audio device in `sunshine.conf`. .. Caution:: Gamepads are not currently supported. Configure autostart service - - MacPorts + **MacPorts** .. code-block:: bash sudo port load Sunshine @@ -153,10 +150,10 @@ For gamepad support, install `ViGEmBus `_. Cross compilation is not @@ -11,7 +9,6 @@ Building Locally Clone ^^^^^ Ensure `git `_ is installed and run the following: - .. code-block:: bash git clone https://github.com/lizardbyte/sunshine.git --recurse-submodules diff --git a/docs/source/building/linux.rst b/docs/source/building/linux.rst index b2b4bfc7..009ef882 100644 --- a/docs/source/building/linux.rst +++ b/docs/source/building/linux.rst @@ -1,5 +1,3 @@ -:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/building/linux.rst - Linux ===== diff --git a/docs/source/building/macos.rst b/docs/source/building/macos.rst index b719c6ee..074eaf4e 100644 --- a/docs/source/building/macos.rst +++ b/docs/source/building/macos.rst @@ -1,5 +1,3 @@ -:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/building/macos.rst - macOS ===== @@ -30,14 +28,13 @@ Build ----- .. Attention:: Ensure you are in the build directory created during the clone step earlier before continuing. - .. code-block:: bash +.. code-block:: bash - cmake .. - make -j ${nproc} + cmake .. + make -j ${nproc} - cpack -G DragNDrop # optionally, create a macOS dmg package + cpack -G DragNDrop # optionally, create a macOS dmg package If cmake fails complaining to find Boost, try to set the path explicitly. - ``cmake -DBOOST_ROOT=[boost path] ..``, e.g., ``cmake -DBOOST_ROOT=/opt/local/libexec/boost/1.76 ..`` diff --git a/docs/source/building/windows.rst b/docs/source/building/windows.rst index 0c6dde57..43f84a22 100644 --- a/docs/source/building/windows.rst +++ b/docs/source/building/windows.rst @@ -1,5 +1,3 @@ -:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/building/windows.rst - Windows ======= @@ -10,18 +8,20 @@ following packages using: .. code-block:: bash - pacman -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git mingw-w64-x86_64-make cmake make gcc + pacman -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake \ + mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost \ + git mingw-w64-x86_64-make cmake make gcc Build ----- .. Attention:: Ensure you are in the build directory created during the clone step earlier before continuing. - .. code-block:: bash +.. code-block:: bash - cmake -G"Unix Makefiles" .. - cmake -G"MinGW Makefiles" .. # alternatively + cmake -G"Unix Makefiles" .. + cmake -G"MinGW Makefiles" .. # alternatively - mingw32-make + mingw32-make - cpack -G NSIS # optionally, create a windows installer - cpack -G ZIP # optionally, create a windows standalone package + cpack -G NSIS # optionally, create a windows installer + cpack -G ZIP # optionally, create a windows standalone package diff --git a/docs/source/conf.py b/docs/source/conf.py index 5ef4356c..65afdc61 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -73,23 +73,11 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'sphinx_rtd_theme' +html_theme = 'furo' html_theme_options = { - # 'analytics_id': 'G-XXXXXXXXXX', # Provided by Google in your dashboard - # 'analytics_anonymize_ip': False, - 'logo_only': False, - 'display_version': False, - 'prev_next_buttons_location': 'bottom', - 'style_external_links': True, - 'vcs_pageview_mode': 'blob', - 'style_nav_header_background': '#151515', - # Toc options - 'collapse_navigation': True, - 'sticky_navigation': True, - 'navigation_depth': 4, - 'includehidden': True, - 'titles_only': False, + "top_of_page_button": "edit", + "source_edit_link": "https://github.com/lizardbyte/sunshine/tree/nightly/docs/source/{filename}", } # extension config options diff --git a/docs/source/contributing/contributing.rst b/docs/source/contributing/contributing.rst index e79fd4ea..217bda13 100644 --- a/docs/source/contributing/contributing.rst +++ b/docs/source/contributing/contributing.rst @@ -1,39 +1,5 @@ -:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/contributing/contributing.rst - Contributing ============ -.. Tip:: If this is your first time contributing to an open source project, it is a good idea to read - MDN's `Basic etiquette for open source projects`_ first. There are a few best practices to adopt that will help - ensure that you and the other project contributors feel valued and safe, and stay productive. - -#. Fork the repo on GitHub -#. Create a new branch for the feature you are adding or the issue you are fixing - - .. Tip:: Base the new branch off the `nightly` branch. It will make your life easier when you submit the PR! - -#. Make changes, push commits, etc. -#. Files should contain an empty line at the end. -#. Document your code! -#. Test your code! -#. When ready create a PR to this repo on the `nightly` branch. - - .. Hint:: If you accidentally make your PR against a different branch, a bot will comment letting you know it's on - the wrong branch. Don't worry. You can edit the PR to change the target branch. There is no reason to close the - PR! - - .. Note:: Draft PRs are also welcome as you work through issues. The benefit of creating a draft PR is that an - automated build can run in a github runner. - - .. Attention:: Do not expect partially complete PRs to be merged. These topics will be considered before merging. - - - Does the code follows the style guidelines of this project? - - .. Tip:: Look at examples of existing code in the project! - - - Is the code well commented? - - Were documentation blocks updated for new or modified components? - - .. Note:: Developers and maintainers will attempt to assist with challenging issues. - -.. _Basic etiquette for open source projects: https://developer.mozilla.org/en-US/docs/MDN/Contribute/Open_source_etiquette +Read our contribution guide in our organization level +`docs `_. diff --git a/docs/source/contributing/localization.rst b/docs/source/contributing/localization.rst index 8b04867f..dc3e26da 100644 --- a/docs/source/contributing/localization.rst +++ b/docs/source/contributing/localization.rst @@ -1,5 +1,3 @@ -:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/contributing/localization.rst - Localization ============ Sunshine is being localized into various languages. The default language is `en` (English) and is highlighted green. @@ -24,15 +22,15 @@ Only elements of the API are planned to be translated. .. Attention:: The rest API has not yet been implemented. -Translations Basics +**Translations Basics** - The brand names `LizardByte` and `Sunshine` should never be translated. - Other brand names should never be translated. Examples: - - AMD - - Nvidia + - AMD + - Nvidia -CrowdIn Integration +**CrowdIn Integration** How does it work? When a change is made to sunshine source code, a workflow generates new translation templates @@ -47,15 +45,14 @@ Extraction There should be minimal cases where strings need to be extracted from source code; however it may be necessary in some situations. For example if a system tray icon is added it should be localized as it is user interfacing. - - Wrap the string to be extracted in a function as shown. - - .. code-block:: cpp +- Wrap the string to be extracted in a function as shown. + .. code-block:: cpp - #include - boost::locale::translate("Hello world!") + #include + boost::locale::translate("Hello world!") - .. Tip:: More examples can be found in the documentation for - `boost locale `_. +.. Tip:: More examples can be found in the documentation for + `boost locale `_. .. Warning:: This is for information only. Contributors should never include manually updated template files, or manually compiled language files in Pull Requests. @@ -65,20 +62,20 @@ used by CrowdIn to generate language specific template files. The file is genera `.github/workflows/localize.yml` workflow and is run on any push event into the `nightly` branch. Jobs are only run if any of the following paths are modified. - .. code-block:: yaml +.. code-block:: yaml - - 'src/**' + - 'src/**' When testing locally it may be desirable to manually extract, initialize, update, and compile strings. Python is required for this, along with the python dependencies in the `./scripts/requirements.txt` file. Additionally, `xgettext `_ must be installed. - Extract, initialize, and update - .. code-block:: bash +**Extract, initialize, and update** + .. code-block:: bash - python ./scripts/_locale.py --extract --init --update + python ./scripts/_locale.py --extract --init --update - Compile - .. code-block:: bash +**Compile** + .. code-block:: bash - python ./scripts/_locale.py --compile + python ./scripts/_locale.py --compile diff --git a/docs/source/contributing/testing.rst b/docs/source/contributing/testing.rst index a08c6f9c..512aed1c 100644 --- a/docs/source/contributing/testing.rst +++ b/docs/source/contributing/testing.rst @@ -1,12 +1,10 @@ -:github_url: https://github.com/RetroArcher/RetroArcher/tree/nightly/docs/source/contributing/testing.rst - Testing ======= Clang Format ------------ Source code is tested against the `.clang-format` file for linting errors. The workflow file responsible for clang -format testing is `.github/workflows/clang.yml`. +format testing is `.github/workflows/cpp-clang-format-lint.yml`. Test clang-format locally. .. Todo:: This documentation needs to be improved. @@ -17,9 +15,9 @@ Test clang-format locally. Sphinx ------ -Sunshine uses `Sphinx `_ for documentation building. Sphinx is included -in the `./scripts/requirements.txt` file. Python is required to build sphinx docs. Installation and setup of python -will not be covered here. +Sunshine uses `Sphinx `_ for documentation building. Sphinx, along with other +required documentation depencies 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. The config file for Sphinx is `docs/source/conf.py`. This is already included in the repo and should not be modified. diff --git a/docs/source/troubleshooting/general.rst b/docs/source/troubleshooting/general.rst index 6c480720..010a52c8 100644 --- a/docs/source/troubleshooting/general.rst +++ b/docs/source/troubleshooting/general.rst @@ -1,22 +1,17 @@ -:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/troubleshooting/general.rst - General ======= If you forgot your credentials to the web UI, try this. - .. code-block:: bash sunshine -creds Can't access the web UI? - #. Check firefall rules. NvFBC, NvENC, or general issues with Nvidia graphics card. - - - Consume grade Nvidia cards are software limited to a specific number of encodes. See - `Video Encode and Decode GPU Support Matrix `_ - for more info. - - You can usually bypass the restriction with a driver patch. See Keylase's - `Linux `_ - or `Windows `_ patches for more guidance. + - Consumer grade Nvidia cards are software limited to a specific number of encodes. See + `Video Encode and Decode GPU Support Matrix `_ + for more info. + - You can usually bypass the restriction with a driver patch. See Keylase's + `Linux `_ + or `Windows `_ patches for more guidance. diff --git a/docs/source/troubleshooting/linux.rst b/docs/source/troubleshooting/linux.rst index a1c8b4da..0c8e3f97 100644 --- a/docs/source/troubleshooting/linux.rst +++ b/docs/source/troubleshooting/linux.rst @@ -1,9 +1,6 @@ -:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/troubleshooting/linux.rst - Linux ===== If screencasting fails with KMS, you may need to run the following to force unprivileged screencasting. - .. code-block:: bash sudo setcap -r $(readlink -f $(which sunshine)) diff --git a/docs/source/troubleshooting/macos.rst b/docs/source/troubleshooting/macos.rst index f6014eb7..70a0d60c 100644 --- a/docs/source/troubleshooting/macos.rst +++ b/docs/source/troubleshooting/macos.rst @@ -1,29 +1,10 @@ -:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/troubleshooting/macos.rst - macOS ===== If you get this error: - - ``Dynamic session lookup supported but failed: launchd did not provide a socket path, verify that - org.freedesktop.dbus-session.plist is loaded!`` + `Dynamic session lookup supported but failed: launchd did not provide a socket path, verify that + org.freedesktop.dbus-session.plist is loaded!` Try this. - .. code-block:: bash launchctl load -w /Library/LaunchAgents/org.freedesktop.dbus-session.plist - -Uninstall: - - - pkg - - .. code-block:: bash - - sudo chmod +x /opt/local/etc/sunshine/assets/uninstall_pkg.sh - sudo /opt/local/etc/sunshine/assets/uninstall_pkg.sh - - - Portfile - - .. code-block:: bash - - sudo port uninstall Sunshine diff --git a/docs/source/troubleshooting/windows.rst b/docs/source/troubleshooting/windows.rst index bb21589c..15053519 100644 --- a/docs/source/troubleshooting/windows.rst +++ b/docs/source/troubleshooting/windows.rst @@ -1,7 +1,4 @@ -:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/troubleshooting/windows.rst - Windows ======= No gamepad is detected. - #. Verify that you've installed `ViGEmBus `_. diff --git a/scripts/requirements.txt b/scripts/requirements.txt index 84c26fe1..ab35a359 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -1,5 +1 @@ Babel==2.10.3 -m2r2==0.3.2 -Sphinx==5.3.0 -sphinx-copybutton==0.5.0 -sphinx-rtd-theme==1.0.0 From f34e3b03fb85b5e1897646ee387a29b19cd357ef Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 30 Oct 2022 13:08:39 -0400 Subject: [PATCH 415/817] v0.15.0 --- CHANGELOG.md | 21 +++++++++++++++++++++ CMakeLists.txt | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 272709ee..eb3955cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## [0.15.0] - 2022-10-30 +### 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 +- (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 +- (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 +- (Windows) Automatically delete start menu entry upon uninstall +- (Windows) Automatically delete program install directory upon uninstall, with user prompt +- (Linux) Handle the case of no default audio sink +- (Windows/Linux/MacOS) Fix default image paths +- (Linux) Fix CUDA RGBA to NV12 conversion + ## [0.14.1] - 2022-08-09 ### Added - (Linux) Flatpak package added diff --git a/CMakeLists.txt b/CMakeLists.txt index cfbbfa7e..840087b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.0) -project(Sunshine VERSION 0.14.1 +project(Sunshine VERSION 0.15.0 DESCRIPTION "Sunshine is a Gamestream host for Moonlight." HOMEPAGE_URL "https://app.lizardbyte.dev" ) From bfdcfbb357eb081b3fe7760230c452ea51cb3e33 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 30 Oct 2022 15:14:50 -0400 Subject: [PATCH 416/817] fix CI setup-release --- .github/workflows/CI.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 653641d4..50ab69cb 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -74,6 +74,8 @@ jobs: steps: - name: Set release details id: release_details + env: + release_body: ${{ needs.check_changelog.outputs.release_body }} run: | # determine to create a release or not if [[ $GITHUB_EVENT_NAME == "push" ]]; then @@ -87,7 +89,7 @@ jobs: if [[ $GITHUB_REF == refs/heads/master ]]; then TAG="${{ needs.check_changelog.outputs.next_version }}" RELEASE_NAME="${{ needs.check_changelog.outputs.next_version }}" - RELEASE_BODY="${{ needs.check_changelog.outputs.release_body }}" + RELEASE_BODY="$release_body" PRE_RELEASE="false" elif [[ $GITHUB_REF == refs/heads/nightly ]]; then TAG="nightly-dev" From c665e211cdb6c17517f54f09169538d6ac110834 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 30 Oct 2022 17:58:01 -0400 Subject: [PATCH 417/817] fix CI env variables --- .github/workflows/CI.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 50ab69cb..e2c9f20d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -75,7 +75,7 @@ jobs: - name: Set release details id: release_details env: - release_body: ${{ needs.check_changelog.outputs.release_body }} + RELEASE_BODY: ${{ needs.check_changelog.outputs.release_body }} run: | # determine to create a release or not if [[ $GITHUB_EVENT_NAME == "push" ]]; then @@ -89,12 +89,12 @@ jobs: if [[ $GITHUB_REF == refs/heads/master ]]; then TAG="${{ needs.check_changelog.outputs.next_version }}" RELEASE_NAME="${{ needs.check_changelog.outputs.next_version }}" - RELEASE_BODY="$release_body" + NEW_RELEASE_BODY="$RELEASE_BODY" PRE_RELEASE="false" elif [[ $GITHUB_REF == refs/heads/nightly ]]; then TAG="nightly-dev" RELEASE_NAME="nightly" - RELEASE_BODY="automated nightly release - $(date -u +'%Y-%m-%dT%H:%M:%SZ') - ${COMMIT}" + NEW_RELEASE_BODY="automated nightly release - $(date -u +'%Y-%m-%dT%H:%M:%SZ') - ${COMMIT}" PRE_RELEASE="true" fi @@ -102,7 +102,7 @@ jobs: echo "release_tag=${TAG}" >> $GITHUB_OUTPUT echo "release_commit=${COMMIT}" >> $GITHUB_OUTPUT echo "release_name=${RELEASE_NAME}" >> $GITHUB_OUTPUT - echo "release_body=${RELEASE_BODY}" >> $GITHUB_OUTPUT + echo "release_body=${NEW_RELEASE_BODY}" >> $GITHUB_OUTPUT echo "pre_release=${PRE_RELEASE}" >> $GITHUB_OUTPUT outputs: From 84ae033a371234beae530b302194f1454eaa2ed9 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 30 Oct 2022 19:57:19 -0400 Subject: [PATCH 418/817] fix env variables for setup_release job --- .github/workflows/CI.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e2c9f20d..062018f8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -89,12 +89,12 @@ jobs: if [[ $GITHUB_REF == refs/heads/master ]]; then TAG="${{ needs.check_changelog.outputs.next_version }}" RELEASE_NAME="${{ needs.check_changelog.outputs.next_version }}" - NEW_RELEASE_BODY="$RELEASE_BODY" + RELEASE_BODY="$RELEASE_BODY" PRE_RELEASE="false" elif [[ $GITHUB_REF == refs/heads/nightly ]]; then TAG="nightly-dev" RELEASE_NAME="nightly" - NEW_RELEASE_BODY="automated nightly release - $(date -u +'%Y-%m-%dT%H:%M:%SZ') - ${COMMIT}" + RELEASE_BODY="automated nightly release - $(date -u +'%Y-%m-%dT%H:%M:%SZ') - ${COMMIT}" PRE_RELEASE="true" fi @@ -102,15 +102,19 @@ jobs: echo "release_tag=${TAG}" >> $GITHUB_OUTPUT echo "release_commit=${COMMIT}" >> $GITHUB_OUTPUT echo "release_name=${RELEASE_NAME}" >> $GITHUB_OUTPUT - echo "release_body=${NEW_RELEASE_BODY}" >> $GITHUB_OUTPUT echo "pre_release=${PRE_RELEASE}" >> $GITHUB_OUTPUT + # this is stupid but works for multiline strings + echo "RELEASE_BODY<> $GITHUB_ENV + echo "$RELEASE_BODY" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + outputs: create_release: ${{ steps.release_details.outputs.create_release }} release_tag: ${{ steps.release_details.outputs.release_tag }} release_commit: ${{ steps.release_details.outputs.release_commit }} release_name: ${{ steps.release_details.outputs.release_name }} - release_body: ${{ steps.release_details.outputs.release_body }} + release_body: ${{ env.RELEASE_BODY }} pre_release: ${{ steps.release_details.outputs.pre_release }} setup_flatpak_matrix: From ca3bab324284d856f2cc5d09ed2350d7e45d84a6 Mon Sep 17 00:00:00 2001 From: Kuba <63459534+bananaman2020@users.noreply.github.com> Date: Mon, 31 Oct 2022 20:35:03 +0000 Subject: [PATCH 419/817] fix typo --- docs/source/troubleshooting/general.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/troubleshooting/general.rst b/docs/source/troubleshooting/general.rst index 010a52c8..9bbbfa53 100644 --- a/docs/source/troubleshooting/general.rst +++ b/docs/source/troubleshooting/general.rst @@ -6,7 +6,7 @@ If you forgot your credentials to the web UI, try this. sunshine -creds Can't access the web UI? - #. Check firefall rules. + #. Check firewall rules. NvFBC, NvENC, or general issues with Nvidia graphics card. - Consumer grade Nvidia cards are software limited to a specific number of encodes. See From bea78dd3ebcdc75562f197b3fcf5069451157baf Mon Sep 17 00:00:00 2001 From: Stefanos Papanatsios Date: Tue, 1 Nov 2022 19:32:04 +0200 Subject: [PATCH 420/817] Fix credentials command typo in docs --- docs/source/troubleshooting/general.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/troubleshooting/general.rst b/docs/source/troubleshooting/general.rst index 9bbbfa53..2bbb93c9 100644 --- a/docs/source/troubleshooting/general.rst +++ b/docs/source/troubleshooting/general.rst @@ -3,7 +3,7 @@ General If you forgot your credentials to the web UI, try this. .. code-block:: bash - sunshine -creds + sunshine --creds Can't access the web UI? #. Check firewall rules. From 6a2a485435e48ad60481157e445e1a7dc6fc7fb3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Nov 2022 12:40:43 +0000 Subject: [PATCH 421/817] Bump babel from 2.10.3 to 2.11.0 Bumps [babel](https://github.com/python-babel/babel) from 2.10.3 to 2.11.0. - [Release notes](https://github.com/python-babel/babel/releases) - [Changelog](https://github.com/python-babel/babel/blob/master/CHANGES.rst) - [Commits](https://github.com/python-babel/babel/compare/v2.10.3...v2.11.0) --- updated-dependencies: - dependency-name: babel dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- scripts/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/requirements.txt b/scripts/requirements.txt index ab35a359..6d761a5a 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -1 +1 @@ -Babel==2.10.3 +Babel==2.11.0 From 89a0109fa73f4f45c5fbf308f02979a98fdd00e3 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 2 Nov 2022 10:09:01 -0400 Subject: [PATCH 422/817] fix winget release Fixes #464 --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 062018f8..066bfc27 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -901,7 +901,7 @@ jobs: release-winget: name: Release to WinGet - needs: build_win + needs: [setup_release, build_win] if: ${{ needs.setup_release.outputs.create_release == 'true' && github.ref == 'refs/heads/master' }} runs-on: windows-latest # the required action can only be run on Windows steps: From 0bdb887e2c5a8dad31340f83c4708ee0f1728788 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 2 Nov 2022 15:06:56 -0400 Subject: [PATCH 423/817] add linux/arm64/v8 docker platform --- .docker_platforms | 2 +- DOCKER_README.md | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.docker_platforms b/.docker_platforms index 11a8121d..f41242ad 100644 --- a/.docker_platforms +++ b/.docker_platforms @@ -1 +1 @@ -linux/amd64 \ No newline at end of file +linux/amd64,linux/arm64/v8 \ No newline at end of file diff --git a/DOCKER_README.md b/DOCKER_README.md index 87dbc672..222cd163 100644 --- a/DOCKER_README.md +++ b/DOCKER_README.md @@ -94,3 +94,15 @@ uid=1001(dockeruser) gid=1001(dockergroup) groups=1001(dockergroup) ``` If you want to change the PUID or PGID after the image has been built, it will require rebuilding the image. + +## Supported Architectures + +Specifying `lizardbyte/sunshine:latest` or `ghcr.io/lizardbyte/sunshine:latest` should retrieve the correct +image for your architecture. + +The architectures supported by this image are: + +| Architecture | Available | +|:------------:|:---------:| +| x86-64 | ✅ | +| arm64 | ✅ | From e924e2eedb42d0a8c6fb3265c6233f6222f89e1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Nov 2022 00:31:17 +0000 Subject: [PATCH 424/817] Bump sphinx-copybutton from 0.5.0 to 0.5.1 Bumps [sphinx-copybutton](https://github.com/executablebooks/sphinx-copybutton) from 0.5.0 to 0.5.1. - [Release notes](https://github.com/executablebooks/sphinx-copybutton/releases) - [Changelog](https://github.com/executablebooks/sphinx-copybutton/blob/master/CHANGELOG.md) - [Commits](https://github.com/executablebooks/sphinx-copybutton/compare/v0.5.0...v0.5.1) --- updated-dependencies: - dependency-name: sphinx-copybutton dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index fc950e03..891743ce 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,4 @@ furo==2022.9.29 m2r2==0.3.3 Sphinx==5.3.0 -sphinx-copybutton==0.5.0 +sphinx-copybutton==0.5.1 From 01b8ba353a60e941b48659ef0c8c3075ac06b3aa Mon Sep 17 00:00:00 2001 From: Mariotaku Date: Sat, 19 Nov 2022 01:07:22 +0900 Subject: [PATCH 425/817] Cover Finder (#216) Adds functionality to search and add game cover images automatically. Co-authored-by: Conn O'Griofa Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> --- .github/workflows/CI.yml | 4 +- CMakeLists.txt | 15 ++- Dockerfile | 1 + packaging/linux/aur/PKGBUILD | 2 +- packaging/macos/Portfile | 1 + src/confighttp.cpp | 69 +++++++++- src/httpcommon.cpp | 52 ++++++++ src/httpcommon.h | 4 + src_assets/common/assets/web/apps.html | 178 ++++++++++++++++++++++++- 9 files changed, 314 insertions(+), 12 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 066bfc27..28fca7e8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -383,6 +383,7 @@ jobs: libboost-log-dev \ libboost-thread-dev \ libcap-dev \ + libcurl4-openssl-dev \ libdrm-dev \ libevdev-dev \ libpulse-dev \ @@ -548,7 +549,7 @@ jobs: - name: Setup Dependencies MacOS run: | # install dependencies using homebrew - brew install boost cmake ffmpeg opus + brew install boost cmake curl ffmpeg opus # fix openssl header not found ln -sf /usr/local/opt/openssl/include/openssl /usr/local/include/openssl @@ -846,6 +847,7 @@ jobs: mingw-w64-x86_64-binutils mingw-w64-x86_64-boost mingw-w64-x86_64-cmake + mingw-w64-x86_64-curl mingw-w64-x86_64-nsis mingw-w64-x86_64-openssl mingw-w64-x86_64-opus diff --git a/CMakeLists.txt b/CMakeLists.txt index 840087b8..bd5ae938 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,9 @@ include_directories(third-party/miniupnp) find_package(Threads REQUIRED) find_package(OpenSSL REQUIRED) +find_package(PkgConfig REQUIRED) +pkg_check_modules (CURL REQUIRED libcurl) + if(NOT APPLE) set(Boost_USE_STATIC_LIBS ON) endif() @@ -88,6 +91,7 @@ if(WIN32) INPUT "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip" DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/pre-compiled) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CURL_STATIC_LDFLAGS} ${CURL_STATIC_CFLAGS}") if(NOT DEFINED SUNSHINE_PREPARED_BINARIES) set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled/windows") @@ -149,6 +153,7 @@ if(WIN32) d3d11 dxgi D3DCompiler setupapi dwmapi + ${CURL_STATIC_LIBRARIES} ) set_source_files_properties(third-party/ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_DEFINITIONS "UNICODE=1;ERROR_INVALID_DEVICE_OBJECT_PARAMETER=650") @@ -451,6 +456,7 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${FFMPEG_LIBRARIES} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} + ${CURL_LIBRARIES} ${PLATFORM_LIBRARIES}) if(NOT WIN32) @@ -458,6 +464,11 @@ if(NOT WIN32) endif() add_executable(sunshine ${SUNSHINE_TARGET_FILES}) + +if(WIN32) + set_target_properties(sunshine PROPERTIES LINK_SEARCH_START_STATIC 1) +endif() + target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES} ${EXTRA_LIBS}) target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS}) set_target_properties(sunshine PROPERTIES CXX_STANDARD 17 @@ -647,8 +658,8 @@ elseif(UNIX) # Dependencies set(CPACK_DEB_COMPONENT_INSTALL ON) - set(CPACK_DEBIAN_PACKAGE_DEPENDS "openssl, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") - set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.1, libavdevice >= 4.3, boost-thread >= 1.67.0, boost-filesystem >= 1.67.0, boost-log >= 1.67.0, pulseaudio-libs >= 10.0, libopusenc >= 0.2.1, libxcb >= 1.13, libXtst >= 1.2.3, libevdev >= 1.5.6, libdrm >= 2.4.97, libcap >= 2.22") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "openssl, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libcurl4, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") + set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.1, libavdevice >= 4.3, boost-thread >= 1.67.0, boost-filesystem >= 1.67.0, boost-log >= 1.67.0, libcurl >= 7.0, pulseaudio-libs >= 10.0, libopusenc >= 0.2.1, libxcb >= 1.13, libXtst >= 1.2.3, libevdev >= 1.5.6, libdrm >= 2.4.97, libcap >= 2.22") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # This should automatically figure out dependencies, doesn't work with the current config endif() endif() diff --git a/Dockerfile b/Dockerfile index b493b0d4..3f52d99e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,7 @@ RUN apt-get update -y \ libboost-log-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* \ libevdev-dev=1.12.1* \ libpulse-dev=1:15.99.1* \ diff --git a/packaging/linux/aur/PKGBUILD b/packaging/linux/aur/PKGBUILD index 1ef6d54b..a1188016 100644 --- a/packaging/linux/aur/PKGBUILD +++ b/packaging/linux/aur/PKGBUILD @@ -9,7 +9,7 @@ arch=('x86_64' 'i686') url=@PROJECT_HOMEPAGE_URL@ license=('GPL3') -depends=('avahi' 'boost-libs' 'ffmpeg4.4' 'libevdev' 'libpulse' 'libx11' 'libxcb' 'libxfixes' 'libxrandr' 'libxtst' 'openssl' 'opus' 'udev') +depends=('avahi' 'boost-libs' 'curl' 'ffmpeg4.4' 'libevdev' 'libpulse' 'libx11' 'libxcb' 'libxfixes' 'libxrandr' 'libxtst' 'openssl' 'opus' 'udev') makedepends=('boost' 'cmake' 'git' 'make') optdepends=('cuda' 'libcap' 'libdrm') diff --git a/packaging/macos/Portfile b/packaging/macos/Portfile index d440fad3..467fd3ee 100644 --- a/packaging/macos/Portfile +++ b/packaging/macos/Portfile @@ -32,6 +32,7 @@ post-fetch { } depends_lib port:avahi \ + port:curl \ port:ffmpeg \ port:libopus diff --git a/src/confighttp.cpp b/src/confighttp.cpp index f5f2ce09..ec38fb93 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -13,6 +13,8 @@ #include +#include + #include #include #include @@ -164,9 +166,12 @@ void getAppsPage(resp_https_t response, req_https_t request) { print_req(request); + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Access-Control-Allow-Origin", "https://images.igdb.com/"); + std::string header = read_file(WEB_DIR "header.html"); std::string content = read_file(WEB_DIR "apps.html"); - response->write(header + content); + response->write(header + content, headers); } void getClientsPage(resp_https_t response, req_https_t request) { @@ -411,6 +416,67 @@ void deleteApp(resp_https_t response, req_https_t request) { proc::refresh(config::stream.file_apps); } +void uploadCover(resp_https_t response, req_https_t request) { + if(!authenticate(response, request)) return; + + std::stringstream ss; + std::stringstream configStream; + ss << request->content.rdbuf(); + pt::ptree outputTree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + SimpleWeb::StatusCode code = SimpleWeb::StatusCode::success_ok; + if(outputTree.get_child_optional("error").has_value()) { + code = SimpleWeb::StatusCode::client_error_bad_request; + } + + pt::write_json(data, outputTree); + response->write(code, data.str()); + }); + pt::ptree inputTree; + try { + pt::read_json(ss, inputTree); + } + catch(std::exception &e) { + BOOST_LOG(warning) << "UploadCover: "sv << e.what(); + outputTree.put("status", "false"); + outputTree.put("error", e.what()); + return; + } + + auto key = inputTree.get("key", ""); + if(key.empty()) { + outputTree.put("error", "Cover key is required"); + return; + } + auto url = inputTree.get("url", ""); + + const std::string coverdir = platf::appdata().string() + "/covers/"; + if(!boost::filesystem::exists(coverdir)) { + boost::filesystem::create_directory(coverdir); + } + + std::basic_string path = coverdir + http::url_escape(key) + ".png"; + if(!url.empty()) { + if(http::url_get_host(url) != "images.igdb.com") { + outputTree.put("error", "Only images.igdb.com is allowed"); + return; + } + if(!http::download_file(url, path)) { + outputTree.put("error", "Failed to download cover"); + return; + } + } + else { + auto data = SimpleWeb::Crypto::Base64::decode(inputTree.get("data")); + + std::ofstream imgfile(path); + imgfile.write(data.data(), (int)data.size()); + } + outputTree.put("path", path); +} + void getConfig(resp_https_t response, req_https_t request) { if(!authenticate(response, request)) return; @@ -616,6 +682,7 @@ void start() { server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; server.resource["^/api/clients/unpair$"]["POST"] = unpairAll; server.resource["^/api/apps/close"]["POST"] = closeApp; + server.resource["^/api/covers/upload$"]["POST"] = uploadCover; server.resource["^/images/favicon.ico$"]["GET"] = getFaviconImage; server.resource["^/images/logo-sunshine-45.png$"]["GET"] = getSunshineLogoImage; server.resource["^/third_party/bootstrap.min.css$"]["GET"] = getBootstrapCss; diff --git a/src/httpcommon.cpp b/src/httpcommon.cpp index 5f1955a6..e4747e32 100644 --- a/src/httpcommon.cpp +++ b/src/httpcommon.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "config.h" #include "crypto.h" @@ -180,4 +181,55 @@ int create_creds(const std::string &pkey, const std::string &cert) { 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); +#ifdef _WIN32 + 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 << ']'; + } + 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_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 result; +} + } // namespace http diff --git a/src/httpcommon.h b/src/httpcommon.h index e1a1509a..4e8eee01 100644 --- a/src/httpcommon.h +++ b/src/httpcommon.h @@ -12,6 +12,10 @@ int save_user_creds( 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); + extern std::string unique_id; extern net::net_e origin_pin_allowed; extern net::net_e origin_web_ui_allowed; diff --git a/src_assets/common/assets/web/apps.html b/src_assets/common/assets/web/apps.html index 9c21fe63..87c22c0d 100644 --- a/src_assets/common/assets/web/apps.html +++ b/src_assets/common/assets/web/apps.html @@ -169,13 +169,47 @@

      Applications

      - +
      + + + +
      Application icon/picture/image path that will be sent to client. Image must be a PNG file. If not set, Sunshine will send default box image. @@ -196,6 +230,12 @@

      Applications

      - + + + diff --git a/src_assets/common/assets/web/header.html b/src_assets/common/assets/web/header.html index 3957a994..80041477 100644 --- a/src_assets/common/assets/web/header.html +++ b/src_assets/common/assets/web/header.html @@ -6,10 +6,10 @@ Sunshine - - - - + + + + diff --git a/src_assets/common/assets/web/package.json b/src_assets/common/assets/web/package.json new file mode 100644 index 00000000..6180112a --- /dev/null +++ b/src_assets/common/assets/web/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "@fortawesome/fontawesome-free": "6.2.0", + "bootstrap": "5.0.0", + "vue": "2.6.12" + } +} diff --git a/src_assets/common/assets/web/third_party/bootstrap.bundle.min.js b/src_assets/common/assets/web/third_party/bootstrap.bundle.min.js deleted file mode 100644 index 7a59950b..00000000 --- a/src_assets/common/assets/web/third_party/bootstrap.bundle.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v5.0.0 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t=t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t},e=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i="#"+i.split("#")[1]),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0},o=t=>{t.dispatchEvent(new Event("transitionend"))},r=t=>(t[0]||t).nodeType,a=(t,e)=>{let i=!1;const n=e+5;t.addEventListener("transitionend",(function e(){i=!0,t.removeEventListener("transitionend",e)})),setTimeout(()=>{i||o(t)},n)},l=(t,e,i)=>{Object.keys(i).forEach(n=>{const s=i[n],o=e[n],a=o&&r(o)?"element":null==(l=o)?""+l:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)})},c=t=>{if(!t)return!1;if(t.style&&t.parentNode&&t.parentNode.style){const e=getComputedStyle(t),i=getComputedStyle(t.parentNode);return"none"!==e.display&&"none"!==i.display&&"hidden"!==e.visibility}return!1},d=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},u=()=>{},f=t=>t.offsetHeight,p=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},g=()=>"rtl"===document.documentElement.dir,m=(t,e)=>{var i;i=()=>{const i=p();if(i){const n=i.fn[t];i.fn[t]=e.jQueryInterface,i.fn[t].Constructor=e,i.fn[t].noConflict=()=>(i.fn[t]=n,e.jQueryInterface)}},"loading"===document.readyState?document.addEventListener("DOMContentLoaded",i):i()},_=t=>{"function"==typeof t&&t()},b=new Map;var v={set(t,e,i){b.has(t)||b.set(t,new Map);const n=b.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>b.has(t)&&b.get(t).get(e)||null,remove(t,e){if(!b.has(t))return;const i=b.get(t);i.delete(e),0===i.size&&b.delete(t)}};const y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,E=/::\d+$/,T={};let A=1;const L={mouseenter:"mouseover",mouseleave:"mouseout"},O=/^(mouseenter|mouseleave)/i,k=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function D(t,e){return e&&`${e}::${A++}`||t.uidEvent||A++}function x(t){const e=D(t);return t.uidEvent=e,T[e]=T[e]||{},T[e]}function C(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;sfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=S(e,i,n),l=x(t),c=l[a]||(l[a]={}),d=C(c,r,o?i:null);if(d)return void(d.oneOff=d.oneOff&&s);const h=D(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&I.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&I.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=h,c[h]=u,t.addEventListener(a,u,o)}function j(t,e,i,n,s){const o=C(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function P(t){return t=t.replace(w,""),L[t]||t}const I={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void j(t,l,r,o,s?i:null)}c&&Object.keys(l).forEach(i=>{!function(t,e,i,n){const s=e[i]||{};Object.keys(s).forEach(o=>{if(o.includes(n)){const n=s[o];j(t,e,i,n.originalHandler,n.delegationSelector)}})}(t,l,i,e.slice(1))});const d=l[r]||{};Object.keys(d).forEach(i=>{const n=i.replace(E,"");if(!a||e.includes(n)){const e=d[i];j(t,l,r,e.originalHandler,e.delegationSelector)}})},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=p(),s=P(e),o=e!==s,r=k.has(s);let a,l=!0,c=!0,d=!1,h=null;return o&&n&&(a=n.Event(e,i),n(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),d=a.isDefaultPrevented()),r?(h=document.createEvent("HTMLEvents"),h.initEvent(s,l,!0)):h=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach(t=>{Object.defineProperty(h,t,{get:()=>i[t]})}),d&&h.preventDefault(),c&&t.dispatchEvent(h),h.defaultPrevented&&void 0!==a&&a.preventDefault(),h}};class M{constructor(t){(t="string"==typeof t?document.querySelector(t):t)&&(this._element=t,v.set(this._element,this.constructor.DATA_KEY,this))}dispose(){v.remove(this._element,this.constructor.DATA_KEY),I.off(this._element,"."+this.constructor.DATA_KEY),this._element=null}static getInstance(t){return v.get(t,this.DATA_KEY)}static get VERSION(){return"5.0.0"}}class H extends M{static get DATA_KEY(){return"bs.alert"}close(t){const e=t?this._getRootElement(t):this._element,i=this._triggerCloseEvent(e);null===i||i.defaultPrevented||this._removeElement(e)}_getRootElement(t){return n(t)||t.closest(".alert")}_triggerCloseEvent(t){return I.trigger(t,"close.bs.alert")}_removeElement(t){if(t.classList.remove("show"),!t.classList.contains("fade"))return void this._destroyElement(t);const e=s(t);I.one(t,"transitionend",()=>this._destroyElement(t)),a(t,e)}_destroyElement(t){t.parentNode&&t.parentNode.removeChild(t),I.trigger(t,"closed.bs.alert")}static jQueryInterface(t){return this.each((function(){let e=v.get(this,"bs.alert");e||(e=new H(this)),"close"===t&&e[t](this)}))}static handleDismiss(t){return function(e){e&&e.preventDefault(),t.close(this)}}}I.on(document,"click.bs.alert.data-api",'[data-bs-dismiss="alert"]',H.handleDismiss(new H)),m("alert",H);class R extends M{static get DATA_KEY(){return"bs.button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){let e=v.get(this,"bs.button");e||(e=new R(this)),"toggle"===t&&e[t]()}))}}function B(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function W(t){return t.replace(/[A-Z]/g,t=>"-"+t.toLowerCase())}I.on(document,"click.bs.button.data-api",'[data-bs-toggle="button"]',t=>{t.preventDefault();const e=t.target.closest('[data-bs-toggle="button"]');let i=v.get(e,"bs.button");i||(i=new R(e)),i.toggle()}),m("button",R);const z={setDataAttribute(t,e,i){t.setAttribute("data-bs-"+W(e),i)},removeDataAttribute(t,e){t.removeAttribute("data-bs-"+W(e))},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter(t=>t.startsWith("bs")).forEach(i=>{let n=i.replace(/^bs/,"");n=n.charAt(0).toLowerCase()+n.slice(1,n.length),e[n]=B(t.dataset[i])}),e},getDataAttribute:(t,e)=>B(t.getAttribute("data-bs-"+W(e))),offset(t){const e=t.getBoundingClientRect();return{top:e.top+document.body.scrollTop,left:e.left+document.body.scrollLeft}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},U={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter(t=>t.matches(e)),parents(t,e){const i=[];let n=t.parentNode;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]}},$={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},F={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},K="next",Y="prev",q="left",V="right";class X extends M{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=U.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return $}static get DATA_KEY(){return"bs.carousel"}next(){this._isSliding||this._slide(K)}nextWhenVisible(){!document.hidden&&c(this._element)&&this.next()}prev(){this._isSliding||this._slide(Y)}pause(t){t||(this._isPaused=!0),U.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(o(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=U.findOne(".active.carousel-item",this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void I.one(this._element,"slid.bs.carousel",()=>this.to(t));if(e===t)return this.pause(),void this.cycle();const i=t>e?K:Y;this._slide(i,this._items[t])}dispose(){this._items=null,this._config=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null,super.dispose()}_getConfig(t){return t={...$,...t},l("carousel",t,F),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?V:q)}_addEventListeners(){this._config.keyboard&&I.on(this._element,"keydown.bs.carousel",t=>this._keydown(t)),"hover"===this._config.pause&&(I.on(this._element,"mouseenter.bs.carousel",t=>this.pause(t)),I.on(this._element,"mouseleave.bs.carousel",t=>this.cycle(t))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>{!this._pointerEvent||"pen"!==t.pointerType&&"touch"!==t.pointerType?this._pointerEvent||(this.touchStartX=t.touches[0].clientX):this.touchStartX=t.clientX},e=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},i=t=>{!this._pointerEvent||"pen"!==t.pointerType&&"touch"!==t.pointerType||(this.touchDeltaX=t.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(t=>this.cycle(t),500+this._config.interval))};U.find(".carousel-item img",this._element).forEach(t=>{I.on(t,"dragstart.bs.carousel",t=>t.preventDefault())}),this._pointerEvent?(I.on(this._element,"pointerdown.bs.carousel",e=>t(e)),I.on(this._element,"pointerup.bs.carousel",t=>i(t)),this._element.classList.add("pointer-event")):(I.on(this._element,"touchstart.bs.carousel",e=>t(e)),I.on(this._element,"touchmove.bs.carousel",t=>e(t)),I.on(this._element,"touchend.bs.carousel",t=>i(t)))}_keydown(t){/input|textarea/i.test(t.target.tagName)||("ArrowLeft"===t.key?(t.preventDefault(),this._slide(V)):"ArrowRight"===t.key&&(t.preventDefault(),this._slide(q)))}_getItemIndex(t){return this._items=t&&t.parentNode?U.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const i=t===K,n=t===Y,s=this._getItemIndex(e),o=this._items.length-1;if((n&&0===s||i&&s===o)&&!this._config.wrap)return e;const r=(s+(n?-1:1))%this._items.length;return-1===r?this._items[this._items.length-1]:this._items[r]}_triggerSlideEvent(t,e){const i=this._getItemIndex(t),n=this._getItemIndex(U.findOne(".active.carousel-item",this._element));return I.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:n,to:i})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=U.findOne(".active",this._indicatorsElement);e.classList.remove("active"),e.removeAttribute("aria-current");const i=U.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{r.classList.remove(h,u),r.classList.add("active"),n.classList.remove("active",u,h),this._isSliding=!1,setTimeout(()=>{I.trigger(this._element,"slid.bs.carousel",{relatedTarget:r,direction:p,from:o,to:l})},0)}),a(n,t)}else n.classList.remove("active"),r.classList.add("active"),this._isSliding=!1,I.trigger(this._element,"slid.bs.carousel",{relatedTarget:r,direction:p,from:o,to:l});c&&this.cycle()}}_directionToOrder(t){return[V,q].includes(t)?g()?t===q?Y:K:t===q?K:Y:t}_orderToDirection(t){return[K,Y].includes(t)?g()?t===Y?q:V:t===Y?V:q:t}static carouselInterface(t,e){let i=v.get(t,"bs.carousel"),n={...$,...z.getDataAttributes(t)};"object"==typeof e&&(n={...n,...e});const s="string"==typeof e?e:n.slide;if(i||(i=new X(t,n)),"number"==typeof e)i.to(e);else if("string"==typeof s){if(void 0===i[s])throw new TypeError(`No method named "${s}"`);i[s]()}else n.interval&&n.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){X.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=n(this);if(!e||!e.classList.contains("carousel"))return;const i={...z.getDataAttributes(e),...z.getDataAttributes(this)},s=this.getAttribute("data-bs-slide-to");s&&(i.interval=!1),X.carouselInterface(e,i),s&&v.get(e,"bs.carousel").to(s),t.preventDefault()}}I.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",X.dataApiClickHandler),I.on(window,"load.bs.carousel.data-api",()=>{const t=U.find('[data-bs-ride="carousel"]');for(let e=0,i=t.length;et===this._element);null!==s&&o.length&&(this._selector=s,this._triggerArray.push(e))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}static get Default(){return Q}static get DATA_KEY(){return"bs.collapse"}toggle(){this._element.classList.contains("show")?this.hide():this.show()}show(){if(this._isTransitioning||this._element.classList.contains("show"))return;let t,e;this._parent&&(t=U.find(".show, .collapsing",this._parent).filter(t=>"string"==typeof this._config.parent?t.getAttribute("data-bs-parent")===this._config.parent:t.classList.contains("collapse")),0===t.length&&(t=null));const i=U.findOne(this._selector);if(t){const n=t.find(t=>i!==t);if(e=n?v.get(n,"bs.collapse"):null,e&&e._isTransitioning)return}if(I.trigger(this._element,"show.bs.collapse").defaultPrevented)return;t&&t.forEach(t=>{i!==t&&Z.collapseInterface(t,"hide"),e||v.set(t,"bs.collapse",null)});const n=this._getDimension();this._element.classList.remove("collapse"),this._element.classList.add("collapsing"),this._element.style[n]=0,this._triggerArray.length&&this._triggerArray.forEach(t=>{t.classList.remove("collapsed"),t.setAttribute("aria-expanded",!0)}),this.setTransitioning(!0);const o="scroll"+(n[0].toUpperCase()+n.slice(1)),r=s(this._element);I.one(this._element,"transitionend",()=>{this._element.classList.remove("collapsing"),this._element.classList.add("collapse","show"),this._element.style[n]="",this.setTransitioning(!1),I.trigger(this._element,"shown.bs.collapse")}),a(this._element,r),this._element.style[n]=this._element[o]+"px"}hide(){if(this._isTransitioning||!this._element.classList.contains("show"))return;if(I.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=this._element.getBoundingClientRect()[t]+"px",f(this._element),this._element.classList.add("collapsing"),this._element.classList.remove("collapse","show");const e=this._triggerArray.length;if(e>0)for(let t=0;t{this.setTransitioning(!1),this._element.classList.remove("collapsing"),this._element.classList.add("collapse"),I.trigger(this._element,"hidden.bs.collapse")}),a(this._element,i)}setTransitioning(t){this._isTransitioning=t}dispose(){super.dispose(),this._config=null,this._parent=null,this._triggerArray=null,this._isTransitioning=null}_getConfig(t){return(t={...Q,...t}).toggle=Boolean(t.toggle),l("collapse",t,G),t}_getDimension(){return this._element.classList.contains("width")?"width":"height"}_getParent(){let{parent:t}=this._config;r(t)?void 0===t.jquery&&void 0===t[0]||(t=t[0]):t=U.findOne(t);const e=`[data-bs-toggle="collapse"][data-bs-parent="${t}"]`;return U.find(e,t).forEach(t=>{const e=n(t);this._addAriaAndCollapsedClass(e,[t])}),t}_addAriaAndCollapsedClass(t,e){if(!t||!e.length)return;const i=t.classList.contains("show");e.forEach(t=>{i?t.classList.remove("collapsed"):t.classList.add("collapsed"),t.setAttribute("aria-expanded",i)})}static collapseInterface(t,e){let i=v.get(t,"bs.collapse");const n={...Q,...z.getDataAttributes(t),..."object"==typeof e&&e?e:{}};if(!i&&n.toggle&&"string"==typeof e&&/show|hide/.test(e)&&(n.toggle=!1),i||(i=new Z(t,n)),"string"==typeof e){if(void 0===i[e])throw new TypeError(`No method named "${e}"`);i[e]()}}static jQueryInterface(t){return this.each((function(){Z.collapseInterface(this,t)}))}}I.on(document,"click.bs.collapse.data-api",'[data-bs-toggle="collapse"]',(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=z.getDataAttributes(this),n=i(this);U.find(n).forEach(t=>{const i=v.get(t,"bs.collapse");let n;i?(null===i._parent&&"string"==typeof e.parent&&(i._config.parent=e.parent,i._parent=i._getParent()),n="toggle"):n=e,Z.collapseInterface(t,n)})})),m("collapse",Z);var J="top",tt="bottom",et="right",it="left",nt=[J,tt,et,it],st=nt.reduce((function(t,e){return t.concat([e+"-start",e+"-end"])}),[]),ot=[].concat(nt,["auto"]).reduce((function(t,e){return t.concat([e,e+"-start",e+"-end"])}),[]),rt=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function at(t){return t?(t.nodeName||"").toLowerCase():null}function lt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function ct(t){return t instanceof lt(t).Element||t instanceof Element}function dt(t){return t instanceof lt(t).HTMLElement||t instanceof HTMLElement}function ht(t){return"undefined"!=typeof ShadowRoot&&(t instanceof lt(t).ShadowRoot||t instanceof ShadowRoot)}var ut={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];dt(s)&&at(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});dt(n)&&at(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function ft(t){return t.split("-")[0]}function pt(t){var e=t.getBoundingClientRect();return{width:e.width,height:e.height,top:e.top,right:e.right,bottom:e.bottom,left:e.left,x:e.left,y:e.top}}function gt(t){var e=pt(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function mt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&ht(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function _t(t){return lt(t).getComputedStyle(t)}function bt(t){return["table","td","th"].indexOf(at(t))>=0}function vt(t){return((ct(t)?t.ownerDocument:t.document)||window.document).documentElement}function yt(t){return"html"===at(t)?t:t.assignedSlot||t.parentNode||(ht(t)?t.host:null)||vt(t)}function wt(t){return dt(t)&&"fixed"!==_t(t).position?t.offsetParent:null}function Et(t){for(var e=lt(t),i=wt(t);i&&bt(i)&&"static"===_t(i).position;)i=wt(i);return i&&("html"===at(i)||"body"===at(i)&&"static"===_t(i).position)?e:i||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&dt(t)&&"fixed"===_t(t).position)return null;for(var i=yt(t);dt(i)&&["html","body"].indexOf(at(i))<0;){var n=_t(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Tt(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var At=Math.max,Lt=Math.min,Ot=Math.round;function kt(t,e,i){return At(t,Lt(e,i))}function Dt(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function xt(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}var Ct={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=ft(i.placement),l=Tt(a),c=[it,et].indexOf(a)>=0?"height":"width";if(o&&r){var d=function(t,e){return Dt("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:xt(t,nt))}(s.padding,i),h=gt(o),u="y"===l?J:it,f="y"===l?tt:et,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],g=r[l]-i.rects.reference[l],m=Et(o),_=m?"y"===l?m.clientHeight||0:m.clientWidth||0:0,b=p/2-g/2,v=d[u],y=_-h[c]-d[f],w=_/2-h[c]/2+b,E=kt(v,w,y),T=l;i.modifiersData[n]=((e={})[T]=E,e.centerOffset=E-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&mt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]},St={top:"auto",right:"auto",bottom:"auto",left:"auto"};function Nt(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.offsets,r=t.position,a=t.gpuAcceleration,l=t.adaptive,c=t.roundOffsets,d=!0===c?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:Ot(Ot(e*n)/n)||0,y:Ot(Ot(i*n)/n)||0}}(o):"function"==typeof c?c(o):o,h=d.x,u=void 0===h?0:h,f=d.y,p=void 0===f?0:f,g=o.hasOwnProperty("x"),m=o.hasOwnProperty("y"),_=it,b=J,v=window;if(l){var y=Et(i),w="clientHeight",E="clientWidth";y===lt(i)&&"static"!==_t(y=vt(i)).position&&(w="scrollHeight",E="scrollWidth"),y=y,s===J&&(b=tt,p-=y[w]-n.height,p*=a?1:-1),s===it&&(_=et,u-=y[E]-n.width,u*=a?1:-1)}var T,A=Object.assign({position:r},l&&St);return a?Object.assign({},A,((T={})[b]=m?"0":"",T[_]=g?"0":"",T.transform=(v.devicePixelRatio||1)<2?"translate("+u+"px, "+p+"px)":"translate3d("+u+"px, "+p+"px, 0)",T)):Object.assign({},A,((e={})[b]=m?p+"px":"",e[_]=g?u+"px":"",e.transform="",e))}var jt={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:ft(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,Nt(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,Nt(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}},Pt={passive:!0},It={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=lt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,Pt)})),a&&l.addEventListener("resize",i.update,Pt),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,Pt)})),a&&l.removeEventListener("resize",i.update,Pt)}},data:{}},Mt={left:"right",right:"left",bottom:"top",top:"bottom"};function Ht(t){return t.replace(/left|right|bottom|top/g,(function(t){return Mt[t]}))}var Rt={start:"end",end:"start"};function Bt(t){return t.replace(/start|end/g,(function(t){return Rt[t]}))}function Wt(t){var e=lt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function zt(t){return pt(vt(t)).left+Wt(t).scrollLeft}function Ut(t){var e=_t(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function $t(t,e){var i;void 0===e&&(e=[]);var n=function t(e){return["html","body","#document"].indexOf(at(e))>=0?e.ownerDocument.body:dt(e)&&Ut(e)?e:t(yt(e))}(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=lt(n),r=s?[o].concat(o.visualViewport||[],Ut(n)?n:[]):n,a=e.concat(r);return s?a:a.concat($t(yt(r)))}function Ft(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Kt(t,e){return"viewport"===e?Ft(function(t){var e=lt(t),i=vt(t),n=e.visualViewport,s=i.clientWidth,o=i.clientHeight,r=0,a=0;return n&&(s=n.width,o=n.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=n.offsetLeft,a=n.offsetTop)),{width:s,height:o,x:r+zt(t),y:a}}(t)):dt(e)?function(t){var e=pt(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Ft(function(t){var e,i=vt(t),n=Wt(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=At(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=At(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+zt(t),l=-n.scrollTop;return"rtl"===_t(s||i).direction&&(a+=At(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(vt(t)))}function Yt(t){return t.split("-")[1]}function qt(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?ft(s):null,r=s?Yt(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case J:e={x:a,y:i.y-n.height};break;case tt:e={x:a,y:i.y+i.height};break;case et:e={x:i.x+i.width,y:l};break;case it:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?Tt(o):null;if(null!=c){var d="y"===c?"height":"width";switch(r){case"start":e[c]=e[c]-(i[d]/2-n[d]/2);break;case"end":e[c]=e[c]+(i[d]/2-n[d]/2)}}return e}function Vt(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.boundary,r=void 0===o?"clippingParents":o,a=i.rootBoundary,l=void 0===a?"viewport":a,c=i.elementContext,d=void 0===c?"popper":c,h=i.altBoundary,u=void 0!==h&&h,f=i.padding,p=void 0===f?0:f,g=Dt("number"!=typeof p?p:xt(p,nt)),m="popper"===d?"reference":"popper",_=t.elements.reference,b=t.rects.popper,v=t.elements[u?m:d],y=function(t,e,i){var n="clippingParents"===e?function(t){var e=$t(yt(t)),i=["absolute","fixed"].indexOf(_t(t).position)>=0&&dt(t)?Et(t):t;return ct(i)?e.filter((function(t){return ct(t)&&mt(t,i)&&"body"!==at(t)})):[]}(t):[].concat(e),s=[].concat(n,[i]),o=s[0],r=s.reduce((function(e,i){var n=Kt(t,i);return e.top=At(n.top,e.top),e.right=Lt(n.right,e.right),e.bottom=Lt(n.bottom,e.bottom),e.left=At(n.left,e.left),e}),Kt(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}(ct(v)?v:v.contextElement||vt(t.elements.popper),r,l),w=pt(_),E=qt({reference:w,element:b,strategy:"absolute",placement:s}),T=Ft(Object.assign({},b,E)),A="popper"===d?T:w,L={top:y.top-A.top+g.top,bottom:A.bottom-y.bottom+g.bottom,left:y.left-A.left+g.left,right:A.right-y.right+g.right},O=t.modifiersData.offset;if("popper"===d&&O){var k=O[s];Object.keys(L).forEach((function(t){var e=[et,tt].indexOf(t)>=0?1:-1,i=[J,tt].indexOf(t)>=0?"y":"x";L[t]+=k[i]*e}))}return L}function Xt(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?ot:l,d=Yt(n),h=d?a?st:st.filter((function(t){return Yt(t)===d})):nt,u=h.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=h);var f=u.reduce((function(e,i){return e[i]=Vt(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[ft(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}var Qt={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,d=i.boundary,h=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,g=i.allowedAutoPlacements,m=e.options.placement,_=ft(m),b=l||(_!==m&&p?function(t){if("auto"===ft(t))return[];var e=Ht(t);return[Bt(t),e,Bt(e)]}(m):[Ht(m)]),v=[m].concat(b).reduce((function(t,i){return t.concat("auto"===ft(i)?Xt(e,{placement:i,boundary:d,rootBoundary:h,padding:c,flipVariations:p,allowedAutoPlacements:g}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,T=!0,A=v[0],L=0;L=0,C=x?"width":"height",S=Vt(e,{placement:O,boundary:d,rootBoundary:h,altBoundary:u,padding:c}),N=x?D?et:it:D?tt:J;y[C]>w[C]&&(N=Ht(N));var j=Ht(N),P=[];if(o&&P.push(S[k]<=0),a&&P.push(S[N]<=0,S[j]<=0),P.every((function(t){return t}))){A=O,T=!1;break}E.set(O,P)}if(T)for(var I=function(t){var e=v.find((function(e){var i=E.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return A=e,"break"},M=p?3:1;M>0&&"break"!==I(M);M--);e.placement!==A&&(e.modifiersData[n]._skip=!0,e.placement=A,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function Gt(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Zt(t){return[J,et,tt,it].some((function(e){return t[e]>=0}))}var Jt={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=Vt(e,{elementContext:"reference"}),a=Vt(e,{altBoundary:!0}),l=Gt(r,n),c=Gt(a,s,o),d=Zt(l),h=Zt(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:d,hasPopperEscaped:h},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":d,"data-popper-escaped":h})}},te={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=ot.reduce((function(t,i){return t[i]=function(t,e,i){var n=ft(t),s=[it,J].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[it,et].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},ee={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=qt({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},ie={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,d=i.altBoundary,h=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,g=void 0===p?0:p,m=Vt(e,{boundary:l,rootBoundary:c,padding:h,altBoundary:d}),_=ft(e.placement),b=Yt(e.placement),v=!b,y=Tt(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,T=e.rects.reference,A=e.rects.popper,L="function"==typeof g?g(Object.assign({},e.rects,{placement:e.placement})):g,O={x:0,y:0};if(E){if(o||a){var k="y"===y?J:it,D="y"===y?tt:et,x="y"===y?"height":"width",C=E[y],S=E[y]+m[k],N=E[y]-m[D],j=f?-A[x]/2:0,P="start"===b?T[x]:A[x],I="start"===b?-A[x]:-T[x],M=e.elements.arrow,H=f&&M?gt(M):{width:0,height:0},R=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},B=R[k],W=R[D],z=kt(0,T[x],H[x]),U=v?T[x]/2-j-z-B-L:P-z-B-L,$=v?-T[x]/2+j+z+W+L:I+z+W+L,F=e.elements.arrow&&Et(e.elements.arrow),K=F?"y"===y?F.clientTop||0:F.clientLeft||0:0,Y=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,q=E[y]+U-Y-K,V=E[y]+$-Y;if(o){var X=kt(f?Lt(S,q):S,C,f?At(N,V):N);E[y]=X,O[y]=X-C}if(a){var Q="x"===y?J:it,G="x"===y?tt:et,Z=E[w],nt=Z+m[Q],st=Z-m[G],ot=kt(f?Lt(nt,q):nt,Z,f?At(st,V):st);E[w]=ot,O[w]=ot-Z}}e.modifiersData[n]=O}},requiresIfExists:["offset"]};function ne(t,e,i){void 0===i&&(i=!1);var n,s,o=vt(e),r=pt(t),a=dt(e),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(a||!a&&!i)&&(("body"!==at(e)||Ut(o))&&(l=(n=e)!==lt(n)&&dt(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:Wt(n)),dt(e)?((c=pt(e)).x+=e.clientLeft,c.y+=e.clientTop):o&&(c.x=zt(o))),{x:r.left+l.scrollLeft-c.x,y:r.top+l.scrollTop-c.y,width:r.width,height:r.height}}var se={placement:"bottom",modifiers:[],strategy:"absolute"};function oe(){for(var t=arguments.length,e=new Array(t),i=0;i"applyStyles"===t.name&&!1===t.enabled);this._popper=ce(e,this._menu,i),n&&z.setDataAttribute(this._menu,"popper","static")}"ontouchstart"in document.documentElement&&!t.closest(".navbar-nav")&&[].concat(...document.body.children).forEach(t=>I.on(t,"mouseover",u)),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.toggle("show"),this._element.classList.toggle("show"),I.trigger(this._element,"shown.bs.dropdown",e)}}hide(){if(d(this._element)||!this._menu.classList.contains("show"))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._menu=null,this._popper&&(this._popper.destroy(),this._popper=null),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_addEventListeners(){I.on(this._element,"click.bs.dropdown",t=>{t.preventDefault(),this.toggle()})}_completeHide(t){I.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>I.off(t,"mouseover",u)),this._popper&&this._popper.destroy(),this._menu.classList.remove("show"),this._element.classList.remove("show"),this._element.setAttribute("aria-expanded","false"),z.removeDataAttribute(this._menu,"popper"),I.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...z.getDataAttributes(this._element),...t},l("dropdown",t,this.constructor.DefaultType),"object"==typeof t.reference&&!r(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError("dropdown".toUpperCase()+': Option "reference" provided type "object" without a required "getBoundingClientRect" method.');return t}_getMenuElement(){return U.next(this._element,".dropdown-menu")[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return me;if(t.classList.contains("dropstart"))return _e;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?fe:ue:e?ge:pe}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem(t){const e=U.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(c);if(!e.length)return;let i=e.indexOf(t.target);"ArrowUp"===t.key&&i>0&&i--,"ArrowDown"===t.key&&ithis.matches('[data-bs-toggle="dropdown"]')?this:U.prev(this,'[data-bs-toggle="dropdown"]')[0];if("Escape"===t.key)return i().focus(),void ye.clearMenus();e||"ArrowUp"!==t.key&&"ArrowDown"!==t.key?e&&"Space"!==t.key?ye.getInstance(i())._selectMenuItem(t):ye.clearMenus():i().click()}}I.on(document,"keydown.bs.dropdown.data-api",'[data-bs-toggle="dropdown"]',ye.dataApiKeydownHandler),I.on(document,"keydown.bs.dropdown.data-api",".dropdown-menu",ye.dataApiKeydownHandler),I.on(document,"click.bs.dropdown.data-api",ye.clearMenus),I.on(document,"keyup.bs.dropdown.data-api",ye.clearMenus),I.on(document,"click.bs.dropdown.data-api",'[data-bs-toggle="dropdown"]',(function(t){t.preventDefault(),ye.dropdownInterface(this)})),m("dropdown",ye);const we=()=>{const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)},Ee=(t=we())=>{Te(),Ae("body","paddingRight",e=>e+t),Ae(".fixed-top, .fixed-bottom, .is-fixed, .sticky-top","paddingRight",e=>e+t),Ae(".sticky-top","marginRight",e=>e-t)},Te=()=>{const t=document.body.style.overflow;t&&z.setDataAttribute(document.body,"overflow",t),document.body.style.overflow="hidden"},Ae=(t,e,i)=>{const n=we();U.find(t).forEach(t=>{if(t!==document.body&&window.innerWidth>t.clientWidth+n)return;const s=t.style[e],o=window.getComputedStyle(t)[e];z.setDataAttribute(t,e,s),t.style[e]=i(Number.parseFloat(o))+"px"})},Le=()=>{Oe("body","overflow"),Oe("body","paddingRight"),Oe(".fixed-top, .fixed-bottom, .is-fixed, .sticky-top","paddingRight"),Oe(".sticky-top","marginRight")},Oe=(t,e)=>{U.find(t).forEach(t=>{const i=z.getDataAttribute(t,e);void 0===i?t.style.removeProperty(e):(z.removeDataAttribute(t,e),t.style[e]=i)})},ke={isVisible:!0,isAnimated:!1,rootElement:document.body,clickCallback:null},De={isVisible:"boolean",isAnimated:"boolean",rootElement:"element",clickCallback:"(function|null)"};class xe{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&f(this._getElement()),this._getElement().classList.add("show"),this._emulateAnimation(()=>{_(t)})):_(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove("show"),this._emulateAnimation(()=>{this.dispose(),_(t)})):_(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className="modal-backdrop",this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return t={...ke,..."object"==typeof t?t:{}},l("backdrop",t,De),t}_append(){this._isAppended||(this._config.rootElement.appendChild(this._getElement()),I.on(this._getElement(),"mousedown.bs.backdrop",()=>{_(this._config.clickCallback)}),this._isAppended=!0)}dispose(){this._isAppended&&(I.off(this._element,"mousedown.bs.backdrop"),this._getElement().parentNode.removeChild(this._element),this._isAppended=!1)}_emulateAnimation(t){if(!this._config.isAnimated)return void _(t);const e=s(this._getElement());I.one(this._getElement(),"transitionend",()=>_(t)),a(this._getElement(),e)}}const Ce={backdrop:!0,keyboard:!0,focus:!0},Se={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"};class Ne extends M{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=U.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1}static get Default(){return Ce}static get DATA_KEY(){return"bs.modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){if(this._isShown||this._isTransitioning)return;this._isAnimated()&&(this._isTransitioning=!0);const e=I.trigger(this._element,"show.bs.modal",{relatedTarget:t});this._isShown||e.defaultPrevented||(this._isShown=!0,Ee(),document.body.classList.add("modal-open"),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),I.on(this._element,"click.dismiss.bs.modal",'[data-bs-dismiss="modal"]',t=>this.hide(t)),I.on(this._dialog,"mousedown.dismiss.bs.modal",()=>{I.one(this._element,"mouseup.dismiss.bs.modal",t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)})}),this._showBackdrop(()=>this._showElement(t)))}hide(t){if(t&&t.preventDefault(),!this._isShown||this._isTransitioning)return;if(I.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const e=this._isAnimated();if(e&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),I.off(document,"focusin.bs.modal"),this._element.classList.remove("show"),I.off(this._element,"click.dismiss.bs.modal"),I.off(this._dialog,"mousedown.dismiss.bs.modal"),e){const t=s(this._element);I.one(this._element,"transitionend",t=>this._hideModal(t)),a(this._element,t)}else this._hideModal()}dispose(){[window,this._dialog].forEach(t=>I.off(t,".bs.modal")),super.dispose(),I.off(document,"focusin.bs.modal"),this._config=null,this._dialog=null,this._backdrop.dispose(),this._backdrop=null,this._isShown=null,this._ignoreBackdropClick=null,this._isTransitioning=null}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new xe({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_getConfig(t){return t={...Ce,...z.getDataAttributes(this._element),...t},l("modal",t,Se),t}_showElement(t){const e=this._isAnimated(),i=U.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,i&&(i.scrollTop=0),e&&f(this._element),this._element.classList.add("show"),this._config.focus&&this._enforceFocus();const n=()=>{this._config.focus&&this._element.focus(),this._isTransitioning=!1,I.trigger(this._element,"shown.bs.modal",{relatedTarget:t})};if(e){const t=s(this._dialog);I.one(this._dialog,"transitionend",n),a(this._dialog,t)}else n()}_enforceFocus(){I.off(document,"focusin.bs.modal"),I.on(document,"focusin.bs.modal",t=>{document===t.target||this._element===t.target||this._element.contains(t.target)||this._element.focus()})}_setEscapeEvent(){this._isShown?I.on(this._element,"keydown.dismiss.bs.modal",t=>{this._config.keyboard&&"Escape"===t.key?(t.preventDefault(),this.hide()):this._config.keyboard||"Escape"!==t.key||this._triggerBackdropTransition()}):I.off(this._element,"keydown.dismiss.bs.modal")}_setResizeEvent(){this._isShown?I.on(window,"resize.bs.modal",()=>this._adjustDialog()):I.off(window,"resize.bs.modal")}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide(()=>{document.body.classList.remove("modal-open"),this._resetAdjustments(),Le(),I.trigger(this._element,"hidden.bs.modal")})}_showBackdrop(t){I.on(this._element,"click.dismiss.bs.modal",t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())}),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(I.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight;t||(this._element.style.overflowY="hidden"),this._element.classList.add("modal-static");const e=s(this._dialog);I.off(this._element,"transitionend"),I.one(this._element,"transitionend",()=>{this._element.classList.remove("modal-static"),t||(I.one(this._element,"transitionend",()=>{this._element.style.overflowY=""}),a(this._element,e))}),a(this._element,e),this._element.focus()}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=we(),i=e>0;(!i&&t&&!g()||i&&!t&&g())&&(this._element.style.paddingLeft=e+"px"),(i&&!t&&!g()||!i&&t&&g())&&(this._element.style.paddingRight=e+"px")}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Ne.getInstance(this)||new Ne(this,"object"==typeof t?t:{});if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}I.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=n(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),I.one(e,"show.bs.modal",t=>{t.defaultPrevented||I.one(e,"hidden.bs.modal",()=>{c(this)&&this.focus()})}),(Ne.getInstance(e)||new Ne(e)).toggle(this)})),m("modal",Ne);const je={backdrop:!0,keyboard:!0,scroll:!1},Pe={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"};class Ie extends M{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._addEventListeners()}static get Default(){return je}static get DATA_KEY(){return"bs.offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){if(this._isShown)return;if(I.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented)return;this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||(Ee(),this._enforceFocusOnElement(this._element)),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add("show");const e=s(this._element);I.one(this._element,"transitionend",()=>{I.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),a(this._element,e)}hide(){if(!this._isShown)return;if(I.trigger(this._element,"hide.bs.offcanvas").defaultPrevented)return;I.off(document,"focusin.bs.offcanvas"),this._element.blur(),this._isShown=!1,this._element.classList.remove("show"),this._backdrop.hide();const t=s(this._element);I.one(this._element,"transitionend",()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||Le(),I.trigger(this._element,"hidden.bs.offcanvas")}),a(this._element,t)}dispose(){this._backdrop.dispose(),super.dispose(),I.off(document,"focusin.bs.offcanvas"),this._config=null,this._backdrop=null}_getConfig(t){return t={...je,...z.getDataAttributes(this._element),..."object"==typeof t?t:{}},l("offcanvas",t,Pe),t}_initializeBackDrop(){return new xe({isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_enforceFocusOnElement(t){I.off(document,"focusin.bs.offcanvas"),I.on(document,"focusin.bs.offcanvas",e=>{document===e.target||t===e.target||t.contains(e.target)||t.focus()}),t.focus()}_addEventListeners(){I.on(this._element,"click.dismiss.bs.offcanvas",'[data-bs-dismiss="offcanvas"]',()=>this.hide()),I.on(this._element,"keydown.dismiss.bs.offcanvas",t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()})}static jQueryInterface(t){return this.each((function(){const e=v.get(this,"bs.offcanvas")||new Ie(this,"object"==typeof t?t:{});if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}I.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=n(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),d(this))return;I.one(e,"hidden.bs.offcanvas",()=>{c(this)&&this.focus()});const i=U.findOne(".offcanvas.show");i&&i!==e&&Ie.getInstance(i).hide(),(v.get(e,"bs.offcanvas")||new Ie(e)).toggle(this)})),I.on(window,"load.bs.offcanvas.data-api",()=>{U.find(".offcanvas.show").forEach(t=>(v.get(t,"bs.offcanvas")||new Ie(t)).show())}),m("offcanvas",Ie);const Me=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),He=/^(?:(?:https?|mailto|ftp|tel|file):|[^#&/:?]*(?:[#/?]|$))/i,Re=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Be=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!Me.has(i)||Boolean(He.test(t.nodeValue)||Re.test(t.nodeValue));const n=e.filter(t=>t instanceof RegExp);for(let t=0,e=n.length;t{Be(t,a)||i.removeAttribute(t.nodeName)})}return n.body.innerHTML}const ze=new RegExp("(^|\\s)bs-tooltip\\S+","g"),Ue=new Set(["sanitize","allowList","sanitizeFn"]),$e={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},Fe={AUTO:"auto",TOP:"top",RIGHT:g()?"left":"right",BOTTOM:"bottom",LEFT:g()?"right":"left"},Ke={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},Ye={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"};class qe extends M{constructor(t,e){if(void 0===de)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return Ke}static get NAME(){return"tooltip"}static get DATA_KEY(){return"bs.tooltip"}static get Event(){return Ye}static get EVENT_KEY(){return".bs.tooltip"}static get DefaultType(){return $e}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains("show"))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),I.off(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this.tip&&this.tip.parentNode&&this.tip.parentNode.removeChild(this.tip),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.config=null,this.tip=null,super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const e=I.trigger(this._element,this.constructor.Event.SHOW),i=h(this._element),n=null===i?this._element.ownerDocument.documentElement.contains(this._element):i.contains(this._element);if(e.defaultPrevented||!n)return;const o=this.getTipElement(),r=t(this.constructor.NAME);o.setAttribute("id",r),this._element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&o.classList.add("fade");const l="function"==typeof this.config.placement?this.config.placement.call(this,o,this._element):this.config.placement,c=this._getAttachment(l);this._addAttachmentClass(c);const d=this._getContainer();v.set(o,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(d.appendChild(o),I.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=ce(this._element,o,this._getPopperConfig(c)),o.classList.add("show");const f="function"==typeof this.config.customClass?this.config.customClass():this.config.customClass;f&&o.classList.add(...f.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>{I.on(t,"mouseover",u)});const p=()=>{const t=this._hoverState;this._hoverState=null,I.trigger(this._element,this.constructor.Event.SHOWN),"out"===t&&this._leave(null,this)};if(this.tip.classList.contains("fade")){const t=s(this.tip);I.one(this.tip,"transitionend",p),a(this.tip,t)}else p()}hide(){if(!this._popper)return;const t=this.getTipElement(),e=()=>{this._isWithActiveTrigger()||("show"!==this._hoverState&&t.parentNode&&t.parentNode.removeChild(t),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),I.trigger(this._element,this.constructor.Event.HIDDEN),this._popper&&(this._popper.destroy(),this._popper=null))};if(!I.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented){if(t.classList.remove("show"),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>I.off(t,"mouseover",u)),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,this.tip.classList.contains("fade")){const i=s(t);I.one(t,"transitionend",e),a(t,i)}else e();this._hoverState=""}}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");return t.innerHTML=this.config.template,this.tip=t.children[0],this.tip}setContent(){const t=this.getTipElement();this.setElementContent(U.findOne(".tooltip-inner",t),this.getTitle()),t.classList.remove("fade","show")}setElementContent(t,e){if(null!==t)return"object"==typeof e&&r(e)?(e.jquery&&(e=e[0]),void(this.config.html?e.parentNode!==t&&(t.innerHTML="",t.appendChild(e)):t.textContent=e.textContent)):void(this.config.html?(this.config.sanitize&&(e=We(e,this.config.allowList,this.config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){let t=this._element.getAttribute("data-bs-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this._element):this.config.title),t}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){const i=this.constructor.DATA_KEY;return(e=e||v.get(t.delegateTarget,i))||(e=new this.constructor(t.delegateTarget,this._getDelegateConfig()),v.set(t.delegateTarget,i,e)),e}_getOffset(){const{offset:t}=this.config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this.config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this.config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this.config.popperConfig?this.config.popperConfig(e):this.config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add("bs-tooltip-"+this.updateAttachment(t))}_getContainer(){return!1===this.config.container?document.body:r(this.config.container)?this.config.container:U.findOne(this.config.container)}_getAttachment(t){return Fe[t.toUpperCase()]}_setListeners(){this.config.trigger.split(" ").forEach(t=>{if("click"===t)I.on(this._element,this.constructor.Event.CLICK,this.config.selector,t=>this.toggle(t));else if("manual"!==t){const e="hover"===t?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i="hover"===t?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;I.on(this._element,e,this.config.selector,t=>this._enter(t)),I.on(this._element,i,this.config.selector,t=>this._leave(t))}}),this._hideModalHandler=()=>{this._element&&this.hide()},I.on(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this.config.selector?this.config={...this.config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?"focus":"hover"]=!0),e.getTipElement().classList.contains("show")||"show"===e._hoverState?e._hoverState="show":(clearTimeout(e._timeout),e._hoverState="show",e.config.delay&&e.config.delay.show?e._timeout=setTimeout(()=>{"show"===e._hoverState&&e.show()},e.config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?"focus":"hover"]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState="out",e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(()=>{"out"===e._hoverState&&e.hide()},e.config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=z.getDataAttributes(this._element);return Object.keys(e).forEach(t=>{Ue.has(t)&&delete e[t]}),t&&"object"==typeof t.container&&t.container.jquery&&(t.container=t.container[0]),"number"==typeof(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),l("tooltip",t,this.constructor.DefaultType),t.sanitize&&(t.template=We(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};if(this.config)for(const e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=t.getAttribute("class").match(ze);null!==e&&e.length>0&&e.map(t=>t.trim()).forEach(e=>t.classList.remove(e))}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}static jQueryInterface(t){return this.each((function(){let e=v.get(this,"bs.tooltip");const i="object"==typeof t&&t;if((e||!/dispose|hide/.test(t))&&(e||(e=new qe(this,i)),"string"==typeof t)){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m("tooltip",qe);const Ve=new RegExp("(^|\\s)bs-popover\\S+","g"),Xe={...qe.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},Qe={...qe.DefaultType,content:"(string|element|function)"},Ge={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class Ze extends qe{static get Default(){return Xe}static get NAME(){return"popover"}static get DATA_KEY(){return"bs.popover"}static get Event(){return Ge}static get EVENT_KEY(){return".bs.popover"}static get DefaultType(){return Qe}isWithContent(){return this.getTitle()||this._getContent()}setContent(){const t=this.getTipElement();this.setElementContent(U.findOne(".popover-header",t),this.getTitle());let e=this._getContent();"function"==typeof e&&(e=e.call(this._element)),this.setElementContent(U.findOne(".popover-body",t),e),t.classList.remove("fade","show")}_addAttachmentClass(t){this.getTipElement().classList.add("bs-popover-"+this.updateAttachment(t))}_getContent(){return this._element.getAttribute("data-bs-content")||this.config.content}_cleanTipClass(){const t=this.getTipElement(),e=t.getAttribute("class").match(Ve);null!==e&&e.length>0&&e.map(t=>t.trim()).forEach(e=>t.classList.remove(e))}static jQueryInterface(t){return this.each((function(){let e=v.get(this,"bs.popover");const i="object"==typeof t?t:null;if((e||!/dispose|hide/.test(t))&&(e||(e=new Ze(this,i),v.set(this,"bs.popover",e)),"string"==typeof t)){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m("popover",Ze);const Je={offset:10,method:"auto",target:""},ti={offset:"number",method:"string",target:"(string|element)"};class ei extends M{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._selector=`${this._config.target} .nav-link, ${this._config.target} .list-group-item, ${this._config.target} .dropdown-item`,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,I.on(this._scrollElement,"scroll.bs.scrollspy",()=>this._process()),this.refresh(),this._process()}static get Default(){return Je}static get DATA_KEY(){return"bs.scrollspy"}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":"position",e="auto"===this._config.method?t:this._config.method,n="position"===e?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),U.find(this._selector).map(t=>{const s=i(t),o=s?U.findOne(s):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[z[e](o).top+n,s]}return null}).filter(t=>t).sort((t,e)=>t[0]-e[0]).forEach(t=>{this._offsets.push(t[0]),this._targets.push(t[1])})}dispose(){super.dispose(),I.off(this._scrollElement,".bs.scrollspy"),this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null}_getConfig(e){if("string"!=typeof(e={...Je,...z.getDataAttributes(this._element),..."object"==typeof e&&e?e:{}}).target&&r(e.target)){let{id:i}=e.target;i||(i=t("scrollspy"),e.target.id=i),e.target="#"+i}return l("scrollspy",e,ti),e}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`),i=U.findOne(e.join(","));i.classList.contains("dropdown-item")?(U.findOne(".dropdown-toggle",i.closest(".dropdown")).classList.add("active"),i.classList.add("active")):(i.classList.add("active"),U.parents(i,".nav, .list-group").forEach(t=>{U.prev(t,".nav-link, .list-group-item").forEach(t=>t.classList.add("active")),U.prev(t,".nav-item").forEach(t=>{U.children(t,".nav-link").forEach(t=>t.classList.add("active"))})})),I.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){U.find(this._selector).filter(t=>t.classList.contains("active")).forEach(t=>t.classList.remove("active"))}static jQueryInterface(t){return this.each((function(){const e=ei.getInstance(this)||new ei(this,"object"==typeof t?t:{});if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}I.on(window,"load.bs.scrollspy.data-api",()=>{U.find('[data-bs-spy="scroll"]').forEach(t=>new ei(t))}),m("scrollspy",ei);class ii extends M{static get DATA_KEY(){return"bs.tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains("active"))return;let t;const e=n(this._element),i=this._element.closest(".nav, .list-group");if(i){const e="UL"===i.nodeName||"OL"===i.nodeName?":scope > li > .active":".active";t=U.find(e,i),t=t[t.length-1]}const s=t?I.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(I.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==s&&s.defaultPrevented)return;this._activate(this._element,i);const o=()=>{I.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),I.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,i){const n=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?U.children(e,".active"):U.find(":scope > li > .active",e))[0],o=i&&n&&n.classList.contains("fade"),r=()=>this._transitionComplete(t,n,i);if(n&&o){const t=s(n);n.classList.remove("show"),I.one(n,"transitionend",r),a(n,t)}else r()}_transitionComplete(t,e,i){if(e){e.classList.remove("active");const t=U.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove("active"),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add("active"),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),f(t),t.classList.contains("fade")&&t.classList.add("show");let n=t.parentNode;if(n&&"LI"===n.nodeName&&(n=n.parentNode),n&&n.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&U.find(".dropdown-toggle",e).forEach(t=>t.classList.add("active")),t.setAttribute("aria-expanded",!0)}i&&i()}static jQueryInterface(t){return this.each((function(){const e=v.get(this,"bs.tab")||new ii(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}I.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),d(this)||(v.get(this,"bs.tab")||new ii(this)).show()})),m("tab",ii);const ni={animation:"boolean",autohide:"boolean",delay:"number"},si={animation:!0,autohide:!0,delay:5e3};class oi extends M{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._setListeners()}static get DefaultType(){return ni}static get Default(){return si}static get DATA_KEY(){return"bs.toast"}show(){if(I.trigger(this._element,"show.bs.toast").defaultPrevented)return;this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");const t=()=>{this._element.classList.remove("showing"),this._element.classList.add("show"),I.trigger(this._element,"shown.bs.toast"),this._config.autohide&&(this._timeout=setTimeout(()=>{this.hide()},this._config.delay))};if(this._element.classList.remove("hide"),f(this._element),this._element.classList.add("showing"),this._config.animation){const e=s(this._element);I.one(this._element,"transitionend",t),a(this._element,e)}else t()}hide(){if(!this._element.classList.contains("show"))return;if(I.trigger(this._element,"hide.bs.toast").defaultPrevented)return;const t=()=>{this._element.classList.add("hide"),I.trigger(this._element,"hidden.bs.toast")};if(this._element.classList.remove("show"),this._config.animation){const e=s(this._element);I.one(this._element,"transitionend",t),a(this._element,e)}else t()}dispose(){this._clearTimeout(),this._element.classList.contains("show")&&this._element.classList.remove("show"),super.dispose(),this._config=null}_getConfig(t){return t={...si,...z.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},l("toast",t,this.constructor.DefaultType),t}_setListeners(){I.on(this._element,"click.dismiss.bs.toast",'[data-bs-dismiss="toast"]',()=>this.hide())}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){let e=v.get(this,"bs.toast");if(e||(e=new oi(this,"object"==typeof t&&t)),"string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return m("toast",oi),{Alert:H,Button:R,Carousel:X,Collapse:Z,Dropdown:ye,Modal:Ne,Offcanvas:Ie,Popover:Ze,ScrollSpy:ei,Tab:ii,Toast:oi,Tooltip:qe}})); -//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/src_assets/common/assets/web/third_party/bootstrap.min.css b/src_assets/common/assets/web/third_party/bootstrap.min.css deleted file mode 100644 index 08ef66a2..00000000 --- a/src_assets/common/assets/web/third_party/bootstrap.min.css +++ /dev/null @@ -1,7 +0,0 @@ -@charset "UTF-8";/*! - * Bootstrap v5.0.0 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors - * Copyright 2011-2021 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0))}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-font-sans-serif);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x)/ -2);margin-left:calc(var(--bs-gutter-x)/ -2)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x)/ 2);padding-left:calc(var(--bs-gutter-x)/ 2);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.3333333333%}.col-2{flex:0 0 auto;width:16.6666666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.3333333333%}.col-5{flex:0 0 auto;width:41.6666666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.3333333333%}.col-8{flex:0 0 auto;width:66.6666666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.3333333333%}.col-11{flex:0 0 auto;width:91.6666666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.3333333333%}.offset-2{margin-left:16.6666666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.3333333333%}.offset-5{margin-left:41.6666666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.3333333333%}.offset-8{margin-left:66.6666666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.3333333333%}.offset-11{margin-left:91.6666666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.3333333333%}.col-sm-2{flex:0 0 auto;width:16.6666666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.3333333333%}.col-sm-5{flex:0 0 auto;width:41.6666666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.3333333333%}.col-sm-8{flex:0 0 auto;width:66.6666666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.3333333333%}.col-sm-11{flex:0 0 auto;width:91.6666666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.3333333333%}.offset-sm-2{margin-left:16.6666666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.3333333333%}.offset-sm-5{margin-left:41.6666666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.3333333333%}.offset-sm-8{margin-left:66.6666666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.3333333333%}.offset-sm-11{margin-left:91.6666666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.3333333333%}.col-md-2{flex:0 0 auto;width:16.6666666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.3333333333%}.col-md-5{flex:0 0 auto;width:41.6666666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.3333333333%}.col-md-8{flex:0 0 auto;width:66.6666666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.3333333333%}.col-md-11{flex:0 0 auto;width:91.6666666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.3333333333%}.offset-md-2{margin-left:16.6666666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.3333333333%}.offset-md-5{margin-left:41.6666666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.3333333333%}.offset-md-8{margin-left:66.6666666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.3333333333%}.offset-md-11{margin-left:91.6666666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.3333333333%}.col-lg-2{flex:0 0 auto;width:16.6666666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.3333333333%}.col-lg-5{flex:0 0 auto;width:41.6666666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.3333333333%}.col-lg-8{flex:0 0 auto;width:66.6666666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.3333333333%}.col-lg-11{flex:0 0 auto;width:91.6666666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.3333333333%}.offset-lg-2{margin-left:16.6666666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.3333333333%}.offset-lg-5{margin-left:41.6666666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.3333333333%}.offset-lg-8{margin-left:66.6666666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.3333333333%}.offset-lg-11{margin-left:91.6666666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.3333333333%}.col-xl-2{flex:0 0 auto;width:16.6666666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.3333333333%}.col-xl-5{flex:0 0 auto;width:41.6666666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.3333333333%}.col-xl-8{flex:0 0 auto;width:66.6666666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.3333333333%}.col-xl-11{flex:0 0 auto;width:91.6666666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.3333333333%}.offset-xl-2{margin-left:16.6666666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.3333333333%}.offset-xl-5{margin-left:41.6666666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.3333333333%}.offset-xl-8{margin-left:66.6666666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.3333333333%}.offset-xl-11{margin-left:91.6666666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.3333333333%}.col-xxl-2{flex:0 0 auto;width:16.6666666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.3333333333%}.col-xxl-5{flex:0 0 auto;width:41.6666666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.3333333333%}.col-xxl-8{flex:0 0 auto;width:66.6666666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.3333333333%}.col-xxl-11{flex:0 0 auto;width:91.6666666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.3333333333%}.offset-xxl-2{margin-left:16.6666666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.3333333333%}.offset-xxl-5{margin-left:41.6666666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.3333333333%}.offset-xxl-8{margin-left:66.6666666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.3333333333%}.offset-xxl-11{margin-left:91.6666666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not(:-moz-read-only){cursor:pointer}.form-control[type=file]:not(:disabled):not(:read-only){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:-moz-read-only{background-color:#e9ecef;opacity:1}.form-control:disabled,.form-control:read-only{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not(:-moz-read-only)::file-selector-button{background-color:#dde0e3}.form-control:hover:not(:disabled):not(:read-only)::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not(:read-only)::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{max-width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not(:-moz-read-only){cursor:pointer}.form-control-color:not(:disabled):not(:read-only){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);padding:1rem .75rem}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast:not(.showing):not(.show){opacity:0}.toast.hide{display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1060;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid #d8d8d8;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1050;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{color:#0d6efd!important}.text-secondary{color:#6c757d!important}.text-success{color:#198754!important}.text-info{color:#0dcaf0!important}.text-warning{color:#ffc107!important}.text-danger{color:#dc3545!important}.text-light{color:#f8f9fa!important}.text-dark{color:#212529!important}.text-white{color:#fff!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-reset{color:inherit!important}.bg-primary{background-color:#0d6efd!important}.bg-secondary{background-color:#6c757d!important}.bg-success{background-color:#198754!important}.bg-info{background-color:#0dcaf0!important}.bg-warning{background-color:#ffc107!important}.bg-danger{background-color:#dc3545!important}.bg-light{background-color:#f8f9fa!important}.bg-dark{background-color:#212529!important}.bg-body{background-color:#fff!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/src_assets/common/assets/web/third_party/vue.js b/src_assets/common/assets/web/third_party/vue.js deleted file mode 100644 index 919aa125..00000000 --- a/src_assets/common/assets/web/third_party/vue.js +++ /dev/null @@ -1,11965 +0,0 @@ -/*! - * Vue.js v2.6.12 - * (c) 2014-2020 Evan You - * Released under the MIT License. - */ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global = global || self, global.Vue = factory()); -}(this, function () { 'use strict'; - - /* */ - - var emptyObject = Object.freeze({}); - - // These helpers produce better VM code in JS engines due to their - // explicitness and function inlining. - function isUndef (v) { - return v === undefined || v === null - } - - function isDef (v) { - return v !== undefined && v !== null - } - - function isTrue (v) { - return v === true - } - - function isFalse (v) { - return v === false - } - - /** - * Check if value is primitive. - */ - function isPrimitive (value) { - return ( - typeof value === 'string' || - typeof value === 'number' || - // $flow-disable-line - typeof value === 'symbol' || - typeof value === 'boolean' - ) - } - - /** - * Quick object check - this is primarily used to tell - * Objects from primitive values when we know the value - * is a JSON-compliant type. - */ - function isObject (obj) { - return obj !== null && typeof obj === 'object' - } - - /** - * Get the raw type string of a value, e.g., [object Object]. - */ - var _toString = Object.prototype.toString; - - function toRawType (value) { - return _toString.call(value).slice(8, -1) - } - - /** - * Strict object type check. Only returns true - * for plain JavaScript objects. - */ - function isPlainObject (obj) { - return _toString.call(obj) === '[object Object]' - } - - function isRegExp (v) { - return _toString.call(v) === '[object RegExp]' - } - - /** - * Check if val is a valid array index. - */ - function isValidArrayIndex (val) { - var n = parseFloat(String(val)); - return n >= 0 && Math.floor(n) === n && isFinite(val) - } - - function isPromise (val) { - return ( - isDef(val) && - typeof val.then === 'function' && - typeof val.catch === 'function' - ) - } - - /** - * Convert a value to a string that is actually rendered. - */ - function toString (val) { - return val == null - ? '' - : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString) - ? JSON.stringify(val, null, 2) - : String(val) - } - - /** - * Convert an input value to a number for persistence. - * If the conversion fails, return original string. - */ - function toNumber (val) { - var n = parseFloat(val); - return isNaN(n) ? val : n - } - - /** - * Make a map and return a function for checking if a key - * is in that map. - */ - function makeMap ( - str, - expectsLowerCase - ) { - var map = Object.create(null); - var list = str.split(','); - for (var i = 0; i < list.length; i++) { - map[list[i]] = true; - } - return expectsLowerCase - ? function (val) { return map[val.toLowerCase()]; } - : function (val) { return map[val]; } - } - - /** - * Check if a tag is a built-in tag. - */ - var isBuiltInTag = makeMap('slot,component', true); - - /** - * Check if an attribute is a reserved attribute. - */ - var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is'); - - /** - * Remove an item from an array. - */ - function remove (arr, item) { - if (arr.length) { - var index = arr.indexOf(item); - if (index > -1) { - return arr.splice(index, 1) - } - } - } - - /** - * Check whether an object has the property. - */ - var hasOwnProperty = Object.prototype.hasOwnProperty; - function hasOwn (obj, key) { - return hasOwnProperty.call(obj, key) - } - - /** - * Create a cached version of a pure function. - */ - function cached (fn) { - var cache = Object.create(null); - return (function cachedFn (str) { - var hit = cache[str]; - return hit || (cache[str] = fn(str)) - }) - } - - /** - * Camelize a hyphen-delimited string. - */ - var camelizeRE = /-(\w)/g; - var camelize = cached(function (str) { - return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; }) - }); - - /** - * Capitalize a string. - */ - var capitalize = cached(function (str) { - return str.charAt(0).toUpperCase() + str.slice(1) - }); - - /** - * Hyphenate a camelCase string. - */ - var hyphenateRE = /\B([A-Z])/g; - var hyphenate = cached(function (str) { - return str.replace(hyphenateRE, '-$1').toLowerCase() - }); - - /** - * Simple bind polyfill for environments that do not support it, - * e.g., PhantomJS 1.x. Technically, we don't need this anymore - * since native bind is now performant enough in most browsers. - * But removing it would mean breaking code that was able to run in - * PhantomJS 1.x, so this must be kept for backward compatibility. - */ - - /* istanbul ignore next */ - function polyfillBind (fn, ctx) { - function boundFn (a) { - var l = arguments.length; - return l - ? l > 1 - ? fn.apply(ctx, arguments) - : fn.call(ctx, a) - : fn.call(ctx) - } - - boundFn._length = fn.length; - return boundFn - } - - function nativeBind (fn, ctx) { - return fn.bind(ctx) - } - - var bind = Function.prototype.bind - ? nativeBind - : polyfillBind; - - /** - * Convert an Array-like object to a real Array. - */ - function toArray (list, start) { - start = start || 0; - var i = list.length - start; - var ret = new Array(i); - while (i--) { - ret[i] = list[i + start]; - } - return ret - } - - /** - * Mix properties into target object. - */ - function extend (to, _from) { - for (var key in _from) { - to[key] = _from[key]; - } - return to - } - - /** - * Merge an Array of Objects into a single Object. - */ - function toObject (arr) { - var res = {}; - for (var i = 0; i < arr.length; i++) { - if (arr[i]) { - extend(res, arr[i]); - } - } - return res - } - - /* eslint-disable no-unused-vars */ - - /** - * Perform no operation. - * Stubbing args to make Flow happy without leaving useless transpiled code - * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/). - */ - function noop (a, b, c) {} - - /** - * Always return false. - */ - var no = function (a, b, c) { return false; }; - - /* eslint-enable no-unused-vars */ - - /** - * Return the same value. - */ - var identity = function (_) { return _; }; - - /** - * Generate a string containing static keys from compiler modules. - */ - function genStaticKeys (modules) { - return modules.reduce(function (keys, m) { - return keys.concat(m.staticKeys || []) - }, []).join(',') - } - - /** - * Check if two values are loosely equal - that is, - * if they are plain objects, do they have the same shape? - */ - function looseEqual (a, b) { - if (a === b) { return true } - var isObjectA = isObject(a); - var isObjectB = isObject(b); - if (isObjectA && isObjectB) { - try { - var isArrayA = Array.isArray(a); - var isArrayB = Array.isArray(b); - if (isArrayA && isArrayB) { - return a.length === b.length && a.every(function (e, i) { - return looseEqual(e, b[i]) - }) - } else if (a instanceof Date && b instanceof Date) { - return a.getTime() === b.getTime() - } else if (!isArrayA && !isArrayB) { - var keysA = Object.keys(a); - var keysB = Object.keys(b); - return keysA.length === keysB.length && keysA.every(function (key) { - return looseEqual(a[key], b[key]) - }) - } else { - /* istanbul ignore next */ - return false - } - } catch (e) { - /* istanbul ignore next */ - return false - } - } else if (!isObjectA && !isObjectB) { - return String(a) === String(b) - } else { - return false - } - } - - /** - * Return the first index at which a loosely equal value can be - * found in the array (if value is a plain object, the array must - * contain an object of the same shape), or -1 if it is not present. - */ - function looseIndexOf (arr, val) { - for (var i = 0; i < arr.length; i++) { - if (looseEqual(arr[i], val)) { return i } - } - return -1 - } - - /** - * Ensure a function is called only once. - */ - function once (fn) { - var called = false; - return function () { - if (!called) { - called = true; - fn.apply(this, arguments); - } - } - } - - var SSR_ATTR = 'data-server-rendered'; - - var ASSET_TYPES = [ - 'component', - 'directive', - 'filter' - ]; - - var LIFECYCLE_HOOKS = [ - 'beforeCreate', - 'created', - 'beforeMount', - 'mounted', - 'beforeUpdate', - 'updated', - 'beforeDestroy', - 'destroyed', - 'activated', - 'deactivated', - 'errorCaptured', - 'serverPrefetch' - ]; - - /* */ - - - - var config = ({ - /** - * Option merge strategies (used in core/util/options) - */ - // $flow-disable-line - optionMergeStrategies: Object.create(null), - - /** - * Whether to suppress warnings. - */ - silent: false, - - /** - * Show production mode tip message on boot? - */ - productionTip: "development" !== 'production', - - /** - * Whether to enable devtools - */ - devtools: "development" !== 'production', - - /** - * Whether to record perf - */ - performance: false, - - /** - * Error handler for watcher errors - */ - errorHandler: null, - - /** - * Warn handler for watcher warns - */ - warnHandler: null, - - /** - * Ignore certain custom elements - */ - ignoredElements: [], - - /** - * Custom user key aliases for v-on - */ - // $flow-disable-line - keyCodes: Object.create(null), - - /** - * Check if a tag is reserved so that it cannot be registered as a - * component. This is platform-dependent and may be overwritten. - */ - isReservedTag: no, - - /** - * Check if an attribute is reserved so that it cannot be used as a component - * prop. This is platform-dependent and may be overwritten. - */ - isReservedAttr: no, - - /** - * Check if a tag is an unknown element. - * Platform-dependent. - */ - isUnknownElement: no, - - /** - * Get the namespace of an element - */ - getTagNamespace: noop, - - /** - * Parse the real tag name for the specific platform. - */ - parsePlatformTagName: identity, - - /** - * Check if an attribute must be bound using property, e.g. value - * Platform-dependent. - */ - mustUseProp: no, - - /** - * Perform updates asynchronously. Intended to be used by Vue Test Utils - * This will significantly reduce performance if set to false. - */ - async: true, - - /** - * Exposed for legacy reasons - */ - _lifecycleHooks: LIFECYCLE_HOOKS - }); - - /* */ - - /** - * unicode letters used for parsing html tags, component names and property paths. - * using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname - * skipping \u10000-\uEFFFF due to it freezing up PhantomJS - */ - var unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/; - - /** - * Check if a string starts with $ or _ - */ - function isReserved (str) { - var c = (str + '').charCodeAt(0); - return c === 0x24 || c === 0x5F - } - - /** - * Define a property. - */ - function def (obj, key, val, enumerable) { - Object.defineProperty(obj, key, { - value: val, - enumerable: !!enumerable, - writable: true, - configurable: true - }); - } - - /** - * Parse simple path. - */ - var bailRE = new RegExp(("[^" + (unicodeRegExp.source) + ".$_\\d]")); - function parsePath (path) { - if (bailRE.test(path)) { - return - } - var segments = path.split('.'); - return function (obj) { - for (var i = 0; i < segments.length; i++) { - if (!obj) { return } - obj = obj[segments[i]]; - } - return obj - } - } - - /* */ - - // can we use __proto__? - var hasProto = '__proto__' in {}; - - // Browser environment sniffing - var inBrowser = typeof window !== 'undefined'; - var inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform; - var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase(); - var UA = inBrowser && window.navigator.userAgent.toLowerCase(); - var isIE = UA && /msie|trident/.test(UA); - var isIE9 = UA && UA.indexOf('msie 9.0') > 0; - var isEdge = UA && UA.indexOf('edge/') > 0; - var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android'); - var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios'); - var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge; - var isPhantomJS = UA && /phantomjs/.test(UA); - var isFF = UA && UA.match(/firefox\/(\d+)/); - - // Firefox has a "watch" function on Object.prototype... - var nativeWatch = ({}).watch; - - var supportsPassive = false; - if (inBrowser) { - try { - var opts = {}; - Object.defineProperty(opts, 'passive', ({ - get: function get () { - /* istanbul ignore next */ - supportsPassive = true; - } - })); // https://github.com/facebook/flow/issues/285 - window.addEventListener('test-passive', null, opts); - } catch (e) {} - } - - // this needs to be lazy-evaled because vue may be required before - // vue-server-renderer can set VUE_ENV - var _isServer; - var isServerRendering = function () { - if (_isServer === undefined) { - /* istanbul ignore if */ - if (!inBrowser && !inWeex && typeof global !== 'undefined') { - // detect presence of vue-server-renderer and avoid - // Webpack shimming the process - _isServer = global['process'] && global['process'].env.VUE_ENV === 'server'; - } else { - _isServer = false; - } - } - return _isServer - }; - - // detect devtools - var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; - - /* istanbul ignore next */ - function isNative (Ctor) { - return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) - } - - var hasSymbol = - typeof Symbol !== 'undefined' && isNative(Symbol) && - typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys); - - var _Set; - /* istanbul ignore if */ // $flow-disable-line - if (typeof Set !== 'undefined' && isNative(Set)) { - // use native Set when available. - _Set = Set; - } else { - // a non-standard Set polyfill that only works with primitive keys. - _Set = /*@__PURE__*/(function () { - function Set () { - this.set = Object.create(null); - } - Set.prototype.has = function has (key) { - return this.set[key] === true - }; - Set.prototype.add = function add (key) { - this.set[key] = true; - }; - Set.prototype.clear = function clear () { - this.set = Object.create(null); - }; - - return Set; - }()); - } - - /* */ - - var warn = noop; - var tip = noop; - var generateComponentTrace = (noop); // work around flow check - var formatComponentName = (noop); - - { - var hasConsole = typeof console !== 'undefined'; - var classifyRE = /(?:^|[-_])(\w)/g; - var classify = function (str) { return str - .replace(classifyRE, function (c) { return c.toUpperCase(); }) - .replace(/[-_]/g, ''); }; - - warn = function (msg, vm) { - var trace = vm ? generateComponentTrace(vm) : ''; - - if (config.warnHandler) { - config.warnHandler.call(null, msg, vm, trace); - } else if (hasConsole && (!config.silent)) { - console.error(("[Vue warn]: " + msg + trace)); - } - }; - - tip = function (msg, vm) { - if (hasConsole && (!config.silent)) { - console.warn("[Vue tip]: " + msg + ( - vm ? generateComponentTrace(vm) : '' - )); - } - }; - - formatComponentName = function (vm, includeFile) { - if (vm.$root === vm) { - return '' - } - var options = typeof vm === 'function' && vm.cid != null - ? vm.options - : vm._isVue - ? vm.$options || vm.constructor.options - : vm; - var name = options.name || options._componentTag; - var file = options.__file; - if (!name && file) { - var match = file.match(/([^/\\]+)\.vue$/); - name = match && match[1]; - } - - return ( - (name ? ("<" + (classify(name)) + ">") : "") + - (file && includeFile !== false ? (" at " + file) : '') - ) - }; - - var repeat = function (str, n) { - var res = ''; - while (n) { - if (n % 2 === 1) { res += str; } - if (n > 1) { str += str; } - n >>= 1; - } - return res - }; - - generateComponentTrace = function (vm) { - if (vm._isVue && vm.$parent) { - var tree = []; - var currentRecursiveSequence = 0; - while (vm) { - if (tree.length > 0) { - var last = tree[tree.length - 1]; - if (last.constructor === vm.constructor) { - currentRecursiveSequence++; - vm = vm.$parent; - continue - } else if (currentRecursiveSequence > 0) { - tree[tree.length - 1] = [last, currentRecursiveSequence]; - currentRecursiveSequence = 0; - } - } - tree.push(vm); - vm = vm.$parent; - } - return '\n\nfound in\n\n' + tree - .map(function (vm, i) { return ("" + (i === 0 ? '---> ' : repeat(' ', 5 + i * 2)) + (Array.isArray(vm) - ? ((formatComponentName(vm[0])) + "... (" + (vm[1]) + " recursive calls)") - : formatComponentName(vm))); }) - .join('\n') - } else { - return ("\n\n(found in " + (formatComponentName(vm)) + ")") - } - }; - } - - /* */ - - var uid = 0; - - /** - * A dep is an observable that can have multiple - * directives subscribing to it. - */ - var Dep = function Dep () { - this.id = uid++; - this.subs = []; - }; - - Dep.prototype.addSub = function addSub (sub) { - this.subs.push(sub); - }; - - Dep.prototype.removeSub = function removeSub (sub) { - remove(this.subs, sub); - }; - - Dep.prototype.depend = function depend () { - if (Dep.target) { - Dep.target.addDep(this); - } - }; - - Dep.prototype.notify = function notify () { - // stabilize the subscriber list first - var subs = this.subs.slice(); - if (!config.async) { - // subs aren't sorted in scheduler if not running async - // we need to sort them now to make sure they fire in correct - // order - subs.sort(function (a, b) { return a.id - b.id; }); - } - for (var i = 0, l = subs.length; i < l; i++) { - subs[i].update(); - } - }; - - // The current target watcher being evaluated. - // This is globally unique because only one watcher - // can be evaluated at a time. - Dep.target = null; - var targetStack = []; - - function pushTarget (target) { - targetStack.push(target); - Dep.target = target; - } - - function popTarget () { - targetStack.pop(); - Dep.target = targetStack[targetStack.length - 1]; - } - - /* */ - - var VNode = function VNode ( - tag, - data, - children, - text, - elm, - context, - componentOptions, - asyncFactory - ) { - this.tag = tag; - this.data = data; - this.children = children; - this.text = text; - this.elm = elm; - this.ns = undefined; - this.context = context; - this.fnContext = undefined; - this.fnOptions = undefined; - this.fnScopeId = undefined; - this.key = data && data.key; - this.componentOptions = componentOptions; - this.componentInstance = undefined; - this.parent = undefined; - this.raw = false; - this.isStatic = false; - this.isRootInsert = true; - this.isComment = false; - this.isCloned = false; - this.isOnce = false; - this.asyncFactory = asyncFactory; - this.asyncMeta = undefined; - this.isAsyncPlaceholder = false; - }; - - var prototypeAccessors = { child: { configurable: true } }; - - // DEPRECATED: alias for componentInstance for backwards compat. - /* istanbul ignore next */ - prototypeAccessors.child.get = function () { - return this.componentInstance - }; - - Object.defineProperties( VNode.prototype, prototypeAccessors ); - - var createEmptyVNode = function (text) { - if ( text === void 0 ) text = ''; - - var node = new VNode(); - node.text = text; - node.isComment = true; - return node - }; - - function createTextVNode (val) { - return new VNode(undefined, undefined, undefined, String(val)) - } - - // optimized shallow clone - // used for static nodes and slot nodes because they may be reused across - // multiple renders, cloning them avoids errors when DOM manipulations rely - // on their elm reference. - function cloneVNode (vnode) { - var cloned = new VNode( - vnode.tag, - vnode.data, - // #7975 - // clone children array to avoid mutating original in case of cloning - // a child. - vnode.children && vnode.children.slice(), - vnode.text, - vnode.elm, - vnode.context, - vnode.componentOptions, - vnode.asyncFactory - ); - cloned.ns = vnode.ns; - cloned.isStatic = vnode.isStatic; - cloned.key = vnode.key; - cloned.isComment = vnode.isComment; - cloned.fnContext = vnode.fnContext; - cloned.fnOptions = vnode.fnOptions; - cloned.fnScopeId = vnode.fnScopeId; - cloned.asyncMeta = vnode.asyncMeta; - cloned.isCloned = true; - return cloned - } - - /* - * not type checking this file because flow doesn't play well with - * dynamically accessing methods on Array prototype - */ - - var arrayProto = Array.prototype; - var arrayMethods = Object.create(arrayProto); - - var methodsToPatch = [ - 'push', - 'pop', - 'shift', - 'unshift', - 'splice', - 'sort', - 'reverse' - ]; - - /** - * Intercept mutating methods and emit events - */ - methodsToPatch.forEach(function (method) { - // cache original method - var original = arrayProto[method]; - def(arrayMethods, method, function mutator () { - var args = [], len = arguments.length; - while ( len-- ) args[ len ] = arguments[ len ]; - - var result = original.apply(this, args); - var ob = this.__ob__; - var inserted; - switch (method) { - case 'push': - case 'unshift': - inserted = args; - break - case 'splice': - inserted = args.slice(2); - break - } - if (inserted) { ob.observeArray(inserted); } - // notify change - ob.dep.notify(); - return result - }); - }); - - /* */ - - var arrayKeys = Object.getOwnPropertyNames(arrayMethods); - - /** - * In some cases we may want to disable observation inside a component's - * update computation. - */ - var shouldObserve = true; - - function toggleObserving (value) { - shouldObserve = value; - } - - /** - * Observer class that is attached to each observed - * object. Once attached, the observer converts the target - * object's property keys into getter/setters that - * collect dependencies and dispatch updates. - */ - var Observer = function Observer (value) { - this.value = value; - this.dep = new Dep(); - this.vmCount = 0; - def(value, '__ob__', this); - if (Array.isArray(value)) { - if (hasProto) { - protoAugment(value, arrayMethods); - } else { - copyAugment(value, arrayMethods, arrayKeys); - } - this.observeArray(value); - } else { - this.walk(value); - } - }; - - /** - * Walk through all properties and convert them into - * getter/setters. This method should only be called when - * value type is Object. - */ - Observer.prototype.walk = function walk (obj) { - var keys = Object.keys(obj); - for (var i = 0; i < keys.length; i++) { - defineReactive$$1(obj, keys[i]); - } - }; - - /** - * Observe a list of Array items. - */ - Observer.prototype.observeArray = function observeArray (items) { - for (var i = 0, l = items.length; i < l; i++) { - observe(items[i]); - } - }; - - // helpers - - /** - * Augment a target Object or Array by intercepting - * the prototype chain using __proto__ - */ - function protoAugment (target, src) { - /* eslint-disable no-proto */ - target.__proto__ = src; - /* eslint-enable no-proto */ - } - - /** - * Augment a target Object or Array by defining - * hidden properties. - */ - /* istanbul ignore next */ - function copyAugment (target, src, keys) { - for (var i = 0, l = keys.length; i < l; i++) { - var key = keys[i]; - def(target, key, src[key]); - } - } - - /** - * Attempt to create an observer instance for a value, - * returns the new observer if successfully observed, - * or the existing observer if the value already has one. - */ - function observe (value, asRootData) { - if (!isObject(value) || value instanceof VNode) { - return - } - var ob; - if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { - ob = value.__ob__; - } else if ( - shouldObserve && - !isServerRendering() && - (Array.isArray(value) || isPlainObject(value)) && - Object.isExtensible(value) && - !value._isVue - ) { - ob = new Observer(value); - } - if (asRootData && ob) { - ob.vmCount++; - } - return ob - } - - /** - * Define a reactive property on an Object. - */ - function defineReactive$$1 ( - obj, - key, - val, - customSetter, - shallow - ) { - var dep = new Dep(); - - var property = Object.getOwnPropertyDescriptor(obj, key); - if (property && property.configurable === false) { - return - } - - // cater for pre-defined getter/setters - var getter = property && property.get; - var setter = property && property.set; - if ((!getter || setter) && arguments.length === 2) { - val = obj[key]; - } - - var childOb = !shallow && observe(val); - Object.defineProperty(obj, key, { - enumerable: true, - configurable: true, - get: function reactiveGetter () { - var value = getter ? getter.call(obj) : val; - if (Dep.target) { - dep.depend(); - if (childOb) { - childOb.dep.depend(); - if (Array.isArray(value)) { - dependArray(value); - } - } - } - return value - }, - set: function reactiveSetter (newVal) { - var value = getter ? getter.call(obj) : val; - /* eslint-disable no-self-compare */ - if (newVal === value || (newVal !== newVal && value !== value)) { - return - } - /* eslint-enable no-self-compare */ - if (customSetter) { - customSetter(); - } - // #7981: for accessor properties without setter - if (getter && !setter) { return } - if (setter) { - setter.call(obj, newVal); - } else { - val = newVal; - } - childOb = !shallow && observe(newVal); - dep.notify(); - } - }); - } - - /** - * Set a property on an object. Adds the new property and - * triggers change notification if the property doesn't - * already exist. - */ - function set (target, key, val) { - if (isUndef(target) || isPrimitive(target) - ) { - warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target)))); - } - if (Array.isArray(target) && isValidArrayIndex(key)) { - target.length = Math.max(target.length, key); - target.splice(key, 1, val); - return val - } - if (key in target && !(key in Object.prototype)) { - target[key] = val; - return val - } - var ob = (target).__ob__; - if (target._isVue || (ob && ob.vmCount)) { - warn( - 'Avoid adding reactive properties to a Vue instance or its root $data ' + - 'at runtime - declare it upfront in the data option.' - ); - return val - } - if (!ob) { - target[key] = val; - return val - } - defineReactive$$1(ob.value, key, val); - ob.dep.notify(); - return val - } - - /** - * Delete a property and trigger change if necessary. - */ - function del (target, key) { - if (isUndef(target) || isPrimitive(target) - ) { - warn(("Cannot delete reactive property on undefined, null, or primitive value: " + ((target)))); - } - if (Array.isArray(target) && isValidArrayIndex(key)) { - target.splice(key, 1); - return - } - var ob = (target).__ob__; - if (target._isVue || (ob && ob.vmCount)) { - warn( - 'Avoid deleting properties on a Vue instance or its root $data ' + - '- just set it to null.' - ); - return - } - if (!hasOwn(target, key)) { - return - } - delete target[key]; - if (!ob) { - return - } - ob.dep.notify(); - } - - /** - * Collect dependencies on array elements when the array is touched, since - * we cannot intercept array element access like property getters. - */ - function dependArray (value) { - for (var e = (void 0), i = 0, l = value.length; i < l; i++) { - e = value[i]; - e && e.__ob__ && e.__ob__.dep.depend(); - if (Array.isArray(e)) { - dependArray(e); - } - } - } - - /* */ - - /** - * Option overwriting strategies are functions that handle - * how to merge a parent option value and a child option - * value into the final value. - */ - var strats = config.optionMergeStrategies; - - /** - * Options with restrictions - */ - { - strats.el = strats.propsData = function (parent, child, vm, key) { - if (!vm) { - warn( - "option \"" + key + "\" can only be used during instance " + - 'creation with the `new` keyword.' - ); - } - return defaultStrat(parent, child) - }; - } - - /** - * Helper that recursively merges two data objects together. - */ - function mergeData (to, from) { - if (!from) { return to } - var key, toVal, fromVal; - - var keys = hasSymbol - ? Reflect.ownKeys(from) - : Object.keys(from); - - for (var i = 0; i < keys.length; i++) { - key = keys[i]; - // in case the object is already observed... - if (key === '__ob__') { continue } - toVal = to[key]; - fromVal = from[key]; - if (!hasOwn(to, key)) { - set(to, key, fromVal); - } else if ( - toVal !== fromVal && - isPlainObject(toVal) && - isPlainObject(fromVal) - ) { - mergeData(toVal, fromVal); - } - } - return to - } - - /** - * Data - */ - function mergeDataOrFn ( - parentVal, - childVal, - vm - ) { - if (!vm) { - // in a Vue.extend merge, both should be functions - if (!childVal) { - return parentVal - } - if (!parentVal) { - return childVal - } - // when parentVal & childVal are both present, - // we need to return a function that returns the - // merged result of both functions... no need to - // check if parentVal is a function here because - // it has to be a function to pass previous merges. - return function mergedDataFn () { - return mergeData( - typeof childVal === 'function' ? childVal.call(this, this) : childVal, - typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal - ) - } - } else { - return function mergedInstanceDataFn () { - // instance merge - var instanceData = typeof childVal === 'function' - ? childVal.call(vm, vm) - : childVal; - var defaultData = typeof parentVal === 'function' - ? parentVal.call(vm, vm) - : parentVal; - if (instanceData) { - return mergeData(instanceData, defaultData) - } else { - return defaultData - } - } - } - } - - strats.data = function ( - parentVal, - childVal, - vm - ) { - if (!vm) { - if (childVal && typeof childVal !== 'function') { - warn( - 'The "data" option should be a function ' + - 'that returns a per-instance value in component ' + - 'definitions.', - vm - ); - - return parentVal - } - return mergeDataOrFn(parentVal, childVal) - } - - return mergeDataOrFn(parentVal, childVal, vm) - }; - - /** - * Hooks and props are merged as arrays. - */ - function mergeHook ( - parentVal, - childVal - ) { - var res = childVal - ? parentVal - ? parentVal.concat(childVal) - : Array.isArray(childVal) - ? childVal - : [childVal] - : parentVal; - return res - ? dedupeHooks(res) - : res - } - - function dedupeHooks (hooks) { - var res = []; - for (var i = 0; i < hooks.length; i++) { - if (res.indexOf(hooks[i]) === -1) { - res.push(hooks[i]); - } - } - return res - } - - LIFECYCLE_HOOKS.forEach(function (hook) { - strats[hook] = mergeHook; - }); - - /** - * Assets - * - * When a vm is present (instance creation), we need to do - * a three-way merge between constructor options, instance - * options and parent options. - */ - function mergeAssets ( - parentVal, - childVal, - vm, - key - ) { - var res = Object.create(parentVal || null); - if (childVal) { - assertObjectType(key, childVal, vm); - return extend(res, childVal) - } else { - return res - } - } - - ASSET_TYPES.forEach(function (type) { - strats[type + 's'] = mergeAssets; - }); - - /** - * Watchers. - * - * Watchers hashes should not overwrite one - * another, so we merge them as arrays. - */ - strats.watch = function ( - parentVal, - childVal, - vm, - key - ) { - // work around Firefox's Object.prototype.watch... - if (parentVal === nativeWatch) { parentVal = undefined; } - if (childVal === nativeWatch) { childVal = undefined; } - /* istanbul ignore if */ - if (!childVal) { return Object.create(parentVal || null) } - { - assertObjectType(key, childVal, vm); - } - if (!parentVal) { return childVal } - var ret = {}; - extend(ret, parentVal); - for (var key$1 in childVal) { - var parent = ret[key$1]; - var child = childVal[key$1]; - if (parent && !Array.isArray(parent)) { - parent = [parent]; - } - ret[key$1] = parent - ? parent.concat(child) - : Array.isArray(child) ? child : [child]; - } - return ret - }; - - /** - * Other object hashes. - */ - strats.props = - strats.methods = - strats.inject = - strats.computed = function ( - parentVal, - childVal, - vm, - key - ) { - if (childVal && "development" !== 'production') { - assertObjectType(key, childVal, vm); - } - if (!parentVal) { return childVal } - var ret = Object.create(null); - extend(ret, parentVal); - if (childVal) { extend(ret, childVal); } - return ret - }; - strats.provide = mergeDataOrFn; - - /** - * Default strategy. - */ - var defaultStrat = function (parentVal, childVal) { - return childVal === undefined - ? parentVal - : childVal - }; - - /** - * Validate component names - */ - function checkComponents (options) { - for (var key in options.components) { - validateComponentName(key); - } - } - - function validateComponentName (name) { - if (!new RegExp(("^[a-zA-Z][\\-\\.0-9_" + (unicodeRegExp.source) + "]*$")).test(name)) { - warn( - 'Invalid component name: "' + name + '". Component names ' + - 'should conform to valid custom element name in html5 specification.' - ); - } - if (isBuiltInTag(name) || config.isReservedTag(name)) { - warn( - 'Do not use built-in or reserved HTML elements as component ' + - 'id: ' + name - ); - } - } - - /** - * Ensure all props option syntax are normalized into the - * Object-based format. - */ - function normalizeProps (options, vm) { - var props = options.props; - if (!props) { return } - var res = {}; - var i, val, name; - if (Array.isArray(props)) { - i = props.length; - while (i--) { - val = props[i]; - if (typeof val === 'string') { - name = camelize(val); - res[name] = { type: null }; - } else { - warn('props must be strings when using array syntax.'); - } - } - } else if (isPlainObject(props)) { - for (var key in props) { - val = props[key]; - name = camelize(key); - res[name] = isPlainObject(val) - ? val - : { type: val }; - } - } else { - warn( - "Invalid value for option \"props\": expected an Array or an Object, " + - "but got " + (toRawType(props)) + ".", - vm - ); - } - options.props = res; - } - - /** - * Normalize all injections into Object-based format - */ - function normalizeInject (options, vm) { - var inject = options.inject; - if (!inject) { return } - var normalized = options.inject = {}; - if (Array.isArray(inject)) { - for (var i = 0; i < inject.length; i++) { - normalized[inject[i]] = { from: inject[i] }; - } - } else if (isPlainObject(inject)) { - for (var key in inject) { - var val = inject[key]; - normalized[key] = isPlainObject(val) - ? extend({ from: key }, val) - : { from: val }; - } - } else { - warn( - "Invalid value for option \"inject\": expected an Array or an Object, " + - "but got " + (toRawType(inject)) + ".", - vm - ); - } - } - - /** - * Normalize raw function directives into object format. - */ - function normalizeDirectives (options) { - var dirs = options.directives; - if (dirs) { - for (var key in dirs) { - var def$$1 = dirs[key]; - if (typeof def$$1 === 'function') { - dirs[key] = { bind: def$$1, update: def$$1 }; - } - } - } - } - - function assertObjectType (name, value, vm) { - if (!isPlainObject(value)) { - warn( - "Invalid value for option \"" + name + "\": expected an Object, " + - "but got " + (toRawType(value)) + ".", - vm - ); - } - } - - /** - * Merge two option objects into a new one. - * Core utility used in both instantiation and inheritance. - */ - function mergeOptions ( - parent, - child, - vm - ) { - { - checkComponents(child); - } - - if (typeof child === 'function') { - child = child.options; - } - - normalizeProps(child, vm); - normalizeInject(child, vm); - normalizeDirectives(child); - - // Apply extends and mixins on the child options, - // but only if it is a raw options object that isn't - // the result of another mergeOptions call. - // Only merged options has the _base property. - if (!child._base) { - if (child.extends) { - parent = mergeOptions(parent, child.extends, vm); - } - if (child.mixins) { - for (var i = 0, l = child.mixins.length; i < l; i++) { - parent = mergeOptions(parent, child.mixins[i], vm); - } - } - } - - var options = {}; - var key; - for (key in parent) { - mergeField(key); - } - for (key in child) { - if (!hasOwn(parent, key)) { - mergeField(key); - } - } - function mergeField (key) { - var strat = strats[key] || defaultStrat; - options[key] = strat(parent[key], child[key], vm, key); - } - return options - } - - /** - * Resolve an asset. - * This function is used because child instances need access - * to assets defined in its ancestor chain. - */ - function resolveAsset ( - options, - type, - id, - warnMissing - ) { - /* istanbul ignore if */ - if (typeof id !== 'string') { - return - } - var assets = options[type]; - // check local registration variations first - if (hasOwn(assets, id)) { return assets[id] } - var camelizedId = camelize(id); - if (hasOwn(assets, camelizedId)) { return assets[camelizedId] } - var PascalCaseId = capitalize(camelizedId); - if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] } - // fallback to prototype chain - var res = assets[id] || assets[camelizedId] || assets[PascalCaseId]; - if (warnMissing && !res) { - warn( - 'Failed to resolve ' + type.slice(0, -1) + ': ' + id, - options - ); - } - return res - } - - /* */ - - - - function validateProp ( - key, - propOptions, - propsData, - vm - ) { - var prop = propOptions[key]; - var absent = !hasOwn(propsData, key); - var value = propsData[key]; - // boolean casting - var booleanIndex = getTypeIndex(Boolean, prop.type); - if (booleanIndex > -1) { - if (absent && !hasOwn(prop, 'default')) { - value = false; - } else if (value === '' || value === hyphenate(key)) { - // only cast empty string / same name to boolean if - // boolean has higher priority - var stringIndex = getTypeIndex(String, prop.type); - if (stringIndex < 0 || booleanIndex < stringIndex) { - value = true; - } - } - } - // check default value - if (value === undefined) { - value = getPropDefaultValue(vm, prop, key); - // since the default value is a fresh copy, - // make sure to observe it. - var prevShouldObserve = shouldObserve; - toggleObserving(true); - observe(value); - toggleObserving(prevShouldObserve); - } - { - assertProp(prop, key, value, vm, absent); - } - return value - } - - /** - * Get the default value of a prop. - */ - function getPropDefaultValue (vm, prop, key) { - // no default, return undefined - if (!hasOwn(prop, 'default')) { - return undefined - } - var def = prop.default; - // warn against non-factory defaults for Object & Array - if (isObject(def)) { - warn( - 'Invalid default value for prop "' + key + '": ' + - 'Props with type Object/Array must use a factory function ' + - 'to return the default value.', - vm - ); - } - // the raw prop value was also undefined from previous render, - // return previous default value to avoid unnecessary watcher trigger - if (vm && vm.$options.propsData && - vm.$options.propsData[key] === undefined && - vm._props[key] !== undefined - ) { - return vm._props[key] - } - // call factory function for non-Function types - // a value is Function if its prototype is function even across different execution context - return typeof def === 'function' && getType(prop.type) !== 'Function' - ? def.call(vm) - : def - } - - /** - * Assert whether a prop is valid. - */ - function assertProp ( - prop, - name, - value, - vm, - absent - ) { - if (prop.required && absent) { - warn( - 'Missing required prop: "' + name + '"', - vm - ); - return - } - if (value == null && !prop.required) { - return - } - var type = prop.type; - var valid = !type || type === true; - var expectedTypes = []; - if (type) { - if (!Array.isArray(type)) { - type = [type]; - } - for (var i = 0; i < type.length && !valid; i++) { - var assertedType = assertType(value, type[i]); - expectedTypes.push(assertedType.expectedType || ''); - valid = assertedType.valid; - } - } - - if (!valid) { - warn( - getInvalidTypeMessage(name, value, expectedTypes), - vm - ); - return - } - var validator = prop.validator; - if (validator) { - if (!validator(value)) { - warn( - 'Invalid prop: custom validator check failed for prop "' + name + '".', - vm - ); - } - } - } - - var simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/; - - function assertType (value, type) { - var valid; - var expectedType = getType(type); - if (simpleCheckRE.test(expectedType)) { - var t = typeof value; - valid = t === expectedType.toLowerCase(); - // for primitive wrapper objects - if (!valid && t === 'object') { - valid = value instanceof type; - } - } else if (expectedType === 'Object') { - valid = isPlainObject(value); - } else if (expectedType === 'Array') { - valid = Array.isArray(value); - } else { - valid = value instanceof type; - } - return { - valid: valid, - expectedType: expectedType - } - } - - /** - * Use function string name to check built-in types, - * because a simple equality check will fail when running - * across different vms / iframes. - */ - function getType (fn) { - var match = fn && fn.toString().match(/^\s*function (\w+)/); - return match ? match[1] : '' - } - - function isSameType (a, b) { - return getType(a) === getType(b) - } - - function getTypeIndex (type, expectedTypes) { - if (!Array.isArray(expectedTypes)) { - return isSameType(expectedTypes, type) ? 0 : -1 - } - for (var i = 0, len = expectedTypes.length; i < len; i++) { - if (isSameType(expectedTypes[i], type)) { - return i - } - } - return -1 - } - - function getInvalidTypeMessage (name, value, expectedTypes) { - var message = "Invalid prop: type check failed for prop \"" + name + "\"." + - " Expected " + (expectedTypes.map(capitalize).join(', ')); - var expectedType = expectedTypes[0]; - var receivedType = toRawType(value); - var expectedValue = styleValue(value, expectedType); - var receivedValue = styleValue(value, receivedType); - // check if we need to specify expected value - if (expectedTypes.length === 1 && - isExplicable(expectedType) && - !isBoolean(expectedType, receivedType)) { - message += " with value " + expectedValue; - } - message += ", got " + receivedType + " "; - // check if we need to specify received value - if (isExplicable(receivedType)) { - message += "with value " + receivedValue + "."; - } - return message - } - - function styleValue (value, type) { - if (type === 'String') { - return ("\"" + value + "\"") - } else if (type === 'Number') { - return ("" + (Number(value))) - } else { - return ("" + value) - } - } - - function isExplicable (value) { - var explicitTypes = ['string', 'number', 'boolean']; - return explicitTypes.some(function (elem) { return value.toLowerCase() === elem; }) - } - - function isBoolean () { - var args = [], len = arguments.length; - while ( len-- ) args[ len ] = arguments[ len ]; - - return args.some(function (elem) { return elem.toLowerCase() === 'boolean'; }) - } - - /* */ - - function handleError (err, vm, info) { - // Deactivate deps tracking while processing error handler to avoid possible infinite rendering. - // See: https://github.com/vuejs/vuex/issues/1505 - pushTarget(); - try { - if (vm) { - var cur = vm; - while ((cur = cur.$parent)) { - var hooks = cur.$options.errorCaptured; - if (hooks) { - for (var i = 0; i < hooks.length; i++) { - try { - var capture = hooks[i].call(cur, err, vm, info) === false; - if (capture) { return } - } catch (e) { - globalHandleError(e, cur, 'errorCaptured hook'); - } - } - } - } - } - globalHandleError(err, vm, info); - } finally { - popTarget(); - } - } - - function invokeWithErrorHandling ( - handler, - context, - args, - vm, - info - ) { - var res; - try { - res = args ? handler.apply(context, args) : handler.call(context); - if (res && !res._isVue && isPromise(res) && !res._handled) { - res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); }); - // issue #9511 - // avoid catch triggering multiple times when nested calls - res._handled = true; - } - } catch (e) { - handleError(e, vm, info); - } - return res - } - - function globalHandleError (err, vm, info) { - if (config.errorHandler) { - try { - return config.errorHandler.call(null, err, vm, info) - } catch (e) { - // if the user intentionally throws the original error in the handler, - // do not log it twice - if (e !== err) { - logError(e, null, 'config.errorHandler'); - } - } - } - logError(err, vm, info); - } - - function logError (err, vm, info) { - { - warn(("Error in " + info + ": \"" + (err.toString()) + "\""), vm); - } - /* istanbul ignore else */ - if ((inBrowser || inWeex) && typeof console !== 'undefined') { - console.error(err); - } else { - throw err - } - } - - /* */ - - var isUsingMicroTask = false; - - var callbacks = []; - var pending = false; - - function flushCallbacks () { - pending = false; - var copies = callbacks.slice(0); - callbacks.length = 0; - for (var i = 0; i < copies.length; i++) { - copies[i](); - } - } - - // Here we have async deferring wrappers using microtasks. - // In 2.5 we used (macro) tasks (in combination with microtasks). - // However, it has subtle problems when state is changed right before repaint - // (e.g. #6813, out-in transitions). - // Also, using (macro) tasks in event handler would cause some weird behaviors - // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109). - // So we now use microtasks everywhere, again. - // A major drawback of this tradeoff is that there are some scenarios - // where microtasks have too high a priority and fire in between supposedly - // sequential events (e.g. #4521, #6690, which have workarounds) - // or even between bubbling of the same event (#6566). - var timerFunc; - - // The nextTick behavior leverages the microtask queue, which can be accessed - // via either native Promise.then or MutationObserver. - // MutationObserver has wider support, however it is seriously bugged in - // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It - // completely stops working after triggering a few times... so, if native - // Promise is available, we will use it: - /* istanbul ignore next, $flow-disable-line */ - if (typeof Promise !== 'undefined' && isNative(Promise)) { - var p = Promise.resolve(); - timerFunc = function () { - p.then(flushCallbacks); - // In problematic UIWebViews, Promise.then doesn't completely break, but - // it can get stuck in a weird state where callbacks are pushed into the - // microtask queue but the queue isn't being flushed, until the browser - // needs to do some other work, e.g. handle a timer. Therefore we can - // "force" the microtask queue to be flushed by adding an empty timer. - if (isIOS) { setTimeout(noop); } - }; - isUsingMicroTask = true; - } else if (!isIE && typeof MutationObserver !== 'undefined' && ( - isNative(MutationObserver) || - // PhantomJS and iOS 7.x - MutationObserver.toString() === '[object MutationObserverConstructor]' - )) { - // Use MutationObserver where native Promise is not available, - // e.g. PhantomJS, iOS7, Android 4.4 - // (#6466 MutationObserver is unreliable in IE11) - var counter = 1; - var observer = new MutationObserver(flushCallbacks); - var textNode = document.createTextNode(String(counter)); - observer.observe(textNode, { - characterData: true - }); - timerFunc = function () { - counter = (counter + 1) % 2; - textNode.data = String(counter); - }; - isUsingMicroTask = true; - } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { - // Fallback to setImmediate. - // Technically it leverages the (macro) task queue, - // but it is still a better choice than setTimeout. - timerFunc = function () { - setImmediate(flushCallbacks); - }; - } else { - // Fallback to setTimeout. - timerFunc = function () { - setTimeout(flushCallbacks, 0); - }; - } - - function nextTick (cb, ctx) { - var _resolve; - callbacks.push(function () { - if (cb) { - try { - cb.call(ctx); - } catch (e) { - handleError(e, ctx, 'nextTick'); - } - } else if (_resolve) { - _resolve(ctx); - } - }); - if (!pending) { - pending = true; - timerFunc(); - } - // $flow-disable-line - if (!cb && typeof Promise !== 'undefined') { - return new Promise(function (resolve) { - _resolve = resolve; - }) - } - } - - /* */ - - var mark; - var measure; - - { - var perf = inBrowser && window.performance; - /* istanbul ignore if */ - if ( - perf && - perf.mark && - perf.measure && - perf.clearMarks && - perf.clearMeasures - ) { - mark = function (tag) { return perf.mark(tag); }; - measure = function (name, startTag, endTag) { - perf.measure(name, startTag, endTag); - perf.clearMarks(startTag); - perf.clearMarks(endTag); - // perf.clearMeasures(name) - }; - } - } - - /* not type checking this file because flow doesn't play well with Proxy */ - - var initProxy; - - { - var allowedGlobals = makeMap( - 'Infinity,undefined,NaN,isFinite,isNaN,' + - 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + - 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + - 'require' // for Webpack/Browserify - ); - - var warnNonPresent = function (target, key) { - warn( - "Property or method \"" + key + "\" is not defined on the instance but " + - 'referenced during render. Make sure that this property is reactive, ' + - 'either in the data option, or for class-based components, by ' + - 'initializing the property. ' + - 'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.', - target - ); - }; - - var warnReservedPrefix = function (target, key) { - warn( - "Property \"" + key + "\" must be accessed with \"$data." + key + "\" because " + - 'properties starting with "$" or "_" are not proxied in the Vue instance to ' + - 'prevent conflicts with Vue internals. ' + - 'See: https://vuejs.org/v2/api/#data', - target - ); - }; - - var hasProxy = - typeof Proxy !== 'undefined' && isNative(Proxy); - - if (hasProxy) { - var isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact'); - config.keyCodes = new Proxy(config.keyCodes, { - set: function set (target, key, value) { - if (isBuiltInModifier(key)) { - warn(("Avoid overwriting built-in modifier in config.keyCodes: ." + key)); - return false - } else { - target[key] = value; - return true - } - } - }); - } - - var hasHandler = { - has: function has (target, key) { - var has = key in target; - var isAllowed = allowedGlobals(key) || - (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data)); - if (!has && !isAllowed) { - if (key in target.$data) { warnReservedPrefix(target, key); } - else { warnNonPresent(target, key); } - } - return has || !isAllowed - } - }; - - var getHandler = { - get: function get (target, key) { - if (typeof key === 'string' && !(key in target)) { - if (key in target.$data) { warnReservedPrefix(target, key); } - else { warnNonPresent(target, key); } - } - return target[key] - } - }; - - initProxy = function initProxy (vm) { - if (hasProxy) { - // determine which proxy handler to use - var options = vm.$options; - var handlers = options.render && options.render._withStripped - ? getHandler - : hasHandler; - vm._renderProxy = new Proxy(vm, handlers); - } else { - vm._renderProxy = vm; - } - }; - } - - /* */ - - var seenObjects = new _Set(); - - /** - * Recursively traverse an object to evoke all converted - * getters, so that every nested property inside the object - * is collected as a "deep" dependency. - */ - function traverse (val) { - _traverse(val, seenObjects); - seenObjects.clear(); - } - - function _traverse (val, seen) { - var i, keys; - var isA = Array.isArray(val); - if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { - return - } - if (val.__ob__) { - var depId = val.__ob__.dep.id; - if (seen.has(depId)) { - return - } - seen.add(depId); - } - if (isA) { - i = val.length; - while (i--) { _traverse(val[i], seen); } - } else { - keys = Object.keys(val); - i = keys.length; - while (i--) { _traverse(val[keys[i]], seen); } - } - } - - /* */ - - var normalizeEvent = cached(function (name) { - var passive = name.charAt(0) === '&'; - name = passive ? name.slice(1) : name; - var once$$1 = name.charAt(0) === '~'; // Prefixed last, checked first - name = once$$1 ? name.slice(1) : name; - var capture = name.charAt(0) === '!'; - name = capture ? name.slice(1) : name; - return { - name: name, - once: once$$1, - capture: capture, - passive: passive - } - }); - - function createFnInvoker (fns, vm) { - function invoker () { - var arguments$1 = arguments; - - var fns = invoker.fns; - if (Array.isArray(fns)) { - var cloned = fns.slice(); - for (var i = 0; i < cloned.length; i++) { - invokeWithErrorHandling(cloned[i], null, arguments$1, vm, "v-on handler"); - } - } else { - // return handler return value for single handlers - return invokeWithErrorHandling(fns, null, arguments, vm, "v-on handler") - } - } - invoker.fns = fns; - return invoker - } - - function updateListeners ( - on, - oldOn, - add, - remove$$1, - createOnceHandler, - vm - ) { - var name, def$$1, cur, old, event; - for (name in on) { - def$$1 = cur = on[name]; - old = oldOn[name]; - event = normalizeEvent(name); - if (isUndef(cur)) { - warn( - "Invalid handler for event \"" + (event.name) + "\": got " + String(cur), - vm - ); - } else if (isUndef(old)) { - if (isUndef(cur.fns)) { - cur = on[name] = createFnInvoker(cur, vm); - } - if (isTrue(event.once)) { - cur = on[name] = createOnceHandler(event.name, cur, event.capture); - } - add(event.name, cur, event.capture, event.passive, event.params); - } else if (cur !== old) { - old.fns = cur; - on[name] = old; - } - } - for (name in oldOn) { - if (isUndef(on[name])) { - event = normalizeEvent(name); - remove$$1(event.name, oldOn[name], event.capture); - } - } - } - - /* */ - - function mergeVNodeHook (def, hookKey, hook) { - if (def instanceof VNode) { - def = def.data.hook || (def.data.hook = {}); - } - var invoker; - var oldHook = def[hookKey]; - - function wrappedHook () { - hook.apply(this, arguments); - // important: remove merged hook to ensure it's called only once - // and prevent memory leak - remove(invoker.fns, wrappedHook); - } - - if (isUndef(oldHook)) { - // no existing hook - invoker = createFnInvoker([wrappedHook]); - } else { - /* istanbul ignore if */ - if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { - // already a merged invoker - invoker = oldHook; - invoker.fns.push(wrappedHook); - } else { - // existing plain hook - invoker = createFnInvoker([oldHook, wrappedHook]); - } - } - - invoker.merged = true; - def[hookKey] = invoker; - } - - /* */ - - function extractPropsFromVNodeData ( - data, - Ctor, - tag - ) { - // we are only extracting raw values here. - // validation and default values are handled in the child - // component itself. - var propOptions = Ctor.options.props; - if (isUndef(propOptions)) { - return - } - var res = {}; - var attrs = data.attrs; - var props = data.props; - if (isDef(attrs) || isDef(props)) { - for (var key in propOptions) { - var altKey = hyphenate(key); - { - var keyInLowerCase = key.toLowerCase(); - if ( - key !== keyInLowerCase && - attrs && hasOwn(attrs, keyInLowerCase) - ) { - tip( - "Prop \"" + keyInLowerCase + "\" is passed to component " + - (formatComponentName(tag || Ctor)) + ", but the declared prop name is" + - " \"" + key + "\". " + - "Note that HTML attributes are case-insensitive and camelCased " + - "props need to use their kebab-case equivalents when using in-DOM " + - "templates. You should probably use \"" + altKey + "\" instead of \"" + key + "\"." - ); - } - } - checkProp(res, props, key, altKey, true) || - checkProp(res, attrs, key, altKey, false); - } - } - return res - } - - function checkProp ( - res, - hash, - key, - altKey, - preserve - ) { - if (isDef(hash)) { - if (hasOwn(hash, key)) { - res[key] = hash[key]; - if (!preserve) { - delete hash[key]; - } - return true - } else if (hasOwn(hash, altKey)) { - res[key] = hash[altKey]; - if (!preserve) { - delete hash[altKey]; - } - return true - } - } - return false - } - - /* */ - - // The template compiler attempts to minimize the need for normalization by - // statically analyzing the template at compile time. - // - // For plain HTML markup, normalization can be completely skipped because the - // generated render function is guaranteed to return Array. There are - // two cases where extra normalization is needed: - - // 1. When the children contains components - because a functional component - // may return an Array instead of a single root. In this case, just a simple - // normalization is needed - if any child is an Array, we flatten the whole - // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep - // because functional components already normalize their own children. - function simpleNormalizeChildren (children) { - for (var i = 0; i < children.length; i++) { - if (Array.isArray(children[i])) { - return Array.prototype.concat.apply([], children) - } - } - return children - } - - // 2. When the children contains constructs that always generated nested Arrays, - // e.g.
      Success! Restart Sunshine to apply changes @@ -771,6 +799,10 @@

      Configuration

      id: "va-api", name: "VA-API encoder", }, + { + id: "vt", + name: "VideoToolbox encoder", + }, ], }; }, @@ -812,6 +844,9 @@

      Configuration

      this.config.nv_coder = this.config.nv_coder || "auto"; this.config.amd_quality = this.config.amd_quality || "default"; this.config.amd_rc = this.config.amd_rc || "auto"; + this.config.vt_coder = this.config.vt_coder || "auto"; + this.config.vt_software = this.config.vt_software || "auto"; + this.config.vt_realtime = this.config.vt_realtime || "enabled"; this.config.fps = this.config.fps || "[10, 30, 60, 90, 120]"; this.config.resolutions = this.config.resolutions || diff --git a/sunshine/config.cpp b/sunshine/config.cpp index a35d5955..6aaeb305 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -162,6 +162,42 @@ int coder_from_view(const std::string_view &coder) { } } // namespace amd +namespace vt { + +enum coder_e : int { + _auto = 0, + cabac, + cavlc +}; + +int coder_from_view(const std::string_view &coder) { + if(coder == "auto"sv) return _auto; + if(coder == "cabac"sv || coder == "ac"sv) return cabac; + if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc; + + return -1; +} + +int allow_software_from_view(const std::string_view &software) { + if(software == "allowed"sv || software == "forced") return 1; + + return 0; +} + +int force_software_from_view(const std::string_view &software) { + if(software == "forced") return 1; + + return 0; +} + +int rt_from_view(const std::string_view &rt) { + if(rt == "disabled" || rt == "off" || rt == "0") return 0; + + return 1; +} + +} // namespace vt + video_t video { 28, // qp @@ -685,6 +721,14 @@ void apply_config(std::unordered_map &&vars) { video.amd.rc_hevc = amd::rc_hevc_from_view(rc); } + int_f(vars, "vt_coder", video.vt.coder, vt::coder_from_view); + video.vt.allow_sw = 0; + int_f(vars, "vt_software", video.vt.allow_sw, vt::allow_software_from_view); + video.vt.require_sw = 0; + int_f(vars, "vt_software", video.vt.require_sw, vt::force_software_from_view); + video.vt.realtime = 1; + int_f(vars, "vt_realtime", video.vt.realtime, vt::rt_from_view); + string_f(vars, "encoder", video.encoder); string_f(vars, "adapter_name", video.adapter_name); string_f(vars, "output_name", video.output_name); diff --git a/sunshine/config.h b/sunshine/config.h index 624e4225..18313961 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -34,6 +34,13 @@ struct video_t { int coder; } amd; + struct { + int allow_sw; + int require_sw; + int realtime; + int coder; + } vt; + std::string encoder; std::string adapter_name; std::string output_name; diff --git a/sunshine/input.cpp b/sunshine/input.cpp index 2a7e4486..050a8687 100644 --- a/sunshine/input.cpp +++ b/sunshine/input.cpp @@ -9,6 +9,7 @@ extern "C" { } #include +#include #include "config.h" #include "input.h" diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index 9d465749..4d1588a3 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -161,7 +161,7 @@ struct sink_t { // Play on host PC std::string host; - // On Windows, it is not possible to create a virtual sink + // On MacOS and Windows, it is not possible to create a virtual sink // Therefore, it is optional struct null_t { std::string stereo; diff --git a/sunshine/platform/macos/av_audio.h b/sunshine/platform/macos/av_audio.h new file mode 100644 index 00000000..8c04e5bb --- /dev/null +++ b/sunshine/platform/macos/av_audio.h @@ -0,0 +1,26 @@ +#ifndef SUNSHINE_PLATFORM_AV_AUDIO_H +#define SUNSHINE_PLATFORM_AV_AUDIO_H + +#import + +#include "sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.h" + +#define kBufferLength 2048 + +@interface AVAudio : NSObject { +@public + TPCircularBuffer audioSampleBuffer; +} + +@property(nonatomic, assign) AVCaptureSession *audioCaptureSession; +@property(nonatomic, assign) AVCaptureConnection *audioConnection; +@property(nonatomic, assign) NSCondition *samplesArrivedSignal; + ++ (NSArray *)microphoneNames; ++ (AVCaptureDevice *)findMicrophone:(NSString *)name; + +- (int)setupMicrophone:(AVCaptureDevice *)device sampleRate:(UInt32)sampleRate frameSize:(UInt32)frameSize channels:(UInt8)channels; + +@end + +#endif //SUNSHINE_PLATFORM_AV_AUDIO_H diff --git a/sunshine/platform/macos/av_audio.m b/sunshine/platform/macos/av_audio.m new file mode 100644 index 00000000..c5cd899e --- /dev/null +++ b/sunshine/platform/macos/av_audio.m @@ -0,0 +1,120 @@ +#import "av_audio.h" + +@implementation AVAudio + ++ (NSArray *)microphones { + AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInMicrophone, + AVCaptureDeviceTypeExternalUnknown] + mediaType:AVMediaTypeAudio + position:AVCaptureDevicePositionUnspecified]; + return discoverySession.devices; +} + ++ (NSArray *)microphoneNames { + NSMutableArray *result = [[NSMutableArray alloc] init]; + + for(AVCaptureDevice *device in [AVAudio microphones]) { + [result addObject:[device localizedName]]; + } + + return result; +} + ++ (AVCaptureDevice *)findMicrophone:(NSString *)name { + for(AVCaptureDevice *device in [AVAudio microphones]) { + if([[device localizedName] isEqualToString:name]) { + return device; + } + } + + return nil; +} + +- (void)dealloc { + // make sure we don't process any further samples + self.audioConnection = nil; + // make sure nothing gets stuck on this signal + [self.samplesArrivedSignal signal]; + [self.samplesArrivedSignal release]; + TPCircularBufferCleanup(&audioSampleBuffer); + [super dealloc]; +} + +- (int)setupMicrophone:(AVCaptureDevice *)device sampleRate:(UInt32)sampleRate frameSize:(UInt32)frameSize channels:(UInt8)channels { + self.audioCaptureSession = [[AVCaptureSession alloc] init]; + + NSError *error; + AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; + if(audioInput == nil) { + return -1; + } + + if([self.audioCaptureSession canAddInput:audioInput]) { + [self.audioCaptureSession addInput:audioInput]; + } + else { + [audioInput dealloc]; + return -1; + } + + AVCaptureAudioDataOutput *audioOutput = [[AVCaptureAudioDataOutput alloc] init]; + + [audioOutput setAudioSettings:@{ + (NSString *)AVFormatIDKey: [NSNumber numberWithUnsignedInt:kAudioFormatLinearPCM], + (NSString *)AVSampleRateKey: [NSNumber numberWithUnsignedInt:sampleRate], + (NSString *)AVNumberOfChannelsKey: [NSNumber numberWithUnsignedInt:channels], + (NSString *)AVLinearPCMBitDepthKey: [NSNumber numberWithUnsignedInt:16], + (NSString *)AVLinearPCMIsFloatKey: @NO, + (NSString *)AVLinearPCMIsNonInterleaved: @NO + }]; + + dispatch_queue_attr_t qos = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, + QOS_CLASS_USER_INITIATED, + DISPATCH_QUEUE_PRIORITY_HIGH); + dispatch_queue_t recordingQueue = dispatch_queue_create("audioSamplingQueue", qos); + + [audioOutput setSampleBufferDelegate:self queue:recordingQueue]; + + if([self.audioCaptureSession canAddOutput:audioOutput]) { + [self.audioCaptureSession addOutput:audioOutput]; + } + else { + [audioInput release]; + [audioOutput release]; + return -1; + } + + self.audioConnection = [audioOutput connectionWithMediaType:AVMediaTypeAudio]; + + [self.audioCaptureSession startRunning]; + + [audioInput release]; + [audioOutput release]; + + self.samplesArrivedSignal = [[NSCondition alloc] init]; + TPCircularBufferInit(&self->audioSampleBuffer, kBufferLength * channels); + + return 0; +} + +- (void)captureOutput:(AVCaptureOutput *)output + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection { + if(connection == self.audioConnection) { + AudioBufferList audioBufferList; + CMBlockBufferRef blockBuffer; + + CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer, NULL, &audioBufferList, sizeof(audioBufferList), NULL, NULL, 0, &blockBuffer); + + //NSAssert(audioBufferList.mNumberBuffers == 1, @"Expected interlveaved PCM format but buffer contained %u streams", audioBufferList.mNumberBuffers); + + // this is safe, because an interleaved PCM stream has exactly one buffer + // and we don't want to do sanity checks in a performance critical exec path + AudioBuffer audioBuffer = audioBufferList.mBuffers[0]; + + TPCircularBufferProduceBytes(&self->audioSampleBuffer, audioBuffer.mData, audioBuffer.mDataByteSize); + [self.samplesArrivedSignal signal]; + } +} + +@end diff --git a/sunshine/platform/macos/av_img_t.h b/sunshine/platform/macos/av_img_t.h new file mode 100644 index 00000000..7af3cbcc --- /dev/null +++ b/sunshine/platform/macos/av_img_t.h @@ -0,0 +1,18 @@ +#ifndef av_img_t_h +#define av_img_t_h + +#include "sunshine/platform/common.h" + +#include +#include + +namespace platf { +struct av_img_t : public img_t { + CVPixelBufferRef pixel_buffer = nullptr; + CMSampleBufferRef sample_buffer = nullptr; + + ~av_img_t(); +}; +} // namespace platf + +#endif /* av_img_t_h */ diff --git a/sunshine/platform/macos/av_video.h b/sunshine/platform/macos/av_video.h new file mode 100644 index 00000000..df441cae --- /dev/null +++ b/sunshine/platform/macos/av_video.h @@ -0,0 +1,43 @@ +#ifndef SUNSHINE_PLATFORM_AV_VIDEO_H +#define SUNSHINE_PLATFORM_AV_VIDEO_H + +#import + + +struct CaptureSession { + AVCaptureVideoDataOutput *output; + NSCondition *captureStopped; +}; + +@interface AVVideo : NSObject + +#define kMaxDisplays 32 + +@property(nonatomic, assign) CGDirectDisplayID displayID; +@property(nonatomic, assign) CMTime minFrameDuration; +@property(nonatomic, assign) OSType pixelFormat; +@property(nonatomic, assign) int frameWidth; +@property(nonatomic, assign) int frameHeight; +@property(nonatomic, assign) float scaling; +@property(nonatomic, assign) int paddingLeft; +@property(nonatomic, assign) int paddingRight; +@property(nonatomic, assign) int paddingTop; +@property(nonatomic, assign) int paddingBottom; + +typedef bool (^FrameCallbackBlock)(CMSampleBufferRef); + +@property(nonatomic, assign) AVCaptureSession *session; +@property(nonatomic, assign) NSMapTable *videoOutputs; +@property(nonatomic, assign) NSMapTable *captureCallbacks; +@property(nonatomic, assign) NSMapTable *captureSignals; + ++ (NSArray *)displayNames; + +- (id)initWithDisplay:(CGDirectDisplayID)displayID frameRate:(int)frameRate; + +- (void)setFrameWidth:(int)frameWidth frameHeight:(int)frameHeight; +- (dispatch_semaphore_t)capture:(FrameCallbackBlock)frameCallback; + +@end + +#endif //SUNSHINE_PLATFORM_AV_VIDEO_H diff --git a/sunshine/platform/macos/av_video.m b/sunshine/platform/macos/av_video.m new file mode 100644 index 00000000..f257d9c4 --- /dev/null +++ b/sunshine/platform/macos/av_video.m @@ -0,0 +1,184 @@ +#import "av_video.h" + +@implementation AVVideo + +// XXX: Currently, this function only returns the screen IDs as names, +// which is not very helpful to the user. The API to retrieve names +// was deprecated with 10.9+. +// However, there is a solution with little external code that can be used: +// https://stackoverflow.com/questions/20025868/cgdisplayioserviceport-is-deprecated-in-os-x-10-9-how-to-replace ++ (NSArray *)displayNames { + CGDirectDisplayID displays[kMaxDisplays]; + uint32_t count; + if(CGGetActiveDisplayList(kMaxDisplays, displays, &count) != kCGErrorSuccess) { + return [NSArray array]; + } + + NSMutableArray *result = [NSMutableArray array]; + + for(uint32_t i = 0; i < count; i++) { + [result addObject:@{ + @"id": [NSNumber numberWithUnsignedInt:displays[i]], + @"name": [NSString stringWithFormat:@"%d", displays[i]] + }]; + } + + return [NSArray arrayWithArray:result]; +} + +- (id)initWithDisplay:(CGDirectDisplayID)displayID frameRate:(int)frameRate { + self = [super init]; + + CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayID); + + self.displayID = displayID; + self.pixelFormat = kCVPixelFormatType_32BGRA; + self.frameWidth = CGDisplayModeGetPixelWidth(mode); + self.frameHeight = CGDisplayModeGetPixelHeight(mode); + self.scaling = CGDisplayPixelsWide(displayID) / CGDisplayModeGetPixelWidth(mode); + self.paddingLeft = 0; + self.paddingRight = 0; + self.paddingTop = 0; + self.paddingBottom = 0; + self.minFrameDuration = CMTimeMake(1, frameRate); + self.session = [[AVCaptureSession alloc] init]; + self.videoOutputs = [[NSMapTable alloc] init]; + self.captureCallbacks = [[NSMapTable alloc] init]; + self.captureSignals = [[NSMapTable alloc] init]; + + CFRelease(mode); + + AVCaptureScreenInput *screenInput = [[AVCaptureScreenInput alloc] initWithDisplayID:self.displayID]; + [screenInput setMinFrameDuration:self.minFrameDuration]; + + if([self.session canAddInput:screenInput]) { + [self.session addInput:screenInput]; + } + else { + [screenInput release]; + return nil; + } + + [self.session startRunning]; + + return self; +} + +- (void)dealloc { + [self.videoOutputs release]; + [self.captureCallbacks release]; + [self.captureSignals release]; + [self.session stopRunning]; + [super dealloc]; +} + +- (void)setFrameWidth:(int)frameWidth frameHeight:(int)frameHeight { + CGImageRef screenshot = CGDisplayCreateImage(self.displayID); + + self.frameWidth = frameWidth; + self.frameHeight = frameHeight; + + double screenRatio = (double)CGImageGetWidth(screenshot) / (double)CGImageGetHeight(screenshot); + double streamRatio = (double)frameWidth / (double)frameHeight; + + if(screenRatio < streamRatio) { + int padding = frameWidth - (frameHeight * screenRatio); + self.paddingLeft = padding / 2; + self.paddingRight = padding - self.paddingLeft; + self.paddingTop = 0; + self.paddingBottom = 0; + } + else { + int padding = frameHeight - (frameWidth / screenRatio); + self.paddingLeft = 0; + self.paddingRight = 0; + self.paddingTop = padding / 2; + self.paddingBottom = padding - self.paddingTop; + } + + // XXX: if the streamed image is larger than the native resolution, we add a black box around + // the frame. Instead the frame should be resized entirely. + int delta_width = frameWidth - (CGImageGetWidth(screenshot) + self.paddingLeft + self.paddingRight); + if(delta_width > 0) { + int adjust_left = delta_width / 2; + int adjust_right = delta_width - adjust_left; + self.paddingLeft += adjust_left; + self.paddingRight += adjust_right; + } + + int delta_height = frameHeight - (CGImageGetHeight(screenshot) + self.paddingTop + self.paddingBottom); + if(delta_height > 0) { + int adjust_top = delta_height / 2; + int adjust_bottom = delta_height - adjust_top; + self.paddingTop += adjust_top; + self.paddingBottom += adjust_bottom; + } + + CFRelease(screenshot); +} + +- (dispatch_semaphore_t)capture:(FrameCallbackBlock)frameCallback { + @synchronized(self) { + AVCaptureVideoDataOutput *videoOutput = [[AVCaptureVideoDataOutput alloc] init]; + + [videoOutput setVideoSettings:@{ + (NSString *)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithUnsignedInt:self.pixelFormat], + (NSString *)kCVPixelBufferWidthKey: [NSNumber numberWithInt:self.frameWidth], + (NSString *)kCVPixelBufferExtendedPixelsRightKey: [NSNumber numberWithInt:self.paddingRight], + (NSString *)kCVPixelBufferExtendedPixelsLeftKey: [NSNumber numberWithInt:self.paddingLeft], + (NSString *)kCVPixelBufferExtendedPixelsTopKey: [NSNumber numberWithInt:self.paddingTop], + (NSString *)kCVPixelBufferExtendedPixelsBottomKey: [NSNumber numberWithInt:self.paddingBottom], + (NSString *)kCVPixelBufferHeightKey: [NSNumber numberWithInt:self.frameHeight] + }]; + + dispatch_queue_attr_t qos = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, + QOS_CLASS_USER_INITIATED, + DISPATCH_QUEUE_PRIORITY_HIGH); + dispatch_queue_t recordingQueue = dispatch_queue_create("videoCaptureQueue", qos); + [videoOutput setSampleBufferDelegate:self queue:recordingQueue]; + + [self.session stopRunning]; + + if([self.session canAddOutput:videoOutput]) { + [self.session addOutput:videoOutput]; + } + else { + [videoOutput release]; + return nil; + } + + AVCaptureConnection *videoConnection = [videoOutput connectionWithMediaType:AVMediaTypeVideo]; + dispatch_semaphore_t signal = dispatch_semaphore_create(0); + + [self.videoOutputs setObject:videoOutput forKey:videoConnection]; + [self.captureCallbacks setObject:frameCallback forKey:videoConnection]; + [self.captureSignals setObject:signal forKey:videoConnection]; + + [self.session startRunning]; + + return signal; + } +} + +- (void)captureOutput:(AVCaptureOutput *)captureOutput + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection { + + FrameCallbackBlock callback = [self.captureCallbacks objectForKey:connection]; + + if(callback != nil) { + if(!callback(sampleBuffer)) { + @synchronized(self) { + [self.session stopRunning]; + [self.captureCallbacks removeObjectForKey:connection]; + [self.session removeOutput:[self.videoOutputs objectForKey:connection]]; + [self.videoOutputs removeObjectForKey:connection]; + dispatch_semaphore_signal([self.captureSignals objectForKey:connection]); + [self.captureSignals removeObjectForKey:connection]; + [self.session startRunning]; + } + } + } +} + +@end diff --git a/sunshine/platform/macos/display.mm b/sunshine/platform/macos/display.mm new file mode 100644 index 00000000..f00297c2 --- /dev/null +++ b/sunshine/platform/macos/display.mm @@ -0,0 +1,196 @@ +#include "sunshine/platform/common.h" +#include "sunshine/platform/macos/av_img_t.h" +#include "sunshine/platform/macos/av_video.h" +#include "sunshine/platform/macos/nv12_zero_device.h" + +#include "sunshine/config.h" +#include "sunshine/main.h" + +namespace fs = std::filesystem; + +namespace platf { +using namespace std::literals; + +av_img_t::~av_img_t() { + if(pixel_buffer != NULL) { + CVPixelBufferUnlockBaseAddress(pixel_buffer, 0); + } + + if(sample_buffer != nullptr) { + CFRelease(sample_buffer); + } + + data = nullptr; +} + +struct av_display_t : public display_t { + AVVideo *av_capture; + CGDirectDisplayID display_id; + + ~av_display_t() { + [av_capture release]; + } + + capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override { + __block auto img_next = std::move(img); + + auto signal = [av_capture capture:^(CMSampleBufferRef sampleBuffer) { + auto av_img_next = std::static_pointer_cast(img_next); + + CFRetain(sampleBuffer); + + CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); + + if(av_img_next->pixel_buffer != nullptr) + CVPixelBufferUnlockBaseAddress(av_img_next->pixel_buffer, 0); + + if(av_img_next->sample_buffer != nullptr) + CFRelease(av_img_next->sample_buffer); + + av_img_next->sample_buffer = sampleBuffer; + av_img_next->pixel_buffer = pixelBuffer; + img_next->data = (uint8_t *)CVPixelBufferGetBaseAddress(pixelBuffer); + + size_t extraPixels[4]; + CVPixelBufferGetExtendedPixels(pixelBuffer, &extraPixels[0], &extraPixels[1], &extraPixels[2], &extraPixels[3]); + + img_next->width = CVPixelBufferGetWidth(pixelBuffer) + extraPixels[0] + extraPixels[1]; + img_next->height = CVPixelBufferGetHeight(pixelBuffer) + extraPixels[2] + extraPixels[3]; + img_next->row_pitch = CVPixelBufferGetBytesPerRow(pixelBuffer); + img_next->pixel_pitch = img_next->row_pitch / img_next->width; + + img_next = snapshot_cb(img_next); + + return img_next != nullptr; + }]; + + dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER); + + return capture_e::ok; + } + + std::shared_ptr alloc_img() override { + return std::make_shared(); + } + + std::shared_ptr make_hwdevice(pix_fmt_e pix_fmt) override { + if(pix_fmt == pix_fmt_e::yuv420p) { + av_capture.pixelFormat = kCVPixelFormatType_32BGRA; + + return std::make_shared(); + } + else if(pix_fmt == pix_fmt_e::nv12) { + auto device = std::make_shared(); + + device->init(static_cast(av_capture), setResolution, setPixelFormat); + + return device; + } + else { + BOOST_LOG(error) << "Unsupported Pixel Format."sv; + return nullptr; + } + } + + int dummy_img(img_t *img) override { + auto signal = [av_capture capture:^(CMSampleBufferRef sampleBuffer) { + auto av_img = (av_img_t *)img; + + CFRetain(sampleBuffer); + + CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); + + // XXX: next_img->img should be moved to a smart pointer with + // the CFRelease as custom deallocator + if(av_img->pixel_buffer != nullptr) + CVPixelBufferUnlockBaseAddress(((av_img_t *)img)->pixel_buffer, 0); + + if(av_img->sample_buffer != nullptr) + CFRelease(av_img->sample_buffer); + + av_img->sample_buffer = sampleBuffer; + av_img->pixel_buffer = pixelBuffer; + img->data = (uint8_t *)CVPixelBufferGetBaseAddress(pixelBuffer); + + size_t extraPixels[4]; + CVPixelBufferGetExtendedPixels(pixelBuffer, &extraPixels[0], &extraPixels[1], &extraPixels[2], &extraPixels[3]); + + img->width = CVPixelBufferGetWidth(pixelBuffer) + extraPixels[0] + extraPixels[1]; + img->height = CVPixelBufferGetHeight(pixelBuffer) + extraPixels[2] + extraPixels[3]; + img->row_pitch = CVPixelBufferGetBytesPerRow(pixelBuffer); + img->pixel_pitch = img->row_pitch / img->width; + + return false; + }]; + + dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER); + + return 0; + } + + /** + * A bridge from the pure C++ code of the hwdevice_t class to the pure Objective C code. + * + * display --> an opaque pointer to an object of this class + * width --> the intended capture width + * height --> the intended capture height + */ + static void setResolution(void *display, int width, int height) { + [static_cast(display) setFrameWidth:width frameHeight:height]; + } + + static void setPixelFormat(void *display, OSType pixelFormat) { + static_cast(display).pixelFormat = pixelFormat; + } +}; + +std::shared_ptr display(platf::mem_type_e hwdevice_type, const std::string &display_name, int framerate) { + if(hwdevice_type != platf::mem_type_e::system) { + BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv; + return nullptr; + } + + auto display = std::make_shared(); + + display->display_id = CGMainDisplayID(); + if(!display_name.empty()) { + auto display_array = [AVVideo displayNames]; + + for(NSDictionary *item in display_array) { + NSString *name = item[@"name"]; + if(name.UTF8String == display_name) { + NSNumber *display_id = item[@"id"]; + display->display_id = [display_id unsignedIntValue]; + } + } + } + + display->av_capture = [[AVVideo alloc] initWithDisplay:display->display_id frameRate:framerate]; + + if(!display->av_capture) { + BOOST_LOG(error) << "Video setup failed."sv; + return nullptr; + } + + display->width = display->av_capture.frameWidth; + display->height = display->av_capture.frameHeight; + + return display; +} + +std::vector display_names(mem_type_e hwdevice_type) { + __block std::vector display_names; + + auto display_array = [AVVideo displayNames]; + + display_names.reserve([display_array count]); + [display_array enumerateObjectsUsingBlock:^(NSDictionary *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { + NSString *name = obj[@"name"]; + display_names.push_back(name.UTF8String); + }]; + + return display_names; +} +} diff --git a/sunshine/platform/macos/input.cpp b/sunshine/platform/macos/input.cpp new file mode 100644 index 00000000..fbea6619 --- /dev/null +++ b/sunshine/platform/macos/input.cpp @@ -0,0 +1,465 @@ +#import +#include +#include + +#include "sunshine/main.h" +#include "sunshine/platform/common.h" +#include "sunshine/utility.h" + +// Delay for a double click +// FIXME: we probably want to make this configurable +#define MULTICLICK_DELAY_NS 500000000 + +namespace platf { +using namespace std::literals; + +struct macos_input_t { +public: + CGDirectDisplayID display; + CGFloat displayScaling; + CGEventSourceRef source; + + // keyboard related stuff + CGEventRef kb_event; + CGEventFlags kb_flags; + + // mouse related stuff + CGEventRef mouse_event; // mouse event source + bool mouse_down[3]; // mouse button status + uint64_t last_mouse_event[3][2]; // timestamp of last mouse events +}; + +// A struct to hold a Windows keycode to Mac virtual keycode mapping. +struct KeyCodeMap { + int win_keycode; + int mac_keycode; +}; + +// Customized less operator for using std::lower_bound() on a KeyCodeMap array. +bool operator<(const KeyCodeMap &a, const KeyCodeMap &b) { + return a.win_keycode < b.win_keycode; +} + +// clang-format off +const KeyCodeMap kKeyCodesMap[] = { + { 0x08 /* VKEY_BACK */, kVK_Delete }, + { 0x09 /* VKEY_TAB */, kVK_Tab }, + { 0x0A /* VKEY_BACKTAB */, 0x21E4 }, + { 0x0C /* VKEY_CLEAR */, kVK_ANSI_KeypadClear }, + { 0x0D /* VKEY_RETURN */, kVK_Return }, + { 0x10 /* VKEY_SHIFT */, kVK_Shift }, + { 0x11 /* VKEY_CONTROL */, kVK_Control }, + { 0x12 /* VKEY_MENU */, kVK_Option }, + { 0x13 /* VKEY_PAUSE */, -1 }, + { 0x14 /* VKEY_CAPITAL */, kVK_CapsLock }, + { 0x15 /* VKEY_KANA */, kVK_JIS_Kana }, + { 0x15 /* VKEY_HANGUL */, -1 }, + { 0x17 /* VKEY_JUNJA */, -1 }, + { 0x18 /* VKEY_FINAL */, -1 }, + { 0x19 /* VKEY_HANJA */, -1 }, + { 0x19 /* VKEY_KANJI */, -1 }, + { 0x1B /* VKEY_ESCAPE */, kVK_Escape }, + { 0x1C /* VKEY_CONVERT */, -1 }, + { 0x1D /* VKEY_NONCONVERT */, -1 }, + { 0x1E /* VKEY_ACCEPT */, -1 }, + { 0x1F /* VKEY_MODECHANGE */, -1 }, + { 0x20 /* VKEY_SPACE */, kVK_Space }, + { 0x21 /* VKEY_PRIOR */, kVK_PageUp }, + { 0x22 /* VKEY_NEXT */, kVK_PageDown }, + { 0x23 /* VKEY_END */, kVK_End }, + { 0x24 /* VKEY_HOME */, kVK_Home }, + { 0x25 /* VKEY_LEFT */, kVK_LeftArrow }, + { 0x26 /* VKEY_UP */, kVK_UpArrow }, + { 0x27 /* VKEY_RIGHT */, kVK_RightArrow }, + { 0x28 /* VKEY_DOWN */, kVK_DownArrow }, + { 0x29 /* VKEY_SELECT */, -1 }, + { 0x2A /* VKEY_PRINT */, -1 }, + { 0x2B /* VKEY_EXECUTE */, -1 }, + { 0x2C /* VKEY_SNAPSHOT */, -1 }, + { 0x2D /* VKEY_INSERT */, kVK_Help }, + { 0x2E /* VKEY_DELETE */, kVK_ForwardDelete }, + { 0x2F /* VKEY_HELP */, kVK_Help }, + { 0x30 /* VKEY_0 */, kVK_ANSI_0 }, + { 0x31 /* VKEY_1 */, kVK_ANSI_1 }, + { 0x32 /* VKEY_2 */, kVK_ANSI_2 }, + { 0x33 /* VKEY_3 */, kVK_ANSI_3 }, + { 0x34 /* VKEY_4 */, kVK_ANSI_4 }, + { 0x35 /* VKEY_5 */, kVK_ANSI_5 }, + { 0x36 /* VKEY_6 */, kVK_ANSI_6 }, + { 0x37 /* VKEY_7 */, kVK_ANSI_7 }, + { 0x38 /* VKEY_8 */, kVK_ANSI_8 }, + { 0x39 /* VKEY_9 */, kVK_ANSI_9 }, + { 0x41 /* VKEY_A */, kVK_ANSI_A }, + { 0x42 /* VKEY_B */, kVK_ANSI_B }, + { 0x43 /* VKEY_C */, kVK_ANSI_C }, + { 0x44 /* VKEY_D */, kVK_ANSI_D }, + { 0x45 /* VKEY_E */, kVK_ANSI_E }, + { 0x46 /* VKEY_F */, kVK_ANSI_F }, + { 0x47 /* VKEY_G */, kVK_ANSI_G }, + { 0x48 /* VKEY_H */, kVK_ANSI_H }, + { 0x49 /* VKEY_I */, kVK_ANSI_I }, + { 0x4A /* VKEY_J */, kVK_ANSI_J }, + { 0x4B /* VKEY_K */, kVK_ANSI_K }, + { 0x4C /* VKEY_L */, kVK_ANSI_L }, + { 0x4D /* VKEY_M */, kVK_ANSI_M }, + { 0x4E /* VKEY_N */, kVK_ANSI_N }, + { 0x4F /* VKEY_O */, kVK_ANSI_O }, + { 0x50 /* VKEY_P */, kVK_ANSI_P }, + { 0x51 /* VKEY_Q */, kVK_ANSI_Q }, + { 0x52 /* VKEY_R */, kVK_ANSI_R }, + { 0x53 /* VKEY_S */, kVK_ANSI_S }, + { 0x54 /* VKEY_T */, kVK_ANSI_T }, + { 0x55 /* VKEY_U */, kVK_ANSI_U }, + { 0x56 /* VKEY_V */, kVK_ANSI_V }, + { 0x57 /* VKEY_W */, kVK_ANSI_W }, + { 0x58 /* VKEY_X */, kVK_ANSI_X }, + { 0x59 /* VKEY_Y */, kVK_ANSI_Y }, + { 0x5A /* VKEY_Z */, kVK_ANSI_Z }, + { 0x5B /* VKEY_LWIN */, kVK_Command }, + { 0x5C /* VKEY_RWIN */, kVK_RightCommand }, + { 0x5D /* VKEY_APPS */, kVK_RightCommand }, + { 0x5F /* VKEY_SLEEP */, -1 }, + { 0x60 /* VKEY_NUMPAD0 */, kVK_ANSI_Keypad0 }, + { 0x61 /* VKEY_NUMPAD1 */, kVK_ANSI_Keypad1 }, + { 0x62 /* VKEY_NUMPAD2 */, kVK_ANSI_Keypad2 }, + { 0x63 /* VKEY_NUMPAD3 */, kVK_ANSI_Keypad3 }, + { 0x64 /* VKEY_NUMPAD4 */, kVK_ANSI_Keypad4 }, + { 0x65 /* VKEY_NUMPAD5 */, kVK_ANSI_Keypad5 }, + { 0x66 /* VKEY_NUMPAD6 */, kVK_ANSI_Keypad6 }, + { 0x67 /* VKEY_NUMPAD7 */, kVK_ANSI_Keypad7 }, + { 0x68 /* VKEY_NUMPAD8 */, kVK_ANSI_Keypad8 }, + { 0x69 /* VKEY_NUMPAD9 */, kVK_ANSI_Keypad9 }, + { 0x6A /* VKEY_MULTIPLY */, kVK_ANSI_KeypadMultiply }, + { 0x6B /* VKEY_ADD */, kVK_ANSI_KeypadPlus }, + { 0x6C /* VKEY_SEPARATOR */, -1 }, + { 0x6D /* VKEY_SUBTRACT */, kVK_ANSI_KeypadMinus }, + { 0x6E /* VKEY_DECIMAL */, kVK_ANSI_KeypadDecimal }, + { 0x6F /* VKEY_DIVIDE */, kVK_ANSI_KeypadDivide }, + { 0x70 /* VKEY_F1 */, kVK_F1 }, + { 0x71 /* VKEY_F2 */, kVK_F2 }, + { 0x72 /* VKEY_F3 */, kVK_F3 }, + { 0x73 /* VKEY_F4 */, kVK_F4 }, + { 0x74 /* VKEY_F5 */, kVK_F5 }, + { 0x75 /* VKEY_F6 */, kVK_F6 }, + { 0x76 /* VKEY_F7 */, kVK_F7 }, + { 0x77 /* VKEY_F8 */, kVK_F8 }, + { 0x78 /* VKEY_F9 */, kVK_F9 }, + { 0x79 /* VKEY_F10 */, kVK_F10 }, + { 0x7A /* VKEY_F11 */, kVK_F11 }, + { 0x7B /* VKEY_F12 */, kVK_F12 }, + { 0x7C /* VKEY_F13 */, kVK_F13 }, + { 0x7D /* VKEY_F14 */, kVK_F14 }, + { 0x7E /* VKEY_F15 */, kVK_F15 }, + { 0x7F /* VKEY_F16 */, kVK_F16 }, + { 0x80 /* VKEY_F17 */, kVK_F17 }, + { 0x81 /* VKEY_F18 */, kVK_F18 }, + { 0x82 /* VKEY_F19 */, kVK_F19 }, + { 0x83 /* VKEY_F20 */, kVK_F20 }, + { 0x84 /* VKEY_F21 */, -1 }, + { 0x85 /* VKEY_F22 */, -1 }, + { 0x86 /* VKEY_F23 */, -1 }, + { 0x87 /* VKEY_F24 */, -1 }, + { 0x90 /* VKEY_NUMLOCK */, -1 }, + { 0x91 /* VKEY_SCROLL */, -1 }, + { 0xA0 /* VKEY_LSHIFT */, kVK_Shift }, + { 0xA1 /* VKEY_RSHIFT */, kVK_RightShift }, + { 0xA2 /* VKEY_LCONTROL */, kVK_Control }, + { 0xA3 /* VKEY_RCONTROL */, kVK_RightControl }, + { 0xA4 /* VKEY_LMENU */, kVK_Option }, + { 0xA5 /* VKEY_RMENU */, kVK_RightOption }, + { 0xA6 /* VKEY_BROWSER_BACK */, -1 }, + { 0xA7 /* VKEY_BROWSER_FORWARD */, -1 }, + { 0xA8 /* VKEY_BROWSER_REFRESH */, -1 }, + { 0xA9 /* VKEY_BROWSER_STOP */, -1 }, + { 0xAA /* VKEY_BROWSER_SEARCH */, -1 }, + { 0xAB /* VKEY_BROWSER_FAVORITES */, -1 }, + { 0xAC /* VKEY_BROWSER_HOME */, -1 }, + { 0xAD /* VKEY_VOLUME_MUTE */, -1 }, + { 0xAE /* VKEY_VOLUME_DOWN */, -1 }, + { 0xAF /* VKEY_VOLUME_UP */, -1 }, + { 0xB0 /* VKEY_MEDIA_NEXT_TRACK */, -1 }, + { 0xB1 /* VKEY_MEDIA_PREV_TRACK */, -1 }, + { 0xB2 /* VKEY_MEDIA_STOP */, -1 }, + { 0xB3 /* VKEY_MEDIA_PLAY_PAUSE */, -1 }, + { 0xB4 /* VKEY_MEDIA_LAUNCH_MAIL */, -1 }, + { 0xB5 /* VKEY_MEDIA_LAUNCH_MEDIA_SELECT */, -1 }, + { 0xB6 /* VKEY_MEDIA_LAUNCH_APP1 */, -1 }, + { 0xB7 /* VKEY_MEDIA_LAUNCH_APP2 */, -1 }, + { 0xBA /* VKEY_OEM_1 */, kVK_ANSI_Semicolon }, + { 0xBB /* VKEY_OEM_PLUS */, kVK_ANSI_Equal }, + { 0xBC /* VKEY_OEM_COMMA */, kVK_ANSI_Comma }, + { 0xBD /* VKEY_OEM_MINUS */, kVK_ANSI_Minus }, + { 0xBE /* VKEY_OEM_PERIOD */, kVK_ANSI_Period }, + { 0xBF /* VKEY_OEM_2 */, kVK_ANSI_Slash }, + { 0xC0 /* VKEY_OEM_3 */, kVK_ANSI_Grave }, + { 0xDB /* VKEY_OEM_4 */, kVK_ANSI_LeftBracket }, + { 0xDC /* VKEY_OEM_5 */, kVK_ANSI_Backslash }, + { 0xDD /* VKEY_OEM_6 */, kVK_ANSI_RightBracket }, + { 0xDE /* VKEY_OEM_7 */, kVK_ANSI_Quote }, + { 0xDF /* VKEY_OEM_8 */, -1 }, + { 0xE2 /* VKEY_OEM_102 */, -1 }, + { 0xE5 /* VKEY_PROCESSKEY */, -1 }, + { 0xE7 /* VKEY_PACKET */, -1 }, + { 0xF6 /* VKEY_ATTN */, -1 }, + { 0xF7 /* VKEY_CRSEL */, -1 }, + { 0xF8 /* VKEY_EXSEL */, -1 }, + { 0xF9 /* VKEY_EREOF */, -1 }, + { 0xFA /* VKEY_PLAY */, -1 }, + { 0xFB /* VKEY_ZOOM */, -1 }, + { 0xFC /* VKEY_NONAME */, -1 }, + { 0xFD /* VKEY_PA1 */, -1 }, + { 0xFE /* VKEY_OEM_CLEAR */, kVK_ANSI_KeypadClear } +}; +// clang-format on + +int keysym(int keycode) { + KeyCodeMap key_map; + + key_map.win_keycode = keycode; + const KeyCodeMap *temp_map = std::lower_bound( + kKeyCodesMap, kKeyCodesMap + sizeof(kKeyCodesMap) / sizeof(kKeyCodesMap[0]), key_map); + + if(temp_map >= kKeyCodesMap + sizeof(kKeyCodesMap) / sizeof(kKeyCodesMap[0]) || + temp_map->win_keycode != keycode || temp_map->mac_keycode == -1) { + return -1; + } + + return temp_map->mac_keycode; +} + +void keyboard(input_t &input, uint16_t modcode, bool release) { + auto key = keysym(modcode); + + BOOST_LOG(debug) << "got keycode: 0x"sv << std::hex << modcode << ", translated to: 0x" << std::hex << key << ", release:" << release; + + if(key < 0) { + return; + } + + auto macos_input = ((macos_input_t *)input.get()); + auto event = macos_input->kb_event; + + if(key == kVK_Shift || key == kVK_RightShift || + key == kVK_Command || key == kVK_RightCommand || + key == kVK_Option || key == kVK_RightOption || + key == kVK_Control || key == kVK_RightControl) { + + CGEventFlags mask; + + switch(key) { + case kVK_Shift: + case kVK_RightShift: + mask = kCGEventFlagMaskShift; + break; + case kVK_Command: + case kVK_RightCommand: + mask = kCGEventFlagMaskCommand; + break; + case kVK_Option: + case kVK_RightOption: + mask = kCGEventFlagMaskAlternate; + break; + case kVK_Control: + case kVK_RightControl: + mask = kCGEventFlagMaskControl; + break; + } + + macos_input->kb_flags = release ? macos_input->kb_flags & ~mask : macos_input->kb_flags | mask; + CGEventSetType(event, kCGEventFlagsChanged); + CGEventSetFlags(event, macos_input->kb_flags); + } + else { + CGEventSetIntegerValueField(event, kCGKeyboardEventKeycode, key); + CGEventSetType(event, release ? kCGEventKeyUp : kCGEventKeyDown); + } + + CGEventPost(kCGHIDEventTap, event); +} + +int alloc_gamepad(input_t &input, int nr, rumble_queue_t rumble_queue) { + BOOST_LOG(info) << "alloc_gamepad: Gamepad not yet implemented for MacOS."sv; + return -1; +} + +void free_gamepad(input_t &input, int nr) { + BOOST_LOG(info) << "free_gamepad: Gamepad not yet implemented for MacOS."sv; +} + +void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) { + BOOST_LOG(info) << "gamepad: Gamepad not yet implemented for MacOS."sv; +} + +// returns current mouse location: +inline CGPoint get_mouse_loc(input_t &input) { + return CGEventGetLocation(((macos_input_t *)input.get())->mouse_event); +} + +void post_mouse(input_t &input, CGMouseButton button, CGEventType type, CGPoint location, int click_count) { + BOOST_LOG(debug) << "mouse_event: "sv << button << ", type: "sv << type << ", location:"sv << location.x << ":"sv << location.y << " click_count: "sv << click_count; + + auto macos_input = (macos_input_t *)input.get(); + auto display = macos_input->display; + auto event = macos_input->mouse_event; + + if(location.x < 0) + location.x = 0; + if(location.x >= CGDisplayPixelsWide(display)) + location.x = CGDisplayPixelsWide(display) - 1; + + if(location.y < 0) + location.y = 0; + if(location.y >= CGDisplayPixelsHigh(display)) + location.y = CGDisplayPixelsHigh(display) - 1; + + CGEventSetType(event, type); + CGEventSetLocation(event, location); + CGEventSetIntegerValueField(event, kCGMouseEventButtonNumber, button); + CGEventSetIntegerValueField(event, kCGMouseEventClickState, click_count); + + CGEventPost(kCGHIDEventTap, event); + + // For why this is here, see: + // https://stackoverflow.com/questions/15194409/simulated-mouseevent-not-working-properly-osx + CGWarpMouseCursorPosition(location); +} + +inline CGEventType event_type_mouse(input_t &input) { + auto macos_input = ((macos_input_t *)input.get()); + + if(macos_input->mouse_down[0]) { + return kCGEventLeftMouseDragged; + } + else if(macos_input->mouse_down[1]) { + return kCGEventOtherMouseDragged; + } + else if(macos_input->mouse_down[2]) { + return kCGEventRightMouseDragged; + } + else { + return kCGEventMouseMoved; + } +} + +void move_mouse(input_t &input, int deltaX, int deltaY) { + auto current = get_mouse_loc(input); + + CGPoint location = CGPointMake(current.x + deltaX, current.y + deltaY); + + post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, 0); +} + +void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) { + auto scaling = ((macos_input_t *)input.get())->displayScaling; + + CGPoint location = CGPointMake(x * scaling, y * scaling); + + post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, 0); +} + +uint64_t time_diff(uint64_t start) { + uint64_t elapsed; + Nanoseconds elapsedNano; + + elapsed = mach_absolute_time() - start; + elapsedNano = AbsoluteToNanoseconds(*(AbsoluteTime *)&elapsed); + + return *(uint64_t *)&elapsedNano; +} + +void button_mouse(input_t &input, int button, bool release) { + CGMouseButton mac_button; + CGEventType event; + + auto mouse = ((macos_input_t *)input.get()); + + switch(button) { + case 1: + mac_button = kCGMouseButtonLeft; + event = release ? kCGEventLeftMouseUp : kCGEventLeftMouseDown; + break; + case 2: + mac_button = kCGMouseButtonCenter; + event = release ? kCGEventOtherMouseUp : kCGEventOtherMouseDown; + break; + case 3: + mac_button = kCGMouseButtonRight; + event = release ? kCGEventRightMouseUp : kCGEventRightMouseDown; + break; + default: + BOOST_LOG(warning) << "Unsupported mouse button for MacOS: "sv << button; + return; + } + + mouse->mouse_down[mac_button] = !release; + + // if the last mouse down was less than MULTICLICK_DELAY_NS, we send a double click event + if(time_diff(mouse->last_mouse_event[mac_button][release]) < MULTICLICK_DELAY_NS) { + post_mouse(input, mac_button, event, get_mouse_loc(input), 2); + } + else { + post_mouse(input, mac_button, event, get_mouse_loc(input), 1); + } + + mouse->last_mouse_event[mac_button][release] = mach_absolute_time(); +} + +void scroll(input_t &input, int high_res_distance) { + CGEventRef upEvent = CGEventCreateScrollWheelEvent( + NULL, + kCGScrollEventUnitLine, + 2, high_res_distance > 0 ? 1 : -1, high_res_distance); + CGEventPost(kCGHIDEventTap, upEvent); + CFRelease(upEvent); +} + +input_t input() { + input_t result { new macos_input_t() }; + + auto macos_input = (macos_input_t *)result.get(); + + // If we don't use the main display in the future, this has to be adapted + macos_input->display = CGMainDisplayID(); + + // Input coordinates are based on the virtual resolution not the physical, so we need the scaling factor + CGDisplayModeRef mode = CGDisplayCopyDisplayMode(macos_input->display); + macos_input->displayScaling = ((CGFloat)CGDisplayPixelsWide(macos_input->display)) / ((CGFloat)CGDisplayModeGetPixelWidth(mode)); + CFRelease(mode); + + macos_input->source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + + macos_input->kb_event = CGEventCreate(macos_input->source); + macos_input->kb_flags = 0; + + macos_input->mouse_event = CGEventCreate(macos_input->source); + macos_input->mouse_down[0] = false; + macos_input->mouse_down[1] = false; + macos_input->mouse_down[2] = false; + macos_input->last_mouse_event[0][0] = 0; + macos_input->last_mouse_event[0][1] = 0; + macos_input->last_mouse_event[1][0] = 0; + macos_input->last_mouse_event[1][1] = 0; + macos_input->last_mouse_event[2][0] = 0; + macos_input->last_mouse_event[2][1] = 0; + + BOOST_LOG(debug) << "Display "sv << macos_input->display << ", pixel dimention: " << CGDisplayPixelsWide(macos_input->display) << "x"sv << CGDisplayPixelsHigh(macos_input->display); + + return result; +} + +void freeInput(void *p) { + auto *input = (macos_input_t *)p; + + CFRelease(input->source); + CFRelease(input->kb_event); + CFRelease(input->mouse_event); + + delete input; +} + +std::vector &supported_gamepads() { + static std::vector gamepads { ""sv }; + + return gamepads; +} +} // namespace platf diff --git a/sunshine/platform/macos/microphone.mm b/sunshine/platform/macos/microphone.mm new file mode 100644 index 00000000..4b651690 --- /dev/null +++ b/sunshine/platform/macos/microphone.mm @@ -0,0 +1,87 @@ +#include "sunshine/platform/common.h" +#include "sunshine/platform/macos/av_audio.h" + +#include "sunshine/config.h" +#include "sunshine/main.h" + +namespace platf { +using namespace std::literals; + +struct av_mic_t : public mic_t { + AVAudio *av_audio_capture; + + ~av_mic_t() { + [av_audio_capture release]; + } + + capture_e sample(std::vector &sample_in) override { + auto sample_size = sample_in.size(); + + uint32_t length = 0; + void *byteSampleBuffer = TPCircularBufferTail(&av_audio_capture->audioSampleBuffer, &length); + + while(length < sample_size * sizeof(std::int16_t)) { + [av_audio_capture.samplesArrivedSignal wait]; + byteSampleBuffer = TPCircularBufferTail(&av_audio_capture->audioSampleBuffer, &length); + } + + const int16_t *sampleBuffer = (int16_t *)byteSampleBuffer; + std::vector vectorBuffer(sampleBuffer, sampleBuffer + sample_size); + + std::copy_n(std::begin(vectorBuffer), sample_size, std::begin(sample_in)); + + TPCircularBufferConsume(&av_audio_capture->audioSampleBuffer, sample_size * sizeof(std::int16_t)); + + return capture_e::ok; + } +}; + +struct macos_audio_control_t : public audio_control_t { + AVCaptureDevice *audio_capture_device; + +public: + int set_sink(const std::string &sink) override { + BOOST_LOG(warning) << "audio_control_t::set_sink() unimplemented: "sv << sink; + return 0; + } + + std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override { + auto mic = std::make_unique(); + const char *audio_sink = ""; + + if(!config::audio.sink.empty()) { + audio_sink = config::audio.sink.c_str(); + } + + if((audio_capture_device = [AVAudio findMicrophone:[NSString stringWithUTF8String:audio_sink]]) == nullptr) { + BOOST_LOG(error) << "opening microphone '"sv << audio_sink << "' failed. Please set a valid input source in the Sunshine config."sv; + BOOST_LOG(error) << "Available inputs:"sv; + + for(NSString *name in [AVAudio microphoneNames]) { + BOOST_LOG(error) << "\t"sv << [name UTF8String]; + } + + return nullptr; + } + + mic->av_audio_capture = [[AVAudio alloc] init]; + + if([mic->av_audio_capture setupMicrophone:audio_capture_device sampleRate:sample_rate frameSize:frame_size channels:channels]) { + BOOST_LOG(error) << "Failed to setup microphone."sv; + return nullptr; + } + + return mic; + } + + std::optional sink_info() override { + sink_t sink; + + return sink; + } +}; + +std::unique_ptr audio_control() { + return std::make_unique(); +} +} diff --git a/sunshine/platform/macos/misc.cpp b/sunshine/platform/macos/misc.cpp new file mode 100644 index 00000000..fdc46688 --- /dev/null +++ b/sunshine/platform/macos/misc.cpp @@ -0,0 +1,161 @@ +#include +#include +#include +#include +#include +#include + +#include "misc.h" +#include "sunshine/main.h" +#include "sunshine/platform/common.h" + +using namespace std::literals; +namespace fs = std::filesystem; + +namespace platf { +std::unique_ptr init() { + if(!CGPreflightScreenCaptureAccess()) { + BOOST_LOG(error) << "No screen capture permission!"sv; + BOOST_LOG(error) << "Please activate it in 'System Preferences' -> 'Privacy' -> 'Screen Recording'"sv; + CGRequestScreenCaptureAccess(); + return nullptr; + } + return std::make_unique(); +} + +fs::path appdata() { + const char *homedir; + if((homedir = getenv("HOME")) == nullptr) { + homedir = getpwuid(geteuid())->pw_dir; + } + + return fs::path { homedir } / ".config/sunshine"sv; +} + +using ifaddr_t = util::safe_ptr; + +ifaddr_t get_ifaddrs() { + ifaddrs *p { nullptr }; + + getifaddrs(&p); + + return ifaddr_t { p }; +} + +std::string from_sockaddr(const sockaddr *const ip_addr) { + char data[INET6_ADDRSTRLEN]; + + auto family = ip_addr->sa_family; + if(family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data, + INET6_ADDRSTRLEN); + } + + if(family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data, + INET_ADDRSTRLEN); + } + + return std::string { data }; +} + +std::pair from_sockaddr_ex(const sockaddr *const ip_addr) { + char data[INET6_ADDRSTRLEN]; + + auto family = ip_addr->sa_family; + std::uint16_t port; + if(family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data, + INET6_ADDRSTRLEN); + port = ((sockaddr_in6 *)ip_addr)->sin6_port; + } + + if(family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data, + INET_ADDRSTRLEN); + port = ((sockaddr_in *)ip_addr)->sin_port; + } + + return { port, std::string { data } }; +} + +std::string get_mac_address(const std::string_view &address) { + auto ifaddrs = get_ifaddrs(); + + for(auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) { + if(pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) { + BOOST_LOG(verbose) << "Looking for MAC of "sv << pos->ifa_name; + + struct ifaddrs *ifap, *ifaptr; + unsigned char *ptr; + std::string mac_address; + + if(getifaddrs(&ifap) == 0) { + for(ifaptr = ifap; ifaptr != NULL; ifaptr = (ifaptr)->ifa_next) { + if(!strcmp((ifaptr)->ifa_name, pos->ifa_name) && (((ifaptr)->ifa_addr)->sa_family == AF_LINK)) { + ptr = (unsigned char *)LLADDR((struct sockaddr_dl *)(ifaptr)->ifa_addr); + char buff[100]; + + snprintf(buff, sizeof(buff), "%02x:%02x:%02x:%02x:%02x:%02x", + *ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3), *(ptr + 4), *(ptr + 5)); + mac_address = buff; + break; + } + } + + freeifaddrs(ifap); + + if(ifaptr != NULL) { + BOOST_LOG(verbose) << "Found MAC of "sv << pos->ifa_name << ": "sv << mac_address; + return mac_address; + } + } + } + } + + BOOST_LOG(warning) << "Unable to find MAC address for "sv << address; + return "00:00:00:00:00:00"s; +} +} // namespace platf + +namespace dyn { +void *handle(const std::vector &libs) { + void *handle; + + for(auto lib : libs) { + handle = dlopen(lib, RTLD_LAZY | RTLD_LOCAL); + if(handle) { + return handle; + } + } + + std::stringstream ss; + ss << "Couldn't find any of the following libraries: ["sv << libs.front(); + std::for_each(std::begin(libs) + 1, std::end(libs), [&](auto lib) { + ss << ", "sv << lib; + }); + + ss << ']'; + + BOOST_LOG(error) << ss.str(); + + return nullptr; +} + +int load(void *handle, const std::vector> &funcs, bool strict) { + int err = 0; + for(auto &func : funcs) { + TUPLE_2D_REF(fn, name, func); + + *fn = (void (*)())dlsym(handle, name); + + if(!*fn && strict) { + BOOST_LOG(error) << "Couldn't find function: "sv << name; + + err = -1; + } + } + + return err; +} +} // namespace dyn diff --git a/sunshine/platform/macos/misc.h b/sunshine/platform/macos/misc.h new file mode 100644 index 00000000..f0d04a33 --- /dev/null +++ b/sunshine/platform/macos/misc.h @@ -0,0 +1,16 @@ +#ifndef SUNSHINE_PLATFORM_MISC_H +#define SUNSHINE_PLATFORM_MISC_H + +#include + +#include + +namespace dyn { +typedef void (*apiproc)(void); + +int load(void *handle, const std::vector> &funcs, bool strict = true); +void *handle(const std::vector &libs); + +} // namespace dyn + +#endif diff --git a/sunshine/platform/macos/nv12_zero_device.cpp b/sunshine/platform/macos/nv12_zero_device.cpp new file mode 100644 index 00000000..7e0a4a77 --- /dev/null +++ b/sunshine/platform/macos/nv12_zero_device.cpp @@ -0,0 +1,82 @@ +#include "sunshine/platform/macos/nv12_zero_device.h" +#include "sunshine/platform/macos/av_img_t.h" + +#include "sunshine/video.h" + +extern "C" { +#include "libavutil/imgutils.h" +} + +namespace platf { + +void free_frame(AVFrame *frame) { + av_frame_free(&frame); +} + +util::safe_ptr av_frame; + +int nv12_zero_device::convert(platf::img_t &img) { + av_frame_make_writable(av_frame.get()); + + av_img_t *av_img = (av_img_t *)&img; + + size_t left_pad, right_pad, top_pad, bottom_pad; + CVPixelBufferGetExtendedPixels(av_img->pixel_buffer, &left_pad, &right_pad, &top_pad, &bottom_pad); + + const uint8_t *data = (const uint8_t *)CVPixelBufferGetBaseAddressOfPlane(av_img->pixel_buffer, 0) - left_pad - (top_pad * img.width); + + int result = av_image_fill_arrays(av_frame->data, av_frame->linesize, data, (AVPixelFormat)av_frame->format, img.width, img.height, 32); + + // We will create the black bars for the padding top/bottom or left/right here in very cheap way. + // The luminance is 0, therefore, we simply need to set the chroma values to 128 for each pixel + // for black bars (instead of green with chroma 0). However, this only works 100% correct, when + // the resolution is devisable by 32. This could be improved by calculating the chroma values for + // the outer content pixels, which should introduce only a minor performance hit. + // + // XXX: Improve the algorithm to take into account the outer pixels + + size_t uv_plane_height = CVPixelBufferGetHeightOfPlane(av_img->pixel_buffer, 1); + + if(left_pad || right_pad) { + for(int l = 0; l < uv_plane_height + (top_pad / 2); l++) { + int line = l * av_frame->linesize[1]; + memset((void *)&av_frame->data[1][line], 128, (size_t)left_pad); + memset((void *)&av_frame->data[1][line + img.width - right_pad], 128, right_pad); + } + } + + if(top_pad || bottom_pad) { + memset((void *)&av_frame->data[1][0], 128, (top_pad / 2) * av_frame->linesize[1]); + memset((void *)&av_frame->data[1][((top_pad / 2) + uv_plane_height) * av_frame->linesize[1]], 128, bottom_pad / 2 * av_frame->linesize[1]); + } + + return result > 0 ? 0 : -1; +} + +int nv12_zero_device::set_frame(AVFrame *frame) { + this->frame = frame; + + av_frame.reset(frame); + + resolution_fn(this->display, frame->width, frame->height); + + return 0; +} + +void nv12_zero_device::set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) { +} + +int nv12_zero_device::init(void *display, resolution_fn_t resolution_fn, pixel_format_fn_t pixel_format_fn) { + pixel_format_fn(display, '420v'); + + this->display = display; + this->resolution_fn = resolution_fn; + + // we never use this pointer but it's existence is checked/used + // by the platform independed code + data = this; + + return 0; +} + +} // namespace platf diff --git a/sunshine/platform/macos/nv12_zero_device.h b/sunshine/platform/macos/nv12_zero_device.h new file mode 100644 index 00000000..847a8f0a --- /dev/null +++ b/sunshine/platform/macos/nv12_zero_device.h @@ -0,0 +1,29 @@ +#ifndef vtdevice_h +#define vtdevice_h + +#include "sunshine/platform/common.h" + +namespace platf { + +class nv12_zero_device : public hwdevice_t { + // display holds a pointer to an av_video object. Since the namespaces of AVFoundation + // and FFMPEG collide, we need this opaque pointer and cannot use the definition + void *display; + +public: + // this function is used to set the resolution on an av_video object that we cannot + // call directly because of namespace collisions between AVFoundation and FFMPEG + using resolution_fn_t = std::function; + resolution_fn_t resolution_fn; + using pixel_format_fn_t = std::function; + + int init(void *display, resolution_fn_t resolution_fn, pixel_format_fn_t pixel_format_fn); + + int convert(img_t &img); + int set_frame(AVFrame *frame); + void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range); +}; + +} // namespace platf + +#endif /* vtdevice_h */ diff --git a/sunshine/platform/macos/publish.cpp b/sunshine/platform/macos/publish.cpp new file mode 100644 index 00000000..cc10cd82 --- /dev/null +++ b/sunshine/platform/macos/publish.cpp @@ -0,0 +1,429 @@ + +// adapted from https://www.avahi.org/doxygen/html/client-publish-service_8c-example.html +#include + +#include "misc.h" +#include "sunshine/main.h" +#include "sunshine/nvhttp.h" +#include "sunshine/platform/common.h" +#include "sunshine/utility.h" + +using namespace std::literals; + +namespace avahi { + +/** Error codes used by avahi */ +enum err_e { + OK = 0, /**< OK */ + ERR_FAILURE = -1, /**< Generic error code */ + ERR_BAD_STATE = -2, /**< Object was in a bad state */ + ERR_INVALID_HOST_NAME = -3, /**< Invalid host name */ + ERR_INVALID_DOMAIN_NAME = -4, /**< Invalid domain name */ + ERR_NO_NETWORK = -5, /**< No suitable network protocol available */ + ERR_INVALID_TTL = -6, /**< Invalid DNS TTL */ + ERR_IS_PATTERN = -7, /**< RR key is pattern */ + ERR_COLLISION = -8, /**< Name collision */ + ERR_INVALID_RECORD = -9, /**< Invalid RR */ + + ERR_INVALID_SERVICE_NAME = -10, /**< Invalid service name */ + ERR_INVALID_SERVICE_TYPE = -11, /**< Invalid service type */ + ERR_INVALID_PORT = -12, /**< Invalid port number */ + ERR_INVALID_KEY = -13, /**< Invalid key */ + ERR_INVALID_ADDRESS = -14, /**< Invalid address */ + ERR_TIMEOUT = -15, /**< Timeout reached */ + ERR_TOO_MANY_CLIENTS = -16, /**< Too many clients */ + ERR_TOO_MANY_OBJECTS = -17, /**< Too many objects */ + ERR_TOO_MANY_ENTRIES = -18, /**< Too many entries */ + ERR_OS = -19, /**< OS error */ + + ERR_ACCESS_DENIED = -20, /**< Access denied */ + ERR_INVALID_OPERATION = -21, /**< Invalid operation */ + ERR_DBUS_ERROR = -22, /**< An unexpected D-Bus error occurred */ + ERR_DISCONNECTED = -23, /**< Daemon connection failed */ + ERR_NO_MEMORY = -24, /**< Memory exhausted */ + ERR_INVALID_OBJECT = -25, /**< The object passed to this function was invalid */ + ERR_NO_DAEMON = -26, /**< Daemon not running */ + ERR_INVALID_INTERFACE = -27, /**< Invalid interface */ + ERR_INVALID_PROTOCOL = -28, /**< Invalid protocol */ + ERR_INVALID_FLAGS = -29, /**< Invalid flags */ + + ERR_NOT_FOUND = -30, /**< Not found */ + ERR_INVALID_CONFIG = -31, /**< Configuration error */ + ERR_VERSION_MISMATCH = -32, /**< Verson mismatch */ + ERR_INVALID_SERVICE_SUBTYPE = -33, /**< Invalid service subtype */ + ERR_INVALID_PACKET = -34, /**< Invalid packet */ + ERR_INVALID_DNS_ERROR = -35, /**< Invlaid DNS return code */ + ERR_DNS_FORMERR = -36, /**< DNS Error: Form error */ + ERR_DNS_SERVFAIL = -37, /**< DNS Error: Server Failure */ + ERR_DNS_NXDOMAIN = -38, /**< DNS Error: No such domain */ + ERR_DNS_NOTIMP = -39, /**< DNS Error: Not implemented */ + + ERR_DNS_REFUSED = -40, /**< DNS Error: Operation refused */ + ERR_DNS_YXDOMAIN = -41, + ERR_DNS_YXRRSET = -42, + ERR_DNS_NXRRSET = -43, + ERR_DNS_NOTAUTH = -44, /**< DNS Error: Not authorized */ + ERR_DNS_NOTZONE = -45, + ERR_INVALID_RDATA = -46, /**< Invalid RDATA */ + ERR_INVALID_DNS_CLASS = -47, /**< Invalid DNS class */ + ERR_INVALID_DNS_TYPE = -48, /**< Invalid DNS type */ + ERR_NOT_SUPPORTED = -49, /**< Not supported */ + + ERR_NOT_PERMITTED = -50, /**< Operation not permitted */ + ERR_INVALID_ARGUMENT = -51, /**< Invalid argument */ + ERR_IS_EMPTY = -52, /**< Is empty */ + ERR_NO_CHANGE = -53, /**< The requested operation is invalid because it is redundant */ + + ERR_MAX = -54 +}; + +constexpr auto IF_UNSPEC = -1; +enum proto { + PROTO_INET = 0, /**< IPv4 */ + PROTO_INET6 = 1, /**< IPv6 */ + PROTO_UNSPEC = -1 /**< Unspecified/all protocol(s) */ +}; + +enum ServerState { + SERVER_INVALID, /**< Invalid state (initial) */ + SERVER_REGISTERING, /**< Host RRs are being registered */ + SERVER_RUNNING, /**< All host RRs have been established */ + SERVER_COLLISION, /**< There is a collision with a host RR. All host RRs have been withdrawn, the user should set a new host name via avahi_server_set_host_name() */ + SERVER_FAILURE /**< Some fatal failure happened, the server is unable to proceed */ +}; + +enum ClientState { + CLIENT_S_REGISTERING = SERVER_REGISTERING, /**< Server state: REGISTERING */ + CLIENT_S_RUNNING = SERVER_RUNNING, /**< Server state: RUNNING */ + CLIENT_S_COLLISION = SERVER_COLLISION, /**< Server state: COLLISION */ + CLIENT_FAILURE = 100, /**< Some kind of error happened on the client side */ + CLIENT_CONNECTING = 101 /**< We're still connecting. This state is only entered when AVAHI_CLIENT_NO_FAIL has been passed to avahi_client_new() and the daemon is not yet available. */ +}; + +enum EntryGroupState { + ENTRY_GROUP_UNCOMMITED, /**< The group has not yet been commited, the user must still call avahi_entry_group_commit() */ + ENTRY_GROUP_REGISTERING, /**< The entries of the group are currently being registered */ + ENTRY_GROUP_ESTABLISHED, /**< The entries have successfully been established */ + ENTRY_GROUP_COLLISION, /**< A name collision for one of the entries in the group has been detected, the entries have been withdrawn */ + ENTRY_GROUP_FAILURE /**< Some kind of failure happened, the entries have been withdrawn */ +}; + +enum ClientFlags { + CLIENT_IGNORE_USER_CONFIG = 1, /**< Don't read user configuration */ + CLIENT_NO_FAIL = 2 /**< Don't fail if the daemon is not available when avahi_client_new() is called, instead enter CLIENT_CONNECTING state and wait for the daemon to appear */ +}; + +/** Some flags for publishing functions */ +enum PublishFlags { + PUBLISH_UNIQUE = 1, /**< For raw records: The RRset is intended to be unique */ + PUBLISH_NO_PROBE = 2, /**< For raw records: Though the RRset is intended to be unique no probes shall be sent */ + PUBLISH_NO_ANNOUNCE = 4, /**< For raw records: Do not announce this RR to other hosts */ + PUBLISH_ALLOW_MULTIPLE = 8, /**< For raw records: Allow multiple local records of this type, even if they are intended to be unique */ + /** \cond fulldocs */ + PUBLISH_NO_REVERSE = 16, /**< For address records: don't create a reverse (PTR) entry */ + PUBLISH_NO_COOKIE = 32, /**< For service records: do not implicitly add the local service cookie to TXT data */ + /** \endcond */ + PUBLISH_UPDATE = 64, /**< Update existing records instead of adding new ones */ + /** \cond fulldocs */ + PUBLISH_USE_WIDE_AREA = 128, /**< Register the record using wide area DNS (i.e. unicast DNS update) */ + PUBLISH_USE_MULTICAST = 256 /**< Register the record using multicast DNS */ + /** \endcond */ +}; + +using IfIndex = int; +using Protocol = int; + +struct EntryGroup; +struct Poll; +struct SimplePoll; +struct Client; + +typedef void (*ClientCallback)(Client *, ClientState, void *userdata); +typedef void (*EntryGroupCallback)(EntryGroup *g, EntryGroupState state, void *userdata); + +typedef void (*free_fn)(void *); + +typedef Client *(*client_new_fn)(const Poll *poll_api, ClientFlags flags, ClientCallback callback, void *userdata, int *error); +typedef void (*client_free_fn)(Client *); +typedef char *(*alternative_service_name_fn)(char *); + +typedef Client *(*entry_group_get_client_fn)(EntryGroup *); + +typedef EntryGroup *(*entry_group_new_fn)(Client *, EntryGroupCallback, void *userdata); +typedef int (*entry_group_add_service_fn)( + EntryGroup *group, + IfIndex interface, + Protocol protocol, + PublishFlags flags, + const char *name, + const char *type, + const char *domain, + const char *host, + uint16_t port, + ...); + +typedef int (*entry_group_is_empty_fn)(EntryGroup *); +typedef int (*entry_group_reset_fn)(EntryGroup *); +typedef int (*entry_group_commit_fn)(EntryGroup *); + +typedef char *(*strdup_fn)(const char *); +typedef char *(*strerror_fn)(int); +typedef int (*client_errno_fn)(Client *); + +typedef Poll *(*simple_poll_get_fn)(SimplePoll *); +typedef int (*simple_poll_loop_fn)(SimplePoll *); +typedef void (*simple_poll_quit_fn)(SimplePoll *); +typedef SimplePoll *(*simple_poll_new_fn)(); +typedef void (*simple_poll_free_fn)(SimplePoll *); + +free_fn free; +client_new_fn client_new; +client_free_fn client_free; +alternative_service_name_fn alternative_service_name; +entry_group_get_client_fn entry_group_get_client; +entry_group_new_fn entry_group_new; +entry_group_add_service_fn entry_group_add_service; +entry_group_is_empty_fn entry_group_is_empty; +entry_group_reset_fn entry_group_reset; +entry_group_commit_fn entry_group_commit; +strdup_fn strdup; +strerror_fn strerror; +client_errno_fn client_errno; +simple_poll_get_fn simple_poll_get; +simple_poll_loop_fn simple_poll_loop; +simple_poll_quit_fn simple_poll_quit; +simple_poll_new_fn simple_poll_new; +simple_poll_free_fn simple_poll_free; + + +int init_common() { + static void *handle { nullptr }; + static bool funcs_loaded = false; + + if(funcs_loaded) return 0; + + if(!handle) { + handle = dyn::handle({ "libavahi-common.3.dylib", "libavahi-common.dylib" }); + if(!handle) { + return -1; + } + } + + std::vector> funcs { + { (dyn::apiproc *)&alternative_service_name, "avahi_alternative_service_name" }, + { (dyn::apiproc *)&free, "avahi_free" }, + { (dyn::apiproc *)&strdup, "avahi_strdup" }, + { (dyn::apiproc *)&strerror, "avahi_strerror" }, + { (dyn::apiproc *)&simple_poll_get, "avahi_simple_poll_get" }, + { (dyn::apiproc *)&simple_poll_loop, "avahi_simple_poll_loop" }, + { (dyn::apiproc *)&simple_poll_quit, "avahi_simple_poll_quit" }, + { (dyn::apiproc *)&simple_poll_new, "avahi_simple_poll_new" }, + { (dyn::apiproc *)&simple_poll_free, "avahi_simple_poll_free" }, + }; + + if(dyn::load(handle, funcs)) { + return -1; + } + + funcs_loaded = true; + return 0; +} + +int init_client() { + if(init_common()) { + return -1; + } + + static void *handle { nullptr }; + static bool funcs_loaded = false; + + if(funcs_loaded) return 0; + + if(!handle) { + handle = dyn::handle({ "libavahi-client.3.dylib", "libavahi-client.dylib" }); + if(!handle) { + return -1; + } + } + + std::vector> funcs { + { (dyn::apiproc *)&client_new, "avahi_client_new" }, + { (dyn::apiproc *)&client_free, "avahi_client_free" }, + { (dyn::apiproc *)&entry_group_get_client, "avahi_entry_group_get_client" }, + { (dyn::apiproc *)&entry_group_new, "avahi_entry_group_new" }, + { (dyn::apiproc *)&entry_group_add_service, "avahi_entry_group_add_service" }, + { (dyn::apiproc *)&entry_group_is_empty, "avahi_entry_group_is_empty" }, + { (dyn::apiproc *)&entry_group_reset, "avahi_entry_group_reset" }, + { (dyn::apiproc *)&entry_group_commit, "avahi_entry_group_commit" }, + { (dyn::apiproc *)&client_errno, "avahi_client_errno" }, + }; + + if(dyn::load(handle, funcs)) { + return -1; + } + + funcs_loaded = true; + return 0; +} +} // namespace avahi + +namespace platf::publish { + +template +void free(T *p) { + avahi::free(p); +} + +template +using ptr_t = util::safe_ptr>; +using client_t = util::dyn_safe_ptr; +using poll_t = util::dyn_safe_ptr; + +avahi::EntryGroup *group = nullptr; + +poll_t poll; +client_t client; + +ptr_t name; + +void create_services(avahi::Client *c); + +void entry_group_callback(avahi::EntryGroup *g, avahi::EntryGroupState state, void *) { + group = g; + + switch(state) { + case avahi::ENTRY_GROUP_ESTABLISHED: + BOOST_LOG(info) << "Avahi service " << name.get() << " successfully established."; + break; + case avahi::ENTRY_GROUP_COLLISION: + name.reset(avahi::alternative_service_name(name.get())); + + BOOST_LOG(info) << "Avahi service name collision, renaming service to " << name.get(); + + create_services(avahi::entry_group_get_client(g)); + break; + case avahi::ENTRY_GROUP_FAILURE: + BOOST_LOG(error) << "Avahi entry group failure: " << avahi::strerror(avahi::client_errno(avahi::entry_group_get_client(g))); + avahi::simple_poll_quit(poll.get()); + break; + case avahi::ENTRY_GROUP_UNCOMMITED: + case avahi::ENTRY_GROUP_REGISTERING:; + } +} + +void create_services(avahi::Client *c) { + int ret; + + auto fg = util::fail_guard([]() { + avahi::simple_poll_quit(poll.get()); + }); + + if(!group) { + if(!(group = avahi::entry_group_new(c, entry_group_callback, nullptr))) { + BOOST_LOG(error) << "avahi::entry_group_new() failed: "sv << avahi::strerror(avahi::client_errno(c)); + return; + } + } + + if(avahi::entry_group_is_empty(group)) { + BOOST_LOG(info) << "Adding avahi service "sv << name.get(); + + ret = avahi::entry_group_add_service( + group, + avahi::IF_UNSPEC, avahi::PROTO_UNSPEC, + avahi::PublishFlags(0), + name.get(), + SERVICE_TYPE, + nullptr, nullptr, + map_port(nvhttp::PORT_HTTP), + nullptr); + + if(ret < 0) { + if(ret == avahi::ERR_COLLISION) { + // A service name collision with a local service happened. Let's pick a new name + name.reset(avahi::alternative_service_name(name.get())); + BOOST_LOG(info) << "Service name collision, renaming service to "sv << name.get(); + + avahi::entry_group_reset(group); + + create_services(c); + + fg.disable(); + return; + } + + BOOST_LOG(error) << "Failed to add "sv << SERVICE_TYPE << " service: "sv << avahi::strerror(ret); + return; + } + + ret = avahi::entry_group_commit(group); + if(ret < 0) { + BOOST_LOG(error) << "Failed to commit entry group: "sv << avahi::strerror(ret); + return; + } + } + + fg.disable(); +} + +void client_callback(avahi::Client *c, avahi::ClientState state, void *) { + switch(state) { + case avahi::CLIENT_S_RUNNING: + create_services(c); + break; + case avahi::CLIENT_FAILURE: + BOOST_LOG(error) << "Client failure: "sv << avahi::strerror(avahi::client_errno(c)); + avahi::simple_poll_quit(poll.get()); + break; + case avahi::CLIENT_S_COLLISION: + case avahi::CLIENT_S_REGISTERING: + if(group) + avahi::entry_group_reset(group); + break; + case avahi::CLIENT_CONNECTING:; + } +} + +class deinit_t : public ::platf::deinit_t { +public: + std::thread poll_thread; + + deinit_t(std::thread poll_thread) : poll_thread { std::move(poll_thread) } {} + + ~deinit_t() override { + if(avahi::simple_poll_quit && poll) { + avahi::simple_poll_quit(poll.get()); + } + + if(poll_thread.joinable()) { + poll_thread.join(); + } + } +}; + +[[nodiscard]] std::unique_ptr<::platf::deinit_t> start() { + if(avahi::init_client()) { + return nullptr; + } + + int avhi_error; + + poll.reset(avahi::simple_poll_new()); + if(!poll) { + BOOST_LOG(error) << "Failed to create simple poll object."sv; + return nullptr; + } + + name.reset(avahi::strdup(SERVICE_NAME)); + + client.reset( + avahi::client_new(avahi::simple_poll_get(poll.get()), avahi::ClientFlags(0), client_callback, nullptr, &avhi_error)); + + if(!client) { + BOOST_LOG(error) << "Failed to create client: "sv << avahi::strerror(avhi_error); + return nullptr; + } + + return std::make_unique(std::thread { avahi::simple_poll_loop, poll.get() }); +} +}; // namespace platf::publish diff --git a/sunshine/rtsp.cpp b/sunshine/rtsp.cpp index 68c6f268..983aff96 100644 --- a/sunshine/rtsp.cpp +++ b/sunshine/rtsp.cpp @@ -22,6 +22,8 @@ extern "C" { #include "stream.h" #include "sync.h" +#include + namespace asio = boost::asio; using asio::ip::tcp; diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 15807eaa..b2a8858b 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -538,13 +538,49 @@ static encoder_t vaapi { }; #endif +#ifdef __APPLE__ +static encoder_t videotoolbox { + "videotoolbox"sv, + { FF_PROFILE_H264_HIGH, FF_PROFILE_HEVC_MAIN, FF_PROFILE_HEVC_MAIN_10 }, + AV_HWDEVICE_TYPE_NONE, + AV_PIX_FMT_VIDEOTOOLBOX, + AV_PIX_FMT_NV12, AV_PIX_FMT_NV12, + { + { + { "allow_sw"s, &config::video.vt.allow_sw }, + { "require_sw"s, &config::video.vt.require_sw }, + { "realtime"s, &config::video.vt.realtime }, + }, + std::nullopt, + "hevc_videotoolbox"s, + }, + { + { + { "allow_sw"s, &config::video.vt.allow_sw }, + { "require_sw"s, &config::video.vt.require_sw }, + { "realtime"s, &config::video.vt.realtime }, + }, + std::nullopt, + "h264_videotoolbox"s, + }, + DEFAULT, + + nullptr +}; +#endif + static std::vector encoders { +#ifndef __APPLE__ nvenc, +#endif #ifdef _WIN32 amdvce, #endif #ifdef __linux__ vaapi, +#endif +#ifdef __APPLE__ + videotoolbox, #endif software }; From c5e6b84e3de97b5da26cf7fb341bb51480fa1083 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 27 Feb 2022 12:17:30 -0500 Subject: [PATCH 169/817] Get version from CMakeLists --- .github/workflows/CI.yml | 24 ------------------------ CMakeLists.txt | 1 + gen-deb.in | 2 +- sunshine.desktop => sunshine.desktop.in | 2 +- 4 files changed, 3 insertions(+), 26 deletions(-) rename sunshine.desktop => sunshine.desktop.in (85%) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e9de6a36..dce77cd2 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -51,30 +51,6 @@ jobs: echo Within 'CMakeLists.txt' change "project(Sunshine VERSION $cmakelists_version)" to "project(Sunshine VERSION ${{ needs.check_changelog.outputs.next_version_bare }})" exit 1 - - name: Check gen-deb.in Version - run: | - version=$(grep -o -E '^Version: [0-9]+\.[0-9]+\.[0-9]+' gen-deb.in | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') - echo "gendeb_version=${version}" >> $GITHUB_ENV - - name: Compare gen-deb.in Version - if: ${{ env.gendeb_version != needs.check_changelog.outputs.next_version_bare }} - run: | - echo gen-deb.in version: "$gendeb_version" - echo Changelog version: "${{ needs.check_changelog.outputs.next_version_bare }}" - echo Within 'gen-deb.in' change "Version: $gendeb_version" to "Version: ${{ needs.check_changelog.outputs.next_version_bare }}" - exit 1 - - - name: Check sunshine.desktop Versions - run: | - version=$(grep -o -E '^X-AppImage-Version=[0-9]+\.[0-9]+\.[0-9]+' sunshine.desktop | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') - echo "appimage_version=${version}" >> $GITHUB_ENV - - name: Compare sunshine.desktop Versions - if: ${{ env.appimage_version != needs.check_changelog.outputs.next_version_bare }} - run: | - echo sunshine.desktop Version: "$appimage_version" - echo Changelog version: "${{ needs.check_changelog.outputs.next_version_bare }}" - echo Within 'sunshine.desktop' change "X-AppImage-Version=$appimage_version" to "X-AppImage-Version=${{ needs.check_changelog.outputs.next_version_bare }}" - exit 1 - build_appimage: name: AppImage runs-on: ubuntu-20.04 diff --git a/CMakeLists.txt b/CMakeLists.txt index fd624bdb..f9f1f5f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -313,6 +313,7 @@ else() set(SUNSHINE_EXECUTABLE_PATH "sunshine") endif() configure_file(gen-deb.in gen-deb @ONLY) + configure_file(sunshine.desktop.in sunshine.desktop @ONLY) configure_file(sunshine.service.in sunshine.service @ONLY) endif() diff --git a/gen-deb.in b/gen-deb.in index 39d585d4..25ca3609 100755 --- a/gen-deb.in +++ b/gen-deb.in @@ -37,7 +37,7 @@ Package: sunshine Architecture: amd64 Maintainer: @loki Priority: optional -Version: 0.12.0 +Version: @PROJECT_VERSION@ Depends: libssl1.1, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2 Description: Gamestream host for Moonlight EOF diff --git a/sunshine.desktop b/sunshine.desktop.in similarity index 85% rename from sunshine.desktop rename to sunshine.desktop.in index 8773a035..195bade3 100644 --- a/sunshine.desktop +++ b/sunshine.desktop.in @@ -8,5 +8,5 @@ Icon=sunshine Categories=Utility; Terminal=true X-AppImage-Name=sunshine -X-AppImage-Version=0.12.0 +X-AppImage-Version=@PROJECT_VERSION@ X-AppImage-Arch=x86_64 From c2d4ffdaeda4cb88cca72244dfa91e2df97cfeb1 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 27 Feb 2022 12:21:23 -0500 Subject: [PATCH 170/817] Use master branch for create_release action - Will always use latest version --- .github/workflows/CI.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e9de6a36..7a8eefea 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -160,7 +160,7 @@ jobs: path: artifacts/ - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: SunshineStream/actions/create_release@v0 + uses: SunshineStream/actions/create_release@master with: token: ${{ secrets.GITHUB_TOKEN }} next_version: ${{ needs.check_changelog.outputs.next_version }} @@ -211,7 +211,7 @@ jobs: path: artifacts/ - name: Create Release if: ${{ matrix.package == '-p' && github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: SunshineStream/actions/create_release@v0 + uses: SunshineStream/actions/create_release@master with: token: ${{ secrets.GITHUB_TOKEN }} next_version: ${{ needs.check_changelog.outputs.next_version }} @@ -275,7 +275,7 @@ jobs: path: artifacts/ - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: SunshineStream/actions/create_release@v0 + uses: SunshineStream/actions/create_release@master with: token: ${{ secrets.GITHUB_TOKEN }} next_version: ${{ needs.check_changelog.outputs.next_version }} From fb7a3a07580a6314fce78e29853b2c2af9ca0b43 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 27 Feb 2022 12:21:48 -0500 Subject: [PATCH 171/817] Bump version to v0.13.0 --- CHANGELOG.md | 4 ++++ CMakeLists.txt | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9d58a8b..5a18ecbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [0.13.0] - 2022-02-27 +### Added +- (MacOS) Initial support for MacOS (#40) + ## [0.12.0] - 2022-02-13 ### Added - New command line argument `--version` diff --git a/CMakeLists.txt b/CMakeLists.txt index fd624bdb..508f1515 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.0) -project(Sunshine VERSION 0.12.0) +project(Sunshine VERSION 0.13.0) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) From 1e913561557d3005fbdd9929c06e7f6571c42fbb Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 27 Feb 2022 12:40:31 -0500 Subject: [PATCH 172/817] Fix desktop file directory --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index dce77cd2..5ff15430 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -111,7 +111,7 @@ jobs: wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage && chmod +x linuxdeploy-x86_64.AppImage - ./linuxdeploy-x86_64.AppImage --appdir ../AppDir -e ../appimage-build/sunshine -i "../$ICON_FILE" -d "../$DESKTOP_FILE" --output appimage + ./linuxdeploy-x86_64.AppImage --appdir ../AppDir -e ../appimage-build/sunshine -i "../$ICON_FILE" -d "../appimage-build/$DESKTOP_FILE" --output appimage mv sunshine*.AppImage sunshine.AppImage mkdir sunshine && mv sunshine.AppImage sunshine/ From 1520cb7bf93f9f55e373f84441775d7f7ea51034 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 27 Feb 2022 18:11:56 +0000 Subject: [PATCH 173/817] Bump actions/stale from 3 to 4.1.0 Bumps [actions/stale](https://github.com/actions/stale) from 3 to 4.1.0. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v3...v4.1.0) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/issues-stale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml index 02aa6ca8..588dc6a6 100644 --- a/.github/workflows/issues-stale.yml +++ b/.github/workflows/issues-stale.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Stale - uses: actions/stale@v3 + uses: actions/stale@v4.1.0 with: stale-issue-message: > This issue is stale because it has been open for 30 days with no activity. @@ -30,7 +30,7 @@ jobs: days-before-close: 5 - name: Invalid Template - uses: actions/stale@v3 + uses: actions/stale@v4.1.0 with: stale-issue-message: > Invalid issues template. From 5e910b5fab6416b57820894ec4004c40d3e213ac Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 27 Feb 2022 13:18:36 -0500 Subject: [PATCH 174/817] Update issues-stale.yml - Remove skip-stale-issue-message - Remove skip-stale-pr-message --- .github/workflows/issues-stale.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml index 588dc6a6..ea11acbf 100644 --- a/.github/workflows/issues-stale.yml +++ b/.github/workflows/issues-stale.yml @@ -37,14 +37,12 @@ jobs: close-issue-message: > This issue was closed because the the template was not completed after 5 days. stale-issue-label: 'invalid:template-incomplete' - skip-stale-issue-message: true stale-pr-message: > Invalid PR template. close-pr-message: > This PR was closed because the the template was not completed after 5 days. stale-pr-label: 'invalid:template-incomplete' exempt-pr-labels: 'status:in-progress' - skip-stale-pr-message: true only-labels: 'invalid:template-incomplete' days-before-stale: 0 days-before-close: 5 From 861331be1cdc52e4897cbca9d9288bbabf87adc6 Mon Sep 17 00:00:00 2001 From: Ryan Caezar Itang Date: Wed, 2 Mar 2022 11:19:05 +0800 Subject: [PATCH 175/817] Add TPCircularBuffer submodule --- sunshine/platform/macos/TPCircularBuffer | 1 + 1 file changed, 1 insertion(+) create mode 160000 sunshine/platform/macos/TPCircularBuffer diff --git a/sunshine/platform/macos/TPCircularBuffer b/sunshine/platform/macos/TPCircularBuffer new file mode 160000 index 00000000..bce9170d --- /dev/null +++ b/sunshine/platform/macos/TPCircularBuffer @@ -0,0 +1 @@ +Subproject commit bce9170d9e8e566fb33d56136ec6e4b97f91ed2d From 8d4bd87ad2cc30c8f673fdc0836d89f133f8730f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Mar 2022 12:36:17 +0000 Subject: [PATCH 176/817] Bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/CI.yml | 10 +++++----- .github/workflows/clang.yml | 2 +- .github/workflows/pull-requests.yml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 82bbc343..d36e4a71 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Verify Changelog id: verify_changelog @@ -37,7 +37,7 @@ jobs: # base_ref for pull request check, ref for push steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Check CMakeLists.txt Version run: | @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: recursive @@ -160,7 +160,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: recursive @@ -201,7 +201,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: recursive - name: MSYS2 Setup diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index 9ac9d06a..37299687 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Clang format lint uses: DoozyX/clang-format-lint-action@v0.13 diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index a322319e..ad014e6d 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Branch check if: ( github.head_ref == 'repo-sync/common-repo-files/default' && github.base_ref == 'master' ) || ( github.head_ref == 'nightly' && github.base_ref == 'master' ) From 4c7afc05c7ecbad3b5e722187415d96cb75eefd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Mar 2022 21:55:46 +0000 Subject: [PATCH 177/817] Bump actions/stale from 4.1.0 to 5 Bumps [actions/stale](https://github.com/actions/stale) from 4.1.0 to 5. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v4.1.0...v5) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/issues-stale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml index ea11acbf..1c40c45d 100644 --- a/.github/workflows/issues-stale.yml +++ b/.github/workflows/issues-stale.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Stale - uses: actions/stale@v4.1.0 + uses: actions/stale@v5 with: stale-issue-message: > This issue is stale because it has been open for 30 days with no activity. @@ -30,7 +30,7 @@ jobs: days-before-close: 5 - name: Invalid Template - uses: actions/stale@v4.1.0 + uses: actions/stale@v5 with: stale-issue-message: > Invalid issues template. From 1f79f4ed1248cd32089709a7af8c5fc4ccd5c3fa Mon Sep 17 00:00:00 2001 From: Mathias Tillman Date: Wed, 9 Mar 2022 00:30:10 +0100 Subject: [PATCH 178/817] Fix rumble events causing hang because the received input events were not read properly. --- sunshine/platform/linux/input.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sunshine/platform/linux/input.cpp b/sunshine/platform/linux/input.cpp index fbdc8133..3c92ba81 100644 --- a/sunshine/platform/linux/input.cpp +++ b/sunshine/platform/linux/input.cpp @@ -716,6 +716,15 @@ inline void rumbleIterate(std::vector &effects, std::vector return; } + // Copy over the received events + for (auto x = 0; x < polls_tmp.size(); ++x) { + for (auto y = 0; y < polls.size(); ++y) { + if (polls_tmp[x].fd == polls[y].el.fd) { + polls[y].el.revents = polls_tmp[x].revents; + } + } + } + for(int x = 0; x < polls.size(); ++x) { auto poll = std::begin(polls) + x; auto effect_it = std::begin(effects) + x; From a31c6c4cd029e31c132aa415e3c33bc3915aaf9e Mon Sep 17 00:00:00 2001 From: Mathias Tillman Date: Wed, 9 Mar 2022 16:20:51 +0100 Subject: [PATCH 179/817] Fix hwdevice being destroyed before context, causing a sigsegv because the context relies on the hwdevice still being active. --- sunshine/video.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sunshine/video.cpp b/sunshine/video.cpp index b2a8858b..f435d507 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -324,7 +324,7 @@ struct encoder_t { class session_t { public: session_t() = default; - session_t(ctx_t &&ctx, std::shared_ptr &&device, int inject) : ctx { std::move(ctx) }, device { std::move(device) }, inject { inject } {} + session_t(ctx_t &&ctx, std::shared_ptr &&device, int inject) : device { std::move(device) }, ctx { std::move(ctx) }, inject { inject } {} session_t(session_t &&other) noexcept = default; @@ -341,8 +341,8 @@ class session_t { return *this; } - ctx_t ctx; std::shared_ptr device; + ctx_t ctx; std::vector replacements; From 9a2689692a32904f3208a8180bd7a8526ef0bca3 Mon Sep 17 00:00:00 2001 From: Mathias Tillman Date: Wed, 9 Mar 2022 16:40:39 +0100 Subject: [PATCH 180/817] Fix lint warning about code style. --- sunshine/platform/linux/input.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sunshine/platform/linux/input.cpp b/sunshine/platform/linux/input.cpp index 3c92ba81..1f9840fb 100644 --- a/sunshine/platform/linux/input.cpp +++ b/sunshine/platform/linux/input.cpp @@ -717,9 +717,10 @@ inline void rumbleIterate(std::vector &effects, std::vector } // Copy over the received events - for (auto x = 0; x < polls_tmp.size(); ++x) { - for (auto y = 0; y < polls.size(); ++y) { - if (polls_tmp[x].fd == polls[y].el.fd) { + for(auto x = 0; x < polls_tmp.size(); ++x) { + auto pfd = polls_tmp[x].fd; + for(auto y = 0; y < polls.size(); ++y) { + if (pfd == polls[y].el.fd) { polls[y].el.revents = polls_tmp[x].revents; } } From 80ebc9982edd3fa284385cf76b221a37cdd99c26 Mon Sep 17 00:00:00 2001 From: Mathias Tillman Date: Wed, 9 Mar 2022 17:27:42 +0100 Subject: [PATCH 181/817] Fix another lint warning. --- sunshine/platform/linux/input.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sunshine/platform/linux/input.cpp b/sunshine/platform/linux/input.cpp index 1f9840fb..c0222927 100644 --- a/sunshine/platform/linux/input.cpp +++ b/sunshine/platform/linux/input.cpp @@ -720,7 +720,7 @@ inline void rumbleIterate(std::vector &effects, std::vector for(auto x = 0; x < polls_tmp.size(); ++x) { auto pfd = polls_tmp[x].fd; for(auto y = 0; y < polls.size(); ++y) { - if (pfd == polls[y].el.fd) { + if(pfd == polls[y].el.fd) { polls[y].el.revents = polls_tmp[x].revents; } } From e61bbe87b4bd4c4554bf84843c830aaed9b0f182 Mon Sep 17 00:00:00 2001 From: Mathias Tillman Date: Wed, 9 Mar 2022 22:23:38 +0100 Subject: [PATCH 182/817] Read revents from the temporary pollfds instead of copying them over to the old vector. Decrement index for polls when erasing to make sure none of the events are skipped. --- sunshine/platform/linux/input.cpp | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/sunshine/platform/linux/input.cpp b/sunshine/platform/linux/input.cpp index c0222927..be923936 100644 --- a/sunshine/platform/linux/input.cpp +++ b/sunshine/platform/linux/input.cpp @@ -696,13 +696,13 @@ struct input_raw_t { }; inline void rumbleIterate(std::vector &effects, std::vector &polls, std::chrono::milliseconds to) { - std::vector polls_tmp; - polls_tmp.reserve(polls.size()); + std::vector polls_recv; + polls_recv.reserve(polls.size()); for(auto &poll : polls) { - polls_tmp.emplace_back(poll.el); + polls_recv.emplace_back(poll.el); } - auto res = poll(polls_tmp.data(), polls.size(), to.count()); + auto res = poll(polls_recv.data(), polls_recv.size(), to.count()); // If timed out if(!res) { @@ -716,16 +716,6 @@ inline void rumbleIterate(std::vector &effects, std::vector return; } - // Copy over the received events - for(auto x = 0; x < polls_tmp.size(); ++x) { - auto pfd = polls_tmp[x].fd; - for(auto y = 0; y < polls.size(); ++y) { - if(pfd == polls[y].el.fd) { - polls[y].el.revents = polls_tmp[x].revents; - } - } - } - for(int x = 0; x < polls.size(); ++x) { auto poll = std::begin(polls) + x; auto effect_it = std::begin(effects) + x; @@ -735,16 +725,17 @@ inline void rumbleIterate(std::vector &effects, std::vector // TUPLE_2D_REF(dev, q, *dev_q_it); // on error - if((*poll)->revents & (POLLHUP | POLLRDHUP | POLLERR)) { + if(polls_recv[x].revents & (POLLHUP | POLLRDHUP | POLLERR)) { BOOST_LOG(warning) << "Gamepad ["sv << x << "] file discriptor closed unexpectedly"sv; polls.erase(poll); effects.erase(effect_it); + --x; continue; } - if(!((*poll)->revents & POLLIN)) { + if(!(polls_recv[x].revents & POLLIN)) { continue; } @@ -761,6 +752,7 @@ inline void rumbleIterate(std::vector &effects, std::vector polls.erase(poll); effects.erase(effect_it); + --x; continue; } From 6fca2c593ceb21dafe71600ad536af6edfd02c60 Mon Sep 17 00:00:00 2001 From: Mathias Tillman Date: Thu, 10 Mar 2022 09:09:24 +0100 Subject: [PATCH 183/817] Use session_t destructor to ensure the context and hwdevice are always destroyed in the correct order. --- sunshine/video.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sunshine/video.cpp b/sunshine/video.cpp index f435d507..0f7eff35 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -324,9 +324,14 @@ struct encoder_t { class session_t { public: session_t() = default; - session_t(ctx_t &&ctx, std::shared_ptr &&device, int inject) : device { std::move(device) }, ctx { std::move(ctx) }, inject { inject } {} + session_t(ctx_t &&ctx, std::shared_ptr &&device, int inject) : ctx { std::move(ctx) }, device { std::move(device) }, inject { inject } {} session_t(session_t &&other) noexcept = default; + ~session_t() { + // Order matters here because the context relies on the hwdevice still being valid + ctx.reset(); + device.reset(); + } // Ensure objects are destroyed in the correct order session_t &operator=(session_t &&other) { @@ -341,8 +346,8 @@ class session_t { return *this; } - std::shared_ptr device; ctx_t ctx; + std::shared_ptr device; std::vector replacements; From 045970bcc58ed88e6ff03451f3da9b5d40d49d18 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 11 Mar 2022 00:20:25 -0500 Subject: [PATCH 184/817] Create requirements.txt --- locale/requirements.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 locale/requirements.txt diff --git a/locale/requirements.txt b/locale/requirements.txt new file mode 100644 index 00000000..9d236e72 --- /dev/null +++ b/locale/requirements.txt @@ -0,0 +1 @@ +Babel==2.9.1 From e28cc5e64597f81991b185dbebb5a95641b3421a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 11 Mar 2022 00:21:12 -0500 Subject: [PATCH 185/817] Create _locale.py --- locale/_locale.py | 156 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 locale/_locale.py diff --git a/locale/_locale.py b/locale/_locale.py new file mode 100644 index 00000000..4a2ec2c1 --- /dev/null +++ b/locale/_locale.py @@ -0,0 +1,156 @@ +"""_locale.py + +Functions related to building, initializing, updating, and compiling localization translations. + +Borrowed from RetroArcher. +""" +# standard imports +import argparse +import datetime +import os +import subprocess + +project_name = 'Sunshine' + +script_dir = os.path.dirname(os.path.abspath(__file__)) +root_dir = os.path.dirname(script_dir) +locale_dir = os.path.join(root_dir, 'locale') +project_dir = os.path.join(root_dir, project_name.lower()) + +year = datetime.datetime.now().year + +# retroarcher target locales +target_locales = [ + 'de', # Deutsch + 'en', # English + 'en_GB', # English (United Kingdom) + 'en_US', # English (United States) + 'es', # español + 'fr', # français + 'it', # italiano + 'ru', # русский +] + + +def x_extract(): + """Executes `xgettext extraction` in subprocess.""" + + commands = [ + 'xgettext', + f'--default-domain={project_name.lower()}', + f'--output={os.path.join(locale_dir, project_name.lower() + ".pot")}', + '--language=C++', + '--boost', + '--from-code=utf-8', + '-F', + f'--msgid-bugs-address=github.com/{project_name.lower()}', + f'--copyright-holder={project_name}', + f'--package-name={project_name}', + '--package-version=v0' + ] + + pot_filepath = os.path.join(locale_dir, f'{project_name.lower()}.pot') + + extensions = ['cpp', 'h', 'm', 'mm'] + + # find input files + for root, dirs, files in os.walk(project_dir, topdown=True): + for name in files: + filename = os.path.join(root, name) + extension = filename.rsplit('.', 1)[-1] + if extension in extensions: # append input files + commands.append(filename) + + print(commands) + proc = subprocess.run(args=commands, cwd=root_dir) + + # fix header + body = "" + with open(file=pot_filepath, mode='r') as file: + for line in file.readlines(): + if line != '"Language: \\n"\n': # do not include this line + if line == '# SOME DESCRIPTIVE TITLE.\n': + body += f'# Translations template for {project_name}.\n' + elif line.startswith('#') and 'YEAR' in line: + body += line.replace('YEAR', str(year)) + elif line.startswith('#') and 'PACKAGE' in line: + body += line.replace('PACKAGE', project_name) + else: + body += line + + # rewrite pot file with updated header + with open(file=pot_filepath, mode='w+') as file: + file.write(body) + + +def babel_init(locale_code: str): + """Executes `pybabel init` in subprocess. + + :param locale_code: str - locale code + """ + commands = [ + 'pybabel', + 'init', + '-i', os.path.join(locale_dir, f'{project_name.lower()}.pot'), + '-d', locale_dir, + '-D', project_name.lower(), + '-l', locale_code + ] + + print(commands) + proc = subprocess.run(args=commands, cwd=root_dir) + + +def babel_update(): + """Executes `pybabel update` in subprocess.""" + commands = [ + 'pybabel', + 'update', + '-i', os.path.join(locale_dir, f'{project_name.lower()}.pot'), + '-d', locale_dir, + '-D', project_name.lower(), + '--update-header-comment' + ] + + print(commands) + proc = subprocess.run(args=commands, cwd=root_dir) + + +def babel_compile(): + """Executes `pybabel compile` in subprocess.""" + commands = [ + 'pybabel', + 'compile', + '-d', locale_dir, + '-D', project_name.lower() + ] + + print(commands) + proc = subprocess.run(args=commands, cwd=root_dir) + + +if __name__ == '__main__': + # Set up and gather command line arguments + parser = argparse.ArgumentParser( + description='Script helps update locale_id translations. Translations must be done manually.') + + parser.add_argument('--extract', action='store_true', help='Extract messages from c++ files.') + parser.add_argument('--init', action='store_true', help='Initialize any new locales specified in target locales.') + parser.add_argument('--update', action='store_true', help='Update existing locales.') + parser.add_argument('--compile', action='store_true', help='Compile translated locales.') + + args = parser.parse_args() + + if args.extract: + x_extract() + + if args.init: + for locale_id in target_locales: + if not os.path.isdir(os.path.join(locale_dir, locale_id)): + babel_init(locale_code=locale_id) + + if args.update: + babel_update() + + if args.compile: + babel_compile() From 3bd9f6b7103fb0c5faffd5a82ad5b38794445167 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 11 Mar 2022 00:22:29 -0500 Subject: [PATCH 186/817] Ignore translation templates and compilations --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 6fb681e7..39afd65a 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,7 @@ cmake-build* /assets/web/fonts/fontawesome-free-web/scss/ /assets/web/fonts/fontawesome-free-web/sprites/ /assets/web/fonts/fontawesome-free-web/svgs/ + +# Translations +*.mo +*.pot From b3cdadca8681f6934446ef1c3a01b6d41737e915 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 11 Mar 2022 00:26:53 -0500 Subject: [PATCH 187/817] Create localize.yml --- .github/workflows/localize.yml | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/localize.yml diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml new file mode 100644 index 00000000..e9407c08 --- /dev/null +++ b/.github/workflows/localize.yml @@ -0,0 +1,42 @@ +name: localize + +on: + push: + branches: [nightly] + workflow_dispatch: + +jobs: + localize: + name: Update Localization + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Python 3.9 + uses: actions/setup-python@v3 # https://github.com/actions/setup-python + with: + python-version: '3.9' + + - name: Set up Python 3.9 Dependencies + run: | + cd ./locale + python -m pip install --upgrade pip setuptools + python -m pip install -r requirements.txt + + - name: Set up xgettext + run: | + sudo apt-get update -y && \ + sudo apt-get --reinstall install -y \ + gettext + + - name: Update Strings + run: | + python ./locale/_locale.py --extract --init --update + + - name: GitHub Commit & Push # push changes back into nightly + uses: actions-js/push@v1.2 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: nightly + message: localization updated by localize workflow From 88cf616a483462db86bf111c94e7a05573b652a2 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 11 Mar 2022 13:46:18 -0500 Subject: [PATCH 188/817] Move _locale.py and requirements --- {locale => scripts}/_locale.py | 0 {locale => scripts}/requirements.txt | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {locale => scripts}/_locale.py (100%) rename {locale => scripts}/requirements.txt (100%) diff --git a/locale/_locale.py b/scripts/_locale.py similarity index 100% rename from locale/_locale.py rename to scripts/_locale.py diff --git a/locale/requirements.txt b/scripts/requirements.txt similarity index 100% rename from locale/requirements.txt rename to scripts/requirements.txt From 01155ef4a3f31beab812fae1b0847c70e34f6172 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 11 Mar 2022 13:48:24 -0500 Subject: [PATCH 189/817] Update trigger events - Don't run if commits are all in 'locale' directory - Allows pushing changes back into nightly from this workflow without triggering and endless loop - Don't run job unless event is 'pull_request.merged' --- .github/workflows/localize.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index e9407c08..c587dcc9 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -3,11 +3,14 @@ name: localize on: push: branches: [nightly] + paths-ignore: + - 'locale/**' workflow_dispatch: jobs: localize: name: Update Localization + if: ${{ github.event.pull_request.merged }} runs-on: ubuntu-latest steps: - name: Checkout @@ -20,7 +23,7 @@ jobs: - name: Set up Python 3.9 Dependencies run: | - cd ./locale + cd ./scripts python -m pip install --upgrade pip setuptools python -m pip install -r requirements.txt @@ -32,7 +35,7 @@ jobs: - name: Update Strings run: | - python ./locale/_locale.py --extract --init --update + python ./scripts/_locale.py --extract --init --update - name: GitHub Commit & Push # push changes back into nightly uses: actions-js/push@v1.2 From f1d82a7d09d9f56b05b9a9a062095128972d8c2d Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 11 Mar 2022 14:00:23 -0500 Subject: [PATCH 190/817] Update trigger conditions - Only run when changes are made on files inside 'sunshine' directory - Prevents workflow from running again when this workflow pushes changes back into 'locale' directory - Should be cleaner than using 'paths-ignore' --- .github/workflows/localize.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index c587dcc9..b6619833 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -3,8 +3,8 @@ name: localize on: push: branches: [nightly] - paths-ignore: - - 'locale/**' + paths: # prevents workflow from running again when this workflow pushes changes back into 'locale' directory + - 'sunshine/**' # only localizing files inside sunshine directory workflow_dispatch: jobs: From 84584c950b37ccc1498055c17bd317d3f6b2a9e8 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 11 Mar 2022 14:07:10 -0500 Subject: [PATCH 191/817] Update comment --- .github/workflows/localize.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index b6619833..0ca9e621 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -3,7 +3,7 @@ name: localize on: push: branches: [nightly] - paths: # prevents workflow from running again when this workflow pushes changes back into 'locale' directory + paths: # prevents workflow from running unless files in these directories change - 'sunshine/**' # only localizing files inside sunshine directory workflow_dispatch: From a014391ae7e415256d47bec9925e93be9caf9922 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 13 Mar 2022 16:29:51 -0400 Subject: [PATCH 192/817] Update for Crowdin Integration - Rename extracted template file to `sunshine.po` - Add `crowdin.yml` - Remove `--init` and `--update` from `localize.yml` - Crowdin will initialize new languages and update existing ones --- .github/workflows/localize.yml | 2 +- crowdin.yml | 17 +++++++++++++++++ scripts/_locale.py | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 crowdin.yml diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index 0ca9e621..eafc7430 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -35,7 +35,7 @@ jobs: - name: Update Strings run: | - python ./scripts/_locale.py --extract --init --update + python ./scripts/_locale.py --extract - name: GitHub Commit & Push # push changes back into nightly uses: actions-js/push@v1.2 diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 00000000..d014a00c --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,17 @@ +"base_path": "." +"base_url": "https://api.crowdin.com" # optional (for Crowdin Enterprise only) +"preserve_hierarchy": false # flatten tree on crowdin + +"files" : [ + { + "source" : "/locale/*.po", + "translation" : "/locale/%two_letters_code%/LC_MESSAGES/%original_file_name%", + "languages_mapping": { + "two_letters_code": { + # map non-two letter codes here, left side is crowdin designation, right side is babel designation + "en-GB": "en_GB", + "en-US": "en_US" + } + } + } +] diff --git a/scripts/_locale.py b/scripts/_locale.py index 4a2ec2c1..b26a0592 100644 --- a/scripts/_locale.py +++ b/scripts/_locale.py @@ -38,7 +38,7 @@ def x_extract(): commands = [ 'xgettext', f'--default-domain={project_name.lower()}', - f'--output={os.path.join(locale_dir, project_name.lower() + ".pot")}', + f'--output={os.path.join(locale_dir, project_name.lower() + ".po")}', '--language=C++', '--boost', '--from-code=utf-8', From 907d0bfcd50855c2078e72846128881183e178c8 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 13 Mar 2022 16:39:33 -0400 Subject: [PATCH 193/817] Fix `.po` file extension --- scripts/_locale.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/_locale.py b/scripts/_locale.py index b26a0592..82e172cc 100644 --- a/scripts/_locale.py +++ b/scripts/_locale.py @@ -49,7 +49,7 @@ def x_extract(): '--package-version=v0' ] - pot_filepath = os.path.join(locale_dir, f'{project_name.lower()}.pot') + pot_filepath = os.path.join(locale_dir, f'{project_name.lower()}.po') extensions = ['cpp', 'h', 'm', 'mm'] @@ -91,7 +91,7 @@ def babel_init(locale_code: str): commands = [ 'pybabel', 'init', - '-i', os.path.join(locale_dir, f'{project_name.lower()}.pot'), + '-i', os.path.join(locale_dir, f'{project_name.lower()}.po'), '-d', locale_dir, '-D', project_name.lower(), '-l', locale_code @@ -106,7 +106,7 @@ def babel_update(): commands = [ 'pybabel', 'update', - '-i', os.path.join(locale_dir, f'{project_name.lower()}.pot'), + '-i', os.path.join(locale_dir, f'{project_name.lower()}.po'), '-d', locale_dir, '-D', project_name.lower(), '--update-header-comment' From 04a2ecaff4fe555cfdd557905892001d23dee4ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 12:38:51 +0000 Subject: [PATCH 194/817] Bump actions-js/push from 1.2 to 1.3 Bumps [actions-js/push](https://github.com/actions-js/push) from 1.2 to 1.3. - [Release notes](https://github.com/actions-js/push/releases) - [Commits](https://github.com/actions-js/push/compare/v1.2...v1.3) --- updated-dependencies: - dependency-name: actions-js/push dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/localize.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index eafc7430..57db544a 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -38,7 +38,7 @@ jobs: python ./scripts/_locale.py --extract - name: GitHub Commit & Push # push changes back into nightly - uses: actions-js/push@v1.2 + uses: actions-js/push@v1.3 with: github_token: ${{ secrets.GITHUB_TOKEN }} branch: nightly From 719f4cef595823964f3b372248116862f8d706fd Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 15 Mar 2022 19:17:06 -0400 Subject: [PATCH 195/817] Add pipefail and comments --- scripts/Dockerfile-ubuntu_20_04 | 6 ++++-- scripts/Dockerfile-ubuntu_21_04 | 4 ++-- scripts/Dockerfile-ubuntu_21_10 | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/scripts/Dockerfile-ubuntu_20_04 b/scripts/Dockerfile-ubuntu_20_04 index 5a633a10..e65f9176 100644 --- a/scripts/Dockerfile-ubuntu_20_04 +++ b/scripts/Dockerfile-ubuntu_20_04 @@ -3,6 +3,7 @@ FROM ubuntu:20.04 AS sunshine-ubuntu_20_04 ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" +SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN apt-get update -y && \ apt-get install -y \ build-essential \ @@ -31,12 +32,13 @@ RUN apt-get update -y && \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# Update gcc alias RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 +# Install CuDA RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm /root/cuda.run +# Entrypoint COPY build-private.sh /root/build.sh - - ENTRYPOINT ["/root/build.sh"] diff --git a/scripts/Dockerfile-ubuntu_21_04 b/scripts/Dockerfile-ubuntu_21_04 index d5668df4..c7c7a3d4 100644 --- a/scripts/Dockerfile-ubuntu_21_04 +++ b/scripts/Dockerfile-ubuntu_21_04 @@ -3,6 +3,7 @@ FROM ubuntu:21.04 AS sunshine-ubuntu_21_04 ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" +SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN apt-get update -y && \ apt-get install -y \ build-essential \ @@ -31,7 +32,6 @@ RUN apt-get update -y && \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# Entrypoint COPY build-private.sh /root/build.sh - - ENTRYPOINT ["/root/build.sh"] diff --git a/scripts/Dockerfile-ubuntu_21_10 b/scripts/Dockerfile-ubuntu_21_10 index 14770625..7b51da3d 100644 --- a/scripts/Dockerfile-ubuntu_21_10 +++ b/scripts/Dockerfile-ubuntu_21_10 @@ -3,6 +3,7 @@ FROM ubuntu:21.10 AS sunshine-ubuntu_21_10 ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" +SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN apt-get update -y && \ apt-get install -y \ build-essential \ @@ -31,7 +32,6 @@ RUN apt-get update -y && \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# Entrypoint COPY build-private.sh /root/build.sh - - ENTRYPOINT ["/root/build.sh"] From 3f309832f740204a3a72affa315150573243dffb Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 15 Mar 2022 19:17:22 -0400 Subject: [PATCH 196/817] Add comments --- scripts/Dockerfile-debian | 3 +-- scripts/Dockerfile-fedora_33 | 3 +-- scripts/Dockerfile-fedora_35 | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/scripts/Dockerfile-debian b/scripts/Dockerfile-debian index 15f0d656..809b20d3 100644 --- a/scripts/Dockerfile-debian +++ b/scripts/Dockerfile-debian @@ -33,7 +33,6 @@ RUN apt-get update -y && \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# Entrypoint COPY build-private.sh /root/build.sh - - ENTRYPOINT ["/root/build.sh"] diff --git a/scripts/Dockerfile-fedora_33 b/scripts/Dockerfile-fedora_33 index ae7e4959..a2c8dfa4 100644 --- a/scripts/Dockerfile-fedora_33 +++ b/scripts/Dockerfile-fedora_33 @@ -21,7 +21,6 @@ RUN dnf -y update && \ && dnf clean all \ && rm -rf /var/cache/yum +# Entrypoint COPY build-private.sh /root/build.sh - - ENTRYPOINT ["/root/build.sh"] diff --git a/scripts/Dockerfile-fedora_35 b/scripts/Dockerfile-fedora_35 index 0cde7aa4..ba7abac5 100644 --- a/scripts/Dockerfile-fedora_35 +++ b/scripts/Dockerfile-fedora_35 @@ -26,7 +26,6 @@ RUN dnf -y update && \ && dnf clean all \ && rm -rf /var/cache/yum +# Entrypoint COPY build-private.sh /root/build.sh - - ENTRYPOINT ["/root/build.sh"] From 615f7e58750d312e341702e21c09389ea4f1a3fc Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 15 Mar 2022 19:18:23 -0400 Subject: [PATCH 197/817] Use correct version of boost and cmake --- scripts/Dockerfile-ubuntu_18_04 | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 5ee33ae3..b6a05f6f 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -6,30 +6,24 @@ ARG TZ="Europe/London" SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN apt-get update -y && \ apt-get install -y \ - apt-transport-https \ - ca-certificates \ - gnupg \ software-properties-common \ - wget \ - && wget -qO - https://apt.kitware.com/keys/kitware-archive-latest.asc | apt-key add - && \ - add-apt-repository 'deb https://apt.kitware.com/ubuntu/ bionic main' && \ - add-apt-repository ppa:ubuntu-toolchain-r/test && \ - add-apt-repository ppa:savoury1/graphics && \ + && add-apt-repository ppa:savoury1/graphics && \ add-apt-repository ppa:savoury1/multimedia && \ add-apt-repository ppa:savoury1/ffmpeg4 && \ + add-apt-repository ppa:savoury1/boost-defaults-1.71 && \ + add-apt-repository ppa:ubuntu-toolchain-r/test && \ apt-get update -y && \ apt-get install -y \ build-essential \ cmake \ - ffmpeg \ gcc-10 \ git \ g++-10 \ libavdevice-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libboost-regex-dev \ - libboost-thread-dev \ + libboost-filesystem1.71-dev \ + libboost-log1.71-dev \ + libboost-regex1.71-dev \ + libboost-thread1.71-dev \ libcap-dev \ libdrm-dev \ libevdev-dev \ @@ -44,15 +38,24 @@ RUN apt-get update -y && \ libxfixes-dev \ libxrandr-dev \ libxtst-dev \ + wget \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# Update gcc alias RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 +# Install CuDA RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm /root/cuda.run -COPY build-private.sh /root/build.sh - +# Install cmake +ADD https://cmake.org/files/v3.22/cmake-3.22.2-linux-x86_64.sh /cmake-3.22.2-linux-x86_64.sh +RUN mkdir /opt/cmake +RUN sh /cmake-3.22.2-linux-x86_64.sh --prefix=/opt/cmake --skip-license +RUN ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake +RUN cmake --version +# Entrypoint +COPY build-private.sh /root/build.sh ENTRYPOINT ["/root/build.sh"] From cbafe093960260be3e1a1f9570c1973ee9cf3e3b Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 15 Mar 2022 19:23:08 -0400 Subject: [PATCH 198/817] Add ubuntu 18.04 build and - Prepare for rpm packaging --- .github/workflows/CI.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index d36e4a71..90557ec4 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -150,13 +150,16 @@ jobs: strategy: fail-fast: true # false to test all, true to fail entire job if any fail matrix: - distro: [ debian, ubuntu_20_04, ubuntu_21_04, ubuntu_21_10 ] # removed ubuntu_18_04 for now + distro: [ debian, ubuntu_18_04, ubuntu_20_04, ubuntu_21_04, ubuntu_21_10 ] package: [ -p ] + extension: 'deb' include: # don't package these - distro: fedora_33 package: '' + extension: 'rpm' - distro: fedora_35 package: '' + extension: 'rpm' steps: - name: Checkout @@ -175,10 +178,10 @@ jobs: cd scripts sudo ./build-sunshine.sh ${{ matrix.package }} -u -n sunshine-${{ matrix.distro }} -s .. - name: Package Linux - if: ${{ matrix.package == '-p' }} + if: ${{ matrix.package != '' }} run: | cd scripts - sudo mv ./sunshine-${{ matrix.distro }}-build/sunshine-${{ matrix.distro }}.deb ../artifacts/ + sudo mv ./sunshine-${{ matrix.distro }}-build/sunshine-${{ matrix.distro }}.{matrix.extension} ../artifacts/ - name: Upload Artifacts if: ${{ matrix.package == '-p' && ( github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' ) }} uses: actions/upload-artifact@v2 From 7f22774e08f1e76afcb6797577d778a617915cb6 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 15 Mar 2022 19:29:12 -0400 Subject: [PATCH 199/817] Fix syntax error --- .github/workflows/CI.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 90557ec4..77c74666 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -152,14 +152,14 @@ jobs: matrix: distro: [ debian, ubuntu_18_04, ubuntu_20_04, ubuntu_21_04, ubuntu_21_10 ] package: [ -p ] - extension: 'deb' + extension: [ deb ] include: # don't package these - distro: fedora_33 package: '' - extension: 'rpm' + extension: rpm - distro: fedora_35 package: '' - extension: 'rpm' + extension: rpm steps: - name: Checkout From 67762aa445d10cacc855e18f68e1622b777dfbe4 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 15 Mar 2022 19:39:21 -0400 Subject: [PATCH 200/817] Fix `matrix.extension` --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 77c74666..6da1c862 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -181,7 +181,7 @@ jobs: if: ${{ matrix.package != '' }} run: | cd scripts - sudo mv ./sunshine-${{ matrix.distro }}-build/sunshine-${{ matrix.distro }}.{matrix.extension} ../artifacts/ + sudo mv ./sunshine-${{ matrix.distro }}-build/sunshine-${{ matrix.distro }}.${{ matrix.extension }} ../artifacts/ - name: Upload Artifacts if: ${{ matrix.package == '-p' && ( github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' ) }} uses: actions/upload-artifact@v2 From 2a7af03f9a0ba79299ef9eafbdec7e5cdb0ce16c Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 15 Mar 2022 22:02:16 -0400 Subject: [PATCH 201/817] Rename packages --- .github/workflows/CI.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6da1c862..66a96154 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -121,9 +121,9 @@ jobs: rm -f "$CONFIG_DIR"/apps_windows.json mkdir -p ./"$HOME_DIR"/.config/"$CONFIG_DIR" cp ./"$CONFIG_DIR"/apps_linux.json ./"$HOME_DIR"/.config/"$CONFIG_DIR" - zip -r ./sunshine_linux.zip ./sunshine/* + zip -r ./sunshine-appimage.zip ./sunshine/* - mv sunshine_linux.zip ../artifacts/ + mv sunshine-appimage.zip ../artifacts/ - name: Verify AppImage run: | cd appimage_temp @@ -236,16 +236,16 @@ jobs: run: | cd sunshine-windows-build del ..\assets\apps_linux.json - 7z a Sunshine-Windows.zip ..\assets - 7z a Sunshine-Windows.zip sunshine.exe - 7z a Sunshine-Windows.zip tools\dxgi-info.exe - 7z a Sunshine-Windows.zip tools\audio-info.exe - 7z a Sunshine-Windows.zip tools\sunshinesvc.exe - 7z a Sunshine-Windows.zip ..\tools\install-service.bat - 7z a Sunshine-Windows.zip ..\tools\uninstall-service.bat + 7z a sunshine-windows.zip ..\assets + 7z a sunshine-windows.zip sunshine.exe + 7z a sunshine-windows.zip tools\dxgi-info.exe + 7z a sunshine-windows.zip tools\audio-info.exe + 7z a sunshine-windows.zip tools\sunshinesvc.exe + 7z a sunshine-windows.zip ..\tools\install-service.bat + 7z a sunshine-windows.zip ..\tools\uninstall-service.bat cd .. mkdir artifacts - move "sunshine-windows-build\Sunshine-Windows.zip" "artifacts" + move "sunshine-windows-build\sunshine-windows.zip" "artifacts" - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v2 From 0a9cc511ed0c0c7cff8800a1cc15facbb13688c8 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 15 Mar 2022 22:06:24 -0400 Subject: [PATCH 202/817] Update config.yml --- .github/ISSUE_TEMPLATE/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 64fadc7a..c96ec16d 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,8 @@ blank_issues_enabled: false contact_links: + - name: AUR Package Issue + url: https://aur.archlinux.org/packages/sunshine + about: AUR Package Issues should be discussed on the AUR - name: Github Discussions url: https://github.com/SunshineStream/Sunshine/discussions about: General discussion, support, feature requests and more! From 20c0426acea5d9db8f2521f596422731ec579931 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 15 Mar 2022 22:11:44 -0400 Subject: [PATCH 203/817] Update pull_request_template.md --- .github/pull_request_template.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e3bcbfdc..6c3bab87 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,26 +1,22 @@ ## Description - -Please include a summary of the changes. + ### Screenshot - -Include screenshots if the changes are UI-related. + ### Issues Fixed or Closed - + - Fixes #(issue) ## Type of Change - -Please delete options that are not relevant. - + - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) ## Checklist - + - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] I have added or updated the documentation blocks for new or existing components +- [ ] I have added or updated the docstring/documentation-blocks for new or existing methods/components From 5bb197ccfcb7079047ff60771f582b54793ac61a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 15 Mar 2022 22:30:01 -0400 Subject: [PATCH 204/817] Update sunshine.ico --- sunshine.ico | Bin 40454 -> 120760 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/sunshine.ico b/sunshine.ico index 8cfdfed9a006a46d7b4feb5c1955eccf9721d0fd..4d07b0852816e4d952accfca4e5703ca7c123412 100644 GIT binary patch literal 120760 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 z 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 literal 40454 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 Date: Tue, 15 Mar 2022 22:30:10 -0400 Subject: [PATCH 205/817] Delete gamepad.png --- gamepad.png | Bin 24804 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 gamepad.png diff --git a/gamepad.png b/gamepad.png deleted file mode 100644 index 274c7cede5d1740322b863990181d0a1810e3567..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24804 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 From 9930880ee6e08af75b8557f8a4d7b65b7a3d57f8 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 15 Mar 2022 22:30:23 -0400 Subject: [PATCH 206/817] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 72674eac..d61b812c 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -![Sunshine icon](gamepad.png "Sunshine") # Introduction Sunshine is a Gamestream host for Moonlight -[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/cgrtw2g3fq9b0b70/branch/master?svg=true)](https://ci.appveyor.com/project/loki-47-6F-64/sunshine/branch/master) -[![Downloads](https://img.shields.io/github/downloads/Loki-47-6F-64/sunshine/total)](https://github.com/Loki-47-6F-64/sunshine/releases) +[![CI](https://github.com/SunshineStream/Sunshine/actions/workflows/CI.yml/badge.svg?branch=master)](https://github.com/SunshineStream/Sunshine/actions/workflows/CI.yml) +[![Downloads](https://img.shields.io/github/downloads/sunshinestream/sunshine/total)](https://github.com/sunshinestream/sunshine/releases) +[![Crowdin](https://badges.crowdin.net/sunshinestream/localized.svg)](https://crowdin.com/project/sunshinestream) - [Building](README.md#building) - [Credits](README.md#credits) @@ -198,11 +198,11 @@ All shortcuts start with CTRL + ALT + SHIFT, just like Moonlight - CTRL + ALT + SHIFT + F1/F13 --> Switch to different monitor for Streaming ## Credits: +- [loki-47-6F-64/sunshine](https://github.com/loki-47-6F-64/sunshine) (For all the hard work put in to create sunshine in the first place!) - [Simple-Web-Server](https://gitlab.com/eidheim/Simple-Web-Server) - [Moonlight](https://github.com/moonlight-stream) - [Looking-Glass](https://github.com/gnif/LookingGlass) (For showing me how to properly capture frames on Windows, saving me a lot of time :) - [Eretik](http://eretik.omegahg.com/) (For creating PolicyConfig.h, allowing me to change the default audio device on Windows programmatically) -- [Twitter emoji](https://github.com/twitter/twemoji/blob/master/LICENSE-GRAPHICS) (Sunshine's icon is made of twemoji) ## Application List: **Note:** You can change the Application List in the "Apps" section of the User Interface `https://xxx.xxx.xxx.xxx:47990/` From 9c976a23de16c972e98dc155f3ff50d7e57bb25f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 16 Mar 2022 18:34:43 -0400 Subject: [PATCH 207/817] Rename artifacts --- .github/workflows/CI.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 66a96154..76d38cd0 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -132,7 +132,7 @@ jobs: if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v2 with: - name: sunshine-AppImage + name: sunshine-appimage path: artifacts/ - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} @@ -250,7 +250,7 @@ jobs: if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v2 with: - name: sunshine-${{ runner.os }} + name: sunshine-windows path: artifacts/ - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} From a9b9d1bd09e9541d9d484ae48aeead8dec2ebae6 Mon Sep 17 00:00:00 2001 From: Mathias Tillman Date: Thu, 7 Apr 2022 08:20:47 +0200 Subject: [PATCH 208/817] Properly catch exceptions in stream broadcast handlers to prevent unhandled exception crash/termination. --- sunshine/stream.cpp | 130 ++++++++++++++++++++++++-------------------- 1 file changed, 71 insertions(+), 59 deletions(-) diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index dd14e829..10e215b6 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -963,63 +963,69 @@ void videoBroadcastThread(udp::socket &sock) { fec_blocks[0] = payload; } - auto blockIndex = 0; - std::for_each(fec_blocks_begin, fec_blocks_end, [&](std::string_view ¤t_payload) { - auto packets = (current_payload.size() + (blocksize - 1)) / blocksize; + try { + auto blockIndex = 0; + std::for_each(fec_blocks_begin, fec_blocks_end, [&](std::string_view ¤t_payload) { + auto packets = (current_payload.size() + (blocksize - 1)) / blocksize; - for(int x = 0; x < packets; ++x) { - auto *inspect = (video_packet_raw_t *)¤t_payload[x * blocksize]; + for(int x = 0; x < packets; ++x) { + auto *inspect = (video_packet_raw_t *)¤t_payload[x * blocksize]; - inspect->packet.frameIndex = packet->pts; - inspect->packet.streamPacketIndex = ((uint32_t)lowseq + x) << 8; + inspect->packet.frameIndex = packet->pts; + inspect->packet.streamPacketIndex = ((uint32_t)lowseq + x) << 8; - // Match multiFecFlags with Moonlight - inspect->packet.multiFecFlags = 0x10; - inspect->packet.multiFecBlocks = (blockIndex << 4) | lastBlockIndex; + // Match multiFecFlags with Moonlight + inspect->packet.multiFecFlags = 0x10; + inspect->packet.multiFecBlocks = (blockIndex << 4) | lastBlockIndex; - if(x == 0) { - inspect->packet.flags |= FLAG_SOF; - } + if(x == 0) { + inspect->packet.flags |= FLAG_SOF; + } - if(x == packets - 1) { - inspect->packet.flags |= FLAG_EOF; + if(x == packets - 1) { + inspect->packet.flags |= FLAG_EOF; + } } - } - auto shards = fec::encode(current_payload, blocksize, fecPercentage, session->config.minRequiredFecPackets); + auto shards = fec::encode(current_payload, blocksize, fecPercentage, session->config.minRequiredFecPackets); - // set FEC info now that we know for sure what our percentage will be for this frame - for(auto x = 0; x < shards.size(); ++x) { - auto *inspect = (video_packet_raw_t *)shards.data(x); + // set FEC info now that we know for sure what our percentage will be for this frame + for(auto x = 0; x < shards.size(); ++x) { + auto *inspect = (video_packet_raw_t *)shards.data(x); - inspect->packet.fecInfo = - (x << 12 | - shards.data_shards << 22 | - shards.percentage << 4); + inspect->packet.fecInfo = + (x << 12 | + shards.data_shards << 22 | + shards.percentage << 4); - inspect->rtp.header = 0x80 | FLAG_EXTENSION; - inspect->rtp.sequenceNumber = util::endian::big(lowseq + x); + inspect->rtp.header = 0x80 | FLAG_EXTENSION; + inspect->rtp.sequenceNumber = util::endian::big(lowseq + x); - inspect->packet.multiFecBlocks = (blockIndex << 4) | lastBlockIndex; - inspect->packet.frameIndex = packet->pts; - } + inspect->packet.multiFecBlocks = (blockIndex << 4) | lastBlockIndex; + inspect->packet.frameIndex = packet->pts; + } - for(auto x = 0; x < shards.size(); ++x) { - sock.send_to(asio::buffer(shards[x]), session->video.peer); - } + for(auto x = 0; x < shards.size(); ++x) { + sock.send_to(asio::buffer(shards[x]), session->video.peer); + } - if(packet->flags & AV_PKT_FLAG_KEY) { - BOOST_LOG(verbose) << "Key Frame ["sv << packet->pts << "] :: send ["sv << shards.size() << "] shards..."sv; - } - else { - BOOST_LOG(verbose) << "Frame ["sv << packet->pts << "] :: send ["sv << shards.size() << "] shards..."sv << std::endl; - } + if(packet->flags & AV_PKT_FLAG_KEY) { + BOOST_LOG(verbose) << "Key Frame ["sv << packet->pts << "] :: send ["sv << shards.size() << "] shards..."sv; + } + else { + BOOST_LOG(verbose) << "Frame ["sv << packet->pts << "] :: send ["sv << shards.size() << "] shards..."sv << std::endl; + } - ++blockIndex; - lowseq += shards.size(); - }); + ++blockIndex; + lowseq += shards.size(); + }); - session->video.lowseq = lowseq; + session->video.lowseq = lowseq; + } + catch(const std::exception &e) { + BOOST_LOG(error) << "Broadcast video failed "sv << e.what(); + std::this_thread::sleep_for(100ms); + } } shutdown_event->raise(true); @@ -1077,30 +1083,36 @@ void audioBroadcastThread(udp::socket &sock) { auto &shards_p = session->audio.shards_p; std::copy_n(audio_packet->payload(), bytes, shards_p[sequenceNumber % RTPA_DATA_SHARDS]); - sock.send_to(asio::buffer((char *)audio_packet.get(), sizeof(audio_packet_raw_t) + bytes), session->audio.peer); + try { + sock.send_to(asio::buffer((char *)audio_packet.get(), sizeof(audio_packet_raw_t) + bytes), session->audio.peer); - BOOST_LOG(verbose) << "Audio ["sv << sequenceNumber << "] :: send..."sv; + BOOST_LOG(verbose) << "Audio ["sv << sequenceNumber << "] :: send..."sv; - auto &fec_packet = session->audio.fec_packet; - // initialize the FEC header at the beginning of the FEC block - if(sequenceNumber % RTPA_DATA_SHARDS == 0) { - fec_packet->fecHeader.baseSequenceNumber = util::endian::big(sequenceNumber); - fec_packet->fecHeader.baseTimestamp = util::endian::big(timestamp); - } + auto &fec_packet = session->audio.fec_packet; + // initialize the FEC header at the beginning of the FEC block + if(sequenceNumber % RTPA_DATA_SHARDS == 0) { + fec_packet->fecHeader.baseSequenceNumber = util::endian::big(sequenceNumber); + fec_packet->fecHeader.baseTimestamp = util::endian::big(timestamp); + } - // generate parity shards at the end of the FEC block - if((sequenceNumber + 1) % RTPA_DATA_SHARDS == 0) { - reed_solomon_encode(rs.get(), shards_p.begin(), RTPA_TOTAL_SHARDS, bytes); + // generate parity shards at the end of the FEC block + if((sequenceNumber + 1) % RTPA_DATA_SHARDS == 0) { + reed_solomon_encode(rs.get(), shards_p.begin(), RTPA_TOTAL_SHARDS, bytes); - for(auto x = 0; x < RTPA_FEC_SHARDS; ++x) { - fec_packet->rtp.sequenceNumber = util::endian::big(sequenceNumber + x + 1); - fec_packet->fecHeader.fecShardIndex = x; - memcpy(fec_packet->payload(), shards_p[RTPA_DATA_SHARDS + x], bytes); - sock.send_to(asio::buffer((char *)fec_packet.get(), sizeof(audio_fec_packet_raw_t) + bytes), session->audio.peer); - BOOST_LOG(verbose) << "Audio FEC ["sv << (sequenceNumber & ~(RTPA_DATA_SHARDS - 1)) << ' ' << x << "] :: send..."sv; + for(auto x = 0; x < RTPA_FEC_SHARDS; ++x) { + fec_packet->rtp.sequenceNumber = util::endian::big(sequenceNumber + x + 1); + fec_packet->fecHeader.fecShardIndex = x; + memcpy(fec_packet->payload(), shards_p[RTPA_DATA_SHARDS + x], bytes); + sock.send_to(asio::buffer((char *)fec_packet.get(), sizeof(audio_fec_packet_raw_t) + bytes), session->audio.peer); + BOOST_LOG(verbose) << "Audio FEC ["sv << (sequenceNumber & ~(RTPA_DATA_SHARDS - 1)) << ' ' << x << "] :: send..."sv; + } } } + catch(const std::exception &e) { + BOOST_LOG(error) << "Broadcast audio failed "sv << e.what(); + std::this_thread::sleep_for(100ms); + } } shutdown_event->raise(true); From b854807d40795c12b2104e7c56c90e1d596a4374 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 14:37:04 +0000 Subject: [PATCH 209/817] Bump actions/upload-artifact from 2 to 3 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 3. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/CI.yml | 6 +++--- .github/workflows/clang.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 76d38cd0..56f1dab1 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -130,7 +130,7 @@ jobs: wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage && chmod +x appimagelint-x86_64.AppImage && ./appimagelint-x86_64.AppImage ./sunshine/sunshine.AppImage - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: sunshine-appimage path: artifacts/ @@ -184,7 +184,7 @@ jobs: sudo mv ./sunshine-${{ matrix.distro }}-build/sunshine-${{ matrix.distro }}.${{ matrix.extension }} ../artifacts/ - name: Upload Artifacts if: ${{ matrix.package == '-p' && ( github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' ) }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: sunshine-${{ matrix.distro }} path: artifacts/ @@ -248,7 +248,7 @@ jobs: move "sunshine-windows-build\sunshine-windows.zip" "artifacts" - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: sunshine-windows path: artifacts/ diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index 37299687..2ab2f600 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -29,7 +29,7 @@ jobs: - name: Upload Artifacts if: ${{ matrix.inplace == true }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: sunshine path: sunshine/ From 1ad0c93ad80b207d0a5d8dbc50963da4e7905f78 Mon Sep 17 00:00:00 2001 From: Michael Rogers Date: Tue, 12 Apr 2022 16:13:05 -0500 Subject: [PATCH 210/817] Changed the video packet to contain AVPacket instead of extending it. --- sunshine/stream.cpp | 18 ++++++++++-------- sunshine/video.cpp | 18 ++++++++++-------- sunshine/video.h | 16 ++++------------ 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index 10e215b6..27fbe85a 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -895,7 +895,8 @@ void videoBroadcastThread(udp::socket &sock) { auto session = (session_t *)packet->channel_data; auto lowseq = session->video.lowseq; - std::string_view payload { (char *)packet->data, (size_t)packet->size }; + auto av_packet = packet->av_packet; + std::string_view payload { (char *)av_packet->data, (size_t)av_packet->size }; std::vector payload_new; auto nv_packet_header = "\0017charss"sv; @@ -904,7 +905,7 @@ void videoBroadcastThread(udp::socket &sock) { payload = { (char *)payload_new.data(), payload_new.size() }; - if(packet->flags & AV_PKT_FLAG_KEY) { + if(av_packet->flags & AV_PKT_FLAG_KEY) { for(auto &replacement : *packet->replacements) { auto frame_old = replacement.old; auto frame_new = replacement._new; @@ -969,9 +970,10 @@ void videoBroadcastThread(udp::socket &sock) { auto packets = (current_payload.size() + (blocksize - 1)) / blocksize; for(int x = 0; x < packets; ++x) { - auto *inspect = (video_packet_raw_t *)¤t_payload[x * blocksize]; + auto *inspect = (video_packet_raw_t *)¤t_payload[x * blocksize]; + auto av_packet = packet->av_packet; - inspect->packet.frameIndex = packet->pts; + inspect->packet.frameIndex = av_packet->pts; inspect->packet.streamPacketIndex = ((uint32_t)lowseq + x) << 8; // Match multiFecFlags with Moonlight @@ -1002,18 +1004,18 @@ void videoBroadcastThread(udp::socket &sock) { inspect->rtp.sequenceNumber = util::endian::big(lowseq + x); inspect->packet.multiFecBlocks = (blockIndex << 4) | lastBlockIndex; - inspect->packet.frameIndex = packet->pts; + inspect->packet.frameIndex = av_packet->pts; } for(auto x = 0; x < shards.size(); ++x) { sock.send_to(asio::buffer(shards[x]), session->video.peer); } - if(packet->flags & AV_PKT_FLAG_KEY) { - BOOST_LOG(verbose) << "Key Frame ["sv << packet->pts << "] :: send ["sv << shards.size() << "] shards..."sv; + if(av_packet->flags & AV_PKT_FLAG_KEY) { + BOOST_LOG(verbose) << "Key Frame ["sv << av_packet->pts << "] :: send ["sv << shards.size() << "] shards..."sv; } else { - BOOST_LOG(verbose) << "Frame ["sv << packet->pts << "] :: send ["sv << shards.size() << "] shards..."sv << std::endl; + BOOST_LOG(verbose) << "Frame ["sv << av_packet->pts << "] :: send ["sv << shards.size() << "] shards..."sv << std::endl; } ++blockIndex; diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 0f7eff35..f49cac95 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -776,9 +776,10 @@ int encode(int64_t frame_nr, session_t &session, frame_t::pointer frame, safe::m } while(ret >= 0) { - auto packet = std::make_unique(nullptr); + auto packet = std::make_unique(nullptr); + auto av_packet = packet.get()->av_packet; - ret = avcodec_receive_packet(ctx.get(), packet.get()); + ret = avcodec_receive_packet(ctx.get(), av_packet); if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { return 0; } @@ -788,12 +789,12 @@ int encode(int64_t frame_nr, session_t &session, frame_t::pointer frame, safe::m if(session.inject) { if(session.inject == 1) { - auto h264 = cbs::make_sps_h264(ctx.get(), packet.get()); + auto h264 = cbs::make_sps_h264(ctx.get(), av_packet); sps = std::move(h264.sps); } else { - auto hevc = cbs::make_sps_hevc(ctx.get(), packet.get()); + auto hevc = cbs::make_sps_hevc(ctx.get(), av_packet); sps = std::move(hevc.sps); vps = std::move(hevc.vps); @@ -1470,20 +1471,21 @@ int validate_config(std::shared_ptr &disp, const encoder_t &en } } - auto packet = packets->pop(); - if(!(packet->flags & AV_PKT_FLAG_KEY)) { + auto packet = packets->pop(); + auto av_packet = packet->av_packet; + if(!(av_packet->flags & AV_PKT_FLAG_KEY)) { BOOST_LOG(error) << "First packet type is not an IDR frame"sv; return -1; } int flag = 0; - if(cbs::validate_sps(&*packet, config.videoFormat ? AV_CODEC_ID_H265 : AV_CODEC_ID_H264)) { + if(cbs::validate_sps(&*av_packet, config.videoFormat ? AV_CODEC_ID_H265 : AV_CODEC_ID_H264)) { flag |= VUI_PARAMS; } auto nalu_prefix = config.videoFormat ? hevc_nalu : h264_nalu; - std::string_view payload { (char *)packet->data, (std::size_t)packet->size }; + std::string_view payload { (char *)av_packet->data, (std::size_t)av_packet->size }; if(std::search(std::begin(payload), std::end(payload), std::begin(nalu_prefix), std::end(nalu_prefix)) != std::end(payload)) { flag |= NALU_PREFIX_5b; } diff --git a/sunshine/video.h b/sunshine/video.h index fab4ff76..05df4463 100644 --- a/sunshine/video.h +++ b/sunshine/video.h @@ -16,17 +16,9 @@ extern "C" { struct AVPacket; namespace video { -struct packet_raw_t : public AVPacket { +struct packet_raw_t { void init_packet() { - pts = AV_NOPTS_VALUE; - dts = AV_NOPTS_VALUE; - pos = -1; - duration = 0; - flags = 0; - stream_index = 0; - buf = nullptr; - side_data = nullptr; - side_data_elems = 0; + this->av_packet = av_packet_alloc(); } template @@ -39,7 +31,7 @@ struct packet_raw_t : public AVPacket { } ~packet_raw_t() { - av_packet_unref(this); + av_packet_unref(this->av_packet); } struct replace_t { @@ -51,8 +43,8 @@ struct packet_raw_t : public AVPacket { replace_t(std::string_view old, std::string_view _new) noexcept : old { std::move(old) }, _new { std::move(_new) } {} }; + AVPacket *av_packet; std::vector *replacements; - void *channel_data; }; From a6921fffad2180cd237dbc4af11920a0d14de58f Mon Sep 17 00:00:00 2001 From: h <65380846+thatsysadmin@users.noreply.github.com> Date: Tue, 12 Apr 2022 20:29:08 -0700 Subject: [PATCH 211/817] Add initial support for RPM packaging (#121) - Add gen-rpm - Package rpm in CI testing and releases - Remove fedora 33 from testing (end of life) - Update arguments for `build_private.sh` and `build_sunshine.sh` Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> --- .github/workflows/CI.yml | 9 +- CMakeLists.txt | 1 + README.md | 45 +++++++- assets/85-sunshine-rules.rules | 1 + gen-rpm.in | 179 ++++++++++++++++++++++++++++++++ scripts/Dockerfile-debian | 2 +- scripts/Dockerfile-fedora_33 | 3 +- scripts/Dockerfile-fedora_35 | 3 +- scripts/Dockerfile-ubuntu_18_04 | 2 +- scripts/Dockerfile-ubuntu_20_04 | 2 +- scripts/Dockerfile-ubuntu_21_04 | 2 +- scripts/Dockerfile-ubuntu_21_10 | 2 +- scripts/build-private.sh | 15 ++- scripts/build-sunshine.sh | 25 ++++- 14 files changed, 271 insertions(+), 20 deletions(-) create mode 100644 assets/85-sunshine-rules.rules create mode 100755 gen-rpm.in diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 76d38cd0..397ee3d8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -153,12 +153,9 @@ jobs: distro: [ debian, ubuntu_18_04, ubuntu_20_04, ubuntu_21_04, ubuntu_21_10 ] package: [ -p ] extension: [ deb ] - include: # don't package these - - distro: fedora_33 - package: '' - extension: rpm + include: # package these differently - distro: fedora_35 - package: '' + package: '-p' extension: rpm steps: @@ -176,7 +173,7 @@ jobs: - name: Build Linux run: | cd scripts - sudo ./build-sunshine.sh ${{ matrix.package }} -u -n sunshine-${{ matrix.distro }} -s .. + sudo ./build-sunshine.sh ${{ matrix.package }} -e ${{ matrix.extension }} -u -n sunshine-${{ matrix.distro }} -s .. - name: Package Linux if: ${{ matrix.package != '' }} run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index c3a1936e..862e4f1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -313,6 +313,7 @@ else() set(SUNSHINE_EXECUTABLE_PATH "sunshine") endif() configure_file(gen-deb.in gen-deb @ONLY) + configure_file(gen-rpm.in gen-rpm @ONLY) configure_file(sunshine.desktop.in sunshine.desktop @ONLY) configure_file(sunshine.service.in sunshine.service @ONLY) endif() diff --git a/README.md b/README.md index d61b812c..f062769e 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ They make use of docker to handle building Sunshine automatically ### Requirements: -Ubuntu 20.04: +#### Ubuntu 20.04: Install the following: #### Common @@ -52,15 +52,56 @@ On Ubuntu 20.04, the cuda compiler will fail since it's version is too old, it's sudo apt install nvidia-cuda-dev nvidia-cuda-toolkit ``` +#### Fedora 35: + +You will need some things in the RPMFusion repo, nost notably ffmpeg. +``` +sudo dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm +``` +#### Development tools and libraries +``` +sudo dnf install \ + boost-devel \ + boost-static.x86_64 \ + cmake \ + ffmpeg-devel \ + gcc-c++ \ + libevdev-devel \ + libxcb-devel \ + libX11-devel \ + libXcursor-devel \ + libXfixes-devel \ + libXinerama-devel \ + libXi-devel \ + libXrandr-devel \ + libXtst-devel \ + mesa-libGL-devel \ + openssl-devel \ + opus-devel \ + pulseaudio-libs-devel +``` +#### If you need to build an RPM binary package: +``` +sudo dnf install rpmbuild +``` + #### Warning: You might require ffmpeg version >= 4.3. Check the troubleshooting section for more information. ### Compilation: -- `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules` + +#### Ubuntu +- `git clone https://github.com/SunshineStream/Sunshine.git --recurse-submodules` - `cd sunshine && mkdir build && cd build` - `cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 ..` - `make -j ${nproc}` +#### Fedora +- `git clone https://github.com/SunshineStream/Sunshine.git --recurse-submodules` +- `cd sunshine && mkdir build && cd build` +- `cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ ..` +- `make -j ${nproc}` + ### Setup: sunshine needs access to uinput to create mouse and gamepad events: diff --git a/assets/85-sunshine-rules.rules b/assets/85-sunshine-rules.rules new file mode 100644 index 00000000..46a78a4a --- /dev/null +++ b/assets/85-sunshine-rules.rules @@ -0,0 +1 @@ +KERNEL=="uinput", GROUP="input", MODE="0660" \ No newline at end of file diff --git a/gen-rpm.in b/gen-rpm.in new file mode 100755 index 00000000..e2bec8f4 --- /dev/null +++ b/gen-rpm.in @@ -0,0 +1,179 @@ +#!/bin/sh + +# Export filepaths +export BUILDDIR=@CMAKE_CURRENT_SOURCE_DIR@/build +export BUILDROOT=~/rpmbuild/ +export RPMSRC=~/rpmbuild/SOURCES +export RPMSPEC=~/rpmbuild/SPECS +export RPMBUILD=~/rpmbuild/BUILD + +# Check for Docker switch +if [ "$1" == "-d" ]; then + export DOCKERSTATUS=TRUE +else + export DOCKERSTATUS=FALSE +fi + +# Check if user's rpmbuild folder is there, if so, temoprairly rename it. +if [ -d ~/rpmbuild ]; then + echo "Backing up rpmbuild" + ~/rpmbuild ~/rpmbuild.bkp + export RPMBUILDEXISTS=TRUE +else + export RPMBUILDEXISTS=FALSE +fi + +# Create rpmbuild folder structure +mkdir ~/rpmbuild +mkdir ~/rpmbuild/BUILD +mkdir ~/rpmbuild/BUILDROOT +mkdir ~/rpmbuild/RPMS +mkdir ~/rpmbuild/SOURCES +mkdir ~/rpmbuild/SPECS +mkdir ~/rpmbuild/SRPMS + +# Create sunshine .spec file with preinstall and postinstall scripts +cat << 'EOF' > $RPMSPEC/sunshine.spec +Name: sunshine +Version: @PROJECT_VERSION@ +Release: 1%{?dist} +Summary: An NVIDIA Gamestream-compatible hosting server +BuildArch: x86_64 + +License: GPLv3 +URL: https://github.com/SunshineStream/Sunshine +Source0: sunshine-@PROJECT_VERSION@_bin.tar.gz + +Requires: systemd + +%description +An NVIDIA Gamestream-compatible hosting server + +%pre +#!/bin/sh + +# Sunshine Pre-Install Script +# Store backup for old config files to prevent it from being overwritten +if [ -f /etc/sunshine/sunshine.conf ]; then + cp /etc/sunshine/sunshine.conf /etc/sunshine/sunshine.conf.old +fi + +if [ -f /etc/sunshine/apps_linux.json ]; then + cp /etc/sunshine/apps_linux.json /etc/sunshine/apps_linux.json.old +fi + +%post +#!/bin/sh + +# Sunshine Post-Install Script +export GROUP_INPUT=input + +if [ -f /etc/group ]; then + if ! grep -q $GROUP_INPUT /etc/group; then + echo "Creating group $GROUP_INPUT" + + groupadd $GROUP_INPUT + fi +else + echo "Warning: /etc/group not found" +fi + +if [ -f /etc/sunshine/sunshine.conf.old ]; then + echo "Restoring old sunshine.conf" + mv /etc/sunshine/sunshine.conf.old /etc/sunshine/sunshine.conf +fi + +if [ -f /etc/sunshine/apps_linux.json.old ]; then + echo "Restoring old apps_linux.json" + mv /etc/sunshine/apps_linux.json.old /etc/sunshine/apps_linux.json +fi + +# Update permissions on config files for Web Manager +if [ -f /etc/sunshine/apps_linux.json ]; then + echo "chmod 666 /etc/sunshine/apps_linux.json" + chmod 666 /etc/sunshine/apps_linux.json +fi + +if [ -f /etc/sunshine/sunshine.conf ]; then + echo "chmod 666 /etc/sunshine/sunshine.conf" + chmod 666 /etc/sunshine/sunshine.conf +fi + +# Ensure Sunshine can grab images from KMS +path_to_setcap=$(which setcap) +if [ -x "$path_to_setcap" ] ; then + echo "$path_to_setcap cap_sys_admin+p /usr/bin/sunshine" + $path_to_setcap cap_sys_admin+p /usr/bin/sunshine +fi + +%prep +%setup -q + +%install +rm -rf $RPM_BUILD_ROOT +mkdir -p $RPM_BUILD_ROOT/%{_bindir} +mkdir -p $RPM_BUILD_ROOT/etc/sunshine +mkdir -p $RPM_BUILD_ROOT/usr/lib/systemd/user +mkdir -p $RPM_BUILD_ROOT/usr/share/applications +mkdir -p $RPM_BUILD_ROOT/etc/udev/rules.d + +cp sunshine $RPM_BUILD_ROOT/%{_bindir}/sunshine +cp sunshine.conf $RPM_BUILD_ROOT/etc/sunshine/sunshine.conf +cp apps_linux.json $RPM_BUILD_ROOT/etc/sunshine/apps_linux.json +cp sunshine.service $RPM_BUILD_ROOT/usr/lib/systemd/user/sunshine.service +cp sunshine.desktop $RPM_BUILD_ROOT/usr/share/applications/sunshine.desktop +cp 85-sunshine-rules.rules $RPM_BUILD_ROOT/etc/udev/rules.d/85-sunshine-rules.rules + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%{_bindir}/sunshine +/usr/lib/systemd/user/sunshine.service +/etc/sunshine/sunshine.conf +/etc/sunshine/apps_linux.json +/usr/share/applications/sunshine.desktop +/etc/udev/rules.d/85-sunshine-rules.rules + +%changelog +* Sat Mar 12 2022 h <65380846+thatsysadmin@users.noreply.github.com> +- Initial packaging of Sunshine. +EOF + +# Copy over sunshine binary and supplemental files into rpmbuild/BUILD/ +mkdir genrpm +mkdir genrpm/sunshine-@PROJECT_VERSION@ +cp sunshine-@PROJECT_VERSION@ genrpm/sunshine-@PROJECT_VERSION@/sunshine +cp sunshine.service genrpm/sunshine-@PROJECT_VERSION@/sunshine.service +cp sunshine.desktop genrpm/sunshine-@PROJECT_VERSION@/sunshine.desktop +cp @CMAKE_CURRENT_SOURCE_DIR@/assets/sunshine.conf genrpm/sunshine-@PROJECT_VERSION@/sunshine.conf +cp @CMAKE_CURRENT_SOURCE_DIR@/assets/apps_linux.json genrpm/sunshine-@PROJECT_VERSION@/apps_linux.json +cp @CMAKE_CURRENT_SOURCE_DIR@/assets/85-sunshine-rules.rules genrpm/sunshine-@PROJECT_VERSION@/85-sunshine-rules.rules +cd genrpm + +# tarball everything as if it was a source file for rpmbuild +tar --create --file sunshine-@PROJECT_VERSION@_bin.tar.gz sunshine-@PROJECT_VERSION@/ +cp sunshine-@PROJECT_VERSION@_bin.tar.gz ~/rpmbuild/SOURCES + +# Use rpmbuild to build the RPM package. +rpmbuild -bb $RPMSPEC/sunshine.spec + +# Check if running in a CT +if [ "$DOCKERSTATUS" == "FALSE" ]; then + # Move the completed RPM into the cmake build folder + mv ~/rpmbuild/RPMS/x86_64/sunshine-@PROJECT_VERSION@-1.fc*.x86_64.rpm @CMAKE_CURRENT_BINARY_DIR@/ + echo "Moving completed RPM package into CMake build folder." +elif [ "$DOCKERSTATUS" == "TRUE" ]; then + # Move into pickup location + mkdir -p /root/sunshine-build/package-rpm/ + mv ~/rpmbuild/RPMS/x86_64/sunshine-@PROJECT_VERSION@-1.fc*.x86_64.rpm /root/sunshine-build/package-rpm/sunshine.rpm + echo "Moving completed RPM package for pickup." +fi + +# Clean up; delete the rpmbuild folder we created and move back the original one +if [ "$RPMBUILDEXISTS" == "TRUE" ]; then + echo "Removing and replacing original rpmbuild folder." + rm -rf ~/rpmbuild + mv ~/rpmbuild.bkp ~/rpmbuild +fi +exit 0 diff --git a/scripts/Dockerfile-debian b/scripts/Dockerfile-debian index 809b20d3..3a53fd9f 100644 --- a/scripts/Dockerfile-debian +++ b/scripts/Dockerfile-debian @@ -35,4 +35,4 @@ RUN apt-get update -y && \ # Entrypoint COPY build-private.sh /root/build.sh -ENTRYPOINT ["/root/build.sh"] +ENTRYPOINT ["/root/build.sh", "-deb"] diff --git a/scripts/Dockerfile-fedora_33 b/scripts/Dockerfile-fedora_33 index a2c8dfa4..50a4e3db 100644 --- a/scripts/Dockerfile-fedora_33 +++ b/scripts/Dockerfile-fedora_33 @@ -18,9 +18,10 @@ RUN dnf -y update && \ openssl-devel \ opus-devel \ pulseaudio-libs-devel \ + rpm-build \ && dnf clean all \ && rm -rf /var/cache/yum # Entrypoint COPY build-private.sh /root/build.sh -ENTRYPOINT ["/root/build.sh"] +ENTRYPOINT ["/root/build.sh", "-rpm"] diff --git a/scripts/Dockerfile-fedora_35 b/scripts/Dockerfile-fedora_35 index ba7abac5..d4f5d843 100644 --- a/scripts/Dockerfile-fedora_35 +++ b/scripts/Dockerfile-fedora_35 @@ -23,9 +23,10 @@ RUN dnf -y update && \ openssl-devel \ opus-devel \ pulseaudio-libs-devel \ + rpm-build \ && dnf clean all \ && rm -rf /var/cache/yum # Entrypoint COPY build-private.sh /root/build.sh -ENTRYPOINT ["/root/build.sh"] +ENTRYPOINT ["/root/build.sh", "-rpm"] diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index b6a05f6f..7225c0c7 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -58,4 +58,4 @@ RUN cmake --version # Entrypoint COPY build-private.sh /root/build.sh -ENTRYPOINT ["/root/build.sh"] +ENTRYPOINT ["/root/build.sh", "-deb"] diff --git a/scripts/Dockerfile-ubuntu_20_04 b/scripts/Dockerfile-ubuntu_20_04 index e65f9176..9ae7dfa9 100644 --- a/scripts/Dockerfile-ubuntu_20_04 +++ b/scripts/Dockerfile-ubuntu_20_04 @@ -41,4 +41,4 @@ RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-m # Entrypoint COPY build-private.sh /root/build.sh -ENTRYPOINT ["/root/build.sh"] +ENTRYPOINT ["/root/build.sh", "-deb"] diff --git a/scripts/Dockerfile-ubuntu_21_04 b/scripts/Dockerfile-ubuntu_21_04 index c7c7a3d4..00801c55 100644 --- a/scripts/Dockerfile-ubuntu_21_04 +++ b/scripts/Dockerfile-ubuntu_21_04 @@ -34,4 +34,4 @@ RUN apt-get update -y && \ # Entrypoint COPY build-private.sh /root/build.sh -ENTRYPOINT ["/root/build.sh"] +ENTRYPOINT ["/root/build.sh", "-deb"] diff --git a/scripts/Dockerfile-ubuntu_21_10 b/scripts/Dockerfile-ubuntu_21_10 index 7b51da3d..83b3e7f8 100644 --- a/scripts/Dockerfile-ubuntu_21_10 +++ b/scripts/Dockerfile-ubuntu_21_10 @@ -34,4 +34,4 @@ RUN apt-get update -y && \ # Entrypoint COPY build-private.sh /root/build.sh -ENTRYPOINT ["/root/build.sh"] +ENTRYPOINT ["/root/build.sh", "-deb"] diff --git a/scripts/build-private.sh b/scripts/build-private.sh index 69446de6..93d18748 100755 --- a/scripts/build-private.sh +++ b/scripts/build-private.sh @@ -32,4 +32,17 @@ cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHI make -j ${nproc} -./gen-deb +# Get preferred package format +if [ "$1" == "-rpm" ] +then + echo "Packaging in .rpm format." + ./gen-rpm -d +elif [ "$1" == "-deb" ] +then + echo "Packaging in .deb format." + ./gen-deb +else + echo "Preferred packaging not specified." + echo "Use -deb or -rpm to specify preferred package format." + exit 1 +fi diff --git a/scripts/build-sunshine.sh b/scripts/build-sunshine.sh index df21df3c..5be82808 100755 --- a/scripts/build-sunshine.sh +++ b/scripts/build-sunshine.sh @@ -4,7 +4,8 @@ set -e usage() { echo "Usage: $0" echo " -d: Generate a debug build" - echo " -p: Generate a debian package" + echo " -p: Generate a linux package" + echo " -e: Extension of package... i.e. 'deb', 'rpm' --> default [deb]" echo " -u: The input device is not a TTY" echo " -n name: Docker container name --> default [sunshine]" echo " -s path/to/sources/sunshine: Use local sources instead of a git repository" @@ -26,13 +27,14 @@ absolute_path() { CMAKE_BUILD_TYPE="-e CMAKE_BUILD_TYPE=Release" SUNSHINE_PACKAGE_BUILD=OFF +SUNSHINE_PACKAGE_EXTENSION=deb SUNSHINE_GIT_URL=https://github.com/sunshinestream/sunshine.git CONTAINER_NAME=sunshine # Docker will fail if ctrl+c is passed through and the input is not a tty DOCKER_INTERACTIVE=-ti -while getopts ":dpuhc:s:n:" arg; do +while getopts ":dpuhc:e:s:n:" arg; do case ${arg} in u) echo "Input device is not a TTY" @@ -49,6 +51,21 @@ while getopts ":dpuhc:s:n:" arg; do SUNSHINE_ASSETS_DIR="-e SUNSHINE_ASSETS_DIR=/etc/sunshine" SUNSHINE_EXECUTABLE_PATH="-e SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine" ;; + e) + echo "Defining package extension: $OPTARG" + if [ "$OPTARG" == "deb" ] + then + SUNSHINE_PACKAGE_EXTENSION=$OPTARG + echo "Package extension: deb" + elif [ "$OPTARG" == "rpm" ] + then + SUNSHINE_PACKAGE_EXTENSION=$OPTARG + echo "Package extension: rpm" + else + echo "Package extension not supported: $OPTARG" + echo "Falling back to default package extension: $SUNSHINE_PACKAGE_EXTENSION" + fi + ;; s) absolute_path "$OPTARG" OPTARG="$RETURN" @@ -98,8 +115,8 @@ then mkdir -p $BUILD_DIR case $SUNSHINE_PACKAGE_BUILD in ON) - echo "Downloading package to: $BUILD_DIR/$CONTAINER_NAME.deb" - docker cp $CONTAINER_NAME:/root/sunshine-build/package-deb/sunshine.deb "$BUILD_DIR/$CONTAINER_NAME.deb" + echo "Downloading package to: $BUILD_DIR/$CONTAINER_NAME.$SUNSHINE_PACKAGE_EXTENSION" + docker cp $CONTAINER_NAME:/root/sunshine-build/package-$SUNSHINE_PACKAGE_EXTENSION/sunshine.$SUNSHINE_PACKAGE_EXTENSION "$BUILD_DIR/$CONTAINER_NAME.$SUNSHINE_PACKAGE_EXTENSION" ;; *) echo "Downloading binary and assets to: $BUILD_DIR" From 536df759ae2033f7c383677249729199961be0db Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 18 Apr 2022 14:53:28 -0400 Subject: [PATCH 212/817] Initial version of sphinx documentation and... - remove ubuntu 21.04 from CI (end of life) - adjust matrix strategy for clang.yml - Use lessons learned from RetroArcher on localize.yml, crowdin.yml, and locale.py - Add end of life comments to Dockerfiles - Adjust dependency order in Dockerfiles --- .github/workflows/CI.yml | 2 +- .github/workflows/clang.yml | 8 +- .github/workflows/localize.yml | 45 +- .readthedocs.yaml | 26 + DOCKER_README.md | 113 ++ README.rst | 65 ++ assets/sunshine.conf | 319 +----- crowdin.yml | 4 + docs/Makefile | 20 + docs/make.bat | 35 + docs/source/about/advanced_usage.rst | 1087 ++++++++++++++++++++ docs/source/about/docker.rst | 5 + docs/source/about/installation.rst | 84 ++ docs/source/about/overview.rst | 1 + docs/source/about/third_party_packages.rst | 65 ++ docs/source/about/usage.rst | 163 +++ docs/source/building/build.rst | 31 + docs/source/building/linux.rst | 241 +++++ docs/source/building/macos.rst | 41 + docs/source/building/windows.rst | 22 + docs/source/conf.py | 88 ++ docs/source/contributing/contributing.rst | 32 + docs/source/contributing/localization.rst | 80 ++ docs/source/contributing/testing.rst | 42 + docs/source/index.rst | 7 + docs/source/toc.rst | 27 + scripts/Dockerfile-debian | 4 +- scripts/Dockerfile-fedora_33 | 3 + scripts/Dockerfile-fedora_35 | 4 +- scripts/Dockerfile-ubuntu_18_04 | 2 + scripts/Dockerfile-ubuntu_20_04 | 2 + scripts/Dockerfile-ubuntu_21_04 | 3 + scripts/Dockerfile-ubuntu_21_10 | 4 +- scripts/_locale.py | 14 +- scripts/requirements.txt | 3 + 35 files changed, 2350 insertions(+), 342 deletions(-) create mode 100644 .readthedocs.yaml create mode 100644 DOCKER_README.md create mode 100644 README.rst create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/source/about/advanced_usage.rst create mode 100644 docs/source/about/docker.rst create mode 100644 docs/source/about/installation.rst create mode 100644 docs/source/about/overview.rst create mode 100644 docs/source/about/third_party_packages.rst create mode 100644 docs/source/about/usage.rst create mode 100644 docs/source/building/build.rst create mode 100644 docs/source/building/linux.rst create mode 100644 docs/source/building/macos.rst create mode 100644 docs/source/building/windows.rst create mode 100644 docs/source/conf.py create mode 100644 docs/source/contributing/contributing.rst create mode 100644 docs/source/contributing/localization.rst create mode 100644 docs/source/contributing/testing.rst create mode 100644 docs/source/index.rst create mode 100644 docs/source/toc.rst diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 60c91f8d..da3cc6ed 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -150,7 +150,7 @@ jobs: strategy: fail-fast: true # false to test all, true to fail entire job if any fail matrix: - distro: [ debian, ubuntu_18_04, ubuntu_20_04, ubuntu_21_04, ubuntu_21_10 ] + distro: [ debian, ubuntu_18_04, ubuntu_20_04, ubuntu_21_10 ] package: [ -p ] extension: [ deb ] include: # package these differently diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index 2ab2f600..f096c5f9 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -9,10 +9,6 @@ jobs: lint: name: Clang Format Lint runs-on: ubuntu-latest - strategy: - fail-fast: false # false to test all, true to fail entire job if any fail - matrix: - inplace: [ true, false ] # removed ubuntu_18_04 for now steps: - name: Checkout @@ -25,10 +21,10 @@ jobs: extensions: 'cpp,h,m,mm' clangFormatVersion: 13 style: file - inplace: ${{ matrix.inplace }} + inplace: false - name: Upload Artifacts - if: ${{ matrix.inplace == true }} + if: failure() uses: actions/upload-artifact@v3 with: name: sunshine diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index 57db544a..b860ad64 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -3,15 +3,16 @@ name: localize on: push: branches: [nightly] - paths: # prevents workflow from running unless files in these directories change - - 'sunshine/**' # only localizing files inside sunshine directory + paths: # prevents workflow from running unless these files change + - 'sunshine/**' + - 'locale/sunshine.po' workflow_dispatch: jobs: localize: name: Update Localization - if: ${{ github.event.pull_request.merged }} runs-on: ubuntu-latest + steps: - name: Checkout uses: actions/checkout@v3 @@ -37,9 +38,37 @@ jobs: run: | python ./scripts/_locale.py --extract - - name: GitHub Commit & Push # push changes back into nightly - uses: actions-js/push@v1.3 + - name: git diff + run: | + # print the git diff + git diff --exit-code locale/sunshine.po + + # set the variable with minimal output + OUTPUT=$(git diff --exit-code --numstat locale/sunshine.po) + echo "git_diff=${OUTPUT}" >> $GITHUB_ENV + + - name: git reset + if: ${{ env.git_diff != '1 1 locale/sunshine.po' }} # only run if more than 1 line changed + run: | + git reset --hard + + - name: Create/Update Pull Request + uses: peter-evans/create-pull-request@v4 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: nightly - message: localization updated by localize workflow + add-paths: | + locale/*.po + token: ${{ secrets.GH_PAT }} # must trigger PR tests + commit-message: New localization template + branch: localize/update + delete-branch: true + base: nightly + title: New Babel Updates + body: | + Update report + - Updated with *today's* date + - Auto-generated by [create-pull-request][1] + + [1]: https://github.com/peter-evans/create-pull-request + labels: | + babel + l10n diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..78c304f8 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,26 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python +build: + os: ubuntu-20.04 + tools: + python: "3.9" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + builder: html + configuration: docs/source/conf.py + fail_on_warning: true + +# Using Sphinx, build docs in additional formats +formats: all + +python: + install: + - requirements: ./scripts/requirements.txt + system_packages: true diff --git a/DOCKER_README.md b/DOCKER_README.md new file mode 100644 index 00000000..8691e01b --- /dev/null +++ b/DOCKER_README.md @@ -0,0 +1,113 @@ +# Docker + +## Using docker run +Create and run the container (substitute your ``): + +```bash +docker run -d \ + --name=sunshine \ + --restart=unless-stopped + -v :/config \ + -e PUID= \ + -e PGID= \ + -e TZ= \ + -p 47990:47990 \ + -p 47984:47984 \ + -p 47989:47989 \ + -p 48010:48010 \ + -p 47998:47998 \ + -p 47999:47999 \ + -p 48000:48000 \ + -p 48002:48002 \ + -p 48010:48010 \ + sunshinestream/sunshine +``` + +To update the container it must be removed and recreated: + +```bash +# Stop the container +docker stop sunshine +# Remove the container +docker rm sunshine +# Pull the latest update +docker pull sunshinestream/sunshine +# Run the container with the same parameters as before +docker run -d ... +``` + +## Using docker-compose + +Create a `docker-compose.yml` file with the following contents (substitute your ``): + +```yaml +version: '3' +services: + retroarcher: + image: sunshinestream/sunshine + container_name: sunshine + restart: unless-stopped + volumes: + - :/config + environment: + - PUID= + - PGID= + - TZ= + ports: + - "47990:47990" + - "47984:47984" + - "47989:47989" + - "48010:48010" + - "47998:47998" + - "47999:47999" + - "48000:48000" + - "48002:48002" + - "48010:48010" +``` + +Create and start the container (run the command from the same folder as your `docker-compose.yml` file): + +```bash +docker-compose up -d +``` + +To update the container: +```bash +# Pull the latest update +docker-compose pull +# Update and restart the container +docker-compose up -d +``` + +## Parameters +You must substitute the `` with your own settings. + +Parameters are split into two halves separated by a colon. The left side represents the host and the right side the +container. + +**Example:** `-p external:internal` - This shows the port mapping from internal to external of the container. +Therefore `-p 47990:47990` would expose port `47990` from inside the container to be accessible from the host's IP on +port `47990` (e.g. `http://:47990`). The internal port must be `47990`, but the external port may be changed +(e.g. `-p 8080:47990`). + + +| Parameter | Function | Example Value | Required | +| --------------------------- | -------------------- | ------------------- | -------- | +| `-p :47990` | Web UI Port | `47990` | True | +| `-v :/config` | Volume mapping | `/home/sunshine` | True | +| `-e PUID=` | User ID | `1001` | False | +| `-e PGID=` | Group ID | `1001` | False | +| `-e TZ=` | Lookup TZ value [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) | `America/New_York` | True | + +### User / Group Identifiers: + +When using data volumes (-v flags) permissions issues can arise between the host OS and the container. To avoid this +issue you can specify the user PUID and group PGID. Ensure the data volume directory on the host is owned by the same +user you specify. + +In this instance `PUID=1001` and `PGID=1001`. To find yours use id user as below: + +```bash +$ id dockeruser +uid=1001(dockeruser) gid=1001(dockergroup) groups=1001(dockergroup) +``` diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..8fb29b6b --- /dev/null +++ b/README.rst @@ -0,0 +1,65 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/README.rst + +Overview +======== + +About +----- +Sunshine is a Game stream host for Moonlight. It is an open source version of GeForce Experience (GFE). + +These are the advantages of Sunshine over GFE. + + - FOSS (Free and Open Source Software) + - Multi-platform + + - Linux (deb, rpm, and AppImage packages) + - MacOS (Portfile) + - Windows (portable binary) + + - Pair over web ui + - Supports AMD and Nvidia GPUs for encoding + - Supports software encoding + - Supports streaming to multiple clients + - Web UI for configuration + +Integrations +------------ + +.. image:: https://img.shields.io/github/workflow/status/sunshinestream/sunshine/CI/master?label=CI%20build&logo=github&style=for-the-badge + :alt: GitHub Workflow Status (CI) + :target: https://github.com/SunshineStream/Sunshine/actions/workflows/CI.yml?query=branch%3Amaster + +.. image:: https://img.shields.io/github/workflow/status/sunshinestream/sunshine/localize/nightly?label=localize%20build&logo=github&style=for-the-badge + :alt: GitHub Workflow Status (localize) + :target: https://github.com/SunshineStream/Sunshine/actions/workflows/localize.yml?query=branch%3Anightly + +.. image:: https://img.shields.io/readthedocs/sunshinestream?label=Docs&style=for-the-badge&logo=readthedocs + :alt: Read the Docs + :target: http://sunshinestream.readthedocs.io/ + +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=localized&style=for-the-badge&query=%24.progress..data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json&logo=crowdin + :alt: CrowdIn + :target: https://crowdin.com/project/sunshinestream + +Support +--------- + +.. image:: https://img.shields.io/discord/938534566107418705?label=Discord&style=for-the-badge&color=blue&logo=discord + :alt: Discord + :target: https://sunshinestream.github.io/discord + +.. image:: https://img.shields.io/github/discussions/sunshinestream/sunshine?logo=github&style=for-the-badge + :alt: GitHub Discussions + :target: https://github.com/SunshineStream/Sunshine/discussions + +Downloads +--------- + +.. image:: https://img.shields.io/github/downloads/sunshinestream/sunshine/total?style=for-the-badge&logo=github + :alt: GitHub Releases + :target: https://github.com/SunshineStream/Sunshine/releases/latest + +.. comment + image:: https://img.shields.io/docker/pulls/sunshinestream/sunshine?style=for-the-badge&logo=docker + :alt: Docker + :target: https://hub.docker.com/r/sunshinestream/sunshine diff --git a/assets/sunshine.conf b/assets/sunshine.conf index a1e020f0..509ff52a 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -1,318 +1 @@ -# If no external IP address is given, Sunshine will attempt to automatically detect external ip-address -# external_ip = 123.456.789.12 - -# Set the familly of ports used by Sunshine -# port = 47989 - -# The private key must be 2048 bits -# pkey = /dir/pkey.pem - -# The certificate must be signed with a 2048 bit key -# cert = /dir/cert.pem - -# The name displayed by Moonlight -# If not specified, the PC's hostname is used -# sunshine_name = Sunshine - -# The minimum log level printed to standard out -# -# none -> no logs are printed to standard out -# -# verbose = [0] -# debug = [1] -# info = [2] -# warning = [3] -# error = [4] -# fatal = [5] -# none = [6] -# -# min_log_level = info - -# The origin of the remote endpoint address that is not denied for HTTP method /pin -# Could be any of the following values: -# pc|lan|wan -# pc: Only localhost may access /pin -# lan: Only those in LAN may access /pin -# wan: Anyone may access /pin -# -# origin_pin_allowed = pc - -# The origin of the remote endpoint address that is not denied for HTTPS Web UI -# Could be any of the following values: -# pc|lan|wan -# pc: Only localhost may access the Web Manager -# lan: Only those in LAN may access the Web Manager -# wan: Anyone may access the Web Manager -# -# origin_web_ui_allowed = lan - -# If UPnP is enabled, Sunshine will attempt to open ports for streaming over the internet -# To enable it, uncomment the following line: -# upnp = on - -# The file where current state of Sunshine is stored -# file_state = sunshine_state.json - -# The file where user credentials for the UI are stored -# By default, credentials are stored in `file_state` -# credentials_file = sunshine_state.json - -# The display modes advertised by Sunshine -# -# Some versions of Moonlight, such as Moonlight-nx (Switch), -# rely on this list to ensure that the requested resolutions and fps -# are supported. -# -# fps = [10, 30, 60, 90, 120] -# resolutions = [ -# 352x240, -# 480x360, -# 858x480, -# 1280x720, -# 1920x1080, -# 2560x1080, -# 3440x1440, -# 1920x1200, -# 3860x2160, -# 3840x1600, -# ] - -# Sometimes it may be usefull to map keybindings. -# Wayland won't allow clients to capture the Win Key for example -# -# See https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes -# -# Note: -# keybindings needs to have a multiple of two elements -# keybindings = [ -# 0x10, 0xA0, -# 0x11, 0xA2, -# 0x12, 0xA4, -# 0x4A, 0x4B -# ] - -# It may be possible that you cannot send the Windows Key from Moonlight directly. -# In those cases it may be useful to make Sunshine think the Right Alt key is the Windows key -# key_rightalt_to_key_win = enabled - -# How long to wait in milliseconds for data from moonlight before shutting down the stream -# ping_timeout = 10000 - -# The file where configuration for the different applications that Sunshine can run during a stream -# file_apps = apps.json - -# Percentage of error correcting packets per data packet in each video frame -# Higher values can correct for more network packet loss, but at the cost of increasing bandwidth usage -# The default value of 20 is what GeForce Experience uses -# -# The value must be greater than 0 and lower than or equal to 255 -# fec_percentage = 20 - -# When multicasting, it could be usefull to have different configurations for each connected Client. -# For example: -# Clients connected through WAN and LAN have different bitrate contstraints. -# Decoders may require different settings for color -# -# Unlike simply broadcasting to multiple Client, this will generate distinct video streams. -# Note, CPU usage increases for each distinct video stream generated -# channels = 1 - -# The back/select button on the controller -# On the Shield, the home and powerbutton are not passed to Moonlight -# If, after the timeout, the back button is still pressed down, Home/Guide button press is emulated. -# If back_button_timeout < 0, then the Home/Guide button will not be emulated -# back_button_timeout = 2000 - -# !! Windows only !! -# Gamepads supported by Sunshine -# Possible values: -# x360 -- xbox 360 controller -# ds4 -- dualshock controller (PS4) -# gamepad = x360 - -# Control how fast keys will repeat themselves -# The initial delay in milliseconds before repeating keys -# key_repeat_delay = 500 -# -# How often keys repeat every second -# This configurable option supports decimals -# key_repeat_frequency = 24.9 - -# The name of the audio sink used for Audio Loopback -# If you do not specify this variable, pulseaudio will select the default monitor device. -# -# You can find the name of the audio sink using the following command: -# !! Linux only !! -# pacmd list-sinks | grep "name:" if running vanilla pulseaudio -# pPipewire: Use `pactl info | grep Source`. In some causes you'd need to use the `sink` device. Try `pactl info | grep Sink`, if _Source_ doesn't work -# audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo -# -# !! Windows only !! -# tools\audio-info.exe -# audio_sink = {0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B} -# -# The virtual sink, is the audio device that's virtual (Like Steam Streaming Speakers), it allows Sunshine -# to stream audio, while muting the speakers. -# virtual_sink = {0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4} - -# -# !! MacOS only !! -# audio_sink = BlackHole 2ch - -# !! Windows only !! -# You can select the video card you want to stream: -# The appropriate values can be found using the following command: -# tools\dxgi-info.exe -# adapter_name = Radeon RX 580 Series -# output_name = \\.\DISPLAY1 - -# !! Linux only !! -# Set the display number to stream. -# You can find them by the following command: -# xrandr --listmonitors -# Example output: "0: +HDMI-1 1920/518x1200/324+0+0 HDMI-1" -# ^ <-- You need this. -# output_name = 0 - -############################################### -# FFmpeg software encoding parameters -# Honestly, I have no idea what the optimal values would be. -# Play around with this :) - -# Quantitization Parameter -# Some devices don't support Constant Bit Rate. For those devices, QP is used instead -# Higher value means more compression, but less quality -# qp = 28 - -# Minimum number of threads used by ffmpeg to encode the video. -# Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually -# worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest -# value that can reliably encode at your desired streaming settings on your hardware. -# min_threads = 1 - -# Allows the client to request HEVC Main or HEVC Main10 video streams. -# HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding. -# If set to 0 (default), Sunshine will specify support for HEVC based on encoder -# If set to 1, Sunshine will not advertise support for HEVC -# If set to 2, Sunshine will advertise support for HEVC Main profile -# If set to 3, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles -# hevc_mode = 2 - -# Force a specific encoder, otherwise Sunshine will use the first encoder that is available -# supported encoders: -# nvenc -# amdvce # NOTE: alpha stage. The cursor is not yet displayed -# software -# -# encoder = nvenc -##################################### Software ##################################### -# See x264 --fullhelp for the different presets -# sw_preset = superfast -# sw_tune = zerolatency -# - -##################################### NVENC ##################################### -###### presets ########### -# default -# hp -- high performance -# hq -- high quality -# slow -- hq 2 passes -# medium -- hq 1 pass -# fast -- hp 1 pass -# bd -# ll -- low latency -# llhq -# llhp -# lossless -# losslesshp -########################## -# nv_preset = llhq -# -####### rate control ##### -# auto -- let ffmpeg decide rate control -# constqp -- constant QP mode -# vbr -- variable bitrate -# cbr -- constant bitrate -# cbr_hq -- cbr high quality -# cbr_ld_hq -- cbr low delay high quality -# vbr_hq -- vbr high quality -########################## -# nv_rc = auto - -###### h264 entropy ###### -# auto -- let ffmpeg nvenc decide the entropy encoding -# cabac -# cavlc -########################## -# nv_coder = auto - -##################################### AMD ##################################### -###### presets ########### -# default -# speed -# balanced -########################## -# amd_quality = balanced -# -####### rate control ##### -# auto -- let ffmpeg decide rate control -# constqp -- constant QP mode -# vbr_latency -- Latency Constrained Variable Bitrate -# vbr_peak -- Peak Contrained Variable Bitrate -# cbr -- constant bitrate -########################## -# amd_rc = auto - -###### h264 entropy ###### -# auto -- let ffmpeg nvenc decide the entropy encoding -# cabac -# cavlc -########################## -# amd_coder = auto - -#################################### VAAPI ################################### -####### adapter ########## -# Unlike with `amdvce` and `nvenc`, it doesn't matter if video encoding is done -# on a different GPU. -# Run the following commands: -# 1. ls /dev/dri/renderD* -# to find all devices capable of VAAPI -# 2. vainfo --display drm --device /dev/dri/renderD129 | grep -E "((VAProfileH264High|VAProfileHEVCMain|VAProfileHEVCMain10).*VAEntrypointEncSlice)|Driver version" -# Lists the name and capabilities of the device. -# To be supported by Sunshine, it needs to have at the very minimum: -# VAProfileH264High : VAEntrypointEncSlice -# adapter_name = /dev/dri/renderD128 - -################################# VideoToolbox ############################### -####### software encoding ########## -# Video Toolbox can be allowed/required to use software encoding instead of -# hardware accelerated encoding. -# auto -- let sunshine decide on encoding -# disabled -- disable software encoding -# allowed -- allow software encoding -# forced -- force software encoding -########################## -# vt_software = auto -# -####### realtime encoding ########## -# Disabling realtime encoding might result in a delayed frame encoding or frame drop -########################## -# vt_realtime = enabled -# -###### h264/hevc entropy ###### -# auto -- let ffmpeg decide the entropy encoding -# cabac -# cavlc -########################## -# vt_coder = auto - - -############################################## -# Some configurable parameters, are merely toggles for specific features -# The first occurrence turns it on, the second occurence turns it off, the third occurence turns it on again, etc, etc -# Here, you change the default state of any flag -# -# To set the initial state of flags -0 and -1 to on, set the following flags: -# flags = 012 -# -# See: sunshine --help for all options under the header: flags +# See our documentation at https://sunshinestream.readthedocs.io/en/latest/about/advanced_usage.html diff --git a/crowdin.yml b/crowdin.yml index d014a00c..ca9c8c5c 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,6 +1,10 @@ "base_path": "." "base_url": "https://api.crowdin.com" # optional (for Crowdin Enterprise only) "preserve_hierarchy": false # flatten tree on crowdin +"pull_request_labels": [ + "crowdin", + "l10n" +] "files" : [ { diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..d0c3cbf1 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..dc1312ab --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst new file mode 100644 index 00000000..e01de0aa --- /dev/null +++ b/docs/source/about/advanced_usage.rst @@ -0,0 +1,1087 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/about/advanced_usage.rst + +Advanced Usage +============== +Sunshine will work with the default settings for most users. In some cases you may want to configure Sunshine further. + +Configuration +------------- +The default location for the configuration file is ``./assets/sunshine.conf``. You can use another location if you +choose, by passing in the full configuration file path as the first argument when you start Sunshine. + +** Default File Location** + +.. table:: + :widths: auto + + ======= =========== + Value Description + ======= =========== + Linux ./assets/sunshine.conf + MacOS /opt/local/etc/sunshine.conf + Windows ./assets/sunshine.conf + ======= =========== + +Example + .. code-block:: bash + + sunshine ~/sunshine_config.conf + +To manually configure sunshine you may edit the `conf` file in a text editor. Use the examples as reference. + +.. Note:: Some settings are not available within the web ui. + +General +------- + +sunshine_name +^^^^^^^^^^^^^ + +Description + The name displayed by Moonlight + +Default + PC hostname + +Example + .. code-block:: text + + sunshine_name = Sunshine + +min_log_level +^^^^^^^^^^^^^ + +Description + The minimum log level printed to standard out. + +**Choices** + +.. table:: + :widths: auto + + ======= =========== + Value Description + ======= =========== + verbose verbose logging + debug debug logging + info info logging + warning warning logging + error error logging + fatal fatal logging + none no logging + ======= =========== + +Default + ``info`` + +Example + .. code-block:: text + + min_log_level = info + +Controls +-------- + +gamepad +^^^^^^^ + +Description + The type of gamepad to emulate on the host. + + .. Caution:: Applies to Windows only. + +**Choices** + +.. table:: + :widths: auto + + ===== =========== + Value Description + ===== =========== + x360 xbox 360 controller + ds4 dualshock controller (PS4) + ===== =========== + +Default + ``x360`` + +Example + .. code-block:: text + + gamepad = x360 + +back_button_timeout +^^^^^^^^^^^^^^^^^^^ + +Description + If, after the timeout, the back/select button is still pressed down, Home/Guide button press is emulated. + + On Nvidia Shield, the home and power button are not passed to Moonlight. + + .. Tip:: If back_button_timeout < 0, then the Home/Guide button will not be emulated. + +Default + ``2000`` + +Example + .. code-block:: text + + back_button_timeout = 2000 + +key_repeat_delay +^^^^^^^^^^^^^^^^ + +Description + The initial delay in milliseconds before repeating keys. Controls how fast keys will repeat themselves. + +Default + ``500`` + +Example + .. code-block:: text + + key_repeat_delay = 500 + +key_repeat_frequency +^^^^^^^^^^^^^^^^^^^^ + +Description + How often keys repeat every second. + + .. Tip:: This configurable option supports decimals. + +Default + .. Todo:: Unknown + +Example + .. code-block:: text + + key_repeat_frequency = 24.9 + +keybindings +^^^^^^^^^^^ + +Description + Sometimes it may be useful to map keybindings. Wayland won't allow clients to capture the Win Key for example. + + .. Tip:: See `virtual key codes `_ + + .. Note:: keybindings needs to have a multiple of two elements. + +Default + None + +Example + .. code-block:: text + + keybindings = [ + 0x10, 0xA0, + 0x11, 0xA2, + 0x12, 0xA4, + 0x4A, 0x4B + ] + +key_rightalt_to_key_win +^^^^^^^^^^^^^^^^^^^^^^^ + +Description + It may be possible that you cannot send the Windows Key from Moonlight directly. In those cases it may be useful to + make Sunshine think the Right Alt key is the Windows key. + +Default + None + +Example + .. code-block:: text + + key_rightalt_to_key_win = enabled + +Display +------- + +adapter_name +^^^^^^^^^^^^ + +Description + Select the video card you want to stream. + + .. Tip:: To find the name of the appropriate values follow these instructions. + + Linux + VA-API + Unlike with `amdvce` and `nvenc`, it doesn't matter if video encoding is done on a different GPU. + + .. code-block:: bash + + ls /dev/dri/renderD* # to find all devices capable of VAAPI + + # replace ``renderD129`` with the device from above to lists the name and capabilities of the device + vainfo --display drm --device /dev/dri/renderD129 | \ + grep -E "((VAProfileH264High|VAProfileHEVCMain|VAProfileHEVCMain10).*VAEntrypointEncSlice)|Driver version" + + To be supported by Sunshine, it needs to have at the very minimum: + ``VAProfileH264High : VAEntrypointEncSlice`` + + .. Todo:: MacOS + + Windows + .. code-block:: batch + + tools\dxgi-info.exe + +Default + Sunshine will select the default video card. + +Examples + Linux + .. code-block:: text + + adapter_name = /dev/dri/renderD128 + + .. Todo:: MacOS + + Windows + .. code-block:: text + + adapter_name = Radeon RX 580 Series + +output_name +^^^^^^^^^^^ + +Description + Select the display number you want to stream. + + .. Tip:: To find the name of the appropriate values follow these instructions. + + Linux + .. code-block:: bash + + xrandr --listmonitors + + Example output: ``0: +HDMI-1 1920/518x1200/324+0+0 HDMI-1`` + + You need to use the value before the colon in the output, e.g. ``0``. + + .. Todo:: MacOS + + Windows + .. code-block:: batch + + tools\dxgi-info.exe + +Default + Sunshine will select the default display. + +Examples + Linux + .. code-block:: text + + output_name = 0 + + .. Todo:: MacOS + + Windows + .. code-block:: text + + output_name = \\.\DISPLAY1 + +fps +^^^ + +Description + The fps modes advertised by Sunshine. + + .. Note:: Some versions of Moonlight, such as Moonlight-nx (Switch), rely on this list to ensure that the requested + fps is supported. + +Default + .. Todo:: Unknown + +Example + .. code-block:: text + + fps = [10, 30, 60, 90, 120] + +resolutions +^^^^^^^^^^^ + +Description + The resolutions advertised by Sunshine. + + .. Note:: Some versions of Moonlight, such as Moonlight-nx (Switch), rely on this list to ensure that the requested + resolution is supported. + +Default + .. Todo:: Unknown + +Example + .. code-block:: text + + resolutions = [ + 352x240, + 480x360, + 858x480, + 1280x720, + 1920x1080, + 2560x1080, + 3440x1440, + 1920x1200, + 3860x2160, + 3840x1600, + ] + +Audio +----- + +audio_sink +^^^^^^^^^^ + +Description + The name of the audio sink used for audio loopback. + + .. Tip:: To find the name of the audio sink follow these instructions. + + Linux + pulseaudio + .. code-block:: bash + + pacmd list-sinks | grep "name:" + + Linux + pipewire + .. code-block:: bash + + pactl info | grep Source + # in some causes you'd need to use the `Sink` device, if `Source` doesn't work, so try: + pactl info | grep Sink + + MacOS + Sunshine can only access microphones on MacOS due to system limitations. To stream system audio use + `Soundflower `_ or + `BlackHole `_. + + Windows + .. code-block:: batch + + tools\audio-info.exe + +Default + Sunshine will select the default audio device. + +Examples + Linux + .. code-block:: text + + audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo + + MacOS + .. code-block:: text + + audio_sink = BlackHole 2ch + + Windows + .. code-block:: text + + audio_sink = {0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B} + +virtual_sink +^^^^^^^^^^^^ + +Description + The audio device that's virtual, like Steam Streaming Speakers. This allows Sunshine to stream audio, while muting + the speakers. + + .. Tip:: See `audio_sink`_! + +Default + .. Todo:: Unknown + +Example + .. code-block:: text + + virtual_sink = {0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4} + +Network +------- + +external_ip +^^^^^^^^^^^ + +Description + If no external IP address is given, Sunshine will attempt to automatically detect external ip-address. + +Default + Automatic + +Example + .. code-block:: text + + external_ip = 123.456.789.12 + +port +^^^^ + +Description + Set the family of ports used by Sunshine. + +Default + ``47989`` + +Example + .. code-block:: text + + port = 47989 + +pkey +^^^^ + +Description + The private key. This must be 2048 bits. + +Default + .. Todo:: Unknown + +Example + .. code-block:: text + + pkey = /dir/pkey.pem + +cert +^^^^ + +Description + The certificate. Must be signed with a 2048 bit key. + +Default + .. Todo:: Unknown + +Example + .. code-block:: text + + cert = /dir/cert.pem + +origin_pin_allowed +^^^^^^^^^^^^^^^^^^ + +Description + The origin of the remote endpoint address that is not denied for HTTP method /pin. + +**Choices** + +.. table:: + :widths: auto + + ===== =========== + Value Description + ===== =========== + pc Only localhost may access /pin + lan Only LAN devices may access /pin + wan Anyone may access /pin + ===== =========== + +Default + ``pc`` + +Example + .. code-block:: text + + origin_pin_allowed = pc + +origin_web_ui_allowed +^^^^^^^^^^^^^^^^^^^^^ + +Description + The origin of the remote endpoint address that is not denied for HTTPS Web UI. + +**Choices** + +.. table:: + :widths: auto + + ===== =========== + Value Description + ===== =========== + pc Only localhost may access the web ui + lan Only LAN devices may access the web ui + wan Anyone may access the web ui + ===== =========== + +Default + ``lan`` + +Example + .. code-block:: text + + origin_web_ui_allowed = lan + +upnp +^^^^ + +Description + Sunshine will attempt to open ports for streaming over the internet. + +**Choices** + +.. table:: + :widths: auto + + ===== =========== + Value Description + ===== =========== + on enable UPnP + off disable UPnP + ===== =========== + +Default + ``off`` + +Example + .. code-block:: text + + upnp = on + +ping_timeout +^^^^^^^^^^^^ + +Description + How long to wait in milliseconds for data from Moonlight before shutting down the stream. + +Default + ``10000`` + +Example + .. code-block:: text + + ping_timeout = 10000 + +Encoding +-------- + +channels +^^^^^^^^ + +Description + This will generate distinct video streams, unlike simply broadcasting to multiple Clients. + + When multicasting, it could be useful to have different configurations for each connected Client. + + For instance: + + - Clients connected through WAN and LAN have different bitrate constraints. + - Decoders may require different settings for color. + + .. Warning:: CPU usage increases for each distinct video stream generated. + +Default + ``1`` + +Example + .. code-block:: text + + channels = 1 + +fec_percentage +^^^^^^^^^^^^^^ + +Description + Percentage of error correcting packets per data packet in each video frame. + + .. Warning:: Higher values can correct for more network packet loss, but at the cost of increasing bandwidth usage. + +Default + ``20`` + +Range + ``1-255`` + +Example + .. code-block:: text + + fec_percentage = 20 + +qp +^^ + +Description + Quantitization Parameter. Some devices don't support Constant Bit Rate. For those devices, QP is used instead. + + .. Warning:: Higher value means more compression, but less quality. + +Default + ``28`` + +Example + .. code-block:: text + + qp = 28 + +min_threads +^^^^^^^^^^^ + +Description + Minimum number of threads used by ffmpeg to encode the video. + + .. Note:: Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually worth it to gain + the use of more CPU cores for encoding. The ideal value is the lowest value that can reliably encode at your + desired streaming settings on your hardware. + +Default + ``1`` + +Example + .. code-block:: text + + min_threads = 1 + +hevc_mode +^^^^^^^^^ + +Description + Allows the client to request HEVC Main or HEVC Main10 video streams. + + .. Warning:: HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software + encoding. + +**Choices** + +.. table:: + :widths: auto + + ===== =========== + Value Description + ===== =========== + 0 advertise support for HEVC based on encoder + 1 do not advertise support for HEVC + 2 advertise support for HEVC Main profile + 3 advertise support for HEVC Main and Main10 (HDR) profiles + ===== =========== + +Default + ``0`` + +Example + .. code-block:: text + + hevc_mode = 2 + +encoder +^^^^^^^ + +Description + Force a specific encoder. + +**Choices** + +.. table:: + :widths: auto + + ======== =========== + Value Description + ======== =========== + nvenc For Nvidia graphics cards + amdvce For AMD graphics cards + software Encoding occurs on the CPU + ======== =========== + +Default + Sunshine will use the first encoder that is available. + +Example + .. code-block:: text + + encoder = nvenc + +sw_preset +^^^^^^^^^ + +Description + The encoder preset to use. + + .. Note:: This option only applies when using software `encoder`_. + + .. Note:: From `FFmpeg `_. + + A preset is a collection of options that will provide a certain encoding speed to compression ratio. A slower + preset will provide better compression (compression is quality per filesize). This means that, for example, if + you target a certain file size or constant bit rate, you will achieve better quality with a slower preset. + Similarly, for constant quality encoding, you will simply save bitrate by choosing a slower preset. + + Use the slowest preset that you have patience for. + +**Choices** + +.. table:: + :widths: auto + + ========= =========== + Value Description + ========= =========== + ultrafast fastest + superfast + veryfast + superfast + faster + fast + medium + slow + slow + slower + veryslow slowest + ========= =========== + +Default + ``superfast`` + +Example + .. code-block:: text + + sw_preset = superfast + +sw_tune +^^^^^^^ + +Description + The tuning preset to use. + + .. Note:: This option only applies when using software `encoder`_. + + .. Note:: From `FFmpeg `_. + + You can optionally use -tune to change settings based upon the specifics of your input. + +**Choices** + +.. table:: + :widths: auto + + =========== =========== + Value Description + =========== =========== + film use for high quality movie content; lowers deblocking + animation good for cartoons; uses higher deblocking and more reference frames + grain preserves the grain structure in old, grainy film material + stillimage good for slideshow-like content + fastdecode allows faster decoding by disabling certain filters + zerolatency good for fast encoding and low-latency streaming + =========== =========== + +Default + ``zerolatency`` + +Example + .. code-block:: text + + sw_tune = zerolatency + +nv_preset +^^^^^^^^^ + +Description + The encoder preset to use. + + .. Note:: This option only applies when using nvenc `encoder`_. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + default let ffmpeg decide + hp high performance + hq high quality + slow high quality, 2 passes + medium high quality, 1 pass + fast high performance, 1 pass + bd + ll low latency + llhq low latency, high quality + llhp low latency, high performance + lossless lossless + losslesshp lossless, high performance + ========== =========== + +Default + ``llhq`` + +Example + .. code-block:: text + + nv_preset = llhq + +nv_rc +^^^^^ + +Description + The encoder rate control. + + .. Note:: This option only applies when using nvenc `encoder`_. + + .. Note:: Moonlight does not currently support variable bitrate, although it can still be selected here. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + auto let ffmpeg decide + constqp constant QP mode + cbr constant bitrate + cbr_hq constant bitrate, high quality + cbr_ld_hq constant bitrate, low delay, high quality + vbr variable bitrate + vbr_hq variable bitrate, high quality + ========== =========== + +Default + ``auto`` + +Example + .. code-block:: text + + nv_rc = auto + +nv_coder +^^^^^^^^ + +Description + The entropy encoding to use. + + .. Note:: This option only applies when using nvenc `encoder`_. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + auto let ffmpeg decide + cabac + cavlc + ========== =========== + +Default + ``auto`` + +Example + .. code-block:: text + + nv_coder = auto + +amd_quality +^^^^^^^^^^^ + +Description + The encoder preset to use. + + .. Note:: This option only applies when using amdvce `encoder`_. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + default let ffmpeg decide + speed fast + balanced balance performance and speed + ========== =========== + +Default + ``balanced`` + +Example + .. code-block:: text + + amd_quality = balanced + +amd_rc +^^^^^^ + +Description + The encoder rate control. + + .. Note:: This option only applies when using amdvce `encoder`_. + + .. Note:: Moonlight does not currently support variable bitrate, although it can still be selected here. + +**Choices** + +.. table:: + :widths: auto + + =========== =========== + Value Description + =========== =========== + auto let ffmpeg decide + constqp constant QP mode + cbr constant bitrate + vbr_latency variable bitrate, latency constrained + vbr_peak variable bitrate, peak constrained + =========== =========== + +Default + ``auto`` + +Example + .. code-block:: text + + amd_rc = auto + +amd_coder +^^^^^^^^^ + +Description + The entropy encoding to use. + + .. Note:: This option only applies when using nvenc `encoder`_. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + auto let ffmpeg decide + cabac + cavlc + ========== =========== + +Default + ``auto`` + +Example + .. code-block:: text + + amd_coder = auto + +vt_software +^^^^^^^^^^^ + +Description + Force Video Toolbox to use software encoding. + + .. Note:: This option only applies when using MacOS. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + auto let ffmpeg decide + disabled disable software encoding + allowed allow software encoding + forced force software encoding + ========== =========== + +Default + ``auto`` + +Example + .. code-block:: text + + vt_software = auto + +vt_realtime +^^^^^^^^^^^ + +Description + Realtime encoding. + + .. Note:: This option only applies when using MacOS. + + .. Warning:: Disabling realtime encoding might result in a delayed frame encoding or frame drop. + +Default + ``enabled`` + +Example + .. code-block:: text + + vt_realtime = enabled + +vt_coder +^^^^^^^^ + +Description + The entropy encoding to use. + + .. Note:: This option only applies when using MacOS. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + auto let ffmpeg decide + cabac + cavlc + ========== =========== + +Default + ``auto`` + +Example + .. code-block:: text + + vt_coder = auto + +Advanced +-------- + +file_apps +^^^^^^^^^ + +Description + The application configuration file path. The file contains a json formatted list of applications that can be started + by Moonlight. + +Default + OS dependent + +Example + .. code-block:: text + + file_apps = apps.json + +file_state +^^^^^^^^^^ + +Description + The file where current state of Sunshine is stored. + +Default + ``sunshine_state.json`` + +Example + .. code-block:: text + + file_state = sunshine_state.json + +credentials_file +^^^^^^^^^^^^^^^^ + +Description + The file where user credentials for the UI are stored. + +Default + ``sunshine_state.json`` + +Example + .. code-block:: text + + credentials_file = sunshine_state.json diff --git a/docs/source/about/docker.rst b/docs/source/about/docker.rst new file mode 100644 index 00000000..f9afa251 --- /dev/null +++ b/docs/source/about/docker.rst @@ -0,0 +1,5 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/DOCKER_README.md + +.. Todo:: This is a planned feature. Currently no Dockerfile or image exists for Sunshine. + +.. mdinclude:: ../../../DOCKER_README.md diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst new file mode 100644 index 00000000..14ee37d2 --- /dev/null +++ b/docs/source/about/installation.rst @@ -0,0 +1,84 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/about/installation.rst + +Installation +============ +The recommended method for running Sunshine is to use the `binaries`_ bundled with the `latest release`_. + +Binaries +-------- +Binaries of Sunshine are created for each release. They are available for Linux, and Windows. +Binaries can be found in the `latest release`_. + +.. Todo:: Create binary package(s) for MacOS. See `here `_. + +.. Tip:: Some third party packages also exist. See + :ref:`Third Party Packages `. + +Docker +------ +.. Todo:: Docker images of Sunshine are planned to be included in the future. + They will be available on `Dockerhub.io`_ and `ghcr.io`_. + +Linux +----- + +AppImage +^^^^^^^^ +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:appimage?logo=github&style=for-the-badge' + :alt: GitHub issues by-label + +#. Download and extract `sunshine-appimage.zip` + +Debian Packages +^^^^^^^^^^^^^^^ +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:debian?logo=github&style=for-the-badge' + :alt: GitHub issues by-label + +#. Download the corresponding `.deb` file, e.g. ``sunshine-ubuntu_20_04.deb`` +#. ``sudo apt install -f ``, e.g. ``sudo apt install -f ./sunshine-ubuntu_20_04.deb`` + +Red Hat Packages +^^^^^^^^^^^^^^^^ +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:fedora?logo=github&style=for-the-badge' + :alt: GitHub issues by-label + +#. Download the corresponding `.rpm` file, e.g. ``sunshine-fedora_35.rpm`` +#. ``sudo rpm -i ``, e.g. ``sudo rpm -i ./sunshine-fedora_35.rpm`` + +.. Note:: If this is the first time installing. + + .. code-block:: bash + + sudo usermod -a -G input $USER + sudo reboot now + +.. Tip:: Optionally, run Sunshine in the background. + + .. code-block:: bash + + systemctl --user start sunshine + +MacOS +----- +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:macos?logo=github&style=for-the-badge' + :alt: GitHub issues by-label + +#. Install `MacPorts `_ +#. Download the `Portfile `_ from this repository to + ``/tmp`` +#. In a terminal run ``cd /tmp && sudo port install`` +#. The first time you start Sunshine, you will be asked to grant access to screen recording and your microphone. + +Windows +------- +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:10?logo=github&style=for-the-badge' + :alt: GitHub issues by-label + +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:11?logo=github&style=for-the-badge' + :alt: GitHub issues by-label + +#. Download and extract ``sunshine-windows.zip`` + +.. _latest release: https://github.com/SunshineStream/Sunshine/releases/latest +.. _Dockerhub.io: https://hub.docker.com/repository/docker/sunshinestream/sunshine +.. _ghcr.io: https://github.com/orgs/SunshineStream/packages?repo_name=sunshine diff --git a/docs/source/about/overview.rst b/docs/source/about/overview.rst new file mode 100644 index 00000000..e4a3ad51 --- /dev/null +++ b/docs/source/about/overview.rst @@ -0,0 +1 @@ +.. include:: ../../../README.rst diff --git a/docs/source/about/third_party_packages.rst b/docs/source/about/third_party_packages.rst new file mode 100644 index 00000000..4f349abe --- /dev/null +++ b/docs/source/about/third_party_packages.rst @@ -0,0 +1,65 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/about/third_party_packages.rst + +Third Party Packages +==================== + +.. Warning:: These packages are not maintained by SunshineStream. Use at your own risk. + +AUR (Arch Linux User Repository) +-------------------------------- + +.. image:: https://img.shields.io/aur/version/sunshine?style=for-the-badge&logo=archlinux + :alt: AUR version + +.. image:: https://img.shields.io/aur/last-modified/sunshine?style=for-the-badge&logo=archlinux + :alt: AUR last modified + +.. image:: https://img.shields.io/aur/votes/sunshine?style=for-the-badge&logo=archlinux + :alt: AUR votes + +.. image:: https://img.shields.io/aur/maintainer/sunshine?style=for-the-badge&logo=archlinux + :alt: AUR maintainer + +.. image:: https://img.shields.io/static/v1?label=maintainer&message=hadogenes&color=blue&style=for-the-badge&logo=github + :alt: GitHub Maintainer + :target: https://github.com/hadogenes + +Chocolatey +---------- + +.. image:: https://img.shields.io/chocolatey/v/Sunshine?style=for-the-badge&logo=chocolatey + :alt: Chocolatey Version + +.. image:: https://img.shields.io/chocolatey/dt/sunshine?style=for-the-badge&logo=chocolatey + :alt: Chocolatey + +.. image:: https://img.shields.io/static/v1?label=maintainer&message=AeliusSaionji&color=blue&style=for-the-badge&logo=github + :alt: GitHub Maintainer + :target: https://github.com/AeliusSaionji + +Scoop +----- + +.. image:: https://img.shields.io/scoop/v/sunshine?bucket=extras&style=for-the-badge + :alt: Scoop Version (extras bucket) + +.. image:: https://img.shields.io/static/v1?label=maintainer&message=sitiom&color=blue&style=for-the-badge&logo=github + :alt: GitHub Maintainer + :target: https://github.com/sitiom + + +Legacy GitHub Repo +------------------ + +.. image:: https://img.shields.io/github/last-commit/loki-47-6F-64/sunshine?style=for-the-badge&logo=github + :alt: GitHub last commit + +.. image:: https://img.shields.io/github/release-date/loki-47-6F-64/sunshine?style=for-the-badge&logo=github + :alt: GitHub Release Date + +.. image:: https://img.shields.io/github/downloads/loki-47-6F-64/sunshine/total?style=for-the-badge&logo=github + :alt: GitHub Releases + +.. image:: https://img.shields.io/static/v1?label=maintainer&message=loki-47-6F-64&color=blue&style=for-the-badge&logo=github + :alt: GitHub Maintainer + :target: https://github.com/loki-47-6F-64 diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst new file mode 100644 index 00000000..6eafe4b9 --- /dev/null +++ b/docs/source/about/usage.rst @@ -0,0 +1,163 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/about/usage.rst + +Usage +===== +#. See the `setup`_ section for your specific OS. +#. Run ``sunshine /sunshine.conf``. + + .. Note:: The configuration file specified will be created if it doesn't exist. + + .. Tip:: If using the Linux AppImage, replace ``sunshine`` with ``./sunshine.AppImage`` + +#. Configure Sunshine in the web ui + The web ui is available on `https://localhost:47990 `_ by default. You may replace + `localhost` with your internal ip address. + + .. Tip:: Ignore any warning given by your browser about "insecure website". + + .. Caution:: If running for the first time, make sure to note the username and password Sunshine showed to you, + since you cannot get back later! + + Add games and applications. + This can be configured in the web ui. + + .. Note:: Additionally, apps can be configured manually. `assets/apps_.json` is an example of a list of + applications that are started just before running a stream. + + .. Note:: Application list is not fully supported on MacOS + +#. In Moonlight, you may need to add the PC manually. +#. When Moonlight request you insert the correct pin on sunshine: + + - Login to the web ui + - Go to "PIN" in the Header + - Type in your PIN and press Enter, you should get a Success Message + - In Moonlight, select one of the Applications listed + +Network +------- +Sunshine will be available on port 47990 by default. + +.. Warning:: Do not expose port 47990, or the web ui, to the internet! + +Arguments +--------- +To get a list of available arguments run the following: + + .. code-block:: bash + + sunshine --help + +Setup +----- + +Linux +^^^^^ +Sunshine needs access to `uinput` to create mouse and gamepad events. + +Add user to group `input`. + .. code-block:: bash + + usermod -a -G input $USER + +Create `udev` rules. + .. code-block:: bash + + nano /etc/udev/rules.d/85-sunshine-input.rules + + Input the following contents: + ``KERNEL=="uinput", GROUP="input", MODE="0660"`` + + Save the file and exit: + + #. ``CTRL+X`` to start exit. + #. ``Y`` to save modifications. + +Configure autostart service + `path/to/build/dir/sunshine.service` is used to start sunshine in the background. To use it, do the following: + + #. Copy it to the users systemd: ``cp sunshine.service ~/.config/systemd/user/`` + #. Starting + + - One time: ``systemctl --user start sunshine`` + - Always on boot: ``systemctl --user enable sunshine`` + +Additional Setup for KMS + .. Note:: ``cap_sys_admin`` may as well be root, except you don't need to be root to run it. + + It is necessary to allow Sunshine to use KMS: ``sudo setcap cap_sys_admin+p sunshine`` + +MacOS +^^^^^ +Sunshine can only access microphones on macOS due to system limitations. To stream system audio use +`Soundflower `_ or +`BlackHole `_ and +select their sink as audio device in `sunshine.conf`. + +.. Note:: Command Keys are not forwarded by Moonlight. Right Option-Key is mapped to CMD-Key. + +.. Caution:: Gamepads are not supported. + +Windows +^^^^^^^ +For gamepad support, install `ViGEmBus `_ + +Shortcuts +--------- +All shortcuts start with CTRL + ALT + SHIFT, just like Moonlight + + - ``CTRL + ALT + SHIFT + N`` - Hide/Unhide the cursor (This may be useful for Remote Desktop Mode for Moonlight) + - ``CTRL + ALT + SHIFT + F1/F13`` - Switch to different monitor for Streaming + +Application List +---------------- +- You can use Environment variables in place of values +- ``$(HOME)` will be replaced by the value of ``$HOME`` +- ``$$`` will be replaced by ``$``, e.g. ``$$(HOME)`` will be replaced by ``$(HOME)`` +- ``env`` - Adds or overwrites Environment variables for the commands/applications run by Sunshine +- ``"Variable name":"Variable value"`` +- ``apps`` - The list of applications +- Example application: + + .. code-block:: json + + { + "name":"An App", + "cmd":"command to open app", + "prep-cmd":[ + { + "do":"some-command", + "undo":"undo-that-command" + } + ], + "detached":[ + "some-command", + "another-command" + ] + } + + - ``name`` - The name of the application/game + - ``output`` - The file where the output of the command is stored + - ``detached`` - A list of commands to be run and forgotten about + - ``prep-cmd`` - A list of commands to be run before/after the application + + - If any of the prep-commands fail, starting the application is aborted + - ``do`` - Run before the application + + - If it fails, all ``undo`` commands of the previously succeeded ``do`` commands are run + + - ``undo`` - Run after the application has terminated + + - This should not fail considering it is supposed to undo the ``do`` commands + - If it fails, Sunshine is terminated + + - ``cmd`` - The main application + + - If not specified, a process is started that sleeps indefinitely + +Considerations +-------------- +- When an application is started, if there is an application already running, it will be terminated. +- When the application has been shutdown, the stream shuts down as well. +- 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. diff --git a/docs/source/building/build.rst b/docs/source/building/build.rst new file mode 100644 index 00000000..556725fc --- /dev/null +++ b/docs/source/building/build.rst @@ -0,0 +1,31 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/building/build.rst + +Build +===== +Sunshine binaries are built using `CMake `_. Cross compilation is not +supported. That means the binaries must be built on the target operating system and architecture. + +Building Locally +---------------- + +Clone +^^^^^ +Ensure `git `_ is installed and run the following: + + .. code-block:: bash + + git clone https://github.com/sunshinestream/sunshine.git --recurse-submodules + cd sunshine && mkdir build && cd build + +Build +^^^^^ +See the section specific to your OS. + +Remote Build +------------ +It may be beneficial to build remotely in some cases. This will enable easier building on different operating systems. + +#. Fork the project +#. Activate workflows +#. Trigger the `CI` workflow manually +#. Download the artifacts/binaries from the workflow run summary diff --git a/docs/source/building/linux.rst b/docs/source/building/linux.rst new file mode 100644 index 00000000..9f87d588 --- /dev/null +++ b/docs/source/building/linux.rst @@ -0,0 +1,241 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/building/linux.rst + +Linux +===== + +Requirements +------------ +.. Warning:: Installing these dependencies may break your distribution. It is recommended to build in a virtual machine + or to use the Dockerfile builds located in the `./scripts` directory. + +Debian Bullseye +""""""""""""""" +End of Life: TBD + +Install Requirements + .. code-block:: bash + + sudo apt update && sudo apt install \ + build-essential \ + cmake \ + git \ + libavdevice-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libboost-thread-dev \ + libcap-dev \ # KMS + libdrm-dev \ # KMS + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ # Wayland + libx11-dev \ # X11 + libxcb-shm0-dev \ # X11 + libxcb-xfixes0-dev \ # X11 + libxcb1-dev \ # X11 + libxfixes-dev \ # X11 + libxrandr-dev \ # X11 + libxtst-dev \ # X11 + nvidia-cuda-dev \ # Cuda, NvFBC + nvidia-cuda-toolkit \ # Cuda, NvFBC + +Fedora 35 +""""""""" +End of Life: TBD + +Install Repositories + .. code-block:: bash + + sudo dnf update && \ + sudo dnf group install "Development Tools" && \ + sudo dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm + +Install Requirements + .. code-block:: bash + + sudo dnf install \ + boost-devel \ + boost-static.x86_64 \ + cmake \ + ffmpeg-devel \ + gcc-c++ \ + libevdev-devel \ + libX11-devel \ # X11 + libxcb-devel \ # X11 + libXcursor-devel \ # X11 + libXfixes-devel \ # X11 + libXinerama-devel \ # X11 + libXi-devel \ # X11 + libXrandr-devel \ # X11 + libXtst-devel \ # X11 + mesa-libGL-devel \ + openssl-devel \ + opus-devel \ + pulseaudio-libs-devel \ + rpm-build \ # if you want to build an RPM binary package + +Ubuntu 18.04 +"""""""""""" +End of Life: April 2028 + +Install Repositories + .. code-block:: bash + + sudo apt update && sudo apt install \ + software-properties-common \ + && add-apt-repository ppa:savoury1/graphics && \ + add-apt-repository ppa:savoury1/multimedia && \ + add-apt-repository ppa:savoury1/ffmpeg4 && \ + add-apt-repository ppa:savoury1/boost-defaults-1.71 && \ + add-apt-repository ppa:ubuntu-toolchain-r/test && \ + +Install Requirements + .. code-block:: bash + + sudo apt install \ + build-essential \ + cmake \ + gcc-10 \ + git \ + g++-10 \ + libavdevice-dev \ + libboost-filesystem1.71-dev \ + libboost-log1.71-dev \ + libboost-regex1.71-dev \ + libboost-thread1.71-dev \ + libcap-dev \ # KMS + libdrm-dev \ # KMS + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ # Wayland + libx11-dev \ # X11 + libxcb-shm0-dev \ # X11 + libxcb-xfixes0-dev \ # X11 + libxcb1-dev \ # X11 + libxfixes-dev \ # X11 + libxrandr-dev \ # X11 + libxtst-dev \ # X11 + wget \ + +Update gcc alias + .. code-block:: bash + + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 + +Install CuDA + .. code-block:: bash + + wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O ./cuda.run && chmod a+x ./cuda.run + ./cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm ./cuda.run + +Install CMake + .. code-block:: bash + + wget https://cmake.org/files/v3.22/cmake-3.22.2-linux-x86_64.sh + mkdir /opt/cmake + sh /cmake-3.22.2-linux-x86_64.sh --prefix=/opt/cmake --skip-license + ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake + cmake --version + +Ubuntu 20.04 +"""""""""""" +End of Life: April 2030 + +Install Requirements + .. code-block:: bash + + sudo apt update && sudo apt install \ + build-essential \ + cmake \ + git \ + g++-10 \ + libavdevice-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libboost-thread-dev \ + libcap-dev \ # KMS + libdrm-dev \ # KMS + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ # Wayland + libx11-dev \ # X11 + libxcb-shm0-dev \ # X11 + libxcb-xfixes0-dev \ # X11 + libxcb1-dev \ # X11 + libxfixes-dev \ # X11 + libxrandr-dev \ # X11 + libxtst-dev \ # X11 + wget \ + +Update gcc alias + .. code-block:: bash + + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 + +Install CuDA + .. code-block:: bash + + wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O ./cuda.run && chmod a+x ./cuda.run + ./cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm ./cuda.run + +Ubuntu 21.10 +"""""""""""" +End of Life: July 2022 + +Install Requirements + .. code-block:: bash + + sudo apt update && sudo apt install \ + build-essential \ + cmake \ + git \ + libavdevice-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libboost-thread-dev \ + libcap-dev \ # KMS + libdrm-dev \ # KMS + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ # Wayland + libx11-dev \ # X11 + libxcb-shm0-dev \ # X11 + libxcb-xfixes0-dev \ # X11 + libxcb1-dev \ # X11 + libxfixes-dev \ # X11 + libxrandr-dev \ # X11 + libxtst-dev \ # X11 + nvidia-cuda-dev \ # Cuda, NvFBC + nvidia-cuda-toolkit \ # Cuda, NvFBC + +Ubuntu 22.04 +"""""""""""" +End of Life: April 2027 + +.. Todo:: Create Ubuntu 22.04 Dockerfile and complete this documentation. + +Build +----- +.. Caution:: Ensure you are in the build directory created during the clone step earlier before continuing. + +Debian based OSes + .. code-block:: bash + + cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 .. + +Red Hat based Oses + .. code-block:: bash + + cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ .. + +Finally + .. code-block:: bash + + make -j ${nproc} diff --git a/docs/source/building/macos.rst b/docs/source/building/macos.rst new file mode 100644 index 00000000..c763ad12 --- /dev/null +++ b/docs/source/building/macos.rst @@ -0,0 +1,41 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/building/macos.rst + +MacOS +===== + +Requirements +------------ +MacOS Big Sur and Xcode 12.5+ + +Use either `MacPorts `_ or `Homebrew `_ + +MacPorts +"""""""" +Install Requirements + .. code-block:: bash + + sudo port install cmake boost libopus ffmpeg + +Homebrew +"""""""" +Install Requirements + .. code-block:: bash + + brew install boost cmake ffmpeg libopusenc + # if there are issues with an SSL header that is not found: + cd /usr/local/include + ln -s ../opt/openssl/include/openssl . + +Build +----- +.. Caution:: Ensure you are in the build directory created during the clone step earlier before continuing. + + .. code-block:: bash + + cmake .. + make -j ${nproc} + +If cmake fails complaining to find Boost, try to set the path explicitly. + + ``cmake -DBOOST_ROOT=[boost path] ..``, e.g., ``cmake -DBOOST_ROOT=/opt/local/libexec/boost/1.76 ..`` + diff --git a/docs/source/building/windows.rst b/docs/source/building/windows.rst new file mode 100644 index 00000000..6a7c1e16 --- /dev/null +++ b/docs/source/building/windows.rst @@ -0,0 +1,22 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/building/windows.rst + +Windows +======= + +Requirements +------------ +First you need to install `MSYS2 `_, then startup "MSYS2 MinGW 64-bit" and install the +following packages using: + +.. code-block:: batch + + pacman -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git mingw-w64-x86_64-make cmake make gcc + +Build +----- +.. Caution:: Ensure you are in the build directory created during the clone step earlier before continuing. + + .. code-block:: batch + + cmake -G"Unix Makefiles" .. + mingw32-make diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 00000000..0375d42b --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,88 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# standard imports +from datetime import datetime + + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +import os +# import sys + +script_dir = os.path.dirname(os.path.abspath(__file__)) # the directory of this file +source_dir = os.path.dirname(script_dir) # the source folder directory +root_dir = os.path.dirname(source_dir) # the root folder directory + +# -- Project information ----------------------------------------------------- +project = 'Sunshine' +copyright = f'{datetime.now ().year}, {project}' +author = 'ReenigneArcher' + +# The full version, including alpha/beta/rc tags +# version = '0.13.0' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'm2r2', # enable markdown files + 'sphinx.ext.autosectionlabel', + 'sphinx.ext.todo', # enable to-do sections + 'sphinx.ext.viewcode' # add links to view source code +] + +# Add any paths that contain templates here, relative to this directory. +# templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['toc.rst'] + +# Extensions to include. +source_suffix = ['.rst', '.md'] + + +# -- Options for HTML output ------------------------------------------------- + +# 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.ico') + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'sphinx_rtd_theme' + +html_theme_options = { + # 'analytics_id': 'G-XXXXXXXXXX', # Provided by Google in your dashboard + # 'analytics_anonymize_ip': False, + 'logo_only': False, + 'display_version': False, + 'prev_next_buttons_location': 'bottom', + 'style_external_links': True, + 'vcs_pageview_mode': 'blob', + # 'style_nav_header_background': 'white', + # Toc options + 'collapse_navigation': True, + 'sticky_navigation': True, + 'navigation_depth': 4, + 'includehidden': True, + 'titles_only': False, +} + +# extension config options +autosectionlabel_prefix_document = True # Make sure the target is unique +todo_include_todos = True diff --git a/docs/source/contributing/contributing.rst b/docs/source/contributing/contributing.rst new file mode 100644 index 00000000..d9f5106f --- /dev/null +++ b/docs/source/contributing/contributing.rst @@ -0,0 +1,32 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/contributing/contributing.rst + +Contributing +============ +#. Fork the repo on GitHub +#. Create a new branch for the feature you are adding or the issue you are fixing + + .. Tip:: Base the new branch off the `nightly` branch. It will make your life easier when you submit the PR! + +#. Make changes, push commits, etc. +#. Files should contain an empty line at the end. +#. Document your code! +#. Test your code! +#. When ready create a PR to this repo on the `nightly` branch. + + .. Hint:: If you accidentally make your PR against a different branch, a bot will comment letting you know it's on + the wrong branch. Don't worry. You can edit the PR to change the target branch. There is no reason to close the + PR! + + .. Note:: Draft PRs are also welcome as you work through issues. The benefit of creating a draft PR is that an + automated build can run in a github runner. + + .. Attention:: Do not expect partially complete PRs to be merged. These topics will be considered before merging. + + - Does the code follows the style guidelines of this project? + + .. Tip:: Look at examples of existing code in the project! + + - Is the code well commented? + - Were documentation blocks updated for new or modified components? + + .. Note:: Developers and maintainers will attempt to assist with challenging issues. diff --git a/docs/source/contributing/localization.rst b/docs/source/contributing/localization.rst new file mode 100644 index 00000000..6d0f6556 --- /dev/null +++ b/docs/source/contributing/localization.rst @@ -0,0 +1,80 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/contributing/localization.rst + +Localization +============ +Sunshine is being localized into various languages. The default language is `en` (English) and is highlighted green. + +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=de&style=for-the-badge&query=%24.progress.0.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json +.. image:: https://img.shields.io/badge/dynamic/json?color=green&label=en&style=for-the-badge&query=%24.progress.1.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=en-GB&style=for-the-badge&query=%24.progress.2.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=en-US&style=for-the-badge&query=%24.progress.3.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=es-ES&style=for-the-badge&query=%24.progress.4.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=fr&style=for-the-badge&query=%24.progress.5.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=it&style=for-the-badge&query=%24.progress.6.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=ru&style=for-the-badge&query=%24.progress.7.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json + +Graph + .. image:: https://badges.awesome-crowdin.com/translation-15178612-503956.png + +CrowdIn +------- +The translations occur on +`CrowdIn `_. Feel free to contribute to localization there. +Only elements of the API are planned to be translated. + +.. Note:: The rest API has not yet been implemented. + +Translations Basics + - The brand names `SunshineStream` and `Sunshine` should never be translated. + - Other brand names should never be translated. + Examples: + + - AMD + - Nvidia + +CrowdIn Integration + How does it work? + + When a change is made to sunshine source code, a workflow generates new translation templates + that get pushed to CrowdIn automatically. + + When translations are updated on CrowdIn, a push gets made to the `l10n_nightly` branch and a PR is made against the + `nightly` branch. Once PR is merged, all updated translations are part of the project and will be included in the + next release. + +Extraction +---------- +There should be minimal cases where strings need to be extracted from source code; however it may be necessary in some +situations. For example if a system tray icon is added it should be localized as it is user interfacing. + + - Wrap the string to be extracted in a function as shown. + + .. code-block:: cpp + + #include + boost::locale::translate("Hello world!") + +.. Warning:: This is for information only. Contributors should never include manually updated template files, or + manually compiled language files in Pull Requests. + +Strings are automatically extracted from the code to the `locale/sunshine.po` template file. The generated file is +used by CrowdIn to generate language specific template files. The file is generated using the +`.github/workflows/localize.yml` workflow and is run on any push event into the `nightly` branch. Jobs are only run if +any of the following paths are modified. + + .. code-block:: yaml + + - 'sunshine/**' + +When testing locally it may be desirable to manually extract, initialize, update, and compile strings. Python is +required for this, along with the dependencies in the `./scripts/requirements.txt` file. + + Extract, initialize, and update + .. code-block:: bash + + python ./scripts/_locale.py --extract --init --update + + Compile + .. code-block:: bash + + python ./scripts/_locale.py --compile diff --git a/docs/source/contributing/testing.rst b/docs/source/contributing/testing.rst new file mode 100644 index 00000000..a08c6f9c --- /dev/null +++ b/docs/source/contributing/testing.rst @@ -0,0 +1,42 @@ +:github_url: https://github.com/RetroArcher/RetroArcher/tree/nightly/docs/source/contributing/testing.rst + +Testing +======= + +Clang Format +------------ +Source code is tested against the `.clang-format` file for linting errors. The workflow file responsible for clang +format testing is `.github/workflows/clang.yml`. + +Test clang-format locally. + .. Todo:: This documentation needs to be improved. + + .. code-block:: bash + + clang-format ... + +Sphinx +------ +Sunshine uses `Sphinx `_ for documentation building. Sphinx is included +in the `./scripts/requirements.txt` file. Python is required to build sphinx docs. Installation and setup of python +will not be covered here. + +The config file for Sphinx is `docs/source/conf.py`. This is already included in the repo and should not be modified. + +Test with Sphinx + .. code-block:: bash + + cd docs + make html + + Alternatively + + .. code-block:: bash + + cd docs + sphinx-build -b html source build + +Unit Testing +------------ +.. Todo:: Sunshine does not currently have any unit tests. If you would like to help us improve please get in contact + with us, or make a PR with suggested changes. diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 00000000..5384c8e2 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,7 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/index.rst + +SunshineStream has this documentation hosted on `Read the Docs `_. + +Table of Contents +================= +.. include:: toc.rst diff --git a/docs/source/toc.rst b/docs/source/toc.rst new file mode 100644 index 00000000..9c0989ee --- /dev/null +++ b/docs/source/toc.rst @@ -0,0 +1,27 @@ +.. toctree:: + :maxdepth: 2 + :caption: About + + about/overview + about/installation + about/docker + about/third_party_packages + about/usage + about/advanced_usage + +.. toctree:: + :maxdepth: 2 + :caption: Build + + building/build + building/linux + building/macos + building/windows + +.. toctree:: + :maxdepth: 2 + :caption: Contributing + + contributing/contributing + contributing/localization + contributing/testing diff --git a/scripts/Dockerfile-debian b/scripts/Dockerfile-debian index 3a53fd9f..fc77eb8e 100644 --- a/scripts/Dockerfile-debian +++ b/scripts/Dockerfile-debian @@ -1,5 +1,7 @@ FROM debian:bullseye AS sunshine-debian +# Debian Bullseye end of life is TBD + ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" @@ -11,9 +13,9 @@ RUN apt-get update -y && \ cmake \ git \ libavdevice-dev \ - libboost-thread-dev \ libboost-filesystem-dev \ libboost-log-dev \ + libboost-thread-dev \ libcap-dev \ libdrm-dev \ libevdev-dev \ diff --git a/scripts/Dockerfile-fedora_33 b/scripts/Dockerfile-fedora_33 index 50a4e3db..136058c3 100644 --- a/scripts/Dockerfile-fedora_33 +++ b/scripts/Dockerfile-fedora_33 @@ -1,5 +1,8 @@ FROM fedora:33 AS sunshine-fedora_33 +# Fedora 33 end of life is November 2021 +# This file remains for reference only + SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN dnf -y update && \ dnf -y group install "Development Tools" && \ diff --git a/scripts/Dockerfile-fedora_35 b/scripts/Dockerfile-fedora_35 index d4f5d843..18b04377 100644 --- a/scripts/Dockerfile-fedora_35 +++ b/scripts/Dockerfile-fedora_35 @@ -1,5 +1,7 @@ FROM fedora:35 AS sunshine-fedora_35 +# Fedora 35 end of life is TBD + SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN dnf -y update && \ dnf -y group install "Development Tools" && \ @@ -11,8 +13,8 @@ RUN dnf -y update && \ ffmpeg-devel \ gcc-c++ \ libevdev-devel \ - libxcb-devel \ libX11-devel \ + libxcb-devel \ libXcursor-devel \ libXfixes-devel \ libXinerama-devel \ diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 7225c0c7..6ad3e8c0 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -1,5 +1,7 @@ FROM ubuntu:18.04 AS sunshine-ubuntu_18_04 +# Ubuntu 18.04 end of life is April 2028 + ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" diff --git a/scripts/Dockerfile-ubuntu_20_04 b/scripts/Dockerfile-ubuntu_20_04 index 9ae7dfa9..44e897a7 100644 --- a/scripts/Dockerfile-ubuntu_20_04 +++ b/scripts/Dockerfile-ubuntu_20_04 @@ -1,5 +1,7 @@ FROM ubuntu:20.04 AS sunshine-ubuntu_20_04 +# Ubuntu 20.04 end of life is April 2030 + ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" diff --git a/scripts/Dockerfile-ubuntu_21_04 b/scripts/Dockerfile-ubuntu_21_04 index 00801c55..012845a5 100644 --- a/scripts/Dockerfile-ubuntu_21_04 +++ b/scripts/Dockerfile-ubuntu_21_04 @@ -1,5 +1,8 @@ FROM ubuntu:21.04 AS sunshine-ubuntu_21_04 +# Ubuntu 21.04 end of life is January 2022 +# This file remains for reference only + ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" diff --git a/scripts/Dockerfile-ubuntu_21_10 b/scripts/Dockerfile-ubuntu_21_10 index 83b3e7f8..6be49dbe 100644 --- a/scripts/Dockerfile-ubuntu_21_10 +++ b/scripts/Dockerfile-ubuntu_21_10 @@ -1,5 +1,7 @@ FROM ubuntu:21.10 AS sunshine-ubuntu_21_10 +# Ubuntu 21.10 end of life is July 2022 + ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" @@ -10,9 +12,9 @@ RUN apt-get update -y && \ cmake \ git \ libavdevice-dev \ - libboost-thread-dev \ libboost-filesystem-dev \ libboost-log-dev \ + libboost-thread-dev \ libcap-dev \ libdrm-dev \ libevdev-dev \ diff --git a/scripts/_locale.py b/scripts/_locale.py index 82e172cc..e47887cc 100644 --- a/scripts/_locale.py +++ b/scripts/_locale.py @@ -1,4 +1,6 @@ -"""_locale.py +""" +.. + _locale.py Functions related to building, initializing, updating, and compiling localization translations. @@ -62,7 +64,7 @@ def x_extract(): commands.append(filename) print(commands) - proc = subprocess.run(args=commands, cwd=root_dir) + subprocess.check_output(args=commands, cwd=root_dir) # fix header body = "" @@ -98,7 +100,7 @@ def babel_init(locale_code: str): ] print(commands) - proc = subprocess.run(args=commands, cwd=root_dir) + subprocess.check_output(args=commands, cwd=root_dir) def babel_update(): @@ -113,7 +115,7 @@ def babel_update(): ] print(commands) - proc = subprocess.run(args=commands, cwd=root_dir) + subprocess.check_output(args=commands, cwd=root_dir) def babel_compile(): @@ -126,13 +128,13 @@ def babel_compile(): ] print(commands) - proc = subprocess.run(args=commands, cwd=root_dir) + subprocess.check_output(args=commands, cwd=root_dir) if __name__ == '__main__': # Set up and gather command line arguments parser = argparse.ArgumentParser( - description='Script helps update locale_id translations. Translations must be done manually.') + description='Script helps update locale translations. Translations must be done manually.') parser.add_argument('--extract', action='store_true', help='Extract messages from c++ files.') parser.add_argument('--init', action='store_true', help='Initialize any new locales specified in target locales.') diff --git a/scripts/requirements.txt b/scripts/requirements.txt index 9d236e72..c089efd9 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -1 +1,4 @@ Babel==2.9.1 +m2r2==0.3.2 +Sphinx==4.5.0 +sphinx-rtd-theme==1.0.0 From 293ee266afdb7a881b94c04c918b4a435a2ea19c Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 18 Apr 2022 15:26:53 -0400 Subject: [PATCH 213/817] Add docker file build instructions and... - Remove readme.md files --- README.md | 334 --------------------------------- docs/source/building/build.rst | 8 +- docs/source/building/linux.rst | 73 ++++++- scripts/README.md | 53 ------ 4 files changed, 72 insertions(+), 396 deletions(-) delete mode 100644 README.md delete mode 100644 scripts/README.md diff --git a/README.md b/README.md deleted file mode 100644 index f062769e..00000000 --- a/README.md +++ /dev/null @@ -1,334 +0,0 @@ -# Introduction -Sunshine is a Gamestream host for Moonlight - -[![CI](https://github.com/SunshineStream/Sunshine/actions/workflows/CI.yml/badge.svg?branch=master)](https://github.com/SunshineStream/Sunshine/actions/workflows/CI.yml) -[![Downloads](https://img.shields.io/github/downloads/sunshinestream/sunshine/total)](https://github.com/sunshinestream/sunshine/releases) -[![Crowdin](https://badges.crowdin.net/sunshinestream/localized.svg)](https://crowdin.com/project/sunshinestream) - -- [Building](README.md#building) -- [Credits](README.md#credits) - -# Building -- [Linux](README.md#linux) -- [MacOS](README.md#macos) -- [Windows](README.md#windows-10) - -## Linux - -If you do not wish to clutter your PC with development files, yet you want the very latest version... -You can use these [build scripts](scripts/README.md) -They make use of docker to handle building Sunshine automatically - -### Requirements: - -#### Ubuntu 20.04: -Install the following: - -#### Common -``` -sudo apt install cmake gcc-10 g++-10 libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libevdev-dev -``` -#### X11 -``` -sudo apt install libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev -``` - -#### KMS -This requires additional [setup](README.md#Setup). -``` -sudo apt install libdrm-dev libcap-dev -``` - -#### Wayland -This is for wlroots based compositores, such as Sway -``` -sudo apt install libwayland-dev -``` - -#### Cuda + NvFBC -This requires proprietary software -On Ubuntu 20.04, the cuda compiler will fail since it's version is too old, it's recommended you compile the sources with the [build scripts](scripts/README.md) -``` -sudo apt install nvidia-cuda-dev nvidia-cuda-toolkit -``` - -#### Fedora 35: - -You will need some things in the RPMFusion repo, nost notably ffmpeg. -``` -sudo dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm -``` -#### Development tools and libraries -``` -sudo dnf install \ - boost-devel \ - boost-static.x86_64 \ - cmake \ - ffmpeg-devel \ - gcc-c++ \ - libevdev-devel \ - libxcb-devel \ - libX11-devel \ - libXcursor-devel \ - libXfixes-devel \ - libXinerama-devel \ - libXi-devel \ - libXrandr-devel \ - libXtst-devel \ - mesa-libGL-devel \ - openssl-devel \ - opus-devel \ - pulseaudio-libs-devel -``` -#### If you need to build an RPM binary package: -``` -sudo dnf install rpmbuild -``` - -#### Warning: -You might require ffmpeg version >= 4.3. Check the troubleshooting section for more information. - -### Compilation: - -#### Ubuntu -- `git clone https://github.com/SunshineStream/Sunshine.git --recurse-submodules` -- `cd sunshine && mkdir build && cd build` -- `cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 ..` -- `make -j ${nproc}` - -#### Fedora -- `git clone https://github.com/SunshineStream/Sunshine.git --recurse-submodules` -- `cd sunshine && mkdir build && cd build` -- `cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ ..` -- `make -j ${nproc}` - -### Setup: -sunshine needs access to uinput to create mouse and gamepad events: - -- Add user to group 'input': - `usermod -a -G input $USER` -- Create udev rules: - - Run the following command: - `nano /etc/udev/rules.d/85-sunshine-input.rules` - - Input the following contents: - `KERNEL=="uinput", GROUP="input", MODE="0660"` - - Save the file and exit - 1. `CTRL+X` to start exit - 2. `Y` to save modifications -- `assets/sunshine.conf` is an example configuration file. Modify it as you see fit, then use it by running: - `sunshine path/to/sunshine.conf` -- Configure autostart service - `path/to/build/dir/sunshine.service` is used to start sunshine in the background. To use it, do the following: - 1. Copy it to the users systemd, `cp sunshine.service ~/.config/systemd/user/` - 2. Starting - - Onetime: - `systemctl --user start sunshine` - - Always on boot: - `systemctl --user enable sunshine` - -- `assets/apps.json` is an [example](README.md#application-list) of a list of applications that are started just before running a stream - -#### Additional Setup for KMS: -Please note that `cap_sys_admin` may as well be root, except you don't need to be root to run it. -It's necessary to allow Sunshine to use KMS -- `sudo setcap cap_sys_admin+p sunshine` - -### Trouleshooting: -- If you get "Could not create Sunshine Gamepad: Permission Denied", ensure you are part of the group "input": - - `groups $USER` - -- If Sunshine sends audio from the microphone instead of the speaker, try the following steps: - 1. Check whether you're using Pulseaudio or Pipewire - - Pulseaudio: Use `pacmd list-sources | grep "name:"` - - Pipewire: Use `pactl info | grep Source`. In some causes you'd need to use the `sink` device. Try `pactl info | grep Sink`, if _Source_ doesn't work. - 2. Copy the name to the configuration option "audio_sink" - 3. Restart sunshine - -- If you get "Error: Failed to create client: Daemon not running", ensure that your avahi-daemon is running: - - `systemctl status avahi-daemon` - -- If you use hardware acceleration on Linux using an Intel or an AMD GPU (with VAAPI), you will get tons of [graphical issues](https://github.com/loki-47-6F-64/sunshine/issues/228) if your ffmpeg version is < 4.3. If it is not available in your distribution's repositories, consider using a newer version of your distribution. - - Ubuntu started to ship ffmpeg 4.3 starting with groovy (20.10). If you're using an older version, you could use [this PPA](https://launchpad.net/%7Esavoury1/+archive/ubuntu/ffmpeg4) instead of upgrading. **Using PPAs is dangerous and may break your system. Use it at your own risk.** - -## macOS - -### Quickstart - -- Install [MacPorts](https://www.macports.org) -- Download the `Portfile` from this repository to `/tmp` -- In a Terminal run `cd /tmp && sudo port install` -- Sunshine configuration is in `/opt/local/etc` -- Run `sunshine` to start the Sunshine server -- You will be asked to grant access to screen recording and your microphone to be able to stream it - -### Manuel Build - -#### Requirements: -macOS Big Sur and Xcode 12.5+: - -Either, using [MacPorts](https://www.macports.org), install the following -``` -sudo port install cmake boost libopus ffmpeg -``` - -Or, using [Homebrew](https://brew.sh), install the follwoing: -``` -brew install boost cmake ffmpeg libopusenc -# if there are issues with an SSL header that is not found: -cd /usr/local/include -ln -s ../opt/openssl/include/openssl . -``` - -#### Compilation: -- `git clone https://github.com/SunshineStream/Sunshine.git --recurse-submodules` -- `cd sunshine && mkdir build && cd build` -- `cmake ..` -- `make -j ${nproc}` - -If cmake fails complaining to find Boost, try to set the path explicitly: `cmake -DBOOST_ROOT=[boost path] ..`, e.g., `cmake -DBOOST_ROOT=/opt/local/libexec/boost/1.76 ..` - -### Setup: -- 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` -- `assets/sunshine.conf` is an example configuration file. Modify it as you see fit, then use it by running: - `sunshine path/to/sunshine.conf` -- `assets/apps.json` is an [example](README.md#application-list) of a list of applications that are started just before running a stream - -### Usage & Limitations: -- Command Keys are not forwarded by Moonlight. Right Option-Key is mapped to CMD-Key. -- Gamepads are not supported - -## Windows 10 - -### Requirements: - -First you need to install [MSYS2](https://www.msys2.org), then startup "MSYS2 MinGW 64-bit" and install the following packages using `pacman -S`: - - mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git mingw-w64-x86_64-make cmake make gcc - -### Compilation: -- `git clone https://github.com/loki-47-6F-64/sunshine.git --recursive` -- `cd sunshine && mkdir build && cd build` -- `cmake -G"Unix Makefiles" ..` -- `mingw32-make` - -### Setup: -- **OPTIONAL** Gamepad support: Download and run 'ViGEmBus_Setup_1.16.116.exe' from [https://github.com/ViGEm/ViGEmBus/releases] - - - -# Common - -## Usage: -- run "sunshine path/to/sunshine.conf" -- If running for the first time, make sure to note the username and password Sunshine showed to you, since you **cannot get back later**! -- In Moonlight: Add PC manually -- When Moonlight request you insert the correct pin on sunshine: - - Type in the URL bar of your browser: `https://xxx.xxx.xxx.xxx:47990` where `xxx.xxx.xxx.xxx` is the IP address of your computer - - Ignore any warning given by your browser about "insecure website" - - You should compile the next page with a new username and a password, needed to login into the next step - - Press "Save" and log in using the credentials given above - - Go to "PIN" in the Header - - Type in your PIN and press Enter, you should get a Success Message -- Click on one of the Applications listed -- Have fun :) - -## Shortcuts: - -All shortcuts start with CTRL + ALT + SHIFT, just like Moonlight -- CTRL + ALT + SHIFT + N --> Hide/Unhide the cursor (This may be usefull for Remote Desktop Mode for Moonlight) -- CTRL + ALT + SHIFT + F1/F13 --> Switch to different monitor for Streaming - -## Credits: -- [loki-47-6F-64/sunshine](https://github.com/loki-47-6F-64/sunshine) (For all the hard work put in to create sunshine in the first place!) -- [Simple-Web-Server](https://gitlab.com/eidheim/Simple-Web-Server) -- [Moonlight](https://github.com/moonlight-stream) -- [Looking-Glass](https://github.com/gnif/LookingGlass) (For showing me how to properly capture frames on Windows, saving me a lot of time :) -- [Eretik](http://eretik.omegahg.com/) (For creating PolicyConfig.h, allowing me to change the default audio device on Windows programmatically) - -## Application List: -**Note:** You can change the Application List in the "Apps" section of the User Interface `https://xxx.xxx.xxx.xxx:47990/` -- You can use Environment variables in place of values - - $(HOME) will be replaced by the value of $HOME - - $$ will be replaced by $ --> $$(HOME) will be replaced by $(HOME) -- env: Adds or overwrites Environment variables for the commands/applications run by Sunshine. - - "Variable name":"Variable value" -- apps: The list of applications - - Example: - ```json - { - "name":"An App", - "cmd":"command to open app", - "prep-cmd":[ - { - "do":"some-command", - "undo":"undo-that-command" - } - ], - "detached":[ - "some-command", - "another-command" - ] - } - ``` - - name: Self explanatory - - output : The file where the output of the command is stored - - If it is not specified, the output is ignored - - detached: A list of commands to be run and forgotten about - - prep-cmd: A list of commands to be run before/after the application - - If any of the prep-commands fail, starting the application is aborted - - do: Run before the application - - If it fails, all 'undo' commands of the previously succeeded 'do' commands are run - - undo : Run after the application has terminated - - This should not fail considering it is supposed to undo the 'do' commands. - - If it fails, Sunshine is terminated - - cmd : The main application - - If not specified, a processs is started that sleeps indefinitely - -1. When an application is started, if there is an application already running, it will be terminated. -2. When the application has been shutdown, the stream shuts down as well. -3. 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. - -Linux -```json -{ - "env":{ - "DISPLAY":":0", - "DRI_PRIME":"1", - "XAUTHORITY":"$(HOME)/.Xauthority", - "PATH":"$(PATH):$(HOME)/.local/bin" - }, - "apps":[ - { - "name":"Low Res Desktop", - "prep-cmd":[ - { "do":"xrandr --output HDMI-1 --mode 1920x1080", "undo":"xrandr --output HDMI-1 --mode 1920x1200" } - ] - }, - { - "name":"Steam BigPicture", - - "output":"steam.txt", - "cmd":"steam -bigpicture", - "prep-cmd":[] - } - ] -} -``` -Windows -```json -{ - "env":{ - "PATH":"$(PATH);C:\\Program Files (x86)\\Steam" - }, - "apps":[ - { - "name":"Steam BigPicture", - - "output":"steam.txt", - "prep-cmd":[ - {"do":"steam \"steam://open/bigpicture\""} - ] - } - ] -} -``` diff --git a/docs/source/building/build.rst b/docs/source/building/build.rst index 556725fc..cd83cb14 100644 --- a/docs/source/building/build.rst +++ b/docs/source/building/build.rst @@ -17,10 +17,14 @@ Ensure `git `_ is installed and run the following: git clone https://github.com/sunshinestream/sunshine.git --recurse-submodules cd sunshine && mkdir build && cd build -Build -^^^^^ +Compile +^^^^^^^ See the section specific to your OS. +- :ref:`Linux ` +- :ref:`MacOS ` +- :ref:`Windows ` + Remote Build ------------ It may be beneficial to build remotely in some cases. This will enable easier building on different operating systems. diff --git a/docs/source/building/linux.rst b/docs/source/building/linux.rst index 9f87d588..15ff0001 100644 --- a/docs/source/building/linux.rst +++ b/docs/source/building/linux.rst @@ -6,10 +6,10 @@ Linux Requirements ------------ .. Warning:: Installing these dependencies may break your distribution. It is recommended to build in a virtual machine - or to use the Dockerfile builds located in the `./scripts` directory. + or to use the `Dockerfile builds`_ located in the `./scripts` directory. Debian Bullseye -""""""""""""""" +^^^^^^^^^^^^^^^ End of Life: TBD Install Requirements @@ -41,7 +41,7 @@ Install Requirements nvidia-cuda-toolkit \ # Cuda, NvFBC Fedora 35 -""""""""" +^^^^^^^^^ End of Life: TBD Install Repositories @@ -76,7 +76,7 @@ Install Requirements rpm-build \ # if you want to build an RPM binary package Ubuntu 18.04 -"""""""""""" +^^^^^^^^^^^^ End of Life: April 2028 Install Repositories @@ -141,7 +141,7 @@ Install CMake cmake --version Ubuntu 20.04 -"""""""""""" +^^^^^^^^^^^^ End of Life: April 2030 Install Requirements @@ -184,7 +184,7 @@ Install CuDA ./cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm ./cuda.run Ubuntu 21.10 -"""""""""""" +^^^^^^^^^^^^ End of Life: July 2022 Install Requirements @@ -216,7 +216,7 @@ Install Requirements nvidia-cuda-toolkit \ # Cuda, NvFBC Ubuntu 22.04 -"""""""""""" +^^^^^^^^^^^^ End of Life: April 2027 .. Todo:: Create Ubuntu 22.04 Dockerfile and complete this documentation. @@ -239,3 +239,62 @@ Finally .. code-block:: bash make -j ${nproc} + +Dockerfile Builds +----------------- +You may wish to simply build sunshine from source, without bloating your OS with development files. +There are scripts located in the ``./scripts`` directory that will create docker images that have the necessary +packages. As a result, removing the development files after you're done is a single command away. +These scripts use docker under the hood, as such, they can only be used to compile the Linux version + +.. Todo:: Publish the Dockerfiles to Dockerhub and ghcr. + +Requirements + Install `Docker `_ + +Instructions + #. :ref:`Clone `. Sunshine. + #. Select the desired Dockerfile from the ``./scripts`` directory. + + Available Files: + .. code-block:: text + + Dockerfile-debian + Dockerfile-fedora_33 # end of life + Dockerfile-fedora_35 + Dockerfile-ubuntu_18_04 + Dockerfile-ubuntu_20_04 + Dockerfile-ubuntu_21_04 # end of life + Dockerfile-ubuntu_21_10 + + #. Execute + + .. code-block:: bash + + cd scripts # move to the scripts directory + ./build-container.sh -f Dockerfile- # create the container (replace the "") + ./build-sunshine.sh -p -s .. # compile and build sunshine + + #. Updating + + .. code-block:: bash + + git pull # pull the latest changes from github + ./build-sunshine.sh -p -s .. # compile and build sunshine + + #. Optionally, delete the container + .. code-block:: bash + + ./build-container.sh -c delete + + #. Install the resulting package + + Debian + .. code-block:: bash + + sudo apt install -f sunshine-build/sunshine.deb + + Red Hat + .. code-block:: bash + + sudo rpm -i -f sunshine-build/sunshine.rpm diff --git a/scripts/README.md b/scripts/README.md deleted file mode 100644 index 16e99ac8..00000000 --- a/scripts/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# Introduction -Sunshine is a Gamestream host for Moonlight - -[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/cgrtw2g3fq9b0b70/branch/master?svg=true)](https://ci.appveyor.com/project/loki-47-6F-64/sunshine/branch/master) -[![Downloads](https://img.shields.io/github/downloads/Loki-47-6F-64/sunshine/total)](https://github.com/Loki-47-6F-64/sunshine/releases) - -You may wish to simply build sunshine from source, without bloating your OS with development files. -These scripts will create a docker images that have the necessary packages. As a result, removing the development files after you're done is a single command away. -These scripts use docker under the hood, as such, they can only be used to compile the Linux version - - -#### Requirements - -``` -sudo apt install docker -``` - -#### instructions - -You'll require one of the following Dockerfiles: -* Dockerfile-2004 --> Ubuntu 20.04 -* Dockerfile-2104 --> Ubuntu 21.04 -* Dockerfile-debian --> Debian Bullseye - -Depending on your system, the build-* scripts may need root privilleges - -First, the docker container needs to be created: -``` -cd scripts -./build-container.sh -f Dockerfile- -``` - -Then, the sources will be compiled and the debian package generated: -``` -./build-sunshine.sh -p -s .. -``` -You can run `build-sunshine -p -s ..` again as long as the docker container exists. - -``` -git pull -./build-sunshine.sh -p -s .. -``` - -Optionally, the docker container can be removed after you're finished: -``` -./build-container.sh -c delete -``` - -Finally install the resulting package: -``` -sudo apt install -f sunshine-build/sunshine.deb -``` - From 56cf3e4ede97006e1aff652c61d42a82586e7b62 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 18 Apr 2022 16:05:17 -0400 Subject: [PATCH 214/817] Update admonitions --- docs/source/about/advanced_usage.rst | 4 ++-- docs/source/about/installation.rst | 2 +- docs/source/about/third_party_packages.rst | 4 +++- docs/source/about/usage.rst | 10 ++++++---- docs/source/building/linux.rst | 4 ++-- docs/source/building/macos.rst | 2 +- docs/source/building/windows.rst | 2 +- docs/source/contributing/localization.rst | 2 +- 8 files changed, 17 insertions(+), 13 deletions(-) diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index e01de0aa..2a23e1bc 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -29,7 +29,7 @@ Example To manually configure sunshine you may edit the `conf` file in a text editor. Use the examples as reference. -.. Note:: Some settings are not available within the web ui. +.. Hint:: Some settings are not available within the web ui. General ------- @@ -166,7 +166,7 @@ Description .. Tip:: See `virtual key codes `_ - .. Note:: keybindings needs to have a multiple of two elements. + .. Hint:: keybindings needs to have a multiple of two elements. Default None diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 14ee37d2..70fd28c2 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -45,7 +45,7 @@ Red Hat Packages #. Download the corresponding `.rpm` file, e.g. ``sunshine-fedora_35.rpm`` #. ``sudo rpm -i ``, e.g. ``sudo rpm -i ./sunshine-fedora_35.rpm`` -.. Note:: If this is the first time installing. +.. Hint:: If this is the first time installing. .. code-block:: bash diff --git a/docs/source/about/third_party_packages.rst b/docs/source/about/third_party_packages.rst index 4f349abe..6069258a 100644 --- a/docs/source/about/third_party_packages.rst +++ b/docs/source/about/third_party_packages.rst @@ -3,7 +3,7 @@ Third Party Packages ==================== -.. Warning:: These packages are not maintained by SunshineStream. Use at your own risk. +.. Danger:: These packages are not maintained by SunshineStream. Use at your own risk. AUR (Arch Linux User Repository) -------------------------------- @@ -51,6 +51,8 @@ Scoop Legacy GitHub Repo ------------------ +.. Attention:: This repo is no longer maintained. Thank you to Loki for bringing this amazing project to life! + .. image:: https://img.shields.io/github/last-commit/loki-47-6F-64/sunshine?style=for-the-badge&logo=github :alt: GitHub last commit diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst index 6eafe4b9..4bb81bc5 100644 --- a/docs/source/about/usage.rst +++ b/docs/source/about/usage.rst @@ -5,7 +5,9 @@ Usage #. See the `setup`_ section for your specific OS. #. Run ``sunshine /sunshine.conf``. - .. Note:: The configuration file specified will be created if it doesn't exist. + .. Note:: You do not need to specify a config file. If no config file is entered the default location will be used. + + .. Attention:: The configuration file specified will be created if it doesn't exist. .. Tip:: If using the Linux AppImage, replace ``sunshine`` with ``./sunshine.AppImage`` @@ -13,7 +15,7 @@ Usage The web ui is available on `https://localhost:47990 `_ by default. You may replace `localhost` with your internal ip address. - .. Tip:: Ignore any warning given by your browser about "insecure website". + .. Attention:: Ignore any warning given by your browser about "insecure website". .. Caution:: If running for the first time, make sure to note the username and password Sunshine showed to you, since you cannot get back later! @@ -24,7 +26,7 @@ Usage .. Note:: Additionally, apps can be configured manually. `assets/apps_.json` is an example of a list of applications that are started just before running a stream. - .. Note:: Application list is not fully supported on MacOS + .. Attention:: Application list is not fully supported on MacOS #. In Moonlight, you may need to add the PC manually. #. When Moonlight request you insert the correct pin on sunshine: @@ -38,7 +40,7 @@ Network ------- Sunshine will be available on port 47990 by default. -.. Warning:: Do not expose port 47990, or the web ui, to the internet! +.. Danger:: Do not expose port 47990, or the web ui, to the internet! Arguments --------- diff --git a/docs/source/building/linux.rst b/docs/source/building/linux.rst index 15ff0001..a519761a 100644 --- a/docs/source/building/linux.rst +++ b/docs/source/building/linux.rst @@ -5,7 +5,7 @@ Linux Requirements ------------ -.. Warning:: Installing these dependencies may break your distribution. It is recommended to build in a virtual machine +.. Danger:: Installing these dependencies may break your distribution. It is recommended to build in a virtual machine or to use the `Dockerfile builds`_ located in the `./scripts` directory. Debian Bullseye @@ -223,7 +223,7 @@ End of Life: April 2027 Build ----- -.. Caution:: Ensure you are in the build directory created during the clone step earlier before continuing. +.. Attention:: Ensure you are in the build directory created during the clone step earlier before continuing. Debian based OSes .. code-block:: bash diff --git a/docs/source/building/macos.rst b/docs/source/building/macos.rst index c763ad12..97a58a23 100644 --- a/docs/source/building/macos.rst +++ b/docs/source/building/macos.rst @@ -28,7 +28,7 @@ Install Requirements Build ----- -.. Caution:: Ensure you are in the build directory created during the clone step earlier before continuing. +.. Attention:: Ensure you are in the build directory created during the clone step earlier before continuing. .. code-block:: bash diff --git a/docs/source/building/windows.rst b/docs/source/building/windows.rst index 6a7c1e16..36cea881 100644 --- a/docs/source/building/windows.rst +++ b/docs/source/building/windows.rst @@ -14,7 +14,7 @@ following packages using: Build ----- -.. Caution:: Ensure you are in the build directory created during the clone step earlier before continuing. +.. Attention:: Ensure you are in the build directory created during the clone step earlier before continuing. .. code-block:: batch diff --git a/docs/source/contributing/localization.rst b/docs/source/contributing/localization.rst index 6d0f6556..721ebc61 100644 --- a/docs/source/contributing/localization.rst +++ b/docs/source/contributing/localization.rst @@ -22,7 +22,7 @@ The translations occur on `CrowdIn `_. Feel free to contribute to localization there. Only elements of the API are planned to be translated. -.. Note:: The rest API has not yet been implemented. +.. Attention:: The rest API has not yet been implemented. Translations Basics - The brand names `SunshineStream` and `Sunshine` should never be translated. From eacae3954e21eaf8a7349ddc8223cd117c117828 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 18 Apr 2022 16:21:05 -0400 Subject: [PATCH 215/817] Fix typos --- DOCKER_README.md | 2 +- docs/source/about/advanced_usage.rst | 2 +- docs/source/about/installation.rst | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/DOCKER_README.md b/DOCKER_README.md index 8691e01b..aa2f1017 100644 --- a/DOCKER_README.md +++ b/DOCKER_README.md @@ -43,7 +43,7 @@ Create a `docker-compose.yml` file with the following contents (substitute your ```yaml version: '3' services: - retroarcher: + sunshine: image: sunshinestream/sunshine container_name: sunshine restart: unless-stopped diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index 2a23e1bc..4f427dcb 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -9,7 +9,7 @@ Configuration The default location for the configuration file is ``./assets/sunshine.conf``. You can use another location if you choose, by passing in the full configuration file path as the first argument when you start Sunshine. -** Default File Location** +**Default File Location** .. table:: :widths: auto diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 70fd28c2..12813cef 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -24,14 +24,14 @@ Linux AppImage ^^^^^^^^ -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:appimage?logo=github&style=for-the-badge' +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:appimage?logo=github&style=for-the-badge :alt: GitHub issues by-label #. Download and extract `sunshine-appimage.zip` Debian Packages ^^^^^^^^^^^^^^^ -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:debian?logo=github&style=for-the-badge' +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:debian?logo=github&style=for-the-badge :alt: GitHub issues by-label #. Download the corresponding `.deb` file, e.g. ``sunshine-ubuntu_20_04.deb`` @@ -39,7 +39,7 @@ Debian Packages Red Hat Packages ^^^^^^^^^^^^^^^^ -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:fedora?logo=github&style=for-the-badge' +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:fedora?logo=github&style=for-the-badge :alt: GitHub issues by-label #. Download the corresponding `.rpm` file, e.g. ``sunshine-fedora_35.rpm`` @@ -60,7 +60,7 @@ Red Hat Packages MacOS ----- -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:macos?logo=github&style=for-the-badge' +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:macos?logo=github&style=for-the-badge :alt: GitHub issues by-label #. Install `MacPorts `_ @@ -71,10 +71,10 @@ MacOS Windows ------- -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:10?logo=github&style=for-the-badge' +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:10?logo=github&style=for-the-badge :alt: GitHub issues by-label -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:11?logo=github&style=for-the-badge' +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:11?logo=github&style=for-the-badge :alt: GitHub issues by-label #. Download and extract ``sunshine-windows.zip`` From f36d81954baf724ea8d4f0b5f9d963ef03b971f9 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 19 Apr 2022 20:38:06 -0400 Subject: [PATCH 216/817] Update rpm install commands --- docs/source/about/installation.rst | 2 +- docs/source/building/linux.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 12813cef..d2b9d877 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -43,7 +43,7 @@ Red Hat Packages :alt: GitHub issues by-label #. Download the corresponding `.rpm` file, e.g. ``sunshine-fedora_35.rpm`` -#. ``sudo rpm -i ``, e.g. ``sudo rpm -i ./sunshine-fedora_35.rpm`` +#. ``sudo dnf install ``, e.g. ``sudo dnf install ./sunshine-fedora_35.rpm`` .. Hint:: If this is the first time installing. diff --git a/docs/source/building/linux.rst b/docs/source/building/linux.rst index a519761a..a6e7eefb 100644 --- a/docs/source/building/linux.rst +++ b/docs/source/building/linux.rst @@ -297,4 +297,4 @@ Instructions Red Hat .. code-block:: bash - sudo rpm -i -f sunshine-build/sunshine.rpm + sudo dnf install sunshine-build/sunshine.rpm From 7a1e5f43d9e4fb622585af9809d8acf8e05bacdc Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 23 Apr 2022 12:35:39 -0400 Subject: [PATCH 217/817] Workflow updates - Do not re-run PR tests on edited PRs - Close added/fixed issues on published release - Issues stale after 60 days instead of 30, close after 10 days instead of 5 - Use Vankka/pr-target-branch-action for checking that PR is made to proper branch - Add version number to sphinx config, must use cmake to configure the file - Add jobs to readthedocs.yaml configuration --- .github/workflows/CI.yml | 2 +- .github/workflows/clang.yml | 2 +- .github/workflows/issues-closer.yml | 21 ++++++++++++++++++ .github/workflows/issues-stale.yml | 4 ++-- .github/workflows/pull-requests.yml | 34 +++++++++-------------------- .readthedocs.yaml | 5 +++++ CMakeLists.txt | 1 + docs/source/{conf.py => conf.py.in} | 4 ++-- 8 files changed, 43 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/issues-closer.yml rename docs/source/{conf.py => conf.py.in} (97%) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index da3cc6ed..b0be1130 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -3,7 +3,7 @@ name: CI on: pull_request: branches: [master, nightly] - types: [opened, synchronize, edited, reopened] + types: [opened, synchronize, reopened] push: branches: [master] workflow_dispatch: diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index f096c5f9..fd62d261 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -3,7 +3,7 @@ name: clang-format-lint on: pull_request: branches: [master, nightly] - types: [opened, synchronize, edited, reopened] + types: [opened, synchronize, reopened] jobs: lint: diff --git a/.github/workflows/issues-closer.yml b/.github/workflows/issues-closer.yml new file mode 100644 index 00000000..943bae37 --- /dev/null +++ b/.github/workflows/issues-closer.yml @@ -0,0 +1,21 @@ +name: Close Added/Fixed Issues + +on: + release: + types: [published] + +jobs: + close_issues: + name: Check Issues / PRs + runs-on: ubuntu-latest + steps: + - name: Close Issues (added/fixed) + uses: actions/stale@v3 + with: + only-issues-labels: 'added,fixed' + close-issue-message: > + This is now available in the latest release. + close-issue-label: 'released' + days-before-issue-stale: 0 + days-before-issue-close: 0 + ignore-updates: true diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml index 1c40c45d..233582bd 100644 --- a/.github/workflows/issues-stale.yml +++ b/.github/workflows/issues-stale.yml @@ -26,8 +26,8 @@ jobs: This PR was closed because it has been stalled for 5 days with no activity. stale-pr-label: 'stale' exempt-pr-labels: 'status:in-progress' - days-before-stale: 30 - days-before-close: 5 + days-before-stale: 60 + days-before-close: 10 - name: Invalid Template uses: actions/stale@v5 diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index ad014e6d..36f597b9 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -5,31 +5,17 @@ on: types: [opened, synchronize, edited, reopened] jobs: - check-branch: + check-pull-request: name: Check Pull Request runs-on: ubuntu-latest steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Branch check - if: ( github.head_ref == 'repo-sync/common-repo-files/default' && github.base_ref == 'master' ) || ( github.head_ref == 'nightly' && github.base_ref == 'master' ) - run: | - echo Base: "$GITHUB_BASE_REF" - echo Head: "$GITHUB_HEAD_REF" - echo "branch=True" >> $GITHUB_ENV - - - name: Comment on Pull Request - uses: mshick/add-pr-comment@v1 - if: github.base_ref != 'nightly' && env.branch != 'True' + - uses: Vankka/pr-target-branch-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - message: Pull requests must be made to the `nightly` branch. Thanks. - repo-token: ${{ secrets.GITHUB_TOKEN }} - repo-token-user-login: 'github-actions[bot]' - - - name: Fail Workflow - if: github.base_ref != 'nightly' && env.branch != 'True' - run: | - echo Base: "$GITHUB_BASE_REF" - echo Head: "$GITHUB_HEAD_REF" - exit 1 + target: master + exclude: nightly # Don't prevent going from nightly -> master + change-to: nightly + comment: | + Your PR was set to `master`, PRs should be sent to `nightly` + The base branch of this PR has been automatically changed to `nightly`, please check that there are no merge conflicts diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 78c304f8..5f62d86b 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,6 +10,11 @@ build: os: ubuntu-20.04 tools: python: "3.9" + jobs: + post_system_dependencies: + - apt-get install cmake + pre_create_environment: + - cmake .. # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/CMakeLists.txt b/CMakeLists.txt index 862e4f1a..dcbd6942 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -319,6 +319,7 @@ else() endif() configure_file(version.h.in version.h @ONLY) +configure_file(docs/source/conf.py.in "${CMAKE_CURRENT_SOURCE_DIR}/docs/source/conf.py" @ONLY) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(SUNSHINE_TARGET_FILES diff --git a/docs/source/conf.py b/docs/source/conf.py.in similarity index 97% rename from docs/source/conf.py rename to docs/source/conf.py.in index 0375d42b..99ce7143 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py.in @@ -26,7 +26,7 @@ author = 'ReenigneArcher' # The full version, including alpha/beta/rc tags -# version = '0.13.0' +version = '@PROJECT_VERSION@' # -- General configuration --------------------------------------------------- @@ -60,7 +60,7 @@ # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] -html_logo = os.path.join(root_dir, 'sunshine.ico') +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. From 4cd1014bacdf7f147d77cca5de2ebd374992b7cd Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 23 Apr 2022 12:40:54 -0400 Subject: [PATCH 218/817] Use apt_packages to install cmake --- .readthedocs.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 5f62d86b..c7d0ed50 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,11 +10,11 @@ build: os: ubuntu-20.04 tools: python: "3.9" + apt_packages: + - cmake jobs: - post_system_dependencies: - - apt-get install cmake - pre_create_environment: - - cmake .. + pre_build: + - cmake -Wno-dev . # Build documentation in the docs/ directory with Sphinx sphinx: From b332633b074c9d7ec02b227438af7bcc2f4e4483 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 23 Apr 2022 13:09:18 -0400 Subject: [PATCH 219/817] Add submodules --- .readthedocs.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index c7d0ed50..ad158de4 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -14,7 +14,12 @@ build: - cmake jobs: pre_build: - - cmake -Wno-dev . + - cmake . + +# Include the submodules, required for cmake +submodules: + include: all + recursive: true # Build documentation in the docs/ directory with Sphinx sphinx: From 780339d91bddf923130ea80396fa8739307f369a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 23 Apr 2022 13:16:21 -0400 Subject: [PATCH 220/817] Add boost dependencies --- .readthedocs.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index ad158de4..87870397 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -12,6 +12,9 @@ build: python: "3.9" apt_packages: - cmake + - libboost-filesystem-dev + - libboost-log-dev + - libboost-thread-dev jobs: pre_build: - cmake . From 521335c387d68e58c85572278c95ed1798be0fe5 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 23 Apr 2022 16:20:06 -0400 Subject: [PATCH 221/817] Add ffmpeg dependency --- .readthedocs.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 87870397..a5997812 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -12,6 +12,7 @@ build: python: "3.9" apt_packages: - cmake + - ffmpeg - libboost-filesystem-dev - libboost-log-dev - libboost-thread-dev From b286c061449715aef215ad8b47d895bfe612ef2e Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 23 Apr 2022 20:50:42 -0400 Subject: [PATCH 222/817] Fix localize `git diff` and `git reset` steps --- .github/workflows/localize.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index b860ad64..a017efab 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -4,6 +4,7 @@ on: push: branches: [nightly] paths: # prevents workflow from running unless these files change + - '.github/workflows/localize.yml' - 'sunshine/**' - 'locale/sunshine.po' workflow_dispatch: @@ -40,15 +41,18 @@ jobs: - name: git diff run: | + # disable the pager + git config --global pager.diff false + # print the git diff - git diff --exit-code locale/sunshine.po + git diff locale/sunshine.po # set the variable with minimal output - OUTPUT=$(git diff --exit-code --numstat locale/sunshine.po) + OUTPUT=$(git diff --numstat locale/sunshine.po) echo "git_diff=${OUTPUT}" >> $GITHUB_ENV - name: git reset - if: ${{ env.git_diff != '1 1 locale/sunshine.po' }} # only run if more than 1 line changed + if: ${{ env.git_diff == '1 1 locale/sunshine.po' }} # only run if more than 1 line changed run: | git reset --hard From ef9abf2f159917c55e3f87ec54709351cdd77525 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 28 Apr 2022 18:06:55 -0400 Subject: [PATCH 223/817] Get version number from CMakeLists --- .github/workflows/issues-closer.yml | 21 ------------------- .readthedocs.yaml | 32 ++++++++++++++++------------- CMakeLists.txt | 1 - docs/source/{conf.py.in => conf.py} | 16 +++++++++++---- 4 files changed, 30 insertions(+), 40 deletions(-) delete mode 100644 .github/workflows/issues-closer.yml rename docs/source/{conf.py.in => conf.py} (84%) diff --git a/.github/workflows/issues-closer.yml b/.github/workflows/issues-closer.yml deleted file mode 100644 index 943bae37..00000000 --- a/.github/workflows/issues-closer.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Close Added/Fixed Issues - -on: - release: - types: [published] - -jobs: - close_issues: - name: Check Issues / PRs - runs-on: ubuntu-latest - steps: - - name: Close Issues (added/fixed) - uses: actions/stale@v3 - with: - only-issues-labels: 'added,fixed' - close-issue-message: > - This is now available in the latest release. - close-issue-label: 'released' - days-before-issue-stale: 0 - days-before-issue-close: 0 - ignore-updates: true diff --git a/.readthedocs.yaml b/.readthedocs.yaml index a5997812..762371f8 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,20 +10,24 @@ build: os: ubuntu-20.04 tools: python: "3.9" - apt_packages: - - cmake - - ffmpeg - - libboost-filesystem-dev - - libboost-log-dev - - libboost-thread-dev - jobs: - pre_build: - - cmake . - -# Include the submodules, required for cmake -submodules: - include: all - recursive: true + +## apt packages required packages to run cmake on sunshine, note that additional packages are required +# apt_packages: +# - cmake +# - ffmpeg +# - libboost-filesystem-dev +# - libboost-log-dev +# - libboost-thread-dev + +## run cmake +# jobs: +# pre_build: +# - cmake . + +## Include the submodules, required for cmake +#submodules: +# include: all +# recursive: true # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/CMakeLists.txt b/CMakeLists.txt index dcbd6942..862e4f1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -319,7 +319,6 @@ else() endif() configure_file(version.h.in version.h @ONLY) -configure_file(docs/source/conf.py.in "${CMAKE_CURRENT_SOURCE_DIR}/docs/source/conf.py" @ONLY) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(SUNSHINE_TARGET_FILES diff --git a/docs/source/conf.py.in b/docs/source/conf.py similarity index 84% rename from docs/source/conf.py.in rename to docs/source/conf.py index 99ce7143..86b7b805 100644 --- a/docs/source/conf.py.in +++ b/docs/source/conf.py @@ -6,6 +6,8 @@ # standard imports from datetime import datetime +import os +import re # -- Path setup -------------------------------------------------------------- @@ -13,8 +15,6 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -import os -# import sys script_dir = os.path.dirname(os.path.abspath(__file__)) # the directory of this file source_dir = os.path.dirname(script_dir) # the source folder directory @@ -26,8 +26,16 @@ author = 'ReenigneArcher' # The full version, including alpha/beta/rc tags -version = '@PROJECT_VERSION@' - +with open(os.path.join(root_dir, 'CMakeLists.txt'), 'r') as f: + version = re.search(r"project\(Sunshine VERSION ((\d+)\.(\d+)\.(\d+))\)", str(f.read())).group(1) +""" +To use cmake method for obtaining version instead of regex, +1. Within CMakeLists.txt add the following line without backticks: + ``configure_file(docs/source/conf.py.in "${CMAKE_CURRENT_SOURCE_DIR}/docs/source/conf.py" @ONLY)`` +2. Rename this file to ``conf.py.in`` +3. Uncomment the next line +""" +# version = '@PROJECT_VERSION@' # use this for cmake configure_file method # -- General configuration --------------------------------------------------- From 734400dc779de3ff8c2595e192b73d478f0e2289 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 28 Apr 2022 18:20:53 -0400 Subject: [PATCH 224/817] Remove white background from png logo --- sunshine.png | Bin 14699 -> 20640 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/sunshine.png b/sunshine.png index 77f4795673e5bc0e5c828e4b37f26a6d8c05105d..785bc479b9655ce9dd158be2f045c25d9e53f137 100644 GIT binary patch literal 20640 zcmZ5nRa{h08(vaa>29Q?Q@XpQyIVR0X;`|uOF&9eLZo5I1w^E!8Fffeb&267Yu@qSI1Li)3f9-=8zEzyrsxq!s{-J zz{bN%dkh<`6S_vg@9zBXk513ldxPvHyVlM&db>HV>#hrpg{g32hqgWce@JPIxw52A zdE2}wy~;g}gmy4yFgLS_jYZbY3jQU zn5gLqdN!sR`jObQdpp_?phtI{T1chVPf(u$@O39RX|yQ~xPwh|t@J3vaV53yi24<< z!rFTEnMF_OFHu-H0!3nnPe;9ADPzri4#slg|>5|xTD+BBs3jv`qqWNqemP%EWSkQcd!O-E5< zB+F7j>qP zCX%KcQCs00!1Fm093=7CAa&{67|K~y=}MqC{*9{#RYRF9iD0O;#xz_UZ>z1k<>(ix zB}=qlEyrWJAd`!FAZ~F2J|b`Y#?p7WQSDk^v**HO+(S*8h`gs5o+? z4LP$zbWLwpT5(K{_4n$b&W20#woUm+rKT)uF)~aF_>8=4z#BY%>&GHrEM14=FIAW` zOBH$QkGFaOO=!m7b;C#Z_M!oOni9S{+GR58qlHp*1faxFJmp+Jn!4I_3go;3HLc4q!v z3Z(uKGg{5oRKbzyLBiIaj8;@@($AvYUzxFW@r?oZ%acuksSd18UX<=HA7g3h@xYHY55> zeI88^`!NK>7A3*56vDbHAb=@@Xa9yA)0pB4h&4o>46aL&pcxt~x77dlJBKDlunwaL z+0lM_uIzXPxeee@J3VA{*Y{Y<@9=)%>*>t~M@)BNR zITd4A;RCo&wRu4w}^$F z6B4ZGg7cPh7N1>znBqN3Ne4mRkaf(BBIs;b6L9eZDt!(iz&=}O8{BG)Nq z+iRSK1&ion=35x(_4=cf_Qk)tjil4pm)7QL9ym&d>iq);vD1iSdr6 z`+Kv6%7j3Up93DH8&b4|PLQS61ww`NMx+bMT_<9}I%V+d3l?FQ6|)%5=78W5THqAJwH(82)nU`Tiw1=n=sT#de zTi-yILuucol8^bzh@p8%0xknpuu98puDde#d

      Ham7aEv9?}_6 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 tMkvIoS9c?+KHU?(UL Date: Thu, 28 Apr 2022 17:38:20 -0700 Subject: [PATCH 225/817] Add RPMFusion/FFMPEG dependancy. --- gen-rpm.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gen-rpm.in b/gen-rpm.in index e2bec8f4..792a3895 100755 --- a/gen-rpm.in +++ b/gen-rpm.in @@ -44,7 +44,7 @@ License: GPLv3 URL: https://github.com/SunshineStream/Sunshine Source0: sunshine-@PROJECT_VERSION@_bin.tar.gz -Requires: systemd +Requires: systemd ffmpeg rpmfusion-free-release %description An NVIDIA Gamestream-compatible hosting server From 3d6611fd50d1eb95732ab6f4306cd57a572ab735 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 28 Apr 2022 22:51:02 -0400 Subject: [PATCH 226/817] Update help argument --- sunshine/main.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sunshine/main.cpp b/sunshine/main.cpp index bcab4b3b..c78c897f 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -61,17 +61,19 @@ void print_help(const char *name) { std::cout << "Usage: "sv << name << " [options] [/path/to/configuration_file] [--cmd]"sv << std::endl << " Any configurable option can be overwritten with: \"name=value\""sv << std::endl + << std:endl + << " Note: The configuration will be created if it doesn't exist."sv << std::endl << std::endl << " --help | print help"sv << std::endl - << " --creds username password | set user credentials for the Web manager" << std::endl - << " --version | print the version of sunshine" << std::endl + << " --creds username password | set user credentials for the Web manager"sv << std::endl + << " --version | print the version of sunshine"sv << std::endl << std::endl << " flags"sv << std::endl << " -0 | Read PIN from stdin"sv << std::endl << " -1 | Do not load previously saved state and do retain any state after shutdown"sv << std::endl << " | Effectively starting as if for the first time without overwriting any pairings with your devices"sv << std::endl - << " -2 | Force replacement of headers in video stream" << std::endl - << " -p | Enable/Disable UPnP" << std::endl + << " -2 | Force replacement of headers in video stream"sv << std::endl + << " -p | Enable/Disable UPnP"sv << std::endl << std::endl; } From a9cf0ebf188095bac103adb12b3ed2d0acd1dbb4 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 28 Apr 2022 23:18:58 -0400 Subject: [PATCH 227/817] Remove unused `name` variable - Add documentation blocks --- sunshine/main.cpp | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/sunshine/main.cpp b/sunshine/main.cpp index c78c897f..93bd40aa 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -57,11 +57,15 @@ struct NoDelete { BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", int) -void print_help(const char *name) { +/** Print the help to stdout. + + This function prints output to stdout. +*/ +void print_help() { std::cout - << "Usage: "sv << name << " [options] [/path/to/configuration_file] [--cmd]"sv << std::endl + << "Usage: "sv << PROJECT_NAME << " [options] [/path/to/configuration_file] [--cmd]"sv << std::endl << " Any configurable option can be overwritten with: \"name=value\""sv << std::endl - << std:endl + << std::endl << " Note: The configuration will be created if it doesn't exist."sv << std::endl << std::endl << " --help | print help"sv << std::endl @@ -77,15 +81,23 @@ void print_help(const char *name) { << std::endl; } +/** Call the print_help function. + + Calls the print_help function and then exits. +*/ namespace help { -int entry(const char *name, int argc, char *argv[]) { - print_help(name); +int entry(int argc, char *argv[]) { + print_help(); return 0; } } // namespace help +/** Print the version details to stdout. + + This function prints the version details to stdout and then exits. +*/ namespace version { -int entry(const char *name, int argc, char *argv[]) { +int entry(int argc, char *argv[]) { std::cout << PROJECT_NAME << " version: v" << PROJECT_VER << std::endl; return 0; } @@ -108,9 +120,9 @@ void on_signal(int sig, FN &&fn) { } namespace gen_creds { -int entry(const char *name, int argc, char *argv[]) { +int entry(int argc, char *argv[]) { if(argc < 2 || argv[0] == "help"sv || argv[1] == "help"sv) { - print_help(name); + print_help(); return 0; } @@ -120,7 +132,7 @@ int entry(const char *name, int argc, char *argv[]) { } } // namespace gen_creds -std::map> cmd_to_func { +std::map> cmd_to_func { { "creds"sv, gen_creds::entry }, { "help"sv, help::entry }, { "version"sv, version::entry } @@ -328,4 +340,4 @@ int write_file(const char *path, const std::string_view &contents) { std::uint16_t map_port(int port) { return (std::uint16_t)((int)config::sunshine.port + port); -} \ No newline at end of file +} From ced0029abc479d49263f6de34b1a81b812d4631e Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 28 Apr 2022 23:47:05 -0400 Subject: [PATCH 228/817] Remove unused argument --- sunshine/config.cpp | 6 +++--- sunshine/main.cpp | 2 +- sunshine/main.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 6aaeb305..022df5e2 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -863,7 +863,7 @@ int parse(int argc, char *argv[]) { auto line = argv[x]; if(line == "--help"sv) { - print_help(*argv); + print_help(); return 1; } else if(*line == '-') { @@ -875,7 +875,7 @@ int parse(int argc, char *argv[]) { break; } if(apply_flags(line + 1)) { - print_help(*argv); + print_help(); return -1; } } @@ -889,7 +889,7 @@ int parse(int argc, char *argv[]) { else { TUPLE_EL(var, 1, parse_option(line, line_end)); if(!var) { - print_help(*argv); + print_help(); return -1; } diff --git a/sunshine/main.cpp b/sunshine/main.cpp index 93bd40aa..5a52f4aa 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -229,7 +229,7 @@ int main(int argc, char *argv[]) { return 7; } - return fn->second(argv[0], config::sunshine.cmd.argc, config::sunshine.cmd.argv); + return fn->second(config::sunshine.cmd.argc, config::sunshine.cmd.argv); } task_pool.start(1); diff --git a/sunshine/main.h b/sunshine/main.h index aa9558b3..48cbc62f 100644 --- a/sunshine/main.h +++ b/sunshine/main.h @@ -24,7 +24,7 @@ extern boost::log::sources::severity_logger fatal; void log_flush(); -void print_help(const char *name); +void print_help(); std::string read_file(const char *path); int write_file(const char *path, const std::string_view &contents); From e8ef7080347d452019e3b37d998e6cd3d0680771 Mon Sep 17 00:00:00 2001 From: Michael Rogers Date: Mon, 11 Apr 2022 18:09:51 -0500 Subject: [PATCH 229/817] Fix virtual sink overriding config sink. --- sunshine/audio.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sunshine/audio.cpp b/sunshine/audio.cpp index 4279eb4c..3cc0e5e0 100644 --- a/sunshine/audio.cpp +++ b/sunshine/audio.cpp @@ -135,9 +135,14 @@ void capture(safe::mail_t mail, config_t config, void *channel_data) { return; } - std::string *sink = - config::audio.sink.empty() ? &ref->sink.host : &config::audio.sink; - if(ref->sink.null) { + // Order of priorty: + // 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: From 62ca9c31a07a7205c8b7d4af367a18cb2a80aff7 Mon Sep 17 00:00:00 2001 From: Michael Rogers Date: Mon, 11 Apr 2022 18:11:06 -0500 Subject: [PATCH 230/817] Updated the linux for better pulse behavior. --- sunshine/platform/linux/audio.cpp | 97 ++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 16 deletions(-) diff --git a/sunshine/platform/linux/audio.cpp b/sunshine/platform/linux/audio.cpp index c210da24..7b6f9c6c 100644 --- a/sunshine/platform/linux/audio.cpp +++ b/sunshine/platform/linux/audio.cpp @@ -65,7 +65,7 @@ struct mic_attr_t : public mic_t { } }; -std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) { +std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, std::string source_name) { auto mic = std::make_unique(); pa_sample_spec ss { PA_SAMPLE_S16LE, sample_rate, (std::uint8_t)channels }; @@ -81,14 +81,9 @@ std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std int status; - const char *audio_sink = "@DEFAULT_MONITOR@"; - if(!config::audio.sink.empty()) { - audio_sink = config::audio.sink.c_str(); - } - mic->mic.reset( pa_simple_new(nullptr, "sunshine", - pa_stream_direction_t::PA_STREAM_RECORD, audio_sink, + pa_stream_direction_t::PA_STREAM_RECORD, source_name.c_str(), "sunshine-record", &ss, &pa_map, &pa_attr, &status)); if(!mic->mic) { @@ -128,6 +123,18 @@ using loop_t = util::safe_ptr; using op_t = util::safe_ptr; using string_t = util::safe_ptr>; +template +using cb_simple_t = std::function i)>; + +template +void cb(ctx_t::pointer ctx, add_const_t i, void *userdata) { + auto &f = *(cb_simple_t *)userdata; + + // Cannot similarly filter on eol here. Unless reported otherwise assume + // we have no need for special filtering like cb? + f(ctx, i); +} + template using cb_t = std::function i, int eol)>; @@ -172,6 +179,7 @@ class server_t : public audio_control_t { public: loop_t loop; ctx_t ctx; + std::string requested_sink; struct { std::uint32_t stereo = PA_INVALID_INDEX; @@ -287,8 +295,6 @@ class server_t : public audio_control_t { sink_t sink; - // If hardware sink with more channels found, set that as host - int channels = 0; // Count of all virtual sinks that are created by us int nullcount = 0; @@ -304,11 +310,6 @@ class server_t : public audio_control_t { return; } - if(sink_info->active_port != nullptr) { - sink.host = sink_info->name; - channels = sink_info->channel_map.channels; - } - // Ensure Sunshine won't create a sink that already exists. if(!std::strcmp(sink_info->name, stereo)) { index.stereo = sink_info->owner_module; @@ -341,8 +342,11 @@ class server_t : public audio_control_t { return std::nullopt; } - if(!channels) { + auto sink_name = get_default_sink_name(); + if(sink_name.empty()) { BOOST_LOG(warning) << "Couldn't find an active sink"sv; + } else { + sink.host = sink_name; } if(index.stereo == PA_INVALID_INDEX) { @@ -382,13 +386,72 @@ class server_t : public audio_control_t { return std::make_optional(std::move(sink)); } + std::string get_default_sink_name() { + std::string sink_name = "@DEFAULT_SINK@"s; + auto alarm = safe::make_alarm(); + + cb_simple_t server_f = [&](ctx_t::pointer ctx, const pa_server_info *server_info) { + if(!server_info) { + BOOST_LOG(error) << "Couldn't get pulseaudio server info: "sv << pa_strerror(pa_context_errno(ctx)); + alarm->ring(-1); + } + + sink_name = server_info->default_sink_name; + alarm->ring(0); + }; + + op_t server_op { pa_context_get_server_info(ctx.get(), cb, &server_f) }; + alarm->wait(); + // No need to check status. If it failed just return default name. + return sink_name; + } + + std::string get_monitor_name(const std::string &sink_name) { + std::string monitor_name = "@DEFAULT_MONITOR@"s; + auto alarm = safe::make_alarm(); + + cb_t sink_f = [&](ctx_t::pointer ctx, const pa_sink_info *sink_info, int eol) { + if(!sink_info) { + if(!eol) { + BOOST_LOG(error) << "Couldn't get pulseaudio sink info for ["sv << sink_name + << "]: "sv << pa_strerror(pa_context_errno(ctx)); + alarm->ring(-1); + } + + alarm->ring(0); + return; + } + + monitor_name = sink_info->monitor_source_name; + }; + + op_t sink_op { pa_context_get_sink_info_by_name(ctx.get(), sink_name.c_str(), cb, &sink_f) }; + + alarm->wait(); + // No need to check status. If it failed just return default name. + BOOST_LOG(info) << "Found default monitor by name: "sv << monitor_name; + return monitor_name; + } + std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override { - return ::platf::microphone(mapping, channels, sample_rate, frame_size); + // Sink choice priority: + // 1. Config sink + // 2. Last sink swapped to (Usually virtual in this case) + // 3. Default Sink + // An attempt was made to always use default to match the switching mechanic, + // but this happens right after the swap so the default returned by PA was not + // the new one just set! + auto sink_name = config::audio.sink; + if(sink_name.empty()) sink_name = requested_sink; + if(sink_name.empty()) sink_name = get_default_sink_name(); + + return ::platf::microphone(mapping, channels, sample_rate, frame_size, get_monitor_name(sink_name)); } int set_sink(const std::string &sink) override { auto alarm = safe::make_alarm(); + BOOST_LOG(info) << "Setting default sink to: ["sv << sink << "]"sv; op_t op { pa_context_set_default_sink( ctx.get(), sink.c_str(), success_cb, alarm.get()), @@ -406,6 +469,8 @@ class server_t : public audio_control_t { return -1; } + requested_sink = sink; + return 0; } From bd033f9e1515bda072d48c0006598f2b21521ab7 Mon Sep 17 00:00:00 2001 From: Michael Rogers Date: Mon, 11 Apr 2022 18:12:11 -0500 Subject: [PATCH 231/817] Fixed formatting. --- sunshine/platform/linux/audio.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sunshine/platform/linux/audio.cpp b/sunshine/platform/linux/audio.cpp index 7b6f9c6c..2770e515 100644 --- a/sunshine/platform/linux/audio.cpp +++ b/sunshine/platform/linux/audio.cpp @@ -345,7 +345,8 @@ class server_t : public audio_control_t { auto sink_name = get_default_sink_name(); if(sink_name.empty()) { BOOST_LOG(warning) << "Couldn't find an active sink"sv; - } else { + } + else { sink.host = sink_name; } @@ -438,13 +439,13 @@ class server_t : public audio_control_t { // 1. Config sink // 2. Last sink swapped to (Usually virtual in this case) // 3. Default Sink - // An attempt was made to always use default to match the switching mechanic, - // but this happens right after the swap so the default returned by PA was not + // An attempt was made to always use default to match the switching mechanic, + // but this happens right after the swap so the default returned by PA was not // the new one just set! auto sink_name = config::audio.sink; if(sink_name.empty()) sink_name = requested_sink; if(sink_name.empty()) sink_name = get_default_sink_name(); - + return ::platf::microphone(mapping, channels, sample_rate, frame_size, get_monitor_name(sink_name)); } From 63a83cdf7a43a35c15c0556733ab43e4372e3a9e Mon Sep 17 00:00:00 2001 From: Michael Rogers Date: Mon, 11 Apr 2022 19:02:47 -0500 Subject: [PATCH 232/817] Fix formatting --- sunshine/audio.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sunshine/audio.cpp b/sunshine/audio.cpp index 3cc0e5e0..2894d128 100644 --- a/sunshine/audio.cpp +++ b/sunshine/audio.cpp @@ -135,14 +135,15 @@ void capture(safe::mail_t mail, config_t config, void *channel_data) { return; } - // Order of priorty: + // Order of priorty: // 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) { + } + else if(ref->sink.null) { auto &null = *ref->sink.null; switch(stream->channelCount) { case 2: From aa46b8e293f065946df84238c71b7347e347e7db Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 29 Apr 2022 12:52:09 -0400 Subject: [PATCH 233/817] Revert removing `name` argument from `print_help` function - The existing method is better because it uses the binary name instead of the project name `Sunshine`. --- sunshine/config.cpp | 6 +++--- sunshine/main.cpp | 18 +++++++++--------- sunshine/main.h | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 022df5e2..6aaeb305 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -863,7 +863,7 @@ int parse(int argc, char *argv[]) { auto line = argv[x]; if(line == "--help"sv) { - print_help(); + print_help(*argv); return 1; } else if(*line == '-') { @@ -875,7 +875,7 @@ int parse(int argc, char *argv[]) { break; } if(apply_flags(line + 1)) { - print_help(); + print_help(*argv); return -1; } } @@ -889,7 +889,7 @@ int parse(int argc, char *argv[]) { else { TUPLE_EL(var, 1, parse_option(line, line_end)); if(!var) { - print_help(); + print_help(*argv); return -1; } diff --git a/sunshine/main.cpp b/sunshine/main.cpp index 5a52f4aa..0f5581d6 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -61,9 +61,9 @@ BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", int) This function prints output to stdout. */ -void print_help() { +void print_help(const char *name) { std::cout - << "Usage: "sv << PROJECT_NAME << " [options] [/path/to/configuration_file] [--cmd]"sv << std::endl + << "Usage: "sv << name << " [options] [/path/to/configuration_file] [--cmd]"sv << std::endl << " Any configurable option can be overwritten with: \"name=value\""sv << std::endl << std::endl << " Note: The configuration will be created if it doesn't exist."sv << std::endl @@ -86,8 +86,8 @@ void print_help() { Calls the print_help function and then exits. */ namespace help { -int entry(int argc, char *argv[]) { - print_help(); +int entry(const char *name, int argc, char *argv[]) { + print_help(name); return 0; } } // namespace help @@ -97,7 +97,7 @@ int entry(int argc, char *argv[]) { This function prints the version details to stdout and then exits. */ namespace version { -int entry(int argc, char *argv[]) { +int entry(const char *name, int argc, char *argv[]) { std::cout << PROJECT_NAME << " version: v" << PROJECT_VER << std::endl; return 0; } @@ -120,9 +120,9 @@ void on_signal(int sig, FN &&fn) { } namespace gen_creds { -int entry(int argc, char *argv[]) { +int entry(const char *name, int argc, char *argv[]) { if(argc < 2 || argv[0] == "help"sv || argv[1] == "help"sv) { - print_help(); + print_help(name); return 0; } @@ -132,7 +132,7 @@ int entry(int argc, char *argv[]) { } } // namespace gen_creds -std::map> cmd_to_func { +std::map> cmd_to_func { { "creds"sv, gen_creds::entry }, { "help"sv, help::entry }, { "version"sv, version::entry } @@ -229,7 +229,7 @@ int main(int argc, char *argv[]) { return 7; } - return fn->second(config::sunshine.cmd.argc, config::sunshine.cmd.argv); + return fn->second(argv[0], config::sunshine.cmd.argc, config::sunshine.cmd.argv); } task_pool.start(1); diff --git a/sunshine/main.h b/sunshine/main.h index 48cbc62f..aa9558b3 100644 --- a/sunshine/main.h +++ b/sunshine/main.h @@ -24,7 +24,7 @@ extern boost::log::sources::severity_logger fatal; void log_flush(); -void print_help(); +void print_help(const char *name); std::string read_file(const char *path); int write_file(const char *path, const std::string_view &contents); From b0a02a5985828952c94eedfd8f7108e44d1b7aec Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 29 Apr 2022 19:02:10 -0400 Subject: [PATCH 234/817] Do not fail workflow if `sunshine.po` doesn't exist - Add proper keywords for boost::locale - Minor documentation updates about localization --- .github/workflows/localize.yml | 17 ++++++- docs/source/contributing/localization.rst | 6 ++- scripts/_locale.py | 56 ++++++++++++++--------- 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index a017efab..31f22997 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -9,6 +9,9 @@ on: - 'locale/sunshine.po' workflow_dispatch: +env: + file: ./locale/sunshine.po + jobs: localize: name: Update Localization @@ -37,9 +40,20 @@ jobs: - name: Update Strings run: | + # first, try to remove existing file as xgettext does not remove unused translations + if [ -f "${{ env.file }}" ]; + then + rm ${{ env.file }} + echo "new_file=false" >> $GITHUB_ENV + else + echo "new_file=true" >> $GITHUB_ENV + fi + + # extract the new strings python ./scripts/_locale.py --extract - name: git diff + if: ${{ env.new_file == 'false' }} run: | # disable the pager git config --global pager.diff false @@ -52,7 +66,8 @@ jobs: echo "git_diff=${OUTPUT}" >> $GITHUB_ENV - name: git reset - if: ${{ env.git_diff == '1 1 locale/sunshine.po' }} # only run if more than 1 line changed + # only run if a single line changed (date/time) and file already existed + if: ${{ env.git_diff == '1 1 locale/sunshine.po' && env.new_file == 'false' }} run: | git reset --hard diff --git a/docs/source/contributing/localization.rst b/docs/source/contributing/localization.rst index 721ebc61..8127eda3 100644 --- a/docs/source/contributing/localization.rst +++ b/docs/source/contributing/localization.rst @@ -54,6 +54,9 @@ situations. For example if a system tray icon is added it should be localized as #include boost::locale::translate("Hello world!") + .. Tip:: More examples can be found in the documentation for + `boost locale `_. + .. Warning:: This is for information only. Contributors should never include manually updated template files, or manually compiled language files in Pull Requests. @@ -67,7 +70,8 @@ any of the following paths are modified. - 'sunshine/**' When testing locally it may be desirable to manually extract, initialize, update, and compile strings. Python is -required for this, along with the dependencies in the `./scripts/requirements.txt` file. +required for this, along with the python dependencies in the `./scripts/requirements.txt` file. Additionally, +`xgettext `_ must be installed. Extract, initialize, and update .. code-block:: bash diff --git a/scripts/_locale.py b/scripts/_locale.py index e47887cc..339e2d4a 100644 --- a/scripts/_locale.py +++ b/scripts/_locale.py @@ -13,6 +13,7 @@ import subprocess project_name = 'Sunshine' +project_owner = 'SunshineStream' script_dir = os.path.dirname(os.path.abspath(__file__)) root_dir = os.path.dirname(script_dir) @@ -37,22 +38,30 @@ def x_extract(): """Executes `xgettext extraction` in subprocess.""" + pot_filepath = os.path.join(locale_dir, f'{project_name.lower()}.po') + commands = [ 'xgettext', + '--keyword=translate:1,1t', + '--keyword=translate:1c,2,2t', + '--keyword=translate:1,2,3t', + '--keyword=translate:1c,2,3,4t', + '--keyword=gettext:1', + '--keyword=pgettext:1c,2', + '--keyword=ngettext:1,2', + '--keyword=npgettext:1c,2,3', f'--default-domain={project_name.lower()}', - f'--output={os.path.join(locale_dir, project_name.lower() + ".po")}', + f'--output={pot_filepath}', '--language=C++', '--boost', '--from-code=utf-8', '-F', - f'--msgid-bugs-address=github.com/{project_name.lower()}', - f'--copyright-holder={project_name}', + f'--msgid-bugs-address=github.com/{project_owner.lower()}/{project_name.lower()}', + f'--copyright-holder={project_owner}', f'--package-name={project_name}', '--package-version=v0' ] - pot_filepath = os.path.join(locale_dir, f'{project_name.lower()}.po') - extensions = ['cpp', 'h', 'm', 'mm'] # find input files @@ -66,23 +75,26 @@ def x_extract(): print(commands) subprocess.check_output(args=commands, cwd=root_dir) - # fix header - body = "" - with open(file=pot_filepath, mode='r') as file: - for line in file.readlines(): - if line != '"Language: \\n"\n': # do not include this line - if line == '# SOME DESCRIPTIVE TITLE.\n': - body += f'# Translations template for {project_name}.\n' - elif line.startswith('#') and 'YEAR' in line: - body += line.replace('YEAR', str(year)) - elif line.startswith('#') and 'PACKAGE' in line: - body += line.replace('PACKAGE', project_name) - else: - body += line - - # rewrite pot file with updated header - with open(file=pot_filepath, mode='w+') as file: - file.write(body) + try: + # fix header + body = "" + with open(file=pot_filepath, mode='r') as file: + for line in file.readlines(): + if line != '"Language: \\n"\n': # do not include this line + if line == '# SOME DESCRIPTIVE TITLE.\n': + body += f'# Translations template for {project_name}.\n' + elif line.startswith('#') and 'YEAR' in line: + body += line.replace('YEAR', str(year)) + elif line.startswith('#') and 'PACKAGE' in line: + body += line.replace('PACKAGE', project_name) + else: + body += line + + # rewrite pot file with updated header + with open(file=pot_filepath, mode='w+') as file: + file.write(body) + except FileNotFoundError: + pass def babel_init(locale_code: str): From c4977b5393e6a266467bb9261b7866f6384cd798 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Sat, 23 Apr 2022 10:48:05 +0100 Subject: [PATCH 235/817] WIP: Moving to cpack in order to unify installers across all platforms --- CMakeLists.txt | 62 ++++++++++++- assets/linux-deb/conffiles | 2 + assets/linux-deb/postinst | 41 +++++++++ assets/linux-deb/preinst | 9 ++ gen-deb.in | 123 ------------------------- gen-rpm.in | 179 ------------------------------------- 6 files changed, 112 insertions(+), 304 deletions(-) create mode 100644 assets/linux-deb/conffiles create mode 100644 assets/linux-deb/postinst create mode 100644 assets/linux-deb/preinst delete mode 100755 gen-deb.in delete mode 100755 gen-rpm.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 862e4f1a..291a6f2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -312,8 +312,6 @@ else() if(NOT DEFINED SUNSHINE_EXECUTABLE_PATH) set(SUNSHINE_EXECUTABLE_PATH "sunshine") endif() - configure_file(gen-deb.in gen-deb @ONLY) - configure_file(gen-rpm.in gen-rpm @ONLY) configure_file(sunshine.desktop.in sunshine.desktop @ONLY) configure_file(sunshine.service.in sunshine.service @ONLY) endif() @@ -448,3 +446,63 @@ foreach(flag IN LISTS SUNSHINE_COMPILE_OPTIONS) endforeach() target_compile_options(sunshine PRIVATE $<$:${SUNSHINE_COMPILE_OPTIONS}>;$<$:${SUNSHINE_COMPILE_OPTIONS_CUDA};-std=c++17>) + +############# +# CPACK +#### + +# Add all assets dependencies +install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/web" DESTINATION ".") +install(FILES "${SUNSHINE_ASSETS_DIR}/sunshine.conf" DESTINATION ".") +if(WIN32) # TODO: test + install(TARGETS sunshine RUNTIME DESTINATION ".") + + install(FILES "${SUNSHINE_ASSETS_DIR}/apps_windows.json" DESTINATION ".") + install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/shaders/directx" DESTINATION "shaders") +endif() +if(APPLE) # TODO: test + install(TARGETS sunshine RUNTIME DESTINATION ".") + + install(FILES "${SUNSHINE_ASSETS_DIR}/apps_mac.json" DESTINATION ".") + # TODO: info.plist ?? +endif() +if(UNIX AND NOT APPLE) + install(FILES "${SUNSHINE_ASSETS_DIR}/apps_linux.json" DESTINATION ".") + install(FILES "${SUNSHINE_ASSETS_DIR}/85-sunshine-rules.rules" DESTINATION "/etc/udev/rules.d") + install(TARGETS sunshine RUNTIME DESTINATION "/usr/bin") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "/usr/lib/systemd/user") + install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/shaders/opengl" DESTINATION "shaders") + + # Pre and post install + set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA + "${SUNSHINE_ASSETS_DIR}/linux-deb/preinst;${SUNSHINE_ASSETS_DIR}/linux-deb/postinst;${SUNSHINE_ASSETS_DIR}/linux-deb/conffiles") + set(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE "${SUNSHINE_ASSETS_DIR}/linux-deb/preinst") + set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${SUNSHINE_ASSETS_DIR}/linux-deb/postinst") +endif() + +# Common options +set(CPACK_PACKAGE_NAME "SunshineStream") +set(CPACK_PACKAGE_VENDOR "CMake.org") +set(CPACK_PACKAGE_CONTACT "https://github.com/SunshineStream/Sunshine") +set(CPACK_DEBIAN_PACKAGE_MAINTAINER "https://github.com/SunshineStream/Sunshine") +set(CPACK_PACKAGE_DESCRIPTION "Gamestream host for Moonlight") +set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/SunshineStream/Sunshine") +set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE) +set(CPACK_PACKAGE_ICON ${PROJECT_SOURCE_DIR}/sunshine.png) +set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}") +set(CPACK_STRIP_FILES YES) + +# Installation destination dir +if(NOT WIN32) + set(CPACK_SET_DESTDIR true) +endif() +if(UNIX AND NOT APPLE) + set(CMAKE_INSTALL_PREFIX "/etc/sunshine") +endif() + +## DEB +set(CPACK_DEB_COMPONENT_INSTALL ON) +set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl1.1, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") +set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) + +include(CPack) \ No newline at end of file diff --git a/assets/linux-deb/conffiles b/assets/linux-deb/conffiles new file mode 100644 index 00000000..78176a81 --- /dev/null +++ b/assets/linux-deb/conffiles @@ -0,0 +1,2 @@ +/etc/sunshine/sunshine.conf +/etc/sunshine/apps_linux.json diff --git a/assets/linux-deb/postinst b/assets/linux-deb/postinst new file mode 100644 index 00000000..cf66231f --- /dev/null +++ b/assets/linux-deb/postinst @@ -0,0 +1,41 @@ +#!/bin/sh + +export GROUP_INPUT=input + +if [ -f /etc/group ]; then + if ! grep -q $GROUP_INPUT /etc/group; then + echo "Creating group $GROUP_INPUT" + + groupadd $GROUP_INPUT + fi +else + echo "Warning: /etc/group not found" +fi + +if [ -f /etc/sunshine/sunshine.conf.old ]; then + echo "Restoring old sunshine.conf" + mv /etc/sunshine/sunshine.conf.old /etc/sunshine/sunshine.conf +fi + +if [ -f /etc/sunshine/apps_linux.json.old ]; then + echo "Restoring old apps_linux.json" + mv /etc/sunshine/apps_linux.json.old /etc/sunshine/apps_linux.json +fi + +# Update permissions on config files for Web Manager +if [ -f /etc/sunshine/apps_linux.json ]; then + echo "chmod 666 /etc/sunshine/apps_linux.json" + chmod 666 /etc/sunshine/apps_linux.json +fi + +if [ -f /etc/sunshine/sunshine.conf ]; then + echo "chmod 666 /etc/sunshine/sunshine.conf" + chmod 666 /etc/sunshine/sunshine.conf +fi + +# Ensure Sunshine can grab images from KMS +path_to_setcap=$(which setcap) +if [ -x "$path_to_setcap" ] ; then + echo "$path_to_setcap cap_sys_admin+p /usr/bin/sunshine" + $path_to_setcap cap_sys_admin+p $(readlink -f /usr/bin/sunshine) +fi diff --git a/assets/linux-deb/preinst b/assets/linux-deb/preinst new file mode 100644 index 00000000..515d1a34 --- /dev/null +++ b/assets/linux-deb/preinst @@ -0,0 +1,9 @@ +#!/bin/sh +#Store backup for old config files to prevent it from being overwritten +if [ -f /etc/sunshine/sunshine.conf ]; then + cp /etc/sunshine/sunshine.conf /etc/sunshine/sunshine.conf.old +fi + +if [ -f /etc/sunshine/apps_linux.json ]; then + cp /etc/sunshine/apps_linux.json /etc/sunshine/apps_linux.json.old +fi diff --git a/gen-deb.in b/gen-deb.in deleted file mode 100755 index 25ca3609..00000000 --- a/gen-deb.in +++ /dev/null @@ -1,123 +0,0 @@ -#!/bin/sh - -if [ ! "@SUNSHINE_UNDEFINED_VARIABLE@" = "" ]; then - echo "Please run gen-deb generated by cmake inside the build directory" - exit 1 -fi - -if [ -d package-deb ]; then - echo "package-deb already exists: It will be replaced" - rm -rf package-deb -fi - -export DEBIAN=@CMAKE_CURRENT_BINARY_DIR@/package-deb/sunshine/DEBIAN -export RULES=@CMAKE_CURRENT_BINARY_DIR@/package-deb/sunshine/etc/udev/rules.d -export BIN=@CMAKE_CURRENT_BINARY_DIR@/package-deb/sunshine/usr/bin -export SERVICE=@CMAKE_CURRENT_BINARY_DIR@/package-deb/sunshine/usr/lib/systemd/user -export ASSETS=@CMAKE_CURRENT_BINARY_DIR@/package-deb/sunshine/etc/sunshine - -mkdir -p $DEBIAN -mkdir -p $RULES -mkdir -p $BIN -mkdir -p $ASSETS/shaders -mkdir -p $SERVICE - -if [ ! -f sunshine ]; then - echo "Error: Can't find sunshine" - exit 1 -fi - -cat << 'EOF' > $DEBIAN/conffiles -/etc/sunshine/sunshine.conf -/etc/sunshine/apps_linux.json -EOF - -cat << 'EOF' > $DEBIAN/control -Package: sunshine -Architecture: amd64 -Maintainer: @loki -Priority: optional -Version: @PROJECT_VERSION@ -Depends: libssl1.1, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2 -Description: Gamestream host for Moonlight -EOF - -cat << 'EOF' > $DEBIAN/preinst -#Store backup for old config files to prevent it from being overwritten -if [ -f /etc/sunshine/sunshine.conf ]; then - cp /etc/sunshine/sunshine.conf /etc/sunshine/sunshine.conf.old -fi - -if [ -f /etc/sunshine/apps_linux.json ]; then - cp /etc/sunshine/apps_linux.json /etc/sunshine/apps_linux.json.old -fi -EOF - -cat << 'EOF' > $DEBIAN/postinst -#!/bin/sh - -export GROUP_INPUT=input - -if [ -f /etc/group ]; then - if ! grep -q $GROUP_INPUT /etc/group; then - echo "Creating group $GROUP_INPUT" - - groupadd $GROUP_INPUT - fi -else - echo "Warning: /etc/group not found" -fi - -if [ -f /etc/sunshine/sunshine.conf.old ]; then - echo "Restoring old sunshine.conf" - mv /etc/sunshine/sunshine.conf.old /etc/sunshine/sunshine.conf -fi - -if [ -f /etc/sunshine/apps_linux.json.old ]; then - echo "Restoring old apps_linux.json" - mv /etc/sunshine/apps_linux.json.old /etc/sunshine/apps_linux.json -fi - -# Update permissions on config files for Web Manager -if [ -f /etc/sunshine/apps_linux.json ]; then - echo "chmod 666 /etc/sunshine/apps_linux.json" - chmod 666 /etc/sunshine/apps_linux.json -fi - -if [ -f /etc/sunshine/sunshine.conf ]; then - echo "chmod 666 /etc/sunshine/sunshine.conf" - chmod 666 /etc/sunshine/sunshine.conf -fi - -# Ensure Sunshine can grab images from KMS -path_to_setcap=$(which setcap) -if [ -x "$path_to_setcap" ] ; then - echo "$path_to_setcap cap_sys_admin+p /usr/bin/sunshine" - $path_to_setcap cap_sys_admin+p /usr/bin/sunshine -fi -EOF - -cat << 'EOF' > $RULES/85-sunshine-rules.rules -KERNEL=="uinput", GROUP="input", MODE="0660" -EOF - -cp sunshine $BIN/sunshine -cp @CMAKE_CURRENT_SOURCE_DIR@/assets/apps_linux.json $ASSETS/apps_linux.json -cp @CMAKE_CURRENT_SOURCE_DIR@/assets/sunshine.conf $ASSETS/sunshine.conf -cp @CMAKE_CURRENT_BINARY_DIR@/sunshine.service $SERVICE/sunshine.service -cp -r @CMAKE_CURRENT_SOURCE_DIR@/assets/web $ASSETS/web -cp -r @CMAKE_CURRENT_SOURCE_DIR@/assets/shaders/opengl $ASSETS/shaders/opengl - -chmod 755 $DEBIAN/postinst -chmod 755 $DEBIAN/preinst -chmod 755 $BIN/sunshine -chmod 644 $RULES/85-sunshine-rules.rules -chmod 666 $ASSETS/apps_linux.json -chmod 666 $ASSETS/sunshine.conf - -cd package-deb -if fakeroot dpkg-deb --build sunshine; then - echo "generated debian package: @CMAKE_CURRENT_BINARY_DIR@/package-deb/sunshine.deb" -fi -cd .. - diff --git a/gen-rpm.in b/gen-rpm.in deleted file mode 100755 index 792a3895..00000000 --- a/gen-rpm.in +++ /dev/null @@ -1,179 +0,0 @@ -#!/bin/sh - -# Export filepaths -export BUILDDIR=@CMAKE_CURRENT_SOURCE_DIR@/build -export BUILDROOT=~/rpmbuild/ -export RPMSRC=~/rpmbuild/SOURCES -export RPMSPEC=~/rpmbuild/SPECS -export RPMBUILD=~/rpmbuild/BUILD - -# Check for Docker switch -if [ "$1" == "-d" ]; then - export DOCKERSTATUS=TRUE -else - export DOCKERSTATUS=FALSE -fi - -# Check if user's rpmbuild folder is there, if so, temoprairly rename it. -if [ -d ~/rpmbuild ]; then - echo "Backing up rpmbuild" - ~/rpmbuild ~/rpmbuild.bkp - export RPMBUILDEXISTS=TRUE -else - export RPMBUILDEXISTS=FALSE -fi - -# Create rpmbuild folder structure -mkdir ~/rpmbuild -mkdir ~/rpmbuild/BUILD -mkdir ~/rpmbuild/BUILDROOT -mkdir ~/rpmbuild/RPMS -mkdir ~/rpmbuild/SOURCES -mkdir ~/rpmbuild/SPECS -mkdir ~/rpmbuild/SRPMS - -# Create sunshine .spec file with preinstall and postinstall scripts -cat << 'EOF' > $RPMSPEC/sunshine.spec -Name: sunshine -Version: @PROJECT_VERSION@ -Release: 1%{?dist} -Summary: An NVIDIA Gamestream-compatible hosting server -BuildArch: x86_64 - -License: GPLv3 -URL: https://github.com/SunshineStream/Sunshine -Source0: sunshine-@PROJECT_VERSION@_bin.tar.gz - -Requires: systemd ffmpeg rpmfusion-free-release - -%description -An NVIDIA Gamestream-compatible hosting server - -%pre -#!/bin/sh - -# Sunshine Pre-Install Script -# Store backup for old config files to prevent it from being overwritten -if [ -f /etc/sunshine/sunshine.conf ]; then - cp /etc/sunshine/sunshine.conf /etc/sunshine/sunshine.conf.old -fi - -if [ -f /etc/sunshine/apps_linux.json ]; then - cp /etc/sunshine/apps_linux.json /etc/sunshine/apps_linux.json.old -fi - -%post -#!/bin/sh - -# Sunshine Post-Install Script -export GROUP_INPUT=input - -if [ -f /etc/group ]; then - if ! grep -q $GROUP_INPUT /etc/group; then - echo "Creating group $GROUP_INPUT" - - groupadd $GROUP_INPUT - fi -else - echo "Warning: /etc/group not found" -fi - -if [ -f /etc/sunshine/sunshine.conf.old ]; then - echo "Restoring old sunshine.conf" - mv /etc/sunshine/sunshine.conf.old /etc/sunshine/sunshine.conf -fi - -if [ -f /etc/sunshine/apps_linux.json.old ]; then - echo "Restoring old apps_linux.json" - mv /etc/sunshine/apps_linux.json.old /etc/sunshine/apps_linux.json -fi - -# Update permissions on config files for Web Manager -if [ -f /etc/sunshine/apps_linux.json ]; then - echo "chmod 666 /etc/sunshine/apps_linux.json" - chmod 666 /etc/sunshine/apps_linux.json -fi - -if [ -f /etc/sunshine/sunshine.conf ]; then - echo "chmod 666 /etc/sunshine/sunshine.conf" - chmod 666 /etc/sunshine/sunshine.conf -fi - -# Ensure Sunshine can grab images from KMS -path_to_setcap=$(which setcap) -if [ -x "$path_to_setcap" ] ; then - echo "$path_to_setcap cap_sys_admin+p /usr/bin/sunshine" - $path_to_setcap cap_sys_admin+p /usr/bin/sunshine -fi - -%prep -%setup -q - -%install -rm -rf $RPM_BUILD_ROOT -mkdir -p $RPM_BUILD_ROOT/%{_bindir} -mkdir -p $RPM_BUILD_ROOT/etc/sunshine -mkdir -p $RPM_BUILD_ROOT/usr/lib/systemd/user -mkdir -p $RPM_BUILD_ROOT/usr/share/applications -mkdir -p $RPM_BUILD_ROOT/etc/udev/rules.d - -cp sunshine $RPM_BUILD_ROOT/%{_bindir}/sunshine -cp sunshine.conf $RPM_BUILD_ROOT/etc/sunshine/sunshine.conf -cp apps_linux.json $RPM_BUILD_ROOT/etc/sunshine/apps_linux.json -cp sunshine.service $RPM_BUILD_ROOT/usr/lib/systemd/user/sunshine.service -cp sunshine.desktop $RPM_BUILD_ROOT/usr/share/applications/sunshine.desktop -cp 85-sunshine-rules.rules $RPM_BUILD_ROOT/etc/udev/rules.d/85-sunshine-rules.rules - -%clean -rm -rf $RPM_BUILD_ROOT - -%files -%{_bindir}/sunshine -/usr/lib/systemd/user/sunshine.service -/etc/sunshine/sunshine.conf -/etc/sunshine/apps_linux.json -/usr/share/applications/sunshine.desktop -/etc/udev/rules.d/85-sunshine-rules.rules - -%changelog -* Sat Mar 12 2022 h <65380846+thatsysadmin@users.noreply.github.com> -- Initial packaging of Sunshine. -EOF - -# Copy over sunshine binary and supplemental files into rpmbuild/BUILD/ -mkdir genrpm -mkdir genrpm/sunshine-@PROJECT_VERSION@ -cp sunshine-@PROJECT_VERSION@ genrpm/sunshine-@PROJECT_VERSION@/sunshine -cp sunshine.service genrpm/sunshine-@PROJECT_VERSION@/sunshine.service -cp sunshine.desktop genrpm/sunshine-@PROJECT_VERSION@/sunshine.desktop -cp @CMAKE_CURRENT_SOURCE_DIR@/assets/sunshine.conf genrpm/sunshine-@PROJECT_VERSION@/sunshine.conf -cp @CMAKE_CURRENT_SOURCE_DIR@/assets/apps_linux.json genrpm/sunshine-@PROJECT_VERSION@/apps_linux.json -cp @CMAKE_CURRENT_SOURCE_DIR@/assets/85-sunshine-rules.rules genrpm/sunshine-@PROJECT_VERSION@/85-sunshine-rules.rules -cd genrpm - -# tarball everything as if it was a source file for rpmbuild -tar --create --file sunshine-@PROJECT_VERSION@_bin.tar.gz sunshine-@PROJECT_VERSION@/ -cp sunshine-@PROJECT_VERSION@_bin.tar.gz ~/rpmbuild/SOURCES - -# Use rpmbuild to build the RPM package. -rpmbuild -bb $RPMSPEC/sunshine.spec - -# Check if running in a CT -if [ "$DOCKERSTATUS" == "FALSE" ]; then - # Move the completed RPM into the cmake build folder - mv ~/rpmbuild/RPMS/x86_64/sunshine-@PROJECT_VERSION@-1.fc*.x86_64.rpm @CMAKE_CURRENT_BINARY_DIR@/ - echo "Moving completed RPM package into CMake build folder." -elif [ "$DOCKERSTATUS" == "TRUE" ]; then - # Move into pickup location - mkdir -p /root/sunshine-build/package-rpm/ - mv ~/rpmbuild/RPMS/x86_64/sunshine-@PROJECT_VERSION@-1.fc*.x86_64.rpm /root/sunshine-build/package-rpm/sunshine.rpm - echo "Moving completed RPM package for pickup." -fi - -# Clean up; delete the rpmbuild folder we created and move back the original one -if [ "$RPMBUILDEXISTS" == "TRUE" ]; then - echo "Removing and replacing original rpmbuild folder." - rm -rf ~/rpmbuild - mv ~/rpmbuild.bkp ~/rpmbuild -fi -exit 0 From 2ea414d1d4d6f152650b41d826c2196b14a47d42 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Sat, 23 Apr 2022 11:04:00 +0100 Subject: [PATCH 236/817] testing RPM package dependencies --- CMakeLists.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 291a6f2e..859b544a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -478,6 +478,12 @@ if(UNIX AND NOT APPLE) "${SUNSHINE_ASSETS_DIR}/linux-deb/preinst;${SUNSHINE_ASSETS_DIR}/linux-deb/postinst;${SUNSHINE_ASSETS_DIR}/linux-deb/conffiles") set(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE "${SUNSHINE_ASSETS_DIR}/linux-deb/preinst") set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${SUNSHINE_ASSETS_DIR}/linux-deb/postinst") + + # Dependencies + set(CPACK_DEB_COMPONENT_INSTALL ON) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl1.1, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") + set(CPACK_RPM_PACKAGE_REQUIRES "libssl==1.1, libavdevice>=58, libboost-thread>=1.67.0, libboost-filesystem>=1.67.0, libboost-log>=1.67.0, libpulse>=0, libopus>=0, libxcb-shm>=0, libxcb-xfixes>=0, libxtst>=0, libevdev>=2.0, libdrm>=2.0, libcap>=2.0") + set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # endif() # Common options @@ -500,9 +506,4 @@ if(UNIX AND NOT APPLE) set(CMAKE_INSTALL_PREFIX "/etc/sunshine") endif() -## DEB -set(CPACK_DEB_COMPONENT_INSTALL ON) -set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl1.1, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") -set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) - include(CPack) \ No newline at end of file From ffdcf0fea86bb046e329554d117510346c987d32 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Sat, 23 Apr 2022 15:13:27 +0100 Subject: [PATCH 237/817] feat: basic OSX .app generation added default brew link libraries path --- CMakeLists.txt | 40 ++++++++++++++++++++++++++-------------- sunshine.icns | Bin 0 -> 23711 bytes 2 files changed, 26 insertions(+), 14 deletions(-) create mode 100644 sunshine.icns diff --git a/CMakeLists.txt b/CMakeLists.txt index 859b544a..d81bda12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,7 @@ if(WIN32) INPUT "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip" DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/pre-compiled) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") - + if(NOT DEFINED SUNSHINE_PREPARED_BINARIES) set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled/windows") endif() @@ -127,8 +127,8 @@ if(WIN32) elseif(APPLE) add_compile_definitions(SUNSHINE_PLATFORM="macos") list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_mac.json") + link_directories(/opt/homebrew/lib/) # Default brew lib location link_directories(/opt/local/lib) - link_directories(/usr/local/lib) ADD_DEFINITIONS(-DBOOST_LOG_DYN_LINK) find_package(FFmpeg REQUIRED) @@ -296,14 +296,14 @@ else() third-party/glad/include/KHR/khrplatform.h third-party/glad/include/glad/gl.h third-party/glad/include/glad/egl.h) - + list(APPEND PLATFORM_LIBRARIES dl evdev pulse pulse-simple ) - + include_directories( /usr/include/libevdev-1.0 third-party/nv-codec-headers/include @@ -451,22 +451,36 @@ target_compile_options(sunshine PRIVATE $<$:${SUNSHINE_COM # CPACK #### -# Add all assets dependencies -install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/web" DESTINATION ".") -install(FILES "${SUNSHINE_ASSETS_DIR}/sunshine.conf" DESTINATION ".") +# Add all assets dependencies if(WIN32) # TODO: test install(TARGETS sunshine RUNTIME DESTINATION ".") + install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/web" DESTINATION ".") + install(FILES "${SUNSHINE_ASSETS_DIR}/sunshine.conf" DESTINATION ".") install(FILES "${SUNSHINE_ASSETS_DIR}/apps_windows.json" DESTINATION ".") install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/shaders/directx" DESTINATION "shaders") endif() if(APPLE) # TODO: test - install(TARGETS sunshine RUNTIME DESTINATION ".") - install(FILES "${SUNSHINE_ASSETS_DIR}/apps_mac.json" DESTINATION ".") - # TODO: info.plist ?? + set(prefix "${CMAKE_PROJECT_NAME}.app/Contents") + set(INSTALL_RUNTIME_DIR "${prefix}/MacOS") + + install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/web" DESTINATION "${INSTALL_RUNTIME_DIR}") + install(FILES "${SUNSHINE_ASSETS_DIR}/sunshine.conf" DESTINATION "${INSTALL_RUNTIME_DIR}") + + install(TARGETS sunshine + BUNDLE DESTINATION . COMPONENT Runtime + RUNTIME DESTINATION ${INSTALL_RUNTIME_DIR} COMPONENT Runtime) + + # TODO: bundle doesn't produce a valid .app use cpack -G DragNDrop + # set(CPACK_BUNDLE_NAME "Sunshine") + # set(CPACK_BUNDLE_PLIST "${SUNSHINE_ASSETS_DIR}/info.plist") + # set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/sunshine.icns") endif() if(UNIX AND NOT APPLE) + install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/web" DESTINATION ".") + install(FILES "${SUNSHINE_ASSETS_DIR}/sunshine.conf" DESTINATION ".") + install(FILES "${SUNSHINE_ASSETS_DIR}/apps_linux.json" DESTINATION ".") install(FILES "${SUNSHINE_ASSETS_DIR}/85-sunshine-rules.rules" DESTINATION "/etc/udev/rules.d") install(TARGETS sunshine RUNTIME DESTINATION "/usr/bin") @@ -474,7 +488,7 @@ if(UNIX AND NOT APPLE) install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/shaders/opengl" DESTINATION "shaders") # Pre and post install - set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA + set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${SUNSHINE_ASSETS_DIR}/linux-deb/preinst;${SUNSHINE_ASSETS_DIR}/linux-deb/postinst;${SUNSHINE_ASSETS_DIR}/linux-deb/conffiles") set(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE "${SUNSHINE_ASSETS_DIR}/linux-deb/preinst") set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${SUNSHINE_ASSETS_DIR}/linux-deb/postinst") @@ -499,10 +513,8 @@ set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${VERSION}_${CPACK_DEBIAN_PAC set(CPACK_STRIP_FILES YES) # Installation destination dir -if(NOT WIN32) - set(CPACK_SET_DESTDIR true) -endif() if(UNIX AND NOT APPLE) + set(CPACK_SET_DESTDIR true) set(CMAKE_INSTALL_PREFIX "/etc/sunshine") endif() diff --git a/sunshine.icns b/sunshine.icns new file mode 100644 index 0000000000000000000000000000000000000000..0420582b244aa1599ebe86a9016d3113ef42c7cc GIT binary patch literal 23711 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 literal 0 HcmV?d00001 From e2bef750b4b3201be8ef6ee49a95263f025af05f Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Wed, 27 Apr 2022 20:50:30 +0100 Subject: [PATCH 238/817] feat: created NSIS windows installer, fixed Linux packaging paths --- CMakeLists.txt | 99 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d81bda12..4c19621a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -451,14 +451,71 @@ target_compile_options(sunshine PRIVATE $<$:${SUNSHINE_COM # CPACK #### -# Add all assets dependencies -if(WIN32) # TODO: test - install(TARGETS sunshine RUNTIME DESTINATION ".") - - install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/web" DESTINATION ".") - install(FILES "${SUNSHINE_ASSETS_DIR}/sunshine.conf" DESTINATION ".") - install(FILES "${SUNSHINE_ASSETS_DIR}/apps_windows.json" DESTINATION ".") - install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/shaders/directx" DESTINATION "shaders") +# Common options +set(CPACK_PACKAGE_NAME "SunshineStream") +set(CPACK_PACKAGE_VENDOR "CMake.org") +set(CPACK_PACKAGE_CONTACT "https://github.com/SunshineStream/Sunshine") +set(CPACK_DEBIAN_PACKAGE_MAINTAINER "https://github.com/SunshineStream/Sunshine") +set(CPACK_PACKAGE_DESCRIPTION "Gamestream host for Moonlight") +set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/SunshineStream/Sunshine") +set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE) +set(CPACK_PACKAGE_ICON ${PROJECT_SOURCE_DIR}/sunshine.png) +set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}") +set(CPACK_STRIP_FILES YES) + +# Platform specific options +if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.html + install(TARGETS sunshine RUNTIME DESTINATION "." COMPONENT application) + + # Adding tools + 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(DIRECTORY "${SUNSHINE_ASSETS_DIR}/web" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT web) + install(FILES "${SUNSHINE_ASSETS_DIR}/apps_windows.json" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) + install(FILES "${SUNSHINE_ASSETS_DIR}/sunshine.conf" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) + install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/shaders/directx" DESTINATION "${SUNSHINE_CONFIG_DIR}/shaders" COMPONENT assets) + + set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}\\\\sunshine.ico") + set(CPACK_NSIS_INSTALLED_ICON_NAME "${PROJECT__DIR}\\\\${PROJECT_EXE}") + string(APPEND CPACK_NSIS_DEFINES "\n RequestExecutionLevel admin") # TODO: Not sure if this is needed but it took me a while to figure out where to put this option so I'm leaving it here + + # Sets permissions on the installed folder so that we can write in it + SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS + "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS} + ExecWait 'icacls \\\"$INSTDIR\\\" /grant:r Users:\\\(OI\\\)\\\(CI\\\)\\\(F\\\)' + ") + + # Adding an option for the start menu and PATH + set(CPACK_NSIS_MODIFY_PATH "OFF") # TODO: it asks to add it to the PATH but then it seems I can't just run it from powershell + set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".") + set(CPACK_NSIS_MUI_FINISHPAGE_RUN "${PROJECT_NAME}.exe") + set(CPACK_NSIS_INSTALLED_ICON_NAME "${PROJECT_NAME}.exe") # This will be shown on the installed apps Windows settings + set(CPACK_NSIS_CREATE_ICONS "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${PROJECT_NAME}.lnk' '\$INSTDIR\\\\${PROJECT_NAME}.exe'") + + # Checking for previous installed versions + # set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL "ON") # TODO: doesn't work on my machine when Sunshine is already installed + + # Setting components groups and dependencies + set(CPACK_COMPONENT_APPLICATION_DISPLAY_NAME "Sunshine") + set(CPACK_COMPONENT_WEB_DISPLAY_NAME "Web interface") + set(CPACK_COMPONENT_ASSETS_DISPLAY_NAME "Extra assets files") + set(CPACK_COMPONENT_LIBRARIES_DISPLAY_NAME "Libraries") + + set(CPACK_COMPONENT_APPLICATION_GROUP "Runtime") + set(CPACK_COMPONENT_WEB_GROUP "Runtime") + set(CPACK_COMPONENT_ASSETS_GROUP "Runtime") + + set(CPACK_COMPONENT_DXGI_DISPLAY_NAME "dxgi-info.exe") + set(CPACK_COMPONENT_AUDIO_DISPLAY_NAME "audio-info.exe") + set(CPACK_COMPONENT_SUNSHINESVC_DISPLAY_NAME "sunshinesvc.exe") + + set(CPACK_COMPONENT_DXGI_GROUP "Extra Tools") + set(CPACK_COMPONENT_AUDIO_GROUP "Extra Tools") + set(CPACK_COMPONENT_SUNSHINESVC_GROUP "Extra Tools") + + set(CPACK_COMPONENT_APPLICATION_DEPENDS web assets) endif() if(APPLE) # TODO: test @@ -478,14 +535,14 @@ if(APPLE) # TODO: test # set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/sunshine.icns") endif() if(UNIX AND NOT APPLE) - install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/web" DESTINATION ".") - install(FILES "${SUNSHINE_ASSETS_DIR}/sunshine.conf" DESTINATION ".") + install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/web" DESTINATION "${SUNSHINE_CONFIG_DIR}") + install(FILES "${SUNSHINE_ASSETS_DIR}/sunshine.conf" DESTINATION "${SUNSHINE_CONFIG_DIR}") - install(FILES "${SUNSHINE_ASSETS_DIR}/apps_linux.json" DESTINATION ".") + install(FILES "${SUNSHINE_ASSETS_DIR}/apps_linux.json" DESTINATION "${SUNSHINE_CONFIG_DIR}") install(FILES "${SUNSHINE_ASSETS_DIR}/85-sunshine-rules.rules" DESTINATION "/etc/udev/rules.d") install(TARGETS sunshine RUNTIME DESTINATION "/usr/bin") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "/usr/lib/systemd/user") - install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/shaders/opengl" DESTINATION "shaders") + install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/shaders/opengl" DESTINATION "${SUNSHINE_CONFIG_DIR}/shaders") # Pre and post install set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA @@ -497,23 +554,9 @@ if(UNIX AND NOT APPLE) set(CPACK_DEB_COMPONENT_INSTALL ON) set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl1.1, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") set(CPACK_RPM_PACKAGE_REQUIRES "libssl==1.1, libavdevice>=58, libboost-thread>=1.67.0, libboost-filesystem>=1.67.0, libboost-log>=1.67.0, libpulse>=0, libopus>=0, libxcb-shm>=0, libxcb-xfixes>=0, libxtst>=0, libevdev>=2.0, libdrm>=2.0, libcap>=2.0") - set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # -endif() + set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # This should automatically figure out dependencies, doesn't work with the current config -# Common options -set(CPACK_PACKAGE_NAME "SunshineStream") -set(CPACK_PACKAGE_VENDOR "CMake.org") -set(CPACK_PACKAGE_CONTACT "https://github.com/SunshineStream/Sunshine") -set(CPACK_DEBIAN_PACKAGE_MAINTAINER "https://github.com/SunshineStream/Sunshine") -set(CPACK_PACKAGE_DESCRIPTION "Gamestream host for Moonlight") -set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/SunshineStream/Sunshine") -set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE) -set(CPACK_PACKAGE_ICON ${PROJECT_SOURCE_DIR}/sunshine.png) -set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}") -set(CPACK_STRIP_FILES YES) - -# Installation destination dir -if(UNIX AND NOT APPLE) + # Installation destination dir set(CPACK_SET_DESTDIR true) set(CMAKE_INSTALL_PREFIX "/etc/sunshine") endif() From cc2d982cebee5cc19894ef2a23bf9fb29f88d638 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Wed, 27 Apr 2022 21:00:20 +0100 Subject: [PATCH 239/817] feat: testing out CI building for new packages --- .github/workflows/CI.yml | 56 ++++++++++++---------------------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index b0be1130..e4a76c6e 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -147,43 +147,29 @@ jobs: name: Linux runs-on: ubuntu-20.04 needs: check_changelog - strategy: - fail-fast: true # false to test all, true to fail entire job if any fail - matrix: - distro: [ debian, ubuntu_18_04, ubuntu_20_04, ubuntu_21_10 ] - package: [ -p ] - extension: [ deb ] - include: # package these differently - - distro: fedora_35 - package: '-p' - extension: rpm - steps: - name: Checkout uses: actions/checkout@v3 with: submodules: recursive - - name: Setup Container + - name: Build DEB and RPM run: | + mkdir -p build mkdir -p artifacts - - cd scripts - sudo ./build-container.sh -c build -f Dockerfile-${{ matrix.distro }} -n sunshine-${{ matrix.distro }} - - name: Build Linux - run: | - cd scripts - sudo ./build-sunshine.sh ${{ matrix.package }} -e ${{ matrix.extension }} -u -n sunshine-${{ matrix.distro }} -s .. - - name: Package Linux - if: ${{ matrix.package != '' }} - run: | - cd scripts - sudo mv ./sunshine-${{ matrix.distro }}-build/sunshine-${{ matrix.distro }}.${{ matrix.extension }} ../artifacts/ + cd build + + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_CONFIG_DIR="." ../ + cpack -G DEB + cpack -G RPM + + cp Sunshine__.rpm ../artifacts/ + cp Sunshine__.deb ../artifacts/ - name: Upload Artifacts if: ${{ matrix.package == '-p' && ( github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' ) }} uses: actions/upload-artifact@v3 with: - name: sunshine-${{ matrix.distro }} + name: sunshine-linux path: artifacts/ - name: Create Release if: ${{ matrix.package == '-p' && github.event_name == 'push' && github.ref == 'refs/heads/master' }} @@ -218,6 +204,7 @@ jobs: mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost + mingw-w64-x86_64-nsis git yasm nasm @@ -226,23 +213,14 @@ jobs: - name: Build Windows shell: msys2 {0} run: | + mkdir artifacts + mkdir sunshine-windows-build && cd sunshine-windows-build cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. mingw32-make -j2 - - name: Package Windows - run: | - cd sunshine-windows-build - del ..\assets\apps_linux.json - 7z a sunshine-windows.zip ..\assets - 7z a sunshine-windows.zip sunshine.exe - 7z a sunshine-windows.zip tools\dxgi-info.exe - 7z a sunshine-windows.zip tools\audio-info.exe - 7z a sunshine-windows.zip tools\sunshinesvc.exe - 7z a sunshine-windows.zip ..\tools\install-service.bat - 7z a sunshine-windows.zip ..\tools\uninstall-service.bat - cd .. - mkdir artifacts - move "sunshine-windows-build\sunshine-windows.zip" "artifacts" + cpack + + cp Sunshine__.exe ../artifacts - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v3 From 4e4a5c8df8c60dcf4fe2a409c4455c9786cc45ef Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Fri, 29 Apr 2022 21:19:05 +0100 Subject: [PATCH 240/817] feat: testing out a better CI workflow --- .github/workflows/CI.yml | 162 ++++++++++++++++++++++++++------------- CMakeLists.txt | 2 +- 2 files changed, 110 insertions(+), 54 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e4a76c6e..b1503feb 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -51,10 +51,60 @@ jobs: echo Within 'CMakeLists.txt' change "project(Sunshine VERSION $cmakelists_version)" to "project(Sunshine VERSION ${{ needs.check_changelog.outputs.next_version_bare }})" exit 1 + build_linux: + name: Build Sunshine on Linux + runs-on: ubuntu-20.04 + needs: check_changelog + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Cache Sunshine build + uses: actions/cache@v2 + with: + path: build + key: ${{ runner.os }}-sunshine-build + + - name: Setup Dependencies + run: | + mkdir -p artifacts + + sudo apt-get update -y && \ + sudo apt-get --reinstall install -y \ + git wget gcc-10 g++-10 build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 + sudo wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && sudo chmod a+x /root/cuda.run + sudo /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && sudo rm /root/cuda.run + sudo add-apt-repository ppa:savoury1/graphics -y + sudo add-apt-repository ppa:savoury1/multimedia -y + sudo add-apt-repository ppa:savoury1/ffmpeg4 -y + sudo apt-get update -y + sudo apt-get upgrade -y && sudo apt-get dist-upgrade -y + sudo apt-get install ffmpeg -y + + - name: Build Sunshine + run: | + CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}" + SUNSHINE_EXECUTABLE_PATH="${SUNSHINE_EXECUTABLE_PATH:-/usr/bin/sunshine}" + SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR:-sunshine.AppImage.config}" + + SUNSHINE_ENABLE_WAYLAND=${SUNSHINE_ENABLE_WAYLAND:-ON} + SUNSHINE_ENABLE_X11=${SUNSHINE_ENABLE_X11:-ON} + SUNSHINE_ENABLE_DRM=${SUNSHINE_ENABLE_DRM:-ON} + SUNSHINE_ENABLE_CUDA=${SUNSHINE_ENABLE_CUDA:-ON} + + mkdir -p build + cd build + + cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" "../" -DCMAKE_INSTALL_PREFIX=/usr + make -j ${nproc} DESTDIR=AppDir + build_appimage: name: AppImage runs-on: ubuntu-20.04 - needs: check_changelog + needs: build_linux steps: - name: Checkout @@ -62,38 +112,12 @@ jobs: with: submodules: recursive - - name: Setup Dependencies AppImage - run: | - mkdir -p artifacts - - sudo apt-get update -y && \ - sudo apt-get --reinstall install -y \ - git wget gcc-10 g++-10 build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 - sudo wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && sudo chmod a+x /root/cuda.run - sudo /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && sudo rm /root/cuda.run - sudo add-apt-repository ppa:savoury1/graphics -y - sudo add-apt-repository ppa:savoury1/multimedia -y - sudo add-apt-repository ppa:savoury1/ffmpeg4 -y - sudo apt-get update -y - sudo apt-get upgrade -y && sudo apt-get dist-upgrade -y - sudo apt-get install ffmpeg -y - - name: Build AppImage - run: | - CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}" - SUNSHINE_EXECUTABLE_PATH="${SUNSHINE_EXECUTABLE_PATH:-/usr/bin/sunshine}" - SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR:-sunshine.AppImage.config}" - - SUNSHINE_ENABLE_WAYLAND=${SUNSHINE_ENABLE_WAYLAND:-ON} - SUNSHINE_ENABLE_X11=${SUNSHINE_ENABLE_X11:-ON} - SUNSHINE_ENABLE_DRM=${SUNSHINE_ENABLE_DRM:-ON} - SUNSHINE_ENABLE_CUDA=${SUNSHINE_ENABLE_CUDA:-ON} - - mkdir -p appimage-build && cd appimage-build - - cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" "../" -DCMAKE_INSTALL_PREFIX=/usr + - name: Cache Sunshine build + uses: actions/cache@v2 + with: + path: build + key: ${{ runner.os }}-sunshine-build - make -j ${nproc} DESTDIR=AppDir - name: Set AppImage Version if: ${{ needs.check_changelog.outputs.next_version_bare != needs.check_changelog.outputs.latest_version }} run: | @@ -143,34 +167,44 @@ jobs: last_version: ${{ needs.check_changelog.outputs.last_version }} release_body: ${{ needs.check_changelog.outputs.release_body }} - build_linux: - name: Linux + build_deb_and_rpm: + name: Build DEB and RPM runs-on: ubuntu-20.04 - needs: check_changelog + needs: build_linux steps: - name: Checkout uses: actions/checkout@v3 with: submodules: recursive + - name: Cache Sunshine build + uses: actions/cache@v2 + with: + path: build + key: ${{ runner.os }}-sunshine-build + + - name: Extra dependencies + run: | + sudo apt-get update -y + sudo apt-get install -y cpack + - name: Build DEB and RPM run: | - mkdir -p build - mkdir -p artifacts cd build - - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_CONFIG_DIR="." ../ cpack -G DEB cpack -G RPM - - cp Sunshine__.rpm ../artifacts/ - cp Sunshine__.deb ../artifacts/ - - name: Upload Artifacts + - name: Upload RPM if: ${{ matrix.package == '-p' && ( github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' ) }} uses: actions/upload-artifact@v3 with: - name: sunshine-linux - path: artifacts/ + name: sunshine-linux-rpm + path: build/Sunshine__.rpm + - name: Upload DEB + if: ${{ matrix.package == '-p' && ( github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' ) }} + uses: actions/upload-artifact@v3 + with: + name: sunshine-linux-deb + path: build/Sunshine__.deb - name: Create Release if: ${{ matrix.package == '-p' && github.event_name == 'push' && github.ref == 'refs/heads/master' }} uses: SunshineStream/actions/create_release@master @@ -212,21 +246,43 @@ jobs: make - name: Build Windows shell: msys2 {0} - run: | - mkdir artifacts - - mkdir sunshine-windows-build && cd sunshine-windows-build + run: | + mkdir sunshine-windows-build + cd sunshine-windows-build cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. mingw32-make -j2 + - name: Create packages + shell: msys2 {0} + run: | + cd sunshine-windows-build cpack + + mkdir -p artifacts/standalone + mkdir -p artifacts/web + mkdir -p artifacts/shaders/directx + + # Installers + mv Sunshine__.exe artifacts/sunshine-windows-installer.exe - cp Sunshine__.exe ../artifacts - - name: Upload Artifacts + # Standalone + mv ../assets/web artifacts/web + mv tools artifacts/tools + mv ../assets/shaders/directx artifacts/shaders/directx + mv ../assets/apps_windows.json artifacts/apps_windows.json + mv ../assets/sunshine.conf artifacts/sunshine.conf + mv sunshine.exe artifacts/sunshine.exe + - name: Upload Sunshine Installer + if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} + uses: actions/upload-artifact@v3 + with: + name: sunshine-windows-installer + path: artifacts/sunshine-windows-installer.exe + - name: Upload Sunshine executable if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v3 with: - name: sunshine-windows - path: artifacts/ + name: sunshine-windows-standalone + path: artifacts/standalone - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} uses: SunshineStream/actions/create_release@master diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c19621a..f34a86b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -457,7 +457,7 @@ set(CPACK_PACKAGE_VENDOR "CMake.org") set(CPACK_PACKAGE_CONTACT "https://github.com/SunshineStream/Sunshine") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "https://github.com/SunshineStream/Sunshine") set(CPACK_PACKAGE_DESCRIPTION "Gamestream host for Moonlight") -set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/SunshineStream/Sunshine") +set(CPACK_PACKAGE_HOMEPAGE_URL "https://sunshinestream.github.io") set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE) set(CPACK_PACKAGE_ICON ${PROJECT_SOURCE_DIR}/sunshine.png) set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}") From af342c8cc98c1f9d5a29dff1dadbf1b3564f113b Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Fri, 29 Apr 2022 21:21:37 +0100 Subject: [PATCH 241/817] fix: CI, better jobs dependencies --- .github/workflows/CI.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index b1503feb..082f1cd9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -32,7 +32,6 @@ jobs: check_versions: name: Check Versions runs-on: ubuntu-latest - needs: check_changelog if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} # base_ref for pull request check, ref for push steps: @@ -54,7 +53,7 @@ jobs: build_linux: name: Build Sunshine on Linux runs-on: ubuntu-20.04 - needs: check_changelog + needs: [check_versions, check_changelog] steps: - name: Checkout uses: actions/checkout@v3 @@ -217,7 +216,7 @@ jobs: build_win: name: Windows runs-on: windows-2019 - needs: check_changelog + needs: [check_versions, check_changelog] steps: - name: Checkout From 6858f9c8d47a4e26fc25544aac25425c5b72db08 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Fri, 29 Apr 2022 23:10:27 +0100 Subject: [PATCH 242/817] fix: testing CI --- .github/workflows/CI.yml | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 082f1cd9..70d442d3 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -32,13 +32,12 @@ jobs: check_versions: name: Check Versions runs-on: ubuntu-latest - if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} - # base_ref for pull request check, ref for push steps: - name: Checkout uses: actions/checkout@v3 - name: Check CMakeLists.txt Version + if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} run: | version=$(grep -o -E '^project\(Sunshine VERSION [0-9]+\.[0-9]+\.[0-9]+\)' CMakeLists.txt | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') echo "cmakelists_version=${version}" >> $GITHUB_ENV @@ -134,7 +133,7 @@ jobs: wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage && chmod +x linuxdeploy-x86_64.AppImage - ./linuxdeploy-x86_64.AppImage --appdir ../AppDir -e ../appimage-build/sunshine -i "../$ICON_FILE" -d "../appimage-build/$DESKTOP_FILE" --output appimage + ./linuxdeploy-x86_64.AppImage --appdir ../AppDir -e ../build/sunshine -i "../$ICON_FILE" -d "../build/$DESKTOP_FILE" --output appimage mv sunshine*.AppImage sunshine.AppImage mkdir sunshine && mv sunshine.AppImage sunshine/ @@ -185,7 +184,7 @@ jobs: - name: Extra dependencies run: | sudo apt-get update -y - sudo apt-get install -y cpack + sudo apt-get install -y cmake # will install cpack - name: Build DEB and RPM run: | @@ -256,32 +255,33 @@ jobs: cd sunshine-windows-build cpack - mkdir -p artifacts/standalone - mkdir -p artifacts/web - mkdir -p artifacts/shaders/directx + mkdir -p ../artifacts # Installers - mv Sunshine__.exe artifacts/sunshine-windows-installer.exe + mv Sunshine__.exe ../artifacts/sunshine-windows-installer.exe # Standalone - mv ../assets/web artifacts/web - mv tools artifacts/tools - mv ../assets/shaders/directx artifacts/shaders/directx - mv ../assets/apps_windows.json artifacts/apps_windows.json - mv ../assets/sunshine.conf artifacts/sunshine.conf - mv sunshine.exe artifacts/sunshine.exe + del ..\assets\apps_linux.json + 7z a sunshine-windows.zip ..\assets + 7z a sunshine-windows.zip sunshine.exe + 7z a sunshine-windows.zip tools\dxgi-info.exe + 7z a sunshine-windows.zip tools\audio-info.exe + 7z a sunshine-windows.zip tools\sunshinesvc.exe + 7z a sunshine-windows.zip ..\tools\install-service.bat + 7z a sunshine-windows.zip ..\tools\uninstall-service.bat + move "sunshine-windows.zip" "../artifacts" - name: Upload Sunshine Installer if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v3 with: - name: sunshine-windows-installer - path: artifacts/sunshine-windows-installer.exe + name: sunshine-windows + path: artifacts - name: Upload Sunshine executable if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v3 with: name: sunshine-windows-standalone - path: artifacts/standalone + path: sunshine-windows-build/artifacts/standalone - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} uses: SunshineStream/actions/create_release@master From 5eb3e7c75f50426a9e4e5ca3eae0d55d167a90ce Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Sat, 30 Apr 2022 09:45:33 +0100 Subject: [PATCH 243/817] CI: use cpack for windows 7Z, split back deb/rpm and appimage, moved release action at the end --- .github/workflows/CI.yml | 287 ++++++++++++++++++--------------------- 1 file changed, 130 insertions(+), 157 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 70d442d3..b85dfcd7 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -5,7 +5,7 @@ on: branches: [master, nightly] types: [opened, synchronize, reopened] push: - branches: [master] + branches: [ master ] workflow_dispatch: jobs: @@ -13,62 +13,57 @@ jobs: name: Check Changelog runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Verify Changelog - id: verify_changelog - if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} - # base_ref for pull request check, ref for push - uses: SunshineStream/actions/verify_changelog@master - with: - token: ${{ secrets.GITHUB_TOKEN }} + - name: Checkout + uses: actions/checkout@v3 + + - name: Verify Changelog + id: verify_changelog + if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} + # base_ref for pull request check, ref for push + uses: SunshineStream/actions/verify_changelog@master + with: + token: ${{ secrets.GITHUB_TOKEN }} outputs: next_version: ${{ steps.verify_changelog.outputs.changelog_parser_version }} next_version_bare: ${{ steps.verify_changelog.outputs.changelog_parser_version_bare }} last_version: ${{ steps.verify_changelog.outputs.latest_release_tag_name }} - release_body: ${{ steps.verify_changelog.outputs.changelog_parser_description }} + release_body: ${{ steps.verify_changelog.outputs.changelog_parser_description }} check_versions: name: Check Versions runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - - name: Check CMakeLists.txt Version - if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} - run: | - version=$(grep -o -E '^project\(Sunshine VERSION [0-9]+\.[0-9]+\.[0-9]+\)' CMakeLists.txt | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') - echo "cmakelists_version=${version}" >> $GITHUB_ENV - - name: Compare CMakeList.txt Version - if: ${{ env.cmakelists_version != needs.check_changelog.outputs.next_version_bare }} - run: | - echo CMakeLists version: "$cmakelists_version" - echo Changelog version: "${{ needs.check_changelog.outputs.next_version_bare }}" - echo Within 'CMakeLists.txt' change "project(Sunshine VERSION $cmakelists_version)" to "project(Sunshine VERSION ${{ needs.check_changelog.outputs.next_version_bare }})" - exit 1 + - name: Check CMakeLists.txt Version + if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} + run: | + version=$(grep -o -E '^project\(Sunshine VERSION [0-9]+\.[0-9]+\.[0-9]+\)' CMakeLists.txt | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') + echo "cmakelists_version=${version}" >> $GITHUB_ENV - build_linux: - name: Build Sunshine on Linux + - name: Compare CMakeList.txt Version + if: ${{ env.cmakelists_version != needs.check_changelog.outputs.next_version_bare }} + run: | + echo CMakeLists version: "$cmakelists_version" + echo Changelog version: "${{ needs.check_changelog.outputs.next_version_bare }}" + echo Within 'CMakeLists.txt' change "project(Sunshine VERSION $cmakelists_version)" to "project(Sunshine VERSION ${{ needs.check_changelog.outputs.next_version_bare }})" + exit 1 + + build_appimage: + name: AppImage runs-on: ubuntu-20.04 - needs: [check_versions, check_changelog] + needs: check_changelog + steps: - name: Checkout uses: actions/checkout@v3 with: submodules: recursive - - name: Cache Sunshine build - uses: actions/cache@v2 - with: - path: build - key: ${{ runner.os }}-sunshine-build - - - name: Setup Dependencies + - name: Setup Dependencies AppImage run: | mkdir -p artifacts - sudo apt-get update -y && \ sudo apt-get --reinstall install -y \ git wget gcc-10 g++-10 build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev @@ -82,146 +77,129 @@ jobs: sudo apt-get upgrade -y && sudo apt-get dist-upgrade -y sudo apt-get install ffmpeg -y - - name: Build Sunshine + - name: Build AppImage run: | CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}" SUNSHINE_EXECUTABLE_PATH="${SUNSHINE_EXECUTABLE_PATH:-/usr/bin/sunshine}" SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR:-sunshine.AppImage.config}" - SUNSHINE_ENABLE_WAYLAND=${SUNSHINE_ENABLE_WAYLAND:-ON} SUNSHINE_ENABLE_X11=${SUNSHINE_ENABLE_X11:-ON} SUNSHINE_ENABLE_DRM=${SUNSHINE_ENABLE_DRM:-ON} SUNSHINE_ENABLE_CUDA=${SUNSHINE_ENABLE_CUDA:-ON} - - mkdir -p build - cd build - + mkdir -p appimage-build && cd appimage-build cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" "../" -DCMAKE_INSTALL_PREFIX=/usr make -j ${nproc} DESTDIR=AppDir - build_appimage: - name: AppImage - runs-on: ubuntu-20.04 - needs: build_linux - - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Cache Sunshine build - uses: actions/cache@v2 - with: - path: build - key: ${{ runner.os }}-sunshine-build - - - name: Set AppImage Version - if: ${{ needs.check_changelog.outputs.next_version_bare != needs.check_changelog.outputs.latest_version }} - run: | - version=${{ needs.check_changelog.outputs.next_version_bare }} - echo "VERSION=${version}" >> $GITHUB_ENV - - name: Package AppImage - # https://docs.appimage.org/packaging-guide/index.html - run: | - mkdir -p appimage_temp && cd appimage_temp - - DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" - ICON_FILE="${ICON_FILE:-sunshine.png}" - CONFIG_DIR="${CONFIG_DIR:-sunshine/sunshine.AppImage.config/}" - HOME_DIR="${HOME_DIR:-sunshine/sunshine.AppImage.home/}" - - wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage && chmod +x linuxdeploy-x86_64.AppImage - - ./linuxdeploy-x86_64.AppImage --appdir ../AppDir -e ../build/sunshine -i "../$ICON_FILE" -d "../build/$DESKTOP_FILE" --output appimage + - name: Set AppImage Version + if: ${{ needs.check_changelog.outputs.next_version_bare != needs.check_changelog.outputs.latest_version }} + run: | + version=${{ needs.check_changelog.outputs.next_version_bare }} + echo "VERSION=${version}" >> $GITHUB_ENV + - name: Package AppImage + # https://docs.appimage.org/packaging-guide/index.html + run: | + mkdir -p appimage_temp && cd appimage_temp + DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" + ICON_FILE="${ICON_FILE:-sunshine.png}" + CONFIG_DIR="${CONFIG_DIR:-sunshine/sunshine.AppImage.config/}" + HOME_DIR="${HOME_DIR:-sunshine/sunshine.AppImage.home/}" + wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage && chmod +x linuxdeploy-x86_64.AppImage + ./linuxdeploy-x86_64.AppImage --appdir ../AppDir -e ../appimage-build/sunshine -i "../$ICON_FILE" -d "../appimage-build/$DESKTOP_FILE" --output appimage + mv sunshine*.AppImage sunshine.AppImage + mkdir sunshine && mv sunshine.AppImage sunshine/ + ./sunshine/sunshine.AppImage --appimage-portable-config + ./sunshine/sunshine.AppImage --appimage-portable-home + cp -r ../assets/* "$CONFIG_DIR" + rm -f "$CONFIG_DIR"/apps_windows.json + mkdir -p ./"$HOME_DIR"/.config/"$CONFIG_DIR" + cp ./"$CONFIG_DIR"/apps_linux.json ./"$HOME_DIR"/.config/"$CONFIG_DIR" + zip -r ./sunshine-appimage.zip ./sunshine/* + mv sunshine-appimage.zip ../artifacts/ - mv sunshine*.AppImage sunshine.AppImage - mkdir sunshine && mv sunshine.AppImage sunshine/ - ./sunshine/sunshine.AppImage --appimage-portable-config - ./sunshine/sunshine.AppImage --appimage-portable-home - cp -r ../assets/* "$CONFIG_DIR" - rm -f "$CONFIG_DIR"/apps_windows.json - mkdir -p ./"$HOME_DIR"/.config/"$CONFIG_DIR" - cp ./"$CONFIG_DIR"/apps_linux.json ./"$HOME_DIR"/.config/"$CONFIG_DIR" - zip -r ./sunshine-appimage.zip ./sunshine/* + - name: Verify AppImage + run: | + cd appimage_temp + wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage && chmod +x appimagelint-x86_64.AppImage && ./appimagelint-x86_64.AppImage ./sunshine/sunshine.AppImage - mv sunshine-appimage.zip ../artifacts/ - - name: Verify AppImage - run: | - cd appimage_temp - wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage && chmod +x appimagelint-x86_64.AppImage && ./appimagelint-x86_64.AppImage ./sunshine/sunshine.AppImage - - name: Upload Artifacts - if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} - uses: actions/upload-artifact@v3 - with: - name: sunshine-appimage - path: artifacts/ - - name: Create Release - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: SunshineStream/actions/create_release@master - with: - token: ${{ secrets.GITHUB_TOKEN }} - next_version: ${{ needs.check_changelog.outputs.next_version }} - last_version: ${{ needs.check_changelog.outputs.last_version }} - release_body: ${{ needs.check_changelog.outputs.release_body }} + - name: Upload Artifacts + if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} + uses: actions/upload-artifact@v3 + with: + name: sunshine-appimage + path: artifacts/ - build_deb_and_rpm: - name: Build DEB and RPM + build_linux: + name: Linux runs-on: ubuntu-20.04 - needs: build_linux + needs: check_changelog steps: - name: Checkout uses: actions/checkout@v3 with: submodules: recursive - - name: Cache Sunshine build - uses: actions/cache@v2 - with: - path: build - key: ${{ runner.os }}-sunshine-build - - - name: Extra dependencies + - name: Setup Dependencies AppImage run: | + mkdir -p artifacts + sudo apt-get update -y && \ + sudo apt-get --reinstall install -y \ + git wget gcc-10 g++-10 build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 + sudo wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && sudo chmod a+x /root/cuda.run + sudo /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && sudo rm /root/cuda.run + sudo add-apt-repository ppa:savoury1/graphics -y + sudo add-apt-repository ppa:savoury1/multimedia -y + sudo add-apt-repository ppa:savoury1/ffmpeg4 -y sudo apt-get update -y - sudo apt-get install -y cmake # will install cpack + sudo apt-get upgrade -y && sudo apt-get dist-upgrade -y + sudo apt-get install ffmpeg -y - - name: Build DEB and RPM + - name: Build Sunshine + run: | + CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}" + SUNSHINE_EXECUTABLE_PATH="${SUNSHINE_EXECUTABLE_PATH:-/usr/bin/sunshine}" + SUNSHINE_ENABLE_WAYLAND=${SUNSHINE_ENABLE_WAYLAND:-ON} + SUNSHINE_ENABLE_X11=${SUNSHINE_ENABLE_X11:-ON} + SUNSHINE_ENABLE_DRM=${SUNSHINE_ENABLE_DRM:-ON} + SUNSHINE_ENABLE_CUDA=${SUNSHINE_ENABLE_CUDA:-ON} + SUNSHINE_CONFIG_DIR=${SUNSHINE_CONFIG_DIR:-.} + + mkdir -p build + mkdir -p artifacts + + cd build + cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_CONFIG_DIR=$SUNSHINE_CONFIG_DIR" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" "../" -DCMAKE_INSTALL_PREFIX=/usr + + - name: Build DEB run: | cd build cpack -G DEB + mv Sunshine__.deb ../artifacts/ + + - name: Build RPM + run: | + cd build cpack -G RPM - - name: Upload RPM - if: ${{ matrix.package == '-p' && ( github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' ) }} - uses: actions/upload-artifact@v3 - with: - name: sunshine-linux-rpm - path: build/Sunshine__.rpm - - name: Upload DEB + mv Sunshine__.rpm ../artifacts/ + + - name: Upload Artifacts if: ${{ matrix.package == '-p' && ( github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' ) }} uses: actions/upload-artifact@v3 with: - name: sunshine-linux-deb - path: build/Sunshine__.deb - - name: Create Release - if: ${{ matrix.package == '-p' && github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: SunshineStream/actions/create_release@master - with: - token: ${{ secrets.GITHUB_TOKEN }} - next_version: ${{ needs.check_changelog.outputs.next_version }} - last_version: ${{ needs.check_changelog.outputs.last_version }} - release_body: ${{ needs.check_changelog.outputs.release_body }} + name: sunshine-linux + path: artifacts/ build_win: name: Windows runs-on: windows-2019 - needs: [check_versions, check_changelog] + needs: [ check_versions, check_changelog ] steps: - name: Checkout uses: actions/checkout@v3 with: submodules: recursive + - name: MSYS2 Setup uses: msys2/setup-msys2@v2 with: @@ -242,46 +220,41 @@ jobs: nasm diffutils make + - name: Build Windows shell: msys2 {0} - run: | + run: | mkdir sunshine-windows-build cd sunshine-windows-build cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. mingw32-make -j2 + - name: Create packages shell: msys2 {0} run: | cd sunshine-windows-build - cpack - + + cpack -G NSIS + cpack -G 7Z + mkdir -p ../artifacts # Installers mv Sunshine__.exe ../artifacts/sunshine-windows-installer.exe - - # Standalone - del ..\assets\apps_linux.json - 7z a sunshine-windows.zip ..\assets - 7z a sunshine-windows.zip sunshine.exe - 7z a sunshine-windows.zip tools\dxgi-info.exe - 7z a sunshine-windows.zip tools\audio-info.exe - 7z a sunshine-windows.zip tools\sunshinesvc.exe - 7z a sunshine-windows.zip ..\tools\install-service.bat - 7z a sunshine-windows.zip ..\tools\uninstall-service.bat - move "sunshine-windows.zip" "../artifacts" + mv Sunshine__.7z ../artifacts/sunshine-windows-standalone.7z + - name: Upload Sunshine Installer if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v3 with: name: sunshine-windows path: artifacts - - name: Upload Sunshine executable - if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} - uses: actions/upload-artifact@v3 - with: - name: sunshine-windows-standalone - path: sunshine-windows-build/artifacts/standalone + + release: + name: Create Release + runs-on: ubuntu-20.04 + needs: [ "build_win", "build_linux", "build_appimage" ] + steps: - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} uses: SunshineStream/actions/create_release@master @@ -289,4 +262,4 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} next_version: ${{ needs.check_changelog.outputs.next_version }} last_version: ${{ needs.check_changelog.outputs.last_version }} - release_body: ${{ needs.check_changelog.outputs.release_body }} + release_body: ${{ needs.check_changelog.outputs.release_body }} \ No newline at end of file From dc4393a583c4dc2bb28838b993c0383b56cd3d97 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Sat, 30 Apr 2022 10:02:26 +0100 Subject: [PATCH 244/817] CI: windows build packaging to ZIP, split artifacts upload for different packages --- .github/workflows/CI.yml | 44 ++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index b85dfcd7..f5cd2a62 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -53,7 +53,7 @@ jobs: build_appimage: name: AppImage runs-on: ubuntu-20.04 - needs: check_changelog + needs: [ check_versions, check_changelog ] steps: - name: Checkout @@ -61,7 +61,7 @@ jobs: with: submodules: recursive - - name: Setup Dependencies AppImage + - name: Setup Dependencies run: | mkdir -p artifacts sudo apt-get update -y && \ @@ -131,14 +131,14 @@ jobs: build_linux: name: Linux runs-on: ubuntu-20.04 - needs: check_changelog + needs: [ check_versions, check_changelog ] steps: - name: Checkout uses: actions/checkout@v3 with: submodules: recursive - - name: Setup Dependencies AppImage + - name: Setup Dependencies run: | mkdir -p artifacts sudo apt-get update -y && \ @@ -174,20 +174,27 @@ jobs: run: | cd build cpack -G DEB - mv Sunshine__.deb ../artifacts/ + mv Sunshine__.deb ../artifacts/sunshine.deb - name: Build RPM run: | cd build cpack -G RPM - mv Sunshine__.rpm ../artifacts/ + mv Sunshine__.rpm ../artifacts/sunshine.rpm - - name: Upload Artifacts - if: ${{ matrix.package == '-p' && ( github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' ) }} + - name: Upload Sunshine DEB + if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v3 with: - name: sunshine-linux - path: artifacts/ + name: sunshine.deb + path: artifacts/sunshine.deb + + - name: Upload Sunshine RPM + if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} + uses: actions/upload-artifact@v3 + with: + name: sunshine.rpm + path: artifacts/sunshine.rpm build_win: name: Windows @@ -235,20 +242,27 @@ jobs: cd sunshine-windows-build cpack -G NSIS - cpack -G 7Z + cpack -G ZIP mkdir -p ../artifacts # Installers mv Sunshine__.exe ../artifacts/sunshine-windows-installer.exe - mv Sunshine__.7z ../artifacts/sunshine-windows-standalone.7z + mv Sunshine__.zip ../artifacts/sunshine-windows-standalone.zip + + - name: Upload Sunshine Windows Installer + if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} + uses: actions/upload-artifact@v3 + with: + name: sunshine-windows-installer + path: artifacts/sunshine-windows-installer.exe - - name: Upload Sunshine Installer + - name: Upload Sunshine Windows Standalone if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v3 with: - name: sunshine-windows - path: artifacts + name: sunshine-windows-installer + path: artifacts/sunshine-windows-standalone.zip release: name: Create Release From 31f7faa6a504cb284962240ec9ad070d3594bc54 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Sat, 30 Apr 2022 10:18:10 +0100 Subject: [PATCH 245/817] win: installation directory fix --- .github/workflows/CI.yml | 2 +- CMakeLists.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f5cd2a62..160fff7f 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -261,7 +261,7 @@ jobs: if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v3 with: - name: sunshine-windows-installer + name: sunshine-windows-standalone path: artifacts/sunshine-windows-standalone.zip release: diff --git a/CMakeLists.txt b/CMakeLists.txt index f34a86b0..cfe45eca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -479,6 +479,7 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}\\\\sunshine.ico") set(CPACK_NSIS_INSTALLED_ICON_NAME "${PROJECT__DIR}\\\\${PROJECT_EXE}") + set(CPACK_PACKAGE_INSTALL_DIRECTORY "Sunshine") # The name of the directory that will be created in C:/Program files/ string(APPEND CPACK_NSIS_DEFINES "\n RequestExecutionLevel admin") # TODO: Not sure if this is needed but it took me a while to figure out where to put this option so I'm leaving it here # Sets permissions on the installed folder so that we can write in it From 2a69385aedbd1794c612d368aeff86186dddfd6a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 30 Apr 2022 12:38:08 -0400 Subject: [PATCH 246/817] Revert workflow logic --- .github/workflows/CI.yml | 131 ++++++++++++++++++++------------------- 1 file changed, 68 insertions(+), 63 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 160fff7f..87a9d9fb 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -5,7 +5,7 @@ on: branches: [master, nightly] types: [opened, synchronize, reopened] push: - branches: [ master ] + branches: [master] workflow_dispatch: jobs: @@ -32,12 +32,14 @@ jobs: check_versions: name: Check Versions runs-on: ubuntu-latest + needs: check_changelog + if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} + # base_ref for pull request check, ref for push steps: - name: Checkout uses: actions/checkout@v3 - name: Check CMakeLists.txt Version - if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} run: | version=$(grep -o -E '^project\(Sunshine VERSION [0-9]+\.[0-9]+\.[0-9]+\)' CMakeLists.txt | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') echo "cmakelists_version=${version}" >> $GITHUB_ENV @@ -53,17 +55,15 @@ jobs: build_appimage: name: AppImage runs-on: ubuntu-20.04 - needs: [ check_versions, check_changelog ] - + needs: check_changelog steps: - name: Checkout uses: actions/checkout@v3 with: submodules: recursive - - name: Setup Dependencies + - name: Setup Dependencies AppImage run: | - mkdir -p artifacts sudo apt-get update -y && \ sudo apt-get --reinstall install -y \ git wget gcc-10 g++-10 build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev @@ -82,11 +82,14 @@ jobs: CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}" SUNSHINE_EXECUTABLE_PATH="${SUNSHINE_EXECUTABLE_PATH:-/usr/bin/sunshine}" SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR:-sunshine.AppImage.config}" + SUNSHINE_ENABLE_WAYLAND=${SUNSHINE_ENABLE_WAYLAND:-ON} SUNSHINE_ENABLE_X11=${SUNSHINE_ENABLE_X11:-ON} SUNSHINE_ENABLE_DRM=${SUNSHINE_ENABLE_DRM:-ON} SUNSHINE_ENABLE_CUDA=${SUNSHINE_ENABLE_CUDA:-ON} + mkdir -p appimage-build && cd appimage-build + cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" "../" -DCMAKE_INSTALL_PREFIX=/usr make -j ${nproc} DESTDIR=AppDir @@ -95,16 +98,21 @@ jobs: run: | version=${{ needs.check_changelog.outputs.next_version_bare }} echo "VERSION=${version}" >> $GITHUB_ENV + - name: Package AppImage # https://docs.appimage.org/packaging-guide/index.html run: | mkdir -p appimage_temp && cd appimage_temp + DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" ICON_FILE="${ICON_FILE:-sunshine.png}" CONFIG_DIR="${CONFIG_DIR:-sunshine/sunshine.AppImage.config/}" HOME_DIR="${HOME_DIR:-sunshine/sunshine.AppImage.home/}" + wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage && chmod +x linuxdeploy-x86_64.AppImage + ./linuxdeploy-x86_64.AppImage --appdir ../AppDir -e ../appimage-build/sunshine -i "../$ICON_FILE" -d "../appimage-build/$DESKTOP_FILE" --output appimage + mv sunshine*.AppImage sunshine.AppImage mkdir sunshine && mv sunshine.AppImage sunshine/ ./sunshine/sunshine.AppImage --appimage-portable-config @@ -114,6 +122,8 @@ jobs: mkdir -p ./"$HOME_DIR"/.config/"$CONFIG_DIR" cp ./"$CONFIG_DIR"/apps_linux.json ./"$HOME_DIR"/.config/"$CONFIG_DIR" zip -r ./sunshine-appimage.zip ./sunshine/* + + mkdir -p artifacts mv sunshine-appimage.zip ../artifacts/ - name: Verify AppImage @@ -128,17 +138,25 @@ jobs: name: sunshine-appimage path: artifacts/ + - name: Create Release + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + uses: SunshineStream/actions/create_release@master + with: + token: ${{ secrets.GITHUB_TOKEN }} + next_version: ${{ needs.check_changelog.outputs.next_version }} + last_version: ${{ needs.check_changelog.outputs.last_version }} + release_body: ${{ needs.check_changelog.outputs.release_body }} + build_linux: name: Linux runs-on: ubuntu-20.04 - needs: [ check_versions, check_changelog ] + needs: check_changelog steps: - name: Checkout uses: actions/checkout@v3 with: submodules: recursive - - - name: Setup Dependencies + - name: Setup Dependencies Linux run: | mkdir -p artifacts sudo apt-get update -y && \ @@ -154,7 +172,7 @@ jobs: sudo apt-get upgrade -y && sudo apt-get dist-upgrade -y sudo apt-get install ffmpeg -y - - name: Build Sunshine + - name: Build Linux run: | CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}" SUNSHINE_EXECUTABLE_PATH="${SUNSHINE_EXECUTABLE_PATH:-/usr/bin/sunshine}" @@ -170,36 +188,37 @@ jobs: cd build cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_CONFIG_DIR=$SUNSHINE_CONFIG_DIR" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" "../" -DCMAKE_INSTALL_PREFIX=/usr - - name: Build DEB + - name: Package Linux run: | cd build - cpack -G DEB - mv Sunshine__.deb ../artifacts/sunshine.deb - - name: Build RPM - run: | - cd build + # package + cpack -G DEB cpack -G RPM + + # move + mv Sunshine__.deb ../artifacts/sunshine.deb mv Sunshine__.rpm ../artifacts/sunshine.rpm - - name: Upload Sunshine DEB - if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} + - name: Upload Artifacts + if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v3 with: - name: sunshine.deb - path: artifacts/sunshine.deb - - - name: Upload Sunshine RPM - if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} - uses: actions/upload-artifact@v3 + name: sunshine-linux + path: artifacts/ + - name: Create Release + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + uses: SunshineStream/actions/create_release@master with: - name: sunshine.rpm - path: artifacts/sunshine.rpm + token: ${{ secrets.GITHUB_TOKEN }} + next_version: ${{ needs.check_changelog.outputs.next_version }} + last_version: ${{ needs.check_changelog.outputs.last_version }} + release_body: ${{ needs.check_changelog.outputs.release_body }} build_win: name: Windows runs-on: windows-2019 - needs: [ check_versions, check_changelog ] + needs: check_changelog steps: - name: Checkout @@ -207,68 +226,54 @@ jobs: with: submodules: recursive - - name: MSYS2 Setup + - name: Setup Dependencies Windows uses: msys2/setup-msys2@v2 with: update: true install: >- base-devel + diffutils git + make mingw-w64-x86_64-binutils - mingw-w64-x86_64-openssl + mingw-w64-x86_64-boost mingw-w64-x86_64-cmake + mingw-w64-x86_64-nsis + mingw-w64-x86_64-openssl + mingw-w64-x86_64-opus mingw-w64-x86_64-toolchain - mingw-w64-x86_64-opus mingw-w64-x86_64-x265 - mingw-w64-x86_64-boost - mingw-w64-x86_64-nsis - git - yasm - nasm - diffutils - make + nasm + yasm - name: Build Windows shell: msys2 {0} run: | - mkdir sunshine-windows-build - cd sunshine-windows-build + mkdir build + cd build cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. mingw32-make -j2 - - - name: Create packages + - name: Package Windows shell: msys2 {0} run: | - cd sunshine-windows-build - + mkdir -p artifacts + cd build + + # package cpack -G NSIS cpack -G ZIP - - mkdir -p ../artifacts - - # Installers + + # move mv Sunshine__.exe ../artifacts/sunshine-windows-installer.exe mv Sunshine__.zip ../artifacts/sunshine-windows-standalone.zip - - name: Upload Sunshine Windows Installer - if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} - uses: actions/upload-artifact@v3 - with: - name: sunshine-windows-installer - path: artifacts/sunshine-windows-installer.exe - - - name: Upload Sunshine Windows Standalone + - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v3 with: - name: sunshine-windows-standalone - path: artifacts/sunshine-windows-standalone.zip + name: sunshine-windows + path: artifacts/ - release: - name: Create Release - runs-on: ubuntu-20.04 - needs: [ "build_win", "build_linux", "build_appimage" ] - steps: - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} uses: SunshineStream/actions/create_release@master @@ -276,4 +281,4 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} next_version: ${{ needs.check_changelog.outputs.next_version }} last_version: ${{ needs.check_changelog.outputs.last_version }} - release_body: ${{ needs.check_changelog.outputs.release_body }} \ No newline at end of file + release_body: ${{ needs.check_changelog.outputs.release_body }} From 369a941c48978f258eac26cc07ba959e3154d4b8 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 30 Apr 2022 17:51:14 -0400 Subject: [PATCH 247/817] Fix artifacts folder for AppImage --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 87a9d9fb..ff36850f 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -102,6 +102,7 @@ jobs: - name: Package AppImage # https://docs.appimage.org/packaging-guide/index.html run: | + mkdir -p artifacts mkdir -p appimage_temp && cd appimage_temp DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" @@ -123,7 +124,6 @@ jobs: cp ./"$CONFIG_DIR"/apps_linux.json ./"$HOME_DIR"/.config/"$CONFIG_DIR" zip -r ./sunshine-appimage.zip ./sunshine/* - mkdir -p artifacts mv sunshine-appimage.zip ../artifacts/ - name: Verify AppImage From 6741997e59b739b0bb20b94e2f99af6e3f04604d Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 30 Apr 2022 18:22:17 -0400 Subject: [PATCH 248/817] Update documentation for cpack --- docs/source/about/installation.rst | 15 +++++++++++++++ docs/source/building/linux.rst | 2 ++ docs/source/building/windows.rst | 9 +++++++-- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index d2b9d877..3900561a 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -27,6 +27,21 @@ AppImage .. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:appimage?logo=github&style=for-the-badge :alt: GitHub issues by-label +The current compatibility of the AppImage is shown below. + + - [✖] Debian oldstable (buster) + - [✔] Debian stable (bullseye) + - [✔] Debian testing (bookworm) + - [✔] Debian unstable (sid) + - [✔] Ubuntu jammy + - [✔] Ubuntu impish + - [✔] Ubuntu focal + - [✖] Ubuntu bionic + - [✖] Ubuntu xenial + - [✖] Ubuntu trusty + - [✖] CentOS 7 + + #. Download and extract `sunshine-appimage.zip` Debian Packages diff --git a/docs/source/building/linux.rst b/docs/source/building/linux.rst index a6e7eefb..475e0c66 100644 --- a/docs/source/building/linux.rst +++ b/docs/source/building/linux.rst @@ -239,6 +239,8 @@ Finally .. code-block:: bash make -j ${nproc} + cpack -G DEB # optionally, create a deb package + cpack -G RPM # optionally, create a rpm package Dockerfile Builds ----------------- diff --git a/docs/source/building/windows.rst b/docs/source/building/windows.rst index 36cea881..7708da49 100644 --- a/docs/source/building/windows.rst +++ b/docs/source/building/windows.rst @@ -8,7 +8,7 @@ Requirements First you need to install `MSYS2 `_, then startup "MSYS2 MinGW 64-bit" and install the following packages using: -.. code-block:: batch +.. code-block:: bash pacman -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git mingw-w64-x86_64-make cmake make gcc @@ -16,7 +16,12 @@ Build ----- .. Attention:: Ensure you are in the build directory created during the clone step earlier before continuing. - .. code-block:: batch + .. code-block:: bash cmake -G"Unix Makefiles" .. + cmake -G"MinGW Makefiles" .. # alternatively + mingw32-make + + cpack -G NSIS # optionally, create a windows installer + cpack -G ZIP # optionally, create a windows standalone package From 12bf5cffc5b1bb8da40a9053b7e8e17ab7110d67 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 30 Apr 2022 19:06:19 -0400 Subject: [PATCH 249/817] Use `CMAKE_PROJECT_NAME` --- .github/workflows/CI.yml | 9 +++++---- CMakeLists.txt | 12 ++++++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ff36850f..236d1602 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -197,8 +197,8 @@ jobs: cpack -G RPM # move - mv Sunshine__.deb ../artifacts/sunshine.deb - mv Sunshine__.rpm ../artifacts/sunshine.rpm + mv Sunshine.deb ../artifacts/sunshine.deb + mv Sunshine.rpm ../artifacts/sunshine.rpm - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} @@ -253,6 +253,7 @@ jobs: cd build cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. mingw32-make -j2 + - name: Package Windows shell: msys2 {0} run: | @@ -264,8 +265,8 @@ jobs: cpack -G ZIP # move - mv Sunshine__.exe ../artifacts/sunshine-windows-installer.exe - mv Sunshine__.zip ../artifacts/sunshine-windows-standalone.zip + mv Sunshine.exe ../artifacts/sunshine-windows-installer.exe + mv Sunshine.zip ../artifacts/sunshine-windows-standalone.zip - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} diff --git a/CMakeLists.txt b/CMakeLists.txt index cfe45eca..02a11376 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -460,7 +460,7 @@ set(CPACK_PACKAGE_DESCRIPTION "Gamestream host for Moonlight") set(CPACK_PACKAGE_HOMEPAGE_URL "https://sunshinestream.github.io") set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE) set(CPACK_PACKAGE_ICON ${PROJECT_SOURCE_DIR}/sunshine.png) -set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}") +set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}") set(CPACK_STRIP_FILES YES) # Platform specific options @@ -479,7 +479,7 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}\\\\sunshine.ico") set(CPACK_NSIS_INSTALLED_ICON_NAME "${PROJECT__DIR}\\\\${PROJECT_EXE}") - set(CPACK_PACKAGE_INSTALL_DIRECTORY "Sunshine") # The name of the directory that will be created in C:/Program files/ + set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}") # The name of the directory that will be created in C:/Program files/ string(APPEND CPACK_NSIS_DEFINES "\n RequestExecutionLevel admin") # TODO: Not sure if this is needed but it took me a while to figure out where to put this option so I'm leaving it here # Sets permissions on the installed folder so that we can write in it @@ -491,15 +491,15 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h # Adding an option for the start menu and PATH set(CPACK_NSIS_MODIFY_PATH "OFF") # TODO: it asks to add it to the PATH but then it seems I can't just run it from powershell set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".") - set(CPACK_NSIS_MUI_FINISHPAGE_RUN "${PROJECT_NAME}.exe") - set(CPACK_NSIS_INSTALLED_ICON_NAME "${PROJECT_NAME}.exe") # This will be shown on the installed apps Windows settings - set(CPACK_NSIS_CREATE_ICONS "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${PROJECT_NAME}.lnk' '\$INSTDIR\\\\${PROJECT_NAME}.exe'") + set(CPACK_NSIS_MUI_FINISHPAGE_RUN "${CMAKE_PROJECT_NAME}.exe") + set(CPACK_NSIS_INSTALLED_ICON_NAME "${CMAKE_PROJECT_NAME}.exe") # This will be shown on the installed apps Windows settings + set(CPACK_NSIS_CREATE_ICONS "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${CMAKE_PROJECT_NAME}.lnk' '\$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe'") # Checking for previous installed versions # set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL "ON") # TODO: doesn't work on my machine when Sunshine is already installed # Setting components groups and dependencies - set(CPACK_COMPONENT_APPLICATION_DISPLAY_NAME "Sunshine") + set(CPACK_COMPONENT_APPLICATION_DISPLAY_NAME "${CMAKE_PROJECT_NAME}") set(CPACK_COMPONENT_WEB_DISPLAY_NAME "Web interface") set(CPACK_COMPONENT_ASSETS_DISPLAY_NAME "Extra assets files") set(CPACK_COMPONENT_LIBRARIES_DISPLAY_NAME "Libraries") From 639af4f08a86fe70f23267ab44186ec4b11c5336 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 30 Apr 2022 19:20:16 -0400 Subject: [PATCH 250/817] Add MacOS build --- .github/workflows/CI.yml | 58 ++++++++++++++++++++++++++++-- CMakeLists.txt | 8 ++--- docs/source/about/installation.rst | 20 +++++++---- docs/source/building/macos.rst | 6 ++-- 4 files changed, 78 insertions(+), 14 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 236d1602..44e75515 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -215,6 +215,60 @@ jobs: last_version: ${{ needs.check_changelog.outputs.last_version }} release_body: ${{ needs.check_changelog.outputs.release_body }} + build_mac: + name: MacOS + runs-on: macos-11 + needs: check_changelog + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Setup Dependencies MacOS + run: | + # install dependencies using homebrew + brew install boost cmake ffmpeg opus + + # fix openssl header not found + cd /usr/local/include + ln -s ../opt/openssl/include/openssl . + + - name: Build MacOS + run: | + mkdir build + cd build + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets .. + make -j ${nproc} + + - name: Package MacOS + run: | + mkdir -p artifacts + cd build + + # package + cpack -G DragNDrop + + # move + mv Sunshine.dmg ../artifacts/sunshine.dmg + + - name: Upload Artifacts + if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} + uses: actions/upload-artifact@v3 + with: + name: sunshine-macos + path: artifacts/ + + - name: Create Release + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + uses: SunshineStream/actions/create_release@master + with: + token: ${{ secrets.GITHUB_TOKEN }} + next_version: ${{ needs.check_changelog.outputs.next_version }} + last_version: ${{ needs.check_changelog.outputs.last_version }} + release_body: ${{ needs.check_changelog.outputs.release_body }} + build_win: name: Windows runs-on: windows-2019 @@ -265,8 +319,8 @@ jobs: cpack -G ZIP # move - mv Sunshine.exe ../artifacts/sunshine-windows-installer.exe - mv Sunshine.zip ../artifacts/sunshine-windows-standalone.zip + mv Sunshine.exe ../artifacts/sunshine-windows.exe + mv Sunshine.zip ../artifacts/sunshine-windows.zip - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 02a11376..944bff64 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,8 +127,8 @@ if(WIN32) elseif(APPLE) add_compile_definitions(SUNSHINE_PLATFORM="macos") list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_mac.json") - link_directories(/opt/homebrew/lib/) # Default brew lib location link_directories(/opt/local/lib) + link_directories(/usr/local/lib) ADD_DEFINITIONS(-DBOOST_LOG_DYN_LINK) find_package(FFmpeg REQUIRED) @@ -531,9 +531,9 @@ if(APPLE) # TODO: test RUNTIME DESTINATION ${INSTALL_RUNTIME_DIR} COMPONENT Runtime) # TODO: bundle doesn't produce a valid .app use cpack -G DragNDrop - # set(CPACK_BUNDLE_NAME "Sunshine") - # set(CPACK_BUNDLE_PLIST "${SUNSHINE_ASSETS_DIR}/info.plist") - # set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/sunshine.icns") + set(CPACK_BUNDLE_NAME "${CMAKE_PROJECT_NAME}") + set(CPACK_BUNDLE_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/assets/Info.plist") + set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/sunshine.icns") endif() if(UNIX AND NOT APPLE) install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/web" DESTINATION "${SUNSHINE_CONFIG_DIR}") diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 3900561a..6634a714 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -78,11 +78,15 @@ MacOS .. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:macos?logo=github&style=for-the-badge :alt: GitHub issues by-label -#. Install `MacPorts `_ -#. Download the `Portfile `_ from this repository to - ``/tmp`` -#. In a terminal run ``cd /tmp && sudo port install`` -#. The first time you start Sunshine, you will be asked to grant access to screen recording and your microphone. +Disk Image File option: + #. Download and install ``sunshine.dmg`` + +Portfile option: + #. Install `MacPorts `_ + #. Download the `Portfile `_ from this repository + to ``/tmp`` + #. In a terminal run ``cd /tmp && sudo port install`` + #. The first time you start Sunshine, you will be asked to grant access to screen recording and your microphone. Windows ------- @@ -92,7 +96,11 @@ Windows .. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:11?logo=github&style=for-the-badge :alt: GitHub issues by-label -#. Download and extract ``sunshine-windows.zip`` +Installed option: + #. Download and install ``sunshine-windows.exe`` + +Standalone option: + #. Download and extract ``sunshine-windows.zip`` .. _latest release: https://github.com/SunshineStream/Sunshine/releases/latest .. _Dockerhub.io: https://hub.docker.com/repository/docker/sunshinestream/sunshine diff --git a/docs/source/building/macos.rst b/docs/source/building/macos.rst index 97a58a23..51ae6b15 100644 --- a/docs/source/building/macos.rst +++ b/docs/source/building/macos.rst @@ -14,14 +14,14 @@ MacPorts Install Requirements .. code-block:: bash - sudo port install cmake boost libopus ffmpeg + sudo port install cmake boost ffmpeg libopus Homebrew """""""" Install Requirements .. code-block:: bash - brew install boost cmake ffmpeg libopusenc + brew install boost cmake ffmpeg opus # if there are issues with an SSL header that is not found: cd /usr/local/include ln -s ../opt/openssl/include/openssl . @@ -35,6 +35,8 @@ Build cmake .. make -j ${nproc} + cpack -G DragNDrop # optionally, create a MacOS dmg package + If cmake fails complaining to find Boost, try to set the path explicitly. ``cmake -DBOOST_ROOT=[boost path] ..``, e.g., ``cmake -DBOOST_ROOT=/opt/local/libexec/boost/1.76 ..`` From e6d6d47be1a486a1d75248baaf20badd238245f3 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 5 May 2022 19:49:16 -0400 Subject: [PATCH 251/817] Create MacOS archive --- .github/workflows/CI.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 44e75515..791fe4eb 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -249,9 +249,11 @@ jobs: # package cpack -G DragNDrop + cpack -G ZIP # move - mv Sunshine.dmg ../artifacts/sunshine.dmg + mv Sunshine.dmg ../artifacts/sunshine-macos.dmg + mv Sunshine.zip ../artifacts/sunshine-macos.zip - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} From 5135c16bdaee6e35be646f939ff1bd8557cef712 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 5 May 2022 21:05:57 -0400 Subject: [PATCH 252/817] Update Portfile and... - Upload Portfile as artifact during builds and releases - Update assets for cpack packages --- .github/workflows/CI.yml | 1 + CMakeLists.txt | 21 ++++++++++++++++----- Portfile => Portfile.in | 18 ++++++++++-------- docs/source/about/installation.rst | 10 ++++++++-- 4 files changed, 35 insertions(+), 15 deletions(-) rename Portfile => Portfile.in (76%) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 791fe4eb..0e71e2de 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -254,6 +254,7 @@ jobs: # move mv Sunshine.dmg ../artifacts/sunshine-macos.dmg mv Sunshine.zip ../artifacts/sunshine-macos.zip + mv Portfile ../artifacts/Portfile - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 944bff64..675a5725 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -312,7 +312,6 @@ else() if(NOT DEFINED SUNSHINE_EXECUTABLE_PATH) set(SUNSHINE_EXECUTABLE_PATH "sunshine") endif() - configure_file(sunshine.desktop.in sunshine.desktop @ONLY) configure_file(sunshine.service.in sunshine.service @ONLY) endif() @@ -448,7 +447,7 @@ endforeach() target_compile_options(sunshine PRIVATE $<$:${SUNSHINE_COMPILE_OPTIONS}>;$<$:${SUNSHINE_COMPILE_OPTIONS_CUDA};-std=c++17>) ############# -# CPACK +# CPACK / Packaging #### # Common options @@ -474,6 +473,8 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/web" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT web) install(FILES "${SUNSHINE_ASSETS_DIR}/apps_windows.json" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) + install(FILES "${SUNSHINE_ASSETS_DIR}/box.png" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) + install(FILES "${SUNSHINE_ASSETS_DIR}/steam.png" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) install(FILES "${SUNSHINE_ASSETS_DIR}/sunshine.conf" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/shaders/directx" DESTINATION "${SUNSHINE_CONFIG_DIR}/shaders" COMPONENT assets) @@ -524,6 +525,8 @@ if(APPLE) # TODO: test set(INSTALL_RUNTIME_DIR "${prefix}/MacOS") install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/web" DESTINATION "${INSTALL_RUNTIME_DIR}") + install(FILES "${SUNSHINE_ASSETS_DIR}/apps_mac.json" DESTINATION "${INSTALL_RUNTIME_DIR}") + install(FILES "${SUNSHINE_ASSETS_DIR}/box.png" DESTINATION "${INSTALL_RUNTIME_DIR}") install(FILES "${SUNSHINE_ASSETS_DIR}/sunshine.conf" DESTINATION "${INSTALL_RUNTIME_DIR}") install(TARGETS sunshine @@ -534,13 +537,18 @@ if(APPLE) # TODO: test set(CPACK_BUNDLE_NAME "${CMAKE_PROJECT_NAME}") set(CPACK_BUNDLE_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/assets/Info.plist") set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/sunshine.icns") + + # Portfile + configure_file(Portfile.in Portfile @ONLY) endif() if(UNIX AND NOT APPLE) install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/web" DESTINATION "${SUNSHINE_CONFIG_DIR}") + install(FILES "${SUNSHINE_ASSETS_DIR}/85-sunshine-rules.rules" DESTINATION "/etc/udev/rules.d") + install(FILES "${SUNSHINE_ASSETS_DIR}/apps_linux.json" DESTINATION "${SUNSHINE_CONFIG_DIR}") + install(FILES "${SUNSHINE_ASSETS_DIR}/box.png" DESTINATION "${SUNSHINE_CONFIG_DIR}") + install(FILES "${SUNSHINE_ASSETS_DIR}/steam.png" DESTINATION "${SUNSHINE_CONFIG_DIR}") install(FILES "${SUNSHINE_ASSETS_DIR}/sunshine.conf" DESTINATION "${SUNSHINE_CONFIG_DIR}") - install(FILES "${SUNSHINE_ASSETS_DIR}/apps_linux.json" DESTINATION "${SUNSHINE_CONFIG_DIR}") - install(FILES "${SUNSHINE_ASSETS_DIR}/85-sunshine-rules.rules" DESTINATION "/etc/udev/rules.d") install(TARGETS sunshine RUNTIME DESTINATION "/usr/bin") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "/usr/lib/systemd/user") install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/shaders/opengl" DESTINATION "${SUNSHINE_CONFIG_DIR}/shaders") @@ -560,6 +568,9 @@ if(UNIX AND NOT APPLE) # Installation destination dir set(CPACK_SET_DESTDIR true) set(CMAKE_INSTALL_PREFIX "/etc/sunshine") + + # AppImage desktop file + configure_file(sunshine.desktop.in sunshine.desktop @ONLY) endif() -include(CPack) \ No newline at end of file +include(CPack) diff --git a/Portfile b/Portfile.in similarity index 76% rename from Portfile rename to Portfile.in index ea751ca8..6973ed40 100644 --- a/Portfile +++ b/Portfile.in @@ -5,25 +5,27 @@ PortGroup cmake 1.1 PortGroup github 1.0 PortGroup boost 1.0 -github.setup abusse sunshine macos-dev -version 20220224 +github.setup sunshinestream sunshine master +version @PROJECT_VERSION@ +revision 1 categories multimedia +license GPL-3 +maintainers {sunshinestream @SunshineStream} {outlook.com:anselm.busse} platforms darwin -license GPL-2 -maintainers {outlook.com:anselm.busse} fetch.type git post-fetch { system -W ${worksrcpath} "${git.cmd} submodule update --init --recursive" } -description Sunshine is a Gamestream host for Moonlight -long_description Sunshine is a Gamestream host for Moonlight - +description Sunshine is a Gamestream host for Moonlight. +long_description {*}${description} homepage https://github.com/SunshineStream/Sunshine -depends_lib port:avahi port:ffmpeg port:libopus +depends_lib port:avahi \ + port:ffmpeg \ + port:libopus boost.version 1.76 diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 6634a714..d7e043a4 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -81,13 +81,19 @@ MacOS Disk Image File option: #. Download and install ``sunshine.dmg`` + .. Warning:: The Disk Image File is experimental. Limited support will be provided. + Portfile option: #. Install `MacPorts `_ - #. Download the `Portfile `_ from this repository - to ``/tmp`` + #. Download the ``Portfile`` to ``/tmp`` #. In a terminal run ``cd /tmp && sudo port install`` #. The first time you start Sunshine, you will be asked to grant access to screen recording and your microphone. +Standalone option: + #. Download and extract ``sunshine-macos.zip`` + + .. Warning:: The Standalone package is experimental. Limited support will be provided. + Windows ------- .. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:10?logo=github&style=for-the-badge From 8b86abfcebbddb7963eca3abbb748ccbe3ed3283 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 5 May 2022 22:17:07 -0400 Subject: [PATCH 253/817] Reorganize assets --- .github/workflows/CI.yml | 35 +++++---- CMakeLists.txt | 38 ++++------ assets/{ => assets_common}/box.png | Bin assets/{ => assets_common}/steam.png | Bin assets/{ => assets_common}/sunshine.conf | 2 +- assets/{ => assets_common}/web/apps.html | 0 assets/{ => assets_common}/web/clients.html | 0 assets/{ => assets_common}/web/config.html | 0 .../fonts/fontawesome-free-web/LICENSE.txt | 0 .../fonts/fontawesome-free-web/attribution.js | 0 .../fontawesome-free-web/css/all.min.css | 0 .../fontawesome-free-web/css/brands.min.css | 0 .../css/fontawesome.min.css | 0 .../fontawesome-free-web/css/regular.min.css | 0 .../fontawesome-free-web/css/solid.min.css | 0 .../css/svg-with-js.min.css | 0 .../fontawesome-free-web/css/v4-shims.min.css | 0 .../webfonts/fa-brands-400.eot | Bin .../webfonts/fa-brands-400.svg | 0 .../webfonts/fa-brands-400.ttf | Bin .../webfonts/fa-brands-400.woff | Bin .../webfonts/fa-brands-400.woff2 | Bin .../webfonts/fa-regular-400.eot | Bin .../webfonts/fa-regular-400.svg | 0 .../webfonts/fa-regular-400.ttf | Bin .../webfonts/fa-regular-400.woff | Bin .../webfonts/fa-regular-400.woff2 | Bin .../webfonts/fa-solid-900.eot | Bin .../webfonts/fa-solid-900.svg | 0 .../webfonts/fa-solid-900.ttf | Bin .../webfonts/fa-solid-900.woff | Bin .../webfonts/fa-solid-900.woff2 | Bin .../web/header-no-nav.html | 0 assets/{ => assets_common}/web/header.html | 0 .../web/images/favicon.ico | Bin .../web/images/logo-sunshine-45.png | Bin assets/{ => assets_common}/web/index.html | 0 assets/{ => assets_common}/web/password.html | 0 assets/{ => assets_common}/web/pin.html | 0 .../web/third_party/bootstrap.bundle.min.js | 0 .../web/third_party/bootstrap.min.css | 0 .../web/third_party/vue.js | 0 .../web/troubleshooting.html | 0 assets/{ => assets_common}/web/welcome.html | 0 .../apps.json} | 0 .../shaders/opengl/ConvertUV.frag | 68 +++++++++--------- .../shaders/opengl/ConvertUV.vert | 52 +++++++------- .../shaders/opengl/ConvertY.frag | 50 ++++++------- .../shaders/opengl/Scene.frag | 26 +++---- .../shaders/opengl/Scene.vert | 42 +++++------ assets/{info.plist => assets_mac/Info.plist} | 0 .../{apps_mac.json => assets_mac/apps.json} | 0 .../apps.json} | 0 .../shaders/directx/ConvertUVPS.hlsl | 64 ++++++++--------- .../shaders/directx/ConvertUVVS.hlsl | 56 +++++++-------- .../shaders/directx/ConvertYPS.hlsl | 48 ++++++------- .../shaders/directx/ScenePS.hlsl | 26 +++---- .../shaders/directx/SceneVS.hlsl | 42 +++++------ .../{ => linux-misc}/85-sunshine-rules.rules | 0 59 files changed, 271 insertions(+), 278 deletions(-) rename assets/{ => assets_common}/box.png (100%) rename assets/{ => assets_common}/steam.png (100%) rename assets/{ => assets_common}/sunshine.conf (99%) rename assets/{ => assets_common}/web/apps.html (100%) rename assets/{ => assets_common}/web/clients.html (100%) rename assets/{ => assets_common}/web/config.html (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/LICENSE.txt (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/attribution.js (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/css/all.min.css (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/css/brands.min.css (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/css/fontawesome.min.css (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/css/regular.min.css (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/css/solid.min.css (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/css/svg-with-js.min.css (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/css/v4-shims.min.css (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.eot (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.svg (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.ttf (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff2 (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.eot (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.svg (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.ttf (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff2 (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.eot (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.svg (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.ttf (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff (100%) rename assets/{ => assets_common}/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff2 (100%) rename assets/{ => assets_common}/web/header-no-nav.html (100%) rename assets/{ => assets_common}/web/header.html (100%) rename assets/{ => assets_common}/web/images/favicon.ico (100%) rename assets/{ => assets_common}/web/images/logo-sunshine-45.png (100%) rename assets/{ => assets_common}/web/index.html (100%) rename assets/{ => assets_common}/web/password.html (100%) rename assets/{ => assets_common}/web/pin.html (100%) rename assets/{ => assets_common}/web/third_party/bootstrap.bundle.min.js (100%) rename assets/{ => assets_common}/web/third_party/bootstrap.min.css (100%) rename assets/{ => assets_common}/web/third_party/vue.js (100%) rename assets/{ => assets_common}/web/troubleshooting.html (100%) rename assets/{ => assets_common}/web/welcome.html (100%) rename assets/{apps_linux.json => assets_linux/apps.json} (100%) rename assets/{ => assets_linux}/shaders/opengl/ConvertUV.frag (96%) rename assets/{ => assets_linux}/shaders/opengl/ConvertUV.vert (95%) rename assets/{ => assets_linux}/shaders/opengl/ConvertY.frag (94%) rename assets/{ => assets_linux}/shaders/opengl/Scene.frag (93%) rename assets/{ => assets_linux}/shaders/opengl/Scene.vert (94%) rename assets/{info.plist => assets_mac/Info.plist} (100%) rename assets/{apps_mac.json => assets_mac/apps.json} (100%) rename assets/{apps_windows.json => assets_windows/apps.json} (100%) rename assets/{ => assets_windows}/shaders/directx/ConvertUVPS.hlsl (96%) rename assets/{ => assets_windows}/shaders/directx/ConvertUVVS.hlsl (96%) rename assets/{ => assets_windows}/shaders/directx/ConvertYPS.hlsl (95%) rename assets/{ => assets_windows}/shaders/directx/ScenePS.hlsl (95%) rename assets/{ => assets_windows}/shaders/directx/SceneVS.hlsl (95%) rename assets/{ => linux-misc}/85-sunshine-rules.rules (100%) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0e71e2de..504e706f 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -107,29 +107,34 @@ jobs: DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" ICON_FILE="${ICON_FILE:-sunshine.png}" - CONFIG_DIR="${CONFIG_DIR:-sunshine/sunshine.AppImage.config/}" - HOME_DIR="${HOME_DIR:-sunshine/sunshine.AppImage.home/}" + # CONFIG_DIR="${CONFIG_DIR:-sunshine/sunshine.AppImage.config/}" + # HOME_DIR="${HOME_DIR:-sunshine/sunshine.AppImage.home/}" wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage && chmod +x linuxdeploy-x86_64.AppImage ./linuxdeploy-x86_64.AppImage --appdir ../AppDir -e ../appimage-build/sunshine -i "../$ICON_FILE" -d "../appimage-build/$DESKTOP_FILE" --output appimage - mv sunshine*.AppImage sunshine.AppImage - mkdir sunshine && mv sunshine.AppImage sunshine/ - ./sunshine/sunshine.AppImage --appimage-portable-config - ./sunshine/sunshine.AppImage --appimage-portable-home - cp -r ../assets/* "$CONFIG_DIR" - rm -f "$CONFIG_DIR"/apps_windows.json - mkdir -p ./"$HOME_DIR"/.config/"$CONFIG_DIR" - cp ./"$CONFIG_DIR"/apps_linux.json ./"$HOME_DIR"/.config/"$CONFIG_DIR" - zip -r ./sunshine-appimage.zip ./sunshine/* + # mv sunshine*.AppImage sunshine.AppImage + # mkdir sunshine && mv sunshine.AppImage sunshine/ + # ./sunshine/sunshine.AppImage --appimage-portable-config + # ./sunshine/sunshine.AppImage --appimage-portable-home + # cp -r ../assets/assets_common/* "$CONFIG_DIR" + # cp -r ../assets/assets_linux/* "$CONFIG_DIR" - mv sunshine-appimage.zip ../artifacts/ + # mkdir -p ./"$HOME_DIR"/.config/"$CONFIG_DIR" + # cp ./"$CONFIG_DIR"/apps.json ./"$HOME_DIR"/.config/"$CONFIG_DIR" + # zip -r ./sunshine-appimage.zip ./sunshine/* + + # mv sunshine-appimage.zip ../artifacts/ + + mv sunshine*.AppImage ../artifacts/sunshine.AppImage + + # if testing succeeds, can remove commented lines - name: Verify AppImage run: | - cd appimage_temp - wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage && chmod +x appimagelint-x86_64.AppImage && ./appimagelint-x86_64.AppImage ./sunshine/sunshine.AppImage + # cd appimage_temp + wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage && chmod +x appimagelint-x86_64.AppImage && ./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} @@ -318,7 +323,7 @@ jobs: cd build # package - cpack -G NSIS + cpack -G NSIS cpack -G ZIP # move diff --git a/CMakeLists.txt b/CMakeLists.txt index 675a5725..ec31a2a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,8 +66,6 @@ if(WIN32) add_compile_definitions(SUNSHINE_PLATFORM="windows") add_subdirectory(tools) #This is temporary, only tools for Windows are needed, for now - list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_windows.json") - include_directories(third-party/ViGEmClient/include) if(NOT DEFINED SUNSHINE_ICON_PATH) @@ -126,7 +124,6 @@ if(WIN32) set_source_files_properties(third-party/ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_FLAGS "-Wno-unknown-pragmas -Wno-misleading-indentation -Wno-class-memaccess") elseif(APPLE) add_compile_definitions(SUNSHINE_PLATFORM="macos") - list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_mac.json") link_directories(/opt/local/lib) link_directories(/usr/local/lib) ADD_DEFINITIONS(-DBOOST_LOG_DYN_LINK) @@ -163,10 +160,9 @@ elseif(APPLE) sunshine/platform/macos/publish.cpp sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.c sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.h - ${CMAKE_CURRENT_SOURCE_DIR}/assets/Info.plist) + ${CMAKE_CURRENT_SOURCE_DIR}/assets/assets_mac/Info.plist) else() add_compile_definitions(SUNSHINE_PLATFORM="linux") - list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_linux.json") option(SUNSHINE_ENABLE_DRM "Enable KMS grab if available" ON) option(SUNSHINE_ENABLE_X11 "Enable X11 grab if available" ON) @@ -423,6 +419,7 @@ endif() list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR}") list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_CONFIG_DIR="${SUNSHINE_CONFIG_DIR}") list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_DEFAULT_DIR="${SUNSHINE_DEFAULT_DIR}") +list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps.json") add_executable(sunshine ${SUNSHINE_TARGET_FILES}) target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES} ${EXTRA_LIBS}) target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS}) @@ -437,7 +434,7 @@ if(NOT DEFINED CMAKE_CUDA_STANDARD) endif() if(APPLE) - target_link_options(sunshine PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,${CMAKE_CURRENT_SOURCE_DIR}/assets/Info.plist) + target_link_options(sunshine PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,${CMAKE_CURRENT_SOURCE_DIR}/assets/assets_mac/Info.plist) endif() foreach(flag IN LISTS SUNSHINE_COMPILE_OPTIONS) @@ -471,12 +468,8 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h install(TARGETS audio-info RUNTIME DESTINATION "tools" COMPONENT audio) install(TARGETS sunshinesvc RUNTIME DESTINATION "tools" COMPONENT sunshinesvc) - install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/web" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT web) - install(FILES "${SUNSHINE_ASSETS_DIR}/apps_windows.json" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) - install(FILES "${SUNSHINE_ASSETS_DIR}/box.png" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) - install(FILES "${SUNSHINE_ASSETS_DIR}/steam.png" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) - install(FILES "${SUNSHINE_ASSETS_DIR}/sunshine.conf" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) - install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/shaders/directx" DESTINATION "${SUNSHINE_CONFIG_DIR}/shaders" COMPONENT assets) + install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/assets_common/" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) + install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/assets_windows/" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}\\\\sunshine.ico") set(CPACK_NSIS_INSTALLED_ICON_NAME "${PROJECT__DIR}\\\\${PROJECT_EXE}") @@ -517,17 +510,15 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h set(CPACK_COMPONENT_AUDIO_GROUP "Extra Tools") set(CPACK_COMPONENT_SUNSHINESVC_GROUP "Extra Tools") - set(CPACK_COMPONENT_APPLICATION_DEPENDS web assets) + set(CPACK_COMPONENT_APPLICATION_DEPENDS assets) endif() if(APPLE) # TODO: test set(prefix "${CMAKE_PROJECT_NAME}.app/Contents") set(INSTALL_RUNTIME_DIR "${prefix}/MacOS") - install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/web" DESTINATION "${INSTALL_RUNTIME_DIR}") - install(FILES "${SUNSHINE_ASSETS_DIR}/apps_mac.json" DESTINATION "${INSTALL_RUNTIME_DIR}") - install(FILES "${SUNSHINE_ASSETS_DIR}/box.png" DESTINATION "${INSTALL_RUNTIME_DIR}") - install(FILES "${SUNSHINE_ASSETS_DIR}/sunshine.conf" DESTINATION "${INSTALL_RUNTIME_DIR}") + install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/assets_common/" DESTINATION "${INSTALL_RUNTIME_DIR}") + install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/assets_mac/" DESTINATION "${INSTALL_RUNTIME_DIR}") install(TARGETS sunshine BUNDLE DESTINATION . COMPONENT Runtime @@ -535,23 +526,20 @@ if(APPLE) # TODO: test # TODO: bundle doesn't produce a valid .app use cpack -G DragNDrop set(CPACK_BUNDLE_NAME "${CMAKE_PROJECT_NAME}") - set(CPACK_BUNDLE_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/assets/Info.plist") + set(CPACK_BUNDLE_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/assets_mac/Info.plist") set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/sunshine.icns") # Portfile configure_file(Portfile.in Portfile @ONLY) endif() if(UNIX AND NOT APPLE) - install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/web" DESTINATION "${SUNSHINE_CONFIG_DIR}") - install(FILES "${SUNSHINE_ASSETS_DIR}/85-sunshine-rules.rules" DESTINATION "/etc/udev/rules.d") - install(FILES "${SUNSHINE_ASSETS_DIR}/apps_linux.json" DESTINATION "${SUNSHINE_CONFIG_DIR}") - install(FILES "${SUNSHINE_ASSETS_DIR}/box.png" DESTINATION "${SUNSHINE_CONFIG_DIR}") - install(FILES "${SUNSHINE_ASSETS_DIR}/steam.png" DESTINATION "${SUNSHINE_CONFIG_DIR}") - install(FILES "${SUNSHINE_ASSETS_DIR}/sunshine.conf" DESTINATION "${SUNSHINE_CONFIG_DIR}") + install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/assets_common/" DESTINATION "${SUNSHINE_CONFIG_DIR}") + install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/assets_linux/" DESTINATION "${SUNSHINE_CONFIG_DIR}") + + install(FILES "${SUNSHINE_ASSETS_DIR}/linux-misc/85-sunshine-rules.rules" DESTINATION "/etc/udev/rules.d") install(TARGETS sunshine RUNTIME DESTINATION "/usr/bin") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "/usr/lib/systemd/user") - install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/shaders/opengl" DESTINATION "${SUNSHINE_CONFIG_DIR}/shaders") # Pre and post install set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA diff --git a/assets/box.png b/assets/assets_common/box.png similarity index 100% rename from assets/box.png rename to assets/assets_common/box.png diff --git a/assets/steam.png b/assets/assets_common/steam.png similarity index 100% rename from assets/steam.png rename to assets/assets_common/steam.png diff --git a/assets/sunshine.conf b/assets/assets_common/sunshine.conf similarity index 99% rename from assets/sunshine.conf rename to assets/assets_common/sunshine.conf index 509ff52a..04d51c2a 100644 --- a/assets/sunshine.conf +++ b/assets/assets_common/sunshine.conf @@ -1 +1 @@ -# See our documentation at https://sunshinestream.readthedocs.io/en/latest/about/advanced_usage.html +# See our documentation at https://sunshinestream.readthedocs.io/en/latest/about/advanced_usage.html diff --git a/assets/web/apps.html b/assets/assets_common/web/apps.html similarity index 100% rename from assets/web/apps.html rename to assets/assets_common/web/apps.html diff --git a/assets/web/clients.html b/assets/assets_common/web/clients.html similarity index 100% rename from assets/web/clients.html rename to assets/assets_common/web/clients.html diff --git a/assets/web/config.html b/assets/assets_common/web/config.html similarity index 100% rename from assets/web/config.html rename to assets/assets_common/web/config.html diff --git a/assets/web/fonts/fontawesome-free-web/LICENSE.txt b/assets/assets_common/web/fonts/fontawesome-free-web/LICENSE.txt similarity index 100% rename from assets/web/fonts/fontawesome-free-web/LICENSE.txt rename to assets/assets_common/web/fonts/fontawesome-free-web/LICENSE.txt diff --git a/assets/web/fonts/fontawesome-free-web/attribution.js b/assets/assets_common/web/fonts/fontawesome-free-web/attribution.js similarity index 100% rename from assets/web/fonts/fontawesome-free-web/attribution.js rename to assets/assets_common/web/fonts/fontawesome-free-web/attribution.js diff --git a/assets/web/fonts/fontawesome-free-web/css/all.min.css b/assets/assets_common/web/fonts/fontawesome-free-web/css/all.min.css similarity index 100% rename from assets/web/fonts/fontawesome-free-web/css/all.min.css rename to assets/assets_common/web/fonts/fontawesome-free-web/css/all.min.css diff --git a/assets/web/fonts/fontawesome-free-web/css/brands.min.css b/assets/assets_common/web/fonts/fontawesome-free-web/css/brands.min.css similarity index 100% rename from assets/web/fonts/fontawesome-free-web/css/brands.min.css rename to assets/assets_common/web/fonts/fontawesome-free-web/css/brands.min.css diff --git a/assets/web/fonts/fontawesome-free-web/css/fontawesome.min.css b/assets/assets_common/web/fonts/fontawesome-free-web/css/fontawesome.min.css similarity index 100% rename from assets/web/fonts/fontawesome-free-web/css/fontawesome.min.css rename to assets/assets_common/web/fonts/fontawesome-free-web/css/fontawesome.min.css diff --git a/assets/web/fonts/fontawesome-free-web/css/regular.min.css b/assets/assets_common/web/fonts/fontawesome-free-web/css/regular.min.css similarity index 100% rename from assets/web/fonts/fontawesome-free-web/css/regular.min.css rename to assets/assets_common/web/fonts/fontawesome-free-web/css/regular.min.css diff --git a/assets/web/fonts/fontawesome-free-web/css/solid.min.css b/assets/assets_common/web/fonts/fontawesome-free-web/css/solid.min.css similarity index 100% rename from assets/web/fonts/fontawesome-free-web/css/solid.min.css rename to assets/assets_common/web/fonts/fontawesome-free-web/css/solid.min.css diff --git a/assets/web/fonts/fontawesome-free-web/css/svg-with-js.min.css b/assets/assets_common/web/fonts/fontawesome-free-web/css/svg-with-js.min.css similarity index 100% rename from assets/web/fonts/fontawesome-free-web/css/svg-with-js.min.css rename to assets/assets_common/web/fonts/fontawesome-free-web/css/svg-with-js.min.css diff --git a/assets/web/fonts/fontawesome-free-web/css/v4-shims.min.css b/assets/assets_common/web/fonts/fontawesome-free-web/css/v4-shims.min.css similarity index 100% rename from assets/web/fonts/fontawesome-free-web/css/v4-shims.min.css rename to assets/assets_common/web/fonts/fontawesome-free-web/css/v4-shims.min.css diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.eot b/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.eot similarity index 100% rename from assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.eot rename to assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.eot diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.svg b/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.svg similarity index 100% rename from assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.svg rename to assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.svg diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.ttf b/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.ttf similarity index 100% rename from assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.ttf rename to assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.ttf diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff b/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff similarity index 100% rename from assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff rename to assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff2 b/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff2 similarity index 100% rename from assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff2 rename to assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff2 diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.eot b/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.eot similarity index 100% rename from assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.eot rename to assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.eot diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.svg b/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.svg similarity index 100% rename from assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.svg rename to assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.svg diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.ttf b/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.ttf similarity index 100% rename from assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.ttf rename to assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.ttf diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff b/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff similarity index 100% rename from assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff rename to assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff2 b/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff2 similarity index 100% rename from assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff2 rename to assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff2 diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.eot b/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.eot similarity index 100% rename from assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.eot rename to assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.eot diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.svg b/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.svg similarity index 100% rename from assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.svg rename to assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.svg diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.ttf b/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.ttf similarity index 100% rename from assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.ttf rename to assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.ttf diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff b/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff similarity index 100% rename from assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff rename to assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff diff --git a/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff2 b/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff2 similarity index 100% rename from assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff2 rename to assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff2 diff --git a/assets/web/header-no-nav.html b/assets/assets_common/web/header-no-nav.html similarity index 100% rename from assets/web/header-no-nav.html rename to assets/assets_common/web/header-no-nav.html diff --git a/assets/web/header.html b/assets/assets_common/web/header.html similarity index 100% rename from assets/web/header.html rename to assets/assets_common/web/header.html diff --git a/assets/web/images/favicon.ico b/assets/assets_common/web/images/favicon.ico similarity index 100% rename from assets/web/images/favicon.ico rename to assets/assets_common/web/images/favicon.ico diff --git a/assets/web/images/logo-sunshine-45.png b/assets/assets_common/web/images/logo-sunshine-45.png similarity index 100% rename from assets/web/images/logo-sunshine-45.png rename to assets/assets_common/web/images/logo-sunshine-45.png diff --git a/assets/web/index.html b/assets/assets_common/web/index.html similarity index 100% rename from assets/web/index.html rename to assets/assets_common/web/index.html diff --git a/assets/web/password.html b/assets/assets_common/web/password.html similarity index 100% rename from assets/web/password.html rename to assets/assets_common/web/password.html diff --git a/assets/web/pin.html b/assets/assets_common/web/pin.html similarity index 100% rename from assets/web/pin.html rename to assets/assets_common/web/pin.html diff --git a/assets/web/third_party/bootstrap.bundle.min.js b/assets/assets_common/web/third_party/bootstrap.bundle.min.js similarity index 100% rename from assets/web/third_party/bootstrap.bundle.min.js rename to assets/assets_common/web/third_party/bootstrap.bundle.min.js diff --git a/assets/web/third_party/bootstrap.min.css b/assets/assets_common/web/third_party/bootstrap.min.css similarity index 100% rename from assets/web/third_party/bootstrap.min.css rename to assets/assets_common/web/third_party/bootstrap.min.css diff --git a/assets/web/third_party/vue.js b/assets/assets_common/web/third_party/vue.js similarity index 100% rename from assets/web/third_party/vue.js rename to assets/assets_common/web/third_party/vue.js diff --git a/assets/web/troubleshooting.html b/assets/assets_common/web/troubleshooting.html similarity index 100% rename from assets/web/troubleshooting.html rename to assets/assets_common/web/troubleshooting.html diff --git a/assets/web/welcome.html b/assets/assets_common/web/welcome.html similarity index 100% rename from assets/web/welcome.html rename to assets/assets_common/web/welcome.html diff --git a/assets/apps_linux.json b/assets/assets_linux/apps.json similarity index 100% rename from assets/apps_linux.json rename to assets/assets_linux/apps.json diff --git a/assets/shaders/opengl/ConvertUV.frag b/assets/assets_linux/shaders/opengl/ConvertUV.frag similarity index 96% rename from assets/shaders/opengl/ConvertUV.frag rename to assets/assets_linux/shaders/opengl/ConvertUV.frag index 416e1e8b..4bd08287 100644 --- a/assets/shaders/opengl/ConvertUV.frag +++ b/assets/assets_linux/shaders/opengl/ConvertUV.frag @@ -1,35 +1,35 @@ -#version 300 es - -#ifdef GL_ES -precision lowp float; -#endif - -uniform sampler2D image; - -layout(shared) uniform ColorMatrix { - vec4 color_vec_y; - vec4 color_vec_u; - vec4 color_vec_v; - vec2 range_y; - vec2 range_uv; -}; - -in vec3 uuv; -layout(location = 0) out vec2 color; - -//-------------------------------------------------------------------------------------- -// Pixel Shader -//-------------------------------------------------------------------------------------- -void main() { - vec3 rgb_left = texture(image, uuv.xz).rgb; - vec3 rgb_right = texture(image, uuv.yz).rgb; - vec3 rgb = (rgb_left + rgb_right) * 0.5; - - float u = dot(color_vec_u.xyz, rgb) + color_vec_u.w; - float v = dot(color_vec_v.xyz, rgb) + color_vec_v.w; - - u = u * range_uv.x + range_uv.y; - v = v * range_uv.x + range_uv.y; - - color = vec2(u, v * 224.0f / 256.0f + 0.0625); +#version 300 es + +#ifdef GL_ES +precision lowp float; +#endif + +uniform sampler2D image; + +layout(shared) uniform ColorMatrix { + vec4 color_vec_y; + vec4 color_vec_u; + vec4 color_vec_v; + vec2 range_y; + vec2 range_uv; +}; + +in vec3 uuv; +layout(location = 0) out vec2 color; + +//-------------------------------------------------------------------------------------- +// Pixel Shader +//-------------------------------------------------------------------------------------- +void main() { + vec3 rgb_left = texture(image, uuv.xz).rgb; + vec3 rgb_right = texture(image, uuv.yz).rgb; + vec3 rgb = (rgb_left + rgb_right) * 0.5; + + float u = dot(color_vec_u.xyz, rgb) + color_vec_u.w; + float v = dot(color_vec_v.xyz, rgb) + color_vec_v.w; + + u = u * range_uv.x + range_uv.y; + v = v * range_uv.x + range_uv.y; + + color = vec2(u, v * 224.0f / 256.0f + 0.0625); } \ No newline at end of file diff --git a/assets/shaders/opengl/ConvertUV.vert b/assets/assets_linux/shaders/opengl/ConvertUV.vert similarity index 95% rename from assets/shaders/opengl/ConvertUV.vert rename to assets/assets_linux/shaders/opengl/ConvertUV.vert index a71fe58d..70c14f4c 100644 --- a/assets/shaders/opengl/ConvertUV.vert +++ b/assets/assets_linux/shaders/opengl/ConvertUV.vert @@ -1,27 +1,27 @@ -#version 300 es - -#ifdef GL_ES -precision mediump float; -#endif - -uniform float width_i; - -out vec3 uuv; -//-------------------------------------------------------------------------------------- -// Vertex Shader -//-------------------------------------------------------------------------------------- -void main() -{ - float idHigh = float(gl_VertexID >> 1); - float idLow = float(gl_VertexID & int(1)); - - float x = idHigh * 4.0 - 1.0; - float y = idLow * 4.0 - 1.0; - - float u_right = idHigh * 2.0; - float u_left = u_right - width_i; - float v = idLow * 2.0; - - uuv = vec3(u_left, u_right, v); - gl_Position = vec4(x, y, 0.0, 1.0); +#version 300 es + +#ifdef GL_ES +precision mediump float; +#endif + +uniform float width_i; + +out vec3 uuv; +//-------------------------------------------------------------------------------------- +// Vertex Shader +//-------------------------------------------------------------------------------------- +void main() +{ + float idHigh = float(gl_VertexID >> 1); + float idLow = float(gl_VertexID & int(1)); + + float x = idHigh * 4.0 - 1.0; + float y = idLow * 4.0 - 1.0; + + float u_right = idHigh * 2.0; + float u_left = u_right - width_i; + float v = idLow * 2.0; + + uuv = vec3(u_left, u_right, v); + gl_Position = vec4(x, y, 0.0, 1.0); } \ No newline at end of file diff --git a/assets/shaders/opengl/ConvertY.frag b/assets/assets_linux/shaders/opengl/ConvertY.frag similarity index 94% rename from assets/shaders/opengl/ConvertY.frag rename to assets/assets_linux/shaders/opengl/ConvertY.frag index 564fa25a..dfd5774e 100644 --- a/assets/shaders/opengl/ConvertY.frag +++ b/assets/assets_linux/shaders/opengl/ConvertY.frag @@ -1,26 +1,26 @@ -#version 300 es - -#ifdef GL_ES -precision lowp float; -#endif - -uniform sampler2D image; - -layout(shared) uniform ColorMatrix { - vec4 color_vec_y; - vec4 color_vec_u; - vec4 color_vec_v; - vec2 range_y; - vec2 range_uv; -}; - -in vec2 tex; -layout(location = 0) out float color; - -void main() -{ - vec3 rgb = texture(image, tex).rgb; - float y = dot(color_vec_y.xyz, rgb); - - color = y * range_y.x + range_y.y; +#version 300 es + +#ifdef GL_ES +precision lowp float; +#endif + +uniform sampler2D image; + +layout(shared) uniform ColorMatrix { + vec4 color_vec_y; + vec4 color_vec_u; + vec4 color_vec_v; + vec2 range_y; + vec2 range_uv; +}; + +in vec2 tex; +layout(location = 0) out float color; + +void main() +{ + vec3 rgb = texture(image, tex).rgb; + float y = dot(color_vec_y.xyz, rgb); + + color = y * range_y.x + range_y.y; } \ No newline at end of file diff --git a/assets/shaders/opengl/Scene.frag b/assets/assets_linux/shaders/opengl/Scene.frag similarity index 93% rename from assets/shaders/opengl/Scene.frag rename to assets/assets_linux/shaders/opengl/Scene.frag index 6375f1fd..ee4b36b9 100644 --- a/assets/shaders/opengl/Scene.frag +++ b/assets/assets_linux/shaders/opengl/Scene.frag @@ -1,14 +1,14 @@ -#version 300 es - -#ifdef GL_ES -precision lowp float; -#endif - -uniform sampler2D image; - -in vec2 tex; -layout(location = 0) out vec4 color; -void main() -{ - color = texture(image, tex); +#version 300 es + +#ifdef GL_ES +precision lowp float; +#endif + +uniform sampler2D image; + +in vec2 tex; +layout(location = 0) out vec4 color; +void main() +{ + color = texture(image, tex); } \ No newline at end of file diff --git a/assets/shaders/opengl/Scene.vert b/assets/assets_linux/shaders/opengl/Scene.vert similarity index 94% rename from assets/shaders/opengl/Scene.vert rename to assets/assets_linux/shaders/opengl/Scene.vert index 258878f4..24c16bef 100644 --- a/assets/shaders/opengl/Scene.vert +++ b/assets/assets_linux/shaders/opengl/Scene.vert @@ -1,22 +1,22 @@ -#version 300 es - -#ifdef GL_ES -precision mediump float; -#endif - -out vec2 tex; - -void main() -{ - float idHigh = float(gl_VertexID >> 1); - float idLow = float(gl_VertexID & int(1)); - - float x = idHigh * 4.0 - 1.0; - float y = idLow * 4.0 - 1.0; - - float u = idHigh * 2.0; - float v = idLow * 2.0; - - gl_Position = vec4(x, y, 0.0, 1.0); - tex = vec2(u, v); +#version 300 es + +#ifdef GL_ES +precision mediump float; +#endif + +out vec2 tex; + +void main() +{ + float idHigh = float(gl_VertexID >> 1); + float idLow = float(gl_VertexID & int(1)); + + float x = idHigh * 4.0 - 1.0; + float y = idLow * 4.0 - 1.0; + + float u = idHigh * 2.0; + float v = idLow * 2.0; + + gl_Position = vec4(x, y, 0.0, 1.0); + tex = vec2(u, v); } \ No newline at end of file diff --git a/assets/info.plist b/assets/assets_mac/Info.plist similarity index 100% rename from assets/info.plist rename to assets/assets_mac/Info.plist diff --git a/assets/apps_mac.json b/assets/assets_mac/apps.json similarity index 100% rename from assets/apps_mac.json rename to assets/assets_mac/apps.json diff --git a/assets/apps_windows.json b/assets/assets_windows/apps.json similarity index 100% rename from assets/apps_windows.json rename to assets/assets_windows/apps.json diff --git a/assets/shaders/directx/ConvertUVPS.hlsl b/assets/assets_windows/shaders/directx/ConvertUVPS.hlsl similarity index 96% rename from assets/shaders/directx/ConvertUVPS.hlsl rename to assets/assets_windows/shaders/directx/ConvertUVPS.hlsl index 2b72cddf..f9bf69df 100644 --- a/assets/shaders/directx/ConvertUVPS.hlsl +++ b/assets/assets_windows/shaders/directx/ConvertUVPS.hlsl @@ -1,33 +1,33 @@ -Texture2D image : register(t0); - -SamplerState def_sampler : register(s0); - -struct FragTexWide { - float3 uuv : TEXCOORD0; -}; - -cbuffer ColorMatrix : register(b0) { - float4 color_vec_y; - float4 color_vec_u; - float4 color_vec_v; - float2 range_y; - float2 range_uv; -}; - -//-------------------------------------------------------------------------------------- -// Pixel Shader -//-------------------------------------------------------------------------------------- -float2 main_ps(FragTexWide input) : SV_Target -{ - float3 rgb_left = image.Sample(def_sampler, input.uuv.xz).rgb; - float3 rgb_right = image.Sample(def_sampler, input.uuv.yz).rgb; - float3 rgb = (rgb_left + rgb_right) * 0.5; - - float u = dot(color_vec_u.xyz, rgb) + color_vec_u.w; - float v = dot(color_vec_v.xyz, rgb) + color_vec_v.w; - - u = u * range_uv.x + range_uv.y; - v = v * range_uv.x + range_uv.y; - - return float2(u, v * 224.0f/256.0f + 0.0625); +Texture2D image : register(t0); + +SamplerState def_sampler : register(s0); + +struct FragTexWide { + float3 uuv : TEXCOORD0; +}; + +cbuffer ColorMatrix : register(b0) { + float4 color_vec_y; + float4 color_vec_u; + float4 color_vec_v; + float2 range_y; + float2 range_uv; +}; + +//-------------------------------------------------------------------------------------- +// Pixel Shader +//-------------------------------------------------------------------------------------- +float2 main_ps(FragTexWide input) : SV_Target +{ + float3 rgb_left = image.Sample(def_sampler, input.uuv.xz).rgb; + float3 rgb_right = image.Sample(def_sampler, input.uuv.yz).rgb; + float3 rgb = (rgb_left + rgb_right) * 0.5; + + float u = dot(color_vec_u.xyz, rgb) + color_vec_u.w; + float v = dot(color_vec_v.xyz, rgb) + color_vec_v.w; + + u = u * range_uv.x + range_uv.y; + v = v * range_uv.x + range_uv.y; + + return float2(u, v * 224.0f/256.0f + 0.0625); } \ No newline at end of file diff --git a/assets/shaders/directx/ConvertUVVS.hlsl b/assets/assets_windows/shaders/directx/ConvertUVVS.hlsl similarity index 96% rename from assets/shaders/directx/ConvertUVVS.hlsl rename to assets/assets_windows/shaders/directx/ConvertUVVS.hlsl index 66d97d43..77ff38d7 100644 --- a/assets/shaders/directx/ConvertUVVS.hlsl +++ b/assets/assets_windows/shaders/directx/ConvertUVVS.hlsl @@ -1,29 +1,29 @@ -struct VertTexPosWide { - float3 uuv : TEXCOORD; - float4 pos : SV_POSITION; -}; - -cbuffer info : register(b0) { - float width_i; -}; - -//-------------------------------------------------------------------------------------- -// Vertex Shader -//-------------------------------------------------------------------------------------- -VertTexPosWide main_vs(uint vI : SV_VERTEXID) -{ - float idHigh = float(vI >> 1); - float idLow = float(vI & uint(1)); - - float x = idHigh * 4.0 - 1.0; - float y = idLow * 4.0 - 1.0; - - float u_right = idHigh * 2.0; - float u_left = u_right - width_i; - float v = 1.0 - idLow * 2.0; - - VertTexPosWide vert_out; - vert_out.uuv = float3(u_left, u_right, v); - vert_out.pos = float4(x, y, 0.0, 1.0); - return vert_out; +struct VertTexPosWide { + float3 uuv : TEXCOORD; + float4 pos : SV_POSITION; +}; + +cbuffer info : register(b0) { + float width_i; +}; + +//-------------------------------------------------------------------------------------- +// Vertex Shader +//-------------------------------------------------------------------------------------- +VertTexPosWide main_vs(uint vI : SV_VERTEXID) +{ + float idHigh = float(vI >> 1); + float idLow = float(vI & uint(1)); + + float x = idHigh * 4.0 - 1.0; + float y = idLow * 4.0 - 1.0; + + float u_right = idHigh * 2.0; + float u_left = u_right - width_i; + float v = 1.0 - idLow * 2.0; + + VertTexPosWide vert_out; + vert_out.uuv = float3(u_left, u_right, v); + vert_out.pos = float4(x, y, 0.0, 1.0); + return vert_out; } \ No newline at end of file diff --git a/assets/shaders/directx/ConvertYPS.hlsl b/assets/assets_windows/shaders/directx/ConvertYPS.hlsl similarity index 95% rename from assets/shaders/directx/ConvertYPS.hlsl rename to assets/assets_windows/shaders/directx/ConvertYPS.hlsl index 386133c8..c38d19c8 100644 --- a/assets/shaders/directx/ConvertYPS.hlsl +++ b/assets/assets_windows/shaders/directx/ConvertYPS.hlsl @@ -1,25 +1,25 @@ -Texture2D image : register(t0); - -SamplerState def_sampler : register(s0); - -cbuffer ColorMatrix : register(b0) { - float4 color_vec_y; - float4 color_vec_u; - float4 color_vec_v; - float2 range_y; - float2 range_uv; -}; - -struct PS_INPUT -{ - float4 pos : SV_POSITION; - float2 tex : TEXCOORD; -}; - -float main_ps(PS_INPUT frag_in) : SV_Target -{ - float3 rgb = image.Sample(def_sampler, frag_in.tex, 0).rgb; - float y = dot(color_vec_y.xyz, rgb); - - return y * range_y.x + range_y.y; +Texture2D image : register(t0); + +SamplerState def_sampler : register(s0); + +cbuffer ColorMatrix : register(b0) { + float4 color_vec_y; + float4 color_vec_u; + float4 color_vec_v; + float2 range_y; + float2 range_uv; +}; + +struct PS_INPUT +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD; +}; + +float main_ps(PS_INPUT frag_in) : SV_Target +{ + float3 rgb = image.Sample(def_sampler, frag_in.tex, 0).rgb; + float y = dot(color_vec_y.xyz, rgb); + + return y * range_y.x + range_y.y; } \ No newline at end of file diff --git a/assets/shaders/directx/ScenePS.hlsl b/assets/assets_windows/shaders/directx/ScenePS.hlsl similarity index 95% rename from assets/shaders/directx/ScenePS.hlsl rename to assets/assets_windows/shaders/directx/ScenePS.hlsl index aa601231..53a9bc8d 100644 --- a/assets/shaders/directx/ScenePS.hlsl +++ b/assets/assets_windows/shaders/directx/ScenePS.hlsl @@ -1,14 +1,14 @@ -Texture2D image : register(t0); - -SamplerState def_sampler : register(s0); - -struct PS_INPUT -{ - float4 pos : SV_POSITION; - float2 tex : TEXCOORD; -}; - -float4 main_ps(PS_INPUT frag_in) : SV_Target -{ - return image.Sample(def_sampler, frag_in.tex, 0); +Texture2D image : register(t0); + +SamplerState def_sampler : register(s0); + +struct PS_INPUT +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD; +}; + +float4 main_ps(PS_INPUT frag_in) : SV_Target +{ + return image.Sample(def_sampler, frag_in.tex, 0); } \ No newline at end of file diff --git a/assets/shaders/directx/SceneVS.hlsl b/assets/assets_windows/shaders/directx/SceneVS.hlsl similarity index 95% rename from assets/shaders/directx/SceneVS.hlsl rename to assets/assets_windows/shaders/directx/SceneVS.hlsl index 51319ddb..3afaffc6 100644 --- a/assets/shaders/directx/SceneVS.hlsl +++ b/assets/assets_windows/shaders/directx/SceneVS.hlsl @@ -1,22 +1,22 @@ -struct PS_INPUT -{ - float4 pos : SV_POSITION; - float2 tex : TEXCOORD; -}; - -PS_INPUT main_vs(uint vI : SV_VERTEXID) -{ - float idHigh = float(vI >> 1); - float idLow = float(vI & uint(1)); - - float x = idHigh * 4.0 - 1.0; - float y = idLow * 4.0 - 1.0; - - float u = idHigh * 2.0; - float v = 1.0 - idLow * 2.0; - - PS_INPUT vert_out; - vert_out.pos = float4(x, y, 0.0, 1.0); - vert_out.tex = float2(u, v); - return vert_out; +struct PS_INPUT +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD; +}; + +PS_INPUT main_vs(uint vI : SV_VERTEXID) +{ + float idHigh = float(vI >> 1); + float idLow = float(vI & uint(1)); + + float x = idHigh * 4.0 - 1.0; + float y = idLow * 4.0 - 1.0; + + float u = idHigh * 2.0; + float v = 1.0 - idLow * 2.0; + + PS_INPUT vert_out; + vert_out.pos = float4(x, y, 0.0, 1.0); + vert_out.tex = float2(u, v); + return vert_out; } \ No newline at end of file diff --git a/assets/85-sunshine-rules.rules b/assets/linux-misc/85-sunshine-rules.rules similarity index 100% rename from assets/85-sunshine-rules.rules rename to assets/linux-misc/85-sunshine-rules.rules From ca6f02c953768520786f2d4221efe1919a16618d Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 5 May 2022 23:58:08 -0400 Subject: [PATCH 254/817] Modify windows installer --- CMakeLists.txt | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ec31a2a9..93ff2307 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -471,8 +471,9 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/assets_common/" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/assets_windows/" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) - set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}\\\\sunshine.ico") - set(CPACK_NSIS_INSTALLED_ICON_NAME "${PROJECT__DIR}\\\\${PROJECT_EXE}") + set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}\\\\sunshine.ico") + set(CPACK_NSIS_INSTALLED_ICON_NAME "${PROJECT__DIR}\\\\${PROJECT_EXE}") + set(CPACK_NSIS_MUI_HEADERIMAGE "${CPACK_PACKAGE_ICON}") # Header image for installer set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}") # The name of the directory that will be created in C:/Program files/ string(APPEND CPACK_NSIS_DEFINES "\n RequestExecutionLevel admin") # TODO: Not sure if this is needed but it took me a while to figure out where to put this option so I'm leaving it here @@ -483,7 +484,7 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h ") # Adding an option for the start menu and PATH - set(CPACK_NSIS_MODIFY_PATH "OFF") # TODO: it asks to add it to the PATH but then it seems I can't just run it from powershell + set(CPACK_NSIS_MODIFY_PATH "ON") # TODO: it asks to add it to the PATH but then it seems I can't just run it from powershell set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".") set(CPACK_NSIS_MUI_FINISHPAGE_RUN "${CMAKE_PROJECT_NAME}.exe") set(CPACK_NSIS_INSTALLED_ICON_NAME "${CMAKE_PROJECT_NAME}.exe") # This will be shown on the installed apps Windows settings @@ -493,22 +494,33 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h # set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL "ON") # TODO: doesn't work on my machine when Sunshine is already installed # Setting components groups and dependencies + # sunshine binary set(CPACK_COMPONENT_APPLICATION_DISPLAY_NAME "${CMAKE_PROJECT_NAME}") - set(CPACK_COMPONENT_WEB_DISPLAY_NAME "Web interface") - set(CPACK_COMPONENT_ASSETS_DISPLAY_NAME "Extra assets files") - set(CPACK_COMPONENT_LIBRARIES_DISPLAY_NAME "Libraries") - - set(CPACK_COMPONENT_APPLICATION_GROUP "Runtime") - set(CPACK_COMPONENT_WEB_GROUP "Runtime") - set(CPACK_COMPONENT_ASSETS_GROUP "Runtime") + set(CPACK_COMPONENT_APPLICATION_DESCRIPTION "The main application.") + set(CPACK_COMPONENT_APPLICATION_GROUP "${CMAKE_PROJECT_NAME}") + set(CPACK_COMPONENT_APPLICATION_REQUIRED true) + + # assets + set(CPACK_COMPONENT_ASSETS_DISPLAY_NAME "Assets") + set(CPACK_COMPONENT_ASSETS_DESCRIPTION "Assets include the apps and configurations, shaders, default box art, and web ui.") + set(CPACK_COMPONENT_ASSETS_GROUP "${CMAKE_PROJECT_NAME}") + set(CPACK_COMPONENT_ASSETS_DEPENDS "${CMAKE_PROJECT_NAME}") + set(CPACK_COMPONENT_ASSETS_REQUIRED true) + + # audio tool + set(CPACK_COMPONENT_AUDIO_DISPLAY_NAME "audio-info.exe") + set(CPACK_COMPONENT_AUDIO_DESCRIPTION "CLI tool that allows you to get information about sound devices.") + set(CPACK_COMPONENT_AUDIO_GROUP "Tools") + # display tool set(CPACK_COMPONENT_DXGI_DISPLAY_NAME "dxgi-info.exe") - set(CPACK_COMPONENT_AUDIO_DISPLAY_NAME "audio-info.exe") - set(CPACK_COMPONENT_SUNSHINESVC_DISPLAY_NAME "sunshinesvc.exe") + set(CPACK_COMPONENT_DXGI_DESCRIPTION "CLI tool that allows you to get information about graphics cards and displays.") + set(CPACK_COMPONENT_DXGI_GROUP "Tools") - set(CPACK_COMPONENT_DXGI_GROUP "Extra Tools") - set(CPACK_COMPONENT_AUDIO_GROUP "Extra Tools") - set(CPACK_COMPONENT_SUNSHINESVC_GROUP "Extra Tools") + # service tool + set(CPACK_COMPONENT_SUNSHINESVC_DISPLAY_NAME "sunshinesvc.exe") + set(CPACK_COMPONENT_SUNSHINESVC_DESCRIPTION "CLI tool that allows you to enable/disable the Sunshine service.") + set(CPACK_COMPONENT_SUNSHINESVC_GROUP "Tools") set(CPACK_COMPONENT_APPLICATION_DEPENDS assets) endif() From 0d0496adf331670f6ea4f050af2c76b79d74b902 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 6 May 2022 10:04:29 -0400 Subject: [PATCH 255/817] Remove CPACK_NSIS_MUI_HEADERIMAGE --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 93ff2307..2c09a1cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -471,9 +471,11 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/assets_common/" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/assets_windows/" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) + + # set(CPACK_NSIS_MUI_HEADERIMAGE "") # TODO: image should be 150x57 bmp + set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}\\\\sunshine.ico") set(CPACK_NSIS_INSTALLED_ICON_NAME "${PROJECT__DIR}\\\\${PROJECT_EXE}") - set(CPACK_NSIS_MUI_HEADERIMAGE "${CPACK_PACKAGE_ICON}") # Header image for installer set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}") # The name of the directory that will be created in C:/Program files/ string(APPEND CPACK_NSIS_DEFINES "\n RequestExecutionLevel admin") # TODO: Not sure if this is needed but it took me a while to figure out where to put this option so I'm leaving it here From c4441db606d37c34a69d6dc5299a9955155156c0 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 6 May 2022 10:16:59 -0400 Subject: [PATCH 256/817] Remove CPACK_COMPONENT_APPLICATION_DEPENDS --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c09a1cd..c12957b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -524,7 +524,7 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h set(CPACK_COMPONENT_SUNSHINESVC_DESCRIPTION "CLI tool that allows you to enable/disable the Sunshine service.") set(CPACK_COMPONENT_SUNSHINESVC_GROUP "Tools") - set(CPACK_COMPONENT_APPLICATION_DEPENDS assets) + # set(CPACK_COMPONENT_APPLICATION_DEPENDS assets) endif() if(APPLE) # TODO: test From c409022df53d273c03e9f1812d6e1a4e6780e78f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 6 May 2022 12:55:43 -0400 Subject: [PATCH 257/817] Combine AppImage and Linux build --- .github/workflows/CI.yml | 149 +++++++++------------------------------ CMakeLists.txt | 9 ++- 2 files changed, 36 insertions(+), 122 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 504e706f..e9d2a4bc 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -52,8 +52,8 @@ jobs: echo Within 'CMakeLists.txt' change "project(Sunshine VERSION $cmakelists_version)" to "project(Sunshine VERSION ${{ needs.check_changelog.outputs.next_version_bare }})" exit 1 - build_appimage: - name: AppImage + build_linux: + name: Linux runs-on: ubuntu-20.04 needs: check_changelog steps: @@ -61,8 +61,7 @@ jobs: uses: actions/checkout@v3 with: submodules: recursive - - - name: Setup Dependencies AppImage + - name: Setup Dependencies Linux run: | sudo apt-get update -y && \ sudo apt-get --reinstall install -y \ @@ -77,21 +76,13 @@ jobs: sudo apt-get upgrade -y && sudo apt-get dist-upgrade -y sudo apt-get install ffmpeg -y - - name: Build AppImage + - name: Build Linux run: | - CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}" - SUNSHINE_EXECUTABLE_PATH="${SUNSHINE_EXECUTABLE_PATH:-/usr/bin/sunshine}" - SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR:-sunshine.AppImage.config}" - - SUNSHINE_ENABLE_WAYLAND=${SUNSHINE_ENABLE_WAYLAND:-ON} - SUNSHINE_ENABLE_X11=${SUNSHINE_ENABLE_X11:-ON} - SUNSHINE_ENABLE_DRM=${SUNSHINE_ENABLE_DRM:-ON} - SUNSHINE_ENABLE_CUDA=${SUNSHINE_ENABLE_CUDA:-ON} - - mkdir -p appimage-build && cd appimage-build - - cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" "../" -DCMAKE_INSTALL_PREFIX=/usr - make -j ${nproc} DESTDIR=AppDir + mkdir -p build + + cd build + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_CONFIG_DIR=. -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine -DSUNSHINE_ENABLE_WAYLAND=ON -DSUNSHINE_ENABLE_X11=ON -DSUNSHINE_ENABLE_DRM=ON -DSUNSHINE_ENABLE_CUDA=ON "../" + make -j ${nproc} - name: Set AppImage Version if: ${{ needs.check_changelog.outputs.next_version_bare != needs.check_changelog.outputs.latest_version }} @@ -99,111 +90,35 @@ jobs: version=${{ needs.check_changelog.outputs.next_version_bare }} echo "VERSION=${version}" >> $GITHUB_ENV - - name: Package AppImage - # https://docs.appimage.org/packaging-guide/index.html + - name: Package Linux run: | mkdir -p artifacts - mkdir -p appimage_temp && cd appimage_temp - + cd build + + # variables DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" ICON_FILE="${ICON_FILE:-sunshine.png}" - # CONFIG_DIR="${CONFIG_DIR:-sunshine/sunshine.AppImage.config/}" - # HOME_DIR="${HOME_DIR:-sunshine/sunshine.AppImage.home/}" - + + # AppImage + # https://docs.appimage.org/packaging-guide/index.html wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage && chmod +x linuxdeploy-x86_64.AppImage + ./linuxdeploy-x86_64.AppImage --appdir . -e ./sunshine -i "../$ICON_FILE" -d "./$DESKTOP_FILE" --output appimage - ./linuxdeploy-x86_64.AppImage --appdir ../AppDir -e ../appimage-build/sunshine -i "../$ICON_FILE" -d "../appimage-build/$DESKTOP_FILE" --output appimage - - # mv sunshine*.AppImage sunshine.AppImage - # mkdir sunshine && mv sunshine.AppImage sunshine/ - # ./sunshine/sunshine.AppImage --appimage-portable-config - # ./sunshine/sunshine.AppImage --appimage-portable-home - # cp -r ../assets/assets_common/* "$CONFIG_DIR" - # cp -r ../assets/assets_linux/* "$CONFIG_DIR" - - # mkdir -p ./"$HOME_DIR"/.config/"$CONFIG_DIR" - # cp ./"$CONFIG_DIR"/apps.json ./"$HOME_DIR"/.config/"$CONFIG_DIR" - # zip -r ./sunshine-appimage.zip ./sunshine/* - - # mv sunshine-appimage.zip ../artifacts/ + # package + cpack -G DEB + cpack -G RPM + # move mv sunshine*.AppImage ../artifacts/sunshine.AppImage - - # if testing succeeds, can remove commented lines + mv ./cpack_artifacts/Sunshine.deb ../artifacts/sunshine.deb + mv ./cpack_artifacts/Sunshine.rpm ../artifacts/sunshine.rpm - name: Verify AppImage run: | - # cd appimage_temp - wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage && chmod +x appimagelint-x86_64.AppImage && ./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage - - - name: Upload Artifacts - if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} - uses: actions/upload-artifact@v3 - with: - name: sunshine-appimage - path: artifacts/ - - - name: Create Release - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: SunshineStream/actions/create_release@master - with: - token: ${{ secrets.GITHUB_TOKEN }} - next_version: ${{ needs.check_changelog.outputs.next_version }} - last_version: ${{ needs.check_changelog.outputs.last_version }} - release_body: ${{ needs.check_changelog.outputs.release_body }} - - build_linux: - name: Linux - runs-on: ubuntu-20.04 - needs: check_changelog - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - submodules: recursive - - name: Setup Dependencies Linux - run: | - mkdir -p artifacts - sudo apt-get update -y && \ - sudo apt-get --reinstall install -y \ - git wget gcc-10 g++-10 build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 - sudo wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && sudo chmod a+x /root/cuda.run - sudo /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && sudo rm /root/cuda.run - sudo add-apt-repository ppa:savoury1/graphics -y - sudo add-apt-repository ppa:savoury1/multimedia -y - sudo add-apt-repository ppa:savoury1/ffmpeg4 -y - sudo apt-get update -y - sudo apt-get upgrade -y && sudo apt-get dist-upgrade -y - sudo apt-get install ffmpeg -y - - - name: Build Linux - run: | - CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}" - SUNSHINE_EXECUTABLE_PATH="${SUNSHINE_EXECUTABLE_PATH:-/usr/bin/sunshine}" - SUNSHINE_ENABLE_WAYLAND=${SUNSHINE_ENABLE_WAYLAND:-ON} - SUNSHINE_ENABLE_X11=${SUNSHINE_ENABLE_X11:-ON} - SUNSHINE_ENABLE_DRM=${SUNSHINE_ENABLE_DRM:-ON} - SUNSHINE_ENABLE_CUDA=${SUNSHINE_ENABLE_CUDA:-ON} - SUNSHINE_CONFIG_DIR=${SUNSHINE_CONFIG_DIR:-.} + wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage + chmod +x appimagelint-x86_64.AppImage - mkdir -p build - mkdir -p artifacts - - cd build - cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_CONFIG_DIR=$SUNSHINE_CONFIG_DIR" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" "../" -DCMAKE_INSTALL_PREFIX=/usr - - - name: Package Linux - run: | - cd build - - # package - cpack -G DEB - cpack -G RPM - - # move - mv Sunshine.deb ../artifacts/sunshine.deb - mv Sunshine.rpm ../artifacts/sunshine.rpm + ./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} @@ -244,7 +159,7 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets .. + cmake -DCMAKE_BUILD_TYPE=Release .. make -j ${nproc} - name: Package MacOS @@ -257,9 +172,9 @@ jobs: cpack -G ZIP # move - mv Sunshine.dmg ../artifacts/sunshine-macos.dmg - mv Sunshine.zip ../artifacts/sunshine-macos.zip mv Portfile ../artifacts/Portfile + mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-macos.dmg + mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-macos.zip - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} @@ -313,7 +228,7 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + cmake -DCMAKE_BUILD_TYPE=Release -G "MinGW Makefiles" .. mingw32-make -j2 - name: Package Windows @@ -327,8 +242,8 @@ jobs: cpack -G ZIP # move - mv Sunshine.exe ../artifacts/sunshine-windows.exe - mv Sunshine.zip ../artifacts/sunshine-windows.zip + mv ./cpack_artifacts/Sunshine.exe ../artifacts/sunshine-windows.exe + mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-windows.zip - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} diff --git a/CMakeLists.txt b/CMakeLists.txt index c12957b3..0640625a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -449,7 +449,8 @@ target_compile_options(sunshine PRIVATE $<$:${SUNSHINE_COM # Common options set(CPACK_PACKAGE_NAME "SunshineStream") -set(CPACK_PACKAGE_VENDOR "CMake.org") +set(CPACK_PACKAGE_VENDOR "SunshineStream") +set(CPACK_PACKAGE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/cpack_artifacts) set(CPACK_PACKAGE_CONTACT "https://github.com/SunshineStream/Sunshine") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "https://github.com/SunshineStream/Sunshine") set(CPACK_PACKAGE_DESCRIPTION "Gamestream host for Moonlight") @@ -486,7 +487,7 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h ") # Adding an option for the start menu and PATH - set(CPACK_NSIS_MODIFY_PATH "ON") # TODO: it asks to add it to the PATH but then it seems I can't just run it from powershell + set(CPACK_NSIS_MODIFY_PATH "OFF") # TODO: it asks to add it to the PATH but is not working https://gitlab.kitware.com/cmake/cmake/-/issues/15635 set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".") set(CPACK_NSIS_MUI_FINISHPAGE_RUN "${CMAKE_PROJECT_NAME}.exe") set(CPACK_NSIS_INSTALLED_ICON_NAME "${CMAKE_PROJECT_NAME}.exe") # This will be shown on the installed apps Windows settings @@ -501,12 +502,12 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h set(CPACK_COMPONENT_APPLICATION_DESCRIPTION "The main application.") set(CPACK_COMPONENT_APPLICATION_GROUP "${CMAKE_PROJECT_NAME}") set(CPACK_COMPONENT_APPLICATION_REQUIRED true) + set(CPACK_COMPONENT_APPLICATION_DEPENDS assets) # assets set(CPACK_COMPONENT_ASSETS_DISPLAY_NAME "Assets") set(CPACK_COMPONENT_ASSETS_DESCRIPTION "Assets include the apps and configurations, shaders, default box art, and web ui.") set(CPACK_COMPONENT_ASSETS_GROUP "${CMAKE_PROJECT_NAME}") - set(CPACK_COMPONENT_ASSETS_DEPENDS "${CMAKE_PROJECT_NAME}") set(CPACK_COMPONENT_ASSETS_REQUIRED true) # audio tool @@ -523,8 +524,6 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h set(CPACK_COMPONENT_SUNSHINESVC_DISPLAY_NAME "sunshinesvc.exe") set(CPACK_COMPONENT_SUNSHINESVC_DESCRIPTION "CLI tool that allows you to enable/disable the Sunshine service.") set(CPACK_COMPONENT_SUNSHINESVC_GROUP "Tools") - - # set(CPACK_COMPONENT_APPLICATION_DEPENDS assets) endif() if(APPLE) # TODO: test From 93aebf461a0b6e2c71b0f5efa292c22fde559e54 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 6 May 2022 13:21:47 -0400 Subject: [PATCH 258/817] Set assets dir for Windows --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e9d2a4bc..fe279f59 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -228,7 +228,7 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -G "MinGW Makefiles" .. + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. mingw32-make -j2 - name: Package Windows From 49bfd2ba1f1bc3d47b5935dbcf36e57febbc0087 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 6 May 2022 14:24:39 -0400 Subject: [PATCH 259/817] Rename apps.json files throughout project - Remove `appveyor.yml` - Add project description to CMakeLists.txt - Add project homepage url to CMakeLists.txt --- .github/workflows/CI.yml | 4 ++-- CMakeLists.txt | 9 +++++--- Portfile.in | 14 ++++++------ appveyor.yml | 44 -------------------------------------- assets/linux-deb/conffiles | 2 +- assets/linux-deb/postinst | 12 +++++------ assets/linux-deb/preinst | 4 ++-- docs/source/conf.py | 2 +- sunshine.desktop.in | 4 ++-- sunshine.service.in | 2 +- 10 files changed, 28 insertions(+), 69 deletions(-) delete mode 100644 appveyor.yml diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index fe279f59..5e854683 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -41,7 +41,7 @@ jobs: - name: Check CMakeLists.txt Version run: | - version=$(grep -o -E '^project\(Sunshine VERSION [0-9]+\.[0-9]+\.[0-9]+\)' CMakeLists.txt | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') + version=$(grep -o -E '^project\(Sunshine \[VERSION [0-9]+\.[0-9]+\.[0-9]+\]' CMakeLists.txt | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') echo "cmakelists_version=${version}" >> $GITHUB_ENV - name: Compare CMakeList.txt Version @@ -49,7 +49,7 @@ jobs: run: | echo CMakeLists version: "$cmakelists_version" echo Changelog version: "${{ needs.check_changelog.outputs.next_version_bare }}" - echo Within 'CMakeLists.txt' change "project(Sunshine VERSION $cmakelists_version)" to "project(Sunshine VERSION ${{ needs.check_changelog.outputs.next_version_bare }})" + echo Within 'CMakeLists.txt' change "project(Sunshine [VERSION $cmakelists_version]" to "project(Sunshine [VERSION ${{ needs.check_changelog.outputs.next_version_bare }}]" exit 1 build_linux: diff --git a/CMakeLists.txt b/CMakeLists.txt index 0640625a..8d9584d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,9 @@ cmake_minimum_required(VERSION 3.0) -project(Sunshine VERSION 0.13.0) +project(Sunshine [VERSION 0.13.0] + [DESCRIPTION Sunshine is a Gamestream host for Moonlight.] + [HOMEPAGE_URL https://sunshinestream.github.io] + ) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) @@ -453,8 +456,8 @@ set(CPACK_PACKAGE_VENDOR "SunshineStream") set(CPACK_PACKAGE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/cpack_artifacts) set(CPACK_PACKAGE_CONTACT "https://github.com/SunshineStream/Sunshine") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "https://github.com/SunshineStream/Sunshine") -set(CPACK_PACKAGE_DESCRIPTION "Gamestream host for Moonlight") -set(CPACK_PACKAGE_HOMEPAGE_URL "https://sunshinestream.github.io") +set(CPACK_PACKAGE_DESCRIPTION ${CMAKE_PROJECT_DESCRIPTION}) +set(CPACK_PACKAGE_HOMEPAGE_URL ${CMAKE_PROJECT_HOMEPAGE_URL}) set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE) set(CPACK_PACKAGE_ICON ${PROJECT_SOURCE_DIR}/sunshine.png) set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}") diff --git a/Portfile.in b/Portfile.in index 6973ed40..60940cb4 100644 --- a/Portfile.in +++ b/Portfile.in @@ -19,9 +19,9 @@ post-fetch { system -W ${worksrcpath} "${git.cmd} submodule update --init --recursive" } -description Sunshine is a Gamestream host for Moonlight. +description @PROJECT_DESCRIPTION@ long_description {*}${description} -homepage https://github.com/SunshineStream/Sunshine +homepage @PROJECT_HOMEPAGE_URL@ depends_lib port:avahi \ port:ffmpeg \ @@ -37,14 +37,14 @@ cmake.out_of_source yes destroot { xinstall -d -m 755 ${destroot}${prefix}/etc/${name} - xinstall ${worksrcpath}/assets/apps_mac.json ${destroot}${prefix}/etc/${name} - xinstall ${worksrcpath}/assets/box.png ${destroot}${prefix}/etc/${name} - xinstall ${worksrcpath}/assets/sunshine.conf ${destroot}${prefix}/etc/${name} + xinstall ${worksrcpath}/assets/assets_mac/apps.json ${destroot}${prefix}/etc/${name} + xinstall ${worksrcpath}/assets/assets_common/box.png ${destroot}${prefix}/etc/${name} + xinstall ${worksrcpath}/assets/assets_common/sunshine.conf ${destroot}${prefix}/etc/${name} xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/web - xinstall {*}[glob ${worksrcpath}/assets/web/*.html] ${destroot}${prefix}/etc/${name}/web + xinstall {*}[glob ${worksrcpath}/assets/assets_common/web/*.html] ${destroot}${prefix}/etc/${name}/web xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/web/third_party - xinstall {*}[glob ${worksrcpath}/assets/web/third_party/*] ${destroot}${prefix}/etc/${name}/web/third_party + xinstall {*}[glob ${worksrcpath}/assets/assets_common/web/third_party/*] ${destroot}${prefix}/etc/${name}/web/third_party xinstall ${workpath}/build/${name} ${destroot}${prefix}/bin } diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 7fb08089..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,44 +0,0 @@ -services: - - docker - -environment: - matrix: - - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 - DOCKERFILE: Dockerfile-ubuntu_20_04 - - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 - DOCKERFILE: Dockerfile-ubuntu_21_04 - - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 - DOCKERFILE: Dockerfile-debian - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - BUILD_TYPE: Release - -install: - - cmd: C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make" - -before_build: - - cmd: git submodule update --init --recursive - - cmd: mkdir build - - cmd: cd build - - sh: cd scripts - - sh: ./build-container.sh -f $DOCKERFILE - -build_script: - - cmd: set OLDPATH=%PATH% - - cmd: set PATH=C:\msys64\mingw64\bin - - cmd: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. - - cmd: mingw32-make -j2 - - cmd: set PATH=%OLDPATH% - - sh: ./build-sunshine.sh -pu - -after_build: - - cmd: Del ..\assets\apps_linux.json - - cmd: 7z a Sunshine-Windows.zip ..\assets - - cmd: 7z a Sunshine-Windows.zip sunshine.exe - - cmd: 7z a Sunshine-Windows.zip tools\dxgi-info.exe - - cmd: 7z a Sunshine-Windows.zip tools\audio-info.exe - - cmd: 7z a Sunshine-Windows.zip tools\sunshinesvc.exe - - cmd: 7z a Sunshine-Windows.zip ..\tools\install-service.bat - - cmd: 7z a Sunshine-Windows.zip ..\tools\uninstall-service.bat - - cmd: appveyor PushArtifact Sunshine-Windows.zip - - sh: appveyor PushArtifact sunshine-build/sunshine.deb - diff --git a/assets/linux-deb/conffiles b/assets/linux-deb/conffiles index 78176a81..cf2d2444 100644 --- a/assets/linux-deb/conffiles +++ b/assets/linux-deb/conffiles @@ -1,2 +1,2 @@ /etc/sunshine/sunshine.conf -/etc/sunshine/apps_linux.json +/etc/sunshine/apps.json diff --git a/assets/linux-deb/postinst b/assets/linux-deb/postinst index cf66231f..d3195c6a 100644 --- a/assets/linux-deb/postinst +++ b/assets/linux-deb/postinst @@ -17,15 +17,15 @@ if [ -f /etc/sunshine/sunshine.conf.old ]; then mv /etc/sunshine/sunshine.conf.old /etc/sunshine/sunshine.conf fi -if [ -f /etc/sunshine/apps_linux.json.old ]; then - echo "Restoring old apps_linux.json" - mv /etc/sunshine/apps_linux.json.old /etc/sunshine/apps_linux.json +if [ -f /etc/sunshine/apps.json.old ]; then + echo "Restoring old apps.json" + mv /etc/sunshine/apps.json.old /etc/sunshine/apps.json fi # Update permissions on config files for Web Manager -if [ -f /etc/sunshine/apps_linux.json ]; then - echo "chmod 666 /etc/sunshine/apps_linux.json" - chmod 666 /etc/sunshine/apps_linux.json +if [ -f /etc/sunshine/apps.json ]; then + echo "chmod 666 /etc/sunshine/apps.json" + chmod 666 /etc/sunshine/apps.json fi if [ -f /etc/sunshine/sunshine.conf ]; then diff --git a/assets/linux-deb/preinst b/assets/linux-deb/preinst index 515d1a34..ec56e883 100644 --- a/assets/linux-deb/preinst +++ b/assets/linux-deb/preinst @@ -4,6 +4,6 @@ if [ -f /etc/sunshine/sunshine.conf ]; then cp /etc/sunshine/sunshine.conf /etc/sunshine/sunshine.conf.old fi -if [ -f /etc/sunshine/apps_linux.json ]; then - cp /etc/sunshine/apps_linux.json /etc/sunshine/apps_linux.json.old +if [ -f /etc/sunshine/apps.json ]; then + cp /etc/sunshine/apps.json /etc/sunshine/apps.json.old fi diff --git a/docs/source/conf.py b/docs/source/conf.py index 86b7b805..360db8f6 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -27,7 +27,7 @@ # The full version, including alpha/beta/rc tags with open(os.path.join(root_dir, 'CMakeLists.txt'), 'r') as f: - version = re.search(r"project\(Sunshine VERSION ((\d+)\.(\d+)\.(\d+))\)", str(f.read())).group(1) + version = re.search(r"project\(Sunshine \[VERSION ((\d+)\.(\d+)\.(\d+))\]", str(f.read())).group(1) """ To use cmake method for obtaining version instead of regex, 1. Within CMakeLists.txt add the following line without backticks: diff --git a/sunshine.desktop.in b/sunshine.desktop.in index 195bade3..a345e5dc 100644 --- a/sunshine.desktop.in +++ b/sunshine.desktop.in @@ -1,9 +1,9 @@ [Desktop Entry] Type=Application -Name=sunshine +Name=@PROJECT_NAME@ Exec=sunshine Version=1.0 -Comment=Host for Moonlight Streaming Client +Comment=@PROJECT_DESCRIPTION@ Icon=sunshine Categories=Utility; Terminal=true diff --git a/sunshine.service.in b/sunshine.service.in index c0c38289..157b3a4d 100644 --- a/sunshine.service.in +++ b/sunshine.service.in @@ -1,5 +1,5 @@ [Unit] -Description=Sunshine Gamestream Server for Moonlight +Description=@PROJECT_DESCRIPTION@ [Service] ExecStart=@SUNSHINE_EXECUTABLE_PATH@ From a3e3da3136d4007ecb15bc66f66d735fc4d7314d Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 6 May 2022 14:39:26 -0400 Subject: [PATCH 260/817] Fix cmake project parameters --- .github/workflows/CI.yml | 2 +- CMakeLists.txt | 6 +++--- docs/source/conf.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 5e854683..97d90219 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -41,7 +41,7 @@ jobs: - name: Check CMakeLists.txt Version run: | - version=$(grep -o -E '^project\(Sunshine \[VERSION [0-9]+\.[0-9]+\.[0-9]+\]' CMakeLists.txt | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') + version=$(grep -o -E '^project\(Sunshine VERSION [0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') echo "cmakelists_version=${version}" >> $GITHUB_ENV - name: Compare CMakeList.txt Version diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d9584d0..b5c38886 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,8 @@ cmake_minimum_required(VERSION 3.0) -project(Sunshine [VERSION 0.13.0] - [DESCRIPTION Sunshine is a Gamestream host for Moonlight.] - [HOMEPAGE_URL https://sunshinestream.github.io] +project(Sunshine VERSION 0.13.0 + DESCRIPTION "Sunshine is a Gamestream host for Moonlight." + HOMEPAGE_URL "https://sunshinestream.github.io" ) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) diff --git a/docs/source/conf.py b/docs/source/conf.py index 360db8f6..f609783c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -27,7 +27,7 @@ # The full version, including alpha/beta/rc tags with open(os.path.join(root_dir, 'CMakeLists.txt'), 'r') as f: - version = re.search(r"project\(Sunshine \[VERSION ((\d+)\.(\d+)\.(\d+))\]", str(f.read())).group(1) + version = re.search(r"project\(Sunshine VERSION ((\d+)\.(\d+)\.(\d+))", str(f.read())).group(1) """ To use cmake method for obtaining version instead of regex, 1. Within CMakeLists.txt add the following line without backticks: From fff419a7ff19c11b029054181db35f578aced4f1 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 6 May 2022 15:19:11 -0400 Subject: [PATCH 261/817] Fix AppImage source filename --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 97d90219..3f8e04e6 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -109,7 +109,7 @@ jobs: cpack -G RPM # move - mv sunshine*.AppImage ../artifacts/sunshine.AppImage + mv Sunshine*.AppImage ../artifacts/sunshine.AppImage mv ./cpack_artifacts/Sunshine.deb ../artifacts/sunshine.deb mv ./cpack_artifacts/Sunshine.rpm ../artifacts/sunshine.rpm From 6c04065ba780b2d34633eb5e5493d209ca3bbb98 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 8 May 2022 21:39:10 -0400 Subject: [PATCH 262/817] Set DEFAULT_SUNSHINE_DIR for CI builds - Add libssl3.0 as CPACK_DEBIAN_PACKAGE_DEPENDS --- .github/workflows/CI.yml | 4 ++-- CMakeLists.txt | 2 +- Portfile.in | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 3f8e04e6..3aed4d1e 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -81,7 +81,7 @@ jobs: mkdir -p build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_CONFIG_DIR=. -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine -DSUNSHINE_ENABLE_WAYLAND=ON -DSUNSHINE_ENABLE_X11=ON -DSUNSHINE_ENABLE_DRM=ON -DSUNSHINE_ENABLE_CUDA=ON "../" + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_CONFIG_DIR=. -DSUNSHINE_DEFAULT_DIR=/etc/sunshine -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine -DSUNSHINE_ENABLE_WAYLAND=ON -DSUNSHINE_ENABLE_X11=ON -DSUNSHINE_ENABLE_DRM=ON -DSUNSHINE_ENABLE_CUDA=ON "../" make -j ${nproc} - name: Set AppImage Version @@ -159,7 +159,7 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release .. + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_DEFAULT_DIR=/etc/sunshine .. make -j ${nproc} - name: Package MacOS diff --git a/CMakeLists.txt b/CMakeLists.txt index b5c38886..f58e0439 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -565,7 +565,7 @@ if(UNIX AND NOT APPLE) # Dependencies set(CPACK_DEB_COMPONENT_INSTALL ON) - set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl1.1, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl1.1 | libssl3.0, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") set(CPACK_RPM_PACKAGE_REQUIRES "libssl==1.1, libavdevice>=58, libboost-thread>=1.67.0, libboost-filesystem>=1.67.0, libboost-log>=1.67.0, libpulse>=0, libopus>=0, libxcb-shm>=0, libxcb-xfixes>=0, libxtst>=0, libevdev>=2.0, libdrm>=2.0, libcap>=2.0") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # This should automatically figure out dependencies, doesn't work with the current config diff --git a/Portfile.in b/Portfile.in index 60940cb4..c784c3b0 100644 --- a/Portfile.in +++ b/Portfile.in @@ -31,7 +31,8 @@ depends_lib port:avahi \ boost.version 1.76 configure.args -DBOOST_ROOT=[boost::install_area] \ - -DSUNSHINE_ASSETS_DIR=${prefix}/etc/sunshine + -DSUNSHINE_ASSETS_DIR=${worksrcpath}/assets + -DSUNSHINE_DEFAULT_DIR=${prefix}/etc/sunshine cmake.out_of_source yes From 496e51d93a60988066735e50c9e9c58f96a7cfcc Mon Sep 17 00:00:00 2001 From: Caz zoo Date: Sat, 7 May 2022 17:32:42 +0200 Subject: [PATCH 263/817] Fixes #8 and relates #146 Adding required missing dependencies for Arch base distro --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index b0be1130..a020205e 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -111,7 +111,7 @@ jobs: wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage && chmod +x linuxdeploy-x86_64.AppImage - ./linuxdeploy-x86_64.AppImage --appdir ../AppDir -e ../appimage-build/sunshine -i "../$ICON_FILE" -d "../appimage-build/$DESKTOP_FILE" --output appimage + ./linuxdeploy-x86_64.AppImage --appdir ../AppDir -e ../appimage-build/sunshine -i "../$ICON_FILE" -d "../appimage-build/$DESKTOP_FILE" -l /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0 -l /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0 -l /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 --output appimage mv sunshine*.AppImage sunshine.AppImage mkdir sunshine && mv sunshine.AppImage sunshine/ From c2752262e5635f138ce1758c136ba27fef2e7a18 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 10 May 2022 18:54:50 -0400 Subject: [PATCH 264/817] Add missing AppImage libraries --- .github/workflows/CI.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 3aed4d1e..6f44197c 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -101,8 +101,17 @@ jobs: # AppImage # https://docs.appimage.org/packaging-guide/index.html - wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage && chmod +x linuxdeploy-x86_64.AppImage - ./linuxdeploy-x86_64.AppImage --appdir . -e ./sunshine -i "../$ICON_FILE" -d "./$DESKTOP_FILE" --output appimage + wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage + ./linuxdeploy-x86_64.AppImage \ + --appdir . \ + -e ./sunshine \ + -i "../$ICON_FILE" \ + -d "./$DESKTOP_FILE" \ + -l /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0 \ + -l /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 \ + -l /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0 \ + --output appimage # package cpack -G DEB From 88925c705f38b14ad4018589968c05d176ccb137 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 10 May 2022 19:19:16 -0400 Subject: [PATCH 265/817] Add gtk plugin for linuxdeploy / AppImage --- .github/workflows/CI.yml | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6f44197c..2e2b72a2 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -103,14 +103,22 @@ jobs: # https://docs.appimage.org/packaging-guide/index.html wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage chmod +x linuxdeploy-x86_64.AppImage + + # https://github.com/linuxdeploy/linuxdeploy-plugin-gtk + sudo apt-get install libgtk-3-dev librsvg2-dev -y + wget https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh + chmod +x linuxdeploy-plugin-gtk.sh + export DEPLOY_GTK_VERSION=3 + ./linuxdeploy-x86_64.AppImage \ --appdir . \ - -e ./sunshine \ - -i "../$ICON_FILE" \ - -d "./$DESKTOP_FILE" \ - -l /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0 \ - -l /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 \ - -l /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0 \ + --plugin gtk \ + --executable ./sunshine \ + --icon-file "../$ICON_FILE" \ + --desktop-file "./$DESKTOP_FILE" \ + --library /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0 \ + --library /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 \ + --library /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0 \ --output appimage # package From ca00949851dba872373b8bebde42d16a2f315930 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 10 May 2022 21:18:37 -0400 Subject: [PATCH 266/817] Refactor build_linux job --- .github/workflows/CI.yml | 84 ++++++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 16 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2e2b72a2..a1b091b9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -63,18 +63,67 @@ jobs: submodules: recursive - name: Setup Dependencies Linux run: | - sudo apt-get update -y && \ - sudo apt-get --reinstall install -y \ - git wget gcc-10 g++-10 build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 - sudo wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && sudo chmod a+x /root/cuda.run - sudo /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && sudo rm /root/cuda.run - sudo add-apt-repository ppa:savoury1/graphics -y - sudo add-apt-repository ppa:savoury1/multimedia -y sudo add-apt-repository ppa:savoury1/ffmpeg4 -y + # sudo add-apt-repository ppa:savoury1/boost-defaults-1.71 -y + sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y + sudo apt-get update -y - sudo apt-get upgrade -y && sudo apt-get dist-upgrade -y - sudo apt-get install ffmpeg -y + sudo apt-get install -y \ + build-essential \ + cmake \ + gcc-10 \ + git \ + g++-10 \ + libavdevice-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libboost-thread-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + wget + # # Ubuntu 20.04+ packages + # libboost-filesystem-dev + # libboost-log-dev + # libboost-thread-dev + + # # Ubuntu 18.04 packages + # libboost-filesystem1.71-dev \ + # libboost-log1.71-dev \ + # libboost-regex1.71-dev \ + # libboost-thread1.71-dev \ + + # clean apt cache + sudo apt-get clean + sudo rm -rf /var/lib/apt/lists/* + + # Update gcc alias + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 + + # Install CuDA + sudo wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run + sudo chmod a+x /root/cuda.run + sudo /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm + sudo rm /root/cuda.run + + # # Install cmake (necessary for 18.04) + # wget https://cmake.org/files/v3.22/cmake-3.22.2-linux-x86_64.sh + # chmod +x cmake-3.22.2-linux-x86_64.sh + # mkdir /opt/cmake + # ./cmake-3.22.2-linux-x86_64.sh --prefix=/opt/cmake --skip-license + # ln --force --symbolic /opt/cmake/bin/cmake /usr/local/bin/cmake + # cmake --version - name: Build Linux run: | @@ -104,15 +153,14 @@ jobs: wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage chmod +x linuxdeploy-x86_64.AppImage - # https://github.com/linuxdeploy/linuxdeploy-plugin-gtk - sudo apt-get install libgtk-3-dev librsvg2-dev -y - wget https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh - chmod +x linuxdeploy-plugin-gtk.sh - export DEPLOY_GTK_VERSION=3 + # # https://github.com/linuxdeploy/linuxdeploy-plugin-gtk + # sudo apt-get install libgtk-3-dev librsvg2-dev -y + # wget https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh + # chmod +x linuxdeploy-plugin-gtk.sh + # export DEPLOY_GTK_VERSION=3 ./linuxdeploy-x86_64.AppImage \ --appdir . \ - --plugin gtk \ --executable ./sunshine \ --icon-file "../$ICON_FILE" \ --desktop-file "./$DESKTOP_FILE" \ @@ -120,6 +168,8 @@ jobs: --library /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 \ --library /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0 \ --output appimage + # # add this argument back if using gtk plugin + # --plugin gtk \ # package cpack -G DEB @@ -135,6 +185,8 @@ jobs: wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage chmod +x appimagelint-x86_64.AppImage + # rm -rf ~/.cache/appimagelint/ + ./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage - name: Upload Artifacts From 68ba1db24ab66df63fd525d15f95b95bc958beac Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 11 May 2022 21:31:31 -0400 Subject: [PATCH 267/817] Refactor assets and config directory --- .github/workflows/CI.yml | 6 +- .gitignore | 16 ++--- CMakeLists.txt | 65 +++++++++++------- Portfile.in | 20 +++--- assets/linux-deb/conffiles | 2 - assets/linux-deb/preinst | 9 --- docs/source/about/advanced_usage.rst | 12 ++-- docs/source/about/installation.rst | 2 +- .../common/assets}/box.png | Bin .../common/assets}/steam.png | Bin .../common/assets}/web/apps.html | 0 .../common/assets}/web/clients.html | 0 .../common/assets}/web/config.html | 0 .../fonts/fontawesome-free-web/LICENSE.txt | 0 .../fonts/fontawesome-free-web/attribution.js | 0 .../fontawesome-free-web/css/all.min.css | 0 .../fontawesome-free-web/css/brands.min.css | 0 .../css/fontawesome.min.css | 0 .../fontawesome-free-web/css/regular.min.css | 0 .../fontawesome-free-web/css/solid.min.css | 0 .../css/svg-with-js.min.css | 0 .../fontawesome-free-web/css/v4-shims.min.css | 0 .../webfonts/fa-brands-400.eot | Bin .../webfonts/fa-brands-400.svg | 0 .../webfonts/fa-brands-400.ttf | Bin .../webfonts/fa-brands-400.woff | Bin .../webfonts/fa-brands-400.woff2 | Bin .../webfonts/fa-regular-400.eot | Bin .../webfonts/fa-regular-400.svg | 0 .../webfonts/fa-regular-400.ttf | Bin .../webfonts/fa-regular-400.woff | Bin .../webfonts/fa-regular-400.woff2 | Bin .../webfonts/fa-solid-900.eot | Bin .../webfonts/fa-solid-900.svg | 0 .../webfonts/fa-solid-900.ttf | Bin .../webfonts/fa-solid-900.woff | Bin .../webfonts/fa-solid-900.woff2 | Bin .../common/assets}/web/header-no-nav.html | 0 .../common/assets}/web/header.html | 0 .../common/assets}/web/images/favicon.ico | Bin .../assets}/web/images/logo-sunshine-45.png | Bin .../common/assets}/web/index.html | 0 .../common/assets}/web/password.html | 0 .../common/assets}/web/pin.html | 0 .../web/third_party/bootstrap.bundle.min.js | 0 .../assets}/web/third_party/bootstrap.min.css | 0 .../common/assets}/web/third_party/vue.js | 0 .../common/assets}/web/troubleshooting.html | 0 .../common/assets}/web/welcome.html | 0 .../common/config}/sunshine.conf | 0 .../assets}/shaders/opengl/ConvertUV.frag | 0 .../assets}/shaders/opengl/ConvertUV.vert | 0 .../assets}/shaders/opengl/ConvertY.frag | 0 .../linux/assets}/shaders/opengl/Scene.frag | 0 .../linux/assets}/shaders/opengl/Scene.vert | 0 .../linux/config}/apps.json | 0 src_assets/linux/deb/conffiles | 2 + .../linux/deb}/postinst | 16 ++--- src_assets/linux/deb/preinst | 9 +++ .../linux/misc}/85-sunshine-rules.rules | 0 .../macos/assets}/Info.plist | 0 .../macos/config}/apps.json | 0 .../assets}/shaders/directx/ConvertUVPS.hlsl | 0 .../assets}/shaders/directx/ConvertUVVS.hlsl | 0 .../assets}/shaders/directx/ConvertYPS.hlsl | 0 .../assets}/shaders/directx/ScenePS.hlsl | 0 .../assets}/shaders/directx/SceneVS.hlsl | 0 .../windows/config}/apps.json | 0 sunshine/config.cpp | 6 +- 69 files changed, 90 insertions(+), 75 deletions(-) delete mode 100644 assets/linux-deb/conffiles delete mode 100644 assets/linux-deb/preinst rename {assets/assets_common => src_assets/common/assets}/box.png (100%) rename {assets/assets_common => src_assets/common/assets}/steam.png (100%) rename {assets/assets_common => src_assets/common/assets}/web/apps.html (100%) rename {assets/assets_common => src_assets/common/assets}/web/clients.html (100%) rename {assets/assets_common => src_assets/common/assets}/web/config.html (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/LICENSE.txt (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/attribution.js (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/css/all.min.css (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/css/brands.min.css (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/css/fontawesome.min.css (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/css/regular.min.css (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/css/solid.min.css (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/css/svg-with-js.min.css (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/css/v4-shims.min.css (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.eot (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.svg (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.ttf (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff2 (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.eot (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.svg (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.ttf (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff2 (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.eot (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.svg (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.ttf (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff (100%) rename {assets/assets_common => src_assets/common/assets}/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff2 (100%) rename {assets/assets_common => src_assets/common/assets}/web/header-no-nav.html (100%) rename {assets/assets_common => src_assets/common/assets}/web/header.html (100%) rename {assets/assets_common => src_assets/common/assets}/web/images/favicon.ico (100%) rename {assets/assets_common => src_assets/common/assets}/web/images/logo-sunshine-45.png (100%) rename {assets/assets_common => src_assets/common/assets}/web/index.html (100%) rename {assets/assets_common => src_assets/common/assets}/web/password.html (100%) rename {assets/assets_common => src_assets/common/assets}/web/pin.html (100%) rename {assets/assets_common => src_assets/common/assets}/web/third_party/bootstrap.bundle.min.js (100%) rename {assets/assets_common => src_assets/common/assets}/web/third_party/bootstrap.min.css (100%) rename {assets/assets_common => src_assets/common/assets}/web/third_party/vue.js (100%) rename {assets/assets_common => src_assets/common/assets}/web/troubleshooting.html (100%) rename {assets/assets_common => src_assets/common/assets}/web/welcome.html (100%) rename {assets/assets_common => src_assets/common/config}/sunshine.conf (100%) rename {assets/assets_linux => src_assets/linux/assets}/shaders/opengl/ConvertUV.frag (100%) rename {assets/assets_linux => src_assets/linux/assets}/shaders/opengl/ConvertUV.vert (100%) rename {assets/assets_linux => src_assets/linux/assets}/shaders/opengl/ConvertY.frag (100%) rename {assets/assets_linux => src_assets/linux/assets}/shaders/opengl/Scene.frag (100%) rename {assets/assets_linux => src_assets/linux/assets}/shaders/opengl/Scene.vert (100%) rename {assets/assets_linux => src_assets/linux/config}/apps.json (100%) create mode 100644 src_assets/linux/deb/conffiles rename {assets/linux-deb => src_assets/linux/deb}/postinst (59%) create mode 100644 src_assets/linux/deb/preinst rename {assets/linux-misc => src_assets/linux/misc}/85-sunshine-rules.rules (100%) rename {assets/assets_mac => src_assets/macos/assets}/Info.plist (100%) rename {assets/assets_mac => src_assets/macos/config}/apps.json (100%) rename {assets/assets_windows => src_assets/windows/assets}/shaders/directx/ConvertUVPS.hlsl (100%) rename {assets/assets_windows => src_assets/windows/assets}/shaders/directx/ConvertUVVS.hlsl (100%) rename {assets/assets_windows => src_assets/windows/assets}/shaders/directx/ConvertYPS.hlsl (100%) rename {assets/assets_windows => src_assets/windows/assets}/shaders/directx/ScenePS.hlsl (100%) rename {assets/assets_windows => src_assets/windows/assets}/shaders/directx/SceneVS.hlsl (100%) rename {assets/assets_windows => src_assets/windows/config}/apps.json (100%) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a1b091b9..dac856ed 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -130,7 +130,7 @@ jobs: mkdir -p build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_CONFIG_DIR=. -DSUNSHINE_DEFAULT_DIR=/etc/sunshine -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine -DSUNSHINE_ENABLE_WAYLAND=ON -DSUNSHINE_ENABLE_X11=ON -DSUNSHINE_ENABLE_DRM=ON -DSUNSHINE_ENABLE_CUDA=ON "../" + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=/usr/local/sunshine/.assets -DSUNSHINE_CONFIG_DIR=/usr/local/sunshine/config -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine -DSUNSHINE_ENABLE_WAYLAND=ON -DSUNSHINE_ENABLE_X11=ON -DSUNSHINE_ENABLE_DRM=ON -DSUNSHINE_ENABLE_CUDA=ON .. make -j ${nproc} - name: Set AppImage Version @@ -228,7 +228,7 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_DEFAULT_DIR=/etc/sunshine .. + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=/usr/local/sunshine/.assets -DSUNSHINE_CONFIG_DIR=/usr/local/sunshine/config .. make -j ${nproc} - name: Package MacOS @@ -297,7 +297,7 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" .. + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -DSUNSHINE_CONFIG_DIR=config -G "MinGW Makefiles" .. mingw32-make -j2 - name: Package Windows diff --git a/.gitignore b/.gitignore index 39afd65a..6c44c5bc 100644 --- a/.gitignore +++ b/.gitignore @@ -10,14 +10,14 @@ cmake-build* .idea # Extra FontAwesome files -/assets/web/fonts/fontawesome-free-web/css/*.css -!/assets/web/fonts/fontawesome-free-web/css/*min.css -/assets/web/fonts/fontawesome-free-web/js/ -/assets/web/fonts/fontawesome-free-web/less/ -/assets/web/fonts/fontawesome-free-web/metadata/ -/assets/web/fonts/fontawesome-free-web/scss/ -/assets/web/fonts/fontawesome-free-web/sprites/ -/assets/web/fonts/fontawesome-free-web/svgs/ +/src_assets/common/assets/web/fonts/fontawesome-free-web/css/*.css +!/src_assets/common/assets/web/fonts/fontawesome-free-web/css/*min.css +/src_assets/common/assets/web/fonts/fontawesome-free-web/js/ +/src_assets/common/assets/web/fonts/fontawesome-free-web/less/ +/src_assets/common/assets/web/fonts/fontawesome-free-web/metadata/ +/src_assets/common/assets/web/fonts/fontawesome-free-web/scss/ +/src_assets/common/assets/web/fonts/fontawesome-free-web/sprites/ +/src_assets/common/assets/web/fonts/fontawesome-free-web/svgs/ # Translations *.mo diff --git a/CMakeLists.txt b/CMakeLists.txt index f58e0439..68035d54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,7 @@ project(Sunshine VERSION 0.13.0 ) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +set(SUNSHINE_SOURCE_ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src_assets") if(WIN32) # Ugly hack to compile with #include @@ -147,6 +148,8 @@ elseif(APPLE) set(PLATFORM_INCLUDE_DIRS ${Boost_INCLUDE_DIR}) + set(APPLE_PLIST_FILE ${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/Info.plist) + set(PLATFORM_TARGET_FILES sunshine/platform/macos/av_audio.h sunshine/platform/macos/av_audio.m @@ -163,7 +166,7 @@ elseif(APPLE) sunshine/platform/macos/publish.cpp sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.c sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.h - ${CMAKE_CURRENT_SOURCE_DIR}/assets/assets_mac/Info.plist) + ${APPLE_PLIST_FILE}) else() add_compile_definitions(SUNSHINE_PLATFORM="linux") @@ -390,15 +393,11 @@ else() endif() if(NOT SUNSHINE_ASSETS_DIR) - set(SUNSHINE_ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets") + set(SUNSHINE_ASSETS_DIR "${CMAKE_CURRENT_BINARY_DIR}/.assets") endif() if(NOT SUNSHINE_CONFIG_DIR) - set(SUNSHINE_CONFIG_DIR "${SUNSHINE_ASSETS_DIR}") -endif() - -if(NOT SUNSHINE_DEFAULT_DIR) - set(SUNSHINE_DEFAULT_DIR "${SUNSHINE_ASSETS_DIR}") + set(SUNSHINE_CONFIG_DIR "${CMAKE_CURRENT_BINARY_DIR}/config") endif() list(APPEND CBS_EXTERNAL_LIBRARIES @@ -421,8 +420,6 @@ endif() list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR}") list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_CONFIG_DIR="${SUNSHINE_CONFIG_DIR}") -list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_DEFAULT_DIR="${SUNSHINE_DEFAULT_DIR}") -list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps.json") add_executable(sunshine ${SUNSHINE_TARGET_FILES}) target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES} ${EXTRA_LIBS}) target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS}) @@ -437,7 +434,7 @@ if(NOT DEFINED CMAKE_CUDA_STANDARD) endif() if(APPLE) - target_link_options(sunshine PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,${CMAKE_CURRENT_SOURCE_DIR}/assets/assets_mac/Info.plist) + target_link_options(sunshine PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,${APPLE_PLIST_FILE}) endif() foreach(flag IN LISTS SUNSHINE_COMPILE_OPTIONS) @@ -472,8 +469,11 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h install(TARGETS audio-info RUNTIME DESTINATION "tools" COMPONENT audio) install(TARGETS sunshinesvc RUNTIME DESTINATION "tools" COMPONENT sunshinesvc) - install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/assets_common/" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) - install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/assets_windows/" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT assets) + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}" COMPONENT assets) + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}" COMPONENT assets) + + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT config) + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT config) # set(CPACK_NSIS_MUI_HEADERIMAGE "") # TODO: image should be 150x57 bmp @@ -509,10 +509,16 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h # assets set(CPACK_COMPONENT_ASSETS_DISPLAY_NAME "Assets") - set(CPACK_COMPONENT_ASSETS_DESCRIPTION "Assets include the apps and configurations, shaders, default box art, and web ui.") + set(CPACK_COMPONENT_ASSETS_DESCRIPTION "Shaders, default box art, and web ui.") set(CPACK_COMPONENT_ASSETS_GROUP "${CMAKE_PROJECT_NAME}") set(CPACK_COMPONENT_ASSETS_REQUIRED true) + # config + set(CPACK_COMPONENT_CONFIG_DISPLAY_NAME "Config") + set(CPACK_COMPONENT_CONFIG_DESCRIPTION "Default config and apps.json files.") + set(CPACK_COMPONENT_CONFIG_GROUP "${CMAKE_PROJECT_NAME}") + set(CPACK_COMPONENT_CONFIG_REQUIRED true) + # audio tool set(CPACK_COMPONENT_AUDIO_DISPLAY_NAME "audio-info.exe") set(CPACK_COMPONENT_AUDIO_DESCRIPTION "CLI tool that allows you to get information about sound devices.") @@ -528,13 +534,23 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h set(CPACK_COMPONENT_SUNSHINESVC_DESCRIPTION "CLI tool that allows you to enable/disable the Sunshine service.") set(CPACK_COMPONENT_SUNSHINESVC_GROUP "Tools") endif() +if(UNIX) + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") + + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") + + # Installation destination dir + set(CPACK_SET_DESTDIR true) + set(CMAKE_INSTALL_PREFIX "/usr/local/sunshine/") +endif() if(APPLE) # TODO: test set(prefix "${CMAKE_PROJECT_NAME}.app/Contents") set(INSTALL_RUNTIME_DIR "${prefix}/MacOS") - install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/assets_common/" DESTINATION "${INSTALL_RUNTIME_DIR}") - install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/assets_mac/" DESTINATION "${INSTALL_RUNTIME_DIR}") + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") + + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") install(TARGETS sunshine BUNDLE DESTINATION . COMPONENT Runtime @@ -542,26 +558,27 @@ if(APPLE) # TODO: test # TODO: bundle doesn't produce a valid .app use cpack -G DragNDrop set(CPACK_BUNDLE_NAME "${CMAKE_PROJECT_NAME}") - set(CPACK_BUNDLE_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/assets_mac/Info.plist") + set(CPACK_BUNDLE_PLIST "${APPLE_PLIST_FILE}") set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/sunshine.icns") # Portfile configure_file(Portfile.in Portfile @ONLY) endif() if(UNIX AND NOT APPLE) - install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/assets_common/" DESTINATION "${SUNSHINE_CONFIG_DIR}") - install(DIRECTORY "${SUNSHINE_ASSETS_DIR}/assets_linux/" DESTINATION "${SUNSHINE_CONFIG_DIR}") + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") + + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") - install(FILES "${SUNSHINE_ASSETS_DIR}/linux-misc/85-sunshine-rules.rules" DESTINATION "/etc/udev/rules.d") + install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine-rules.rules" DESTINATION "/etc/udev/rules.d") install(TARGETS sunshine RUNTIME DESTINATION "/usr/bin") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "/usr/lib/systemd/user") # Pre and post install set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA - "${SUNSHINE_ASSETS_DIR}/linux-deb/preinst;${SUNSHINE_ASSETS_DIR}/linux-deb/postinst;${SUNSHINE_ASSETS_DIR}/linux-deb/conffiles") - set(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE "${SUNSHINE_ASSETS_DIR}/linux-deb/preinst") - set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${SUNSHINE_ASSETS_DIR}/linux-deb/postinst") + "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/deb/preinst;${SUNSHINE_SOURCE_ASSETS_DIR}/linux/deb/postinst;${SUNSHINE_SOURCE_ASSETS_DIR}/linux/deb/conffiles") + set(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/deb/preinst") + set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/deb/postinst") # Dependencies set(CPACK_DEB_COMPONENT_INSTALL ON) @@ -569,10 +586,6 @@ if(UNIX AND NOT APPLE) set(CPACK_RPM_PACKAGE_REQUIRES "libssl==1.1, libavdevice>=58, libboost-thread>=1.67.0, libboost-filesystem>=1.67.0, libboost-log>=1.67.0, libpulse>=0, libopus>=0, libxcb-shm>=0, libxcb-xfixes>=0, libxtst>=0, libevdev>=2.0, libdrm>=2.0, libcap>=2.0") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # This should automatically figure out dependencies, doesn't work with the current config - # Installation destination dir - set(CPACK_SET_DESTDIR true) - set(CMAKE_INSTALL_PREFIX "/etc/sunshine") - # AppImage desktop file configure_file(sunshine.desktop.in sunshine.desktop @ONLY) endif() diff --git a/Portfile.in b/Portfile.in index c784c3b0..2cb35466 100644 --- a/Portfile.in +++ b/Portfile.in @@ -31,21 +31,19 @@ depends_lib port:avahi \ boost.version 1.76 configure.args -DBOOST_ROOT=[boost::install_area] \ - -DSUNSHINE_ASSETS_DIR=${worksrcpath}/assets - -DSUNSHINE_DEFAULT_DIR=${prefix}/etc/sunshine + -DSUNSHINE_ASSETS_DIR=${prefix}/usr/local/sunshine/.assets + -DSUNSHINE_CONFIG_DIR=${prefix}/usr/local/sunshine/config cmake.out_of_source yes destroot { - xinstall -d -m 755 ${destroot}${prefix}/etc/${name} - xinstall ${worksrcpath}/assets/assets_mac/apps.json ${destroot}${prefix}/etc/${name} - xinstall ${worksrcpath}/assets/assets_common/box.png ${destroot}${prefix}/etc/${name} - xinstall ${worksrcpath}/assets/assets_common/sunshine.conf ${destroot}${prefix}/etc/${name} - - xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/web - xinstall {*}[glob ${worksrcpath}/assets/assets_common/web/*.html] ${destroot}${prefix}/etc/${name}/web - xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/web/third_party - xinstall {*}[glob ${worksrcpath}/assets/assets_common/web/third_party/*] ${destroot}${prefix}/etc/${name}/web/third_party + xinstall -d -m 755 ${destroot}${prefix}/usr/local/${name}/.assets + xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/*] ${destroot}${prefix}/usr/local/${name}/.assets + xinstall {*}[glob ${worksrcpath}/src_assets/macos/assets/*] ${destroot}${prefix}/usr/local/${name}/.assets + + xinstall -d -m 755 ${destroot}${prefix}/usr/local/${name}/config + xinstall {*}[glob ${worksrcpath}/src_assets/common/config/*] ${destroot}${prefix}/usr/local/${name}/config + xinstall {*}[glob ${worksrcpath}/src_assets/macos/config/*] ${destroot}${prefix}/usr/local/${name}/config xinstall ${workpath}/build/${name} ${destroot}${prefix}/bin } diff --git a/assets/linux-deb/conffiles b/assets/linux-deb/conffiles deleted file mode 100644 index cf2d2444..00000000 --- a/assets/linux-deb/conffiles +++ /dev/null @@ -1,2 +0,0 @@ -/etc/sunshine/sunshine.conf -/etc/sunshine/apps.json diff --git a/assets/linux-deb/preinst b/assets/linux-deb/preinst deleted file mode 100644 index ec56e883..00000000 --- a/assets/linux-deb/preinst +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -#Store backup for old config files to prevent it from being overwritten -if [ -f /etc/sunshine/sunshine.conf ]; then - cp /etc/sunshine/sunshine.conf /etc/sunshine/sunshine.conf.old -fi - -if [ -f /etc/sunshine/apps.json ]; then - cp /etc/sunshine/apps.json /etc/sunshine/apps.json.old -fi diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index 4f427dcb..7699232c 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -6,9 +6,12 @@ Sunshine will work with the default settings for most users. In some cases you m Configuration ------------- -The default location for the configuration file is ``./assets/sunshine.conf``. You can use another location if you +The default location for the configuration file is listed below. You can use another location if you choose, by passing in the full configuration file path as the first argument when you start Sunshine. +The default location of the ``apps.json`` is the same as the configuration file. You can use a custom +location by modifying the configuration file. + **Default File Location** .. table:: @@ -17,9 +20,10 @@ choose, by passing in the full configuration file path as the first argument whe ======= =========== Value Description ======= =========== - Linux ./assets/sunshine.conf - MacOS /opt/local/etc/sunshine.conf - Windows ./assets/sunshine.conf + Docker /config/ + Linux /usr/local/sunshine/config/ + MacOS /usr/local/sunshine/config/ + Windows ./config/ ======= =========== Example diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index d7e043a4..40eecd20 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -27,7 +27,7 @@ AppImage .. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:appimage?logo=github&style=for-the-badge :alt: GitHub issues by-label -The current compatibility of the AppImage is shown below. +The current known compatibility of the AppImage is shown below. - [✖] Debian oldstable (buster) - [✔] Debian stable (bullseye) diff --git a/assets/assets_common/box.png b/src_assets/common/assets/box.png similarity index 100% rename from assets/assets_common/box.png rename to src_assets/common/assets/box.png diff --git a/assets/assets_common/steam.png b/src_assets/common/assets/steam.png similarity index 100% rename from assets/assets_common/steam.png rename to src_assets/common/assets/steam.png diff --git a/assets/assets_common/web/apps.html b/src_assets/common/assets/web/apps.html similarity index 100% rename from assets/assets_common/web/apps.html rename to src_assets/common/assets/web/apps.html diff --git a/assets/assets_common/web/clients.html b/src_assets/common/assets/web/clients.html similarity index 100% rename from assets/assets_common/web/clients.html rename to src_assets/common/assets/web/clients.html diff --git a/assets/assets_common/web/config.html b/src_assets/common/assets/web/config.html similarity index 100% rename from assets/assets_common/web/config.html rename to src_assets/common/assets/web/config.html diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/LICENSE.txt b/src_assets/common/assets/web/fonts/fontawesome-free-web/LICENSE.txt similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/LICENSE.txt rename to src_assets/common/assets/web/fonts/fontawesome-free-web/LICENSE.txt diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/attribution.js b/src_assets/common/assets/web/fonts/fontawesome-free-web/attribution.js similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/attribution.js rename to src_assets/common/assets/web/fonts/fontawesome-free-web/attribution.js diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/css/all.min.css b/src_assets/common/assets/web/fonts/fontawesome-free-web/css/all.min.css similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/css/all.min.css rename to src_assets/common/assets/web/fonts/fontawesome-free-web/css/all.min.css diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/css/brands.min.css b/src_assets/common/assets/web/fonts/fontawesome-free-web/css/brands.min.css similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/css/brands.min.css rename to src_assets/common/assets/web/fonts/fontawesome-free-web/css/brands.min.css diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/css/fontawesome.min.css b/src_assets/common/assets/web/fonts/fontawesome-free-web/css/fontawesome.min.css similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/css/fontawesome.min.css rename to src_assets/common/assets/web/fonts/fontawesome-free-web/css/fontawesome.min.css diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/css/regular.min.css b/src_assets/common/assets/web/fonts/fontawesome-free-web/css/regular.min.css similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/css/regular.min.css rename to src_assets/common/assets/web/fonts/fontawesome-free-web/css/regular.min.css diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/css/solid.min.css b/src_assets/common/assets/web/fonts/fontawesome-free-web/css/solid.min.css similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/css/solid.min.css rename to src_assets/common/assets/web/fonts/fontawesome-free-web/css/solid.min.css diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/css/svg-with-js.min.css b/src_assets/common/assets/web/fonts/fontawesome-free-web/css/svg-with-js.min.css similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/css/svg-with-js.min.css rename to src_assets/common/assets/web/fonts/fontawesome-free-web/css/svg-with-js.min.css diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/css/v4-shims.min.css b/src_assets/common/assets/web/fonts/fontawesome-free-web/css/v4-shims.min.css similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/css/v4-shims.min.css rename to src_assets/common/assets/web/fonts/fontawesome-free-web/css/v4-shims.min.css diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.eot b/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.eot similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.eot rename to src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.eot diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.svg b/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.svg similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.svg rename to src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.svg diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.ttf b/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.ttf similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.ttf rename to src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.ttf diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff b/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff rename to src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff2 b/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff2 similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff2 rename to src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-brands-400.woff2 diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.eot b/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.eot similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.eot rename to src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.eot diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.svg b/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.svg similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.svg rename to src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.svg diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.ttf b/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.ttf similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.ttf rename to src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.ttf diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff b/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff rename to src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff2 b/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff2 similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff2 rename to src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-regular-400.woff2 diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.eot b/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.eot similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.eot rename to src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.eot diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.svg b/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.svg similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.svg rename to src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.svg diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.ttf b/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.ttf similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.ttf rename to src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.ttf diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff b/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff rename to src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff diff --git a/assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff2 b/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff2 similarity index 100% rename from assets/assets_common/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff2 rename to src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/fa-solid-900.woff2 diff --git a/assets/assets_common/web/header-no-nav.html b/src_assets/common/assets/web/header-no-nav.html similarity index 100% rename from assets/assets_common/web/header-no-nav.html rename to src_assets/common/assets/web/header-no-nav.html diff --git a/assets/assets_common/web/header.html b/src_assets/common/assets/web/header.html similarity index 100% rename from assets/assets_common/web/header.html rename to src_assets/common/assets/web/header.html diff --git a/assets/assets_common/web/images/favicon.ico b/src_assets/common/assets/web/images/favicon.ico similarity index 100% rename from assets/assets_common/web/images/favicon.ico rename to src_assets/common/assets/web/images/favicon.ico diff --git a/assets/assets_common/web/images/logo-sunshine-45.png b/src_assets/common/assets/web/images/logo-sunshine-45.png similarity index 100% rename from assets/assets_common/web/images/logo-sunshine-45.png rename to src_assets/common/assets/web/images/logo-sunshine-45.png diff --git a/assets/assets_common/web/index.html b/src_assets/common/assets/web/index.html similarity index 100% rename from assets/assets_common/web/index.html rename to src_assets/common/assets/web/index.html diff --git a/assets/assets_common/web/password.html b/src_assets/common/assets/web/password.html similarity index 100% rename from assets/assets_common/web/password.html rename to src_assets/common/assets/web/password.html diff --git a/assets/assets_common/web/pin.html b/src_assets/common/assets/web/pin.html similarity index 100% rename from assets/assets_common/web/pin.html rename to src_assets/common/assets/web/pin.html diff --git a/assets/assets_common/web/third_party/bootstrap.bundle.min.js b/src_assets/common/assets/web/third_party/bootstrap.bundle.min.js similarity index 100% rename from assets/assets_common/web/third_party/bootstrap.bundle.min.js rename to src_assets/common/assets/web/third_party/bootstrap.bundle.min.js diff --git a/assets/assets_common/web/third_party/bootstrap.min.css b/src_assets/common/assets/web/third_party/bootstrap.min.css similarity index 100% rename from assets/assets_common/web/third_party/bootstrap.min.css rename to src_assets/common/assets/web/third_party/bootstrap.min.css diff --git a/assets/assets_common/web/third_party/vue.js b/src_assets/common/assets/web/third_party/vue.js similarity index 100% rename from assets/assets_common/web/third_party/vue.js rename to src_assets/common/assets/web/third_party/vue.js diff --git a/assets/assets_common/web/troubleshooting.html b/src_assets/common/assets/web/troubleshooting.html similarity index 100% rename from assets/assets_common/web/troubleshooting.html rename to src_assets/common/assets/web/troubleshooting.html diff --git a/assets/assets_common/web/welcome.html b/src_assets/common/assets/web/welcome.html similarity index 100% rename from assets/assets_common/web/welcome.html rename to src_assets/common/assets/web/welcome.html diff --git a/assets/assets_common/sunshine.conf b/src_assets/common/config/sunshine.conf similarity index 100% rename from assets/assets_common/sunshine.conf rename to src_assets/common/config/sunshine.conf diff --git a/assets/assets_linux/shaders/opengl/ConvertUV.frag b/src_assets/linux/assets/shaders/opengl/ConvertUV.frag similarity index 100% rename from assets/assets_linux/shaders/opengl/ConvertUV.frag rename to src_assets/linux/assets/shaders/opengl/ConvertUV.frag diff --git a/assets/assets_linux/shaders/opengl/ConvertUV.vert b/src_assets/linux/assets/shaders/opengl/ConvertUV.vert similarity index 100% rename from assets/assets_linux/shaders/opengl/ConvertUV.vert rename to src_assets/linux/assets/shaders/opengl/ConvertUV.vert diff --git a/assets/assets_linux/shaders/opengl/ConvertY.frag b/src_assets/linux/assets/shaders/opengl/ConvertY.frag similarity index 100% rename from assets/assets_linux/shaders/opengl/ConvertY.frag rename to src_assets/linux/assets/shaders/opengl/ConvertY.frag diff --git a/assets/assets_linux/shaders/opengl/Scene.frag b/src_assets/linux/assets/shaders/opengl/Scene.frag similarity index 100% rename from assets/assets_linux/shaders/opengl/Scene.frag rename to src_assets/linux/assets/shaders/opengl/Scene.frag diff --git a/assets/assets_linux/shaders/opengl/Scene.vert b/src_assets/linux/assets/shaders/opengl/Scene.vert similarity index 100% rename from assets/assets_linux/shaders/opengl/Scene.vert rename to src_assets/linux/assets/shaders/opengl/Scene.vert diff --git a/assets/assets_linux/apps.json b/src_assets/linux/config/apps.json similarity index 100% rename from assets/assets_linux/apps.json rename to src_assets/linux/config/apps.json diff --git a/src_assets/linux/deb/conffiles b/src_assets/linux/deb/conffiles new file mode 100644 index 00000000..4a35822b --- /dev/null +++ b/src_assets/linux/deb/conffiles @@ -0,0 +1,2 @@ +/usr/local/sunshine/config/sunshine.conf +/usr/local/sunshine/config/apps.json diff --git a/assets/linux-deb/postinst b/src_assets/linux/deb/postinst similarity index 59% rename from assets/linux-deb/postinst rename to src_assets/linux/deb/postinst index d3195c6a..a42d3ca5 100644 --- a/assets/linux-deb/postinst +++ b/src_assets/linux/deb/postinst @@ -12,25 +12,25 @@ else echo "Warning: /etc/group not found" fi -if [ -f /etc/sunshine/sunshine.conf.old ]; then +if [ -f /usr/local/sunshine/config/sunshine.conf.old ]; then echo "Restoring old sunshine.conf" - mv /etc/sunshine/sunshine.conf.old /etc/sunshine/sunshine.conf + mv /usr/local/sunshine/config/sunshine.conf.old /usr/local/sunshine/config/sunshine.conf fi -if [ -f /etc/sunshine/apps.json.old ]; then +if [ -f /usr/local/sunshine/config/apps.json.old ]; then echo "Restoring old apps.json" - mv /etc/sunshine/apps.json.old /etc/sunshine/apps.json + mv /usr/local/sunshine/config/apps.json.old /usr/local/sunshine/config/apps.json fi # Update permissions on config files for Web Manager -if [ -f /etc/sunshine/apps.json ]; then +if [ -f /usr/local/sunshine/config/apps.json ]; then echo "chmod 666 /etc/sunshine/apps.json" - chmod 666 /etc/sunshine/apps.json + chmod 666 /usr/local/sunshine/config/apps.json fi -if [ -f /etc/sunshine/sunshine.conf ]; then +if [ -f /usr/local/sunshine/config/sunshine.conf ]; then echo "chmod 666 /etc/sunshine/sunshine.conf" - chmod 666 /etc/sunshine/sunshine.conf + chmod 666 /usr/local/sunshine/config/sunshine.conf fi # Ensure Sunshine can grab images from KMS diff --git a/src_assets/linux/deb/preinst b/src_assets/linux/deb/preinst new file mode 100644 index 00000000..0522b5d6 --- /dev/null +++ b/src_assets/linux/deb/preinst @@ -0,0 +1,9 @@ +#!/bin/sh +#Store backup for old config files to prevent it from being overwritten +if [ -f /usr/local/sunshine/config/sunshine.conf ]; then + cp /usr/local/sunshine/config/sunshine.conf /usr/local/sunshine/config/sunshine.conf.old +fi + +if [ -f /usr/local/sunshine/config/apps.json ]; then + cp /usr/local/sunshine/config/apps.json /usr/local/sunshine/config/apps.json.old +fi diff --git a/assets/linux-misc/85-sunshine-rules.rules b/src_assets/linux/misc/85-sunshine-rules.rules similarity index 100% rename from assets/linux-misc/85-sunshine-rules.rules rename to src_assets/linux/misc/85-sunshine-rules.rules diff --git a/assets/assets_mac/Info.plist b/src_assets/macos/assets/Info.plist similarity index 100% rename from assets/assets_mac/Info.plist rename to src_assets/macos/assets/Info.plist diff --git a/assets/assets_mac/apps.json b/src_assets/macos/config/apps.json similarity index 100% rename from assets/assets_mac/apps.json rename to src_assets/macos/config/apps.json diff --git a/assets/assets_windows/shaders/directx/ConvertUVPS.hlsl b/src_assets/windows/assets/shaders/directx/ConvertUVPS.hlsl similarity index 100% rename from assets/assets_windows/shaders/directx/ConvertUVPS.hlsl rename to src_assets/windows/assets/shaders/directx/ConvertUVPS.hlsl diff --git a/assets/assets_windows/shaders/directx/ConvertUVVS.hlsl b/src_assets/windows/assets/shaders/directx/ConvertUVVS.hlsl similarity index 100% rename from assets/assets_windows/shaders/directx/ConvertUVVS.hlsl rename to src_assets/windows/assets/shaders/directx/ConvertUVVS.hlsl diff --git a/assets/assets_windows/shaders/directx/ConvertYPS.hlsl b/src_assets/windows/assets/shaders/directx/ConvertYPS.hlsl similarity index 100% rename from assets/assets_windows/shaders/directx/ConvertYPS.hlsl rename to src_assets/windows/assets/shaders/directx/ConvertYPS.hlsl diff --git a/assets/assets_windows/shaders/directx/ScenePS.hlsl b/src_assets/windows/assets/shaders/directx/ScenePS.hlsl similarity index 100% rename from assets/assets_windows/shaders/directx/ScenePS.hlsl rename to src_assets/windows/assets/shaders/directx/ScenePS.hlsl diff --git a/assets/assets_windows/shaders/directx/SceneVS.hlsl b/src_assets/windows/assets/shaders/directx/SceneVS.hlsl similarity index 100% rename from assets/assets_windows/shaders/directx/SceneVS.hlsl rename to src_assets/windows/assets/shaders/directx/SceneVS.hlsl diff --git a/assets/assets_windows/apps.json b/src_assets/windows/config/apps.json similarity index 100% rename from assets/assets_windows/apps.json rename to src_assets/windows/config/apps.json diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 6aaeb305..6dd3c1b2 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -20,7 +20,7 @@ using namespace std::literals; #define PRIVATE_KEY_FILE CA_DIR "/cakey.pem" #define CERTIFICATE_FILE CA_DIR "/cacert.pem" -#define APPS_JSON_PATH SUNSHINE_CONFIG_DIR "/" APPS_JSON +#define APPS_JSON_PATH SUNSHINE_CONFIG_DIR "/apps.json" namespace config { namespace nv { @@ -695,7 +695,7 @@ int apply_flags(const char *line) { void apply_config(std::unordered_map &&vars) { if(!fs::exists(stream.file_apps.c_str())) { - fs::copy_file(SUNSHINE_DEFAULT_DIR "/" APPS_JSON, stream.file_apps); + fs::copy_file(SUNSHINE_CONFIG_DIR "/apps.json", stream.file_apps); } for(auto &[name, val] : vars) { @@ -906,7 +906,7 @@ int parse(int argc, char *argv[]) { } if(!fs::exists(sunshine.config_file)) { - fs::copy_file(SUNSHINE_DEFAULT_DIR "/sunshine.conf", sunshine.config_file); + fs::copy_file(SUNSHINE_CONFIG_DIR "/sunshine.conf", sunshine.config_file); } auto vars = parse_config(read_file(sunshine.config_file.c_str())); From c719ddfc31e418c58b4b3244661e33ffd78032fb Mon Sep 17 00:00:00 2001 From: Karl Vogel Date: Sat, 14 May 2022 12:16:14 +0200 Subject: [PATCH 268/817] fedora: add libcap/libdrm-devel to enable KMS code --- scripts/Dockerfile-fedora_33 | 2 ++ scripts/Dockerfile-fedora_35 | 2 ++ 2 files changed, 4 insertions(+) diff --git a/scripts/Dockerfile-fedora_33 b/scripts/Dockerfile-fedora_33 index 136058c3..320c6a67 100644 --- a/scripts/Dockerfile-fedora_33 +++ b/scripts/Dockerfile-fedora_33 @@ -21,6 +21,8 @@ RUN dnf -y update && \ openssl-devel \ opus-devel \ pulseaudio-libs-devel \ + libcap-devel \ + libdrm-devel \ rpm-build \ && dnf clean all \ && rm -rf /var/cache/yum diff --git a/scripts/Dockerfile-fedora_35 b/scripts/Dockerfile-fedora_35 index 18b04377..18f5bf53 100644 --- a/scripts/Dockerfile-fedora_35 +++ b/scripts/Dockerfile-fedora_35 @@ -25,6 +25,8 @@ RUN dnf -y update && \ openssl-devel \ opus-devel \ pulseaudio-libs-devel \ + libcap-devel \ + libdrm-devel \ rpm-build \ && dnf clean all \ && rm -rf /var/cache/yum From 4f6b00148355ecac62ddfb1ea74e04d371524521 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 14 May 2022 08:43:58 -0400 Subject: [PATCH 269/817] Update paths and dependencies - Updates paths for Linux and MacOS builds - Strategy matrix build for Linux (CPACK/AppImage) - Fix dependencies for rpm package --- .github/workflows/CI.yml | 54 ++++++++++++++++++------ CMakeLists.txt | 20 +++++---- Portfile.in | 4 +- src_assets/linux/{deb => misc}/conffiles | 0 src_assets/linux/{deb => misc}/postinst | 0 src_assets/linux/{deb => misc}/preinst | 0 6 files changed, 53 insertions(+), 25 deletions(-) rename src_assets/linux/{deb => misc}/conffiles (100%) rename src_assets/linux/{deb => misc}/postinst (100%) rename src_assets/linux/{deb => misc}/preinst (100%) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 285ef572..71abe96e 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -56,6 +56,19 @@ jobs: name: Linux runs-on: ubuntu-20.04 needs: check_changelog + strategy: + fail-fast: false # false to test all, true to fail entire job if any fail + matrix: + include: # package these differently + - type: cpack + CMAKE_INSTALL_PREFIX: '/usr/local/sunshine' + SUNSHINE_ASSETS_DIR: 'assets' + SUNSHINE_CONFIG_DIR: 'config' + - type: appimage + CMAKE_INSTALL_PREFIX: '.' + SUNSHINE_ASSETS_DIR: 'usr/local/sunshine/assets' + SUNSHINE_CONFIG_DIR: 'usr/local/sunshine/config' + steps: - name: Checkout uses: actions/checkout@v3 @@ -128,21 +141,39 @@ jobs: - name: Build Linux run: | mkdir -p build + mkdir -p artifacts cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=/usr/local/sunshine/.assets -DSUNSHINE_CONFIG_DIR=/usr/local/sunshine/config -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine -DSUNSHINE_ENABLE_WAYLAND=ON -DSUNSHINE_ENABLE_X11=ON -DSUNSHINE_ENABLE_DRM=ON -DSUNSHINE_ENABLE_CUDA=ON .. + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${{ matrix.CMAKE_INSTALL_PREFIX }} -DSUNSHINE_ASSETS_DIR=${{ matrix.SUNSHINE_ASSETS_DIR }} -DSUNSHINE_CONFIG_DIR=${{ matrix.SUNSHINE_CONFIG_DIR }} -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine -DSUNSHINE_ENABLE_WAYLAND=ON -DSUNSHINE_ENABLE_X11=ON -DSUNSHINE_ENABLE_DRM=ON -DSUNSHINE_ENABLE_CUDA=ON .. make -j ${nproc} + - name: Package Linux - CPACK + if: ${{ matrix.type == 'cpack' }} + working-directory: build + run: | + # package + cpack -G DEB + cpack -G RPM + + # move + mv ./cpack_artifacts/Sunshine.deb ../artifacts/sunshine.deb + mv ./cpack_artifacts/Sunshine.rpm ../artifacts/sunshine.rpm + - name: Set AppImage Version - if: ${{ needs.check_changelog.outputs.next_version_bare != needs.check_changelog.outputs.latest_version }} + if: ${{ matrix.type == 'appimage' && ( needs.check_changelog.outputs.next_version_bare != needs.check_changelog.outputs.latest_version ) }} run: | version=${{ needs.check_changelog.outputs.next_version_bare }} echo "VERSION=${version}" >> $GITHUB_ENV - - name: Package Linux + - name: Package Linux - AppImage + if: ${{ matrix.type == 'appimage' }} + working-directory: build run: | - mkdir -p artifacts - cd build + # install sunshine to the DESTDIR + make install DESTDIR=AppDir + + # testing only + ls AppDir # variables DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" @@ -160,7 +191,7 @@ jobs: # export DEPLOY_GTK_VERSION=3 ./linuxdeploy-x86_64.AppImage \ - --appdir . \ + --appdir ./AppDir \ --executable ./sunshine \ --icon-file "../$ICON_FILE" \ --desktop-file "./$DESKTOP_FILE" \ @@ -171,16 +202,11 @@ jobs: # # add this argument back if using gtk plugin # --plugin gtk \ - # package - cpack -G DEB - cpack -G RPM - # move mv Sunshine*.AppImage ../artifacts/sunshine.AppImage - mv ./cpack_artifacts/Sunshine.deb ../artifacts/sunshine.deb - mv ./cpack_artifacts/Sunshine.rpm ../artifacts/sunshine.rpm - name: Verify AppImage + if: ${{ matrix.type == 'appimage' }} run: | wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage chmod +x appimagelint-x86_64.AppImage @@ -193,7 +219,7 @@ jobs: if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v3 with: - name: sunshine-linux + name: sunshine-linux-${{ matrix.type }} path: artifacts/ - name: Create Release @@ -229,7 +255,7 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=/usr/local/sunshine/.assets -DSUNSHINE_CONFIG_DIR=/usr/local/sunshine/config .. + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=/usr/local/sunshine/assets -DSUNSHINE_CONFIG_DIR=/usr/local/sunshine/config .. make -j ${nproc} - name: Package MacOS diff --git a/CMakeLists.txt b/CMakeLists.txt index 68035d54..53358f35 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -393,7 +393,7 @@ else() endif() if(NOT SUNSHINE_ASSETS_DIR) - set(SUNSHINE_ASSETS_DIR "${CMAKE_CURRENT_BINARY_DIR}/.assets") + set(SUNSHINE_ASSETS_DIR "${CMAKE_CURRENT_BINARY_DIR}/assets") endif() if(NOT SUNSHINE_CONFIG_DIR) @@ -535,13 +535,15 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h set(CPACK_COMPONENT_SUNSHINESVC_GROUP "Tools") endif() if(UNIX) + # Installation destination dir + set(CPACK_SET_DESTDIR true) + if(NOT CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_PREFIX "/usr/local/sunshine") + endif() + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") - - # Installation destination dir - set(CPACK_SET_DESTDIR true) - set(CMAKE_INSTALL_PREFIX "/usr/local/sunshine/") endif() if(APPLE) # TODO: test @@ -576,14 +578,14 @@ if(UNIX AND NOT APPLE) # Pre and post install set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA - "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/deb/preinst;${SUNSHINE_SOURCE_ASSETS_DIR}/linux/deb/postinst;${SUNSHINE_SOURCE_ASSETS_DIR}/linux/deb/conffiles") - set(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/deb/preinst") - set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/deb/postinst") + "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/preinst;${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst;${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/conffiles") + set(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/preinst") + set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst") # Dependencies set(CPACK_DEB_COMPONENT_INSTALL ON) set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl1.1 | libssl3.0, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") - set(CPACK_RPM_PACKAGE_REQUIRES "libssl==1.1, libavdevice>=58, libboost-thread>=1.67.0, libboost-filesystem>=1.67.0, libboost-log>=1.67.0, libpulse>=0, libopus>=0, libxcb-shm>=0, libxcb-xfixes>=0, libxtst>=0, libevdev>=2.0, libdrm>=2.0, libcap>=2.0") + set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.1, libavdevice >= 4.3, boost-thread >= 1.67.0, boost-filesystem >= 1.67.0, boost-log >= 1.67.0, pulseaudio-libs >= 10.0, libopusenc >= 0.2.1, libxcb >= 1.13, libXtst >= 1.2.3, libevdev >= 1.5.6, libdrm >= 2.4.97, libcap >= 2.22") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # This should automatically figure out dependencies, doesn't work with the current config # AppImage desktop file diff --git a/Portfile.in b/Portfile.in index 2cb35466..3ab6b0ff 100644 --- a/Portfile.in +++ b/Portfile.in @@ -31,8 +31,8 @@ depends_lib port:avahi \ boost.version 1.76 configure.args -DBOOST_ROOT=[boost::install_area] \ - -DSUNSHINE_ASSETS_DIR=${prefix}/usr/local/sunshine/.assets - -DSUNSHINE_CONFIG_DIR=${prefix}/usr/local/sunshine/config + -DSUNSHINE_ASSETS_DIR=${prefix}/etc/sunshine/assets + -DSUNSHINE_CONFIG_DIR=${prefix}/etc/sunshine/config cmake.out_of_source yes diff --git a/src_assets/linux/deb/conffiles b/src_assets/linux/misc/conffiles similarity index 100% rename from src_assets/linux/deb/conffiles rename to src_assets/linux/misc/conffiles diff --git a/src_assets/linux/deb/postinst b/src_assets/linux/misc/postinst similarity index 100% rename from src_assets/linux/deb/postinst rename to src_assets/linux/misc/postinst diff --git a/src_assets/linux/deb/preinst b/src_assets/linux/misc/preinst similarity index 100% rename from src_assets/linux/deb/preinst rename to src_assets/linux/misc/preinst From f2934c620bef48ee1c3f15b28ac941ec11c207a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Jun 2022 12:35:18 +0000 Subject: [PATCH 270/817] Bump DoozyX/clang-format-lint-action from 0.13 to 0.14 Bumps [DoozyX/clang-format-lint-action](https://github.com/DoozyX/clang-format-lint-action) from 0.13 to 0.14. - [Release notes](https://github.com/DoozyX/clang-format-lint-action/releases) - [Commits](https://github.com/DoozyX/clang-format-lint-action/compare/v0.13...v0.14) --- updated-dependencies: - dependency-name: DoozyX/clang-format-lint-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/clang.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index fd62d261..3c0a70e6 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v3 - name: Clang format lint - uses: DoozyX/clang-format-lint-action@v0.13 + uses: DoozyX/clang-format-lint-action@v0.14 with: source: './sunshine' extensions: 'cpp,h,m,mm' From 0f3eaf0f8406632a678a579e417d72d43bd405dc Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 7 Jun 2022 18:32:09 -0400 Subject: [PATCH 271/817] Add pip for dependabot --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 60b85432..32fce3f3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,3 +6,10 @@ updates: interval: "daily" target-branch: "nightly" open-pull-requests-limit: 20 + + - package-ecosystem: "pip" + directory: "/scripts" + schedule: + interval: "daily" + target-branch: "nightly" + open-pull-requests-limit: 10 From e7ec6050d9a820549d556bf80e6dcb17460ee996 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 7 Jun 2022 18:32:28 -0400 Subject: [PATCH 272/817] Use portable config directory for AppImage --- .github/workflows/CI.yml | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 71abe96e..49436c28 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -65,9 +65,9 @@ jobs: SUNSHINE_ASSETS_DIR: 'assets' SUNSHINE_CONFIG_DIR: 'config' - type: appimage - CMAKE_INSTALL_PREFIX: '.' - SUNSHINE_ASSETS_DIR: 'usr/local/sunshine/assets' - SUNSHINE_CONFIG_DIR: 'usr/local/sunshine/config' + CMAKE_INSTALL_PREFIX: '/usr' + SUNSHINE_ASSETS_DIR: 'sunshine.AppImage.config' + SUNSHINE_CONFIG_DIR: 'sunshine.AppImage.home' steps: - name: Checkout @@ -172,8 +172,11 @@ jobs: # install sunshine to the DESTDIR make install DESTDIR=AppDir - # testing only - ls AppDir + # portable home and config + # todo - this is ugly... we should use a custom AppRun script to take care of this + mv ./AppDir${{ matrix.CMAKE_INSTALL_PREFIX }}/sunshine.AppImage.* ../artifacts/ + mkdir -p ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_CONFIG_DIR }} + cp ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/apps.json ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_CONFIG_DIR }}/ # variables DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" @@ -204,6 +207,9 @@ jobs: # move mv Sunshine*.AppImage ../artifacts/sunshine.AppImage + + # permissions + chmod +x ../artifacts/sunshine.AppImage - name: Verify AppImage if: ${{ matrix.type == 'appimage' }} @@ -215,6 +221,17 @@ jobs: ./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage + - name: Archive AppImage + if: ${{ matrix.type == 'appimage' }} + working-directory: artifacts + run: | + chmod +x ./sunshine.AppImage + + zip --recurse-paths --move --test ./sunshine-appimage.zip ./* + + # testing + ls + - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v3 From 91cf3bcdcc30d2af4eee6f086d18b64bc34ac2b6 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 7 Jun 2022 18:32:28 -0400 Subject: [PATCH 273/817] Use portable config directory for AppImage --- .github/workflows/CI.yml | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 71abe96e..6fa2d004 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -65,9 +65,9 @@ jobs: SUNSHINE_ASSETS_DIR: 'assets' SUNSHINE_CONFIG_DIR: 'config' - type: appimage - CMAKE_INSTALL_PREFIX: '.' - SUNSHINE_ASSETS_DIR: 'usr/local/sunshine/assets' - SUNSHINE_CONFIG_DIR: 'usr/local/sunshine/config' + CMAKE_INSTALL_PREFIX: '/usr' + SUNSHINE_ASSETS_DIR: 'sunshine.AppImage.config' + SUNSHINE_CONFIG_DIR: 'sunshine.AppImage.home' steps: - name: Checkout @@ -172,8 +172,11 @@ jobs: # install sunshine to the DESTDIR make install DESTDIR=AppDir - # testing only - ls AppDir + # portable home and config + # todo - this is ugly... we should use a custom AppRun script to take care of this + mv ./AppDir${{ matrix.CMAKE_INSTALL_PREFIX }}/sunshine.AppImage.* ../artifacts/ + mkdir -p ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_CONFIG_DIR }} + cp ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/apps.json ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_CONFIG_DIR }}/ # variables DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" @@ -204,6 +207,9 @@ jobs: # move mv Sunshine*.AppImage ../artifacts/sunshine.AppImage + + # permissions + chmod +x ../artifacts/sunshine.AppImage - name: Verify AppImage if: ${{ matrix.type == 'appimage' }} @@ -215,6 +221,14 @@ jobs: ./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage + - name: Archive AppImage + if: ${{ matrix.type == 'appimage' }} + working-directory: artifacts + run: | + chmod +x ./sunshine.AppImage + + zip --recurse-paths --move --test ./sunshine-appimage.zip ./* + - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v3 From 34f1e89366bf716aadc40c1e287b303627cc5c58 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 8 Jun 2022 21:01:34 -0400 Subject: [PATCH 274/817] Update linux installation instructions --- docs/source/about/installation.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 40eecd20..4fab6e68 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -43,22 +43,24 @@ The current known compatibility of the AppImage is shown below. #. Download and extract `sunshine-appimage.zip` +#. Edit ``/etc/udev/rules.d/85-sunshine-rules.rules`` and add + ``KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput"`` Debian Packages ^^^^^^^^^^^^^^^ .. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:debian?logo=github&style=for-the-badge :alt: GitHub issues by-label -#. Download the corresponding `.deb` file, e.g. ``sunshine-ubuntu_20_04.deb`` -#. ``sudo apt install -f ``, e.g. ``sudo apt install -f ./sunshine-ubuntu_20_04.deb`` +#. Download ``sunshine.deb`` +#. ``sudo apt install -f ./sunshine.deb`` Red Hat Packages ^^^^^^^^^^^^^^^^ .. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:fedora?logo=github&style=for-the-badge :alt: GitHub issues by-label -#. Download the corresponding `.rpm` file, e.g. ``sunshine-fedora_35.rpm`` -#. ``sudo dnf install ``, e.g. ``sudo dnf install ./sunshine-fedora_35.rpm`` +#. Download ``sunshine.rpm`` +#. ``sudo dnf install ./sunshine.rpm`` .. Hint:: If this is the first time installing. From 4a65d2cafcbd9baf0867168bf47824190e98bce8 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 8 Jun 2022 21:02:28 -0400 Subject: [PATCH 275/817] Fix libssl3 dependency for Ubuntu 22.04 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 53358f35..6081f8d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -584,7 +584,7 @@ if(UNIX AND NOT APPLE) # Dependencies set(CPACK_DEB_COMPONENT_INSTALL ON) - set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl1.1 | libssl3.0, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl1.1 | libssl3, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.1, libavdevice >= 4.3, boost-thread >= 1.67.0, boost-filesystem >= 1.67.0, boost-log >= 1.67.0, pulseaudio-libs >= 10.0, libopusenc >= 0.2.1, libxcb >= 1.13, libXtst >= 1.2.3, libevdev >= 1.5.6, libdrm >= 2.4.97, libcap >= 2.22") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # This should automatically figure out dependencies, doesn't work with the current config From 7cb0286414d6f60dc3f9256588fab0c7f926cf61 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 8 Jun 2022 21:27:41 -0400 Subject: [PATCH 276/817] Fix paths for linux cpack build --- .github/workflows/CI.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6fa2d004..beea51ec 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -61,9 +61,9 @@ jobs: matrix: include: # package these differently - type: cpack - CMAKE_INSTALL_PREFIX: '/usr/local/sunshine' - SUNSHINE_ASSETS_DIR: 'assets' - SUNSHINE_CONFIG_DIR: 'config' + CMAKE_INSTALL_PREFIX: '' + SUNSHINE_ASSETS_DIR: '/usr/local/sunshine/assets' + SUNSHINE_CONFIG_DIR: '/usr/local/sunshine/config' - type: appimage CMAKE_INSTALL_PREFIX: '/usr' SUNSHINE_ASSETS_DIR: 'sunshine.AppImage.config' From 9b4fc8a2708bc2ed9aee6baa92bbff32cb813b6f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 8 Jun 2022 23:00:18 -0400 Subject: [PATCH 277/817] Fix udev rules - Adds to permission to create "Sunshine Mouse" --- src_assets/linux/misc/85-sunshine-rules.rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src_assets/linux/misc/85-sunshine-rules.rules b/src_assets/linux/misc/85-sunshine-rules.rules index 46a78a4a..77c337c4 100644 --- a/src_assets/linux/misc/85-sunshine-rules.rules +++ b/src_assets/linux/misc/85-sunshine-rules.rules @@ -1 +1 @@ -KERNEL=="uinput", GROUP="input", MODE="0660" \ No newline at end of file +KERNEL=="uinput", GROUP="input", MODE="0660", OPTION+="static_node=uinput" \ No newline at end of file From e9b46201fd65e53305bc6d510e4ce4dc6e43548e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Jun 2022 12:42:27 +0000 Subject: [PATCH 278/817] Bump actions/setup-python from 3 to 4 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 3 to 4. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/localize.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index 31f22997..f4828e99 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v3 - name: Install Python 3.9 - uses: actions/setup-python@v3 # https://github.com/actions/setup-python + uses: actions/setup-python@v4 # https://github.com/actions/setup-python with: python-version: '3.9' From 65c4f019989009389ca95d9598b943acf96647e2 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 8 Jun 2022 23:00:59 -0400 Subject: [PATCH 279/817] Update documentation - Add copy button to code blocks - Fix some badges - Improve Linux installation instructions --- docs/source/about/advanced_usage.rst | 2 +- docs/source/about/installation.rst | 59 ++++++++++++++------ docs/source/about/third_party_packages.rst | 26 +++------ docs/source/about/usage.rst | 63 +++++++++++++++++----- docs/source/conf.py | 3 +- scripts/requirements.txt | 1 + 6 files changed, 105 insertions(+), 49 deletions(-) diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index 7699232c..61c9e60f 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -1055,7 +1055,7 @@ Description by Moonlight. Default - OS dependent + OS and package dependent Example .. code-block:: text diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 4fab6e68..deb6f60a 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -21,6 +21,26 @@ Docker Linux ----- +Follow the instructions for your preferred package type below. + +.. Hint:: If this is the first time installing. + + .. code-block:: bash + + sudo usermod -a -G input $USER + sudo reboot now + +.. Tip:: Optionally, run Sunshine in the background. + + .. code-block:: bash + + systemctl --user start sunshine + +.. Note:: If screencasting fails with Wayland, you may need to run the following to force screencasting with X11. + + .. code-block:: bash + + sudo setcap -r $(readlink -f $(which sunshine)) AppImage ^^^^^^^^ @@ -41,39 +61,40 @@ The current known compatibility of the AppImage is shown below. - [✖] Ubuntu trusty - [✖] CentOS 7 - -#. Download and extract `sunshine-appimage.zip` -#. Edit ``/etc/udev/rules.d/85-sunshine-rules.rules`` and add - ``KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput"`` +#. Download and extract ``sunshine-appimage.zip`` to your home directory. Debian Packages ^^^^^^^^^^^^^^^ .. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:debian?logo=github&style=for-the-badge :alt: GitHub issues by-label -#. Download ``sunshine.deb`` -#. ``sudo apt install -f ./sunshine.deb`` +#. Download ``sunshine.deb`` and run the following code. + + .. code-block:: bash + + sudo apt install -f ./sunshine.deb + +.. Tip:: You can double click the deb file to see details about the package and begin installation. Red Hat Packages ^^^^^^^^^^^^^^^^ .. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:fedora?logo=github&style=for-the-badge :alt: GitHub issues by-label -#. Download ``sunshine.rpm`` -#. ``sudo dnf install ./sunshine.rpm`` +#. Add `rpmfusion` repositories by running the following code. -.. Hint:: If this is the first time installing. + .. code-block:: bash - .. code-block:: bash + sudo dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm \ + https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm - sudo usermod -a -G input $USER - sudo reboot now +#. Download ``sunshine.rpm`` and run the following code. -.. Tip:: Optionally, run Sunshine in the background. + .. code-block:: bash - .. code-block:: bash + sudo dnf install ./sunshine.rpm - systemctl --user start sunshine +.. Tip:: You can double click the rpm file to see details about the package and begin installation. MacOS ----- @@ -87,8 +108,12 @@ Disk Image File option: Portfile option: #. Install `MacPorts `_ - #. Download the ``Portfile`` to ``/tmp`` - #. In a terminal run ``cd /tmp && sudo port install`` + #. Download the ``Portfile`` to ``/tmp`` and run the following code. + + .. code-block:: bash + + cd /tmp && sudo port install + #. The first time you start Sunshine, you will be asked to grant access to screen recording and your microphone. Standalone option: diff --git a/docs/source/about/third_party_packages.rst b/docs/source/about/third_party_packages.rst index 6069258a..5b63a496 100644 --- a/docs/source/about/third_party_packages.rst +++ b/docs/source/about/third_party_packages.rst @@ -10,6 +10,7 @@ AUR (Arch Linux User Repository) .. image:: https://img.shields.io/aur/version/sunshine?style=for-the-badge&logo=archlinux :alt: AUR version + :target: https://aur.archlinux.org/packages/sunshine .. image:: https://img.shields.io/aur/last-modified/sunshine?style=for-the-badge&logo=archlinux :alt: AUR last modified @@ -20,38 +21,31 @@ AUR (Arch Linux User Repository) .. image:: https://img.shields.io/aur/maintainer/sunshine?style=for-the-badge&logo=archlinux :alt: AUR maintainer -.. image:: https://img.shields.io/static/v1?label=maintainer&message=hadogenes&color=blue&style=for-the-badge&logo=github - :alt: GitHub Maintainer - :target: https://github.com/hadogenes - 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 .. image:: https://img.shields.io/chocolatey/dt/sunshine?style=for-the-badge&logo=chocolatey :alt: Chocolatey -.. image:: https://img.shields.io/static/v1?label=maintainer&message=AeliusSaionji&color=blue&style=for-the-badge&logo=github - :alt: GitHub Maintainer - :target: https://github.com/AeliusSaionji - Scoop ----- .. image:: https://img.shields.io/scoop/v/sunshine?bucket=extras&style=for-the-badge :alt: Scoop Version (extras bucket) - -.. image:: https://img.shields.io/static/v1?label=maintainer&message=sitiom&color=blue&style=for-the-badge&logo=github - :alt: GitHub Maintainer - :target: https://github.com/sitiom - + :target: https://scoop.sh/#/apps?s=0&d=1&o=true&q=sunshine Legacy GitHub Repo ------------------ -.. Attention:: This repo is no longer maintained. Thank you to Loki for bringing this amazing project to life! +.. Attention:: This repo is not maintained. Thank you to Loki for bringing this amazing project to life! + +.. image:: https://img.shields.io/static/v1?label=repo&message=loki-47-6F-64/sunshine&color=blue&style=for-the-badge&logo=github + :alt: GitHub Maintainer + :target: https://github.com/loki-47-6F-64/sunshine/releases .. image:: https://img.shields.io/github/last-commit/loki-47-6F-64/sunshine?style=for-the-badge&logo=github :alt: GitHub last commit @@ -61,7 +55,3 @@ Legacy GitHub Repo .. image:: https://img.shields.io/github/downloads/loki-47-6F-64/sunshine/total?style=for-the-badge&logo=github :alt: GitHub Releases - -.. image:: https://img.shields.io/static/v1?label=maintainer&message=loki-47-6F-64&color=blue&style=for-the-badge&logo=github - :alt: GitHub Maintainer - :target: https://github.com/loki-47-6F-64 diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst index 4bb81bc5..8f73d204 100644 --- a/docs/source/about/usage.rst +++ b/docs/source/about/usage.rst @@ -23,8 +23,9 @@ Usage Add games and applications. This can be configured in the web ui. - .. Note:: Additionally, apps can be configured manually. `assets/apps_.json` is an example of a list of - applications that are started just before running a stream. + .. Note:: Additionally, apps can be configured manually. `src_assets//config/apps.json` is an example of a + list of applications that are started just before running a stream. This is the directory within the GitHub + repo. .. Attention:: Application list is not fully supported on MacOS @@ -55,6 +56,9 @@ Setup Linux ^^^^^ +The deb and rpm packages handle these steps automatically. The AppImage does not, and third party packages may not as +well. + Sunshine needs access to `uinput` to create mouse and gamepad events. Add user to group `input`. @@ -62,13 +66,23 @@ Add user to group `input`. usermod -a -G input $USER + .. Warning:: If the above doesn't work you can try the following. This is not an advised method as it makes the + current user the owner of ``/dev/uinput``. Use at your own risk. + + .. code-block:: bash + + sudo chown $USER /dev/uinput + Create `udev` rules. .. code-block:: bash nano /etc/udev/rules.d/85-sunshine-input.rules - Input the following contents: - ``KERNEL=="uinput", GROUP="input", MODE="0660"`` + Input the following contents. + + .. code-block:: + + KERNEL=="uinput", GROUP="input", MODE="0660", OPTION+="static_node=uinput" Save the file and exit: @@ -76,18 +90,43 @@ Create `udev` rules. #. ``Y`` to save modifications. Configure autostart service - `path/to/build/dir/sunshine.service` is used to start sunshine in the background. To use it, do the following: + - filename: ``~/.config/systemd/user/sunshine.service`` + - contents: + + .. code-block:: + + [Unit] + Description=Sunshine Gamestream Server for Moonlight - #. Copy it to the users systemd: ``cp sunshine.service ~/.config/systemd/user/`` - #. Starting + [Service] + ExecStart=~/sunshine.AppImage - - One time: ``systemctl --user start sunshine`` - - Always on boot: ``systemctl --user enable sunshine`` + [Install] + WantedBy=graphical-session.target + + Start once + .. code-block:: bash + + systemctl --user start sunshine + + Start on boot + .. code-block:: bash + + systemctl --user enable sunshine Additional Setup for KMS - .. Note:: ``cap_sys_admin`` may as well be root, except you don't need to be root to run it. + .. Note:: ``cap_sys_admin`` may as well be root, except you don't need to be root to run it. It is necessary to + allow Sunshine to use KMS. + + Enable + .. code-block:: bash + + sudo setcap cap_sys_admin+p $(readlink -f $(which sunshine)) + + Disable + .. code-block:: bash - It is necessary to allow Sunshine to use KMS: ``sudo setcap cap_sys_admin+p sunshine`` + sudo setcap -r $(readlink -f $(which sunshine)) MacOS ^^^^^ @@ -114,7 +153,7 @@ All shortcuts start with CTRL + ALT + SHIFT, just like Moonlight Application List ---------------- - You can use Environment variables in place of values -- ``$(HOME)` will be replaced by the value of ``$HOME`` +- ``$(HOME)`` will be replaced by the value of ``$HOME`` - ``$$`` will be replaced by ``$``, e.g. ``$$(HOME)`` will be replaced by ``$(HOME)`` - ``env`` - Adds or overwrites Environment variables for the commands/applications run by Sunshine - ``"Variable name":"Variable value"`` diff --git a/docs/source/conf.py b/docs/source/conf.py index f609783c..f4873f15 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -46,7 +46,8 @@ 'm2r2', # enable markdown files 'sphinx.ext.autosectionlabel', 'sphinx.ext.todo', # enable to-do sections - 'sphinx.ext.viewcode' # add links to view source code + 'sphinx.ext.viewcode', # add links to view source code + 'sphinx_copybutton', # add a copy button to code blocks ] # Add any paths that contain templates here, relative to this directory. diff --git a/scripts/requirements.txt b/scripts/requirements.txt index c089efd9..73eefbe1 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -1,4 +1,5 @@ Babel==2.9.1 m2r2==0.3.2 Sphinx==4.5.0 +sphinx-copybutton==0.5.0 sphinx-rtd-theme==1.0.0 From 7a5890469ca60bbe926a4f852f723c741dcbcf91 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 10 Jun 2022 23:20:24 -0400 Subject: [PATCH 280/817] Update MacOS build --- .github/workflows/CI.yml | 95 ++++++++++++++++++++++++++++-- CMakeLists.txt | 3 +- Portfile.in | 86 +++++++++++++++++---------- docs/source/about/installation.rst | 26 +++++++- 4 files changed, 172 insertions(+), 38 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index beea51ec..b5c0772b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -247,7 +247,7 @@ jobs: build_mac: name: MacOS - runs-on: macos-11 + runs-on: macos-12 needs: check_changelog steps: @@ -264,12 +264,33 @@ jobs: # fix openssl header not found cd /usr/local/include ln -s ../opt/openssl/include/openssl . + + # update paths for macports + echo "/opt/local/sbin" >> $GITHUB_PATH + echo "/opt/local/bin" >> $GITHUB_PATH - name: Build MacOS run: | + # variables for Portfile + owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]' ) + repo=$(echo ${GITHUB_REPOSITORY#*/} | tr '[:upper:]' '[:lower:]' ) + branch=${GITHUB_HEAD_REF} + + # check the branch variable + if [ -z "$branch" ] + then + echo "This is a PUSH event" + branch=${GITHUB_BASE_REF} + else + echo "This is a PR event" + fi + echo "Owner: ${owner}" + echo "Repo: ${repo}" + echo "Branch: ${branch}" + mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=/usr/local/sunshine/assets -DSUNSHINE_CONFIG_DIR=/usr/local/sunshine/config .. + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=/usr/local/sunshine/assets -DSUNSHINE_CONFIG_DIR=/usr/local/sunshine/config -DGITHUB_OWNER=${owner} -DGITHUB_REPO=${repo} -DGITHUB_BRANCH=${branch} .. make -j ${nproc} - name: Package MacOS @@ -279,12 +300,69 @@ jobs: # package cpack -G DragNDrop + mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-macos-experimental-dragndrop.dmg + + cpack -G Bundle + mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-macos-experimental-bundle.dmg + cpack -G ZIP + mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-macos-experimental-archive.zip # move mv Portfile ../artifacts/Portfile - mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-macos.dmg - mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-macos.zip + + - name: Setup Macports + run : | + curl -O https://distfiles.macports.org/MacPorts/MacPorts-2.7.2.tar.bz2 + tar xf MacPorts-2.7.2.tar.bz2 + + cd MacPorts-2.7.2 + ./configure + make + sudo make install + cd ../ + rm -rf MacPorts-2.7.2* + + # update sources + sudo port -v selfupdate + + # use custom sources + sudo chmod 777 /opt/local/etc/macports/sources.conf + echo file://$(echo ~)/ports > /opt/local/etc/macports/sources.conf + echo rsync://rsync.macports.org/macports/release/tarballs/ports.tar [default] >> /opt/local/etc/macports/sources.conf + sudo chmod 644 /opt/local/etc/macports/sources.conf + + - name: Package with MacPorts + run: | + # setup custom port + mkdir -p ~/ports/multimedia/sunshine + + # copy configured Portfile + cp ./artifacts/Portfile ~/ports/multimedia/sunshine/ + + # index the ports + cd ~/ports + portindex + + # build port + sudo port install sunshine + # || cat /opt/local/var/macports/logs/_Users_runner_ports_multimedia_sunshine/Sunshine/main.log \ + # && exit 1 + + # create packages + sudo port dmg sunshine + sudo port pkg sunshine + + # move + mv $(port work sunshine)/Sunshine*.dmg ./artifacts/sunshine.dmg + mv $(port work sunshine)/Sunshine*.ppkg ./artifacts/sunshine.pkg + + # testing only + ls ~/ports/multimedia/sunshine + cat ~/ports/multimedia/sunshine/Portfile + cat /opt/local/etc/macports/sources.conf + cat ~/ports/Portindex + port search sunshine - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} @@ -293,6 +371,15 @@ jobs: name: sunshine-macos path: artifacts/ + # this step can be removed after packages are fixed + - name: Delete experimental packages + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + working-directory: artifacts + run: | + rm -f ./sunshine-macos-experimental-dragndrop.dmg + rm -f ./sunshine-macos-experimental-bundle.dmg + rm -f ./sunshine-macos-experimental-archive.zip + - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} uses: SunshineStream/actions/create_release@master diff --git a/CMakeLists.txt b/CMakeLists.txt index 6081f8d2..2963417e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -443,9 +443,7 @@ endforeach() target_compile_options(sunshine PRIVATE $<$:${SUNSHINE_COMPILE_OPTIONS}>;$<$:${SUNSHINE_COMPILE_OPTIONS_CUDA};-std=c++17>) -############# # CPACK / Packaging -#### # Common options set(CPACK_PACKAGE_NAME "SunshineStream") @@ -562,6 +560,7 @@ if(APPLE) # TODO: test set(CPACK_BUNDLE_NAME "${CMAKE_PROJECT_NAME}") set(CPACK_BUNDLE_PLIST "${APPLE_PLIST_FILE}") set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/sunshine.icns") + # set(CPACK_BUNDLE_STARTUP_COMMAND "${INSTALL_RUNTIME_DIR}/sunshine") # Portfile configure_file(Portfile.in Portfile @ONLY) diff --git a/Portfile.in b/Portfile.in index 3ab6b0ff..36a7d476 100644 --- a/Portfile.in +++ b/Portfile.in @@ -1,49 +1,75 @@ # -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4 -PortSystem 1.0 -PortGroup cmake 1.1 -PortGroup github 1.0 -PortGroup boost 1.0 +PortSystem 1.0 +PortGroup cmake 1.1 +PortGroup github 1.0 +PortGroup boost 1.0 -github.setup sunshinestream sunshine master -version @PROJECT_VERSION@ -revision 1 +# bump revision when changes are made to this file +revision 1 -categories multimedia -license GPL-3 -maintainers {sunshinestream @SunshineStream} {outlook.com:anselm.busse} -platforms darwin +github.setup @GITHUB_OWNER@ @GITHUB_REPO@ @GITHUB_BRANCH@ +name @PROJECT_NAME@ +version @PROJECT_VERSION@ +categories multimedia emulators games +platforms darwin +license GPL-3 +maintainers {@SunshineStream sunshinestream} +description @PROJECT_DESCRIPTION@ +long_description {*}${description} +homepage @PROJECT_HOMEPAGE_URL@ +master_sites https://github.com/@GITHUB_OWNER@/@GITHUB_REPO@/releases -fetch.type git +fetch.type git post-fetch { system -W ${worksrcpath} "${git.cmd} submodule update --init --recursive" } -description @PROJECT_DESCRIPTION@ -long_description {*}${description} -homepage @PROJECT_HOMEPAGE_URL@ +depends_lib port:avahi \ + port:ffmpeg \ + port:libopus -depends_lib port:avahi \ - port:ffmpeg \ - port:libopus +boost.version 1.76 +configure.args -DBOOST_ROOT=[boost::install_area] \ + -DSUNSHINE_ASSETS_DIR=${prefix}/etc/sunshine/assets \ + -DSUNSHINE_CONFIG_DIR=${prefix}/etc/sunshine/config -boost.version 1.76 +cmake.out_of_source yes -configure.args -DBOOST_ROOT=[boost::install_area] \ - -DSUNSHINE_ASSETS_DIR=${prefix}/etc/sunshine/assets - -DSUNSHINE_CONFIG_DIR=${prefix}/etc/sunshine/config - -cmake.out_of_source yes +startupitem.create yes +startupitem.executable "${prefix}/bin/{$name}" +startupitem.location LaunchDaemons +startupitem.name ${name} +startupitem.netchange yes +# is this actually necessary? this should all be handled by CMakeLists destroot { - xinstall -d -m 755 ${destroot}${prefix}/usr/local/${name}/.assets - xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/*] ${destroot}${prefix}/usr/local/${name}/.assets - xinstall {*}[glob ${worksrcpath}/src_assets/macos/assets/*] ${destroot}${prefix}/usr/local/${name}/.assets + # install assets + xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets + xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/*.*] ${destroot}${prefix}/etc/${name}/assets + xinstall {*}[glob ${worksrcpath}/src_assets/macos/assets/*.*] ${destroot}${prefix}/etc/${name}/assets + + # install web assets + xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web/css + xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web/webfonts + xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/*.*] ${destroot}${prefix}/etc/${name}/assets/web + xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/fonts/fontawesome-free-web/*.*] ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web + xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/fonts/fontawesome-free-web/css/*.*] ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web/css + xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/*.*] ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web/webfonts + xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets/web/images + xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets/web/third_party + xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/images/*.*] ${destroot}${prefix}/etc/${name}/assets/web/images + xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/third_party/*.*] ${destroot}${prefix}/etc/${name}/assets/web/third_party + + xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/config + + # install sunshine.conf + xinstall {*}[glob ${worksrcpath}/src_assets/common/config/*.*] ${destroot}${prefix}/etc/${name}/config - xinstall -d -m 755 ${destroot}${prefix}/usr/local/${name}/config - xinstall {*}[glob ${worksrcpath}/src_assets/common/config/*] ${destroot}${prefix}/usr/local/${name}/config - xinstall {*}[glob ${worksrcpath}/src_assets/macos/config/*] ${destroot}${prefix}/usr/local/${name}/config + # install apps.json + xinstall {*}[glob ${worksrcpath}/src_assets/macos/config/*.*] ${destroot}${prefix}/etc/${name}/config + # install the binary xinstall ${workpath}/build/${name} ${destroot}${prefix}/bin } diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index deb6f60a..ab4b6888 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -108,13 +108,35 @@ Disk Image File option: Portfile option: #. Install `MacPorts `_ - #. Download the ``Portfile`` to ``/tmp`` and run the following code. + #. Update the Macports sources. .. code-block:: bash - cd /tmp && sudo port install + sudo nano /opt/local/etc/macports/sources.conf + + Add this line, replacing your username, below the line that starts with ``rsync``. + + file://Users//ports + + ``Ctrl+x``, then ``Y`` to exit and save changes. + + #. Download the ``Portfile`` to ``~/Downloads`` and run the following code. + + .. code-block:: bash + + mkdir -p ~/ports/multimedia/sunshine + mv ~/Downlaods/Portfile ~/ports/multimedia/sunshine + cd ~/ports + portindex + sudo port install sunshine #. The first time you start Sunshine, you will be asked to grant access to screen recording and your microphone. + #. Try to run the following code if you get this error: `Dynamic session lookup supported but failed: launchd did + not provide a socket path, verify that org.freedesktop.dbus-session.plist is loaded!` + + .. code-block:: bash + + launchctl load -w /Library/LaunchAgents/org.freedesktop.dbus-session.plist Standalone option: #. Download and extract ``sunshine-macos.zip`` From 1a1cf20152ebf7f4134b0a93505fe4912d7149f5 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 10 Jun 2022 23:20:24 -0400 Subject: [PATCH 281/817] Update MacOS build --- .github/workflows/CI.yml | 99 +++++++++++++++++++++++++++++- CMakeLists.txt | 3 +- Portfile.in | 86 +++++++++++++++++--------- docs/source/about/installation.rst | 26 +++++++- 4 files changed, 177 insertions(+), 37 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index beea51ec..b9447f12 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -264,12 +264,35 @@ jobs: # fix openssl header not found cd /usr/local/include ln -s ../opt/openssl/include/openssl . + + # update paths for macports + # echo "/opt/local/sbin" >> $GITHUB_PATH + echo "/Users/runner/macports/sbin" >> $GITHUB_PATH + # echo "/opt/local/bin" >> $GITHUB_PATH + echo "/Users/runner/macports/bin" >> $GITHUB_PATH - name: Build MacOS run: | + # variables for Portfile + owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]' ) + repo=$(echo ${GITHUB_REPOSITORY#*/} | tr '[:upper:]' '[:lower:]' ) + branch=${GITHUB_HEAD_REF} + + # check the branch variable + if [ -z "$branch" ] + then + echo "This is a PUSH event" + branch=${GITHUB_BASE_REF} + else + echo "This is a PR event" + fi + echo "Owner: ${owner}" + echo "Repo: ${repo}" + echo "Branch: ${branch}" + mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=/usr/local/sunshine/assets -DSUNSHINE_CONFIG_DIR=/usr/local/sunshine/config .. + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=/usr/local/sunshine/assets -DSUNSHINE_CONFIG_DIR=/usr/local/sunshine/config -DGITHUB_OWNER=${owner} -DGITHUB_REPO=${repo} -DGITHUB_BRANCH=${branch} .. make -j ${nproc} - name: Package MacOS @@ -279,12 +302,73 @@ jobs: # package cpack -G DragNDrop + mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-macos-experimental-dragndrop.dmg + + cpack -G Bundle + mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-macos-experimental-bundle.dmg + cpack -G ZIP + mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-macos-experimental-archive.zip # move mv Portfile ../artifacts/Portfile - mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-macos.dmg - mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-macos.zip + + - name: Setup Macports + run : | + curl -O https://distfiles.macports.org/MacPorts/MacPorts-2.7.2.tar.bz2 + tar xf MacPorts-2.7.2.tar.bz2 + + cd MacPorts-2.7.2 + ./configure --prefix=/Users/runner/macports + make + sudo make install + cd ../ + rm -rf MacPorts-2.7.2* + + # update sources + sudo port -v selfupdate + + # use custom sources + # sudo chmod 777 /opt/local/etc/macports/sources.conf + sudo chmod 777 /Users/runner/macports/etc/macports/sources.conf + # echo file://$(echo ~)/ports > /opt/local/etc/macports/sources.conf + echo file://$(echo ~)/ports > /Users/runner/macports/etc/macports/sources.conf + # echo rsync://rsync.macports.org/macports/release/tarballs/ports.tar [default] >> /opt/local/etc/macports/sources.conf + echo rsync://rsync.macports.org/macports/release/tarballs/ports.tar [default] >> /Users/runner/macports/etc/macports/sources.conf + # sudo chmod 644 /opt/local/etc/macports/sources.conf + sudo chmod 644 /Users/runner/macports/etc/macports/sources.conf + + - name: Package with MacPorts + run: | + # setup custom port + mkdir -p ~/ports/multimedia/sunshine + + # copy configured Portfile + cp ./artifacts/Portfile ~/ports/multimedia/sunshine/ + + # index the ports + cd ~/ports + portindex + + # build port + sudo port install sunshine + # || cat /opt/local/var/macports/logs/_Users_runner_ports_multimedia_sunshine/Sunshine/main.log \ + # && exit 1 + + # create packages + sudo port dmg sunshine + sudo port pkg sunshine + + # move + mv $(port work sunshine)/Sunshine*.dmg ./artifacts/sunshine.dmg + mv $(port work sunshine)/Sunshine*.ppkg ./artifacts/sunshine.pkg + + # testing only + # ls ~/ports/multimedia/sunshine + # cat ~/ports/multimedia/sunshine/Portfile + # cat /opt/local/etc/macports/sources.conf + # cat ~/ports/Portindex + # port search sunshine - name: Upload Artifacts if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} @@ -293,6 +377,15 @@ jobs: name: sunshine-macos path: artifacts/ + # this step can be removed after packages are fixed + - name: Delete experimental packages + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + working-directory: artifacts + run: | + rm -f ./sunshine-macos-experimental-dragndrop.dmg + rm -f ./sunshine-macos-experimental-bundle.dmg + rm -f ./sunshine-macos-experimental-archive.zip + - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} uses: SunshineStream/actions/create_release@master diff --git a/CMakeLists.txt b/CMakeLists.txt index 6081f8d2..2963417e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -443,9 +443,7 @@ endforeach() target_compile_options(sunshine PRIVATE $<$:${SUNSHINE_COMPILE_OPTIONS}>;$<$:${SUNSHINE_COMPILE_OPTIONS_CUDA};-std=c++17>) -############# # CPACK / Packaging -#### # Common options set(CPACK_PACKAGE_NAME "SunshineStream") @@ -562,6 +560,7 @@ if(APPLE) # TODO: test set(CPACK_BUNDLE_NAME "${CMAKE_PROJECT_NAME}") set(CPACK_BUNDLE_PLIST "${APPLE_PLIST_FILE}") set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/sunshine.icns") + # set(CPACK_BUNDLE_STARTUP_COMMAND "${INSTALL_RUNTIME_DIR}/sunshine") # Portfile configure_file(Portfile.in Portfile @ONLY) diff --git a/Portfile.in b/Portfile.in index 3ab6b0ff..36a7d476 100644 --- a/Portfile.in +++ b/Portfile.in @@ -1,49 +1,75 @@ # -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4 -PortSystem 1.0 -PortGroup cmake 1.1 -PortGroup github 1.0 -PortGroup boost 1.0 +PortSystem 1.0 +PortGroup cmake 1.1 +PortGroup github 1.0 +PortGroup boost 1.0 -github.setup sunshinestream sunshine master -version @PROJECT_VERSION@ -revision 1 +# bump revision when changes are made to this file +revision 1 -categories multimedia -license GPL-3 -maintainers {sunshinestream @SunshineStream} {outlook.com:anselm.busse} -platforms darwin +github.setup @GITHUB_OWNER@ @GITHUB_REPO@ @GITHUB_BRANCH@ +name @PROJECT_NAME@ +version @PROJECT_VERSION@ +categories multimedia emulators games +platforms darwin +license GPL-3 +maintainers {@SunshineStream sunshinestream} +description @PROJECT_DESCRIPTION@ +long_description {*}${description} +homepage @PROJECT_HOMEPAGE_URL@ +master_sites https://github.com/@GITHUB_OWNER@/@GITHUB_REPO@/releases -fetch.type git +fetch.type git post-fetch { system -W ${worksrcpath} "${git.cmd} submodule update --init --recursive" } -description @PROJECT_DESCRIPTION@ -long_description {*}${description} -homepage @PROJECT_HOMEPAGE_URL@ +depends_lib port:avahi \ + port:ffmpeg \ + port:libopus -depends_lib port:avahi \ - port:ffmpeg \ - port:libopus +boost.version 1.76 +configure.args -DBOOST_ROOT=[boost::install_area] \ + -DSUNSHINE_ASSETS_DIR=${prefix}/etc/sunshine/assets \ + -DSUNSHINE_CONFIG_DIR=${prefix}/etc/sunshine/config -boost.version 1.76 +cmake.out_of_source yes -configure.args -DBOOST_ROOT=[boost::install_area] \ - -DSUNSHINE_ASSETS_DIR=${prefix}/etc/sunshine/assets - -DSUNSHINE_CONFIG_DIR=${prefix}/etc/sunshine/config - -cmake.out_of_source yes +startupitem.create yes +startupitem.executable "${prefix}/bin/{$name}" +startupitem.location LaunchDaemons +startupitem.name ${name} +startupitem.netchange yes +# is this actually necessary? this should all be handled by CMakeLists destroot { - xinstall -d -m 755 ${destroot}${prefix}/usr/local/${name}/.assets - xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/*] ${destroot}${prefix}/usr/local/${name}/.assets - xinstall {*}[glob ${worksrcpath}/src_assets/macos/assets/*] ${destroot}${prefix}/usr/local/${name}/.assets + # install assets + xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets + xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/*.*] ${destroot}${prefix}/etc/${name}/assets + xinstall {*}[glob ${worksrcpath}/src_assets/macos/assets/*.*] ${destroot}${prefix}/etc/${name}/assets + + # install web assets + xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web/css + xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web/webfonts + xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/*.*] ${destroot}${prefix}/etc/${name}/assets/web + xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/fonts/fontawesome-free-web/*.*] ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web + xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/fonts/fontawesome-free-web/css/*.*] ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web/css + xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/*.*] ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web/webfonts + xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets/web/images + xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets/web/third_party + xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/images/*.*] ${destroot}${prefix}/etc/${name}/assets/web/images + xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/third_party/*.*] ${destroot}${prefix}/etc/${name}/assets/web/third_party + + xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/config + + # install sunshine.conf + xinstall {*}[glob ${worksrcpath}/src_assets/common/config/*.*] ${destroot}${prefix}/etc/${name}/config - xinstall -d -m 755 ${destroot}${prefix}/usr/local/${name}/config - xinstall {*}[glob ${worksrcpath}/src_assets/common/config/*] ${destroot}${prefix}/usr/local/${name}/config - xinstall {*}[glob ${worksrcpath}/src_assets/macos/config/*] ${destroot}${prefix}/usr/local/${name}/config + # install apps.json + xinstall {*}[glob ${worksrcpath}/src_assets/macos/config/*.*] ${destroot}${prefix}/etc/${name}/config + # install the binary xinstall ${workpath}/build/${name} ${destroot}${prefix}/bin } diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index deb6f60a..ab4b6888 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -108,13 +108,35 @@ Disk Image File option: Portfile option: #. Install `MacPorts `_ - #. Download the ``Portfile`` to ``/tmp`` and run the following code. + #. Update the Macports sources. .. code-block:: bash - cd /tmp && sudo port install + sudo nano /opt/local/etc/macports/sources.conf + + Add this line, replacing your username, below the line that starts with ``rsync``. + + file://Users//ports + + ``Ctrl+x``, then ``Y`` to exit and save changes. + + #. Download the ``Portfile`` to ``~/Downloads`` and run the following code. + + .. code-block:: bash + + mkdir -p ~/ports/multimedia/sunshine + mv ~/Downlaods/Portfile ~/ports/multimedia/sunshine + cd ~/ports + portindex + sudo port install sunshine #. The first time you start Sunshine, you will be asked to grant access to screen recording and your microphone. + #. Try to run the following code if you get this error: `Dynamic session lookup supported but failed: launchd did + not provide a socket path, verify that org.freedesktop.dbus-session.plist is loaded!` + + .. code-block:: bash + + launchctl load -w /Library/LaunchAgents/org.freedesktop.dbus-session.plist Standalone option: #. Download and extract ``sunshine-macos.zip`` From 9990b9b04b1146680efd6fe8e8871a98ba0248d1 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 13 Jun 2022 20:50:22 -0400 Subject: [PATCH 282/817] Separate job for Macports build --- .github/workflows/CI.yml | 123 ++++++++++++++++++++++++++------------- 1 file changed, 83 insertions(+), 40 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index acf8b120..d777444b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -230,7 +230,6 @@ jobs: zip --recurse-paths --move --test ./sunshine-appimage.zip ./* - name: Upload Artifacts - if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v3 with: name: sunshine-linux-${{ matrix.type }} @@ -247,7 +246,7 @@ jobs: build_mac: name: MacOS - runs-on: macos-12 + runs-on: macos-11 needs: check_changelog steps: @@ -256,20 +255,20 @@ jobs: with: submodules: recursive + # this is for the macports job + - name: Cache Artifacts + uses: actions/cache@v3 + with: + path: artifacts + key: ${{ runner.os }}-artifacts + - name: Setup Dependencies MacOS run: | # install dependencies using homebrew brew install boost cmake ffmpeg opus # fix openssl header not found - cd /usr/local/include - ln -s ../opt/openssl/include/openssl . - - # update paths for macports - # echo "/opt/local/sbin" >> $GITHUB_PATH - echo "/Users/runner/macports/sbin" >> $GITHUB_PATH - # echo "/opt/local/bin" >> $GITHUB_PATH - echo "/Users/runner/macports/bin" >> $GITHUB_PATH + ln -sf /usr/local/opt/openssl/include/openssl /usr/local/include/openssl - name: Build MacOS run: | @@ -297,6 +296,10 @@ jobs: - name: Package MacOS run: | + # remove cached artifacts + rm -r -f ./artifacts + mkdir artifacts + mkdir -p artifacts cd build @@ -313,47 +316,98 @@ jobs: # move mv Portfile ../artifacts/Portfile + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: sunshine-macos + path: artifacts/ + + # this step can be removed after packages are fixed + - name: Delete experimental packages + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + working-directory: artifacts + run: | + rm -f ./sunshine-macos-experimental-dragndrop.dmg + rm -f ./sunshine-macos-experimental-bundle.dmg + rm -f ./sunshine-macos-experimental-archive.zip + + - name: Create Release + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + uses: SunshineStream/actions/create_release@master + with: + token: ${{ secrets.GITHUB_TOKEN }} + next_version: ${{ needs.check_changelog.outputs.next_version }} + last_version: ${{ needs.check_changelog.outputs.last_version }} + release_body: ${{ needs.check_changelog.outputs.release_body }} + + build_mac_port: + name: Macports + runs-on: macos-11 + needs: [check_changelog, build_mac] + if: never() # disable this job + + steps: + - name: Cache Artifacts + uses: actions/cache@v3 + with: + path: artifacts + key: ${{ runner.os }}-artifacts + - name: Setup Macports run : | + # update paths for macports + echo "/opt/local/sbin" >> $GITHUB_PATH + echo "/opt/local/bin" >> $GITHUB_PATH + + # Set OpenSSL 1.1 as default + # rm -rf /usr/local/opt/openssl + # rm -rf /usr/local/bin/openssl + # ln -sf /usr/local/Cellar/openssl@1.1/1.1.1o/bin/openssl /usr/local/bin/openssl + # ln -sf /usr/local/Cellar/openssl@1.1/1.1.1o /usr/local/opt/openssl + + # download and extract macports curl -O https://distfiles.macports.org/MacPorts/MacPorts-2.7.2.tar.bz2 tar xf MacPorts-2.7.2.tar.bz2 + # build macports cd MacPorts-2.7.2 - ./configure --prefix=/Users/runner/macports + ./configure make sudo make install cd ../ rm -rf MacPorts-2.7.2* - + + - name: Configure Macports + run: | # update sources sudo port -v selfupdate # use custom sources - # sudo chmod 777 /opt/local/etc/macports/sources.conf - sudo chmod 777 /Users/runner/macports/etc/macports/sources.conf - # echo file://$(echo ~)/ports > /opt/local/etc/macports/sources.conf - echo file://$(echo ~)/ports > /Users/runner/macports/etc/macports/sources.conf - # echo rsync://rsync.macports.org/macports/release/tarballs/ports.tar [default] >> /opt/local/etc/macports/sources.conf - echo rsync://rsync.macports.org/macports/release/tarballs/ports.tar [default] >> /Users/runner/macports/etc/macports/sources.conf - # sudo chmod 644 /opt/local/etc/macports/sources.conf - sudo chmod 644 /Users/runner/macports/etc/macports/sources.conf - - - name: Package with MacPorts - run: | + sudo chmod 777 /opt/local/etc/macports/sources.conf + echo file://$(echo ~)/ports > /opt/local/etc/macports/sources.conf + echo rsync://rsync.macports.org/macports/release/tarballs/ports.tar [default] >> /opt/local/etc/macports/sources.conf + sudo chmod 644 /opt/local/etc/macports/sources.conf + # setup custom port mkdir -p ~/ports/multimedia/sunshine # copy configured Portfile - cp ./artifacts/Portfile ~/ports/multimedia/sunshine/ + mv ./artifacts/Portfile ~/ports/multimedia/sunshine/ + + # remove remaining cached artifacts + rm -r -f ./artifacts + mkdir artifacts # index the ports cd ~/ports portindex - + + - name: Build + run: | # build port - sudo port install sunshine - # || cat /opt/local/var/macports/logs/_Users_runner_ports_multimedia_sunshine/Sunshine/main.log \ - # && exit 1 + sudo port install sunshine \ + || cat /opt/local/var/macports/logs/_Users_runner_ports_multimedia_sunshine/Sunshine/main.log \ + && exit 1 # create packages sudo port dmg sunshine @@ -371,21 +425,11 @@ jobs: # port search sunshine - name: Upload Artifacts - if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v3 with: - name: sunshine-macos + name: sunshine-macports path: artifacts/ - # this step can be removed after packages are fixed - - name: Delete experimental packages - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - working-directory: artifacts - run: | - rm -f ./sunshine-macos-experimental-dragndrop.dmg - rm -f ./sunshine-macos-experimental-bundle.dmg - rm -f ./sunshine-macos-experimental-archive.zip - - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} uses: SunshineStream/actions/create_release@master @@ -449,7 +493,6 @@ jobs: mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-windows.zip - name: Upload Artifacts - if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} uses: actions/upload-artifact@v3 with: name: sunshine-windows From ca21e6a8ac80c91767da5d6e52d4318f0ec35624 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 15 Jun 2022 15:52:38 -0400 Subject: [PATCH 283/817] Update documentation --- docs/source/about/installation.rst | 37 +---------------- docs/source/about/usage.rst | 41 ++++++++++++------- docs/source/toc.rst | 9 ++++ docs/source/troubleshooting/general.rst | 13 ++++++ docs/source/troubleshooting/linux.rst | 9 ++++ docs/source/troubleshooting/macos.rst | 14 +++++++ docs/source/troubleshooting/windows.rst | 7 ++++ src_assets/linux/misc/85-sunshine-rules.rules | 2 +- 8 files changed, 80 insertions(+), 52 deletions(-) create mode 100644 docs/source/troubleshooting/general.rst create mode 100644 docs/source/troubleshooting/linux.rst create mode 100644 docs/source/troubleshooting/macos.rst create mode 100644 docs/source/troubleshooting/windows.rst diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index ab4b6888..2176206a 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -23,25 +23,6 @@ Linux ----- Follow the instructions for your preferred package type below. -.. Hint:: If this is the first time installing. - - .. code-block:: bash - - sudo usermod -a -G input $USER - sudo reboot now - -.. Tip:: Optionally, run Sunshine in the background. - - .. code-block:: bash - - systemctl --user start sunshine - -.. Note:: If screencasting fails with Wayland, you may need to run the following to force screencasting with X11. - - .. code-block:: bash - - sudo setcap -r $(readlink -f $(which sunshine)) - AppImage ^^^^^^^^ .. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:appimage?logo=github&style=for-the-badge @@ -101,12 +82,7 @@ MacOS .. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:macos?logo=github&style=for-the-badge :alt: GitHub issues by-label -Disk Image File option: - #. Download and install ``sunshine.dmg`` - - .. Warning:: The Disk Image File is experimental. Limited support will be provided. - -Portfile option: +Portfile #. Install `MacPorts `_ #. Update the Macports sources. @@ -131,17 +107,6 @@ Portfile option: sudo port install sunshine #. The first time you start Sunshine, you will be asked to grant access to screen recording and your microphone. - #. Try to run the following code if you get this error: `Dynamic session lookup supported but failed: launchd did - not provide a socket path, verify that org.freedesktop.dbus-session.plist is loaded!` - - .. code-block:: bash - - launchctl load -w /Library/LaunchAgents/org.freedesktop.dbus-session.plist - -Standalone option: - #. Download and extract ``sunshine-macos.zip`` - - .. Warning:: The Standalone package is experimental. Limited support will be provided. Windows ------- diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst index 8f73d204..79d285f9 100644 --- a/docs/source/about/usage.rst +++ b/docs/source/about/usage.rst @@ -56,33 +56,26 @@ Setup Linux ^^^^^ -The deb and rpm packages handle these steps automatically. The AppImage does not, and third party packages may not as -well. +The deb and rpm packages handle these steps automatically. The AppImage does not, third party packages may not as well. Sunshine needs access to `uinput` to create mouse and gamepad events. -Add user to group `input`. +Add user to group `input`, if this is the first time installing. .. code-block:: bash - usermod -a -G input $USER - - .. Warning:: If the above doesn't work you can try the following. This is not an advised method as it makes the - current user the owner of ``/dev/uinput``. Use at your own risk. - - .. code-block:: bash - - sudo chown $USER /dev/uinput + sudo usermod -a -G input $USER + sudo reboot now Create `udev` rules. .. code-block:: bash - nano /etc/udev/rules.d/85-sunshine-input.rules + sudo nano /etc/udev/rules.d/85-sunshine-input.rules Input the following contents. .. code-block:: - KERNEL=="uinput", GROUP="input", MODE="0660", OPTION+="static_node=uinput" + KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput" Save the file and exit: @@ -99,11 +92,22 @@ Configure autostart service Description=Sunshine Gamestream Server for Moonlight [Service] - ExecStart=~/sunshine.AppImage + ExecStart= [Install] WantedBy=graphical-session.target + .. table:: + :widths: auto + + ======== =================== =============== + package ExecStart Auto Configured + ======== =================== =============== + deb /usr/bin/sunshine ✔ + rpm /usr/bin/sunshine ✔ + AppImage ~/sunshine.AppImage ✖ + ======== =================== =============== + Start once .. code-block:: bash @@ -137,7 +141,14 @@ select their sink as audio device in `sunshine.conf`. .. Note:: Command Keys are not forwarded by Moonlight. Right Option-Key is mapped to CMD-Key. -.. Caution:: Gamepads are not supported. +.. Caution:: Gamepads are not currently supported. + +Configure autostart service + + MacPorts + .. code-block:: bash + + sudo port load Sunshine Windows ^^^^^^^ diff --git a/docs/source/toc.rst b/docs/source/toc.rst index 9c0989ee..efafa92b 100644 --- a/docs/source/toc.rst +++ b/docs/source/toc.rst @@ -9,6 +9,15 @@ about/usage about/advanced_usage +.. toctree:: + :maxdepth: 2 + :caption: Troubleshooting + + troubleshooting/general + troubleshooting/linux + troubleshooting/macos + troubleshooting/windows + .. toctree:: :maxdepth: 2 :caption: Build diff --git a/docs/source/troubleshooting/general.rst b/docs/source/troubleshooting/general.rst new file mode 100644 index 00000000..322327cc --- /dev/null +++ b/docs/source/troubleshooting/general.rst @@ -0,0 +1,13 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/troubleshooting/general.rst + +General +======= +If you forgot your credentials to the web UI, try this. + + .. code-block:: bash + + sunshine -creds + +Can't access the web UI? + + #. Check firefall rules. diff --git a/docs/source/troubleshooting/linux.rst b/docs/source/troubleshooting/linux.rst new file mode 100644 index 00000000..71e9eac0 --- /dev/null +++ b/docs/source/troubleshooting/linux.rst @@ -0,0 +1,9 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/troubleshooting/linux.rst + +Linux +===== +If screencasting fails with Wayland, you may need to run the following to force screencasting with X11. + + .. code-block:: bash + + sudo setcap -r $(readlink -f $(which sunshine)) diff --git a/docs/source/troubleshooting/macos.rst b/docs/source/troubleshooting/macos.rst new file mode 100644 index 00000000..18ea9361 --- /dev/null +++ b/docs/source/troubleshooting/macos.rst @@ -0,0 +1,14 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/troubleshooting/macos.rst + +MacOS +===== +If you get this error: + + ``Dynamic session lookup supported but failed: launchd did not provide a socket path, verify that + org.freedesktop.dbus-session.plist is loaded!`` + + Try this. + + .. code-block:: bash + + launchctl load -w /Library/LaunchAgents/org.freedesktop.dbus-session.plist diff --git a/docs/source/troubleshooting/windows.rst b/docs/source/troubleshooting/windows.rst new file mode 100644 index 00000000..461bce0a --- /dev/null +++ b/docs/source/troubleshooting/windows.rst @@ -0,0 +1,7 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/troubleshooting/windows.rst + +Windows +======= +No gamepad is detected. + + #. Verify that you've installed `ViGEmBus `_. diff --git a/src_assets/linux/misc/85-sunshine-rules.rules b/src_assets/linux/misc/85-sunshine-rules.rules index 77c337c4..a1ee7cd0 100644 --- a/src_assets/linux/misc/85-sunshine-rules.rules +++ b/src_assets/linux/misc/85-sunshine-rules.rules @@ -1 +1 @@ -KERNEL=="uinput", GROUP="input", MODE="0660", OPTION+="static_node=uinput" \ No newline at end of file +KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput" \ No newline at end of file From e86207606af6eee2a02e95e017ee2cf864d65662 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 15 Jun 2022 15:53:04 -0400 Subject: [PATCH 284/817] Use openssl for deb package instead of libssl1.1/3 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2963417e..5c9a135f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -583,7 +583,7 @@ if(UNIX AND NOT APPLE) # Dependencies set(CPACK_DEB_COMPONENT_INSTALL ON) - set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl1.1 | libssl3, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "openssl, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.1, libavdevice >= 4.3, boost-thread >= 1.67.0, boost-filesystem >= 1.67.0, boost-log >= 1.67.0, pulseaudio-libs >= 10.0, libopusenc >= 0.2.1, libxcb >= 1.13, libXtst >= 1.2.3, libevdev >= 1.5.6, libdrm >= 2.4.97, libcap >= 2.22") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # This should automatically figure out dependencies, doesn't work with the current config From dd73f45175c4028c24ea91f251eba7afcb6194df Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 15 Jun 2022 16:05:23 -0400 Subject: [PATCH 285/817] Deactivate mac port job --- .github/workflows/CI.yml | 195 +++++++++++++++++++-------------------- 1 file changed, 97 insertions(+), 98 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index d777444b..18d7e89c 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -340,104 +340,103 @@ jobs: last_version: ${{ needs.check_changelog.outputs.last_version }} release_body: ${{ needs.check_changelog.outputs.release_body }} - build_mac_port: - name: Macports - runs-on: macos-11 - needs: [check_changelog, build_mac] - if: never() # disable this job - - steps: - - name: Cache Artifacts - uses: actions/cache@v3 - with: - path: artifacts - key: ${{ runner.os }}-artifacts - - - name: Setup Macports - run : | - # update paths for macports - echo "/opt/local/sbin" >> $GITHUB_PATH - echo "/opt/local/bin" >> $GITHUB_PATH - - # Set OpenSSL 1.1 as default - # rm -rf /usr/local/opt/openssl - # rm -rf /usr/local/bin/openssl - # ln -sf /usr/local/Cellar/openssl@1.1/1.1.1o/bin/openssl /usr/local/bin/openssl - # ln -sf /usr/local/Cellar/openssl@1.1/1.1.1o /usr/local/opt/openssl - - # download and extract macports - curl -O https://distfiles.macports.org/MacPorts/MacPorts-2.7.2.tar.bz2 - tar xf MacPorts-2.7.2.tar.bz2 - - # build macports - cd MacPorts-2.7.2 - ./configure - make - sudo make install - cd ../ - rm -rf MacPorts-2.7.2* - - - name: Configure Macports - run: | - # update sources - sudo port -v selfupdate - - # use custom sources - sudo chmod 777 /opt/local/etc/macports/sources.conf - echo file://$(echo ~)/ports > /opt/local/etc/macports/sources.conf - echo rsync://rsync.macports.org/macports/release/tarballs/ports.tar [default] >> /opt/local/etc/macports/sources.conf - sudo chmod 644 /opt/local/etc/macports/sources.conf - - # setup custom port - mkdir -p ~/ports/multimedia/sunshine - - # copy configured Portfile - mv ./artifacts/Portfile ~/ports/multimedia/sunshine/ - - # remove remaining cached artifacts - rm -r -f ./artifacts - mkdir artifacts - - # index the ports - cd ~/ports - portindex - - - name: Build - run: | - # build port - sudo port install sunshine \ - || cat /opt/local/var/macports/logs/_Users_runner_ports_multimedia_sunshine/Sunshine/main.log \ - && exit 1 - - # create packages - sudo port dmg sunshine - sudo port pkg sunshine - - # move - mv $(port work sunshine)/Sunshine*.dmg ./artifacts/sunshine.dmg - mv $(port work sunshine)/Sunshine*.ppkg ./artifacts/sunshine.pkg - - # testing only - # ls ~/ports/multimedia/sunshine - # cat ~/ports/multimedia/sunshine/Portfile - # cat /opt/local/etc/macports/sources.conf - # cat ~/ports/Portindex - # port search sunshine - - - name: Upload Artifacts - uses: actions/upload-artifact@v3 - with: - name: sunshine-macports - path: artifacts/ - - - name: Create Release - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: SunshineStream/actions/create_release@master - with: - token: ${{ secrets.GITHUB_TOKEN }} - next_version: ${{ needs.check_changelog.outputs.next_version }} - last_version: ${{ needs.check_changelog.outputs.last_version }} - release_body: ${{ needs.check_changelog.outputs.release_body }} +# build_mac_port: +# name: Macports +# runs-on: macos-11 +# needs: [check_changelog, build_mac] +# +# steps: +# - name: Cache Artifacts +# uses: actions/cache@v3 +# with: +# path: artifacts +# key: ${{ runner.os }}-artifacts +# +# - name: Setup Macports +# run : | +# # update paths for macports +# echo "/opt/local/sbin" >> $GITHUB_PATH +# echo "/opt/local/bin" >> $GITHUB_PATH +# +# # Set OpenSSL 1.1 as default +# # rm -rf /usr/local/opt/openssl +# # rm -rf /usr/local/bin/openssl +# # ln -sf /usr/local/Cellar/openssl@1.1/1.1.1o/bin/openssl /usr/local/bin/openssl +# # ln -sf /usr/local/Cellar/openssl@1.1/1.1.1o /usr/local/opt/openssl +# +# # download and extract macports +# curl -O https://distfiles.macports.org/MacPorts/MacPorts-2.7.2.tar.bz2 +# tar xf MacPorts-2.7.2.tar.bz2 +# +# # build macports +# cd MacPorts-2.7.2 +# ./configure +# make +# sudo make install +# cd ../ +# rm -rf MacPorts-2.7.2* +# +# - name: Configure Macports +# run: | +# # update sources +# sudo port -v selfupdate +# +# # use custom sources +# sudo chmod 777 /opt/local/etc/macports/sources.conf +# echo file://$(echo ~)/ports > /opt/local/etc/macports/sources.conf +# echo rsync://rsync.macports.org/macports/release/tarballs/ports.tar [default] >> /opt/local/etc/macports/sources.conf +# sudo chmod 644 /opt/local/etc/macports/sources.conf +# +# # setup custom port +# mkdir -p ~/ports/multimedia/sunshine +# +# # copy configured Portfile +# mv ./artifacts/Portfile ~/ports/multimedia/sunshine/ +# +# # remove remaining cached artifacts +# rm -r -f ./artifacts +# mkdir artifacts +# +# # index the ports +# cd ~/ports +# portindex +# +# - name: Build +# run: | +# # build port +# sudo port install sunshine \ +# || cat /opt/local/var/macports/logs/_Users_runner_ports_multimedia_sunshine/Sunshine/main.log \ +# && exit 1 +# +# # create packages +# sudo port dmg sunshine +# sudo port pkg sunshine +# +# # move +# mv $(port work sunshine)/Sunshine*.dmg ./artifacts/sunshine.dmg +# mv $(port work sunshine)/Sunshine*.ppkg ./artifacts/sunshine.pkg +# +# # testing only +# # ls ~/ports/multimedia/sunshine +# # cat ~/ports/multimedia/sunshine/Portfile +# # cat /opt/local/etc/macports/sources.conf +# # cat ~/ports/Portindex +# # port search sunshine +# +# - name: Upload Artifacts +# uses: actions/upload-artifact@v3 +# with: +# name: sunshine-macports +# path: artifacts/ +# +# - name: Create Release +# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} +# uses: SunshineStream/actions/create_release@master +# with: +# token: ${{ secrets.GITHUB_TOKEN }} +# next_version: ${{ needs.check_changelog.outputs.next_version }} +# last_version: ${{ needs.check_changelog.outputs.last_version }} +# release_body: ${{ needs.check_changelog.outputs.release_body }} build_win: name: Windows From 0c827690ec8c12abaf54b522e42401c1997b138c Mon Sep 17 00:00:00 2001 From: Conn O'Griofa Date: Wed, 18 May 2022 17:52:43 +0100 Subject: [PATCH 286/817] config: move VideoToolbox variables into correct video_t struct location --- sunshine/config.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 6dd3c1b2..e06a2801 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -220,6 +220,12 @@ video_t video { std::nullopt, -1 }, // amd + { + 0, + 0, + 1, + -1 }, // vt + {}, // encoder {}, // adapter_name {}, // output_name @@ -722,11 +728,8 @@ void apply_config(std::unordered_map &&vars) { } int_f(vars, "vt_coder", video.vt.coder, vt::coder_from_view); - video.vt.allow_sw = 0; int_f(vars, "vt_software", video.vt.allow_sw, vt::allow_software_from_view); - video.vt.require_sw = 0; int_f(vars, "vt_software", video.vt.require_sw, vt::force_software_from_view); - video.vt.realtime = 1; int_f(vars, "vt_realtime", video.vt.realtime, vt::rt_from_view); string_f(vars, "encoder", video.encoder); From ab0a6b5fa645c51df4822ce7e0b12b88258a8caa Mon Sep 17 00:00:00 2001 From: Conn O'Griofa Date: Thu, 19 May 2022 20:11:44 +0100 Subject: [PATCH 287/817] webui: remove erroneous v-bind properties These were causing unusual behaviour (select dialogs displaying a blank label when a value should be selected, and values randomly setting themselves to undefined when switching tabs). --- src_assets/common/assets/web/config.html | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index 203e807f..425b953e 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -37,13 +37,13 @@

      Configuration

      class="form-select" v-model="config.min_log_level" > - - - - - - - + + + + + + +
      The minimum log level printed to standard out @@ -497,7 +497,7 @@

      Configuration

      - + + + + +
      + Improves capture latency during mouse movement.
      + Enabling this may prevent the client's FPS from exceeding the host monitor's active refresh rate.
      @@ -832,6 +843,7 @@

      Configuration

      this.config.key_rightalt_to_key_win || "disabled"; this.config.gamepad = this.config.gamepad || "x360"; this.config.upnp = this.config.upnp || "disabled"; + this.config.dwmflush = this.config.dwmflush || "disabled"; this.config.min_log_level = this.config.min_log_level || 2; this.config.origin_pin_allowed = this.config.origin_pin_allowed || "pc"; diff --git a/sunshine/config.cpp b/sunshine/config.cpp index e06a2801..ac1d9ccf 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -226,9 +226,10 @@ video_t video { 1, -1 }, // vt - {}, // encoder - {}, // adapter_name - {}, // output_name + {}, // encoder + {}, // adapter_name + {}, // output_name + false // dwmflush }; audio_t audio {}; @@ -735,6 +736,7 @@ void apply_config(std::unordered_map &&vars) { string_f(vars, "encoder", video.encoder); string_f(vars, "adapter_name", video.adapter_name); string_f(vars, "output_name", video.output_name); + bool_f(vars, "dwmflush", video.dwmflush); path_f(vars, "pkey", nvhttp.pkey); path_f(vars, "cert", nvhttp.cert); diff --git a/sunshine/config.h b/sunshine/config.h index 18313961..4ecb9f32 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -44,6 +44,7 @@ struct video_t { std::string encoder; std::string adapter_name; std::string output_name; + bool dwmflush; }; struct audio_t { diff --git a/sunshine/platform/windows/display.h b/sunshine/platform/windows/display.h index 37020fef..60539efc 100644 --- a/sunshine/platform/windows/display.h +++ b/sunshine/platform/windows/display.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -172,4 +173,4 @@ class display_vram_t : public display_base_t, public std::enable_shared_from_thi }; } // namespace platf::dxgi -#endif \ No newline at end of file +#endif diff --git a/sunshine/platform/windows/display_base.cpp b/sunshine/platform/windows/display_base.cpp index 02b08c37..12f8cad0 100644 --- a/sunshine/platform/windows/display_base.cpp +++ b/sunshine/platform/windows/display_base.cpp @@ -20,6 +20,10 @@ capture_e duplication_t::next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::ch return capture_status; } + if(config::video.dwmflush) { + DwmFlush(); + } + auto status = dup->AcquireNextFrame(timeout.count(), &frame_info, res_p); switch(status) { From 2d969c2cccc61a3fdc718b7aaa0b13410cd850f0 Mon Sep 17 00:00:00 2001 From: Conn O'Griofa Date: Fri, 20 May 2022 16:25:09 +0100 Subject: [PATCH 290/817] platform/windows: change dwmflush default, add autodetection On each re/init, query the active monitor refresh rate via DwmGetCompositionTimingInfo. If the client requested framerate exceeds the host monitor refresh, automatically disable DwmFlush. This avoids the problem by which DwmFlush would constrain the client FPS if the host monitor runs at a lower refresh rate, thus allowing the feature to be enabled by default. If there are other issues caused by DwmFlush for certain systems, it can still be disabled via configuration. --- docs/source/about/advanced_usage.rst | 7 ++++--- src_assets/common/assets/web/config.html | 6 +++--- sunshine/config.cpp | 8 ++++---- sunshine/platform/windows/display.h | 1 + sunshine/platform/windows/display_base.cpp | 18 +++++++++++++++++- 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index e071ada5..e0903223 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -339,11 +339,12 @@ dwmflush Description Invoke DwmFlush() to sync screen capture to the Windows presentation interval. - .. Caution:: Applies to Windows only. Alleviates visual stuttering during mouse movement, but causes the capture - rate to be limited to the host monitor's currently active refresh rate. + .. Caution:: Applies to Windows only. Alleviates visual stuttering during mouse movement. + If enabled, this feature will automatically deactivate if the client framerate exceeds + the host monitor's current refresh rate. Default - ``disabled`` + ``enabled`` Examples diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index 1d34512c..01954e76 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -410,8 +410,8 @@

      Configuration

      - Improves capture latency during mouse movement.
      - Enabling this may prevent the client's FPS from exceeding the host monitor's active refresh rate. + Improves capture latency/smoothness during mouse movement.
      + Disable if you encounter any VSync-related issues.
      @@ -843,7 +843,7 @@

      Configuration

      this.config.key_rightalt_to_key_win || "disabled"; this.config.gamepad = this.config.gamepad || "x360"; this.config.upnp = this.config.upnp || "disabled"; - this.config.dwmflush = this.config.dwmflush || "disabled"; + this.config.dwmflush = this.config.dwmflush || "enabled"; this.config.min_log_level = this.config.min_log_level || 2; this.config.origin_pin_allowed = this.config.origin_pin_allowed || "pc"; diff --git a/sunshine/config.cpp b/sunshine/config.cpp index ac1d9ccf..21c27e2d 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -226,10 +226,10 @@ video_t video { 1, -1 }, // vt - {}, // encoder - {}, // adapter_name - {}, // output_name - false // dwmflush + {}, // encoder + {}, // adapter_name + {}, // output_name + true // dwmflush }; audio_t audio {}; diff --git a/sunshine/platform/windows/display.h b/sunshine/platform/windows/display.h index 60539efc..6a150135 100644 --- a/sunshine/platform/windows/display.h +++ b/sunshine/platform/windows/display.h @@ -96,6 +96,7 @@ class duplication_t { public: dup_t dup; bool has_frame {}; + bool use_dwmflush {}; capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p); capture_e reset(dup_t::pointer dup_p = dup_t::pointer()); diff --git a/sunshine/platform/windows/display_base.cpp b/sunshine/platform/windows/display_base.cpp index 12f8cad0..9ee9ad97 100644 --- a/sunshine/platform/windows/display_base.cpp +++ b/sunshine/platform/windows/display_base.cpp @@ -2,6 +2,7 @@ // Created by loki on 1/12/20. // +#include #include #include "display.h" @@ -20,7 +21,7 @@ capture_e duplication_t::next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::ch return capture_status; } - if(config::video.dwmflush) { + if(use_dwmflush) { DwmFlush(); } @@ -217,6 +218,21 @@ int display_base_t::init(int framerate, const std::string &display_name) { << "Offset : "sv << offset_x << 'x' << offset_y << std::endl << "Virtual Desktop : "sv << env_width << 'x' << env_height; + // Enable DwmFlush() only if the current refresh rate can match the client framerate. + auto refresh_rate = framerate; + DWM_TIMING_INFO timing_info; + timing_info.cbSize = sizeof(timing_info); + + status = DwmGetCompositionTimingInfo(NULL, &timing_info); + if(FAILED(status)) { + BOOST_LOG(warning) << "Failed to detect active refresh rate."; + } + else { + refresh_rate = std::round((double)timing_info.rateRefresh.uiNumerator / (double)timing_info.rateRefresh.uiDenominator); + } + + dup.use_dwmflush = config::video.dwmflush && !(framerate > refresh_rate) ? true : false; + // Bump up thread priority { const DWORD flags = TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY; From a02d314dde90a6163d926f3fc11af73988f286a5 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 15 Jun 2022 17:50:11 -0400 Subject: [PATCH 291/817] Update CHANGELOG.md for v0.14.0 --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a18ecbd..1844e355 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## [0.14.0] - 2022-06-15 +### 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 +- (AMD) Fixed hwdevice being destroyed before context +- (Linux) Added missing dependencies to AppImage +- (Linux) Fixed rumble events causing game to freeze +- (Linux) Improved Pulse/Pipewire compatibility +- (Linux) Moved to single deb package +- (MacOS) Fixed missing TPCircularBuffer submodule +- (Stream) Properly catch exceptions in stream broadcast handlers +- (Stream/Video) AVPacket fix + ## [0.13.0] - 2022-02-27 ### Added - (MacOS) Initial support for MacOS (#40) From 651d75fce760b41481fdb3ae358aad063306e46b Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 15 Jun 2022 18:17:12 -0400 Subject: [PATCH 292/817] Update version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8827b453..44f24e7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.0) -project(Sunshine VERSION 0.13.0 +project(Sunshine VERSION 0.14.0 DESCRIPTION "Sunshine is a Gamestream host for Moonlight." HOMEPAGE_URL "https://sunshinestream.github.io" ) From e78ec5c2ce9b70ba3ed16b509e0fa26ce6bb483c Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 16 Jun 2022 16:18:59 -0400 Subject: [PATCH 293/817] Add notes --- Portfile.in | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Portfile.in b/Portfile.in index 36a7d476..371b5cdf 100644 --- a/Portfile.in +++ b/Portfile.in @@ -43,6 +43,13 @@ startupitem.location LaunchDaemons startupitem.name ${name} startupitem.netchange yes +platform darwin { + if { ${os.major} < 20 } { + # See: https://github.com/SunshineStream/Sunshine/discussions/117#discussioncomment-2513494 + notes-append "Port is limited to software encoding, when used with macOS releases prior to Big Sur." + } +} + # is this actually necessary? this should all be handled by CMakeLists destroot { # install assets From 3663e35ecfb8df4598c04298f2e590116a17ca91 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 16 Jun 2022 16:55:16 -0400 Subject: [PATCH 294/817] Lint and built Portfile --- .github/workflows/CI.yml | 276 +++++++++++++++++++++++++-------------- 1 file changed, 179 insertions(+), 97 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 18d7e89c..50f00c69 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -340,103 +340,185 @@ jobs: last_version: ${{ needs.check_changelog.outputs.last_version }} release_body: ${{ needs.check_changelog.outputs.release_body }} -# build_mac_port: -# name: Macports -# runs-on: macos-11 -# needs: [check_changelog, build_mac] -# -# steps: -# - name: Cache Artifacts -# uses: actions/cache@v3 -# with: -# path: artifacts -# key: ${{ runner.os }}-artifacts -# -# - name: Setup Macports -# run : | -# # update paths for macports -# echo "/opt/local/sbin" >> $GITHUB_PATH -# echo "/opt/local/bin" >> $GITHUB_PATH -# -# # Set OpenSSL 1.1 as default -# # rm -rf /usr/local/opt/openssl -# # rm -rf /usr/local/bin/openssl -# # ln -sf /usr/local/Cellar/openssl@1.1/1.1.1o/bin/openssl /usr/local/bin/openssl -# # ln -sf /usr/local/Cellar/openssl@1.1/1.1.1o /usr/local/opt/openssl -# -# # download and extract macports -# curl -O https://distfiles.macports.org/MacPorts/MacPorts-2.7.2.tar.bz2 -# tar xf MacPorts-2.7.2.tar.bz2 -# -# # build macports -# cd MacPorts-2.7.2 -# ./configure -# make -# sudo make install -# cd ../ -# rm -rf MacPorts-2.7.2* -# -# - name: Configure Macports -# run: | -# # update sources -# sudo port -v selfupdate -# -# # use custom sources -# sudo chmod 777 /opt/local/etc/macports/sources.conf -# echo file://$(echo ~)/ports > /opt/local/etc/macports/sources.conf -# echo rsync://rsync.macports.org/macports/release/tarballs/ports.tar [default] >> /opt/local/etc/macports/sources.conf -# sudo chmod 644 /opt/local/etc/macports/sources.conf -# -# # setup custom port -# mkdir -p ~/ports/multimedia/sunshine -# -# # copy configured Portfile -# mv ./artifacts/Portfile ~/ports/multimedia/sunshine/ -# -# # remove remaining cached artifacts -# rm -r -f ./artifacts -# mkdir artifacts -# -# # index the ports -# cd ~/ports -# portindex -# -# - name: Build -# run: | -# # build port -# sudo port install sunshine \ -# || cat /opt/local/var/macports/logs/_Users_runner_ports_multimedia_sunshine/Sunshine/main.log \ -# && exit 1 -# -# # create packages -# sudo port dmg sunshine -# sudo port pkg sunshine -# -# # move -# mv $(port work sunshine)/Sunshine*.dmg ./artifacts/sunshine.dmg -# mv $(port work sunshine)/Sunshine*.ppkg ./artifacts/sunshine.pkg -# -# # testing only -# # ls ~/ports/multimedia/sunshine -# # cat ~/ports/multimedia/sunshine/Portfile -# # cat /opt/local/etc/macports/sources.conf -# # cat ~/ports/Portindex -# # port search sunshine -# -# - name: Upload Artifacts -# uses: actions/upload-artifact@v3 -# with: -# name: sunshine-macports -# path: artifacts/ -# -# - name: Create Release -# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} -# uses: SunshineStream/actions/create_release@master -# with: -# token: ${{ secrets.GITHUB_TOKEN }} -# next_version: ${{ needs.check_changelog.outputs.next_version }} -# last_version: ${{ needs.check_changelog.outputs.last_version }} -# release_body: ${{ needs.check_changelog.outputs.release_body }} + build_mac_port: + name: Macports + needs: [check_changelog, build_mac] + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ macos-10.15, macos-11, macos-12 ] + + steps: + - name: Cache Artifacts + uses: actions/cache@v3 + with: + path: artifacts + key: ${{ runner.os }}-artifacts + + - name: Checkout ports + uses: actions/checkout@v3 + with: + repository: macports/macports-ports + fetch-depth: 64 + path: ports + + - name: Checkout mpbb + uses: actions/checkout@v3 + with: + repository: macports/mpbb + path: mpbb + + - name: Bootstrap MacPorts + run: | + . ports/.github/workflows/bootstrap.sh + + - name: Setup Macports + run : | + # Add getopt, mpbb and the MacPorts paths to $PATH for the subsequent steps. + echo "/opt/mports/bin" >> $GITHUB_PATH + echo "${PWD}/mpbb" >> $GITHUB_PATH + echo "/opt/local/bin" >> $GITHUB_PATH + echo "/opt/local/sbin" >> $GITHUB_PATH + + # copy Portfile from artifacts to ports + mkdir -p ./ports/multimedia/Sunshine + cp -f ./artifacts/Portfile ./ports/multimedia/Sunshine/Portfile + + - name: Determine list of subports + id: subportlist + run: | + set -eu + port=Sunshine + subportlist="" + + echo "Listing subports for Sunshine" + new_subports=$(mpbb \ + --work-dir /tmp/mpbb \ + list-subports \ + --archive-site= \ + --archive-site-private= \ + --include-deps=no \ + "$port" \ + | tr '\n' ' ') + for subport in $new_subports; do + echo "$subport" + subportlist="$subportlist $subport" + done + echo "::set-output name=subportlist::${subportlist}" + + - name: Run port lint for all subports + run: | + set -eu + fail=0 + for subport in $subportlist; do + echo "::group::${subport}" + path=$(port file "$subport") + messagetype="warning" + if ! messages=$(port -q lint "$subport" 2>&1); then + messagetype="error" + fail=1 + fi + if [ -n "$messages" ]; then + echo "$messages" + # See https://github.com/actions/toolkit/issues/193#issuecomment-605394935 + encoded_messages="port lint ${subport}:%0A" + encoded_messages+="$(echo "${messages}" | sed -E 's/$/%0A/g' | tr -d '\n')" + echo "::${messagetype} file=${path#${PWD}/ports/},line=1,col=1::${encoded_messages}" + fi + echo "::endgroup::" + done + exit "$fail" + env: + subportlist: ${{ steps.subportlist.outputs.subportlist }} + + - name: Build subports + run: | + set -eu + fail=0 + for subport in $subportlist; do + workdir="/tmp/mpbb/$subport" + mkdir -p "$workdir/logs" + touch "$workdir/logs/dependencies-progress.txt" + echo "::group::Cleaning up between ports" + sudo mpbb --work-dir "$workdir" cleanup + echo "::endgroup::" + echo "::group::Installing dependencies for ${subport}" + sudo mpbb \ + --work-dir "$workdir" \ + install-dependencies \ + "$subport" >"$workdir/logs/install-dependencies.log" 2>&1 & + deps_pid=$! + tail -f "$workdir/logs/dependencies-progress.txt" 2>/dev/null & + tail_pid=$! + set +e + wait "$deps_pid" + deps_exit=$? + set -e + kill "$tail_pid" || true + if [ "$deps_exit" -ne 0 ]; then + echo "::endgroup::" + echo "::error::Failed to install dependencies for ${subport}" + fail=1 + continue + fi + echo "::endgroup::" + echo "::group::Installing ${subport}" + set +e + sudo mpbb \ + --work-dir "$workdir" \ + install-port \ + --source \ + "$subport" + install_exit=$? + set -e + if [ "$install_exit" -ne 0 ]; then + echo "::endgroup::" + echo "::error::Failed to install ${subport}" + fail=1 + continue + fi + echo "::endgroup::" + done + exit "$fail" + env: + subportlist: ${{ steps.subportlist.outputs.subportlist }} + + - name: Package + run: | + # build port + # sudo port install sunshine \ + # || cat /opt/local/var/macports/logs/_Users_runner_ports_multimedia_sunshine/Sunshine/main.log \ + # && exit 1 + + # create packages + sudo port dmg sunshine + sudo port pkg sunshine + + # move + mv $(port work sunshine)/Sunshine*.dmg ./artifacts/sunshine.dmg + mv $(port work sunshine)/Sunshine*.ppkg ./artifacts/sunshine.pkg + + # testing only + # ls ~/ports/multimedia/sunshine + # cat ~/ports/multimedia/sunshine/Portfile + # cat /opt/local/etc/macports/sources.conf + # cat ~/ports/Portindex + # port search sunshine + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: sunshine-macports + path: artifacts/ + + - name: Create Release + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + uses: SunshineStream/actions/create_release@master + with: + token: ${{ secrets.GITHUB_TOKEN }} + next_version: ${{ needs.check_changelog.outputs.next_version }} + last_version: ${{ needs.check_changelog.outputs.last_version }} + release_body: ${{ needs.check_changelog.outputs.release_body }} build_win: name: Windows From 7da652e299024235dac1044e14402d2d760ab8ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jun 2022 13:25:50 +0000 Subject: [PATCH 295/817] Bump sphinx from 4.5.0 to 5.0.2 in /scripts Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.5.0 to 5.0.2. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/5.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.5.0...v5.0.2) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- scripts/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/requirements.txt b/scripts/requirements.txt index 73eefbe1..ade56a2c 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -1,5 +1,5 @@ Babel==2.9.1 m2r2==0.3.2 -Sphinx==4.5.0 +Sphinx==5.0.2 sphinx-copybutton==0.5.0 sphinx-rtd-theme==1.0.0 From 1175a3184e13bc110e3ecf50ce89308df5b50697 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jun 2022 14:03:34 +0000 Subject: [PATCH 296/817] Bump babel from 2.9.1 to 2.10.3 in /scripts Bumps [babel](https://github.com/python-babel/babel) from 2.9.1 to 2.10.3. - [Release notes](https://github.com/python-babel/babel/releases) - [Changelog](https://github.com/python-babel/babel/blob/master/CHANGES.rst) - [Commits](https://github.com/python-babel/babel/compare/v2.9.1...v2.10.3) --- updated-dependencies: - dependency-name: babel dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- scripts/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/requirements.txt b/scripts/requirements.txt index ade56a2c..2ed4a470 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -1,4 +1,4 @@ -Babel==2.9.1 +Babel==2.10.3 m2r2==0.3.2 Sphinx==5.0.2 sphinx-copybutton==0.5.0 From 4a0d632c6ed51491d748d3d078e75d77da506468 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 16 Jun 2022 16:55:16 -0400 Subject: [PATCH 297/817] Lint and built Portfile --- .github/workflows/CI.yml | 281 +++++++++++++++++++++++++-------------- 1 file changed, 183 insertions(+), 98 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 18d7e89c..71a86ab8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -297,7 +297,7 @@ jobs: - name: Package MacOS run: | # remove cached artifacts - rm -r -f ./artifacts + rm -r -f ./artifacts/ mkdir artifacts mkdir -p artifacts @@ -340,103 +340,188 @@ jobs: last_version: ${{ needs.check_changelog.outputs.last_version }} release_body: ${{ needs.check_changelog.outputs.release_body }} -# build_mac_port: -# name: Macports -# runs-on: macos-11 -# needs: [check_changelog, build_mac] -# -# steps: -# - name: Cache Artifacts -# uses: actions/cache@v3 -# with: -# path: artifacts -# key: ${{ runner.os }}-artifacts -# -# - name: Setup Macports -# run : | -# # update paths for macports -# echo "/opt/local/sbin" >> $GITHUB_PATH -# echo "/opt/local/bin" >> $GITHUB_PATH -# -# # Set OpenSSL 1.1 as default -# # rm -rf /usr/local/opt/openssl -# # rm -rf /usr/local/bin/openssl -# # ln -sf /usr/local/Cellar/openssl@1.1/1.1.1o/bin/openssl /usr/local/bin/openssl -# # ln -sf /usr/local/Cellar/openssl@1.1/1.1.1o /usr/local/opt/openssl -# -# # download and extract macports -# curl -O https://distfiles.macports.org/MacPorts/MacPorts-2.7.2.tar.bz2 -# tar xf MacPorts-2.7.2.tar.bz2 -# -# # build macports -# cd MacPorts-2.7.2 -# ./configure -# make -# sudo make install -# cd ../ -# rm -rf MacPorts-2.7.2* -# -# - name: Configure Macports -# run: | -# # update sources -# sudo port -v selfupdate -# -# # use custom sources -# sudo chmod 777 /opt/local/etc/macports/sources.conf -# echo file://$(echo ~)/ports > /opt/local/etc/macports/sources.conf -# echo rsync://rsync.macports.org/macports/release/tarballs/ports.tar [default] >> /opt/local/etc/macports/sources.conf -# sudo chmod 644 /opt/local/etc/macports/sources.conf -# -# # setup custom port -# mkdir -p ~/ports/multimedia/sunshine -# -# # copy configured Portfile -# mv ./artifacts/Portfile ~/ports/multimedia/sunshine/ -# -# # remove remaining cached artifacts -# rm -r -f ./artifacts -# mkdir artifacts -# -# # index the ports -# cd ~/ports -# portindex -# -# - name: Build -# run: | -# # build port -# sudo port install sunshine \ -# || cat /opt/local/var/macports/logs/_Users_runner_ports_multimedia_sunshine/Sunshine/main.log \ -# && exit 1 -# -# # create packages -# sudo port dmg sunshine -# sudo port pkg sunshine -# -# # move -# mv $(port work sunshine)/Sunshine*.dmg ./artifacts/sunshine.dmg -# mv $(port work sunshine)/Sunshine*.ppkg ./artifacts/sunshine.pkg -# -# # testing only -# # ls ~/ports/multimedia/sunshine -# # cat ~/ports/multimedia/sunshine/Portfile -# # cat /opt/local/etc/macports/sources.conf -# # cat ~/ports/Portindex -# # port search sunshine -# -# - name: Upload Artifacts -# uses: actions/upload-artifact@v3 -# with: -# name: sunshine-macports -# path: artifacts/ -# -# - name: Create Release -# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} -# uses: SunshineStream/actions/create_release@master -# with: -# token: ${{ secrets.GITHUB_TOKEN }} -# next_version: ${{ needs.check_changelog.outputs.next_version }} -# last_version: ${{ needs.check_changelog.outputs.last_version }} -# release_body: ${{ needs.check_changelog.outputs.release_body }} + build_mac_port: + name: Macports + needs: [check_changelog, build_mac] + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ macos-10.15, macos-11, macos-12 ] + + steps: + - name: Cache Artifacts + uses: actions/cache@v3 + with: + path: artifacts + key: ${{ runner.os }}-artifacts + + - name: Checkout ports + uses: actions/checkout@v3 + with: + repository: macports/macports-ports + fetch-depth: 64 + path: ports + + - name: Checkout mpbb + uses: actions/checkout@v3 + with: + repository: macports/mpbb + path: mpbb + + - name: Bootstrap MacPorts + run: | + # copy Portfile from artifacts to ports + mkdir -p ./ports/multimedia/Sunshine + cp -f ./artifacts/Portfile ./ports/multimedia/Sunshine/Portfile + + # display the Portfile + cat ./ports/multimedia/Sunshine/Portfile + + . ports/.github/workflows/bootstrap.sh + + - name: Setup Macports + run : | + # Add getopt, mpbb and the MacPorts paths to $PATH for the subsequent steps. + echo "/opt/mports/bin" >> $GITHUB_PATH + echo "${PWD}/mpbb" >> $GITHUB_PATH + echo "/opt/local/bin" >> $GITHUB_PATH + echo "/opt/local/sbin" >> $GITHUB_PATH + + - name: Determine list of subports + id: subportlist + run: | + set -eu + port=Sunshine + subportlist="" + + echo "Listing subports for Sunshine" + new_subports=$(mpbb \ + --work-dir /tmp/mpbb \ + list-subports \ + --archive-site= \ + --archive-site-private= \ + --include-deps=no \ + "$port" \ + | tr '\n' ' ') + for subport in $new_subports; do + echo "$subport" + subportlist="$subportlist $subport" + done + echo "::set-output name=subportlist::${subportlist}" + + - name: Run port lint for all subports + run: | + set -eu + fail=0 + for subport in $subportlist; do + echo "::group::${subport}" + path=$(port file "$subport") + messagetype="warning" + if ! messages=$(port -q lint "$subport" 2>&1); then + messagetype="error" + fail=1 + fi + if [ -n "$messages" ]; then + echo "$messages" + # See https://github.com/actions/toolkit/issues/193#issuecomment-605394935 + encoded_messages="port lint ${subport}:%0A" + encoded_messages+="$(echo "${messages}" | sed -E 's/$/%0A/g' | tr -d '\n')" + echo "::${messagetype} file=${path#${PWD}/ports/},line=1,col=1::${encoded_messages}" + fi + echo "::endgroup::" + done + exit "$fail" + env: + subportlist: ${{ steps.subportlist.outputs.subportlist }} + + - name: Build subports + run: | + set -eu + fail=0 + for subport in $subportlist; do + workdir="/tmp/mpbb/$subport" + mkdir -p "$workdir/logs" + touch "$workdir/logs/dependencies-progress.txt" + echo "::group::Cleaning up between ports" + sudo mpbb --work-dir "$workdir" cleanup + echo "::endgroup::" + echo "::group::Installing dependencies for ${subport}" + sudo mpbb \ + --work-dir "$workdir" \ + install-dependencies \ + "$subport" >"$workdir/logs/install-dependencies.log" 2>&1 & + deps_pid=$! + tail -f "$workdir/logs/dependencies-progress.txt" 2>/dev/null & + tail_pid=$! + set +e + wait "$deps_pid" + deps_exit=$? + set -e + kill "$tail_pid" || true + if [ "$deps_exit" -ne 0 ]; then + echo "::endgroup::" + echo "::error::Failed to install dependencies for ${subport}" + fail=1 + continue + fi + echo "::endgroup::" + echo "::group::Installing ${subport}" + set +e + sudo mpbb \ + --work-dir "$workdir" \ + install-port \ + --source \ + "$subport" + install_exit=$? + set -e + if [ "$install_exit" -ne 0 ]; then + echo "::endgroup::" + echo "::error::Failed to install ${subport}" + fail=1 + continue + fi + echo "::endgroup::" + done + exit "$fail" + env: + subportlist: ${{ steps.subportlist.outputs.subportlist }} + + - name: Package + run: | + # build port + # sudo port install sunshine \ + # || cat /opt/local/var/macports/logs/_Users_runner_ports_multimedia_sunshine/Sunshine/main.log \ + # && exit 1 + + # create packages + sudo port dmg sunshine + sudo port pkg sunshine + + # move + mv $(port work sunshine)/Sunshine*.dmg ./artifacts/sunshine.dmg + mv $(port work sunshine)/Sunshine*.ppkg ./artifacts/sunshine.pkg + + # testing only + # ls ~/ports/multimedia/sunshine + # cat ~/ports/multimedia/sunshine/Portfile + # cat /opt/local/etc/macports/sources.conf + # cat ~/ports/Portindex + # port search sunshine + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: sunshine-macports + path: artifacts/ + + - name: Create Release + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + uses: SunshineStream/actions/create_release@master + with: + token: ${{ secrets.GITHUB_TOKEN }} + next_version: ${{ needs.check_changelog.outputs.next_version }} + last_version: ${{ needs.check_changelog.outputs.last_version }} + release_body: ${{ needs.check_changelog.outputs.release_body }} build_win: name: Windows From 1cf0360520f10765823c5fadd7cc2135028f238e Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 15 Jun 2022 21:50:02 -0400 Subject: [PATCH 298/817] Initial flatpak packaging - adds `com.github.sunshinestream.sunshine.yml` - moves and renames `sunshine.desktop` - moves and renames `Portfile` - adds cmake options for configuration only Co-Authored-By: istori1 <107304850+istori1@users.noreply.github.com> --- .github/workflows/CI.yml | 83 ++++++- CMakeLists.txt | 19 +- .../com.github.sunshinestream.sunshine.yml | 212 ++++++++++++++++++ .../linux/sunshine.desktop | 0 Portfile.in => packaging/macos/Portfile | 0 5 files changed, 306 insertions(+), 8 deletions(-) create mode 100644 packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml rename sunshine.desktop.in => packaging/linux/sunshine.desktop (100%) rename Portfile.in => packaging/macos/Portfile (100%) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 18d7e89c..8d9e7ad5 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -52,6 +52,82 @@ jobs: echo Within 'CMakeLists.txt' change "project(Sunshine [VERSION $cmakelists_version]" to "project(Sunshine [VERSION ${{ needs.check_changelog.outputs.next_version_bare }}]" exit 1 + build_linux_flatpak: + name: Linux Flatpak + runs-on: ubuntu-18.04 + needs: check_changelog + + steps: + - name: Maximize build space + uses: easimon/maximize-build-space@master + with: + root-reserve-mb: 2048 + swap-size-mb: 8192 + remove-dotnet: 'true' + remove-android: 'true' + remove-haskell: 'true' + + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Dependencies Linux Flatpak + run: | + sudo add-apt-repository ppa:flatpak/stable -y + sudo apt-get update -y + sudo apt-get install -y \ + cmake \ + flatpak \ + flatpak-builder + sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + sudo flatpak install flathub org.freedesktop.Platform//21.08 org.freedesktop.Sdk//21.08 -y + + - name: Configure Flatpak Manifest + run: | + # variables for manifest + owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]' ) + repo=$(echo ${GITHUB_REPOSITORY#*/} | tr '[:upper:]' '[:lower:]' ) + branch=${GITHUB_HEAD_REF} + + # check the branch variable + if [ -z "$branch" ] + then + echo "This is a PUSH event" + branch=${GITHUB_BASE_REF} + else + echo "This is a PR event" + fi + echo "Owner: ${owner}" + echo "Repo: ${repo}" + echo "Branch: ${branch}" + + mkdir -p build + mkdir -p artifacts + + cd build + cmake -DGITHUB_OWNER=${owner} -DGITHUB_REPO=${repo} -DGITHUB_BRANCH=${branch} -DSUNSHINE_CONFIGURE_FLATPAK=ON -DSUNSHINE_CONFIGURE_ONLY=ON .. + + - name: Build Linux Flatpak + working-directory: build + run: | + sudo flatpak-builder build-dir com.github.sunshinestream.sunshine.yml + sudo flatpak-builder --repo=repo --force-clean build-dir com.github.sunshinestream.sunshine.yml + sudo flatpak build-bundle ./repo ../artifacts/sunshine.flatpak com.github.sunshinestream.sunshine + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: sunshine-linux-flatpak + path: artifacts/ + + - name: Create Release + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + uses: SunshineStream/actions/create_release@master + with: + token: ${{ secrets.GITHUB_TOKEN }} + next_version: ${{ needs.check_changelog.outputs.next_version }} + last_version: ${{ needs.check_changelog.outputs.last_version }} + release_body: ${{ needs.check_changelog.outputs.release_body }} + build_linux: name: Linux runs-on: ubuntu-20.04 @@ -64,16 +140,19 @@ jobs: CMAKE_INSTALL_PREFIX: '' SUNSHINE_ASSETS_DIR: '/usr/local/sunshine/assets' SUNSHINE_CONFIG_DIR: '/usr/local/sunshine/config' + EXTRA_ARGS: '' - type: appimage CMAKE_INSTALL_PREFIX: '/usr' SUNSHINE_ASSETS_DIR: 'sunshine.AppImage.config' SUNSHINE_CONFIG_DIR: 'sunshine.AppImage.home' + EXTRA_ARGS: '-DSUNSHINE_CONFIGURE_APPIMAGE=ON' steps: - name: Checkout uses: actions/checkout@v3 with: submodules: recursive + - name: Setup Dependencies Linux run: | sudo add-apt-repository ppa:savoury1/ffmpeg4 -y @@ -144,7 +223,7 @@ jobs: mkdir -p artifacts cd build - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${{ matrix.CMAKE_INSTALL_PREFIX }} -DSUNSHINE_ASSETS_DIR=${{ matrix.SUNSHINE_ASSETS_DIR }} -DSUNSHINE_CONFIG_DIR=${{ matrix.SUNSHINE_CONFIG_DIR }} -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine -DSUNSHINE_ENABLE_WAYLAND=ON -DSUNSHINE_ENABLE_X11=ON -DSUNSHINE_ENABLE_DRM=ON -DSUNSHINE_ENABLE_CUDA=ON .. + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${{ matrix.CMAKE_INSTALL_PREFIX }} -DSUNSHINE_ASSETS_DIR=${{ matrix.SUNSHINE_ASSETS_DIR }} -DSUNSHINE_CONFIG_DIR=${{ matrix.SUNSHINE_CONFIG_DIR }} -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine -DSUNSHINE_ENABLE_WAYLAND=ON -DSUNSHINE_ENABLE_X11=ON -DSUNSHINE_ENABLE_DRM=ON -DSUNSHINE_ENABLE_CUDA=ON ${{ matrix.EXTRA_ARGS }} .. make -j ${nproc} - name: Package Linux - CPACK @@ -291,7 +370,7 @@ jobs: mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=/usr/local/sunshine/assets -DSUNSHINE_CONFIG_DIR=/usr/local/sunshine/config -DGITHUB_OWNER=${owner} -DGITHUB_REPO=${repo} -DGITHUB_BRANCH=${branch} .. + cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=/usr/local/sunshine/assets -DSUNSHINE_CONFIG_DIR=/usr/local/sunshine/config -DGITHUB_OWNER=${owner} -DGITHUB_REPO=${repo} -DGITHUB_BRANCH=${branch} -DSUNSHINE_CONFIGURE_PORTFILE=ON .. make -j ${nproc} - name: Package MacOS diff --git a/CMakeLists.txt b/CMakeLists.txt index 44f24e7d..72386de6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,19 @@ project(Sunshine VERSION 0.14.0 HOMEPAGE_URL "https://sunshinestream.github.io" ) +if(${SUNSHINE_CONFIGURE_APPIMAGE}) + configure_file(packaging/linux/sunshine.desktop sunshine.desktop @ONLY) +elseif(${SUNSHINE_CONFIGURE_FLATPAK}) + configure_file(packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml com.github.sunshinestream.sunshine.yml @ONLY) +elseif(${SUNSHINE_CONFIGURE_PORTFILE}) + configure_file(packaging/macos/Portfile Portfile @ONLY) +endif() + +# return if configure only is set +if(${SUNSHINE_CONFIGURE_ONLY}) + return() +endif() + set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) set(SUNSHINE_SOURCE_ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src_assets") @@ -562,9 +575,6 @@ if(APPLE) # TODO: test set(CPACK_BUNDLE_PLIST "${APPLE_PLIST_FILE}") set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/sunshine.icns") # set(CPACK_BUNDLE_STARTUP_COMMAND "${INSTALL_RUNTIME_DIR}/sunshine") - - # Portfile - configure_file(Portfile.in Portfile @ONLY) endif() if(UNIX AND NOT APPLE) install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") @@ -587,9 +597,6 @@ if(UNIX AND NOT APPLE) set(CPACK_DEBIAN_PACKAGE_DEPENDS "openssl, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.1, libavdevice >= 4.3, boost-thread >= 1.67.0, boost-filesystem >= 1.67.0, boost-log >= 1.67.0, pulseaudio-libs >= 10.0, libopusenc >= 0.2.1, libxcb >= 1.13, libXtst >= 1.2.3, libevdev >= 1.5.6, libdrm >= 2.4.97, libcap >= 2.22") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # This should automatically figure out dependencies, doesn't work with the current config - - # AppImage desktop file - configure_file(sunshine.desktop.in sunshine.desktop @ONLY) endif() include(CPack) diff --git a/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml b/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml new file mode 100644 index 00000000..228d14d5 --- /dev/null +++ b/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml @@ -0,0 +1,212 @@ +app-id: com.github.sunshinestream.sunshine +runtime: org.freedesktop.Platform +runtime-version: "21.08" +sdk: org.freedesktop.Sdk +command: sunshine +separate-locales: false +finish-args: + - --share=ipc + - --socket=x11 + - --socket=wayland + - --socket=pulseaudio + - --share=network + - --device=all + - --persist=. + - --system-talk-name=org.freedesktop.Avahi + - --env=PULSE_PROP_media.category=Manager + +cleanup: + - /include + - /lib/cmake + - /lib/pkgconfig + - /lib/*.la + - /lib/*.a + - /share + +modules: +# - name: cuda +# buildsystem: simple +# only-arches: +# - x86_64 +# - aarch64 +# cleanup: +# - '*' +# build-commands: +# - chmod u+x ./cuda.run +# - ./cuda.run --silent --toolkit --toolkitpath=/app --no-opengl-libs --no-man-page --no-drm +# - rm ./cuda.run +# - rm -r /app/nsight-systems-2021.3.2 +# sources: +# - type: file +# only-arches: +# - x86_64 +# url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run +# sha256: bbd87ca0e913f837454a796367473513cddef555082e4d86ed9a38659cc81f0a +# dest-filename: cuda.run +# - type: file +# only-arches: +# - aarch64 +# url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux_sbsa.run +# sha256: f2c4a52e06329606c8dfb7c5ea3f4cb4c0b28f9d3fdffeeb734fcc98daf580d8 +# dest-filename: cuda.run + + - name: boost + buildsystem: simple + build-commands: + - ./bootstrap.sh --prefix=/app --with-libraries=system,thread,log + - ./b2 install variant=release link=static,shared runtime-link=shared cxxflags="$CXXFLAGS" linkflags="$LDFLAGS" -j $FLATPAK_BUILDER_N_JOBS + sources: + - type: archive + url: https://boostorg.jfrog.io/artifactory/main/release/1.79.0/source/boost_1_79_0.tar.bz2 + sha256: 475d589d51a7f8b3ba2ba4eda022b170e562ca3b760ee922c146b6c65856ef39 + + - name: ffmpeg + config-opts: + - --enable-gpl + - --disable-static + - --enable-shared + - --disable-doc + - --disable-programs + - --disable-decoders + - --enable-libfontconfig + - --enable-libfreetype + - --enable-libopus + - --enable-libvorbis + - --enable-libvpx + - --enable-libx264 + - --enable-libx265 + - --enable-nvenc + - --enable-encoder=h264_v4l2m2m + - --enable-encoder=hevc_v4l2m2m + # - --enable-nonfree + # - --enable-cuda-nvcc + # - --enable-libnpp + # - --extra-cflags=-I/app/include + # - --extra-ldflags=-L/app/lib64 + cleanup: + - /share/ffmpeg/examples + sources: + - type: archive + url: https://ffmpeg.org/releases/ffmpeg-4.4.2.tar.xz + sha256: af419a7f88adbc56c758ab19b4c708afbcae15ef09606b82b855291f6a6faa93 + modules: + - name: vmaf + buildsystem: meson + subdir: libvmaf + cleanup: + - /bin + sources: + - type: archive + url: https://github.com/Netflix/vmaf/archive/refs/tags/v2.3.1.tar.gz + sha256: 8d60b1ddab043ada25ff11ced821da6e0c37fd7730dd81c24f1fc12be7293ef2 + - name: x264 + config-opts: + - --disable-cli + - --enable-shared + sources: + - type: archive + url: https://code.videolan.org/videolan/x264/-/archive/stable/x264-stable.tar.bz2 + sha256: 8fedb184045722d8cc39353099373a5b7350171d0964d01fff8eced21b959b29 + - name: x265 + buildsystem: cmake-ninja + builddir: true + subdir: source + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DENABLE_CLI=OFF + sources: + - type: archive + url: https://bitbucket.org/multicoreware/x265_git/downloads/x265_3.5.tar.gz + sha256: e70a3335cacacbba0b3a20ec6fecd6783932288ebc8163ad74bcc9606477cae8 + - name: nv-codec-headers + no-autogen: true + make-install-args: + - PREFIX=/app + cleanup: + - '*' + sources: + - type: archive + url: https://github.com/FFmpeg/nv-codec-headers/archive/refs/tags/n11.1.5.1.tar.gz + sha256: d095fbd56aa93772471a323be0ebe65504a0f43f06c76a30b6d25da77b06ae9c + + - name: avahi + cleanup: + - /bin + - /lib/avahi + - /share/applications/*.desktop + - /share/avahi + config-opts: + - --with-distro=none + - --disable-gobject + - --disable-introspection + - --disable-qt3 + - --disable-qt4 + - --disable-qt5 + - --disable-gtk + - --disable-core-docs + - --disable-manpages + - --disable-libdaemon + - --disable-python + - --disable-pygobject + - --disable-mono + - --disable-monodoc + - --disable-autoipd + - --disable-doxygen-doc + - --disable-doxygen-dot + - --disable-doxygen-xml + - --disable-doxygen-html + - --disable-manpages + - --disable-xmltoman + sources: + - type: archive + url: https://avahi.org/download/avahi-0.8.tar.gz + sha256: 060309d7a333d38d951bc27598c677af1796934dbd98e1024e7ad8de798fedda + modules: + - name: libevent + cleanup: + - /bin + sources: + - type: archive + url: https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz + sha256: 92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb + + - name: libevdev + buildsystem: meson + cleanup: + - /bin + sources: + - type: archive + url: https://www.freedesktop.org/software/libevdev/libevdev-1.12.1.tar.xz + sha256: 1dbba41bc516d3ca7abc0da5b862efe3ea8a7018fa6e9b97ce9d39401b22426c + modules: + - name: libcheck + buildsystem: cmake + cleanup: + - /bin + sources: + - type: archive + url: https://github.com/libcheck/check/archive/refs/tags/0.15.2.tar.gz + sha256: 998d355294bb94072f40584272cf4424571c396c631620ce463f6ea97aa67d2e + + - name: sunshine + buildsystem: cmake + no-make-install: false + build-options: + cxxflags: -I/app/include/libevdev-1.0 + config-opts: + - -DCMAKE_BUILD_TYPE=Release + - -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=/app/bin + - -DCMAKE_INSTALL_PREFIX='' + - -DSUNSHINE_CONFIG_DIR=/app/config + - -DSUNSHINE_ASSETS_DIR=/app/assets + - -DSUNSHINE_EXECUTABLE_PATH=/app/bin/sunshine + - -DSUNSHINE_ENABLE_WAYLAND=ON + - -DSUNSHINE_ENABLE_X11=ON + - -DSUNSHINE_ENABLE_DRM=ON + - -DSUNSHINE_ENABLE_CUDA=ON + post-install: + - cp -f ./src_assets/linux/misc/85-sunshine-rules.rules /etc/udev/rules.d/ + sources: + - type: git + url: https://github.com/@GITHUB_OWNER@/@GITHUB_REPO@.git + branch: @GITHUB_BRANCH@ diff --git a/sunshine.desktop.in b/packaging/linux/sunshine.desktop similarity index 100% rename from sunshine.desktop.in rename to packaging/linux/sunshine.desktop diff --git a/Portfile.in b/packaging/macos/Portfile similarity index 100% rename from Portfile.in rename to packaging/macos/Portfile From bd51a7d5fa7059863774b910fa39cd2d9327be1e Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 17 Jun 2022 15:30:56 -0400 Subject: [PATCH 299/817] Refactor assets and config directories --- .github/workflows/CI.yml | 8 ++++---- CMakeLists.txt | 7 +++++++ .../linux/flatpak/com.github.sunshinestream.sunshine.yml | 6 +++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 8d9e7ad5..a4d67abe 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -137,9 +137,9 @@ jobs: matrix: include: # package these differently - type: cpack - CMAKE_INSTALL_PREFIX: '' - SUNSHINE_ASSETS_DIR: '/usr/local/sunshine/assets' - SUNSHINE_CONFIG_DIR: '/usr/local/sunshine/config' + CMAKE_INSTALL_PREFIX: '/usr/local/sunshine' + SUNSHINE_ASSETS_DIR: 'assets' + SUNSHINE_CONFIG_DIR: 'config' EXTRA_ARGS: '' - type: appimage CMAKE_INSTALL_PREFIX: '/usr' @@ -370,7 +370,7 @@ jobs: mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=/usr/local/sunshine/assets -DSUNSHINE_CONFIG_DIR=/usr/local/sunshine/config -DGITHUB_OWNER=${owner} -DGITHUB_REPO=${repo} -DGITHUB_BRANCH=${branch} -DSUNSHINE_CONFIGURE_PORTFILE=ON .. + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local/sunshine -DSUNSHINE_ASSETS_DIR=assets -DSUNSHINE_CONFIG_DIR=config -DGITHUB_OWNER=${owner} -DGITHUB_REPO=${repo} -DGITHUB_BRANCH=${branch} -DSUNSHINE_CONFIGURE_PORTFILE=ON .. make -j ${nproc} - name: Package MacOS diff --git a/CMakeLists.txt b/CMakeLists.txt index 72386de6..0e083567 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -414,6 +414,13 @@ if(NOT SUNSHINE_CONFIG_DIR) set(SUNSHINE_CONFIG_DIR "${CMAKE_CURRENT_BINARY_DIR}/config") endif() +if(CMAKE_INSTALL_PREFIX) + if(NOT ${SUNSHINE_CONFIGURE_APPIMAGE}) + set(SUNSHINE_ASSETS_DIR "${CMAKE_INSTALL_PREFIX}/${SUNSHINE_ASSETS_DIR}") + set(SUNSHINE_CONFIG_DIR "${CMAKE_INSTALL_PREFIX}/${SUNSHINE_CONFIG_DIR}") + endif() +endif() + list(APPEND CBS_EXTERNAL_LIBRARIES cbs) diff --git a/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml b/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml index 228d14d5..7d6b92fe 100644 --- a/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml +++ b/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml @@ -196,9 +196,9 @@ modules: config-opts: - -DCMAKE_BUILD_TYPE=Release - -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=/app/bin - - -DCMAKE_INSTALL_PREFIX='' - - -DSUNSHINE_CONFIG_DIR=/app/config - - -DSUNSHINE_ASSETS_DIR=/app/assets + - -DCMAKE_INSTALL_PREFIX='/app' + - -DSUNSHINE_ASSETS_DIR=assets + - -DSUNSHINE_CONFIG_DIR=config - -DSUNSHINE_EXECUTABLE_PATH=/app/bin/sunshine - -DSUNSHINE_ENABLE_WAYLAND=ON - -DSUNSHINE_ENABLE_X11=ON From 2a13697fefe4e4ec3d68e6d137d0a98b56d0de1a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 17 Jun 2022 16:30:59 -0400 Subject: [PATCH 300/817] Replace hardcoded paths for unix and... - Add cache for flatpak job Co-Authored-By: istori1 <107304850+istori1@users.noreply.github.com> --- .github/workflows/CI.yml | 14 ++++++++++---- CMakeLists.txt | 19 +++++++++++-------- .../com.github.sunshinestream.sunshine.yml | 6 ++---- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a4d67abe..34d1c7c3 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -67,6 +67,12 @@ jobs: remove-android: 'true' remove-haskell: 'true' + - name: Cache flatpak-builder + uses: actions/cache@v3 + with: + path: ./build/.flatpak-builder + key: ${{ runner.os }}-flatpak-builder + - name: Checkout uses: actions/checkout@v3 @@ -137,9 +143,9 @@ jobs: matrix: include: # package these differently - type: cpack - CMAKE_INSTALL_PREFIX: '/usr/local/sunshine' - SUNSHINE_ASSETS_DIR: 'assets' - SUNSHINE_CONFIG_DIR: 'config' + CMAKE_INSTALL_PREFIX: '/usr' + SUNSHINE_ASSETS_DIR: 'local/sunshine/assets' + SUNSHINE_CONFIG_DIR: 'local/sunshine/config' EXTRA_ARGS: '' - type: appimage CMAKE_INSTALL_PREFIX: '/usr' @@ -370,7 +376,7 @@ jobs: mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local/sunshine -DSUNSHINE_ASSETS_DIR=assets -DSUNSHINE_CONFIG_DIR=config -DGITHUB_OWNER=${owner} -DGITHUB_REPO=${repo} -DGITHUB_BRANCH=${branch} -DSUNSHINE_CONFIGURE_PORTFILE=ON .. + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DSUNSHINE_ASSETS_DIR=local/sunshine/assets -DSUNSHINE_CONFIG_DIR=local/sunshine/config -DGITHUB_OWNER=${owner} -DGITHUB_REPO=${repo} -DGITHUB_BRANCH=${branch} -DSUNSHINE_CONFIGURE_PORTFILE=ON .. make -j ${nproc} - name: Package MacOS diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e083567..d37fc1fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,11 @@ project(Sunshine VERSION 0.14.0 HOMEPAGE_URL "https://sunshinestream.github.io" ) +option(SUNSHINE_CONFIGURE_APPIMAGE "Configure files required for AppImage." OFF) +option(SUNSHINE_CONFIGURE_FLATPAK "Configure files required for Flatpak." OFF) +option(SUNSHINE_CONFIGURE_PORTFILE "Configure MacOS Portfile." OFF) +option(SUNSHINE_CONFIGURE_ONLY "Configure special files only, then exit." OFF) + if(${SUNSHINE_CONFIGURE_APPIMAGE}) configure_file(packaging/linux/sunshine.desktop sunshine.desktop @ONLY) elseif(${SUNSHINE_CONFIGURE_FLATPAK}) @@ -414,11 +419,9 @@ if(NOT SUNSHINE_CONFIG_DIR) set(SUNSHINE_CONFIG_DIR "${CMAKE_CURRENT_BINARY_DIR}/config") endif() -if(CMAKE_INSTALL_PREFIX) - if(NOT ${SUNSHINE_CONFIGURE_APPIMAGE}) - set(SUNSHINE_ASSETS_DIR "${CMAKE_INSTALL_PREFIX}/${SUNSHINE_ASSETS_DIR}") - set(SUNSHINE_CONFIG_DIR "${CMAKE_INSTALL_PREFIX}/${SUNSHINE_CONFIG_DIR}") - endif() +if(UNIX AND CMAKE_INSTALL_PREFIX AND NOT ${SUNSHINE_CONFIGURE_APPIMAGE}) + set(SUNSHINE_ASSETS_DIR "${CMAKE_INSTALL_PREFIX}/${SUNSHINE_ASSETS_DIR}") + set(SUNSHINE_CONFIG_DIR "${CMAKE_INSTALL_PREFIX}/${SUNSHINE_CONFIG_DIR}") endif() list(APPEND CBS_EXTERNAL_LIBRARIES @@ -435,7 +438,7 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${OPENSSL_LIBRARIES} ${PLATFORM_LIBRARIES}) -if (NOT WIN32) +if(NOT WIN32) list(APPEND SUNSHINE_EXTERNAL_LIBRARIES Boost::log) endif() @@ -590,8 +593,8 @@ if(UNIX AND NOT APPLE) install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine-rules.rules" DESTINATION "/etc/udev/rules.d") - install(TARGETS sunshine RUNTIME DESTINATION "/usr/bin") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "/usr/lib/systemd/user") + install(TARGETS sunshine RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "${CMAKE_INSTALL_LIBDIR}/systemd/user") # Pre and post install set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA diff --git a/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml b/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml index 7d6b92fe..08e47999 100644 --- a/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml +++ b/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml @@ -191,11 +191,11 @@ modules: - name: sunshine buildsystem: cmake no-make-install: false + builddir: true build-options: - cxxflags: -I/app/include/libevdev-1.0 + cxxflags: -I${C_INCLUDE_PATH}/libevdev-1.0 config-opts: - -DCMAKE_BUILD_TYPE=Release - - -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=/app/bin - -DCMAKE_INSTALL_PREFIX='/app' - -DSUNSHINE_ASSETS_DIR=assets - -DSUNSHINE_CONFIG_DIR=config @@ -204,8 +204,6 @@ modules: - -DSUNSHINE_ENABLE_X11=ON - -DSUNSHINE_ENABLE_DRM=ON - -DSUNSHINE_ENABLE_CUDA=ON - post-install: - - cp -f ./src_assets/linux/misc/85-sunshine-rules.rules /etc/udev/rules.d/ sources: - type: git url: https://github.com/@GITHUB_OWNER@/@GITHUB_REPO@.git From 850926019436dcf21e8526db0adad3f1b5d75000 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 17 Jun 2022 19:04:43 -0400 Subject: [PATCH 301/817] Fix branch for PUSH events - This partially fixes #194 --- .github/workflows/CI.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 34d1c7c3..b25b7353 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -93,24 +93,30 @@ jobs: owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]' ) repo=$(echo ${GITHUB_REPOSITORY#*/} | tr '[:upper:]' '[:lower:]' ) branch=${GITHUB_HEAD_REF} + commit=${{ github.event.pull_request.head.sha }} # check the branch variable if [ -z "$branch" ] then echo "This is a PUSH event" - branch=${GITHUB_BASE_REF} + branch=branch=${{ github.ref_name }} + commit=${{ github.sha }} else echo "This is a PR event" fi echo "Owner: ${owner}" echo "Repo: ${repo}" echo "Branch: ${branch}" + echo "Commit: ${commit}" mkdir -p build mkdir -p artifacts cd build cmake -DGITHUB_OWNER=${owner} -DGITHUB_REPO=${repo} -DGITHUB_BRANCH=${branch} -DSUNSHINE_CONFIGURE_FLATPAK=ON -DSUNSHINE_CONFIGURE_ONLY=ON .. + + # add the commit + echo " commit: ${commit}" >> ./com.github.sunshinestream.sunshine.yml - name: Build Linux Flatpak working-directory: build @@ -361,18 +367,21 @@ jobs: owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]' ) repo=$(echo ${GITHUB_REPOSITORY#*/} | tr '[:upper:]' '[:lower:]' ) branch=${GITHUB_HEAD_REF} + commit=${{ github.event.pull_request.head.sha }} # check the branch variable if [ -z "$branch" ] then echo "This is a PUSH event" - branch=${GITHUB_BASE_REF} + branch=branch=${{ github.ref_name }} + commit=${{ github.sha }} else echo "This is a PR event" fi echo "Owner: ${owner}" echo "Repo: ${repo}" echo "Branch: ${branch}" + echo "Commit: ${commit}" mkdir build cd build From e45452b9bc3f3118f768b4db5296a3160ec7a209 Mon Sep 17 00:00:00 2001 From: sitiom Date: Sat, 18 Jun 2022 21:42:05 +0800 Subject: [PATCH 302/817] Add Winget in Third Party Packages (#207) * Add Winget in Third Party Packages * Add etiquette tip in Contributing section Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> --- docs/source/about/third_party_packages.rst | 6 ++++++ docs/source/contributing/contributing.rst | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/docs/source/about/third_party_packages.rst b/docs/source/about/third_party_packages.rst index 5b63a496..7877e09c 100644 --- a/docs/source/about/third_party_packages.rst +++ b/docs/source/about/third_party_packages.rst @@ -38,6 +38,12 @@ Scoop :alt: Scoop Version (extras bucket) :target: https://scoop.sh/#/apps?s=0&d=1&o=true&q=sunshine +Winget +------ +.. image:: https://img.shields.io/badge/dynamic/xml?color=orange&label=Winget&style=for-the-badge&prefix=v&query=%2F%2Ftr%5B%40id%3D%27winget%27%5D%2Ftd%5B3%5D%2Fspan%2Fa&url=https%3A%2F%2Frepology.org%2Fproject%2Fsunshine%2Fversions&logo=microsoft + :alt: Winget Version + :target: https://github.com/microsoft/winget-pkgs/tree/master/manifests/s/SunshineStream/Sunshine + Legacy GitHub Repo ------------------ diff --git a/docs/source/contributing/contributing.rst b/docs/source/contributing/contributing.rst index d9f5106f..dfd2e080 100644 --- a/docs/source/contributing/contributing.rst +++ b/docs/source/contributing/contributing.rst @@ -2,6 +2,11 @@ Contributing ============ + +.. Tip:: If this is your first time contributing to an open source project, it is a good idea to read + MDN's `Basic etiquette for open source projects`_ first. There are a few best practices to adopt that will help + ensure that you and the other project contributors feel valued and safe, and stay productive. + #. Fork the repo on GitHub #. Create a new branch for the feature you are adding or the issue you are fixing @@ -30,3 +35,5 @@ Contributing - Were documentation blocks updated for new or modified components? .. Note:: Developers and maintainers will attempt to assist with challenging issues. + +.. _Basic etiquette for open source projects: https://developer.mozilla.org/en-US/docs/MDN/Contribute/Open_source_etiquette From 840013ec78bc670ca9d4b1b9da747a6f8265d671 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 17 Jun 2022 20:05:51 -0400 Subject: [PATCH 303/817] Update documentation for Flatpak --- .github/workflows/CI.yml | 2 +- README.rst | 1 + docs/source/about/installation.rst | 36 ++++++++--- docs/source/about/usage.rst | 99 ++++++++++++++++-------------- docs/source/index.rst | 2 - 5 files changed, 82 insertions(+), 58 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index b25b7353..2562d68d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -70,7 +70,7 @@ jobs: - name: Cache flatpak-builder uses: actions/cache@v3 with: - path: ./build/.flatpak-builder + path: ./build/.flatpak-builder/build key: ${{ runner.os }}-flatpak-builder - name: Checkout diff --git a/README.rst b/README.rst index 8fb29b6b..24d0c161 100644 --- a/README.rst +++ b/README.rst @@ -2,6 +2,7 @@ Overview ======== +SunshineStream has the full documentation hosted on `Read the Docs `_. About ----- diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 2176206a..9973b820 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -28,7 +28,7 @@ AppImage .. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:appimage?logo=github&style=for-the-badge :alt: GitHub issues by-label -The current known compatibility of the AppImage is shown below. +According to AppImageLint the AppImage can run on the following distros. - [✖] Debian oldstable (buster) - [✔] Debian stable (bullseye) @@ -42,11 +42,11 @@ The current known compatibility of the AppImage is shown below. - [✖] Ubuntu trusty - [✖] CentOS 7 -#. Download and extract ``sunshine-appimage.zip`` to your home directory. +#. Download ``sunshine-appimage.zip`` and extract the contents to your home directory. -Debian Packages -^^^^^^^^^^^^^^^ -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:debian?logo=github&style=for-the-badge +Debian Package +^^^^^^^^^^^^^^ +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:deb?logo=github&style=for-the-badge :alt: GitHub issues by-label #. Download ``sunshine.deb`` and run the following code. @@ -57,9 +57,29 @@ Debian Packages .. Tip:: You can double click the deb file to see details about the package and begin installation. -Red Hat Packages -^^^^^^^^^^^^^^^^ -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:fedora?logo=github&style=for-the-badge +Flatpak Package +^^^^^^^^^^^^^^^ +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:flatpak?logo=github&style=for-the-badge + :alt: GitHub issues by-label + +.. Todo:: This package needs to have CUDA added. + +#. Install `Flatpak `_ as required. +#. Download ``sunshine.flatpak`` and run the following code. + + System level (recommended) + .. code-block:: bash + + flatpak install --system sunshine.flatpak + + User level + .. code-block:: bash + + flatpak install --user sunshine.flatpak + +RPM Package +^^^^^^^^^^^ +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:rpm?logo=github&style=for-the-badge :alt: GitHub issues by-label #. Add `rpmfusion` repositories by running the following code. diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst index 79d285f9..8ae0287c 100644 --- a/docs/source/about/usage.rst +++ b/docs/source/about/usage.rst @@ -33,7 +33,7 @@ Usage #. When Moonlight request you insert the correct pin on sunshine: - Login to the web ui - - Go to "PIN" in the Header + - Go to "PIN" in the Navbar - Type in your PIN and press Enter, you should get a Success Message - In Moonlight, select one of the Applications listed @@ -60,77 +60,82 @@ The deb and rpm packages handle these steps automatically. The AppImage does not Sunshine needs access to `uinput` to create mouse and gamepad events. -Add user to group `input`, if this is the first time installing. - .. code-block:: bash +#. Add user to group `input`, if this is the first time installing. + .. code-block:: bash - sudo usermod -a -G input $USER - sudo reboot now + sudo usermod -a -G input $USER -Create `udev` rules. - .. code-block:: bash +#. Create `udev` rules. + .. code-block:: bash - sudo nano /etc/udev/rules.d/85-sunshine-input.rules + sudo nano /etc/udev/rules.d/85-sunshine-input.rules - Input the following contents. + Input the following contents. - .. code-block:: + .. code-block:: - KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput" + KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput" - Save the file and exit: + Save the file and exit: - #. ``CTRL+X`` to start exit. - #. ``Y`` to save modifications. + #. ``CTRL+X`` to start exit. + #. ``Y`` to save modifications. -Configure autostart service - - filename: ``~/.config/systemd/user/sunshine.service`` - - contents: +#. Optionally, configure autostart service + - filename: ``~/.config/systemd/user/sunshine.service`` + - contents: - .. code-block:: + .. code-block:: - [Unit] - Description=Sunshine Gamestream Server for Moonlight + [Unit] + Description=Sunshine Gamestream Server for Moonlight - [Service] - ExecStart= + [Service] + ExecStart= - [Install] - WantedBy=graphical-session.target + [Install] + WantedBy=graphical-session.target - .. table:: - :widths: auto + .. table:: + :widths: auto - ======== =================== =============== - package ExecStart Auto Configured - ======== =================== =============== - deb /usr/bin/sunshine ✔ - rpm /usr/bin/sunshine ✔ - AppImage ~/sunshine.AppImage ✖ - ======== =================== =============== + ======== ============================================== =============== + package ExecStart Auto Configured + ======== ============================================== =============== + deb /usr/bin/sunshine ✔ + rpm /usr/bin/sunshine ✔ + AppImage ~/sunshine.AppImage ✖ + Flatpak flatpak run com.github.sunshinestream.sunshine ✖ + ======== ============================================== =============== - Start once - .. code-block:: bash + Start once + .. code-block:: bash - systemctl --user start sunshine + systemctl --user start sunshine - Start on boot - .. code-block:: bash + Start on boot + .. code-block:: bash - systemctl --user enable sunshine + systemctl --user enable sunshine -Additional Setup for KMS - .. Note:: ``cap_sys_admin`` may as well be root, except you don't need to be root to run it. It is necessary to - allow Sunshine to use KMS. +#. Additional Setup for KMS + .. Note:: ``cap_sys_admin`` may as well be root, except you don't need to be root to run it. It is necessary to + allow Sunshine to use KMS. - Enable - .. code-block:: bash + Enable + .. code-block:: bash + + sudo setcap cap_sys_admin+p $(readlink -f $(which sunshine)) + + Disable + .. code-block:: bash - sudo setcap cap_sys_admin+p $(readlink -f $(which sunshine)) + sudo setcap -r $(readlink -f $(which sunshine)) - Disable +#. Reboot .. code-block:: bash - sudo setcap -r $(readlink -f $(which sunshine)) + sudo reboot now MacOS ^^^^^ diff --git a/docs/source/index.rst b/docs/source/index.rst index 5384c8e2..aaa2663c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,7 +1,5 @@ :github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/index.rst -SunshineStream has this documentation hosted on `Read the Docs `_. - Table of Contents ================= .. include:: toc.rst From 9c4763af752c72316cc6099b39a6427c0c5c2a31 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Jun 2022 13:05:34 -0400 Subject: [PATCH 304/817] Configure Portfile in Macports job --- .github/workflows/CI.yml | 130 +++++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 67 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index c97883ee..3da5a4b0 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -346,13 +346,6 @@ jobs: with: submodules: recursive - # this is for the macports job - - name: Cache Artifacts - uses: actions/cache@v3 - with: - path: artifacts - key: ${{ runner.os }}-artifacts - - name: Setup Dependencies MacOS run: | # install dependencies using homebrew @@ -363,29 +356,9 @@ jobs: - name: Build MacOS run: | - # variables for Portfile - owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]' ) - repo=$(echo ${GITHUB_REPOSITORY#*/} | tr '[:upper:]' '[:lower:]' ) - branch=${GITHUB_HEAD_REF} - commit=${{ github.event.pull_request.head.sha }} - - # check the branch variable - if [ -z "$branch" ] - then - echo "This is a PUSH event" - branch=branch=${{ github.ref_name }} - commit=${{ github.sha }} - else - echo "This is a PR event" - fi - echo "Owner: ${owner}" - echo "Repo: ${repo}" - echo "Branch: ${branch}" - echo "Commit: ${commit}" - mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DSUNSHINE_ASSETS_DIR=local/sunshine/assets -DSUNSHINE_CONFIG_DIR=local/sunshine/config -DGITHUB_OWNER=${owner} -DGITHUB_REPO=${repo} -DGITHUB_BRANCH=${branch} -DSUNSHINE_CONFIGURE_PORTFILE=ON .. + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DSUNSHINE_ASSETS_DIR=local/sunshine/assets -DSUNSHINE_CONFIG_DIR=local/sunshine/config .. make -j ${nproc} - name: Package MacOS @@ -407,9 +380,6 @@ jobs: cpack -G ZIP mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-macos-experimental-archive.zip - # move - mv Portfile ../artifacts/Portfile - - name: Upload Artifacts uses: actions/upload-artifact@v3 with: @@ -436,18 +406,16 @@ jobs: build_mac_port: name: Macports - needs: [check_changelog, build_mac] + needs: check_changelog runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [ macos-10.15, macos-11, macos-12 ] steps: - - name: Cache Artifacts - uses: actions/cache@v3 - with: - path: artifacts - key: ${{ runner.os }}-artifacts + - name: Checkout + uses: actions/checkout@v3 - name: Checkout ports uses: actions/checkout@v3 @@ -462,28 +430,56 @@ jobs: repository: macports/mpbb path: mpbb - - name: Bootstrap MacPorts + - name: Setup Dependencies Macports run: | - # copy Portfile from artifacts to ports - mkdir -p ./ports/multimedia/Sunshine - cp -f ./artifacts/Portfile ./ports/multimedia/Sunshine/Portfile + # install dependencies using homebrew + brew install cmake - # display the Portfile - cat ./ports/multimedia/Sunshine/Portfile + - name: Configure Portfile + run: | + # variables for Portfile + owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]' ) + repo=$(echo ${GITHUB_REPOSITORY#*/} | tr '[:upper:]' '[:lower:]' ) + branch=${GITHUB_HEAD_REF} + commit=${{ github.event.pull_request.head.sha }} + + # check the branch variable + if [ -z "$branch" ] + then + echo "This is a PUSH event" + branch=branch=${{ github.ref_name }} + commit=${{ github.sha }} + else + echo "This is a PR event" + fi + echo "Owner: ${owner}" + echo "Repo: ${repo}" + echo "Branch: ${branch}" + echo "Commit: ${commit}" + + mkdir build + cd build + cmake -DGITHUB_OWNER=${owner} -DGITHUB_REPO=${repo} -DGITHUB_BRANCH=${branch} -DSUNSHINE_CONFIGURE_PORTFILE=ON -DSUNSHINE_CONFIGURE_ONLY=ON .. + + cd .. + + # copy Portfile to artifacts + mkdir -p artifacts + cp -f ./build/Portfile ./artifacts/ + + # copy Portfile to ports + mkdir -p ./ports/multimedia/Sunshine + cp -f ./build/Portfile ./ports/multimedia/Sunshine/Portfile + - name: Bootstrap MacPorts + run: | . ports/.github/workflows/bootstrap.sh - - - name: Setup Macports - run : | + # Add getopt, mpbb and the MacPorts paths to $PATH for the subsequent steps. echo "/opt/mports/bin" >> $GITHUB_PATH echo "${PWD}/mpbb" >> $GITHUB_PATH echo "/opt/local/bin" >> $GITHUB_PATH echo "/opt/local/sbin" >> $GITHUB_PATH - - # copy Portfile from artifacts to ports - mkdir -p ./ports/multimedia/Sunshine - cp -f ./artifacts/Portfile ./ports/multimedia/Sunshine/Portfile - name: Determine list of subports id: subportlist @@ -586,30 +582,30 @@ jobs: - name: Package run: | - # build port - # sudo port install sunshine \ - # || cat /opt/local/var/macports/logs/_Users_runner_ports_multimedia_sunshine/Sunshine/main.log \ - # && exit 1 - # create packages - sudo port dmg sunshine sudo port pkg sunshine + sudo port dmg sunshine - # move - mv $(port work sunshine)/Sunshine*.dmg ./artifacts/sunshine.dmg - mv $(port work sunshine)/Sunshine*.ppkg ./artifacts/sunshine.pkg - - # testing only - # ls ~/ports/multimedia/sunshine - # cat ~/ports/multimedia/sunshine/Portfile - # cat /opt/local/etc/macports/sources.conf - # cat ~/ports/Portindex - # port search sunshine + work=$(port work sunshine) + echo "Sunshine port work directory: ${work}" + + # testing + ls ${work} + + # move components out of port work directory + sudo mv ${work}/Sunshine*component.pkg /tmp/ + + # copy artifacts + sudo mv ${work}/Sunshine*.pkg ./artifacts/sunshine.pkg + sudo mv ${work}/Sunshine*.dmg ./artifacts/sunshine.dmg + + # move components back + # sudo mv /tmp/Sunshine*component.pkg ${work}/ - name: Upload Artifacts uses: actions/upload-artifact@v3 with: - name: sunshine-macports + name: sunshine-macports-${{ matrix.os }} path: artifacts/ - name: Create Release From f4074341a577d19ab695d99871266d1ae1162678 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 Jun 2022 20:46:59 -0400 Subject: [PATCH 305/817] Add AUR PKGBUILD --- .github/workflows/CI.yml | 85 +++++++++++++++++ CMakeLists.txt | 4 + packaging/linux/aur/sunshine-git/.SRCINFO | 35 +++++++ packaging/linux/aur/sunshine-git/PKGBUILD | 92 +++++++++++++++++++ packaging/linux/aur/sunshine/.SRCINFO | 33 +++++++ packaging/linux/aur/sunshine/PKGBUILD | 81 ++++++++++++++++ packaging/linux/aur/tmp/sunshine.install | 6 ++ .../linux/aur/tmp/systemd-user-config.patch | 14 +++ packaging/linux/aur/tmp/udev.rules | 1 + 9 files changed, 351 insertions(+) create mode 100644 packaging/linux/aur/sunshine-git/.SRCINFO create mode 100644 packaging/linux/aur/sunshine-git/PKGBUILD create mode 100644 packaging/linux/aur/sunshine/.SRCINFO create mode 100644 packaging/linux/aur/sunshine/PKGBUILD create mode 100644 packaging/linux/aur/tmp/sunshine.install create mode 100644 packaging/linux/aur/tmp/systemd-user-config.patch create mode 100644 packaging/linux/aur/tmp/udev.rules diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2562d68d..4e274b9e 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -52,6 +52,91 @@ jobs: echo Within 'CMakeLists.txt' change "project(Sunshine [VERSION $cmakelists_version]" to "project(Sunshine [VERSION ${{ needs.check_changelog.outputs.next_version_bare }}]" exit 1 + build_linux_aur: + name: Linux AUR + runs-on: ubuntu-latest + needs: check_changelog + strategy: + fail-fast: false + matrix: + include: + - aur_pkg: sunshine + - aur_pkg: sunshine-git + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup Dependencies Linux AUR + run: | + sudo apt-get update -y + sudo apt-get install -y \ + cmake + + - name: Configure PKGBUILD files + run: | + mkdir -p artifacts + mkdir -p build + + cd build + cmake -DSUNSHINE_CONFIGURE_AUR=ON -DSUNSHINE_CONFIGURE_ONLY=ON -DSUNSHINE_AUR_PKG=${{ matrix.aur_pkg }} .. + cd .. + + mv ./build/PKGBUILD ./artifacts/ + # mv ./build/.SRCINFO ./artifacts/ + + # mv ./packaging/linux/aur/tmp/* ./artifacts/ + + ls artifacts + + - name: Validate package + uses: hapakaien/archlinux-package-action@v2 + with: + path: artifacts + flags: '--syncdeps --noconfirm' + namcap: true + srcinfo: true + aur: true # workaround mirror problem + + - name: Test artifacts + run: | + ls artifacts + +# - name: PKGBUILD AUR +# uses: sunshinestream/pkgbuild-aur@sunshine-fix +# with: +# pkg-name: artifacts + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: sunshine-linux-aur-${{ matrix.aur_pkg }} + path: artifacts/ + + - name: Setup Publish AUR Variables + if: ${{ github.event_name == 'push' }} + run: | + if [ ${{ github.ref == 'refs/heads/master' && matrix.aur_pkg == 'sunshine' }} ] + then + echo "aur_publish=true" >> $GITHUB_ENV + elif [ ${{ github.ref == 'refs/heads/nightly' && matrix.aur_pkg == 'sunshine-git' }} ] + then + echo "aur_publish=true" >> $GITHUB_ENV + else + echo "aur_publish=false" >> $GITHUB_ENV + fi + + - name: Publish AUR package + if: ${{ env.aur_publish }} + uses: KSXGitHub/github-actions-deploy-aur@v2.2.5 + with: + pkgname: ${{ env.aur_pkgname }} + pkgbuild: ./artifacts/PKGBUILD + commit_username: ${{ secrets.AUR_USERNAME }} + commit_email: ${{ secrets.AUR_EMAIL }} + ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} + commit_message: Autoupdate from GitHub + build_linux_flatpak: name: Linux Flatpak runs-on: ubuntu-18.04 diff --git a/CMakeLists.txt b/CMakeLists.txt index d37fc1fd..520524ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,12 +6,16 @@ project(Sunshine VERSION 0.14.0 ) option(SUNSHINE_CONFIGURE_APPIMAGE "Configure files required for AppImage." OFF) +option(SUNSHINE_CONFIGURE_AUR "Configure files required for AUR." OFF) option(SUNSHINE_CONFIGURE_FLATPAK "Configure files required for Flatpak." OFF) option(SUNSHINE_CONFIGURE_PORTFILE "Configure MacOS Portfile." OFF) option(SUNSHINE_CONFIGURE_ONLY "Configure special files only, then exit." OFF) if(${SUNSHINE_CONFIGURE_APPIMAGE}) configure_file(packaging/linux/sunshine.desktop sunshine.desktop @ONLY) +elseif(${SUNSHINE_CONFIGURE_AUR}) + configure_file(packaging/linux/aur/${SUNSHINE_AUR_PKG}/.SRCINFO .SRCINFO @ONLY) + configure_file(packaging/linux/aur/${SUNSHINE_AUR_PKG}/PKGBUILD PKGBUILD @ONLY) elseif(${SUNSHINE_CONFIGURE_FLATPAK}) configure_file(packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml com.github.sunshinestream.sunshine.yml @ONLY) elseif(${SUNSHINE_CONFIGURE_PORTFILE}) diff --git a/packaging/linux/aur/sunshine-git/.SRCINFO b/packaging/linux/aur/sunshine-git/.SRCINFO new file mode 100644 index 00000000..490be198 --- /dev/null +++ b/packaging/linux/aur/sunshine-git/.SRCINFO @@ -0,0 +1,35 @@ +pkgbase = sunshine-git + pkgdesc = Open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield + pkgver = 0.13.0.957.4b658cd + pkgrel = 1 + url = https://github.com/SunshineStream/sunshine + install = sunshine.install + arch = x86_64 + arch = i686 + license = GPL3 + makedepends = git + makedepends = cmake + makedepends = boost + makedepends = make + depends = boost-libs + depends = ffmpeg4.4 + depends = openssl + depends = libpulse + depends = opus + depends = libxtst + depends = libx11 + depends = libxfixes + depends = libevdev + depends = libxcb + depends = libxrandr + depends = udev + provides = sunshine + conflicts = sunshine + source = sunshine-git::git+https://github.com/SunshineStream/sunshine.git + source = systemd-user-config.patch + source = udev.rules + sha256sums = SKIP + sha256sums = 1642eb8672b137e94aa16e4aadde37f68bf1920dfadd1325cca480d7731f38c9 + sha256sums = 5ce01689247cb01d3f119cac32c731607d99bb875dcdd39c92b547f76d2befa0 + +pkgname = sunshine-git \ No newline at end of file diff --git a/packaging/linux/aur/sunshine-git/PKGBUILD b/packaging/linux/aur/sunshine-git/PKGBUILD new file mode 100644 index 00000000..a590177b --- /dev/null +++ b/packaging/linux/aur/sunshine-git/PKGBUILD @@ -0,0 +1,92 @@ +# Maintainer: Jacek Szafarkiewicz +# Contributor: Levente Polyak + +pkgname=sunshine-git +pkgver=0.13.0.957.4b658cd +pkgrel=1 +pkgdesc="Open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield" +url="https://github.com/SunshineStream/sunshine" +arch=('x86_64' 'i686') +license=('GPL3') + +depends=('boost-libs' 'ffmpeg4.4' 'openssl' 'libpulse' 'opus' 'libxtst' 'libx11' 'libxfixes' 'libevdev' 'libxcb' 'libxrandr' 'udev') +makedepends=('git' 'cmake' 'boost' 'make') + +provides=('sunshine') +conflicts=("sunshine") + +# source=("$pkgname::git+https://github.com/SunshineStream/sunshine.git" +# "systemd-user-config.patch" +# "udev.rules") +source=("$pkgname::git+https://github.com/SunshineStream/sunshine.git") +# sha256sums=('SKIP' +# '1642eb8672b137e94aa16e4aadde37f68bf1920dfadd1325cca480d7731f38c9' +# '5ce01689247cb01d3f119cac32c731607d99bb875dcdd39c92b547f76d2befa0') +sha256sums=('SKIP') +# install=sunshine.install + +_assets_path=/usr/share/sunshine + +pkgver() { + cd "$pkgname" + printf "%s.%s.%s" "$(git describe --tags $(git rev-list --tags --max-count=1) | sed 's/^v//')" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" +} + +prepare() { + cd "$pkgname" + git submodule update --recursive --init + + # patch -p1 < ../systemd-user-config.patch +} + +build() { + export CFLAGS="${CFLAGS/-Werror=format-security/}" + export CXXFLAGS="${CXXFLAGS/-Werror=format-security/}" + + cmake \ + -S "$pkgname" \ + -B build \ + -Wno-dev \ + -D SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ + -D SUNSHINE_ASSETS_DIR="$_assets_path" \ + \ + -D LIBAVCODEC_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBAVCODEC_LIBRARIES=/usr/lib/ffmpeg4.4/libavcodec.so \ + -D LIBAVDEVICE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBAVDEVICE_LIBRARIES=/usr/lib/ffmpeg4.4/libavdevice.so \ + -D LIBAVFORMAT_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBAVFORMAT_LIBRARIES=/usr/lib/ffmpeg4.4/libavformat.so \ + -D LIBAVUTIL_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBAVUTIL_LIBRARIES=/usr/lib/ffmpeg4.4/libavutil.so \ + -D LIBSWSCALE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBAVUTIL_LIBRARIES=/usr/lib/ffmpeg4.4/libavutil.so \ + -D LIBSWSCALE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBSWSCALE_LIBRARIES=/usr/lib/ffmpeg4.4/libswscale.so + + make -C build +} + +package() { + pushd "$pkgname/assets" + install -Dvm644 sunshine.conf "$pkgdir/$_assets_path/sunshine.conf" + install -Dvm644 apps_linux.json "$pkgdir/$_assets_path/apps_linux.json" + + find web shaders/opengl -type f -print0 | xargs -0 -I {} install -Dvm644 {} "$pkgdir/$_assets_path/{}" + popd + + pushd build + install -Dvm755 sunshine "$pkgdir/usr/bin/sunshine" + install -Dvm644 sunshine.service "$pkgdir/usr/lib/systemd/user/sunshine.service" + popd + + # install -Dvm644 udev.rules "$pkgdir/usr/lib/udev/rules.d/85-sunshine.rules" +} + +post_install() { + if ! getent group input > /dev/null; then + echo "Creating group input" + groupadd -r input + fi +} + +# vim: ts=2 sw=2 et: diff --git a/packaging/linux/aur/sunshine/.SRCINFO b/packaging/linux/aur/sunshine/.SRCINFO new file mode 100644 index 00000000..2a093d28 --- /dev/null +++ b/packaging/linux/aur/sunshine/.SRCINFO @@ -0,0 +1,33 @@ +pkgbase = sunshine + pkgdesc = Open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield + pkgver = 0.13.0 + pkgrel = 1 + url = https://github.com/SunshineStream/sunshine + install = sunshine.install + arch = x86_64 + arch = i686 + license = GPL3 + makedepends = git + makedepends = cmake + makedepends = boost + makedepends = make + depends = boost-libs + depends = ffmpeg4.4 + depends = openssl + depends = libpulse + depends = opus + depends = libxtst + depends = libx11 + depends = libxfixes + depends = libevdev + depends = libxcb + depends = libxrandr + depends = udev + source = sunshine::git+https://github.com/SunshineStream/sunshine.git#tag=v0.13.0 + source = systemd-user-config.patch + source = udev.rules + sha256sums = SKIP + sha256sums = 1642eb8672b137e94aa16e4aadde37f68bf1920dfadd1325cca480d7731f38c9 + sha256sums = 5ce01689247cb01d3f119cac32c731607d99bb875dcdd39c92b547f76d2befa0 + +pkgname = sunshine \ No newline at end of file diff --git a/packaging/linux/aur/sunshine/PKGBUILD b/packaging/linux/aur/sunshine/PKGBUILD new file mode 100644 index 00000000..2e857fbe --- /dev/null +++ b/packaging/linux/aur/sunshine/PKGBUILD @@ -0,0 +1,81 @@ +# Maintainer: Jacek Szafarkiewicz + +pkgname=sunshine +pkgver=0.13.0 +pkgrel=1 +pkgdesc="Open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield" +url="https://github.com/SunshineStream/sunshine" +arch=('x86_64' 'i686') +license=('GPL3') + +depends=('boost-libs' 'ffmpeg4.4' 'openssl' 'libpulse' 'opus' 'libxtst' 'libx11' 'libxfixes' 'libevdev' 'libxcb' 'libxrandr' 'udev') +makedepends=('git' 'cmake' 'boost' 'make') + +# source=("$pkgname::git+https://github.com/SunshineStream/sunshine.git#tag=v$pkgver" +# "systemd-user-config.patch" +# "udev.rules") +source=("$pkgname::git+https://github.com/SunshineStream/sunshine.git#tag=v$pkgver") +# sha256sums=('SKIP' +# '1642eb8672b137e94aa16e4aadde37f68bf1920dfadd1325cca480d7731f38c9' +# '5ce01689247cb01d3f119cac32c731607d99bb875dcdd39c92b547f76d2befa0') +sha256sums=('SKIP') +# install=sunshine.install + +_assets_path=/usr/share/$pkgname + +prepare() { + cd "$pkgname" + git submodule update --recursive --init + + # patch -p1 < ../systemd-user-config.patch +} + +build() { + export CFLAGS="${CFLAGS/-Werror=format-security/}" + export CXXFLAGS="${CXXFLAGS/-Werror=format-security/}" + + cmake \ + -S "$pkgname" \ + -B build \ + -Wno-dev \ + -D SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ + -D SUNSHINE_ASSETS_DIR="$_assets_path" \ + \ + -D LIBAVCODEC_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBAVCODEC_LIBRARIES=/usr/lib/ffmpeg4.4/libavcodec.so \ + -D LIBAVDEVICE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBAVDEVICE_LIBRARIES=/usr/lib/ffmpeg4.4/libavdevice.so \ + -D LIBAVFORMAT_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBAVFORMAT_LIBRARIES=/usr/lib/ffmpeg4.4/libavformat.so \ + -D LIBAVUTIL_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBAVUTIL_LIBRARIES=/usr/lib/ffmpeg4.4/libavutil.so \ + -D LIBSWSCALE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBSWSCALE_LIBRARIES=/usr/lib/ffmpeg4.4/libswscale.so + + make -C build +} + +package() { + pushd "$pkgname/assets" + install -Dvm644 sunshine.conf "$pkgdir/$_assets_path/sunshine.conf" + install -Dvm644 apps_linux.json "$pkgdir/$_assets_path/apps_linux.json" + + find web shaders/opengl -type f -print0 | xargs -0 -I {} install -Dvm644 {} "$pkgdir/$_assets_path/{}" + popd + + pushd build + install -Dvm755 sunshine "$pkgdir/usr/bin/sunshine" + install -Dvm644 sunshine.service "$pkgdir/usr/lib/systemd/user/sunshine.service" + popd + + # install -Dvm644 udev.rules "$pkgdir/usr/lib/udev/rules.d/85-sunshine.rules" +} + +post_install() { + if ! getent group input > /dev/null; then + echo "Creating group input" + groupadd -r input + fi +} + +# vim: ts=2 sw=2 et: diff --git a/packaging/linux/aur/tmp/sunshine.install b/packaging/linux/aur/tmp/sunshine.install new file mode 100644 index 00000000..d10a7a04 --- /dev/null +++ b/packaging/linux/aur/tmp/sunshine.install @@ -0,0 +1,6 @@ +post_install() { + if ! getent group input > /dev/null; then + echo "Creating group input" + groupadd -r input + fi +} diff --git a/packaging/linux/aur/tmp/systemd-user-config.patch b/packaging/linux/aur/tmp/systemd-user-config.patch new file mode 100644 index 00000000..3e872e3e --- /dev/null +++ b/packaging/linux/aur/tmp/systemd-user-config.patch @@ -0,0 +1,14 @@ +diff --git a/sunshine.service.in b/sunshine.service.in +index c0c3828..fe45460 100644 +--- a/sunshine.service.in ++++ b/sunshine.service.in +@@ -2,7 +2,8 @@ + Description=Sunshine Gamestream Server for Moonlight + + [Service] +-ExecStart=@SUNSHINE_EXECUTABLE_PATH@ ++ExecStartPre=/bin/sh -c "test -e %E/sunshine || cp -r '@SUNSHINE_ASSETS_DIR@' '%E/sunshine'" ++ExecStart=@SUNSHINE_EXECUTABLE_PATH@ %E/sunshine/sunshine.conf + + [Install] + WantedBy=graphical-session.target diff --git a/packaging/linux/aur/tmp/udev.rules b/packaging/linux/aur/tmp/udev.rules new file mode 100644 index 00000000..d626ba3f --- /dev/null +++ b/packaging/linux/aur/tmp/udev.rules @@ -0,0 +1 @@ +KERNEL=="uinput", GROUP="input", MODE="0660" From f07171315fa2483dd677d19a3bb11164536b8345 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 19 Jun 2022 16:20:25 -0400 Subject: [PATCH 306/817] Configure PKGBUILD according to github event - Release to AUR on push into `master` - Update AUR installation instructions - Use common linux directories for `PKGBUILD` --- .github/workflows/CI.yml | 95 +++++++++++-------- CMakeLists.txt | 3 +- README.rst | 4 + docs/source/about/installation.rst | 10 ++ packaging/linux/aur/PKGBUILD | 55 +++++++++++ packaging/linux/aur/sunshine-git/.SRCINFO | 35 ------- packaging/linux/aur/sunshine-git/PKGBUILD | 92 ------------------ packaging/linux/aur/sunshine/.SRCINFO | 33 ------- packaging/linux/aur/sunshine/PKGBUILD | 81 ---------------- packaging/linux/aur/tmp/sunshine.install | 6 -- .../linux/aur/tmp/systemd-user-config.patch | 14 --- packaging/linux/aur/tmp/udev.rules | 1 - 12 files changed, 126 insertions(+), 303 deletions(-) create mode 100644 packaging/linux/aur/PKGBUILD delete mode 100644 packaging/linux/aur/sunshine-git/.SRCINFO delete mode 100644 packaging/linux/aur/sunshine-git/PKGBUILD delete mode 100644 packaging/linux/aur/sunshine/.SRCINFO delete mode 100644 packaging/linux/aur/sunshine/PKGBUILD delete mode 100644 packaging/linux/aur/tmp/sunshine.install delete mode 100644 packaging/linux/aur/tmp/systemd-user-config.patch delete mode 100644 packaging/linux/aur/tmp/udev.rules diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 4e274b9e..1782cffc 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -56,12 +56,6 @@ jobs: name: Linux AUR runs-on: ubuntu-latest needs: check_changelog - strategy: - fail-fast: false - matrix: - include: - - aur_pkg: sunshine - - aur_pkg: sunshine-git steps: - name: Checkout @@ -75,19 +69,60 @@ jobs: - name: Configure PKGBUILD files run: | + # variables for manifest + owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]' ) + repo=$(echo ${GITHUB_REPOSITORY#*/} | tr '[:upper:]' '[:lower:]' ) + branch=${GITHUB_HEAD_REF} + commit=${{ github.event.pull_request.head.sha }} + + echo "aur_publish=false" >> $GITHUB_ENV + aur_pkg=sunshine-dev + fragment="" + sub_version="" + conflicts="'sunshine'" + provides="'sunshine'" + + # check the branch variable + if [ -z "$branch" ] + then + echo "This is a PUSH event" + branch=branch=${{ github.ref_name }} + commit=${{ github.sha }} + + if [ ${{ github.ref == 'refs/heads/master' }} ] + then + aur_pkg=sunshine-git + # conflicts="" + # provides="" + + echo "aur_publish=true" >> $GITHUB_ENV + elif [ ${{ github.ref == 'refs/heads/nightly' }} ] + then + aur_pkg=sunshine-nightly + sub_version=".r${commit}" + fi + else + echo "This is a PR event" + sub_version=".r${commit}" + fi + + echo "aur_pkg=${aur_pkg}" >> $GITHUB_ENV + + fragment="#commit=${commit}" + + echo "Owner: ${owner}" + echo "Repo: ${repo}" + echo "Branch: ${branch}" + echo "Commit: ${commit}" + mkdir -p artifacts mkdir -p build cd build - cmake -DSUNSHINE_CONFIGURE_AUR=ON -DSUNSHINE_CONFIGURE_ONLY=ON -DSUNSHINE_AUR_PKG=${{ matrix.aur_pkg }} .. + cmake -DSUNSHINE_CONFIGURE_AUR=ON -DSUNSHINE_AUR_PKG=${aur_pkg} -DSUNSHINE_SUB_VERSION=${sub_version} -DSUNSHINE_AUR_CONFLICTS=${conflicts} -DSUNSHINE_AUR_PROVIDES=${provides} -DSUNSHINE_AUR_FRAGMENT=${fragment} -DGITHUB_OWNER=${owner} -DGITHUB_REPO=${repo} -DSUNSHINE_CONFIGURE_ONLY=ON .. cd .. mv ./build/PKGBUILD ./artifacts/ - # mv ./build/.SRCINFO ./artifacts/ - - # mv ./packaging/linux/aur/tmp/* ./artifacts/ - - ls artifacts - name: Validate package uses: hapakaien/archlinux-package-action@v2 @@ -98,44 +133,26 @@ jobs: srcinfo: true aur: true # workaround mirror problem - - name: Test artifacts - run: | - ls artifacts - -# - name: PKGBUILD AUR -# uses: sunshinestream/pkgbuild-aur@sunshine-fix -# with: -# pkg-name: artifacts - - name: Upload Artifacts + if: ${{ always() }} uses: actions/upload-artifact@v3 with: - name: sunshine-linux-aur-${{ matrix.aur_pkg }} + name: sunshine-linux-aur path: artifacts/ - - name: Setup Publish AUR Variables - if: ${{ github.event_name == 'push' }} - run: | - if [ ${{ github.ref == 'refs/heads/master' && matrix.aur_pkg == 'sunshine' }} ] - then - echo "aur_publish=true" >> $GITHUB_ENV - elif [ ${{ github.ref == 'refs/heads/nightly' && matrix.aur_pkg == 'sunshine-git' }} ] - then - echo "aur_publish=true" >> $GITHUB_ENV - else - echo "aur_publish=false" >> $GITHUB_ENV - fi - - name: Publish AUR package - if: ${{ env.aur_publish }} - uses: KSXGitHub/github-actions-deploy-aur@v2.2.5 + if: ${{ env.aur_publish == 'true' }} + uses: KSXGitHub/github-actions-deploy-aur@master # assets arg not in latest release with: - pkgname: ${{ env.aur_pkgname }} + pkgname: ${{ env.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: Autoupdate from GitHub + commit_message: Release ${{ needs.check_changelog.outputs.next_version }} + allow_empty_commits: false build_linux_flatpak: name: Linux Flatpak diff --git a/CMakeLists.txt b/CMakeLists.txt index 520524ba..e74c7b9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,8 +14,7 @@ option(SUNSHINE_CONFIGURE_ONLY "Configure special files only, then exit." OFF) if(${SUNSHINE_CONFIGURE_APPIMAGE}) configure_file(packaging/linux/sunshine.desktop sunshine.desktop @ONLY) elseif(${SUNSHINE_CONFIGURE_AUR}) - configure_file(packaging/linux/aur/${SUNSHINE_AUR_PKG}/.SRCINFO .SRCINFO @ONLY) - configure_file(packaging/linux/aur/${SUNSHINE_AUR_PKG}/PKGBUILD PKGBUILD @ONLY) + configure_file(packaging/linux/aur/PKGBUILD PKGBUILD @ONLY) elseif(${SUNSHINE_CONFIGURE_FLATPAK}) configure_file(packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml com.github.sunshinestream.sunshine.yml @ONLY) elseif(${SUNSHINE_CONFIGURE_PORTFILE}) diff --git a/README.rst b/README.rst index 24d0c161..0e24b0cd 100644 --- a/README.rst +++ b/README.rst @@ -60,6 +60,10 @@ Downloads :alt: GitHub Releases :target: https://github.com/SunshineStream/Sunshine/releases/latest +.. image:: https://img.shields.io/aur/version/sunshine-git?style=for-the-badge&logo=archlinux + :alt: AUR version + :target: https://aur.archlinux.org/packages/sunshine-git + .. comment image:: https://img.shields.io/docker/pulls/sunshinestream/sunshine?style=for-the-badge&logo=docker :alt: Docker diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 9973b820..6609b429 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -44,6 +44,16 @@ According to AppImageLint the AppImage can run on the following distros. #. Download ``sunshine-appimage.zip`` and extract the contents to your home directory. +AUR Package +^^^^^^^^^^^ +#. Open terminal and run the following code. + + .. code-block:: bash + + git clone https://aur.archlinux.org/sunshine-git.git + cd sunshine-git + makepkg -fi + Debian Package ^^^^^^^^^^^^^^ .. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:deb?logo=github&style=for-the-badge diff --git a/packaging/linux/aur/PKGBUILD b/packaging/linux/aur/PKGBUILD new file mode 100644 index 00000000..6c6e31b3 --- /dev/null +++ b/packaging/linux/aur/PKGBUILD @@ -0,0 +1,55 @@ +# Edit on github: https://github.com/SunshineStream/Sunshine/tree/nightly/packaging/linux/aur/PKGBUILD +# Reference: https://wiki.archlinux.org/title/PKGBUILD + +pkgname=@SUNSHINE_AUR_PKG@ +pkgver=@PROJECT_VERSION@@SUNSHINE_SUB_VERSION@ +pkgrel=1 +pkgdesc="@PROJECT_DESCRIPTION@" +arch=('x86_64' 'i686') +url=@PROJECT_HOMEPAGE_URL@ +license=('GPL3') + +depends=('boost-libs' 'ffmpeg4.4' 'libpulse' 'libevdev' 'libx11' 'libxcb' 'libxfixes' 'libxrandr' 'libxtst' 'openssl' 'opus' 'udev') +makedepends=('git' 'cmake' 'boost' 'make') +optdepends=('cuda' 'libdrm' 'libcap') + +provides=(@SUNSHINE_AUR_PROVIDES@) +conflicts=(@SUNSHINE_AUR_CONFLICTS@) + +source=("$pkgname::git+https://github.com/@GITHUB_OWNER@/@GITHUB_REPO@.git@SUNSHINE_AUR_FRAGMENT@") +sha256sums=('SKIP') + +prepare() { + cd "$pkgname" + git submodule update --recursive --init +} + +build() { + export CFLAGS="${CFLAGS/-Werror=format-security/}" + export CXXFLAGS="${CXXFLAGS/-Werror=format-security/}" + + cmake \ + -S "$pkgname" \ + -B build \ + -Wno-dev \ + -D SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ + -D CMAKE_INSTALL_PREFIX="/usr" \ + -D SUNSHINE_ASSETS_DIR="local/sunshine/assets" \ + -D SUNSHINE_CONFIG_DIR="local/sunshine/config" \ + -D LIBAVCODEC_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBAVCODEC_LIBRARIES=/usr/lib/ffmpeg4.4/libavcodec.so \ + -D LIBAVDEVICE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBAVDEVICE_LIBRARIES=/usr/lib/ffmpeg4.4/libavdevice.so \ + -D LIBAVFORMAT_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBAVFORMAT_LIBRARIES=/usr/lib/ffmpeg4.4/libavformat.so \ + -D LIBAVUTIL_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBAVUTIL_LIBRARIES=/usr/lib/ffmpeg4.4/libavutil.so \ + -D LIBSWSCALE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBSWSCALE_LIBRARIES=/usr/lib/ffmpeg4.4/libswscale.so + + make -C build +} + +package() { + make -C build install DESTDIR="$pkgdir" +} diff --git a/packaging/linux/aur/sunshine-git/.SRCINFO b/packaging/linux/aur/sunshine-git/.SRCINFO deleted file mode 100644 index 490be198..00000000 --- a/packaging/linux/aur/sunshine-git/.SRCINFO +++ /dev/null @@ -1,35 +0,0 @@ -pkgbase = sunshine-git - pkgdesc = Open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield - pkgver = 0.13.0.957.4b658cd - pkgrel = 1 - url = https://github.com/SunshineStream/sunshine - install = sunshine.install - arch = x86_64 - arch = i686 - license = GPL3 - makedepends = git - makedepends = cmake - makedepends = boost - makedepends = make - depends = boost-libs - depends = ffmpeg4.4 - depends = openssl - depends = libpulse - depends = opus - depends = libxtst - depends = libx11 - depends = libxfixes - depends = libevdev - depends = libxcb - depends = libxrandr - depends = udev - provides = sunshine - conflicts = sunshine - source = sunshine-git::git+https://github.com/SunshineStream/sunshine.git - source = systemd-user-config.patch - source = udev.rules - sha256sums = SKIP - sha256sums = 1642eb8672b137e94aa16e4aadde37f68bf1920dfadd1325cca480d7731f38c9 - sha256sums = 5ce01689247cb01d3f119cac32c731607d99bb875dcdd39c92b547f76d2befa0 - -pkgname = sunshine-git \ No newline at end of file diff --git a/packaging/linux/aur/sunshine-git/PKGBUILD b/packaging/linux/aur/sunshine-git/PKGBUILD deleted file mode 100644 index a590177b..00000000 --- a/packaging/linux/aur/sunshine-git/PKGBUILD +++ /dev/null @@ -1,92 +0,0 @@ -# Maintainer: Jacek Szafarkiewicz -# Contributor: Levente Polyak - -pkgname=sunshine-git -pkgver=0.13.0.957.4b658cd -pkgrel=1 -pkgdesc="Open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield" -url="https://github.com/SunshineStream/sunshine" -arch=('x86_64' 'i686') -license=('GPL3') - -depends=('boost-libs' 'ffmpeg4.4' 'openssl' 'libpulse' 'opus' 'libxtst' 'libx11' 'libxfixes' 'libevdev' 'libxcb' 'libxrandr' 'udev') -makedepends=('git' 'cmake' 'boost' 'make') - -provides=('sunshine') -conflicts=("sunshine") - -# source=("$pkgname::git+https://github.com/SunshineStream/sunshine.git" -# "systemd-user-config.patch" -# "udev.rules") -source=("$pkgname::git+https://github.com/SunshineStream/sunshine.git") -# sha256sums=('SKIP' -# '1642eb8672b137e94aa16e4aadde37f68bf1920dfadd1325cca480d7731f38c9' -# '5ce01689247cb01d3f119cac32c731607d99bb875dcdd39c92b547f76d2befa0') -sha256sums=('SKIP') -# install=sunshine.install - -_assets_path=/usr/share/sunshine - -pkgver() { - cd "$pkgname" - printf "%s.%s.%s" "$(git describe --tags $(git rev-list --tags --max-count=1) | sed 's/^v//')" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" -} - -prepare() { - cd "$pkgname" - git submodule update --recursive --init - - # patch -p1 < ../systemd-user-config.patch -} - -build() { - export CFLAGS="${CFLAGS/-Werror=format-security/}" - export CXXFLAGS="${CXXFLAGS/-Werror=format-security/}" - - cmake \ - -S "$pkgname" \ - -B build \ - -Wno-dev \ - -D SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ - -D SUNSHINE_ASSETS_DIR="$_assets_path" \ - \ - -D LIBAVCODEC_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ - -D LIBAVCODEC_LIBRARIES=/usr/lib/ffmpeg4.4/libavcodec.so \ - -D LIBAVDEVICE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ - -D LIBAVDEVICE_LIBRARIES=/usr/lib/ffmpeg4.4/libavdevice.so \ - -D LIBAVFORMAT_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ - -D LIBAVFORMAT_LIBRARIES=/usr/lib/ffmpeg4.4/libavformat.so \ - -D LIBAVUTIL_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ - -D LIBAVUTIL_LIBRARIES=/usr/lib/ffmpeg4.4/libavutil.so \ - -D LIBSWSCALE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ - -D LIBAVUTIL_LIBRARIES=/usr/lib/ffmpeg4.4/libavutil.so \ - -D LIBSWSCALE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ - -D LIBSWSCALE_LIBRARIES=/usr/lib/ffmpeg4.4/libswscale.so - - make -C build -} - -package() { - pushd "$pkgname/assets" - install -Dvm644 sunshine.conf "$pkgdir/$_assets_path/sunshine.conf" - install -Dvm644 apps_linux.json "$pkgdir/$_assets_path/apps_linux.json" - - find web shaders/opengl -type f -print0 | xargs -0 -I {} install -Dvm644 {} "$pkgdir/$_assets_path/{}" - popd - - pushd build - install -Dvm755 sunshine "$pkgdir/usr/bin/sunshine" - install -Dvm644 sunshine.service "$pkgdir/usr/lib/systemd/user/sunshine.service" - popd - - # install -Dvm644 udev.rules "$pkgdir/usr/lib/udev/rules.d/85-sunshine.rules" -} - -post_install() { - if ! getent group input > /dev/null; then - echo "Creating group input" - groupadd -r input - fi -} - -# vim: ts=2 sw=2 et: diff --git a/packaging/linux/aur/sunshine/.SRCINFO b/packaging/linux/aur/sunshine/.SRCINFO deleted file mode 100644 index 2a093d28..00000000 --- a/packaging/linux/aur/sunshine/.SRCINFO +++ /dev/null @@ -1,33 +0,0 @@ -pkgbase = sunshine - pkgdesc = Open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield - pkgver = 0.13.0 - pkgrel = 1 - url = https://github.com/SunshineStream/sunshine - install = sunshine.install - arch = x86_64 - arch = i686 - license = GPL3 - makedepends = git - makedepends = cmake - makedepends = boost - makedepends = make - depends = boost-libs - depends = ffmpeg4.4 - depends = openssl - depends = libpulse - depends = opus - depends = libxtst - depends = libx11 - depends = libxfixes - depends = libevdev - depends = libxcb - depends = libxrandr - depends = udev - source = sunshine::git+https://github.com/SunshineStream/sunshine.git#tag=v0.13.0 - source = systemd-user-config.patch - source = udev.rules - sha256sums = SKIP - sha256sums = 1642eb8672b137e94aa16e4aadde37f68bf1920dfadd1325cca480d7731f38c9 - sha256sums = 5ce01689247cb01d3f119cac32c731607d99bb875dcdd39c92b547f76d2befa0 - -pkgname = sunshine \ No newline at end of file diff --git a/packaging/linux/aur/sunshine/PKGBUILD b/packaging/linux/aur/sunshine/PKGBUILD deleted file mode 100644 index 2e857fbe..00000000 --- a/packaging/linux/aur/sunshine/PKGBUILD +++ /dev/null @@ -1,81 +0,0 @@ -# Maintainer: Jacek Szafarkiewicz - -pkgname=sunshine -pkgver=0.13.0 -pkgrel=1 -pkgdesc="Open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield" -url="https://github.com/SunshineStream/sunshine" -arch=('x86_64' 'i686') -license=('GPL3') - -depends=('boost-libs' 'ffmpeg4.4' 'openssl' 'libpulse' 'opus' 'libxtst' 'libx11' 'libxfixes' 'libevdev' 'libxcb' 'libxrandr' 'udev') -makedepends=('git' 'cmake' 'boost' 'make') - -# source=("$pkgname::git+https://github.com/SunshineStream/sunshine.git#tag=v$pkgver" -# "systemd-user-config.patch" -# "udev.rules") -source=("$pkgname::git+https://github.com/SunshineStream/sunshine.git#tag=v$pkgver") -# sha256sums=('SKIP' -# '1642eb8672b137e94aa16e4aadde37f68bf1920dfadd1325cca480d7731f38c9' -# '5ce01689247cb01d3f119cac32c731607d99bb875dcdd39c92b547f76d2befa0') -sha256sums=('SKIP') -# install=sunshine.install - -_assets_path=/usr/share/$pkgname - -prepare() { - cd "$pkgname" - git submodule update --recursive --init - - # patch -p1 < ../systemd-user-config.patch -} - -build() { - export CFLAGS="${CFLAGS/-Werror=format-security/}" - export CXXFLAGS="${CXXFLAGS/-Werror=format-security/}" - - cmake \ - -S "$pkgname" \ - -B build \ - -Wno-dev \ - -D SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ - -D SUNSHINE_ASSETS_DIR="$_assets_path" \ - \ - -D LIBAVCODEC_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ - -D LIBAVCODEC_LIBRARIES=/usr/lib/ffmpeg4.4/libavcodec.so \ - -D LIBAVDEVICE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ - -D LIBAVDEVICE_LIBRARIES=/usr/lib/ffmpeg4.4/libavdevice.so \ - -D LIBAVFORMAT_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ - -D LIBAVFORMAT_LIBRARIES=/usr/lib/ffmpeg4.4/libavformat.so \ - -D LIBAVUTIL_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ - -D LIBAVUTIL_LIBRARIES=/usr/lib/ffmpeg4.4/libavutil.so \ - -D LIBSWSCALE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ - -D LIBSWSCALE_LIBRARIES=/usr/lib/ffmpeg4.4/libswscale.so - - make -C build -} - -package() { - pushd "$pkgname/assets" - install -Dvm644 sunshine.conf "$pkgdir/$_assets_path/sunshine.conf" - install -Dvm644 apps_linux.json "$pkgdir/$_assets_path/apps_linux.json" - - find web shaders/opengl -type f -print0 | xargs -0 -I {} install -Dvm644 {} "$pkgdir/$_assets_path/{}" - popd - - pushd build - install -Dvm755 sunshine "$pkgdir/usr/bin/sunshine" - install -Dvm644 sunshine.service "$pkgdir/usr/lib/systemd/user/sunshine.service" - popd - - # install -Dvm644 udev.rules "$pkgdir/usr/lib/udev/rules.d/85-sunshine.rules" -} - -post_install() { - if ! getent group input > /dev/null; then - echo "Creating group input" - groupadd -r input - fi -} - -# vim: ts=2 sw=2 et: diff --git a/packaging/linux/aur/tmp/sunshine.install b/packaging/linux/aur/tmp/sunshine.install deleted file mode 100644 index d10a7a04..00000000 --- a/packaging/linux/aur/tmp/sunshine.install +++ /dev/null @@ -1,6 +0,0 @@ -post_install() { - if ! getent group input > /dev/null; then - echo "Creating group input" - groupadd -r input - fi -} diff --git a/packaging/linux/aur/tmp/systemd-user-config.patch b/packaging/linux/aur/tmp/systemd-user-config.patch deleted file mode 100644 index 3e872e3e..00000000 --- a/packaging/linux/aur/tmp/systemd-user-config.patch +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/sunshine.service.in b/sunshine.service.in -index c0c3828..fe45460 100644 ---- a/sunshine.service.in -+++ b/sunshine.service.in -@@ -2,7 +2,8 @@ - Description=Sunshine Gamestream Server for Moonlight - - [Service] --ExecStart=@SUNSHINE_EXECUTABLE_PATH@ -+ExecStartPre=/bin/sh -c "test -e %E/sunshine || cp -r '@SUNSHINE_ASSETS_DIR@' '%E/sunshine'" -+ExecStart=@SUNSHINE_EXECUTABLE_PATH@ %E/sunshine/sunshine.conf - - [Install] - WantedBy=graphical-session.target diff --git a/packaging/linux/aur/tmp/udev.rules b/packaging/linux/aur/tmp/udev.rules deleted file mode 100644 index d626ba3f..00000000 --- a/packaging/linux/aur/tmp/udev.rules +++ /dev/null @@ -1 +0,0 @@ -KERNEL=="uinput", GROUP="input", MODE="0660" From 54221ae93800b21e10377da56f30741cfdeaa1ba Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 20 Jun 2022 18:55:35 -0400 Subject: [PATCH 307/817] Use cached responses for AUR badges --- README.rst | 4 ++-- docs/source/about/third_party_packages.rst | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 0e24b0cd..bc2e01e0 100644 --- a/README.rst +++ b/README.rst @@ -60,8 +60,8 @@ Downloads :alt: GitHub Releases :target: https://github.com/SunshineStream/Sunshine/releases/latest -.. image:: https://img.shields.io/aur/version/sunshine-git?style=for-the-badge&logo=archlinux - :alt: AUR version +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=AUR&style=for-the-badge&query=$.results.0.NumVotes&url=https%3A%2F%2Fgithub.com%2FSunshineStream%2Fservice-repo%2Freleases%2Fdownload%2Fdaily%2Faur_sunshine-git.json&logo=archlinux + :alt: AUR votes :target: https://aur.archlinux.org/packages/sunshine-git .. comment diff --git a/docs/source/about/third_party_packages.rst b/docs/source/about/third_party_packages.rst index 7877e09c..99c7e96c 100644 --- a/docs/source/about/third_party_packages.rst +++ b/docs/source/about/third_party_packages.rst @@ -8,17 +8,14 @@ Third Party Packages AUR (Arch Linux User Repository) -------------------------------- -.. image:: https://img.shields.io/aur/version/sunshine?style=for-the-badge&logo=archlinux +.. image:: https://img.shields.io/badge/dynamic/json?color=orange&label=AUR&style=for-the-badge&prefix=v&query=$.results.0.Version&url=https%3A%2F%2Fgithub.com%2FSunshineStream%2Fservice-repo%2Freleases%2Fdownload%2Fdaily%2Faur_sunshine.json&logo=archlinux :alt: AUR version :target: https://aur.archlinux.org/packages/sunshine -.. image:: https://img.shields.io/aur/last-modified/sunshine?style=for-the-badge&logo=archlinux - :alt: AUR last modified - -.. image:: https://img.shields.io/aur/votes/sunshine?style=for-the-badge&logo=archlinux +.. image:: https://img.shields.io/badge/dynamic/json?color=yellowgreen&label=votes&style=for-the-badge&query=$.results.0.NumVotes&url=https%3A%2F%2Fgithub.com%2FSunshineStream%2Fservice-repo%2Freleases%2Fdownload%2Fdaily%2Faur_sunshine.json&logo=archlinux :alt: AUR votes -.. image:: https://img.shields.io/aur/maintainer/sunshine?style=for-the-badge&logo=archlinux +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=maintainer&style=for-the-badge&query=$.results.0.Maintainer&url=https%3A%2F%2Fgithub.com%2FSunshineStream%2Fservice-repo%2Freleases%2Fdownload%2Fdaily%2Faur_sunshine.json&logo=archlinux :alt: AUR maintainer Chocolatey From 2b76111e51432308f9913649e47ff596e284045f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 20 Jun 2022 19:44:20 -0400 Subject: [PATCH 308/817] Remove AUR from issue template config --- .github/ISSUE_TEMPLATE/config.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index c96ec16d..25157730 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,11 +1,8 @@ blank_issues_enabled: false contact_links: - - name: AUR Package Issue - url: https://aur.archlinux.org/packages/sunshine - about: AUR Package Issues should be discussed on the AUR - name: Github Discussions url: https://github.com/SunshineStream/Sunshine/discussions about: General discussion, support, feature requests and more! - name: Discord support - url: https://discord.com/invite/CGg5JxN + url: https://sunshinestream.github.io/discord_join about: Ask question about Sunshine in Discord From 8fc8884dbcae11dfbf92e15b4948692ccdea502b Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 20 Jun 2022 20:35:19 -0400 Subject: [PATCH 309/817] Update PKGBUILD - Add `avahi` as dependency - Order dependencies in alphabetical order - Use fast compilation for `make` command --- packaging/linux/aur/PKGBUILD | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packaging/linux/aur/PKGBUILD b/packaging/linux/aur/PKGBUILD index 6c6e31b3..ace00a5b 100644 --- a/packaging/linux/aur/PKGBUILD +++ b/packaging/linux/aur/PKGBUILD @@ -9,9 +9,9 @@ arch=('x86_64' 'i686') url=@PROJECT_HOMEPAGE_URL@ license=('GPL3') -depends=('boost-libs' 'ffmpeg4.4' 'libpulse' 'libevdev' 'libx11' 'libxcb' 'libxfixes' 'libxrandr' 'libxtst' 'openssl' 'opus' 'udev') -makedepends=('git' 'cmake' 'boost' 'make') -optdepends=('cuda' 'libdrm' 'libcap') +depends=('avahi' 'boost-libs' 'ffmpeg4.4' 'libevdev' 'libpulse' 'libx11' 'libxcb' 'libxfixes' 'libxrandr' 'libxtst' 'openssl' 'opus' 'udev') +makedepends=('boost' 'cmake' 'git' 'make') +optdepends=('cuda' 'libcap' 'libdrm') provides=(@SUNSHINE_AUR_PROVIDES@) conflicts=(@SUNSHINE_AUR_CONFLICTS@) @@ -47,7 +47,7 @@ build() { -D LIBSWSCALE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ -D LIBSWSCALE_LIBRARIES=/usr/lib/ffmpeg4.4/libswscale.so - make -C build + make -C build -j ${nproc} } package() { From 6b641495913f6d724c6116db1da1b1a54dde02ce Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 20 Jun 2022 22:53:40 -0400 Subject: [PATCH 310/817] Update Portfile - Add `compiler.cxx_standard` - Don't overwrite config files - Fix `maintainers` - Correct cmake configure arguments - Remove `destroot` - Use `git` instead of `github.setup` - Set revision to 0 - Add long description --- .github/workflows/CI.yml | 27 ++++++++++----- CMakeLists.txt | 4 +++ packaging/macos/Portfile | 71 ++++++++++++++++++---------------------- 3 files changed, 55 insertions(+), 47 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 3da5a4b0..66e7d6da 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -9,6 +9,17 @@ on: workflow_dispatch: jobs: + github_env: + name: GitHub Env Debug + runs-on: ubuntu-latest + + steps: + - name: Dump github context + run: echo "$GITHUB_CONTEXT" + shell: bash + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + check_changelog: name: Check Changelog runs-on: ubuntu-latest @@ -438,28 +449,25 @@ jobs: - name: Configure Portfile run: | # variables for Portfile - owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]' ) - repo=$(echo ${GITHUB_REPOSITORY#*/} | tr '[:upper:]' '[:lower:]' ) branch=${GITHUB_HEAD_REF} - commit=${{ github.event.pull_request.head.sha }} # check the branch variable if [ -z "$branch" ] then echo "This is a PUSH event" - branch=branch=${{ github.ref_name }} commit=${{ github.sha }} + clone_url=${{ github.event.repository.clone_url }} else echo "This is a PR event" + commit=${{ github.event.pull_request.head.sha }} + clone_url=${{ github.event.pull_request.head.repo.clone_url }} fi - echo "Owner: ${owner}" - echo "Repo: ${repo}" - echo "Branch: ${branch}" echo "Commit: ${commit}" + echo "Clone URL: ${clone_url}" mkdir build cd build - cmake -DGITHUB_OWNER=${owner} -DGITHUB_REPO=${repo} -DGITHUB_BRANCH=${branch} -DSUNSHINE_CONFIGURE_PORTFILE=ON -DSUNSHINE_CONFIGURE_ONLY=ON .. + cmake -DGITHUB_COMMIT=${commit} -DGITHUB_CLONE_URL=${clone_url} -DSUNSHINE_CONFIGURE_PORTFILE=ON -DSUNSHINE_CONFIGURE_ONLY=ON .. cd .. @@ -470,6 +478,9 @@ jobs: # copy Portfile to ports mkdir -p ./ports/multimedia/Sunshine cp -f ./build/Portfile ./ports/multimedia/Sunshine/Portfile + + # testing + cat ./artifacts/Portfile - name: Bootstrap MacPorts run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index d37fc1fd..884c14dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,10 @@ project(Sunshine VERSION 0.14.0 HOMEPAGE_URL "https://sunshinestream.github.io" ) +set(PROJECT_LONG_DESCRIPTION "Sunshine is a self hosted, low latency, cloud gaming solution with support for AMD, \ +Intel, and Nvidia gpus. It is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield. \ +Connect to Sunshine from any Moonlight client, available for nearly any device imaginable.") + option(SUNSHINE_CONFIGURE_APPIMAGE "Configure files required for AppImage." OFF) option(SUNSHINE_CONFIGURE_FLATPAK "Configure files required for Flatpak." OFF) option(SUNSHINE_CONFIGURE_PORTFILE "Configure MacOS Portfile." OFF) diff --git a/packaging/macos/Portfile b/packaging/macos/Portfile index 371b5cdf..4bb28b7b 100644 --- a/packaging/macos/Portfile +++ b/packaging/macos/Portfile @@ -1,26 +1,32 @@ # -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4 +# initial PR into macports: https://github.com/macports/macports-ports/pull/15143 + PortSystem 1.0 PortGroup cmake 1.1 PortGroup github 1.0 PortGroup boost 1.0 -# bump revision when changes are made to this file -revision 1 - -github.setup @GITHUB_OWNER@ @GITHUB_REPO@ @GITHUB_BRANCH@ name @PROJECT_NAME@ version @PROJECT_VERSION@ +revision 0 categories multimedia emulators games platforms darwin license GPL-3 -maintainers {@SunshineStream sunshinestream} +maintainers @SunshineStream description @PROJECT_DESCRIPTION@ -long_description {*}${description} + +# long_description will not be split into multiple lines as it's configured by CMakeLists +long_description @PROJECT_LONG_DESCRIPTION@ homepage @PROJECT_HOMEPAGE_URL@ -master_sites https://github.com/@GITHUB_OWNER@/@GITHUB_REPO@/releases +master_sites https://github.com/sunshinestream/sunshine/releases +compiler.cxx_standard 2017 fetch.type git + +git.url @GITHUB_CLONE_URL@ +git.branch @GITHUB_COMMIT@ + post-fetch { system -W ${worksrcpath} "${git.cmd} submodule update --init --recursive" } @@ -31,11 +37,9 @@ depends_lib port:avahi \ boost.version 1.76 -configure.args -DBOOST_ROOT=[boost::install_area] \ - -DSUNSHINE_ASSETS_DIR=${prefix}/etc/sunshine/assets \ - -DSUNSHINE_CONFIG_DIR=${prefix}/etc/sunshine/config - -cmake.out_of_source yes +configure.args -DCMAKE_INSTALL_PREFIX=${prefix} \ + -DSUNSHINE_ASSETS_DIR=etc/sunshine/assets \ + -DSUNSHINE_CONFIG_DIR=etc/sunshine/config startupitem.create yes startupitem.executable "${prefix}/bin/{$name}" @@ -50,33 +54,22 @@ platform darwin { } } -# is this actually necessary? this should all be handled by CMakeLists -destroot { - # install assets - xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets - xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/*.*] ${destroot}${prefix}/etc/${name}/assets - xinstall {*}[glob ${worksrcpath}/src_assets/macos/assets/*.*] ${destroot}${prefix}/etc/${name}/assets - - # install web assets - xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web/css - xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web/webfonts - xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/*.*] ${destroot}${prefix}/etc/${name}/assets/web - xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/fonts/fontawesome-free-web/*.*] ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web - xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/fonts/fontawesome-free-web/css/*.*] ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web/css - xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/*.*] ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web/webfonts - xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets/web/images - xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets/web/third_party - xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/images/*.*] ${destroot}${prefix}/etc/${name}/assets/web/images - xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/third_party/*.*] ${destroot}${prefix}/etc/${name}/assets/web/third_party - - xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/config +# destroot not required as cmake install directive handles moving files - # install sunshine.conf - xinstall {*}[glob ${worksrcpath}/src_assets/common/config/*.*] ${destroot}${prefix}/etc/${name}/config - - # install apps.json - xinstall {*}[glob ${worksrcpath}/src_assets/macos/config/*.*] ${destroot}${prefix}/etc/${name}/config +# Rename files in `destroot` +post-destroot { + file rename ${destroot}${prefix}/etc/${name}/config/sunshine.conf ${destroot}${prefix}/etc/${name}/config/sunshine.conf.sample + file rename ${destroot}${prefix}/etc/${name}/config/apps.json ${destroot}${prefix}/etc/${name}/config/apps.json.sample +} - # install the binary - xinstall ${workpath}/build/${name} ${destroot}${prefix}/bin +# Don't overwrite existing preference files +post-activate { + if {![file exists ${prefix}/etc/${name}/config/sunshine.conf]} { + file copy ${destroot}${prefix}/etc/${name}/config/sunshine.conf.sample \ + ${prefix}/etc/${name}/config/sunshine.conf + } + if {![file exists ${prefix}/etc/${name}/config/apps.json]} { + file copy ${destroot}${prefix}/etc/${name}/config/apps.json.sample \ + ${prefix}/etc/${name}/config/apps.json + } } From 802e5c79fa822ca1e12b8c29db3f81c457712dee Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 22 Jun 2022 19:20:04 -0400 Subject: [PATCH 311/817] Add config option `SUNSHINE_MACOS_PACKAGE` - Refactor UNIX/APPLE packaging --- CMakeLists.txt | 84 +++++++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 884c14dc..5382a5e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -151,6 +151,9 @@ if(WIN32) set_source_files_properties(third-party/ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_FLAGS "-Wno-unknown-pragmas -Wno-misleading-indentation -Wno-class-memaccess") elseif(APPLE) add_compile_definitions(SUNSHINE_PLATFORM="macos") + + option(SUNSHINE_MACOS_PACKAGE "Should only be used when creating a MACOS package/dmg." OFF) + link_directories(/opt/local/lib) link_directories(/usr/local/lib) ADD_DEFINITIONS(-DBOOST_LOG_DYN_LINK) @@ -560,57 +563,62 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h set(CPACK_COMPONENT_SUNSHINESVC_DESCRIPTION "CLI tool that allows you to enable/disable the Sunshine service.") set(CPACK_COMPONENT_SUNSHINESVC_GROUP "Tools") endif() -if(UNIX) - # Installation destination dir - set(CPACK_SET_DESTDIR true) - if(NOT CMAKE_INSTALL_PREFIX) - set(CMAKE_INSTALL_PREFIX "/usr/local/sunshine") - endif() - - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") - - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") +if(APPLE) + # TODO: bundle doesn't produce a valid .app use cpack -G DragNDrop + set(CPACK_BUNDLE_NAME "${CMAKE_PROJECT_NAME}") + set(CPACK_BUNDLE_PLIST "${APPLE_PLIST_FILE}") + set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/sunshine.icns") + # set(CPACK_BUNDLE_STARTUP_COMMAND "${INSTALL_RUNTIME_DIR}/sunshine") endif() -if(APPLE) # TODO: test - +if(APPLE AND SUNSHINE_MACOS_PACKAGE) # TODO set(prefix "${CMAKE_PROJECT_NAME}.app/Contents") set(INSTALL_RUNTIME_DIR "${prefix}/MacOS") - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") install(TARGETS sunshine BUNDLE DESTINATION . COMPONENT Runtime RUNTIME DESTINATION ${INSTALL_RUNTIME_DIR} COMPONENT Runtime) +elseif(UNIX) + # Installation destination dir + set(CPACK_SET_DESTDIR true) + if(NOT CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_PREFIX "/usr/local/sunshine") + endif() - # TODO: bundle doesn't produce a valid .app use cpack -G DragNDrop - set(CPACK_BUNDLE_NAME "${CMAKE_PROJECT_NAME}") - set(CPACK_BUNDLE_PLIST "${APPLE_PLIST_FILE}") - set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/sunshine.icns") - # set(CPACK_BUNDLE_STARTUP_COMMAND "${INSTALL_RUNTIME_DIR}/sunshine") -endif() -if(UNIX AND NOT APPLE) - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") - - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") + install(TARGETS sunshine RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") - install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine-rules.rules" DESTINATION "/etc/udev/rules.d") + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") - install(TARGETS sunshine RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "${CMAKE_INSTALL_LIBDIR}/systemd/user") - - # Pre and post install - set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA - "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/preinst;${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst;${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/conffiles") - set(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/preinst") - set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst") - - # Dependencies - set(CPACK_DEB_COMPONENT_INSTALL ON) - set(CPACK_DEBIAN_PACKAGE_DEPENDS "openssl, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") - set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.1, libavdevice >= 4.3, boost-thread >= 1.67.0, boost-filesystem >= 1.67.0, boost-log >= 1.67.0, pulseaudio-libs >= 10.0, libopusenc >= 0.2.1, libxcb >= 1.13, libXtst >= 1.2.3, libevdev >= 1.5.6, libdrm >= 2.4.97, libcap >= 2.22") - set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # This should automatically figure out dependencies, doesn't work with the current config + if(APPLE) + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") + else() + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") + + install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine-rules.rules" DESTINATION "/etc/udev/rules.d") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "${CMAKE_INSTALL_LIBDIR}/systemd/user") + + # Pre and post install + set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA + "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/preinst" + "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst" + "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/conffiles") + set(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/preinst") + set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst") + + # Dependencies + set(CPACK_DEB_COMPONENT_INSTALL ON) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "openssl, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") + set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.1, libavdevice >= 4.3, boost-thread >= 1.67.0, boost-filesystem >= 1.67.0, boost-log >= 1.67.0, pulseaudio-libs >= 10.0, libopusenc >= 0.2.1, libxcb >= 1.13, libXtst >= 1.2.3, libevdev >= 1.5.6, libdrm >= 2.4.97, libcap >= 2.22") + set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # This should automatically figure out dependencies, doesn't work with the current config + endif() endif() include(CPack) From c61b31e8a6732977d11c6304014d69172eac012d Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 22 Jun 2022 21:42:10 -0400 Subject: [PATCH 312/817] Create uninstall_pkg.sh --- CMakeLists.txt | 2 ++ src_assets/macos/misc/uninstall_pkg.sh | 39 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src_assets/macos/misc/uninstall_pkg.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 5382a5e7..ba27a8a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -598,6 +598,8 @@ elseif(UNIX) if(APPLE) install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") + + install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/misc/uninstall_pkg.sh" DESTINATION "${SUNSHINE_ASSETS_DIR}") else() install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") diff --git a/src_assets/macos/misc/uninstall_pkg.sh b/src_assets/macos/misc/uninstall_pkg.sh new file mode 100644 index 00000000..8cc7f62c --- /dev/null +++ b/src_assets/macos/misc/uninstall_pkg.sh @@ -0,0 +1,39 @@ +#!/bin/bash -e +set -e + +FILES=(pkgutil --files org.macports.Sunshine) + +remove_config=True +remove_apps=True + +for n in {1..2}; do + echo "Loop: $n" + for file in "${FILES[@]}"; do + if [[ $file == *sunshine.conf ]]; then + if [ $remove_config == True ]; then + while true; do + read -p -r "Do you wish to remove 'sunshine.conf'?" yn + case $yn in + [Yy]* ) rm --force "$file"; break;; + [Nn]* ) remove_config=False; break;; + * ) echo "Please answer yes or no.";; + esac + done + fi + fi + if [[ $file == *apps.json ]]; then + if [ $remove_apps == True ]; then + while true; do + read -p -r "Do you wish to remove 'apps.conf'?" yn + case $yn in + [Yy]* ) rm --force "$file"; break;; + [Nn]* ) remove_apps=False; break;; + * ) echo "Please answer yes or no.";; + esac + done + fi + fi + + rm --force --dir "$file" + done +done From a1d8cc22968eaade1fada30ce61f8cd6fce336ef Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 22 Jun 2022 21:43:18 -0400 Subject: [PATCH 313/817] Update installation.rst --- docs/source/about/installation.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 9973b820..e175bd99 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -99,6 +99,16 @@ RPM Package MacOS ----- +Requirements + .. table:: + :widths: auto + + =========== ============= + requirement reason + =========== ============= + macOS 10.8+ Video Toolbox + =========== ============= + .. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:macos?logo=github&style=for-the-badge :alt: GitHub issues by-label From 7019756fa1ed368f1f9d79cd37c25f98bde01df0 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 23 Jun 2022 20:42:18 -0400 Subject: [PATCH 314/817] Fix macos uninstall pkg script --- src_assets/macos/misc/uninstall_pkg.sh | 75 ++++++++++++++++---------- 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/src_assets/macos/misc/uninstall_pkg.sh b/src_assets/macos/misc/uninstall_pkg.sh index 8cc7f62c..69f31df1 100644 --- a/src_assets/macos/misc/uninstall_pkg.sh +++ b/src_assets/macos/misc/uninstall_pkg.sh @@ -1,39 +1,58 @@ #!/bin/bash -e set -e -FILES=(pkgutil --files org.macports.Sunshine) +package_name=org.macports.Sunshine + +echo "Removing files now..." +FILES=$(pkgutil --files --only-files $package_name) remove_config=True remove_apps=True -for n in {1..2}; do - echo "Loop: $n" - for file in "${FILES[@]}"; do - if [[ $file == *sunshine.conf ]]; then - if [ $remove_config == True ]; then - while true; do - read -p -r "Do you wish to remove 'sunshine.conf'?" yn - case $yn in - [Yy]* ) rm --force "$file"; break;; - [Nn]* ) remove_config=False; break;; - * ) echo "Please answer yes or no.";; - esac - done - fi +for file in ${FILES}; do + file="/$file" + remove_current=True + if [[ $file == *sunshine.conf ]]; then + if [[ $remove_config == True ]]; then + while true; do + read -p -r "Do you wish to remove 'sunshine.conf'?" yn + case $yn in + [Yy]* ) echo "removing: $file"; rm -f "$file"; break;; + [Nn]* ) remove_config=False; remove_current=False; break;; + * ) echo "Please answer yes or no.";; + esac + done fi - if [[ $file == *apps.json ]]; then - if [ $remove_apps == True ]; then - while true; do - read -p -r "Do you wish to remove 'apps.conf'?" yn - case $yn in - [Yy]* ) rm --force "$file"; break;; - [Nn]* ) remove_apps=False; break;; - * ) echo "Please answer yes or no.";; - esac - done - fi + fi + if [[ $file == *apps.json ]]; then + if [[ $remove_apps == True ]]; then + while true; do + read -p -r "Do you wish to remove 'apps.conf'?" yn + case $yn in + [Yy]* ) echo "removing: $file"; rm -f "$file"; break;; + [Nn]* ) remove_apps=False; remove_current=False; break;; + * ) echo "Please answer yes or no.";; + esac + done fi + fi + + if [[ $remove_current == True ]]; then + echo "removing: $file" + rm -f "$file" + fi +done - rm --force --dir "$file" - done +echo "Removing directories now..." +DIRECTORIES=$(pkgutil --files --only-dirs org.macports.Sunshine) + +for dir in ${DIRECTORIES}; do + dir="/$dir" + echo "Checking if empty directory: $dir" + find "$dir" -type d -empty -exec rm -f -d {} \; done + +echo "Forgetting Sunshine..." +pkgutil --forget $package_name + +echo "Sunshine has been uninstalled..." From 46dede3381babb59ea17b3c2b6d7c323fd176840 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 23 Jun 2022 22:58:27 -0400 Subject: [PATCH 315/817] Disable matrix for Macports - Currently cannot build Sunshine on macOS < 10.8 due to missing video toolbox framework --- .github/workflows/CI.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 66e7d6da..af348f7b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -418,11 +418,12 @@ jobs: build_mac_port: name: Macports needs: check_changelog - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ macos-10.15, macos-11, macos-12 ] + runs-on: macos-11 +# runs-on: ${{ matrix.os }} +# strategy: +# fail-fast: false +# matrix: +# os: [ macos-10.15, macos-11, macos-12 ] steps: - name: Checkout @@ -616,7 +617,7 @@ jobs: - name: Upload Artifacts uses: actions/upload-artifact@v3 with: - name: sunshine-macports-${{ matrix.os }} + name: sunshine-macports path: artifacts/ - name: Create Release From 270d4ddffed16bf403b00537bef1510d27ab3957 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 23 Jun 2022 23:36:06 -0400 Subject: [PATCH 316/817] Fix paths for AUR and... - Rename udev rules file - Refactor CI to properly collect clone url for PRs (AUR only) --- .github/workflows/CI.yml | 30 +++++++------------ CMakeLists.txt | 2 +- docs/source/about/usage.rst | 2 +- packaging/linux/aur/PKGBUILD | 8 ++--- ...sunshine-rules.rules => 85-sunshine.rules} | 0 5 files changed, 17 insertions(+), 25 deletions(-) rename src_assets/linux/misc/{85-sunshine-rules.rules => 85-sunshine.rules} (100%) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 1782cffc..18fb421b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -70,56 +70,48 @@ jobs: - name: Configure PKGBUILD files run: | # variables for manifest - owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]' ) - repo=$(echo ${GITHUB_REPOSITORY#*/} | tr '[:upper:]' '[:lower:]' ) - branch=${GITHUB_HEAD_REF} - commit=${{ github.event.pull_request.head.sha }} - echo "aur_publish=false" >> $GITHUB_ENV aur_pkg=sunshine-dev - fragment="" sub_version="" conflicts="'sunshine'" provides="'sunshine'" + branch=${GITHUB_HEAD_REF} + # check the branch variable if [ -z "$branch" ] then echo "This is a PUSH event" - branch=branch=${{ github.ref_name }} commit=${{ github.sha }} + clone_url=${{ github.event.repository.clone_url }} - if [ ${{ github.ref == 'refs/heads/master' }} ] - then + if [[ ${{ github.ref == 'refs/heads/master' }} ]]; then aur_pkg=sunshine-git # conflicts="" # provides="" echo "aur_publish=true" >> $GITHUB_ENV - elif [ ${{ github.ref == 'refs/heads/nightly' }} ] - then + elif [[ ${{ github.ref == 'refs/heads/nightly' }} ]]; then aur_pkg=sunshine-nightly 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_pkg=${aur_pkg}" >> $GITHUB_ENV - fragment="#commit=${commit}" - - echo "Owner: ${owner}" - echo "Repo: ${repo}" - echo "Branch: ${branch}" - echo "Commit: ${commit}" - 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} -DSUNSHINE_AUR_FRAGMENT=${fragment} -DGITHUB_OWNER=${owner} -DGITHUB_REPO=${repo} -DSUNSHINE_CONFIGURE_ONLY=ON .. + 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/ diff --git a/CMakeLists.txt b/CMakeLists.txt index e74c7b9f..02ddfadb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -594,7 +594,7 @@ if(UNIX AND NOT APPLE) install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") - install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine-rules.rules" DESTINATION "/etc/udev/rules.d") + install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine.rules" DESTINATION "${CMAKE_INSTALL_LIBDIR}/udev/rules.d") install(TARGETS sunshine RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "${CMAKE_INSTALL_LIBDIR}/systemd/user") diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst index 8ae0287c..b29873da 100644 --- a/docs/source/about/usage.rst +++ b/docs/source/about/usage.rst @@ -68,7 +68,7 @@ Sunshine needs access to `uinput` to create mouse and gamepad events. #. Create `udev` rules. .. code-block:: bash - sudo nano /etc/udev/rules.d/85-sunshine-input.rules + sudo nano /etc/udev/rules.d/85-sunshine.rules Input the following contents. diff --git a/packaging/linux/aur/PKGBUILD b/packaging/linux/aur/PKGBUILD index ace00a5b..be0e38fe 100644 --- a/packaging/linux/aur/PKGBUILD +++ b/packaging/linux/aur/PKGBUILD @@ -16,7 +16,7 @@ optdepends=('cuda' 'libcap' 'libdrm') provides=(@SUNSHINE_AUR_PROVIDES@) conflicts=(@SUNSHINE_AUR_CONFLICTS@) -source=("$pkgname::git+https://github.com/@GITHUB_OWNER@/@GITHUB_REPO@.git@SUNSHINE_AUR_FRAGMENT@") +source=("$pkgname::git+@GITHUB_CLONE_URL@#commit=@GITHUB_COMMIT@") sha256sums=('SKIP') prepare() { @@ -34,8 +34,8 @@ build() { -Wno-dev \ -D SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ -D CMAKE_INSTALL_PREFIX="/usr" \ - -D SUNSHINE_ASSETS_DIR="local/sunshine/assets" \ - -D SUNSHINE_CONFIG_DIR="local/sunshine/config" \ + -D SUNSHINE_ASSETS_DIR="share/sunshine/assets" \ + -D SUNSHINE_CONFIG_DIR="share/sunshine/config" \ -D LIBAVCODEC_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ -D LIBAVCODEC_LIBRARIES=/usr/lib/ffmpeg4.4/libavcodec.so \ -D LIBAVDEVICE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ @@ -47,7 +47,7 @@ build() { -D LIBSWSCALE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ -D LIBSWSCALE_LIBRARIES=/usr/lib/ffmpeg4.4/libswscale.so - make -C build -j ${nproc} + make -C build } package() { diff --git a/src_assets/linux/misc/85-sunshine-rules.rules b/src_assets/linux/misc/85-sunshine.rules similarity index 100% rename from src_assets/linux/misc/85-sunshine-rules.rules rename to src_assets/linux/misc/85-sunshine.rules From 25e21ee807b13c822b3406ad8fabe101d127f750 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 25 Jun 2022 19:10:47 -0400 Subject: [PATCH 317/817] Fix removing directories for macos uninstall_pkg --- src_assets/macos/misc/uninstall_pkg.sh | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src_assets/macos/misc/uninstall_pkg.sh b/src_assets/macos/misc/uninstall_pkg.sh index 69f31df1..7e136109 100644 --- a/src_assets/macos/misc/uninstall_pkg.sh +++ b/src_assets/macos/misc/uninstall_pkg.sh @@ -4,7 +4,7 @@ set -e package_name=org.macports.Sunshine echo "Removing files now..." -FILES=$(pkgutil --files --only-files $package_name) +FILES=$(pkgutil --files $package_name --only-files) remove_config=True remove_apps=True @@ -44,12 +44,22 @@ for file in ${FILES}; do done echo "Removing directories now..." -DIRECTORIES=$(pkgutil --files --only-dirs org.macports.Sunshine) +DIRECTORIES=$(pkgutil --files org.macports.Sunshine --only-dirs) for dir in ${DIRECTORIES}; do dir="/$dir" echo "Checking if empty directory: $dir" - find "$dir" -type d -empty -exec rm -f -d {} \; + + # check if directory is empty... could just use ${DIRECTORIES} here if pkgutils added the `/` prefix + empty_dir=$(find "$dir" -depth 0 -type d -empty) + + # remove the directory if it is empty + if [[ $empty_dir != "" ]]; then # prevent the loop from running and failing if no directories found + for i in "${empty_dir}"; do # don't split words as we already know this will be a single directory + echo "Removing empty directory: ${i}" + rmdir "${i}" + done + fi done echo "Forgetting Sunshine..." From 4f07672cfa5af859691f3340f8c2f18dab12fb22 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 25 Jun 2022 20:02:40 -0400 Subject: [PATCH 318/817] CI cleanup --- .github/workflows/CI.yml | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index af348f7b..c3ee11fd 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -374,10 +374,6 @@ jobs: - name: Package MacOS run: | - # remove cached artifacts - rm -r -f ./artifacts/ - mkdir artifacts - mkdir -p artifacts cd build @@ -406,14 +402,15 @@ jobs: rm -f ./sunshine-macos-experimental-bundle.dmg rm -f ./sunshine-macos-experimental-archive.zip - - name: Create Release - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: SunshineStream/actions/create_release@master - with: - token: ${{ secrets.GITHUB_TOKEN }} - next_version: ${{ needs.check_changelog.outputs.next_version }} - last_version: ${{ needs.check_changelog.outputs.last_version }} - release_body: ${{ needs.check_changelog.outputs.release_body }} + # no artifacts to release currently +# - name: Create Release +# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} +# uses: SunshineStream/actions/create_release@master +# with: +# token: ${{ secrets.GITHUB_TOKEN }} +# next_version: ${{ needs.check_changelog.outputs.next_version }} +# last_version: ${{ needs.check_changelog.outputs.last_version }} +# release_body: ${{ needs.check_changelog.outputs.release_body }} build_mac_port: name: Macports @@ -601,9 +598,6 @@ jobs: work=$(port work sunshine) echo "Sunshine port work directory: ${work}" - # testing - ls ${work} - # move components out of port work directory sudo mv ${work}/Sunshine*component.pkg /tmp/ From 3cd3d261e97f43a7f2873494200da8dd6fe8ad31 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 28 Jun 2022 19:16:24 -0400 Subject: [PATCH 319/817] Update macOS documentation --- CMakeLists.txt | 4 ++-- README.rst | 15 +++++++++------ docs/source/about/advanced_usage.rst | 22 +++++++++++----------- docs/source/about/installation.rst | 9 ++++++--- docs/source/about/usage.rst | 4 ++-- docs/source/building/build.rst | 2 +- docs/source/building/macos.rst | 6 +++--- docs/source/troubleshooting/macos.rst | 17 ++++++++++++++++- sunshine/platform/common.h | 2 +- 9 files changed, 51 insertions(+), 30 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba27a8a5..86a68db0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,12 +6,12 @@ project(Sunshine VERSION 0.14.0 ) set(PROJECT_LONG_DESCRIPTION "Sunshine is a self hosted, low latency, cloud gaming solution with support for AMD, \ -Intel, and Nvidia gpus. It is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield. \ +Intel, and Nvidia GPUs. It is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield. \ Connect to Sunshine from any Moonlight client, available for nearly any device imaginable.") option(SUNSHINE_CONFIGURE_APPIMAGE "Configure files required for AppImage." OFF) option(SUNSHINE_CONFIGURE_FLATPAK "Configure files required for Flatpak." OFF) -option(SUNSHINE_CONFIGURE_PORTFILE "Configure MacOS Portfile." OFF) +option(SUNSHINE_CONFIGURE_PORTFILE "Configure macOS Portfile." OFF) option(SUNSHINE_CONFIGURE_ONLY "Configure special files only, then exit." OFF) if(${SUNSHINE_CONFIGURE_APPIMAGE}) diff --git a/README.rst b/README.rst index 24d0c161..95408df8 100644 --- a/README.rst +++ b/README.rst @@ -6,19 +6,22 @@ SunshineStream has the full documentation hosted on `Read the Docs `_ or `BlackHole `_. @@ -395,7 +395,7 @@ Examples audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo - MacOS + macOS .. code-block:: text audio_sink = BlackHole 2ch @@ -993,7 +993,7 @@ vt_software Description Force Video Toolbox to use software encoding. - .. Note:: This option only applies when using MacOS. + .. Note:: This option only applies when using macOS. **Choices** @@ -1023,7 +1023,7 @@ vt_realtime Description Realtime encoding. - .. Note:: This option only applies when using MacOS. + .. Note:: This option only applies when using macOS. .. Warning:: Disabling realtime encoding might result in a delayed frame encoding or frame drop. @@ -1041,7 +1041,7 @@ vt_coder Description The entropy encoding to use. - .. Note:: This option only applies when using MacOS. + .. Note:: This option only applies when using macOS. **Choices** diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index e175bd99..ed8e9ac1 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -9,8 +9,6 @@ Binaries Binaries of Sunshine are created for each release. They are available for Linux, and Windows. Binaries can be found in the `latest release`_. -.. Todo:: Create binary package(s) for MacOS. See `here `_. - .. Tip:: Some third party packages also exist. See :ref:`Third Party Packages `. @@ -97,7 +95,7 @@ RPM Package .. Tip:: You can double click the rpm file to see details about the package and begin installation. -MacOS +macOS ----- Requirements .. table:: @@ -112,6 +110,11 @@ Requirements .. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:macos?logo=github&style=for-the-badge :alt: GitHub issues by-label +pkg + .. Warning:: The `pkg` does not include runtime dependencies and should be considered experimental. + + #. Download the ``sunshine.pkg`` file and install it as normal. + Portfile #. Install `MacPorts `_ #. Update the Macports sources. diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst index 8ae0287c..92038cf9 100644 --- a/docs/source/about/usage.rst +++ b/docs/source/about/usage.rst @@ -27,7 +27,7 @@ Usage list of applications that are started just before running a stream. This is the directory within the GitHub repo. - .. Attention:: Application list is not fully supported on MacOS + .. Attention:: Application list is not fully supported on macOS #. In Moonlight, you may need to add the PC manually. #. When Moonlight request you insert the correct pin on sunshine: @@ -137,7 +137,7 @@ Sunshine needs access to `uinput` to create mouse and gamepad events. sudo reboot now -MacOS +macOS ^^^^^ Sunshine can only access microphones on macOS due to system limitations. To stream system audio use `Soundflower `_ or diff --git a/docs/source/building/build.rst b/docs/source/building/build.rst index cd83cb14..a703959d 100644 --- a/docs/source/building/build.rst +++ b/docs/source/building/build.rst @@ -22,7 +22,7 @@ Compile See the section specific to your OS. - :ref:`Linux ` -- :ref:`MacOS ` +- :ref:`macOS ` - :ref:`Windows ` Remote Build diff --git a/docs/source/building/macos.rst b/docs/source/building/macos.rst index 51ae6b15..d20a5517 100644 --- a/docs/source/building/macos.rst +++ b/docs/source/building/macos.rst @@ -1,11 +1,11 @@ :github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/building/macos.rst -MacOS +macOS ===== Requirements ------------ -MacOS Big Sur and Xcode 12.5+ +macOS Big Sur and Xcode 12.5+ Use either `MacPorts `_ or `Homebrew `_ @@ -35,7 +35,7 @@ Build cmake .. make -j ${nproc} - cpack -G DragNDrop # optionally, create a MacOS dmg package + cpack -G DragNDrop # optionally, create a macOS dmg package If cmake fails complaining to find Boost, try to set the path explicitly. diff --git a/docs/source/troubleshooting/macos.rst b/docs/source/troubleshooting/macos.rst index 18ea9361..5a6ae658 100644 --- a/docs/source/troubleshooting/macos.rst +++ b/docs/source/troubleshooting/macos.rst @@ -1,6 +1,6 @@ :github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/troubleshooting/macos.rst -MacOS +macOS ===== If you get this error: @@ -12,3 +12,18 @@ If you get this error: .. code-block:: bash launchctl load -w /Library/LaunchAgents/org.freedesktop.dbus-session.plist + +Uninstall: + + - pkg + + .. code-block:: bash + + sudo chmod +x /opt/local/etc/sunshine/assets/uninstall_pkg.sh + sudo /opt/local/etc/sunshine/assets/uninstall_pkg.sh + + - Portfile + + .. code-block:: bash + + sudo port uninstall Sunshine diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index 4d1588a3..ef9a2cf6 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -161,7 +161,7 @@ struct sink_t { // Play on host PC std::string host; - // On MacOS and Windows, it is not possible to create a virtual sink + // On macOS and Windows, it is not possible to create a virtual sink // Therefore, it is optional struct null_t { std::string stereo; From 00405892cb26d84680b675ebdb2249ea7a644002 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 28 Jun 2022 19:26:34 -0400 Subject: [PATCH 320/817] Don't rename default config files --- packaging/macos/Portfile | 41 ++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/packaging/macos/Portfile b/packaging/macos/Portfile index 4bb28b7b..061027e9 100644 --- a/packaging/macos/Portfile +++ b/packaging/macos/Portfile @@ -56,20 +56,29 @@ platform darwin { # destroot not required as cmake install directive handles moving files -# Rename files in `destroot` -post-destroot { - file rename ${destroot}${prefix}/etc/${name}/config/sunshine.conf ${destroot}${prefix}/etc/${name}/config/sunshine.conf.sample - file rename ${destroot}${prefix}/etc/${name}/config/apps.json ${destroot}${prefix}/etc/${name}/config/apps.json.sample -} +# # Rename files in `destroot` +# post-destroot { +# file rename ${destroot}${prefix}/etc/${name}/config/sunshine.conf ${destroot}${prefix}/etc/${name}/config/sunshine.conf.sample +# file rename ${destroot}${prefix}/etc/${name}/config/apps.json ${destroot}${prefix}/etc/${name}/config/apps.json.sample +# } -# Don't overwrite existing preference files -post-activate { - if {![file exists ${prefix}/etc/${name}/config/sunshine.conf]} { - file copy ${destroot}${prefix}/etc/${name}/config/sunshine.conf.sample \ - ${prefix}/etc/${name}/config/sunshine.conf - } - if {![file exists ${prefix}/etc/${name}/config/apps.json]} { - file copy ${destroot}${prefix}/etc/${name}/config/apps.json.sample \ - ${prefix}/etc/${name}/config/apps.json - } -} +# # Don't overwrite existing preference files +# post-activate { +# if {![file exists ${prefix}/etc/${name}/config/sunshine.conf]} { +# file copy ${destroot}${prefix}/etc/${name}/config/sunshine.conf.sample \ +# ${prefix}/etc/${name}/config/sunshine.conf +# } +# if {![file exists ${prefix}/etc/${name}/config/apps.json]} { +# file copy ${destroot}${prefix}/etc/${name}/config/apps.json.sample \ +# ${prefix}/etc/${name}/config/apps.json +# } +# } + +# disabled not overwriting config files... these are the default config files required by Sunshine +# this did not work with pkg created by macports +# we should always install the default files and user should start sunshine like "sunshine " +# if the file doesn't exist sunshine will copy the default config to that location +notes-append "Run @PROJECT_NAME@ by executing 'sunshine ', e.g. 'sunshine ~/sunshine.conf' " +notes-append "The config file will be created if it doesn't exist." +notes-append "It is recommended to set a location for the apps file in the config." +notes-append "See our documentation at 'https://sunshinestream.readthedocs.io/en/v@PROJECT_VERSION@/' for further info." From 3b2226c4ea8183748840e601d5fc9c7c17488df0 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 28 Jun 2022 20:39:33 -0400 Subject: [PATCH 321/817] Add paths for aur package --- docs/source/about/advanced_usage.rst | 18 ++++++++++-------- docs/source/about/usage.rst | 1 + 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index e0903223..3aefa0ea 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -17,14 +17,16 @@ location by modifying the configuration file. .. table:: :widths: auto - ======= =========== - Value Description - ======= =========== - Docker /config/ - Linux /usr/local/sunshine/config/ - MacOS /usr/local/sunshine/config/ - Windows ./config/ - ======= =========== + ========= =========== + Value Description + ========= =========== + Docker /config/ + Linux-aur /usr/share/sunshine/config/ + Linux-deb /usr/local/sunshine/config/ + Linux-rpm /usr/local/sunshine/config/ + MacOS /usr/local/sunshine/config/ + Windows ./config/ + ========= =========== Example .. code-block:: bash diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst index b29873da..a591cca5 100644 --- a/docs/source/about/usage.rst +++ b/docs/source/about/usage.rst @@ -102,6 +102,7 @@ Sunshine needs access to `uinput` to create mouse and gamepad events. ======== ============================================== =============== package ExecStart Auto Configured ======== ============================================== =============== + aur /usr/bin/sunshine ✔ deb /usr/bin/sunshine ✔ rpm /usr/bin/sunshine ✔ AppImage ~/sunshine.AppImage ✖ From 819501c4e7f7497bbf50908445cf8361e43666fb Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 28 Jun 2022 20:39:58 -0400 Subject: [PATCH 322/817] Use v2.3.0 of `KSXGitHub/github-actions-deploy-aur` action --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 18fb421b..f8c1c8d9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -134,7 +134,7 @@ jobs: - name: Publish AUR package if: ${{ env.aur_publish == 'true' }} - uses: KSXGitHub/github-actions-deploy-aur@master # assets arg not in latest release + uses: KSXGitHub/github-actions-deploy-aur@v2.3.0 with: pkgname: ${{ env.aur_pkg }} pkgbuild: ./artifacts/PKGBUILD From 8ba3c073e7432d5c8016666ab6f66f6c161d6d33 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 29 Jun 2022 19:08:14 -0400 Subject: [PATCH 323/817] Remove macOS requirements table --- docs/source/about/installation.rst | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 2b84c3f6..cbe5aca3 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -107,16 +107,6 @@ RPM Package macOS ----- -Requirements - .. table:: - :widths: auto - - =========== ============= - requirement reason - =========== ============= - macOS 10.8+ Video Toolbox - =========== ============= - .. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:macos?logo=github&style=for-the-badge :alt: GitHub issues by-label From fd0dc9ab8eac81424216131bd9661238ebe8bc89 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 29 Jun 2022 20:02:15 -0400 Subject: [PATCH 324/817] Fix flatpak CI for outside pull requests --- .github/workflows/CI.yml | 16 ++++++---------- .../com.github.sunshinestream.sunshine.yml | 3 ++- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a6ee7c3b..a24a891c 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -195,33 +195,29 @@ jobs: - name: Configure Flatpak Manifest run: | # variables for manifest - owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]' ) - repo=$(echo ${GITHUB_REPOSITORY#*/} | tr '[:upper:]' '[:lower:]' ) branch=${GITHUB_HEAD_REF} - commit=${{ github.event.pull_request.head.sha }} # check the branch variable if [ -z "$branch" ] then echo "This is a PUSH event" - branch=branch=${{ github.ref_name }} + branch=${{ github.ref_name }} commit=${{ github.sha }} + clone_url=${{ github.event.repository.clone_url }} else echo "This is a PR event" + commit=${{ github.event.pull_request.head.sha }} + clone_url=${{ github.event.pull_request.head.repo.clone_url }} fi - echo "Owner: ${owner}" - echo "Repo: ${repo}" echo "Branch: ${branch}" echo "Commit: ${commit}" + echo "Clone URL: ${clone_url}" mkdir -p build mkdir -p artifacts cd build - cmake -DGITHUB_OWNER=${owner} -DGITHUB_REPO=${repo} -DGITHUB_BRANCH=${branch} -DSUNSHINE_CONFIGURE_FLATPAK=ON -DSUNSHINE_CONFIGURE_ONLY=ON .. - - # add the commit - echo " commit: ${commit}" >> ./com.github.sunshinestream.sunshine.yml + cmake -DGITHUB_CLONE_URL=${clone_url} -DGITHUB_BRANCH=${branch} -DGITHUB_COMMIT=${commit} -DSUNSHINE_CONFIGURE_FLATPAK=ON -DSUNSHINE_CONFIGURE_ONLY=ON .. - name: Build Linux Flatpak working-directory: build diff --git a/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml b/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml index 08e47999..f2c297b6 100644 --- a/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml +++ b/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml @@ -206,5 +206,6 @@ modules: - -DSUNSHINE_ENABLE_CUDA=ON sources: - type: git - url: https://github.com/@GITHUB_OWNER@/@GITHUB_REPO@.git + url: @GITHUB_CLONE_URL@ branch: @GITHUB_BRANCH@ + commit: @GITHUB_COMMIT@ From d19c8830677036fbc48bfaaf113878e8d1ffc9fb Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Tue, 5 Jul 2022 19:27:14 -0400 Subject: [PATCH 325/817] Add CUDA to Flatpak (#218) --- .github/workflows/CI.yml | 30 ++------- .../com.github.sunshinestream.sunshine.yml | 67 ++++++++++--------- 2 files changed, 41 insertions(+), 56 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a24a891c..a99066a8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -159,38 +159,21 @@ jobs: build_linux_flatpak: name: Linux Flatpak - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest needs: check_changelog steps: - - name: Maximize build space - uses: easimon/maximize-build-space@master - with: - root-reserve-mb: 2048 - swap-size-mb: 8192 - remove-dotnet: 'true' - remove-android: 'true' - remove-haskell: 'true' - - - name: Cache flatpak-builder - uses: actions/cache@v3 - with: - path: ./build/.flatpak-builder/build - key: ${{ runner.os }}-flatpak-builder - - name: Checkout uses: actions/checkout@v3 - name: Setup Dependencies Linux Flatpak run: | - sudo add-apt-repository ppa:flatpak/stable -y sudo apt-get update -y sudo apt-get install -y \ cmake \ - flatpak \ - flatpak-builder - sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo - sudo flatpak install flathub org.freedesktop.Platform//21.08 org.freedesktop.Sdk//21.08 -y + flatpak + sudo su $(whoami) -c 'flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo' + sudo su $(whoami) -c 'flatpak install --user flathub org.flatpak.Builder org.freedesktop.Platform//21.08 org.freedesktop.Sdk//21.08 -y' - name: Configure Flatpak Manifest run: | @@ -222,9 +205,8 @@ jobs: - name: Build Linux Flatpak working-directory: build run: | - sudo flatpak-builder build-dir com.github.sunshinestream.sunshine.yml - sudo flatpak-builder --repo=repo --force-clean build-dir com.github.sunshinestream.sunshine.yml - sudo flatpak build-bundle ./repo ../artifacts/sunshine.flatpak com.github.sunshinestream.sunshine + sudo su $(whoami) -c 'flatpak run org.flatpak.Builder --repo=repo --force-clean build-sunshine com.github.sunshinestream.sunshine.yml' + sudo su $(whoami) -c 'flatpak build-bundle ./repo ../artifacts/sunshine.flatpak com.github.sunshinestream.sunshine' - name: Upload Artifacts uses: actions/upload-artifact@v3 diff --git a/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml b/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml index f2c297b6..4c16819d 100644 --- a/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml +++ b/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml @@ -11,7 +11,7 @@ finish-args: - --socket=pulseaudio - --share=network - --device=all - - --persist=. + - --persist=.config/sunshine - --system-talk-name=org.freedesktop.Avahi - --env=PULSE_PROP_media.category=Manager @@ -24,36 +24,37 @@ cleanup: - /share modules: -# - name: cuda -# buildsystem: simple -# only-arches: -# - x86_64 -# - aarch64 -# cleanup: -# - '*' -# build-commands: -# - chmod u+x ./cuda.run -# - ./cuda.run --silent --toolkit --toolkitpath=/app --no-opengl-libs --no-man-page --no-drm -# - rm ./cuda.run -# - rm -r /app/nsight-systems-2021.3.2 -# sources: -# - type: file -# only-arches: -# - x86_64 -# url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run -# sha256: bbd87ca0e913f837454a796367473513cddef555082e4d86ed9a38659cc81f0a -# dest-filename: cuda.run -# - type: file -# only-arches: -# - aarch64 -# url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux_sbsa.run -# sha256: f2c4a52e06329606c8dfb7c5ea3f4cb4c0b28f9d3fdffeeb734fcc98daf580d8 -# dest-filename: cuda.run + - name: cuda + disabled: false + buildsystem: simple + only-arches: + - x86_64 + - aarch64 + cleanup: + - '*' + build-commands: + - chmod u+x ./cuda.run + - ./cuda.run --silent --toolkit --toolkitpath=$FLATPAK_DEST/cuda --no-opengl-libs --no-man-page --no-drm --tmpdir=$FLATPAK_BUILDER_BUILDDIR + - rm -r $FLATPAK_DEST/cuda/nsight-systems-2021.3.2 + - rm ./cuda.run + sources: + - type: file + only-arches: + - x86_64 + url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run + sha256: bbd87ca0e913f837454a796367473513cddef555082e4d86ed9a38659cc81f0a + dest-filename: cuda.run + - type: file + only-arches: + - aarch64 + url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux_sbsa.run + sha256: f2c4a52e06329606c8dfb7c5ea3f4cb4c0b28f9d3fdffeeb734fcc98daf580d8 + dest-filename: cuda.run - name: boost buildsystem: simple build-commands: - - ./bootstrap.sh --prefix=/app --with-libraries=system,thread,log + - ./bootstrap.sh --prefix=$FLATPAK_DEST --with-libraries=system,thread,log - ./b2 install variant=release link=static,shared runtime-link=shared cxxflags="$CXXFLAGS" linkflags="$LDFLAGS" -j $FLATPAK_BUILDER_N_JOBS sources: - type: archive @@ -81,8 +82,9 @@ modules: # - --enable-nonfree # - --enable-cuda-nvcc # - --enable-libnpp - # - --extra-cflags=-I/app/include - # - --extra-ldflags=-L/app/lib64 + # - --extra-cflags=-I${FLATPAK_DEST}/cuda/include + # - --extra-ldflags=-L${FLATPAK_DEST}/cuda/lib64 + # - --nvccflags="-gencode arch=compute_52,code=sm_52 -O2" cleanup: - /share/ffmpeg/examples sources: @@ -118,10 +120,10 @@ modules: - type: archive url: https://bitbucket.org/multicoreware/x265_git/downloads/x265_3.5.tar.gz sha256: e70a3335cacacbba0b3a20ec6fecd6783932288ebc8163ad74bcc9606477cae8 - - name: nv-codec-headers + - name: ffnvcodec no-autogen: true make-install-args: - - PREFIX=/app + - PREFIX=${FLATPAK_DEST} cleanup: - '*' sources: @@ -196,7 +198,8 @@ modules: cxxflags: -I${C_INCLUDE_PATH}/libevdev-1.0 config-opts: - -DCMAKE_BUILD_TYPE=Release - - -DCMAKE_INSTALL_PREFIX='/app' + - -DCMAKE_INSTALL_PREFIX=/app + - -DCMAKE_CUDA_COMPILER=/app/cuda/bin/nvcc - -DSUNSHINE_ASSETS_DIR=assets - -DSUNSHINE_CONFIG_DIR=config - -DSUNSHINE_EXECUTABLE_PATH=/app/bin/sunshine From 7d4df19cdc560df3434dd141f984c797c734f047 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 29 Jul 2022 08:56:07 -0400 Subject: [PATCH 326/817] fix aur build (#273) * fix aur build * Update both aur repos --- .github/workflows/CI.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a99066a8..5448633f 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -97,14 +97,16 @@ jobs: clone_url=${{ github.event.repository.clone_url }} if [[ ${{ github.ref == 'refs/heads/master' }} ]]; then - aur_pkg=sunshine-git - # conflicts="" - # provides="" + aur_pkg=sunshine + conflicts="" + provides="" echo "aur_publish=true" >> $GITHUB_ENV elif [[ ${{ github.ref == 'refs/heads/nightly' }} ]]; then - aur_pkg=sunshine-nightly + aur_pkg=sunshine-git sub_version=".r${commit}" + + echo "aur_publish=true" >> $GITHUB_ENV fi else echo "This is a PR event" @@ -128,7 +130,9 @@ jobs: mv ./build/PKGBUILD ./artifacts/ - name: Validate package - uses: hapakaien/archlinux-package-action@v2 + # uses: hapakaien/archlinux-package-action@v2 + # the above action has an issue with the archlinux-keychain + uses: lizardbyte/archlinux-package-action@main with: path: artifacts flags: '--syncdeps --noconfirm' @@ -154,7 +158,7 @@ jobs: commit_username: ${{ secrets.AUR_USERNAME }} commit_email: ${{ secrets.AUR_EMAIL }} ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} - commit_message: Release ${{ needs.check_changelog.outputs.next_version }} + commit_message: Automatic update from GitHub ${{ github.repository }} per ${{ github.ref }} allow_empty_commits: false build_linux_flatpak: From da3c39e9e36bc8754cc02a60c8d1961f7e7dde9b Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 29 Jul 2022 09:45:26 -0400 Subject: [PATCH 327/817] change org to LizardByte (#263) - updates issue template - updates docker readme --- .github/ISSUE_TEMPLATE/bug-report.yml | 28 +++++++++++--- .github/workflows/CI.yml | 26 ++++++------- .github/workflows/localize.yml | 2 +- CMakeLists.txt | 12 +++--- DOCKER_README.md | 38 +++++++------------ README.rst | 34 ++++++++--------- docs/source/about/advanced_usage.rst | 2 +- docs/source/about/docker.rst | 2 +- docs/source/about/installation.rst | 22 +++++------ docs/source/about/third_party_packages.rst | 17 +-------- docs/source/about/usage.rst | 4 +- docs/source/building/build.rst | 4 +- docs/source/building/linux.rst | 2 +- docs/source/building/macos.rst | 2 +- docs/source/building/windows.rst | 2 +- docs/source/contributing/contributing.rst | 2 +- docs/source/contributing/localization.rst | 4 +- docs/source/index.rst | 2 +- docs/source/troubleshooting/general.rst | 2 +- docs/source/troubleshooting/linux.rst | 2 +- docs/source/troubleshooting/macos.rst | 2 +- docs/source/troubleshooting/windows.rst | 2 +- packaging/linux/aur/PKGBUILD | 2 +- ...nshine.yml => dev.lizardbyte.sunshine.yml} | 2 +- packaging/macos/Portfile | 8 ++-- scripts/_locale.py | 2 +- scripts/build-private.sh | 2 +- scripts/build-sunshine.sh | 2 +- src_assets/common/assets/web/index.html | 11 +++--- src_assets/common/config/sunshine.conf | 2 +- src_assets/macos/assets/Info.plist | 2 +- 31 files changed, 117 insertions(+), 129 deletions(-) rename packaging/linux/flatpak/{com.github.sunshinestream.sunshine.yml => dev.lizardbyte.sunshine.yml} (99%) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index a9c38290..8e4bfc76 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -5,7 +5,7 @@ body: attributes: value: > **THIS IS NOT THE PLACE TO ASK FOR SUPPORT!** - Please use [Github Discussions](https://github.com/SunshineStream/Sunshine/discussions) for support issues. + Please use [Discord](https://docs.lizardbyte.dev/about/support.html#discord) for support issues. - type: textarea id: description attributes: @@ -23,15 +23,25 @@ body: attributes: label: Additional Context description: Add any other context about the bug here. - - type: input + - type: dropdown id: os attributes: - label: Sunshine Host Operating System and Version - placeholder: eg. Windows 10, macOS 10.15, Ubuntu 20.04, etc. + label: Host Operating System + description: What version operating system are you running the software on? + options: + - Linux + - macOS + - Windows + - other + - type: input + id: os-version + attributes: + label: Operating System Version + description: Provide the version of the operating system. Additionally a build number would be helpful. validations: required: true - type: input - id: architecture + id: os-architecture attributes: label: Architecture placeholder: e.g. 32 bit, 64 bit, arm @@ -41,7 +51,7 @@ body: id: version attributes: label: Sunshine Version - placeholder: eg. 0.11.1 + placeholder: eg. 0.14.0 validations: required: true - type: input @@ -76,6 +86,12 @@ body: placeholder: e.g. PipeWire/KVM/X11 validations: required: false + - type: textarea + id: logs + attributes: + label: Relevant log output + 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: | diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 5448633f..649ab696 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -31,7 +31,7 @@ jobs: id: verify_changelog if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} # base_ref for pull request check, ref for push - uses: SunshineStream/actions/verify_changelog@master + uses: LizardByte/.github/actions/verify_changelog@master with: token: ${{ secrets.GITHUB_TOKEN }} outputs: @@ -209,8 +209,8 @@ jobs: - name: Build Linux Flatpak working-directory: build run: | - sudo su $(whoami) -c 'flatpak run org.flatpak.Builder --repo=repo --force-clean build-sunshine com.github.sunshinestream.sunshine.yml' - sudo su $(whoami) -c 'flatpak build-bundle ./repo ../artifacts/sunshine.flatpak com.github.sunshinestream.sunshine' + sudo su $(whoami) -c 'flatpak run org.flatpak.Builder --repo=repo --force-clean build-sunshine dev.lizardbyte.sunshine.yml' + sudo su $(whoami) -c 'flatpak build-bundle ./repo ../artifacts/sunshine.flatpak dev.lizardbyte.sunshine' - name: Upload Artifacts uses: actions/upload-artifact@v3 @@ -220,9 +220,9 @@ jobs: - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: SunshineStream/actions/create_release@master + uses: LizardByte/.github/actions/create_release@master with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GH_BOT_TOKEN }} next_version: ${{ needs.check_changelog.outputs.next_version }} last_version: ${{ needs.check_changelog.outputs.last_version }} release_body: ${{ needs.check_changelog.outputs.release_body }} @@ -415,9 +415,9 @@ jobs: - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: SunshineStream/actions/create_release@master + uses: LizardByte/.github/actions/create_release@master with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GH_BOT_TOKEN }} next_version: ${{ needs.check_changelog.outputs.next_version }} last_version: ${{ needs.check_changelog.outputs.last_version }} release_body: ${{ needs.check_changelog.outputs.release_body }} @@ -481,9 +481,9 @@ jobs: # no artifacts to release currently # - name: Create Release # if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} -# uses: SunshineStream/actions/create_release@master +# uses: LizardByte/.github/actions/create_release@master # with: -# token: ${{ secrets.GITHUB_TOKEN }} +# token: ${{ secrets.GH_BOT_TOKEN }} # next_version: ${{ needs.check_changelog.outputs.next_version }} # last_version: ${{ needs.check_changelog.outputs.last_version }} # release_body: ${{ needs.check_changelog.outputs.release_body }} @@ -692,9 +692,9 @@ jobs: - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: SunshineStream/actions/create_release@master + uses: LizardByte/.github/actions/create_release@master with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GH_BOT_TOKEN }} next_version: ${{ needs.check_changelog.outputs.next_version }} last_version: ${{ needs.check_changelog.outputs.last_version }} release_body: ${{ needs.check_changelog.outputs.release_body }} @@ -760,9 +760,9 @@ jobs: - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: SunshineStream/actions/create_release@master + uses: LizardByte/.github/actions/create_release@master with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GH_BOT_TOKEN }} next_version: ${{ needs.check_changelog.outputs.next_version }} last_version: ${{ needs.check_changelog.outputs.last_version }} release_body: ${{ needs.check_changelog.outputs.release_body }} diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index f4828e99..d48ab223 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -76,7 +76,7 @@ jobs: with: add-paths: | locale/*.po - token: ${{ secrets.GH_PAT }} # must trigger PR tests + token: ${{ secrets.GH_BOT_TOKEN }} # must trigger PR tests commit-message: New localization template branch: localize/update delete-branch: true diff --git a/CMakeLists.txt b/CMakeLists.txt index d86a7d9a..5e94d53a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.0) project(Sunshine VERSION 0.14.0 DESCRIPTION "Sunshine is a Gamestream host for Moonlight." - HOMEPAGE_URL "https://sunshinestream.github.io" + HOMEPAGE_URL "https://app.lizardbyte.dev" ) set(PROJECT_LONG_DESCRIPTION "Sunshine is a self hosted, low latency, cloud gaming solution with support for AMD, \ @@ -20,7 +20,7 @@ if(${SUNSHINE_CONFIGURE_APPIMAGE}) elseif(${SUNSHINE_CONFIGURE_AUR}) configure_file(packaging/linux/aur/PKGBUILD PKGBUILD @ONLY) elseif(${SUNSHINE_CONFIGURE_FLATPAK}) - configure_file(packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml com.github.sunshinestream.sunshine.yml @ONLY) + configure_file(packaging/linux/flatpak/dev.lizardbyte.sunshine.yml dev.lizardbyte.sunshine.yml @ONLY) elseif(${SUNSHINE_CONFIGURE_PORTFILE}) configure_file(packaging/macos/Portfile Portfile @ONLY) endif() @@ -480,11 +480,11 @@ target_compile_options(sunshine PRIVATE $<$:${SUNSHINE_COM # CPACK / Packaging # Common options -set(CPACK_PACKAGE_NAME "SunshineStream") -set(CPACK_PACKAGE_VENDOR "SunshineStream") +set(CPACK_PACKAGE_NAME "Sunshine") +set(CPACK_PACKAGE_VENDOR "LizardByte") set(CPACK_PACKAGE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/cpack_artifacts) -set(CPACK_PACKAGE_CONTACT "https://github.com/SunshineStream/Sunshine") -set(CPACK_DEBIAN_PACKAGE_MAINTAINER "https://github.com/SunshineStream/Sunshine") +set(CPACK_PACKAGE_CONTACT "https://app.lizardbyte.dev") +set(CPACK_DEBIAN_PACKAGE_MAINTAINER "https://github.com/LizardByte") set(CPACK_PACKAGE_DESCRIPTION ${CMAKE_PROJECT_DESCRIPTION}) set(CPACK_PACKAGE_HOMEPAGE_URL ${CMAKE_PROJECT_HOMEPAGE_URL}) set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE) diff --git a/DOCKER_README.md b/DOCKER_README.md index aa2f1017..a2647e00 100644 --- a/DOCKER_README.md +++ b/DOCKER_README.md @@ -11,16 +11,10 @@ docker run -d \ -e PUID= \ -e PGID= \ -e TZ= \ - -p 47990:47990 \ - -p 47984:47984 \ - -p 47989:47989 \ + -p 47984-47990:47984-47990/tcp \ -p 48010:48010 \ - -p 47998:47998 \ - -p 47999:47999 \ - -p 48000:48000 \ - -p 48002:48002 \ - -p 48010:48010 \ - sunshinestream/sunshine + -p 47998-48000:47998-48000/udp \ + lizardbyte/sunshine ``` To update the container it must be removed and recreated: @@ -31,7 +25,7 @@ docker stop sunshine # Remove the container docker rm sunshine # Pull the latest update -docker pull sunshinestream/sunshine +docker pull lizardbyte/sunshine # Run the container with the same parameters as before docker run -d ... ``` @@ -44,25 +38,19 @@ Create a `docker-compose.yml` file with the following contents (substitute your version: '3' services: sunshine: - image: sunshinestream/sunshine + image: lizardbyte/sunshine container_name: sunshine restart: unless-stopped volumes: - - :/config + - :/config environment: - - PUID= - - PGID= - - TZ= + - PUID= + - PGID= + - TZ= ports: - - "47990:47990" - - "47984:47984" - - "47989:47989" - - "48010:48010" - - "47998:47998" - - "47999:47999" - - "48000:48000" - - "48002:48002" - - "48010:48010" + - 47984-47990:47984-47990/tcp + - 48010:48010 + - 47998-48000:47998-48000/udp ``` Create and start the container (run the command from the same folder as your `docker-compose.yml` file): @@ -88,7 +76,7 @@ container. **Example:** `-p external:internal` - This shows the port mapping from internal to external of the container. Therefore `-p 47990:47990` would expose port `47990` from inside the container to be accessible from the host's IP on port `47990` (e.g. `http://:47990`). The internal port must be `47990`, but the external port may be changed -(e.g. `-p 8080:47990`). +(e.g. `-p 8080:47990`). All the ports listed in the `docker run` and `docker-compose` examples are required. | Parameter | Function | Example Value | Required | diff --git a/README.rst b/README.rst index eaf4b4a5..1e1a156e 100644 --- a/README.rst +++ b/README.rst @@ -1,8 +1,8 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/README.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/README.rst Overview ======== -SunshineStream has the full documentation hosted on `Read the Docs `_. +LizardByte has the full documentation hosted on `Read the Docs `_. About ----- @@ -29,13 +29,13 @@ These are the advantages of Sunshine over GeForce Experience. Integrations ------------ -.. image:: https://img.shields.io/github/workflow/status/sunshinestream/sunshine/CI/master?label=CI%20build&logo=github&style=for-the-badge +.. image:: https://img.shields.io/github/workflow/status/lizardbyte/sunshine/CI/master?label=CI%20build&logo=github&style=for-the-badge :alt: GitHub Workflow Status (CI) - :target: https://github.com/SunshineStream/Sunshine/actions/workflows/CI.yml?query=branch%3Amaster + :target: https://github.com/LizardByte/Sunshine/actions/workflows/CI.yml?query=branch%3Amaster -.. image:: https://img.shields.io/github/workflow/status/sunshinestream/sunshine/localize/nightly?label=localize%20build&logo=github&style=for-the-badge +.. image:: https://img.shields.io/github/workflow/status/lizardbyte/sunshine/localize/nightly?label=localize%20build&logo=github&style=for-the-badge :alt: GitHub Workflow Status (localize) - :target: https://github.com/SunshineStream/Sunshine/actions/workflows/localize.yml?query=branch%3Anightly + :target: https://github.com/LizardByte/Sunshine/actions/workflows/localize.yml?query=branch%3Anightly .. image:: https://img.shields.io/readthedocs/sunshinestream?label=Docs&style=for-the-badge&logo=readthedocs :alt: Read the Docs @@ -48,26 +48,24 @@ Integrations Support --------- -.. image:: https://img.shields.io/discord/938534566107418705?label=Discord&style=for-the-badge&color=blue&logo=discord - :alt: Discord - :target: https://sunshinestream.github.io/discord - -.. image:: https://img.shields.io/github/discussions/sunshinestream/sunshine?logo=github&style=for-the-badge - :alt: GitHub Discussions - :target: https://github.com/SunshineStream/Sunshine/discussions +Our support methods are listed in our `LizardByte Docs `_. Downloads --------- -.. image:: https://img.shields.io/github/downloads/sunshinestream/sunshine/total?style=for-the-badge&logo=github +.. image:: https://img.shields.io/github/downloads/lizardbyte/sunshine/total?style=for-the-badge&logo=github :alt: GitHub Releases - :target: https://github.com/SunshineStream/Sunshine/releases/latest + :target: https://github.com/LizardByte/Sunshine/releases/latest -.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=AUR&style=for-the-badge&query=$.results.0.NumVotes&url=https%3A%2F%2Fgithub.com%2FSunshineStream%2Fservice-repo%2Freleases%2Fdownload%2Fdaily%2Faur_sunshine-git.json&logo=archlinux +.. 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 + +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=AUR-git&style=for-the-badge&query=$.results.0.NumVotes&url=https%3A%2F%2Fapp.lizardbyte.dev%2Funo%2Faur%2Fsunshine-git.json&logo=archlinux + :alt: AUR-git votes :target: https://aur.archlinux.org/packages/sunshine-git .. comment - image:: https://img.shields.io/docker/pulls/sunshinestream/sunshine?style=for-the-badge&logo=docker + image:: https://img.shields.io/docker/pulls/lizardbyte/sunshine?style=for-the-badge&logo=docker :alt: Docker - :target: https://hub.docker.com/r/sunshinestream/sunshine + :target: https://hub.docker.com/r/lizardbyte/sunshine diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index 2cf18103..0d65175e 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/about/advanced_usage.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/about/advanced_usage.rst Advanced Usage ============== diff --git a/docs/source/about/docker.rst b/docs/source/about/docker.rst index f9afa251..8a597e26 100644 --- a/docs/source/about/docker.rst +++ b/docs/source/about/docker.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/DOCKER_README.md +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/DOCKER_README.md .. Todo:: This is a planned feature. Currently no Dockerfile or image exists for Sunshine. diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index cbe5aca3..f1687b3d 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/about/installation.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/about/installation.rst Installation ============ @@ -23,7 +23,7 @@ Follow the instructions for your preferred package type below. AppImage ^^^^^^^^ -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:appimage?logo=github&style=for-the-badge +.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/pkg:appimage?logo=github&style=for-the-badge :alt: GitHub issues by-label According to AppImageLint the AppImage can run on the following distros. @@ -54,7 +54,7 @@ AUR Package Debian Package ^^^^^^^^^^^^^^ -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:deb?logo=github&style=for-the-badge +.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/pkg:deb?logo=github&style=for-the-badge :alt: GitHub issues by-label #. Download ``sunshine.deb`` and run the following code. @@ -67,7 +67,7 @@ Debian Package Flatpak Package ^^^^^^^^^^^^^^^ -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:flatpak?logo=github&style=for-the-badge +.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/pkg:flatpak?logo=github&style=for-the-badge :alt: GitHub issues by-label .. Todo:: This package needs to have CUDA added. @@ -87,7 +87,7 @@ Flatpak Package RPM Package ^^^^^^^^^^^ -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:rpm?logo=github&style=for-the-badge +.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/pkg:rpm?logo=github&style=for-the-badge :alt: GitHub issues by-label #. Add `rpmfusion` repositories by running the following code. @@ -107,7 +107,7 @@ RPM Package macOS ----- -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:macos?logo=github&style=for-the-badge +.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/os:macos?logo=github&style=for-the-badge :alt: GitHub issues by-label pkg @@ -143,10 +143,10 @@ Portfile Windows ------- -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:10?logo=github&style=for-the-badge +.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/os:windows:10?logo=github&style=for-the-badge :alt: GitHub issues by-label -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:11?logo=github&style=for-the-badge +.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/os:windows:11?logo=github&style=for-the-badge :alt: GitHub issues by-label Installed option: @@ -155,6 +155,6 @@ Installed option: Standalone option: #. Download and extract ``sunshine-windows.zip`` -.. _latest release: https://github.com/SunshineStream/Sunshine/releases/latest -.. _Dockerhub.io: https://hub.docker.com/repository/docker/sunshinestream/sunshine -.. _ghcr.io: https://github.com/orgs/SunshineStream/packages?repo_name=sunshine +.. _latest release: https://github.com/LizardByte/Sunshine/releases/latest +.. _Dockerhub.io: https://hub.docker.com/repository/docker/lizardbyte/sunshine +.. _ghcr.io: https://github.com/orgs/LizardByte/packages?repo_name=sunshine diff --git a/docs/source/about/third_party_packages.rst b/docs/source/about/third_party_packages.rst index 99c7e96c..456a201f 100644 --- a/docs/source/about/third_party_packages.rst +++ b/docs/source/about/third_party_packages.rst @@ -1,22 +1,9 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/about/third_party_packages.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/about/third_party_packages.rst Third Party Packages ==================== -.. Danger:: These packages are not maintained by SunshineStream. Use at your own risk. - -AUR (Arch Linux User Repository) --------------------------------- - -.. image:: https://img.shields.io/badge/dynamic/json?color=orange&label=AUR&style=for-the-badge&prefix=v&query=$.results.0.Version&url=https%3A%2F%2Fgithub.com%2FSunshineStream%2Fservice-repo%2Freleases%2Fdownload%2Fdaily%2Faur_sunshine.json&logo=archlinux - :alt: AUR version - :target: https://aur.archlinux.org/packages/sunshine - -.. image:: https://img.shields.io/badge/dynamic/json?color=yellowgreen&label=votes&style=for-the-badge&query=$.results.0.NumVotes&url=https%3A%2F%2Fgithub.com%2FSunshineStream%2Fservice-repo%2Freleases%2Fdownload%2Fdaily%2Faur_sunshine.json&logo=archlinux - :alt: AUR votes - -.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=maintainer&style=for-the-badge&query=$.results.0.Maintainer&url=https%3A%2F%2Fgithub.com%2FSunshineStream%2Fservice-repo%2Freleases%2Fdownload%2Fdaily%2Faur_sunshine.json&logo=archlinux - :alt: AUR maintainer +.. Danger:: These packages are not maintained by LizardByte. Use at your own risk. Chocolatey ---------- diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst index da835bdf..009585ed 100644 --- a/docs/source/about/usage.rst +++ b/docs/source/about/usage.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/about/usage.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/about/usage.rst Usage ===== @@ -106,7 +106,7 @@ Sunshine needs access to `uinput` to create mouse and gamepad events. deb /usr/bin/sunshine ✔ rpm /usr/bin/sunshine ✔ AppImage ~/sunshine.AppImage ✖ - Flatpak flatpak run com.github.sunshinestream.sunshine ✖ + Flatpak flatpak run dev.lizardbyte.sunshine ✖ ======== ============================================== =============== Start once diff --git a/docs/source/building/build.rst b/docs/source/building/build.rst index a703959d..4ce4745e 100644 --- a/docs/source/building/build.rst +++ b/docs/source/building/build.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/building/build.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/building/build.rst Build ===== @@ -14,7 +14,7 @@ Ensure `git `_ is installed and run the following: .. code-block:: bash - git clone https://github.com/sunshinestream/sunshine.git --recurse-submodules + git clone https://github.com/lizardbyte/sunshine.git --recurse-submodules cd sunshine && mkdir build && cd build Compile diff --git a/docs/source/building/linux.rst b/docs/source/building/linux.rst index 475e0c66..c841ad61 100644 --- a/docs/source/building/linux.rst +++ b/docs/source/building/linux.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/building/linux.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/building/linux.rst Linux ===== diff --git a/docs/source/building/macos.rst b/docs/source/building/macos.rst index d20a5517..b719c6ee 100644 --- a/docs/source/building/macos.rst +++ b/docs/source/building/macos.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/building/macos.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/building/macos.rst macOS ===== diff --git a/docs/source/building/windows.rst b/docs/source/building/windows.rst index 7708da49..0c6dde57 100644 --- a/docs/source/building/windows.rst +++ b/docs/source/building/windows.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/building/windows.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/building/windows.rst Windows ======= diff --git a/docs/source/contributing/contributing.rst b/docs/source/contributing/contributing.rst index dfd2e080..e79fd4ea 100644 --- a/docs/source/contributing/contributing.rst +++ b/docs/source/contributing/contributing.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/contributing/contributing.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/contributing/contributing.rst Contributing ============ diff --git a/docs/source/contributing/localization.rst b/docs/source/contributing/localization.rst index 8127eda3..32ec4a5e 100644 --- a/docs/source/contributing/localization.rst +++ b/docs/source/contributing/localization.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/contributing/localization.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/contributing/localization.rst Localization ============ @@ -25,7 +25,7 @@ Only elements of the API are planned to be translated. .. Attention:: The rest API has not yet been implemented. Translations Basics - - The brand names `SunshineStream` and `Sunshine` should never be translated. + - The brand names `LizardByte` and `Sunshine` should never be translated. - Other brand names should never be translated. Examples: diff --git a/docs/source/index.rst b/docs/source/index.rst index aaa2663c..62697791 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/index.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/index.rst Table of Contents ================= diff --git a/docs/source/troubleshooting/general.rst b/docs/source/troubleshooting/general.rst index 322327cc..865214db 100644 --- a/docs/source/troubleshooting/general.rst +++ b/docs/source/troubleshooting/general.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/troubleshooting/general.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/troubleshooting/general.rst General ======= diff --git a/docs/source/troubleshooting/linux.rst b/docs/source/troubleshooting/linux.rst index 71e9eac0..5bac8c08 100644 --- a/docs/source/troubleshooting/linux.rst +++ b/docs/source/troubleshooting/linux.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/troubleshooting/linux.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/troubleshooting/linux.rst Linux ===== diff --git a/docs/source/troubleshooting/macos.rst b/docs/source/troubleshooting/macos.rst index 5a6ae658..f6014eb7 100644 --- a/docs/source/troubleshooting/macos.rst +++ b/docs/source/troubleshooting/macos.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/troubleshooting/macos.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/troubleshooting/macos.rst macOS ===== diff --git a/docs/source/troubleshooting/windows.rst b/docs/source/troubleshooting/windows.rst index 461bce0a..bb21589c 100644 --- a/docs/source/troubleshooting/windows.rst +++ b/docs/source/troubleshooting/windows.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/troubleshooting/windows.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/troubleshooting/windows.rst Windows ======= diff --git a/packaging/linux/aur/PKGBUILD b/packaging/linux/aur/PKGBUILD index be0e38fe..ede1af25 100644 --- a/packaging/linux/aur/PKGBUILD +++ b/packaging/linux/aur/PKGBUILD @@ -1,4 +1,4 @@ -# Edit on github: https://github.com/SunshineStream/Sunshine/tree/nightly/packaging/linux/aur/PKGBUILD +# 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@ diff --git a/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml similarity index 99% rename from packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml rename to packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index 4c16819d..7dd789b8 100644 --- a/packaging/linux/flatpak/com.github.sunshinestream.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -1,4 +1,4 @@ -app-id: com.github.sunshinestream.sunshine +app-id: dev.lizardbyte.sunshine runtime: org.freedesktop.Platform runtime-version: "21.08" sdk: org.freedesktop.Sdk diff --git a/packaging/macos/Portfile b/packaging/macos/Portfile index 061027e9..8707cedc 100644 --- a/packaging/macos/Portfile +++ b/packaging/macos/Portfile @@ -13,13 +13,13 @@ revision 0 categories multimedia emulators games platforms darwin license GPL-3 -maintainers @SunshineStream +maintainers @LizardByte description @PROJECT_DESCRIPTION@ # long_description will not be split into multiple lines as it's configured by CMakeLists long_description @PROJECT_LONG_DESCRIPTION@ homepage @PROJECT_HOMEPAGE_URL@ -master_sites https://github.com/sunshinestream/sunshine/releases +master_sites https://github.com/lizardbyte/sunshine/releases compiler.cxx_standard 2017 fetch.type git @@ -49,7 +49,7 @@ startupitem.netchange yes platform darwin { if { ${os.major} < 20 } { - # See: https://github.com/SunshineStream/Sunshine/discussions/117#discussioncomment-2513494 + # See: https://github.com/LizardByte/Sunshine/discussions/117#discussioncomment-2513494 notes-append "Port is limited to software encoding, when used with macOS releases prior to Big Sur." } } @@ -81,4 +81,4 @@ platform darwin { notes-append "Run @PROJECT_NAME@ by executing 'sunshine ', e.g. 'sunshine ~/sunshine.conf' " notes-append "The config file will be created if it doesn't exist." notes-append "It is recommended to set a location for the apps file in the config." -notes-append "See our documentation at 'https://sunshinestream.readthedocs.io/en/v@PROJECT_VERSION@/' for further info." +notes-append "See our documentation at 'https://docs.lizardbyte.dev/projects/sunshine/en/v@PROJECT_VERSION@/' for further info." diff --git a/scripts/_locale.py b/scripts/_locale.py index 339e2d4a..b8274aed 100644 --- a/scripts/_locale.py +++ b/scripts/_locale.py @@ -13,7 +13,7 @@ import subprocess project_name = 'Sunshine' -project_owner = 'SunshineStream' +project_owner = 'LizardByte' script_dir = os.path.dirname(os.path.abspath(__file__)) root_dir = os.path.dirname(script_dir) diff --git a/scripts/build-private.sh b/scripts/build-private.sh index 93d18748..80c7d6bc 100755 --- a/scripts/build-private.sh +++ b/scripts/build-private.sh @@ -8,7 +8,7 @@ SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR:-/etc/sunshine}" SUNSHINE_ROOT="${SUNSHINE_ROOT:-/root/sunshine}" SUNSHINE_TAG="${SUNSHINE_TAG:-master}" -SUNSHINE_GIT_URL="${SUNSHINE_GIT_URL:-https://github.com/sunshinestream/sunshine.git}" +SUNSHINE_GIT_URL="${SUNSHINE_GIT_URL:-https://github.com/lizardbyte/sunshine.git}" SUNSHINE_ENABLE_WAYLAND=${SUNSHINE_ENABLE_WAYLAND:-ON} diff --git a/scripts/build-sunshine.sh b/scripts/build-sunshine.sh index 5be82808..42843752 100755 --- a/scripts/build-sunshine.sh +++ b/scripts/build-sunshine.sh @@ -28,7 +28,7 @@ absolute_path() { CMAKE_BUILD_TYPE="-e CMAKE_BUILD_TYPE=Release" SUNSHINE_PACKAGE_BUILD=OFF SUNSHINE_PACKAGE_EXTENSION=deb -SUNSHINE_GIT_URL=https://github.com/sunshinestream/sunshine.git +SUNSHINE_GIT_URL=https://github.com/lizardbyte/sunshine.git CONTAINER_NAME=sunshine # Docker will fail if ctrl+c is passed through and the input is not a tty diff --git a/src_assets/common/assets/web/index.html b/src_assets/common/assets/web/index.html index 3273ec5d..32e8ed6c 100644 --- a/src_assets/common/assets/web/index.html +++ b/src_assets/common/assets/web/index.html @@ -9,10 +9,9 @@

      Resources

      Resources for Sunshine!

      -
      Official Website - Github Discussions - Sunshine Discord - Moonlight Discord + LizardByte Website + Discord + Github Discussions
      @@ -23,8 +22,8 @@

      Legal

      By continuing to use this software you agree to the terms and conditions in the following documents.

      - License - Third Party Notice + License + Third Party Notice