More work on cocoa darg drop still not functional

I think I am going to rewrite the API to integrate with the event loop,
now that I have a good handle on the semantics of DnD in the two major
platforms.
This commit is contained in:
Kovid Goyal 2026-02-07 23:00:23 +05:30
parent 19f24f2623
commit 0d465f71a3
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
3 changed files with 90 additions and 47 deletions

View file

@ -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 <NSDraggingInfo>)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 <NSDraggingInfo>)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];

7
glfw/internal.h vendored
View file

@ -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

View file

@ -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);