Browse Source

wayland: Add support for images in clipboard.

Re-writes the clipboard data handling in wayland to an on demand
solution where callbacks are provided to generate/provide the clipboard
data when requested by the OS.
Linus Probert 2 years ago
parent
commit
19adfa3ad9

+ 90 - 0
include/SDL3/SDL_clipboard.h

@@ -129,6 +129,96 @@ extern DECLSPEC char * SDLCALL SDL_GetPrimarySelectionText(void);
  */
 extern DECLSPEC SDL_bool SDLCALL SDL_HasPrimarySelectionText(void);
 
+/**
+ * Callback function that will be called when data for the specified mime-type
+ * is requested by the OS.
+ *
+ * \param size      The length of the returned data
+ * \param mime_type The requested mime-type
+ * \param userdata  A pointer to provided user data
+ * \returns a pointer to the data for the provided mime-type. Returning NULL or
+ *          setting length to 0 will cause no data to be sent to the "receiver". It is
+ *          up to the receiver to handle this. Essentially returning no data is more or
+ *          less undefined behavior and may cause breakage in receiving applications.
+ *          The returned data will not be freed so it needs to be retained and dealt
+ *          with internally.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetClipboardData
+ */
+typedef void *(SDLCALL *SDL_ClipboardDataCallback)(size_t *size, const char *mime_type, void *userdata);
+
+/**
+ * \brief Offer clipboard data to the OS
+ *
+ * Tell the operating system that the application is offering clipboard data
+ * for each of the proivded mime-types. Once another application requests the
+ * data the callback function will be called allowing it to generate and
+ * respond with the data for the requested mime-type.
+ *
+ * The userdata submitted to this function needs to be freed manually. The
+ * following scenarios need to be handled:
+ *
+ * - When the programs clipboard is replaced (cancelled) SDL_EVENT_CLIPBOARD_CANCELLED
+ * - Before calling SDL_Quit()
+ *
+ * \param callback      A function pointer to the function that provides the clipboard data
+ * \param mime_count    The number of mime-types in the mime_types list
+ * \param mime_types    A list of mime-types that are being offered
+ * \param userdata      An opaque pointer that will be forwarded to the callback
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_ClipboardDataCallback
+ * \sa SDL_GetClipboardUserdata
+ * \sa SDL_SetClipboardData
+ * \sa SDL_GetClipboardData
+ * \sa SDL_HasClipboardData
+ */
+extern DECLSPEC int SDLCALL SDL_SetClipboardData(SDL_ClipboardDataCallback callback, size_t mime_count,
+                                                 const char **mime_types, void *userdata);
+
+/**
+ * Retrieve previously set userdata if any.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \returns a pointer to the data or NULL if no data exists
+ */
+extern DECLSPEC void *SDLCALL SDL_GetClipboardUserdata(void);
+
+/**
+ * Get the data from clipboard for a given mime type
+ *
+ * \param[out] length   A pointer to hold the buffer length
+ * \param mime_type     The mime type to read from the clipboard
+ * \returns the retrieved data buffer or NULL on failure; call
+ *          SDL_GetError() for more information. Caller must call
+ *          SDL_free() on the returned pointer when done with it.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetClipboardData
+ */
+extern DECLSPEC void *SDLCALL SDL_GetClipboardData(size_t *length, const char *mime_type);
+
+/**
+ * Query whether there is data in the clipboard for the provided mime type
+ *
+ * \param mime_type The mime type to check for data for
+ *
+ * \returns SDL_TRUE if there exists data in clipboard for the provided mime
+ *          type, SDL_FALSE if it does not.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetClipboardData
+ * \sa SDL_GetClipboardData
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_HasClipboardData(const char *mime_type);
 
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus

+ 14 - 0
include/SDL3/SDL_events.h

@@ -172,6 +172,7 @@ typedef enum
 
     /* Clipboard events */
     SDL_EVENT_CLIPBOARD_UPDATE = 0x900, /**< The clipboard or primary selection changed */
+    SDL_EVENT_CLIPBOARD_CANCELLED,      /**< The clipboard or primary selection cancelled */
 
     /* Drag and drop events */
     SDL_EVENT_DROP_FILE        = 0x1000, /**< The system requests a file open */
@@ -530,6 +531,18 @@ typedef struct SDL_DropEvent
     float y;            /**< Y coordinate, relative to window (not on begin) */
 } SDL_DropEvent;
 
+/**
+ * \brief An event triggered when the applications active clipboard content is cancelled by new clipboard content
+ * \note Primary use for this event is to free any userdata you may have provided when setting the clipboard data.
+ *
+ * \sa SDL_SetClipboardData
+ */
+typedef struct SDL_ClipboardCancelled
+{
+    Uint32 type;        /**< ::SDL_EVENT_CLIPBOARD_CANCELLED or ::SDL_EVENT_CLIPBOARD_UPDATE */
+    Uint64 timestamp;   /**< In nanoseconds, populated using SDL_GetTicksNS() */
+    void *userdata;     /**< User data if any has been set. NULL for ::SDL_EVENT_CLIPBOARD_UPDATE */
+} SDL_ClipboardEvent;
 
 /**
  *  \brief Sensor event structure (event.sensor.*)
@@ -624,6 +637,7 @@ typedef union SDL_Event
     SDL_SysWMEvent syswm;                   /**< System dependent window event data */
     SDL_TouchFingerEvent tfinger;           /**< Touch finger event data */
     SDL_DropEvent drop;                     /**< Drag and drop event data */
+    SDL_ClipboardEvent clipboard;       /**< Clipboard cancelled event data */
 
     /* This is necessary for ABI compatibility between Visual C++ and GCC.
        Visual C++ will respect the push pack pragma and use 52 bytes (size of

+ 4 - 0
src/dynapi/SDL_dynapi.sym

@@ -842,6 +842,10 @@ SDL3_0.0.0 {
     SDL_CreatePopupWindow;
     SDL_GetWindowParent;
     SDL_CreateWindowWithPosition;
+    SDL_SetClipboardData;
+    SDL_GetClipboardUserdata;
+    SDL_GetClipboardData;
+    SDL_HasClipboardData;
     SDL_GetAudioStreamFormat;
     SDL_SetAudioStreamFormat;
     SDL_CreateRWLock;

+ 4 - 0
src/dynapi/SDL_dynapi_overrides.h

@@ -868,6 +868,10 @@
 #define SDL_CreatePopupWindow SDL_CreatePopupWindow_REAL
 #define SDL_GetWindowParent SDL_GetWindowParent_REAL
 #define SDL_CreateWindowWithPosition SDL_CreateWindowWithPosition_REAL
+#define SDL_SetClipboardData SDL_SetClipboardData_REAL
+#define SDL_GetClipboardUserdata SDL_GetClipboardUserdata_REAL
+#define SDL_GetClipboardData SDL_GetClipboardData_REAL
+#define SDL_HasClipboardData SDL_HasClipboardData_REAL
 #define SDL_GetAudioStreamFormat SDL_GetAudioStreamFormat_REAL
 #define SDL_SetAudioStreamFormat SDL_SetAudioStreamFormat_REAL
 #define SDL_CreateRWLock SDL_CreateRWLock_REAL

+ 4 - 0
src/dynapi/SDL_dynapi_procs.h

@@ -913,6 +913,10 @@ SDL_DYNAPI_PROC(SDL_SystemTheme,SDL_GetSystemTheme,(void),(),return)
 SDL_DYNAPI_PROC(SDL_Window*,SDL_CreatePopupWindow,(SDL_Window *a, int b, int c, int d, int e, Uint32 f),(a,b,c,d,e,f),return)
 SDL_DYNAPI_PROC(SDL_Window*,SDL_GetWindowParent,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(SDL_Window*,SDL_CreateWindowWithPosition,(const char *a, int b, int c, int d, int e, Uint32 f),(a,b,c,d,e,f),return)
+SDL_DYNAPI_PROC(int,SDL_SetClipboardData,(SDL_ClipboardDataCallback a, size_t b, const char **c, void *d),(a,b,c,d),return)
+SDL_DYNAPI_PROC(void*,SDL_GetClipboardUserdata,(void),(),return)
+SDL_DYNAPI_PROC(void*,SDL_GetClipboardData,(size_t *a, const char *b),(a,b),return)
+SDL_DYNAPI_PROC(SDL_bool,SDL_HasClipboardData,(const char *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_GetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioFormat *b, int *c, int *d, SDL_AudioFormat *e, int *f, int *g),(a,b,c,d,e,f,g),return)
 SDL_DYNAPI_PROC(int,SDL_SetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioFormat b, int c, int d, SDL_AudioFormat e, int f, int g),(a,b,c,d,e,f,g),return)
 SDL_DYNAPI_PROC(SDL_RWLock*,SDL_CreateRWLock,(void),(),return)

+ 18 - 1
src/events/SDL_clipboardevents.c

@@ -34,7 +34,24 @@ int SDL_SendClipboardUpdate(void)
     if (SDL_EventEnabled(SDL_EVENT_CLIPBOARD_UPDATE)) {
         SDL_Event event;
         event.type = SDL_EVENT_CLIPBOARD_UPDATE;
-        event.common.timestamp = 0;
+        event.clipboard.timestamp = 0;
+        event.clipboard.userdata = NULL;
+        posted = (SDL_PushEvent(&event) > 0);
+    }
+    return posted;
+}
+
+int SDL_SendClipboardCancelled(void *userdata)
+{
+    int posted;
+
+    /* Post the event, if desired */
+    posted = 0;
+    if (SDL_EventEnabled(SDL_EVENT_CLIPBOARD_CANCELLED)) {
+        SDL_Event event;
+        event.type = SDL_EVENT_CLIPBOARD_CANCELLED;
+        event.clipboard.timestamp = 0;
+        event.clipboard.userdata = userdata;
         posted = (SDL_PushEvent(&event) > 0);
     }
     return posted;

+ 2 - 0
src/events/SDL_clipboardevents_c.h

@@ -25,4 +25,6 @@
 
 extern int SDL_SendClipboardUpdate(void);
 
+extern int SDL_SendClipboardCancelled(void *userdata);
+
 #endif /* SDL_clipboardevents_c_h_ */

+ 63 - 0
src/video/SDL_clipboard.c

@@ -22,6 +22,22 @@
 
 #include "SDL_sysvideo.h"
 
+int
+SDL_SetClipboardData(SDL_ClipboardDataCallback callback, size_t mime_count, const char **mime_types, void *userdata)
+{
+    SDL_VideoDevice *_this = SDL_GetVideoDevice();
+
+    if (_this == NULL) {
+        return SDL_SetError("Video subsystem must be initialized to set clipboard text");
+    }
+
+    if (_this->SetClipboardData) {
+        return _this->SetClipboardData(_this, callback, mime_count, mime_types, userdata);
+    } else {
+        return SDL_Unsupported();
+    }
+}
+
 int SDL_SetClipboardText(const char *text)
 {
     SDL_VideoDevice *_this = SDL_GetVideoDevice();
@@ -62,6 +78,22 @@ int SDL_SetPrimarySelectionText(const char *text)
     }
 }
 
+void *
+SDL_GetClipboardData(size_t *length, const char *mime_type)
+{
+    SDL_VideoDevice *_this = SDL_GetVideoDevice();
+    if (_this == NULL) {
+        SDL_SetError("Video subsystem must be initialized to get clipboard data");
+        return NULL;
+    }
+
+    if (_this->GetClipboardData) {
+        return _this->GetClipboardData(_this, length, mime_type);
+    } else {
+        return NULL;
+    }
+}
+
 char *
 SDL_GetClipboardText(void)
 {
@@ -104,6 +136,21 @@ SDL_GetPrimarySelectionText(void)
     }
 }
 
+SDL_bool
+SDL_HasClipboardData(const char *mime_type)
+{
+    SDL_VideoDevice *_this = SDL_GetVideoDevice();
+    if (_this == NULL) {
+        SDL_SetError("Video subsystem must be initialized to check clipboard data");
+        return SDL_FALSE;
+    }
+
+    if (_this->HasClipboardData) {
+        return _this->HasClipboardData(_this, mime_type);
+    }
+    return SDL_FALSE;
+}
+
 SDL_bool
 SDL_HasClipboardText(void)
 {
@@ -145,3 +192,19 @@ SDL_HasPrimarySelectionText(void)
 
     return SDL_FALSE;
 }
+
+void *
+SDL_GetClipboardUserdata(void)
+{
+    SDL_VideoDevice *_this = SDL_GetVideoDevice();
+
+    if (_this == NULL) {
+        SDL_SetError("Video subsystem must be initialized to check clipboard userdata");
+        return NULL;
+    }
+
+    if (_this->GetClipboardUserdata) {
+        return _this->GetClipboardUserdata(_this);
+    }
+    return NULL;
+}

+ 5 - 0
src/video/SDL_sysvideo.h

@@ -334,6 +334,11 @@ struct SDL_VideoDevice
     int (*SetPrimarySelectionText)(SDL_VideoDevice *_this, const char *text);
     char *(*GetPrimarySelectionText)(SDL_VideoDevice *_this);
     SDL_bool (*HasPrimarySelectionText)(SDL_VideoDevice *_this);
+    int (*SetClipboardData)(SDL_VideoDevice *_this, SDL_ClipboardDataCallback callback, size_t mime_count,
+                            const char **mime_types, void *userdata);
+    void *(*GetClipboardData)(SDL_VideoDevice *_this, size_t *len, const char *mime_type);
+    SDL_bool (*HasClipboardData)(SDL_VideoDevice *_this, const char *mime_type);
+    void *(*GetClipboardUserdata)(SDL_VideoDevice *_this);
 
     /* MessageBox */
     int (*ShowMessageBox)(SDL_VideoDevice *_this, const SDL_MessageBoxData *messageboxdata, int *buttonid);

+ 130 - 23
src/video/wayland/SDL_waylandclipboard.c

@@ -22,10 +22,74 @@
 
 #ifdef SDL_VIDEO_DRIVER_WAYLAND
 
+#include "../../events/SDL_events_c.h"
 #include "SDL_waylanddatamanager.h"
 #include "SDL_waylandevents_c.h"
 #include "SDL_waylandclipboard.h"
 
+int Wayland_SetClipboardData(SDL_VideoDevice *_this, SDL_ClipboardDataCallback callback, size_t mime_count, const char **mime_types,
+                             void *userdata)
+{
+    SDL_VideoData *video_data = NULL;
+    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;
+
+        if (callback && mime_types) {
+            SDL_WaylandDataSource *source = Wayland_data_source_create(_this);
+            Wayland_data_source_set_callback(source, callback, userdata, SDL_FALSE);
+
+            status = Wayland_data_device_set_selection(data_device, source, mime_count, mime_types);
+            if (status != 0) {
+                Wayland_data_source_destroy(source);
+            }
+        } else {
+            status = Wayland_data_device_clear_selection(data_device);
+        }
+    }
+
+    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 void *Wayland_ClipboardTextCallback(size_t *length, const char *mime_type, void *userdata)
+{
+    void *data = NULL;
+    SDL_bool valid_mime_type = SDL_FALSE;
+    *length = 0;
+
+    if (userdata == NULL) {
+        return data;
+    }
+
+    for (size_t i = 0; i < TEXT_MIME_TYPES_LEN; ++i) {
+        if (SDL_strcmp(mime_type, text_mime_types[i]) == 0) {
+            valid_mime_type = SDL_TRUE;
+            break;
+        }
+    }
+
+    if (valid_mime_type) {
+        char *text = userdata;
+        *length = SDL_strlen(text);
+        data = userdata;
+    }
+
+    return data;
+}
+
 int Wayland_SetClipboardText(SDL_VideoDevice *_this, const char *text)
 {
     SDL_VideoData *video_data = NULL;
@@ -39,12 +103,12 @@ int Wayland_SetClipboardText(SDL_VideoDevice *_this, const char *text)
         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_add_data(source, TEXT_MIME, text,
-                                             SDL_strlen(text));
+                Wayland_data_source_set_callback(source, Wayland_ClipboardTextCallback, SDL_strdup(text), SDL_TRUE);
 
-                status = Wayland_data_device_set_selection(data_device, source);
+                status = Wayland_data_device_set_selection(data_device, source, TEXT_MIME_TYPES_LEN, text_mime_types);
                 if (status != 0) {
                     Wayland_data_source_destroy(source);
                 }
@@ -72,11 +136,12 @@ int Wayland_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text)
             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_add_data(source, TEXT_MIME, text,
-                                                          SDL_strlen(text));
+                Wayland_primary_selection_source_set_callback(source, Wayland_ClipboardTextCallback, SDL_strdup(text));
 
                 status = Wayland_primary_selection_device_set_selection(primary_selection_device,
-                                                                        source);
+                                                                        source,
+                                                                        TEXT_MIME_TYPES_LEN,
+                                                                        text_mime_types);
                 if (status != 0) {
                     Wayland_primary_selection_source_destroy(source);
                 }
@@ -89,6 +154,29 @@ int Wayland_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text)
     return status;
 }
 
+void *Wayland_GetClipboardData(SDL_VideoDevice *_this, size_t *length, const char *mime_type)
+{
+    SDL_VideoData *video_data = NULL;
+    SDL_WaylandDataDevice *data_device = NULL;
+
+    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 != NULL) {
+            buffer = Wayland_data_source_get_data(data_device->selection_source,
+                    length, mime_type, SDL_FALSE);
+        } else if (Wayland_data_offer_has_mime(
+                    data_device->selection_offer, mime_type)) {
+            buffer = Wayland_data_offer_receive(data_device->selection_offer,
+                    length, mime_type, SDL_FALSE);
+        }
+    }
+
+    return buffer;
+}
+
 char *Wayland_GetClipboardText(SDL_VideoDevice *_this)
 {
     SDL_VideoData *video_data = NULL;
@@ -103,13 +191,10 @@ char *Wayland_GetClipboardText(SDL_VideoDevice *_this)
         video_data = _this->driverdata;
         if (video_data->input != NULL && video_data->input->data_device != NULL) {
             data_device = video_data->input->data_device;
-            /* Prefer own selection, if not canceled */
-            if (Wayland_data_source_has_mime(
-                    data_device->selection_source, TEXT_MIME)) {
-                text = Wayland_data_source_get_data(data_device->selection_source,
-                                                    &length, TEXT_MIME, SDL_TRUE);
+            if (data_device->selection_source != NULL) {
+                text = Wayland_data_source_get_data(data_device->selection_source, &length, TEXT_MIME, SDL_TRUE);
             } else if (Wayland_data_offer_has_mime(
-                           data_device->selection_offer, TEXT_MIME)) {
+                        data_device->selection_offer, TEXT_MIME)) {
                 text = Wayland_data_offer_receive(data_device->selection_offer,
                                                   &length, TEXT_MIME, SDL_TRUE);
             }
@@ -137,13 +222,10 @@ char *Wayland_GetPrimarySelectionText(SDL_VideoDevice *_this)
         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;
-            /* Prefer own selection, if not canceled */
-            if (Wayland_primary_selection_source_has_mime(
-                    primary_selection_device->selection_source, TEXT_MIME)) {
-                text = Wayland_primary_selection_source_get_data(primary_selection_device->selection_source,
-                                                                 &length, TEXT_MIME, SDL_TRUE);
+            if (primary_selection_device->selection_source != NULL) {
+                text = Wayland_primary_selection_source_get_data(primary_selection_device->selection_source, &length, TEXT_MIME, SDL_TRUE);
             } else if (Wayland_primary_selection_offer_has_mime(
-                           primary_selection_device->selection_offer, TEXT_MIME)) {
+                        primary_selection_device->selection_offer, TEXT_MIME)) {
                 text = Wayland_primary_selection_offer_receive(primary_selection_device->selection_offer,
                                                                &length, TEXT_MIME, SDL_TRUE);
             }
@@ -157,7 +239,7 @@ char *Wayland_GetPrimarySelectionText(SDL_VideoDevice *_this)
     return text;
 }
 
-SDL_bool Wayland_HasClipboardText(SDL_VideoDevice *_this)
+static SDL_bool HasClipboardData(SDL_VideoDevice *_this, const char *mime_type)
 {
     SDL_VideoData *video_data = NULL;
     SDL_WaylandDataDevice *data_device = NULL;
@@ -170,13 +252,22 @@ SDL_bool Wayland_HasClipboardText(SDL_VideoDevice *_this)
         if (video_data->input != NULL && video_data->input->data_device != NULL) {
             data_device = video_data->input->data_device;
             result = result ||
-                     Wayland_data_source_has_mime(data_device->selection_source, TEXT_MIME) ||
-                     Wayland_data_offer_has_mime(data_device->selection_offer, TEXT_MIME);
+                     Wayland_data_offer_has_mime(data_device->selection_offer, mime_type);
         }
     }
     return result;
 }
 
+SDL_bool Wayland_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type)
+{
+    return HasClipboardData(_this, mime_type);
+}
+
+SDL_bool Wayland_HasClipboardText(SDL_VideoDevice *_this)
+{
+    return HasClipboardData(_this, TEXT_MIME);
+}
+
 SDL_bool Wayland_HasPrimarySelectionText(SDL_VideoDevice *_this)
 {
     SDL_VideoData *video_data = NULL;
@@ -190,8 +281,6 @@ SDL_bool Wayland_HasPrimarySelectionText(SDL_VideoDevice *_this)
         if (video_data->input != NULL && video_data->input->primary_selection_device != NULL) {
             primary_selection_device = video_data->input->primary_selection_device;
             result = result ||
-                     Wayland_primary_selection_source_has_mime(
-                         primary_selection_device->selection_source, TEXT_MIME) ||
                      Wayland_primary_selection_offer_has_mime(
                          primary_selection_device->selection_offer, TEXT_MIME);
         }
@@ -199,4 +288,22 @@ SDL_bool Wayland_HasPrimarySelectionText(SDL_VideoDevice *_this)
     return result;
 }
 
+void *Wayland_GetClipboardUserdata(SDL_VideoDevice *_this)
+{
+    SDL_VideoData *video_data = NULL;
+    SDL_WaylandDataDevice *data_device = NULL;
+    void *data = 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 != NULL
+                && data_device->selection_source->userdata.internal == SDL_FALSE) {
+            data = data_device->selection_source->userdata.data;
+        }
+    }
+
+    return data;
+}
+
 #endif /* SDL_VIDEO_DRIVER_WAYLAND */

+ 5 - 0
src/video/wayland/SDL_waylandclipboard.h

@@ -23,11 +23,16 @@
 #ifndef SDL_waylandclipboard_h_
 #define SDL_waylandclipboard_h_
 
+extern int Wayland_SetClipboardData(SDL_VideoDevice *_this, SDL_ClipboardDataCallback callback, size_t mime_count,
+                                    const char **mime_types, void *userdata);
+extern void *Wayland_GetClipboardData(SDL_VideoDevice *_this, size_t *length, const char *mime_type);
+extern SDL_bool Wayland_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type);
 extern int Wayland_SetClipboardText(SDL_VideoDevice *_this, const char *text);
 extern char *Wayland_GetClipboardText(SDL_VideoDevice *_this);
 extern SDL_bool Wayland_HasClipboardText(SDL_VideoDevice *_this);
 extern int Wayland_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text);
 extern char *Wayland_GetPrimarySelectionText(SDL_VideoDevice *_this);
 extern SDL_bool Wayland_HasPrimarySelectionText(SDL_VideoDevice *_this);
+extern void *Wayland_GetClipboardUserdata(SDL_VideoDevice *_this);
 
 #endif /* SDL_waylandclipboard_h_ */

+ 89 - 145
src/video/wayland/SDL_waylanddatamanager.c

@@ -29,6 +29,7 @@
 #include <signal.h>
 
 #include "../../core/unix/SDL_poll.h"
+#include "../../events/SDL_events_c.h"
 
 #include "SDL_waylandvideo.h"
 #include "SDL_waylanddatamanager.h"
@@ -136,31 +137,6 @@ static ssize_t read_pipe(int fd, void **buffer, size_t *total_length, SDL_bool n
     return bytes_read;
 }
 
-#define MIME_LIST_SIZE 4
-
-static const char *mime_conversion_list[MIME_LIST_SIZE][2] = {
-    { "text/plain", TEXT_MIME },
-    { "TEXT", TEXT_MIME },
-    { "UTF8_STRING", TEXT_MIME },
-    { "STRING", TEXT_MIME }
-};
-
-const char *Wayland_convert_mime_type(const char *mime_type)
-{
-    const char *found = mime_type;
-
-    size_t index = 0;
-
-    for (index = 0; index < MIME_LIST_SIZE; ++index) {
-        if (SDL_strcmp(mime_conversion_list[index][0], mime_type) == 0) {
-            found = mime_conversion_list[index][1];
-            break;
-        }
-    }
-
-    return found;
-}
-
 static SDL_MimeDataList *mime_data_list_find(struct wl_list *list,
                                              const char *mime_type)
 {
@@ -242,131 +218,112 @@ static void mime_data_list_free(struct wl_list *list)
     }
 }
 
-static ssize_t
-Wayland_source_send(SDL_MimeDataList *mime_data, const char *mime_type, int fd)
+static size_t
+Wayland_send_data(void *data, size_t length, int fd)
 {
-    size_t written_bytes = 0;
-    ssize_t status = 0;
+    size_t result = 0;
 
-    if (mime_data == NULL || mime_data->data == NULL) {
-        status = SDL_SetError("Invalid mime type");
-        close(fd);
-    } else {
-        while (write_pipe(fd,
-                          mime_data->data,
-                          mime_data->length,
-                          &written_bytes) > 0) {
+    if (length > 0 && data != NULL) {
+        while (write_pipe(fd, data, length, &result) > 0) {
+            /* Just keep spinning */
         }
-        close(fd);
-        status = written_bytes;
     }
-    return status;
+    close(fd);
+
+    return result;
 }
 
 ssize_t
 Wayland_data_source_send(SDL_WaylandDataSource *source,
                          const char *mime_type, int fd)
 {
-    SDL_MimeDataList *mime_data = NULL;
+    void *data = NULL;
+    size_t length = 0;
 
-    mime_type = Wayland_convert_mime_type(mime_type);
-    mime_data = mime_data_list_find(&source->mimes,
-                                    mime_type);
+    if (source->callback) {
+        data = source->callback(&length, mime_type, source->userdata.data);
+    }
 
-    return Wayland_source_send(mime_data, mime_type, fd);
+    return Wayland_send_data(data, length, fd);
 }
 
 ssize_t
 Wayland_primary_selection_source_send(SDL_WaylandPrimarySelectionSource *source,
                                       const char *mime_type, int fd)
 {
-    SDL_MimeDataList *mime_data = NULL;
-
-    mime_type = Wayland_convert_mime_type(mime_type);
-    mime_data = mime_data_list_find(&source->mimes,
-                                    mime_type);
+    void *data = NULL;
+    size_t length = 0;
 
-    return Wayland_source_send(mime_data, mime_type, fd);
-}
-
-int Wayland_data_source_add_data(SDL_WaylandDataSource *source,
-                                 const char *mime_type,
-                                 const void *buffer,
-                                 size_t length)
-{
-    return mime_data_list_add(&source->mimes, mime_type, buffer, length);
-}
+    if (source->callback) {
+        data = source->callback(&length, mime_type, source->userdata.data);
+    }
 
-int Wayland_primary_selection_source_add_data(SDL_WaylandPrimarySelectionSource *source,
-                                              const char *mime_type,
-                                              const void *buffer,
-                                              size_t length)
-{
-    return mime_data_list_add(&source->mimes, mime_type, buffer, length);
+    return Wayland_send_data(data, length, fd);
 }
 
-SDL_bool Wayland_data_source_has_mime(SDL_WaylandDataSource *source,
-                                      const char *mime_type)
+void Wayland_data_source_set_callback(SDL_WaylandDataSource *source,
+                                      SDL_ClipboardDataCallback callback,
+                                      void *userdata,
+                                      SDL_bool internal)
 {
-    SDL_bool found = SDL_FALSE;
-
     if (source != NULL) {
-        found = mime_data_list_find(&source->mimes, mime_type) != NULL;
+    source->callback = callback;
+    source->userdata.internal = internal;
+    source->userdata.data = userdata;
     }
-    return found;
 }
 
-SDL_bool Wayland_primary_selection_source_has_mime(SDL_WaylandPrimarySelectionSource *source,
-                                                   const char *mime_type)
+int Wayland_primary_selection_source_set_callback(SDL_WaylandPrimarySelectionSource *source,
+                                                  SDL_ClipboardDataCallback callback,
+                                                  void *userdata)
 {
-    SDL_bool found = SDL_FALSE;
-
-    if (source != NULL) {
-        found = mime_data_list_find(&source->mimes, mime_type) != NULL;
+    if (source == NULL) {
+        return SDL_InvalidParamError("source");
     }
-    return found;
+    source->callback = callback;
+    source->userdata.internal = SDL_TRUE;
+    source->userdata.data = userdata;
+    return 0;
 }
 
-static void *Wayland_source_get_data(SDL_MimeDataList *mime_data,
-                                     size_t *length,
-                                     SDL_bool null_terminate)
+static void *Wayland_clone_data_buffer(void *buffer, size_t *len, SDL_bool null_terminate)
 {
-    void *buffer = NULL;
-
-    if (mime_data != NULL && mime_data->length > 0) {
-        size_t buffer_length = mime_data->length;
-
+    void *clone = NULL;
+    if (*len > 0 && buffer != NULL) {
         if (null_terminate == SDL_TRUE) {
-            ++buffer_length;
-        }
-        buffer = SDL_malloc(buffer_length);
-        if (buffer == NULL) {
-            *length = SDL_OutOfMemory();
+            clone = SDL_malloc((*len)+1);
+            if (clone == NULL) {
+                SDL_OutOfMemory();
+            } else {
+                SDL_memcpy(clone, buffer, *len);
+                ((char *) clone)[*len] = '\0';
+                *len += 1;
+            }
         } else {
-            *length = mime_data->length;
-            SDL_memcpy(buffer, mime_data->data, mime_data->length);
-            if (null_terminate) {
-                *((Uint8 *)buffer + mime_data->length) = 0;
+            clone = SDL_malloc(*len);
+            if (clone == NULL) {
+                SDL_OutOfMemory();
+            } else {
+                SDL_memcpy(clone, buffer, *len);
             }
         }
     }
-
-    return buffer;
+    return clone;
 }
 
 void *Wayland_data_source_get_data(SDL_WaylandDataSource *source,
                                    size_t *length, const char *mime_type,
                                    SDL_bool null_terminate)
 {
-    SDL_MimeDataList *mime_data = NULL;
     void *buffer = NULL;
+    void *internal_buffer;
     *length = 0;
 
     if (source == NULL) {
         SDL_SetError("Invalid data source");
-    } else {
-        mime_data = mime_data_list_find(&source->mimes, mime_type);
-        buffer = Wayland_source_get_data(mime_data, length, null_terminate);
+    } else if (source->callback != NULL) {
+        internal_buffer = source->callback(length, mime_type, source->userdata.data);
+        buffer = Wayland_clone_data_buffer(internal_buffer, length, null_terminate);
     }
 
     return buffer;
@@ -376,15 +333,15 @@ void *Wayland_primary_selection_source_get_data(SDL_WaylandPrimarySelectionSourc
                                                 size_t *length, const char *mime_type,
                                                 SDL_bool null_terminate)
 {
-    SDL_MimeDataList *mime_data = NULL;
     void *buffer = NULL;
+    void *internal_buffer;
     *length = 0;
 
     if (source == NULL) {
         SDL_SetError("Invalid primary selection source");
-    } else {
-        mime_data = mime_data_list_find(&source->mimes, mime_type);
-        buffer = Wayland_source_get_data(mime_data, length, null_terminate);
+    } else if (source->callback) {
+        internal_buffer = source->callback(length, mime_type, source->userdata.data);
+        buffer = Wayland_clone_data_buffer(internal_buffer, length, null_terminate);
     }
 
     return buffer;
@@ -398,7 +355,11 @@ void Wayland_data_source_destroy(SDL_WaylandDataSource *source)
             data_device->selection_source = NULL;
         }
         wl_data_source_destroy(source->source);
-        mime_data_list_free(&source->mimes);
+        if (source->userdata.internal == SDL_TRUE) {
+            SDL_free(source->userdata.data);
+        } else {
+            SDL_SendClipboardCancelled(source->userdata.data);
+        }
         SDL_free(source);
     }
 }
@@ -411,7 +372,9 @@ void Wayland_primary_selection_source_destroy(SDL_WaylandPrimarySelectionSource
             primary_selection_device->selection_source = NULL;
         }
         zwp_primary_selection_source_v1_destroy(source->source);
-        mime_data_list_free(&source->mimes);
+        if (source->userdata.internal == SDL_TRUE) {
+            SDL_free(source->userdata.data);
+        }
         SDL_free(source);
     }
 }
@@ -566,36 +529,27 @@ int Wayland_primary_selection_device_clear_selection(SDL_WaylandPrimarySelection
 }
 
 int Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device,
-                                      SDL_WaylandDataSource *source)
+                                      SDL_WaylandDataSource *source,
+                                      size_t mime_count,
+                                      const char **mime_types)
 {
     int status = 0;
-    size_t num_offers = 0;
-    size_t index = 0;
 
     if (data_device == NULL) {
         status = SDL_SetError("Invalid Data Device");
     } else if (source == NULL) {
         status = SDL_SetError("Invalid source");
     } else {
-        SDL_MimeDataList *mime_data = NULL;
+        size_t index = 0;
+        const char *mime_type;
 
-        wl_list_for_each (mime_data, &(source->mimes), link) {
+        for (index = 0; index < mime_count; ++index) {
+            mime_type = mime_types[index];
             wl_data_source_offer(source->source,
-                                 mime_data->mime_type);
-
-            /* TODO - Improve system for multiple mime types to same data */
-            for (index = 0; index < MIME_LIST_SIZE; ++index) {
-                if (SDL_strcmp(mime_conversion_list[index][1], mime_data->mime_type) == 0) {
-                    wl_data_source_offer(source->source,
-                                         mime_conversion_list[index][0]);
-                }
-            }
-            /* */
-
-            ++num_offers;
+                                 mime_type);
         }
 
-        if (num_offers == 0) {
+        if (index == 0) {
             Wayland_data_device_clear_selection(data_device);
             status = SDL_SetError("No mime data");
         } else {
@@ -617,36 +571,26 @@ int Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device,
 }
 
 int Wayland_primary_selection_device_set_selection(SDL_WaylandPrimarySelectionDevice *primary_selection_device,
-                                                   SDL_WaylandPrimarySelectionSource *source)
+                                                   SDL_WaylandPrimarySelectionSource *source,
+                                                   size_t mime_count,
+                                                   const char **mime_types)
 {
     int status = 0;
-    size_t num_offers = 0;
-    size_t index = 0;
 
     if (primary_selection_device == NULL) {
         status = SDL_SetError("Invalid Primary Selection Device");
     } else if (source == NULL) {
         status = SDL_SetError("Invalid source");
     } else {
-        SDL_MimeDataList *mime_data = NULL;
-
-        wl_list_for_each (mime_data, &(source->mimes), link) {
-            zwp_primary_selection_source_v1_offer(source->source,
-                                                  mime_data->mime_type);
-
-            /* TODO - Improve system for multiple mime types to same data */
-            for (index = 0; index < MIME_LIST_SIZE; ++index) {
-                if (SDL_strcmp(mime_conversion_list[index][1], mime_data->mime_type) == 0) {
-                    zwp_primary_selection_source_v1_offer(source->source,
-                                                          mime_conversion_list[index][0]);
-                }
-            }
-            /* */
+        size_t index = 0;
+        const char *mime_type = mime_types[index];
 
-            ++num_offers;
+        for (index = 0; index < mime_count; ++index) {
+            mime_type = mime_types[index];
+            zwp_primary_selection_source_v1_offer(source->source, mime_type);
         }
 
-        if (num_offers == 0) {
+        if (index == 0) {
             Wayland_primary_selection_device_clear_selection(primary_selection_device);
             status = SDL_SetError("No mime data");
         } else {

+ 24 - 18
src/video/wayland/SDL_waylanddatamanager.h

@@ -38,18 +38,27 @@ typedef struct
     struct wl_list link;
 } SDL_MimeDataList;
 
+typedef struct SDL_WaylandUserdata
+{
+    SDL_bool internal;
+    void *data;
+} SDL_WaylandUserdata;
+
 typedef struct
 {
     struct wl_data_source *source;
-    struct wl_list mimes;
     void *data_device;
+    SDL_ClipboardDataCallback callback;
+    SDL_WaylandUserdata userdata;
 } SDL_WaylandDataSource;
 
 typedef struct
 {
     struct zwp_primary_selection_source_v1 *source;
-    struct wl_list mimes;
+    void *data_device;
     void *primary_selection_device;
+    SDL_ClipboardDataCallback callback;
+    SDL_WaylandUserdata userdata;
 } SDL_WaylandPrimarySelectionSource;
 
 typedef struct
@@ -92,8 +101,6 @@ typedef struct
     SDL_WaylandPrimarySelectionOffer *selection_offer;
 } SDL_WaylandPrimarySelectionDevice;
 
-extern const char *Wayland_convert_mime_type(const char *mime_type);
-
 /* Wayland Data Source / Primary Selection Source - (Sending) */
 extern SDL_WaylandDataSource *Wayland_data_source_create(SDL_VideoDevice *_this);
 extern SDL_WaylandPrimarySelectionSource *Wayland_primary_selection_source_create(SDL_VideoDevice *_this);
@@ -101,18 +108,13 @@ extern ssize_t Wayland_data_source_send(SDL_WaylandDataSource *source,
                                         const char *mime_type, int fd);
 extern ssize_t Wayland_primary_selection_source_send(SDL_WaylandPrimarySelectionSource *source,
                                                      const char *mime_type, int fd);
-extern int Wayland_data_source_add_data(SDL_WaylandDataSource *source,
-                                        const char *mime_type,
-                                        const void *buffer,
-                                        size_t length);
-extern int Wayland_primary_selection_source_add_data(SDL_WaylandPrimarySelectionSource *source,
-                                                     const char *mime_type,
-                                                     const void *buffer,
-                                                     size_t length);
-extern SDL_bool Wayland_data_source_has_mime(SDL_WaylandDataSource *source,
-                                             const char *mime_type);
-extern SDL_bool Wayland_primary_selection_source_has_mime(SDL_WaylandPrimarySelectionSource *source,
-                                                          const char *mime_type);
+extern void Wayland_data_source_set_callback(SDL_WaylandDataSource *source,
+                                            SDL_ClipboardDataCallback callback,
+                                            void *userdata,
+                                            SDL_bool internal);
+extern int Wayland_primary_selection_source_set_callback(SDL_WaylandPrimarySelectionSource *source,
+                                                         SDL_ClipboardDataCallback callback,
+                                                         void *userdata);
 extern void *Wayland_data_source_get_data(SDL_WaylandDataSource *source,
                                           size_t *length,
                                           const char *mime_type,
@@ -148,9 +150,13 @@ extern void Wayland_primary_selection_offer_destroy(SDL_WaylandPrimarySelectionO
 extern int Wayland_data_device_clear_selection(SDL_WaylandDataDevice *device);
 extern int Wayland_primary_selection_device_clear_selection(SDL_WaylandPrimarySelectionDevice *device);
 extern int Wayland_data_device_set_selection(SDL_WaylandDataDevice *device,
-                                             SDL_WaylandDataSource *source);
+                                             SDL_WaylandDataSource *source,
+                                             size_t mime_count,
+                                             const char **mime_types);
 extern int Wayland_primary_selection_device_set_selection(SDL_WaylandPrimarySelectionDevice *device,
-                                                          SDL_WaylandPrimarySelectionSource *source);
+                                                          SDL_WaylandPrimarySelectionSource *source,
+                                                          size_t mime_count,
+                                                          const char **mime_types);
 extern int Wayland_data_device_set_serial(SDL_WaylandDataDevice *device,
                                           uint32_t serial);
 extern int Wayland_primary_selection_device_set_serial(SDL_WaylandPrimarySelectionDevice *device,

+ 7 - 3
src/video/wayland/SDL_waylandevents.c

@@ -1673,7 +1673,10 @@ static void data_source_handle_send(void *data, struct wl_data_source *wl_data_s
 
 static void data_source_handle_cancelled(void *data, struct wl_data_source *wl_data_source)
 {
-    Wayland_data_source_destroy(data);
+    SDL_WaylandDataSource *source = data;
+    if (source != NULL) {
+        Wayland_data_source_destroy(source);
+    }
 }
 
 static void data_source_handle_dnd_drop_performed(void *data, struct wl_data_source *wl_data_source)
@@ -1739,7 +1742,6 @@ SDL_WaylandDataSource *Wayland_data_source_create(SDL_VideoDevice *_this)
                 SDL_OutOfMemory();
                 wl_data_source_destroy(id);
             } else {
-                WAYLAND_wl_list_init(&(data_source->mimes));
                 data_source->source = id;
                 wl_data_source_set_user_data(id, data_source);
                 wl_data_source_add_listener(id, &data_source_listener,
@@ -1774,7 +1776,6 @@ SDL_WaylandPrimarySelectionSource *Wayland_primary_selection_source_create(SDL_V
                 SDL_OutOfMemory();
                 zwp_primary_selection_source_v1_destroy(id);
             } else {
-                WAYLAND_wl_list_init(&(primary_selection_source->mimes));
                 primary_selection_source->source = id;
                 zwp_primary_selection_source_v1_add_listener(id, &primary_selection_source_listener,
                                                              primary_selection_source);
@@ -2728,6 +2729,9 @@ void Wayland_display_destroy_input(SDL_VideoData *d)
         if (input->primary_selection_device->selection_offer != NULL) {
             Wayland_primary_selection_offer_destroy(input->primary_selection_device->selection_offer);
         }
+        if (input->primary_selection_device->selection_source != NULL) {
+            Wayland_primary_selection_source_destroy(input->primary_selection_device->selection_source);
+        }
         SDL_free(input->primary_selection_device);
     }
 

+ 4 - 0
src/video/wayland/SDL_waylandvideo.c

@@ -269,12 +269,16 @@ static SDL_VideoDevice *Wayland_CreateDevice(void)
         device->system_theme = SDL_SystemTheme_Get();
 #endif
 
+    device->SetClipboardData = Wayland_SetClipboardData;
+    device->GetClipboardData = Wayland_GetClipboardData;
+    device->HasClipboardData = Wayland_HasClipboardData;
     device->SetClipboardText = Wayland_SetClipboardText;
     device->GetClipboardText = Wayland_GetClipboardText;
     device->HasClipboardText = Wayland_HasClipboardText;
     device->SetPrimarySelectionText = Wayland_SetPrimarySelectionText;
     device->GetPrimarySelectionText = Wayland_GetPrimarySelectionText;
     device->HasPrimarySelectionText = Wayland_HasPrimarySelectionText;
+    device->GetClipboardUserdata = Wayland_GetClipboardUserdata;
     device->StartTextInput = Wayland_StartTextInput;
     device->StopTextInput = Wayland_StopTextInput;
     device->SetTextInputRect = Wayland_SetTextInputRect;

+ 66 - 1
test/testautomation_clipboard.c

@@ -22,6 +22,19 @@ static int clipboard_testHasClipboardText(void *arg)
     return TEST_COMPLETED;
 }
 
+/**
+ * \brief Check call to SDL_HasClipboardData
+ *
+ * \sa SDL_HasClipboardData
+ */
+static int clipboard_testHasClipboardData(void *arg)
+{
+    SDL_HasClipboardData("image/png");
+    SDLTest_AssertPass("Call to SDL_HasClipboardText succeeded");
+
+    return TEST_COMPLETED;
+}
+
 /**
  * \brief Check call to SDL_HasPrimarySelectionText
  *
@@ -51,6 +64,25 @@ static int clipboard_testGetClipboardText(void *arg)
     return TEST_COMPLETED;
 }
 
+/**
+ * \brief Check call to SDL_GetClipboardData
+ *
+ * \sa SDL_GetClipboardText
+ */
+static int clipboard_testGetClipboardData(void *arg)
+{
+    void *buffer = NULL;
+    size_t length;
+    buffer = SDL_GetClipboardData(&length, "image/png");
+    SDLTest_AssertPass("Call to SDL_GetClipboardData succeeded");
+
+    if (buffer != NULL) {
+        SDL_free(buffer);
+    }
+
+    return TEST_COMPLETED;
+}
+
 /**
  * \brief Check call to SDL_GetPrimarySelectionText
  *
@@ -94,6 +126,27 @@ static int clipboard_testSetClipboardText(void *arg)
     return TEST_COMPLETED;
 }
 
+/**
+ * \brief Check call to SDL_SetClipboardData
+ * \sa SDL_SetClipboardText
+ */
+static int clipboard_testSetClipboardData(void *arg)
+{
+    int result = -1;
+
+    result = SDL_SetClipboardData(NULL, 0, NULL, NULL);
+    SDLTest_AssertPass("Call to SDL_SetClipboardData succeeded");
+    SDLTest_AssertCheck(
+        result == 0,
+        "Validate SDL_SetClipboardData result, expected 0, got %i",
+        result);
+
+    SDL_GetClipboardUserdata();
+    SDLTest_AssertPass("Call to SDL_GetClipboardUserdata succeeded");
+
+    return TEST_COMPLETED;
+}
+
 /**
  * \brief Check call to SDL_SetPrimarySelectionText
  * \sa SDL_SetPrimarySelectionText
@@ -310,9 +363,21 @@ static const SDLTest_TestCaseReference clipboardTest8 = {
     (SDLTest_TestCaseFp)clipboard_testPrimarySelectionTextFunctions, "clipboard_testPrimarySelectionTextFunctions", "End-to-end test of SDL_xyzPrimarySelectionText functions", TEST_ENABLED
 };
 
+static const SDLTest_TestCaseReference clipboardTest9 = {
+    (SDLTest_TestCaseFp)clipboard_testGetClipboardData, "clipboard_testGetClipboardData", "Check call to SDL_GetClipboardData", TEST_ENABLED
+};
+
+static const SDLTest_TestCaseReference clipboardTest10 = {
+    (SDLTest_TestCaseFp)clipboard_testSetClipboardData, "clipboard_testSetClipboardData", "Check call to SDL_SetClipboardData", TEST_ENABLED
+};
+
+static const SDLTest_TestCaseReference clipboardTest11 = {
+    (SDLTest_TestCaseFp)clipboard_testHasClipboardData, "clipboard_testHasClipboardData", "Check call to SDL_HasClipboardData", TEST_ENABLED
+};
+
 /* Sequence of Clipboard test cases */
 static const SDLTest_TestCaseReference *clipboardTests[] = {
-    &clipboardTest1, &clipboardTest2, &clipboardTest3, &clipboardTest4, &clipboardTest5, &clipboardTest6, &clipboardTest7, &clipboardTest8, NULL
+    &clipboardTest1, &clipboardTest2, &clipboardTest3, &clipboardTest4, &clipboardTest5, &clipboardTest6, &clipboardTest7, &clipboardTest8, &clipboardTest9, &clipboardTest10, &clipboardTest11, NULL
 };
 
 /* Clipboard test suite (global) */