diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-09-11 14:25:03 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-09-11 14:25:03 +0300 |
commit | 25456ce008c7fe78378ddaaf784c74209a2d8c14 (patch) | |
tree | fbb86324f468ab984ca1be1c25f92872499ed948 /src | |
parent | 2fc802367acd63b86fecda92461d63d029d7378c (diff) |
macOS: Handling launch URLs and drop'n'drop
Improved drop and drop event handling: multiple dropped files/URLs open in new tabs. The application registers gemini: as a handled URL scheme.
Diffstat (limited to 'src')
-rw-r--r-- | src/app.c | 57 | ||||
-rw-r--r-- | src/app.h | 1 | ||||
-rw-r--r-- | src/gmrequest.c | 3 | ||||
-rw-r--r-- | src/macos.m | 53 | ||||
-rw-r--r-- | src/main.c | 2 | ||||
-rw-r--r-- | src/ui/window.c | 7 |
6 files changed, 110 insertions, 13 deletions
@@ -88,6 +88,9 @@ struct Impl_App { | |||
88 | iBool running; | 88 | iBool running; |
89 | iBool pendingRefresh; | 89 | iBool pendingRefresh; |
90 | int tabEnum; | 90 | int tabEnum; |
91 | iStringList *launchCommands; | ||
92 | iBool isFinishedLaunching; | ||
93 | iTime lastDropTime; /* for detecting drops of multiple items */ | ||
91 | /* Preferences: */ | 94 | /* Preferences: */ |
92 | iBool commandEcho; /* --echo */ | 95 | iBool commandEcho; /* --echo */ |
93 | iBool retainWindowSize; | 96 | iBool retainWindowSize; |
@@ -283,6 +286,9 @@ static void saveState_App_(const iApp *d) { | |||
283 | } | 286 | } |
284 | 287 | ||
285 | static void init_App_(iApp *d, int argc, char **argv) { | 288 | static void init_App_(iApp *d, int argc, char **argv) { |
289 | d->isFinishedLaunching = iFalse; | ||
290 | d->launchCommands = new_StringList(); | ||
291 | iZap(d->lastDropTime); | ||
286 | init_CommandLine(&d->args, argc, argv); | 292 | init_CommandLine(&d->args, argc, argv); |
287 | init_SortedArray(&d->tickers, sizeof(iTicker), cmp_Ticker_); | 293 | init_SortedArray(&d->tickers, sizeof(iTicker), cmp_Ticker_); |
288 | d->lastTickerTime = SDL_GetTicks(); | 294 | d->lastTickerTime = SDL_GetTicks(); |
@@ -304,6 +310,9 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
304 | init_String(&d->gopherProxy); | 310 | init_String(&d->gopherProxy); |
305 | init_String(&d->httpProxy); | 311 | init_String(&d->httpProxy); |
306 | setThemePalette_Color(d->theme); | 312 | setThemePalette_Color(d->theme); |
313 | #if defined (iPlatformApple) | ||
314 | setupApplication_MacOS(); | ||
315 | #endif | ||
307 | loadPrefs_App_(d); | 316 | loadPrefs_App_(d); |
308 | load_Visited(d->visited, dataDir_App_); | 317 | load_Visited(d->visited, dataDir_App_); |
309 | load_Bookmarks(d->bookmarks, dataDir_App_); | 318 | load_Bookmarks(d->bookmarks, dataDir_App_); |
@@ -324,6 +333,12 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
324 | postCommand_App("navigate.home"); | 333 | postCommand_App("navigate.home"); |
325 | } | 334 | } |
326 | postCommand_App("window.unfreeze"); | 335 | postCommand_App("window.unfreeze"); |
336 | d->isFinishedLaunching = iTrue; | ||
337 | /* Run any commands that were pending completion of launch. */ { | ||
338 | iForEach(StringList, i, d->launchCommands) { | ||
339 | postCommandString_App(i.value); | ||
340 | } | ||
341 | } | ||
327 | } | 342 | } |
328 | 343 | ||
329 | static void deinit_App(iApp *d) { | 344 | static void deinit_App(iApp *d) { |
@@ -340,6 +355,7 @@ static void deinit_App(iApp *d) { | |||
340 | delete_Window(d->window); | 355 | delete_Window(d->window); |
341 | d->window = NULL; | 356 | d->window = NULL; |
342 | deinit_CommandLine(&d->args); | 357 | deinit_CommandLine(&d->args); |
358 | iRelease(d->launchCommands); | ||
343 | } | 359 | } |
344 | 360 | ||
345 | const iString *execPath_App(void) { | 361 | const iString *execPath_App(void) { |
@@ -350,6 +366,21 @@ const iString *dataDir_App(void) { | |||
350 | return collect_String(cleanedCStr_Path(dataDir_App_)); | 366 | return collect_String(cleanedCStr_Path(dataDir_App_)); |
351 | } | 367 | } |
352 | 368 | ||
369 | const iString *debugInfo_App(void) { | ||
370 | iApp *d = &app_; | ||
371 | iString *msg = collectNew_String(); | ||
372 | format_String(msg, "# Debug information\n"); | ||
373 | appendFormat_String(msg, "## Launch arguments\n"); | ||
374 | iConstForEach(StringList, i, args_CommandLine(&d->args)) { | ||
375 | appendFormat_String(msg, "* %zu: %s\n", i.pos, cstr_String(i.value)); | ||
376 | } | ||
377 | appendFormat_String(msg, "## Launch commands\n"); | ||
378 | iConstForEach(StringList, j, d->launchCommands) { | ||
379 | appendFormat_String(msg, "%s\n", cstr_String(j.value)); | ||
380 | } | ||
381 | return msg; | ||
382 | } | ||
383 | |||
353 | iLocalDef iBool isWaitingAllowed_App_(const iApp *d) { | 384 | iLocalDef iBool isWaitingAllowed_App_(const iApp *d) { |
354 | return !d->pendingRefresh && isEmpty_SortedArray(&d->tickers); | 385 | return !d->pendingRefresh && isEmpty_SortedArray(&d->tickers); |
355 | } | 386 | } |
@@ -365,9 +396,22 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
365 | case SDL_QUIT: | 396 | case SDL_QUIT: |
366 | d->running = iFalse; | 397 | d->running = iFalse; |
367 | goto backToMainLoop; | 398 | goto backToMainLoop; |
368 | case SDL_DROPFILE: | 399 | case SDL_DROPFILE: { |
369 | postCommandf_App("open url:file://%s", ev.drop.file); | 400 | iBool newTab = iFalse; |
401 | if (elapsedSeconds_Time(&d->lastDropTime) < 0.1) { | ||
402 | /* Each additional drop gets a new tab. */ | ||
403 | newTab = iTrue; | ||
404 | } | ||
405 | d->lastDropTime = now_Time(); | ||
406 | if (startsWithCase_CStr(ev.drop.file, "gemini:") || | ||
407 | startsWithCase_CStr(ev.drop.file, "file:")) { | ||
408 | postCommandf_App("~open newtab:%d url:%s", newTab, ev.drop.file); | ||
409 | } | ||
410 | else { | ||
411 | postCommandf_App("~open newtab:%d url:file://%s", newTab, ev.drop.file); | ||
412 | } | ||
370 | break; | 413 | break; |
414 | } | ||
371 | default: { | 415 | default: { |
372 | iBool wasUsed = processEvent_Window(d->window, &ev); | 416 | iBool wasUsed = processEvent_Window(d->window, &ev); |
373 | if (ev.type == SDL_USEREVENT && ev.user.code == command_UserEventCode) { | 417 | if (ev.type == SDL_USEREVENT && ev.user.code == command_UserEventCode) { |
@@ -488,12 +532,21 @@ void postRefresh_App(void) { | |||
488 | } | 532 | } |
489 | 533 | ||
490 | void postCommand_App(const char *command) { | 534 | void postCommand_App(const char *command) { |
535 | iApp *d = &app_; | ||
491 | iAssert(command); | 536 | iAssert(command); |
492 | SDL_Event ev; | 537 | SDL_Event ev; |
493 | if (*command == '!') { | 538 | if (*command == '!') { |
494 | /* Global command; this is global context so just ignore. */ | 539 | /* Global command; this is global context so just ignore. */ |
495 | command++; | 540 | command++; |
496 | } | 541 | } |
542 | if (*command == '~') { | ||
543 | /* Requires launch to be finished; defer it if needed. */ | ||
544 | command++; | ||
545 | if (!d->isFinishedLaunching) { | ||
546 | pushBackCStr_StringList(d->launchCommands, command); | ||
547 | return; | ||
548 | } | ||
549 | } | ||
497 | ev.user.type = SDL_USEREVENT; | 550 | ev.user.type = SDL_USEREVENT; |
498 | ev.user.code = command_UserEventCode; | 551 | ev.user.code = command_UserEventCode; |
499 | ev.user.windowID = get_Window() ? SDL_GetWindowID(get_Window()->win) : 0; | 552 | ev.user.windowID = get_Window() ? SDL_GetWindowID(get_Window()->win) : 0; |
@@ -48,6 +48,7 @@ enum iUserEventCode { | |||
48 | 48 | ||
49 | const iString *execPath_App (void); | 49 | const iString *execPath_App (void); |
50 | const iString *dataDir_App (void); | 50 | const iString *dataDir_App (void); |
51 | const iString *debugInfo_App (void); | ||
51 | 52 | ||
52 | int run_App (int argc, char **argv); | 53 | int run_App (int argc, char **argv); |
53 | void processEvents_App (enum iAppEventMode mode); | 54 | void processEvents_App (enum iAppEventMode mode); |
diff --git a/src/gmrequest.c b/src/gmrequest.c index 137e8303..843d2f46 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c | |||
@@ -308,6 +308,9 @@ static const iBlock *aboutPageSource_(iRangecc path) { | |||
308 | if (equalCase_Rangecc(path, "version")) { | 308 | if (equalCase_Rangecc(path, "version")) { |
309 | return &blobVersion_Embedded; | 309 | return &blobVersion_Embedded; |
310 | } | 310 | } |
311 | if (equalCase_Rangecc(path, "debug")) { | ||
312 | return utf8_String(debugInfo_App()); | ||
313 | } | ||
311 | return src; | 314 | return src; |
312 | } | 315 | } |
313 | 316 | ||
diff --git a/src/macos.m b/src/macos.m index d44be7d0..bbbaaa19 100644 --- a/src/macos.m +++ b/src/macos.m | |||
@@ -121,8 +121,9 @@ enum iTouchBarVariant { | |||
121 | } | 121 | } |
122 | - (id)initWithSDLDelegate:(NSObject<NSApplicationDelegate> *)sdl; | 122 | - (id)initWithSDLDelegate:(NSObject<NSApplicationDelegate> *)sdl; |
123 | //- (NSTouchBar *)makeTouchBar; | 123 | //- (NSTouchBar *)makeTouchBar; |
124 | /* SDL needs to do its own thing. */ | 124 | - (BOOL)application:(NSApplication *)app openFile:(NSString *)filename; |
125 | - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename; | 125 | - (void)application:(NSApplication *)app openFiles:(NSArray<NSString *> *)filenames; |
126 | - (void)application:(NSApplication *)app openURLs:(NSArray<NSURL *> *)urls; | ||
126 | - (void)applicationDidFinishLaunching:(NSNotification *)notifications; | 127 | - (void)applicationDidFinishLaunching:(NSNotification *)notifications; |
127 | @end | 128 | @end |
128 | 129 | ||
@@ -151,7 +152,7 @@ enum iTouchBarVariant { | |||
151 | static void appearanceChanged_MacOS_(NSString *name) { | 152 | static void appearanceChanged_MacOS_(NSString *name) { |
152 | const iBool isDark = [name containsString:@"Dark"]; | 153 | const iBool isDark = [name containsString:@"Dark"]; |
153 | const iBool isHighContrast = [name containsString:@"HighContrast"]; | 154 | const iBool isHighContrast = [name containsString:@"HighContrast"]; |
154 | postCommandf_App("os.theme.changed dark:%d contrast:%d", isDark ? 1 : 0, isHighContrast ? 1 : 0); | 155 | postCommandf_App("~os.theme.changed dark:%d contrast:%d", isDark ? 1 : 0, isHighContrast ? 1 : 0); |
155 | // printf("Effective appearance changed: %s\n", [name cStringUsingEncoding:NSUTF8StringEncoding]); | 156 | // printf("Effective appearance changed: %s\n", [name cStringUsingEncoding:NSUTF8StringEncoding]); |
156 | // fflush(stdout); | 157 | // fflush(stdout); |
157 | } | 158 | } |
@@ -170,8 +171,23 @@ static void appearanceChanged_MacOS_(NSString *name) { | |||
170 | [menuCommands setObject:command forKey:[menuItem title]]; | 171 | [menuCommands setObject:command forKey:[menuItem title]]; |
171 | } | 172 | } |
172 | 173 | ||
173 | - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { | 174 | - (BOOL)application:(NSApplication *)app openFile:(NSString *)filename { |
174 | return [sdlDelegate application:theApplication openFile:filename]; | 175 | return [sdlDelegate application:app openFile:filename]; |
176 | } | ||
177 | |||
178 | - (void)application:(NSApplication *)app openFiles:(NSArray<NSString *> *)filenames { | ||
179 | /* TODO: According to AppKit docs, this method won't be called when openURLs is defined. */ | ||
180 | for (NSString *fn in filenames) { | ||
181 | NSLog(@"openFiles: %@", fn); | ||
182 | [self application:app openFile:fn]; | ||
183 | } | ||
184 | } | ||
185 | |||
186 | - (void)application:(NSApplication *)app openURLs:(NSArray<NSURL *> *)urls { | ||
187 | for (NSURL *url in urls) { | ||
188 | NSLog(@"openURLs: %@", [url absoluteString]); | ||
189 | [sdlDelegate application:app openFile:[url absoluteString]]; | ||
190 | } | ||
175 | } | 191 | } |
176 | 192 | ||
177 | - (void)applicationDidFinishLaunching:(NSNotification *)notification { | 193 | - (void)applicationDidFinishLaunching:(NSNotification *)notification { |
@@ -397,9 +413,30 @@ void enableMomentumScroll_MacOS(void) { | |||
397 | forKey: @"AppleMomentumScrollSupported"]; | 413 | forKey: @"AppleMomentumScrollSupported"]; |
398 | } | 414 | } |
399 | 415 | ||
416 | @interface UrlHandler : NSObject | ||
417 | - (void)handleURLEvent:(NSAppleEventDescriptor*)event | ||
418 | withReplyEvent:(NSAppleEventDescriptor*)replyEvent; | ||
419 | @end | ||
420 | |||
421 | @implementation UrlHandler | ||
422 | - (void)handleURLEvent:(NSAppleEventDescriptor*)event | ||
423 | withReplyEvent:(NSAppleEventDescriptor*)replyEvent { | ||
424 | NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; | ||
425 | postCommandf_App("~open url:%s", [url cStringUsingEncoding:NSUTF8StringEncoding]); | ||
426 | } | ||
427 | @end | ||
428 | |||
429 | void registerURLHandler_MacOS(void) { | ||
430 | UrlHandler *handler = [[UrlHandler alloc] init]; | ||
431 | [[NSAppleEventManager sharedAppleEventManager] | ||
432 | setEventHandler:handler | ||
433 | andSelector:@selector(handleURLEvent:withReplyEvent:) | ||
434 | forEventClass:kInternetEventClass | ||
435 | andEventID:kAEGetURL]; | ||
436 | } | ||
437 | |||
400 | void setupApplication_MacOS(void) { | 438 | void setupApplication_MacOS(void) { |
401 | NSApplication *app = [NSApplication sharedApplication]; | 439 | NSApplication *app = [NSApplication sharedApplication]; |
402 | //appearanceChanged_MacOS_([[app effectiveAppearance] name]); | ||
403 | /* Our delegate will override SDL's delegate. */ | 440 | /* Our delegate will override SDL's delegate. */ |
404 | MyDelegate *myDel = [[MyDelegate alloc] initWithSDLDelegate:app.delegate]; | 441 | MyDelegate *myDel = [[MyDelegate alloc] initWithSDLDelegate:app.delegate]; |
405 | [myDel setAppearance:[[app effectiveAppearance] name]]; | 442 | [myDel setAppearance:[[app effectiveAppearance] name]]; |
@@ -502,3 +539,7 @@ void handleCommand_MacOS(const char *cmd) { | |||
502 | } | 539 | } |
503 | #endif | 540 | #endif |
504 | } | 541 | } |
542 | |||
543 | void log_MacOS(const char *msg) { | ||
544 | NSLog(@"%s", msg); | ||
545 | } | ||
@@ -31,11 +31,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
31 | 31 | ||
32 | #if defined (iPlatformApple) | 32 | #if defined (iPlatformApple) |
33 | extern void enableMomentumScroll_MacOS(void); | 33 | extern void enableMomentumScroll_MacOS(void); |
34 | extern void registerURLHandler_MacOS(void); | ||
34 | #endif | 35 | #endif |
35 | 36 | ||
36 | int main(int argc, char **argv) { | 37 | int main(int argc, char **argv) { |
37 | #if defined (iPlatformApple) | 38 | #if defined (iPlatformApple) |
38 | enableMomentumScroll_MacOS(); | 39 | enableMomentumScroll_MacOS(); |
40 | registerURLHandler_MacOS(); | ||
39 | #endif | 41 | #endif |
40 | #if defined (iPlatformMsys) | 42 | #if defined (iPlatformMsys) |
41 | /* MSYS runtime takes care of WinMain. */ | 43 | /* MSYS runtime takes care of WinMain. */ |
diff --git a/src/ui/window.c b/src/ui/window.c index 87ca2614..0a63a941 100644 --- a/src/ui/window.c +++ b/src/ui/window.c | |||
@@ -138,8 +138,8 @@ static const iMenuItem viewMenuItems[] = { | |||
138 | { "Show Page Outline", '4', KMOD_PRIMARY, "sidebar.mode arg:3 toggle:1" }, | 138 | { "Show Page Outline", '4', KMOD_PRIMARY, "sidebar.mode arg:3 toggle:1" }, |
139 | { "Toggle Sidebar", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, | 139 | { "Toggle Sidebar", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, |
140 | { "---", 0, 0, NULL }, | 140 | { "---", 0, 0, NULL }, |
141 | { "Go Back", navigateBack_KeyShortcut, "navigate.back" }, | 141 | { "Go Back", SDLK_LEFTBRACKET, KMOD_PRIMARY, "navigate.back" }, |
142 | { "Go Forward", navigateForward_KeyShortcut, "navigate.forward" }, | 142 | { "Go Forward", SDLK_RIGHTBRACKET, KMOD_PRIMARY, "navigate.forward" }, |
143 | { "Reload Page", reload_KeyShortcut, "navigate.reload" }, | 143 | { "Reload Page", reload_KeyShortcut, "navigate.reload" }, |
144 | { "---", 0, 0, NULL }, | 144 | { "---", 0, 0, NULL }, |
145 | { "Zoom In", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" }, | 145 | { "Zoom In", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" }, |
@@ -546,9 +546,6 @@ void init_Window(iWindow *d, iRect rect) { | |||
546 | d->presentTime = 0.0; | 546 | d->presentTime = 0.0; |
547 | setId_Widget(d->root, "root"); | 547 | setId_Widget(d->root, "root"); |
548 | init_Text(d->render); | 548 | init_Text(d->render); |
549 | #if defined (iPlatformApple) && !defined (iPlatformIOS) | ||
550 | setupApplication_MacOS(); | ||
551 | #endif | ||
552 | setupUserInterface_Window(d); | 549 | setupUserInterface_Window(d); |
553 | updateRootSize_Window_(d); | 550 | updateRootSize_Window_(d); |
554 | } | 551 | } |