Lagrange [work/v1.10]

Viewing unsupported files in another app

=> 336b9d7272ed8b1a9dccee27dec20e3377ee0c74

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 94b70ea0..37afe9d2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -330,6 +330,7 @@ target_include_directories (app PUBLIC
 target_compile_options (app PUBLIC
     -Werror=implicit-function-declaration
     -Werror=incompatible-pointer-types
+    -Wno-deprecated-declarations
     ${SDL2_CFLAGS}
     -DSTB_VORBIS_NO_STDIO=1
     -DSTB_VORBIS_NO_INTEGER_CONVERSION=1
diff --git a/po/en.po b/po/en.po
index 04b6ac19..61d7d3f7 100644
--- a/po/en.po
+++ b/po/en.po
@@ -196,6 +196,9 @@ msgstr "Find on Page"
 msgid "macos.menu.find"
 msgstr "Find"
 
+msgid "menu.open.external"
+msgstr "Open in Another App"
+
 # Used on iOS. "Files" refers to Apple's iOS app where you can pick an iCloud folder.
 msgid "menu.save.files"
 msgstr "Save to Files"
diff --git a/res/lang/cs.bin b/res/lang/cs.bin
index 97197051..ec75dc3a 100644
Binary files a/res/lang/cs.bin and b/res/lang/cs.bin differ
diff --git a/res/lang/de.bin b/res/lang/de.bin
index d2dceda6..96fd003f 100644
Binary files a/res/lang/de.bin and b/res/lang/de.bin differ
diff --git a/res/lang/en.bin b/res/lang/en.bin
index 942dfdc2..26e3c36a 100644
Binary files a/res/lang/en.bin and b/res/lang/en.bin differ
diff --git a/res/lang/eo.bin b/res/lang/eo.bin
index 513c3a0b..2df0e6b8 100644
Binary files a/res/lang/eo.bin and b/res/lang/eo.bin differ
diff --git a/res/lang/es.bin b/res/lang/es.bin
index 92037aea..be919a78 100644
Binary files a/res/lang/es.bin and b/res/lang/es.bin differ
diff --git a/res/lang/es_MX.bin b/res/lang/es_MX.bin
index 7b7e1219..d88b3386 100644
Binary files a/res/lang/es_MX.bin and b/res/lang/es_MX.bin differ
diff --git a/res/lang/fi.bin b/res/lang/fi.bin
index 243e9740..5dba618c 100644
Binary files a/res/lang/fi.bin and b/res/lang/fi.bin differ
diff --git a/res/lang/fr.bin b/res/lang/fr.bin
index 507cbb4c..830bbc72 100644
Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ
diff --git a/res/lang/gl.bin b/res/lang/gl.bin
index 447ed72d..4df0c57c 100644
Binary files a/res/lang/gl.bin and b/res/lang/gl.bin differ
diff --git a/res/lang/hu.bin b/res/lang/hu.bin
index c2a085ec..f4c826c8 100644
Binary files a/res/lang/hu.bin and b/res/lang/hu.bin differ
diff --git a/res/lang/ia.bin b/res/lang/ia.bin
index 5305304c..6a18bd7d 100644
Binary files a/res/lang/ia.bin and b/res/lang/ia.bin differ
diff --git a/res/lang/ie.bin b/res/lang/ie.bin
index 69ac42d4..84cc2cd7 100644
Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ
diff --git a/res/lang/isv.bin b/res/lang/isv.bin
index 26c23dc5..1a31a1f7 100644
Binary files a/res/lang/isv.bin and b/res/lang/isv.bin differ
diff --git a/res/lang/pl.bin b/res/lang/pl.bin
index 1ad99e5d..a003ce03 100644
Binary files a/res/lang/pl.bin and b/res/lang/pl.bin differ
diff --git a/res/lang/ru.bin b/res/lang/ru.bin
index 03092acf..807e032d 100644
Binary files a/res/lang/ru.bin and b/res/lang/ru.bin differ
diff --git a/res/lang/sk.bin b/res/lang/sk.bin
index 4d91e8a6..33c83c09 100644
Binary files a/res/lang/sk.bin and b/res/lang/sk.bin differ
diff --git a/res/lang/sr.bin b/res/lang/sr.bin
index 823818e2..7c4c8de1 100644
Binary files a/res/lang/sr.bin and b/res/lang/sr.bin differ
diff --git a/res/lang/tok.bin b/res/lang/tok.bin
index 50743783..601b07f3 100644
Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ
diff --git a/res/lang/tr.bin b/res/lang/tr.bin
index 543739cd..8cbaa556 100644
Binary files a/res/lang/tr.bin and b/res/lang/tr.bin differ
diff --git a/res/lang/uk.bin b/res/lang/uk.bin
index f866f86f..48adc59b 100644
Binary files a/res/lang/uk.bin and b/res/lang/uk.bin differ
diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin
index 179d87db..759e618a 100644
Binary files a/res/lang/zh_Hans.bin and b/res/lang/zh_Hans.bin differ
diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin
index aad28410..2bfc817b 100644
Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ
diff --git a/src/app.c b/src/app.c
index b04b6388..b0b36783 100644
--- a/src/app.c
+++ b/src/app.c
@@ -1021,7 +1021,7 @@ const iString *downloadDir_App(void) {
     return collect_String(cleaned_Path(&app_.prefs.strings[downloadDir_PrefsString]));
 }
 
-const iString *downloadPathForUrl_App(const iString *url, const iString *mime) {
+const iString *fileNameForUrl_App(const iString *url, const iString *mime) {
     /* Figure out a file name from the URL. */
     iUrl parts;
     init_Url(&parts, url);
@@ -1047,22 +1047,27 @@ const iString *downloadPathForUrl_App(const iString *url, const iString *mime) {
         }
     }
     if (startsWith_String(name, "~")) {
-        /* This would be interpreted as a reference to a home directory. */
+        /* This might be interpreted as a reference to a home directory. */
         remove_Block(&name->chars, 0, 1);
     }
-    iString *savePath = concat_Path(downloadDir_App(), name);
-    if (lastIndexOfCStr_String(savePath, ".") == iInvalidPos) {
+    if (lastIndexOfCStr_String(name, ".") == iInvalidPos) {
+        /* TODO: Needs the inverse of `mediaTypeFromFileExtension_String()`. */
         /* No extension specified in URL. */
         if (startsWith_String(mime, "text/gemini")) {
-            appendCStr_String(savePath, ".gmi");
+            appendCStr_String(name, ".gmi");
         }
         else if (startsWith_String(mime, "text/")) {
-            appendCStr_String(savePath, ".txt");
+            appendCStr_String(name, ".txt");
         }
         else if (startsWith_String(mime, "image/")) {
-            appendCStr_String(savePath, cstr_String(mime) + 6);
+            appendCStr_String(name, cstr_String(mime) + 6);
         }
     }
+    return name;
+}
+
+const iString *downloadPathForUrl_App(const iString *url, const iString *mime) {
+    iString *savePath = concat_Path(downloadDir_App(), fileNameForUrl_App(url, mime));
     if (fileExists_FileInfo(savePath)) {
         /* Make it unique. */
         iDate now;
diff --git a/src/app.h b/src/app.h
index 50d3ac6b..0c336e65 100644
--- a/src/app.h
+++ b/src/app.h
@@ -110,6 +110,7 @@ enum iColorTheme    colorTheme_App      (void);
 const iString *     schemeProxy_App     (iRangecc scheme);
 iBool               willUseProxy_App    (const iRangecc scheme);
 const iString *     searchQueryUrl_App  (const iString *queryStringUnescaped);
+const iString *     fileNameForUrl_App  (const iString *url, const iString *mime);
 const iString *     downloadPathForUrl_App(const iString *url, const iString *mime);
 
 typedef void (*iTickerFunc)(iAny *);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 04257a1c..3771dd6c 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -2260,6 +2260,11 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
                                       0,
                                       format_CStr("document.setmediatype mime:%s", mtype) });
                 }
+                pushBack_Array(&items,
+                               &(iMenuItem){ export_Icon " ${menu.open.external}",
+                                             SDLK_RETURN,
+                                             KMOD_PRIMARY,
+                                             "document.save extview:1" });
                 pushBack_Array(
                     &items,
                     &(iMenuItem){ translateCStr_Lang(download_Icon " " saveToDownloads_Label),
@@ -3337,9 +3342,8 @@ static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) {
     return iFalse;
 }
 
-static const iString *saveToDownloads_(const iString *url, const iString *mime, const iBlock *content,
-                                       iBool showDialog) {
-    const iString *savePath = downloadPathForUrl_App(url, mime);
+static iBool saveToFile_(const iString *savePath, const iBlock *content, iBool showDialog) {
+    iBool ok = iFalse;
     /* Write the file. */ {
         iFile *f = new_File(savePath);
         if (open_File(f, writeOnly_FileMode)) {
@@ -3351,21 +3355,21 @@ static const iString *saveToDownloads_(const iString *url, const iString *mime,
             exportDownloadedFile_iOS(savePath);
 #else
             if (showDialog) {
-            const iMenuItem items[2] = {
-                { "${dlg.save.opendownload}", 0, 0,
-                    format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) },
-                { "${dlg.message.ok}", 0, 0, "message.ok" },
-            };
-            makeMessage_Widget(uiHeading_ColorEscape "${heading.save}",
-                                   format_CStr("%s\n${dlg.save.size} %.3f %s",
-                                               cstr_String(path_File(f)),
-                                           isMega ? size / 1.0e6f : (size / 1.0e3f),
-                                           isMega ? "${mb}" : "${kb}"),
-                                   items,
-                                   iElemCount(items));
+                const iMenuItem items[2] = {
+                    { "${dlg.save.opendownload}", 0, 0,
+                        format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) },
+                    { "${dlg.message.ok}", 0, 0, "message.ok" },
+                };
+                makeMessage_Widget(uiHeading_ColorEscape "${heading.save}",
+                                       format_CStr("%s\n${dlg.save.size} %.3f %s",
+                                                   cstr_String(path_File(f)),
+                                               isMega ? size / 1.0e6f : (size / 1.0e3f),
+                                               isMega ? "${mb}" : "${kb}"),
+                                       items,
+                                       iElemCount(items));
             }
 #endif
-            return savePath;
+            ok = iTrue;
         }
         else {
             makeSimpleMessage_Widget(uiTextCaution_ColorEscape "${heading.save.error}",
@@ -3373,7 +3377,16 @@ static const iString *saveToDownloads_(const iString *url, const iString *mime,
         }
         iRelease(f);
     }
-    return collectNew_String();
+    return ok;
+}
+
+static const iString *saveToDownloads_(const iString *url, const iString *mime, const iBlock *content,
+                                       iBool showDialog) {
+    const iString *savePath = downloadPathForUrl_App(url, mime);
+    if (!saveToFile_(savePath, content, showDialog)) {
+        return collectNew_String();
+    }
+    return savePath;
 }
 
 static void addAllLinks_(void *context, const iGmRun *run) {
@@ -4111,12 +4124,27 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
                                      "${dlg.save.incomplete}");
         }
         else if (!isEmpty_Block(&d->sourceContent)) {
-            const iBool    doOpen   = argLabel_Command(cmd, "open");
-            const iString *savePath = saveToDownloads_(d->mod.url, &d->sourceMime,
-                                                       &d->sourceContent, !doOpen);
-            if (!isEmpty_String(savePath) && doOpen) {
-                postCommandf_Root(
-                    w->root, "!open url:%s", cstrCollect_String(makeFileUrl_String(savePath)));
+            if (argLabel_Command(cmd, "extview")) {
+                iString       *tmpPath = collectNewCStr_String(tmpnam(NULL));
+                const iRangecc tmpDir  = dirName_Path(tmpPath);
+                set_String(
+                    tmpPath,
+                    collect_String(concat_Path(collectNewRange_String(tmpDir),
+                                               fileNameForUrl_App(d->mod.url, &d->sourceMime))));
+                if (saveToFile_(tmpPath, &d->sourceContent, iFalse)) {
+                    /* TODO: Remember this temporary path and delete it when quitting the app. */
+                    postCommandf_Root(w->root, "!open default:1 url:%s",
+                                      cstrCollect_String(makeFileUrl_String(tmpPath)));
+                }
+            }
+            else {
+                const iBool    doOpen   = argLabel_Command(cmd, "open");
+                const iString *savePath = saveToDownloads_(d->mod.url, &d->sourceMime,
+                                                           &d->sourceContent, !doOpen);
+                if (!isEmpty_String(savePath) && doOpen) {
+                    postCommandf_Root(
+                        w->root, "!open url:%s", cstrCollect_String(makeFileUrl_String(savePath)));
+                }
             }
         }
         return iTrue;
Proxy Information
Original URL
gemini://git.skyjake.fi/lagrange/work%2Fv1.10/cdiff/336b9d7272ed8b1a9dccee27dec20e3377ee0c74
Status Code
Success (20)
Meta
text/gemini; charset=utf-8
Capsule Response Time
80.541857 milliseconds
Gemini-to-HTML Time
0.428033 milliseconds

This content has been proxied by September (ba2dc).