Lagrange [work/v1.13]

macOS: Running without windows

=> ab3ed41e6d6a2f30e781c7202741e066a2b21d7b

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/cdiff/ab3ed41e6d6a2f30e781c7202741e066a2b21d7b
Status Code
Success (20)
Meta
text/gemini; charset=utf-8
Capsule Response Time
246.490503 milliseconds
Gemini-to-HTML Time
2.334147 milliseconds

This content has been proxied by September (ba2dc).