Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
dc73002
third-party: Add stubs for EGL DMA-BUF query APIs
garnacho May 4, 2024
c91d191
feat(linux): Support streaming through XDG portals and Pipewire
garnacho May 5, 2024
39e9956
style: fix lint errors
ReenigneArcher Nov 12, 2025
9066895
Update XDG portal dependencies and error message
ReenigneArcher Nov 12, 2025
7cb8933
Add Pipewire as a dependency for Linux builds
ReenigneArcher Nov 13, 2025
5356dc7
fix(portalgrab): change PERSIST_WHILE_RUNNING to 2
ReenigneArcher Dec 6, 2025
69c0947
feat(linux/portal): improve XDG portal capture for KDE and dual GPU s…
Traine9 Jan 20, 2026
14b12d1
fix(xdg)!: remove default setcap on sunshine binaries
ReenigneArcher Jan 22, 2026
4333ad5
refactor: move xdg portal capture after x11
ReenigneArcher Jan 23, 2026
e9a0044
Fix install path for sunshine-kms binary
ReenigneArcher Jan 23, 2026
cca272e
style: fix several sonar warnings
ReenigneArcher Jan 23, 2026
ecb6454
Refactor to use std::array for buffer and struct storage
ReenigneArcher Jan 23, 2026
268512b
Refactor array usage and variable declarations in portalgrab.cpp
ReenigneArcher Jan 23, 2026
a4947e8
Replace g_strdup_printf with std::format for path generation
ReenigneArcher Jan 23, 2026
a7947ee
Refactor portalgrab.cpp for code clarity
ReenigneArcher Jan 23, 2026
8401227
Refactor parameter assignment and improve type usage
ReenigneArcher Jan 23, 2026
c60ca9e
Refactor portalgrab.cpp for const correctness and C++ casts
ReenigneArcher Jan 23, 2026
850f2a6
Enable SUNSHINE_ENABLE_PORTAL in build configurations
ReenigneArcher Jan 23, 2026
ac222ae
fix(linux): Split service unit into privileged and unprivileged varia…
psyke83 Jan 24, 2026
dd56d99
fix(xdg-portal): add support for cursor_mode
ReenigneArcher Jan 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci-freebsd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ jobs:
graphics/wayland \
lang/python312 \
multimedia/libva \
multimedia/pipewire \
net/miniupnpc \
ports-mgmt/pkg \
security/openssl \
Expand Down Expand Up @@ -167,6 +168,7 @@ jobs:
-DSUNSHINE_EXECUTABLE_PATH=/usr/local/bin/sunshine \
-DSUNSHINE_ENABLE_CUDA=OFF \
-DSUNSHINE_ENABLE_DRM=OFF \
-DSUNSHINE_ENABLE_PORTAL=ON \
-DSUNSHINE_ENABLE_WAYLAND=ON \
-DSUNSHINE_ENABLE_X11=ON \
-DSUNSHINE_PUBLISHER_NAME="${GITHUB_REPOSITORY_OWNER}" \
Expand Down
19 changes: 18 additions & 1 deletion cmake/compile_definitions/linux.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,29 @@ if(X11_FOUND)
"${CMAKE_SOURCE_DIR}/src/platform/linux/x11grab.cpp")
endif()

# XDG portal
if(${SUNSHINE_ENABLE_PORTAL})
pkg_check_modules(GIO gio-2.0 gio-unix-2.0 REQUIRED)
pkg_check_modules(PIPEWIRE libpipewire-0.3 REQUIRED)
else()
set(GIO_FOUND OFF)
set(PIPEWIRE_FOUND OFF)
endif()
if(PIPEWIRE_FOUND)
add_compile_definitions(SUNSHINE_BUILD_PORTAL)
include_directories(SYSTEM ${GIO_INCLUDE_DIRS} ${PIPEWIRE_INCLUDE_DIRS})
list(APPEND PLATFORM_LIBRARIES ${GIO_LIBRARIES} ${PIPEWIRE_LIBRARIES})
list(APPEND PLATFORM_TARGET_FILES
"${CMAKE_SOURCE_DIR}/src/platform/linux/portalgrab.cpp")
endif()

if(NOT ${CUDA_FOUND}
AND NOT ${WAYLAND_FOUND}
AND NOT ${X11_FOUND}
AND NOT ${PIPEWIRE_FOUND}
AND NOT (${LIBDRM_FOUND} AND ${LIBCAP_FOUND})
AND NOT ${LIBVA_FOUND})
message(FATAL_ERROR "Couldn't find either cuda, wayland, x11, (libdrm and libcap), or libva")
message(FATAL_ERROR "Couldn't find either cuda, libva, pipewire, wayland, x11, or (libdrm and libcap)")
endif()

# tray icon
Expand Down
7 changes: 6 additions & 1 deletion cmake/packaging/linux.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ if(${SUNSHINE_BUILD_APPIMAGE} OR ${SUNSHINE_BUILD_FLATPAK})
DESTINATION "${SUNSHINE_ASSETS_DIR}/modules-load.d")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service"
DESTINATION "${SUNSHINE_ASSETS_DIR}/systemd/user")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine-kms.service"
DESTINATION "${SUNSHINE_ASSETS_DIR}/systemd/user")
else()
find_package(Systemd)
find_package(Udev)
Expand All @@ -29,6 +31,8 @@ else()
if(SYSTEMD_FOUND)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service"
DESTINATION "${SYSTEMD_USER_UNIT_INSTALL_DIR}")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine-kms.service"
DESTINATION "${SYSTEMD_USER_UNIT_INSTALL_DIR}")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/60-sunshine.conf"
DESTINATION "${SYSTEMD_MODULES_LOAD_DIR}")
endif()
Expand Down Expand Up @@ -105,10 +109,11 @@ list(APPEND CPACK_FREEBSD_PACKAGE_DEPS
audio/opus
ftp/curl
devel/libevdev
multimedia/pipewire
net/avahi
x11/libX11
net/miniupnpc
security/openssl
x11/libX11
)

if(NOT BOOST_USE_STATIC)
Expand Down
2 changes: 2 additions & 0 deletions cmake/prep/options.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,6 @@ elseif(UNIX) # Linux
"Enable building wayland specific code." ON)
option(SUNSHINE_ENABLE_X11
"Enable X11 grab if available." ON)
option(SUNSHINE_ENABLE_PORTAL
"Enable XDG portal grab if available" ON)
endif()
1 change: 1 addition & 0 deletions cmake/prep/special_package_configuration.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ elseif(UNIX)

# configure service
configure_file(packaging/linux/sunshine.service.in sunshine.service @ONLY)
configure_file(packaging/linux/sunshine-kms.service.in sunshine-kms.service @ONLY)

# configure the arch linux pkgbuild
if(${SUNSHINE_CONFIGURE_PKGBUILD})
Expand Down
23 changes: 3 additions & 20 deletions docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,28 +419,11 @@ After adding yourself to the group, log out and log back in for the changes to t

### Linux

#### KMS Capture

> [!WARNING]
> Capture of most Wayland-based desktop environments will fail unless this step is performed.
#### Services

> [!NOTE]
> `cap_sys_admin` may as well be root, except you don't need to be root to run the program. This is necessary to
> allow Sunshine to use KMS capture.

##### Enable
```bash
sudo setcap cap_sys_admin+p $(readlink -f $(which sunshine))
```

#### X11 Capture
For X11 capture to work, you may need to disable the capabilities that were set for KMS capture.

```bash
sudo setcap -r $(readlink -f $(which sunshine))
```

#### Service
> Two service unit files are available. Pick "sunshine" for unprivileged XDG Portal or X11 capture, otherwise
> pick "sunshine-kms" for privileged KMS capture.

**Start once**
```bash
Expand Down
6 changes: 4 additions & 2 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,12 @@ sudo usermod -aG input $USER
```

### KMS Streaming fails
If screencasting fails with KMS, you may need to run the following to force unprivileged screencasting.
If screencasting fails with KMS, you may be using the unprivileged sunshine service unit. Switch to the privileged
sunshine-kms service:

```bash
sudo setcap -r $(readlink -f $(which sunshine))
systemctl --user disable sunshine
systemctl --user enable sunshine-kms --now
```

> [!NOTE]
Expand Down
2 changes: 2 additions & 0 deletions packaging/linux/AppImage/AppRun
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ function install() {
cp -r "$SUNSHINE_SHARE_HERE/systemd/user/" ~/.config/systemd/
# patch service executable path
sed -i -e "s#$SUNSHINE_PATH#$(readlink -f $ARGV0)#g" ~/.config/systemd/user/sunshine.service
sed -i -e "s#$SUNSHINE_PATH#$(readlink -f $ARGV0)#g" ~/.config/systemd/user/sunshine-kms.service

# setcap
sudo setcap cap_sys_admin+p "$(readlink -f "$SUNSHINE_BIN_HERE")"
Expand All @@ -72,6 +73,7 @@ function remove() {

# remove service
sudo rm -f ~/.config/systemd/user/sunshine.service
sudo rm -f ~/.config/systemd/user/sunshine-kms.service
}

# process arguments
Expand Down
1 change: 1 addition & 0 deletions packaging/linux/Arch/PKGBUILD
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ depends=(
'libevdev'
'libmfx'
'libnotify'
'libpipewire'
'libpulse'
'libva'
'libx11'
Expand Down
7 changes: 5 additions & 2 deletions packaging/linux/copr/Sunshine.spec
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ BuildRequires: libXrandr-devel
BuildRequires: libXtst-devel
BuildRequires: npm
BuildRequires: openssl-devel
BuildRequires: pipewire-devel
BuildRequires: rpm-build
BuildRequires: systemd-rpm-macros
BuildRequires: wget
Expand Down Expand Up @@ -187,9 +188,10 @@ cmake_args=(
"-DCMAKE_INSTALL_PREFIX=%{_prefix}"
"-DSUNSHINE_ASSETS_DIR=%{_datadir}/sunshine"
"-DSUNSHINE_EXECUTABLE_PATH=%{_bindir}/sunshine"
"-DSUNSHINE_ENABLE_DRM=ON"
"-DSUNSHINE_ENABLE_PORTAL=ON"
"-DSUNSHINE_ENABLE_WAYLAND=ON"
"-DSUNSHINE_ENABLE_X11=ON"
"-DSUNSHINE_ENABLE_DRM=ON"
"-DSUNSHINE_PUBLISHER_NAME=LizardByte"
"-DSUNSHINE_PUBLISHER_WEBSITE=https://app.lizardbyte.dev"
"-DSUNSHINE_PUBLISHER_ISSUE_URL=https://app.lizardbyte.dev/support"
Expand Down Expand Up @@ -311,8 +313,9 @@ fi
%caps(cap_sys_admin+p) %{_bindir}/sunshine
%caps(cap_sys_admin+p) %{_bindir}/sunshine-*

# Systemd unit file for user services
# Systemd unit files for user services
%{_userunitdir}/sunshine.service
%{_userunitdir}/sunshine-kms.service

# Udev rules
%{_udevrulesdir}/*-sunshine.rules
Expand Down
5 changes: 3 additions & 2 deletions packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,11 @@ modules:
- -DSUNSHINE_ASSETS_DIR=share/sunshine
- -DSUNSHINE_BUILD_FLATPAK=ON
- -DSUNSHINE_EXECUTABLE_PATH=/app/bin/sunshine
- -DSUNSHINE_ENABLE_CUDA=ON
- -DSUNSHINE_ENABLE_DRM=ON
- -DSUNSHINE_ENABLE_PORTAL=ON
- -DSUNSHINE_ENABLE_WAYLAND=ON
- -DSUNSHINE_ENABLE_X11=ON
- -DSUNSHINE_ENABLE_DRM=ON
- -DSUNSHINE_ENABLE_CUDA=ON
- -DSUNSHINE_PUBLISHER_NAME='LizardByte'
- -DSUNSHINE_PUBLISHER_WEBSITE='https://app.lizardbyte.dev'
- -DSUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support'
Expand Down
5 changes: 3 additions & 2 deletions packaging/linux/flatpak/scripts/additional-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
# 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-kms.service" "$HOME/.config/systemd/user/sunshine-kms.service"
echo "Sunshine User Services have been installed."
echo "Use [systemctl --user enable sunshine] or [systemctl --user enable sunshine-kms] once to autostart Sunshine on login."

# Load uhid (DS5 emulation)
UHID=$(cat /app/share/sunshine/modules-load.d/60-sunshine.conf)
Expand Down
3 changes: 2 additions & 1 deletion packaging/linux/flatpak/scripts/remove-additional-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
# User Service
systemctl --user stop sunshine
rm "$HOME/.config/systemd/user/sunshine.service"
rm "$HOME/.config/systemd/user/sunshine-kms.service"
systemctl --user daemon-reload
echo "Sunshine User Service has been removed."
echo "Sunshine User Services have been removed."

# Remove rules
flatpak-spawn --host pkexec sh -c "rm /etc/modules-load.d/60-sunshine.conf"
Expand Down
16 changes: 16 additions & 0 deletions packaging/linux/sunshine-kms.service.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[Unit]
Description=@PROJECT_DESCRIPTION@
StartLimitIntervalSec=500
StartLimitBurst=5
Conflicts=sunshine.service

[Service]
# Avoid starting Sunshine before the desktop is fully initialized.
ExecStartPre=/bin/sleep 5
@SUNSHINE_SERVICE_START_COMMAND@
@SUNSHINE_SERVICE_STOP_COMMAND@
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=xdg-desktop-autostart.target
2 changes: 2 additions & 0 deletions packaging/linux/sunshine.service.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Description=@PROJECT_DESCRIPTION@
StartLimitIntervalSec=500
StartLimitBurst=5
Conflicts=sunshine-kms.service

[Service]
# Avoid starting Sunshine before the desktop is fully initialized.
Expand All @@ -10,6 +11,7 @@ ExecStartPre=/bin/sleep 5
@SUNSHINE_SERVICE_STOP_COMMAND@
Restart=on-failure
RestartSec=5s
NoNewPrivileges=true

[Install]
WantedBy=xdg-desktop-autostart.target
1 change: 1 addition & 0 deletions packaging/sunshine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class Sunshine < Formula
depends_on "mesa"
depends_on "numactl"
depends_on "pango"
depends_on "pipewire"
depends_on "pulseaudio"
depends_on "systemd"
depends_on "wayland"
Expand Down
5 changes: 4 additions & 1 deletion scripts/linux_build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ function add_debian_based_deps() {
"libnotify-dev"
"libnuma-dev"
"libopus-dev"
"libpipewire-0.3-dev"
"libpulse-dev"
"libssl-dev"
"libsystemd-dev"
Expand Down Expand Up @@ -322,6 +323,7 @@ function add_fedora_deps() {
"numactl-devel"
"openssl-devel"
"opus-devel"
"pipewire-devel"
"pulseaudio-libs-devel"
"rpm-build" # if you want to build an RPM binary package
"wget" # necessary for cuda install with `run` file
Expand Down Expand Up @@ -545,9 +547,10 @@ function run_step_cmake() {
"-DCMAKE_INSTALL_PREFIX=/usr"
"-DSUNSHINE_ASSETS_DIR=share/sunshine"
"-DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine"
"-DSUNSHINE_ENABLE_DRM=ON"
"-DSUNSHINE_ENABLE_PORTAL=ON"
"-DSUNSHINE_ENABLE_WAYLAND=ON"
"-DSUNSHINE_ENABLE_X11=ON"
"-DSUNSHINE_ENABLE_DRM=ON"
)

if [[ "$appimage_build" == 1 ]]; then
Expand Down
52 changes: 36 additions & 16 deletions src/platform/linux/misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,9 @@ namespace platf {
#endif
#ifdef SUNSHINE_BUILD_X11
X11, ///< X11
#endif
#ifdef SUNSHINE_BUILD_PORTAL
PORTAL, ///< XDG PORTAL
#endif
MAX_FLAGS ///< The maximum number of flags
};
Expand Down Expand Up @@ -931,6 +934,15 @@ namespace platf {
}
#endif

#ifdef SUNSHINE_BUILD_PORTAL
std::vector<std::string> portal_display_names();
std::shared_ptr<display_t> portal_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);

bool verify_portal() {
return !portal_display_names().empty();
}
#endif

std::vector<std::string> display_names(mem_type_e hwdevice_type) {
#ifdef SUNSHINE_BUILD_CUDA
// display using NvFBC only supports mem_type_e::cuda
Expand All @@ -952,6 +964,11 @@ namespace platf {
if (sources[source::X11]) {
return x11_display_names();
}
#endif
#ifdef SUNSHINE_BUILD_PORTAL
if (sources[source::PORTAL]) {
return portal_display_names();
}
#endif
return {};
}
Expand Down Expand Up @@ -990,6 +1007,12 @@ namespace platf {
return x11_display(hwdevice_type, display_name, config);
}
#endif
#ifdef SUNSHINE_BUILD_PORTAL
if (sources[source::PORTAL]) {
BOOST_LOG(info) << "Screencasting with XDG portal"sv;
return portal_display(hwdevice_type, display_name, config);
}
#endif

return nullptr;
}
Expand Down Expand Up @@ -1019,33 +1042,30 @@ namespace platf {
#endif

#ifdef SUNSHINE_BUILD_CUDA
if ((config::video.capture.empty() && sources.none()) || config::video.capture == "nvfbc") {
if (verify_nvfbc()) {
sources[source::NVFBC] = true;
}
if (((config::video.capture.empty() && sources.none()) || config::video.capture == "nvfbc") && verify_nvfbc()) {
sources[source::NVFBC] = true;
}
#endif
#ifdef SUNSHINE_BUILD_WAYLAND
if ((config::video.capture.empty() && sources.none()) || config::video.capture == "wlr") {
if (verify_wl()) {
sources[source::WAYLAND] = true;
}
if (((config::video.capture.empty() && sources.none()) || config::video.capture == "wlr") && verify_wl()) {
sources[source::WAYLAND] = true;
}
#endif
#ifdef SUNSHINE_BUILD_DRM
if ((config::video.capture.empty() && sources.none()) || config::video.capture == "kms") {
if (verify_kms()) {
sources[source::KMS] = true;
}
if (((config::video.capture.empty() && sources.none()) || config::video.capture == "kms") && verify_kms()) {
sources[source::KMS] = true;
}
#endif
#ifdef SUNSHINE_BUILD_X11
// We enumerate this capture backend regardless of other suitable sources,
// since it may be needed as a NvFBC fallback for software encoding on X11.
if (config::video.capture.empty() || config::video.capture == "x11") {
if (verify_x11()) {
sources[source::X11] = true;
}
if ((config::video.capture.empty() || config::video.capture == "x11") && verify_x11()) {
sources[source::X11] = true;
}
#endif
#ifdef SUNSHINE_BUILD_PORTAL
if ((config::video.capture.empty() || config::video.capture == "portal") && verify_portal()) {
sources[source::PORTAL] = true;
}
#endif

Expand Down
Loading
Loading