From 6575b8157bec3de9db3c4098568eea6134c5598a Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 31 Dec 2024 13:02:31 -0800
Subject: [PATCH] Synchronize clipboard mime types with external clipboard
updates
Fixes https://github.com/libsdl-org/SDL/issues/8338
Fixes https://github.com/libsdl-org/SDL/issues/9587
---
src/events/SDL_clipboardevents.c | 6 ++
src/video/SDL_clipboard.c | 54 ++++++++------
src/video/SDL_clipboard_c.h | 5 +-
src/video/SDL_video.c | 2 +-
src/video/cocoa/SDL_cocoaclipboard.m | 105 +++++++++++++++++++++++++--
5 files changed, 140 insertions(+), 32 deletions(-)
diff --git a/src/events/SDL_clipboardevents.c b/src/events/SDL_clipboardevents.c
index 568d5244fccaf..a852c2cf9b765 100644
--- a/src/events/SDL_clipboardevents.c
+++ b/src/events/SDL_clipboardevents.c
@@ -28,6 +28,12 @@
void SDL_SendClipboardUpdate(bool owner, char **mime_types, size_t num_mime_types)
{
+ if (!owner) {
+ // Clear our internal clipboard contents when external clipboard is set
+ SDL_CancelClipboardData(0);
+ SDL_SaveClipboardMimeTypes((const char **)mime_types, num_mime_types);
+ }
+
if (SDL_EventEnabled(SDL_EVENT_CLIPBOARD_UPDATE)) {
SDL_Event event;
event.type = SDL_EVENT_CLIPBOARD_UPDATE;
diff --git a/src/video/SDL_clipboard.c b/src/video/SDL_clipboard.c
index a0a65c666c302..51550f44bf3bb 100644
--- a/src/video/SDL_clipboard.c
+++ b/src/video/SDL_clipboard.c
@@ -42,7 +42,7 @@ void SDL_CancelClipboardData(Uint32 sequence)
{
SDL_VideoDevice *_this = SDL_GetVideoDevice();
- if (sequence != _this->clipboard_sequence) {
+ if (sequence && sequence != _this->clipboard_sequence) {
// This clipboard data was already canceled
return;
}
@@ -58,10 +58,36 @@ void SDL_CancelClipboardData(Uint32 sequence)
_this->clipboard_userdata = NULL;
}
+bool SDL_SaveClipboardMimeTypes(const char **mime_types, size_t num_mime_types)
+{
+ SDL_VideoDevice *_this = SDL_GetVideoDevice();
+
+ SDL_FreeClipboardMimeTypes(_this);
+
+ if (mime_types && num_mime_types > 0) {
+ size_t num_allocated = 0;
+
+ _this->clipboard_mime_types = (char **)SDL_malloc(num_mime_types * sizeof(char *));
+ if (_this->clipboard_mime_types) {
+ for (size_t i = 0; i < num_mime_types; ++i) {
+ _this->clipboard_mime_types[i] = SDL_strdup(mime_types[i]);
+ if (_this->clipboard_mime_types[i]) {
+ ++num_allocated;
+ }
+ }
+ }
+ if (num_allocated < num_mime_types) {
+ SDL_FreeClipboardMimeTypes(_this);
+ return false;
+ }
+ _this->num_clipboard_mime_types = num_mime_types;
+ }
+ return true;
+}
+
bool SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardCleanupCallback cleanup, void *userdata, const char **mime_types, size_t num_mime_types)
{
SDL_VideoDevice *_this = SDL_GetVideoDevice();
- size_t i;
if (!_this) {
return SDL_UninitializedVideo();
@@ -78,7 +104,7 @@ bool SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardClean
return true;
}
- SDL_CancelClipboardData(_this->clipboard_sequence);
+ SDL_CancelClipboardData(0);
++_this->clipboard_sequence;
if (!_this->clipboard_sequence) {
@@ -88,23 +114,9 @@ bool SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardClean
_this->clipboard_cleanup = cleanup;
_this->clipboard_userdata = userdata;
- if (mime_types && num_mime_types > 0) {
- size_t num_allocated = 0;
-
- _this->clipboard_mime_types = (char **)SDL_malloc(num_mime_types * sizeof(char *));
- if (_this->clipboard_mime_types) {
- for (i = 0; i < num_mime_types; ++i) {
- _this->clipboard_mime_types[i] = SDL_strdup(mime_types[i]);
- if (_this->clipboard_mime_types[i]) {
- ++num_allocated;
- }
- }
- }
- if (num_allocated < num_mime_types) {
- SDL_ClearClipboardData();
- return false;
- }
- _this->num_clipboard_mime_types = num_mime_types;
+ if (!SDL_SaveClipboardMimeTypes(mime_types, num_mime_types)) {
+ SDL_ClearClipboardData();
+ return false;
}
if (_this->SetClipboardData) {
@@ -115,7 +127,7 @@ bool SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardClean
char *text = NULL;
size_t size;
- for (i = 0; i < num_mime_types; ++i) {
+ for (size_t 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);
diff --git a/src/video/SDL_clipboard_c.h b/src/video/SDL_clipboard_c.h
index 7cc670b968a2f..64a82fc68dc35 100644
--- a/src/video/SDL_clipboard_c.h
+++ b/src/video/SDL_clipboard_c.h
@@ -39,7 +39,8 @@ 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);
+bool SDL_SaveClipboardMimeTypes(const char **mime_types, size_t num_mime_types);
+void SDL_FreeClipboardMimeTypes(SDL_VideoDevice *_this);
+char **SDL_CopyClipboardMimeTypes(const char **clipboard_mime_types, size_t num_mime_types, bool temporary);
#endif // SDL_clipboard_c_h_
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index 57fdf689f92f8..73c7dbbe191fc 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -4264,7 +4264,7 @@ void SDL_VideoQuit(void)
SDL_free(_this->displays);
_this->displays = NULL;
- SDL_CancelClipboardData(_this->clipboard_sequence);
+ SDL_CancelClipboardData(0);
if (_this->primary_selection_text) {
SDL_free(_this->primary_selection_text);
diff --git a/src/video/cocoa/SDL_cocoaclipboard.m b/src/video/cocoa/SDL_cocoaclipboard.m
index 9b266fcb5e80c..2de4aa14e0767 100644
--- a/src/video/cocoa/SDL_cocoaclipboard.m
+++ b/src/video/cocoa/SDL_cocoaclipboard.m
@@ -23,8 +23,11 @@
#ifdef SDL_VIDEO_DRIVER_COCOA
#include "SDL_cocoavideo.h"
+#include "../../events/SDL_events_c.h"
#include "../../events/SDL_clipboardevents_c.h"
+#include <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
+
#if MAC_OS_X_VERSION_MAX_ALLOWED < 101300
typedef NSString *NSPasteboardType; // Defined in macOS 10.13+
#endif
@@ -72,6 +75,66 @@ - (void)pasteboard:(NSPasteboard *)pasteboard
@end
+static char **GetMimeTypes(int *pnformats)
+{
+ char **new_mime_types = NULL;
+
+ *pnformats = 0;
+
+ int nformats = 0;
+ int formatsSz = 0;
+ NSArray<NSPasteboardItem *> *items = [[NSPasteboard generalPasteboard] pasteboardItems];
+ NSUInteger nitems = [items count];
+ if (nitems > 0) {
+ for (NSPasteboardItem *item in items) {
+ NSArray<NSString *> *types = [item types];
+ for (NSString *type in types) {
+ if (@available(macOS 11.0, *)) {
+ UTType *uttype = [UTType typeWithIdentifier:type];
+ NSString *mime_type = [uttype preferredMIMEType];
+ if (mime_type) {
+ NSUInteger len = [mime_type lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
+ formatsSz += len;
+ ++nformats;
+ }
+ }
+ NSUInteger len = [type lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
+ formatsSz += len;
+ ++nformats;
+ }
+ }
+
+ new_mime_types = SDL_AllocateTemporaryMemory((nformats + 1) * sizeof(char *) + formatsSz);
+ if (new_mime_types) {
+ int i = 0;
+ char *strPtr = (char *)(new_mime_types + nformats + 1);
+ for (NSPasteboardItem *item in items) {
+ NSArray<NSString *> *types = [item types];
+ for (NSString *type in types) {
+ if (@available(macOS 11.0, *)) {
+ UTType *uttype = [UTType typeWithIdentifier:type];
+ NSString *mime_type = [uttype preferredMIMEType];
+ if (mime_type) {
+ NSUInteger len = [mime_type lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
+ SDL_memcpy(strPtr, [mime_type UTF8String], len);
+ new_mime_types[i++] = strPtr;
+ strPtr += len;
+ }
+ }
+ NSUInteger len = [type lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
+ SDL_memcpy(strPtr, [type UTF8String], len);
+ new_mime_types[i++] = strPtr;
+ strPtr += len;
+ }
+ }
+
+ new_mime_types[nformats] = NULL;
+ *pnformats = nformats;
+ }
+ }
+ return new_mime_types;
+}
+
void Cocoa_CheckClipboardUpdate(SDL_CocoaVideoData *data)
{
@@ -83,8 +146,11 @@ void Cocoa_CheckClipboardUpdate(SDL_CocoaVideoData *data)
count = [pasteboard changeCount];
if (count != data.clipboard_count) {
if (data.clipboard_count) {
- // TODO: compute mime types
- SDL_SendClipboardUpdate(false, NULL, 0);
+ int nformats = 0;
+ char **new_mime_types = GetMimeTypes(&nformats);
+ if (new_mime_types) {
+ SDL_SendClipboardUpdate(false, new_mime_types, nformats);
+ }
}
data.clipboard_count = count;
}
@@ -129,6 +195,33 @@ bool Cocoa_SetClipboardData(SDL_VideoDevice *_this)
return true;
}
+static bool IsMimeType(const char *tag)
+{
+ if (SDL_strchr(tag, '/')) {
+ // MIME types have slashes
+ return true;
+ } else if (SDL_strchr(tag, '.')) {
+ // UTI identifiers have periods
+ return false;
+ } else {
+ // Not sure, but it's not a UTI identifier
+ return true;
+ }
+}
+
+static CFStringRef GetUTIType(const char *tag)
+{
+ CFStringRef utiType;
+ if (IsMimeType(tag)) {
+ CFStringRef mimeType = CFStringCreateWithCString(NULL, tag, kCFStringEncodingUTF8);
+ utiType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL);
+ CFRelease(mimeType);
+ } else {
+ utiType = CFStringCreateWithCString(NULL, tag, kCFStringEncodingUTF8);
+ }
+ return utiType;
+}
+
void *Cocoa_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *size)
{
@autoreleasepool {
@@ -137,9 +230,7 @@ bool Cocoa_SetClipboardData(SDL_VideoDevice *_this)
*size = 0;
for (NSPasteboardItem *item in [pasteboard pasteboardItems]) {
NSData *itemData;
- CFStringRef mimeType = CFStringCreateWithCString(NULL, mime_type, kCFStringEncodingUTF8);
- CFStringRef utiType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL);
- CFRelease(mimeType);
+ CFStringRef utiType = GetUTIType(mime_type);
itemData = [item dataForType: (__bridge NSString *)utiType];
CFRelease(utiType);
if (itemData != nil) {
@@ -162,9 +253,7 @@ bool Cocoa_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type)
bool result = false;
@autoreleasepool {
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
- CFStringRef mimeType = CFStringCreateWithCString(NULL, mime_type, kCFStringEncodingUTF8);
- CFStringRef utiType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL);
- CFRelease(mimeType);
+ CFStringRef utiType = GetUTIType(mime_type);
if ([pasteboard canReadItemWithDataConformingToTypes: @[(__bridge NSString *)utiType]]) {
result = true;
}