SDL: clipboard: include mime types in SDL_ClipboarUpdate

From e00b1fdd6765b0022cd9ef9a6dd2f1b1e85c7f11 Mon Sep 17 00:00:00 2001
From: David Fort <[EMAIL REDACTED]>
Date: Wed, 2 Oct 2024 21:19:42 +0200
Subject: [PATCH] clipboard: include mime types in SDL_ClipboarUpdate

This patch modifies the clipboard handling so that when we receive an external
clipboard update, the suppported mime types are included in the SDL_ClipboarUpdate
event. The patch also introduces the owner field that allows to know if the update
is because we own the clipboard (internal update) or if it was an external update.
---
 include/SDL3/SDL_events.h                |  3 +
 src/core/android/SDL_android.c           |  3 +-
 src/events/SDL_clipboardevents.c         |  9 ++-
 src/events/SDL_clipboardevents_c.h       |  2 +-
 src/events/SDL_events.c                  |  3 +
 src/video/SDL_clipboard.c                | 79 +++++++++++++++---------
 src/video/SDL_clipboard_c.h              |  3 +
 src/video/cocoa/SDL_cocoaclipboard.m     |  3 +-
 src/video/uikit/SDL_uikitclipboard.m     |  3 +-
 src/video/wayland/SDL_waylandevents.c    | 38 +++++++++++-
 src/video/windows/SDL_windowsclipboard.c | 68 ++++++++++++++++++--
 src/video/x11/SDL_x11clipboard.c         |  2 +-
 src/video/x11/SDL_x11clipboard.h         |  1 +
 src/video/x11/SDL_x11events.c            | 73 ++++++++++++++++++----
 src/video/x11/SDL_x11video.c             |  4 ++
 src/video/x11/SDL_x11video.h             |  4 ++
 src/video/x11/SDL_x11window.c            |  2 +-
 src/video/x11/SDL_x11xfixes.c            |  2 +-
 18 files changed, 246 insertions(+), 56 deletions(-)

diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h
index fc175df8a2e2c..9befaf21c8048 100644
--- a/include/SDL3/SDL_events.h
+++ b/include/SDL3/SDL_events.h
@@ -845,6 +845,9 @@ typedef struct SDL_ClipboardEvent
     SDL_EventType type; /**< SDL_EVENT_CLIPBOARD_UPDATE */
     Uint32 reserved;
     Uint64 timestamp;   /**< In nanoseconds, populated using SDL_GetTicksNS() */
+    bool owner;       /**< are we owning the clipboard (internal update) */
+    Sint32 n_mime_types;     /**< number of mime types */
+    const char **mime_types; /**< current mime types */
 } SDL_ClipboardEvent;
 
 /**
diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c
index abf0a016f2d03..78d8b71941e1c 100644
--- a/src/core/android/SDL_android.c
+++ b/src/core/android/SDL_android.c
@@ -1377,7 +1377,8 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)(
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
     JNIEnv *env, jclass jcls)
 {
-    SDL_SendClipboardUpdate();
+    // TODO: compute new mime types
+    SDL_SendClipboardUpdate(false, NULL, 0);
 }
 
 // Low memory
diff --git a/src/events/SDL_clipboardevents.c b/src/events/SDL_clipboardevents.c
index 0bf462a163862..95245e196bb93 100644
--- a/src/events/SDL_clipboardevents.c
+++ b/src/events/SDL_clipboardevents.c
@@ -25,12 +25,17 @@
 #include "SDL_events_c.h"
 #include "SDL_clipboardevents_c.h"
 
-void SDL_SendClipboardUpdate(void)
+void SDL_SendClipboardUpdate(bool owner, char **mime_types, size_t n_mime_types)
 {
     if (SDL_EventEnabled(SDL_EVENT_CLIPBOARD_UPDATE)) {
         SDL_Event event;
         event.type = SDL_EVENT_CLIPBOARD_UPDATE;
-        event.clipboard.timestamp = 0;
+
+        SDL_ClipboardEvent *cevent = &event.clipboard;
+        cevent->timestamp = 0;
+        cevent->owner = owner;
+        cevent->mime_types = (const char **)mime_types;
+        cevent->n_mime_types = (Uint32)n_mime_types;
         SDL_PushEvent(&event);
     }
 }
diff --git a/src/events/SDL_clipboardevents_c.h b/src/events/SDL_clipboardevents_c.h
index 0288a3af4b91c..140cc48218ef7 100644
--- a/src/events/SDL_clipboardevents_c.h
+++ b/src/events/SDL_clipboardevents_c.h
@@ -23,6 +23,6 @@
 #ifndef SDL_clipboardevents_c_h_
 #define SDL_clipboardevents_c_h_
 
-extern void SDL_SendClipboardUpdate(void);
+extern void SDL_SendClipboardUpdate(bool owner, char **mime_types, size_t n_mime_types);
 
 #endif // SDL_clipboardevents_c_h_
diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index 6f2ee9058119b..890a78eed5b06 100644
--- a/src/events/SDL_events.c
+++ b/src/events/SDL_events.c
@@ -235,6 +235,9 @@ static void SDL_TransferTemporaryMemoryToEvent(SDL_EventEntry *event)
         SDL_LinkTemporaryMemoryToEvent(event, event->event.drop.source);
         SDL_LinkTemporaryMemoryToEvent(event, event->event.drop.data);
         break;
+    case SDL_EVENT_CLIPBOARD_UPDATE:
+        SDL_LinkTemporaryMemoryToEvent(event, event->event.clipboard.mime_types);
+        break;
     default:
         break;
     }
diff --git a/src/video/SDL_clipboard.c b/src/video/SDL_clipboard.c
index 4e1cbd37e7105..9d663980bf49d 100644
--- a/src/video/SDL_clipboard.c
+++ b/src/video/SDL_clipboard.c
@@ -22,13 +22,25 @@
 
 #include "SDL_clipboard_c.h"
 #include "SDL_sysvideo.h"
+#include "../events/SDL_events_c.h"
 #include "../events/SDL_clipboardevents_c.h"
 
+void SDL_FreeClipboardMimeTypes(SDL_VideoDevice *_this)
+{
+    if (_this->clipboard_mime_types) {
+        for (size_t i = 0; i < _this->num_clipboard_mime_types; ++i) {
+            SDL_free(_this->clipboard_mime_types[i]);
+        }
+        SDL_free(_this->clipboard_mime_types);
+        _this->clipboard_mime_types = NULL;
+        _this->num_clipboard_mime_types = 0;
+    }
+}
+
 
 void SDL_CancelClipboardData(Uint32 sequence)
 {
     SDL_VideoDevice *_this = SDL_GetVideoDevice();
-    size_t i;
 
     if (sequence != _this->clipboard_sequence) {
         // This clipboard data was already canceled
@@ -39,14 +51,7 @@ void SDL_CancelClipboardData(Uint32 sequence)
         _this->clipboard_cleanup(_this->clipboard_userdata);
     }
 
-    if (_this->clipboard_mime_types) {
-        for (i = 0; i < _this->num_clipboard_mime_types; ++i) {
-            SDL_free(_this->clipboard_mime_types[i]);
-        }
-        SDL_free(_this->clipboard_mime_types);
-        _this->clipboard_mime_types = NULL;
-        _this->num_clipboard_mime_types = 0;
-    }
+    SDL_FreeClipboardMimeTypes(_this);
 
     _this->clipboard_callback = NULL;
     _this->clipboard_cleanup = NULL;
@@ -135,7 +140,11 @@ bool SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardClean
         }
     }
 
-    SDL_SendClipboardUpdate();
+    char **mime_types_copy = SDL_CopyClipboardMimeTypes(mime_types, num_mime_types, true);
+    if (!mime_types_copy)
+        return SDL_SetError("unable to copy current mime types");
+
+    SDL_SendClipboardUpdate(true, mime_types_copy, num_mime_types);
     return true;
 }
 
@@ -232,44 +241,52 @@ bool SDL_HasClipboardData(const char *mime_type)
     }
 }
 
-char **SDL_GetClipboardMimeTypes(size_t *num_mime_types)
+char **SDL_CopyClipboardMimeTypes(const char **clipboard_mime_types, size_t num_mime_types, bool temporary)
 {
-    SDL_VideoDevice *_this = SDL_GetVideoDevice();
-
-    if (!_this) {
-        SDL_SetError("Video subsystem must be initialized to query clipboard mime types");
-        return NULL;
-    }
-
     size_t allocSize = sizeof(char *);
-    for (size_t i = 0; i < _this->num_clipboard_mime_types; i++) {
-        allocSize += sizeof(char *) + SDL_strlen(_this->clipboard_mime_types[i]) + 1;
+    for (size_t i = 0; i < num_mime_types; i++) {
+        allocSize += sizeof(char *) + SDL_strlen(clipboard_mime_types[i]) + 1;
     }
 
-    char *ret = (char *)SDL_malloc(allocSize);
+    char *ret;
+    if (temporary)
+        ret = (char *)SDL_AllocateTemporaryMemory(allocSize);
+    else
+        ret = (char *)SDL_malloc(allocSize);
     if (!ret) {
         return NULL;
     }
 
     char **result = (char **)ret;
-    ret += sizeof(char *) * (_this->num_clipboard_mime_types + 1);
+    ret += sizeof(char *) * (num_mime_types + 1);
 
-    for (size_t i = 0; i < _this->num_clipboard_mime_types; i++) {
+    for (size_t i = 0; i < num_mime_types; i++) {
         result[i] = ret;
 
-        const char *mime_type = _this->clipboard_mime_types[i];
+        const char *mime_type = clipboard_mime_types[i];
         // Copy the whole string including the terminating null char
         char c;
         do {
             c = *ret++ = *mime_type++;
         } while (c != '\0');
     }
-    result[_this->num_clipboard_mime_types] = NULL;
+    result[num_mime_types] = NULL;
 
-    if (num_mime_types) {
-        *num_mime_types = _this->num_clipboard_mime_types;
-    }
     return result;
+
+}
+
+char **SDL_GetClipboardMimeTypes(size_t *num_mime_types)
+{
+    SDL_VideoDevice *_this = SDL_GetVideoDevice();
+
+    if (!_this) {
+        SDL_SetError("Video subsystem must be initialized to query clipboard mime types");
+        return NULL;
+    }
+
+    *num_mime_types = _this->num_clipboard_mime_types;
+    return SDL_CopyClipboardMimeTypes((const char **)_this->clipboard_mime_types, _this->num_clipboard_mime_types, false);
 }
 
 // Clipboard text
@@ -392,7 +409,11 @@ bool SDL_SetPrimarySelectionText(const char *text)
         _this->primary_selection_text = SDL_strdup(text);
     }
 
-    SDL_SendClipboardUpdate();
+    char **mime_types = SDL_CopyClipboardMimeTypes((const char **)_this->clipboard_mime_types, _this->num_clipboard_mime_types, true);
+    if (!mime_types)
+        return SDL_SetError("unable to copy current mime types");
+
+    SDL_SendClipboardUpdate(true, mime_types, _this->num_clipboard_mime_types);
     return true;
 }
 
diff --git a/src/video/SDL_clipboard_c.h b/src/video/SDL_clipboard_c.h
index 0d5e9dc8fb6db..7cc670b968a2f 100644
--- a/src/video/SDL_clipboard_c.h
+++ b/src/video/SDL_clipboard_c.h
@@ -39,4 +39,7 @@ extern bool SDL_HasInternalClipboardData(SDL_VideoDevice *_this, const char *mim
 // General purpose clipboard text callback
 const void * SDLCALL SDL_ClipboardTextCallback(void *userdata, const char *mime_type, size_t *size);
 
+void SDLCALL SDL_FreeClipboardMimeTypes(SDL_VideoDevice *_this);
+char ** SDLCALL SDL_CopyClipboardMimeTypes(const char **clipboard_mime_types, size_t num_mime_types, bool temporary);
+
 #endif // SDL_clipboard_c_h_
diff --git a/src/video/cocoa/SDL_cocoaclipboard.m b/src/video/cocoa/SDL_cocoaclipboard.m
index 9cfdd829086e7..9b266fcb5e80c 100644
--- a/src/video/cocoa/SDL_cocoaclipboard.m
+++ b/src/video/cocoa/SDL_cocoaclipboard.m
@@ -83,7 +83,8 @@ void Cocoa_CheckClipboardUpdate(SDL_CocoaVideoData *data)
         count = [pasteboard changeCount];
         if (count != data.clipboard_count) {
             if (data.clipboard_count) {
-                SDL_SendClipboardUpdate();
+                // TODO: compute mime types
+                SDL_SendClipboardUpdate(false, NULL, 0);
             }
             data.clipboard_count = count;
         }
diff --git a/src/video/uikit/SDL_uikitclipboard.m b/src/video/uikit/SDL_uikitclipboard.m
index 1ba0eca29f50c..cf4564e6b7967 100644
--- a/src/video/uikit/SDL_uikitclipboard.m
+++ b/src/video/uikit/SDL_uikitclipboard.m
@@ -80,7 +80,8 @@ void UIKit_InitClipboard(SDL_VideoDevice *_this)
                                           object:nil
                                            queue:nil
                                       usingBlock:^(NSNotification *note) {
-                                        SDL_SendClipboardUpdate();
+                                        // TODO: compute mime types
+                                        SDL_SendClipboardUpdate(false, NULL, 0);
                                       }];
 
         data.pasteboardObserver = observer;
diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index e71a20e68e063..9e8f7677fbea3 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -2277,6 +2277,40 @@ static void data_device_handle_drop(void *data, struct wl_data_device *wl_data_d
     data_device->drag_offer = NULL;
 }
 
+static void notifyFromMimes(struct wl_list *mimes) {
+    int nformats = 0;
+    char **new_mime_types = NULL;
+    if (mimes) {
+        nformats = WAYLAND_wl_list_length(mimes);
+        size_t alloc_size = (nformats + 1) * sizeof(char *);
+
+        /* do a first pass to compute allocation size */
+        SDL_MimeDataList *item = NULL;
+        wl_list_for_each(item, mimes, link) {
+            alloc_size += SDL_strlen(item->mime_type) + 1;
+        }
+
+        new_mime_types = SDL_AllocateTemporaryMemory(alloc_size);
+        if (!new_mime_types) {
+            SDL_LogError(SDL_LOG_CATEGORY_INPUT, "unable to allocate new_mime_types");
+            return;
+        }
+
+        /* second pass to fill*/
+        char *strPtr = (char *)(new_mime_types + nformats + 1);
+        item = NULL;
+        int i = 0;
+        wl_list_for_each(item, mimes, link) {
+            new_mime_types[i] = strPtr;
+            strPtr = stpcpy(strPtr, item->mime_type) + 1;
+            i++;
+        }
+        new_mime_types[nformats] = NULL;
+    }
+
+    SDL_SendClipboardUpdate(false, new_mime_types, nformats);
+}
+
 static void data_device_handle_selection(void *data, struct wl_data_device *wl_data_device,
                                          struct wl_data_offer *id)
 {
@@ -2295,7 +2329,7 @@ static void data_device_handle_selection(void *data, struct wl_data_device *wl_d
         data_device->selection_offer = offer;
     }
 
-    SDL_SendClipboardUpdate();
+    notifyFromMimes(offer ? &offer->mimes : NULL);
 }
 
 static const struct wl_data_device_listener data_device_listener = {
@@ -2341,7 +2375,7 @@ static void primary_selection_device_handle_selection(void *data, struct zwp_pri
                  ". In zwp_primary_selection_device_v1_listener . primary_selection_device_handle_selection on primary_selection_offer 0x%08x\n",
                  (id ? WAYLAND_wl_proxy_get_id((struct wl_proxy *)id) : -1));
 
-    SDL_SendClipboardUpdate();
+    notifyFromMimes(offer ? &offer->mimes : NULL);
 }
 
 static const struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = {
diff --git a/src/video/windows/SDL_windowsclipboard.c b/src/video/windows/SDL_windowsclipboard.c
index 98d8b87cc846e..6a0cc35c74d51 100644
--- a/src/video/windows/SDL_windowsclipboard.c
+++ b/src/video/windows/SDL_windowsclipboard.c
@@ -25,6 +25,7 @@
 #include "SDL_windowsvideo.h"
 #include "SDL_windowswindow.h"
 #include "../SDL_clipboard_c.h"
+#include "../../events/SDL_events_c.h"
 #include "../../events/SDL_clipboardevents_c.h"
 
 #ifdef UNICODE
@@ -329,14 +330,73 @@ bool WIN_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type)
     return false;
 }
 
+static char **GetMimeTypes(int *pnformats)
+{
+    *pnformats = 0;
+
+    int nformats = CountClipboardFormats();
+    size_t allocSize = (nformats + 1) * sizeof(char*);
+
+    UINT format = 0;
+    int formatsSz = 0;
+    int i;
+    for (i = 0; i < nformats; i++) {
+        format = EnumClipboardFormats(format);
+        if (!format) {
+            nformats = i;
+            break;
+        }
+
+        char mimeType[200];
+        int nchars = GetClipboardFormatNameA(format, mimeType, sizeof(mimeType));
+        formatsSz += nchars + 1;
+    }
+
+    char **new_mime_types = SDL_AllocateTemporaryMemory(allocSize + formatsSz);
+    if (!new_mime_types)
+        return NULL;
+
+    format = 0;
+    char *strPtr = (char *)(new_mime_types + nformats + 1);
+    int formatRemains = formatsSz;
+    for (i = 0; i < nformats; i++) {
+        format = EnumClipboardFormats(format);
+        if (!format) {
+            nformats = i;
+            break;
+        }
+
+        new_mime_types[i] = strPtr;
+
+        int nchars = GetClipboardFormatNameA(format, strPtr, formatRemains-1);
+        strPtr += nchars;
+        *strPtr = '\0';
+        strPtr++;
+
+        formatRemains -= (nchars + 1);
+    }
+
+    new_mime_types[nformats] = NULL;
+    *pnformats = nformats;
+    return new_mime_types;
+}
+
 void WIN_CheckClipboardUpdate(struct SDL_VideoData *data)
 {
-    const DWORD count = GetClipboardSequenceNumber();
-    if (count != data->clipboard_count) {
+    const DWORD seq = GetClipboardSequenceNumber();
+    if (seq != data->clipboard_count) {
         if (data->clipboard_count) {
-            SDL_SendClipboardUpdate();
+            int nformats = 0;
+            char **new_mime_types = GetMimeTypes(&nformats);
+            if (new_mime_types) {
+                SDL_SendClipboardUpdate(false, new_mime_types, nformats);
+            } else {
+                WIN_SetError("Couldn't get clipboard mime types");
+            }
+
         }
-        data->clipboard_count = count;
+
+        data->clipboard_count = seq;
     }
 }
 
diff --git a/src/video/x11/SDL_x11clipboard.c b/src/video/x11/SDL_x11clipboard.c
index 913be162bcc49..0a72faf8e168a 100644
--- a/src/video/x11/SDL_x11clipboard.c
+++ b/src/video/x11/SDL_x11clipboard.c
@@ -38,7 +38,7 @@ static const char *text_mime_types[] = {
 };
 
 // Get any application owned window handle for clipboard association
-static Window GetWindow(SDL_VideoDevice *_this)
+Window GetWindow(SDL_VideoDevice *_this)
 {
     SDL_VideoData *data = _this->internal;
 
diff --git a/src/video/x11/SDL_x11clipboard.h b/src/video/x11/SDL_x11clipboard.h
index cf9d07e705201..dc22d69cff025 100644
--- a/src/video/x11/SDL_x11clipboard.h
+++ b/src/video/x11/SDL_x11clipboard.h
@@ -41,5 +41,6 @@ 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/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c
index 1b883b8673cb1..8d13c22c1c5dc 100644
--- a/src/video/x11/SDL_x11events.c
+++ b/src/video/x11/SDL_x11events.c
@@ -415,7 +415,7 @@ static void DispatchWindowMove(SDL_VideoDevice *_this, const SDL_WindowData *dat
 
     evt.xclient.type = ClientMessage;
     evt.xclient.window = data->xwindow;
-    evt.xclient.message_type = X11_XInternAtom(display, "_NET_WM_MOVERESIZE", True);
+    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;
@@ -450,7 +450,7 @@ static void InitiateWindowResize(SDL_VideoDevice *_this, const SDL_WindowData *d
 
     evt.xclient.type = ClientMessage;
     evt.xclient.window = data->xwindow;
-    evt.xclient.message_type = X11_XInternAtom(display, "_NET_WM_MOVERESIZE", True);
+    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;
@@ -553,7 +553,7 @@ static void X11_HandleClipboardEvent(SDL_VideoDevice *_this, const XEvent *xeven
         int mime_formats;
         unsigned char *seln_data;
         size_t seln_length = 0;
-        Atom XA_TARGETS = X11_XInternAtom(display, "TARGETS", 0);
+        Atom XA_TARGETS = videodata->atoms.TARGETS;
         SDLX11_ClipboardData *clipboard;
 
 #ifdef DEBUG_XEVENTS
@@ -627,17 +627,52 @@ static void X11_HandleClipboardEvent(SDL_VideoDevice *_this, const XEvent *xeven
 
     case SelectionNotify:
     {
+        const XSelectionEvent *xsel = &xevent->xselection;
 #ifdef DEBUG_XEVENTS
-        SDL_Log("window CLIPBOARD: SelectionNotify (requestor = 0x%lx, target = 0x%lx)\n",
-               xevent->xselection.requestor, xevent->xselection.target);
-#endif
+        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)\n",
+               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++) {
+                allocationsize += SDL_strlen( X11_XGetAtomName(display, *patom) ) + 1;
+            }
+
+            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++) {
+                    new_mime_types[j] = strPtr;
+                    strPtr = stpcpy(strPtr, X11_XGetAtomName(display, *patom)) + 1;
+                }
+                new_mime_types[length] = NULL;
+
+                SDL_SendClipboardUpdate(false, new_mime_types, length);
+            }
+        }
+
         videodata->selection_waiting = false;
     } break;
 
     case SelectionClear:
     {
-        // !!! FIXME: cache atoms
-        Atom XA_CLIPBOARD = X11_XInternAtom(display, "CLIPBOARD", 0);
+        Atom XA_CLIPBOARD = videodata->atoms.CLIPBOARD;
         SDLX11_ClipboardData *clipboard = NULL;
 
 #ifdef DEBUG_XEVENTS
@@ -994,11 +1029,25 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
                X11_XGetAtomName(display, ev->selection));
 #endif
 
-        if (ev->selection == XA_PRIMARY ||
-            (videodata->atoms.CLIPBOARD != None && ev->selection == videodata->atoms.CLIPBOARD)) {
-            SDL_SendClipboardUpdate();
-            return;
+        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
 
diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c
index a8d6ec86bb73f..57e51d6740f5c 100644
--- a/src/video/x11/SDL_x11video.c
+++ b/src/video/x11/SDL_x11video.c
@@ -403,6 +403,7 @@ static bool X11_VideoInit(SDL_VideoDevice *_this)
     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);
@@ -420,6 +421,9 @@ static bool X11_VideoInit(SDL_VideoDevice *_this)
     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(XdndPosition);
     GET_ATOM(XdndStatus);
diff --git a/src/video/x11/SDL_x11video.h b/src/video/x11/SDL_x11video.h
index f97ba902b98bf..9509ba70f8c0f 100644
--- a/src/video/x11/SDL_x11video.h
+++ b/src/video/x11/SDL_x11video.h
@@ -82,6 +82,7 @@ struct SDL_VideoData
         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;
@@ -98,6 +99,9 @@ struct SDL_VideoData
         Atom CLIPBOARD;
         Atom INCR;
         Atom SDL_SELECTION;
+        Atom TARGETS;
+        Atom SDL_FORMATS;
+        Atom XdndAware;
         Atom XdndEnter;
         Atom XdndPosition;
         Atom XdndStatus;
diff --git a/src/video/x11/SDL_x11window.c b/src/video/x11/SDL_x11window.c
index 7de9024414757..5802a2eaa97ff 100644
--- a/src/video/x11/SDL_x11window.c
+++ b/src/video/x11/SDL_x11window.c
@@ -2001,7 +2001,7 @@ void X11_AcceptDragAndDrop(SDL_Window *window, bool accept)
 {
     SDL_WindowData *data = window->internal;
     Display *display = data->videodata->display;
-    Atom XdndAware = X11_XInternAtom(display, "XdndAware", False);
+    Atom XdndAware = data->videodata->atoms.XdndAware;
 
     if (accept) {
         Atom xdnd_version = 5;
diff --git a/src/video/x11/SDL_x11xfixes.c b/src/video/x11/SDL_x11xfixes.c
index 417a864a84095..1e4432a11c3d5 100644
--- a/src/video/x11/SDL_x11xfixes.c
+++ b/src/video/x11/SDL_x11xfixes.c
@@ -51,7 +51,7 @@ void X11_InitXfixes(SDL_VideoDevice *_this)
     int event, error;
     int fixes_opcode;
 
-    Atom XA_CLIPBOARD = X11_XInternAtom(data->display, "CLIPBOARD", 0);
+    Atom XA_CLIPBOARD = data->atoms.CLIPBOARD;
 
     if (!SDL_X11_HAVE_XFIXES ||
         !X11_XQueryExtension(data->display, "XFIXES", &fixes_opcode, &event, &error)) {