From eeef57b168247151306fe419519bdb9b1eed5c13 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 30 May 2025 22:35:54 -0400 Subject: [PATCH 01/67] ci(windows): change runner from windows-2019 to windows-2022 (#3928) --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e1171a27..9df09f6d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -742,7 +742,7 @@ jobs: build_win: name: Windows needs: setup_release - runs-on: windows-2019 + runs-on: windows-2022 steps: - name: Checkout uses: actions/checkout@v4 From a857c8b7154491c0770b4c38c96463d4ce95419e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 23:21:11 -0400 Subject: [PATCH 02/67] build(deps): bump third-party/build-deps from `d60197e` to `a51a06a` (#3927) Bumps [third-party/build-deps](https://github.com/LizardByte/build-deps) from `d60197e` to `a51a06a`. - [Commits](https://github.com/LizardByte/build-deps/compare/d60197e1543d63cc415ebe0225afd47025a819e2...a51a06adf774c51f6a3916b4fe10ca7d436690d3) --- updated-dependencies: - dependency-name: third-party/build-deps dependency-version: a51a06adf774c51f6a3916b4fe10ca7d436690d3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- third-party/build-deps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/build-deps b/third-party/build-deps index d60197e1..a51a06ad 160000 --- a/third-party/build-deps +++ b/third-party/build-deps @@ -1 +1 @@ -Subproject commit d60197e1543d63cc415ebe0225afd47025a819e2 +Subproject commit a51a06adf774c51f6a3916b4fe10ca7d436690d3 From 0de8cc864c2790196142b330735c74e434a754dd Mon Sep 17 00:00:00 2001 From: activatekillswitch Date: Sat, 31 May 2025 14:55:49 +0100 Subject: [PATCH 03/67] build(linux): add Ubuntu 25.04 support to linux_build.sh (#3919) Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> --- scripts/linux_build.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/linux_build.sh b/scripts/linux_build.sh index 1ac5f162..b391f7dc 100755 --- a/scripts/linux_build.sh +++ b/scripts/linux_build.sh @@ -564,6 +564,15 @@ elif grep -q "Ubuntu 24.04" /etc/os-release; then cuda_build="520.61.05" gcc_version="11" nvm_node=0 +elif grep -q "Ubuntu 25.04" /etc/os-release; then + distro="ubuntu" + version="25.04" + package_update_command="${sudo_cmd} apt-get update" + package_install_command="${sudo_cmd} apt-get install -y" + cuda_version="11.8.0" + cuda_build="520.61.05" + gcc_version="11" + nvm_node=0 else echo "Unsupported Distro or Version" exit 1 From 1938385bf058caf030292b48a468368f0264b806 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 7 Jun 2025 16:29:45 -0400 Subject: [PATCH 04/67] build(windows): fix gcc15 compatibility (#3946) --- .github/workflows/CI.yml | 71 +++++++++++++++---------- cmake/compile_definitions/windows.cmake | 4 ++ tools/CMakeLists.txt | 1 + tools/audio.cpp | 12 +++-- 4 files changed, 55 insertions(+), 33 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 9df09f6d..418e3133 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -864,48 +864,61 @@ jobs: - name: Update Windows dependencies env: - gcc_version: "14.2.0-3" + MSYSTEM: "ucrt64" + TOOLCHAIN: "ucrt-x86_64" shell: msys2 {0} run: | - broken_deps=( - "mingw-w64-ucrt-x86_64-gcc" - "mingw-w64-ucrt-x86_64-gcc-libs" + # variables + declare -A pinned_deps + + # dependencies + dependencies=( + "git" + "mingw-w64-${TOOLCHAIN}-cmake" + "mingw-w64-${TOOLCHAIN}-cppwinrt" + "mingw-w64-${TOOLCHAIN}-curl-winssl" + "mingw-w64-${TOOLCHAIN}-gcc" + "mingw-w64-${TOOLCHAIN}-graphviz" + "mingw-w64-${TOOLCHAIN}-MinHook" + "mingw-w64-${TOOLCHAIN}-miniupnpc" + "mingw-w64-${TOOLCHAIN}-nlohmann-json" + "mingw-w64-${TOOLCHAIN}-nodejs" + "mingw-w64-${TOOLCHAIN}-nsis" + "mingw-w64-${TOOLCHAIN}-onevpl" + "mingw-w64-${TOOLCHAIN}-openssl" + "mingw-w64-${TOOLCHAIN}-opus" + "mingw-w64-${TOOLCHAIN}-toolchain" ) - tarballs="" - for dep in "${broken_deps[@]}"; do - tarball="${dep}-${gcc_version}-any.pkg.tar.zst" + # do not modify below this line - # download and install working version - wget https://repo.msys2.org/mingw/ucrt64/${tarball} + ignore_packages=() + tarballs="" + for pkg in "${!pinned_deps[@]}"; do + ignore_packages+=("${pkg}") + version="${pinned_deps[$pkg]}" + tarball="${pkg}-${version}-any.pkg.tar.zst" + + # download working version + wget "https://repo.msys2.org/mingw/${MSYSTEM}/${tarball}" tarballs="${tarballs} ${tarball}" done - # install broken dependencies + # Create the ignore string for pacman + ignore_list=$(IFS=,; echo "${ignore_packages[*]}") + + # install pinned dependencies if [ -n "$tarballs" ]; then pacman -U --noconfirm ${tarballs} fi - # install dependencies - dependencies=( - "git" - "mingw-w64-ucrt-x86_64-cmake" - "mingw-w64-ucrt-x86_64-cppwinrt" - "mingw-w64-ucrt-x86_64-curl-winssl" - "mingw-w64-ucrt-x86_64-graphviz" - "mingw-w64-ucrt-x86_64-MinHook" - "mingw-w64-ucrt-x86_64-miniupnpc" - "mingw-w64-ucrt-x86_64-nlohmann-json" - "mingw-w64-ucrt-x86_64-nodejs" - "mingw-w64-ucrt-x86_64-nsis" - "mingw-w64-ucrt-x86_64-onevpl" - "mingw-w64-ucrt-x86_64-openssl" - "mingw-w64-ucrt-x86_64-opus" - "mingw-w64-ucrt-x86_64-toolchain" - ) - - pacman -Syu --noconfirm --ignore="$(IFS=,; echo "${broken_deps[*]}")" "${dependencies[@]}" + # Only add --ignore if we have packages to ignore + if [ -n "$ignore_list" ]; then + pacman -Syu --noconfirm --ignore="${ignore_list}" "${dependencies[@]}" + else + pacman -Syu --noconfirm "${dependencies[@]}" + fi - name: Install Doxygen # GCC compiled doxygen has issues when running graphviz diff --git a/cmake/compile_definitions/windows.cmake b/cmake/compile_definitions/windows.cmake index a3009be0..d40fb0fa 100644 --- a/cmake/compile_definitions/windows.cmake +++ b/cmake/compile_definitions/windows.cmake @@ -9,6 +9,10 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") # gcc complains about misleading indentation in some mingw includes list(APPEND SUNSHINE_COMPILE_OPTIONS -Wno-misleading-indentation) +# gcc15 complains about non-template type 'coroutine_handle' used as a template in Windows.Foundation.h +# can remove after https://gcc.gnu.org/bugzilla/show_bug.cgi?id=120495 is available in mingw-w64 +list(APPEND SUNSHINE_COMPILE_OPTIONS -Wno-template-body) + # see gcc bug 98723 add_definitions(-DUSE_BOOST_REGEX) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 8d4c08e3..59651565 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -15,6 +15,7 @@ target_compile_options(dxgi-info PRIVATE ${SUNSHINE_COMPILE_OPTIONS}) add_executable(audio-info audio.cpp) set_target_properties(audio-info PROPERTIES CXX_STANDARD 20) target_link_libraries(audio-info + ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ksuser ${PLATFORM_LIBRARIES}) diff --git a/tools/audio.cpp b/tools/audio.cpp index 2fe4a7eb..c5ca506f 100644 --- a/tools/audio.cpp +++ b/tools/audio.cpp @@ -3,8 +3,8 @@ * @brief Handles collecting audio device information from Windows. */ #define INITGUID -#include "src/utility.h" +// platform includes #include #include #include @@ -13,6 +13,12 @@ #include #include +// lib includes +#include + +// local includes +#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 DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2); @@ -44,8 +50,6 @@ namespace audio { using handle_t = util::safe_ptr_v2; - static std::wstring_convert, wchar_t> converter; - class prop_var_t { public: prop_var_t() { @@ -204,7 +208,7 @@ namespace audio { // so we can take the first match as the current format to display. auto audio_client = make_audio_client(device, format); if (audio_client) { - current_format = converter.from_bytes(format.name.data()); + current_format = boost::locale::conv::utf_to_utf(format.name.data()); break; } } From a80ec754658fc19dd55243619dc41bcb36fda2b6 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 7 Jun 2025 17:19:04 -0400 Subject: [PATCH 05/67] ci(codeql): use no build mode (#3943) --- .codeql-prebuild-cpp-Linux.sh | 11 ----- .codeql-prebuild-cpp-Windows.sh | 59 ----------------------- .codeql-prebuild-cpp-macOS.sh | 32 ------------- .github/workflows/codeql.yml | 85 ++++++++++++++------------------- 4 files changed, 36 insertions(+), 151 deletions(-) delete mode 100644 .codeql-prebuild-cpp-Linux.sh delete mode 100644 .codeql-prebuild-cpp-Windows.sh delete mode 100644 .codeql-prebuild-cpp-macOS.sh diff --git a/.codeql-prebuild-cpp-Linux.sh b/.codeql-prebuild-cpp-Linux.sh deleted file mode 100644 index f0d03bb2..00000000 --- a/.codeql-prebuild-cpp-Linux.sh +++ /dev/null @@ -1,11 +0,0 @@ -# install dependencies for C++ analysis -set -e - -chmod +x ./scripts/linux_build.sh -./scripts/linux_build.sh --skip-package --ubuntu-test-repo - -# Delete CUDA -rm -rf ./build/cuda - -# skip autobuild -echo "skip_autobuild=true" >> "$GITHUB_OUTPUT" diff --git a/.codeql-prebuild-cpp-Windows.sh b/.codeql-prebuild-cpp-Windows.sh deleted file mode 100644 index b860c9e8..00000000 --- a/.codeql-prebuild-cpp-Windows.sh +++ /dev/null @@ -1,59 +0,0 @@ -# install dependencies for C++ analysis -set -e - -# update pacman -pacman --noconfirm -Syu - -gcc_version="14.2.0-3" - -broken_deps=( - "mingw-w64-ucrt-x86_64-gcc" - "mingw-w64-ucrt-x86_64-gcc-libs" -) - -tarballs="" -for dep in "${broken_deps[@]}"; do - tarball="${dep}-${gcc_version}-any.pkg.tar.zst" - - # download and install working version - wget https://repo.msys2.org/mingw/ucrt64/${tarball} - - tarballs="${tarballs} ${tarball}" -done - -# install broken dependencies -if [ -n "$tarballs" ]; then - pacman -U --noconfirm ${tarballs} -fi - -# install dependencies -dependencies=( - "git" - "mingw-w64-ucrt-x86_64-cmake" - "mingw-w64-ucrt-x86_64-cppwinrt" - "mingw-w64-ucrt-x86_64-curl-winssl" - "mingw-w64-ucrt-x86_64-MinHook" - "mingw-w64-ucrt-x86_64-miniupnpc" - "mingw-w64-ucrt-x86_64-nlohmann-json" - "mingw-w64-ucrt-x86_64-nodejs" - "mingw-w64-ucrt-x86_64-nsis" - "mingw-w64-ucrt-x86_64-onevpl" - "mingw-w64-ucrt-x86_64-openssl" - "mingw-w64-ucrt-x86_64-opus" - "mingw-w64-ucrt-x86_64-toolchain" -) - -pacman -Syu --noconfirm --ignore="$(IFS=,; echo "${broken_deps[*]}")" "${dependencies[@]}" - -# build -mkdir -p build -cmake \ - -B build \ - -G Ninja \ - -S . \ - -DBUILD_DOCS=OFF \ - -DBUILD_WERROR=ON -ninja -C build - -# skip autobuild -echo "skip_autobuild=true" >> "$GITHUB_OUTPUT" diff --git a/.codeql-prebuild-cpp-macOS.sh b/.codeql-prebuild-cpp-macOS.sh deleted file mode 100644 index a21a69c3..00000000 --- a/.codeql-prebuild-cpp-macOS.sh +++ /dev/null @@ -1,32 +0,0 @@ -# install dependencies for C++ analysis -set -e - -# setup homebrew for x86_64 -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -eval "$(/usr/local/bin/brew shellenv)" - -# install dependencies -dependencies=( - "cmake" - "miniupnpc" - "ninja" - "node" - "openssl@3" - "opus" - "pkg-config" -) -brew install "${dependencies[@]}" - -# build -mkdir -p build -cmake \ - -B build \ - -G Ninja \ - -S . \ - -DBOOST_USE_STATIC=OFF \ - -DBUILD_DOCS=OFF \ - -DBUILD_WERROR=ON -ninja -C build - -# skip autobuild -echo "skip_autobuild=true" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c9949dd3..24a4945e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -39,13 +39,23 @@ jobs: uses: actions/github-script@v7 with: script: | - // CodeQL supports ['cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift'] - // Use only 'java' to analyze code written in Java, Kotlin or both - // Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + // CodeQL supports the following: + // ['actions', 'c', 'cpp', 'csharp', 'go', 'java', 'javascript', 'kotlin', 'python', 'ruby', 'swift'] + // Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - const supported_languages = ['cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift'] + const supported_languages = [ + 'cpp', + 'csharp', + 'go', + 'java', + 'javascript', + 'python', + 'ruby', + 'swift', + ] const remap_languages = { + 'c': 'cpp', 'c++': 'cpp', 'c#': 'csharp', 'kotlin': 'java', @@ -73,7 +83,8 @@ jobs: "category": "/language:actions", "language": "actions", "name": "actions", - "os": "ubuntu-latest" + "os": "ubuntu-latest", + "build-mode": "none", }); } @@ -94,8 +105,6 @@ jobs: let osList = ['ubuntu-latest']; if (normalizedKey === 'swift') { osList = ['macos-latest']; - } else if (normalizedKey === 'cpp') { - osList = ['macos-latest', 'ubuntu-latest', 'windows-latest']; } for (let os of osList) { // set name for matrix @@ -103,8 +112,21 @@ jobs: // set category for matrix let category = `/language:${normalizedKey}` - if (normalizedKey === 'cpp') { - category = `/language:cpp-${os.split('-')[0]}` + let build_mode = 'none'; + + // Set build mode based on language + switch (normalizedKey) { + case 'csharp': + build_mode = 'autobuild' + break + case 'go': + build_mode = 'autobuild' + break + case 'java': + build_mode = 'autobuild' + break + default: + build_mode = 'none' } // add to matrix @@ -112,7 +134,8 @@ jobs: "category": category, "language": normalizedKey, "name": name, - "os": os + "os": os, + "build-mode": build_mode, }) } } @@ -140,9 +163,6 @@ jobs: analyze: name: Analyze (${{ matrix.name }}) if: needs.languages.outputs.continue == 'true' - defaults: - run: - shell: ${{ matrix.os == 'windows-latest' && 'msys2 {0}' || 'bash' }} env: GITHUB_CODEQL_BUILD: true needs: languages @@ -154,35 +174,13 @@ jobs: strategy: fail-fast: false matrix: ${{ fromJson(needs.languages.outputs.matrix) }} - timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 60 }} steps: - - name: Maximize build space - if: >- - runner.os == 'Linux' && - matrix.language == 'cpp' - uses: easimon/maximize-build-space@v10 - with: - root-reserve-mb: 30720 - remove-dotnet: ${{ (matrix.language == 'csharp' && 'false') || 'true' }} - remove-android: 'true' - remove-haskell: 'true' - remove-codeql: 'false' - remove-docker-images: 'true' - - name: Checkout repository uses: actions/checkout@v4 with: submodules: recursive - - name: Setup msys2 - if: >- - runner.os == 'Windows' && - matrix.language == 'cpp' - uses: msys2/setup-msys2@v2 - with: - msystem: ucrt64 - update: true - # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 @@ -200,22 +198,11 @@ jobs: - build - node_modules - third-party - - # Pre autobuild - # create a file named .codeql-prebuild-${{ matrix.language }}-${{ runner.os }}.sh in the root of your repository - - name: Prebuild - id: prebuild - run: | - # check if prebuild script exists - filename=".codeql-prebuild-${{ matrix.language }}-${{ runner.os }}.sh" - if [ -f "./${filename}" ]; then - echo "Running prebuild script: ${filename}" - ./${filename} - fi + build-mode: ${{ matrix.build-mode || 'none' }} # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). - name: Autobuild - if: steps.prebuild.outputs.skip_autobuild != 'true' + if: matrix.build-mode == 'autobuild' uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis From eaa434ab45ac2cfba1012019fbdb8ff20ee54365 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 7 Jun 2025 18:40:28 -0400 Subject: [PATCH 06/67] build(linux): disable cuda option in cmake when --skip-cuda arg is passed (#3951) --- scripts/linux_build.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/linux_build.sh b/scripts/linux_build.sh index b391f7dc..3b09eed5 100755 --- a/scripts/linux_build.sh +++ b/scripts/linux_build.sh @@ -386,7 +386,7 @@ function run_install() { "gcc-ranlib" ) - #set gcc version based on distros + #set gcc version based on distros if [ "$distro" == "arch" ]; then export CC=gcc-14 export CXX=g++-14 @@ -460,6 +460,8 @@ function run_install() { install_cuda cmake_args+=("-DSUNSHINE_ENABLE_CUDA=ON") cmake_args+=("-DCMAKE_CUDA_COMPILER:PATH=$nvcc_path") + else + cmake_args+=("-DSUNSHINE_ENABLE_CUDA=OFF") fi # Cmake stuff here From 0a98094613d8e9ee2c0759e6042197d675e9e474 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 7 Jun 2025 21:29:28 -0400 Subject: [PATCH 07/67] build(homebrew): temporarily use miniupnpc head (#3952) --- packaging/sunshine.rb | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/packaging/sunshine.rb b/packaging/sunshine.rb index f13a5a38..ce8c7e81 100644 --- a/packaging/sunshine.rb +++ b/packaging/sunshine.rb @@ -33,7 +33,6 @@ class @PROJECT_NAME@ < Formula depends_on "node" => :build depends_on "pkg-config" => :build depends_on "curl" - depends_on "miniupnpc" depends_on "openssl" depends_on "opus" depends_on "icu4c" => :recommended @@ -199,6 +198,11 @@ index 5b3638d..aca9481 100644 end end + resource "miniupnpc" do + url "https://github.com/miniupnp/miniupnp.git", + revision: "e263ab6f56c382e10fed31347ec68095d691a0e8" + end + def install ENV["BRANCH"] = "@GITHUB_BRANCH@" ENV["BUILD_VERSION"] = "@BUILD_VERSION@" @@ -301,6 +305,23 @@ index 5b3638d..aca9481 100644 end end + # Build miniupnpc + resource("miniupnpc").stage do + # Change to the miniupnpc directory within the repo + cd "miniupnpc" do + system "make", "INSTALLPREFIX=#{prefix}/miniupnpc", "install" + end + + # Copy the shared libraries to the main lib directory + # This ensures they're in the library search path at runtime + cp Dir["#{prefix}/miniupnpc/lib/libminiupnpc.so*"], "#{lib}/" if OS.linux? + + # Set include and library paths for the custom miniupnpc + ENV.prepend_path "PKG_CONFIG_PATH", "#{prefix}/miniupnpc/lib/pkgconfig" + ENV.prepend "CPPFLAGS", "-I#{prefix}/miniupnpc/include" + ENV.prepend "LDFLAGS", "-L#{prefix}/miniupnpc/lib" + end + system "cmake", "-S", ".", "-B", "build", "-G", "Unix Makefiles", *std_cmake_args, *args From e40b7cb73193ddaa490b20baaca47ea60e2d8339 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Jun 2025 02:14:28 +0000 Subject: [PATCH 08/67] build(deps): bump third-party/build-deps from `a51a06a` to `cf5dffa` (#3934) Bumps [third-party/build-deps](https://github.com/LizardByte/build-deps) from `a51a06a` to `cf5dffa`. - [Commits](https://github.com/LizardByte/build-deps/compare/a51a06adf774c51f6a3916b4fe10ca7d436690d3...cf5dffaf4c62a5e5e2949c37b642cfc5ad962b98) --- updated-dependencies: - dependency-name: third-party/build-deps dependency-version: cf5dffaf4c62a5e5e2949c37b642cfc5ad962b98 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- third-party/build-deps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/build-deps b/third-party/build-deps index a51a06ad..cf5dffaf 160000 --- a/third-party/build-deps +++ b/third-party/build-deps @@ -1 +1 @@ -Subproject commit a51a06adf774c51f6a3916b4fe10ca7d436690d3 +Subproject commit cf5dffaf4c62a5e5e2949c37b642cfc5ad962b98 From 1984d822be7fbe1e8f5acf5f21f21c82e60b7165 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Jun 2025 03:01:10 +0000 Subject: [PATCH 09/67] build(deps): bump third-party/inputtino from `17a9b9c` to `3a7a658` (#3925) Bumps [third-party/inputtino](https://github.com/games-on-whales/inputtino) from `17a9b9c` to `3a7a658`. - [Commits](https://github.com/games-on-whales/inputtino/compare/17a9b9ce85c6b8e711f777146d3c706c1a2a9fd9...3a7a658782217d77ee1d1055c3930874cfd299aa) --- updated-dependencies: - dependency-name: third-party/inputtino dependency-version: 3a7a658782217d77ee1d1055c3930874cfd299aa dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- third-party/inputtino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/inputtino b/third-party/inputtino index 17a9b9ce..3a7a6587 160000 --- a/third-party/inputtino +++ b/third-party/inputtino @@ -1 +1 @@ -Subproject commit 17a9b9ce85c6b8e711f777146d3c706c1a2a9fd9 +Subproject commit 3a7a658782217d77ee1d1055c3930874cfd299aa From 878437362e34b08f5b2224691957b6962f3ec09e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Jun 2025 03:41:47 +0000 Subject: [PATCH 10/67] build(deps): bump vue from 3.5.14 to 3.5.16 (#3924) Bumps [vue](https://github.com/vuejs/core) from 3.5.14 to 3.5.16. - [Release notes](https://github.com/vuejs/core/releases) - [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/vuejs/core/compare/v3.5.14...v3.5.16) --- updated-dependencies: - dependency-name: vue dependency-version: 3.5.16 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5e159b84..741ea4c8 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "@lizardbyte/shared-web": "2025.326.11214", - "vue": "3.5.14", + "vue": "3.5.16", "vue-i18n": "11.1.4" }, "devDependencies": { From 7291fa5f1225a7572954a8916742a13017d12be3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Jun 2025 04:49:30 +0000 Subject: [PATCH 11/67] build(deps): bump vue-i18n from 11.1.4 to 11.1.5 (#3920) Bumps [vue-i18n](https://github.com/intlify/vue-i18n/tree/HEAD/packages/vue-i18n) from 11.1.4 to 11.1.5. - [Release notes](https://github.com/intlify/vue-i18n/releases) - [Changelog](https://github.com/intlify/vue-i18n/blob/master/CHANGELOG.md) - [Commits](https://github.com/intlify/vue-i18n/commits/v11.1.5/packages/vue-i18n) --- updated-dependencies: - dependency-name: vue-i18n dependency-version: 11.1.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 741ea4c8..1906804e 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "dependencies": { "@lizardbyte/shared-web": "2025.326.11214", "vue": "3.5.16", - "vue-i18n": "11.1.4" + "vue-i18n": "11.1.5" }, "devDependencies": { "@codecov/vite-plugin": "1.9.0", From 8afb672bb8954e57cad9ba4f5ab2de0ab33d8a90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Jun 2025 15:07:38 +0000 Subject: [PATCH 12/67] build(deps): bump packaging/linux/flatpak/deps/flatpak-builder-tools from `fe89c19` to `ea9bfa2` (#3941) build(deps): bump packaging/linux/flatpak/deps/flatpak-builder-tools Bumps [packaging/linux/flatpak/deps/flatpak-builder-tools](https://github.com/flatpak/flatpak-builder-tools) from `fe89c19` to `ea9bfa2`. - [Commits](https://github.com/flatpak/flatpak-builder-tools/compare/fe89c19b147432d896f7c1c686630a992132d583...ea9bfa22d175066dd3044544cc55aa070f8282f4) --- updated-dependencies: - dependency-name: packaging/linux/flatpak/deps/flatpak-builder-tools dependency-version: ea9bfa22d175066dd3044544cc55aa070f8282f4 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packaging/linux/flatpak/deps/flatpak-builder-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/linux/flatpak/deps/flatpak-builder-tools b/packaging/linux/flatpak/deps/flatpak-builder-tools index fe89c19b..ea9bfa22 160000 --- a/packaging/linux/flatpak/deps/flatpak-builder-tools +++ b/packaging/linux/flatpak/deps/flatpak-builder-tools @@ -1 +1 @@ -Subproject commit fe89c19b147432d896f7c1c686630a992132d583 +Subproject commit ea9bfa22d175066dd3044544cc55aa070f8282f4 From 0ad1b1234eea41dd8e6f75daebbd2fceb8aa007f Mon Sep 17 00:00:00 2001 From: Coia Prant Date: Mon, 9 Jun 2025 01:28:25 +0800 Subject: [PATCH 13/67] chore: drop 32-bit and add more x86_64 check (#3938) Signed-off-by: Coia Prant --- src/platform/windows/audio.cpp | 4 +--- src/rswrapper.c | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/platform/windows/audio.cpp b/src/platform/windows/audio.cpp index 7c38b1bc..20e600ac 100644 --- a/src/platform/windows/audio.cpp +++ b/src/platform/windows/audio.cpp @@ -27,10 +27,8 @@ DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x2 DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2); -#if defined(__x86_64) || defined(_M_AMD64) +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) #define STEAM_DRIVER_SUBDIR L"x64" -#elif defined(__i386) || defined(_M_IX86) - #define STEAM_DRIVER_SUBDIR L"x86" #else #warning No known Steam audio driver for this architecture #endif diff --git a/src/rswrapper.c b/src/rswrapper.c index 953ba477..53069620 100644 --- a/src/rswrapper.c +++ b/src/rswrapper.c @@ -39,7 +39,7 @@ #define gemm DECORATE_FUNC(gemm, ISA_SUFFIX) #define invert_mat DECORATE_FUNC(invert_mat, ISA_SUFFIX) -#if defined(__x86_64__) || defined(__i386__) +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) // Compile a variant for SSSE3 #if defined(__clang__) @@ -122,7 +122,7 @@ reed_solomon_decode_t reed_solomon_decode_fn; * @details The streaming code will directly invoke these function pointers during encoding. */ void reed_solomon_init(void) { -#if defined(__x86_64__) || defined(__i386__) +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) if (__builtin_cpu_supports("avx512f") && __builtin_cpu_supports("avx512bw")) { reed_solomon_new_fn = reed_solomon_new_avx512; reed_solomon_release_fn = reed_solomon_release_avx512; From 924823151008a8271b3159e9f938e6146563b825 Mon Sep 17 00:00:00 2001 From: Joey Riches Date: Sun, 8 Jun 2025 19:06:27 +0100 Subject: [PATCH 14/67] fix(package/linux): update desktop metainfo (#3901) --- packaging/linux/sunshine.appdata.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packaging/linux/sunshine.appdata.xml b/packaging/linux/sunshine.appdata.xml index 93757eb6..cb999d41 100644 --- a/packaging/linux/sunshine.appdata.xml +++ b/packaging/linux/sunshine.appdata.xml @@ -1,7 +1,7 @@ - @PROJECT_NAME@.desktop - @PROJECT_LICENSE@ + @PROJECT_NAME@ + CC0-1.0 @PROJECT_LICENSE@ @PROJECT_NAME@ @CMAKE_PROJECT_HOMEPAGE_URL@ @@ -11,6 +11,7 @@ @PROJECT_LONG_DESCRIPTION@

+ sunshine.desktop https://app.lizardbyte.dev/Sunshine/assets/images/AdobeStock_305732536_1920x1280.jpg From e7a8f3b3ce1627282d371fdd0d7414427891524d Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 8 Jun 2025 19:26:54 -0400 Subject: [PATCH 15/67] docs(site): add screenshots (#3956) --- .../screenshots/01-sunshine-welcome-page.png | Bin 0 -> 136483 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 gh-pages-template/assets/img/screenshots/01-sunshine-welcome-page.png diff --git a/gh-pages-template/assets/img/screenshots/01-sunshine-welcome-page.png b/gh-pages-template/assets/img/screenshots/01-sunshine-welcome-page.png new file mode 100644 index 0000000000000000000000000000000000000000..97c5a5af07e4a8f6b091c1dc967ca33096cb0916 GIT binary patch literal 136483 zcmd3Og;!PGxAs1?AR!@2gLJ2Kswj;}cc*msK|!RXyFt1eq`SLYx(?laH}Ctq-~Gn@ z1HSR?F*aw{T6^skb3Sv|Kkbli>s&+d;wy&_st#YQjUAlz z?2G_;Ju@o@X4wylWL(T_%-lg!m%QLJ)kdlk#u5_r019}52oOLJ0SI^`3GRgdIR;w; z&_dw20xu7E^q*2}tgKIWsXnHJWO7z@0>DGF#CuUi7j4+0 zkq7QaLg2_m&Mso1b5XFip0c)H!^~$@Ik3JeN}*8Imd>h^+y$;xD z(YN#&x%|yr%hX2}Qe6{cXKQQc!;89j6IwA8GG8n)6!L%mD1Nq`LHj>vzF7I6SfPIZ zb576sT=(AxpPo)fMTb}YPeJfJSF9a)`QH_RMMwVsFPe!$4l~LXVLb7@v$eHl z!AT&LI=psF8>l}m0`|#BGcI|DF6*o3NCEHljf^^(AMB*5zgSrnYnN=DUV8|oY6bsO zAC_3Kc&2sDeIs&rDS^ebw?mGgAzY0IBrpd%)&_f8_msKp6i$d^%T5*8|Z09~DIa z5TwOfw?%T0igp`?8D8;2{E>$?Af^`uQ4Q!+@EdGU$ai*j+!~kK z#Pm%}q(0-;BZ)mK{O=ZcUIQ@#_zD>kPL#Q8v;l-yZxLhjTUN>n@N$$|3uxAt9Dnk_ zmmG*tIh#&}nvWoQSbT5!rgbg6ww^b>Snp``aDUYsnZRXUYC)zbCMKpYZu~Lx=#s>Ck|H{u4cJNz~Qw_vWIAY6Kc*yO8KK{uUx~B;opj7rtGp{mNESgC3x(~qVvai!r-f8ZQ{bM6dJjgZ=GhqY+K%XsPhU&OwauTq9JCH- z@_&$h6w9#oQSL3iWAhNwDdD~=U)>`0R{djmoMAGYQf3j=WyqdfkM+m+JfAd%S*_mq zJi1_7Iy-bYBrYbV$!K2ISR*u5sHUn)5B%1W_b34&A<1tIj8Bba@L4pmoNiNoetcF) zB>ELiUn8YssdsRGabAa+g~#FVNJ``FF}~jk4yvE7hdmA!im#$ys!p3Jr;yj(`qqZ< z{nZ*~&B1~J{+oYgNuT0q^Km9iOPcS1|N9H%xMqaquBcm)QghUX6Tjj>oS$iS#QF2n zij&l7zzGFh@mHVUIlAnn7GBcHe2(Apr)*A7kf9wN?-R0Txg=TRrS(?0i543g?-_XO zTy_4~4eDN9IiaDUwHIrj96du|-J40jZ?3oQ_m0!@R1Dg#i`B?bq zn4F`7Wu#Zq7tPDZ<1yJhJ#oA&OZ>QhW$izetF4sBVfdR8K92HbYAV)l^N;?GUnIO4 zj`e?Q<9bF7Xe!Mozx5YwlbYbl!=(ej?~7uqVk?;bf5c}z{SCL*naOLeu*;;L3%E-} z7tHj9AKzw3dElVKk}izp(A|(+=5`T^>~i3MltxaBoZC-p-+*2PHJ=DKcXE&S<%RRX zqMU?`(KIdj8;;Yd>RVvOCM1J0zwc0=2-YN>$%@AvRxl8>1b&e z{#f4DWQy0IJo^=xuL>y~C@jq5FkWmtZ|P?t+i|@6>C&o8m?Zlqv#vae(9 znBeW#u*eddsv170D_u~n){8%LK3tTNiux=PFHIeoaWZyFBJ9;xd?pkv>G{z1As{)Z zL4G#fo*UWS-3k3{m?pMAlt|e2?1TbHKfRO8} zuAZKr74yr0_Y|NWeTukL*U#2<16~SO!@co{F)XSn z<>hwvw#FesmDra8%VCG+B6EMt^*bnEaTCMs(E;9zLQ zLo#VJ*Y^wsyB>Q$``e*npC0Ye*BQqLMz}r!yHmK?3mn6SG#D`FDdU**{6NX)^x11| zF&6Ew%f-pDI`XS~+n@%hHlO{b1)4IZ2}?DPEh_Jj;89+(BnzJvkQdE6vcGyBP?9N^ z$~#{7g~kaUFz&ixGw8OQcYZ!`a*We;e>To;UlynJ3;+F7QZ7&wf%vz-K<(7lP9e4ICu+&)PikLb zRg0N0Kfg!DG|*|D%2fo&u!Ah73O{GMoky7c#*U025p-jW>@}T3K%i7GJKqIWU_lql zrEX19QqsrJ)Zf|J`B9Q-(EgRVxp|cWbuL!2zZE9GIO0u#whcQRY#bW7cqckoHZ{xI z6)DXhryc3fhgKFw1N>&Jui`QjA|t;zD1M+Qi;Y#;SLZ4!ESy0X`mA~tBJ{n9ZSAvJ zz_I(~J~5{aP%nOsB^H>y!{~M^^!v(P>!b=_2k*n@58N^9>rwupB)wzfGSoQ0r{7~O zEiLL?7Eu-B`eI_M<0ix9LwZ)|Rcyy7LeiUDk{`|ygC`OODhq})}2I6-GGQBg=egxoi*rzclj=KH=@BcMB2 zvNzKSioPhxjrfYKKTd~K)ds>ywQjeV_r_NhCc~x)i-ESAFkZms`~=><8^jY8K|!PD zRori^JkQUMpna2S8V~@@og1@R5|rcy+@N;InBKEqY~nD!^QE7(fDa|kEiZMpc)4H) zzKflmeU>SR*K_mh{STSB>cg4hcRAbJD!yU2m!!`L*eBW^8UyBu7Cp12lT+g2R3}(z zXiUH-LA(OzQi7aX5c}BC`z0bWa(Q0b?BwL+@~){y?Gr-^UOjS>oXCb5hjC{|SNVL@ zcjW?NUbnA_)%*MoyJHzjgpZO+jd$M!JiqU9e;sQwFxf1TsmJiV#q1j%HsPjzpEJG~ zT+3xQP}?_Y0Ws>GW0U$Ay2+$oGGEFDTAhn0Uj)2Pe!CU94Useftd=*=tJDXJKMM_~ z2wtKWE@W%@V$hx&X6t;7OOz8j^Wsr|X?y^&Jear>=d z95JxrAS6wS&q?0u8+v#MSfDe4)APz}F4(+k7fuz|8Uv6eX)2nlRH4N! zq^4p?nzpq)#b+$HKQ}IF?Qb4)UY288qw?2om?*Wa06!I>=IiO|hLmz%uVI3ay;J{` zWg;h93Wsx(CUcG%A0uOfM(EP!Md4oZe451RxXDnIfIbx!O-`YDMYZgG7f=}WZIf$v zLg!{p4%A1mx1EK{rI06Nli4kMRq4(z)@fQbH!-1jF(&Mjz2obPaG?NBoNZwcUE+BY zh~Z%&mC{$rJ=YzjWJzdZ>ve#WmNOsc_5Uf+TYU1t)J|eS0^9R@%FMDNy0lgc=d*1F z{-2`##cI^il7btzd4R6o)>yr@ca~&lu_y{TrH~Mb$k)T&olO;95Vv93!Deg$8@tQA z&oz2|$SKyKj70-Tgdh}zv57Bgz$t8KWW=I<0uRjaxvTTlHsWW#nB)25YEN~c$&gbp zT^xJ!GRgykMaj+WMy_84fOZUZPR&NrIMP1!ppog8hx+^Zm1$sSDCE8Uy3kSP02f|V zR6+CcH&H=z*C! zmq5#mtB*>Tun@eSNx zgqoObY-3E^LKrz1q>#A*+SfW>&MPReH<=kJ%`T(4W8S& zO4$7Pz{9u+%(>5yJ%mUwX11(%Sw=!G7F^5Efu+zcg{fZ$BtkwI#Y;fxVN0t5%LN6v z2CbL#7=W32w_~Q|`WPmdX51IIzkf^me(tkF7^RSF!VS8j8<~9jp#OL@wo9Op!a&S! zmb)O+a)}c>S{|foxV{W}DzoQF&6IkqY&$>cqisz8qJbJ76%{g(FW2XT2YeYJ`u|`47W>Wsu)vW%2fB?|afAONm3AVtV>&y+L znO&rhn^zIOy?<*3d!<51XBt&3dokV>D&S4d$t)zq#I!%P&u^i^Vxd^0Y;2xTkh7eH zHvRF}>&L$<@1`VQ4g92*vM(zNnFQZV_QtX`gNoZq{P*wQ0$z89gMnEG#X~#1 z*G)drazy+mw@sdih=}0ps$IvC(A;a{IcR?P`R8P33whpry`M{6&`eTbxA%lEAQCFI zK5V*IPyW>?2mn0;1L~!w#|cW!wQkoNzqc^9C(pM>*PcCl79b&k&!R=by~+?h^mlvH z^L+Cjl<3V~#NAepYzJ1%#C(oBW6ZUB`@5jC5KBM9%v!Eys3_6VYP`^X&zP9sv5;tc zm|!?-)YMC>S#Q<%_`u4kvb<%n)3)n3TRJ{VXLol_R@R4wKUFueS?3li!;&)grzgVh zyLa2iIHbbY1I{{(FEL+@=tR=2+1LGw*+h%IE494+RZhb|i+74z$k?I3(#S(;?paspRBiGWL7N(u=fq5I9iEMEU6x3yyE z;-+Ub+#;#4>&^HHUX8W3^?cL#1u?%)Q*czT;dFbOi6^&g0rzeQlJ%n`^5vt~T_}!t zX87U*Q=Z?e0n6O6lQhs>l9x;cjq6`~QWQDP!oX;p?Hjpdp~$ExTs&gWtDaFS;(02X z{`X=WCYY(#UEYt|qXrl1OKuN$og*(YnKf&zHvWp^#2xLAoZ^dp`7*AruOIqoo8diN z{JLCIg{Ar4{c<3=nN^3`q=qG1B=7k6gx<$Gr<;P=H~kf)dna zo3%a;E3%i z&6{qX#_QB&eJD3a2Ws7s0hFnSKTV}!jaxN`3zo}yL3KrFaq~`xvpH#LVM;5Rf~AV| z=a~Io`dxjHmdYItDPrc2<7bVxn5y@h!QyM1Od61uac(RT0jsv(xw*Nkc(3g0JjSgx zJm3pz-EMqZ{&?mTXJP2@dEH?eD}k$|5uScJfBNXoPhL40pGC)McW!ZNwtbv?WV~c- zVuwS}1D+{a033?;{MYyN+3v$apnOYd@&V*`X*pa)ThHPvmJWo8_#gb0Z?z8v8nwO2 z&oY9Xp@}uZ0HbQHs!m3|{4D6|wKt=rq;%RJ)iB&g0KAiUdKp<18Uk8%Rh#wR04_I3 zke@W3+#8bxabSa8U0wIMzn&bS+kw6m*2AR&xn3*Hjo-=zpzCWm30_*KPE^O!bMGQq zjzIQmy%z_tIX*fs&^!hErrK%|2>`ViIg>^#%m?rFYdX6lhx4=3qi4^}CSL;L!9>Yn zEf3Lo+lEIMB?!Q!WU#doE$*V*#S1n5cs0HYLLS=m%H=!|Y1W<`m>wkAQc66wyv!g6>U=|~wJ|N*2H9y>4!BNc-T>}fj zePZ_2p4eNiXM2f@dslAG1@(*7p#oGd#_f`2RT|*l@bYGX!_g8g3DYFxH1j&2b5usG zQm=5FccQ`6aklmbW*Qs`3*f%*_#k=|a5WbjC^(Rxy)H$5ir1W}w2**~xFg(ptmExfJYH!5evq&Fyw1%rI}*XWmKb|vI5ADI`Cx9@`jb|X_y(eMJp zlr6Oy?*QH22vS8V5@&mR`$VLgO6POz`8w@C9$BW>*~@P^GEwRs4%JRX1#c!9jPJ+* z-yhnB(Lyd)SRySQ%E~48(RdnkcGtT~_$=PW%aYz|)NXw!rd66%*}a(oy!^6t6rKqq0I?dgR?|s#f(}+B z|JUUQ_$(oPod$1N#)j->_AE&`5l~iBX*5C6Lz+y2C2UD`vTt#&*uF-)Z>(r52YYP9b*vJw3Ym zs=ut3K-f}9v;Z}zGVL96za-su*g$1_#Dg$`G7GqaeE;#=Qy0s0v=`8y@09Y^UG$@P zlewg=vw%ibTvE<9?N{SX_4H&0ul*MO%ks?jU_tjwcCPqmq%)nQ^+-W*s$l(b_UEge zQiXiMNl6P%#PvMxSE_uqew9{)w>Mn%NaO|6cc=hHu{H>Ao1Ge?AkDs5Maq|J$@VRLNr${-&oeJB09ZXOxPGjacNB+c8Cbtu(5i{#y2+6LZQjTkpH(*FtU+pEMFrh7dt> z`+>(*GC5lMb5u~|eX!XwKVLb!Nn`ug@y+G_c+Df3pCr6MKf`o!R#sL8Lt`{_dhdlCp@_#6%rl2ypco&GpKLM*7q z#r*X9tBFC;C1G08*i(Wue(^}b)NU*f|N74t2iY&2qmytgygE-~O=O{62G(Hd<22Y{EQb zNmrNqDKv_818t`6@_87ptRt&Oo8u=d2ZVrO3>Re>pH84qYM(m-HTw2hhc3b<dlkMy|=O?Zm!0U5BFf)vmyuY1qwoy z#Uv8h%j&mfC2I-_GkWFh?-%}f9t=>Ww>*cA7nsQ5$6hML#WD#Cd!Kg|f#!*sM2U%) zaPRVPs@9rkOiWCB(*r%ro7wU4oskF6553KjnPwBEJ|SH0Z{Pih%rt1fId4%EL4l0a zSWS*iO~q(EgXXD~$XY6;Nb7bPAbI1lc%IX5c9gYKq_^~rH!Bsyh^3dhnAu_nd?-24 z8joC~wXY}Sej_On?XJtL>& z_ORY5JI9-q@#?dFxakbH4(UR@duv8#?=jmoB5EqM&+YreR%*;-9!|N@UNs1hoDwl_ zt{9jwtHkGX?CZtR3Cq!z$}6fY7fK<}@b(oj#8IKoH$ceDy7p1XJuWp>ry=()V$Gl{ zltk}xg*789%VFTJ5ZyD0x7A}&6N)$XYl%qTUx+N!efj9q60CKh9NC2)4>=z|?4fUT zKU^C3zcfv-dO4OK3}plmkdUT|v?5)}Sk5i+0y;p}gWVGO8B4YKIYGetRYJv(^Q|2n zqn5wq49bNKkAHn6i;TJ7{S(<-iF|{R+m?P#$sM>Zh)e>7!WDBI6)hzy!}`ZI!jlL^ zXfsBkvS`Kt5u5)ZIdv9q(iW6eqc`8%4~-C4o4wYk~oa*Peli8Prk(Mg@8 zr)5o(FM=QG=x&UKOUjnnV$rlKuRwYy3cA0M{V&~q8uqXsi2DY8_y*1ABH(nfPNurP zy|tsnwht#`qod+r2-3QPaLP2h9K(rNvcA%&v79SZ!*L2>7xaA4WbS7tkZGwYQq04S zy*&=9G#(_-@8-rqqo$@RqmX=_KwZtO4S_&l-}D)om>?~t?AJ)j&rnhI@tdiz1Coe{ zA<%Bn>s4y-3ycRr-^j>G?oMWTZb(L}W_8BL$19a+;{i!uv*c1g?w-3jg`CiDoR@<29q1an5aTn*u#5`H64MVB6bQ+WnWdyh)PL^c!>&Kv+VIvV6<0 zUXair`WXc){SgV{0|cr{IWRysAU)V4J!tq~XH@>hn<$arMETzBe5z0@_Ik$|`zhdY zKOJ=re1CW?&DY~0g13a6juHMEQ}gQyZl>d^%*0Djvpw$=$N8O|m4eZ@x_*Em0$TR!4S1p51!|euP4b8o zi6+C^xTM_pczE}#N^t3BKv{jg`?0ckCLKB33&6qcU6rILK~;8I8rdT}Dk|!kTcaOn zCU=(TZjE#)8|WS#mas!8e|-|I!6c?m!s`TM z5rjMZ;#ON9OWaakQBmP=!WnOh;jcegSyPkBYHQ~q{aYvO@`oc~mt35OAmd#mpZu#3 z@xOC%8gUh9fuKAlAvplqUpyNqYhFLoS?32QH{QYHy@ulu-QQKJI7N&1>FwM?9vJBJ zc%X=2aU5Z&0t>(*YkBGfxLHD7HStw>n*v>vSxlTa+j{VBhWl9Lzp7V!UG_p3nNItEDU61F73AGk(coKjL zp%2Cq2tp(Daxu{YWFL;+fizThQJ^KO6;8wuBa2%4=9N@lrQZDw7-v8o(#9}J0baAm zGjcIY=1rl$x{I#ky;Z9*3c^amvy!bw#w4OkK?04B`ZT%$7DdN{xvAmd8W)@*<-`foY2+dE6~2~TVTWrijp*n_f+b03yt$)tQR^kseSjx6zKNx!iAB9x zzZ3UI4U*W*>AmI&-tQyLPyvQ2>r`#{+e<>u(#JH&ee?BA!OnP-&g0&k<(@Jh?+jnv z?cLeh7^qxl2LIs$1-1fH!bEIhdf2%}AjJdMh7N!aQy^(?0@M-xSUz>xiosgi2-(>K z1TYYY{=$VTzrpSw?(ylgv2(IJ#+cYaM{?DB-@#;@E}7Nz+LjQiu$f(Bwm6pY9vM|d zCHBXlm?;i=T7QoVM@K;RPDcj<5XoLcMoT-ge@`OKm(0vdh3NYh7usryF!GXxsaHNM z9*Z!V=rT}^qy7q>lVEVqm$FPA(4{p&$ggc{69eGV!X+swMv-wo;h7kBIomrzSYv`| zqmRnsAb`9zHH{Gqi$BO2OG?trt2?qhNDm#^>|fQVz|qg_t1(F}NlSx1E0xSrNH0CB zyk~^-i7zNXgF;ZQXFlH>Vh8+|+8QeGR%7#S&A5vD1djT_zVP4qV-_9p@`4A?K9`Rk z9-hu$7w;yg+p+aC73=J{S(-=sr}>>=NBi5hgxr?D0zAP;odT~ctMbX!u=1~SEU`~{ zsM8 z22xHWEITN?E+O!CD3!Pc_4)I!%jI>9g2+&>i#y&5`6A_gA)A@bc4UAa5$U8BHbiD@ z{7rU$KAWZ+@)J~j96QDl5fPv_mkE{xq^VDu7uLd)zqe+!yVm?Zy1u`|=03E5h=$Lk zT3QNgzD9dFl*BcE0RMVW>M26X7Vbe(1H)?ZkCPV9Ud*Y?${iNG(uwIcWKWRBMO062 zvMSmI%QCwPNZlt28Dv8xFwdr03DAw4TIq-*|7?`39UQ#1+`SaOZ=j%KPUzprtB43C z5x$_Y^SOVN2bPw!6W`vsJ&0{@vn~J5Uf(duu64g%_B?Y~^|b}v5wrEyI(hHZ9&J&` zbqA0G$95e&>#JFYi49u6`Ppv`?k#1FBb%_Y5+K60;)e!OoXK^p*!*$Y*DSC?56l2v z&gwnromyrxz z$zs2wK;Vg95Ugp8MJVV5{Ijm8u z`_Sfx*7u-MY13Lx>Yc08Ok!b4+O}Z}dLv}034n>zh*v*Bas)&KV&lN4lvD0k-gM-k*Lc_u$S~)f4?9aQ~3$RLtr%}*&7-zrLuaNe##mgesIEUdS1Q^sCe&?Fty98xrw6)M^@SJqIRyppcPA~tPAgot!R; z=4;y9+pQNG@+M=(-f0$0HhH{u-5OdvIwAz$WhMSz!1aWa~O-7S~0RO`xd*A;R^$bGQbObR+KE?$9D4@FxK;|_=tZsm)K z*SZn#M^Gd(+&Jj=c%?{>~jkq1{kOW}3k=N;`S;Nn4gq96j-l1Xb1Ad{9lFggOV zykM9ct!1HA(v55Npdd~aKtZdAP-rL>7s?hT92{7IfeO(3v;9i3sCv{?GT8MsDLy6u z2CD(_$li%+gZYTOUjqnmG#`?`Y6VY*L+Qb%j z;A_v*vYem8%?QG&Sy@JzKhV2>5ud^i&L=HY@bV|9arOySjYhv+gAPrT=?8E|Ar=qG z5+sv|5T%B-Fh9Is{Np4c{j@!QX4S>~Zo4@ID~&d=UGK8m*qHWV#h>){Inh|5+W6nU zn}dnm)+1>TpeNWFws4+K>uRWYc9N>%!SANICLZ$bdm@N(mnzL7wc&p-YBo@^(FJG` zilobf9@}n^z~OdAJOSRUkx97N<~Du!_93=Jldj4dcDlu)?(18L^6VK}b1Sp^-eiGF zk$TnhXV3H&MaIUBT3cJ0)GK-1&T20fez)}vv{yf)y`OjYlSgb&C>^ggvQSX)L)m&a15TP?DQzlb?@6> zB#ia?C__3SIFL>L03j02a6l(_7-clPjnH8Z(&GZ~rTHF+B$3mja`KXGLUb^TL+Fr8 zh7-S)egcUe`4cGXC$+qJ$7W_`oHD{5XP?Z;5uhz;E!_6Qv43}W=h9{R2PP2u7MMXf z0rFWuUj-b>SmYXCwY3WSuQ#|T0)m2Z*9~tDgc#3nw^L0F48GJUgb6S#_KBZd9goZH zH>~Zg!M8Y;#w>VV{Z2Y&0YB}$i;16s4p1ym|GMQH=ee;!ls zJnmae?HnqbZvTE(S0|{iT05_J)-s zDAY^&qRx;#9u@7O8lD*!WnpoVMlQ)_^+-ElLbGTTZusPEhlA9u;V!4Nyz=Dq{PVhD z;8#YS4;87{!qG)VMLGX$8#J=Afg{bTAT>uaYij%XknAt*6q3#$(*z;cU58IOBvP@A zUgxV4H7SkVzYh`{ z^TV4z-?rjq$(z(-?Q1E$^lwVSIz^NoRqvSPQEAkhHru*aX!=B#CsLZi%?2Zrs3yXrdnK+4Mnb|r6 zjbqdgPeg{B>n)7^d8S+2op$#ZgJSBTL|n!LGsnq)oQ7pW=F2SYtq$Cq9r5UK3B7NN zWD=(m-xeazak}(OuXV}6gl$Us7_MtV2*H7Fc0GML^}Nl`FA%S}5H?lSZ%xPz_=IP6 z1rrOW%QZvv-9s&6S$vm&wmhX&q@%)ti3T>l)JW(fNC4_{gkXf0m{;1^la{QWrQ^3W zti9=f=?!82XY=wch!VtqdBgBOzfXZspbnV+g#+o%AFZ9*0toE~G10pM{Li01Z(L8k z!o!Ou!2f_l(1B50%nVz7ENOuPth-4ITFhGYFYqtTO72U(R2Vw~S*n!5UJpEAbim2h zj3P7RL}5*ald00)+4kC-NJ$K6iCArz4heMK)I*9iHukj1MF9+rjRoCKa1o|p-yTbh z2NTdq`QFDQ8Jk$+#fHU&sY;sodt>qQYY6L*0+CsDVBED^&8P_xC4Jat-)*L`e%Q>& zDN`JsH|eq|R=dfr%DtvIF!=1C@di9G+Ru*;!e_eM>CQ6wMVugLLIM96{g9J_&d`nc`_`yeC=Et4x4?!;rudZ`Sj|e@HpTj|qaZK=1kHWFA-Hulmcq<^j z_FF8q9-CKVR6+PG>R0`N8{F^YtY&H@=%ZgMiGyPCLHkw9O?M{BeRf533^WDkg#SX4 zd+==+G6K^<6Gks0_fy#^w4^*2SjS=|pxfhXIrib#OB+N)V?HwN8w)(i!>m4lRi}bZ zjzXEen7O;>0m)Kg`l}$?;b5n(Fwx8WU56%cTLSfJs$ zJUZGI-Y!VGgVUf1wd8ls`R(x=Cq_JT_Vm^(uTevNb?@`L+bfsHS>4sti9$02pa`6Q zp=O$=pV=X5q=M)l3_ZURs+YlU#tCrA@mp`?ipsJ>Z$AX7~=t#ghY3isM;Ay&@ z#m=YK+1a>sd21!debF=f$bs?U+Ql9I$VI@)s_9NAtWS`P41lhlx~j1FYuCAjXlW*# zG~a@mNj6f_U2p)4cGE(w8CsZFGd5{yzV;wsj#*mibf@zStg`j;Rxf0*5g=)>$Kw3hDnRFUVMM3ld_JpOSB@FiUI~;ty!$E_=-X^u9Mch1} zH^13UW_M^%&HZU1@j{!mcoMI-E$C!n@Y0?FV*@cv>Wo?qJ##mMaxXwCs5+}RGhi}M zd(5E1Xbr|%pv}#VjmgPHMJ1um-*3)otBY_ef`V5B0rV7E`4)H5FB5 z$2QI-N(GxWZ+;A?a1qlag9fCcs4lQa`U|dY1K!35Ff|>)N`tfnZ<9nWc%}tC417I! zk9mU-(U@%b6*?i`gEX>3H;)x)AeZtUXI6?884LR_MDYOG3es|`yLAmJzT=ho+Gm4T z0ReQE2g}nt4zqS2#;HRPzm0WcvYSuker}W`lljT`W^W?ieu!kLu6+H%Qk|EVudDzL zfadsr?8exCj;MBk@m8beuKKa1m7M}^z`CN|=}}u8V_JbLSI1hn2E!E*6=WdHN~vg~ zH1-sK4%RMF!PRK4t8?jTc?e8$$n)d5+Z|l4{z@lEhYjG|UafWX635&rz92K2{$|pDNhaVu zyWt4%LH18p$7)|^fkw3zgJS8&aNP7~yl$|oCBga*3Ua_l6le1w8{-6Imsl}EO;MFL)fsXp^#TRQjlzX%u!}7wyuWI7pW(>*_50ASgPh__XLL#UA2!EX{ zroHLX6D{pVr+vF^UZAz`^%uc~+2+XE`FY2^smb;l1z0~#D2bqPF;7A^XsSZ4nhu7v zv+j>{EiJV?0PC{?m3r@GWC62FZBOx#yD3dZuadU_04BN&r}9fhGjQ~ASS2 z#O+tw5ao@ytVfMu+u_&&zDjkH8-4GP&=H{EfP&;d4zy?F$H&Jai698!NPwF_L6AUl#!Ujw)I|IxK&N$_X(&#e&$l8T|V}7w@*hSB+jvjbRY0up| z3t#RZ)(b&LghLBc^cRz=^?Z#2p~JkQW<}1IWUx6hDp{!jQ~A6W>!qCBjKNJ@oI19j zDt{p?Y}3fwGD4D4)JIf3SsUc5*E#Fxb|FLQmFN=_V^6rEMysT@`nY!UtN1Wep=DpT z4rc14KC3gf>vApy{{#uT2R{Zi^5l|qKhxsMCb4Q80dw!?6N0*A65PNL0=*I)2X^-F zY^#Mc4Tm%L?{f*<)^9;Ki|_R4(t~!tm+Bv<#hZz-vA1Bf0Oa)*X*N!^P73l08P5gB zGl_khjea;pfD;WpSQOd)J!QJsn0)Ikc(PD;O7+E`^$VvqN+ZuvLCM zpYE8=esj=zs*Ih#4o5@zgYQ5>NrcnXHm zq#rffl!jC}CeKyH^==wO8uhCb8X6}t%2M~MHeO6;`eUj}4D|m|Zwf=hXDL>@-7AP2 z!x78+{ad>hi@w@%V#CD6-F?s%gzPT5uznSIWN*jSo`=7ihl5|kqN6=IR$l@GiNg2q;t}rd@6D%k;Q&k$A^n3g z+z-4n{J<-10avVh`h>+a-;RzBe(v@bRUW4^+CV z%TNQBk4>JE`zaS!$C3T`EYP>s6S*%odNHv`Q*G8mzW}Uy3$&NmYG_HuTR?XI+7OQR*#|nhoP($#(2PQeFA?p4ypV@WGY^3b zo0DDX=59yw@bDlYb0dzJJMuNQrj73uOcZMD9)@&&#+@wx@RNcjIo?OcniWB*KqkgmnCymYKHPWH4N#F)f+_%lrDE?*Xj>*4uoB{d&hG?xkf`nvpTh}qg+fNXuRMY zNfUl~i>{`s8vI(&Z6qxHS=nch67q5&k>410#bq(QSm(|HV%~Il5Q7MKtj}DIXP*-u zS=uEqGBA}bLt7)MrD@-U!XCezn=3h&=!8edd>ZG({FGA+Z!(nNd2|%Mtx}>$uLQPt z@TbPS6D$b~*7#p|TKVZ12yc6%f>zNJ#D}&7liD7FhPhX*`=Pd|sHndFYS|i1pg%R3 z=X-U$c5_A;hDErsu_bIWHNdvkRiah%0FnSfsu2LM4szgteXlaQf((cgG*v*c+}mmj~-;)?mS^eLdW zgoMiJ{#jPQoh%jg1pda&-~aJiJI0|w6Bda5say~e9Eb!QrkQJN8+lsVI5?OtS91ZH zfwd){y?X=b*vn7q&tWU;nv$uuk-bq#5g_^4XVQGSu$P%x1{~P4({AaH93aaKj0)Y^ zfTk^dv?MLo%={c64{}%sM8T9&Ej~xqm&RK4j@HLxq@bJpA%o5ejHINqK+{ke+1N%6 z*~?;I=CJ%GEqS|Npnnr$Fh{{<8J>Wl5pOjUPr{#&!jq6PlbCX{Q~TgBC7aP87wih# ze4ZJRYp{LS?{I^}XBD>-pR@BeZb&WuLN%fKlT4zI&}=}{4M(GXQXX5J7*Dpg=D2*? zmg1XX7M$e3#%f{myDoGKd=?csVU}hxAg)F?Tr6Ezg`S*^?FDpgg>ZWVpR(NcKv*|S zy~tR_Ew$J150=~K-Bp?RdAA!xpBao6B7`Z;Jb0B=0FDq~lgDnFAOT)7suxb0t6A{a zUl0McE;rwLR%R=<3Kf2c7YI7u1UJ^RpcH{DM^RIFT?{zFlnI|VWynoBL9qlFTL$Jv2MGZ%g2>Fv8^8EaWYGNx0NgCz zAZc)mMY}X8h!@{*_-rPQgrPXQ56r1o)~Y!0&XRzDsA$N#MM{1P_4Bqyr&{ zt}&u6289NRz3;U$pwC*7JzxpZU4FQSU8GVlGdF2x8Y{S~fIKts?MH>R3dn#})f`BY zoZ0da(;s`H@wb3kuV4F#l=k#LP7?XP9r%2IaBG&AXI8km;e6He8L+uHIzK@xx4i&L z!GDpV+HlW-j3vM6^u+nsUHNN!He;JC+pXQN5zDN@R$*IVe*v;2!5&A#@)?PtaRkzt zps#>G=iA8<34ct6NuF{Y=+22fK$d0crOojfA{5N5Dw9+z*21i?KX-CC#mHCZV5iS_ z868@52U@0PaYRtaAU-l>Xdg}Ww&2Ke6^kzXMuMatYFGR0S8#b9Z#V&-`rFF=nXL7q zG?)^|d>S?A)|jaSS~_}{H#WadZ4zw@;z{N6r`DPbBlR`5`esfU>=!eOyB5)?bbg+)&a z25u7)`m}Foz=Wr;uomqbzq_k7cA(V3W*(&2w7&DlAQ8CQ`*W`K=Mn)6+T*m6K{fl7 zdg3eKd4u~zOk8NZN1T$4@)HD8F;e{+>xzPMtivMfolBbTgM_jj`4*S%kpZZI9x`Cb z?;vFNU%b6{IM@9f|NTZ}WQ53$GLx*xmLl0JvX$%=LN*~~R`%X|XOoqXz4wgly*Izp z^*xUJxbNe*|NeFT+12Os{=C-ve4Xd>`8fARR)-7rorKJY_$n*msJiBHf##d)vi2dK z-!A%i>@0bt#dxVny5pBn-KKMaJqP6S5v0D(dDCrs{@b^29jxBFB&@7VtgJe}|Fm|- z3C=qsx6cN7$w1FPODMw-eN1qOmAN*vfV*J7c9)?jgLQ2kD!S+QJ?ov0{ldg)rcF$i z?@9z=Af91&J>E9wj)Knkg|xhp6`&|C)`52M<_(=QbGBsRbttE*iH@qEdfxq7uKgjD z`(LDJ(^&-C?`Wonq6eJcOTYZ1MmrWyu8<847Tu4`r1qS}Bi6d>$oS$w=Z}WC&BD!N zl&?8=+A)4$8k_j_DXZc#`D+lw$kK_SAonNvA3uGLDd!p2F(6SpYYyn(nslN=P( z5>>NGt&9X&acH0A_mO0mIte+aYVMm3^4o93-*3N`h!7{X-?#t!E>xpyI2W@@FjhU`3^+Hty>ftZs zrH~#JZOj!)c$Q3?`b31C$p6|dDVy7E`y_bewF3kUq*VM4(w_sRg4|!;{gWXT*0x5D zdG37cZQ8;5c!utD?fJ<*1!$p8O3fA?#^Od#_~0z$Y1E|u6LpIwSZEwGP7Y*MF+8=Lfa_s%+2p5OmWm%+czTM4ypztp^=T1Ri>&VB+ zcLij*iGm!gPI9t$3&rzm=?h<%ze&h_wgDPR*l%~h9@p* zt_cBA_SC!Ve&TnhJNSAsi_8noNuqaVc8?)r$1d_bMuvO0uw&lFM~t?*@A}gINkzrc zxWi`pJ;wWCQhv!yem_b|Z0zjv)QhV?Xqd&U9rekn#&a}T^bXVhxY5lTorJGnZ(Fb0 zOPTsW9;k>7B-^Xv}sn80JG zkfhn?X((mwJ$;S(Nv?o5)Jn~cKdwRVt!enVg-*^Od7y-0wZZU!F}GrG!NycgM0E78 zd*^>`DtXGv^7HfU%!Uv4_Kpu_t7_O3~^5RaRTaXZY)aDTRew+u)Om7wl|n zdn;!mJl!!iHr72|Ah)g|7ZhahGbT7eP{?VI3E5lfO|gs{12jb2^Z#E*@D@F*=0X43 zXfbj}Kw!LULt9BOO{sFP#h^Xn!F6_v@mhUQs*=_^8jDgesb5TB@3Nlw}b!B-05b2VP; zMADq>`GTbB4#JIc*TWO_ktj7T3#tUFw}KWbNf`6Ep@QC1HXWZ9wa@gVxtNlk*v9g9 zaTvn;bt(r}ye7=B{<^y!O)~nN=D+{64_j7O3&io;7a9hVi;H(Bh`_1DkHiYvWt&B? zHEFsM#>ri-9T(qME9i{A3<*nn6(2@U;+6v0SLAELL*1WyzK?R;OvaOTttf_zlKEJl z2T`t%m*XSPB*b5WY?Y+e?+X@GtURU8~60+A?!BwztM>BZwU76ingHR zl44hW6@S8sn3{0S$U8{n)ifZ8s%kj!bFO>%FLr0DZ1QM-*GO@s0_{PkX4y@wY4PZLR>hRYkJ60n zd81@H*z3FX3`|;6NgunP2bGmXxk$5M+uR~P%d&O{!9T2E-C`T@OO(kx%1O6r*Q1RZ z=QSxP()3INQ0LNxlI<0`hmmGsHd^WkFFR`D-LGaJ?+M*oqHRLRB>wO`DZ}~^@b$I# z+q=J4X%+FKBoulQuP0)8V0}PsE5n^1pz7=RbKJi5kMO2~o~)-2mY}I3ZDW!lBY~9P zChC>XttZ2@uVNc&ODG?5RP!rr_Dy?jU@xoE%A)9PHZ(#|xLJDqp6*+rYqfpNsU13^ zE|D&%+ZC{2?=HZ(0Zy=v&ofGp&sr1u0CxcUm6_Su4mJ!)87ZZl!L>95+h=F35=Zch zP<0|@M@NT?%TE;1gb16J4l={$`XB0&tSda`*a)M195d6=%$`3`2mbAyhrZb9AV$Ju z!W-EBD_7w`xk_!uufB$Uel(8o1&ZH(BPv2(k0jpvddOoL_S##9=q<$)OVnE^LP!Wr zRI;Z&swe&Yfaeb@`S{cj5xkJdm}m7U6l{?(^))QFb{@RU;EL3&(-isd_ZxkaN^7h; z9vcJ%NAJ0>LA!2C6Gyv+xjFez0X`y6BM9ZZ&%Ljl_!&c5iBC^TH!ObDa=xq7N)p9H z=#qLm)?|~b#!5|Hh0L#c7 za);k~!KF8z)M51?@2>BGz_@x5PFse~WR=C310Yo%oLSE|`Ua0Q(SLAublB)2K_F{z z;q>Zu^-zB<7H1O^zx3bC{BQnSBsj20u!#XS!*n?{BSLnK{Ei=QWxk(2ojN}% zKdKYzr(~9E_16-w2ZjI0l0FVd{DAvsKjYsHJ>lhz4yHm{J1LP2qNMxazBUnCOr^sUhCqc z_iS}KhNR0>zd!a<&A4A11}6w)GqoSPQzIScZsri9MbUi<3wsIG6omVsE`6?M9fvl< zeHPH+fxfB5l~*X!w6k1brxm0&&4JW;o!vbv@8=lhv2gH*SNFxMpZAw&TQNJa~&O%SxQ%0fQeX~|6~g<;j~@&3BEaZ97}dj%;i2GeIoYevZlHl_d!It)Ge z_LrzA3W}8E9XXy)*~PUam8b>XWvSy4G!Ji_aN^GaD*J3I#k2$?#JN(-e8E?VmCG1+ zS;9lQ(NRAAX4%K~{~mi>0LuYl5B7FgOzbWLk&D4J$h#PBtEqH5z?PUP5Ql}toI@_dsY1yqVK9X^zb17r=bDu-qxI2a`N3(lse|2#8aQD`iXDneW2b0y$gW{y*{ooEg z{z-S($+4!rOS*p#9CFVCE_K4B5^)F$ z4D76q!$3T|y}fUyH0$72qcJGeJ=uSPc5-}j1aJla2sW!@i#f;wf@sV3&&-pAZ@=S` z`d7;>CwX3<5hG}yEk5ln_vM-p85tSDP9makUhJxwBW<*d7a2;mo=ax9j!#A1Q%CdJ zTr_2^x7zvLuA%VM6!JAR*5&e|@&4<-CRjz_;$NP~B2v~(>HXYP2BN7v-Ez5_Dm!hh zSd(Zu`G`1tTp>F;;?Fkg1*;4JG-?MfEihS|9xo>D3&YFBIDw@8O=jPV=BJW15`FTX zoOqbX7V-h1Ok}##q2U#=`vTs-;Xdm_JnR36sW8&~67mdFDx6AFI$sm?;#S zJx%3esy}u0=3TNn?8er6aQ?T&(H81l^{D@8T1+NyiVjyewx{?W@Cop7w9^PXl~|sq zPYRTyU?NlRyI44na;$1&1e4$m-N8#Q_U9xph9QXNdc92TS-c{wfZHPZQu9)|^B7*+ zhR=TEw}R*>GzC7$vszgw8EK>l~}o+J62By*KQ1sd)Ojd*Vg`n z5iVVBB;2YW+Z$iNNh0MVE_EFb|1K2i2W!u6m3N}XA(*FkvdWI?WbkV;osXFHY1ibq z!v?sEZ|`9sg+I!6wziDNQmr8lPg644-v5Jx4W{)EAmk`1d7mcsL{ZO@c6)End^p`s zK|znqGjd|$r@-Nc_(XtYL>dJyMj{g_=`c&!eK36eYHbDS`wt4@knnJ~7f-Ex~-XnY>5tgM7!HvO%rssJZrsa+0*rtlZ#Wf1fFbce0(@lF-82Ht&!2<_xsNww7P#4 zT3s;;g&92zI6Va^lytl_uo(Dm7k_>s=ZW(9TMq_%6cdrS4CS2#;-Y(_HXm-ii1&RW zhZhNp_BT?7Ty$o7q^k@PY~}efk6%@4{N`XXPHuP;P!2_o3p?FB3 zbI7+&N`H;PE4g{?P2pD$QG^?p{1FrOore@}Tfd%sHb!AW@DNf_?3p*uD_>qO&sIny zn^(hUjq_~3en$8Oj>VU^FHimh(C(GjBdSfl03u!ILVJbiaf-bb7Q|-6Z+tN!{pSVh zOKf91hzd~&5Nr>e(A%QedULpC`7=urj%q$05;Kap9eJK!Ld{ptjQ#&X75?|I_kD*WHN4WO3^q}^-1JuQ7Q*XE-&9@g{LEj! z{`Y8V|IeezqZj)1F!Y!;B%-C<1f(abC@?&qqD0_OQg3y5JVe+MCJC-!W4CVn2P;jg zLxu0J6(J>ia*0XFhS~Y#4cZW%-s%l|HoZmcGB&;S8%oy;3sI8L|Ho>*3iN4JUjN@? znf~{M|NGXY|7+ix>&_F>`j!c(JVjVjpDzK^)`d^Yuq!bxj1vbl% zjQ_YPCcHoI{(ZJ3D&53>d-`wJWVM;Qc0^oABioUhq^$I*^{6~}tVYl;;Y3?l)Kz6* zVEBR4<30tt7PUN`kjT*AY)8XMCMO^zthV2T`#X4V7cS0PzJ&ObJRGf9<`u)NzWpB` z?pzbZB?%VbJFk5*m_2BzkY*@^f5|fKDw22BT?H?tm}H^z_;^ zbpgT^XrbgIx#<&4*I6E|MhQ4uO&9q)2KBA(laWQyIHjL3g|yxaF)%LGuzzx#n$bRF zIdL-DdH->k(j%jT+_N(m`-aPJS~_3jLUs?E@5?gu2bd_|^1D=aAv^a1g`I1io{w14 zThg;t0x=MA+pIfPY@>-NQUY>nbmx|;>JSMD$r|T>6TkNm=)dkfr4s!~vi@jwnBJMtu8Gt zvntqnOLnV5R z>w|GxqK8+VmB`jjc8xxv5NZkt$~e+hB{(( zVOA2Tu`{}!vkHg{-l0|Xlbd2Z<|kJ(2MH(bgUk7w3hUD+Y$+mM?ejy}JG1XDh!#I7 z++pV>jW|&XV|jTLNCCQxNN(#hpySOoeZ1Ordk+uFg=mr=e&mUAadkBt%vCR!gNndR zfiIrx7VFMSnH$wQ%b%;OuRD+GiYBS#9~rw2r^XjWS`W?Fr66L|9hgwtoHTC%uPI`uh&6cW`%LOSHGP`e3YTPNXGwTlL6^ZxHj&bmsIJoAD z+b;?tZK)Gl6<-WYJNlf_SU)y!l>mNq&D6}?TqE0mo*S`Ce#m^1{s8oKN@8MtU*C^_ zgwRcq51GBj(T^MqfeWN+`-@PcA@Q@HRWMN?exzqkfQuYO( z!^KChni>^4#hgKKPRqFGgAEl{c5EzVnv#JD^MkSXZf@rwc70k9*YUL|1u};z5<>I2Jr-vGNmI^Eq zMPhNx7m|`thTrz`4UXZp04$%rrDc3*==;U4xX{o6)4}Y8w?;ryT-pEhA8xhj4To$$ z0EhVFg1Q$Op(yb7?Z3r#ak`rcmu!SdN?DPl-y;&N*hm1cbBmZnik0=1yp)sI>-{m@ zK=L7w(7W}DadC0^C4*-uUeML)a;ioGcZCcW125t};TPj8PLiy=d^e<=BiftZSocbR z3?-0(DCvck)=$+O0OoxD@@2Br%&0{!ZjYY|kcMBLbrk+iO?`VcjxjjM0{k2MKn7(c zr5~(MVPx+TV23Cx(OFI}4;ELeusq0a{2u%Ldt4m+$ga8VNXDAT$lKJxE#^4LED<4~ z!JpB+=821pjD!+UObiY&JzLrGtdg?w{0{0j$=^TwCVmRC1v3(2`?EyKq=S^>hPs5D z93Em|d0{E!{k5cIZ(yJVCMr;(J=RYA?T>(gfl!bGhI|>K*jpVs=b0V1n$XhrABL$d z&}qW-5%UyPC}F>Q_bEdN0LZyAbPZKiO{Juu5Ml|OaZKATG||^dbi%?7&COLy+FkAK z(o#}{H9}GTkU<4n6i&?tpqGw~kD73kgF2lp0tW#NL2D~BtZXQWGZ0z4-Zq?PB@bkv zrTx}2movBm;uCm0wg^VTFW~tBCL)NIp1e--@j)+^W8l@Yk@AyaXz%J`V`2)GyeIQb zr(n9m;55;#hLVypl8BRmr%Y5wL4{U;krCF={rfqEg*_9$J|5cpu#)HT@GiM6?qT5*{16 z{2lMV!@*IMm{_><>1$LJeMl=%8$bL!w0{We<^KKgs#k~IkOPDhnL+eyVUV8D$iQHJ zvHjSkIGI;FN#Py?gMonoh~Xh?zIy;Q&ELQ8)#%`h(%)lf+uem2Rl~{Y^^+v{RWed- zUt4!}ch$>3!W9|Tcy4a)#DpN8Z@U&bLkKemhhhP*0x^6ZjLixzM%FV_1Y$x?btfI2 z+9 z_XuxMc|+z0K-atFnysCkfNHx{0Ei}vDXlI)&FBcURy?*@{)PqW2d?a#8L#3hwT%bA((m^@7AgR1? zscDx0LNVCQgQkxn+GS;AIyyT`OG;SxH8N*p7(z7i*ya{4$zX*1*o5FUxK#J$s`&7k z=JPG`{}=7ju(4UT-A$wGOn{cddg`MOBvg6G13SKh;W4h-URujVqilU?RH=uVv7_)AE}4GgbdV{^iZwxPk6@w)F`YKebZ1!!p-?j}b2T73oZP?o<{ zU{&A^jHiZ%sx54jfG3p3>|J>ekF2@7S_SgV_p5{DmMMlvq2;+mm{g}ZlK`U;XhS@H zm`(k#2phvy&DZEpmi2YSA<0&;(lNgYqII*wcwmfMNjxKVH<3uO{ujBu5JX+;`BD%q z8&$z!W9qKAx3BMY)tvrB6MqcZr-)v^RSa-+```Zlqj+B}Z?wb_2VvFOY5dhBjZ)uI z_r2JG0P*(mB@%;)8Ooc!8d?)cGqYh2PcPr*TDMN_VbfADla|^Y7QC!`vHnT6E`dk? z;#ZS))xOLN*#~1e-@9}%4U$zl+(9{nqIVcC)L{mPT3q%1NX?>y*k`4zGrLJAvqO)s zs_T__@r`v<=Q{@Xob6x*sOD-3=?|>|N^5V0w@^F=4`FxS_z9VTx=vHL#DGkf=CQ} zd@lR@?9$~PBo_S0tNo?P!;ML22BOngMsK(u519?PbZDb=&%(;-`xnNMD)=n?4UHOh4hB+OL*TfqoPX#%zp~ zgydpx*AF1GmM0dr!||?WLsmDC`)=wlOBew$d5TZDwxC<8KTM5motJ>%BC6xXK8Vj? z9UmJEu9U@9p@_EgtS1ON+t0TRejTD(;Wo!W+TBjn*&?`B;db&om!cbbTu%3S8!aYXT4ZBG*+MwP;UELmM{QWPko$#Lno>Iswrajkf3f@} z824**bTbw9z)lZHG02hS47Saq!v>_{H~;WP={vmjat7fDK=T0Y0EbyMSSWI==93yF z1YHkgk`y3kzB*)qJa*Xl37uE6RA9Sb^YA7lb*YjWs!^K;cAQrM=Xuuvhd7Y;|Su=~6JK+_gXEudQNmh$l$Of_p8$A-3-IVcs7Fn2rV?9*dsZwD;zxB(S$#3M74Dg7dM)Hb@*{B-x>{o)@#;QJ&}z684BA`f+IRkBt1YvR+F2hH3q zrjqT<*UW%2^(r&+kb$lAs`X=d!$mfZGSUytCzBpwaXmfw{vDlxS$lLLyr};v4q$Dh$w-p1_`2`w^2Nn*A5oeVB#@J-C z-FPxD9nV3jn&_hil}gt|Jda~2tjwdM!?^SEI&6PWv!mdVm6DDRJiT z^|z+CP5Jl(rl^gOVC?8KQw?yZG*gusk49v zI;0T{g;05L_3a#^+)BUI9a~aAKezd% zG1UAhWcDnMxh)tpIRZqvWbjJCIAEMd`= zr$R1!pVhmgij7b@;N=U8E=|dCt0&2{bk)e9=F9Xg($kZD8b-Ocd7B!YhY{@&7h7|Y z)A$|Ev_M)oYInUWt{T|_G^*GiVCSRE9IOlc zcg#%HU5e%m!c88J`lPe_zAC6r;SQAWm4K8+9#eh0y$v+a@SS3kPF1;27xTsIEC|F% zShe;*^(fPkuh>rl(xld|Rz;RDII}MsypVT?Q!zQtkg*H&(tMzKVJjh#u9U?cEhF;a zveK?Ps6e;QP+03=V;mnLVvuJhtB3c-HWn7J|J;`TD8jxTUbWrbfkk85afFp+V$xw& zXS2_O#6GtEe*8-7=UxkS%_(3W+4=Y?feyDOKY|U_&#M#szuq&`A_a?rEM3p~aaRX7 zMpGb-Hd&sl-H`Sej$zpsFJ6B_yFt!we3`%gGDr2Mzj(OPvWJ^%Fb!WM(5@GE=}Aaj zsHdSB5g8icty`srgAc)uw!ODS;NJv%Byq_F@b&8? zr5p&DtIdzQsrXAsp*vVng_{=!#E(tC)n8#I`3FCDhEoy*HXzq&Ys+@-o{}LvM7xgH zmB3#lzo|p`<=H)AC>i9sw}iu4m13fChafU0#`ZB~mP%VdEFrc}N8v-;qN7fQxWkYJ z0`grZ+_jOeH0ur~u#NAocG&I)keyXeqU-O??s)3SO~>OR_cx zQ=P9y5861^iY1K{Id_lg4)dB!ykaHsZ<=imB!3Y3NQm+9Zpa>%&#zYHbnPmarnj6X zrB!MLI(gds0<#;gK1x`~kM~guOu4G1rdnEOQ)s>smlgN!)ax=4!zJ}#ZFFwyOK}>b z^4({2VR%c!-D7Q1_Xr2G)py4g^{IuO%(L3WD@9f25(W~^E{*mCk7Ar1h}=9l>#hHf0#qePlN z2IJwMwpRD8t((R7SXfxlVFB=#K{-_^3npryKrNUoxcT2W`QyX{uN2^l9jsPVeEcX@ zfJ6-Jy+_i&qI!JREN763aTQuKe^+eSLFOjJI?>wj`qxoW#}gdPE!{fneh@cZ>5Szm zsVFh_R@>)x`}_MLrP0}Ow8%C&K64GCPaUMiD_V6JS;sLuV z=<;9{lD#j0F{Ch(YMoNx^!~%nKP=i*hk|(L0~%;pcg>@s;z%m&<5;%G>1oR zNdZO<@lJ9kKt`wo9kGrDb$3s(@0Jfl+#Bp+;%jXVQ}Tbx{mQdU<2e%vD?!20CDK>~|fIpe_FTK^wS z?8mQda)A{4>swIK|J?M()^<1iff&%gFJ_V&Wvn-KgoFfze%RW~&d%D}*xrAc69-6| zfBR^jaZ`y#8(_j60^kf}Lb4PH?`W}gYq1_2?CfkGV0eHz*IcVkHK!M;hw0LajZ@3l zpmN&gN7jLn z6UEm!*fhUxW>sW)ege8&s8n2OK&2#QqS!5S0iD;2y!c;zA=pEA@?taq<0)V^1nky& zz+wnhPF9V_(}}#4r*qec;;*qosIop%X}elx0TimYRpbVS1{@Yxh@${nqFjar`9$0J zY7ON}zOa?&Q(*CXlBr>KekUUTzr%kf%Q__yQbn+%gD0KL4y$R>zp z?=IOKI$1yu2d!y|hl z?&9a_>%Y|pR9dA@xJ1aCAe~;0YaWoe0`$sT@D$E5$f=jTxrO;b+)-!G>1m+pVB{I= z4F;R%b*{HemN}hy6Cm2=+#CjcdJrXFo61$gIC(A0>3j=CP2{U2Jx%9BHUiACa*Oj* z&!<;u-IC0uk&Vf!GHgq7A|f-+UXG%o&S;(_CYHNg$|=3xpFUYm7kN4I2Gd+V4d0T= z(DxOKG?&%pFL@v}@f34=>s_$CB;=Mg@(zaLNXxCJ$7)@#+qOYpx}`yXMwk&t0Q#f* zKpF?){^22u@iNQtGTY(&!kmaeiMt5IugPYMK}yTjB~R#zZj1iHlR-s90c!a)ng^&b zZl8*}qVf8WJdxSdytU;f=W z())W)I)WnwV&(uB=Wj3;7N>avhn&Sf;GhU=3j>3AR765S|1#7M_|2ETvc>Uh{d5Uq zNQH}A+Tf3k2Q+*38F1IfTN@EHA4#Q`cCT=mXR&SKFlmR+xHy^%=5>gt5Bcb4`Z z(-LBID1(BUlCHNpz?_>lkeugVoowtqVr(t#I4&;Es7r=>44`H={FS=-DUg!O`C_)w z7jNsxO6dt%;3s$mlh6mktLaL_XhldzAf}~F8HJOYSO|o!^}mh29VT_Ir$Mf#2fp#u z5WPjUHG%JMbE5S0beRiAj?Zo(7R@(EQ@y73nh`fywS%edd;dG$V2a>W3zy1syG21k zIPvo++uYoo^#Q9?jYOo+%j};5=N|-h65U1Lz9qJGf`oy_&kwCYtMfBmS|)LM-Sgg`F>c8FQj|6c_g|DNeKL76aYL{in@-^z6Fq)YMe1Qec(L z`^U5#MamgLFaj0twmCzXAJRXn(V66z@yqFo8>_CWB3=(rU@{*o*>1W&92v)P8y7U& z#cna?Vhqv+nl(R$PZ%%9JnRt(*owItJdWdx~*nxI;9YYO~c- zn#Tuzr3t6H63~{YurQPSxx+1MTS>`q_`vrm)^vBR)=pI2y)VyTaz?+oR=aCe-4kY` zmhu+XsqxFwE$~Sw*vwzo?2dh0W!LW{@O(U3i7nXZ^)|zjG7EOS21{!MX=;YipSMw2 zCY(M>rF*^xZ;JS2`x9GPFJ5((Fk!oeHMF#7CiSJZzx{C|>KTi*gM-<4)u2z0^ysL1 z%f;itDg){Ckl^|bnIxr}(f3sW?Q@qu-=xK@SD!x=BccoYDSdncDfzNsT}g~CilR%W zVpbxaK^f70>ez&hkMAIc8QjuuTw=mWX4{pT)=Kg~ePv~~?Y$~t?d1xSA1c$$8|Vna z#pUKtLJQ(0sH;FLkDAYZ23`DOnCJ?EB8THAh78e$Q*YuL{-pXFDu}u+WmVksOZNLH zB`)1@0VJ3!-N{C z%d}Msn1OdYzN8q*QO5CENV&Xe_AOw}|5rm4hB$KJQbsf?X8W`Cb-Y(^oFYKJ( zNmW_~!-wnE%xgKhuA&g5oR>Lp*MC1d(FCX|2=%TXZt5F0*VeF0t*bT~_ov&v-*dIK z^#@Eiqg1iPbEkFxWhaGs6B|8u`B{Y)8_F-2aQJPAyL&MqFZO@Yiwg91UY$-aOjKYcxz$99 zfx9H)aFBbvyTBUBYt*$C#g!qSLre@tg!;MNhraRmS7Mt!9L$O*bgfUFvCAbM>C;BE z5Pf+TNd732*MX;a$I;fXJDRTs3=d7i2m*1f<8FG)Aytj_E=JDhFOS*xFrK!5ol)n? zcnF_=@sn?Nu``Ox+0gf3-H%#-#MKD%UFMIjDSV-=EyuGKMQfQ~cc8DWl&q}trQ@O( zltb&=9tDz3i6b7#kF7MCYlG|#cPUQ>^_-oZp_vR$Ln9!si{iOk!* zLaR?*ak~rW(E>(8v7=m6lxrr@`5HNHy@`AaJsf-iQ+Z2hl$2Db`)hzdih^YM;by;o zG83;uIaHDVg%VeaEw8WRB4VBhlj&AHiumTj#3)yWY~1YPV!I9%f-V|yFe8IhPzI3& zkEb}7C+~Me3E7SfFoO?<1QSI2p!y?-yfd}XWzB<$^Z>SG#J zm0-K8(QSZp2Qs9LLDU-y3m6C?4h{vyYQ(p1O=jdbJ}EDXQSh49|9yu^AZxz?m!)JS zAEj^K)V4eW5m_R zr(H!aYvGOQBAn2eMA*kU)!7^VUHJW<#Oa}e<>`WHNaaNm#e4~Jtf=mc=hMHK;Jt3U z0G$h5ghFrqf^hA5(0P%4>rdcpzz$?0&K``$$0(SQ)q{4>2(l-kFS%=&>tCYWf+t8l z49Y`s$#+X>?c3W}z?C5Je+JdexH0buAg!M@Nk4e{F>1>iOGQcwyT9`~qGD<%C1tw( zg%}MD%}_3(3iK1tovR_2$S(Bd%;jfu0D03|{m1`S7l+O7cOaw)Y29NOI=JwW=ufMQ zjrgY6ty4?RX`BdbH_3a+SYUc@vg9OQ6^)dksYu&8cA>7*jv3lyO8{I|XXI4oYc$o) zlJ}!&?ZcqCR!?!VGkjGdmFDEIqtKBbKB4(*ZYMV{H(tnH$HfF>0ZB^xNoLoOA5&V- z5adc}y-QkZHjL`w{o8x&rC05-Q)XTuZbJ9s?g!>&GVw<~Vz2$b&OFLdz1ZmwRyJe{ ztgwsWn?^-F18Yu|+3C<)ZmgFto>_?o7gsT)fJT63S?BhE0CRn9ZDMjFn%4pno>9=d zXm4fIOFgN$cz1C1ue$oCy+i>hL@(bdBv2FkD(_6Ok@zP(ul;WD$1glmrWrn@_*Y9> zGE-Yqlfn3cthn~U?N;X2m-o>j3&#%|hf{1)+nmND!5wL{A|U6kyxH-9M@(+2mxUboGur&eq{|q&7-@XEh+8 zyzv{vq)IwqTWQTw_dk@6JVp<=EIvy`N|5Qew%|NZX0V)r=Q* zCnF$0ka+5NSmhKv7eg7&r0Md{!&t&r2eRQFws~c_JAp1OjaG$dK?W(Z42(vLWJI;v z+wIwVd%XAw@b^Hcd<1%$@ID|)uJWFfoUhy}#tM`mB5}LR$mrYncW=Gld1Fl-B<+!; z&`cKkM&o%XP2Kzi#FfdVzeZj0H8$%Spq7qI3Ptry1S=kn+bDtC97agws2)3%SiD>2 zzyyZs@mox(?nPW-;X4M*u*k>?{o(j*Vav5rGsR~-|J-TA3_d{zvAPoi9*dsvT&5%!k+$>#-@pyagjd;2*)4TpA7zAnCIu~S%7eZVpU5z(oky`;1Abs8EghT2h3 z*LbJ?gU7tpxk*Muaa#Dwy!;ICJZtSOWKmHaLj_uuRd($Qv)VZAUg;)4QUS)|#ZMdD zkluh+P514-`R^R>LBTX{9TE}}ZfU*8G_z2NdY8joK{uUG1W0KIT9%bpX^RQI2$egGao6b)J@QVDG^Iz zzf?cpniJ?F=O8M%Y9A=Dr&aFexF|;zl`r4u>wcFITeEC+B4^VBFoyd} z{g*H%1LJHWP+~qQZ_Lut)*@kxBqh5wnPhtixpBMAL5HKvBD3(0jj>W3EpW;H5Td_Uz?y@ysv@|V!HX;0+cmO|DxbRA%~Yx^f|Yh-?AVLK_&2}*=P_&=%w zqvM2a-46CPqZlkS%Fo;kS zH4oSN-Jxc~#q|T@2DMcG`_@D>*?vujH5=UALGDyLsMm;Tl#dsh>@IT|MV+x;MY7NL zEc+@HhpAnm<+f6>L?;+022K02)uvIrYEKyW=)^SuTL{b@E>&^nu>r;N6ONX*QuX8UKu-FKs_Ji78uim~C|^-*T|F|2s`MBwQ9c zqpA2^-t~SrfX7~irQ5s1~0>vCIu4y~5Bop#_J?>~E zQC#wKNQ!dt5S7yb8M{-*Q9uXlLStN4mr2+M3z~wFd9b7@8*N0!RKrb^$tMfU=2Tgz29Bo zaLN@NK7xh=Ovu*O)~lK~$AIe5EZzLyrai@_dBh`f*=de(Y*Q(hao>#!Ea_0%-qS^W zrkJU~6pAPmOY8#-YGq_I2F!J^2sXE&PlWIj)6SS9VY8PQUP%fhx&vuJ1cWyaL^Jwt zq7tLKIvtw!b5^{_3DS1n+kj^%r*{4I>z9<_bq70ZYOa;>&5ZM&GJTlq)AL#@_rbO& zSYNEzxbq~SN3{R-e=k|=J~A8lEcM;h_=ch0 zphlS2$8@;_h+Pt4m}R5+^`YFnzcPgGQMJEpzcB#O0XjqKK5r9?XJhUyj2m#x3(evZl+9!*< zTU-AO4ae7}rZyam46#1m8Drivq9`PA&j4ixycsjByfq-L5*i*xCyLxw4E6C5f2UCR z>g;X(x2md>y}kFP8t91O!UBMs9yCgVfn9)~Zv{VJ3oas%P*lw^@hdAU!o$Tf!IBIM z3j_1)!N#O!-Tkj}8P1Vkdj>|lIT%T#&(O{+EX*}*62cR76||Ic`%O{4zGAh1_kL^J zt?z|wMt@pb#;}SSc=KI;y2i%F-xv;y|E8#+1P5_}ju6PFTdbLxgR^sTe!qE&MK@FLDO$R|dWn)Ks0El$3A>YT#lA4-ZsL zS+)-kt;h_evs8qgLdSaUWSDrCmzVqa9G{=!gIFWF=)xzqaS* z<~@BX_KfakBRgMZ<-EZ>Ym1*Z9WZUxhRZ14J>urRM^Arxa>D<3WY;VVhR|4LZ>`r$ z1a);~m57?5#9U8N(b`gnt(`qcPL5=Ogs+lMCte4L&u|Hus>$r>A)uh3xbO)0@@3HY z5m4#5u6`Y;$^HDW1NfpzDxBfJY7weu3y;|tc?&8s2?%U#?Lwr|KZyK#M*a2~Ln$&2~ z)9V+BfYFeZk=ohY+S=iy#Lf1RVUYem%)RwnlwbHQJb)6C3QCs+(kWdcpdc;XAl)sE zQqn4&BOqPUk^|D6Lx*&C!!Yx1{Cv+j*ZT*&*Lih*0Ir#No*mEL`(F22>z0>S&5u=i z0-P;=EoDBAlLcuB{!YiY-QDG$&|mCrZ})1T_m%_J0aa&JM1L;a3Q+PP83T)p2xVYDou^XJX6zV3Kn=VY179h> zd2=vY0aBfP{k_}s#gu*=+b-|kyTk@G20#s*(Xv%Q>inllK17`98|Gast;Cw)%CP0% z0F47Z9+ruLj^UzhDx2{ zI~_YoewMVc!K3URpP10mntDjG2Fh4uW_|9*&BN@!@ zuTyGLzLt`M65v-$7h4K+Z>Sc$?|%ChSfr$yi9fQ>pdubtf`S;S2G4d?Z0{RA0 zsCPXFd2~1d=mPh%I)MYR!&2aygpNK7UTXxo*kP9ZzWeUg%Bmykqn6eb66tHYx*9cT zF(*lc0u{+l)q(fDN0PoRFX2-p+8anX9rkBBBqjLPn+-LwSQLTHAbffYYNG^L$|Tre7n2J|lQlpXlZIcnpI)7cVDg-vxt}g~e~r(`n!% z#-`i!(&&%eAjMfY9rCnNJXwSIzpq8HJoT4CY@h7S3GMkJ(i<0ck!v%PzpoS6ph?G9 zHopAMnQ5v_YGOA|e@9lW7yRlAO=m4W)14@j@UE_sqod*MKlq<7V*G$tDRdMI|Mm&lJJDDxCM;o<5}vCijz8jXAP={ra6vk?-RUkO(>i zg-!tRQtW0pYmAc5j)~;n&1;Opi6)=rHTl*tb4DhnI+snZPRFZIyw=xJQZk)wzgTb~ zyj4{b_Op2fCFAUp|1+APi%?9rJs$!V#>U#co9R03)(9Ajm>BBXN9-jp?;-7d|JvFl zaXJo`b8|9E%Jz;9+u4(ED)}~VSNQEZ2j*Hs2xItoDxY%UIj>BXVPFwbkdxOu744tx zdv_6{@q&TDd@S#qBGxb8i^~K9UlDL4koOvDgNYTtGc#3Sc(-Fy>5SK!cLJDWtq)MT zyu4KlJ}~?45mR$tp~6wl;3+?v4$Z9L1K#{`3%n94WB{NRiCuf^07`-pCdBU(sEG_* zkc>K@ZRJJU#mIfzyWl$y6{XNM+x#>E`FrHSKGwxG=yYDPWvoNOgksbeBhA(Z*mE8p z9?mB^C~bM5xOw^%9eaN+-0m$4D|5JCBELg3BpDg)V|@o54Z9Ct6%8zY;6H$Flz8Cu zLm@dx6WnKDx_p7PfH*xeVn+mBkpDW6jGP>wTHp*36s}!7{%ck&3fzz9`W_{pz3blW z)a)$PGK?a}QiUm+N<`G9P4kHkAXN7b4b0ikHJNB`h`laWhhFVAWR?wc07oB?J?@`F zkAezYCLS_8d-_x%{bknApJN;Wx6?O0eD)zR$^-kbECKa2kVr~dRKX4NFbmdH^rIOP z`n9B>!0K&iXr!~dJM?lo0wmunpZ08Rvm5ZwR9Pp)$C_Uk7QVin0_=iO%hC}vAGd}E z0i^{nGSj)YLSc3g7 zzKP#HkDcEjjr&0N^8UPg6I zON9fD@PL80`uq1bH;C|dx%mFHn!lsPP_>CMh`4M|7O#C%I|m+$${yIzgDsKIehw5- zINh08-$owd&UUnq2m%;XY#1uR%5f$htp83>a{0%c1GZ%kOp&iq0OMvQ-sW z9)33kBJl4|X4yg;ccgY!Mio8b2dt5WSo;GNjctX>io7G*Z|GW~O1 zZSCpqFzX9}TGO^duxTzzVhw)0TorPEvSrHUt3D-Ao3S`2EFyf7vwxnMsZs3V47?Y6 z)r;!+18bZQnTe3j3<33@SDyn~|GWaA zGP3=Q9d^Aa1C)wjWP@nmHiKP~V;S`fREAxgjx_K&yzILF1e{30znpjJ!mdY8Tb)qK zgZEgH5)*-hrM|X~!VKYojrN!jaXb0NLht<2w*iviV#ZQ3PVY(Jq|wkITz^8L3^q~o z?40aKGQND^IMBDDO#J-aUyg+McuWYOQ>dCZBp+=IgKIrkNHjDy{{A%)8v@RCk}|NL zaEs@_+}*7&ky)d}=Ej>+Ot@vY-ramqhm3@T-}Gq3*XJ4_?euqmyevGS*uv)hyTpT$ z$+E2bbiQQy&rL@^ndDxs7m_XaPT1ScA6;gR$z(}Kk%{>t;&%=9LED>tDs)nbxFL#N zDFs796jNUE@-rTUv%|DuNWCOoyI)Bv`IYXaz@f$!7@vW)jZ)06C_n#2hvrifsn_!} z*EfVWS7!i+@s_vkZwLz4nN~>?ard3JF2%yaQb-f50&ym|uLh6f_?A=;$q+mN_d_n! z88G#mjwpIZNJz-X$CoV|^X(fB1UQudLVt`t1(5r3bmR^q&aN7Qj50D*a}{v#acKBl zJLbN;@l}_W9s%epSXb}f#RP3tNnlHp!K3vB%wXTwZns<6#krw?LMKl& zsqu3o*#suwNOa3>{FPnHZ04o-J>_m%ulUJ7|2fHF2Ve)Y=OQ(C(B zB95wam6QbN(yDp(jme$^5g~WsWtqCKgoK^GzN)G!c$3OVjEuS(9FF)51Z4#U>B5H0 ztju!nWPx4{Fos5~pm>)*-xq*52Jq`15EFxQ5T5U>tDv5O-Ch9JVQi49v5GMer=2c` zxPeHe`^5{y=Mz5ltYNbcAKt%z-}T`rFt80I{~0R4;#aKDIUYy$HkV6m(2acAYp&*Xap3jKj ze1sF^krzQs&&Ju=ZM*48PV0`hWlg#`sbs{r&H;QfIA5E+dHDz#Ejg7$(o zfL3mAn_g+%o+}PfpF-UtU@y7e!By3OsYd=K48&~L1XP}XCnU+1b8>RJRSl3Xz3M@) zTGqj~2_J`BR!y5Ss~7uh@77&;it1&nz{ke&c$3}d^-%L2@)X7Kihe|>U)F=7Z@XWR zEk7(AZ@3&6lFbeqw6ah#i~zYF`bxL{uC5hgxK)*ORts5YM@NwBUJ2alG6}bTSM*|I zxg+O!(UYT9^nG9${5YXkM@4&ulmsH~ z=_%O3k6dg$*lRq(y_cMvoTHH93-nJBW$&J2_$MZ6MmPd(Y>obn-F)2~*JTr}uea?u zRj?69hxLMI=bvGq8_a)p4#Xmm=T%iztS?yiW@|CfodIPKnx~Sl4!azm*&aVWP5PE3 z@Zlm4jL&KB?dF8iwyLW8`Lm7Vl>IM1o0!-F_@N%a&&(BTT{Ng2E|%*{QUf;4%GdcS zMiD@l0hWIcOcydiW1CL9#$Y^J*VQfFrVrB6YhcAhyb@AOe~ICLbyQe-X`QjPm0S$< z*`prIA8gJD2#(COofVrauWHErO#2s6F$nv(op#EqA%9P5Il0Py(BO9<>SC&TcfS#~ z*$F*A)_@`6B!Q+eLc{!1)%Mr>CXm0qrEWm6AL01!-A)(ha{bAcm~r37Cs@(Yb5y2< zO&i(7*qC>dt(RdZTdPd8`@y|rAwMJeZQApWj*e(Pdo+&IGr}{REk>7vs0Pv?POv*9 ziLB$BL%$9$Gt^z&Ip5&^J6*MnDD?yT&5nRGY_`mKG=vtd*&OOqA|@H(sHZmvQfc}r zH_{4rB@&N1J}!PY6+;}D^vcAWhD1`-d-SUNN-CuCb6QO10WEn0kH_@THxo3&(-eC+ z9DW$IyeF1;*kf&^=c$m0;DS(zdg7U+CM2Lh6Md1Z&0sI35i$x(ipi}CADpYdk9aC2 z)h9H zH6YL<)iEI8DecZD2TLj^C+DIZIAv;SX`7j@zn%@J;kAm*4!R3v`}U@&xEPRN_oby> zv2j&j#PRa-y1D{PRw>s6)WCxLFjx>Z@b)|S(;m*16tTrbaKnLv zmL`j@XDn7knSwqq$RC?Lc_Nx^zaU!GK83ipW@2V$?T-Lk!n8^gI4l7{qSbLR4+7^d zgsjnhfkV)2B=C^6_yiQ00X~57>0pVERvEPj0~x1+($doWe4qT@FR2Z;<9oTvC0lUuzqmd`JhLnU4($bJ=rQD#jJN@w`~cR|S3pnT+}Gjg3dV z%UaHrxH&mra&xbWB_}0Iw|AJCngT3>hnH7?pMPa#&22-Z$ZLY_RG@o(Fg35Fgn)nm zASXZ-v?0enm@2xvcPa-<6Mp2r{AWk2^iZ+udrZtkQ|(npdxg0ob?@6KohF-5FihoD z5Fm55Z>+7W_rvwcCx<3!1=C*ZEL0j;3R8)>uW z3#mPSW)b6QLM3c$uCAxO3GAa#dI+c!QleX?%kc1zLAkS2Y_@6l)?V^3fWF~oFSsQD^YucbtIde^!4010E~h{Ic0q33{t6>U`&AhpIz7pExfr5f?u^7aI9{wsPL6Fs^F33Hg#A!2w6QJH)6>Np5$MCY`*})E z9TZ}ioEFs7nQ__Rr#qAorj4NxGCMi+5%Qh(`=JAC0Ul*bopnd2r!}_2oRQ-zZBIdve-2dS>@$6^5jx^VThzE2{geN) zCi33S{9H=fb@Z?D^71OzUzL@qoIV+8#qzys38MMmhR5C#g9t!ZL&I;kqu#?xv4W_|d)-E!ls09yh zHWi=(?uLWaC3q^QuOS z=Hq%XUt?n<2!`G~y@!J{-{g9%QGW(P4AC38tID5aJD1MufF?>wPF}`xz1`AU@jN=| zx`PV%d6#M0n3!<8bQ$Kq_uS|jUOm=N;qm|iLEv52A|X*YUexHcS|D8rx2Qrqi+}SJ zlm-HrH+Y`L4eq=GWE!wQpjrHsR{8ekwelFy?seeN@|axpf+Ue~ndakf@BBQb!LFp& zMG@ zSgM_2Vw}gFat!un)T)ZOGjeaVp433&cA<7ZU}R>-@X!47Ct7s&WHINQnwlEOEP%b< z%3ETwnakE?f{e+w$={&d@a;+VW7Gnwkzu;H@+87_zu|PWNu;!i%lYD029Wpsl?h&A z&*))ddU!8rk;mU^YYR&A;hxFRR-(b#4&w_}R>nSNMi$0CMMk!P?YTk*2&n7*mY4y7 zXmuC!tyrGf_nkazcZ*+!&^4%b625GNCMAI)XrN?Ij@Ug(+^E(o`=nvN`CuX@R@+ld z(_o`9tJ8}4r4Y`S-mCmAv%dEz)pr=M+4fhb1fxx z7m*qs_Py!aEg>z9mDay7p^Cx~-I*-*A?tlMpT`;4WF8U{8NQD(yL#CV4$od<@7@O+ zW*ctc@^8-N=dbleA39$4U8jsQsrAG!RAEXh60AkD@m4yP0!eFlcg! zryaHGUJ4>D(KVDUBp`Nwpj%GQlclg{_~k7j}b)ENYM6-P!N|yC8ps{=7EsH4|MhDwyw=j}+z>3W zlj{l)sYFl;t)=J*5z-29IR23X3Vq9Nu8-6ezRwIJbV~FudIfZJD5U~Y4MM1A#QfZk zNw??q{LW@C+9!xLiVda8FGLA z^l!Pl%zZH7l4G`ZzBdnoE1S14%KILR2(2)?VN-E)X_XBE{TYUh@ufwJK>M-ET0AT4RvQb=B4helYd%TGBjYHfGPbt zbIkc)3B)F`3>fUQllU!{cT!SPy)*Q;5O(I=A2JsxJ9qCE8=ObMp!@N~(Bu6x zDSwNwcnWf7sx^*ZEap~b)yvy@NF)+ML`CAV`^O%{)Q#S&R;Y|DV`CFE2$2$r=|OKE z>ORH+>(BI`8&&ITngbIv_1X5+3&zQ`4_Y9v3_m>0wn$#)=yb$ZF6~QvX|;kK_!Tgf z8EE}326U{;%PSb<(=LHQ=r+n*jFRgM=b`od_cyo}LPA0T=(go$WxgKMV?dwoQ|VEN zIBb1$!$gDzIn)7W%)?ya4=0CFy8qqY5hhwsD!(PBsvYGTTV)Q$U{ z&^_tb+B!P+n&)RnrgE@LH$@khcP=h{NCN`hIwyEf&(`Twg*uA?7;iCz<^!{r;NSzQ-^fJKM21C$MupyPH)-`y7?jqprC?aN zHvanrU)AsvA`?)68rNri4*wQbzU+yc{QVnvfXhhFbF<`k0xNc5v1L%XSb3NeA0MAY z%8|v~A)_OD{vL!{$j#eioSAZd#$|CSJ27!GZ(~Em>zqc!O&9h{6!2yB8$EYs4_D=u zrE7$rgVj6$3xDzu?k1#Tje_`zaxyX=wDKhsc^(%7m(|X;Hffp8ovG_IWNM+c6#VBaGd&(&>j9;fy87D228dK(mC3mOuxB}{&O>^dZ*M9M=No977QB`_V4f z2-PoCXESKD0%doBy@h6z_tf`zOm%Rc2bVrOjbbMq@NgX-i6b*1Hi;?Un!X4jJY7f8 ziDq~~kt+H3AzuRj#L7f#XTD;J6J+31go%k}D)maf3{?6G+lO28rSF9U%PCxPoUvrc*QS zPo(i^0rY>#oSFMXchCrwgjdls(9s}-JP73%c~ZFO815A*t*WhF0K~%4JgbX~WgmzC z*t_}8HOgH8ByfqCv!nR4-{7Yo{fkeut2g43xHU?3%RJ#>fRkFH+2n$3;M>{VfzWYq z{EgEvqo~>Qe|tNRVc`JwGXyCov7M=h52@=N4v&Ch>()6uoAZe_jd>)Q=*}7_cWFPT zRSh&OHRBIS6I?*Xm{wYZNl=iU)8n#$PXl(UV5}m0UZMwrGFTJvF-j=W)u}e_pRSpp z74o$@`WcESvOsI}*scc1ND1&v>6%>0B7FHc5Qqfq*V)+&YRy1+Xg^=;wYXwrZ*LDF z8u|<|C|&D7VB_yiX!%881QHFck;r5C=ZAJr^Jx%L4ej5^)3WN7fjihdohp@2dzof3 zL)JzO0oir8y{oldd`hvot6JT8@zF}P?cv8nLAU3A6`TcThr>U2?fa4%cYCRO)r+-i zj8vse;Y0M2+WpGWZy53LanOi?TE4DYI712>L~nP3gWN$~$?2Xj#aP+w-UPO!)1E%t z8JKpHRsXd6_yoLHI+AQ){odWrpdtgvM4KF&8?b0KZOvYW0|Q}lL6?J=XPX&B$vlpK zif7!H_Bz?M$~>ofcsmtR8ypV1X8H8(JwC_|$A(uB8-%?2c90#a3UcWik#BAXXE&+7 zex_SB7vQ(CacRp-J3MYDU6Hgt$#HT0ShXuom!O8`#oA)DifWZMYb%FJXj}JG=pui- zDF!;iAWhI{y~G~mFa@Gav|b7ct=|xeCZAtf6A+w%db0MV3kqp(WEIx<6|#nP0sj5> z6ZG!q>X{4dJD&wA%x#e?SpYu*yE;dN?z{uvW0h=`{zOsRxfy%CMz;XP2@4+ged&X= z8^B7ivbNTW&t~kIpQq78xae%=;%_YYowEbqIKg6-eDYu;t+SJ)1>eif5TL%L54^Zg zoROe|Kp0t=>OO9kzk9x%3lifZ$1}B#t(ZV5iy>|(-n_0O*5BK|%&$-gPZvF&Dc;sV zb!l_r#Uv+paNxE6%E>VUk)~~RnN_c|`WX%uR!CCm!2D@{O;wdEORp1^hLV!QO^3Yb z4}e)908lR1+k?mO$=d!VUSIG>dfS`!RAdF~{pTH!72&g<^N2Bb6>l8fd6N)ll zGV@-JD))M8>uXzbK+PYn@s~WaXg65jWS^!BVjqk?n;!cKDiciqxqI9hO*x~YPutNP zJv=bjV(7(*8G&!@@8`Za%6q`$E1%BRQC@_^+{!)nBOAjp_--oTzjiZXRbE}G6(bzM z8NwNqEJRn?2fc-U-A61yT+IuT+`;51gMZm%fH8vv9^i-_19qc;=Y@Y<_A{u8hDHt8 zUkR3dykKyP%7wkTdgQ2{&mWAZ4?^F~bGWSxoiEHlAl4w^c#w4bX+E8$Hz|p%*vs5@ zkVXXZqao9HhIB>|>DJxfu1rUwJ~1}ncN3>o+Sn0oqXyjUoG198-Uy>X-vA(k1H7)} zq%>P&8$^FeDcau(+Zk{|p&T!@CO-)?osvsnFa3C|r)pxt%p6{sijcQ2O$PbhuuO$i z;nVJM+sD9LbnJZ=yiPy}OejeZuw->AHVB`dA_KghgnjS+u-{PY)U(p9h+>?8!+|d! zAhuq7^g4bqQL=EcbIe|_^Hf*)93=$IHd1yS&c*CQcEvPf4qAO+ z@DU=BHP!Q3Va_utB`GdW1CMgnZ}a;zq;C_1E~Z=b>f`YlyXd}!t8r%T02zo$Dy0#Np-V@8M%wI$B4-#eS{^)ENfE zbj)Z9znuwao$Kx&#qY7Pvt=eBzpu~1sar)?m&@fu?z(AeI9JggLMG_6Ga_}|5lz{d z@ZOpyN;D&7qBM!my+mA`;rTP4y4B-robL3!5<%(!q9WbukALKr(f&>z#(;u+z#wjm z=+St&9eDmx&QzVi*{=qVTC!5|sXx5r2ISeqfQqQ^$Kj}5nn;g9!M{Yg(O)SX!-om6 z@72_U0`F|s#0~TgvNN-u9-o7v!)k?afQDN|Qb3*>heDz3VpJa$?Cj)Sd3(!wSRKGI z#nsi;qvUB!FYq;lGd;{?BqojlRj@PWE#yy6%Yj>1vAVUf)zaRYzP^5r37P2Y?@3AB z97Qk~6p5<>2vGyw`NhTUR16QoeHt3UDfn_yT|vQ4-WK1Gg$R3YR`;@cP)$t@5X7{L zgEK9F{Gqwo|7GbA4@EynBxGdJvzm06MBw6vGcomO#XrOjj8cfQD=G1g2MTe;K>#v% zkKcS($?s!lRN`UOjJg{U87U8VpZ)zQ>v#~ZBw=oD)y|EG$cP(+H+MyO#7`9i1B2uo z5Sv?XZvnoP)r(XR9I6+BWQsqh!eH0e-W96OdqIyiG&E#nut2?Ki>|J&lar&$s*e$Fu6q61pxvh+PO!q26cquh66{ZJSlry5 ztvxX4ZjVVsiceU7-F{*T_NlVj3WZDpG?E~HqVkFgTzq^S!u_#))!e^7=pHowA6f(U zk|kgk+>=VewZgbz3tmf zpFfah^iD`2r0FiALAtP~17jL}=ht-A0*)ak*H@X(L-!b8h##M_|GOUh zb6}X?g;rrWkHuJKBnkm!640tPKO7vxD!;)m5k%||f@CiL?~rGAo1c3A8xeMXJbwv8 zf>et49>Gcf4G+o=guH)@ll}8O6-Y7uO%DAx=*~kV=w^7Mx6vIyl-u7xwu6Gmo5{X@ zt^7CAU;K#hDgzs|693JmKj6}hLqv2R5~Xj)ye&Ku^BpGEf78q&=+3_h1UNRtM=t+O zzoi%dm*&I&pD$Y}4YpWxt4=IT`2ZzsZ1NnG?HzO?Y&>EpMg|IX@3=nTWBi)WA*w(p zgDB6`ft>}kfPW6U7t&k43Q|23`L`q z6_IfDI2j%@wP2KbDSph1AF*#IBl`9@E7c{6y4QmLH1KO=!&75nOAV z$rz;Td%X0_H0DTA}qXcPvSchYg(IYen_OrGQ;fT=T%`v}fJAtTr-$U8AMw~;H zMl}110|)H^O2==1tfEj4l09R;en7CY}Y_bPzpYwj;u7-P*}@a_w)NisADnKNw6b?IM$Ch zO=pSCvDa_K(3z6usi)!_+%zUV+00 z@x%M=d`x})J(D->>nzu^UA$5+Qb@4;5HAH5eVr*^hm91(nMcvyET*L4A5FuG=~=Vj z8s|!Y# zP0s=qutnEiDZohbB~G4Q_asGtukcfvS9)$wLpPLKT=;fVYA zj)nKz8+oKR=_@>cn+EYAvtN>Qj9r5_Hlyfi3g?3j8QFOSN9AcVqHp|SsmMlv-ob89 z-#7HU6yaxF4d^e^lp(#&Ogr*fOgbeUFwtj)NrDs42|#4A=_ zzW*(DnYOEw@lv}_h;@N=$~GE(m9{HtUaCz^*l#zz{!?%PbND@HV<>@wsc2XXd;;T@ z|5w?3ZxVNG69l5zNUNGos6d$>L)@Rp%teDu|F^TH+yc_4pT*{{y@g8PSM@>xC&P(G zp#)Al0?2S>b6499%Dvm+v0FyXXQbLOpRmix-K z3eyoeADmXRe#`L4*CcmWFcruT8Yni&LI-@t-zz@QVLx~86>Ch#=G5-r-A*9NBW3fm zZVLSbt5?P9{&?Kw&!3A-B2_K{RqctH8wWH#((e+1 zNeJ3D>rFQTTF_|TNBaqBCFW&rg!k%ON?g&e|9 z>RECTjYGwQn%Z<9%RL9R%X0aCvGM@bZDe}X{+Bjs5?BG*lY61rFfk_LS8wQeq`#ow z5g&E8Ul2U*%`Ii0S|%OYWKZkXIde*>yEaSk~y;!<`t{)F=@vaD!*6- zyyL1DRl!sKKUF0I}?wg`3R(n52xweF&i*!`O0q6{S z6`o3_x2l4X$))qswN(CI4z8yqd znL^E(mAC6dk;2T@85w9QIW91*1?)fA1Q^(9xx);qrSvgDjAt5zNdgP`sxpQuzNA_MU{@=w_ zEE0T@_AvFwm0>&HUM({QfZK$8 zdau-dl6l3c$wlsw-*|Cpal*s|u(4QsDE%h;J?~2%2)bI<=LM=r*{O~-x}VBSfSyHy zFE8J7Rd@9~2%V#|bIl@ooQL-)*CkEbK2|W$KQlHgxD$pe&kFBC2|BP8Di?x>2Mn-u zA)0nGS6#5D*75nCmQT%#d(K5m-DT&$ZK%5GxBmUFRu$R zBhs&0eq>~&NWQA1<9o81urHb-Y~m>E^s47(pE<0BujttNc)`{296bu-9(-afUC8I4 zWe$4(E}7vHGwtb)h!P9$OI`eA^{y>@Ju$y)1Sy%M-oq1Gk=50SN6u8*+~&<&gnsg} zUy#1=J*|{n7?*po_7sWp{+@lvIPU8oUnQVM#~tqyt&B|Z$xQm%rKM6usxz8oU+@Oo z-K?7^q@>ZnEKYN)qu3xyditK1h_cC)>${J|u{-2ZKT$u70EF6{Q={RwKPSf$Kilvt zzZ{RH$f#p>F0JltD36!f{M?w6?w&tdR6rU`np1F<&%k%2@!TKT+JVs-N3xbCc%*lh zRcQQTOrzyOV*-!z^m|)ZAiO}4TI{D?f!Ate+?Pl1V@dRd-Jia=!p{7mjOnZa%GL~u z2V7`Ny*&)E?kKrF8N1m?Ie+5bz1`e~-k~rzQom0Z_6UL=DeHQCm2GV<+Cdf@$+42% z-YTuYQB#hV>`Ug18e_M&yIT4#&WP?mlKI&>VNmqDwk=ZL? z?j1}M{7&ZM4|VC;u?3~qNkX&5mGNDx_)632Jd{swZ7ond9LE$fP5M1UnJDCl(NYB}b$Ae946m&lh5o#Z@sPQp?T8 zCf~j)3#9d6Wqo#t=-%(Wx6ds|^u@4yM(i>q7$d9qotMjEANwUaA19_XZutECT!s#< zVPacI8L8qvYp){uos#)E{q&pa`Wilm)P^QxPtd6Hggee_B?Fvg13gZ=9vjFB%V&DBQUgB@wDx7t!5|8`1aXep=l|EvBR!cOQy?>%5Hz(|aCY}p_ zJzwi{IVMOT>GLqqb+^KB^-)>U@cQ`^ecSx3^CF6~r?9cx8zM@tm&ZB5y&YPG%cdrx;e+=3Ci-d;b z9xdp?BA0#B;$zbn zxgpOKFAQ#3)`*}In(Au1W3jaXEZ0pva_xKuX>NWtRsSk^a21g#;I_p4@+`{m0u$5w zq&YnkaUxYH%EehTw!prv!M-i@@#M>)31oRJ5NiSOk!I$-&@=vO-KYGKC+%^wx4Tb3 z^%6ckZ8kAJA^q;k@M7|?*{NTdCai#wwc%u@`=Sfufr+73BG+~=O{1rBC4^Bn%chRh zad{~;QjpaGKID9`a~Na5T~m!7R_BIvQkhM1RDF~xa;D}g^-5kK@SL36Xqr-CFI{nf z20};FF<)D4-b<13A``-=-r7Z8s+e}!(Te4$>^?w9CAvCL^`yPlohi(4GIokMci<%_ z-+B$70O#UZi+uU?gQ^DzAp&2^9?JBlPmmv9`HDOmQM2%wQQG#W?bLg#1s4DUPncUnbpcYV|i{F_%YH1b9eYW;sD1TedX!{d!IM6;!N==uW9%^!t?JJ zjT_0}ovHE>p}drU3b{f#?=ThK7g019Y@%Bisiq4AlzcV&zusEK{TXPHVtgPY1M+*X zc~~`nS?W1x^5wtxUSF$PzFtVF->Uz{&bl@2t~<8tylR@AL0wOmv0=2+xL;G8da>21 zs;@I;e$bbkJiWc{maNV1y2;r!Ia{7lZH025p`heF*03WNwu%q2he4ZkclTz7Tc+?K z#+k+{%YFwZ`(kb*$t_vtpNHDMd`*P3#2xKc4d%lu)msr9}Tr{LPyw=`GyX_^;)S$Xwc zf9v&VA+OWzkt(l3e_W80<`R9mv^uqqPa#mh@XJy_z;ufI1*jk*Uim|U&5y|W_iqmN zhR%)J{Zg0X0}wFmUU^q#SItoNic)iU=M69#o4) zKi4VmQPJrSW}YjjJNqd)N9v<{l$0M9yf5kHl}p*@gdN&g+!y8-{K`hye@3T;7v_{L zu6%<&*f%ghjMqrY3=(V$ZVkCxzhj*fY4k=rh%8JPpuRCY8?o<_dwmO|zqWj+ym^??kS%pc}2&&8a&k0d$jiOefpM<@nI= zgu`vUGdx&Pmva>!cN5YN%%)*pyPBN`=@0Txc0w_x$O+eqFN}x2Ow^wMH@=YpJN-D* zAOl~1pXRP-qh^h8_l;hKy|i6ws%I)sZsJ~}XEZkYXlX4cNz!g=a$8d}o2YV&lNjQW zsOc#uC&iBxpBtJGJBwylGO`QZc%F-@_Sx=`oVpIXN2|jb>rTjcjAzUDZe+wF!n^tv zw#S|6o)JB5n-e-ao=!T3k#jp*mP!yu_S;aU&D}9}l$Oq~(8|aNQeEcyNzd7B_33N|`Yklfcj8V)8D%-jxCWWv$W{1rf;| zlAhik#8N=gP4Ga{)pQ-QReyZz4g*2a-W19-^xJ*I*@j4xxMalNYg=Y>_G`PopFuvs z@3^_D!3^c{#-k%*dRPU#7_kBg$}8PtC*WxUh{Ye@N~dOop0?3C9q%lyY@tCI2=@uG zcjxk*Nu{%pyDKeSlnX4L+s#9q&VuJO?%pWvjLG4d3EJV$5i*wN`+JYRq)9hb8iH;Y zIwkWwMU=wT;1VrHufDbYB}q$0h_e-LPCI{n^BBIr;eOon%$;2f3K4_kl`wjdIr~md z^5v~JgcH#hOW$yum54JoIh{@TK6w;L#!Epz*1#V~a#SZ`;KM?a($w^E`NubBD&0SX zdwRQjqwx85{B4&Bwi~yriRks>E5{W%XD|Fzr(J|1KK_=!Kb`DTw!YrzrbCQy`QWG| z-VbB5B|b9ko$b#VUI)8wF~w7eX_-f`vKF^`5z2WwlrQ{xR?6I6kqNWHdAvP}|fHxxM>%D0Q%!B;%#$_)XU+ML!{{+1S8z$@QE2jhLeq zI@z*a-r6G{HyVj(=Ns5b==~t;F;|7lfYn%8i5gmT8z^xbU<29id;7dG!!Pbb=(_s4 z2IOH)A6JF!&_2DiOJ8jCiu5sU&0ZkD=diajgso;G_zQP>)X+FsX0VWd1iwe17BF3^KtTIm)%yx#H`QFZmM3Ih=_T= zfrM(^{}HM3dk_g$Ydxkero?V{HcrXF#e5kP)v>**^LB-E5Pcm0Ix(E`F# zM=87J-qIGZ*tM}T+uK;t)K7hu0Ed7Ix1Zl{j9CBq_02@YyD>5%a(Ow^A}p=KAv-HN zIt`ct>CbuLL*1^Y?TaZ)g>&HQbzbf}VlIJI0WmU$4yYDMYi44mo_n7%jUU?^D;!VH zKC_dMsNSPV^E%%ZH8i#vlb6nCWG?*e;fC6Bhn*4NR=A!Y5$^LiFHYnVHVu~BXuVy8 zSJ1xmHdXx?JzuTBHb9deW8e#~86}DzrVkV^3=NN}ti<`a8`(~vKkt?<>T`9nGiE+J zmqPj}^8sxK=?4Q}k2Px=#FE_ z?j=A;i!Yz&Ny_LjkYe$j-_MeX_BHJW>IJfXRSc*}3F<4(4HkY`bu~$Nw%gfRb=2Ik zQ#pjbs(dq{vA44cF#t44)N`}cy0c!VltcM)gz8f+A5eRutqBc0mIW0d360@8z(U9K zguE?Z4ah9W&$q7VO*$sMJZHQ-cA(slt#h*~aCSwe_Vftvw2^Szxg?vlH-}(uvWZBcXLZZHzN1O%=*t@zO=2v0$J{ zP}iO8m@NO@p?)Cf*7G^Uy|fU{B%YT?H_^v61cy&e!{_=ZO5I`f4?^#7m}!mwXQB^a+t`TdnzgNN1>MN(!Y20zh zHGz*w-_QKBbbSI*-+*9(`{CJd3A6B{w!Z(N;-++Ps;)Uba5_8M68lJY<8saye@ciq zoR?8hK)^Wm!-9pq$nE?96wJfkLu|EPD~k`WKD(@X_zfh0>Eds-9l~|y@L&_)G{Gs4 zkDehx{n`!ZC#5NBvJdInoLH2ifV}7Egz%T60|E9nk2SLHx+Xe4F8+`6V zG}C~*tCQR7$BosmfH609`>)v@58T<$pNsY8JPD|W^Utl%P4+Re;<{Hm{ie&kZ;(Uj zB0Itjx)>M+1}BT{PZqj9IQ?L3{_(Y?*~r9O@>MZ)YD2&)v%X}F#D_^KX`@>|1Ix;4 zg9__I{4tp+bx0N2nT5Q3{cFopw{S)^E`tK61?qzXgYlqTPc~cBfF954>s*7kTU)HD zWT)aU*K?PXQ(8f9j@G>I5B84rNSKd}x)N&wgxt!V-8Sd!zc#9dx7brpYK%L);3Kd#IDa#>N06O%5Fg6teQr@L{t=bcP&-~M|jq{!;tanj~v`$qQ z9REx9E%~j$ylmwg{C}0z*46-2fQ$@NbD*aO-FaFXpn(JZ3NOpB6%}L~{g@x`xxKb| z{Z2lzA#)|Fg|dpHH%BaNm)EKEQGmIeE}5Et!@p={_I7R+F@C^hTEFz>vg z*aaBzLoXpNq}vB=D?!#fT0Uw%KDLrR(wZSkkiVU~=&x%%e)AH_XR0NTn99kg#(Y;< zwv%a_5Bo^w3d?LL(9c1OK*KOBPO$&c)s{-T(b>`+xhP(2ga#ptU)F^9Qj)i-+hD`t zJkV z-g$kfBOw=|JEyy4eMGtHn*HqVE6y~i#=&2=4?DYE*ELUY@AX*qQo<=BCB!o-E5QO{ zFXv@GwCQj9$q4XYTD|%Q-$Zq2cZ`pZ^b80vKOwh}khYQy84#&I*?^{`;Xv+QCix3! z@R_%YW4^-6@*<+FKwYOn?UQR=B~dd8)!|RgSm(lA<%y>A8HUge$%?hp5+1U1y7?W@ z(eJ6ho-O{8xPj&5p=zq-TR6F3!edKCM1tqwm6iHz z|Cir0-yH7`J87xPOO)#KX~+#GU_RF5^C~DOsO<99^`ld zHErc}`6P7Ac?CTq$VgN*Sf;|D+~s43pt8D-21>qT@6+;spKE9cuSP?@R2>~rOjZrc zGh1a4gB}!peTi~BYdM`m%nx?Cl{6Ma)kCdcg1|HMlV@?Al`38Exw|6J z9RKvTJILr!hcA>yPU2P!K7eWmf{)^&PR=$Gi za$deBKQygi+}n!qG-Y*Gu_^C~z((0~xt!3lB63vOs&2Q3K7~&w&Y8g&N?CfSUc8CG zxLFx~mlXP;7DH+fCT?c2bw-eymDXjhED{7G+JDYaG2Z#LK!j7&(y}k z3ie)M5f5H5qM(dSo~&3w!Cv2bWN7=x$4wh2YvrJ}4y~p9Kh(77;Q&1Muq)#aa2zxn zvHe}Fc`V2t18*O}a|%=sKYSZptZUJz%b+5Q^XXnVPL7r=;!ihqOY%#{!&*jJ5i+ z3^`zYU6dx#Mtn8ni!gpgRn`{lYH(5zmzBZ$qTnnM>61;IMTqQ_%wAPYPOWOU9I14@^%#G0$d1VhwAs`F1xu%SudSkS6FpY>Qge}(I{dZA z4i*F+#!hU2r6DZc;fNNBSLx|r<#T;o+?S%_Z?*H35}l5kw0Q+Wf&{ff3#rXdaN115Iyk&LwWHrP74(lA7G@?XO6dXgKVQJ%x%`&ez8U zSuXUqHz)wyqp@@Y%;kFXMRZ{?J)$~*F;paXA5lzTs(X0asH1@~bJ5|pv9i8IQf?!g z&9(!Bg5_JIS>~PnMaBM9226K&?h5<(MYF5HNZ$ud#>@xlyb}9++sb_>9(Jkx+Hnu2Rz;LtkWUu>F)_0!MQMrlN+e0R!sfTwC2m*b)I_$KOgI#&c!m0sdv0x zwIztP1w2>NK>_^f`PE^G&ls%4aWTx1hDo*7Y!X2~4z9<#ioSi!+j{wg~DMrMz?Z`UFUua|2)QRj3<=#hcxZb{qbEIF63T z^Eu)&D?;ltLd~(Tom0Y$ByjE6%)t8q(Y1@sGZP;dlJL+1S z{M#kkPuW=$9j(HpY@y&S>nxKOWL?#sM_WZG6uP%LzQ>Cb!=6>BxHEBsJ72o@hlvgI zSe3AU(e7_4_Ma$p0H3Js+)j-*HomcOHmYVL>L|kn8?9%W&hRnsE zz$FBRB)8A$hm4l#xv{9iAP|T}c;o4@`%;cEW7QoPvWwi{8}H&g0fEcb+0oHIw+4TS zU#9;W)0gp>r$Pd%StvbJcCZH&sfJ#9rqgYAHGPt-_s^||{Xt7G2UiH-wY!R7s-GpM z`(!vOng23^#|)~&qlE))R+Y9cq~3d1)zz#&_Yt%<_&iZ>@l85;IX`x2r_Ctk4#=vv zdA{zkClWhd9xe8#5Q0E~LiYAHvq0AR!}WB+w-uVnmKL{0=5eC`s0g7i`KSBG*e)4E zAR3*0U_idq!$3-E+J#T!Wv$_bE$7vGV$X$l+d)(x0r^uvGYI5cUG1i#0R)?RT>a!s zg$;aA3l=Az2dnPXRhiwI67X~1{duEc(1^m(kKNhiLmwWRVz9p7E$~IA4+J6Go-BvgS)_hYMMzWE0 zGq%?&l0~gxNSs$;p?7`i_yjVGC1+_HNh9dcs^8L+H91W&-)Uz+wE()*v5v#Zo8b1*L@x@Kz_II zh-5RnW*`Fj8rJJ;7geq`neLbltgkrizS4&*_*|{kEhI`tNtph4sIIM0R1zyPTs$HE9tmut~hCKQ$b6!v88$ zo=E(##VrcRIh3SM1bf7Mo^H!ycQ!cQWo|d!a9HO|)<5CqLko_Gu~~c>X^FYJA>f(+TN(yrekV!H=x)?JNBig<#Lms_J3F{K_$mNpsYxOUwvt?BTBT1&-NTbo9t_`D4UE-E2CL54a;Wm`V|0N<|2P(>-wBiE}@$98K`F#TO7x*xz5?=JSAW z=gWFx9s%F0{Z(YM0hytnfH=gcW`pz2!Ap*_Q_ueEh4)K9R1``TA>z6iCCo2F+vxRQb{e1|9^=BU=<$MAA|7erc_$%ZLxa-FkWZ_r`1B^BrP<%?y|k znb>%y_)hf9P@pL6oUW5OO)sf*bzPi(E`twxT85|7Po>?opCT8qpI~8DnmIJpHOZKG zoHUuZlpDR3=Di+z`X?T)S8{-X3+&bL0A40gp>|*W=Vol4XAg-(28o z%Z9aQCd38{3rW%y_HCDh*R&Lgk_*;le?M5qyF|nM!~=?k@B^WLVH{Sb!GCDb@Py+B zfl#Go3f{E4&)3_^W=hPflM}@O4u+r&tO>3MoT+Nol#7j))t_R>9C))9a(?dfb>nbM z!$?8utD7reIW#ElA&dZ1rzRaHJf{qF;tQT=ujyE#W``Y|4WZzXFNT4lNd-!y92Mld zgLy87Q@G|+LW>PZb*`4b2w-6%@_M~+|HM<%NbYR7b=4&aw8WA7gY^|1-Y>Ui=69Yg zG5p0co|#{aZfHsI7t%Kfy+rH?^XjJJ>YV&P=yH=#wn)(8V-E zVfPGxp-e1HxU&YLi>P$?o2?GtISxAcZ*iKONybk8#=}}^Kn9f9sf?rMX(Zu?K^YJqK z+xpvRBv$VQYxSR++@^*?#6Tf-7sepM-g=WBwu5|!+*BwzS`z4F++Y} zOBwfpIKM9|&kWy?E)hS-`IFuKa7;m8P`sK@L|?qT+K*qQDy0yebA13;O4d^g3j~|< zYpTRXo12!FS*scwn40}_b5PCj&6ayclCXt-^jgc7!uo*%{Y!ptd$?3U2RSoW&w6wb zgFsBkr3(I{c`)ISi9_OYW3nAmGc0;mwil;F(>v#5mNmn`RTdyIi(gPq#9CV7g!_s6 z!TvmirjNnE>nR5w4Td_QuiH%P`A#k)}IPYObRn0-c-lE zZ7)`ry1G!+P?^}i);e8ZMaMM9X1y}i8M1fcqC41M8`b^&D@X|1kC7G$D68X$+2H^Q zPdORPhTVvT2>`P4F9b$O!Jb-<`nPsxFL5v`s%BJ>$v}H`ZioS|Sc)B_h|n zULi9XfEMS?nI;7c%!FlRUkzI{gJY7HTDsTypdT^IFePYr=Oi`mC)caOXNqi^>SS!G z5UcMPiY-B8K$$LIYSCi9*VgR( ztF@K8N-kpqT|AP|*m=%ZoMUm&EwIKTq6!FNZ^lVR_uL+PexK& znDFPf7Znu^lG4iWLi*Y=V;Q9j@5BfHTRm;_N3;c1X+KwX1j(1qr}IBK>VA7Np=|YNo#!e>X4w%zGp_9gw^c z?R7#tJ+oi@%tA|R>B!yq-D87>p?pG8CNiuo?&|MPJu_850VntO2`EvS+NuUX^%Ysr z-@l)D2Nc)w3Cgia35hQxck{PMi0a0A;--GHgX`nxPeGNK+r$Z2SmdiQlAh@LCX}%w z4jwlddR7)T<~IEbg;d_^x*E&Lv6$~n49CksWEJOpcjFZxyT?-u+jl(|V*X7IfzX{r zDBX(_R;#**Rsy{;#o4vi4u1AZaw=YW>U^@1Z&%q zorEP6eyRw$53ECZLA`Qjuu;(H#sG@KuAOpOb!>UKly z6&fvu86av-ms~<;XN7cBeUAQa#m4EzT~)f~QCrf|28DBZoVH(K8f#3yhE(5=XRWRj zEWm#V;LF4+|24PP{G@%5X=r^)2x0iv)Z%p{?K+_^TcN|IG9M*#*j-FiAi`=cafxP_ zSE2jRwN@LD5my@)8ygn%Jmqx~E@h++(3{Y5D>M0RZ1j)Lx5!wTzp*#+yq@fzo^Jc6 zk3J+1Zu?t_rkNNWuM|77=k)gW57)HFx?J8I824Sx_C8-$$0m)xEaq(hz$FVy!-aey zDl|_;h2lYJi6)o(!jxt=17WSKsXVRKHRSKGSOp3+ig7VTg}eJk`R=YyD~l^j+%Q=W zZrV0FGP0tQ)33>;2QlgzDRLQ{s^k{0!5+wkh5djUpc>;~L~3{^_k7=&V$kARtDP&z z=~NmiFjL2`k->);=cA^g+Al(4kK1l$S^_v6VjnrQtmM|B&$hbsv<%+y=q|6*G_k#d z-=5ODSCl8`^1b`EJADsOer)^Fg+gw-}O{a$P@-}QBVo_IR7X$ zj*6k7oxL+~^Rlv%p6m3|X9Q<3S?5$2ucPKtmut9PZ6+l#YK=;dj7oapIv>oBjllJI zw6W+7is#5185jVUY2>_}uHm<45y*wK(JHgCo*19mGWTCz9ex<6dyl=%IZG_ZKHDC>brcHaaNb zEna6O)y3)|<6E6fc;-W_$}rjxe2?eORc{#}*4JsjKq|2F8$^r^#$G7-^{804(Q-o500tLlN?h!32Y$-qb5_*})E+T8 zn@^8_SaW<6X`SHM(oguET_?Pu(qf@N;1tgwO@7_xsSvI zXTOb>*+4~|z=M$}y!mWNC_xC1XZIS9r|%A()hVGiH_=k9a&Zp?MAtZjO?~~cR1uh- zrb1ROIwq#w<#|}al%3dv$|Y9Z=m7oaRp-;`Y53~s?CwLdCC&s>PJLDhwdI=dTFfG4vpdBv7YAwZ!-0a35E)hs8$`soR<_Q(Bd00FjN#!h^QAgdfX9E zC5l6Un8t_2y`1hO5qZugzuCRpBXpFd0w4lBA4Dfh@3xN>uvOenz7#J9L)uzLM z5^7*;n44Q3F}+Xxs9Zp~4(Mzy=J$wS{M|LUIQC79-d^@{^>=h}!iC+DUKz=3N&oo* zL%~FDNczuWkZNbkKFq!_o%VE?D{k(O%gamrpUh#HHKG=K7e)reER2vrz5+#4$!gE8 z&rbl8UkPiE7w`mcd3f8~16s^k2qP!}l2$t$vdhnF zBlTdmn#)TxCN?+EiLkDzKKlF_l5%WZS#oso03pa{G(6fwe5e5pHBGn>lwJ~Jj-2|a zxvHrTXBll{~?DMW*qz$VbK^ZZcUmiiw$(4 zY}I#{*Y!0G(82Z9^^SHZQ(r~<3k%;M1buz`Fimk(2{b)�jT!kVHQd(bm;B5;6b$ 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 w2A-i=Ta(Vv#bl4Qe#2PH_Hq)$ literal 0 HcmV?d00001 From b2d44f5ba94d4a4184a7e4088d6521f05bf8309e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 08:43:05 -0400 Subject: [PATCH 16/67] build(deps): bump third-party/moonlight-common-c from `e95feaf` to `58902e3` (#3957) build(deps): bump third-party/moonlight-common-c Bumps [third-party/moonlight-common-c](https://github.com/moonlight-stream/moonlight-common-c) from `e95feaf` to `58902e3`. - [Commits](https://github.com/moonlight-stream/moonlight-common-c/compare/e95feaf4951b8dc774671a5d6a1c31d76d78e3ac...58902e342f6d53d6783c99fe79a03168d46cd56f) --- updated-dependencies: - dependency-name: third-party/moonlight-common-c dependency-version: 58902e342f6d53d6783c99fe79a03168d46cd56f dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- third-party/moonlight-common-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/moonlight-common-c b/third-party/moonlight-common-c index e95feaf4..58902e34 160000 --- a/third-party/moonlight-common-c +++ b/third-party/moonlight-common-c @@ -1 +1 @@ -Subproject commit e95feaf4951b8dc774671a5d6a1c31d76d78e3ac +Subproject commit 58902e342f6d53d6783c99fe79a03168d46cd56f From c9a06fd8a7e8f2f3411e7d307e724b7571a47fbe Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 9 Jun 2025 12:39:57 -0400 Subject: [PATCH 17/67] build(linux): consolidate appstream metainfo (#3954) --- .github/workflows/CI.yml | 4 ++- .github/workflows/update-flathub-repo.yml | 23 ++----------- cmake/packaging/linux.cmake | 23 +++---------- cmake/prep/build_version.cmake | 31 +++++++++++++++++ .../prep/special_package_configuration.cmake | 14 +++----- ...op => dev.lizardbyte.app.Sunshine.desktop} | 12 +++---- packaging/linux/Arch/PKGBUILD | 8 +++++ ...op => dev.lizardbyte.app.Sunshine.desktop} | 16 ++++----- .../dev.lizardbyte.app.Sunshine.metainfo.xml | 34 ++++++++++++------- ....lizardbyte.app.Sunshine.terminal.desktop} | 0 packaging/linux/fedora/Sunshine.spec | 12 +++++-- ...op => dev.lizardbyte.app.Sunshine.desktop} | 2 +- packaging/linux/flatpak/exceptions.json | 2 +- packaging/linux/sunshine.appdata.xml | 21 ------------ scripts/linux_build.sh | 16 +++++++++ 15 files changed, 118 insertions(+), 100 deletions(-) rename packaging/linux/AppImage/{sunshine.desktop => dev.lizardbyte.app.Sunshine.desktop} (86%) rename packaging/linux/{sunshine.desktop => dev.lizardbyte.app.Sunshine.desktop} (71%) rename packaging/linux/{flatpak => }/dev.lizardbyte.app.Sunshine.metainfo.xml (65%) rename packaging/linux/{sunshine_terminal.desktop => dev.lizardbyte.app.Sunshine.terminal.desktop} (100%) rename packaging/linux/flatpak/{sunshine.desktop => dev.lizardbyte.app.Sunshine.desktop} (81%) delete mode 100644 packaging/linux/sunshine.appdata.xml diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 418e3133..6123ca60 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -264,6 +264,8 @@ jobs: build_linux: name: Linux ${{ matrix.type }} + env: + APP_ID: dev.lizardbyte.app.Sunshine runs-on: ubuntu-${{ matrix.dist }} needs: setup_release strategy: @@ -364,7 +366,7 @@ jobs: chmod +x ./AppDir/AppRun # variables - DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" + DESKTOP_FILE="${DESKTOP_FILE:-${APP_ID}.desktop}" ICON_FILE="${ICON_FILE:-sunshine.png}" # AppImage diff --git a/.github/workflows/update-flathub-repo.yml b/.github/workflows/update-flathub-repo.yml index 524a8a41..b1004cb1 100644 --- a/.github/workflows/update-flathub-repo.yml +++ b/.github/workflows/update-flathub-repo.yml @@ -133,30 +133,11 @@ jobs: xml_file="flathub/${{ env.FLATHUB_PKG }}/${{ env.FLATHUB_PKG }}.metainfo.xml" # Extract release information - version="${{ github.event.release.tag_name }}" && version="${version#v}" - date="${{ github.event.release.published_at }}" && date="${date%%T*}" changelog="${{ github.event.release.body }}" && changelog="${changelog//&/&}" && \ changelog="${changelog///>}" - # Store the old release information into a temp file to be used for precise replacement - tmpfile=$(mktemp) - - # Match the existing block, replace it with the new data - awk -v version="$version" -v date="$date" -v changelog="$changelog" ' - BEGIN { replaced = 0 } - // { - if (!replaced) { - print " " - print "

" changelog "

" - print "
" - replaced = 1 - } - } - !// && !/<\/release>/ { print $0 } - ' "$xml_file" > "$tmpfile" - - # Move the updated file back to the original location - mv "$tmpfile" "$xml_file" + # Replace changelog placeholder with actual changelog + sed -i "s||$changelog|g" "$xml_file" - name: Update submodule if: >- diff --git a/cmake/packaging/linux.cmake b/cmake/packaging/linux.cmake index 6f4ebbe3..659bbc37 100644 --- a/cmake/packaging/linux.cmake +++ b/cmake/packaging/linux.cmake @@ -136,26 +136,13 @@ if(${SUNSHINE_TRAY} STREQUAL 1) endif() # desktop file -# todo - validate desktop files with `desktop-file-validate` -if(NOT ${SUNSHINE_BUILD_FLATPAK}) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.desktop" - DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications") -else() - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.desktop" - DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications" - RENAME "${PROJECT_FQDN}.desktop") -endif() +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_FQDN}.desktop" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications") if(NOT ${SUNSHINE_BUILD_APPIMAGE} AND NOT ${SUNSHINE_BUILD_FLATPAK}) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine_terminal.desktop" + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_FQDN}.terminal.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications") endif() # metadata file -# todo - validate file with `appstream-util validate-relax` -if(NOT ${SUNSHINE_BUILD_FLATPAK}) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.appdata.xml" - DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo") -else() - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_FQDN}.metainfo.xml" - DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo") -endif() +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_FQDN}.metainfo.xml" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo") diff --git a/cmake/prep/build_version.cmake b/cmake/prep/build_version.cmake index 5457fed1..e0d43380 100644 --- a/cmake/prep/build_version.cmake +++ b/cmake/prep/build_version.cmake @@ -54,3 +54,34 @@ else() MESSAGE(WARNING ": Git not found, cannot find git version") endif() endif() + +# set date variables +set(PROJECT_YEAR "1990") +set(PROJECT_MONTH "01") +set(PROJECT_DAY "01") + +# Extract year, month, and day +if(PROJECT_VERSION MATCHES "^([0-9]{4})[.]([0-9]{3,4})") + # First capture group is the year + set(PROJECT_YEAR "${CMAKE_MATCH_1}") + + # Second capture group is month/day + set(MONTH_DAY "${CMAKE_MATCH_2}") + string(LENGTH "${MONTH_DAY}" MONTH_DAY_LENGTH) + if(MONTH_DAY_LENGTH EQUAL 3) + string(SUBSTRING "${MONTH_DAY}" 0 1 PROJECT_MONTH) + string(SUBSTRING "${MONTH_DAY}" 1 2 PROJECT_DAY) + elseif(MONTH_DAY_LENGTH EQUAL 4) + string(SUBSTRING "${MONTH_DAY}" 0 2 PROJECT_MONTH) + string(SUBSTRING "${MONTH_DAY}" 2 2 PROJECT_DAY) + endif() + + # Ensure month is two digits + if(PROJECT_MONTH LESS 10 AND NOT PROJECT_MONTH MATCHES "^0") + set(PROJECT_MONTH "0${PROJECT_MONTH}") + endif() + # Ensure day is two digits + if(PROJECT_DAY LESS 10 AND NOT PROJECT_DAY MATCHES "^0") + set(PROJECT_DAY "0${PROJECT_DAY}") + endif() +endif() diff --git a/cmake/prep/special_package_configuration.cmake b/cmake/prep/special_package_configuration.cmake index 298c0226..74613523 100644 --- a/cmake/prep/special_package_configuration.cmake +++ b/cmake/prep/special_package_configuration.cmake @@ -12,19 +12,17 @@ elseif(UNIX) # configure the .desktop file set(SUNSHINE_DESKTOP_ICON "sunshine") if(${SUNSHINE_BUILD_APPIMAGE}) - configure_file(packaging/linux/AppImage/sunshine.desktop sunshine.desktop @ONLY) + configure_file(packaging/linux/AppImage/${PROJECT_FQDN}.desktop ${PROJECT_FQDN}.desktop @ONLY) elseif(${SUNSHINE_BUILD_FLATPAK}) set(SUNSHINE_DESKTOP_ICON "${PROJECT_FQDN}") - configure_file(packaging/linux/flatpak/sunshine.desktop sunshine.desktop @ONLY) - configure_file(packaging/linux/flatpak/${PROJECT_FQDN}.metainfo.xml - ${PROJECT_FQDN}.metainfo.xml @ONLY) + configure_file(packaging/linux/flatpak/${PROJECT_FQDN}.desktop ${PROJECT_FQDN}.desktop @ONLY) else() - configure_file(packaging/linux/sunshine.desktop sunshine.desktop @ONLY) - configure_file(packaging/linux/sunshine_terminal.desktop sunshine_terminal.desktop @ONLY) + configure_file(packaging/linux/${PROJECT_FQDN}.desktop ${PROJECT_FQDN}.desktop @ONLY) + configure_file(packaging/linux/${PROJECT_FQDN}.terminal.desktop ${PROJECT_FQDN}.terminal.desktop @ONLY) endif() # configure metadata file - configure_file(packaging/linux/sunshine.appdata.xml sunshine.appdata.xml @ONLY) + configure_file(packaging/linux/${PROJECT_FQDN}.metainfo.xml ${PROJECT_FQDN}.metainfo.xml @ONLY) # configure service configure_file(packaging/linux/sunshine.service.in sunshine.service @ONLY) @@ -38,8 +36,6 @@ elseif(UNIX) # configure the flatpak manifest if(${SUNSHINE_CONFIGURE_FLATPAK_MAN}) configure_file(packaging/linux/flatpak/${PROJECT_FQDN}.yml ${PROJECT_FQDN}.yml @ONLY) - configure_file(packaging/linux/flatpak/${PROJECT_FQDN}.metainfo.xml - ${PROJECT_FQDN}.metainfo.xml @ONLY) file(COPY packaging/linux/flatpak/deps/ DESTINATION ${CMAKE_BINARY_DIR}) file(COPY packaging/linux/flatpak/modules DESTINATION ${CMAKE_BINARY_DIR}) file(COPY generated-sources.json DESTINATION ${CMAKE_BINARY_DIR}) diff --git a/packaging/linux/AppImage/sunshine.desktop b/packaging/linux/AppImage/dev.lizardbyte.app.Sunshine.desktop similarity index 86% rename from packaging/linux/AppImage/sunshine.desktop rename to packaging/linux/AppImage/dev.lizardbyte.app.Sunshine.desktop index 911735c8..91da7902 100644 --- a/packaging/linux/AppImage/sunshine.desktop +++ b/packaging/linux/AppImage/dev.lizardbyte.app.Sunshine.desktop @@ -1,13 +1,13 @@ [Desktop Entry] -Type=Application -Name=@PROJECT_NAME@ -Exec=sunshine -Version=1.0 +Categories=RemoteAccess;Network; Comment=@PROJECT_DESCRIPTION@ +Exec=sunshine Icon=sunshine Keywords=gamestream;stream;moonlight;remote play; -Categories=AudioVideo;Network;RemoteAccess; +Name=@PROJECT_NAME@ Terminal=true +Type=Application +Version=1.0 +X-AppImage-Arch=x86_64 X-AppImage-Name=sunshine X-AppImage-Version=@PROJECT_VERSION@ -X-AppImage-Arch=x86_64 diff --git a/packaging/linux/Arch/PKGBUILD b/packaging/linux/Arch/PKGBUILD index e8729163..17fc672a 100644 --- a/packaging/linux/Arch/PKGBUILD +++ b/packaging/linux/Arch/PKGBUILD @@ -36,7 +36,10 @@ depends=( ) makedepends=( + 'appstream' + 'appstream-glib' 'cmake' + 'desktop-file-utils' 'cuda' "gcc${_gcc_version}" 'git' @@ -86,6 +89,11 @@ build() { -D SUNSHINE_PUBLISHER_WEBSITE='https://app.lizardbyte.dev' \ -D SUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support' + appstreamcli validate "build/dev.lizardbyte.app.Sunshine.metainfo.xml" + appstream-util validate "build/dev.lizardbyte.app.Sunshine.metainfo.xml" + desktop-file-validate "build/dev.lizardbyte.app.Sunshine.desktop" + desktop-file-validate "build/dev.lizardbyte.app.Sunshine.terminal.desktop" + make -C build } diff --git a/packaging/linux/sunshine.desktop b/packaging/linux/dev.lizardbyte.app.Sunshine.desktop similarity index 71% rename from packaging/linux/sunshine.desktop rename to packaging/linux/dev.lizardbyte.app.Sunshine.desktop index bc8a995c..29252ae5 100644 --- a/packaging/linux/sunshine.desktop +++ b/packaging/linux/dev.lizardbyte.app.Sunshine.desktop @@ -1,15 +1,15 @@ [Desktop Entry] -Type=Application -Name=@PROJECT_NAME@ -Exec=/usr/bin/env systemctl start --u sunshine -Version=1.0 +Actions=RunInTerminal; +Categories=RemoteAccess;Network; Comment=@PROJECT_DESCRIPTION@ +Exec=/usr/bin/env systemctl start --u sunshine Icon=@SUNSHINE_DESKTOP_ICON@ Keywords=gamestream;stream;moonlight;remote play; -Categories=AudioVideo;Network;RemoteAccess; -Actions=RunInTerminal; +Name=@PROJECT_NAME@ +Type=Application +Version=1.0 [Desktop Action RunInTerminal] -Name=Run in Terminal +Exec=gio launch @CMAKE_INSTALL_FULL_DATAROOTDIR@/applications/@PROJECT_FQDN@.terminal.desktop Icon=application-x-executable -Exec=gio launch @CMAKE_INSTALL_FULL_DATAROOTDIR@/applications/sunshine_terminal.desktop +Name=Run in Terminal diff --git a/packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.metainfo.xml b/packaging/linux/dev.lizardbyte.app.Sunshine.metainfo.xml similarity index 65% rename from packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.metainfo.xml rename to packaging/linux/dev.lizardbyte.app.Sunshine.metainfo.xml index fe5f9d76..6be35169 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.metainfo.xml +++ b/packaging/linux/dev.lizardbyte.app.Sunshine.metainfo.xml @@ -1,8 +1,8 @@ - + @PROJECT_FQDN@ - @CMAKE_PROJECT_NAME@ + @PROJECT_NAME@ @PROJECT_BRIEF_DESCRIPTION@ CC0-1.0 @@ -15,39 +15,49 @@ gamepad - https://github.com/LizardByte/Sunshine/issues @PROJECT_HOMEPAGE_URL@ + https://github.com/LizardByte/Sunshine/issues + https://docs.lizardbyte.dev/projects/sunshine/latest/md_docs_2troubleshooting.html + https://docs.lizardbyte.dev/projects/sunshine https://app.lizardbyte.dev/#Donate - https://app.lizardbyte.dev/support https://translate.lizardbyte.dev - https://docs.lizardbyte.dev - https://github.com/LizardByte/Sunshine + https://app.lizardbyte.dev/support

@PROJECT_LONG_DESCRIPTION@

-

NOTE: Sunshine requires additional installation steps.

+

NOTE: Sunshine requires additional installation steps (Flatpak).

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

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

-

NOTE: KMS Grab (Optional)

+

NOTE: KMS Grab (Flatpak)

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

- + + +

+ See the full changelog on GitHub + + +

+
+
- LizardByte + + LizardByte + - https://app.lizardbyte.dev/Sunshine/assets/img/banners/AdobeStock_305732536_1920x1280.jpg - Sunshine + https://app.lizardbyte.dev/Sunshine/assets/img/screenshots/01-sunshine-welcome-page.png + Sunshine welcome page diff --git a/packaging/linux/sunshine_terminal.desktop b/packaging/linux/dev.lizardbyte.app.Sunshine.terminal.desktop similarity index 100% rename from packaging/linux/sunshine_terminal.desktop rename to packaging/linux/dev.lizardbyte.app.Sunshine.terminal.desktop diff --git a/packaging/linux/fedora/Sunshine.spec b/packaging/linux/fedora/Sunshine.spec index 3becff3b..1f7a8782 100644 --- a/packaging/linux/fedora/Sunshine.spec +++ b/packaging/linux/fedora/Sunshine.spec @@ -15,8 +15,11 @@ License: GPLv3-only URL: https://github.com/LizardByte/Sunshine Source0: tarball.tar.gz +BuildRequires: appstream # BuildRequires: boost-devel >= 1.86.0 BuildRequires: cmake >= 3.25.0 +BuildRequires: desktop-file-utils +BuildRequires: libappstream-glib BuildRequires: libayatana-appindicator3-devel BuildRequires: libcap-devel BuildRequires: libcurl-devel @@ -197,6 +200,11 @@ cmake "${cmake_args[@]}" make -j$(nproc) -C "%{_builddir}/Sunshine/build" %check +# validate the metainfo file +appstreamcli validate %{buildroot}%{_metainfodir}/*.metainfo.xml +appstream-util validate %{buildroot}%{_metainfodir}/*.metainfo.xml +desktop-file-validate %{buildroot}%{_datadir}/applications/*.desktop + # run tests cd %{_builddir}/Sunshine/build xvfb-run ./tests/test_sunshine @@ -252,14 +260,14 @@ rm -f /usr/lib/modules-load.d/uhid.conf %{_modulesloaddir}/uhid.conf # Desktop entries -%{_datadir}/applications/sunshine*.desktop +%{_datadir}/applications/*.desktop # Icons %{_datadir}/icons/hicolor/scalable/apps/sunshine.svg %{_datadir}/icons/hicolor/scalable/status/sunshine*.svg # Metainfo -%{_datadir}/metainfo/sunshine.appdata.xml +%{_datadir}/metainfo/*.metainfo.xml # Assets %{_datadir}/sunshine/** diff --git a/packaging/linux/flatpak/sunshine.desktop b/packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.desktop similarity index 81% rename from packaging/linux/flatpak/sunshine.desktop rename to packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.desktop index eca745ef..a6ab9ea5 100644 --- a/packaging/linux/flatpak/sunshine.desktop +++ b/packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Categories=AudioVideo;Network;RemoteAccess; +Categories=RemoteAccess;Network; Comment=@PROJECT_DESCRIPTION@ Exec=sunshine.sh Icon=@SUNSHINE_DESKTOP_ICON@ diff --git a/packaging/linux/flatpak/exceptions.json b/packaging/linux/flatpak/exceptions.json index 957f7384..f341c260 100644 --- a/packaging/linux/flatpak/exceptions.json +++ b/packaging/linux/flatpak/exceptions.json @@ -1,6 +1,6 @@ { "dev.lizardbyte.app.Sunshine": [ - "appstream-missing-screenshots", + "appstream-external-screenshot-url", "appstream-screenshots-not-mirrored-in-ostree", "external-gitmodule-url-found", "finish-args-flatpak-spawn-access" diff --git a/packaging/linux/sunshine.appdata.xml b/packaging/linux/sunshine.appdata.xml deleted file mode 100644 index cb999d41..00000000 --- a/packaging/linux/sunshine.appdata.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - @PROJECT_NAME@ - CC0-1.0 - @PROJECT_LICENSE@ - @PROJECT_NAME@ - @CMAKE_PROJECT_HOMEPAGE_URL@ - @PROJECT_BRIEF_DESCRIPTION@ - -

- @PROJECT_LONG_DESCRIPTION@ -

-
- sunshine.desktop - - - https://app.lizardbyte.dev/Sunshine/assets/images/AdobeStock_305732536_1920x1280.jpg - Sunshine - - -
diff --git a/scripts/linux_build.sh b/scripts/linux_build.sh index 3b09eed5..4132c39d 100755 --- a/scripts/linux_build.sh +++ b/scripts/linux_build.sh @@ -138,9 +138,12 @@ function add_arch_deps() { function add_debian_based_deps() { dependencies+=( + "appstream" + "appstream-util" "bison" # required if we need to compile doxygen "build-essential" "cmake" + "desktop-file-utils" "doxygen" "flex" # required if we need to compile doxygen "gcc-${gcc_version}" @@ -201,13 +204,16 @@ function add_ubuntu_deps() { function add_fedora_deps() { dependencies+=( + "appstream" "cmake" + "desktop-file-utils" "doxygen" "gcc${gcc_version}" "gcc${gcc_version}-c++" "git" "graphviz" "libappindicator-gtk3-devel" + "libappstream-glib" "libcap-devel" "libcurl-devel" "libdrm-devel" @@ -469,6 +475,16 @@ function run_install() { echo "cmake args:" echo "${cmake_args[@]}" cmake "${cmake_args[@]}" + + # Run appstream validation, etc. + appstreamcli validate "build/dev.lizardbyte.app.Sunshine.metainfo.xml" + appstream-util validate "build/dev.lizardbyte.app.Sunshine.metainfo.xml" + desktop-file-validate "build/dev.lizardbyte.app.Sunshine.desktop" + if [ "$appimage_build" == 0 ]; then + desktop-file-validate "build/dev.lizardbyte.app.Sunshine.terminal.desktop" + fi + + # Build the project ninja -C "build" # Create the package From 7017b8e88cf9b3de35362ce7737e038dacc1fce3 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 10 Jun 2025 20:56:25 -0400 Subject: [PATCH 18/67] ci(homebrew): add macos-15 (#3963) --- .github/workflows/CI.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6123ca60..cf005b1b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -508,6 +508,8 @@ jobs: os_name: "macos" - os_version: "14" os_name: "macos" + - os_version: "15" + os_name: "macos" - os_version: "latest" os_name: "ubuntu" - os_version: "latest" # this job will only configure the formula for release, no validation From a212a542a007a77e41236fe33b146abf0b80397e Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Thu, 12 Jun 2025 20:42:08 -0400 Subject: [PATCH 19/67] chore: update global workflows (#3907) --- .flake8 | 1 + .github/label-actions.yml | 49 --- .github/semantic.yml | 1 + .github/workflows/_codeql.yml | 30 ++ .github/workflows/_common-lint.yml | 27 ++ .github/workflows/_docker.yml | 39 ++ .github/workflows/_release-notifier.yml | 23 ++ ...te-changelog.yml => _update-changelog.yml} | 20 +- .github/workflows/_update-docs.yml | 34 ++ .github/workflows/_update-flathub-repo.yml | 29 ++ .github/workflows/_update-homebrew-repo.yml | 31 ++ .github/workflows/_update-pacman-repo.yml | 29 ++ .github/workflows/_update-winget-repo.yml | 29 ++ .github/workflows/ci-docker.yml | 380 ------------------ .github/workflows/codeql.yml | 237 ----------- .github/workflows/common-lint.yml | 273 ------------- .github/workflows/release-notifier.yml | 137 ------- .github/workflows/update-docs.yml | 99 ----- .github/workflows/update-flathub-repo.yml | 187 --------- .github/workflows/update-homebrew-release.yml | 73 ---- .github/workflows/update-pacman-repo.yml | 134 ------ .github/workflows/update-winget-release.yml | 71 ---- 22 files changed, 280 insertions(+), 1653 deletions(-) delete mode 100644 .github/label-actions.yml create mode 100644 .github/workflows/_codeql.yml create mode 100644 .github/workflows/_common-lint.yml create mode 100644 .github/workflows/_docker.yml create mode 100644 .github/workflows/_release-notifier.yml rename .github/workflows/{update-changelog.yml => _update-changelog.yml} (52%) create mode 100644 .github/workflows/_update-docs.yml create mode 100644 .github/workflows/_update-flathub-repo.yml create mode 100644 .github/workflows/_update-homebrew-repo.yml create mode 100644 .github/workflows/_update-pacman-repo.yml create mode 100644 .github/workflows/_update-winget-repo.yml delete mode 100644 .github/workflows/ci-docker.yml delete mode 100644 .github/workflows/codeql.yml delete mode 100644 .github/workflows/common-lint.yml delete mode 100644 .github/workflows/release-notifier.yml delete mode 100644 .github/workflows/update-docs.yml delete mode 100644 .github/workflows/update-flathub-repo.yml delete mode 100644 .github/workflows/update-homebrew-release.yml delete mode 100644 .github/workflows/update-pacman-repo.yml delete mode 100644 .github/workflows/update-winget-release.yml diff --git a/.flake8 b/.flake8 index 2ea73951..a8948ef1 100644 --- a/.flake8 +++ b/.flake8 @@ -3,4 +3,5 @@ filename = *.py max-line-length = 120 extend-exclude = + .venv/ venv/ diff --git a/.github/label-actions.yml b/.github/label-actions.yml deleted file mode 100644 index 653cf860..00000000 --- a/.github/label-actions.yml +++ /dev/null @@ -1,49 +0,0 @@ ---- -# 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. - -# Configuration for Label Actions - https://github.com/dessant/label-actions - -added: - comment: > - This feature has been added and will be available in the next release. -fixed: - comment: > - This issue has been fixed and will be available in the next release. -invalid:duplicate: - comment: > - :wave: @{issue-author}, this appears to be a duplicate of a pre-existing issue. - close: true - lock: true - unlabel: 'status:awaiting-triage' - --invalid:duplicate: - reopen: true - unlock: true - -invalid:support: - comment: > - :wave: @{issue-author}, we use the issue tracker exclusively for bug reports. - However, this issue appears to be a support request. Please use our - [Support Center](https://app.lizardbyte.dev/support) for support issues. Thanks. - close: true - lock: true - lock-reason: 'off-topic' - unlabel: 'status:awaiting-triage' - --invalid:support: - reopen: true - unlock: true - -invalid:template-incomplete: - issues: - comment: > - :wave: @{issue-author}, please edit your issue to complete the template with - all the required info. Your issue will be automatically closed in 5 days if - the template is not completed. Thanks. - prs: - comment: > - :wave: @{issue-author}, please edit your PR to complete the template with - all the required info. Your PR will be automatically closed in 5 days if - the template is not completed. Thanks. diff --git a/.github/semantic.yml b/.github/semantic.yml index b5eb70d0..92d97a74 100644 --- a/.github/semantic.yml +++ b/.github/semantic.yml @@ -12,3 +12,4 @@ titleAndCommits: false anyCommit: false allowMergeCommits: false allowRevertCommits: false +targetUrl: https://docs.lizardbyte.dev/latest/developers/contributing.html#creating-a-pull-request diff --git a/.github/workflows/_codeql.yml b/.github/workflows/_codeql.yml new file mode 100644 index 00000000..cfa961cb --- /dev/null +++ b/.github/workflows/_codeql.yml @@ -0,0 +1,30 @@ +--- +# This workflow is centrally managed in https://github.com/LizardByte/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +name: CodeQL +permissions: + actions: read + contents: read + security-events: write + +on: + push: + branches: + - master + pull_request: + branches: + - master + schedule: + - cron: '00 12 * * 0' # every Sunday at 12:00 UTC + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + call-codeql: + name: CodeQL + uses: LizardByte/.github/.github/workflows/__call-codeql.yml@master + if: ${{ github.repository != 'LizardByte/.github' }} diff --git a/.github/workflows/_common-lint.yml b/.github/workflows/_common-lint.yml new file mode 100644 index 00000000..7c317130 --- /dev/null +++ b/.github/workflows/_common-lint.yml @@ -0,0 +1,27 @@ +--- +# This workflow is centrally managed in https://github.com/LizardByte/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +name: common lint +permissions: + contents: read + +on: + pull_request: + branches: + - master + types: + - opened + - synchronize + - reopened + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + lint: + name: Common Lint + uses: LizardByte/.github/.github/workflows/__call-common-lint.yml@master + if: ${{ github.repository != 'LizardByte/.github' }} diff --git a/.github/workflows/_docker.yml b/.github/workflows/_docker.yml new file mode 100644 index 00000000..f9cd482f --- /dev/null +++ b/.github/workflows/_docker.yml @@ -0,0 +1,39 @@ +--- +# This workflow is centrally managed in https://github.com/LizardByte/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +name: Docker +permissions: + contents: write + packages: write + +on: + pull_request: + branches: + - master + types: + - opened + - synchronize + - reopened + push: + branches: + - master + workflow_dispatch: + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + call-docker: + name: Docker + uses: LizardByte/.github/.github/workflows/__call-docker.yml@master + if: ${{ github.repository != 'LizardByte/.github' }} + secrets: + DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }} + DOCKER_HUB_PASSWORD: ${{ secrets.DOCKER_HUB_PASSWORD }} + DOCKER_HUB_ACCESS_TOKEN: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + GH_BOT_NAME: ${{ secrets.GH_BOT_NAME }} + GH_BOT_TOKEN: ${{ secrets.GH_BOT_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/_release-notifier.yml b/.github/workflows/_release-notifier.yml new file mode 100644 index 00000000..b32da784 --- /dev/null +++ b/.github/workflows/_release-notifier.yml @@ -0,0 +1,23 @@ +--- +# This workflow is centrally managed in https://github.com/LizardByte/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +# Create a blog post for a new release and open a PR to the blog repo + +name: Release Notifications +permissions: + contents: read + +on: + release: + types: + - released # this triggers when a release is published, but does not include pre-releases or drafts + +jobs: + update-blog: + name: Update blog + uses: LizardByte/.github/.github/workflows/__call-release-notifier.yml@master + if: github.repository_owner == 'LizardByte' + secrets: + GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/_update-changelog.yml similarity index 52% rename from .github/workflows/update-changelog.yml rename to .github/workflows/_update-changelog.yml index 35ed0b93..283e7770 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/_update-changelog.yml @@ -1,10 +1,8 @@ --- -# This workflow is centrally managed in https://github.com//.github/ +# This workflow is centrally managed in https://github.com/LizardByte/.github/ # Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in # the above-mentioned repo. -# Update changelog on release events. - name: Update changelog permissions: contents: read @@ -24,14 +22,10 @@ concurrency: jobs: update-changelog: name: Update Changelog + uses: LizardByte/.github/.github/workflows/__call-update-changelog.yml@master if: >- - github.event_name == 'workflow_dispatch' || - (!github.event.release.prerelease && !github.event.release.draft) - runs-on: ubuntu-latest - steps: - - name: Update Changelog - uses: LizardByte/update-changelog-action@v2025.426.173858 - with: - changelogBranch: changelog - changelogFile: CHANGELOG.md - token: ${{ secrets.GH_BOT_TOKEN }} + github.repository_owner == 'LizardByte' && + (github.event_name == 'workflow_dispatch' || + (!github.event.release.prerelease && !github.event.release.draft)) + secrets: + GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/_update-docs.yml b/.github/workflows/_update-docs.yml new file mode 100644 index 00000000..6dd66c55 --- /dev/null +++ b/.github/workflows/_update-docs.yml @@ -0,0 +1,34 @@ +--- +# This workflow is centrally managed in https://github.com/LizardByte/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +# To use, add the `rtd` repository label to identify repositories that should trigger this workflow. +# If the project slug is not the repository name, add a repository variable named `READTHEDOCS_SLUG` with the value of +# the ReadTheDocs project slug. + +# Update readthedocs on release events. + +name: Update docs +permissions: {} + +on: + release: + types: + - created + - edited + - deleted + +concurrency: + group: "${{ github.workflow }}-${{ github.event.release.tag_name }}" + cancel-in-progress: true + +jobs: + update-docs: + name: Update docs + uses: LizardByte/.github/.github/workflows/__call-update-docs.yml@master + if: github.repository_owner == 'LizardByte' + with: + readthedocs_slug: ${{ vars.READTHEDOCS_SLUG }} + secrets: + READTHEDOCS_TOKEN: ${{ secrets.READTHEDOCS_TOKEN }} diff --git a/.github/workflows/_update-flathub-repo.yml b/.github/workflows/_update-flathub-repo.yml new file mode 100644 index 00000000..1f4ba3c7 --- /dev/null +++ b/.github/workflows/_update-flathub-repo.yml @@ -0,0 +1,29 @@ +--- +# This workflow is centrally managed in https://github.com/LizardByte/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +# To use, add the `flathub-pkg` repository label to identify repositories that should trigger this workflow. + +# Update Flathub on release events. + +name: Update Flathub repo +permissions: + contents: read + +on: + release: + types: + - released + +concurrency: + group: "${{ github.workflow }}-${{ github.event.release.tag_name }}" + cancel-in-progress: true + +jobs: + update-flathub-repo: + name: Update Flathub Repo + uses: LizardByte/.github/.github/workflows/__call-update-flathub-repo.yml@master + if: github.repository_owner == 'LizardByte' + secrets: + GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/_update-homebrew-repo.yml b/.github/workflows/_update-homebrew-repo.yml new file mode 100644 index 00000000..947c7872 --- /dev/null +++ b/.github/workflows/_update-homebrew-repo.yml @@ -0,0 +1,31 @@ +--- +# This workflow is centrally managed in https://github.com/LizardByte/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +# To use, add the `homebrew-pkg` repository label to identify repositories that should trigger this workflow. + +# Update Homebrew on release events. + +name: Update Homebrew repo +permissions: + contents: read + +on: + release: + types: + - released + +concurrency: + group: "${{ github.workflow }}-${{ github.event.release.tag_name }}" + cancel-in-progress: true + +jobs: + update-homebrew-repo: + name: Update Homebrew repo + uses: LizardByte/.github/.github/workflows/__call-update-homebrew-repo.yml@master + if: github.repository_owner == 'LizardByte' + secrets: + GH_EMAIL: ${{ secrets.GH_BOT_EMAIL }} + GH_USERNAME: ${{ secrets.GH_BOT_NAME }} + GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/_update-pacman-repo.yml b/.github/workflows/_update-pacman-repo.yml new file mode 100644 index 00000000..c62b34a4 --- /dev/null +++ b/.github/workflows/_update-pacman-repo.yml @@ -0,0 +1,29 @@ +--- +# This workflow is centrally managed in https://github.com/LizardByte/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +# To use, add the `pacman-pkg` repository label to identify repositories that should trigger this workflow. + +# Update pacman repo on release events. + +name: Update pacman repo +permissions: + contents: read + +on: + release: + types: + - released + +concurrency: + group: "${{ github.workflow }}-${{ github.event.release.tag_name }}" + cancel-in-progress: true + +jobs: + update-homebrew-release: + name: Update pacman repo + uses: LizardByte/.github/.github/workflows/__call-update-pacman-repo.yml@master + if: github.repository_owner == 'LizardByte' + secrets: + GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/_update-winget-repo.yml b/.github/workflows/_update-winget-repo.yml new file mode 100644 index 00000000..1cac56bf --- /dev/null +++ b/.github/workflows/_update-winget-repo.yml @@ -0,0 +1,29 @@ +--- +# This workflow is centrally managed in https://github.com/LizardByte/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +# To use, add the `winget-pkg` repository label to identify repositories that should trigger this workflow. + +# Update Winget on release events. + +name: Update Winget repo +permissions: + contents: read + +on: + release: + types: + - released + +concurrency: + group: "${{ github.workflow }}-${{ github.event.release.tag_name }}" + cancel-in-progress: true + +jobs: + update-winget-repo: + name: Update Winget repo + uses: LizardByte/.github/.github/workflows/__call-update-winget-repo.yml@master + if: github.repository_owner == 'LizardByte' + secrets: + GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml deleted file mode 100644 index d307d369..00000000 --- a/.github/workflows/ci-docker.yml +++ /dev/null @@ -1,380 +0,0 @@ ---- -# This workflow 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 intended to work with all our organization Docker projects. A readme named `DOCKER_README.md` -# will be used to update the description on Docker hub. - -# custom comments in dockerfiles: - -# `# platforms: ` -# Comma separated list of platforms, i.e. `# platforms: linux/386,linux/amd64`. Docker platforms can alternatively -# be listed in a file named `.docker_platforms`. -# `# platforms_pr: ` -# Comma separated list of platforms to run for PR events, i.e. `# platforms_pr: linux/amd64`. This will take -# precedence over the `# platforms: ` directive. -# `# artifacts: ` -# `true` to build in two steps, stopping at `artifacts` build stage and extracting the image from there to the -# GitHub runner. - -name: CI Docker -permissions: - contents: read - -on: - pull_request: - branches: - - master - types: - - opened - - synchronize - - reopened - push: - branches: - - master - workflow_dispatch: - -concurrency: - group: "${{ github.workflow }}-${{ github.ref }}" - cancel-in-progress: true - -jobs: - check_dockerfiles: - name: Check Dockerfiles - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Find dockerfiles - id: find - run: | - dockerfiles=$(find . -type f -iname "Dockerfile" -o -iname "*.dockerfile") - - echo "found dockerfiles: ${dockerfiles}" - - # do not quote to keep this as a single line - echo dockerfiles=${dockerfiles} >> $GITHUB_OUTPUT - - MATRIX_COMBINATIONS="" - for FILE in ${dockerfiles}; do - # extract tag from file name - tag=$(echo $FILE | sed -r -z -e 's/(\.\/)*.*\/(Dockerfile)/None/gm') - if [[ $tag == "None" ]]; then - MATRIX_COMBINATIONS="$MATRIX_COMBINATIONS {\"dockerfile\": \"$FILE\"}," - else - tag=$(echo $FILE | sed -r -z -e 's/(\.\/)*.*\/(.+)(\.dockerfile)/-\2/gm') - MATRIX_COMBINATIONS="$MATRIX_COMBINATIONS {\"dockerfile\": \"$FILE\", \"tag\": \"$tag\"}," - fi - done - - # removes the last character (i.e. comma) - MATRIX_COMBINATIONS=${MATRIX_COMBINATIONS::-1} - - # setup matrix for later jobs - matrix=$(( - echo "{ \"include\": [$MATRIX_COMBINATIONS] }" - ) | jq -c .) - - echo $matrix - echo $matrix | jq . - echo "matrix=$matrix" >> $GITHUB_OUTPUT - - - name: Find dotnet solution file - id: find_dotnet - run: | - solution=$(find . -maxdepth 1 -type f -iname "*.sln") - - echo "found solution: ${solution}" - - # do not quote to keep this as a single line - echo solution=${solution} >> $GITHUB_OUTPUT - - if [[ $solution != "" ]]; then - echo "dotnet=true" >> $GITHUB_OUTPUT - else - echo "dotnet=false" >> $GITHUB_OUTPUT - fi - - outputs: - dockerfiles: ${{ steps.find.outputs.dockerfiles }} - matrix: ${{ steps.find.outputs.matrix }} - dotnet: ${{ steps.find_dotnet.outputs.dotnet }} - solution: ${{ steps.find_dotnet.outputs.solution }} - - setup_release: - name: Setup Release - if: needs.check_dockerfiles.outputs.dockerfiles - needs: check_dockerfiles - outputs: - publish_release: ${{ steps.setup_release.outputs.publish_release }} - release_body: ${{ steps.setup_release.outputs.release_body }} - release_commit: ${{ steps.setup_release.outputs.release_commit }} - release_generate_release_notes: ${{ steps.setup_release.outputs.release_generate_release_notes }} - release_tag: ${{ steps.setup_release.outputs.release_tag }} - release_version: ${{ steps.setup_release.outputs.release_version }} - permissions: - contents: write # read does not work to check squash and merge details - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Release - id: setup_release - uses: LizardByte/setup-release-action@v2025.426.225 - with: - dotnet: ${{ needs.check_dockerfiles.outputs.dotnet }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - docker: - name: Docker${{ matrix.tag }} - if: needs.check_dockerfiles.outputs.dockerfiles - needs: - - check_dockerfiles - - setup_release - permissions: - packages: write - contents: write - runs-on: ubuntu-22.04 - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.check_dockerfiles.outputs.matrix) }} - steps: - - name: Maximize build space - uses: easimon/maximize-build-space@v10 - with: - root-reserve-mb: 30720 # https://github.com/easimon/maximize-build-space#caveats - remove-dotnet: 'true' - remove-android: 'true' - remove-haskell: 'true' - remove-codeql: 'true' - remove-docker-images: 'true' - - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Prepare - id: prepare - env: - NV: ${{ needs.setup_release.outputs.release_tag }} - run: | - # get branch name - BRANCH=${GITHUB_HEAD_REF} - - RELEASE=${{ needs.setup_release.outputs.publish_release }} - COMMIT=${{ needs.setup_release.outputs.release_commit }} - - if [ -z "$BRANCH" ]; then - echo "This is a PUSH event" - BRANCH=${{ github.ref_name }} - CLONE_URL=${{ github.event.repository.clone_url }} - else - echo "This is a PULL REQUEST event" - CLONE_URL=${{ github.event.pull_request.head.repo.clone_url }} - fi - - # determine to push image to dockerhub and ghcr or not - if [[ $GITHUB_EVENT_NAME == "push" ]]; then - PUSH=true - else - PUSH=false - fi - - # setup the tags - REPOSITORY=${{ github.repository }} - BASE_TAG=$(echo $REPOSITORY | tr '[:upper:]' '[:lower:]') - - TAGS="${BASE_TAG}:${COMMIT:0:7}${{ matrix.tag }},ghcr.io/${BASE_TAG}:${COMMIT:0:7}${{ matrix.tag }}" - - if [[ $GITHUB_REF == refs/heads/master ]]; then - TAGS="${TAGS},${BASE_TAG}:latest${{ matrix.tag }},ghcr.io/${BASE_TAG}:latest${{ matrix.tag }}" - TAGS="${TAGS},${BASE_TAG}:master${{ matrix.tag }},ghcr.io/${BASE_TAG}:master${{ matrix.tag }}" - else - TAGS="${TAGS},${BASE_TAG}:test${{ matrix.tag }},ghcr.io/${BASE_TAG}:test${{ matrix.tag }}" - fi - - if [[ ${NV} != "" ]]; then - TAGS="${TAGS},${BASE_TAG}:${NV}${{ matrix.tag }},ghcr.io/${BASE_TAG}:${NV}${{ matrix.tag }}" - fi - - # parse custom directives out of dockerfile - # try to get the platforms from the dockerfile custom directive, i.e. `# platforms: xxx,yyy` - # directives for PR event, i.e. not push event - if [[ ${RELEASE} == "false" ]]; then - while read -r line; do - if [[ $line == "# platforms_pr: "* && $PLATFORMS == "" ]]; then - # echo the line and use `sed` to remove the custom directive - PLATFORMS=$(echo -e "$line" | sed 's/# platforms_pr: //') - elif [[ $PLATFORMS != "" ]]; then - # break while loop once all custom "PR" event directives are found - break - fi - done <"${{ matrix.dockerfile }}" - fi - # directives for all events... above directives will not be parsed if they were already found - while read -r line; do - if [[ $line == "# platforms: "* && $PLATFORMS == "" ]]; then - # echo the line and use `sed` to remove the custom directive - PLATFORMS=$(echo -e "$line" | sed 's/# platforms: //') - elif [[ $line == "# artifacts: "* && $ARTIFACTS == "" ]]; then - # echo the line and use `sed` to remove the custom directive - ARTIFACTS=$(echo -e "$line" | sed 's/# artifacts: //') - elif [[ $line == "# no-cache-filters: "* && $NO_CACHE_FILTERS == "" ]]; then - # echo the line and use `sed` to remove the custom directive - NO_CACHE_FILTERS=$(echo -e "$line" | sed 's/# no-cache-filters: //') - elif [[ $PLATFORMS != "" && $ARTIFACTS != "" && $NO_CACHE_FILTERS != "" ]]; then - # break while loop once all custom directives are found - break - fi - done <"${{ matrix.dockerfile }}" - # if PLATFORMS is blank, fall back to the legacy method of reading from the `.docker_platforms` file - if [[ $PLATFORMS == "" ]]; then - # read the platforms from `.docker_platforms` - PLATFORMS=$(<.docker_platforms) - fi - # if PLATFORMS is still blank, fall back to `linux/amd64` - if [[ $PLATFORMS == "" ]]; then - PLATFORMS="linux/amd64" - fi - - echo "branch=${BRANCH}" >> $GITHUB_OUTPUT - echo "build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT - echo "clone_url=${CLONE_URL}" >> $GITHUB_OUTPUT - echo "artifacts=${ARTIFACTS}" >> $GITHUB_OUTPUT - echo "no_cache_filters=${NO_CACHE_FILTERS}" >> $GITHUB_OUTPUT - echo "platforms=${PLATFORMS}" >> $GITHUB_OUTPUT - echo "tags=${TAGS}" >> $GITHUB_OUTPUT - - - name: Set Up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - id: buildx - - - name: Cache Docker Layers - uses: actions/cache@v4 - with: - path: /tmp/.buildx-cache - key: Docker-buildx${{ matrix.tag }}-${{ github.sha }} - restore-keys: | - Docker-buildx${{ matrix.tag }}- - - - name: Log in to Docker Hub - if: needs.setup_release.outputs.publish_release == 'true' # PRs do not have access to secrets - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - - - name: Log in to the Container registry - if: needs.setup_release.outputs.publish_release == 'true' # PRs do not have access to secrets - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ secrets.GH_BOT_NAME }} - password: ${{ secrets.GH_BOT_TOKEN }} - - - name: Build artifacts - if: steps.prepare.outputs.artifacts == 'true' - id: build_artifacts - uses: docker/build-push-action@v6 - with: - context: ./ - file: ${{ matrix.dockerfile }} - target: artifacts - outputs: type=local,dest=artifacts - push: false - platforms: ${{ steps.prepare.outputs.platforms }} - build-args: | - BRANCH=${{ steps.prepare.outputs.branch }} - BUILD_DATE=${{ steps.prepare.outputs.build_date }} - BUILD_VERSION=${{ needs.setup_release.outputs.release_tag }} - COMMIT=${{ needs.setup_release.outputs.release_commit }} - CLONE_URL=${{ steps.prepare.outputs.clone_url }} - RELEASE=${{ needs.setup_release.outputs.publish_release }} - tags: ${{ steps.prepare.outputs.tags }} - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache - no-cache-filters: ${{ steps.prepare.outputs.no_cache_filters }} - - - name: Build and push - id: build - uses: docker/build-push-action@v6 - with: - context: ./ - file: ${{ matrix.dockerfile }} - push: ${{ needs.setup_release.outputs.publish_release }} - platforms: ${{ steps.prepare.outputs.platforms }} - build-args: | - BRANCH=${{ steps.prepare.outputs.branch }} - BUILD_DATE=${{ steps.prepare.outputs.build_date }} - BUILD_VERSION=${{ needs.setup_release.outputs.release_tag }} - COMMIT=${{ needs.setup_release.outputs.release_commit }} - CLONE_URL=${{ steps.prepare.outputs.clone_url }} - RELEASE=${{ needs.setup_release.outputs.publish_release }} - tags: ${{ steps.prepare.outputs.tags }} - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache - no-cache-filters: ${{ steps.prepare.outputs.no_cache_filters }} - - - name: Arrange Artifacts - if: steps.prepare.outputs.artifacts == 'true' - working-directory: artifacts - run: | - # debug directory - echo "Current directory: $(pwd)" - echo "Directory contents: $(ls -Ra)" - - # artifacts will be in sub directories named after the docker target platform, e.g. `linux_amd64` - # so move files to the artifacts directory - # https://unix.stackexchange.com/a/52816 - find \ - ./ \ - -maxdepth 2 \ - -mindepth 2 \ - -type f \ - -not -name 'provenance.json' \ - -exec mv -t ./ -n '{}' + - - # remove provenance file - rm -f ./provenance.json - - - name: Upload Artifacts - if: steps.prepare.outputs.artifacts == 'true' - uses: actions/upload-artifact@v4 - with: - name: Docker${{ matrix.tag }} - path: artifacts/ - if-no-files-found: error - - - name: Create/Update GitHub Release - if: > - needs.setup_release.outputs.publish_release == 'true' && - steps.prepare.outputs.artifacts == 'true' - uses: LizardByte/create-release-action@v2025.426.1549 - with: - allowUpdates: true - artifacts: "*artifacts/*" - body: ${{ needs.setup_release.outputs.release_body }} - generateReleaseNotes: ${{ needs.setup_release.outputs.release_generate_release_notes }} - name: ${{ needs.setup_release.outputs.release_tag }} - prerelease: true - tag: ${{ needs.setup_release.outputs.release_tag }} - token: ${{ secrets.GH_BOT_TOKEN }} - - - name: Update Docker Hub Description - if: > - github.event_name == 'push' && - github.ref == 'refs/heads/master' - uses: peter-evans/dockerhub-description@v4 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_PASSWORD }} # token is not currently supported - repository: ${{ env.BASE_TAG }} - short-description: ${{ github.event.repository.description }} - readme-filepath: ./DOCKER_README.md diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 24a4945e..00000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,237 +0,0 @@ ---- -# This workflow 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 will analyze all supported languages in the repository using CodeQL Analysis. - -name: "CodeQL" -permissions: - contents: read - -on: - push: - branches: - - master - pull_request: - branches: - - master - schedule: - - cron: '00 12 * * 0' # every Sunday at 12:00 UTC - -concurrency: - group: "${{ github.workflow }}-${{ github.ref }}" - cancel-in-progress: true - -jobs: - languages: - name: Get language matrix - outputs: - matrix: ${{ steps.lang.outputs.result }} - continue: ${{ steps.continue.outputs.result }} - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Get repo languages - id: lang - uses: actions/github-script@v7 - with: - script: | - // CodeQL supports the following: - // ['actions', 'c', 'cpp', 'csharp', 'go', 'java', 'javascript', 'kotlin', 'python', 'ruby', 'swift'] - - // Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - const supported_languages = [ - 'cpp', - 'csharp', - 'go', - 'java', - 'javascript', - 'python', - 'ruby', - 'swift', - ] - - const remap_languages = { - 'c': 'cpp', - 'c++': 'cpp', - 'c#': 'csharp', - 'kotlin': 'java', - 'typescript': 'javascript', - } - - const repo = context.repo - const response = await github.rest.repos.listLanguages(repo) - let matrix = { - "include": [] - } - - // Track languages we've already added to avoid duplicates - const addedLanguages = new Set() - - // Check if workflow files exist to determine if we should add actions language - const fs = require('fs'); - const hasYmlFiles = fs.existsSync('.github/workflows') && - fs.readdirSync('.github/workflows').some(file => file.endsWith('.yml') || file.endsWith('.yaml')); - - // Add actions language if workflow files exist - if (hasYmlFiles) { - console.log('Found GitHub Actions workflow files. Adding actions to the matrix.'); - matrix['include'].push({ - "category": "/language:actions", - "language": "actions", - "name": "actions", - "os": "ubuntu-latest", - "build-mode": "none", - }); - } - - for (let [key, value] of Object.entries(response.data)) { - // remap language - if (remap_languages[key.toLowerCase()]) { - console.log(`Remapping language: ${key} to ${remap_languages[key.toLowerCase()]}`) - key = remap_languages[key.toLowerCase()] - } - - const normalizedKey = key.toLowerCase() - - if (supported_languages.includes(normalizedKey) && !addedLanguages.has(normalizedKey)) { - // Mark this language as added - addedLanguages.add(normalizedKey) - - console.log(`Found supported language: ${normalizedKey}`) - let osList = ['ubuntu-latest']; - if (normalizedKey === 'swift') { - osList = ['macos-latest']; - } - for (let os of osList) { - // set name for matrix - let name = osList.length === 1 ? normalizedKey : `${normalizedKey}, ${os}` - - // set category for matrix - let category = `/language:${normalizedKey}` - let build_mode = 'none'; - - // Set build mode based on language - switch (normalizedKey) { - case 'csharp': - build_mode = 'autobuild' - break - case 'go': - build_mode = 'autobuild' - break - case 'java': - build_mode = 'autobuild' - break - default: - build_mode = 'none' - } - - // add to matrix - matrix['include'].push({ - "category": category, - "language": normalizedKey, - "name": name, - "os": os, - "build-mode": build_mode, - }) - } - } - } - - // print languages - console.log(`matrix: ${JSON.stringify(matrix)}`) - - return matrix - - - name: Continue - id: continue - uses: actions/github-script@v7 - with: - script: | - // if matrix['include'] is an empty list return false, otherwise true - const matrix = ${{ steps.lang.outputs.result }} // this is already json encoded - - if (matrix['include'].length == 0) { - return false - } else { - return true - } - - analyze: - name: Analyze (${{ matrix.name }}) - if: needs.languages.outputs.continue == 'true' - env: - GITHUB_CODEQL_BUILD: true - needs: languages - permissions: - actions: read - contents: read - security-events: write - runs-on: ${{ matrix.os || 'ubuntu-latest' }} - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.languages.outputs.matrix) }} - timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 60 }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - submodules: recursive - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # yamllint disable-line rule:line-length - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - config: | - paths-ignore: - - build - - node_modules - - third-party - build-mode: ${{ matrix.build-mode || 'none' }} - - # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). - - name: Autobuild - if: matrix.build-mode == 'autobuild' - uses: github/codeql-action/autobuild@v3 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "${{ matrix.category }}" - output: sarif-results - upload: failure-only - - - name: filter-sarif - uses: advanced-security/filter-sarif@v1 - with: - input: sarif-results/${{ matrix.language }}.sarif - output: sarif-results/${{ matrix.language }}.sarif - patterns: | - -build/** - -node_modules/** - -third\-party/** - - - name: Upload SARIF - uses: github/codeql-action/upload-sarif@v3 - with: - category: "${{ matrix.category }}" - sarif_file: sarif-results/${{ matrix.language }}.sarif - - - name: Upload loc as a Build Artifact - uses: actions/upload-artifact@v4 - with: - name: sarif-results-${{ matrix.language }}-${{ runner.os }} - path: sarif-results - if-no-files-found: error - retention-days: 1 diff --git a/.github/workflows/common-lint.yml b/.github/workflows/common-lint.yml deleted file mode 100644 index 524be6ff..00000000 --- a/.github/workflows/common-lint.yml +++ /dev/null @@ -1,273 +0,0 @@ ---- -# This workflow 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. - -# Common linting. - -name: common lint -permissions: - contents: read - -on: - pull_request: - branches: - - master - types: - - opened - - synchronize - - reopened - -concurrency: - group: "${{ github.workflow }}-${{ github.ref }}" - cancel-in-progress: true - -jobs: - lint: - name: Common Lint - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Install dependencies - run: | - python -m pip install --upgrade \ - pip \ - setuptools \ - wheel \ - cmakelang \ - flake8 \ - nb-clean \ - nbqa[toolchain] - - - name: C++ - find files - id: cpp_files - run: | - # find files - found_files=$(find . -type f \ - -iname "*.c" -o \ - -iname "*.cpp" -o \ - -iname "*.h" -o \ - -iname "*.hpp" -o \ - -iname "*.m" -o \ - -iname "*.mm" \ - ) - ignore_files=$(find . -type f -iname ".clang-format-ignore") - - # Loop through each C++ file - for file in $found_files; do - for ignore_file in $ignore_files; do - ignore_directory=$(dirname "$ignore_file") - # if directory of ignore_file is beginning of file - if [[ "$file" == "$ignore_directory"* ]]; then - echo "ignoring file: ${file}" - found_files="${found_files//${file}/}" - break 1 - fi - done - done - - # remove empty lines - found_files=$(echo "$found_files" | sed '/^\s*$/d') - - echo "found cpp files: ${found_files}" - - # do not quote to keep this as a single line - echo found_files=${found_files} >> $GITHUB_OUTPUT - - - name: C++ - Clang format lint - if: always() && steps.cpp_files.outputs.found_files - uses: DoozyX/clang-format-lint-action@v0.20 - with: - source: ${{ steps.cpp_files.outputs.found_files }} - clangFormatVersion: '20' - extensions: 'c,cpp,h,hpp,m,mm' - style: file - inplace: false - - - name: CMake - find files - id: cmake_files - if: always() - run: | - # find files - found_files=$(find . -type f -iname "CMakeLists.txt" -o -iname "*.cmake") - ignore_files=$(find . -type f -iname ".cmake-lint-ignore") - - # Loop through each C++ file - for file in $found_files; do - for ignore_file in $ignore_files; do - ignore_directory=$(dirname "$ignore_file") - # if directory of ignore_file is beginning of file - if [[ "$file" == "$ignore_directory"* ]]; then - echo "ignoring file: ${file}" - found_files="${found_files//${file}/}" - break 1 - fi - done - done - - # remove empty lines - found_files=$(echo "$found_files" | sed '/^\s*$/d') - - echo "found cmake files: ${found_files}" - - # do not quote to keep this as a single line - echo found_files=${found_files} >> $GITHUB_OUTPUT - - - name: CMake - cmake-lint - if: always() && steps.cmake_files.outputs.found_files - run: | - cmake-lint --line-width 120 --tab-size 4 ${{ steps.cmake_files.outputs.found_files }} - - - name: Docker - find files - id: dokcer_files - if: always() - run: | - found_files=$(find . -type f -iname "Dockerfile" -o -iname "*.dockerfile") - - echo "found_files: ${found_files}" - - # do not quote to keep this as a single line - echo found_files=${found_files} >> $GITHUB_OUTPUT - - - name: Docker - hadolint - if: always() && steps.dokcer_files.outputs.found_files - run: | - docker pull hadolint/hadolint - - # create hadolint config file - cat < .hadolint.yaml - --- - ignored: - - DL3008 - - DL3013 - - DL3016 - - DL3018 - - DL3028 - - DL3059 - EOF - - failed=0 - failed_files="" - - for file in ${{ steps.dokcer_files.outputs.found_files }}; do - echo "::group::${file}" - docker run --rm -i \ - -e "NO_COLOR=0" \ - -e "HADOLINT_VERBOSE=1" \ - -v $(pwd)/.hadolint.yaml:/.config/hadolint.yaml \ - hadolint/hadolint < $file || { - failed=1 - failed_files="$failed_files $file" - } - echo "::endgroup::" - done - - if [ $failed -ne 0 ]; then - echo "::error:: hadolint failed for the following files: $failed_files" - exit 1 - fi - - - name: Python - flake8 - if: always() - run: | - python -m flake8 \ - --color=always \ - --verbose - - - name: Python - nbqa flake8 - if: always() - run: | - python -m nbqa flake8 \ - --color=always \ - --verbose \ - . - - - name: Python - nb-clean - if: always() - run: | - output=$(find . -name '*.ipynb' -exec nb-clean check {} \;) - - # fail if there are any issues - if [ -n "$output" ]; then - echo "$output" - exit 1 - fi - - - name: Rust - find Cargo.toml - id: run_cargo - if: always() - run: | - # check if Cargo.toml exists - if [ -f "Cargo.toml" ]; then - echo "found_cargo=true" >> $GITHUB_OUTPUT - else - echo "found_cargo=false" >> $GITHUB_OUTPUT - fi - - - name: Rust - setup toolchain - if: always() && steps.run_cargo.outputs.found_cargo == 'true' - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt - - - name: Rust - cargo fmt - if: always() && steps.run_cargo.outputs.found_cargo == 'true' - run: | - cargo fmt -- --check - - - name: YAML - find files - id: yaml_files - if: always() - run: | - # space separated list of files - FILES=.clang-format - - # empty placeholder - found_files="" - - for FILE in ${FILES}; do - if [ -f "$FILE" ] - then - found_files="$found_files $FILE" - fi - done - - echo "found_files=${found_files}" >> $GITHUB_OUTPUT - - - name: YAML - yamllint - id: yamllint - if: always() - uses: ibiqlik/action-yamllint@v3 - with: - # https://yamllint.readthedocs.io/en/stable/configuration.html#default-configuration - config_data: | - extends: default - rules: - comments: - level: error - document-start: - level: error - line-length: - max: 120 - new-line-at-end-of-file: - level: error - new-lines: - type: unix - truthy: - # GitHub uses "on" for workflow event triggers - # .clang-format file has options of "Yes" "No" that will be caught by this, so changed to "warning" - allowed-values: ['true', 'false', 'on'] - check-keys: true - level: warning - file_or_dir: . ${{ steps.yaml_files.outputs.found_files }} - - - name: YAML - log - if: always() && steps.yamllint.outcome == 'failure' - run: cat "${{ steps.yamllint.outputs.logfile }}" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release-notifier.yml b/.github/workflows/release-notifier.yml deleted file mode 100644 index d724abf3..00000000 --- a/.github/workflows/release-notifier.yml +++ /dev/null @@ -1,137 +0,0 @@ ---- -# This workflow 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. - -# Create a blog post for a new release and open a PR to the blog repo - -name: Release Notifications -permissions: - contents: read - -on: - release: - types: - - released # this triggers when a release is published, but does not include pre-releases or drafts - -jobs: - update-blog: - name: Update blog - if: github.repository_owner == 'LizardByte' - runs-on: ubuntu-latest - steps: - - name: Check topics - env: - TOPIC: replicator-release-notifications - id: check-label - uses: actions/github-script@v7 - with: - script: | - const topic = process.env.TOPIC; - console.log(`Checking if repo has topic: ${topic}`); - - const repoTopics = await github.rest.repos.getAllTopics({ - owner: context.repo.owner, - repo: context.repo.repo - }); - console.log(`Repo topics: ${repoTopics.data.names}`); - - const hasTopic = repoTopics.data.names.includes(topic); - console.log(`Has topic: ${hasTopic}`); - - core.setOutput('hasTopic', hasTopic); - - - name: Check if latest GitHub release - id: check-release - if: steps.check-label.outputs.hasTopic == 'true' - uses: actions/github-script@v7 - with: - script: | - const latestRelease = await github.rest.repos.getLatestRelease({ - owner: context.repo.owner, - repo: context.repo.repo - }); - - core.setOutput('isLatestRelease', latestRelease.data.tag_name === context.payload.release.tag_name); - - - name: Checkout blog - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' - uses: actions/checkout@v4 - with: - repository: "LizardByte/LizardByte.github.io" - - - name: Create blog post - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' - run: | - # setup variables - tag_name="${{ github.event.release.tag_name }}" - semver="${tag_name#v}" - repo_lower="$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]')" - - # extract year, month, and day - year="${semver%%.*}" - month_day="${semver#*.}" - month_day="${month_day%%.*}" - - # ensure month_day is 4 digits - month_day=$(printf "%04d" "$month_day") - - # create the filename - file_name="_posts/releases/${repo_lower}/${year}-${month_day:0:2}-${month_day:2:2}-v${semver}.md" - mkdir -p "$(dirname "${file_name}")" - - # create jekyll blog post - echo "---" > "${file_name}" - echo "layout: release" >> "${file_name}" - echo "title: ${{ github.event.repository.name }} ${tag_name} Released" >> "${file_name}" - echo "release-tag: ${tag_name}" >> "${file_name}" - echo "gh-repo: ${{ github.repository }}" >> "${file_name}" - echo "gh-badge: [follow, fork, star]" >> "${file_name}" - echo "tags: [release, ${repo_lower}]" >> "${file_name}" - echo "comments: true" >> "${file_name}" - echo "author: LizardByte-bot" >> "${file_name}" - echo "---" >> "${file_name}" - echo "" >> "${file_name}" - - release_body=$(cat <> "${file_name}" - - - name: Create/Update Pull Request - id: create-pr - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' - uses: peter-evans/create-pull-request@v7 - with: - token: ${{ secrets.GH_BOT_TOKEN }} - commit-message: | - chore: Add blog post for ${{ github.event.repository.name }} release ${{ github.event.release.tag_name }} - branch: bot/add-${{ github.event.repository.name }}-${{ github.event.release.tag_name }} - delete-branch: true - title: | - chore: Add blog post for ${{ github.event.repository.name }} release ${{ github.event.release.tag_name }} - body: ${{ github.event.release.body }} - labels: - blog - - - name: Automerge PR - env: - GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' - run: | - gh pr merge \ - --auto \ - --delete-branch \ - --repo "LizardByte/LizardByte.github.io" \ - --squash \ - "${{ steps.create-pr.outputs.pull-request-number }}" diff --git a/.github/workflows/update-docs.yml b/.github/workflows/update-docs.yml deleted file mode 100644 index 04e1e903..00000000 --- a/.github/workflows/update-docs.yml +++ /dev/null @@ -1,99 +0,0 @@ ---- -# This workflow 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. - -# To use, add the `rtd` repository label to identify repositories that should trigger this workflow. -# If the project slug is not the repository name, add a repository variable named `READTHEDOCS_SLUG` with the value of -# the ReadTheDocs project slug. - -# Update readthedocs on release events. - -name: Update docs -permissions: {} - -on: - release: - types: - - created - - edited - - deleted - -concurrency: - group: "${{ github.workflow }}-${{ github.event.release.tag_name }}" - cancel-in-progress: true - -jobs: - update-docs: - env: - RTD_SLUG: ${{ vars.READTHEDOCS_SLUG }} - RTD_TOKEN: ${{ secrets.READTHEDOCS_TOKEN }} - TAG: ${{ github.event.release.tag_name }} - if: >- - !github.event.release.draft - runs-on: ubuntu-latest - steps: - - name: Get RTD_SLUG - run: | - # if the RTD_SLUG is not set, use the repository name in lowercase - if [ -z "${RTD_SLUG}" ]; then - RTD_SLUG=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]') - fi - echo "RTD_SLUG=${RTD_SLUG}" >> $GITHUB_ENV - - - name: Deactivate deleted release - if: >- - github.event_name == 'release' && - github.event.action == 'deleted' - run: | - json_body=$(jq -n \ - --arg active "false" \ - --arg hidden "false" \ - --arg privacy_level "public" \ - '{active: $active, hidden: $hidden, privacy_level: $privacy_level}') - - curl \ - -X PATCH \ - -H "Authorization: Token ${RTD_TOKEN}" \ - https://readthedocs.org/api/v3/projects/${RTD_SLUG}/versions/${TAG}/ \ - -H "Content-Type: application/json" \ - -d "$json_body" - - - name: Check if edited release is latest GitHub release - id: check - if: >- - github.event_name == 'release' && - github.event.action == 'edited' - uses: actions/github-script@v7 - with: - script: | - const latestRelease = await github.rest.repos.getLatestRelease({ - owner: context.repo.owner, - repo: context.repo.repo - }); - - core.setOutput('isLatestRelease', latestRelease.data.tag_name === context.payload.release.tag_name); - - - name: Update RTD project - # changing the default branch in readthedocs makes "latest" point to that branch/tag - # we can also update other properties like description, etc. - if: steps.check.outputs.isLatestRelease == 'true' - run: | - json_body=$(jq -n \ - --arg default_branch "${TAG}" \ - --arg description "${{ github.event.repository.description }}" \ - '{default_branch: $default_branch}') - - # change the default branch to the latest release - curl \ - -X PATCH \ - -H "Authorization: Token ${RTD_TOKEN}" \ - -H "Content-Type: application/json" \ - https://readthedocs.org/api/v3/projects/${RTD_SLUG}/ \ - -d "$json_body" - - # trigger a build for the latest version - curl \ - -X POST \ - -H "Authorization: Token ${RTD_TOKEN}" \ - https://readthedocs.org/api/v3/projects/${RTD_SLUG}/versions/latest/builds/ diff --git a/.github/workflows/update-flathub-repo.yml b/.github/workflows/update-flathub-repo.yml deleted file mode 100644 index b1004cb1..00000000 --- a/.github/workflows/update-flathub-repo.yml +++ /dev/null @@ -1,187 +0,0 @@ ---- -# This workflow 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. - -# To use, add the `flathub-pkg` repository label to identify repositories that should trigger this workflow. - -# Update Flathub on release events. - -name: Update flathub repo -permissions: - contents: read - -on: - release: - types: - - released - -concurrency: - group: "${{ github.workflow }}-${{ github.event.release.tag_name }}" - cancel-in-progress: true - -jobs: - update-flathub-repo: - env: - FLATHUB_PKG: dev.lizardbyte.app.${{ github.event.repository.name }} - if: github.repository_owner == 'LizardByte' - runs-on: ubuntu-latest - steps: - - name: Check if flathub repo - id: check-label - env: - TOPIC: flathub-pkg - uses: actions/github-script@v7 - with: - script: | - const topic = process.env.TOPIC; - console.log(`Checking if repo has topic: ${topic}`); - - const repoTopics = await github.rest.repos.getAllTopics({ - owner: context.repo.owner, - repo: context.repo.repo - }); - console.log(`Repo topics: ${repoTopics.data.names}`); - - const hasTopic = repoTopics.data.names.includes(topic); - console.log(`Has topic: ${hasTopic}`); - - core.setOutput('hasTopic', hasTopic); - - - name: Check if latest GitHub release - id: check-release - if: steps.check-label.outputs.hasTopic == 'true' - uses: actions/github-script@v7 - with: - script: | - const latestRelease = await github.rest.repos.getLatestRelease({ - owner: context.repo.owner, - repo: context.repo.repo - }); - - core.setOutput('isLatestRelease', latestRelease.data.tag_name === context.payload.release.tag_name); - - - name: Checkout - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' - uses: actions/checkout@v4 - - - name: Checkout flathub-repo - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' - uses: actions/checkout@v4 - with: - repository: "flathub/${{ env.FLATHUB_PKG }}" - path: "flathub/${{ env.FLATHUB_PKG }}" - - - name: Clean up legacy files - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' - working-directory: flathub/${{ env.FLATHUB_PKG }} - run: | - rm -rf ./* - - - name: Copy github files - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' - working-directory: flathub/${{ env.FLATHUB_PKG }} - run: | - mkdir -p .github/ISSUE_TEMPLATE - - # sponsors - curl -sSL https://github.com/LizardByte/.github/raw/refs/heads/master/.github/FUNDING.yml \ - -o .github/FUNDING.yml - # pull request template - curl -sSL https://github.com/LizardByte/.github/raw/refs/heads/master/.github/pull_request_template.md \ - -o .github/pull_request_template.md - # issue config - curl -sSL https://github.com/LizardByte/.github/raw/refs/heads/master/.github/ISSUE_TEMPLATE/config.yml \ - -o .github/ISSUE_TEMPLATE/config.yml - - - name: Download release asset - id: download - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' - uses: robinraju/release-downloader@v1.12 - with: - repository: "${{ github.repository }}" - tag: "${{ github.event.release.tag_name }}" - fileName: "flathub.tar.gz" - tarBall: false - zipBall: false - out-file-path: "flathub/${{ env.FLATHUB_PKG }}" - extract: true - - - name: Delete archive - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' - run: | - rm -f flathub/${{ env.FLATHUB_PKG }}/flathub.tar.gz - - - name: Update metainfo.xml - id: update_metainfo - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' - run: | - xml_file="flathub/${{ env.FLATHUB_PKG }}/${{ env.FLATHUB_PKG }}.metainfo.xml" - - # Extract release information - changelog="${{ github.event.release.body }}" && changelog="${changelog//&/&}" && \ - changelog="${changelog///>}" - - # Replace changelog placeholder with actual changelog - sed -i "s||$changelog|g" "$xml_file" - - - name: Update submodule - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' - run: | - # Get the current commit of the submodule in the main repository - git submodule update --init packaging/linux/flatpak/deps/shared-modules - cd ${{ github.workspace }}/packaging/linux/flatpak/deps/shared-modules - main_commit=$(git rev-parse HEAD) - - # update submodules - cd ${{ github.workspace }}/flathub/${{ env.FLATHUB_PKG }} - git submodule update --init shared-modules - cd shared-modules - git checkout $main_commit - - - name: Create/Update Pull Request - id: create-pr - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' && - fromJson(steps.download.outputs.downloaded_files)[0] - uses: peter-evans/create-pull-request@v7 - with: - path: "flathub/${{ env.FLATHUB_PKG }}" - token: ${{ secrets.GH_BOT_TOKEN }} - commit-message: "chore: Update ${{ env.FLATHUB_PKG }} to ${{ github.event.release.tag_name }}" - branch: bot/bump-${{ env.FLATHUB_PKG }}-${{ github.event.release.tag_name }} - delete-branch: true - title: "chore: Update ${{ env.FLATHUB_PKG }} to ${{ github.event.release.tag_name }}" - body: ${{ github.event.release.body }} - - - name: Automerge PR - env: - GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' && - fromJson(steps.download.outputs.downloaded_files)[0] - run: | - gh pr merge \ - --auto \ - --delete-branch \ - --repo "flathub/${{ env.FLATHUB_PKG }}" \ - --squash \ - "${{ steps.create-pr.outputs.pull-request-number }}" diff --git a/.github/workflows/update-homebrew-release.yml b/.github/workflows/update-homebrew-release.yml deleted file mode 100644 index 10e03d59..00000000 --- a/.github/workflows/update-homebrew-release.yml +++ /dev/null @@ -1,73 +0,0 @@ ---- -# This workflow 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. - -# To use, add the `homebrew-pkg` repository label to identify repositories that should trigger this workflow. - -# Update Homebrew on release events. - -name: Update Homebrew release -permissions: - contents: read - -on: - release: - types: - - released - -concurrency: - group: "${{ github.workflow }}-${{ github.event.release.tag_name }}" - cancel-in-progress: true - -jobs: - update-homebrew-release: - if: github.repository_owner == 'LizardByte' - runs-on: ubuntu-latest - steps: - - name: Check if Homebrew repo - id: check-label - env: - TOPIC: homebrew-pkg - uses: actions/github-script@v7 - with: - script: | - const topic = process.env.TOPIC; - console.log(`Checking if repo has topic: ${topic}`); - - const repoTopics = await github.rest.repos.getAllTopics({ - owner: context.repo.owner, - repo: context.repo.repo - }); - console.log(`Repo topics: ${repoTopics.data.names}`); - - const hasTopic = repoTopics.data.names.includes(topic); - console.log(`Has topic: ${hasTopic}`); - - core.setOutput('hasTopic', hasTopic); - - - name: Download release asset - id: download - if: steps.check-label.outputs.hasTopic == 'true' - uses: robinraju/release-downloader@v1.12 - with: - repository: "${{ github.repository }}" - tag: "${{ github.event.release.tag_name }}" - fileName: "*.rb" - tarBall: false - zipBall: false - out-file-path: "release_downloads" - extract: false - - - name: Publish Homebrew Formula - if: >- - steps.check-label.outputs.hasTopic == 'true' && - fromJson(steps.download.outputs.downloaded_files)[0] - uses: LizardByte/homebrew-release-action@v2025.506.15440 - with: - formula_file: ${{ fromJson(steps.download.outputs.downloaded_files)[0] }} - git_email: ${{ secrets.GH_BOT_EMAIL }} - git_username: ${{ secrets.GH_BOT_NAME }} - publish: true - token: ${{ secrets.GH_BOT_TOKEN }} - validate: false diff --git a/.github/workflows/update-pacman-repo.yml b/.github/workflows/update-pacman-repo.yml deleted file mode 100644 index a0fd8183..00000000 --- a/.github/workflows/update-pacman-repo.yml +++ /dev/null @@ -1,134 +0,0 @@ ---- -# This workflow 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. - -# To use, add the `pacman-pkg` repository label to identify repositories that should trigger this workflow. - -# Update pacman repo on release events. - -name: Update pacman repo -permissions: - contents: read - -on: - release: - types: - - released - -concurrency: - group: "${{ github.workflow }}-${{ github.event.release.tag_name }}" - cancel-in-progress: true - -jobs: - update-homebrew-release: - if: github.repository_owner == 'LizardByte' - runs-on: ubuntu-latest - steps: - - name: Check if pacman repo - id: check-label - env: - TOPIC: pacman-pkg - uses: actions/github-script@v7 - with: - script: | - const topic = process.env.TOPIC; - console.log(`Checking if repo has topic: ${topic}`); - - const repoTopics = await github.rest.repos.getAllTopics({ - owner: context.repo.owner, - repo: context.repo.repo - }); - console.log(`Repo topics: ${repoTopics.data.names}`); - - const hasTopic = repoTopics.data.names.includes(topic); - console.log(`Has topic: ${hasTopic}`); - - core.setOutput('hasTopic', hasTopic); - - - name: Check if latest GitHub release - id: check-release - if: >- - steps.check-label.outputs.hasTopic == 'true' - uses: actions/github-script@v7 - with: - script: | - const latestRelease = await github.rest.repos.getLatestRelease({ - owner: context.repo.owner, - repo: context.repo.repo - }); - - core.setOutput('isLatestRelease', latestRelease.data.tag_name === context.payload.release.tag_name); - - - name: Checkout pacman-repo - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' - uses: actions/checkout@v4 - with: - repository: ${{ github.repository_owner }}/pacman-repo - - - name: Prep - id: prep - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' - run: | - echo "pkg_name=$(echo ${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT - - - name: Download release asset - id: download - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' - uses: robinraju/release-downloader@v1.12 - with: - repository: "${{ github.repository }}" - tag: "${{ github.event.release.tag_name }}" - fileName: "*.pkg.tar.gz" - tarBall: false - zipBall: false - out-file-path: "pkgbuilds/${{ steps.prep.outputs.pkg_name }}" - extract: true - - - name: Remove pkg.tar.gz - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' && - fromJson(steps.download.outputs.downloaded_files)[0] - run: | - rm -f "pkgbuilds/${{ steps.prep.outputs.pkg_name }}" - - - name: Create/Update Pull Request - id: create-pr - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' && - fromJson(steps.download.outputs.downloaded_files)[0] - uses: peter-evans/create-pull-request@v7 - with: - add-paths: | - pkgbuilds/* - token: ${{ secrets.GH_BOT_TOKEN }} - commit-message: "chore: Update ${{ github.repository }} to ${{ github.event.release.tag_name }}" - branch: bot/bump-${{ github.repository }}-${{ github.event.release.tag_name }} - delete-branch: true - title: "chore: Update ${{ github.repository }} to ${{ github.event.release.tag_name }}" - body: ${{ github.event.release.body }} - labels: | - auto-approve - auto-merge - - - name: Automerge PR - env: - GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} - if: >- - steps.check-label.outputs.hasTopic == 'true' && - steps.check-release.outputs.isLatestRelease == 'true' && - fromJson(steps.download.outputs.downloaded_files)[0] - run: | - gh pr merge \ - --auto \ - --delete-branch \ - --squash \ - "${{ steps.create-pr.outputs.pull-request-number }}" diff --git a/.github/workflows/update-winget-release.yml b/.github/workflows/update-winget-release.yml deleted file mode 100644 index 860f086f..00000000 --- a/.github/workflows/update-winget-release.yml +++ /dev/null @@ -1,71 +0,0 @@ ---- -# This workflow 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. - -# To use, add the `winget-pkg` repository label to identify repositories that should trigger this workflow. - -# Update Winget on release events. - -name: Update Winget release -permissions: - contents: read - -on: - release: - types: - - released - -concurrency: - group: "${{ github.workflow }}-${{ github.event.release.tag_name }}" - cancel-in-progress: true - -jobs: - update-winget-release: - if: github.repository_owner == 'LizardByte' - runs-on: ubuntu-latest - steps: - - name: Check if Winget repo - id: check-label - env: - TOPIC: winget-pkg - uses: actions/github-script@v7 - with: - script: | - const topic = process.env.TOPIC; - console.log(`Checking if repo has topic: ${topic}`); - - const repoTopics = await github.rest.repos.getAllTopics({ - owner: context.repo.owner, - repo: context.repo.repo - }); - console.log(`Repo topics: ${repoTopics.data.names}`); - - const hasTopic = repoTopics.data.names.includes(topic); - console.log(`Has topic: ${hasTopic}`); - - core.setOutput('hasTopic', hasTopic); - - - name: Download release asset - id: download - if: steps.check-label.outputs.hasTopic == 'true' - uses: robinraju/release-downloader@v1.12 - with: - repository: "${{ github.repository }}" - tag: "${{ github.event.release.tag_name }}" - fileName: "*.exe" - tarBall: false - zipBall: false - out-file-path: "release_downloads" - extract: false - - - name: Release to WinGet - if: >- - steps.check-label.outputs.hasTopic == 'true' && - fromJson(steps.download.outputs.downloaded_files)[0] - uses: vedantmgoyal2009/winget-releaser@v2 - with: - identifier: "${{ github.repository_owner }}.${{ github.event.repository.name }}" - release-tag: ${{ github.event.release.tag_name }} - installers-regex: '\.exe$' - token: ${{ secrets.GH_BOT_TOKEN }} From 1e082ab790b10c1f11b81a270587c534bc2d4d38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 22:12:41 -0400 Subject: [PATCH 20/67] build(deps): bump the lizardbyte-actions group across 1 directory with 3 updates (#3970) Bumps the lizardbyte-actions group with 3 updates in the / directory: [LizardByte/setup-release-action](https://github.com/lizardbyte/setup-release-action), [LizardByte/create-release-action](https://github.com/lizardbyte/create-release-action) and [LizardByte/homebrew-release-action](https://github.com/lizardbyte/homebrew-release-action). Updates `LizardByte/setup-release-action` from 2025.426.225 to 2025.612.120948 - [Release notes](https://github.com/lizardbyte/setup-release-action/releases) - [Commits](https://github.com/lizardbyte/setup-release-action/compare/v2025.426.225...v2025.612.120948) Updates `LizardByte/create-release-action` from 2025.426.1549 to 2025.612.13419 - [Release notes](https://github.com/lizardbyte/create-release-action/releases) - [Commits](https://github.com/lizardbyte/create-release-action/compare/v2025.426.1549...v2025.612.13419) Updates `LizardByte/homebrew-release-action` from 2025.506.15440 to 2025.612.123332 - [Release notes](https://github.com/lizardbyte/homebrew-release-action/releases) - [Commits](https://github.com/lizardbyte/homebrew-release-action/compare/v2025.506.15440...v2025.612.123332) --- updated-dependencies: - dependency-name: LizardByte/setup-release-action dependency-version: 2025.612.120948 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: lizardbyte-actions - dependency-name: LizardByte/create-release-action dependency-version: 2025.612.13419 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: lizardbyte-actions - dependency-name: LizardByte/homebrew-release-action dependency-version: 2025.612.123332 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: lizardbyte-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/CI.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index cf005b1b..06274527 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -49,7 +49,7 @@ jobs: - name: Setup Release id: setup_release - uses: LizardByte/setup-release-action@v2025.426.225 + uses: LizardByte/setup-release-action@v2025.612.120948 with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -252,7 +252,7 @@ jobs: - name: Create/Update GitHub Release if: needs.setup_release.outputs.publish_release == 'true' - uses: LizardByte/create-release-action@v2025.426.1549 + uses: LizardByte/create-release-action@v2025.612.13419 with: allowUpdates: true body: ${{ needs.setup_release.outputs.release_body }} @@ -485,7 +485,7 @@ jobs: - name: Create/Update GitHub Release if: needs.setup_release.outputs.publish_release == 'true' - uses: LizardByte/create-release-action@v2025.426.1549 + uses: LizardByte/create-release-action@v2025.612.13419 with: allowUpdates: true body: ${{ needs.setup_release.outputs.release_body }} @@ -621,7 +621,7 @@ jobs: - name: Validate Homebrew Formula id: test if: matrix.release != true - uses: LizardByte/homebrew-release-action@v2025.506.15440 + uses: LizardByte/homebrew-release-action@v2025.612.123332 with: formula_file: ${{ github.workspace }}/homebrew/sunshine.rb git_email: ${{ secrets.GH_BOT_EMAIL }} @@ -699,7 +699,7 @@ jobs: if: >- matrix.release && needs.setup_release.outputs.publish_release == 'true' - uses: LizardByte/create-release-action@v2025.426.1549 + uses: LizardByte/create-release-action@v2025.612.13419 with: allowUpdates: true artifacts: '${{ github.workspace }}/homebrew/*' @@ -734,7 +734,7 @@ jobs: github.repository_owner == 'LizardByte' && matrix.release && needs.setup_release.outputs.publish_release == 'true' - uses: LizardByte/homebrew-release-action@v2025.506.15440 + uses: LizardByte/homebrew-release-action@v2025.612.123332 with: formula_file: ${{ github.workspace }}/homebrew/sunshine-beta.rb git_email: ${{ secrets.GH_BOT_EMAIL }} @@ -1077,7 +1077,7 @@ jobs: - name: Create/Update GitHub Release if: needs.setup_release.outputs.publish_release == 'true' - uses: LizardByte/create-release-action@v2025.426.1549 + uses: LizardByte/create-release-action@v2025.612.13419 with: allowUpdates: true body: ${{ needs.setup_release.outputs.release_body }} From 9db11a906167bd962e57896223d7b9718058aeb2 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 12 Jun 2025 22:11:03 -0500 Subject: [PATCH 21/67] feat(security/windows): Add defense-in-depth measure for insecure system PATH configuration (#3971) feature(security/windows): Add defense-in-depth measure for insecure system PATH configuration If an administrator has configured their system insecurely by adding a user-writeable path to the system-wide PATH variable, this can cause apps running as admin to load DLLs planted in this directory. While the root cause is clearly the misconfigured system, we can reduce Sunshine's exposure to this by asking Windows not to search the PATH. https://devblogs.microsoft.com/oldnewthing/20200420-00/?p=103685 --- src/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index b91dedce..7f71f7fb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -95,6 +95,10 @@ int main(int argc, char *argv[]) { task_pool_util::TaskPool::task_id_t force_shutdown = nullptr; #ifdef _WIN32 + // Avoid searching the PATH in case a user has configured their system insecurely + // by placing a user-writable directory in the system-wide PATH variable. + SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32); + setlocale(LC_ALL, "C"); #endif From 64e6c48577e32d0c2cb3c3d92f1a6265f32735af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 09:38:02 -0400 Subject: [PATCH 22/67] build(deps): bump third-party/tray from `d45306e` to `f6d0684` (#3975) Bumps [third-party/tray](https://github.com/LizardByte/tray) from `d45306e` to `f6d0684`. - [Commits](https://github.com/LizardByte/tray/compare/d45306e686c90a18f5792a1541783d7bc8555bc6...f6d0684c2bc3b288b3cc29d8a51e0f4654eb8b72) --- updated-dependencies: - dependency-name: third-party/tray dependency-version: f6d0684c2bc3b288b3cc29d8a51e0f4654eb8b72 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- third-party/tray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/tray b/third-party/tray index d45306e6..f6d0684c 160000 --- a/third-party/tray +++ b/third-party/tray @@ -1 +1 @@ -Subproject commit d45306e686c90a18f5792a1541783d7bc8555bc6 +Subproject commit f6d0684c2bc3b288b3cc29d8a51e0f4654eb8b72 From 919f54495be14a9d2cb98b6fdc46c13cb002c6e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 11:52:37 -0400 Subject: [PATCH 23/67] build(deps): bump third-party/libdisplaydevice from `13a4aca` to `a067474` (#3973) build(deps): bump third-party/libdisplaydevice Bumps [third-party/libdisplaydevice](https://github.com/LizardByte/libdisplaydevice) from `13a4aca` to `a067474`. - [Release notes](https://github.com/LizardByte/libdisplaydevice/releases) - [Commits](https://github.com/LizardByte/libdisplaydevice/compare/13a4aca3c2f3eb78adef8f70231920f93f91f833...a0674741ec9a5eea335cddc0e7002b9f50782909) --- updated-dependencies: - dependency-name: third-party/libdisplaydevice dependency-version: a0674741ec9a5eea335cddc0e7002b9f50782909 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- third-party/libdisplaydevice | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/libdisplaydevice b/third-party/libdisplaydevice index 13a4aca3..a0674741 160000 --- a/third-party/libdisplaydevice +++ b/third-party/libdisplaydevice @@ -1 +1 @@ -Subproject commit 13a4aca3c2f3eb78adef8f70231920f93f91f833 +Subproject commit a0674741ec9a5eea335cddc0e7002b9f50782909 From 6c2efced6de6fd525b618c5d653ea0c4ed986221 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 13:09:19 -0400 Subject: [PATCH 24/67] build(deps): bump third-party/doxyconfig from `4501c7b` to `a73f908` (#3968) Bumps [third-party/doxyconfig](https://github.com/LizardByte/doxyconfig) from `4501c7b` to `a73f908`. - [Commits](https://github.com/LizardByte/doxyconfig/compare/4501c7b191170cd2adcc12336821b65449186d85...a73f908fb70fac4f6076a28f0a751239ea5ac2d3) --- updated-dependencies: - dependency-name: third-party/doxyconfig dependency-version: a73f908fb70fac4f6076a28f0a751239ea5ac2d3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- third-party/doxyconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/doxyconfig b/third-party/doxyconfig index 4501c7b1..a73f908f 160000 --- a/third-party/doxyconfig +++ b/third-party/doxyconfig @@ -1 +1 @@ -Subproject commit 4501c7b191170cd2adcc12336821b65449186d85 +Subproject commit a73f908fb70fac4f6076a28f0a751239ea5ac2d3 From 958d783d9431f029719dafd9cd451fb5397476b2 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 14 Jun 2025 23:45:01 -0400 Subject: [PATCH 25/67] build(arch-packaging): update gcc (#3978) --- docs/getting_started.md | 13 +++++++------ packaging/linux/Arch/PKGBUILD | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/getting_started.md b/docs/getting_started.md index 88f40844..299a7337 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -56,27 +56,28 @@ need to install CUDA.} 12.0.0 - 525.60.13 + 525.60.13 50;52;60;61;62;70;72;75;80;86;87;89;90 sunshine-debian-bookworm-{arch}.deb - - 12.5.1 - sunshine.pkg.tar.zst - 12.6.2 560.35.03 sunshine_{arch}.flatpak - Sunshine (copr - Fedora 40/41) + Sunshine (copr - Fedora 41) 12.8.1 570.124.06 Sunshine (copr - Fedora 42) + + 12.9.1 + 575.57.08 + sunshine.pkg.tar.zst + #### AppImage diff --git a/packaging/linux/Arch/PKGBUILD b/packaging/linux/Arch/PKGBUILD index 17fc672a..d0045cd9 100644 --- a/packaging/linux/Arch/PKGBUILD +++ b/packaging/linux/Arch/PKGBUILD @@ -10,7 +10,7 @@ url=@PROJECT_HOMEPAGE_URL@ license=('GPL-3.0-only') install=sunshine.install -_gcc_version=13 +_gcc_version=14 depends=( 'avahi' From b48e6303eacef404b14eb797a890b004f32c3fd4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 18:07:07 -0400 Subject: [PATCH 26/67] build(deps): bump vue-i18n from 11.1.5 to 11.1.6 (#3984) Bumps [vue-i18n](https://github.com/intlify/vue-i18n/tree/HEAD/packages/vue-i18n) from 11.1.5 to 11.1.6. - [Release notes](https://github.com/intlify/vue-i18n/releases) - [Changelog](https://github.com/intlify/vue-i18n/blob/master/CHANGELOG.md) - [Commits](https://github.com/intlify/vue-i18n/commits/v11.1.6/packages/vue-i18n) --- updated-dependencies: - dependency-name: vue-i18n dependency-version: 11.1.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1906804e..9285421b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "dependencies": { "@lizardbyte/shared-web": "2025.326.11214", "vue": "3.5.16", - "vue-i18n": "11.1.5" + "vue-i18n": "11.1.6" }, "devDependencies": { "@codecov/vite-plugin": "1.9.0", From 8e061c44c541980d362242c7cbbdeed40dd32536 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 21 Jun 2025 15:41:59 -0400 Subject: [PATCH 27/67] fix(deps): workaround miniupnpc `type_t` error (#3993) --- packaging/sunshine.rb | 23 +---------------------- src/upnp.cpp | 3 +++ 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/packaging/sunshine.rb b/packaging/sunshine.rb index ce8c7e81..f13a5a38 100644 --- a/packaging/sunshine.rb +++ b/packaging/sunshine.rb @@ -33,6 +33,7 @@ class @PROJECT_NAME@ < Formula depends_on "node" => :build depends_on "pkg-config" => :build depends_on "curl" + depends_on "miniupnpc" depends_on "openssl" depends_on "opus" depends_on "icu4c" => :recommended @@ -198,11 +199,6 @@ index 5b3638d..aca9481 100644 end end - resource "miniupnpc" do - url "https://github.com/miniupnp/miniupnp.git", - revision: "e263ab6f56c382e10fed31347ec68095d691a0e8" - end - def install ENV["BRANCH"] = "@GITHUB_BRANCH@" ENV["BUILD_VERSION"] = "@BUILD_VERSION@" @@ -305,23 +301,6 @@ index 5b3638d..aca9481 100644 end end - # Build miniupnpc - resource("miniupnpc").stage do - # Change to the miniupnpc directory within the repo - cd "miniupnpc" do - system "make", "INSTALLPREFIX=#{prefix}/miniupnpc", "install" - end - - # Copy the shared libraries to the main lib directory - # This ensures they're in the library search path at runtime - cp Dir["#{prefix}/miniupnpc/lib/libminiupnpc.so*"], "#{lib}/" if OS.linux? - - # Set include and library paths for the custom miniupnpc - ENV.prepend_path "PKG_CONFIG_PATH", "#{prefix}/miniupnpc/lib/pkgconfig" - ENV.prepend "CPPFLAGS", "-I#{prefix}/miniupnpc/include" - ENV.prepend "LDFLAGS", "-L#{prefix}/miniupnpc/lib" - end - system "cmake", "-S", ".", "-B", "build", "-G", "Unix Makefiles", *std_cmake_args, *args diff --git a/src/upnp.cpp b/src/upnp.cpp index 14103ad5..625a7a69 100644 --- a/src/upnp.cpp +++ b/src/upnp.cpp @@ -2,6 +2,9 @@ * @file src/upnp.cpp * @brief Definitions for UPnP port mapping. */ +// standard includes +#include // workaround for type_t error in miniupnpc 2.3.3, see https://github.com/miniupnp/miniupnp/commit/e263ab6f56c382e10fed31347ec68095d691a0e8 + // lib includes #include #include From fb51c2c649c9f57f877c34725b191bb1035bc5e0 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 21 Jun 2025 16:30:42 -0400 Subject: [PATCH 28/67] fix(homebrew): workaround dynamic caveats audit error (#3994) --- packaging/sunshine.rb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packaging/sunshine.rb b/packaging/sunshine.rb index f13a5a38..497b2670 100644 --- a/packaging/sunshine.rb +++ b/packaging/sunshine.rb @@ -319,31 +319,31 @@ index 5b3638d..aca9481 100644 run [opt_bin/"sunshine", "~/.config/sunshine/sunshine.conf"] end - def caveats - caveats_message = <<~EOS - Thanks for installing @PROJECT_NAME@! - - To get started, review the documentation at: - https://docs.lizardbyte.dev/projects/sunshine - EOS - + def post_install if OS.linux? - caveats_message += <<~EOS + opoo <<~EOS ATTENTION: To complete installation, you must run the following command: `sudo #{bin}/postinst` EOS end if OS.mac? - caveats_message += <<~EOS + opoo <<~EOS Sunshine can only access microphones on macOS due to system limitations. To stream system audio use "Soundflower" or "BlackHole". Gamepads are not currently supported on macOS. EOS end + end - caveats_message + def caveats + <<~EOS + Thanks for installing @PROJECT_NAME@! + + To get started, review the documentation at: + https://docs.lizardbyte.dev/projects/sunshine + EOS end test do From 926cafa6a9e2936433ae102e30626fd84efce178 Mon Sep 17 00:00:00 2001 From: water-vapor Date: Sat, 21 Jun 2025 16:19:11 -0500 Subject: [PATCH 29/67] fix(macOS): nil displayName on macOS 26 beta (#3991) --- src/platform/macos/av_video.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/macos/av_video.m b/src/platform/macos/av_video.m index 34d70a0c..630d7101 100644 --- a/src/platform/macos/av_video.m +++ b/src/platform/macos/av_video.m @@ -34,7 +34,7 @@ + (NSString *)getDisplayName:(CGDirectDisplayID)displayID { for (NSScreen *screen in [NSScreen screens]) { - if (screen.deviceDescription[@"NSScreenNumber"] == [NSNumber numberWithUnsignedInt:displayID]) { + if ([screen.deviceDescription[@"NSScreenNumber"] isEqualToNumber:[NSNumber numberWithUnsignedInt:displayID]]) { return screen.localizedName; } } From 986aa176ee0d7c931256e218cf4e748923967dca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 08:40:15 -0400 Subject: [PATCH 30/67] build(deps): bump third-party/TPCircularBuffer from `8833b3a` to `cc52039` (#4002) build(deps): bump third-party/TPCircularBuffer Bumps [third-party/TPCircularBuffer](https://github.com/michaeltyson/TPCircularBuffer) from `8833b3a` to `cc52039`. - [Commits](https://github.com/michaeltyson/TPCircularBuffer/compare/8833b3a73fab6530cc51e2063a85cced01714cfb...cc520397504bb72bc6df79ff03eb72988a6dc50d) --- updated-dependencies: - dependency-name: third-party/TPCircularBuffer dependency-version: cc520397504bb72bc6df79ff03eb72988a6dc50d dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- third-party/TPCircularBuffer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/TPCircularBuffer b/third-party/TPCircularBuffer index 8833b3a7..cc520397 160000 --- a/third-party/TPCircularBuffer +++ b/third-party/TPCircularBuffer @@ -1 +1 @@ -Subproject commit 8833b3a73fab6530cc51e2063a85cced01714cfb +Subproject commit cc520397504bb72bc6df79ff03eb72988a6dc50d From 597bccef922ea42fcc3ad2f7670efaab85c182e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 21:49:06 -0400 Subject: [PATCH 31/67] build(deps): bump vue-i18n from 11.1.6 to 11.1.7 (#4001) Bumps [vue-i18n](https://github.com/intlify/vue-i18n/tree/HEAD/packages/vue-i18n) from 11.1.6 to 11.1.7. - [Release notes](https://github.com/intlify/vue-i18n/releases) - [Changelog](https://github.com/intlify/vue-i18n/blob/master/CHANGELOG.md) - [Commits](https://github.com/intlify/vue-i18n/commits/v11.1.7/packages/vue-i18n) --- updated-dependencies: - dependency-name: vue-i18n dependency-version: 11.1.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9285421b..a86e3f8b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "dependencies": { "@lizardbyte/shared-web": "2025.326.11214", "vue": "3.5.16", - "vue-i18n": "11.1.6" + "vue-i18n": "11.1.7" }, "devDependencies": { "@codecov/vite-plugin": "1.9.0", From 684530af1f372a41bc7afb90ae9c74df093fdaff Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 24 Jun 2025 23:11:12 -0400 Subject: [PATCH 32/67] build(flatpak): add exception for home filesystem access (#4005) --- packaging/linux/flatpak/exceptions.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packaging/linux/flatpak/exceptions.json b/packaging/linux/flatpak/exceptions.json index f341c260..30ea0d21 100644 --- a/packaging/linux/flatpak/exceptions.json +++ b/packaging/linux/flatpak/exceptions.json @@ -3,6 +3,7 @@ "appstream-external-screenshot-url", "appstream-screenshots-not-mirrored-in-ostree", "external-gitmodule-url-found", - "finish-args-flatpak-spawn-access" + "finish-args-flatpak-spawn-access", + "finish-args-home-filesystem-access" ] } From 373df9d7f74197ba5c4386656b956c707ef189be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Jun 2025 08:10:07 -0400 Subject: [PATCH 33/67] build(deps): bump vue from 3.5.16 to 3.5.17 (#3987) Bumps [vue](https://github.com/vuejs/core) from 3.5.16 to 3.5.17. - [Release notes](https://github.com/vuejs/core/releases) - [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/vuejs/core/compare/v3.5.16...v3.5.17) --- updated-dependencies: - dependency-name: vue dependency-version: 3.5.17 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a86e3f8b..4568f08a 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "@lizardbyte/shared-web": "2025.326.11214", - "vue": "3.5.16", + "vue": "3.5.17", "vue-i18n": "11.1.7" }, "devDependencies": { From 543e239f835e0625f97043650f08eb17896bfd16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Jun 2025 09:15:39 -0400 Subject: [PATCH 34/67] build(deps): bump packaging/linux/flatpak/deps/flatpak-builder-tools from `ea9bfa2` to `903919f` (#3959) build(deps): bump packaging/linux/flatpak/deps/flatpak-builder-tools Bumps [packaging/linux/flatpak/deps/flatpak-builder-tools](https://github.com/flatpak/flatpak-builder-tools) from `ea9bfa2` to `903919f`. - [Commits](https://github.com/flatpak/flatpak-builder-tools/compare/ea9bfa22d175066dd3044544cc55aa070f8282f4...903919f82f4cd6356bb4e9afe2755e44e8d8d7da) --- updated-dependencies: - dependency-name: packaging/linux/flatpak/deps/flatpak-builder-tools dependency-version: 903919f82f4cd6356bb4e9afe2755e44e8d8d7da dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packaging/linux/flatpak/deps/flatpak-builder-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/linux/flatpak/deps/flatpak-builder-tools b/packaging/linux/flatpak/deps/flatpak-builder-tools index ea9bfa22..903919f8 160000 --- a/packaging/linux/flatpak/deps/flatpak-builder-tools +++ b/packaging/linux/flatpak/deps/flatpak-builder-tools @@ -1 +1 @@ -Subproject commit ea9bfa22d175066dd3044544cc55aa070f8282f4 +Subproject commit 903919f82f4cd6356bb4e9afe2755e44e8d8d7da From 63229ac28bcad7d704dd5dc91a158a512887f25a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Jun 2025 16:15:56 -0400 Subject: [PATCH 35/67] build(deps): bump packaging/linux/flatpak/deps/shared-modules from `1f8e591` to `8a98211` (#3982) build(deps): bump packaging/linux/flatpak/deps/shared-modules Bumps [packaging/linux/flatpak/deps/shared-modules](https://github.com/flathub/shared-modules) from `1f8e591` to `8a98211`. - [Commits](https://github.com/flathub/shared-modules/compare/1f8e591b263eef8a0dc04929f2da135af59fac3c...8a98211689e216491c78bc55b4b1d4e8abeda0ad) --- updated-dependencies: - dependency-name: packaging/linux/flatpak/deps/shared-modules dependency-version: 8a98211689e216491c78bc55b4b1d4e8abeda0ad dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packaging/linux/flatpak/deps/shared-modules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/linux/flatpak/deps/shared-modules b/packaging/linux/flatpak/deps/shared-modules index 1f8e591b..8a982116 160000 --- a/packaging/linux/flatpak/deps/shared-modules +++ b/packaging/linux/flatpak/deps/shared-modules @@ -1 +1 @@ -Subproject commit 1f8e591b263eef8a0dc04929f2da135af59fac3c +Subproject commit 8a98211689e216491c78bc55b4b1d4e8abeda0ad From 6203e8118ab5f6ec0f0242c6a229872639dac125 Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Thu, 26 Jun 2025 11:09:01 -0400 Subject: [PATCH 36/67] chore: update global workflows (#4008) --- .github/workflows/issues.yml | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 .github/workflows/issues.yml diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml deleted file mode 100644 index 5bd4e881..00000000 --- a/.github/workflows/issues.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- -# This workflow 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. - -# Label and un-label actions using `../label-actions.yml`. - -name: Issues -permissions: {} - -on: - issues: - types: - - labeled - - unlabeled - discussion: - types: - - labeled - - unlabeled - -jobs: - label: - name: Label Actions - if: startsWith(github.repository, 'LizardByte/') - runs-on: ubuntu-latest - steps: - - name: Label Actions - uses: dessant/label-actions@v4 - with: - github-token: ${{ secrets.GH_BOT_TOKEN }} From f62299d0180c8c0b46b7cc168e0e0995eb3d4cfb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 17:00:50 -0400 Subject: [PATCH 37/67] build(deps): bump @lizardbyte/shared-web from 2025.326.11214 to 2025.626.181239 (#4000) build(deps): bump @lizardbyte/shared-web Bumps [@lizardbyte/shared-web](https://github.com/LizardByte/shared-web) from 2025.326.11214 to 2025.620.225139. - [Release notes](https://github.com/LizardByte/shared-web/releases) - [Commits](https://github.com/LizardByte/shared-web/compare/v2025.326.11214...v2025.620.225139) --- updated-dependencies: - dependency-name: "@lizardbyte/shared-web" dependency-version: 2025.620.225139 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4568f08a..692ad9b6 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "serve": "serve ./tests/fixtures/http --no-port-switching" }, "dependencies": { - "@lizardbyte/shared-web": "2025.326.11214", + "@lizardbyte/shared-web": "2025.626.181239", "vue": "3.5.17", "vue-i18n": "11.1.7" }, From ced363833cd15ae174a02b8167e67b7357123df8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 12:29:51 -0400 Subject: [PATCH 38/67] build(deps-dev): bump the dev-dependencies group across 1 directory with 2 updates (#4014) Bumps the dev-dependencies group with 2 updates in the / directory: @codecov/vite-plugin and [serve](https://github.com/vercel/serve). Updates `@codecov/vite-plugin` from 1.9.0 to 1.9.1 Updates `serve` from 14.2.3 to 14.2.4 - [Release notes](https://github.com/vercel/serve/releases) - [Commits](https://github.com/vercel/serve/compare/14.2.3...14.2.4) --- updated-dependencies: - dependency-name: "@codecov/vite-plugin" dependency-version: 1.9.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: serve dependency-version: 14.2.4 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 692ad9b6..64d05229 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,9 @@ "vue-i18n": "11.1.7" }, "devDependencies": { - "@codecov/vite-plugin": "1.9.0", + "@codecov/vite-plugin": "1.9.1", "@vitejs/plugin-vue": "4.6.2", - "serve": "14.2.3", + "serve": "14.2.4", "vite": "4.5.14", "vite-plugin-ejs": "1.6.4" } From 4cdb5cd4520f7662c05044a44596ccc1085f4389 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 27 Jun 2025 13:29:34 -0400 Subject: [PATCH 39/67] build(deps): pin googletest to v1.17.0 (#4015) deps(build): bump googletest to v1.17.0 --- third-party/googletest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/googletest b/third-party/googletest index 04ee1b4f..52eb8108 160000 --- a/third-party/googletest +++ b/third-party/googletest @@ -1 +1 @@ -Subproject commit 04ee1b4f2aefdffb0135d7cf2a2c519fe50dabe4 +Subproject commit 52eb8108c5bdec04579160ae17225d66034bd723 From d6820ba019f2e68f90e135b5325207a1af215dc7 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:28:21 -0400 Subject: [PATCH 40/67] build(deps): bump wayland-protocols to 1.45 (#4017) --- third-party/wayland-protocols | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/wayland-protocols b/third-party/wayland-protocols index 810f1ada..0091197f 160000 --- a/third-party/wayland-protocols +++ b/third-party/wayland-protocols @@ -1 +1 @@ -Subproject commit 810f1adaf33521cc55fc510566efba2a1418174f +Subproject commit 0091197f5c1b1f2c131f1410e99f9c95d50646be From 738ac93a0ec1cd10412d1f339968775f53bfefe0 Mon Sep 17 00:00:00 2001 From: TheElixZammuto Date: Fri, 27 Jun 2025 22:57:59 +0200 Subject: [PATCH 41/67] Merge commit from fork * (security) Mandate content-type on POST calls * (security) Add JSON content-type in POST requests with a body * Added Content Type on missing endpoints * (review) docs and newlines * (docs) add JSON content type header * style(clang-format): fix lint errors --------- Co-authored-by: axfla Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> --- docs/api.js | 3 +- src/confighttp.cpp | 66 +++++++++++++++++++ src_assets/common/assets/web/apps.html | 13 +++- src_assets/common/assets/web/config.html | 8 ++- src_assets/common/assets/web/password.html | 3 + src_assets/common/assets/web/pin.html | 8 ++- .../common/assets/web/troubleshooting.html | 31 +++++++-- src_assets/common/assets/web/welcome.html | 3 + 8 files changed, 127 insertions(+), 8 deletions(-) diff --git a/docs/api.js b/docs/api.js index 5f887e94..eff63583 100644 --- a/docs/api.js +++ b/docs/api.js @@ -9,7 +9,7 @@ function generateExamples(endpoint, method, body = null) { } return { - cURL: `curl -u user:pass -X ${method.trim()} -k https://localhost:47990${endpoint.trim()}${curlBodyString}`, + cURL: `curl -u user:pass -H "Content-Type: application/json" -X ${method.trim()} -k https://localhost:47990${endpoint.trim()}${curlBodyString}`, Python: `import json import requests from requests.auth import HTTPBasicAuth @@ -30,6 +30,7 @@ requests.${method.trim().toLowerCase()}( .then(data => console.log(data));`, PowerShell: `Invoke-RestMethod \` -SkipCertificateCheck \` + -ContentType 'application/json' \` -Uri 'https://localhost:47990${endpoint.trim()}' \` -Method ${method.trim()} \` -Headers @{Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes('user:pass'))} diff --git a/src/confighttp.cpp b/src/confighttp.cpp index 4ba09b88..43d6df24 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -213,6 +213,39 @@ namespace confighttp { response->write(code, tree.dump(), headers); } + /** + * @brief Validate the request content type and send bad request when mismatch. + * @param response The HTTP response object. + * @param request The HTTP request object. + * @param contentType The expected content type + */ + bool check_content_type(resp_https_t response, req_https_t request, const std::string_view &contentType) { + auto requestContentType = request->header.find("content-type"); + if (requestContentType == request->header.end()) { + bad_request(response, request, "Content type not provided"); + return false; + } + // Extract the media type part before any parameters (e.g., charset) + std::string actualContentType = requestContentType->second; + size_t semicolonPos = actualContentType.find(';'); + if (semicolonPos != std::string::npos) { + actualContentType = actualContentType.substr(0, semicolonPos); + } + + // Trim whitespace and convert to lowercase for case-insensitive comparison + boost::algorithm::trim(actualContentType); + boost::algorithm::to_lower(actualContentType); + + std::string expectedContentType(contentType); + boost::algorithm::to_lower(expectedContentType); + + if (actualContentType != expectedContentType) { + bad_request(response, request, "Content type mismatch"); + return false; + } + return true; + } + /** * @brief Get the index page. * @param response The HTTP response object. @@ -535,6 +568,9 @@ namespace confighttp { * @api_examples{/api/apps| POST| {"name":"Hello, World!","index":-1}} */ void saveApp(resp_https_t response, req_https_t request) { + if (!check_content_type(response, request, "application/json")) { + return; + } if (!authenticate(response, request)) { return; } @@ -602,6 +638,9 @@ namespace confighttp { * @api_examples{/api/apps/close| POST| null} */ void closeApp(resp_https_t response, req_https_t request) { + if (!check_content_type(response, request, "application/json")) { + return; + } if (!authenticate(response, request)) { return; } @@ -623,6 +662,9 @@ namespace confighttp { * @api_examples{/api/apps/9999| DELETE| null} */ void deleteApp(resp_https_t response, req_https_t request) { + if (!check_content_type(response, request, "application/json")) { + return; + } if (!authenticate(response, request)) { return; } @@ -703,6 +745,9 @@ namespace confighttp { * @api_examples{/api/unpair| POST| {"uuid":"1234"}} */ void unpair(resp_https_t response, req_https_t request) { + if (!check_content_type(response, request, "application/json")) { + return; + } if (!authenticate(response, request)) { return; } @@ -733,6 +778,9 @@ namespace confighttp { * @api_examples{/api/clients/unpair-all| POST| null} */ void unpairAll(resp_https_t response, req_https_t request) { + if (!check_content_type(response, request, "application/json")) { + return; + } if (!authenticate(response, request)) { return; } @@ -809,6 +857,9 @@ namespace confighttp { * @api_examples{/api/config| POST| {"key":"value"}} */ void saveConfig(resp_https_t response, req_https_t request) { + if (!check_content_type(response, request, "application/json")) { + return; + } if (!authenticate(response, request)) { return; } @@ -855,6 +906,9 @@ namespace confighttp { * @api_examples{/api/covers/upload| POST| {"key":"igdb_1234","url":"https://images.igdb.com/igdb/image/upload/t_cover_big_2x/abc123.png"}} */ void uploadCover(resp_https_t response, req_https_t request) { + if (!check_content_type(response, request, "application/json")) { + return; + } if (!authenticate(response, request)) { return; } @@ -938,6 +992,9 @@ namespace confighttp { * @api_examples{/api/password| POST| {"currentUsername":"admin","currentPassword":"admin","newUsername":"admin","newPassword":"admin","confirmNewPassword":"admin"}} */ void savePassword(resp_https_t response, req_https_t request) { + if (!check_content_type(response, request, "application/json")) { + return; + } if (!config::sunshine.username.empty() && !authenticate(response, request)) { return; } @@ -1008,6 +1065,9 @@ namespace confighttp { * @api_examples{/api/pin| POST| {"pin":"1234","name":"My PC"}} */ void savePin(resp_https_t response, req_https_t request) { + if (!check_content_type(response, request, "application/json")) { + return; + } if (!authenticate(response, request)) { return; } @@ -1044,6 +1104,9 @@ namespace confighttp { * @api_examples{/api/reset-display-device-persistence| POST| null} */ void resetDisplayDevicePersistence(resp_https_t response, req_https_t request) { + if (!check_content_type(response, request, "application/json")) { + return; + } if (!authenticate(response, request)) { return; } @@ -1063,6 +1126,9 @@ namespace confighttp { * @api_examples{/api/restart| POST| null} */ void restart(resp_https_t response, req_https_t request) { + if (!check_content_type(response, request, "application/json")) { + return; + } if (!authenticate(response, request)) { return; } diff --git a/src_assets/common/assets/web/apps.html b/src_assets/common/assets/web/apps.html index 66e264e3..8b0cb517 100644 --- a/src_assets/common/assets/web/apps.html +++ b/src_assets/common/assets/web/apps.html @@ -440,7 +440,12 @@ "Are you sure to delete " + this.apps[id].name + "?" ); if (resp) { - fetch("./api/apps/" + id, { method: "DELETE" }).then((r) => { + fetch("./api/apps/" + id, { + method: "DELETE", + headers: { + "Content-Type": "application/json" + }, + }).then((r) => { if (r.status === 200) document.location.reload(); }); } @@ -540,6 +545,9 @@ this.coverFinderBusy = true; fetch("./api/covers/upload", { method: "POST", + headers: { + 'Content-Type': 'application/json' + }, body: JSON.stringify({ key: cover.key, url: cover.saveUrl, @@ -555,6 +563,9 @@ this.editForm["image-path"] = this.editForm["image-path"].toString().replace(/"/g, ''); fetch("./api/apps", { method: "POST", + headers: { + 'Content-Type': 'application/json' + }, body: JSON.stringify(this.editForm), }).then((r) => { if (r.status === 200) document.location.reload(); diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index e6a85b87..f021fd71 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -371,6 +371,9 @@ return fetch("./api/config", { method: "POST", + headers: { + 'Content-Type': 'application/json' + }, body: JSON.stringify(config), }).then((r) => { if (r.status === 200) { @@ -393,7 +396,10 @@ this.saved = this.restarted = false; }, 5000); fetch("./api/restart", { - method: "POST" + method: "POST", + headers: { + "Content-Type": "application/json" + } }); } }); diff --git a/src_assets/common/assets/web/password.html b/src_assets/common/assets/web/password.html index 854ee596..9f1e7194 100644 --- a/src_assets/common/assets/web/password.html +++ b/src_assets/common/assets/web/password.html @@ -92,6 +92,9 @@ this.error = null; fetch("./api/password", { method: "POST", + headers: { + 'Content-Type': 'application/json' + }, body: JSON.stringify(this.passwordData), }).then((r) => { if (r.status === 200) { diff --git a/src_assets/common/assets/web/pin.html b/src_assets/common/assets/web/pin.html index a5dcdb5d..d16a5de1 100644 --- a/src_assets/common/assets/web/pin.html +++ b/src_assets/common/assets/web/pin.html @@ -39,7 +39,13 @@ let name = document.querySelector("#name-input").value; document.querySelector("#status").innerHTML = ""; let b = JSON.stringify({pin: pin, name: name}); - fetch("./api/pin", {method: "POST", body: b}) + fetch("./api/pin", { + method: "POST", + headers: { + 'Content-Type': 'application/json' + }, + body: b + }) .then((response) => response.json()) .then((response) => { if (response.status === true) { diff --git a/src_assets/common/assets/web/troubleshooting.html b/src_assets/common/assets/web/troubleshooting.html index 46d7218b..d742867f 100644 --- a/src_assets/common/assets/web/troubleshooting.html +++ b/src_assets/common/assets/web/troubleshooting.html @@ -207,7 +207,11 @@ }, closeApp() { this.closeAppPressed = true; - fetch("./api/apps/close", { method: "POST" }) + fetch("./api/apps/close", { + method: "POST", + headers: { + "Content-Type": "application/json" + } }) .then((r) => r.json()) .then((r) => { this.closeAppPressed = false; @@ -219,7 +223,12 @@ }, unpairAll() { this.unpairAllPressed = true; - fetch("./api/clients/unpair-all", { method: "POST" }) + fetch("./api/clients/unpair-all", { + method: "POST", + headers: { + "Content-Type": "application/json" + } + }) .then((r) => r.json()) .then((r) => { this.unpairAllPressed = false; @@ -231,7 +240,13 @@ }); }, unpairSingle(uuid) { - fetch("./api/clients/unpair", { method: "POST", body: JSON.stringify({ uuid }) }).then(() => { + fetch("./api/clients/unpair", { + method: "POST", + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ uuid }) + }).then(() => { this.showApplyMessage = true; this.refreshClients(); }); @@ -263,11 +278,19 @@ }, 5000); fetch("./api/restart", { method: "POST", + headers: { + "Content-Type": "application/json" + } }); }, ddResetPersistence() { this.ddResetPressed = true; - fetch("/api/reset-display-device-persistence", { method: "POST" }) + fetch("/api/reset-display-device-persistence", { + method: "POST", + headers: { + "Content-Type": "application/json" + } + }) .then((r) => r.json()) .then((r) => { this.ddResetPressed = false; diff --git a/src_assets/common/assets/web/welcome.html b/src_assets/common/assets/web/welcome.html index 2f06c8a1..bdacf941 100644 --- a/src_assets/common/assets/web/welcome.html +++ b/src_assets/common/assets/web/welcome.html @@ -78,6 +78,9 @@ this.loading = true; fetch("./api/password", { method: "POST", + headers: { + 'Content-Type': 'application/json' + }, body: JSON.stringify(this.passwordData), }).then((r) => { this.loading = false; From 2f27a57d01911436017f87bf08b9e36dcfaa86cc Mon Sep 17 00:00:00 2001 From: axfla <33458662+axfla@users.noreply.github.com> Date: Fri, 27 Jun 2025 23:27:35 +0200 Subject: [PATCH 42/67] Merge commit from fork --- src/confighttp.cpp | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/confighttp.cpp b/src/confighttp.cpp index 43d6df24..059b6242 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -81,7 +81,8 @@ namespace confighttp { void send_response(resp_https_t response, const nlohmann::json &output_tree) { SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "application/json"); - + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(output_tree.dump(), headers); } @@ -103,7 +104,9 @@ namespace confighttp { const SimpleWeb::CaseInsensitiveMultimap headers { {"Content-Type", "application/json"}, - {"WWW-Authenticate", R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")"} + {"WWW-Authenticate", R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")"}, + {"X-Frame-Options", "DENY"}, + {"Content-Security-Policy", "frame-ancestors 'none';"} }; response->write(code, tree.dump(), headers); @@ -119,7 +122,9 @@ namespace confighttp { auto address = net::addr_to_normalized_string(request->remote_endpoint().address()); BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv; const SimpleWeb::CaseInsensitiveMultimap headers { - {"Location", path} + {"Location", path}, + {"X-Frame-Options", "DENY"}, + {"Content-Security-Policy", "frame-ancestors 'none';"} }; response->write(SimpleWeb::StatusCode::redirection_temporary_redirect, headers); } @@ -189,6 +194,8 @@ namespace confighttp { SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "application/json"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(code, tree.dump(), headers); } @@ -209,6 +216,8 @@ namespace confighttp { SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "application/json"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(code, tree.dump(), headers); } @@ -262,6 +271,8 @@ namespace confighttp { std::string content = file_handler::read_file(WEB_DIR "index.html"); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "text/html; charset=utf-8"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(content, headers); } @@ -280,6 +291,8 @@ namespace confighttp { std::string content = file_handler::read_file(WEB_DIR "pin.html"); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "text/html; charset=utf-8"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(content, headers); } @@ -298,6 +311,8 @@ namespace confighttp { std::string content = file_handler::read_file(WEB_DIR "apps.html"); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "text/html; charset=utf-8"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); headers.emplace("Access-Control-Allow-Origin", "https://images.igdb.com/"); response->write(content, headers); } @@ -317,6 +332,8 @@ namespace confighttp { std::string content = file_handler::read_file(WEB_DIR "clients.html"); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "text/html; charset=utf-8"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(content, headers); } @@ -335,6 +352,8 @@ namespace confighttp { std::string content = file_handler::read_file(WEB_DIR "config.html"); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "text/html; charset=utf-8"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(content, headers); } @@ -353,6 +372,8 @@ namespace confighttp { std::string content = file_handler::read_file(WEB_DIR "password.html"); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "text/html; charset=utf-8"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(content, headers); } @@ -370,6 +391,8 @@ namespace confighttp { std::string content = file_handler::read_file(WEB_DIR "welcome.html"); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "text/html; charset=utf-8"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(content, headers); } @@ -388,6 +411,8 @@ namespace confighttp { std::string content = file_handler::read_file(WEB_DIR "troubleshooting.html"); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "text/html; charset=utf-8"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(content, headers); } @@ -404,6 +429,8 @@ namespace confighttp { std::ifstream in(WEB_DIR "images/sunshine.ico", std::ios::binary); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "image/x-icon"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(SimpleWeb::StatusCode::success_ok, in, headers); } @@ -420,6 +447,8 @@ namespace confighttp { std::ifstream in(WEB_DIR "images/logo-sunshine-45.png", std::ios::binary); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "image/png"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(SimpleWeb::StatusCode::success_ok, in, headers); } @@ -471,6 +500,8 @@ namespace confighttp { // if it is, set the content type to the mime type SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", mimeType->second); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); std::ifstream in(filePath.string(), std::ios::binary); response->write(SimpleWeb::StatusCode::success_ok, in, headers); } @@ -971,6 +1002,8 @@ namespace confighttp { std::string content = file_handler::read_file(config::sunshine.log_file.c_str()); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "text/plain"); + headers.emplace("X-Frame-Options", "DENY"); + headers.emplace("Content-Security-Policy", "frame-ancestors 'none';"); response->write(SimpleWeb::StatusCode::success_ok, content, headers); } From 27f71c389518ab83ade959be94a3293736667454 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 27 Jun 2025 18:17:15 -0400 Subject: [PATCH 43/67] fix(video): base min frame time strictly on client framerate (#3844) --- docs/configuration.md | 29 ------------------- src/config.cpp | 2 -- src/config.h | 1 - src/video.cpp | 6 ++-- src_assets/common/assets/web/config.html | 1 - .../tabs/audiovideo/DisplayModesSettings.vue | 7 ----- .../assets/web/public/assets/locale/en.json | 2 -- 7 files changed, 3 insertions(+), 45 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index ef248643..36237f0d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1363,35 +1363,6 @@ editing the `conf` file in a text editor. Use the examples as reference. -### min_fps_factor - - - - - - - - - - - - - - - - - - -
Description - Sunshine will use this factor to calculate the minimum time between frames. Increasing this value may help - when streaming mostly static content. - @warning{Higher values will consume more bandwidth.} -
Default@code{} - 1 - @endcode
Range1-3
Example@code{} - min_fps_factor = 1 - @endcode
- ## Network ### upnp diff --git a/src/config.cpp b/src/config.cpp index 7147fd6c..89a5a445 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -504,7 +504,6 @@ namespace config { {} // wa }, // display_device - 1, // min_fps_factor 0 // max_bitrate }; @@ -1143,7 +1142,6 @@ namespace config { video.dd.wa.hdr_toggle_delay = std::chrono::milliseconds {value}; } - int_between_f(vars, "min_fps_factor", video.min_fps_factor, {1, 3}); int_f(vars, "max_bitrate", video.max_bitrate); path_f(vars, "pkey", nvhttp.pkey); diff --git a/src/config.h b/src/config.h index 066b95df..2e088ea7 100644 --- a/src/config.h +++ b/src/config.h @@ -140,7 +140,6 @@ namespace config { workarounds_t wa; } dd; - int min_fps_factor; // Minimum fps target, determines minimum frame time int max_bitrate; // Maximum bitrate, sets ceiling in kbps for bitrate requested from client }; diff --git a/src/video.cpp b/src/video.cpp index 0ff4d740..b4e3c147 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -1875,9 +1875,9 @@ namespace video { } }); - // set minimum frame time, avoiding violation of client-requested target framerate - auto minimum_frame_time = std::chrono::milliseconds(1000 / std::min(config.framerate, (config::video.min_fps_factor * 10))); - BOOST_LOG(debug) << "Minimum frame time set to "sv << minimum_frame_time.count() << "ms, based on min fps factor of "sv << config::video.min_fps_factor << "."sv; + // set minimum frame time based on client-requested target framerate + std::chrono::duration minimum_frame_time {1000.0 / config.framerate}; + BOOST_LOG(info) << "Minimum frame time set to "sv << minimum_frame_time.count() << "ms, based on client-requested target framerate "sv << config.framerate << "."sv; auto shutdown_event = mail->event(mail::shutdown); auto packets = mail::man->queue(mail::video_packets); diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index f021fd71..6e766e93 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -178,7 +178,6 @@ "dd_config_revert_on_disconnect": "disabled", "dd_mode_remapping": {"mixed": [], "resolution_only": [], "refresh_rate_only": []}, "dd_wa_hdr_toggle_delay": 0, - "min_fps_factor": 1, "max_bitrate": 0, }, }, diff --git a/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayModesSettings.vue b/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayModesSettings.vue index 2bf997ba..25b12deb 100644 --- a/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayModesSettings.vue +++ b/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayModesSettings.vue @@ -11,13 +11,6 @@ const config = ref(props.config)