summaryrefslogtreecommitdiff
path: root/src/app.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2022-01-20 12:06:39 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2022-01-20 12:06:39 +0200
commit6da3bdb612e0ead07ff93f4f6dc5aef46c7b9c9e (patch)
tree4378984d744dc67b7453573524d5839b9ce7f9d5 /src/app.c
parentd5169339b3454c80a6f2ed5f8cb937e5d5613fc0 (diff)
parent33816278c84fd7ac7e895f4111229c4ff4436b53 (diff)
Merge branch 'dev' of skyjake.fi:gemini/lagrange into dev
Diffstat (limited to 'src/app.c')
-rw-r--r--src/app.c397
1 files changed, 292 insertions, 105 deletions
diff --git a/src/app.c b/src/app.c
index 28b32939..6392e7fa 100644
--- a/src/app.c
+++ b/src/app.c
@@ -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";
108static const char *prefsFileName_App_ = "prefs.cfg"; 112static const char *prefsFileName_App_ = "prefs.cfg";
109static const char *oldStateFileName_App_ = "state.binary"; 113static const char *oldStateFileName_App_ = "state.binary";
110static const char *stateFileName_App_ = "state.lgr"; 114static const char *stateFileName_App_ = "state.lgr";
115static const char *tempStateFileName_App_ = "state.lgr.tmp";
111static const char *defaultDownloadDir_App_ = "~/Downloads"; 116static const char *defaultDownloadDir_App_ = "~/Downloads";
112 117
113static const int idleThreshold_App_ = 1000; /* ms */ 118static const int idleThreshold_App_ = 1000; /* ms */
@@ -115,6 +120,7 @@ static const int idleThreshold_App_ = 1000; /* ms */
115struct Impl_App { 120struct 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
165static int cmp_Ticker_(const void *a, const void *b) { 172static 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
315static const char *downloadDir_App_(void) { 334static 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
991const iString *execPath_App(void) { 1042const 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
1003const iString *downloadPathForUrl_App(const iString *url, const iString *mime) { 1054const 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
1099const 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
1115const 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
1059const iString *debugInfo_App(void) { 1131const 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
1177iLocalDef iBool isWaitingAllowed_App_(iApp *d) { 1249iLocalDef 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
1192static iBool nextEvent_App_(iApp *d, enum iAppEventMode eventMode, SDL_Event *event) { 1261static 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
1206static iPtrArray *listWindows_App_(const iApp *d, iPtrArray *windows) { 1278static 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
1469backToMainLoop:; 1525backToMainLoop:;
@@ -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
1499static int resizeWatcher_(void *user, SDL_Event *event) { 1567static 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
1692void postCommandf_Root(iRoot *d, const char *command, ...) { 1766void postCommandf_Root(iRoot *d, const char *command, ...) {
@@ -1823,6 +1897,12 @@ static void updatePrefsPinSplitButtons_(iWidget *d, int value) {
1823 } 1897 }
1824} 1898}
1825 1899
1900static 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
1826static void updateScrollSpeedButtons_(iWidget *d, enum iScrollType type, const int value) { 1906static 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
3283void revealPath_App(const iString *path) { 3447void 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
3368float displayDensity_Android(void) { 3543float 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
3550JNIEXPORT 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