Rewrite macOS drop handling code to workaround Apple's insanely limited API

This commit is contained in:
Kovid Goyal 2026-05-16 13:06:23 +05:30
parent 418ea57382
commit c278e82ab5
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
2 changed files with 246 additions and 241 deletions

13
glfw/cocoa_platform.h vendored
View file

@ -120,10 +120,14 @@ typedef struct _GLFWDropData {
const char **copy_mimes; // Working copy passed to callbacks; pointers into mimes[]
size_t copy_mimes_count; // Accepted count after last callback
bool drag_accepted;
id pasteboard;
id data_mapping;
id file_promise_mapping;
id file_promise_temp_dir; // NSURL* for the unique temp dir used when receiving file promises for text/uri-list
struct {
unsigned long long request_id;
id temp_dir;
id data_map; // map MIME to NSError or NSInputStream
id path_map; // map MIME to NSError or NSInputStream or NSUrl
id pending_requests;
bool promises_loaded;
} in_progress_drop;
} _GLFWDropData;
// Cocoa-specific per-window data
@ -184,6 +188,7 @@ typedef struct _GLFWwindowNS
// Cached MIME types from drag enter (for move events)
_GLFWDropData drop_data;
unsigned long long drop_request_counter;
} _GLFWwindowNS;

View file

@ -7,7 +7,7 @@
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// Permission is granted to anyone to use this software fo any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
@ -1477,31 +1477,31 @@ - (BOOL)wantsPeriodicDraggingUpdates
return YES;
}
static void
free_in_progress_drop_data(_GLFWwindow *window) {
_GLFWDropData *d = &window->ns.drop_data;
if (d->in_progress_drop.temp_dir) {
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtURL:d->in_progress_drop.temp_dir error:&error];
[d->in_progress_drop.temp_dir release];
}
if (d->in_progress_drop.data_map) [d->in_progress_drop.data_map release];
if (d->in_progress_drop.path_map) [d->in_progress_drop.path_map release];
if (d->in_progress_drop.pending_requests) [d->in_progress_drop.pending_requests release];
memset(&d->in_progress_drop, 0, sizeof(d->in_progress_drop));
}
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((void*)window->ns.drop_data.mimes[i]);
free(window->ns.drop_data.mimes);
_GLFWDropData *d = &window->ns.drop_data;
if (d->mimes) {
for (size_t i = 0; i < d->mimes_count; i++) free((void*)d->mimes[i]);
free(d->mimes);
}
free(window->ns.drop_data.copy_mimes); // pointer array only; strings owned by mimes[]
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];
if (pair[0] != [NSNull null]) { error = nil; [fileManager removeItemAtURL:pair[0] error:&error]; }
}
[window->ns.drop_data.file_promise_mapping release];
}
if (window->ns.drop_data.file_promise_temp_dir) {
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtURL:window->ns.drop_data.file_promise_temp_dir error:&error];
[window->ns.drop_data.file_promise_temp_dir release];
}
memset(&window->ns.drop_data, 0, sizeof(_GLFWDropData));
free(d->copy_mimes); // pointer array only; strings owned by mimes[]
d->mimes = NULL; d->copy_mimes = NULL;
d->mimes_count = 0; d->copy_mimes_count = 0; d->drag_accepted = NO;
free_in_progress_drop_data(window);
}
static void
@ -1559,9 +1559,8 @@ - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
size_t mime_count = 0;
// Check for common types first (use _glfw_strdup since we need to own the strings)
NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
bool has_uri_list = false;
if ([pasteboard canReadObjectForClasses:@[[NSURL class]] options:options]) {
if ([pasteboard canReadObjectForClasses:@[[NSURL class]] options:nil]) {
has_uri_list = true;
mime_array[mime_count++] = _glfw_strdup("text/uri-list");
}
@ -1650,37 +1649,10 @@ - (void)draggingExited:(id <NSDraggingInfo>)sender
free_drop_data(window);
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
if (!window->ns.drop_data.drag_accepted) return NO;
const NSRect contentRect = [window->ns.view frame];
const NSPoint pos = [sender draggingLocation];
double xpos = pos.x;
double ypos = contentRect.size.height - pos.y;
bool from_self = ([sender draggingSource] != nil);
_GLFWDropData *d = &window->ns.drop_data;
if (!reset_drop_copy_mimes(d)) return NO;
size_t num_accepted = _glfwInputDropEvent(window, GLFW_DROP_DROP, xpos, ypos, d->copy_mimes, d->copy_mimes_count, from_self);
if (d->copy_mimes) {
update_drop_state(window, num_accepted, GLFW_DROP_DROP);
window->ns.drop_data.pasteboard = [[sender draggingPasteboard] retain];
for (size_t i = 0; i < num_accepted; i++)
_glfwPlatformRequestDropData(window, d->copy_mimes[i]);
}
// Restore first-responder status after native DnD; the drag operation can
// displace the content view from first responder, silently breaking keyboard
// input even though osw->is_focused remains true.
[window->ns.object makeFirstResponder:window->ns.view];
- (BOOL) prepareForDragOperation:(id<NSDraggingInfo>) sender {
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);
@ -1694,206 +1666,234 @@ - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
});
}
static void
create_uri_list(_GLFWDropData *d, NSArray *urls) {
NSMutableArray<NSString *> *items = [NSMutableArray array];
NSCharacterSet *allowedChars = [NSCharacterSet URLQueryAllowedCharacterSet];
for (NSURL *url in urls) {
NSString *absoluteString = url.absoluteString;
NSString *q = [absoluteString stringByAddingPercentEncodingWithAllowedCharacters:allowedChars];
[items addObject:q];
}
NSString *result = [urls componentsJoinedByString:@"\r\n"];
NSData *data = [result dataUsingEncoding:NSUTF8StringEncoding];
NSInputStream *inputStream = [NSInputStream inputStreamWithData:data];
[inputStream open];
d->in_progress_drop.data_map[@"text/uri-list"] = inputStream;
}
static void
build_uri_list(_GLFWDropData *d) {
if (!d->in_progress_drop.path_map) return;
NSMutableArray<NSURL *> *urls = [NSMutableArray array];
for (NSString *mime in d->in_progress_drop.path_map) {
if (![mime hasPrefix:@"kitty-internal/uri-list-item-"]) continue;
id x = d->in_progress_drop.path_map[mime];
if ([x isKindOfClass:[NSError class]]) {
d->in_progress_drop.data_map[@"text/uri-list"] = x;
return;
}
if ([x isKindOfClass:[NSURL class]]) [urls addObject:x];
}
if ([urls count] > 0) create_uri_list(d, urls);
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
free_in_progress_drop_data(window);
if (!window->ns.drop_data.drag_accepted) return NO;
const NSRect contentRect = [window->ns.view frame];
const NSPoint pos = [sender draggingLocation];
double xpos = pos.x;
double ypos = contentRect.size.height - pos.y;
bool from_self = ([sender draggingSource] != nil);
_GLFWDropData *d = &window->ns.drop_data;
if (!reset_drop_copy_mimes(d)) return NO;
NSError *mkdirError = nil;
NSURL *tempDirURL = [[NSFileManager defaultManager]
URLForDirectory:NSItemReplacementDirectory
inDomain:NSUserDomainMask
appropriateForURL:[NSURL fileURLWithPath:NSTemporaryDirectory()]
create:YES
error:&mkdirError];
if (!tempDirURL) {
NSLog(@"Failed to create temp dir for file promises: %@", mkdirError);
return NO;
}
[[NSFileManager defaultManager] setAttributes:@{NSFilePosixPermissions: @(0777)} ofItemAtPath:[tempDirURL path] error:&mkdirError];
d->in_progress_drop.data_map = [[NSMutableDictionary alloc] init];
NSPasteboard *pasteboard = [sender draggingPasteboard];
NSMutableArray<NSURL *> *urls = [NSMutableArray array];
for (NSURL *url in [pasteboard readObjectsForClasses:@[[NSURL class]] options:nil]) [urls addObject:url];
if ([urls count] > 0) create_uri_list(d, urls);
NSMutableArray<NSString *> *texts = [NSMutableArray array];
for (NSString *text in [pasteboard readObjectsForClasses:@[[NSString class]] options:nil]) [texts addObject:text];
if ([texts count] > 0) {
NSString *text = [texts componentsJoinedByString:@"\n\n"];
NSInputStream *s = [NSInputStream inputStreamWithData:[text dataUsingEncoding:NSUTF8StringEncoding]];
[s open];
d->in_progress_drop.data_map[@"text/plain"] = s;
}
for (NSPasteboardType type in [pasteboard types]) {
const char *mime = uti_to_mime(type);
if (!mime || d->in_progress_drop.data_map[@(mime)] != nil) continue;
NSData *data = [pasteboard dataForType:type];
if (data) {
NSInputStream *s = [NSInputStream inputStreamWithData:data];
[s open];
d->in_progress_drop.data_map[@(mime)] = s;
}
}
NSArray *receivers = [pasteboard readObjectsForClasses:@[[NSFilePromiseReceiver class]] options:nil];
if ([receivers count] > 0) {
d->in_progress_drop.request_id = ++window->ns.drop_request_counter;
unsigned long long request_id = d->in_progress_drop.request_id;
GLFWid wid = window->id;
NSOperationQueue *workQueue = [[[NSOperationQueue alloc] init] autorelease];
dispatch_group_t promiseGroup = dispatch_group_create();
NSMutableDictionary *results = [[NSMutableDictionary alloc] init];
unsigned url_counter = 0;
for (NSFilePromiseReceiver *receiver in receivers) {
dispatch_group_enter(promiseGroup);
NSArray<NSString *> *types = receiver.fileTypes;
unsigned url_list_idx = 0;
for (NSString *type in types) {
UTType *promisedType = [UTType typeWithIdentifier:type];
if (promisedType && [promisedType conformsToType:UTTypeFileURL]) {
url_list_idx = ++url_counter;
break;
}
}
[receiver receivePromisedFilesAtDestination:tempDirURL options:@{} operationQueue:workQueue
reader:^(NSURL * _Nonnull fileURL, NSError * _Nullable error) {
if (url_list_idx) results[
[NSString stringWithFormat:@"kitty-internal/uri-list-item-%u", url_list_idx-1]] = error ? error : fileURL;
else {
id result = error;
if (!result) {
NSInputStream *s = [NSInputStream inputStreamWithURL:fileURL];
[s open];
if ([s streamStatus] == NSStreamStatusError) result = [s streamError];
else result = s;
}
for (NSString *type in types) {
const char *mime = uti_to_mime(type);
if (mime) results[@(mime)] = result;
}
}
dispatch_group_leave(promiseGroup);
}];
}
dispatch_group_notify(promiseGroup, dispatch_get_main_queue(), ^{
_GLFWwindow *w = NULL;
for (_GLFWwindow *ww = _glfw.windowListHead; ww; ww = ww->next) {
if (ww->id == wid) { w = ww; break; }
}
if (w && w->ns.drop_data.in_progress_drop.request_id == request_id) {
_GLFWDropData *d = &w->ns.drop_data;
d->in_progress_drop.path_map = results;
d->in_progress_drop.promises_loaded = YES;
d->in_progress_drop.temp_dir = [tempDirURL retain];
build_uri_list(d);
if (d->in_progress_drop.pending_requests) {
for (NSString *mime in d->in_progress_drop.pending_requests) {
send_data_available_event_on_next_event_loop_tick(wid, [mime UTF8String]);
}
}
} else {
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtURL:tempDirURL error:&error];
}
});
[promiseGroup release];
} else d->in_progress_drop.promises_loaded = true;
size_t num_accepted = _glfwInputDropEvent(
window, GLFW_DROP_DROP, xpos, ypos, d->copy_mimes, d->copy_mimes_count, from_self);
if (d->copy_mimes) {
update_drop_state(window, num_accepted, GLFW_DROP_DROP);
for (size_t i = 0; i < num_accepted; i++)
_glfwPlatformRequestDropData(window, d->copy_mimes[i]);
}
// Restore first-responder status after native DnD; the drag operation can
// displace the content view from first responder, silently breaking keyboard
// input even though osw->is_focused remains true.
[window->ns.object makeFirstResponder:window->ns.view];
return YES;
}
- (void) concludeDragOperation:(id<NSDraggingInfo>) sender {
}
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.
}
int
_glfwPlatformRequestDropData(_GLFWwindow *window, 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);
_GLFWDropData *d = &window->ns.drop_data;
if (d->in_progress_drop.data_map[@(mime)] != nil) {
send_data_available_event_on_next_event_loop_tick(window->id, 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);
if (!d->in_progress_drop.promises_loaded) {
if (d->in_progress_drop.pending_requests == nil) d->in_progress_drop.pending_requests = [[NSMutableSet alloc] init];
[d->in_progress_drop.pending_requests addObject:@(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 {
window->ns.drop_data.file_promise_mapping[@(mime)] = @[[NSNull null], [NSNull null], [NSNull null]];
// Create a unique temp directory that lives until the next drop event or window destruction
NSError *mkdirError = nil;
NSString *tmpDirPath = [NSTemporaryDirectory() stringByAppendingPathComponent:
[[NSUUID UUID] UUIDString]];
NSURL *tmpDirURL = [NSURL fileURLWithPath:tmpDirPath isDirectory:YES];
if (![[NSFileManager defaultManager] createDirectoryAtURL:tmpDirURL
withIntermediateDirectories:YES attributes:nil error:&mkdirError]) {
NSLog(@"Failed to create temp dir for file promises: %@", mkdirError);
if (d->in_progress_drop.path_map && d->in_progress_drop.path_map[@(mime)] != nil) {
send_data_available_event_on_next_event_loop_tick(window->id, mime);
return 0;
}
window->ns.drop_data.file_promise_temp_dir = [tmpDirURL retain];
// Collect file promise receivers whose UTIs conform to UTTypeFileURL
NSArray *all_receivers = [pasteboard readObjectsForClasses:@[[NSFilePromiseReceiver class]] options:@{}];
NSMutableArray *file_receivers = [NSMutableArray array];
for (NSFilePromiseReceiver *receiver in all_receivers) {
for (NSString *ruti in receiver.fileTypes) {
UTType *promisedType = [UTType typeWithIdentifier:ruti];
if (promisedType && [promisedType conformsToType:UTTypeFileURL]) {
[file_receivers addObject:receiver];
break;
return ENOENT;
}
static NSString*
NSStringFromStreamStatus(NSStreamStatus status) {
switch (status) {
case NSStreamStatusNotOpen: return @"NSStreamStatusNotOpen";
case NSStreamStatusOpening: return @"NSStreamStatusOpening";
case NSStreamStatusOpen: return @"NSStreamStatusOpen";
case NSStreamStatusReading: return @"NSStreamStatusReading";
case NSStreamStatusWriting: return @"NSStreamStatusWriting";
case NSStreamStatusAtEnd: return @"NSStreamStatusAtEnd";
case NSStreamStatusClosed: return @"NSStreamStatusClosed";
case NSStreamStatusError: return @"NSStreamStatusError";
default: return [NSString stringWithFormat:@"Unknown (%lu)", (unsigned long)status];
}
}
}
if ([file_receivers count] == 0) {
// Nothing to receive; emit an empty uri-list immediately
if (window->ns.drop_data.data_mapping == nil)
window->ns.drop_data.data_mapping = [[NSMutableDictionary alloc] init];
window->ns.drop_data.data_mapping[@(mime)] = @[[NSData data], @0];
[window->ns.drop_data.file_promise_mapping removeObjectForKey:@(mime)];
send_data_available_event_on_next_event_loop_tick(wid, mime);
return 0;
}
char *mt = _glfw_strdup(mime);
NSMutableArray *collected_urls = [[NSMutableArray alloc] init];
dispatch_group_t group = dispatch_group_create();
NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];
opQueue.maxConcurrentOperationCount = 1; // serial: safe to append to collected_urls
for (NSFilePromiseReceiver *receiver in file_receivers) {
dispatch_group_enter(group);
[receiver receivePromisedFilesAtDestination:tmpDirURL options:@{}
operationQueue:opQueue
reader:^(NSURL *fileURL, NSError *errorOrNil) {
if (!errorOrNil && fileURL) [collected_urls addObject:fileURL];
dispatch_group_leave(group);
}];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
dispatch_release(group);
_GLFWwindow *w = _glfwWindowForId(wid);
if (w && w->ns.drop_data.file_promise_mapping) {
if (w->ns.drop_data.data_mapping == nil)
w->ns.drop_data.data_mapping = [[NSMutableDictionary alloc] init];
NSMutableString *uri_list = [NSMutableString stringWithCapacity:4096];
for (NSURL *url in collected_urls) {
if ([uri_list length] > 0) [uri_list appendString:@"\n"];
[uri_list appendString:url.absoluteString];
}
NSData *result = [uri_list dataUsingEncoding:NSUTF8StringEncoding];
w->ns.drop_data.data_mapping[@(mt)] = @[result, @0];
[w->ns.drop_data.file_promise_mapping removeObjectForKey:@(mt)];
const char *mimes[1] = {mt};
_glfwInputDropEvent(w, GLFW_DROP_DATA_AVAILABLE, 0, 0, mimes, 1, false);
}
[collected_urls release];
[opQueue release];
free(mt);
});
return 0;
}
} 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;
_GLFWDropData *d = &window->ns.drop_data;
id x = d->in_progress_drop.data_map[@(mime)];
if (x == nil && d->in_progress_drop.promises_loaded && d->in_progress_drop.path_map != nil)
x = d->in_progress_drop.path_map[@(mime)];
if (x == nil) return -ENOENT;
if ([x isKindOfClass:[NSError class]]) {
NSError *error = x;
if ([error.domain isEqualToString:NSPOSIXErrorDomain]) return - (int)error.code;
return -EINVAL;
}
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;
if ([x isKindOfClass:[NSInputStream class]]) {
NSInputStream *stream = x;
if ([stream streamStatus] == NSStreamStatusNotOpen) [stream open];
NSInteger bytesRead = [stream read:(uint8_t*)buffer maxLength:capacity];
if (bytesRead > 0) {
send_data_available_event_on_next_event_loop_tick(window->id, mime);
return (ssize_t)bytesRead;
}
if (bytesRead == 0) return 0; // EOF
NSError *error = [stream streamError];
NSLog(@"Failed to read from drop data stream, status: %@ error: %@", NSStringFromStreamStatus([stream streamStatus]), error);
if (error && [error.domain isEqualToString:NSPOSIXErrorDomain]) return - (int)error.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;
}