diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2022-01-20 12:06:39 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2022-01-20 12:06:39 +0200 |
commit | 6da3bdb612e0ead07ff93f4f6dc5aef46c7b9c9e (patch) | |
tree | 4378984d744dc67b7453573524d5839b9ce7f9d5 /src/app.c | |
parent | d5169339b3454c80a6f2ed5f8cb937e5d5613fc0 (diff) | |
parent | 33816278c84fd7ac7e895f4111229c4ff4436b53 (diff) |
Merge branch 'dev' of skyjake.fi:gemini/lagrange into dev
Diffstat (limited to 'src/app.c')
-rw-r--r-- | src/app.c | 397 |
1 files changed, 292 insertions, 105 deletions
@@ -55,6 +55,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
55 | #include <the_Foundation/path.h> | 55 | #include <the_Foundation/path.h> |
56 | #include <the_Foundation/process.h> | 56 | #include <the_Foundation/process.h> |
57 | #include <the_Foundation/sortedarray.h> | 57 | #include <the_Foundation/sortedarray.h> |
58 | #include <the_Foundation/stringset.h> | ||
58 | #include <the_Foundation/time.h> | 59 | #include <the_Foundation/time.h> |
59 | #include <the_Foundation/version.h> | 60 | #include <the_Foundation/version.h> |
60 | #include <SDL.h> | 61 | #include <SDL.h> |
@@ -71,6 +72,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
71 | #if defined (iPlatformAppleMobile) | 72 | #if defined (iPlatformAppleMobile) |
72 | # include "ios.h" | 73 | # include "ios.h" |
73 | #endif | 74 | #endif |
75 | #if defined (iPlatformAndroidMobile) | ||
76 | #include <SDL_log.h> | ||
77 | #endif | ||
74 | #if defined (iPlatformMsys) | 78 | #if defined (iPlatformMsys) |
75 | # include "win32.h" | 79 | # include "win32.h" |
76 | #endif | 80 | #endif |
@@ -108,6 +112,7 @@ static const char *defaultDataDir_App_ = "~/config/settings/lagrange"; | |||
108 | static const char *prefsFileName_App_ = "prefs.cfg"; | 112 | static const char *prefsFileName_App_ = "prefs.cfg"; |
109 | static const char *oldStateFileName_App_ = "state.binary"; | 113 | static const char *oldStateFileName_App_ = "state.binary"; |
110 | static const char *stateFileName_App_ = "state.lgr"; | 114 | static const char *stateFileName_App_ = "state.lgr"; |
115 | static const char *tempStateFileName_App_ = "state.lgr.tmp"; | ||
111 | static const char *defaultDownloadDir_App_ = "~/Downloads"; | 116 | static const char *defaultDownloadDir_App_ = "~/Downloads"; |
112 | 117 | ||
113 | static const int idleThreshold_App_ = 1000; /* ms */ | 118 | static const int idleThreshold_App_ = 1000; /* ms */ |
@@ -115,6 +120,7 @@ static const int idleThreshold_App_ = 1000; /* ms */ | |||
115 | struct Impl_App { | 120 | struct Impl_App { |
116 | iCommandLine args; | 121 | iCommandLine args; |
117 | iString * execPath; | 122 | iString * execPath; |
123 | iStringSet * tempFilesPendingDeletion; | ||
118 | iMimeHooks * mimehooks; | 124 | iMimeHooks * mimehooks; |
119 | iGmCerts * certs; | 125 | iGmCerts * certs; |
120 | iVisited * visited; | 126 | iVisited * visited; |
@@ -127,6 +133,7 @@ struct Impl_App { | |||
127 | iBool isRunning; | 133 | iBool isRunning; |
128 | iBool isRunningUnderWindowSystem; | 134 | iBool isRunningUnderWindowSystem; |
129 | iBool isDarkSystemTheme; | 135 | iBool isDarkSystemTheme; |
136 | iBool isSuspended; | ||
130 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) | 137 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) |
131 | iBool isIdling; | 138 | iBool isIdling; |
132 | uint32_t lastEventTime; | 139 | uint32_t lastEventTime; |
@@ -164,7 +171,11 @@ struct Impl_Ticker { | |||
164 | 171 | ||
165 | static int cmp_Ticker_(const void *a, const void *b) { | 172 | static int cmp_Ticker_(const void *a, const void *b) { |
166 | const iTicker *elems[2] = { a, b }; | 173 | const iTicker *elems[2] = { a, b }; |
167 | return iCmp(elems[0]->context, elems[1]->context); | 174 | const int cmp = iCmp(elems[0]->context, elems[1]->context); |
175 | if (cmp) { | ||
176 | return cmp; | ||
177 | } | ||
178 | return iCmp((void *) elems[0]->callback, (void *) elems[1]->callback); | ||
168 | } | 179 | } |
169 | 180 | ||
170 | /*----------------------------------------------------------------------------------------------*/ | 181 | /*----------------------------------------------------------------------------------------------*/ |
@@ -237,6 +248,13 @@ static iString *serializePrefs_App_(const iApp *d) { | |||
237 | appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth); | 248 | appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth); |
238 | appendFormat_String(str, "linespacing.set arg:%f\n", d->prefs.lineSpacing); | 249 | appendFormat_String(str, "linespacing.set arg:%f\n", d->prefs.lineSpacing); |
239 | appendFormat_String(str, "returnkey.set arg:%d\n", d->prefs.returnKey); | 250 | appendFormat_String(str, "returnkey.set arg:%d\n", d->prefs.returnKey); |
251 | for (size_t i = 0; i < iElemCount(d->prefs.navbarActions); i++) { | ||
252 | appendFormat_String(str, "navbar.action.set arg:%d button:%d\n", d->prefs.navbarActions[i], i); | ||
253 | } | ||
254 | #if defined (iPlatformMobile) | ||
255 | appendFormat_String(str, "toolbar.action.set arg:%d button:0\n", d->prefs.toolbarActions[0]); | ||
256 | appendFormat_String(str, "toolbar.action.set arg:%d button:1\n", d->prefs.toolbarActions[1]); | ||
257 | #endif | ||
240 | iConstForEach(StringSet, fp, d->prefs.disabledFontPacks) { | 258 | iConstForEach(StringSet, fp, d->prefs.disabledFontPacks) { |
241 | appendFormat_String(str, "fontpack.disable id:%s\n", cstr_String(fp.value)); | 259 | appendFormat_String(str, "fontpack.disable id:%s\n", cstr_String(fp.value)); |
242 | } | 260 | } |
@@ -263,6 +281,7 @@ static iString *serializePrefs_App_(const iApp *d) { | |||
263 | { "prefs.bookmarks.addbottom", &d->prefs.addBookmarksToBottom }, | 281 | { "prefs.bookmarks.addbottom", &d->prefs.addBookmarksToBottom }, |
264 | { "prefs.archive.openindex", &d->prefs.openArchiveIndexPages }, | 282 | { "prefs.archive.openindex", &d->prefs.openArchiveIndexPages }, |
265 | { "prefs.font.warnmissing", &d->prefs.warnAboutMissingGlyphs }, | 283 | { "prefs.font.warnmissing", &d->prefs.warnAboutMissingGlyphs }, |
284 | { "prefs.blink", &d->prefs.blinkingCursor }, | ||
266 | }; | 285 | }; |
267 | iForIndices(i, boolPrefs) { | 286 | iForIndices(i, boolPrefs) { |
268 | appendFormat_String(str, "%s.changed arg:%d\n", boolPrefs[i].id, *boolPrefs[i].value); | 287 | appendFormat_String(str, "%s.changed arg:%d\n", boolPrefs[i].id, *boolPrefs[i].value); |
@@ -313,6 +332,9 @@ static const char *dataDir_App_(void) { | |||
313 | } | 332 | } |
314 | 333 | ||
315 | static const char *downloadDir_App_(void) { | 334 | static const char *downloadDir_App_(void) { |
335 | #if defined (iPlatformAndroidMobile) | ||
336 | return concatPath_CStr(SDL_AndroidGetInternalStoragePath(), "Downloads"); | ||
337 | #endif | ||
316 | #if defined (iPlatformLinux) || defined (iPlatformOther) | 338 | #if defined (iPlatformLinux) || defined (iPlatformOther) |
317 | /* Parse user-dirs.dirs using the `xdg-user-dir` tool. */ | 339 | /* Parse user-dirs.dirs using the `xdg-user-dir` tool. */ |
318 | iProcess *proc = iClob(new_Process()); | 340 | iProcess *proc = iClob(new_Process()); |
@@ -363,7 +385,7 @@ static void loadPrefs_App_(iApp *d) { | |||
363 | setUiScale_Window(get_Window(), argf_Command(cmd)); | 385 | setUiScale_Window(get_Window(), argf_Command(cmd)); |
364 | } | 386 | } |
365 | else if (equal_Command(cmd, "uilang")) { | 387 | else if (equal_Command(cmd, "uilang")) { |
366 | const char *id = cstr_Rangecc(range_Command(cmd, "id")); | 388 | const char *id = cstr_Command(cmd, "id"); |
367 | setCStr_String(&d->prefs.strings[uiLanguage_PrefsString], id); | 389 | setCStr_String(&d->prefs.strings[uiLanguage_PrefsString], id); |
368 | setCurrent_Lang(id); | 390 | setCurrent_Lang(id); |
369 | } | 391 | } |
@@ -385,6 +407,12 @@ static void loadPrefs_App_(iApp *d) { | |||
385 | insert_StringSet(d->prefs.disabledFontPacks, | 407 | insert_StringSet(d->prefs.disabledFontPacks, |
386 | collect_String(suffix_Command(cmd, "id"))); | 408 | collect_String(suffix_Command(cmd, "id"))); |
387 | } | 409 | } |
410 | #if defined (iPlatformAndroidMobile) | ||
411 | else if (equal_Command(cmd, "returnkey.set")) { | ||
412 | /* Hardcoded to avoid accidental presses of the virtual Return key. */ | ||
413 | d->prefs.returnKey = default_ReturnKeyBehavior; | ||
414 | } | ||
415 | #endif | ||
388 | #if !defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) | 416 | #if !defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) |
389 | else if (equal_Command(cmd, "downloads")) { | 417 | else if (equal_Command(cmd, "downloads")) { |
390 | continue; /* can't change downloads directory */ | 418 | continue; /* can't change downloads directory */ |
@@ -410,11 +438,13 @@ static void loadPrefs_App_(iApp *d) { | |||
410 | iRelease(f); | 438 | iRelease(f); |
411 | /* Upgrade checks. */ | 439 | /* Upgrade checks. */ |
412 | if (cmp_Version(&upgradedFromAppVersion, &(iVersion){ 1, 8, 0 }) < 0) { | 440 | if (cmp_Version(&upgradedFromAppVersion, &(iVersion){ 1, 8, 0 }) < 0) { |
441 | #if !defined (iPlatformAppleMobile) && !defined (iPlatformAndroidMobile) | ||
413 | /* When upgrading to v1.8.0, the old hardcoded font library is gone and that means | 442 | /* When upgrading to v1.8.0, the old hardcoded font library is gone and that means |
414 | UI strings may not have the right fonts available for the UI to remain | 443 | UI strings may not have the right fonts available for the UI to remain |
415 | usable. */ | 444 | usable. */ |
416 | postCommandf_App("uilang id:en"); | 445 | postCommandf_App("uilang id:en"); |
417 | postCommand_App("~fontpack.suggest.classic"); | 446 | postCommand_App("~fontpack.suggest.classic"); |
447 | #endif | ||
418 | } | 448 | } |
419 | #if !defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 449 | #if !defined (LAGRANGE_ENABLE_CUSTOM_FRAME) |
420 | d->prefs.customFrame = iFalse; | 450 | d->prefs.customFrame = iFalse; |
@@ -505,7 +535,7 @@ static iBool loadState_App_(iApp *d) { | |||
505 | if (flags & 8) { | 535 | if (flags & 8) { |
506 | postCommand_Widget(sidebar2, "feeds.mode arg:%d", unread_FeedsMode); | 536 | postCommand_Widget(sidebar2, "feeds.mode arg:%d", unread_FeedsMode); |
507 | } | 537 | } |
508 | if (deviceType_App() != phone_AppDeviceType) { | 538 | if (deviceType_App() == desktop_AppDeviceType) { |
509 | setWidth_SidebarWidget(sidebar, widths[0]); | 539 | setWidth_SidebarWidget(sidebar, widths[0]); |
510 | setWidth_SidebarWidget(sidebar2, widths[1]); | 540 | setWidth_SidebarWidget(sidebar2, widths[1]); |
511 | if (flags & 1) postCommand_Root(root, "sidebar.toggle noanim:1"); | 541 | if (flags & 1) postCommand_Root(root, "sidebar.toggle noanim:1"); |
@@ -561,7 +591,7 @@ static void saveState_App_(const iApp *d) { | |||
561 | navigation history, cached content) and depends closely on the widget | 591 | navigation history, cached content) and depends closely on the widget |
562 | tree. The data is largely not reorderable and should not be modified | 592 | tree. The data is largely not reorderable and should not be modified |
563 | by the user manually. */ | 593 | by the user manually. */ |
564 | iFile *f = newCStr_File(concatPath_CStr(dataDir_App_(), stateFileName_App_)); | 594 | iFile *f = newCStr_File(concatPath_CStr(dataDir_App_(), tempStateFileName_App_)); |
565 | if (open_File(f, writeOnly_FileMode)) { | 595 | if (open_File(f, writeOnly_FileMode)) { |
566 | writeData_File(f, magicState_App_, 4); | 596 | writeData_File(f, magicState_App_, 4); |
567 | writeU32_File(f, latest_FileVersion); /* version */ | 597 | writeU32_File(f, latest_FileVersion); /* version */ |
@@ -603,11 +633,19 @@ static void saveState_App_(const iApp *d) { | |||
603 | write8_File(f, flags); | 633 | write8_File(f, flags); |
604 | serializeState_DocumentWidget(i.object, stream_File(f)); | 634 | serializeState_DocumentWidget(i.object, stream_File(f)); |
605 | } | 635 | } |
636 | iRelease(f); | ||
606 | } | 637 | } |
607 | else { | 638 | else { |
639 | iRelease(f); | ||
608 | fprintf(stderr, "[App] failed to save state: %s\n", strerror(errno)); | 640 | fprintf(stderr, "[App] failed to save state: %s\n", strerror(errno)); |
641 | return; | ||
609 | } | 642 | } |
610 | iRelease(f); | 643 | /* Copy it over to the real file. This avoids truncation if the app for any reason crashes |
644 | before the state file is fully written. */ | ||
645 | const char *tempName = concatPath_CStr(dataDir_App_(), tempStateFileName_App_); | ||
646 | const char *finalName = concatPath_CStr(dataDir_App_(), stateFileName_App_); | ||
647 | remove(finalName); | ||
648 | rename(tempName, finalName); | ||
611 | } | 649 | } |
612 | 650 | ||
613 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) | 651 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) |
@@ -678,18 +716,22 @@ static void communicateWithRunningInstance_App_(iApp *d, iProcessId instance, | |||
678 | appendCStr_String(cmds, "tabs.new\n"); | 716 | appendCStr_String(cmds, "tabs.new\n"); |
679 | requestRaise = iTrue; | 717 | requestRaise = iTrue; |
680 | } | 718 | } |
719 | iBool gotResult = iFalse; | ||
681 | if (!isEmpty_String(cmds)) { | 720 | if (!isEmpty_String(cmds)) { |
682 | iString *result = communicate_Ipc(cmds, requestRaise); | 721 | iString *result = communicate_Ipc(cmds, requestRaise); |
683 | if (result) { | 722 | if (result) { |
684 | fwrite(cstr_String(result), 1, size_String(result), stdout); | 723 | fwrite(cstr_String(result), 1, size_String(result), stdout); |
685 | fflush(stdout); | 724 | fflush(stdout); |
725 | if (!isEmpty_String(result)) { | ||
726 | gotResult = iTrue; | ||
727 | } | ||
686 | } | 728 | } |
687 | delete_String(result); | 729 | delete_String(result); |
688 | } | 730 | } |
689 | iUnused(instance); | 731 | iUnused(instance); |
690 | // else { | 732 | if (!gotResult) { |
691 | // printf("Lagrange already running (PID %d)\n", instance); | 733 | printf("Commands sent to Lagrange process %d\n", instance); |
692 | // } | 734 | } |
693 | terminate_App_(0); | 735 | terminate_App_(0); |
694 | } | 736 | } |
695 | #endif /* defined (LAGRANGE_ENABLE_IPC) */ | 737 | #endif /* defined (LAGRANGE_ENABLE_IPC) */ |
@@ -714,6 +756,8 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
714 | d->isRunningUnderWindowSystem = iTrue; | 756 | d->isRunningUnderWindowSystem = iTrue; |
715 | #endif | 757 | #endif |
716 | d->isDarkSystemTheme = iTrue; /* will be updated by system later on, if supported */ | 758 | d->isDarkSystemTheme = iTrue; /* will be updated by system later on, if supported */ |
759 | d->isSuspended = iFalse; | ||
760 | d->tempFilesPendingDeletion = new_StringSet(); | ||
717 | init_CommandLine(&d->args, argc, argv); | 761 | init_CommandLine(&d->args, argc, argv); |
718 | /* Where was the app started from? We ask SDL first because the command line alone | 762 | /* Where was the app started from? We ask SDL first because the command line alone |
719 | cannot be relied on (behavior differs depending on OS). */ { | 763 | cannot be relied on (behavior differs depending on OS). */ { |
@@ -920,6 +964,8 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
920 | if (!loadState_App_(d)) { | 964 | if (!loadState_App_(d)) { |
921 | postCommand_Root(NULL, "open url:about:help"); | 965 | postCommand_Root(NULL, "open url:about:help"); |
922 | } | 966 | } |
967 | postCommand_App("~navbar.actions.changed"); | ||
968 | postCommand_App("~toolbar.actions.changed"); | ||
923 | postCommand_Root(NULL, "~window.unfreeze"); | 969 | postCommand_Root(NULL, "~window.unfreeze"); |
924 | postCommand_Root(NULL, "font.reset"); | 970 | postCommand_Root(NULL, "font.reset"); |
925 | d->autoReloadTimer = SDL_AddTimer(60 * 1000, postAutoReloadCommand_App_, NULL); | 971 | d->autoReloadTimer = SDL_AddTimer(60 * 1000, postAutoReloadCommand_App_, NULL); |
@@ -986,6 +1032,11 @@ static void deinit_App(iApp *d) { | |||
986 | deinit_Periodic(&d->periodic); | 1032 | deinit_Periodic(&d->periodic); |
987 | deinit_Lang(); | 1033 | deinit_Lang(); |
988 | iRecycle(); | 1034 | iRecycle(); |
1035 | /* Delete all temporary files created while running. */ | ||
1036 | iConstForEach(StringSet, tmp, d->tempFilesPendingDeletion) { | ||
1037 | remove(cstr_String(tmp.value)); | ||
1038 | } | ||
1039 | iRelease(d->tempFilesPendingDeletion); | ||
989 | } | 1040 | } |
990 | 1041 | ||
991 | const iString *execPath_App(void) { | 1042 | const iString *execPath_App(void) { |
@@ -1000,7 +1051,7 @@ const iString *downloadDir_App(void) { | |||
1000 | return collect_String(cleaned_Path(&app_.prefs.strings[downloadDir_PrefsString])); | 1051 | return collect_String(cleaned_Path(&app_.prefs.strings[downloadDir_PrefsString])); |
1001 | } | 1052 | } |
1002 | 1053 | ||
1003 | const iString *downloadPathForUrl_App(const iString *url, const iString *mime) { | 1054 | const iString *fileNameForUrl_App(const iString *url, const iString *mime) { |
1004 | /* Figure out a file name from the URL. */ | 1055 | /* Figure out a file name from the URL. */ |
1005 | iUrl parts; | 1056 | iUrl parts; |
1006 | init_Url(&parts, url); | 1057 | init_Url(&parts, url); |
@@ -1026,22 +1077,27 @@ const iString *downloadPathForUrl_App(const iString *url, const iString *mime) { | |||
1026 | } | 1077 | } |
1027 | } | 1078 | } |
1028 | if (startsWith_String(name, "~")) { | 1079 | if (startsWith_String(name, "~")) { |
1029 | /* This would be interpreted as a reference to a home directory. */ | 1080 | /* This might be interpreted as a reference to a home directory. */ |
1030 | remove_Block(&name->chars, 0, 1); | 1081 | remove_Block(&name->chars, 0, 1); |
1031 | } | 1082 | } |
1032 | iString *savePath = concat_Path(downloadDir_App(), name); | 1083 | if (lastIndexOfCStr_String(name, ".") == iInvalidPos) { |
1033 | if (lastIndexOfCStr_String(savePath, ".") == iInvalidPos) { | 1084 | /* TODO: Needs the inverse of `mediaTypeFromFileExtension_String()`. */ |
1034 | /* No extension specified in URL. */ | 1085 | /* No extension specified in URL. */ |
1035 | if (startsWith_String(mime, "text/gemini")) { | 1086 | if (startsWith_String(mime, "text/gemini")) { |
1036 | appendCStr_String(savePath, ".gmi"); | 1087 | appendCStr_String(name, ".gmi"); |
1037 | } | 1088 | } |
1038 | else if (startsWith_String(mime, "text/")) { | 1089 | else if (startsWith_String(mime, "text/")) { |
1039 | appendCStr_String(savePath, ".txt"); | 1090 | appendCStr_String(name, ".txt"); |
1040 | } | 1091 | } |
1041 | else if (startsWith_String(mime, "image/")) { | 1092 | else if (startsWith_String(mime, "image/")) { |
1042 | appendCStr_String(savePath, cstr_String(mime) + 6); | 1093 | appendCStr_String(name, cstr_String(mime) + 6); |
1043 | } | 1094 | } |
1044 | } | 1095 | } |
1096 | return name; | ||
1097 | } | ||
1098 | |||
1099 | const iString *downloadPathForUrl_App(const iString *url, const iString *mime) { | ||
1100 | iString *savePath = concat_Path(downloadDir_App(), fileNameForUrl_App(url, mime)); | ||
1045 | if (fileExists_FileInfo(savePath)) { | 1101 | if (fileExists_FileInfo(savePath)) { |
1046 | /* Make it unique. */ | 1102 | /* Make it unique. */ |
1047 | iDate now; | 1103 | iDate now; |
@@ -1056,6 +1112,22 @@ const iString *downloadPathForUrl_App(const iString *url, const iString *mime) { | |||
1056 | return collect_String(savePath); | 1112 | return collect_String(savePath); |
1057 | } | 1113 | } |
1058 | 1114 | ||
1115 | const iString *temporaryPathForUrl_App(const iString *url, const iString *mime) { | ||
1116 | iApp *d = &app_; | ||
1117 | #if defined (P_tmpdir) | ||
1118 | iString * tmpPath = collectNew_String(); | ||
1119 | const iRangecc tmpDir = range_CStr(P_tmpdir); | ||
1120 | #else | ||
1121 | iString * tmpPath = collectNewCStr_String(tmpnam(NULL)); | ||
1122 | const iRangecc tmpDir = dirName_Path(tmpPath); | ||
1123 | #endif | ||
1124 | set_String( | ||
1125 | tmpPath, | ||
1126 | collect_String(concat_Path(collectNewRange_String(tmpDir), fileNameForUrl_App(url, mime)))); | ||
1127 | insert_StringSet(d->tempFilesPendingDeletion, tmpPath); /* deleted in `deinit_App` */ | ||
1128 | return tmpPath; | ||
1129 | } | ||
1130 | |||
1059 | const iString *debugInfo_App(void) { | 1131 | const iString *debugInfo_App(void) { |
1060 | extern char **environ; /* The environment variables. */ | 1132 | extern char **environ; /* The environment variables. */ |
1061 | iApp *d = &app_; | 1133 | iApp *d = &app_; |
@@ -1175,9 +1247,6 @@ iBool findCachedContent_App(const iString *url, iString *mime_out, iBlock *data_ | |||
1175 | #endif | 1247 | #endif |
1176 | 1248 | ||
1177 | iLocalDef iBool isWaitingAllowed_App_(iApp *d) { | 1249 | iLocalDef iBool isWaitingAllowed_App_(iApp *d) { |
1178 | if (!isEmpty_Periodic(&d->periodic)) { | ||
1179 | return iFalse; | ||
1180 | } | ||
1181 | if (d->warmupFrames > 0) { | 1250 | if (d->warmupFrames > 0) { |
1182 | return iFalse; | 1251 | return iFalse; |
1183 | } | 1252 | } |
@@ -1191,16 +1260,19 @@ iLocalDef iBool isWaitingAllowed_App_(iApp *d) { | |||
1191 | 1260 | ||
1192 | static iBool nextEvent_App_(iApp *d, enum iAppEventMode eventMode, SDL_Event *event) { | 1261 | static iBool nextEvent_App_(iApp *d, enum iAppEventMode eventMode, SDL_Event *event) { |
1193 | if (eventMode == waitForNewEvents_AppEventMode && isWaitingAllowed_App_(d)) { | 1262 | if (eventMode == waitForNewEvents_AppEventMode && isWaitingAllowed_App_(d)) { |
1194 | /* If there are periodic commands pending, wait only for a short while. */ | ||
1195 | if (!isEmpty_Periodic(&d->periodic)) { | ||
1196 | return SDL_WaitEventTimeout(event, 500); | ||
1197 | } | ||
1198 | /* We may be allowed to block here until an event comes in. */ | 1263 | /* We may be allowed to block here until an event comes in. */ |
1199 | if (isWaitingAllowed_App_(d)) { | 1264 | if (isWaitingAllowed_App_(d)) { |
1200 | return SDL_WaitEvent(event); | 1265 | return SDL_WaitEvent(event); |
1201 | } | 1266 | } |
1202 | } | 1267 | } |
1268 | /* SDL regression circa 2.0.18? SDL_PollEvent() doesn't always return | ||
1269 | events posted immediately beforehand. Waiting with a very short timeout | ||
1270 | seems to work better. */ | ||
1271 | #if defined (iPlatformLinux) && SDL_VERSION_ATLEAST(2, 0, 18) | ||
1272 | return SDL_WaitEventTimeout(event, 1); | ||
1273 | #else | ||
1203 | return SDL_PollEvent(event); | 1274 | return SDL_PollEvent(event); |
1275 | #endif | ||
1204 | } | 1276 | } |
1205 | 1277 | ||
1206 | static iPtrArray *listWindows_App_(const iApp *d, iPtrArray *windows) { | 1278 | static iPtrArray *listWindows_App_(const iApp *d, iPtrArray *windows) { |
@@ -1239,6 +1311,7 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1239 | break; | 1311 | break; |
1240 | case SDL_APP_WILLENTERFOREGROUND: | 1312 | case SDL_APP_WILLENTERFOREGROUND: |
1241 | invalidate_Window(as_Window(d->window)); | 1313 | invalidate_Window(as_Window(d->window)); |
1314 | d->isSuspended = iFalse; | ||
1242 | break; | 1315 | break; |
1243 | case SDL_APP_DIDENTERFOREGROUND: | 1316 | case SDL_APP_DIDENTERFOREGROUND: |
1244 | gotEvents = iTrue; | 1317 | gotEvents = iTrue; |
@@ -1256,6 +1329,7 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1256 | setFreezeDraw_MainWindow(d->window, iTrue); | 1329 | setFreezeDraw_MainWindow(d->window, iTrue); |
1257 | savePrefs_App_(d); | 1330 | savePrefs_App_(d); |
1258 | saveState_App_(d); | 1331 | saveState_App_(d); |
1332 | d->isSuspended = iTrue; | ||
1259 | break; | 1333 | break; |
1260 | case SDL_APP_TERMINATING: | 1334 | case SDL_APP_TERMINATING: |
1261 | setFreezeDraw_MainWindow(d->window, iTrue); | 1335 | setFreezeDraw_MainWindow(d->window, iTrue); |
@@ -1284,6 +1358,10 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1284 | break; | 1358 | break; |
1285 | } | 1359 | } |
1286 | default: { | 1360 | default: { |
1361 | if (ev.type == SDL_USEREVENT && ev.user.code == periodic_UserEventCode) { | ||
1362 | dispatchCommands_Periodic(&d->periodic); | ||
1363 | continue; | ||
1364 | } | ||
1287 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) | 1365 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) |
1288 | if (ev.type == SDL_USEREVENT && ev.user.code == asleep_UserEventCode) { | 1366 | if (ev.type == SDL_USEREVENT && ev.user.code == asleep_UserEventCode) { |
1289 | if (SDL_GetTicks() - d->lastEventTime > idleThreshold_App_ && | 1367 | if (SDL_GetTicks() - d->lastEventTime > idleThreshold_App_ && |
@@ -1323,28 +1401,6 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1323 | #endif | 1401 | #endif |
1324 | /* Scroll events may be per-pixel or mouse wheel steps. */ | 1402 | /* Scroll events may be per-pixel or mouse wheel steps. */ |
1325 | if (ev.type == SDL_MOUSEWHEEL) { | 1403 | if (ev.type == SDL_MOUSEWHEEL) { |
1326 | #if defined (iPlatformAppleDesktop) | ||
1327 | /* On macOS, we handle both trackpad and mouse events. We expect SDL to identify | ||
1328 | which device is sending the event. */ | ||
1329 | if (ev.wheel.which == 0) { | ||
1330 | /* Trackpad with precise scrolling w/inertia (points). */ | ||
1331 | setPerPixel_MouseWheelEvent(&ev.wheel, iTrue); | ||
1332 | ev.wheel.x *= -d->window->base.pixelRatio; | ||
1333 | ev.wheel.y *= d->window->base.pixelRatio; | ||
1334 | /* Only scroll on one axis at a time. */ | ||
1335 | if (iAbs(ev.wheel.x) > iAbs(ev.wheel.y)) { | ||
1336 | ev.wheel.y = 0; | ||
1337 | } | ||
1338 | else { | ||
1339 | ev.wheel.x = 0; | ||
1340 | } | ||
1341 | } | ||
1342 | else { | ||
1343 | /* Disregard wheel acceleration applied by the OS. */ | ||
1344 | ev.wheel.x = -ev.wheel.x; | ||
1345 | ev.wheel.y = iSign(ev.wheel.y); | ||
1346 | } | ||
1347 | #endif | ||
1348 | #if defined (iPlatformMsys) | 1404 | #if defined (iPlatformMsys) |
1349 | ev.wheel.x = -ev.wheel.x; | 1405 | ev.wheel.x = -ev.wheel.x; |
1350 | #endif | 1406 | #endif |
@@ -1460,10 +1516,10 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1460 | deinit_PtrArray(&windows); | 1516 | deinit_PtrArray(&windows); |
1461 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) | 1517 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) |
1462 | if (d->isIdling && !gotEvents) { | 1518 | if (d->isIdling && !gotEvents) { |
1463 | /* This is where we spend most of our time when idle. 60 Hz still quite a lot but we | 1519 | /* This is where we spend most of our time when idle. 30 Hz still quite a lot but we |
1464 | can't wait too long after the user tries to interact again with the app. In any | 1520 | can't wait too long after the user tries to interact again with the app. In any |
1465 | case, on macOS SDL_WaitEvent() seems to use 10x more CPU time than sleeping. */ | 1521 | case, on iOS SDL_WaitEvent() seems to use 10x more CPU time than sleeping (2.0.18). */ |
1466 | SDL_Delay(1000 / 60); | 1522 | SDL_Delay(1000 / 30); |
1467 | } | 1523 | } |
1468 | #endif | 1524 | #endif |
1469 | backToMainLoop:; | 1525 | backToMainLoop:; |
@@ -1478,6 +1534,12 @@ static void runTickers_App_(iApp *d) { | |||
1478 | d->lastTickerTime = 0; | 1534 | d->lastTickerTime = 0; |
1479 | return; | 1535 | return; |
1480 | } | 1536 | } |
1537 | iForIndices(i, d->window->base.roots) { | ||
1538 | iRoot *root = d->window->base.roots[i]; | ||
1539 | if (root) { | ||
1540 | root->didAnimateVisualOffsets = iFalse; | ||
1541 | } | ||
1542 | } | ||
1481 | /* Tickers may add themselves again, so we'll run off a copy. */ | 1543 | /* Tickers may add themselves again, so we'll run off a copy. */ |
1482 | iSortedArray *pending = copy_SortedArray(&d->tickers); | 1544 | iSortedArray *pending = copy_SortedArray(&d->tickers); |
1483 | clear_SortedArray(&d->tickers); | 1545 | clear_SortedArray(&d->tickers); |
@@ -1494,6 +1556,12 @@ static void runTickers_App_(iApp *d) { | |||
1494 | if (isEmpty_SortedArray(&d->tickers)) { | 1556 | if (isEmpty_SortedArray(&d->tickers)) { |
1495 | d->lastTickerTime = 0; | 1557 | d->lastTickerTime = 0; |
1496 | } | 1558 | } |
1559 | // iForIndices(i, d->window->base.roots) { | ||
1560 | // iRoot *root = d->window->base.roots[i]; | ||
1561 | // if (root) { | ||
1562 | // notifyVisualOffsetChange_Root(root); | ||
1563 | // } | ||
1564 | // } | ||
1497 | } | 1565 | } |
1498 | 1566 | ||
1499 | static int resizeWatcher_(void *user, SDL_Event *event) { | 1567 | static int resizeWatcher_(void *user, SDL_Event *event) { |
@@ -1526,7 +1594,6 @@ static int run_App_(iApp *d) { | |||
1526 | SDL_AddEventWatch(resizeWatcher_, d); /* redraw window during resizing */ | 1594 | SDL_AddEventWatch(resizeWatcher_, d); /* redraw window during resizing */ |
1527 | #endif | 1595 | #endif |
1528 | while (d->isRunning) { | 1596 | while (d->isRunning) { |
1529 | dispatchCommands_Periodic(&d->periodic); | ||
1530 | processEvents_App(waitForNewEvents_AppEventMode); | 1597 | processEvents_App(waitForNewEvents_AppEventMode); |
1531 | runTickers_App_(d); | 1598 | runTickers_App_(d); |
1532 | refresh_App(); | 1599 | refresh_App(); |
@@ -1680,13 +1747,20 @@ void postCommand_Root(iRoot *d, const char *command) { | |||
1680 | ev.user.data1 = strdup(command); | 1747 | ev.user.data1 = strdup(command); |
1681 | ev.user.data2 = d; /* all events are root-specific */ | 1748 | ev.user.data2 = d; /* all events are root-specific */ |
1682 | SDL_PushEvent(&ev); | 1749 | SDL_PushEvent(&ev); |
1750 | iWindow *win = get_Window(); | ||
1751 | #if defined (iPlatformAndroid) | ||
1752 | SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%s[command] {%d} %s", | ||
1753 | app_.isLoadingPrefs ? "[Prefs] " : "", | ||
1754 | (d == NULL || win == NULL ? 0 : d == win->roots[0] ? 1 : 2), | ||
1755 | command); | ||
1756 | #else | ||
1683 | if (app_.commandEcho) { | 1757 | if (app_.commandEcho) { |
1684 | iWindow *win = get_Window(); | ||
1685 | printf("%s[command] {%d} %s\n", | 1758 | printf("%s[command] {%d} %s\n", |
1686 | app_.isLoadingPrefs ? "[Prefs] " : "", | 1759 | app_.isLoadingPrefs ? "[Prefs] " : "", |
1687 | (d == NULL || win == NULL ? 0 : d == win->roots[0] ? 1 : 2), | 1760 | (d == NULL || win == NULL ? 0 : d == win->roots[0] ? 1 : 2), |
1688 | command); fflush(stdout); | 1761 | command); fflush(stdout); |
1689 | } | 1762 | } |
1763 | #endif | ||
1690 | } | 1764 | } |
1691 | 1765 | ||
1692 | void postCommandf_Root(iRoot *d, const char *command, ...) { | 1766 | void postCommandf_Root(iRoot *d, const char *command, ...) { |
@@ -1823,6 +1897,12 @@ static void updatePrefsPinSplitButtons_(iWidget *d, int value) { | |||
1823 | } | 1897 | } |
1824 | } | 1898 | } |
1825 | 1899 | ||
1900 | static void updatePrefsToolBarActionButton_(iWidget *prefs, int buttonIndex, int action) { | ||
1901 | updateDropdownSelection_LabelWidget( | ||
1902 | findChild_Widget(prefs, format_CStr("prefs.toolbaraction%d", buttonIndex + 1)), | ||
1903 | format_CStr(" arg:%d button:%d", action, buttonIndex)); | ||
1904 | } | ||
1905 | |||
1826 | static void updateScrollSpeedButtons_(iWidget *d, enum iScrollType type, const int value) { | 1906 | static void updateScrollSpeedButtons_(iWidget *d, enum iScrollType type, const int value) { |
1827 | const char *typeStr = (type == mouse_ScrollType ? "mouse" : "keyboard"); | 1907 | const char *typeStr = (type == mouse_ScrollType ? "mouse" : "keyboard"); |
1828 | for (int i = 0; i <= 40; i++) { | 1908 | for (int i = 0; i <= 40; i++) { |
@@ -1913,6 +1993,10 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { | |||
1913 | format_CStr("returnkey.set arg:%d", arg_Command(cmd))); | 1993 | format_CStr("returnkey.set arg:%d", arg_Command(cmd))); |
1914 | return iFalse; | 1994 | return iFalse; |
1915 | } | 1995 | } |
1996 | else if (equal_Command(cmd, "toolbar.action.set")) { | ||
1997 | updatePrefsToolBarActionButton_(d, argLabel_Command(cmd, "button"), arg_Command(cmd)); | ||
1998 | return iFalse; | ||
1999 | } | ||
1916 | else if (equal_Command(cmd, "pinsplit.set")) { | 2000 | else if (equal_Command(cmd, "pinsplit.set")) { |
1917 | updatePrefsPinSplitButtons_(d, arg_Command(cmd)); | 2001 | updatePrefsPinSplitButtons_(d, arg_Command(cmd)); |
1918 | return iFalse; | 2002 | return iFalse; |
@@ -1957,8 +2041,11 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { | |||
1957 | } | 2041 | } |
1958 | } | 2042 | } |
1959 | else if (equalWidget_Command(cmd, d, "input.resized")) { | 2043 | else if (equalWidget_Command(cmd, d, "input.resized")) { |
1960 | updatePreferencesLayout_Widget(d); | 2044 | if (!d->root->pendingArrange) { |
1961 | return iFalse; | 2045 | d->root->pendingArrange = iTrue; |
2046 | postCommand_Root(d->root, "root.arrange"); | ||
2047 | } | ||
2048 | return iTrue; | ||
1962 | } | 2049 | } |
1963 | return iFalse; | 2050 | return iFalse; |
1964 | } | 2051 | } |
@@ -2176,6 +2263,7 @@ iBool handleCommand_App(const char *cmd) { | |||
2176 | return iTrue; | 2263 | return iTrue; |
2177 | } | 2264 | } |
2178 | else if (equal_Command(cmd, "fontpack.suggest.classic")) { | 2265 | else if (equal_Command(cmd, "fontpack.suggest.classic")) { |
2266 | /* TODO: Don't use this when system fonts are accessible. */ | ||
2179 | if (!isInstalled_Fonts("classic-set") && !isInstalled_Fonts("cjk")) { | 2267 | if (!isInstalled_Fonts("classic-set") && !isInstalled_Fonts("cjk")) { |
2180 | makeQuestion_Widget( | 2268 | makeQuestion_Widget( |
2181 | uiHeading_ColorEscape "${heading.fontpack.classic}", | 2269 | uiHeading_ColorEscape "${heading.fontpack.classic}", |
@@ -2208,6 +2296,22 @@ iBool handleCommand_App(const char *cmd) { | |||
2208 | } | 2296 | } |
2209 | return iTrue; | 2297 | return iTrue; |
2210 | } | 2298 | } |
2299 | else if (equal_Command(cmd, "navbar.action.set")) { | ||
2300 | d->prefs.navbarActions[iClamp(argLabel_Command(cmd, "button"), 0, maxNavbarActions_Prefs - 1)] = | ||
2301 | iClamp(arg_Command(cmd), 0, max_ToolbarAction - 1); | ||
2302 | if (!isFrozen) { | ||
2303 | postCommand_App("~navbar.actions.changed"); | ||
2304 | } | ||
2305 | return iTrue; | ||
2306 | } | ||
2307 | else if (equal_Command(cmd, "toolbar.action.set")) { | ||
2308 | d->prefs.toolbarActions[iClamp(argLabel_Command(cmd, "button"), 0, 1)] = | ||
2309 | iClamp(arg_Command(cmd), 0, max_ToolbarAction - 1); | ||
2310 | if (!isFrozen) { | ||
2311 | postCommand_App("~toolbar.actions.changed"); | ||
2312 | } | ||
2313 | return iTrue; | ||
2314 | } | ||
2211 | else if (equal_Command(cmd, "translation.languages")) { | 2315 | else if (equal_Command(cmd, "translation.languages")) { |
2212 | d->prefs.langFrom = argLabel_Command(cmd, "from"); | 2316 | d->prefs.langFrom = argLabel_Command(cmd, "from"); |
2213 | d->prefs.langTo = argLabel_Command(cmd, "to"); | 2317 | d->prefs.langTo = argLabel_Command(cmd, "to"); |
@@ -2229,6 +2333,9 @@ iBool handleCommand_App(const char *cmd) { | |||
2229 | (argLabel_Command(cmd, "axis") ? vertical_WindowSplit : 0) | (arg_Command(cmd) << 1); | 2333 | (argLabel_Command(cmd, "axis") ? vertical_WindowSplit : 0) | (arg_Command(cmd) << 1); |
2230 | const char *url = suffixPtr_Command(cmd, "url"); | 2334 | const char *url = suffixPtr_Command(cmd, "url"); |
2231 | setCStr_String(d->window->pendingSplitUrl, url ? url : ""); | 2335 | setCStr_String(d->window->pendingSplitUrl, url ? url : ""); |
2336 | if (hasLabel_Command(cmd, "origin")) { | ||
2337 | set_String(d->window->pendingSplitOrigin, string_Command(cmd, "origin")); | ||
2338 | } | ||
2232 | postRefresh_App(); | 2339 | postRefresh_App(); |
2233 | return iTrue; | 2340 | return iTrue; |
2234 | } | 2341 | } |
@@ -2587,6 +2694,10 @@ iBool handleCommand_App(const char *cmd) { | |||
2587 | d->prefs.uiAnimations = arg_Command(cmd) != 0; | 2694 | d->prefs.uiAnimations = arg_Command(cmd) != 0; |
2588 | return iTrue; | 2695 | return iTrue; |
2589 | } | 2696 | } |
2697 | else if (equal_Command(cmd, "prefs.blink.changed")) { | ||
2698 | d->prefs.blinkingCursor = arg_Command(cmd) != 0; | ||
2699 | return iTrue; | ||
2700 | } | ||
2590 | else if (equal_Command(cmd, "prefs.time.24h.changed")) { | 2701 | else if (equal_Command(cmd, "prefs.time.24h.changed")) { |
2591 | d->prefs.time24h = arg_Command(cmd) != 0; | 2702 | d->prefs.time24h = arg_Command(cmd) != 0; |
2592 | return iTrue; | 2703 | return iTrue; |
@@ -2642,7 +2753,9 @@ iBool handleCommand_App(const char *cmd) { | |||
2642 | } | 2753 | } |
2643 | #endif | 2754 | #endif |
2644 | else if (equal_Command(cmd, "downloads.open")) { | 2755 | else if (equal_Command(cmd, "downloads.open")) { |
2645 | postCommandf_App("open url:%s", cstrCollect_String(makeFileUrl_String(downloadDir_App()))); | 2756 | postCommandf_App("open newtab:%d url:%s", |
2757 | argLabel_Command(cmd, "newtab"), | ||
2758 | cstrCollect_String(makeFileUrl_String(downloadDir_App()))); | ||
2646 | return iTrue; | 2759 | return iTrue; |
2647 | } | 2760 | } |
2648 | else if (equal_Command(cmd, "ca.file")) { | 2761 | else if (equal_Command(cmd, "ca.file")) { |
@@ -2673,14 +2786,29 @@ iBool handleCommand_App(const char *cmd) { | |||
2673 | } | 2786 | } |
2674 | return iTrue; | 2787 | return iTrue; |
2675 | } | 2788 | } |
2789 | else if (equal_Command(cmd, "reveal")) { | ||
2790 | const iString *path = NULL; | ||
2791 | if (hasLabel_Command(cmd, "path")) { | ||
2792 | path = suffix_Command(cmd, "path"); | ||
2793 | } | ||
2794 | else if (hasLabel_Command(cmd, "url")) { | ||
2795 | path = collect_String(localFilePathFromUrl_String(suffix_Command(cmd, "url"))); | ||
2796 | } | ||
2797 | if (path) { | ||
2798 | revealPath_App(path); | ||
2799 | } | ||
2800 | return iTrue; | ||
2801 | } | ||
2676 | else if (equal_Command(cmd, "open")) { | 2802 | else if (equal_Command(cmd, "open")) { |
2677 | const char *urlArg = suffixPtr_Command(cmd, "url"); | 2803 | const char *urlArg = suffixPtr_Command(cmd, "url"); |
2678 | if (!urlArg) { | 2804 | if (!urlArg) { |
2679 | return iTrue; /* invalid command */ | 2805 | return iTrue; /* invalid command */ |
2680 | } | 2806 | } |
2681 | iString *url = collectNewCStr_String(urlArg); | 2807 | if (findWidget_App("prefs")) { |
2682 | const iBool noProxy = argLabel_Command(cmd, "noproxy") != 0; | 2808 | postCommand_App("prefs.dismiss"); |
2683 | const iBool fromSidebar = argLabel_Command(cmd, "fromsidebar") != 0; | 2809 | } |
2810 | iString *url = collectNewCStr_String(urlArg); | ||
2811 | const iBool noProxy = argLabel_Command(cmd, "noproxy") != 0; | ||
2684 | iUrl parts; | 2812 | iUrl parts; |
2685 | init_Url(&parts, url); | 2813 | init_Url(&parts, url); |
2686 | if (equal_Rangecc(parts.scheme, "about") && equal_Rangecc(parts.path, "command") && | 2814 | if (equal_Rangecc(parts.scheme, "about") && equal_Rangecc(parts.path, "command") && |
@@ -2711,12 +2839,23 @@ iBool handleCommand_App(const char *cmd) { | |||
2711 | openInDefaultBrowser_App(url); | 2839 | openInDefaultBrowser_App(url); |
2712 | return iTrue; | 2840 | return iTrue; |
2713 | } | 2841 | } |
2842 | iDocumentWidget *doc = document_Command(cmd); | ||
2843 | iDocumentWidget *origin = doc; | ||
2844 | if (hasLabel_Command(cmd, "origin")) { | ||
2845 | iDocumentWidget *cmdOrig = findWidget_App(cstr_Command(cmd, "origin")); | ||
2846 | if (cmdOrig) { | ||
2847 | origin = cmdOrig; | ||
2848 | } | ||
2849 | } | ||
2714 | const int newTab = argLabel_Command(cmd, "newtab"); | 2850 | const int newTab = argLabel_Command(cmd, "newtab"); |
2715 | if (newTab & otherRoot_OpenTabFlag && numRoots_Window(get_Window()) == 1) { | 2851 | if (newTab & otherRoot_OpenTabFlag && numRoots_Window(get_Window()) == 1) { |
2716 | /* Need to split first. */ | 2852 | /* Need to split first. */ |
2717 | const iInt2 winSize = get_Window()->size; | 2853 | const iInt2 winSize = get_Window()->size; |
2718 | postCommandf_App("ui.split arg:3 axis:%d newtab:%d url:%s", | 2854 | const int splitMode = argLabel_Command(cmd, "splitmode"); |
2855 | postCommandf_App("ui.split arg:%d axis:%d origin:%s newtab:%d url:%s", | ||
2856 | splitMode ? splitMode : 3, | ||
2719 | (float) winSize.x / (float) winSize.y < 0.7f ? 1 : 0, | 2857 | (float) winSize.x / (float) winSize.y < 0.7f ? 1 : 0, |
2858 | cstr_String(id_Widget(as_Widget(origin))), | ||
2720 | newTab & ~otherRoot_OpenTabFlag, | 2859 | newTab & ~otherRoot_OpenTabFlag, |
2721 | cstr_String(url)); | 2860 | cstr_String(url)); |
2722 | return iTrue; | 2861 | return iTrue; |
@@ -2727,15 +2866,16 @@ iBool handleCommand_App(const char *cmd) { | |||
2727 | root = otherRoot_Window(as_Window(d->window), root); | 2866 | root = otherRoot_Window(as_Window(d->window), root); |
2728 | setKeyRoot_Window(as_Window(d->window), root); | 2867 | setKeyRoot_Window(as_Window(d->window), root); |
2729 | setCurrent_Root(root); /* need to change for widget creation */ | 2868 | setCurrent_Root(root); /* need to change for widget creation */ |
2869 | doc = document_Command(cmd); /* may be different */ | ||
2730 | } | 2870 | } |
2731 | iDocumentWidget *doc = document_Command(cmd); | ||
2732 | if (newTab & (new_OpenTabFlag | newBackground_OpenTabFlag)) { | 2871 | if (newTab & (new_OpenTabFlag | newBackground_OpenTabFlag)) { |
2733 | doc = newTab_App(NULL, (newTab & new_OpenTabFlag) != 0); /* `newtab:2` to open in background */ | 2872 | doc = newTab_App(NULL, (newTab & new_OpenTabFlag) != 0); /* `newtab:2` to open in background */ |
2734 | } | 2873 | } |
2735 | iHistory *history = history_DocumentWidget(doc); | 2874 | iHistory *history = history_DocumentWidget(doc); |
2736 | const iBool isHistory = argLabel_Command(cmd, "history") != 0; | 2875 | const iBool isHistory = argLabel_Command(cmd, "history") != 0; |
2737 | int redirectCount = argLabel_Command(cmd, "redirect"); | 2876 | int redirectCount = argLabel_Command(cmd, "redirect"); |
2738 | if (!isHistory) { | 2877 | if (!isHistory) { |
2878 | /* TODO: Shouldn't DocumentWidget manage history on its own? */ | ||
2739 | if (redirectCount) { | 2879 | if (redirectCount) { |
2740 | replace_History(history, url); | 2880 | replace_History(history, url); |
2741 | } | 2881 | } |
@@ -2745,16 +2885,10 @@ iBool handleCommand_App(const char *cmd) { | |||
2745 | } | 2885 | } |
2746 | setInitialScroll_DocumentWidget(doc, argfLabel_Command(cmd, "scroll")); | 2886 | setInitialScroll_DocumentWidget(doc, argfLabel_Command(cmd, "scroll")); |
2747 | setRedirectCount_DocumentWidget(doc, redirectCount); | 2887 | setRedirectCount_DocumentWidget(doc, redirectCount); |
2888 | setOrigin_DocumentWidget(doc, origin); | ||
2748 | showCollapsed_Widget(findWidget_App("document.progress"), iFalse); | 2889 | showCollapsed_Widget(findWidget_App("document.progress"), iFalse); |
2749 | if (prefs_App()->decodeUserVisibleURLs) { | ||
2750 | urlDecodePath_String(url); | ||
2751 | } | ||
2752 | else { | ||
2753 | urlEncodePath_String(url); | ||
2754 | } | ||
2755 | setUrlFlags_DocumentWidget(doc, url, | 2890 | setUrlFlags_DocumentWidget(doc, url, |
2756 | (isHistory ? useCachedContentIfAvailable_DocumentWidgetSetUrlFlag : 0) | | 2891 | isHistory ? useCachedContentIfAvailable_DocumentWidgetSetUrlFlag : 0); |
2757 | (fromSidebar ? openedFromSidebar_DocumentWidgetSetUrlFlag : 0)); | ||
2758 | /* Optionally, jump to a text in the document. This will only work if the document | 2892 | /* Optionally, jump to a text in the document. This will only work if the document |
2759 | is already available, e.g., it's from "about:" or restored from cache. */ | 2893 | is already available, e.g., it's from "about:" or restored from cache. */ |
2760 | const iRangecc gotoHeading = range_Command(cmd, "gotoheading"); | 2894 | const iRangecc gotoHeading = range_Command(cmd, "gotoheading"); |
@@ -2895,6 +3029,8 @@ iBool handleCommand_App(const char *cmd) { | |||
2895 | iWidget *dlg = makePreferences_Widget(); | 3029 | iWidget *dlg = makePreferences_Widget(); |
2896 | updatePrefsThemeButtons_(dlg); | 3030 | updatePrefsThemeButtons_(dlg); |
2897 | setText_InputWidget(findChild_Widget(dlg, "prefs.downloads"), &d->prefs.strings[downloadDir_PrefsString]); | 3031 | setText_InputWidget(findChild_Widget(dlg, "prefs.downloads"), &d->prefs.strings[downloadDir_PrefsString]); |
3032 | /* TODO: Use a common table in Prefs to do this more conviently. | ||
3033 | Also see `serializePrefs_App_()`. */ | ||
2898 | setToggle_Widget(findChild_Widget(dlg, "prefs.hoverlink"), d->prefs.hoverLink); | 3034 | setToggle_Widget(findChild_Widget(dlg, "prefs.hoverlink"), d->prefs.hoverLink); |
2899 | setToggle_Widget(findChild_Widget(dlg, "prefs.smoothscroll"), d->prefs.smoothScrolling); | 3035 | setToggle_Widget(findChild_Widget(dlg, "prefs.smoothscroll"), d->prefs.smoothScrolling); |
2900 | setToggle_Widget(findChild_Widget(dlg, "prefs.imageloadscroll"), d->prefs.loadImageInsteadOfScrolling); | 3036 | setToggle_Widget(findChild_Widget(dlg, "prefs.imageloadscroll"), d->prefs.loadImageInsteadOfScrolling); |
@@ -2905,7 +3041,7 @@ iBool handleCommand_App(const char *cmd) { | |||
2905 | setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->prefs.useSystemTheme); | 3041 | setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->prefs.useSystemTheme); |
2906 | setToggle_Widget(findChild_Widget(dlg, "prefs.customframe"), d->prefs.customFrame); | 3042 | setToggle_Widget(findChild_Widget(dlg, "prefs.customframe"), d->prefs.customFrame); |
2907 | setToggle_Widget(findChild_Widget(dlg, "prefs.animate"), d->prefs.uiAnimations); | 3043 | setToggle_Widget(findChild_Widget(dlg, "prefs.animate"), d->prefs.uiAnimations); |
2908 | // setText_InputWidget(findChild_Widget(dlg, "prefs.userfont"), &d->prefs.symbolFontPath); | 3044 | setToggle_Widget(findChild_Widget(dlg, "prefs.blink"), d->prefs.blinkingCursor); |
2909 | updatePrefsPinSplitButtons_(dlg, d->prefs.pinSplit); | 3045 | updatePrefsPinSplitButtons_(dlg, d->prefs.pinSplit); |
2910 | updateScrollSpeedButtons_(dlg, mouse_ScrollType, d->prefs.smoothScrollSpeed[mouse_ScrollType]); | 3046 | updateScrollSpeedButtons_(dlg, mouse_ScrollType, d->prefs.smoothScrollSpeed[mouse_ScrollType]); |
2911 | updateScrollSpeedButtons_(dlg, keyboard_ScrollType, d->prefs.smoothScrollSpeed[keyboard_ScrollType]); | 3047 | updateScrollSpeedButtons_(dlg, keyboard_ScrollType, d->prefs.smoothScrollSpeed[keyboard_ScrollType]); |
@@ -2914,16 +3050,11 @@ iBool handleCommand_App(const char *cmd) { | |||
2914 | updateDropdownSelection_LabelWidget( | 3050 | updateDropdownSelection_LabelWidget( |
2915 | findChild_Widget(dlg, "prefs.returnkey"), | 3051 | findChild_Widget(dlg, "prefs.returnkey"), |
2916 | format_CStr("returnkey.set arg:%d", d->prefs.returnKey)); | 3052 | format_CStr("returnkey.set arg:%d", d->prefs.returnKey)); |
3053 | updatePrefsToolBarActionButton_(dlg, 0, d->prefs.toolbarActions[0]); | ||
3054 | updatePrefsToolBarActionButton_(dlg, 1, d->prefs.toolbarActions[1]); | ||
2917 | setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize); | 3055 | setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize); |
2918 | setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"), | 3056 | setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"), |
2919 | collectNewFormat_String("%g", uiScale_Window(as_Window(d->window)))); | 3057 | collectNewFormat_String("%g", uiScale_Window(as_Window(d->window)))); |
2920 | // setFlags_Widget(findChild_Widget(dlg, format_CStr("prefs.font.%d", d->prefs.font)), | ||
2921 | // selected_WidgetFlag, | ||
2922 | // iTrue); | ||
2923 | // setFlags_Widget( | ||
2924 | // findChild_Widget(dlg, format_CStr("prefs.headingfont.%d", d->prefs.headingFont)), | ||
2925 | // selected_WidgetFlag, | ||
2926 | // iTrue); | ||
2927 | setFlags_Widget(findChild_Widget(dlg, "prefs.mono.gemini"), | 3058 | setFlags_Widget(findChild_Widget(dlg, "prefs.mono.gemini"), |
2928 | selected_WidgetFlag, | 3059 | selected_WidgetFlag, |
2929 | d->prefs.monospaceGemini); | 3060 | d->prefs.monospaceGemini); |
@@ -2990,13 +3121,16 @@ iBool handleCommand_App(const char *cmd) { | |||
2990 | showTabPage_Widget(tabs, tabPage_Widget(tabs, d->prefs.dialogTab)); | 3121 | showTabPage_Widget(tabs, tabPage_Widget(tabs, d->prefs.dialogTab)); |
2991 | } | 3122 | } |
2992 | setCommandHandler_Widget(dlg, handlePrefsCommands_); | 3123 | setCommandHandler_Widget(dlg, handlePrefsCommands_); |
3124 | if (argLabel_Command(cmd, "idents") && deviceType_App() != desktop_AppDeviceType) { | ||
3125 | iWidget *idPanel = panel_Mobile(dlg, 2); | ||
3126 | iWidget *button = findUserData_Widget(findChild_Widget(dlg, "panel.top"), idPanel); | ||
3127 | postCommand_Widget(button, "panel.open"); | ||
3128 | } | ||
2993 | } | 3129 | } |
2994 | else if (equal_Command(cmd, "navigate.home")) { | 3130 | else if (equal_Command(cmd, "navigate.home")) { |
2995 | /* Look for bookmarks tagged "homepage". */ | 3131 | /* Look for bookmarks tagged "homepage". */ |
2996 | iRegExp *pattern = iClob(new_RegExp("\\b" homepage_BookmarkTag "\\b", | ||
2997 | caseInsensitive_RegExpOption)); | ||
2998 | const iPtrArray *homepages = | 3132 | const iPtrArray *homepages = |
2999 | list_Bookmarks(d->bookmarks, NULL, filterTagsRegExp_Bookmarks, pattern); | 3133 | list_Bookmarks(d->bookmarks, NULL, filterHomepage_Bookmark, NULL); |
3000 | if (isEmpty_PtrArray(homepages)) { | 3134 | if (isEmpty_PtrArray(homepages)) { |
3001 | postCommand_Root(get_Root(), "open url:about:lagrange"); | 3135 | postCommand_Root(get_Root(), "open url:about:lagrange"); |
3002 | } | 3136 | } |
@@ -3147,6 +3281,7 @@ iBool handleCommand_App(const char *cmd) { | |||
3147 | d->certs, | 3281 | d->certs, |
3148 | findIdentity_GmCerts(d->certs, collect_Block(hexDecode_Rangecc(range_Command(cmd, "ident")))), | 3282 | findIdentity_GmCerts(d->certs, collect_Block(hexDecode_Rangecc(range_Command(cmd, "ident")))), |
3149 | url); | 3283 | url); |
3284 | postCommand_App("navigate.reload"); | ||
3150 | postCommand_App("idents.changed"); | 3285 | postCommand_App("idents.changed"); |
3151 | return iTrue; | 3286 | return iTrue; |
3152 | } | 3287 | } |
@@ -3159,9 +3294,29 @@ iBool handleCommand_App(const char *cmd) { | |||
3159 | else { | 3294 | else { |
3160 | setUse_GmIdentity(ident, collect_String(suffix_Command(cmd, "url")), iFalse); | 3295 | setUse_GmIdentity(ident, collect_String(suffix_Command(cmd, "url")), iFalse); |
3161 | } | 3296 | } |
3297 | postCommand_App("navigate.reload"); | ||
3162 | postCommand_App("idents.changed"); | 3298 | postCommand_App("idents.changed"); |
3163 | return iTrue; | 3299 | return iTrue; |
3164 | } | 3300 | } |
3301 | else if (equal_Command(cmd, "ident.switch")) { | ||
3302 | /* This is different than "ident.signin" in that the currently used identity's activation | ||
3303 | URL is used instead of the current one. */ | ||
3304 | const iString *docUrl = url_DocumentWidget(document_App()); | ||
3305 | const iGmIdentity *cur = identityForUrl_GmCerts(d->certs, docUrl); | ||
3306 | iGmIdentity *dst = findIdentity_GmCerts( | ||
3307 | d->certs, collect_Block(hexDecode_Rangecc(range_Command(cmd, "fp")))); | ||
3308 | if (dst && cur != dst) { | ||
3309 | iString *useUrl = copy_String(findUse_GmIdentity(cur, docUrl)); | ||
3310 | if (isEmpty_String(useUrl)) { | ||
3311 | useUrl = copy_String(docUrl); | ||
3312 | } | ||
3313 | signIn_GmCerts(d->certs, dst, useUrl); | ||
3314 | postCommand_App("idents.changed"); | ||
3315 | postCommand_App("navigate.reload"); | ||
3316 | delete_String(useUrl); | ||
3317 | } | ||
3318 | return iTrue; | ||
3319 | } | ||
3165 | else if (equal_Command(cmd, "idents.changed")) { | 3320 | else if (equal_Command(cmd, "idents.changed")) { |
3166 | saveIdentities_GmCerts(d->certs); | 3321 | saveIdentities_GmCerts(d->certs); |
3167 | return iFalse; | 3322 | return iFalse; |
@@ -3259,50 +3414,69 @@ void openInDefaultBrowser_App(const iString *url) { | |||
3259 | return; | 3414 | return; |
3260 | } | 3415 | } |
3261 | #endif | 3416 | #endif |
3262 | #if !defined (iPlatformAppleMobile) | 3417 | #if defined (iPlatformAppleMobile) |
3418 | if (equalCase_Rangecc(urlScheme_String(url), "file")) { | ||
3419 | revealPath_App(collect_String(localFilePathFromUrl_String(url))); | ||
3420 | } | ||
3421 | return; | ||
3422 | #endif | ||
3263 | iProcess *proc = new_Process(); | 3423 | iProcess *proc = new_Process(); |
3264 | setArguments_Process(proc, | 3424 | setArguments_Process(proc, iClob(newStringsCStr_StringList( |
3265 | #if defined (iPlatformAppleDesktop) | 3425 | #if defined (iPlatformAppleDesktop) |
3266 | iClob(newStringsCStr_StringList("/usr/bin/env", "open", cstr_String(url), NULL)) | 3426 | "/usr/bin/env", |
3427 | "open", | ||
3428 | cstr_String(url), | ||
3267 | #elif defined (iPlatformLinux) || defined (iPlatformOther) || defined (iPlatformHaiku) | 3429 | #elif defined (iPlatformLinux) || defined (iPlatformOther) || defined (iPlatformHaiku) |
3268 | iClob(newStringsCStr_StringList("/usr/bin/env", "xdg-open", cstr_String(url), NULL)) | 3430 | "/usr/bin/env", |
3431 | "xdg-open", | ||
3432 | cstr_String(url), | ||
3269 | #elif defined (iPlatformMsys) | 3433 | #elif defined (iPlatformMsys) |
3270 | iClob(newStringsCStr_StringList( | 3434 | concatPath_CStr(cstr_String(execPath_App()), "../urlopen.bat"), |
3271 | concatPath_CStr(cstr_String(execPath_App()), "../urlopen.bat"), | 3435 | cstr_String(url), |
3272 | cstr_String(url), | ||
3273 | NULL)) | ||
3274 | /* TODO: The prompt window is shown momentarily... */ | 3436 | /* TODO: The prompt window is shown momentarily... */ |
3275 | #endif | 3437 | #endif |
3438 | NULL)) | ||
3276 | ); | 3439 | ); |
3277 | start_Process(proc); | 3440 | start_Process(proc); |
3278 | waitForFinished_Process(proc); /* TODO: test on Windows */ | 3441 | waitForFinished_Process(proc); |
3279 | iRelease(proc); | 3442 | iRelease(proc); |
3280 | #endif | ||
3281 | } | 3443 | } |
3282 | 3444 | ||
3445 | #include <the_Foundation/thread.h> | ||
3446 | |||
3283 | void revealPath_App(const iString *path) { | 3447 | void revealPath_App(const iString *path) { |
3284 | #if defined (iPlatformAppleDesktop) | 3448 | #if defined (iPlatformAppleDesktop) |
3285 | const char *scriptPath = concatPath_CStr(dataDir_App_(), "revealfile.scpt"); | 3449 | iProcess *proc = new_Process(); |
3286 | iFile *f = newCStr_File(scriptPath); | 3450 | setArguments_Process( |
3287 | if (open_File(f, writeOnly_FileMode | text_FileMode)) { | 3451 | proc, iClob(newStringsCStr_StringList("/usr/bin/open", "-R", cstr_String(path), NULL))); |
3288 | /* AppleScript to select a specific file. */ | 3452 | start_Process(proc); |
3289 | write_File(f, collect_Block(newCStr_Block("on run argv\n" | 3453 | iRelease(proc); |
3290 | " tell application \"Finder\"\n" | 3454 | #elif defined (iPlatformAppleMobile) |
3291 | " activate\n" | 3455 | /* Use a share sheet. */ |
3292 | " reveal POSIX file (item 1 of argv) as text\n" | 3456 | openFileActivityView_iOS(path); |
3293 | " end tell\n" | 3457 | #elif defined (iPlatformLinux) || defined (iPlatformHaiku) |
3294 | "end run\n"))); | 3458 | iProcess *proc = NULL; |
3295 | close_File(f); | 3459 | /* Try with `dbus-send` first. */ { |
3296 | iProcess *proc = new_Process(); | 3460 | proc = new_Process(); |
3297 | setArguments_Process( | 3461 | setArguments_Process( |
3298 | proc, | 3462 | proc, |
3299 | iClob(newStringsCStr_StringList( | 3463 | iClob(newStringsCStr_StringList( |
3300 | "/usr/bin/osascript", scriptPath, cstr_String(path), NULL))); | 3464 | "/usr/bin/dbus-send", |
3465 | "--print-reply", | ||
3466 | "--dest=org.freedesktop.FileManager1", | ||
3467 | "/org/freedesktop/FileManager1", | ||
3468 | "org.freedesktop.FileManager1.ShowItems", | ||
3469 | format_CStr("array:string:%s", makeFileUrl_CStr(cstr_String(path))), | ||
3470 | "string:", | ||
3471 | NULL))); | ||
3301 | start_Process(proc); | 3472 | start_Process(proc); |
3473 | waitForFinished_Process(proc); | ||
3474 | const iBool dbusDidSucceed = (exitStatus_Process(proc) == 0); | ||
3302 | iRelease(proc); | 3475 | iRelease(proc); |
3476 | if (dbusDidSucceed) { | ||
3477 | return; | ||
3478 | } | ||
3303 | } | 3479 | } |
3304 | iRelease(f); | ||
3305 | #elif defined (iPlatformLinux) || defined (iPlatformHaiku) | ||
3306 | iFileInfo *inf = iClob(new_FileInfo(path)); | 3480 | iFileInfo *inf = iClob(new_FileInfo(path)); |
3307 | iRangecc target; | 3481 | iRangecc target; |
3308 | if (isDirectory_FileInfo(inf)) { | 3482 | if (isDirectory_FileInfo(inf)) { |
@@ -3311,7 +3485,7 @@ void revealPath_App(const iString *path) { | |||
3311 | else { | 3485 | else { |
3312 | target = dirName_Path(path); | 3486 | target = dirName_Path(path); |
3313 | } | 3487 | } |
3314 | iProcess *proc = new_Process(); | 3488 | proc = new_Process(); |
3315 | setArguments_Process( | 3489 | setArguments_Process( |
3316 | proc, iClob(newStringsCStr_StringList("/usr/bin/env", "xdg-open", cstr_Rangecc(target), NULL))); | 3490 | proc, iClob(newStringsCStr_StringList("/usr/bin/env", "xdg-open", cstr_Rangecc(target), NULL))); |
3317 | start_Process(proc); | 3491 | start_Process(proc); |
@@ -3365,8 +3539,21 @@ void closePopups_App(void) { | |||
3365 | } | 3539 | } |
3366 | 3540 | ||
3367 | #if defined (iPlatformAndroidMobile) | 3541 | #if defined (iPlatformAndroidMobile) |
3542 | |||
3368 | float displayDensity_Android(void) { | 3543 | float displayDensity_Android(void) { |
3369 | iApp *d = &app_; | 3544 | iApp *d = &app_; |
3370 | return toFloat_String(at_CommandLine(&d->args, 1)); | 3545 | return toFloat_String(at_CommandLine(&d->args, 1)); |
3371 | } | 3546 | } |
3547 | |||
3548 | #include <jni.h> | ||
3549 | |||
3550 | JNIEXPORT void JNICALL Java_fi_skyjake_lagrange_LagrangeActivity_postAppCommand( | ||
3551 | JNIEnv* env, jclass jcls, | ||
3552 | jstring command) | ||
3553 | { | ||
3554 | const char *cmd = (*env)->GetStringUTFChars(env, command, NULL); | ||
3555 | postCommand_Root(NULL, cmd); | ||
3556 | (*env)->ReleaseStringUTFChars(env, command, cmd); | ||
3557 | } | ||
3558 | |||
3372 | #endif | 3559 | #endif |