Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ios/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ is removed. Settings live in Application Support; the working directory is
Documents. Open starts the native document picker in Documents. Local documents
open directly; external selections are read with security-scoped access and file
coordination, then copied into Documents under a unique name. Native Save asks
for a filename, applies the filter extension and confirms replacement. External directory picking, offscreen native
for a filename, applies the filter extension and confirms replacement. Files dragged in
from other apps are copied into Documents under a unique name and posted as
`kEventDragDrop` at the drop point, one event per file. External directory picking, offscreen native
windows and joystick discovery are not implemented; rejected requests log to
stderr. Orion routes both Open and Save to these native dialogs.

Expand Down
1 change: 1 addition & 0 deletions ios/ios_local.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ extern EAGLContext *ios_context;
extern CADisplayLink *ios_display_link;
void ios_resize_surface(void);
void ios_cancel_timers(void);
void ios_install_drop(UIView *view);
void ios_post_gesture(ax_gesture_t gesture);
void ios_post_touch(uint32_t event, uint32_t wparam, void *lparam, ax_pointer_t pointer);
#endif
71 changes: 61 additions & 10 deletions ios/ios_system.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,65 @@ void axJoystickShutdown(void) {}
return url;
}

// "name", then "name 2", "name 3", ... starting at the given attempt.
static NSURL *ios_unique_document(NSURL *documents, NSString *name, int attempt) {
NSURL *destination = nil;
for (int suffix = attempt; !destination || [NSFileManager.defaultManager fileExistsAtPath:destination.path]; suffix++) {
NSString *stem = name;
if (suffix > 1) {
stem = [NSString stringWithFormat:@"%@ %d", name.stringByDeletingPathExtension, suffix];
if (name.pathExtension.length) stem = [stem stringByAppendingPathExtension:name.pathExtension];
}
destination = [documents URLByAppendingPathComponent:stem];
}
return destination;
}

// Files dragged in from other apps are copied into Documents, then posted as
// kEventDragDrop at the drop point, one event per file.
@interface AXDropDelegate : NSObject <UIDropInteractionDelegate>
@end
@implementation AXDropDelegate
- (BOOL)dropInteraction:(UIDropInteraction *)interaction canHandleSession:(id<UIDropSession>)session {
return [session hasItemsConformingToTypeIdentifiers:@[UTTypeData.identifier]];
}
- (UIDropProposal *)dropInteraction:(UIDropInteraction *)interaction sessionDidUpdate:(id<UIDropSession>)session {
return [[UIDropProposal alloc] initWithDropOperation:UIDropOperationCopy];
}
- (void)dropInteraction:(UIDropInteraction *)interaction performDrop:(id<UIDropSession>)session {
CGPoint at = [session locationInView:interaction.view];
NSURL *documents = ios_documents_url();
if (!documents) return;
for (UIDragItem *item in session.items) {
NSItemProvider *provider = item.itemProvider;
NSString *type = nil;
for (NSString *candidate in provider.registeredTypeIdentifiers)
if ([[UTType typeWithIdentifier:candidate] conformsToType:UTTypeData]) { type = candidate; break; }
if (!type) { IOS_TRACE("drop item has no file representation types=%s", provider.registeredTypeIdentifiers.description.UTF8String); continue; }
[provider loadFileRepresentationForTypeIdentifier:type completionHandler:^(NSURL *url, NSError *error) {
if (!url) { IOS_TRACE("drop load failed type=%s: %s", type.UTF8String, error.localizedDescription.UTF8String); return; }
// The provider's file is deleted when this handler returns; concurrent drops may race for a name.
NSError *copy_error = nil;
NSURL *destination = nil;
for (int attempt = 1; attempt < 100; attempt++) {
destination = ios_unique_document(documents, url.lastPathComponent, attempt);
copy_error = nil;
if ([NSFileManager.defaultManager copyItemAtURL:url toURL:destination error:&copy_error]) break;
if (copy_error.code != NSFileWriteFileExistsError) break;
}
if (copy_error) { IOS_TRACE("drop import failed name=%s: %s", url.lastPathComponent.UTF8String, copy_error.localizedDescription.UTF8String); return; }
axNotifyFileDropEvent(destination.fileSystemRepresentation, (float)at.x, (float)at.y);
}];
}
}
@end

void ios_install_drop(UIView *view) {
static AXDropDelegate *delegate; // UIDropInteraction holds its delegate weakly
if (!delegate) delegate = [AXDropDelegate new];
[view addInteraction:[[UIDropInteraction alloc] initWithDelegate:delegate]];
}

static void ios_wait_for_dismissal(void) {
while (ios_window.rootViewController.presentedViewController) axWaitMessage(10);
}
Expand Down Expand Up @@ -80,16 +139,8 @@ bool_t axGetOpenFileName(AXopenfilename const *ofn) {
NSString *root = [documents.URLByResolvingSymlinksInPath.path stringByAppendingString:@"/"];
BOOL local = [source.URLByResolvingSymlinksInPath.path hasPrefix:root];
BOOL ok = YES;
if (!local) {
// Import external providers into permanent, app-owned storage without replacing another drawing.
NSString *name = source.lastPathComponent;
destination = [documents URLByAppendingPathComponent:name];
for (int suffix = 2; [NSFileManager.defaultManager fileExistsAtPath:destination.path]; suffix++) {
NSString *stem = [NSString stringWithFormat:@"%@ %d", name.stringByDeletingPathExtension, suffix];
if (name.pathExtension.length) stem = [stem stringByAppendingPathExtension:name.pathExtension];
destination = [documents URLByAppendingPathComponent:stem];
}
}
// Import external providers into permanent, app-owned storage without replacing another drawing.
if (!local) destination = ios_unique_document(documents, source.lastPathComponent, 1);
if (strlen(destination.fileSystemRepresentation) >= ofn->nMaxFile) {
ios_file_error(@"The selected filename is too long."); ok = NO;
} else if (!local) {
Expand Down
1 change: 1 addition & 0 deletions ios/ios_window.m
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ - (void)viewDidLoad {
[ios_view.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[ios_view.topAnchor constraintEqualToAnchor:self.view.topAnchor],
[ios_view.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor]]];
ios_install_drop(ios_view);
[ios_view addGestureRecognizer:[[UIHoverGestureRecognizer alloc] initWithTarget:ios_view action:@selector(hover:)]];
UIPanGestureRecognizer *scroll = [[UIPanGestureRecognizer alloc] initWithTarget:ios_view action:@selector(scroll:)];
scroll.minimumNumberOfTouches = 2;
Expand Down
1 change: 1 addition & 0 deletions platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,7 @@ axMutexDestroy(axmutex_t mutex);
* | `drag <x1> <y1> <x2> <y2>` | Press at (x1,y1), drag to (x2,y2), release |
* | `move <x> <y>` | Mouse moved to (x, y) |
* | `scroll <x> <y> <dx> <dy>` | Scroll at (x, y) by the given wheel delta |
* | `drop <x> <y> <path>` | Drop the file at path on (x, y), as a file dragged in |
* | `key <code> [mods]` | Key down + key up (virtual key code integer) |
* | `keydown <code> [mods]` | Key down only |
* | `keyup <code> [mods]` | Key up only |
Expand Down
9 changes: 9 additions & 0 deletions rc_server_impl.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ rc_dispatch(int conn, const char *line)
(void *)(intptr_t)MAKEDWORD(dx, dy));
rc_reply(conn, "ok\n");

} else if (strncmp(line, "drop ", 5) == 0) {
int consumed = 0;
if (sscanf(line + 5, "%d %d %n", &x, &y, &consumed) == 2 && line[5 + consumed]) {
axNotifyFileDropEvent(line + 5 + consumed, (float)x, (float)y);
rc_reply(conn, "ok\n");
} else {
rc_reply(conn, "err usage: drop <x> <y> <path>\n");
}

/* --- Keyboard input --- */

} else if (sscanf(line, "keydown %d %d", &code, &mods) == 2) {
Expand Down
Loading