From 5a079a2d114f96d4847d1ee305d5b7c16eeec50e Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 27 Dec 2025 12:03:39 -0800 Subject: Initial commit --- contrib/SDL-3.2.8/src/video/x11/SDL_x11clipboard.c | 329 +++ contrib/SDL-3.2.8/src/video/x11/SDL_x11clipboard.h | 46 + contrib/SDL-3.2.8/src/video/x11/SDL_x11dyn.c | 211 ++ contrib/SDL-3.2.8/src/video/x11/SDL_x11dyn.h | 113 + contrib/SDL-3.2.8/src/video/x11/SDL_x11events.c | 2205 +++++++++++++++++++ contrib/SDL-3.2.8/src/video/x11/SDL_x11events.h | 40 + .../SDL-3.2.8/src/video/x11/SDL_x11framebuffer.c | 261 +++ .../SDL-3.2.8/src/video/x11/SDL_x11framebuffer.h | 34 + contrib/SDL-3.2.8/src/video/x11/SDL_x11keyboard.c | 789 +++++++ contrib/SDL-3.2.8/src/video/x11/SDL_x11keyboard.h | 40 + .../SDL-3.2.8/src/video/x11/SDL_x11messagebox.c | 887 ++++++++ .../SDL-3.2.8/src/video/x11/SDL_x11messagebox.h | 31 + contrib/SDL-3.2.8/src/video/x11/SDL_x11modes.c | 1051 +++++++++ contrib/SDL-3.2.8/src/video/x11/SDL_x11modes.h | 69 + contrib/SDL-3.2.8/src/video/x11/SDL_x11mouse.c | 552 +++++ contrib/SDL-3.2.8/src/video/x11/SDL_x11mouse.h | 40 + contrib/SDL-3.2.8/src/video/x11/SDL_x11opengl.c | 1116 ++++++++++ contrib/SDL-3.2.8/src/video/x11/SDL_x11opengl.h | 96 + contrib/SDL-3.2.8/src/video/x11/SDL_x11opengles.c | 152 ++ contrib/SDL-3.2.8/src/video/x11/SDL_x11opengles.h | 55 + contrib/SDL-3.2.8/src/video/x11/SDL_x11pen.c | 437 ++++ contrib/SDL-3.2.8/src/video/x11/SDL_x11pen.h | 72 + contrib/SDL-3.2.8/src/video/x11/SDL_x11settings.c | 129 ++ contrib/SDL-3.2.8/src/video/x11/SDL_x11settings.h | 39 + contrib/SDL-3.2.8/src/video/x11/SDL_x11shape.c | 111 + contrib/SDL-3.2.8/src/video/x11/SDL_x11shape.h | 28 + contrib/SDL-3.2.8/src/video/x11/SDL_x11sym.h | 354 +++ contrib/SDL-3.2.8/src/video/x11/SDL_x11touch.c | 46 + contrib/SDL-3.2.8/src/video/x11/SDL_x11touch.h | 30 + contrib/SDL-3.2.8/src/video/x11/SDL_x11video.c | 505 +++++ contrib/SDL-3.2.8/src/video/x11/SDL_x11video.h | 177 ++ contrib/SDL-3.2.8/src/video/x11/SDL_x11vulkan.c | 290 +++ contrib/SDL-3.2.8/src/video/x11/SDL_x11vulkan.h | 52 + contrib/SDL-3.2.8/src/video/x11/SDL_x11window.c | 2243 ++++++++++++++++++++ contrib/SDL-3.2.8/src/video/x11/SDL_x11window.h | 169 ++ contrib/SDL-3.2.8/src/video/x11/SDL_x11xfixes.c | 214 ++ contrib/SDL-3.2.8/src/video/x11/SDL_x11xfixes.h | 39 + contrib/SDL-3.2.8/src/video/x11/SDL_x11xinput2.c | 829 ++++++++ contrib/SDL-3.2.8/src/video/x11/SDL_x11xinput2.h | 44 + contrib/SDL-3.2.8/src/video/x11/SDL_x11xsync.c | 148 ++ contrib/SDL-3.2.8/src/video/x11/SDL_x11xsync.h | 39 + contrib/SDL-3.2.8/src/video/x11/edid-parse.c | 753 +++++++ contrib/SDL-3.2.8/src/video/x11/edid.h | 191 ++ contrib/SDL-3.2.8/src/video/x11/xsettings-client.c | 859 ++++++++ contrib/SDL-3.2.8/src/video/x11/xsettings-client.h | 153 ++ 45 files changed, 16068 insertions(+) create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11clipboard.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11clipboard.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11dyn.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11dyn.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11events.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11events.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11framebuffer.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11framebuffer.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11keyboard.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11keyboard.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11messagebox.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11messagebox.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11modes.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11modes.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11mouse.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11mouse.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11opengl.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11opengl.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11opengles.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11opengles.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11pen.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11pen.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11settings.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11settings.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11shape.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11shape.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11sym.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11touch.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11touch.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11video.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11video.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11vulkan.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11vulkan.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11window.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11window.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11xfixes.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11xfixes.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11xinput2.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11xinput2.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11xsync.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11xsync.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/edid-parse.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/edid.h create mode 100644 contrib/SDL-3.2.8/src/video/x11/xsettings-client.c create mode 100644 contrib/SDL-3.2.8/src/video/x11/xsettings-client.h (limited to 'contrib/SDL-3.2.8/src/video/x11') diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11clipboard.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11clipboard.c new file mode 100644 index 0000000..5e33555 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11clipboard.c @@ -0,0 +1,329 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifdef SDL_VIDEO_DRIVER_X11 + +#include // For INT_MAX + +#include "SDL_x11video.h" +#include "SDL_x11clipboard.h" +#include "../SDL_clipboard_c.h" +#include "../../events/SDL_events_c.h" + +static const char *text_mime_types[] = { + "UTF8_STRING", + "text/plain;charset=utf-8", + "text/plain", + "TEXT", + "STRING" +}; + +// Get any application owned window handle for clipboard association +Window GetWindow(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + + /* We create an unmapped window that exists just to manage the clipboard, + since X11 selection data is tied to a specific window and dies with it. + We create the window on demand, so apps that don't use the clipboard + don't have to keep an unnecessary resource around. */ + if (data->clipboard_window == None) { + Display *dpy = data->display; + Window parent = RootWindow(dpy, DefaultScreen(dpy)); + XSetWindowAttributes xattr; + data->clipboard_window = X11_XCreateWindow(dpy, parent, -10, -10, 1, 1, 0, + CopyFromParent, InputOnly, + CopyFromParent, 0, &xattr); + + X11_XSelectInput(dpy, data->clipboard_window, PropertyChangeMask); + X11_XFlush(data->display); + } + + return data->clipboard_window; +} + +static bool SetSelectionData(SDL_VideoDevice *_this, Atom selection, SDL_ClipboardDataCallback callback, + void *userdata, const char **mime_types, size_t mime_count, Uint32 sequence) +{ + SDL_VideoData *videodata = _this->internal; + Display *display = videodata->display; + Window window; + SDLX11_ClipboardData *clipboard; + bool clipboard_owner = false; + + window = GetWindow(_this); + if (window == None) { + return SDL_SetError("Couldn't find a window to own the selection"); + } + + if (selection == XA_PRIMARY) { + clipboard = &videodata->primary_selection; + } else { + clipboard = &videodata->clipboard; + } + + clipboard_owner = X11_XGetSelectionOwner(display, selection) == window; + + // If we are canceling our own data we need to clean it up + if (clipboard_owner && clipboard->sequence == 0) { + SDL_free(clipboard->userdata); + } + + clipboard->callback = callback; + clipboard->userdata = userdata; + clipboard->mime_types = mime_types; + clipboard->mime_count = mime_count; + clipboard->sequence = sequence; + + X11_XSetSelectionOwner(display, selection, window, CurrentTime); + return true; +} + +static void *CloneDataBuffer(const void *buffer, const size_t len) +{ + void *clone = NULL; + if (len > 0 && buffer) { + clone = SDL_malloc(len + sizeof(Uint32)); + if (clone) { + SDL_memcpy(clone, buffer, len); + SDL_memset((Uint8 *)clone + len, 0, sizeof(Uint32)); + } + } + return clone; +} + +/* + * original_buffer is considered unusable after the function is called. + */ +static void *AppendDataBuffer(void *original_buffer, const size_t old_len, const void *buffer, const size_t buffer_len) +{ + void *resized_buffer; + + if (buffer_len > 0 && buffer) { + resized_buffer = SDL_realloc(original_buffer, old_len + buffer_len + sizeof(Uint32)); + if (resized_buffer) { + SDL_memcpy((Uint8 *)resized_buffer + old_len, buffer, buffer_len); + SDL_memset((Uint8 *)resized_buffer + old_len + buffer_len, 0, sizeof(Uint32)); + } + + return resized_buffer; + } else { + return original_buffer; + } +} + +static bool WaitForSelection(SDL_VideoDevice *_this, Atom selection_type, bool *flag) +{ + Uint64 waitStart; + Uint64 waitElapsed; + + waitStart = SDL_GetTicks(); + *flag = true; + while (*flag) { + SDL_PumpEvents(); + waitElapsed = SDL_GetTicks() - waitStart; + // Wait one second for a selection response. + if (waitElapsed > 1000) { + *flag = false; + SDL_SetError("Selection timeout"); + /* We need to set the selection text so that next time we won't + timeout, otherwise we will hang on every call to this function. */ + SetSelectionData(_this, selection_type, SDL_ClipboardTextCallback, NULL, + text_mime_types, SDL_arraysize(text_mime_types), 0); + return false; + } + } + + return true; +} + +static void *GetSelectionData(SDL_VideoDevice *_this, Atom selection_type, + const char *mime_type, size_t *length) +{ + SDL_VideoData *videodata = _this->internal; + Display *display = videodata->display; + Window window; + Window owner; + Atom selection; + Atom seln_type; + int seln_format; + unsigned long count; + unsigned long overflow; + + SDLX11_ClipboardData *clipboard; + void *data = NULL; + unsigned char *src = NULL; + bool incr_success = false; + Atom XA_MIME = X11_XInternAtom(display, mime_type, False); + + *length = 0; + + // Get the window that holds the selection + window = GetWindow(_this); + owner = X11_XGetSelectionOwner(display, selection_type); + if (owner == None) { + // This requires a fallback to ancient X10 cut-buffers. We will just skip those for now + data = NULL; + } else if (owner == window) { + owner = DefaultRootWindow(display); + if (selection_type == XA_PRIMARY) { + clipboard = &videodata->primary_selection; + } else { + clipboard = &videodata->clipboard; + } + + if (clipboard->callback) { + const void *clipboard_data = clipboard->callback(clipboard->userdata, mime_type, length); + data = CloneDataBuffer(clipboard_data, *length); + } + } else { + // Request that the selection owner copy the data to our window + owner = window; + selection = videodata->atoms.SDL_SELECTION; + X11_XConvertSelection(display, selection_type, XA_MIME, selection, owner, + CurrentTime); + + if (WaitForSelection(_this, selection_type, &videodata->selection_waiting) == false) { + data = NULL; + *length = 0; + } + + if (X11_XGetWindowProperty(display, owner, selection, 0, INT_MAX / 4, False, + XA_MIME, &seln_type, &seln_format, &count, &overflow, &src) == Success) { + if (seln_type == XA_MIME) { + *length = (size_t)count; + data = CloneDataBuffer(src, count); + } else if (seln_type == videodata->atoms.INCR) { + while (1) { + // Only delete the property after being done with the previous "chunk". + X11_XDeleteProperty(display, owner, selection); + X11_XFlush(display); + + if (WaitForSelection(_this, selection_type, &videodata->selection_incr_waiting) == false) { + break; + } + + X11_XFree(src); + if (X11_XGetWindowProperty(display, owner, selection, 0, INT_MAX / 4, False, + XA_MIME, &seln_type, &seln_format, &count, &overflow, &src) != Success) { + break; + } + + if (count == 0) { + incr_success = true; + break; + } + + if (*length == 0) { + *length = (size_t)count; + data = CloneDataBuffer(src, count); + } else { + data = AppendDataBuffer(data, *length, src, count); + *length += (size_t)count; + } + + if (data == NULL) { + break; + } + } + + if (incr_success == false) { + SDL_free(data); + data = 0; + *length = 0; + } + } + X11_XFree(src); + } + } + return data; +} + +const char **X11_GetTextMimeTypes(SDL_VideoDevice *_this, size_t *num_mime_types) +{ + *num_mime_types = SDL_arraysize(text_mime_types); + return text_mime_types; +} + +bool X11_SetClipboardData(SDL_VideoDevice *_this) +{ + SDL_VideoData *videodata = _this->internal; + return SetSelectionData(_this, videodata->atoms.CLIPBOARD, _this->clipboard_callback, _this->clipboard_userdata, (const char **)_this->clipboard_mime_types, _this->num_clipboard_mime_types, _this->clipboard_sequence); +} + +void *X11_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *length) +{ + SDL_VideoData *videodata = _this->internal; + return GetSelectionData(_this, videodata->atoms.CLIPBOARD, mime_type, length); +} + +bool X11_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type) +{ + size_t length; + void *data; + data = X11_GetClipboardData(_this, mime_type, &length); + if (data) { + SDL_free(data); + } + return length > 0; +} + +bool X11_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text) +{ + return SetSelectionData(_this, XA_PRIMARY, SDL_ClipboardTextCallback, SDL_strdup(text), text_mime_types, SDL_arraysize(text_mime_types), 0); +} + +char *X11_GetPrimarySelectionText(SDL_VideoDevice *_this) +{ + size_t length; + char *text = GetSelectionData(_this, XA_PRIMARY, text_mime_types[0], &length); + if (!text) { + text = SDL_strdup(""); + } + return text; +} + +bool X11_HasPrimarySelectionText(SDL_VideoDevice *_this) +{ + bool result = false; + char *text = X11_GetPrimarySelectionText(_this); + if (text) { + if (text[0] != '\0') { + result = true; + } + SDL_free(text); + } + return result; +} + +void X11_QuitClipboard(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + if (data->primary_selection.sequence == 0) { + SDL_free(data->primary_selection.userdata); + } + if (data->clipboard.sequence == 0) { + SDL_free(data->clipboard.userdata); + } +} + +#endif // SDL_VIDEO_DRIVER_X11 diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11clipboard.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11clipboard.h new file mode 100644 index 0000000..da5990a --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11clipboard.h @@ -0,0 +1,46 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifndef SDL_x11clipboard_h_ +#define SDL_x11clipboard_h_ + +#include + +typedef struct X11_ClipboardData { + SDL_ClipboardDataCallback callback; + void *userdata; + const char **mime_types; + size_t mime_count; + Uint32 sequence; +} SDLX11_ClipboardData; + +extern const char **X11_GetTextMimeTypes(SDL_VideoDevice *_this, size_t *num_mime_types); +extern bool X11_SetClipboardData(SDL_VideoDevice *_this); +extern void *X11_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *length); +extern bool X11_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type); +extern bool X11_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text); +extern char *X11_GetPrimarySelectionText(SDL_VideoDevice *_this); +extern bool X11_HasPrimarySelectionText(SDL_VideoDevice *_this); +extern void X11_QuitClipboard(SDL_VideoDevice *_this); +Window GetWindow(SDL_VideoDevice *_this); + +#endif // SDL_x11clipboard_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11dyn.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11dyn.c new file mode 100644 index 0000000..7c48ed5 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11dyn.c @@ -0,0 +1,211 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifdef SDL_VIDEO_DRIVER_X11 + +#define DEBUG_DYNAMIC_X11 0 + +#include "SDL_x11dyn.h" + +#if DEBUG_DYNAMIC_X11 +#include +#endif + +#ifdef SDL_VIDEO_DRIVER_X11_DYNAMIC + +typedef struct +{ + SDL_SharedObject *lib; + const char *libname; +} x11dynlib; + +#ifndef SDL_VIDEO_DRIVER_X11_DYNAMIC_XEXT +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XEXT NULL +#endif +#ifndef SDL_VIDEO_DRIVER_X11_DYNAMIC_XCURSOR +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XCURSOR NULL +#endif +#ifndef SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 NULL +#endif +#ifndef SDL_VIDEO_DRIVER_X11_DYNAMIC_XFIXES +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XFIXES NULL +#endif +#ifndef SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR NULL +#endif +#ifndef SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS NULL +#endif + +static x11dynlib x11libs[] = { + { NULL, SDL_VIDEO_DRIVER_X11_DYNAMIC }, + { NULL, SDL_VIDEO_DRIVER_X11_DYNAMIC_XEXT }, + { NULL, SDL_VIDEO_DRIVER_X11_DYNAMIC_XCURSOR }, + { NULL, SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 }, + { NULL, SDL_VIDEO_DRIVER_X11_DYNAMIC_XFIXES }, + { NULL, SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR }, + { NULL, SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS } +}; + +static void *X11_GetSym(const char *fnname, int *pHasModule) +{ + int i; + void *fn = NULL; + for (i = 0; i < SDL_arraysize(x11libs); i++) { + if (x11libs[i].lib) { + fn = SDL_LoadFunction(x11libs[i].lib, fnname); + if (fn) { + break; + } + } + } + +#if DEBUG_DYNAMIC_X11 + if (fn) + printf("X11: Found '%s' in %s (%p)\n", fnname, x11libs[i].libname, fn); + else + printf("X11: Symbol '%s' NOT FOUND!\n", fnname); +#endif + + if (!fn) { + *pHasModule = 0; // kill this module. + } + + return fn; +} + +#endif // SDL_VIDEO_DRIVER_X11_DYNAMIC + +// Define all the function pointers and wrappers... +#define SDL_X11_SYM(rc, fn, params, args, ret) SDL_DYNX11FN_##fn X11_##fn = NULL; +#include "SDL_x11sym.h" + +// Annoying varargs entry point... +#ifdef X_HAVE_UTF8_STRING +SDL_DYNX11FN_XCreateIC X11_XCreateIC = NULL; +SDL_DYNX11FN_XGetICValues X11_XGetICValues = NULL; +SDL_DYNX11FN_XSetICValues X11_XSetICValues = NULL; +SDL_DYNX11FN_XVaCreateNestedList X11_XVaCreateNestedList = NULL; +#endif + +/* These SDL_X11_HAVE_* flags are here whether you have dynamic X11 or not. */ +#define SDL_X11_MODULE(modname) int SDL_X11_HAVE_##modname = 0; +#include "SDL_x11sym.h" + +static int x11_load_refcount = 0; + +void SDL_X11_UnloadSymbols(void) +{ + // Don't actually unload if more than one module is using the libs... + if (x11_load_refcount > 0) { + if (--x11_load_refcount == 0) { +#ifdef SDL_VIDEO_DRIVER_X11_DYNAMIC + int i; +#endif + + // set all the function pointers to NULL. +#define SDL_X11_MODULE(modname) SDL_X11_HAVE_##modname = 0; +#define SDL_X11_SYM(rc, fn, params, args, ret) X11_##fn = NULL; +#include "SDL_x11sym.h" + +#ifdef X_HAVE_UTF8_STRING + X11_XCreateIC = NULL; + X11_XGetICValues = NULL; + X11_XSetICValues = NULL; + X11_XVaCreateNestedList = NULL; +#endif + +#ifdef SDL_VIDEO_DRIVER_X11_DYNAMIC + for (i = 0; i < SDL_arraysize(x11libs); i++) { + if (x11libs[i].lib) { + SDL_UnloadObject(x11libs[i].lib); + x11libs[i].lib = NULL; + } + } +#endif + } + } +} + +// returns non-zero if all needed symbols were loaded. +bool SDL_X11_LoadSymbols(void) +{ + bool result = true; // always succeed if not using Dynamic X11 stuff. + + // deal with multiple modules (dga, x11, etc) needing these symbols... + if (x11_load_refcount++ == 0) { +#ifdef SDL_VIDEO_DRIVER_X11_DYNAMIC + int i; + int *thismod = NULL; + for (i = 0; i < SDL_arraysize(x11libs); i++) { + if (x11libs[i].libname) { + x11libs[i].lib = SDL_LoadObject(x11libs[i].libname); + } + } + +#define SDL_X11_MODULE(modname) SDL_X11_HAVE_##modname = 1; // default yes +#include "SDL_x11sym.h" + +#define SDL_X11_MODULE(modname) thismod = &SDL_X11_HAVE_##modname; +#define SDL_X11_SYM(a, fn, x, y, z) X11_##fn = (SDL_DYNX11FN_##fn)X11_GetSym(#fn, thismod); +#include "SDL_x11sym.h" + +#ifdef X_HAVE_UTF8_STRING + X11_XCreateIC = (SDL_DYNX11FN_XCreateIC) + X11_GetSym("XCreateIC", &SDL_X11_HAVE_UTF8); + X11_XGetICValues = (SDL_DYNX11FN_XGetICValues) + X11_GetSym("XGetICValues", &SDL_X11_HAVE_UTF8); + X11_XSetICValues = (SDL_DYNX11FN_XSetICValues) + X11_GetSym("XSetICValues", &SDL_X11_HAVE_UTF8); + X11_XVaCreateNestedList = (SDL_DYNX11FN_XVaCreateNestedList) + X11_GetSym("XVaCreateNestedList", &SDL_X11_HAVE_UTF8); +#endif + + if (SDL_X11_HAVE_BASEXLIB) { + // all required symbols loaded. + SDL_ClearError(); + } else { + // in case something got loaded... + SDL_X11_UnloadSymbols(); + result = false; + } + +#else // no dynamic X11 + +#define SDL_X11_MODULE(modname) SDL_X11_HAVE_##modname = 1; // default yes +#define SDL_X11_SYM(a, fn, x, y, z) X11_##fn = (SDL_DYNX11FN_##fn)fn; +#include "SDL_x11sym.h" + +#ifdef X_HAVE_UTF8_STRING + X11_XCreateIC = XCreateIC; + X11_XGetICValues = XGetICValues; + X11_XSetICValues = XSetICValues; + X11_XVaCreateNestedList = XVaCreateNestedList; +#endif +#endif + } + + return result; +} + +#endif // SDL_VIDEO_DRIVER_X11 diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11dyn.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11dyn.h new file mode 100644 index 0000000..e9831fc --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11dyn.h @@ -0,0 +1,113 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifndef SDL_x11dyn_h_ +#define SDL_x11dyn_h_ + +#include +#include +#include +#include + +#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM +#include +#endif + +// Apparently some X11 systems can't include this multiple times... +#ifndef SDL_INCLUDED_XLIBINT_H +#define SDL_INCLUDED_XLIBINT_H 1 +#include +#endif + +#include +#include + +#ifndef NO_SHARED_MEMORY +#include +#include +#include +#endif + +#ifdef SDL_VIDEO_DRIVER_X11_XCURSOR +#include +#endif +#ifdef SDL_VIDEO_DRIVER_X11_XDBE +#include +#endif +#if defined(SDL_VIDEO_DRIVER_X11_XINPUT2) || defined(SDL_VIDEO_DRIVER_X11_XFIXES) +#include +#endif +#ifdef SDL_VIDEO_DRIVER_X11_XFIXES +#include +#endif +#ifdef SDL_VIDEO_DRIVER_X11_XSYNC +#include +#endif +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR +#include +#endif +#ifdef SDL_VIDEO_DRIVER_X11_XSCRNSAVER +#include +#endif +#ifdef SDL_VIDEO_DRIVER_X11_XSHAPE +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// evil function signatures... +typedef Bool (*SDL_X11_XESetWireToEventRetType)(Display *, XEvent *, xEvent *); +typedef int (*SDL_X11_XSynchronizeRetType)(Display *); +typedef Status (*SDL_X11_XESetEventToWireRetType)(Display *, XEvent *, xEvent *); + +extern bool SDL_X11_LoadSymbols(void); +extern void SDL_X11_UnloadSymbols(void); + +// Declare all the function pointers and wrappers... +#define SDL_X11_SYM(rc, fn, params, args, ret) \ + typedef rc(*SDL_DYNX11FN_##fn) params; \ + extern SDL_DYNX11FN_##fn X11_##fn; +#include "SDL_x11sym.h" + +// Annoying varargs entry point... +#ifdef X_HAVE_UTF8_STRING +typedef XIC (*SDL_DYNX11FN_XCreateIC)(XIM, ...); +typedef char *(*SDL_DYNX11FN_XGetICValues)(XIC, ...); +typedef char *(*SDL_DYNX11FN_XSetICValues)(XIC, ...); +typedef XVaNestedList (*SDL_DYNX11FN_XVaCreateNestedList)(int, ...); +extern SDL_DYNX11FN_XCreateIC X11_XCreateIC; +extern SDL_DYNX11FN_XGetICValues X11_XGetICValues; +extern SDL_DYNX11FN_XSetICValues X11_XSetICValues; +extern SDL_DYNX11FN_XVaCreateNestedList X11_XVaCreateNestedList; +#endif + +/* These SDL_X11_HAVE_* flags are here whether you have dynamic X11 or not. */ +#define SDL_X11_MODULE(modname) extern int SDL_X11_HAVE_##modname; +#include "SDL_x11sym.h" + +#ifdef __cplusplus +} +#endif + +#endif // !defined SDL_x11dyn_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11events.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11events.c new file mode 100644 index 0000000..02c2d90 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11events.c @@ -0,0 +1,2205 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifdef SDL_VIDEO_DRIVER_X11 + +#include +#include +#include +#include +#include // For INT_MAX + +#include "SDL_x11video.h" +#include "SDL_x11pen.h" +#include "SDL_x11touch.h" +#include "SDL_x11xinput2.h" +#include "SDL_x11xfixes.h" +#include "SDL_x11settings.h" +#include "../SDL_clipboard_c.h" +#include "SDL_x11xsync.h" +#include "../../core/unix/SDL_poll.h" +#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 "../SDL_sysvideo.h" + +#include + +#if 0 +#define DEBUG_XEVENTS +#endif + +#ifndef _NET_WM_MOVERESIZE_SIZE_TOPLEFT +#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 +#endif + +#ifndef _NET_WM_MOVERESIZE_SIZE_TOP +#define _NET_WM_MOVERESIZE_SIZE_TOP 1 +#endif + +#ifndef _NET_WM_MOVERESIZE_SIZE_TOPRIGHT +#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 +#endif + +#ifndef _NET_WM_MOVERESIZE_SIZE_RIGHT +#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3 +#endif + +#ifndef _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 +#endif + +#ifndef _NET_WM_MOVERESIZE_SIZE_BOTTOM +#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5 +#endif + +#ifndef _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 +#endif + +#ifndef _NET_WM_MOVERESIZE_SIZE_LEFT +#define _NET_WM_MOVERESIZE_SIZE_LEFT 7 +#endif + +#ifndef _NET_WM_MOVERESIZE_MOVE +#define _NET_WM_MOVERESIZE_MOVE 8 +#endif + +typedef struct +{ + unsigned char *data; + int format, count; + Atom type; +} SDL_x11Prop; + +/* Reads property + Must call X11_XFree on results + */ +static void X11_ReadProperty(SDL_x11Prop *p, Display *disp, Window w, Atom prop) +{ + unsigned char *ret = NULL; + Atom type; + int fmt; + unsigned long count; + unsigned long bytes_left; + int bytes_fetch = 0; + + do { + if (ret) { + X11_XFree(ret); + } + X11_XGetWindowProperty(disp, w, prop, 0, bytes_fetch, False, AnyPropertyType, &type, &fmt, &count, &bytes_left, &ret); + bytes_fetch += bytes_left; + } while (bytes_left != 0); + + p->data = ret; + p->format = fmt; + p->count = count; + p->type = type; +} + +/* Find text-uri-list in a list of targets and return it's atom + if available, else return None */ +static Atom X11_PickTarget(Display *disp, Atom list[], int list_count) +{ + Atom request = None; + char *name; + int i; + for (i = 0; i < list_count && request == None; i++) { + name = X11_XGetAtomName(disp, list[i]); + // Preferred MIME targets + if ((SDL_strcmp("text/uri-list", name) == 0) || + (SDL_strcmp("text/plain;charset=utf-8", name) == 0) || + (SDL_strcmp("UTF8_STRING", name) == 0)) { + request = list[i]; + } + // Fallback MIME targets + if ((SDL_strcmp("text/plain", name) == 0) || + (SDL_strcmp("TEXT", name) == 0)) { + if (request == None) { + request = list[i]; + } + } + X11_XFree(name); + } + return request; +} + +/* Wrapper for X11_PickTarget for a maximum of three targets, a special + case in the Xdnd protocol */ +static Atom X11_PickTargetFromAtoms(Display *disp, Atom a0, Atom a1, Atom a2) +{ + int count = 0; + Atom atom[3]; + if (a0 != None) { + atom[count++] = a0; + } + if (a1 != None) { + atom[count++] = a1; + } + if (a2 != None) { + atom[count++] = a2; + } + return X11_PickTarget(disp, atom, count); +} + +struct KeyRepeatCheckData +{ + XEvent *event; + bool found; +}; + +static Bool X11_KeyRepeatCheckIfEvent(Display *display, XEvent *chkev, + XPointer arg) +{ + struct KeyRepeatCheckData *d = (struct KeyRepeatCheckData *)arg; + if (chkev->type == KeyPress && chkev->xkey.keycode == d->event->xkey.keycode && chkev->xkey.time - d->event->xkey.time < 2) { + d->found = true; + } + return False; +} + +/* Check to see if this is a repeated key. + (idea shamelessly lifted from GII -- thanks guys! :) + */ +static bool X11_KeyRepeat(Display *display, XEvent *event) +{ + XEvent dummyev; + struct KeyRepeatCheckData d; + d.event = event; + d.found = false; + if (X11_XPending(display)) { + X11_XCheckIfEvent(display, &dummyev, X11_KeyRepeatCheckIfEvent, (XPointer)&d); + } + return d.found; +} + +static bool X11_IsWheelEvent(Display *display, int button, int *xticks, int *yticks) +{ + /* according to the xlib docs, no specific mouse wheel events exist. + However, the defacto standard is that the vertical wheel is X buttons + 4 (up) and 5 (down) and a horizontal wheel is 6 (left) and 7 (right). */ + + // Xlib defines "Button1" through 5, so we just use literals here. + switch (button) { + case 4: + *yticks = 1; + return true; + case 5: + *yticks = -1; + return true; + case 6: + *xticks = 1; + return true; + case 7: + *xticks = -1; + return true; + default: + break; + } + return false; +} + +// An X11 event hook +static SDL_X11EventHook g_X11EventHook = NULL; +static void *g_X11EventHookData = NULL; + +void SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata) +{ + g_X11EventHook = callback; + g_X11EventHookData = userdata; +} + +#ifdef SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS +static void X11_HandleGenericEvent(SDL_VideoDevice *_this, XEvent *xev) +{ + SDL_VideoData *videodata = _this->internal; + + // event is a union, so cookie == &event, but this is type safe. + XGenericEventCookie *cookie = &xev->xcookie; + if (X11_XGetEventData(videodata->display, cookie)) { + if (!g_X11EventHook || g_X11EventHook(g_X11EventHookData, xev)) { + X11_HandleXinput2Event(_this, cookie); + } + X11_XFreeEventData(videodata->display, cookie); + } +} +#endif // SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS + +static void X11_UpdateSystemKeyModifiers(SDL_VideoData *viddata) +{ + Window junk_window; + int x, y; + + X11_XQueryPointer(viddata->display, DefaultRootWindow(viddata->display), &junk_window, &junk_window, &x, &y, &x, &y, &viddata->xkb.xkb_modifiers); +} + +static void X11_ReconcileModifiers(SDL_VideoData *viddata) +{ + const Uint32 xk_modifiers = viddata->xkb.xkb_modifiers; + + /* If a modifier was activated by a keypress, it will be tied to the + * specific left/right key that initiated it. Otherwise, the ambiguous + * left/right combo is used. + */ + if (xk_modifiers & ShiftMask) { + if (!(viddata->xkb.sdl_modifiers & SDL_KMOD_SHIFT)) { + viddata->xkb.sdl_modifiers |= SDL_KMOD_SHIFT; + } + } else { + viddata->xkb.sdl_modifiers &= ~SDL_KMOD_SHIFT; + } + + if (xk_modifiers & ControlMask) { + if (!(viddata->xkb.sdl_modifiers & SDL_KMOD_CTRL)) { + viddata->xkb.sdl_modifiers |= SDL_KMOD_CTRL; + } + } else { + viddata->xkb.sdl_modifiers &= ~SDL_KMOD_CTRL; + } + + // Mod1 is used for the Alt keys + if (xk_modifiers & Mod1Mask) { + if (!(viddata->xkb.sdl_modifiers & SDL_KMOD_ALT)) { + viddata->xkb.sdl_modifiers |= SDL_KMOD_ALT; + } + } else { + viddata->xkb.sdl_modifiers &= ~SDL_KMOD_ALT; + } + + // Mod4 is used for the Super (aka GUI/Logo) keys. + if (xk_modifiers & Mod4Mask) { + if (!(viddata->xkb.sdl_modifiers & SDL_KMOD_GUI)) { + viddata->xkb.sdl_modifiers |= SDL_KMOD_GUI; + } + } else { + viddata->xkb.sdl_modifiers &= ~SDL_KMOD_GUI; + } + + // Mod3 is typically Level 5 shift. + if (xk_modifiers & Mod3Mask) { + viddata->xkb.sdl_modifiers |= SDL_KMOD_LEVEL5; + } else { + viddata->xkb.sdl_modifiers &= ~SDL_KMOD_LEVEL5; + } + + // Mod5 is typically Level 3 shift (aka AltGr). + if (xk_modifiers & Mod5Mask) { + viddata->xkb.sdl_modifiers |= SDL_KMOD_MODE; + } else { + viddata->xkb.sdl_modifiers &= ~SDL_KMOD_MODE; + } + + if (xk_modifiers & LockMask) { + viddata->xkb.sdl_modifiers |= SDL_KMOD_CAPS; + } else { + viddata->xkb.sdl_modifiers &= ~SDL_KMOD_CAPS; + } + + if (xk_modifiers & viddata->xkb.numlock_mask) { + viddata->xkb.sdl_modifiers |= SDL_KMOD_NUM; + } else { + viddata->xkb.sdl_modifiers &= ~SDL_KMOD_NUM; + } + + if (xk_modifiers & viddata->xkb.scrolllock_mask) { + viddata->xkb.sdl_modifiers |= SDL_KMOD_SCROLL; + } else { + viddata->xkb.sdl_modifiers &= ~SDL_KMOD_SCROLL; + } + + SDL_SetModState(viddata->xkb.sdl_modifiers); +} + +static void X11_HandleModifierKeys(SDL_VideoData *viddata, SDL_Scancode scancode, bool pressed, bool allow_reconciliation) +{ + const SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false); + SDL_Keymod mod = SDL_KMOD_NONE; + bool reconcile = false; + + /* SDL clients expect modifier state to be activated at the same time as the + * source keypress, so we set pressed modifier state with the usual modifier + * keys here, as the explicit modifier event won't arrive until after the + * keypress event. If this is wrong, it will be corrected when the explicit + * modifier state is checked. + */ + switch (keycode) { + case SDLK_LSHIFT: + mod = SDL_KMOD_LSHIFT; + break; + case SDLK_RSHIFT: + mod = SDL_KMOD_RSHIFT; + break; + case SDLK_LCTRL: + mod = SDL_KMOD_LCTRL; + break; + case SDLK_RCTRL: + mod = SDL_KMOD_RCTRL; + break; + case SDLK_LALT: + mod = SDL_KMOD_LALT; + break; + case SDLK_RALT: + mod = SDL_KMOD_RALT; + break; + case SDLK_LGUI: + mod = SDL_KMOD_LGUI; + break; + case SDLK_RGUI: + mod = SDL_KMOD_RGUI; + break; + case SDLK_MODE: + mod = SDL_KMOD_MODE; + break; + case SDLK_LEVEL5_SHIFT: + mod = SDL_KMOD_LEVEL5; + break; + case SDLK_CAPSLOCK: + case SDLK_NUMLOCKCLEAR: + case SDLK_SCROLLLOCK: + { + /* For locking modifier keys, query the lock state directly, or we may have to wait until the next + * key press event to know if a lock was actually activated from the key event. + */ + unsigned int cur_mask = viddata->xkb.xkb_modifiers; + X11_UpdateSystemKeyModifiers(viddata); + + if (viddata->xkb.xkb_modifiers & LockMask) { + cur_mask |= LockMask; + } else { + cur_mask &= ~LockMask; + } + if (viddata->xkb.xkb_modifiers & viddata->xkb.numlock_mask) { + cur_mask |= viddata->xkb.numlock_mask; + } else { + cur_mask &= ~viddata->xkb.numlock_mask; + } + if (viddata->xkb.xkb_modifiers & viddata->xkb.scrolllock_mask) { + cur_mask |= viddata->xkb.scrolllock_mask; + } else { + cur_mask &= ~viddata->xkb.scrolllock_mask; + } + + viddata->xkb.xkb_modifiers = cur_mask; + } SDL_FALLTHROUGH; + default: + reconcile = true; + break; + } + + if (pressed) { + viddata->xkb.sdl_modifiers |= mod; + } else { + viddata->xkb.sdl_modifiers &= ~mod; + } + + if (allow_reconciliation) { + if (reconcile) { + X11_ReconcileModifiers(viddata); + } else { + SDL_SetModState(viddata->xkb.sdl_modifiers); + } + } +} + +void X11_ReconcileKeyboardState(SDL_VideoDevice *_this) +{ + SDL_VideoData *videodata = _this->internal; + Display *display = videodata->display; + char keys[32]; + int keycode; + const bool *keyboardState; + + X11_XQueryKeymap(display, keys); + + keyboardState = SDL_GetKeyboardState(0); + for (keycode = 0; keycode < SDL_arraysize(videodata->key_layout); ++keycode) { + SDL_Scancode scancode = videodata->key_layout[keycode]; + bool x11KeyPressed = (keys[keycode / 8] & (1 << (keycode % 8))) != 0; + bool sdlKeyPressed = keyboardState[scancode]; + + if (x11KeyPressed && !sdlKeyPressed) { + // Only update modifier state for keys that are pressed in another application + switch (SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false)) { + case SDLK_LCTRL: + case SDLK_RCTRL: + case SDLK_LSHIFT: + case SDLK_RSHIFT: + case SDLK_LALT: + case SDLK_RALT: + case SDLK_LGUI: + case SDLK_RGUI: + case SDLK_MODE: + case SDLK_LEVEL5_SHIFT: + X11_HandleModifierKeys(videodata, scancode, true, false); + SDL_SendKeyboardKeyIgnoreModifiers(0, SDL_GLOBAL_KEYBOARD_ID, keycode, scancode, true); + break; + default: + break; + } + } else if (!x11KeyPressed && sdlKeyPressed) { + X11_HandleModifierKeys(videodata, scancode, false, false); + SDL_SendKeyboardKeyIgnoreModifiers(0, SDL_GLOBAL_KEYBOARD_ID, keycode, scancode, false); + } + } + + X11_UpdateSystemKeyModifiers(videodata); + X11_ReconcileModifiers(videodata); +} + +static void X11_DispatchFocusIn(SDL_VideoDevice *_this, SDL_WindowData *data) +{ +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: Dispatching FocusIn", data->xwindow); +#endif + SDL_SetKeyboardFocus(data->window); + X11_ReconcileKeyboardState(_this); +#ifdef X_HAVE_UTF8_STRING + if (data->ic) { + X11_XSetICFocus(data->ic); + } +#endif + if (data->flashing_window) { + X11_FlashWindow(_this, data->window, SDL_FLASH_CANCEL); + } +} + +static void X11_DispatchFocusOut(SDL_VideoDevice *_this, SDL_WindowData *data) +{ +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: Dispatching FocusOut", data->xwindow); +#endif + /* If another window has already processed a focus in, then don't try to + * remove focus here. Doing so will incorrectly remove focus from that + * window, and the focus lost event for this window will have already + * been dispatched anyway. */ + if (data->window == SDL_GetKeyboardFocus()) { + SDL_SetKeyboardFocus(NULL); + } +#ifdef X_HAVE_UTF8_STRING + if (data->ic) { + X11_XUnsetICFocus(data->ic); + } +#endif +} + +static void X11_DispatchMapNotify(SDL_WindowData *data) +{ + SDL_Window *window = data->window; + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0); + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_SHOWN, 0, 0); + if (!(window->flags & SDL_WINDOW_HIDDEN) && (window->flags & SDL_WINDOW_INPUT_FOCUS)) { + SDL_UpdateWindowGrab(window); + } +} + +static void X11_DispatchUnmapNotify(SDL_WindowData *data) +{ + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIDDEN, 0, 0); + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); +} + +static void DispatchWindowMove(SDL_VideoDevice *_this, const SDL_WindowData *data, const SDL_Point *point) +{ + SDL_VideoData *videodata = _this->internal; + SDL_Window *window = data->window; + Display *display = videodata->display; + XEvent evt; + + // !!! FIXME: we need to regrab this if necessary when the drag is done. + X11_XUngrabPointer(display, 0L); + X11_XFlush(display); + + evt.xclient.type = ClientMessage; + evt.xclient.window = data->xwindow; + evt.xclient.message_type = videodata->atoms._NET_WM_MOVERESIZE; + evt.xclient.format = 32; + evt.xclient.data.l[0] = (size_t)window->x + point->x; + evt.xclient.data.l[1] = (size_t)window->y + point->y; + evt.xclient.data.l[2] = _NET_WM_MOVERESIZE_MOVE; + evt.xclient.data.l[3] = Button1; + evt.xclient.data.l[4] = 0; + X11_XSendEvent(display, DefaultRootWindow(display), False, SubstructureRedirectMask | SubstructureNotifyMask, &evt); + + X11_XSync(display, 0); +} + +static void ScheduleWindowMove(SDL_VideoDevice *_this, SDL_WindowData *data, const SDL_Point *point) +{ + data->pending_move = true; + data->pending_move_point = *point; +} + +static void InitiateWindowResize(SDL_VideoDevice *_this, const SDL_WindowData *data, const SDL_Point *point, int direction) +{ + SDL_VideoData *videodata = _this->internal; + SDL_Window *window = data->window; + Display *display = videodata->display; + XEvent evt; + + if (direction < _NET_WM_MOVERESIZE_SIZE_TOPLEFT || direction > _NET_WM_MOVERESIZE_SIZE_LEFT) { + return; + } + + // !!! FIXME: we need to regrab this if necessary when the drag is done. + X11_XUngrabPointer(display, 0L); + X11_XFlush(display); + + evt.xclient.type = ClientMessage; + evt.xclient.window = data->xwindow; + evt.xclient.message_type = videodata->atoms._NET_WM_MOVERESIZE; + evt.xclient.format = 32; + evt.xclient.data.l[0] = (size_t)window->x + point->x; + evt.xclient.data.l[1] = (size_t)window->y + point->y; + evt.xclient.data.l[2] = direction; + evt.xclient.data.l[3] = Button1; + evt.xclient.data.l[4] = 0; + X11_XSendEvent(display, DefaultRootWindow(display), False, SubstructureRedirectMask | SubstructureNotifyMask, &evt); + + X11_XSync(display, 0); +} + +bool X11_ProcessHitTest(SDL_VideoDevice *_this, SDL_WindowData *data, const float x, const float y, bool force_new_result) +{ + SDL_Window *window = data->window; + if (!window->hit_test) return false; + const SDL_Point point = { (int)x, (int)y }; + SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data); + if (!force_new_result && rc == data->hit_test_result) { + return true; + } + X11_SetHitTestCursor(rc); + data->hit_test_result = rc; + return true; +} + +bool X11_TriggerHitTestAction(SDL_VideoDevice *_this, SDL_WindowData *data, const float x, const float y) +{ + SDL_Window *window = data->window; + + if (window->hit_test) { + const SDL_Point point = { (int)x, (int)y }; + static const int directions[] = { + _NET_WM_MOVERESIZE_SIZE_TOPLEFT, _NET_WM_MOVERESIZE_SIZE_TOP, + _NET_WM_MOVERESIZE_SIZE_TOPRIGHT, _NET_WM_MOVERESIZE_SIZE_RIGHT, + _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT, _NET_WM_MOVERESIZE_SIZE_BOTTOM, + _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT, _NET_WM_MOVERESIZE_SIZE_LEFT + }; + + switch (data->hit_test_result) { + case SDL_HITTEST_DRAGGABLE: + /* Some window managers get in a bad state when a move event starts while input is transitioning + to the SDL window. This can happen when clicking on a drag region of an unfocused window + where the same mouse down event will trigger a drag event and a window activate. */ + if (data->window->flags & SDL_WINDOW_INPUT_FOCUS) { + DispatchWindowMove(_this, data, &point); + } else { + ScheduleWindowMove(_this, data, &point); + } + return true; + + case SDL_HITTEST_RESIZE_TOPLEFT: + case SDL_HITTEST_RESIZE_TOP: + case SDL_HITTEST_RESIZE_TOPRIGHT: + case SDL_HITTEST_RESIZE_RIGHT: + case SDL_HITTEST_RESIZE_BOTTOMRIGHT: + case SDL_HITTEST_RESIZE_BOTTOM: + case SDL_HITTEST_RESIZE_BOTTOMLEFT: + case SDL_HITTEST_RESIZE_LEFT: + InitiateWindowResize(_this, data, &point, directions[data->hit_test_result - SDL_HITTEST_RESIZE_TOPLEFT]); + return true; + + default: + return false; + } + } + + return false; +} + +static void X11_UpdateUserTime(SDL_WindowData *data, const unsigned long latest) +{ + if (latest && (latest != data->user_time)) { + SDL_VideoData *videodata = data->videodata; + Display *display = videodata->display; + X11_XChangeProperty(display, data->xwindow, videodata->atoms._NET_WM_USER_TIME, + XA_CARDINAL, 32, PropModeReplace, + (const unsigned char *)&latest, 1); +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: updating _NET_WM_USER_TIME to %lu", data->xwindow, latest); +#endif + data->user_time = latest; + } +} + +static void X11_HandleClipboardEvent(SDL_VideoDevice *_this, const XEvent *xevent) +{ + int i; + SDL_VideoData *videodata = _this->internal; + Display *display = videodata->display; + + SDL_assert(videodata->clipboard_window != None); + SDL_assert(xevent->xany.window == videodata->clipboard_window); + + switch (xevent->type) { + // Copy the selection from our own CUTBUFFER to the requested property + case SelectionRequest: + { + const XSelectionRequestEvent *req = &xevent->xselectionrequest; + XEvent sevent; + int mime_formats; + unsigned char *seln_data; + size_t seln_length = 0; + Atom XA_TARGETS = videodata->atoms.TARGETS; + SDLX11_ClipboardData *clipboard; + +#ifdef DEBUG_XEVENTS + char *atom_name; + atom_name = X11_XGetAtomName(display, req->target); + SDL_Log("window CLIPBOARD: SelectionRequest (requestor = 0x%lx, target = 0x%lx, mime_type = %s)", + req->requestor, req->target, atom_name); + if (atom_name) { + X11_XFree(atom_name); + } +#endif + + if (req->selection == XA_PRIMARY) { + clipboard = &videodata->primary_selection; + } else { + clipboard = &videodata->clipboard; + } + + SDL_zero(sevent); + sevent.xany.type = SelectionNotify; + sevent.xselection.selection = req->selection; + sevent.xselection.target = None; + sevent.xselection.property = None; // tell them no by default + sevent.xselection.requestor = req->requestor; + sevent.xselection.time = req->time; + + /* !!! FIXME: We were probably storing this on the root window + because an SDL window might go away...? but we don't have to do + this now (or ever, really). */ + + if (req->target == XA_TARGETS) { + Atom *supportedFormats; + supportedFormats = SDL_malloc((clipboard->mime_count + 1) * sizeof(Atom)); + supportedFormats[0] = XA_TARGETS; + mime_formats = 1; + for (i = 0; i < clipboard->mime_count; ++i) { + supportedFormats[mime_formats++] = X11_XInternAtom(display, clipboard->mime_types[i], False); + } + X11_XChangeProperty(display, req->requestor, req->property, + XA_ATOM, 32, PropModeReplace, + (unsigned char *)supportedFormats, + mime_formats); + sevent.xselection.property = req->property; + sevent.xselection.target = XA_TARGETS; + SDL_free(supportedFormats); + } else { + if (clipboard->callback) { + for (i = 0; i < clipboard->mime_count; ++i) { + const char *mime_type = clipboard->mime_types[i]; + if (X11_XInternAtom(display, mime_type, False) != req->target) { + continue; + } + + // FIXME: We don't support the X11 INCR protocol for large clipboards. Do we want that? - Yes, yes we do. + // This is a safe cast, XChangeProperty() doesn't take a const value, but it doesn't modify the data + seln_data = (unsigned char *)clipboard->callback(clipboard->userdata, mime_type, &seln_length); + if (seln_data) { + X11_XChangeProperty(display, req->requestor, req->property, + req->target, 8, PropModeReplace, + seln_data, seln_length); + sevent.xselection.property = req->property; + sevent.xselection.target = req->target; + } + break; + } + } + } + X11_XSendEvent(display, req->requestor, False, 0, &sevent); + X11_XSync(display, False); + } break; + + case SelectionNotify: + { + const XSelectionEvent *xsel = &xevent->xselection; +#ifdef DEBUG_XEVENTS + const char *propName = xsel->property ? X11_XGetAtomName(display, xsel->property) : "None"; + const char *targetName = xsel->target ? X11_XGetAtomName(display, xsel->target) : "None"; + + SDL_Log("window CLIPBOARD: SelectionNotify (requestor = 0x%lx, target = %s, property = %s)", + xsel->requestor, targetName, propName); +#endif + if (xsel->target == videodata->atoms.TARGETS && xsel->property == videodata->atoms.SDL_FORMATS) { + /* the new mime formats are the SDL_FORMATS property as an array of Atoms */ + Atom atom = None; + Atom *patom; + unsigned char* data = NULL; + int format_property = 0; + unsigned long length = 0; + unsigned long bytes_left = 0; + int j; + + X11_XGetWindowProperty(display, GetWindow(_this), videodata->atoms.SDL_FORMATS, 0, 200, + 0, XA_ATOM, &atom, &format_property, &length, &bytes_left, &data); + + int allocationsize = (length + 1) * sizeof(char*); + for (j = 0, patom = (Atom*)data; j < length; j++, patom++) { + char *atomStr = X11_XGetAtomName(display, *patom); + allocationsize += SDL_strlen(atomStr) + 1; + X11_XFree(atomStr); + } + + char **new_mime_types = SDL_AllocateTemporaryMemory(allocationsize); + if (new_mime_types) { + char *strPtr = (char *)(new_mime_types + length + 1); + + for (j = 0, patom = (Atom*)data; j < length; j++, patom++) { + char *atomStr = X11_XGetAtomName(display, *patom); + new_mime_types[j] = strPtr; + strPtr = stpcpy(strPtr, atomStr) + 1; + X11_XFree(atomStr); + } + new_mime_types[length] = NULL; + + SDL_SendClipboardUpdate(false, new_mime_types, length); + } + + if (data) { + X11_XFree(data); + } + } + + videodata->selection_waiting = false; + } break; + + case SelectionClear: + { + Atom XA_CLIPBOARD = videodata->atoms.CLIPBOARD; + SDLX11_ClipboardData *clipboard = NULL; + +#ifdef DEBUG_XEVENTS + SDL_Log("window CLIPBOARD: SelectionClear (requestor = 0x%lx, target = 0x%lx)", + xevent->xselection.requestor, xevent->xselection.target); +#endif + + if (xevent->xselectionclear.selection == XA_PRIMARY) { + clipboard = &videodata->primary_selection; + } else if (XA_CLIPBOARD != None && xevent->xselectionclear.selection == XA_CLIPBOARD) { + clipboard = &videodata->clipboard; + } + if (clipboard && clipboard->callback) { + if (clipboard->sequence) { + SDL_CancelClipboardData(clipboard->sequence); + } else { + SDL_free(clipboard->userdata); + } + SDL_zerop(clipboard); + } + } break; + + case PropertyNotify: + { + char *name_of_atom = X11_XGetAtomName(display, xevent->xproperty.atom); + + if (SDL_strncmp(name_of_atom, "SDL_SELECTION", sizeof("SDL_SELECTION") - 1) == 0 && xevent->xproperty.state == PropertyNewValue) { + videodata->selection_incr_waiting = false; + } + + if (name_of_atom) { + X11_XFree(name_of_atom); + } + } break; + } +} + +static void X11_HandleSettingsEvent(SDL_VideoDevice *_this, const XEvent *xevent) +{ + SDL_VideoData *videodata = _this->internal; + + 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; + + unmap = (XUnmapEvent *)arg; + + return ev->type == MapNotify && + ev->xmap.window == unmap->window && + ev->xmap.serial == unmap->serial; +} + +static Bool isReparentNotify(Display *display, XEvent *ev, XPointer arg) +{ + XUnmapEvent *unmap; + + unmap = (XUnmapEvent *)arg; + + return ev->type == ReparentNotify && + ev->xreparent.window == unmap->window && + ev->xreparent.serial == unmap->serial; +} + +static bool IsHighLatin1(const char *string, int length) +{ + while (length-- > 0) { + Uint8 ch = (Uint8)*string; + if (ch >= 0x80) { + return true; + } + ++string; + } + return false; +} + +static int XLookupStringAsUTF8(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, XComposeStatus *status_in_out) +{ + int result = X11_XLookupString(event_struct, buffer_return, bytes_buffer, keysym_return, status_in_out); + if (IsHighLatin1(buffer_return, result)) { + char *utf8_text = SDL_iconv_string("UTF-8", "ISO-8859-1", buffer_return, result + 1); + if (utf8_text) { + SDL_strlcpy(buffer_return, utf8_text, bytes_buffer); + SDL_free(utf8_text); + return SDL_strlen(buffer_return); + } else { + return 0; + } + } + return result; +} + +SDL_WindowData *X11_FindWindow(SDL_VideoDevice *_this, Window window) +{ + const SDL_VideoData *videodata = _this->internal; + int i; + + if (videodata && videodata->windowlist) { + for (i = 0; i < videodata->numwindows; ++i) { + if ((videodata->windowlist[i] != NULL) && + (videodata->windowlist[i]->xwindow == window)) { + return videodata->windowlist[i]; + } + } + } + return NULL; +} + +Uint64 X11_GetEventTimestamp(unsigned long time) +{ + // FIXME: Get the event time in the SDL tick time base + return SDL_GetTicksNS(); +} + +void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_KeyboardID keyboardID, XEvent *xevent) +{ + SDL_VideoData *videodata = _this->internal; + Display *display = videodata->display; + KeyCode keycode = xevent->xkey.keycode; + KeySym keysym = NoSymbol; + int text_length = 0; + char text[64]; + Status status = 0; + bool handled_by_ime = false; + bool pressed = (xevent->type == KeyPress); + SDL_Scancode scancode = videodata->key_layout[keycode]; + Uint64 timestamp = X11_GetEventTimestamp(xevent->xkey.time); + +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx %s (X11 keycode = 0x%X)", xevent->xany.window, (xevent->type == KeyPress ? "KeyPress" : "KeyRelease"), xevent->xkey.keycode); +#endif +#ifdef DEBUG_SCANCODES + if (scancode == SDL_SCANCODE_UNKNOWN && keycode) { + int min_keycode, max_keycode; + X11_XDisplayKeycodes(display, &min_keycode, &max_keycode); + keysym = X11_KeyCodeToSym(_this, keycode, xevent->xkey.state >> 13); + SDL_Log("The key you just pressed is not recognized by SDL. To help get this fixed, please report this to the SDL forums/mailing list X11 KeyCode %d (%d), X11 KeySym 0x%lX (%s).", + keycode, keycode - min_keycode, keysym, + X11_XKeysymToString(keysym)); + } +#endif // DEBUG SCANCODES + + text[0] = '\0'; + videodata->xkb.xkb_modifiers = xevent->xkey.state; + + if (SDL_TextInputActive(windowdata->window)) { + // filter events catches XIM events and sends them to the correct handler + if (X11_XFilterEvent(xevent, None)) { +#ifdef DEBUG_XEVENTS + SDL_Log("Filtered event type = %d display = %p window = 0x%lx", + xevent->type, xevent->xany.display, xevent->xany.window); +#endif + handled_by_ime = true; + } + + if (!handled_by_ime) { +#ifdef X_HAVE_UTF8_STRING + if (windowdata->ic && xevent->type == KeyPress) { + text_length = X11_Xutf8LookupString(windowdata->ic, &xevent->xkey, text, sizeof(text) - 1, + &keysym, &status); + } else { + text_length = XLookupStringAsUTF8(&xevent->xkey, text, sizeof(text) - 1, &keysym, NULL); + } +#else + text_length = XLookupStringAsUTF8(&xevent->xkey, text, sizeof(text) - 1, &keysym, NULL); +#endif + } + } + + if (!handled_by_ime) { + if (pressed) { + X11_HandleModifierKeys(videodata, scancode, true, true); + SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true); + + if (*text) { + text[text_length] = '\0'; + X11_ClearComposition(windowdata); + SDL_SendKeyboardText(text); + } + } else { + if (X11_KeyRepeat(display, xevent)) { + // We're about to get a repeated key down, ignore the key up + return; + } + + X11_HandleModifierKeys(videodata, scancode, false, true); + SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, false); + } + } + + if (pressed) { + X11_UpdateUserTime(windowdata, xevent->xkey.time); + } +} + +void X11_HandleButtonPress(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_MouseID mouseID, int button, float x, float y, unsigned long time) +{ + SDL_Window *window = windowdata->window; + const SDL_VideoData *videodata = _this->internal; + Display *display = videodata->display; + int xticks = 0, yticks = 0; + Uint64 timestamp = X11_GetEventTimestamp(time); + +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: ButtonPress (X11 button = %d)", windowdata->xwindow, button); +#endif + + SDL_Mouse *mouse = SDL_GetMouse(); + if (!mouse->relative_mode && (x != mouse->x || y != mouse->y)) { + X11_ProcessHitTest(_this, windowdata, x, y, false); + SDL_SendMouseMotion(timestamp, window, mouseID, false, x, y); + } + + if (X11_IsWheelEvent(display, button, &xticks, &yticks)) { + SDL_SendMouseWheel(timestamp, window, mouseID, (float)-xticks, (float)yticks, SDL_MOUSEWHEEL_NORMAL); + } else { + bool ignore_click = false; + if (button > 7) { + /* X button values 4-7 are used for scrolling, so X1 is 8, X2 is 9, ... + => subtract (8-SDL_BUTTON_X1) to get value SDL expects */ + button -= (8 - SDL_BUTTON_X1); + } + if (button == Button1) { + if (X11_TriggerHitTestAction(_this, windowdata, x, y)) { + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0); + return; // don't pass this event on to app. + } + } + if (windowdata->last_focus_event_time) { + const int X11_FOCUS_CLICK_TIMEOUT = 10; + if (SDL_GetTicks() < (windowdata->last_focus_event_time + X11_FOCUS_CLICK_TIMEOUT)) { + ignore_click = !SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, false); + } + windowdata->last_focus_event_time = 0; + } + if (!ignore_click) { + SDL_SendMouseButton(timestamp, window, mouseID, button, true); + } + } + X11_UpdateUserTime(windowdata, time); +} + +void X11_HandleButtonRelease(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_MouseID mouseID, int button, unsigned long time) +{ + SDL_Window *window = windowdata->window; + const SDL_VideoData *videodata = _this->internal; + Display *display = videodata->display; + // The X server sends a Release event for each Press for wheels. Ignore them. + int xticks = 0, yticks = 0; + Uint64 timestamp = X11_GetEventTimestamp(time); + +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: ButtonRelease (X11 button = %d)", windowdata->xwindow, button); +#endif + if (!X11_IsWheelEvent(display, button, &xticks, &yticks)) { + if (button > 7) { + // see explanation at case ButtonPress + button -= (8 - SDL_BUTTON_X1); + } + SDL_SendMouseButton(timestamp, window, mouseID, button, false); + } +} + +void X11_GetBorderValues(SDL_WindowData *data) +{ + SDL_VideoData *videodata = data->videodata; + Display *display = videodata->display; + + Atom type; + int format; + unsigned long nitems, bytes_after; + unsigned char *property; + + // Some compositors will send extents even when the border hint is turned off. Ignore them in this case. + if (!(data->window->flags & SDL_WINDOW_BORDERLESS)) { + if (X11_XGetWindowProperty(display, data->xwindow, videodata->atoms._NET_FRAME_EXTENTS, 0, 16, 0, XA_CARDINAL, &type, &format, &nitems, &bytes_after, &property) == Success) { + if (type != None && nitems == 4) { + data->border_left = (int)((long *)property)[0]; + data->border_right = (int)((long *)property)[1]; + data->border_top = (int)((long *)property)[2]; + data->border_bottom = (int)((long *)property)[3]; + } + X11_XFree(property); + +#ifdef DEBUG_XEVENTS + SDL_Log("New _NET_FRAME_EXTENTS: left=%d right=%d, top=%d, bottom=%d", data->border_left, data->border_right, data->border_top, data->border_bottom); +#endif + } + } else { + data->border_left = data->border_top = data->border_right = data->border_bottom = 0; + } +} + +static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) +{ + SDL_VideoData *videodata = _this->internal; + Display *display; + SDL_WindowData *data; + XClientMessageEvent m; + int i; + + SDL_assert(videodata != NULL); + display = videodata->display; + + // filter events catches XIM events and sends them to the correct handler + // Key press/release events are filtered in X11_HandleKeyEvent() + if (xevent->type != KeyPress && xevent->type != KeyRelease) { + if (X11_XFilterEvent(xevent, None)) { +#ifdef DEBUG_XEVENTS + SDL_Log("Filtered event type = %d display = %p window = 0x%lx", + xevent->type, xevent->xany.display, xevent->xany.window); +#endif + return; + } + } + +#ifdef SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS + if (xevent->type == GenericEvent) { + X11_HandleGenericEvent(_this, xevent); + return; + } +#endif + + // Calling the event hook for generic events happens in X11_HandleGenericEvent(), where the event data is available + if (g_X11EventHook) { + if (!g_X11EventHook(g_X11EventHookData, xevent)) { + return; + } + } + +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR + if (videodata->xrandr_event_base && (xevent->type == (videodata->xrandr_event_base + RRNotify))) { + X11_HandleXRandREvent(_this, xevent); + } +#endif + +#ifdef DEBUG_XEVENTS + SDL_Log("X11 event type = %d display = %p window = 0x%lx", + xevent->type, xevent->xany.display, xevent->xany.window); +#endif + +#ifdef SDL_VIDEO_DRIVER_X11_XFIXES + if (SDL_X11_HAVE_XFIXES && + xevent->type == X11_GetXFixesSelectionNotifyEvent()) { + XFixesSelectionNotifyEvent *ev = (XFixesSelectionNotifyEvent *)xevent; + +#ifdef DEBUG_XEVENTS + SDL_Log("window CLIPBOARD: XFixesSelectionNotify (selection = %s)", + X11_XGetAtomName(display, ev->selection)); +#endif + + if (ev->subtype == XFixesSetSelectionOwnerNotify) + { + if (ev->selection != videodata->atoms.CLIPBOARD) + return; + + if (X11_XGetSelectionOwner(display, ev->selection) == videodata->clipboard_window) + return; + + /* when here we're notified that the clipboard had an external change, we request the + * available mime types by asking for a conversion to the TARGETS format. We should get a + * SelectionNotify event later, and when treating these results, we will push a ClipboardUpdated + * event + */ + + X11_XConvertSelection(display, videodata->atoms.CLIPBOARD, videodata->atoms.TARGETS, + videodata->atoms.SDL_FORMATS, GetWindow(_this), CurrentTime); + } + + return; + } +#endif // SDL_VIDEO_DRIVER_X11_XFIXES + + if ((videodata->clipboard_window != None) && + (videodata->clipboard_window == xevent->xany.window)) { + X11_HandleClipboardEvent(_this, 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) { + // The window for KeymapNotify, etc events is 0 + if (xevent->type == KeymapNotify) { +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: KeymapNotify!", xevent->xany.window); +#endif + if (SDL_GetKeyboardFocus() != NULL) { +#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM + if (videodata->xkb.desc_ptr) { + XkbStateRec state; + if (X11_XkbGetState(videodata->display, XkbUseCoreKbd, &state) == Success) { + if (state.group != videodata->xkb.current_group) { + // Only rebuild the keymap if the layout has changed. + videodata->xkb.current_group = state.group; + X11_UpdateKeymap(_this, true); + } + } + } +#endif + X11_ReconcileKeyboardState(_this); + } + } else if (xevent->type == MappingNotify) { + // Has the keyboard layout changed? + const int request = xevent->xmapping.request; + +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: MappingNotify!", xevent->xany.window); +#endif + if ((request == MappingKeyboard) || (request == MappingModifier)) { + X11_XRefreshKeyboardMapping(&xevent->xmapping); + } + + X11_UpdateKeymap(_this, true); + } else if (xevent->type == PropertyNotify && videodata && videodata->windowlist) { + char *name_of_atom = X11_XGetAtomName(display, xevent->xproperty.atom); + + if (SDL_strncmp(name_of_atom, "_ICC_PROFILE", sizeof("_ICC_PROFILE") - 1) == 0) { + XWindowAttributes attrib; + int screennum; + for (i = 0; i < videodata->numwindows; ++i) { + if (videodata->windowlist[i] != NULL) { + data = videodata->windowlist[i]; + X11_XGetWindowAttributes(display, data->xwindow, &attrib); + screennum = X11_XScreenNumberOfScreen(attrib.screen); + if (screennum == 0 && SDL_strcmp(name_of_atom, "_ICC_PROFILE") == 0) { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_ICCPROF_CHANGED, 0, 0); + } else if (SDL_strncmp(name_of_atom, "_ICC_PROFILE_", sizeof("_ICC_PROFILE_") - 1) == 0 && SDL_strlen(name_of_atom) > sizeof("_ICC_PROFILE_") - 1) { + int iccscreennum = SDL_atoi(&name_of_atom[sizeof("_ICC_PROFILE_") - 1]); + + if (screennum == iccscreennum) { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_ICCPROF_CHANGED, 0, 0); + } + } + } + } + } + + if (name_of_atom) { + X11_XFree(name_of_atom); + } + } + return; + } + + switch (xevent->type) { + + // Gaining mouse coverage? + case EnterNotify: + { + SDL_Mouse *mouse = SDL_GetMouse(); +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: EnterNotify! (%d,%d,%d)", xevent->xany.window, + xevent->xcrossing.x, + xevent->xcrossing.y, + xevent->xcrossing.mode); + if (xevent->xcrossing.mode == NotifyGrab) { + SDL_Log("Mode: NotifyGrab"); + } + if (xevent->xcrossing.mode == NotifyUngrab) { + SDL_Log("Mode: NotifyUngrab"); + } +#endif + SDL_SetMouseFocus(data->window); + + mouse->last_x = xevent->xcrossing.x; + mouse->last_y = xevent->xcrossing.y; + +#ifdef SDL_VIDEO_DRIVER_X11_XFIXES + { + // Only create the barriers if we have input focus + SDL_WindowData *windowdata = data->window->internal; + if ((data->pointer_barrier_active == true) && windowdata->window->flags & SDL_WINDOW_INPUT_FOCUS) { + X11_ConfineCursorWithFlags(_this, windowdata->window, &windowdata->barrier_rect, X11_BARRIER_HANDLED_BY_EVENT); + } + } +#endif + + if (!mouse->relative_mode) { + SDL_SendMouseMotion(0, data->window, SDL_GLOBAL_MOUSE_ID, false, (float)xevent->xcrossing.x, (float)xevent->xcrossing.y); + } + + // We ungrab in LeaveNotify, so we may need to grab again here + SDL_UpdateWindowGrab(data->window); + + X11_ProcessHitTest(_this, data, mouse->last_x, mouse->last_y, true); + } break; + // Losing mouse coverage? + case LeaveNotify: + { +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: LeaveNotify! (%d,%d,%d)", xevent->xany.window, + xevent->xcrossing.x, + xevent->xcrossing.y, + xevent->xcrossing.mode); + if (xevent->xcrossing.mode == NotifyGrab) { + SDL_Log("Mode: NotifyGrab"); + } + if (xevent->xcrossing.mode == NotifyUngrab) { + SDL_Log("Mode: NotifyUngrab"); + } +#endif + if (!SDL_GetMouse()->relative_mode) { + SDL_SendMouseMotion(0, data->window, SDL_GLOBAL_MOUSE_ID, false, (float)xevent->xcrossing.x, (float)xevent->xcrossing.y); + } + + if (xevent->xcrossing.mode != NotifyGrab && + xevent->xcrossing.mode != NotifyUngrab && + xevent->xcrossing.detail != NotifyInferior) { + + /* In order for interaction with the window decorations and menu to work properly + on Mutter, we need to ungrab the keyboard when the the mouse leaves. */ + if (!(data->window->flags & SDL_WINDOW_FULLSCREEN)) { + X11_SetWindowKeyboardGrab(_this, data->window, false); + } + + SDL_SetMouseFocus(NULL); + } + } break; + + // Gaining input focus? + case FocusIn: + { + if (xevent->xfocus.mode == NotifyGrab || xevent->xfocus.mode == NotifyUngrab) { + // Someone is handling a global hotkey, ignore it +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: FocusIn (NotifyGrab/NotifyUngrab, ignoring)", xevent->xany.window); +#endif + break; + } + + if (xevent->xfocus.detail == NotifyInferior || xevent->xfocus.detail == NotifyPointer) { +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: FocusIn (NotifyInferior/NotifyPointer, ignoring)", xevent->xany.window); +#endif + break; + } +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: FocusIn!", xevent->xany.window); +#endif + if (!videodata->last_mode_change_deadline) /* no recent mode changes */ { + data->pending_focus = PENDING_FOCUS_NONE; + data->pending_focus_time = 0; + X11_DispatchFocusIn(_this, data); + } else { + data->pending_focus = PENDING_FOCUS_IN; + data->pending_focus_time = SDL_GetTicks() + PENDING_FOCUS_TIME; + } + data->last_focus_event_time = SDL_GetTicks(); + } break; + + // Losing input focus? + case FocusOut: + { + if (xevent->xfocus.mode == NotifyGrab || xevent->xfocus.mode == NotifyUngrab) { + // Someone is handling a global hotkey, ignore it +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: FocusOut (NotifyGrab/NotifyUngrab, ignoring)", xevent->xany.window); +#endif + break; + } + if (xevent->xfocus.detail == NotifyInferior || xevent->xfocus.detail == NotifyPointer) { + /* We still have focus if a child gets focus. We also don't + care about the position of the pointer when the keyboard + focus changed. */ +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: FocusOut (NotifyInferior/NotifyPointer, ignoring)", xevent->xany.window); +#endif + break; + } +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: FocusOut!", xevent->xany.window); +#endif + if (!videodata->last_mode_change_deadline) /* no recent mode changes */ { + data->pending_focus = PENDING_FOCUS_NONE; + data->pending_focus_time = 0; + X11_DispatchFocusOut(_this, data); + } else { + data->pending_focus = PENDING_FOCUS_OUT; + data->pending_focus_time = SDL_GetTicks() + PENDING_FOCUS_TIME; + } + +#ifdef SDL_VIDEO_DRIVER_X11_XFIXES + // Disable confinement if it is activated. + if (data->pointer_barrier_active == true) { + X11_ConfineCursorWithFlags(_this, data->window, NULL, X11_BARRIER_HANDLED_BY_EVENT); + } +#endif // SDL_VIDEO_DRIVER_X11_XFIXES + } break; + + + // Have we been iconified? + case UnmapNotify: + { + XEvent ev; + +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: UnmapNotify!", xevent->xany.window); +#endif + + if (X11_XCheckIfEvent(display, &ev, &isReparentNotify, (XPointer)&xevent->xunmap)) { + X11_XCheckIfEvent(display, &ev, &isMapNotify, (XPointer)&xevent->xunmap); + } else { + X11_DispatchUnmapNotify(data); + } + +#ifdef SDL_VIDEO_DRIVER_X11_XFIXES + // Disable confinement if the window gets hidden. + if (data->pointer_barrier_active == true) { + X11_ConfineCursorWithFlags(_this, data->window, NULL, X11_BARRIER_HANDLED_BY_EVENT); + } +#endif // SDL_VIDEO_DRIVER_X11_XFIXES + } break; + + // Have we been restored? + case MapNotify: + { +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: MapNotify!", xevent->xany.window); +#endif + X11_DispatchMapNotify(data); + +#ifdef SDL_VIDEO_DRIVER_X11_XFIXES + // Enable confinement if it was activated. + if (data->pointer_barrier_active == true) { + X11_ConfineCursorWithFlags(_this, data->window, &data->barrier_rect, X11_BARRIER_HANDLED_BY_EVENT); + } +#endif // SDL_VIDEO_DRIVER_X11_XFIXES + } break; + + // Have we been resized or moved? + case ConfigureNotify: + { +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: ConfigureNotify! (position: %d,%d, size: %dx%d)", xevent->xany.window, + xevent->xconfigure.x, xevent->xconfigure.y, + xevent->xconfigure.width, xevent->xconfigure.height); +#endif + // Real configure notify events are relative to the parent, synthetic events are absolute. + if (!xevent->xconfigure.send_event) + { + unsigned int NumChildren; + Window ChildReturn, Root, Parent; + Window *Children; + // Translate these coordinates back to relative to root + X11_XQueryTree(data->videodata->display, xevent->xconfigure.window, &Root, &Parent, &Children, &NumChildren); + X11_XTranslateCoordinates(xevent->xconfigure.display, + Parent, DefaultRootWindow(xevent->xconfigure.display), + xevent->xconfigure.x, xevent->xconfigure.y, + &xevent->xconfigure.x, &xevent->xconfigure.y, + &ChildReturn); + } + + if (xevent->xconfigure.x != data->last_xconfigure.x || + xevent->xconfigure.y != data->last_xconfigure.y) { + if (!data->size_move_event_flags) { + SDL_Window *w; + int x = xevent->xconfigure.x; + int y = xevent->xconfigure.y; + + data->pending_operation &= ~X11_PENDING_OP_MOVE; + SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y); + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, x, y); + + for (w = data->window->first_child; w; w = w->next_sibling) { + // Don't update hidden child popup windows, their relative position doesn't change + if (SDL_WINDOW_IS_POPUP(w) && !(w->flags & SDL_WINDOW_HIDDEN)) { + X11_UpdateWindowPosition(w, true); + } + } + } + } + +#ifdef SDL_VIDEO_DRIVER_X11_XSYNC + X11_HandleConfigure(data->window, &xevent->xconfigure); +#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */ + + if (xevent->xconfigure.width != data->last_xconfigure.width || + xevent->xconfigure.height != data->last_xconfigure.height) { + if (!data->size_move_event_flags) { + data->pending_operation &= ~X11_PENDING_OP_RESIZE; + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESIZED, + xevent->xconfigure.width, + xevent->xconfigure.height); + } + } + + data->last_xconfigure = xevent->xconfigure; + } break; + + // Have we been requested to quit (or another client message?) + case ClientMessage: + { + static int xdnd_version = 0; + + if (xevent->xclient.message_type == videodata->atoms.XdndEnter) { + + bool use_list = xevent->xclient.data.l[1] & 1; + data->xdnd_source = xevent->xclient.data.l[0]; + xdnd_version = (xevent->xclient.data.l[1] >> 24); +#ifdef DEBUG_XEVENTS + SDL_Log("XID of source window : 0x%lx", data->xdnd_source); + SDL_Log("Protocol version to use : %d", xdnd_version); + SDL_Log("More then 3 data types : %d", (int)use_list); +#endif + + if (use_list) { + // fetch conversion targets + SDL_x11Prop p; + X11_ReadProperty(&p, display, data->xdnd_source, videodata->atoms.XdndTypeList); + // pick one + data->xdnd_req = X11_PickTarget(display, (Atom *)p.data, p.count); + X11_XFree(p.data); + } else { + // pick from list of three + data->xdnd_req = X11_PickTargetFromAtoms(display, xevent->xclient.data.l[2], xevent->xclient.data.l[3], xevent->xclient.data.l[4]); + } + } else if (xevent->xclient.message_type == videodata->atoms.XdndLeave) { +#ifdef DEBUG_XEVENTS + SDL_Log("XID of source window : 0x%lx", xevent->xclient.data.l[0]); +#endif + SDL_SendDropComplete(data->window); + } else if (xevent->xclient.message_type == videodata->atoms.XdndPosition) { + +#ifdef DEBUG_XEVENTS + Atom act = videodata->atoms.XdndActionCopy; + if (xdnd_version >= 2) { + act = xevent->xclient.data.l[4]; + } + SDL_Log("Action requested by user is : %s", X11_XGetAtomName(display, act)); +#endif + { + // Drag and Drop position + int root_x, root_y, window_x, window_y; + Window ChildReturn; + root_x = xevent->xclient.data.l[2] >> 16; + root_y = xevent->xclient.data.l[2] & 0xffff; + // Translate from root to current window position + X11_XTranslateCoordinates(display, DefaultRootWindow(display), data->xwindow, + root_x, root_y, &window_x, &window_y, &ChildReturn); + + SDL_SendDropPosition(data->window, (float)window_x, (float)window_y); + } + + // reply with status + SDL_memset(&m, 0, sizeof(XClientMessageEvent)); + m.type = ClientMessage; + m.display = xevent->xclient.display; + m.window = xevent->xclient.data.l[0]; + m.message_type = videodata->atoms.XdndStatus; + m.format = 32; + m.data.l[0] = data->xwindow; + m.data.l[1] = (data->xdnd_req != None); + m.data.l[2] = 0; // specify an empty rectangle + m.data.l[3] = 0; + m.data.l[4] = videodata->atoms.XdndActionCopy; // we only accept copying anyway + + X11_XSendEvent(display, xevent->xclient.data.l[0], False, NoEventMask, (XEvent *)&m); + X11_XFlush(display); + } else if (xevent->xclient.message_type == videodata->atoms.XdndDrop) { + if (data->xdnd_req == None) { + // say again - not interested! + SDL_memset(&m, 0, sizeof(XClientMessageEvent)); + m.type = ClientMessage; + m.display = xevent->xclient.display; + m.window = xevent->xclient.data.l[0]; + m.message_type = videodata->atoms.XdndFinished; + m.format = 32; + m.data.l[0] = data->xwindow; + m.data.l[1] = 0; + m.data.l[2] = None; // fail! + X11_XSendEvent(display, xevent->xclient.data.l[0], False, NoEventMask, (XEvent *)&m); + } else { + // convert + if (xdnd_version >= 1) { + X11_XConvertSelection(display, videodata->atoms.XdndSelection, data->xdnd_req, videodata->atoms.PRIMARY, data->xwindow, xevent->xclient.data.l[2]); + } else { + X11_XConvertSelection(display, videodata->atoms.XdndSelection, data->xdnd_req, videodata->atoms.PRIMARY, data->xwindow, CurrentTime); + } + } + } else if ((xevent->xclient.message_type == videodata->atoms.WM_PROTOCOLS) && + (xevent->xclient.format == 32) && + (xevent->xclient.data.l[0] == videodata->atoms._NET_WM_PING)) { + Window root = DefaultRootWindow(display); + +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: _NET_WM_PING", xevent->xany.window); +#endif + xevent->xclient.window = root; + X11_XSendEvent(display, root, False, SubstructureRedirectMask | SubstructureNotifyMask, xevent); + break; + } + + else if ((xevent->xclient.message_type == videodata->atoms.WM_PROTOCOLS) && + (xevent->xclient.format == 32) && + (xevent->xclient.data.l[0] == videodata->atoms.WM_DELETE_WINDOW)) { + +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: WM_DELETE_WINDOW", xevent->xany.window); +#endif + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0); + break; + } else if ((xevent->xclient.message_type == videodata->atoms.WM_PROTOCOLS) && + (xevent->xclient.format == 32) && + (xevent->xclient.data.l[0] == videodata->atoms._NET_WM_SYNC_REQUEST)) { + +#ifdef DEBUG_XEVENTS + printf("window %p: _NET_WM_SYNC_REQUEST\n", data); +#endif +#ifdef SDL_VIDEO_DRIVER_X11_XSYNC + X11_HandleSyncRequest(data->window, &xevent->xclient); +#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */ + break; + } + } break; + + // Do we need to refresh ourselves? + case Expose: + { +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: Expose (count = %d)", xevent->xany.window, xevent->xexpose.count); +#endif + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_EXPOSED, 0, 0); + } break; + + /* Use XInput2 instead of the xevents API if possible, for: + - KeyPress + - KeyRelease + - MotionNotify + - ButtonPress + - ButtonRelease + XInput2 has more precise information, e.g., to distinguish different input devices. */ + case KeyPress: + case KeyRelease: + { + if (data->xinput2_keyboard_enabled) { + // This input is being handled by XInput2 + break; + } + + X11_HandleKeyEvent(_this, data, SDL_GLOBAL_KEYBOARD_ID, xevent); + } break; + + case MotionNotify: + { + if (data->xinput2_mouse_enabled && !data->mouse_grabbed) { + // This input is being handled by XInput2 + break; + } + + SDL_Mouse *mouse = SDL_GetMouse(); + if (!mouse->relative_mode) { +#ifdef DEBUG_MOTION + SDL_Log("window 0x%lx: X11 motion: %d,%d", xevent->xany.window, xevent->xmotion.x, xevent->xmotion.y); +#endif + + X11_ProcessHitTest(_this, data, (float)xevent->xmotion.x, (float)xevent->xmotion.y, false); + SDL_SendMouseMotion(0, data->window, SDL_GLOBAL_MOUSE_ID, false, (float)xevent->xmotion.x, (float)xevent->xmotion.y); + } + } break; + + case ButtonPress: + { + if (data->xinput2_mouse_enabled) { + // This input is being handled by XInput2 + break; + } + + X11_HandleButtonPress(_this, data, SDL_GLOBAL_MOUSE_ID, xevent->xbutton.button, + xevent->xbutton.x, xevent->xbutton.y, xevent->xbutton.time); + } break; + + case ButtonRelease: + { + if (data->xinput2_mouse_enabled) { + // This input is being handled by XInput2 + break; + } + + X11_HandleButtonRelease(_this, data, SDL_GLOBAL_MOUSE_ID, xevent->xbutton.button, xevent->xbutton.time); + } break; + + case PropertyNotify: + { +#ifdef DEBUG_XEVENTS + unsigned char *propdata; + int status, real_format; + Atom real_type; + unsigned long items_read, items_left; + + char *name = X11_XGetAtomName(display, xevent->xproperty.atom); + if (name) { + SDL_Log("window 0x%lx: PropertyNotify: %s %s time=%lu", xevent->xany.window, name, (xevent->xproperty.state == PropertyDelete) ? "deleted" : "changed", xevent->xproperty.time); + X11_XFree(name); + } + + status = X11_XGetWindowProperty(display, data->xwindow, xevent->xproperty.atom, 0L, 8192L, False, AnyPropertyType, &real_type, &real_format, &items_read, &items_left, &propdata); + if (status == Success && items_read > 0) { + if (real_type == XA_INTEGER) { + int *values = (int *)propdata; + + SDL_Log("{"); + for (i = 0; i < items_read; i++) { + SDL_Log(" %d", values[i]); + } + SDL_Log(" }"); + } else if (real_type == XA_CARDINAL) { + if (real_format == 32) { + Uint32 *values = (Uint32 *)propdata; + + SDL_Log("{"); + for (i = 0; i < items_read; i++) { + SDL_Log(" %d", values[i]); + } + SDL_Log(" }"); + } else if (real_format == 16) { + Uint16 *values = (Uint16 *)propdata; + + SDL_Log("{"); + for (i = 0; i < items_read; i++) { + SDL_Log(" %d", values[i]); + } + SDL_Log(" }"); + } else if (real_format == 8) { + Uint8 *values = (Uint8 *)propdata; + + SDL_Log("{"); + for (i = 0; i < items_read; i++) { + SDL_Log(" %d", values[i]); + } + SDL_Log(" }"); + } + } else if (real_type == XA_STRING || + real_type == videodata->atoms.UTF8_STRING) { + SDL_Log("{ \"%s\" }", propdata); + } else if (real_type == XA_ATOM) { + Atom *atoms = (Atom *)propdata; + + SDL_Log("{"); + for (i = 0; i < items_read; i++) { + char *atomname = X11_XGetAtomName(display, atoms[i]); + if (atomname) { + SDL_Log(" %s", atomname); + X11_XFree(atomname); + } + } + SDL_Log(" }"); + } else { + char *atomname = X11_XGetAtomName(display, real_type); + SDL_Log("Unknown type: 0x%lx (%s)", real_type, atomname ? atomname : "UNKNOWN"); + if (atomname) { + X11_XFree(atomname); + } + } + } + if (status == Success) { + X11_XFree(propdata); + } +#endif // DEBUG_XEVENTS + + /* Take advantage of this moment to make sure user_time has a + valid timestamp from the X server, so if we later try to + raise/restore this window, _NET_ACTIVE_WINDOW can have a + non-zero timestamp, even if there's never been a mouse or + key press to this window so far. Note that we don't try to + set _NET_WM_USER_TIME here, though. That's only for legit + user interaction with the window. */ + if (!data->user_time) { + data->user_time = xevent->xproperty.time; + } + + if (xevent->xproperty.atom == data->videodata->atoms._NET_WM_STATE) { + /* Get the new state from the window manager. + Compositing window managers can alter visibility of windows + without ever mapping / unmapping them, so we handle that here, + because they use the NETWM protocol to notify us of changes. + */ + const SDL_WindowFlags flags = X11_GetNetWMState(_this, data->window, xevent->xproperty.window); + const SDL_WindowFlags changed = flags ^ data->window->flags; + + if ((changed & (SDL_WINDOW_HIDDEN | SDL_WINDOW_FULLSCREEN)) != 0) { + if (flags & SDL_WINDOW_HIDDEN) { + X11_DispatchUnmapNotify(data); + } else { + X11_DispatchMapNotify(data); + } + } + + if (!SDL_WINDOW_IS_POPUP(data->window)) { + if (changed & SDL_WINDOW_FULLSCREEN) { + data->pending_operation &= ~X11_PENDING_OP_FULLSCREEN; + + if (flags & SDL_WINDOW_FULLSCREEN) { + if (!(flags & SDL_WINDOW_MINIMIZED)) { + const bool commit = SDL_memcmp(&data->window->current_fullscreen_mode, &data->requested_fullscreen_mode, sizeof(SDL_DisplayMode)) != 0; + + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0); + if (commit) { + /* This was initiated by the compositor, or the mode was changed between the request and the window + * becoming fullscreen. Switch to the application requested mode if necessary. + */ + SDL_copyp(&data->window->current_fullscreen_mode, &data->window->requested_fullscreen_mode); + SDL_UpdateFullscreenMode(data->window, SDL_FULLSCREEN_OP_UPDATE, true); + } else { + SDL_UpdateFullscreenMode(data->window, SDL_FULLSCREEN_OP_ENTER, false); + } + } + } else { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); + SDL_UpdateFullscreenMode(data->window, false, false); + + SDL_zero(data->requested_fullscreen_mode); + + // Need to restore or update any limits changed while the window was fullscreen. + X11_SetWindowMinMax(data->window, !!(flags & SDL_WINDOW_MAXIMIZED)); + + // Toggle the borders if they were forced on while creating a borderless fullscreen window. + if (data->fullscreen_borders_forced_on) { + data->toggle_borders = true; + data->fullscreen_borders_forced_on = false; + } + } + + if ((flags & SDL_WINDOW_FULLSCREEN) && + (data->border_top || data->border_left || data->border_bottom || data->border_right)) { + /* If the window is entering fullscreen and the borders are + * non-zero sized, turn off size events until the borders are + * shut off to avoid bogus window sizes and positions, and + * note that the old borders were non-zero for restoration. + */ + data->size_move_event_flags |= X11_SIZE_MOVE_EVENTS_WAIT_FOR_BORDERS; + data->previous_borders_nonzero = true; + } else if (!(flags & SDL_WINDOW_FULLSCREEN) && + data->previous_borders_nonzero && + (!data->border_top && !data->border_left && !data->border_bottom && !data->border_right)) { + /* If the window is leaving fullscreen and the current borders + * are zero sized, but weren't when entering fullscreen, turn + * off size events until the borders come back to avoid bogus + * window sizes and positions. + */ + data->size_move_event_flags |= X11_SIZE_MOVE_EVENTS_WAIT_FOR_BORDERS; + data->previous_borders_nonzero = false; + } else { + data->size_move_event_flags = 0; + data->previous_borders_nonzero = false; + + if (!(data->window->flags & SDL_WINDOW_FULLSCREEN) && data->toggle_borders) { + data->toggle_borders = false; + X11_SetWindowBordered(_this, data->window, !(data->window->flags & SDL_WINDOW_BORDERLESS)); + } + } + } + if ((changed & SDL_WINDOW_MAXIMIZED) && ((flags & SDL_WINDOW_MAXIMIZED) && !(flags & SDL_WINDOW_MINIMIZED))) { + data->pending_operation &= ~X11_PENDING_OP_MAXIMIZE; + if ((changed & SDL_WINDOW_MINIMIZED)) { + data->pending_operation &= ~X11_PENDING_OP_RESTORE; + // If coming out of minimized, send a restore event before sending maximized. + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0); + } + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0); + } + if ((changed & SDL_WINDOW_MINIMIZED) && (flags & SDL_WINDOW_MINIMIZED)) { + data->pending_operation &= ~X11_PENDING_OP_MINIMIZE; + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); + } + if (!(flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED))) { + data->pending_operation &= ~X11_PENDING_OP_RESTORE; + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0); + + // Apply any pending state if restored. + if (!(flags & SDL_WINDOW_FULLSCREEN)) { + if (data->pending_position) { + data->pending_position = false; + data->pending_operation |= X11_PENDING_OP_MOVE; + data->expected.x = data->window->pending.x - data->border_left; + data->expected.y = data->window->pending.y - data->border_top; + X11_XMoveWindow(display, data->xwindow, data->expected.x, data->expected.y); + } + if (data->pending_size) { + data->pending_size = false; + data->pending_operation |= X11_PENDING_OP_RESIZE; + data->expected.w = data->window->pending.w; + data->expected.h = data->window->pending.h; + X11_XResizeWindow(display, data->xwindow, data->window->pending.w, data->window->pending.h); + } + } + } + if ((flags & SDL_WINDOW_INPUT_FOCUS)) { + if (data->pending_move) { + DispatchWindowMove(_this, data, &data->pending_move_point); + data->pending_move = false; + } + } + } + if (changed & SDL_WINDOW_OCCLUDED) { + SDL_SendWindowEvent(data->window, (flags & SDL_WINDOW_OCCLUDED) ? SDL_EVENT_WINDOW_OCCLUDED : SDL_EVENT_WINDOW_EXPOSED, 0, 0); + } + } else if (xevent->xproperty.atom == videodata->atoms.XKLAVIER_STATE) { + /* Hack for Ubuntu 12.04 (etc) that doesn't send MappingNotify + events when the keyboard layout changes (for example, + changing from English to French on the menubar's keyboard + icon). Since it changes the XKLAVIER_STATE property, we + notice and reinit our keymap here. This might not be the + right approach, but it seems to work. */ + X11_UpdateKeymap(_this, true); + } else if (xevent->xproperty.atom == videodata->atoms._NET_FRAME_EXTENTS) { + /* Events are disabled when leaving fullscreen until the borders appear to avoid + * incorrect size/position events. + */ + if (data->size_move_event_flags) { + data->size_move_event_flags &= ~X11_SIZE_MOVE_EVENTS_WAIT_FOR_BORDERS; + X11_GetBorderValues(data); + + } + if (!(data->window->flags & SDL_WINDOW_FULLSCREEN) && data->toggle_borders) { + data->toggle_borders = false; + X11_SetWindowBordered(_this, data->window, !(data->window->flags & SDL_WINDOW_BORDERLESS)); + } + } + } break; + + case SelectionNotify: + { + Atom target = xevent->xselection.target; +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: SelectionNotify (requestor = 0x%lx, target = 0x%lx)", xevent->xany.window, + xevent->xselection.requestor, xevent->xselection.target); +#endif + if (target == data->xdnd_req) { + // read data + SDL_x11Prop p; + X11_ReadProperty(&p, display, data->xwindow, videodata->atoms.PRIMARY); + + if (p.format == 8) { + char *saveptr = NULL; + char *name = X11_XGetAtomName(display, target); + if (name) { + char *token = SDL_strtok_r((char *)p.data, "\r\n", &saveptr); + while (token) { + if ((SDL_strcmp("text/plain;charset=utf-8", name) == 0) || + (SDL_strcmp("UTF8_STRING", name) == 0) || + (SDL_strcmp("text/plain", name) == 0) || + (SDL_strcmp("TEXT", name) == 0)) { + SDL_SendDropText(data->window, token); + } else if (SDL_strcmp("text/uri-list", name) == 0) { + if (SDL_URIToLocal(token, token) >= 0) { + SDL_SendDropFile(data->window, NULL, token); + } + } + token = SDL_strtok_r(NULL, "\r\n", &saveptr); + } + X11_XFree(name); + } + SDL_SendDropComplete(data->window); + } + X11_XFree(p.data); + + // send reply + SDL_memset(&m, 0, sizeof(XClientMessageEvent)); + m.type = ClientMessage; + m.display = display; + m.window = data->xdnd_source; + m.message_type = videodata->atoms.XdndFinished; + m.format = 32; + m.data.l[0] = data->xwindow; + m.data.l[1] = 1; + m.data.l[2] = videodata->atoms.XdndActionCopy; + X11_XSendEvent(display, data->xdnd_source, False, NoEventMask, (XEvent *)&m); + + X11_XSync(display, False); + } + } break; + + default: + { +#ifdef DEBUG_XEVENTS + SDL_Log("window 0x%lx: Unhandled event %d", xevent->xany.window, xevent->type); +#endif + } break; + } +} + +static void X11_HandleFocusChanges(SDL_VideoDevice *_this) +{ + SDL_VideoData *videodata = _this->internal; + int i; + + if (videodata && videodata->windowlist) { + for (i = 0; i < videodata->numwindows; ++i) { + SDL_WindowData *data = videodata->windowlist[i]; + if (data && data->pending_focus != PENDING_FOCUS_NONE) { + Uint64 now = SDL_GetTicks(); + if (now >= data->pending_focus_time) { + if (data->pending_focus == PENDING_FOCUS_IN) { + X11_DispatchFocusIn(_this, data); + } else { + X11_DispatchFocusOut(_this, data); + } + data->pending_focus = PENDING_FOCUS_NONE; + } + } + } + } +} + +static Bool isAnyEvent(Display *display, XEvent *ev, XPointer arg) +{ + return True; +} + +static bool X11_PollEvent(Display *display, XEvent *event) +{ + if (!X11_XCheckIfEvent(display, event, isAnyEvent, NULL)) { + return false; + } + + return true; +} + +void X11_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_VideoData *data = _this->internal; + Display *req_display = data->request_display; + Window xwindow = window->internal->xwindow; + XClientMessageEvent event; + + SDL_memset(&event, 0, sizeof(XClientMessageEvent)); + event.type = ClientMessage; + event.display = req_display; + event.send_event = True; + event.message_type = data->atoms._SDL_WAKEUP; + event.format = 8; + + X11_XSendEvent(req_display, xwindow, False, NoEventMask, (XEvent *)&event); + /* XSendEvent returns a status and it could be BadValue or BadWindow. If an + error happens it is an SDL's internal error and there is nothing we can do here. */ + X11_XFlush(req_display); +} + +int X11_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS) +{ + SDL_VideoData *videodata = _this->internal; + Display *display; + XEvent xevent; + display = videodata->display; + + SDL_zero(xevent); + + // Flush and poll to grab any events already read and queued + X11_XFlush(display); + if (X11_PollEvent(display, &xevent)) { + // Fall through + } else if (timeoutNS == 0) { + return 0; + } else { + // Use SDL_IOR_NO_RETRY to ensure SIGINT will break us out of our wait + int err = SDL_IOReady(ConnectionNumber(display), SDL_IOR_READ | SDL_IOR_NO_RETRY, timeoutNS); + if (err > 0) { + if (!X11_PollEvent(display, &xevent)) { + /* Someone may have beat us to reading the fd. Return 1 here to + * trigger the normal spurious wakeup logic in the event core. */ + return 1; + } + } else if (err == 0) { + // Timeout + return 0; + } else { + // Error returned from poll()/select() + + if (errno == EINTR) { + /* If the wait was interrupted by a signal, we may have generated a + * SDL_EVENT_QUIT event. Let the caller know to call SDL_PumpEvents(). */ + return 1; + } else { + return err; + } + } + } + + X11_DispatchEvent(_this, &xevent); + +#ifdef SDL_USE_LIBDBUS + SDL_DBus_PumpEvents(); +#endif + return 1; +} + +void X11_PumpEvents(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + XEvent xevent; + int i; + + /* Check if a display had the mode changed and is waiting for a window to asynchronously become + * fullscreen. If there is no fullscreen window past the elapsed timeout, revert the mode switch. + */ + for (i = 0; i < _this->num_displays; ++i) { + if (_this->displays[i]->internal->mode_switch_deadline_ns) { + if (_this->displays[i]->fullscreen_window) { + _this->displays[i]->internal->mode_switch_deadline_ns = 0; + } else if (SDL_GetTicksNS() >= _this->displays[i]->internal->mode_switch_deadline_ns) { + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, + "Time out elapsed after mode switch on display %" SDL_PRIu32 " with no window becoming fullscreen; reverting", _this->displays[i]->id); + SDL_SetDisplayModeForDisplay(_this->displays[i], NULL); + } + } + } + + if (data->last_mode_change_deadline) { + if (SDL_GetTicks() >= data->last_mode_change_deadline) { + data->last_mode_change_deadline = 0; // assume we're done. + } + } + + // Update activity every 30 seconds to prevent screensaver + if (_this->suspend_screensaver) { + Uint64 now = SDL_GetTicks(); + if (!data->screensaver_activity || now >= (data->screensaver_activity + 30000)) { + X11_XResetScreenSaver(data->display); + +#ifdef SDL_USE_LIBDBUS + SDL_DBus_ScreensaverTickle(); +#endif + + data->screensaver_activity = now; + } + } + + SDL_zero(xevent); + + // Keep processing pending events + while (X11_PollEvent(data->display, &xevent)) { + X11_DispatchEvent(_this, &xevent); + } + +#ifdef SDL_USE_LIBDBUS + SDL_DBus_PumpEvents(); +#endif + + // FIXME: Only need to do this when there are pending focus changes + X11_HandleFocusChanges(_this); + + // FIXME: Only need to do this when there are flashing windows + for (i = 0; i < data->numwindows; ++i) { + if (data->windowlist[i] != NULL && + data->windowlist[i]->flash_cancel_time && + SDL_GetTicks() >= data->windowlist[i]->flash_cancel_time) { + X11_FlashWindow(_this, data->windowlist[i]->window, SDL_FLASH_CANCEL); + } + } + + if (data->xinput_hierarchy_changed) { + X11_Xinput2UpdateDevices(_this, false); + data->xinput_hierarchy_changed = false; + } +} + +bool X11_SuspendScreenSaver(SDL_VideoDevice *_this) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XSCRNSAVER + SDL_VideoData *data = _this->internal; + int dummy; + int major_version, minor_version; +#endif // SDL_VIDEO_DRIVER_X11_XSCRNSAVER + +#ifdef SDL_USE_LIBDBUS + if (SDL_DBus_ScreensaverInhibit(_this->suspend_screensaver)) { + return true; + } + + if (_this->suspend_screensaver) { + SDL_DBus_ScreensaverTickle(); + } +#endif + +#ifdef SDL_VIDEO_DRIVER_X11_XSCRNSAVER + if (SDL_X11_HAVE_XSS) { + // X11_XScreenSaverSuspend was introduced in MIT-SCREEN-SAVER 1.1 + if (!X11_XScreenSaverQueryExtension(data->display, &dummy, &dummy) || + !X11_XScreenSaverQueryVersion(data->display, + &major_version, &minor_version) || + major_version < 1 || (major_version == 1 && minor_version < 1)) { + return SDL_Unsupported(); + } + + X11_XScreenSaverSuspend(data->display, _this->suspend_screensaver); + X11_XResetScreenSaver(data->display); + return true; + } +#endif + return SDL_Unsupported(); +} + +#endif // SDL_VIDEO_DRIVER_X11 diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11events.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11events.h new file mode 100644 index 0000000..bb76f83 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11events.h @@ -0,0 +1,40 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifndef SDL_x11events_h_ +#define SDL_x11events_h_ + +extern void X11_PumpEvents(SDL_VideoDevice *_this); +extern int X11_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS); +extern void X11_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window); +extern bool X11_SuspendScreenSaver(SDL_VideoDevice *_this); +extern void X11_ReconcileKeyboardState(SDL_VideoDevice *_this); +extern void X11_GetBorderValues(SDL_WindowData *data); +extern Uint64 X11_GetEventTimestamp(unsigned long time); +extern void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_KeyboardID keyboardID, XEvent *xevent); +extern void X11_HandleButtonPress(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_MouseID mouseID, int button, float x, float y, unsigned long time); +extern void X11_HandleButtonRelease(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_MouseID mouseID, int button, unsigned long time); +extern SDL_WindowData *X11_FindWindow(SDL_VideoDevice *_this, Window window); +extern bool X11_ProcessHitTest(SDL_VideoDevice *_this, SDL_WindowData *data, const float x, const float y, bool force_new_result); +extern bool X11_TriggerHitTestAction(SDL_VideoDevice *_this, SDL_WindowData *data, const float x, const float y); + +#endif // SDL_x11events_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11framebuffer.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11framebuffer.c new file mode 100644 index 0000000..12642cc --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11framebuffer.c @@ -0,0 +1,261 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifdef SDL_VIDEO_DRIVER_X11 + +#include "SDL_x11video.h" +#include "SDL_x11framebuffer.h" +#include "SDL_x11xsync.h" + +#ifndef NO_SHARED_MEMORY + +// Shared memory error handler routine +static int shm_error; +static int (*X_handler)(Display *, XErrorEvent *) = NULL; +static int shm_errhandler(Display *d, XErrorEvent *e) +{ + if (e->error_code == BadAccess) { + shm_error = True; + return 0; + } + return X_handler(d, e); +} + +static bool have_mitshm(Display *dpy) +{ + // Only use shared memory on local X servers + return X11_XShmQueryExtension(dpy) ? SDL_X11_HAVE_SHM : false; +} + +#endif // !NO_SHARED_MEMORY + +bool X11_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, SDL_PixelFormat *format, + void **pixels, int *pitch) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + XGCValues gcv; + XVisualInfo vinfo; + int w, h; + + SDL_GetWindowSizeInPixels(window, &w, &h); + + // Free the old framebuffer surface + X11_DestroyWindowFramebuffer(_this, window); + + // Create the graphics context for drawing + gcv.graphics_exposures = False; + data->gc = X11_XCreateGC(display, data->xwindow, GCGraphicsExposures, &gcv); + if (!data->gc) { + return SDL_SetError("Couldn't create graphics context"); + } + + // Find out the pixel format and depth + if (!X11_GetVisualInfoFromVisual(display, data->visual, &vinfo)) { + return SDL_SetError("Couldn't get window visual information"); + } + + *format = X11_GetPixelFormatFromVisualInfo(display, &vinfo); + if (*format == SDL_PIXELFORMAT_UNKNOWN) { + return SDL_SetError("Unknown window pixel format"); + } + + // Calculate pitch + *pitch = (((w * SDL_BYTESPERPIXEL(*format)) + 3) & ~3); + + // Create the actual image +#ifndef NO_SHARED_MEMORY + if (have_mitshm(display)) { + XShmSegmentInfo *shminfo = &data->shminfo; + + shminfo->shmid = shmget(IPC_PRIVATE, (size_t)h * (*pitch), IPC_CREAT | 0777); + if (shminfo->shmid >= 0) { + shminfo->shmaddr = (char *)shmat(shminfo->shmid, 0, 0); + shminfo->readOnly = False; + if (shminfo->shmaddr != (char *)-1) { + shm_error = False; + X_handler = X11_XSetErrorHandler(shm_errhandler); + X11_XShmAttach(display, shminfo); + X11_XSync(display, False); + X11_XSetErrorHandler(X_handler); + if (shm_error) { + shmdt(shminfo->shmaddr); + } + } else { + shm_error = True; + } + shmctl(shminfo->shmid, IPC_RMID, NULL); + } else { + shm_error = True; + } + if (!shm_error) { + data->ximage = X11_XShmCreateImage(display, data->visual, + vinfo.depth, ZPixmap, + shminfo->shmaddr, shminfo, + w, h); + if (!data->ximage) { + X11_XShmDetach(display, shminfo); + X11_XSync(display, False); + shmdt(shminfo->shmaddr); + } else { + // Done! + data->ximage->byte_order = (SDL_BYTEORDER == SDL_BIG_ENDIAN) ? MSBFirst : LSBFirst; + data->use_mitshm = true; + *pixels = shminfo->shmaddr; + return true; + } + } + } +#endif // not NO_SHARED_MEMORY + + *pixels = SDL_malloc((size_t)h * (*pitch)); + if (!*pixels) { + return false; + } + + data->ximage = X11_XCreateImage(display, data->visual, + vinfo.depth, ZPixmap, 0, (char *)(*pixels), + w, h, 32, 0); + if (!data->ximage) { + SDL_free(*pixels); + return SDL_SetError("Couldn't create XImage"); + } + data->ximage->byte_order = (SDL_BYTEORDER == SDL_BIG_ENDIAN) ? MSBFirst : LSBFirst; + return true; +} + +bool X11_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, const SDL_Rect *rects, + int numrects) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + int i; + int x, y, w, h; + int window_w, window_h; + + SDL_GetWindowSizeInPixels(window, &window_w, &window_h); + +#ifndef NO_SHARED_MEMORY + if (data->use_mitshm) { + for (i = 0; i < numrects; ++i) { + x = rects[i].x; + y = rects[i].y; + w = rects[i].w; + h = rects[i].h; + + if (w <= 0 || h <= 0 || (x + w) <= 0 || (y + h) <= 0) { + // Clipped? + continue; + } + if (x < 0) { + x += w; + w += rects[i].x; + } + if (y < 0) { + y += h; + h += rects[i].y; + } + if (x + w > window_w) { + w = window_w - x; + } + if (y + h > window_h) { + h = window_h - y; + } + + X11_XShmPutImage(display, data->xwindow, data->gc, data->ximage, + x, y, x, y, w, h, False); + } + } else +#endif // !NO_SHARED_MEMORY + { + for (i = 0; i < numrects; ++i) { + x = rects[i].x; + y = rects[i].y; + w = rects[i].w; + h = rects[i].h; + + if (w <= 0 || h <= 0 || (x + w) <= 0 || (y + h) <= 0) { + // Clipped? + continue; + } + if (x < 0) { + x += w; + w += rects[i].x; + } + if (y < 0) { + y += h; + h += rects[i].y; + } + if (x + w > window_w) { + w = window_w - x; + } + if (y + h > window_h) { + h = window_h - y; + } + + X11_XPutImage(display, data->xwindow, data->gc, data->ximage, + x, y, x, y, w, h); + } + } + +#ifdef SDL_VIDEO_DRIVER_X11_XSYNC + X11_HandlePresent(data->window); +#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */ + + X11_XSync(display, False); + + return true; +} + +void X11_DestroyWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + Display *display; + + if (!data) { + // The window wasn't fully initialized + return; + } + + display = data->videodata->display; + + if (data->ximage) { + XDestroyImage(data->ximage); + +#ifndef NO_SHARED_MEMORY + if (data->use_mitshm) { + X11_XShmDetach(display, &data->shminfo); + X11_XSync(display, False); + shmdt(data->shminfo.shmaddr); + data->use_mitshm = false; + } +#endif // !NO_SHARED_MEMORY + + data->ximage = NULL; + } + if (data->gc) { + X11_XFreeGC(display, data->gc); + data->gc = NULL; + } +} + +#endif // SDL_VIDEO_DRIVER_X11 diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11framebuffer.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11framebuffer.h new file mode 100644 index 0000000..08feda4 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11framebuffer.h @@ -0,0 +1,34 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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_x11framebuffer_h_ +#define SDL_x11framebuffer_h_ + +#include "SDL_internal.h" + +extern bool X11_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, + SDL_PixelFormat *format, + void **pixels, int *pitch); +extern bool X11_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, + const SDL_Rect *rects, int numrects); +extern void X11_DestroyWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window); + +#endif // SDL_x11framebuffer_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11keyboard.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11keyboard.c new file mode 100644 index 0000000..c48e829 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11keyboard.c @@ -0,0 +1,789 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifdef SDL_VIDEO_DRIVER_X11 + +#include "SDL_x11video.h" + +#include "../../events/SDL_keyboard_c.h" +#include "../../events/SDL_scancode_tables_c.h" + +#include +#include + +#include "../../events/imKStoUCS.h" +#include "../../events/SDL_keysym_to_scancode_c.h" +#include "../../events/SDL_keysym_to_keycode_c.h" + +#ifdef X_HAVE_UTF8_STRING +#include +#endif + +static SDL_ScancodeTable scancode_set[] = { + SDL_SCANCODE_TABLE_DARWIN, + SDL_SCANCODE_TABLE_XFREE86_1, + SDL_SCANCODE_TABLE_XFREE86_2, + SDL_SCANCODE_TABLE_XVNC, +}; + +static bool X11_ScancodeIsRemappable(SDL_Scancode scancode) +{ + /* + * XKB remappings can assign different keysyms for these scancodes, but + * as these keys are in fixed positions, the scancodes themselves shouldn't + * be switched. Mark them as not being remappable. + */ + switch (scancode) { + case SDL_SCANCODE_ESCAPE: + case SDL_SCANCODE_CAPSLOCK: + case SDL_SCANCODE_NUMLOCKCLEAR: + case SDL_SCANCODE_LSHIFT: + case SDL_SCANCODE_RSHIFT: + case SDL_SCANCODE_LCTRL: + case SDL_SCANCODE_RCTRL: + case SDL_SCANCODE_LALT: + case SDL_SCANCODE_RALT: + case SDL_SCANCODE_LGUI: + case SDL_SCANCODE_RGUI: + return false; + default: + return true; + } +} + +// This function only correctly maps letters and numbers for keyboards in US QWERTY layout +static SDL_Scancode X11_KeyCodeToSDLScancode(SDL_VideoDevice *_this, KeyCode keycode) +{ + const KeySym keysym = X11_KeyCodeToSym(_this, keycode, 0, 0); + + if (keysym == NoSymbol) { + return SDL_SCANCODE_UNKNOWN; + } + + return SDL_GetScancodeFromKeySym(keysym, keycode); +} + +KeySym X11_KeyCodeToSym(SDL_VideoDevice *_this, KeyCode keycode, unsigned char group, unsigned int mod_mask) +{ + SDL_VideoData *data = _this->internal; + KeySym keysym; + unsigned int mods_ret[16]; + + SDL_zero(mods_ret); + +#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM + if (data->xkb.desc_ptr) { + int num_groups = XkbKeyNumGroups(data->xkb.desc_ptr, keycode); + unsigned char info = XkbKeyGroupInfo(data->xkb.desc_ptr, keycode); + + if (num_groups && group >= num_groups) { + + int action = XkbOutOfRangeGroupAction(info); + + if (action == XkbRedirectIntoRange) { + group = XkbOutOfRangeGroupNumber(info); + if (group >= num_groups) { + group = 0; + } + } else if (action == XkbClampIntoRange) { + group = num_groups - 1; + } else { + group %= num_groups; + } + } + + if (X11_XkbLookupKeySym(data->display, keycode, XkbBuildCoreState(mod_mask, group), mods_ret, &keysym) == NoSymbol) { + keysym = NoSymbol; + } + } else +#endif + { + // TODO: Handle groups and modifiers on the legacy path. + keysym = X11_XKeycodeToKeysym(data->display, keycode, 0); + } + + return keysym; +} + +bool X11_InitKeyboard(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + int i = 0; + int j = 0; + int min_keycode, max_keycode; + struct + { + SDL_Scancode scancode; + KeySym keysym; + int value; + } fingerprint[] = { + { SDL_SCANCODE_HOME, XK_Home, 0 }, + { SDL_SCANCODE_PAGEUP, XK_Prior, 0 }, + { SDL_SCANCODE_UP, XK_Up, 0 }, + { SDL_SCANCODE_LEFT, XK_Left, 0 }, + { SDL_SCANCODE_DELETE, XK_Delete, 0 }, + { SDL_SCANCODE_KP_ENTER, XK_KP_Enter, 0 }, + }; + int best_distance; + int best_index; + int distance; + Bool xkb_repeat = 0; + +#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM + { + int xkb_major = XkbMajorVersion; + int xkb_minor = XkbMinorVersion; + + if (X11_XkbQueryExtension(data->display, NULL, &data->xkb.event, NULL, &xkb_major, &xkb_minor)) { + data->xkb.desc_ptr = X11_XkbGetMap(data->display, XkbAllClientInfoMask, XkbUseCoreKbd); + } + + // This will remove KeyRelease events for held keys + X11_XkbSetDetectableAutoRepeat(data->display, True, &xkb_repeat); + } +#endif + + // Open a connection to the X input manager +#ifdef X_HAVE_UTF8_STRING + if (SDL_X11_HAVE_UTF8) { + /* Set the locale, and call XSetLocaleModifiers before XOpenIM so that + Compose keys will work correctly. */ + char *prev_locale = setlocale(LC_ALL, NULL); + char *prev_xmods = X11_XSetLocaleModifiers(NULL); + + if (prev_locale) { + prev_locale = SDL_strdup(prev_locale); + } + + if (prev_xmods) { + prev_xmods = SDL_strdup(prev_xmods); + } + + (void)setlocale(LC_ALL, ""); + X11_XSetLocaleModifiers(""); + + data->im = X11_XOpenIM(data->display, NULL, NULL, NULL); + + /* Reset the locale + X locale modifiers back to how they were, + locale first because the X locale modifiers depend on it. */ + (void)setlocale(LC_ALL, prev_locale); + X11_XSetLocaleModifiers(prev_xmods); + + if (prev_locale) { + SDL_free(prev_locale); + } + + if (prev_xmods) { + SDL_free(prev_xmods); + } + } +#endif + // Try to determine which scancodes are being used based on fingerprint + best_distance = SDL_arraysize(fingerprint) + 1; + best_index = -1; + X11_XDisplayKeycodes(data->display, &min_keycode, &max_keycode); + for (i = 0; i < SDL_arraysize(fingerprint); ++i) { + fingerprint[i].value = X11_XKeysymToKeycode(data->display, fingerprint[i].keysym) - min_keycode; + } + for (i = 0; i < SDL_arraysize(scancode_set); ++i) { + int table_size; + const SDL_Scancode *table = SDL_GetScancodeTable(scancode_set[i], &table_size); + + distance = 0; + for (j = 0; j < SDL_arraysize(fingerprint); ++j) { + if (fingerprint[j].value < 0 || fingerprint[j].value >= table_size) { + distance += 1; + } else if (table[fingerprint[j].value] != fingerprint[j].scancode) { + distance += 1; + } + } + if (distance < best_distance) { + best_distance = distance; + best_index = i; + } + } + if (best_index < 0 || best_distance > 2) { + // This is likely to be SDL_SCANCODE_TABLE_XFREE86_2 with remapped keys, double check a rarely remapped value + int fingerprint_value = X11_XKeysymToKeycode(data->display, 0x1008FF5B /* XF86Documents */) - min_keycode; + if (fingerprint_value == 235) { + for (i = 0; i < SDL_arraysize(scancode_set); ++i) { + if (scancode_set[i] == SDL_SCANCODE_TABLE_XFREE86_2) { + best_index = i; + best_distance = 0; + break; + } + } + } + } + if (best_index >= 0 && best_distance <= 2) { + int table_size; + const SDL_Scancode *table = SDL_GetScancodeTable(scancode_set[best_index], &table_size); + +#ifdef DEBUG_KEYBOARD + SDL_Log("Using scancode set %d, min_keycode = %d, max_keycode = %d, table_size = %d", best_index, min_keycode, max_keycode, table_size); +#endif + // This should never happen, but just in case... + if (table_size > (SDL_arraysize(data->key_layout) - min_keycode)) { + table_size = (SDL_arraysize(data->key_layout) - min_keycode); + } + SDL_memcpy(&data->key_layout[min_keycode], table, sizeof(SDL_Scancode) * table_size); + + /* Scancodes represent physical locations on the keyboard, unaffected by keyboard mapping. + However, there are a number of extended scancodes that have no standard location, so use + the X11 mapping for all non-character keys. + */ + for (i = min_keycode; i <= max_keycode; ++i) { + SDL_Scancode scancode = X11_KeyCodeToSDLScancode(_this, i); +#ifdef DEBUG_KEYBOARD + { + KeySym sym; + sym = X11_KeyCodeToSym(_this, (KeyCode)i, 0); + SDL_Log("code = %d, sym = 0x%X (%s) ", i - min_keycode, + (unsigned int)sym, sym == NoSymbol ? "NoSymbol" : X11_XKeysymToString(sym)); + } +#endif + if (scancode == data->key_layout[i]) { + continue; + } + if ((SDL_GetKeymapKeycode(NULL, scancode, SDL_KMOD_NONE) & (SDLK_SCANCODE_MASK | SDLK_EXTENDED_MASK)) && X11_ScancodeIsRemappable(scancode)) { + // Not a character key and the scancode is safe to remap +#ifdef DEBUG_KEYBOARD + SDL_Log("Changing scancode, was %d (%s), now %d (%s)", data->key_layout[i], SDL_GetScancodeName(data->key_layout[i]), scancode, SDL_GetScancodeName(scancode)); +#endif + data->key_layout[i] = scancode; + } + } + } else { +#ifdef DEBUG_SCANCODES + SDL_Log("Keyboard layout unknown, please report the following to the SDL forums/mailing list (https://discourse.libsdl.org/):"); +#endif + + // Determine key_layout - only works on US QWERTY layout + for (i = min_keycode; i <= max_keycode; ++i) { + SDL_Scancode scancode = X11_KeyCodeToSDLScancode(_this, i); +#ifdef DEBUG_SCANCODES + { + KeySym sym; + sym = X11_KeyCodeToSym(_this, (KeyCode)i, 0); + SDL_Log("code = %d, sym = 0x%X (%s) ", i - min_keycode, + (unsigned int)sym, sym == NoSymbol ? "NoSymbol" : X11_XKeysymToString(sym)); + } + if (scancode == SDL_SCANCODE_UNKNOWN) { + SDL_Log("scancode not found"); + } else { + SDL_Log("scancode = %d (%s)", scancode, SDL_GetScancodeName(scancode)); + } +#endif + data->key_layout[i] = scancode; + } + } + + X11_UpdateKeymap(_this, false); + + SDL_SetScancodeName(SDL_SCANCODE_APPLICATION, "Menu"); + + X11_ReconcileKeyboardState(_this); + + return true; +} + +static unsigned X11_GetNumLockModifierMask(SDL_VideoDevice *_this) +{ + SDL_VideoData *videodata = _this->internal; + Display *display = videodata->display; + unsigned num_mask = 0; + int i, j; + XModifierKeymap *xmods; + unsigned n; + + xmods = X11_XGetModifierMapping(display); + n = xmods->max_keypermod; + for (i = 3; i < 8; i++) { + for (j = 0; j < n; j++) { + KeyCode kc = xmods->modifiermap[i * n + j]; + if (videodata->key_layout[kc] == SDL_SCANCODE_NUMLOCKCLEAR) { + num_mask = 1 << i; + break; + } + } + } + X11_XFreeModifiermap(xmods); + + return num_mask; +} + +static unsigned X11_GetScrollLockModifierMask(SDL_VideoDevice *_this) +{ + SDL_VideoData *videodata = _this->internal; + Display *display = videodata->display; + unsigned num_mask = 0; + int i, j; + XModifierKeymap *xmods; + unsigned n; + + xmods = X11_XGetModifierMapping(display); + n = xmods->max_keypermod; + for (i = 3; i < 8; i++) { + for (j = 0; j < n; j++) { + KeyCode kc = xmods->modifiermap[i * n + j]; + if (videodata->key_layout[kc] == SDL_SCANCODE_SCROLLLOCK) { + num_mask = 1 << i; + break; + } + } + } + X11_XFreeModifiermap(xmods); + + return num_mask; +} + +void X11_UpdateKeymap(SDL_VideoDevice *_this, bool send_event) +{ + struct Keymod_masks + { + SDL_Keymod sdl_mask; + unsigned int xkb_mask; + } const keymod_masks[] = { + { SDL_KMOD_NONE, 0 }, + { SDL_KMOD_SHIFT, ShiftMask }, + { SDL_KMOD_CAPS, LockMask }, + { SDL_KMOD_SHIFT | SDL_KMOD_CAPS, ShiftMask | LockMask }, + { SDL_KMOD_MODE, Mod5Mask }, + { SDL_KMOD_MODE | SDL_KMOD_SHIFT, Mod5Mask | ShiftMask }, + { SDL_KMOD_MODE | SDL_KMOD_CAPS, Mod5Mask | LockMask }, + { SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, Mod5Mask | ShiftMask | LockMask }, + { SDL_KMOD_LEVEL5, Mod3Mask }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_SHIFT, Mod3Mask | ShiftMask }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_CAPS, Mod3Mask | LockMask }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, Mod3Mask | ShiftMask | LockMask }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE, Mod5Mask | Mod3Mask }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_SHIFT, Mod3Mask | Mod5Mask | ShiftMask }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_CAPS, Mod3Mask | Mod5Mask | LockMask }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, Mod3Mask | Mod5Mask | ShiftMask | LockMask } + }; + + SDL_VideoData *data = _this->internal; + SDL_Scancode scancode; + SDL_Keymap *keymap = SDL_CreateKeymap(); + +#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM + if (data->xkb.desc_ptr) { + XkbStateRec state; + X11_XkbGetUpdatedMap(data->display, XkbAllClientInfoMask, data->xkb.desc_ptr); + + if (X11_XkbGetState(data->display, XkbUseCoreKbd, &state) == Success) { + data->xkb.current_group = state.group; + } + } +#endif + + for (int m = 0; m < SDL_arraysize(keymod_masks); ++m) { + for (int i = 0; i < SDL_arraysize(data->key_layout); ++i) { + // Make sure this is a valid scancode + scancode = data->key_layout[i]; + if (scancode == SDL_SCANCODE_UNKNOWN) { + continue; + } + + const KeySym keysym = X11_KeyCodeToSym(_this, i, data->xkb.current_group, keymod_masks[m].xkb_mask); + + if (keysym != NoSymbol) { + SDL_Keycode keycode = SDL_GetKeyCodeFromKeySym(keysym, i, keymod_masks[m].sdl_mask); + + if (!keycode) { + switch (scancode) { + case SDL_SCANCODE_RETURN: + keycode = SDLK_RETURN; + break; + case SDL_SCANCODE_ESCAPE: + keycode = SDLK_ESCAPE; + break; + case SDL_SCANCODE_BACKSPACE: + keycode = SDLK_BACKSPACE; + break; + case SDL_SCANCODE_DELETE: + keycode = SDLK_DELETE; + break; + default: + keycode = SDL_SCANCODE_TO_KEYCODE(scancode); + break; + } + } + + SDL_SetKeymapEntry(keymap, scancode, keymod_masks[m].sdl_mask, keycode); + } + } + } + + data->xkb.numlock_mask = X11_GetNumLockModifierMask(_this); + data->xkb.scrolllock_mask = X11_GetScrollLockModifierMask(_this); + SDL_SetKeymap(keymap, send_event); +} + +void X11_QuitKeyboard(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + +#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM + if (data->xkb.desc_ptr) { + X11_XkbFreeKeyboard(data->xkb.desc_ptr, 0, True); + data->xkb.desc_ptr = NULL; + } +#endif +} + +void X11_ClearComposition(SDL_WindowData *data) +{ + if (data->preedit_length > 0) { + data->preedit_text[0] = '\0'; + data->preedit_length = 0; + } + + if (data->ime_needs_clear_composition) { + SDL_SendEditingText("", 0, 0); + data->ime_needs_clear_composition = false; + } +} + +static void X11_SendEditingEvent(SDL_WindowData *data) +{ + if (data->preedit_length == 0) { + X11_ClearComposition(data); + return; + } + + bool in_highlight = false; + int start = -1, length = 0, i; + for (i = 0; i < data->preedit_length; ++i) { + if (data->preedit_feedback[i] & (XIMReverse | XIMHighlight)) { + if (start < 0) { + start = i; + in_highlight = true; + } + } else if (in_highlight) { + // Found the end of the highlight + break; + } + } + if (in_highlight) { + length = (i - start); + } else { + start = SDL_clamp(data->preedit_cursor, 0, data->preedit_length); + } + SDL_SendEditingText(data->preedit_text, start, length); + + data->ime_needs_clear_composition = true; +} + +static int preedit_start_callback(XIC xic, XPointer client_data, XPointer call_data) +{ + // No limit on preedit text length + return -1; +} + +static void preedit_done_callback(XIC xic, XPointer client_data, XPointer call_data) +{ +} + +static void preedit_draw_callback(XIC xic, XPointer client_data, XIMPreeditDrawCallbackStruct *call_data) +{ + SDL_WindowData *data = (SDL_WindowData *)client_data; + int chg_first = SDL_clamp(call_data->chg_first, 0, data->preedit_length); + int chg_length = SDL_clamp(call_data->chg_length, 0, data->preedit_length - chg_first); + + const char *start = data->preedit_text; + if (chg_length > 0) { + // Delete text in range + for (int i = 0; start && *start && i < chg_first; ++i) { + SDL_StepUTF8(&start, NULL); + } + + const char *end = start; + for (int i = 0; end && *end && i < chg_length; ++i) { + SDL_StepUTF8(&end, NULL); + } + + if (end > start) { + SDL_memmove((char *)start, end, SDL_strlen(end) + 1); + if ((chg_first + chg_length) > data->preedit_length) { + SDL_memmove(&data->preedit_feedback[chg_first], &data->preedit_feedback[chg_first + chg_length], (data->preedit_length - chg_first - chg_length) * sizeof(*data->preedit_feedback)); + } + } + data->preedit_length -= chg_length; + } + + XIMText *text = call_data->text; + if (text) { + // Insert text in range + SDL_assert(!text->encoding_is_wchar); + + // The text length isn't calculated as directed by the spec, recalculate it now + if (text->string.multi_byte) { + text->length = SDL_utf8strlen(text->string.multi_byte); + } + + size_t string_size = SDL_strlen(text->string.multi_byte); + size_t size = string_size + 1; + if (data->preedit_text) { + size += SDL_strlen(data->preedit_text); + } + char *preedit_text = (char *)SDL_malloc(size * sizeof(*preedit_text)); + if (preedit_text) { + size_t pre_size = (start - data->preedit_text); + size_t post_size = start ? SDL_strlen(start) : 0; + if (pre_size > 0) { + SDL_memcpy(&preedit_text[0], data->preedit_text, pre_size); + } + SDL_memcpy(&preedit_text[pre_size], text->string.multi_byte, string_size); + if (post_size > 0) { + SDL_memcpy(&preedit_text[pre_size + string_size], start, post_size); + } + preedit_text[size - 1] = '\0'; + } + + size_t feedback_size = data->preedit_length + text->length; + XIMFeedback *feedback = (XIMFeedback *)SDL_malloc(feedback_size * sizeof(*feedback)); + if (feedback) { + size_t pre_size = (size_t)chg_first; + size_t post_size = (size_t)data->preedit_length - pre_size; + if (pre_size > 0) { + SDL_memcpy(&feedback[0], data->preedit_feedback, pre_size * sizeof(*feedback)); + } + SDL_memcpy(&feedback[pre_size], text->feedback, text->length * sizeof(*feedback)); + if (post_size > 0) { + SDL_memcpy(&feedback[pre_size + text->length], &data->preedit_feedback[pre_size], post_size * sizeof(*feedback)); + } + } + + if (preedit_text && feedback) { + SDL_free(data->preedit_text); + data->preedit_text = preedit_text; + + SDL_free(data->preedit_feedback); + data->preedit_feedback = feedback; + + data->preedit_length += text->length; + } else { + SDL_free(preedit_text); + SDL_free(feedback); + } + } + + data->preedit_cursor = call_data->caret; + +#ifdef DEBUG_XIM + if (call_data->chg_length > 0) { + SDL_Log("Draw callback deleted %d characters at %d", call_data->chg_length, call_data->chg_first); + } + if (text) { + SDL_Log("Draw callback inserted %s at %d, caret: %d", text->string.multi_byte, call_data->chg_first, call_data->caret); + } + SDL_Log("Pre-edit text: %s", data->preedit_text); +#endif + + X11_SendEditingEvent(data); +} + +static void preedit_caret_callback(XIC xic, XPointer client_data, XIMPreeditCaretCallbackStruct *call_data) +{ + SDL_WindowData *data = (SDL_WindowData *)client_data; + + switch (call_data->direction) { + case XIMAbsolutePosition: + if (call_data->position != data->preedit_cursor) { + data->preedit_cursor = call_data->position; + X11_SendEditingEvent(data); + } + break; + case XIMDontChange: + break; + default: + // Not currently supported + break; + } +} + +void X11_CreateInputContext(SDL_WindowData *data) +{ +#ifdef X_HAVE_UTF8_STRING + SDL_VideoData *videodata = data->videodata; + + if (SDL_X11_HAVE_UTF8 && videodata->im) { + const char *hint = SDL_GetHint(SDL_HINT_IME_IMPLEMENTED_UI); + if (hint && SDL_strstr(hint, "composition")) { + XIMCallback draw_callback; + draw_callback.client_data = (XPointer)data; + draw_callback.callback = (XIMProc)preedit_draw_callback; + + XIMCallback start_callback; + start_callback.client_data = (XPointer)data; + start_callback.callback = (XIMProc)preedit_start_callback; + + XIMCallback done_callback; + done_callback.client_data = (XPointer)data; + done_callback.callback = (XIMProc)preedit_done_callback; + + XIMCallback caret_callback; + caret_callback.client_data = (XPointer)data; + caret_callback.callback = (XIMProc)preedit_caret_callback; + + XVaNestedList attr = X11_XVaCreateNestedList(0, + XNPreeditStartCallback, &start_callback, + XNPreeditDoneCallback, &done_callback, + XNPreeditDrawCallback, &draw_callback, + XNPreeditCaretCallback, &caret_callback, + NULL); + if (attr) { + data->ic = X11_XCreateIC(videodata->im, + XNInputStyle, XIMPreeditCallbacks | XIMStatusCallbacks, + XNPreeditAttributes, attr, + XNClientWindow, data->xwindow, + NULL); + X11_XFree(attr); + } + } + if (!data->ic) { + data->ic = X11_XCreateIC(videodata->im, + XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, data->xwindow, + NULL); + } + data->xim_spot.x = -1; + data->xim_spot.y = -1; + } +#endif // X_HAVE_UTF8_STRING +} + +static void X11_ResetXIM(SDL_VideoDevice *_this, SDL_Window *window) +{ +#ifdef X_HAVE_UTF8_STRING + SDL_WindowData *data = window->internal; + + if (data && data->ic) { + // Clear any partially entered dead keys + char *contents = X11_Xutf8ResetIC(data->ic); + if (contents) { + X11_XFree(contents); + } + } +#endif // X_HAVE_UTF8_STRING +} + +bool X11_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props) +{ + X11_ResetXIM(_this, window); + + return X11_UpdateTextInputArea(_this, window); +} + +bool X11_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window) +{ + X11_ResetXIM(_this, window); + return true; +} + +bool X11_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window) +{ +#ifdef X_HAVE_UTF8_STRING + SDL_WindowData *data = window->internal; + + if (data && data->ic) { + XPoint spot; + spot.x = window->text_input_rect.x + window->text_input_cursor; + spot.y = window->text_input_rect.y + window->text_input_rect.h; + if (spot.x != data->xim_spot.x || spot.y != data->xim_spot.y) { + XVaNestedList attr = X11_XVaCreateNestedList(0, XNSpotLocation, &spot, NULL); + if (attr) { + X11_XSetICValues(data->ic, XNPreeditAttributes, attr, NULL); + X11_XFree(attr); + } + SDL_copyp(&data->xim_spot, &spot); + } + } +#endif + return true; +} + +bool X11_HasScreenKeyboardSupport(SDL_VideoDevice *_this) +{ + SDL_VideoData *videodata = _this->internal; + return videodata->is_steam_deck; +} + +void X11_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props) +{ + SDL_VideoData *videodata = _this->internal; + + if (videodata->is_steam_deck) { + /* For more documentation of the URL parameters, see: + * https://partner.steamgames.com/doc/api/ISteamUtils#ShowFloatingGamepadTextInput + */ + const int k_EFloatingGamepadTextInputModeModeSingleLine = 0; // Enter dismisses the keyboard + const int k_EFloatingGamepadTextInputModeModeMultipleLines = 1; // User needs to explicitly dismiss the keyboard + const int k_EFloatingGamepadTextInputModeModeEmail = 2; // Keyboard is displayed in a special mode that makes it easier to enter emails + const int k_EFloatingGamepadTextInputModeModeNumeric = 3; // Numeric keypad is shown + char deeplink[128]; + int mode; + + switch (SDL_GetTextInputType(props)) { + case SDL_TEXTINPUT_TYPE_TEXT_EMAIL: + mode = k_EFloatingGamepadTextInputModeModeEmail; + break; + case SDL_TEXTINPUT_TYPE_NUMBER: + case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN: + case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE: + mode = k_EFloatingGamepadTextInputModeModeNumeric; + break; + default: + if (SDL_GetTextInputMultiline(props)) { + mode = k_EFloatingGamepadTextInputModeModeMultipleLines; + } else { + mode = k_EFloatingGamepadTextInputModeModeSingleLine; + } + break; + } + (void)SDL_snprintf(deeplink, sizeof(deeplink), + "steam://open/keyboard?XPosition=0&YPosition=0&Width=0&Height=0&Mode=%d", + mode); + SDL_OpenURL(deeplink); + videodata->steam_keyboard_open = true; + } +} + +void X11_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_VideoData *videodata = _this->internal; + + if (videodata->is_steam_deck) { + SDL_OpenURL("steam://close/keyboard"); + videodata->steam_keyboard_open = false; + } +} + +bool X11_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_VideoData *videodata = _this->internal; + + return videodata->steam_keyboard_open; +} + +#endif // SDL_VIDEO_DRIVER_X11 diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11keyboard.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11keyboard.h new file mode 100644 index 0000000..a6cd2f7 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11keyboard.h @@ -0,0 +1,40 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifndef SDL_x11keyboard_h_ +#define SDL_x11keyboard_h_ + +extern bool X11_InitKeyboard(SDL_VideoDevice *_this); +extern void X11_UpdateKeymap(SDL_VideoDevice *_this, bool send_event); +extern void X11_QuitKeyboard(SDL_VideoDevice *_this); +extern void X11_CreateInputContext(SDL_WindowData *data); +extern void X11_ClearComposition(SDL_WindowData *data); +extern bool X11_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props); +extern bool X11_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window); +extern bool X11_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window); +extern bool X11_HasScreenKeyboardSupport(SDL_VideoDevice *_this); +extern void X11_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props); +extern void X11_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window); +extern bool X11_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window); +extern KeySym X11_KeyCodeToSym(SDL_VideoDevice *_this, KeyCode, unsigned char group, unsigned int mod_mask); + +#endif // SDL_x11keyboard_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11messagebox.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11messagebox.c new file mode 100644 index 0000000..8aa1c6a --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11messagebox.c @@ -0,0 +1,887 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifdef SDL_VIDEO_DRIVER_X11 + +#include "SDL_x11video.h" +#include "SDL_x11dyn.h" +#include "SDL_x11messagebox.h" + +#include +#include + +#define SDL_FORK_MESSAGEBOX 1 +#define SDL_SET_LOCALE 1 + +#if SDL_FORK_MESSAGEBOX +#include +#include +#include +#include +#endif + +#define MAX_BUTTONS 8 // Maximum number of buttons supported +#define MIN_BUTTON_WIDTH 64 // Minimum button width +#define MIN_DIALOG_WIDTH 200 // Minimum dialog width +#define MIN_DIALOG_HEIGHT 100 // Minimum dialog height + +static const char g_MessageBoxFontLatin1[] = + "-*-*-medium-r-normal--0-120-*-*-p-0-iso8859-1"; + +static const char* g_MessageBoxFont[] = { + "-*-*-medium-r-normal--*-120-*-*-*-*-iso10646-1", // explicitly unicode (iso10646-1) + "-*-*-medium-r-*--*-120-*-*-*-*-iso10646-1", // explicitly unicode (iso10646-1) + "-misc-*-*-*-*--*-*-*-*-*-*-iso10646-1", // misc unicode (fix for some systems) + "-*-*-*-*-*--*-*-*-*-*-*-iso10646-1", // just give me anything Unicode. + "-*-*-medium-r-normal--*-120-*-*-*-*-iso8859-1", // explicitly latin1, in case low-ASCII works out. + "-*-*-medium-r-*--*-120-*-*-*-*-iso8859-1", // explicitly latin1, in case low-ASCII works out. + "-misc-*-*-*-*--*-*-*-*-*-*-iso8859-1", // misc latin1 (fix for some systems) + "-*-*-*-*-*--*-*-*-*-*-*-iso8859-1", // just give me anything latin1. + NULL +}; + +static const SDL_MessageBoxColor g_default_colors[SDL_MESSAGEBOX_COLOR_COUNT] = { + { 56, 54, 53 }, // SDL_MESSAGEBOX_COLOR_BACKGROUND, + { 209, 207, 205 }, // SDL_MESSAGEBOX_COLOR_TEXT, + { 140, 135, 129 }, // SDL_MESSAGEBOX_COLOR_BUTTON_BORDER, + { 105, 102, 99 }, // SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND, + { 205, 202, 53 }, // SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED, +}; + +#define SDL_MAKE_RGB(_r, _g, _b) (((Uint32)(_r) << 16) | \ + ((Uint32)(_g) << 8) | \ + ((Uint32)(_b))) + +typedef struct SDL_MessageBoxButtonDataX11 +{ + int x, y; // Text position + int length; // Text length + int text_width; // Text width + + SDL_Rect rect; // Rectangle for entire button + + const SDL_MessageBoxButtonData *buttondata; // Button data from caller +} SDL_MessageBoxButtonDataX11; + +typedef struct TextLineData +{ + int width; // Width of this text line + int length; // String length of this text line + const char *text; // Text for this line +} TextLineData; + +typedef struct SDL_MessageBoxDataX11 +{ + Display *display; + int screen; + Window window; +#ifdef SDL_VIDEO_DRIVER_X11_XDBE + XdbeBackBuffer buf; + bool xdbe; // Whether Xdbe is present or not +#endif + long event_mask; + Atom wm_protocols; + Atom wm_delete_message; + + int dialog_width; // Dialog box width. + int dialog_height; // Dialog box height. + + XFontSet font_set; // for UTF-8 systems + XFontStruct *font_struct; // Latin1 (ASCII) fallback. + int xtext, ytext; // Text position to start drawing at. + int numlines; // Count of Text lines. + int text_height; // Height for text lines. + TextLineData *linedata; + + int *pbuttonid; // Pointer to user return buttonID value. + + int button_press_index; // Index into buttondata/buttonpos for button which is pressed (or -1). + int mouse_over_index; // Index into buttondata/buttonpos for button mouse is over (or -1). + + int numbuttons; // Count of buttons. + const SDL_MessageBoxButtonData *buttondata; + SDL_MessageBoxButtonDataX11 buttonpos[MAX_BUTTONS]; + + Uint32 color[SDL_MESSAGEBOX_COLOR_COUNT]; + + const SDL_MessageBoxData *messageboxdata; +} SDL_MessageBoxDataX11; + +// Maximum helper for ints. +static SDL_INLINE int IntMax(int a, int b) +{ + return (a > b) ? a : b; +} + +// Return width and height for a string. +static void GetTextWidthHeight(SDL_MessageBoxDataX11 *data, const char *str, int nbytes, int *pwidth, int *pheight) +{ +#ifdef X_HAVE_UTF8_STRING + if (SDL_X11_HAVE_UTF8) { + XRectangle overall_ink, overall_logical; + X11_Xutf8TextExtents(data->font_set, str, nbytes, &overall_ink, &overall_logical); + *pwidth = overall_logical.width; + *pheight = overall_logical.height; + } else +#endif + { + XCharStruct text_structure; + int font_direction, font_ascent, font_descent; + X11_XTextExtents(data->font_struct, str, nbytes, + &font_direction, &font_ascent, &font_descent, + &text_structure); + *pwidth = text_structure.width; + *pheight = text_structure.ascent + text_structure.descent; + } +} + +// Return index of button if position x,y is contained therein. +static int GetHitButtonIndex(SDL_MessageBoxDataX11 *data, int x, int y) +{ + int i; + int numbuttons = data->numbuttons; + SDL_MessageBoxButtonDataX11 *buttonpos = data->buttonpos; + + for (i = 0; i < numbuttons; i++) { + SDL_Rect *rect = &buttonpos[i].rect; + + if ((x >= rect->x) && + (x <= (rect->x + rect->w)) && + (y >= rect->y) && + (y <= (rect->y + rect->h))) { + return i; + } + } + + return -1; +} + +// Initialize SDL_MessageBoxData structure and Display, etc. +static bool X11_MessageBoxInit(SDL_MessageBoxDataX11 *data, const SDL_MessageBoxData *messageboxdata, int *pbuttonid) +{ + int i; + int numbuttons = messageboxdata->numbuttons; + const SDL_MessageBoxButtonData *buttondata = messageboxdata->buttons; + const SDL_MessageBoxColor *colorhints; + + if (numbuttons > MAX_BUTTONS) { + return SDL_SetError("Too many buttons (%d max allowed)", MAX_BUTTONS); + } + + data->dialog_width = MIN_DIALOG_WIDTH; + data->dialog_height = MIN_DIALOG_HEIGHT; + data->messageboxdata = messageboxdata; + data->buttondata = buttondata; + data->numbuttons = numbuttons; + data->pbuttonid = pbuttonid; + + data->display = X11_XOpenDisplay(NULL); + if (!data->display) { + return SDL_SetError("Couldn't open X11 display"); + } + +#ifdef X_HAVE_UTF8_STRING + if (SDL_X11_HAVE_UTF8) { + char **missing = NULL; + int num_missing = 0; + int i_font; + for (i_font = 0; g_MessageBoxFont[i_font]; ++i_font) { + data->font_set = X11_XCreateFontSet(data->display, g_MessageBoxFont[i_font], + &missing, &num_missing, NULL); + if (missing) { + X11_XFreeStringList(missing); + } + if (data->font_set) { + break; + } + } + if (!data->font_set) { + return SDL_SetError("Couldn't load x11 message box font"); + } + } else +#endif + { + data->font_struct = X11_XLoadQueryFont(data->display, g_MessageBoxFontLatin1); + if (!data->font_struct) { + return SDL_SetError("Couldn't load font %s", g_MessageBoxFontLatin1); + } + } + + if (messageboxdata->colorScheme) { + colorhints = messageboxdata->colorScheme->colors; + } else { + colorhints = g_default_colors; + } + + // Convert our SDL_MessageBoxColor r,g,b values to packed RGB format. + for (i = 0; i < SDL_MESSAGEBOX_COLOR_COUNT; i++) { + data->color[i] = SDL_MAKE_RGB(colorhints[i].r, colorhints[i].g, colorhints[i].b); + } + + return true; +} + +static int CountLinesOfText(const char *text) +{ + int result = 0; + while (text && *text) { + const char *lf = SDL_strchr(text, '\n'); + result++; // even without an endline, this counts as a line. + text = lf ? lf + 1 : NULL; + } + return result; +} + +// Calculate and initialize text and button locations. +static bool X11_MessageBoxInitPositions(SDL_MessageBoxDataX11 *data) +{ + int i; + int ybuttons; + int text_width_max = 0; + int button_text_height = 0; + int button_width = MIN_BUTTON_WIDTH; + const SDL_MessageBoxData *messageboxdata = data->messageboxdata; + + // Go over text and break linefeeds into separate lines. + if (messageboxdata && messageboxdata->message[0]) { + const char *text = messageboxdata->message; + const int linecount = CountLinesOfText(text); + TextLineData *plinedata = (TextLineData *)SDL_malloc(sizeof(TextLineData) * linecount); + + if (!plinedata) { + return false; + } + + data->linedata = plinedata; + data->numlines = linecount; + + for (i = 0; i < linecount; i++, plinedata++) { + const char *lf = SDL_strchr(text, '\n'); + const int length = lf ? (lf - text) : SDL_strlen(text); + int height; + + plinedata->text = text; + + GetTextWidthHeight(data, text, length, &plinedata->width, &height); + + // Text and widths are the largest we've ever seen. + data->text_height = IntMax(data->text_height, height); + text_width_max = IntMax(text_width_max, plinedata->width); + + plinedata->length = length; + if (lf && (lf > text) && (lf[-1] == '\r')) { + plinedata->length--; + } + + text += length + 1; + + // Break if there are no more linefeeds. + if (!lf) { + break; + } + } + + // Bump up the text height slightly. + data->text_height += 2; + } + + // Loop through all buttons and calculate the button widths and height. + for (i = 0; i < data->numbuttons; i++) { + int height; + + data->buttonpos[i].buttondata = &data->buttondata[i]; + data->buttonpos[i].length = SDL_strlen(data->buttondata[i].text); + + GetTextWidthHeight(data, data->buttondata[i].text, SDL_strlen(data->buttondata[i].text), + &data->buttonpos[i].text_width, &height); + + button_width = IntMax(button_width, data->buttonpos[i].text_width); + button_text_height = IntMax(button_text_height, height); + } + + if (data->numlines) { + // x,y for this line of text. + data->xtext = data->text_height; + data->ytext = data->text_height + data->text_height; + + // Bump button y down to bottom of text. + ybuttons = 3 * data->ytext / 2 + (data->numlines - 1) * data->text_height; + + // Bump the dialog box width and height up if needed. + data->dialog_width = IntMax(data->dialog_width, 2 * data->xtext + text_width_max); + data->dialog_height = IntMax(data->dialog_height, ybuttons); + } else { + // Button y starts at height of button text. + ybuttons = button_text_height; + } + + if (data->numbuttons) { + int x, y; + int width_of_buttons; + int button_spacing = button_text_height; + int button_height = 2 * button_text_height; + + // Bump button width up a bit. + button_width += button_text_height; + + // Get width of all buttons lined up. + width_of_buttons = data->numbuttons * button_width + (data->numbuttons - 1) * button_spacing; + + // Bump up dialog width and height if buttons are wider than text. + data->dialog_width = IntMax(data->dialog_width, width_of_buttons + 2 * button_spacing); + data->dialog_height = IntMax(data->dialog_height, ybuttons + 2 * button_height); + + // Location for first button. + if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_RIGHT_TO_LEFT) { + x = data->dialog_width - (data->dialog_width - width_of_buttons) / 2 - (button_width + button_spacing); + } else { + x = (data->dialog_width - width_of_buttons) / 2; + } + y = ybuttons + (data->dialog_height - ybuttons - button_height) / 2; + + for (i = 0; i < data->numbuttons; i++) { + // Button coordinates. + data->buttonpos[i].rect.x = x; + data->buttonpos[i].rect.y = y; + data->buttonpos[i].rect.w = button_width; + data->buttonpos[i].rect.h = button_height; + + // Button text coordinates. + data->buttonpos[i].x = x + (button_width - data->buttonpos[i].text_width) / 2; + data->buttonpos[i].y = y + (button_height - button_text_height - 1) / 2 + button_text_height; + + // Scoot over for next button. + if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_RIGHT_TO_LEFT) { + x -= button_width + button_spacing; + } else { + x += button_width + button_spacing; + } + } + } + + return true; +} + +// Free SDL_MessageBoxData data. +static void X11_MessageBoxShutdown(SDL_MessageBoxDataX11 *data) +{ + if (data->font_set) { + X11_XFreeFontSet(data->display, data->font_set); + data->font_set = NULL; + } + + if (data->font_struct) { + X11_XFreeFont(data->display, data->font_struct); + data->font_struct = NULL; + } + +#ifdef SDL_VIDEO_DRIVER_X11_XDBE + if (SDL_X11_HAVE_XDBE && data->xdbe) { + X11_XdbeDeallocateBackBufferName(data->display, data->buf); + } +#endif + + if (data->display) { + if (data->window != None) { + X11_XWithdrawWindow(data->display, data->window, data->screen); + X11_XDestroyWindow(data->display, data->window); + data->window = None; + } + + X11_XCloseDisplay(data->display); + data->display = NULL; + } + + SDL_free(data->linedata); +} + +// Create and set up our X11 dialog box indow. +static bool X11_MessageBoxCreateWindow(SDL_MessageBoxDataX11 *data) +{ + int x, y; + XSizeHints *sizehints; + XSetWindowAttributes wnd_attr; + Atom _NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_DIALOG; + Display *display = data->display; + SDL_WindowData *windowdata = NULL; + const SDL_MessageBoxData *messageboxdata = data->messageboxdata; + + if (messageboxdata->window) { + SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(messageboxdata->window); + windowdata = messageboxdata->window->internal; + data->screen = displaydata->screen; + } else { + data->screen = DefaultScreen(display); + } + + data->event_mask = ExposureMask | + ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask | + StructureNotifyMask | FocusChangeMask | PointerMotionMask; + wnd_attr.event_mask = data->event_mask; + + data->window = X11_XCreateWindow( + display, RootWindow(display, data->screen), + 0, 0, + data->dialog_width, data->dialog_height, + 0, CopyFromParent, InputOutput, CopyFromParent, + CWEventMask, &wnd_attr); + if (data->window == None) { + return SDL_SetError("Couldn't create X window"); + } + + if (windowdata) { + Atom _NET_WM_STATE = X11_XInternAtom(display, "_NET_WM_STATE", False); + Atom stateatoms[16]; + size_t statecount = 0; + // Set some message-boxy window states when attached to a parent window... + // we skip the taskbar since this will pop to the front when the parent window is clicked in the taskbar, etc + stateatoms[statecount++] = X11_XInternAtom(display, "_NET_WM_STATE_SKIP_TASKBAR", False); + stateatoms[statecount++] = X11_XInternAtom(display, "_NET_WM_STATE_SKIP_PAGER", False); + stateatoms[statecount++] = X11_XInternAtom(display, "_NET_WM_STATE_FOCUSED", False); + stateatoms[statecount++] = X11_XInternAtom(display, "_NET_WM_STATE_MODAL", False); + SDL_assert(statecount <= SDL_arraysize(stateatoms)); + X11_XChangeProperty(display, data->window, _NET_WM_STATE, XA_ATOM, 32, + PropModeReplace, (unsigned char *)stateatoms, statecount); + + // http://tronche.com/gui/x/icccm/sec-4.html#WM_TRANSIENT_FOR + X11_XSetTransientForHint(display, data->window, windowdata->xwindow); + } + + SDL_X11_SetWindowTitle(display, data->window, (char *)messageboxdata->title); + + // Let the window manager know this is a dialog box + _NET_WM_WINDOW_TYPE = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE", False); + _NET_WM_WINDOW_TYPE_DIALOG = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", False); + X11_XChangeProperty(display, data->window, _NET_WM_WINDOW_TYPE, XA_ATOM, 32, + PropModeReplace, + (unsigned char *)&_NET_WM_WINDOW_TYPE_DIALOG, 1); + + // Allow the window to be deleted by the window manager + data->wm_delete_message = X11_XInternAtom(display, "WM_DELETE_WINDOW", False); + X11_XSetWMProtocols(display, data->window, &data->wm_delete_message, 1); + + data->wm_protocols = X11_XInternAtom(display, "WM_PROTOCOLS", False); + + if (windowdata) { + XWindowAttributes attrib; + Window dummy; + + X11_XGetWindowAttributes(display, windowdata->xwindow, &attrib); + x = attrib.x + (attrib.width - data->dialog_width) / 2; + y = attrib.y + (attrib.height - data->dialog_height) / 3; + X11_XTranslateCoordinates(display, windowdata->xwindow, RootWindow(display, data->screen), x, y, &x, &y, &dummy); + } else { + const SDL_VideoDevice *dev = SDL_GetVideoDevice(); + if (dev && dev->displays && dev->num_displays > 0) { + const SDL_VideoDisplay *dpy = dev->displays[0]; + const SDL_DisplayData *dpydata = dpy->internal; + x = dpydata->x + ((dpy->current_mode->w - data->dialog_width) / 2); + y = dpydata->y + ((dpy->current_mode->h - data->dialog_height) / 3); + } else { // oh well. This will misposition on a multi-head setup. Init first next time. + x = (DisplayWidth(display, data->screen) - data->dialog_width) / 2; + y = (DisplayHeight(display, data->screen) - data->dialog_height) / 3; + } + } + X11_XMoveWindow(display, data->window, x, y); + + sizehints = X11_XAllocSizeHints(); + if (sizehints) { + sizehints->flags = USPosition | USSize | PMaxSize | PMinSize; + sizehints->x = x; + sizehints->y = y; + sizehints->width = data->dialog_width; + sizehints->height = data->dialog_height; + + sizehints->min_width = sizehints->max_width = data->dialog_width; + sizehints->min_height = sizehints->max_height = data->dialog_height; + + X11_XSetWMNormalHints(display, data->window, sizehints); + + X11_XFree(sizehints); + } + + X11_XMapRaised(display, data->window); + +#ifdef SDL_VIDEO_DRIVER_X11_XDBE + // Initialise a back buffer for double buffering + if (SDL_X11_HAVE_XDBE) { + int xdbe_major, xdbe_minor; + if (X11_XdbeQueryExtension(display, &xdbe_major, &xdbe_minor) != 0) { + data->xdbe = true; + data->buf = X11_XdbeAllocateBackBufferName(display, data->window, XdbeUndefined); + } else { + data->xdbe = false; + } + } +#endif + + return true; +} + +// Draw our message box. +static void X11_MessageBoxDraw(SDL_MessageBoxDataX11 *data, GC ctx) +{ + int i; + Drawable window = data->window; + Display *display = data->display; + +#ifdef SDL_VIDEO_DRIVER_X11_XDBE + if (SDL_X11_HAVE_XDBE && data->xdbe) { + window = data->buf; + X11_XdbeBeginIdiom(data->display); + } +#endif + + X11_XSetForeground(display, ctx, data->color[SDL_MESSAGEBOX_COLOR_BACKGROUND]); + X11_XFillRectangle(display, window, ctx, 0, 0, data->dialog_width, data->dialog_height); + + X11_XSetForeground(display, ctx, data->color[SDL_MESSAGEBOX_COLOR_TEXT]); + for (i = 0; i < data->numlines; i++) { + TextLineData *plinedata = &data->linedata[i]; + +#ifdef X_HAVE_UTF8_STRING + if (SDL_X11_HAVE_UTF8) { + X11_Xutf8DrawString(display, window, data->font_set, ctx, + data->xtext, data->ytext + i * data->text_height, + plinedata->text, plinedata->length); + } else +#endif + { + X11_XDrawString(display, window, ctx, + data->xtext, data->ytext + i * data->text_height, + plinedata->text, plinedata->length); + } + } + + for (i = 0; i < data->numbuttons; i++) { + SDL_MessageBoxButtonDataX11 *buttondatax11 = &data->buttonpos[i]; + const SDL_MessageBoxButtonData *buttondata = buttondatax11->buttondata; + int border = (buttondata->flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) ? 2 : 0; + int offset = ((data->mouse_over_index == i) && (data->button_press_index == data->mouse_over_index)) ? 1 : 0; + + X11_XSetForeground(display, ctx, data->color[SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND]); + X11_XFillRectangle(display, window, ctx, + buttondatax11->rect.x - border, buttondatax11->rect.y - border, + buttondatax11->rect.w + 2 * border, buttondatax11->rect.h + 2 * border); + + X11_XSetForeground(display, ctx, data->color[SDL_MESSAGEBOX_COLOR_BUTTON_BORDER]); + X11_XDrawRectangle(display, window, ctx, + buttondatax11->rect.x, buttondatax11->rect.y, + buttondatax11->rect.w, buttondatax11->rect.h); + + X11_XSetForeground(display, ctx, (data->mouse_over_index == i) ? data->color[SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED] : data->color[SDL_MESSAGEBOX_COLOR_TEXT]); + +#ifdef X_HAVE_UTF8_STRING + if (SDL_X11_HAVE_UTF8) { + X11_Xutf8DrawString(display, window, data->font_set, ctx, + buttondatax11->x + offset, + buttondatax11->y + offset, + buttondata->text, buttondatax11->length); + } else +#endif + { + X11_XDrawString(display, window, ctx, + buttondatax11->x + offset, buttondatax11->y + offset, + buttondata->text, buttondatax11->length); + } + } + +#ifdef SDL_VIDEO_DRIVER_X11_XDBE + if (SDL_X11_HAVE_XDBE && data->xdbe) { + XdbeSwapInfo swap_info; + swap_info.swap_window = data->window; + swap_info.swap_action = XdbeUndefined; + X11_XdbeSwapBuffers(data->display, &swap_info, 1); + X11_XdbeEndIdiom(data->display); + } +#endif +} + +// NOLINTNEXTLINE(readability-non-const-parameter): cannot make XPointer a const pointer due to typedef +static Bool X11_MessageBoxEventTest(Display *display, XEvent *event, XPointer arg) +{ + const SDL_MessageBoxDataX11 *data = (const SDL_MessageBoxDataX11 *)arg; + return ((event->xany.display == data->display) && (event->xany.window == data->window)) ? True : False; +} + +// Loop and handle message box event messages until something kills it. +static bool X11_MessageBoxLoop(SDL_MessageBoxDataX11 *data) +{ + GC ctx; + XGCValues ctx_vals; + bool close_dialog = false; + bool has_focus = true; + KeySym last_key_pressed = XK_VoidSymbol; + unsigned long gcflags = GCForeground | GCBackground; +#ifdef X_HAVE_UTF8_STRING + const int have_utf8 = SDL_X11_HAVE_UTF8; +#else + const int have_utf8 = 0; +#endif + + SDL_zero(ctx_vals); + ctx_vals.foreground = data->color[SDL_MESSAGEBOX_COLOR_BACKGROUND]; + ctx_vals.background = data->color[SDL_MESSAGEBOX_COLOR_BACKGROUND]; + + if (!have_utf8) { + gcflags |= GCFont; + ctx_vals.font = data->font_struct->fid; + } + + ctx = X11_XCreateGC(data->display, data->window, gcflags, &ctx_vals); + if (ctx == None) { + return SDL_SetError("Couldn't create graphics context"); + } + + data->button_press_index = -1; // Reset what button is currently depressed. + data->mouse_over_index = -1; // Reset what button the mouse is over. + + while (!close_dialog) { + XEvent e; + bool draw = true; + + // can't use XWindowEvent() because it can't handle ClientMessage events. + // can't use XNextEvent() because we only want events for this window. + X11_XIfEvent(data->display, &e, X11_MessageBoxEventTest, (XPointer)data); + + /* If X11_XFilterEvent returns True, then some input method has filtered the + event, and the client should discard the event. */ + if ((e.type != Expose) && X11_XFilterEvent(&e, None)) { + continue; + } + + switch (e.type) { + case Expose: + if (e.xexpose.count > 0) { + draw = false; + } + break; + + case FocusIn: + // Got focus. + has_focus = true; + break; + + case FocusOut: + // lost focus. Reset button and mouse info. + has_focus = false; + data->button_press_index = -1; + data->mouse_over_index = -1; + break; + + case MotionNotify: + if (has_focus) { + // Mouse moved... + const int previndex = data->mouse_over_index; + data->mouse_over_index = GetHitButtonIndex(data, e.xbutton.x, e.xbutton.y); + if (data->mouse_over_index == previndex) { + draw = false; + } + } + break; + + case ClientMessage: + if (e.xclient.message_type == data->wm_protocols && + e.xclient.format == 32 && + e.xclient.data.l[0] == data->wm_delete_message) { + close_dialog = true; + } + break; + + case KeyPress: + // Store key press - we make sure in key release that we got both. + last_key_pressed = X11_XLookupKeysym(&e.xkey, 0); + break; + + case KeyRelease: + { + Uint32 mask = 0; + KeySym key = X11_XLookupKeysym(&e.xkey, 0); + + // If this is a key release for something we didn't get the key down for, then bail. + if (key != last_key_pressed) { + break; + } + + if (key == XK_Escape) { + mask = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT; + } else if ((key == XK_Return) || (key == XK_KP_Enter)) { + mask = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT; + } + + if (mask) { + int i; + + // Look for first button with this mask set, and return it if found. + for (i = 0; i < data->numbuttons; i++) { + SDL_MessageBoxButtonDataX11 *buttondatax11 = &data->buttonpos[i]; + + if (buttondatax11->buttondata->flags & mask) { + *data->pbuttonid = buttondatax11->buttondata->buttonID; + close_dialog = true; + break; + } + } + } + break; + } + + case ButtonPress: + data->button_press_index = -1; + if (e.xbutton.button == Button1) { + // Find index of button they clicked on. + data->button_press_index = GetHitButtonIndex(data, e.xbutton.x, e.xbutton.y); + } + break; + + case ButtonRelease: + // If button is released over the same button that was clicked down on, then return it. + if ((e.xbutton.button == Button1) && (data->button_press_index >= 0)) { + int button = GetHitButtonIndex(data, e.xbutton.x, e.xbutton.y); + + if (data->button_press_index == button) { + SDL_MessageBoxButtonDataX11 *buttondatax11 = &data->buttonpos[button]; + + *data->pbuttonid = buttondatax11->buttondata->buttonID; + close_dialog = true; + } + } + data->button_press_index = -1; + break; + } + + if (draw) { + // Draw our dialog box. + X11_MessageBoxDraw(data, ctx); + } + } + + X11_XFreeGC(data->display, ctx); + return true; +} + +static bool X11_ShowMessageBoxImpl(const SDL_MessageBoxData *messageboxdata, int *buttonID) +{ + bool result = false; + SDL_MessageBoxDataX11 data; +#if SDL_SET_LOCALE + char *origlocale; +#endif + + SDL_zero(data); + + if (!SDL_X11_LoadSymbols()) { + return false; + } + +#if SDL_SET_LOCALE + origlocale = setlocale(LC_ALL, NULL); + if (origlocale) { + origlocale = SDL_strdup(origlocale); + if (!origlocale) { + return false; + } + (void)setlocale(LC_ALL, ""); + } +#endif + + // This code could get called from multiple threads maybe? + X11_XInitThreads(); + + // Initialize the return buttonID value to -1 (for error or dialogbox closed). + *buttonID = -1; + + // Init and display the message box. + if (X11_MessageBoxInit(&data, messageboxdata, buttonID) && + X11_MessageBoxInitPositions(&data) && + X11_MessageBoxCreateWindow(&data)) { + result = X11_MessageBoxLoop(&data); + } + + X11_MessageBoxShutdown(&data); + +#if SDL_SET_LOCALE + if (origlocale) { + (void)setlocale(LC_ALL, origlocale); + SDL_free(origlocale); + } +#endif + + return result; +} + +// Display an x11 message box. +bool X11_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID) +{ +#if SDL_FORK_MESSAGEBOX + // Use a child process to protect against setlocale(). Annoying. + pid_t pid; + int fds[2]; + int status = 0; + bool result = true; + + if (pipe(fds) == -1) { + return X11_ShowMessageBoxImpl(messageboxdata, buttonID); // oh well. + } + + pid = fork(); + if (pid == -1) { // failed + close(fds[0]); + close(fds[1]); + return X11_ShowMessageBoxImpl(messageboxdata, buttonID); // oh well. + } else if (pid == 0) { // we're the child + int exitcode = 0; + close(fds[0]); + result = X11_ShowMessageBoxImpl(messageboxdata, buttonID); + if (write(fds[1], &result, sizeof(result)) != sizeof(result)) { + exitcode = 1; + } else if (write(fds[1], buttonID, sizeof(*buttonID)) != sizeof(*buttonID)) { + exitcode = 1; + } + close(fds[1]); + _exit(exitcode); // don't run atexit() stuff, static destructors, etc. + } else { // we're the parent + pid_t rc; + close(fds[1]); + do { + rc = waitpid(pid, &status, 0); + } while ((rc == -1) && (errno == EINTR)); + + SDL_assert(rc == pid); // not sure what to do if this fails. + + if ((rc == -1) || (!WIFEXITED(status)) || (WEXITSTATUS(status) != 0)) { + result = SDL_SetError("msgbox child process failed"); + } else if ((read(fds[0], &result, sizeof(result)) != sizeof(result)) || + (read(fds[0], buttonID, sizeof(*buttonID)) != sizeof(*buttonID))) { + result = SDL_SetError("read from msgbox child process failed"); + *buttonID = 0; + } + close(fds[0]); + + return result; + } +#else + return X11_ShowMessageBoxImpl(messageboxdata, buttonID); +#endif +} +#endif // SDL_VIDEO_DRIVER_X11 diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11messagebox.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11messagebox.h new file mode 100644 index 0000000..9baa875 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11messagebox.h @@ -0,0 +1,31 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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_x11messagebox_h_ +#define SDL_x11messagebox_h_ + +#ifdef SDL_VIDEO_DRIVER_X11 + +extern bool X11_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID); + +#endif // SDL_VIDEO_DRIVER_X11 + +#endif // SDL_x11messagebox_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11modes.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11modes.c new file mode 100644 index 0000000..e17a2d1 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11modes.c @@ -0,0 +1,1051 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifdef SDL_VIDEO_DRIVER_X11 + +#include "SDL_x11video.h" +#include "SDL_x11settings.h" +#include "edid.h" +#include "../../events/SDL_displayevents_c.h" + +// #define X11MODES_DEBUG + +/* Timeout and revert mode switches if the timespan has elapsed without the window becoming fullscreen. + * 5 seconds seems good from testing. + */ +#define MODE_SWITCH_TIMEOUT_NS SDL_NS_PER_SECOND * 5 + +/* I'm becoming more and more convinced that the application should never + * use XRandR, and it's the window manager's responsibility to track and + * manage display modes for fullscreen windows. Right now XRandR is completely + * broken with respect to window manager behavior on every window manager that + * I can find. For example, on Unity 3D if you show a fullscreen window while + * the resolution is changing (within ~250 ms) your window will retain the + * fullscreen state hint but be decorated and windowed. + * + * However, many people swear by it, so let them swear at it. :) + */ +// #define XRANDR_DISABLED_BY_DEFAULT + +static float GetGlobalContentScale(SDL_VideoDevice *_this) +{ + static double scale_factor = 0.0; + + if (scale_factor <= 0.0) { + + // First use the forced scaling factor specified by the app/user + const char *hint = SDL_GetHint(SDL_HINT_VIDEO_X11_SCALING_FACTOR); + if (hint && *hint) { + double value = SDL_atof(hint); + if (value >= 1.0f && value <= 10.0f) { + scale_factor = value; + } + } + + // If that failed, try "Xft.dpi" from the XResourcesDatabase... + if (scale_factor <= 0.0) + { + SDL_VideoData *data = _this->internal; + Display *display = data->display; + char *resource_manager; + XrmDatabase db; + XrmValue value; + char *type; + + X11_XrmInitialize(); + + resource_manager = X11_XResourceManagerString(display); + if (resource_manager) { + db = X11_XrmGetStringDatabase(resource_manager); + + // Get the value of Xft.dpi from the Database + if (X11_XrmGetResource(db, "Xft.dpi", "String", &type, &value)) { + if (value.addr && type && SDL_strcmp(type, "String") == 0) { + int dpi = SDL_atoi(value.addr); + scale_factor = dpi / 96.0; + } + } + X11_XrmDestroyDatabase(db); + } + } + + // If that failed, try the XSETTINGS keys... + if (scale_factor <= 0.0) { + scale_factor = X11_GetXsettingsIntKey(_this, "Gdk/WindowScalingFactor", -1); + + // The Xft/DPI key is stored in increments of 1024th + if (scale_factor <= 0.0) { + int dpi = X11_GetXsettingsIntKey(_this, "Xft/DPI", -1); + if (dpi > 0) { + scale_factor = (double) dpi / 1024.0; + scale_factor /= 96.0; + } + } + } + + // If that failed, try the GDK_SCALE envvar... + if (scale_factor <= 0.0) { + const char *scale_str = SDL_getenv("GDK_SCALE"); + if (scale_str) { + scale_factor = SDL_atoi(scale_str); + } + } + + // Nothing or a bad value, just fall back to 1.0 + if (scale_factor <= 0.0) { + scale_factor = 1.0; + } + } + + return (float)scale_factor; +} + +static bool get_visualinfo(Display *display, int screen, XVisualInfo *vinfo) +{ + const char *visual_id = SDL_GetHint(SDL_HINT_VIDEO_X11_VISUALID); + int depth; + + // Look for an exact visual, if requested + if (visual_id && *visual_id) { + XVisualInfo *vi, template; + int nvis; + + SDL_zero(template); + template.visualid = SDL_strtol(visual_id, NULL, 0); + vi = X11_XGetVisualInfo(display, VisualIDMask, &template, &nvis); + if (vi) { + *vinfo = *vi; + X11_XFree(vi); + return true; + } + } + + depth = DefaultDepth(display, screen); + if ((X11_UseDirectColorVisuals() && + X11_XMatchVisualInfo(display, screen, depth, DirectColor, vinfo)) || + X11_XMatchVisualInfo(display, screen, depth, TrueColor, vinfo) || + X11_XMatchVisualInfo(display, screen, depth, PseudoColor, vinfo) || + X11_XMatchVisualInfo(display, screen, depth, StaticColor, vinfo)) { + return true; + } + return false; +} + +bool X11_GetVisualInfoFromVisual(Display *display, Visual *visual, XVisualInfo *vinfo) +{ + XVisualInfo *vi; + int nvis; + + vinfo->visualid = X11_XVisualIDFromVisual(visual); + vi = X11_XGetVisualInfo(display, VisualIDMask, vinfo, &nvis); + if (vi) { + *vinfo = *vi; + X11_XFree(vi); + return true; + } + return false; +} + +SDL_PixelFormat X11_GetPixelFormatFromVisualInfo(Display *display, XVisualInfo *vinfo) +{ + if (vinfo->class == DirectColor || vinfo->class == TrueColor) { + int bpp; + Uint32 Rmask, Gmask, Bmask, Amask; + + Rmask = vinfo->visual->red_mask; + Gmask = vinfo->visual->green_mask; + Bmask = vinfo->visual->blue_mask; + if (vinfo->depth == 32) { + Amask = (0xFFFFFFFF & ~(Rmask | Gmask | Bmask)); + } else { + Amask = 0; + } + + bpp = vinfo->depth; + if (bpp == 24) { + int i, n; + XPixmapFormatValues *p = X11_XListPixmapFormats(display, &n); + if (p) { + for (i = 0; i < n; ++i) { + if (p[i].depth == 24) { + bpp = p[i].bits_per_pixel; + break; + } + } + X11_XFree(p); + } + } + + return SDL_GetPixelFormatForMasks(bpp, Rmask, Gmask, Bmask, Amask); + } + + if (vinfo->class == PseudoColor || vinfo->class == StaticColor) { + switch (vinfo->depth) { + case 8: + return SDL_PIXELFORMAT_INDEX8; + case 4: + if (BitmapBitOrder(display) == LSBFirst) { + return SDL_PIXELFORMAT_INDEX4LSB; + } else { + return SDL_PIXELFORMAT_INDEX4MSB; + } + // break; -Wunreachable-code-break + case 1: + if (BitmapBitOrder(display) == LSBFirst) { + return SDL_PIXELFORMAT_INDEX1LSB; + } else { + return SDL_PIXELFORMAT_INDEX1MSB; + } + // break; -Wunreachable-code-break + } + } + + return SDL_PIXELFORMAT_UNKNOWN; +} + +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR +static bool CheckXRandR(Display *display, int *major, int *minor) +{ + // Default the extension not available + *major = *minor = 0; + + // Allow environment override +#ifdef XRANDR_DISABLED_BY_DEFAULT + if (!SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_XRANDR, false)) { +#ifdef X11MODES_DEBUG + printf("XRandR disabled by default due to window manager issues\n"); +#endif + return false; + } +#else + if (!SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_XRANDR, true)) { +#ifdef X11MODES_DEBUG + printf("XRandR disabled due to hint\n"); +#endif + return false; + } +#endif // XRANDR_DISABLED_BY_DEFAULT + + if (!SDL_X11_HAVE_XRANDR) { +#ifdef X11MODES_DEBUG + printf("XRandR support not available\n"); +#endif + return false; + } + + // Query the extension version + *major = 1; + *minor = 3; // we want 1.3 + if (!X11_XRRQueryVersion(display, major, minor)) { +#ifdef X11MODES_DEBUG + printf("XRandR not active on the display\n"); +#endif + *major = *minor = 0; + return false; + } +#ifdef X11MODES_DEBUG + printf("XRandR available at version %d.%d!\n", *major, *minor); +#endif + return true; +} + +#define XRANDR_ROTATION_LEFT (1 << 1) +#define XRANDR_ROTATION_RIGHT (1 << 3) + +static void CalculateXRandRRefreshRate(const XRRModeInfo *info, int *numerator, int *denominator) +{ + unsigned int vTotal = info->vTotal; + + if (info->modeFlags & RR_DoubleScan) { + // doublescan doubles the number of lines + vTotal *= 2; + } + + if (info->modeFlags & RR_Interlace) { + // interlace splits the frame into two fields + // the field rate is what is typically reported by monitors + vTotal /= 2; + } + + if (info->hTotal && vTotal) { + *numerator = info->dotClock; + *denominator = (info->hTotal * vTotal); + } else { + *numerator = 0; + *denominator = 0; + } +} + +static bool SetXRandRModeInfo(Display *display, XRRScreenResources *res, RRCrtc crtc, + RRMode modeID, SDL_DisplayMode *mode) +{ + int i; + for (i = 0; i < res->nmode; ++i) { + const XRRModeInfo *info = &res->modes[i]; + if (info->id == modeID) { + XRRCrtcInfo *crtcinfo; + Rotation rotation = 0; + XFixed scale_w = 0x10000, scale_h = 0x10000; + XRRCrtcTransformAttributes *attr; + + crtcinfo = X11_XRRGetCrtcInfo(display, res, crtc); + if (crtcinfo) { + rotation = crtcinfo->rotation; + X11_XRRFreeCrtcInfo(crtcinfo); + } + if (X11_XRRGetCrtcTransform(display, crtc, &attr) && attr) { + scale_w = attr->currentTransform.matrix[0][0]; + scale_h = attr->currentTransform.matrix[1][1]; + X11_XFree(attr); + } + + if (rotation & (XRANDR_ROTATION_LEFT | XRANDR_ROTATION_RIGHT)) { + mode->w = (info->height * scale_w + 0xffff) >> 16; + mode->h = (info->width * scale_h + 0xffff) >> 16; + } else { + mode->w = (info->width * scale_w + 0xffff) >> 16; + mode->h = (info->height * scale_h + 0xffff) >> 16; + } + CalculateXRandRRefreshRate(info, &mode->refresh_rate_numerator, &mode->refresh_rate_denominator); + mode->internal->xrandr_mode = modeID; +#ifdef X11MODES_DEBUG + printf("XRandR mode %d: %dx%d@%d/%dHz\n", (int)modeID, + mode->screen_w, mode->screen_h, mode->refresh_rate_numerator, mode->refresh_rate_denominator); +#endif + return true; + } + } + return false; +} + +static void SetXRandRDisplayName(Display *dpy, Atom EDID, char *name, const size_t namelen, RROutput output, const unsigned long widthmm, const unsigned long heightmm) +{ + // See if we can get the EDID data for the real monitor name + int inches; + int nprop; + Atom *props = X11_XRRListOutputProperties(dpy, output, &nprop); + int i; + + for (i = 0; i < nprop; ++i) { + unsigned char *prop; + int actual_format; + unsigned long nitems, bytes_after; + Atom actual_type; + + if (props[i] == EDID) { + if (X11_XRRGetOutputProperty(dpy, output, props[i], 0, 100, False, + False, AnyPropertyType, &actual_type, + &actual_format, &nitems, &bytes_after, + &prop) == Success) { + MonitorInfo *info = decode_edid(prop); + if (info) { +#ifdef X11MODES_DEBUG + printf("Found EDID data for %s\n", name); + dump_monitor_info(info); +#endif + SDL_strlcpy(name, info->dsc_product_name, namelen); + SDL_free(info); + } + X11_XFree(prop); + } + break; + } + } + + if (props) { + X11_XFree(props); + } + + inches = (int)((SDL_sqrtf(widthmm * widthmm + heightmm * heightmm) / 25.4f) + 0.5f); + if (*name && inches) { + const size_t len = SDL_strlen(name); + (void)SDL_snprintf(&name[len], namelen - len, " %d\"", inches); + } + +#ifdef X11MODES_DEBUG + printf("Display name: %s\n", name); +#endif +} + +static bool X11_FillXRandRDisplayInfo(SDL_VideoDevice *_this, Display *dpy, int screen, RROutput outputid, XRRScreenResources *res, SDL_VideoDisplay *display, char *display_name, size_t display_name_size) +{ + Atom EDID = X11_XInternAtom(dpy, "EDID", False); + XRROutputInfo *output_info; + int display_x, display_y; + unsigned long display_mm_width, display_mm_height; + SDL_DisplayData *displaydata; + SDL_DisplayMode mode; + SDL_DisplayModeData *modedata; + RRMode modeID; + RRCrtc output_crtc; + XRRCrtcInfo *crtc; + XVisualInfo vinfo; + Uint32 pixelformat; + XPixmapFormatValues *pixmapformats; + int scanline_pad; + int i, n; + + if (!display || !display_name) { + return false; // invalid parameters + } + + if (!get_visualinfo(dpy, screen, &vinfo)) { + return false; // uh, skip this screen? + } + + pixelformat = X11_GetPixelFormatFromVisualInfo(dpy, &vinfo); + if (SDL_ISPIXELFORMAT_INDEXED(pixelformat)) { + return false; // Palettized video modes are no longer supported, ignore this one. + } + + scanline_pad = SDL_BYTESPERPIXEL(pixelformat) * 8; + pixmapformats = X11_XListPixmapFormats(dpy, &n); + if (pixmapformats) { + for (i = 0; i < n; i++) { + if (pixmapformats[i].depth == vinfo.depth) { + scanline_pad = pixmapformats[i].scanline_pad; + break; + } + } + X11_XFree(pixmapformats); + } + + output_info = X11_XRRGetOutputInfo(dpy, res, outputid); + if (!output_info || !output_info->crtc || output_info->connection == RR_Disconnected) { + X11_XRRFreeOutputInfo(output_info); + return false; // ignore this one. + } + + SDL_strlcpy(display_name, output_info->name, display_name_size); + display_mm_width = output_info->mm_width; + display_mm_height = output_info->mm_height; + output_crtc = output_info->crtc; + X11_XRRFreeOutputInfo(output_info); + + crtc = X11_XRRGetCrtcInfo(dpy, res, output_crtc); + if (!crtc) { + return false; // oh well, ignore it. + } + + SDL_zero(mode); + modeID = crtc->mode; + mode.w = crtc->width; + mode.h = crtc->height; + mode.format = pixelformat; + + display_x = crtc->x; + display_y = crtc->y; + + X11_XRRFreeCrtcInfo(crtc); + + displaydata = (SDL_DisplayData *)SDL_calloc(1, sizeof(*displaydata)); + if (!displaydata) { + return false; + } + + modedata = (SDL_DisplayModeData *)SDL_calloc(1, sizeof(SDL_DisplayModeData)); + if (!modedata) { + SDL_free(displaydata); + return false; + } + + modedata->xrandr_mode = modeID; + mode.internal = modedata; + + displaydata->screen = screen; + displaydata->visual = vinfo.visual; + displaydata->depth = vinfo.depth; + displaydata->scanline_pad = scanline_pad; + displaydata->x = display_x; + displaydata->y = display_y; + displaydata->use_xrandr = true; + displaydata->xrandr_output = outputid; + SDL_strlcpy(displaydata->connector_name, display_name, sizeof(displaydata->connector_name)); + + SetXRandRModeInfo(dpy, res, output_crtc, modeID, &mode); + SetXRandRDisplayName(dpy, EDID, display_name, display_name_size, outputid, display_mm_width, display_mm_height); + + SDL_zero(*display); + if (*display_name) { + display->name = display_name; + } + display->desktop_mode = mode; + display->content_scale = GetGlobalContentScale(_this); + display->internal = displaydata; + + return true; +} + +static bool X11_AddXRandRDisplay(SDL_VideoDevice *_this, Display *dpy, int screen, RROutput outputid, XRRScreenResources *res, bool send_event) +{ + SDL_VideoDisplay display; + char display_name[128]; + + if (!X11_FillXRandRDisplayInfo(_this, dpy, screen, outputid, res, &display, display_name, sizeof(display_name))) { + return true; // failed to query data, skip this display + } + + if (SDL_AddVideoDisplay(&display, send_event) == 0) { + return false; + } + + return true; +} + + +static bool X11_UpdateXRandRDisplay(SDL_VideoDevice *_this, Display *dpy, int screen, RROutput outputid, XRRScreenResources *res, SDL_VideoDisplay *existing_display) +{ + SDL_VideoDisplay display; + char display_name[128]; + + if (!X11_FillXRandRDisplayInfo(_this, dpy, screen, outputid, res, &display, display_name, sizeof(display_name))) { + return false; // failed to query current display state + } + + // update mode - this call takes ownership of display.desktop_mode.internal + SDL_SetDesktopDisplayMode(existing_display, &display.desktop_mode); + + // update bounds + if (existing_display->internal->x != display.internal->x || + existing_display->internal->y != display.internal->y) { + existing_display->internal->x = display.internal->x; + existing_display->internal->y = display.internal->y; + SDL_SendDisplayEvent(existing_display, SDL_EVENT_DISPLAY_MOVED, 0, 0); + } + + // update scale + SDL_SetDisplayContentScale(existing_display, display.content_scale); + + // SDL_DisplayData is updated piece-meal above, free our local copy of this data + SDL_free( display.internal ); + + return true; +} + +static XRRScreenResources *X11_GetScreenResources(Display *dpy, int screen) +{ + XRRScreenResources *res = X11_XRRGetScreenResourcesCurrent(dpy, RootWindow(dpy, screen)); + if (!res || res->noutput == 0) { + if (res) { + X11_XRRFreeScreenResources(res); + } + res = X11_XRRGetScreenResources(dpy, RootWindow(dpy, screen)); + } + return res; +} + +static void X11_CheckDisplaysMoved(SDL_VideoDevice *_this, Display *dpy) +{ + const int screen = DefaultScreen(dpy); + XRRScreenResources *res = X11_GetScreenResources(dpy, screen); + if (!res) { + return; + } + + SDL_DisplayID *displays = SDL_GetDisplays(NULL); + if (displays) { + for (int i = 0; displays[i]; ++i) { + SDL_VideoDisplay *display = SDL_GetVideoDisplay(displays[i]); + const SDL_DisplayData *displaydata = display->internal; + X11_UpdateXRandRDisplay(_this, dpy, screen, displaydata->xrandr_output, res, display); + } + SDL_free(displays); + } + X11_XRRFreeScreenResources(res); +} + +static void X11_HandleXRandROutputChange(SDL_VideoDevice *_this, const XRROutputChangeNotifyEvent *ev) +{ + SDL_DisplayID *displays; + SDL_VideoDisplay *display = NULL; + int i; + +#if 0 + printf("XRROutputChangeNotifyEvent! [output=%u, crtc=%u, mode=%u, rotation=%u, connection=%u]", (unsigned int) ev->output, (unsigned int) ev->crtc, (unsigned int) ev->mode, (unsigned int) ev->rotation, (unsigned int) ev->connection); +#endif + + displays = SDL_GetDisplays(NULL); + if (displays) { + for (i = 0; displays[i]; ++i) { + SDL_VideoDisplay *thisdisplay = SDL_GetVideoDisplay(displays[i]); + const SDL_DisplayData *displaydata = thisdisplay->internal; + if (displaydata->xrandr_output == ev->output) { + display = thisdisplay; + break; + } + } + SDL_free(displays); + } + + if (ev->connection == RR_Disconnected) { // output is going away + if (display) { + SDL_DelVideoDisplay(display->id, true); + } + X11_CheckDisplaysMoved(_this, ev->display); + + } else if (ev->connection == RR_Connected) { // output is coming online + if (!display) { + Display *dpy = ev->display; + const int screen = DefaultScreen(dpy); + XRRScreenResources *res = X11_GetScreenResources(dpy, screen); + if (res) { + X11_AddXRandRDisplay(_this, dpy, screen, ev->output, res, true); + X11_XRRFreeScreenResources(res); + } + } + X11_CheckDisplaysMoved(_this, ev->display); + } +} + +void X11_HandleXRandREvent(SDL_VideoDevice *_this, const XEvent *xevent) +{ + SDL_VideoData *videodata = _this->internal; + SDL_assert(xevent->type == (videodata->xrandr_event_base + RRNotify)); + + switch (((const XRRNotifyEvent *)xevent)->subtype) { + case RRNotify_OutputChange: + X11_HandleXRandROutputChange(_this, (const XRROutputChangeNotifyEvent *)xevent); + break; + default: + break; + } +} + +static void X11_SortOutputsByPriorityHint(SDL_VideoDevice *_this) +{ + const char *name_hint = SDL_GetHint(SDL_HINT_VIDEO_DISPLAY_PRIORITY); + + if (name_hint) { + char *saveptr; + char *str = SDL_strdup(name_hint); + SDL_VideoDisplay **sorted_list = SDL_malloc(sizeof(SDL_VideoDisplay *) * _this->num_displays); + + if (str && sorted_list) { + int sorted_index = 0; + + // Sort the requested displays to the front of the list. + const char *token = SDL_strtok_r(str, ",", &saveptr); + while (token) { + for (int i = 0; i < _this->num_displays; ++i) { + SDL_VideoDisplay *d = _this->displays[i]; + if (d) { + SDL_DisplayData *data = d->internal; + if (SDL_strcmp(token, data->connector_name) == 0) { + sorted_list[sorted_index++] = d; + _this->displays[i] = NULL; + break; + } + } + } + + token = SDL_strtok_r(NULL, ",", &saveptr); + } + + // Append the remaining displays to the end of the list. + for (int i = 0; i < _this->num_displays; ++i) { + if (_this->displays[i]) { + sorted_list[sorted_index++] = _this->displays[i]; + } + } + + // Copy the sorted list back to the display list. + SDL_memcpy(_this->displays, sorted_list, sizeof(SDL_VideoDisplay *) * _this->num_displays); + } + + SDL_free(str); + SDL_free(sorted_list); + } +} + +static bool X11_InitModes_XRandR(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + Display *dpy = data->display; + const int screencount = ScreenCount(dpy); + const int default_screen = DefaultScreen(dpy); + RROutput primary = X11_XRRGetOutputPrimary(dpy, RootWindow(dpy, default_screen)); + int xrandr_error_base = 0; + int looking_for_primary; + int output; + int screen; + + if (!X11_XRRQueryExtension(dpy, &data->xrandr_event_base, &xrandr_error_base)) { + return SDL_SetError("XRRQueryExtension failed"); + } + + for (looking_for_primary = 1; looking_for_primary >= 0; looking_for_primary--) { + for (screen = 0; screen < screencount; screen++) { + + // we want the primary output first, and then skipped later. + if (looking_for_primary && (screen != default_screen)) { + continue; + } + + XRRScreenResources *res = X11_GetScreenResources(dpy, screen); + if (!res) { + continue; + } + + for (output = 0; output < res->noutput; output++) { + // The primary output _should_ always be sorted first, but just in case... + if ((looking_for_primary && (res->outputs[output] != primary)) || + (!looking_for_primary && (screen == default_screen) && (res->outputs[output] == primary))) { + continue; + } + if (!X11_AddXRandRDisplay(_this, dpy, screen, res->outputs[output], res, false)) { + break; + } + } + + X11_XRRFreeScreenResources(res); + + // This will generate events for displays that come and go at runtime. + X11_XRRSelectInput(dpy, RootWindow(dpy, screen), RROutputChangeNotifyMask); + } + } + + if (_this->num_displays == 0) { + return SDL_SetError("No available displays"); + } + + X11_SortOutputsByPriorityHint(_this); + + return true; +} +#endif // SDL_VIDEO_DRIVER_X11_XRANDR + +/* This is used if there's no better functionality--like XRandR--to use. + It won't attempt to supply different display modes at all, but it can + enumerate the current displays and their current sizes. */ +static bool X11_InitModes_StdXlib(SDL_VideoDevice *_this) +{ + // !!! FIXME: a lot of copy/paste from X11_InitModes_XRandR in this function. + SDL_VideoData *data = _this->internal; + Display *dpy = data->display; + const int default_screen = DefaultScreen(dpy); + Screen *screen = ScreenOfDisplay(dpy, default_screen); + int scanline_pad, n, i; + SDL_DisplayModeData *modedata; + SDL_DisplayData *displaydata; + SDL_DisplayMode mode; + XPixmapFormatValues *pixmapformats; + Uint32 pixelformat; + XVisualInfo vinfo; + SDL_VideoDisplay display; + + // note that generally even if you have a multiple physical monitors, ScreenCount(dpy) still only reports ONE screen. + + if (!get_visualinfo(dpy, default_screen, &vinfo)) { + return SDL_SetError("Failed to find an X11 visual for the primary display"); + } + + pixelformat = X11_GetPixelFormatFromVisualInfo(dpy, &vinfo); + if (SDL_ISPIXELFORMAT_INDEXED(pixelformat)) { + return SDL_SetError("Palettized video modes are no longer supported"); + } + + SDL_zero(mode); + mode.w = WidthOfScreen(screen); + mode.h = HeightOfScreen(screen); + mode.format = pixelformat; + + displaydata = (SDL_DisplayData *)SDL_calloc(1, sizeof(*displaydata)); + if (!displaydata) { + return false; + } + + modedata = (SDL_DisplayModeData *)SDL_calloc(1, sizeof(SDL_DisplayModeData)); + if (!modedata) { + SDL_free(displaydata); + return false; + } + mode.internal = modedata; + + displaydata->screen = default_screen; + displaydata->visual = vinfo.visual; + displaydata->depth = vinfo.depth; + + scanline_pad = SDL_BYTESPERPIXEL(pixelformat) * 8; + pixmapformats = X11_XListPixmapFormats(dpy, &n); + if (pixmapformats) { + for (i = 0; i < n; ++i) { + if (pixmapformats[i].depth == vinfo.depth) { + scanline_pad = pixmapformats[i].scanline_pad; + break; + } + } + X11_XFree(pixmapformats); + } + + displaydata->scanline_pad = scanline_pad; + displaydata->x = 0; + displaydata->y = 0; + displaydata->use_xrandr = false; + + SDL_zero(display); + display.name = (char *)"Generic X11 Display"; /* this is just copied and thrown away, it's safe to cast to char* here. */ + display.desktop_mode = mode; + display.internal = displaydata; + display.content_scale = GetGlobalContentScale(_this); + if (SDL_AddVideoDisplay(&display, true) == 0) { + return false; + } + return true; +} + +bool X11_InitModes(SDL_VideoDevice *_this) +{ + /* XRandR is the One True Modern Way to do this on X11. If this + fails, we just won't report any display modes except the current + desktop size. */ +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR + { + SDL_VideoData *data = _this->internal; + int xrandr_major, xrandr_minor; + // require at least XRandR v1.3 + if (CheckXRandR(data->display, &xrandr_major, &xrandr_minor) && + (xrandr_major >= 2 || (xrandr_major == 1 && xrandr_minor >= 3)) && + X11_InitModes_XRandR(_this)) { + return true; + } + } +#endif // SDL_VIDEO_DRIVER_X11_XRANDR + + // still here? Just set up an extremely basic display. + return X11_InitModes_StdXlib(_this); +} + +bool X11_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR + SDL_DisplayData *data = sdl_display->internal; + SDL_DisplayMode mode; + + /* Unfortunately X11 requires the window to be created with the correct + * visual and depth ahead of time, but the SDL API allows you to create + * a window before setting the fullscreen display mode. This means that + * we have to use the same format for all windows and all display modes. + * (or support recreating the window with a new visual behind the scenes) + */ + SDL_zero(mode); + mode.format = sdl_display->desktop_mode.format; + + if (data->use_xrandr) { + Display *display = _this->internal->display; + XRRScreenResources *res; + + res = X11_XRRGetScreenResources(display, RootWindow(display, data->screen)); + if (res) { + SDL_DisplayModeData *modedata; + XRROutputInfo *output_info; + int i; + + output_info = X11_XRRGetOutputInfo(display, res, data->xrandr_output); + if (output_info && output_info->connection != RR_Disconnected) { + for (i = 0; i < output_info->nmode; ++i) { + modedata = (SDL_DisplayModeData *)SDL_calloc(1, sizeof(SDL_DisplayModeData)); + if (!modedata) { + continue; + } + mode.internal = modedata; + + if (!SetXRandRModeInfo(display, res, output_info->crtc, output_info->modes[i], &mode) || + !SDL_AddFullscreenDisplayMode(sdl_display, &mode)) { + SDL_free(modedata); + } + } + } + X11_XRRFreeOutputInfo(output_info); + X11_XRRFreeScreenResources(res); + } + } +#endif // SDL_VIDEO_DRIVER_X11_XRANDR + return true; +} + +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR +// This catches an error from XRRSetScreenSize, as a workaround for now. +// !!! FIXME: remove this later when we have a better solution. +static int (*PreXRRSetScreenSizeErrorHandler)(Display *, XErrorEvent *) = NULL; +static int SDL_XRRSetScreenSizeErrHandler(Display *d, XErrorEvent *e) +{ + // BadMatch: https://github.com/libsdl-org/SDL/issues/4561 + // BadValue: https://github.com/libsdl-org/SDL/issues/4840 + if ((e->error_code == BadMatch) || (e->error_code == BadValue)) { + return 0; + } + + return PreXRRSetScreenSizeErrorHandler(d, e); +} +#endif + +bool X11_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display, SDL_DisplayMode *mode) +{ + SDL_VideoData *viddata = _this->internal; + SDL_DisplayData *data = sdl_display->internal; + + viddata->last_mode_change_deadline = SDL_GetTicks() + (PENDING_FOCUS_TIME * 2); + + // XWayland mode switches are emulated with viewports and thus instantaneous. + if (!viddata->is_xwayland) { + if (sdl_display->current_mode != mode) { + data->mode_switch_deadline_ns = SDL_GetTicksNS() + MODE_SWITCH_TIMEOUT_NS; + } else { + data->mode_switch_deadline_ns = 0; + } + } + +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR + if (data->use_xrandr) { + Display *display = viddata->display; + SDL_DisplayModeData *modedata = mode->internal; + int mm_width, mm_height; + XRRScreenResources *res; + XRROutputInfo *output_info; + XRRCrtcInfo *crtc; + Status status; + + res = X11_XRRGetScreenResources(display, RootWindow(display, data->screen)); + if (!res) { + return SDL_SetError("Couldn't get XRandR screen resources"); + } + + output_info = X11_XRRGetOutputInfo(display, res, data->xrandr_output); + if (!output_info || output_info->connection == RR_Disconnected) { + X11_XRRFreeScreenResources(res); + return SDL_SetError("Couldn't get XRandR output info"); + } + + crtc = X11_XRRGetCrtcInfo(display, res, output_info->crtc); + if (!crtc) { + X11_XRRFreeOutputInfo(output_info); + X11_XRRFreeScreenResources(res); + return SDL_SetError("Couldn't get XRandR crtc info"); + } + + if (crtc->mode == modedata->xrandr_mode) { +#ifdef X11MODES_DEBUG + printf("already in desired mode 0x%lx (%ux%u), nothing to do\n", + crtc->mode, crtc->width, crtc->height); +#endif + status = Success; + goto freeInfo; + } + + X11_XGrabServer(display); + status = X11_XRRSetCrtcConfig(display, res, output_info->crtc, CurrentTime, + 0, 0, None, crtc->rotation, NULL, 0); + if (status != Success) { + goto ungrabServer; + } + + mm_width = mode->w * DisplayWidthMM(display, data->screen) / DisplayWidth(display, data->screen); + mm_height = mode->h * DisplayHeightMM(display, data->screen) / DisplayHeight(display, data->screen); + + /* !!! FIXME: this can get into a problem scenario when a window is + bigger than a physical monitor in a configuration where one screen + spans multiple physical monitors. A detailed reproduction case is + discussed at https://github.com/libsdl-org/SDL/issues/4561 ... + for now we cheat and just catch the X11 error and carry on, which + is likely to cause subtle issues but is better than outright + crashing */ + X11_XSync(display, False); + PreXRRSetScreenSizeErrorHandler = X11_XSetErrorHandler(SDL_XRRSetScreenSizeErrHandler); + X11_XRRSetScreenSize(display, RootWindow(display, data->screen), + mode->w, mode->h, mm_width, mm_height); + X11_XSync(display, False); + X11_XSetErrorHandler(PreXRRSetScreenSizeErrorHandler); + + status = X11_XRRSetCrtcConfig(display, res, output_info->crtc, CurrentTime, + crtc->x, crtc->y, modedata->xrandr_mode, crtc->rotation, + &data->xrandr_output, 1); + + ungrabServer: + X11_XUngrabServer(display); + freeInfo: + X11_XRRFreeCrtcInfo(crtc); + X11_XRRFreeOutputInfo(output_info); + X11_XRRFreeScreenResources(res); + + if (status != Success) { + return SDL_SetError("X11_XRRSetCrtcConfig failed"); + } + } +#else + (void)data; +#endif // SDL_VIDEO_DRIVER_X11_XRANDR + + return true; +} + +void X11_QuitModes(SDL_VideoDevice *_this) +{ +} + +bool X11_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display, SDL_Rect *rect) +{ + SDL_DisplayData *data = sdl_display->internal; + + rect->x = data->x; + rect->y = data->y; + rect->w = sdl_display->current_mode->w; + rect->h = sdl_display->current_mode->h; + return true; +} + +bool X11_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display, SDL_Rect *rect) +{ + SDL_VideoData *data = _this->internal; + Display *display = data->display; + Atom _NET_WORKAREA; + int real_format; + Atom real_type; + unsigned long items_read = 0, items_left = 0; + unsigned char *propdata = NULL; + bool result = false; + + if (!X11_GetDisplayBounds(_this, sdl_display, rect)) { + return false; + } + + _NET_WORKAREA = X11_XInternAtom(display, "_NET_WORKAREA", False); + int status = X11_XGetWindowProperty(display, DefaultRootWindow(display), + _NET_WORKAREA, 0L, 4L, False, XA_CARDINAL, + &real_type, &real_format, &items_read, + &items_left, &propdata); + if ((status == Success) && (items_read >= 4)) { + const long *p = (long *)propdata; + const SDL_Rect usable = { (int)p[0], (int)p[1], (int)p[2], (int)p[3] }; + result = true; + if (!SDL_GetRectIntersection(rect, &usable, rect)) { + SDL_zerop(rect); + } + } + + if (propdata) { + X11_XFree(propdata); + } + + return result; +} + +#endif // SDL_VIDEO_DRIVER_X11 diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11modes.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11modes.h new file mode 100644 index 0000000..35fd866 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11modes.h @@ -0,0 +1,69 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifndef SDL_x11modes_h_ +#define SDL_x11modes_h_ + +struct SDL_DisplayData +{ + int screen; + Visual *visual; + int depth; + int scanline_pad; + int x; + int y; + + Uint64 mode_switch_deadline_ns; + + bool use_xrandr; + +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR + RROutput xrandr_output; + char connector_name[16]; +#endif +}; + +struct SDL_DisplayModeData +{ +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR + RRMode xrandr_mode; +#else + int unused; // just so struct isn't empty. +#endif +}; + +extern bool X11_InitModes(SDL_VideoDevice *_this); +extern bool X11_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display); +extern bool X11_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode); +extern void X11_QuitModes(SDL_VideoDevice *_this); + +// Some utility functions for working with visuals +extern bool X11_GetVisualInfoFromVisual(Display *display, Visual *visual, XVisualInfo *vinfo); +extern SDL_PixelFormat X11_GetPixelFormatFromVisualInfo(Display *display, XVisualInfo *vinfo); +extern bool X11_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display, SDL_Rect *rect); +extern bool X11_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display, SDL_Rect *rect); + +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR +extern void X11_HandleXRandREvent(SDL_VideoDevice *_this, const XEvent *xevent); +#endif + +#endif // SDL_x11modes_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11mouse.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11mouse.c new file mode 100644 index 0000000..5c72dbf --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11mouse.c @@ -0,0 +1,552 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifdef SDL_VIDEO_DRIVER_X11 + +#include +#include "SDL_x11video.h" +#include "SDL_x11mouse.h" +#include "SDL_x11xinput2.h" +#include "../SDL_video_c.h" +#include "../../events/SDL_mouse_c.h" + +struct SDL_CursorData +{ + Cursor cursor; +}; + +// FIXME: Find a better place to put this... +static Cursor x11_empty_cursor = None; +static bool x11_cursor_visible = true; + +static SDL_Cursor *sys_cursors[SDL_HITTEST_RESIZE_LEFT + 1]; + +static Display *GetDisplay(void) +{ + return SDL_GetVideoDevice()->internal->display; +} + +static Cursor X11_CreateEmptyCursor(void) +{ + if (x11_empty_cursor == None) { + Display *display = GetDisplay(); + char data[1]; + XColor color; + Pixmap pixmap; + + SDL_zeroa(data); + color.red = color.green = color.blue = 0; + pixmap = X11_XCreateBitmapFromData(display, DefaultRootWindow(display), + data, 1, 1); + if (pixmap) { + x11_empty_cursor = X11_XCreatePixmapCursor(display, pixmap, pixmap, + &color, &color, 0, 0); + X11_XFreePixmap(display, pixmap); + } + } + return x11_empty_cursor; +} + +static void X11_DestroyEmptyCursor(void) +{ + if (x11_empty_cursor != None) { + X11_XFreeCursor(GetDisplay(), x11_empty_cursor); + x11_empty_cursor = None; + } +} + +static SDL_Cursor *X11_CreateCursorAndData(Cursor x11_cursor) +{ + SDL_Cursor *cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor)); + if (cursor) { + SDL_CursorData *data = (SDL_CursorData *)SDL_calloc(1, sizeof(*data)); + if (!data) { + SDL_free(cursor); + return NULL; + } + data->cursor = x11_cursor; + cursor->internal = data; + } + return cursor; +} + +#ifdef SDL_VIDEO_DRIVER_X11_XCURSOR +static Cursor X11_CreateXCursorCursor(SDL_Surface *surface, int hot_x, int hot_y) +{ + Display *display = GetDisplay(); + Cursor cursor = None; + XcursorImage *image; + + image = X11_XcursorImageCreate(surface->w, surface->h); + if (!image) { + SDL_OutOfMemory(); + return None; + } + image->xhot = hot_x; + image->yhot = hot_y; + image->delay = 0; + + SDL_assert(surface->format == SDL_PIXELFORMAT_ARGB8888); + SDL_assert(surface->pitch == surface->w * 4); + SDL_memcpy(image->pixels, surface->pixels, (size_t)surface->h * surface->pitch); + + cursor = X11_XcursorImageLoadCursor(display, image); + + X11_XcursorImageDestroy(image); + + return cursor; +} +#endif // SDL_VIDEO_DRIVER_X11_XCURSOR + +static Cursor X11_CreatePixmapCursor(SDL_Surface *surface, int hot_x, int hot_y) +{ + Display *display = GetDisplay(); + XColor fg, bg; + Cursor cursor = None; + Uint32 *ptr; + Uint8 *data_bits, *mask_bits; + Pixmap data_pixmap, mask_pixmap; + int x, y; + unsigned int rfg, gfg, bfg, rbg, gbg, bbg, fgBits, bgBits; + size_t width_bytes = ((surface->w + 7) & ~((size_t)7)) / 8; + + data_bits = SDL_calloc(1, surface->h * width_bytes); + if (!data_bits) { + return None; + } + + mask_bits = SDL_calloc(1, surface->h * width_bytes); + if (!mask_bits) { + SDL_free(data_bits); + return None; + } + + // Code below assumes ARGB pixel format + SDL_assert(surface->format == SDL_PIXELFORMAT_ARGB8888); + + rfg = gfg = bfg = rbg = gbg = bbg = fgBits = bgBits = 0; + for (y = 0; y < surface->h; ++y) { + ptr = (Uint32 *)((Uint8 *)surface->pixels + y * surface->pitch); + for (x = 0; x < surface->w; ++x) { + int alpha = (*ptr >> 24) & 0xff; + int red = (*ptr >> 16) & 0xff; + int green = (*ptr >> 8) & 0xff; + int blue = (*ptr >> 0) & 0xff; + if (alpha > 25) { + mask_bits[y * width_bytes + x / 8] |= (0x01 << (x % 8)); + + if ((red + green + blue) > 0x40) { + fgBits++; + rfg += red; + gfg += green; + bfg += blue; + data_bits[y * width_bytes + x / 8] |= (0x01 << (x % 8)); + } else { + bgBits++; + rbg += red; + gbg += green; + bbg += blue; + } + } + ++ptr; + } + } + + if (fgBits) { + fg.red = rfg * 257 / fgBits; + fg.green = gfg * 257 / fgBits; + fg.blue = bfg * 257 / fgBits; + } else { + fg.red = fg.green = fg.blue = 0; + } + + if (bgBits) { + bg.red = rbg * 257 / bgBits; + bg.green = gbg * 257 / bgBits; + bg.blue = bbg * 257 / bgBits; + } else { + bg.red = bg.green = bg.blue = 0; + } + + data_pixmap = X11_XCreateBitmapFromData(display, DefaultRootWindow(display), + (char *)data_bits, + surface->w, surface->h); + mask_pixmap = X11_XCreateBitmapFromData(display, DefaultRootWindow(display), + (char *)mask_bits, + surface->w, surface->h); + cursor = X11_XCreatePixmapCursor(display, data_pixmap, mask_pixmap, + &fg, &bg, hot_x, hot_y); + X11_XFreePixmap(display, data_pixmap); + X11_XFreePixmap(display, mask_pixmap); + SDL_free(data_bits); + SDL_free(mask_bits); + + return cursor; +} + +static SDL_Cursor *X11_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y) +{ + Cursor x11_cursor = None; + +#ifdef SDL_VIDEO_DRIVER_X11_XCURSOR + if (SDL_X11_HAVE_XCURSOR) { + x11_cursor = X11_CreateXCursorCursor(surface, hot_x, hot_y); + } +#endif + if (x11_cursor == None) { + x11_cursor = X11_CreatePixmapCursor(surface, hot_x, hot_y); + } + return X11_CreateCursorAndData(x11_cursor); +} + +static unsigned int GetLegacySystemCursorShape(SDL_SystemCursor id) +{ + switch (id) { + // X Font Cursors reference: + // http://tronche.com/gui/x/xlib/appendix/b/ + case SDL_SYSTEM_CURSOR_DEFAULT: return XC_left_ptr; + case SDL_SYSTEM_CURSOR_TEXT: return XC_xterm; + case SDL_SYSTEM_CURSOR_WAIT: return XC_watch; + case SDL_SYSTEM_CURSOR_CROSSHAIR: return XC_tcross; + case SDL_SYSTEM_CURSOR_PROGRESS: return XC_watch; + case SDL_SYSTEM_CURSOR_NWSE_RESIZE: return XC_top_left_corner; + case SDL_SYSTEM_CURSOR_NESW_RESIZE: return XC_top_right_corner; + case SDL_SYSTEM_CURSOR_EW_RESIZE: return XC_sb_h_double_arrow; + case SDL_SYSTEM_CURSOR_NS_RESIZE: return XC_sb_v_double_arrow; + case SDL_SYSTEM_CURSOR_MOVE: return XC_fleur; + case SDL_SYSTEM_CURSOR_NOT_ALLOWED: return XC_pirate; + case SDL_SYSTEM_CURSOR_POINTER: return XC_hand2; + case SDL_SYSTEM_CURSOR_NW_RESIZE: return XC_top_left_corner; + case SDL_SYSTEM_CURSOR_N_RESIZE: return XC_top_side; + case SDL_SYSTEM_CURSOR_NE_RESIZE: return XC_top_right_corner; + case SDL_SYSTEM_CURSOR_E_RESIZE: return XC_right_side; + case SDL_SYSTEM_CURSOR_SE_RESIZE: return XC_bottom_right_corner; + case SDL_SYSTEM_CURSOR_S_RESIZE: return XC_bottom_side; + case SDL_SYSTEM_CURSOR_SW_RESIZE: return XC_bottom_left_corner; + case SDL_SYSTEM_CURSOR_W_RESIZE: return XC_left_side; + case SDL_SYSTEM_CURSOR_COUNT: break; // so the compiler might notice if an enum value is missing here. + } + + SDL_assert(0); + return 0; +} + +static SDL_Cursor *X11_CreateSystemCursor(SDL_SystemCursor id) +{ + SDL_Cursor *cursor = NULL; + Display *dpy = GetDisplay(); + Cursor x11_cursor = None; + +#ifdef SDL_VIDEO_DRIVER_X11_XCURSOR + if (SDL_X11_HAVE_XCURSOR) { + x11_cursor = X11_XcursorLibraryLoadCursor(dpy, SDL_GetCSSCursorName(id, NULL)); + } +#endif + + if (x11_cursor == None) { + x11_cursor = X11_XCreateFontCursor(dpy, GetLegacySystemCursorShape(id)); + } + + if (x11_cursor != None) { + cursor = X11_CreateCursorAndData(x11_cursor); + } + + return cursor; +} + +static SDL_Cursor *X11_CreateDefaultCursor(void) +{ + SDL_SystemCursor id = SDL_GetDefaultSystemCursor(); + return X11_CreateSystemCursor(id); +} + +static void X11_FreeCursor(SDL_Cursor *cursor) +{ + Cursor x11_cursor = cursor->internal->cursor; + + if (x11_cursor != None) { + X11_XFreeCursor(GetDisplay(), x11_cursor); + } + SDL_free(cursor->internal); + SDL_free(cursor); +} + +static bool X11_ShowCursor(SDL_Cursor *cursor) +{ + Cursor x11_cursor = 0; + + if (cursor) { + x11_cursor = cursor->internal->cursor; + } else { + x11_cursor = X11_CreateEmptyCursor(); + } + + // FIXME: Is there a better way than this? + { + SDL_VideoDevice *video = SDL_GetVideoDevice(); + Display *display = GetDisplay(); + SDL_Window *window; + + x11_cursor_visible = !!cursor; + + for (window = video->windows; window; window = window->next) { + SDL_WindowData *data = window->internal; + if (data) { + if (x11_cursor != None) { + X11_XDefineCursor(display, data->xwindow, x11_cursor); + } else { + X11_XUndefineCursor(display, data->xwindow); + } + } + } + X11_XFlush(display); + } + return true; +} + +static void X11_WarpMouseInternal(Window xwindow, float x, float y) +{ + SDL_VideoData *videodata = SDL_GetVideoDevice()->internal; + Display *display = videodata->display; + bool warp_hack = false; + + // XWayland will only warp the cursor if it is hidden, so this workaround is required. + if (videodata->is_xwayland && x11_cursor_visible) { + warp_hack = true; + } + + if (warp_hack) { + X11_ShowCursor(NULL); + } +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + int deviceid = 0; + if (X11_Xinput2IsInitialized()) { + /* It seems XIWarpPointer() doesn't work correctly on multi-head setups: + * https://developer.blender.org/rB165caafb99c6846e53d11c4e966990aaffc06cea + */ + if (ScreenCount(display) == 1) { + X11_XIGetClientPointer(display, None, &deviceid); + } + } + if (deviceid != 0) { + SDL_assert(SDL_X11_HAVE_XINPUT2); + X11_XIWarpPointer(display, deviceid, None, xwindow, 0.0, 0.0, 0, 0, x, y); + } else +#endif + { + X11_XWarpPointer(display, None, xwindow, 0, 0, 0, 0, (int)x, (int)y); + } + + if (warp_hack) { + X11_ShowCursor(SDL_GetCursor()); + } + X11_XSync(display, False); + videodata->global_mouse_changed = true; +} + +static bool X11_WarpMouse(SDL_Window *window, float x, float y) +{ + SDL_WindowData *data = window->internal; + +#ifdef SDL_VIDEO_DRIVER_X11_XFIXES + // If we have no barrier, we need to warp + if (data->pointer_barrier_active == false) { + X11_WarpMouseInternal(data->xwindow, x, y); + } +#else + X11_WarpMouseInternal(data->xwindow, x, y); +#endif + return true; +} + +static bool X11_WarpMouseGlobal(float x, float y) +{ + X11_WarpMouseInternal(DefaultRootWindow(GetDisplay()), x, y); + return true; +} + +static bool X11_SetRelativeMouseMode(bool enabled) +{ + if (!X11_Xinput2IsInitialized()) { + return SDL_Unsupported(); + } + return true; +} + +static bool X11_CaptureMouse(SDL_Window *window) +{ + Display *display = GetDisplay(); + SDL_Window *mouse_focus = SDL_GetMouseFocus(); + + if (window) { + SDL_WindowData *data = window->internal; + + /* If XInput2 is handling the pointer input, non-confinement grabs will always fail with 'AlreadyGrabbed', + * since the pointer is being grabbed by XInput2. + */ + if (!data->xinput2_mouse_enabled || data->mouse_grabbed) { + const unsigned int mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask; + Window confined = (data->mouse_grabbed ? data->xwindow : None); + const int rc = X11_XGrabPointer(display, data->xwindow, False, + mask, GrabModeAsync, GrabModeAsync, + confined, None, CurrentTime); + if (rc != GrabSuccess) { + return SDL_SetError("X server refused mouse capture"); + } + + if (data->mouse_grabbed) { + // XGrabPointer can warp the cursor when confining, so update the coordinates. + data->videodata->global_mouse_changed = true; + } + } + } else if (mouse_focus) { + SDL_UpdateWindowGrab(mouse_focus); + } else { + X11_XUngrabPointer(display, CurrentTime); + } + + X11_XSync(display, False); + + return true; +} + +static SDL_MouseButtonFlags X11_GetGlobalMouseState(float *x, float *y) +{ + SDL_VideoData *videodata = SDL_GetVideoDevice()->internal; + SDL_DisplayID *displays; + Display *display = GetDisplay(); + int i; + + // !!! FIXME: should we XSync() here first? + + if (!X11_Xinput2IsInitialized()) { + videodata->global_mouse_changed = true; + } + + // check if we have this cached since XInput last saw the mouse move. + // !!! FIXME: can we just calculate this from XInput's events? + if (videodata->global_mouse_changed) { + displays = SDL_GetDisplays(NULL); + if (displays) { + for (i = 0; displays[i]; ++i) { + SDL_DisplayData *data = SDL_GetDisplayDriverData(displays[i]); + if (data) { + Window root, child; + int rootx, rooty, winx, winy; + unsigned int mask; + if (X11_XQueryPointer(display, RootWindow(display, data->screen), &root, &child, &rootx, &rooty, &winx, &winy, &mask)) { + XWindowAttributes root_attrs; + SDL_MouseButtonFlags buttons = 0; + buttons |= (mask & Button1Mask) ? SDL_BUTTON_LMASK : 0; + buttons |= (mask & Button2Mask) ? SDL_BUTTON_MMASK : 0; + buttons |= (mask & Button3Mask) ? SDL_BUTTON_RMASK : 0; + // Use the SDL state for the extended buttons - it's better than nothing + buttons |= (SDL_GetMouseState(NULL, NULL) & (SDL_BUTTON_X1MASK | SDL_BUTTON_X2MASK)); + /* SDL_DisplayData->x,y point to screen origin, and adding them to mouse coordinates relative to root window doesn't do the right thing + * (observed on dual monitor setup with primary display being the rightmost one - mouse was offset to the right). + * + * Adding root position to root-relative coordinates seems to be a better way to get absolute position. */ + X11_XGetWindowAttributes(display, root, &root_attrs); + videodata->global_mouse_position.x = root_attrs.x + rootx; + videodata->global_mouse_position.y = root_attrs.y + rooty; + videodata->global_mouse_buttons = buttons; + videodata->global_mouse_changed = false; + break; + } + } + } + SDL_free(displays); + } + } + + SDL_assert(!videodata->global_mouse_changed); // The pointer wasn't on any X11 screen?! + + *x = (float)videodata->global_mouse_position.x; + *y = (float)videodata->global_mouse_position.y; + return videodata->global_mouse_buttons; +} + +void X11_InitMouse(SDL_VideoDevice *_this) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + + mouse->CreateCursor = X11_CreateCursor; + mouse->CreateSystemCursor = X11_CreateSystemCursor; + mouse->ShowCursor = X11_ShowCursor; + mouse->FreeCursor = X11_FreeCursor; + mouse->WarpMouse = X11_WarpMouse; + mouse->WarpMouseGlobal = X11_WarpMouseGlobal; + mouse->SetRelativeMouseMode = X11_SetRelativeMouseMode; + mouse->CaptureMouse = X11_CaptureMouse; + mouse->GetGlobalMouseState = X11_GetGlobalMouseState; + + SDL_HitTestResult r = SDL_HITTEST_NORMAL; + while (r <= SDL_HITTEST_RESIZE_LEFT) { + switch (r) { + case SDL_HITTEST_NORMAL: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); break; + case SDL_HITTEST_DRAGGABLE: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); break; + case SDL_HITTEST_RESIZE_TOPLEFT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_NW_RESIZE); break; + case SDL_HITTEST_RESIZE_TOP: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_N_RESIZE); break; + case SDL_HITTEST_RESIZE_TOPRIGHT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_NE_RESIZE); break; + case SDL_HITTEST_RESIZE_RIGHT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_E_RESIZE); break; + case SDL_HITTEST_RESIZE_BOTTOMRIGHT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_SE_RESIZE); break; + case SDL_HITTEST_RESIZE_BOTTOM: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_S_RESIZE); break; + case SDL_HITTEST_RESIZE_BOTTOMLEFT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_SW_RESIZE); break; + case SDL_HITTEST_RESIZE_LEFT: sys_cursors[r] = X11_CreateSystemCursor(SDL_SYSTEM_CURSOR_W_RESIZE); break; + } + r++; + } + + SDL_SetDefaultCursor(X11_CreateDefaultCursor()); +} + +void X11_QuitMouse(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + SDL_XInput2DeviceInfo *i; + SDL_XInput2DeviceInfo *next; + int j; + + for (j = 0; j < SDL_arraysize(sys_cursors); j++) { + X11_FreeCursor(sys_cursors[j]); + sys_cursors[j] = NULL; + } + + for (i = data->mouse_device_info; i; i = next) { + next = i->next; + SDL_free(i); + } + data->mouse_device_info = NULL; + + X11_DestroyEmptyCursor(); +} + +void X11_SetHitTestCursor(SDL_HitTestResult rc) +{ + if (rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) { + SDL_SetCursor(NULL); + } else { + X11_ShowCursor(sys_cursors[rc]); + } +} + +#endif // SDL_VIDEO_DRIVER_X11 diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11mouse.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11mouse.h new file mode 100644 index 0000000..2a2973c --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11mouse.h @@ -0,0 +1,40 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifndef SDL_x11mouse_h_ +#define SDL_x11mouse_h_ + +typedef struct SDL_XInput2DeviceInfo +{ + int device_id; + bool relative[2]; + double minval[2]; + double maxval[2]; + double prev_coords[2]; + struct SDL_XInput2DeviceInfo *next; +} SDL_XInput2DeviceInfo; + +extern void X11_InitMouse(SDL_VideoDevice *_this); +extern void X11_QuitMouse(SDL_VideoDevice *_this); +extern void X11_SetHitTestCursor(SDL_HitTestResult rc); + +#endif // SDL_x11mouse_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11opengl.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11opengl.c new file mode 100644 index 0000000..d46409f --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11opengl.c @@ -0,0 +1,1116 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 2021 NVIDIA Corporation + + 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_VIDEO_DRIVER_X11 + +#include "SDL_x11video.h" +#include "SDL_x11xsync.h" + +// GLX implementation of SDL OpenGL support + +#ifdef SDL_VIDEO_OPENGL_GLX +#include "SDL_x11opengles.h" + +#if defined(SDL_PLATFORM_IRIX) || defined(SDL_PLATFORM_NETBSD) || defined(SDL_PLATFORM_OPENBSD) +/* + * IRIX doesn't have a GL library versioning system. + * NetBSD and OpenBSD have different GL library versions depending on how + * the library was installed. + */ +#define DEFAULT_OPENGL "libGL.so" +#elif defined(SDL_PLATFORM_MACOS) +#define DEFAULT_OPENGL "/opt/X11/lib/libGL.1.dylib" +#else +#define DEFAULT_OPENGL "libGL.so.1" +#endif + +#ifndef GLX_NONE_EXT +#define GLX_NONE_EXT 0x8000 +#endif + +#ifndef GLX_ARB_multisample +#define GLX_ARB_multisample +#define GLX_SAMPLE_BUFFERS_ARB 100000 +#define GLX_SAMPLES_ARB 100001 +#endif + +#ifndef GLX_EXT_visual_rating +#define GLX_EXT_visual_rating +#define GLX_VISUAL_CAVEAT_EXT 0x20 +#define GLX_NONE_EXT 0x8000 +#define GLX_SLOW_VISUAL_EXT 0x8001 +#define GLX_NON_CONFORMANT_VISUAL_EXT 0x800D +#endif + +#ifndef GLX_EXT_visual_info +#define GLX_EXT_visual_info +#define GLX_X_VISUAL_TYPE_EXT 0x22 +#define GLX_DIRECT_COLOR_EXT 0x8003 +#endif + +#ifndef GLX_ARB_create_context +#define GLX_ARB_create_context +#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define GLX_CONTEXT_FLAGS_ARB 0x2094 +#define GLX_CONTEXT_DEBUG_BIT_ARB 0x0001 +#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002 + +// Typedef for the GL 3.0 context creation function +typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC)(Display *dpy, + GLXFBConfig config, + GLXContext + share_context, + Bool direct, + const int + *attrib_list); +#endif + +#ifndef GLX_ARB_create_context_profile +#define GLX_ARB_create_context_profile +#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 +#endif + +#ifndef GLX_ARB_create_context_robustness +#define GLX_ARB_create_context_robustness +#define GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004 +#define GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 +#define GLX_NO_RESET_NOTIFICATION_ARB 0x8261 +#define GLX_LOSE_CONTEXT_ON_RESET_ARB 0x8252 +#endif + +#ifndef GLX_EXT_create_context_es2_profile +#define GLX_EXT_create_context_es2_profile +#ifndef GLX_CONTEXT_ES2_PROFILE_BIT_EXT +#define GLX_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000002 +#endif +#endif + +#ifndef GLX_ARB_framebuffer_sRGB +#define GLX_ARB_framebuffer_sRGB +#ifndef GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB +#define GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20B2 +#endif +#endif + +#ifndef GLX_ARB_fbconfig_float +#define GLX_ARB_fbconfig_float +#ifndef GLX_RGBA_FLOAT_TYPE_ARB +#define GLX_RGBA_FLOAT_TYPE_ARB 0x20B9 +#endif +#ifndef GLX_RGBA_FLOAT_BIT_ARB +#define GLX_RGBA_FLOAT_BIT_ARB 0x00000004 +#endif +#endif + +#ifndef GLX_ARB_create_context_no_error +#define GLX_ARB_create_context_no_error +#ifndef GLX_CONTEXT_OPENGL_NO_ERROR_ARB +#define GLX_CONTEXT_OPENGL_NO_ERROR_ARB 0x31B3 +#endif +#endif + +#ifndef GLX_EXT_swap_control +#define GLX_SWAP_INTERVAL_EXT 0x20F1 +#define GLX_MAX_SWAP_INTERVAL_EXT 0x20F2 +#endif + +#ifndef GLX_EXT_swap_control_tear +#define GLX_LATE_SWAPS_TEAR_EXT 0x20F3 +#endif + +#ifndef GLX_ARB_context_flush_control +#define GLX_ARB_context_flush_control +#define GLX_CONTEXT_RELEASE_BEHAVIOR_ARB 0x2097 +#define GLX_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB 0x0000 +#define GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 0x2098 +#endif + +#define OPENGL_REQUIRES_DLOPEN +#if defined(OPENGL_REQUIRES_DLOPEN) && defined(HAVE_DLOPEN) +#include +#define GL_LoadObject(X) dlopen(X, (RTLD_NOW | RTLD_GLOBAL)) +#define GL_LoadFunction dlsym +#define GL_UnloadObject dlclose +#else +#define GL_LoadObject SDL_LoadObject +#define GL_LoadFunction SDL_LoadFunction +#define GL_UnloadObject SDL_UnloadObject +#endif + +static void X11_GL_InitExtensions(SDL_VideoDevice *_this); + +bool X11_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path) +{ + Display *display; + SDL_SharedObject *handle; + + if (_this->gl_data) { + return SDL_SetError("OpenGL context already created"); + } + + // Load the OpenGL library + if (path == NULL) { + path = SDL_GetHint(SDL_HINT_OPENGL_LIBRARY); + } + if (path == NULL) { + path = DEFAULT_OPENGL; + } + _this->gl_config.dll_handle = GL_LoadObject(path); + if (!_this->gl_config.dll_handle) { +#if defined(OPENGL_REQUIRES_DLOPEN) && defined(HAVE_DLOPEN) + SDL_SetError("Failed loading %s: %s", path, dlerror()); +#endif + return false; + } + SDL_strlcpy(_this->gl_config.driver_path, path, + SDL_arraysize(_this->gl_config.driver_path)); + + // Allocate OpenGL memory + _this->gl_data = + (struct SDL_GLDriverData *)SDL_calloc(1, + sizeof(struct + SDL_GLDriverData)); + if (!_this->gl_data) { + return false; + } + + // Load function pointers + handle = _this->gl_config.dll_handle; + _this->gl_data->glXQueryExtension = + (Bool(*)(Display *, int *, int *)) + GL_LoadFunction(handle, "glXQueryExtension"); + _this->gl_data->glXGetProcAddress = + (__GLXextFuncPtr (*)(const GLubyte *)) + GL_LoadFunction(handle, "glXGetProcAddressARB"); + _this->gl_data->glXChooseVisual = + (XVisualInfo * (*)(Display *, int, int *)) + X11_GL_GetProcAddress(_this, "glXChooseVisual"); + _this->gl_data->glXCreateContext = + (GLXContext(*)(Display *, XVisualInfo *, GLXContext, int)) + X11_GL_GetProcAddress(_this, "glXCreateContext"); + _this->gl_data->glXDestroyContext = + (void (*)(Display *, GLXContext)) + X11_GL_GetProcAddress(_this, "glXDestroyContext"); + _this->gl_data->glXMakeCurrent = + (int (*)(Display *, GLXDrawable, GLXContext)) + X11_GL_GetProcAddress(_this, "glXMakeCurrent"); + _this->gl_data->glXSwapBuffers = + (void (*)(Display *, GLXDrawable)) + X11_GL_GetProcAddress(_this, "glXSwapBuffers"); + _this->gl_data->glXQueryDrawable = + (void (*)(Display *, GLXDrawable, int, unsigned int *)) + X11_GL_GetProcAddress(_this, "glXQueryDrawable"); + + if (!_this->gl_data->glXQueryExtension || + !_this->gl_data->glXChooseVisual || + !_this->gl_data->glXCreateContext || + !_this->gl_data->glXDestroyContext || + !_this->gl_data->glXMakeCurrent || + !_this->gl_data->glXSwapBuffers) { + return SDL_SetError("Could not retrieve OpenGL functions"); + } + + display = _this->internal->display; + if (!_this->gl_data->glXQueryExtension(display, &_this->gl_data->errorBase, &_this->gl_data->eventBase)) { + return SDL_SetError("GLX is not supported"); + } + + _this->gl_data->swap_interval_tear_behavior = SDL_SWAPINTERVALTEAR_UNTESTED; + + // Initialize extensions + /* See lengthy comment about the inc/dec in + ../windows/SDL_windowsopengl.c. */ + ++_this->gl_config.driver_loaded; + X11_GL_InitExtensions(_this); + --_this->gl_config.driver_loaded; + + /* If we need a GL ES context and there's no + * GLX_EXT_create_context_es2_profile extension, switch over to X11_GLES functions + */ + if (((_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) || + SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false)) && + X11_GL_UseEGL(_this)) { +#ifdef SDL_VIDEO_OPENGL_EGL + X11_GL_UnloadLibrary(_this); + _this->GL_LoadLibrary = X11_GLES_LoadLibrary; + _this->GL_GetProcAddress = X11_GLES_GetProcAddress; + _this->GL_UnloadLibrary = X11_GLES_UnloadLibrary; + _this->GL_CreateContext = X11_GLES_CreateContext; + _this->GL_MakeCurrent = X11_GLES_MakeCurrent; + _this->GL_SetSwapInterval = X11_GLES_SetSwapInterval; + _this->GL_GetSwapInterval = X11_GLES_GetSwapInterval; + _this->GL_SwapWindow = X11_GLES_SwapWindow; + _this->GL_DestroyContext = X11_GLES_DestroyContext; + return X11_GLES_LoadLibrary(_this, NULL); +#else + return SDL_SetError("SDL not configured with EGL support"); +#endif + } + + return true; +} + +SDL_FunctionPointer X11_GL_GetProcAddress(SDL_VideoDevice *_this, const char *proc) +{ + if (_this->gl_data->glXGetProcAddress) { + return _this->gl_data->glXGetProcAddress((const GLubyte *)proc); + } + return GL_LoadFunction(_this->gl_config.dll_handle, proc); +} + +void X11_GL_UnloadLibrary(SDL_VideoDevice *_this) +{ + /* Don't actually unload the library, since it may have registered + * X11 shutdown hooks, per the notes at: + * http://dri.sourceforge.net/doc/DRIuserguide.html + */ +#if 0 + GL_UnloadObject(_this->gl_config.dll_handle); + _this->gl_config.dll_handle = NULL; +#endif + + // Free OpenGL memory + SDL_free(_this->gl_data); + _this->gl_data = NULL; +} + +static bool HasExtension(const char *extension, const char *extensions) +{ + const char *start; + const char *where, *terminator; + + if (!extensions) { + return false; + } + + // Extension names should not have spaces. + where = SDL_strchr(extension, ' '); + if (where || *extension == '\0') { + return false; + } + + /* It takes a bit of care to be fool-proof about parsing the + * OpenGL extensions string. Don't be fooled by sub-strings, + * etc. */ + + start = extensions; + + for (;;) { + where = SDL_strstr(start, extension); + if (!where) { + break; + } + + terminator = where + SDL_strlen(extension); + if (where == start || *(where - 1) == ' ') { + if (*terminator == ' ' || *terminator == '\0') { + return true; + } + } + + start = terminator; + } + return false; +} + +static void X11_GL_InitExtensions(SDL_VideoDevice *_this) +{ + Display *display = _this->internal->display; + const int screen = DefaultScreen(display); + XVisualInfo *vinfo = NULL; + Window w = 0; + GLXContext prev_ctx = 0; + GLXDrawable prev_drawable = 0; + GLXContext context = 0; + const char *(*glXQueryExtensionsStringFunc)(Display *, int); + const char *extensions; + + vinfo = X11_GL_GetVisual(_this, display, screen, false); + if (vinfo) { + GLXContext (*glXGetCurrentContextFunc)(void) = + (GLXContext(*)(void)) + X11_GL_GetProcAddress(_this, "glXGetCurrentContext"); + + GLXDrawable (*glXGetCurrentDrawableFunc)(void) = + (GLXDrawable(*)(void)) + X11_GL_GetProcAddress(_this, "glXGetCurrentDrawable"); + + if (glXGetCurrentContextFunc && glXGetCurrentDrawableFunc) { + XSetWindowAttributes xattr; + prev_ctx = glXGetCurrentContextFunc(); + prev_drawable = glXGetCurrentDrawableFunc(); + + xattr.background_pixel = 0; + xattr.border_pixel = 0; + xattr.colormap = + X11_XCreateColormap(display, RootWindow(display, screen), + vinfo->visual, AllocNone); + w = X11_XCreateWindow(display, RootWindow(display, screen), 0, 0, + 32, 32, 0, vinfo->depth, InputOutput, vinfo->visual, + (CWBackPixel | CWBorderPixel | CWColormap), &xattr); + + context = _this->gl_data->glXCreateContext(display, vinfo, + NULL, True); + if (context) { + _this->gl_data->glXMakeCurrent(display, w, context); + } + } + + X11_XFree(vinfo); + } + + glXQueryExtensionsStringFunc = + (const char *(*)(Display *, int))X11_GL_GetProcAddress(_this, + "glXQueryExtensionsString"); + if (glXQueryExtensionsStringFunc) { + extensions = glXQueryExtensionsStringFunc(display, screen); + } else { + extensions = NULL; + } + + // Check for GLX_EXT_swap_control(_tear) + _this->gl_data->HAS_GLX_EXT_swap_control_tear = false; + if (HasExtension("GLX_EXT_swap_control", extensions)) { + _this->gl_data->glXSwapIntervalEXT = + (void (*)(Display *, GLXDrawable, int)) + X11_GL_GetProcAddress(_this, "glXSwapIntervalEXT"); + if (HasExtension("GLX_EXT_swap_control_tear", extensions)) { + _this->gl_data->HAS_GLX_EXT_swap_control_tear = true; + } + } + + // Check for GLX_MESA_swap_control + if (HasExtension("GLX_MESA_swap_control", extensions)) { + _this->gl_data->glXSwapIntervalMESA = + (int (*)(int))X11_GL_GetProcAddress(_this, "glXSwapIntervalMESA"); + _this->gl_data->glXGetSwapIntervalMESA = + (int (*)(void))X11_GL_GetProcAddress(_this, + "glXGetSwapIntervalMESA"); + } + + // Check for GLX_SGI_swap_control + if (HasExtension("GLX_SGI_swap_control", extensions)) { + _this->gl_data->glXSwapIntervalSGI = + (int (*)(int))X11_GL_GetProcAddress(_this, "glXSwapIntervalSGI"); + } + + // Check for GLX_ARB_create_context + if (HasExtension("GLX_ARB_create_context", extensions)) { + _this->gl_data->glXCreateContextAttribsARB = + (GLXContext(*)(Display *, GLXFBConfig, GLXContext, Bool, const int *)) + X11_GL_GetProcAddress(_this, "glXCreateContextAttribsARB"); + _this->gl_data->glXChooseFBConfig = + (GLXFBConfig * (*)(Display *, int, const int *, int *)) + X11_GL_GetProcAddress(_this, "glXChooseFBConfig"); + _this->gl_data->glXGetVisualFromFBConfig = + (XVisualInfo * (*)(Display *, GLXFBConfig)) + X11_GL_GetProcAddress(_this, "glXGetVisualFromFBConfig"); + } + + // Check for GLX_EXT_visual_rating + if (HasExtension("GLX_EXT_visual_rating", extensions)) { + _this->gl_data->HAS_GLX_EXT_visual_rating = true; + } + + // Check for GLX_EXT_visual_info + if (HasExtension("GLX_EXT_visual_info", extensions)) { + _this->gl_data->HAS_GLX_EXT_visual_info = true; + } + + // Check for GLX_EXT_create_context_es2_profile + if (HasExtension("GLX_EXT_create_context_es2_profile", extensions)) { + // this wants to call glGetString(), so it needs a context. + // !!! FIXME: it would be nice not to make a context here though! + if (context) { + SDL_GL_DeduceMaxSupportedESProfile( + &_this->gl_data->es_profile_max_supported_version.major, + &_this->gl_data->es_profile_max_supported_version.minor); + } + } + + // Check for GLX_ARB_context_flush_control + if (HasExtension("GLX_ARB_context_flush_control", extensions)) { + _this->gl_data->HAS_GLX_ARB_context_flush_control = true; + } + + // Check for GLX_ARB_create_context_robustness + if (HasExtension("GLX_ARB_create_context_robustness", extensions)) { + _this->gl_data->HAS_GLX_ARB_create_context_robustness = true; + } + + // Check for GLX_ARB_create_context_no_error + if (HasExtension("GLX_ARB_create_context_no_error", extensions)) { + _this->gl_data->HAS_GLX_ARB_create_context_no_error = true; + } + + if (context) { + _this->gl_data->glXMakeCurrent(display, None, NULL); + _this->gl_data->glXDestroyContext(display, context); + if (prev_ctx && prev_drawable) { + _this->gl_data->glXMakeCurrent(display, prev_drawable, prev_ctx); + } + } + + if (w) { + X11_XDestroyWindow(display, w); + } + X11_PumpEvents(_this); +} + +/* glXChooseVisual and glXChooseFBConfig have some small differences in + * the attribute encoding, it can be chosen with the for_FBConfig parameter. + * Some targets fail if you use GLX_X_VISUAL_TYPE_EXT/GLX_DIRECT_COLOR_EXT, + * so it gets specified last if used and is pointed to by *_pvistypeattr. + * In case of failure, if that pointer is not NULL, set that pointer to None + * and try again. + */ +static int X11_GL_GetAttributes(SDL_VideoDevice *_this, Display *display, int screen, int *attribs, int size, Bool for_FBConfig, int **_pvistypeattr, bool transparent) +{ + int i = 0; + const int MAX_ATTRIBUTES = 64; + int *pvistypeattr = NULL; + + // assert buffer is large enough to hold all SDL attributes. + SDL_assert(size >= MAX_ATTRIBUTES); + + // Setup our GLX attributes according to the gl_config. + if (for_FBConfig) { + attribs[i++] = GLX_RENDER_TYPE; + if (_this->gl_config.floatbuffers) { + attribs[i++] = GLX_RGBA_FLOAT_BIT_ARB; + } else { + attribs[i++] = GLX_RGBA_BIT; + } + } else { + attribs[i++] = GLX_RGBA; + } + attribs[i++] = GLX_RED_SIZE; + attribs[i++] = _this->gl_config.red_size; + attribs[i++] = GLX_GREEN_SIZE; + attribs[i++] = _this->gl_config.green_size; + attribs[i++] = GLX_BLUE_SIZE; + attribs[i++] = _this->gl_config.blue_size; + + if (_this->gl_config.alpha_size) { + attribs[i++] = GLX_ALPHA_SIZE; + attribs[i++] = _this->gl_config.alpha_size; + } + + if (_this->gl_config.double_buffer) { + attribs[i++] = GLX_DOUBLEBUFFER; + if (for_FBConfig) { + attribs[i++] = True; + } + } + + attribs[i++] = GLX_DEPTH_SIZE; + attribs[i++] = _this->gl_config.depth_size; + + if (_this->gl_config.stencil_size) { + attribs[i++] = GLX_STENCIL_SIZE; + attribs[i++] = _this->gl_config.stencil_size; + } + + if (_this->gl_config.accum_red_size) { + attribs[i++] = GLX_ACCUM_RED_SIZE; + attribs[i++] = _this->gl_config.accum_red_size; + } + + if (_this->gl_config.accum_green_size) { + attribs[i++] = GLX_ACCUM_GREEN_SIZE; + attribs[i++] = _this->gl_config.accum_green_size; + } + + if (_this->gl_config.accum_blue_size) { + attribs[i++] = GLX_ACCUM_BLUE_SIZE; + attribs[i++] = _this->gl_config.accum_blue_size; + } + + if (_this->gl_config.accum_alpha_size) { + attribs[i++] = GLX_ACCUM_ALPHA_SIZE; + attribs[i++] = _this->gl_config.accum_alpha_size; + } + + if (_this->gl_config.stereo) { + attribs[i++] = GLX_STEREO; + if (for_FBConfig) { + attribs[i++] = True; + } + } + + if (_this->gl_config.multisamplebuffers) { + attribs[i++] = GLX_SAMPLE_BUFFERS_ARB; + attribs[i++] = _this->gl_config.multisamplebuffers; + } + + if (_this->gl_config.multisamplesamples) { + attribs[i++] = GLX_SAMPLES_ARB; + attribs[i++] = _this->gl_config.multisamplesamples; + } + + if (_this->gl_config.floatbuffers) { + attribs[i++] = GLX_RENDER_TYPE; + attribs[i++] = GLX_RGBA_FLOAT_TYPE_ARB; + } + + if (_this->gl_config.framebuffer_srgb_capable) { + attribs[i++] = GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB; + attribs[i++] = True; // always needed, for_FBConfig or not! + } + + if (_this->gl_config.accelerated >= 0 && + _this->gl_data->HAS_GLX_EXT_visual_rating) { + attribs[i++] = GLX_VISUAL_CAVEAT_EXT; + attribs[i++] = _this->gl_config.accelerated ? GLX_NONE_EXT : GLX_SLOW_VISUAL_EXT; + } + + // Un-wanted when we request a transparent buffer + if (!transparent) { + /* If we're supposed to use DirectColor visuals, and we've got the + EXT_visual_info extension, then add GLX_X_VISUAL_TYPE_EXT. */ + if (X11_UseDirectColorVisuals() && _this->gl_data->HAS_GLX_EXT_visual_info) { + pvistypeattr = &attribs[i]; + attribs[i++] = GLX_X_VISUAL_TYPE_EXT; + attribs[i++] = GLX_DIRECT_COLOR_EXT; + } + } + + attribs[i++] = None; + + SDL_assert(i <= MAX_ATTRIBUTES); + + if (_pvistypeattr) { + *_pvistypeattr = pvistypeattr; + } + + return i; +} + +//get the first transparent Visual +static XVisualInfo* X11_GL_GetTransparentVisualInfo(Display *display, int screen) +{ + XVisualInfo* visualinfo = NULL; + XVisualInfo vi_in; + int out_count = 0; + + vi_in.screen = screen; + visualinfo = X11_XGetVisualInfo(display, VisualScreenMask, &vi_in, &out_count); + if (visualinfo != NULL) { + int i = 0; + for (i = 0; i < out_count; i++) { + XVisualInfo* v = &visualinfo[i]; + Uint32 format = X11_GetPixelFormatFromVisualInfo(display, v); + if (SDL_ISPIXELFORMAT_ALPHA(format)) { + vi_in.screen = screen; + vi_in.visualid = v->visualid; + X11_XFree(visualinfo); + visualinfo = X11_XGetVisualInfo(display, VisualScreenMask | VisualIDMask, &vi_in, &out_count); + break; + } + } + } + return visualinfo; +} + +XVisualInfo *X11_GL_GetVisual(SDL_VideoDevice *_this, Display *display, int screen, bool transparent) +{ + // 64 seems nice. + int attribs[64]; + XVisualInfo *vinfo = NULL; + int *pvistypeattr = NULL; + + if (!_this->gl_data) { + // The OpenGL library wasn't loaded, SDL_GetError() should have info + return NULL; + } + + if (_this->gl_data->glXChooseFBConfig && + _this->gl_data->glXGetVisualFromFBConfig) { + GLXFBConfig *framebuffer_config = NULL; + int fbcount = 0; + + X11_GL_GetAttributes(_this, display, screen, attribs, 64, true, &pvistypeattr, transparent); + framebuffer_config = _this->gl_data->glXChooseFBConfig(display, screen, attribs, &fbcount); + if (!framebuffer_config && (pvistypeattr != NULL)) { + *pvistypeattr = None; + framebuffer_config = _this->gl_data->glXChooseFBConfig(display, screen, attribs, &fbcount); + } + + if (transparent) { + // Return the first transparent Visual + int i; + for (i = 0; i < fbcount; i++) { + Uint32 format; + vinfo = _this->gl_data->glXGetVisualFromFBConfig(display, framebuffer_config[i]); + format = X11_GetPixelFormatFromVisualInfo(display, vinfo); + if (SDL_ISPIXELFORMAT_ALPHA(format)) { // found! + X11_XFree(framebuffer_config); + framebuffer_config = NULL; + break; + } + X11_XFree(vinfo); + vinfo = NULL; + } + } + + if (framebuffer_config) { + vinfo = _this->gl_data->glXGetVisualFromFBConfig(display, framebuffer_config[0]); + } + + X11_XFree(framebuffer_config); + } + + if (!vinfo) { + X11_GL_GetAttributes(_this, display, screen, attribs, 64, false, &pvistypeattr, transparent); + vinfo = _this->gl_data->glXChooseVisual(display, screen, attribs); + + if (!vinfo && (pvistypeattr != NULL)) { + *pvistypeattr = None; + vinfo = _this->gl_data->glXChooseVisual(display, screen, attribs); + } + } + + if (transparent && vinfo) { + Uint32 format = X11_GetPixelFormatFromVisualInfo(display, vinfo); + if (!SDL_ISPIXELFORMAT_ALPHA(format)) { + // not transparent! + XVisualInfo* visualinfo = X11_GL_GetTransparentVisualInfo(display, screen); + if (visualinfo != NULL) { + X11_XFree(vinfo); + vinfo = visualinfo; + } + } + } + + if (!vinfo) { + SDL_SetError("Couldn't find matching GLX visual"); + } + return vinfo; +} + +static int (*handler)(Display *, XErrorEvent *) = NULL; +static const char *errorHandlerOperation = NULL; +static int errorBase = 0; +static int errorCode = 0; +static int X11_GL_ErrorHandler(Display *d, XErrorEvent *e) +{ + char *x11_error = NULL; + char x11_error_locale[256]; + + errorCode = e->error_code; + if (X11_XGetErrorText(d, errorCode, x11_error_locale, sizeof(x11_error_locale)) == Success) { + x11_error = SDL_iconv_string("UTF-8", "", x11_error_locale, SDL_strlen(x11_error_locale) + 1); + } + + if (x11_error) { + SDL_SetError("Could not %s: %s", errorHandlerOperation, x11_error); + SDL_free(x11_error); + } else { + SDL_SetError("Could not %s: %i (Base %i)", errorHandlerOperation, errorCode, errorBase); + } + + return 0; +} + +bool X11_GL_UseEGL(SDL_VideoDevice *_this) +{ + SDL_assert(_this->gl_data != NULL); + if (SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false)) { + // use of EGL has been requested, even for desktop GL + return true; + } + + SDL_assert(_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES); + return (SDL_GetHintBoolean(SDL_HINT_OPENGL_ES_DRIVER, false) || _this->gl_config.major_version == 1 // No GLX extension for OpenGL ES 1.x profiles. + || _this->gl_config.major_version > _this->gl_data->es_profile_max_supported_version.major || (_this->gl_config.major_version == _this->gl_data->es_profile_max_supported_version.major && _this->gl_config.minor_version > _this->gl_data->es_profile_max_supported_version.minor)); +} + +SDL_GLContext X11_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + int screen = SDL_GetDisplayDriverDataForWindow(window)->screen; + XWindowAttributes xattr; + XVisualInfo v, *vinfo; + int n; + SDL_GLContext context = NULL; + GLXContext share_context; + const int transparent = (window->flags & SDL_WINDOW_TRANSPARENT) ? true : false; + + if (_this->gl_config.share_with_current_context) { + share_context = (GLXContext)SDL_GL_GetCurrentContext(); + } else { + share_context = NULL; + } + + // We do this to create a clean separation between X and GLX errors. + X11_XSync(display, False); + errorHandlerOperation = "create GL context"; + errorBase = _this->gl_data->errorBase; + errorCode = Success; + handler = X11_XSetErrorHandler(X11_GL_ErrorHandler); + X11_XGetWindowAttributes(display, data->xwindow, &xattr); + v.screen = screen; + v.visualid = X11_XVisualIDFromVisual(xattr.visual); + vinfo = X11_XGetVisualInfo(display, VisualScreenMask | VisualIDMask, &v, &n); + if (vinfo) { + if (_this->gl_config.major_version < 3 && + _this->gl_config.profile_mask == 0 && + _this->gl_config.flags == 0 && !transparent) { + // Create legacy context + context = + (SDL_GLContext)_this->gl_data->glXCreateContext(display, vinfo, share_context, True); + } else { + // max 14 attributes plus terminator + int attribs[15] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, + _this->gl_config.major_version, + GLX_CONTEXT_MINOR_VERSION_ARB, + _this->gl_config.minor_version, + 0 + }; + int iattr = 4; + + // SDL profile bits match GLX profile bits + if (_this->gl_config.profile_mask != 0) { + attribs[iattr++] = GLX_CONTEXT_PROFILE_MASK_ARB; + attribs[iattr++] = _this->gl_config.profile_mask; + } + + // SDL flags match GLX flags + if (_this->gl_config.flags != 0) { + attribs[iattr++] = GLX_CONTEXT_FLAGS_ARB; + attribs[iattr++] = _this->gl_config.flags; + } + + // only set if glx extension is available and not the default setting + if ((_this->gl_data->HAS_GLX_ARB_context_flush_control) && (_this->gl_config.release_behavior == 0)) { + attribs[iattr++] = GLX_CONTEXT_RELEASE_BEHAVIOR_ARB; + attribs[iattr++] = + _this->gl_config.release_behavior ? GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB : GLX_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB; + } + + // only set if glx extension is available and not the default setting + if ((_this->gl_data->HAS_GLX_ARB_create_context_robustness) && (_this->gl_config.reset_notification != 0)) { + attribs[iattr++] = GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB; + attribs[iattr++] = + _this->gl_config.reset_notification ? GLX_LOSE_CONTEXT_ON_RESET_ARB : GLX_NO_RESET_NOTIFICATION_ARB; + } + + // only set if glx extension is available and not the default setting + if ((_this->gl_data->HAS_GLX_ARB_create_context_no_error) && (_this->gl_config.no_error != 0)) { + attribs[iattr++] = GLX_CONTEXT_OPENGL_NO_ERROR_ARB; + attribs[iattr++] = _this->gl_config.no_error; + } + + attribs[iattr++] = 0; + + // Get a pointer to the context creation function for GL 3.0 + if (!_this->gl_data->glXCreateContextAttribsARB) { + SDL_SetError("OpenGL 3.0 and later are not supported by this system"); + } else { + int glxAttribs[64]; + + // Create a GL 3.x context + GLXFBConfig *framebuffer_config = NULL; + int fbcount = 0; + int *pvistypeattr = NULL; + + X11_GL_GetAttributes(_this, display, screen, glxAttribs, 64, true, &pvistypeattr, transparent); + + if (_this->gl_data->glXChooseFBConfig) { + framebuffer_config = _this->gl_data->glXChooseFBConfig(display, + DefaultScreen(display), glxAttribs, + &fbcount); + + if (!framebuffer_config && (pvistypeattr != NULL)) { + *pvistypeattr = None; + framebuffer_config = _this->gl_data->glXChooseFBConfig(display, + DefaultScreen(display), glxAttribs, + &fbcount); + } + + if (transparent && (framebuffer_config != NULL)) { + int i; + for (i = 0; i < fbcount; i++) { + XVisualInfo* vinfo_temp = _this->gl_data->glXGetVisualFromFBConfig(display, framebuffer_config[i]); + if ( vinfo_temp != NULL) { + Uint32 format = X11_GetPixelFormatFromVisualInfo(display, vinfo_temp); + if (SDL_ISPIXELFORMAT_ALPHA(format)) { + // found! + context = (SDL_GLContext)_this->gl_data->glXCreateContextAttribsARB(display, + framebuffer_config[i], + share_context, True, attribs); + X11_XFree(framebuffer_config); + framebuffer_config = NULL; + X11_XFree(vinfo_temp); + break; + } + X11_XFree(vinfo_temp); + } + } + } + if (framebuffer_config) { + context = (SDL_GLContext)_this->gl_data->glXCreateContextAttribsARB(display, + framebuffer_config[0], + share_context, True, attribs); + X11_XFree(framebuffer_config); + } + } + } + } + X11_XFree(vinfo); + } + X11_XSync(display, False); + X11_XSetErrorHandler(handler); + + if (!context) { + if (errorCode == Success) { + SDL_SetError("Could not create GL context"); + } + return NULL; + } + + if (!X11_GL_MakeCurrent(_this, window, context)) { + X11_GL_DestroyContext(_this, context); + return NULL; + } + + return context; +} + +bool X11_GL_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context) +{ + Display *display = _this->internal->display; + Window drawable = + (context ? window->internal->xwindow : None); + GLXContext glx_context = (GLXContext)context; + int rc; + + if (!_this->gl_data) { + return SDL_SetError("OpenGL not initialized"); + } + + // We do this to create a clean separation between X and GLX errors. + X11_XSync(display, False); + errorHandlerOperation = "make GL context current"; + errorBase = _this->gl_data->errorBase; + errorCode = Success; + handler = X11_XSetErrorHandler(X11_GL_ErrorHandler); + rc = _this->gl_data->glXMakeCurrent(display, drawable, glx_context); + X11_XSetErrorHandler(handler); + + if (errorCode != Success) { // uhoh, an X error was thrown! + return false; // the error handler called SDL_SetError() already. + } else if (!rc) { // glXMakeCurrent() failed without throwing an X error + return SDL_SetError("Unable to make GL context current"); + } + + return true; +} + +/* + 0 is a valid argument to glXSwapInterval(MESA|EXT) and setting it to 0 + will undo the effect of a previous call with a value that is greater + than zero (or at least that is what the docs say). OTOH, 0 is an invalid + argument to glXSwapIntervalSGI and it returns an error if you call it + with 0 as an argument. +*/ + +static int swapinterval = 0; +bool X11_GL_SetSwapInterval(SDL_VideoDevice *_this, int interval) +{ + bool result = false; + + if ((interval < 0) && (!_this->gl_data->HAS_GLX_EXT_swap_control_tear)) { + return SDL_SetError("Negative swap interval unsupported in this GL"); + } else if (_this->gl_data->glXSwapIntervalEXT) { + Display *display = _this->internal->display; + const SDL_WindowData *windowdata = SDL_GL_GetCurrentWindow()->internal; + + Window drawable = windowdata->xwindow; + + /* + * This is a workaround for a bug in NVIDIA drivers. Bug has been reported + * and will be fixed in a future release (probably 319.xx). + * + * There's a bug where glXSetSwapIntervalEXT ignores updates because + * it has the wrong value cached. To work around it, we just run a no-op + * update to the current value. + */ + int currentInterval = 0; + X11_GL_GetSwapInterval(_this, ¤tInterval); + _this->gl_data->glXSwapIntervalEXT(display, drawable, currentInterval); + _this->gl_data->glXSwapIntervalEXT(display, drawable, interval); + result = true; + swapinterval = interval; + } else if (_this->gl_data->glXSwapIntervalMESA) { + const int rc = _this->gl_data->glXSwapIntervalMESA(interval); + if (rc == 0) { + swapinterval = interval; + result = true; + } else { + result = SDL_SetError("glXSwapIntervalMESA failed"); + } + } else if (_this->gl_data->glXSwapIntervalSGI) { + const int rc = _this->gl_data->glXSwapIntervalSGI(interval); + if (rc == 0) { + swapinterval = interval; + result = true; + } else { + result = SDL_SetError("glXSwapIntervalSGI failed"); + } + } else { + return SDL_Unsupported(); + } + return result; +} + +static SDL_GLSwapIntervalTearBehavior CheckSwapIntervalTearBehavior(SDL_VideoDevice *_this, Window drawable, unsigned int current_val, unsigned int current_allow_late) +{ + /* Mesa and Nvidia interpret GLX_EXT_swap_control_tear differently, as of this writing, so + figure out which behavior we have. + Technical details: https://github.com/libsdl-org/SDL/issues/8004#issuecomment-1819603282 */ + if (_this->gl_data->swap_interval_tear_behavior == SDL_SWAPINTERVALTEAR_UNTESTED) { + if (!_this->gl_data->HAS_GLX_EXT_swap_control_tear) { + _this->gl_data->swap_interval_tear_behavior = SDL_SWAPINTERVALTEAR_UNKNOWN; + } else { + Display *display = _this->internal->display; + unsigned int allow_late_swap_tearing = 22; + int original_val = (int) current_val; + + /* + * This is a workaround for a bug in NVIDIA drivers. Bug has been reported + * and will be fixed in a future release (probably 319.xx). + * + * There's a bug where glXSetSwapIntervalEXT ignores updates because + * it has the wrong value cached. To work around it, we just run a no-op + * update to the current value. + */ + _this->gl_data->glXSwapIntervalEXT(display, drawable, current_val); + + // set it to no swap interval and see how it affects GLX_LATE_SWAPS_TEAR_EXT... + _this->gl_data->glXSwapIntervalEXT(display, drawable, 0); + _this->gl_data->glXQueryDrawable(display, drawable, GLX_LATE_SWAPS_TEAR_EXT, &allow_late_swap_tearing); + + if (allow_late_swap_tearing == 0) { // GLX_LATE_SWAPS_TEAR_EXT says whether late swapping is currently in use + _this->gl_data->swap_interval_tear_behavior = SDL_SWAPINTERVALTEAR_NVIDIA; + if (current_allow_late) { + original_val = -original_val; + } + } else if (allow_late_swap_tearing == 1) { // GLX_LATE_SWAPS_TEAR_EXT says whether the Drawable can use late swapping at all + _this->gl_data->swap_interval_tear_behavior = SDL_SWAPINTERVALTEAR_MESA; + } else { // unexpected outcome! + _this->gl_data->swap_interval_tear_behavior = SDL_SWAPINTERVALTEAR_UNKNOWN; + } + + // set us back to what it was originally... + _this->gl_data->glXSwapIntervalEXT(display, drawable, original_val); + } + } + + return _this->gl_data->swap_interval_tear_behavior; +} + + +bool X11_GL_GetSwapInterval(SDL_VideoDevice *_this, int *interval) +{ + if (_this->gl_data->glXSwapIntervalEXT) { + Display *display = _this->internal->display; + const SDL_WindowData *windowdata = SDL_GL_GetCurrentWindow()->internal; + Window drawable = windowdata->xwindow; + unsigned int allow_late_swap_tearing = 0; + unsigned int val = 0; + + if (_this->gl_data->HAS_GLX_EXT_swap_control_tear) { + allow_late_swap_tearing = 22; // set this to nonsense. + _this->gl_data->glXQueryDrawable(display, drawable, + GLX_LATE_SWAPS_TEAR_EXT, + &allow_late_swap_tearing); + } + + _this->gl_data->glXQueryDrawable(display, drawable, + GLX_SWAP_INTERVAL_EXT, &val); + + *interval = (int)val; + + switch (CheckSwapIntervalTearBehavior(_this, drawable, val, allow_late_swap_tearing)) { + case SDL_SWAPINTERVALTEAR_MESA: + *interval = (int)val; // unsigned int cast to signed that generates negative value if necessary. + break; + + case SDL_SWAPINTERVALTEAR_NVIDIA: + default: + if ((allow_late_swap_tearing) && (val > 0)) { + *interval = -((int)val); + } + break; + } + + return true; + } else if (_this->gl_data->glXGetSwapIntervalMESA) { + int val = _this->gl_data->glXGetSwapIntervalMESA(); + if (val == GLX_BAD_CONTEXT) { + return SDL_SetError("GLX_BAD_CONTEXT"); + } + *interval = val; + return true; + } else { + *interval = swapinterval; + return true; + } +} + +bool X11_GL_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + + _this->gl_data->glXSwapBuffers(display, data->xwindow); + +#ifdef SDL_VIDEO_DRIVER_X11_XSYNC + X11_HandlePresent(data->window); +#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */ + + return true; +} + +bool X11_GL_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context) +{ + Display *display = _this->internal->display; + GLXContext glx_context = (GLXContext)context; + + if (!_this->gl_data) { + return true; + } + _this->gl_data->glXDestroyContext(display, glx_context); + X11_XSync(display, False); + return true; +} + +#endif // SDL_VIDEO_OPENGL_GLX + +#endif // SDL_VIDEO_DRIVER_X11 diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11opengl.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11opengl.h new file mode 100644 index 0000000..24db485 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11opengl.h @@ -0,0 +1,96 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifndef SDL_x11opengl_h_ +#define SDL_x11opengl_h_ + +#ifdef SDL_VIDEO_OPENGL_GLX +#include +#include + +typedef void (*__GLXextFuncPtr)(void); + +typedef enum SDL_GLSwapIntervalTearBehavior +{ + SDL_SWAPINTERVALTEAR_UNTESTED, + SDL_SWAPINTERVALTEAR_UNKNOWN, + SDL_SWAPINTERVALTEAR_MESA, + SDL_SWAPINTERVALTEAR_NVIDIA +} SDL_GLSwapIntervalTearBehavior; + +struct SDL_GLDriverData +{ + int errorBase, eventBase; + + bool HAS_GLX_EXT_visual_rating; + bool HAS_GLX_EXT_visual_info; + bool HAS_GLX_EXT_swap_control_tear; + bool HAS_GLX_ARB_context_flush_control; + bool HAS_GLX_ARB_create_context_robustness; + bool HAS_GLX_ARB_create_context_no_error; + + /* Max version of OpenGL ES context that can be created if the + implementation supports GLX_EXT_create_context_es2_profile. + major = minor = 0 when unsupported. + */ + struct + { + int major; + int minor; + } es_profile_max_supported_version; + + SDL_GLSwapIntervalTearBehavior swap_interval_tear_behavior; + + Bool (*glXQueryExtension)(Display *, int *, int *); + __GLXextFuncPtr (*glXGetProcAddress)(const GLubyte *); + XVisualInfo *(*glXChooseVisual)(Display *, int, int *); + GLXContext (*glXCreateContext)(Display *, XVisualInfo *, GLXContext, Bool); + GLXContext (*glXCreateContextAttribsARB)(Display *, GLXFBConfig, GLXContext, Bool, const int *); + GLXFBConfig *(*glXChooseFBConfig)(Display *, int, const int *, int *); + XVisualInfo *(*glXGetVisualFromFBConfig)(Display *, GLXFBConfig); + void (*glXDestroyContext)(Display *, GLXContext); + Bool (*glXMakeCurrent)(Display *, GLXDrawable, GLXContext); + void (*glXSwapBuffers)(Display *, GLXDrawable); + void (*glXQueryDrawable)(Display *, GLXDrawable, int, unsigned int *); + void (*glXSwapIntervalEXT)(Display *, GLXDrawable, int); + int (*glXSwapIntervalSGI)(int); + int (*glXSwapIntervalMESA)(int); + int (*glXGetSwapIntervalMESA)(void); +}; + +// OpenGL functions +extern bool X11_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path); +extern SDL_FunctionPointer X11_GL_GetProcAddress(SDL_VideoDevice *_this, const char *proc); +extern void X11_GL_UnloadLibrary(SDL_VideoDevice *_this); +extern bool X11_GL_UseEGL(SDL_VideoDevice *_this); +extern XVisualInfo *X11_GL_GetVisual(SDL_VideoDevice *_this, Display *display, int screen, bool transparent); +extern SDL_GLContext X11_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window *window); +extern bool X11_GL_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, + SDL_GLContext context); +extern bool X11_GL_SetSwapInterval(SDL_VideoDevice *_this, int interval); +extern bool X11_GL_GetSwapInterval(SDL_VideoDevice *_this, int *interval); +extern bool X11_GL_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern bool X11_GL_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context); + +#endif // SDL_VIDEO_OPENGL_GLX + +#endif // SDL_x11opengl_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11opengles.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11opengles.c new file mode 100644 index 0000000..9c1910f --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11opengles.c @@ -0,0 +1,152 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#if defined(SDL_VIDEO_DRIVER_X11) && defined(SDL_VIDEO_OPENGL_EGL) + +#include "SDL_x11video.h" +#include "SDL_x11opengles.h" +#include "SDL_x11opengl.h" +#include "SDL_x11xsync.h" + +// EGL implementation of SDL OpenGL support + +bool X11_GLES_LoadLibrary(SDL_VideoDevice *_this, const char *path) +{ + SDL_VideoData *data = _this->internal; + + // If the profile requested is not GL ES, switch over to X11_GL functions + if ((_this->gl_config.profile_mask != SDL_GL_CONTEXT_PROFILE_ES) && + !SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false)) { +#ifdef SDL_VIDEO_OPENGL_GLX + X11_GLES_UnloadLibrary(_this); + _this->GL_LoadLibrary = X11_GL_LoadLibrary; + _this->GL_GetProcAddress = X11_GL_GetProcAddress; + _this->GL_UnloadLibrary = X11_GL_UnloadLibrary; + _this->GL_CreateContext = X11_GL_CreateContext; + _this->GL_MakeCurrent = X11_GL_MakeCurrent; + _this->GL_SetSwapInterval = X11_GL_SetSwapInterval; + _this->GL_GetSwapInterval = X11_GL_GetSwapInterval; + _this->GL_SwapWindow = X11_GL_SwapWindow; + _this->GL_DestroyContext = X11_GL_DestroyContext; + return X11_GL_LoadLibrary(_this, path); +#else + return SDL_SetError("SDL not configured with OpenGL/GLX support"); +#endif + } + + return SDL_EGL_LoadLibrary(_this, path, (NativeDisplayType)data->display, _this->gl_config.egl_platform); +} + +XVisualInfo *X11_GLES_GetVisual(SDL_VideoDevice *_this, Display *display, int screen, bool transparent) +{ + + XVisualInfo *egl_visualinfo = NULL; + EGLint visual_id = 0; + XVisualInfo vi_in; + int out_count = 0; + + if (!_this->egl_data) { + // The EGL library wasn't loaded, SDL_GetError() should have info + return NULL; + } + + if (_this->egl_data->eglGetConfigAttrib(_this->egl_data->egl_display, + _this->egl_data->egl_config, + EGL_NATIVE_VISUAL_ID, + &visual_id) == EGL_FALSE) { + visual_id = 0; + } + if (visual_id != 0) { + vi_in.screen = screen; + vi_in.visualid = visual_id; + egl_visualinfo = X11_XGetVisualInfo(display, VisualScreenMask | VisualIDMask, &vi_in, &out_count); + if (transparent && egl_visualinfo) { + Uint32 format = X11_GetPixelFormatFromVisualInfo(display, egl_visualinfo); + if (!SDL_ISPIXELFORMAT_ALPHA(format)) { + // not transparent! + X11_XFree(egl_visualinfo); + egl_visualinfo = NULL; + } + } + } + + if(!egl_visualinfo) { + // Use the default visual when all else fails + vi_in.screen = screen; + egl_visualinfo = X11_XGetVisualInfo(display, + VisualScreenMask, + &vi_in, &out_count); + + // Return the first transparent Visual + if (transparent) { + int i; + for (i = 0; i < out_count; i++) { + XVisualInfo *v = &egl_visualinfo[i]; + Uint32 format = X11_GetPixelFormatFromVisualInfo(display, v); + if (SDL_ISPIXELFORMAT_ALPHA(format)) { // found! + // re-request it to have a copy that can be X11_XFree'ed later + vi_in.screen = screen; + vi_in.visualid = v->visualid; + X11_XFree(egl_visualinfo); + egl_visualinfo = X11_XGetVisualInfo(display, VisualScreenMask | VisualIDMask, &vi_in, &out_count); + return egl_visualinfo; + } + } + } + } + return egl_visualinfo; +} + +SDL_GLContext X11_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_GLContext context; + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + + X11_XSync(display, False); + context = SDL_EGL_CreateContext(_this, data->egl_surface); + X11_XSync(display, False); + + return context; +} + +SDL_EGLSurface X11_GLES_GetEGLSurface(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + return data->egl_surface; +} + +bool X11_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + const bool ret = SDL_EGL_SwapBuffers(_this, window->internal->egl_surface); \ + +#ifdef SDL_VIDEO_DRIVER_X11_XSYNC + X11_HandlePresent(window); +#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */ + + return ret; +} + +SDL_EGL_MakeCurrent_impl(X11) + +#endif // SDL_VIDEO_DRIVER_X11 && SDL_VIDEO_OPENGL_EGL + diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11opengles.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11opengles.h new file mode 100644 index 0000000..f8e8d3b --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11opengles.h @@ -0,0 +1,55 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifndef SDL_x11opengles_h_ +#define SDL_x11opengles_h_ + +#ifdef SDL_VIDEO_OPENGL_EGL + +#include "../SDL_sysvideo.h" +#include "../SDL_egl_c.h" + +typedef struct SDL_PrivateGLESData +{ + // 1401 If the struct-declaration-list contains no named members, the behavior is undefined. + // warning: empty struct has size 0 in C, size 1 in C++ [-Wc++-compat] + int dummy; +} SDL_PrivateGLESData; + +// OpenGLES functions +#define X11_GLES_GetAttribute SDL_EGL_GetAttribute +#define X11_GLES_GetProcAddress SDL_EGL_GetProcAddressInternal +#define X11_GLES_UnloadLibrary SDL_EGL_UnloadLibrary +#define X11_GLES_SetSwapInterval SDL_EGL_SetSwapInterval +#define X11_GLES_GetSwapInterval SDL_EGL_GetSwapInterval +#define X11_GLES_DestroyContext SDL_EGL_DestroyContext + +extern bool X11_GLES_LoadLibrary(SDL_VideoDevice *_this, const char *path); +extern XVisualInfo *X11_GLES_GetVisual(SDL_VideoDevice *_this, Display *display, int screen, bool transparent); +extern SDL_GLContext X11_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window *window); +extern bool X11_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern bool X11_GLES_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context); +extern SDL_EGLSurface X11_GLES_GetEGLSurface(SDL_VideoDevice *_this, SDL_Window *window); + +#endif // SDL_VIDEO_OPENGL_EGL + +#endif // SDL_x11opengles_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11pen.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11pen.c new file mode 100644 index 0000000..f16da51 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11pen.c @@ -0,0 +1,437 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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 "../../events/SDL_pen_c.h" +#include "../SDL_sysvideo.h" +#include "SDL_x11pen.h" +#include "SDL_x11video.h" +#include "SDL_x11xinput2.h" + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + +// Does this device have a valuator for pressure sensitivity? +static bool X11_XInput2DeviceIsPen(SDL_VideoDevice *_this, const XIDeviceInfo *dev) +{ + const SDL_VideoData *data = _this->internal; + for (int i = 0; i < dev->num_classes; i++) { + const XIAnyClassInfo *classinfo = dev->classes[i]; + if (classinfo->type == XIValuatorClass) { + const XIValuatorClassInfo *val_classinfo = (const XIValuatorClassInfo *)classinfo; + if (val_classinfo->label == data->atoms.pen_atom_abs_pressure) { + return true; + } + } + } + + return false; +} + +// Heuristically determines if device is an eraser +static bool X11_XInput2PenIsEraser(SDL_VideoDevice *_this, int deviceid, char *devicename) +{ + #define PEN_ERASER_NAME_TAG "eraser" // String constant to identify erasers + SDL_VideoData *data = _this->internal; + + if (data->atoms.pen_atom_wacom_tool_type != None) { + Atom type_return; + int format_return; + unsigned long num_items_return; + unsigned long bytes_after_return; + unsigned char *tooltype_name_info = NULL; + + // Try Wacom-specific method + if (Success == X11_XIGetProperty(data->display, deviceid, + data->atoms.pen_atom_wacom_tool_type, + 0, 32, False, + AnyPropertyType, &type_return, &format_return, + &num_items_return, &bytes_after_return, + &tooltype_name_info) && + tooltype_name_info != NULL && num_items_return > 0) { + + bool result = false; + char *tooltype_name = NULL; + + if (type_return == XA_ATOM) { + // Atom instead of string? Un-intern + Atom atom = *((Atom *)tooltype_name_info); + if (atom != None) { + tooltype_name = X11_XGetAtomName(data->display, atom); + } + } else if (type_return == XA_STRING && format_return == 8) { + tooltype_name = (char *)tooltype_name_info; + } + + if (tooltype_name) { + if (SDL_strcasecmp(tooltype_name, PEN_ERASER_NAME_TAG) == 0) { + result = true; + } + if (tooltype_name != (char *)tooltype_name_info) { + X11_XFree(tooltype_name_info); + } + X11_XFree(tooltype_name); + + return result; + } + } + } + + // Non-Wacom device? + + /* We assume that a device is an eraser if its name contains the string "eraser". + * Unfortunately there doesn't seem to be a clean way to distinguish these cases (as of 2022-03). */ + return (SDL_strcasestr(devicename, PEN_ERASER_NAME_TAG)) ? true : false; +} + +// Read out an integer property and store into a preallocated Sint32 array, extending 8 and 16 bit values suitably. +// Returns number of Sint32s written (<= max_words), or 0 on error. +static size_t X11_XInput2PenGetIntProperty(SDL_VideoDevice *_this, int deviceid, Atom property, Sint32 *dest, size_t max_words) +{ + const SDL_VideoData *data = _this->internal; + Atom type_return; + int format_return; + unsigned long num_items_return; + unsigned long bytes_after_return; + unsigned char *output; + + if (property == None) { + return 0; + } + + if (Success != X11_XIGetProperty(data->display, deviceid, + property, + 0, max_words, False, + XA_INTEGER, &type_return, &format_return, + &num_items_return, &bytes_after_return, + &output) || + num_items_return == 0 || output == NULL) { + return 0; + } + + if (type_return == XA_INTEGER) { + int k; + const int to_copy = SDL_min(max_words, num_items_return); + + if (format_return == 8) { + Sint8 *numdata = (Sint8 *)output; + for (k = 0; k < to_copy; ++k) { + dest[k] = numdata[k]; + } + } else if (format_return == 16) { + Sint16 *numdata = (Sint16 *)output; + for (k = 0; k < to_copy; ++k) { + dest[k] = numdata[k]; + } + } else { + SDL_memcpy(dest, output, sizeof(Sint32) * to_copy); + } + X11_XFree(output); + return to_copy; + } + + return 0; // type mismatch +} + +// Identify Wacom devices (if true is returned) and extract their device type and serial IDs +static bool X11_XInput2PenWacomDeviceID(SDL_VideoDevice *_this, int deviceid, Uint32 *wacom_devicetype_id, Uint32 *wacom_serial) +{ + SDL_VideoData *data = _this->internal; + Sint32 serial_id_buf[3]; + int result; + + if ((result = X11_XInput2PenGetIntProperty(_this, deviceid, data->atoms.pen_atom_wacom_serial_ids, serial_id_buf, 3)) == 3) { + *wacom_devicetype_id = serial_id_buf[2]; + *wacom_serial = serial_id_buf[1]; + return true; + } + + *wacom_devicetype_id = *wacom_serial = 0; + return false; +} + + +typedef struct FindPenByDeviceIDData +{ + int x11_deviceid; + void *handle; +} FindPenByDeviceIDData; + +static bool FindPenByDeviceID(void *handle, void *userdata) +{ + const X11_PenHandle *x11_handle = (const X11_PenHandle *) handle; + FindPenByDeviceIDData *data = (FindPenByDeviceIDData *) userdata; + if (x11_handle->x11_deviceid != data->x11_deviceid) { + return false; + } + data->handle = handle; + return true; +} + +X11_PenHandle *X11_FindPenByDeviceID(int deviceid) +{ + FindPenByDeviceIDData data; + data.x11_deviceid = deviceid; + data.handle = NULL; + SDL_FindPenByCallback(FindPenByDeviceID, &data); + return (X11_PenHandle *) data.handle; +} + +static X11_PenHandle *X11_MaybeAddPen(SDL_VideoDevice *_this, const XIDeviceInfo *dev) +{ + SDL_VideoData *data = _this->internal; + SDL_PenCapabilityFlags capabilities = 0; + X11_PenHandle *handle = NULL; + + if ((dev->use != XISlavePointer && (dev->use != XIFloatingSlave)) || dev->enabled == 0 || !X11_XInput2DeviceIsPen(_this, dev)) { + return NULL; // Only track physical devices that are enabled and look like pens + } else if ((handle = X11_FindPenByDeviceID(dev->deviceid)) != 0) { + return handle; // already have this pen, skip it. + } else if ((handle = SDL_calloc(1, sizeof (*handle))) == NULL) { + return NULL; // oh well. + } + + for (int i = 0; i < SDL_arraysize(handle->valuator_for_axis); i++) { + handle->valuator_for_axis[i] = SDL_X11_PEN_AXIS_VALUATOR_MISSING; // until proven otherwise + } + + int total_buttons = 0; + for (int i = 0; i < dev->num_classes; i++) { + const XIAnyClassInfo *classinfo = dev->classes[i]; + if (classinfo->type == XIButtonClass) { + const XIButtonClassInfo *button_classinfo = (const XIButtonClassInfo *)classinfo; + total_buttons += button_classinfo->num_buttons; + } else if (classinfo->type == XIValuatorClass) { + const XIValuatorClassInfo *val_classinfo = (const XIValuatorClassInfo *)classinfo; + const Sint8 valuator_nr = val_classinfo->number; + const Atom vname = val_classinfo->label; + const float min = (float)val_classinfo->min; + const float max = (float)val_classinfo->max; + bool use_this_axis = true; + SDL_PenAxis axis = SDL_PEN_AXIS_COUNT; + + // afaict, SDL_PEN_AXIS_DISTANCE is never reported by XInput2 (Wayland can offer it, though) + if (vname == data->atoms.pen_atom_abs_pressure) { + axis = SDL_PEN_AXIS_PRESSURE; + } else if (vname == data->atoms.pen_atom_abs_tilt_x) { + axis = SDL_PEN_AXIS_XTILT; + } else if (vname == data->atoms.pen_atom_abs_tilt_y) { + axis = SDL_PEN_AXIS_YTILT; + } else { + use_this_axis = false; + } + + // !!! FIXME: there are wacom-specific hacks for getting SDL_PEN_AXIS_(ROTATION|SLIDER) on some devices, but for simplicity, we're skipping all that for now. + + if (use_this_axis) { + capabilities |= SDL_GetPenCapabilityFromAxis(axis); + handle->valuator_for_axis[axis] = valuator_nr; + handle->axis_min[axis] = min; + handle->axis_max[axis] = max; + } + } + } + + // We have a pen if and only if the device measures pressure. + // We checked this in X11_XInput2DeviceIsPen, so just assert it here. + SDL_assert(capabilities & SDL_PEN_CAPABILITY_PRESSURE); + + const bool is_eraser = X11_XInput2PenIsEraser(_this, dev->deviceid, dev->name); + Uint32 wacom_devicetype_id = 0; + Uint32 wacom_serial = 0; + X11_XInput2PenWacomDeviceID(_this, dev->deviceid, &wacom_devicetype_id, &wacom_serial); + + SDL_PenInfo peninfo; + SDL_zero(peninfo); + peninfo.capabilities = capabilities; + peninfo.max_tilt = -1; + peninfo.wacom_id = wacom_devicetype_id; + peninfo.num_buttons = total_buttons; + peninfo.subtype = is_eraser ? SDL_PEN_TYPE_ERASER : SDL_PEN_TYPE_PEN; + if (is_eraser) { + peninfo.capabilities |= SDL_PEN_CAPABILITY_ERASER; + } + + handle->is_eraser = is_eraser; + handle->x11_deviceid = dev->deviceid; + + handle->pen = SDL_AddPenDevice(0, dev->name, &peninfo, handle); + if (!handle->pen) { + SDL_free(handle); + return NULL; + } + + return handle; +} + +X11_PenHandle *X11_MaybeAddPenByDeviceID(SDL_VideoDevice *_this, int deviceid) +{ + SDL_VideoData *data = _this->internal; + int num_device_info = 0; + XIDeviceInfo *device_info = X11_XIQueryDevice(data->display, deviceid, &num_device_info); + if (device_info) { + SDL_assert(num_device_info == 1); + X11_PenHandle *handle = X11_MaybeAddPen(_this, device_info); + X11_XIFreeDeviceInfo(device_info); + return handle; + } + return NULL; +} + +void X11_RemovePenByDeviceID(int deviceid) +{ + X11_PenHandle *handle = X11_FindPenByDeviceID(deviceid); + if (handle) { + SDL_RemovePenDevice(0, handle->pen); + SDL_free(handle); + } +} + +void X11_InitPen(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + + #define LOOKUP_PEN_ATOM(X) X11_XInternAtom(data->display, X, False) + data->atoms.pen_atom_device_product_id = LOOKUP_PEN_ATOM("Device Product ID"); + data->atoms.pen_atom_wacom_serial_ids = LOOKUP_PEN_ATOM("Wacom Serial IDs"); + data->atoms.pen_atom_wacom_tool_type = LOOKUP_PEN_ATOM("Wacom Tool Type"); + data->atoms.pen_atom_abs_pressure = LOOKUP_PEN_ATOM("Abs Pressure"); + data->atoms.pen_atom_abs_tilt_x = LOOKUP_PEN_ATOM("Abs Tilt X"); + data->atoms.pen_atom_abs_tilt_y = LOOKUP_PEN_ATOM("Abs Tilt Y"); + #undef LOOKUP_PEN_ATOM + + // Do an initial check on devices. After this, we'll add/remove individual pens when XI_HierarchyChanged events alert us. + int num_device_info = 0; + XIDeviceInfo *device_info = X11_XIQueryDevice(data->display, XIAllDevices, &num_device_info); + if (device_info) { + for (int i = 0; i < num_device_info; i++) { + X11_MaybeAddPen(_this, &device_info[i]); + } + X11_XIFreeDeviceInfo(device_info); + } +} + +static void X11_FreePenHandle(SDL_PenID instance_id, void *handle, void *userdata) +{ + SDL_free(handle); +} + +void X11_QuitPen(SDL_VideoDevice *_this) +{ + SDL_RemoveAllPenDevices(X11_FreePenHandle, NULL); +} + +static void X11_XInput2NormalizePenAxes(const X11_PenHandle *pen, float *coords) +{ + // Normalise axes + for (int axis = 0; axis < SDL_PEN_AXIS_COUNT; ++axis) { + const int valuator = pen->valuator_for_axis[axis]; + if (valuator == SDL_X11_PEN_AXIS_VALUATOR_MISSING) { + continue; + } + + float value = coords[axis]; + const float min = pen->axis_min[axis]; + const float max = pen->axis_max[axis]; + + if (axis == SDL_PEN_AXIS_SLIDER) { + value += pen->slider_bias; + } + + // min ... 0 ... max + if (min < 0.0) { + // Normalise so that 0 remains 0.0 + if (value < 0) { + value = value / (-min); + } else { + if (max == 0.0f) { + value = 0.0f; + } else { + value = value / max; + } + } + } else { + // 0 ... min ... max + // including 0.0 = min + if (max == 0.0f) { + value = 0.0f; + } else { + value = (value - min) / max; + } + } + + switch (axis) { + case SDL_PEN_AXIS_XTILT: + case SDL_PEN_AXIS_YTILT: + //if (peninfo->info.max_tilt > 0.0f) { + // value *= peninfo->info.max_tilt; // normalize to physical max + //} + break; + + case SDL_PEN_AXIS_ROTATION: + // normalised to -1..1, so let's convert to degrees + value *= 180.0f; + value += pen->rotation_bias; + + // handle simple over/underflow + if (value >= 180.0f) { + value -= 360.0f; + } else if (value < -180.0f) { + value += 360.0f; + } + break; + + default: + break; + } + + coords[axis] = value; + } +} + +void X11_PenAxesFromValuators(const X11_PenHandle *pen, + const double *input_values, const unsigned char *mask, int mask_len, + float axis_values[SDL_PEN_AXIS_COUNT]) +{ + for (int i = 0; i < SDL_PEN_AXIS_COUNT; i++) { + const int valuator = pen->valuator_for_axis[i]; + if ((valuator == SDL_X11_PEN_AXIS_VALUATOR_MISSING) || (valuator >= mask_len * 8) || !(XIMaskIsSet(mask, valuator))) { + axis_values[i] = 0.0f; + } else { + axis_values[i] = (float)input_values[valuator]; + } + } + X11_XInput2NormalizePenAxes(pen, axis_values); +} + +#else + +void X11_InitPen(SDL_VideoDevice *_this) +{ + (void) _this; +} + +void X11_QuitPen(SDL_VideoDevice *_this) +{ + (void) _this; +} + +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 + diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11pen.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11pen.h new file mode 100644 index 0000000..de75181 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11pen.h @@ -0,0 +1,72 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifndef SDL_x11pen_h_ +#define SDL_x11pen_h_ + +// Pressure-sensitive pen support for X11. + +#include "SDL_x11video.h" +#include "../../events/SDL_pen_c.h" + +// Prep pen support (never fails; pens simply won't be added if there's a problem). +extern void X11_InitPen(SDL_VideoDevice *_this); + +// Clean up pen support. +extern void X11_QuitPen(SDL_VideoDevice *_this); + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + +// Forward definition for SDL_x11video.h +struct SDL_VideoData; + +#define SDL_X11_PEN_AXIS_VALUATOR_MISSING -1 + +typedef struct X11_PenHandle +{ + SDL_PenID pen; + bool is_eraser; + int x11_deviceid; + int valuator_for_axis[SDL_PEN_AXIS_COUNT]; + float slider_bias; // shift value to add to PEN_AXIS_SLIDER (before normalisation) + float rotation_bias; // rotation to add to PEN_AXIS_ROTATION (after normalisation) + float axis_min[SDL_PEN_AXIS_COUNT]; + float axis_max[SDL_PEN_AXIS_COUNT]; +} X11_PenHandle; + +// Converts XINPUT2 valuators into pen axis information, including normalisation. +extern void X11_PenAxesFromValuators(const X11_PenHandle *pen, + const double *input_values, const unsigned char *mask, int mask_len, + float axis_values[SDL_PEN_AXIS_COUNT]); + +// Add a pen (if this function's further checks validate it). +extern X11_PenHandle *X11_MaybeAddPenByDeviceID(SDL_VideoDevice *_this, int deviceid); + +// Remove a pen. It's okay if deviceid is bogus or not a pen, we'll check it. +extern void X11_RemovePenByDeviceID(int deviceid); + +// Map X11 device ID to pen ID. +extern X11_PenHandle *X11_FindPenByDeviceID(int deviceid); + +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 + +#endif // SDL_x11pen_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11settings.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11settings.c new file mode 100644 index 0000000..7a7ae01 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11settings.c @@ -0,0 +1,129 @@ +/* + 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" +#define SDL_XSETTINGS_XFT_DPI "Xft/DPI" + +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 || + SDL_strcmp(name, SDL_XSETTINGS_XFT_DPI) != 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; + if (SDL_strcmp(name, SDL_XSETTINGS_XFT_DPI) == 0) { + scale_factor = scale_factor / 1024.0f / 96.0f; + } + 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->internal; + 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->internal; + 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->internal; + 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; + } + } +} + +int X11_GetXsettingsIntKey(SDL_VideoDevice *_this, const char *key, int fallback_value) { + SDL_VideoData *data = _this->internal; + SDLX11_SettingsData *xsettings_data = &data->xsettings_data; + XSettingsSetting *setting = NULL; + int res = fallback_value; + + + if (xsettings_data->xsettings) { + if (xsettings_client_get_setting(xsettings_data->xsettings, key, &setting) != XSETTINGS_SUCCESS) { + goto no_key; + } + + if (setting->type != XSETTINGS_TYPE_INT) { + goto no_key; + } + + res = setting->data.v_int; + } + +no_key: + if (setting) { + xsettings_setting_free(setting); + } + + return res; +} + +#endif // SDL_VIDEO_DRIVER_X11 diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11settings.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11settings.h new file mode 100644 index 0000000..5b36884 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11settings.h @@ -0,0 +1,39 @@ +/* + 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); +extern int X11_GetXsettingsIntKey(SDL_VideoDevice *_this, const char *key, int fallback_value); + +#endif // SDL_x11settings_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11shape.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11shape.c new file mode 100644 index 0000000..e433598 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11shape.c @@ -0,0 +1,111 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifdef SDL_VIDEO_DRIVER_X11 + +#include "SDL_x11video.h" +#include "SDL_x11shape.h" + + +#ifdef SDL_VIDEO_DRIVER_X11_XSHAPE +static Uint8 *GenerateShapeMask(SDL_Surface *shape) +{ + int x, y; + const size_t ppb = 8; + const size_t bytes_per_scanline = (shape->w + (ppb - 1)) / ppb; + const Uint8 *a; + Uint8 *mask; + Uint8 *mask_scanline; + Uint8 mask_value; + + mask = (Uint8 *)SDL_calloc(1, shape->h * bytes_per_scanline); + if (mask) { + for (y = 0; y < shape->h; y++) { + a = (const Uint8 *)shape->pixels + y * shape->pitch; + mask_scanline = mask + y * bytes_per_scanline; + for (x = 0; x < shape->w; x++) { + mask_value = (*a == SDL_ALPHA_TRANSPARENT) ? 0 : 1; + mask_scanline[x / ppb] |= mask_value << (x % ppb); + a += 4; + } + } + } + return mask; +} +#endif // SDL_VIDEO_DRIVER_X11_XSHAPE + +bool X11_UpdateWindowShape(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *shape) +{ + bool result = false; + +#ifdef SDL_VIDEO_DRIVER_X11_XSHAPE + SDL_WindowData *windowdata = window->internal; + + // Generate a set of spans for the region + if (shape) { + SDL_Surface *stretched = NULL; + Uint8 *mask; + Pixmap pixmap; + + if (shape->w != window->w || shape->h != window->h) { + stretched = SDL_CreateSurface(window->w, window->h, SDL_PIXELFORMAT_ARGB32); + if (!stretched) { + return false; + } + if (!SDL_StretchSurface(shape, NULL, stretched, NULL, SDL_SCALEMODE_LINEAR)) { + SDL_DestroySurface(stretched); + return false; + } + shape = stretched; + } + + mask = GenerateShapeMask(shape); + if (mask) { + pixmap = X11_XCreateBitmapFromData(windowdata->videodata->display, windowdata->xwindow, (const char *)mask, shape->w, shape->h); + X11_XShapeCombineMask(windowdata->videodata->display, windowdata->xwindow, ShapeInput, 0, 0, pixmap, ShapeSet); + SDL_free(mask); + + result = true; + } + + if (stretched) { + SDL_DestroySurface(stretched); + } + } else { + Region region = X11_XCreateRegion(); + XRectangle rect; + + rect.x = 0; + rect.y = 0; + rect.width = window->w; + rect.height = window->h; + X11_XUnionRectWithRegion(&rect, region, region); + X11_XShapeCombineRegion(windowdata->videodata->display, windowdata->xwindow, ShapeInput, 0, 0, region, ShapeSet); + X11_XDestroyRegion(region); + result = true; + } +#endif // SDL_VIDEO_DRIVER_X11_XSHAPE + + return result; +} + +#endif // SDL_VIDEO_DRIVER_X11 diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11shape.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11shape.h new file mode 100644 index 0000000..d47d103 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11shape.h @@ -0,0 +1,28 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifndef SDL_x11shape_h_ +#define SDL_x11shape_h_ + +extern bool X11_UpdateWindowShape(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *shape); + +#endif // SDL_x11shape_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11sym.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11sym.h new file mode 100644 index 0000000..68d70cd --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11sym.h @@ -0,0 +1,354 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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. +*/ + +/* *INDENT-OFF* */ // clang-format off + +#ifndef SDL_X11_MODULE +#define SDL_X11_MODULE(modname) +#endif + +#ifndef SDL_X11_SYM +#define SDL_X11_SYM(rc,fn,params,args,ret) +#endif + +SDL_X11_MODULE(BASEXLIB) +SDL_X11_SYM(XSizeHints*,XAllocSizeHints,(void),(),return) +SDL_X11_SYM(XWMHints*,XAllocWMHints,(void),(),return) +SDL_X11_SYM(XClassHint*,XAllocClassHint,(void),(),return) +SDL_X11_SYM(int,XChangePointerControl,(Display* a,Bool b,Bool c,int d,int e,int f),(a,b,c,d,e,f),return) +SDL_X11_SYM(int,XChangeProperty,(Display* a,Window b,Atom c,Atom d,int e,int f,_Xconst unsigned char* g,int h),(a,b,c,d,e,f,g,h),return) +SDL_X11_SYM(Bool,XCheckIfEvent,(Display* a,XEvent *b,Bool (*c)(Display*,XEvent*,XPointer),XPointer d),(a,b,c,d),return) +SDL_X11_SYM(int,XClearWindow,(Display* a,Window b),(a,b),return) +SDL_X11_SYM(int,XCloseDisplay,(Display* a),(a),return) +SDL_X11_SYM(int,XConvertSelection,(Display* a,Atom b,Atom c,Atom d,Window e,Time f),(a,b,c,d,e,f),return) +SDL_X11_SYM(Pixmap,XCreateBitmapFromData,(Display *dpy,Drawable d,_Xconst char *data,unsigned int width,unsigned int height),(dpy,d,data,width,height),return) +SDL_X11_SYM(Colormap,XCreateColormap,(Display* a,Window b,Visual* c,int d),(a,b,c,d),return) +SDL_X11_SYM(Cursor,XCreatePixmapCursor,(Display* a,Pixmap b,Pixmap c,XColor* d,XColor* e,unsigned int f,unsigned int g),(a,b,c,d,e,f,g),return) +SDL_X11_SYM(Cursor,XCreateFontCursor,(Display* a,unsigned int b),(a,b),return) +SDL_X11_SYM(XFontSet,XCreateFontSet,(Display* a, _Xconst char* b, char*** c, int* d, char** e),(a,b,c,d,e),return) +SDL_X11_SYM(GC,XCreateGC,(Display* a,Drawable b,unsigned long c,XGCValues* d),(a,b,c,d),return) +SDL_X11_SYM(XImage*,XCreateImage,(Display* a,Visual* b,unsigned int c,int d,int e,char* f,unsigned int g,unsigned int h,int i,int j),(a,b,c,d,e,f,g,h,i,j),return) +SDL_X11_SYM(Window,XCreateWindow,(Display* a,Window b,int c,int d,unsigned int e,unsigned int f,unsigned int g,int h,unsigned int i,Visual* j,unsigned long k,XSetWindowAttributes* l),(a,b,c,d,e,f,g,h,i,j,k,l),return) +SDL_X11_SYM(int,XDefineCursor,(Display* a,Window b,Cursor c),(a,b,c),return) +SDL_X11_SYM(int,XDeleteProperty,(Display* a,Window b,Atom c),(a,b,c),return) +SDL_X11_SYM(int,XDestroyWindow,(Display* a,Window b),(a,b),return) +SDL_X11_SYM(int,XDisplayKeycodes,(Display* a,int* b,int* c),(a,b,c),return) +SDL_X11_SYM(int,XDrawRectangle,(Display* a,Drawable b,GC c,int d,int e,unsigned int f,unsigned int g),(a,b,c,d,e,f,g),return) +SDL_X11_SYM(char*,XDisplayName,(_Xconst char* a),(a),return) +SDL_X11_SYM(int,XDrawString,(Display* a,Drawable b,GC c,int d,int e,_Xconst char* f,int g),(a,b,c,d,e,f,g),return) +SDL_X11_SYM(int,XEventsQueued,(Display* a,int b),(a,b),return) +SDL_X11_SYM(int,XFillRectangle,(Display* a,Drawable b,GC c,int d,int e,unsigned int f,unsigned int g),(a,b,c,d,e,f,g),return) +SDL_X11_SYM(Bool,XFilterEvent,(XEvent *event,Window w),(event,w),return) +SDL_X11_SYM(int,XFlush,(Display* a),(a),return) +SDL_X11_SYM(int,XFree,(void*a),(a),return) +SDL_X11_SYM(int,XFreeCursor,(Display* a,Cursor b),(a,b),return) +SDL_X11_SYM(void,XFreeFontSet,(Display* a, XFontSet b),(a,b),) +SDL_X11_SYM(int,XFreeGC,(Display* a,GC b),(a,b),return) +SDL_X11_SYM(int,XFreeFont,(Display* a, XFontStruct* b),(a,b),return) +SDL_X11_SYM(int,XFreeModifiermap,(XModifierKeymap* a),(a),return) +SDL_X11_SYM(int,XFreePixmap,(Display* a,Pixmap b),(a,b),return) +SDL_X11_SYM(void,XFreeStringList,(char** a),(a),) +SDL_X11_SYM(char*,XGetAtomName,(Display *a,Atom b),(a,b),return) +SDL_X11_SYM(int,XGetInputFocus,(Display *a,Window *b,int *c),(a,b,c),return) +SDL_X11_SYM(int,XGetErrorDatabaseText,(Display* a,_Xconst char* b,_Xconst char* c,_Xconst char* d,char* e,int f),(a,b,c,d,e,f),return) +SDL_X11_SYM(XModifierKeymap*,XGetModifierMapping,(Display* a),(a),return) +SDL_X11_SYM(int,XGetPointerControl,(Display* a,int* b,int* c,int* d),(a,b,c,d),return) +SDL_X11_SYM(Window,XGetSelectionOwner,(Display* a,Atom b),(a,b),return) +SDL_X11_SYM(XVisualInfo*,XGetVisualInfo,(Display* a,long b,XVisualInfo* c,int* d),(a,b,c,d),return) +SDL_X11_SYM(Status,XGetWindowAttributes,(Display* a,Window b,XWindowAttributes* c),(a,b,c),return) +SDL_X11_SYM(int,XGetWindowProperty,(Display* a,Window b,Atom c,long d,long e,Bool f,Atom g,Atom* h,int* i,unsigned long* j,unsigned long *k,unsigned char **l),(a,b,c,d,e,f,g,h,i,j,k,l),return) +SDL_X11_SYM(XWMHints*,XGetWMHints,(Display* a,Window b),(a,b),return) +SDL_X11_SYM(Status,XGetWMNormalHints,(Display *a,Window b, XSizeHints *c, long *d),(a,b,c,d),return) +SDL_X11_SYM(int,XIfEvent,(Display* a,XEvent *b,Bool (*c)(Display*,XEvent*,XPointer),XPointer d),(a,b,c,d),return) +SDL_X11_SYM(int,XGrabKeyboard,(Display* a,Window b,Bool c,int d,int e,Time f),(a,b,c,d,e,f),return) +SDL_X11_SYM(int,XGrabPointer,(Display* a,Window b,Bool c,unsigned int d,int e,int f,Window g,Cursor h,Time i),(a,b,c,d,e,f,g,h,i),return) +SDL_X11_SYM(int,XGrabServer,(Display* a),(a),return) +SDL_X11_SYM(Status,XIconifyWindow,(Display* a,Window b,int c),(a,b,c),return) +SDL_X11_SYM(KeyCode,XKeysymToKeycode,(Display* a,KeySym b),(a,b),return) +SDL_X11_SYM(char*,XKeysymToString,(KeySym a),(a),return) +SDL_X11_SYM(int,XInstallColormap,(Display* a,Colormap b),(a,b),return) +SDL_X11_SYM(Atom,XInternAtom,(Display* a,_Xconst char* b,Bool c),(a,b,c),return) +SDL_X11_SYM(XPixmapFormatValues*,XListPixmapFormats,(Display* a,int* b),(a,b),return) +SDL_X11_SYM(XFontStruct*,XLoadQueryFont,(Display* a,_Xconst char* b),(a,b),return) +SDL_X11_SYM(KeySym,XLookupKeysym,(XKeyEvent* a,int b),(a,b),return) +SDL_X11_SYM(int,XLookupString,(XKeyEvent* a,char* b,int c,KeySym* d,XComposeStatus* e),(a,b,c,d,e),return) +SDL_X11_SYM(int,XMapRaised,(Display* a,Window b),(a,b),return) +SDL_X11_SYM(Status,XMatchVisualInfo,(Display* a,int b,int c,int d,XVisualInfo* e),(a,b,c,d,e),return) +SDL_X11_SYM(int,XMissingExtension,(Display* a,_Xconst char* b),(a,b),return) +SDL_X11_SYM(int,XMoveWindow,(Display* a,Window b,int c,int d),(a,b,c,d),return) +SDL_X11_SYM(Display*,XOpenDisplay,(_Xconst char* a),(a),return) +SDL_X11_SYM(Status,XInitThreads,(void),(),return) +SDL_X11_SYM(int,XPeekEvent,(Display* a,XEvent* b),(a,b),return) +SDL_X11_SYM(int,XPending,(Display* a),(a),return) +SDL_X11_SYM(int,XPutImage,(Display* a,Drawable b,GC c,XImage* d,int e,int f,int g,int h,unsigned int i,unsigned int j),(a,b,c,d,e,f,g,h,i,j),return) +SDL_X11_SYM(int,XQueryKeymap,(Display* a,char b[32]),(a,b),return) +SDL_X11_SYM(Bool,XQueryPointer,(Display* a,Window b,Window* c,Window* d,int* e,int* f,int* g,int* h,unsigned int* i),(a,b,c,d,e,f,g,h,i),return) +SDL_X11_SYM(int,XRaiseWindow,(Display* a,Window b),(a,b),return) +SDL_X11_SYM(int,XReparentWindow,(Display* a,Window b,Window c,int d,int e),(a,b,c,d,e),return) +SDL_X11_SYM(int,XResetScreenSaver,(Display* a),(a),return) +SDL_X11_SYM(int,XResizeWindow,(Display* a,Window b,unsigned int c,unsigned int d),(a,b,c,d),return) +SDL_X11_SYM(int,XScreenNumberOfScreen,(Screen* a),(a),return) +SDL_X11_SYM(int,XSelectInput,(Display* a,Window b,long c),(a,b,c),return) +SDL_X11_SYM(Status,XSendEvent,(Display* a,Window b,Bool c,long d,XEvent* e),(a,b,c,d,e),return) +SDL_X11_SYM(XErrorHandler,XSetErrorHandler,(XErrorHandler a),(a),return) +SDL_X11_SYM(int,XSetForeground,(Display* a,GC b,unsigned long c),(a,b,c),return) +SDL_X11_SYM(XIOErrorHandler,XSetIOErrorHandler,(XIOErrorHandler a),(a),return) +SDL_X11_SYM(int,XSetInputFocus,(Display *a,Window b,int c,Time d),(a,b,c,d),return) +SDL_X11_SYM(int,XSetSelectionOwner,(Display* a,Atom b,Window c,Time d),(a,b,c,d),return) +SDL_X11_SYM(int,XSetTransientForHint,(Display* a,Window b,Window c),(a,b,c),return) +SDL_X11_SYM(void,XSetTextProperty,(Display* a,Window b,XTextProperty* c,Atom d),(a,b,c,d),) +SDL_X11_SYM(int,XSetWindowBackground,(Display* a,Window b,unsigned long c),(a,b,c),return) +SDL_X11_SYM(void,XSetWMHints,(Display* a,Window b,XWMHints* c),(a,b,c),) +SDL_X11_SYM(void,XSetWMNormalHints,(Display* a,Window b,XSizeHints* c),(a,b,c),) +SDL_X11_SYM(void,XSetWMProperties,(Display* a,Window b,XTextProperty* c,XTextProperty* d,char** e,int f,XSizeHints* g,XWMHints* h,XClassHint* i),(a,b,c,d,e,f,g,h,i),) +SDL_X11_SYM(Status,XSetWMProtocols,(Display* a,Window b,Atom* c,int d),(a,b,c,d),return) +SDL_X11_SYM(int,XStoreColors,(Display* a,Colormap b,XColor* c,int d),(a,b,c,d),return) +SDL_X11_SYM(int,XStoreName,(Display* a,Window b,_Xconst char* c),(a,b,c),return) +SDL_X11_SYM(Status,XStringListToTextProperty,(char** a,int b,XTextProperty* c),(a,b,c),return) +SDL_X11_SYM(int,XSync,(Display* a,Bool b),(a,b),return) +SDL_X11_SYM(int,XTextExtents,(XFontStruct* a,_Xconst char* b,int c,int* d,int* e,int* f,XCharStruct* g),(a,b,c,d,e,f,g),return) +SDL_X11_SYM(Bool,XTranslateCoordinates,(Display *a,Window b,Window c,int d,int e,int* f,int* g,Window* h),(a,b,c,d,e,f,g,h),return) +SDL_X11_SYM(int,XUndefineCursor,(Display* a,Window b),(a,b),return) +SDL_X11_SYM(int,XUngrabKeyboard,(Display* a,Time b),(a,b),return) +SDL_X11_SYM(int,XUngrabPointer,(Display* a,Time b),(a,b),return) +SDL_X11_SYM(int,XUngrabServer,(Display* a),(a),return) +SDL_X11_SYM(int,XUninstallColormap,(Display* a,Colormap b),(a,b),return) +SDL_X11_SYM(int,XUnloadFont,(Display* a,Font b),(a,b),return) +SDL_X11_SYM(int,XWarpPointer,(Display* a,Window b,Window c,int d,int e,unsigned int f,unsigned int g,int h,int i),(a,b,c,d,e,f,g,h,i),return) +SDL_X11_SYM(int,XWindowEvent,(Display* a,Window b,long c,XEvent* d),(a,b,c,d),return) +SDL_X11_SYM(Status,XWithdrawWindow,(Display* a,Window b,int c),(a,b,c),return) +SDL_X11_SYM(VisualID,XVisualIDFromVisual,(Visual* a),(a),return) +SDL_X11_SYM(char*,XGetDefault,(Display* a,_Xconst char* b, _Xconst char* c),(a,b,c),return) +SDL_X11_SYM(Bool,XQueryExtension,(Display* a,_Xconst char* b,int* c,int* d,int* e),(a,b,c,d,e),return) +SDL_X11_SYM(char *,XDisplayString,(Display* a),(a),return) +SDL_X11_SYM(int,XGetErrorText,(Display* a,int b,char* c,int d),(a,b,c,d),return) +SDL_X11_SYM(void,_XEatData,(Display* a,unsigned long b),(a,b),) +SDL_X11_SYM(void,_XFlush,(Display* a),(a),) +SDL_X11_SYM(void,_XFlushGCCache,(Display* a,GC b),(a,b),) +SDL_X11_SYM(int,_XRead,(Display* a,char* b,long c),(a,b,c),return) +SDL_X11_SYM(void,_XReadPad,(Display* a,char* b,long c),(a,b,c),) +SDL_X11_SYM(void,_XSend,(Display* a,_Xconst char* b,long c),(a,b,c),) +SDL_X11_SYM(Status,_XReply,(Display* a,xReply* b,int c,Bool d),(a,b,c,d),return) +SDL_X11_SYM(unsigned long,_XSetLastRequestRead,(Display* a,xGenericReply* b),(a,b),return) +SDL_X11_SYM(SDL_X11_XSynchronizeRetType,XSynchronize,(Display* a,Bool b),(a,b),return) +SDL_X11_SYM(SDL_X11_XESetWireToEventRetType,XESetWireToEvent,(Display* a,int b,SDL_X11_XESetWireToEventRetType c),(a,b,c),return) +SDL_X11_SYM(SDL_X11_XESetEventToWireRetType,XESetEventToWire,(Display* a,int b,SDL_X11_XESetEventToWireRetType c),(a,b,c),return) +SDL_X11_SYM(void,XRefreshKeyboardMapping,(XMappingEvent *a),(a),) +SDL_X11_SYM(int,XQueryTree,(Display* a,Window b,Window* c,Window* d,Window** e,unsigned int* f),(a,b,c,d,e,f),return) +SDL_X11_SYM(Bool,XSupportsLocale,(void),(),return) +SDL_X11_SYM(Status,XmbTextListToTextProperty,(Display* a,char** b,int c,XICCEncodingStyle d,XTextProperty* e),(a,b,c,d,e),return) +SDL_X11_SYM(Region,XCreateRegion,(void),(),return) +SDL_X11_SYM(int,XUnionRectWithRegion,(XRectangle *a, Region b, Region c),(a,b,c), return) +SDL_X11_SYM(void,XDestroyRegion,(Region),(a),) +SDL_X11_SYM(void,XrmInitialize,(void),(),) +SDL_X11_SYM(char*,XResourceManagerString,(Display *display),(display),) +SDL_X11_SYM(XrmDatabase,XrmGetStringDatabase,(char *data),(data),) +SDL_X11_SYM(void,XrmDestroyDatabase,(XrmDatabase db),(db),) +SDL_X11_SYM(Bool,XrmGetResource,(XrmDatabase db, char* str_name, char* str_class, char **str_type_return, XrmValue *),(db, str_name, str_class,str_type_return,value_return),) + +#ifdef SDL_VIDEO_DRIVER_X11_XFIXES +SDL_X11_MODULE(XFIXES) +SDL_X11_SYM(PointerBarrier, XFixesCreatePointerBarrier, (Display* a, Window b, int c, int d, int e, int f, int g, int h, int *i),(a,b,c,d,e,f,g,h,i),return) +SDL_X11_SYM(void, XFixesDestroyPointerBarrier, (Display* a, PointerBarrier b), (a,b),) +SDL_X11_SYM(int, XIBarrierReleasePointer,(Display* a, int b, PointerBarrier c, BarrierEventID d), (a,b,c,d), return) // this is actually Xinput2 +SDL_X11_SYM(Status, XFixesQueryVersion,(Display* a, int* b, int* c), (a,b,c), return) +SDL_X11_SYM(Status, XFixesSelectSelectionInput, (Display* a, Window b, Atom c, unsigned long d), (a,b,c,d), return) +#endif + +#ifdef SDL_VIDEO_DRIVER_X11_XSYNC +SDL_X11_MODULE(XSYNC) +SDL_X11_SYM(Status, XSyncQueryExtension, (Display* a, int* b, int* c), (a, b, c), return) +SDL_X11_SYM(Status, XSyncInitialize, (Display* a, int* b, int* c), (a, b, c), return) +SDL_X11_SYM(XSyncCounter, XSyncCreateCounter, (Display* a, XSyncValue b), (a, b), return) +SDL_X11_SYM(Status, XSyncDestroyCounter, (Display* a, XSyncCounter b), (a, b), return) +SDL_X11_SYM(Status, XSyncSetCounter, (Display* a, XSyncCounter b, XSyncValue c), (a, b, c), return) +#endif + +#ifdef SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS +SDL_X11_SYM(Bool,XGetEventData,(Display* a,XGenericEventCookie* b),(a,b),return) +SDL_X11_SYM(void,XFreeEventData,(Display* a,XGenericEventCookie* b),(a,b),) +#endif + +#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM +SDL_X11_SYM(Bool,XkbQueryExtension,(Display* a,int * b,int * c,int * d,int * e, int *f),(a,b,c,d,e,f),return) +#if NeedWidePrototypes +SDL_X11_SYM(Bool,XkbLookupKeySym,(Display* a, unsigned int b, unsigned int c, unsigned int* d, KeySym* e),(a,b,c,d,e),return) +#else +SDL_X11_SYM(Bool,XkbLookupKeySym,(Display* a, KeyCode b, unsigned int c, unsigned int* d, KeySym* e),(a,b,c,d,e),return) +#endif +SDL_X11_SYM(Status,XkbGetState,(Display* a,unsigned int b,XkbStatePtr c),(a,b,c),return) +SDL_X11_SYM(Status,XkbGetUpdatedMap,(Display* a,unsigned int b,XkbDescPtr c),(a,b,c),return) +SDL_X11_SYM(XkbDescPtr,XkbGetMap,(Display* a,unsigned int b,unsigned int c),(a,b,c),return) +SDL_X11_SYM(void,XkbFreeClientMap,(XkbDescPtr a,unsigned int b, Bool c),(a,b,c),) +SDL_X11_SYM(void,XkbFreeKeyboard,(XkbDescPtr a,unsigned int b, Bool c),(a,b,c),) +SDL_X11_SYM(Bool,XkbSetDetectableAutoRepeat,(Display* a, Bool b, Bool* c),(a,b,c),return) +#endif + +// XKeycodeToKeysym is a deprecated function +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif +#if NeedWidePrototypes +SDL_X11_SYM(KeySym,XKeycodeToKeysym,(Display* a,unsigned int b,int c),(a,b,c),return) +#else +SDL_X11_SYM(KeySym,XKeycodeToKeysym,(Display* a,KeyCode b,int c),(a,b,c),return) +#endif +#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA +#pragma GCC diagnostic pop +#endif + +#ifdef X_HAVE_UTF8_STRING +SDL_X11_MODULE(UTF8) +SDL_X11_SYM(int,Xutf8TextListToTextProperty,(Display* a,char** b,int c,XICCEncodingStyle d,XTextProperty* e),(a,b,c,d,e),return) +SDL_X11_SYM(int,Xutf8LookupString,(XIC a,XKeyPressedEvent* b,char* c,int d,KeySym* e,Status* f),(a,b,c,d,e,f),return) +// SDL_X11_SYM(XIC,XCreateIC,(XIM, ...),return) !!! ARGH! +SDL_X11_SYM(void,XDestroyIC,(XIC a),(a),) +/* SDL_X11_SYM(char*,XGetICValues,(XIC, ...),return) !!! ARGH! */ +/* SDL_X11_SYM(char*,XSetICValues,(XIC, ...),return) !!! ARGH! */ +/* SDL_X11_SYM(XVaNestedList,XVaCreateNestedList,(int, ...),return) !!! ARGH! */ +SDL_X11_SYM(void,XSetICFocus,(XIC a),(a),) +SDL_X11_SYM(void,XUnsetICFocus,(XIC a),(a),) +SDL_X11_SYM(XIM,XOpenIM,(Display* a,struct _XrmHashBucketRec* b,char* c,char* d),(a,b,c,d),return) +SDL_X11_SYM(Status,XCloseIM,(XIM a),(a),return) +SDL_X11_SYM(void,Xutf8DrawString,(Display *a, Drawable b, XFontSet c, GC d, int e, int f, _Xconst char *g, int h),(a,b,c,d,e,f,g,h),) +SDL_X11_SYM(int,Xutf8TextExtents,(XFontSet a, _Xconst char* b, int c, XRectangle* d, XRectangle* e),(a,b,c,d,e),return) +SDL_X11_SYM(char*,XSetLocaleModifiers,(const char *a),(a),return) +SDL_X11_SYM(char*,Xutf8ResetIC,(XIC a),(a),return) +#endif + +#ifndef NO_SHARED_MEMORY +SDL_X11_MODULE(SHM) +SDL_X11_SYM(Status,XShmAttach,(Display* a,XShmSegmentInfo* b),(a,b),return) +SDL_X11_SYM(Status,XShmDetach,(Display* a,XShmSegmentInfo* b),(a,b),return) +SDL_X11_SYM(Status,XShmPutImage,(Display* a,Drawable b,GC c,XImage* d,int e,int f,int g,int h,unsigned int i,unsigned int j,Bool k),(a,b,c,d,e,f,g,h,i,j,k),return) +SDL_X11_SYM(XImage*,XShmCreateImage,(Display* a,Visual* b,unsigned int c,int d,char* e,XShmSegmentInfo* f,unsigned int g,unsigned int h),(a,b,c,d,e,f,g,h),return) +SDL_X11_SYM(Pixmap,XShmCreatePixmap,(Display *a,Drawable b,char* c,XShmSegmentInfo* d, unsigned int e, unsigned int f, unsigned int g),(a,b,c,d,e,f,g),return) +SDL_X11_SYM(Bool,XShmQueryExtension,(Display* a),(a),return) +#endif + +/* + * Not required...these only exist in code in headers on some 64-bit platforms, + * and are removed via macros elsewhere, so it's safe for them to be missing. + */ +#ifdef LONG64 +SDL_X11_MODULE(IO_32BIT) +SDL_X11_SYM(int,_XData32,(Display *dpy,register _Xconst long *data,unsigned len),(dpy,data,len),return) +SDL_X11_SYM(void,_XRead32,(Display *dpy,register long *data,long len),(dpy,data,len),) +#endif + +/* + * These only show up on some variants of Unix. + */ +#ifdef SDL_PLATFORM_OSF +SDL_X11_MODULE(OSF_ENTRY_POINTS) +SDL_X11_SYM(void,_SmtBufferOverflow,(Display *dpy,register smtDisplayPtr p),(dpy,p),) +SDL_X11_SYM(void,_SmtIpError,(Display *dpy,register smtDisplayPtr p,int i),(dpy,p,i),) +SDL_X11_SYM(int,ipAllocateData,(ChannelPtr a,IPCard b,IPDataPtr * c),(a,b,c),return) +SDL_X11_SYM(int,ipUnallocateAndSendData,(ChannelPtr a,IPCard b),(a,b),return) +#endif + +// XCursor support +#ifdef SDL_VIDEO_DRIVER_X11_XCURSOR +SDL_X11_MODULE(XCURSOR) +SDL_X11_SYM(XcursorImage*,XcursorImageCreate,(int a,int b),(a,b),return) +SDL_X11_SYM(void,XcursorImageDestroy,(XcursorImage *a),(a),) +SDL_X11_SYM(Cursor,XcursorImageLoadCursor,(Display *a,const XcursorImage *b),(a,b),return) +SDL_X11_SYM(Cursor,XcursorLibraryLoadCursor,(Display *a, const char *b),(a,b),return) +#endif + +// Xdbe support +#ifdef SDL_VIDEO_DRIVER_X11_XDBE +SDL_X11_MODULE(XDBE) +SDL_X11_SYM(Status,XdbeQueryExtension,(Display *dpy,int *major_version_return,int *minor_version_return),(dpy,major_version_return,minor_version_return),return) +SDL_X11_SYM(XdbeBackBuffer,XdbeAllocateBackBufferName,(Display *dpy,Window window,XdbeSwapAction swap_action),(dpy,window,swap_action),return) +SDL_X11_SYM(Status,XdbeDeallocateBackBufferName,(Display *dpy,XdbeBackBuffer buffer),(dpy,buffer),return) +SDL_X11_SYM(Status,XdbeSwapBuffers,(Display *dpy,XdbeSwapInfo *swap_info,int num_windows),(dpy,swap_info,num_windows),return) +SDL_X11_SYM(Status,XdbeBeginIdiom,(Display *dpy),(dpy),return) +SDL_X11_SYM(Status,XdbeEndIdiom,(Display *dpy),(dpy),return) +SDL_X11_SYM(XdbeScreenVisualInfo*,XdbeGetVisualInfo,(Display *dpy,Drawable *screen_specifiers,int *num_screens),(dpy,screen_specifiers,num_screens),return) +SDL_X11_SYM(void,XdbeFreeVisualInfo,(XdbeScreenVisualInfo *visual_info),(visual_info),) +SDL_X11_SYM(XdbeBackBufferAttributes*,XdbeGetBackBufferAttributes,(Display *dpy,XdbeBackBuffer buffer),(dpy,buffer),return) +#endif + +// XInput2 support for multiple mice, tablets, etc. +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 +SDL_X11_MODULE(XINPUT2) +SDL_X11_SYM(XIDeviceInfo*,XIQueryDevice,(Display *a,int b,int *c),(a,b,c),return) +SDL_X11_SYM(void,XIFreeDeviceInfo,(XIDeviceInfo *a),(a),) +SDL_X11_SYM(int,XISelectEvents,(Display *a,Window b,XIEventMask *c,int d),(a,b,c,d),return) +SDL_X11_SYM(int,XIGrabTouchBegin,(Display *a,int b,Window c,int d,XIEventMask *e,int f,XIGrabModifiers *g),(a,b,c,d,e,f,g),return) +SDL_X11_SYM(int,XIUngrabTouchBegin, (Display *a,int b,Window c, int d,XIGrabModifiers *e),(a, b, c, d, e),return) +SDL_X11_SYM(Status,XIQueryVersion,(Display *a,int *b,int *c),(a,b,c),return) +SDL_X11_SYM(XIEventMask*,XIGetSelectedEvents,(Display *a,Window b,int *c),(a,b,c),return) +SDL_X11_SYM(Bool,XIGetClientPointer,(Display *a,Window b,int *c),(a,b,c),return) +SDL_X11_SYM(Bool,XIWarpPointer,(Display *a,int b,Window c,Window d,double e,double f,int g,int h,double i,double j),(a,b,c,d,e,f,g,h,i,j),return) +SDL_X11_SYM(Status,XIGetProperty,(Display *a,int b,Atom c,long d,long e,Bool f, Atom g, Atom *h, int *i, unsigned long *j, unsigned long *k, unsigned char **l),(a,b,c,d,e,f,g,h,i,j,k,l),return) +#endif + +// XRandR support +#ifdef SDL_VIDEO_DRIVER_X11_XRANDR +SDL_X11_MODULE(XRANDR) +SDL_X11_SYM(Status,XRRQueryVersion,(Display *dpy,int *major_versionp,int *minor_versionp),(dpy,major_versionp,minor_versionp),return) +SDL_X11_SYM(Bool,XRRQueryExtension,(Display *dpy,int *event_base_return,int *error_base_return),(dpy,event_base_return,error_base_return),return) +SDL_X11_SYM(XRRScreenConfiguration *,XRRGetScreenInfo,(Display *dpy,Drawable draw),(dpy,draw),return) +SDL_X11_SYM(SizeID,XRRConfigCurrentConfiguration,(XRRScreenConfiguration *config,Rotation *rotation),(config,rotation),return) +SDL_X11_SYM(short,XRRConfigCurrentRate,(XRRScreenConfiguration *config),(config),return) +SDL_X11_SYM(short *,XRRConfigRates,(XRRScreenConfiguration *config,int sizeID,int *nrates),(config,sizeID,nrates),return) +SDL_X11_SYM(XRRScreenSize *,XRRConfigSizes,(XRRScreenConfiguration *config,int *nsizes),(config,nsizes),return) +SDL_X11_SYM(Status,XRRSetScreenConfigAndRate,(Display *dpy,XRRScreenConfiguration *config,Drawable draw,int size_index,Rotation rotation,short rate,Time timestamp),(dpy,config,draw,size_index,rotation,rate,timestamp),return) +SDL_X11_SYM(void,XRRFreeScreenConfigInfo,(XRRScreenConfiguration *config),(config),) +SDL_X11_SYM(void,XRRSetScreenSize,(Display *dpy, Window window,int width, int height,int mmWidth, int mmHeight),(dpy,window,width,height,mmWidth,mmHeight),) +SDL_X11_SYM(Status,XRRGetScreenSizeRange,(Display *dpy, Window window,int *minWidth, int *minHeight, int *maxWidth, int *maxHeight),(dpy,window,minWidth,minHeight,maxWidth,maxHeight),return) +SDL_X11_SYM(XRRScreenResources *,XRRGetScreenResources,(Display *dpy, Window window),(dpy, window),return) +SDL_X11_SYM(XRRScreenResources *,XRRGetScreenResourcesCurrent,(Display *dpy, Window window),(dpy, window),return) +SDL_X11_SYM(void,XRRFreeScreenResources,(XRRScreenResources *resources),(resources),) +SDL_X11_SYM(XRROutputInfo *,XRRGetOutputInfo,(Display *dpy, XRRScreenResources *resources, RROutput output),(dpy,resources,output),return) +SDL_X11_SYM(void,XRRFreeOutputInfo,(XRROutputInfo *outputInfo),(outputInfo),) +SDL_X11_SYM(XRRCrtcInfo *,XRRGetCrtcInfo,(Display *dpy, XRRScreenResources *resources, RRCrtc crtc),(dpy,resources,crtc),return) +SDL_X11_SYM(void,XRRFreeCrtcInfo,(XRRCrtcInfo *crtcInfo),(crtcInfo),) +SDL_X11_SYM(Status,XRRSetCrtcConfig,(Display *dpy, XRRScreenResources *resources, RRCrtc crtc, Time timestamp, int x, int y, RRMode mode, Rotation rotation, RROutput *outputs, int noutputs),(dpy,resources,crtc,timestamp,x,y,mode,rotation,outputs,noutputs),return) +SDL_X11_SYM(Atom*,XRRListOutputProperties,(Display *dpy, RROutput output, int *nprop),(dpy,output,nprop),return) +SDL_X11_SYM(XRRPropertyInfo*,XRRQueryOutputProperty,(Display *dpy,RROutput output, Atom property),(dpy,output,property),return) +SDL_X11_SYM(int,XRRGetOutputProperty,(Display *dpy,RROutput output, Atom property, long offset, long length, Bool _delete, Bool pending, Atom req_type, Atom *actual_type, int *actual_format, unsigned long *nitems, unsigned long *bytes_after, unsigned char **prop),(dpy,output,property,offset,length, _delete, pending, req_type, actual_type, actual_format, nitems, bytes_after, prop),return) +SDL_X11_SYM(RROutput,XRRGetOutputPrimary,(Display *dpy,Window window),(dpy,window),return) +SDL_X11_SYM(void,XRRSelectInput,(Display *dpy, Window window, int mask),(dpy,window,mask),) +SDL_X11_SYM(Status,XRRGetCrtcTransform,(Display *dpy,RRCrtc crtc,XRRCrtcTransformAttributes **attributes),(dpy,crtc,attributes),return) +#endif + +// MIT-SCREEN-SAVER support +#ifdef SDL_VIDEO_DRIVER_X11_XSCRNSAVER +SDL_X11_MODULE(XSS) +SDL_X11_SYM(Bool,XScreenSaverQueryExtension,(Display *dpy,int *event_base,int *error_base),(dpy,event_base,error_base),return) +SDL_X11_SYM(Status,XScreenSaverQueryVersion,(Display *dpy,int *major_versionp,int *minor_versionp),(dpy,major_versionp,minor_versionp),return) +SDL_X11_SYM(void,XScreenSaverSuspend,(Display *dpy,Bool suspend),(dpy,suspend),return) +#endif + +#ifdef SDL_VIDEO_DRIVER_X11_XSHAPE +SDL_X11_MODULE(XSHAPE) +SDL_X11_SYM(void,XShapeCombineMask,(Display *dpy,Window dest,int dest_kind,int x_off,int y_off,Pixmap src,int op),(dpy,dest,dest_kind,x_off,y_off,src,op),) +SDL_X11_SYM(void,XShapeCombineRegion,(Display *a,Window b,int c,int d,int e,Region f,int g),(a,b,c,d,e,f,g),) +#endif + +#undef SDL_X11_MODULE +#undef SDL_X11_SYM + +/* *INDENT-ON* */ // clang-format on diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11touch.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11touch.c new file mode 100644 index 0000000..5b42c97 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11touch.c @@ -0,0 +1,46 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifdef SDL_VIDEO_DRIVER_X11 + +#include "SDL_x11video.h" +#include "SDL_x11touch.h" +#include "SDL_x11xinput2.h" +#include "../../events/SDL_touch_c.h" + +void X11_InitTouch(SDL_VideoDevice *_this) +{ + X11_InitXinput2Multitouch(_this); +} + +void X11_QuitTouch(SDL_VideoDevice *_this) +{ + SDL_QuitTouch(); +} + +void X11_ResetTouch(SDL_VideoDevice *_this) +{ + X11_QuitTouch(_this); + X11_InitTouch(_this); +} + +#endif // SDL_VIDEO_DRIVER_X11 diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11touch.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11touch.h new file mode 100644 index 0000000..548bf07 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11touch.h @@ -0,0 +1,30 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifndef SDL_x11touch_h_ +#define SDL_x11touch_h_ + +extern void X11_InitTouch(SDL_VideoDevice *_this); +extern void X11_QuitTouch(SDL_VideoDevice *_this); +extern void X11_ResetTouch(SDL_VideoDevice *_this); + +#endif // SDL_x11touch_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11video.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11video.c new file mode 100644 index 0000000..75862db --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11video.c @@ -0,0 +1,505 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifdef SDL_VIDEO_DRIVER_X11 + +#include // For getpid() and readlink() + +#include "../../core/linux/SDL_system_theme.h" +#include "../../events/SDL_keyboard_c.h" +#include "../../events/SDL_mouse_c.h" +#include "../SDL_pixels_c.h" +#include "../SDL_sysvideo.h" + +#include "SDL_x11framebuffer.h" +#include "SDL_x11pen.h" +#include "SDL_x11touch.h" +#include "SDL_x11video.h" +#include "SDL_x11xfixes.h" +#include "SDL_x11xinput2.h" +#include "SDL_x11messagebox.h" +#include "SDL_x11shape.h" +#include "SDL_x11xsync.h" + +#ifdef SDL_VIDEO_OPENGL_EGL +#include "SDL_x11opengles.h" +#endif + +// Initialization/Query functions +static bool X11_VideoInit(SDL_VideoDevice *_this); +static void X11_VideoQuit(SDL_VideoDevice *_this); + +// X11 driver bootstrap functions + +static void X11_DeleteDevice(SDL_VideoDevice *device) +{ + SDL_VideoData *data = device->internal; + if (device->vulkan_config.loader_handle) { + device->Vulkan_UnloadLibrary(device); + } + if (data->display) { + X11_XCloseDisplay(data->display); + } + if (data->request_display) { + X11_XCloseDisplay(data->request_display); + } + SDL_free(data->windowlist); + if (device->wakeup_lock) { + SDL_DestroyMutex(device->wakeup_lock); + } + SDL_free(device->internal); + SDL_free(device); + + SDL_X11_UnloadSymbols(); +} + +static bool X11_IsXWayland(Display *d) +{ + int opcode, event, error; + return X11_XQueryExtension(d, "XWAYLAND", &opcode, &event, &error) == True; +} + +static bool X11_CheckCurrentDesktop(const char *name) +{ + SDL_Environment *env = SDL_GetEnvironment(); + + const char *desktopVar = SDL_GetEnvironmentVariable(env, "DESKTOP_SESSION"); + if (desktopVar && SDL_strcasecmp(desktopVar, name) == 0) { + return true; + } + + desktopVar = SDL_GetEnvironmentVariable(env, "XDG_CURRENT_DESKTOP"); + if (desktopVar && SDL_strcasestr(desktopVar, name)) { + return true; + } + + return false; +} + +static SDL_VideoDevice *X11_CreateDevice(void) +{ + SDL_VideoDevice *device; + SDL_VideoData *data; + const char *display = NULL; // Use the DISPLAY environment variable + Display *x11_display = NULL; + + if (!SDL_X11_LoadSymbols()) { + return NULL; + } + + /* Need for threading gl calls. This is also required for the proprietary + nVidia driver to be threaded. */ + X11_XInitThreads(); + + // Open the display first to be sure that X11 is available + x11_display = X11_XOpenDisplay(display); + + if (!x11_display) { + SDL_X11_UnloadSymbols(); + return NULL; + } + + // Initialize all variables that we clean on shutdown + device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); + if (!device) { + return NULL; + } + data = (struct SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData)); + if (!data) { + SDL_free(device); + return NULL; + } + device->internal = data; + + data->global_mouse_changed = true; + +#ifdef SDL_VIDEO_DRIVER_X11_XFIXES + data->active_cursor_confined_window = NULL; +#endif // SDL_VIDEO_DRIVER_X11_XFIXES + + data->display = x11_display; + data->request_display = X11_XOpenDisplay(display); + if (!data->request_display) { + X11_XCloseDisplay(data->display); + SDL_free(device->internal); + SDL_free(device); + SDL_X11_UnloadSymbols(); + return NULL; + } + + device->wakeup_lock = SDL_CreateMutex(); + +#ifdef X11_DEBUG + X11_XSynchronize(data->display, True); +#endif + + /* Steam Deck will have an on-screen keyboard, so check their environment + * variable so we can make use of SDL_StartTextInput. + */ + data->is_steam_deck = SDL_GetHintBoolean("SteamDeck", false); + + // Set the function pointers + device->VideoInit = X11_VideoInit; + device->VideoQuit = X11_VideoQuit; + device->ResetTouch = X11_ResetTouch; + device->GetDisplayModes = X11_GetDisplayModes; + device->GetDisplayBounds = X11_GetDisplayBounds; + device->GetDisplayUsableBounds = X11_GetDisplayUsableBounds; + device->GetWindowICCProfile = X11_GetWindowICCProfile; + device->SetDisplayMode = X11_SetDisplayMode; + device->SuspendScreenSaver = X11_SuspendScreenSaver; + device->PumpEvents = X11_PumpEvents; + device->WaitEventTimeout = X11_WaitEventTimeout; + device->SendWakeupEvent = X11_SendWakeupEvent; + + device->CreateSDLWindow = X11_CreateWindow; + device->SetWindowTitle = X11_SetWindowTitle; + device->SetWindowIcon = X11_SetWindowIcon; + device->SetWindowPosition = X11_SetWindowPosition; + device->SetWindowSize = X11_SetWindowSize; + device->SetWindowMinimumSize = X11_SetWindowMinimumSize; + device->SetWindowMaximumSize = X11_SetWindowMaximumSize; + device->SetWindowAspectRatio = X11_SetWindowAspectRatio; + device->GetWindowBordersSize = X11_GetWindowBordersSize; + device->SetWindowOpacity = X11_SetWindowOpacity; + device->SetWindowParent = X11_SetWindowParent; + device->SetWindowModal = X11_SetWindowModal; + device->ShowWindow = X11_ShowWindow; + device->HideWindow = X11_HideWindow; + device->RaiseWindow = X11_RaiseWindow; + device->MaximizeWindow = X11_MaximizeWindow; + device->MinimizeWindow = X11_MinimizeWindow; + device->RestoreWindow = X11_RestoreWindow; + device->SetWindowBordered = X11_SetWindowBordered; + device->SetWindowResizable = X11_SetWindowResizable; + device->SetWindowAlwaysOnTop = X11_SetWindowAlwaysOnTop; + device->SetWindowFullscreen = X11_SetWindowFullscreen; + device->SetWindowMouseGrab = X11_SetWindowMouseGrab; + device->SetWindowKeyboardGrab = X11_SetWindowKeyboardGrab; + device->DestroyWindow = X11_DestroyWindow; + device->CreateWindowFramebuffer = X11_CreateWindowFramebuffer; + device->UpdateWindowFramebuffer = X11_UpdateWindowFramebuffer; + device->DestroyWindowFramebuffer = X11_DestroyWindowFramebuffer; + device->SetWindowHitTest = X11_SetWindowHitTest; + device->AcceptDragAndDrop = X11_AcceptDragAndDrop; + device->UpdateWindowShape = X11_UpdateWindowShape; + device->FlashWindow = X11_FlashWindow; + device->ShowWindowSystemMenu = X11_ShowWindowSystemMenu; + device->SetWindowFocusable = X11_SetWindowFocusable; + device->SyncWindow = X11_SyncWindow; + +#ifdef SDL_VIDEO_DRIVER_X11_XFIXES + device->SetWindowMouseRect = X11_SetWindowMouseRect; +#endif // SDL_VIDEO_DRIVER_X11_XFIXES + +#ifdef SDL_VIDEO_OPENGL_GLX + device->GL_LoadLibrary = X11_GL_LoadLibrary; + device->GL_GetProcAddress = X11_GL_GetProcAddress; + device->GL_UnloadLibrary = X11_GL_UnloadLibrary; + device->GL_CreateContext = X11_GL_CreateContext; + device->GL_MakeCurrent = X11_GL_MakeCurrent; + device->GL_SetSwapInterval = X11_GL_SetSwapInterval; + device->GL_GetSwapInterval = X11_GL_GetSwapInterval; + device->GL_SwapWindow = X11_GL_SwapWindow; + device->GL_DestroyContext = X11_GL_DestroyContext; + device->GL_GetEGLSurface = NULL; +#endif +#ifdef SDL_VIDEO_OPENGL_EGL +#ifdef SDL_VIDEO_OPENGL_GLX + if (SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false)) { +#endif + device->GL_LoadLibrary = X11_GLES_LoadLibrary; + device->GL_GetProcAddress = X11_GLES_GetProcAddress; + device->GL_UnloadLibrary = X11_GLES_UnloadLibrary; + device->GL_CreateContext = X11_GLES_CreateContext; + device->GL_MakeCurrent = X11_GLES_MakeCurrent; + device->GL_SetSwapInterval = X11_GLES_SetSwapInterval; + device->GL_GetSwapInterval = X11_GLES_GetSwapInterval; + device->GL_SwapWindow = X11_GLES_SwapWindow; + device->GL_DestroyContext = X11_GLES_DestroyContext; + device->GL_GetEGLSurface = X11_GLES_GetEGLSurface; +#ifdef SDL_VIDEO_OPENGL_GLX + } +#endif +#endif + + device->GetTextMimeTypes = X11_GetTextMimeTypes; + device->SetClipboardData = X11_SetClipboardData; + device->GetClipboardData = X11_GetClipboardData; + device->HasClipboardData = X11_HasClipboardData; + device->SetPrimarySelectionText = X11_SetPrimarySelectionText; + device->GetPrimarySelectionText = X11_GetPrimarySelectionText; + device->HasPrimarySelectionText = X11_HasPrimarySelectionText; + device->StartTextInput = X11_StartTextInput; + device->StopTextInput = X11_StopTextInput; + device->UpdateTextInputArea = X11_UpdateTextInputArea; + device->HasScreenKeyboardSupport = X11_HasScreenKeyboardSupport; + device->ShowScreenKeyboard = X11_ShowScreenKeyboard; + device->HideScreenKeyboard = X11_HideScreenKeyboard; + device->IsScreenKeyboardShown = X11_IsScreenKeyboardShown; + + device->free = X11_DeleteDevice; + +#ifdef SDL_VIDEO_VULKAN + device->Vulkan_LoadLibrary = X11_Vulkan_LoadLibrary; + device->Vulkan_UnloadLibrary = X11_Vulkan_UnloadLibrary; + device->Vulkan_GetInstanceExtensions = X11_Vulkan_GetInstanceExtensions; + device->Vulkan_CreateSurface = X11_Vulkan_CreateSurface; + device->Vulkan_DestroySurface = X11_Vulkan_DestroySurface; + device->Vulkan_GetPresentationSupport = X11_Vulkan_GetPresentationSupport; +#endif + +#ifdef SDL_USE_LIBDBUS + if (SDL_SystemTheme_Init()) + device->system_theme = SDL_SystemTheme_Get(); +#endif + + device->device_caps = VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT; + + /* Openbox doesn't send the new window dimensions when entering fullscreen, so the events must be synthesized. + * This is otherwise not wanted, as it can break fullscreen window positioning on multi-monitor configurations. + */ + if (!X11_CheckCurrentDesktop("openbox")) { + device->device_caps |= VIDEO_DEVICE_CAPS_SENDS_DISPLAY_CHANGES; + } + + data->is_xwayland = X11_IsXWayland(x11_display); + if (data->is_xwayland) { + device->device_caps |= VIDEO_DEVICE_CAPS_MODE_SWITCHING_EMULATED | + VIDEO_DEVICE_CAPS_DISABLE_MOUSE_WARP_ON_FULLSCREEN_TRANSITIONS; + } + + return device; +} + +VideoBootStrap X11_bootstrap = { + "x11", "SDL X11 video driver", + X11_CreateDevice, + X11_ShowMessageBox, + false +}; + +static int (*handler)(Display *, XErrorEvent *) = NULL; +static int X11_CheckWindowManagerErrorHandler(Display *d, XErrorEvent *e) +{ + if (e->error_code == BadWindow) { + return 0; + } else { + return handler(d, e); + } +} + +static void X11_CheckWindowManager(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + Display *display = data->display; + Atom _NET_SUPPORTING_WM_CHECK; + int status, real_format; + Atom real_type; + unsigned long items_read = 0, items_left = 0; + unsigned char *propdata = NULL; + Window wm_window = 0; +#ifdef DEBUG_WINDOW_MANAGER + char *wm_name; +#endif + + // Set up a handler to gracefully catch errors + X11_XSync(display, False); + handler = X11_XSetErrorHandler(X11_CheckWindowManagerErrorHandler); + + _NET_SUPPORTING_WM_CHECK = X11_XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", False); + status = X11_XGetWindowProperty(display, DefaultRootWindow(display), _NET_SUPPORTING_WM_CHECK, 0L, 1L, False, XA_WINDOW, &real_type, &real_format, &items_read, &items_left, &propdata); + if (status == Success) { + if (items_read) { + wm_window = ((Window *)propdata)[0]; + } + if (propdata) { + X11_XFree(propdata); + propdata = NULL; + } + } + + if (wm_window) { + status = X11_XGetWindowProperty(display, wm_window, _NET_SUPPORTING_WM_CHECK, 0L, 1L, False, XA_WINDOW, &real_type, &real_format, &items_read, &items_left, &propdata); + if (status != Success || !items_read || wm_window != ((Window *)propdata)[0]) { + wm_window = None; + } + if (status == Success && propdata) { + X11_XFree(propdata); + propdata = NULL; + } + } + + // Reset the error handler, we're done checking + X11_XSync(display, False); + X11_XSetErrorHandler(handler); + + if (!wm_window) { +#ifdef DEBUG_WINDOW_MANAGER + printf("Couldn't get _NET_SUPPORTING_WM_CHECK property\n"); +#endif + return; + } + data->net_wm = true; + +#ifdef DEBUG_WINDOW_MANAGER + wm_name = X11_GetWindowTitle(_this, wm_window); + printf("Window manager: %s\n", wm_name); + SDL_free(wm_name); +#endif +} + +static bool X11_VideoInit(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + + // Get the process PID to be associated to the window + data->pid = getpid(); + + // I have no idea how random this actually is, or has to be. + data->window_group = (XID)(((size_t)data->pid) ^ ((size_t)_this)); + + // Look up some useful Atoms +#define GET_ATOM(X) data->atoms.X = X11_XInternAtom(data->display, #X, False) + GET_ATOM(WM_PROTOCOLS); + GET_ATOM(WM_DELETE_WINDOW); + GET_ATOM(WM_TAKE_FOCUS); + GET_ATOM(WM_NAME); + GET_ATOM(WM_TRANSIENT_FOR); + GET_ATOM(_NET_WM_STATE); + GET_ATOM(_NET_WM_STATE_HIDDEN); + GET_ATOM(_NET_WM_STATE_FOCUSED); + GET_ATOM(_NET_WM_STATE_MAXIMIZED_VERT); + GET_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ); + GET_ATOM(_NET_WM_STATE_FULLSCREEN); + GET_ATOM(_NET_WM_STATE_ABOVE); + GET_ATOM(_NET_WM_STATE_SKIP_TASKBAR); + GET_ATOM(_NET_WM_STATE_SKIP_PAGER); + GET_ATOM(_NET_WM_MOVERESIZE); + GET_ATOM(_NET_WM_STATE_MODAL); + GET_ATOM(_NET_WM_ALLOWED_ACTIONS); + GET_ATOM(_NET_WM_ACTION_FULLSCREEN); + GET_ATOM(_NET_WM_NAME); + GET_ATOM(_NET_WM_ICON_NAME); + GET_ATOM(_NET_WM_ICON); + GET_ATOM(_NET_WM_PING); + GET_ATOM(_NET_WM_SYNC_REQUEST); + GET_ATOM(_NET_WM_SYNC_REQUEST_COUNTER); + GET_ATOM(_NET_WM_WINDOW_OPACITY); + GET_ATOM(_NET_WM_USER_TIME); + GET_ATOM(_NET_ACTIVE_WINDOW); + GET_ATOM(_NET_FRAME_EXTENTS); + GET_ATOM(_SDL_WAKEUP); + GET_ATOM(UTF8_STRING); + GET_ATOM(PRIMARY); + GET_ATOM(CLIPBOARD); + GET_ATOM(INCR); + GET_ATOM(SDL_SELECTION); + GET_ATOM(TARGETS); + GET_ATOM(SDL_FORMATS); + GET_ATOM(XdndAware); + GET_ATOM(XdndEnter); + GET_ATOM(XdndLeave); + GET_ATOM(XdndPosition); + GET_ATOM(XdndStatus); + GET_ATOM(XdndTypeList); + GET_ATOM(XdndActionCopy); + GET_ATOM(XdndDrop); + GET_ATOM(XdndFinished); + GET_ATOM(XdndSelection); + GET_ATOM(XKLAVIER_STATE); + + // Detect the window manager + X11_CheckWindowManager(_this); + + if (!X11_InitModes(_this)) { + return false; + } + + if (!X11_InitXinput2(_this)) { + // Assume a mouse and keyboard are attached + SDL_AddKeyboard(SDL_DEFAULT_KEYBOARD_ID, NULL, false); + SDL_AddMouse(SDL_DEFAULT_MOUSE_ID, NULL, false); + } + +#ifdef SDL_VIDEO_DRIVER_X11_XFIXES + X11_InitXfixes(_this); +#endif // SDL_VIDEO_DRIVER_X11_XFIXES + + X11_InitXsettings(_this); + +#ifdef SDL_VIDEO_DRIVER_X11_XSYNC + X11_InitXsync(_this); +#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */ + +#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 + + if (!X11_InitKeyboard(_this)) { + return false; + } + X11_InitMouse(_this); + + X11_InitTouch(_this); + + X11_InitPen(_this); + + return true; +} + +void X11_VideoQuit(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + + if (data->clipboard_window) { + 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); + } +#endif + + X11_QuitModes(_this); + X11_QuitKeyboard(_this); + X11_QuitMouse(_this); + X11_QuitTouch(_this); + X11_QuitPen(_this); + X11_QuitClipboard(_this); + X11_QuitXsettings(_this); +} + +bool X11_UseDirectColorVisuals(void) +{ + if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_NODIRECTCOLOR, false)) { + return false; + } + return true; +} + +#endif // SDL_VIDEO_DRIVER_X11 diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11video.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11video.h new file mode 100644 index 0000000..a336a80 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11video.h @@ -0,0 +1,177 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifndef SDL_x11video_h_ +#define SDL_x11video_h_ + +#include "../SDL_sysvideo.h" + +#include "../../core/linux/SDL_dbus.h" +#include "../../core/linux/SDL_ime.h" + +#include "SDL_x11dyn.h" + +#include "SDL_x11clipboard.h" +#include "SDL_x11events.h" +#include "SDL_x11keyboard.h" +#include "SDL_x11modes.h" +#include "SDL_x11mouse.h" +#include "SDL_x11opengl.h" +#include "SDL_x11settings.h" +#include "SDL_x11window.h" +#include "SDL_x11vulkan.h" + +// Private display data + +struct SDL_VideoData +{ + Display *display; + Display *request_display; + pid_t pid; + XIM im; + Uint64 screensaver_activity; + int numwindows; + SDL_WindowData **windowlist; + int windowlistlength; + XID window_group; + Window clipboard_window; + SDLX11_ClipboardData clipboard; + SDLX11_ClipboardData primary_selection; +#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 + bool net_wm; + + // Useful atoms + struct { + Atom WM_PROTOCOLS; + Atom WM_DELETE_WINDOW; + Atom WM_TAKE_FOCUS; + Atom WM_NAME; + Atom WM_TRANSIENT_FOR; + Atom _NET_WM_STATE; + Atom _NET_WM_STATE_HIDDEN; + Atom _NET_WM_STATE_FOCUSED; + Atom _NET_WM_STATE_MAXIMIZED_VERT; + Atom _NET_WM_STATE_MAXIMIZED_HORZ; + Atom _NET_WM_STATE_FULLSCREEN; + Atom _NET_WM_STATE_ABOVE; + Atom _NET_WM_STATE_SKIP_TASKBAR; + Atom _NET_WM_STATE_SKIP_PAGER; + Atom _NET_WM_STATE_MODAL; + Atom _NET_WM_MOVERESIZE; + Atom _NET_WM_ALLOWED_ACTIONS; + Atom _NET_WM_ACTION_FULLSCREEN; + Atom _NET_WM_NAME; + Atom _NET_WM_ICON_NAME; + Atom _NET_WM_ICON; + Atom _NET_WM_PING; + Atom _NET_WM_SYNC_REQUEST; + Atom _NET_WM_SYNC_REQUEST_COUNTER; + Atom _NET_WM_WINDOW_OPACITY; + Atom _NET_WM_USER_TIME; + Atom _NET_ACTIVE_WINDOW; + Atom _NET_FRAME_EXTENTS; + Atom _SDL_WAKEUP; + Atom UTF8_STRING; + Atom PRIMARY; + Atom CLIPBOARD; + Atom INCR; + Atom SDL_SELECTION; + Atom TARGETS; + Atom SDL_FORMATS; + Atom XdndAware; + Atom XdndEnter; + Atom XdndLeave; + Atom XdndPosition; + Atom XdndStatus; + Atom XdndTypeList; + Atom XdndActionCopy; + Atom XdndDrop; + Atom XdndFinished; + Atom XdndSelection; + Atom XKLAVIER_STATE; + + // Pen atoms (these have names that don't map well to C symbols) + Atom pen_atom_device_product_id; + Atom pen_atom_abs_pressure; + Atom pen_atom_abs_tilt_x; + Atom pen_atom_abs_tilt_y; + Atom pen_atom_wacom_serial_ids; + Atom pen_atom_wacom_tool_type; + } atoms; + + SDL_Scancode key_layout[256]; + bool selection_waiting; + bool selection_incr_waiting; + + bool broken_pointer_grab; // true if XGrabPointer seems unreliable. + + Uint64 last_mode_change_deadline; + + bool global_mouse_changed; + SDL_Point global_mouse_position; + Uint32 global_mouse_buttons; + + SDL_XInput2DeviceInfo *mouse_device_info; + int xinput_master_pointer_device; + bool xinput_hierarchy_changed; + + int xrandr_event_base; + struct + { +#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM + XkbDescPtr desc_ptr; +#endif + int event; + unsigned int current_group; + unsigned int xkb_modifiers; + + SDL_Keymod sdl_modifiers; + + Uint32 numlock_mask; + Uint32 scrolllock_mask; + } xkb; + + KeyCode filter_code; + Time filter_time; + +#ifdef SDL_VIDEO_VULKAN + // Vulkan variables only valid if _this->vulkan_config.loader_handle is not NULL + SDL_SharedObject *vulkan_xlib_xcb_library; + PFN_XGetXCBConnection vulkan_XGetXCBConnection; +#endif + + // Used to interact with the on-screen keyboard + bool is_steam_deck; + bool steam_keyboard_open; + + bool is_xwayland; +}; + +extern bool X11_UseDirectColorVisuals(void); + +#endif // SDL_x11video_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11vulkan.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11vulkan.c new file mode 100644 index 0000000..065549b --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11vulkan.c @@ -0,0 +1,290 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_X11) + +#include "../SDL_vulkan_internal.h" + +#include "SDL_x11video.h" + +#include "SDL_x11vulkan.h" + +#include +// #include + +#ifdef SDL_PLATFORM_OPENBSD +#define DEFAULT_VULKAN "libvulkan.so" +#define DEFAULT_X11_XCB "libX11-xcb.so" +#else +#define DEFAULT_VULKAN "libvulkan.so.1" +#define DEFAULT_X11_XCB "libX11-xcb.so.1" +#endif + +/* +typedef uint32_t xcb_window_t; +typedef uint32_t xcb_visualid_t; +*/ + +bool X11_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) +{ + SDL_VideoData *videoData = _this->internal; + VkExtensionProperties *extensions = NULL; + Uint32 extensionCount = 0; + bool hasSurfaceExtension = false; + bool hasXlibSurfaceExtension = false; + bool hasXCBSurfaceExtension = false; + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL; + Uint32 i; + if (_this->vulkan_config.loader_handle) { + return SDL_SetError("Vulkan already loaded"); + } + + // Load the Vulkan loader library + if (!path) { + path = SDL_GetHint(SDL_HINT_VULKAN_LIBRARY); + } + if (!path) { + path = DEFAULT_VULKAN; + } + _this->vulkan_config.loader_handle = SDL_LoadObject(path); + if (!_this->vulkan_config.loader_handle) { + return false; + } + SDL_strlcpy(_this->vulkan_config.loader_path, path, SDL_arraysize(_this->vulkan_config.loader_path)); + vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_LoadFunction( + _this->vulkan_config.loader_handle, "vkGetInstanceProcAddr"); + if (!vkGetInstanceProcAddr) { + goto fail; + } + _this->vulkan_config.vkGetInstanceProcAddr = (void *)vkGetInstanceProcAddr; + _this->vulkan_config.vkEnumerateInstanceExtensionProperties = + (void *)((PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr)( + VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"); + if (!_this->vulkan_config.vkEnumerateInstanceExtensionProperties) { + goto fail; + } + extensions = SDL_Vulkan_CreateInstanceExtensionsList( + (PFN_vkEnumerateInstanceExtensionProperties) + _this->vulkan_config.vkEnumerateInstanceExtensionProperties, + &extensionCount); + if (!extensions) { + goto fail; + } + for (i = 0; i < extensionCount; i++) { + if (SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) { + hasSurfaceExtension = true; + } else if (SDL_strcmp(VK_KHR_XCB_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) { + hasXCBSurfaceExtension = true; + } else if (SDL_strcmp(VK_KHR_XLIB_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) { + hasXlibSurfaceExtension = true; + } + } + SDL_free(extensions); + if (!hasSurfaceExtension) { + SDL_SetError("Installed Vulkan doesn't implement the " VK_KHR_SURFACE_EXTENSION_NAME " extension"); + goto fail; + } + if (hasXlibSurfaceExtension) { + videoData->vulkan_xlib_xcb_library = NULL; + } else if (!hasXCBSurfaceExtension) { + SDL_SetError("Installed Vulkan doesn't implement either the " VK_KHR_XCB_SURFACE_EXTENSION_NAME "extension or the " VK_KHR_XLIB_SURFACE_EXTENSION_NAME " extension"); + goto fail; + } else { + const char *libX11XCBLibraryName = SDL_GetHint(SDL_HINT_X11_XCB_LIBRARY); + if (!libX11XCBLibraryName || !*libX11XCBLibraryName) { + libX11XCBLibraryName = DEFAULT_X11_XCB; + } + videoData->vulkan_xlib_xcb_library = SDL_LoadObject(libX11XCBLibraryName); + if (!videoData->vulkan_xlib_xcb_library) { + goto fail; + } + videoData->vulkan_XGetXCBConnection = + (PFN_XGetXCBConnection)SDL_LoadFunction(videoData->vulkan_xlib_xcb_library, "XGetXCBConnection"); + if (!videoData->vulkan_XGetXCBConnection) { + SDL_UnloadObject(videoData->vulkan_xlib_xcb_library); + goto fail; + } + } + return true; + +fail: + SDL_UnloadObject(_this->vulkan_config.loader_handle); + _this->vulkan_config.loader_handle = NULL; + return false; +} + +void X11_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) +{ + SDL_VideoData *videoData = _this->internal; + if (_this->vulkan_config.loader_handle) { + if (videoData->vulkan_xlib_xcb_library) { + SDL_UnloadObject(videoData->vulkan_xlib_xcb_library); + } + SDL_UnloadObject(_this->vulkan_config.loader_handle); + _this->vulkan_config.loader_handle = NULL; + } +} + +char const* const* X11_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count) +{ + SDL_VideoData *videoData = _this->internal; + if (videoData->vulkan_xlib_xcb_library) { + static const char *const extensionsForXCB[] = { + VK_KHR_SURFACE_EXTENSION_NAME, + VK_KHR_XCB_SURFACE_EXTENSION_NAME, + }; + if(count) { + *count = SDL_arraysize(extensionsForXCB); + } + return extensionsForXCB; + } else { + static const char *const extensionsForXlib[] = { + VK_KHR_SURFACE_EXTENSION_NAME, + VK_KHR_XLIB_SURFACE_EXTENSION_NAME, + }; + if(count) { + *count = SDL_arraysize(extensionsForXlib); + } + return extensionsForXlib; + } +} + +bool X11_Vulkan_CreateSurface(SDL_VideoDevice *_this, + SDL_Window *window, + VkInstance instance, + const struct VkAllocationCallbacks *allocator, + VkSurfaceKHR *surface) +{ + SDL_VideoData *videoData = _this->internal; + SDL_WindowData *windowData = window->internal; + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; + if (!_this->vulkan_config.loader_handle) { + return SDL_SetError("Vulkan is not loaded"); + } + vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr; + if (videoData->vulkan_xlib_xcb_library) { + PFN_vkCreateXcbSurfaceKHR vkCreateXcbSurfaceKHR = + (PFN_vkCreateXcbSurfaceKHR)vkGetInstanceProcAddr(instance, + "vkCreateXcbSurfaceKHR"); + VkXcbSurfaceCreateInfoKHR createInfo; + VkResult result; + if (!vkCreateXcbSurfaceKHR) { + return SDL_SetError(VK_KHR_XCB_SURFACE_EXTENSION_NAME " extension is not enabled in the Vulkan instance."); + } + SDL_zero(createInfo); + createInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; + createInfo.connection = videoData->vulkan_XGetXCBConnection(videoData->display); + if (!createInfo.connection) { + return SDL_SetError("XGetXCBConnection failed"); + } + createInfo.window = (xcb_window_t)windowData->xwindow; + result = vkCreateXcbSurfaceKHR(instance, &createInfo, allocator, surface); + if (result != VK_SUCCESS) { + return SDL_SetError("vkCreateXcbSurfaceKHR failed: %s", SDL_Vulkan_GetResultString(result)); + } + } else { + PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR = + (PFN_vkCreateXlibSurfaceKHR)vkGetInstanceProcAddr(instance, + "vkCreateXlibSurfaceKHR"); + VkXlibSurfaceCreateInfoKHR createInfo; + VkResult result; + if (!vkCreateXlibSurfaceKHR) { + return SDL_SetError(VK_KHR_XLIB_SURFACE_EXTENSION_NAME " extension is not enabled in the Vulkan instance."); + } + SDL_zero(createInfo); + createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; + createInfo.dpy = videoData->display; + createInfo.window = (xcb_window_t)windowData->xwindow; + result = vkCreateXlibSurfaceKHR(instance, &createInfo, allocator, surface); + if (result != VK_SUCCESS) { + return SDL_SetError("vkCreateXlibSurfaceKHR failed: %s", SDL_Vulkan_GetResultString(result)); + } + } + + return true; // success! +} + +void X11_Vulkan_DestroySurface(SDL_VideoDevice *_this, + VkInstance instance, + VkSurfaceKHR surface, + const struct VkAllocationCallbacks *allocator) +{ + if (_this->vulkan_config.loader_handle) { + SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator); + } +} + +bool X11_Vulkan_GetPresentationSupport(SDL_VideoDevice *_this, + VkInstance instance, + VkPhysicalDevice physicalDevice, + Uint32 queueFamilyIndex) +{ + SDL_VideoData *videoData = _this->internal; + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; + const char *forced_visual_id; + VisualID visualid; + + if (!_this->vulkan_config.loader_handle) { + return SDL_SetError("Vulkan is not loaded"); + } + vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr; + + forced_visual_id = SDL_GetHint(SDL_HINT_VIDEO_X11_WINDOW_VISUALID); + if (forced_visual_id) { + visualid = SDL_strtol(forced_visual_id, NULL, 0); + } else { + visualid = X11_XVisualIDFromVisual(DefaultVisual(videoData->display, DefaultScreen(videoData->display))); + } + + if (videoData->vulkan_xlib_xcb_library) { + PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR vkGetPhysicalDeviceXcbPresentationSupportKHR = + (PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)vkGetInstanceProcAddr( + instance, + "vkGetPhysicalDeviceXcbPresentationSupportKHR"); + + if (!vkGetPhysicalDeviceXcbPresentationSupportKHR) { + return SDL_SetError(VK_KHR_XCB_SURFACE_EXTENSION_NAME " extension is not enabled in the Vulkan instance."); + } + + return vkGetPhysicalDeviceXcbPresentationSupportKHR(physicalDevice, + queueFamilyIndex, + videoData->vulkan_XGetXCBConnection(videoData->display), + visualid); + } else { + PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR = + (PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR)vkGetInstanceProcAddr( + instance, + "vkGetPhysicalDeviceXlibPresentationSupportKHR"); + + if (!vkGetPhysicalDeviceXlibPresentationSupportKHR) { + return SDL_SetError(VK_KHR_XLIB_SURFACE_EXTENSION_NAME " extension is not enabled in the Vulkan instance."); + } + + return vkGetPhysicalDeviceXlibPresentationSupportKHR(physicalDevice, + queueFamilyIndex, + videoData->display, + visualid); + } +} + +#endif diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11vulkan.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11vulkan.h new file mode 100644 index 0000000..2a62596 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11vulkan.h @@ -0,0 +1,52 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifndef SDL_x11vulkan_h_ +#define SDL_x11vulkan_h_ + +#include + +#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_X11) + +typedef struct xcb_connection_t xcb_connection_t; +typedef xcb_connection_t *(*PFN_XGetXCBConnection)(Display *dpy); + +extern bool X11_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); +extern void X11_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); +extern char const* const* X11_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); +extern bool X11_Vulkan_CreateSurface(SDL_VideoDevice *_this, + SDL_Window *window, + VkInstance instance, + const struct VkAllocationCallbacks *allocator, + VkSurfaceKHR *surface); +extern void X11_Vulkan_DestroySurface(SDL_VideoDevice *_this, + VkInstance instance, + VkSurfaceKHR surface, + const struct VkAllocationCallbacks *allocator); +extern bool X11_Vulkan_GetPresentationSupport(SDL_VideoDevice *_this, + VkInstance instance, + VkPhysicalDevice physicalDevice, + Uint32 queueFamilyIndex); + +#endif + +#endif // SDL_x11vulkan_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11window.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11window.c new file mode 100644 index 0000000..5562053 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11window.c @@ -0,0 +1,2243 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifdef SDL_VIDEO_DRIVER_X11 + +#include "../SDL_sysvideo.h" +#include "../SDL_pixels_c.h" +#include "../../events/SDL_keyboard_c.h" +#include "../../events/SDL_mouse_c.h" +#include "../../events/SDL_events_c.h" +#include "../../core/unix/SDL_appid.h" + +#include "SDL_x11video.h" +#include "SDL_x11mouse.h" +#include "SDL_x11xinput2.h" +#include "SDL_x11xfixes.h" + +#ifdef SDL_VIDEO_OPENGL_EGL +#include "SDL_x11opengles.h" +#endif + +#include "SDL_x11xsync.h" + +#define _NET_WM_STATE_REMOVE 0l +#define _NET_WM_STATE_ADD 1l + +#define CHECK_WINDOW_DATA(window) \ + if (!window) { \ + return SDL_SetError("Invalid window"); \ + } \ + if (!window->internal) { \ + return SDL_SetError("Invalid window driver data"); \ + } + +#define CHECK_DISPLAY_DATA(display) \ + if (!_display) { \ + return SDL_SetError("Invalid display"); \ + } \ + if (!_display->internal) { \ + return SDL_SetError("Invalid display driver data"); \ + } + +static Bool isMapNotify(Display *dpy, XEvent *ev, XPointer win) // NOLINT(readability-non-const-parameter): cannot make XPointer a const pointer due to typedef +{ + return ev->type == MapNotify && ev->xmap.window == *((Window *)win); +} +static Bool isUnmapNotify(Display *dpy, XEvent *ev, XPointer win) // NOLINT(readability-non-const-parameter): cannot make XPointer a const pointer due to typedef +{ + return ev->type == UnmapNotify && ev->xunmap.window == *((Window *)win); +} + +/* +static Bool isConfigureNotify(Display *dpy, XEvent *ev, XPointer win) +{ + return ev->type == ConfigureNotify && ev->xconfigure.window == *((Window*)win); +} +static Bool X11_XIfEventTimeout(Display *display, XEvent *event_return, Bool (*predicate)(), XPointer arg, int timeoutMS) +{ + Uint64 start = SDL_GetTicks(); + + while (!X11_XCheckIfEvent(display, event_return, predicate, arg)) { + if (SDL_GetTicks() >= (start + timeoutMS)) { + return False; + } + } + return True; +} +*/ + +static bool X11_IsWindowMapped(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + SDL_VideoData *videodata = _this->internal; + XWindowAttributes attr; + + X11_XGetWindowAttributes(videodata->display, data->xwindow, &attr); + if (attr.map_state != IsUnmapped) { + return true; + } else { + return false; + } +} + +static bool X11_IsDisplayOk(Display *display) +{ + if (display->flags & XlibDisplayIOError) { + return false; + } + return true; +} + +#if 0 +static bool X11_IsActionAllowed(SDL_Window *window, Atom action) +{ + SDL_WindowData *data = window->internal; + Atom _NET_WM_ALLOWED_ACTIONS = data->videodata->_NET_WM_ALLOWED_ACTIONS; + Atom type; + Display *display = data->videodata->display; + int form; + unsigned long remain; + unsigned long len, i; + Atom *list; + bool ret = false; + + if (X11_XGetWindowProperty(display, data->xwindow, _NET_WM_ALLOWED_ACTIONS, 0, 1024, False, XA_ATOM, &type, &form, &len, &remain, (unsigned char **)&list) == Success) { + for (i=0; ilast_position_pending; + const bool last_size_pending = window->last_size_pending; + + X11_SyncWindow(_this, window); + + window->last_position_pending = last_position_pending; + window->last_size_pending = last_size_pending; +} + +void X11_SetNetWMState(SDL_VideoDevice *_this, Window xwindow, SDL_WindowFlags flags) +{ + SDL_VideoData *videodata = _this->internal; + Display *display = videodata->display; + // !!! FIXME: just dereference videodata below instead of copying to locals. + Atom _NET_WM_STATE = videodata->atoms._NET_WM_STATE; + // Atom _NET_WM_STATE_HIDDEN = videodata->atoms._NET_WM_STATE_HIDDEN; + Atom _NET_WM_STATE_FOCUSED = videodata->atoms._NET_WM_STATE_FOCUSED; + Atom _NET_WM_STATE_MAXIMIZED_VERT = videodata->atoms._NET_WM_STATE_MAXIMIZED_VERT; + Atom _NET_WM_STATE_MAXIMIZED_HORZ = videodata->atoms._NET_WM_STATE_MAXIMIZED_HORZ; + Atom _NET_WM_STATE_FULLSCREEN = videodata->atoms._NET_WM_STATE_FULLSCREEN; + Atom _NET_WM_STATE_ABOVE = videodata->atoms._NET_WM_STATE_ABOVE; + Atom _NET_WM_STATE_SKIP_TASKBAR = videodata->atoms._NET_WM_STATE_SKIP_TASKBAR; + Atom _NET_WM_STATE_SKIP_PAGER = videodata->atoms._NET_WM_STATE_SKIP_PAGER; + Atom _NET_WM_STATE_MODAL = videodata->atoms._NET_WM_STATE_MODAL; + Atom atoms[16]; + int count = 0; + + /* The window manager sets this property, we shouldn't set it. + If we did, this would indicate to the window manager that we don't + actually want to be mapped during X11_XMapRaised(), which would be bad. + * + if ((flags & SDL_WINDOW_HIDDEN) != 0) { + atoms[count++] = _NET_WM_STATE_HIDDEN; + } + */ + + if (flags & SDL_WINDOW_ALWAYS_ON_TOP) { + atoms[count++] = _NET_WM_STATE_ABOVE; + } + if (flags & SDL_WINDOW_UTILITY) { + atoms[count++] = _NET_WM_STATE_SKIP_TASKBAR; + atoms[count++] = _NET_WM_STATE_SKIP_PAGER; + } + if (flags & SDL_WINDOW_INPUT_FOCUS) { + atoms[count++] = _NET_WM_STATE_FOCUSED; + } + if (flags & SDL_WINDOW_MAXIMIZED) { + atoms[count++] = _NET_WM_STATE_MAXIMIZED_VERT; + atoms[count++] = _NET_WM_STATE_MAXIMIZED_HORZ; + } + if (flags & SDL_WINDOW_FULLSCREEN) { + atoms[count++] = _NET_WM_STATE_FULLSCREEN; + } + if (flags & SDL_WINDOW_MODAL) { + atoms[count++] = _NET_WM_STATE_MODAL; + } + + SDL_assert(count <= SDL_arraysize(atoms)); + + if (count > 0) { + X11_XChangeProperty(display, xwindow, _NET_WM_STATE, XA_ATOM, 32, + PropModeReplace, (unsigned char *)atoms, count); + } else { + X11_XDeleteProperty(display, xwindow, _NET_WM_STATE); + } +} + +static void X11_ConstrainPopup(SDL_Window *window, bool output_to_pending) +{ + // Clamp popup windows to the output borders + if (SDL_WINDOW_IS_POPUP(window)) { + SDL_Window *w; + SDL_DisplayID displayID; + SDL_Rect rect; + int abs_x = window->last_position_pending ? window->pending.x : window->floating.x; + int abs_y = window->last_position_pending ? window->pending.y : window->floating.y; + int offset_x = 0, offset_y = 0; + + // Calculate the total offset from the parents + for (w = window->parent; SDL_WINDOW_IS_POPUP(w); w = w->parent) { + offset_x += w->x; + offset_y += w->y; + } + + offset_x += w->x; + offset_y += w->y; + abs_x += offset_x; + abs_y += offset_y; + + displayID = SDL_GetDisplayForWindow(w); + + SDL_GetDisplayBounds(displayID, &rect); + if (abs_x + window->w > rect.x + rect.w) { + abs_x -= (abs_x + window->w) - (rect.x + rect.w); + } + if (abs_y + window->h > rect.y + rect.h) { + abs_y -= (abs_y + window->h) - (rect.y + rect.h); + } + abs_x = SDL_max(abs_x, rect.x); + abs_y = SDL_max(abs_y, rect.y); + + if (output_to_pending) { + window->pending.x = abs_x - offset_x; + window->pending.y = abs_y - offset_y; + } else { + window->floating.x = window->windowed.x = abs_x - offset_x; + window->floating.y = window->windowed.y = abs_y - offset_y; + } + } +} + +static void X11_SetKeyboardFocus(SDL_Window *window, bool set_active_focus) +{ + SDL_Window *toplevel = window; + + // Find the toplevel parent + while (SDL_WINDOW_IS_POPUP(toplevel)) { + toplevel = toplevel->parent; + } + + toplevel->internal->keyboard_focus = window; + + if (set_active_focus && !window->is_hiding && !window->is_destroying) { + SDL_SetKeyboardFocus(window); + } +} + +Uint32 X11_GetNetWMState(SDL_VideoDevice *_this, SDL_Window *window, Window xwindow) +{ + SDL_VideoData *videodata = _this->internal; + Display *display = videodata->display; + Atom _NET_WM_STATE = videodata->atoms._NET_WM_STATE; + Atom _NET_WM_STATE_HIDDEN = videodata->atoms._NET_WM_STATE_HIDDEN; + Atom _NET_WM_STATE_FOCUSED = videodata->atoms._NET_WM_STATE_FOCUSED; + Atom _NET_WM_STATE_MAXIMIZED_VERT = videodata->atoms._NET_WM_STATE_MAXIMIZED_VERT; + Atom _NET_WM_STATE_MAXIMIZED_HORZ = videodata->atoms._NET_WM_STATE_MAXIMIZED_HORZ; + Atom _NET_WM_STATE_FULLSCREEN = videodata->atoms._NET_WM_STATE_FULLSCREEN; + Atom actualType; + int actualFormat; + unsigned long i, numItems, bytesAfter; + unsigned char *propertyValue = NULL; + long maxLength = 1024; + SDL_WindowFlags flags = 0; + + if (X11_XGetWindowProperty(display, xwindow, _NET_WM_STATE, + 0l, maxLength, False, XA_ATOM, &actualType, + &actualFormat, &numItems, &bytesAfter, + &propertyValue) == Success) { + Atom *atoms = (Atom *)propertyValue; + int maximized = 0; + int fullscreen = 0; + + for (i = 0; i < numItems; ++i) { + if (atoms[i] == _NET_WM_STATE_HIDDEN) { + flags |= SDL_WINDOW_MINIMIZED | SDL_WINDOW_OCCLUDED; + } else if (atoms[i] == _NET_WM_STATE_FOCUSED) { + flags |= SDL_WINDOW_INPUT_FOCUS; + } else if (atoms[i] == _NET_WM_STATE_MAXIMIZED_VERT) { + maximized |= 1; + } else if (atoms[i] == _NET_WM_STATE_MAXIMIZED_HORZ) { + maximized |= 2; + } else if (atoms[i] == _NET_WM_STATE_FULLSCREEN) { + fullscreen = 1; + } + } + + if (fullscreen == 1) { + if (window->flags & SDL_WINDOW_FULLSCREEN) { + // Pick whatever state the window expects + flags |= (window->flags & SDL_WINDOW_FULLSCREEN); + } else { + // Assume we're fullscreen desktop + flags |= SDL_WINDOW_FULLSCREEN; + } + } + + if (maximized == 3) { + /* Fullscreen windows are maximized on some window managers, + and this is functional behavior - if maximized is removed, + the windows remain floating centered and not covering the + rest of the desktop. So we just won't change the maximize + state for fullscreen windows here, otherwise SDL would think + we're always maximized when fullscreen and not restore the + correct state when leaving fullscreen. + */ + if (fullscreen) { + flags |= (window->flags & SDL_WINDOW_MAXIMIZED); + } else { + flags |= SDL_WINDOW_MAXIMIZED; + } + } + + /* If the window is unmapped, numItems will be zero and _NET_WM_STATE_HIDDEN + * will not be set. Do an additional check to see if the window is unmapped + * and mark it as SDL_WINDOW_HIDDEN if it is. + */ + { + XWindowAttributes attr; + SDL_memset(&attr, 0, sizeof(attr)); + X11_XGetWindowAttributes(videodata->display, xwindow, &attr); + if (attr.map_state == IsUnmapped) { + flags |= SDL_WINDOW_HIDDEN; + } + } + X11_XFree(propertyValue); + } + + // FIXME, check the size hints for resizable + // flags |= SDL_WINDOW_RESIZABLE; + + return flags; +} + +static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, Window w) +{ + SDL_VideoData *videodata = _this->internal; + SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window); + SDL_WindowData *data; + int numwindows = videodata->numwindows; + int windowlistlength = videodata->windowlistlength; + SDL_WindowData **windowlist = videodata->windowlist; + + // Allocate the window data + data = (SDL_WindowData *)SDL_calloc(1, sizeof(*data)); + if (!data) { + return false; + } + data->videodata = videodata; + data->window = window; + data->xwindow = w; + data->hit_test_result = SDL_HITTEST_NORMAL; + + X11_CreateInputContext(data); + + // Associate the data with the window + + if (numwindows < windowlistlength) { + windowlist[numwindows] = data; + videodata->numwindows++; + } else { + SDL_WindowData ** new_windowlist = (SDL_WindowData **)SDL_realloc(windowlist, (numwindows + 1) * sizeof(*windowlist)); + if (!new_windowlist) { + SDL_free(data); + return false; + } + windowlist = new_windowlist; + windowlist[numwindows] = data; + videodata->numwindows++; + videodata->windowlistlength++; + videodata->windowlist = windowlist; + } + + // Fill in the SDL window with the window data + { + XWindowAttributes attrib; + + X11_XGetWindowAttributes(data->videodata->display, w, &attrib); + if (!SDL_WINDOW_IS_POPUP(window)) { + window->x = window->windowed.x = window->floating.x = attrib.x; + window->y = window->windowed.y = window->floating.y = attrib.y - data->border_top; + } + window->w = window->windowed.w = window->floating.w = attrib.width; + window->h = window->windowed.h = window->floating.h = attrib.height; + if (attrib.map_state != IsUnmapped) { + window->flags &= ~SDL_WINDOW_HIDDEN; + } else { + window->flags |= SDL_WINDOW_HIDDEN; + } + data->visual = attrib.visual; + data->colormap = attrib.colormap; + } + + window->flags |= X11_GetNetWMState(_this, window, w); + + { + Window FocalWindow; + int RevertTo = 0; + X11_XGetInputFocus(data->videodata->display, &FocalWindow, &RevertTo); + if (FocalWindow == w) { + window->flags |= SDL_WINDOW_INPUT_FOCUS; + } + + if (window->flags & SDL_WINDOW_INPUT_FOCUS) { + SDL_SetKeyboardFocus(data->window); + } + + if (window->flags & SDL_WINDOW_MOUSE_GRABBED) { + // Tell x11 to clip mouse + } + } + + if (window->flags & SDL_WINDOW_EXTERNAL) { + // Query the title from the existing window + window->title = X11_GetWindowTitle(_this, w); + } + + SDL_PropertiesID props = SDL_GetWindowProperties(window); + int screen = (displaydata ? displaydata->screen : 0); + SDL_SetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, data->videodata->display); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_X11_SCREEN_NUMBER, screen); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, data->xwindow); + + // All done! + window->internal = data; + return true; +} + +static void SetupWindowInput(SDL_VideoDevice *_this, SDL_Window *window) +{ + long fevent = 0; + SDL_WindowData *data = window->internal; + Window xwindow = data->xwindow; + +#ifdef X_HAVE_UTF8_STRING + if (SDL_X11_HAVE_UTF8 && data->ic) { + X11_XGetICValues(data->ic, XNFilterEvents, &fevent, NULL); + } +#endif + + X11_Xinput2SelectTouch(_this, window); + + { + unsigned int x11_keyboard_events = KeyPressMask | KeyReleaseMask; + unsigned int x11_pointer_events = ButtonPressMask | ButtonReleaseMask | PointerMotionMask; + + X11_Xinput2SelectMouseAndKeyboard(_this, window); + + // If XInput2 can handle pointer and keyboard events, we don't track them here + if (data->xinput2_keyboard_enabled) { + x11_keyboard_events = 0; + } + if (data->xinput2_mouse_enabled) { + x11_pointer_events = 0; + } + + X11_XSelectInput(data->videodata->display, xwindow, + (FocusChangeMask | EnterWindowMask | LeaveWindowMask | ExposureMask | + x11_keyboard_events | x11_pointer_events | + PropertyChangeMask | StructureNotifyMask | + KeymapStateMask | fevent)); + } +} + +static void SetWindowBordered(Display *display, int screen, Window window, bool border) +{ + /* + * this code used to check for KWM_WIN_DECORATION, but KDE hasn't + * supported it for years and years. It now respects _MOTIF_WM_HINTS. + * Gnome is similar: just use the Motif atom. + */ + + Atom WM_HINTS = X11_XInternAtom(display, "_MOTIF_WM_HINTS", True); + if (WM_HINTS != None) { + // Hints used by Motif compliant window managers + struct + { + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long input_mode; + unsigned long status; + } MWMHints = { + (1L << 1), 0, border ? 1 : 0, 0, 0 + }; + + X11_XChangeProperty(display, window, WM_HINTS, WM_HINTS, 32, + PropModeReplace, (unsigned char *)&MWMHints, + sizeof(MWMHints) / sizeof(long)); + } else { // set the transient hints instead, if necessary + X11_XSetTransientForHint(display, window, RootWindow(display, screen)); + } +} + +bool X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) +{ + Window w = (Window)SDL_GetNumberProperty(create_props, SDL_PROP_WINDOW_CREATE_X11_WINDOW_NUMBER, + (Window)SDL_GetPointerProperty(create_props, "sdl2-compat.external_window", NULL)); + if (w) { + window->flags |= SDL_WINDOW_EXTERNAL; + + if (!SetupWindowData(_this, window, w)) { + return false; + } + + SetupWindowInput(_this, window); + return true; + } + + SDL_VideoData *data = _this->internal; + SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window); + if (!displaydata) { + return SDL_SetError("Could not find display info"); + } + + const bool force_override_redirect = SDL_GetHintBoolean(SDL_HINT_X11_FORCE_OVERRIDE_REDIRECT, false); + const bool use_resize_sync = (window->flags & SDL_WINDOW_VULKAN); /* doesn't work well with Vulkan */ + SDL_WindowData *windowdata; + Display *display = data->display; + int screen = displaydata->screen; + Visual *visual; + int depth; + XSetWindowAttributes xattr; + XSizeHints *sizehints; + XWMHints *wmhints; + XClassHint *classhints; + Atom _NET_WM_BYPASS_COMPOSITOR; + Atom _NET_WM_WINDOW_TYPE; + Atom wintype; + const char *wintype_name = NULL; + long compositor = 1; + Atom _NET_WM_PID; + const char *hint = NULL; + int win_x, win_y; + bool undefined_position = false; + +#if defined(SDL_VIDEO_OPENGL_GLX) || defined(SDL_VIDEO_OPENGL_EGL) + const int transparent = (window->flags & SDL_WINDOW_TRANSPARENT) ? true : false; + const char *forced_visual_id = SDL_GetHint(SDL_HINT_VIDEO_X11_WINDOW_VISUALID); + const char *display_visual_id = SDL_GetHint(SDL_HINT_VIDEO_X11_VISUALID); + + if (forced_visual_id && *forced_visual_id) { + XVisualInfo *vi, template; + int nvis; + + SDL_zero(template); + template.visualid = SDL_strtol(forced_visual_id, NULL, 0); + vi = X11_XGetVisualInfo(display, VisualIDMask, &template, &nvis); + if (vi) { + visual = vi->visual; + depth = vi->depth; + X11_XFree(vi); + } else { + return false; + } + } else if ((window->flags & SDL_WINDOW_OPENGL) && + (!display_visual_id || !*display_visual_id)) { + XVisualInfo *vinfo = NULL; + +#ifdef SDL_VIDEO_OPENGL_EGL + if (((_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) || + SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false)) +#ifdef SDL_VIDEO_OPENGL_GLX + && (!_this->gl_data || X11_GL_UseEGL(_this)) +#endif + ) { + vinfo = X11_GLES_GetVisual(_this, display, screen, transparent); + } else +#endif + { +#ifdef SDL_VIDEO_OPENGL_GLX + vinfo = X11_GL_GetVisual(_this, display, screen, transparent); +#endif + } + + if (!vinfo) { + return false; + } + visual = vinfo->visual; + depth = vinfo->depth; + X11_XFree(vinfo); + } else +#endif + { + visual = displaydata->visual; + depth = displaydata->depth; + } + + xattr.override_redirect = ((window->flags & SDL_WINDOW_TOOLTIP) || (window->flags & SDL_WINDOW_POPUP_MENU) || force_override_redirect) ? True : False; + xattr.backing_store = NotUseful; + xattr.background_pixmap = None; + xattr.border_pixel = 0; + + if (visual->class == DirectColor) { + XColor *colorcells; + int i; + int ncolors; + int rmax, gmax, bmax; + int rmask, gmask, bmask; + int rshift, gshift, bshift; + + xattr.colormap = + X11_XCreateColormap(display, RootWindow(display, screen), + visual, AllocAll); + + // If we can't create a colormap, then we must die + if (!xattr.colormap) { + return SDL_SetError("Could not create writable colormap"); + } + + // OK, we got a colormap, now fill it in as best as we can + colorcells = SDL_malloc(visual->map_entries * sizeof(XColor)); + if (!colorcells) { + return false; + } + ncolors = visual->map_entries; + rmax = 0xffff; + gmax = 0xffff; + bmax = 0xffff; + + rshift = 0; + rmask = visual->red_mask; + while (0 == (rmask & 1)) { + rshift++; + rmask >>= 1; + } + + gshift = 0; + gmask = visual->green_mask; + while (0 == (gmask & 1)) { + gshift++; + gmask >>= 1; + } + + bshift = 0; + bmask = visual->blue_mask; + while (0 == (bmask & 1)) { + bshift++; + bmask >>= 1; + } + + // build the color table pixel values + for (i = 0; i < ncolors; i++) { + Uint32 red = (rmax * i) / (ncolors - 1); + Uint32 green = (gmax * i) / (ncolors - 1); + Uint32 blue = (bmax * i) / (ncolors - 1); + + Uint32 rbits = (rmask * i) / (ncolors - 1); + Uint32 gbits = (gmask * i) / (ncolors - 1); + Uint32 bbits = (bmask * i) / (ncolors - 1); + + Uint32 pix = + (rbits << rshift) | (gbits << gshift) | (bbits << bshift); + + colorcells[i].pixel = pix; + + colorcells[i].red = red; + colorcells[i].green = green; + colorcells[i].blue = blue; + + colorcells[i].flags = DoRed | DoGreen | DoBlue; + } + + X11_XStoreColors(display, xattr.colormap, colorcells, ncolors); + + SDL_free(colorcells); + } else { + xattr.colormap = + X11_XCreateColormap(display, RootWindow(display, screen), + visual, AllocNone); + } + + if (window->undefined_x && window->undefined_y && + window->last_displayID == SDL_GetPrimaryDisplay()) { + undefined_position = true; + } + + if (SDL_WINDOW_IS_POPUP(window)) { + X11_ConstrainPopup(window, false); + } + SDL_RelativeToGlobalForWindow(window, + window->floating.x, window->floating.y, + &win_x, &win_y); + + /* Always create this with the window->floating.* fields; if we're creating a windowed mode window, + * that's fine. If we're creating a maximized or fullscreen window, the window manager will want to + * know these values so it can use them if we go _back_ to the base floating windowed mode. SDL manages + * migration to fullscreen after CreateSDLWindow returns, which will put all the SDL_Window fields and + * system state as expected. + */ + w = X11_XCreateWindow(display, RootWindow(display, screen), + win_x, win_y, window->floating.w, window->floating.h, + 0, depth, InputOutput, visual, + (CWOverrideRedirect | CWBackPixmap | CWBorderPixel | + CWBackingStore | CWColormap), + &xattr); + if (!w) { + return SDL_SetError("Couldn't create window"); + } + + /* Don't set the borderless flag if we're about to go fullscreen. + * This prevents the window manager from moving a full-screen borderless + * window to a different display before we actually go fullscreen. + */ + if (!(window->pending_flags & SDL_WINDOW_FULLSCREEN)) { + SetWindowBordered(display, screen, w, !(window->flags & SDL_WINDOW_BORDERLESS)); + } + + sizehints = X11_XAllocSizeHints(); + // Setup the normal size hints + sizehints->flags = 0; + if (!(window->flags & SDL_WINDOW_RESIZABLE)) { + sizehints->min_width = sizehints->max_width = window->floating.w; + sizehints->min_height = sizehints->max_height = window->floating.h; + sizehints->flags |= (PMaxSize | PMinSize); + } + if (!undefined_position) { + sizehints->x = win_x; + sizehints->y = win_y; + sizehints->flags |= USPosition; + } + + // Setup the input hints so we get keyboard input + wmhints = X11_XAllocWMHints(); + wmhints->input = !(window->flags & SDL_WINDOW_NOT_FOCUSABLE) ? True : False; + wmhints->window_group = data->window_group; + wmhints->flags = InputHint | WindowGroupHint; + + // Setup the class hints so we can get an icon (AfterStep) + classhints = X11_XAllocClassHint(); + classhints->res_name = (char *)SDL_GetExeName(); + classhints->res_class = (char *)SDL_GetAppID(); + + // Set the size, input and class hints, and define WM_CLIENT_MACHINE and WM_LOCALE_NAME + X11_XSetWMProperties(display, w, NULL, NULL, NULL, 0, sizehints, wmhints, classhints); + + X11_XFree(sizehints); + X11_XFree(wmhints); + X11_XFree(classhints); + // Set the PID related to the window for the given hostname, if possible + if (data->pid > 0) { + long pid = (long)data->pid; + _NET_WM_PID = X11_XInternAtom(display, "_NET_WM_PID", False); + X11_XChangeProperty(display, w, _NET_WM_PID, XA_CARDINAL, 32, PropModeReplace, + (unsigned char *)&pid, 1); + } + + // Set the window manager state + X11_SetNetWMState(_this, w, window->flags); + + compositor = 2; // don't disable compositing except for "normal" windows + hint = SDL_GetHint(SDL_HINT_X11_WINDOW_TYPE); + if (window->flags & SDL_WINDOW_UTILITY) { + wintype_name = "_NET_WM_WINDOW_TYPE_UTILITY"; + } else if (window->flags & SDL_WINDOW_TOOLTIP) { + wintype_name = "_NET_WM_WINDOW_TYPE_TOOLTIP"; + } else if (window->flags & SDL_WINDOW_POPUP_MENU) { + wintype_name = "_NET_WM_WINDOW_TYPE_POPUP_MENU"; + } else if (hint && *hint) { + wintype_name = hint; + } else { + wintype_name = "_NET_WM_WINDOW_TYPE_NORMAL"; + compositor = 1; // disable compositing for "normal" windows + } + + // Let the window manager know what type of window we are. + _NET_WM_WINDOW_TYPE = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE", False); + wintype = X11_XInternAtom(display, wintype_name, False); + X11_XChangeProperty(display, w, _NET_WM_WINDOW_TYPE, XA_ATOM, 32, + PropModeReplace, (unsigned char *)&wintype, 1); + if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, true)) { + _NET_WM_BYPASS_COMPOSITOR = X11_XInternAtom(display, "_NET_WM_BYPASS_COMPOSITOR", False); + X11_XChangeProperty(display, w, _NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL, 32, + PropModeReplace, + (unsigned char *)&compositor, 1); + } + + { + Atom protocols[4]; + int proto_count = 0; + + protocols[proto_count++] = data->atoms.WM_DELETE_WINDOW; // Allow window to be deleted by the WM + protocols[proto_count++] = data->atoms.WM_TAKE_FOCUS; // Since we will want to set input focus explicitly + + // Default to using ping if there is no hint + if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_NET_WM_PING, true)) { + protocols[proto_count++] = data->atoms._NET_WM_PING; // Respond so WM knows we're alive + } + +#ifdef SDL_VIDEO_DRIVER_X11_XSYNC + if (use_resize_sync) { + protocols[proto_count++] = data->atoms._NET_WM_SYNC_REQUEST; /* Respond after completing resize */ + } +#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */ + + SDL_assert(proto_count <= sizeof(protocols) / sizeof(protocols[0])); + + X11_XSetWMProtocols(display, w, protocols, proto_count); + } + + if (!SetupWindowData(_this, window, w)) { + X11_XDestroyWindow(display, w); + return false; + } + windowdata = window->internal; + + // Set the parent if this is a non-popup window. + if (!SDL_WINDOW_IS_POPUP(window) && window->parent) { + X11_XSetTransientForHint(display, w, window->parent->internal->xwindow); + } + + // Set the flag if the borders were forced on when creating a fullscreen window for later removal. + windowdata->fullscreen_borders_forced_on = !!(window->pending_flags & SDL_WINDOW_FULLSCREEN) && + !!(window->flags & SDL_WINDOW_BORDERLESS); + +#ifdef SDL_VIDEO_DRIVER_X11_XSYNC + if (use_resize_sync) { + X11_InitResizeSync(window); + } +#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */ + +#if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2) || defined(SDL_VIDEO_OPENGL_EGL) + if ((window->flags & SDL_WINDOW_OPENGL) && + ((_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) || + SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false)) +#ifdef SDL_VIDEO_OPENGL_GLX + && (!_this->gl_data || X11_GL_UseEGL(_this)) +#endif + ) { +#ifdef SDL_VIDEO_OPENGL_EGL + if (!_this->egl_data) { + return false; + } + + // Create the GLES window surface + windowdata->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)w); + + if (windowdata->egl_surface == EGL_NO_SURFACE) { + return SDL_SetError("Could not create GLES window surface"); + } +#else + return SDL_SetError("Could not create GLES window surface (EGL support not configured)"); +#endif // SDL_VIDEO_OPENGL_EGL + } +#endif + +#ifdef SDL_VIDEO_DRIVER_X11_XSHAPE + // Tooltips do not receive input + if (window->flags & SDL_WINDOW_TOOLTIP) { + Region region = X11_XCreateRegion(); + X11_XShapeCombineRegion(display, w, ShapeInput, 0, 0, region, ShapeSet); + X11_XDestroyRegion(region); + } +#endif + + SetupWindowInput(_this, window); + + // For _ICC_PROFILE. + X11_XSelectInput(display, RootWindow(display, screen), PropertyChangeMask); + + X11_XFlush(display); + + return true; +} + +char *X11_GetWindowTitle(SDL_VideoDevice *_this, Window xwindow) +{ + SDL_VideoData *data = _this->internal; + Display *display = data->display; + int status, real_format; + Atom real_type; + unsigned long items_read, items_left; + unsigned char *propdata; + char *title = NULL; + + status = X11_XGetWindowProperty(display, xwindow, data->atoms._NET_WM_NAME, + 0L, 8192L, False, data->atoms.UTF8_STRING, &real_type, &real_format, + &items_read, &items_left, &propdata); + if (status == Success && propdata) { + title = SDL_strdup(SDL_static_cast(char *, propdata)); + X11_XFree(propdata); + } else { + status = X11_XGetWindowProperty(display, xwindow, XA_WM_NAME, + 0L, 8192L, False, XA_STRING, &real_type, &real_format, + &items_read, &items_left, &propdata); + if (status == Success && propdata) { + title = SDL_iconv_string("UTF-8", "", SDL_static_cast(char *, propdata), items_read + 1); + SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Failed to convert WM_NAME title expecting UTF8! Title: %s", title); + X11_XFree(propdata); + } else { + SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Could not get any window title response from Xorg, returning empty string!"); + title = SDL_strdup(""); + } + } + return title; +} + +void X11_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + Window xwindow = data->xwindow; + Display *display = data->videodata->display; + char *title = window->title ? window->title : ""; + + SDL_X11_SetWindowTitle(display, xwindow, title); +} + +static bool caught_x11_error = false; +static int X11_CatchAnyError(Display *d, XErrorEvent *e) +{ + /* this may happen during tumultuous times when we are polling anyhow, + so just note we had an error and return control. */ + caught_x11_error = true; + return 0; +} + +/* Wait a brief time, or not, to see if the window manager decided to move/resize the window. + * Send MOVED and RESIZED window events */ +static bool X11_SyncWindowTimeout(SDL_VideoDevice *_this, SDL_Window *window, Uint64 param_timeout) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + int (*prev_handler)(Display *, XErrorEvent *); + Uint64 timeout = 0; + bool force_exit = false; + bool result = true; + + X11_XSync(display, False); + prev_handler = X11_XSetErrorHandler(X11_CatchAnyError); + + if (param_timeout) { + timeout = SDL_GetTicksNS() + param_timeout; + } + + while (true) { + X11_XSync(display, False); + X11_PumpEvents(_this); + + if ((data->pending_operation & X11_PENDING_OP_MOVE) && (window->x == data->expected.x + data->border_left && window->y == data->expected.y + data->border_top)) { + data->pending_operation &= ~X11_PENDING_OP_MOVE; + } + if ((data->pending_operation & X11_PENDING_OP_RESIZE) && (window->w == data->expected.w && window->h == data->expected.h)) { + data->pending_operation &= ~X11_PENDING_OP_RESIZE; + } + + if (data->pending_operation == X11_PENDING_OP_NONE) { + if (force_exit || + (window->x == data->expected.x + data->border_left && window->y == data->expected.y + data->border_top && + window->w == data->expected.w && window->h == data->expected.h)) { + // The window is in the expected state and nothing is pending. Done. + break; + } + + /* No operations are pending, but the window still isn't in the expected state. + * Try one more time before exiting. + */ + force_exit = true; + } + + if (SDL_GetTicksNS() >= timeout) { + // Timed out without the expected values. Update the requested data so future sync calls won't block. + data->expected.x = window->x; + data->expected.y = window->y; + data->expected.w = window->w; + data->expected.h = window->h; + + result = false; + break; + } + + SDL_Delay(10); + } + + data->pending_operation = X11_PENDING_OP_NONE; + + if (!caught_x11_error) { + X11_PumpEvents(_this); + } else { + result = false; + } + + X11_XSetErrorHandler(prev_handler); + caught_x11_error = false; + + return result; +} + +bool X11_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + Atom _NET_WM_ICON = data->videodata->atoms._NET_WM_ICON; + int (*prevHandler)(Display *, XErrorEvent *) = NULL; + bool result = true; + + if (icon) { + int x, y; + int propsize; + long *propdata; + Uint32 *src; + long *dst; + + // Set the _NET_WM_ICON property + SDL_assert(icon->format == SDL_PIXELFORMAT_ARGB8888); + propsize = 2 + (icon->w * icon->h); + propdata = SDL_malloc(propsize * sizeof(long)); + + if (!propdata) { + return false; + } + + X11_XSync(display, False); + prevHandler = X11_XSetErrorHandler(X11_CatchAnyError); + + propdata[0] = icon->w; + propdata[1] = icon->h; + dst = &propdata[2]; + + for (y = 0; y < icon->h; ++y) { + src = (Uint32 *)((Uint8 *)icon->pixels + y * icon->pitch); + for (x = 0; x < icon->w; ++x) { + *dst++ = *src++; + } + } + + X11_XChangeProperty(display, data->xwindow, _NET_WM_ICON, XA_CARDINAL, + 32, PropModeReplace, (unsigned char *)propdata, + propsize); + SDL_free(propdata); + + if (caught_x11_error) { + result = SDL_SetError("An error occurred while trying to set the window's icon"); + } + } + + X11_XFlush(display); + + if (prevHandler) { + X11_XSetErrorHandler(prevHandler); + caught_x11_error = false; + } + + return result; +} + +void X11_UpdateWindowPosition(SDL_Window *window, bool use_current_position) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + const int rel_x = use_current_position ? window->x : window->pending.x; + const int rel_y = use_current_position ? window->y : window->pending.y; + + SDL_RelativeToGlobalForWindow(window, + rel_x - data->border_left, rel_y - data->border_top, + &data->expected.x, &data->expected.y); + + // Attempt to move the window + if (window->flags & SDL_WINDOW_HIDDEN) { + window->internal->pending_position = true; + } else { + data->pending_operation |= X11_PENDING_OP_MOVE; + X11_XMoveWindow(display, data->xwindow, data->expected.x, data->expected.y); + } +} + +bool X11_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window) +{ + // Sync any pending fullscreen or maximize events. + if (window->internal->pending_operation & (X11_PENDING_OP_FULLSCREEN | X11_PENDING_OP_MAXIMIZE)) { + X11_FlushPendingEvents(_this, window); + } + + // Set the position as pending if the window is maximized with a restore pending. + if (window->flags & SDL_WINDOW_MAXIMIZED) { + if (window->internal->pending_operation & X11_PENDING_OP_RESTORE) { + window->internal->pending_position = true; + } + return true; + } + + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + if (SDL_WINDOW_IS_POPUP(window)) { + X11_ConstrainPopup(window, true); + } + X11_UpdateWindowPosition(window, false); + } else { + SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_UPDATE, true); + } + return true; +} + +void X11_SetWindowMinMax(SDL_Window *window, bool use_current) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + XSizeHints *sizehints = X11_XAllocSizeHints(); + long hint_flags = 0; + + X11_XGetWMNormalHints(display, data->xwindow, sizehints, &hint_flags); + sizehints->flags &= ~(PMinSize | PMaxSize | PAspect); + + if (data->window->flags & SDL_WINDOW_RESIZABLE) { + if (data->window->min_w || data->window->min_h) { + sizehints->flags |= PMinSize; + sizehints->min_width = data->window->min_w; + sizehints->min_height = data->window->min_h; + } + if (data->window->max_w || data->window->max_h) { + sizehints->flags |= PMaxSize; + sizehints->max_width = data->window->max_w; + sizehints->max_height = data->window->max_h; + } + if (data->window->min_aspect > 0.0f || data->window->max_aspect > 0.0f) { + sizehints->flags |= PAspect; + SDL_CalculateFraction(data->window->min_aspect, &sizehints->min_aspect.x, &sizehints->min_aspect.y); + SDL_CalculateFraction(data->window->max_aspect, &sizehints->max_aspect.x, &sizehints->max_aspect.y); + } + } else { + // Set the min/max to the same values to make the window non-resizable + sizehints->flags |= PMinSize | PMaxSize; + sizehints->min_width = sizehints->max_width = use_current ? data->window->floating.w : window->windowed.w; + sizehints->min_height = sizehints->max_height = use_current ? data->window->floating.h : window->windowed.h; + } + + X11_XSetWMNormalHints(display, data->xwindow, sizehints); + X11_XFree(sizehints); +} + +void X11_SetWindowMinimumSize(SDL_VideoDevice *_this, SDL_Window *window) +{ + if (window->internal->pending_operation & X11_PENDING_OP_FULLSCREEN) { + X11_SyncWindow(_this, window); + } + + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + X11_SetWindowMinMax(window, true); + } +} + +void X11_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window) +{ + if (window->internal->pending_operation & X11_PENDING_OP_FULLSCREEN) { + X11_SyncWindow(_this, window); + } + + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + X11_SetWindowMinMax(window, true); + } +} + +void X11_SetWindowAspectRatio(SDL_VideoDevice *_this, SDL_Window *window) +{ + if (window->internal->pending_operation & X11_PENDING_OP_FULLSCREEN) { + X11_SyncWindow(_this, window); + } + + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + X11_SetWindowMinMax(window, true); + } +} + +void X11_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + + /* Wait for pending maximize and fullscreen operations to complete, as these windows + * don't get size changes. + */ + if (data->pending_operation & (X11_PENDING_OP_MAXIMIZE | X11_PENDING_OP_FULLSCREEN)) { + X11_FlushPendingEvents(_this, window); + } + + // Set the size as pending if the window is being restored. + if (window->flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_FULLSCREEN)) { + // New size will be set when the window is restored. + if (data->pending_operation & X11_PENDING_OP_RESTORE) { + data->pending_size = true; + } else { + // Can't resize the window. + window->last_size_pending = false; + } + return; + } + + if (!(window->flags & SDL_WINDOW_RESIZABLE)) { + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + /* Apparently, if the X11 Window is set to a 'non-resizable' window, you cannot resize it using the X11_XResizeWindow, thus + * we must set the size hints to adjust the window size. + */ + XSizeHints *sizehints = X11_XAllocSizeHints(); + long userhints; + int dest_x, dest_y; + + X11_XGetWMNormalHints(display, data->xwindow, sizehints, &userhints); + + sizehints->min_width = sizehints->max_width = window->pending.w; + sizehints->min_height = sizehints->max_height = window->pending.h; + sizehints->flags |= PMinSize | PMaxSize; + + X11_XSetWMNormalHints(display, data->xwindow, sizehints); + + /* From Pierre-Loup: + WMs each have their little quirks with that. When you change the + size hints, they get a ConfigureNotify event with the + WM_NORMAL_SIZE_HINTS Atom. They all save the hints then, but they + don't all resize the window right away to enforce the new hints. + + Some of them resize only after: + - A user-initiated move or resize + - A code-initiated move or resize + - Hiding & showing window (Unmap & map) + + The following move & resize seems to help a lot of WMs that didn't + properly update after the hints were changed. We don't do a + hide/show, because there are supposedly subtle problems with doing so + and transitioning from windowed to fullscreen in Unity. + */ + X11_XResizeWindow(display, data->xwindow, window->pending.w, window->pending.h); + const int x = window->last_position_pending ? window->pending.x : window->x; + const int y = window->last_position_pending ? window->pending.y : window->y; + SDL_RelativeToGlobalForWindow(window, + x - data->border_left, + y - data->border_top, + &dest_x, &dest_y); + X11_XMoveWindow(display, data->xwindow, dest_x, dest_y); + X11_XRaiseWindow(display, data->xwindow); + + X11_XFree(sizehints); + } + } else { + data->expected.w = window->pending.w; + data->expected.h = window->pending.h; + data->pending_operation |= X11_PENDING_OP_RESIZE; + X11_XResizeWindow(display, data->xwindow, data->expected.w, data->expected.h); + } +} + +bool X11_GetWindowBordersSize(SDL_VideoDevice *_this, SDL_Window *window, int *top, int *left, int *bottom, int *right) +{ + SDL_WindowData *data = window->internal; + + *left = data->border_left; + *right = data->border_right; + *top = data->border_top; + *bottom = data->border_bottom; + + return true; +} + +bool X11_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + Atom _NET_WM_WINDOW_OPACITY = data->videodata->atoms._NET_WM_WINDOW_OPACITY; + + if (opacity == 1.0f) { + X11_XDeleteProperty(display, data->xwindow, _NET_WM_WINDOW_OPACITY); + } else { + const Uint32 FullyOpaque = 0xFFFFFFFF; + const long alpha = (long)((double)opacity * (double)FullyOpaque); + X11_XChangeProperty(display, data->xwindow, _NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32, + PropModeReplace, (unsigned char *)&alpha, 1); + } + + return true; +} + +bool X11_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent) +{ + SDL_WindowData *data = window->internal; + SDL_WindowData *parent_data = parent ? parent->internal : NULL; + SDL_VideoData *video_data = _this->internal; + Display *display = video_data->display; + + if (parent_data) { + X11_XSetTransientForHint(display, data->xwindow, parent_data->xwindow); + } else { + X11_XDeleteProperty(display, data->xwindow, video_data->atoms.WM_TRANSIENT_FOR); + } + + return true; +} + +bool X11_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal) +{ + SDL_WindowData *data = window->internal; + SDL_VideoData *video_data = _this->internal; + SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window); + Display *display = video_data->display; + Uint32 flags = window->flags; + Atom _NET_WM_STATE = data->videodata->atoms._NET_WM_STATE; + Atom _NET_WM_STATE_MODAL = data->videodata->atoms._NET_WM_STATE_MODAL; + + if (modal) { + flags |= SDL_WINDOW_MODAL; + } else { + flags &= ~SDL_WINDOW_MODAL; + X11_XDeleteProperty(display, data->xwindow, video_data->atoms.WM_TRANSIENT_FOR); + } + + if (X11_IsWindowMapped(_this, window)) { + XEvent e; + + SDL_zero(e); + e.xany.type = ClientMessage; + e.xclient.message_type = _NET_WM_STATE; + e.xclient.format = 32; + e.xclient.window = data->xwindow; + e.xclient.data.l[0] = modal ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; + e.xclient.data.l[1] = _NET_WM_STATE_MODAL; + e.xclient.data.l[3] = 0l; + + X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0, + SubstructureNotifyMask | SubstructureRedirectMask, &e); + } else { + X11_SetNetWMState(_this, data->xwindow, flags); + } + + X11_XFlush(display); + + return true; +} + +void X11_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered) +{ + const bool focused = (window->flags & SDL_WINDOW_INPUT_FOCUS) ? true : false; + const bool visible = (!(window->flags & SDL_WINDOW_HIDDEN)) ? true : false; + SDL_WindowData *data = window->internal; + SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window); + Display *display = data->videodata->display; + XEvent event; + + if (data->pending_operation & X11_PENDING_OP_FULLSCREEN) { + X11_SyncWindow(_this, window); + } + + // If the window is fullscreen, the resize capability will be set/cleared when it is returned to windowed mode. + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + SetWindowBordered(display, displaydata->screen, data->xwindow, bordered); + X11_XFlush(display); + + if (visible) { + XWindowAttributes attr; + do { + X11_XSync(display, False); + X11_XGetWindowAttributes(display, data->xwindow, &attr); + } while (attr.map_state != IsViewable); + + if (focused) { + X11_XSetInputFocus(display, data->xwindow, RevertToParent, CurrentTime); + } + } + + // make sure these don't make it to the real event queue if they fired here. + X11_XSync(display, False); + X11_XCheckIfEvent(display, &event, &isUnmapNotify, (XPointer)&data->xwindow); + X11_XCheckIfEvent(display, &event, &isMapNotify, (XPointer)&data->xwindow); + + // Turning the borders off doesn't send an extent event, so they must be cleared here. + X11_GetBorderValues(data); + + // Make sure the window manager didn't resize our window for the difference. + X11_XResizeWindow(display, data->xwindow, window->floating.w, window->floating.h); + X11_XSync(display, False); + } else { + // If fullscreen, set a flag to toggle the borders when returning to windowed mode. + data->toggle_borders = true; + data->fullscreen_borders_forced_on = false; + } +} + +void X11_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable) +{ + SDL_WindowData *data = window->internal; + + if (data->pending_operation & X11_PENDING_OP_FULLSCREEN) { + X11_SyncWindow(_this, window); + } + + // If the window is fullscreen, the resize capability will be set/cleared when it is returned to windowed mode. + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + X11_SetWindowMinMax(window, true); + } +} + +void X11_SetWindowAlwaysOnTop(SDL_VideoDevice *_this, SDL_Window *window, bool on_top) +{ + SDL_WindowData *data = window->internal; + SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window); + Display *display = data->videodata->display; + Atom _NET_WM_STATE = data->videodata->atoms._NET_WM_STATE; + Atom _NET_WM_STATE_ABOVE = data->videodata->atoms._NET_WM_STATE_ABOVE; + + if (X11_IsWindowMapped(_this, window)) { + XEvent e; + + SDL_zero(e); + e.xany.type = ClientMessage; + e.xclient.message_type = _NET_WM_STATE; + e.xclient.format = 32; + e.xclient.window = data->xwindow; + e.xclient.data.l[0] = + on_top ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; + e.xclient.data.l[1] = _NET_WM_STATE_ABOVE; + e.xclient.data.l[3] = 0l; + + X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0, + SubstructureNotifyMask | SubstructureRedirectMask, &e); + } else { + X11_SetNetWMState(_this, data->xwindow, window->flags); + } + X11_XFlush(display); +} + +void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + bool bActivate = SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN, true); + bool position_is_absolute = false; + bool set_position = false; + XEvent event; + + if (SDL_WINDOW_IS_POPUP(window)) { + // Update the position in case the parent moved while we were hidden + X11_ConstrainPopup(window, true); + data->pending_position = true; + + // Coordinates after X11_ConstrainPopup() are already in the global space. + position_is_absolute = true; + set_position = true; + } + + /* Whether XMapRaised focuses the window is based on the window type and it is + * wm specific. There isn't much we can do here */ + (void)bActivate; + + if (!X11_IsWindowMapped(_this, window)) { + X11_XMapRaised(display, data->xwindow); + /* Blocking wait for "MapNotify" event. + * We use X11_XIfEvent because pXWindowEvent takes a mask rather than a type, + * and XCheckTypedWindowEvent doesn't block */ + if (!(window->flags & SDL_WINDOW_EXTERNAL) && X11_IsDisplayOk(display)) { + X11_XIfEvent(display, &event, &isMapNotify, (XPointer)&data->xwindow); + } + X11_XFlush(display); + set_position = data->pending_position || + (!(window->flags & SDL_WINDOW_BORDERLESS) && !window->undefined_x && !window->undefined_y); + } + + if (!data->videodata->net_wm) { + // no WM means no FocusIn event, which confuses us. Force it. + X11_XSync(display, False); + X11_XSetInputFocus(display, data->xwindow, RevertToNone, CurrentTime); + X11_XFlush(display); + } + + // Popup menus grab the keyboard + if (window->flags & SDL_WINDOW_POPUP_MENU) { + X11_SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus()); + } + + // Get some valid border values, if we haven't received them yet + if (data->border_left == 0 && data->border_right == 0 && data->border_top == 0 && data->border_bottom == 0) { + X11_GetBorderValues(data); + } + + if (set_position) { + // Apply the window position, accounting for offsets due to the borders appearing. + const int tx = data->pending_position ? window->pending.x : window->x; + const int ty = data->pending_position ? window->pending.y : window->y; + int x, y; + if (position_is_absolute) { + x = tx; + y = ty; + } else { + SDL_RelativeToGlobalForWindow(window, + tx - data->border_left, ty - data->border_top, + &x, &y); + } + data->pending_position = false; + X11_XMoveWindow(display, data->xwindow, x, y); + } + + /* Some window managers can send garbage coordinates while mapping the window, so don't emit size and position + * events during the initial configure events. + */ + data->size_move_event_flags = X11_SIZE_MOVE_EVENTS_DISABLE; + X11_XSync(display, False); + X11_PumpEvents(_this); + data->size_move_event_flags = 0; + + // If a configure event was received (type is non-zero), send the final window size and coordinates. + if (data->last_xconfigure.type) { + int x, y; + SDL_GlobalToRelativeForWindow(data->window, data->last_xconfigure.x, data->last_xconfigure.y, &x, &y); + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, data->last_xconfigure.width, data->last_xconfigure.height); + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, x, y); + } +} + +void X11_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window); + int screen = (displaydata ? displaydata->screen : 0); + Display *display = data->videodata->display; + XEvent event; + + if (X11_IsWindowMapped(_this, window)) { + X11_XWithdrawWindow(display, data->xwindow, screen); + // Blocking wait for "UnmapNotify" event + if (!(window->flags & SDL_WINDOW_EXTERNAL) && X11_IsDisplayOk(display)) { + X11_XIfEvent(display, &event, &isUnmapNotify, (XPointer)&data->xwindow); + } + X11_XFlush(display); + } + + // Transfer keyboard focus back to the parent + if (window->flags & SDL_WINDOW_POPUP_MENU) { + SDL_Window *new_focus = window->parent; + bool set_focus = window == SDL_GetKeyboardFocus(); + + // Find the highest level window, up to the toplevel parent, that isn't being hidden or destroyed. + while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) { + new_focus = new_focus->parent; + + // If some window in the chain currently had focus, set it to the new lowest-level window. + if (!set_focus) { + set_focus = new_focus == SDL_GetKeyboardFocus(); + } + } + + X11_SetKeyboardFocus(new_focus, set_focus); + } + + X11_XSync(display, False); + X11_PumpEvents(_this); +} + +static bool X11_SetWindowActive(SDL_VideoDevice *_this, SDL_Window *window) +{ + CHECK_WINDOW_DATA(window); + + SDL_WindowData *data = window->internal; + SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window); + Display *display = data->videodata->display; + Atom _NET_ACTIVE_WINDOW = data->videodata->atoms._NET_ACTIVE_WINDOW; + + if (X11_IsWindowMapped(_this, window)) { + XEvent e; + + // printf("SDL Window %p: sending _NET_ACTIVE_WINDOW with timestamp %lu\n", window, data->user_time); + + SDL_zero(e); + e.xany.type = ClientMessage; + e.xclient.message_type = _NET_ACTIVE_WINDOW; + e.xclient.format = 32; + e.xclient.window = data->xwindow; + e.xclient.data.l[0] = 1; // source indication. 1 = application + e.xclient.data.l[1] = data->user_time; + e.xclient.data.l[2] = 0; + + X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0, + SubstructureNotifyMask | SubstructureRedirectMask, &e); + + X11_XFlush(display); + } + return true; +} + +void X11_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + bool bActivate = SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, true); + + X11_XRaiseWindow(display, data->xwindow); + if (bActivate) { + X11_SetWindowActive(_this, window); + } + X11_XFlush(display); +} + +static bool X11_SetWindowMaximized(SDL_VideoDevice *_this, SDL_Window *window, bool maximized) +{ + CHECK_WINDOW_DATA(window); + + SDL_WindowData *data = window->internal; + SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window); + Display *display = data->videodata->display; + Atom _NET_WM_STATE = data->videodata->atoms._NET_WM_STATE; + Atom _NET_WM_STATE_MAXIMIZED_VERT = data->videodata->atoms._NET_WM_STATE_MAXIMIZED_VERT; + Atom _NET_WM_STATE_MAXIMIZED_HORZ = data->videodata->atoms._NET_WM_STATE_MAXIMIZED_HORZ; + + if (window->flags & SDL_WINDOW_FULLSCREEN) { + /* Fullscreen windows are maximized on some window managers, + and this is functional behavior, so don't remove that state + now, we'll take care of it when we leave fullscreen mode. + */ + return true; + } + + if (X11_IsWindowMapped(_this, window)) { + XEvent e; + + SDL_zero(e); + e.xany.type = ClientMessage; + e.xclient.message_type = _NET_WM_STATE; + e.xclient.format = 32; + e.xclient.window = data->xwindow; + e.xclient.data.l[0] = + maximized ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; + e.xclient.data.l[1] = _NET_WM_STATE_MAXIMIZED_VERT; + e.xclient.data.l[2] = _NET_WM_STATE_MAXIMIZED_HORZ; + e.xclient.data.l[3] = 0l; + + if (maximized) { + SDL_DisplayID displayID = SDL_GetDisplayForWindow(window); + SDL_Rect bounds; + + SDL_zero(bounds); + SDL_GetDisplayUsableBounds(displayID, &bounds); + + data->expected.x = bounds.x + data->border_left; + data->expected.y = bounds.y + data->border_top; + data->expected.w = bounds.w - (data->border_left + data->border_right); + data->expected.h = bounds.h - (data->border_top + data->border_bottom); + } else { + data->expected.x = window->floating.x; + data->expected.y = window->floating.y; + data->expected.w = window->floating.w; + data->expected.h = window->floating.h; + } + + X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0, + SubstructureNotifyMask | SubstructureRedirectMask, &e); + } else { + X11_SetNetWMState(_this, data->xwindow, window->flags); + } + X11_XFlush(display); + + return true; +} + +void X11_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + if (window->internal->pending_operation & (X11_PENDING_OP_FULLSCREEN | X11_PENDING_OP_MINIMIZE)) { + SDL_SyncWindow(window); + } + + if (window->flags & SDL_WINDOW_FULLSCREEN) { + // If fullscreen, just toggle the restored state. + window->internal->window_was_maximized = true; + return; + } + + if (!(window->flags & SDL_WINDOW_MINIMIZED)) { + window->internal->pending_operation |= X11_PENDING_OP_MAXIMIZE; + X11_SetWindowMaximized(_this, window, true); + } +} + +void X11_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window); + Display *display = data->videodata->display; + + if (data->pending_operation & SDL_WINDOW_FULLSCREEN) { + SDL_SyncWindow(window); + } + + data->pending_operation |= X11_PENDING_OP_MINIMIZE; + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + data->window_was_maximized = !!(window->flags & SDL_WINDOW_MAXIMIZED); + } + X11_XIconifyWindow(display, data->xwindow, displaydata->screen); + X11_XFlush(display); +} + +void X11_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + if (window->internal->pending_operation & (X11_PENDING_OP_FULLSCREEN | X11_PENDING_OP_MAXIMIZE | X11_PENDING_OP_MINIMIZE)) { + SDL_SyncWindow(window); + } + + if ((window->flags & SDL_WINDOW_FULLSCREEN) && !(window->flags & SDL_WINDOW_MINIMIZED)) { + // If fullscreen and not minimized, just toggle the restored state. + window->internal->window_was_maximized = false; + return; + } + + if (window->flags & (SDL_WINDOW_MINIMIZED | SDL_WINDOW_MAXIMIZED) || + (window->internal->pending_operation & X11_PENDING_OP_MINIMIZE)) { + window->internal->pending_operation |= X11_PENDING_OP_RESTORE; + } + + // If the window was minimized while maximized, restore as maximized. + const bool maximize = !!(window->flags & SDL_WINDOW_MINIMIZED) && window->internal->window_was_maximized; + X11_SetWindowMaximized(_this, window, maximize); + X11_ShowWindow(_this, window); + X11_SetWindowActive(_this, window); +} + +// This asks the Window Manager to handle fullscreen for us. This is the modern way. +static SDL_FullscreenResult X11_SetWindowFullscreenViaWM(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *_display, SDL_FullscreenOp fullscreen) +{ + CHECK_WINDOW_DATA(window); + CHECK_DISPLAY_DATA(_display); + + SDL_WindowData *data = window->internal; + SDL_DisplayData *displaydata = _display->internal; + Display *display = data->videodata->display; + Atom _NET_WM_STATE = data->videodata->atoms._NET_WM_STATE; + Atom _NET_WM_STATE_FULLSCREEN = data->videodata->atoms._NET_WM_STATE_FULLSCREEN; + + if (X11_IsWindowMapped(_this, window)) { + XEvent e; + + // Flush any pending fullscreen events. + if (data->pending_operation & (X11_PENDING_OP_FULLSCREEN | X11_PENDING_OP_MAXIMIZE | X11_PENDING_OP_MOVE)) { + X11_SyncWindow(_this, window); + } + + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + if (fullscreen == SDL_FULLSCREEN_OP_UPDATE) { + // Request was out of date; set -1 to signal the video core to undo a mode switch. + return SDL_FULLSCREEN_FAILED; + } else if (fullscreen == SDL_FULLSCREEN_OP_LEAVE) { + // Nothing to do. + return SDL_FULLSCREEN_SUCCEEDED; + } + } + + if (fullscreen && !(window->flags & SDL_WINDOW_RESIZABLE)) { + /* Compiz refuses fullscreen toggle if we're not resizable, so update the hints so we + can be resized to the fullscreen resolution (or reset so we're not resizable again) */ + XSizeHints *sizehints = X11_XAllocSizeHints(); + long flags = 0; + X11_XGetWMNormalHints(display, data->xwindow, sizehints, &flags); + // we are going fullscreen so turn the flags off + sizehints->flags &= ~(PMinSize | PMaxSize | PAspect); + X11_XSetWMNormalHints(display, data->xwindow, sizehints); + X11_XFree(sizehints); + } + + SDL_zero(e); + e.xany.type = ClientMessage; + e.xclient.message_type = _NET_WM_STATE; + e.xclient.format = 32; + e.xclient.window = data->xwindow; + e.xclient.data.l[0] = + fullscreen ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; + e.xclient.data.l[1] = _NET_WM_STATE_FULLSCREEN; + e.xclient.data.l[3] = 0l; + + X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0, + SubstructureNotifyMask | SubstructureRedirectMask, &e); + + if (!!(window->flags & SDL_WINDOW_FULLSCREEN) != fullscreen) { + data->pending_operation |= X11_PENDING_OP_FULLSCREEN; + } + + // Set the position so the window will be on the target display + if (fullscreen) { + SDL_DisplayID current = SDL_GetDisplayForWindowPosition(window); + SDL_copyp(&data->requested_fullscreen_mode, &window->current_fullscreen_mode); + if (fullscreen != !!(window->flags & SDL_WINDOW_FULLSCREEN)) { + data->window_was_maximized = !!(window->flags & SDL_WINDOW_MAXIMIZED); + } + data->expected.x = displaydata->x; + data->expected.y = displaydata->y; + data->expected.w = _display->current_mode->w; + data->expected.h = _display->current_mode->h; + + // Only move the window if it isn't fullscreen or already on the target display. + if (!(window->flags & SDL_WINDOW_FULLSCREEN) || (!current || current != _display->id)) { + X11_XMoveWindow(display, data->xwindow, displaydata->x, displaydata->y); + data->pending_operation |= X11_PENDING_OP_MOVE; + } + } else { + SDL_zero(data->requested_fullscreen_mode); + + /* Fullscreen windows sometimes end up being marked maximized by + * window managers. Force it back to how we expect it to be. + */ + SDL_zero(e); + e.xany.type = ClientMessage; + e.xclient.message_type = _NET_WM_STATE; + e.xclient.format = 32; + e.xclient.window = data->xwindow; + if (data->window_was_maximized) { + e.xclient.data.l[0] = _NET_WM_STATE_ADD; + } else { + e.xclient.data.l[0] = _NET_WM_STATE_REMOVE; + } + e.xclient.data.l[1] = data->videodata->atoms._NET_WM_STATE_MAXIMIZED_VERT; + e.xclient.data.l[2] = data->videodata->atoms._NET_WM_STATE_MAXIMIZED_HORZ; + e.xclient.data.l[3] = 0l; + X11_XSendEvent(display, RootWindow(display, displaydata->screen), 0, + SubstructureNotifyMask | SubstructureRedirectMask, &e); + } + } else { + SDL_WindowFlags flags; + + flags = window->flags; + if (fullscreen) { + flags |= SDL_WINDOW_FULLSCREEN; + } else { + flags &= ~SDL_WINDOW_FULLSCREEN; + } + X11_SetNetWMState(_this, data->xwindow, flags); + } + + if (data->visual->class == DirectColor) { + if (fullscreen) { + X11_XInstallColormap(display, data->colormap); + } else { + X11_XUninstallColormap(display, data->colormap); + } + } + + return SDL_FULLSCREEN_PENDING; +} + +SDL_FullscreenResult X11_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *_display, SDL_FullscreenOp fullscreen) +{ + return X11_SetWindowFullscreenViaWM(_this, window, _display, fullscreen); +} + +typedef struct +{ + unsigned char *data; + int format, count; + Atom type; +} SDL_x11Prop; + +/* Reads property + Must call X11_XFree on results + */ +static void X11_ReadProperty(SDL_x11Prop *p, Display *disp, Window w, Atom prop) +{ + unsigned char *ret = NULL; + Atom type; + int fmt; + unsigned long count; + unsigned long bytes_left; + int bytes_fetch = 0; + + do { + if (ret) { + X11_XFree(ret); + } + X11_XGetWindowProperty(disp, w, prop, 0, bytes_fetch, False, AnyPropertyType, &type, &fmt, &count, &bytes_left, &ret); + bytes_fetch += bytes_left; + } while (bytes_left != 0); + + p->data = ret; + p->format = fmt; + p->count = count; + p->type = type; +} + +void *X11_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + XWindowAttributes attributes; + Atom icc_profile_atom; + char icc_atom_string[sizeof("_ICC_PROFILE_") + 12]; + void *ret_icc_profile_data = NULL; + CARD8 *icc_profile_data; + int real_format; + unsigned long real_nitems; + SDL_x11Prop atomProp; + + X11_XGetWindowAttributes(display, data->xwindow, &attributes); + if (X11_XScreenNumberOfScreen(attributes.screen) > 0) { + (void)SDL_snprintf(icc_atom_string, sizeof("_ICC_PROFILE_") + 12, "%s%d", "_ICC_PROFILE_", X11_XScreenNumberOfScreen(attributes.screen)); + } else { + SDL_strlcpy(icc_atom_string, "_ICC_PROFILE", sizeof("_ICC_PROFILE")); + } + X11_XGetWindowAttributes(display, RootWindowOfScreen(attributes.screen), &attributes); + + icc_profile_atom = X11_XInternAtom(display, icc_atom_string, True); + if (icc_profile_atom == None) { + SDL_SetError("Screen is not calibrated."); + return NULL; + } + + X11_ReadProperty(&atomProp, display, RootWindowOfScreen(attributes.screen), icc_profile_atom); + real_format = atomProp.format; + real_nitems = atomProp.count; + icc_profile_data = atomProp.data; + if (real_format == None) { + SDL_SetError("Screen is not calibrated."); + return NULL; + } + + ret_icc_profile_data = SDL_malloc(real_nitems); + if (!ret_icc_profile_data) { + return NULL; + } + + SDL_memcpy(ret_icc_profile_data, icc_profile_data, real_nitems); + *size = real_nitems; + X11_XFree(icc_profile_data); + + return ret_icc_profile_data; +} + +bool X11_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed) +{ + SDL_WindowData *data = window->internal; + Display *display; + + if (!data) { + return SDL_SetError("Invalid window data"); + } + data->mouse_grabbed = false; + + display = data->videodata->display; + + if (grabbed) { + /* If the window is unmapped, XGrab calls return GrabNotViewable, + so when we get a MapNotify later, we'll try to update the grab as + appropriate. */ + if (window->flags & SDL_WINDOW_HIDDEN) { + return true; + } + + /* If XInput2 is enabled, it will grab the pointer on button presses, + * which results in XGrabPointer returning AlreadyGrabbed. If buttons + * are currently pressed, clear any existing grabs before attempting + * the confinement grab. + */ + if (data->xinput2_mouse_enabled && SDL_GetMouseState(NULL, NULL)) { + X11_XUngrabPointer(display, CurrentTime); + } + + // Try to grab the mouse + if (!data->videodata->broken_pointer_grab) { + const unsigned int mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask; + int attempts; + int result = 0; + + // Try for up to 5000ms (5s) to grab. If it still fails, stop trying. + for (attempts = 0; attempts < 100; attempts++) { + result = X11_XGrabPointer(display, data->xwindow, False, mask, GrabModeAsync, + GrabModeAsync, data->xwindow, None, CurrentTime); + if (result == GrabSuccess) { + data->mouse_grabbed = true; + break; + } + SDL_Delay(50); + } + + if (result != GrabSuccess) { + data->videodata->broken_pointer_grab = true; // don't try again. + } + } + + X11_Xinput2GrabTouch(_this, window); + + // Raise the window if we grab the mouse + X11_XRaiseWindow(display, data->xwindow); + } else { + X11_XUngrabPointer(display, CurrentTime); + + X11_Xinput2UngrabTouch(_this, window); + } + X11_XSync(display, False); + + if (!data->videodata->broken_pointer_grab) { + return true; + } else { + return SDL_SetError("The X server refused to let us grab the mouse. You might experience input bugs."); + } +} + +bool X11_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed) +{ + SDL_WindowData *data = window->internal; + Display *display; + + if (!data) { + return SDL_SetError("Invalid window data"); + } + + display = data->videodata->display; + + if (grabbed) { + /* If the window is unmapped, XGrab calls return GrabNotViewable, + so when we get a MapNotify later, we'll try to update the grab as + appropriate. */ + if (window->flags & SDL_WINDOW_HIDDEN) { + return true; + } + + X11_XGrabKeyboard(display, data->xwindow, True, GrabModeAsync, + GrabModeAsync, CurrentTime); + } else { + X11_XUngrabKeyboard(display, CurrentTime); + } + X11_XSync(display, False); + + return true; +} + +void X11_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + + if (data) { + SDL_VideoData *videodata = data->videodata; + Display *display = videodata->display; + int numwindows = videodata->numwindows; + SDL_WindowData **windowlist = videodata->windowlist; + int i; + + if (windowlist) { + for (i = 0; i < numwindows; ++i) { + if (windowlist[i] && (windowlist[i]->window == window)) { + windowlist[i] = windowlist[numwindows - 1]; + windowlist[numwindows - 1] = NULL; + videodata->numwindows--; + break; + } + } + } +#ifdef X_HAVE_UTF8_STRING + if (data->ic) { + X11_XDestroyIC(data->ic); + SDL_free(data->preedit_text); + SDL_free(data->preedit_feedback); + } +#endif + +#ifdef SDL_VIDEO_DRIVER_X11_XSYNC + X11_TermResizeSync(window); +#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */ + + if (!(window->flags & SDL_WINDOW_EXTERNAL)) { + X11_XDestroyWindow(display, data->xwindow); + X11_XFlush(display); + } + SDL_free(data); + +#ifdef SDL_VIDEO_DRIVER_X11_XFIXES + // If the pointer barriers are active for this, deactivate it. + if (videodata->active_cursor_confined_window == window) { + X11_DestroyPointerBarrier(_this, window); + } +#endif // SDL_VIDEO_DRIVER_X11_XFIXES + } + window->internal = NULL; +} + +bool X11_SetWindowHitTest(SDL_Window *window, bool enabled) +{ + return true; // just succeed, the real work is done elsewhere. +} + +void X11_AcceptDragAndDrop(SDL_Window *window, bool accept) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + Atom XdndAware = data->videodata->atoms.XdndAware; + + if (accept) { + Atom xdnd_version = 5; + X11_XChangeProperty(display, data->xwindow, XdndAware, XA_ATOM, 32, + PropModeReplace, (unsigned char *)&xdnd_version, 1); + } else { + X11_XDeleteProperty(display, data->xwindow, XdndAware); + } +} + +bool X11_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + XWMHints *wmhints; + + wmhints = X11_XGetWMHints(display, data->xwindow); + if (!wmhints) { + return SDL_SetError("Couldn't get WM hints"); + } + + wmhints->flags &= ~XUrgencyHint; + data->flashing_window = false; + data->flash_cancel_time = 0; + + switch (operation) { + case SDL_FLASH_CANCEL: + // Taken care of above + break; + case SDL_FLASH_BRIEFLY: + if (!(window->flags & SDL_WINDOW_INPUT_FOCUS)) { + wmhints->flags |= XUrgencyHint; + data->flashing_window = true; + // On Ubuntu 21.04 this causes a dialog to pop up, so leave it up for a full second so users can see it + data->flash_cancel_time = SDL_GetTicks() + 1000; + } + break; + case SDL_FLASH_UNTIL_FOCUSED: + if (!(window->flags & SDL_WINDOW_INPUT_FOCUS)) { + wmhints->flags |= XUrgencyHint; + data->flashing_window = true; + } + break; + default: + break; + } + + X11_XSetWMHints(display, data->xwindow, wmhints); + X11_XFree(wmhints); + return true; +} + +bool SDL_X11_SetWindowTitle(Display *display, Window xwindow, char *title) +{ + Atom _NET_WM_NAME = X11_XInternAtom(display, "_NET_WM_NAME", False); + XTextProperty titleprop; + int conv = X11_XmbTextListToTextProperty(display, &title, 1, XTextStyle, &titleprop); + Status status; + + if (X11_XSupportsLocale() != True) { + return SDL_SetError("Current locale not supported by X server, cannot continue."); + } + + if (conv == 0) { + X11_XSetTextProperty(display, xwindow, &titleprop, XA_WM_NAME); + X11_XFree(titleprop.value); + // we know this can't be a locale error as we checked X locale validity + } else if (conv < 0) { + return SDL_OutOfMemory(); + } else { // conv > 0 + SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "%d characters were not convertible to the current locale!", conv); + return true; + } + +#ifdef X_HAVE_UTF8_STRING + status = X11_Xutf8TextListToTextProperty(display, &title, 1, XUTF8StringStyle, &titleprop); + if (status == Success) { + X11_XSetTextProperty(display, xwindow, &titleprop, _NET_WM_NAME); + X11_XFree(titleprop.value); + } else { + return SDL_SetError("Failed to convert title to UTF8! Bad encoding, or bad Xorg encoding? Window title: «%s»", title); + } +#endif + + X11_XFlush(display); + return true; +} + +void X11_ShowWindowSystemMenu(SDL_Window *window, int x, int y) +{ + SDL_WindowData *data = window->internal; + SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window); + Display *display = data->videodata->display; + Window root = RootWindow(display, displaydata->screen); + XClientMessageEvent e; + Window childReturn; + int wx, wy; + + SDL_zero(e); + X11_XTranslateCoordinates(display, data->xwindow, root, x, y, &wx, &wy, &childReturn); + + e.type = ClientMessage; + e.window = data->xwindow; + e.message_type = X11_XInternAtom(display, "_GTK_SHOW_WINDOW_MENU", 0); + e.data.l[0] = 0; // GTK device ID (unused) + e.data.l[1] = wx; // X coordinate relative to root + e.data.l[2] = wy; // Y coordinate relative to root + e.format = 32; + + X11_XSendEvent(display, root, False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&e); + X11_XFlush(display); +} + +bool X11_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + const Uint64 current_time = SDL_GetTicksNS(); + Uint64 timeout = 0; + + // Allow time for any pending mode switches to complete. + for (int i = 0; i < _this->num_displays; ++i) { + if (_this->displays[i]->internal->mode_switch_deadline_ns && + current_time < _this->displays[i]->internal->mode_switch_deadline_ns) { + timeout = SDL_max(_this->displays[i]->internal->mode_switch_deadline_ns - current_time, timeout); + } + } + + /* 100ms is fine for most cases, but, for some reason, maximizing + * a window can take a very long time. + */ + timeout += window->internal->pending_operation & X11_PENDING_OP_MAXIMIZE ? SDL_MS_TO_NS(1000) : SDL_MS_TO_NS(100); + + return X11_SyncWindowTimeout(_this, window, timeout); +} + +bool X11_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + XWMHints *wmhints; + + wmhints = X11_XGetWMHints(display, data->xwindow); + if (!wmhints) { + return SDL_SetError("Couldn't get WM hints"); + } + + wmhints->input = focusable ? True : False; + wmhints->flags |= InputHint; + + X11_XSetWMHints(display, data->xwindow, wmhints); + X11_XFree(wmhints); + + return true; +} + +#endif // SDL_VIDEO_DRIVER_X11 diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11window.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11window.h new file mode 100644 index 0000000..f1a73ab --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11window.h @@ -0,0 +1,169 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifndef SDL_x11window_h_ +#define SDL_x11window_h_ + +/* We need to queue the focus in/out changes because they may occur during + video mode changes and we can respond to them by triggering more mode + changes. +*/ +#define PENDING_FOCUS_TIME 200 + +#ifdef SDL_VIDEO_OPENGL_EGL +#include +#endif + +typedef enum +{ + PENDING_FOCUS_NONE, + PENDING_FOCUS_IN, + PENDING_FOCUS_OUT +} PendingFocusEnum; + +struct SDL_WindowData +{ + SDL_Window *window; + Window xwindow; + Visual *visual; + Colormap colormap; +#ifndef NO_SHARED_MEMORY + // MIT shared memory extension information + bool use_mitshm; + XShmSegmentInfo shminfo; +#endif + XImage *ximage; + GC gc; + XIC ic; + bool created; + int border_left; + int border_right; + int border_top; + int border_bottom; + bool xinput2_mouse_enabled; + bool xinput2_keyboard_enabled; + bool mouse_grabbed; + Uint64 last_focus_event_time; + PendingFocusEnum pending_focus; + Uint64 pending_focus_time; + bool pending_move; + SDL_Point pending_move_point; + XConfigureEvent last_xconfigure; + struct SDL_VideoData *videodata; + unsigned long user_time; + Atom xdnd_req; + Window xdnd_source; + bool flashing_window; + Uint64 flash_cancel_time; + SDL_Window *keyboard_focus; +#ifdef SDL_VIDEO_OPENGL_EGL + EGLSurface egl_surface; +#endif +#ifdef SDL_VIDEO_DRIVER_X11_XFIXES + bool pointer_barrier_active; + PointerBarrier barrier[4]; + SDL_Rect barrier_rect; +#endif // SDL_VIDEO_DRIVER_X11_XFIXES +#ifdef SDL_VIDEO_DRIVER_X11_XSYNC + XSyncCounter resize_counter; + XSyncValue resize_id; + bool resize_in_progress; +#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */ + + SDL_Rect expected; + SDL_DisplayMode requested_fullscreen_mode; + + enum + { + X11_PENDING_OP_NONE = 0x00, + X11_PENDING_OP_RESTORE = 0x01, + X11_PENDING_OP_MINIMIZE = 0x02, + X11_PENDING_OP_MAXIMIZE = 0x04, + X11_PENDING_OP_FULLSCREEN = 0x08, + X11_PENDING_OP_MOVE = 0x10, + X11_PENDING_OP_RESIZE = 0x20 + } pending_operation; + + enum + { + X11_SIZE_MOVE_EVENTS_DISABLE = 0x01, // Events are completely disabled. + X11_SIZE_MOVE_EVENTS_WAIT_FOR_BORDERS = 0x02, // Events are disabled until a _NET_FRAME_EXTENTS event arrives. + } size_move_event_flags; + + bool pending_size; + bool pending_position; + bool window_was_maximized; + bool previous_borders_nonzero; + bool toggle_borders; + bool fullscreen_borders_forced_on; + SDL_HitTestResult hit_test_result; + + XPoint xim_spot; + char *preedit_text; + XIMFeedback *preedit_feedback; + int preedit_length; + int preedit_cursor; + bool ime_needs_clear_composition; +}; + +extern void X11_SetNetWMState(SDL_VideoDevice *_this, Window xwindow, SDL_WindowFlags flags); +extern Uint32 X11_GetNetWMState(SDL_VideoDevice *_this, SDL_Window *window, Window xwindow); + +extern bool X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); +extern char *X11_GetWindowTitle(SDL_VideoDevice *_this, Window xwindow); +extern void X11_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); +extern bool X11_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon); +extern bool X11_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window); +extern void X11_SetWindowMinimumSize(SDL_VideoDevice *_this, SDL_Window *window); +extern void X11_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window); +extern void X11_SetWindowAspectRatio(SDL_VideoDevice *_this, SDL_Window *window); +extern bool X11_GetWindowBordersSize(SDL_VideoDevice *_this, SDL_Window *window, int *top, int *left, int *bottom, int *right); +extern bool X11_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity); +extern bool X11_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent); +extern bool X11_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal); +extern void X11_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window); +extern void X11_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern void X11_HideWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern void X11_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern void X11_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern void X11_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern void X11_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern void X11_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered); +extern void X11_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable); +extern void X11_SetWindowAlwaysOnTop(SDL_VideoDevice *_this, SDL_Window *window, bool on_top); +extern SDL_FullscreenResult X11_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen); +extern void *X11_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size); +extern bool X11_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed); +extern bool X11_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed); +extern void X11_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern bool X11_SetWindowHitTest(SDL_Window *window, bool enabled); +extern void X11_AcceptDragAndDrop(SDL_Window *window, bool accept); +extern bool X11_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation); +extern void X11_ShowWindowSystemMenu(SDL_Window *window, int x, int y); +extern bool X11_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern bool X11_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable); + +extern bool SDL_X11_SetWindowTitle(Display *display, Window xwindow, char *title); +extern void X11_UpdateWindowPosition(SDL_Window *window, bool use_current_position); +extern void X11_SetWindowMinMax(SDL_Window *window, bool use_current); + +#endif // SDL_x11window_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11xfixes.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11xfixes.c new file mode 100644 index 0000000..517ebc8 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11xfixes.c @@ -0,0 +1,214 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#if defined(SDL_VIDEO_DRIVER_X11) && defined(SDL_VIDEO_DRIVER_X11_XFIXES) + +#include "SDL_x11video.h" +#include "SDL_x11xfixes.h" +#include "../../events/SDL_mouse_c.h" +#include "../../events/SDL_touch_c.h" + +static bool xfixes_initialized = true; +static int xfixes_selection_notify_event = 0; + +static int query_xfixes_version(Display *display, int major, int minor) +{ + // We don't care if this fails, so long as it sets major/minor on it's way out the door. + X11_XFixesQueryVersion(display, &major, &minor); + return (major * 1000) + minor; +} + +static bool xfixes_version_atleast(const int version, const int wantmajor, const int wantminor) +{ + return version >= ((wantmajor * 1000) + wantminor); +} + +void X11_InitXfixes(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + + int version = 0; + int event, error; + int fixes_opcode; + + Atom XA_CLIPBOARD = data->atoms.CLIPBOARD; + + if (!SDL_X11_HAVE_XFIXES || + !X11_XQueryExtension(data->display, "XFIXES", &fixes_opcode, &event, &error)) { + return; + } + + // Selection tracking is available in all versions of XFixes + xfixes_selection_notify_event = event + XFixesSelectionNotify; + X11_XFixesSelectSelectionInput(data->display, DefaultRootWindow(data->display), + XA_CLIPBOARD, XFixesSetSelectionOwnerNotifyMask); + X11_XFixesSelectSelectionInput(data->display, DefaultRootWindow(data->display), + XA_PRIMARY, XFixesSetSelectionOwnerNotifyMask); + + // We need at least 5.0 for barriers. + version = query_xfixes_version(data->display, 5, 0); + if (!xfixes_version_atleast(version, 5, 0)) { + return; // X server does not support the version we want at all. + } + + xfixes_initialized = 1; +} + +bool X11_XfixesIsInitialized(void) +{ + return xfixes_initialized; +} + +int X11_GetXFixesSelectionNotifyEvent(void) +{ + return xfixes_selection_notify_event; +} + +bool X11_SetWindowMouseRect(SDL_VideoDevice *_this, SDL_Window *window) +{ + if (SDL_RectEmpty(&window->mouse_rect)) { + X11_ConfineCursorWithFlags(_this, window, NULL, 0); + } else { + if (window->flags & SDL_WINDOW_INPUT_FOCUS) { + X11_ConfineCursorWithFlags(_this, window, &window->mouse_rect, 0); + } else { + // Save the state for when we get focus again + SDL_WindowData *wdata = window->internal; + + SDL_memcpy(&wdata->barrier_rect, &window->mouse_rect, sizeof(wdata->barrier_rect)); + + wdata->pointer_barrier_active = true; + } + } + + return true; +} + +bool X11_ConfineCursorWithFlags(SDL_VideoDevice *_this, SDL_Window *window, const SDL_Rect *rect, int flags) +{ + /* Yaakuro: For some reason Xfixes when confining inside a rect where the + * edges exactly match, a rectangle the cursor 'slips' out of the barrier. + * To prevent that the lines for the barriers will span the whole screen. + */ + SDL_VideoData *data = _this->internal; + SDL_WindowData *wdata; + + if (!X11_XfixesIsInitialized()) { + return SDL_Unsupported(); + } + + // If there is already a set of barriers active, disable them. + if (data->active_cursor_confined_window) { + X11_DestroyPointerBarrier(_this, data->active_cursor_confined_window); + } + + SDL_assert(window != NULL); + wdata = window->internal; + + /* If user did not specify an area to confine, destroy the barrier that was/is assigned to + * this window it was assigned */ + if (rect) { + int x1, y1, x2, y2; + SDL_Rect bounds; + SDL_GetWindowPosition(window, &bounds.x, &bounds.y); + SDL_GetWindowSize(window, &bounds.w, &bounds.h); + + /** Negative values are not allowed. Clip values relative to the specified window. */ + x1 = bounds.x + SDL_max(rect->x, 0); + y1 = bounds.y + SDL_max(rect->y, 0); + x2 = SDL_min(bounds.x + rect->x + rect->w, bounds.x + bounds.w); + y2 = SDL_min(bounds.y + rect->y + rect->h, bounds.y + bounds.h); + + if ((wdata->barrier_rect.x != rect->x) || + (wdata->barrier_rect.y != rect->y) || + (wdata->barrier_rect.w != rect->w) || + (wdata->barrier_rect.h != rect->h)) { + wdata->barrier_rect = *rect; + } + + // Use the display bounds to ensure the barriers don't have corner gaps + SDL_GetDisplayBounds(SDL_GetDisplayForWindow(window), &bounds); + + /** Create the left barrier */ + wdata->barrier[0] = X11_XFixesCreatePointerBarrier(data->display, wdata->xwindow, + x1, bounds.y, + x1, bounds.y + bounds.h, + BarrierPositiveX, + 0, NULL); + /** Create the right barrier */ + wdata->barrier[1] = X11_XFixesCreatePointerBarrier(data->display, wdata->xwindow, + x2, bounds.y, + x2, bounds.y + bounds.h, + BarrierNegativeX, + 0, NULL); + /** Create the top barrier */ + wdata->barrier[2] = X11_XFixesCreatePointerBarrier(data->display, wdata->xwindow, + bounds.x, y1, + bounds.x + bounds.w, y1, + BarrierPositiveY, + 0, NULL); + /** Create the bottom barrier */ + wdata->barrier[3] = X11_XFixesCreatePointerBarrier(data->display, wdata->xwindow, + bounds.x, y2, + bounds.x + bounds.w, y2, + BarrierNegativeY, + 0, NULL); + + X11_XFlush(data->display); + + // Lets remember current active confined window. + data->active_cursor_confined_window = window; + + /* User activated the confinement for this window. We use this later to reactivate + * the confinement if it got deactivated by FocusOut or UnmapNotify */ + wdata->pointer_barrier_active = true; + } else { + X11_DestroyPointerBarrier(_this, window); + + // Only set barrier inactive when user specified NULL and not handled by focus out. + if (flags != X11_BARRIER_HANDLED_BY_EVENT) { + wdata->pointer_barrier_active = false; + } + } + return true; +} + +void X11_DestroyPointerBarrier(SDL_VideoDevice *_this, SDL_Window *window) +{ + int i; + SDL_VideoData *data = _this->internal; + if (window) { + SDL_WindowData *wdata = window->internal; + + for (i = 0; i < 4; i++) { + if (wdata->barrier[i] > 0) { + X11_XFixesDestroyPointerBarrier(data->display, wdata->barrier[i]); + wdata->barrier[i] = 0; + } + } + X11_XFlush(data->display); + } + data->active_cursor_confined_window = NULL; +} + +#endif // SDL_VIDEO_DRIVER_X11 && SDL_VIDEO_DRIVER_X11_XFIXES diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11xfixes.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11xfixes.h new file mode 100644 index 0000000..bd8e437 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11xfixes.h @@ -0,0 +1,39 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifndef SDL_x11xfixes_h_ +#define SDL_x11xfixes_h_ + +#ifdef SDL_VIDEO_DRIVER_X11_XFIXES + +#define X11_BARRIER_HANDLED_BY_EVENT 1 + +extern void X11_InitXfixes(SDL_VideoDevice *_this); +extern bool X11_XfixesIsInitialized(void); +extern bool X11_SetWindowMouseRect(SDL_VideoDevice *_this, SDL_Window *window); +extern bool X11_ConfineCursorWithFlags(SDL_VideoDevice *_this, SDL_Window *window, const SDL_Rect *rect, int flags); +extern void X11_DestroyPointerBarrier(SDL_VideoDevice *_this, SDL_Window *window); +extern int X11_GetXFixesSelectionNotifyEvent(void); +#endif // SDL_VIDEO_DRIVER_X11_XFIXES + +#endif // SDL_x11xfixes_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11xinput2.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11xinput2.c new file mode 100644 index 0000000..afe4a7c --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11xinput2.c @@ -0,0 +1,829 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifdef SDL_VIDEO_DRIVER_X11 + +#include "SDL_x11pen.h" +#include "SDL_x11video.h" +#include "SDL_x11xinput2.h" +#include "../../events/SDL_events_c.h" +#include "../../events/SDL_mouse_c.h" +#include "../../events/SDL_pen_c.h" +#include "../../events/SDL_touch_c.h" + +#define MAX_AXIS 16 + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 +static bool xinput2_initialized; + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH +static bool xinput2_multitouch_supported; +#endif + +/* Opcode returned X11_XQueryExtension + * It will be used in event processing + * to know that the event came from + * this extension */ +static int xinput2_opcode; + +static void parse_valuators(const double *input_values, const unsigned char *mask, int mask_len, + double *output_values, int output_values_len) +{ + int i = 0, z = 0; + int top = mask_len * 8; + if (top > MAX_AXIS) { + top = MAX_AXIS; + } + + SDL_memset(output_values, 0, output_values_len * sizeof(double)); + for (; i < top && z < output_values_len; i++) { + if (XIMaskIsSet(mask, i)) { + const int value = (int)*input_values; + output_values[z] = value; + input_values++; + } + z++; + } +} + +static int query_xinput2_version(Display *display, int major, int minor) +{ + // We don't care if this fails, so long as it sets major/minor on it's way out the door. + X11_XIQueryVersion(display, &major, &minor); + return (major * 1000) + minor; +} + +static bool xinput2_version_atleast(const int version, const int wantmajor, const int wantminor) +{ + return version >= ((wantmajor * 1000) + wantminor); +} + +static SDL_WindowData *xinput2_get_sdlwindowdata(SDL_VideoData *videodata, Window window) +{ + int i; + for (i = 0; i < videodata->numwindows; i++) { + SDL_WindowData *d = videodata->windowlist[i]; + if (d->xwindow == window) { + return d; + } + } + return NULL; +} + +static SDL_Window *xinput2_get_sdlwindow(SDL_VideoData *videodata, Window window) +{ + const SDL_WindowData *windowdata = xinput2_get_sdlwindowdata(videodata, window); + return windowdata ? windowdata->window : NULL; +} + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH +static void xinput2_normalize_touch_coordinates(SDL_Window *window, double in_x, double in_y, float *out_x, float *out_y) +{ + if (window) { + if (window->w == 1) { + *out_x = 0.5f; + } else { + *out_x = (float)in_x / (window->w - 1); + } + if (window->h == 1) { + *out_y = 0.5f; + } else { + *out_y = (float)in_y / (window->h - 1); + } + } else { + // couldn't find the window... + *out_x = (float)in_x; + *out_y = (float)in_y; + } +} +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 + +bool X11_InitXinput2(SDL_VideoDevice *_this) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + SDL_VideoData *data = _this->internal; + + int version = 0; + XIEventMask eventmask; + unsigned char mask[4] = { 0, 0, 0, 0 }; + int event, err; + + /* XInput2 is required for relative mouse mode, so you probably want to leave this enabled */ + if (!SDL_GetHintBoolean("SDL_VIDEO_X11_XINPUT2", true)) { + return false; + } + + /* + * Initialize XInput 2 + * According to http://who-t.blogspot.com/2009/05/xi2-recipes-part-1.html its better + * to inform Xserver what version of Xinput we support.The server will store the version we support. + * "As XI2 progresses it becomes important that you use this call as the server may treat the client + * differently depending on the supported version". + * + * FIXME:event and err are not needed but if not passed X11_XQueryExtension returns SegmentationFault + */ + if (!SDL_X11_HAVE_XINPUT2 || + !X11_XQueryExtension(data->display, "XInputExtension", &xinput2_opcode, &event, &err)) { + return false; // X server does not have XInput at all + } + + // We need at least 2.2 for Multitouch, 2.0 otherwise. + version = query_xinput2_version(data->display, 2, 2); + if (!xinput2_version_atleast(version, 2, 0)) { + return false; // X server does not support the version we want at all. + } + + xinput2_initialized = true; + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH // Multitouch needs XInput 2.2 + xinput2_multitouch_supported = xinput2_version_atleast(version, 2, 2); +#endif + + // Enable raw motion events for this display + SDL_zero(eventmask); + SDL_zeroa(mask); + eventmask.deviceid = XIAllMasterDevices; + eventmask.mask_len = sizeof(mask); + eventmask.mask = mask; + + XISetMask(mask, XI_RawMotion); + XISetMask(mask, XI_RawButtonPress); + XISetMask(mask, XI_RawButtonRelease); + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + // Enable raw touch events if supported + if (X11_Xinput2IsMultitouchSupported()) { + XISetMask(mask, XI_RawTouchBegin); + XISetMask(mask, XI_RawTouchUpdate); + XISetMask(mask, XI_RawTouchEnd); + } +#endif + + X11_XISelectEvents(data->display, DefaultRootWindow(data->display), &eventmask, 1); + + SDL_zero(eventmask); + SDL_zeroa(mask); + eventmask.deviceid = XIAllDevices; + eventmask.mask_len = sizeof(mask); + eventmask.mask = mask; + + XISetMask(mask, XI_HierarchyChanged); + X11_XISelectEvents(data->display, DefaultRootWindow(data->display), &eventmask, 1); + + X11_Xinput2UpdateDevices(_this, true); + + return true; +#else + return false; +#endif +} + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 +// xi2 device went away? take it out of the list. +static void xinput2_remove_device_info(SDL_VideoData *videodata, const int device_id) +{ + SDL_XInput2DeviceInfo *prev = NULL; + SDL_XInput2DeviceInfo *devinfo; + + for (devinfo = videodata->mouse_device_info; devinfo; devinfo = devinfo->next) { + if (devinfo->device_id == device_id) { + SDL_assert((devinfo == videodata->mouse_device_info) == (prev == NULL)); + if (!prev) { + videodata->mouse_device_info = devinfo->next; + } else { + prev->next = devinfo->next; + } + SDL_free(devinfo); + return; + } + prev = devinfo; + } +} + +static SDL_XInput2DeviceInfo *xinput2_get_device_info(SDL_VideoData *videodata, const int device_id) +{ + // cache device info as we see new devices. + SDL_XInput2DeviceInfo *prev = NULL; + SDL_XInput2DeviceInfo *devinfo; + XIDeviceInfo *xidevinfo; + int axis = 0; + int i; + + for (devinfo = videodata->mouse_device_info; devinfo; devinfo = devinfo->next) { + if (devinfo->device_id == device_id) { + SDL_assert((devinfo == videodata->mouse_device_info) == (prev == NULL)); + if (prev) { // move this to the front of the list, assuming we'll get more from this one. + prev->next = devinfo->next; + devinfo->next = videodata->mouse_device_info; + videodata->mouse_device_info = devinfo; + } + return devinfo; + } + prev = devinfo; + } + + // don't know about this device yet, query and cache it. + devinfo = (SDL_XInput2DeviceInfo *)SDL_calloc(1, sizeof(SDL_XInput2DeviceInfo)); + if (!devinfo) { + return NULL; + } + + xidevinfo = X11_XIQueryDevice(videodata->display, device_id, &i); + if (!xidevinfo) { + SDL_free(devinfo); + return NULL; + } + + devinfo->device_id = device_id; + + /* !!! FIXME: this is sort of hacky because we only care about the first two axes we see, but any given + !!! FIXME: axis could be relative or absolute, and they might not even be the X and Y axes! + !!! FIXME: But we go on, for now. Maybe we need a more robust mouse API in SDL3... */ + for (i = 0; i < xidevinfo->num_classes; i++) { + const XIValuatorClassInfo *v = (const XIValuatorClassInfo *)xidevinfo->classes[i]; + if (v->type == XIValuatorClass) { + devinfo->relative[axis] = (v->mode == XIModeRelative); + devinfo->minval[axis] = v->min; + devinfo->maxval[axis] = v->max; + if (++axis >= 2) { + break; + } + } + } + + X11_XIFreeDeviceInfo(xidevinfo); + + devinfo->next = videodata->mouse_device_info; + videodata->mouse_device_info = devinfo; + + return devinfo; +} +#endif + +void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + SDL_VideoData *videodata = _this->internal; + + if (cookie->extension != xinput2_opcode) { + return; + } + + switch (cookie->evtype) { + case XI_HierarchyChanged: + { + const XIHierarchyEvent *hierev = (const XIHierarchyEvent *)cookie->data; + int i; + for (i = 0; i < hierev->num_info; i++) { + // pen stuff... + if ((hierev->info[i].flags & (XISlaveRemoved | XIDeviceDisabled)) != 0) { + X11_RemovePenByDeviceID(hierev->info[i].deviceid); // it's okay if this thing isn't actually a pen, it'll handle it. + } else if ((hierev->info[i].flags & (XISlaveAdded | XIDeviceEnabled)) != 0) { + X11_MaybeAddPenByDeviceID(_this, hierev->info[i].deviceid); // this will do more checks to make sure this is valid. + } + + // not pen stuff... + if (hierev->info[i].flags & XISlaveRemoved) { + xinput2_remove_device_info(videodata, hierev->info[i].deviceid); + } + } + videodata->xinput_hierarchy_changed = true; + } break; + + // !!! FIXME: the pen code used to rescan all devices here, but we can do this device-by-device with XI_HierarchyChanged. When do these events fire and why? + //case XI_PropertyEvent: + //case XI_DeviceChanged: + + case XI_RawMotion: + { + const XIRawEvent *rawev = (const XIRawEvent *)cookie->data; + const bool is_pen = X11_FindPenByDeviceID(rawev->sourceid) != NULL; + SDL_Mouse *mouse = SDL_GetMouse(); + SDL_XInput2DeviceInfo *devinfo; + double coords[2]; + double processed_coords[2]; + int i; + Uint64 timestamp = X11_GetEventTimestamp(rawev->time); + + videodata->global_mouse_changed = true; + if (is_pen) { + break; // Pens check for XI_Motion instead + } + + devinfo = xinput2_get_device_info(videodata, rawev->deviceid); + if (!devinfo) { + break; // oh well. + } + + parse_valuators(rawev->raw_values, rawev->valuators.mask, + rawev->valuators.mask_len, coords, 2); + + for (i = 0; i < 2; i++) { + if (devinfo->relative[i]) { + processed_coords[i] = coords[i]; + } else { + processed_coords[i] = devinfo->prev_coords[i] - coords[i]; // convert absolute to relative + } + } + + // Relative mouse motion is delivered to the window with keyboard focus + if (mouse->relative_mode && SDL_GetKeyboardFocus()) { + SDL_SendMouseMotion(timestamp, mouse->focus, (SDL_MouseID)rawev->sourceid, true, (float)processed_coords[0], (float)processed_coords[1]); + } + + devinfo->prev_coords[0] = coords[0]; + devinfo->prev_coords[1] = coords[1]; + } break; + + case XI_KeyPress: + case XI_KeyRelease: + { + const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; + SDL_WindowData *windowdata = X11_FindWindow(_this, xev->event); + XEvent xevent; + + if (xev->deviceid != xev->sourceid) { + // Discard events from "Master" devices to avoid duplicates. + break; + } + + if (cookie->evtype == XI_KeyPress) { + xevent.type = KeyPress; + } else { + xevent.type = KeyRelease; + } + xevent.xkey.serial = xev->serial; + xevent.xkey.send_event = xev->send_event; + xevent.xkey.display = xev->display; + xevent.xkey.window = xev->event; + xevent.xkey.root = xev->root; + xevent.xkey.subwindow = xev->child; + xevent.xkey.time = xev->time; + xevent.xkey.x = (int)xev->event_x; + xevent.xkey.y = (int)xev->event_y; + xevent.xkey.x_root = (int)xev->root_x; + xevent.xkey.y_root = (int)xev->root_y; + xevent.xkey.state = xev->mods.effective; + xevent.xkey.keycode = xev->detail; + xevent.xkey.same_screen = 1; + + X11_HandleKeyEvent(_this, windowdata, (SDL_KeyboardID)xev->sourceid, &xevent); + } break; + + case XI_RawButtonPress: + case XI_RawButtonRelease: +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + case XI_RawTouchBegin: + case XI_RawTouchUpdate: + case XI_RawTouchEnd: +#endif + { + videodata->global_mouse_changed = true; + } break; + + case XI_ButtonPress: + case XI_ButtonRelease: + { + const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; + X11_PenHandle *pen = X11_FindPenByDeviceID(xev->deviceid); + const int button = xev->detail; + const bool down = (cookie->evtype == XI_ButtonPress); + + if (pen) { + // Only report button event; if there was also pen movement / pressure changes, we expect an XI_Motion event first anyway. + SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); + if (button == 1) { // button 1 is the pen tip + SDL_SendPenTouch(0, pen->pen, window, pen->is_eraser, down); + } else { + SDL_SendPenButton(0, pen->pen, window, button - 1, down); + } + } else { + // Otherwise assume a regular mouse + SDL_WindowData *windowdata = xinput2_get_sdlwindowdata(videodata, xev->event); + + if (xev->deviceid != xev->sourceid) { + // Discard events from "Master" devices to avoid duplicates. + break; + } + + if (down) { + X11_HandleButtonPress(_this, windowdata, (SDL_MouseID)xev->sourceid, button, + (float)xev->event_x, (float)xev->event_y, xev->time); + } else { + X11_HandleButtonRelease(_this, windowdata, (SDL_MouseID)xev->sourceid, button, xev->time); + } + } + } break; + + /* Register to receive XI_Motion (which deactivates MotionNotify), so that we can distinguish + real mouse motions from synthetic ones, for multitouch and pen support. */ + case XI_Motion: + { + const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + bool pointer_emulated = ((xev->flags & XIPointerEmulated) != 0); +#else + bool pointer_emulated = false; +#endif + + videodata->global_mouse_changed = true; + + X11_PenHandle *pen = X11_FindPenByDeviceID(xev->deviceid); + if (pen) { + if (xev->deviceid != xev->sourceid) { + // Discard events from "Master" devices to avoid duplicates. + break; + } + + SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); + SDL_SendPenMotion(0, pen->pen, window, (float) xev->event_x, (float) xev->event_y); + + float axes[SDL_PEN_AXIS_COUNT]; + X11_PenAxesFromValuators(pen, xev->valuators.values, xev->valuators.mask, xev->valuators.mask_len, axes); + + for (int i = 0; i < SDL_arraysize(axes); i++) { + if (pen->valuator_for_axis[i] != SDL_X11_PEN_AXIS_VALUATOR_MISSING) { + SDL_SendPenAxis(0, pen->pen, window, (SDL_PenAxis) i, axes[i]); + } + } + } else if (!pointer_emulated && xev->deviceid == videodata->xinput_master_pointer_device) { + // Use the master device for non-relative motion, as the slave devices can seemingly lag behind. + SDL_Mouse *mouse = SDL_GetMouse(); + if (!mouse->relative_mode) { + SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); + if (window) { + X11_ProcessHitTest(_this, window->internal, (float)xev->event_x, (float)xev->event_y, false); + SDL_SendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, (float)xev->event_x, (float)xev->event_y); + } + } + } + } break; + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + case XI_TouchBegin: + { + const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; + float x, y; + SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); + xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); + SDL_SendTouch(0, xev->sourceid, xev->detail, window, SDL_EVENT_FINGER_DOWN, x, y, 1.0); + } break; + + case XI_TouchEnd: + { + const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; + float x, y; + SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); + xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); + SDL_SendTouch(0, xev->sourceid, xev->detail, window, SDL_EVENT_FINGER_UP, x, y, 1.0); + } break; + + case XI_TouchUpdate: + { + const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; + float x, y; + SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); + xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); + SDL_SendTouchMotion(0, xev->sourceid, xev->detail, window, x, y, 1.0); + } break; +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + } +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 +} + +void X11_InitXinput2Multitouch(SDL_VideoDevice *_this) +{ +} + +void X11_Xinput2SelectTouch(SDL_VideoDevice *_this, SDL_Window *window) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + SDL_VideoData *data = NULL; + XIEventMask eventmask; + unsigned char mask[4] = { 0, 0, 0, 0 }; + SDL_WindowData *window_data = NULL; + + if (!X11_Xinput2IsMultitouchSupported()) { + return; + } + + data = _this->internal; + window_data = window->internal; + + eventmask.deviceid = XIAllMasterDevices; + eventmask.mask_len = sizeof(mask); + eventmask.mask = mask; + + XISetMask(mask, XI_TouchBegin); + XISetMask(mask, XI_TouchUpdate); + XISetMask(mask, XI_TouchEnd); + XISetMask(mask, XI_Motion); + + X11_XISelectEvents(data->display, window_data->xwindow, &eventmask, 1); +#endif +} + +bool X11_Xinput2IsInitialized(void) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + return xinput2_initialized; +#else + return false; +#endif +} + +bool X11_Xinput2SelectMouseAndKeyboard(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *windowdata = window->internal; + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + const SDL_VideoData *data = _this->internal; + + if (X11_Xinput2IsInitialized()) { + XIEventMask eventmask; + unsigned char mask[4] = { 0, 0, 0, 0 }; + + eventmask.mask_len = sizeof(mask); + eventmask.mask = mask; + eventmask.deviceid = XIAllDevices; + +// This is not enabled by default because these events are only delivered to the window with mouse focus, not keyboard focus +#ifdef USE_XINPUT2_KEYBOARD + XISetMask(mask, XI_KeyPress); + XISetMask(mask, XI_KeyRelease); + windowdata->xinput2_keyboard_enabled = true; +#endif + + XISetMask(mask, XI_ButtonPress); + XISetMask(mask, XI_ButtonRelease); + XISetMask(mask, XI_Motion); + windowdata->xinput2_mouse_enabled = true; + + XISetMask(mask, XI_Enter); + XISetMask(mask, XI_Leave); + + // Hotplugging: + XISetMask(mask, XI_DeviceChanged); + XISetMask(mask, XI_HierarchyChanged); + XISetMask(mask, XI_PropertyEvent); // E.g., when swapping tablet pens + + if (X11_XISelectEvents(data->display, windowdata->xwindow, &eventmask, 1) != Success) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Could not enable XInput2 event handling"); + windowdata->xinput2_keyboard_enabled = false; + windowdata->xinput2_mouse_enabled = false; + } + } +#endif + + if (windowdata->xinput2_keyboard_enabled || windowdata->xinput2_mouse_enabled) { + return true; + } + return false; +} + +bool X11_Xinput2IsMultitouchSupported(void) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + return xinput2_initialized && xinput2_multitouch_supported; +#else + return true; +#endif +} + +void X11_Xinput2GrabTouch(SDL_VideoDevice *_this, SDL_Window *window) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + + unsigned char mask[4] = { 0, 0, 0, 0 }; + XIGrabModifiers mods; + XIEventMask eventmask; + + if (!X11_Xinput2IsMultitouchSupported()) { + return; + } + + mods.modifiers = XIAnyModifier; + mods.status = 0; + + eventmask.deviceid = XIAllDevices; + eventmask.mask_len = sizeof(mask); + eventmask.mask = mask; + + XISetMask(eventmask.mask, XI_TouchBegin); + XISetMask(eventmask.mask, XI_TouchUpdate); + XISetMask(eventmask.mask, XI_TouchEnd); + XISetMask(eventmask.mask, XI_Motion); + + X11_XIGrabTouchBegin(display, XIAllDevices, data->xwindow, True, &eventmask, 1, &mods); +#endif +} + +void X11_Xinput2UngrabTouch(SDL_VideoDevice *_this, SDL_Window *window) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + + XIGrabModifiers mods; + + if (!X11_Xinput2IsMultitouchSupported()) { + return; + } + + mods.modifiers = XIAnyModifier; + mods.status = 0; + + X11_XIUngrabTouchBegin(display, XIAllDevices, data->xwindow, 1, &mods); +#endif +} + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + +static void AddDeviceID(Uint32 deviceID, Uint32 **list, int *count) +{ + int new_count = (*count + 1); + Uint32 *new_list = (Uint32 *)SDL_realloc(*list, new_count * sizeof(*new_list)); + if (!new_list) { + // Oh well, we'll drop this one + return; + } + new_list[new_count - 1] = deviceID; + + *count = new_count; + *list = new_list; +} + +static bool HasDeviceID(Uint32 deviceID, const Uint32 *list, int count) +{ + for (int i = 0; i < count; ++i) { + if (deviceID == list[i]) { + return true; + } + } + return false; +} + +static void AddDeviceID64(Uint64 deviceID, Uint64 **list, int *count) +{ + int new_count = (*count + 1); + Uint64 *new_list = (Uint64 *)SDL_realloc(*list, new_count * sizeof(*new_list)); + if (!new_list) { + // Oh well, we'll drop this one + return; + } + new_list[new_count - 1] = deviceID; + + *count = new_count; + *list = new_list; +} + +static bool HasDeviceID64(Uint64 deviceID, const Uint64 *list, int count) +{ + for (int i = 0; i < count; ++i) { + if (deviceID == list[i]) { + return true; + } + } + return false; +} + +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 + +void X11_Xinput2UpdateDevices(SDL_VideoDevice *_this, bool initial_check) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + SDL_VideoData *data = _this->internal; + XIDeviceInfo *info; + int ndevices; + int old_keyboard_count = 0; + SDL_KeyboardID *old_keyboards = NULL; + int new_keyboard_count = 0; + SDL_KeyboardID *new_keyboards = NULL; + int old_mouse_count = 0; + SDL_MouseID *old_mice = NULL; + int new_mouse_count = 0; + SDL_MouseID *new_mice = NULL; + int old_touch_count = 0; + Uint64 *old_touch_devices = NULL; + int new_touch_count = 0; + Uint64 *new_touch_devices = NULL; + bool send_event = !initial_check; + + SDL_assert(X11_Xinput2IsInitialized()); + + info = X11_XIQueryDevice(data->display, XIAllDevices, &ndevices); + + old_keyboards = SDL_GetKeyboards(&old_keyboard_count); + old_mice = SDL_GetMice(&old_mouse_count); + old_touch_devices = SDL_GetTouchDevices(&old_touch_count); + + for (int i = 0; i < ndevices; i++) { + XIDeviceInfo *dev = &info[i]; + + switch (dev->use) { + case XIMasterKeyboard: + case XISlaveKeyboard: + { + SDL_KeyboardID keyboardID = (SDL_KeyboardID)dev->deviceid; + AddDeviceID(keyboardID, &new_keyboards, &new_keyboard_count); + if (!HasDeviceID(keyboardID, old_keyboards, old_keyboard_count)) { + SDL_AddKeyboard(keyboardID, dev->name, send_event); + } + } + break; + case XIMasterPointer: + data->xinput_master_pointer_device = dev->deviceid; + SDL_FALLTHROUGH; + case XISlavePointer: + { + SDL_MouseID mouseID = (SDL_MouseID)dev->deviceid; + AddDeviceID(mouseID, &new_mice, &new_mouse_count); + if (!HasDeviceID(mouseID, old_mice, old_mouse_count)) { + SDL_AddMouse(mouseID, dev->name, send_event); + } + } + break; + default: + break; + } + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + for (int j = 0; j < dev->num_classes; j++) { + Uint64 touchID; + SDL_TouchDeviceType touchType; + XIAnyClassInfo *class = dev->classes[j]; + XITouchClassInfo *t = (XITouchClassInfo *)class; + + // Only touch devices + if (class->type != XITouchClass) { + continue; + } + + touchID = (Uint64)t->sourceid; + AddDeviceID64(touchID, &new_touch_devices, &new_touch_count); + if (!HasDeviceID64(touchID, old_touch_devices, old_touch_count)) { + if (t->mode == XIDependentTouch) { + touchType = SDL_TOUCH_DEVICE_INDIRECT_RELATIVE; + } else { // XIDirectTouch + touchType = SDL_TOUCH_DEVICE_DIRECT; + } + SDL_AddTouch(touchID, touchType, dev->name); + } + } +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + } + + for (int i = old_keyboard_count; i--;) { + if (!HasDeviceID(old_keyboards[i], new_keyboards, new_keyboard_count)) { + SDL_RemoveKeyboard(old_keyboards[i], send_event); + } + } + + for (int i = old_mouse_count; i--;) { + if (!HasDeviceID(old_mice[i], new_mice, new_mouse_count)) { + SDL_RemoveMouse(old_mice[i], send_event); + } + } + + for (int i = old_touch_count; i--;) { + if (!HasDeviceID64(old_touch_devices[i], new_touch_devices, new_touch_count)) { + SDL_DelTouch(old_touch_devices[i]); + } + } + + SDL_free(old_keyboards); + SDL_free(new_keyboards); + SDL_free(old_mice); + SDL_free(new_mice); + SDL_free(old_touch_devices); + SDL_free(new_touch_devices); + + X11_XIFreeDeviceInfo(info); + +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 +} + +#endif // SDL_VIDEO_DRIVER_X11 diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11xinput2.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11xinput2.h new file mode 100644 index 0000000..c96c020 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11xinput2.h @@ -0,0 +1,44 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifndef SDL_x11xinput2_h_ +#define SDL_x11xinput2_h_ + +#ifndef SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS +/* Define XGenericEventCookie as forward declaration when + *xinput2 is not available in order to compile */ +struct XGenericEventCookie; +typedef struct XGenericEventCookie XGenericEventCookie; +#endif + +extern bool X11_InitXinput2(SDL_VideoDevice *_this); +extern void X11_InitXinput2Multitouch(SDL_VideoDevice *_this); +extern void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie); +extern bool X11_Xinput2IsInitialized(void); +extern bool X11_Xinput2IsMultitouchSupported(void); +extern void X11_Xinput2SelectTouch(SDL_VideoDevice *_this, SDL_Window *window); +extern void X11_Xinput2GrabTouch(SDL_VideoDevice *_this, SDL_Window *window); +extern void X11_Xinput2UngrabTouch(SDL_VideoDevice *_this, SDL_Window *window); +extern bool X11_Xinput2SelectMouseAndKeyboard(SDL_VideoDevice *_this, SDL_Window *window); +extern void X11_Xinput2UpdateDevices(SDL_VideoDevice *_this, bool initial_check); + +#endif // SDL_x11xinput2_h_ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11xsync.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11xsync.c new file mode 100644 index 0000000..5310d67 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11xsync.c @@ -0,0 +1,148 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#if defined(SDL_VIDEO_DRIVER_X11) && defined(SDL_VIDEO_DRIVER_X11_XSYNC) + +#include "SDL_x11video.h" +#include "SDL_x11xsync.h" + +static int xsync_initialized = 0; + +static int query_xsync_version(Display *display, int major, int minor) +{ + /* We don't care if this fails, so long as it sets major/minor on it's way out the door. */ + X11_XSyncInitialize(display, &major, &minor); + return (major * 1000) + minor; +} + +static bool xsync_version_atleast(const int version, const int wantmajor, const int wantminor) +{ + return version >= ((wantmajor * 1000) + wantminor); +} + +void X11_InitXsync(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + + int version = 0; + int event, error; + int sync_opcode; + + if (!SDL_X11_HAVE_XSYNC || + !X11_XQueryExtension(data->display, "SYNC", &sync_opcode, &event, &error)) { + return; + } + + /* We need at least 5.0 for barriers. */ + version = query_xsync_version(data->display, 5, 0); + if (!xsync_version_atleast(version, 3, 0)) { + return; /* X server does not support the version we want at all. */ + } + + xsync_initialized = 1; +} + +int X11_XsyncIsInitialized(void) +{ + return xsync_initialized; +} + +int X11_InitResizeSync(SDL_Window *window) +{ + SDL_assert(window != NULL); + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + Atom counter_prop = data->videodata->atoms._NET_WM_SYNC_REQUEST_COUNTER; + XSyncCounter counter; + CARD32 counter_id; + + if (!X11_XsyncIsInitialized()){ + return SDL_Unsupported(); + } + + counter = X11_XSyncCreateCounter(display, (XSyncValue){0, 0}); + data->resize_counter = counter; + data->resize_id.lo = 0; + data->resize_id.hi = 0; + data->resize_in_progress = false; + + if (counter == None){ + return SDL_Unsupported(); + } + + counter_id = counter; + X11_XChangeProperty(display, data->xwindow, counter_prop, XA_CARDINAL, 32, + PropModeReplace, (unsigned char *)&counter_id, 1); + + return 0; +} + +void X11_TermResizeSync(SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + Atom counter_prop = data->videodata->atoms._NET_WM_SYNC_REQUEST_COUNTER; + XSyncCounter counter = data->resize_counter; + + X11_XDeleteProperty(display, data->xwindow, counter_prop); + if (counter != None) { + X11_XSyncDestroyCounter(display, counter); + } +} + +void X11_HandleSyncRequest(SDL_Window *window, XClientMessageEvent *event) +{ + SDL_WindowData *data = window->internal; + + data->resize_id.lo = event->data.l[2]; + data->resize_id.hi = event->data.l[3]; + data->resize_in_progress = false; +} + +void X11_HandleConfigure(SDL_Window *window, XConfigureEvent *event) +{ + SDL_WindowData *data = window->internal; + + if (data->resize_id.lo || data->resize_id.hi) { + data->resize_in_progress = true; + } +} + +void X11_HandlePresent(SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + XSyncCounter counter = data->resize_counter; + + if ((counter == None) || (!data->resize_in_progress)) { + return; + } + + X11_XSyncSetCounter(display, counter, data->resize_id); + + data->resize_id.lo = 0; + data->resize_id.hi = 0; + data->resize_in_progress = false; +} + +#endif /* SDL_VIDEO_DRIVER_X11 && SDL_VIDEO_DRIVER_X11_XSYNC */ diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11xsync.h b/contrib/SDL-3.2.8/src/video/x11/SDL_x11xsync.h new file mode 100644 index 0000000..bc747c1 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11xsync.h @@ -0,0 +1,39 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 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" + +#ifndef SDL_x11xsync_h_ +#define SDL_x11xsync_h_ + +#ifdef SDL_VIDEO_DRIVER_X11_XSYNC + +extern void X11_InitXsync(SDL_VideoDevice *_this); +extern int X11_XsyncIsInitialized(void); +int X11_InitResizeSync(SDL_Window *window); +void X11_TermResizeSync(SDL_Window *window); +void X11_HandleSyncRequest(SDL_Window *window, XClientMessageEvent *event); +void X11_HandleConfigure(SDL_Window *window, XConfigureEvent *event); +void X11_HandlePresent(SDL_Window *window); + +#endif /* SDL_VIDEO_DRIVER_X11_XSYNC */ + +#endif /* SDL_x11xsync_h_ */ diff --git a/contrib/SDL-3.2.8/src/video/x11/edid-parse.c b/contrib/SDL-3.2.8/src/video/x11/edid-parse.c new file mode 100644 index 0000000..eca187b --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/edid-parse.c @@ -0,0 +1,753 @@ +/* + * Copyright 2007 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * on the rights to use, copy, modify, merge, publish, distribute, sub + * license, and/or sell copies of the Software, and to permit persons to whom + * the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Author: Soren Sandmann */ +#include "SDL_internal.h" + +#include "edid.h" +#include +#include +#include +#include + +#define TRUE 1 +#define FALSE 0 + +static int +get_bit (int in, int bit) +{ + return (in & (1 << bit)) >> bit; +} + +static int +get_bits (int in, int begin, int end) +{ + int mask = (1 << (end - begin + 1)) - 1; + + return (in >> begin) & mask; +} + +static int +decode_header (const uchar *edid) +{ + if (SDL_memcmp (edid, "\x00\xff\xff\xff\xff\xff\xff\x00", 8) == 0) + return TRUE; + return FALSE; +} + +static int +decode_vendor_and_product_identification (const uchar *edid, MonitorInfo *info) +{ + int is_model_year; + + /* Manufacturer Code */ + info->manufacturer_code[0] = get_bits (edid[0x08], 2, 6); + info->manufacturer_code[1] = get_bits (edid[0x08], 0, 1) << 3; + info->manufacturer_code[1] |= get_bits (edid[0x09], 5, 7); + info->manufacturer_code[2] = get_bits (edid[0x09], 0, 4); + info->manufacturer_code[3] = '\0'; + + info->manufacturer_code[0] += 'A' - 1; + info->manufacturer_code[1] += 'A' - 1; + info->manufacturer_code[2] += 'A' - 1; + + /* Product Code */ + info->product_code = edid[0x0b] << 8 | edid[0x0a]; + + /* Serial Number */ + info->serial_number = + edid[0x0c] | edid[0x0d] << 8 | edid[0x0e] << 16 | (Uint32)edid[0x0f] << 24; + + /* Week and Year */ + is_model_year = FALSE; + switch (edid[0x10]) + { + case 0x00: + info->production_week = -1; + break; + + case 0xff: + info->production_week = -1; + is_model_year = TRUE; + break; + + default: + info->production_week = edid[0x10]; + break; + } + + if (is_model_year) + { + info->production_year = -1; + info->model_year = 1990 + edid[0x11]; + } + else + { + info->production_year = 1990 + edid[0x11]; + info->model_year = -1; + } + + return TRUE; +} + +static int +decode_edid_version (const uchar *edid, MonitorInfo *info) +{ + info->major_version = edid[0x12]; + info->minor_version = edid[0x13]; + + return TRUE; +} + +static int +decode_display_parameters (const uchar *edid, MonitorInfo *info) +{ + /* Digital vs Analog */ + info->is_digital = get_bit (edid[0x14], 7); + + if (info->is_digital) + { + int bits; + + static const int bit_depth[8] = + { + -1, 6, 8, 10, 12, 14, 16, -1 + }; + + static const Interface interfaces[6] = + { + UNDEFINED, DVI, HDMI_A, HDMI_B, MDDI, DISPLAY_PORT + }; + + bits = get_bits (edid[0x14], 4, 6); + info->ad.digital.bits_per_primary = bit_depth[bits]; + + bits = get_bits (edid[0x14], 0, 3); + + if (bits <= 5) + info->ad.digital.interface = interfaces[bits]; + else + info->ad.digital.interface = UNDEFINED; + } + else + { + int bits = get_bits (edid[0x14], 5, 6); + + static const double levels[][3] = + { + { 0.7, 0.3, 1.0 }, + { 0.714, 0.286, 1.0 }, + { 1.0, 0.4, 1.4 }, + { 0.7, 0.0, 0.7 }, + }; + + info->ad.analog.video_signal_level = levels[bits][0]; + info->ad.analog.sync_signal_level = levels[bits][1]; + info->ad.analog.total_signal_level = levels[bits][2]; + + info->ad.analog.blank_to_black = get_bit (edid[0x14], 4); + + info->ad.analog.separate_hv_sync = get_bit (edid[0x14], 3); + info->ad.analog.composite_sync_on_h = get_bit (edid[0x14], 2); + info->ad.analog.composite_sync_on_green = get_bit (edid[0x14], 1); + + info->ad.analog.serration_on_vsync = get_bit (edid[0x14], 0); + } + + /* Screen Size / Aspect Ratio */ + if (edid[0x15] == 0 && edid[0x16] == 0) + { + info->width_mm = -1; + info->height_mm = -1; + info->aspect_ratio = -1.0; + } + else if (edid[0x16] == 0) + { + info->width_mm = -1; + info->height_mm = -1; + info->aspect_ratio = 100.0 / (edid[0x15] + 99); + } + else if (edid[0x15] == 0) + { + info->width_mm = -1; + info->height_mm = -1; + info->aspect_ratio = 100.0 / (edid[0x16] + 99); + info->aspect_ratio = 1/info->aspect_ratio; /* portrait */ + } + else + { + info->width_mm = 10 * edid[0x15]; + info->height_mm = 10 * edid[0x16]; + } + + /* Gamma */ + if (edid[0x17] == 0xFF) + info->gamma = -1.0; + else + info->gamma = (edid[0x17] + 100.0) / 100.0; + + /* Features */ + info->standby = get_bit (edid[0x18], 7); + info->suspend = get_bit (edid[0x18], 6); + info->active_off = get_bit (edid[0x18], 5); + + if (info->is_digital) + { + info->ad.digital.rgb444 = TRUE; + if (get_bit (edid[0x18], 3)) + info->ad.digital.ycrcb444 = 1; + if (get_bit (edid[0x18], 4)) + info->ad.digital.ycrcb422 = 1; + } + else + { + int bits = get_bits (edid[0x18], 3, 4); + ColorType color_type[4] = + { + MONOCHROME, RGB, OTHER_COLOR, UNDEFINED_COLOR + }; + + info->ad.analog.color_type = color_type[bits]; + } + + info->srgb_is_standard = get_bit (edid[0x18], 2); + + /* In 1.3 this is called "has preferred timing" */ + info->preferred_timing_includes_native = get_bit (edid[0x18], 1); + + /* FIXME: In 1.3 this indicates whether the monitor accepts GTF */ + info->continuous_frequency = get_bit (edid[0x18], 0); + return TRUE; +} + +static double +decode_fraction (int high, int low) +{ + double result = 0.0; + int i; + + high = (high << 2) | low; + + for (i = 0; i < 10; ++i) + result += get_bit (high, i) * SDL_pow (2, i - 10); + + return result; +} + +static int +decode_color_characteristics (const uchar *edid, MonitorInfo *info) +{ + info->red_x = decode_fraction (edid[0x1b], get_bits (edid[0x19], 6, 7)); + info->red_y = decode_fraction (edid[0x1c], get_bits (edid[0x19], 5, 4)); + info->green_x = decode_fraction (edid[0x1d], get_bits (edid[0x19], 2, 3)); + info->green_y = decode_fraction (edid[0x1e], get_bits (edid[0x19], 0, 1)); + info->blue_x = decode_fraction (edid[0x1f], get_bits (edid[0x1a], 6, 7)); + info->blue_y = decode_fraction (edid[0x20], get_bits (edid[0x1a], 4, 5)); + info->white_x = decode_fraction (edid[0x21], get_bits (edid[0x1a], 2, 3)); + info->white_y = decode_fraction (edid[0x22], get_bits (edid[0x1a], 0, 1)); + + return TRUE; +} + +static int +decode_established_timings (const uchar *edid, MonitorInfo *info) +{ + static const Timing established[][8] = + { + { + { 800, 600, 60 }, + { 800, 600, 56 }, + { 640, 480, 75 }, + { 640, 480, 72 }, + { 640, 480, 67 }, + { 640, 480, 60 }, + { 720, 400, 88 }, + { 720, 400, 70 } + }, + { + { 1280, 1024, 75 }, + { 1024, 768, 75 }, + { 1024, 768, 70 }, + { 1024, 768, 60 }, + { 1024, 768, 87 }, + { 832, 624, 75 }, + { 800, 600, 75 }, + { 800, 600, 72 } + }, + { + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 1152, 870, 75 } + }, + }; + + int i, j, idx; + + idx = 0; + for (i = 0; i < 3; ++i) + { + for (j = 0; j < 8; ++j) + { + int byte = edid[0x23 + i]; + + if (get_bit (byte, j) && established[i][j].frequency != 0) + info->established[idx++] = established[i][j]; + } + } + return TRUE; +} + +static int +decode_standard_timings (const uchar *edid, MonitorInfo *info) +{ + int i; + + for (i = 0; i < 8; i++) + { + int first = edid[0x26 + 2 * i]; + int second = edid[0x27 + 2 * i]; + + if (first != 0x01 && second != 0x01) + { + int w = 8 * (first + 31); + int h = 0; + + switch (get_bits (second, 6, 7)) + { + case 0x00: h = (w / 16) * 10; break; + case 0x01: h = (w / 4) * 3; break; + case 0x02: h = (w / 5) * 4; break; + case 0x03: h = (w / 16) * 9; break; + } + + info->standard[i].width = w; + info->standard[i].height = h; + info->standard[i].frequency = get_bits (second, 0, 5) + 60; + } + } + + return TRUE; +} + +static void +decode_lf_string (const uchar *s, int n_chars, char *result) +{ + int i; + for (i = 0; i < n_chars; ++i) + { + if (s[i] == 0x0a) + { + *result++ = '\0'; + break; + } + else if (s[i] == 0x00) + { + /* Convert embedded 0's to spaces */ + *result++ = ' '; + } + else + { + *result++ = s[i]; + } + } +} + +static void +decode_display_descriptor (const uchar *desc, + MonitorInfo *info) +{ + switch (desc[0x03]) + { + case 0xFC: + decode_lf_string (desc + 5, 13, info->dsc_product_name); + break; + case 0xFF: + decode_lf_string (desc + 5, 13, info->dsc_serial_number); + break; + case 0xFE: + decode_lf_string (desc + 5, 13, info->dsc_string); + break; + case 0xFD: + /* Range Limits */ + break; + case 0xFB: + /* Color Point */ + break; + case 0xFA: + /* Timing Identifications */ + break; + case 0xF9: + /* Color Management */ + break; + case 0xF8: + /* Timing Codes */ + break; + case 0xF7: + /* Established Timings */ + break; + case 0x10: + break; + } +} + +static void +decode_detailed_timing (const uchar *timing, + DetailedTiming *detailed) +{ + int bits; + StereoType stereo[] = + { + NO_STEREO, NO_STEREO, FIELD_RIGHT, FIELD_LEFT, + TWO_WAY_RIGHT_ON_EVEN, TWO_WAY_LEFT_ON_EVEN, + FOUR_WAY_INTERLEAVED, SIDE_BY_SIDE + }; + + detailed->pixel_clock = (timing[0x00] | timing[0x01] << 8) * 10000; + detailed->h_addr = timing[0x02] | ((timing[0x04] & 0xf0) << 4); + detailed->h_blank = timing[0x03] | ((timing[0x04] & 0x0f) << 8); + detailed->v_addr = timing[0x05] | ((timing[0x07] & 0xf0) << 4); + detailed->v_blank = timing[0x06] | ((timing[0x07] & 0x0f) << 8); + detailed->h_front_porch = timing[0x08] | get_bits (timing[0x0b], 6, 7) << 8; + detailed->h_sync = timing[0x09] | get_bits (timing[0x0b], 4, 5) << 8; + detailed->v_front_porch = + get_bits (timing[0x0a], 4, 7) | get_bits (timing[0x0b], 2, 3) << 4; + detailed->v_sync = + get_bits (timing[0x0a], 0, 3) | get_bits (timing[0x0b], 0, 1) << 4; + detailed->width_mm = timing[0x0c] | get_bits (timing[0x0e], 4, 7) << 8; + detailed->height_mm = timing[0x0d] | get_bits (timing[0x0e], 0, 3) << 8; + detailed->right_border = timing[0x0f]; + detailed->top_border = timing[0x10]; + + detailed->interlaced = get_bit (timing[0x11], 7); + + /* Stereo */ + bits = get_bits (timing[0x11], 5, 6) << 1 | get_bit (timing[0x11], 0); + detailed->stereo = stereo[bits]; + + /* Sync */ + bits = timing[0x11]; + + detailed->digital_sync = get_bit (bits, 4); + if (detailed->digital_sync) + { + detailed->ad.digital.composite = !get_bit (bits, 3); + + if (detailed->ad.digital.composite) + { + detailed->ad.digital.serrations = get_bit (bits, 2); + detailed->ad.digital.negative_vsync = FALSE; + } + else + { + detailed->ad.digital.serrations = FALSE; + detailed->ad.digital.negative_vsync = !get_bit (bits, 2); + } + + detailed->ad.digital.negative_hsync = !get_bit (bits, 0); + } + else + { + detailed->ad.analog.bipolar = get_bit (bits, 3); + detailed->ad.analog.serrations = get_bit (bits, 2); + detailed->ad.analog.sync_on_green = !get_bit (bits, 1); + } +} + +static int +decode_descriptors (const uchar *edid, MonitorInfo *info) +{ + int i; + int timing_idx; + + timing_idx = 0; + + for (i = 0; i < 4; ++i) + { + int index = 0x36 + i * 18; + + if (edid[index + 0] == 0x00 && edid[index + 1] == 0x00) + { + decode_display_descriptor (edid + index, info); + } + else + { + decode_detailed_timing ( + edid + index, &(info->detailed_timings[timing_idx++])); + } + } + + info->n_detailed_timings = timing_idx; + + return TRUE; +} + +static void +decode_check_sum (const uchar *edid, + MonitorInfo *info) +{ + int i; + uchar check = 0; + + for (i = 0; i < 128; ++i) + check += edid[i]; + + info->checksum = check; +} + +MonitorInfo * +decode_edid (const uchar *edid) +{ + MonitorInfo *info = SDL_calloc (1, sizeof (MonitorInfo)); + + decode_check_sum (edid, info); + + if (!decode_header (edid) || + !decode_vendor_and_product_identification (edid, info) || + !decode_edid_version (edid, info) || + !decode_display_parameters (edid, info) || + !decode_color_characteristics (edid, info) || + !decode_established_timings (edid, info) || + !decode_standard_timings (edid, info) || + !decode_descriptors (edid, info)) { + SDL_free(info); + return NULL; + } + + return info; +} + +static const char * +yesno (int v) +{ + return v? "yes" : "no"; +} + +void +dump_monitor_info (MonitorInfo *info) +{ + int i; + + printf ("Checksum: %d (%s)\n", + info->checksum, info->checksum? "incorrect" : "correct"); + printf ("Manufacturer Code: %s\n", info->manufacturer_code); + printf ("Product Code: 0x%x\n", info->product_code); + printf ("Serial Number: %u\n", info->serial_number); + + if (info->production_week != -1) + printf ("Production Week: %d\n", info->production_week); + else + printf ("Production Week: unspecified\n"); + + if (info->production_year != -1) + printf ("Production Year: %d\n", info->production_year); + else + printf ("Production Year: unspecified\n"); + + if (info->model_year != -1) + printf ("Model Year: %d\n", info->model_year); + else + printf ("Model Year: unspecified\n"); + + printf ("EDID revision: %d.%d\n", info->major_version, info->minor_version); + + printf ("Display is %s\n", info->is_digital? "digital" : "analog"); + if (info->is_digital) + { + const char *interface; + if (info->ad.digital.bits_per_primary != -1) + printf ("Bits Per Primary: %d\n", info->ad.digital.bits_per_primary); + else + printf ("Bits Per Primary: undefined\n"); + + switch (info->ad.digital.interface) + { + case DVI: interface = "DVI"; break; + case HDMI_A: interface = "HDMI-a"; break; + case HDMI_B: interface = "HDMI-b"; break; + case MDDI: interface = "MDDI"; break; + case DISPLAY_PORT: interface = "DisplayPort"; break; + case UNDEFINED: interface = "undefined"; break; + default: interface = "unknown"; break; + } + printf ("Interface: %s\n", interface); + + printf ("RGB 4:4:4: %s\n", yesno (info->ad.digital.rgb444)); + printf ("YCrCb 4:4:4: %s\n", yesno (info->ad.digital.ycrcb444)); + printf ("YCrCb 4:2:2: %s\n", yesno (info->ad.digital.ycrcb422)); + } + else + { + const char *s; + printf ("Video Signal Level: %f\n", info->ad.analog.video_signal_level); + printf ("Sync Signal Level: %f\n", info->ad.analog.sync_signal_level); + printf ("Total Signal Level: %f\n", info->ad.analog.total_signal_level); + + printf ("Blank to Black: %s\n", + yesno (info->ad.analog.blank_to_black)); + printf ("Separate HV Sync: %s\n", + yesno (info->ad.analog.separate_hv_sync)); + printf ("Composite Sync on H: %s\n", + yesno (info->ad.analog.composite_sync_on_h)); + printf ("Serration on VSync: %s\n", + yesno (info->ad.analog.serration_on_vsync)); + + switch (info->ad.analog.color_type) + { + case UNDEFINED_COLOR: s = "undefined"; break; + case MONOCHROME: s = "monochrome"; break; + case RGB: s = "rgb"; break; + case OTHER_COLOR: s = "other color"; break; + default: s = "unknown"; break; + } + + printf ("Color: %s\n", s); + } + + if (info->width_mm == -1) + printf ("Width: undefined\n"); + else + printf ("Width: %d mm\n", info->width_mm); + + if (info->height_mm == -1) + printf ("Height: undefined\n"); + else + printf ("Height: %d mm\n", info->height_mm); + + if (info->aspect_ratio > 0) + printf ("Aspect Ratio: %f\n", info->aspect_ratio); + else + printf ("Aspect Ratio: undefined\n"); + + if (info->gamma >= 0) + printf ("Gamma: %f\n", info->gamma); + else + printf ("Gamma: undefined\n"); + + printf ("Standby: %s\n", yesno (info->standby)); + printf ("Suspend: %s\n", yesno (info->suspend)); + printf ("Active Off: %s\n", yesno (info->active_off)); + + printf ("SRGB is Standard: %s\n", yesno (info->srgb_is_standard)); + printf ("Preferred Timing Includes Native: %s\n", + yesno (info->preferred_timing_includes_native)); + printf ("Continuous Frequency: %s\n", yesno (info->continuous_frequency)); + + printf ("Red X: %f\n", info->red_x); + printf ("Red Y: %f\n", info->red_y); + printf ("Green X: %f\n", info->green_x); + printf ("Green Y: %f\n", info->green_y); + printf ("Blue X: %f\n", info->blue_x); + printf ("Blue Y: %f\n", info->blue_y); + printf ("White X: %f\n", info->white_x); + printf ("White Y: %f\n", info->white_y); + + printf ("Established Timings:\n"); + + for (i = 0; i < 24; ++i) + { + Timing *timing = &(info->established[i]); + + if (timing->frequency == 0) + break; + + printf (" %d x %d @ %d Hz\n", + timing->width, timing->height, timing->frequency); + + } + + printf ("Standard Timings:\n"); + for (i = 0; i < 8; ++i) + { + Timing *timing = &(info->standard[i]); + + if (timing->frequency == 0) + break; + + printf (" %d x %d @ %d Hz\n", + timing->width, timing->height, timing->frequency); + } + + for (i = 0; i < info->n_detailed_timings; ++i) + { + DetailedTiming *timing = &(info->detailed_timings[i]); + const char *s; + + printf ("Timing%s: \n", + (i == 0 && info->preferred_timing_includes_native)? + " (Preferred)" : ""); + printf (" Pixel Clock: %d\n", timing->pixel_clock); + printf (" H Addressable: %d\n", timing->h_addr); + printf (" H Blank: %d\n", timing->h_blank); + printf (" H Front Porch: %d\n", timing->h_front_porch); + printf (" H Sync: %d\n", timing->h_sync); + printf (" V Addressable: %d\n", timing->v_addr); + printf (" V Blank: %d\n", timing->v_blank); + printf (" V Front Porch: %d\n", timing->v_front_porch); + printf (" V Sync: %d\n", timing->v_sync); + printf (" Width: %d mm\n", timing->width_mm); + printf (" Height: %d mm\n", timing->height_mm); + printf (" Right Border: %d\n", timing->right_border); + printf (" Top Border: %d\n", timing->top_border); + switch (timing->stereo) + { + default: + case NO_STEREO: s = "No Stereo"; break; + case FIELD_RIGHT: s = "Field Sequential, Right on Sync"; break; + case FIELD_LEFT: s = "Field Sequential, Left on Sync"; break; + case TWO_WAY_RIGHT_ON_EVEN: s = "Two-way, Right on Even"; break; + case TWO_WAY_LEFT_ON_EVEN: s = "Two-way, Left on Even"; break; + case FOUR_WAY_INTERLEAVED: s = "Four-way Interleaved"; break; + case SIDE_BY_SIDE: s = "Side-by-Side"; break; + } + printf (" Stereo: %s\n", s); + + if (timing->digital_sync) + { + printf (" Digital Sync:\n"); + printf (" composite: %s\n", yesno (timing->ad.digital.composite)); + printf (" serrations: %s\n", yesno (timing->ad.digital.serrations)); + printf (" negative vsync: %s\n", + yesno (timing->ad.digital.negative_vsync)); + printf (" negative hsync: %s\n", + yesno (timing->ad.digital.negative_hsync)); + } + else + { + printf (" Analog Sync:\n"); + printf (" bipolar: %s\n", yesno (timing->ad.analog.bipolar)); + printf (" serrations: %s\n", yesno (timing->ad.analog.serrations)); + printf (" sync on green: %s\n", yesno ( + timing->ad.analog.sync_on_green)); + } + } + + printf ("Detailed Product information:\n"); + printf (" Product Name: %s\n", info->dsc_product_name); + printf (" Serial Number: %s\n", info->dsc_serial_number); + printf (" Unspecified String: %s\n", info->dsc_string); +} + diff --git a/contrib/SDL-3.2.8/src/video/x11/edid.h b/contrib/SDL-3.2.8/src/video/x11/edid.h new file mode 100644 index 0000000..4581291 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/edid.h @@ -0,0 +1,191 @@ +/* + * Copyright 2007 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * on the rights to use, copy, modify, merge, publish, distribute, sub + * license, and/or sell copies of the Software, and to permit persons to whom + * the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Author: Soren Sandmann */ + +typedef unsigned char uchar; +typedef struct MonitorInfo MonitorInfo; +typedef struct Timing Timing; +typedef struct DetailedTiming DetailedTiming; + +typedef enum +{ + UNDEFINED, + DVI, + HDMI_A, + HDMI_B, + MDDI, + DISPLAY_PORT +} Interface; + +typedef enum +{ + UNDEFINED_COLOR, + MONOCHROME, + RGB, + OTHER_COLOR +} ColorType; + +typedef enum +{ + NO_STEREO, + FIELD_RIGHT, + FIELD_LEFT, + TWO_WAY_RIGHT_ON_EVEN, + TWO_WAY_LEFT_ON_EVEN, + FOUR_WAY_INTERLEAVED, + SIDE_BY_SIDE +} StereoType; + +struct Timing +{ + int width; + int height; + int frequency; +}; + +struct DetailedTiming +{ + int pixel_clock; + int h_addr; + int h_blank; + int h_sync; + int h_front_porch; + int v_addr; + int v_blank; + int v_sync; + int v_front_porch; + int width_mm; + int height_mm; + int right_border; + int top_border; + int interlaced; + StereoType stereo; + + int digital_sync; + union + { + struct + { + int bipolar; + int serrations; + int sync_on_green; + } analog; + + struct + { + int composite; + int serrations; + int negative_vsync; + int negative_hsync; + } digital; + } ad; +}; + +struct MonitorInfo +{ + int checksum; + char manufacturer_code[4]; + int product_code; + unsigned int serial_number; + + int production_week; // -1 if not specified + int production_year; // -1 if not specified + int model_year; // -1 if not specified + + int major_version; + int minor_version; + + int is_digital; + + union + { + struct + { + int bits_per_primary; + Interface interface; + int rgb444; + int ycrcb444; + int ycrcb422; + } digital; + + struct + { + double video_signal_level; + double sync_signal_level; + double total_signal_level; + + int blank_to_black; + + int separate_hv_sync; + int composite_sync_on_h; + int composite_sync_on_green; + int serration_on_vsync; + ColorType color_type; + } analog; + } ad; + + int width_mm; // -1 if not specified + int height_mm; // -1 if not specified + double aspect_ratio; // -1.0 if not specififed + + double gamma; // -1.0 if not specified + + int standby; + int suspend; + int active_off; + + int srgb_is_standard; + int preferred_timing_includes_native; + int continuous_frequency; + + double red_x; + double red_y; + double green_x; + double green_y; + double blue_x; + double blue_y; + double white_x; + double white_y; + + Timing established[24]; // Terminated by 0x0x0 + Timing standard[8]; + + int n_detailed_timings; + DetailedTiming detailed_timings[4]; /* If monitor has a preferred + * mode, it is the first one + * (whether it has, is + * determined by the + * preferred_timing_includes + * bit. + */ + + // Optional product description + char dsc_serial_number[14]; + char dsc_product_name[14]; + char dsc_string[14]; // Unspecified ASCII data +}; + +MonitorInfo *decode_edid(const uchar *data); +void dump_monitor_info(MonitorInfo *info); +char *make_display_name(const char *output_name, + const MonitorInfo *info); diff --git a/contrib/SDL-3.2.8/src/video/x11/xsettings-client.c b/contrib/SDL-3.2.8/src/video/x11/xsettings-client.c new file mode 100644 index 0000000..8fb1cd3 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/xsettings-client.c @@ -0,0 +1,859 @@ +/* + * 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->next = NULL; + } + + + 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/contrib/SDL-3.2.8/src/video/x11/xsettings-client.h b/contrib/SDL-3.2.8/src/video/x11/xsettings-client.h new file mode 100644 index 0000000..863c6f9 --- /dev/null +++ b/contrib/SDL-3.2.8/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 */ -- cgit v1.2.3