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
This commit is contained in:
Sam Lantinga
2024-12-31 13:02:31 -08:00
parent 30a22d3fed
commit 6575b8157b
5 changed files with 140 additions and 32 deletions

View File

@@ -28,6 +28,12 @@
void SDL_SendClipboardUpdate(bool owner, char **mime_types, size_t num_mime_types) 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)) { if (SDL_EventEnabled(SDL_EVENT_CLIPBOARD_UPDATE)) {
SDL_Event event; SDL_Event event;
event.type = SDL_EVENT_CLIPBOARD_UPDATE; event.type = SDL_EVENT_CLIPBOARD_UPDATE;

View File

@@ -42,7 +42,7 @@ void SDL_CancelClipboardData(Uint32 sequence)
{ {
SDL_VideoDevice *_this = SDL_GetVideoDevice(); SDL_VideoDevice *_this = SDL_GetVideoDevice();
if (sequence != _this->clipboard_sequence) { if (sequence && sequence != _this->clipboard_sequence) {
// This clipboard data was already canceled // This clipboard data was already canceled
return; return;
} }
@@ -58,10 +58,36 @@ void SDL_CancelClipboardData(Uint32 sequence)
_this->clipboard_userdata = NULL; _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) 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(); SDL_VideoDevice *_this = SDL_GetVideoDevice();
size_t i;
if (!_this) { if (!_this) {
return SDL_UninitializedVideo(); return SDL_UninitializedVideo();
@@ -78,7 +104,7 @@ bool SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardClean
return true; return true;
} }
SDL_CancelClipboardData(_this->clipboard_sequence); SDL_CancelClipboardData(0);
++_this->clipboard_sequence; ++_this->clipboard_sequence;
if (!_this->clipboard_sequence) { if (!_this->clipboard_sequence) {
@@ -88,24 +114,10 @@ bool SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardClean
_this->clipboard_cleanup = cleanup; _this->clipboard_cleanup = cleanup;
_this->clipboard_userdata = userdata; _this->clipboard_userdata = userdata;
if (mime_types && num_mime_types > 0) { if (!SDL_SaveClipboardMimeTypes(mime_types, num_mime_types)) {
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(); SDL_ClearClipboardData();
return false; return false;
} }
_this->num_clipboard_mime_types = num_mime_types;
}
if (_this->SetClipboardData) { if (_this->SetClipboardData) {
if (!_this->SetClipboardData(_this)) { if (!_this->SetClipboardData(_this)) {
@@ -115,7 +127,7 @@ bool SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardClean
char *text = NULL; char *text = NULL;
size_t size; 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]; const char *mime_type = _this->clipboard_mime_types[i];
if (SDL_IsTextMimeType(mime_type)) { if (SDL_IsTextMimeType(mime_type)) {
const void *data = _this->clipboard_callback(_this->clipboard_userdata, mime_type, &size); const void *data = _this->clipboard_callback(_this->clipboard_userdata, mime_type, &size);

View File

@@ -39,7 +39,8 @@ extern bool SDL_HasInternalClipboardData(SDL_VideoDevice *_this, const char *mim
// General purpose clipboard text callback // General purpose clipboard text callback
const void * SDLCALL SDL_ClipboardTextCallback(void *userdata, const char *mime_type, size_t *size); const void * SDLCALL SDL_ClipboardTextCallback(void *userdata, const char *mime_type, size_t *size);
void SDLCALL SDL_FreeClipboardMimeTypes(SDL_VideoDevice *_this); bool SDL_SaveClipboardMimeTypes(const char **mime_types, size_t num_mime_types);
char ** SDLCALL SDL_CopyClipboardMimeTypes(const char **clipboard_mime_types, size_t num_mime_types, bool temporary); 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_ #endif // SDL_clipboard_c_h_

View File

@@ -4264,7 +4264,7 @@ void SDL_VideoQuit(void)
SDL_free(_this->displays); SDL_free(_this->displays);
_this->displays = NULL; _this->displays = NULL;
SDL_CancelClipboardData(_this->clipboard_sequence); SDL_CancelClipboardData(0);
if (_this->primary_selection_text) { if (_this->primary_selection_text) {
SDL_free(_this->primary_selection_text); SDL_free(_this->primary_selection_text);

View File

@@ -23,8 +23,11 @@
#ifdef SDL_VIDEO_DRIVER_COCOA #ifdef SDL_VIDEO_DRIVER_COCOA
#include "SDL_cocoavideo.h" #include "SDL_cocoavideo.h"
#include "../../events/SDL_events_c.h"
#include "../../events/SDL_clipboardevents_c.h" #include "../../events/SDL_clipboardevents_c.h"
#include <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#if MAC_OS_X_VERSION_MAX_ALLOWED < 101300 #if MAC_OS_X_VERSION_MAX_ALLOWED < 101300
typedef NSString *NSPasteboardType; // Defined in macOS 10.13+ typedef NSString *NSPasteboardType; // Defined in macOS 10.13+
#endif #endif
@@ -72,6 +75,66 @@ provideDataForType:(NSPasteboardType)type
@end @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) void Cocoa_CheckClipboardUpdate(SDL_CocoaVideoData *data)
{ {
@@ -83,8 +146,11 @@ void Cocoa_CheckClipboardUpdate(SDL_CocoaVideoData *data)
count = [pasteboard changeCount]; count = [pasteboard changeCount];
if (count != data.clipboard_count) { if (count != data.clipboard_count) {
if (data.clipboard_count) { if (data.clipboard_count) {
// TODO: compute mime types int nformats = 0;
SDL_SendClipboardUpdate(false, NULL, 0); char **new_mime_types = GetMimeTypes(&nformats);
if (new_mime_types) {
SDL_SendClipboardUpdate(false, new_mime_types, nformats);
}
} }
data.clipboard_count = count; data.clipboard_count = count;
} }
@@ -129,6 +195,33 @@ bool Cocoa_SetClipboardData(SDL_VideoDevice *_this)
return true; 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) void *Cocoa_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *size)
{ {
@autoreleasepool { @autoreleasepool {
@@ -137,9 +230,7 @@ void *Cocoa_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size
*size = 0; *size = 0;
for (NSPasteboardItem *item in [pasteboard pasteboardItems]) { for (NSPasteboardItem *item in [pasteboard pasteboardItems]) {
NSData *itemData; NSData *itemData;
CFStringRef mimeType = CFStringCreateWithCString(NULL, mime_type, kCFStringEncodingUTF8); CFStringRef utiType = GetUTIType(mime_type);
CFStringRef utiType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL);
CFRelease(mimeType);
itemData = [item dataForType: (__bridge NSString *)utiType]; itemData = [item dataForType: (__bridge NSString *)utiType];
CFRelease(utiType); CFRelease(utiType);
if (itemData != nil) { if (itemData != nil) {
@@ -162,9 +253,7 @@ bool Cocoa_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type)
bool result = false; bool result = false;
@autoreleasepool { @autoreleasepool {
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
CFStringRef mimeType = CFStringCreateWithCString(NULL, mime_type, kCFStringEncodingUTF8); CFStringRef utiType = GetUTIType(mime_type);
CFStringRef utiType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL);
CFRelease(mimeType);
if ([pasteboard canReadItemWithDataConformingToTypes: @[(__bridge NSString *)utiType]]) { if ([pasteboard canReadItemWithDataConformingToTypes: @[(__bridge NSString *)utiType]]) {
result = true; result = true;
} }