#include "SDL_internal.h"
#ifdef SDL_PLATFORM_LINUX
#ifndef SDL_THREADS_DISABLED
#include <sys/time.h>
#include <sys/resource.h>
#include <pthread.h>
#include <sched.h>
#include <unistd.h>
#ifndef RLIMIT_RTTIME
#define RLIMIT_RTTIME 15
#endif
#ifndef SCHED_RESET_ON_FORK
#define SCHED_RESET_ON_FORK 0x40000000
#endif
#include "SDL_dbus.h"
#ifdef SDL_USE_LIBDBUS
#define RTKIT_DBUS_NODE "org.freedesktop.RealtimeKit1"
#define RTKIT_DBUS_PATH "/org/freedesktop/RealtimeKit1"
#define RTKIT_DBUS_INTERFACE "org.freedesktop.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 bool rtkit_use_session_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;
static bool realtime_portal_supported(DBusConnection *conn)
{
Sint64 res;
return SDL_DBus_QueryPropertyOnConnection(conn, NULL, XDG_PORTAL_DBUS_NODE, XDG_PORTAL_DBUS_PATH, XDG_PORTAL_DBUS_INTERFACE,
"RTTimeUSecMax", DBUS_TYPE_INT64, &res);
}
static void set_rtkit_interface(void)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
if (dbus && realtime_portal_supported(dbus->session_conn)) {
rtkit_use_session_conn = true;
rtkit_dbus_node = XDG_PORTAL_DBUS_NODE;
rtkit_dbus_path = XDG_PORTAL_DBUS_PATH;
rtkit_dbus_interface = XDG_PORTAL_DBUS_INTERFACE;
} else { rtkit_use_session_conn = false;
rtkit_dbus_node = RTKIT_DBUS_NODE;
rtkit_dbus_path = RTKIT_DBUS_PATH;
rtkit_dbus_interface = RTKIT_DBUS_INTERFACE;
}
}
static DBusConnection *get_rtkit_dbus_connection(void)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
if (dbus) {
return rtkit_use_session_conn ? dbus->session_conn : dbus->system_conn;
}
return NULL;
}
static void rtkit_initialize(void)
{
DBusConnection *dbus_conn;
set_rtkit_interface();
dbus_conn = get_rtkit_dbus_connection();
if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, NULL, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MinNiceLevel",
DBUS_TYPE_INT32, &rtkit_min_nice_level)) {
rtkit_min_nice_level = -20;
}
if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, NULL, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MaxRealtimePriority",
DBUS_TYPE_INT32, &rtkit_max_realtime_priority)) {
rtkit_max_realtime_priority = 99;
}
if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, NULL, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "RTTimeUSecMax",
DBUS_TYPE_INT64, &rtkit_max_rttime_usec)) {
rtkit_max_rttime_usec = 200000;
}
}
static bool rtkit_initialize_realtime_thread(void)
{
int err;
struct rlimit rlimit;
int nLimit = RLIMIT_RTTIME;
pid_t nPid = 0; int nSchedPolicy = sched_getscheduler(nPid) | SCHED_RESET_ON_FORK;
struct sched_param schedParam;
SDL_zero(schedParam);
err = getrlimit(nLimit, &rlimit);
if (err) {
return false;
}
rlimit.rlim_max = rtkit_max_rttime_usec;
rlimit.rlim_cur = rlimit.rlim_max / 2;
err = setrlimit(nLimit, &rlimit);
if (err) {
return false;
}
err = sched_getparam(nPid, &schedParam);
if (err) {
return false;
}
err = sched_setscheduler(nPid, nSchedPolicy, &schedParam);
if (err) {
return false;
}
return true;
}
static bool rtkit_setpriority_nice(pid_t thread, int nice_level)
{
DBusConnection *dbus_conn;
Uint64 pid = (Uint64)getpid();
Uint64 tid = (Uint64)thread;
Sint32 nice = (Sint32)nice_level;
pthread_once(&rtkit_initialize_once, rtkit_initialize);
dbus_conn = get_rtkit_dbus_connection();
if (nice < rtkit_min_nice_level) {
nice = rtkit_min_nice_level;
}
if (!dbus_conn || !SDL_DBus_CallMethodOnConnection(dbus_conn, NULL,
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 false;
}
return true;
}
static bool rtkit_setpriority_realtime(pid_t thread, int rt_priority)
{
DBusConnection *dbus_conn;
Uint64 pid = (Uint64)getpid();
Uint64 tid = (Uint64)thread;
Uint32 priority = (Uint32)rt_priority;
pthread_once(&rtkit_initialize_once, rtkit_initialize);
dbus_conn = get_rtkit_dbus_connection();
if (priority > rtkit_max_realtime_priority) {
priority = rtkit_max_realtime_priority;
}
rtkit_initialize_realtime_thread();
if (!dbus_conn || !SDL_DBus_CallMethodOnConnection(dbus_conn, NULL,
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 false;
}
return true;
}
#else
#define rtkit_max_realtime_priority 99
#endif #endif
bool SDL_SetLinuxThreadPriority(Sint64 threadID, int priority)
{
#ifdef SDL_THREADS_DISABLED
return SDL_Unsupported();
#else
if (setpriority(PRIO_PROCESS, (id_t)threadID, priority) == 0) {
return true;
}
#ifdef SDL_USE_LIBDBUS
if (rtkit_setpriority_nice((pid_t)threadID, priority)) {
return true;
}
#endif
return SDL_SetError("setpriority() failed");
#endif
}
bool SDL_SetLinuxThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy)
{
#ifdef SDL_THREADS_DISABLED
return SDL_Unsupported();
#else
int osPriority;
if (schedPolicy == SCHED_RR || schedPolicy == SCHED_FIFO) {
if (sdlPriority == SDL_THREAD_PRIORITY_LOW) {
osPriority = 1;
} else if (sdlPriority == SDL_THREAD_PRIORITY_HIGH) {
osPriority = rtkit_max_realtime_priority * 3 / 4;
} else if (sdlPriority == SDL_THREAD_PRIORITY_TIME_CRITICAL) {
osPriority = rtkit_max_realtime_priority;
} else {
osPriority = rtkit_max_realtime_priority / 2;
}
} else {
if (sdlPriority == SDL_THREAD_PRIORITY_LOW) {
osPriority = 19;
} else if (sdlPriority == SDL_THREAD_PRIORITY_HIGH) {
osPriority = -10;
} else if (sdlPriority == SDL_THREAD_PRIORITY_TIME_CRITICAL) {
osPriority = -20;
} else {
osPriority = 0;
}
if (setpriority(PRIO_PROCESS, (id_t)threadID, osPriority) == 0) {
return true;
}
}
#ifdef SDL_USE_LIBDBUS
if (schedPolicy == SCHED_RR || schedPolicy == SCHED_FIFO) {
if (rtkit_setpriority_realtime((pid_t)threadID, osPriority)) {
return true;
}
} else {
if (rtkit_setpriority_nice((pid_t)threadID, osPriority)) {
return true;
}
}
#endif
return SDL_SetError("setpriority() failed");
#endif
}
#endif