From 7d6856535f0f1e118c414bf80ec533641c98a752 Mon Sep 17 00:00:00 2001 From: Sam Lantinga 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 | 106 +++++++++++++++++++++++++-- 5 files changed, 141 insertions(+), 32 deletions(-) diff --git a/src/events/SDL_clipboardevents.c b/src/events/SDL_clipboardevents.c index 568d5244fccaff..a852c2cf9b7654 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 a0a65c666c302f..51550f44bf3bb5 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 7cc670b968a2f6..64a82fc68dc353 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 57fdf689f92f84..73c7dbbe191fc1 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 9b266fcb5e80cc..213c77fccc393c 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 + #if MAC_OS_X_VERSION_MAX_ALLOWED < 101300 typedef NSString *NSPasteboardType; // Defined in macOS 10.13+ #endif @@ -72,6 +75,67 @@ - (void)pasteboard:(NSPasteboard *)pasteboard @end +static char **GetMimeTypes(int *pnformats) +{ + char **new_mime_types = NULL; + + *pnformats = 0; + + int nformats = 0; + int formatsSz = 0; + NSArray *items = [[NSPasteboard generalPasteboard] pasteboardItems]; + NSUInteger nitems = [items count]; + if (nitems > 0) { + for (NSPasteboardItem *item in items) { + NSArray *types = [item types]; + for (NSString *type in types) { + NSString *mime_type; + 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 *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 +147,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 +196,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 +231,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 +254,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; }