SDL: core: linux: Favor xdg-desktop-portal for elevating thread priority

From 22f25b03d912db59c8d45992c37eb6365a9959aa Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Sun, 24 Jul 2022 13:22:39 -0400
Subject: [PATCH] core: linux: Favor xdg-desktop-portal for elevating thread
 priority

Use the xdg-desktop-portal interface to RealtimeKit1, when available, to set realtime scheduling and elevated priority for threads.  This portal allows for the use of rtkit within containers such as Flatpak.  It will fall back to using RealtimeKit1 directly if the xdg-desktop-portal interface is too old or not available.
---
 src/core/linux/SDL_threadprio.c | 83 +++++++++++++++++++++++++--------
 1 file changed, 63 insertions(+), 20 deletions(-)

diff --git a/src/core/linux/SDL_threadprio.c b/src/core/linux/SDL_threadprio.c
index 9009d5dfcf9..73abbc8f320 100644
--- a/src/core/linux/SDL_threadprio.c
+++ b/src/core/linux/SDL_threadprio.c
@@ -30,6 +30,7 @@
 #include <sys/time.h>
 #include <sys/resource.h>
 #include <pthread.h>
+#include <unistd.h>
 #include "SDL_system.h"
 
 /* RLIMIT_RTTIME requires kernel >= 2.6.25 and is in glibc >= 2.14 */
@@ -51,30 +52,72 @@
 #define RTKIT_DBUS_PATH "/org/freedesktop/RealtimeKit1"
 #define RTKIT_DBUS_INTERFACE "org.freedesktop.RealtimeKit1"
 
+/* d-bus queries to the XDG portal interface to RealtimeKit1 */
+#define XDG_PORTAL_DBUS_NODE "org.freedesktop.portal.Desktop"
+#define XDG_PORTAL_DBUS_PATH "/org/freedesktop/portal/desktop"
+#define XDG_PORTAL_DBUS_INTERFACE "org.freedesktop.portal.Realtime"
+
+static DBusConnection *rtkit_dbus_conn;
+static const char *rtkit_dbus_node;
+static const char *rtkit_dbus_path;
+static const char *rtkit_dbus_interface;
+
 static pthread_once_t rtkit_initialize_once = PTHREAD_ONCE_INIT;
 static Sint32 rtkit_min_nice_level = -20;
 static Sint32 rtkit_max_realtime_priority = 99;
 static Sint64 rtkit_max_rttime_usec = 200000;
 
+/*
+ * Checking that the RTTimeUSecMax property exists and is an int64 confirms that:
+ *  - The desktop portal exists and supports the realtime interface.
+ *  - The realtime interface is new enough to have the required bug fixes applied.
+ */
+static SDL_bool
+realtime_portal_supported(DBusConnection *conn)
+{
+    Sint64 res;
+    return SDL_DBus_QueryPropertyOnConnection(conn, XDG_PORTAL_DBUS_NODE, XDG_PORTAL_DBUS_PATH, XDG_PORTAL_DBUS_INTERFACE,
+                                              "RTTimeUSecMax", DBUS_TYPE_INT64, &res);
+}
+
 static void
-rtkit_initialize()
+set_rtkit_interface()
 {
     SDL_DBusContext *dbus = SDL_DBus_GetContext();
 
+    /* xdg-desktop-portal works in all instances, so check for it first. */
+    if (dbus && realtime_portal_supported(dbus->session_conn)) {
+        rtkit_dbus_conn = dbus->session_conn;
+        rtkit_dbus_node = XDG_PORTAL_DBUS_NODE;
+        rtkit_dbus_path = XDG_PORTAL_DBUS_PATH;
+        rtkit_dbus_interface = XDG_PORTAL_DBUS_INTERFACE;
+    } else { /* Fall back to the standard rtkit interface in all other cases. */
+        rtkit_dbus_conn = dbus ? dbus->system_conn : NULL;
+        rtkit_dbus_node = RTKIT_DBUS_NODE;
+        rtkit_dbus_path = RTKIT_DBUS_PATH;
+        rtkit_dbus_interface = RTKIT_DBUS_INTERFACE;
+    }
+}
+
+static void
+rtkit_initialize()
+{
+    set_rtkit_interface();
+
     /* Try getting minimum nice level: this is often greater than PRIO_MIN (-20). */
-    if (!dbus || !SDL_DBus_QueryPropertyOnConnection(dbus->system_conn, RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "MinNiceLevel",
+    if (!rtkit_dbus_conn || !SDL_DBus_QueryPropertyOnConnection(rtkit_dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MinNiceLevel",
                                             DBUS_TYPE_INT32, &rtkit_min_nice_level)) {
         rtkit_min_nice_level = -20;
     }
 
     /* Try getting maximum realtime priority: this can be less than the POSIX default (99). */
-    if (!dbus || !SDL_DBus_QueryPropertyOnConnection(dbus->system_conn, RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "MaxRealtimePriority",
+    if (!rtkit_dbus_conn || !SDL_DBus_QueryPropertyOnConnection(rtkit_dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MaxRealtimePriority",
                                             DBUS_TYPE_INT32, &rtkit_max_realtime_priority)) {
         rtkit_max_realtime_priority = 99;
     }
 
     /* Try getting maximum rttime allowed by rtkit: exceeding this value will result in SIGKILL */
-    if (!dbus || !SDL_DBus_QueryPropertyOnConnection(dbus->system_conn, RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "RTTimeUSecMax",
+    if (!rtkit_dbus_conn || !SDL_DBus_QueryPropertyOnConnection(rtkit_dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "RTTimeUSecMax",
                                             DBUS_TYPE_INT64, &rtkit_max_rttime_usec)) {
         rtkit_max_rttime_usec = 200000;
     }
@@ -147,18 +190,18 @@ rtkit_initialize_realtime_thread()
 static SDL_bool
 rtkit_setpriority_nice(pid_t thread, int nice_level)
 {
-    Uint64 ui64 = (Uint64)thread;
-    Sint32 si32 = (Sint32)nice_level;
-    SDL_DBusContext *dbus = SDL_DBus_GetContext();
+    Uint64 pid = (Uint64)getpid();
+    Uint64 tid = (Uint64)thread;
+    Sint32 nice = (Sint32)nice_level;
 
     pthread_once(&rtkit_initialize_once, rtkit_initialize);
 
-    if (si32 < rtkit_min_nice_level)
-        si32 = rtkit_min_nice_level;
+    if (nice < rtkit_min_nice_level)
+        nice = rtkit_min_nice_level;
 
-    if (!dbus || !SDL_DBus_CallMethodOnConnection(dbus->system_conn,
-            RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "MakeThreadHighPriority",
-            DBUS_TYPE_UINT64, &ui64, DBUS_TYPE_INT32, &si32, DBUS_TYPE_INVALID,
+    if (!rtkit_dbus_conn || !SDL_DBus_CallMethodOnConnection(rtkit_dbus_conn,
+            rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MakeThreadHighPriorityWithPID",
+            DBUS_TYPE_UINT64, &pid, DBUS_TYPE_UINT64, &tid, DBUS_TYPE_INT32, &nice, DBUS_TYPE_INVALID,
             DBUS_TYPE_INVALID)) {
         return SDL_FALSE;
     }
@@ -168,14 +211,14 @@ rtkit_setpriority_nice(pid_t thread, int nice_level)
 static SDL_bool
 rtkit_setpriority_realtime(pid_t thread, int rt_priority)
 {
-    Uint64 ui64 = (Uint64)thread;
-    Uint32 ui32 = (Uint32)rt_priority;
-    SDL_DBusContext *dbus = SDL_DBus_GetContext();
+    Uint64 pid = (Uint64)getpid();
+    Uint64 tid = (Uint64)thread;
+    Uint32 priority = (Uint32)rt_priority;
 
     pthread_once(&rtkit_initialize_once, rtkit_initialize);
 
-    if (ui32 > rtkit_max_realtime_priority)
-        ui32 = rtkit_max_realtime_priority;
+    if (priority > rtkit_max_realtime_priority)
+        priority = rtkit_max_realtime_priority;
 
     // We always perform the thread state changes necessary for rtkit.
     // This wastes some system calls if the state is already set but
@@ -185,9 +228,9 @@ rtkit_setpriority_realtime(pid_t thread, int rt_priority)
     // go through to determine whether it really needs to fail or not.
     rtkit_initialize_realtime_thread();
 
-    if (!dbus || !SDL_DBus_CallMethodOnConnection(dbus->system_conn,
-            RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "MakeThreadRealtime",
-            DBUS_TYPE_UINT64, &ui64, DBUS_TYPE_UINT32, &ui32, DBUS_TYPE_INVALID,
+    if (!rtkit_dbus_conn || !SDL_DBus_CallMethodOnConnection(rtkit_dbus_conn,
+            rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MakeThreadRealtimeWithPID",
+            DBUS_TYPE_UINT64, &pid, DBUS_TYPE_UINT64, &tid, DBUS_TYPE_UINT32, &priority, DBUS_TYPE_INVALID,
             DBUS_TYPE_INVALID)) {
         return SDL_FALSE;
     }