Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 9 additions & 1 deletion cmake/compile_definitions/linux.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,16 @@ if(LIBDRM_FOUND AND LIBCAP_FOUND)
include_directories(SYSTEM ${LIBCAP_INCLUDE_DIRS})
list(APPEND PLATFORM_LIBRARIES ${LIBCAP_LIBRARIES})
list(APPEND PLATFORM_TARGET_FILES
"${CMAKE_SOURCE_DIR}/src/platform/linux/kmsgrab.cpp")
"${CMAKE_SOURCE_DIR}/src/platform/linux/kmsgrab.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/linux/virtual_display.h"
"${CMAKE_SOURCE_DIR}/src/platform/linux/virtual_display.cpp")
list(APPEND SUNSHINE_DEFINITIONS EGL_NO_X11=1)

# Privileged helper for virtual display debugfs writes (cap_dac_override)
add_executable(apollo-vdisplay-helper
"${CMAKE_SOURCE_DIR}/src/platform/linux/vdisplay_helper.cpp")
set_target_properties(apollo-vdisplay-helper PROPERTIES CXX_STANDARD 17)
install(TARGETS apollo-vdisplay-helper RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
endif()

# evdev
Expand Down
7 changes: 6 additions & 1 deletion cmake/packaging/linux.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ else()
DESTINATION "${SYSTEMD_USER_UNIT_INSTALL_DIR}")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/60-sunshine.conf"
DESTINATION "${SYSTEMD_MODULES_LOAD_DIR}")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/apollo-sysusers.conf"
DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/sysusers.d"
RENAME "apollo.conf")
endif()
endif()

Expand All @@ -40,7 +43,9 @@ set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc

# Apply setcap for RPM
# https://github.com/coreos/rpm-ostree/discussions/5036#discussioncomment-10291071
set(CPACK_RPM_USER_FILELIST "%caps(cap_sys_admin+p) ${SUNSHINE_EXECUTABLE_PATH}")
set(CPACK_RPM_USER_FILELIST
"%caps(cap_sys_admin+p) ${SUNSHINE_EXECUTABLE_PATH}"
"%attr(0750,root,apollo) %caps(cap_dac_override+ep) %{_bindir}/apollo-vdisplay-helper")

# Dependencies
set(CPACK_DEB_COMPONENT_INSTALL ON)
Expand Down
13 changes: 13 additions & 0 deletions packaging/linux/Arch/sunshine.install
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ do_setcap() {
setcap cap_sys_admin+p $(readlink -f usr/bin/sunshine)
}

do_vdisplay_helper() {
local helper=$(readlink -f usr/bin/apollo-vdisplay-helper 2>/dev/null)
if [ -x "$helper" ]; then
if getent group apollo >/dev/null 2>&1; then
chown root:apollo "$helper"
chmod 0750 "$helper"
fi
setcap cap_dac_override+ep "$helper"
fi
}

do_udev_reload() {
udevadm control --reload-rules
udevadm trigger --property-match=DEVNAME=/dev/uinput
Expand All @@ -12,12 +23,14 @@ do_udev_reload() {

post_install() {
do_setcap
do_vdisplay_helper
do_udev_reload
modprobe uhid
}

post_upgrade() {
do_setcap
do_vdisplay_helper
do_udev_reload
modprobe uhid
}
28 changes: 26 additions & 2 deletions packaging/linux/fedora/Sunshine.spec
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ cd %{_builddir}/Sunshine/build
%make_install

%post
# Note: this is copied from the postinst script
# Note: this should be kept in sync with src_assets/linux/misc/postinst

# Load uhid (DS5 emulation)
echo "Loading uhid kernel module for DS5 emulation."
Expand All @@ -222,6 +222,30 @@ modprobe uhid
if [ ! -x "$(command -v rpm-ostree)" ]; then
echo "Not in an rpm-ostree environment, proceeding with post install steps."

# Create apollo system group if it doesn't exist
if ! getent group apollo >/dev/null 2>&1; then
if [ -x "$(command -v groupadd)" ]; then
groupadd --system apollo
echo "Created 'apollo' system group."
fi
fi

# Add the installing user to the apollo group
if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then
target_user="$SUDO_USER"
elif [ -n "$PKEXEC_UID" ]; then
target_user=$(id -nu "$PKEXEC_UID" 2>/dev/null)
else
target_user=""
fi

if [ -n "$target_user" ]; then
if ! id -nG "$target_user" 2>/dev/null | grep -qw apollo; then
usermod -aG apollo "$target_user"
echo "Added '$target_user' to 'apollo' group. Log out and back in for group to take effect."
fi
fi

# Trigger udev rule reload for /dev/uinput and /dev/uhid
path_to_udevadm=$(which udevadm)
if [ -x "$path_to_udevadm" ]; then
Expand All @@ -240,7 +264,7 @@ fi
%files
# Executables
%caps(cap_sys_admin+p) %{_bindir}/sunshine
%caps(cap_sys_admin+p) %{_bindir}/sunshine-*
%attr(0750,root,apollo) %caps(cap_dac_override+ep) %{_bindir}/apollo-vdisplay-helper

# Systemd unit file for user services
%{_userunitdir}/sunshine.service
Expand Down
4 changes: 4 additions & 0 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,8 @@ namespace config {
"1920x1080x60", // fallback_mode
false, // isolated Display
false, // ignore_encoder_probe_failure
false, // linux_virtual_display_experimental
{}, // vdisplay_connector
};

audio_t audio {
Expand Down Expand Up @@ -1209,6 +1211,8 @@ namespace config {
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);
bool_f(vars, "linux_virtual_display_experimental", video.linux_virtual_display_experimental);
string_f(vars, "vdisplay_connector", video.vdisplay_connector);

path_f(vars, "pkey", nvhttp.pkey);
path_f(vars, "cert", nvhttp.cert);
Expand Down
2 changes: 2 additions & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ namespace config {
std::string fallback_mode;
bool isolated_virtual_display_option;
bool ignore_encoder_probe_failure;
bool linux_virtual_display_experimental;
std::string vdisplay_connector;
};

struct audio_t {
Expand Down
8 changes: 8 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
#ifdef _WIN32
#include "platform/windows/misc.h"
#include "platform/windows/virtual_display.h"
#elif defined(__linux__) && defined(SUNSHINE_BUILD_DRM)
#include "platform/linux/virtual_display.h"
#endif

#define PROBE_DISPLAY_UUID "38F72B96-B00C-4F21-8B6C-E1BFF1602B0E"
Expand Down Expand Up @@ -206,6 +208,12 @@ int main(int argc, char *argv[]) {
BOOST_LOG(error) << "Display device session failed to initialize"sv;
}

#if defined(__linux__) && defined(SUNSHINE_BUILD_DRM)
if (config::video.linux_virtual_display_experimental) {
linux_vdisplay::recover_crash_state();
}
#endif

#ifdef _WIN32
// Modify relevant NVIDIA control panel settings if the system has corresponding gpu
if (nvprefs_instance.load()) {
Expand Down
156 changes: 156 additions & 0 deletions src/platform/linux/vdisplay_helper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/**
* @file src/platform/linux/vdisplay_helper.cpp
* @brief Privileged helper for virtual display debugfs/sysfs writes.
*
* Installed with cap_dac_override+ep so the main apollo process
* does not need CAP_DAC_OVERRIDE in its capability set.
*/
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <regex.h>
#include <sys/stat.h>
#include <unistd.h>

static bool
validate_path(const char *path) {
if (strcmp(path, "/sys/kernel/debug/dri") == 0) {
return true;
}

static const char *patterns[] = {
"^/sys/kernel/debug/dri/[0-9]+/[A-Za-z]+(-[A-Za-z]+)*-[0-9]+/edid_override$",
"^/sys/kernel/debug/dri/[0-9]+/[A-Za-z]+(-[A-Za-z]+)*-[0-9]+/trigger_hotplug$",
"^/sys/class/drm/card[0-9]+-[A-Za-z]+(-[A-Za-z]+)*-[0-9]+/status$",
};

for (auto pattern : patterns) {
regex_t re;
if (regcomp(&re, pattern, REG_EXTENDED | REG_NOSUB) != 0) {
continue;
}
int match = regexec(&re, path, 0, nullptr, 0);
regfree(&re);
if (match == 0) {
return true;
}
}

return false;
}

static int
safe_open(const char *path) {
return open(path, O_WRONLY | O_NOFOLLOW);
}

static int
cmd_write_edid(const char *path) {
uint8_t buf[256];
ssize_t total = 0;
while (total < static_cast<ssize_t>(sizeof(buf))) {
ssize_t n = read(STDIN_FILENO, buf + total, sizeof(buf) - total);
if (n <= 0) {
break;
}
total += n;
}
if (total != 128 && total != 256) {
fprintf(stderr, "Invalid EDID size: %zd\n", total);
return 3;
}
int fd = safe_open(path);
if (fd < 0) {
perror("open");
return 4;
}
ssize_t written = write(fd, buf, total);
close(fd);
if (written != total) {
perror("write");
return 5;
}
return 0;
}

static int
cmd_clear_edid(const char *path) {
int fd = safe_open(path);
if (fd < 0) {
perror("open");
return 4;
}
ssize_t n = write(fd, "reset", 5);
if (n < 0) {
lseek(fd, 0, SEEK_SET);
write(fd, "", 0);
}
close(fd);
return 0;
}

static int
cmd_write_string(const char *path, const char *value) {
int fd = safe_open(path);
if (fd < 0) {
perror("open");
return 4;
}
ssize_t n = write(fd, value, strlen(value));
close(fd);
if (n < 0) {
perror("write");
return 5;
}
return 0;
}

int main(int argc, char *argv[]) {
if (argc < 3) {
fprintf(stderr, "Usage: %s <command> <path> [value]\n", argv[0]);
return 1;
}

const char *command = argv[1];
const char *path = argv[2];

if (!validate_path(path)) {
fprintf(stderr, "Path rejected: %s\n", path);
return 2;
}

if (strcmp(command, "write-edid") == 0) {
return cmd_write_edid(path);
}
if (strcmp(command, "clear-edid") == 0) {
return cmd_clear_edid(path);
}
if (strcmp(command, "write-string") == 0) {
if (argc < 4) {
fprintf(stderr, "write-string requires a value argument\n");
return 1;
}
const char *value = argv[3];
static const char *allowed_values[] = {"1", "on", "off", "detect"};
bool valid = false;
for (auto v : allowed_values) {
if (strcmp(value, v) == 0) {
valid = true;
break;
}
}
if (!valid) {
fprintf(stderr, "Value rejected: %s\n", value);
return 2;
}
return cmd_write_string(path, value);
}
if (strcmp(command, "check-path") == 0) {
struct stat st;
return stat(path, &st) == 0 ? 0 : 1;
}

fprintf(stderr, "Unknown command: %s\n", command);
return 1;
}
Loading