diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index b01fec581..a067573f0 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -763,7 +763,6 @@ - (void)doCommandBySelector:(SEL)selector // Structure to hold async drag state typedef struct { - NSURL* destinationURL; // URL to write to void (^completionHandler)(NSError*); // Completion block to call NSFileHandle* fileHandle; // File handle for writing bool finished; // Whether writing is complete @@ -792,10 +791,6 @@ - (void)doCommandBySelector:(SEL)selector [state->fileHandle release]; state->fileHandle = nil; } - if (state->destinationURL) { - [state->destinationURL release]; - state->destinationURL = nil; - } free(state); } free(data->mime_type); @@ -930,38 +925,27 @@ - (void)filePromiseProvider:(NSFilePromiseProvider*)filePromiseProvider } // Create the file promise state + char *mt = _glfw_strdup(mimeType); GLFWFilePromiseState* state = calloc(1, sizeof(GLFWFilePromiseState)); - if (!state) { - free(source_data); + if (!state || !mt) { + free(source_data); free(mt); free(state); [fileHandle closeFile]; completionHandler([NSError errorWithDomain:NSPOSIXErrorDomain code:ENOMEM userInfo:nil]); return; } - state->destinationURL = [url retain]; state->completionHandler = [completionHandler copy]; state->fileHandle = [fileHandle retain]; state->finished = false; state->errorCode = 0; source_data->window_id = windowId; - source_data->mime_type = _glfw_strdup(mimeType); + source_data->mime_type = mt; source_data->write_fd = -1; source_data->finished = false; source_data->error_code = 0; source_data->platform_data = state; - if (!source_data->mime_type) { - [state->fileHandle closeFile]; - [state->fileHandle release]; - [state->destinationURL release]; - Block_release(state->completionHandler); - free(state); - free(source_data); - completionHandler([NSError errorWithDomain:NSPOSIXErrorDomain code:ENOMEM userInfo:nil]); - return; - } - // Track this source data for cleanup on cancellation if (!add_ns_pending_drag_source_data(window, source_data)) { // Call completion handler with memory error before cleanup @@ -1619,7 +1603,7 @@ - (NSDragOperation)draggingEntered:(id )sender // Count total types across all pasteboard items plus 2 for uri-list and text/plain size_t max_types = 2; for (NSPasteboardItem* item in pasteboard.pasteboardItems) max_types += [item.types count]; - NSArray *classes = [NSArray arrayWithObject:[NSFilePromiseReceiver class]]; + NSArray *classes = @[[NSFilePromiseReceiver class]]; NSArray *receivers = [pasteboard readObjectsForClasses:classes options:@{}]; for (NSFilePromiseReceiver *receiver in receivers) max_types += [receiver.fileTypes count]; @@ -1758,6 +1742,7 @@ - (BOOL)performDragOperation:(id )sender 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 bool from_self = ([sender draggingSource] != nil); @@ -1789,11 +1774,12 @@ - (void)draggingSession:(NSDraggingSession *)session { (void)session; (void)screenPoint; - (void)operation; - // Clean up all pending drag source data - cleanup_all_ns_pending_drag_source_data(window); - // Notify the application that the drag source is closed - _glfwInputDragSourceRequest(window, NULL, NULL); + if (operation == NSDragOperationNone) { // drag was canceled + // Clean up all pending drag source data + cleanup_all_ns_pending_drag_source_data(window); + // Notify the application that the drag source is closed + _glfwInputDragSourceRequest(window, NULL, NULL); + } } - (BOOL)hasMarkedText @@ -4113,7 +4099,8 @@ int _glfwPlatformStartDrag(_GLFWwindow* window, } } -ssize_t _glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* data, size_t size) { +ssize_t +_glfwPlatformSendDragData(GLFWDragSourceData* source_data, const void* data, size_t size) { if (!source_data || source_data->finished) return -EINVAL; if (!source_data->platform_data) return -EINVAL; @@ -4255,16 +4242,18 @@ void _glfwPlatformUpdateDragState(_GLFWwindow* window) { // If switching MIME types, release previous data if (drop->current_mime && strcmp(drop->current_mime, mime) != 0) { if (drop->current_data) { - [(NSData*)drop->current_data release]; + [(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; + 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}; @@ -4284,39 +4273,87 @@ void _glfwPlatformUpdateDragState(_GLFWwindow* window) { NSString* str = strings[0]; data = [str dataUsingEncoding:NSUTF8StringEncoding]; } - } else { + } + 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 (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) return -ENOENT; - drop->current_data = [data retain]; + 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 - 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; + 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]; diff --git a/glfw/internal.h b/glfw/internal.h index 5ae8321c3..9b2bb4311 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -98,12 +98,19 @@ struct GLFWDropData { // Platform-specific data fields void* current_data; // NSData* (Cocoa) or unsigned char* from XGetWindowProperty (X11) size_t data_offset; // Read offset in current data (Cocoa/X11) +#ifdef __APPLE__ + // Cocoa specific fields + bool data_is_file_promise; // true if current_data is a file promise provider rather than NSData + void *file_handle, *file_io_error; +#endif +#ifdef _GLFW_X11 // X11-specific fields size_t x11_data_size; // Size of current X11 data unsigned long x11_drop_target; // Window handle where the drop occurred (X11) unsigned long x11_drop_time; // Time from the drop event (X11) unsigned long x11_source; // Source window for XdndFinished (X11) int x11_version; // Xdnd protocol version (X11) +#endif }; // Drag source data structure for chunked writing of drag data diff --git a/kitty/glfw.c b/kitty/glfw.c index 4106659c9..ae3a35149 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -721,8 +721,7 @@ read_drop_data(GLFWDropData *drop, const char *mime) { } else if (ret == 0) { if (_PyBytes_Resize(&ans, pos) != 0) return NULL; return Py_NewRef(ans); - } - else { + } else { errno = -ret; PyErr_SetFromErrno(PyExc_OSError); return NULL; @@ -801,7 +800,7 @@ try_sending_drag_source_data(id_type timer_id UNUSED, void *callback_data UNUSED static void drag_source_callback(GLFWwindow *window UNUSED, const char* mime_type, GLFWDragSourceData* source_data) { - PyObject *data; + PyObject *data = NULL; if (mime_type == NULL) { ds.is_active = false; Py_CLEAR(ds.drag_data);