diff --git a/cmake/dependencies/glad.cmake b/cmake/dependencies/glad.cmake index bf8f4b1e..99f9e52b 100644 --- a/cmake/dependencies/glad.cmake +++ b/cmake/dependencies/glad.cmake @@ -179,6 +179,7 @@ glad_add_library(glad_egl EGL_EXT_platform_base EGL_EXT_platform_wayland EGL_EXT_platform_x11 + EGL_IMG_context_priority EGL_KHR_create_context EGL_KHR_image_base EGL_KHR_surfaceless_context diff --git a/cmake/packaging/linux.cmake b/cmake/packaging/linux.cmake index ad2b8a4a..1d5e5141 100644 --- a/cmake/packaging/linux.cmake +++ b/cmake/packaging/linux.cmake @@ -64,7 +64,7 @@ endif() # 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,cap_sys_nice+p) ${SUNSHINE_EXECUTABLE_PATH}") # Dependencies set(CPACK_DEB_COMPONENT_INSTALL ON) diff --git a/docs/building.md b/docs/building.md index 742d14fd..91c0dd3d 100644 --- a/docs/building.md +++ b/docs/building.md @@ -59,7 +59,7 @@ from source and using the binary directly, this will also work: ```bash sudo cp build/sunshine /tmp -sudo setcap cap_sys_admin+p /tmp/sunshine +sudo setcap cap_sys_admin,cap_sys_nice+p /tmp/sunshine sudo getcap /tmp/sunshine sudo mv /tmp/sunshine build/sunshine ``` diff --git a/packaging/linux/AppImage/AppRun b/packaging/linux/AppImage/AppRun index da2ff631..f2dc7802 100644 --- a/packaging/linux/AppImage/AppRun +++ b/packaging/linux/AppImage/AppRun @@ -60,7 +60,7 @@ function install() { sed -i -e "s#$SUNSHINE_PATH#$(readlink -f $ARGV0)#g" ~/.config/systemd/user/app-dev.lizardbyte.app.Sunshine.service # setcap - sudo setcap cap_sys_admin+p "$(readlink -f "$SUNSHINE_BIN_HERE")" + sudo setcap cap_sys_admin,cap_sys_nice+p "$(readlink -f "$SUNSHINE_BIN_HERE")" } function remove() { diff --git a/packaging/linux/Arch/sunshine.install b/packaging/linux/Arch/sunshine.install index d7b87737..9f9fb36c 100644 --- a/packaging/linux/Arch/sunshine.install +++ b/packaging/linux/Arch/sunshine.install @@ -1,5 +1,5 @@ do_setcap() { - setcap cap_sys_admin+p $(readlink -f usr/bin/sunshine) + setcap cap_sys_admin,cap_sys_nice+p $(readlink -f usr/bin/sunshine) } do_udev_reload() { diff --git a/packaging/linux/copr/Sunshine.spec b/packaging/linux/copr/Sunshine.spec index ece778aa..5b42be37 100644 --- a/packaging/linux/copr/Sunshine.spec +++ b/packaging/linux/copr/Sunshine.spec @@ -387,8 +387,8 @@ fi %files # Executables -%caps(cap_sys_admin+p) %{_bindir}/sunshine -%caps(cap_sys_admin+p) %{_bindir}/sunshine-* +%caps(cap_sys_admin,cap_sys_nice+p) %{_bindir}/sunshine +%caps(cap_sys_admin,cap_sys_nice+p) %{_bindir}/sunshine-* # Systemd unit files for user services %{_userunitdir}/*.service diff --git a/src/platform/linux/graphics.cpp b/src/platform/linux/graphics.cpp index 38eb0071..6cc2eb3b 100644 --- a/src/platform/linux/graphics.cpp +++ b/src/platform/linux/graphics.cpp @@ -11,6 +11,11 @@ #include "src/logging.h" #include "src/video.h" +// platform includes +#if !defined(__FreeBSD__) + #include +#endif + extern "C" { #include } @@ -387,6 +392,18 @@ namespace egl { } std::optional make_ctx(display_t::pointer display) { + bool nice_warning = false; +#if !defined(__FreeBSD__) + cap_t caps = cap_get_proc(); + + cap_value_t sys_nice = CAP_SYS_NICE; + if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &sys_nice, CAP_SET) || cap_set_proc(caps)) { + BOOST_LOG(debug) << "Failed to gain CAP_SYS_NICE"sv; + nice_warning = true; + } + cap_free(caps); +#endif + constexpr int conf_attr[] { EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, @@ -405,20 +422,42 @@ namespace egl { return std::nullopt; } - constexpr int attr[] { - EGL_CONTEXT_CLIENT_VERSION, - 3, - EGL_NONE - }; + const char *extension_st = eglQueryString(display, EGL_EXTENSIONS); - ctx_t ctx {display, eglCreateContext(display, conf, EGL_NO_CONTEXT, attr)}; - if (fail()) { + std::vector attr; + attr.push_back(EGL_CONTEXT_CLIENT_VERSION); + attr.push_back(3); + + // Only add the high priority attribute if the driver explicitly supports it + if (extension_st && std::string_view(extension_st).contains("EGL_IMG_context_priority"sv)) { + BOOST_LOG(debug) << "EGL: High priority context supported"sv; + attr.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG); + attr.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG); + } + attr.push_back(EGL_NONE); + + EGLContext raw_ctx = eglCreateContext(display, conf, EGL_NO_CONTEXT, attr.data()); + if (raw_ctx == EGL_NO_CONTEXT) { BOOST_LOG(error) << "Couldn't create EGL context: ["sv << util::hex(eglGetError()).to_string_view() << ']'; return std::nullopt; } - TUPLE_EL_REF(ctx_p, 1, ctx.el); - if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx_p)) { + ctx_t ctx {display, raw_ctx}; + + EGLint actual_priority = EGL_CONTEXT_PRIORITY_MEDIUM_IMG; + std::string actual_priority_str = "MEDIUM"; + if (eglQueryContext(display, raw_ctx, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &actual_priority)) { + if (actual_priority == EGL_CONTEXT_PRIORITY_HIGH_IMG) { + actual_priority_str = "HIGH"; + } + if (nice_warning) { + BOOST_LOG(warning) << "EGL: context priority set to "sv << actual_priority_str << " but CAP_SYS_NICE capability is missing"sv; + } else { + BOOST_LOG(info) << "EGL: context priority set to "sv << actual_priority_str; + } + } + + if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, raw_ctx)) { BOOST_LOG(error) << "Couldn't make current display"sv; return std::nullopt; } @@ -453,6 +492,14 @@ namespace egl { gl::ctx.PixelStorei(GL_UNPACK_ALIGNMENT, 1); +#if !defined(__FreeBSD__) + caps = cap_get_proc(); + if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &sys_nice, CAP_CLEAR) || cap_set_proc(caps)) { + BOOST_LOG(debug) << "Failed to drop CAP_SYS_NICE"sv; + } + cap_free(caps); +#endif + return ctx; } diff --git a/src/platform/linux/portalgrab.cpp b/src/platform/linux/portalgrab.cpp index 6a991114..32ee0741 100644 --- a/src/platform/linux/portalgrab.cpp +++ b/src/platform/linux/portalgrab.cpp @@ -236,7 +236,7 @@ namespace portal { void finalize_portal_security() { #if !defined(__FreeBSD__) - BOOST_LOG(debug) << "Finalizing Portal security: dropping CAP_SYS_ADMIN and resetting dumpable"sv; + BOOST_LOG(debug) << "Finalizing Portal security: dropping capabilities and resetting dumpable"sv; cap_t caps = cap_get_proc(); if (!caps) { @@ -244,10 +244,11 @@ namespace portal { return; } - std::array remove_list {CAP_SYS_ADMIN}; + std::array effective_list {CAP_SYS_ADMIN, CAP_SYS_NICE}; + std::array permitted_list {CAP_SYS_ADMIN, CAP_SYS_NICE}; - cap_set_flag(caps, CAP_PERMITTED, remove_list.size(), remove_list.data(), CAP_CLEAR); - cap_set_flag(caps, CAP_EFFECTIVE, remove_list.size(), remove_list.data(), CAP_CLEAR); + cap_set_flag(caps, CAP_EFFECTIVE, effective_list.size(), effective_list.data(), CAP_CLEAR); + cap_set_flag(caps, CAP_PERMITTED, permitted_list.size(), permitted_list.data(), CAP_CLEAR); if (cap_set_proc(caps) != 0) { BOOST_LOG(error) << "Failed to prune capabilities: "sv << std::strerror(errno); diff --git a/src_assets/linux/misc/postinst b/src_assets/linux/misc/postinst index 4783c2ec..bc1e555d 100644 --- a/src_assets/linux/misc/postinst +++ b/src_assets/linux/misc/postinst @@ -12,10 +12,10 @@ if [ ! -x "$(command -v rpm-ostree)" ]; then path_to_setcap=$(which setcap) path_to_sunshine=$(readlink -f "$(which sunshine)") if [ -x "$path_to_setcap" ] ; then - echo "Setting CAP_SYS_ADMIN capability on Sunshine binary." - echo "$path_to_setcap cap_sys_admin+p $path_to_sunshine" - $path_to_setcap cap_sys_admin+p $path_to_sunshine - echo "CAP_SYS_ADMIN capability set on Sunshine binary." + echo "Setting CAP_SYS_ADMIN, CAP_SYS_NICE capabilities on Sunshine binary." + echo "$path_to_setcap cap_sys_admin,cap_sys_nice+p $path_to_sunshine" + $path_to_setcap cap_sys_admin,cap_sys_nice+p $path_to_sunshine + echo "CAP_SYS_ADMIN, CAP_SYS_NICE capabilities set on Sunshine binary." else echo "error: setcap not found or not executable." fi