diff --git a/src/app.c b/src/app.c

index 3c354438..a6a2a0a2 100644

--- a/src/app.c

+++ b/src/app.c

@@ -1645,19 +1645,21 @@ void processEvents_App(enum iAppEventMode eventMode) {

                 }

             }

#endif

- /* Per-window processing. */

             iBool wasUsed = iFalse;

- listWindows_App_(d, &windows);

- iConstForEach(PtrArray, iter, &windows) {

- iWindow *window = iter.ptr;

- setCurrent_Window(window);

- window->lastHover = window->hover;

- wasUsed = processEvent_Window(window, &ev);

- if (ev.type == SDL_MOUSEMOTION) {

- /* Only offered to the frontmost window. */

- break;

+ /* Per-window processing. */

+ if (!isEmpty_PtrArray(&d->mainWindows)) {

+ listWindows_App_(d, &windows);

+ iConstForEach(PtrArray, iter, &windows) {

+ iWindow *window = iter.ptr;

+ setCurrent_Window(window);

+ window->lastHover = window->hover;

+ wasUsed = processEvent_Window(window, &ev);

+ if (ev.type == SDL_MOUSEMOTION) {

+ /* Only offered to the frontmost window. */

+ break;

+ }

+ if (wasUsed) break;

                 }

- if (wasUsed) break;

             }

             setCurrent_Window(d->window);

             if (!wasUsed) {

@@ -1778,7 +1780,14 @@ static int resizeWatcher_(void *user, SDL_Event *event) {

         dispatchEvent_Window(as_Window(d->window), &u);

     }

#endif

- drawWhileResizing_MainWindow(d->window, winev->data1, winev->data2);

+ /* Find the window that is being resized and redraw it immediately. */

+ iConstForEach(PtrArray, i, &d->mainWindows) {

+ const iMainWindow *win = i.ptr;

+ if (SDL_GetWindowID(win->base.win) == winev->windowID) {

+ drawWhileResizing_MainWindow(d->window, winev->data1, winev->data2);

+ break;

+ }

+ }

 }

 return 0;

}

@@ -2051,6 +2060,9 @@ void addWindow_App(iMainWindow *win) {

void removeWindow_App(iMainWindow *win) {

 iApp *d = &app_;

 removeOne_PtrArray(&d->mainWindows, win);

+ if (isEmpty_PtrArray(&d->mainWindows)) {

+ d->window = NULL;

+ }

}



size_t numWindows_App(void) {

@@ -2386,11 +2398,6 @@ void closeWindow_App(iMainWindow *win) {

         }

     }

 }

- if (isEmpty_PtrArray(&d->mainWindows)) {

- d->window = NULL;

- setActiveWindow_App(NULL);

- setCurrent_Window(NULL);

- }

}



static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) {

@@ -2552,36 +2559,10 @@ static void invalidateCachedDocuments_App_(void) {

 }

}



-iBool handleCommand_App(const char *cmd) {

- iApp *d = &app_;

+static iBool handleNonWindowRelatedCommand_App_(iApp *d, const char *cmd) {

 const iBool isFrozen = !d->window || d->window->isDrawFrozen;

- /* TODO: Maybe break this up a little bit? There's a very long list of ifs here. */

- if (equal_Command(cmd, "config.error")) {

- makeSimpleMessage_Widget(uiTextCaution_ColorEscape "CONFIG ERROR",

- format_CStr("Error in config file: %s\n"

- "See "about:debug" for details.",

- suffixPtr_Command(cmd, "where")));

- return iTrue;

- }

-#if 0 /* disabled in v1.11 */

- else if (equal_Command(cmd, "fontpack.suggest.classic")) {

- /* TODO: Don't use this when system fonts are accessible. */

- if (!isInstalled_Fonts("classic-set") && !isInstalled_Fonts("cjk")) {

- makeQuestion_Widget(

- uiHeading_ColorEscape "${heading.fontpack.classic}",

- "${dlg.fontpack.classic.msg}",

- (iMenuItem[]){

- { "${cancel}" },

- { uiTextAction_ColorEscape "${dlg.fontpack.classic}",

- 0,

- 0,

- "!open newtab:1 url:gemini://skyjake.fi/fonts/classic-set.fontpack" } },

- 2);

- }

- return iTrue;

- }

-#endif

- else if (equal_Command(cmd, "prefs.changed")) {

+ /* Commands related to preferences. */

+ if (equal_Command(cmd, "prefs.changed")) {

     savePrefs_App_(d);

     return iTrue;

 }

@@ -2641,28 +2622,6 @@ iBool handleCommand_App(const char *cmd) {

     d->prefs.langTo   = argLabel_Command(cmd, "to");

     return iTrue;

 }

- else if (equal_Command(cmd, "ui.split")) {

- if (argLabel_Command(cmd, "swap")) {

- swapRoots_MainWindow(d->window);

- return iTrue;

- }

- if (argLabel_Command(cmd, "focusother")) {

- iWindow *baseWin = &d->window->base;

- if (baseWin->roots[1]) {

- baseWin->keyRoot =

- (baseWin->keyRoot == baseWin->roots[1] ? baseWin->roots[0] : baseWin->roots[1]);

- }

- }

- d->window->pendingSplitMode =

- (argLabel_Command(cmd, "axis") ? vertical_WindowSplit : 0) | (arg_Command(cmd) << 1);

- const char *url = suffixPtr_Command(cmd, "url");

- setCStr_String(d->window->pendingSplitUrl, url ? url : "");

- if (hasLabel_Command(cmd, "origin")) {

- set_String(d->window->pendingSplitOrigin, string_Command(cmd, "origin"));

- }

- postRefresh_App();

- return iTrue;

- }

 else if (equal_Command(cmd, "window.retain")) {

     d->prefs.retainWindowSize = arg_Command(cmd);

     return iTrue;

@@ -2671,186 +2630,174 @@ iBool handleCommand_App(const char *cmd) {

     d->prefs.customFrame = arg_Command(cmd);

     return iTrue;

 }

- else if (equal_Command(cmd, "window.maximize")) {

- const size_t winIndex = argU32Label_Command(cmd, "index");

- if (winIndex < size_PtrArray(&d->mainWindows)) {

- iMainWindow *win = at_PtrArray(&d->mainWindows, winIndex);

- if (!argLabel_Command(cmd, "toggle")) {

- setSnap_MainWindow(win, maximized_WindowSnap);

- }

- else {

- setSnap_MainWindow(

- win, snap_MainWindow(win) == maximized_WindowSnap ? 0 : maximized_WindowSnap);

- }

- }

- return iTrue;

- }

- else if (equal_Command(cmd, "window.fullscreen")) {

- const iBool wasFull = snap_MainWindow(d->window) == fullscreen_WindowSnap;

- setSnap_MainWindow(d->window, wasFull ? 0 : fullscreen_WindowSnap);

- postCommandf_App("window.fullscreen.changed arg:%d", !wasFull);

- return iTrue;

- }

 else if (equal_Command(cmd, "prefs.retaintabs.changed")) {

     d->prefs.retainTabs = arg_Command(cmd);

     return iTrue;

 }

- else if (equal_Command(cmd, "font.reset")) {

- resetFonts_App();

+ else if (equal_Command(cmd, "prefs.font.smooth.changed")) {

+ if (!isFrozen) {

+ setFreezeDraw_MainWindow(get_MainWindow(), iTrue);

+ }

+ d->prefs.fontSmoothing = arg_Command(cmd) != 0;

+ if (!isFrozen) {

+ resetFontCache_Text(text_Window(get_MainWindow())); /* clear the glyph cache */

+ postCommand_App("font.changed");

+ postCommand_App("window.unfreeze");

+ }

     return iTrue;

 }

- else if (equal_Command(cmd, "font.reload")) {

- reload_Fonts(); /* also does font cache reset, window invalidation */

+ else if (equal_Command(cmd, "prefs.gemtext.ansi.fg.changed")) {

+ iChangeFlags(d->prefs.gemtextAnsiEscapes, allowFg_AnsiFlag, arg_Command(cmd));

     return iTrue;

 }

- else if (equal_Command(cmd, "font.find")) {

- searchOnlineLibraryForCharacters_Fonts(string_Command(cmd, "chars"));

+ else if (equal_Command(cmd, "prefs.gemtext.ansi.bg.changed")) {

+ iChangeFlags(d->prefs.gemtextAnsiEscapes, allowBg_AnsiFlag, arg_Command(cmd));

     return iTrue;

 }

- else if (equal_Command(cmd, "font.found")) {

- if (hasLabel_Command(cmd, "error")) {

- makeSimpleMessage_Widget("${heading.glyphfinder}",

- format_CStr("%d %s",

- argLabel_Command(cmd, "error"),

- suffixPtr_Command(cmd, "msg")));

- return iTrue;

- }

- iString *src = collectNew_String();

- setCStr_String(src, "# ${heading.glyphfinder.results}\n\n");

- iRangecc path = iNullRange;

- iBool isFirst = iTrue;

- while (nextSplit_Rangecc(range_Command(cmd, "packs"), ",", &path)) {

- if (isFirst) {

- appendCStr_String(src, "${glyphfinder.results}\n\n");

- }

- iRangecc fpath = path;

- iRangecc fsize = path;

- fpath.end = strchr(fpath.start, ';');

- fsize.start = fpath.end + 1;

- const uint32_t size = strtoul(fsize.start, NULL, 10);

- appendFormat_String(src, "=> gemini://skyjake.fi/fonts/%s %s (%.1f MB)\n",

- cstr_Rangecc(fpath),

- cstr_Rangecc(fpath),

- (double) size / 1.0e6);

- isFirst = iFalse;

- }

- if (isFirst) {

- appendFormat_String(src, "${glyphfinder.results.empty}\n");

- }

- appendCStr_String(src, "\n=> about:fonts ${menu.fonts}");

- iDocumentWidget *page = newTab_App(NULL, iTrue);

- translate_Lang(src);

- setUrlAndSource_DocumentWidget(page,

- collectNewCStr_String(""),

- collectNewCStr_String("text/gemini"),

- utf8_String(src));

+ else if (equal_Command(cmd, "prefs.gemtext.ansi.fontstyle.changed")) {

+ iChangeFlags(d->prefs.gemtextAnsiEscapes, allowFontStyle_AnsiFlag, arg_Command(cmd));

     return iTrue;

 }

- else if (equal_Command(cmd, "font.set")) {

+ else if (equal_Command(cmd, "prefs.mono.gemini.changed") ||

+ equal_Command(cmd, "prefs.mono.gopher.changed")) {

+ const iBool isSet = (arg_Command(cmd) != 0);

     if (!isFrozen) {

         setFreezeDraw_MainWindow(get_MainWindow(), iTrue);

     }

- struct {

- const char *label;

- enum iPrefsString ps;

- int fontId;

- } params[] = {

- { "ui", uiFont_PrefsString, default_FontId },

- { "mono", monospaceFont_PrefsString, monospace_FontId },

- { "heading", headingFont_PrefsString, documentHeading_FontId },

- { "body", bodyFont_PrefsString, documentBody_FontId },

- { "monodoc", monospaceDocumentFont_PrefsString, documentMonospace_FontId },

- };

- iBool wasChanged = iFalse;

- iForIndices(i, params) {

- if (hasLabel_Command(cmd, params[i].label)) {

- iString *ps = &d->prefs.strings[params[i].ps];

- const iString *newFont = string_Command(cmd, params[i].label);

- if (!equal_String(ps, newFont)) {

- set_String(ps, newFont);

- wasChanged = iTrue;

- }

- }

+ if (startsWith_CStr(cmd, "prefs.mono.gemini")) {

+ d->prefs.monospaceGemini = isSet;

     }

- if (wasChanged) {

- if (isFinishedLaunching_App()) { /* there's a reset when launch is finished */

- resetFonts_Text(text_Window(get_MainWindow()));

- }

- postCommand_App("font.changed");

+ else {

+ d->prefs.monospaceGopher = isSet;

     }

     if (!isFrozen) {

+ postCommand_App("font.changed");

         postCommand_App("window.unfreeze");

     }

     return iTrue;

 }

- else if (equal_Command(cmd, "zoom.set")) {

- if (!isFrozen) {

- setFreezeDraw_MainWindow(get_MainWindow(), iTrue); /* no intermediate draws before docs updated */

+ else if (equal_Command(cmd, "prefs.boldlink.dark.changed") ||

+ equal_Command(cmd, "prefs.boldlink.light.changed") ||

+ equal_Command(cmd, "prefs.boldlink.visited.changed")) {

+ const iBool isSet = (arg_Command(cmd) != 0);

+ if (startsWith_CStr(cmd, "prefs.boldlink.visited")) {

+ d->prefs.boldLinkVisited = isSet;

     }

- if (arg_Command(cmd) != d->prefs.zoomPercent) {

- d->prefs.zoomPercent = arg_Command(cmd);

- invalidateCachedDocuments_App_();

+ else if (startsWith_CStr(cmd, "prefs.boldlink.dark")) {

+ d->prefs.boldLinkDark = isSet;

     }

- setDocumentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f);

- if (!isFrozen) {

+ else {

+ d->prefs.boldLinkLight = isSet;

+ }

+ if (!d->isLoadingPrefs) {

         postCommand_App("font.changed");

- postCommand_App("window.unfreeze");

     }

     return iTrue;

 }

- else if (equal_Command(cmd, "zoom.delta")) {

- if (!isFrozen) {

- setFreezeDraw_MainWindow(get_MainWindow(), iTrue); /* no intermediate draws before docs updated */

- }

- int delta = arg_Command(cmd);

- if (d->prefs.zoomPercent < 100 || (delta < 0 && d->prefs.zoomPercent == 100)) {

- delta /= 2;

- }

- d->prefs.zoomPercent = iClamp(d->prefs.zoomPercent + delta, 50, 200);

- invalidateCachedDocuments_App_();

- setDocumentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f);

- if (!isFrozen) {

- postCommand_App("font.changed");

- postCommand_App("window.unfreeze");

+ else if (equal_Command(cmd, "prefs.biglede.changed")) {

+ d->prefs.bigFirstParagraph = arg_Command(cmd) != 0;

+ if (!d->isLoadingPrefs) {

+ postCommand_App("document.layout.changed");

     }

     return iTrue;

 }

- else if (equal_Command(cmd, "smoothscroll")) {

- d->prefs.smoothScrolling = arg_Command(cmd);

+ else if (equal_Command(cmd, "prefs.justify.changed")) {

+ d->prefs.justifyParagraph = arg_Command(cmd) != 0;

+ if (!d->isLoadingPrefs) {

+ postCommand_App("document.layout.changed");

+ }

     return iTrue;

 }

- else if (equal_Command(cmd, "scrollspeed")) {

- const int type = argLabel_Command(cmd, "type");

- if (type == keyboard_ScrollType || type == mouse_ScrollType) {

- d->prefs.smoothScrollSpeed[type] = iClamp(arg_Command(cmd), 1, 40);

+ else if (equal_Command(cmd, "prefs.plaintext.wrap.changed")) {

+ d->prefs.plainTextWrap = arg_Command(cmd) != 0;

+ if (!d->isLoadingPrefs) {

+ postCommand_App("document.layout.changed");

     }

     return iTrue;

 }

- else if (equal_Command(cmd, "decodeurls")) {

- d->prefs.decodeUserVisibleURLs = arg_Command(cmd);

+ else if (equal_Command(cmd, "prefs.sideicon.changed")) {

+ d->prefs.sideIcon = arg_Command(cmd) != 0;

+ postRefresh_App();

     return iTrue;

 }

- else if (equal_Command(cmd, "imageloadscroll")) {

- d->prefs.loadImageInsteadOfScrolling = arg_Command(cmd);

+ else if (equal_Command(cmd, "prefs.centershort.changed")) {

+ d->prefs.centerShortDocs = arg_Command(cmd) != 0;

+ if (!isFrozen) {

+ invalidate_Window(d->window);

+ }

     return iTrue;

 }

- else if (equal_Command(cmd, "hidetoolbarscroll")) {

- d->prefs.hideToolbarOnScroll = arg_Command(cmd);

- if (!d->prefs.hideToolbarOnScroll) {

- showToolbar_Root(get_Root(), iTrue);

- }

+ else if (equal_Command(cmd, "prefs.collapsepreonload.changed")) {

+ d->prefs.collapsePreOnLoad = arg_Command(cmd) != 0;

     return iTrue;

 }

- else if (equal_Command(cmd, "returnkey.set")) {

- d->prefs.returnKey = arg_Command(cmd);

+ else if (equal_Command(cmd, "prefs.hoverlink.changed")) {

+ d->prefs.hoverLink = arg_Command(cmd) != 0;

+ postRefresh_App();

     return iTrue;

 }

- else if (equal_Command(cmd, "pinsplit.set")) {

- d->prefs.pinSplit = arg_Command(cmd);

+ else if (equal_Command(cmd, "prefs.hoverlink.toggle")) {

+ d->prefs.hoverLink = !d->prefs.hoverLink;

+ postRefresh_App();

     return iTrue;

 }

- else if (equal_Command(cmd, "theme.set")) {

- const int isAuto = argLabel_Command(cmd, "auto");

+ else if (equal_Command(cmd, "prefs.dataurl.openimages.changed")) {

+ d->prefs.openDataUrlImagesOnLoad = arg_Command(cmd) != 0;

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "prefs.archive.openindex.changed")) {

+ d->prefs.openArchiveIndexPages = arg_Command(cmd) != 0;

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "prefs.bookmarks.addbottom.changed")) {

+ d->prefs.addBookmarksToBottom = arg_Command(cmd) != 0;

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "prefs.font.warnmissing.changed")) {

+ d->prefs.warnAboutMissingGlyphs = arg_Command(cmd) != 0;

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "prefs.animate.changed")) {

+ d->prefs.uiAnimations = arg_Command(cmd) != 0;

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "prefs.blink.changed")) {

+ d->prefs.blinkingCursor = arg_Command(cmd) != 0;

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "prefs.time.24h.changed")) {

+ d->prefs.time24h = arg_Command(cmd) != 0;

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "smoothscroll")) {

+ d->prefs.smoothScrolling = arg_Command(cmd);

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "scrollspeed")) {

+ const int type = argLabel_Command(cmd, "type");

+ if (type == keyboard_ScrollType || type == mouse_ScrollType) {

+ d->prefs.smoothScrollSpeed[type] = iClamp(arg_Command(cmd), 1, 40);

+ }

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "decodeurls")) {

+ d->prefs.decodeUserVisibleURLs = arg_Command(cmd);

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "imageloadscroll")) {

+ d->prefs.loadImageInsteadOfScrolling = arg_Command(cmd);

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "returnkey.set")) {

+ d->prefs.returnKey = arg_Command(cmd);

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "pinsplit.set")) {

+ d->prefs.pinSplit = arg_Command(cmd);

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "theme.set")) {

+ const int isAuto = argLabel_Command(cmd, "auto");

     d->prefs.theme = arg_Command(cmd);

     if (!isAuto) {

         if (isDark_ColorTheme(d->prefs.theme) && d->isDarkSystemTheme) {

@@ -2923,246 +2870,426 @@ iBool handleCommand_App(const char *cmd) {

     postCommand_App("document.layout.changed redo:1");

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.font.smooth.changed")) {

+ else if (equal_Command(cmd, "ansiescape")) {

+ d->prefs.gemtextAnsiEscapes = arg_Command(cmd);

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "saturation.set")) {

+ d->prefs.saturation = (float) arg_Command(cmd) / 100.0f;

     if (!isFrozen) {

- setFreezeDraw_MainWindow(get_MainWindow(), iTrue);

+ invalidate_Window(d->window);

     }

- d->prefs.fontSmoothing = arg_Command(cmd) != 0;

- if (!isFrozen) {

- resetFontCache_Text(text_Window(get_MainWindow())); /* clear the glyph cache */

- postCommand_App("font.changed");

- postCommand_App("window.unfreeze");

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "cachesize.set")) {

+ d->prefs.maxCacheSize = arg_Command(cmd);

+ if (d->prefs.maxCacheSize <= 0) {

+ d->prefs.maxCacheSize = 0;

     }

     return iTrue;

 }

- else if (equal_Command(cmd, "ansiescape")) {

- d->prefs.gemtextAnsiEscapes = arg_Command(cmd);

+ else if (equal_Command(cmd, "memorysize.set")) {

+ d->prefs.maxMemorySize = arg_Command(cmd);

+ if (d->prefs.maxMemorySize <= 0) {

+ d->prefs.maxMemorySize = 0;

+ }

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.gemtext.ansi.fg.changed")) {

- iChangeFlags(d->prefs.gemtextAnsiEscapes, allowFg_AnsiFlag, arg_Command(cmd));

+ else if (equal_Command(cmd, "urlsize.set")) {

+ d->prefs.maxUrlSize = arg_Command(cmd);

+ if (d->prefs.maxUrlSize < 1024) {

+ d->prefs.maxUrlSize = 1024; /* Gemini protocol requirement */

+ }

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.gemtext.ansi.bg.changed")) {

- iChangeFlags(d->prefs.gemtextAnsiEscapes, allowBg_AnsiFlag, arg_Command(cmd));

+ else if (equal_Command(cmd, "searchurl")) {

+ iString *url = &d->prefs.strings[searchUrl_PrefsString];

+ setCStr_String(url, suffixPtr_Command(cmd, "address"));

+ if (startsWith_String(url, "//")) {

+ prependCStr_String(url, "gemini:");

+ }

+ if (!isEmpty_String(url) && !startsWithCase_String(url, "gemini://")) {

+ prependCStr_String(url, "gemini://");

+ }

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.gemtext.ansi.fontstyle.changed")) {

- iChangeFlags(d->prefs.gemtextAnsiEscapes, allowFontStyle_AnsiFlag, arg_Command(cmd));

+ else if (equal_Command(cmd, "proxy.gemini")) {

+ setCStr_String(&d->prefs.strings[geminiProxy_PrefsString], suffixPtr_Command(cmd, "address"));

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.mono.gemini.changed") ||

- equal_Command(cmd, "prefs.mono.gopher.changed")) {

- const iBool isSet = (arg_Command(cmd) != 0);

- if (!isFrozen) {

- setFreezeDraw_MainWindow(get_MainWindow(), iTrue);

+ else if (equal_Command(cmd, "proxy.gopher")) {

+ setCStr_String(&d->prefs.strings[gopherProxy_PrefsString], suffixPtr_Command(cmd, "address"));

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "proxy.http")) {

+ setCStr_String(&d->prefs.strings[httpProxy_PrefsString], suffixPtr_Command(cmd, "address"));

+ return iTrue;

+ }

+#if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT)

+ else if (equal_Command(cmd, "downloads")) {

+ setCStr_String(&d->prefs.strings[downloadDir_PrefsString], suffixPtr_Command(cmd, "path"));

+ return iTrue;

+ }

+#endif

+ else if (equal_Command(cmd, "downloads.open")) {

+ postCommandf_App("open newtab:%d url:%s",

+ argLabel_Command(cmd, "newtab"),

+ cstrCollect_String(makeFileUrl_String(downloadDir_App())));

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "ca.file")) {

+ setCStr_String(&d->prefs.strings[caFile_PrefsString], suffixPtr_Command(cmd, "path"));

+ if (!argLabel_Command(cmd, "noset")) {

+ setCACertificates_TlsRequest(&d->prefs.strings[caFile_PrefsString], &d->prefs.strings[caPath_PrefsString]);

     }

- if (startsWith_CStr(cmd, "prefs.mono.gemini")) {

- d->prefs.monospaceGemini = isSet;

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "ca.path")) {

+ setCStr_String(&d->prefs.strings[caPath_PrefsString], suffixPtr_Command(cmd, "path"));

+ if (!argLabel_Command(cmd, "noset")) {

+ setCACertificates_TlsRequest(&d->prefs.strings[caFile_PrefsString], &d->prefs.strings[caPath_PrefsString]);

     }

- else {

- d->prefs.monospaceGopher = isSet;

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "search")) {

+ const int newTab = argLabel_Command(cmd, "newtab");

+ const iString *query = collect_String(suffix_Command(cmd, "query"));

+ if (!isLikelyUrl_String(query)) {

+ const iString *url = searchQueryUrl_App(query);

+ if (!isEmpty_String(url)) {

+ postCommandf_App("open newtab:%d url:%s", newTab, cstr_String(url));

+ }

     }

- if (!isFrozen) {

- postCommand_App("font.changed");

- postCommand_App("window.unfreeze");

+ else {

+ postCommandf_App("open newtab:%d url:%s", newTab, cstr_String(query));

     }

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.boldlink.dark.changed") ||

- equal_Command(cmd, "prefs.boldlink.light.changed") ||

- equal_Command(cmd, "prefs.boldlink.visited.changed")) {

- const iBool isSet = (arg_Command(cmd) != 0);

- if (startsWith_CStr(cmd, "prefs.boldlink.visited")) {

- d->prefs.boldLinkVisited = isSet;

- }

- else if (startsWith_CStr(cmd, "prefs.boldlink.dark")) {

- d->prefs.boldLinkDark = isSet;

+ else if (equal_Command(cmd, "reveal")) {

+ const iString *path = NULL;

+ if (hasLabel_Command(cmd, "path")) {

+ path = suffix_Command(cmd, "path");

     }

- else {

- d->prefs.boldLinkLight = isSet;

+ else if (hasLabel_Command(cmd, "url")) {

+ path = collect_String(localFilePathFromUrl_String(suffix_Command(cmd, "url")));

     }

- if (!d->isLoadingPrefs) {

- postCommand_App("font.changed");

+ if (path) {

+ revealPath_App(path);

     }

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.biglede.changed")) {

- d->prefs.bigFirstParagraph = arg_Command(cmd) != 0;

- if (!d->isLoadingPrefs) {

- postCommand_App("document.layout.changed");

+ else if (equal_Command(cmd, "window.new")) {

+ iMainWindow *newWin = new_MainWindow(initialWindowRect_App_(d, numWindows_App()));

+ addWindow_App(newWin); /* takes ownership */

+ SDL_ShowWindow(newWin->base.win);

+ setCurrent_Window(newWin);

+ if (hasLabel_Command(cmd, "url")) {

+ postCommandf_Root(newWin->base.roots[0], "~open %s", cmd + 11 /* all arguments passed on */);

     }

+ else {

+ postCommand_Root(newWin->base.roots[0], "~navigate.home");

+ }

+ postCommand_Root(newWin->base.roots[0], "~window.unfreeze");

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.justify.changed")) {

- d->prefs.justifyParagraph = arg_Command(cmd) != 0;

- if (!d->isLoadingPrefs) {

- postCommand_App("document.layout.changed");

+ else if (equal_Command(cmd, "bookmarks.changed")) {

+ save_Bookmarks(d->bookmarks, dataDir_App_());

+ return iFalse;

+ }

+ else if (equal_Command(cmd, "bookmarks.sort")) {

+ sort_Bookmarks(d->bookmarks, arg_Command(cmd), cmpTitleAscending_Bookmark);

+ postCommand_App("bookmarks.changed");

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "bookmarks.reload.remote")) {

+ fetchRemote_Bookmarks(bookmarks_App());

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "bookmarks.request.finished")) {

+ requestFinished_Bookmarks(bookmarks_App(), pointerLabel_Command(cmd, "req"));

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "feeds.refresh")) {

+ refresh_Feeds();

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "visited.changed")) {

+ save_Visited(d->visited, dataDir_App_());

+ return iFalse;

+ }

+ else if (equal_Command(cmd, "idents.changed")) {

+ saveIdentities_GmCerts(d->certs);

+ return iFalse;

+ }

+ else if (equal_Command(cmd, "ident.signin")) {

+ const iString *url = collect_String(suffix_Command(cmd, "url"));

+ signIn_GmCerts(

+ d->certs,

+ findIdentity_GmCerts(d->certs, collect_Block(hexDecode_Rangecc(range_Command(cmd, "ident")))),

+ url);

+ postCommand_App("navigate.reload");

+ postCommand_App("idents.changed");

+ return iTrue;

+ }

+ else if (equal_Command(cmd, "ident.signout")) {

+ iGmIdentity *ident = findIdentity_GmCerts(

+ d->certs, collect_Block(hexDecode_Rangecc(range_Command(cmd, "ident"))));

+ if (arg_Command(cmd)) {

+ clearUse_GmIdentity(ident);

+ }

+ else {

+ setUse_GmIdentity(ident, collect_String(suffix_Command(cmd, "url")), iFalse);

     }

+ postCommand_App("navigate.reload");

+ postCommand_App("idents.changed");

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.plaintext.wrap.changed")) {

- d->prefs.plainTextWrap = arg_Command(cmd) != 0;

- if (!d->isLoadingPrefs) {

- postCommand_App("document.layout.changed");

+ else if (equal_Command(cmd, "os.theme.changed")) {

+ const int dark = argLabel_Command(cmd, "dark");

+ d->isDarkSystemTheme = dark;

+ if (d->prefs.useSystemTheme) {

+ const int contrast = argLabel_Command(cmd, "contrast");

+ const int preferred = d->prefs.systemPreferredColorTheme[dark ^ 1];

+ postCommandf_App("theme.set arg:%d auto:1",

+ preferred >= 0 ? preferred

+ : dark ? (contrast ? pureBlack_ColorTheme : dark_ColorTheme)

+ : (contrast ? pureWhite_ColorTheme : light_ColorTheme));

     }

+ return iFalse;

+ }

+ else if (equal_Command(cmd, "updater.check")) {

+ checkNow_Updater();

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.sideicon.changed")) {

- d->prefs.sideIcon = arg_Command(cmd) != 0;

- postRefresh_App();

+ else if (equal_Command(cmd, "fontpack.enable")) {

+ const iString *packId = collect_String(suffix_Command(cmd, "id"));

+ enablePack_Fonts(packId, arg_Command(cmd));

+ postCommand_App("navigate.reload");

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.centershort.changed")) {

- d->prefs.centerShortDocs = arg_Command(cmd) != 0;

- if (!isFrozen) {

- invalidate_Window(d->window);

+#if defined (LAGRANGE_ENABLE_IPC)

+ else if (equal_Command(cmd, "ipc.list.urls")) {

+ iProcessId pid = argLabel_Command(cmd, "pid");

+ if (pid) {

+ iString *urls = collectNew_String();

+ iConstForEach(ObjectList, i, iClob(listDocuments_App(NULL))) {

+ append_String(urls, url_DocumentWidget(i.object));

+ appendCStr_String(urls, "\n");

+ }

+ write_Ipc(pid, urls, response_IpcWrite);

     }

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.collapsepreonload.changed")) {

- d->prefs.collapsePreOnLoad = arg_Command(cmd) != 0;

+ else if (equal_Command(cmd, "ipc.active.url")) {

+ write_Ipc(argLabel_Command(cmd, "pid"),

+ collectNewFormat_String(

+ "%s\n", d->window ? cstr_String(url_DocumentWidget(document_App())) : ""),

+ response_IpcWrite);

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.hoverlink.changed")) {

- d->prefs.hoverLink = arg_Command(cmd) != 0;

- postRefresh_App();

+ else if (equal_Command(cmd, "ipc.signal")) {

+ if (argLabel_Command(cmd, "raise")) {

+ if (d->window && d->window->base.win) {

+ SDL_RaiseWindow(d->window->base.win);

+ }

+ }

+ signal_Ipc(arg_Command(cmd));

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.hoverlink.toggle")) {

- d->prefs.hoverLink = !d->prefs.hoverLink;

- postRefresh_App();

- return iTrue;

+#endif /* defined (LAGRANGE_ENABLE_IPC) */

+ else if (equal_Command(cmd, "quit")) {

+ SDL_Event ev;

+ ev.type = SDL_QUIT;

+ SDL_PushEvent(&ev);

 }

- else if (equal_Command(cmd, "prefs.dataurl.openimages.changed")) {

- d->prefs.openDataUrlImagesOnLoad = arg_Command(cmd) != 0;

+ return iFalse;

+}

+

+iBool handleCommand_App(const char *cmd) {

+ iApp *d = &app_;

+ const iBool isFrozen = !d->window || d->window->isDrawFrozen;

+ const iBool isHeadless = numWindows_App() == 0;

+ if (handleNonWindowRelatedCommand_App_(d, cmd)) {

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.archive.openindex.changed")) {

- d->prefs.openArchiveIndexPages = arg_Command(cmd) != 0;

+ if (isHeadless) {

+ /* All the subsequent commands assume that a window exists. */

+ return iFalse;

+ }

+ /* TODO: Maybe break this up a little bit? There's a very long list of ifs here. */

+ if (equal_Command(cmd, "config.error")) {

+ makeSimpleMessage_Widget(uiTextCaution_ColorEscape "CONFIG ERROR",

+ format_CStr("Error in config file: %s\n"

+ "See "about:debug" for details.",

+ suffixPtr_Command(cmd, "where")));

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.bookmarks.addbottom.changed")) {

- d->prefs.addBookmarksToBottom = arg_Command(cmd) != 0;

+ else if (equal_Command(cmd, "ui.split")) {

+ if (argLabel_Command(cmd, "swap")) {

+ swapRoots_MainWindow(d->window);

+ return iTrue;

+ }

+ if (argLabel_Command(cmd, "focusother")) {

+ iWindow *baseWin = &d->window->base;

+ if (baseWin->roots[1]) {

+ baseWin->keyRoot =

+ (baseWin->keyRoot == baseWin->roots[1] ? baseWin->roots[0] : baseWin->roots[1]);

+ }

+ }

+ d->window->pendingSplitMode =

+ (argLabel_Command(cmd, "axis") ? vertical_WindowSplit : 0) | (arg_Command(cmd) << 1);

+ const char *url = suffixPtr_Command(cmd, "url");

+ setCStr_String(d->window->pendingSplitUrl, url ? url : "");

+ if (hasLabel_Command(cmd, "origin")) {

+ set_String(d->window->pendingSplitOrigin, string_Command(cmd, "origin"));

+ }

+ postRefresh_App();

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.font.warnmissing.changed")) {

- d->prefs.warnAboutMissingGlyphs = arg_Command(cmd) != 0;

+ else if (equal_Command(cmd, "window.maximize")) {

+ const size_t winIndex = argU32Label_Command(cmd, "index");

+ if (winIndex < size_PtrArray(&d->mainWindows)) {

+ iMainWindow *win = at_PtrArray(&d->mainWindows, winIndex);

+ if (!argLabel_Command(cmd, "toggle")) {

+ setSnap_MainWindow(win, maximized_WindowSnap);

+ }

+ else {

+ setSnap_MainWindow(

+ win, snap_MainWindow(win) == maximized_WindowSnap ? 0 : maximized_WindowSnap);

+ }

+ }

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.animate.changed")) {

- d->prefs.uiAnimations = arg_Command(cmd) != 0;

+ else if (equal_Command(cmd, "window.fullscreen")) {

+ const iBool wasFull = snap_MainWindow(d->window) == fullscreen_WindowSnap;

+ setSnap_MainWindow(d->window, wasFull ? 0 : fullscreen_WindowSnap);

+ postCommandf_App("window.fullscreen.changed arg:%d", !wasFull);

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.blink.changed")) {

- d->prefs.blinkingCursor = arg_Command(cmd) != 0;

+ else if (equal_Command(cmd, "font.reset")) {

+ resetFonts_App();

     return iTrue;

 }

- else if (equal_Command(cmd, "prefs.time.24h.changed")) {

- d->prefs.time24h = arg_Command(cmd) != 0;

+ else if (equal_Command(cmd, "font.reload")) {

+ reload_Fonts(); /* also does font cache reset, window invalidation */

     return iTrue;

 }

- else if (equal_Command(cmd, "saturation.set")) {

- d->prefs.saturation = (float) arg_Command(cmd) / 100.0f;

- if (!isFrozen) {

- invalidate_Window(d->window);

- }

+ else if (equal_Command(cmd, "font.find")) {

+ searchOnlineLibraryForCharacters_Fonts(string_Command(cmd, "chars"));

     return iTrue;

 }

- else if (equal_Command(cmd, "cachesize.set")) {

- d->prefs.maxCacheSize = arg_Command(cmd);

- if (d->prefs.maxCacheSize <= 0) {

- d->prefs.maxCacheSize = 0;

+ else if (equal_Command(cmd, "font.found")) {

+ if (hasLabel_Command(cmd, "error")) {

+ makeSimpleMessage_Widget("${heading.glyphfinder}",

+ format_CStr("%d %s",

+ argLabel_Command(cmd, "error"),

+ suffixPtr_Command(cmd, "msg")));

+ return iTrue;

     }

+ iString *src = collectNew_String();

+ setCStr_String(src, "# ${heading.glyphfinder.results}\n\n");

+ iRangecc path = iNullRange;

+ iBool isFirst = iTrue;

+ while (nextSplit_Rangecc(range_Command(cmd, "packs"), ",", &path)) {

+ if (isFirst) {

+ appendCStr_String(src, "${glyphfinder.results}\n\n");

+ }

+ iRangecc fpath = path;

+ iRangecc fsize = path;

+ fpath.end = strchr(fpath.start, ';');

+ fsize.start = fpath.end + 1;

+ const uint32_t size = strtoul(fsize.start, NULL, 10);

+ appendFormat_String(src, "=> gemini://skyjake.fi/fonts/%s %s (%.1f MB)\n",

+ cstr_Rangecc(fpath),

+ cstr_Rangecc(fpath),

+ (double) size / 1.0e6);

+ isFirst = iFalse;

+ }

+ if (isFirst) {

+ appendFormat_String(src, "${glyphfinder.results.empty}\n");

+ }

+ appendCStr_String(src, "\n=> about:fonts ${menu.fonts}");

+ iDocumentWidget *page = newTab_App(NULL, iTrue);

+ translate_Lang(src);

+ setUrlAndSource_DocumentWidget(page,

+ collectNewCStr_String(""),

+ collectNewCStr_String("text/gemini"),

+ utf8_String(src));

     return iTrue;

 }

- else if (equal_Command(cmd, "memorysize.set")) {

- d->prefs.maxMemorySize = arg_Command(cmd);

- if (d->prefs.maxMemorySize <= 0) {

- d->prefs.maxMemorySize = 0;

+ else if (equal_Command(cmd, "font.set")) {

+ if (!isFrozen) {

+ setFreezeDraw_MainWindow(get_MainWindow(), iTrue);

     }

- return iTrue;

- }

- else if (equal_Command(cmd, "urlsize.set")) {

- d->prefs.maxUrlSize = arg_Command(cmd);

- if (d->prefs.maxUrlSize < 1024) {

- d->prefs.maxUrlSize = 1024; /* Gemini protocol requirement */

+ struct {

+ const char *label;

+ enum iPrefsString ps;

+ int fontId;

+ } params[] = {

+ { "ui", uiFont_PrefsString, default_FontId },

+ { "mono", monospaceFont_PrefsString, monospace_FontId },

+ { "heading", headingFont_PrefsString, documentHeading_FontId },

+ { "body", bodyFont_PrefsString, documentBody_FontId },

+ { "monodoc", monospaceDocumentFont_PrefsString, documentMonospace_FontId },

+ };

+ iBool wasChanged = iFalse;

+ iForIndices(i, params) {

+ if (hasLabel_Command(cmd, params[i].label)) {

+ iString *ps = &d->prefs.strings[params[i].ps];

+ const iString *newFont = string_Command(cmd, params[i].label);

+ if (!equal_String(ps, newFont)) {

+ set_String(ps, newFont);

+ wasChanged = iTrue;

+ }

+ }

     }

- return iTrue;

- }

- else if (equal_Command(cmd, "searchurl")) {

- iString *url = &d->prefs.strings[searchUrl_PrefsString];

- setCStr_String(url, suffixPtr_Command(cmd, "address"));

- if (startsWith_String(url, "//")) {

- prependCStr_String(url, "gemini:");

+ if (wasChanged) {

+ if (isFinishedLaunching_App()) { /* there's a reset when launch is finished */

+ resetFonts_Text(text_Window(get_MainWindow()));

+ }

+ postCommand_App("font.changed");

     }

- if (!isEmpty_String(url) && !startsWithCase_String(url, "gemini://")) {

- prependCStr_String(url, "gemini://");

+ if (!isFrozen) {

+ postCommand_App("window.unfreeze");

     }

     return iTrue;

 }

- else if (equal_Command(cmd, "proxy.gemini")) {

- setCStr_String(&d->prefs.strings[geminiProxy_PrefsString], suffixPtr_Command(cmd, "address"));

- return iTrue;

- }

- else if (equal_Command(cmd, "proxy.gopher")) {

- setCStr_String(&d->prefs.strings[gopherProxy_PrefsString], suffixPtr_Command(cmd, "address"));

- return iTrue;

- }

- else if (equal_Command(cmd, "proxy.http")) {

- setCStr_String(&d->prefs.strings[httpProxy_PrefsString], suffixPtr_Command(cmd, "address"));

- return iTrue;

- }

-#if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT)

- else if (equal_Command(cmd, "downloads")) {

- setCStr_String(&d->prefs.strings[downloadDir_PrefsString], suffixPtr_Command(cmd, "path"));

- return iTrue;

- }

-#endif

- else if (equal_Command(cmd, "downloads.open")) {

- postCommandf_App("open newtab:%d url:%s",

- argLabel_Command(cmd, "newtab"),

- cstrCollect_String(makeFileUrl_String(downloadDir_App())));

- return iTrue;

- }

- else if (equal_Command(cmd, "ca.file")) {

- setCStr_String(&d->prefs.strings[caFile_PrefsString], suffixPtr_Command(cmd, "path"));

- if (!argLabel_Command(cmd, "noset")) {

- setCACertificates_TlsRequest(&d->prefs.strings[caFile_PrefsString], &d->prefs.strings[caPath_PrefsString]);

+ else if (equal_Command(cmd, "zoom.set")) {

+ if (!isFrozen) {

+ setFreezeDraw_MainWindow(get_MainWindow(), iTrue); /* no intermediate draws before docs updated */

     }

- return iTrue;

- }

- else if (equal_Command(cmd, "ca.path")) {

- setCStr_String(&d->prefs.strings[caPath_PrefsString], suffixPtr_Command(cmd, "path"));

- if (!argLabel_Command(cmd, "noset")) {

- setCACertificates_TlsRequest(&d->prefs.strings[caFile_PrefsString], &d->prefs.strings[caPath_PrefsString]);

+ if (arg_Command(cmd) != d->prefs.zoomPercent) {

+ d->prefs.zoomPercent = arg_Command(cmd);

+ invalidateCachedDocuments_App_();

+ }

+ setDocumentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f);

+ if (!isFrozen) {

+ postCommand_App("font.changed");

+ postCommand_App("window.unfreeze");

     }

     return iTrue;

 }

- else if (equal_Command(cmd, "search")) {

- const int newTab = argLabel_Command(cmd, "newtab");

- const iString *query = collect_String(suffix_Command(cmd, "query"));

- if (!isLikelyUrl_String(query)) {

- const iString *url = searchQueryUrl_App(query);

- if (!isEmpty_String(url)) {

- postCommandf_App("open newtab:%d url:%s", newTab, cstr_String(url));

- }

+ else if (equal_Command(cmd, "zoom.delta")) {

+ if (!isFrozen) {

+ setFreezeDraw_MainWindow(get_MainWindow(), iTrue); /* no intermediate draws before docs updated */

     }

- else {

- postCommandf_App("open newtab:%d url:%s", newTab, cstr_String(query));

+ int delta = arg_Command(cmd);

+ if (d->prefs.zoomPercent < 100 || (delta < 0 && d->prefs.zoomPercent == 100)) {

+ delta /= 2;

+ }

+ d->prefs.zoomPercent = iClamp(d->prefs.zoomPercent + delta, 50, 200);

+ invalidateCachedDocuments_App_();

+ setDocumentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f);

+ if (!isFrozen) {

+ postCommand_App("font.changed");

+ postCommand_App("window.unfreeze");

     }

     return iTrue;

 }

- else if (equal_Command(cmd, "reveal")) {

- const iString *path = NULL;

- if (hasLabel_Command(cmd, "path")) {

- path = suffix_Command(cmd, "path");

- }

- else if (hasLabel_Command(cmd, "url")) {

- path = collect_String(localFilePathFromUrl_String(suffix_Command(cmd, "url")));

- }

- if (path) {

- revealPath_App(path);

+ else if (equal_Command(cmd, "hidetoolbarscroll")) {

+ d->prefs.hideToolbarOnScroll = arg_Command(cmd);

+ if (!d->prefs.hideToolbarOnScroll) {

+ showToolbar_Root(get_Root(), iTrue);

     }

     return iTrue;

 }

@@ -3337,20 +3464,6 @@ iBool handleCommand_App(const char *cmd) {

#endif

     return iFalse;

 }

- else if (equal_Command(cmd, "window.new")) {

- iMainWindow *newWin = new_MainWindow(initialWindowRect_App_(d, numWindows_App()));

- addWindow_App(newWin); /* takes ownership */

- SDL_ShowWindow(newWin->base.win);

- setCurrent_Window(newWin);

- if (hasLabel_Command(cmd, "url")) {

- postCommandf_Root(newWin->base.roots[0], "~open %s", cmd + 11 /* all arguments passed on */);

- }

- else {

- postCommand_Root(newWin->base.roots[0], "~navigate.home");

- }

- postCommand_Root(newWin->base.roots[0], "~window.unfreeze");

- return iTrue;

- }

 else if (equal_Command(cmd, "tabs.new")) {

     const iBool isDuplicate = argLabel_Command(cmd, "duplicate") != 0;

     newTab_App(isDuplicate ? document_App() : NULL, iTrue);

@@ -3411,12 +3524,16 @@ iBool handleCommand_App(const char *cmd) {

             }

         }

     }

+#if defined (iPlatformAppleDesktop)

+ closeWindow_App(d->window);

+#else

     else if (numWindows_App() > 1) {

         closeWindow_App(d->window);

     }

     else {

         postCommand_App("quit");

     }

+#endif

     return iTrue;

 }

 else if (equal_Command(cmd, "keyroot.next")) {

@@ -3426,11 +3543,6 @@ iBool handleCommand_App(const char *cmd) {

     }

     return iTrue;

 }

- else if (equal_Command(cmd, "quit")) {

- SDL_Event ev;

- ev.type = SDL_QUIT;

- SDL_PushEvent(&ev);

- }

 else if (equal_Command(cmd, "preferences")) {

     iWidget *dlg = makePreferences_Widget();

     updatePrefsThemeButtons_(dlg);

@@ -3618,27 +3730,6 @@ iBool handleCommand_App(const char *cmd) {

     }

     return iTrue;

 }

- else if (equal_Command(cmd, "bookmarks.sort")) {

- sort_Bookmarks(d->bookmarks, arg_Command(cmd), cmpTitleAscending_Bookmark);

- postCommand_App("bookmarks.changed");

- return iTrue;

- }

- else if (equal_Command(cmd, "bookmarks.reload.remote")) {

- fetchRemote_Bookmarks(bookmarks_App());

- return iTrue;

- }

- else if (equal_Command(cmd, "bookmarks.request.finished")) {

- requestFinished_Bookmarks(bookmarks_App(), pointerLabel_Command(cmd, "req"));

- return iTrue;

- }

- else if (equal_Command(cmd, "bookmarks.changed")) {

- save_Bookmarks(d->bookmarks, dataDir_App_());

- return iFalse;

- }

- else if (equal_Command(cmd, "feeds.refresh")) {

- refresh_Feeds();

- return iTrue;

- }

 else if (startsWith_CStr(cmd, "feeds.update.")) {

     const iWidget *navBar = findChild_Widget(get_Window()->roots[0]->widget, "navbar");

     iAnyObject *prog = findChild_Widget(navBar, "feeds.progress");

@@ -3663,10 +3754,6 @@ iBool handleCommand_App(const char *cmd) {

     }

     return iFalse;

 }

- else if (equal_Command(cmd, "visited.changed")) {

- save_Visited(d->visited, dataDir_App_());

- return iFalse;

- }

 else if (equal_Command(cmd, "document.changed")) {

     /* Set of open tabs has changed. */

     postCommand_App("document.openurls.changed");

@@ -3691,29 +3778,6 @@ iBool handleCommand_App(const char *cmd) {

     postRefresh_App();

     return iTrue;

 }

- else if (equal_Command(cmd, "ident.signin")) {

- const iString *url = collect_String(suffix_Command(cmd, "url"));

- signIn_GmCerts(

- d->certs,

- findIdentity_GmCerts(d->certs, collect_Block(hexDecode_Rangecc(range_Command(cmd, "ident")))),

- url);

- postCommand_App("navigate.reload");

- postCommand_App("idents.changed");

- return iTrue;

- }

- else if (equal_Command(cmd, "ident.signout")) {

- iGmIdentity *ident = findIdentity_GmCerts(

- d->certs, collect_Block(hexDecode_Rangecc(range_Command(cmd, "ident"))));

- if (arg_Command(cmd)) {

- clearUse_GmIdentity(ident);

- }

- else {

- setUse_GmIdentity(ident, collect_String(suffix_Command(cmd, "url")), iFalse);

- }

- postCommand_App("navigate.reload");

- postCommand_App("idents.changed");

- return iTrue;

- }

 else if (equal_Command(cmd, "ident.switch")) {

     /* This is different than "ident.signin" in that the currently used identity's activation

        URL is used instead of the current one. */

@@ -3733,33 +3797,6 @@ iBool handleCommand_App(const char *cmd) {

     }

     return iTrue;

 }

- else if (equal_Command(cmd, "idents.changed")) {

- saveIdentities_GmCerts(d->certs);

- return iFalse;

- }

- else if (equal_Command(cmd, "os.theme.changed")) {

- const int dark = argLabel_Command(cmd, "dark");

- d->isDarkSystemTheme = dark;

- if (d->prefs.useSystemTheme) {

- const int contrast = argLabel_Command(cmd, "contrast");

- const int preferred = d->prefs.systemPreferredColorTheme[dark ^ 1];

- postCommandf_App("theme.set arg:%d auto:1",

- preferred >= 0 ? preferred

- : dark ? (contrast ? pureBlack_ColorTheme : dark_ColorTheme)

- : (contrast ? pureWhite_ColorTheme : light_ColorTheme));

- }

- return iFalse;

- }

- else if (equal_Command(cmd, "updater.check")) {

- checkNow_Updater();

- return iTrue;

- }

- else if (equal_Command(cmd, "fontpack.enable")) {

- const iString *packId = collect_String(suffix_Command(cmd, "id"));

- enablePack_Fonts(packId, arg_Command(cmd));

- postCommand_App("navigate.reload");

- return iTrue;

- }

 else if (equal_Command(cmd, "fontpack.delete")) {

     const iString *packId = collect_String(suffix_Command(cmd, "id"));

     if (isEmpty_String(packId)) {

@@ -3840,35 +3877,6 @@ iBool handleCommand_App(const char *cmd) {

     }

     return iTrue;

 }

-#if defined (LAGRANGE_ENABLE_IPC)

- else if (equal_Command(cmd, "ipc.list.urls")) {

- iProcessId pid = argLabel_Command(cmd, "pid");

- if (pid) {

- iString *urls = collectNew_String();

- iConstForEach(ObjectList, i, iClob(listDocuments_App(NULL))) {

- append_String(urls, url_DocumentWidget(i.object));

- appendCStr_String(urls, "\n");

- }

- write_Ipc(pid, urls, response_IpcWrite);

- }

- return iTrue;

- }

- else if (equal_Command(cmd, "ipc.active.url")) {

- write_Ipc(argLabel_Command(cmd, "pid"),

- collectNewFormat_String("%s\n", cstr_String(url_DocumentWidget(document_App()))),

- response_IpcWrite);

- return iTrue;

- }

- else if (equal_Command(cmd, "ipc.signal")) {

- if (argLabel_Command(cmd, "raise")) {

- if (d->window && d->window->base.win) {

- SDL_RaiseWindow(d->window->base.win);

- }

- }

- signal_Ipc(arg_Command(cmd));

- return iTrue;

- }

-#endif /* defined (LAGRANGE_ENABLE_IPC) */

 else {

     return iFalse;

 }

diff --git a/src/ui/keys.c b/src/ui/keys.c

index 3428bcc8..40859f7c 100644

--- a/src/ui/keys.c

+++ b/src/ui/keys.c

@@ -451,7 +451,7 @@ void setLabel_Keys(int id, const char *label) {



iBool processEvent_Keys(const SDL_Event *ev) {

 iKeys *d = &keys_;

- iRoot *root = get_Window()->keyRoot;

+ iRoot *root = get_Window() ? get_Window()->keyRoot : NULL;

 if (ev->type == SDL_KEYDOWN || ev->type == SDL_KEYUP) {

     const iBinding *bind = find_Keys_(d, ev->key.keysym.sym, keyMods_Sym(ev->key.keysym.mod));

     if (bind) {

diff --git a/src/ui/widget.c b/src/ui/widget.c

index cb373c43..138b219e 100644

--- a/src/ui/widget.c

+++ b/src/ui/widget.c

@@ -2154,6 +2154,9 @@ static const iWidget *findFocusRoot_Widget_(const iWidget *d) {

}



iAny *findFocusable_Widget(const iWidget *startFrom, enum iWidgetFocusDir focusDir) {

+ if (!get_Window()) {

+ return NULL;

+ }

 iRoot *uiRoot = (startFrom ? startFrom->root : get_Window()->keyRoot);

 const iWidget *focusRoot = findFocusRoot_Widget_(uiRoot->widget);

 iAssert(focusRoot != NULL);

diff --git a/src/ui/window.c b/src/ui/window.c

index cbfd0f5c..2ec64f0e 100644

--- a/src/ui/window.c

+++ b/src/ui/window.c

@@ -186,22 +186,32 @@ const iMenuItem topLevelMenus_Window[6] = {



#if defined (LAGRANGE_MAC_MENUBAR)



+static iBool macMenusInserted_;

+

static void insertMacMenus_(void) {

+ if (macMenusInserted_) {

+ return;

+ }

 insertMenuItems_MacOS("${menu.title.file}", 1, fileMenuItems_, iElemCount(fileMenuItems_));

 insertMenuItems_MacOS("${menu.title.edit}", 2, editMenuItems_, iElemCount(editMenuItems_));

 insertMenuItems_MacOS("${menu.title.view}", 3, viewMenuItems_, iElemCount(viewMenuItems_));

 insertMenuItems_MacOS("${menu.title.bookmarks}", 4, bookmarksMenuItems_, iElemCount(bookmarksMenuItems_));

 insertMenuItems_MacOS("${menu.title.identity}", 5, identityMenuItems_, iElemCount(identityMenuItems_));

 insertMenuItems_MacOS("${menu.title.help}", 7, helpMenuItems_, iElemCount(helpMenuItems_));

+ macMenusInserted_ = iTrue;

}



static void removeMacMenus_(void) {

+ if (!macMenusInserted_) {

+ return;

+ }

 removeMenu_MacOS(7);

 removeMenu_MacOS(5);

 removeMenu_MacOS(4);

 removeMenu_MacOS(3);

 removeMenu_MacOS(2);

 removeMenu_MacOS(1);

+ macMenusInserted_ = iFalse;

}



#endif /* LAGRANGE_MAC_MENUBAR */

@@ -246,9 +256,7 @@ static void windowSizeChanged_MainWindow_(iMainWindow *d) {



static void setupUserInterface_MainWindow(iMainWindow *d) {

#if defined (LAGRANGE_MAC_MENUBAR)

- if (numWindows_App() == 0) {

- insertMacMenus_(); /* TODO: Shouldn't this be in the App? */

- }

+ insertMacMenus_(); /* TODO: Shouldn't this be in the App? */

#endif

 /* One root is created by default. */

 d->base.roots[0] = new_Root();

@@ -1652,6 +1660,9 @@ void setKeyboardHeight_MainWindow(iMainWindow *d, int height) {



iObjectList *listDocuments_MainWindow(iMainWindow *d, const iRoot *rootOrNull) {

 iObjectList *docs = new_ObjectList();

+ if (!d) {

+ return docs;

+ }

 iForIndices(i, d->base.roots) {

     iRoot *root = d->base.roots[i];

     if (!root) continue;

Proxy Information
Original URL
gemini://git.skyjake.fi/lagrange/work%2Fv1.13/pcdiff/ab3ed41e6d6a2f30e781c7202741e066a2b21d7b
Status Code
Success (20)
Meta
text/plain
Capsule Response Time
78.397871 milliseconds
Gemini-to-HTML Time
15.547772 milliseconds

This content has been proxied by September (ba2dc).