Path: blob/master/thirdparty/sdl/core/linux/SDL_dbus.c
10279 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"21#include "SDL_dbus.h"22#include "../../stdlib/SDL_vacopy.h"2324#ifdef SDL_USE_LIBDBUS25// we never link directly to libdbus.26static const char *dbus_library = "libdbus-1.so.3";27static SDL_SharedObject *dbus_handle = NULL;28static char *inhibit_handle = NULL;29static unsigned int screensaver_cookie = 0;30static SDL_DBusContext dbus;3132static bool LoadDBUSSyms(void)33{34#define SDL_DBUS_SYM2_OPTIONAL(TYPE, x, y) \35dbus.x = (TYPE)SDL_LoadFunction(dbus_handle, #y)3637#define SDL_DBUS_SYM2(TYPE, x, y) \38if (!(dbus.x = (TYPE)SDL_LoadFunction(dbus_handle, #y))) \39return false4041#define SDL_DBUS_SYM_OPTIONAL(TYPE, x) \42SDL_DBUS_SYM2_OPTIONAL(TYPE, x, dbus_##x)4344#define SDL_DBUS_SYM(TYPE, x) \45SDL_DBUS_SYM2(TYPE, x, dbus_##x)4647SDL_DBUS_SYM(DBusConnection *(*)(DBusBusType, DBusError *), bus_get_private);48SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusError *), bus_register);49SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_add_match);50SDL_DBUS_SYM(DBusConnection *(*)(const char *, DBusError *), connection_open_private);51SDL_DBUS_SYM(void (*)(DBusConnection *, dbus_bool_t), connection_set_exit_on_disconnect);52SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *), connection_get_is_connected);53SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusHandleMessageFunction, void *, DBusFreeFunction), connection_add_filter);54SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusHandleMessageFunction, void *), connection_remove_filter);55SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, const char *, const DBusObjectPathVTable *, void *, DBusError *), connection_try_register_object_path);56SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusMessage *, dbus_uint32_t *), connection_send);57SDL_DBUS_SYM(DBusMessage *(*)(DBusConnection *, DBusMessage *, int, DBusError *), connection_send_with_reply_and_block);58SDL_DBUS_SYM(void (*)(DBusConnection *), connection_close);59SDL_DBUS_SYM(void (*)(DBusConnection *), connection_ref);60SDL_DBUS_SYM(void (*)(DBusConnection *), connection_unref);61SDL_DBUS_SYM(void (*)(DBusConnection *), connection_flush);62SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write);63SDL_DBUS_SYM(DBusDispatchStatus (*)(DBusConnection *), connection_dispatch);64SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *, const char *), message_is_signal);65SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *), message_has_path);66SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *, const char *), message_new_method_call);67SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, ...), message_append_args);68SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, va_list), message_append_args_valist);69SDL_DBUS_SYM(void (*)(DBusMessage *, DBusMessageIter *), message_iter_init_append);70SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, int, const char *, DBusMessageIter *), message_iter_open_container);71SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, int, const void *), message_iter_append_basic);72SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, DBusMessageIter *), message_iter_close_container);73SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusError *, int, ...), message_get_args);74SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusError *, int, va_list), message_get_args_valist);75SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusMessageIter *), message_iter_init);76SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *), message_iter_next);77SDL_DBUS_SYM(void (*)(DBusMessageIter *, void *), message_iter_get_basic);78SDL_DBUS_SYM(int (*)(DBusMessageIter *), message_iter_get_arg_type);79SDL_DBUS_SYM(void (*)(DBusMessageIter *, DBusMessageIter *), message_iter_recurse);80SDL_DBUS_SYM(void (*)(DBusMessage *), message_unref);81SDL_DBUS_SYM(dbus_bool_t (*)(void), threads_init_default);82SDL_DBUS_SYM(void (*)(DBusError *), error_init);83SDL_DBUS_SYM(dbus_bool_t (*)(const DBusError *), error_is_set);84SDL_DBUS_SYM(void (*)(DBusError *), error_free);85SDL_DBUS_SYM(char *(*)(void), get_local_machine_id);86SDL_DBUS_SYM_OPTIONAL(char *(*)(DBusError *), try_get_local_machine_id);87SDL_DBUS_SYM(void (*)(void *), free);88SDL_DBUS_SYM(void (*)(char **), free_string_array);89SDL_DBUS_SYM(void (*)(void), shutdown);9091#undef SDL_DBUS_SYM92#undef SDL_DBUS_SYM29394return true;95}9697static void UnloadDBUSLibrary(void)98{99#ifdef SOWRAP_ENABLED // Godot build system constant100if (dbus_handle) {101SDL_UnloadObject(dbus_handle);102dbus_handle = NULL;103}104#endif105}106107static bool LoadDBUSLibrary(void)108{109bool result = true;110#ifdef SOWRAP_ENABLED // Godot build system constant111if (!dbus_handle) {112dbus_handle = SDL_LoadObject(dbus_library);113if (!dbus_handle) {114result = false;115// Don't call SDL_SetError(): SDL_LoadObject already did.116} else {117result = LoadDBUSSyms();118if (!result) {119UnloadDBUSLibrary();120}121}122}123#else124result = LoadDBUSSyms();125#endif126return result;127}128129static SDL_InitState dbus_init;130131void SDL_DBus_Init(void)132{133static bool is_dbus_available = true;134135if (!is_dbus_available) {136return; // don't keep trying if this fails.137}138139if (!SDL_ShouldInit(&dbus_init)) {140return;141}142143if (!LoadDBUSLibrary()) {144goto error;145}146147if (!dbus.threads_init_default()) {148goto error;149}150151DBusError err;152dbus.error_init(&err);153// session bus is required154155dbus.session_conn = dbus.bus_get_private(DBUS_BUS_SESSION, &err);156if (dbus.error_is_set(&err)) {157dbus.error_free(&err);158goto error;159}160dbus.connection_set_exit_on_disconnect(dbus.session_conn, 0);161162// system bus is optional163dbus.system_conn = dbus.bus_get_private(DBUS_BUS_SYSTEM, &err);164if (!dbus.error_is_set(&err)) {165dbus.connection_set_exit_on_disconnect(dbus.system_conn, 0);166}167168dbus.error_free(&err);169SDL_SetInitialized(&dbus_init, true);170return;171172error:173is_dbus_available = false;174SDL_SetInitialized(&dbus_init, true);175SDL_DBus_Quit();176}177178void SDL_DBus_Quit(void)179{180if (!SDL_ShouldQuit(&dbus_init)) {181return;182}183184if (dbus.system_conn) {185dbus.connection_close(dbus.system_conn);186dbus.connection_unref(dbus.system_conn);187}188if (dbus.session_conn) {189dbus.connection_close(dbus.session_conn);190dbus.connection_unref(dbus.session_conn);191}192193if (SDL_GetHintBoolean(SDL_HINT_SHUTDOWN_DBUS_ON_QUIT, false)) {194if (dbus.shutdown) {195dbus.shutdown();196}197198UnloadDBUSLibrary();199} else {200/* Leaving libdbus loaded when skipping dbus_shutdown() avoids201* spurious leak warnings from LeakSanitizer on internal D-Bus202* allocations that would be freed by dbus_shutdown(). */203dbus_handle = NULL;204}205206SDL_zero(dbus);207if (inhibit_handle) {208SDL_free(inhibit_handle);209inhibit_handle = NULL;210}211212SDL_SetInitialized(&dbus_init, false);213}214215SDL_DBusContext *SDL_DBus_GetContext(void)216{217if (!dbus_handle || !dbus.session_conn) {218SDL_DBus_Init();219}220221return (dbus_handle && dbus.session_conn) ? &dbus : NULL;222}223224static bool SDL_DBus_CallMethodInternal(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, va_list ap)225{226bool result = false;227228if (conn) {229DBusMessage *msg = dbus.message_new_method_call(node, path, interface, method);230if (msg) {231int firstarg;232va_list ap_reply;233va_copy(ap_reply, ap); // copy the arg list so we don't compete with D-Bus for it234firstarg = va_arg(ap, int);235if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_append_args_valist(msg, firstarg, ap)) {236DBusMessage *reply = dbus.connection_send_with_reply_and_block(conn, msg, 300, NULL);237if (reply) {238// skip any input args, get to output args.239while ((firstarg = va_arg(ap_reply, int)) != DBUS_TYPE_INVALID) {240// we assume D-Bus already validated all this.241{242void *dumpptr = va_arg(ap_reply, void *);243(void)dumpptr;244}245if (firstarg == DBUS_TYPE_ARRAY) {246{247const int dumpint = va_arg(ap_reply, int);248(void)dumpint;249}250}251}252firstarg = va_arg(ap_reply, int);253if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_get_args_valist(reply, NULL, firstarg, ap_reply)) {254result = true;255}256dbus.message_unref(reply);257}258}259va_end(ap_reply);260dbus.message_unref(msg);261}262}263264return result;265}266267bool SDL_DBus_CallMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...)268{269bool result;270va_list ap;271va_start(ap, method);272result = SDL_DBus_CallMethodInternal(conn, node, path, interface, method, ap);273va_end(ap);274return result;275}276277bool SDL_DBus_CallMethod(const char *node, const char *path, const char *interface, const char *method, ...)278{279bool result;280va_list ap;281va_start(ap, method);282result = SDL_DBus_CallMethodInternal(dbus.session_conn, node, path, interface, method, ap);283va_end(ap);284return result;285}286287static bool SDL_DBus_CallVoidMethodInternal(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, va_list ap)288{289bool result = false;290291if (conn) {292DBusMessage *msg = dbus.message_new_method_call(node, path, interface, method);293if (msg) {294int firstarg = va_arg(ap, int);295if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_append_args_valist(msg, firstarg, ap)) {296if (dbus.connection_send(conn, msg, NULL)) {297dbus.connection_flush(conn);298result = true;299}300}301302dbus.message_unref(msg);303}304}305306return result;307}308309static bool SDL_DBus_CallWithBasicReply(DBusConnection *conn, DBusMessage *msg, const int expectedtype, void *result)310{311bool retval = false;312313DBusMessage *reply = dbus.connection_send_with_reply_and_block(conn, msg, 300, NULL);314if (reply) {315DBusMessageIter iter, actual_iter;316dbus.message_iter_init(reply, &iter);317if (dbus.message_iter_get_arg_type(&iter) == DBUS_TYPE_VARIANT) {318dbus.message_iter_recurse(&iter, &actual_iter);319} else {320actual_iter = iter;321}322323if (dbus.message_iter_get_arg_type(&actual_iter) == expectedtype) {324dbus.message_iter_get_basic(&actual_iter, result);325retval = true;326}327328dbus.message_unref(reply);329}330331return retval;332}333334bool SDL_DBus_CallVoidMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...)335{336bool result;337va_list ap;338va_start(ap, method);339result = SDL_DBus_CallVoidMethodInternal(conn, node, path, interface, method, ap);340va_end(ap);341return result;342}343344bool SDL_DBus_CallVoidMethod(const char *node, const char *path, const char *interface, const char *method, ...)345{346bool result;347va_list ap;348va_start(ap, method);349result = SDL_DBus_CallVoidMethodInternal(dbus.session_conn, node, path, interface, method, ap);350va_end(ap);351return result;352}353354bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result)355{356bool retval = false;357358if (conn) {359DBusMessage *msg = dbus.message_new_method_call(node, path, "org.freedesktop.DBus.Properties", "Get");360if (msg) {361if (dbus.message_append_args(msg, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) {362retval = SDL_DBus_CallWithBasicReply(conn, msg, expectedtype, result);363}364dbus.message_unref(msg);365}366}367368return retval;369}370371bool SDL_DBus_QueryProperty(const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result)372{373return SDL_DBus_QueryPropertyOnConnection(dbus.session_conn, node, path, interface, property, expectedtype, result);374}375376void SDL_DBus_ScreensaverTickle(void)377{378if (screensaver_cookie == 0 && !inhibit_handle) { // no need to tickle if we're inhibiting.379// 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.380SDL_DBus_CallVoidMethod("org.gnome.ScreenSaver", "/org/gnome/ScreenSaver", "org.gnome.ScreenSaver", "SimulateUserActivity", DBUS_TYPE_INVALID);381SDL_DBus_CallVoidMethod("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", "SimulateUserActivity", DBUS_TYPE_INVALID);382}383}384385static bool SDL_DBus_AppendDictWithKeysAndValues(DBusMessageIter *iterInit, const char **keys, const char **values, int count)386{387DBusMessageIter iterDict;388389if (!dbus.message_iter_open_container(iterInit, DBUS_TYPE_ARRAY, "{sv}", &iterDict)) {390goto failed;391}392393for (int i = 0; i < count; i++) {394DBusMessageIter iterEntry, iterValue;395const char *key = keys[i];396const char *value = values[i];397398if (!dbus.message_iter_open_container(&iterDict, DBUS_TYPE_DICT_ENTRY, NULL, &iterEntry)) {399goto failed;400}401402if (!dbus.message_iter_append_basic(&iterEntry, DBUS_TYPE_STRING, &key)) {403goto failed;404}405406if (!dbus.message_iter_open_container(&iterEntry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &iterValue)) {407goto failed;408}409410if (!dbus.message_iter_append_basic(&iterValue, DBUS_TYPE_STRING, &value)) {411goto failed;412}413414if (!dbus.message_iter_close_container(&iterEntry, &iterValue) || !dbus.message_iter_close_container(&iterDict, &iterEntry)) {415goto failed;416}417}418419if (!dbus.message_iter_close_container(iterInit, &iterDict)) {420goto failed;421}422423return true;424425failed:426/* message_iter_abandon_container_if_open() and message_iter_abandon_container() might be427* missing if libdbus is too old. Instead, we just return without cleaning up any eventual428* open container */429return false;430}431432static bool SDL_DBus_AppendDictWithKeyValue(DBusMessageIter *iterInit, const char *key, const char *value)433{434const char *keys[1];435const char *values[1];436437keys[0] = key;438values[0] = value;439return SDL_DBus_AppendDictWithKeysAndValues(iterInit, keys, values, 1);440}441442bool SDL_DBus_ScreensaverInhibit(bool inhibit)443{444const char *default_inhibit_reason = "Playing a game";445446if ((inhibit && (screensaver_cookie != 0 || inhibit_handle)) || (!inhibit && (screensaver_cookie == 0 && !inhibit_handle))) {447return true;448}449450if (!dbus.session_conn) {451/* We either lost connection to the session bus or were not able to452* load the D-Bus library at all. */453return false;454}455456if (SDL_GetSandbox() != SDL_SANDBOX_NONE) {457const char *bus_name = "org.freedesktop.portal.Desktop";458const char *path = "/org/freedesktop/portal/desktop";459const char *interface = "org.freedesktop.portal.Inhibit";460const char *window = ""; // As a future improvement we could gather the X11 XID or Wayland surface identifier461static const unsigned int INHIBIT_IDLE = 8; // Taken from the portal API reference462DBusMessageIter iterInit;463464if (inhibit) {465DBusMessage *msg;466bool result = false;467const char *key = "reason";468const char *reply = NULL;469const char *reason = SDL_GetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME);470if (!reason || !reason[0]) {471reason = default_inhibit_reason;472}473474msg = dbus.message_new_method_call(bus_name, path, interface, "Inhibit");475if (!msg) {476return false;477}478479if (!dbus.message_append_args(msg, DBUS_TYPE_STRING, &window, DBUS_TYPE_UINT32, &INHIBIT_IDLE, DBUS_TYPE_INVALID)) {480dbus.message_unref(msg);481return false;482}483484dbus.message_iter_init_append(msg, &iterInit);485486// a{sv}487if (!SDL_DBus_AppendDictWithKeyValue(&iterInit, key, reason)) {488dbus.message_unref(msg);489return false;490}491492if (SDL_DBus_CallWithBasicReply(dbus.session_conn, msg, DBUS_TYPE_OBJECT_PATH, &reply)) {493inhibit_handle = SDL_strdup(reply);494result = true;495}496497dbus.message_unref(msg);498return result;499} else {500if (!SDL_DBus_CallVoidMethod(bus_name, inhibit_handle, "org.freedesktop.portal.Request", "Close", DBUS_TYPE_INVALID)) {501return false;502}503SDL_free(inhibit_handle);504inhibit_handle = NULL;505}506} else {507const char *bus_name = "org.freedesktop.ScreenSaver";508const char *path = "/org/freedesktop/ScreenSaver";509const char *interface = "org.freedesktop.ScreenSaver";510511if (inhibit) {512const char *app = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);513const char *reason = SDL_GetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME);514if (!reason || !reason[0]) {515reason = default_inhibit_reason;516}517518if (!SDL_DBus_CallMethod(bus_name, path, interface, "Inhibit",519DBUS_TYPE_STRING, &app, DBUS_TYPE_STRING, &reason, DBUS_TYPE_INVALID,520DBUS_TYPE_UINT32, &screensaver_cookie, DBUS_TYPE_INVALID)) {521return false;522}523return (screensaver_cookie != 0);524} else {525if (!SDL_DBus_CallVoidMethod(bus_name, path, interface, "UnInhibit", DBUS_TYPE_UINT32, &screensaver_cookie, DBUS_TYPE_INVALID)) {526return false;527}528screensaver_cookie = 0;529}530}531532return true;533}534535void SDL_DBus_PumpEvents(void)536{537if (dbus.session_conn) {538dbus.connection_read_write(dbus.session_conn, 0);539540while (dbus.connection_dispatch(dbus.session_conn) == DBUS_DISPATCH_DATA_REMAINS) {541// Do nothing, actual work happens in DBus_MessageFilter542SDL_DelayNS(SDL_US_TO_NS(10));543}544}545}546547/*548* Get the machine ID if possible. Result must be freed with dbus->free().549*/550char *SDL_DBus_GetLocalMachineId(void)551{552DBusError err;553char *result;554555dbus.error_init(&err);556557if (dbus.try_get_local_machine_id) {558// Available since dbus 1.12.0, has proper error-handling559result = dbus.try_get_local_machine_id(&err);560} else {561/* Available since time immemorial, but has no error-handling:562* if the machine ID can't be read, many versions of libdbus will563* treat that as a fatal mis-installation and abort() */564result = dbus.get_local_machine_id();565}566567if (result) {568return result;569}570571if (dbus.error_is_set(&err)) {572SDL_SetError("%s: %s", err.name, err.message);573dbus.error_free(&err);574} else {575SDL_SetError("Error getting D-Bus machine ID");576}577578return NULL;579}580581/*582* Convert file drops with mime type "application/vnd.portal.filetransfer" to file paths583* Result must be freed with dbus->free_string_array().584* https://flatpak.github.io/xdg-desktop-portal/#gdbus-method-org-freedesktop-portal-FileTransfer.RetrieveFiles585*/586char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *path_count)587{588DBusError err;589DBusMessageIter iter, iterDict;590char **paths = NULL;591DBusMessage *reply = NULL;592DBusMessage *msg = dbus.message_new_method_call("org.freedesktop.portal.Documents", // Node593"/org/freedesktop/portal/documents", // Path594"org.freedesktop.portal.FileTransfer", // Interface595"RetrieveFiles"); // Method596597// Make sure we have a connection to the dbus session bus598if (!SDL_DBus_GetContext() || !dbus.session_conn) {599/* We either cannot connect to the session bus or were unable to600* load the D-Bus library at all. */601return NULL;602}603604dbus.error_init(&err);605606// First argument is a "application/vnd.portal.filetransfer" key from a DnD or clipboard event607if (!dbus.message_append_args(msg, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID)) {608SDL_OutOfMemory();609dbus.message_unref(msg);610goto failed;611}612613/* Second argument is a variant dictionary for options.614* The spec doesn't define any entries yet so it's empty. */615dbus.message_iter_init_append(msg, &iter);616if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) ||617!dbus.message_iter_close_container(&iter, &iterDict)) {618SDL_OutOfMemory();619dbus.message_unref(msg);620goto failed;621}622623reply = dbus.connection_send_with_reply_and_block(dbus.session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, &err);624dbus.message_unref(msg);625626if (reply) {627dbus.message_get_args(reply, &err, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &paths, path_count, DBUS_TYPE_INVALID);628dbus.message_unref(reply);629}630631if (paths) {632return paths;633}634635failed:636if (dbus.error_is_set(&err)) {637SDL_SetError("%s: %s", err.name, err.message);638dbus.error_free(&err);639} else {640SDL_SetError("Error retrieving paths for documents portal \"%s\"", key);641}642643return NULL;644}645646#endif647648649