From 61dafb3b2f1831ed43bd9485828fd822fe1cb2fb Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 30 Nov 2023 16:36:22 +0000 Subject: [PATCH] Add XSETTINGS support to x11 driver Import the XSettingsClient implementation to handle the settings selection. Currently, we only care about the Gdk/WindowScalingFactor value used by the windowing system to notify us of display-wide changes in the scaling factor. --- cmake/3rdparty.cmake | 5 +- src/video/x11/SDL_x11events.c | 17 + src/video/x11/SDL_x11settings.c | 97 ++++ src/video/x11/SDL_x11settings.h | 38 ++ src/video/x11/SDL_x11video.c | 7 + src/video/x11/SDL_x11video.h | 3 + src/video/x11/xsettings-client.c | 855 +++++++++++++++++++++++++++++++ src/video/x11/xsettings-client.h | 153 ++++++ 8 files changed, 1174 insertions(+), 1 deletion(-) create mode 100644 src/video/x11/SDL_x11settings.c create mode 100644 src/video/x11/SDL_x11settings.h create mode 100644 src/video/x11/xsettings-client.c create mode 100644 src/video/x11/xsettings-client.h diff --git a/cmake/3rdparty.cmake b/cmake/3rdparty.cmake index 04f0898ce5..becb6d3abe 100644 --- a/cmake/3rdparty.cmake +++ b/cmake/3rdparty.cmake @@ -104,7 +104,10 @@ function(get_clang_tidy_ignored_files OUTVAR) "hid.m" "hidraw.cpp" "hidusb.cpp" - "hidapi.h") + "hidapi.h" + # XSETTINGS + "xsettings-client.c" + "xsettings-client.h") foreach(SOURCE_FILE ${3RD_PARTY_SOURCES}) list(APPEND IGNORED_LIST "{\"name\":\"${SOURCE_FILE}\",\"lines\":[[1,1]]}") diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index 20e9183a19..7530c30922 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -33,6 +33,7 @@ #include "SDL_x11touch.h" #include "SDL_x11xinput2.h" #include "SDL_x11xfixes.h" +#include "SDL_x11settings.h" #include "../SDL_clipboard_c.h" #include "../../core/unix/SDL_poll.h" #include "../../events/SDL_events_c.h" @@ -779,6 +780,16 @@ static void X11_HandleClipboardEvent(SDL_VideoDevice *_this, const XEvent *xeven } } +static void X11_HandleSettingsEvent(SDL_VideoDevice *_this, const XEvent *xevent) +{ + SDL_VideoData *videodata = _this->driverdata; + + SDL_assert(videodata->xsettings_window != None); + SDL_assert(xevent->xany.window == videodata->xsettings_window); + + X11_HandleXsettings(_this, xevent); +} + static Bool isMapNotify(Display *display, XEvent *ev, XPointer arg) { XUnmapEvent *unmap; @@ -1103,6 +1114,12 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) return; } + if ((videodata->xsettings_window != None) && + (videodata->xsettings_window == xevent->xany.window)) { + X11_HandleSettingsEvent(_this, xevent); + return; + } + data = X11_FindWindow(_this, xevent->xany.window); if (!data) { diff --git a/src/video/x11/SDL_x11settings.c b/src/video/x11/SDL_x11settings.c new file mode 100644 index 0000000000..dc65003d12 --- /dev/null +++ b/src/video/x11/SDL_x11settings.c @@ -0,0 +1,97 @@ +/* + Simple DirectMedia Layer + Copyright 2024 Igalia S.L. + + 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" + +#if defined(SDL_VIDEO_DRIVER_X11) + +#include "SDL_x11video.h" +#include "SDL_x11settings.h" + +#define SDL_XSETTINGS_GDK_WINDOW_SCALING_FACTOR "Gdk/WindowScalingFactor" + +static void X11_XsettingsNotify(const char *name, XSettingsAction action, XSettingsSetting *setting, void *data) +{ + SDL_VideoDevice *_this = data; + float scale_factor = 1.0; + int i; + + if (SDL_strcmp(name, SDL_XSETTINGS_GDK_WINDOW_SCALING_FACTOR) != 0) { + return; + } + + if (setting->type != XSETTINGS_TYPE_INT) { + return; + } + + switch (action) { + case XSETTINGS_ACTION_NEW: + SDL_FALLTHROUGH; + case XSETTINGS_ACTION_CHANGED: + scale_factor = setting->data.v_int; + break; + case XSETTINGS_ACTION_DELETED: + scale_factor = 1.0; + break; + } + + if (_this) { + for (i = 0; i < _this->num_displays; ++i) { + SDL_SetDisplayContentScale(_this->displays[i], scale_factor); + } + } +} + +void X11_InitXsettings(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->driverdata; + SDLX11_SettingsData *xsettings_data = &data->xsettings_data; + + xsettings_data->xsettings = xsettings_client_new(data->display, + DefaultScreen(data->display), X11_XsettingsNotify, NULL, _this); + +} + +void X11_QuitXsettings(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->driverdata; + SDLX11_SettingsData *xsettings_data = &data->xsettings_data; + + if (xsettings_data->xsettings) { + xsettings_client_destroy(xsettings_data->xsettings); + xsettings_data->xsettings = NULL; + } +} + +void X11_HandleXsettings(SDL_VideoDevice *_this, const XEvent *xevent) +{ + SDL_VideoData *data = _this->driverdata; + SDLX11_SettingsData *xsettings_data = &data->xsettings_data; + + if (xsettings_data->xsettings) { + if (!xsettings_client_process_event(xsettings_data->xsettings, xevent)) { + xsettings_client_destroy(xsettings_data->xsettings); + xsettings_data->xsettings = NULL; + } + } +} + +#endif /* SDL_VIDEO_DRIVER_X11 */ diff --git a/src/video/x11/SDL_x11settings.h b/src/video/x11/SDL_x11settings.h new file mode 100644 index 0000000000..ad1f6a1d64 --- /dev/null +++ b/src/video/x11/SDL_x11settings.h @@ -0,0 +1,38 @@ +/* + Simple DirectMedia Layer + Copyright 2024 Igalia S.L. + + 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_x11settings_h_ +#define SDL_x11settings_h_ + +#include +#include "xsettings-client.h" + +typedef struct X11_SettingsData { + XSettingsClient *xsettings; +} SDLX11_SettingsData; + +extern void X11_InitXsettings(SDL_VideoDevice *_this); +extern void X11_QuitXsettings(SDL_VideoDevice *_this); +extern void X11_HandleXsettings(SDL_VideoDevice *_this, const XEvent *xevent); + +#endif /* SDL_x11settings_h_ */ diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index 4a2692faa4..d417ce9223 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -443,6 +443,8 @@ int X11_VideoInit(SDL_VideoDevice *_this) X11_InitXfixes(_this); #endif /* SDL_VIDEO_DRIVER_X11_XFIXES */ + X11_InitXsettings(_this); + #ifndef X_HAVE_UTF8_STRING #warning X server does not support UTF8_STRING, a feature introduced in 2000! This is likely to become a hard error in a future libSDL3. #endif @@ -469,6 +471,10 @@ void X11_VideoQuit(SDL_VideoDevice *_this) X11_XDestroyWindow(data->display, data->clipboard_window); } + if (data->xsettings_window) { + X11_XDestroyWindow(data->display, data->xsettings_window); + } + #ifdef X_HAVE_UTF8_STRING if (data->im) { X11_XCloseIM(data->im); @@ -480,6 +486,7 @@ void X11_VideoQuit(SDL_VideoDevice *_this) X11_QuitMouse(_this); X11_QuitTouch(_this); X11_QuitClipboard(_this); + X11_QuitXsettings(_this); } SDL_bool X11_UseDirectColorVisuals(void) diff --git a/src/video/x11/SDL_x11video.h b/src/video/x11/SDL_x11video.h index 0df95fa57b..dbe1a6d53e 100644 --- a/src/video/x11/SDL_x11video.h +++ b/src/video/x11/SDL_x11video.h @@ -36,6 +36,7 @@ #include "SDL_x11modes.h" #include "SDL_x11mouse.h" #include "SDL_x11opengl.h" +#include "SDL_x11settings.h" #include "SDL_x11window.h" #include "SDL_x11vulkan.h" @@ -58,6 +59,8 @@ struct SDL_VideoData #ifdef SDL_VIDEO_DRIVER_X11_XFIXES SDL_Window *active_cursor_confined_window; #endif /* SDL_VIDEO_DRIVER_X11_XFIXES */ + Window xsettings_window; + SDLX11_SettingsData xsettings_data; /* This is true for ICCCM2.0-compliant window managers */ SDL_bool net_wm; diff --git a/src/video/x11/xsettings-client.c b/src/video/x11/xsettings-client.c new file mode 100644 index 0000000000..7d413ab64f --- /dev/null +++ b/src/video/x11/xsettings-client.c @@ -0,0 +1,855 @@ +/* + * Copyright © 2001, 2007 Red Hat, Inc. + * Copyright 2024 Igalia S.L. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Red Hat not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. Red Hat makes no representations about the + * suitability of this software for any purpose. It is provided "as is" + * without express or implied warranty. + * + * RED HAT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL RED HAT + * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Author: Owen Taylor, Red Hat, Inc. + */ + +#include "SDL_internal.h" + +#ifdef SDL_VIDEO_DRIVER_X11 + +#include "SDL_x11video.h" + +#include +#include +#include +#include + +#include "xsettings-client.h" + +struct _XSettingsClient +{ + Display *display; + int screen; + XSettingsNotifyFunc notify; + XSettingsWatchFunc watch; + void *cb_data; + + XSettingsGrabFunc grab; + XSettingsGrabFunc ungrab; + + Window manager_window; + Atom manager_atom; + Atom selection_atom; + Atom xsettings_atom; + + XSettingsList *settings; +}; + +static void +notify_changes (XSettingsClient *client, + XSettingsList *old_list) +{ + XSettingsList *old_iter = old_list; + XSettingsList *new_iter = client->settings; + + if (!client->notify) + return; + + while (old_iter || new_iter) + { + int cmp; + + if (old_iter && new_iter) + cmp = strcmp (old_iter->setting->name, new_iter->setting->name); + else if (old_iter) + cmp = -1; + else + cmp = 1; + + if (cmp < 0) + { + client->notify (old_iter->setting->name, + XSETTINGS_ACTION_DELETED, + NULL, + client->cb_data); + } + else if (cmp == 0) + { + if (!xsettings_setting_equal (old_iter->setting, + new_iter->setting)) + client->notify (old_iter->setting->name, + XSETTINGS_ACTION_CHANGED, + new_iter->setting, + client->cb_data); + } + else + { + client->notify (new_iter->setting->name, + XSETTINGS_ACTION_NEW, + new_iter->setting, + client->cb_data); + } + + if (old_iter) + old_iter = old_iter->next; + if (new_iter) + new_iter = new_iter->next; + } +} + +static int +ignore_errors (Display *display, XErrorEvent *event) +{ + return True; +} + +static char local_byte_order = '\0'; + +#define BYTES_LEFT(buffer) ((buffer)->data + (buffer)->len - (buffer)->pos) + +static XSettingsResult +fetch_card16 (XSettingsBuffer *buffer, + CARD16 *result) +{ + CARD16 x; + + if (BYTES_LEFT (buffer) < 2) + return XSETTINGS_ACCESS; + + x = *(CARD16 *)buffer->pos; + buffer->pos += 2; + + if (buffer->byte_order == local_byte_order) + *result = x; + else + *result = (x << 8) | (x >> 8); + + return XSETTINGS_SUCCESS; +} + +static XSettingsResult +fetch_ushort (XSettingsBuffer *buffer, + unsigned short *result) +{ + CARD16 x; + XSettingsResult r; + + r = fetch_card16 (buffer, &x); + if (r == XSETTINGS_SUCCESS) + *result = x; + + return r; +} + +static XSettingsResult +fetch_card32 (XSettingsBuffer *buffer, + CARD32 *result) +{ + CARD32 x; + + if (BYTES_LEFT (buffer) < 4) + return XSETTINGS_ACCESS; + + x = *(CARD32 *)buffer->pos; + buffer->pos += 4; + + if (buffer->byte_order == local_byte_order) + *result = x; + else + *result = (x << 24) | ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) | (x >> 24); + + return XSETTINGS_SUCCESS; +} + +static XSettingsResult +fetch_card8 (XSettingsBuffer *buffer, + CARD8 *result) +{ + if (BYTES_LEFT (buffer) < 1) + return XSETTINGS_ACCESS; + + *result = *(CARD8 *)buffer->pos; + buffer->pos += 1; + + return XSETTINGS_SUCCESS; +} + +#define XSETTINGS_PAD(n,m) ((n + m - 1) & (~(m-1))) + +static XSettingsList * +parse_settings (unsigned char *data, + size_t len) +{ + XSettingsBuffer buffer; + XSettingsResult result = XSETTINGS_SUCCESS; + XSettingsList *settings = NULL; + CARD32 serial; + CARD32 n_entries; + CARD32 i; + XSettingsSetting *setting = NULL; + char buffer_byte_order = '\0'; + + local_byte_order = xsettings_byte_order (); + + buffer.pos = buffer.data = data; + buffer.len = len; + buffer.byte_order = '\0'; + + result = fetch_card8 (&buffer, (unsigned char *) &buffer_byte_order); + if (buffer_byte_order != MSBFirst && + buffer_byte_order != LSBFirst) + { + fprintf (stderr, "Invalid byte order in XSETTINGS property\n"); + result = XSETTINGS_FAILED; + goto out; + } + + buffer.byte_order = buffer_byte_order; + buffer.pos += 3; + + result = fetch_card32 (&buffer, &serial); + if (result != XSETTINGS_SUCCESS) + goto out; + + result = fetch_card32 (&buffer, &n_entries); + if (result != XSETTINGS_SUCCESS) + goto out; + + for (i = 0; i < n_entries; i++) + { + CARD8 type; + CARD16 name_len; + CARD32 v_int; + size_t pad_len; + + result = fetch_card8 (&buffer, &type); + if (result != XSETTINGS_SUCCESS) + goto out; + + buffer.pos += 1; + + result = fetch_card16 (&buffer, &name_len); + if (result != XSETTINGS_SUCCESS) + goto out; + + pad_len = XSETTINGS_PAD(name_len, 4); + if (BYTES_LEFT (&buffer) < pad_len) + { + result = XSETTINGS_ACCESS; + goto out; + } + + setting = malloc (sizeof *setting); + if (!setting) + { + result = XSETTINGS_NO_MEM; + goto out; + } + setting->type = XSETTINGS_TYPE_INT; /* No allocated memory */ + + setting->name = malloc (name_len + 1); + if (!setting->name) + { + result = XSETTINGS_NO_MEM; + goto out; + } + + memcpy (setting->name, buffer.pos, name_len); + setting->name[name_len] = '\0'; + buffer.pos += pad_len; + + result = fetch_card32 (&buffer, &v_int); + if (result != XSETTINGS_SUCCESS) + goto out; + setting->last_change_serial = v_int; + + switch (type) + { + case XSETTINGS_TYPE_INT: + result = fetch_card32 (&buffer, &v_int); + if (result != XSETTINGS_SUCCESS) + goto out; + + setting->data.v_int = (INT32)v_int; + break; + case XSETTINGS_TYPE_STRING: + result = fetch_card32 (&buffer, &v_int); + if (result != XSETTINGS_SUCCESS) + goto out; + + pad_len = XSETTINGS_PAD (v_int, 4); + if (v_int + 1 == 0 || /* Guard against wrap-around */ + BYTES_LEFT (&buffer) < pad_len) + { + result = XSETTINGS_ACCESS; + goto out; + } + + setting->data.v_string = malloc (v_int + 1); + if (!setting->data.v_string) + { + result = XSETTINGS_NO_MEM; + goto out; + } + + memcpy (setting->data.v_string, buffer.pos, v_int); + setting->data.v_string[v_int] = '\0'; + buffer.pos += pad_len; + + break; + case XSETTINGS_TYPE_COLOR: + result = fetch_ushort (&buffer, &setting->data.v_color.red); + if (result != XSETTINGS_SUCCESS) + goto out; + result = fetch_ushort (&buffer, &setting->data.v_color.green); + if (result != XSETTINGS_SUCCESS) + goto out; + result = fetch_ushort (&buffer, &setting->data.v_color.blue); + if (result != XSETTINGS_SUCCESS) + goto out; + result = fetch_ushort (&buffer, &setting->data.v_color.alpha); + if (result != XSETTINGS_SUCCESS) + goto out; + + break; + default: + /* Quietly ignore unknown types */ + break; + } + + setting->type = type; + + result = xsettings_list_insert (&settings, setting); + if (result != XSETTINGS_SUCCESS) + goto out; + + setting = NULL; + } + + out: + + if (result != XSETTINGS_SUCCESS) + { + switch (result) + { + case XSETTINGS_NO_MEM: + fprintf(stderr, "Out of memory reading XSETTINGS property\n"); + break; + case XSETTINGS_ACCESS: + fprintf(stderr, "Invalid XSETTINGS property (read off end)\n"); + break; + case XSETTINGS_DUPLICATE_ENTRY: + fprintf (stderr, "Duplicate XSETTINGS entry for '%s'\n", setting->name); + SDL_FALLTHROUGH; + case XSETTINGS_FAILED: + SDL_FALLTHROUGH; + case XSETTINGS_SUCCESS: + SDL_FALLTHROUGH; + case XSETTINGS_NO_ENTRY: + break; + } + + if (setting) + xsettings_setting_free (setting); + + xsettings_list_free (settings); + settings = NULL; + + } + + return settings; +} + +static void +read_settings (XSettingsClient *client) +{ + Atom type; + int format; + unsigned long n_items; + unsigned long bytes_after; + unsigned char *data; + int result; + + int (*old_handler) (Display *, XErrorEvent *); + + XSettingsList *old_list = client->settings; + + client->settings = NULL; + + if (client->manager_window) + { + old_handler = X11_XSetErrorHandler (ignore_errors); + result = X11_XGetWindowProperty (client->display, client->manager_window, + client->xsettings_atom, 0, LONG_MAX, + False, client->xsettings_atom, + &type, &format, &n_items, &bytes_after, &data); + X11_XSetErrorHandler (old_handler); + + if (result == Success && type != None) + { + if (type != client->xsettings_atom) + { + fprintf (stderr, "Invalid type for XSETTINGS property"); + } + else if (format != 8) + { + fprintf (stderr, "Invalid format for XSETTINGS property %d", format); + } + else + client->settings = parse_settings (data, n_items); + + X11_XFree (data); + } + } + + notify_changes (client, old_list); + xsettings_list_free (old_list); +} + +static void +add_events (Display *display, + Window window, + long mask) +{ + XWindowAttributes attr; + + X11_XGetWindowAttributes (display, window, &attr); + X11_XSelectInput (display, window, attr.your_event_mask | mask); +} + +static void +check_manager_window (XSettingsClient *client) +{ + if (client->manager_window && client->watch) + client->watch (client->manager_window, False, 0, client->cb_data); + + if (client->grab) + client->grab (client->display); + else + X11_XGrabServer (client->display); + + client->manager_window = X11_XGetSelectionOwner (client->display, + client->selection_atom); + if (client->manager_window) + X11_XSelectInput (client->display, client->manager_window, + PropertyChangeMask | StructureNotifyMask); + + if (client->ungrab) + client->ungrab (client->display); + else + X11_XUngrabServer (client->display); + + X11_XFlush (client->display); + + if (client->manager_window && client->watch) + { + if (!client->watch (client->manager_window, True, + PropertyChangeMask | StructureNotifyMask, + client->cb_data)) + { + /* Inability to watch the window probably means that it was destroyed + * after we ungrabbed + */ + client->manager_window = None; + return; + } + } + + + read_settings (client); +} + +XSettingsClient * +xsettings_client_new (Display *display, + int screen, + XSettingsNotifyFunc notify, + XSettingsWatchFunc watch, + void *cb_data) +{ + return xsettings_client_new_with_grab_funcs (display, screen, notify, watch, cb_data, + NULL, NULL); +} + +XSettingsClient * +xsettings_client_new_with_grab_funcs (Display *display, + int screen, + XSettingsNotifyFunc notify, + XSettingsWatchFunc watch, + void *cb_data, + XSettingsGrabFunc grab, + XSettingsGrabFunc ungrab) +{ + XSettingsClient *client; + char buffer[256]; + char *atom_names[3]; + Atom atoms[3]; + + client = malloc (sizeof *client); + if (!client) + return NULL; + + client->display = display; + client->screen = screen; + client->notify = notify; + client->watch = watch; + client->cb_data = cb_data; + client->grab = grab; + client->ungrab = ungrab; + + client->manager_window = None; + client->settings = NULL; + + sprintf(buffer, "_XSETTINGS_S%d", screen); + atom_names[0] = buffer; + atom_names[1] = "_XSETTINGS_SETTINGS"; + atom_names[2] = "MANAGER"; + +#ifdef HAVE_XINTERNATOMS + XInternAtoms (display, atom_names, 3, False, atoms); +#else + atoms[0] = X11_XInternAtom (display, atom_names[0], False); + atoms[1] = X11_XInternAtom (display, atom_names[1], False); + atoms[2] = X11_XInternAtom (display, atom_names[2], False); +#endif + + client->selection_atom = atoms[0]; + client->xsettings_atom = atoms[1]; + client->manager_atom = atoms[2]; + + /* Select on StructureNotify so we get MANAGER events + */ + add_events (display, RootWindow (display, screen), StructureNotifyMask); + + if (client->watch) + client->watch (RootWindow (display, screen), True, StructureNotifyMask, + client->cb_data); + + check_manager_window (client); + + return client; +} + + +void +xsettings_client_set_grab_func (XSettingsClient *client, + XSettingsGrabFunc grab) +{ + client->grab = grab; +} + +void +xsettings_client_set_ungrab_func (XSettingsClient *client, + XSettingsGrabFunc ungrab) +{ + client->ungrab = ungrab; +} + +void +xsettings_client_destroy (XSettingsClient *client) +{ + if (client->watch) + client->watch (RootWindow (client->display, client->screen), + False, 0, client->cb_data); + if (client->manager_window && client->watch) + client->watch (client->manager_window, False, 0, client->cb_data); + + xsettings_list_free (client->settings); + free (client); +} + +XSettingsResult +xsettings_client_get_setting (XSettingsClient *client, + const char *name, + XSettingsSetting **setting) +{ + XSettingsSetting *search = xsettings_list_lookup (client->settings, name); + if (search) + { + *setting = xsettings_setting_copy (search); + return *setting ? XSETTINGS_SUCCESS : XSETTINGS_NO_MEM; + } + else + return XSETTINGS_NO_ENTRY; +} + +Bool +xsettings_client_process_event (XSettingsClient *client, + const XEvent *xev) +{ + /* The checks here will not unlikely cause us to reread + * the properties from the manager window a number of + * times when the manager changes from A->B. But manager changes + * are going to be pretty rare. + */ + if (xev->xany.window == RootWindow (client->display, client->screen)) + { + if (xev->xany.type == ClientMessage && + xev->xclient.message_type == client->manager_atom && + xev->xclient.data.l[1] == client->selection_atom) + { + check_manager_window (client); + return True; + } + } + else if (xev->xany.window == client->manager_window) + { + if (xev->xany.type == DestroyNotify) + { + check_manager_window (client); + return False; + } + else if (xev->xany.type == PropertyNotify) + { + read_settings (client); + return True; + } + } + + return False; +} + +XSettingsSetting * +xsettings_setting_copy (XSettingsSetting *setting) +{ + XSettingsSetting *result; + size_t str_len; + + result = malloc (sizeof *result); + if (!result) + return NULL; + + str_len = strlen (setting->name); + result->name = malloc (str_len + 1); + if (!result->name) + goto err; + + memcpy (result->name, setting->name, str_len + 1); + + result->type = setting->type; + + switch (setting->type) + { + case XSETTINGS_TYPE_INT: + result->data.v_int = setting->data.v_int; + break; + case XSETTINGS_TYPE_COLOR: + result->data.v_color = setting->data.v_color; + break; + case XSETTINGS_TYPE_STRING: + str_len = strlen (setting->data.v_string); + result->data.v_string = malloc (str_len + 1); + if (!result->data.v_string) + goto err; + + memcpy (result->data.v_string, setting->data.v_string, str_len + 1); + break; + } + + result->last_change_serial = setting->last_change_serial; + + return result; + + err: + if (result->name) + free (result->name); + free (result); + + return NULL; +} + +XSettingsList * +xsettings_list_copy (XSettingsList *list) +{ + XSettingsList *new = NULL; + XSettingsList *old_iter = list; + XSettingsList *new_iter = NULL; + + while (old_iter) + { + XSettingsList *new_node; + + new_node = malloc (sizeof *new_node); + if (!new_node) + goto error; + + new_node->setting = xsettings_setting_copy (old_iter->setting); + if (!new_node->setting) + { + free (new_node); + goto error; + } + + if (new_iter) + new_iter->next = new_node; + else + new = new_node; + + new_iter = new_node; + + old_iter = old_iter->next; + } + + return new; + + error: + xsettings_list_free (new); + return NULL; +} + +int +xsettings_setting_equal (XSettingsSetting *setting_a, + XSettingsSetting *setting_b) +{ + if (setting_a->type != setting_b->type) + return 0; + + if (strcmp (setting_a->name, setting_b->name) != 0) + return 0; + + switch (setting_a->type) + { + case XSETTINGS_TYPE_INT: + return setting_a->data.v_int == setting_b->data.v_int; + case XSETTINGS_TYPE_COLOR: + return (setting_a->data.v_color.red == setting_b->data.v_color.red && + setting_a->data.v_color.green == setting_b->data.v_color.green && + setting_a->data.v_color.blue == setting_b->data.v_color.blue && + setting_a->data.v_color.alpha == setting_b->data.v_color.alpha); + case XSETTINGS_TYPE_STRING: + return strcmp (setting_a->data.v_string, setting_b->data.v_string) == 0; + } + + return 0; +} + +void +xsettings_setting_free (XSettingsSetting *setting) +{ + if (setting->type == XSETTINGS_TYPE_STRING) + free (setting->data.v_string); + + if (setting->name) + free (setting->name); + + free (setting); +} + +void +xsettings_list_free (XSettingsList *list) +{ + while (list) + { + XSettingsList *next = list->next; + + xsettings_setting_free (list->setting); + free (list); + + list = next; + } +} + +XSettingsResult +xsettings_list_insert (XSettingsList **list, + XSettingsSetting *setting) +{ + XSettingsList *node; + XSettingsList *iter; + XSettingsList *last = NULL; + + node = malloc (sizeof *node); + if (!node) + return XSETTINGS_NO_MEM; + node->setting = setting; + + iter = *list; + while (iter) + { + int cmp = strcmp (setting->name, iter->setting->name); + + if (cmp < 0) + break; + else if (cmp == 0) + { + free (node); + return XSETTINGS_DUPLICATE_ENTRY; + } + + last = iter; + iter = iter->next; + } + + if (last) + last->next = node; + else + *list = node; + + node->next = iter; + + return XSETTINGS_SUCCESS; +} + +XSettingsResult +xsettings_list_delete (XSettingsList **list, + const char *name) +{ + XSettingsList *iter; + XSettingsList *last = NULL; + + iter = *list; + while (iter) + { + if (strcmp (name, iter->setting->name) == 0) + { + if (last) + last->next = iter->next; + else + *list = iter->next; + + xsettings_setting_free (iter->setting); + free (iter); + + return XSETTINGS_SUCCESS; + } + + last = iter; + iter = iter->next; + } + + return XSETTINGS_FAILED; +} + +XSettingsSetting * +xsettings_list_lookup (XSettingsList *list, + const char *name) +{ + XSettingsList *iter; + + iter = list; + while (iter) + { + if (strcmp (name, iter->setting->name) == 0) + return iter->setting; + + iter = iter->next; + } + + return NULL; +} + +char +xsettings_byte_order (void) +{ + CARD32 myint = 0x01020304; + return (*(char *)&myint == 1) ? MSBFirst : LSBFirst; +} + +#endif /* SDL_VIDEO_DRIVER_X11 */ diff --git a/src/video/x11/xsettings-client.h b/src/video/x11/xsettings-client.h new file mode 100644 index 0000000000..863c6f9b7c --- /dev/null +++ b/src/video/x11/xsettings-client.h @@ -0,0 +1,153 @@ +/* + * Copyright © 2001, 2007 Red Hat, Inc. + * Copyright 2024 Igalia S.L. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Red Hat not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. Red Hat makes no representations about the + * suitability of this software for any purpose. It is provided "as is" + * without express or implied warranty. + * + * RED HAT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL RED HAT + * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Author: Owen Taylor, Red Hat, Inc. + */ +#ifndef XSETTINGS_CLIENT_H +#define XSETTINGS_CLIENT_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct _XSettingsBuffer XSettingsBuffer; +typedef struct _XSettingsColor XSettingsColor; +typedef struct _XSettingsList XSettingsList; +typedef struct _XSettingsSetting XSettingsSetting; + +/* Types of settings possible. Enum values correspond to + * protocol values. + */ +typedef enum +{ + XSETTINGS_TYPE_INT = 0, + XSETTINGS_TYPE_STRING = 1, + XSETTINGS_TYPE_COLOR = 2 +} XSettingsType; + +typedef enum +{ + XSETTINGS_SUCCESS, + XSETTINGS_NO_MEM, + XSETTINGS_ACCESS, + XSETTINGS_FAILED, + XSETTINGS_NO_ENTRY, + XSETTINGS_DUPLICATE_ENTRY +} XSettingsResult; + +struct _XSettingsBuffer +{ + char byte_order; + size_t len; + unsigned char *data; + unsigned char *pos; +}; + +struct _XSettingsColor +{ + unsigned short red, green, blue, alpha; +}; + +struct _XSettingsList +{ + XSettingsSetting *setting; + XSettingsList *next; +}; + +struct _XSettingsSetting +{ + char *name; + XSettingsType type; + + union { + int v_int; + char *v_string; + XSettingsColor v_color; + } data; + + unsigned long last_change_serial; +}; + +XSettingsSetting *xsettings_setting_copy (XSettingsSetting *setting); +void xsettings_setting_free (XSettingsSetting *setting); +int xsettings_setting_equal (XSettingsSetting *setting_a, + XSettingsSetting *setting_b); + +void xsettings_list_free (XSettingsList *list); +XSettingsList *xsettings_list_copy (XSettingsList *list); +XSettingsResult xsettings_list_insert (XSettingsList **list, + XSettingsSetting *setting); +XSettingsSetting *xsettings_list_lookup (XSettingsList *list, + const char *name); +XSettingsResult xsettings_list_delete (XSettingsList **list, + const char *name); + +char xsettings_byte_order (void); + +#define XSETTINGS_PAD(n,m) ((n + m - 1) & (~(m-1))) + +typedef struct _XSettingsClient XSettingsClient; + +typedef enum +{ + XSETTINGS_ACTION_NEW, + XSETTINGS_ACTION_CHANGED, + XSETTINGS_ACTION_DELETED +} XSettingsAction; + +typedef void (*XSettingsNotifyFunc) (const char *name, + XSettingsAction action, + XSettingsSetting *setting, + void *cb_data); +typedef Bool (*XSettingsWatchFunc) (Window window, + Bool is_start, + long mask, + void *cb_data); +typedef void (*XSettingsGrabFunc) (Display *display); + +XSettingsClient *xsettings_client_new (Display *display, + int screen, + XSettingsNotifyFunc notify, + XSettingsWatchFunc watch, + void *cb_data); +XSettingsClient *xsettings_client_new_with_grab_funcs (Display *display, + int screen, + XSettingsNotifyFunc notify, + XSettingsWatchFunc watch, + void *cb_data, + XSettingsGrabFunc grab, + XSettingsGrabFunc ungrab); +void xsettings_client_set_grab_func (XSettingsClient *client, + XSettingsGrabFunc grab); +void xsettings_client_set_ungrab_func (XSettingsClient *client, + XSettingsGrabFunc ungrab); +void xsettings_client_destroy (XSettingsClient *client); +Bool xsettings_client_process_event (XSettingsClient *client, + const XEvent *xev); +XSettingsResult xsettings_client_get_setting (XSettingsClient *client, + const char *name, + XSettingsSetting **setting); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* XSETTINGS_CLIENT_H */