From ad95c93bf4fe066db2b37f8a70bfca2f2dccce0f Mon Sep 17 00:00:00 2001 From: Guldoman Date: Thu, 9 Mar 2023 22:26:33 +0100 Subject: [PATCH] Add portal interface to support `SDL_GetSystemTheme` in linux --- CMakeLists.txt | 1 + src/core/linux/SDL_system_theme.c | 175 ++++++++++++++++++++++++++ src/core/linux/SDL_system_theme.h | 31 +++++ src/video/wayland/SDL_waylandevents.c | 9 ++ src/video/wayland/SDL_waylandvideo.c | 6 + src/video/x11/SDL_x11events.c | 9 ++ src/video/x11/SDL_x11video.c | 6 + 7 files changed, 237 insertions(+) create mode 100644 src/core/linux/SDL_system_theme.c create mode 100644 src/core/linux/SDL_system_theme.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 902934d58..add77d321 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1552,6 +1552,7 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) if(HAVE_DBUS_DBUS_H) list(APPEND SOURCE_FILES "${SDL3_SOURCE_DIR}/src/core/linux/SDL_dbus.c") + list(APPEND SOURCE_FILES "${SDL3_SOURCE_DIR}/src/core/linux/SDL_system_theme.c") endif() if(SDL_USE_IME) diff --git a/src/core/linux/SDL_system_theme.c b/src/core/linux/SDL_system_theme.c new file mode 100644 index 000000000..3f0077edd --- /dev/null +++ b/src/core/linux/SDL_system_theme.c @@ -0,0 +1,175 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + 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 "../SDL_sysvideo.h" + +#include + +#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 SDL_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 SDL_FALSE; + dbus->message_iter_recurse(iter, &variant_iter); + if (dbus->message_iter_get_arg_type(&variant_iter) != DBUS_TYPE_UINT32) + return SDL_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 SDL_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; +} + +SDL_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 == NULL) { + return SDL_FALSE; + } + + msg = dbus->message_new_method_call(PORTAL_DESTINATION, PORTAL_PATH, PORTAL_INTERFACE, PORTAL_METHOD); + if (msg != NULL) { + 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 <> */ + 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 SDL_TRUE; +} + +SDL_SystemTheme +SDL_SystemTheme_Get(void) +{ + return system_theme_data.theme; +} + +void +SDL_SystemTheme_PumpEvents(void) +{ + SDL_DBusContext *dbus = system_theme_data.dbus; + DBusConnection *conn; + if (dbus == NULL) return; + 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 */ + usleep(10); + } +} diff --git a/src/core/linux/SDL_system_theme.h b/src/core/linux/SDL_system_theme.h new file mode 100644 index 000000000..2510ecd30 --- /dev/null +++ b/src/core/linux/SDL_system_theme.h @@ -0,0 +1,31 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + 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 SDL_bool SDL_SystemTheme_Init(void); +extern SDL_SystemTheme SDL_SystemTheme_Get(void); +extern void SDL_SystemTheme_PumpEvents(void); + +#endif /* SDL_system_theme_h_ */ diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index d5a38c86b..b8023078a 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -26,6 +26,7 @@ #include "../../core/unix/SDL_poll.h" #include "../../events/SDL_events_c.h" #include "../../events/SDL_scancode_tables_c.h" +#include "../../core/linux/SDL_system_theme.h" #include "../SDL_sysvideo.h" #include "SDL_waylandvideo.h" @@ -386,6 +387,10 @@ int Wayland_WaitEventTimeout(_THIS, Sint64 timeoutNS) } #endif +#ifdef SDL_USE_LIBDBUS + SDL_SystemTheme_PumpEvents(); +#endif + /* If key repeat is active, we'll need to cap our maximum wait time to handle repeats */ if (input && keyboard_repeat_is_set(&input->keyboard_repeat)) { const Uint64 elapsed = SDL_GetTicksNS() - input->keyboard_repeat.sdl_press_time_ns; @@ -455,6 +460,10 @@ void Wayland_PumpEvents(_THIS) } #endif +#ifdef SDL_USE_LIBDBUS + SDL_SystemTheme_PumpEvents(); +#endif + #ifdef HAVE_LIBDECOR_H if (d->shell.libdecor) { libdecor_dispatch(d->shell.libdecor, 0); diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 9a5c7b9ea..066227cd0 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -24,6 +24,7 @@ #if SDL_VIDEO_DRIVER_WAYLAND #include "../../events/SDL_events_c.h" +#include "../../core/linux/SDL_system_theme.h" #include "SDL_waylandvideo.h" #include "SDL_waylandevents_c.h" @@ -249,6 +250,11 @@ static SDL_VideoDevice *Wayland_CreateDevice(void) device->FlashWindow = Wayland_FlashWindow; device->HasScreenKeyboardSupport = Wayland_HasScreenKeyboardSupport; +#ifdef SDL_USE_LIBDBUS + if (SDL_SystemTheme_Init()) + device->system_theme = SDL_SystemTheme_Get(); +#endif + device->SetClipboardText = Wayland_SetClipboardText; device->GetClipboardText = Wayland_GetClipboardText; device->HasClipboardText = Wayland_HasClipboardText; diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index 965a81174..72d7b391b 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -36,6 +36,7 @@ #include "../../events/SDL_events_c.h" #include "../../events/SDL_mouse_c.h" #include "../../events/SDL_touch_c.h" +#include "../../core/linux/SDL_system_theme.h" #include @@ -1683,6 +1684,10 @@ int X11_WaitEventTimeout(_THIS, Sint64 timeoutNS) SDL_IME_PumpEvents(); } #endif + +#ifdef SDL_USE_LIBDBUS + SDL_SystemTheme_PumpEvents(); +#endif return 1; } @@ -1725,6 +1730,10 @@ void X11_PumpEvents(_THIS) } #endif +#ifdef SDL_USE_LIBDBUS + SDL_SystemTheme_PumpEvents(); +#endif + /* FIXME: Only need to do this when there are pending focus changes */ X11_HandleFocusChanges(_this); diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index f789857ed..1d3424b91 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -26,6 +26,7 @@ #include "../SDL_sysvideo.h" #include "../SDL_pixels_c.h" +#include "../../core/linux/SDL_system_theme.h" #include "SDL_x11video.h" #include "SDL_x11framebuffer.h" @@ -315,6 +316,11 @@ static SDL_VideoDevice *X11_CreateDevice(void) device->Vulkan_CreateSurface = X11_Vulkan_CreateSurface; #endif +#ifdef SDL_USE_LIBDBUS + if (SDL_SystemTheme_Init()) + device->system_theme = SDL_SystemTheme_Get(); +#endif + return device; }