SDL: Implement generic clipboard data on MacOS

From f94aa6208ab533512a98984ecfd783af873a4bbe Mon Sep 17 00:00:00 2001
From: Vid Tadel <[EMAIL REDACTED]>
Date: Mon, 15 May 2023 10:44:34 +0200
Subject: [PATCH] Implement generic clipboard data on MacOS

---
 src/video/cocoa/SDL_cocoaclipboard.h |   5 ++
 src/video/cocoa/SDL_cocoaclipboard.m | 121 +++++++++++++++++++++++++++
 src/video/cocoa/SDL_cocoavideo.m     |   4 +
 3 files changed, 130 insertions(+)

diff --git a/src/video/cocoa/SDL_cocoaclipboard.h b/src/video/cocoa/SDL_cocoaclipboard.h
index 72756a4db94f..bad760cf8e66 100644
--- a/src/video/cocoa/SDL_cocoaclipboard.h
+++ b/src/video/cocoa/SDL_cocoaclipboard.h
@@ -30,5 +30,10 @@ extern int Cocoa_SetClipboardText(SDL_VideoDevice *_this, const char *text);
 extern char *Cocoa_GetClipboardText(SDL_VideoDevice *_this);
 extern SDL_bool Cocoa_HasClipboardText(SDL_VideoDevice *_this);
 extern void Cocoa_CheckClipboardUpdate(SDL_CocoaVideoData *data);
+extern void *Cocoa_GetClipboardData(SDL_VideoDevice *_this, size_t *len, const char *mime_type);
+extern SDL_bool Cocoa_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type);
+extern int Cocoa_SetClipboardData(SDL_VideoDevice *_this, SDL_ClipboardDataCallback callback, size_t mime_count,
+                                  const char **mime_types, void *userdata);
+
 
 #endif /* SDL_cocoaclipboard_h_ */
diff --git a/src/video/cocoa/SDL_cocoaclipboard.m b/src/video/cocoa/SDL_cocoaclipboard.m
index ad424707cd5d..c54a76c18e55 100644
--- a/src/video/cocoa/SDL_cocoaclipboard.m
+++ b/src/video/cocoa/SDL_cocoaclipboard.m
@@ -25,6 +25,50 @@
 #include "SDL_cocoavideo.h"
 #include "../../events/SDL_clipboardevents_c.h"
 
+@interface Cocoa_PasteboardDataProvider : NSObject<NSPasteboardItemDataProvider>
+{
+    SDL_ClipboardDataCallback m_callback;
+    void *m_userdata;
+}
+@end
+
+@implementation Cocoa_PasteboardDataProvider
+
+- (nullable instancetype)initWith:(SDL_ClipboardDataCallback)callback
+                         userData:(void *)userdata
+{
+    self = [super init];
+    if (!self) {
+        return self;
+    }
+    m_callback = callback;
+    m_userdata = userdata;
+    return self;
+}
+
+- (void)pasteboard:(NSPasteboard *)pasteboard
+              item:(NSPasteboardItem *)item
+provideDataForType:(NSPasteboardType)type
+{
+    @autoreleasepool {
+        size_t size = 0;
+        CFStringRef mimeType;
+        void *callbackData;
+        NSData *data;
+        mimeType = UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)type, kUTTagClassMIMEType);
+        callbackData = m_callback(&size, [(__bridge NSString *)mimeType UTF8String], m_userdata);
+        CFRelease(mimeType);
+        if (callbackData == NULL || size == 0) {
+            return;
+        }
+        data = [NSData dataWithBytes: callbackData length: size];
+        [item setData: data forType: type];
+    }
+}
+
+@end
+
+
 int Cocoa_SetClipboardText(SDL_VideoDevice *_this, const char *text)
 {
     @autoreleasepool {
@@ -103,4 +147,81 @@ void Cocoa_CheckClipboardUpdate(SDL_CocoaVideoData *data)
     }
 }
 
+void *Cocoa_GetClipboardData(SDL_VideoDevice *_this, size_t *len, const char *mime_type)
+{
+    @autoreleasepool {
+        NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
+        NSData *itemData;
+        void *data;
+        *len = 0;
+        for (NSPasteboardItem *item in [pasteboard pasteboardItems]) {
+            CFStringRef mimeType = CFStringCreateWithCString(NULL, mime_type, kCFStringEncodingUTF8);
+            CFStringRef utiType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL);
+            CFRelease(mimeType);
+            itemData = [item dataForType: (__bridge NSString *)utiType];
+            CFRelease(utiType);
+            if (itemData != nil) {
+                *len = (size_t)[itemData length];
+                data = malloc(*len);
+                [itemData getBytes: data length: *len];
+                return data;
+            }
+        }
+        return nil;
+    }
+}
+
+SDL_bool Cocoa_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type)
+{
+
+    SDL_bool result = SDL_FALSE;
+    @autoreleasepool {
+        NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
+        CFStringRef mimeType = CFStringCreateWithCString(NULL, mime_type, kCFStringEncodingUTF8);
+        CFStringRef utiType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL);
+        CFRelease(mimeType);
+        if ([pasteboard canReadItemWithDataConformingToTypes: @[(__bridge NSString *)utiType]]) {
+            result = SDL_TRUE;
+        }
+        CFRelease(utiType);
+    }
+    return result;
+
+}
+
+int Cocoa_SetClipboardData(SDL_VideoDevice *_this, SDL_ClipboardDataCallback callback, size_t mime_count,
+                           const char **mime_types, void *userdata)
+{
+    @autoreleasepool {
+        NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
+        NSPasteboardItem *newItem = [NSPasteboardItem new];
+        NSMutableArray *utiTypes = [NSMutableArray new];
+        Cocoa_PasteboardDataProvider *provider = [[Cocoa_PasteboardDataProvider alloc] initWith: callback userData: userdata];
+        BOOL itemResult = FALSE;
+        BOOL writeResult = FALSE;
+        SDL_CocoaVideoData *data = (__bridge SDL_CocoaVideoData *)_this->driverdata;
+
+        for (int i = 0; i < mime_count; i++) {
+            CFStringRef mimeType = CFStringCreateWithCString(NULL, 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");
+        }
+
+        [pasteboard clearContents];
+        writeResult = [pasteboard writeObjects: @[newItem]];
+        if (writeResult == FALSE) {
+            return SDL_SetError("Unable to set clipboard data");
+        }
+        data.clipboard_count = [pasteboard changeCount];
+    }
+    return 0;
+}
+
 #endif /* SDL_VIDEO_DRIVER_COCOA */
diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m
index d8e939f937af..cc4d3848921a 100644
--- a/src/video/cocoa/SDL_cocoavideo.m
+++ b/src/video/cocoa/SDL_cocoavideo.m
@@ -175,6 +175,10 @@ static void Cocoa_DeleteDevice(SDL_VideoDevice *device)
         device->GetClipboardText = Cocoa_GetClipboardText;
         device->HasClipboardText = Cocoa_HasClipboardText;
 
+        device->GetClipboardData = Cocoa_GetClipboardData;
+        device->HasClipboardData = Cocoa_HasClipboardData;
+        device->SetClipboardData = Cocoa_SetClipboardData;
+
         device->free = Cocoa_DeleteDevice;
 
         device->quirk_flags = VIDEO_DEVICE_QUIRK_HAS_POPUP_WINDOW_SUPPORT;