diff --git a/glfw/cocoa_platform.h b/glfw/cocoa_platform.h index 44847e72a..9d28eafa9 100644 --- a/glfw/cocoa_platform.h +++ b/glfw/cocoa_platform.h @@ -116,6 +116,13 @@ typedef void* (*PFN_TISGetInputSourceProperty)(TISInputSourceRef,CFStringRef); typedef UInt8 (*PFN_LMGetKbdType)(void); #define LMGetKbdType _glfw.ns.tis.GetKbdType +typedef struct _GLFWDropData { + const char **mimes; + size_t mimes_count; + id pasteboard; + id data_mapping; + id file_promise_mapping; +} _GLFWDropData; // Cocoa-specific per-window data // @@ -168,15 +175,12 @@ typedef struct _GLFWwindowNS bool delayed_cursor_update_requested; GLFWcocoarenderframefun resizeCallback; - // Current drag operation type for NSDraggingSource - int dragOperations; // Bitfield of GLFWDragOperationType - // Cached MIME types from drag enter (for move events) - const char** dragMimes; - int dragMimeCount; // Current count of MIME types (may be reduced by callback) - int dragMimeArraySize; // Original array size for proper cleanup + _GLFWDropData drop_data; // Pending drag source data requests (for cleanup on cancellation) + // Current drag operation type for NSDraggingSource + int dragOperations; // Bitfield of GLFWDragOperationType GLFWDragSourceData** pendingDragSourceData; int pendingDragSourceDataCount; int pendingDragSourceDataCapacity; diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index a067573f0..949e4c179 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -42,7 +42,26 @@ // Macro and forward declaration needed before draggingEntered: (uti_to_mime is defined in Clipboard section) #define UTI_ROUNDTRIP_PREFIX @"uti-is-typical-apple-nih." -static const char* uti_to_mime(NSString *uti); +static NSString* +mime_to_uti(const char *mime) { + if (strcmp(mime, "text/plain") == 0) return NSPasteboardTypeString; + if (@available(macOS 11.0, *)) { + UTType *t = [UTType typeWithMIMEType:@(mime)]; // auto-released + if (t != nil && !t.dynamic) return t.identifier; + } + return [NSString stringWithFormat:@"%@%s", UTI_ROUNDTRIP_PREFIX, mime]; // auto-released +} + +static const char* +uti_to_mime(NSString *uti) { + if ([uti isEqualToString:NSPasteboardTypeString]) return "text/plain"; + if ([uti hasPrefix:UTI_ROUNDTRIP_PREFIX]) return [[uti substringFromIndex:[UTI_ROUNDTRIP_PREFIX length]] UTF8String]; + if (@available(macOS 11.0, *)) { + UTType *t = [UTType typeWithIdentifier:uti]; // auto-released + if (t.preferredMIMEType != nil) return [t.preferredMIMEType UTF8String]; + } + return ""; +} static const char* polymorphic_string_as_utf8(id string) { @@ -1552,6 +1571,7 @@ is_modifier_pressed(NSUInteger flags, NSUInteger target_mask, NSUInteger other_m _glfwInputScroll(window, &ev); } +// Drop implementation for drag and drop {{{ // Return YES to receive periodic dragging updates even when the mouse hasn't moved. // This allows the application to update acceptance status asynchronously. - (BOOL)wantsPeriodicDraggingUpdates @@ -1559,31 +1579,33 @@ is_modifier_pressed(NSUInteger flags, NSUInteger target_mask, NSUInteger other_m return YES; } -// Helper function to free dragged MIME types array -static void freeDragMimes(_GLFWwindow* window) { - if (window->ns.dragMimes) { - // Free based on array size, not count (callback may have reduced count) - for (int i = 0; i < window->ns.dragMimeArraySize; i++) { - if (window->ns.dragMimes[i]) - free((char*)window->ns.dragMimes[i]); - } - free(window->ns.dragMimes); - window->ns.dragMimes = NULL; - window->ns.dragMimeCount = 0; - window->ns.dragMimeArraySize = 0; +static void +free_drop_data(_GLFWwindow *window) { + if (window->ns.drop_data.mimes) { + for (size_t i = 0; i < window->ns.drop_data.mimes_count; i++) free(window->ns.drop_data.mimes + i); } + if (window->ns.drop_data.pasteboard) [window->ns.drop_data.pasteboard release]; + if (window->ns.drop_data.data_mapping) [window->ns.drop_data.data_mapping release]; + if (window->ns.drop_data.file_promise_mapping) { + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *error = nil; + for (NSString *key in window->ns.drop_data.file_promise_mapping) { + NSArray *pair = [window->ns.drop_data.file_promise_mapping objectForKey:key]; + error = nil; if (pair[1] != [NSNull null]) [pair[1] closeAndReturnError:&error]; + error = nil; [fileManager removeItemAtURL:pair[0] error:&error]; + } + [window->ns.drop_data.file_promise_mapping release]; + } + memset(&window->ns.drop_data, 0, sizeof(_GLFWDropData)); } -// Helper to free entries that were filtered out by callback -static void freeFilteredDragMimes(_GLFWwindow* window, int old_count, int new_count) { - if (window->ns.dragMimes && new_count < old_count) { - for (int i = new_count; i < old_count; i++) { - if (window->ns.dragMimes[i]) { - free((char*)window->ns.dragMimes[i]); - window->ns.dragMimes[i] = NULL; - } - } +static void +update_drop_state(_GLFWwindow *window, size_t mime_count) { + _GLFWDropData *d = &window->ns.drop_data; + for (size_t i = mime_count; i < d->mimes_count; i++) { + if (d->mimes[i]) { free((void*)d->mimes[i]); d->mimes[i] = NULL; } } + d->mimes_count = mime_count; } - (NSDragOperation)draggingEntered:(id )sender @@ -1595,7 +1617,7 @@ static void freeFilteredDragMimes(_GLFWwindow* window, int old_count, int new_co float xscale = 1, yscale = 1; _glfwPlatformGetWindowContentScale(window, &xscale, &yscale); xpos *= xscale; ypos *= yscale; - + free_drop_data(window); // Get MIME types from the dragging pasteboard NSPasteboard* pasteboard = [sender draggingPasteboard]; @@ -1607,9 +1629,6 @@ static void freeFilteredDragMimes(_GLFWwindow* window, int old_count, int new_co NSArray *receivers = [pasteboard readObjectsForClasses:classes options:@{}]; for (NSFilePromiseReceiver *receiver in receivers) max_types += [receiver.fileTypes count]; - // Free any previously cached MIME types - freeDragMimes(window); - // Pre-allocate C array for MIME types const char** mime_array = (const char**)calloc(max_types, sizeof(const char*)); if (!mime_array) { @@ -1617,7 +1636,7 @@ static void freeFilteredDragMimes(_GLFWwindow* window, int old_count, int new_co return accepted ? NSDragOperationGeneric : NSDragOperationNone; } - int mime_count = 0; + size_t mime_count = 0; // Check for common types first (use _glfw_strdup since we need to own the strings) NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES}; @@ -1631,7 +1650,7 @@ static void freeFilteredDragMimes(_GLFWwindow* window, int old_count, int new_co const char* mime = uti_to_mime(uti); \ if (mime && mime[0]) { \ bool duplicate = false; \ - for (int i = 0; i < mime_count; i++) { \ + for (size_t i = 0; i < mime_count; i++) { \ if (strcmp(mime_array[i], mime) == 0) { \ duplicate = true; \ break; \ @@ -1654,28 +1673,17 @@ static void freeFilteredDragMimes(_GLFWwindow* window, int old_count, int new_co } } - // Store MIME types for later use in move events - window->ns.dragMimes = mime_array; - window->ns.dragMimeCount = mime_count; - window->ns.dragMimeArraySize = mime_count; - - // Call drag enter callback with writable MIME types array - int old_count = mime_count; - int accepted = _glfwInputDragEvent(window, GLFW_DRAG_ENTER, xpos, ypos, mime_array, &mime_count); - - // Free any entries that were filtered out by the callback - freeFilteredDragMimes(window, old_count, mime_count); - - // Update cached mime count with callback result - window->ns.dragMimeCount = mime_count; - - if (accepted) - return NSDragOperationGeneric; - return NSDragOperationNone; + window->ns.drop_data.mimes = mime_array; + window->ns.drop_data.mimes_count = mime_count; + bool from_self = ([sender draggingSource] != nil); + mime_count = _glfwInputDropEvent(window, GLFW_DROP_ENTER, xpos, ypos, mime_array, mime_count, from_self); + update_drop_state(window, mime_count); + return mime_count ? NSDragOperationGeneric :NSDragOperationNone; } - (NSDragOperation)draggingUpdated:(id )sender { + if (!window->ns.drop_data.mimes_count) return NSDragOperationNone; const NSRect contentRect = [window->ns.view frame]; const NSPoint pos = [sender draggingLocation]; double xpos = pos.x; @@ -1684,73 +1692,201 @@ static void freeFilteredDragMimes(_GLFWwindow* window, int old_count, int new_co _glfwPlatformGetWindowContentScale(window, &xscale, &yscale); xpos *= xscale; ypos *= yscale; - // Call drag move callback with cached MIME types - int old_count = window->ns.dragMimeCount; - int mime_count = old_count; - int accepted = _glfwInputDragEvent(window, GLFW_DRAG_MOVE, xpos, ypos, window->ns.dragMimes, &mime_count); - - // Free any entries that were filtered out by the callback - freeFilteredDragMimes(window, old_count, mime_count); - - // Update cached mime count with callback result - window->ns.dragMimeCount = mime_count; - - if (accepted) - return NSDragOperationGeneric; - return NSDragOperationNone; + bool from_self = ([sender draggingSource] != nil); + _GLFWDropData *d = &window->ns.drop_data; + size_t mime_count = _glfwInputDropEvent(window, GLFW_DROP_MOVE, xpos, ypos, d->mimes, d->mimes_count, from_self); + update_drop_state(window, mime_count); + return mime_count ? NSDragOperationGeneric :NSDragOperationNone; } - (void)draggingExited:(id )sender { - (void)sender; - // Call drag leave callback - _glfwInputDragEvent(window, GLFW_DRAG_LEAVE, 0, 0, NULL, NULL); - - // Free cached MIME types - freeDragMimes(window); + bool from_self = ([sender draggingSource] != nil); + _GLFWDropData *d = &window->ns.drop_data; + size_t mime_count = _glfwInputDropEvent(window, GLFW_DROP_LEAVE, 0, 0, d->mimes, d->mimes_count, from_self); + update_drop_state(window, mime_count); + free_drop_data(window); } - (BOOL)performDragOperation:(id )sender { + if (!window->ns.drop_data.mimes_count) return NO; const NSRect contentRect = [window->ns.view frame]; - // NOTE: The returned location uses base 0,1 not 0,0 const NSPoint pos = [sender draggingLocation]; - _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y); - - NSPasteboard* pasteboard = [sender draggingPasteboard]; - - // Heap-allocate drop data structure for chunked reading - // The application is responsible for freeing this via glfwFinishDrop - GLFWDropData* drop_data = calloc(1, sizeof(GLFWDropData)); - if (!drop_data) { - _glfwInputError(GLFW_OUT_OF_MEMORY, "Cocoa: Failed to allocate drop data"); - return NO; - } - // Transfer ownership of mimes array from window to drop object - drop_data->mime_types = window->ns.dragMimes; - drop_data->mime_count = window->ns.dragMimeCount; - drop_data->mime_array_size = window->ns.dragMimeArraySize; - // Clear window's references since drop object now owns the mimes - window->ns.dragMimes = NULL; - window->ns.dragMimeCount = 0; - window->ns.dragMimeArraySize = 0; - - drop_data->current_mime = NULL; - drop_data->read_fd = -1; - drop_data->bytes_read = 0; - drop_data->platform_data = [pasteboard retain]; // Retain the pasteboard - drop_data->eof_reached = false; - drop_data->current_data = NULL; - drop_data->data_offset = 0; - drop_data->data_is_file_promise = false; - // Check if the drop is from this application - // draggingSource returns the source object if the drag started in this application + double xpos = pos.x; + double ypos = contentRect.size.height - pos.y; + float xscale = 1, yscale = 1; + _glfwPlatformGetWindowContentScale(window, &xscale, &yscale); + xpos *= xscale; ypos *= yscale; bool from_self = ([sender draggingSource] != nil); - _glfwInputDrop(window, drop_data, from_self); - // Note: drop_data is NOT freed here - application must call glfwFinishDrop + _GLFWDropData *d = &window->ns.drop_data; + size_t mime_count = _glfwInputDropEvent(window, GLFW_DROP_DROP, xpos, ypos, d->mimes, d->mimes_count, from_self); + update_drop_state(window, mime_count); + window->ns.drop_data.pasteboard = [[sender draggingPasteboard] retain]; + for (size_t i = 0; i < window->ns.drop_data.mimes_count; i++) + _glfwPlatformRequestDropData(window, window->ns.drop_data.mimes[i]); return YES; } +void +_glfwPlatformRequestDropUpdate(_GLFWwindow* window UNUSED) { + // No-op since macOS is calling the drop move callback periodically anyway + // thanks to wantsPeriodicDraggingUpdates and we have no way to inform + // macOS of any changes except in the cocoa callbacks. +} + +static void +send_data_available_event_on_next_event_loop_tick(GLFWid wid, const char *mime) { + char *mt = _glfw_strdup(mime); + dispatch_async(dispatch_get_main_queue(), ^{ + _GLFWwindow *window = _glfwWindowForId(wid); + if (window) { + const char *mimes[1] = {mt}; + _glfwInputDropEvent(window, GLFW_DROP_DATA_AVAILABLE, 0, 0, mimes, 1, false); + } + free(mt); + }); +} + +int +_glfwPlatformRequestDropData(_GLFWwindow *window UNUSED, const char *mime) { + NSPasteboard* pasteboard = window->ns.drop_data.pasteboard; + if (!pasteboard) return EINVAL; + GLFWid wid = window->id; + if (window->ns.drop_data.data_mapping == nil) window->ns.drop_data.data_mapping = [[NSMutableDictionary alloc] init]; + NSArray *pair; + if ((pair = window->ns.drop_data.data_mapping[@(mime)])) { + window->ns.drop_data.data_mapping[@(mime)] = @[pair[0], @0]; + send_data_available_event_on_next_event_loop_tick(wid, mime); + return 0; + } + if (window->ns.drop_data.file_promise_mapping == nil) window->ns.drop_data.file_promise_mapping = [[NSMutableDictionary alloc] init]; + if ((pair = window->ns.drop_data.file_promise_mapping[@(mime)])) { + if (pair[0] == [NSNull null]) return 0; // waiting for promise + if (pair[1] != [NSNull null]) { + NSFileHandle *h = pair[1]; NSError *error = nil; + [h seekToOffset:0 error:&error]; + } + send_data_available_event_on_next_event_loop_tick(wid, mime); + return 0; + } + NSData* data = nil; NSFilePromiseReceiver *file_promise = nil; + // Handle special MIME types + if (strcmp(mime, "text/uri-list") == 0) { + NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES}; + NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]] options:options]; + if (urls && [urls count] > 0) { + NSMutableString *uri_list = [NSMutableString stringWithCapacity:4096]; + for (NSURL* url in urls) { + if ([uri_list length] > 0) [uri_list appendString:@"\n"]; + if (url.fileURL) [uri_list appendString:url.filePathURL.absoluteString]; + else [uri_list appendString:url.absoluteString]; + } + data = [uri_list dataUsingEncoding:NSUTF8StringEncoding]; + } + } else if (strcmp(mime, "text/plain") == 0 || strcmp(mime, "text/plain;charset=utf-8") == 0) { + NSArray* strings = [pasteboard readObjectsForClasses:@[[NSString class]] options:nil]; + if (strings && [strings count] > 0) { + NSString* str = strings[0]; + data = [str dataUsingEncoding:NSUTF8StringEncoding]; + } + } + if (data == nil) { + // Try to read data for other MIME types using UTI + NSString* uti = mime_to_uti(mime); + if (uti) { + NSPasteboardType pbType = [pasteboard availableTypeFromArray:@[uti]]; + if (pbType) data = [pasteboard dataForType:pbType]; + } + if (data == nil) { + // look in the file promise providers + NSArray *receivers = [pasteboard readObjectsForClasses:@[[NSFilePromiseReceiver class]] options:@{}]; + for (NSFilePromiseReceiver *receiver in receivers) { + for (NSString *uti in receiver.fileTypes) { + const char *q = uti_to_mime(uti); + if (q && strcmp(q, mime) == 0) { + file_promise = receiver; + break; + } + } + if (file_promise) break; + } + } + } + if (!data && !file_promise) return ENOENT; + if (file_promise != nil) { + window->ns.drop_data.file_promise_mapping[@(mime)] = @[[NSNull null], [NSNull null], [NSNull null]]; + char *mt = _glfw_strdup(mime); + [file_promise receivePromisedFilesAtDestination:[NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES] + options:@{} operationQueue:[NSOperationQueue mainQueue] reader:^(NSURL *fileURL, NSError *errorOrNil) { + _GLFWwindow *window = _glfwWindowForId(wid); + if (!window || !window->ns.drop_data.file_promise_mapping) return; + id null = [NSNull null]; + if (errorOrNil) { + NSLog(@"Error receiving file: %@: %@", fileURL, errorOrNil); + window->ns.drop_data.file_promise_mapping[@(mt)] = @[fileURL, null, errorOrNil]; + } else { + NSError *err = nil; + NSFileHandle *file_handle = [NSFileHandle fileHandleForReadingFromURL:fileURL error:&err]; + window->ns.drop_data.file_promise_mapping[@(mt)] = err ? @[fileURL, null, err] : @[fileURL, file_handle, null]; + } + const char *mimes[1] = {mt}; + _glfwInputDropEvent(window, GLFW_DROP_DATA_AVAILABLE, 0, 0, mimes, 1, false); + free(mt); + }]; + } else { + window->ns.drop_data.data_mapping[@(mime)] = @[data, @0]; + const char *mimes[1] = {mime}; + _glfwInputDropEvent(window, GLFW_DROP_DATA_AVAILABLE, 0, 0, mimes, 1, false); + } + return 0; +} + +ssize_t +_glfwPlatformReadAvailableDropData(GLFWwindow *w, GLFWDropEvent *ev, char *buffer, size_t capacity) { + _GLFWwindow *window = (_GLFWwindow*)w; const char *mime = ev->mimes[0]; + NSArray *pair; + if ((pair = window->ns.drop_data.data_mapping[@(mime)])) { + NSData *data = pair[0]; + size_t offset = [pair[1] unsignedIntegerValue]; + NSUInteger dataLength = [data length]; + if (offset >= dataLength) return 0; // EOF + NSUInteger remaining = dataLength - offset; + NSUInteger to_read = (remaining < capacity) ? remaining : capacity; + [data getBytes:buffer range:NSMakeRange(offset, to_read)]; + offset += to_read; + window->ns.drop_data.data_mapping[@(mime)] = @[data, @(offset)]; + if (to_read) send_data_available_event_on_next_event_loop_tick(window->id, mime); + return (ssize_t)to_read; + } + if ((pair = window->ns.drop_data.file_promise_mapping[@(mime)])) { + id null = [NSNull null]; + if (pair[0] == null) { return -ENOENT; } + if (pair[2] != null) { + NSError *err = pair[2]; + if ([err.domain isEqualToString:NSPOSIXErrorDomain]) return -err.code; + NSError *underlyingError = err.userInfo[NSUnderlyingErrorKey]; + if (underlyingError && [underlyingError.domain isEqualToString:NSPOSIXErrorDomain]) return -underlyingError.code; + return -EIO; + } + NSFileHandle *h = pair[1]; + int fd = h.fileDescriptor; + ssize_t bytesRead; do { + bytesRead = read(fd, buffer, capacity); + } while (bytesRead == -1 && errno == EINTR); + bytesRead = bytesRead < 0 ? -errno : bytesRead; + if (bytesRead > 0) send_data_available_event_on_next_event_loop_tick(window->id, mime); + return bytesRead; + } + return -ENOENT; +} + +void +_glfwPlatformEndDrop(GLFWwindow *w UNUSED, GLFWDragOperationType op UNUSED) { + free_drop_data((_GLFWwindow*)w); +} +// }}} + // NSDraggingSource protocol methods - (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context @@ -3370,27 +3506,6 @@ bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) { // Clipboard {{{ -static NSString* -mime_to_uti(const char *mime) { - if (strcmp(mime, "text/plain") == 0) return NSPasteboardTypeString; - if (@available(macOS 11.0, *)) { - UTType *t = [UTType typeWithMIMEType:@(mime)]; // auto-released - if (t != nil && !t.dynamic) return t.identifier; - } - return [NSString stringWithFormat:@"%@%s", UTI_ROUNDTRIP_PREFIX, mime]; // auto-released -} - -static const char* -uti_to_mime(NSString *uti) { - if ([uti isEqualToString:NSPasteboardTypeString]) return "text/plain"; - if ([uti hasPrefix:UTI_ROUNDTRIP_PREFIX]) return [[uti substringFromIndex:[UTI_ROUNDTRIP_PREFIX length]] UTF8String]; - if (@available(macOS 11.0, *)) { - UTType *t = [UTType typeWithIdentifier:uti]; // auto-released - if (t.preferredMIMEType != nil) return [t.preferredMIMEType UTF8String]; - } - return ""; -} - static void list_clipboard_mimetypes(GLFWclipboardwritedatafun write_data, void *object) { #define w(x) { if (ok) ok = write_data(object, x, strlen(x)); } @@ -4204,181 +4319,4 @@ _glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* data, siz return (ssize_t)size; } -void _glfwPlatformUpdateDragState(_GLFWwindow* window) { - // Call the drag callback with STATUS_UPDATE to get updated acceptance and MIME list - // Position values are not valid for this event type - if (window->ns.dragMimes) { - int old_count = window->ns.dragMimeCount; - int mime_count = old_count; - _glfwInputDragEvent(window, GLFW_DRAG_STATUS_UPDATE, 0, 0, window->ns.dragMimes, &mime_count); - // Free any entries that were filtered out by the callback - freeFilteredDragMimes(window, old_count, mime_count); - window->ns.dragMimeCount = mime_count; - } -} - -const char** -_glfwPlatformGetDropMimeTypes(GLFWDropData* drop, int* count) { - if (!drop || !count) return NULL; - *count = drop->mime_count; - return drop->mime_types; -} - -ssize_t -_glfwPlatformReadDropData(GLFWDropData* drop, const char* mime, void* buffer, size_t capacity, monotonic_t timeout UNUSED) { - NSPasteboard* pasteboard = (NSPasteboard*)drop->platform_data; - if (!pasteboard) return -EINVAL; - - // Check if the MIME type is available - bool mime_found = false; - for (int i = 0; i < drop->mime_count; i++) { - if (drop->mime_types[i] && strcmp(drop->mime_types[i], mime) == 0) { - mime_found = true; - break; - } - } - if (!mime_found) return -ENOENT; - - // If switching MIME types, release previous data - if (drop->current_mime && strcmp(drop->current_mime, mime) != 0) { - if (drop->current_data) { - [(NSObject*)drop->current_data release]; - drop->current_data = NULL; - } - drop->data_offset = 0; - drop->data_is_file_promise = false; - free(drop->current_mime); drop->current_mime = NULL; - } - - // If we need to fetch data for this MIME type - if (drop->current_data == NULL || drop->current_mime == NULL) { - NSData* data = nil; NSFilePromiseReceiver *file_promise = nil; - drop->data_is_file_promise = false; - // Handle special MIME types - if (strcmp(mime, "text/uri-list") == 0) { - NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES}; - NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]] options:options]; - if (urls && [urls count] > 0) { - NSMutableString *uri_list = [NSMutableString stringWithCapacity:4096]; - for (NSURL* url in urls) { - if ([uri_list length] > 0) [uri_list appendString:@"\n"]; - if (url.fileURL) [uri_list appendString:url.filePathURL.absoluteString]; - else [uri_list appendString:url.absoluteString]; - } - data = [uri_list dataUsingEncoding:NSUTF8StringEncoding]; - } - } else if (strcmp(mime, "text/plain") == 0 || strcmp(mime, "text/plain;charset=utf-8") == 0) { - NSArray* strings = [pasteboard readObjectsForClasses:@[[NSString class]] options:nil]; - if (strings && [strings count] > 0) { - NSString* str = strings[0]; - data = [str dataUsingEncoding:NSUTF8StringEncoding]; - } - } - if (data == nil) { - // Try to read data for other MIME types using UTI - NSString* uti = mime_to_uti(mime); - if (uti) { - NSPasteboardType pbType = [pasteboard availableTypeFromArray:@[uti]]; - if (pbType) data = [pasteboard dataForType:pbType]; - } - if (data == nil) { - // look in the file promise providers - NSArray *receivers = [pasteboard readObjectsForClasses:@[[NSFilePromiseReceiver class]] options:@{}]; - for (NSFilePromiseReceiver *receiver in receivers) { - for (NSString *uti in receiver.fileTypes) { - const char *q = uti_to_mime(uti); - if (q && strcmp(q, mime) == 0) { - file_promise = receiver; - break; - } - } - if (file_promise) break; - } - } - } - if (!data && !file_promise) return -ENOENT; - drop->current_mime = _glfw_strdup(mime); - drop->data_offset = 0; - if (file_promise != nil) { - drop->data_is_file_promise = true; - [file_promise receivePromisedFilesAtDestination:[NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES] - options:@{} operationQueue:[NSOperationQueue mainQueue] reader:^(NSURL *fileURL, NSError *errorOrNil) { - if (errorOrNil) { - NSLog(@"Error receiving file: %@: %@", fileURL, errorOrNil); - drop->file_io_error = [errorOrNil retain]; - } else { - NSError *err = nil; - drop->file_handle = [NSFileHandle fileHandleForReadingFromURL:fileURL error:&err]; - if (err) drop->file_io_error = [err retain]; - } - }]; - } else { - drop->current_data = [data retain]; - drop->data_is_file_promise = false; - } - } - - // Read data from buffer - if (drop->data_is_file_promise) { - if (drop->file_io_error == nil && drop->file_handle == nil) return -EAGAIN; - if (drop->file_io_error) { - NSError *err = drop->file_io_error; - if ([err.domain isEqualToString:NSPOSIXErrorDomain]) return -err.code; - NSError *underlyingError = err.userInfo[NSUnderlyingErrorKey]; - if (underlyingError && [underlyingError.domain isEqualToString:NSPOSIXErrorDomain]) return -underlyingError.code; - return -EIO; - } - int fd = ((NSFileHandle*)drop->file_handle).fileDescriptor; - ssize_t bytesRead; do { - bytesRead = read(fd, buffer, capacity); - } while (bytesRead == -1 && errno == EINTR); - if (bytesRead < 0) return -errno; - return bytesRead; - } else { - NSData* data = (NSData*)drop->current_data; - NSUInteger dataLength = [data length]; - if (drop->data_offset >= dataLength) return 0; // EOF - NSUInteger remaining = dataLength - drop->data_offset; - NSUInteger to_read = (remaining < capacity) ? remaining : capacity; - [data getBytes:buffer range:NSMakeRange(drop->data_offset, to_read)]; - drop->data_offset += to_read; - return (ssize_t)to_read; - } -} - -void -_glfwPlatformFinishDrop(GLFWDropData* drop, GLFWDragOperationType operation UNUSED, bool success UNUSED) { - if (!drop) return; - free(drop->current_mime); drop->current_mime = NULL; - if (drop->file_io_error) [(NSError*)drop->file_io_error release]; - drop->file_io_error = NULL; - if (drop->file_handle) [(NSFileHandle*)drop->file_handle closeFile]; - drop->file_handle = NULL; - // Release the retained current data - if (drop->current_data) { - [(NSData*)drop->current_data release]; - drop->current_data = NULL; - } - - // Release the retained pasteboard - // Note: Cocoa drag operations don't have a way to report back to the source - // like X11's XdndFinished, so we just clean up our resources - if (drop->platform_data) { - [(NSPasteboard*)drop->platform_data release]; - drop->platform_data = NULL; - } - - // Free the mime types array (owned by drop object) - if (drop->mime_types) { - for (int i = 0; i < drop->mime_array_size; i++) { - if (drop->mime_types[i]) - free((char*)drop->mime_types[i]); - } - free(drop->mime_types); - drop->mime_types = NULL; - } - - // Free the heap-allocated drop data structure - free(drop); -}