SDL: Reimplement clipboard text in terms of clipboard data

From 55ff09de38b0383ce91cb6420217fde2c78cb68d Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 4 Jul 2023 21:17:53 -0700
Subject: [PATCH] Reimplement clipboard text in terms of clipboard data

This will simplify the X11 and Wayland implementations, which were doing that under the hood, and makes application interaction between the two APIs consistent.
---
 include/SDL3/SDL_clipboard.h               |   4 +
 src/test/SDL_test_common.c                 |  11 +-
 src/video/SDL_clipboard.c                  | 279 ++++++++++++++-------
 src/video/SDL_clipboard_c.h                |  13 +
 src/video/SDL_sysvideo.h                   |  10 +-
 src/video/SDL_video.c                      |   6 +-
 src/video/cocoa/SDL_cocoaclipboard.m       |  98 ++------
 src/video/cocoa/SDL_cocoavideo.m           |   4 -
 src/video/wayland/SDL_waylandclipboard.c   | 252 +++++--------------
 src/video/wayland/SDL_waylandclipboard.h   |   4 +-
 src/video/wayland/SDL_waylanddatamanager.c |  69 ++---
 src/video/wayland/SDL_waylanddatamanager.h |  18 +-
 src/video/wayland/SDL_waylandevents.c      |   4 +-
 src/video/wayland/SDL_waylandvideo.c       |   4 +-
 src/video/windows/SDL_windowsclipboard.c   | 216 ++++++++--------
 src/video/windows/SDL_windowsclipboard.h   |   9 +-
 src/video/windows/SDL_windowsvideo.c       |   3 -
 src/video/x11/SDL_x11clipboard.c           | 116 +++------
 src/video/x11/SDL_x11clipboard.h           |   4 +-
 src/video/x11/SDL_x11video.c               |   4 +-
 20 files changed, 496 insertions(+), 632 deletions(-)

diff --git a/include/SDL3/SDL_clipboard.h b/include/SDL3/SDL_clipboard.h
index ee9d7a821082..cf1f7a12ea52 100644
--- a/include/SDL3/SDL_clipboard.h
+++ b/include/SDL3/SDL_clipboard.h
@@ -171,6 +171,8 @@ typedef void (SDLCALL *SDL_ClipboardCleanupCallback)(void *userdata);
  * data the callback function will be called allowing it to generate and
  * respond with the data for the requested mime-type.
  *
+ * The size of text data does not include any terminator, and the text does not need to be null terminated (e.g. you can directly copy a portion of a document)
+ *
  * \param callback A function pointer to the function that provides the
  *                 clipboard data
  * \param cleanup A function pointer to the function that cleans up the
@@ -202,6 +204,8 @@ extern DECLSPEC int SDLCALL SDL_ClearClipboardData();
 /**
  * Get the data from clipboard for a given mime type
  *
+ * The size of text data does not include the terminator, but the text is guaranteed to be null terminated.
+ *
  * \param mime_type The mime type to read from the clipboard
  * \param size      A pointer filled in with the length of the returned data
  * \returns the retrieved data buffer or NULL on failure; call SDL_GetError()
diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c
index 31a29243a555..aad6646c69b5 100644
--- a/src/test/SDL_test_common.c
+++ b/src/test/SDL_test_common.c
@@ -1848,7 +1848,15 @@ static const void *SDLTest_ScreenShotClipboardProvider(void *context, const char
 {
     SDLTest_ClipboardData *data = (SDLTest_ClipboardData *)context;
 
-    SDL_Log("Providing screenshot image data to clipboard!\n");
+    if (SDL_strncmp(mime_type, "text", 4) == 0) {
+        SDL_Log("Providing screenshot title to clipboard!\n");
+
+        /* Return "Test screenshot" */
+        *size = 15;
+        return "Test screenshot (but this isn't part of it)";
+    }
+
+    SDL_Log("Providing screenshot image to clipboard!\n");
 
     if (!data->image) {
         SDL_RWops *file;
@@ -1884,6 +1892,7 @@ static void SDLTest_CopyScreenShot(SDL_Renderer *renderer)
     SDL_Rect viewport;
     SDL_Surface *surface;
     const char *image_formats[] = {
+        "text/plain;charset=utf-8",
         "image/bmp"
     };
     SDLTest_ClipboardData *clipboard_data;
diff --git a/src/video/SDL_clipboard.c b/src/video/SDL_clipboard.c
index b6cbcb3fb00e..a95620cb5976 100644
--- a/src/video/SDL_clipboard.c
+++ b/src/video/SDL_clipboard.c
@@ -106,6 +106,33 @@ int SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardCleanu
         if (_this->SetClipboardData(_this) < 0) {
             return -1;
         }
+    } else if (_this->SetClipboardText) {
+        char *text = NULL;
+        size_t size;
+
+        for (i = 0; i < num_mime_types; ++i) {
+            const char *mime_type = _this->clipboard_mime_types[i];
+            if (SDL_IsTextMimeType(mime_type)) {
+                const void *data = _this->clipboard_callback(_this->clipboard_userdata, mime_type, &size);
+                if (data) {
+                    text = (char *)SDL_malloc(size + 1);
+                    SDL_memcpy(text, data, size);
+                    text[size] = '\0';
+                    if (_this->SetClipboardText(_this, text) < 0) {
+                        SDL_free(text);
+                        return -1;
+                    }
+                    break;
+                }
+            }
+        }
+        if (text) {
+            SDL_free(text);
+        } else {
+            if (_this->SetClipboardText(_this, "") < 0) {
+                return -1;
+            }
+        }
     }
 
     SDL_SendClipboardUpdate();
@@ -117,177 +144,242 @@ int SDL_ClearClipboardData(void)
     return SDL_SetClipboardData(NULL, NULL, NULL, NULL, 0);
 }
 
-int SDL_SetClipboardText(const char *text)
+void *SDL_GetInternalClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *size)
+{
+    void *data = NULL;
+
+    if (_this->clipboard_callback) {
+        const void *provided_data = _this->clipboard_callback(_this->clipboard_userdata, mime_type, size);
+        if (provided_data) {
+            /* Make a copy of it for the caller and guarantee null termination */
+            data = SDL_malloc(*size + sizeof(Uint32));
+            if (data) {
+                SDL_memcpy(data, provided_data, *size);
+                SDL_memset((Uint8 *)data + *size, 0, sizeof(Uint32));
+            } else {
+                SDL_OutOfMemory();
+            }
+        }
+    }
+    return data;
+}
+
+void *SDL_GetClipboardData(const char *mime_type, size_t *size)
 {
     SDL_VideoDevice *_this = SDL_GetVideoDevice();
 
     if (_this == NULL) {
-        return SDL_SetError("Video subsystem must be initialized to set clipboard text");
+        SDL_SetError("Video subsystem must be initialized to get clipboard data");
+        return NULL;
     }
 
-    if (text == NULL) {
-        text = "";
+    if (!mime_type) {
+        SDL_InvalidParamError("mime_type");
+        return NULL;
     }
-    if (_this->SetClipboardText) {
-        if (_this->SetClipboardText(_this, text) < 0) {
-            return -1;
+    if (!size) {
+        SDL_InvalidParamError("size");
+        return NULL;
+    }
+
+    /* Initialize size to empty, so implementations don't have to worry about it */
+    *size = 0;
+
+    if (_this->GetClipboardData) {
+        return _this->GetClipboardData(_this, mime_type, size);
+    } else if (_this->GetClipboardText && SDL_IsTextMimeType(mime_type)) {
+        void *data = _this->GetClipboardText(_this);
+        if (data && *(char *)data == '\0') {
+            SDL_free(data);
+            data = NULL;
         }
+        return data;
     } else {
-        SDL_free(_this->clipboard_text);
-        _this->clipboard_text = SDL_strdup(text);
+        return SDL_GetInternalClipboardData(_this, mime_type, size);
     }
+}
 
-    SDL_SendClipboardUpdate();
-    return 0;
+SDL_bool SDL_HasInternalClipboardData(SDL_VideoDevice *_this, const char *mime_type)
+{
+    size_t i;
+
+    for (i = 0; i < _this->num_clipboard_mime_types; ++i) {
+        if (SDL_strcmp(mime_type, _this->clipboard_mime_types[i]) == 0) {
+            return SDL_TRUE;
+        }
+    }
+    return SDL_FALSE;
 }
 
-int SDL_SetPrimarySelectionText(const char *text)
+SDL_bool SDL_HasClipboardData(const char *mime_type)
 {
     SDL_VideoDevice *_this = SDL_GetVideoDevice();
 
     if (_this == NULL) {
-        return SDL_SetError("Video subsystem must be initialized to set primary selection text");
+        SDL_SetError("Video subsystem must be initialized to check clipboard data");
+        return SDL_FALSE;
     }
 
-    if (text == NULL) {
-        text = "";
+    if (!mime_type) {
+        SDL_InvalidParamError("mime_type");
+        return SDL_FALSE;
     }
-    if (_this->SetPrimarySelectionText) {
-        if (_this->SetPrimarySelectionText(_this, text) < 0) {
-            return -1;
-        }
+
+    if (_this->HasClipboardData) {
+        return _this->HasClipboardData(_this, mime_type);
+    } else if (_this->HasClipboardText && SDL_IsTextMimeType(mime_type)) {
+        return _this->HasClipboardText(_this);
     } else {
-        SDL_free(_this->primary_selection_text);
-        _this->primary_selection_text = SDL_strdup(text);
+        return SDL_HasInternalClipboardData(_this, mime_type);
     }
+}
 
-    SDL_SendClipboardUpdate();
-    return 0;
+/* Clipboard text */
+
+SDL_bool SDL_IsTextMimeType(const char *mime_type)
+{
+    return (SDL_strncmp(mime_type, "text", 4) == 0);
 }
 
-void *SDL_GetClipboardData(const char *mime_type, size_t *size)
+static const char **SDL_GetTextMimeTypes(SDL_VideoDevice *_this, size_t *num_mime_types)
 {
-    SDL_VideoDevice *_this = SDL_GetVideoDevice();
-    void *data = NULL;
+    if (_this->GetTextMimeTypes) {
+        return _this->GetTextMimeTypes(_this, num_mime_types);
+    } else {
+        static const char *text_mime_types[] = {
+            "text/plain;charset=utf-8"
+        };
 
-    if (_this == NULL) {
-        SDL_SetError("Video subsystem must be initialized to get clipboard data");
-        return NULL;
+        *num_mime_types = SDL_arraysize(text_mime_types);
+        return text_mime_types;
     }
+}
 
-    if (!mime_type) {
-        SDL_InvalidParamError("mime_type");
-        return NULL;
+const void *SDL_ClipboardTextCallback(void *userdata, const char *mime_type, size_t *size)
+{
+    char *text = (char *)userdata;
+    if (text) {
+        *size = SDL_strlen(text);
+    } else {
+        *size = 0;
     }
-    if (!size) {
-        SDL_InvalidParamError("size");
-        return NULL;
+    return text;
+}
+
+int SDL_SetClipboardText(const char *text)
+{
+    SDL_VideoDevice *_this = SDL_GetVideoDevice();
+    size_t num_mime_types;
+    const char **text_mime_types;
+
+    if (_this == NULL) {
+        return SDL_SetError("Video subsystem must be initialized to set clipboard text");
     }
 
-    if (_this->GetClipboardData) {
-        data = _this->GetClipboardData(_this, mime_type, size);
-    } else if (_this->clipboard_callback) {
-        const void *provided_data = _this->clipboard_callback(_this->clipboard_userdata, mime_type, size);
-        if (provided_data) {
-            /* Make a copy of it for the caller */
-            data = SDL_malloc(*size);
-            if (data) {
-                SDL_memcpy(data, provided_data, *size);
-            } else {
-                SDL_OutOfMemory();
-            }
-        }
+    if (SDL_ClearClipboardData() < 0) {
+        return -1;
     }
-    if (!data) {
-        *size = 0;
+
+    if (!text || !*text) {
+        /* All done! */
+        return 0;
     }
-    return data;
+
+    text_mime_types = SDL_GetTextMimeTypes(_this, &num_mime_types);
+
+    return SDL_SetClipboardData(SDL_ClipboardTextCallback, SDL_free, SDL_strdup(text), text_mime_types, num_mime_types);
 }
 
 char *SDL_GetClipboardText(void)
 {
     SDL_VideoDevice *_this = SDL_GetVideoDevice();
+    size_t i, num_mime_types;
+    const char **text_mime_types;
+    size_t length;
+    char *text = NULL;
 
     if (_this == NULL) {
         SDL_SetError("Video subsystem must be initialized to get clipboard text");
         return SDL_strdup("");
     }
 
-    if (_this->GetClipboardText) {
-        return _this->GetClipboardText(_this);
-    } else {
-        const char *text = _this->clipboard_text;
-        if (text == NULL) {
-            text = "";
+    text_mime_types = SDL_GetTextMimeTypes(_this, &num_mime_types);
+    for (i = 0; i < num_mime_types; ++i) {
+        text = SDL_GetClipboardData(text_mime_types[i], &length);
+        if (text) {
+            break;
         }
-        return SDL_strdup(text);
     }
+
+    if (!text) {
+        text = SDL_strdup("");
+    }
+    return text;
 }
 
-char *SDL_GetPrimarySelectionText(void)
+SDL_bool SDL_HasClipboardText(void)
 {
     SDL_VideoDevice *_this = SDL_GetVideoDevice();
+    size_t i, num_mime_types;
+    const char **text_mime_types;
 
     if (_this == NULL) {
-        SDL_SetError("Video subsystem must be initialized to get primary selection text");
-        return SDL_strdup("");
+        SDL_SetError("Video subsystem must be initialized to check clipboard text");
+        return SDL_FALSE;
     }
 
-    if (_this->GetPrimarySelectionText) {
-        return _this->GetPrimarySelectionText(_this);
-    } else {
-        const char *text = _this->primary_selection_text;
-        if (text == NULL) {
-            text = "";
+    text_mime_types = SDL_GetTextMimeTypes(_this, &num_mime_types);
+    for (i = 0; i < num_mime_types; ++i) {
+        if (SDL_HasClipboardData(text_mime_types[i])) {
+            return SDL_TRUE;
         }
-        return SDL_strdup(text);
     }
+    return SDL_FALSE;
 }
 
-SDL_bool SDL_HasClipboardData(const char *mime_type)
+/* Primary selection text */
+
+int SDL_SetPrimarySelectionText(const char *text)
 {
     SDL_VideoDevice *_this = SDL_GetVideoDevice();
-    size_t i;
 
     if (_this == NULL) {
-        SDL_SetError("Video subsystem must be initialized to check clipboard data");
-        return SDL_FALSE;
+        return SDL_SetError("Video subsystem must be initialized to set primary selection text");
     }
 
-    if (!mime_type) {
-        SDL_InvalidParamError("mime_type");
-        return SDL_FALSE;
+    if (text == NULL) {
+        text = "";
     }
-
-    if (_this->HasClipboardData) {
-        return _this->HasClipboardData(_this, mime_type);
-    } else {
-        for (i = 0; i < _this->num_clipboard_mime_types; ++i) {
-            if (SDL_strcmp(mime_type, _this->clipboard_mime_types[i]) == 0) {
-                return SDL_TRUE;
-            }
+    if (_this->SetPrimarySelectionText) {
+        if (_this->SetPrimarySelectionText(_this, text) < 0) {
+            return -1;
         }
-        return SDL_FALSE;
+    } else {
+        SDL_free(_this->primary_selection_text);
+        _this->primary_selection_text = SDL_strdup(text);
     }
+
+    SDL_SendClipboardUpdate();
+    return 0;
 }
 
-SDL_bool SDL_HasClipboardText(void)
+char *SDL_GetPrimarySelectionText(void)
 {
     SDL_VideoDevice *_this = SDL_GetVideoDevice();
 
     if (_this == NULL) {
-        SDL_SetError("Video subsystem must be initialized to check clipboard text");
-        return SDL_FALSE;
+        SDL_SetError("Video subsystem must be initialized to get primary selection text");
+        return SDL_strdup("");
     }
 
-    if (_this->HasClipboardText) {
-        return _this->HasClipboardText(_this);
+    if (_this->GetPrimarySelectionText) {
+        return _this->GetPrimarySelectionText(_this);
     } else {
-        if (_this->clipboard_text && _this->clipboard_text[0] != '\0') {
-            return SDL_TRUE;
-        } else {
-            return SDL_FALSE;
+        const char *text = _this->primary_selection_text;
+        if (text == NULL) {
+            text = "";
         }
+        return SDL_strdup(text);
     }
 }
 
@@ -310,3 +402,4 @@ SDL_bool SDL_HasPrimarySelectionText(void)
         }
     }
 }
+
diff --git a/src/video/SDL_clipboard_c.h b/src/video/SDL_clipboard_c.h
index e741f888d170..50a245bd8fd8 100644
--- a/src/video/SDL_clipboard_c.h
+++ b/src/video/SDL_clipboard_c.h
@@ -23,7 +23,20 @@
 #ifndef SDL_clipboard_c_h_
 #define SDL_clipboard_c_h_
 
+#include "SDL_sysvideo.h"
+
+
+/* Return true if the mime type is valid clipboard text */
+extern SDL_bool SDL_IsTextMimeType(const char *mime_type);
+
 /* Cancel the clipboard data callback, called internally for cleanup */
 extern void SDL_CancelClipboardData(Uint32 sequence);
 
+/* Call the clipboard callback for application data */
+extern void *SDL_GetInternalClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *size);
+extern SDL_bool SDL_HasInternalClipboardData(SDL_VideoDevice *_this, const char *mime_type);
+
+/* General purpose clipboard text callback */
+const void *SDL_ClipboardTextCallback(void *userdata, const char *mime_type, size_t *size);
+
 #endif /* SDL_clipboard_c_h_ */
diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h
index eff11b949d8e..d4eedd2a6a8a 100644
--- a/src/video/SDL_sysvideo.h
+++ b/src/video/SDL_sysvideo.h
@@ -332,15 +332,18 @@ struct SDL_VideoDevice
     SDL_bool (*IsScreenKeyboardShown)(SDL_VideoDevice *_this, SDL_Window *window);
 
     /* Clipboard */
+    const char **(*GetTextMimeTypes)(SDL_VideoDevice *_this, size_t *num_mime_types);
+    int (*SetClipboardData)(SDL_VideoDevice *_this);
+    void *(*GetClipboardData)(SDL_VideoDevice *_this, const char *mime_type, size_t *size);
+    SDL_bool (*HasClipboardData)(SDL_VideoDevice *_this, const char *mime_type);
+    /* If you implement *ClipboardData, you don't need to implement *ClipboardText */
     int (*SetClipboardText)(SDL_VideoDevice *_this, const char *text);
     char *(*GetClipboardText)(SDL_VideoDevice *_this);
     SDL_bool (*HasClipboardText)(SDL_VideoDevice *_this);
+    /* These functions are only needed if the platform has a separate primary selection buffer */
     int (*SetPrimarySelectionText)(SDL_VideoDevice *_this, const char *text);
     char *(*GetPrimarySelectionText)(SDL_VideoDevice *_this);
     SDL_bool (*HasPrimarySelectionText)(SDL_VideoDevice *_this);
-    int (*SetClipboardData)(SDL_VideoDevice *_this);
-    void *(*GetClipboardData)(SDL_VideoDevice *_this, const char *mime_type, size_t *size);
-    SDL_bool (*HasClipboardData)(SDL_VideoDevice *_this, const char *mime_type);
 
     /* MessageBox */
     int (*ShowMessageBox)(SDL_VideoDevice *_this, const SDL_MessageBoxData *messageboxdata, int *buttonid);
@@ -371,7 +374,6 @@ struct SDL_VideoDevice
     void *clipboard_userdata;
     char **clipboard_mime_types;
     size_t num_clipboard_mime_types;
-    char *clipboard_text;
     char *primary_selection_text;
     SDL_bool setting_display_mode;
     Uint32 quirk_flags;
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index b6fcee353f54..5811ed90ee69 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -3722,8 +3722,10 @@ void SDL_VideoQuit(void)
         _this->displays = NULL;
         _this->num_displays = 0;
     }
-    SDL_free(_this->clipboard_text);
-    _this->clipboard_text = NULL;
+    if (_this->primary_selection_text) {
+        SDL_free(_this->primary_selection_text);
+        _this->primary_selection_text = NULL;
+    }
     _this->free(_this);
     _this = NULL;
 }
diff --git a/src/video/cocoa/SDL_cocoaclipboard.m b/src/video/cocoa/SDL_cocoaclipboard.m
index ed7d3bbfa073..a6f85a13bcb9 100644
--- a/src/video/cocoa/SDL_cocoaclipboard.m
+++ b/src/video/cocoa/SDL_cocoaclipboard.m
@@ -73,65 +73,6 @@ - (void)pasteboard:(NSPasteboard *)pasteboard
 @end
 
 
-int Cocoa_SetClipboardText(SDL_VideoDevice *_this, const char *text)
-{
-    @autoreleasepool {
-        SDL_CocoaVideoData *data = (__bridge SDL_CocoaVideoData *)_this->driverdata;
-        NSPasteboard *pasteboard;
-        NSString *format = NSPasteboardTypeString;
-        NSString *nsstr = [NSString stringWithUTF8String:text];
-        if (nsstr == nil) {
-            return SDL_SetError("Couldn't create NSString; is your string data in UTF-8 format?");
-        }
-
-        pasteboard = [NSPasteboard generalPasteboard];
-        data.clipboard_count = [pasteboard declareTypes:[NSArray arrayWithObject:format] owner:nil];
-        [pasteboard setString:nsstr forType:format];
-
-        return 0;
-    }
-}
-
-char *Cocoa_GetClipboardText(SDL_VideoDevice *_this)
-{
-    @autoreleasepool {
-        NSPasteboard *pasteboard;
-        NSString *format = NSPasteboardTypeString;
-        NSString *available;
-        char *text;
-
-        pasteboard = [NSPasteboard generalPasteboard];
-        available = [pasteboard availableTypeFromArray:[NSArray arrayWithObject:format]];
-        if ([available isEqualToString:format]) {
-            NSString *string;
-            const char *utf8;
-
-            string = [pasteboard stringForType:format];
-            if (string == nil) {
-                utf8 = "";
-            } else {
-                utf8 = [string UTF8String];
-            }
-            text = SDL_strdup(utf8 ? utf8 : "");
-        } else {
-            text = SDL_strdup("");
-        }
-
-        return text;
-    }
-}
-
-SDL_bool Cocoa_HasClipboardText(SDL_VideoDevice *_this)
-{
-    SDL_bool result = SDL_FALSE;
-    char *text = Cocoa_GetClipboardText(_this);
-    if (text) {
-        result = text[0] != '\0' ? SDL_TRUE : SDL_FALSE;
-        SDL_free(text);
-    }
-    return result;
-}
-
 void Cocoa_CheckClipboardUpdate(SDL_CocoaVideoData *data)
 {
     @autoreleasepool {
@@ -152,31 +93,35 @@ void Cocoa_CheckClipboardUpdate(SDL_CocoaVideoData *data)
 int Cocoa_SetClipboardData(SDL_VideoDevice *_this)
 {
     @autoreleasepool {
+        SDL_CocoaVideoData *data = (__bridge SDL_CocoaVideoData *)_this->driverdata;
         NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
         NSPasteboardItem *newItem = [NSPasteboardItem new];
         NSMutableArray *utiTypes = [NSMutableArray new];
         Cocoa_PasteboardDataProvider *provider = [[Cocoa_PasteboardDataProvider alloc] initWith: _this->clipboard_callback userData: _this->clipboard_userdata];
         BOOL itemResult = FALSE;
         BOOL writeResult = FALSE;
-        SDL_CocoaVideoData *data = (__bridge SDL_CocoaVideoData *)_this->driverdata;
 
-        for (int i = 0; i < _this->num_clipboard_mime_types; i++) {
-            CFStringRef mimeType = CFStringCreateWithCString(NULL, _this->clipboard_mime_types[i], kCFStringEncodingUTF8);
-            CFStringRef utiType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL);
-            CFRelease(mimeType);
+        if (_this->clipboard_callback) {
+            for (int i = 0; i < _this->num_clipboard_mime_types; i++) {
+                CFStringRef mimeType = CFStringCreateWithCString(NULL, _this->clipboard_mime_types[i], kCFStringEncodingUTF8);
+                CFStringRef utiType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL);
+                CFRelease(mimeType);
 
-            [utiTypes addObject: (__bridge NSString *)utiType];
-            CFRelease(utiType);
-        }
-        itemResult = [newItem setDataProvider: provider forTypes: utiTypes];
-        if (itemResult == FALSE) {
-            return SDL_SetError("Unable to set clipboard item data");
-        }
+                [utiTypes addObject: (__bridge NSString *)utiType];
+                CFRelease(utiType);
+            }
+            itemResult = [newItem setDataProvider: provider forTypes: utiTypes];
+            if (itemResult == FALSE) {
+                return SDL_SetError("Unable to set clipboard item data");
+            }
 
-        [pasteboard clearContents];
-        writeResult = [pasteboard writeObjects: @[newItem]];
-        if (writeResult == FALSE) {
-            return SDL_SetError("Unable to set clipboard data");
+            [pasteboard clearContents];
+            writeResult = [pasteboard writeObjects: @[newItem]];
+            if (writeResult == FALSE) {
+                return SDL_SetError("Unable to set clipboard data");
+            }
+        } else {
+            [pasteboard clearContents];
         }
         data.clipboard_count = [pasteboard changeCount];
     }
@@ -199,9 +144,10 @@ int Cocoa_SetClipboardData(SDL_VideoDevice *_this)
             if (itemData != nil) {
                 NSUInteger length = [itemData length];
                 *size = (size_t)length;
-                data = SDL_malloc(*size);
+                data = SDL_malloc(*size + sizeof(Uint32));
                 if (data) {
                     [itemData getBytes: data length: length];
+                    SDL_memset((Uint8 *)data + length, 0, sizeof(Uint32));
                 } else {
                     SDL_OutOfMemory();
                 }
diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m
index 4ca146eb5aaf..d59590aee623 100644
--- a/src/video/cocoa/SDL_cocoavideo.m
+++ b/src/video/cocoa/SDL_cocoavideo.m
@@ -171,10 +171,6 @@ static void Cocoa_DeleteDevice(SDL_VideoDevice *device)
         device->StopTextInput = Cocoa_StopTextInput;
         device->SetTextInputRect = Cocoa_SetTextInputRect;
 
-        device->SetClipboardText = Cocoa_SetClipboardText;
-        device->GetClipboardText = Cocoa_GetClipboardText;
-        device->HasClipboardText = Cocoa_HasClipboardText;
-
         device->SetClipboardData = Cocoa_SetClipboardData;
         device->GetClipboardData = Cocoa_GetClipboardData;
         device->HasClipboardData = Cocoa_HasClipboardData;
diff --git a/src/video/wayland/SDL_waylandclipboard.c b/src/video/wayland/SDL_waylandclipboard.c
index a9a7e0a24bfe..0346b2cf5437 100644
--- a/src/video/wayland/SDL_waylandclipboard.c
+++ b/src/video/wayland/SDL_waylandclipboard.c
@@ -22,19 +22,19 @@
 
 #ifdef SDL_VIDEO_DRIVER_WAYLAND
 
-#include "../../events/SDL_events_c.h"
 #include "SDL_waylanddatamanager.h"
 #include "SDL_waylandevents_c.h"
 #include "SDL_waylandclipboard.h"
+#include "../SDL_clipboard_c.h"
+#include "../../events/SDL_events_c.h"
+
 
 int Wayland_SetClipboardData(SDL_VideoDevice *_this)
 {
-    SDL_VideoData *video_data = NULL;
+    SDL_VideoData *video_data = _this->driverdata;
     SDL_WaylandDataDevice *data_device = NULL;
-
     int status = 0;
 
-    video_data = _this->driverdata;
     if (video_data->input != NULL && video_data->input->data_device != NULL) {
         data_device = video_data->input->data_device;
 
@@ -54,170 +54,95 @@ int Wayland_SetClipboardData(SDL_VideoDevice *_this)
     return status;
 }
 
-#define TEXT_MIME_TYPES_LEN 5
-static const char *text_mime_types[TEXT_MIME_TYPES_LEN] = {
-    TEXT_MIME,
-    "text/plain",
-    "TEXT",
-    "UTF8_STRING",
-    "STRING",
-};
-
-static const void *Wayland_ClipboardTextCallback(void *userdata, const char *mime_type, size_t *length)
-{
-    const void *data = NULL;
-
-    *length = 0;
-
-    if (userdata) {
-        for (size_t i = 0; i < TEXT_MIME_TYPES_LEN; ++i) {
-            if (SDL_strcmp(mime_type, text_mime_types[i]) == 0) {
-                char *text = userdata;
-                *length = SDL_strlen(text);
-                data = userdata;
-                break;
-            }
-        }
-    }
-    return data;
-}
-
-int Wayland_SetClipboardText(SDL_VideoDevice *_this, const char *text)
+void *Wayland_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *length)
 {
-    SDL_VideoData *video_data = NULL;
+    SDL_VideoData *video_data = _this->driverdata;
     SDL_WaylandDataDevice *data_device = NULL;
+    void *buffer = NULL;
 
-    int status = 0;
-
-    if (_this == NULL || _this->driverdata == NULL) {
-        status = SDL_SetError("Video driver uninitialized");
-    } else {
-        video_data = _this->driverdata;
-        if (video_data->input != NULL && video_data->input->data_device != NULL) {
-            data_device = video_data->input->data_device;
-
-            if (text[0] != '\0') {
-                SDL_WaylandDataSource *source = Wayland_data_source_create(_this);
-                Wayland_data_source_set_callback(source, Wayland_ClipboardTextCallback, SDL_strdup(text), 0);
-
-                status = Wayland_data_device_set_selection(data_device, source, text_mime_types, TEXT_MIME_TYPES_LEN);
-                if (status != 0) {
-                    Wayland_data_source_destroy(source);
-                }
-            } else {
-                status = Wayland_data_device_clear_selection(data_device);
-            }
-        }
-    }
-
-    return status;
-}
-
-int Wayland_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text)
-{
-    SDL_VideoData *video_data = NULL;
-    SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL;
-
-    int status = 0;
-
-    if (_this == NULL || _this->driverdata == NULL) {
-        status = SDL_SetError("Video driver uninitialized");
-    } else {
-        video_data = _this->driverdata;
-        if (video_data->input != NULL && video_data->input->primary_selection_device != NULL) {
-            primary_selection_device = video_data->input->primary_selection_device;
-            if (text[0] != '\0') {
-                SDL_WaylandPrimarySelectionSource *source = Wayland_primary_selection_source_create(_this);
-                Wayland_primary_selection_source_set_callback(source, Wayland_ClipboardTextCallback, SDL_strdup(text));
-
-                status = Wayland_primary_selection_device_set_selection(primary_selection_device,
-                                                                        source,
-                                                                        text_mime_types,
-                                                                        TEXT_MIME_TYPES_LEN);
-                if (status != 0) {
-                    Wayland_primary_selection_source_destroy(source);
-                }
-            } else {
-                status = Wayland_primary_selection_device_clear_selection(primary_selection_device);
-            }
+    if (video_data->input != NULL && video_data->input->data_device != NULL) {
+        data_device = video_data->input->data_device;
+        if (data_device->selection_source) {
+            buffer = SDL_GetInternalClipboardData(_this, mime_type, length);
+        } else if (Wayland_data_offer_has_mime(data_device->selection_offer, mime_type)) {
+            buffer = Wayland_data_offer_receive(data_device->selection_offer, mime_type, length);
         }
     }
 
-    return status;
+    return buffer;
 }
 
-void *Wayland_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *length)
+SDL_bool Wayland_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type)
 {
-    SDL_VideoData *video_data = NULL;
+    SDL_VideoData *video_data = _this->driverdata;
     SDL_WaylandDataDevice *data_device = NULL;
+    SDL_bool result = SDL_FALSE;
 
-    void *buffer = NULL;
-
-    video_data = _this->driverdata;
     if (video_data->input != NULL && video_data->input->data_device != NULL) {
         data_device = video_data->input->data_device;
-        if (data_device->selection_source && data_device->selection_source->userdata.sequence != 0) {
-            buffer = Wayland_data_source_get_data(data_device->selection_source, mime_type, length, SDL_FALSE);
-        } else if (Wayland_data_offer_has_mime(data_device->selection_offer, mime_type)) {
-            buffer = Wayland_data_offer_receive(data_device->selection_offer, mime_type, length, SDL_FALSE);
+        if (data_device->selection_source != NULL) {
+            result = SDL_HasInternalClipboardData(_this, mime_type);
+        } else {
+            result = Wayland_data_offer_has_mime(data_device->selection_offer, mime_type);
         }
     }
+    return result;
+}
 
-    return buffer;
+static const char *text_mime_types[] = {
+    TEXT_MIME,
+    "text/plain",
+    "TEXT",
+    "UTF8_STRING",
+    "STRING"
+};
+
+const char **Wayland_GetTextMimeTypes(SDL_VideoDevice *_this, size_t *num_mime_types)
+{
+    *num_mime_types = SDL_arraysize(text_mime_types);
+    return text_mime_

(Patch may be truncated, please check the link at the top of this post.)