diff --git a/.codeql-prebuild-cpp-Linux.sh b/.codeql-prebuild-cpp-Linux.sh deleted file mode 100644 index f0d03bb2..00000000 --- a/.codeql-prebuild-cpp-Linux.sh +++ /dev/null @@ -1,11 +0,0 @@ -# install dependencies for C++ analysis -set -e - -chmod +x ./scripts/linux_build.sh -./scripts/linux_build.sh --skip-package --ubuntu-test-repo - -# Delete CUDA -rm -rf ./build/cuda - -# skip autobuild -echo "skip_autobuild=true" >> "$GITHUB_OUTPUT" diff --git a/.codeql-prebuild-cpp-Windows.sh b/.codeql-prebuild-cpp-Windows.sh deleted file mode 100644 index b860c9e8..00000000 --- a/.codeql-prebuild-cpp-Windows.sh +++ /dev/null @@ -1,59 +0,0 @@ -# install dependencies for C++ analysis -set -e - -# update pacman -pacman --noconfirm -Syu - -gcc_version="14.2.0-3" - -broken_deps=( - "mingw-w64-ucrt-x86_64-gcc" - "mingw-w64-ucrt-x86_64-gcc-libs" -) - -tarballs="" -for dep in "${broken_deps[@]}"; do - tarball="${dep}-${gcc_version}-any.pkg.tar.zst" - - # download and install working version - wget https://repo.msys2.org/mingw/ucrt64/${tarball} - - tarballs="${tarballs} ${tarball}" -done - -# install broken dependencies -if [ -n "$tarballs" ]; then - pacman -U --noconfirm ${tarballs} -fi - -# install dependencies -dependencies=( - "git" - "mingw-w64-ucrt-x86_64-cmake" - "mingw-w64-ucrt-x86_64-cppwinrt" - "mingw-w64-ucrt-x86_64-curl-winssl" - "mingw-w64-ucrt-x86_64-MinHook" - "mingw-w64-ucrt-x86_64-miniupnpc" - "mingw-w64-ucrt-x86_64-nlohmann-json" - "mingw-w64-ucrt-x86_64-nodejs" - "mingw-w64-ucrt-x86_64-nsis" - "mingw-w64-ucrt-x86_64-onevpl" - "mingw-w64-ucrt-x86_64-openssl" - "mingw-w64-ucrt-x86_64-opus" - "mingw-w64-ucrt-x86_64-toolchain" -) - -pacman -Syu --noconfirm --ignore="$(IFS=,; echo "${broken_deps[*]}")" "${dependencies[@]}" - -# build -mkdir -p build -cmake \ - -B build \ - -G Ninja \ - -S . \ - -DBUILD_DOCS=OFF \ - -DBUILD_WERROR=ON -ninja -C build - -# skip autobuild -echo "skip_autobuild=true" >> "$GITHUB_OUTPUT" diff --git a/.codeql-prebuild-cpp-macOS.sh b/.codeql-prebuild-cpp-macOS.sh deleted file mode 100644 index a21a69c3..00000000 --- a/.codeql-prebuild-cpp-macOS.sh +++ /dev/null @@ -1,32 +0,0 @@ -# install dependencies for C++ analysis -set -e - -# setup homebrew for x86_64 -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -eval "$(/usr/local/bin/brew shellenv)" - -# install dependencies -dependencies=( - "cmake" - "miniupnpc" - "ninja" - "node" - "openssl@3" - "opus" - "pkg-config" -) -brew install "${dependencies[@]}" - -# build -mkdir -p build -cmake \ - -B build \ - -G Ninja \ - -S . \ - -DBOOST_USE_STATIC=OFF \ - -DBUILD_DOCS=OFF \ - -DBUILD_WERROR=ON -ninja -C build - -# skip autobuild -echo "skip_autobuild=true" >> "$GITHUB_OUTPUT" diff --git a/.flake8 b/.flake8 index 2ea73951..a8948ef1 100644 --- a/.flake8 +++ b/.flake8 @@ -3,4 +3,5 @@ filename = *.py max-line-length = 120 extend-exclude = + .venv/ venv/ diff --git a/.github/semantic.yml b/.github/semantic.yml index b5eb70d0..92d97a74 100644 --- a/.github/semantic.yml +++ b/.github/semantic.yml @@ -12,3 +12,4 @@ titleAndCommits: false anyCommit: false allowMergeCommits: false allowRevertCommits: false +targetUrl: https://docs.lizardbyte.dev/latest/developers/contributing.html#creating-a-pull-request diff --git a/.github/workflows/_codeql.yml b/.github/workflows/_codeql.yml new file mode 100644 index 00000000..cfa961cb --- /dev/null +++ b/.github/workflows/_codeql.yml @@ -0,0 +1,30 @@ +--- +# This workflow is centrally managed in https://github.com/LizardByte/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +name: CodeQL +permissions: + actions: read + contents: read + security-events: write + +on: + push: + branches: + - master + pull_request: + branches: + - master + schedule: + - cron: '00 12 * * 0' # every Sunday at 12:00 UTC + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + call-codeql: + name: CodeQL + uses: LizardByte/.github/.github/workflows/__call-codeql.yml@master + if: ${{ github.repository != 'LizardByte/.github' }} diff --git a/.github/workflows/_common-lint.yml b/.github/workflows/_common-lint.yml new file mode 100644 index 00000000..7c317130 --- /dev/null +++ b/.github/workflows/_common-lint.yml @@ -0,0 +1,27 @@ +--- +# This workflow is centrally managed in https://github.com/LizardByte/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +name: common lint +permissions: + contents: read + +on: + pull_request: + branches: + - master + types: + - opened + - synchronize + - reopened + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + lint: + name: Common Lint + uses: LizardByte/.github/.github/workflows/__call-common-lint.yml@master + if: ${{ github.repository != 'LizardByte/.github' }} diff --git a/.github/workflows/_release-notifier.yml b/.github/workflows/_release-notifier.yml new file mode 100644 index 00000000..b32da784 --- /dev/null +++ b/.github/workflows/_release-notifier.yml @@ -0,0 +1,23 @@ +--- +# This workflow is centrally managed in https://github.com/LizardByte/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +# Create a blog post for a new release and open a PR to the blog repo + +name: Release Notifications +permissions: + contents: read + +on: + release: + types: + - released # this triggers when a release is published, but does not include pre-releases or drafts + +jobs: + update-blog: + name: Update blog + uses: LizardByte/.github/.github/workflows/__call-release-notifier.yml@master + if: github.repository_owner == 'LizardByte' + secrets: + GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/_update-docs.yml b/.github/workflows/_update-docs.yml new file mode 100644 index 00000000..6dd66c55 --- /dev/null +++ b/.github/workflows/_update-docs.yml @@ -0,0 +1,34 @@ +--- +# This workflow is centrally managed in https://github.com/LizardByte/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +# To use, add the `rtd` repository label to identify repositories that should trigger this workflow. +# If the project slug is not the repository name, add a repository variable named `READTHEDOCS_SLUG` with the value of +# the ReadTheDocs project slug. + +# Update readthedocs on release events. + +name: Update docs +permissions: {} + +on: + release: + types: + - created + - edited + - deleted + +concurrency: + group: "${{ github.workflow }}-${{ github.event.release.tag_name }}" + cancel-in-progress: true + +jobs: + update-docs: + name: Update docs + uses: LizardByte/.github/.github/workflows/__call-update-docs.yml@master + if: github.repository_owner == 'LizardByte' + with: + readthedocs_slug: ${{ vars.READTHEDOCS_SLUG }} + secrets: + READTHEDOCS_TOKEN: ${{ secrets.READTHEDOCS_TOKEN }} diff --git a/.github/workflows/_update-flathub-repo.yml b/.github/workflows/_update-flathub-repo.yml new file mode 100644 index 00000000..1f4ba3c7 --- /dev/null +++ b/.github/workflows/_update-flathub-repo.yml @@ -0,0 +1,29 @@ +--- +# This workflow is centrally managed in https://github.com/LizardByte/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +# To use, add the `flathub-pkg` repository label to identify repositories that should trigger this workflow. + +# Update Flathub on release events. + +name: Update Flathub repo +permissions: + contents: read + +on: + release: + types: + - released + +concurrency: + group: "${{ github.workflow }}-${{ github.event.release.tag_name }}" + cancel-in-progress: true + +jobs: + update-flathub-repo: + name: Update Flathub Repo + uses: LizardByte/.github/.github/workflows/__call-update-flathub-repo.yml@master + if: github.repository_owner == 'LizardByte' + secrets: + GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/_update-homebrew-repo.yml b/.github/workflows/_update-homebrew-repo.yml new file mode 100644 index 00000000..947c7872 --- /dev/null +++ b/.github/workflows/_update-homebrew-repo.yml @@ -0,0 +1,31 @@ +--- +# This workflow is centrally managed in https://github.com/LizardByte/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +# To use, add the `homebrew-pkg` repository label to identify repositories that should trigger this workflow. + +# Update Homebrew on release events. + +name: Update Homebrew repo +permissions: + contents: read + +on: + release: + types: + - released + +concurrency: + group: "${{ github.workflow }}-${{ github.event.release.tag_name }}" + cancel-in-progress: true + +jobs: + update-homebrew-repo: + name: Update Homebrew repo + uses: LizardByte/.github/.github/workflows/__call-update-homebrew-repo.yml@master + if: github.repository_owner == 'LizardByte' + secrets: + GH_EMAIL: ${{ secrets.GH_BOT_EMAIL }} + GH_USERNAME: ${{ secrets.GH_BOT_NAME }} + GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/_update-pacman-repo.yml b/.github/workflows/_update-pacman-repo.yml new file mode 100644 index 00000000..c62b34a4 --- /dev/null +++ b/.github/workflows/_update-pacman-repo.yml @@ -0,0 +1,29 @@ +--- +# This workflow is centrally managed in https://github.com/LizardByte/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +# To use, add the `pacman-pkg` repository label to identify repositories that should trigger this workflow. + +# Update pacman repo on release events. + +name: Update pacman repo +permissions: + contents: read + +on: + release: + types: + - released + +concurrency: + group: "${{ github.workflow }}-${{ github.event.release.tag_name }}" + cancel-in-progress: true + +jobs: + update-homebrew-release: + name: Update pacman repo + uses: LizardByte/.github/.github/workflows/__call-update-pacman-repo.yml@master + if: github.repository_owner == 'LizardByte' + secrets: + GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/_update-winget-repo.yml b/.github/workflows/_update-winget-repo.yml new file mode 100644 index 00000000..1cac56bf --- /dev/null +++ b/.github/workflows/_update-winget-repo.yml @@ -0,0 +1,29 @@ +--- +# This workflow is centrally managed in https://github.com/LizardByte/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +# To use, add the `winget-pkg` repository label to identify repositories that should trigger this workflow. + +# Update Winget on release events. + +name: Update Winget repo +permissions: + contents: read + +on: + release: + types: + - released + +concurrency: + group: "${{ github.workflow }}-${{ github.event.release.tag_name }}" + cancel-in-progress: true + +jobs: + update-winget-repo: + name: Update Winget repo + uses: LizardByte/.github/.github/workflows/__call-update-winget-repo.yml@master + if: github.repository_owner == 'LizardByte' + secrets: + GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/ci-flatpak.yml b/.github/workflows/ci-flatpak.yml new file mode 100644 index 00000000..df02d98f --- /dev/null +++ b/.github/workflows/ci-flatpak.yml @@ -0,0 +1,213 @@ +--- +name: CI-Flatpak +permissions: + contents: read + +on: + workflow_call: + inputs: + release_commit: + required: true + type: string + release_version: + required: true + type: string + +jobs: + build_linux_flatpak: + name: ${{ matrix.arch }} + env: + APP_ID: dev.lizardbyte.app.Sunshine + NODE_VERSION: "20" + PLATFORM_VERSION: "23.08" + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + runner: ubuntu-22.04 + - arch: aarch64 + runner: ubuntu-22.04-arm + steps: + - name: Maximize build space + if: matrix.arch == 'x86_64' + uses: easimon/maximize-build-space@v10 + with: + root-reserve-mb: 10240 + remove-dotnet: 'true' + remove-android: 'true' + remove-haskell: 'true' + remove-codeql: 'true' + remove-docker-images: 'true' + + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup node + id: node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Install npm dependencies + run: npm install --package-lock-only + + - name: Debug package-lock.json + run: cat package-lock.json + + - name: Setup python + id: python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Setup Dependencies Linux Flatpak + run: | + python -m pip install ./packaging/linux/flatpak/deps/flatpak-builder-tools/node + + sudo apt-get update -y + sudo apt-get install -y \ + cmake \ + flatpak + + sudo su $(whoami) -c "flatpak --user remote-add --if-not-exists flathub \ + https://flathub.org/repo/flathub.flatpakrepo" + + sudo su $(whoami) -c "flatpak --user install -y flathub \ + org.flatpak.Builder \ + org.freedesktop.Platform/${{ matrix.arch }}/${PLATFORM_VERSION} \ + org.freedesktop.Sdk/${{ matrix.arch }}/${PLATFORM_VERSION} \ + org.freedesktop.Sdk.Extension.node${NODE_VERSION}/${{ matrix.arch }}/${PLATFORM_VERSION} \ + " + + flatpak run org.flatpak.Builder --version + + - name: flatpak node generator + # https://github.com/flatpak/flatpak-builder-tools/blob/master/node/README.md + run: flatpak-node-generator npm package-lock.json + + - name: Debug generated-sources.json + run: cat generated-sources.json + + - name: Cache Flatpak build + uses: actions/cache@v4 + with: + path: ./build/.flatpak-builder + key: flatpak-${{ matrix.arch }}-${{ github.sha }} + restore-keys: | + flatpak-${{ matrix.arch }}- + + - name: Configure Flatpak Manifest + run: | + # variables for manifest + branch="${{ github.head_ref }}" + build_version=${{ inputs.release_version }} + commit=${{ inputs.release_commit }} + + # check the branch variable + if [ -z "$branch" ] + then + echo "This is a PUSH event" + branch=${{ github.ref_name }} + clone_url=${{ github.event.repository.clone_url }} + else + echo "This is a PR event" + clone_url=${{ github.event.pull_request.head.repo.clone_url }} + fi + echo "Branch: ${branch}" + echo "Commit: ${commit}" + echo "Clone URL: ${clone_url}" + + export BRANCH=${branch} + export BUILD_VERSION=${build_version} + export CLONE_URL=${clone_url} + export COMMIT=${commit} + + mkdir -p build + mkdir -p artifacts + + cmake -DGITHUB_CLONE_URL=${clone_url} \ + -B build \ + -S . \ + -DSUNSHINE_CONFIGURE_FLATPAK_MAN=ON \ + -DSUNSHINE_CONFIGURE_ONLY=ON + + - name: Debug Manifest + working-directory: build + run: cat ${APP_ID}.yml + + - name: Build Linux Flatpak + working-directory: build + run: | + sudo su $(whoami) -c "flatpak run org.flatpak.Builder \ + --arch=${{ matrix.arch }} \ + --force-clean \ + --repo=repo \ + --sandbox \ + --stop-at=cuda build-sunshine ${APP_ID}.yml" + cp -r .flatpak-builder copy-of-flatpak-builder + sudo su $(whoami) -c "flatpak run org.flatpak.Builder \ + --arch=${{ matrix.arch }} \ + --force-clean \ + --repo=repo \ + --sandbox \ + build-sunshine ${APP_ID}.yml" + rm -rf .flatpak-builder + mv copy-of-flatpak-builder .flatpak-builder + sudo su $(whoami) -c "flatpak build-bundle \ + --arch=${{ matrix.arch }} \ + ./repo \ + ../artifacts/sunshine_${{ matrix.arch }}.flatpak ${APP_ID}" + sudo su $(whoami) -c "flatpak build-bundle \ + --runtime \ + --arch=${{ matrix.arch }} \ + ./repo \ + ../artifacts/sunshine_debug_${{ matrix.arch }}.flatpak ${APP_ID}.Debug" + + - name: Lint Flatpak + working-directory: build + run: | + exceptions_file="${{ github.workspace }}/packaging/linux/flatpak/exceptions.json" + + echo "Linting flatpak manifest" + flatpak run --command=flatpak-builder-lint org.flatpak.Builder \ + --exceptions \ + --user-exceptions "${exceptions_file}" \ + manifest \ + ${APP_ID}.yml + + echo "Linting flatpak repo" + # TODO: add arg + # --mirror-screenshots-url=https://dl.flathub.org/media \ + flatpak run --command=flatpak-builder-lint org.flatpak.Builder \ + --exceptions \ + --user-exceptions "${exceptions_file}" \ + repo \ + repo + + - name: Package Flathub repo archive + # copy files required to generate the Flathub repo + if: matrix.arch == 'x86_64' + run: | + mkdir -p flathub/modules + cp ./build/generated-sources.json ./flathub/ + cp ./build/package-lock.json ./flathub/ + cp ./build/${APP_ID}.yml ./flathub/ + cp ./build/${APP_ID}.metainfo.xml ./flathub/ + cp ./packaging/linux/flatpak/README.md ./flathub/ + cp ./packaging/linux/flatpak/flathub.json ./flathub/ + cp -r ./packaging/linux/flatpak/modules/. ./flathub/modules/ + # submodules will need to be handled in the workflow that creates the PR + + # create the archive + tar -czf ./artifacts/flathub.tar.gz -C ./flathub . + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: build-Linux-Flatpak-${{ matrix.arch }} + path: artifacts/ + if-no-files-found: error diff --git a/.github/workflows/ci-homebrew.yml b/.github/workflows/ci-homebrew.yml new file mode 100644 index 00000000..1b7234a8 --- /dev/null +++ b/.github/workflows/ci-homebrew.yml @@ -0,0 +1,235 @@ +--- +name: CI-Homebrew +permissions: + contents: read + +on: + workflow_call: + inputs: + publish_release: + required: true + type: string + release_commit: + required: true + type: string + release_tag: + required: true + type: string + release_version: + required: true + type: string + secrets: + GH_TOKEN: + required: true + GIT_EMAIL: + required: true + GIT_USERNAME: + required: true + +jobs: + build_homebrew: + name: ${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }} + runs-on: ${{ matrix.os_name }}-${{ matrix.os_version }} + strategy: + fail-fast: false + matrix: + include: + # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories + # while GitHub has larger macOS runners, they are not available for our repos :( + - os_version: "13" + os_name: "macos" + - os_version: "14" + os_name: "macos" + - os_version: "15" + os_name: "macos" + - os_version: "latest" + os_name: "ubuntu" + - os_version: "latest" # this job will only configure the formula for release, no validation + os_name: "ubuntu" + release: true + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Fix homebrew python + if: matrix.os_name == 'macos' && matrix.os_version == '13' + run: | + rm '/usr/local/bin/2to3' + rm '/usr/local/bin/2to3-3.12' + rm '/usr/local/bin/idle3' + rm '/usr/local/bin/idle3.12' + rm '/usr/local/bin/idle3.13' + rm '/usr/local/bin/pip3.12' + rm '/usr/local/bin/pip3.13' + rm '/usr/local/bin/pydoc3' + rm '/usr/local/bin/pydoc3.12' + rm '/usr/local/bin/pydoc3.13' + rm '/usr/local/bin/python3' + rm '/usr/local/bin/python3.12' + rm '/usr/local/bin/python3.13' + rm '/usr/local/bin/python3-config' + rm '/usr/local/bin/python3.12-config' + rm '/usr/local/bin/python3.13-config' + brew install python3 + + - name: Configure formula + run: | + # variables for formula + branch="${{ github.head_ref }}" + build_version=${{ inputs.release_version }} + commit=${{ inputs.release_commit }} + + # check the branch variable + if [ -z "$branch" ] + then + echo "This is a PUSH event" + clone_url=${{ github.event.repository.clone_url }} + branch="${{ github.ref_name }}" + default_branch="${{ github.event.repository.default_branch }}" + + if [ "${{ matrix.release }}" == "true" ]; then + # we will publish the formula with the release tag + tag="${{ inputs.release_tag }}" + else + tag="${{ github.ref_name }}" + fi + else + echo "This is a PR event" + clone_url=${{ github.event.pull_request.head.repo.clone_url }} + branch="${{ github.event.pull_request.head.ref }}" + default_branch="${{ github.event.pull_request.head.repo.default_branch }}" + tag="${{ github.event.pull_request.head.ref }}" + fi + echo "Branch: ${branch}" + echo "Clone URL: ${clone_url}" + echo "Tag: ${tag}" + + export BRANCH=${branch} + export BUILD_VERSION=${build_version} + export CLONE_URL=${clone_url} + export COMMIT=${commit} + export TAG=${tag} + + mkdir -p build + cmake \ + -B build \ + -S . \ + -DGITHUB_DEFAULT_BRANCH="${default_branch}" \ + -DSUNSHINE_CONFIGURE_HOMEBREW=ON \ + -DSUNSHINE_CONFIGURE_ONLY=ON + + # copy formula to artifacts + mkdir -p homebrew + cp -f ./build/sunshine.rb ./homebrew/sunshine.rb + + # testing + cat ./homebrew/sunshine.rb + + - name: Upload Artifacts + if: matrix.release + uses: actions/upload-artifact@v4 + with: + name: build-Homebrew + path: homebrew/ + if-no-files-found: error + + - name: Setup Xvfb + if: matrix.release != true && runner.os == 'Linux' + run: | + sudo apt-get update -y + sudo apt-get install -y \ + xvfb + + export DISPLAY=:1 + Xvfb ${DISPLAY} -screen 0 1024x768x24 & + + echo "DISPLAY=${DISPLAY}" >> $GITHUB_ENV + + - name: Validate Homebrew Formula + id: test + if: matrix.release != true + uses: LizardByte/actions/actions/release_homebrew@v2025.711.172650 + with: + formula_file: ${{ github.workspace }}/homebrew/sunshine.rb + git_email: ${{ secrets.GIT_EMAIL }} + git_username: ${{ secrets.GIT_USERNAME }} + publish: false + token: ${{ secrets.GH_TOKEN }} + validate: true + + - name: Setup python + id: python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Generate gcov report + id: test_report + # any except canceled or skipped + # TODO: fix coverage, no .gcno files are being created + # TODO: .gcno files are supposed to be created next to .o files + if: false + # if: >- + # always() && + # matrix.release != true && + # (steps.test.outcome == 'success' || steps.test.outcome == 'failure') + run: | + cp -rf ${{ steps.test.outputs.buildpath }}/build/ ./build/ + cd build + ls -Ra + + ${{ steps.python.outputs.python-path }} -m pip install gcovr + ${{ steps.python.outputs.python-path }} -m gcovr . -r ../src \ + --exclude-noncode-lines \ + --exclude-throw-branches \ + --exclude-unreachable-branches \ + --verbose \ + --xml-pretty \ + -o coverage.xml + + - name: Upload coverage artifact + if: >- + always() && + matrix.release != true && + (steps.test.outcome == 'success' || steps.test.outcome == 'failure') && + startsWith(github.repository, 'LizardByte/') + uses: actions/upload-artifact@v4 + with: + name: coverage-Homebrew-${{ matrix.os_name }}-${{ matrix.os_version }} + path: | + build/coverage.xml + ${{ steps.test.outputs.testpath }}/test_results.xml + if-no-files-found: error + + - name: Patch homebrew formula + # create beta version of the formula + # don't run this on macOS, as the sed command fails + if: matrix.release + run: | + # variables + formula_file="homebrew/sunshine-beta.rb" + + # rename the file + mv homebrew/sunshine.rb $formula_file + + # update the formula + sed -i 's/class Sunshine < Formula/class SunshineBeta < Formula/' $formula_file + sed -i 's/# conflicts_with/conflicts_with/' $formula_file + + # print new file + echo "New formula:" + cat $formula_file + + - name: Upload Homebrew Beta Formula + if: >- + github.repository_owner == 'LizardByte' && + matrix.release && + inputs.publish_release == 'true' + uses: LizardByte/actions/actions/release_homebrew@v2025.711.172650 + with: + formula_file: ${{ github.workspace }}/homebrew/sunshine-beta.rb + git_email: ${{ secrets.GIT_EMAIL }} + git_username: ${{ secrets.GIT_USERNAME }} + publish: true + token: ${{ secrets.GH_TOKEN }} + validate: false diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml new file mode 100644 index 00000000..3e917505 --- /dev/null +++ b/.github/workflows/ci-linux.yml @@ -0,0 +1,216 @@ +--- +name: CI-Linux +permissions: + contents: read + +on: + workflow_call: + inputs: + release_commit: + required: true + type: string + release_version: + required: true + type: string + +jobs: + build_linux: + name: ${{ matrix.name }} + env: + APP_ID: dev.lizardbyte.app.Sunshine + runs-on: ubuntu-${{ matrix.dist }} + strategy: + fail-fast: false + matrix: + include: + - name: AppImage + EXTRA_ARGS: '--appimage-build' + dist: 22.04 + steps: + - name: Maximize build space + uses: easimon/maximize-build-space@v10 + with: + root-reserve-mb: 30720 + remove-dotnet: 'true' + remove-android: 'true' + remove-haskell: 'true' + remove-codeql: 'true' + remove-docker-images: 'true' + + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Dependencies Linux + timeout-minutes: 5 + run: | + # create the artifacts directory + mkdir -p artifacts + + # allow libfuse2 for appimage on 22.04+ + sudo add-apt-repository universe + + sudo apt-get install -y \ + libdrm-dev \ + libfuse2 \ + libgl-dev \ + libwayland-dev \ + libx11-xcb-dev \ + libxcb-dri3-dev \ + libxfixes-dev + + - name: Setup python + id: python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Build latest libva + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + timeout-minutes: 5 + run: | + gh release download --archive=tar.gz --repo=intel/libva + tar xzf libva-*.tar.gz && rm libva-*.tar.gz + cd libva-* + ./autogen.sh --prefix=/usr --libdir=/usr/lib/x86_64-linux-gnu \ + --enable-drm \ + --enable-x11 \ + --enable-glx \ + --enable-wayland \ + --without-legacy # emgd, nvctrl, fglrx + make -j $(nproc) + sudo make install + cd .. && rm -rf libva-* + + - name: Build Linux + env: + BRANCH: ${{ github.head_ref || github.ref_name }} + BUILD_VERSION: ${{ inputs.release_version }} + COMMIT: ${{ inputs.release_commit }} + run: | + chmod +x ./scripts/linux_build.sh + ./scripts/linux_build.sh \ + --publisher-name='${{ github.repository_owner }}' \ + --publisher-website='https://app.lizardbyte.dev' \ + --publisher-issue-url='https://app.lizardbyte.dev/support' \ + --skip-cleanup \ + --skip-package \ + --ubuntu-test-repo ${{ matrix.EXTRA_ARGS }} + + - name: Set AppImage Version + if: matrix.name == 'AppImage' + run: | + version=${{ inputs.release_version }} + echo "VERSION=${version}" >> $GITHUB_ENV + + - name: Package Linux - AppImage + if: matrix.name == 'AppImage' + working-directory: build + run: | + # install sunshine to the DESTDIR + DESTDIR=AppDir ninja install + + # custom AppRun file + cp -f ../packaging/linux/AppImage/AppRun ./AppDir/ + chmod +x ./AppDir/AppRun + + # variables + DESKTOP_FILE="${DESKTOP_FILE:-${APP_ID}.desktop}" + ICON_FILE="${ICON_FILE:-sunshine.png}" + + # AppImage + # https://docs.appimage.org/packaging-guide/index.html + wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage + + # https://github.com/linuxdeploy/linuxdeploy-plugin-gtk + sudo apt-get install libgtk-3-dev librsvg2-dev -y + wget -q https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh + chmod +x linuxdeploy-plugin-gtk.sh + export DEPLOY_GTK_VERSION=3 + + ./linuxdeploy-x86_64.AppImage \ + --appdir ./AppDir \ + --plugin gtk \ + --executable ./sunshine \ + --icon-file "../$ICON_FILE" \ + --desktop-file "./$DESKTOP_FILE" \ + --output appimage + + # move + mv Sunshine*.AppImage ../artifacts/sunshine.AppImage + + # permissions + chmod +x ../artifacts/sunshine.AppImage + + - name: Delete CUDA + # free up space on the runner + run: | + rm -rf ./build/cuda + + - name: Verify AppImage + if: matrix.name == 'AppImage' + run: | + wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage + chmod +x appimagelint-x86_64.AppImage + + ./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage + + - name: Install test deps + run: | + sudo apt-get update -y + sudo apt-get install -y \ + x11-xserver-utils \ + xvfb + + # clean apt cache + sudo apt-get clean + sudo rm -rf /var/lib/apt/lists/* + + - name: Run tests + id: test + working-directory: build/tests + run: | + export DISPLAY=:1 + Xvfb ${DISPLAY} -screen 0 1024x768x24 & + sleep 5 # give Xvfb time to start + + ./test_sunshine --gtest_color=yes --gtest_output=xml:test_results.xml + + - name: Generate gcov report + id: test_report + # any except canceled or skipped + if: >- + always() && + (steps.test.outcome == 'success' || steps.test.outcome == 'failure') + working-directory: build + run: | + ${{ steps.python.outputs.python-path }} -m pip install gcovr + ${{ steps.python.outputs.python-path }} -m gcovr . -r ../src \ + --exclude-noncode-lines \ + --exclude-throw-branches \ + --exclude-unreachable-branches \ + --verbose \ + --xml-pretty \ + -o coverage.xml + + - name: Upload coverage artifact + if: >- + always() && + (steps.test_report.outcome == 'success') + uses: actions/upload-artifact@v4 + with: + name: coverage-Linux-${{ matrix.name }} + path: | + build/coverage.xml + build/tests/test_results.xml + if-no-files-found: error + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: build-Linux-${{ matrix.name }} + path: artifacts/ + if-no-files-found: error diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml new file mode 100644 index 00000000..e66f52da --- /dev/null +++ b/.github/workflows/ci-windows.yml @@ -0,0 +1,345 @@ +--- +name: CI-Windows +permissions: + contents: read + +on: + workflow_call: + inputs: + release_commit: + required: true + type: string + release_version: + required: true + type: string + secrets: + CODECOV_TOKEN: + required: false + +jobs: + build_windows: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + defaults: + run: + shell: msys2 {0} + strategy: + fail-fast: false + matrix: + include: + - name: Windows-AMD64 + os: windows-2022 + arch: x86_64 + msystem: ucrt64 + toolchain: ucrt-x86_64 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Prepare tests + id: prepare-tests + if: false # todo: DirectX11 is not available, so even software encoder fails + shell: pwsh + run: | + # function to download and extract a zip file + function DownloadAndExtract { + param ( + [string]$Uri, + [string]$OutFile + ) + + $maxRetries = 5 + $retryCount = 0 + $success = $false + + while (-not $success -and $retryCount -lt $maxRetries) { + $retryCount++ + Write-Host "Downloading $Uri to $OutFile, attempt $retryCount of $maxRetries" + try { + Invoke-WebRequest -Uri $Uri -OutFile $OutFile + $success = $true + } catch { + Write-Host "Attempt $retryCount of $maxRetries failed with error: $($_.Exception.Message). Retrying..." + Start-Sleep -Seconds 5 + } + } + + if (-not $success) { + Write-Host "Failed to download the file after $maxRetries attempts." + exit 1 + } + + # use .NET to get the base name of the file + $baseName = (Get-Item $OutFile).BaseName + + # Extract the zip file + Expand-Archive -Path $OutFile -DestinationPath $baseName + } + + # virtual display driver + DownloadAndExtract ` + -Uri "https://www.amyuni.com/downloads/usbmmidd_v2.zip" ` + -OutFile "usbmmidd_v2.zip" + + # install + Set-Location -Path usbmmidd_v2/usbmmidd_v2 + ./deviceinstaller64 install usbmmidd.inf usbmmidd + + # create the virtual display + ./deviceinstaller64 enableidd 1 + + # move up a directory + Set-Location -Path ../.. + + # install devcon + DownloadAndExtract ` + -Uri "https://github.com/Drawbackz/DevCon-Installer/releases/download/1.4-rc/Devcon.Installer.zip" ` + -OutFile "Devcon.Installer.zip" + Set-Location -Path Devcon.Installer + # hash needs to match OS version + # https://github.com/Drawbackz/DevCon-Installer/blob/master/devcon_sources.json + Start-Process -FilePath "./Devcon Installer.exe" -Wait -ArgumentList ` + 'install', ` + '-hash', '54004C83EE34F6A55380528A8B29F4C400E61FBB947A19E0AB9E5A193D7D961E', ` + '-addpath', ` + '-update', ` + '-dir', 'C:\Windows\System32' + + # disable Hyper-V Video + # https://stackoverflow.com/a/59490940 + C:\Windows\System32\devcon.exe disable "VMBUS\{da0a7802-e377-4aac-8e77-0558eb1073f8}" + + # move up a directory + Set-Location -Path .. + + # multi monitor tool + DownloadAndExtract ` + -Uri "http://www.nirsoft.net/utils/multimonitortool-x64.zip" ` + -OutFile "multimonitortool.zip" + + # enable the virtual display + # http://www.nirsoft.net/utils/multi_monitor_tool.html + Set-Location -Path multimonitortool + + # Original Hyper-V is \\.\DISPLAY1, it will recreate itself as \\.\DISPLAY6 (or something higher than 2) + # USB Mobile Monitor Virtual Display is \\.\DISPLAY2 + + # these don't seem to work if not using runAs + # todo: do they work if not using runAs? + Start-Process powershell -Verb runAs -ArgumentList '-Command ./MultiMonitorTool.exe /enable \\.\DISPLAY2' + Start-Process powershell -Verb runAs -ArgumentList '-Command ./MultiMonitorTool.exe /SetPrimary \\.\DISPLAY2' + + # wait a few seconds + Start-Sleep -s 5 + + # list monitors + ./MultiMonitorTool.exe /stext monitor_list.txt + + # wait a few seconds + Start-Sleep -s 5 + + # print the monitor list + Get-Content -Path monitor_list.txt + + - name: Setup Dependencies Windows + # if a dependency needs to be pinned, see https://github.com/LizardByte/build-deps/pull/186 + uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.msystem }} + update: true + install: >- + wget + + - name: Update Windows dependencies + env: + MSYSTEM: ${{ matrix.msystem }} + TOOLCHAIN: ${{ matrix.toolchain }} + shell: msys2 {0} + run: | + # variables + declare -A pinned_deps + + # dependencies + dependencies=( + "git" + "mingw-w64-${TOOLCHAIN}-cmake" + "mingw-w64-${TOOLCHAIN}-cppwinrt" + "mingw-w64-${TOOLCHAIN}-curl-winssl" + "mingw-w64-${TOOLCHAIN}-gcc" + "mingw-w64-${TOOLCHAIN}-graphviz" + "mingw-w64-${TOOLCHAIN}-MinHook" + "mingw-w64-${TOOLCHAIN}-miniupnpc" + "mingw-w64-${TOOLCHAIN}-nlohmann-json" + "mingw-w64-${TOOLCHAIN}-nodejs" + "mingw-w64-${TOOLCHAIN}-nsis" + "mingw-w64-${TOOLCHAIN}-onevpl" + "mingw-w64-${TOOLCHAIN}-openssl" + "mingw-w64-${TOOLCHAIN}-opus" + "mingw-w64-${TOOLCHAIN}-toolchain" + ) + + # do not modify below this line + + ignore_packages=() + tarballs="" + for pkg in "${!pinned_deps[@]}"; do + ignore_packages+=("${pkg}") + version="${pinned_deps[$pkg]}" + tarball="${pkg}-${version}-any.pkg.tar.zst" + + # download working version + wget "https://repo.msys2.org/mingw/${MSYSTEM}/${tarball}" + + tarballs="${tarballs} ${tarball}" + done + + # Create the ignore string for pacman + ignore_list=$(IFS=,; echo "${ignore_packages[*]}") + + # install pinned dependencies + if [ -n "$tarballs" ]; then + pacman -U --noconfirm ${tarballs} + fi + + # Only add --ignore if we have packages to ignore + if [ -n "$ignore_list" ]; then + pacman -Syu --noconfirm --ignore="${ignore_list}" "${dependencies[@]}" + else + pacman -Syu --noconfirm "${dependencies[@]}" + fi + + - name: Install Doxygen + # GCC compiled doxygen has issues when running graphviz + env: + DOXYGEN_VERSION: "1.11.0" + shell: pwsh + run: | + # Set version variables + $doxy_ver = $env:DOXYGEN_VERSION + $_doxy_ver = $doxy_ver.Replace(".", "_") + + # Download the Doxygen installer + Invoke-WebRequest -Uri ` + "https://github.com/doxygen/doxygen/releases/download/Release_${_doxy_ver}/doxygen-${doxy_ver}-setup.exe" ` + -OutFile "doxygen-setup.exe" + + # Run the installer + Start-Process ` + -FilePath .\doxygen-setup.exe ` + -ArgumentList ` + '/VERYSILENT' ` + -Wait ` + -NoNewWindow + + # Clean up + Remove-Item -Path doxygen-setup.exe + + - name: Setup python + id: setup-python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Python Path + id: python-path + shell: msys2 {0} + run: | + # replace backslashes with double backslashes + python_path=$(echo "${{ steps.setup-python.outputs.python-path }}" | sed 's/\\/\\\\/g') + + # step output + echo "python-path=${python_path}" + echo "python-path=${python_path}" >> $GITHUB_OUTPUT + + - name: Build Windows + shell: msys2 {0} + env: + BRANCH: ${{ github.head_ref || github.ref_name }} + BUILD_VERSION: ${{ inputs.release_version }} + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + COMMIT: ${{ inputs.release_commit }} + run: | + mkdir -p build + cmake \ + -B build \ + -G Ninja \ + -S . \ + -DBUILD_WERROR=ON \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DSUNSHINE_ASSETS_DIR=assets \ + -DSUNSHINE_PUBLISHER_NAME='${{ github.repository_owner }}' \ + -DSUNSHINE_PUBLISHER_WEBSITE='https://app.lizardbyte.dev' \ + -DSUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support' + ninja -C build + + - name: Package Windows + shell: msys2 {0} + run: | + mkdir -p artifacts + cd build + + # package + cpack -G NSIS + cpack -G ZIP + + # move + mv ./cpack_artifacts/Sunshine.exe ../artifacts/Sunshine-${{ matrix.name }}-installer.exe + mv ./cpack_artifacts/Sunshine.zip ../artifacts/Sunshine-${{ matrix.name }}-portable.zip + + - name: Run tests + id: test + shell: msys2 {0} + working-directory: build/tests + run: | + ./test_sunshine.exe --gtest_color=yes --gtest_output=xml:test_results.xml + + - name: Generate gcov report + id: test_report + # any except canceled or skipped + if: always() && (steps.test.outcome == 'success' || steps.test.outcome == 'failure') + shell: msys2 {0} + working-directory: build + run: | + ${{ steps.python-path.outputs.python-path }} -m pip install gcovr + ${{ steps.python-path.outputs.python-path }} -m gcovr . -r ../src \ + --exclude-noncode-lines \ + --exclude-throw-branches \ + --exclude-unreachable-branches \ + --verbose \ + --xml-pretty \ + -o coverage.xml + + - name: Upload coverage artifact + if: >- + always() && + (steps.test_report.outcome == 'success') + uses: actions/upload-artifact@v4 + with: + name: coverage-${{ matrix.name }} + path: | + build/coverage.xml + build/tests/test_results.xml + if-no-files-found: error + + - name: Package Windows Debug Info + shell: pwsh + working-directory: build + run: | + # use .dbg file extension for binaries to avoid confusion with real packages + Get-ChildItem -File -Recurse | ` + % { Rename-Item -Path $_.PSPath -NewName $_.Name.Replace(".exe",".dbg") } + + # save the binaries with debug info + 7z -r ` + "-xr!CMakeFiles" ` + "-xr!cpack_artifacts" ` + a "../artifacts/Sunshine-${{ matrix.name }}-debuginfo.7z" "*.dbg" + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: build-${{ matrix.name }} + path: artifacts/ + if-no-files-found: error diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..c4faf3bd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,214 @@ +--- +name: CI +permissions: + contents: read + +on: + pull_request: + branches: + - master + types: + - opened + - synchronize + - reopened + push: + branches: + - master + workflow_dispatch: + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + github-env: + name: GitHub Env Debug + uses: LizardByte/.github/.github/workflows/__call-github-env.yml@master + + release-setup: + name: Release Setup + outputs: + publish_release: ${{ steps.release-setup.outputs.publish_release }} + release_body: ${{ steps.release-setup.outputs.release_body }} + release_commit: ${{ steps.release-setup.outputs.release_commit }} + release_generate_release_notes: ${{ steps.release-setup.outputs.release_generate_release_notes }} + release_tag: ${{ steps.release-setup.outputs.release_tag }} + release_version: ${{ steps.release-setup.outputs.release_version }} + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Release Setup + id: release-setup + uses: LizardByte/actions/actions/release_setup@v2025.711.172650 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + build-docker: + name: Docker + needs: release-setup + permissions: + contents: read + packages: write + uses: LizardByte/.github/.github/workflows/__call-docker.yml@master + with: + maximize_build_space: true + publish_release: ${{ needs.release-setup.outputs.publish_release }} + release_commit: ${{ needs.release-setup.outputs.release_commit }} + release_tag: ${{ needs.release-setup.outputs.release_tag }} + release_version: ${{ needs.release-setup.outputs.release_version }} + secrets: + DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }} + DOCKER_HUB_PASSWORD: ${{ secrets.DOCKER_HUB_PASSWORD }} + DOCKER_HUB_ACCESS_TOKEN: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + GH_BOT_NAME: ${{ secrets.GH_BOT_NAME }} + GH_BOT_TOKEN: ${{ secrets.GH_BOT_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build-homebrew: + name: Homebrew + needs: release-setup + uses: ./.github/workflows/ci-homebrew.yml + with: + publish_release: ${{ needs.release-setup.outputs.publish_release }} + release_commit: ${{ needs.release-setup.outputs.release_commit }} + release_tag: ${{ needs.release-setup.outputs.release_tag }} + release_version: ${{ needs.release-setup.outputs.release_version }} + secrets: + GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} + GIT_EMAIL: ${{ secrets.GH_BOT_EMAIL }} + GIT_USERNAME: ${{ secrets.GH_BOT_NAME }} + + build-linux: + name: Linux + needs: release-setup + uses: ./.github/workflows/ci-linux.yml + with: + release_commit: ${{ needs.release-setup.outputs.release_commit }} + release_version: ${{ needs.release-setup.outputs.release_version }} + + build-linux-copr: + name: Linux Copr + if: github.event_name != 'push' # releases are handled directly in ci-copr.yml + needs: release-setup + uses: ./.github/workflows/ci-copr.yml + secrets: + COPR_BETA_WEBHOOK_TOKEN: ${{ secrets.COPR_BETA_WEBHOOK_TOKEN }} + COPR_STABLE_WEBHOOK_TOKEN: ${{ secrets.COPR_STABLE_WEBHOOK_TOKEN }} + COPR_CLI_CONFIG: ${{ secrets.COPR_CLI_CONFIG }} + + build-linux-flatpak: + name: Linux Flatpak + needs: release-setup + uses: ./.github/workflows/ci-flatpak.yml + with: + release_commit: ${{ needs.release-setup.outputs.release_commit }} + release_version: ${{ needs.release-setup.outputs.release_version }} + + build-windows: + name: Windows + needs: release-setup + uses: ./.github/workflows/ci-windows.yml + with: + release_commit: ${{ needs.release-setup.outputs.release_commit }} + release_version: ${{ needs.release-setup.outputs.release_version }} + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + coverage: + name: Coverage-${{ matrix.name }} + if: >- + always() && + !cancelled() && + startsWith(github.repository, 'LizardByte/') + needs: + - build-linux + - build-linux-flatpak + - build-homebrew + - build-windows + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - name: Linux-AppImage + coverage: true + - name: Homebrew-macos-13 + coverage: false + - name: Homebrew-macos-14 + coverage: false + - name: Homebrew-macos-15 + coverage: false + - name: Homebrew-ubuntu-latest + coverage: false + - name: Windows-AMD64 + coverage: true + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download coverage artifact + uses: actions/download-artifact@v4 + with: + name: coverage-${{ matrix.name }} + path: _coverage + + - name: Upload test results + uses: codecov/test-results-action@v1 + with: + disable_search: true + fail_ci_if_error: true + files: ./_coverage/tests/test_results.xml + flags: ${{ matrix.name }} + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true + + - name: Upload coverage + uses: codecov/codecov-action@v5 + if: matrix.coverage != false + with: + disable_search: true + fail_ci_if_error: true + files: ./_coverage/coverage.xml + flags: ${{ matrix.name }} + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true + + release: + name: Release + if: + needs.release-setup.outputs.publish_release == 'true' && + startsWith(github.repository, 'LizardByte/') + needs: + - release-setup + - build-docker + - build-linux + - build-linux-flatpak + - build-homebrew + - build-windows + runs-on: ubuntu-latest + steps: + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + pattern: build-* + merge-multiple: true + + - name: Debug artifacts + run: ls -l artifacts + + - name: Create/Update GitHub Release + uses: LizardByte/actions/actions/release_create@v2025.711.172650 + with: + allowUpdates: false + body: ${{ needs.release-setup.outputs.release_body }} + generateReleaseNotes: ${{ needs.release-setup.outputs.release_generate_release_notes }} + name: ${{ needs.release-setup.outputs.release_tag }} + prerelease: true + tag: ${{ needs.release-setup.outputs.release_tag }} + token: ${{ secrets.GH_BOT_TOKEN }} + virustotal_api_key: ${{ secrets.VIRUSTOTAL_API_KEY }} diff --git a/cmake/compile_definitions/common.cmake b/cmake/compile_definitions/common.cmake index 723a1a7a..f3873305 100644 --- a/cmake/compile_definitions/common.cmake +++ b/cmake/compile_definitions/common.cmake @@ -52,9 +52,6 @@ include_directories(BEFORE SYSTEM "${CMAKE_SOURCE_DIR}/third-party/nv-codec-head file(GLOB NVENC_SOURCES CONFIGURE_DEPENDS "src/nvenc/*.cpp" "src/nvenc/*.h") list(APPEND PLATFORM_TARGET_FILES ${NVENC_SOURCES}) -configure_file("${CMAKE_SOURCE_DIR}/src/version.h.in" version.h @ONLY) -include_directories(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") # required for importing version.h - set(SUNSHINE_TARGET_FILES "${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c/src/Input.h" "${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c/src/Rtsp.h" diff --git a/cmake/compile_definitions/linux.cmake b/cmake/compile_definitions/linux.cmake index b4267ecb..c019af67 100644 --- a/cmake/compile_definitions/linux.cmake +++ b/cmake/compile_definitions/linux.cmake @@ -85,18 +85,27 @@ if(CUDA_FOUND) add_compile_definitions(SUNSHINE_BUILD_CUDA) endif() -# drm -if(${SUNSHINE_ENABLE_DRM}) +# libdrm is required for both DRM (KMS) and Wayland +if(${SUNSHINE_ENABLE_DRM} OR ${SUNSHINE_ENABLE_WAYLAND}) find_package(LIBDRM REQUIRED) - find_package(LIBCAP REQUIRED) else() set(LIBDRM_FOUND OFF) +endif() +if(LIBDRM_FOUND) + include_directories(SYSTEM ${LIBDRM_INCLUDE_DIRS}) + list(APPEND PLATFORM_LIBRARIES ${LIBDRM_LIBRARIES}) +endif() + +# drm +if(${SUNSHINE_ENABLE_DRM}) + find_package(LIBCAP REQUIRED) +else() set(LIBCAP_FOUND OFF) endif() if(LIBDRM_FOUND AND LIBCAP_FOUND) add_compile_definitions(SUNSHINE_BUILD_DRM) - include_directories(SYSTEM ${LIBDRM_INCLUDE_DIRS} ${LIBCAP_INCLUDE_DIRS}) - list(APPEND PLATFORM_LIBRARIES ${LIBDRM_LIBRARIES} ${LIBCAP_LIBRARIES}) + include_directories(SYSTEM ${LIBCAP_INCLUDE_DIRS}) + list(APPEND PLATFORM_LIBRARIES ${LIBCAP_LIBRARIES}) list(APPEND PLATFORM_TARGET_FILES "${CMAKE_SOURCE_DIR}/src/platform/linux/kmsgrab.cpp") list(APPEND SUNSHINE_DEFINITIONS EGL_NO_X11=1) diff --git a/cmake/compile_definitions/windows.cmake b/cmake/compile_definitions/windows.cmake index 60e10b41..5bd34f1b 100644 --- a/cmake/compile_definitions/windows.cmake +++ b/cmake/compile_definitions/windows.cmake @@ -38,10 +38,17 @@ if(NOT DEFINED SUNSHINE_ICON_PATH) set(SUNSHINE_ICON_PATH "${CMAKE_SOURCE_DIR}/apollo.ico") endif() -configure_file("${CMAKE_SOURCE_DIR}/src/platform/windows/windows.rc.in" windows.rc @ONLY) +# Create a separate object library for the RC file with minimal includes +add_library(sunshine_rc_object OBJECT "${CMAKE_SOURCE_DIR}/src/platform/windows/windows.rc") + +# Set minimal properties for RC compilation - only what's needed for the resource file +# Otherwise compilation can fail due to "line too long" errors +set_target_properties(sunshine_rc_object PROPERTIES + COMPILE_DEFINITIONS "PROJECT_ICON_PATH=${SUNSHINE_ICON_PATH};PROJECT_NAME=${PROJECT_NAME};PROJECT_VENDOR=${SUNSHINE_PUBLISHER_NAME};PROJECT_VERSION=${PROJECT_VERSION};PROJECT_VERSION_MAJOR=${PROJECT_VERSION_MAJOR};PROJECT_VERSION_MINOR=${PROJECT_VERSION_MINOR};PROJECT_VERSION_PATCH=${PROJECT_VERSION_PATCH}" # cmake-lint: disable=C0301 + INCLUDE_DIRECTORIES "" +) set(PLATFORM_TARGET_FILES - "${CMAKE_CURRENT_BINARY_DIR}/windows.rc" "${CMAKE_SOURCE_DIR}/src/platform/windows/publish.cpp" "${CMAKE_SOURCE_DIR}/src/platform/windows/misc.h" "${CMAKE_SOURCE_DIR}/src/platform/windows/misc.cpp" diff --git a/cmake/packaging/common.cmake b/cmake/packaging/common.cmake index 35c9b858..3b196a99 100644 --- a/cmake/packaging/common.cmake +++ b/cmake/packaging/common.cmake @@ -3,7 +3,10 @@ # common cpack options set(CPACK_PACKAGE_NAME ${CMAKE_PROJECT_NAME}) set(CPACK_PACKAGE_VENDOR "SudoMaker") -string(REGEX REPLACE "^v" "" CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) # remove the v prefix if it exists +set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) +set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) set(CPACK_PACKAGE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/cpack_artifacts) set(CPACK_PACKAGE_CONTACT "https://www.sudomaker.com") set(CPACK_PACKAGE_DESCRIPTION ${CMAKE_PROJECT_DESCRIPTION}) diff --git a/cmake/packaging/linux.cmake b/cmake/packaging/linux.cmake index 90f3fffe..041430dc 100644 --- a/cmake/packaging/linux.cmake +++ b/cmake/packaging/linux.cmake @@ -136,26 +136,13 @@ if(${SUNSHINE_TRAY} STREQUAL 1) endif() # desktop file -# todo - validate desktop files with `desktop-file-validate` -if(NOT ${SUNSHINE_BUILD_FLATPAK}) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.desktop" - DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications") -else() - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.desktop" - DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications" - RENAME "${PROJECT_FQDN}.desktop") -endif() +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_FQDN}.desktop" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications") if(NOT ${SUNSHINE_BUILD_APPIMAGE} AND NOT ${SUNSHINE_BUILD_FLATPAK}) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine_terminal.desktop" + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_FQDN}.terminal.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications") endif() # metadata file -# todo - validate file with `appstream-util validate-relax` -if(NOT ${SUNSHINE_BUILD_FLATPAK}) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.appdata.xml" - DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo") -else() - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_FQDN}.metainfo.xml" - DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo") -endif() +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_FQDN}.metainfo.xml" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo") diff --git a/cmake/prep/build_version.cmake b/cmake/prep/build_version.cmake index 0ca6cf0a..69c4d7cd 100644 --- a/cmake/prep/build_version.cmake +++ b/cmake/prep/build_version.cmake @@ -1,18 +1,34 @@ +# Set build variables if env variables are defined +# These are used in configured files such as manifests for different packages +if(DEFINED ENV{BRANCH}) # cmake-lint: disable=W0106 + set(GITHUB_BRANCH $ENV{BRANCH}) +endif() +if(DEFINED ENV{BUILD_VERSION}) # cmake-lint: disable=W0106 + set(BUILD_VERSION $ENV{BUILD_VERSION}) +endif() +if(DEFINED ENV{CLONE_URL}) # cmake-lint: disable=W0106 + set(GITHUB_CLONE_URL $ENV{CLONE_URL}) +endif() +if(DEFINED ENV{COMMIT}) # cmake-lint: disable=W0106 + set(GITHUB_COMMIT $ENV{COMMIT}) +endif() +if(DEFINED ENV{TAG}) # cmake-lint: disable=W0106 + set(GITHUB_TAG $ENV{TAG}) +endif() + # Check if env vars are defined before attempting to access them, variables will be defined even if blank -if((DEFINED ENV{BRANCH}) AND (DEFINED ENV{BUILD_VERSION}) AND (DEFINED ENV{COMMIT})) # cmake-lint: disable=W0106 - if(($ENV{BRANCH} STREQUAL "master") AND (NOT $ENV{BUILD_VERSION} STREQUAL "")) - # If BRANCH is "master" and BUILD_VERSION is not empty, then we are building a master branch - MESSAGE("Got from CI master branch and version $ENV{BUILD_VERSION}") +if((DEFINED ENV{BRANCH}) AND (DEFINED ENV{BUILD_VERSION})) # cmake-lint: disable=W0106 + if((DEFINED ENV{BRANCH}) AND (NOT $ENV{BUILD_VERSION} STREQUAL "")) + # If BRANCH is defined and BUILD_VERSION is not empty, then we are building from CI + # If BRANCH is master we are building a push/release build + MESSAGE("Got from CI '$ENV{BRANCH}' branch and version '$ENV{BUILD_VERSION}'") set(PROJECT_VERSION $ENV{BUILD_VERSION}) + string(REGEX REPLACE "^v" "" PROJECT_VERSION ${PROJECT_VERSION}) # remove the v prefix if it exists set(CMAKE_PROJECT_VERSION ${PROJECT_VERSION}) # cpack will use this to set the binary versions - elseif((DEFINED ENV{BRANCH}) AND (DEFINED ENV{COMMIT})) - # If BRANCH is set but not BUILD_VERSION we are building a PR, we gather only the commit hash - MESSAGE("Got from CI $ENV{BRANCH} branch and commit $ENV{COMMIT}") - set(PROJECT_VERSION ${PROJECT_VERSION}.$ENV{COMMIT}) endif() +else() # Generate Sunshine Version based of the git tag # https://github.com/nocnokneo/cmake-git-versioning-example/blob/master/LICENSE -else() find_package(Git) if(GIT_EXECUTABLE) MESSAGE("${CMAKE_SOURCE_DIR}") @@ -54,3 +70,72 @@ else() MESSAGE(WARNING ": Git not found, cannot find git version") endif() endif() + +# set date variables +set(PROJECT_YEAR "1990") +set(PROJECT_MONTH "01") +set(PROJECT_DAY "01") + +# Extract year, month, and day (do this AFTER version parsing) +# Note: Cmake doesn't support "{}" regex syntax +if(PROJECT_VERSION MATCHES "^([0-9][0-9][0-9][0-9])\\.([0-9][0-9][0-9][0-9]?)\\.([0-9]+)$") + message("Extracting year and month/day from PROJECT_VERSION: ${PROJECT_VERSION}") + # First capture group is the year + set(PROJECT_YEAR "${CMAKE_MATCH_1}") + + # Second capture group contains month and day + set(MONTH_DAY "${CMAKE_MATCH_2}") + + # Extract month (first 1-2 digits) and day (last 2 digits) + string(LENGTH "${MONTH_DAY}" MONTH_DAY_LENGTH) + if(MONTH_DAY_LENGTH EQUAL 3) + # Format: MDD (e.g., 703 = month 7, day 03) + string(SUBSTRING "${MONTH_DAY}" 0 1 PROJECT_MONTH) + string(SUBSTRING "${MONTH_DAY}" 1 2 PROJECT_DAY) + elseif(MONTH_DAY_LENGTH EQUAL 4) + # Format: MMDD (e.g., 1203 = month 12, day 03) + string(SUBSTRING "${MONTH_DAY}" 0 2 PROJECT_MONTH) + string(SUBSTRING "${MONTH_DAY}" 2 2 PROJECT_DAY) + endif() + + # Ensure month is two digits + if(PROJECT_MONTH LESS 10 AND NOT PROJECT_MONTH MATCHES "^0") + set(PROJECT_MONTH "0${PROJECT_MONTH}") + endif() + # Ensure day is two digits + if(PROJECT_DAY LESS 10 AND NOT PROJECT_DAY MATCHES "^0") + set(PROJECT_DAY "0${PROJECT_DAY}") + endif() +endif() + +# Parse PROJECT_VERSION to extract major, minor, and patch components +if(PROJECT_VERSION MATCHES "([0-9]+)\\.([0-9]+)\\.([0-9]+)") + set(PROJECT_VERSION_MAJOR "${CMAKE_MATCH_1}") + set(CMAKE_PROJECT_VERSION_MAJOR "${CMAKE_MATCH_1}") + + set(PROJECT_VERSION_MINOR "${CMAKE_MATCH_2}") + set(CMAKE_PROJECT_VERSION_MINOR "${CMAKE_MATCH_2}") + + set(PROJECT_VERSION_PATCH "${CMAKE_MATCH_3}") + set(CMAKE_PROJECT_VERSION_PATCH "${CMAKE_MATCH_3}") +endif() + +message("PROJECT_NAME: ${PROJECT_NAME}") +message("PROJECT_VERSION: ${PROJECT_VERSION}") +message("PROJECT_VERSION_MAJOR: ${PROJECT_VERSION_MAJOR}") +message("PROJECT_VERSION_MINOR: ${PROJECT_VERSION_MINOR}") +message("PROJECT_VERSION_PATCH: ${PROJECT_VERSION_PATCH}") +message("CMAKE_PROJECT_VERSION: ${CMAKE_PROJECT_VERSION}") +message("CMAKE_PROJECT_VERSION_MAJOR: ${CMAKE_PROJECT_VERSION_MAJOR}") +message("CMAKE_PROJECT_VERSION_MINOR: ${CMAKE_PROJECT_VERSION_MINOR}") +message("CMAKE_PROJECT_VERSION_PATCH: ${CMAKE_PROJECT_VERSION_PATCH}") +message("PROJECT_YEAR: ${PROJECT_YEAR}") +message("PROJECT_MONTH: ${PROJECT_MONTH}") +message("PROJECT_DAY: ${PROJECT_DAY}") + +list(APPEND SUNSHINE_DEFINITIONS PROJECT_NAME="${PROJECT_NAME}") +list(APPEND SUNSHINE_DEFINITIONS PROJECT_VERSION="${PROJECT_VERSION}") +list(APPEND SUNSHINE_DEFINITIONS PROJECT_VERSION_MAJOR="${PROJECT_VERSION_MAJOR}") +list(APPEND SUNSHINE_DEFINITIONS PROJECT_VERSION_MINOR="${PROJECT_VERSION_MINOR}") +list(APPEND SUNSHINE_DEFINITIONS PROJECT_VERSION_PATCH="${PROJECT_VERSION_PATCH}") +list(APPEND SUNSHINE_DEFINITIONS PROJECT_VERSION_COMMIT="${GITHUB_COMMIT}") diff --git a/cmake/prep/special_package_configuration.cmake b/cmake/prep/special_package_configuration.cmake index 5e03f5ad..aaec4add 100644 --- a/cmake/prep/special_package_configuration.cmake +++ b/cmake/prep/special_package_configuration.cmake @@ -12,19 +12,17 @@ elseif(UNIX) # configure the .desktop file set(SUNSHINE_DESKTOP_ICON "apollo.svg") if(${SUNSHINE_BUILD_APPIMAGE}) - configure_file(packaging/linux/AppImage/sunshine.desktop sunshine.desktop @ONLY) + configure_file(packaging/linux/AppImage/${PROJECT_FQDN}.desktop ${PROJECT_FQDN}.desktop @ONLY) elseif(${SUNSHINE_BUILD_FLATPAK}) set(SUNSHINE_DESKTOP_ICON "${PROJECT_FQDN}") - configure_file(packaging/linux/flatpak/sunshine.desktop sunshine.desktop @ONLY) - configure_file(packaging/linux/flatpak/${PROJECT_FQDN}.metainfo.xml - ${PROJECT_FQDN}.metainfo.xml @ONLY) + configure_file(packaging/linux/flatpak/${PROJECT_FQDN}.desktop ${PROJECT_FQDN}.desktop @ONLY) else() - configure_file(packaging/linux/sunshine.desktop sunshine.desktop @ONLY) - configure_file(packaging/linux/sunshine_terminal.desktop sunshine_terminal.desktop @ONLY) + configure_file(packaging/linux/${PROJECT_FQDN}.desktop ${PROJECT_FQDN}.desktop @ONLY) + configure_file(packaging/linux/${PROJECT_FQDN}.terminal.desktop ${PROJECT_FQDN}.terminal.desktop @ONLY) endif() # configure metadata file - configure_file(packaging/linux/sunshine.appdata.xml sunshine.appdata.xml @ONLY) + configure_file(packaging/linux/${PROJECT_FQDN}.metainfo.xml ${PROJECT_FQDN}.metainfo.xml @ONLY) # configure service configure_file(packaging/linux/sunshine.service.in sunshine.service @ONLY) @@ -38,8 +36,6 @@ elseif(UNIX) # configure the flatpak manifest if(${SUNSHINE_CONFIGURE_FLATPAK_MAN}) configure_file(packaging/linux/flatpak/${PROJECT_FQDN}.yml ${PROJECT_FQDN}.yml @ONLY) - configure_file(packaging/linux/flatpak/${PROJECT_FQDN}.metainfo.xml - ${PROJECT_FQDN}.metainfo.xml @ONLY) file(COPY packaging/linux/flatpak/deps/ DESTINATION ${CMAKE_BINARY_DIR}) file(COPY packaging/linux/flatpak/modules DESTINATION ${CMAKE_BINARY_DIR}) file(COPY generated-sources.json DESTINATION ${CMAKE_BINARY_DIR}) diff --git a/cmake/targets/windows.cmake b/cmake/targets/windows.cmake index b7f8fbcf..33a67cfd 100644 --- a/cmake/targets/windows.cmake +++ b/cmake/targets/windows.cmake @@ -3,5 +3,6 @@ set_target_properties(sunshine PROPERTIES LINK_SEARCH_START_STATIC 1) set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll") find_library(ZLIB ZLIB1) list(APPEND SUNSHINE_EXTERNAL_LIBRARIES + $ Windowsapp.lib Wtsapi32.lib) diff --git a/crowdin.yml b/crowdin.yml index 3a5e4281..a1297270 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -17,7 +17,9 @@ "two_letters_code": { # map non-two letter codes here, left side is crowdin designation, right side is babel designation "en-GB": "en_GB", - "en-US": "en_US" + "en-US": "en_US", + "pt-BR": "pt_BR", + "zh-TW": "zh_TW" } }, "update_option": "update_as_unapproved" diff --git a/docker/archlinux.dockerfile b/docker/archlinux.dockerfile index f628ac07..89071f60 100644 --- a/docker/archlinux.dockerfile +++ b/docker/archlinux.dockerfile @@ -28,6 +28,7 @@ ARG CLONE_URL ENV BRANCH=${BRANCH} ENV BUILD_VERSION=${BUILD_VERSION} ENV COMMIT=${COMMIT} +ENV CLONE_URL=${CLONE_URL} SHELL ["/bin/bash", "-o", "pipefail", "-c"] @@ -68,19 +69,16 @@ WORKDIR /build/sunshine/build RUN <<_MAKE #!/bin/bash set -e -if [[ "${BUILD_VERSION}" == '' ]]; then + +sub_version="" +if [[ "${BRANCH}" != "master" ]]; then sub_version=".r${COMMIT}" -else - sub_version="" fi + cmake \ + -DSUNSHINE_CONFIGURE_ONLY=ON \ -DSUNSHINE_CONFIGURE_PKGBUILD=ON \ -DSUNSHINE_SUB_VERSION="${sub_version}" \ - -DGITHUB_CLONE_URL="${CLONE_URL}" \ - -DGITHUB_BRANCH=${BRANCH} \ - -DGITHUB_BUILD_VERSION=${BUILD_VERSION} \ - -DGITHUB_COMMIT="${COMMIT}" \ - -DSUNSHINE_CONFIGURE_ONLY=ON \ /build/sunshine _MAKE @@ -114,14 +112,13 @@ rm -f /build/sunshine/pkg/sunshine-debug*.pkg.tar.zst ls -a _PKGBUILD -FROM scratch AS artifacts - -COPY --link --from=sunshine-build /build/sunshine/pkg/sunshine*.pkg.tar.zst /sunshine.pkg.tar.zst -COPY --link --from=sunshine-build /build/sunshine/sunshine.pkg.tar.gz /sunshine.pkg.tar.gz - FROM sunshine-base AS sunshine -COPY --link --from=artifacts /sunshine.pkg.tar.zst / +COPY --link --from=sunshine-build /build/sunshine/pkg/sunshine*.pkg.tar.zst /sunshine.pkg.tar.zst + +# artifacts to be extracted in CI +COPY --link --from=sunshine-build /build/sunshine/pkg/sunshine*.pkg.tar.zst /artifacts/sunshine.pkg.tar.zst +COPY --link --from=sunshine-build /build/sunshine/sunshine.pkg.tar.gz /artifacts/sunshine.pkg.tar.gz # install sunshine RUN <<_INSTALL_SUNSHINE diff --git a/docker/debian-bookworm.dockerfile b/docker/debian-bookworm.dockerfile index 84edc4c9..f88cea05 100644 --- a/docker/debian-bookworm.dockerfile +++ b/docker/debian-bookworm.dockerfile @@ -51,16 +51,17 @@ Xvfb ${DISPLAY} -screen 0 1024x768x24 & ./test_sunshine --gtest_color=yes _TEST -FROM scratch AS artifacts +FROM sunshine-base AS sunshine + ARG BASE ARG TAG ARG TARGETARCH -COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb -FROM sunshine-base AS sunshine +# artifacts to be extracted in CI +COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /artifacts/sunshine-${BASE}-${TAG}-${TARGETARCH}.deb # copy deb from builder -COPY --link --from=artifacts /sunshine*.deb /sunshine.deb +COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine.deb # install sunshine RUN <<_INSTALL_SUNSHINE diff --git a/docker/ubuntu-22.04.dockerfile b/docker/ubuntu-22.04.dockerfile index 24ceda2b..7bb24eb8 100644 --- a/docker/ubuntu-22.04.dockerfile +++ b/docker/ubuntu-22.04.dockerfile @@ -51,16 +51,17 @@ Xvfb ${DISPLAY} -screen 0 1024x768x24 & ./test_sunshine --gtest_color=yes _TEST -FROM scratch AS artifacts +FROM sunshine-base AS sunshine + ARG BASE ARG TAG ARG TARGETARCH -COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb -FROM sunshine-base AS sunshine +# artifacts to be extracted in CI +COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /artifacts/sunshine-${BASE}-${TAG}-${TARGETARCH}.deb # copy deb from builder -COPY --link --from=artifacts /sunshine*.deb /sunshine.deb +COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine.deb # install sunshine RUN <<_INSTALL_SUNSHINE diff --git a/docker/ubuntu-24.04.dockerfile b/docker/ubuntu-24.04.dockerfile index 1b7c0d6e..984cfdcb 100644 --- a/docker/ubuntu-24.04.dockerfile +++ b/docker/ubuntu-24.04.dockerfile @@ -51,16 +51,17 @@ Xvfb ${DISPLAY} -screen 0 1024x768x24 & ./test_sunshine --gtest_color=yes _TEST -FROM scratch AS artifacts +FROM sunshine-base AS sunshine + ARG BASE ARG TAG ARG TARGETARCH -COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb -FROM sunshine-base AS sunshine +# artifacts to be extracted in CI +COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /artifacts/sunshine-${BASE}-${TAG}-${TARGETARCH}.deb # copy deb from builder -COPY --link --from=artifacts /sunshine*.deb /sunshine.deb +COPY --link --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine.deb # install sunshine RUN <<_INSTALL_SUNSHINE diff --git a/docs/api.js b/docs/api.js index 5f887e94..eff63583 100644 --- a/docs/api.js +++ b/docs/api.js @@ -9,7 +9,7 @@ function generateExamples(endpoint, method, body = null) { } return { - cURL: `curl -u user:pass -X ${method.trim()} -k https://localhost:47990${endpoint.trim()}${curlBodyString}`, + cURL: `curl -u user:pass -H "Content-Type: application/json" -X ${method.trim()} -k https://localhost:47990${endpoint.trim()}${curlBodyString}`, Python: `import json import requests from requests.auth import HTTPBasicAuth @@ -30,6 +30,7 @@ requests.${method.trim().toLowerCase()}( .then(data => console.log(data));`, PowerShell: `Invoke-RestMethod \` -SkipCertificateCheck \` + -ContentType 'application/json' \` -Uri 'https://localhost:47990${endpoint.trim()}' \` -Method ${method.trim()} \` -Headers @{Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes('user:pass'))} diff --git a/docs/configuration.md b/docs/configuration.md index 2b2eaf83..0d860111 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -57,10 +57,14 @@ editing the `conf` file in a text editor. Use the examples as reference. @endcode - Choices + Choices bg Bulgarian + + cs + Czech + de German @@ -129,6 +133,10 @@ editing the `conf` file in a text editor. Use the examples as reference. zh Chinese (Simplified) + + zh_TW + Chinese (Traditional) + ### sunshine_name @@ -1384,35 +1392,6 @@ editing the `conf` file in a text editor. Use the examples as reference. -### min_fps_factor - - - - - - - - - - - - - - - - - - -
Description - Sunshine will use this factor to calculate the minimum time between frames. Increasing this value may help - when streaming mostly static content. - @warning{Higher values will consume more bandwidth.} -
Default@code{} - 1 - @endcode
Range1-3
Example@code{} - min_fps_factor = 1 - @endcode
- ## Network ### upnp diff --git a/docs/getting_started.md b/docs/getting_started.md index 88f40844..f3036239 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -56,27 +56,28 @@ need to install CUDA.} 12.0.0 - 525.60.13 + 525.60.13 50;52;60;61;62;70;72;75;80;86;87;89;90 sunshine-debian-bookworm-{arch}.deb - - 12.5.1 - sunshine.pkg.tar.zst - 12.6.2 560.35.03 sunshine_{arch}.flatpak - Sunshine (copr - Fedora 40/41) + Sunshine (copr - Fedora 41) 12.8.1 570.124.06 Sunshine (copr - Fedora 42) + + 12.9.1 + 575.57.08 + sunshine.pkg.tar.zst + #### AppImage @@ -283,7 +284,7 @@ brew uninstall sunshine #### Installer (recommended) 1. Download and install - [sunshine-windows-installer.exe](https://github.com/LizardByte/Sunshine/releases/latest/download/sunshine-windows-installer.exe) + [Sunshine-Windows-AMD64-installer.exe](https://github.com/LizardByte/Sunshine/releases/latest/download/Sunshine-Windows-AMD64-installer.exe) @attention{You should carefully select or unselect the options you want to install. Do not blindly install or enable features.} @@ -297,7 +298,7 @@ overflow menu. Different versions of Windows may provide slightly different step recommended for most users. No support will be provided!} 1. Download and extract - [sunshine-windows-portable.zip](https://github.com/LizardByte/Sunshine/releases/latest/download/sunshine-windows-portable.zip) + [Sunshine-Windows-AMD64-portable.zip](https://github.com/LizardByte/Sunshine/releases/latest/download/Sunshine-Windows-AMD64-portable.zip) 2. Open command prompt as administrator 3. Firewall rules diff --git a/gh-pages-template/assets/img/screenshots/01-sunshine-welcome-page.png b/gh-pages-template/assets/img/screenshots/01-sunshine-welcome-page.png new file mode 100644 index 00000000..97c5a5af Binary files /dev/null and b/gh-pages-template/assets/img/screenshots/01-sunshine-welcome-page.png differ diff --git a/package.json b/package.json index 5e159b84..0e7c10d4 100644 --- a/package.json +++ b/package.json @@ -8,14 +8,14 @@ "serve": "serve ./tests/fixtures/http --no-port-switching" }, "dependencies": { - "@lizardbyte/shared-web": "2025.326.11214", - "vue": "3.5.14", - "vue-i18n": "11.1.4" + "@lizardbyte/shared-web": "2025.626.181239", + "vue": "3.5.17", + "vue-i18n": "11.1.9" }, "devDependencies": { - "@codecov/vite-plugin": "1.9.0", + "@codecov/vite-plugin": "1.9.1", "@vitejs/plugin-vue": "4.6.2", - "serve": "14.2.3", + "serve": "14.2.4", "vite": "4.5.14", "vite-plugin-ejs": "1.6.4" } diff --git a/packaging/linux/AppImage/sunshine.desktop b/packaging/linux/AppImage/dev.lizardbyte.app.Sunshine.desktop similarity index 86% rename from packaging/linux/AppImage/sunshine.desktop rename to packaging/linux/AppImage/dev.lizardbyte.app.Sunshine.desktop index 911735c8..91da7902 100644 --- a/packaging/linux/AppImage/sunshine.desktop +++ b/packaging/linux/AppImage/dev.lizardbyte.app.Sunshine.desktop @@ -1,13 +1,13 @@ [Desktop Entry] -Type=Application -Name=@PROJECT_NAME@ -Exec=sunshine -Version=1.0 +Categories=RemoteAccess;Network; Comment=@PROJECT_DESCRIPTION@ +Exec=sunshine Icon=sunshine Keywords=gamestream;stream;moonlight;remote play; -Categories=AudioVideo;Network;RemoteAccess; +Name=@PROJECT_NAME@ Terminal=true +Type=Application +Version=1.0 +X-AppImage-Arch=x86_64 X-AppImage-Name=sunshine X-AppImage-Version=@PROJECT_VERSION@ -X-AppImage-Arch=x86_64 diff --git a/packaging/linux/Arch/PKGBUILD b/packaging/linux/Arch/PKGBUILD index e8729163..eab48e31 100644 --- a/packaging/linux/Arch/PKGBUILD +++ b/packaging/linux/Arch/PKGBUILD @@ -10,7 +10,7 @@ url=@PROJECT_HOMEPAGE_URL@ license=('GPL-3.0-only') install=sunshine.install -_gcc_version=13 +_gcc_version=14 depends=( 'avahi' @@ -36,7 +36,10 @@ depends=( ) makedepends=( + 'appstream' + 'appstream-glib' 'cmake' + 'desktop-file-utils' 'cuda' "gcc${_gcc_version}" 'git' @@ -64,7 +67,7 @@ prepare() { build() { export BRANCH="@GITHUB_BRANCH@" - export BUILD_VERSION="@GITHUB_BUILD_VERSION@" + export BUILD_VERSION="@BUILD_VERSION@" export COMMIT="@GITHUB_COMMIT@" export CC="gcc-${_gcc_version}" @@ -86,6 +89,11 @@ build() { -D SUNSHINE_PUBLISHER_WEBSITE='https://app.lizardbyte.dev' \ -D SUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support' + appstreamcli validate "build/dev.lizardbyte.app.Sunshine.metainfo.xml" + appstream-util validate "build/dev.lizardbyte.app.Sunshine.metainfo.xml" + desktop-file-validate "build/dev.lizardbyte.app.Sunshine.desktop" + desktop-file-validate "build/dev.lizardbyte.app.Sunshine.terminal.desktop" + make -C build } diff --git a/packaging/linux/sunshine.desktop b/packaging/linux/dev.lizardbyte.app.Sunshine.desktop similarity index 71% rename from packaging/linux/sunshine.desktop rename to packaging/linux/dev.lizardbyte.app.Sunshine.desktop index bc8a995c..29252ae5 100644 --- a/packaging/linux/sunshine.desktop +++ b/packaging/linux/dev.lizardbyte.app.Sunshine.desktop @@ -1,15 +1,15 @@ [Desktop Entry] -Type=Application -Name=@PROJECT_NAME@ -Exec=/usr/bin/env systemctl start --u sunshine -Version=1.0 +Actions=RunInTerminal; +Categories=RemoteAccess;Network; Comment=@PROJECT_DESCRIPTION@ +Exec=/usr/bin/env systemctl start --u sunshine Icon=@SUNSHINE_DESKTOP_ICON@ Keywords=gamestream;stream;moonlight;remote play; -Categories=AudioVideo;Network;RemoteAccess; -Actions=RunInTerminal; +Name=@PROJECT_NAME@ +Type=Application +Version=1.0 [Desktop Action RunInTerminal] -Name=Run in Terminal +Exec=gio launch @CMAKE_INSTALL_FULL_DATAROOTDIR@/applications/@PROJECT_FQDN@.terminal.desktop Icon=application-x-executable -Exec=gio launch @CMAKE_INSTALL_FULL_DATAROOTDIR@/applications/sunshine_terminal.desktop +Name=Run in Terminal diff --git a/packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.metainfo.xml b/packaging/linux/dev.lizardbyte.app.Sunshine.metainfo.xml similarity index 65% rename from packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.metainfo.xml rename to packaging/linux/dev.lizardbyte.app.Sunshine.metainfo.xml index fe5f9d76..6be35169 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.metainfo.xml +++ b/packaging/linux/dev.lizardbyte.app.Sunshine.metainfo.xml @@ -1,8 +1,8 @@ - + @PROJECT_FQDN@ - @CMAKE_PROJECT_NAME@ + @PROJECT_NAME@ @PROJECT_BRIEF_DESCRIPTION@ CC0-1.0 @@ -15,39 +15,49 @@ gamepad - https://github.com/LizardByte/Sunshine/issues @PROJECT_HOMEPAGE_URL@ + https://github.com/LizardByte/Sunshine/issues + https://docs.lizardbyte.dev/projects/sunshine/latest/md_docs_2troubleshooting.html + https://docs.lizardbyte.dev/projects/sunshine https://app.lizardbyte.dev/#Donate - https://app.lizardbyte.dev/support https://translate.lizardbyte.dev - https://docs.lizardbyte.dev - https://github.com/LizardByte/Sunshine + https://app.lizardbyte.dev/support

@PROJECT_LONG_DESCRIPTION@

-

NOTE: Sunshine requires additional installation steps.

+

NOTE: Sunshine requires additional installation steps (Flatpak).

flatpak run --command=additional-install.sh @PROJECT_FQDN@

NOTE: Sunshine uses a self-signed certificate. The web browser will report it as not secure, but it is safe.

-

NOTE: KMS Grab (Optional)

+

NOTE: KMS Grab (Flatpak)

sudo -i PULSE_SERVER=unix:/run/user/$(id -u $whoami)/pulse/native flatpak run @PROJECT_FQDN@

- + + +

+ See the full changelog on GitHub + + +

+
+
- LizardByte + + LizardByte + - https://app.lizardbyte.dev/Sunshine/assets/img/banners/AdobeStock_305732536_1920x1280.jpg - Sunshine + https://app.lizardbyte.dev/Sunshine/assets/img/screenshots/01-sunshine-welcome-page.png + Sunshine welcome page diff --git a/packaging/linux/sunshine_terminal.desktop b/packaging/linux/dev.lizardbyte.app.Sunshine.terminal.desktop similarity index 100% rename from packaging/linux/sunshine_terminal.desktop rename to packaging/linux/dev.lizardbyte.app.Sunshine.terminal.desktop diff --git a/packaging/linux/fedora/Sunshine.spec b/packaging/linux/fedora/Sunshine.spec index 4d2d1d02..d98bbbf1 100644 --- a/packaging/linux/fedora/Sunshine.spec +++ b/packaging/linux/fedora/Sunshine.spec @@ -15,8 +15,11 @@ License: GPLv3-only URL: https://github.com/LizardByte/Sunshine Source0: tarball.tar.gz +BuildRequires: appstream # BuildRequires: boost-devel >= 1.86.0 BuildRequires: cmake >= 3.25.0 +BuildRequires: desktop-file-utils +BuildRequires: libappstream-glib BuildRequires: libayatana-appindicator3-devel BuildRequires: libcap-devel BuildRequires: libcurl-devel @@ -197,6 +200,11 @@ cmake "${cmake_args[@]}" make -j$(nproc) -C "%{_builddir}/Sunshine/build" %check +# validate the metainfo file +appstreamcli validate %{buildroot}%{_metainfodir}/*.metainfo.xml +appstream-util validate %{buildroot}%{_metainfodir}/*.metainfo.xml +desktop-file-validate %{buildroot}%{_datadir}/applications/*.desktop + # run tests cd %{_builddir}/Sunshine/build xvfb-run ./tests/test_sunshine @@ -252,14 +260,14 @@ rm -f /usr/lib/modules-load.d/uhid.conf %{_modulesloaddir}/uhid.conf # Desktop entries -%{_datadir}/applications/sunshine*.desktop +%{_datadir}/applications/*.desktop # Icons %{_datadir}/icons/hicolor/scalable/apps/apollo.svg %{_datadir}/icons/hicolor/scalable/status/apollo*.svg # Metainfo -%{_datadir}/metainfo/sunshine.appdata.xml +%{_datadir}/metainfo/*.metainfo.xml # Assets %{_datadir}/sunshine/** diff --git a/packaging/linux/flatpak/deps/flatpak-builder-tools b/packaging/linux/flatpak/deps/flatpak-builder-tools index fe89c19b..903919f8 160000 --- a/packaging/linux/flatpak/deps/flatpak-builder-tools +++ b/packaging/linux/flatpak/deps/flatpak-builder-tools @@ -1 +1 @@ -Subproject commit fe89c19b147432d896f7c1c686630a992132d583 +Subproject commit 903919f82f4cd6356bb4e9afe2755e44e8d8d7da diff --git a/packaging/linux/flatpak/deps/shared-modules b/packaging/linux/flatpak/deps/shared-modules index 1f8e591b..756091e3 160000 --- a/packaging/linux/flatpak/deps/shared-modules +++ b/packaging/linux/flatpak/deps/shared-modules @@ -1 +1 @@ -Subproject commit 1f8e591b263eef8a0dc04929f2da135af59fac3c +Subproject commit 756091e3d5d6582bec4e270184319e59c04ab365 diff --git a/packaging/linux/flatpak/sunshine.desktop b/packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.desktop similarity index 81% rename from packaging/linux/flatpak/sunshine.desktop rename to packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.desktop index eca745ef..a6ab9ea5 100644 --- a/packaging/linux/flatpak/sunshine.desktop +++ b/packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Categories=AudioVideo;Network;RemoteAccess; +Categories=RemoteAccess;Network; Comment=@PROJECT_DESCRIPTION@ Exec=sunshine.sh Icon=@SUNSHINE_DESKTOP_ICON@ diff --git a/packaging/linux/flatpak/exceptions.json b/packaging/linux/flatpak/exceptions.json index 957f7384..30ea0d21 100644 --- a/packaging/linux/flatpak/exceptions.json +++ b/packaging/linux/flatpak/exceptions.json @@ -1,8 +1,9 @@ { "dev.lizardbyte.app.Sunshine": [ - "appstream-missing-screenshots", + "appstream-external-screenshot-url", "appstream-screenshots-not-mirrored-in-ostree", "external-gitmodule-url-found", - "finish-args-flatpak-spawn-access" + "finish-args-flatpak-spawn-access", + "finish-args-home-filesystem-access" ] } diff --git a/packaging/linux/flatpak/modules/avahi.json b/packaging/linux/flatpak/modules/avahi.json index 3e0998c7..faa4cc6e 100644 --- a/packaging/linux/flatpak/modules/avahi.json +++ b/packaging/linux/flatpak/modules/avahi.json @@ -32,15 +32,12 @@ "sources": [ { "type": "git", - "url": "https://salsa.debian.org/utopia-team/avahi.git", - "commit": "1412c015d348166d58ea9c192239b00769eae24e", - "tag": "debian/0.8-13", + "url": "https://github.com/avahi/avahi.git", + "commit": "f060abee2807c943821d88839c013ce15db17b58", + "tag": "v0.8", "x-checker-data": { "type": "git", - "tag-pattern": "^debian\\/(\\d.+)$", - "versions": { - "<": "0.9" - } + "tag-pattern": "^v([\\d.]+)$" } }, { diff --git a/packaging/linux/flatpak/modules/libevdev.json b/packaging/linux/flatpak/modules/libevdev.json index e3cbb27e..394d78b5 100644 --- a/packaging/linux/flatpak/modules/libevdev.json +++ b/packaging/linux/flatpak/modules/libevdev.json @@ -5,22 +5,24 @@ "-Ddocumentation=disabled", "-Dtests=disabled" ], - "cleanup": [ - "/bin" - ], "sources": [ { "type": "git", - "url": "https://salsa.debian.org/debian/libevdev.git", - "commit": "1aa7baa233d6df4cee6a66fbc61bb5ffc8b6e88d", - "tag": "debian/1.13.0+dfsg-1", + "url": "https://github.com/LizardByte-infrastructure/libevdev.git", + "commit": "ac0056961c3332a260db063ab4fccc7747638a1d", + "tag": "libevdev-1.13.4", "x-checker-data": { - "type": "git", - "tag-pattern": "^debian\\/(\\d.\\d+\\.\\d+)", - "versions": { - "<": "1.13.1" - } + "type": "anitya", + "project-id": 20540, + "stable-only": true, + "tag-template": "libevdev-$version" } } + ], + "cleanup": [ + "/bin", + "/include", + "/lib/pkgconfig", + "/share" ] } diff --git a/packaging/linux/flatpak/modules/libnotify.json b/packaging/linux/flatpak/modules/libnotify.json index 8bede092..664d76e9 100644 --- a/packaging/linux/flatpak/modules/libnotify.json +++ b/packaging/linux/flatpak/modules/libnotify.json @@ -11,15 +11,12 @@ "sources": [ { "type": "git", - "url": "https://salsa.debian.org/gnome-team/libnotify.git", - "commit": "ccf2f62ef0a4b264dd4eff32cab70a3e213ceb1a", - "tag": "debian/0.8.1-1", + "url": "https://github.com/LizardByte-infrastructure/libnotify.git", + "commit": "131aad01ff5f563b4863becbb6ed84dac6e75d5a", + "tag": "0.8.6", "x-checker-data": { "type": "git", - "tag-pattern": "^debian\\/(\\d.+)$", - "versions": { - "<": "0.8.2" - } + "tag-pattern": "^([\\d.]+)$" } } ] diff --git a/packaging/linux/flatpak/modules/miniupnpc.json b/packaging/linux/flatpak/modules/miniupnpc.json index 1a3ce99b..2ab0c2dc 100644 --- a/packaging/linux/flatpak/modules/miniupnpc.json +++ b/packaging/linux/flatpak/modules/miniupnpc.json @@ -1,25 +1,33 @@ { "name": "miniupnpc", "buildsystem": "cmake-ninja", + "builddir": true, "config-opts": [ - "-DCMAKE_BUILD_TYPE=Release", - "-DUPNPC_BUILD_SAMPLE=OFF", + "-DCMAKE_BUILD_TYPE=RelWithDebInfo", + "-DUPNPC_BUILD_STATIC=OFF", "-DUPNPC_BUILD_SHARED=ON", - "-DUPNPC_BUILD_TESTS=OFF" + "-DUPNPC_BUILD_TESTS=OFF", + "-DUPNPC_BUILD_SAMPLE=OFF" ], "sources": [ { - "type": "git", - "url": "https://salsa.debian.org/miniupnp-team/miniupnpc.git", - "commit": "c5fe3aa794e92a503cecec6a4071eb6d310b4e42", - "tag": "debian/2.2.4-1", + "url": "https://miniupnp.tuxfamily.org/files/miniupnpc-2.3.3.tar.gz", + "sha256": "d52a0afa614ad6c088cc9ddff1ae7d29c8c595ac5fdd321170a05f41e634bd1a", "x-checker-data": { - "type": "git", - "tag-pattern": "^debian\\/(\\d.+)$", - "versions": { - "<": "2.2.5" - } - } + "type": "anitya", + "project-id": 1986, + "stable-only": true, + "url-template": "https://miniupnp.tuxfamily.org/files/miniupnpc-$version.tar.gz" + }, + "type": "archive" } + ], + "cleanup": [ + "/share/man", + "/lib/pkgconfig", + "/lib/libminiupnpc.so", + "/lib/cmake", + "/include", + "/bin/external-ip.sh" ] } diff --git a/packaging/linux/flatpak/modules/numactl.json b/packaging/linux/flatpak/modules/numactl.json index b03403be..b0a48bea 100644 --- a/packaging/linux/flatpak/modules/numactl.json +++ b/packaging/linux/flatpak/modules/numactl.json @@ -1,22 +1,24 @@ { "name": "numactl", - "buildsystem": "autotools", - "cleanup": [ - "/bin" - ], "sources": [ { "type": "git", - "url": "https://salsa.debian.org/debian/numactl.git", - "commit": "640bb34497702f9aaeb8af1b491f32b91d03ec80", - "tag": "debian/2.0.16-1", + "url": "https://github.com/numactl/numactl.git", + "tag": "v2.0.19", + "commit": "3bc85e37d5a30da6790cb7e8bb488bb8f679170f", "x-checker-data": { "type": "git", - "tag-pattern": "^debian\\/(\\d.+)$", - "versions": { - "<": "2.0.17" - } + "tag-pattern": "^v([\\d.]+)$" } } + ], + "rm-configure": true, + "cleanup": [ + "/include", + "/lib/pkgconfig", + "/lib/*.a", + "/lib/*.la", + "/lib/*.so", + "/share/man" ] } diff --git a/packaging/linux/sunshine.appdata.xml b/packaging/linux/sunshine.appdata.xml deleted file mode 100644 index 93757eb6..00000000 --- a/packaging/linux/sunshine.appdata.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - @PROJECT_NAME@.desktop - @PROJECT_LICENSE@ - @PROJECT_LICENSE@ - @PROJECT_NAME@ - @CMAKE_PROJECT_HOMEPAGE_URL@ - @PROJECT_BRIEF_DESCRIPTION@ - -

- @PROJECT_LONG_DESCRIPTION@ -

-
- - - https://app.lizardbyte.dev/Sunshine/assets/images/AdobeStock_305732536_1920x1280.jpg - Sunshine - - -
diff --git a/packaging/sunshine.rb b/packaging/sunshine.rb index f13a5a38..ad7b46ee 100644 --- a/packaging/sunshine.rb +++ b/packaging/sunshine.rb @@ -6,7 +6,7 @@ class @PROJECT_NAME@ < Formula homepage "@PROJECT_HOMEPAGE_URL@" url "@GITHUB_CLONE_URL@", tag: "@GITHUB_TAG@" - version "@FORMULA_VERSION@" + version "@BUILD_VERSION@" license all_of: ["GPL-3.0-only"] head "@GITHUB_CLONE_URL@", branch: "@GITHUB_DEFAULT_BRANCH@" @@ -319,31 +319,31 @@ index 5b3638d..aca9481 100644 run [opt_bin/"sunshine", "~/.config/sunshine/sunshine.conf"] end - def caveats - caveats_message = <<~EOS - Thanks for installing @PROJECT_NAME@! - - To get started, review the documentation at: - https://docs.lizardbyte.dev/projects/sunshine - EOS - + def post_install if OS.linux? - caveats_message += <<~EOS + opoo <<~EOS ATTENTION: To complete installation, you must run the following command: `sudo #{bin}/postinst` EOS end if OS.mac? - caveats_message += <<~EOS + opoo <<~EOS Sunshine can only access microphones on macOS due to system limitations. To stream system audio use "Soundflower" or "BlackHole". Gamepads are not currently supported on macOS. EOS end + end - caveats_message + def caveats + <<~EOS + Thanks for installing @PROJECT_NAME@! + + To get started, review the documentation at: + https://docs.lizardbyte.dev/projects/sunshine + EOS end test do diff --git a/scripts/_locale.py b/scripts/_locale.py index 84ae3385..0a2cda87 100644 --- a/scripts/_locale.py +++ b/scripts/_locale.py @@ -25,6 +25,7 @@ year = datetime.datetime.now().year # target locales target_locales = [ 'bg', # Bulgarian + 'cs', # Czech 'de', # German 'en', # English 'en_GB', # English (United Kingdom) @@ -42,6 +43,7 @@ target_locales = [ 'tr', # Turkish 'uk', # Ukrainian 'zh', # Chinese + 'zh_TW', # Chinese (Traditional) ] diff --git a/scripts/linux_build.sh b/scripts/linux_build.sh index eb901b8b..66d0e9c8 100644 --- a/scripts/linux_build.sh +++ b/scripts/linux_build.sh @@ -138,9 +138,12 @@ function add_arch_deps() { function add_debian_based_deps() { dependencies+=( + "appstream" + "appstream-util" "bison" # required if we need to compile doxygen "build-essential" "cmake" + "desktop-file-utils" "doxygen" "flex" # required if we need to compile doxygen "gcc-${gcc_version}" @@ -201,13 +204,16 @@ function add_ubuntu_deps() { function add_fedora_deps() { dependencies+=( + "appstream" "cmake" + "desktop-file-utils" "doxygen" "gcc${gcc_version}" "gcc${gcc_version}-c++" "git" "graphviz" "libappindicator-gtk3-devel" + "libappstream-glib" "libcap-devel" "libcurl-devel" "libdrm-devel" @@ -392,7 +398,7 @@ function run_install() { "gcc-ranlib" ) - #set gcc version based on distros + #set gcc version based on distros if [ "$distro" == "arch" ]; then export CC=gcc-14 export CXX=g++-14 @@ -466,6 +472,8 @@ function run_install() { install_cuda cmake_args+=("-DSUNSHINE_ENABLE_CUDA=ON") cmake_args+=("-DCMAKE_CUDA_COMPILER:PATH=$nvcc_path") + else + cmake_args+=("-DSUNSHINE_ENABLE_CUDA=OFF") fi # Cmake stuff here @@ -473,6 +481,16 @@ function run_install() { echo "cmake args:" echo "${cmake_args[@]}" cmake "${cmake_args[@]}" + + # Run appstream validation, etc. + appstreamcli validate "build/dev.lizardbyte.app.Sunshine.metainfo.xml" + appstream-util validate "build/dev.lizardbyte.app.Sunshine.metainfo.xml" + desktop-file-validate "build/dev.lizardbyte.app.Sunshine.desktop" + if [ "$appimage_build" == 0 ]; then + desktop-file-validate "build/dev.lizardbyte.app.Sunshine.terminal.desktop" + fi + + # Build the project ninja -C "build" # Create the package @@ -570,6 +588,15 @@ elif grep -q "Ubuntu 24.04" /etc/os-release; then cuda_build="520.61.05" gcc_version="11" nvm_node=0 +elif grep -q "Ubuntu 25.04" /etc/os-release; then + distro="ubuntu" + version="25.04" + package_update_command="${sudo_cmd} apt-get update" + package_install_command="${sudo_cmd} apt-get install -y" + cuda_version="11.8.0" + cuda_build="520.61.05" + gcc_version="11" + nvm_node=0 else echo "Unsupported Distro or Version" exit 1 diff --git a/src/config.cpp b/src/config.cpp index 119e95df..e68691bb 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -509,7 +509,6 @@ namespace config { {} // wa }, // display_device - 1, // min_fps_factor 0, // max_bitrate "1920x1080x60", // fallback_mode @@ -1195,7 +1194,6 @@ namespace config { video.dd.wa.hdr_toggle_delay = std::chrono::milliseconds {value}; } - int_between_f(vars, "min_fps_factor", video.min_fps_factor, {1, 3}); int_f(vars, "max_bitrate", video.max_bitrate); string_f(vars, "fallback_mode", video.fallback_mode); bool_f(vars, "isolated_virtual_display_option", video.isolated_virtual_display_option); @@ -1304,6 +1302,7 @@ namespace config { string_restricted_f(vars, "locale", config::sunshine.locale, { "bg"sv, // Bulgarian + "cs"sv, // Czech "de"sv, // German "en"sv, // English "en_GB"sv, // English (UK) @@ -1321,6 +1320,7 @@ namespace config { "tr"sv, // Turkish "uk"sv, // Ukrainian "zh"sv, // Chinese + "zh_TW"sv, // Chinese (Traditional) }); std::string log_level_string; diff --git a/src/config.h b/src/config.h index 978f81c1..49de6bc1 100644 --- a/src/config.h +++ b/src/config.h @@ -143,7 +143,6 @@ namespace config { workarounds_t wa; } dd; - int min_fps_factor; // Minimum fps target, determines minimum frame time int max_bitrate; // Maximum bitrate, sets ceiling in kbps for bitrate requested from client std::string fallback_mode; diff --git a/src/confighttp.cpp b/src/confighttp.cpp index e6722b43..56719aa5 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -38,7 +38,6 @@ #include "process.h" #include "utility.h" #include "uuid.h" -#include "version.h" #ifdef _WIN32 #include "platform/windows/utils.h" @@ -89,6 +88,8 @@ namespace confighttp { void send_response(resp_https_t response, const nlohmann::json &output_tree) { SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "application/json"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(output_tree.dump(), headers); } @@ -106,7 +107,9 @@ namespace confighttp { tree["status"] = false; tree["error"] = "Unauthorized"; const SimpleWeb::CaseInsensitiveMultimap headers { - {"Content-Type", "application/json"} + {"Content-Type", "application/json"}, + {"X-Frame-Options", "DENY"}, + {"Content-Security-Policy", "frame-ancestors 'none';"} }; response->write(code, tree.dump(), headers); } @@ -121,7 +124,9 @@ namespace confighttp { auto address = net::addr_to_normalized_string(request->remote_endpoint().address()); BOOST_LOG(info) << "Web UI: ["sv << address << "] -- redirecting"sv; const SimpleWeb::CaseInsensitiveMultimap headers { - {"Location", path} + {"Location", path}, + {"X-Frame-Options", "DENY"}, + {"Content-Security-Policy", "frame-ancestors 'none';"} }; response->write(SimpleWeb::StatusCode::redirection_temporary_redirect, headers); } @@ -218,6 +223,9 @@ namespace confighttp { tree["error"] = "Not Found"; SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "application/json"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); + response->write(code, tree.dump(), headers); } @@ -235,6 +243,9 @@ namespace confighttp { tree["error"] = error_message; SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "application/json"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); + response->write(code, tree.dump(), headers); } @@ -252,10 +263,25 @@ namespace confighttp { return false; } - if (requestContentType->second != contentType) { + // Extract the media type part before any parameters (e.g., charset) + std::string actualContentType = requestContentType->second; + size_t semicolonPos = actualContentType.find(';'); + if (semicolonPos != std::string::npos) { + actualContentType = actualContentType.substr(0, semicolonPos); + } + + // Trim whitespace and convert to lowercase for case-insensitive comparison + boost::algorithm::trim(actualContentType); + boost::algorithm::to_lower(actualContentType); + + std::string expectedContentType(contentType); + boost::algorithm::to_lower(expectedContentType); + + if (actualContentType != expectedContentType) { bad_request(response, request, "Content type mismatch"); return false; } + return true; return true; } @@ -273,9 +299,10 @@ namespace confighttp { print_req(request); std::string content = file_handler::read_file(WEB_DIR "index.html"); - SimpleWeb::CaseInsensitiveMultimap headers { - {"Content-Type", "text/html; charset=utf-8"} - }; + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "text/html; charset=utf-8"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(content, headers); } @@ -292,9 +319,10 @@ namespace confighttp { print_req(request); std::string content = file_handler::read_file(WEB_DIR "pin.html"); - SimpleWeb::CaseInsensitiveMultimap headers { - {"Content-Type", "text/html; charset=utf-8"} - }; + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "text/html; charset=utf-8"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(content, headers); } @@ -311,10 +339,11 @@ namespace confighttp { print_req(request); std::string content = file_handler::read_file(WEB_DIR "apps.html"); - SimpleWeb::CaseInsensitiveMultimap headers { - {"Content-Type", "text/html; charset=utf-8"}, - {"Access-Control-Allow-Origin", "https://images.igdb.com/"} - }; + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "text/html; charset=utf-8"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); + headers.emplace("Access-Control-Allow-Origin", "https://images.igdb.com/"); response->write(content, headers); } @@ -331,9 +360,10 @@ namespace confighttp { print_req(request); std::string content = file_handler::read_file(WEB_DIR "clients.html"); - SimpleWeb::CaseInsensitiveMultimap headers { - {"Content-Type", "text/html; charset=utf-8"} - }; + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "text/html; charset=utf-8"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(content, headers); } @@ -350,9 +380,10 @@ namespace confighttp { print_req(request); std::string content = file_handler::read_file(WEB_DIR "config.html"); - SimpleWeb::CaseInsensitiveMultimap headers { - {"Content-Type", "text/html; charset=utf-8"} - }; + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "text/html; charset=utf-8"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(content, headers); } @@ -369,9 +400,10 @@ namespace confighttp { print_req(request); std::string content = file_handler::read_file(WEB_DIR "password.html"); - SimpleWeb::CaseInsensitiveMultimap headers { - {"Content-Type", "text/html; charset=utf-8"} - }; + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "text/html; charset=utf-8"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(content, headers); } @@ -393,9 +425,10 @@ namespace confighttp { } std::string content = file_handler::read_file(WEB_DIR "login.html"); - SimpleWeb::CaseInsensitiveMultimap headers { - {"Content-Type", "text/html; charset=utf-8"} - }; + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "text/html; charset=utf-8"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(content, headers); } @@ -413,9 +446,10 @@ namespace confighttp { } std::string content = file_handler::read_file(WEB_DIR "welcome.html"); - SimpleWeb::CaseInsensitiveMultimap headers { - {"Content-Type", "text/html; charset=utf-8"} - }; + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "text/html; charset=utf-8"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(content, headers); } @@ -432,9 +466,10 @@ namespace confighttp { print_req(request); std::string content = file_handler::read_file(WEB_DIR "troubleshooting.html"); - SimpleWeb::CaseInsensitiveMultimap headers { - {"Content-Type", "text/html; charset=utf-8"} - }; + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "text/html; charset=utf-8"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(content, headers); } @@ -447,9 +482,10 @@ namespace confighttp { print_req(request); std::ifstream in(WEB_DIR "images/apollo.ico", std::ios::binary); - SimpleWeb::CaseInsensitiveMultimap headers { - {"Content-Type", "image/x-icon"} - }; + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "image/x-icon"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(SimpleWeb::StatusCode::success_ok, in, headers); } @@ -465,9 +501,10 @@ namespace confighttp { print_req(request); std::ifstream in(WEB_DIR "images/logo-apollo-45.png", std::ios::binary); - SimpleWeb::CaseInsensitiveMultimap headers { - {"Content-Type", "image/png"} - }; + SimpleWeb::CaseInsensitiveMultimap headers; + headers.emplace("Content-Type", "image/png"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(SimpleWeb::StatusCode::success_ok, in, headers); } @@ -518,6 +555,8 @@ namespace confighttp { } SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", mimeType->second); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); std::ifstream in(filePath.string(), std::ios::binary); response->write(SimpleWeb::StatusCode::success_ok, in, headers); } @@ -631,6 +670,9 @@ namespace confighttp { * @api_examples{/api/apps/close| POST| null} */ void closeApp(resp_https_t response, req_https_t request) { + if (!check_content_type(response, request, "application/json")) { + return; + } if (!authenticate(response, request)) { return; } @@ -936,7 +978,7 @@ namespace confighttp { * @api_examples{/api/clients/unpair-all| POST| null} */ void unpairAll(resp_https_t response, req_https_t request) { - if (!authenticate(response, request)) { + if (!validateContentType(response, request, "application/json"sv) || !authenticate(response, request)) { return; } @@ -964,7 +1006,7 @@ namespace confighttp { nlohmann::json output_tree; output_tree["status"] = true; output_tree["platform"] = SUNSHINE_PLATFORM; - output_tree["version"] = PROJECT_VER; + output_tree["version"] = PROJECT_VERSION; #ifdef _WIN32 output_tree["vdisplayStatus"] = (int)proc::vDisplayDriverStatus; #endif @@ -1109,6 +1151,8 @@ namespace confighttp { contentType += currentCodePageToCharset(); #endif headers.emplace("Content-Type", contentType); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(SimpleWeb::StatusCode::success_ok, content, headers); } @@ -1264,7 +1308,7 @@ namespace confighttp { * @api_examples{/api/reset-display-device-persistence| POST| null} */ void resetDisplayDevicePersistence(resp_https_t response, req_https_t request) { - if (!authenticate(response, request)) { + if (!validateContentType(response, request, "application/json"sv) || !authenticate(response, request)) { return; } @@ -1283,7 +1327,7 @@ namespace confighttp { * @api_examples{/api/restart| POST| null} */ void restart(resp_https_t response, req_https_t request) { - if (!authenticate(response, request)) { + if (!validateContentType(response, request, "application/json"sv) || !authenticate(response, request)) { return; } diff --git a/src/entry_handler.cpp b/src/entry_handler.cpp index 161d55fd..c5810898 100644 --- a/src/entry_handler.cpp +++ b/src/entry_handler.cpp @@ -16,7 +16,6 @@ #include "logging.h" #include "network.h" #include "platform/common.h" -#include "version.h" extern "C" { #ifdef _WIN32 diff --git a/src/main.cpp b/src/main.cpp index f3980032..911adb1f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -103,6 +103,10 @@ int main(int argc, char *argv[]) { task_pool_util::TaskPool::task_id_t force_shutdown = nullptr; #ifdef _WIN32 + // Avoid searching the PATH in case a user has configured their system insecurely + // by placing a user-writable directory in the system-wide PATH variable. + SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32); + setlocale(LC_ALL, "C"); #endif @@ -129,7 +133,7 @@ int main(int argc, char *argv[]) { // logging can begin at this point // if anything is logged prior to this point, it will appear in stdout, but not in the log viewer in the UI // the version should be printed to the log before anything else - BOOST_LOG(info) << PROJECT_NAME << " version: " << PROJECT_VER; + BOOST_LOG(info) << PROJECT_NAME << " version: " << PROJECT_VERSION << " commit: " << PROJECT_VERSION_COMMIT; // Log publisher metadata log_publisher_data(); @@ -389,6 +393,7 @@ int main(int argc, char *argv[]) { std::thread httpThread {nvhttp::start}; std::thread configThread {confighttp::start}; + std::thread rtspThread {rtsp_stream::start}; #ifdef _WIN32 // If we're using the default port and GameStream is enabled, warn the user @@ -398,10 +403,12 @@ int main(int argc, char *argv[]) { } #endif - rtsp_stream::rtpThread(); + // Wait for shutdown + shutdown_event->view(); httpThread.join(); configThread.join(); + rtspThread.join(); task_pool.stop(); task_pool.join(); diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 23cca8db..cacbd001 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -513,7 +513,7 @@ std::string get_local_ip_for_gateway() { // UDP GSO on Linux currently only supports sending 64K or 64 segments at a time size_t seg_index = 0; const size_t seg_max = 65536 / 1500; - struct iovec iovs[(send_info.headers ? std::min(seg_max, send_info.block_count) : 1) * max_iovs_per_msg] = {}; + struct iovec iovs[(send_info.headers ? std::min(seg_max, send_info.block_count) : 1) * max_iovs_per_msg]; auto msg_size = send_info.header_size + send_info.payload_size; while (seg_index < send_info.block_count) { int iovlen = 0; @@ -596,10 +596,11 @@ std::string get_local_ip_for_gateway() { { // If GSO is not supported, use sendmmsg() instead. - struct mmsghdr msgs[send_info.block_count] = {}; - struct iovec iovs[send_info.block_count * (send_info.headers ? 2 : 1)] = {}; + struct mmsghdr msgs[send_info.block_count]; + struct iovec iovs[send_info.block_count * (send_info.headers ? 2 : 1)]; int iov_idx = 0; for (size_t i = 0; i < send_info.block_count; i++) { + msgs[i].msg_len = 0; msgs[i].msg_hdr.msg_iov = &iovs[iov_idx]; msgs[i].msg_hdr.msg_iovlen = send_info.headers ? 2 : 1; @@ -617,6 +618,7 @@ std::string get_local_ip_for_gateway() { msgs[i].msg_hdr.msg_namelen = msg.msg_namelen; msgs[i].msg_hdr.msg_control = cmbuf.buf; msgs[i].msg_hdr.msg_controllen = cmbuflen; + msgs[i].msg_hdr.msg_flags = 0; } // Call sendmmsg() until all messages are sent @@ -709,7 +711,7 @@ std::string get_local_ip_for_gateway() { memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo)); } - struct iovec iovs[2] = {}; + struct iovec iovs[2]; int iovlen = 0; if (send_info.header) { iovs[iovlen].iov_base = (void *) send_info.header; diff --git a/src/platform/macos/av_video.m b/src/platform/macos/av_video.m index 34d70a0c..630d7101 100644 --- a/src/platform/macos/av_video.m +++ b/src/platform/macos/av_video.m @@ -34,7 +34,7 @@ + (NSString *)getDisplayName:(CGDirectDisplayID)displayID { for (NSScreen *screen in [NSScreen screens]) { - if (screen.deviceDescription[@"NSScreenNumber"] == [NSNumber numberWithUnsignedInt:displayID]) { + if ([screen.deviceDescription[@"NSScreenNumber"] isEqualToNumber:[NSNumber numberWithUnsignedInt:displayID]]) { return screen.localizedName; } } diff --git a/src/platform/windows/audio.cpp b/src/platform/windows/audio.cpp index 04bfe198..b6853e4a 100644 --- a/src/platform/windows/audio.cpp +++ b/src/platform/windows/audio.cpp @@ -27,10 +27,8 @@ DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x2 DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2); -#if defined(__x86_64) || defined(_M_AMD64) +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) #define STEAM_DRIVER_SUBDIR L"x64" -#elif defined(__i386) || defined(_M_IX86) - #define STEAM_DRIVER_SUBDIR L"x86" #else #warning No known Steam audio driver for this architecture #endif diff --git a/src/platform/windows/windows.rc.in b/src/platform/windows/windows.rc similarity index 51% rename from src/platform/windows/windows.rc.in rename to src/platform/windows/windows.rc index d583d2dc..e2463aad 100644 --- a/src/platform/windows/windows.rc.in +++ b/src/platform/windows/windows.rc @@ -1,13 +1,15 @@ /** - * @file src/platform/windows/windows.rc.in - * @brief Windows resource file template. - * @note The final `windows.rc` is generated from this file during the CMake build. - * @todo Use CMake definitions directly, instead of configuring this file. + * @file src/platform/windows/windows.rc + * @brief Windows resource file. */ #include "winver.h" + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + VS_VERSION_INFO VERSIONINFO -FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 -PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 +FILEVERSION PROJECT_VERSION_MAJOR,PROJECT_VERSION_MINOR,PROJECT_VERSION_PATCH,0 +PRODUCTVERSION PROJECT_VERSION_MAJOR,PROJECT_VERSION_MINOR,PROJECT_VERSION_PATCH,0 FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE VFT2_UNKNOWN @@ -16,13 +18,13 @@ BEGIN BEGIN BLOCK "040904E4" BEGIN - VALUE "CompanyName", "SudoMaker\0" - VALUE "FileDescription", "Apollo\0" - VALUE "FileVersion", "@PROJECT_VERSION@\0" - VALUE "InternalName", "Apollo\0" - VALUE "LegalCopyright", "https://raw.githubusercontent.com/ClassicOldSong/Apollo/master/LICENSE\0" - VALUE "ProductName", "Apollo\0" - VALUE "ProductVersion", "@PROJECT_VERSION@\0" + VALUE "CompanyName", TOSTRING(PROJECT_VENDOR) + VALUE "FileDescription", TOSTRING(PROJECT_NAME) + VALUE "FileVersion", TOSTRING(PROJECT_VERSION) + VALUE "InternalName", TOSTRING(PROJECT_NAME) + VALUE "ProductName", TOSTRING(PROJECT_NAME) + VALUE "ProductVersion", TOSTRING(PROJECT_VERSION) + VALUE "LegalCopyright", "https://raw.githubusercontent.com/ClassicOldSong/Apollo/master/LICENSE" END END @@ -39,4 +41,4 @@ BEGIN END END -SuperDuperAmazing ICON DISCARDABLE "@SUNSHINE_ICON_PATH@" +SuperDuperAmazing ICON DISCARDABLE PROJECT_ICON_PATH diff --git a/src/rswrapper.c b/src/rswrapper.c index 953ba477..53069620 100644 --- a/src/rswrapper.c +++ b/src/rswrapper.c @@ -39,7 +39,7 @@ #define gemm DECORATE_FUNC(gemm, ISA_SUFFIX) #define invert_mat DECORATE_FUNC(invert_mat, ISA_SUFFIX) -#if defined(__x86_64__) || defined(__i386__) +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) // Compile a variant for SSSE3 #if defined(__clang__) @@ -122,7 +122,7 @@ reed_solomon_decode_t reed_solomon_decode_fn; * @details The streaming code will directly invoke these function pointers during encoding. */ void reed_solomon_init(void) { -#if defined(__x86_64__) || defined(__i386__) +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) if (__builtin_cpu_supports("avx512f") && __builtin_cpu_supports("avx512bw")) { reed_solomon_new_fn = reed_solomon_new_avx512; reed_solomon_release_fn = reed_solomon_release_avx512; diff --git a/src/rtsp.cpp b/src/rtsp.cpp index b6998ebf..09d724a1 100644 --- a/src/rtsp.cpp +++ b/src/rtsp.cpp @@ -432,11 +432,6 @@ namespace rtsp_stream { return 0; } - template - void iterate(std::chrono::duration timeout) { - io_context.run_one_for(timeout); - } - void handle_msg(tcp::socket &sock, launch_session_t &session, msg_t &&req) { auto func = _map_cmd_cb.find(req->message.request.command); if (func != std::end(_map_cmd_cb)) { @@ -494,15 +489,24 @@ namespace rtsp_stream { * @param launch_session Streaming session information. */ void session_raise(std::shared_ptr launch_session) { - auto now = std::chrono::steady_clock::now(); - // If a launch event is still pending, don't overwrite it. - if (raised_timeout > now && launch_event.peek()) { + if (launch_event.view(0s)) { return; } - raised_timeout = now + config::stream.ping_timeout; + // Raise the new launch session to prepare for the RTSP handshake launch_event.raise(std::move(launch_session)); + + // Arm the timer to expire this launch session if the client times out + raised_timer.expires_after(config::stream.ping_timeout); + raised_timer.async_wait([this](const boost::system::error_code &ec) { + if (!ec) { + auto discarded = launch_event.pop(0s); + if (discarded) { + BOOST_LOG(debug) << "Event timeout: "sv << discarded->unique_id; + } + } + }); } /** @@ -517,6 +521,7 @@ namespace rtsp_stream { if (launch_session->id != launch_session_id) { BOOST_LOG(error) << "Attempted to clear unexpected session: "sv << launch_session_id << " vs "sv << launch_session->id; } else { + raised_timer.cancel(); launch_event.pop(); } } @@ -541,14 +546,6 @@ namespace rtsp_stream { * @examples_end */ void clear(bool all = true) { - // if a launch event timed out --> Remove it. - if (raised_timeout < std::chrono::steady_clock::now()) { - auto discarded = launch_event.pop(0s); - if (discarded) { - BOOST_LOG(debug) << "Event timeout: "sv << discarded->unique_id; - } - } - auto lg = _session_slots.lock(); for (auto i = _session_slots->begin(); i != _session_slots->end();) { @@ -583,6 +580,26 @@ namespace rtsp_stream { BOOST_LOG(info) << "New streaming session started [active sessions: "sv << _session_slots->size() << ']'; } + /** + * @brief Runs an iteration of the RTSP server loop + */ + void iterate() { + // If we have a session, we will return to the server loop every + // 500ms to allow session cleanup to happen. + if (session_count() > 0) { + io_context.run_one_for(500ms); + } else { + io_context.run_one(); + } + } + + /** + * @brief Stop the RTSP server. + */ + void stop() { + acceptor.close(); + io_context.stop(); + clear(); std::shared_ptr find_session(const std::string_view& uuid) { auto lg = _session_slots.lock(); @@ -613,10 +630,9 @@ namespace rtsp_stream { sync_util::sync_t>> _session_slots; - std::chrono::steady_clock::time_point raised_timeout; - boost::asio::io_context io_context; tcp::acceptor acceptor {io_context}; + boost::asio::steady_timer raised_timer {io_context}; std::shared_ptr next_socket; }; @@ -1163,9 +1179,8 @@ namespace rtsp_stream { respond(sock, session, &option, 200, "OK", req->sequenceNumber, {}); } - void rtpThread() { + void start() { auto shutdown_event = mail::man->event(mail::shutdown); - auto broadcast_shutdown_event = mail::man->event(mail::broadcast_shutdown); server.map("OPTIONS"sv, &cmd_option); server.map("DESCRIBE"sv, &cmd_describe); @@ -1181,18 +1196,29 @@ namespace rtsp_stream { return; } - while (!shutdown_event->peek()) { - server.iterate(std::min(500ms, config::stream.ping_timeout)); + std::thread rtsp_thread {[&shutdown_event] { + auto broadcast_shutdown_event = mail::man->event(mail::broadcast_shutdown); - if (broadcast_shutdown_event->peek()) { - server.clear(); - } else { - // cleanup all stopped sessions - server.clear(false); + while (!shutdown_event->peek()) { + server.iterate(); + + if (broadcast_shutdown_event->peek()) { + server.clear(); + } else { + // cleanup all stopped sessions + server.clear(false); + } } - } - server.clear(); + server.clear(); + }}; + + // Wait for shutdown + shutdown_event->view(); + + // Stop the server and join the server thread + server.stop(); + rtsp_thread.join(); } void print_msg(PRTSP_MESSAGE msg) { diff --git a/src/rtsp.h b/src/rtsp.h index 76eb76a1..c426bd93 100644 --- a/src/rtsp.h +++ b/src/rtsp.h @@ -88,6 +88,8 @@ namespace rtsp_stream { */ void terminate_sessions(); - void rtpThread(); - + /** + * @brief Runs the RTSP server loop. + */ + void start(); } // namespace rtsp_stream diff --git a/src/system_tray.cpp b/src/system_tray.cpp index a84e76f6..fb62e382 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -51,7 +51,6 @@ #include "process.h" #include "network.h" #include "src/entry_handler.h" - #include "version.h" using namespace std::literals; diff --git a/src/upnp.cpp b/src/upnp.cpp index 14103ad5..625a7a69 100644 --- a/src/upnp.cpp +++ b/src/upnp.cpp @@ -2,6 +2,9 @@ * @file src/upnp.cpp * @brief Definitions for UPnP port mapping. */ +// standard includes +#include // workaround for type_t error in miniupnpc 2.3.3, see https://github.com/miniupnp/miniupnp/commit/e263ab6f56c382e10fed31347ec68095d691a0e8 + // lib includes #include #include diff --git a/src/version.h.in b/src/version.h.in deleted file mode 100644 index 49d1386b..00000000 --- a/src/version.h.in +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @file src/version.h.in - * @brief Version definitions for Sunshine. - * @note The final `version.h` is generated from this file during the CMake build. - * @todo Use CMake definitions directly, instead of configuring this file. - */ -#pragma once - -#define PROJECT_NAME "@PROJECT_NAME@" -#define PROJECT_VER "@PROJECT_VERSION@" -#define PROJECT_VER_MAJOR "@PROJECT_VERSION_MAJOR@" -#define PROJECT_VER_MINOR "@PROJECT_VERSION_MINOR@" -#define PROJECT_VER_PATCH "@PROJECT_VERSION_PATCH@" diff --git a/src/video.cpp b/src/video.cpp index 16b64f25..c02c0b73 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -775,6 +775,18 @@ namespace video { {"usage"s, &config::video.amd.amd_usage_hevc}, {"vbaq"s, &config::video.amd.amd_vbaq}, {"enforce_hrd"s, &config::video.amd.amd_enforce_hrd}, + {"level"s, [](const config_t &cfg) { + auto size = cfg.width * cfg.height; + // For 4K and below, try to use level 5.1 or 5.2 if possible + if (size <= 8912896) { + if (size * cfg.framerate <= 534773760) { + return "5.1"s; + } else if (size * cfg.framerate <= 1069547520) { + return "5.2"s; + } + } + return "auto"s; + }}, }, {}, // SDR-specific options {}, // HDR-specific options @@ -1672,7 +1684,7 @@ namespace video { ctx->thread_count = ctx->slices; AVDictionary *options {nullptr}; - auto handle_option = [&options](const encoder_t::option_t &option) { + auto handle_option = [&options, &config](const encoder_t::option_t &option) { std::visit( util::overloaded { [&](int v) { @@ -1686,7 +1698,7 @@ namespace video { av_dict_set_int(&options, option.name.c_str(), **v, 0); } }, - [&](std::function v) { + [&](const std::function &v) { av_dict_set_int(&options, option.name.c_str(), v(), 0); }, [&](const std::string &v) { @@ -1696,6 +1708,9 @@ namespace video { if (!v->empty()) { av_dict_set(&options, option.name.c_str(), v->c_str(), 0); } + }, + [&](const std::function &v) { + av_dict_set(&options, option.name.c_str(), v(config).c_str(), 0); } }, option.value @@ -1907,8 +1922,8 @@ namespace video { } }); - // set minimum frame time, avoiding violation of client-requested target framerate - auto minimum_frame_time = std::chrono::milliseconds(1000 / std::min(config.framerate, (config::video.min_fps_factor * 10))); + // set minimum frame time based on client-requested target framerate + auto minimum_frame_time = std::chrono::milliseconds(1000 / config.framerate); auto frame_threshold = std::chrono::microseconds(1000ms * 1000 / config.encodingFramerate); BOOST_LOG(debug) << "Minimum frame time set to "sv << minimum_frame_time.count() << "ms, based on min fps factor of "sv << config::video.min_fps_factor << "."sv; BOOST_LOG(info) << "Frame threshold: "sv << frame_threshold; diff --git a/src/video.h b/src/video.h index a35dc190..ced37114 100644 --- a/src/video.h +++ b/src/video.h @@ -156,7 +156,7 @@ namespace video { option_t(const option_t &) = default; std::string name; - std::variant *, std::function, std::string, std::string *> value; + std::variant *, std::function, std::string, std::string *, std::function> value; option_t(std::string &&name, decltype(value) &&value): name {std::move(name)}, diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index fcf5990a..41dfe376 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -206,7 +206,6 @@ "headless_mode": "disabled", "double_refreshrate": "disabled", "dd_wa_hdr_toggle_delay": 0, - "min_fps_factor": 1, "max_bitrate": 0, "isolated_virtual_display_option": "disabled", }, diff --git a/src_assets/common/assets/web/configs/tabs/General.vue b/src_assets/common/assets/web/configs/tabs/General.vue index 51a71696..039b7ec4 100644 --- a/src_assets/common/assets/web/configs/tabs/General.vue +++ b/src_assets/common/assets/web/configs/tabs/General.vue @@ -62,6 +62,7 @@ onMounted(() => {
{{ $t('config.locale_desc') }}
diff --git a/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayModesSettings.vue b/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayModesSettings.vue index 2bf997ba..25b12deb 100644 --- a/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayModesSettings.vue +++ b/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayModesSettings.vue @@ -11,13 +11,6 @@ const config = ref(props.config)