aboutsummaryrefslogtreecommitdiffstats
path: root/src/mac.m
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2022-05-22 12:24:59 -0400
committerDavid Robillard <d@drobilla.net>2022-05-23 16:50:43 -0400
commit2501218801437ea413091007b535d7c097801713 (patch)
treecf938dd335f8aa9b547b458f97f05e7e18d8b9d3 /src/mac.m
parent0093196a4c624da6d7f78a909a442f2e784c37aa (diff)
downloadpugl-2501218801437ea413091007b535d7c097801713.tar.gz
pugl-2501218801437ea413091007b535d7c097801713.tar.bz2
pugl-2501218801437ea413091007b535d7c097801713.zip
Add rich clipboard support
This implements a more powerful protocol for working with clipboards, which supports datatype negotiation, and fixes various issues by mapping more directly to how things work on X11.
Diffstat (limited to 'src/mac.m')
-rw-r--r--src/mac.m196
1 files changed, 172 insertions, 24 deletions
diff --git a/src/mac.m b/src/mac.m
index 9cd3e1c..2a54134 100644
--- a/src/mac.m
+++ b/src/mac.m
@@ -1,4 +1,4 @@
-// Copyright 2012-2020 David Robillard <d@drobilla.net>
+// Copyright 2012-2022 David Robillard <d@drobilla.net>
// Copyright 2017 Hanspeter Portner <dev@open-music-kontrollers.ch>
// SPDX-License-Identifier: ISC
@@ -25,6 +25,70 @@ typedef NSUInteger NSEventSubtype;
typedef NSUInteger NSWindowStyleMask;
#endif
+typedef struct {
+ const char* uti;
+ const char* mimeType;
+} Datatype;
+
+#define NUM_DATATYPES 16
+
+static const Datatype datatypes[NUM_DATATYPES + 1] = {
+ {"com.apple.pasteboard.promised-file-url", "text/uri-list"},
+ {"org.7-zip.7-zip-archive", "application/x-7z-compressed"},
+ {"org.gnu.gnu-zip-tar-archive", "application/tar+gzip"},
+ {"public.7z-archive", "application/x-7z-compressed"},
+ {"public.cpio-archive", "application/x-cpio"},
+ {"public.deb-archive", "application/vnd.debian.binary-package"},
+ {"public.file-url", "text/uri-list"},
+ {"public.html", "text/html"},
+ {"public.png", "image/png"},
+ {"public.rar-archive", "application/x-rar-compressed"},
+ {"public.rpm-archive", "application/x-rpm"},
+ {"public.rtf", "text/rtf"},
+ {"public.url", "text/uri-list"},
+ {"public.utf8-plain-text", "text/plain"},
+ {"public.utf8-tab-separated-values-text", "text/tab-separated-values"},
+ {"public.xz-archive", "application/x-xz"},
+ {NULL, NULL},
+};
+
+static NSString*
+mimeTypeForUti(const NSString* const uti)
+{
+ const char* const utiString = [uti UTF8String];
+
+ // First try internal map to override types the system won't convert sensibly
+ for (const Datatype* datatype = datatypes; datatype->uti; ++datatype) {
+ if (!strcmp(utiString, datatype->uti)) {
+ return [NSString stringWithUTF8String:datatype->mimeType];
+ }
+ }
+
+ // Try to get the MIME type from the system
+ return (NSString*)CFBridgingRelease(UTTypeCopyPreferredTagWithClass(
+ (__bridge CFStringRef)uti, kUTTagClassMIMEType));
+}
+
+static NSString*
+utiForMimeType(const NSString* const mimeType)
+{
+ const char* const mimeTypeString = [mimeType UTF8String];
+
+ // First try internal map to override types the system won't convert sensibly
+ for (const Datatype* datatype = datatypes; datatype->mimeType; ++datatype) {
+ if (!strcmp(mimeTypeString, datatype->mimeType)) {
+ return [NSString stringWithUTF8String:datatype->uti];
+ }
+ }
+
+ // Try to get the UTI from the system
+ CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(
+ kUTTagClassMIMEType, (__bridge CFStringRef)mimeType, NULL);
+
+ return (uti && UTTypeIsDynamic(uti)) ? (NSString*)CFBridgingRelease(uti)
+ : NULL;
+}
+
static NSRect
rectToScreen(NSScreen* screen, NSRect rect)
{
@@ -179,6 +243,10 @@ updateViewRect(PuglView* view)
NSTrackingArea* trackingArea;
NSMutableAttributedString* markedText;
NSMutableDictionary* userTimers;
+ id<NSDraggingInfo> dragSource;
+ NSDragOperation dragOperation;
+ size_t dragTypeIndex;
+ NSString* droppedUriList;
bool reshaped;
}
@@ -846,6 +914,8 @@ puglInitWorldInternals(PuglWorldType type, PuglWorldFlags PUGL_UNUSED(flags))
if (type == PUGL_PROGRAM) {
impl->autoreleasePool = [NSAutoreleasePool new];
+
+ [impl->app setActivationPolicy:NSApplicationActivationPolicyRegular];
}
return impl;
@@ -868,7 +938,7 @@ puglGetNativeWorld(PuglWorld* world)
}
PuglInternals*
-puglInitViewInternals(void)
+puglInitViewInternals(PuglWorld* PUGL_UNUSED(world))
{
PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals));
@@ -1493,21 +1563,106 @@ puglSetTransientParent(PuglView* view, PuglNativeView parent)
return PUGL_FAILURE;
}
+PuglStatus
+puglPaste(PuglView* const view)
+{
+ const PuglDataOfferEvent offer = {
+ PUGL_DATA_OFFER,
+ 0,
+ mach_absolute_time() / 1e9,
+ };
+
+ PuglEvent offerEvent;
+ offerEvent.offer = offer;
+ puglDispatchEvent(view, &offerEvent);
+ return PUGL_SUCCESS;
+}
+
+uint32_t
+puglGetNumClipboardTypes(const PuglView* PUGL_UNUSED(view))
+{
+ NSPasteboard* const pasteboard = [NSPasteboard generalPasteboard];
+
+ return pasteboard ? (uint32_t)[[pasteboard types] count] : 0;
+}
+
+const char*
+puglGetClipboardType(const PuglView* PUGL_UNUSED(view),
+ const uint32_t typeIndex)
+{
+ NSPasteboard* const pasteboard = [NSPasteboard generalPasteboard];
+ if (!pasteboard) {
+ return NULL;
+ }
+
+ const NSArray<NSPasteboardType>* const types = [pasteboard types];
+ if (typeIndex >= [types count]) {
+ return NULL;
+ }
+
+ NSString* const uti = [types objectAtIndex:typeIndex];
+ NSString* const mimeType = mimeTypeForUti(uti);
+
+ // FIXME: lifetime?
+ return mimeType ? [mimeType UTF8String] : [uti UTF8String];
+}
+
+PuglStatus
+puglAcceptOffer(PuglView* const view,
+ const PuglDataOfferEvent* const PUGL_UNUSED(offer),
+ const uint32_t typeIndex)
+{
+ PuglWrapperView* const wrapper = view->impl->wrapperView;
+ NSPasteboard* const pasteboard = [NSPasteboard generalPasteboard];
+ if (!pasteboard) {
+ return PUGL_BAD_PARAMETER;
+ }
+
+ const NSArray<NSPasteboardType>* const types = [pasteboard types];
+ if (typeIndex >= [types count]) {
+ return PUGL_BAD_PARAMETER;
+ }
+
+ wrapper->dragOperation = NSDragOperationCopy;
+ wrapper->dragTypeIndex = typeIndex;
+
+ const PuglDataEvent data = {
+ PUGL_DATA, 0u, mach_absolute_time() / 1e9, (uint32_t)typeIndex};
+
+ PuglEvent dataEvent;
+ dataEvent.data = data;
+ puglDispatchEvent(view, &dataEvent);
+ return PUGL_SUCCESS;
+}
+
const void*
-puglGetClipboard(PuglView* const view,
- const char** const type,
- size_t* const len)
+puglGetClipboard(PuglView* const view,
+ const uint32_t typeIndex,
+ size_t* const len)
{
+ *len = 0;
+
NSPasteboard* const pasteboard = [NSPasteboard generalPasteboard];
+ if (!pasteboard) {
+ return NULL;
+ }
- if ([[pasteboard types] containsObject:NSStringPboardType]) {
- const NSString* str = [pasteboard stringForType:NSStringPboardType];
- const char* utf8 = [str UTF8String];
+ const NSArray<NSPasteboardType>* const types = [pasteboard types];
+ if (typeIndex >= [types count]) {
+ return NULL;
+ }
- puglSetBlob(&view->clipboard, utf8, strlen(utf8) + 1);
+ NSString* const uti = [types objectAtIndex:typeIndex];
+ if ([uti isEqualToString:@"public.file-url"] ||
+ [uti isEqualToString:@"com.apple.pasteboard.promised-file-url"]) {
+ *len = [view->impl->wrapperView->droppedUriList length];
+ return [view->impl->wrapperView->droppedUriList UTF8String];
}
- return puglGetInternalClipboard(view, type, len);
+ const NSData* const data = [pasteboard dataForType:uti];
+
+ *len = [data length];
+ return [data bytes];
}
static NSCursor*
@@ -1552,28 +1707,21 @@ puglSetCursor(PuglView* view, PuglCursor cursor)
}
PuglStatus
-puglSetClipboard(PuglView* const view,
+puglSetClipboard(PuglView* PUGL_UNUSED(view),
const char* const type,
const void* const data,
const size_t len)
{
NSPasteboard* const pasteboard = [NSPasteboard generalPasteboard];
- const char* const str = (const char*)data;
-
- PuglStatus st = puglSetInternalClipboard(view, type, data, len);
- if (st) {
- return st;
- }
+ NSString* const mimeType = [NSString stringWithUTF8String:type];
+ NSString* const uti = utiForMimeType(mimeType);
+ NSData* const blob = [NSData dataWithBytes:data length:len];
- NSString* nsString = [NSString stringWithUTF8String:str];
- if (nsString) {
- [pasteboard declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil]
- owner:nil];
-
- [pasteboard setString:nsString forType:NSStringPboardType];
+ [pasteboard declareTypes:[NSArray arrayWithObjects:uti, nil] owner:nil];
+ if ([pasteboard setData:blob forType:uti]) {
return PUGL_SUCCESS;
}
- return PUGL_UNKNOWN_ERROR;
+ return PUGL_FAILURE;
}