diff --git a/.clang-format b/.clang-format index d28dcc4eaba..6944ec3eb08 100644 --- a/.clang-format +++ b/.clang-format @@ -1,3 +1,7 @@ +# This file is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + # Generated from CLion C/C++ Code Style settings BasedOnStyle: LLVM AccessModifierOffset: -2 diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000000..2ea739519c1 --- /dev/null +++ b/.flake8 @@ -0,0 +1,6 @@ +[flake8] +filename = + *.py +max-line-length = 120 +extend-exclude = + venv/ diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index a9c3829049b..bd21df2945f 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -1,3 +1,4 @@ +--- name: Bug Report description: Create a bug report to help us improve. body: @@ -5,7 +6,7 @@ body: attributes: value: > **THIS IS NOT THE PLACE TO ASK FOR SUPPORT!** - Please use [Github Discussions](https://github.com/SunshineStream/Sunshine/discussions) for support issues. + Please use [Discord](https://docs.lizardbyte.dev/en/latest/about/support.html#discord) for support issues. - type: textarea id: description attributes: @@ -23,15 +24,25 @@ body: attributes: label: Additional Context description: Add any other context about the bug here. - - type: input + - type: dropdown id: os attributes: - label: Sunshine Host Operating System and Version - placeholder: eg. Windows 10, macOS 10.15, Ubuntu 20.04, etc. + label: Host Operating System + description: What version operating system are you running the software on? + options: + - Linux + - macOS + - Windows + - other + - type: input + id: os-version + attributes: + label: Operating System Version + description: Provide the version of the operating system. Additionally a build number would be helpful. validations: required: true - type: input - id: architecture + id: os-architecture attributes: label: Architecture placeholder: e.g. 32 bit, 64 bit, arm @@ -41,7 +52,7 @@ body: id: version attributes: label: Sunshine Version - placeholder: eg. 0.11.1 + placeholder: eg. 0.14.0 validations: required: true - type: input @@ -76,7 +87,16 @@ body: placeholder: e.g. PipeWire/KVM/X11 validations: required: false + - type: textarea + id: logs + attributes: + label: Relevant log output + description: | + Please copy and paste any relevant log output. This will be automatically formatted into code, + so no need for backticks. + render: Shell - type: markdown attributes: value: | - Make sure to close your issue when it's solved! If you found the solution yourself please comment so that others benefit from it. + Make sure to close your issue when it's solved! If you found the solution yourself please comment + so that others benefit from it. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index c96ec16de2d..ba507895ebf 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,11 +1,19 @@ +--- +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + blank_issues_enabled: false contact_links: - - name: AUR Package Issue - url: https://aur.archlinux.org/packages/sunshine - about: AUR Package Issues should be discussed on the AUR - - name: Github Discussions - url: https://github.com/SunshineStream/Sunshine/discussions - about: General discussion, support, feature requests and more! - name: Discord support - url: https://discord.com/invite/CGg5JxN - about: Ask question about Sunshine in Discord + url: https://docs.lizardbyte.dev/about/support.html#discord + about: Ask questions in Discord + - name: Reddit support + url: https://www.reddit.com/r/LizardByte + about: Get community support on Reddit + - name: Facebook support + url: https://www.facebook.com/groups/lizardbyte + about: Get community support on Facebook + - name: Feature request + url: https://feedback.lizardbyte.dev + about: Share your suggestions or ideas to help us improve diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 32fce3f3d8d..e3b47ad73aa 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,15 +1,30 @@ +--- +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" + time: "00:00" target-branch: "nightly" - open-pull-requests-limit: 20 + open-pull-requests-limit: 10 + + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" + time: "00:00" + target-branch: "nightly" + open-pull-requests-limit: 10 - package-ecosystem: "pip" - directory: "/scripts" + directory: "/" schedule: interval: "daily" + time: "00:00" target-branch: "nightly" open-pull-requests-limit: 10 diff --git a/.github/label-actions.yml b/.github/label-actions.yml index de9759f70b5..6d0a74a2a32 100644 --- a/.github/label-actions.yml +++ b/.github/label-actions.yml @@ -1,13 +1,16 @@ +--- +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + # Configuration for Label Actions - https://github.com/dessant/label-actions added: comment: > This feature has been added and will be available in the next release. - fixed: comment: > This issue has been fixed and will be available in the next release. - invalid:duplicate: comment: > :wave: @{issue-author}, this appears to be a duplicate of a pre-existing issue. @@ -22,8 +25,8 @@ invalid:duplicate: invalid:support: comment: > :wave: @{issue-author}, we use the issue tracker exclusively for bug reports. - However, this issue appears to be a support request. Please use our - [Discord Server](https://discord.com/invite/CGg5JxN) to get help. Thanks. + However, this issue appears to be a support request. Please use + [Discord](https://docs.lizardbyte.dev/about/support.html#discord) for support issues. Thanks. close: true lock: true lock-reason: 'off-topic' diff --git a/.github/pr_release_template.md b/.github/pr_release_template.md new file mode 100644 index 00000000000..7c96c6b694f --- /dev/null +++ b/.github/pr_release_template.md @@ -0,0 +1,24 @@ +## Description + +This PR was created automatically. + + +### Screenshot + + + +### Issues Fixed or Closed + + + + + +## Type of Change +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update (changes to documentation) +- [ ] Repository update (changes to repository files) + +## Changelog Summary + diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 18d7e89cc13..c9e6b0df0e6 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,3 +1,4 @@ +--- name: CI on: @@ -9,6 +10,17 @@ on: workflow_dispatch: jobs: + github_env: + name: GitHub Env Debug + runs-on: ubuntu-latest + + steps: + - name: Dump github context + run: echo "$GITHUB_CONTEXT" + shell: bash + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + check_changelog: name: Check Changelog runs-on: ubuntu-latest @@ -20,7 +32,7 @@ jobs: id: verify_changelog if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} # base_ref for pull request check, ref for push - uses: SunshineStream/actions/verify_changelog@master + uses: LizardByte/.github/actions/verify_changelog@master with: token: ${{ secrets.GITHUB_TOKEN }} outputs: @@ -41,7 +53,8 @@ jobs: - name: Check CMakeLists.txt Version run: | - version=$(grep -o -E '^project\(Sunshine VERSION [0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') + version=$(grep -o -E '^project\(Sunshine VERSION [0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt | \ + grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') echo "cmakelists_version=${version}" >> $GITHUB_ENV - name: Compare CMakeList.txt Version @@ -49,9 +62,188 @@ jobs: run: | echo CMakeLists version: "$cmakelists_version" echo Changelog version: "${{ needs.check_changelog.outputs.next_version_bare }}" - echo Within 'CMakeLists.txt' change "project(Sunshine [VERSION $cmakelists_version]" to "project(Sunshine [VERSION ${{ needs.check_changelog.outputs.next_version_bare }}]" + echo Within 'CMakeLists.txt' change "project(Sunshine [VERSION $cmakelists_version]" to \ + "project(Sunshine [VERSION ${{ needs.check_changelog.outputs.next_version_bare }}]" exit 1 + build_linux_aur: + name: Linux AUR + runs-on: ubuntu-latest + needs: check_changelog + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup Dependencies Linux AUR + run: | + sudo apt-get update -y + sudo apt-get install -y \ + cmake + + - name: Configure PKGBUILD files + run: | + # variables for manifest + echo "aur_publish=false" >> $GITHUB_ENV + aur_pkg=sunshine-dev + sub_version="" + conflicts="'sunshine'" + provides="'sunshine'" + + branch=${GITHUB_HEAD_REF} + + # check the branch variable + if [ -z "$branch" ] + then + echo "This is a PUSH event" + commit=${{ github.sha }} + clone_url=${{ github.event.repository.clone_url }} + + if [[ ${{ github.ref == 'refs/heads/master' }} ]]; then + aur_pkg=sunshine + conflicts="" + provides="" + + echo "aur_publish=true" >> $GITHUB_ENV + elif [[ ${{ github.ref == 'refs/heads/nightly' }} ]]; then + aur_pkg=sunshine-git + sub_version=".r${commit}" + + echo "aur_publish=true" >> $GITHUB_ENV + fi + else + echo "This is a PR event" + commit=${{ github.event.pull_request.head.sha }} + clone_url=${{ github.event.pull_request.head.repo.clone_url }} + + sub_version=".r${commit}" + fi + echo "Commit: ${commit}" + echo "Clone URL: ${clone_url}" + + echo "aur_pkg=${aur_pkg}" >> $GITHUB_ENV + + mkdir -p artifacts + mkdir -p build + + cd build + cmake -DSUNSHINE_CONFIGURE_AUR=ON \ + -DSUNSHINE_AUR_PKG=${aur_pkg} \ + -DSUNSHINE_SUB_VERSION=${sub_version} \ + -DSUNSHINE_AUR_CONFLICTS=${conflicts} \ + -DSUNSHINE_AUR_PROVIDES=${provides} \ + -DGITHUB_CLONE_URL=${clone_url} \ + -DGITHUB_COMMIT=${commit} \ + -DSUNSHINE_CONFIGURE_ONLY=ON \ + .. + cd .. + + mv ./build/PKGBUILD ./artifacts/ + + - name: Validate package + uses: hapakaien/archlinux-package-action@v2.2.0 + with: + path: artifacts + flags: '--syncdeps --noconfirm' + namcap: true + srcinfo: true + aur: true # workaround mirror problem + + - name: Upload Artifacts + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: sunshine-linux-aur + path: artifacts/ + + - name: Publish AUR package + if: ${{ env.aur_publish == 'true' }} + uses: KSXGitHub/github-actions-deploy-aur@v2.4.1 + with: + pkgname: ${{ env.aur_pkg }} + pkgbuild: ./artifacts/PKGBUILD + assets: | + ./artifacts/* + commit_username: ${{ secrets.AUR_USERNAME }} + commit_email: ${{ secrets.AUR_EMAIL }} + ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} + commit_message: Automatic update from GitHub ${{ github.repository }} per ${{ github.ref }} + allow_empty_commits: false + + build_linux_flatpak: + name: Linux Flatpak + runs-on: ubuntu-latest + needs: check_changelog + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Dependencies Linux Flatpak + run: | + sudo apt-get update -y + sudo apt-get install -y \ + cmake \ + flatpak + sudo su $(whoami) -c 'flatpak remote-add --user --if-not-exists flathub \ + https://flathub.org/repo/flathub.flatpakrepo' + sudo su $(whoami) -c 'flatpak install --user flathub \ + org.flatpak.Builder org.freedesktop.Platform//21.08 org.freedesktop.Sdk//21.08 -y' + + - name: Configure Flatpak Manifest + run: | + # variables for manifest + branch=${GITHUB_HEAD_REF} + + # check the branch variable + if [ -z "$branch" ] + then + echo "This is a PUSH event" + branch=${{ github.ref_name }} + commit=${{ github.sha }} + clone_url=${{ github.event.repository.clone_url }} + else + echo "This is a PR event" + commit=${{ github.event.pull_request.head.sha }} + clone_url=${{ github.event.pull_request.head.repo.clone_url }} + fi + echo "Branch: ${branch}" + echo "Commit: ${commit}" + echo "Clone URL: ${clone_url}" + + mkdir -p build + mkdir -p artifacts + + cd build + cmake -DGITHUB_CLONE_URL=${clone_url} \ + -DGITHUB_BRANCH=${branch} \ + -DGITHUB_COMMIT=${commit} \ + -DSUNSHINE_CONFIGURE_FLATPAK=ON \ + -DSUNSHINE_CONFIGURE_ONLY=ON \ + .. + + - name: Build Linux Flatpak + working-directory: build + run: | + sudo su $(whoami) -c 'flatpak run org.flatpak.Builder --repo=repo --force-clean build-sunshine \ + dev.lizardbyte.sunshine.yml' + sudo su $(whoami) -c 'flatpak build-bundle ./repo ../artifacts/sunshine.flatpak dev.lizardbyte.sunshine' + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: sunshine-linux-flatpak + path: artifacts/ + + - name: Create Release + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + uses: LizardByte/.github/actions/create_release@master + with: + token: ${{ secrets.GH_BOT_TOKEN }} + next_version: ${{ needs.check_changelog.outputs.next_version }} + last_version: ${{ needs.check_changelog.outputs.last_version }} + release_body: ${{ needs.check_changelog.outputs.release_body }} + build_linux: name: Linux runs-on: ubuntu-20.04 @@ -59,77 +251,83 @@ jobs: strategy: fail-fast: false # false to test all, true to fail entire job if any fail matrix: - include: # package these differently + include: # package these differently - type: cpack - CMAKE_INSTALL_PREFIX: '' - SUNSHINE_ASSETS_DIR: '/usr/local/sunshine/assets' - SUNSHINE_CONFIG_DIR: '/usr/local/sunshine/config' + CMAKE_INSTALL_PREFIX: '/usr' + SUNSHINE_ASSETS_DIR: 'local/sunshine/assets' + SUNSHINE_CONFIG_DIR: 'local/sunshine/config' + EXTRA_ARGS: '' - type: appimage CMAKE_INSTALL_PREFIX: '/usr' SUNSHINE_ASSETS_DIR: 'sunshine.AppImage.config' SUNSHINE_CONFIG_DIR: 'sunshine.AppImage.home' + EXTRA_ARGS: '-DSUNSHINE_CONFIGURE_APPIMAGE=ON' steps: - name: Checkout uses: actions/checkout@v3 with: submodules: recursive + - name: Setup Dependencies Linux run: | sudo add-apt-repository ppa:savoury1/ffmpeg4 -y # sudo add-apt-repository ppa:savoury1/boost-defaults-1.71 -y sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y - + sudo apt-get update -y sudo apt-get install -y \ - build-essential \ - cmake \ - gcc-10 \ - git \ - g++-10 \ - libavdevice-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libboost-thread-dev \ - libcap-dev \ - libdrm-dev \ - libevdev-dev \ - libpulse-dev \ - libopus-dev \ - libssl-dev \ - libwayland-dev \ - libx11-dev \ - libxcb-shm0-dev \ - libxcb-xfixes0-dev \ - libxcb1-dev \ - libxfixes-dev \ - libxrandr-dev \ - libxtst-dev \ - wget + build-essential \ + cmake \ + gcc-10 \ + git \ + g++-10 \ + libavdevice-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libboost-thread-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + wget # # Ubuntu 20.04+ packages # libboost-filesystem-dev # libboost-log-dev # libboost-thread-dev - + # # Ubuntu 18.04 packages # libboost-filesystem1.71-dev \ # libboost-log1.71-dev \ # libboost-regex1.71-dev \ # libboost-thread1.71-dev \ - + # clean apt cache sudo apt-get clean sudo rm -rf /var/lib/apt/lists/* - + # Update gcc alias - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 - + sudo update-alternatives --install \ + /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 + # Install CuDA - sudo wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run + sudo wget \ + https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run \ + --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run sudo chmod a+x /root/cuda.run sudo /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm sudo rm /root/cuda.run - + # # Install cmake (necessary for 18.04) # wget https://cmake.org/files/v3.22/cmake-3.22.2-linux-x86_64.sh # chmod +x cmake-3.22.2-linux-x86_64.sh @@ -142,9 +340,19 @@ jobs: run: | mkdir -p build mkdir -p artifacts - + cd build - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${{ matrix.CMAKE_INSTALL_PREFIX }} -DSUNSHINE_ASSETS_DIR=${{ matrix.SUNSHINE_ASSETS_DIR }} -DSUNSHINE_CONFIG_DIR=${{ matrix.SUNSHINE_CONFIG_DIR }} -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine -DSUNSHINE_ENABLE_WAYLAND=ON -DSUNSHINE_ENABLE_X11=ON -DSUNSHINE_ENABLE_DRM=ON -DSUNSHINE_ENABLE_CUDA=ON .. + cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=${{ matrix.CMAKE_INSTALL_PREFIX }} \ + -DSUNSHINE_ASSETS_DIR=${{ matrix.SUNSHINE_ASSETS_DIR }} \ + -DSUNSHINE_CONFIG_DIR=${{ matrix.SUNSHINE_CONFIG_DIR }} \ + -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ + -DSUNSHINE_ENABLE_WAYLAND=ON \ + -DSUNSHINE_ENABLE_X11=ON \ + -DSUNSHINE_ENABLE_DRM=ON \ + -DSUNSHINE_ENABLE_CUDA=ON \ + ${{ matrix.EXTRA_ARGS }} \ + .. make -j ${nproc} - name: Package Linux - CPACK @@ -160,7 +368,7 @@ jobs: mv ./cpack_artifacts/Sunshine.rpm ../artifacts/sunshine.rpm - name: Set AppImage Version - if: ${{ matrix.type == 'appimage' && ( needs.check_changelog.outputs.next_version_bare != needs.check_changelog.outputs.latest_version ) }} + if: ${{ matrix.type == 'appimage' && ( needs.check_changelog.outputs.next_version_bare != needs.check_changelog.outputs.latest_version ) }} # yamllint disable-line rule:line-length run: | version=${{ needs.check_changelog.outputs.next_version_bare }} echo "VERSION=${version}" >> $GITHUB_ENV @@ -171,43 +379,44 @@ jobs: run: | # install sunshine to the DESTDIR make install DESTDIR=AppDir - + # portable home and config # todo - this is ugly... we should use a custom AppRun script to take care of this mv ./AppDir${{ matrix.CMAKE_INSTALL_PREFIX }}/sunshine.AppImage.* ../artifacts/ mkdir -p ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_CONFIG_DIR }} - cp ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/apps.json ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_CONFIG_DIR }}/ - + cp ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/apps.json \ + ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_CONFIG_DIR }}/ + # variables DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" ICON_FILE="${ICON_FILE:-sunshine.png}" - + # AppImage # https://docs.appimage.org/packaging-guide/index.html wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage chmod +x linuxdeploy-x86_64.AppImage - + # # https://github.com/linuxdeploy/linuxdeploy-plugin-gtk # sudo apt-get install libgtk-3-dev librsvg2-dev -y # wget https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh # chmod +x linuxdeploy-plugin-gtk.sh # export DEPLOY_GTK_VERSION=3 - + ./linuxdeploy-x86_64.AppImage \ - --appdir ./AppDir \ - --executable ./sunshine \ - --icon-file "../$ICON_FILE" \ - --desktop-file "./$DESKTOP_FILE" \ - --library /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0 \ - --library /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 \ - --library /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0 \ - --output appimage + --appdir ./AppDir \ + --executable ./sunshine \ + --icon-file "../$ICON_FILE" \ + --desktop-file "./$DESKTOP_FILE" \ + --library /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0 \ + --library /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 \ + --library /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0 \ + --output appimage # # add this argument back if using gtk plugin # --plugin gtk \ # move mv Sunshine*.AppImage ../artifacts/sunshine.AppImage - + # permissions chmod +x ../artifacts/sunshine.AppImage @@ -216,9 +425,9 @@ jobs: run: | wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage chmod +x appimagelint-x86_64.AppImage - + # rm -rf ~/.cache/appimagelint/ - + ./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage - name: Archive AppImage @@ -226,7 +435,7 @@ jobs: working-directory: artifacts run: | chmod +x ./sunshine.AppImage - + zip --recurse-paths --move --test ./sunshine-appimage.zip ./* - name: Upload Artifacts @@ -237,9 +446,9 @@ jobs: - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: SunshineStream/actions/create_release@master + uses: LizardByte/.github/actions/create_release@master with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GH_BOT_TOKEN }} next_version: ${{ needs.check_changelog.outputs.next_version }} last_version: ${{ needs.check_changelog.outputs.last_version }} release_body: ${{ needs.check_changelog.outputs.release_body }} @@ -255,13 +464,6 @@ jobs: with: submodules: recursive - # this is for the macports job - - name: Cache Artifacts - uses: actions/cache@v3 - with: - path: artifacts - key: ${{ runner.os }}-artifacts - - name: Setup Dependencies MacOS run: | # install dependencies using homebrew @@ -272,50 +474,30 @@ jobs: - name: Build MacOS run: | - # variables for Portfile - owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]' ) - repo=$(echo ${GITHUB_REPOSITORY#*/} | tr '[:upper:]' '[:lower:]' ) - branch=${GITHUB_HEAD_REF} - - # check the branch variable - if [ -z "$branch" ] - then - echo "This is a PUSH event" - branch=${GITHUB_BASE_REF} - else - echo "This is a PR event" - fi - echo "Owner: ${owner}" - echo "Repo: ${repo}" - echo "Branch: ${branch}" - mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=/usr/local/sunshine/assets -DSUNSHINE_CONFIG_DIR=/usr/local/sunshine/config -DGITHUB_OWNER=${owner} -DGITHUB_REPO=${repo} -DGITHUB_BRANCH=${branch} .. + cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DSUNSHINE_ASSETS_DIR=local/sunshine/assets \ + -DSUNSHINE_CONFIG_DIR=local/sunshine/config \ + .. make -j ${nproc} - name: Package MacOS run: | - # remove cached artifacts - rm -r -f ./artifacts - mkdir artifacts - mkdir -p artifacts cd build # package cpack -G DragNDrop mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-macos-experimental-dragndrop.dmg - + cpack -G Bundle mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-macos-experimental-bundle.dmg - + cpack -G ZIP mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-macos-experimental-archive.zip - # move - mv Portfile ../artifacts/Portfile - - name: Upload Artifacts uses: actions/upload-artifact@v3 with: @@ -331,113 +513,225 @@ jobs: rm -f ./sunshine-macos-experimental-bundle.dmg rm -f ./sunshine-macos-experimental-archive.zip + ## no artifacts to release currently + # - name: Create Release + # if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + # uses: LizardByte/.github/actions/create_release@master + # with: + # token: ${{ secrets.GH_BOT_TOKEN }} + # next_version: ${{ needs.check_changelog.outputs.next_version }} + # last_version: ${{ needs.check_changelog.outputs.last_version }} + # release_body: ${{ needs.check_changelog.outputs.release_body }} + + build_mac_port: + name: Macports + needs: check_changelog + runs-on: macos-11 + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Checkout ports + uses: actions/checkout@v3 + with: + repository: macports/macports-ports + fetch-depth: 64 + path: ports + + - name: Checkout mpbb + uses: actions/checkout@v3 + with: + repository: macports/mpbb + path: mpbb + + - name: Setup Dependencies Macports + run: | + # install dependencies using homebrew + brew install cmake + + - name: Configure Portfile + run: | + # variables for Portfile + branch=${GITHUB_HEAD_REF} + + # check the branch variable + if [ -z "$branch" ] + then + echo "This is a PUSH event" + commit=${{ github.sha }} + clone_url=${{ github.event.repository.clone_url }} + else + echo "This is a PR event" + commit=${{ github.event.pull_request.head.sha }} + clone_url=${{ github.event.pull_request.head.repo.clone_url }} + fi + echo "Commit: ${commit}" + echo "Clone URL: ${clone_url}" + + mkdir build + cd build + cmake -DGITHUB_COMMIT=${commit} \ + -DGITHUB_CLONE_URL=${clone_url} \ + -DSUNSHINE_CONFIGURE_PORTFILE=ON \ + -DSUNSHINE_CONFIGURE_ONLY=ON \ + .. + cd .. + + # copy Portfile to artifacts + mkdir -p artifacts + cp -f ./build/Portfile ./artifacts/ + + # copy Portfile to ports + mkdir -p ./ports/multimedia/Sunshine + cp -f ./build/Portfile ./ports/multimedia/Sunshine/Portfile + + # testing + cat ./artifacts/Portfile + + - name: Bootstrap MacPorts + run: | + . ports/.github/workflows/bootstrap.sh + + # Add getopt, mpbb and the MacPorts paths to $PATH for the subsequent steps. + echo "/opt/mports/bin" >> $GITHUB_PATH + echo "${PWD}/mpbb" >> $GITHUB_PATH + echo "/opt/local/bin" >> $GITHUB_PATH + echo "/opt/local/sbin" >> $GITHUB_PATH + + - name: Determine list of subports + id: subportlist + run: | + set -eu + port=Sunshine + subportlist="" + + echo "Listing subports for Sunshine" + new_subports=$(mpbb \ + --work-dir /tmp/mpbb \ + list-subports \ + --archive-site= \ + --archive-site-private= \ + --include-deps=no \ + "$port" \ + | tr '\n' ' ') + for subport in $new_subports; do + echo "$subport" + subportlist="$subportlist $subport" + done + echo "::set-output name=subportlist::${subportlist}" + + - name: Run port lint for all subports + run: | + set -eu + fail=0 + for subport in $subportlist; do + echo "::group::${subport}" + path=$(port file "$subport") + messagetype="warning" + if ! messages=$(port -q lint "$subport" 2>&1); then + messagetype="error" + fail=1 + fi + if [ -n "$messages" ]; then + echo "$messages" + # See https://github.com/actions/toolkit/issues/193#issuecomment-605394935 + encoded_messages="port lint ${subport}:%0A" + encoded_messages+="$(echo "${messages}" | sed -E 's/$/%0A/g' | tr -d '\n')" + echo "::${messagetype} file=${path#${PWD}/ports/},line=1,col=1::${encoded_messages}" + fi + echo "::endgroup::" + done + exit "$fail" + env: + subportlist: ${{ steps.subportlist.outputs.subportlist }} + + - name: Build subports + run: | + set -eu + fail=0 + for subport in $subportlist; do + workdir="/tmp/mpbb/$subport" + mkdir -p "$workdir/logs" + touch "$workdir/logs/dependencies-progress.txt" + echo "::group::Cleaning up between ports" + sudo mpbb --work-dir "$workdir" cleanup + echo "::endgroup::" + echo "::group::Installing dependencies for ${subport}" + sudo mpbb \ + --work-dir "$workdir" \ + install-dependencies \ + "$subport" >"$workdir/logs/install-dependencies.log" 2>&1 & + deps_pid=$! + tail -f "$workdir/logs/dependencies-progress.txt" 2>/dev/null & + tail_pid=$! + set +e + wait "$deps_pid" + deps_exit=$? + set -e + kill "$tail_pid" || true + if [ "$deps_exit" -ne 0 ]; then + echo "::endgroup::" + echo "::error::Failed to install dependencies for ${subport}" + fail=1 + continue + fi + echo "::endgroup::" + echo "::group::Installing ${subport}" + set +e + sudo mpbb \ + --work-dir "$workdir" \ + install-port \ + --source \ + "$subport" + install_exit=$? + set -e + if [ "$install_exit" -ne 0 ]; then + echo "::endgroup::" + echo "::error::Failed to install ${subport}" + fail=1 + continue + fi + echo "::endgroup::" + done + exit "$fail" + env: + subportlist: ${{ steps.subportlist.outputs.subportlist }} + + - name: Package + run: | + # create packages + sudo port pkg sunshine + sudo port dmg sunshine + + work=$(port work sunshine) + echo "Sunshine port work directory: ${work}" + + # move components out of port work directory + sudo mv ${work}/Sunshine*component.pkg /tmp/ + + # copy artifacts + sudo mv ${work}/Sunshine*.pkg ./artifacts/sunshine.pkg + sudo mv ${work}/Sunshine*.dmg ./artifacts/sunshine.dmg + + # move components back + # sudo mv /tmp/Sunshine*component.pkg ${work}/ + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: sunshine-macports + path: artifacts/ + - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: SunshineStream/actions/create_release@master + uses: LizardByte/.github/actions/create_release@master with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GH_BOT_TOKEN }} next_version: ${{ needs.check_changelog.outputs.next_version }} last_version: ${{ needs.check_changelog.outputs.last_version }} release_body: ${{ needs.check_changelog.outputs.release_body }} -# build_mac_port: -# name: Macports -# runs-on: macos-11 -# needs: [check_changelog, build_mac] -# -# steps: -# - name: Cache Artifacts -# uses: actions/cache@v3 -# with: -# path: artifacts -# key: ${{ runner.os }}-artifacts -# -# - name: Setup Macports -# run : | -# # update paths for macports -# echo "/opt/local/sbin" >> $GITHUB_PATH -# echo "/opt/local/bin" >> $GITHUB_PATH -# -# # Set OpenSSL 1.1 as default -# # rm -rf /usr/local/opt/openssl -# # rm -rf /usr/local/bin/openssl -# # ln -sf /usr/local/Cellar/openssl@1.1/1.1.1o/bin/openssl /usr/local/bin/openssl -# # ln -sf /usr/local/Cellar/openssl@1.1/1.1.1o /usr/local/opt/openssl -# -# # download and extract macports -# curl -O https://distfiles.macports.org/MacPorts/MacPorts-2.7.2.tar.bz2 -# tar xf MacPorts-2.7.2.tar.bz2 -# -# # build macports -# cd MacPorts-2.7.2 -# ./configure -# make -# sudo make install -# cd ../ -# rm -rf MacPorts-2.7.2* -# -# - name: Configure Macports -# run: | -# # update sources -# sudo port -v selfupdate -# -# # use custom sources -# sudo chmod 777 /opt/local/etc/macports/sources.conf -# echo file://$(echo ~)/ports > /opt/local/etc/macports/sources.conf -# echo rsync://rsync.macports.org/macports/release/tarballs/ports.tar [default] >> /opt/local/etc/macports/sources.conf -# sudo chmod 644 /opt/local/etc/macports/sources.conf -# -# # setup custom port -# mkdir -p ~/ports/multimedia/sunshine -# -# # copy configured Portfile -# mv ./artifacts/Portfile ~/ports/multimedia/sunshine/ -# -# # remove remaining cached artifacts -# rm -r -f ./artifacts -# mkdir artifacts -# -# # index the ports -# cd ~/ports -# portindex -# -# - name: Build -# run: | -# # build port -# sudo port install sunshine \ -# || cat /opt/local/var/macports/logs/_Users_runner_ports_multimedia_sunshine/Sunshine/main.log \ -# && exit 1 -# -# # create packages -# sudo port dmg sunshine -# sudo port pkg sunshine -# -# # move -# mv $(port work sunshine)/Sunshine*.dmg ./artifacts/sunshine.dmg -# mv $(port work sunshine)/Sunshine*.ppkg ./artifacts/sunshine.pkg -# -# # testing only -# # ls ~/ports/multimedia/sunshine -# # cat ~/ports/multimedia/sunshine/Portfile -# # cat /opt/local/etc/macports/sources.conf -# # cat ~/ports/Portindex -# # port search sunshine -# -# - name: Upload Artifacts -# uses: actions/upload-artifact@v3 -# with: -# name: sunshine-macports -# path: artifacts/ -# -# - name: Create Release -# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} -# uses: SunshineStream/actions/create_release@master -# with: -# token: ${{ secrets.GITHUB_TOKEN }} -# next_version: ${{ needs.check_changelog.outputs.next_version }} -# last_version: ${{ needs.check_changelog.outputs.last_version }} -# release_body: ${{ needs.check_changelog.outputs.release_body }} - build_win: name: Windows runs-on: windows-2019 @@ -474,7 +768,11 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -DSUNSHINE_CONFIG_DIR=config -G "MinGW Makefiles" .. + cmake -DCMAKE_BUILD_TYPE=Release \ + -DSUNSHINE_ASSETS_DIR=assets \ + -DSUNSHINE_CONFIG_DIR=config \ + -G "MinGW Makefiles" \ + .. mingw32-make -j2 - name: Package Windows @@ -499,9 +797,9 @@ jobs: - name: Create Release if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: SunshineStream/actions/create_release@master + uses: LizardByte/.github/actions/create_release@master with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GH_BOT_TOKEN }} next_version: ${{ needs.check_changelog.outputs.next_version }} last_version: ${{ needs.check_changelog.outputs.last_version }} release_body: ${{ needs.check_changelog.outputs.release_body }} diff --git a/.github/workflows/auto-create-pr.yml b/.github/workflows/auto-create-pr.yml new file mode 100644 index 00000000000..ef19e40ff1e --- /dev/null +++ b/.github/workflows/auto-create-pr.yml @@ -0,0 +1,31 @@ +--- +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +name: Auto create PR + +on: + push: + branches: + - 'nightly' + +jobs: + create_pr: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Create Pull Request + uses: repo-sync/pull-request@v2 + with: + source_branch: "" # should be "nightly" as it's the triggering branch + destination_branch: "master" + pr_title: "Pulling ${{ github.ref_name }} into master" + pr_template: ".github/pr_release_template.md" + pr_assignee: "${{ secrets.GH_BOT_NAME }}" + pr_draft: true + pr_allow_empty: false + github_token: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 00000000000..7ff83e0a846 --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,59 @@ +--- +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +name: Automerge PR + +on: + pull_request: + types: + - opened + - synchronize + +jobs: + autoapprove: + if: > + contains(fromJson('["LizardByte-bot"]'), github.event.pull_request.user.login) && + contains(fromJson('["LizardByte-bot"]'), github.actor) + runs-on: ubuntu-latest + steps: + - name: Autoapproving + uses: hmarr/auto-approve-action@v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Label autoapproved + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GH_BOT_TOKEN }} + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['autoapproved', 'autoupdate'] + }) + + automerge: + needs: [autoapprove] + runs-on: ubuntu-latest + concurrency: + group: automerge-${{ github.ref }} + cancel-in-progress: true + + steps: + - name: Automerging + uses: pascalgn/automerge-action@v0.15.3 + env: + BASE_BRANCHES: nightly + GITHUB_TOKEN: ${{ secrets.GH_BOT_TOKEN }} + GITHUB_LOGIN: ${{ secrets.GH_BOT_NAME }} + MERGE_LABELS: "" + MERGE_METHOD: "squash" + MERGE_COMMIT_MESSAGE: "{pullRequest.title} (#{pullRequest.number})" + MERGE_DELETE_BRANCH: true + MERGE_ERROR_FAIL: true + MERGE_FILTER_AUTHOR: ${{ secrets.GH_BOT_NAME }} + MERGE_RETRIES: "240" # 1 hour + MERGE_RETRY_SLEEP: "15000" # 15 seconds diff --git a/.github/workflows/autoupdate.yml b/.github/workflows/autoupdate.yml new file mode 100644 index 00000000000..f32e65c78c4 --- /dev/null +++ b/.github/workflows/autoupdate.yml @@ -0,0 +1,32 @@ +--- +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +# This workflow is designed to work with: +# - automerge workflows + +# It uses GitHub Action that auto-updates pull requests branches, when changes are pushed to their destination branch. +# Auto-updating to the latest destination branch works only in the context of upstream repo and not forks. + +name: autoupdate + +on: + push: + branches: + - 'nightly' + +jobs: + autoupdate-for-bot: + name: Autoupdate autoapproved PR created in the upstream + if: startsWith(github.repository, 'LizardByte/') + runs-on: ubuntu-latest + steps: + - name: Update + uses: docker://chinthakagodawita/autoupdate-action:v1 + env: + GITHUB_TOKEN: '${{ secrets.GH_BOT_TOKEN }}' + PR_FILTER: "labelled" + PR_LABELS: "autoupdate" + PR_READY_STATE: "ready_for_review" + MERGE_CONFLICT_ACTION: "ignore" diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml deleted file mode 100644 index 3c0a70e6d59..00000000000 --- a/.github/workflows/clang.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: clang-format-lint - -on: - pull_request: - branches: [master, nightly] - types: [opened, synchronize, reopened] - -jobs: - lint: - name: Clang Format Lint - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Clang format lint - uses: DoozyX/clang-format-lint-action@v0.14 - with: - source: './sunshine' - extensions: 'cpp,h,m,mm' - clangFormatVersion: 13 - style: file - inplace: false - - - name: Upload Artifacts - if: failure() - uses: actions/upload-artifact@v3 - with: - name: sunshine - path: sunshine/ diff --git a/.github/workflows/cpp-clang-format-lint.yml b/.github/workflows/cpp-clang-format-lint.yml new file mode 100644 index 00000000000..4717f70a5e3 --- /dev/null +++ b/.github/workflows/cpp-clang-format-lint.yml @@ -0,0 +1,60 @@ +--- +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +name: Clang Format Lint + +on: + pull_request: + branches: [master, nightly] + types: [opened, synchronize, reopened] + +jobs: + check_src: + name: Check src + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Check + id: check + run: | + if [ -d "./src" ] + then + FOUND=true + else + FOUND=false + fi + + echo "::set-output name=src::${FOUND}" + + outputs: + src: ${{ steps.check.outputs.src }} + + lint: + name: Clang Format Lint + needs: [check_src] + if: ${{ needs.check_src.outputs.src == 'true' }} + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Clang format lint + uses: DoozyX/clang-format-lint-action@v0.14 + with: + source: './src' + extensions: 'cpp,h,m,mm' + clangFormatVersion: 13 + style: file + inplace: false + + - name: Upload Artifacts + if: failure() + uses: actions/upload-artifact@v3 + with: + name: clang-format-fixes + path: src/ diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml index 233582bd47c..225b07de39d 100644 --- a/.github/workflows/issues-stale.yml +++ b/.github/workflows/issues-stale.yml @@ -1,3 +1,8 @@ +--- +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + name: Stale Issues / PRs on: @@ -6,43 +11,44 @@ on: jobs: stale: - name: Check Issues / PRs + name: Check Stale Issues / PRs runs-on: ubuntu-latest steps: - name: Stale uses: actions/stale@v5 with: - stale-issue-message: > - This issue is stale because it has been open for 30 days with no activity. - Remove the stale label or comment, otherwise this will be closed in 5 days. close-issue-message: > This issue was closed because it has been stalled for 5 days with no activity. - stale-issue-label: 'stale' - exempt-issue-labels: 'added,fixed,type:enhancement,status:awaiting-triage,status:in-progress' - stale-pr-message: > - This PR is stale because it has been open for 30 days with no activity. - Remove the stale label or comment, otherwise this will be closed in 5 days. close-pr-message: > - This PR was closed because it has been stalled for 5 days with no activity. - stale-pr-label: 'stale' - exempt-pr-labels: 'status:in-progress' - days-before-stale: 60 + This PR was closed because it has been stalled for 10 days with no activity. + days-before-stale: 90 days-before-close: 10 + exempt-all-assignees: true + exempt-issue-labels: 'added,fixed' + exempt-pr-labels: 'dependencies,l10n' + stale-issue-label: 'stale' + stale-issue-message: > + This issue is stale because it has been open for 30 days with no activity. + Comment or remove the stale label, otherwise this will be closed in 5 days. + stale-pr-label: 'stale' + stale-pr-message: > + This PR is stale because it has been open for 90 days with no activity. + Comment or remove the stale label, otherwise this will be closed in 10 days. - name: Invalid Template uses: actions/stale@v5 with: - stale-issue-message: > - Invalid issues template. close-issue-message: > This issue was closed because the the template was not completed after 5 days. - stale-issue-label: 'invalid:template-incomplete' - stale-pr-message: > - Invalid PR template. close-pr-message: > This PR was closed because the the template was not completed after 5 days. - stale-pr-label: 'invalid:template-incomplete' - exempt-pr-labels: 'status:in-progress' - only-labels: 'invalid:template-incomplete' days-before-stale: 0 days-before-close: 5 + exempt-pr-labels: 'dependencies,l10n' + only-labels: 'invalid:template-incomplete' + stale-issue-label: 'invalid:template-incomplete' + stale-issue-message: > + Invalid issues template. + stale-pr-label: 'invalid:template-incomplete' + stale-pr-message: > + Invalid PR template. diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml index e009fbf2fce..f89975f9986 100644 --- a/.github/workflows/issues.yml +++ b/.github/workflows/issues.yml @@ -1,10 +1,15 @@ -name: Label Actions +--- +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +name: Issues on: issues: types: [labeled, unlabeled] discussion: - types: [ labeled, unlabeled ] + types: [labeled, unlabeled] jobs: label: diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index f4828e99cc6..79674aa427a 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -1,3 +1,4 @@ +--- name: localize on: @@ -5,7 +6,7 @@ on: branches: [nightly] paths: # prevents workflow from running unless these files change - '.github/workflows/localize.yml' - - 'sunshine/**' + - 'src/**' - 'locale/sunshine.po' workflow_dispatch: @@ -18,76 +19,77 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - - name: Install Python 3.9 - uses: actions/setup-python@v4 # https://github.com/actions/setup-python - with: - python-version: '3.9' + - name: Install Python 3.9 + uses: actions/setup-python@v4 # https://github.com/actions/setup-python + with: + python-version: '3.9' - - name: Set up Python 3.9 Dependencies - run: | - cd ./scripts - python -m pip install --upgrade pip setuptools - python -m pip install -r requirements.txt + - name: Set up Python 3.9 Dependencies + run: | + cd ./scripts + python -m pip install --upgrade pip setuptools + python -m pip install -r requirements.txt - - name: Set up xgettext - run: | - sudo apt-get update -y && \ - sudo apt-get --reinstall install -y \ - gettext + - name: Set up xgettext + run: | + sudo apt-get update -y && \ + sudo apt-get --reinstall install -y \ + gettext - - name: Update Strings - run: | - # first, try to remove existing file as xgettext does not remove unused translations - if [ -f "${{ env.file }}" ]; - then - rm ${{ env.file }} - echo "new_file=false" >> $GITHUB_ENV - else - echo "new_file=true" >> $GITHUB_ENV - fi + - name: Update Strings + run: | + # first, try to remove existing file as xgettext does not remove unused translations + if [ -f "${{ env.file }}" ]; + then + rm ${{ env.file }} + echo "new_file=false" >> $GITHUB_ENV + else + echo "new_file=true" >> $GITHUB_ENV + fi - # extract the new strings - python ./scripts/_locale.py --extract + # extract the new strings + python ./scripts/_locale.py --extract - - name: git diff - if: ${{ env.new_file == 'false' }} - run: | - # disable the pager - git config --global pager.diff false + - name: git diff + if: ${{ env.new_file == 'false' }} + run: | + # disable the pager + git config --global pager.diff false - # print the git diff - git diff locale/sunshine.po + # print the git diff + git diff locale/sunshine.po - # set the variable with minimal output - OUTPUT=$(git diff --numstat locale/sunshine.po) - echo "git_diff=${OUTPUT}" >> $GITHUB_ENV + # set the variable with minimal output + OUTPUT=$(git diff --numstat locale/sunshine.po) + echo "git_diff=${OUTPUT}" >> $GITHUB_ENV - - name: git reset - # only run if a single line changed (date/time) and file already existed - if: ${{ env.git_diff == '1 1 locale/sunshine.po' && env.new_file == 'false' }} - run: | - git reset --hard + - name: git reset + # only run if a single line changed (date/time) and file already existed + # \t in next line is a tab character + if: ${{ env.git_diff == '1\t1\tlocale/sunshine.po' && env.new_file == 'false' }} + run: | + git reset --hard - - name: Create/Update Pull Request - uses: peter-evans/create-pull-request@v4 - with: - add-paths: | - locale/*.po - token: ${{ secrets.GH_PAT }} # must trigger PR tests - commit-message: New localization template - branch: localize/update - delete-branch: true - base: nightly - title: New Babel Updates - body: | - Update report - - Updated with *today's* date - - Auto-generated by [create-pull-request][1] + - name: Create/Update Pull Request + uses: peter-evans/create-pull-request@v4 + with: + add-paths: | + locale/*.po + token: ${{ secrets.GH_BOT_TOKEN }} # must trigger PR tests + commit-message: New localization template + branch: localize/update + delete-branch: true + base: nightly + title: New Babel Updates + body: | + Update report + - Updated with *today's* date + - Auto-generated by [create-pull-request][1] - [1]: https://github.com/peter-evans/create-pull-request - labels: | - babel - l10n + [1]: https://github.com/peter-evans/create-pull-request + labels: | + babel + l10n diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 36f597b909e..0abc26b8f2f 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -1,3 +1,8 @@ +--- +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + name: Pull Requests on: @@ -14,8 +19,9 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: target: master - exclude: nightly # Don't prevent going from nightly -> master + exclude: nightly # Don't prevent going from nightly -> master change-to: nightly comment: | - Your PR was set to `master`, PRs should be sent to `nightly` - The base branch of this PR has been automatically changed to `nightly`, please check that there are no merge conflicts + Your PR was set to `master`, PRs should be sent to `nightly`. + The base branch of this PR has been automatically changed to `nightly`. + Please check that there are no merge conflicts diff --git a/.github/workflows/python-flake8.yml b/.github/workflows/python-flake8.yml new file mode 100644 index 00000000000..463fb8a2033 --- /dev/null +++ b/.github/workflows/python-flake8.yml @@ -0,0 +1,31 @@ +--- +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +name: flake8 + +on: + pull_request: + branches: [master, nightly] + types: [opened, synchronize, reopened] + +jobs: + flake8: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 # https://github.com/actions/setup-python + with: + python-version: '3.10' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools flake8 + + - name: Test with flake8 + run: | + python -m flake8 --verbose diff --git a/.github/workflows/release-notifier.yml b/.github/workflows/release-notifier.yml new file mode 100644 index 00000000000..7a2c13ea5cf --- /dev/null +++ b/.github/workflows/release-notifier.yml @@ -0,0 +1,81 @@ +--- +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +name: Release Notifications + +on: + release: + types: [published] + # https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#onevent_nametypes + +jobs: + discord: + runs-on: ubuntu-latest + steps: + - name: discord + uses: sarisia/actions-status-discord@v1 # https://github.com/sarisia/actions-status-discord + with: + webhook: ${{ secrets.DISCORD_RELEASE_WEBHOOK }} + nodetail: true + nofail: false + username: ${{ secrets.DISCORD_USERNAME }} + avatar_url: ${{ secrets.ORG_LOGO_URL }} + title: ${{ github.event.repository.name }} ${{ github.ref_name }} Released + description: ${{ github.event.release.body }} + color: 0xFF4500 + + facebook_group: + runs-on: ubuntu-latest + steps: + - name: facebook-post-action + uses: ReenigneArcher/facebook-post-action@v1 # https://github.com/ReenigneArcher/facebook-post-action + with: + page_id: ${{ secrets.FACEBOOK_GROUP_ID }} + access_token: ${{ secrets.FACEBOOK_ACCESS_TOKEN }} + message: | + ${{ github.event.repository.name }} ${{ github.ref_name }} Released + ${{ github.event.release.body }} + url: ${{ github.event.release.html_url }} + + facebook_page: + runs-on: ubuntu-latest + steps: + - name: facebook-post-action + uses: ReenigneArcher/facebook-post-action@v1 # https://github.com/ReenigneArcher/facebook-post-action + with: + page_id: ${{ secrets.FACEBOOK_PAGE_ID }} + access_token: ${{ secrets.FACEBOOK_ACCESS_TOKEN }} + message: | + ${{ github.event.repository.name }} ${{ github.ref_name }} Released + ${{ github.event.release.body }} + url: ${{ github.event.release.html_url }} + + reddit: + runs-on: ubuntu-latest + steps: + - name: reddit + uses: bluwy/release-for-reddit-action@v1 # https://github.com/bluwy/release-for-reddit-action + with: + username: ${{ secrets.REDDIT_USERNAME }} + password: ${{ secrets.REDDIT_PASSWORD }} + app-id: ${{ secrets.REDDIT_CLIENT_ID }} + app-secret: ${{ secrets.REDDIT_CLIENT_SECRET }} + subreddit: ${{ secrets.REDDIT_SUBREDDIT }} + title: ${{ github.event.repository.name }} ${{ github.ref_name }} Released + url: ${{ github.event.release.html_url }} + flair-id: ${{ secrets.REDDIT_FLAIR_ID }} # https://www.reddit.com/r/>/api/link_flair.json + comment: ${{ github.event.release.body }} + + twitter: + runs-on: ubuntu-latest + steps: + - name: twitter + uses: ethomson/send-tweet-action@v1 # https://github.com/ethomson/send-tweet-action + with: + consumer-key: ${{ secrets.TWITTER_API_KEY }} + consumer-secret: ${{ secrets.TWITTER_API_SECRET }} + access-token: ${{ secrets.TWITTER_ACCESS_TOKEN }} + access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} + status: ${{ github.event.release.html_url }} diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml new file mode 100644 index 00000000000..0cf5b371277 --- /dev/null +++ b/.github/workflows/winget.yml @@ -0,0 +1,17 @@ +--- +name: Publish to WinGet + +on: + release: + types: [released] + +jobs: + winget-releaser: + name: winget releaser + runs-on: windows-latest + steps: + - name: winget releaser + uses: vedantmgoyal2009/winget-releaser@latest + with: + identifier: LizardByte.Sunshine + token: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/yaml-lint.yml b/.github/workflows/yaml-lint.yml new file mode 100644 index 00000000000..83de6c2356c --- /dev/null +++ b/.github/workflows/yaml-lint.yml @@ -0,0 +1,46 @@ +--- +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +name: yaml lint + +on: + pull_request: + branches: [master, nightly] + types: [opened, synchronize, reopened] + +jobs: + yaml-lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: yaml lint + id: yaml-lint + uses: ibiqlik/action-yamllint@v3 + with: + # https://yamllint.readthedocs.io/en/stable/configuration.html#default-configuration + config_data: | + extends: default + rules: + comments: + level: error + line-length: + max: 120 + truthy: + allowed-values: ['true', 'false', 'on'] # GitHub uses "on" for workflow event triggers + check-keys: true + level: error + + - name: Log + run: | + echo ${{ steps.yaml-lint.outputs.logfile }} + + - name: Upload logs + uses: actions/upload-artifact@v2 + if: failure() + with: + name: yamllint-logfile + path: ${{ steps.yaml-lint.outputs.logfile }} diff --git a/.gitmodules b/.gitmodules index 814f340942e..1b60acde639 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,6 +13,6 @@ [submodule "third-party/nv-codec-headers"] path = third-party/nv-codec-headers url = https://github.com/FFmpeg/nv-codec-headers -[submodule "sunshine/platform/macos/TPCircularBuffer"] - path = sunshine/platform/macos/TPCircularBuffer +[submodule "third-party/TPCircularBuffer"] + path = third-party/TPCircularBuffer url = https://github.com/michaeltyson/TPCircularBuffer diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 762371f85d7..156e5624620 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,3 +1,4 @@ +--- # .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details @@ -25,9 +26,9 @@ build: # - cmake . ## Include the submodules, required for cmake -#submodules: -# include: all -# recursive: true +# submodules: +# include: all +# recursive: true # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1844e3559cc..272709ee1ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [0.14.1] - 2022-08-09 +### Added +- (Linux) Flatpak package added +- (Linux) AUR package automated updates +- (Windows) Winget package automated updates +### Changed +- (General) Moved repo to @LizardByte GitHub org +- (WebUI) Fixed button spacing on home page +- (WebUI) Added Discord WidgetBot Crate +### Fixed +- (Linux/Mac) Default config and app files now copied to user home directory +- (Windows) Default config and app files now copied to working directory + ## [0.14.0] - 2022-06-15 ### Added - (Documentation) Added Sphinx documentation available at https://sunshinestream.readthedocs.io/en/latest/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 44f24e7d6b2..d3e36294fc0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,35 @@ cmake_minimum_required(VERSION 3.0) -project(Sunshine VERSION 0.14.0 +project(Sunshine VERSION 0.14.1 DESCRIPTION "Sunshine is a Gamestream host for Moonlight." - HOMEPAGE_URL "https://sunshinestream.github.io" + HOMEPAGE_URL "https://app.lizardbyte.dev" ) +set(PROJECT_LONG_DESCRIPTION "Sunshine is a self hosted, low latency, cloud gaming solution with support for AMD, \ +Intel, and Nvidia GPUs. It is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield. \ +Connect to Sunshine from any Moonlight client, available for nearly any device imaginable.") + +option(SUNSHINE_CONFIGURE_APPIMAGE "Configure files required for AppImage." OFF) +option(SUNSHINE_CONFIGURE_AUR "Configure files required for AUR." OFF) +option(SUNSHINE_CONFIGURE_FLATPAK "Configure files required for Flatpak." OFF) +option(SUNSHINE_CONFIGURE_PORTFILE "Configure macOS Portfile." OFF) +option(SUNSHINE_CONFIGURE_ONLY "Configure special files only, then exit." OFF) + +if(${SUNSHINE_CONFIGURE_APPIMAGE}) + configure_file(packaging/linux/sunshine.desktop sunshine.desktop @ONLY) +elseif(${SUNSHINE_CONFIGURE_AUR}) + configure_file(packaging/linux/aur/PKGBUILD PKGBUILD @ONLY) +elseif(${SUNSHINE_CONFIGURE_FLATPAK}) + configure_file(packaging/linux/flatpak/dev.lizardbyte.sunshine.yml dev.lizardbyte.sunshine.yml @ONLY) +elseif(${SUNSHINE_CONFIGURE_PORTFILE}) + configure_file(packaging/macos/Portfile Portfile @ONLY) +endif() + +# return if configure only is set +if(${SUNSHINE_CONFIGURE_ONLY}) + return() +endif() + set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) set(SUNSHINE_SOURCE_ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src_assets") @@ -75,18 +100,18 @@ if(WIN32) if(NOT DEFINED SUNSHINE_ICON_PATH) set(SUNSHINE_ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/sunshine.ico") endif() - configure_file(sunshine/platform/windows/windows.rs.in windows.rc @ONLY) + configure_file(src/platform/windows/windows.rs.in windows.rc @ONLY) set(PLATFORM_TARGET_FILES "${CMAKE_CURRENT_BINARY_DIR}/windows.rc" - sunshine/platform/windows/publish.cpp - sunshine/platform/windows/misc.h - sunshine/platform/windows/misc.cpp - sunshine/platform/windows/input.cpp - sunshine/platform/windows/display.h - sunshine/platform/windows/display_base.cpp - sunshine/platform/windows/display_vram.cpp - sunshine/platform/windows/display_ram.cpp - sunshine/platform/windows/audio.cpp + src/platform/windows/publish.cpp + src/platform/windows/misc.h + src/platform/windows/misc.cpp + src/platform/windows/input.cpp + src/platform/windows/display.h + src/platform/windows/display_base.cpp + src/platform/windows/display_vram.cpp + src/platform/windows/display_ram.cpp + src/platform/windows/audio.cpp third-party/ViGEmClient/src/ViGEmClient.cpp third-party/ViGEmClient/include/ViGEm/Client.h third-party/ViGEmClient/include/ViGEm/Common.h @@ -129,6 +154,9 @@ if(WIN32) set_source_files_properties(third-party/ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_FLAGS "-Wno-unknown-pragmas -Wno-misleading-indentation -Wno-class-memaccess") elseif(APPLE) add_compile_definitions(SUNSHINE_PLATFORM="macos") + + option(SUNSHINE_MACOS_PACKAGE "Should only be used when creating a MACOS package/dmg." OFF) + link_directories(/opt/local/lib) link_directories(/usr/local/lib) ADD_DEFINITIONS(-DBOOST_LOG_DYN_LINK) @@ -152,21 +180,21 @@ elseif(APPLE) set(APPLE_PLIST_FILE ${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/Info.plist) set(PLATFORM_TARGET_FILES - sunshine/platform/macos/av_audio.h - sunshine/platform/macos/av_audio.m - sunshine/platform/macos/av_img_t.h - sunshine/platform/macos/av_video.h - sunshine/platform/macos/av_video.m - sunshine/platform/macos/display.mm - sunshine/platform/macos/input.cpp - sunshine/platform/macos/microphone.mm - sunshine/platform/macos/misc.cpp - sunshine/platform/macos/misc.h - sunshine/platform/macos/nv12_zero_device.cpp - sunshine/platform/macos/nv12_zero_device.h - sunshine/platform/macos/publish.cpp - sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.c - sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.h + src/platform/macos/av_audio.h + src/platform/macos/av_audio.m + src/platform/macos/av_img_t.h + src/platform/macos/av_video.h + src/platform/macos/av_video.m + src/platform/macos/display.mm + src/platform/macos/input.cpp + src/platform/macos/microphone.mm + src/platform/macos/misc.cpp + src/platform/macos/misc.h + src/platform/macos/nv12_zero_device.cpp + src/platform/macos/nv12_zero_device.h + src/platform/macos/publish.cpp + third-party/TPCircularBuffer/TPCircularBuffer.c + third-party/TPCircularBuffer/TPCircularBuffer.h ${APPLE_PLIST_FILE}) else() add_compile_definitions(SUNSHINE_PLATFORM="linux") @@ -214,14 +242,14 @@ else() if(X11_FOUND) add_compile_definitions(SUNSHINE_BUILD_X11) include_directories(${X11_INCLUDE_DIR}) - list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/x11grab.cpp) + list(APPEND PLATFORM_TARGET_FILES src/platform/linux/x11grab.cpp) endif() if(CUDA_FOUND) include_directories(third-party/nvfbc) list(APPEND PLATFORM_TARGET_FILES - sunshine/platform/linux/cuda.cu - sunshine/platform/linux/cuda.cpp + src/platform/linux/cuda.cu + src/platform/linux/cuda.cpp third-party/nvfbc/NvFBC.h) add_compile_definitions(SUNSHINE_BUILD_CUDA) @@ -231,7 +259,7 @@ else() add_compile_definitions(SUNSHINE_BUILD_DRM) include_directories(${LIBDRM_INCLUDE_DIRS} ${LIBCAP_INCLUDE_DIRS}) list(APPEND PLATFORM_LIBRARIES ${LIBDRM_LIBRARIES} ${LIBCAP_LIBRARIES}) - list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/kmsgrab.cpp) + list(APPEND PLATFORM_TARGET_FILES src/platform/linux/kmsgrab.cpp) list(APPEND SUNSHINE_DEFINITIONS EGL_NO_X11=1) elseif(LIBDRM_FOUND) message(WARNING "Found libdrm, yet there is no libcap") @@ -273,26 +301,26 @@ else() list(APPEND PLATFORM_LIBRARIES ${WAYLAND_LIBRARIES}) list(APPEND PLATFORM_TARGET_FILES - sunshine/platform/linux/wlgrab.cpp - sunshine/platform/linux/wayland.cpp) + src/platform/linux/wlgrab.cpp + src/platform/linux/wayland.cpp) endif() if(NOT ${X11_FOUND} AND NOT (${LIBDRM_FOUND} AND ${LIBCAP_FOUND}) AND NOT ${WAYLAND_FOUND} AND NOT ${}) message(FATAL_ERROR "Couldn't find either x11, wayland, cuda or (libdrm and libcap)") endif() list(APPEND PLATFORM_TARGET_FILES - sunshine/platform/linux/publish.cpp - sunshine/platform/linux/vaapi.h - sunshine/platform/linux/vaapi.cpp - sunshine/platform/linux/cuda.h - sunshine/platform/linux/graphics.h - sunshine/platform/linux/graphics.cpp - sunshine/platform/linux/misc.h - sunshine/platform/linux/misc.cpp - sunshine/platform/linux/audio.cpp - sunshine/platform/linux/input.cpp - sunshine/platform/linux/x11grab.h - sunshine/platform/linux/wayland.h + src/platform/linux/publish.cpp + src/platform/linux/vaapi.h + src/platform/linux/vaapi.cpp + src/platform/linux/cuda.h + src/platform/linux/graphics.h + src/platform/linux/graphics.cpp + src/platform/linux/misc.h + src/platform/linux/misc.cpp + src/platform/linux/audio.cpp + src/platform/linux/input.cpp + src/platform/linux/x11grab.h + src/platform/linux/wayland.h third-party/glad/src/egl.c third-party/glad/src/gl.c third-party/glad/include/EGL/eglplatform.h @@ -328,47 +356,47 @@ set(SUNSHINE_TARGET_FILES third-party/moonlight-common-c/src/Rtsp.h third-party/moonlight-common-c/src/RtspParser.c third-party/moonlight-common-c/src/Video.h - sunshine/upnp.cpp - sunshine/upnp.h - sunshine/cbs.cpp - sunshine/utility.h - sunshine/uuid.h - sunshine/config.h - sunshine/config.cpp - sunshine/main.cpp - sunshine/main.h - sunshine/crypto.cpp - sunshine/crypto.h - sunshine/nvhttp.cpp - sunshine/nvhttp.h - sunshine/httpcommon.cpp - sunshine/httpcommon.h - sunshine/confighttp.cpp - sunshine/confighttp.h - sunshine/rtsp.cpp - sunshine/rtsp.h - sunshine/stream.cpp - sunshine/stream.h - sunshine/video.cpp - sunshine/video.h - sunshine/input.cpp - sunshine/input.h - sunshine/audio.cpp - sunshine/audio.h - sunshine/platform/common.h - sunshine/process.cpp - sunshine/process.h - sunshine/network.cpp - sunshine/network.h - sunshine/move_by_copy.h - sunshine/task_pool.h - sunshine/thread_pool.h - sunshine/thread_safe.h - sunshine/sync.h - sunshine/round_robin.h + src/upnp.cpp + src/upnp.h + src/cbs.cpp + src/utility.h + src/uuid.h + src/config.h + src/config.cpp + src/main.cpp + src/main.h + src/crypto.cpp + src/crypto.h + src/nvhttp.cpp + src/nvhttp.h + src/httpcommon.cpp + src/httpcommon.h + src/confighttp.cpp + src/confighttp.h + src/rtsp.cpp + src/rtsp.h + src/stream.cpp + src/stream.h + src/video.cpp + src/video.h + src/input.cpp + src/input.h + src/audio.cpp + src/audio.h + src/platform/common.h + src/process.cpp + src/process.h + src/network.cpp + src/network.h + src/move_by_copy.h + src/task_pool.h + src/thread_pool.h + src/thread_safe.h + src/sync.h + src/round_robin.h ${PLATFORM_TARGET_FILES}) -set_source_files_properties(sunshine/upnp.cpp PROPERTIES COMPILE_FLAGS -Wno-pedantic) +set_source_files_properties(src/upnp.cpp PROPERTIES COMPILE_FLAGS -Wno-pedantic) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} @@ -386,7 +414,7 @@ string(TOUPPER "x${CMAKE_BUILD_TYPE}" BUILD_TYPE) if("${BUILD_TYPE}" STREQUAL "XDEBUG") list(APPEND SUNSHINE_COMPILE_OPTIONS -O0 -ggdb3) if(WIN32) - set_source_files_properties(sunshine/nvhttp.cpp PROPERTIES COMPILE_FLAGS -O2) + set_source_files_properties(src/nvhttp.cpp PROPERTIES COMPILE_FLAGS -O2) endif() else() add_definitions(-DNDEBUG) @@ -401,6 +429,11 @@ if(NOT SUNSHINE_CONFIG_DIR) set(SUNSHINE_CONFIG_DIR "${CMAKE_CURRENT_BINARY_DIR}/config") endif() +if(UNIX AND CMAKE_INSTALL_PREFIX AND NOT ${SUNSHINE_CONFIGURE_APPIMAGE}) + set(SUNSHINE_ASSETS_DIR "${CMAKE_INSTALL_PREFIX}/${SUNSHINE_ASSETS_DIR}") + set(SUNSHINE_CONFIG_DIR "${CMAKE_INSTALL_PREFIX}/${SUNSHINE_CONFIG_DIR}") +endif() + list(APPEND CBS_EXTERNAL_LIBRARIES cbs) @@ -415,7 +448,7 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${OPENSSL_LIBRARIES} ${PLATFORM_LIBRARIES}) -if (NOT WIN32) +if(NOT WIN32) list(APPEND SUNSHINE_EXTERNAL_LIBRARIES Boost::log) endif() @@ -447,11 +480,11 @@ target_compile_options(sunshine PRIVATE $<$:${SUNSHINE_COM # CPACK / Packaging # Common options -set(CPACK_PACKAGE_NAME "SunshineStream") -set(CPACK_PACKAGE_VENDOR "SunshineStream") +set(CPACK_PACKAGE_NAME "Sunshine") +set(CPACK_PACKAGE_VENDOR "LizardByte") set(CPACK_PACKAGE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/cpack_artifacts) -set(CPACK_PACKAGE_CONTACT "https://github.com/SunshineStream/Sunshine") -set(CPACK_DEBIAN_PACKAGE_MAINTAINER "https://github.com/SunshineStream/Sunshine") +set(CPACK_PACKAGE_CONTACT "https://app.lizardbyte.dev") +set(CPACK_DEBIAN_PACKAGE_MAINTAINER "https://github.com/LizardByte") set(CPACK_PACKAGE_DESCRIPTION ${CMAKE_PROJECT_DESCRIPTION}) set(CPACK_PACKAGE_HOMEPAGE_URL ${CMAKE_PROJECT_HOMEPAGE_URL}) set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE) @@ -533,63 +566,64 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h set(CPACK_COMPONENT_SUNSHINESVC_DESCRIPTION "CLI tool that allows you to enable/disable the Sunshine service.") set(CPACK_COMPONENT_SUNSHINESVC_GROUP "Tools") endif() -if(UNIX) - # Installation destination dir - set(CPACK_SET_DESTDIR true) - if(NOT CMAKE_INSTALL_PREFIX) - set(CMAKE_INSTALL_PREFIX "/usr/local/sunshine") - endif() - - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") - - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") +if(APPLE) + # TODO: bundle doesn't produce a valid .app use cpack -G DragNDrop + set(CPACK_BUNDLE_NAME "${CMAKE_PROJECT_NAME}") + set(CPACK_BUNDLE_PLIST "${APPLE_PLIST_FILE}") + set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/sunshine.icns") + # set(CPACK_BUNDLE_STARTUP_COMMAND "${INSTALL_RUNTIME_DIR}/sunshine") endif() -if(APPLE) # TODO: test - +if(APPLE AND SUNSHINE_MACOS_PACKAGE) # TODO set(prefix "${CMAKE_PROJECT_NAME}.app/Contents") set(INSTALL_RUNTIME_DIR "${prefix}/MacOS") - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") install(TARGETS sunshine BUNDLE DESTINATION . COMPONENT Runtime RUNTIME DESTINATION ${INSTALL_RUNTIME_DIR} COMPONENT Runtime) +elseif(UNIX) + # Installation destination dir + set(CPACK_SET_DESTDIR true) + if(NOT CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_PREFIX "/usr/local/sunshine") + endif() - # TODO: bundle doesn't produce a valid .app use cpack -G DragNDrop - set(CPACK_BUNDLE_NAME "${CMAKE_PROJECT_NAME}") - set(CPACK_BUNDLE_PLIST "${APPLE_PLIST_FILE}") - set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/sunshine.icns") - # set(CPACK_BUNDLE_STARTUP_COMMAND "${INSTALL_RUNTIME_DIR}/sunshine") - - # Portfile - configure_file(Portfile.in Portfile @ONLY) -endif() -if(UNIX AND NOT APPLE) - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") - - install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") - - install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine-rules.rules" DESTINATION "/etc/udev/rules.d") - - install(TARGETS sunshine RUNTIME DESTINATION "/usr/bin") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "/usr/lib/systemd/user") + install(TARGETS sunshine RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") - # Pre and post install - set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA - "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/preinst;${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst;${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/conffiles") - set(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/preinst") - set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst") + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") - # Dependencies - set(CPACK_DEB_COMPONENT_INSTALL ON) - set(CPACK_DEBIAN_PACKAGE_DEPENDS "openssl, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") - set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.1, libavdevice >= 4.3, boost-thread >= 1.67.0, boost-filesystem >= 1.67.0, boost-log >= 1.67.0, pulseaudio-libs >= 10.0, libopusenc >= 0.2.1, libxcb >= 1.13, libXtst >= 1.2.3, libevdev >= 1.5.6, libdrm >= 2.4.97, libcap >= 2.22") - set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # This should automatically figure out dependencies, doesn't work with the current config + if(APPLE) + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") - # AppImage desktop file - configure_file(sunshine.desktop.in sunshine.desktop @ONLY) + install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/misc/uninstall_pkg.sh" DESTINATION "${SUNSHINE_ASSETS_DIR}") + else() + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") + install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}") + + install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine.rules" DESTINATION "${CMAKE_INSTALL_LIBDIR}/udev/rules.d") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "${CMAKE_INSTALL_LIBDIR}/systemd/user") + + # Pre and post install + set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA + "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/preinst" + "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst" + "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/conffiles") + set(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/preinst") + set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst") + + # Dependencies + set(CPACK_DEB_COMPONENT_INSTALL ON) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "openssl, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2") + set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.1, libavdevice >= 4.3, boost-thread >= 1.67.0, boost-filesystem >= 1.67.0, boost-log >= 1.67.0, pulseaudio-libs >= 10.0, libopusenc >= 0.2.1, libxcb >= 1.13, libXtst >= 1.2.3, libevdev >= 1.5.6, libdrm >= 2.4.97, libcap >= 2.22") + set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # This should automatically figure out dependencies, doesn't work with the current config + endif() endif() include(CPack) diff --git a/DOCKER_README.md b/DOCKER_README.md index aa2f1017a62..a2647e00d75 100644 --- a/DOCKER_README.md +++ b/DOCKER_README.md @@ -11,16 +11,10 @@ docker run -d \ -e PUID= \ -e PGID= \ -e TZ= \ - -p 47990:47990 \ - -p 47984:47984 \ - -p 47989:47989 \ + -p 47984-47990:47984-47990/tcp \ -p 48010:48010 \ - -p 47998:47998 \ - -p 47999:47999 \ - -p 48000:48000 \ - -p 48002:48002 \ - -p 48010:48010 \ - sunshinestream/sunshine + -p 47998-48000:47998-48000/udp \ + lizardbyte/sunshine ``` To update the container it must be removed and recreated: @@ -31,7 +25,7 @@ docker stop sunshine # Remove the container docker rm sunshine # Pull the latest update -docker pull sunshinestream/sunshine +docker pull lizardbyte/sunshine # Run the container with the same parameters as before docker run -d ... ``` @@ -44,25 +38,19 @@ Create a `docker-compose.yml` file with the following contents (substitute your version: '3' services: sunshine: - image: sunshinestream/sunshine + image: lizardbyte/sunshine container_name: sunshine restart: unless-stopped volumes: - - :/config + - :/config environment: - - PUID= - - PGID= - - TZ= + - PUID= + - PGID= + - TZ= ports: - - "47990:47990" - - "47984:47984" - - "47989:47989" - - "48010:48010" - - "47998:47998" - - "47999:47999" - - "48000:48000" - - "48002:48002" - - "48010:48010" + - 47984-47990:47984-47990/tcp + - 48010:48010 + - 47998-48000:47998-48000/udp ``` Create and start the container (run the command from the same folder as your `docker-compose.yml` file): @@ -88,7 +76,7 @@ container. **Example:** `-p external:internal` - This shows the port mapping from internal to external of the container. Therefore `-p 47990:47990` would expose port `47990` from inside the container to be accessible from the host's IP on port `47990` (e.g. `http://:47990`). The internal port must be `47990`, but the external port may be changed -(e.g. `-p 8080:47990`). +(e.g. `-p 8080:47990`). All the ports listed in the `docker run` and `docker-compose` examples are required. | Parameter | Function | Example Value | Required | diff --git a/Portfile.in b/Portfile.in deleted file mode 100644 index 36a7d476a24..00000000000 --- a/Portfile.in +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4 - -PortSystem 1.0 -PortGroup cmake 1.1 -PortGroup github 1.0 -PortGroup boost 1.0 - -# bump revision when changes are made to this file -revision 1 - -github.setup @GITHUB_OWNER@ @GITHUB_REPO@ @GITHUB_BRANCH@ -name @PROJECT_NAME@ -version @PROJECT_VERSION@ -categories multimedia emulators games -platforms darwin -license GPL-3 -maintainers {@SunshineStream sunshinestream} -description @PROJECT_DESCRIPTION@ -long_description {*}${description} -homepage @PROJECT_HOMEPAGE_URL@ -master_sites https://github.com/@GITHUB_OWNER@/@GITHUB_REPO@/releases - -fetch.type git -post-fetch { - system -W ${worksrcpath} "${git.cmd} submodule update --init --recursive" -} - -depends_lib port:avahi \ - port:ffmpeg \ - port:libopus - -boost.version 1.76 - -configure.args -DBOOST_ROOT=[boost::install_area] \ - -DSUNSHINE_ASSETS_DIR=${prefix}/etc/sunshine/assets \ - -DSUNSHINE_CONFIG_DIR=${prefix}/etc/sunshine/config - -cmake.out_of_source yes - -startupitem.create yes -startupitem.executable "${prefix}/bin/{$name}" -startupitem.location LaunchDaemons -startupitem.name ${name} -startupitem.netchange yes - -# is this actually necessary? this should all be handled by CMakeLists -destroot { - # install assets - xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets - xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/*.*] ${destroot}${prefix}/etc/${name}/assets - xinstall {*}[glob ${worksrcpath}/src_assets/macos/assets/*.*] ${destroot}${prefix}/etc/${name}/assets - - # install web assets - xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web/css - xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web/webfonts - xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/*.*] ${destroot}${prefix}/etc/${name}/assets/web - xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/fonts/fontawesome-free-web/*.*] ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web - xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/fonts/fontawesome-free-web/css/*.*] ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web/css - xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/fonts/fontawesome-free-web/webfonts/*.*] ${destroot}${prefix}/etc/${name}/assets/web/fonts/fontawesome-free-web/webfonts - xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets/web/images - xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/assets/web/third_party - xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/images/*.*] ${destroot}${prefix}/etc/${name}/assets/web/images - xinstall {*}[glob ${worksrcpath}/src_assets/common/assets/web/third_party/*.*] ${destroot}${prefix}/etc/${name}/assets/web/third_party - - xinstall -d -m 755 ${destroot}${prefix}/etc/${name}/config - - # install sunshine.conf - xinstall {*}[glob ${worksrcpath}/src_assets/common/config/*.*] ${destroot}${prefix}/etc/${name}/config - - # install apps.json - xinstall {*}[glob ${worksrcpath}/src_assets/macos/config/*.*] ${destroot}${prefix}/etc/${name}/config - - # install the binary - xinstall ${workpath}/build/${name} ${destroot}${prefix}/bin -} diff --git a/README.rst b/README.rst index 8fb29b6b355..8d51ff0969e 100644 --- a/README.rst +++ b/README.rst @@ -1,23 +1,27 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/README.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/README.rst Overview ======== +LizardByte has the full documentation hosted on `Read the Docs `_. About ----- -Sunshine is a Game stream host for Moonlight. It is an open source version of GeForce Experience (GFE). +Sunshine is a Game stream host for Moonlight. +Sunshine is a self hosted, low latency, cloud gaming solution with support for AMD, Intel, and Nvidia gpus. +It is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield. +Connect to Sunshine from any Moonlight client, available for nearly any device imaginable. -These are the advantages of Sunshine over GFE. +These are the advantages of Sunshine over GeForce Experience. - FOSS (Free and Open Source Software) - Multi-platform - - Linux (deb, rpm, and AppImage packages) - - MacOS (Portfile) - - Windows (portable binary) + - Linux + - macOS + - Windows - Pair over web ui - - Supports AMD and Nvidia GPUs for encoding + - Supports AMD, Intel, and Nvidia GPUs for encoding - Supports software encoding - Supports streaming to multiple clients - Web UI for configuration @@ -25,13 +29,13 @@ These are the advantages of Sunshine over GFE. Integrations ------------ -.. image:: https://img.shields.io/github/workflow/status/sunshinestream/sunshine/CI/master?label=CI%20build&logo=github&style=for-the-badge +.. image:: https://img.shields.io/github/workflow/status/lizardbyte/sunshine/CI/master?label=CI%20build&logo=github&style=for-the-badge :alt: GitHub Workflow Status (CI) - :target: https://github.com/SunshineStream/Sunshine/actions/workflows/CI.yml?query=branch%3Amaster + :target: https://github.com/LizardByte/Sunshine/actions/workflows/CI.yml?query=branch%3Amaster -.. image:: https://img.shields.io/github/workflow/status/sunshinestream/sunshine/localize/nightly?label=localize%20build&logo=github&style=for-the-badge +.. image:: https://img.shields.io/github/workflow/status/lizardbyte/sunshine/localize/nightly?label=localize%20build&logo=github&style=for-the-badge :alt: GitHub Workflow Status (localize) - :target: https://github.com/SunshineStream/Sunshine/actions/workflows/localize.yml?query=branch%3Anightly + :target: https://github.com/LizardByte/Sunshine/actions/workflows/localize.yml?query=branch%3Anightly .. image:: https://img.shields.io/readthedocs/sunshinestream?label=Docs&style=for-the-badge&logo=readthedocs :alt: Read the Docs @@ -44,22 +48,20 @@ Integrations Support --------- -.. image:: https://img.shields.io/discord/938534566107418705?label=Discord&style=for-the-badge&color=blue&logo=discord - :alt: Discord - :target: https://sunshinestream.github.io/discord - -.. image:: https://img.shields.io/github/discussions/sunshinestream/sunshine?logo=github&style=for-the-badge - :alt: GitHub Discussions - :target: https://github.com/SunshineStream/Sunshine/discussions +Our support methods are listed in our `LizardByte Docs `_. Downloads --------- -.. image:: https://img.shields.io/github/downloads/sunshinestream/sunshine/total?style=for-the-badge&logo=github +.. image:: https://img.shields.io/github/downloads/lizardbyte/sunshine/total?style=for-the-badge&logo=github :alt: GitHub Releases - :target: https://github.com/SunshineStream/Sunshine/releases/latest + :target: https://github.com/LizardByte/Sunshine/releases/latest + +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=AUR&style=for-the-badge&query=$.results.0.NumVotes&url=https%3A%2F%2Fapp.lizardbyte.dev%2Funo%2Faur%2Fsunshine.json&logo=archlinux + :alt: AUR votes + :target: https://aur.archlinux.org/packages/sunshine .. comment - image:: https://img.shields.io/docker/pulls/sunshinestream/sunshine?style=for-the-badge&logo=docker + image:: https://img.shields.io/docker/pulls/lizardbyte/sunshine?style=for-the-badge&logo=docker :alt: Docker - :target: https://hub.docker.com/r/sunshinestream/sunshine + :target: https://hub.docker.com/r/lizardbyte/sunshine diff --git a/crowdin.yml b/crowdin.yml index ca9c8c5c58b..0be504ba7d8 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,3 +1,4 @@ +--- "base_path": "." "base_url": "https://api.crowdin.com" # optional (for Crowdin Enterprise only) "preserve_hierarchy": false # flatten tree on crowdin @@ -6,10 +7,10 @@ "l10n" ] -"files" : [ +"files": [ { - "source" : "/locale/*.po", - "translation" : "/locale/%two_letters_code%/LC_MESSAGES/%original_file_name%", + "source": "/locale/*.po", + "translation": "/locale/%two_letters_code%/LC_MESSAGES/%original_file_name%", "languages_mapping": { "two_letters_code": { # map non-two letter codes here, left side is crowdin designation, right side is babel designation diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index e0903223454..0d65175e4c2 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/about/advanced_usage.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/about/advanced_usage.rst Advanced Usage ============== @@ -17,14 +17,16 @@ location by modifying the configuration file. .. table:: :widths: auto - ======= =========== - Value Description - ======= =========== - Docker /config/ - Linux /usr/local/sunshine/config/ - MacOS /usr/local/sunshine/config/ - Windows ./config/ - ======= =========== + ========= =========== + Value Description + ========= =========== + Docker /config/ + Linux-aur /usr/share/sunshine/config/ + Linux-deb /usr/local/sunshine/config/ + Linux-rpm /usr/local/sunshine/config/ + macOS /usr/local/sunshine/config/ + Windows ./config/ + ========= =========== Example .. code-block:: bash @@ -225,7 +227,7 @@ Description To be supported by Sunshine, it needs to have at the very minimum: ``VAProfileH264High : VAEntrypointEncSlice`` - .. Todo:: MacOS + .. Todo:: macOS Windows .. code-block:: batch @@ -241,7 +243,7 @@ Examples adapter_name = /dev/dri/renderD128 - .. Todo:: MacOS + .. Todo:: macOS Windows .. code-block:: text @@ -265,7 +267,7 @@ Description You need to use the value before the colon in the output, e.g. ``0``. - .. Todo:: MacOS + .. Todo:: macOS Windows .. code-block:: batch @@ -281,7 +283,7 @@ Examples output_name = 0 - .. Todo:: MacOS + .. Todo:: macOS Windows .. code-block:: text @@ -376,8 +378,8 @@ Description # in some causes you'd need to use the `Sink` device, if `Source` doesn't work, so try: pactl info | grep Sink - MacOS - Sunshine can only access microphones on MacOS due to system limitations. To stream system audio use + macOS + Sunshine can only access microphones on macOS due to system limitations. To stream system audio use `Soundflower `_ or `BlackHole `_. @@ -395,7 +397,7 @@ Examples audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo - MacOS + macOS .. code-block:: text audio_sink = BlackHole 2ch @@ -993,7 +995,7 @@ vt_software Description Force Video Toolbox to use software encoding. - .. Note:: This option only applies when using MacOS. + .. Note:: This option only applies when using macOS. **Choices** @@ -1023,7 +1025,7 @@ vt_realtime Description Realtime encoding. - .. Note:: This option only applies when using MacOS. + .. Note:: This option only applies when using macOS. .. Warning:: Disabling realtime encoding might result in a delayed frame encoding or frame drop. @@ -1041,7 +1043,7 @@ vt_coder Description The entropy encoding to use. - .. Note:: This option only applies when using MacOS. + .. Note:: This option only applies when using macOS. **Choices** diff --git a/docs/source/about/docker.rst b/docs/source/about/docker.rst index f9afa251915..8a597e261fa 100644 --- a/docs/source/about/docker.rst +++ b/docs/source/about/docker.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/DOCKER_README.md +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/DOCKER_README.md .. Todo:: This is a planned feature. Currently no Dockerfile or image exists for Sunshine. diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 2176206a851..f1687b3d1c0 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/about/installation.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/about/installation.rst Installation ============ @@ -9,8 +9,6 @@ Binaries Binaries of Sunshine are created for each release. They are available for Linux, and Windows. Binaries can be found in the `latest release`_. -.. Todo:: Create binary package(s) for MacOS. See `here `_. - .. Tip:: Some third party packages also exist. See :ref:`Third Party Packages `. @@ -25,10 +23,10 @@ Follow the instructions for your preferred package type below. AppImage ^^^^^^^^ -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:appimage?logo=github&style=for-the-badge +.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/pkg:appimage?logo=github&style=for-the-badge :alt: GitHub issues by-label -The current known compatibility of the AppImage is shown below. +According to AppImageLint the AppImage can run on the following distros. - [✖] Debian oldstable (buster) - [✔] Debian stable (bullseye) @@ -42,11 +40,21 @@ The current known compatibility of the AppImage is shown below. - [✖] Ubuntu trusty - [✖] CentOS 7 -#. Download and extract ``sunshine-appimage.zip`` to your home directory. +#. Download ``sunshine-appimage.zip`` and extract the contents to your home directory. -Debian Packages -^^^^^^^^^^^^^^^ -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:debian?logo=github&style=for-the-badge +AUR Package +^^^^^^^^^^^ +#. Open terminal and run the following code. + + .. code-block:: bash + + git clone https://aur.archlinux.org/sunshine-git.git + cd sunshine-git + makepkg -fi + +Debian Package +^^^^^^^^^^^^^^ +.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/pkg:deb?logo=github&style=for-the-badge :alt: GitHub issues by-label #. Download ``sunshine.deb`` and run the following code. @@ -57,9 +65,29 @@ Debian Packages .. Tip:: You can double click the deb file to see details about the package and begin installation. -Red Hat Packages -^^^^^^^^^^^^^^^^ -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:fedora?logo=github&style=for-the-badge +Flatpak Package +^^^^^^^^^^^^^^^ +.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/pkg:flatpak?logo=github&style=for-the-badge + :alt: GitHub issues by-label + +.. Todo:: This package needs to have CUDA added. + +#. Install `Flatpak `_ as required. +#. Download ``sunshine.flatpak`` and run the following code. + + System level (recommended) + .. code-block:: bash + + flatpak install --system sunshine.flatpak + + User level + .. code-block:: bash + + flatpak install --user sunshine.flatpak + +RPM Package +^^^^^^^^^^^ +.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/pkg:rpm?logo=github&style=for-the-badge :alt: GitHub issues by-label #. Add `rpmfusion` repositories by running the following code. @@ -77,11 +105,16 @@ Red Hat Packages .. Tip:: You can double click the rpm file to see details about the package and begin installation. -MacOS +macOS ----- -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:macos?logo=github&style=for-the-badge +.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/os:macos?logo=github&style=for-the-badge :alt: GitHub issues by-label +pkg + .. Warning:: The `pkg` does not include runtime dependencies and should be considered experimental. + + #. Download the ``sunshine.pkg`` file and install it as normal. + Portfile #. Install `MacPorts `_ #. Update the Macports sources. @@ -110,10 +143,10 @@ Portfile Windows ------- -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:10?logo=github&style=for-the-badge +.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/os:windows:10?logo=github&style=for-the-badge :alt: GitHub issues by-label -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:11?logo=github&style=for-the-badge +.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/os:windows:11?logo=github&style=for-the-badge :alt: GitHub issues by-label Installed option: @@ -122,6 +155,6 @@ Installed option: Standalone option: #. Download and extract ``sunshine-windows.zip`` -.. _latest release: https://github.com/SunshineStream/Sunshine/releases/latest -.. _Dockerhub.io: https://hub.docker.com/repository/docker/sunshinestream/sunshine -.. _ghcr.io: https://github.com/orgs/SunshineStream/packages?repo_name=sunshine +.. _latest release: https://github.com/LizardByte/Sunshine/releases/latest +.. _Dockerhub.io: https://hub.docker.com/repository/docker/lizardbyte/sunshine +.. _ghcr.io: https://github.com/orgs/LizardByte/packages?repo_name=sunshine diff --git a/docs/source/about/third_party_packages.rst b/docs/source/about/third_party_packages.rst index 5b63a496bdc..5ae51e8bc9e 100644 --- a/docs/source/about/third_party_packages.rst +++ b/docs/source/about/third_party_packages.rst @@ -1,25 +1,9 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/about/third_party_packages.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/about/third_party_packages.rst Third Party Packages ==================== -.. Danger:: These packages are not maintained by SunshineStream. Use at your own risk. - -AUR (Arch Linux User Repository) --------------------------------- - -.. image:: https://img.shields.io/aur/version/sunshine?style=for-the-badge&logo=archlinux - :alt: AUR version - :target: https://aur.archlinux.org/packages/sunshine - -.. image:: https://img.shields.io/aur/last-modified/sunshine?style=for-the-badge&logo=archlinux - :alt: AUR last modified - -.. image:: https://img.shields.io/aur/votes/sunshine?style=for-the-badge&logo=archlinux - :alt: AUR votes - -.. image:: https://img.shields.io/aur/maintainer/sunshine?style=for-the-badge&logo=archlinux - :alt: AUR maintainer +.. Danger:: These packages are not maintained by LizardByte. Use at your own risk. Chocolatey ---------- @@ -38,6 +22,12 @@ Scoop :alt: Scoop Version (extras bucket) :target: https://scoop.sh/#/apps?s=0&d=1&o=true&q=sunshine +Winget +------ +.. image:: https://img.shields.io/badge/dynamic/xml?color=orange&label=Winget&style=for-the-badge&prefix=v&query=%2F%2Ftr%5B%40id%3D%27winget%27%5D%2Ftd%5B3%5D%2Fspan%2Fa&url=https%3A%2F%2Frepology.org%2Fproject%2Fsunshine%2Fversions&logo=microsoft + :alt: Winget Version + :target: https://github.com/microsoft/winget-pkgs/tree/master/manifests/l/LizardByte/Sunshine + Legacy GitHub Repo ------------------ diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst index 79d285f9cfe..009585ed031 100644 --- a/docs/source/about/usage.rst +++ b/docs/source/about/usage.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/about/usage.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/about/usage.rst Usage ===== @@ -27,13 +27,13 @@ Usage list of applications that are started just before running a stream. This is the directory within the GitHub repo. - .. Attention:: Application list is not fully supported on MacOS + .. Attention:: Application list is not fully supported on macOS #. In Moonlight, you may need to add the PC manually. #. When Moonlight request you insert the correct pin on sunshine: - Login to the web ui - - Go to "PIN" in the Header + - Go to "PIN" in the Navbar - Type in your PIN and press Enter, you should get a Success Message - In Moonlight, select one of the Applications listed @@ -60,79 +60,85 @@ The deb and rpm packages handle these steps automatically. The AppImage does not Sunshine needs access to `uinput` to create mouse and gamepad events. -Add user to group `input`, if this is the first time installing. - .. code-block:: bash +#. Add user to group `input`, if this is the first time installing. + .. code-block:: bash - sudo usermod -a -G input $USER - sudo reboot now + sudo usermod -a -G input $USER -Create `udev` rules. - .. code-block:: bash +#. Create `udev` rules. + .. code-block:: bash - sudo nano /etc/udev/rules.d/85-sunshine-input.rules + sudo nano /etc/udev/rules.d/85-sunshine.rules - Input the following contents. + Input the following contents. - .. code-block:: + .. code-block:: - KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput" + KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput" - Save the file and exit: + Save the file and exit: - #. ``CTRL+X`` to start exit. - #. ``Y`` to save modifications. + #. ``CTRL+X`` to start exit. + #. ``Y`` to save modifications. -Configure autostart service - - filename: ``~/.config/systemd/user/sunshine.service`` - - contents: +#. Optionally, configure autostart service + - filename: ``~/.config/systemd/user/sunshine.service`` + - contents: - .. code-block:: + .. code-block:: - [Unit] - Description=Sunshine Gamestream Server for Moonlight + [Unit] + Description=Sunshine Gamestream Server for Moonlight - [Service] - ExecStart= + [Service] + ExecStart= - [Install] - WantedBy=graphical-session.target + [Install] + WantedBy=graphical-session.target - .. table:: - :widths: auto + .. table:: + :widths: auto - ======== =================== =============== - package ExecStart Auto Configured - ======== =================== =============== - deb /usr/bin/sunshine ✔ - rpm /usr/bin/sunshine ✔ - AppImage ~/sunshine.AppImage ✖ - ======== =================== =============== + ======== ============================================== =============== + package ExecStart Auto Configured + ======== ============================================== =============== + aur /usr/bin/sunshine ✔ + deb /usr/bin/sunshine ✔ + rpm /usr/bin/sunshine ✔ + AppImage ~/sunshine.AppImage ✖ + Flatpak flatpak run dev.lizardbyte.sunshine ✖ + ======== ============================================== =============== - Start once - .. code-block:: bash + Start once + .. code-block:: bash - systemctl --user start sunshine + systemctl --user start sunshine - Start on boot - .. code-block:: bash + Start on boot + .. code-block:: bash - systemctl --user enable sunshine + systemctl --user enable sunshine -Additional Setup for KMS - .. Note:: ``cap_sys_admin`` may as well be root, except you don't need to be root to run it. It is necessary to - allow Sunshine to use KMS. +#. Additional Setup for KMS + .. Note:: ``cap_sys_admin`` may as well be root, except you don't need to be root to run it. It is necessary to + allow Sunshine to use KMS. - Enable - .. code-block:: bash + Enable + .. code-block:: bash + + sudo setcap cap_sys_admin+p $(readlink -f $(which sunshine)) + + Disable + .. code-block:: bash - sudo setcap cap_sys_admin+p $(readlink -f $(which sunshine)) + sudo setcap -r $(readlink -f $(which sunshine)) - Disable +#. Reboot .. code-block:: bash - sudo setcap -r $(readlink -f $(which sunshine)) + sudo reboot now -MacOS +macOS ^^^^^ Sunshine can only access microphones on macOS due to system limitations. To stream system audio use `Soundflower `_ or diff --git a/docs/source/building/build.rst b/docs/source/building/build.rst index cd83cb14032..4ce4745e393 100644 --- a/docs/source/building/build.rst +++ b/docs/source/building/build.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/building/build.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/building/build.rst Build ===== @@ -14,7 +14,7 @@ Ensure `git `_ is installed and run the following: .. code-block:: bash - git clone https://github.com/sunshinestream/sunshine.git --recurse-submodules + git clone https://github.com/lizardbyte/sunshine.git --recurse-submodules cd sunshine && mkdir build && cd build Compile @@ -22,7 +22,7 @@ Compile See the section specific to your OS. - :ref:`Linux ` -- :ref:`MacOS ` +- :ref:`macOS ` - :ref:`Windows ` Remote Build diff --git a/docs/source/building/linux.rst b/docs/source/building/linux.rst index 475e0c66969..c841ad61cfb 100644 --- a/docs/source/building/linux.rst +++ b/docs/source/building/linux.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/building/linux.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/building/linux.rst Linux ===== diff --git a/docs/source/building/macos.rst b/docs/source/building/macos.rst index 51ae6b1597a..b719c6ee80b 100644 --- a/docs/source/building/macos.rst +++ b/docs/source/building/macos.rst @@ -1,11 +1,11 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/building/macos.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/building/macos.rst -MacOS +macOS ===== Requirements ------------ -MacOS Big Sur and Xcode 12.5+ +macOS Big Sur and Xcode 12.5+ Use either `MacPorts `_ or `Homebrew `_ @@ -35,7 +35,7 @@ Build cmake .. make -j ${nproc} - cpack -G DragNDrop # optionally, create a MacOS dmg package + cpack -G DragNDrop # optionally, create a macOS dmg package If cmake fails complaining to find Boost, try to set the path explicitly. diff --git a/docs/source/building/windows.rst b/docs/source/building/windows.rst index 7708da49b73..0c6dde572c1 100644 --- a/docs/source/building/windows.rst +++ b/docs/source/building/windows.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/building/windows.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/building/windows.rst Windows ======= diff --git a/docs/source/conf.py b/docs/source/conf.py index f4873f1531a..5ef4356c70e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -83,7 +83,7 @@ 'prev_next_buttons_location': 'bottom', 'style_external_links': True, 'vcs_pageview_mode': 'blob', - # 'style_nav_header_background': 'white', + 'style_nav_header_background': '#151515', # Toc options 'collapse_navigation': True, 'sticky_navigation': True, diff --git a/docs/source/contributing/contributing.rst b/docs/source/contributing/contributing.rst index d9f5106f398..e79fd4eab7e 100644 --- a/docs/source/contributing/contributing.rst +++ b/docs/source/contributing/contributing.rst @@ -1,7 +1,12 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/contributing/contributing.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/contributing/contributing.rst Contributing ============ + +.. Tip:: If this is your first time contributing to an open source project, it is a good idea to read + MDN's `Basic etiquette for open source projects`_ first. There are a few best practices to adopt that will help + ensure that you and the other project contributors feel valued and safe, and stay productive. + #. Fork the repo on GitHub #. Create a new branch for the feature you are adding or the issue you are fixing @@ -30,3 +35,5 @@ Contributing - Were documentation blocks updated for new or modified components? .. Note:: Developers and maintainers will attempt to assist with challenging issues. + +.. _Basic etiquette for open source projects: https://developer.mozilla.org/en-US/docs/MDN/Contribute/Open_source_etiquette diff --git a/docs/source/contributing/localization.rst b/docs/source/contributing/localization.rst index 8127eda3d53..8b04867f16d 100644 --- a/docs/source/contributing/localization.rst +++ b/docs/source/contributing/localization.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/contributing/localization.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/contributing/localization.rst Localization ============ @@ -25,7 +25,7 @@ Only elements of the API are planned to be translated. .. Attention:: The rest API has not yet been implemented. Translations Basics - - The brand names `SunshineStream` and `Sunshine` should never be translated. + - The brand names `LizardByte` and `Sunshine` should never be translated. - Other brand names should never be translated. Examples: @@ -67,7 +67,7 @@ any of the following paths are modified. .. code-block:: yaml - - 'sunshine/**' + - 'src/**' When testing locally it may be desirable to manually extract, initialize, update, and compile strings. Python is required for this, along with the python dependencies in the `./scripts/requirements.txt` file. Additionally, diff --git a/docs/source/index.rst b/docs/source/index.rst index 5384c8e2821..62697791810 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,6 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/index.rst - -SunshineStream has this documentation hosted on `Read the Docs `_. +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/index.rst Table of Contents ================= diff --git a/docs/source/troubleshooting/general.rst b/docs/source/troubleshooting/general.rst index 322327ccbab..865214dbc63 100644 --- a/docs/source/troubleshooting/general.rst +++ b/docs/source/troubleshooting/general.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/troubleshooting/general.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/troubleshooting/general.rst General ======= diff --git a/docs/source/troubleshooting/linux.rst b/docs/source/troubleshooting/linux.rst index 71e9eac0e6c..5bac8c08319 100644 --- a/docs/source/troubleshooting/linux.rst +++ b/docs/source/troubleshooting/linux.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/troubleshooting/linux.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/troubleshooting/linux.rst Linux ===== diff --git a/docs/source/troubleshooting/macos.rst b/docs/source/troubleshooting/macos.rst index 18ea93615bf..f6014eb738a 100644 --- a/docs/source/troubleshooting/macos.rst +++ b/docs/source/troubleshooting/macos.rst @@ -1,6 +1,6 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/troubleshooting/macos.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/troubleshooting/macos.rst -MacOS +macOS ===== If you get this error: @@ -12,3 +12,18 @@ If you get this error: .. code-block:: bash launchctl load -w /Library/LaunchAgents/org.freedesktop.dbus-session.plist + +Uninstall: + + - pkg + + .. code-block:: bash + + sudo chmod +x /opt/local/etc/sunshine/assets/uninstall_pkg.sh + sudo /opt/local/etc/sunshine/assets/uninstall_pkg.sh + + - Portfile + + .. code-block:: bash + + sudo port uninstall Sunshine diff --git a/docs/source/troubleshooting/windows.rst b/docs/source/troubleshooting/windows.rst index 461bce0a9e3..bb21589c153 100644 --- a/docs/source/troubleshooting/windows.rst +++ b/docs/source/troubleshooting/windows.rst @@ -1,4 +1,4 @@ -:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/troubleshooting/windows.rst +:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/troubleshooting/windows.rst Windows ======= diff --git a/packaging/linux/aur/PKGBUILD b/packaging/linux/aur/PKGBUILD new file mode 100644 index 00000000000..ede1af256eb --- /dev/null +++ b/packaging/linux/aur/PKGBUILD @@ -0,0 +1,55 @@ +# Edit on github: https://github.com/LizardByte/Sunshine/tree/nightly/packaging/linux/aur/PKGBUILD +# Reference: https://wiki.archlinux.org/title/PKGBUILD + +pkgname=@SUNSHINE_AUR_PKG@ +pkgver=@PROJECT_VERSION@@SUNSHINE_SUB_VERSION@ +pkgrel=1 +pkgdesc="@PROJECT_DESCRIPTION@" +arch=('x86_64' 'i686') +url=@PROJECT_HOMEPAGE_URL@ +license=('GPL3') + +depends=('avahi' 'boost-libs' 'ffmpeg4.4' 'libevdev' 'libpulse' 'libx11' 'libxcb' 'libxfixes' 'libxrandr' 'libxtst' 'openssl' 'opus' 'udev') +makedepends=('boost' 'cmake' 'git' 'make') +optdepends=('cuda' 'libcap' 'libdrm') + +provides=(@SUNSHINE_AUR_PROVIDES@) +conflicts=(@SUNSHINE_AUR_CONFLICTS@) + +source=("$pkgname::git+@GITHUB_CLONE_URL@#commit=@GITHUB_COMMIT@") +sha256sums=('SKIP') + +prepare() { + cd "$pkgname" + git submodule update --recursive --init +} + +build() { + export CFLAGS="${CFLAGS/-Werror=format-security/}" + export CXXFLAGS="${CXXFLAGS/-Werror=format-security/}" + + cmake \ + -S "$pkgname" \ + -B build \ + -Wno-dev \ + -D SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ + -D CMAKE_INSTALL_PREFIX="/usr" \ + -D SUNSHINE_ASSETS_DIR="share/sunshine/assets" \ + -D SUNSHINE_CONFIG_DIR="share/sunshine/config" \ + -D LIBAVCODEC_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBAVCODEC_LIBRARIES=/usr/lib/ffmpeg4.4/libavcodec.so \ + -D LIBAVDEVICE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBAVDEVICE_LIBRARIES=/usr/lib/ffmpeg4.4/libavdevice.so \ + -D LIBAVFORMAT_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBAVFORMAT_LIBRARIES=/usr/lib/ffmpeg4.4/libavformat.so \ + -D LIBAVUTIL_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBAVUTIL_LIBRARIES=/usr/lib/ffmpeg4.4/libavutil.so \ + -D LIBSWSCALE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \ + -D LIBSWSCALE_LIBRARIES=/usr/lib/ffmpeg4.4/libswscale.so + + make -C build +} + +package() { + make -C build install DESTDIR="$pkgdir" +} diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml new file mode 100644 index 00000000000..04d3df5a6a9 --- /dev/null +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -0,0 +1,216 @@ +--- +app-id: dev.lizardbyte.sunshine +runtime: org.freedesktop.Platform +runtime-version: "21.08" +sdk: org.freedesktop.Sdk +command: sunshine +separate-locales: false +finish-args: + - --device=all + - --env=PULSE_PROP_media.category=Manager + - --persist=.config/sunshine + - --share=ipc + - --share=network + - --socket=pulseaudio + - --socket=wayland + - --socket=x11 + - --system-talk-name=org.freedesktop.Avahi + - --talk-name=org.freedesktop.Flatpak + +cleanup: + - /include + - /lib/cmake + - /lib/pkgconfig + - /lib/*.la + - /lib/*.a + - /share + +modules: + - name: cuda + disabled: false + buildsystem: simple + only-arches: + - x86_64 + - aarch64 + cleanup: + - '*' + build-commands: + - chmod u+x ./cuda.run + - ./cuda.run --silent --toolkit --toolkitpath=$FLATPAK_DEST/cuda --no-opengl-libs --no-man-page --no-drm --tmpdir=$FLATPAK_BUILDER_BUILDDIR # yamllint disable-line rule:line-length + - rm -r $FLATPAK_DEST/cuda/nsight-systems-2021.3.2 + - rm ./cuda.run + sources: + - type: file + only-arches: + - x86_64 + url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run + sha256: bbd87ca0e913f837454a796367473513cddef555082e4d86ed9a38659cc81f0a + dest-filename: cuda.run + - type: file + only-arches: + - aarch64 + url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux_sbsa.run # yamllint disable-line rule:line-length + sha256: f2c4a52e06329606c8dfb7c5ea3f4cb4c0b28f9d3fdffeeb734fcc98daf580d8 + dest-filename: cuda.run + + - name: boost + buildsystem: simple + build-commands: + - ./bootstrap.sh --prefix=$FLATPAK_DEST --with-libraries=system,thread,log + - ./b2 install variant=release link=static,shared runtime-link=shared cxxflags="$CXXFLAGS" linkflags="$LDFLAGS" -j $FLATPAK_BUILDER_N_JOBS # yamllint disable-line rule:line-length + sources: + - type: archive + url: https://boostorg.jfrog.io/artifactory/main/release/1.79.0/source/boost_1_79_0.tar.bz2 + sha256: 475d589d51a7f8b3ba2ba4eda022b170e562ca3b760ee922c146b6c65856ef39 + + - name: ffmpeg + config-opts: + - --enable-gpl + - --disable-static + - --enable-shared + - --disable-doc + - --disable-programs + - --disable-decoders + - --enable-libfontconfig + - --enable-libfreetype + - --enable-libopus + - --enable-libvorbis + - --enable-libvpx + - --enable-libx264 + - --enable-libx265 + - --enable-nvenc + - --enable-encoder=h264_v4l2m2m + - --enable-encoder=hevc_v4l2m2m + # - --enable-nonfree + # - --enable-cuda-nvcc + # - --enable-libnpp + # - --extra-cflags=-I${FLATPAK_DEST}/cuda/include + # - --extra-ldflags=-L${FLATPAK_DEST}/cuda/lib64 + # - --nvccflags="-gencode arch=compute_52,code=sm_52 -O2" + cleanup: + - /share/ffmpeg/examples + sources: + - type: archive + url: http://archive.ubuntu.com/ubuntu/pool/universe/f/ffmpeg/ffmpeg_4.4.2.orig.tar.xz + sha256: af419a7f88adbc56c758ab19b4c708afbcae15ef09606b82b855291f6a6faa93 + modules: + - name: vmaf + buildsystem: meson + subdir: libvmaf + cleanup: + - /bin + sources: + - type: archive + url: https://github.com/Netflix/vmaf/archive/refs/tags/v2.3.1.tar.gz + sha256: 8d60b1ddab043ada25ff11ced821da6e0c37fd7730dd81c24f1fc12be7293ef2 + - name: x264 + config-opts: + - --disable-cli + - --enable-shared + sources: + - type: archive + url: https://code.videolan.org/videolan/x264/-/archive/stable/x264-stable.tar.bz2 + sha256: 8fedb184045722d8cc39353099373a5b7350171d0964d01fff8eced21b959b29 + - name: x265 + buildsystem: cmake-ninja + builddir: true + subdir: source + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DENABLE_CLI=OFF + sources: + - type: archive + url: https://bitbucket.org/multicoreware/x265_git/downloads/x265_3.5.tar.gz + sha256: e70a3335cacacbba0b3a20ec6fecd6783932288ebc8163ad74bcc9606477cae8 + - name: ffnvcodec + no-autogen: true + make-install-args: + - PREFIX=${FLATPAK_DEST} + cleanup: + - '*' + sources: + - type: archive + url: https://github.com/FFmpeg/nv-codec-headers/archive/refs/tags/n11.1.5.1.tar.gz + sha256: d095fbd56aa93772471a323be0ebe65504a0f43f06c76a30b6d25da77b06ae9c + + - name: avahi + cleanup: + - /bin + - /lib/avahi + - /share/applications/*.desktop + - /share/avahi + config-opts: + - --with-distro=none + - --disable-gobject + - --disable-introspection + - --disable-qt3 + - --disable-qt4 + - --disable-qt5 + - --disable-gtk + - --disable-core-docs + - --disable-manpages + - --disable-libdaemon + - --disable-python + - --disable-pygobject + - --disable-mono + - --disable-monodoc + - --disable-autoipd + - --disable-doxygen-doc + - --disable-doxygen-dot + - --disable-doxygen-xml + - --disable-doxygen-html + - --disable-manpages + - --disable-xmltoman + sources: + - type: archive + url: https://avahi.org/download/avahi-0.8.tar.gz + sha256: 060309d7a333d38d951bc27598c677af1796934dbd98e1024e7ad8de798fedda + modules: + - name: libevent + cleanup: + - /bin + sources: + - type: archive + url: https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz # yamllint disable-line rule:line-length + sha256: 92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb + + - name: libevdev + buildsystem: meson + cleanup: + - /bin + sources: + - type: archive + url: https://www.freedesktop.org/software/libevdev/libevdev-1.12.1.tar.xz + sha256: 1dbba41bc516d3ca7abc0da5b862efe3ea8a7018fa6e9b97ce9d39401b22426c + modules: + - name: libcheck + buildsystem: cmake + cleanup: + - /bin + sources: + - type: archive + url: https://github.com/libcheck/check/archive/refs/tags/0.15.2.tar.gz + sha256: 998d355294bb94072f40584272cf4424571c396c631620ce463f6ea97aa67d2e + + - name: sunshine + buildsystem: cmake + no-make-install: false + builddir: true + build-options: + cxxflags: -I${C_INCLUDE_PATH}/libevdev-1.0 + config-opts: + - -DCMAKE_BUILD_TYPE=Release + - -DCMAKE_INSTALL_PREFIX=/app + - -DCMAKE_CUDA_COMPILER=/app/cuda/bin/nvcc + - -DSUNSHINE_ASSETS_DIR=assets + - -DSUNSHINE_CONFIG_DIR=config + - -DSUNSHINE_EXECUTABLE_PATH=/app/bin/sunshine + - -DSUNSHINE_ENABLE_WAYLAND=ON + - -DSUNSHINE_ENABLE_X11=ON + - -DSUNSHINE_ENABLE_DRM=ON + - -DSUNSHINE_ENABLE_CUDA=ON + sources: + - type: git + url: '@GITHUB_CLONE_URL@' + branch: '@GITHUB_BRANCH@' + commit: '@GITHUB_COMMIT@' diff --git a/sunshine.desktop.in b/packaging/linux/sunshine.desktop similarity index 100% rename from sunshine.desktop.in rename to packaging/linux/sunshine.desktop diff --git a/packaging/macos/Portfile b/packaging/macos/Portfile new file mode 100644 index 00000000000..8707cedca13 --- /dev/null +++ b/packaging/macos/Portfile @@ -0,0 +1,84 @@ +# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4 + +# initial PR into macports: https://github.com/macports/macports-ports/pull/15143 + +PortSystem 1.0 +PortGroup cmake 1.1 +PortGroup github 1.0 +PortGroup boost 1.0 + +name @PROJECT_NAME@ +version @PROJECT_VERSION@ +revision 0 +categories multimedia emulators games +platforms darwin +license GPL-3 +maintainers @LizardByte +description @PROJECT_DESCRIPTION@ + +# long_description will not be split into multiple lines as it's configured by CMakeLists +long_description @PROJECT_LONG_DESCRIPTION@ +homepage @PROJECT_HOMEPAGE_URL@ +master_sites https://github.com/lizardbyte/sunshine/releases + +compiler.cxx_standard 2017 +fetch.type git + +git.url @GITHUB_CLONE_URL@ +git.branch @GITHUB_COMMIT@ + +post-fetch { + system -W ${worksrcpath} "${git.cmd} submodule update --init --recursive" +} + +depends_lib port:avahi \ + port:ffmpeg \ + port:libopus + +boost.version 1.76 + +configure.args -DCMAKE_INSTALL_PREFIX=${prefix} \ + -DSUNSHINE_ASSETS_DIR=etc/sunshine/assets \ + -DSUNSHINE_CONFIG_DIR=etc/sunshine/config + +startupitem.create yes +startupitem.executable "${prefix}/bin/{$name}" +startupitem.location LaunchDaemons +startupitem.name ${name} +startupitem.netchange yes + +platform darwin { + if { ${os.major} < 20 } { + # See: https://github.com/LizardByte/Sunshine/discussions/117#discussioncomment-2513494 + notes-append "Port is limited to software encoding, when used with macOS releases prior to Big Sur." + } +} + +# destroot not required as cmake install directive handles moving files + +# # Rename files in `destroot` +# post-destroot { +# file rename ${destroot}${prefix}/etc/${name}/config/sunshine.conf ${destroot}${prefix}/etc/${name}/config/sunshine.conf.sample +# file rename ${destroot}${prefix}/etc/${name}/config/apps.json ${destroot}${prefix}/etc/${name}/config/apps.json.sample +# } + +# # Don't overwrite existing preference files +# post-activate { +# if {![file exists ${prefix}/etc/${name}/config/sunshine.conf]} { +# file copy ${destroot}${prefix}/etc/${name}/config/sunshine.conf.sample \ +# ${prefix}/etc/${name}/config/sunshine.conf +# } +# if {![file exists ${prefix}/etc/${name}/config/apps.json]} { +# file copy ${destroot}${prefix}/etc/${name}/config/apps.json.sample \ +# ${prefix}/etc/${name}/config/apps.json +# } +# } + +# disabled not overwriting config files... these are the default config files required by Sunshine +# this did not work with pkg created by macports +# we should always install the default files and user should start sunshine like "sunshine " +# if the file doesn't exist sunshine will copy the default config to that location +notes-append "Run @PROJECT_NAME@ by executing 'sunshine ', e.g. 'sunshine ~/sunshine.conf' " +notes-append "The config file will be created if it doesn't exist." +notes-append "It is recommended to set a location for the apps file in the config." +notes-append "See our documentation at 'https://docs.lizardbyte.dev/projects/sunshine/en/v@PROJECT_VERSION@/' for further info." diff --git a/scripts/_locale.py b/scripts/_locale.py index 339e2d4aea9..d967974e3f5 100644 --- a/scripts/_locale.py +++ b/scripts/_locale.py @@ -13,12 +13,12 @@ import subprocess project_name = 'Sunshine' -project_owner = 'SunshineStream' +project_owner = 'LizardByte' script_dir = os.path.dirname(os.path.abspath(__file__)) root_dir = os.path.dirname(script_dir) locale_dir = os.path.join(root_dir, 'locale') -project_dir = os.path.join(root_dir, project_name.lower()) +project_dir = os.path.join(root_dir, 'src') year = datetime.datetime.now().year diff --git a/scripts/build-private.sh b/scripts/build-private.sh index 93d18748899..80c7d6bcb49 100755 --- a/scripts/build-private.sh +++ b/scripts/build-private.sh @@ -8,7 +8,7 @@ SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR:-/etc/sunshine}" SUNSHINE_ROOT="${SUNSHINE_ROOT:-/root/sunshine}" SUNSHINE_TAG="${SUNSHINE_TAG:-master}" -SUNSHINE_GIT_URL="${SUNSHINE_GIT_URL:-https://github.com/sunshinestream/sunshine.git}" +SUNSHINE_GIT_URL="${SUNSHINE_GIT_URL:-https://github.com/lizardbyte/sunshine.git}" SUNSHINE_ENABLE_WAYLAND=${SUNSHINE_ENABLE_WAYLAND:-ON} diff --git a/scripts/build-sunshine.sh b/scripts/build-sunshine.sh index 5be8280823d..4284375290b 100755 --- a/scripts/build-sunshine.sh +++ b/scripts/build-sunshine.sh @@ -28,7 +28,7 @@ absolute_path() { CMAKE_BUILD_TYPE="-e CMAKE_BUILD_TYPE=Release" SUNSHINE_PACKAGE_BUILD=OFF SUNSHINE_PACKAGE_EXTENSION=deb -SUNSHINE_GIT_URL=https://github.com/sunshinestream/sunshine.git +SUNSHINE_GIT_URL=https://github.com/lizardbyte/sunshine.git CONTAINER_NAME=sunshine # Docker will fail if ctrl+c is passed through and the input is not a tty diff --git a/scripts/requirements.txt b/scripts/requirements.txt index 73eefbe1ff1..a572e2493ab 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -1,5 +1,5 @@ -Babel==2.9.1 +Babel==2.10.3 m2r2==0.3.2 -Sphinx==4.5.0 +Sphinx==5.1.1 sphinx-copybutton==0.5.0 sphinx-rtd-theme==1.0.0 diff --git a/sunshine/audio.cpp b/src/audio.cpp similarity index 100% rename from sunshine/audio.cpp rename to src/audio.cpp diff --git a/sunshine/audio.h b/src/audio.h similarity index 100% rename from sunshine/audio.h rename to src/audio.h diff --git a/sunshine/cbs.cpp b/src/cbs.cpp similarity index 100% rename from sunshine/cbs.cpp rename to src/cbs.cpp diff --git a/sunshine/cbs.h b/src/cbs.h similarity index 100% rename from sunshine/cbs.h rename to src/cbs.h diff --git a/sunshine/config.cpp b/src/config.cpp similarity index 97% rename from sunshine/config.cpp rename to src/config.cpp index 21c27e2d94b..fd887605019 100644 --- a/sunshine/config.cpp +++ b/src/config.cpp @@ -20,7 +20,7 @@ using namespace std::literals; #define PRIVATE_KEY_FILE CA_DIR "/cakey.pem" #define CERTIFICATE_FILE CA_DIR "/cacert.pem" -#define APPS_JSON_PATH SUNSHINE_CONFIG_DIR "/apps.json" +#define APPS_JSON_PATH platf::appdata().string() + "/apps.json" namespace config { namespace nv { @@ -286,14 +286,14 @@ input_t input { }; sunshine_t sunshine { - 2, // min_log_level - 0, // flags - {}, // User file - {}, // Username - {}, // Password - {}, // Password Salt - SUNSHINE_CONFIG_DIR "/sunshine.conf", // config file - {}, // cmd args + 2, // min_log_level + 0, // flags + {}, // User file + {}, // Username + {}, // Password + {}, // Password Salt + platf::appdata().string() + "/sunshine.conf", // config file + {}, // cmd args 47989, }; diff --git a/sunshine/config.h b/src/config.h similarity index 100% rename from sunshine/config.h rename to src/config.h diff --git a/sunshine/confighttp.cpp b/src/confighttp.cpp similarity index 100% rename from sunshine/confighttp.cpp rename to src/confighttp.cpp diff --git a/sunshine/confighttp.h b/src/confighttp.h similarity index 100% rename from sunshine/confighttp.h rename to src/confighttp.h diff --git a/sunshine/crypto.cpp b/src/crypto.cpp similarity index 100% rename from sunshine/crypto.cpp rename to src/crypto.cpp diff --git a/sunshine/crypto.h b/src/crypto.h similarity index 100% rename from sunshine/crypto.h rename to src/crypto.h diff --git a/sunshine/httpcommon.cpp b/src/httpcommon.cpp similarity index 100% rename from sunshine/httpcommon.cpp rename to src/httpcommon.cpp diff --git a/sunshine/httpcommon.h b/src/httpcommon.h similarity index 100% rename from sunshine/httpcommon.h rename to src/httpcommon.h diff --git a/sunshine/input.cpp b/src/input.cpp similarity index 100% rename from sunshine/input.cpp rename to src/input.cpp diff --git a/sunshine/input.h b/src/input.h similarity index 100% rename from sunshine/input.h rename to src/input.h diff --git a/sunshine/main.cpp b/src/main.cpp similarity index 100% rename from sunshine/main.cpp rename to src/main.cpp diff --git a/sunshine/main.h b/src/main.h similarity index 100% rename from sunshine/main.h rename to src/main.h diff --git a/sunshine/move_by_copy.h b/src/move_by_copy.h similarity index 100% rename from sunshine/move_by_copy.h rename to src/move_by_copy.h diff --git a/sunshine/network.cpp b/src/network.cpp similarity index 100% rename from sunshine/network.cpp rename to src/network.cpp diff --git a/sunshine/network.h b/src/network.h similarity index 100% rename from sunshine/network.h rename to src/network.h diff --git a/sunshine/nvhttp.cpp b/src/nvhttp.cpp similarity index 99% rename from sunshine/nvhttp.cpp rename to src/nvhttp.cpp index 5da896e2416..67f8878c20b 100644 --- a/sunshine/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -832,7 +832,7 @@ void start() { X509_NAME_oneline(X509_get_subject_name(x509), subject_name, sizeof(subject_name)); - BOOST_LOG(info) << subject_name << " -- "sv << (verified ? "verfied"sv : "denied"sv); + BOOST_LOG(info) << subject_name << " -- "sv << (verified ? "verified"sv : "denied"sv); }); while(add_cert->peek()) { diff --git a/sunshine/nvhttp.h b/src/nvhttp.h similarity index 100% rename from sunshine/nvhttp.h rename to src/nvhttp.h diff --git a/sunshine/platform/common.h b/src/platform/common.h similarity index 98% rename from sunshine/platform/common.h rename to src/platform/common.h index 4d1588a3e57..60ae9d141e1 100644 --- a/sunshine/platform/common.h +++ b/src/platform/common.h @@ -11,8 +11,8 @@ #include #include -#include "sunshine/thread_safe.h" -#include "sunshine/utility.h" +#include "src/thread_safe.h" +#include "src/utility.h" struct sockaddr; struct AVFrame; @@ -161,7 +161,7 @@ struct sink_t { // Play on host PC std::string host; - // On MacOS and Windows, it is not possible to create a virtual sink + // On macOS and Windows, it is not possible to create a virtual sink // Therefore, it is optional struct null_t { std::string stereo; diff --git a/sunshine/platform/linux/audio.cpp b/src/platform/linux/audio.cpp similarity index 99% rename from sunshine/platform/linux/audio.cpp rename to src/platform/linux/audio.cpp index 2770e515732..d3ac03f97b5 100644 --- a/sunshine/platform/linux/audio.cpp +++ b/src/platform/linux/audio.cpp @@ -10,11 +10,11 @@ #include #include -#include "sunshine/platform/common.h" +#include "src/platform/common.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/thread_safe.h" +#include "src/config.h" +#include "src/main.h" +#include "src/thread_safe.h" namespace platf { using namespace std::literals; diff --git a/sunshine/platform/linux/cuda.cpp b/src/platform/linux/cuda.cpp similarity index 99% rename from sunshine/platform/linux/cuda.cpp rename to src/platform/linux/cuda.cpp index c907ae6c236..faef891d828 100644 --- a/sunshine/platform/linux/cuda.cpp +++ b/src/platform/linux/cuda.cpp @@ -11,8 +11,8 @@ extern "C" { #include "cuda.h" #include "graphics.h" -#include "sunshine/main.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/utility.h" #include "wayland.h" #define SUNSHINE_STRINGVIEW_HELPER(x) x##sv diff --git a/sunshine/platform/linux/cuda.cu b/src/platform/linux/cuda.cu similarity index 100% rename from sunshine/platform/linux/cuda.cu rename to src/platform/linux/cuda.cu diff --git a/sunshine/platform/linux/cuda.h b/src/platform/linux/cuda.h similarity index 100% rename from sunshine/platform/linux/cuda.h rename to src/platform/linux/cuda.h diff --git a/sunshine/platform/linux/graphics.cpp b/src/platform/linux/graphics.cpp similarity index 99% rename from sunshine/platform/linux/graphics.cpp rename to src/platform/linux/graphics.cpp index 8d31e37b7f7..3c0adbda389 100644 --- a/sunshine/platform/linux/graphics.cpp +++ b/src/platform/linux/graphics.cpp @@ -1,5 +1,5 @@ #include "graphics.h" -#include "sunshine/video.h" +#include "src/video.h" #include diff --git a/sunshine/platform/linux/graphics.h b/src/platform/linux/graphics.h similarity index 98% rename from sunshine/platform/linux/graphics.h rename to src/platform/linux/graphics.h index 2cc24b01210..c566b48fc4f 100644 --- a/sunshine/platform/linux/graphics.h +++ b/src/platform/linux/graphics.h @@ -8,9 +8,9 @@ #include #include "misc.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/utility.h" #define SUNSHINE_STRINGIFY_HELPER(x) #x #define SUNSHINE_STRINGIFY(x) SUNSHINE_STRINGIFY_HELPER(x) diff --git a/sunshine/platform/linux/input.cpp b/src/platform/linux/input.cpp similarity index 99% rename from sunshine/platform/linux/input.cpp rename to src/platform/linux/input.cpp index be923936135..c8e5887e95e 100644 --- a/sunshine/platform/linux/input.cpp +++ b/src/platform/linux/input.cpp @@ -9,11 +9,11 @@ #include #include -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/utility.h" -#include "sunshine/platform/common.h" +#include "src/platform/common.h" // Support older versions #ifndef REL_HWHEEL_HI_RES diff --git a/sunshine/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp similarity index 99% rename from sunshine/platform/linux/kmsgrab.cpp rename to src/platform/linux/kmsgrab.cpp index 28ef656e413..3b9257be37f 100644 --- a/sunshine/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -8,10 +8,10 @@ #include -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/round_robin.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/round_robin.h" +#include "src/utility.h" // Cursor rendering support through x11 #include "graphics.h" diff --git a/sunshine/platform/linux/misc.cpp b/src/platform/linux/misc.cpp similarity index 99% rename from sunshine/platform/linux/misc.cpp rename to src/platform/linux/misc.cpp index 6d7643c0121..36896dd9d9b 100644 --- a/sunshine/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -11,8 +11,8 @@ #include "misc.h" #include "vaapi.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" +#include "src/main.h" +#include "src/platform/common.h" #ifdef __GNUC__ #define SUNSHINE_GNUC_EXTENSION __extension__ diff --git a/sunshine/platform/linux/misc.h b/src/platform/linux/misc.h similarity index 94% rename from sunshine/platform/linux/misc.h rename to src/platform/linux/misc.h index 432aa9ed42e..515087d73f4 100644 --- a/sunshine/platform/linux/misc.h +++ b/src/platform/linux/misc.h @@ -4,7 +4,7 @@ #include #include -#include "sunshine/utility.h" +#include "src/utility.h" KITTY_USING_MOVE_T(file_t, int, -1, { if(el >= 0) { diff --git a/sunshine/platform/linux/publish.cpp b/src/platform/linux/publish.cpp similarity index 99% rename from sunshine/platform/linux/publish.cpp rename to src/platform/linux/publish.cpp index 825f37407e2..19cbb3f5433 100644 --- a/sunshine/platform/linux/publish.cpp +++ b/src/platform/linux/publish.cpp @@ -3,10 +3,10 @@ #include #include "misc.h" -#include "sunshine/main.h" -#include "sunshine/nvhttp.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/nvhttp.h" +#include "src/platform/common.h" +#include "src/utility.h" using namespace std::literals; diff --git a/sunshine/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp similarity index 99% rename from sunshine/platform/linux/vaapi.cpp rename to src/platform/linux/vaapi.cpp index 37a64cb850a..9dcb2dfead8 100644 --- a/sunshine/platform/linux/vaapi.cpp +++ b/src/platform/linux/vaapi.cpp @@ -9,10 +9,10 @@ extern "C" { #include "graphics.h" #include "misc.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/config.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/utility.h" using namespace std::literals; diff --git a/sunshine/platform/linux/vaapi.h b/src/platform/linux/vaapi.h similarity index 95% rename from sunshine/platform/linux/vaapi.h rename to src/platform/linux/vaapi.h index ddb5c61ee17..27c97a7ffc6 100644 --- a/sunshine/platform/linux/vaapi.h +++ b/src/platform/linux/vaapi.h @@ -2,7 +2,7 @@ #define SUNSHINE_VAAPI_H #include "misc.h" -#include "sunshine/platform/common.h" +#include "src/platform/common.h" namespace egl { struct surface_descriptor_t; diff --git a/sunshine/platform/linux/wayland.cpp b/src/platform/linux/wayland.cpp similarity index 98% rename from sunshine/platform/linux/wayland.cpp rename to src/platform/linux/wayland.cpp index 9b65ef8d307..dace461ccfe 100644 --- a/sunshine/platform/linux/wayland.cpp +++ b/src/platform/linux/wayland.cpp @@ -4,10 +4,10 @@ #include #include "graphics.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/round_robin.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/round_robin.h" +#include "src/utility.h" #include "wayland.h" extern const wl_interface wl_output_interface; diff --git a/sunshine/platform/linux/wayland.h b/src/platform/linux/wayland.h similarity index 100% rename from sunshine/platform/linux/wayland.h rename to src/platform/linux/wayland.h diff --git a/sunshine/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp similarity index 99% rename from sunshine/platform/linux/wlgrab.cpp rename to src/platform/linux/wlgrab.cpp index c4ab3dbfe9e..31d5cfa062b 100644 --- a/sunshine/platform/linux/wlgrab.cpp +++ b/src/platform/linux/wlgrab.cpp @@ -1,6 +1,6 @@ -#include "sunshine/platform/common.h" +#include "src/platform/common.h" -#include "sunshine/main.h" +#include "src/main.h" #include "vaapi.h" #include "wayland.h" diff --git a/sunshine/platform/linux/x11grab.cpp b/src/platform/linux/x11grab.cpp similarity index 99% rename from sunshine/platform/linux/x11grab.cpp rename to src/platform/linux/x11grab.cpp index c9b4beaad57..c024f4d8f40 100644 --- a/sunshine/platform/linux/x11grab.cpp +++ b/src/platform/linux/x11grab.cpp @@ -2,7 +2,7 @@ // Created by loki on 6/21/19. // -#include "sunshine/platform/common.h" +#include "src/platform/common.h" #include @@ -16,9 +16,9 @@ #include #include -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/task_pool.h" +#include "src/config.h" +#include "src/main.h" +#include "src/task_pool.h" #include "cuda.h" #include "graphics.h" diff --git a/sunshine/platform/linux/x11grab.h b/src/platform/linux/x11grab.h similarity index 94% rename from sunshine/platform/linux/x11grab.h rename to src/platform/linux/x11grab.h index 9fde2664740..801daad5535 100644 --- a/sunshine/platform/linux/x11grab.h +++ b/src/platform/linux/x11grab.h @@ -3,8 +3,8 @@ #include -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/platform/common.h" +#include "src/utility.h" // X11 Display extern "C" struct _XDisplay; diff --git a/sunshine/platform/macos/av_audio.h b/src/platform/macos/av_audio.h similarity index 91% rename from sunshine/platform/macos/av_audio.h rename to src/platform/macos/av_audio.h index 8c04e5bb7b3..2a0b5a434cc 100644 --- a/sunshine/platform/macos/av_audio.h +++ b/src/platform/macos/av_audio.h @@ -3,7 +3,7 @@ #import -#include "sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.h" +#include "third-party/TPCircularBuffer/TPCircularBuffer.h" #define kBufferLength 2048 diff --git a/sunshine/platform/macos/av_audio.m b/src/platform/macos/av_audio.m similarity index 100% rename from sunshine/platform/macos/av_audio.m rename to src/platform/macos/av_audio.m diff --git a/sunshine/platform/macos/av_img_t.h b/src/platform/macos/av_img_t.h similarity index 89% rename from sunshine/platform/macos/av_img_t.h rename to src/platform/macos/av_img_t.h index 7af3cbcc839..a7a24935ed1 100644 --- a/sunshine/platform/macos/av_img_t.h +++ b/src/platform/macos/av_img_t.h @@ -1,7 +1,7 @@ #ifndef av_img_t_h #define av_img_t_h -#include "sunshine/platform/common.h" +#include "src/platform/common.h" #include #include diff --git a/sunshine/platform/macos/av_video.h b/src/platform/macos/av_video.h similarity index 100% rename from sunshine/platform/macos/av_video.h rename to src/platform/macos/av_video.h diff --git a/sunshine/platform/macos/av_video.m b/src/platform/macos/av_video.m similarity index 100% rename from sunshine/platform/macos/av_video.m rename to src/platform/macos/av_video.m diff --git a/sunshine/platform/macos/display.mm b/src/platform/macos/display.mm similarity index 96% rename from sunshine/platform/macos/display.mm rename to src/platform/macos/display.mm index f00297c2979..1601e3ecd83 100644 --- a/sunshine/platform/macos/display.mm +++ b/src/platform/macos/display.mm @@ -1,10 +1,10 @@ -#include "sunshine/platform/common.h" -#include "sunshine/platform/macos/av_img_t.h" -#include "sunshine/platform/macos/av_video.h" -#include "sunshine/platform/macos/nv12_zero_device.h" +#include "src/platform/common.h" +#include "src/platform/macos/av_img_t.h" +#include "src/platform/macos/av_video.h" +#include "src/platform/macos/nv12_zero_device.h" -#include "sunshine/config.h" -#include "sunshine/main.h" +#include "src/config.h" +#include "src/main.h" namespace fs = std::filesystem; diff --git a/sunshine/platform/macos/input.cpp b/src/platform/macos/input.cpp similarity index 99% rename from sunshine/platform/macos/input.cpp rename to src/platform/macos/input.cpp index fbea6619730..3d9c21a35ec 100644 --- a/sunshine/platform/macos/input.cpp +++ b/src/platform/macos/input.cpp @@ -2,9 +2,9 @@ #include #include -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/utility.h" // Delay for a double click // FIXME: we probably want to make this configurable diff --git a/sunshine/platform/macos/microphone.mm b/src/platform/macos/microphone.mm similarity index 94% rename from sunshine/platform/macos/microphone.mm rename to src/platform/macos/microphone.mm index 4b6516909c9..cfe9562da17 100644 --- a/sunshine/platform/macos/microphone.mm +++ b/src/platform/macos/microphone.mm @@ -1,8 +1,8 @@ -#include "sunshine/platform/common.h" -#include "sunshine/platform/macos/av_audio.h" +#include "src/platform/common.h" +#include "src/platform/macos/av_audio.h" -#include "sunshine/config.h" -#include "sunshine/main.h" +#include "src/config.h" +#include "src/main.h" namespace platf { using namespace std::literals; diff --git a/sunshine/platform/macos/misc.cpp b/src/platform/macos/misc.cpp similarity index 98% rename from sunshine/platform/macos/misc.cpp rename to src/platform/macos/misc.cpp index fdc4668817e..a7b0a89f53f 100644 --- a/sunshine/platform/macos/misc.cpp +++ b/src/platform/macos/misc.cpp @@ -6,8 +6,8 @@ #include #include "misc.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" +#include "src/main.h" +#include "src/platform/common.h" using namespace std::literals; namespace fs = std::filesystem; diff --git a/sunshine/platform/macos/misc.h b/src/platform/macos/misc.h similarity index 100% rename from sunshine/platform/macos/misc.h rename to src/platform/macos/misc.h diff --git a/sunshine/platform/macos/nv12_zero_device.cpp b/src/platform/macos/nv12_zero_device.cpp similarity index 95% rename from sunshine/platform/macos/nv12_zero_device.cpp rename to src/platform/macos/nv12_zero_device.cpp index 7e0a4a77606..1af0e058058 100644 --- a/sunshine/platform/macos/nv12_zero_device.cpp +++ b/src/platform/macos/nv12_zero_device.cpp @@ -1,7 +1,7 @@ -#include "sunshine/platform/macos/nv12_zero_device.h" -#include "sunshine/platform/macos/av_img_t.h" +#include "src/platform/macos/nv12_zero_device.h" +#include "src/platform/macos/av_img_t.h" -#include "sunshine/video.h" +#include "src/video.h" extern "C" { #include "libavutil/imgutils.h" diff --git a/sunshine/platform/macos/nv12_zero_device.h b/src/platform/macos/nv12_zero_device.h similarity index 96% rename from sunshine/platform/macos/nv12_zero_device.h rename to src/platform/macos/nv12_zero_device.h index 847a8f0ab19..3b74ebcc6ba 100644 --- a/sunshine/platform/macos/nv12_zero_device.h +++ b/src/platform/macos/nv12_zero_device.h @@ -1,7 +1,7 @@ #ifndef vtdevice_h #define vtdevice_h -#include "sunshine/platform/common.h" +#include "src/platform/common.h" namespace platf { diff --git a/sunshine/platform/macos/publish.cpp b/src/platform/macos/publish.cpp similarity index 99% rename from sunshine/platform/macos/publish.cpp rename to src/platform/macos/publish.cpp index cc10cd826ea..bd943ec9966 100644 --- a/sunshine/platform/macos/publish.cpp +++ b/src/platform/macos/publish.cpp @@ -3,10 +3,10 @@ #include #include "misc.h" -#include "sunshine/main.h" -#include "sunshine/nvhttp.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/nvhttp.h" +#include "src/platform/common.h" +#include "src/utility.h" using namespace std::literals; diff --git a/sunshine/platform/windows/PolicyConfig.h b/src/platform/windows/PolicyConfig.h similarity index 96% rename from sunshine/platform/windows/PolicyConfig.h rename to src/platform/windows/PolicyConfig.h index 60f36a68b2e..be18ec6f8a3 100644 --- a/sunshine/platform/windows/PolicyConfig.h +++ b/src/platform/windows/PolicyConfig.h @@ -1,164 +1,164 @@ -// ---------------------------------------------------------------------------- -// PolicyConfig.h -// Undocumented COM-interface IPolicyConfig. -// Use for set default audio render endpoint -// @author EreTIk -// http://eretik.omegahg.com/ -// ---------------------------------------------------------------------------- - - -#pragma once - -#ifdef __MINGW32__ -#undef DEFINE_GUID -#ifdef __cplusplus -#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } -#else -#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } -#endif - -DEFINE_GUID(IID_IPolicyConfig, 0xf8679f50, 0x850a, 0x41cf, 0x9c, 0x72, 0x43, 0x0f, 0x29, 0x02, 0x90, 0xc8); -DEFINE_GUID(CLSID_CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf, 0x0d, 0xe6, 0x3d, 0xf4, 0x0c, 0x2b, 0xc9); - -#endif - -interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig; -class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient; -// ---------------------------------------------------------------------------- -// class CPolicyConfigClient -// {870af99c-171d-4f9e-af0d-e63df40c2bc9} -// -// interface IPolicyConfig -// {f8679f50-850a-41cf-9c72-430f290290c8} -// -// Query interface: -// CComPtr PolicyConfig; -// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigClient)); -// -// @compatible: Windows 7 and Later -// ---------------------------------------------------------------------------- -interface IPolicyConfig : public IUnknown { -public: - virtual HRESULT GetMixFormat( - PCWSTR, - WAVEFORMATEX **); - - virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( - PCWSTR, - INT, - WAVEFORMATEX **); - - virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat( - PCWSTR); - - virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( - PCWSTR, - WAVEFORMATEX *, - WAVEFORMATEX *); - - virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod( - PCWSTR, - INT, - PINT64, - PINT64); - - virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod( - PCWSTR, - PINT64); - - virtual HRESULT STDMETHODCALLTYPE GetShareMode( - PCWSTR, - struct DeviceShareMode *); - - virtual HRESULT STDMETHODCALLTYPE SetShareMode( - PCWSTR, - struct DeviceShareMode *); - - virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( - PCWSTR, - const PROPERTYKEY &, - PROPVARIANT *); - - virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( - PCWSTR, - const PROPERTYKEY &, - PROPVARIANT *); - - virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( - PCWSTR wszDeviceId, - ERole eRole); - - virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( - PCWSTR, - INT); -}; - -interface DECLSPEC_UUID("568b9108-44bf-40b4-9006-86afe5b5a620") IPolicyConfigVista; -class DECLSPEC_UUID("294935CE-F637-4E7C-A41B-AB255460B862") CPolicyConfigVistaClient; -// ---------------------------------------------------------------------------- -// class CPolicyConfigVistaClient -// {294935CE-F637-4E7C-A41B-AB255460B862} -// -// interface IPolicyConfigVista -// {568b9108-44bf-40b4-9006-86afe5b5a620} -// -// Query interface: -// CComPtr PolicyConfig; -// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigVistaClient)); -// -// @compatible: Windows Vista and Later -// ---------------------------------------------------------------------------- -interface IPolicyConfigVista : public IUnknown { -public: - virtual HRESULT GetMixFormat( - PCWSTR, - WAVEFORMATEX **); // not available on Windows 7, use method from IPolicyConfig - - virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( - PCWSTR, - INT, - WAVEFORMATEX **); - - virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( - PCWSTR, - WAVEFORMATEX *, - WAVEFORMATEX *); - - virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod( - PCWSTR, - INT, - PINT64, - PINT64); // not available on Windows 7, use method from IPolicyConfig - - virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod( - PCWSTR, - PINT64); // not available on Windows 7, use method from IPolicyConfig - - virtual HRESULT STDMETHODCALLTYPE GetShareMode( - PCWSTR, - struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig - - virtual HRESULT STDMETHODCALLTYPE SetShareMode( - PCWSTR, - struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig - - virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( - PCWSTR, - const PROPERTYKEY &, - PROPVARIANT *); - - virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( - PCWSTR, - const PROPERTYKEY &, - PROPVARIANT *); - - virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( - PCWSTR wszDeviceId, - ERole eRole); - - virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( - PCWSTR, - INT); // not available on Windows 7, use method from IPolicyConfig -}; - -// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- +// PolicyConfig.h +// Undocumented COM-interface IPolicyConfig. +// Use for set default audio render endpoint +// @author EreTIk +// http://eretik.omegahg.com/ +// ---------------------------------------------------------------------------- + + +#pragma once + +#ifdef __MINGW32__ +#undef DEFINE_GUID +#ifdef __cplusplus +#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } +#else +#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } +#endif + +DEFINE_GUID(IID_IPolicyConfig, 0xf8679f50, 0x850a, 0x41cf, 0x9c, 0x72, 0x43, 0x0f, 0x29, 0x02, 0x90, 0xc8); +DEFINE_GUID(CLSID_CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf, 0x0d, 0xe6, 0x3d, 0xf4, 0x0c, 0x2b, 0xc9); + +#endif + +interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig; +class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient; +// ---------------------------------------------------------------------------- +// class CPolicyConfigClient +// {870af99c-171d-4f9e-af0d-e63df40c2bc9} +// +// interface IPolicyConfig +// {f8679f50-850a-41cf-9c72-430f290290c8} +// +// Query interface: +// CComPtr PolicyConfig; +// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigClient)); +// +// @compatible: Windows 7 and Later +// ---------------------------------------------------------------------------- +interface IPolicyConfig : public IUnknown { +public: + virtual HRESULT GetMixFormat( + PCWSTR, + WAVEFORMATEX **); + + virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( + PCWSTR, + INT, + WAVEFORMATEX **); + + virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat( + PCWSTR); + + virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( + PCWSTR, + WAVEFORMATEX *, + WAVEFORMATEX *); + + virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod( + PCWSTR, + INT, + PINT64, + PINT64); + + virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod( + PCWSTR, + PINT64); + + virtual HRESULT STDMETHODCALLTYPE GetShareMode( + PCWSTR, + struct DeviceShareMode *); + + virtual HRESULT STDMETHODCALLTYPE SetShareMode( + PCWSTR, + struct DeviceShareMode *); + + virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( + PCWSTR, + const PROPERTYKEY &, + PROPVARIANT *); + + virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( + PCWSTR, + const PROPERTYKEY &, + PROPVARIANT *); + + virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( + PCWSTR wszDeviceId, + ERole eRole); + + virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( + PCWSTR, + INT); +}; + +interface DECLSPEC_UUID("568b9108-44bf-40b4-9006-86afe5b5a620") IPolicyConfigVista; +class DECLSPEC_UUID("294935CE-F637-4E7C-A41B-AB255460B862") CPolicyConfigVistaClient; +// ---------------------------------------------------------------------------- +// class CPolicyConfigVistaClient +// {294935CE-F637-4E7C-A41B-AB255460B862} +// +// interface IPolicyConfigVista +// {568b9108-44bf-40b4-9006-86afe5b5a620} +// +// Query interface: +// CComPtr PolicyConfig; +// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigVistaClient)); +// +// @compatible: Windows Vista and Later +// ---------------------------------------------------------------------------- +interface IPolicyConfigVista : public IUnknown { +public: + virtual HRESULT GetMixFormat( + PCWSTR, + WAVEFORMATEX **); // not available on Windows 7, use method from IPolicyConfig + + virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( + PCWSTR, + INT, + WAVEFORMATEX **); + + virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( + PCWSTR, + WAVEFORMATEX *, + WAVEFORMATEX *); + + virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod( + PCWSTR, + INT, + PINT64, + PINT64); // not available on Windows 7, use method from IPolicyConfig + + virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod( + PCWSTR, + PINT64); // not available on Windows 7, use method from IPolicyConfig + + virtual HRESULT STDMETHODCALLTYPE GetShareMode( + PCWSTR, + struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig + + virtual HRESULT STDMETHODCALLTYPE SetShareMode( + PCWSTR, + struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig + + virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( + PCWSTR, + const PROPERTYKEY &, + PROPVARIANT *); + + virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( + PCWSTR, + const PROPERTYKEY &, + PROPVARIANT *); + + virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( + PCWSTR wszDeviceId, + ERole eRole); + + virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( + PCWSTR, + INT); // not available on Windows 7, use method from IPolicyConfig +}; + +// ---------------------------------------------------------------------------- diff --git a/sunshine/platform/windows/audio.cpp b/src/platform/windows/audio.cpp similarity index 96% rename from sunshine/platform/windows/audio.cpp rename to src/platform/windows/audio.cpp index 61df413afa0..b630f73e85d 100644 --- a/sunshine/platform/windows/audio.cpp +++ b/src/platform/windows/audio.cpp @@ -1,988 +1,988 @@ -// -// Created by loki on 1/12/20. -// - -#include -#include -#include - -#include - -#include - -#define INITGUID -#include -#undef INITGUID - -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" - -// Must be the last included file -// clang-format off -#include "PolicyConfig.h" -// clang-format on - -DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING -DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING -DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2); - -const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); -const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); -const IID IID_IAudioClient = __uuidof(IAudioClient); -const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); - -using namespace std::literals; -namespace platf::audio { -constexpr auto SAMPLE_RATE = 48000; - -template -void Release(T *p) { - p->Release(); -} - -template -void co_task_free(T *p) { - CoTaskMemFree((LPVOID)p); -} - -using device_enum_t = util::safe_ptr>; -using device_t = util::safe_ptr>; -using collection_t = util::safe_ptr>; -using audio_client_t = util::safe_ptr>; -using audio_capture_t = util::safe_ptr>; -using wave_format_t = util::safe_ptr>; -using wstring_t = util::safe_ptr>; -using handle_t = util::safe_ptr_v2; -using policy_t = util::safe_ptr>; -using prop_t = util::safe_ptr>; - -class co_init_t : public deinit_t { -public: - co_init_t() { - CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY); - } - - ~co_init_t() override { - CoUninitialize(); - } -}; - -class prop_var_t { -public: - prop_var_t() { - PropVariantInit(&prop); - } - - ~prop_var_t() { - PropVariantClear(&prop); - } - - PROPVARIANT prop; -}; - -class audio_pipe_t { -public: - static constexpr auto stereo = 2; - static constexpr auto channels51 = 6; - static constexpr auto channels71 = 8; - - using samples_t = std::vector; - using buf_t = util::buffer_t; - - virtual void to_stereo(samples_t &out, const buf_t &in) = 0; - virtual void to_51(samples_t &out, const buf_t &in) = 0; - virtual void to_71(samples_t &out, const buf_t &in) = 0; -}; - -class mono_t : public audio_pipe_t { -public: - void to_stereo(samples_t &out, const buf_t &in) override { - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end;) { - *sample_out_p++ = *sample_in_pos * 7 / 10; - *sample_out_p++ = *sample_in_pos++ * 7 / 10; - } - } - - void to_51(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { - int left = *sample_in_pos++; - - auto fl = (left * 7 / 10); - - sample_out_p[FRONT_LEFT] = fl; - sample_out_p[FRONT_RIGHT] = fl; - sample_out_p[FRONT_CENTER] = fl * 6; - sample_out_p[LOW_FREQUENCY] = fl / 10; - sample_out_p[BACK_LEFT] = left * 4 / 10; - sample_out_p[BACK_RIGHT] = left * 4 / 10; - } - } - - void to_71(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { - int left = *sample_in_pos++; - - auto fl = (left * 7 / 10); - - sample_out_p[FRONT_LEFT] = fl; - sample_out_p[FRONT_RIGHT] = fl; - sample_out_p[FRONT_CENTER] = fl * 6; - sample_out_p[LOW_FREQUENCY] = fl / 10; - sample_out_p[BACK_LEFT] = left * 4 / 10; - sample_out_p[BACK_RIGHT] = left * 4 / 10; - sample_out_p[SIDE_LEFT] = left * 5 / 10; - sample_out_p[SIDE_RIGHT] = left * 5 / 10; - } - } -}; - -class stereo_t : public audio_pipe_t { -public: - void to_stereo(samples_t &out, const buf_t &in) override { - std::copy_n(std::begin(in), out.size(), std::begin(out)); - } - - void to_51(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { - int left = sample_in_pos[speaker::FRONT_LEFT]; - int right = sample_in_pos[speaker::FRONT_RIGHT]; - - sample_in_pos += 2; - - auto fl = (left * 7 / 10); - auto fr = (right * 7 / 10); - - auto mix = (fl + fr) / 2; - - sample_out_p[FRONT_LEFT] = fl; - sample_out_p[FRONT_RIGHT] = fr; - sample_out_p[FRONT_CENTER] = mix; - sample_out_p[LOW_FREQUENCY] = mix / 2; - sample_out_p[BACK_LEFT] = left * 4 / 10; - sample_out_p[BACK_RIGHT] = right * 4 / 10; - } - } - - void to_71(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { - int left = sample_in_pos[speaker::FRONT_LEFT]; - int right = sample_in_pos[speaker::FRONT_RIGHT]; - - sample_in_pos += 2; - - auto fl = (left * 7 / 10); - auto fr = (right * 7 / 10); - - auto mix = (fl + fr) / 2; - - sample_out_p[FRONT_LEFT] = fl; - sample_out_p[FRONT_RIGHT] = fr; - sample_out_p[FRONT_CENTER] = mix; - sample_out_p[LOW_FREQUENCY] = mix / 2; - sample_out_p[BACK_LEFT] = left * 4 / 10; - sample_out_p[BACK_RIGHT] = right * 4 / 10; - sample_out_p[SIDE_LEFT] = left * 5 / 10; - sample_out_p[SIDE_RIGHT] = right * 5 / 10; - } - } -}; - -class surr51_t : public audio_pipe_t { -public: - void to_stereo(samples_t &out, const buf_t &in) { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += stereo) { - int left {}, right {}; - - left += sample_in_pos[FRONT_LEFT]; - left += sample_in_pos[FRONT_CENTER] * 9 / 10; - left += sample_in_pos[LOW_FREQUENCY] * 3 / 10; - left += sample_in_pos[BACK_LEFT] * 7 / 10; - left += sample_in_pos[BACK_RIGHT] * 3 / 10; - - right += sample_in_pos[FRONT_RIGHT]; - right += sample_in_pos[FRONT_CENTER] * 9 / 10; - right += sample_in_pos[LOW_FREQUENCY] * 3 / 10; - right += sample_in_pos[BACK_LEFT] * 3 / 10; - right += sample_in_pos[BACK_RIGHT] * 7 / 10; - - sample_out_p[0] = left; - sample_out_p[1] = right; - - sample_in_pos += channels51; - } - } - - void to_51(samples_t &out, const buf_t &in) override { - std::copy_n(std::begin(in), out.size(), std::begin(out)); - } - - void to_71(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { - int fl = sample_in_pos[FRONT_LEFT]; - int fr = sample_in_pos[FRONT_RIGHT]; - int bl = sample_in_pos[BACK_LEFT]; - int br = sample_in_pos[BACK_RIGHT]; - - auto mix_l = (fl + bl) / 2; - auto mix_r = (bl + br) / 2; - - sample_out_p[FRONT_LEFT] = fl; - sample_out_p[FRONT_RIGHT] = fr; - sample_out_p[FRONT_CENTER] = sample_in_pos[FRONT_CENTER]; - sample_out_p[LOW_FREQUENCY] = sample_in_pos[LOW_FREQUENCY]; - sample_out_p[BACK_LEFT] = bl; - sample_out_p[BACK_RIGHT] = br; - sample_out_p[SIDE_LEFT] = mix_l; - sample_out_p[SIDE_RIGHT] = mix_r; - - sample_in_pos += channels51; - } - } -}; - -class surr71_t : public audio_pipe_t { -public: - void to_stereo(samples_t &out, const buf_t &in) { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += stereo) { - int left {}, right {}; - - left += sample_in_pos[FRONT_LEFT]; - left += sample_in_pos[FRONT_CENTER] * 9 / 10; - left += sample_in_pos[LOW_FREQUENCY] * 3 / 10; - left += sample_in_pos[BACK_LEFT] * 7 / 10; - left += sample_in_pos[BACK_RIGHT] * 3 / 10; - left += sample_in_pos[SIDE_LEFT]; - - right += sample_in_pos[FRONT_RIGHT]; - right += sample_in_pos[FRONT_CENTER] * 9 / 10; - right += sample_in_pos[LOW_FREQUENCY] * 3 / 10; - right += sample_in_pos[BACK_LEFT] * 3 / 10; - right += sample_in_pos[BACK_RIGHT] * 7 / 10; - right += sample_in_pos[SIDE_RIGHT]; - - sample_out_p[0] = left; - sample_out_p[1] = right; - - sample_in_pos += channels71; - } - } - - void to_51(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { - auto sl = (int)sample_out_p[SIDE_LEFT] * 3 / 10; - auto sr = (int)sample_out_p[SIDE_RIGHT] * 3 / 10; - - sample_out_p[FRONT_LEFT] = sample_in_pos[FRONT_LEFT] + sl; - sample_out_p[FRONT_RIGHT] = sample_in_pos[FRONT_RIGHT] + sr; - sample_out_p[FRONT_CENTER] = sample_in_pos[FRONT_CENTER]; - sample_out_p[LOW_FREQUENCY] = sample_in_pos[LOW_FREQUENCY]; - sample_out_p[BACK_LEFT] = sample_in_pos[BACK_LEFT] + sl; - sample_out_p[BACK_RIGHT] = sample_in_pos[BACK_RIGHT] + sr; - - sample_in_pos += channels71; - } - } - - void to_71(samples_t &out, const buf_t &in) override { - std::copy_n(std::begin(in), out.size(), std::begin(out)); - } -}; - -static std::wstring_convert, wchar_t> converter; -struct format_t { - enum type_e : int { - none, - mono, - stereo, - surr51, - surr71, - } type; - - std::string_view name; - int channels; - int channel_mask; -} formats[] { - { - format_t::mono, - "Mono"sv, - 1, - SPEAKER_FRONT_CENTER, - }, - { - format_t::stereo, - "Stereo"sv, - 2, - SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, - }, - { - format_t::surr51, - "Surround 5.1"sv, - 6, - SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT, - }, - { - format_t::surr71, - "Surround 7.1"sv, - 8, - SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT, - }, -}; - -static format_t surround_51_side_speakers { - format_t::surr51, - "Surround 5.1"sv, - 6, - SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT, -}; - -void set_wave_format(audio::wave_format_t &wave_format, const format_t &format) { - wave_format->nChannels = format.channels; - wave_format->nBlockAlign = wave_format->nChannels * wave_format->wBitsPerSample / 8; - wave_format->nAvgBytesPerSec = wave_format->nSamplesPerSec * wave_format->nBlockAlign; - - if(wave_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { - ((PWAVEFORMATEXTENSIBLE)wave_format.get())->dwChannelMask = format.channel_mask; - } -} - -int init_wave_format(audio::wave_format_t &wave_format, DWORD sample_rate) { - wave_format->wBitsPerSample = 16; - wave_format->nSamplesPerSec = sample_rate; - switch(wave_format->wFormatTag) { - case WAVE_FORMAT_PCM: - break; - case WAVE_FORMAT_IEEE_FLOAT: - break; - case WAVE_FORMAT_EXTENSIBLE: { - auto wave_ex = (PWAVEFORMATEXTENSIBLE)wave_format.get(); - if(IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) { - wave_ex->Samples.wValidBitsPerSample = 16; - wave_ex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - } - - BOOST_LOG(error) << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']'; - } - default: - BOOST_LOG(error) << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']'; - return -1; - }; - - return 0; -} - -audio_client_t make_audio_client(device_t &device, const format_t &format, int sample_rate) { - audio_client_t audio_client; - auto status = device->Activate( - IID_IAudioClient, - CLSCTX_ALL, - nullptr, - (void **)&audio_client); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']'; - - return nullptr; - } - - wave_format_t wave_format; - status = audio_client->GetMixFormat(&wave_format); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; - - return nullptr; - } - - if(init_wave_format(wave_format, sample_rate)) { - return nullptr; - } - set_wave_format(wave_format, format); - - status = audio_client->Initialize( - AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - 0, 0, - wave_format.get(), - nullptr); - - if(status) { - BOOST_LOG(debug) << "Couldn't initialize audio client for ["sv << format.name << "]: [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; - } - - return audio_client; -} - -const wchar_t *no_null(const wchar_t *str) { - return str ? str : L"Unknown"; -} - -format_t::type_e validate_device(device_t &device, int sample_rate) { - for(const auto &format : formats) { - // Ensure WaveFromat is compatible - auto audio_client = make_audio_client(device, format, sample_rate); - - BOOST_LOG(debug) << format.name << ": "sv << (!audio_client ? "unsupported"sv : "supported"sv); - - if(audio_client) { - return format.type; - } - } - - return format_t::none; -} - -device_t default_device(device_enum_t &device_enum) { - device_t device; - HRESULT status; - status = device_enum->GetDefaultAudioEndpoint( - eRender, - eConsole, - &device); - - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't create audio Device [0x"sv << util::hex(status).to_string_view() << ']'; - - return nullptr; - } - - return device; -} - -class mic_wasapi_t : public mic_t { -public: - capture_e sample(std::vector &sample_out) override { - auto sample_size = sample_out.size() / channels_out * channels_in; - while(sample_buf_pos - std::begin(sample_buf) < sample_size) { - //FIXME: Use IAudioClient3 instead of IAudioClient, that would allows for adjusting the latency of the audio samples - auto capture_result = _fill_buffer(); - - if(capture_result != capture_e::ok) { - return capture_result; - } - } - - switch(channels_out) { - case 2: - pipe->to_stereo(sample_out, sample_buf); - break; - case 6: - pipe->to_51(sample_out, sample_buf); - break; - case 8: - pipe->to_71(sample_out, sample_buf); - break; - default: - BOOST_LOG(error) << "converting to ["sv << channels_out << "] channels is not supported"sv; - return capture_e::error; - } - - // The excess samples should be in front of the queue - std::move(&sample_buf[sample_size], sample_buf_pos, std::begin(sample_buf)); - sample_buf_pos -= sample_size; - - return capture_e::ok; - } - - - int init(std::uint32_t sample_rate, std::uint32_t frame_size, std::uint32_t channels_out) { - audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr)); - if(!audio_event) { - BOOST_LOG(error) << "Couldn't create Event handle"sv; - - return -1; - } - - HRESULT status; - - status = CoCreateInstance( - CLSID_MMDeviceEnumerator, - nullptr, - CLSCTX_ALL, - IID_IMMDeviceEnumerator, - (void **)&device_enum); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't create Device Enumerator [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - auto device = default_device(device_enum); - if(!device) { - return -1; - } - - for(auto &format : formats) { - BOOST_LOG(debug) << "Trying audio format ["sv << format.name << ']'; - audio_client = make_audio_client(device, format, sample_rate); - - if(audio_client) { - BOOST_LOG(debug) << "Found audio format ["sv << format.name << ']'; - channels_in = format.channels; - this->channels_out = channels_out; - - switch(channels_in) { - case 1: - pipe = std::make_unique(); - break; - case 2: - pipe = std::make_unique(); - break; - case 6: - pipe = std::make_unique(); - break; - case 8: - pipe = std::make_unique(); - break; - default: - BOOST_LOG(error) << "converting from ["sv << channels_in << "] channels is not supported"sv; - return -1; - } - break; - } - } - - if(!audio_client) { - BOOST_LOG(error) << "Couldn't find supported format for audio"sv; - return -1; - } - - REFERENCE_TIME default_latency; - audio_client->GetDevicePeriod(&default_latency, nullptr); - default_latency_ms = default_latency / 1000; - - std::uint32_t frames; - status = audio_client->GetBufferSize(&frames); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't acquire the number of audio frames [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - // *2 --> needs to fit double - sample_buf = util::buffer_t { std::max(frames, frame_size) * 2 * channels_in }; - sample_buf_pos = std::begin(sample_buf); - - status = audio_client->GetService(IID_IAudioCaptureClient, (void **)&audio_capture); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't initialize audio capture client [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - status = audio_client->SetEventHandle(audio_event.get()); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't set event handle [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - status = audio_client->Start(); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't start recording [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - return 0; - } - - ~mic_wasapi_t() override { - if(audio_client) { - audio_client->Stop(); - } - } - -private: - capture_e _fill_buffer() { - HRESULT status; - - // Total number of samples - struct sample_aligned_t { - std::uint32_t uninitialized; - std::int16_t *samples; - } sample_aligned; - - // number of samples / number of channels - struct block_aligned_t { - std::uint32_t audio_sample_size; - } block_aligned; - - status = WaitForSingleObjectEx(audio_event.get(), default_latency_ms, FALSE); - switch(status) { - case WAIT_OBJECT_0: - break; - case WAIT_TIMEOUT: - return capture_e::timeout; - default: - BOOST_LOG(error) << "Couldn't wait for audio event: [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - std::uint32_t packet_size {}; - for( - status = audio_capture->GetNextPacketSize(&packet_size); - SUCCEEDED(status) && packet_size > 0; - status = audio_capture->GetNextPacketSize(&packet_size)) { - DWORD buffer_flags; - status = audio_capture->GetBuffer( - (BYTE **)&sample_aligned.samples, - &block_aligned.audio_sample_size, - &buffer_flags, - nullptr, nullptr); - - switch(status) { - case S_OK: - break; - case AUDCLNT_E_DEVICE_INVALIDATED: - return capture_e::reinit; - default: - BOOST_LOG(error) << "Couldn't capture audio [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - sample_aligned.uninitialized = std::end(sample_buf) - sample_buf_pos; - auto n = std::min(sample_aligned.uninitialized, block_aligned.audio_sample_size * channels_in); - - if(buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) { - std::fill_n(sample_buf_pos, n, 0); - } - else { - std::copy_n(sample_aligned.samples, n, sample_buf_pos); - } - - sample_buf_pos += n; - - audio_capture->ReleaseBuffer(block_aligned.audio_sample_size); - } - - if(status == AUDCLNT_E_DEVICE_INVALIDATED) { - return capture_e::reinit; - } - - if(FAILED(status)) { - return capture_e::error; - } - - return capture_e::ok; - } - -public: - handle_t audio_event; - - device_enum_t device_enum; - device_t device; - audio_client_t audio_client; - audio_capture_t audio_capture; - - REFERENCE_TIME default_latency_ms; - - util::buffer_t sample_buf; - std::int16_t *sample_buf_pos; - - // out --> our audio output - int channels_out; - // in --> our wasapi input - int channels_in; - - std::unique_ptr pipe; -}; - -class audio_control_t : public ::platf::audio_control_t { -public: - std::optional sink_info() override { - auto virtual_adapter_name = L"Steam Streaming Speakers"sv; - - sink_t sink; - - audio::device_enum_t device_enum; - auto status = CoCreateInstance( - CLSID_MMDeviceEnumerator, - nullptr, - CLSCTX_ALL, - IID_IMMDeviceEnumerator, - (void **)&device_enum); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']'; - - return std::nullopt; - } - - auto device = default_device(device_enum); - if(!device) { - return std::nullopt; - } - - audio::wstring_t wstring; - device->GetId(&wstring); - - sink.host = converter.to_bytes(wstring.get()); - - collection_t collection; - status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']'; - - return std::nullopt; - } - - UINT count; - collection->GetCount(&count); - - std::string virtual_device_id = config::audio.virtual_sink; - for(auto x = 0; x < count; ++x) { - audio::device_t device; - collection->Item(x, &device); - - auto type = validate_device(device, SAMPLE_RATE); - if(type == format_t::none) { - continue; - } - - audio::wstring_t wstring; - device->GetId(&wstring); - - audio::prop_t prop; - device->OpenPropertyStore(STGM_READ, &prop); - - prop_var_t adapter_friendly_name; - prop_var_t device_friendly_name; - prop_var_t device_desc; - - prop->GetValue(PKEY_Device_FriendlyName, &device_friendly_name.prop); - prop->GetValue(PKEY_DeviceInterface_FriendlyName, &adapter_friendly_name.prop); - prop->GetValue(PKEY_Device_DeviceDesc, &device_desc.prop); - - auto adapter_name = no_null((LPWSTR)adapter_friendly_name.prop.pszVal); - BOOST_LOG(verbose) - << L"===== Device ====="sv << std::endl - << L"Device ID : "sv << wstring.get() << std::endl - << L"Device name : "sv << no_null((LPWSTR)device_friendly_name.prop.pszVal) << std::endl - << L"Adapter name : "sv << adapter_name << std::endl - << L"Device description : "sv << no_null((LPWSTR)device_desc.prop.pszVal) << std::endl - << std::endl; - - if(virtual_device_id.empty() && adapter_name == virtual_adapter_name) { - virtual_device_id = converter.to_bytes(wstring.get()); - } - } - - if(!virtual_device_id.empty()) { - sink.null = std::make_optional(sink_t::null_t { - "virtual-"s.append(formats[format_t::stereo - 1].name) + virtual_device_id, - "virtual-"s.append(formats[format_t::surr51 - 1].name) + virtual_device_id, - "virtual-"s.append(formats[format_t::surr71 - 1].name) + virtual_device_id, - }); - } - - return sink; - } - - std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override { - auto mic = std::make_unique(); - - if(mic->init(sample_rate, frame_size, channels)) { - return nullptr; - } - - return mic; - } - - /** - * If the requested sink is a virtual sink, meaning no speakers attached to - * the host, then we can seamlessly set the format to stereo and surround sound. - * - * Any virtual sink detected will be prefixed by: - * virtual-(format name) - * If it doesn't contain that prefix, then the format will not be changed - */ - std::optional set_format(const std::string &sink) { - std::string_view sv { sink.c_str(), sink.size() }; - - format_t::type_e type = format_t::none; - // sink format: - // [virtual-(format name)]device_id - auto prefix = "virtual-"sv; - if(sv.find(prefix) == 0) { - sv = sv.substr(prefix.size(), sv.size() - prefix.size()); - - for(auto &format : formats) { - auto &name = format.name; - if(sv.find(name) == 0) { - type = format.type; - sv = sv.substr(name.size(), sv.size() - name.size()); - - break; - } - } - } - - auto wstring_device_id = converter.from_bytes(sv.data()); - - if(type == format_t::none) { - // wstring_device_id does not contain virtual-(format name) - // It's a simple deviceId, just pass it back - return std::make_optional(std::move(wstring_device_id)); - } - - wave_format_t wave_format; - auto status = policy->GetMixFormat(wstring_device_id.c_str(), &wave_format); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; - - return std::nullopt; - } - - if(init_wave_format(wave_format, SAMPLE_RATE)) { - return std::nullopt; - } - set_wave_format(wave_format, formats[(int)type - 1]); - - WAVEFORMATEXTENSIBLE p {}; - status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p); - - // Surround 5.1 might contain side-{left, right} instead of speaker in the back - // Try again with different speaker mask. - if(status == 0x88890008 && type == format_t::surr51) { - set_wave_format(wave_format, surround_51_side_speakers); - status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p); - } - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't set Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; - - return std::nullopt; - } - - return std::make_optional(std::move(wstring_device_id)); - } - - int set_sink(const std::string &sink) override { - auto wstring_device_id = set_format(sink); - if(!wstring_device_id) { - return -1; - } - - int failure {}; - for(int x = 0; x < (int)ERole_enum_count; ++x) { - auto status = policy->SetDefaultEndpoint(wstring_device_id->c_str(), (ERole)x); - if(status) { - BOOST_LOG(warning) << "Couldn't set ["sv << sink << "] to role ["sv << x << ']'; - - ++failure; - } - } - - return failure; - } - - int init() { - auto status = CoCreateInstance( - CLSID_CPolicyConfigClient, - nullptr, - CLSCTX_ALL, - IID_IPolicyConfig, - (void **)&policy); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't create audio policy config: [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - return 0; - } - - ~audio_control_t() override {} - - policy_t policy; -}; -} // namespace platf::audio - -namespace platf { - -// It's not big enough to justify it's own source file :/ -namespace dxgi { -int init(); -} - -std::unique_ptr audio_control() { - auto control = std::make_unique(); - - if(control->init()) { - return nullptr; - } - - return control; -} - -std::unique_ptr init() { - if(dxgi::init()) { - return nullptr; - } - return std::make_unique(); -} -} // namespace platf +// +// Created by loki on 1/12/20. +// + +#include +#include +#include + +#include + +#include + +#define INITGUID +#include +#undef INITGUID + +#include "src/config.h" +#include "src/main.h" +#include "src/platform/common.h" + +// Must be the last included file +// clang-format off +#include "PolicyConfig.h" +// clang-format on + +DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING +DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING +DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2); + +const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); +const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); +const IID IID_IAudioClient = __uuidof(IAudioClient); +const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); + +using namespace std::literals; +namespace platf::audio { +constexpr auto SAMPLE_RATE = 48000; + +template +void Release(T *p) { + p->Release(); +} + +template +void co_task_free(T *p) { + CoTaskMemFree((LPVOID)p); +} + +using device_enum_t = util::safe_ptr>; +using device_t = util::safe_ptr>; +using collection_t = util::safe_ptr>; +using audio_client_t = util::safe_ptr>; +using audio_capture_t = util::safe_ptr>; +using wave_format_t = util::safe_ptr>; +using wstring_t = util::safe_ptr>; +using handle_t = util::safe_ptr_v2; +using policy_t = util::safe_ptr>; +using prop_t = util::safe_ptr>; + +class co_init_t : public deinit_t { +public: + co_init_t() { + CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY); + } + + ~co_init_t() override { + CoUninitialize(); + } +}; + +class prop_var_t { +public: + prop_var_t() { + PropVariantInit(&prop); + } + + ~prop_var_t() { + PropVariantClear(&prop); + } + + PROPVARIANT prop; +}; + +class audio_pipe_t { +public: + static constexpr auto stereo = 2; + static constexpr auto channels51 = 6; + static constexpr auto channels71 = 8; + + using samples_t = std::vector; + using buf_t = util::buffer_t; + + virtual void to_stereo(samples_t &out, const buf_t &in) = 0; + virtual void to_51(samples_t &out, const buf_t &in) = 0; + virtual void to_71(samples_t &out, const buf_t &in) = 0; +}; + +class mono_t : public audio_pipe_t { +public: + void to_stereo(samples_t &out, const buf_t &in) override { + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end;) { + *sample_out_p++ = *sample_in_pos * 7 / 10; + *sample_out_p++ = *sample_in_pos++ * 7 / 10; + } + } + + void to_51(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { + int left = *sample_in_pos++; + + auto fl = (left * 7 / 10); + + sample_out_p[FRONT_LEFT] = fl; + sample_out_p[FRONT_RIGHT] = fl; + sample_out_p[FRONT_CENTER] = fl * 6; + sample_out_p[LOW_FREQUENCY] = fl / 10; + sample_out_p[BACK_LEFT] = left * 4 / 10; + sample_out_p[BACK_RIGHT] = left * 4 / 10; + } + } + + void to_71(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { + int left = *sample_in_pos++; + + auto fl = (left * 7 / 10); + + sample_out_p[FRONT_LEFT] = fl; + sample_out_p[FRONT_RIGHT] = fl; + sample_out_p[FRONT_CENTER] = fl * 6; + sample_out_p[LOW_FREQUENCY] = fl / 10; + sample_out_p[BACK_LEFT] = left * 4 / 10; + sample_out_p[BACK_RIGHT] = left * 4 / 10; + sample_out_p[SIDE_LEFT] = left * 5 / 10; + sample_out_p[SIDE_RIGHT] = left * 5 / 10; + } + } +}; + +class stereo_t : public audio_pipe_t { +public: + void to_stereo(samples_t &out, const buf_t &in) override { + std::copy_n(std::begin(in), out.size(), std::begin(out)); + } + + void to_51(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { + int left = sample_in_pos[speaker::FRONT_LEFT]; + int right = sample_in_pos[speaker::FRONT_RIGHT]; + + sample_in_pos += 2; + + auto fl = (left * 7 / 10); + auto fr = (right * 7 / 10); + + auto mix = (fl + fr) / 2; + + sample_out_p[FRONT_LEFT] = fl; + sample_out_p[FRONT_RIGHT] = fr; + sample_out_p[FRONT_CENTER] = mix; + sample_out_p[LOW_FREQUENCY] = mix / 2; + sample_out_p[BACK_LEFT] = left * 4 / 10; + sample_out_p[BACK_RIGHT] = right * 4 / 10; + } + } + + void to_71(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { + int left = sample_in_pos[speaker::FRONT_LEFT]; + int right = sample_in_pos[speaker::FRONT_RIGHT]; + + sample_in_pos += 2; + + auto fl = (left * 7 / 10); + auto fr = (right * 7 / 10); + + auto mix = (fl + fr) / 2; + + sample_out_p[FRONT_LEFT] = fl; + sample_out_p[FRONT_RIGHT] = fr; + sample_out_p[FRONT_CENTER] = mix; + sample_out_p[LOW_FREQUENCY] = mix / 2; + sample_out_p[BACK_LEFT] = left * 4 / 10; + sample_out_p[BACK_RIGHT] = right * 4 / 10; + sample_out_p[SIDE_LEFT] = left * 5 / 10; + sample_out_p[SIDE_RIGHT] = right * 5 / 10; + } + } +}; + +class surr51_t : public audio_pipe_t { +public: + void to_stereo(samples_t &out, const buf_t &in) { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += stereo) { + int left {}, right {}; + + left += sample_in_pos[FRONT_LEFT]; + left += sample_in_pos[FRONT_CENTER] * 9 / 10; + left += sample_in_pos[LOW_FREQUENCY] * 3 / 10; + left += sample_in_pos[BACK_LEFT] * 7 / 10; + left += sample_in_pos[BACK_RIGHT] * 3 / 10; + + right += sample_in_pos[FRONT_RIGHT]; + right += sample_in_pos[FRONT_CENTER] * 9 / 10; + right += sample_in_pos[LOW_FREQUENCY] * 3 / 10; + right += sample_in_pos[BACK_LEFT] * 3 / 10; + right += sample_in_pos[BACK_RIGHT] * 7 / 10; + + sample_out_p[0] = left; + sample_out_p[1] = right; + + sample_in_pos += channels51; + } + } + + void to_51(samples_t &out, const buf_t &in) override { + std::copy_n(std::begin(in), out.size(), std::begin(out)); + } + + void to_71(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { + int fl = sample_in_pos[FRONT_LEFT]; + int fr = sample_in_pos[FRONT_RIGHT]; + int bl = sample_in_pos[BACK_LEFT]; + int br = sample_in_pos[BACK_RIGHT]; + + auto mix_l = (fl + bl) / 2; + auto mix_r = (bl + br) / 2; + + sample_out_p[FRONT_LEFT] = fl; + sample_out_p[FRONT_RIGHT] = fr; + sample_out_p[FRONT_CENTER] = sample_in_pos[FRONT_CENTER]; + sample_out_p[LOW_FREQUENCY] = sample_in_pos[LOW_FREQUENCY]; + sample_out_p[BACK_LEFT] = bl; + sample_out_p[BACK_RIGHT] = br; + sample_out_p[SIDE_LEFT] = mix_l; + sample_out_p[SIDE_RIGHT] = mix_r; + + sample_in_pos += channels51; + } + } +}; + +class surr71_t : public audio_pipe_t { +public: + void to_stereo(samples_t &out, const buf_t &in) { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += stereo) { + int left {}, right {}; + + left += sample_in_pos[FRONT_LEFT]; + left += sample_in_pos[FRONT_CENTER] * 9 / 10; + left += sample_in_pos[LOW_FREQUENCY] * 3 / 10; + left += sample_in_pos[BACK_LEFT] * 7 / 10; + left += sample_in_pos[BACK_RIGHT] * 3 / 10; + left += sample_in_pos[SIDE_LEFT]; + + right += sample_in_pos[FRONT_RIGHT]; + right += sample_in_pos[FRONT_CENTER] * 9 / 10; + right += sample_in_pos[LOW_FREQUENCY] * 3 / 10; + right += sample_in_pos[BACK_LEFT] * 3 / 10; + right += sample_in_pos[BACK_RIGHT] * 7 / 10; + right += sample_in_pos[SIDE_RIGHT]; + + sample_out_p[0] = left; + sample_out_p[1] = right; + + sample_in_pos += channels71; + } + } + + void to_51(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { + auto sl = (int)sample_out_p[SIDE_LEFT] * 3 / 10; + auto sr = (int)sample_out_p[SIDE_RIGHT] * 3 / 10; + + sample_out_p[FRONT_LEFT] = sample_in_pos[FRONT_LEFT] + sl; + sample_out_p[FRONT_RIGHT] = sample_in_pos[FRONT_RIGHT] + sr; + sample_out_p[FRONT_CENTER] = sample_in_pos[FRONT_CENTER]; + sample_out_p[LOW_FREQUENCY] = sample_in_pos[LOW_FREQUENCY]; + sample_out_p[BACK_LEFT] = sample_in_pos[BACK_LEFT] + sl; + sample_out_p[BACK_RIGHT] = sample_in_pos[BACK_RIGHT] + sr; + + sample_in_pos += channels71; + } + } + + void to_71(samples_t &out, const buf_t &in) override { + std::copy_n(std::begin(in), out.size(), std::begin(out)); + } +}; + +static std::wstring_convert, wchar_t> converter; +struct format_t { + enum type_e : int { + none, + mono, + stereo, + surr51, + surr71, + } type; + + std::string_view name; + int channels; + int channel_mask; +} formats[] { + { + format_t::mono, + "Mono"sv, + 1, + SPEAKER_FRONT_CENTER, + }, + { + format_t::stereo, + "Stereo"sv, + 2, + SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + }, + { + format_t::surr51, + "Surround 5.1"sv, + 6, + SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT, + }, + { + format_t::surr71, + "Surround 7.1"sv, + 8, + SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT | + SPEAKER_SIDE_LEFT | + SPEAKER_SIDE_RIGHT, + }, +}; + +static format_t surround_51_side_speakers { + format_t::surr51, + "Surround 5.1"sv, + 6, + SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_SIDE_LEFT | + SPEAKER_SIDE_RIGHT, +}; + +void set_wave_format(audio::wave_format_t &wave_format, const format_t &format) { + wave_format->nChannels = format.channels; + wave_format->nBlockAlign = wave_format->nChannels * wave_format->wBitsPerSample / 8; + wave_format->nAvgBytesPerSec = wave_format->nSamplesPerSec * wave_format->nBlockAlign; + + if(wave_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + ((PWAVEFORMATEXTENSIBLE)wave_format.get())->dwChannelMask = format.channel_mask; + } +} + +int init_wave_format(audio::wave_format_t &wave_format, DWORD sample_rate) { + wave_format->wBitsPerSample = 16; + wave_format->nSamplesPerSec = sample_rate; + switch(wave_format->wFormatTag) { + case WAVE_FORMAT_PCM: + break; + case WAVE_FORMAT_IEEE_FLOAT: + break; + case WAVE_FORMAT_EXTENSIBLE: { + auto wave_ex = (PWAVEFORMATEXTENSIBLE)wave_format.get(); + if(IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) { + wave_ex->Samples.wValidBitsPerSample = 16; + wave_ex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + } + + BOOST_LOG(error) << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']'; + } + default: + BOOST_LOG(error) << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']'; + return -1; + }; + + return 0; +} + +audio_client_t make_audio_client(device_t &device, const format_t &format, int sample_rate) { + audio_client_t audio_client; + auto status = device->Activate( + IID_IAudioClient, + CLSCTX_ALL, + nullptr, + (void **)&audio_client); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']'; + + return nullptr; + } + + wave_format_t wave_format; + status = audio_client->GetMixFormat(&wave_format); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; + + return nullptr; + } + + if(init_wave_format(wave_format, sample_rate)) { + return nullptr; + } + set_wave_format(wave_format, format); + + status = audio_client->Initialize( + AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, 0, + wave_format.get(), + nullptr); + + if(status) { + BOOST_LOG(debug) << "Couldn't initialize audio client for ["sv << format.name << "]: [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + return audio_client; +} + +const wchar_t *no_null(const wchar_t *str) { + return str ? str : L"Unknown"; +} + +format_t::type_e validate_device(device_t &device, int sample_rate) { + for(const auto &format : formats) { + // Ensure WaveFromat is compatible + auto audio_client = make_audio_client(device, format, sample_rate); + + BOOST_LOG(debug) << format.name << ": "sv << (!audio_client ? "unsupported"sv : "supported"sv); + + if(audio_client) { + return format.type; + } + } + + return format_t::none; +} + +device_t default_device(device_enum_t &device_enum) { + device_t device; + HRESULT status; + status = device_enum->GetDefaultAudioEndpoint( + eRender, + eConsole, + &device); + + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't create audio Device [0x"sv << util::hex(status).to_string_view() << ']'; + + return nullptr; + } + + return device; +} + +class mic_wasapi_t : public mic_t { +public: + capture_e sample(std::vector &sample_out) override { + auto sample_size = sample_out.size() / channels_out * channels_in; + while(sample_buf_pos - std::begin(sample_buf) < sample_size) { + //FIXME: Use IAudioClient3 instead of IAudioClient, that would allows for adjusting the latency of the audio samples + auto capture_result = _fill_buffer(); + + if(capture_result != capture_e::ok) { + return capture_result; + } + } + + switch(channels_out) { + case 2: + pipe->to_stereo(sample_out, sample_buf); + break; + case 6: + pipe->to_51(sample_out, sample_buf); + break; + case 8: + pipe->to_71(sample_out, sample_buf); + break; + default: + BOOST_LOG(error) << "converting to ["sv << channels_out << "] channels is not supported"sv; + return capture_e::error; + } + + // The excess samples should be in front of the queue + std::move(&sample_buf[sample_size], sample_buf_pos, std::begin(sample_buf)); + sample_buf_pos -= sample_size; + + return capture_e::ok; + } + + + int init(std::uint32_t sample_rate, std::uint32_t frame_size, std::uint32_t channels_out) { + audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr)); + if(!audio_event) { + BOOST_LOG(error) << "Couldn't create Event handle"sv; + + return -1; + } + + HRESULT status; + + status = CoCreateInstance( + CLSID_MMDeviceEnumerator, + nullptr, + CLSCTX_ALL, + IID_IMMDeviceEnumerator, + (void **)&device_enum); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't create Device Enumerator [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + auto device = default_device(device_enum); + if(!device) { + return -1; + } + + for(auto &format : formats) { + BOOST_LOG(debug) << "Trying audio format ["sv << format.name << ']'; + audio_client = make_audio_client(device, format, sample_rate); + + if(audio_client) { + BOOST_LOG(debug) << "Found audio format ["sv << format.name << ']'; + channels_in = format.channels; + this->channels_out = channels_out; + + switch(channels_in) { + case 1: + pipe = std::make_unique(); + break; + case 2: + pipe = std::make_unique(); + break; + case 6: + pipe = std::make_unique(); + break; + case 8: + pipe = std::make_unique(); + break; + default: + BOOST_LOG(error) << "converting from ["sv << channels_in << "] channels is not supported"sv; + return -1; + } + break; + } + } + + if(!audio_client) { + BOOST_LOG(error) << "Couldn't find supported format for audio"sv; + return -1; + } + + REFERENCE_TIME default_latency; + audio_client->GetDevicePeriod(&default_latency, nullptr); + default_latency_ms = default_latency / 1000; + + std::uint32_t frames; + status = audio_client->GetBufferSize(&frames); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't acquire the number of audio frames [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + // *2 --> needs to fit double + sample_buf = util::buffer_t { std::max(frames, frame_size) * 2 * channels_in }; + sample_buf_pos = std::begin(sample_buf); + + status = audio_client->GetService(IID_IAudioCaptureClient, (void **)&audio_capture); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't initialize audio capture client [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + status = audio_client->SetEventHandle(audio_event.get()); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't set event handle [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + status = audio_client->Start(); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't start recording [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + return 0; + } + + ~mic_wasapi_t() override { + if(audio_client) { + audio_client->Stop(); + } + } + +private: + capture_e _fill_buffer() { + HRESULT status; + + // Total number of samples + struct sample_aligned_t { + std::uint32_t uninitialized; + std::int16_t *samples; + } sample_aligned; + + // number of samples / number of channels + struct block_aligned_t { + std::uint32_t audio_sample_size; + } block_aligned; + + status = WaitForSingleObjectEx(audio_event.get(), default_latency_ms, FALSE); + switch(status) { + case WAIT_OBJECT_0: + break; + case WAIT_TIMEOUT: + return capture_e::timeout; + default: + BOOST_LOG(error) << "Couldn't wait for audio event: [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + std::uint32_t packet_size {}; + for( + status = audio_capture->GetNextPacketSize(&packet_size); + SUCCEEDED(status) && packet_size > 0; + status = audio_capture->GetNextPacketSize(&packet_size)) { + DWORD buffer_flags; + status = audio_capture->GetBuffer( + (BYTE **)&sample_aligned.samples, + &block_aligned.audio_sample_size, + &buffer_flags, + nullptr, nullptr); + + switch(status) { + case S_OK: + break; + case AUDCLNT_E_DEVICE_INVALIDATED: + return capture_e::reinit; + default: + BOOST_LOG(error) << "Couldn't capture audio [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + sample_aligned.uninitialized = std::end(sample_buf) - sample_buf_pos; + auto n = std::min(sample_aligned.uninitialized, block_aligned.audio_sample_size * channels_in); + + if(buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) { + std::fill_n(sample_buf_pos, n, 0); + } + else { + std::copy_n(sample_aligned.samples, n, sample_buf_pos); + } + + sample_buf_pos += n; + + audio_capture->ReleaseBuffer(block_aligned.audio_sample_size); + } + + if(status == AUDCLNT_E_DEVICE_INVALIDATED) { + return capture_e::reinit; + } + + if(FAILED(status)) { + return capture_e::error; + } + + return capture_e::ok; + } + +public: + handle_t audio_event; + + device_enum_t device_enum; + device_t device; + audio_client_t audio_client; + audio_capture_t audio_capture; + + REFERENCE_TIME default_latency_ms; + + util::buffer_t sample_buf; + std::int16_t *sample_buf_pos; + + // out --> our audio output + int channels_out; + // in --> our wasapi input + int channels_in; + + std::unique_ptr pipe; +}; + +class audio_control_t : public ::platf::audio_control_t { +public: + std::optional sink_info() override { + auto virtual_adapter_name = L"Steam Streaming Speakers"sv; + + sink_t sink; + + audio::device_enum_t device_enum; + auto status = CoCreateInstance( + CLSID_MMDeviceEnumerator, + nullptr, + CLSCTX_ALL, + IID_IMMDeviceEnumerator, + (void **)&device_enum); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']'; + + return std::nullopt; + } + + auto device = default_device(device_enum); + if(!device) { + return std::nullopt; + } + + audio::wstring_t wstring; + device->GetId(&wstring); + + sink.host = converter.to_bytes(wstring.get()); + + collection_t collection; + status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']'; + + return std::nullopt; + } + + UINT count; + collection->GetCount(&count); + + std::string virtual_device_id = config::audio.virtual_sink; + for(auto x = 0; x < count; ++x) { + audio::device_t device; + collection->Item(x, &device); + + auto type = validate_device(device, SAMPLE_RATE); + if(type == format_t::none) { + continue; + } + + audio::wstring_t wstring; + device->GetId(&wstring); + + audio::prop_t prop; + device->OpenPropertyStore(STGM_READ, &prop); + + prop_var_t adapter_friendly_name; + prop_var_t device_friendly_name; + prop_var_t device_desc; + + prop->GetValue(PKEY_Device_FriendlyName, &device_friendly_name.prop); + prop->GetValue(PKEY_DeviceInterface_FriendlyName, &adapter_friendly_name.prop); + prop->GetValue(PKEY_Device_DeviceDesc, &device_desc.prop); + + auto adapter_name = no_null((LPWSTR)adapter_friendly_name.prop.pszVal); + BOOST_LOG(verbose) + << L"===== Device ====="sv << std::endl + << L"Device ID : "sv << wstring.get() << std::endl + << L"Device name : "sv << no_null((LPWSTR)device_friendly_name.prop.pszVal) << std::endl + << L"Adapter name : "sv << adapter_name << std::endl + << L"Device description : "sv << no_null((LPWSTR)device_desc.prop.pszVal) << std::endl + << std::endl; + + if(virtual_device_id.empty() && adapter_name == virtual_adapter_name) { + virtual_device_id = converter.to_bytes(wstring.get()); + } + } + + if(!virtual_device_id.empty()) { + sink.null = std::make_optional(sink_t::null_t { + "virtual-"s.append(formats[format_t::stereo - 1].name) + virtual_device_id, + "virtual-"s.append(formats[format_t::surr51 - 1].name) + virtual_device_id, + "virtual-"s.append(formats[format_t::surr71 - 1].name) + virtual_device_id, + }); + } + + return sink; + } + + std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override { + auto mic = std::make_unique(); + + if(mic->init(sample_rate, frame_size, channels)) { + return nullptr; + } + + return mic; + } + + /** + * If the requested sink is a virtual sink, meaning no speakers attached to + * the host, then we can seamlessly set the format to stereo and surround sound. + * + * Any virtual sink detected will be prefixed by: + * virtual-(format name) + * If it doesn't contain that prefix, then the format will not be changed + */ + std::optional set_format(const std::string &sink) { + std::string_view sv { sink.c_str(), sink.size() }; + + format_t::type_e type = format_t::none; + // sink format: + // [virtual-(format name)]device_id + auto prefix = "virtual-"sv; + if(sv.find(prefix) == 0) { + sv = sv.substr(prefix.size(), sv.size() - prefix.size()); + + for(auto &format : formats) { + auto &name = format.name; + if(sv.find(name) == 0) { + type = format.type; + sv = sv.substr(name.size(), sv.size() - name.size()); + + break; + } + } + } + + auto wstring_device_id = converter.from_bytes(sv.data()); + + if(type == format_t::none) { + // wstring_device_id does not contain virtual-(format name) + // It's a simple deviceId, just pass it back + return std::make_optional(std::move(wstring_device_id)); + } + + wave_format_t wave_format; + auto status = policy->GetMixFormat(wstring_device_id.c_str(), &wave_format); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; + + return std::nullopt; + } + + if(init_wave_format(wave_format, SAMPLE_RATE)) { + return std::nullopt; + } + set_wave_format(wave_format, formats[(int)type - 1]); + + WAVEFORMATEXTENSIBLE p {}; + status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p); + + // Surround 5.1 might contain side-{left, right} instead of speaker in the back + // Try again with different speaker mask. + if(status == 0x88890008 && type == format_t::surr51) { + set_wave_format(wave_format, surround_51_side_speakers); + status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p); + } + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't set Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; + + return std::nullopt; + } + + return std::make_optional(std::move(wstring_device_id)); + } + + int set_sink(const std::string &sink) override { + auto wstring_device_id = set_format(sink); + if(!wstring_device_id) { + return -1; + } + + int failure {}; + for(int x = 0; x < (int)ERole_enum_count; ++x) { + auto status = policy->SetDefaultEndpoint(wstring_device_id->c_str(), (ERole)x); + if(status) { + BOOST_LOG(warning) << "Couldn't set ["sv << sink << "] to role ["sv << x << ']'; + + ++failure; + } + } + + return failure; + } + + int init() { + auto status = CoCreateInstance( + CLSID_CPolicyConfigClient, + nullptr, + CLSCTX_ALL, + IID_IPolicyConfig, + (void **)&policy); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't create audio policy config: [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + return 0; + } + + ~audio_control_t() override {} + + policy_t policy; +}; +} // namespace platf::audio + +namespace platf { + +// It's not big enough to justify it's own source file :/ +namespace dxgi { +int init(); +} + +std::unique_ptr audio_control() { + auto control = std::make_unique(); + + if(control->init()) { + return nullptr; + } + + return control; +} + +std::unique_ptr init() { + if(dxgi::init()) { + return nullptr; + } + return std::make_unique(); +} +} // namespace platf diff --git a/sunshine/platform/windows/display.h b/src/platform/windows/display.h similarity index 96% rename from sunshine/platform/windows/display.h rename to src/platform/windows/display.h index 6a1501354e4..52faddb7179 100644 --- a/sunshine/platform/windows/display.h +++ b/src/platform/windows/display.h @@ -1,177 +1,177 @@ -// -// Created by loki on 4/23/20. -// - -#ifndef SUNSHINE_DISPLAY_H -#define SUNSHINE_DISPLAY_H - -#include -#include -#include -#include -#include -#include - -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" - -namespace platf::dxgi { -extern const char *format_str[]; - -template -void Release(T *dxgi) { - dxgi->Release(); -} - -using factory1_t = util::safe_ptr>; -using dxgi_t = util::safe_ptr>; -using dxgi1_t = util::safe_ptr>; -using device_t = util::safe_ptr>; -using device_ctx_t = util::safe_ptr>; -using adapter_t = util::safe_ptr>; -using output_t = util::safe_ptr>; -using output1_t = util::safe_ptr>; -using dup_t = util::safe_ptr>; -using texture2d_t = util::safe_ptr>; -using texture1d_t = util::safe_ptr>; -using resource_t = util::safe_ptr>; -using multithread_t = util::safe_ptr>; -using vs_t = util::safe_ptr>; -using ps_t = util::safe_ptr>; -using blend_t = util::safe_ptr>; -using input_layout_t = util::safe_ptr>; -using render_target_t = util::safe_ptr>; -using shader_res_t = util::safe_ptr>; -using buf_t = util::safe_ptr>; -using raster_state_t = util::safe_ptr>; -using sampler_state_t = util::safe_ptr>; -using blob_t = util::safe_ptr>; -using depth_stencil_state_t = util::safe_ptr>; -using depth_stencil_view_t = util::safe_ptr>; - -namespace video { -using device_t = util::safe_ptr>; -using ctx_t = util::safe_ptr>; -using processor_t = util::safe_ptr>; -using processor_out_t = util::safe_ptr>; -using processor_in_t = util::safe_ptr>; -using processor_enum_t = util::safe_ptr>; -} // namespace video - -class hwdevice_t; -struct cursor_t { - std::vector img_data; - - DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info; - int x, y; - bool visible; -}; - -class gpu_cursor_t { -public: - gpu_cursor_t() : cursor_view { 0, 0, 0, 0, 0.0f, 1.0f } {}; - void set_pos(LONG rel_x, LONG rel_y, bool visible) { - cursor_view.TopLeftX = rel_x; - cursor_view.TopLeftY = rel_y; - - this->visible = visible; - } - - void set_texture(LONG width, LONG height, texture2d_t &&texture) { - cursor_view.Width = width; - cursor_view.Height = height; - - this->texture = std::move(texture); - } - - texture2d_t texture; - shader_res_t input_res; - - D3D11_VIEWPORT cursor_view; - - bool visible; -}; - -class duplication_t { -public: - dup_t dup; - bool has_frame {}; - bool use_dwmflush {}; - - capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p); - capture_e reset(dup_t::pointer dup_p = dup_t::pointer()); - capture_e release_frame(); - - ~duplication_t(); -}; - -class display_base_t : public display_t { -public: - int init(int framerate, const std::string &display_name); - - std::chrono::nanoseconds delay; - - factory1_t factory; - adapter_t adapter; - output_t output; - device_t device; - device_ctx_t device_ctx; - duplication_t dup; - - DXGI_FORMAT format; - D3D_FEATURE_LEVEL feature_level; - - typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS { - D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE, - D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL, - D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL, - D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL, - D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH, - D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME - } D3DKMT_SCHEDULINGPRIORITYCLASS; - - typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS); -}; - -class display_ram_t : public display_base_t { -public: - capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; - capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); - - - std::shared_ptr alloc_img() override; - int dummy_img(img_t *img) override; - - int init(int framerate, const std::string &display_name); - - cursor_t cursor; - D3D11_MAPPED_SUBRESOURCE img_info; - texture2d_t texture; -}; - -class display_vram_t : public display_base_t, public std::enable_shared_from_this { -public: - capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; - capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); - - std::shared_ptr alloc_img() override; - int dummy_img(img_t *img_base) override; - - int init(int framerate, const std::string &display_name); - - std::shared_ptr make_hwdevice(pix_fmt_e pix_fmt) override; - - sampler_state_t sampler_linear; - - blend_t blend_enable; - blend_t blend_disable; - - ps_t scene_ps; - vs_t scene_vs; - - texture2d_t src; - gpu_cursor_t cursor; -}; -} // namespace platf::dxgi - -#endif +// +// Created by loki on 4/23/20. +// + +#ifndef SUNSHINE_DISPLAY_H +#define SUNSHINE_DISPLAY_H + +#include +#include +#include +#include +#include +#include + +#include "src/platform/common.h" +#include "src/utility.h" + +namespace platf::dxgi { +extern const char *format_str[]; + +template +void Release(T *dxgi) { + dxgi->Release(); +} + +using factory1_t = util::safe_ptr>; +using dxgi_t = util::safe_ptr>; +using dxgi1_t = util::safe_ptr>; +using device_t = util::safe_ptr>; +using device_ctx_t = util::safe_ptr>; +using adapter_t = util::safe_ptr>; +using output_t = util::safe_ptr>; +using output1_t = util::safe_ptr>; +using dup_t = util::safe_ptr>; +using texture2d_t = util::safe_ptr>; +using texture1d_t = util::safe_ptr>; +using resource_t = util::safe_ptr>; +using multithread_t = util::safe_ptr>; +using vs_t = util::safe_ptr>; +using ps_t = util::safe_ptr>; +using blend_t = util::safe_ptr>; +using input_layout_t = util::safe_ptr>; +using render_target_t = util::safe_ptr>; +using shader_res_t = util::safe_ptr>; +using buf_t = util::safe_ptr>; +using raster_state_t = util::safe_ptr>; +using sampler_state_t = util::safe_ptr>; +using blob_t = util::safe_ptr>; +using depth_stencil_state_t = util::safe_ptr>; +using depth_stencil_view_t = util::safe_ptr>; + +namespace video { +using device_t = util::safe_ptr>; +using ctx_t = util::safe_ptr>; +using processor_t = util::safe_ptr>; +using processor_out_t = util::safe_ptr>; +using processor_in_t = util::safe_ptr>; +using processor_enum_t = util::safe_ptr>; +} // namespace video + +class hwdevice_t; +struct cursor_t { + std::vector img_data; + + DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info; + int x, y; + bool visible; +}; + +class gpu_cursor_t { +public: + gpu_cursor_t() : cursor_view { 0, 0, 0, 0, 0.0f, 1.0f } {}; + void set_pos(LONG rel_x, LONG rel_y, bool visible) { + cursor_view.TopLeftX = rel_x; + cursor_view.TopLeftY = rel_y; + + this->visible = visible; + } + + void set_texture(LONG width, LONG height, texture2d_t &&texture) { + cursor_view.Width = width; + cursor_view.Height = height; + + this->texture = std::move(texture); + } + + texture2d_t texture; + shader_res_t input_res; + + D3D11_VIEWPORT cursor_view; + + bool visible; +}; + +class duplication_t { +public: + dup_t dup; + bool has_frame {}; + bool use_dwmflush {}; + + capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p); + capture_e reset(dup_t::pointer dup_p = dup_t::pointer()); + capture_e release_frame(); + + ~duplication_t(); +}; + +class display_base_t : public display_t { +public: + int init(int framerate, const std::string &display_name); + + std::chrono::nanoseconds delay; + + factory1_t factory; + adapter_t adapter; + output_t output; + device_t device; + device_ctx_t device_ctx; + duplication_t dup; + + DXGI_FORMAT format; + D3D_FEATURE_LEVEL feature_level; + + typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS { + D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE, + D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL, + D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL, + D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL, + D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH, + D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME + } D3DKMT_SCHEDULINGPRIORITYCLASS; + + typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS); +}; + +class display_ram_t : public display_base_t { +public: + capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; + capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); + + + std::shared_ptr alloc_img() override; + int dummy_img(img_t *img) override; + + int init(int framerate, const std::string &display_name); + + cursor_t cursor; + D3D11_MAPPED_SUBRESOURCE img_info; + texture2d_t texture; +}; + +class display_vram_t : public display_base_t, public std::enable_shared_from_this { +public: + capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; + capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); + + std::shared_ptr alloc_img() override; + int dummy_img(img_t *img_base) override; + + int init(int framerate, const std::string &display_name); + + std::shared_ptr make_hwdevice(pix_fmt_e pix_fmt) override; + + sampler_state_t sampler_linear; + + blend_t blend_enable; + blend_t blend_disable; + + ps_t scene_ps; + vs_t scene_vs; + + texture2d_t src; + gpu_cursor_t cursor; +}; +} // namespace platf::dxgi + +#endif diff --git a/sunshine/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp similarity index 99% rename from sunshine/platform/windows/display_base.cpp rename to src/platform/windows/display_base.cpp index 9ee9ad974e9..e6951cce47e 100644 --- a/sunshine/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -7,9 +7,9 @@ #include "display.h" #include "misc.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" +#include "src/config.h" +#include "src/main.h" +#include "src/platform/common.h" namespace platf { using namespace std::literals; diff --git a/sunshine/platform/windows/display_ram.cpp b/src/platform/windows/display_ram.cpp similarity index 96% rename from sunshine/platform/windows/display_ram.cpp rename to src/platform/windows/display_ram.cpp index 285f0bcb6d6..596d4880ff3 100644 --- a/sunshine/platform/windows/display_ram.cpp +++ b/src/platform/windows/display_ram.cpp @@ -1,327 +1,327 @@ -#include "display.h" -#include "sunshine/main.h" - -namespace platf { -using namespace std::literals; -} - -namespace platf::dxgi { -struct img_t : public ::platf::img_t { - ~img_t() override { - delete[] data; - data = nullptr; - } -}; - -void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) { - int height = cursor.shape_info.Height / 2; - int width = cursor.shape_info.Width; - int pitch = cursor.shape_info.Pitch; - - // img cursor.{x,y} < 0, skip parts of the cursor.img_data - auto cursor_skip_y = -std::min(0, cursor.y); - auto cursor_skip_x = -std::min(0, cursor.x); - - // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data - auto cursor_truncate_y = std::max(0, cursor.y - img.height); - auto cursor_truncate_x = std::max(0, cursor.x - img.width); - - auto cursor_width = width - cursor_skip_x - cursor_truncate_x; - auto cursor_height = height - cursor_skip_y - cursor_truncate_y; - - if(cursor_height > height || cursor_width > width) { - return; - } - - auto img_skip_y = std::max(0, cursor.y); - auto img_skip_x = std::max(0, cursor.x); - - auto cursor_img_data = cursor.img_data.data() + cursor_skip_y * pitch; - - int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); - int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); - - auto pixels_per_byte = width / pitch; - auto bytes_per_row = delta_width / pixels_per_byte; - - auto img_data = (int *)img.data; - for(int i = 0; i < delta_height; ++i) { - auto and_mask = &cursor_img_data[i * pitch]; - auto xor_mask = &cursor_img_data[(i + height) * pitch]; - - auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; - - auto skip_x = cursor_skip_x; - for(int x = 0; x < bytes_per_row; ++x) { - for(auto bit = 0u; bit < 8; ++bit) { - if(skip_x > 0) { - --skip_x; - - continue; - } - - int and_ = *and_mask & (1 << (7 - bit)) ? -1 : 0; - int xor_ = *xor_mask & (1 << (7 - bit)) ? -1 : 0; - - *img_pixel_p &= and_; - *img_pixel_p ^= xor_; - - ++img_pixel_p; - } - - ++and_mask; - ++xor_mask; - } - } -} - -void apply_color_alpha(int *img_pixel_p, int cursor_pixel) { - auto colors_out = (std::uint8_t *)&cursor_pixel; - auto colors_in = (std::uint8_t *)img_pixel_p; - - //TODO: When use of IDXGIOutput5 is implemented, support different color formats - auto alpha = colors_out[3]; - if(alpha == 255) { - *img_pixel_p = cursor_pixel; - } - else { - colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255; - colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255; - colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255; - } -} - -void apply_color_masked(int *img_pixel_p, int cursor_pixel) { - //TODO: When use of IDXGIOutput5 is implemented, support different color formats - auto alpha = ((std::uint8_t *)&cursor_pixel)[3]; - if(alpha == 0xFF) { - *img_pixel_p ^= cursor_pixel; - } - else { - *img_pixel_p = cursor_pixel; - } -} - -void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) { - int height = cursor.shape_info.Height; - int width = cursor.shape_info.Width; - int pitch = cursor.shape_info.Pitch; - - // img cursor.y < 0, skip parts of the cursor.img_data - auto cursor_skip_y = -std::min(0, cursor.y); - auto cursor_skip_x = -std::min(0, cursor.x); - - // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data - auto cursor_truncate_y = std::max(0, cursor.y - img.height); - auto cursor_truncate_x = std::max(0, cursor.x - img.width); - - auto img_skip_y = std::max(0, cursor.y); - auto img_skip_x = std::max(0, cursor.x); - - auto cursor_width = width - cursor_skip_x - cursor_truncate_x; - auto cursor_height = height - cursor_skip_y - cursor_truncate_y; - - if(cursor_height > height || cursor_width > width) { - return; - } - - auto cursor_img_data = (int *)&cursor.img_data[cursor_skip_y * pitch]; - - int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); - int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); - - auto img_data = (int *)img.data; - - for(int i = 0; i < delta_height; ++i) { - auto cursor_begin = &cursor_img_data[i * cursor.shape_info.Width + cursor_skip_x]; - auto cursor_end = &cursor_begin[delta_width]; - - auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; - std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) { - if(masked) { - apply_color_masked(img_pixel_p, cursor_pixel); - } - else { - apply_color_alpha(img_pixel_p, cursor_pixel); - } - ++img_pixel_p; - }); - } -} - -void blend_cursor(const cursor_t &cursor, img_t &img) { - switch(cursor.shape_info.Type) { - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: - blend_cursor_color(cursor, img, false); - break; - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME: - blend_cursor_monochrome(cursor, img); - break; - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: - blend_cursor_color(cursor, img, true); - break; - default: - BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']'; - } -} - -capture_e display_ram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { - auto next_frame = std::chrono::steady_clock::now(); - - while(img) { - auto now = std::chrono::steady_clock::now(); - while(next_frame > now) { - now = std::chrono::steady_clock::now(); - } - next_frame = now + delay; - - auto status = snapshot(img.get(), 1000ms, *cursor); - switch(status) { - case platf::capture_e::reinit: - case platf::capture_e::error: - return status; - case platf::capture_e::timeout: - std::this_thread::sleep_for(1ms); - continue; - case platf::capture_e::ok: - img = snapshot_cb(img); - break; - default: - BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; - return status; - } - } - - return capture_e::ok; -} - -capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { - auto img = (img_t *)img_base; - - HRESULT status; - - DXGI_OUTDUPL_FRAME_INFO frame_info; - - resource_t::pointer res_p {}; - auto capture_status = dup.next_frame(frame_info, timeout, &res_p); - resource_t res { res_p }; - - if(capture_status != capture_e::ok) { - return capture_status; - } - - if(frame_info.PointerShapeBufferSize > 0) { - auto &img_data = cursor.img_data; - - img_data.resize(frame_info.PointerShapeBufferSize); - - UINT dummy; - status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; - - return capture_e::error; - } - } - - if(frame_info.LastMouseUpdateTime.QuadPart) { - cursor.x = frame_info.PointerPosition.Position.x; - cursor.y = frame_info.PointerPosition.Position.y; - cursor.visible = frame_info.PointerPosition.Visible; - } - - // If frame has been updated - if(frame_info.LastPresentTime.QuadPart != 0) { - { - texture2d_t src {}; - status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - //Copy from GPU to CPU - device_ctx->CopyResource(texture.get(), src.get()); - } - - if(img_info.pData) { - device_ctx->Unmap(texture.get(), 0); - img_info.pData = nullptr; - } - - status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']'; - - return capture_e::error; - } - } - - const bool mouse_update = - (frame_info.LastMouseUpdateTime.QuadPart || frame_info.PointerShapeBufferSize > 0) && - (cursor_visible && cursor.visible); - - const bool update_flag = frame_info.LastPresentTime.QuadPart != 0 || mouse_update; - - if(!update_flag) { - return capture_e::timeout; - } - - std::copy_n((std::uint8_t *)img_info.pData, height * img_info.RowPitch, (std::uint8_t *)img->data); - - if(cursor_visible && cursor.visible) { - blend_cursor(cursor, *img); - } - - return capture_e::ok; -} - -std::shared_ptr display_ram_t::alloc_img() { - auto img = std::make_shared(); - - img->pixel_pitch = 4; - img->row_pitch = img_info.RowPitch; - img->width = width; - img->height = height; - img->data = new std::uint8_t[img->row_pitch * height]; - - return img; -} - -int display_ram_t::dummy_img(platf::img_t *img) { - return 0; -} - -int display_ram_t::init(int framerate, const std::string &display_name) { - if(display_base_t::init(framerate, display_name)) { - return -1; - } - - D3D11_TEXTURE2D_DESC t {}; - t.Width = width; - t.Height = height; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_STAGING; - t.Format = format; - t.CPUAccessFlags = D3D11_CPU_ACCESS_READ; - - auto status = device->CreateTexture2D(&t, nullptr, &texture); - - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create texture [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - // map the texture simply to get the pitch and stride - status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to map the texture [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - return 0; -} -} // namespace platf::dxgi +#include "display.h" +#include "src/main.h" + +namespace platf { +using namespace std::literals; +} + +namespace platf::dxgi { +struct img_t : public ::platf::img_t { + ~img_t() override { + delete[] data; + data = nullptr; + } +}; + +void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) { + int height = cursor.shape_info.Height / 2; + int width = cursor.shape_info.Width; + int pitch = cursor.shape_info.Pitch; + + // img cursor.{x,y} < 0, skip parts of the cursor.img_data + auto cursor_skip_y = -std::min(0, cursor.y); + auto cursor_skip_x = -std::min(0, cursor.x); + + // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data + auto cursor_truncate_y = std::max(0, cursor.y - img.height); + auto cursor_truncate_x = std::max(0, cursor.x - img.width); + + auto cursor_width = width - cursor_skip_x - cursor_truncate_x; + auto cursor_height = height - cursor_skip_y - cursor_truncate_y; + + if(cursor_height > height || cursor_width > width) { + return; + } + + auto img_skip_y = std::max(0, cursor.y); + auto img_skip_x = std::max(0, cursor.x); + + auto cursor_img_data = cursor.img_data.data() + cursor_skip_y * pitch; + + int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); + int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); + + auto pixels_per_byte = width / pitch; + auto bytes_per_row = delta_width / pixels_per_byte; + + auto img_data = (int *)img.data; + for(int i = 0; i < delta_height; ++i) { + auto and_mask = &cursor_img_data[i * pitch]; + auto xor_mask = &cursor_img_data[(i + height) * pitch]; + + auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; + + auto skip_x = cursor_skip_x; + for(int x = 0; x < bytes_per_row; ++x) { + for(auto bit = 0u; bit < 8; ++bit) { + if(skip_x > 0) { + --skip_x; + + continue; + } + + int and_ = *and_mask & (1 << (7 - bit)) ? -1 : 0; + int xor_ = *xor_mask & (1 << (7 - bit)) ? -1 : 0; + + *img_pixel_p &= and_; + *img_pixel_p ^= xor_; + + ++img_pixel_p; + } + + ++and_mask; + ++xor_mask; + } + } +} + +void apply_color_alpha(int *img_pixel_p, int cursor_pixel) { + auto colors_out = (std::uint8_t *)&cursor_pixel; + auto colors_in = (std::uint8_t *)img_pixel_p; + + //TODO: When use of IDXGIOutput5 is implemented, support different color formats + auto alpha = colors_out[3]; + if(alpha == 255) { + *img_pixel_p = cursor_pixel; + } + else { + colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255; + colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255; + colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255; + } +} + +void apply_color_masked(int *img_pixel_p, int cursor_pixel) { + //TODO: When use of IDXGIOutput5 is implemented, support different color formats + auto alpha = ((std::uint8_t *)&cursor_pixel)[3]; + if(alpha == 0xFF) { + *img_pixel_p ^= cursor_pixel; + } + else { + *img_pixel_p = cursor_pixel; + } +} + +void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) { + int height = cursor.shape_info.Height; + int width = cursor.shape_info.Width; + int pitch = cursor.shape_info.Pitch; + + // img cursor.y < 0, skip parts of the cursor.img_data + auto cursor_skip_y = -std::min(0, cursor.y); + auto cursor_skip_x = -std::min(0, cursor.x); + + // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data + auto cursor_truncate_y = std::max(0, cursor.y - img.height); + auto cursor_truncate_x = std::max(0, cursor.x - img.width); + + auto img_skip_y = std::max(0, cursor.y); + auto img_skip_x = std::max(0, cursor.x); + + auto cursor_width = width - cursor_skip_x - cursor_truncate_x; + auto cursor_height = height - cursor_skip_y - cursor_truncate_y; + + if(cursor_height > height || cursor_width > width) { + return; + } + + auto cursor_img_data = (int *)&cursor.img_data[cursor_skip_y * pitch]; + + int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); + int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); + + auto img_data = (int *)img.data; + + for(int i = 0; i < delta_height; ++i) { + auto cursor_begin = &cursor_img_data[i * cursor.shape_info.Width + cursor_skip_x]; + auto cursor_end = &cursor_begin[delta_width]; + + auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; + std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) { + if(masked) { + apply_color_masked(img_pixel_p, cursor_pixel); + } + else { + apply_color_alpha(img_pixel_p, cursor_pixel); + } + ++img_pixel_p; + }); + } +} + +void blend_cursor(const cursor_t &cursor, img_t &img) { + switch(cursor.shape_info.Type) { + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: + blend_cursor_color(cursor, img, false); + break; + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME: + blend_cursor_monochrome(cursor, img); + break; + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: + blend_cursor_color(cursor, img, true); + break; + default: + BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']'; + } +} + +capture_e display_ram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { + auto next_frame = std::chrono::steady_clock::now(); + + while(img) { + auto now = std::chrono::steady_clock::now(); + while(next_frame > now) { + now = std::chrono::steady_clock::now(); + } + next_frame = now + delay; + + auto status = snapshot(img.get(), 1000ms, *cursor); + switch(status) { + case platf::capture_e::reinit: + case platf::capture_e::error: + return status; + case platf::capture_e::timeout: + std::this_thread::sleep_for(1ms); + continue; + case platf::capture_e::ok: + img = snapshot_cb(img); + break; + default: + BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; + return status; + } + } + + return capture_e::ok; +} + +capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { + auto img = (img_t *)img_base; + + HRESULT status; + + DXGI_OUTDUPL_FRAME_INFO frame_info; + + resource_t::pointer res_p {}; + auto capture_status = dup.next_frame(frame_info, timeout, &res_p); + resource_t res { res_p }; + + if(capture_status != capture_e::ok) { + return capture_status; + } + + if(frame_info.PointerShapeBufferSize > 0) { + auto &img_data = cursor.img_data; + + img_data.resize(frame_info.PointerShapeBufferSize); + + UINT dummy; + status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; + + return capture_e::error; + } + } + + if(frame_info.LastMouseUpdateTime.QuadPart) { + cursor.x = frame_info.PointerPosition.Position.x; + cursor.y = frame_info.PointerPosition.Position.y; + cursor.visible = frame_info.PointerPosition.Visible; + } + + // If frame has been updated + if(frame_info.LastPresentTime.QuadPart != 0) { + { + texture2d_t src {}; + status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + //Copy from GPU to CPU + device_ctx->CopyResource(texture.get(), src.get()); + } + + if(img_info.pData) { + device_ctx->Unmap(texture.get(), 0); + img_info.pData = nullptr; + } + + status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']'; + + return capture_e::error; + } + } + + const bool mouse_update = + (frame_info.LastMouseUpdateTime.QuadPart || frame_info.PointerShapeBufferSize > 0) && + (cursor_visible && cursor.visible); + + const bool update_flag = frame_info.LastPresentTime.QuadPart != 0 || mouse_update; + + if(!update_flag) { + return capture_e::timeout; + } + + std::copy_n((std::uint8_t *)img_info.pData, height * img_info.RowPitch, (std::uint8_t *)img->data); + + if(cursor_visible && cursor.visible) { + blend_cursor(cursor, *img); + } + + return capture_e::ok; +} + +std::shared_ptr display_ram_t::alloc_img() { + auto img = std::make_shared(); + + img->pixel_pitch = 4; + img->row_pitch = img_info.RowPitch; + img->width = width; + img->height = height; + img->data = new std::uint8_t[img->row_pitch * height]; + + return img; +} + +int display_ram_t::dummy_img(platf::img_t *img) { + return 0; +} + +int display_ram_t::init(int framerate, const std::string &display_name) { + if(display_base_t::init(framerate, display_name)) { + return -1; + } + + D3D11_TEXTURE2D_DESC t {}; + t.Width = width; + t.Height = height; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_STAGING; + t.Format = format; + t.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + + auto status = device->CreateTexture2D(&t, nullptr, &texture); + + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create texture [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + // map the texture simply to get the pitch and stride + status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to map the texture [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + return 0; +} +} // namespace platf::dxgi diff --git a/sunshine/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp similarity index 96% rename from sunshine/platform/windows/display_vram.cpp rename to src/platform/windows/display_vram.cpp index 9a932665b53..3cf7c97789b 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -1,888 +1,888 @@ -#include - -#include - -#include -#include - -extern "C" { -#include -#include -} - -#include "display.h" -#include "sunshine/main.h" -#include "sunshine/video.h" - - -#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/directx" -namespace platf { -using namespace std::literals; -} - -static void free_frame(AVFrame *frame) { - av_frame_free(&frame); -} - -using frame_t = util::safe_ptr; - -namespace platf::dxgi { - -template -buf_t make_buffer(device_t::pointer device, const T &t) { - static_assert(sizeof(T) % 16 == 0, "Buffer needs to be aligned on a 16-byte alignment"); - - D3D11_BUFFER_DESC buffer_desc { - sizeof(T), - D3D11_USAGE_IMMUTABLE, - D3D11_BIND_CONSTANT_BUFFER - }; - - D3D11_SUBRESOURCE_DATA init_data { - &t - }; - - buf_t::pointer buf_p; - auto status = device->CreateBuffer(&buffer_desc, &init_data, &buf_p); - if(status) { - BOOST_LOG(error) << "Failed to create buffer: [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; - } - - return buf_t { buf_p }; -} - -blend_t make_blend(device_t::pointer device, bool enable) { - D3D11_BLEND_DESC bdesc {}; - auto &rt = bdesc.RenderTarget[0]; - rt.BlendEnable = enable; - rt.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; - - if(enable) { - rt.BlendOp = D3D11_BLEND_OP_ADD; - rt.BlendOpAlpha = D3D11_BLEND_OP_ADD; - - rt.SrcBlend = D3D11_BLEND_SRC_ALPHA; - rt.DestBlend = D3D11_BLEND_INV_SRC_ALPHA; - - rt.SrcBlendAlpha = D3D11_BLEND_ZERO; - rt.DestBlendAlpha = D3D11_BLEND_ZERO; - } - - blend_t blend; - auto status = device->CreateBlendState(&bdesc, &blend); - if(status) { - BOOST_LOG(error) << "Failed to create blend state: [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; - } - - return blend; -} - -blob_t convert_UV_vs_hlsl; -blob_t convert_UV_ps_hlsl; -blob_t scene_vs_hlsl; -blob_t convert_Y_ps_hlsl; -blob_t scene_ps_hlsl; - -struct img_d3d_t : public platf::img_t { - std::shared_ptr display; - - shader_res_t input_res; - render_target_t scene_rt; - - texture2d_t texture; - - ~img_d3d_t() override = default; -}; - -util::buffer_t make_cursor_image(util::buffer_t &&img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) { - constexpr std::uint32_t black = 0xFF000000; - constexpr std::uint32_t white = 0xFFFFFFFF; - constexpr std::uint32_t transparent = 0; - - switch(shape_info.Type) { - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: - std::for_each((std::uint32_t *)std::begin(img_data), (std::uint32_t *)std::end(img_data), [](auto &pixel) { - if(pixel & 0xFF000000) { - pixel = transparent; - } - }); - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: - return std::move(img_data); - default: - break; - } - - shape_info.Height /= 2; - - util::buffer_t cursor_img { shape_info.Width * shape_info.Height * 4 }; - - auto bytes = shape_info.Pitch * shape_info.Height; - auto pixel_begin = (std::uint32_t *)std::begin(cursor_img); - auto pixel_data = pixel_begin; - auto and_mask = std::begin(img_data); - auto xor_mask = std::begin(img_data) + bytes; - - for(auto x = 0; x < bytes; ++x) { - for(auto c = 7; c >= 0; --c) { - auto bit = 1 << c; - auto color_type = ((*and_mask & bit) ? 1 : 0) + ((*xor_mask & bit) ? 2 : 0); - - switch(color_type) { - case 0: //black - *pixel_data = black; - break; - case 2: //white - *pixel_data = white; - break; - case 1: //transparent - { - *pixel_data = transparent; - - break; - } - case 3: //inverse - { - auto top_p = pixel_data - shape_info.Width; - auto left_p = pixel_data - 1; - auto right_p = pixel_data + 1; - auto bottom_p = pixel_data + shape_info.Width; - - // Get the x coordinate of the pixel - auto column = (pixel_data - pixel_begin) % shape_info.Width != 0; - - if(top_p >= pixel_begin && *top_p == transparent) { - *top_p = black; - } - - if(column != 0 && left_p >= pixel_begin && *left_p == transparent) { - *left_p = black; - } - - if(bottom_p < (std::uint32_t *)std::end(cursor_img)) { - *bottom_p = black; - } - - if(column != shape_info.Width - 1) { - *right_p = black; - } - *pixel_data = white; - } - } - - ++pixel_data; - } - ++and_mask; - ++xor_mask; - } - - return cursor_img; -} - -blob_t compile_shader(LPCSTR file, LPCSTR entrypoint, LPCSTR shader_model) { - blob_t::pointer msg_p = nullptr; - blob_t::pointer compiled_p; - - DWORD flags = D3DCOMPILE_ENABLE_STRICTNESS; - -#ifndef NDEBUG - flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; -#endif - std::wstring_convert, wchar_t> converter; - - auto wFile = converter.from_bytes(file); - auto status = D3DCompileFromFile(wFile.c_str(), nullptr, nullptr, entrypoint, shader_model, flags, 0, &compiled_p, &msg_p); - - if(msg_p) { - BOOST_LOG(warning) << std::string_view { (const char *)msg_p->GetBufferPointer(), msg_p->GetBufferSize() - 1 }; - msg_p->Release(); - } - - if(status) { - BOOST_LOG(error) << "Couldn't compile ["sv << file << "] [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; - } - - return blob_t { compiled_p }; -} - -blob_t compile_pixel_shader(LPCSTR file) { - return compile_shader(file, "main_ps", "ps_5_0"); -} - -blob_t compile_vertex_shader(LPCSTR file) { - return compile_shader(file, "main_vs", "vs_5_0"); -} - -int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format, texture2d_t::pointer tex) { - D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_desc { - format, - D3D11_SRV_DIMENSION_TEXTURE2D - }; - shader_resource_desc.Texture2D.MipLevels = 1; - - auto status = device->CreateShaderResourceView(tex, &shader_resource_desc, &shader_res); - if(status) { - BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - D3D11_RENDER_TARGET_VIEW_DESC render_target_desc { - format, - D3D11_RTV_DIMENSION_TEXTURE2D - }; - - status = device->CreateRenderTargetView(tex, &render_target_desc, &render_target); - if(status) { - BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - return 0; -} - -int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format) { - D3D11_TEXTURE2D_DESC desc {}; - - desc.Width = width; - desc.Height = height; - desc.Format = format; - desc.Usage = D3D11_USAGE_DEFAULT; - desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; - desc.MipLevels = 1; - desc.ArraySize = 1; - desc.SampleDesc.Count = 1; - - texture2d_t tex; - auto status = device->CreateTexture2D(&desc, nullptr, &tex); - if(status) { - BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - return init_rt(device, shader_res, render_target, width, height, format, tex.get()); -} - -class hwdevice_t : public platf::hwdevice_t { -public: - int convert(platf::img_t &img_base) override { - auto &img = (img_d3d_t &)img_base; - - device_ctx_p->IASetInputLayout(input_layout.get()); - - _init_view_port(this->img.width, this->img.height); - device_ctx_p->OMSetRenderTargets(1, &nv12_Y_rt, nullptr); - device_ctx_p->VSSetShader(scene_vs.get(), nullptr, 0); - device_ctx_p->PSSetShader(convert_Y_ps.get(), nullptr, 0); - device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res); - device_ctx_p->Draw(3, 0); - - device_ctx_p->RSSetViewports(1, &outY_view); - device_ctx_p->PSSetShaderResources(0, 1, &img.input_res); - device_ctx_p->Draw(3, 0); - - // Artifacts start appearing on the rendered image if Sunshine doesn't flush - // before rendering on the UV part of the image. - device_ctx_p->Flush(); - - _init_view_port(this->img.width / 2, this->img.height / 2); - device_ctx_p->OMSetRenderTargets(1, &nv12_UV_rt, nullptr); - device_ctx_p->VSSetShader(convert_UV_vs.get(), nullptr, 0); - device_ctx_p->PSSetShader(convert_UV_ps.get(), nullptr, 0); - device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res); - device_ctx_p->Draw(3, 0); - - device_ctx_p->RSSetViewports(1, &outUV_view); - device_ctx_p->PSSetShaderResources(0, 1, &img.input_res); - device_ctx_p->Draw(3, 0); - device_ctx_p->Flush(); - - return 0; - } - - void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override { - switch(colorspace) { - case 5: // SWS_CS_SMPTE170M - color_p = &::video::colors[0]; - break; - case 1: // SWS_CS_ITU709 - color_p = &::video::colors[2]; - break; - case 9: // SWS_CS_BT2020 - default: - BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv; - color_p = &::video::colors[0]; - }; - - if(color_range > 1) { - // Full range - ++color_p; - } - - auto color_matrix = make_buffer((device_t::pointer)data, *color_p); - if(!color_matrix) { - BOOST_LOG(warning) << "Failed to create color matrix"sv; - return; - } - - device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); - this->color_matrix = std::move(color_matrix); - } - - int set_frame(AVFrame *frame) { - this->hwframe.reset(frame); - this->frame = frame; - - auto device_p = (device_t::pointer)data; - - auto out_width = frame->width; - auto out_height = frame->height; - - float in_width = img.display->width; - float in_height = img.display->height; - - // // Ensure aspect ratio is maintained - auto scalar = std::fminf(out_width / in_width, out_height / in_height); - auto out_width_f = in_width * scalar; - auto out_height_f = in_height * scalar; - - // result is always positive - auto offsetX = (out_width - out_width_f) / 2; - auto offsetY = (out_height - out_height_f) / 2; - - outY_view = D3D11_VIEWPORT { offsetX, offsetY, out_width_f, out_height_f, 0.0f, 1.0f }; - outUV_view = D3D11_VIEWPORT { offsetX / 2, offsetY / 2, out_width_f / 2, out_height_f / 2, 0.0f, 1.0f }; - - D3D11_TEXTURE2D_DESC t {}; - t.Width = out_width; - t.Height = out_height; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_DEFAULT; - t.Format = format; - t.BindFlags = D3D11_BIND_RENDER_TARGET; - - auto status = device_p->CreateTexture2D(&t, nullptr, &img.texture); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create render target texture [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - img.width = out_width; - img.height = out_height; - img.data = (std::uint8_t *)img.texture.get(); - img.row_pitch = out_width * 4; - img.pixel_pitch = 4; - - float info_in[16 / sizeof(float)] { 1.0f / (float)out_width }; //aligned to 16-byte - info_scene = make_buffer(device_p, info_in); - - if(!info_in) { - BOOST_LOG(error) << "Failed to create info scene buffer"sv; - return -1; - } - - D3D11_RENDER_TARGET_VIEW_DESC nv12_rt_desc { - DXGI_FORMAT_R8_UNORM, - D3D11_RTV_DIMENSION_TEXTURE2D - }; - - status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_Y_rt); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - nv12_rt_desc.Format = DXGI_FORMAT_R8G8_UNORM; - - status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_UV_rt); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - // Need to have something refcounted - if(!frame->buf[0]) { - frame->buf[0] = av_buffer_allocz(sizeof(AVD3D11FrameDescriptor)); - } - - auto desc = (AVD3D11FrameDescriptor *)frame->buf[0]->data; - desc->texture = (ID3D11Texture2D *)img.data; - desc->index = 0; - - frame->data[0] = img.data; - frame->data[1] = 0; - - frame->linesize[0] = img.row_pitch; - - frame->height = img.height; - frame->width = img.width; - - return 0; - } - - int init( - std::shared_ptr display, device_t::pointer device_p, device_ctx_t::pointer device_ctx_p, - pix_fmt_e pix_fmt) { - - HRESULT status; - - device_p->AddRef(); - data = device_p; - - this->device_ctx_p = device_ctx_p; - - format = (pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010); - status = device_p->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); - if(status) { - BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - status = device_p->CreatePixelShader(convert_Y_ps_hlsl->GetBufferPointer(), convert_Y_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps); - if(status) { - BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - status = device_p->CreatePixelShader(convert_UV_ps_hlsl->GetBufferPointer(), convert_UV_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps); - if(status) { - BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - status = device_p->CreateVertexShader(convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), nullptr, &convert_UV_vs); - if(status) { - BOOST_LOG(error) << "Failed to create convertUV vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - status = device_p->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps); - if(status) { - BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - color_matrix = make_buffer(device_p, ::video::colors[0]); - if(!color_matrix) { - BOOST_LOG(error) << "Failed to create color matrix buffer"sv; - return -1; - } - - D3D11_INPUT_ELEMENT_DESC layout_desc { - "SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 - }; - - status = device_p->CreateInputLayout( - &layout_desc, 1, - convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), - &input_layout); - - img.display = std::move(display); - - // Color the background black, so that the padding for keeping the aspect ratio - // is black - if(img.display->dummy_img(&back_img)) { - BOOST_LOG(warning) << "Couldn't create an image to set background color to black"sv; - return -1; - } - - D3D11_SHADER_RESOURCE_VIEW_DESC desc { - DXGI_FORMAT_B8G8R8A8_UNORM, - D3D11_SRV_DIMENSION_TEXTURE2D - }; - desc.Texture2D.MipLevels = 1; - - status = device_p->CreateShaderResourceView(back_img.texture.get(), &desc, &back_img.input_res); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create input shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - device_ctx_p->IASetInputLayout(input_layout.get()); - device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); - device_ctx_p->VSSetConstantBuffers(0, 1, &info_scene); - - return 0; - } - - ~hwdevice_t() override { - if(data) { - ((ID3D11Device *)data)->Release(); - } - } - -private: - void _init_view_port(float x, float y, float width, float height) { - D3D11_VIEWPORT view { - x, y, - width, height, - 0.0f, 1.0f - }; - - device_ctx_p->RSSetViewports(1, &view); - } - - void _init_view_port(float width, float height) { - _init_view_port(0.0f, 0.0f, width, height); - } - -public: - frame_t hwframe; - - ::video::color_t *color_p; - - buf_t info_scene; - buf_t color_matrix; - - input_layout_t input_layout; - - render_target_t nv12_Y_rt; - render_target_t nv12_UV_rt; - - // The image referenced by hwframe - // The resulting image is stored here. - img_d3d_t img; - - // Clear nv12 render target to black - img_d3d_t back_img; - - vs_t convert_UV_vs; - ps_t convert_UV_ps; - ps_t convert_Y_ps; - ps_t scene_ps; - vs_t scene_vs; - - D3D11_VIEWPORT outY_view; - D3D11_VIEWPORT outUV_view; - - DXGI_FORMAT format; - - device_ctx_t::pointer device_ctx_p; -}; - -capture_e display_vram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { - auto next_frame = std::chrono::steady_clock::now(); - - while(img) { - auto now = std::chrono::steady_clock::now(); - while(next_frame > now) { - now = std::chrono::steady_clock::now(); - } - next_frame = now + delay; - - auto status = snapshot(img.get(), 1000ms, *cursor); - switch(status) { - case platf::capture_e::reinit: - case platf::capture_e::error: - return status; - case platf::capture_e::timeout: - std::this_thread::sleep_for(1ms); - continue; - case platf::capture_e::ok: - img = snapshot_cb(img); - break; - default: - BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; - return status; - } - } - - return capture_e::ok; -} - -capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { - auto img = (img_d3d_t *)img_base; - - HRESULT status; - - DXGI_OUTDUPL_FRAME_INFO frame_info; - - resource_t::pointer res_p {}; - auto capture_status = dup.next_frame(frame_info, timeout, &res_p); - resource_t res { res_p }; - - if(capture_status != capture_e::ok) { - return capture_status; - } - - const bool mouse_update_flag = frame_info.LastMouseUpdateTime.QuadPart != 0 || frame_info.PointerShapeBufferSize > 0; - const bool frame_update_flag = frame_info.AccumulatedFrames != 0 || frame_info.LastPresentTime.QuadPart != 0; - const bool update_flag = mouse_update_flag || frame_update_flag; - - if(!update_flag) { - return capture_e::timeout; - } - - if(frame_info.PointerShapeBufferSize > 0) { - DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info {}; - - util::buffer_t img_data { frame_info.PointerShapeBufferSize }; - - UINT dummy; - status = dup.dup->GetFramePointerShape(img_data.size(), std::begin(img_data), &dummy, &shape_info); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; - - return capture_e::error; - } - - auto cursor_img = make_cursor_image(std::move(img_data), shape_info); - - D3D11_SUBRESOURCE_DATA data { - std::begin(cursor_img), - 4 * shape_info.Width, - 0 - }; - - // Create texture for cursor - D3D11_TEXTURE2D_DESC t {}; - t.Width = shape_info.Width; - t.Height = cursor_img.size() / data.SysMemPitch; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_DEFAULT; - t.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - t.BindFlags = D3D11_BIND_SHADER_RESOURCE; - - texture2d_t texture; - auto status = device->CreateTexture2D(&t, &data, &texture); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create mouse texture [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - D3D11_SHADER_RESOURCE_VIEW_DESC desc { - DXGI_FORMAT_B8G8R8A8_UNORM, - D3D11_SRV_DIMENSION_TEXTURE2D - }; - desc.Texture2D.MipLevels = 1; - - // Free resources before allocating on the next line. - cursor.input_res.reset(); - status = device->CreateShaderResourceView(texture.get(), &desc, &cursor.input_res); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create cursor shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - cursor.set_texture(t.Width, t.Height, std::move(texture)); - } - - if(frame_info.LastMouseUpdateTime.QuadPart) { - cursor.set_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, frame_info.PointerPosition.Visible && cursor_visible); - } - - if(frame_update_flag) { - src.reset(); - status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - } - - device_ctx->CopyResource(img->texture.get(), src.get()); - if(cursor.visible) { - D3D11_VIEWPORT view { - 0.0f, 0.0f, - (float)width, (float)height, - 0.0f, 1.0f - }; - - device_ctx->VSSetShader(scene_vs.get(), nullptr, 0); - device_ctx->PSSetShader(scene_ps.get(), nullptr, 0); - device_ctx->RSSetViewports(1, &view); - device_ctx->OMSetRenderTargets(1, &img->scene_rt, nullptr); - device_ctx->PSSetShaderResources(0, 1, &cursor.input_res); - device_ctx->OMSetBlendState(blend_enable.get(), nullptr, 0xFFFFFFFFu); - device_ctx->RSSetViewports(1, &cursor.cursor_view); - device_ctx->Draw(3, 0); - device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); - } - - return capture_e::ok; -} - -int display_vram_t::init(int framerate, const std::string &display_name) { - if(display_base_t::init(framerate, display_name)) { - return -1; - } - - D3D11_SAMPLER_DESC sampler_desc {}; - sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; - sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; - sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; - sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; - sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER; - sampler_desc.MinLOD = 0; - sampler_desc.MaxLOD = D3D11_FLOAT32_MAX; - - auto status = device->CreateSamplerState(&sampler_desc, &sampler_linear); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create point sampler state [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - status = device->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); - if(status) { - BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - status = device->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps); - if(status) { - BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - blend_enable = make_blend(device.get(), true); - blend_disable = make_blend(device.get(), false); - - if(!blend_disable || !blend_enable) { - return -1; - } - - device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); - device_ctx->PSSetSamplers(0, 1, &sampler_linear); - device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); - - return 0; -} - -std::shared_ptr display_vram_t::alloc_img() { - auto img = std::make_shared(); - - img->pixel_pitch = 4; - img->row_pitch = img->pixel_pitch * width; - img->width = width; - img->height = height; - img->display = shared_from_this(); - - auto dummy_data = std::make_unique(img->row_pitch * height); - D3D11_SUBRESOURCE_DATA data { - dummy_data.get(), - (UINT)img->row_pitch - }; - std::fill_n(dummy_data.get(), img->row_pitch * height, 0); - - D3D11_TEXTURE2D_DESC t {}; - t.Width = width; - t.Height = height; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_DEFAULT; - t.Format = format; - t.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; - - auto status = device->CreateTexture2D(&t, &data, &img->texture); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create img buf texture [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; - } - - if(init_rt(device.get(), img->input_res, img->scene_rt, width, height, format, img->texture.get())) { - return nullptr; - } - - img->data = (std::uint8_t *)img->texture.get(); - - return img; -} - -int display_vram_t::dummy_img(platf::img_t *img_base) { - auto img = (img_d3d_t *)img_base; - - if(img->texture) { - return 0; - } - - img->row_pitch = width * 4; - auto dummy_data = std::make_unique(width * height); - D3D11_SUBRESOURCE_DATA data { - dummy_data.get(), - (UINT)img->row_pitch - }; - std::fill_n(dummy_data.get(), width * height, 0); - - D3D11_TEXTURE2D_DESC t {}; - t.Width = width; - t.Height = height; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_DEFAULT; - t.Format = format; - t.BindFlags = D3D11_BIND_SHADER_RESOURCE; - - dxgi::texture2d_t tex; - auto status = device->CreateTexture2D(&t, &data, &tex); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create dummy texture [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - img->texture = std::move(tex); - img->data = (std::uint8_t *)img->texture.get(); - - return 0; -} - -std::shared_ptr display_vram_t::make_hwdevice(pix_fmt_e pix_fmt) { - if(pix_fmt != platf::pix_fmt_e::nv12) { - BOOST_LOG(error) << "display_vram_t doesn't support pixel format ["sv << from_pix_fmt(pix_fmt) << ']'; - - return nullptr; - } - - auto hwdevice = std::make_shared(); - - auto ret = hwdevice->init( - shared_from_this(), - device.get(), - device_ctx.get(), - pix_fmt); - - if(ret) { - return nullptr; - } - - return hwdevice; -} - -int init() { - BOOST_LOG(info) << "Compiling shaders..."sv; - scene_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/SceneVS.hlsl"); - if(!scene_vs_hlsl) { - return -1; - } - - convert_Y_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertYPS.hlsl"); - if(!convert_Y_ps_hlsl) { - return -1; - } - - convert_UV_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertUVPS.hlsl"); - if(!convert_UV_ps_hlsl) { - return -1; - } - - convert_UV_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/ConvertUVVS.hlsl"); - if(!convert_UV_vs_hlsl) { - return -1; - } - - scene_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ScenePS.hlsl"); - if(!scene_ps_hlsl) { - return -1; - } - BOOST_LOG(info) << "Compiled shaders"sv; - - return 0; -} +#include + +#include + +#include +#include + +extern "C" { +#include +#include +} + +#include "display.h" +#include "src/main.h" +#include "src/video.h" + + +#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/directx" +namespace platf { +using namespace std::literals; +} + +static void free_frame(AVFrame *frame) { + av_frame_free(&frame); +} + +using frame_t = util::safe_ptr; + +namespace platf::dxgi { + +template +buf_t make_buffer(device_t::pointer device, const T &t) { + static_assert(sizeof(T) % 16 == 0, "Buffer needs to be aligned on a 16-byte alignment"); + + D3D11_BUFFER_DESC buffer_desc { + sizeof(T), + D3D11_USAGE_IMMUTABLE, + D3D11_BIND_CONSTANT_BUFFER + }; + + D3D11_SUBRESOURCE_DATA init_data { + &t + }; + + buf_t::pointer buf_p; + auto status = device->CreateBuffer(&buffer_desc, &init_data, &buf_p); + if(status) { + BOOST_LOG(error) << "Failed to create buffer: [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + return buf_t { buf_p }; +} + +blend_t make_blend(device_t::pointer device, bool enable) { + D3D11_BLEND_DESC bdesc {}; + auto &rt = bdesc.RenderTarget[0]; + rt.BlendEnable = enable; + rt.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + + if(enable) { + rt.BlendOp = D3D11_BLEND_OP_ADD; + rt.BlendOpAlpha = D3D11_BLEND_OP_ADD; + + rt.SrcBlend = D3D11_BLEND_SRC_ALPHA; + rt.DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + + rt.SrcBlendAlpha = D3D11_BLEND_ZERO; + rt.DestBlendAlpha = D3D11_BLEND_ZERO; + } + + blend_t blend; + auto status = device->CreateBlendState(&bdesc, &blend); + if(status) { + BOOST_LOG(error) << "Failed to create blend state: [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + return blend; +} + +blob_t convert_UV_vs_hlsl; +blob_t convert_UV_ps_hlsl; +blob_t scene_vs_hlsl; +blob_t convert_Y_ps_hlsl; +blob_t scene_ps_hlsl; + +struct img_d3d_t : public platf::img_t { + std::shared_ptr display; + + shader_res_t input_res; + render_target_t scene_rt; + + texture2d_t texture; + + ~img_d3d_t() override = default; +}; + +util::buffer_t make_cursor_image(util::buffer_t &&img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) { + constexpr std::uint32_t black = 0xFF000000; + constexpr std::uint32_t white = 0xFFFFFFFF; + constexpr std::uint32_t transparent = 0; + + switch(shape_info.Type) { + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: + std::for_each((std::uint32_t *)std::begin(img_data), (std::uint32_t *)std::end(img_data), [](auto &pixel) { + if(pixel & 0xFF000000) { + pixel = transparent; + } + }); + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: + return std::move(img_data); + default: + break; + } + + shape_info.Height /= 2; + + util::buffer_t cursor_img { shape_info.Width * shape_info.Height * 4 }; + + auto bytes = shape_info.Pitch * shape_info.Height; + auto pixel_begin = (std::uint32_t *)std::begin(cursor_img); + auto pixel_data = pixel_begin; + auto and_mask = std::begin(img_data); + auto xor_mask = std::begin(img_data) + bytes; + + for(auto x = 0; x < bytes; ++x) { + for(auto c = 7; c >= 0; --c) { + auto bit = 1 << c; + auto color_type = ((*and_mask & bit) ? 1 : 0) + ((*xor_mask & bit) ? 2 : 0); + + switch(color_type) { + case 0: //black + *pixel_data = black; + break; + case 2: //white + *pixel_data = white; + break; + case 1: //transparent + { + *pixel_data = transparent; + + break; + } + case 3: //inverse + { + auto top_p = pixel_data - shape_info.Width; + auto left_p = pixel_data - 1; + auto right_p = pixel_data + 1; + auto bottom_p = pixel_data + shape_info.Width; + + // Get the x coordinate of the pixel + auto column = (pixel_data - pixel_begin) % shape_info.Width != 0; + + if(top_p >= pixel_begin && *top_p == transparent) { + *top_p = black; + } + + if(column != 0 && left_p >= pixel_begin && *left_p == transparent) { + *left_p = black; + } + + if(bottom_p < (std::uint32_t *)std::end(cursor_img)) { + *bottom_p = black; + } + + if(column != shape_info.Width - 1) { + *right_p = black; + } + *pixel_data = white; + } + } + + ++pixel_data; + } + ++and_mask; + ++xor_mask; + } + + return cursor_img; +} + +blob_t compile_shader(LPCSTR file, LPCSTR entrypoint, LPCSTR shader_model) { + blob_t::pointer msg_p = nullptr; + blob_t::pointer compiled_p; + + DWORD flags = D3DCOMPILE_ENABLE_STRICTNESS; + +#ifndef NDEBUG + flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; +#endif + std::wstring_convert, wchar_t> converter; + + auto wFile = converter.from_bytes(file); + auto status = D3DCompileFromFile(wFile.c_str(), nullptr, nullptr, entrypoint, shader_model, flags, 0, &compiled_p, &msg_p); + + if(msg_p) { + BOOST_LOG(warning) << std::string_view { (const char *)msg_p->GetBufferPointer(), msg_p->GetBufferSize() - 1 }; + msg_p->Release(); + } + + if(status) { + BOOST_LOG(error) << "Couldn't compile ["sv << file << "] [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + return blob_t { compiled_p }; +} + +blob_t compile_pixel_shader(LPCSTR file) { + return compile_shader(file, "main_ps", "ps_5_0"); +} + +blob_t compile_vertex_shader(LPCSTR file) { + return compile_shader(file, "main_vs", "vs_5_0"); +} + +int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format, texture2d_t::pointer tex) { + D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_desc { + format, + D3D11_SRV_DIMENSION_TEXTURE2D + }; + shader_resource_desc.Texture2D.MipLevels = 1; + + auto status = device->CreateShaderResourceView(tex, &shader_resource_desc, &shader_res); + if(status) { + BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + D3D11_RENDER_TARGET_VIEW_DESC render_target_desc { + format, + D3D11_RTV_DIMENSION_TEXTURE2D + }; + + status = device->CreateRenderTargetView(tex, &render_target_desc, &render_target); + if(status) { + BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + return 0; +} + +int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format) { + D3D11_TEXTURE2D_DESC desc {}; + + desc.Width = width; + desc.Height = height; + desc.Format = format; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.SampleDesc.Count = 1; + + texture2d_t tex; + auto status = device->CreateTexture2D(&desc, nullptr, &tex); + if(status) { + BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + return init_rt(device, shader_res, render_target, width, height, format, tex.get()); +} + +class hwdevice_t : public platf::hwdevice_t { +public: + int convert(platf::img_t &img_base) override { + auto &img = (img_d3d_t &)img_base; + + device_ctx_p->IASetInputLayout(input_layout.get()); + + _init_view_port(this->img.width, this->img.height); + device_ctx_p->OMSetRenderTargets(1, &nv12_Y_rt, nullptr); + device_ctx_p->VSSetShader(scene_vs.get(), nullptr, 0); + device_ctx_p->PSSetShader(convert_Y_ps.get(), nullptr, 0); + device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res); + device_ctx_p->Draw(3, 0); + + device_ctx_p->RSSetViewports(1, &outY_view); + device_ctx_p->PSSetShaderResources(0, 1, &img.input_res); + device_ctx_p->Draw(3, 0); + + // Artifacts start appearing on the rendered image if Sunshine doesn't flush + // before rendering on the UV part of the image. + device_ctx_p->Flush(); + + _init_view_port(this->img.width / 2, this->img.height / 2); + device_ctx_p->OMSetRenderTargets(1, &nv12_UV_rt, nullptr); + device_ctx_p->VSSetShader(convert_UV_vs.get(), nullptr, 0); + device_ctx_p->PSSetShader(convert_UV_ps.get(), nullptr, 0); + device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res); + device_ctx_p->Draw(3, 0); + + device_ctx_p->RSSetViewports(1, &outUV_view); + device_ctx_p->PSSetShaderResources(0, 1, &img.input_res); + device_ctx_p->Draw(3, 0); + device_ctx_p->Flush(); + + return 0; + } + + void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override { + switch(colorspace) { + case 5: // SWS_CS_SMPTE170M + color_p = &::video::colors[0]; + break; + case 1: // SWS_CS_ITU709 + color_p = &::video::colors[2]; + break; + case 9: // SWS_CS_BT2020 + default: + BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv; + color_p = &::video::colors[0]; + }; + + if(color_range > 1) { + // Full range + ++color_p; + } + + auto color_matrix = make_buffer((device_t::pointer)data, *color_p); + if(!color_matrix) { + BOOST_LOG(warning) << "Failed to create color matrix"sv; + return; + } + + device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); + this->color_matrix = std::move(color_matrix); + } + + int set_frame(AVFrame *frame) { + this->hwframe.reset(frame); + this->frame = frame; + + auto device_p = (device_t::pointer)data; + + auto out_width = frame->width; + auto out_height = frame->height; + + float in_width = img.display->width; + float in_height = img.display->height; + + // // Ensure aspect ratio is maintained + auto scalar = std::fminf(out_width / in_width, out_height / in_height); + auto out_width_f = in_width * scalar; + auto out_height_f = in_height * scalar; + + // result is always positive + auto offsetX = (out_width - out_width_f) / 2; + auto offsetY = (out_height - out_height_f) / 2; + + outY_view = D3D11_VIEWPORT { offsetX, offsetY, out_width_f, out_height_f, 0.0f, 1.0f }; + outUV_view = D3D11_VIEWPORT { offsetX / 2, offsetY / 2, out_width_f / 2, out_height_f / 2, 0.0f, 1.0f }; + + D3D11_TEXTURE2D_DESC t {}; + t.Width = out_width; + t.Height = out_height; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = format; + t.BindFlags = D3D11_BIND_RENDER_TARGET; + + auto status = device_p->CreateTexture2D(&t, nullptr, &img.texture); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create render target texture [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + img.width = out_width; + img.height = out_height; + img.data = (std::uint8_t *)img.texture.get(); + img.row_pitch = out_width * 4; + img.pixel_pitch = 4; + + float info_in[16 / sizeof(float)] { 1.0f / (float)out_width }; //aligned to 16-byte + info_scene = make_buffer(device_p, info_in); + + if(!info_in) { + BOOST_LOG(error) << "Failed to create info scene buffer"sv; + return -1; + } + + D3D11_RENDER_TARGET_VIEW_DESC nv12_rt_desc { + DXGI_FORMAT_R8_UNORM, + D3D11_RTV_DIMENSION_TEXTURE2D + }; + + status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_Y_rt); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + nv12_rt_desc.Format = DXGI_FORMAT_R8G8_UNORM; + + status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_UV_rt); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + // Need to have something refcounted + if(!frame->buf[0]) { + frame->buf[0] = av_buffer_allocz(sizeof(AVD3D11FrameDescriptor)); + } + + auto desc = (AVD3D11FrameDescriptor *)frame->buf[0]->data; + desc->texture = (ID3D11Texture2D *)img.data; + desc->index = 0; + + frame->data[0] = img.data; + frame->data[1] = 0; + + frame->linesize[0] = img.row_pitch; + + frame->height = img.height; + frame->width = img.width; + + return 0; + } + + int init( + std::shared_ptr display, device_t::pointer device_p, device_ctx_t::pointer device_ctx_p, + pix_fmt_e pix_fmt) { + + HRESULT status; + + device_p->AddRef(); + data = device_p; + + this->device_ctx_p = device_ctx_p; + + format = (pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010); + status = device_p->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); + if(status) { + BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + status = device_p->CreatePixelShader(convert_Y_ps_hlsl->GetBufferPointer(), convert_Y_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps); + if(status) { + BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + status = device_p->CreatePixelShader(convert_UV_ps_hlsl->GetBufferPointer(), convert_UV_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps); + if(status) { + BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + status = device_p->CreateVertexShader(convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), nullptr, &convert_UV_vs); + if(status) { + BOOST_LOG(error) << "Failed to create convertUV vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + status = device_p->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps); + if(status) { + BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + color_matrix = make_buffer(device_p, ::video::colors[0]); + if(!color_matrix) { + BOOST_LOG(error) << "Failed to create color matrix buffer"sv; + return -1; + } + + D3D11_INPUT_ELEMENT_DESC layout_desc { + "SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 + }; + + status = device_p->CreateInputLayout( + &layout_desc, 1, + convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), + &input_layout); + + img.display = std::move(display); + + // Color the background black, so that the padding for keeping the aspect ratio + // is black + if(img.display->dummy_img(&back_img)) { + BOOST_LOG(warning) << "Couldn't create an image to set background color to black"sv; + return -1; + } + + D3D11_SHADER_RESOURCE_VIEW_DESC desc { + DXGI_FORMAT_B8G8R8A8_UNORM, + D3D11_SRV_DIMENSION_TEXTURE2D + }; + desc.Texture2D.MipLevels = 1; + + status = device_p->CreateShaderResourceView(back_img.texture.get(), &desc, &back_img.input_res); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create input shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + device_ctx_p->IASetInputLayout(input_layout.get()); + device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); + device_ctx_p->VSSetConstantBuffers(0, 1, &info_scene); + + return 0; + } + + ~hwdevice_t() override { + if(data) { + ((ID3D11Device *)data)->Release(); + } + } + +private: + void _init_view_port(float x, float y, float width, float height) { + D3D11_VIEWPORT view { + x, y, + width, height, + 0.0f, 1.0f + }; + + device_ctx_p->RSSetViewports(1, &view); + } + + void _init_view_port(float width, float height) { + _init_view_port(0.0f, 0.0f, width, height); + } + +public: + frame_t hwframe; + + ::video::color_t *color_p; + + buf_t info_scene; + buf_t color_matrix; + + input_layout_t input_layout; + + render_target_t nv12_Y_rt; + render_target_t nv12_UV_rt; + + // The image referenced by hwframe + // The resulting image is stored here. + img_d3d_t img; + + // Clear nv12 render target to black + img_d3d_t back_img; + + vs_t convert_UV_vs; + ps_t convert_UV_ps; + ps_t convert_Y_ps; + ps_t scene_ps; + vs_t scene_vs; + + D3D11_VIEWPORT outY_view; + D3D11_VIEWPORT outUV_view; + + DXGI_FORMAT format; + + device_ctx_t::pointer device_ctx_p; +}; + +capture_e display_vram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { + auto next_frame = std::chrono::steady_clock::now(); + + while(img) { + auto now = std::chrono::steady_clock::now(); + while(next_frame > now) { + now = std::chrono::steady_clock::now(); + } + next_frame = now + delay; + + auto status = snapshot(img.get(), 1000ms, *cursor); + switch(status) { + case platf::capture_e::reinit: + case platf::capture_e::error: + return status; + case platf::capture_e::timeout: + std::this_thread::sleep_for(1ms); + continue; + case platf::capture_e::ok: + img = snapshot_cb(img); + break; + default: + BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; + return status; + } + } + + return capture_e::ok; +} + +capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { + auto img = (img_d3d_t *)img_base; + + HRESULT status; + + DXGI_OUTDUPL_FRAME_INFO frame_info; + + resource_t::pointer res_p {}; + auto capture_status = dup.next_frame(frame_info, timeout, &res_p); + resource_t res { res_p }; + + if(capture_status != capture_e::ok) { + return capture_status; + } + + const bool mouse_update_flag = frame_info.LastMouseUpdateTime.QuadPart != 0 || frame_info.PointerShapeBufferSize > 0; + const bool frame_update_flag = frame_info.AccumulatedFrames != 0 || frame_info.LastPresentTime.QuadPart != 0; + const bool update_flag = mouse_update_flag || frame_update_flag; + + if(!update_flag) { + return capture_e::timeout; + } + + if(frame_info.PointerShapeBufferSize > 0) { + DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info {}; + + util::buffer_t img_data { frame_info.PointerShapeBufferSize }; + + UINT dummy; + status = dup.dup->GetFramePointerShape(img_data.size(), std::begin(img_data), &dummy, &shape_info); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; + + return capture_e::error; + } + + auto cursor_img = make_cursor_image(std::move(img_data), shape_info); + + D3D11_SUBRESOURCE_DATA data { + std::begin(cursor_img), + 4 * shape_info.Width, + 0 + }; + + // Create texture for cursor + D3D11_TEXTURE2D_DESC t {}; + t.Width = shape_info.Width; + t.Height = cursor_img.size() / data.SysMemPitch; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + t.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + texture2d_t texture; + auto status = device->CreateTexture2D(&t, &data, &texture); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create mouse texture [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + D3D11_SHADER_RESOURCE_VIEW_DESC desc { + DXGI_FORMAT_B8G8R8A8_UNORM, + D3D11_SRV_DIMENSION_TEXTURE2D + }; + desc.Texture2D.MipLevels = 1; + + // Free resources before allocating on the next line. + cursor.input_res.reset(); + status = device->CreateShaderResourceView(texture.get(), &desc, &cursor.input_res); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create cursor shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + cursor.set_texture(t.Width, t.Height, std::move(texture)); + } + + if(frame_info.LastMouseUpdateTime.QuadPart) { + cursor.set_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, frame_info.PointerPosition.Visible && cursor_visible); + } + + if(frame_update_flag) { + src.reset(); + status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + } + + device_ctx->CopyResource(img->texture.get(), src.get()); + if(cursor.visible) { + D3D11_VIEWPORT view { + 0.0f, 0.0f, + (float)width, (float)height, + 0.0f, 1.0f + }; + + device_ctx->VSSetShader(scene_vs.get(), nullptr, 0); + device_ctx->PSSetShader(scene_ps.get(), nullptr, 0); + device_ctx->RSSetViewports(1, &view); + device_ctx->OMSetRenderTargets(1, &img->scene_rt, nullptr); + device_ctx->PSSetShaderResources(0, 1, &cursor.input_res); + device_ctx->OMSetBlendState(blend_enable.get(), nullptr, 0xFFFFFFFFu); + device_ctx->RSSetViewports(1, &cursor.cursor_view); + device_ctx->Draw(3, 0); + device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); + } + + return capture_e::ok; +} + +int display_vram_t::init(int framerate, const std::string &display_name) { + if(display_base_t::init(framerate, display_name)) { + return -1; + } + + D3D11_SAMPLER_DESC sampler_desc {}; + sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; + sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; + sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; + sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER; + sampler_desc.MinLOD = 0; + sampler_desc.MaxLOD = D3D11_FLOAT32_MAX; + + auto status = device->CreateSamplerState(&sampler_desc, &sampler_linear); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create point sampler state [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + status = device->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); + if(status) { + BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + status = device->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps); + if(status) { + BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + blend_enable = make_blend(device.get(), true); + blend_disable = make_blend(device.get(), false); + + if(!blend_disable || !blend_enable) { + return -1; + } + + device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); + device_ctx->PSSetSamplers(0, 1, &sampler_linear); + device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + + return 0; +} + +std::shared_ptr display_vram_t::alloc_img() { + auto img = std::make_shared(); + + img->pixel_pitch = 4; + img->row_pitch = img->pixel_pitch * width; + img->width = width; + img->height = height; + img->display = shared_from_this(); + + auto dummy_data = std::make_unique(img->row_pitch * height); + D3D11_SUBRESOURCE_DATA data { + dummy_data.get(), + (UINT)img->row_pitch + }; + std::fill_n(dummy_data.get(), img->row_pitch * height, 0); + + D3D11_TEXTURE2D_DESC t {}; + t.Width = width; + t.Height = height; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = format; + t.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + + auto status = device->CreateTexture2D(&t, &data, &img->texture); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create img buf texture [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + if(init_rt(device.get(), img->input_res, img->scene_rt, width, height, format, img->texture.get())) { + return nullptr; + } + + img->data = (std::uint8_t *)img->texture.get(); + + return img; +} + +int display_vram_t::dummy_img(platf::img_t *img_base) { + auto img = (img_d3d_t *)img_base; + + if(img->texture) { + return 0; + } + + img->row_pitch = width * 4; + auto dummy_data = std::make_unique(width * height); + D3D11_SUBRESOURCE_DATA data { + dummy_data.get(), + (UINT)img->row_pitch + }; + std::fill_n(dummy_data.get(), width * height, 0); + + D3D11_TEXTURE2D_DESC t {}; + t.Width = width; + t.Height = height; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = format; + t.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + dxgi::texture2d_t tex; + auto status = device->CreateTexture2D(&t, &data, &tex); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create dummy texture [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + img->texture = std::move(tex); + img->data = (std::uint8_t *)img->texture.get(); + + return 0; +} + +std::shared_ptr display_vram_t::make_hwdevice(pix_fmt_e pix_fmt) { + if(pix_fmt != platf::pix_fmt_e::nv12) { + BOOST_LOG(error) << "display_vram_t doesn't support pixel format ["sv << from_pix_fmt(pix_fmt) << ']'; + + return nullptr; + } + + auto hwdevice = std::make_shared(); + + auto ret = hwdevice->init( + shared_from_this(), + device.get(), + device_ctx.get(), + pix_fmt); + + if(ret) { + return nullptr; + } + + return hwdevice; +} + +int init() { + BOOST_LOG(info) << "Compiling shaders..."sv; + scene_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/SceneVS.hlsl"); + if(!scene_vs_hlsl) { + return -1; + } + + convert_Y_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertYPS.hlsl"); + if(!convert_Y_ps_hlsl) { + return -1; + } + + convert_UV_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertUVPS.hlsl"); + if(!convert_UV_ps_hlsl) { + return -1; + } + + convert_UV_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/ConvertUVVS.hlsl"); + if(!convert_UV_vs_hlsl) { + return -1; + } + + scene_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ScenePS.hlsl"); + if(!scene_ps_hlsl) { + return -1; + } + BOOST_LOG(info) << "Compiled shaders"sv; + + return 0; +} } // namespace platf::dxgi \ No newline at end of file diff --git a/sunshine/platform/windows/input.cpp b/src/platform/windows/input.cpp old mode 100755 new mode 100644 similarity index 99% rename from sunshine/platform/windows/input.cpp rename to src/platform/windows/input.cpp index 88183f4b5fd..9ac01a26852 --- a/sunshine/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -5,9 +5,9 @@ #include #include "misc.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" +#include "src/config.h" +#include "src/main.h" +#include "src/platform/common.h" namespace platf { using namespace std::literals; diff --git a/sunshine/platform/windows/misc.cpp b/src/platform/windows/misc.cpp similarity index 95% rename from sunshine/platform/windows/misc.cpp rename to src/platform/windows/misc.cpp index 828e0374597..b9d085434a6 100644 --- a/sunshine/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -1,123 +1,123 @@ -#include -#include -#include - - -// prevent clang format from "optimizing" the header include order -// clang-format off -#include -#include -#include -#include -#include -// clang-format on - -#include "sunshine/main.h" -#include "sunshine/utility.h" - -using namespace std::literals; -namespace platf { -using adapteraddrs_t = util::c_ptr; - -std::filesystem::path appdata() { - return L"."sv; -} - -std::string from_sockaddr(const sockaddr *const socket_address) { - char data[INET6_ADDRSTRLEN]; - - auto family = socket_address->sa_family; - if(family == AF_INET6) { - inet_ntop(AF_INET6, &((sockaddr_in6 *)socket_address)->sin6_addr, data, INET6_ADDRSTRLEN); - } - - if(family == AF_INET) { - inet_ntop(AF_INET, &((sockaddr_in *)socket_address)->sin_addr, data, INET_ADDRSTRLEN); - } - - return std::string { data }; -} - -std::pair from_sockaddr_ex(const sockaddr *const ip_addr) { - char data[INET6_ADDRSTRLEN]; - - auto family = ip_addr->sa_family; - std::uint16_t port; - if(family == AF_INET6) { - inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN); - port = ((sockaddr_in6 *)ip_addr)->sin6_port; - } - - if(family == AF_INET) { - inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data, INET_ADDRSTRLEN); - port = ((sockaddr_in *)ip_addr)->sin_port; - } - - return { port, std::string { data } }; -} - -adapteraddrs_t get_adapteraddrs() { - adapteraddrs_t info { nullptr }; - ULONG size = 0; - - while(GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, info.get(), &size) == ERROR_BUFFER_OVERFLOW) { - info.reset((PIP_ADAPTER_ADDRESSES)malloc(size)); - } - - return info; -} - -std::string get_mac_address(const std::string_view &address) { - adapteraddrs_t info = get_adapteraddrs(); - for(auto adapter_pos = info.get(); adapter_pos != nullptr; adapter_pos = adapter_pos->Next) { - for(auto addr_pos = adapter_pos->FirstUnicastAddress; addr_pos != nullptr; addr_pos = addr_pos->Next) { - if(adapter_pos->PhysicalAddressLength != 0 && address == from_sockaddr(addr_pos->Address.lpSockaddr)) { - std::stringstream mac_addr; - mac_addr << std::hex; - for(int i = 0; i < adapter_pos->PhysicalAddressLength; i++) { - if(i > 0) { - mac_addr << ':'; - } - mac_addr << std::setw(2) << std::setfill('0') << (int)adapter_pos->PhysicalAddress[i]; - } - return mac_addr.str(); - } - } - } - BOOST_LOG(warning) << "Unable to find MAC address for "sv << address; - return "00:00:00:00:00:00"s; -} - -HDESK syncThreadDesktop() { - auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL); - if(!hDesk) { - auto err = GetLastError(); - BOOST_LOG(error) << "Failed to Open Input Desktop [0x"sv << util::hex(err).to_string_view() << ']'; - - return nullptr; - } - - if(!SetThreadDesktop(hDesk)) { - auto err = GetLastError(); - BOOST_LOG(error) << "Failed to sync desktop to thread [0x"sv << util::hex(err).to_string_view() << ']'; - } - - CloseDesktop(hDesk); - - return hDesk; -} - -void print_status(const std::string_view &prefix, HRESULT status) { - char err_string[1024]; - - DWORD bytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, - status, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - err_string, - sizeof(err_string), - nullptr); - - BOOST_LOG(error) << prefix << ": "sv << std::string_view { err_string, bytes }; -} +#include +#include +#include + + +// prevent clang format from "optimizing" the header include order +// clang-format off +#include +#include +#include +#include +#include +// clang-format on + +#include "src/main.h" +#include "src/utility.h" + +using namespace std::literals; +namespace platf { +using adapteraddrs_t = util::c_ptr; + +std::filesystem::path appdata() { + return L"."sv; +} + +std::string from_sockaddr(const sockaddr *const socket_address) { + char data[INET6_ADDRSTRLEN]; + + auto family = socket_address->sa_family; + if(family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6 *)socket_address)->sin6_addr, data, INET6_ADDRSTRLEN); + } + + if(family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in *)socket_address)->sin_addr, data, INET_ADDRSTRLEN); + } + + return std::string { data }; +} + +std::pair from_sockaddr_ex(const sockaddr *const ip_addr) { + char data[INET6_ADDRSTRLEN]; + + auto family = ip_addr->sa_family; + std::uint16_t port; + if(family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN); + port = ((sockaddr_in6 *)ip_addr)->sin6_port; + } + + if(family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data, INET_ADDRSTRLEN); + port = ((sockaddr_in *)ip_addr)->sin_port; + } + + return { port, std::string { data } }; +} + +adapteraddrs_t get_adapteraddrs() { + adapteraddrs_t info { nullptr }; + ULONG size = 0; + + while(GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, info.get(), &size) == ERROR_BUFFER_OVERFLOW) { + info.reset((PIP_ADAPTER_ADDRESSES)malloc(size)); + } + + return info; +} + +std::string get_mac_address(const std::string_view &address) { + adapteraddrs_t info = get_adapteraddrs(); + for(auto adapter_pos = info.get(); adapter_pos != nullptr; adapter_pos = adapter_pos->Next) { + for(auto addr_pos = adapter_pos->FirstUnicastAddress; addr_pos != nullptr; addr_pos = addr_pos->Next) { + if(adapter_pos->PhysicalAddressLength != 0 && address == from_sockaddr(addr_pos->Address.lpSockaddr)) { + std::stringstream mac_addr; + mac_addr << std::hex; + for(int i = 0; i < adapter_pos->PhysicalAddressLength; i++) { + if(i > 0) { + mac_addr << ':'; + } + mac_addr << std::setw(2) << std::setfill('0') << (int)adapter_pos->PhysicalAddress[i]; + } + return mac_addr.str(); + } + } + } + BOOST_LOG(warning) << "Unable to find MAC address for "sv << address; + return "00:00:00:00:00:00"s; +} + +HDESK syncThreadDesktop() { + auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL); + if(!hDesk) { + auto err = GetLastError(); + BOOST_LOG(error) << "Failed to Open Input Desktop [0x"sv << util::hex(err).to_string_view() << ']'; + + return nullptr; + } + + if(!SetThreadDesktop(hDesk)) { + auto err = GetLastError(); + BOOST_LOG(error) << "Failed to sync desktop to thread [0x"sv << util::hex(err).to_string_view() << ']'; + } + + CloseDesktop(hDesk); + + return hDesk; +} + +void print_status(const std::string_view &prefix, HRESULT status) { + char err_string[1024]; + + DWORD bytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + status, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + err_string, + sizeof(err_string), + nullptr); + + BOOST_LOG(error) << prefix << ": "sv << std::string_view { err_string, bytes }; +} } // namespace platf \ No newline at end of file diff --git a/sunshine/platform/windows/misc.h b/src/platform/windows/misc.h similarity index 95% rename from sunshine/platform/windows/misc.h rename to src/platform/windows/misc.h index 4cd8791face..a045d23bd3b 100644 --- a/sunshine/platform/windows/misc.h +++ b/src/platform/windows/misc.h @@ -1,13 +1,13 @@ -#ifndef SUNSHINE_WINDOWS_MISC_H -#define SUNSHINE_WINDOWS_MISC_H - -#include -#include -#include - -namespace platf { -void print_status(const std::string_view &prefix, HRESULT status); -HDESK syncThreadDesktop(); -} // namespace platf - +#ifndef SUNSHINE_WINDOWS_MISC_H +#define SUNSHINE_WINDOWS_MISC_H + +#include +#include +#include + +namespace platf { +void print_status(const std::string_view &prefix, HRESULT status); +HDESK syncThreadDesktop(); +} // namespace platf + #endif \ No newline at end of file diff --git a/sunshine/platform/windows/publish.cpp b/src/platform/windows/publish.cpp similarity index 92% rename from sunshine/platform/windows/publish.cpp rename to src/platform/windows/publish.cpp index 7fdef3fc994..92673e1de77 100644 --- a/sunshine/platform/windows/publish.cpp +++ b/src/platform/windows/publish.cpp @@ -1,195 +1,195 @@ -#include - -#include - -#include -#include - -#include - -#include "misc.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/network.h" -#include "sunshine/nvhttp.h" -#include "sunshine/platform/common.h" -#include "sunshine/thread_safe.h" - -#define _FN(x, ret, args) \ - typedef ret(*x##_fn) args; \ - static x##_fn x - -using namespace std::literals; - -#define __SV(quote) L##quote##sv -#define SV(quote) __SV(quote) - -extern "C" { -#ifndef __MINGW32__ -constexpr auto DNS_REQUEST_PENDING = 9506L; -constexpr auto DNS_QUERY_REQUEST_VERSION1 = 0x1; -constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1; -#endif - -#define SERVICE_DOMAIN "local" - -constexpr auto SERVICE_INSTANCE_NAME = SV(SERVICE_NAME "." SERVICE_TYPE "." SERVICE_DOMAIN); -constexpr auto SERVICE_TYPE_DOMAIN = SV(SERVICE_TYPE "." SERVICE_DOMAIN); - -#ifndef __MINGW32__ -typedef struct _DNS_SERVICE_INSTANCE { - LPWSTR pszInstanceName; - LPWSTR pszHostName; - - IP4_ADDRESS *ip4Address; - IP6_ADDRESS *ip6Address; - - WORD wPort; - WORD wPriority; - WORD wWeight; - - // Property list - DWORD dwPropertyCount; - - PWSTR *keys; - PWSTR *values; - - DWORD dwInterfaceIndex; -} DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE; -#endif - -typedef VOID WINAPI DNS_SERVICE_REGISTER_COMPLETE( - _In_ DWORD Status, - _In_ PVOID pQueryContext, - _In_ PDNS_SERVICE_INSTANCE pInstance); - -typedef DNS_SERVICE_REGISTER_COMPLETE *PDNS_SERVICE_REGISTER_COMPLETE; - -#ifndef __MINGW32__ -typedef struct _DNS_SERVICE_CANCEL { - PVOID reserved; -} DNS_SERVICE_CANCEL, *PDNS_SERVICE_CANCEL; - -typedef struct _DNS_SERVICE_REGISTER_REQUEST { - ULONG Version; - ULONG InterfaceIndex; - PDNS_SERVICE_INSTANCE pServiceInstance; - PDNS_SERVICE_REGISTER_COMPLETE pRegisterCompletionCallback; - PVOID pQueryContext; - HANDLE hCredentials; - BOOL unicastEnabled; -} DNS_SERVICE_REGISTER_REQUEST, *PDNS_SERVICE_REGISTER_REQUEST; -#endif - -_FN(_DnsServiceFreeInstance, VOID, (_In_ PDNS_SERVICE_INSTANCE pInstance)); -_FN(_DnsServiceDeRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel)); -_FN(_DnsServiceRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel)); -} /* extern "C" */ - -namespace platf::publish { -VOID WINAPI register_cb(DWORD status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) { - auto alarm = (safe::alarm_t::element_type *)pQueryContext; - - auto fg = util::fail_guard([&]() { - if(pInstance) { - _DnsServiceFreeInstance(pInstance); - } - }); - - if(status) { - print_status("register_cb()"sv, status); - alarm->ring(-1); - - return; - } - - alarm->ring(0); -} - -static int service(bool enable) { - auto alarm = safe::make_alarm(); - - std::wstring_convert, wchar_t> converter; - - std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() }; - std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() }; - - auto host = converter.from_bytes(boost::asio::ip::host_name() + ".local"); - - DNS_SERVICE_INSTANCE instance {}; - instance.pszInstanceName = name.data(); - instance.wPort = map_port(nvhttp::PORT_HTTP); - instance.pszHostName = host.data(); - - DNS_SERVICE_REGISTER_REQUEST req {}; - req.Version = DNS_QUERY_REQUEST_VERSION1; - req.pQueryContext = alarm.get(); - req.pServiceInstance = &instance; - req.pRegisterCompletionCallback = register_cb; - - DNS_STATUS status {}; - - if(enable) { - status = _DnsServiceRegister(&req, nullptr); - } - else { - status = _DnsServiceDeRegister(&req, nullptr); - } - - alarm->wait(); - - status = *alarm->status(); - if(status) { - BOOST_LOG(error) << "No mDNS service"sv; - return -1; - } - - return 0; -} - -class deinit_t : public ::platf::deinit_t { -public: - ~deinit_t() override { - if(service(false)) { - std::abort(); - } - - BOOST_LOG(info) << "Unregistered Sunshine Gamestream service"sv; - } -}; - -int load_funcs(HMODULE handle) { - auto fg = util::fail_guard([handle]() { - FreeLibrary(handle); - }); - - _DnsServiceFreeInstance = (_DnsServiceFreeInstance_fn)GetProcAddress(handle, "DnsServiceFreeInstance"); - _DnsServiceDeRegister = (_DnsServiceDeRegister_fn)GetProcAddress(handle, "DnsServiceDeRegister"); - _DnsServiceRegister = (_DnsServiceRegister_fn)GetProcAddress(handle, "DnsServiceRegister"); - - if(!(_DnsServiceFreeInstance && _DnsServiceDeRegister && _DnsServiceRegister)) { - BOOST_LOG(error) << "mDNS service not available in dnsapi.dll"sv; - return -1; - } - - fg.disable(); - return 0; -} - -std::unique_ptr<::platf::deinit_t> start() { - HMODULE handle = LoadLibrary("dnsapi.dll"); - - if(!handle || load_funcs(handle)) { - BOOST_LOG(error) << "Couldn't load dnsapi.dll, You'll need to add PC manually from Moonlight"sv; - return nullptr; - } - - if(service(true)) { - return nullptr; - } - - BOOST_LOG(info) << "Registered Sunshine Gamestream service"sv; - - return std::make_unique(); -} -} // namespace platf::publish +#include + +#include + +#include +#include + +#include + +#include "misc.h" +#include "src/config.h" +#include "src/main.h" +#include "src/network.h" +#include "src/nvhttp.h" +#include "src/platform/common.h" +#include "src/thread_safe.h" + +#define _FN(x, ret, args) \ + typedef ret(*x##_fn) args; \ + static x##_fn x + +using namespace std::literals; + +#define __SV(quote) L##quote##sv +#define SV(quote) __SV(quote) + +extern "C" { +#ifndef __MINGW32__ +constexpr auto DNS_REQUEST_PENDING = 9506L; +constexpr auto DNS_QUERY_REQUEST_VERSION1 = 0x1; +constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1; +#endif + +#define SERVICE_DOMAIN "local" + +constexpr auto SERVICE_INSTANCE_NAME = SV(SERVICE_NAME "." SERVICE_TYPE "." SERVICE_DOMAIN); +constexpr auto SERVICE_TYPE_DOMAIN = SV(SERVICE_TYPE "." SERVICE_DOMAIN); + +#ifndef __MINGW32__ +typedef struct _DNS_SERVICE_INSTANCE { + LPWSTR pszInstanceName; + LPWSTR pszHostName; + + IP4_ADDRESS *ip4Address; + IP6_ADDRESS *ip6Address; + + WORD wPort; + WORD wPriority; + WORD wWeight; + + // Property list + DWORD dwPropertyCount; + + PWSTR *keys; + PWSTR *values; + + DWORD dwInterfaceIndex; +} DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE; +#endif + +typedef VOID WINAPI DNS_SERVICE_REGISTER_COMPLETE( + _In_ DWORD Status, + _In_ PVOID pQueryContext, + _In_ PDNS_SERVICE_INSTANCE pInstance); + +typedef DNS_SERVICE_REGISTER_COMPLETE *PDNS_SERVICE_REGISTER_COMPLETE; + +#ifndef __MINGW32__ +typedef struct _DNS_SERVICE_CANCEL { + PVOID reserved; +} DNS_SERVICE_CANCEL, *PDNS_SERVICE_CANCEL; + +typedef struct _DNS_SERVICE_REGISTER_REQUEST { + ULONG Version; + ULONG InterfaceIndex; + PDNS_SERVICE_INSTANCE pServiceInstance; + PDNS_SERVICE_REGISTER_COMPLETE pRegisterCompletionCallback; + PVOID pQueryContext; + HANDLE hCredentials; + BOOL unicastEnabled; +} DNS_SERVICE_REGISTER_REQUEST, *PDNS_SERVICE_REGISTER_REQUEST; +#endif + +_FN(_DnsServiceFreeInstance, VOID, (_In_ PDNS_SERVICE_INSTANCE pInstance)); +_FN(_DnsServiceDeRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel)); +_FN(_DnsServiceRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel)); +} /* extern "C" */ + +namespace platf::publish { +VOID WINAPI register_cb(DWORD status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) { + auto alarm = (safe::alarm_t::element_type *)pQueryContext; + + auto fg = util::fail_guard([&]() { + if(pInstance) { + _DnsServiceFreeInstance(pInstance); + } + }); + + if(status) { + print_status("register_cb()"sv, status); + alarm->ring(-1); + + return; + } + + alarm->ring(0); +} + +static int service(bool enable) { + auto alarm = safe::make_alarm(); + + std::wstring_convert, wchar_t> converter; + + std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() }; + std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() }; + + auto host = converter.from_bytes(boost::asio::ip::host_name() + ".local"); + + DNS_SERVICE_INSTANCE instance {}; + instance.pszInstanceName = name.data(); + instance.wPort = map_port(nvhttp::PORT_HTTP); + instance.pszHostName = host.data(); + + DNS_SERVICE_REGISTER_REQUEST req {}; + req.Version = DNS_QUERY_REQUEST_VERSION1; + req.pQueryContext = alarm.get(); + req.pServiceInstance = &instance; + req.pRegisterCompletionCallback = register_cb; + + DNS_STATUS status {}; + + if(enable) { + status = _DnsServiceRegister(&req, nullptr); + } + else { + status = _DnsServiceDeRegister(&req, nullptr); + } + + alarm->wait(); + + status = *alarm->status(); + if(status) { + BOOST_LOG(error) << "No mDNS service"sv; + return -1; + } + + return 0; +} + +class deinit_t : public ::platf::deinit_t { +public: + ~deinit_t() override { + if(service(false)) { + std::abort(); + } + + BOOST_LOG(info) << "Unregistered Sunshine Gamestream service"sv; + } +}; + +int load_funcs(HMODULE handle) { + auto fg = util::fail_guard([handle]() { + FreeLibrary(handle); + }); + + _DnsServiceFreeInstance = (_DnsServiceFreeInstance_fn)GetProcAddress(handle, "DnsServiceFreeInstance"); + _DnsServiceDeRegister = (_DnsServiceDeRegister_fn)GetProcAddress(handle, "DnsServiceDeRegister"); + _DnsServiceRegister = (_DnsServiceRegister_fn)GetProcAddress(handle, "DnsServiceRegister"); + + if(!(_DnsServiceFreeInstance && _DnsServiceDeRegister && _DnsServiceRegister)) { + BOOST_LOG(error) << "mDNS service not available in dnsapi.dll"sv; + return -1; + } + + fg.disable(); + return 0; +} + +std::unique_ptr<::platf::deinit_t> start() { + HMODULE handle = LoadLibrary("dnsapi.dll"); + + if(!handle || load_funcs(handle)) { + BOOST_LOG(error) << "Couldn't load dnsapi.dll, You'll need to add PC manually from Moonlight"sv; + return nullptr; + } + + if(service(true)) { + return nullptr; + } + + BOOST_LOG(info) << "Registered Sunshine Gamestream service"sv; + + return std::make_unique(); +} +} // namespace platf::publish diff --git a/sunshine/platform/windows/windows.rs.in b/src/platform/windows/windows.rs.in similarity index 100% rename from sunshine/platform/windows/windows.rs.in rename to src/platform/windows/windows.rs.in diff --git a/sunshine/process.cpp b/src/process.cpp similarity index 100% rename from sunshine/process.cpp rename to src/process.cpp diff --git a/sunshine/process.h b/src/process.h similarity index 100% rename from sunshine/process.h rename to src/process.h diff --git a/sunshine/round_robin.h b/src/round_robin.h similarity index 100% rename from sunshine/round_robin.h rename to src/round_robin.h diff --git a/sunshine/rtsp.cpp b/src/rtsp.cpp similarity index 100% rename from sunshine/rtsp.cpp rename to src/rtsp.cpp diff --git a/sunshine/rtsp.h b/src/rtsp.h similarity index 100% rename from sunshine/rtsp.h rename to src/rtsp.h diff --git a/sunshine/stream.cpp b/src/stream.cpp similarity index 100% rename from sunshine/stream.cpp rename to src/stream.cpp diff --git a/sunshine/stream.h b/src/stream.h similarity index 100% rename from sunshine/stream.h rename to src/stream.h diff --git a/sunshine/sync.h b/src/sync.h similarity index 100% rename from sunshine/sync.h rename to src/sync.h diff --git a/sunshine/task_pool.h b/src/task_pool.h similarity index 100% rename from sunshine/task_pool.h rename to src/task_pool.h diff --git a/sunshine/thread_pool.h b/src/thread_pool.h similarity index 100% rename from sunshine/thread_pool.h rename to src/thread_pool.h diff --git a/sunshine/thread_safe.h b/src/thread_safe.h similarity index 100% rename from sunshine/thread_safe.h rename to src/thread_safe.h diff --git a/sunshine/upnp.cpp b/src/upnp.cpp similarity index 100% rename from sunshine/upnp.cpp rename to src/upnp.cpp diff --git a/sunshine/upnp.h b/src/upnp.h similarity index 100% rename from sunshine/upnp.h rename to src/upnp.h diff --git a/sunshine/utility.h b/src/utility.h similarity index 100% rename from sunshine/utility.h rename to src/utility.h diff --git a/sunshine/uuid.h b/src/uuid.h similarity index 100% rename from sunshine/uuid.h rename to src/uuid.h diff --git a/sunshine/video.cpp b/src/video.cpp similarity index 100% rename from sunshine/video.cpp rename to src/video.cpp diff --git a/sunshine/video.h b/src/video.h similarity index 100% rename from sunshine/video.h rename to src/video.h diff --git a/src_assets/common/assets/web/header.html b/src_assets/common/assets/web/header.html index 336a609dc52..3957a994430 100644 --- a/src_assets/common/assets/web/header.html +++ b/src_assets/common/assets/web/header.html @@ -68,4 +68,13 @@ .nav-link.active { font-weight: 500; } - \ No newline at end of file + + + + diff --git a/src_assets/common/assets/web/index.html b/src_assets/common/assets/web/index.html index 3273ec5d8b9..55b41947ba9 100644 --- a/src_assets/common/assets/web/index.html +++ b/src_assets/common/assets/web/index.html @@ -9,10 +9,13 @@

Resources

Resources for Sunshine!

- Official Website - Github Discussions - Sunshine Discord - Moonlight Discord + @@ -23,8 +26,12 @@

Legal

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

- License - Third Party Notice + diff --git a/src_assets/common/config/sunshine.conf b/src_assets/common/config/sunshine.conf index 04d51c2af1d..58dd670b003 100644 --- a/src_assets/common/config/sunshine.conf +++ b/src_assets/common/config/sunshine.conf @@ -1 +1 @@ -# See our documentation at https://sunshinestream.readthedocs.io/en/latest/about/advanced_usage.html +# See our documentation at https://docs.lizardbyte.dev/projects/sunshine diff --git a/src_assets/linux/misc/85-sunshine-rules.rules b/src_assets/linux/misc/85-sunshine.rules similarity index 100% rename from src_assets/linux/misc/85-sunshine-rules.rules rename to src_assets/linux/misc/85-sunshine.rules diff --git a/src_assets/macos/assets/Info.plist b/src_assets/macos/assets/Info.plist index c849435adc4..0cd880953bf 100644 --- a/src_assets/macos/assets/Info.plist +++ b/src_assets/macos/assets/Info.plist @@ -3,7 +3,7 @@ CFBundleIdentifier - com.github.sunshinestream.sunshine + dev.lizardbyte.sunshine CFBundleName Sunshine NSMicrophoneUsageDescription diff --git a/src_assets/macos/misc/uninstall_pkg.sh b/src_assets/macos/misc/uninstall_pkg.sh new file mode 100644 index 00000000000..7e136109f64 --- /dev/null +++ b/src_assets/macos/misc/uninstall_pkg.sh @@ -0,0 +1,68 @@ +#!/bin/bash -e +set -e + +package_name=org.macports.Sunshine + +echo "Removing files now..." +FILES=$(pkgutil --files $package_name --only-files) + +remove_config=True +remove_apps=True + +for file in ${FILES}; do + file="/$file" + remove_current=True + if [[ $file == *sunshine.conf ]]; then + if [[ $remove_config == True ]]; then + while true; do + read -p -r "Do you wish to remove 'sunshine.conf'?" yn + case $yn in + [Yy]* ) echo "removing: $file"; rm -f "$file"; break;; + [Nn]* ) remove_config=False; remove_current=False; break;; + * ) echo "Please answer yes or no.";; + esac + done + fi + fi + if [[ $file == *apps.json ]]; then + if [[ $remove_apps == True ]]; then + while true; do + read -p -r "Do you wish to remove 'apps.conf'?" yn + case $yn in + [Yy]* ) echo "removing: $file"; rm -f "$file"; break;; + [Nn]* ) remove_apps=False; remove_current=False; break;; + * ) echo "Please answer yes or no.";; + esac + done + fi + fi + + if [[ $remove_current == True ]]; then + echo "removing: $file" + rm -f "$file" + fi +done + +echo "Removing directories now..." +DIRECTORIES=$(pkgutil --files org.macports.Sunshine --only-dirs) + +for dir in ${DIRECTORIES}; do + dir="/$dir" + echo "Checking if empty directory: $dir" + + # check if directory is empty... could just use ${DIRECTORIES} here if pkgutils added the `/` prefix + empty_dir=$(find "$dir" -depth 0 -type d -empty) + + # remove the directory if it is empty + if [[ $empty_dir != "" ]]; then # prevent the loop from running and failing if no directories found + for i in "${empty_dir}"; do # don't split words as we already know this will be a single directory + echo "Removing empty directory: ${i}" + rmdir "${i}" + done + fi +done + +echo "Forgetting Sunshine..." +pkgutil --forget $package_name + +echo "Sunshine has been uninstalled..." diff --git a/sunshine/platform/macos/TPCircularBuffer b/third-party/TPCircularBuffer similarity index 100% rename from sunshine/platform/macos/TPCircularBuffer rename to third-party/TPCircularBuffer diff --git a/tools/audio.cpp b/tools/audio.cpp index a130d384e73..592170b75c3 100644 --- a/tools/audio.cpp +++ b/tools/audio.cpp @@ -14,7 +14,7 @@ #include -#include "sunshine/utility.h" +#include "src/utility.h" DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING diff --git a/tools/dxgi.cpp b/tools/dxgi.cpp index 9c5f7307d56..7ecaaa2f5af 100644 --- a/tools/dxgi.cpp +++ b/tools/dxgi.cpp @@ -7,7 +7,7 @@ #include -#include "sunshine/utility.h" +#include "src/utility.h" using namespace std::literals; namespace dxgi {