initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

647
thirdparty/sdl/core/linux/SDL_dbus.c vendored Normal file
View File

@@ -0,0 +1,647 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_dbus.h"
#include "../../stdlib/SDL_vacopy.h"
#ifdef SDL_USE_LIBDBUS
// we never link directly to libdbus.
static const char *dbus_library = "libdbus-1.so.3";
static SDL_SharedObject *dbus_handle = NULL;
static char *inhibit_handle = NULL;
static unsigned int screensaver_cookie = 0;
static SDL_DBusContext dbus;
static bool LoadDBUSSyms(void)
{
#define SDL_DBUS_SYM2_OPTIONAL(TYPE, x, y) \
dbus.x = (TYPE)SDL_LoadFunction(dbus_handle, #y)
#define SDL_DBUS_SYM2(TYPE, x, y) \
if (!(dbus.x = (TYPE)SDL_LoadFunction(dbus_handle, #y))) \
return false
#define SDL_DBUS_SYM_OPTIONAL(TYPE, x) \
SDL_DBUS_SYM2_OPTIONAL(TYPE, x, dbus_##x)
#define SDL_DBUS_SYM(TYPE, x) \
SDL_DBUS_SYM2(TYPE, x, dbus_##x)
SDL_DBUS_SYM(DBusConnection *(*)(DBusBusType, DBusError *), bus_get_private);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusError *), bus_register);
SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_add_match);
SDL_DBUS_SYM(DBusConnection *(*)(const char *, DBusError *), connection_open_private);
SDL_DBUS_SYM(void (*)(DBusConnection *, dbus_bool_t), connection_set_exit_on_disconnect);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *), connection_get_is_connected);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusHandleMessageFunction, void *, DBusFreeFunction), connection_add_filter);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusHandleMessageFunction, void *), connection_remove_filter);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, const char *, const DBusObjectPathVTable *, void *, DBusError *), connection_try_register_object_path);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusMessage *, dbus_uint32_t *), connection_send);
SDL_DBUS_SYM(DBusMessage *(*)(DBusConnection *, DBusMessage *, int, DBusError *), connection_send_with_reply_and_block);
SDL_DBUS_SYM(void (*)(DBusConnection *), connection_close);
SDL_DBUS_SYM(void (*)(DBusConnection *), connection_ref);
SDL_DBUS_SYM(void (*)(DBusConnection *), connection_unref);
SDL_DBUS_SYM(void (*)(DBusConnection *), connection_flush);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write);
SDL_DBUS_SYM(DBusDispatchStatus (*)(DBusConnection *), connection_dispatch);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *, const char *), message_is_signal);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *), message_has_path);
SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *, const char *), message_new_method_call);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, ...), message_append_args);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, va_list), message_append_args_valist);
SDL_DBUS_SYM(void (*)(DBusMessage *, DBusMessageIter *), message_iter_init_append);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, int, const char *, DBusMessageIter *), message_iter_open_container);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, int, const void *), message_iter_append_basic);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, DBusMessageIter *), message_iter_close_container);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusError *, int, ...), message_get_args);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusError *, int, va_list), message_get_args_valist);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusMessageIter *), message_iter_init);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *), message_iter_next);
SDL_DBUS_SYM(void (*)(DBusMessageIter *, void *), message_iter_get_basic);
SDL_DBUS_SYM(int (*)(DBusMessageIter *), message_iter_get_arg_type);
SDL_DBUS_SYM(void (*)(DBusMessageIter *, DBusMessageIter *), message_iter_recurse);
SDL_DBUS_SYM(void (*)(DBusMessage *), message_unref);
SDL_DBUS_SYM(dbus_bool_t (*)(void), threads_init_default);
SDL_DBUS_SYM(void (*)(DBusError *), error_init);
SDL_DBUS_SYM(dbus_bool_t (*)(const DBusError *), error_is_set);
SDL_DBUS_SYM(void (*)(DBusError *), error_free);
SDL_DBUS_SYM(char *(*)(void), get_local_machine_id);
SDL_DBUS_SYM_OPTIONAL(char *(*)(DBusError *), try_get_local_machine_id);
SDL_DBUS_SYM(void (*)(void *), free);
SDL_DBUS_SYM(void (*)(char **), free_string_array);
SDL_DBUS_SYM(void (*)(void), shutdown);
#undef SDL_DBUS_SYM
#undef SDL_DBUS_SYM2
return true;
}
static void UnloadDBUSLibrary(void)
{
#ifdef SOWRAP_ENABLED // Godot build system constant
if (dbus_handle) {
SDL_UnloadObject(dbus_handle);
dbus_handle = NULL;
}
#endif
}
static bool LoadDBUSLibrary(void)
{
bool result = true;
#ifdef SOWRAP_ENABLED // Godot build system constant
if (!dbus_handle) {
dbus_handle = SDL_LoadObject(dbus_library);
if (!dbus_handle) {
result = false;
// Don't call SDL_SetError(): SDL_LoadObject already did.
} else {
result = LoadDBUSSyms();
if (!result) {
UnloadDBUSLibrary();
}
}
}
#else
result = LoadDBUSSyms();
#endif
return result;
}
static SDL_InitState dbus_init;
void SDL_DBus_Init(void)
{
static bool is_dbus_available = true;
if (!is_dbus_available) {
return; // don't keep trying if this fails.
}
if (!SDL_ShouldInit(&dbus_init)) {
return;
}
if (!LoadDBUSLibrary()) {
goto error;
}
if (!dbus.threads_init_default()) {
goto error;
}
DBusError err;
dbus.error_init(&err);
// session bus is required
dbus.session_conn = dbus.bus_get_private(DBUS_BUS_SESSION, &err);
if (dbus.error_is_set(&err)) {
dbus.error_free(&err);
goto error;
}
dbus.connection_set_exit_on_disconnect(dbus.session_conn, 0);
// system bus is optional
dbus.system_conn = dbus.bus_get_private(DBUS_BUS_SYSTEM, &err);
if (!dbus.error_is_set(&err)) {
dbus.connection_set_exit_on_disconnect(dbus.system_conn, 0);
}
dbus.error_free(&err);
SDL_SetInitialized(&dbus_init, true);
return;
error:
is_dbus_available = false;
SDL_SetInitialized(&dbus_init, true);
SDL_DBus_Quit();
}
void SDL_DBus_Quit(void)
{
if (!SDL_ShouldQuit(&dbus_init)) {
return;
}
if (dbus.system_conn) {
dbus.connection_close(dbus.system_conn);
dbus.connection_unref(dbus.system_conn);
}
if (dbus.session_conn) {
dbus.connection_close(dbus.session_conn);
dbus.connection_unref(dbus.session_conn);
}
if (SDL_GetHintBoolean(SDL_HINT_SHUTDOWN_DBUS_ON_QUIT, false)) {
if (dbus.shutdown) {
dbus.shutdown();
}
UnloadDBUSLibrary();
} else {
/* Leaving libdbus loaded when skipping dbus_shutdown() avoids
* spurious leak warnings from LeakSanitizer on internal D-Bus
* allocations that would be freed by dbus_shutdown(). */
dbus_handle = NULL;
}
SDL_zero(dbus);
if (inhibit_handle) {
SDL_free(inhibit_handle);
inhibit_handle = NULL;
}
SDL_SetInitialized(&dbus_init, false);
}
SDL_DBusContext *SDL_DBus_GetContext(void)
{
if (!dbus_handle || !dbus.session_conn) {
SDL_DBus_Init();
}
return (dbus_handle && dbus.session_conn) ? &dbus : NULL;
}
static bool SDL_DBus_CallMethodInternal(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, va_list ap)
{
bool result = false;
if (conn) {
DBusMessage *msg = dbus.message_new_method_call(node, path, interface, method);
if (msg) {
int firstarg;
va_list ap_reply;
va_copy(ap_reply, ap); // copy the arg list so we don't compete with D-Bus for it
firstarg = va_arg(ap, int);
if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_append_args_valist(msg, firstarg, ap)) {
DBusMessage *reply = dbus.connection_send_with_reply_and_block(conn, msg, 300, NULL);
if (reply) {
// skip any input args, get to output args.
while ((firstarg = va_arg(ap_reply, int)) != DBUS_TYPE_INVALID) {
// we assume D-Bus already validated all this.
{
void *dumpptr = va_arg(ap_reply, void *);
(void)dumpptr;
}
if (firstarg == DBUS_TYPE_ARRAY) {
{
const int dumpint = va_arg(ap_reply, int);
(void)dumpint;
}
}
}
firstarg = va_arg(ap_reply, int);
if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_get_args_valist(reply, NULL, firstarg, ap_reply)) {
result = true;
}
dbus.message_unref(reply);
}
}
va_end(ap_reply);
dbus.message_unref(msg);
}
}
return result;
}
bool SDL_DBus_CallMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...)
{
bool result;
va_list ap;
va_start(ap, method);
result = SDL_DBus_CallMethodInternal(conn, node, path, interface, method, ap);
va_end(ap);
return result;
}
bool SDL_DBus_CallMethod(const char *node, const char *path, const char *interface, const char *method, ...)
{
bool result;
va_list ap;
va_start(ap, method);
result = SDL_DBus_CallMethodInternal(dbus.session_conn, node, path, interface, method, ap);
va_end(ap);
return result;
}
static bool SDL_DBus_CallVoidMethodInternal(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, va_list ap)
{
bool result = false;
if (conn) {
DBusMessage *msg = dbus.message_new_method_call(node, path, interface, method);
if (msg) {
int firstarg = va_arg(ap, int);
if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_append_args_valist(msg, firstarg, ap)) {
if (dbus.connection_send(conn, msg, NULL)) {
dbus.connection_flush(conn);
result = true;
}
}
dbus.message_unref(msg);
}
}
return result;
}
static bool SDL_DBus_CallWithBasicReply(DBusConnection *conn, DBusMessage *msg, const int expectedtype, void *result)
{
bool retval = false;
DBusMessage *reply = dbus.connection_send_with_reply_and_block(conn, msg, 300, NULL);
if (reply) {
DBusMessageIter iter, actual_iter;
dbus.message_iter_init(reply, &iter);
if (dbus.message_iter_get_arg_type(&iter) == DBUS_TYPE_VARIANT) {
dbus.message_iter_recurse(&iter, &actual_iter);
} else {
actual_iter = iter;
}
if (dbus.message_iter_get_arg_type(&actual_iter) == expectedtype) {
dbus.message_iter_get_basic(&actual_iter, result);
retval = true;
}
dbus.message_unref(reply);
}
return retval;
}
bool SDL_DBus_CallVoidMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...)
{
bool result;
va_list ap;
va_start(ap, method);
result = SDL_DBus_CallVoidMethodInternal(conn, node, path, interface, method, ap);
va_end(ap);
return result;
}
bool SDL_DBus_CallVoidMethod(const char *node, const char *path, const char *interface, const char *method, ...)
{
bool result;
va_list ap;
va_start(ap, method);
result = SDL_DBus_CallVoidMethodInternal(dbus.session_conn, node, path, interface, method, ap);
va_end(ap);
return result;
}
bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result)
{
bool retval = false;
if (conn) {
DBusMessage *msg = dbus.message_new_method_call(node, path, "org.freedesktop.DBus.Properties", "Get");
if (msg) {
if (dbus.message_append_args(msg, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) {
retval = SDL_DBus_CallWithBasicReply(conn, msg, expectedtype, result);
}
dbus.message_unref(msg);
}
}
return retval;
}
bool SDL_DBus_QueryProperty(const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result)
{
return SDL_DBus_QueryPropertyOnConnection(dbus.session_conn, node, path, interface, property, expectedtype, result);
}
void SDL_DBus_ScreensaverTickle(void)
{
if (screensaver_cookie == 0 && !inhibit_handle) { // no need to tickle if we're inhibiting.
// org.gnome.ScreenSaver is the legacy interface, but it'll either do nothing or just be a second harmless tickle on newer systems, so we leave it for now.
SDL_DBus_CallVoidMethod("org.gnome.ScreenSaver", "/org/gnome/ScreenSaver", "org.gnome.ScreenSaver", "SimulateUserActivity", DBUS_TYPE_INVALID);
SDL_DBus_CallVoidMethod("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", "SimulateUserActivity", DBUS_TYPE_INVALID);
}
}
static bool SDL_DBus_AppendDictWithKeysAndValues(DBusMessageIter *iterInit, const char **keys, const char **values, int count)
{
DBusMessageIter iterDict;
if (!dbus.message_iter_open_container(iterInit, DBUS_TYPE_ARRAY, "{sv}", &iterDict)) {
goto failed;
}
for (int i = 0; i < count; i++) {
DBusMessageIter iterEntry, iterValue;
const char *key = keys[i];
const char *value = values[i];
if (!dbus.message_iter_open_container(&iterDict, DBUS_TYPE_DICT_ENTRY, NULL, &iterEntry)) {
goto failed;
}
if (!dbus.message_iter_append_basic(&iterEntry, DBUS_TYPE_STRING, &key)) {
goto failed;
}
if (!dbus.message_iter_open_container(&iterEntry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &iterValue)) {
goto failed;
}
if (!dbus.message_iter_append_basic(&iterValue, DBUS_TYPE_STRING, &value)) {
goto failed;
}
if (!dbus.message_iter_close_container(&iterEntry, &iterValue) || !dbus.message_iter_close_container(&iterDict, &iterEntry)) {
goto failed;
}
}
if (!dbus.message_iter_close_container(iterInit, &iterDict)) {
goto failed;
}
return true;
failed:
/* message_iter_abandon_container_if_open() and message_iter_abandon_container() might be
* missing if libdbus is too old. Instead, we just return without cleaning up any eventual
* open container */
return false;
}
static bool SDL_DBus_AppendDictWithKeyValue(DBusMessageIter *iterInit, const char *key, const char *value)
{
const char *keys[1];
const char *values[1];
keys[0] = key;
values[0] = value;
return SDL_DBus_AppendDictWithKeysAndValues(iterInit, keys, values, 1);
}
bool SDL_DBus_ScreensaverInhibit(bool inhibit)
{
const char *default_inhibit_reason = "Playing a game";
if ((inhibit && (screensaver_cookie != 0 || inhibit_handle)) || (!inhibit && (screensaver_cookie == 0 && !inhibit_handle))) {
return true;
}
if (!dbus.session_conn) {
/* We either lost connection to the session bus or were not able to
* load the D-Bus library at all. */
return false;
}
if (SDL_GetSandbox() != SDL_SANDBOX_NONE) {
const char *bus_name = "org.freedesktop.portal.Desktop";
const char *path = "/org/freedesktop/portal/desktop";
const char *interface = "org.freedesktop.portal.Inhibit";
const char *window = ""; // As a future improvement we could gather the X11 XID or Wayland surface identifier
static const unsigned int INHIBIT_IDLE = 8; // Taken from the portal API reference
DBusMessageIter iterInit;
if (inhibit) {
DBusMessage *msg;
bool result = false;
const char *key = "reason";
const char *reply = NULL;
const char *reason = SDL_GetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME);
if (!reason || !reason[0]) {
reason = default_inhibit_reason;
}
msg = dbus.message_new_method_call(bus_name, path, interface, "Inhibit");
if (!msg) {
return false;
}
if (!dbus.message_append_args(msg, DBUS_TYPE_STRING, &window, DBUS_TYPE_UINT32, &INHIBIT_IDLE, DBUS_TYPE_INVALID)) {
dbus.message_unref(msg);
return false;
}
dbus.message_iter_init_append(msg, &iterInit);
// a{sv}
if (!SDL_DBus_AppendDictWithKeyValue(&iterInit, key, reason)) {
dbus.message_unref(msg);
return false;
}
if (SDL_DBus_CallWithBasicReply(dbus.session_conn, msg, DBUS_TYPE_OBJECT_PATH, &reply)) {
inhibit_handle = SDL_strdup(reply);
result = true;
}
dbus.message_unref(msg);
return result;
} else {
if (!SDL_DBus_CallVoidMethod(bus_name, inhibit_handle, "org.freedesktop.portal.Request", "Close", DBUS_TYPE_INVALID)) {
return false;
}
SDL_free(inhibit_handle);
inhibit_handle = NULL;
}
} else {
const char *bus_name = "org.freedesktop.ScreenSaver";
const char *path = "/org/freedesktop/ScreenSaver";
const char *interface = "org.freedesktop.ScreenSaver";
if (inhibit) {
const char *app = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
const char *reason = SDL_GetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME);
if (!reason || !reason[0]) {
reason = default_inhibit_reason;
}
if (!SDL_DBus_CallMethod(bus_name, path, interface, "Inhibit",
DBUS_TYPE_STRING, &app, DBUS_TYPE_STRING, &reason, DBUS_TYPE_INVALID,
DBUS_TYPE_UINT32, &screensaver_cookie, DBUS_TYPE_INVALID)) {
return false;
}
return (screensaver_cookie != 0);
} else {
if (!SDL_DBus_CallVoidMethod(bus_name, path, interface, "UnInhibit", DBUS_TYPE_UINT32, &screensaver_cookie, DBUS_TYPE_INVALID)) {
return false;
}
screensaver_cookie = 0;
}
}
return true;
}
void SDL_DBus_PumpEvents(void)
{
if (dbus.session_conn) {
dbus.connection_read_write(dbus.session_conn, 0);
while (dbus.connection_dispatch(dbus.session_conn) == DBUS_DISPATCH_DATA_REMAINS) {
// Do nothing, actual work happens in DBus_MessageFilter
SDL_DelayNS(SDL_US_TO_NS(10));
}
}
}
/*
* Get the machine ID if possible. Result must be freed with dbus->free().
*/
char *SDL_DBus_GetLocalMachineId(void)
{
DBusError err;
char *result;
dbus.error_init(&err);
if (dbus.try_get_local_machine_id) {
// Available since dbus 1.12.0, has proper error-handling
result = dbus.try_get_local_machine_id(&err);
} else {
/* Available since time immemorial, but has no error-handling:
* if the machine ID can't be read, many versions of libdbus will
* treat that as a fatal mis-installation and abort() */
result = dbus.get_local_machine_id();
}
if (result) {
return result;
}
if (dbus.error_is_set(&err)) {
SDL_SetError("%s: %s", err.name, err.message);
dbus.error_free(&err);
} else {
SDL_SetError("Error getting D-Bus machine ID");
}
return NULL;
}
/*
* Convert file drops with mime type "application/vnd.portal.filetransfer" to file paths
* Result must be freed with dbus->free_string_array().
* https://flatpak.github.io/xdg-desktop-portal/#gdbus-method-org-freedesktop-portal-FileTransfer.RetrieveFiles
*/
char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *path_count)
{
DBusError err;
DBusMessageIter iter, iterDict;
char **paths = NULL;
DBusMessage *reply = NULL;
DBusMessage *msg = dbus.message_new_method_call("org.freedesktop.portal.Documents", // Node
"/org/freedesktop/portal/documents", // Path
"org.freedesktop.portal.FileTransfer", // Interface
"RetrieveFiles"); // Method
// Make sure we have a connection to the dbus session bus
if (!SDL_DBus_GetContext() || !dbus.session_conn) {
/* We either cannot connect to the session bus or were unable to
* load the D-Bus library at all. */
return NULL;
}
dbus.error_init(&err);
// First argument is a "application/vnd.portal.filetransfer" key from a DnD or clipboard event
if (!dbus.message_append_args(msg, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID)) {
SDL_OutOfMemory();
dbus.message_unref(msg);
goto failed;
}
/* Second argument is a variant dictionary for options.
* The spec doesn't define any entries yet so it's empty. */
dbus.message_iter_init_append(msg, &iter);
if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) ||
!dbus.message_iter_close_container(&iter, &iterDict)) {
SDL_OutOfMemory();
dbus.message_unref(msg);
goto failed;
}
reply = dbus.connection_send_with_reply_and_block(dbus.session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, &err);
dbus.message_unref(msg);
if (reply) {
dbus.message_get_args(reply, &err, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &paths, path_count, DBUS_TYPE_INVALID);
dbus.message_unref(reply);
}
if (paths) {
return paths;
}
failed:
if (dbus.error_is_set(&err)) {
SDL_SetError("%s: %s", err.name, err.message);
dbus.error_free(&err);
} else {
SDL_SetError("Error retrieving paths for documents portal \"%s\"", key);
}
return NULL;
}
#endif

114
thirdparty/sdl/core/linux/SDL_dbus.h vendored Normal file
View File

@@ -0,0 +1,114 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_dbus_h_
#define SDL_dbus_h_
#ifdef HAVE_DBUS_DBUS_H
#define SDL_USE_LIBDBUS 1
#include <dbus/dbus.h>
#ifndef DBUS_TIMEOUT_USE_DEFAULT
#define DBUS_TIMEOUT_USE_DEFAULT -1
#endif
#ifndef DBUS_TIMEOUT_INFINITE
#define DBUS_TIMEOUT_INFINITE ((int) 0x7fffffff)
#endif
typedef struct SDL_DBusContext
{
DBusConnection *session_conn;
DBusConnection *system_conn;
DBusConnection *(*bus_get_private)(DBusBusType, DBusError *);
dbus_bool_t (*bus_register)(DBusConnection *, DBusError *);
void (*bus_add_match)(DBusConnection *, const char *, DBusError *);
DBusConnection *(*connection_open_private)(const char *, DBusError *);
void (*connection_set_exit_on_disconnect)(DBusConnection *, dbus_bool_t);
dbus_bool_t (*connection_get_is_connected)(DBusConnection *);
dbus_bool_t (*connection_add_filter)(DBusConnection *, DBusHandleMessageFunction, void *, DBusFreeFunction);
dbus_bool_t (*connection_remove_filter)(DBusConnection *, DBusHandleMessageFunction, void *);
dbus_bool_t (*connection_try_register_object_path)(DBusConnection *, const char *,
const DBusObjectPathVTable *, void *, DBusError *);
dbus_bool_t (*connection_send)(DBusConnection *, DBusMessage *, dbus_uint32_t *);
DBusMessage *(*connection_send_with_reply_and_block)(DBusConnection *, DBusMessage *, int, DBusError *);
void (*connection_close)(DBusConnection *);
void (*connection_ref)(DBusConnection *);
void (*connection_unref)(DBusConnection *);
void (*connection_flush)(DBusConnection *);
dbus_bool_t (*connection_read_write)(DBusConnection *, int);
DBusDispatchStatus (*connection_dispatch)(DBusConnection *);
dbus_bool_t (*message_is_signal)(DBusMessage *, const char *, const char *);
dbus_bool_t (*message_has_path)(DBusMessage *, const char *);
DBusMessage *(*message_new_method_call)(const char *, const char *, const char *, const char *);
dbus_bool_t (*message_append_args)(DBusMessage *, int, ...);
dbus_bool_t (*message_append_args_valist)(DBusMessage *, int, va_list);
void (*message_iter_init_append)(DBusMessage *, DBusMessageIter *);
dbus_bool_t (*message_iter_open_container)(DBusMessageIter *, int, const char *, DBusMessageIter *);
dbus_bool_t (*message_iter_append_basic)(DBusMessageIter *, int, const void *);
dbus_bool_t (*message_iter_close_container)(DBusMessageIter *, DBusMessageIter *);
dbus_bool_t (*message_get_args)(DBusMessage *, DBusError *, int, ...);
dbus_bool_t (*message_get_args_valist)(DBusMessage *, DBusError *, int, va_list);
dbus_bool_t (*message_iter_init)(DBusMessage *, DBusMessageIter *);
dbus_bool_t (*message_iter_next)(DBusMessageIter *);
void (*message_iter_get_basic)(DBusMessageIter *, void *);
int (*message_iter_get_arg_type)(DBusMessageIter *);
void (*message_iter_recurse)(DBusMessageIter *, DBusMessageIter *);
void (*message_unref)(DBusMessage *);
dbus_bool_t (*threads_init_default)(void);
void (*error_init)(DBusError *);
dbus_bool_t (*error_is_set)(const DBusError *);
void (*error_free)(DBusError *);
char *(*get_local_machine_id)(void);
char *(*try_get_local_machine_id)(DBusError *);
void (*free)(void *);
void (*free_string_array)(char **);
void (*shutdown)(void);
} SDL_DBusContext;
extern void SDL_DBus_Init(void);
extern void SDL_DBus_Quit(void);
extern SDL_DBusContext *SDL_DBus_GetContext(void);
// These use the built-in Session connection.
extern bool SDL_DBus_CallMethod(const char *node, const char *path, const char *interface, const char *method, ...);
extern bool SDL_DBus_CallVoidMethod(const char *node, const char *path, const char *interface, const char *method, ...);
extern bool SDL_DBus_QueryProperty(const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result);
// These use whatever connection you like.
extern bool SDL_DBus_CallMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...);
extern bool SDL_DBus_CallVoidMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...);
extern bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result);
extern void SDL_DBus_ScreensaverTickle(void);
extern bool SDL_DBus_ScreensaverInhibit(bool inhibit);
extern void SDL_DBus_PumpEvents(void);
extern char *SDL_DBus_GetLocalMachineId(void);
extern char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *files_count);
#endif // HAVE_DBUS_DBUS_H
#endif // SDL_dbus_h_

1037
thirdparty/sdl/core/linux/SDL_evdev.c vendored Normal file

File diff suppressed because it is too large Load Diff

41
thirdparty/sdl/core/linux/SDL_evdev.h vendored Normal file
View File

@@ -0,0 +1,41 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_evdev_h_
#define SDL_evdev_h_
#ifdef SDL_INPUT_LINUXEV
struct input_event;
extern bool SDL_EVDEV_Init(void);
extern void SDL_EVDEV_Quit(void);
extern void SDL_EVDEV_SetVTSwitchCallbacks(void (*release_callback)(void*), void *release_callback_data,
void (*acquire_callback)(void*), void *acquire_callback_data);
extern int SDL_EVDEV_GetDeviceCount(int device_class);
extern void SDL_EVDEV_Poll(void);
extern Uint64 SDL_EVDEV_GetEventTimestamp(struct input_event *event);
#endif // SDL_INPUT_LINUXEV
#endif // SDL_evdev_h_

View File

@@ -0,0 +1,168 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 2020 Collabora Ltd.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_evdev_capabilities.h"
#ifdef HAVE_LINUX_INPUT_H
// missing defines in older Linux kernel headers
#ifndef BTN_TRIGGER_HAPPY
#define BTN_TRIGGER_HAPPY 0x2c0
#endif
#ifndef BTN_DPAD_UP
#define BTN_DPAD_UP 0x220
#endif
#ifndef KEY_ALS_TOGGLE
#define KEY_ALS_TOGGLE 0x230
#endif
extern int
SDL_EVDEV_GuessDeviceClass(const unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)],
const unsigned long bitmask_ev[NBITS(EV_MAX)],
const unsigned long bitmask_abs[NBITS(ABS_MAX)],
const unsigned long bitmask_key[NBITS(KEY_MAX)],
const unsigned long bitmask_rel[NBITS(REL_MAX)])
{
struct range
{
unsigned start;
unsigned end;
};
// key code ranges above BTN_MISC (start is inclusive, stop is exclusive)
static const struct range high_key_blocks[] = {
{ KEY_OK, BTN_DPAD_UP },
{ KEY_ALS_TOGGLE, BTN_TRIGGER_HAPPY }
};
int devclass = 0;
unsigned long keyboard_mask;
// If the kernel specifically says it's an accelerometer, believe it
if (test_bit(INPUT_PROP_ACCELEROMETER, bitmask_props)) {
return SDL_UDEV_DEVICE_ACCELEROMETER;
}
// We treat pointing sticks as indistinguishable from mice
if (test_bit(INPUT_PROP_POINTING_STICK, bitmask_props)) {
return SDL_UDEV_DEVICE_MOUSE;
}
// We treat buttonpads as equivalent to touchpads
if (test_bit(INPUT_PROP_TOPBUTTONPAD, bitmask_props) ||
test_bit(INPUT_PROP_BUTTONPAD, bitmask_props) ||
test_bit(INPUT_PROP_SEMI_MT, bitmask_props)) {
return SDL_UDEV_DEVICE_TOUCHPAD;
}
// X, Y, Z axes but no buttons probably means an accelerometer
if (test_bit(EV_ABS, bitmask_ev) &&
test_bit(ABS_X, bitmask_abs) &&
test_bit(ABS_Y, bitmask_abs) &&
test_bit(ABS_Z, bitmask_abs) &&
!test_bit(EV_KEY, bitmask_ev)) {
return SDL_UDEV_DEVICE_ACCELEROMETER;
}
/* RX, RY, RZ axes but no buttons probably means a gyro or
* accelerometer (we don't distinguish) */
if (test_bit(EV_ABS, bitmask_ev) &&
test_bit(ABS_RX, bitmask_abs) &&
test_bit(ABS_RY, bitmask_abs) &&
test_bit(ABS_RZ, bitmask_abs) &&
!test_bit(EV_KEY, bitmask_ev)) {
return SDL_UDEV_DEVICE_ACCELEROMETER;
}
if (test_bit(EV_ABS, bitmask_ev) &&
test_bit(ABS_X, bitmask_abs) && test_bit(ABS_Y, bitmask_abs)) {
if (test_bit(BTN_STYLUS, bitmask_key) || test_bit(BTN_TOOL_PEN, bitmask_key)) {
; // ID_INPUT_TABLET
} else if (test_bit(BTN_TOOL_FINGER, bitmask_key) && !test_bit(BTN_TOOL_PEN, bitmask_key)) {
devclass |= SDL_UDEV_DEVICE_TOUCHPAD; // ID_INPUT_TOUCHPAD
} else if (test_bit(BTN_MOUSE, bitmask_key)) {
devclass |= SDL_UDEV_DEVICE_MOUSE; // ID_INPUT_MOUSE
} else if (test_bit(BTN_TOUCH, bitmask_key)) {
/* TODO: better determining between touchscreen and multitouch touchpad,
see https://github.com/systemd/systemd/blob/master/src/udev/udev-builtin-input_id.c */
devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN; // ID_INPUT_TOUCHSCREEN
}
if (test_bit(BTN_TRIGGER, bitmask_key) ||
test_bit(BTN_A, bitmask_key) ||
test_bit(BTN_1, bitmask_key) ||
test_bit(ABS_RX, bitmask_abs) ||
test_bit(ABS_RY, bitmask_abs) ||
test_bit(ABS_RZ, bitmask_abs) ||
test_bit(ABS_THROTTLE, bitmask_abs) ||
test_bit(ABS_RUDDER, bitmask_abs) ||
test_bit(ABS_WHEEL, bitmask_abs) ||
test_bit(ABS_GAS, bitmask_abs) ||
test_bit(ABS_BRAKE, bitmask_abs)) {
devclass |= SDL_UDEV_DEVICE_JOYSTICK; // ID_INPUT_JOYSTICK
}
}
if (test_bit(EV_REL, bitmask_ev) &&
test_bit(REL_X, bitmask_rel) && test_bit(REL_Y, bitmask_rel) &&
test_bit(BTN_MOUSE, bitmask_key)) {
devclass |= SDL_UDEV_DEVICE_MOUSE; // ID_INPUT_MOUSE
}
if (test_bit(EV_KEY, bitmask_ev)) {
unsigned i;
unsigned long found = 0;
for (i = 0; i < BTN_MISC / BITS_PER_LONG; ++i) {
found |= bitmask_key[i];
}
// If there are no keys in the lower block, check the higher blocks
if (!found) {
unsigned block;
for (block = 0; block < (sizeof(high_key_blocks) / sizeof(struct range)); ++block) {
for (i = high_key_blocks[block].start; i < high_key_blocks[block].end; ++i) {
if (test_bit(i, bitmask_key)) {
found = 1;
break;
}
}
}
}
if (found > 0) {
devclass |= SDL_UDEV_DEVICE_HAS_KEYS; // ID_INPUT_KEY
}
}
/* the first 32 bits are ESC, numbers, and Q to D, so if we have all of
* those, consider it to be a fully-featured keyboard;
* do not test KEY_RESERVED, though */
keyboard_mask = 0xFFFFFFFE;
if ((bitmask_key[0] & keyboard_mask) == keyboard_mask) {
devclass |= SDL_UDEV_DEVICE_KEYBOARD; // ID_INPUT_KEYBOARD
}
return devclass;
}
#endif // HAVE_LINUX_INPUT_H

View File

@@ -0,0 +1,76 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 2020 Collabora Ltd.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_evdev_capabilities_h_
#define SDL_evdev_capabilities_h_
#ifdef HAVE_LINUX_INPUT_H
#include <linux/input.h>
#ifndef INPUT_PROP_SEMI_MT
#define INPUT_PROP_SEMI_MT 0x03
#endif
#ifndef INPUT_PROP_TOPBUTTONPAD
#define INPUT_PROP_TOPBUTTONPAD 0x04
#endif
#ifndef INPUT_PROP_POINTING_STICK
#define INPUT_PROP_POINTING_STICK 0x05
#endif
#ifndef INPUT_PROP_ACCELEROMETER
#define INPUT_PROP_ACCELEROMETER 0x06
#endif
#ifndef INPUT_PROP_MAX
#define INPUT_PROP_MAX 0x1f
#endif
// A device can be any combination of these classes
typedef enum
{
SDL_UDEV_DEVICE_UNKNOWN = 0x0000,
SDL_UDEV_DEVICE_MOUSE = 0x0001,
SDL_UDEV_DEVICE_KEYBOARD = 0x0002,
SDL_UDEV_DEVICE_JOYSTICK = 0x0004,
SDL_UDEV_DEVICE_SOUND = 0x0008,
SDL_UDEV_DEVICE_TOUCHSCREEN = 0x0010,
SDL_UDEV_DEVICE_ACCELEROMETER = 0x0020,
SDL_UDEV_DEVICE_TOUCHPAD = 0x0040,
SDL_UDEV_DEVICE_HAS_KEYS = 0x0080,
SDL_UDEV_DEVICE_VIDEO_CAPTURE = 0x0100,
} SDL_UDEV_deviceclass;
#define BITS_PER_LONG (sizeof(unsigned long) * 8)
#define NBITS(x) ((((x)-1) / BITS_PER_LONG) + 1)
#define EVDEV_OFF(x) ((x) % BITS_PER_LONG)
#define EVDEV_LONG(x) ((x) / BITS_PER_LONG)
#define test_bit(bit, array) ((array[EVDEV_LONG(bit)] >> EVDEV_OFF(bit)) & 1)
extern int SDL_EVDEV_GuessDeviceClass(const unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)],
const unsigned long bitmask_ev[NBITS(EV_MAX)],
const unsigned long bitmask_abs[NBITS(ABS_MAX)],
const unsigned long bitmask_key[NBITS(KEY_MAX)],
const unsigned long bitmask_rel[NBITS(REL_MAX)]);
#endif // HAVE_LINUX_INPUT_H
#endif // SDL_evdev_capabilities_h_

View File

@@ -0,0 +1,997 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_evdev_kbd.h"
#ifdef SDL_INPUT_LINUXKD
// This logic is adapted from drivers/tty/vt/keyboard.c in the Linux kernel source
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/kd.h>
#include <linux/keyboard.h>
#include <linux/vt.h>
#include <linux/tiocl.h> // for TIOCL_GETSHIFTSTATE
#include <signal.h>
#include "../../events/SDL_events_c.h"
#include "SDL_evdev_kbd_default_accents.h"
#include "SDL_evdev_kbd_default_keymap.h"
// These are not defined in older Linux kernel headers
#ifndef K_UNICODE
#define K_UNICODE 0x03
#endif
#ifndef K_OFF
#define K_OFF 0x04
#endif
/*
* Handler Tables.
*/
#define K_HANDLERS \
k_self, k_fn, k_spec, k_pad, \
k_dead, k_cons, k_cur, k_shift, \
k_meta, k_ascii, k_lock, k_lowercase, \
k_slock, k_dead2, k_brl, k_ignore
typedef void(k_handler_fn)(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag);
static k_handler_fn K_HANDLERS;
static k_handler_fn *k_handler[16] = { K_HANDLERS };
typedef void(fn_handler_fn)(SDL_EVDEV_keyboard_state *kbd);
static void fn_enter(SDL_EVDEV_keyboard_state *kbd);
static void fn_caps_toggle(SDL_EVDEV_keyboard_state *kbd);
static void fn_caps_on(SDL_EVDEV_keyboard_state *kbd);
static void fn_num(SDL_EVDEV_keyboard_state *kbd);
static void fn_compose(SDL_EVDEV_keyboard_state *kbd);
static fn_handler_fn *fn_handler[] = {
NULL, fn_enter, NULL, NULL,
NULL, NULL, NULL, fn_caps_toggle,
fn_num, NULL, NULL, NULL,
NULL, fn_caps_on, fn_compose, NULL,
NULL, NULL, NULL, fn_num
};
/*
* Keyboard State
*/
struct SDL_EVDEV_keyboard_state
{
int console_fd;
bool muted;
int old_kbd_mode;
unsigned short **key_maps;
unsigned char shift_down[NR_SHIFT]; // shift state counters..
bool dead_key_next;
int npadch; // -1 or number assembled on pad
struct kbdiacrs *accents;
unsigned int diacr;
bool rep; // flag telling character repeat
unsigned char lockstate;
unsigned char slockstate;
unsigned char ledflagstate;
char shift_state;
char text[128];
unsigned int text_len;
void (*vt_release_callback)(void *);
void *vt_release_callback_data;
void (*vt_acquire_callback)(void *);
void *vt_acquire_callback_data;
};
#ifdef DUMP_ACCENTS
static void SDL_EVDEV_dump_accents(SDL_EVDEV_keyboard_state *kbd)
{
unsigned int i;
printf("static struct kbdiacrs default_accents = {\n");
printf(" %d,\n", kbd->accents->kb_cnt);
printf(" {\n");
for (i = 0; i < kbd->accents->kb_cnt; ++i) {
struct kbdiacr *diacr = &kbd->accents->kbdiacr[i];
printf(" { 0x%.2x, 0x%.2x, 0x%.2x },\n",
diacr->diacr, diacr->base, diacr->result);
}
while (i < 256) {
printf(" { 0x00, 0x00, 0x00 },\n");
++i;
}
printf(" }\n");
printf("};\n");
}
#endif // DUMP_ACCENTS
#ifdef DUMP_KEYMAP
static void SDL_EVDEV_dump_keymap(SDL_EVDEV_keyboard_state *kbd)
{
int i, j;
for (i = 0; i < MAX_NR_KEYMAPS; ++i) {
if (kbd->key_maps[i]) {
printf("static unsigned short default_key_map_%d[NR_KEYS] = {", i);
for (j = 0; j < NR_KEYS; ++j) {
if ((j % 8) == 0) {
printf("\n ");
}
printf("0x%.4x, ", kbd->key_maps[i][j]);
}
printf("\n};\n");
}
}
printf("\n");
printf("static unsigned short *default_key_maps[MAX_NR_KEYMAPS] = {\n");
for (i = 0; i < MAX_NR_KEYMAPS; ++i) {
if (kbd->key_maps[i]) {
printf(" default_key_map_%d,\n", i);
} else {
printf(" NULL,\n");
}
}
printf("};\n");
}
#endif // DUMP_KEYMAP
static SDL_EVDEV_keyboard_state *kbd_cleanup_state = NULL;
static int kbd_cleanup_sigactions_installed = 0;
static int kbd_cleanup_atexit_installed = 0;
static struct sigaction old_sigaction[NSIG];
static int fatal_signals[] = {
// Handlers for SIGTERM and SIGINT are installed in SDL_InitQuit.
SIGHUP, SIGQUIT, SIGILL, SIGABRT,
SIGFPE, SIGSEGV, SIGPIPE, SIGBUS,
SIGSYS
};
static void kbd_cleanup(void)
{
SDL_EVDEV_keyboard_state *kbd = kbd_cleanup_state;
if (!kbd) {
return;
}
kbd_cleanup_state = NULL;
ioctl(kbd->console_fd, KDSKBMODE, kbd->old_kbd_mode);
}
static void SDL_EVDEV_kbd_reraise_signal(int sig)
{
(void)raise(sig);
}
static siginfo_t *SDL_EVDEV_kdb_cleanup_siginfo = NULL;
static void *SDL_EVDEV_kdb_cleanup_ucontext = NULL;
static void kbd_cleanup_signal_action(int signum, siginfo_t *info, void *ucontext)
{
struct sigaction *old_action_p = &(old_sigaction[signum]);
sigset_t sigset;
// Restore original signal handler before going any further.
sigaction(signum, old_action_p, NULL);
// Unmask current signal.
sigemptyset(&sigset);
sigaddset(&sigset, signum);
sigprocmask(SIG_UNBLOCK, &sigset, NULL);
// Save original signal info and context for archeologists.
SDL_EVDEV_kdb_cleanup_siginfo = info;
SDL_EVDEV_kdb_cleanup_ucontext = ucontext;
// Restore keyboard.
kbd_cleanup();
// Reraise signal.
SDL_EVDEV_kbd_reraise_signal(signum);
}
static void kbd_unregister_emerg_cleanup(void)
{
int tabidx;
kbd_cleanup_state = NULL;
if (!kbd_cleanup_sigactions_installed) {
return;
}
kbd_cleanup_sigactions_installed = 0;
for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) {
struct sigaction *old_action_p;
struct sigaction cur_action;
int signum = fatal_signals[tabidx];
old_action_p = &(old_sigaction[signum]);
// Examine current signal action
if (sigaction(signum, NULL, &cur_action)) {
continue;
}
// Check if action installed and not modified
if (!(cur_action.sa_flags & SA_SIGINFO) || cur_action.sa_sigaction != &kbd_cleanup_signal_action) {
continue;
}
// Restore original action
sigaction(signum, old_action_p, NULL);
}
}
static void kbd_cleanup_atexit(void)
{
// Restore keyboard.
kbd_cleanup();
// Try to restore signal handlers in case shared library is being unloaded
kbd_unregister_emerg_cleanup();
}
static void kbd_register_emerg_cleanup(SDL_EVDEV_keyboard_state *kbd)
{
int tabidx;
if (kbd_cleanup_state) {
return;
}
kbd_cleanup_state = kbd;
if (!kbd_cleanup_atexit_installed) {
/* Since glibc 2.2.3, atexit() (and on_exit(3)) can be used within a shared library to establish
* functions that are called when the shared library is unloaded.
* -- man atexit(3)
*/
(void)atexit(kbd_cleanup_atexit);
kbd_cleanup_atexit_installed = 1;
}
if (kbd_cleanup_sigactions_installed) {
return;
}
kbd_cleanup_sigactions_installed = 1;
for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) {
struct sigaction *old_action_p;
struct sigaction new_action;
int signum = fatal_signals[tabidx];
old_action_p = &(old_sigaction[signum]);
if (sigaction(signum, NULL, old_action_p)) {
continue;
}
/* Skip SIGHUP and SIGPIPE if handler is already installed
* - assume the handler will do the cleanup
*/
if ((signum == SIGHUP || signum == SIGPIPE) && (old_action_p->sa_handler != SIG_DFL || (void (*)(int))old_action_p->sa_sigaction != SIG_DFL)) {
continue;
}
new_action = *old_action_p;
new_action.sa_flags |= SA_SIGINFO;
new_action.sa_sigaction = &kbd_cleanup_signal_action;
sigaction(signum, &new_action, NULL);
}
}
enum {
VT_SIGNAL_NONE,
VT_SIGNAL_RELEASE,
VT_SIGNAL_ACQUIRE,
};
static int vt_release_signal;
static int vt_acquire_signal;
static SDL_AtomicInt vt_signal_pending;
typedef void (*signal_handler)(int signum);
static void kbd_vt_release_signal_action(int signum)
{
SDL_SetAtomicInt(&vt_signal_pending, VT_SIGNAL_RELEASE);
}
static void kbd_vt_acquire_signal_action(int signum)
{
SDL_SetAtomicInt(&vt_signal_pending, VT_SIGNAL_ACQUIRE);
}
static bool setup_vt_signal(int signum, signal_handler handler)
{
struct sigaction *old_action_p;
struct sigaction new_action;
old_action_p = &(old_sigaction[signum]);
SDL_zero(new_action);
new_action.sa_handler = handler;
new_action.sa_flags = SA_RESTART;
if (sigaction(signum, &new_action, old_action_p) < 0) {
return false;
}
if (old_action_p->sa_handler != SIG_DFL) {
// This signal is already in use
sigaction(signum, old_action_p, NULL);
return false;
}
return true;
}
static int find_free_signal(signal_handler handler)
{
#ifdef SIGRTMIN
int i;
for (i = SIGRTMIN + 2; i <= SIGRTMAX; ++i) {
if (setup_vt_signal(i, handler)) {
return i;
}
}
#endif
if (setup_vt_signal(SIGUSR1, handler)) {
return SIGUSR1;
}
if (setup_vt_signal(SIGUSR2, handler)) {
return SIGUSR2;
}
return 0;
}
static void kbd_vt_quit(int console_fd)
{
struct vt_mode mode;
if (vt_release_signal) {
sigaction(vt_release_signal, &old_sigaction[vt_release_signal], NULL);
vt_release_signal = 0;
}
if (vt_acquire_signal) {
sigaction(vt_acquire_signal, &old_sigaction[vt_acquire_signal], NULL);
vt_acquire_signal = 0;
}
SDL_zero(mode);
mode.mode = VT_AUTO;
ioctl(console_fd, VT_SETMODE, &mode);
}
static bool kbd_vt_init(int console_fd)
{
struct vt_mode mode;
vt_release_signal = find_free_signal(kbd_vt_release_signal_action);
vt_acquire_signal = find_free_signal(kbd_vt_acquire_signal_action);
if (!vt_release_signal || !vt_acquire_signal ) {
kbd_vt_quit(console_fd);
return false;
}
SDL_zero(mode);
mode.mode = VT_PROCESS;
mode.relsig = vt_release_signal;
mode.acqsig = vt_acquire_signal;
mode.frsig = SIGIO;
if (ioctl(console_fd, VT_SETMODE, &mode) < 0) {
kbd_vt_quit(console_fd);
return false;
}
return true;
}
static void kbd_vt_update(SDL_EVDEV_keyboard_state *state)
{
int signal_pending = SDL_GetAtomicInt(&vt_signal_pending);
if (signal_pending != VT_SIGNAL_NONE) {
if (signal_pending == VT_SIGNAL_RELEASE) {
if (state->vt_release_callback) {
state->vt_release_callback(state->vt_release_callback_data);
}
ioctl(state->console_fd, VT_RELDISP, 1);
} else {
if (state->vt_acquire_callback) {
state->vt_acquire_callback(state->vt_acquire_callback_data);
}
ioctl(state->console_fd, VT_RELDISP, VT_ACKACQ);
}
SDL_CompareAndSwapAtomicInt(&vt_signal_pending, signal_pending, VT_SIGNAL_NONE);
}
}
SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void)
{
SDL_EVDEV_keyboard_state *kbd;
char flag_state;
char kbtype;
char shift_state[sizeof(long)] = { TIOCL_GETSHIFTSTATE, 0 };
kbd = (SDL_EVDEV_keyboard_state *)SDL_calloc(1, sizeof(*kbd));
if (!kbd) {
return NULL;
}
// This might fail if we're not connected to a tty (e.g. on the Steam Link)
kbd->console_fd = open("/dev/tty", O_RDONLY | O_CLOEXEC);
if (!((ioctl(kbd->console_fd, KDGKBTYPE, &kbtype) == 0) && ((kbtype == KB_101) || (kbtype == KB_84)))) {
close(kbd->console_fd);
kbd->console_fd = -1;
}
kbd->npadch = -1;
if (ioctl(kbd->console_fd, TIOCLINUX, shift_state) == 0) {
kbd->shift_state = *shift_state;
}
if (ioctl(kbd->console_fd, KDGKBLED, &flag_state) == 0) {
kbd->ledflagstate = flag_state;
}
kbd->accents = &default_accents;
kbd->key_maps = default_key_maps;
if (ioctl(kbd->console_fd, KDGKBMODE, &kbd->old_kbd_mode) == 0) {
// Set the keyboard in UNICODE mode and load the keymaps
ioctl(kbd->console_fd, KDSKBMODE, K_UNICODE);
}
kbd_vt_init(kbd->console_fd);
return kbd;
}
void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted)
{
if (!state) {
return;
}
if (muted == state->muted) {
return;
}
if (muted) {
if (SDL_GetHintBoolean(SDL_HINT_MUTE_CONSOLE_KEYBOARD, true)) {
/* Mute the keyboard so keystrokes only generate evdev events
* and do not leak through to the console
*/
ioctl(state->console_fd, KDSKBMODE, K_OFF);
/* Make sure to restore keyboard if application fails to call
* SDL_Quit before exit or fatal signal is raised.
*/
if (!SDL_GetHintBoolean(SDL_HINT_NO_SIGNAL_HANDLERS, false)) {
kbd_register_emerg_cleanup(state);
}
}
} else {
kbd_unregister_emerg_cleanup();
// Restore the original keyboard mode
ioctl(state->console_fd, KDSKBMODE, state->old_kbd_mode);
}
state->muted = muted;
}
void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data)
{
if (state == NULL) {
return;
}
state->vt_release_callback = release_callback;
state->vt_release_callback_data = release_callback_data;
state->vt_acquire_callback = acquire_callback;
state->vt_acquire_callback_data = acquire_callback_data;
}
void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state)
{
if (!state) {
return;
}
kbd_vt_update(state);
}
void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state)
{
if (state == NULL) {
return;
}
SDL_EVDEV_kbd_set_muted(state, false);
kbd_vt_quit(state->console_fd);
if (state->console_fd >= 0) {
close(state->console_fd);
state->console_fd = -1;
}
if (state->key_maps && state->key_maps != default_key_maps) {
int i;
for (i = 0; i < MAX_NR_KEYMAPS; ++i) {
if (state->key_maps[i]) {
SDL_free(state->key_maps[i]);
}
}
SDL_free(state->key_maps);
}
SDL_free(state);
}
/*
* Helper Functions.
*/
static void put_queue(SDL_EVDEV_keyboard_state *kbd, uint c)
{
// c is already part of a UTF-8 sequence and safe to add as a character
if (kbd->text_len < (sizeof(kbd->text) - 1)) {
kbd->text[kbd->text_len++] = (char)c;
}
}
static void put_utf8(SDL_EVDEV_keyboard_state *kbd, uint c)
{
if (c < 0x80) {
put_queue(kbd, c); /* 0******* */
} else if (c < 0x800) {
/* 110***** 10****** */
put_queue(kbd, 0xc0 | (c >> 6));
put_queue(kbd, 0x80 | (c & 0x3f));
} else if (c < 0x10000) {
if (c >= 0xD800 && c < 0xE000) {
return;
}
if (c == 0xFFFF) {
return;
}
/* 1110**** 10****** 10****** */
put_queue(kbd, 0xe0 | (c >> 12));
put_queue(kbd, 0x80 | ((c >> 6) & 0x3f));
put_queue(kbd, 0x80 | (c & 0x3f));
} else if (c < 0x110000) {
/* 11110*** 10****** 10****** 10****** */
put_queue(kbd, 0xf0 | (c >> 18));
put_queue(kbd, 0x80 | ((c >> 12) & 0x3f));
put_queue(kbd, 0x80 | ((c >> 6) & 0x3f));
put_queue(kbd, 0x80 | (c & 0x3f));
}
}
/*
* We have a combining character DIACR here, followed by the character CH.
* If the combination occurs in the table, return the corresponding value.
* Otherwise, if CH is a space or equals DIACR, return DIACR.
* Otherwise, conclude that DIACR was not combining after all,
* queue it and return CH.
*/
static unsigned int handle_diacr(SDL_EVDEV_keyboard_state *kbd, unsigned int ch)
{
unsigned int d = kbd->diacr;
unsigned int i;
kbd->diacr = 0;
if (kbd->console_fd >= 0)
if (ioctl(kbd->console_fd, KDGKBDIACR, kbd->accents) < 0) {
// No worries, we'll use the default accent table
}
for (i = 0; i < kbd->accents->kb_cnt; i++) {
if (kbd->accents->kbdiacr[i].diacr == d &&
kbd->accents->kbdiacr[i].base == ch) {
return kbd->accents->kbdiacr[i].result;
}
}
if (ch == ' ' || ch == d) {
return d;
}
put_utf8(kbd, d);
return ch;
}
static bool vc_kbd_led(SDL_EVDEV_keyboard_state *kbd, int flag)
{
return (kbd->ledflagstate & flag) != 0;
}
static void set_vc_kbd_led(SDL_EVDEV_keyboard_state *kbd, int flag)
{
kbd->ledflagstate |= flag;
ioctl(kbd->console_fd, KDSETLED, (unsigned long)(kbd->ledflagstate));
}
static void clr_vc_kbd_led(SDL_EVDEV_keyboard_state *kbd, int flag)
{
kbd->ledflagstate &= ~flag;
ioctl(kbd->console_fd, KDSETLED, (unsigned long)(kbd->ledflagstate));
}
static void chg_vc_kbd_lock(SDL_EVDEV_keyboard_state *kbd, int flag)
{
kbd->lockstate ^= 1 << flag;
}
static void chg_vc_kbd_slock(SDL_EVDEV_keyboard_state *kbd, int flag)
{
kbd->slockstate ^= 1 << flag;
}
static void chg_vc_kbd_led(SDL_EVDEV_keyboard_state *kbd, int flag)
{
kbd->ledflagstate ^= flag;
ioctl(kbd->console_fd, KDSETLED, (unsigned long)(kbd->ledflagstate));
}
/*
* Special function handlers
*/
static void fn_enter(SDL_EVDEV_keyboard_state *kbd)
{
if (kbd->diacr) {
put_utf8(kbd, kbd->diacr);
kbd->diacr = 0;
}
}
static void fn_caps_toggle(SDL_EVDEV_keyboard_state *kbd)
{
if (kbd->rep) {
return;
}
chg_vc_kbd_led(kbd, K_CAPSLOCK);
}
static void fn_caps_on(SDL_EVDEV_keyboard_state *kbd)
{
if (kbd->rep) {
return;
}
set_vc_kbd_led(kbd, K_CAPSLOCK);
}
static void fn_num(SDL_EVDEV_keyboard_state *kbd)
{
if (!kbd->rep) {
chg_vc_kbd_led(kbd, K_NUMLOCK);
}
}
static void fn_compose(SDL_EVDEV_keyboard_state *kbd)
{
kbd->dead_key_next = true;
}
/*
* Special key handlers
*/
static void k_ignore(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_spec(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
if (up_flag) {
return;
}
if (value >= SDL_arraysize(fn_handler)) {
return;
}
if (fn_handler[value]) {
fn_handler[value](kbd);
}
}
static void k_lowercase(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_self(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
if (up_flag) {
return; // no action, if this is a key release
}
if (kbd->diacr) {
value = handle_diacr(kbd, value);
}
if (kbd->dead_key_next) {
kbd->dead_key_next = false;
kbd->diacr = value;
return;
}
put_utf8(kbd, value);
}
static void k_deadunicode(SDL_EVDEV_keyboard_state *kbd, unsigned int value, char up_flag)
{
if (up_flag) {
return;
}
kbd->diacr = (kbd->diacr ? handle_diacr(kbd, value) : value);
}
static void k_dead(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
const unsigned char ret_diacr[NR_DEAD] = { '`', '\'', '^', '~', '"', ',' };
k_deadunicode(kbd, ret_diacr[value], up_flag);
}
static void k_dead2(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
k_deadunicode(kbd, value, up_flag);
}
static void k_cons(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_fn(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_cur(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_pad(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
static const char pad_chars[] = "0123456789+-*/\015,.?()#";
if (up_flag) {
return; // no action, if this is a key release
}
if (!vc_kbd_led(kbd, K_NUMLOCK)) {
// unprintable action
return;
}
put_queue(kbd, pad_chars[value]);
}
static void k_shift(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
int old_state = kbd->shift_state;
if (kbd->rep) {
return;
}
/*
* Mimic typewriter:
* a CapsShift key acts like Shift but undoes CapsLock
*/
if (value == KVAL(K_CAPSSHIFT)) {
value = KVAL(K_SHIFT);
if (!up_flag) {
clr_vc_kbd_led(kbd, K_CAPSLOCK);
}
}
if (up_flag) {
/*
* handle the case that two shift or control
* keys are depressed simultaneously
*/
if (kbd->shift_down[value]) {
kbd->shift_down[value]--;
}
} else {
kbd->shift_down[value]++;
}
if (kbd->shift_down[value]) {
kbd->shift_state |= (1 << value);
} else {
kbd->shift_state &= ~(1 << value);
}
// kludge
if (up_flag && kbd->shift_state != old_state && kbd->npadch != -1) {
put_utf8(kbd, kbd->npadch);
kbd->npadch = -1;
}
}
static void k_meta(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_ascii(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
int base;
if (up_flag) {
return;
}
if (value < 10) {
// decimal input of code, while Alt depressed
base = 10;
} else {
// hexadecimal input of code, while AltGr depressed
value -= 10;
base = 16;
}
if (kbd->npadch == -1) {
kbd->npadch = value;
} else {
kbd->npadch = kbd->npadch * base + value;
}
}
static void k_lock(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
if (up_flag || kbd->rep) {
return;
}
chg_vc_kbd_lock(kbd, value);
}
static void k_slock(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
k_shift(kbd, value, up_flag);
if (up_flag || kbd->rep) {
return;
}
chg_vc_kbd_slock(kbd, value);
// try to make Alt, oops, AltGr and such work
if (!kbd->key_maps[kbd->lockstate ^ kbd->slockstate]) {
kbd->slockstate = 0;
chg_vc_kbd_slock(kbd, value);
}
}
static void k_brl(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *state, unsigned int keycode, int down)
{
unsigned char shift_final;
unsigned char type;
unsigned short *key_map;
unsigned short keysym;
if (!state) {
return;
}
state->rep = (down == 2);
shift_final = (state->shift_state | state->slockstate) ^ state->lockstate;
key_map = state->key_maps[shift_final];
if (!key_map) {
// Unsupported shift state (e.g. ctrl = 4, alt = 8), just reset to the default state
state->shift_state = 0;
state->slockstate = 0;
state->lockstate = 0;
return;
}
if (keycode < NR_KEYS) {
if (state->console_fd < 0) {
keysym = key_map[keycode];
} else {
struct kbentry kbe;
kbe.kb_table = shift_final;
kbe.kb_index = keycode;
if (ioctl(state->console_fd, KDGKBENT, &kbe) == 0)
keysym = (kbe.kb_value ^ 0xf000);
else
return;
}
} else {
return;
}
type = KTYP(keysym);
if (type < 0xf0) {
if (down) {
put_utf8(state, keysym);
}
} else {
type -= 0xf0;
// if type is KT_LETTER then it can be affected by Caps Lock
if (type == KT_LETTER) {
type = KT_LATIN;
if (vc_kbd_led(state, K_CAPSLOCK)) {
shift_final = shift_final ^ (1 << KG_SHIFT);
key_map = state->key_maps[shift_final];
if (key_map) {
if (state->console_fd < 0) {
keysym = key_map[keycode];
} else {
struct kbentry kbe;
kbe.kb_table = shift_final;
kbe.kb_index = keycode;
if (ioctl(state->console_fd, KDGKBENT, &kbe) == 0)
keysym = (kbe.kb_value ^ 0xf000);
}
}
}
}
(*k_handler[type])(state, keysym & 0xff, !down);
if (type != KT_SLOCK) {
state->slockstate = 0;
}
}
if (state->text_len > 0) {
state->text[state->text_len] = '\0';
SDL_SendKeyboardText(state->text);
state->text_len = 0;
}
}
#elif !defined(SDL_INPUT_FBSDKBIO) // !SDL_INPUT_LINUXKD
SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void)
{
return NULL;
}
void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted)
{
}
void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data)
{
}
void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state)
{
}
void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *state, unsigned int keycode, int down)
{
}
void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state)
{
}
#endif // SDL_INPUT_LINUXKD

View File

@@ -0,0 +1,35 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_evdev_kbd_h_
#define SDL_evdev_kbd_h_
struct SDL_EVDEV_keyboard_state;
typedef struct SDL_EVDEV_keyboard_state SDL_EVDEV_keyboard_state;
extern SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void);
extern void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted);
extern void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data);
extern void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state);
extern void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *state, unsigned int keycode, int down);
extern void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state);
#endif // SDL_evdev_kbd_h_

View File

@@ -0,0 +1,282 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
static struct kbdiacrs default_accents = {
68,
{
{ 0x60, 0x41, 0xc0 },
{ 0x60, 0x61, 0xe0 },
{ 0x27, 0x41, 0xc1 },
{ 0x27, 0x61, 0xe1 },
{ 0x5e, 0x41, 0xc2 },
{ 0x5e, 0x61, 0xe2 },
{ 0x7e, 0x41, 0xc3 },
{ 0x7e, 0x61, 0xe3 },
{ 0x22, 0x41, 0xc4 },
{ 0x22, 0x61, 0xe4 },
{ 0x4f, 0x41, 0xc5 },
{ 0x6f, 0x61, 0xe5 },
{ 0x30, 0x41, 0xc5 },
{ 0x30, 0x61, 0xe5 },
{ 0x41, 0x41, 0xc5 },
{ 0x61, 0x61, 0xe5 },
{ 0x41, 0x45, 0xc6 },
{ 0x61, 0x65, 0xe6 },
{ 0x2c, 0x43, 0xc7 },
{ 0x2c, 0x63, 0xe7 },
{ 0x60, 0x45, 0xc8 },
{ 0x60, 0x65, 0xe8 },
{ 0x27, 0x45, 0xc9 },
{ 0x27, 0x65, 0xe9 },
{ 0x5e, 0x45, 0xca },
{ 0x5e, 0x65, 0xea },
{ 0x22, 0x45, 0xcb },
{ 0x22, 0x65, 0xeb },
{ 0x60, 0x49, 0xcc },
{ 0x60, 0x69, 0xec },
{ 0x27, 0x49, 0xcd },
{ 0x27, 0x69, 0xed },
{ 0x5e, 0x49, 0xce },
{ 0x5e, 0x69, 0xee },
{ 0x22, 0x49, 0xcf },
{ 0x22, 0x69, 0xef },
{ 0x2d, 0x44, 0xd0 },
{ 0x2d, 0x64, 0xf0 },
{ 0x7e, 0x4e, 0xd1 },
{ 0x7e, 0x6e, 0xf1 },
{ 0x60, 0x4f, 0xd2 },
{ 0x60, 0x6f, 0xf2 },
{ 0x27, 0x4f, 0xd3 },
{ 0x27, 0x6f, 0xf3 },
{ 0x5e, 0x4f, 0xd4 },
{ 0x5e, 0x6f, 0xf4 },
{ 0x7e, 0x4f, 0xd5 },
{ 0x7e, 0x6f, 0xf5 },
{ 0x22, 0x4f, 0xd6 },
{ 0x22, 0x6f, 0xf6 },
{ 0x2f, 0x4f, 0xd8 },
{ 0x2f, 0x6f, 0xf8 },
{ 0x60, 0x55, 0xd9 },
{ 0x60, 0x75, 0xf9 },
{ 0x27, 0x55, 0xda },
{ 0x27, 0x75, 0xfa },
{ 0x5e, 0x55, 0xdb },
{ 0x5e, 0x75, 0xfb },
{ 0x22, 0x55, 0xdc },
{ 0x22, 0x75, 0xfc },
{ 0x27, 0x59, 0xdd },
{ 0x27, 0x79, 0xfd },
{ 0x54, 0x48, 0xde },
{ 0x74, 0x68, 0xfe },
{ 0x73, 0x73, 0xdf },
{ 0x22, 0x79, 0xff },
{ 0x73, 0x7a, 0xdf },
{ 0x69, 0x6a, 0xff },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
}
};

File diff suppressed because it is too large Load Diff

414
thirdparty/sdl/core/linux/SDL_fcitx.c vendored Normal file
View File

@@ -0,0 +1,414 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include <unistd.h>
#include "SDL_fcitx.h"
//#include "../../video/SDL_sysvideo.h"
#include "SDL_dbus.h"
#ifdef SDL_VIDEO_DRIVER_X11
#include "../../video/x11/SDL_x11video.h"
#endif
#define FCITX_DBUS_SERVICE "org.freedesktop.portal.Fcitx"
#define FCITX_IM_DBUS_PATH "/org/freedesktop/portal/inputmethod"
#define FCITX_IM_DBUS_INTERFACE "org.fcitx.Fcitx.InputMethod1"
#define FCITX_IC_DBUS_INTERFACE "org.fcitx.Fcitx.InputContext1"
#define DBUS_TIMEOUT 500
typedef struct FcitxClient
{
SDL_DBusContext *dbus;
char *ic_path;
int id;
SDL_Rect cursor_rect;
} FcitxClient;
static FcitxClient fcitx_client;
static char *GetAppName(void)
{
#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_FREEBSD)
char *spot;
char procfile[1024];
char linkfile[1024];
int linksize;
#ifdef SDL_PLATFORM_LINUX
(void)SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/exe", getpid());
#elif defined(SDL_PLATFORM_FREEBSD)
(void)SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/file", getpid());
#endif
linksize = readlink(procfile, linkfile, sizeof(linkfile) - 1);
if (linksize > 0) {
linkfile[linksize] = '\0';
spot = SDL_strrchr(linkfile, '/');
if (spot) {
return SDL_strdup(spot + 1);
} else {
return SDL_strdup(linkfile);
}
}
#endif // SDL_PLATFORM_LINUX || SDL_PLATFORM_FREEBSD
return SDL_strdup("SDL_App");
}
static size_t Fcitx_GetPreeditString(SDL_DBusContext *dbus,
DBusMessage *msg,
char **ret,
Sint32 *start_pos,
Sint32 *end_pos)
{
char *text = NULL, *subtext;
size_t text_bytes = 0;
DBusMessageIter iter, array, sub;
Sint32 p_start_pos = -1;
Sint32 p_end_pos = -1;
dbus->message_iter_init(msg, &iter);
// Message type is a(si)i, we only need string part
if (dbus->message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY) {
size_t pos = 0;
// First pass: calculate string length
dbus->message_iter_recurse(&iter, &array);
while (dbus->message_iter_get_arg_type(&array) == DBUS_TYPE_STRUCT) {
dbus->message_iter_recurse(&array, &sub);
subtext = NULL;
if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
dbus->message_iter_get_basic(&sub, &subtext);
if (subtext && *subtext) {
text_bytes += SDL_strlen(subtext);
}
}
dbus->message_iter_next(&sub);
if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_INT32 && p_end_pos == -1) {
// Type is a bit field defined as follows:
// bit 3: Underline, bit 4: HighLight, bit 5: DontCommit,
// bit 6: Bold, bit 7: Strike, bit 8: Italic
Sint32 type;
dbus->message_iter_get_basic(&sub, &type);
// We only consider highlight
if (type & (1 << 4)) {
if (p_start_pos == -1) {
p_start_pos = pos;
}
} else if (p_start_pos != -1 && p_end_pos == -1) {
p_end_pos = pos;
}
}
dbus->message_iter_next(&array);
if (subtext && *subtext) {
pos += SDL_utf8strlen(subtext);
}
}
if (p_start_pos != -1 && p_end_pos == -1) {
p_end_pos = pos;
}
if (text_bytes) {
text = SDL_malloc(text_bytes + 1);
}
if (text) {
char *pivot = text;
// Second pass: join all the sub string
dbus->message_iter_recurse(&iter, &array);
while (dbus->message_iter_get_arg_type(&array) == DBUS_TYPE_STRUCT) {
dbus->message_iter_recurse(&array, &sub);
if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
dbus->message_iter_get_basic(&sub, &subtext);
if (subtext && *subtext) {
size_t length = SDL_strlen(subtext);
SDL_strlcpy(pivot, subtext, length + 1);
pivot += length;
}
}
dbus->message_iter_next(&array);
}
} else {
text_bytes = 0;
}
}
*ret = text;
*start_pos = p_start_pos;
*end_pos = p_end_pos;
return text_bytes;
}
static Sint32 Fcitx_GetPreeditCursorByte(SDL_DBusContext *dbus, DBusMessage *msg)
{
Sint32 byte = -1;
DBusMessageIter iter;
dbus->message_iter_init(msg, &iter);
dbus->message_iter_next(&iter);
if (dbus->message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) {
return -1;
}
dbus->message_iter_get_basic(&iter, &byte);
return byte;
}
static DBusHandlerResult DBus_MessageFilter(DBusConnection *conn, DBusMessage *msg, void *data)
{
SDL_DBusContext *dbus = (SDL_DBusContext *)data;
if (dbus->message_is_signal(msg, FCITX_IC_DBUS_INTERFACE, "CommitString")) {
DBusMessageIter iter;
const char *text = NULL;
dbus->message_iter_init(msg, &iter);
dbus->message_iter_get_basic(&iter, &text);
//SDL_SendKeyboardText(text);
return DBUS_HANDLER_RESULT_HANDLED;
}
if (dbus->message_is_signal(msg, FCITX_IC_DBUS_INTERFACE, "UpdateFormattedPreedit")) {
char *text = NULL;
Sint32 start_pos, end_pos;
size_t text_bytes = Fcitx_GetPreeditString(dbus, msg, &text, &start_pos, &end_pos);
if (text_bytes) {
if (start_pos == -1) {
Sint32 byte_pos = Fcitx_GetPreeditCursorByte(dbus, msg);
start_pos = byte_pos >= 0 ? SDL_utf8strnlen(text, byte_pos) : -1;
}
//SDL_SendEditingText(text, start_pos, end_pos >= 0 ? end_pos - start_pos : -1);
SDL_free(text);
} else {
//SDL_SendEditingText("", 0, 0);
}
//SDL_Fcitx_UpdateTextInputArea(SDL_GetKeyboardFocus());
return DBUS_HANDLER_RESULT_HANDLED;
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static void FcitxClientICCallMethod(FcitxClient *client, const char *method)
{
if (!client->ic_path) {
return;
}
SDL_DBus_CallVoidMethod(FCITX_DBUS_SERVICE, client->ic_path, FCITX_IC_DBUS_INTERFACE, method, DBUS_TYPE_INVALID);
}
static void SDLCALL Fcitx_SetCapabilities(void *data,
const char *name,
const char *old_val,
const char *hint)
{
FcitxClient *client = (FcitxClient *)data;
Uint64 caps = 0;
if (!client->ic_path) {
return;
}
if (hint && SDL_strstr(hint, "composition")) {
caps |= (1 << 1); // Preedit Flag
caps |= (1 << 4); // Formatted Preedit Flag
}
if (hint && SDL_strstr(hint, "candidates")) {
// FIXME, turn off native candidate rendering
}
SDL_DBus_CallVoidMethod(FCITX_DBUS_SERVICE, client->ic_path, FCITX_IC_DBUS_INTERFACE, "SetCapability", DBUS_TYPE_UINT64, &caps, DBUS_TYPE_INVALID);
}
static bool FcitxCreateInputContext(SDL_DBusContext *dbus, const char *appname, char **ic_path)
{
const char *program = "program";
bool result = false;
if (dbus && dbus->session_conn) {
DBusMessage *msg = dbus->message_new_method_call(FCITX_DBUS_SERVICE, FCITX_IM_DBUS_PATH, FCITX_IM_DBUS_INTERFACE, "CreateInputContext");
if (msg) {
DBusMessage *reply = NULL;
DBusMessageIter args, array, sub;
dbus->message_iter_init_append(msg, &args);
dbus->message_iter_open_container(&args, DBUS_TYPE_ARRAY, "(ss)", &array);
dbus->message_iter_open_container(&array, DBUS_TYPE_STRUCT, 0, &sub);
dbus->message_iter_append_basic(&sub, DBUS_TYPE_STRING, &program);
dbus->message_iter_append_basic(&sub, DBUS_TYPE_STRING, &appname);
dbus->message_iter_close_container(&array, &sub);
dbus->message_iter_close_container(&args, &array);
reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, 300, NULL);
if (reply) {
if (dbus->message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH, ic_path, DBUS_TYPE_INVALID)) {
result = true;
}
dbus->message_unref(reply);
}
dbus->message_unref(msg);
}
}
return result;
}
static bool FcitxClientCreateIC(FcitxClient *client)
{
char *appname = GetAppName();
char *ic_path = NULL;
SDL_DBusContext *dbus = client->dbus;
// SDL_DBus_CallMethod cannot handle a(ss) type, call dbus function directly
if (!FcitxCreateInputContext(dbus, appname, &ic_path)) {
ic_path = NULL; // just in case.
}
SDL_free(appname);
if (ic_path) {
SDL_free(client->ic_path);
client->ic_path = SDL_strdup(ic_path);
dbus->bus_add_match(dbus->session_conn,
"type='signal', interface='org.fcitx.Fcitx.InputContext1'",
NULL);
dbus->connection_add_filter(dbus->session_conn,
&DBus_MessageFilter, dbus,
NULL);
dbus->connection_flush(dbus->session_conn);
SDL_AddHintCallback(SDL_HINT_IME_IMPLEMENTED_UI, Fcitx_SetCapabilities, client);
return true;
}
return false;
}
static Uint32 Fcitx_ModState(void)
{
Uint32 fcitx_mods = 0;
SDL_Keymod sdl_mods = SDL_GetModState();
if (sdl_mods & SDL_KMOD_SHIFT) {
fcitx_mods |= (1 << 0);
}
if (sdl_mods & SDL_KMOD_CAPS) {
fcitx_mods |= (1 << 1);
}
if (sdl_mods & SDL_KMOD_CTRL) {
fcitx_mods |= (1 << 2);
}
if (sdl_mods & SDL_KMOD_ALT) {
fcitx_mods |= (1 << 3);
}
if (sdl_mods & SDL_KMOD_NUM) {
fcitx_mods |= (1 << 4);
}
if (sdl_mods & SDL_KMOD_MODE) {
fcitx_mods |= (1 << 7);
}
if (sdl_mods & SDL_KMOD_LGUI) {
fcitx_mods |= (1 << 6);
}
if (sdl_mods & SDL_KMOD_RGUI) {
fcitx_mods |= (1 << 28);
}
return fcitx_mods;
}
bool SDL_Fcitx_Init(void)
{
fcitx_client.dbus = SDL_DBus_GetContext();
fcitx_client.cursor_rect.x = -1;
fcitx_client.cursor_rect.y = -1;
fcitx_client.cursor_rect.w = 0;
fcitx_client.cursor_rect.h = 0;
return FcitxClientCreateIC(&fcitx_client);
}
void SDL_Fcitx_Quit(void)
{
FcitxClientICCallMethod(&fcitx_client, "DestroyIC");
if (fcitx_client.ic_path) {
SDL_free(fcitx_client.ic_path);
fcitx_client.ic_path = NULL;
}
}
void SDL_Fcitx_SetFocus(bool focused)
{
if (focused) {
FcitxClientICCallMethod(&fcitx_client, "FocusIn");
} else {
FcitxClientICCallMethod(&fcitx_client, "FocusOut");
}
}
void SDL_Fcitx_Reset(void)
{
FcitxClientICCallMethod(&fcitx_client, "Reset");
}
bool SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down)
{
Uint32 mod_state = Fcitx_ModState();
Uint32 handled = false;
Uint32 is_release = !down;
Uint32 event_time = 0;
if (!fcitx_client.ic_path) {
return false;
}
if (SDL_DBus_CallMethod(FCITX_DBUS_SERVICE, fcitx_client.ic_path, FCITX_IC_DBUS_INTERFACE, "ProcessKeyEvent",
DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &keycode, DBUS_TYPE_UINT32, &mod_state, DBUS_TYPE_BOOLEAN, &is_release, DBUS_TYPE_UINT32, &event_time, DBUS_TYPE_INVALID,
DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID)) {
if (handled) {
//SDL_Fcitx_UpdateTextInputArea(SDL_GetKeyboardFocus());
return true;
}
}
return false;
}
void SDL_Fcitx_PumpEvents(void)
{
SDL_DBusContext *dbus = fcitx_client.dbus;
DBusConnection *conn = dbus->session_conn;
dbus->connection_read_write(conn, 0);
while (dbus->connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS) {
// Do nothing, actual work happens in DBus_MessageFilter
}
}

35
thirdparty/sdl/core/linux/SDL_fcitx.h vendored Normal file
View File

@@ -0,0 +1,35 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_fcitx_h_
#define SDL_fcitx_h_
#include "SDL_internal.h"
extern bool SDL_Fcitx_Init(void);
extern void SDL_Fcitx_Quit(void);
extern void SDL_Fcitx_SetFocus(bool focused);
extern void SDL_Fcitx_Reset(void);
extern bool SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down);
extern void SDL_Fcitx_UpdateTextInputArea(SDL_Window *window);
extern void SDL_Fcitx_PumpEvents(void);
#endif // SDL_fcitx_h_

694
thirdparty/sdl/core/linux/SDL_ibus.c vendored Normal file
View File

@@ -0,0 +1,694 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef HAVE_IBUS_IBUS_H
#include "SDL_ibus.h"
#include "SDL_dbus.h"
#ifdef SDL_USE_LIBDBUS
//#include "../../video/SDL_sysvideo.h"
#include "../../events/SDL_keyboard_c.h"
#ifdef SDL_VIDEO_DRIVER_X11
#include "../../video/x11/SDL_x11video.h"
#endif
#include <sys/inotify.h>
#include <unistd.h>
#include <fcntl.h>
static const char IBUS_PATH[] = "/org/freedesktop/IBus";
static const char IBUS_SERVICE[] = "org.freedesktop.IBus";
static const char IBUS_INTERFACE[] = "org.freedesktop.IBus";
static const char IBUS_INPUT_INTERFACE[] = "org.freedesktop.IBus.InputContext";
static const char IBUS_PORTAL_SERVICE[] = "org.freedesktop.portal.IBus";
static const char IBUS_PORTAL_INTERFACE[] = "org.freedesktop.IBus.Portal";
static const char IBUS_PORTAL_INPUT_INTERFACE[] = "org.freedesktop.IBus.InputContext";
static const char *ibus_service = NULL;
static const char *ibus_interface = NULL;
static const char *ibus_input_interface = NULL;
static char *input_ctx_path = NULL;
static SDL_Rect ibus_cursor_rect = { 0, 0, 0, 0 };
static DBusConnection *ibus_conn = NULL;
static bool ibus_is_portal_interface = false;
static char *ibus_addr_file = NULL;
static int inotify_fd = -1, inotify_wd = -1;
static Uint32 IBus_ModState(void)
{
Uint32 ibus_mods = 0;
SDL_Keymod sdl_mods = SDL_GetModState();
// Not sure about MOD3, MOD4 and HYPER mappings
if (sdl_mods & SDL_KMOD_LSHIFT) {
ibus_mods |= IBUS_SHIFT_MASK;
}
if (sdl_mods & SDL_KMOD_CAPS) {
ibus_mods |= IBUS_LOCK_MASK;
}
if (sdl_mods & SDL_KMOD_LCTRL) {
ibus_mods |= IBUS_CONTROL_MASK;
}
if (sdl_mods & SDL_KMOD_LALT) {
ibus_mods |= IBUS_MOD1_MASK;
}
if (sdl_mods & SDL_KMOD_NUM) {
ibus_mods |= IBUS_MOD2_MASK;
}
if (sdl_mods & SDL_KMOD_MODE) {
ibus_mods |= IBUS_MOD5_MASK;
}
if (sdl_mods & SDL_KMOD_LGUI) {
ibus_mods |= IBUS_SUPER_MASK;
}
if (sdl_mods & SDL_KMOD_RGUI) {
ibus_mods |= IBUS_META_MASK;
}
return ibus_mods;
}
static bool IBus_EnterVariant(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus,
DBusMessageIter *inside, const char *struct_id, size_t id_size)
{
DBusMessageIter sub;
if (dbus->message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT) {
return false;
}
dbus->message_iter_recurse(iter, &sub);
if (dbus->message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
return false;
}
dbus->message_iter_recurse(&sub, inside);
if (dbus->message_iter_get_arg_type(inside) != DBUS_TYPE_STRING) {
return false;
}
dbus->message_iter_get_basic(inside, &struct_id);
if (!struct_id || SDL_strncmp(struct_id, struct_id, id_size) != 0) {
return false;
}
return true;
}
static bool IBus_GetDecorationPosition(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus,
Uint32 *start_pos, Uint32 *end_pos)
{
DBusMessageIter sub1, sub2, array;
if (!IBus_EnterVariant(conn, iter, dbus, &sub1, "IBusText", sizeof("IBusText"))) {
return false;
}
dbus->message_iter_next(&sub1);
dbus->message_iter_next(&sub1);
dbus->message_iter_next(&sub1);
if (!IBus_EnterVariant(conn, &sub1, dbus, &sub2, "IBusAttrList", sizeof("IBusAttrList"))) {
return false;
}
dbus->message_iter_next(&sub2);
dbus->message_iter_next(&sub2);
if (dbus->message_iter_get_arg_type(&sub2) != DBUS_TYPE_ARRAY) {
return false;
}
dbus->message_iter_recurse(&sub2, &array);
while (dbus->message_iter_get_arg_type(&array) == DBUS_TYPE_VARIANT) {
DBusMessageIter sub;
if (IBus_EnterVariant(conn, &array, dbus, &sub, "IBusAttribute", sizeof("IBusAttribute"))) {
Uint32 type;
dbus->message_iter_next(&sub);
dbus->message_iter_next(&sub);
// From here on, the structure looks like this:
// Uint32 type: 1=underline, 2=foreground, 3=background
// Uint32 value: for underline it's 0=NONE, 1=SINGLE, 2=DOUBLE,
// 3=LOW, 4=ERROR
// for foreground and background it's a color
// Uint32 start_index: starting position for the style (utf8-char)
// Uint32 end_index: end position for the style (utf8-char)
dbus->message_iter_get_basic(&sub, &type);
// We only use the background type to determine the selection
if (type == 3) {
Uint32 start = -1;
dbus->message_iter_next(&sub);
dbus->message_iter_next(&sub);
if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32) {
dbus->message_iter_get_basic(&sub, &start);
dbus->message_iter_next(&sub);
if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32) {
dbus->message_iter_get_basic(&sub, end_pos);
*start_pos = start;
return true;
}
}
}
}
dbus->message_iter_next(&array);
}
return false;
}
static const char *IBus_GetVariantText(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus)
{
// The text we need is nested weirdly, use dbus-monitor to see the structure better
const char *text = NULL;
DBusMessageIter sub;
if (!IBus_EnterVariant(conn, iter, dbus, &sub, "IBusText", sizeof("IBusText"))) {
return NULL;
}
dbus->message_iter_next(&sub);
dbus->message_iter_next(&sub);
if (dbus->message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) {
return NULL;
}
dbus->message_iter_get_basic(&sub, &text);
return text;
}
static bool IBus_GetVariantCursorPos(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus,
Uint32 *pos)
{
dbus->message_iter_next(iter);
if (dbus->message_iter_get_arg_type(iter) != DBUS_TYPE_UINT32) {
return false;
}
dbus->message_iter_get_basic(iter, pos);
return true;
}
static DBusHandlerResult IBus_MessageHandler(DBusConnection *conn, DBusMessage *msg, void *user_data)
{
SDL_DBusContext *dbus = (SDL_DBusContext *)user_data;
if (dbus->message_is_signal(msg, ibus_input_interface, "CommitText")) {
DBusMessageIter iter;
const char *text;
dbus->message_iter_init(msg, &iter);
text = IBus_GetVariantText(conn, &iter, dbus);
SDL_SendKeyboardText(text);
return DBUS_HANDLER_RESULT_HANDLED;
}
if (dbus->message_is_signal(msg, ibus_input_interface, "UpdatePreeditText")) {
DBusMessageIter iter;
const char *text;
dbus->message_iter_init(msg, &iter);
text = IBus_GetVariantText(conn, &iter, dbus);
if (text) {
Uint32 pos, start_pos, end_pos;
bool has_pos = false;
bool has_dec_pos = false;
dbus->message_iter_init(msg, &iter);
has_dec_pos = IBus_GetDecorationPosition(conn, &iter, dbus, &start_pos, &end_pos);
if (!has_dec_pos) {
dbus->message_iter_init(msg, &iter);
has_pos = IBus_GetVariantCursorPos(conn, &iter, dbus, &pos);
}
if (has_dec_pos) {
SDL_SendEditingText(text, start_pos, end_pos - start_pos);
} else if (has_pos) {
SDL_SendEditingText(text, pos, -1);
} else {
SDL_SendEditingText(text, -1, -1);
}
}
//SDL_IBus_UpdateTextInputArea(SDL_GetKeyboardFocus());
return DBUS_HANDLER_RESULT_HANDLED;
}
if (dbus->message_is_signal(msg, ibus_input_interface, "HidePreeditText")) {
SDL_SendEditingText("", 0, 0);
return DBUS_HANDLER_RESULT_HANDLED;
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static char *IBus_ReadAddressFromFile(const char *file_path)
{
char addr_buf[1024];
bool success = false;
FILE *addr_file;
addr_file = fopen(file_path, "r");
if (!addr_file) {
return NULL;
}
while (fgets(addr_buf, sizeof(addr_buf), addr_file)) {
if (SDL_strncmp(addr_buf, "IBUS_ADDRESS=", sizeof("IBUS_ADDRESS=") - 1) == 0) {
size_t sz = SDL_strlen(addr_buf);
if (addr_buf[sz - 1] == '\n') {
addr_buf[sz - 1] = 0;
}
if (addr_buf[sz - 2] == '\r') {
addr_buf[sz - 2] = 0;
}
success = true;
break;
}
}
(void)fclose(addr_file);
if (success) {
return SDL_strdup(addr_buf + (sizeof("IBUS_ADDRESS=") - 1));
} else {
return NULL;
}
}
static char *IBus_GetDBusAddressFilename(void)
{
SDL_DBusContext *dbus;
const char *disp_env;
char config_dir[PATH_MAX];
char *display = NULL;
const char *addr;
const char *conf_env;
char *key;
char file_path[PATH_MAX];
const char *host;
char *disp_num, *screen_num;
if (ibus_addr_file) {
return SDL_strdup(ibus_addr_file);
}
dbus = SDL_DBus_GetContext();
if (!dbus) {
return NULL;
}
// Use this environment variable if it exists.
addr = SDL_getenv("IBUS_ADDRESS");
if (addr && *addr) {
return SDL_strdup(addr);
}
/* Otherwise, we have to get the hostname, display, machine id, config dir
and look up the address from a filepath using all those bits, eek. */
disp_env = SDL_getenv("DISPLAY");
if (!disp_env || !*disp_env) {
display = SDL_strdup(":0.0");
} else {
display = SDL_strdup(disp_env);
}
host = display;
disp_num = SDL_strrchr(display, ':');
screen_num = SDL_strrchr(display, '.');
if (!disp_num) {
SDL_free(display);
return NULL;
}
*disp_num = 0;
disp_num++;
if (screen_num) {
*screen_num = 0;
}
if (!*host) {
const char *session = SDL_getenv("XDG_SESSION_TYPE");
if (session && SDL_strcmp(session, "wayland") == 0) {
host = "unix-wayland";
} else {
host = "unix";
}
}
SDL_memset(config_dir, 0, sizeof(config_dir));
conf_env = SDL_getenv("XDG_CONFIG_HOME");
if (conf_env && *conf_env) {
SDL_strlcpy(config_dir, conf_env, sizeof(config_dir));
} else {
const char *home_env = SDL_getenv("HOME");
if (!home_env || !*home_env) {
SDL_free(display);
return NULL;
}
(void)SDL_snprintf(config_dir, sizeof(config_dir), "%s/.config", home_env);
}
key = SDL_DBus_GetLocalMachineId();
if (!key) {
SDL_free(display);
return NULL;
}
SDL_memset(file_path, 0, sizeof(file_path));
(void)SDL_snprintf(file_path, sizeof(file_path), "%s/ibus/bus/%s-%s-%s",
config_dir, key, host, disp_num);
dbus->free(key);
SDL_free(display);
return SDL_strdup(file_path);
}
static bool IBus_CheckConnection(SDL_DBusContext *dbus);
static void SDLCALL IBus_SetCapabilities(void *data, const char *name, const char *old_val,
const char *hint)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
if (IBus_CheckConnection(dbus)) {
Uint32 caps = IBUS_CAP_FOCUS;
if (hint && SDL_strstr(hint, "composition")) {
caps |= IBUS_CAP_PREEDIT_TEXT;
}
if (hint && SDL_strstr(hint, "candidates")) {
// FIXME, turn off native candidate rendering
}
SDL_DBus_CallVoidMethodOnConnection(ibus_conn, ibus_service, input_ctx_path, ibus_input_interface, "SetCapabilities",
DBUS_TYPE_UINT32, &caps, DBUS_TYPE_INVALID);
}
}
static bool IBus_SetupConnection(SDL_DBusContext *dbus, const char *addr)
{
const char *client_name = "SDL3_Application";
const char *path = NULL;
bool result = false;
DBusObjectPathVTable ibus_vtable;
SDL_zero(ibus_vtable);
ibus_vtable.message_function = &IBus_MessageHandler;
/* try the portal interface first. Modern systems have this in general,
and sandbox things like FlakPak and Snaps, etc, require it. */
ibus_is_portal_interface = true;
ibus_service = IBUS_PORTAL_SERVICE;
ibus_interface = IBUS_PORTAL_INTERFACE;
ibus_input_interface = IBUS_PORTAL_INPUT_INTERFACE;
ibus_conn = dbus->session_conn;
result = SDL_DBus_CallMethodOnConnection(ibus_conn, ibus_service, IBUS_PATH, ibus_interface, "CreateInputContext",
DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID,
DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID);
if (!result) {
ibus_is_portal_interface = false;
ibus_service = IBUS_SERVICE;
ibus_interface = IBUS_INTERFACE;
ibus_input_interface = IBUS_INPUT_INTERFACE;
ibus_conn = dbus->connection_open_private(addr, NULL);
if (!ibus_conn) {
return false; // oh well.
}
dbus->connection_flush(ibus_conn);
if (!dbus->bus_register(ibus_conn, NULL)) {
ibus_conn = NULL;
return false;
}
dbus->connection_flush(ibus_conn);
result = SDL_DBus_CallMethodOnConnection(ibus_conn, ibus_service, IBUS_PATH, ibus_interface, "CreateInputContext",
DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID,
DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID);
} else {
// re-using dbus->session_conn
dbus->connection_ref(ibus_conn);
}
if (result) {
char matchstr[128];
(void)SDL_snprintf(matchstr, sizeof(matchstr), "type='signal',interface='%s'", ibus_input_interface);
SDL_free(input_ctx_path);
input_ctx_path = SDL_strdup(path);
SDL_AddHintCallback(SDL_HINT_IME_IMPLEMENTED_UI, IBus_SetCapabilities, NULL);
dbus->bus_add_match(ibus_conn, matchstr, NULL);
dbus->connection_try_register_object_path(ibus_conn, input_ctx_path, &ibus_vtable, dbus, NULL);
dbus->connection_flush(ibus_conn);
}
return result;
}
static bool IBus_CheckConnection(SDL_DBusContext *dbus)
{
if (!dbus) {
return false;
}
if (ibus_conn && dbus->connection_get_is_connected(ibus_conn)) {
return true;
}
if (inotify_fd > 0 && inotify_wd > 0) {
char buf[1024];
ssize_t readsize = read(inotify_fd, buf, sizeof(buf));
if (readsize > 0) {
char *p;
bool file_updated = false;
for (p = buf; p < buf + readsize; /**/) {
struct inotify_event *event = (struct inotify_event *)p;
if (event->len > 0) {
char *addr_file_no_path = SDL_strrchr(ibus_addr_file, '/');
if (!addr_file_no_path) {
return false;
}
if (SDL_strcmp(addr_file_no_path + 1, event->name) == 0) {
file_updated = true;
break;
}
}
p += sizeof(struct inotify_event) + event->len;
}
if (file_updated) {
char *addr = IBus_ReadAddressFromFile(ibus_addr_file);
if (addr) {
bool result = IBus_SetupConnection(dbus, addr);
SDL_free(addr);
return result;
}
}
}
}
return false;
}
bool SDL_IBus_Init(void)
{
bool result = false;
SDL_DBusContext *dbus = SDL_DBus_GetContext();
if (dbus) {
char *addr_file = IBus_GetDBusAddressFilename();
char *addr;
char *addr_file_dir;
if (!addr_file) {
return false;
}
addr = IBus_ReadAddressFromFile(addr_file);
if (!addr) {
SDL_free(addr_file);
return false;
}
if (ibus_addr_file) {
SDL_free(ibus_addr_file);
}
ibus_addr_file = SDL_strdup(addr_file);
if (inotify_fd < 0) {
inotify_fd = inotify_init();
fcntl(inotify_fd, F_SETFL, O_NONBLOCK);
}
addr_file_dir = SDL_strrchr(addr_file, '/');
if (addr_file_dir) {
*addr_file_dir = 0;
}
inotify_wd = inotify_add_watch(inotify_fd, addr_file, IN_CREATE | IN_MODIFY);
SDL_free(addr_file);
result = IBus_SetupConnection(dbus, addr);
SDL_free(addr);
// don't use the addr_file if using the portal interface.
if (result && ibus_is_portal_interface) {
if (inotify_fd > 0) {
if (inotify_wd > 0) {
inotify_rm_watch(inotify_fd, inotify_wd);
inotify_wd = -1;
}
close(inotify_fd);
inotify_fd = -1;
}
}
}
return result;
}
void SDL_IBus_Quit(void)
{
SDL_DBusContext *dbus;
if (input_ctx_path) {
SDL_free(input_ctx_path);
input_ctx_path = NULL;
}
if (ibus_addr_file) {
SDL_free(ibus_addr_file);
ibus_addr_file = NULL;
}
dbus = SDL_DBus_GetContext();
// if using portal, ibus_conn == session_conn; don't release it here.
if (dbus && ibus_conn && !ibus_is_portal_interface) {
dbus->connection_close(ibus_conn);
dbus->connection_unref(ibus_conn);
}
ibus_conn = NULL;
ibus_service = NULL;
ibus_interface = NULL;
ibus_input_interface = NULL;
ibus_is_portal_interface = false;
if (inotify_fd > 0 && inotify_wd > 0) {
inotify_rm_watch(inotify_fd, inotify_wd);
inotify_wd = -1;
}
// !!! FIXME: should we close(inotify_fd) here?
SDL_RemoveHintCallback(SDL_HINT_IME_IMPLEMENTED_UI, IBus_SetCapabilities, NULL);
SDL_memset(&ibus_cursor_rect, 0, sizeof(ibus_cursor_rect));
}
static void IBus_SimpleMessage(const char *method)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
if ((input_ctx_path) && (IBus_CheckConnection(dbus))) {
SDL_DBus_CallVoidMethodOnConnection(ibus_conn, ibus_service, input_ctx_path, ibus_input_interface, method, DBUS_TYPE_INVALID);
}
}
void SDL_IBus_SetFocus(bool focused)
{
const char *method = focused ? "FocusIn" : "FocusOut";
IBus_SimpleMessage(method);
}
void SDL_IBus_Reset(void)
{
IBus_SimpleMessage("Reset");
}
bool SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down)
{
Uint32 result = 0;
SDL_DBusContext *dbus = SDL_DBus_GetContext();
if (IBus_CheckConnection(dbus)) {
Uint32 mods = IBus_ModState();
Uint32 ibus_keycode = keycode - 8;
if (!down) {
mods |= (1 << 30); // IBUS_RELEASE_MASK
}
if (!SDL_DBus_CallMethodOnConnection(ibus_conn, ibus_service, input_ctx_path, ibus_input_interface, "ProcessKeyEvent",
DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &ibus_keycode, DBUS_TYPE_UINT32, &mods, DBUS_TYPE_INVALID,
DBUS_TYPE_BOOLEAN, &result, DBUS_TYPE_INVALID)) {
result = 0;
}
}
//SDL_IBus_UpdateTextInputArea(SDL_GetKeyboardFocus());
return (result != 0);
}
void SDL_IBus_PumpEvents(void)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
if (IBus_CheckConnection(dbus)) {
dbus->connection_read_write(ibus_conn, 0);
while (dbus->connection_dispatch(ibus_conn) == DBUS_DISPATCH_DATA_REMAINS) {
// Do nothing, actual work happens in IBus_MessageHandler
}
}
}
#endif // SDL_USE_LIBDBUS
#endif

55
thirdparty/sdl/core/linux/SDL_ibus.h vendored Normal file
View File

@@ -0,0 +1,55 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_ibus_h_
#define SDL_ibus_h_
#ifdef HAVE_IBUS_IBUS_H
#define SDL_USE_IBUS 1
#include <ibus.h>
extern bool SDL_IBus_Init(void);
extern void SDL_IBus_Quit(void);
// Lets the IBus server know about changes in window focus
extern void SDL_IBus_SetFocus(bool focused);
// Closes the candidate list and resets any text currently being edited
extern void SDL_IBus_Reset(void);
/* Sends a keypress event to IBus, returns true if IBus used this event to
update its candidate list or change input methods. PumpEvents should be
called some time after this, to receive the TextInput / TextEditing event back. */
extern bool SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down);
/* Update the position of IBus' candidate list. If rect is NULL then this will
just reposition it relative to the focused window's new position. */
extern void SDL_IBus_UpdateTextInputArea(SDL_Window *window);
/* Checks DBus for new IBus events, and calls SDL_SendKeyboardText /
SDL_SendEditingText for each event it finds */
extern void SDL_IBus_PumpEvents(void);
#endif // HAVE_IBUS_IBUS_H
#endif // SDL_ibus_h_

150
thirdparty/sdl/core/linux/SDL_ime.c vendored Normal file
View File

@@ -0,0 +1,150 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_ime.h"
#include "SDL_ibus.h"
#include "SDL_fcitx.h"
typedef bool (*SDL_IME_Init_t)(void);
typedef void (*SDL_IME_Quit_t)(void);
typedef void (*SDL_IME_SetFocus_t)(bool);
typedef void (*SDL_IME_Reset_t)(void);
typedef bool (*SDL_IME_ProcessKeyEvent_t)(Uint32, Uint32, bool down);
typedef void (*SDL_IME_UpdateTextInputArea_t)(SDL_Window *window);
typedef void (*SDL_IME_PumpEvents_t)(void);
static SDL_IME_Init_t SDL_IME_Init_Real = NULL;
static SDL_IME_Quit_t SDL_IME_Quit_Real = NULL;
static SDL_IME_SetFocus_t SDL_IME_SetFocus_Real = NULL;
static SDL_IME_Reset_t SDL_IME_Reset_Real = NULL;
static SDL_IME_ProcessKeyEvent_t SDL_IME_ProcessKeyEvent_Real = NULL;
static SDL_IME_UpdateTextInputArea_t SDL_IME_UpdateTextInputArea_Real = NULL;
static SDL_IME_PumpEvents_t SDL_IME_PumpEvents_Real = NULL;
static void InitIME(void)
{
static bool inited = false;
#ifdef HAVE_FCITX
const char *im_module = SDL_getenv("SDL_IM_MODULE");
const char *xmodifiers = SDL_getenv("XMODIFIERS");
#endif
if (inited == true) {
return;
}
inited = true;
// See if fcitx IME support is being requested
#ifdef HAVE_FCITX
if (!SDL_IME_Init_Real &&
((im_module && SDL_strcmp(im_module, "fcitx") == 0) ||
(!im_module && xmodifiers && SDL_strstr(xmodifiers, "@im=fcitx") != NULL))) {
SDL_IME_Init_Real = SDL_Fcitx_Init;
SDL_IME_Quit_Real = SDL_Fcitx_Quit;
SDL_IME_SetFocus_Real = SDL_Fcitx_SetFocus;
SDL_IME_Reset_Real = SDL_Fcitx_Reset;
SDL_IME_ProcessKeyEvent_Real = SDL_Fcitx_ProcessKeyEvent;
SDL_IME_UpdateTextInputArea_Real = SDL_Fcitx_UpdateTextInputArea;
SDL_IME_PumpEvents_Real = SDL_Fcitx_PumpEvents;
}
#endif // HAVE_FCITX
// default to IBus
#ifdef HAVE_IBUS_IBUS_H
if (!SDL_IME_Init_Real) {
SDL_IME_Init_Real = SDL_IBus_Init;
SDL_IME_Quit_Real = SDL_IBus_Quit;
SDL_IME_SetFocus_Real = SDL_IBus_SetFocus;
SDL_IME_Reset_Real = SDL_IBus_Reset;
SDL_IME_ProcessKeyEvent_Real = SDL_IBus_ProcessKeyEvent;
SDL_IME_UpdateTextInputArea_Real = SDL_IBus_UpdateTextInputArea;
SDL_IME_PumpEvents_Real = SDL_IBus_PumpEvents;
}
#endif // HAVE_IBUS_IBUS_H
}
bool SDL_IME_Init(void)
{
InitIME();
if (SDL_IME_Init_Real) {
if (SDL_IME_Init_Real()) {
return true;
}
// uhoh, the IME implementation's init failed! Disable IME support.
SDL_IME_Init_Real = NULL;
SDL_IME_Quit_Real = NULL;
SDL_IME_SetFocus_Real = NULL;
SDL_IME_Reset_Real = NULL;
SDL_IME_ProcessKeyEvent_Real = NULL;
SDL_IME_UpdateTextInputArea_Real = NULL;
SDL_IME_PumpEvents_Real = NULL;
}
return false;
}
void SDL_IME_Quit(void)
{
if (SDL_IME_Quit_Real) {
SDL_IME_Quit_Real();
}
}
void SDL_IME_SetFocus(bool focused)
{
if (SDL_IME_SetFocus_Real) {
SDL_IME_SetFocus_Real(focused);
}
}
void SDL_IME_Reset(void)
{
if (SDL_IME_Reset_Real) {
SDL_IME_Reset_Real();
}
}
bool SDL_IME_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down)
{
if (SDL_IME_ProcessKeyEvent_Real) {
return SDL_IME_ProcessKeyEvent_Real(keysym, keycode, down);
}
return false;
}
void SDL_IME_UpdateTextInputArea(SDL_Window *window)
{
if (SDL_IME_UpdateTextInputArea_Real) {
SDL_IME_UpdateTextInputArea_Real(window);
}
}
void SDL_IME_PumpEvents(void)
{
if (SDL_IME_PumpEvents_Real) {
SDL_IME_PumpEvents_Real();
}
}

35
thirdparty/sdl/core/linux/SDL_ime.h vendored Normal file
View File

@@ -0,0 +1,35 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_ime_h_
#define SDL_ime_h_
#include "SDL_internal.h"
extern bool SDL_IME_Init(void);
extern void SDL_IME_Quit(void);
extern void SDL_IME_SetFocus(bool focused);
extern void SDL_IME_Reset(void);
extern bool SDL_IME_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down);
extern void SDL_IME_UpdateTextInputArea(SDL_Window *window);
extern void SDL_IME_PumpEvents(void);
#endif // SDL_ime_h_

View File

@@ -0,0 +1,156 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_dbus.h"
#include "SDL_system_theme.h"
//#include "../../video/SDL_sysvideo.h"
#include <unistd.h>
#define PORTAL_DESTINATION "org.freedesktop.portal.Desktop"
#define PORTAL_PATH "/org/freedesktop/portal/desktop"
#define PORTAL_INTERFACE "org.freedesktop.portal.Settings"
#define PORTAL_METHOD "Read"
#define SIGNAL_INTERFACE "org.freedesktop.portal.Settings"
#define SIGNAL_NAMESPACE "org.freedesktop.appearance"
#define SIGNAL_NAME "SettingChanged"
#define SIGNAL_KEY "color-scheme"
typedef struct SystemThemeData
{
SDL_DBusContext *dbus;
SDL_SystemTheme theme;
} SystemThemeData;
static SystemThemeData system_theme_data;
static bool DBus_ExtractThemeVariant(DBusMessageIter *iter, SDL_SystemTheme *theme) {
SDL_DBusContext *dbus = system_theme_data.dbus;
Uint32 color_scheme;
DBusMessageIter variant_iter;
if (dbus->message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT)
return false;
dbus->message_iter_recurse(iter, &variant_iter);
if (dbus->message_iter_get_arg_type(&variant_iter) != DBUS_TYPE_UINT32)
return false;
dbus->message_iter_get_basic(&variant_iter, &color_scheme);
switch (color_scheme) {
case 0:
*theme = SDL_SYSTEM_THEME_UNKNOWN;
break;
case 1:
*theme = SDL_SYSTEM_THEME_DARK;
break;
case 2:
*theme = SDL_SYSTEM_THEME_LIGHT;
break;
}
return true;
}
static DBusHandlerResult DBus_MessageFilter(DBusConnection *conn, DBusMessage *msg, void *data) {
SDL_DBusContext *dbus = (SDL_DBusContext *)data;
if (dbus->message_is_signal(msg, SIGNAL_INTERFACE, SIGNAL_NAME)) {
DBusMessageIter signal_iter;
const char *namespace, *key;
dbus->message_iter_init(msg, &signal_iter);
// Check if the parameters are what we expect
if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_STRING)
goto not_our_signal;
dbus->message_iter_get_basic(&signal_iter, &namespace);
if (SDL_strcmp(SIGNAL_NAMESPACE, namespace) != 0)
goto not_our_signal;
if (!dbus->message_iter_next(&signal_iter))
goto not_our_signal;
if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_STRING)
goto not_our_signal;
dbus->message_iter_get_basic(&signal_iter, &key);
if (SDL_strcmp(SIGNAL_KEY, key) != 0)
goto not_our_signal;
if (!dbus->message_iter_next(&signal_iter))
goto not_our_signal;
if (!DBus_ExtractThemeVariant(&signal_iter, &system_theme_data.theme))
goto not_our_signal;
//SDL_SetSystemTheme(system_theme_data.theme);
return DBUS_HANDLER_RESULT_HANDLED;
}
not_our_signal:
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
bool SDL_SystemTheme_Init(void)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
DBusMessage *msg;
static const char *namespace = SIGNAL_NAMESPACE;
static const char *key = SIGNAL_KEY;
system_theme_data.theme = SDL_SYSTEM_THEME_UNKNOWN;
system_theme_data.dbus = dbus;
if (!dbus) {
return false;
}
msg = dbus->message_new_method_call(PORTAL_DESTINATION, PORTAL_PATH, PORTAL_INTERFACE, PORTAL_METHOD);
if (msg) {
if (dbus->message_append_args(msg, DBUS_TYPE_STRING, &namespace, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID)) {
DBusMessage *reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, 300, NULL);
if (reply) {
DBusMessageIter reply_iter, variant_outer_iter;
dbus->message_iter_init(reply, &reply_iter);
// The response has signature <<u>>
if (dbus->message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_VARIANT)
goto incorrect_type;
dbus->message_iter_recurse(&reply_iter, &variant_outer_iter);
if (!DBus_ExtractThemeVariant(&variant_outer_iter, &system_theme_data.theme))
goto incorrect_type;
incorrect_type:
dbus->message_unref(reply);
}
}
dbus->message_unref(msg);
}
dbus->bus_add_match(dbus->session_conn,
"type='signal', interface='"SIGNAL_INTERFACE"',"
"member='"SIGNAL_NAME"', arg0='"SIGNAL_NAMESPACE"',"
"arg1='"SIGNAL_KEY"'", NULL);
dbus->connection_add_filter(dbus->session_conn,
&DBus_MessageFilter, dbus, NULL);
dbus->connection_flush(dbus->session_conn);
return true;
}
SDL_SystemTheme SDL_SystemTheme_Get(void)
{
return system_theme_data.theme;
}

View File

@@ -0,0 +1,30 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_system_theme_h_
#define SDL_system_theme_h_
#include "SDL_internal.h"
extern bool SDL_SystemTheme_Init(void);
extern SDL_SystemTheme SDL_SystemTheme_Get(void);
#endif // SDL_system_theme_h_

View File

@@ -0,0 +1,345 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#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>
// RLIMIT_RTTIME requires kernel >= 2.6.25 and is in glibc >= 2.14
#ifndef RLIMIT_RTTIME
#define RLIMIT_RTTIME 15
#endif
// SCHED_RESET_ON_FORK is in kernel >= 2.6.32.
#ifndef SCHED_RESET_ON_FORK
#define SCHED_RESET_ON_FORK 0x40000000
#endif
#include "SDL_dbus.h"
#ifdef SDL_USE_LIBDBUS
// d-bus queries to org.freedesktop.RealtimeKit1.
#define RTKIT_DBUS_NODE "org.freedesktop.RealtimeKit1"
#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 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;
/*
* 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 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 set_rtkit_interface(void)
{
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_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 { // Fall back to the standard rtkit interface in all other cases.
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();
// Try getting minimum nice level: this is often greater than PRIO_MIN (-20).
if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(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_conn || !SDL_DBus_QueryPropertyOnConnection(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_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, 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)
{
// Following is an excerpt from rtkit README that outlines the requirements
// a thread must meet before making rtkit requests:
//
// * Only clients with RLIMIT_RTTIME set will get RT scheduling
//
// * RT scheduling will only be handed out to processes with
// SCHED_RESET_ON_FORK set to guarantee that the scheduling
// settings cannot 'leak' to child processes, thus making sure
// that 'RT fork bombs' cannot be used to bypass RLIMIT_RTTIME
// and take the system down.
//
// * Limits are enforced on all user controllable resources, only
// a maximum number of users, processes, threads can request RT
// scheduling at the same time.
//
// * Only a limited number of threads may be made RT in a
// specific time frame.
//
// * Client authorization is verified with PolicyKit
int err;
struct rlimit rlimit;
int nLimit = RLIMIT_RTTIME;
pid_t nPid = 0; // self
int nSchedPolicy = sched_getscheduler(nPid) | SCHED_RESET_ON_FORK;
struct sched_param schedParam;
SDL_zero(schedParam);
// Requirement #1: Set RLIMIT_RTTIME
err = getrlimit(nLimit, &rlimit);
if (err) {
return false;
}
// Current rtkit allows a max of 200ms right now
rlimit.rlim_max = rtkit_max_rttime_usec;
rlimit.rlim_cur = rlimit.rlim_max / 2;
err = setrlimit(nLimit, &rlimit);
if (err) {
return false;
}
// Requirement #2: Add SCHED_RESET_ON_FORK to the scheduler policy
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,
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;
}
// We always perform the thread state changes necessary for rtkit.
// This wastes some system calls if the state is already set but
// typically code sets a thread priority and leaves it so it's
// not expected that this wasted effort will be an issue.
// We also do not quit if this fails, we let the rtkit request
// go through to determine whether it really needs to fail or not.
rtkit_initialize_realtime_thread();
if (!dbus_conn || !SDL_DBus_CallMethodOnConnection(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 false;
}
return true;
}
#else
#define rtkit_max_realtime_priority 99
#endif // dbus
#endif // threads
// this is a public symbol, so it has to exist even if threads are disabled.
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
/* Note that this fails you most likely:
* Have your process's scheduler incorrectly configured.
See the requirements at:
http://git.0pointer.net/rtkit.git/tree/README#n16
* Encountered dbus/polkit security restrictions. Note
that the RealtimeKit1 dbus endpoint is inaccessible
over ssh connections for most common distro configs.
You might want to check your local config for details:
/usr/share/polkit-1/actions/org.freedesktop.RealtimeKit1.policy
README and sample code at: http://git.0pointer.net/rtkit.git
*/
if (rtkit_setpriority_nice((pid_t)threadID, priority)) {
return true;
}
#endif
return SDL_SetError("setpriority() failed");
#endif
}
// this is a public symbol, so it has to exist even if threads are disabled.
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
/* Note that this fails you most likely:
* Have your process's scheduler incorrectly configured.
See the requirements at:
http://git.0pointer.net/rtkit.git/tree/README#n16
* Encountered dbus/polkit security restrictions. Note
that the RealtimeKit1 dbus endpoint is inaccessible
over ssh connections for most common distro configs.
You might want to check your local config for details:
/usr/share/polkit-1/actions/org.freedesktop.RealtimeKit1.policy
README and sample code at: http://git.0pointer.net/rtkit.git
*/
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 // SDL_PLATFORM_LINUX

597
thirdparty/sdl/core/linux/SDL_udev.c vendored Normal file
View File

@@ -0,0 +1,597 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
/*
* To list the properties of a device, try something like:
* udevadm info -a -n snd/hwC0D0 (for a sound card)
* udevadm info --query=all -n input/event3 (for a keyboard, mouse, etc)
* udevadm info --query=property -n input/event2
*/
#include "SDL_udev.h"
#ifdef SDL_USE_LIBUDEV
#include <linux/input.h>
#include <sys/stat.h>
#include "SDL_evdev_capabilities.h"
#include "../unix/SDL_poll.h"
static const char *SDL_UDEV_LIBS[] = { "libudev.so.1", "libudev.so.0" };
SDL_UDEV_PrivateData *SDL_UDEV_PrivateData_this = NULL;
#define _this SDL_UDEV_PrivateData_this
static bool SDL_UDEV_load_sym(const char *fn, void **addr);
static bool SDL_UDEV_load_syms(void);
static bool SDL_UDEV_hotplug_update_available(void);
static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len);
static int guess_device_class(struct udev_device *dev);
static int device_class(struct udev_device *dev);
static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev);
static bool SDL_UDEV_load_sym(const char *fn, void **addr)
{
*addr = SDL_LoadFunction(_this->udev_handle, fn);
if (!*addr) {
// Don't call SDL_SetError(): SDL_LoadFunction already did.
return false;
}
return true;
}
static bool SDL_UDEV_load_syms(void)
{
/* cast funcs to char* first, to please GCC's strict aliasing rules. */
#define SDL_UDEV_SYM(x) \
if (!SDL_UDEV_load_sym(#x, (void **)(char *)&_this->syms.x)) \
return false
SDL_UDEV_SYM(udev_device_get_action);
SDL_UDEV_SYM(udev_device_get_devnode);
SDL_UDEV_SYM(udev_device_get_syspath);
SDL_UDEV_SYM(udev_device_get_subsystem);
SDL_UDEV_SYM(udev_device_get_parent_with_subsystem_devtype);
SDL_UDEV_SYM(udev_device_get_property_value);
SDL_UDEV_SYM(udev_device_get_sysattr_value);
SDL_UDEV_SYM(udev_device_new_from_syspath);
SDL_UDEV_SYM(udev_device_unref);
SDL_UDEV_SYM(udev_enumerate_add_match_property);
SDL_UDEV_SYM(udev_enumerate_add_match_subsystem);
SDL_UDEV_SYM(udev_enumerate_get_list_entry);
SDL_UDEV_SYM(udev_enumerate_new);
SDL_UDEV_SYM(udev_enumerate_scan_devices);
SDL_UDEV_SYM(udev_enumerate_unref);
SDL_UDEV_SYM(udev_list_entry_get_name);
SDL_UDEV_SYM(udev_list_entry_get_next);
SDL_UDEV_SYM(udev_monitor_enable_receiving);
SDL_UDEV_SYM(udev_monitor_filter_add_match_subsystem_devtype);
SDL_UDEV_SYM(udev_monitor_get_fd);
SDL_UDEV_SYM(udev_monitor_new_from_netlink);
SDL_UDEV_SYM(udev_monitor_receive_device);
SDL_UDEV_SYM(udev_monitor_unref);
SDL_UDEV_SYM(udev_new);
SDL_UDEV_SYM(udev_unref);
SDL_UDEV_SYM(udev_device_new_from_devnum);
SDL_UDEV_SYM(udev_device_get_devnum);
#undef SDL_UDEV_SYM
return true;
}
static bool SDL_UDEV_hotplug_update_available(void)
{
if (_this->udev_mon) {
const int fd = _this->syms.udev_monitor_get_fd(_this->udev_mon);
if (SDL_IOReady(fd, SDL_IOR_READ, 0)) {
return true;
}
}
return false;
}
bool SDL_UDEV_Init(void)
{
if (!_this) {
_this = (SDL_UDEV_PrivateData *)SDL_calloc(1, sizeof(*_this));
if (!_this) {
return false;
}
if (!SDL_UDEV_LoadLibrary()) {
SDL_UDEV_Quit();
return false;
}
/* Set up udev monitoring
* Listen for input devices (mouse, keyboard, joystick, etc) and sound devices
*/
_this->udev = _this->syms.udev_new();
if (!_this->udev) {
SDL_UDEV_Quit();
return SDL_SetError("udev_new() failed");
}
_this->udev_mon = _this->syms.udev_monitor_new_from_netlink(_this->udev, "udev");
if (!_this->udev_mon) {
SDL_UDEV_Quit();
return SDL_SetError("udev_monitor_new_from_netlink() failed");
}
_this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "input", NULL);
_this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "sound", NULL);
_this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "video4linux", NULL);
_this->syms.udev_monitor_enable_receiving(_this->udev_mon);
// Do an initial scan of existing devices
SDL_UDEV_Scan();
}
_this->ref_count += 1;
return true;
}
void SDL_UDEV_Quit(void)
{
if (!_this) {
return;
}
_this->ref_count -= 1;
if (_this->ref_count < 1) {
if (_this->udev_mon) {
_this->syms.udev_monitor_unref(_this->udev_mon);
_this->udev_mon = NULL;
}
if (_this->udev) {
_this->syms.udev_unref(_this->udev);
_this->udev = NULL;
}
// Remove existing devices
while (_this->first) {
SDL_UDEV_CallbackList *item = _this->first;
_this->first = _this->first->next;
SDL_free(item);
}
SDL_UDEV_UnloadLibrary();
SDL_free(_this);
_this = NULL;
}
}
bool SDL_UDEV_Scan(void)
{
struct udev_enumerate *enumerate = NULL;
struct udev_list_entry *devs = NULL;
struct udev_list_entry *item = NULL;
if (!_this) {
return true;
}
enumerate = _this->syms.udev_enumerate_new(_this->udev);
if (!enumerate) {
SDL_UDEV_Quit();
return SDL_SetError("udev_enumerate_new() failed");
}
_this->syms.udev_enumerate_add_match_subsystem(enumerate, "input");
_this->syms.udev_enumerate_add_match_subsystem(enumerate, "sound");
_this->syms.udev_enumerate_add_match_subsystem(enumerate, "video4linux");
_this->syms.udev_enumerate_scan_devices(enumerate);
devs = _this->syms.udev_enumerate_get_list_entry(enumerate);
for (item = devs; item; item = _this->syms.udev_list_entry_get_next(item)) {
const char *path = _this->syms.udev_list_entry_get_name(item);
struct udev_device *dev = _this->syms.udev_device_new_from_syspath(_this->udev, path);
if (dev) {
device_event(SDL_UDEV_DEVICEADDED, dev);
_this->syms.udev_device_unref(dev);
}
}
_this->syms.udev_enumerate_unref(enumerate);
return true;
}
bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class)
{
struct stat statbuf;
char type;
struct udev_device *dev;
const char* val;
int class_temp;
if (!_this) {
return false;
}
if (stat(device_path, &statbuf) == -1) {
return false;
}
if (S_ISBLK(statbuf.st_mode)) {
type = 'b';
}
else if (S_ISCHR(statbuf.st_mode)) {
type = 'c';
}
else {
return false;
}
dev = _this->syms.udev_device_new_from_devnum(_this->udev, type, statbuf.st_rdev);
if (!dev) {
return false;
}
val = _this->syms.udev_device_get_property_value(dev, "ID_VENDOR_ID");
if (val) {
*vendor = (Uint16)SDL_strtol(val, NULL, 16);
}
val = _this->syms.udev_device_get_property_value(dev, "ID_MODEL_ID");
if (val) {
*product = (Uint16)SDL_strtol(val, NULL, 16);
}
val = _this->syms.udev_device_get_property_value(dev, "ID_REVISION");
if (val) {
*version = (Uint16)SDL_strtol(val, NULL, 16);
}
class_temp = device_class(dev);
if (class_temp) {
*class = class_temp;
}
_this->syms.udev_device_unref(dev);
return true;
}
void SDL_UDEV_UnloadLibrary(void)
{
if (!_this) {
return;
}
if (_this->udev_handle) {
SDL_UnloadObject(_this->udev_handle);
_this->udev_handle = NULL;
}
}
bool SDL_UDEV_LoadLibrary(void)
{
bool result = true;
if (!_this) {
return SDL_SetError("UDEV not initialized");
}
// See if there is a udev library already loaded
if (SDL_UDEV_load_syms()) {
return true;
}
#ifdef SDL_UDEV_DYNAMIC
// Check for the build environment's libudev first
if (!_this->udev_handle) {
_this->udev_handle = SDL_LoadObject(SDL_UDEV_DYNAMIC);
if (_this->udev_handle) {
result = SDL_UDEV_load_syms();
if (!result) {
SDL_UDEV_UnloadLibrary();
}
}
}
#endif
if (!_this->udev_handle) {
for (int i = 0; i < SDL_arraysize(SDL_UDEV_LIBS); i++) {
_this->udev_handle = SDL_LoadObject(SDL_UDEV_LIBS[i]);
if (_this->udev_handle) {
result = SDL_UDEV_load_syms();
if (!result) {
SDL_UDEV_UnloadLibrary();
} else {
break;
}
}
}
if (!_this->udev_handle) {
result = false;
// Don't call SDL_SetError(): SDL_LoadObject already did.
}
}
return result;
}
static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len)
{
const char *value;
char text[4096];
char *word;
int i;
unsigned long v;
SDL_memset(bitmask, 0, bitmask_len * sizeof(*bitmask));
value = _this->syms.udev_device_get_sysattr_value(pdev, attr);
if (!value) {
return;
}
SDL_strlcpy(text, value, sizeof(text));
i = 0;
while ((word = SDL_strrchr(text, ' ')) != NULL) {
v = SDL_strtoul(word + 1, NULL, 16);
if (i < bitmask_len) {
bitmask[i] = v;
}
++i;
*word = '\0';
}
v = SDL_strtoul(text, NULL, 16);
if (i < bitmask_len) {
bitmask[i] = v;
}
}
static int guess_device_class(struct udev_device *dev)
{
struct udev_device *pdev;
unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)];
unsigned long bitmask_ev[NBITS(EV_MAX)];
unsigned long bitmask_abs[NBITS(ABS_MAX)];
unsigned long bitmask_key[NBITS(KEY_MAX)];
unsigned long bitmask_rel[NBITS(REL_MAX)];
/* walk up the parental chain until we find the real input device; the
* argument is very likely a subdevice of this, like eventN */
pdev = dev;
while (pdev && !_this->syms.udev_device_get_sysattr_value(pdev, "capabilities/ev")) {
pdev = _this->syms.udev_device_get_parent_with_subsystem_devtype(pdev, "input", NULL);
}
if (!pdev) {
return 0;
}
get_caps(dev, pdev, "properties", bitmask_props, SDL_arraysize(bitmask_props));
get_caps(dev, pdev, "capabilities/ev", bitmask_ev, SDL_arraysize(bitmask_ev));
get_caps(dev, pdev, "capabilities/abs", bitmask_abs, SDL_arraysize(bitmask_abs));
get_caps(dev, pdev, "capabilities/rel", bitmask_rel, SDL_arraysize(bitmask_rel));
get_caps(dev, pdev, "capabilities/key", bitmask_key, SDL_arraysize(bitmask_key));
return SDL_EVDEV_GuessDeviceClass(&bitmask_props[0],
&bitmask_ev[0],
&bitmask_abs[0],
&bitmask_key[0],
&bitmask_rel[0]);
}
static int device_class(struct udev_device *dev)
{
const char *subsystem;
const char *val = NULL;
int devclass = 0;
subsystem = _this->syms.udev_device_get_subsystem(dev);
if (!subsystem) {
return 0;
}
if (SDL_strcmp(subsystem, "sound") == 0) {
devclass = SDL_UDEV_DEVICE_SOUND;
} else if (SDL_strcmp(subsystem, "video4linux") == 0) {
val = _this->syms.udev_device_get_property_value(dev, "ID_V4L_CAPABILITIES");
if (val && SDL_strcasestr(val, "capture")) {
devclass = SDL_UDEV_DEVICE_VIDEO_CAPTURE;
}
} else if (SDL_strcmp(subsystem, "input") == 0) {
// udev rules reference: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK");
if (val && SDL_strcmp(val, "1") == 0) {
devclass |= SDL_UDEV_DEVICE_JOYSTICK;
}
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_ACCELEROMETER");
if (val && SDL_strcmp(val, "1") == 0) {
devclass |= SDL_UDEV_DEVICE_ACCELEROMETER;
}
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_MOUSE");
if (val && SDL_strcmp(val, "1") == 0) {
devclass |= SDL_UDEV_DEVICE_MOUSE;
}
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN");
if (val && SDL_strcmp(val, "1") == 0) {
devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN;
}
/* The undocumented rule is:
- All devices with keys get ID_INPUT_KEY
- From this subset, if they have ESC, numbers, and Q to D, it also gets ID_INPUT_KEYBOARD
Ref: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c#n183
*/
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEY");
if (val && SDL_strcmp(val, "1") == 0) {
devclass |= SDL_UDEV_DEVICE_HAS_KEYS;
}
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD");
if (val && SDL_strcmp(val, "1") == 0) {
devclass |= SDL_UDEV_DEVICE_KEYBOARD;
}
if (devclass == 0) {
// Fall back to old style input classes
val = _this->syms.udev_device_get_property_value(dev, "ID_CLASS");
if (val) {
if (SDL_strcmp(val, "joystick") == 0) {
devclass = SDL_UDEV_DEVICE_JOYSTICK;
} else if (SDL_strcmp(val, "mouse") == 0) {
devclass = SDL_UDEV_DEVICE_MOUSE;
} else if (SDL_strcmp(val, "kbd") == 0) {
devclass = SDL_UDEV_DEVICE_HAS_KEYS | SDL_UDEV_DEVICE_KEYBOARD;
}
} else {
// We could be linked with libudev on a system that doesn't have udev running
devclass = guess_device_class(dev);
}
}
}
return devclass;
}
static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev)
{
int devclass = 0;
const char *path;
SDL_UDEV_CallbackList *item;
path = _this->syms.udev_device_get_devnode(dev);
if (!path) {
return;
}
if (type == SDL_UDEV_DEVICEADDED) {
devclass = device_class(dev);
if (!devclass) {
return;
}
} else {
// The device has been removed, the class isn't available
}
// Process callbacks
for (item = _this->first; item; item = item->next) {
item->callback(type, devclass, path);
}
}
void SDL_UDEV_Poll(void)
{
struct udev_device *dev = NULL;
const char *action = NULL;
if (!_this) {
return;
}
while (SDL_UDEV_hotplug_update_available()) {
dev = _this->syms.udev_monitor_receive_device(_this->udev_mon);
if (!dev) {
break;
}
action = _this->syms.udev_device_get_action(dev);
if (action) {
if (SDL_strcmp(action, "add") == 0) {
device_event(SDL_UDEV_DEVICEADDED, dev);
} else if (SDL_strcmp(action, "remove") == 0) {
device_event(SDL_UDEV_DEVICEREMOVED, dev);
}
}
_this->syms.udev_device_unref(dev);
}
}
bool SDL_UDEV_AddCallback(SDL_UDEV_Callback cb)
{
SDL_UDEV_CallbackList *item;
item = (SDL_UDEV_CallbackList *)SDL_calloc(1, sizeof(SDL_UDEV_CallbackList));
if (!item) {
return false;
}
item->callback = cb;
if (!_this->last) {
_this->first = _this->last = item;
} else {
_this->last->next = item;
_this->last = item;
}
return true;
}
void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb)
{
SDL_UDEV_CallbackList *item;
SDL_UDEV_CallbackList *prev = NULL;
if (!_this) {
return;
}
for (item = _this->first; item; item = item->next) {
// found it, remove it.
if (item->callback == cb) {
if (prev) {
prev->next = item->next;
} else {
SDL_assert(_this->first == item);
_this->first = item->next;
}
if (item == _this->last) {
_this->last = prev;
}
SDL_free(item);
return;
}
prev = item;
}
}
const SDL_UDEV_Symbols *SDL_UDEV_GetUdevSyms(void)
{
if (!SDL_UDEV_Init()) {
SDL_SetError("Could not initialize UDEV");
return NULL;
}
return &_this->syms;
}
void SDL_UDEV_ReleaseUdevSyms(void)
{
SDL_UDEV_Quit();
}
#endif // SDL_USE_LIBUDEV

114
thirdparty/sdl/core/linux/SDL_udev.h vendored Normal file
View File

@@ -0,0 +1,114 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_udev_h_
#define SDL_udev_h_
#if defined(HAVE_LIBUDEV_H) && defined(HAVE_LINUX_INPUT_H)
#ifndef SDL_USE_LIBUDEV
#define SDL_USE_LIBUDEV 1
#endif
//#include <libudev.h>
#include "thirdparty/linuxbsd_headers/udev/libudev.h"
#include <sys/time.h>
#include <sys/types.h>
/**
* Device type
*/
typedef enum
{
SDL_UDEV_DEVICEADDED = 1,
SDL_UDEV_DEVICEREMOVED
} SDL_UDEV_deviceevent;
typedef void (*SDL_UDEV_Callback)(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath);
typedef struct SDL_UDEV_CallbackList
{
SDL_UDEV_Callback callback;
struct SDL_UDEV_CallbackList *next;
} SDL_UDEV_CallbackList;
typedef struct SDL_UDEV_Symbols
{
const char *(*udev_device_get_action)(struct udev_device *);
const char *(*udev_device_get_devnode)(struct udev_device *);
const char *(*udev_device_get_syspath)(struct udev_device *);
const char *(*udev_device_get_subsystem)(struct udev_device *);
struct udev_device *(*udev_device_get_parent_with_subsystem_devtype)(struct udev_device *udev_device, const char *subsystem, const char *devtype);
const char *(*udev_device_get_property_value)(struct udev_device *, const char *);
const char *(*udev_device_get_sysattr_value)(struct udev_device *udev_device, const char *sysattr);
struct udev_device *(*udev_device_new_from_syspath)(struct udev *, const char *);
void (*udev_device_unref)(struct udev_device *);
int (*udev_enumerate_add_match_property)(struct udev_enumerate *, const char *, const char *);
int (*udev_enumerate_add_match_subsystem)(struct udev_enumerate *, const char *);
struct udev_list_entry *(*udev_enumerate_get_list_entry)(struct udev_enumerate *);
struct udev_enumerate *(*udev_enumerate_new)(struct udev *);
int (*udev_enumerate_scan_devices)(struct udev_enumerate *);
void (*udev_enumerate_unref)(struct udev_enumerate *);
const char *(*udev_list_entry_get_name)(struct udev_list_entry *);
struct udev_list_entry *(*udev_list_entry_get_next)(struct udev_list_entry *);
int (*udev_monitor_enable_receiving)(struct udev_monitor *);
int (*udev_monitor_filter_add_match_subsystem_devtype)(struct udev_monitor *, const char *, const char *);
int (*udev_monitor_get_fd)(struct udev_monitor *);
struct udev_monitor *(*udev_monitor_new_from_netlink)(struct udev *, const char *);
struct udev_device *(*udev_monitor_receive_device)(struct udev_monitor *);
void (*udev_monitor_unref)(struct udev_monitor *);
struct udev *(*udev_new)(void);
void (*udev_unref)(struct udev *);
struct udev_device *(*udev_device_new_from_devnum)(struct udev *udev, char type, dev_t devnum);
dev_t (*udev_device_get_devnum)(struct udev_device *udev_device);
} SDL_UDEV_Symbols;
typedef struct SDL_UDEV_PrivateData
{
const char *udev_library;
SDL_SharedObject *udev_handle;
struct udev *udev;
struct udev_monitor *udev_mon;
int ref_count;
SDL_UDEV_CallbackList *first, *last;
// Function pointers
SDL_UDEV_Symbols syms;
} SDL_UDEV_PrivateData;
extern bool SDL_UDEV_Init(void);
extern void SDL_UDEV_Quit(void);
extern void SDL_UDEV_UnloadLibrary(void);
extern bool SDL_UDEV_LoadLibrary(void);
extern void SDL_UDEV_Poll(void);
extern bool SDL_UDEV_Scan(void);
extern bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class);
extern bool SDL_UDEV_AddCallback(SDL_UDEV_Callback cb);
extern void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb);
extern const SDL_UDEV_Symbols *SDL_UDEV_GetUdevSyms(void);
extern void SDL_UDEV_ReleaseUdevSyms(void);
#endif // HAVE_LIBUDEV_H && HAVE_LINUX_INPUT_H
#endif // SDL_udev_h_