diff --git a/.gitattributes b/.gitattributes
index 3e0ae29c..a1b22303 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,6 +1,5 @@
-# ensure dockerfiles are checked out with LF line endings
+# ensure Linux specific files are checked out with LF line endings
Dockerfile text eol=lf
*.dockerfile text eol=lf
-
-# ensure flatpak lint json files are checked out with LF line endings
*flatpak-lint-*.json text eol=lf
+*.sh text eol=lf
diff --git a/.github/matchers/copr-ci.json b/.github/matchers/copr-ci.json
new file mode 100644
index 00000000..d884c61a
--- /dev/null
+++ b/.github/matchers/copr-ci.json
@@ -0,0 +1,17 @@
+{
+ "problemMatcher": [
+ {
+ "owner": "copr-ci-gcc",
+ "pattern": [
+ {
+ "regexp": "^/?(?:[^/]+/){5}([^:]+):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
+ "file": 1,
+ "line": 2,
+ "column": 3,
+ "severity": 4,
+ "message": 5
+ }
+ ]
+ }
+ ]
+}
diff --git a/.github/matchers/docker.json b/.github/matchers/docker.json
new file mode 100644
index 00000000..df43ee03
--- /dev/null
+++ b/.github/matchers/docker.json
@@ -0,0 +1,17 @@
+{
+ "problemMatcher": [
+ {
+ "owner": "docker-gcc",
+ "pattern": [
+ {
+ "regexp": "^(?:#\\d+\\s+\\d+\\.\\d+\\s+)?/?(?:[^/]+/){2}([^:]+):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
+ "file": 1,
+ "line": 2,
+ "column": 3,
+ "severity": 4,
+ "message": 5
+ }
+ ]
+ }
+ ]
+}
diff --git a/.github/matchers/gcc-strip3.json b/.github/matchers/gcc-strip3.json
new file mode 100644
index 00000000..8b6027f2
--- /dev/null
+++ b/.github/matchers/gcc-strip3.json
@@ -0,0 +1,17 @@
+{
+ "problemMatcher": [
+ {
+ "owner": "gcc-strip3",
+ "pattern": [
+ {
+ "regexp": "^/?(?:[^/]+/){3}([^:]+):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
+ "file": 1,
+ "line": 2,
+ "column": 3,
+ "severity": 4,
+ "message": 5
+ }
+ ]
+ }
+ ]
+}
diff --git a/.github/matchers/gcc.json b/.github/matchers/gcc.json
new file mode 100644
index 00000000..13f987bf
--- /dev/null
+++ b/.github/matchers/gcc.json
@@ -0,0 +1,29 @@
+{
+ "problemMatcher": [
+ {
+ "owner": "gcc",
+ "pattern": [
+ {
+ "regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
+ "file": 1,
+ "line": 2,
+ "column": 3,
+ "severity": 4,
+ "message": 5
+ }
+ ]
+ },
+ {
+ "owner": "doxygen",
+ "pattern": [
+ {
+ "regexp": "^.*?([A-Za-z]:[\\\\/][^:]+|[\\\\/][^:]+):(\\d+): ([a-zA-Z]+): (.+)$",
+ "file": 1,
+ "line": 2,
+ "severity": 3,
+ "message": 4
+ }
+ ]
+ }
+ ]
+}
diff --git a/cmake/packaging/linux.cmake b/cmake/packaging/linux.cmake
index 041430dc..24d084a7 100644
--- a/cmake/packaging/linux.cmake
+++ b/cmake/packaging/linux.cmake
@@ -45,6 +45,7 @@ set(CPACK_DEBIAN_PACKAGE_DEPENDS "\
libcap2, \
libcurl4, \
libdrm2, \
+ libgbm1, \
libevdev2, \
libnuma1, \
libopus0, \
@@ -65,6 +66,7 @@ set(CPACK_RPM_PACKAGE_REQUIRES "\
libva >= 2.14.0, \
libwayland-client >= 1.20.0, \
libX11 >= 1.7.3.1, \
+ mesa-libgbm >= 25.0.7, \
miniupnpc >= 2.2.4, \
numactl-libs >= 2.0.14, \
openssl >= 3.0.2, \
diff --git a/cmake/packaging/windows.cmake b/cmake/packaging/windows.cmake
index 67c767b2..5170111d 100644
--- a/cmake/packaging/windows.cmake
+++ b/cmake/packaging/windows.cmake
@@ -4,6 +4,20 @@ install(TARGETS sunshine RUNTIME DESTINATION "." COMPONENT application)
# Hardening: include zlib1.dll (loaded via LoadLibrary() in openssl's libcrypto.a)
install(FILES "${ZLIB}" DESTINATION "." COMPONENT application)
+# ViGEmBus installer
+set(VIGEMBUS_INSTALLER "${CMAKE_BINARY_DIR}/vigembus_installer.exe")
+file(DOWNLOAD
+ "https://github.com/nefarius/ViGEmBus/releases/download/v1.21.442.0/ViGEmBus_1.21.442_x64_x86_arm64.exe"
+ ${VIGEMBUS_INSTALLER}
+ SHOW_PROGRESS
+ EXPECTED_HASH SHA256=155c50f1eec07bdc28d2f61a3e3c2c6c132fee7328412de224695f89143316bc
+ TIMEOUT 60
+)
+install(FILES ${VIGEMBUS_INSTALLER}
+ DESTINATION "scripts"
+ RENAME "vigembus_installer.exe"
+ COMPONENT gamepad)
+
# Adding tools
install(TARGETS dxgi-info RUNTIME DESTINATION "tools" COMPONENT dxgi)
install(TARGETS audio-info RUNTIME DESTINATION "tools" COMPONENT audio)
diff --git a/cmake/packaging/windows_nsis.cmake b/cmake/packaging/windows_nsis.cmake
index 79454b7c..50e43749 100644
--- a/cmake/packaging/windows_nsis.cmake
+++ b/cmake/packaging/windows_nsis.cmake
@@ -16,7 +16,8 @@ SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS
nsExec::ExecToLog '\\\"$INSTDIR\\\\drivers\\\\sudovda\\\\install.bat\\\"'
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\migrate-config.bat\\\"'
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\add-firewall-rule.bat\\\"'
- nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\install-gamepad.bat\\\"'
+ nsExec::ExecToLog \
+ 'powershell.exe -ExecutionPolicy Bypass -File \\\"$INSTDIR\\\\scripts\\\\install-gamepad.ps1\\\"'
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\install-service.bat\\\"'
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\autostart-service.bat\\\"'
NoController:
@@ -32,7 +33,9 @@ set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS
MessageBox MB_YESNO|MB_ICONQUESTION \
'Do you want to remove Virtual Gamepad?' \
/SD IDNO IDNO NoGamepad
- nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\uninstall-gamepad.bat\\\"'; skipped if no
+ nsExec::ExecToLog \
+ 'powershell.exe -ExecutionPolicy Bypass -File \\\"$INSTDIR\\\\scripts\\\\uninstall-gamepad.ps1\\\"'; \
+ skipped if no
NoGamepad:
MessageBox MB_YESNO|MB_ICONQUESTION \
'Do you want to remove SudoVDA Virtual Display Driver?' \
diff --git a/cmake/prep/build_version.cmake b/cmake/prep/build_version.cmake
index 69c4d7cd..3043c86f 100644
--- a/cmake/prep/build_version.cmake
+++ b/cmake/prep/build_version.cmake
@@ -1,18 +1,18 @@
# Set build variables if env variables are defined
# These are used in configured files such as manifests for different packages
-if(DEFINED ENV{BRANCH}) # cmake-lint: disable=W0106
+if(DEFINED ENV{BRANCH})
set(GITHUB_BRANCH $ENV{BRANCH})
endif()
if(DEFINED ENV{BUILD_VERSION}) # cmake-lint: disable=W0106
set(BUILD_VERSION $ENV{BUILD_VERSION})
endif()
-if(DEFINED ENV{CLONE_URL}) # cmake-lint: disable=W0106
+if(DEFINED ENV{CLONE_URL})
set(GITHUB_CLONE_URL $ENV{CLONE_URL})
endif()
-if(DEFINED ENV{COMMIT}) # cmake-lint: disable=W0106
+if(DEFINED ENV{COMMIT})
set(GITHUB_COMMIT $ENV{COMMIT})
endif()
-if(DEFINED ENV{TAG}) # cmake-lint: disable=W0106
+if(DEFINED ENV{TAG})
set(GITHUB_TAG $ENV{TAG})
endif()
diff --git a/cmake/targets/common.cmake b/cmake/targets/common.cmake
index 4cff2ce6..ba378cbb 100644
--- a/cmake/targets/common.cmake
+++ b/cmake/targets/common.cmake
@@ -27,7 +27,7 @@ endif()
target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES} ${EXTRA_LIBS})
target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS})
-set_target_properties(sunshine PROPERTIES CXX_STANDARD 20
+set_target_properties(sunshine PROPERTIES CXX_STANDARD 23
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR})
diff --git a/docker/archlinux.dockerfile b/docker/archlinux.dockerfile
index 89071f60..63a9be63 100644
--- a/docker/archlinux.dockerfile
+++ b/docker/archlinux.dockerfile
@@ -32,7 +32,6 @@ ENV CLONE_URL=${CLONE_URL}
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
-# hadolint ignore=SC2016
RUN <<_SETUP
#!/bin/bash
set -e
@@ -42,6 +41,7 @@ useradd -m builder
echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers
# patch the build flags
+# shellcheck disable=SC2016
sed -i 's,#MAKEFLAGS="-j2",MAKEFLAGS="-j$(nproc)",g' /etc/makepkg.conf
# install dependencies
diff --git a/docker/clion-toolchain.dockerfile b/docker/clion-toolchain.dockerfile
index ec54568c..f5080d18 100644
--- a/docker/clion-toolchain.dockerfile
+++ b/docker/clion-toolchain.dockerfile
@@ -19,7 +19,6 @@ ENV DISPLAY=:0
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# install dependencies
-# hadolint ignore=SC1091
RUN <<_DEPS
#!/bin/bash
set -e
@@ -74,7 +73,6 @@ WORKDIR /build/cuda
# versions: https://developer.nvidia.com/cuda-toolkit-archive
ENV CUDA_VERSION="11.8.0"
ENV CUDA_BUILD="520.61.05"
-# hadolint ignore=SC3010
RUN <<_INSTALL_CUDA
#!/bin/bash
set -e
diff --git a/docker/debian-bookworm.dockerfile b/docker/debian-bookworm.dockerfile
index f88cea05..ffa68b7c 100644
--- a/docker/debian-bookworm.dockerfile
+++ b/docker/debian-bookworm.dockerfile
@@ -42,7 +42,6 @@ _BUILD
# run tests
WORKDIR /build/sunshine/build/tests
-# hadolint ignore=SC1091
RUN <<_TEST
#!/bin/bash
set -e
diff --git a/docker/ubuntu-22.04.dockerfile b/docker/ubuntu-22.04.dockerfile
index 7bb24eb8..06032175 100644
--- a/docker/ubuntu-22.04.dockerfile
+++ b/docker/ubuntu-22.04.dockerfile
@@ -42,7 +42,6 @@ _BUILD
# run tests
WORKDIR /build/sunshine/build/tests
-# hadolint ignore=SC1091
RUN <<_TEST
#!/bin/bash
set -e
diff --git a/docker/ubuntu-24.04.dockerfile b/docker/ubuntu-24.04.dockerfile
index 984cfdcb..e11f18b2 100644
--- a/docker/ubuntu-24.04.dockerfile
+++ b/docker/ubuntu-24.04.dockerfile
@@ -42,7 +42,6 @@ _BUILD
# run tests
WORKDIR /build/sunshine/build/tests
-# hadolint ignore=SC1091
RUN <<_TEST
#!/bin/bash
set -e
diff --git a/docs/configuration.md b/docs/configuration.md
index 0d860111..b37578e2 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -1291,7 +1291,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
Remap the requested resolution and FPS to another display mode.
Depending on the [dd_resolution_option](#dd_resolution_option) and
- [dd_refresh_rate_option](#dd_refresh_rate_option) values, the following mapping
+ [dd_refresh_rate_option](#dd_refresh_rate_option) values, the following mapping
groups are available:
- `mixed` - both options are set to `auto`.
@@ -1302,7 +1302,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
`refresh_rate_only` - only [dd_refresh_rate_option](#dd_refresh_rate_option) is set to `auto`.
- For each of those groups, a list of fields can be configured to perform remapping:
+ For each of those groups, a list of fields can be configured to perform remapping:
-
`requested_resolution` - resolution that needs to be matched in order to use this remapping entry.
@@ -1312,10 +1312,10 @@ editing the `conf` file in a text editor. Use the examples as reference.
- `final_refresh_rate` - refresh rate value to be used if the entry was matched.
If `requested_*` field is left empty, it will match everything.
- If `final_*` field is left empty, the original value will not be remapped and either a requested, manual
- or current value is used. However, at least one `final_*` must be set, otherwise the entry is considered
+ If `final_*` field is left empty, the original value will not be remapped and either a requested, manual
+ or current value is used. However, at least one `final_*` must be set, otherwise the entry is considered
invalid.
- @note{"Optimize game settings" must be enabled on client side for ANY entry with `resolution`
+ @note{"Optimize game settings" must be enabled on client side for ANY entry with `resolution`
field to be considered.}
@note{First entry to be matched in the list is the one that will be used.}
@tip{`requested_resolution` and `final_resolution` can be omitted for `refresh_rate_only` group.}
@@ -1392,6 +1392,32 @@ editing the `conf` file in a text editor. Use the examples as reference.
+### minimum_fps_target
+
+
+
+ | Description |
+
+ Sunshine tries to save bandwidth when content on screen is static or a low framerate. Because many clients expect a constant stream of video frames, a certain amount of duplicate frames are sent when this happens. This setting controls the lowest effective framerate a stream can reach.
+ |
+
+
+ | Default |
+ @code{}
+ 0
+ @endcode |
+
+
+ | Choices |
+ 0 |
+ Use half the stream's FPS as the minimum target. |
+
+
+ | 1-1000 |
+ Specify your own value. The real minimum may differ from this value. |
+
+
+
## Network
### upnp
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
index 8bd22eed..479956bb 100644
--- a/docs/troubleshooting.md
+++ b/docs/troubleshooting.md
@@ -84,9 +84,35 @@ client only 1 Gbit/s or Wi-Fi. Similarly, a 1 Gbps host may be too fast for a
client having only a 100 Mbps interface.
As a workaround the transmission speed of the host NIC can be reduced: 1 Gbps
-instead of 2.5 or 100 Mbps instead of 1 Gbps. (A technically more advanced
+instead of 2.5 or 100 Mbps instead of 1 Gbps. A technically more advanced
solution would be to configure traffic shaping rules at the OS-level, so that
-only Sunshine's traffic is slowed down.)
+only Sunshine's traffic is slowed down.
+
+Such a solution on Linux could look like that:
+
+```bash
+# 1) Remove existing qdisc (pfifo_fast)
+sudo tc qdisc del dev root
+
+# 2) Add HTB root qdisc with default class 1:1
+sudo tc qdisc add dev root handle 1: htb default 1
+
+# 3) Create class 1:1 for full 10 Gbit/s (all other traffic)
+sudo tc class add dev parent 1: classid 1:1 htb \
+ rate 10000mbit ceil 10000mbit burst 32k
+
+# 4) Create class 1:10 for Sunshine game stream at 1 Gbit/s
+sudo tc class add dev parent 1: classid 1:10 htb \
+ rate 1000mbit ceil 1000mbit burst 32k
+
+# 5) Filter UDP source port 47998 into class 1:10
+sudo tc filter add dev protocol ip parent 1: prio 1 \
+ u32 match ip protocol 17 0xff \
+ match ip sport 47998 0xffff flowid 1:10
+```
+
+In that way only the Sunshine traffic is limited by 1 Gbit. This is not persistent on reboots.
+If you use a different port for the game stream you need to adjust the last command.
Sunshine versions > 0.23.1 include improved networking code that should
alleviate or even solve this issue (without reducing the NIC speed).
diff --git a/package.json b/package.json
index 0e7c10d4..39094545 100644
--- a/package.json
+++ b/package.json
@@ -9,8 +9,8 @@
},
"dependencies": {
"@lizardbyte/shared-web": "2025.626.181239",
- "vue": "3.5.17",
- "vue-i18n": "11.1.9"
+ "vue": "3.5.18",
+ "vue-i18n": "11.1.11"
},
"devDependencies": {
"@codecov/vite-plugin": "1.9.1",
diff --git a/packaging/linux/fedora/Sunshine.spec b/packaging/linux/fedora/Sunshine.spec
index d98bbbf1..d027b361 100644
--- a/packaging/linux/fedora/Sunshine.spec
+++ b/packaging/linux/fedora/Sunshine.spec
@@ -97,9 +97,6 @@ tar -xzf %{SOURCE0} -C %{_builddir}/Sunshine
# list directory
ls -a %{_builddir}/Sunshine
-# patches
-%autopatch -p1
-
%build
# exit on error
set -e
diff --git a/packaging/linux/flatpak/deps/flatpak-builder-tools b/packaging/linux/flatpak/deps/flatpak-builder-tools
index 903919f8..7090720d 160000
--- a/packaging/linux/flatpak/deps/flatpak-builder-tools
+++ b/packaging/linux/flatpak/deps/flatpak-builder-tools
@@ -1 +1 @@
-Subproject commit 903919f82f4cd6356bb4e9afe2755e44e8d8d7da
+Subproject commit 7090720d43404c60b88445a3149835ec3c585717
diff --git a/packaging/linux/flatpak/deps/shared-modules b/packaging/linux/flatpak/deps/shared-modules
index 756091e3..b63062b3 160000
--- a/packaging/linux/flatpak/deps/shared-modules
+++ b/packaging/linux/flatpak/deps/shared-modules
@@ -1 +1 @@
-Subproject commit 756091e3d5d6582bec4e270184319e59c04ab365
+Subproject commit b63062b3cdf451e537ed6ca1f68f9c2701ad8a98
diff --git a/packaging/linux/flatpak/scripts/additional-install.sh b/packaging/linux/flatpak/scripts/additional-install.sh
index a27db4e0..06c5835e 100644
--- a/packaging/linux/flatpak/scripts/additional-install.sh
+++ b/packaging/linux/flatpak/scripts/additional-install.sh
@@ -2,12 +2,12 @@
# User Service
mkdir -p ~/.config/systemd/user
-cp /app/share/sunshine/systemd/user/sunshine.service $HOME/.config/systemd/user/sunshine.service
-echo Sunshine User Service has been installed.
-echo Use [systemctl --user enable sunshine] once to autostart Sunshine on login.
+cp "/app/share/sunshine/systemd/user/sunshine.service" "$HOME/.config/systemd/user/sunshine.service"
+echo "Sunshine User Service has been installed."
+echo "Use [systemctl --user enable sunshine] once to autostart Sunshine on login."
# Udev rule
UDEV=$(cat /app/share/sunshine/udev/rules.d/60-sunshine.rules)
-echo Configuring mouse permission.
+echo "Configuring mouse permission."
flatpak-spawn --host pkexec sh -c "echo '$UDEV' > /etc/udev/rules.d/60-sunshine.rules"
-echo Restart computer for mouse permission to take effect.
+echo "Restart computer for mouse permission to take effect."
diff --git a/packaging/linux/flatpak/scripts/remove-additional-install.sh b/packaging/linux/flatpak/scripts/remove-additional-install.sh
index 27d7af03..ea2680ef 100644
--- a/packaging/linux/flatpak/scripts/remove-additional-install.sh
+++ b/packaging/linux/flatpak/scripts/remove-additional-install.sh
@@ -2,10 +2,10 @@
# User Service
systemctl --user stop sunshine
-rm $HOME/.config/systemd/user/sunshine.service
+rm "$HOME/.config/systemd/user/sunshine.service"
systemctl --user daemon-reload
-echo Sunshine User Service has been removed.
+echo "Sunshine User Service has been removed."
# Udev rule
flatpak-spawn --host pkexec sh -c "rm /etc/udev/rules.d/60-sunshine.rules"
-echo Input rules removed. Restart computer to take effect.
+echo "Input rules removed. Restart computer to take effect."
diff --git a/packaging/sunshine.rb b/packaging/sunshine.rb
index ad7b46ee..97eb9fe5 100644
--- a/packaging/sunshine.rb
+++ b/packaging/sunshine.rb
@@ -29,9 +29,8 @@ class @PROJECT_NAME@ < Formula
depends_on "cmake" => :build
depends_on "doxygen" => :build
depends_on "graphviz" => :build
- depends_on "ninja" => :build
depends_on "node" => :build
- depends_on "pkg-config" => :build
+ depends_on "pkgconf" => :build
depends_on "curl"
depends_on "miniupnpc"
depends_on "openssl"
@@ -39,25 +38,8 @@ class @PROJECT_NAME@ < Formula
depends_on "icu4c" => :recommended
on_linux do
- # the "build" dependencies are for libayatana-appindicator
- depends_on "at-spi2-core" => :build
- depends_on "cairo" => :build
- depends_on "fontconfig" => :build
- depends_on "freetype" => :build
- depends_on "fribidi" => :build
- depends_on "gettext" => :build
- depends_on "gobject-introspection" => :build
- depends_on "graphite2" => :build
- depends_on "gtk+3" => :build
- depends_on "harfbuzz" => :build
- depends_on "intltool" => :build
- depends_on "libepoxy" => :build
- depends_on "libxdamage" => :build
- depends_on "libxkbcommon" => :build
- depends_on "pango" => :build
- depends_on "perl" => :build
- depends_on "pixman" => :build
depends_on "avahi"
+ depends_on "libayatana-appindicator"
depends_on "libcap"
depends_on "libdrm"
depends_on "libnotify"
@@ -75,128 +57,6 @@ class @PROJECT_NAME@ < Formula
depends_on "pulseaudio"
depends_on "systemd"
depends_on "wayland"
-
- # resources that do not have brew packages
- resource "libayatana-appindicator" do
- url "https://github.com/AyatanaIndicators/libayatana-appindicator/archive/refs/tags/0.5.94.tar.gz"
- sha256 "884a6bc77994c0b58c961613ca4c4b974dc91aa0f804e70e92f38a542d0d0f90"
- end
-
- resource "libdbusmenu" do
- url "https://launchpad.net/libdbusmenu/16.04/16.04.0/+download/libdbusmenu-16.04.0.tar.gz"
- sha256 "b9cc4a2acd74509435892823607d966d424bd9ad5d0b00938f27240a1bfa878a"
-
- patch 'From 729546c51806a1b3ea6cb6efb7a115b1baa811f1 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Stefan=20Br=C3=BCns?=
-Date: Mon, 18 Nov 2019 19:58:53 +0100
-Subject: [PATCH 1/1] Fix HAVE_VALGRIND AM_CONDITIONAL
-
-The AM_CONDITIONAL should also be run with --disable-tests, otherwise
-HAVE_VALGRIND is undefined.
----
- configure | 4 ++--
- configure.ac | 2 +-
- 2 files changed, 3 insertions(+), 3 deletions(-)
-
-diff --git a/configure b/configure
-index 831a3bb..8913b9b 100644
---- a/configure
-+++ b/configure
-@@ -14801,6 +14801,8 @@ else
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
- $as_echo "yes" >&6; }
- have_valgrind=yes
-+fi
-+
- fi
- if test "x$have_valgrind" = "xyes"; then
- HAVE_VALGRIND_TRUE=
-@@ -14811,8 +14813,6 @@ else
- fi
-
-
--fi
--
-
-
-
-diff --git a/configure.ac b/configure.ac
-index ace54d1..cbd38a6 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -120,8 +120,8 @@ PKG_CHECK_MODULES(DBUSMENUTESTS, json-glib-1.0 >= $JSON_GLIB_REQUIRED_VERSION
- [have_tests=yes]
- )
- PKG_CHECK_MODULES(DBUSMENUTESTSVALGRIND, valgrind, have_valgrind=yes, have_valgrind=no)
--AM_CONDITIONAL([HAVE_VALGRIND], [test "x$have_valgrind" = "xyes"])
- ])
-+AM_CONDITIONAL([HAVE_VALGRIND], [test "x$have_valgrind" = "xyes"])
-
- AC_SUBST(DBUSMENUTESTS_CFLAGS)
- AC_SUBST(DBUSMENUTESTS_LIBS)
---
-2.46.2
-
-
-'
- end
-
- resource "ayatana-ido" do
- url "https://github.com/AyatanaIndicators/ayatana-ido/archive/refs/tags/0.10.4.tar.gz"
- sha256 "bd59abd5f1314e411d0d55ce3643e91cef633271f58126be529de5fb71c5ab38"
-
- patch 'From 8a09e6ad33c58c017c0c8fd756da036fc39428ea Mon Sep 17 00:00:00 2001
-From: Alexander Koskovich
-Date: Sun, 29 Sep 2024 13:47:54 -0400
-Subject: [PATCH 1/1] Make introspection configurable
-
----
- CMakeLists.txt | 1 +
- src/CMakeLists.txt | 4 ++++
- 2 files changed, 5 insertions(+)
-
-diff --git a/CMakeLists.txt b/CMakeLists.txt
-index 0e13fcd..f3e9ec0 100644
---- a/CMakeLists.txt
-+++ b/CMakeLists.txt
-@@ -12,6 +12,7 @@ endif(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
- option(ENABLE_TESTS "Enable all tests and checks" OFF)
- option(ENABLE_COVERAGE "Enable coverage reports (includes enabling all tests and checks)" OFF)
- option(ENABLE_WERROR "Treat all build warnings as errors" OFF)
-+option(ENABLE_INTROSPECTION "Enable introspection" ON)
-
- if(ENABLE_COVERAGE)
- set(ENABLE_TESTS ON)
-diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
-index 5b3638d..aca9481 100644
---- a/src/CMakeLists.txt
-+++ b/src/CMakeLists.txt
-@@ -108,6 +108,8 @@ install(TARGETS "ayatana-ido3-0.4" LIBRARY DESTINATION "${CMAKE_INSTALL_FULL_LIB
-
- # AyatanaIdo3-0.4.gir
-
-+if (ENABLE_INTROSPECTION)
-+
- find_package(GObjectIntrospection REQUIRED QUIET)
-
- if (INTROSPECTION_FOUND)
-@@ -183,3 +185,5 @@ if (INTROSPECTION_FOUND)
- endif ()
-
- endif ()
-+
-+endif ()
---
-2.46.2
-
-
-'
- end
-
- resource "libayatana-indicator" do
- url "https://github.com/AyatanaIndicators/libayatana-indicator/archive/refs/tags/0.9.4.tar.gz"
- sha256 "a18d3c682e29afd77db24366f8475b26bda22b0e16ff569a2ec71cd6eb4eac95"
- end
end
def install
@@ -206,7 +66,7 @@ index 5b3638d..aca9481 100644
args = %W[
-DBUILD_WERROR=ON
- -DCMAKE_CXX_STANDARD=20
+ -DCMAKE_CXX_STANDARD=23
-DCMAKE_INSTALL_PREFIX=#{prefix}
-DHOMEBREW_ALLOW_FETCHCONTENT=ON
-DOPENSSL_ROOT_DIR=#{Formula["openssl"].opt_prefix}
@@ -248,59 +108,6 @@ index 5b3638d..aca9481 100644
args << "-DCUDA_FAIL_ON_MISSING=OFF" if OS.linux?
args << "-DSUNSHINE_ENABLE_TRAY=OFF" if OS.mac?
- # Handle system tray on Linux
- if OS.linux?
- # Build and install libayatana components
-
- # Build libdbusmenu
- resource("libdbusmenu").stage do
- system "./configure",
- "--prefix=#{prefix}",
- "--with-gtk=3",
- "--disable-dumper",
- "--disable-static",
- "--disable-tests",
- "--disable-gtk-doc",
- "--enable-introspection=no",
- "--disable-vala"
- system "make", "install"
- end
-
- # Build ayatana-ido
- resource("ayatana-ido").stage do
- system "cmake", "-S", ".", "-B", "build", "-G", "Ninja",
- "-DCMAKE_INSTALL_PREFIX=#{prefix}",
- "-DENABLE_INTROSPECTION=OFF",
- *std_cmake_args
- system "ninja", "-C", "build"
- system "ninja", "-C", "build", "install"
- end
-
- # Build libayatana-indicator
- resource("libayatana-indicator").stage do
- ENV.append_path "PKG_CONFIG_PATH", "#{lib}/pkgconfig"
- ENV.append "LDFLAGS", "-L#{lib}"
-
- system "cmake", "-S", ".", "-B", "build", "-G", "Ninja",
- "-DCMAKE_INSTALL_PREFIX=#{prefix}",
- *std_cmake_args
- system "ninja", "-C", "build"
- system "ninja", "-C", "build", "install"
- end
-
- # Build libayatana-appindicator
- resource("libayatana-appindicator").stage do
- system "cmake", "-S", ".", "-B", "build", "-G", "Ninja",
- "-DCMAKE_INSTALL_PREFIX=#{prefix}",
- "-DENABLE_BINDINGS_MONO=OFF",
- "-DENABLE_BINDINGS_VALA=OFF",
- "-DENABLE_GTKDOC=OFF",
- *std_cmake_args
- system "ninja", "-C", "build"
- system "ninja", "-C", "build", "install"
- end
- end
-
system "cmake", "-S", ".", "-B", "build", "-G", "Unix Makefiles",
*std_cmake_args,
*args
diff --git a/scripts/icons/convert_and_pack.sh b/scripts/icons/convert_and_pack.sh
index e2bae63f..0ce197d1 100644
--- a/scripts/icons/convert_and_pack.sh
+++ b/scripts/icons/convert_and_pack.sh
@@ -1,61 +1,63 @@
-#!/bin/bash
-
-if ! [ -x "$(command -v ./go-png2ico)" ]; then
- echo "./go-png2ico not found"
- echo "download the executable from https://github.com/J-Siu/go-png2ico"
- echo "and drop it in this folder"
- exit 1
-fi
-
-if ! [ -x "$(command -v ./oxipng)" ]; then
- echo "./oxipng executable not found"
- echo "download the executable from https://github.com/shssoichiro/oxipng"
- echo "and drop it in this folder"
- exit 1
-fi
-
-if ! [ -x "$(command -v inkscape)" ]; then
- echo "inkscape executable not found"
- exit 1
-fi
-
-icon_base_sizes=(16 64)
-icon_sizes_keys=() # associative array to prevent duplicates
-icon_sizes_keys[256]=1
-
-for icon_base_size in ${icon_base_sizes[@]}; do
- # increment in 25% till 400%
- icon_size_increment=$((icon_base_size / 4))
- for ((i = 0; i <= 12; i++)); do
- icon_sizes_keys[$((icon_base_size + i * icon_size_increment))]=1
- done
-done
-
-# convert to normal array
-icon_sizes=${!icon_sizes_keys[@]}
-
-echo "using icon sizes:"
-echo ${icon_sizes[@]}
-
-src_vectors=("../../src_assets/common/assets/web/public/images/apollo-locked.svg"
- "../../src_assets/common/assets/web/public/images/apollo-pausing.svg"
- "../../src_assets/common/assets/web/public/images/apollo-playing.svg"
- "../../apollo.svg")
-
-echo "using sources vectors:"
-echo ${src_vectors[@]}
-
-for src_vector in ${src_vectors[@]}; do
- file_name=`basename "$src_vector" .svg`
- png_files=()
- for icon_size in ${icon_sizes[@]}; do
- png_file="${file_name}${icon_size}.png"
- echo "converting ${png_file}"
- inkscape -w $icon_size -h $icon_size "$src_vector" --export-filename "${png_file}" &&
- ./oxipng -o max --strip safe --alpha "${png_file}" &&
- png_files+=("${png_file}")
- done
-
- echo "packing ${file_name}.ico"
- ./go-png2ico "${png_files[@]}" "${file_name}.ico"
-done
+#!/bin/bash
+
+if ! [ -x "$(command -v ./go-png2ico)" ]; then
+ echo "./go-png2ico not found"
+ echo "download the executable from https://github.com/J-Siu/go-png2ico"
+ echo "and drop it in this folder"
+ exit 1
+fi
+
+if ! [ -x "$(command -v ./oxipng)" ]; then
+ echo "./oxipng executable not found"
+ echo "download the executable from https://github.com/shssoichiro/oxipng"
+ echo "and drop it in this folder"
+ exit 1
+fi
+
+if ! [ -x "$(command -v inkscape)" ]; then
+ echo "inkscape executable not found"
+ exit 1
+fi
+
+icon_base_sizes=(16 64)
+icon_sizes_keys=() # associative array to prevent duplicates
+icon_sizes_keys[256]=1
+
+for icon_base_size in "${icon_base_sizes[@]}"; do
+ # increment in 25% till 400%
+ icon_size_increment=$((icon_base_size / 4))
+ for ((i = 0; i <= 12; i++)); do
+ icon_sizes_keys[icon_base_size + i * icon_size_increment]=1
+ done
+done
+
+# convert to normal array
+icon_sizes=("${!icon_sizes_keys[@]}")
+
+echo "using icon sizes:"
+# shellcheck disable=SC2068 # intentionally word split
+echo ${icon_sizes[@]}
+
+src_vectors=("../../src_assets/common/assets/web/public/images/apollo-locked.svg"
+ "../../src_assets/common/assets/web/public/images/apollo-pausing.svg"
+ "../../src_assets/common/assets/web/public/images/apollo-playing.svg"
+ "../../apollo.svg")
+
+echo "using sources vectors:"
+# shellcheck disable=SC2068 # intentionally word split
+echo ${src_vectors[@]}
+
+for src_vector in "${src_vectors[@]}"; do
+ file_name=$(basename "${src_vector}" .svg)
+ png_files=()
+ for icon_size in "${icon_sizes[@]}"; do
+ png_file="${file_name}${icon_size}.png"
+ echo "converting ${png_file}"
+ inkscape -w "${icon_size}" -h "${icon_size}" "${src_vector}" --export-filename "${png_file}" &&
+ ./oxipng -o max --strip safe --alpha "${png_file}" &&
+ png_files+=("${png_file}")
+ done
+
+ echo "packing ${file_name}.ico"
+ ./go-png2ico "${png_files[@]}" "${file_name}.ico"
+done
diff --git a/scripts/linux_build.sh b/scripts/linux_build.sh
index 66d0e9c8..646619ec 100644
--- a/scripts/linux_build.sh
+++ b/scripts/linux_build.sh
@@ -96,9 +96,11 @@ function add_arch_deps() {
'base-devel'
'cmake'
'curl'
+ 'doxygen'
"gcc${gcc_version}"
"gcc${gcc_version}-libs"
'git'
+ 'graphviz'
'libayatana-appindicator'
'libcap'
'libdrm'
@@ -406,7 +408,7 @@ function run_install() {
for file in "${gcc_alternative_files[@]}"; do
file_path="/etc/alternatives/$file"
if [ -e "$file_path" ]; then
- mv "$file_path" "$file_path.bak"
+ ${sudo_cmd} mv "$file_path" "$file_path.bak"
fi
done
@@ -445,12 +447,14 @@ function run_install() {
echo "Compiling doxygen"
doxygen_url="https://github.com/doxygen/doxygen/releases/download/Release_${_doxygen_min}/doxygen-${doxygen_min}.src.tar.gz"
echo "doxygen url: ${doxygen_url}"
- wget "$doxygen_url" --progress=bar:force:noscroll -q --show-progress -O "${build_dir}/doxygen.tar.gz"
- tar -xzf "${build_dir}/doxygen.tar.gz"
- cd "doxygen-${doxygen_min}"
- cmake -DCMAKE_BUILD_TYPE=Release -G="Ninja" -B="build" -S="."
- ninja -C "build" -j"${num_processors}"
- ninja -C "build" install
+ pushd "${build_dir}"
+ wget "$doxygen_url" --progress=bar:force:noscroll -q --show-progress -O "doxygen.tar.gz"
+ tar -xzf "doxygen.tar.gz"
+ cd "doxygen-${doxygen_min}"
+ cmake -DCMAKE_BUILD_TYPE=Release -G="Ninja" -B="build" -S="."
+ ninja -C "build" -j"${num_processors}"
+ ${sudo_cmd} ninja -C "build" install
+ popd
else
echo "Doxygen version not in range, skipping docs"
cmake_args+=("-DBUILD_DOCS=OFF")
@@ -462,6 +466,8 @@ function run_install() {
nvm_url="https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh"
echo "nvm url: ${nvm_url}"
wget -qO- ${nvm_url} | bash
+
+ # shellcheck source=/dev/null # we don't care that shellcheck cannot find nvm.sh
source "$HOME/.nvm/nvm.sh"
nvm install node
nvm use node
diff --git a/src/config.cpp b/src/config.cpp
index 08613b14..f30596d5 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -34,7 +34,7 @@
#include "platform/windows/utils.h"
#endif
-#ifndef __APPLE__
+#if !defined(__ANDROID__) && !defined(__APPLE__)
// For NVENC legacy constants
#include
#endif
@@ -511,6 +511,7 @@ namespace config {
}, // display_device
0, // max_bitrate
+ 0, // minimum_fps_target (0 = framerate)
"1920x1080x60", // fallback_mode
false, // isolated Display
@@ -1085,9 +1086,12 @@ namespace config {
}
void apply_config(std::unordered_map &&vars) {
+#ifndef __ANDROID__
+ // TODO: Android can possibly support this
if (!fs::exists(stream.file_apps.c_str())) {
fs::copy_file(SUNSHINE_ASSETS_DIR "/apps.json", stream.file_apps);
}
+#endif
for (auto &[name, val] : vars) {
#ifdef _WIN32
@@ -1121,7 +1125,7 @@ namespace config {
bool_f(vars, "nvenc_opengl_vulkan_on_dxgi", video.nv_opengl_vulkan_on_dxgi);
bool_f(vars, "nvenc_latency_over_power", video.nv_sunshine_high_power_mode);
-#ifndef __APPLE__
+#if !defined(__ANDROID__) && !defined(__APPLE__)
video.nv_legacy.preset = video.nv.quality_preset + 11;
video.nv_legacy.multipass = video.nv.two_pass == nvenc::nvenc_two_pass::quarter_resolution ? NV_ENC_TWO_PASS_QUARTER_RESOLUTION :
video.nv.two_pass == nvenc::nvenc_two_pass::full_resolution ? NV_ENC_TWO_PASS_FULL_RESOLUTION :
@@ -1198,6 +1202,8 @@ namespace config {
}
int_f(vars, "max_bitrate", video.max_bitrate);
+ double_between_f(vars, "minimum_fps_target", video.minimum_fps_target, {0.0, 1000.0});
+
string_f(vars, "fallback_mode", video.fallback_mode);
bool_f(vars, "isolated_virtual_display_option", video.isolated_virtual_display_option);
bool_f(vars, "ignore_encoder_probe_failure", video.ignore_encoder_probe_failure);
diff --git a/src/config.h b/src/config.h
index c1f73b54..bec1ac95 100644
--- a/src/config.h
+++ b/src/config.h
@@ -144,6 +144,7 @@ namespace config {
} dd;
int max_bitrate; // Maximum bitrate, sets ceiling in kbps for bitrate requested from client
+ double minimum_fps_target; ///< Lowest framerate that will be used when streaming. Range 0-1000, 0 = half of client's requested framerate.
std::string fallback_mode;
bool isolated_virtual_display_option;
diff --git a/src/logging.cpp b/src/logging.cpp
index ce97197c..c9d682f8 100644
--- a/src/logging.cpp
+++ b/src/logging.cpp
@@ -16,11 +16,17 @@
#include
#include
#include
-#include
// local includes
#include "logging.h"
+// conditional includes
+#ifdef __ANDROID__
+ #include
+#else
+ #include
+#endif
+
extern "C" {
#include
}
@@ -98,6 +104,48 @@ namespace logging {
os << "["sv << std::put_time(<, "%Y-%m-%d %H:%M:%S.") << boost::format("%03u") % ms.count() << "]: "sv
<< log_type << view.attribute_values()[message].extract();
}
+#ifdef __ANDROID__
+ namespace sinks = boost::log::sinks;
+ namespace expr = boost::log::expressions;
+
+ void android_log(const std::string &message, int severity) {
+ android_LogPriority android_priority;
+ switch (severity) {
+ case 0:
+ android_priority = ANDROID_LOG_VERBOSE;
+ break;
+ case 1:
+ android_priority = ANDROID_LOG_DEBUG;
+ break;
+ case 2:
+ android_priority = ANDROID_LOG_INFO;
+ break;
+ case 3:
+ android_priority = ANDROID_LOG_WARN;
+ break;
+ case 4:
+ android_priority = ANDROID_LOG_ERROR;
+ break;
+ case 5:
+ android_priority = ANDROID_LOG_FATAL;
+ break;
+ default:
+ android_priority = ANDROID_LOG_UNKNOWN;
+ break;
+ }
+ __android_log_print(android_priority, "Sunshine", "%s", message.c_str());
+ }
+
+ // custom sink backend for android
+ struct android_sink_backend: public sinks::basic_sink_backend {
+ void consume(const bl::record_view &rec) {
+ int log_sev = rec[severity].get();
+ const std::string log_msg = rec[expr::smessage].get();
+ // log to android
+ android_log(log_msg, log_sev);
+ }
+ };
+#endif
[[nodiscard]] std::unique_ptr init(int min_log_level, const std::string &log_file) {
if (sink) {
@@ -120,15 +168,18 @@ namespace logging {
}
}
+#ifndef __ANDROID__
setup_av_logging(min_log_level);
setup_libdisplaydevice_logging(min_log_level);
+#endif
sink = boost::make_shared();
#ifndef SUNSHINE_TESTS
boost::shared_ptr stream {&std::cout, boost::null_deleter()};
sink->locked_backend()->add_stream(stream);
- #endif
+#endif
+
sink->locked_backend()->add_stream(boost::make_shared(log_file));
sink->set_filter(severity >= min_log_level);
sink->set_formatter(&formatter);
@@ -138,9 +189,15 @@ namespace logging {
sink->locked_backend()->auto_flush(true);
bl::core::get()->add_sink(sink);
+
+#ifdef __ANDROID__
+ auto android_sink = boost::make_shared>();
+ bl::core::get()->add_sink(android_sink);
+#endif
return std::make_unique();
}
+#ifndef __ANDROID__
void setup_av_logging(int min_log_level) {
if (min_log_level >= 1) {
av_log_set_level(AV_LOG_QUIET);
@@ -198,6 +255,7 @@ namespace logging {
}
});
}
+#endif
void log_flush() {
if (sink) {
diff --git a/src/video.cpp b/src/video.cpp
index b720b2c5..2156911d 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -1922,10 +1922,12 @@ namespace video {
}
});
- // set minimum frame time based on client-requested target framerate
- auto minimum_frame_time = std::chrono::nanoseconds(1000ms) * 1000 / config.encodingFramerate;
+ // set minimum frame time based on client-requested target framerate// set max frame time based on client-requested target framerate.
+ double minimum_fps_target = (config::video.minimum_fps_target > 0.0) ? config::video.minimum_fps_target : config.encodingFramerate;
+ auto max_frametime = std::chrono::nanoseconds(1000ms) * 1000 / minimum_fps_target;
auto encode_frame_threshold = std::chrono::nanoseconds(1000ms) * 1000 / config.encodingFramerate;
auto frame_variation_threshold = encode_frame_threshold / 4;
+ BOOST_LOG(info) << "Minimum FPS target set to ~"sv << (minimum_fps_target / 2) << "fps ("sv << max_frametime.count() * 2 << "ns)"sv;
BOOST_LOG(info) << "Encoding Frame threshold: "sv << encode_frame_threshold;
auto shutdown_event = mail->event(mail::shutdown);
@@ -1997,7 +1999,7 @@ namespace video {
// Encode at a minimum FPS to avoid image quality issues with static content
if (!requested_idr_frame || images->peek()) {
- if (auto img = images->pop(minimum_frame_time)) {
+ if (auto img = images->pop(max_frametime)) {
frame_timestamp = img->frame_timestamp;
// If new frame comes in way too fast, just drop
if (*frame_timestamp < (next_frame_start - frame_variation_threshold)) {
diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html
index 11f94d81..c0cb4873 100644
--- a/src_assets/common/assets/web/config.html
+++ b/src_assets/common/assets/web/config.html
@@ -208,6 +208,7 @@
"double_refreshrate": "disabled",
"dd_wa_hdr_toggle_delay": 0,
"max_bitrate": 0,
+ "minimum_fps_target": 0,
"isolated_virtual_display_option": "disabled",
},
},
diff --git a/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayDeviceOptions.vue b/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayDeviceOptions.vue
index 69784006..4b7b72e0 100644
--- a/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayDeviceOptions.vue
+++ b/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayDeviceOptions.vue
@@ -1,6 +1,7 @@
|