summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2022-02-06 15:07:17 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2022-02-06 15:07:17 +0200
commit076e34aa8d1e57cbf3142abdc91c8e88d21b75d6 (patch)
tree3ee91315ee1a083b74d84637af28252f039fa084
parentedb260848a42de2e1af44cab93dbf267d20e1f6f (diff)
parent53ab74c3a25521faa6f41b40b1969a3b8e8ad027 (diff)
Merge branch 'dev' into work/v1.11
# Conflicts: # CMakeLists.txt # res/fonts/SmolEmoji-Regular.ttf # res/lang/gl.bin # src/app.c
-rw-r--r--AUTHORS.md19
-rw-r--r--CMakeLists.txt17
-rw-r--r--README.md4
-rw-r--r--po/eo.po17
-rw-r--r--po/gl.po10
-rw-r--r--po/ie.po124
-rw-r--r--res/about/android-version.gmi10
-rw-r--r--res/about/ios-version.gmi16
-rw-r--r--res/about/version-0.13.gmi246
-rw-r--r--res/about/version-1.5.gmi371
-rw-r--r--res/about/version.gmi643
-rw-r--r--res/fonts/SmolEmoji-Regular.ttfbin67168 -> 67168 bytes
-rw-r--r--res/lagrange.1149
-rw-r--r--res/lagrange.1.md127
-rw-r--r--res/lang/gl.binbin30585 -> 29969 bytes
-rwxr-xr-xres/makeman.sh3
-rw-r--r--src/app.c32
-rw-r--r--src/feeds.c7
-rw-r--r--src/gmdocument.c86
-rw-r--r--src/gmrequest.c6
-rw-r--r--src/gmutil.c62
-rw-r--r--src/gmutil.h2
-rw-r--r--src/gopher.c10
-rw-r--r--src/gopher.h2
-rw-r--r--src/ios.m32
-rw-r--r--src/macos.m13
-rw-r--r--src/prefs.c4
-rw-r--r--src/resources.c4
-rw-r--r--src/resources.h2
-rw-r--r--src/ui/color.c27
-rw-r--r--src/ui/documentwidget.c57
-rw-r--r--src/ui/indicatorwidget.c66
-rw-r--r--src/ui/inputwidget.c20
-rw-r--r--src/ui/mobile.c4
-rw-r--r--src/ui/paint.c6
-rw-r--r--src/ui/root.c31
-rw-r--r--src/ui/root.h2
-rw-r--r--src/ui/sidebarwidget.c57
-rw-r--r--src/ui/text.c42
-rw-r--r--src/ui/text.h2
-rw-r--r--src/ui/touch.c5
-rw-r--r--src/ui/window.c3
42 files changed, 1484 insertions, 856 deletions
diff --git a/AUTHORS.md b/AUTHORS.md
index 1555ab65..8672133f 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -5,17 +5,17 @@ Lagrange was created by Jaakko Keränen (<jaakko.keranen@iki.fi>) in July 2020.
5Legend: `C` code, `T` translation 5Legend: `C` code, `T` translation
6 6
7``` 7```
8C 3009 Jaakko Keränen <jaakko.keranen@iki.fi> 8C 3070 Jaakko Keränen <jaakko.keranen@iki.fi>
9CT 43 Nikolay Korotkiy <sikmir@gmail.com> 9CT 47 Nikolay Korotkiy <sikmir@gmail.com>
10 T 42 Alyssa Liddell <e-liss@tuta.io> 10 T 45 Alyssa Liddell <e-liss@tuta.io>
11 T 41 Olga Smirnova <mistresssilvara@hotmail.com> 11 T 42 Olga Smirnova <mistresssilvara@hotmail.com>
12 T 30 Anna “CyberTailor” <cyber@sysrq.in> 12 T 30 Anna “CyberTailor” <cyber@sysrq.in>
13 T 27 Страхиња Радић <contact@strahinja.org> 13 T 29 Страхиња Радић <contact@strahinja.org>
14 T 26 Shibo Lyu <github@of.sb> 14 T 26 Shibo Lyu <github@of.sb>
15 T 19 Wally Hackenslacker <mastor89@protonmail.com> 15 T 20 Wally Hackenslacker <mastor89@protonmail.com>
16 T 18 MCMic <come@chilliet.eu> 16 T 18 MCMic <come@chilliet.eu>
17 T 14 Xosé M <correoxm@disroot.org> 17 T 15 Xosé M <correoxm@disroot.org>
18 T 13 Tadeáš Erban <tadysekerbosek@gmail.com> 18 T 14 Tadeáš Erban <tadysekerbosek@gmail.com>
19 T 9 Aaron Fischer <mail@aaron-fischer.net> 19 T 9 Aaron Fischer <mail@aaron-fischer.net>
20 T 8 El Mau <public@correolibre.net> 20 T 8 El Mau <public@correolibre.net>
21 T 7 Waterrail <maksymiliankrol03@gmail.com> 21 T 7 Waterrail <maksymiliankrol03@gmail.com>
@@ -27,6 +27,7 @@ C 2 Alyssa Rosenzweig <alyssa@rosenzweig.io>
27 T 2 Andrij Mizyk <andmizyk@gmail.com> 27 T 2 Andrij Mizyk <andmizyk@gmail.com>
28 T 2 Arns Udovič <zordsdavini@arns.lt> 28 T 2 Arns Udovič <zordsdavini@arns.lt>
29C 2 Br0000k <77938600+Br0000k@users.noreply.github.com> 29C 2 Br0000k <77938600+Br0000k@users.noreply.github.com>
30 T 2 Emir SARI <emir_sari@icloud.com>
30 T 2 Gabriel de Oliveira Ferreira Machado <goliv04053@tutanota.com> 31 T 2 Gabriel de Oliveira Ferreira Machado <goliv04053@tutanota.com>
31 T 2 Jop Vernooy <jop@jopv.net> 32 T 2 Jop Vernooy <jop@jopv.net>
32C 2 Manos Pitsidianakis <el13635@mail.ntua.gr> 33C 2 Manos Pitsidianakis <el13635@mail.ntua.gr>
@@ -36,7 +37,6 @@ C 1 Adam Mizerski <adam@mizerski.pl>
36C 1 Charles <charles@cdaniels.net> 37C 1 Charles <charles@cdaniels.net>
37C 1 Dario Vladovic <d.vladimyr@gmail.com> 38C 1 Dario Vladovic <d.vladimyr@gmail.com>
38C 1 David Gillies <dave.gillies@gmail.com> 39C 1 David Gillies <dave.gillies@gmail.com>
39 T 1 Emir SARI <emir_sari@icloud.com>
40 T 1 Eric <spice2wolf@gmail.com> 40 T 1 Eric <spice2wolf@gmail.com>
41 T 1 Marek Ľach <mareklachbc@tutanota.com> 41 T 1 Marek Ľach <mareklachbc@tutanota.com>
42C 1 Raph M <raph@raphm.com> 42C 1 Raph M <raph@raphm.com>
@@ -47,6 +47,7 @@ C 1 Waweic <waweic@activ.ism.rocks>
47C 1 Waweic <waweic@protonmail.com> 47C 1 Waweic <waweic@protonmail.com>
48C 1 Zach DeCook <zachdecook@librem.one> 48C 1 Zach DeCook <zachdecook@librem.one>
49C 1 jcromero <jcromero+github@disconfor.me> 49C 1 jcromero <jcromero+github@disconfor.me>
50C 1 jgart <47760695+jgarte@users.noreply.github.com>
50C 1 zocker <zocker@10zen.eu> 51C 1 zocker <zocker@10zen.eu>
51``` 52```
52 53
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b185f646..11348fcc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,13 +25,13 @@ project (Lagrange
25set (COPYRIGHT_YEAR 2022) 25set (COPYRIGHT_YEAR 2022)
26if (IOS) 26if (IOS)
27 set (PROJECT_VERSION 1.10) 27 set (PROJECT_VERSION 1.10)
28 set (IOS_BUNDLE_VERSION 9) 28 set (IOS_BUNDLE_VERSION 12)
29 set (IOS_BUILD_DATE "2022-01-01") 29 set (IOS_BUILD_DATE "2022-01-28")
30endif () 30endif ()
31if (ANDROID) 31if (ANDROID)
32 set (PROJECT_VERSION 1.10) 32 set (PROJECT_VERSION 1.10)
33 set (ANDROID_BUILD_VERSION a3) # remember to update Gradle, AndroidManifest.xml 33 set (ANDROID_BUILD_VERSION a5) # remember to update Gradle, AndroidManifest.xml
34 set (ANDROID_BUILD_DATE "2022-01-03") 34 set (ANDROID_BUILD_DATE "2022-01-24")
35endif () 35endif ()
36 36
37# Defaults that depend on environment. 37# Defaults that depend on environment.
@@ -118,6 +118,8 @@ elseif (ANDROID)
118else () 118else ()
119 list (APPEND RESOURCES 119 list (APPEND RESOURCES
120 res/about/help.gmi 120 res/about/help.gmi
121 res/about/version-0.13.gmi
122 res/about/version-1.5.gmi
121 res/about/version.gmi 123 res/about/version.gmi
122 res/arg-help.txt 124 res/arg-help.txt
123 ) 125 )
@@ -232,7 +234,7 @@ set (SOURCES
232 src/ui/scrollwidget.c 234 src/ui/scrollwidget.c
233 src/ui/scrollwidget.h 235 src/ui/scrollwidget.h
234 src/ui/sidebarwidget.c 236 src/ui/sidebarwidget.c
235 src/ui/sidebarwidget.h 237 src/ui/sidebarwidget.h
236 src/ui/text.c 238 src/ui/text.c
237 src/ui/text.h 239 src/ui/text.h
238 src/ui/touch.c 240 src/ui/touch.c
@@ -435,7 +437,7 @@ if (APPLE)
435 XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "fi.skyjake.lagrange" 437 XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "fi.skyjake.lagrange"
436 ) 438 )
437 if (IOS) 439 if (IOS)
438 target_compile_definitions (app PUBLIC 440 target_compile_definitions (app PUBLIC
439 LAGRANGE_IOS_VERSION="${IOS_BUNDLE_VERSION}" 441 LAGRANGE_IOS_VERSION="${IOS_BUNDLE_VERSION}"
440 LAGRANGE_IOS_BUILD_DATE="${IOS_BUILD_DATE}" 442 LAGRANGE_IOS_BUILD_DATE="${IOS_BUILD_DATE}"
441 ) 443 )
@@ -464,7 +466,7 @@ endif ()
464if (MSYS) 466if (MSYS)
465 target_link_libraries (app PUBLIC d2d1 uuid dwmapi) # querying DPI 467 target_link_libraries (app PUBLIC d2d1 uuid dwmapi) # querying DPI
466 if (ENABLE_WINSPARKLE) 468 if (ENABLE_WINSPARKLE)
467 target_link_libraries (app PUBLIC winsparkle) 469 target_link_libraries (app PUBLIC winsparkle)
468 endif () 470 endif ()
469endif () 471endif ()
470if (UNIX) 472if (UNIX)
@@ -522,6 +524,7 @@ MimeType=x-scheme-handler/gemini;x-scheme-handler/gopher;
522 install (FILES res/fi.skyjake.Lagrange.appdata.xml 524 install (FILES res/fi.skyjake.Lagrange.appdata.xml
523 DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo 525 DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo
524 ) 526 )
527 install (FILES res/lagrange.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
525 if (NOT ENABLE_RELATIVE_EMBED) 528 if (NOT ENABLE_RELATIVE_EMBED)
526 target_compile_definitions (app PUBLIC 529 target_compile_definitions (app PUBLIC
527 LAGRANGE_EMB_BIN="${CMAKE_INSTALL_FULL_DATADIR}/lagrange/resources.lgr") 530 LAGRANGE_EMB_BIN="${CMAKE_INSTALL_FULL_DATADIR}/lagrange/resources.lgr")
diff --git a/README.md b/README.md
index b6fc2258..4f3d1580 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,11 @@ Please check [MacPorts](https://www.macports.org) if you are using macOS 10.12 o
30On openSUSE Tumbleweed: 30On openSUSE Tumbleweed:
31 31
32 sudo zypper install lagrange 32 sudo zypper install lagrange
33
34Using GNU Guix:
33 35
36 guix install lagrange
37
34## How to compile 38## How to compile
35 39
36You need a POSIX-compatible environment to compile Lagrange. 40You need a POSIX-compatible environment to compile Lagrange.
diff --git a/po/eo.po b/po/eo.po
index 3d770531..3b4b9b30 100644
--- a/po/eo.po
+++ b/po/eo.po
@@ -1,7 +1,7 @@
1msgid "" 1msgid ""
2msgstr "" 2msgstr ""
3"Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" 3"Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n"
4"PO-Revision-Date: 2022-01-16 17:31+0000\n" 4"PO-Revision-Date: 2022-02-05 04:50+0000\n"
5"Last-Translator: Nikolay Korotkiy <sikmir@gmail.com>\n" 5"Last-Translator: Nikolay Korotkiy <sikmir@gmail.com>\n"
6"Language-Team: Esperanto <http://weblate.skyjake.fi/projects/lagrange/ui/eo/>" 6"Language-Team: Esperanto <http://weblate.skyjake.fi/projects/lagrange/ui/eo/>"
7"\n" 7"\n"
@@ -329,7 +329,7 @@ msgid "heading.lookup.identities"
329msgstr "IDENTECOJ" 329msgstr "IDENTECOJ"
330 330
331msgid "link.hint.audio" 331msgid "link.hint.audio"
332msgstr "Ludi la sondosieron" 332msgstr "Sono"
333 333
334msgid "link.hint.image" 334msgid "link.hint.image"
335msgstr "Bildo" 335msgstr "Bildo"
@@ -803,3 +803,16 @@ msgstr "Malfari"
803 803
804msgid "sidebar.action.show" 804msgid "sidebar.action.show"
805msgstr "Montri:" 805msgstr "Montri:"
806
807msgid "fontpack.meta.installed"
808msgstr "Instalita"
809
810msgid "gempub.cover.aboutbook"
811msgstr "Pri ĉi tiu libro"
812
813msgid "gempub.cover.untitled"
814msgstr "Sentitola libro"
815
816#, c-format
817msgid "fontpack.install"
818msgstr "Instali \"%s\""
diff --git a/po/gl.po b/po/gl.po
index 6d9f7e91..ac2ef700 100644
--- a/po/gl.po
+++ b/po/gl.po
@@ -1,7 +1,7 @@
1msgid "" 1msgid ""
2msgstr "" 2msgstr ""
3"Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" 3"Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n"
4"PO-Revision-Date: 2022-01-08 14:50+0000\n" 4"PO-Revision-Date: 2022-01-20 06:20+0000\n"
5"Last-Translator: Xosé M. <correoxm@disroot.org>\n" 5"Last-Translator: Xosé M. <correoxm@disroot.org>\n"
6"Language-Team: Galician <http://weblate.skyjake.fi/projects/lagrange/ui/gl/>" 6"Language-Team: Galician <http://weblate.skyjake.fi/projects/lagrange/ui/gl/>"
7"\n" 7"\n"
@@ -2233,3 +2233,11 @@ msgstr "Cursor intermitente:"
2233 2233
2234msgid "keys.upload.edit" 2234msgid "keys.upload.edit"
2235msgstr "Editar páxina con Titan" 2235msgstr "Editar páxina con Titan"
2236
2237# Shows where a local file is using the File Manager.
2238msgid "menu.reveal.filemgr"
2239msgstr "Mostrar no Xestor de ficheiros"
2240
2241# Menu heading shown when customizing navbar button actions.
2242msgid "menu.toolbar.setaction"
2243msgstr "Establece acción:"
diff --git a/po/ie.po b/po/ie.po
index 4901cb89..da697aba 100644
--- a/po/ie.po
+++ b/po/ie.po
@@ -1,7 +1,7 @@
1msgid "" 1msgid ""
2msgstr "" 2msgstr ""
3"Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n" 3"Report-Msgid-Bugs-To: jaakko.keranen@iki.fi\n"
4"PO-Revision-Date: 2021-12-04 10:50+0000\n" 4"PO-Revision-Date: 2022-01-24 10:50+0000\n"
5"Last-Translator: Olga Smirnova <mistresssilvara@hotmail.com>\n" 5"Last-Translator: Olga Smirnova <mistresssilvara@hotmail.com>\n"
6"Language-Team: Occidental <http://weblate.skyjake.fi/projects/lagrange/ui/ie/" 6"Language-Team: Occidental <http://weblate.skyjake.fi/projects/lagrange/ui/ie/"
7">\n" 7">\n"
@@ -614,10 +614,10 @@ msgid "dlg.certimport.nokey"
614msgstr "Null clave privat" 614msgstr "Null clave privat"
615 615
616msgid "link.hint.audio" 616msgid "link.hint.audio"
617msgstr "Reproducter li audio" 617msgstr "Audio"
618 618
619msgid "link.hint.image" 619msgid "link.hint.image"
620msgstr "Vider li image" 620msgstr "Image"
621 621
622msgid "bookmark.title.blank" 622msgid "bookmark.title.blank"
623msgstr "Págine blanc" 623msgstr "Págine blanc"
@@ -638,20 +638,20 @@ msgid "dlg.bookmark.icon"
638msgstr "Icone:" 638msgstr "Icone:"
639 639
640msgid "heading.prefs" 640msgid "heading.prefs"
641msgstr "PREFERENTIES" 641msgstr "Preferenties"
642 642
643msgid "heading.prefs.certs" 643msgid "heading.prefs.certs"
644msgstr "CERTIFICATES" 644msgstr "Certificates"
645 645
646# tab button 646# tab button
647msgid "heading.prefs.colors" 647msgid "heading.prefs.colors"
648msgstr "Colores" 648msgstr "Colores"
649 649
650msgid "heading.prefs.proxies" 650msgid "heading.prefs.proxies"
651msgstr "PROXYS" 651msgstr "Proxys"
652 652
653msgid "heading.prefs.scrolling" 653msgid "heading.prefs.scrolling"
654msgstr "RULAMENT" 654msgstr "Rulament"
655 655
656# tab button 656# tab button
657msgid "heading.prefs.style" 657msgid "heading.prefs.style"
@@ -806,7 +806,7 @@ msgid "heading.prefs.network"
806msgstr "Rete" 806msgstr "Rete"
807 807
808msgid "heading.prefs.paragraph" 808msgid "heading.prefs.paragraph"
809msgstr "PARAGRAF" 809msgstr "Paragraf"
810 810
811msgid "prefs.saturation" 811msgid "prefs.saturation"
812msgstr "Saturation:" 812msgstr "Saturation:"
@@ -976,7 +976,7 @@ msgid "menu.autoreload"
976msgstr "Auto-recarga…" 976msgstr "Auto-recarga…"
977 977
978msgid "heading.history.clear" 978msgid "heading.history.clear"
979msgstr "VACUAR LI DIARIUM" 979msgstr "Vacuar li diarium"
980 980
981msgid "dlg.confirm.history.clear" 981msgid "dlg.confirm.history.clear"
982msgstr "Esque vu vole efaciar li diarium de omni visitat págines?" 982msgstr "Esque vu vole efaciar li diarium de omni visitat págines?"
@@ -1077,7 +1077,7 @@ msgid "heading.prefs.pagecontent"
1077msgstr "COLORES DE PÁGINE" 1077msgstr "COLORES DE PÁGINE"
1078 1078
1079msgid "heading.prefs.sizing" 1079msgid "heading.prefs.sizing"
1080msgstr "DIMENSION" 1080msgstr "Dimension"
1081 1081
1082msgid "heading.prefs.widelayout" 1082msgid "heading.prefs.widelayout"
1083msgstr "LARG ARANGEMENT" 1083msgstr "LARG ARANGEMENT"
@@ -1384,7 +1384,7 @@ msgstr "sur clar"
1384 1384
1385# Link download progress message. 1385# Link download progress message.
1386msgid "doc.fetching" 1386msgid "doc.fetching"
1387msgstr "Obtenente" 1387msgstr "Cargante"
1388 1388
1389# Inline download status message. 1389# Inline download status message.
1390msgid "media.download.complete" 1390msgid "media.download.complete"
@@ -1455,7 +1455,7 @@ msgid "menu.downloads"
1455msgstr "Monstrar Descargates" 1455msgstr "Monstrar Descargates"
1456 1456
1457msgid "menu.split.merge" 1457msgid "menu.split.merge"
1458msgstr "Unir cartes" 1458msgstr "Reunir"
1459 1459
1460msgid "menu.view.split" 1460msgid "menu.view.split"
1461msgstr "Fender li vise…" 1461msgstr "Fender li vise…"
@@ -1671,7 +1671,7 @@ msgid "dlg.file.delete"
1671msgstr "Remover" 1671msgstr "Remover"
1672 1672
1673msgid "heading.prefs.uitheme" 1673msgid "heading.prefs.uitheme"
1674msgstr "COLORES DE IDU" 1674msgstr "Colores de IdU"
1675 1675
1676msgid "prefs.scrollspeed.keyboard" 1676msgid "prefs.scrollspeed.keyboard"
1677msgstr "Rapiditá del tastatura:" 1677msgstr "Rapiditá del tastatura:"
@@ -1721,7 +1721,7 @@ msgid "prefs.returnkey.accept"
1721msgstr "Acceptar" 1721msgstr "Acceptar"
1722 1722
1723msgid "keys.upload" 1723msgid "keys.upload"
1724msgstr "Cargar un págine med Titan" 1724msgstr "Cargar med Titan"
1725 1725
1726msgid "error.certexpired" 1726msgid "error.certexpired"
1727msgstr "Certificate ha expirat" 1727msgstr "Certificate ha expirat"
@@ -1734,10 +1734,10 @@ msgid "menu.unexpire"
1734msgstr "Ignorar li expiration e continuar cargar" 1734msgstr "Ignorar li expiration e continuar cargar"
1735 1735
1736msgid "menu.page.upload" 1736msgid "menu.page.upload"
1737msgstr "Cargar un págine med Titan…" 1737msgstr "Cargar med Titan…"
1738 1738
1739msgid "heading.upload" 1739msgid "heading.upload"
1740msgstr "CARGAR MED TITAN" 1740msgstr "Cargar med Titan"
1741 1741
1742msgid "heading.upload.text" 1742msgid "heading.upload.text"
1743msgstr "Textu" 1743msgstr "Textu"
@@ -1851,7 +1851,7 @@ msgstr "Adjunter un fólder"
1851 1851
1852# used on mobile 1852# used on mobile
1853msgid "heading.settings" 1853msgid "heading.settings"
1854msgstr "PARAMETRES" 1854msgstr "Parametres"
1855 1855
1856msgid "prefs.imagestyle" 1856msgid "prefs.imagestyle"
1857msgstr "Colorisar images:" 1857msgstr "Colorisar images:"
@@ -2119,3 +2119,93 @@ msgstr "Controlar actualisamentes…"
2119 2119
2120msgid "sidebar.action.show" 2120msgid "sidebar.action.show"
2121msgstr "Monstrar:" 2121msgstr "Monstrar:"
2122
2123# The %s represents the name of an identity.
2124#, c-format
2125msgid "ident.switch"
2126msgstr "Usar %s"
2127
2128# Paste the line preceding the clicked link into the input prompt.
2129msgid "menu.input.precedingline"
2130msgstr "Collar li precedent linea"
2131
2132# Shows where a local file is using the File Manager.
2133msgid "menu.reveal.filemgr"
2134msgstr "Monstrar in li gerente de files"
2135
2136msgid "menu.page.upload.edit"
2137msgstr "Modificar li págine med Titan…"
2138
2139msgid "heading.prefs.toolbaractions"
2140msgstr "Actiones del instrumentarium"
2141
2142msgid "prefs.toolbaraction1"
2143msgstr "Buton 1"
2144
2145msgid "prefs.toolbaraction2"
2146msgstr "Buton 2"
2147
2148msgid "keys.upload.edit"
2149msgstr "Modificar li págine med Titan"
2150
2151msgid "menu.open.external"
2152msgstr "Aperter med un altri app"
2153
2154# Active identity toolbar menu.
2155msgid "menu.hide.identities"
2156msgstr "Celar identitás"
2157
2158msgid "menu.home"
2159msgstr "Ear al hem"
2160
2161msgid "menu.identities"
2162msgstr "Gerer identitás"
2163
2164# Shows where a local file is using the Finder.
2165msgid "menu.reveal.macos"
2166msgstr "Monstrar in Finder"
2167
2168msgid "menu.share"
2169msgstr "Partir"
2170
2171msgid "heading.upload.id"
2172msgstr "Autorisation"
2173
2174msgid "menu.upload.export"
2175msgstr "Exportar li textu"
2176
2177msgid "menu.upload.delete"
2178msgstr "Remover omni"
2179
2180msgid "menu.upload.delete.confirm"
2181msgstr "Vermen remover omni (impossibil a defar)"
2182
2183# Mobile subheading in the Upload dialog.
2184msgid "upload.url"
2185msgstr "Adresse"
2186
2187# Menu heading shown when customizing navbar button actions.
2188msgid "menu.toolbar.setaction"
2189msgstr "Assignar un action:"
2190
2191msgid "sidebar.close"
2192msgstr "Fin"
2193
2194msgid "sidebar.action.bookmarks.newfolder"
2195msgstr "Nov fólder"
2196
2197msgid "sidebar.action.bookmarks.edit"
2198msgstr "Modificar"
2199
2200msgid "sidebar.action.history.clear"
2201msgstr "Vacuar"
2202
2203msgid "sidebar.empty.unread"
2204msgstr "Null ínleet elementes"
2205
2206# Mobile subheading: buttons for entering uploaded data.
2207msgid "upload.content"
2208msgstr "Contenete"
2209
2210msgid "hint.upload.path"
2211msgstr "Rute"
diff --git a/res/about/android-version.gmi b/res/about/android-version.gmi
index fc7b444b..ea1475c7 100644
--- a/res/about/android-version.gmi
+++ b/res/about/android-version.gmi
@@ -6,6 +6,16 @@
6``` 6```
7# Release notes 7# Release notes
8 8
9## 1.10 (Alpha 5)
10* Fixed cancelling an input query. Now pressing Cancel will navigate back to the previous page as intended.
11* Gopher: Fixed navigating to parent. Set item type to 1 to show a gophermap and not the plain source.
12* Updated Smol Emoji: added the rest of Unicode 8 Emoji glyphs.
13
14## 1.10 (Alpha 4)
15* Save downloads to the external storage so they're accessible from a file manager.
16* Handle Gemini, Gopher and Finger URIs opened from other apps.
17* Back button dismisses UI panels/dialogs when appropriate instead of navigating back.
18
9## 1.10 (Alpha 3) 19## 1.10 (Alpha 3)
10* Added Android-specific release notes. 20* Added Android-specific release notes.
11* Added Settings > UI > Toolbar Actions: customize the two leftmost phone toolbar buttons. 21* Added Settings > UI > Toolbar Actions: customize the two leftmost phone toolbar buttons.
diff --git a/res/about/ios-version.gmi b/res/about/ios-version.gmi
index f3122dbe..55f80d02 100644
--- a/res/about/ios-version.gmi
+++ b/res/about/ios-version.gmi
@@ -6,6 +6,22 @@
6``` 6```
7# Release notes 7# Release notes
8 8
9## 1.10 (12)
10* Tapping the status bar area scrolls the page to the top.
11* "/index.gmi" is considered equal to "/" when navigating to parent directory.
12* Fixed crash when a media player is active and a new download is started.
13* Fixed crash when a line contains nothing but an ANSI escape sequence.
14* Fixed height of navbar buttons on iPad.
15
16## 1.10 (11)
17* Fixed cancelling an input query. Now pressing Cancel will navigate back to the previous page as intended.
18* Gopher: Fixed navigating to parent. Set item type to 1 to show a gophermap and not the plain source.
19* Updated Smol Emoji: added the rest of Unicode 8 Emoji glyphs.
20
21## 1.10 (10)
22* Fixed a hang when tapping "New Folder" while editing bookmarks.
23* Bug fixes, improvements, and updated UI translations from the desktop version (v1.10.1).
24
9## 1.10 (9) 25## 1.10 (9)
10* Added "Share" actions for downloaded files (via "Download Linked File") and selected text on page. 26* Added "Share" actions for downloaded files (via "Download Linked File") and selected text on page.
11* Added "Edit Page with Titan": opens the Upload dialog with the page contents prefilled (previous contents are lost!). 27* Added "Edit Page with Titan": opens the Upload dialog with the page contents prefilled (previous contents are lost!).
diff --git a/res/about/version-0.13.gmi b/res/about/version-0.13.gmi
new file mode 100644
index 00000000..72434f76
--- /dev/null
+++ b/res/about/version-0.13.gmi
@@ -0,0 +1,246 @@
1# Release notes
2
3=> about:version-1.5 Newer versions
4
5## 0.13.2
6* Added a little spinner to indicate ongoing requests.
7* Fixed very thin progress bar on HiDPI displays.
8* Fixed link to Help page from the Identities sidebar tab.
9* Fixed incorrect text colors in the UI (e.g., selected lookup result).
10* macOS: Disable menu shortcuts for navigation when editing text to prevent accidental page changes.
11
12## 0.13.1
13* Fixed build failure on Linux.
14
15## 0.13
16* Support for Internationalized Domain Names (IDN) in network requests.
17* Percent-encoded URL paths are shown decoded in the UI, and encoded in outgoing requests.
18* Added option to disable decoding of percent-encoded paths for the UI.
19* Quick search via URL bar shows entries from subscribed feeds.
20* Added keybindings for zooming.
21* Improved usability of page content searching (${CTRL+}F, Escape).
22* Clicking on a certificate warning shows the Certificate Status dialog.
23* macOS: Keyboard shortcuts in native menus are updated according to bindings.
24* Tweaked handling of Emojis in monospace text. They are given more space as needed, but the original monospace spacing is restored after whitespace.
25* Fira Mono replaced with Iosevka Term Extended.
26* Fixed use of variable-width fonts in input fields.
27* Fixed handling of Unicode joiners and modifiers (by ignoring them, since we lack the glyphs).
28* Fixed a layout issue with sidebars where the bottommost content line was occasionally not visible.
29* Fixed exit when a hook program didn't read its input.
30* Fixed crash when using an identity (with LibreSSL on OpenBSD).
31
32## 0.12.1
33* 'text/*' content falls back to plain text.
34* Minimized visual artifacts in Unicode box-drawing characters (overlapping/gaps) by fine-tuning glyph scaling.
35* Fixed truncated tab titles when opening tabs in background.
36* Fixed possible exit if hook program not found (SIGPIPE).
37* REQUEST_URL is set in the environment when running MIME hooks.
38* "about:debug" lists the configured MIME hooks.
39* macOS: Fixed excessive CPU usage while idling.
40
41## 0.12
42* Added MIME hooks: pipe Gemini responses through external programs for arbitrary processing. (See "about:help" for usage.)
43* Added a right-hand sidebar; have a sidebar on the right or on both sides at once.
44* Added a clear warning banner when there is an issue with the server's TLS certificate.
45* Follow Weiph/pikkulogs — subscribe to new headings on pages.
46* Added UI for subscribing: feed name, entry type (Gemini feed or new headings).
47* Added keyboard shortcut ${SHIFT+}${CTRL+}D for subscribing to page.
48* Feeds sidebar is capped to 100 entries. "about:feeds" shows all known entries.
49* Network connections have a timeout in case server doesn't respond at all.
50* Adjusted spacing before/after links to reflect use of empty lines in the source.
51* Clicking on page area unfocuses URL input field.
52* Added keybindings for switching tabs.
53* Gopher: Query links have a 🔍 icon.
54* Fixed handling of "file:///" URIs on Windows.
55* Fixed misaligned Unicode box-drawing characters.
56* Fixed missing error page if status code is unknown (torture test 34).
57* Fixed detection of invalid headers (torture test 39).
58* Fixed rendering of soft hyphens (torture test 50).
59
60## 0.11
61* Added feed subscriptions. A subscription is any bookmark with the "subscribed" tag. Subscribed feeds are refreshed in the background while Lagrange is running.
62* Added a new sidebar tab for feeds.
63* Added "about:feeds" to show entries from all subscriptions on one page.
64* Added icons for special bookmark tags, and context menu items for toggling "homepage" and "subscribed".
65* Improved stability: fixed data races, undefined behavior, thread leaks.
66* Wide preformatted blocks can be scrolled horizontally with the mouse wheel or trackpad.
67* Line widths are slightly narrower for improved readability.
68* Light mode UI color palette is less saturated, more sepia-toned.
69* Tall menus/dialogs can be scrolled with the mouse wheel.
70* Improved download progress updates: never update more often than the UI can be refreshed.
71* macOS: Control-Click works as a right mouse click.
72* Unix: Location of `xdg-open` is no longer hardcoded.
73* Fixed large downloads (10 MB+) stopping prematurely due to a decryption error.
74* Fixed window contents not being updated during window resizing.
75* Fixed selection/search markers disappearing when scrolling down.
76* Fixed displaying of "about:" URLs in history.
77* Fixed build on FreeBSD (tested on 12.1).
78
79## 0.10
80* Added option to load inline images when pressing Space or ↓ for a more focused reading experience — just keep tapping a single key to proceed. If an image link is visible, it will be loaded instead of scrolling. This option is disabled by default.
81* Added context menu item to save inline images to Downloads.
82* Added an option to use a proxy server for Gemini requests.
83* Added a new keyboard link navigation mode focusing on the home row keys. The default keybinding for this is "F".
84* Added a keybinding to activate keyboard link modifier mode. The keyboard link keys are active while the modifier is held down. The default is ${ALT}.
85* Clearing and resetting keybindings via a context menu.
86* Added a Window tab in the Preferences dialog; moved some of the settings around for better organization.
87* Improved page search visualization: if the match is inside a link URL, the link icon is now highlighted. Previously these matches were not visualized in any way.
88* Improvements to URI parsing with regard to RFC 3986. Cases that are handled better are double slashes, query-only relative URIs, relative URIs that begin with a tilde, IPv6 literals, username in the authority.
89* Replaced EB Garamond with Tinos for improved readability.
90* Replaced Kosugi Maru with Noto Sans CJK JP for better glyph coverage.
91* Fixed font sizing of level 3 headings.
92* Fixed download progress indicators sometimes remaining visible even after leaving the page.
93
94## 0.9
95* Clicking on the top banner of a page (where the site icon and hostname are shown) navigates to the root directory of the site.
96* Added menu items and keybindings for navigating to site root or the parent directory.
97* Added option to use a monospace body text font on all Gemini and/or Gopher pages.
98* Remember redirect source URLs as visited but not shown in the History tab. Note that "visited.txt" is no longer fully compatible if opened in an older version of Lagrange.
99* "gopher:" scheme is allowed in command line arguments.
100* XDG: .desktop file declares support for opening Gopher URLs.
101* Fixed an issue where copying the URL input field would not place anything on the clipboard.
102* Fixed the Lagrange window visibly changing position during launch.
103* Fixed crash when a single percent sign was typed in an input field.
104* macOS: Fixed native menu keyboard shortcuts causing redundant command activations.
105* macOS: Fixed assigning keybindings when there is an equivalent native menu shortcut.
106
107## 0.8.1
108* Fixed potential lockup when navigating back to a query prompt.
109* macOS: Improved handling of scroll wheel events from a mouse.
110
111## 0.8
112* Added support for Gopher.
113* Added support for the full palette of 8-bit ANSI foreground colors.
114* Added option to disable smooth scrolling.
115* Added button to manually set server certificate as trusted (if the certificate is valid but untrusted).
116* Added keybindings for Back/Forward navigation.
117* Added a context menu item for opening HTTP links in the default browser even when a proxy is configured.
118* Revised identity creation dialog: changed field order, added warning about temporary identities not being saved.
119* ${CTRL+}Click opens tab in background, ${SHIFT+}${CTRL+}Click opens as foreground tab. The same modifier keys work with keyboard navigation.
120* Improved word wrapping of emoticons (:D).
121* Automatic redirects allowed when the destination URL uses the same scheme as the originating URL. For example, when using a proxy, HTTP(S) is allowed to automatically redirect to other HTTP(S) URLs.
122* Windows: Fixed handling of drag-and-dropped and command line file paths.
123
124## 0.7.2
125* Fixed parsing of the server's response. In some cases it was possible that the response was only partially read.
126* Fixed handling of TLS/SSL connection being closed without the socket being closed.
127
128## 0.7.1
129* Fixed build on OpenBSD.
130* Fixed build with LibreSSL.
131* Fixed a potential crash at app shutdown.
132* Fixed a potential crash when a thread exits.
133* Fixed a potential lockup when a thread exits.
134* Linux/Unix: Open "mailto:" links with xdg-open instead of the web browser.
135
136## 0.7
137* Basic set of user-configurable key bindings.
138* Sidebar: Added a "New Identity" button and a link to "about:help" if there are no identities.
139* Faster drawing of certain UI elements: site icon and current heading in the left margin, unfocused input fields, timestamp at the end of the page.
140* History is not updated until a network request finishes.
141* Improved opening connections when multiple IP addresses are found for a hostname.
142* Fixed handling of TLS/SSL errors and hostname lookup problems — an error page is shown.
143* Fixed an issue where window contents were not being updated immediately after the window gets exposed when using, e.g., openbox or dwm.
144
145## 0.6
146* Added an indicator to visualize progress of network requests.
147* Added new color themes for page content: Colorful Light, Black, Gray, Sepia, High Contrast.
148* Added page content color theme selection in Preferences.
149* Added quote indicator option: icon or vertical line.
150* Added a new font for Korean glyphs.
151* Smoother smooth scrolling, making it easier to keep one's eyes on the content throughout the motion.
152* Windows: Register Lagrange as a handler of "gemini:" URLs.
153* macOS: Fixed glitchy window dragging during audio playback.
154* Fixed timestamps of cached pages.
155
156## 0.5
157* Added MP3 support in the audio player (using mpg123).
158* Added volume control in the audio player.
159* Metadata in Vorbis and MP3 audio content (title, artist, etc.) is shown in the audio player menu.
160* Added new serif fonts: EB Garamond and Literata.
161* Allow configuring separate fonts for headings and body text for better visual distinction.
162* Preferences dialog remembers the previously open tab.
163* Paste from clipboard on middle mouse button click.
164* Open links in new tab with middle mouse button.
165* Fixed failure to find resources when launching via PATH.
166* Fixed color saturation setting not affecting the default color theme.
167=> https://mpg123.org/ mpg123: MPEG audio player and decoder library
168
169## 0.4.1
170* Set keyboard focus to URL input field after opening a new tab.
171* Pause other audio players when a new one is started. One can still choose to have multiple audio players playing simultaneously by unpausing them again.
172* Fixed dismissing an audio player that is still downloading content. The partially downloaded data is discarded.
173* Fixed saving pages whose name starts with a tilde.
174* Fixed saving pages restored from cache.
175* Windows: The app is now distributed as an installer created with Inno Setup.
176* Windows: All binaries are signed.
177
178## 0.4
179* Added audio playback with support for streaming. Supported audio formats in this release are WAV (PCM, mono/stereo, 8/16/24/32 integer/float) and Ogg Vorbis. Shoutout to Sean Barrett et al. for stb_vorbis.
180* Added inline audio player that works like inline images. Clicking on an audio link opens the audio player below the link (works for URLs that have file extension .wav/.ogg).
181* Visual fine-tuning: increased Fira Sans line spacing; list bullets use an accent color; adjusted accent colors in the light mode palette.
182* Sidebar has a maximum width — the document must remain visible.
183* Windows: Support for HiDPI displays and the system UI scaling factor. The UI will be scaled according to your settings automatically without having to adjust the UI scaling in Preferences.
184* macOS: Use OpenGL on 10.13 for potentially better compatibility.
185* Fixed a memory leak when closing tabs.
186* Fixed unnecessary continual window redrawing related to the scrollbar hover outline.
187=> https://github.com/nothings/stb stb: single-file public domain libraries for C/C++
188
189## 0.3
190* Added style customization.
191* Added new font option: Fira Sans.
192* Added a setting for maximum line width.
193* Added a setting for adjusting color saturation.
194* Added an option for "Outline on scrollbar": page outline appears when mouse is hovering over the scrollbar.
195* Added an option for site icon and current top heading that appear when the window is wide enough.
196* Added tabs in Preferences for better grouping.
197* Added "Open Link in Background Tab" in link context menus.
198* More flexible text selection behavior when starting on empty space.
199* Smaller first paragraph font size.
200* Fixed centering of popups so they remain centered when window is resized.
201* Fixed sizing and alignment of Unicode symbols in preformatted text.
202* Fixed sizing of Japanese glyphs in UI text (e.g., tab titles).
203
204## 0.2.1
205* Fixed window size/state restoration issues, e.g., after window is maximized.
206* Windows: Fixed text disappearing when window is resized.
207
208## 0.2
209* Added an icon for quote paragraphs.
210* Added Downloads folder to Preferences.
211* Added "Save to Downloads" menu item (${CTRL+}S) for saving page contents.
212* Added a download progress indicator in the URL input field.
213* Added a progress indicator for inline image fetching.
214* Added `--sw` option to force software rendering.
215* Added macOS touch bar buttons for Back, Forward, Find, New Tab, and sidebar modes.
216* Home button opens a random bookmark with the "homepage" tag.
217* Improved context menu when right-clicking on links or the page.
218* Recognize and handle "mailto:" links.
219* Fixed behavior of images on single-image pages; cannot be hidden like inline images.
220* Fall back to software rendering automatically if accelerated graphics are not available.
221* Minor bug fixes.
222
223## 0.1.1
224* Fixed a potential crash at startup.
225* Fixed bug where user's query input is handled by all tabs.
226* Default sidebar mode is Bookmarks.
227* Windows: Fixed opening HTTP links in the default web browser.
228
229## 0.1
230* The major version zero is reserved for non-feature-complete releases.
231* Beautiful typography using Unicode fonts.
232* Autogenerated page style and Unicode icon for each Gemini domain.
233* Smart suggestions when typing the URL — search bookmarks, history, identities.
234* Sidebar for page outline, managing bookmarks and identities, and viewing history.
235* Multiple tabs.
236* Identity management — create and use TLS client certificates.
237* Light and dark UI themes.
238* Select and copy text with the mouse.
239* Find text on the page.
240* Open image links inline on the same page.
241* Open links via keyboard shortcuts.
242* Instant back/forward navigation.
243* Smooth scrolling.
244* Scaling page content (50%...200%).
245* Scaling factor for the UI (for arbitrary monitor DPI).
246* Persistent app state — tabs and history are restored on next run.
diff --git a/res/about/version-1.5.gmi b/res/about/version-1.5.gmi
new file mode 100644
index 00000000..c970a908
--- /dev/null
+++ b/res/about/version-1.5.gmi
@@ -0,0 +1,371 @@
1# Release notes
2
3=> about:version Newer versions
4
5## 1.5.2
6* Fixed pasting a PEM-formatted certificate and/or private key via clipboard in Import Identity.
7* Possible workaround for a visual glitch in the URL field.
8* Specify `StartupWMClass` in .desktop file.
9* Normalize page contents to avoid the most common issues with diacritics (Unicode NFC).
10* Expanded the set of recognized custom link icons.
11* Updated "Smol Emoji" font with new glyphs.
12* Allow use of TLS cipher "DHE-RSA-AES256-GCM-SHA384".
13
14## 1.5.1
15* Updated UI translations.
16* Updated "Smol Emoji" font with new and improved glyphs.
17
18## 1.5
19* Added "Smol Emoji" and "Noto Sans Symbols" fonts, removed Symbola.
20⚠️ Many Emoji and pictographs defined in the last five years are currently missing.
21* Added document footer buttons: on certain pages (e.g., error messages) show relevant actions in the bottom of the page. For example, if a certificate is required for viewing a page, show buttons for creating a new identity and showing the Identities sidebar.
22* Error pages include the human-readable text sent by the server.
23* Disregard old feed entries whose unread status would have been forgotten.
24* Added UI language: Polish.
25
26Identity management:
27* Revised New Identity dialog. An option is provided to automatically use the new identity on the current domain/page. The additional fields are hidden by default.
28* Improved usability of Identities sidebar. No more accidental activations: left-clicking an identity opens the context menu without making any changes. The context menu shows each active URL as a menu item for easy access. Identity icons reflect the usage status: all identities used on the current domain get highlighted in addition to the currently used one.
29* Identities can be exported: certificate and private key are opened in a new tab in plain text PEM format.
30* Fixed issues with identity usage: a higher-up URL overrides and deactivates all contained URLs to avoid redundant activation.
31
32Text input:
33* Revised text input widgets: added support for multiple lines, and when entering user response to a query, show how many bytes are remaining for the response URL about to be submitted. In dialogs, input fields expand vertically instead of scrolling their content horizontally.
34* Input widgets allow inserting newlines using Shift+Return.
35* Disallow sending query responses that are too long (1024 bytes maximum).
36* Shift-click to select a range of text in input widgets (i.e, without dragging).
37
38Rendering:
39* Animate showing and hiding of sidebars and dialogs. Animations are enabled by default, by can be disabled with Preferences > Interface > Animations.
40* Added setting for a custom TrueType symbol font for any missing characters. Note: Must be a .TTF file — OpenType and bitmap fonts are not supported.
41* Link navigation shortcut icons (home row and numbered) are drawn with a consistent appearance.
42* Improved icon alignment in lists.
43* Reduced line gap between word-wrapped top-level headings.
44* Modal dialog background dimming fades in/out smoothly.
45* macOS: Workaround for an issue that causes UI refresh to pause occasionally for ~100 ms.
46
47Split view:
48* Added keybindings for split view menu items.
49* Changed default split view keys to conform to Emacs (3 for horizontal, 2 for vertical split).
50* Fixes and improvements for touch screen event handling in split view mode.
51
52Command line:
53* Added --url-or-search (-u) command line option. Depending on the parameter, either open an URL or make a search query.
54* Open all URLs/files specified on the command line in new tabs, and raise the window if the app is already running. (Kudos to Alyssa Rosenzweig.)
55
56Gempub:
57* Linear navigation through the book with Left/Right arrow keys and via footer buttons. The navigation order is determined by links on the Gempub index page.
58
59## 1.4.2
60* Fixed UI colors being all black on the first run.
61* Fixed right mouse click on an inactive split not having any effect.
62* Fixed action buttons showing under the Help link in an empty Identities sidebar.
63* Fixed potential crash at shutdown.
64* Fixed minor UI layout issues.
65
66## 1.4.1
67* Fixed removing the left side split by closing all its tabs. The URL input field got confused about which tab was currently open, and the wrong theme was active.
68* Fixed tab merging when unsplitting the window: keep the currently active tab open.
69* Fixed issue with sidebars sometimes becoming unresponsive.
70* Fixed font used for visited monospace Gopher links.
71* Fixed incorrectly shown/hidden ◧ indicator.
72* Fixed scrollbar in Preferences > Keys being hidden until the list is scrolled.
73
74## 1.4
75* Added split view modes: two tabs at once, horizontal/vertical split, 1:1/2:1/1:2 weights, merge tabs, swap sides. See section 1.8 in Help for details.
76* Split view pinning: keep a page pinned on one side while all opened links go to the other side.
77* "file://" URLs can be used for viewing contents of local directories and ZIP archives.
78* Basic Gempub support: a cover page is generated based on metadata, and there's an automatic split view for index and contents. On macOS, Lagrange is registered as a viewer of .gpub files.
79* Bold link styling is used for indicating which links are unvisited.
80* Page rendering was optimized: now each line of text is rendered into the view buffer only once, and whenever the view is stationary, content is prefilled in the available space outside the viewport. Previously, at least one line of text was rendered every frame when the viewport was moving, which was mostly redundant.
81* Added UI languages: Interlingua, Toki Pona.
82* Added "New"/"Import" buttons in the bottom of the Identities tab.
83* Added an "All"/"Unread" mode switch in the bottom of the Feeds tab.
84* Added toggles for special tags in the bookmark creation/editor dialog.
85* Added "Show Downloads" to the File/main menu.
86* Added "Open Downloaded File" to the file save dialog to make it easy to find the local copy of the file.
87* Updated the UI font to Source Sans 3. It now has all the styles and weights needed for page rendering, too.
88* Added a semibold Fira Sans weight (used for unvisited links).
89* Preferences: Reorganized the fonts dropdown menu.
90* Changed popup dismiss behavior so that a click outside just dismisses the popup and does not trigger further actions.
91* All lists support smooth scrolling.
92* Multitouch scrolling: each finger can scroll a different widget.
93* Adjustments to how display DPI affects UI scaling.
94* Fixed allocation of page rendering buffers. Previously, some buffers may have gone unused or were allocated erroneously to the same position, causing unnecessary work for the page renderer.
95* Fixed various issues in the UI layout.
96* Fixed parsing URI scheme (limited set of characters allowed).
97* Don't percent encode equal signs in URL paths.
98
99## 1.3.4
100* Allow server certificates with a `*.tld` subject wildcard.
101* Updated the French UI translation.
102* Fixed media type check in the audio player. Media types with parameters failed to be recognized.
103* Fixed crash after a redirect.
104* Fixed a rare issue with handling multiple rapid network requests.
105* Fixed a rare situation where a network connection would fail to open.
106* Minor stability improvements.
107
108## 1.3.3
109* Added UI languages: French, German. (Note that neither is 100% finished yet.)
110* Added build option to disable IPC for compatibility reasons.
111* Added environment variable LAGRANGE_OVERRIDE_DPI.
112* Back/forward navigation buttons are disabled if they have no more pages to switch to.
113* Minor UI color tuning.
114* Fixed possible crash when closing a tab.
115* Fixed possible crash when restoring application state at launch.
116* Fixed problems parsing and making requests with literal IPv6 addresses.
117
118## 1.3.2
119* Fixed crash after updating from v1.2 due to undefined CA file/path configuration.
120* Fixed conflation of pixel ratio and display DPI. Pixel ratio is now always separately detected so mouse events can be correctly positioned. You may find that adjusting the UI scale factor (Preferences > Interface) is necessary after upgrading.
121* Fixed sidebar width changing when moving the window to a different display.
122* Fixed inability to use Tab in keybindings.
123* Fixed opening Gopher URLs via drag-and-drop.
124* Fixed "Add bookmark..." on a feed entry.
125* Fixed keybindings list not being updated immediately when UI language changes.
126* Fixed trimming of link label text when a custom Emoji is used.
127* Windows: Fixed maximum window size being restricted to the initial display's size.
128
129## 1.3.1
130* Added UI languages: Serbian, Interlingue.
131* Added option to disable bold links for light/dark backgrounds.
132* Updated the Nunito font to the latest version.
133* Fixed crash during word wrapping.
134* Fixed keybindings overriding the home row key navigation mode.
135* Fixed kerning in the text renderer.
136* Fixed issue with overlapped drawing of list bullets vs. list items.
137* Fixed cropped list bullets when using Literata.
138* Fixed whitespace normalization in plain text files (tab characters).
139* Fixed issues buffering window contents, possibly causing missing font glyphs.
140
141## 1.3
142
143Localization:
144* Added the first set of UI translations: Chinese (Simplified, Traditional), Finnish, Russian, and Spanish. Many thanks to the translators!
145* Added page content translation using a LibreTranslate instance running on `xlt.skyjake.fi`. This is somewhat experimental and may occasionally mess up Gemtext markup. Expect long pages to be quite slow to translate (more than a minute).
146=> https://weblate.skyjake.fi/projects/lagrange/ui Lagrange UI translations (Weblate project)
147=> https://libretranslate.com More information about LibreTranslate
148=> gemini://skyjake.fi/lagrange/privacy.gmi Lagrange privacy policy
149
150Resources:
151* Added Noto Sans CJK (Simplified Chinese) font.
152* Added Noto Sans Arabic font. Note that right-to-left/bidirectional text rendering is *not* implemented yet.
153* Added "about:about" that lists all the available "about:" pages.
154
155Browsing:
156* Alt text is shown when hovering over a preformatted block.
157* Clicking on a preformatted block collapses it, leaving only the alt text.
158* Added option to collapse all preformatted blocks on page load.
159* A server certificate can also be verified by Certificate Authorities. When "CA file" and/or "CA path" are set in Preferences, CA verification will mark a certificate as trusted.
160* Relaxed TOFU certificate checking when it comes to domain names: `domain.tld` in a certificate is implicitly considered to also mean `*.domain.tld`.
161* Fixed handling of IDNs when the user sets a server certificate as trusted via the UI.
162* Fixed handling of unknown URI schemes. Previously they were forcefully converted to absolute URIs, breaking them.
163
164Page content and rendering:
165* Color adjustments to the "Colorful Dark", "Colorful Light", and "Gray" themes. "Gray" looks different in dark and light UI modes.
166* Spacing of bullet lists vs. link lists is more consistent.
167* Links are shown in bold, and tinted with the page theme color for more coherent appearance.
168* Custom link icons: Gemini links whose destination is on the same domain use as icon the Emoji or other pictograph at the start of the link label. (For example, see Astrobotany menus.)
169* Added option to wrap lines in plain text files. This is on by default because there is no horizontal scrolling for plain text.
170* Large images are downscaled to an appropriate size for presentation.
171* Improved font glyph caching to reduce stuttering during scrolling. All required glyphs are cached after a page load finishes. This allows more efficient copying of glyphs as the operations can be batched.
172* Larger content buffers for scrolling, reducing need to redraw content.
173* Gopher: Handling the 'h' line type.
174* Fixed minor issues with word wrapping.
175* Fixed minor blending artifacts with the current heading shown on the right side of the page.
176
177User interface:
178* Improved event handling to support touch screens on any platform (if supported by SDL).
179* UI scaling factor is applied immediately when closing Preferences. Restarting is no longer necessary.
180* Window rescaled automatically when moving it to a display with a different DPI.
181* Added unread feed entry count to the sidebar.
182* Added a context menu for toggling sidebars. The menu appears when right-clicking on the navbar or on the sidebar tab buttons.
183* Double/triple click selection modes. Double click and drag will select by word, triple click by paragraph.
184* Popup menus have icons for items to make it faster to find the item you're looking for.
185* Soft shadows for popup menus.
186* Scrollbars fade away on macOS/iOS and dim on other platforms.
187* Reload button moved into the URL field.
188* Narrow URL input fields will not display the default "gemini" scheme.
189* Domain name is highlighted in URL fields.
190* Added a cut/copy/paste context menu to input fields.
191* Added an "Import..." button to the empty Identities sidebar.
192* Preferences dialog was partially reorganized for clarity.
193* Tabs in Preferences look the same as tabs in the sidebar.
194* Cleaner appearance for unread feed items in the sidebar.
195* Dialog buttons show the corresponding shortcut key.
196* Fixed background activity indicators overlapping each other.
197* Fixed glitches when widgets extend beyond the left edge of the window.
198* Fixed use of plurals in UI strings, enabling support for three or more plural forms.
199
200Keybindings:
201* Default page scroll keybindings changed: PageUp/Down scroll a whole page, and Space/Shift+Space scrolls half a page. If you're changed these bindings, note that they will be reset to defaults.
202* Added keybinding for subscribing to a page.
203* Keyboard modifiers can be remapped using the `modmap.txt` configuration file.
204* Caps Lock can be used as a modifier key.
205* Internet hot keys (if present on a keyboard) can be used to navigate back/forward and reload the page.
206
207Command line use:
208* Added conventional `--help`, `--version` options.
209* A previously started instance can be controlled with command line options.
210* `--list-tab-urls` prints a list of the currently open URLs in the running instance.
211
212Bug fixes:
213* Only one instance of Lagrange is allowed to run per user directory. This prevents instances from overwriting each other's data.
214* Fixed use of multiple search terms for quick lookup.
215* Fixed handling of multiple feeds having an entry with the same URL.
216* Fixed percent-decoding of URLs on the command line and coming in via system URL handlers.
217
218## 1.2.3
219* Fixed XML parser hanging on numeric character entities.
220* Fixed "Monospace Body" option causing Gemtext line markup to be visible.
221* Fixed bookmarking a wrapped link. Now the entire label text gets used instead of just the clicked segment.
222* Fixed handling of non-advancing monospace glyphs.
223* CMake: Automatically check if `lib/the_Foundation` is up-to-date.
224* CMake: Build configuration fails if an SSL library is not found.
225* Reverted default bookmarks having "Getting Started" as a remote bookmark source. Now it's just a regular bookmark.
226
227## 1.2.2
228* Stability improvements.
229* Whitespace is no longer normalized when using the monospace body setting.
230* Fixed issues with percent-encoded Gopher paths.
231* macOS: Try to prevent forced use of high-performance GPU.
232
233## 1.2.1
234* Fixed crash when creating a bookmark.
235
236## 1.2
237
238New features:
239* Atom feed subscriptions: Atom XML documents are automatically converted to Gemini feed index pages. This is a built-in version of the Atom-to-Gemini example on the Help page.
240* Inline downloads: right-click on any link that is openable inside Lagrange and select "Download Linked File".
241* Editable bookmark icons: use your favorite Unicode character as the symbol to represent a bookmarked site.
242* Searching via URL field: non-URL text entered in the field is passed onto the configured search query URL (Preferences > Network). An indicator is shown if a query will take place.
243* Tab auto-reloading: configure a reloading interval using the page context menu ("Set Auto-Reload..."). Auto-reloading is part of the persistent state of the tab.
244* "Iosevka" and "Source Sans Pro" (the UI font) can be used as heading and body fonts.
245* User preference for aligning all pages to the top of the window.
246* Keybinding (F11) for toggling fullscreen mode. On macOS, the shortcut is ⌃⌘F as before.
247* Keybinding for finding text on page.
248
249UI design:
250* Enhanced navbar: adjusted spacing, URL field has a maximum width, tab titles have less pronounced borders.
251* Improved sidebar appearance: bold subheadings, larger feed icons, adjusted spacing, background color.
252* Font consistency: all UI elements use the same font (i.e., no more monospace input fields).
253* Added setting for UI accent color (teal, orange).
254* General fine-tuning of the color palette.
255* Dialog buttons are aligned to the right edge, leaving room for additional action buttons on the left.
256* Page Information button is embedded in the URL field.
257* Page Information dialog is attached to its button.
258* Site icons use a different color in tab titles for visual distinction.
259* Fade background behind modal dialogs.
260* Responsive page margins.
261* Windows: Added a custom window frame to replace the default Windows one. This looks nicer but does not behave exactly like a native window frame. Added a setting to Preferences for switching back to the default frame.
262
263Other changes:
264* Help is opened on first run instead of the "About Lagrange" page to make it easier to discover important Gemini links like the FAQ.
265* "Go to Root" respects a user name found in the URL. One can still "Go to Parent" to get above the user level.
266* Feed entries are sorted by refresh time if they are published on the same date.
267* Don't show future-dated feed entries in Feeds.
268* Middle-clicking on links: open new tab in background or foreground depending on the Shift key.
269* Shift+Insert can be used for pasting clipboard contents into input fields.
270* Removed a strange violet-on-green color theme pairing.
271
272Bug fixes:
273* Fixed text prompt dialogs closing and accepting the entered text when switching focus away from the app.
274* Scroll position remains fixed while horizontally resizing the window or sidebars.
275* Fixed a crash when opening the audio player menu.
276* Fixed Gopher requests that were using URL (percent) encoded characters.
277* Windows: Fixed a flash of white when the window is first opened.
278
279## 1.1.4
280* Fixed feed entry highlight/read status issue in the sidebar.
281* Fixed Gopher menu links that contain spaces.
282* Fixed vertical alignment of short pages: top banner is excluded because it is not part of the content.
283
284## 1.1.3
285* Fixed crash when deleting a bookmark that was being used as a remote bookmark source.
286* Fixed potential crash at shutdown.
287* Fixed URL path decoding not respecting reserved characters.
288* AppImage: Disable SSE 4.1 for improved compatibility.
289* Windows: Fixed portable build so it stores user files under the "userdata" directory and not under AppData\Roaming\.
290
291## 1.1.2
292* Fixed potential crash at launch.
293* Fixed input query from a background tab being applied to the foreground tab. An input query now forces a tab to the foreground.
294* Fixed window scroll position moving when resizing vertically.
295* Fixed feed refresh waiting forever if a server doesn't respond.
296* Fixed hover state not being cleared when right-clicking links.
297* Fixed remote bookmarks not appearing if they lacked a user-friendly name on the source page.
298
299## 1.1.1
300* Fixed focus cycling inside dialogs. Widgets outside a dialog are not allowed to be focused.
301* Fixed missing cursor in the New Identity "Valid until" field.
302* Fixed "Import Links as Bookmarks" so it can be used to import local copies of remote bookmarks when viewing the remote source page.
303* Fixed a cosmetic issue in the Import Identity dialog where part of the widget frames were not drawn.
304* Fixed word wrapping issue in unread feed entry titles.
305* Fixed opening a URI that contains a fragment in the default browser. The fragment is no longer omitted.
306* Fixed default value for the Line Width setting.
307* Linux: Check monitor DPI for UI scaling like on Windows and macOS.
308* Allow reloading the same URL when clicking on a link or bookmark, or pressing Enter in the navbar.
309* Bookmark URLs are normalized by removing the default Gemini port.
310* Added a list of environment variables to "about:debug".
311* XDG: Respect `XDG_CONFIG_HOME` environment variable for storing user files.
312* XDG: Use the configured XDG download directory as default Downloads location.
313* The default set of bookmarks now uses a remote source.
314=> gemini://skyjake.fi/lagrange/getting_started.gmi "Getting Started" bookmarks
315
316## 1.1
317* Added identity importing: existing client certificates in PEM format can be imported from the current page, from clipboard, or from drag-and-dropped files.
318* Added setting for maximum cache size. This is the amount of fetched data kept in memory for page navigation and content lookup. The default is 10 MB.
319* Added option to show full link URL when hovering on it.
320* Added support for Finger links (courtesy of John Cowan).
321* Added keybindings for page reload, opening link in new tab via home row keys (${SHIFT+}F), and hovering on a link via home row keys (H).
322* Added keybinding for switching to next set of links for home row navigation.
323* Added keybindings for new tab, close tab, adding a bookmark.
324* Added a bold UI font. It is used for unread feed entries and default dialog buttons.
325* Better alignment of quick lookup result icons and labels.
326* Remote bookmarks are grouped under their source in the bookmark list.
327* A bookmark's icon is updated after loading the page so it matches the site.
328* Trusting a new server certificate manually will update the current page without reloading.
329* Middle-clicking on a tab title closes the tab.
330* Internal state files now use an ".lgr" extension. If downgrading after running v1.1, note that the old version will not read these.
331* Fixed timestamps of visited URLs. Your browsing history will be cleared but can still be viewed manually in "visited.txt".
332* Fixed issue where "heading" feed entries would turn unread after a while.
333* Fixed duplicate U key for home row navigation.
334* Fixed an issue with Unicode characters in quick lookup search terms.
335
336## 1.0.3
337* Improved font glyph caching: only rasterize glyphs when drawing text, and retry after failure. This makes initial document layout faster and avoids issues with permanently lost glyphs.
338* Fixed parts of text disappearing when the glyph cache fills up. The cache also uses less memory now.
339* Fixed percent-encoding of spaces when copying URLs to clipboard.
340* Fixed feed entry context menu showing the wrong menu item for Mark as Read/Unread.
341* Fixed parentheses and brackets being trimmed from feed entry titles.
342* Fixed quotes appearing continuous even when there are empty lines in between.
343* Fixed quote border line not showing up on empty quote lines.
344* Fixed handling of redirection in a background tab — redirected URL would open in the current tab.
345* Fixed Gemini URL normalization with regard to default port.
346* Fixed a very minor memory leak.
347* Fixed missing "gopher:" URL scheme registration on macOS.
348
349## 1.0.2
350* Fixed URL input field showing the wrong URL when opening tabs in the background.
351* Fixed trusting a renewed server certificate when multiple tabs are open.
352* Fixed opening sidebar feed entries in new/background tab with modifier keys.
353* Fixed editing identity notes. The entered new notes were not being applied.
354* Server certificate domain name check accepts a matching CN even when SAN doesn't match.
355
356## 1.0.1
357* Fixed percent-encoding of the query string.
358* Fixed cursor positioning in input fields.
359
360## 1.0
361* Added remote bookmarks. Any bookmarked 'text/gemini' page with the "remotesource" tag is a source of remote bookmarks. Each link on the page is shown as a remote bookmark in Bookmarks.
362* Added a way to export bookmarks via the special page "about:bookmarks". The page can also list all bookmarks by tag or by creation date.
363* Added context menu item for duplicating a bookmark.
364* Import all links on a page as bookmarks. (Importing tags is not supported in this version.)
365* Links can be bookmarked via context menu.
366* Open links with an unrecognized scheme in the default browser.
367* Open sidebar items in new/background tab via context menu or modifier keys.
368* "Edit Feed..." menu item shows the Feed Settings dialog.
369* Removed unimplemented menu items.
370
371=> about:version-0.13 Older versions
diff --git a/res/about/version.gmi b/res/about/version.gmi
index ea420916..15336d77 100644
--- a/res/about/version.gmi
+++ b/res/about/version.gmi
@@ -6,6 +6,40 @@
6``` 6```
7# Release notes 7# Release notes
8 8
9## 1.10.5
10* Fixed animation issue with sidebars in the right side of split view.
11* macOS: Fixed a sidebar clipping issue with Metal.
12
13## 1.10.4
14* Added missing ANSI background color codes 100-107 (high-intensity VGA).
15* Fixed how the ANSI FG color is adjusted to keep text legible on dark or bright backgrounds when BG color is unset.
16* Fixed possible crash when there are ANSI escapes in the alt text of a preformatted block.
17* Fixed tab/window titles containing ANSI escapes (escapes are removed).
18* macOS: Use Metal for drawing graphics if display refresh rate is higher than 60 Hz.
19* macOS: Handling scroll events meant for other windows.
20* Updated UI translations.
21
22## 1.10.3
23* Unix: Added a lagrange(1) manual page.
24* Hide the [+] button on the tab button row if the navbar has a New Tab button.
25* "/index.gmi" is considered equal to "/" when navigating to parent directory.
26* Site icon ❑ replaced with ⌘ ("place of interest") for a more distinct appearance and to avoid similarity with the ❐ "Open in New Tab" icon.
27* Navigating to parent or root from "about:" pages goes to "about:about" to see overview of all the About pages.
28* Gopher: Fixed navigating to root, e.g., when clicking on the page top banner. Set item type to 1 to show a gophermap and not the plain source.
29* Titan: When navigating to parent/root, switch URL scheme to "gemini". This action occurs on a Titan response page, so initiating a new upload with the parent/root URL is probably not appropriate.
30* Fixed crash when a media player is active and a new download is started.
31* Fixed crash when a line contains nothing but an ANSI escape sequence.
32* Fixed a possible crash when saving state of subscribed feeds.
33* Fixed the right-hand sidebar handling the U and Shift+U key events if it was showing the Feeds tab, even though the sidebar was hidden.
34* Fixed line wrapping at backslashes. They are now considered word-breaking characters.
35
36## 1.10.2
37* Fixed cancelling an input query. Now pressing Cancel will navigate back to the previous page as intended.
38* Gopher: Fixed navigating to parent. Set item type to 1 to show a gophermap and not the plain source.
39* macOS: Workaround for a crash when launching. Prevent event processing from being triggered while the app window is becoming visible for the first time.
40* Fixed a build issue on Haiku OS.
41* Updated Smol Emoji: added the rest of Unicode 8 Emoji glyphs.
42
9## 1.10.1 43## 1.10.1
10* Fixed bottom actions of the Feeds sidebar getting hidden when all entries are read. This prevented switching between Unread/All filter modes. 44* Fixed bottom actions of the Feeds sidebar getting hidden when all entries are read. This prevented switching between Unread/All filter modes.
11* Fixed potential crash when downloading a large file (e.g., a fontpack). 45* Fixed potential crash when downloading a large file (e.g., a fontpack).
@@ -338,611 +372,4 @@ Fixes:
338* Gopher: All preformatted blocks are unindented to avoid misaligned ASCII art. 372* Gopher: All preformatted blocks are unindented to avoid misaligned ASCII art.
339* macOS: ^⌘Space shows the system-provided symbols and Emoji character palette. 373* macOS: ^⌘Space shows the system-provided symbols and Emoji character palette.
340 374
341## 1.5.2 375=> about:version-1.5 Older versions
342* Fixed pasting a PEM-formatted certificate and/or private key via clipboard in Import Identity.
343* Possible workaround for a visual glitch in the URL field.
344* Specify `StartupWMClass` in .desktop file.
345* Normalize page contents to avoid the most common issues with diacritics (Unicode NFC).
346* Expanded the set of recognized custom link icons.
347* Updated "Smol Emoji" font with new glyphs.
348* Allow use of TLS cipher "DHE-RSA-AES256-GCM-SHA384".
349
350## 1.5.1
351* Updated UI translations.
352* Updated "Smol Emoji" font with new and improved glyphs.
353
354## 1.5
355* Added "Smol Emoji" and "Noto Sans Symbols" fonts, removed Symbola.
356⚠️ Many Emoji and pictographs defined in the last five years are currently missing.
357* Added document footer buttons: on certain pages (e.g., error messages) show relevant actions in the bottom of the page. For example, if a certificate is required for viewing a page, show buttons for creating a new identity and showing the Identities sidebar.
358* Error pages include the human-readable text sent by the server.
359* Disregard old feed entries whose unread status would have been forgotten.
360* Added UI language: Polish.
361
362Identity management:
363* Revised New Identity dialog. An option is provided to automatically use the new identity on the current domain/page. The additional fields are hidden by default.
364* Improved usability of Identities sidebar. No more accidental activations: left-clicking an identity opens the context menu without making any changes. The context menu shows each active URL as a menu item for easy access. Identity icons reflect the usage status: all identities used on the current domain get highlighted in addition to the currently used one.
365* Identities can be exported: certificate and private key are opened in a new tab in plain text PEM format.
366* Fixed issues with identity usage: a higher-up URL overrides and deactivates all contained URLs to avoid redundant activation.
367
368Text input:
369* Revised text input widgets: added support for multiple lines, and when entering user response to a query, show how many bytes are remaining for the response URL about to be submitted. In dialogs, input fields expand vertically instead of scrolling their content horizontally.
370* Input widgets allow inserting newlines using Shift+Return.
371* Disallow sending query responses that are too long (1024 bytes maximum).
372* Shift-click to select a range of text in input widgets (i.e, without dragging).
373
374Rendering:
375* Animate showing and hiding of sidebars and dialogs. Animations are enabled by default, by can be disabled with Preferences > Interface > Animations.
376* Added setting for a custom TrueType symbol font for any missing characters. Note: Must be a .TTF file — OpenType and bitmap fonts are not supported.
377* Link navigation shortcut icons (home row and numbered) are drawn with a consistent appearance.
378* Improved icon alignment in lists.
379* Reduced line gap between word-wrapped top-level headings.
380* Modal dialog background dimming fades in/out smoothly.
381* macOS: Workaround for an issue that causes UI refresh to pause occasionally for ~100 ms.
382
383Split view:
384* Added keybindings for split view menu items.
385* Changed default split view keys to conform to Emacs (3 for horizontal, 2 for vertical split).
386* Fixes and improvements for touch screen event handling in split view mode.
387
388Command line:
389* Added --url-or-search (-u) command line option. Depending on the parameter, either open an URL or make a search query.
390* Open all URLs/files specified on the command line in new tabs, and raise the window if the app is already running. (Kudos to Alyssa Rosenzweig.)
391
392Gempub:
393* Linear navigation through the book with Left/Right arrow keys and via footer buttons. The navigation order is determined by links on the Gempub index page.
394
395## 1.4.2
396* Fixed UI colors being all black on the first run.
397* Fixed right mouse click on an inactive split not having any effect.
398* Fixed action buttons showing under the Help link in an empty Identities sidebar.
399* Fixed potential crash at shutdown.
400* Fixed minor UI layout issues.
401
402## 1.4.1
403* Fixed removing the left side split by closing all its tabs. The URL input field got confused about which tab was currently open, and the wrong theme was active.
404* Fixed tab merging when unsplitting the window: keep the currently active tab open.
405* Fixed issue with sidebars sometimes becoming unresponsive.
406* Fixed font used for visited monospace Gopher links.
407* Fixed incorrectly shown/hidden ◧ indicator.
408* Fixed scrollbar in Preferences > Keys being hidden until the list is scrolled.
409
410## 1.4
411* Added split view modes: two tabs at once, horizontal/vertical split, 1:1/2:1/1:2 weights, merge tabs, swap sides. See section 1.8 in Help for details.
412* Split view pinning: keep a page pinned on one side while all opened links go to the other side.
413* "file://" URLs can be used for viewing contents of local directories and ZIP archives.
414* Basic Gempub support: a cover page is generated based on metadata, and there's an automatic split view for index and contents. On macOS, Lagrange is registered as a viewer of .gpub files.
415* Bold link styling is used for indicating which links are unvisited.
416* Page rendering was optimized: now each line of text is rendered into the view buffer only once, and whenever the view is stationary, content is prefilled in the available space outside the viewport. Previously, at least one line of text was rendered every frame when the viewport was moving, which was mostly redundant.
417* Added UI languages: Interlingua, Toki Pona.
418* Added "New"/"Import" buttons in the bottom of the Identities tab.
419* Added an "All"/"Unread" mode switch in the bottom of the Feeds tab.
420* Added toggles for special tags in the bookmark creation/editor dialog.
421* Added "Show Downloads" to the File/main menu.
422* Added "Open Downloaded File" to the file save dialog to make it easy to find the local copy of the file.
423* Updated the UI font to Source Sans 3. It now has all the styles and weights needed for page rendering, too.
424* Added a semibold Fira Sans weight (used for unvisited links).
425* Preferences: Reorganized the fonts dropdown menu.
426* Changed popup dismiss behavior so that a click outside just dismisses the popup and does not trigger further actions.
427* All lists support smooth scrolling.
428* Multitouch scrolling: each finger can scroll a different widget.
429* Adjustments to how display DPI affects UI scaling.
430* Fixed allocation of page rendering buffers. Previously, some buffers may have gone unused or were allocated erroneously to the same position, causing unnecessary work for the page renderer.
431* Fixed various issues in the UI layout.
432* Fixed parsing URI scheme (limited set of characters allowed).
433* Don't percent encode equal signs in URL paths.
434
435## 1.3.4
436* Allow server certificates with a `*.tld` subject wildcard.
437* Updated the French UI translation.
438* Fixed media type check in the audio player. Media types with parameters failed to be recognized.
439* Fixed crash after a redirect.
440* Fixed a rare issue with handling multiple rapid network requests.
441* Fixed a rare situation where a network connection would fail to open.
442* Minor stability improvements.
443
444## 1.3.3
445* Added UI languages: French, German. (Note that neither is 100% finished yet.)
446* Added build option to disable IPC for compatibility reasons.
447* Added environment variable LAGRANGE_OVERRIDE_DPI.
448* Back/forward navigation buttons are disabled if they have no more pages to switch to.
449* Minor UI color tuning.
450* Fixed possible crash when closing a tab.
451* Fixed possible crash when restoring application state at launch.
452* Fixed problems parsing and making requests with literal IPv6 addresses.
453
454## 1.3.2
455* Fixed crash after updating from v1.2 due to undefined CA file/path configuration.
456* Fixed conflation of pixel ratio and display DPI. Pixel ratio is now always separately detected so mouse events can be correctly positioned. You may find that adjusting the UI scale factor (Preferences > Interface) is necessary after upgrading.
457* Fixed sidebar width changing when moving the window to a different display.
458* Fixed inability to use Tab in keybindings.
459* Fixed opening Gopher URLs via drag-and-drop.
460* Fixed "Add bookmark..." on a feed entry.
461* Fixed keybindings list not being updated immediately when UI language changes.
462* Fixed trimming of link label text when a custom Emoji is used.
463* Windows: Fixed maximum window size being restricted to the initial display's size.
464
465## 1.3.1
466* Added UI languages: Serbian, Interlingue.
467* Added option to disable bold links for light/dark backgrounds.
468* Updated the Nunito font to the latest version.
469* Fixed crash during word wrapping.
470* Fixed keybindings overriding the home row key navigation mode.
471* Fixed kerning in the text renderer.
472* Fixed issue with overlapped drawing of list bullets vs. list items.
473* Fixed cropped list bullets when using Literata.
474* Fixed whitespace normalization in plain text files (tab characters).
475* Fixed issues buffering window contents, possibly causing missing font glyphs.
476
477## 1.3
478
479Localization:
480* Added the first set of UI translations: Chinese (Simplified, Traditional), Finnish, Russian, and Spanish. Many thanks to the translators!
481* Added page content translation using a LibreTranslate instance running on `xlt.skyjake.fi`. This is somewhat experimental and may occasionally mess up Gemtext markup. Expect long pages to be quite slow to translate (more than a minute).
482=> https://weblate.skyjake.fi/projects/lagrange/ui Lagrange UI translations (Weblate project)
483=> https://libretranslate.com More information about LibreTranslate
484=> gemini://skyjake.fi/lagrange/privacy.gmi Lagrange privacy policy
485
486Resources:
487* Added Noto Sans CJK (Simplified Chinese) font.
488* Added Noto Sans Arabic font. Note that right-to-left/bidirectional text rendering is *not* implemented yet.
489* Added "about:about" that lists all the available "about:" pages.
490
491Browsing:
492* Alt text is shown when hovering over a preformatted block.
493* Clicking on a preformatted block collapses it, leaving only the alt text.
494* Added option to collapse all preformatted blocks on page load.
495* A server certificate can also be verified by Certificate Authorities. When "CA file" and/or "CA path" are set in Preferences, CA verification will mark a certificate as trusted.
496* Relaxed TOFU certificate checking when it comes to domain names: `domain.tld` in a certificate is implicitly considered to also mean `*.domain.tld`.
497* Fixed handling of IDNs when the user sets a server certificate as trusted via the UI.
498* Fixed handling of unknown URI schemes. Previously they were forcefully converted to absolute URIs, breaking them.
499
500Page content and rendering:
501* Color adjustments to the "Colorful Dark", "Colorful Light", and "Gray" themes. "Gray" looks different in dark and light UI modes.
502* Spacing of bullet lists vs. link lists is more consistent.
503* Links are shown in bold, and tinted with the page theme color for more coherent appearance.
504* Custom link icons: Gemini links whose destination is on the same domain use as icon the Emoji or other pictograph at the start of the link label. (For example, see Astrobotany menus.)
505* Added option to wrap lines in plain text files. This is on by default because there is no horizontal scrolling for plain text.
506* Large images are downscaled to an appropriate size for presentation.
507* Improved font glyph caching to reduce stuttering during scrolling. All required glyphs are cached after a page load finishes. This allows more efficient copying of glyphs as the operations can be batched.
508* Larger content buffers for scrolling, reducing need to redraw content.
509* Gopher: Handling the 'h' line type.
510* Fixed minor issues with word wrapping.
511* Fixed minor blending artifacts with the current heading shown on the right side of the page.
512
513User interface:
514* Improved event handling to support touch screens on any platform (if supported by SDL).
515* UI scaling factor is applied immediately when closing Preferences. Restarting is no longer necessary.
516* Window rescaled automatically when moving it to a display with a different DPI.
517* Added unread feed entry count to the sidebar.
518* Added a context menu for toggling sidebars. The menu appears when right-clicking on the navbar or on the sidebar tab buttons.
519* Double/triple click selection modes. Double click and drag will select by word, triple click by paragraph.
520* Popup menus have icons for items to make it faster to find the item you're looking for.
521* Soft shadows for popup menus.
522* Scrollbars fade away on macOS/iOS and dim on other platforms.
523* Reload button moved into the URL field.
524* Narrow URL input fields will not display the default "gemini" scheme.
525* Domain name is highlighted in URL fields.
526* Added a cut/copy/paste context menu to input fields.
527* Added an "Import..." button to the empty Identities sidebar.
528* Preferences dialog was partially reorganized for clarity.
529* Tabs in Preferences look the same as tabs in the sidebar.
530* Cleaner appearance for unread feed items in the sidebar.
531* Dialog buttons show the corresponding shortcut key.
532* Fixed background activity indicators overlapping each other.
533* Fixed glitches when widgets extend beyond the left edge of the window.
534* Fixed use of plurals in UI strings, enabling support for three or more plural forms.
535
536Keybindings:
537* Default page scroll keybindings changed: PageUp/Down scroll a whole page, and Space/Shift+Space scrolls half a page. If you're changed these bindings, note that they will be reset to defaults.
538* Added keybinding for subscribing to a page.
539* Keyboard modifiers can be remapped using the `modmap.txt` configuration file.
540* Caps Lock can be used as a modifier key.
541* Internet hot keys (if present on a keyboard) can be used to navigate back/forward and reload the page.
542
543Command line use:
544* Added conventional `--help`, `--version` options.
545* A previously started instance can be controlled with command line options.
546* `--list-tab-urls` prints a list of the currently open URLs in the running instance.
547
548Bug fixes:
549* Only one instance of Lagrange is allowed to run per user directory. This prevents instances from overwriting each other's data.
550* Fixed use of multiple search terms for quick lookup.
551* Fixed handling of multiple feeds having an entry with the same URL.
552* Fixed percent-decoding of URLs on the command line and coming in via system URL handlers.
553
554## 1.2.3
555* Fixed XML parser hanging on numeric character entities.
556* Fixed "Monospace Body" option causing Gemtext line markup to be visible.
557* Fixed bookmarking a wrapped link. Now the entire label text gets used instead of just the clicked segment.
558* Fixed handling of non-advancing monospace glyphs.
559* CMake: Automatically check if `lib/the_Foundation` is up-to-date.
560* CMake: Build configuration fails if an SSL library is not found.
561* Reverted default bookmarks having "Getting Started" as a remote bookmark source. Now it's just a regular bookmark.
562
563## 1.2.2
564* Stability improvements.
565* Whitespace is no longer normalized when using the monospace body setting.
566* Fixed issues with percent-encoded Gopher paths.
567* macOS: Try to prevent forced use of high-performance GPU.
568
569## 1.2.1
570* Fixed crash when creating a bookmark.
571
572## 1.2
573
574New features:
575* Atom feed subscriptions: Atom XML documents are automatically converted to Gemini feed index pages. This is a built-in version of the Atom-to-Gemini example on the Help page.
576* Inline downloads: right-click on any link that is openable inside Lagrange and select "Download Linked File".
577* Editable bookmark icons: use your favorite Unicode character as the symbol to represent a bookmarked site.
578* Searching via URL field: non-URL text entered in the field is passed onto the configured search query URL (Preferences > Network). An indicator is shown if a query will take place.
579* Tab auto-reloading: configure a reloading interval using the page context menu ("Set Auto-Reload..."). Auto-reloading is part of the persistent state of the tab.
580* "Iosevka" and "Source Sans Pro" (the UI font) can be used as heading and body fonts.
581* User preference for aligning all pages to the top of the window.
582* Keybinding (F11) for toggling fullscreen mode. On macOS, the shortcut is ⌃⌘F as before.
583* Keybinding for finding text on page.
584
585UI design:
586* Enhanced navbar: adjusted spacing, URL field has a maximum width, tab titles have less pronounced borders.
587* Improved sidebar appearance: bold subheadings, larger feed icons, adjusted spacing, background color.
588* Font consistency: all UI elements use the same font (i.e., no more monospace input fields).
589* Added setting for UI accent color (teal, orange).
590* General fine-tuning of the color palette.
591* Dialog buttons are aligned to the right edge, leaving room for additional action buttons on the left.
592* Page Information button is embedded in the URL field.
593* Page Information dialog is attached to its button.
594* Site icons use a different color in tab titles for visual distinction.
595* Fade background behind modal dialogs.
596* Responsive page margins.
597* Windows: Added a custom window frame to replace the default Windows one. This looks nicer but does not behave exactly like a native window frame. Added a setting to Preferences for switching back to the default frame.
598
599Other changes:
600* Help is opened on first run instead of the "About Lagrange" page to make it easier to discover important Gemini links like the FAQ.
601* "Go to Root" respects a user name found in the URL. One can still "Go to Parent" to get above the user level.
602* Feed entries are sorted by refresh time if they are published on the same date.
603* Don't show future-dated feed entries in Feeds.
604* Middle-clicking on links: open new tab in background or foreground depending on the Shift key.
605* Shift+Insert can be used for pasting clipboard contents into input fields.
606* Removed a strange violet-on-green color theme pairing.
607
608Bug fixes:
609* Fixed text prompt dialogs closing and accepting the entered text when switching focus away from the app.
610* Scroll position remains fixed while horizontally resizing the window or sidebars.
611* Fixed a crash when opening the audio player menu.
612* Fixed Gopher requests that were using URL (percent) encoded characters.
613* Windows: Fixed a flash of white when the window is first opened.
614
615## 1.1.4
616* Fixed feed entry highlight/read status issue in the sidebar.
617* Fixed Gopher menu links that contain spaces.
618* Fixed vertical alignment of short pages: top banner is excluded because it is not part of the content.
619
620## 1.1.3
621* Fixed crash when deleting a bookmark that was being used as a remote bookmark source.
622* Fixed potential crash at shutdown.
623* Fixed URL path decoding not respecting reserved characters.
624* AppImage: Disable SSE 4.1 for improved compatibility.
625* Windows: Fixed portable build so it stores user files under the "userdata" directory and not under AppData\Roaming\.
626
627## 1.1.2
628* Fixed potential crash at launch.
629* Fixed input query from a background tab being applied to the foreground tab. An input query now forces a tab to the foreground.
630* Fixed window scroll position moving when resizing vertically.
631* Fixed feed refresh waiting forever if a server doesn't respond.
632* Fixed hover state not being cleared when right-clicking links.
633* Fixed remote bookmarks not appearing if they lacked a user-friendly name on the source page.
634
635## 1.1.1
636* Fixed focus cycling inside dialogs. Widgets outside a dialog are not allowed to be focused.
637* Fixed missing cursor in the New Identity "Valid until" field.
638* Fixed "Import Links as Bookmarks" so it can be used to import local copies of remote bookmarks when viewing the remote source page.
639* Fixed a cosmetic issue in the Import Identity dialog where part of the widget frames were not drawn.
640* Fixed word wrapping issue in unread feed entry titles.
641* Fixed opening a URI that contains a fragment in the default browser. The fragment is no longer omitted.
642* Fixed default value for the Line Width setting.
643* Linux: Check monitor DPI for UI scaling like on Windows and macOS.
644* Allow reloading the same URL when clicking on a link or bookmark, or pressing Enter in the navbar.
645* Bookmark URLs are normalized by removing the default Gemini port.
646* Added a list of environment variables to "about:debug".
647* XDG: Respect `XDG_CONFIG_HOME` environment variable for storing user files.
648* XDG: Use the configured XDG download directory as default Downloads location.
649* The default set of bookmarks now uses a remote source.
650=> gemini://skyjake.fi/lagrange/getting_started.gmi "Getting Started" bookmarks
651
652## 1.1
653* Added identity importing: existing client certificates in PEM format can be imported from the current page, from clipboard, or from drag-and-dropped files.
654* Added setting for maximum cache size. This is the amount of fetched data kept in memory for page navigation and content lookup. The default is 10 MB.
655* Added option to show full link URL when hovering on it.
656* Added support for Finger links (courtesy of John Cowan).
657* Added keybindings for page reload, opening link in new tab via home row keys (${SHIFT+}F), and hovering on a link via home row keys (H).
658* Added keybinding for switching to next set of links for home row navigation.
659* Added keybindings for new tab, close tab, adding a bookmark.
660* Added a bold UI font. It is used for unread feed entries and default dialog buttons.
661* Better alignment of quick lookup result icons and labels.
662* Remote bookmarks are grouped under their source in the bookmark list.
663* A bookmark's icon is updated after loading the page so it matches the site.
664* Trusting a new server certificate manually will update the current page without reloading.
665* Middle-clicking on a tab title closes the tab.
666* Internal state files now use an ".lgr" extension. If downgrading after running v1.1, note that the old version will not read these.
667* Fixed timestamps of visited URLs. Your browsing history will be cleared but can still be viewed manually in "visited.txt".
668* Fixed issue where "heading" feed entries would turn unread after a while.
669* Fixed duplicate U key for home row navigation.
670* Fixed an issue with Unicode characters in quick lookup search terms.
671
672## 1.0.3
673* Improved font glyph caching: only rasterize glyphs when drawing text, and retry after failure. This makes initial document layout faster and avoids issues with permanently lost glyphs.
674* Fixed parts of text disappearing when the glyph cache fills up. The cache also uses less memory now.
675* Fixed percent-encoding of spaces when copying URLs to clipboard.
676* Fixed feed entry context menu showing the wrong menu item for Mark as Read/Unread.
677* Fixed parentheses and brackets being trimmed from feed entry titles.
678* Fixed quotes appearing continuous even when there are empty lines in between.
679* Fixed quote border line not showing up on empty quote lines.
680* Fixed handling of redirection in a background tab — redirected URL would open in the current tab.
681* Fixed Gemini URL normalization with regard to default port.
682* Fixed a very minor memory leak.
683* Fixed missing "gopher:" URL scheme registration on macOS.
684
685## 1.0.2
686* Fixed URL input field showing the wrong URL when opening tabs in the background.
687* Fixed trusting a renewed server certificate when multiple tabs are open.
688* Fixed opening sidebar feed entries in new/background tab with modifier keys.
689* Fixed editing identity notes. The entered new notes were not being applied.
690* Server certificate domain name check accepts a matching CN even when SAN doesn't match.
691
692## 1.0.1
693* Fixed percent-encoding of the query string.
694* Fixed cursor positioning in input fields.
695
696## 1.0
697* Added remote bookmarks. Any bookmarked 'text/gemini' page with the "remotesource" tag is a source of remote bookmarks. Each link on the page is shown as a remote bookmark in Bookmarks.
698* Added a way to export bookmarks via the special page "about:bookmarks". The page can also list all bookmarks by tag or by creation date.
699* Added context menu item for duplicating a bookmark.
700* Import all links on a page as bookmarks. (Importing tags is not supported in this version.)
701* Links can be bookmarked via context menu.
702* Open links with an unrecognized scheme in the default browser.
703* Open sidebar items in new/background tab via context menu or modifier keys.
704* "Edit Feed..." menu item shows the Feed Settings dialog.
705* Removed unimplemented menu items.
706
707## 0.13.2
708* Added a little spinner to indicate ongoing requests.
709* Fixed very thin progress bar on HiDPI displays.
710* Fixed link to Help page from the Identities sidebar tab.
711* Fixed incorrect text colors in the UI (e.g., selected lookup result).
712* macOS: Disable menu shortcuts for navigation when editing text to prevent accidental page changes.
713
714## 0.13.1
715* Fixed build failure on Linux.
716
717## 0.13
718* Support for Internationalized Domain Names (IDN) in network requests.
719* Percent-encoded URL paths are shown decoded in the UI, and encoded in outgoing requests.
720* Added option to disable decoding of percent-encoded paths for the UI.
721* Quick search via URL bar shows entries from subscribed feeds.
722* Added keybindings for zooming.
723* Improved usability of page content searching (${CTRL+}F, Escape).
724* Clicking on a certificate warning shows the Certificate Status dialog.
725* macOS: Keyboard shortcuts in native menus are updated according to bindings.
726* Tweaked handling of Emojis in monospace text. They are given more space as needed, but the original monospace spacing is restored after whitespace.
727* Fira Mono replaced with Iosevka Term Extended.
728* Fixed use of variable-width fonts in input fields.
729* Fixed handling of Unicode joiners and modifiers (by ignoring them, since we lack the glyphs).
730* Fixed a layout issue with sidebars where the bottommost content line was occasionally not visible.
731* Fixed exit when a hook program didn't read its input.
732* Fixed crash when using an identity (with LibreSSL on OpenBSD).
733
734## 0.12.1
735* 'text/*' content falls back to plain text.
736* Minimized visual artifacts in Unicode box-drawing characters (overlapping/gaps) by fine-tuning glyph scaling.
737* Fixed truncated tab titles when opening tabs in background.
738* Fixed possible exit if hook program not found (SIGPIPE).
739* REQUEST_URL is set in the environment when running MIME hooks.
740* "about:debug" lists the configured MIME hooks.
741* macOS: Fixed excessive CPU usage while idling.
742
743## 0.12
744* Added MIME hooks: pipe Gemini responses through external programs for arbitrary processing. (See "about:help" for usage.)
745* Added a right-hand sidebar; have a sidebar on the right or on both sides at once.
746* Added a clear warning banner when there is an issue with the server's TLS certificate.
747* Follow Weiph/pikkulogs — subscribe to new headings on pages.
748* Added UI for subscribing: feed name, entry type (Gemini feed or new headings).
749* Added keyboard shortcut ${SHIFT+}${CTRL+}D for subscribing to page.
750* Feeds sidebar is capped to 100 entries. "about:feeds" shows all known entries.
751* Network connections have a timeout in case server doesn't respond at all.
752* Adjusted spacing before/after links to reflect use of empty lines in the source.
753* Clicking on page area unfocuses URL input field.
754* Added keybindings for switching tabs.
755* Gopher: Query links have a 🔍 icon.
756* Fixed handling of "file:///" URIs on Windows.
757* Fixed misaligned Unicode box-drawing characters.
758* Fixed missing error page if status code is unknown (torture test 34).
759* Fixed detection of invalid headers (torture test 39).
760* Fixed rendering of soft hyphens (torture test 50).
761
762## 0.11
763* Added feed subscriptions. A subscription is any bookmark with the "subscribed" tag. Subscribed feeds are refreshed in the background while Lagrange is running.
764* Added a new sidebar tab for feeds.
765* Added "about:feeds" to show entries from all subscriptions on one page.
766* Added icons for special bookmark tags, and context menu items for toggling "homepage" and "subscribed".
767* Improved stability: fixed data races, undefined behavior, thread leaks.
768* Wide preformatted blocks can be scrolled horizontally with the mouse wheel or trackpad.
769* Line widths are slightly narrower for improved readability.
770* Light mode UI color palette is less saturated, more sepia-toned.
771* Tall menus/dialogs can be scrolled with the mouse wheel.
772* Improved download progress updates: never update more often than the UI can be refreshed.
773* macOS: Control-Click works as a right mouse click.
774* Unix: Location of `xdg-open` is no longer hardcoded.
775* Fixed large downloads (10 MB+) stopping prematurely due to a decryption error.
776* Fixed window contents not being updated during window resizing.
777* Fixed selection/search markers disappearing when scrolling down.
778* Fixed displaying of "about:" URLs in history.
779* Fixed build on FreeBSD (tested on 12.1).
780
781## 0.10
782* Added option to load inline images when pressing Space or ↓ for a more focused reading experience — just keep tapping a single key to proceed. If an image link is visible, it will be loaded instead of scrolling. This option is disabled by default.
783* Added context menu item to save inline images to Downloads.
784* Added an option to use a proxy server for Gemini requests.
785* Added a new keyboard link navigation mode focusing on the home row keys. The default keybinding for this is "F".
786* Added a keybinding to activate keyboard link modifier mode. The keyboard link keys are active while the modifier is held down. The default is ${ALT}.
787* Clearing and resetting keybindings via a context menu.
788* Added a Window tab in the Preferences dialog; moved some of the settings around for better organization.
789* Improved page search visualization: if the match is inside a link URL, the link icon is now highlighted. Previously these matches were not visualized in any way.
790* Improvements to URI parsing with regard to RFC 3986. Cases that are handled better are double slashes, query-only relative URIs, relative URIs that begin with a tilde, IPv6 literals, username in the authority.
791* Replaced EB Garamond with Tinos for improved readability.
792* Replaced Kosugi Maru with Noto Sans CJK JP for better glyph coverage.
793* Fixed font sizing of level 3 headings.
794* Fixed download progress indicators sometimes remaining visible even after leaving the page.
795
796## 0.9
797* Clicking on the top banner of a page (where the site icon and hostname are shown) navigates to the root directory of the site.
798* Added menu items and keybindings for navigating to site root or the parent directory.
799* Added option to use a monospace body text font on all Gemini and/or Gopher pages.
800* Remember redirect source URLs as visited but not shown in the History tab. Note that "visited.txt" is no longer fully compatible if opened in an older version of Lagrange.
801* "gopher:" scheme is allowed in command line arguments.
802* XDG: .desktop file declares support for opening Gopher URLs.
803* Fixed an issue where copying the URL input field would not place anything on the clipboard.
804* Fixed the Lagrange window visibly changing position during launch.
805* Fixed crash when a single percent sign was typed in an input field.
806* macOS: Fixed native menu keyboard shortcuts causing redundant command activations.
807* macOS: Fixed assigning keybindings when there is an equivalent native menu shortcut.
808
809## 0.8.1
810* Fixed potential lockup when navigating back to a query prompt.
811* macOS: Improved handling of scroll wheel events from a mouse.
812
813## 0.8
814* Added support for Gopher.
815* Added support for the full palette of 8-bit ANSI foreground colors.
816* Added option to disable smooth scrolling.
817* Added button to manually set server certificate as trusted (if the certificate is valid but untrusted).
818* Added keybindings for Back/Forward navigation.
819* Added a context menu item for opening HTTP links in the default browser even when a proxy is configured.
820* Revised identity creation dialog: changed field order, added warning about temporary identities not being saved.
821* ${CTRL+}Click opens tab in background, ${SHIFT+}${CTRL+}Click opens as foreground tab. The same modifier keys work with keyboard navigation.
822* Improved word wrapping of emoticons (:D).
823* Automatic redirects allowed when the destination URL uses the same scheme as the originating URL. For example, when using a proxy, HTTP(S) is allowed to automatically redirect to other HTTP(S) URLs.
824* Windows: Fixed handling of drag-and-dropped and command line file paths.
825
826## 0.7.2
827* Fixed parsing of the server's response. In some cases it was possible that the response was only partially read.
828* Fixed handling of TLS/SSL connection being closed without the socket being closed.
829
830## 0.7.1
831* Fixed build on OpenBSD.
832* Fixed build with LibreSSL.
833* Fixed a potential crash at app shutdown.
834* Fixed a potential crash when a thread exits.
835* Fixed a potential lockup when a thread exits.
836* Linux/Unix: Open "mailto:" links with xdg-open instead of the web browser.
837
838## 0.7
839* Basic set of user-configurable key bindings.
840* Sidebar: Added a "New Identity" button and a link to "about:help" if there are no identities.
841* Faster drawing of certain UI elements: site icon and current heading in the left margin, unfocused input fields, timestamp at the end of the page.
842* History is not updated until a network request finishes.
843* Improved opening connections when multiple IP addresses are found for a hostname.
844* Fixed handling of TLS/SSL errors and hostname lookup problems — an error page is shown.
845* Fixed an issue where window contents were not being updated immediately after the window gets exposed when using, e.g., openbox or dwm.
846
847## 0.6
848* Added an indicator to visualize progress of network requests.
849* Added new color themes for page content: Colorful Light, Black, Gray, Sepia, High Contrast.
850* Added page content color theme selection in Preferences.
851* Added quote indicator option: icon or vertical line.
852* Added a new font for Korean glyphs.
853* Smoother smooth scrolling, making it easier to keep one's eyes on the content throughout the motion.
854* Windows: Register Lagrange as a handler of "gemini:" URLs.
855* macOS: Fixed glitchy window dragging during audio playback.
856* Fixed timestamps of cached pages.
857
858## 0.5
859* Added MP3 support in the audio player (using mpg123).
860* Added volume control in the audio player.
861* Metadata in Vorbis and MP3 audio content (title, artist, etc.) is shown in the audio player menu.
862* Added new serif fonts: EB Garamond and Literata.
863* Allow configuring separate fonts for headings and body text for better visual distinction.
864* Preferences dialog remembers the previously open tab.
865* Paste from clipboard on middle mouse button click.
866* Open links in new tab with middle mouse button.
867* Fixed failure to find resources when launching via PATH.
868* Fixed color saturation setting not affecting the default color theme.
869=> https://mpg123.org/ mpg123: MPEG audio player and decoder library
870
871## 0.4.1
872* Set keyboard focus to URL input field after opening a new tab.
873* Pause other audio players when a new one is started. One can still choose to have multiple audio players playing simultaneously by unpausing them again.
874* Fixed dismissing an audio player that is still downloading content. The partially downloaded data is discarded.
875* Fixed saving pages whose name starts with a tilde.
876* Fixed saving pages restored from cache.
877* Windows: The app is now distributed as an installer created with Inno Setup.
878* Windows: All binaries are signed.
879
880## 0.4
881* Added audio playback with support for streaming. Supported audio formats in this release are WAV (PCM, mono/stereo, 8/16/24/32 integer/float) and Ogg Vorbis. Shoutout to Sean Barrett et al. for stb_vorbis.
882* Added inline audio player that works like inline images. Clicking on an audio link opens the audio player below the link (works for URLs that have file extension .wav/.ogg).
883* Visual fine-tuning: increased Fira Sans line spacing; list bullets use an accent color; adjusted accent colors in the light mode palette.
884* Sidebar has a maximum width — the document must remain visible.
885* Windows: Support for HiDPI displays and the system UI scaling factor. The UI will be scaled according to your settings automatically without having to adjust the UI scaling in Preferences.
886* macOS: Use OpenGL on 10.13 for potentially better compatibility.
887* Fixed a memory leak when closing tabs.
888* Fixed unnecessary continual window redrawing related to the scrollbar hover outline.
889=> https://github.com/nothings/stb stb: single-file public domain libraries for C/C++
890
891## 0.3
892* Added style customization.
893* Added new font option: Fira Sans.
894* Added a setting for maximum line width.
895* Added a setting for adjusting color saturation.
896* Added an option for "Outline on scrollbar": page outline appears when mouse is hovering over the scrollbar.
897* Added an option for site icon and current top heading that appear when the window is wide enough.
898* Added tabs in Preferences for better grouping.
899* Added "Open Link in Background Tab" in link context menus.
900* More flexible text selection behavior when starting on empty space.
901* Smaller first paragraph font size.
902* Fixed centering of popups so they remain centered when window is resized.
903* Fixed sizing and alignment of Unicode symbols in preformatted text.
904* Fixed sizing of Japanese glyphs in UI text (e.g., tab titles).
905
906## 0.2.1
907* Fixed window size/state restoration issues, e.g., after window is maximized.
908* Windows: Fixed text disappearing when window is resized.
909
910## 0.2
911* Added an icon for quote paragraphs.
912* Added Downloads folder to Preferences.
913* Added "Save to Downloads" menu item (${CTRL+}S) for saving page contents.
914* Added a download progress indicator in the URL input field.
915* Added a progress indicator for inline image fetching.
916* Added `--sw` option to force software rendering.
917* Added macOS touch bar buttons for Back, Forward, Find, New Tab, and sidebar modes.
918* Home button opens a random bookmark with the "homepage" tag.
919* Improved context menu when right-clicking on links or the page.
920* Recognize and handle "mailto:" links.
921* Fixed behavior of images on single-image pages; cannot be hidden like inline images.
922* Fall back to software rendering automatically if accelerated graphics are not available.
923* Minor bug fixes.
924
925## 0.1.1
926* Fixed a potential crash at startup.
927* Fixed bug where user's query input is handled by all tabs.
928* Default sidebar mode is Bookmarks.
929* Windows: Fixed opening HTTP links in the default web browser.
930
931## 0.1
932* The major version zero is reserved for non-feature-complete releases.
933* Beautiful typography using Unicode fonts.
934* Autogenerated page style and Unicode icon for each Gemini domain.
935* Smart suggestions when typing the URL — search bookmarks, history, identities.
936* Sidebar for page outline, managing bookmarks and identities, and viewing history.
937* Multiple tabs.
938* Identity management — create and use TLS client certificates.
939* Light and dark UI themes.
940* Select and copy text with the mouse.
941* Find text on the page.
942* Open image links inline on the same page.
943* Open links via keyboard shortcuts.
944* Instant back/forward navigation.
945* Smooth scrolling.
946* Scaling page content (50%...200%).
947* Scaling factor for the UI (for arbitrary monitor DPI).
948* Persistent app state — tabs and history are restored on next run.
diff --git a/res/fonts/SmolEmoji-Regular.ttf b/res/fonts/SmolEmoji-Regular.ttf
index a9694f3a..08c5ca2d 100644
--- a/res/fonts/SmolEmoji-Regular.ttf
+++ b/res/fonts/SmolEmoji-Regular.ttf
Binary files differ
diff --git a/res/lagrange.1 b/res/lagrange.1
new file mode 100644
index 00000000..7b8815c1
--- /dev/null
+++ b/res/lagrange.1
@@ -0,0 +1,149 @@
1.\" Automatically generated by Pandoc 2.9.2.1
2.\"
3.TH "LAGRANGE" "1" "January 2022" "" ""
4.hy
5.SH NAME
6.PP
7lagrange - a beautiful Gemini client
8.SH SYNOPSIS
9.PP
10\f[B]lagrange\f[R]
11[options]\ [\f[I]URL\f[R]]\&...\ [\f[I]FILE\f[R]]\&...
12.SH DESCRIPTION
13.PP
14Lagrange is a graphical client for the Gemini, Gopher, and Finger
15protocols.
16It offers modern conveniences familiar from web browsers, such as smooth
17scrolling, inline image viewing, multiple tabs, visual themes, Unicode
18fonts, and bookmarks.
19.SH OPTIONS
20.PP
21When multiple URLs and/or local files are specified, they are opened in
22separate tabs.
23.TP
24\f[B]-E\f[R], \f[B]--echo\f[R]
25Print all internal application events to stdout.
26Useful for debugging.
27.TP
28\f[B]--help\f[R]
29List the available command line options.
30.TP
31\f[B]-u\f[R], \f[B]--url-or-search\f[R] \f[I]URL\f[R] | \f[I]TEXT\f[R]
32Open a URL, or make a search query with given text.
33This only works if the search query URL has been configured.
34.TP
35\f[B]-V\f[R], \f[B]--version\f[R]
36Output the version number.
37.SS Window options:
38.TP
39\f[B]-h\f[R], \f[B]--height\f[R] \f[I]N\f[R]
40Set initial window height to \f[I]N\f[R] pixels.
41.TP
42\f[B]--sw\f[R]
43Disable hardware-accelerated rendering.
44.TP
45\f[B]-w\f[R], \f[B]--width\f[R] \f[I]N\f[R]
46Set initial window width to \f[I]N\f[R] pixels.
47.SS Control options:
48.PP
49These options are used to control the currently running Lagrange
50instance via the command line.
51.TP
52\f[B]--close-tab\f[R]
53Close the current tab.
54.TP
55\f[B]-L\f[R], \f[B]--list-tab-urls\f[R]
56Print the URLs of open tabs to stdout.
57If the app isn\[cq]t running, nothing is printed.
58.TP
59\f[B]--new-tab\f[R] [\f[I]URL\f[R]]
60Open a new tab.
61If the URL argument is omitted, the user\[cq]s homepage is opened.
62.TP
63\f[B]--tab-url\f[R]
64Print the URL of the active tab.
65.SH ENVIRONMENT
66.TP
67\f[B]\f[CB]LAGRANGE_OVERRIDE_DPI\f[B]\f[R]
68Override the autodetected screen DPI with a user-provided value.
69Some window systems and/or monitors may not provide an appropriate DPI
70value, so this enables further tuning the UI scaling in addition to the
71\[lq]UI scale factor\[rq] found in Preferences.
72.SH FILES
73.PP
74User-specific files such as bookmarks and navigation history are stored
75in the following operating system dependent locations:
76.IP \[bu] 2
77Windows:
78\[lq]C:\[rs]Users\[rs]Name\[rs]AppData\[rs]Roaming\[rs]fi.skyjake.Lagrange\[rq]
79.IP \[bu] 2
80macOS: \[lq]\[ti]/Library/Application Support/fi.skyjake.Lagrange\[rq]
81.IP \[bu] 2
82Other: \[lq]\[ti]/.config/lagrange\[rq]
83.PP
84The directory contains:
85.TP
86\f[B]bindings.txt\f[R]
87Customized key bindings.
88.TP
89\f[B]bookmarks.ini\f[R]
90Bookmarks in TOML format.
91.TP
92\f[B]feeds.txt\f[R]
93State of subscribed feeds: all the known entries and latest update
94timestamps.
95.TP
96\f[B]fonts.ini\f[R]
97Custom fonts to load at launch.
98.TP
99\f[B]idents.lgr\f[R]
100Information about identities.
101.TP
102\f[B]idents/\f[R]
103Subdirectory containing client certificates and private keys in PEM
104format.
105.TP
106\f[B]modmap.txt\f[R]
107Customized keyboard modifier mapping.
108.TP
109\f[B]mimehooks.txt\f[R]
110Configuration of external programs to filter page contents depending on
111MIME type.
112.TP
113\f[B]palette.txt\f[R]
114Colors of the UI palette.
115.TP
116\f[B]prefs.cfg\f[R]
117User\[cq]s preferences.
118This is a list of UI events that gets executed at launch (cf.\ output of
119\f[B]--echo\f[R]).
120.TP
121\f[B]state.lgr\f[R]
122Serialized UI state, specifying open tabs and sidebar state.
123.TP
124\f[B]sitespec.ini\f[R]
125Site-specific preferences in TOML format.
126.TP
127\f[B]trusted.2.txt\f[R]
128Fingerprints of trusted server certificates.
129.TP
130\f[B]visited.2.txt\f[R]
131List of visited URLs with timestamps.
132.SH STANDARDS
133.IP \[bu] 2
134Gemini Protocol
135Specification (https://gemini.circumlunar.space/docs/specification.gmi)
136.IP \[bu] 2
137Gempub Specification (https://codeberg.org/oppenlab/gempub)
138.IP \[bu] 2
139RFC 1436: The Internet Gopher
140Protocol (https://datatracker.ietf.org/doc/html/rfc1436)
141.IP \[bu] 2
142RFC 1288: The Finger User Information
143Protocol (https://datatracker.ietf.org/doc/html/rfc1288)
144.SH SEE ALSO
145.PP
146Open \[lq]about:help\[rq] in the application to view the complete Help
147page.
148.SH AUTHORS
149Jaakko Ker\[:a]nen (jaakko.keranen\[at]iki.fi).
diff --git a/res/lagrange.1.md b/res/lagrange.1.md
new file mode 100644
index 00000000..658e0ddf
--- /dev/null
+++ b/res/lagrange.1.md
@@ -0,0 +1,127 @@
1% LAGRANGE(1)
2% Jaakko Keränen (jaakko.keranen@iki.fi)
3% January 2022
4
5# NAME
6
7lagrange - a beautiful Gemini client
8
9# SYNOPSIS
10
11**lagrange** \[options\]\ \[_URL_\]...\ \[_FILE_\]...
12
13# DESCRIPTION
14
15Lagrange is a graphical client for the Gemini, Gopher, and Finger protocols.
16It offers modern conveniences familiar from web browsers, such as smooth scrolling, inline image viewing, multiple tabs, visual themes, Unicode fonts, and bookmarks.
17
18# OPTIONS
19
20When multiple URLs and/or local files are specified, they are opened in separate tabs.
21
22**-E**, **\--echo**
23: Print all internal application events to stdout. Useful for debugging.
24
25**\--help**
26: List the available command line options.
27
28**-u**, **\--url-or-search** _URL_ | _TEXT_
29: Open a URL, or make a search query with given text. This only works if the search query URL has been configured.
30
31**-V**, **\--version**
32: Output the version number.
33
34## Window options:
35
36**-h**, **\--height** _N_
37: Set initial window height to _N_ pixels.
38
39**\--sw**
40: Disable hardware-accelerated rendering.
41
42**-w**, **\--width** _N_
43: Set initial window width to _N_ pixels.
44
45## Control options:
46
47These options are used to control the currently running Lagrange instance via the command line.
48
49**\--close-tab**
50: Close the current tab.
51
52**-L**, **\--list-tab-urls**
53: Print the URLs of open tabs to stdout. If the app isn't running, nothing is printed.
54
55**\--new-tab** [_URL_]
56: Open a new tab. If the URL argument is omitted, the user's homepage is opened.
57
58**\--tab-url**
59: Print the URL of the active tab.
60
61# ENVIRONMENT
62
63`LAGRANGE_OVERRIDE_DPI`
64: Override the autodetected screen DPI with a user-provided value. Some window systems and/or monitors may not provide an appropriate DPI value, so this enables further tuning the UI scaling in addition to the "UI scale factor" found in Preferences.
65
66# FILES
67
68User-specific files such as bookmarks and navigation history are stored in the following operating system dependent locations:
69
70- Windows: "C:\\Users\\Name\\AppData\\Roaming\\fi.skyjake.Lagrange"
71- macOS: "~/Library/Application Support/fi.skyjake.Lagrange"
72- Other: "~/.config/lagrange"
73
74The directory contains:
75
76**bindings.txt**
77: Customized key bindings.
78
79**bookmarks.ini**
80: Bookmarks in TOML format.
81
82**feeds.txt**
83: State of subscribed feeds: all the known entries and latest update timestamps.
84
85**fonts.ini**
86: Custom fonts to load at launch.
87
88**idents.lgr**
89: Information about identities.
90
91**idents/**
92: Subdirectory containing client certificates and private keys in PEM format.
93
94**modmap.txt**
95: Customized keyboard modifier mapping.
96
97**mimehooks.txt**
98: Configuration of external programs to filter page contents depending on MIME type.
99
100**palette.txt**
101: Colors of the UI palette.
102
103**prefs.cfg**
104: User's preferences. This is a list of UI events that gets executed at launch (cf. output of **\--echo**).
105
106**state.lgr**
107: Serialized UI state, specifying open tabs and sidebar state.
108
109**sitespec.ini**
110: Site-specific preferences in TOML format.
111
112**trusted.2.txt**
113: Fingerprints of trusted server certificates.
114
115**visited.2.txt**
116: List of visited URLs with timestamps.
117
118# STANDARDS
119
120* [Gemini Protocol Specification](https://gemini.circumlunar.space/docs/specification.gmi)
121* [Gempub Specification](https://codeberg.org/oppenlab/gempub)
122* [RFC 1436: The Internet Gopher Protocol](https://datatracker.ietf.org/doc/html/rfc1436)
123* [RFC 1288: The Finger User Information Protocol](https://datatracker.ietf.org/doc/html/rfc1288)
124
125# SEE ALSO
126
127Open "about:help" in the application to view the complete Help page. \ No newline at end of file
diff --git a/res/lang/gl.bin b/res/lang/gl.bin
index 7727f587..925994f9 100644
--- a/res/lang/gl.bin
+++ b/res/lang/gl.bin
Binary files differ
diff --git a/res/makeman.sh b/res/makeman.sh
new file mode 100755
index 00000000..510c4cf6
--- /dev/null
+++ b/res/makeman.sh
@@ -0,0 +1,3 @@
1#!/bin/sh
2pandoc lagrange.1.md -s -t man > lagrange.1
3
diff --git a/src/app.c b/src/app.c
index 2b1ca1b6..1820905b 100644
--- a/src/app.c
+++ b/src/app.c
@@ -124,7 +124,7 @@ struct Impl_App {
124 iMimeHooks * mimehooks; 124 iMimeHooks * mimehooks;
125 iGmCerts * certs; 125 iGmCerts * certs;
126 iVisited * visited; 126 iVisited * visited;
127 iBookmarks * bookmarks; 127 iBookmarks * bookmarks;
128 iMainWindow *window; 128 iMainWindow *window;
129 iPtrArray popupWindows; 129 iPtrArray popupWindows;
130 iSortedArray tickers; /* per-frame callbacks, used for animations */ 130 iSortedArray tickers; /* per-frame callbacks, used for animations */
@@ -333,7 +333,9 @@ static const char *dataDir_App_(void) {
333 333
334static const char *downloadDir_App_(void) { 334static const char *downloadDir_App_(void) {
335#if defined (iPlatformAndroidMobile) 335#if defined (iPlatformAndroidMobile)
336 return concatPath_CStr(SDL_AndroidGetInternalStoragePath(), "Downloads"); 336 const char *dir = concatPath_CStr(SDL_AndroidGetExternalStoragePath(), "Downloads");
337 makeDirs_Path(collectNewCStr_String(dir));
338 return dir;
337#endif 339#endif
338#if defined (iPlatformLinux) || defined (iPlatformOther) 340#if defined (iPlatformLinux) || defined (iPlatformOther)
339 /* Parse user-dirs.dirs using the `xdg-user-dir` tool. */ 341 /* Parse user-dirs.dirs using the `xdg-user-dir` tool. */
@@ -759,7 +761,7 @@ static void init_App_(iApp *d, int argc, char **argv) {
759 d->isSuspended = iFalse; 761 d->isSuspended = iFalse;
760 d->tempFilesPendingDeletion = new_StringSet(); 762 d->tempFilesPendingDeletion = new_StringSet();
761 init_CommandLine(&d->args, argc, argv); 763 init_CommandLine(&d->args, argc, argv);
762 /* Where was the app started from? We ask SDL first because the command line alone 764 /* Where was the app started from? We ask SDL first because the command line alone
763 cannot be relied on (behavior differs depending on OS). */ { 765 cannot be relied on (behavior differs depending on OS). */ {
764 char *exec = SDL_GetBasePath(); 766 char *exec = SDL_GetBasePath();
765 if (exec) { 767 if (exec) {
@@ -1289,9 +1291,10 @@ void processEvents_App(enum iAppEventMode eventMode) {
1289 iRoot *oldCurrentRoot = current_Root(); /* restored afterwards */ 1291 iRoot *oldCurrentRoot = current_Root(); /* restored afterwards */
1290 SDL_Event ev; 1292 SDL_Event ev;
1291 iBool gotEvents = iFalse; 1293 iBool gotEvents = iFalse;
1294 iBool gotRefresh = iFalse;
1292 iPtrArray windows; 1295 iPtrArray windows;
1293 init_PtrArray(&windows); 1296 init_PtrArray(&windows);
1294 while (nextEvent_App_(d, eventMode, &ev)) { 1297 while (nextEvent_App_(d, gotRefresh ? postedEventsOnly_AppEventMode : eventMode, &ev)) {
1295#if defined (iPlatformAppleMobile) 1298#if defined (iPlatformAppleMobile)
1296 if (processEvent_iOS(&ev)) { 1299 if (processEvent_iOS(&ev)) {
1297 continue; 1300 continue;
@@ -1314,9 +1317,9 @@ void processEvents_App(enum iAppEventMode eventMode) {
1314 d->isSuspended = iFalse; 1317 d->isSuspended = iFalse;
1315 break; 1318 break;
1316 case SDL_APP_DIDENTERFOREGROUND: 1319 case SDL_APP_DIDENTERFOREGROUND:
1317 gotEvents = iTrue;
1318 d->warmupFrames = 5; 1320 d->warmupFrames = 5;
1319#if defined (LAGRANGE_ENABLE_IDLE_SLEEP) 1321#if defined (LAGRANGE_ENABLE_IDLE_SLEEP)
1322 gotEvents = iTrue;
1320 d->isIdling = iFalse; 1323 d->isIdling = iFalse;
1321 d->lastEventTime = SDL_GetTicks(); 1324 d->lastEventTime = SDL_GetTicks();
1322#endif 1325#endif
@@ -1366,6 +1369,10 @@ void processEvents_App(enum iAppEventMode eventMode) {
1366 iRelease(ev.user.data1); 1369 iRelease(ev.user.data1);
1367 continue; 1370 continue;
1368 } 1371 }
1372 if (ev.type == SDL_USEREVENT && ev.user.code == refresh_UserEventCode) {
1373 gotRefresh = iTrue;
1374 continue;
1375 }
1369#if defined (LAGRANGE_ENABLE_IDLE_SLEEP) 1376#if defined (LAGRANGE_ENABLE_IDLE_SLEEP)
1370 if (ev.type == SDL_USEREVENT && ev.user.code == asleep_UserEventCode) { 1377 if (ev.type == SDL_USEREVENT && ev.user.code == asleep_UserEventCode) {
1371 if (SDL_GetTicks() - d->lastEventTime > idleThreshold_App_ && 1378 if (SDL_GetTicks() - d->lastEventTime > idleThreshold_App_ &&
@@ -1384,8 +1391,8 @@ void processEvents_App(enum iAppEventMode eventMode) {
1384// fflush(stdout); 1391// fflush(stdout);
1385 } 1392 }
1386 d->isIdling = iFalse; 1393 d->isIdling = iFalse;
1387#endif
1388 gotEvents = iTrue; 1394 gotEvents = iTrue;
1395#endif
1389 /* Keyboard modifier mapping. */ 1396 /* Keyboard modifier mapping. */
1390 if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) { 1397 if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) {
1391 /* Track Caps Lock state as a modifier. */ 1398 /* Track Caps Lock state as a modifier. */
@@ -1395,6 +1402,16 @@ void processEvents_App(enum iAppEventMode eventMode) {
1395 ev.key.keysym.mod = mapMods_Keys(ev.key.keysym.mod & ~KMOD_CAPS); 1402 ev.key.keysym.mod = mapMods_Keys(ev.key.keysym.mod & ~KMOD_CAPS);
1396 } 1403 }
1397#if defined (iPlatformAndroidMobile) 1404#if defined (iPlatformAndroidMobile)
1405 /* Use the system Back button to close panels, if they're open. */
1406 if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_AC_BACK) {
1407 SDL_UserEvent panelBackCmd = { .type = SDL_USEREVENT,
1408 .code = command_UserEventCode,
1409 .data1 = iDupStr("panel.close"),
1410 .data2 = d->window->base.keyRoot };
1411 if (dispatchEvent_Window(&d->window->base, (SDL_Event *) &panelBackCmd)) {
1412 continue; /* Was handled by someone. */
1413 }
1414 }
1398 /* Ignore all mouse events; just use touch. */ 1415 /* Ignore all mouse events; just use touch. */
1399 if (ev.type == SDL_MOUSEBUTTONDOWN || 1416 if (ev.type == SDL_MOUSEBUTTONDOWN ||
1400 ev.type == SDL_MOUSEBUTTONUP || 1417 ev.type == SDL_MOUSEBUTTONUP ||
@@ -2078,7 +2095,6 @@ iDocumentWidget *document_Command(const char *cmd) {
2078} 2095}
2079 2096
2080iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNew) { 2097iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNew) {
2081 //iApp *d = &app_;
2082 iWidget *tabs = findWidget_Root("doctabs"); 2098 iWidget *tabs = findWidget_Root("doctabs");
2083 setFlags_Widget(tabs, hidden_WidgetFlag, iFalse); 2099 setFlags_Widget(tabs, hidden_WidgetFlag, iFalse);
2084 iWidget *newTabButton = findChild_Widget(tabs, "newtab"); 2100 iWidget *newTabButton = findChild_Widget(tabs, "newtab");
@@ -2094,6 +2110,7 @@ iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNe
2094 iRelease(doc); /* now owned by the tabs */ 2110 iRelease(doc); /* now owned by the tabs */
2095 addTabCloseButton_Widget(tabs, as_Widget(doc), "tabs.close"); 2111 addTabCloseButton_Widget(tabs, as_Widget(doc), "tabs.close");
2096 addChild_Widget(findChild_Widget(tabs, "tabs.buttons"), iClob(newTabButton)); 2112 addChild_Widget(findChild_Widget(tabs, "tabs.buttons"), iClob(newTabButton));
2113 showOrHideNewTabButton_Root(tabs->root);
2097 if (switchToNew) { 2114 if (switchToNew) {
2098 postCommandf_App("tabs.switch page:%p", doc); 2115 postCommandf_App("tabs.switch page:%p", doc);
2099 } 2116 }
@@ -2876,6 +2893,7 @@ iBool handleCommand_App(const char *cmd) {
2876 return iTrue; 2893 return iTrue;
2877 } 2894 }
2878 iDocumentWidget *doc = document_Command(cmd); 2895 iDocumentWidget *doc = document_Command(cmd);
2896 iAssert(doc);
2879 iDocumentWidget *origin = doc; 2897 iDocumentWidget *origin = doc;
2880 if (hasLabel_Command(cmd, "origin")) { 2898 if (hasLabel_Command(cmd, "origin")) {
2881 iDocumentWidget *cmdOrig = findWidget_App(cstr_Command(cmd, "origin")); 2899 iDocumentWidget *cmdOrig = findWidget_App(cstr_Command(cmd, "origin"));
diff --git a/src/feeds.c b/src/feeds.c
index a8cbf47a..7b679dc1 100644
--- a/src/feeds.c
+++ b/src/feeds.c
@@ -275,7 +275,8 @@ static void save_Feeds_(iFeeds *d) {
275 if (open_File(f, write_FileMode | text_FileMode)) { 275 if (open_File(f, write_FileMode | text_FileMode)) {
276 lock_Mutex(d->mtx); 276 lock_Mutex(d->mtx);
277 iString *str = new_String(); 277 iString *str = new_String();
278 format_String(str, "%llu\n# Feeds\n", integralSeconds_Time(&d->lastRefreshedAt)); 278 format_String(str, "%llu\n# Feeds\n", (unsigned long long)
279 integralSeconds_Time(&d->lastRefreshedAt));
279 write_File(f, utf8_String(str)); 280 write_File(f, utf8_String(str));
280 /* Index of feeds for IDs. */ { 281 /* Index of feeds for IDs. */ {
281 iConstForEach(PtrArray, i, listSubscriptions_()) { 282 iConstForEach(PtrArray, i, listSubscriptions_()) {
@@ -296,8 +297,8 @@ static void save_Feeds_(iFeeds *d) {
296 } 297 }
297 format_String(str, "%x\n%llu\n%llu\n%s\n%s\n", 298 format_String(str, "%x\n%llu\n%llu\n%s\n%s\n",
298 entry->bookmarkId, 299 entry->bookmarkId,
299 integralSeconds_Time(&entry->posted), 300 (unsigned long long) integralSeconds_Time(&entry->posted),
300 integralSeconds_Time(&entry->discovered), 301 (unsigned long long) integralSeconds_Time(&entry->discovered),
301 cstr_String(&entry->url), 302 cstr_String(&entry->url),
302 cstr_String(&entry->title)); 303 cstr_String(&entry->title));
303 write_File(f, utf8_String(str)); 304 write_File(f, utf8_String(str));
diff --git a/src/gmdocument.c b/src/gmdocument.c
index 19230392..b5e71e21 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -547,9 +547,11 @@ static void clear_RunTypesetter_(iRunTypesetter *d) {
547 clear_Array(&d->layout); 547 clear_Array(&d->layout);
548} 548}
549 549
550static void commit_RunTypesetter_(iRunTypesetter *d, iGmDocument *doc) { 550static size_t commit_RunTypesetter_(iRunTypesetter *d, iGmDocument *doc) {
551 const size_t n = size_Array(&d->layout);
551 pushBackN_Array(&doc->layout, constData_Array(&d->layout), size_Array(&d->layout)); 552 pushBackN_Array(&doc->layout, constData_Array(&d->layout), size_Array(&d->layout));
552 clear_RunTypesetter_(d); 553 clear_RunTypesetter_(d);
554 return n;
553} 555}
554 556
555static const int maxLedeLines_ = 10; 557static const int maxLedeLines_ = 10;
@@ -611,6 +613,10 @@ static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange,
611} 613}
612 614
613static void doLayout_GmDocument_(iGmDocument *d) { 615static void doLayout_GmDocument_(iGmDocument *d) {
616 static iRegExp *ansiPattern_;
617 if (!ansiPattern_) {
618 ansiPattern_ = makeAnsiEscapePattern_Text(iTrue /* with ESC */);
619 }
614 const iPrefs *prefs = prefs_App(); 620 const iPrefs *prefs = prefs_App();
615 const iBool isMono = isForcedMonospace_GmDocument_(d); 621 const iBool isMono = isForcedMonospace_GmDocument_(d);
616 const iBool isGopher = isGopher_GmDocument_(d); 622 const iBool isGopher = isGopher_GmDocument_(d);
@@ -618,8 +624,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
618 const iBool isVeryNarrow = d->size.x <= 70 * gap_Text; 624 const iBool isVeryNarrow = d->size.x <= 70 * gap_Text;
619 const iBool isExtremelyNarrow = d->size.x <= 60 * gap_Text; 625 const iBool isExtremelyNarrow = d->size.x <= 60 * gap_Text;
620 const iBool isFullWidthImages = (d->outsideMargin < 5 * gap_UI); 626 const iBool isFullWidthImages = (d->outsideMargin < 5 * gap_UI);
621// const iBool isDarkBg = isDark_GmDocumentTheme( 627
622// isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight);
623 initTheme_GmDocument_(d); 628 initTheme_GmDocument_(d);
624 d->isLayoutInvalidated = iFalse; 629 d->isLayoutInvalidated = iFalse;
625 /* TODO: Collect these parameters into a GmTheme. */ 630 /* TODO: Collect these parameters into a GmTheme. */
@@ -657,7 +662,6 @@ static void doLayout_GmDocument_(iGmDocument *d) {
657 const iArray *oldPreMeta = collect_Array(copy_Array(&d->preMeta)); /* remember fold states */ 662 const iArray *oldPreMeta = collect_Array(copy_Array(&d->preMeta)); /* remember fold states */
658 clear_Array(&d->preMeta); 663 clear_Array(&d->preMeta);
659 clear_String(&d->title); 664 clear_String(&d->title);
660// clear_String(&d->bannerText);
661 if (d->size.x <= 0 || isEmpty_String(&d->source)) { 665 if (d->size.x <= 0 || isEmpty_String(&d->source)) {
662 return; 666 return;
663 } 667 }
@@ -671,7 +675,6 @@ static void doLayout_GmDocument_(iGmDocument *d) {
671 int preFont = preformatted_FontId; 675 int preFont = preformatted_FontId;
672 uint16_t preId = 0; 676 uint16_t preId = 0;
673 iBool enableIndents = iFalse; 677 iBool enableIndents = iFalse;
674// iBool addSiteBanner = d->bannerType != none_GmDocumentBanner;
675 const iBool isNormalized = isNormalized_GmDocument_(d); 678 const iBool isNormalized = isNormalized_GmDocument_(d);
676 enum iGmLineType prevType = text_GmLineType; 679 enum iGmLineType prevType = text_GmLineType;
677 enum iGmLineType prevNonBlankType = text_GmLineType; 680 enum iGmLineType prevNonBlankType = text_GmLineType;
@@ -755,7 +758,6 @@ static void doLayout_GmDocument_(iGmDocument *d) {
755 if (d->format == gemini_SourceFormat && 758 if (d->format == gemini_SourceFormat &&
756 startsWithSc_Rangecc(line, "```", &iCaseSensitive)) { 759 startsWithSc_Rangecc(line, "```", &iCaseSensitive)) {
757 isPreformat = iFalse; 760 isPreformat = iFalse;
758// addSiteBanner = iFalse; /* overrides the banner */
759 continue; 761 continue;
760 } 762 }
761 run.mediaType = max_MediaType; /* preformatted block */ 763 run.mediaType = max_MediaType; /* preformatted block */
@@ -763,28 +765,6 @@ static void doLayout_GmDocument_(iGmDocument *d) {
763 run.font = (d->format == plainText_SourceFormat ? plainText_FontId : preFont); 765 run.font = (d->format == plainText_SourceFormat ? plainText_FontId : preFont);
764 indent = indents[type]; 766 indent = indents[type];
765 } 767 }
766#if 0
767 if (addSiteBanner) {
768 addSiteBanner = iFalse;
769 const iRangecc bannerText = urlHost_String(&d->url);
770 if (!isEmpty_Range(&bannerText)) {
771 setRange_String(&d->bannerText, bannerText);
772 iGmRun banner = { .flags = decoration_GmRunFlag | siteBanner_GmRunFlag };
773 banner.bounds = zero_Rect();
774 banner.visBounds = init_Rect(0, 0, d->size.x, lineHeight_Text(banner_FontId) * 2);
775 if (d->bannerType == certificateWarning_GmDocumentBanner) {
776 banner.visBounds.size.y += iMaxi(6000 * lineHeight_Text(uiLabel_FontId) /
777 d->size.x, lineHeight_Text(uiLabel_FontId) * 5);
778 }
779 banner.text = bannerText;
780 banner.font = banner_FontId;
781 banner.color = tmBannerTitle_ColorId;
782 pushBack_Array(&d->layout, &banner);
783 pos.y += height_Rect(banner.visBounds) +
784 1.5f * lineHeight_Text(paragraph_FontId) * prefs->lineSpacing;
785 }
786 }
787#endif
788 /* Empty lines don't produce text runs. */ 768 /* Empty lines don't produce text runs. */
789 if (isEmpty_Range(&line)) { 769 if (isEmpty_Range(&line)) {
790 if (type == quote_GmLineType && !prefs->quoteIcon) { 770 if (type == quote_GmLineType && !prefs->quoteIcon) {
@@ -865,6 +845,8 @@ static void doLayout_GmDocument_(iGmDocument *d) {
865 if ((type == heading1_GmLineType || type == heading2_GmLineType) && 845 if ((type == heading1_GmLineType || type == heading2_GmLineType) &&
866 isEmpty_String(&d->title)) { 846 isEmpty_String(&d->title)) {
867 setRange_String(&d->title, line); 847 setRange_String(&d->title, line);
848 /* Get rid of ANSI escapes. */
849 replaceRegExp_String(&d->title, ansiPattern_, "", NULL, NULL);
868 } 850 }
869 /* List bullet. */ 851 /* List bullet. */
870 if (type == bullet_GmLineType) { 852 if (type == bullet_GmLineType) {
@@ -964,6 +946,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
964 } 946 }
965 } 947 }
966 iAssert(!isEmpty_Range(&line)); /* must have something at this point */ 948 iAssert(!isEmpty_Range(&line)); /* must have something at this point */
949 size_t numRunsAdded = 0;
967 /* Typeset the paragraph. */ { 950 /* Typeset the paragraph. */ {
968 iRunTypesetter rts; 951 iRunTypesetter rts;
969 init_RunTypesetter_(&rts); 952 init_RunTypesetter_(&rts);
@@ -1036,7 +1019,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
1036 : 1.0f); 1019 : 1.0f);
1037 } 1020 }
1038 } 1021 }
1039 commit_RunTypesetter_(&rts, d); 1022 numRunsAdded = commit_RunTypesetter_(&rts, d);
1040 break; 1023 break;
1041 } 1024 }
1042 /* Try again... */ 1025 /* Try again... */
@@ -1050,6 +1033,11 @@ static void doLayout_GmDocument_(iGmDocument *d) {
1050 deinit_RunTypesetter_(&rts); 1033 deinit_RunTypesetter_(&rts);
1051 } 1034 }
1052 /* Flag the end of line, too. */ 1035 /* Flag the end of line, too. */
1036 if (numRunsAdded == 0) {
1037 pos.y += lineHeight_Text(run.font) * prefs->lineSpacing;
1038 followsBlank = iTrue;
1039 continue;
1040 }
1053 iGmRun *lastRun = back_Array(&d->layout); 1041 iGmRun *lastRun = back_Array(&d->layout);
1054 lastRun->flags |= endOfLine_GmRunFlag; 1042 lastRun->flags |= endOfLine_GmRunFlag;
1055 if (lastRun->linkId && lastRun->flags & startOfLine_GmRunFlag) { 1043 if (lastRun->linkId && lastRun->flags & startOfLine_GmRunFlag) {
@@ -1301,7 +1289,7 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
1301 0x203b, 0x2042, 0x205c, 0x2182, 0x25ed, 0x2600, 0x2601, 0x2604, 0x2605, 0x2606, 1289 0x203b, 0x2042, 0x205c, 0x2182, 0x25ed, 0x2600, 0x2601, 0x2604, 0x2605, 0x2606,
1302 0x265c, 0x265e, 0x2690, 0x2691, 0x2693, 0x2698, 0x2699, 0x26f0, 0x270e, 0x2728, 1290 0x265c, 0x265e, 0x2690, 0x2691, 0x2693, 0x2698, 0x2699, 0x26f0, 0x270e, 0x2728,
1303 0x272a, 0x272f, 0x2731, 0x2738, 0x273a, 0x273e, 0x2740, 0x2742, 0x2744, 0x2748, 1291 0x272a, 0x272f, 0x2731, 0x2738, 0x273a, 0x273e, 0x2740, 0x2742, 0x2744, 0x2748,
1304 0x274a, 0x2751, 0x2756, 0x2766, 0x27bd, 0x27c1, 0x27d0, 0x2b19, 0x1f300, 0x1f303, 1292 0x274a, 0x2318, 0x2756, 0x2766, 0x27bd, 0x27c1, 0x27d0, 0x2b19, 0x1f300, 0x1f303,
1305 0x1f306, 0x1f308, 0x1f30a, 0x1f319, 0x1f31f, 0x1f320, 0x1f340, 0x1f4cd, 0x1f4e1, 0x1f531, 1293 0x1f306, 0x1f308, 0x1f30a, 0x1f319, 0x1f31f, 0x1f320, 0x1f340, 0x1f4cd, 0x1f4e1, 0x1f531,
1306 0x1f533, 0x1f657, 0x1f659, 0x1f665, 0x1f668, 0x1f66b, 0x1f78b, 0x1f796, 0x1f79c, 1294 0x1f533, 0x1f657, 0x1f659, 0x1f665, 0x1f668, 0x1f66b, 0x1f78b, 0x1f796, 0x1f79c,
1307 }; 1295 };
@@ -1951,44 +1939,6 @@ void setUrl_GmDocument(iGmDocument *d, const iString *url) {
1951 } 1939 }
1952} 1940}
1953 1941
1954int replaceRegExp_String(iString *d, const iRegExp *regexp, const char *replacement,
1955 void (*matchHandler)(void *, const iRegExpMatch *),
1956 void *context) {
1957 iRegExpMatch m;
1958 iString result;
1959 int numMatches = 0;
1960 const char *pos = constBegin_String(d);
1961 init_RegExpMatch(&m);
1962 init_String(&result);
1963 while (matchString_RegExp(regexp, d, &m)) {
1964 appendRange_String(&result, (iRangecc){ pos, begin_RegExpMatch(&m) });
1965 /* Replace any capture group back-references. */
1966 for (const char *ch = replacement; *ch; ch++) {
1967 if (*ch == '\\') {
1968 ch++;
1969 if (*ch == '\\') {
1970 appendCStr_String(&result, "\\");
1971 }
1972 else if (*ch >= '0' && *ch <= '9') {
1973 appendRange_String(&result, capturedRange_RegExpMatch(&m, *ch - '0'));
1974 }
1975 }
1976 else {
1977 appendData_Block(&result.chars, ch, 1);
1978 }
1979 }
1980 if (matchHandler) {
1981 matchHandler(context, &m);
1982 }
1983 pos = end_RegExpMatch(&m);
1984 numMatches++;
1985 }
1986 appendRange_String(&result, (iRangecc){ pos, constEnd_String(d) });
1987 set_String(d, &result);
1988 deinit_String(&result);
1989 return numMatches;
1990}
1991
1992iDeclareType(PendingLink) 1942iDeclareType(PendingLink)
1993struct Impl_PendingLink { 1943struct Impl_PendingLink {
1994 iString *url; 1944 iString *url;
diff --git a/src/gmrequest.c b/src/gmrequest.c
index 3d5a4aef..82c232e1 100644
--- a/src/gmrequest.c
+++ b/src/gmrequest.c
@@ -358,6 +358,12 @@ static const iBlock *aboutPageSource_(iRangecc path, iRangecc query) {
358 if (equalCase_Rangecc(path, "version")) { 358 if (equalCase_Rangecc(path, "version")) {
359 return &blobVersion_Resources; 359 return &blobVersion_Resources;
360 } 360 }
361 if (equalCase_Rangecc(path, "version-1.5")) {
362 return &blobVersion_1_5_Resources;
363 }
364 if (equalCase_Rangecc(path, "version-0.13")) {
365 return &blobVersion_0_13_Resources;
366 }
361 if (equalCase_Rangecc(path, "debug")) { 367 if (equalCase_Rangecc(path, "debug")) {
362 return utf8_String(debugInfo_App()); 368 return utf8_String(debugInfo_App());
363 } 369 }
diff --git a/src/gmutil.c b/src/gmutil.c
index 98e4d4d6..e59e6649 100644
--- a/src/gmutil.c
+++ b/src/gmutil.c
@@ -131,6 +131,16 @@ static iRangecc prevPathSeg_(const char *end, const char *start) {
131 return seg; 131 return seg;
132} 132}
133 133
134void stripUrlPort_String(iString *d) {
135 iUrl parts;
136 init_Url(&parts, d);
137 if (!isEmpty_Range(&parts.port)) {
138 /* Always preceded by a colon. */
139 remove_Block(&d->chars, parts.port.start - 1 - constBegin_String(d),
140 size_Range(&parts.port) + 1);
141 }
142}
143
134void stripDefaultUrlPort_String(iString *d) { 144void stripDefaultUrlPort_String(iString *d) {
135 iUrl parts; 145 iUrl parts;
136 init_Url(&parts, d); 146 init_Url(&parts, d);
@@ -248,6 +258,9 @@ iRangecc urlRoot_String(const iString *d) {
248 else { 258 else {
249 iUrl parts; 259 iUrl parts;
250 init_Url(&parts, d); 260 init_Url(&parts, d);
261 if (equalCase_Rangecc(parts.scheme, "about")) {
262 return (iRangecc){ constBegin_String(d), parts.path.start };
263 }
251 rootEnd = parts.path.start; 264 rootEnd = parts.path.start;
252 } 265 }
253 return (iRangecc){ constBegin_String(d), rootEnd }; 266 return (iRangecc){ constBegin_String(d), rootEnd };
@@ -681,6 +694,17 @@ const iString *withSpacesEncoded_String(const iString *d) {
681 return d; 694 return d;
682} 695}
683 696
697const iString *withScheme_String(const iString *d, const char *scheme) {
698 iUrl parts;
699 init_Url(&parts, d);
700 if (!equalCase_Rangecc(parts.scheme, scheme)) {
701 iString *repl = collectNewCStr_String(scheme);
702 appendRange_String(repl, (iRangecc){ parts.scheme.end, constEnd_String(d) });
703 return repl;
704 }
705 return d;
706}
707
684const iString *canonicalUrl_String(const iString *d) { 708const iString *canonicalUrl_String(const iString *d) {
685 /* The "canonical" form, used for internal storage and comparisons, is: 709 /* The "canonical" form, used for internal storage and comparisons, is:
686 - all non-reserved characters decoded (i.e., it's an IRI) 710 - all non-reserved characters decoded (i.e., it's an IRI)
@@ -880,3 +904,41 @@ const iGmError *get_GmError(enum iGmStatusCode code) {
880 iAssert(errors_[0].code == unknownStatusCode_GmStatusCode); 904 iAssert(errors_[0].code == unknownStatusCode_GmStatusCode);
881 return &errors_[0].err; /* unknown */ 905 return &errors_[0].err; /* unknown */
882} 906}
907
908int replaceRegExp_String(iString *d, const iRegExp *regexp, const char *replacement,
909 void (*matchHandler)(void *, const iRegExpMatch *),
910 void *context) {
911 iRegExpMatch m;
912 iString result;
913 int numMatches = 0;
914 const char *pos = constBegin_String(d);
915 init_RegExpMatch(&m);
916 init_String(&result);
917 while (matchString_RegExp(regexp, d, &m)) {
918 appendRange_String(&result, (iRangecc){ pos, begin_RegExpMatch(&m) });
919 /* Replace any capture group back-references. */
920 for (const char *ch = replacement; *ch; ch++) {
921 if (*ch == '\\') {
922 ch++;
923 if (*ch == '\\') {
924 appendCStr_String(&result, "\\");
925 }
926 else if (*ch >= '0' && *ch <= '9') {
927 appendRange_String(&result, capturedRange_RegExpMatch(&m, *ch - '0'));
928 }
929 }
930 else {
931 appendData_Block(&result.chars, ch, 1);
932 }
933 }
934 if (matchHandler) {
935 matchHandler(context, &m);
936 }
937 pos = end_RegExpMatch(&m);
938 numMatches++;
939 }
940 appendRange_String(&result, (iRangecc){ pos, constEnd_String(d) });
941 set_String(d, &result);
942 deinit_String(&result);
943 return numMatches;
944}
diff --git a/src/gmutil.h b/src/gmutil.h
index 15bb7b2e..1594afc4 100644
--- a/src/gmutil.h
+++ b/src/gmutil.h
@@ -127,6 +127,7 @@ iBool isKnownScheme_Rangecc (iRangecc scheme); /* any URI scheme */
127iBool isKnownUrlScheme_Rangecc(iRangecc scheme); /* URL schemes only */ 127iBool isKnownUrlScheme_Rangecc(iRangecc scheme); /* URL schemes only */
128void punyEncodeDomain_Rangecc(iRangecc domain, iString *encoded_out); 128void punyEncodeDomain_Rangecc(iRangecc domain, iString *encoded_out);
129void punyEncodeUrlHost_String(iString *absoluteUrl); 129void punyEncodeUrlHost_String(iString *absoluteUrl);
130void stripUrlPort_String (iString *);
130void stripDefaultUrlPort_String(iString *); 131void stripDefaultUrlPort_String(iString *);
131const iString * urlFragmentStripped_String(const iString *); 132const iString * urlFragmentStripped_String(const iString *);
132const iString * urlQueryStripped_String (const iString *); 133const iString * urlQueryStripped_String (const iString *);
@@ -138,6 +139,7 @@ const char * makeFileUrl_CStr (const char *localFilePath);
138iString * localFilePathFromUrl_String(const iString *); 139iString * localFilePathFromUrl_String(const iString *);
139void urlEncodeSpaces_String (iString *); 140void urlEncodeSpaces_String (iString *);
140const iString * withSpacesEncoded_String(const iString *); 141const iString * withSpacesEncoded_String(const iString *);
142const iString * withScheme_String (const iString *, const char *scheme); /* replace URI scheme */
141const iString * canonicalUrl_String (const iString *); 143const iString * canonicalUrl_String (const iString *);
142 144
143const char * mediaType_Path (const iString *path); 145const char * mediaType_Path (const iString *path);
diff --git a/src/gopher.c b/src/gopher.c
index 008a7743..0e34fe6a 100644
--- a/src/gopher.c
+++ b/src/gopher.c
@@ -299,3 +299,13 @@ iBool processResponse_Gopher(iGopher *d, const iBlock *data) {
299 } 299 }
300 return changed; 300 return changed;
301} 301}
302
303void setUrlItemType_Gopher(iString *url, char itemType) {
304 iUrl parts;
305 init_Url(&parts, url);
306 if (equalCase_Rangecc(parts.scheme, "gopher")) {
307 if (parts.path.start && size_Range(&parts.path) >= 2) {
308 ((char *) parts.path.start)[1] = itemType;
309 }
310 }
311}
diff --git a/src/gopher.h b/src/gopher.h
index 3ad7e374..3cad0c21 100644
--- a/src/gopher.h
+++ b/src/gopher.h
@@ -44,3 +44,5 @@ iDeclareTypeConstruction(Gopher)
44void open_Gopher (iGopher *, const iString *url); 44void open_Gopher (iGopher *, const iString *url);
45iBool processResponse_Gopher (iGopher *, const iBlock *data); 45iBool processResponse_Gopher (iGopher *, const iBlock *data);
46void cancel_Gopher (iGopher *); 46void cancel_Gopher (iGopher *);
47
48void setUrlItemType_Gopher (iString *url, char itemType);
diff --git a/src/ios.m b/src/ios.m
index 82596ffd..68c03827 100644
--- a/src/ios.m
+++ b/src/ios.m
@@ -163,7 +163,8 @@ API_AVAILABLE(ios(13.0))
163 163
164/*----------------------------------------------------------------------------------------------*/ 164/*----------------------------------------------------------------------------------------------*/
165 165
166@interface AppState : NSObject<UIDocumentPickerDelegate, UITextFieldDelegate, UITextViewDelegate> { 166@interface AppState : NSObject<UIDocumentPickerDelegate, UITextFieldDelegate, UITextViewDelegate,
167 UIScrollViewDelegate> {
167 iString *fileBeingSaved; 168 iString *fileBeingSaved;
168 iString *pickFileCommand; 169 iString *pickFileCommand;
169 iSystemTextInput *sysCtrl; 170 iSystemTextInput *sysCtrl;
@@ -173,6 +174,7 @@ API_AVAILABLE(ios(13.0))
173@end 174@end
174 175
175static AppState *appState_; 176static AppState *appState_;
177static UIScrollView *statusBarTapper_; /* dummy scroll view just for getting notified of taps */
176 178
177@implementation AppState 179@implementation AppState
178 180
@@ -310,8 +312,15 @@ replacementString:(NSString *)string {
310 notifyChange_SystemTextInput_(sysCtrl); 312 notifyChange_SystemTextInput_(sysCtrl);
311} 313}
312 314
315- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
316 postCommand_App("scroll.top smooth:1");
317 return NO;
318}
319
313@end 320@end
314 321
322/*----------------------------------------------------------------------------------------------*/
323
315static void enableMouse_(iBool yes) { 324static void enableMouse_(iBool yes) {
316 SDL_EventState(SDL_MOUSEBUTTONDOWN, yes); 325 SDL_EventState(SDL_MOUSEBUTTONDOWN, yes);
317 SDL_EventState(SDL_MOUSEMOTION, yes); 326 SDL_EventState(SDL_MOUSEMOTION, yes);
@@ -426,6 +435,19 @@ void setupWindow_iOS(iWindow *window) {
426 UIViewController *ctl = viewController_(window); 435 UIViewController *ctl = viewController_(window);
427 isSystemDarkMode_ = isDarkMode_(window); 436 isSystemDarkMode_ = isDarkMode_(window);
428 postCommandf_App("~os.theme.changed dark:%d contrast:1", isSystemDarkMode_ ? 1 : 0); 437 postCommandf_App("~os.theme.changed dark:%d contrast:1", isSystemDarkMode_ ? 1 : 0);
438 /* A hack to get notified on status bar taps. We create a thin dummy UIScrollView
439 that occupies the top of the screen where the status bar is located. */ {
440 CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
441 statusBarTapper_ = [[UIScrollView alloc] initWithFrame:statusBarFrame];
442// [statusBarTapper_ setBackgroundColor:[UIColor greenColor]]; /* to see where it is */
443 [statusBarTapper_ setShowsVerticalScrollIndicator:NO];
444 [statusBarTapper_ setShowsHorizontalScrollIndicator:NO];
445 [statusBarTapper_ setContentSize:(CGSize){ 10000, 10000 }];
446 [statusBarTapper_ setContentOffset:(CGPoint){ 0, 1000 }];
447 [statusBarTapper_ setScrollsToTop:YES];
448 [statusBarTapper_ setDelegate:appState_];
449 [ctl.view addSubview:statusBarTapper_];
450 }
429} 451}
430 452
431void playHapticEffect_iOS(enum iHapticEffect effect) { 453void playHapticEffect_iOS(enum iHapticEffect effect) {
@@ -443,6 +465,14 @@ void playHapticEffect_iOS(enum iHapticEffect effect) {
443} 465}
444 466
445iBool processEvent_iOS(const SDL_Event *ev) { 467iBool processEvent_iOS(const SDL_Event *ev) {
468 if (ev->type == SDL_DISPLAYEVENT) {
469 if (deviceType_App() == phone_AppDeviceType) {
470 [statusBarTapper_ setHidden:(ev->display.data1 == SDL_ORIENTATION_LANDSCAPE ||
471 ev->display.data1 == SDL_ORIENTATION_LANDSCAPE_FLIPPED)];
472 }
473 [statusBarTapper_ setFrame:[UIApplication sharedApplication].statusBarFrame];
474 return iFalse;
475 }
446 if (ev->type == SDL_WINDOWEVENT) { 476 if (ev->type == SDL_WINDOWEVENT) {
447 if (ev->window.event == SDL_WINDOWEVENT_RESTORED) { 477 if (ev->window.event == SDL_WINDOWEVENT_RESTORED) {
448 const iBool isDark = isDarkMode_(get_Window()); 478 const iBool isDark = isDarkMode_(get_Window());
diff --git a/src/macos.m b/src/macos.m
index 4ad267c1..191842f6 100644
--- a/src/macos.m
+++ b/src/macos.m
@@ -72,10 +72,10 @@ static NSString *currentSystemAppearance_(void) {
72} 72}
73 73
74iBool shouldDefaultToMetalRenderer_MacOS(void) { 74iBool shouldDefaultToMetalRenderer_MacOS(void) {
75 /* TODO: Test if SDL 2.0.16 works better (no stutters with Metal?). */
76 return iFalse; /*
77 const iInt2 ver = macVer_(); 75 const iInt2 ver = macVer_();
78 return ver.x > 10 || ver.y > 13;*/ 76 SDL_DisplayMode dispMode;
77 SDL_GetDesktopDisplayMode(0, &dispMode);
78 return dispMode.refresh_rate > 60 && (ver.x > 10 || ver.y > 13);
79} 79}
80 80
81static void ignoreImmediateKeyDownEvents_(void) { 81static void ignoreImmediateKeyDownEvents_(void) {
@@ -436,6 +436,10 @@ static iBool processScrollWheelEvent_(NSEvent *event) {
436 const iBool isInertia = (event.momentumPhase & (NSEventPhaseBegan | NSEventPhaseChanged)) != 0; 436 const iBool isInertia = (event.momentumPhase & (NSEventPhaseBegan | NSEventPhaseChanged)) != 0;
437 const iBool isEnded = event.scrollingDeltaX == 0.0f && event.scrollingDeltaY == 0.0f && !isInertia; 437 const iBool isEnded = event.scrollingDeltaX == 0.0f && event.scrollingDeltaY == 0.0f && !isInertia;
438 const iWindow *win = &get_MainWindow()->base; 438 const iWindow *win = &get_MainWindow()->base;
439 if (event.window != nsWindow_(win->win)) {
440 /* Not the main window. */
441 return iFalse;
442 }
439 if (isPerPixel) { 443 if (isPerPixel) {
440 /* On macOS 12.1, stopping ongoing inertia scroll with a tap seems to sometimes produce 444 /* On macOS 12.1, stopping ongoing inertia scroll with a tap seems to sometimes produce
441 spurious large scroll events. */ 445 spurious large scroll events. */
@@ -525,7 +529,6 @@ static iBool processScrollWheelEvent_(NSEvent *event) {
525 ev.wheel.y = iSign(ev.wheel.y); 529 ev.wheel.y = iSign(ev.wheel.y);
526 } 530 }
527#endif 531#endif
528
529 return iTrue; 532 return iTrue;
530} 533}
531 534
@@ -734,7 +737,7 @@ enum iColorId removeColorEscapes_String(iString *d) {
734static NSString *cleanString_(const iString *ansiEscapedText) { 737static NSString *cleanString_(const iString *ansiEscapedText) {
735 iString mod; 738 iString mod;
736 initCopy_String(&mod, ansiEscapedText); 739 initCopy_String(&mod, ansiEscapedText);
737 iRegExp *ansi = makeAnsiEscapePattern_Text(); 740 iRegExp *ansi = makeAnsiEscapePattern_Text(iTrue /* with ESC */);
738 replaceRegExp_String(&mod, ansi, "", NULL, NULL); 741 replaceRegExp_String(&mod, ansi, "", NULL, NULL);
739 iRelease(ansi); 742 iRelease(ansi);
740 NSString *clean = [NSString stringWithUTF8String:cstr_String(&mod)]; 743 NSString *clean = [NSString stringWithUTF8String:cstr_String(&mod)];
diff --git a/src/prefs.c b/src/prefs.c
index 6164ca25..13a1dab7 100644
--- a/src/prefs.c
+++ b/src/prefs.c
@@ -25,8 +25,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
25#include <the_Foundation/fileinfo.h> 25#include <the_Foundation/fileinfo.h>
26#include <assert.h> 26#include <assert.h>
27 27
28static_assert(offsetof(iPrefs, plainTextWrap) == offsetof(iPrefs, bools[plainTextWrap_PrefsBool]), 28_Static_assert(offsetof(iPrefs, plainTextWrap) == offsetof(iPrefs, bools[plainTextWrap_PrefsBool]),
29 "memory layout mismatch (needs struct packing?)"); 29 "memory layout mismatch (needs struct packing?)");
30 30
31void init_Prefs(iPrefs *d) { 31void init_Prefs(iPrefs *d) {
32 iForIndices(i, d->strings) { 32 iForIndices(i, d->strings) {
diff --git a/src/resources.c b/src/resources.c
index ae85463a..fa7485ce 100644
--- a/src/resources.c
+++ b/src/resources.c
@@ -33,6 +33,8 @@ iBlock blobAbout_Resources;
33iBlock blobHelp_Resources; 33iBlock blobHelp_Resources;
34iBlock blobLagrange_Resources; 34iBlock blobLagrange_Resources;
35iBlock blobLicense_Resources; 35iBlock blobLicense_Resources;
36iBlock blobVersion_0_13_Resources;
37iBlock blobVersion_1_5_Resources;
36iBlock blobVersion_Resources; 38iBlock blobVersion_Resources;
37iBlock blobArghelp_Resources; 39iBlock blobArghelp_Resources;
38iBlock blobCs_Resources; 40iBlock blobCs_Resources;
@@ -76,6 +78,8 @@ static struct {
76 { &blobVersion_Resources, "about/android-version.gmi" }, 78 { &blobVersion_Resources, "about/android-version.gmi" },
77#else 79#else
78 { &blobHelp_Resources, "about/help.gmi" }, 80 { &blobHelp_Resources, "about/help.gmi" },
81 { &blobVersion_0_13_Resources, "about/version-0.13.gmi" },
82 { &blobVersion_1_5_Resources, "about/version-1.5.gmi" },
79 { &blobVersion_Resources, "about/version.gmi" }, 83 { &blobVersion_Resources, "about/version.gmi" },
80#endif 84#endif
81 { &blobArghelp_Resources, "arg-help.txt" }, 85 { &blobArghelp_Resources, "arg-help.txt" },
diff --git a/src/resources.h b/src/resources.h
index 3852cc3e..e440fda3 100644
--- a/src/resources.h
+++ b/src/resources.h
@@ -35,6 +35,8 @@ extern iBlock blobAbout_Resources;
35extern iBlock blobHelp_Resources; 35extern iBlock blobHelp_Resources;
36extern iBlock blobLagrange_Resources; 36extern iBlock blobLagrange_Resources;
37extern iBlock blobLicense_Resources; 37extern iBlock blobLicense_Resources;
38extern iBlock blobVersion_0_13_Resources;
39extern iBlock blobVersion_1_5_Resources;
38extern iBlock blobVersion_Resources; 40extern iBlock blobVersion_Resources;
39extern iBlock blobArghelp_Resources; 41extern iBlock blobArghelp_Resources;
40extern iBlock blobCs_Resources; 42extern iBlock blobCs_Resources;
diff --git a/src/ui/color.c b/src/ui/color.c
index 3c2f0339..824342ae 100644
--- a/src/ui/color.c
+++ b/src/ui/color.c
@@ -868,23 +868,16 @@ void ansiColors_Color(iRangecc escapeSequence, int fgDefault, int bgDefault,
868 case 97: 868 case 97:
869 fg = ansi8BitColors_[8 + arg - 90]; 869 fg = ansi8BitColors_[8 + arg - 90];
870 break; 870 break;
871 } 871 case 100:
872 } 872 case 101:
873 /* Ensure legibility if only the foreground color is set. */ 873 case 102:
874 if (fg.a) { 874 case 103:
875 const iHSLColor themeBg = get_HSLColor(tmBackground_ColorId); 875 case 104:
876 const float bgLuminance = luma_Color(get_Color(tmBackground_ColorId)); 876 case 105:
877 if (bgLuminance > 0.4f) { 877 case 106:
878 float dim = (bgLuminance - 0.4f); 878 case 107:
879 fg.r *= 0.5f * dim; 879 bg = ansi8BitColors_[8 + arg - 100];
880 fg.g *= 0.5f * dim; 880 break;
881 fg.b *= 0.5f * dim;
882 }
883 if (themeBg.sat > 0.15f && themeBg.lum >= 0.5f) {
884 iHSLColor fgHsl = hsl_Color(fg);
885 fgHsl.hue = themeBg.hue;
886 fgHsl.lum = themeBg.lum * 0.5f;
887 fg = rgb_HSLColor(fgHsl);
888 } 881 }
889 } 882 }
890 if (fg.a && fg_out) { 883 if (fg.a && fg_out) {
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 03119ca2..97ecb4ba 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -36,6 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
36#include "gmdocument.h" 36#include "gmdocument.h"
37#include "gmrequest.h" 37#include "gmrequest.h"
38#include "gmutil.h" 38#include "gmutil.h"
39#include "gopher.h"
39#include "history.h" 40#include "history.h"
40#include "indicatorwidget.h" 41#include "indicatorwidget.h"
41#include "inputwidget.h" 42#include "inputwidget.h"
@@ -921,6 +922,7 @@ static void documentRunsInvalidated_DocumentView_(iDocumentView *d) {
921 d->hoverPre = NULL; 922 d->hoverPre = NULL;
922 d->hoverAltPre = NULL; 923 d->hoverAltPre = NULL;
923 d->hoverLink = NULL; 924 d->hoverLink = NULL;
925 clear_PtrArray(&d->visibleMedia);
924 iZap(d->visibleRuns); 926 iZap(d->visibleRuns);
925 iZap(d->renderRuns); 927 iZap(d->renderRuns);
926} 928}
@@ -2900,10 +2902,14 @@ static void addBannerWarnings_DocumentWidget_(iDocumentWidget *d) {
2900 add_Banner(d->banner, warning_BannerType, none_GmStatusCode, title, str); 2902 add_Banner(d->banner, warning_BannerType, none_GmStatusCode, title, str);
2901 } 2903 }
2902 /* Warnings related to page contents. */ 2904 /* Warnings related to page contents. */
2903 const int dismissed = 2905 int dismissed =
2904 value_SiteSpec(collectNewRange_String(urlRoot_String(d->mod.url)), 2906 value_SiteSpec(collectNewRange_String(urlRoot_String(d->mod.url)),
2905 dismissWarnings_SiteSpecKey) | 2907 dismissWarnings_SiteSpecKey) |
2906 (!prefs_App()->warnAboutMissingGlyphs ? missingGlyphs_GmDocumentWarning : 0); 2908 (!prefs_App()->warnAboutMissingGlyphs ? missingGlyphs_GmDocumentWarning : 0);
2909 /* File pages don't allow dismissing warnings, so skip it. */
2910 if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) {
2911 dismissed |= ansiEscapes_GmDocumentWarning;
2912 }
2907 const int warnings = warnings_GmDocument(d->view.doc) & ~dismissed; 2913 const int warnings = warnings_GmDocument(d->view.doc) & ~dismissed;
2908 if (warnings & missingGlyphs_GmDocumentWarning) { 2914 if (warnings & missingGlyphs_GmDocumentWarning) {
2909 add_Banner(d->banner, warning_BannerType, missingGlyphs_GmStatusCode, NULL, NULL); 2915 add_Banner(d->banner, warning_BannerType, missingGlyphs_GmStatusCode, NULL, NULL);
@@ -4069,14 +4075,12 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
4069 return iTrue; 4075 return iTrue;
4070 } 4076 }
4071 else if (equal_Command(cmd, "valueinput.cancelled") && 4077 else if (equal_Command(cmd, "valueinput.cancelled") &&
4072 equal_Rangecc(range_Command(cmd, "id"), "document.input.submit") && document_App() == d) { 4078 equal_Rangecc(range_Command(cmd, "id"), "!document.input.submit") && document_App() == d) {
4073 postCommand_Root(get_Root(), "navigate.back"); 4079 postCommand_Root(get_Root(), "navigate.back");
4074 return iTrue; 4080 return iTrue;
4075 } 4081 }
4076 else if (equalWidget_Command(cmd, w, "document.request.updated") && 4082 else if (equalWidget_Command(cmd, w, "document.request.updated") &&
4077 id_GmRequest(d->request) == argU32Label_Command(cmd, "reqid")) { 4083 id_GmRequest(d->request) == argU32Label_Command(cmd, "reqid")) {
4078// set_Block(&d->sourceContent, &lockResponse_GmRequest(d->request)->body);
4079// unlockResponse_GmRequest(d->request);
4080 if (document_App() == d) { 4084 if (document_App() == d) {
4081 updateFetchProgress_DocumentWidget_(d); 4085 updateFetchProgress_DocumentWidget_(d);
4082 } 4086 }
@@ -4302,6 +4306,9 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
4302 else if (equal_Command(cmd, "navigate.parent") && document_App() == d) { 4306 else if (equal_Command(cmd, "navigate.parent") && document_App() == d) {
4303 iUrl parts; 4307 iUrl parts;
4304 init_Url(&parts, d->mod.url); 4308 init_Url(&parts, d->mod.url);
4309 if (endsWith_Rangecc(parts.path, "/index.gmi")) {
4310 parts.path.end -= 9; /* This is the default index page. */
4311 }
4305 /* Remove the last path segment. */ 4312 /* Remove the last path segment. */
4306 if (size_Range(&parts.path) > 1) { 4313 if (size_Range(&parts.path) > 1) {
4307 if (parts.path.end[-1] == '/') { 4314 if (parts.path.end[-1] == '/') {
@@ -4311,14 +4318,42 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
4311 if (parts.path.end[-1] == '/') break; 4318 if (parts.path.end[-1] == '/') break;
4312 parts.path.end--; 4319 parts.path.end--;
4313 } 4320 }
4314 postCommandf_Root(w->root, 4321 iString *parentUrl = collectNewRange_String((iRangecc){ constBegin_String(d->mod.url),
4315 "open url:%s", 4322 parts.path.end });
4316 cstr_Rangecc((iRangecc){ constBegin_String(d->mod.url), parts.path.end })); 4323 /* Always go to a gophermap. */
4324 setUrlItemType_Gopher(parentUrl, '1');
4325 /* Hierarchical navigation doesn't make sense with Titan. */
4326 if (startsWith_String(parentUrl, "titan://")) {
4327 /* We have no way of knowing if the corresponding URL is valid for Gemini,
4328 but let's try anyway. */
4329 set_String(parentUrl, withScheme_String(parentUrl, "gemini"));
4330 stripUrlPort_String(parentUrl);
4331 }
4332 if (!cmpCase_String(parentUrl, "about:")) {
4333 setCStr_String(parentUrl, "about:about");
4334 }
4335 postCommandf_Root(w->root, "open url:%s", cstr_String(parentUrl));
4317 } 4336 }
4318 return iTrue; 4337 return iTrue;
4319 } 4338 }
4320 else if (equal_Command(cmd, "navigate.root") && document_App() == d) { 4339 else if (equal_Command(cmd, "navigate.root") && document_App() == d) {
4321 postCommandf_Root(w->root, "open url:%s/", cstr_Rangecc(urlRoot_String(d->mod.url))); 4340 iString *rootUrl = collectNewRange_String(urlRoot_String(d->mod.url));
4341 /* Always go to a gophermap. */
4342 setUrlItemType_Gopher(rootUrl, '1');
4343 /* Hierarchical navigation doesn't make sense with Titan. */
4344 if (startsWith_String(rootUrl, "titan://")) {
4345 /* We have no way of knowing if the corresponding URL is valid for Gemini,
4346 but let's try anyway. */
4347 set_String(rootUrl, withScheme_String(rootUrl, "gemini"));
4348 stripUrlPort_String(rootUrl);
4349 }
4350 if (!cmpCase_String(rootUrl, "about:")) {
4351 setCStr_String(rootUrl, "about:about");
4352 }
4353 else {
4354 appendCStr_String(rootUrl, "/");
4355 }
4356 postCommandf_Root(w->root, "open url:%s", cstr_String(rootUrl));
4322 return iTrue; 4357 return iTrue;
4323 } 4358 }
4324 else if (equalWidget_Command(cmd, w, "scroll.moved")) { 4359 else if (equalWidget_Command(cmd, w, "scroll.moved")) {
@@ -4341,6 +4376,12 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
4341 return iTrue; 4376 return iTrue;
4342 } 4377 }
4343 else if (equal_Command(cmd, "scroll.top") && document_App() == d) { 4378 else if (equal_Command(cmd, "scroll.top") && document_App() == d) {
4379 if (argLabel_Command(cmd, "smooth")) {
4380 stopWidgetMomentum_Touch(w);
4381 smoothScroll_DocumentView_(&d->view, -pos_SmoothScroll(&d->view.scrollY), 500);
4382 d->view.scrollY.flags |= muchSofter_AnimFlag;
4383 return iTrue;
4384 }
4344 init_Anim(&d->view.scrollY.pos, 0); 4385 init_Anim(&d->view.scrollY.pos, 0);
4345 invalidate_VisBuf(d->view.visBuf); 4386 invalidate_VisBuf(d->view.visBuf);
4346 clampScroll_DocumentView_(&d->view); 4387 clampScroll_DocumentView_(&d->view);
diff --git a/src/ui/indicatorwidget.c b/src/ui/indicatorwidget.c
index bc0bd0fa..e16550ff 100644
--- a/src/ui/indicatorwidget.c
+++ b/src/ui/indicatorwidget.c
@@ -28,32 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
28 28
29#include <SDL_timer.h> 29#include <SDL_timer.h>
30 30
31static int timerId_; /* common timer for all indicators */ 31struct Impl_IndicatorWidget {
32static int animCount_; /* number of animating indicators */
33
34static uint32_t postRefresh_(uint32_t interval, void *context) {
35 iUnused(context);
36 postRefresh_App();
37 return interval;
38}
39
40static void startTimer_(void) {
41 animCount_++;
42 if (!timerId_) {
43 timerId_ = SDL_AddTimer(1000 / 60, postRefresh_, NULL);
44 }
45}
46
47static void stopTimer_(void) {
48 iAssert(animCount_ > 0);
49 if (--animCount_ == 0) {
50 iAssert(timerId_);
51 SDL_RemoveTimer(timerId_);
52 timerId_ = 0;
53 }
54}
55
56struct Impl_IndicatorWidget{
57 iWidget widget; 32 iWidget widget;
58 iAnim pos; 33 iAnim pos;
59}; 34};
@@ -64,6 +39,14 @@ iLocalDef iBool isActive_IndicatorWidget_(const iIndicatorWidget *d) {
64 return isSelected_Widget(d); 39 return isSelected_Widget(d);
65} 40}
66 41
42static void animate_IndicatorWidget_(void *ptr) {
43 iIndicatorWidget *d = ptr;
44 if (!isFinished_Anim(&d->pos)) {
45 addTickerRoot_App(animate_IndicatorWidget_, d->widget.root, ptr);
46 }
47 postRefresh_App();
48}
49
67static void setActive_IndicatorWidget_(iIndicatorWidget *d, iBool set) { 50static void setActive_IndicatorWidget_(iIndicatorWidget *d, iBool set) {
68 setFlags_Widget(as_Widget(d), selected_WidgetFlag, set); 51 setFlags_Widget(as_Widget(d), selected_WidgetFlag, set);
69} 52}
@@ -75,22 +58,8 @@ void init_IndicatorWidget(iIndicatorWidget *d) {
75 setFlags_Widget(w, unhittable_WidgetFlag, iTrue); 58 setFlags_Widget(w, unhittable_WidgetFlag, iTrue);
76} 59}
77 60
78static void startTimer_IndicatorWidget_(iIndicatorWidget *d) {
79 if (!isActive_IndicatorWidget_(d)) {
80 startTimer_();
81 setActive_IndicatorWidget_(d, iTrue);
82 }
83}
84
85static void stopTimer_IndicatorWidget_(iIndicatorWidget *d) {
86 if (isActive_IndicatorWidget_(d)) {
87 stopTimer_();
88 setActive_IndicatorWidget_(d, iFalse);
89 }
90}
91
92void deinit_IndicatorWidget(iIndicatorWidget *d) { 61void deinit_IndicatorWidget(iIndicatorWidget *d) {
93 stopTimer_IndicatorWidget_(d); 62 removeTicker_App(animate_IndicatorWidget_, d);
94} 63}
95 64
96static iBool isCompleted_IndicatorWidget_(const iIndicatorWidget *d) { 65static iBool isCompleted_IndicatorWidget_(const iIndicatorWidget *d) {
@@ -116,12 +85,7 @@ void draw_IndicatorWidget_(const iIndicatorWidget *d) {
116 85
117iBool processEvent_IndicatorWidget_(iIndicatorWidget *d, const SDL_Event *ev) { 86iBool processEvent_IndicatorWidget_(iIndicatorWidget *d, const SDL_Event *ev) {
118 iWidget *w = &d->widget; 87 iWidget *w = &d->widget;
119 if (ev->type == SDL_USEREVENT && ev->user.code == refresh_UserEventCode) { 88 if (isCommand_SDLEvent(ev)) {
120 if (isFinished_Anim(&d->pos)) {
121 stopTimer_IndicatorWidget_(d);
122 }
123 }
124 else if (isCommand_SDLEvent(ev)) {
125 const char *cmd = command_UserEvent(ev); 89 const char *cmd = command_UserEvent(ev);
126 if (startsWith_CStr(cmd, "document.request.")) { 90 if (startsWith_CStr(cmd, "document.request.")) {
127 if (pointerLabel_Command(cmd, "doc") == parent_Widget(w)) { 91 if (pointerLabel_Command(cmd, "doc") == parent_Widget(w)) {
@@ -130,23 +94,23 @@ iBool processEvent_IndicatorWidget_(iIndicatorWidget *d, const SDL_Event *ev) {
130 setValue_Anim(&d->pos, 0, 0); 94 setValue_Anim(&d->pos, 0, 0);
131 setValue_Anim(&d->pos, 0.75f, 4000); 95 setValue_Anim(&d->pos, 0.75f, 4000);
132 setFlags_Anim(&d->pos, easeOut_AnimFlag, iTrue); 96 setFlags_Anim(&d->pos, easeOut_AnimFlag, iTrue);
133 startTimer_IndicatorWidget_(d); 97 animate_IndicatorWidget_(d);
134 } 98 }
135 else if (equal_Command(cmd, "finished")) { 99 else if (equal_Command(cmd, "finished")) {
136 if (value_Anim(&d->pos) > 0.01f) { 100 if (value_Anim(&d->pos) > 0.01f) {
137 setValue_Anim(&d->pos, 1.0f, 250); 101 setValue_Anim(&d->pos, 1.0f, 250);
138 setFlags_Anim(&d->pos, easeOut_AnimFlag, iFalse); 102 setFlags_Anim(&d->pos, easeOut_AnimFlag, iFalse);
139 startTimer_IndicatorWidget_(d); 103 animate_IndicatorWidget_(d);
140 } 104 }
141 else { 105 else {
142 setValue_Anim(&d->pos, 0, 0); 106 setValue_Anim(&d->pos, 0, 0);
143 stopTimer_IndicatorWidget_(d); 107 animate_IndicatorWidget_(d);
144 refresh_Widget(d); 108 refresh_Widget(d);
145 } 109 }
146 } 110 }
147 else if (equal_Command(cmd, "cancelled")) { 111 else if (equal_Command(cmd, "cancelled")) {
148 setValue_Anim(&d->pos, 0, 0); 112 setValue_Anim(&d->pos, 0, 0);
149 stopTimer_IndicatorWidget_(d); 113 animate_IndicatorWidget_(d);
150 refresh_Widget(d); 114 refresh_Widget(d);
151 } 115 }
152 } 116 }
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 9261da0c..aa55f3f0 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -742,15 +742,17 @@ static void startOrStopCursorTimer_InputWidget_(iInputWidget *d, int doStart) {
742#else /* using a system-provided text control */ 742#else /* using a system-provided text control */
743 743
744static void updateAllLinesAndResizeHeight_InputWidget_(iInputWidget *d) { 744static void updateAllLinesAndResizeHeight_InputWidget_(iInputWidget *d) {
745 /* Rewrap the buffered text and resize accordingly. */ 745 if (width_Widget(d) >= minWidth_InputWidget_) {
746 iWrapText wt = wrap_InputWidget_(d, 0); 746 /* Rewrap the buffered text and resize accordingly. */
747 /* TODO: Set max lines limit for WrapText. */ 747 iWrapText wt = wrap_InputWidget_(d, 0);
748 const int height = measure_WrapText(&wt, d->font).bounds.size.y; 748 /* TODO: Set max lines limit for WrapText. */
749 /* We use this to store the number wrapped lines for determining widget height. */ 749 const int height = measure_WrapText(&wt, d->font).bounds.size.y;
750 d->visWrapLines.start = 0; 750 /* We use this to store the number wrapped lines for determining widget height. */
751 d->visWrapLines.end = iMax(d->minWrapLines, 751 d->visWrapLines.start = 0;
752 iMin(d->maxWrapLines, height / lineHeight_Text(d->font))); 752 d->visWrapLines.end = iMax(d->minWrapLines,
753 updateMetrics_InputWidget_(d); 753 iMin(d->maxWrapLines, height / lineHeight_Text(d->font)));
754 updateMetrics_InputWidget_(d);
755 }
754} 756}
755 757
756#endif 758#endif
diff --git a/src/ui/mobile.c b/src/ui/mobile.c
index cf955423..aefeebc6 100644
--- a/src/ui/mobile.c
+++ b/src/ui/mobile.c
@@ -43,7 +43,7 @@ const iToolbarActionSpec toolbarActions_Mobile[max_ToolbarAction] = {
43 { home_Icon, "${menu.home}", "navigate.home" }, 43 { home_Icon, "${menu.home}", "navigate.home" },
44 { upArrow_Icon, "${menu.parent}", "navigate.parent" }, 44 { upArrow_Icon, "${menu.parent}", "navigate.parent" },
45 { reload_Icon, "${menu.reload}", "navigate.reload" }, 45 { reload_Icon, "${menu.reload}", "navigate.reload" },
46 { openTab_Icon, "${menu.newtab}", "tabs.new" }, 46 { add_Icon, "${menu.newtab}", "tabs.new" },
47 { close_Icon, "${menu.closetab}", "tabs.close" }, 47 { close_Icon, "${menu.closetab}", "tabs.close" },
48 { bookmark_Icon, "${menu.page.bookmark}", "bookmark.add" }, 48 { bookmark_Icon, "${menu.page.bookmark}", "bookmark.add" },
49 { globe_Icon, "${menu.page.translate}", "document.translate" }, 49 { globe_Icon, "${menu.page.translate}", "document.translate" },
@@ -940,7 +940,7 @@ void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) {
940 } 940 }
941 const int maxOffset = isHorizPanel ? width_Widget(sheet) 941 const int maxOffset = isHorizPanel ? width_Widget(sheet)
942 : isPortraitPhone_App() ? height_Widget(sheet) 942 : isPortraitPhone_App() ? height_Widget(sheet)
943 : (12 * gap_UI); 943 : (6 * gap_UI);
944 if (isIncoming) { 944 if (isIncoming) {
945 setVisualOffset_Widget(sheet, maxOffset, 0, 0); 945 setVisualOffset_Widget(sheet, maxOffset, 0, 0);
946 setVisualOffset_Widget(sheet, 0, 330, easeOut_AnimFlag | softer_AnimFlag); 946 setVisualOffset_Widget(sheet, 0, 330, easeOut_AnimFlag | softer_AnimFlag);
diff --git a/src/ui/paint.c b/src/ui/paint.c
index 5e66f521..67b509fb 100644
--- a/src/ui/paint.c
+++ b/src/ui/paint.c
@@ -71,9 +71,6 @@ void endTarget_Paint(iPaint *d) {
71 71
72void setClip_Paint(iPaint *d, iRect rect) { 72void setClip_Paint(iPaint *d, iRect rect) {
73 addv_I2(&rect.pos, origin_Paint); 73 addv_I2(&rect.pos, origin_Paint);
74 if (isEmpty_Rect(rect)) {
75 rect = init_Rect(0, 0, 1, 1);
76 }
77 iRect targetRect = zero_Rect(); 74 iRect targetRect = zero_Rect();
78 SDL_Texture *target = SDL_GetRenderTarget(renderer_Paint_(d)); 75 SDL_Texture *target = SDL_GetRenderTarget(renderer_Paint_(d));
79 if (target) { 76 if (target) {
@@ -83,6 +80,9 @@ void setClip_Paint(iPaint *d, iRect rect) {
83 else { 80 else {
84 rect = intersect_Rect(rect, rect_Root(get_Root())); 81 rect = intersect_Rect(rect, rect_Root(get_Root()));
85 } 82 }
83 if (isEmpty_Rect(rect)) {
84 rect = init_Rect(0, 0, 1, 1);
85 }
86 SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect); 86 SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect);
87} 87}
88 88
diff --git a/src/ui/root.c b/src/ui/root.c
index 5c4296cf..6e187313 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -703,6 +703,20 @@ void updateToolbarColors_Root(iRoot *d) {
703#endif 703#endif
704} 704}
705 705
706void showOrHideNewTabButton_Root(iRoot *d) {
707 iWidget *tabs = findChild_Widget(d->widget, "doctabs");
708 iWidget *newTabButton = findChild_Widget(tabs, "newtab");
709 iBool hide = iFalse;
710 iForIndices(i, prefs_App()->navbarActions) {
711 if (prefs_App()->navbarActions[i] == newTab_ToolbarAction) {
712 hide = iTrue;
713 break;
714 }
715 }
716 setFlags_Widget(newTabButton, hidden_WidgetFlag, hide);
717 arrange_Widget(findChild_Widget(tabs, "tabs.buttons"));
718}
719
706void notifyVisualOffsetChange_Root(iRoot *d) { 720void notifyVisualOffsetChange_Root(iRoot *d) {
707 if (d && (d->didAnimateVisualOffsets || d->didChangeArrangement)) { 721 if (d && (d->didAnimateVisualOffsets || d->didChangeArrangement)) {
708 iNotifyAudience(d, visualOffsetsChanged, RootVisualOffsetsChanged); 722 iNotifyAudience(d, visualOffsetsChanged, RootVisualOffsetsChanged);
@@ -848,6 +862,7 @@ static void updateNavBarActions_(iWidget *navBar) {
848 } 862 }
849 iEndCollect(); 863 iEndCollect();
850 } 864 }
865 showOrHideNewTabButton_Root(navBar->root);
851} 866}
852 867
853static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { 868static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
@@ -1312,8 +1327,7 @@ void createUserInterface_Root(iRoot *d) {
1312#if defined (iPlatformApple) 1327#if defined (iPlatformApple)
1313 addUnsplitButton_(navBar); 1328 addUnsplitButton_(navBar);
1314#endif 1329#endif
1315 iWidget *navBack; 1330 setId_Widget(addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget(backArrow_Icon, 0, 0, "navigate.back")), collapse_WidgetFlag), "navbar.action1");
1316 setId_Widget(navBack = addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget(backArrow_Icon, 0, 0, "navigate.back")), collapse_WidgetFlag), "navbar.action1");
1317 setId_Widget(addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget(forwardArrow_Icon, 0, 0, "navigate.forward")), collapse_WidgetFlag), "navbar.action2"); 1331 setId_Widget(addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget(forwardArrow_Icon, 0, 0, "navigate.forward")), collapse_WidgetFlag), "navbar.action2");
1318 /* Button for toggling the left sidebar. */ 1332 /* Button for toggling the left sidebar. */
1319 setId_Widget(addChildFlags_Widget( 1333 setId_Widget(addChildFlags_Widget(
@@ -1497,6 +1511,16 @@ void createUserInterface_Root(iRoot *d) {
1497 /* On PC platforms, the close buttons are generally on the top right. */ 1511 /* On PC platforms, the close buttons are generally on the top right. */
1498 addUnsplitButton_(navBar); 1512 addUnsplitButton_(navBar);
1499#endif 1513#endif
1514 if (deviceType_App() == tablet_AppDeviceType) {
1515 /* Ensure that all navbar buttons match the height of the input field.
1516 This is required because touch input fields are given extra padding,
1517 making them taller than buttons by default. */
1518 iForEach(ObjectList, i, children_Widget(navBar)) {
1519 if (isInstance_Object(i.object, &Class_LabelWidget)) {
1520 as_Widget(i.object)->sizeRef = as_Widget(url);
1521 }
1522 }
1523 }
1500 } 1524 }
1501 /* Tab bar. */ { 1525 /* Tab bar. */ {
1502 iWidget *mainStack = new_Widget(); 1526 iWidget *mainStack = new_Widget();
@@ -1517,7 +1541,7 @@ void createUserInterface_Root(iRoot *d) {
1517 } 1541 }
1518 setId_Widget( 1542 setId_Widget(
1519 addChildFlags_Widget(buttons, iClob(newIcon_LabelWidget(add_Icon, 0, 0, "tabs.new")), 1543 addChildFlags_Widget(buttons, iClob(newIcon_LabelWidget(add_Icon, 0, 0, "tabs.new")),
1520 moveToParentRightEdge_WidgetFlag), 1544 moveToParentRightEdge_WidgetFlag | collapse_WidgetFlag),
1521 "newtab"); 1545 "newtab");
1522 } 1546 }
1523 /* Sidebars. */ { 1547 /* Sidebars. */ {
@@ -1528,6 +1552,7 @@ void createUserInterface_Root(iRoot *d) {
1528 addChildPos_Widget(content, iClob(sidebar1), front_WidgetAddPos); 1552 addChildPos_Widget(content, iClob(sidebar1), front_WidgetAddPos);
1529 iSidebarWidget *sidebar2 = new_SidebarWidget(right_SidebarSide); 1553 iSidebarWidget *sidebar2 = new_SidebarWidget(right_SidebarSide);
1530 addChildPos_Widget(content, iClob(sidebar2), back_WidgetAddPos); 1554 addChildPos_Widget(content, iClob(sidebar2), back_WidgetAddPos);
1555 setFlags_Widget(as_Widget(sidebar2), disabledWhenHidden_WidgetFlag, iTrue);
1531 } 1556 }
1532 else { 1557 else {
1533 /* Sidebar is a slide-over sheet. */ 1558 /* Sidebar is a slide-over sheet. */
diff --git a/src/ui/root.h b/src/ui/root.h
index 7e831be3..a81ebdf7 100644
--- a/src/ui/root.h
+++ b/src/ui/root.h
@@ -43,6 +43,8 @@ void updatePadding_Root (iRoot *); /* TODO: is part of m
43void dismissPortraitPhoneSidebars_Root (iRoot *); 43void dismissPortraitPhoneSidebars_Root (iRoot *);
44void showToolbar_Root (iRoot *, iBool show); 44void showToolbar_Root (iRoot *, iBool show);
45void updateToolbarColors_Root (iRoot *); 45void updateToolbarColors_Root (iRoot *);
46void showOrHideNewTabButton_Root (iRoot *);
47
46void notifyVisualOffsetChange_Root (iRoot *); 48void notifyVisualOffsetChange_Root (iRoot *);
47 49
48iInt2 size_Root (const iRoot *); 50iInt2 size_Root (const iRoot *);
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index 16677f9e..da0ec22c 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -1181,39 +1181,43 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char *
1181 argLabel_Command(cmd, "noanim") == 0 && 1181 argLabel_Command(cmd, "noanim") == 0 &&
1182 (d->side == left_SidebarSide || deviceType_App() != phone_AppDeviceType); 1182 (d->side == left_SidebarSide || deviceType_App() != phone_AppDeviceType);
1183 int visX = 0; 1183 int visX = 0;
1184 int visY = 0; 1184// int visY = 0;
1185 if (isVisible_Widget(w)) { 1185 if (isVisible_Widget(w)) {
1186 visX = left_Rect(bounds_Widget(w)) - left_Rect(w->root->widget->rect); 1186 visX = left_Rect(bounds_Widget(w)) - left_Rect(w->root->widget->rect);
1187 visY = top_Rect(bounds_Widget(w)) - top_Rect(w->root->widget->rect); 1187// visY = top_Rect(bounds_Widget(w)) - top_Rect(w->root->widget->rect);
1188 } 1188 }
1189 const iBool isHiding = isVisible_Widget(w); 1189 const iBool isHiding = isVisible_Widget(w);
1190 setFlags_Widget(w, hidden_WidgetFlag, isHiding); 1190 setFlags_Widget(w, hidden_WidgetFlag, isHiding);
1191 /* Safe area inset for mobile. */ 1191 /* Safe area inset for mobile. */
1192 const int safePad = (d->side == left_SidebarSide ? left_Rect(safeRect_Root(w->root)) : 0); 1192 const int safePad =
1193 deviceType_App() == desktop_AppDeviceType
1194 ? 0
1195 : (d->side == left_SidebarSide ? left_Rect(safeRect_Root(w->root)) : 0);
1193 const int animFlags = easeOut_AnimFlag | softer_AnimFlag; 1196 const int animFlags = easeOut_AnimFlag | softer_AnimFlag;
1194 if (!isPortraitPhone_App()) { 1197 if (!isPortraitPhone_App()) {
1195 if (!isHiding) { 1198 if (!isHiding) {
1196 setFlags_Widget(w, keepOnTop_WidgetFlag, iFalse); 1199 setFlags_Widget(w, keepOnTop_WidgetFlag, iFalse);
1197 w->rect.size.x = d->widthAsGaps * gap_UI; 1200 w->rect.size.x = d->widthAsGaps * gap_UI;
1198 invalidate_ListWidget(d->list); 1201 invalidate_ListWidget(d->list);
1199 if (isAnimated) { 1202 if (isAnimated) {
1200 setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); 1203 setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue);
1201 setVisualOffset_Widget( 1204 setVisualOffset_Widget(w,
1202 w, (d->side == left_SidebarSide ? -1 : 1) * (w->rect.size.x + safePad), 0, 0); 1205 (d->side == left_SidebarSide ? -1 : 1) *
1206 (w->rect.size.x + safePad),
1207 0,
1208 0);
1203 setVisualOffset_Widget(w, 0, 300, animFlags); 1209 setVisualOffset_Widget(w, 0, 300, animFlags);
1210 }
1204 } 1211 }
1205 } 1212 else if (isAnimated) {
1206 else if (isAnimated) { 1213 setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue);
1207 setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); 1214 if (d->side == right_SidebarSide) {
1208 if (d->side == right_SidebarSide) { 1215 setVisualOffset_Widget(w, visX, 0, 0);
1209 setVisualOffset_Widget(w, visX, 0, 0); 1216 setVisualOffset_Widget(w, visX + w->rect.size.x + safePad, 300, animFlags);
1210 setVisualOffset_Widget( 1217 }
1211 w, visX + w->rect.size.x + safePad, 300, animFlags); 1218 else {
1212 } 1219 setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue);
1213 else { 1220 setVisualOffset_Widget(w, -w->rect.size.x - safePad, 300, animFlags);
1214 setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue);
1215 setVisualOffset_Widget(
1216 w, -w->rect.size.x - safePad, 300, animFlags);
1217 } 1221 }
1218 } 1222 }
1219 setScrollMode_ListWidget(d->list, normal_ScrollMode); 1223 setScrollMode_ListWidget(d->list, normal_ScrollMode);
@@ -1226,15 +1230,16 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char *
1226 w->rect.pos.y = height_Rect(safeRect_Root(w->root)) - d->midHeight; 1230 w->rect.pos.y = height_Rect(safeRect_Root(w->root)) - d->midHeight;
1227 setVisualOffset_Widget(w, bottom_Rect(rect_Root(w->root)) - w->rect.pos.y, 0, 0); 1231 setVisualOffset_Widget(w, bottom_Rect(rect_Root(w->root)) - w->rect.pos.y, 0, 0);
1228 setVisualOffset_Widget(w, 0, 300, animFlags); 1232 setVisualOffset_Widget(w, 0, 300, animFlags);
1229 //animateSlidingSheetHeight_SidebarWidget_(d); 1233 // animateSlidingSheetHeight_SidebarWidget_(d);
1230 setScrollMode_ListWidget(d->list, disabledAtTopBothDirections_ScrollMode); 1234 setScrollMode_ListWidget(d->list, disabledAtTopBothDirections_ScrollMode);
1231 } 1235 }
1232 else { 1236 else {
1233 setVisualOffset_Widget(w, bottom_Rect(rect_Root(w->root)) - w->rect.pos.y, 300, animFlags); 1237 setVisualOffset_Widget(
1238 w, bottom_Rect(rect_Root(w->root)) - w->rect.pos.y, 300, animFlags);
1234 if (d->isEditing) { 1239 if (d->isEditing) {
1235 setMobileEditMode_SidebarWidget_(d, iFalse); 1240 setMobileEditMode_SidebarWidget_(d, iFalse);
1241 }
1236 } 1242 }
1237 }
1238 showToolbar_Root(w->root, isHiding); 1243 showToolbar_Root(w->root, isHiding);
1239 } 1244 }
1240 updateToolbarColors_Root(w->root); 1245 updateToolbarColors_Root(w->root);
@@ -1242,7 +1247,7 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char *
1242 /* BUG: Rearranging because the arrange above didn't fully resolve the height. */ 1247 /* BUG: Rearranging because the arrange above didn't fully resolve the height. */
1243 arrange_Widget(w); 1248 arrange_Widget(w);
1244 if (!isPortraitPhone_App()) { 1249 if (!isPortraitPhone_App()) {
1245 updateSize_DocumentWidget(document_App()); 1250 updateSize_DocumentWidget(document_App());
1246 } 1251 }
1247 if (isVisible_Widget(w)) { 1252 if (isVisible_Widget(w)) {
1248 updateItems_SidebarWidget_(d); 1253 updateItems_SidebarWidget_(d);
diff --git a/src/ui/text.c b/src/ui/text.c
index 86ac709b..ab2af2b2 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -391,8 +391,12 @@ static void deinitCache_Text_(iText *d) {
391 SDL_DestroyTexture(d->cache); 391 SDL_DestroyTexture(d->cache);
392} 392}
393 393
394iRegExp *makeAnsiEscapePattern_Text(void) { 394iRegExp *makeAnsiEscapePattern_Text(iBool includeEscChar) {
395 return new_RegExp("[[()][?]?([0-9;AB]*?)([ABCDEFGHJKSTfhilmn])", 0); 395 const char *pattern = "\x1b[[()][?]?([0-9;AB]*?)([ABCDEFGHJKSTfhilmn])";
396 if (!includeEscChar) {
397 pattern++;
398 }
399 return new_RegExp(pattern, 0);
396} 400}
397 401
398void init_Text(iText *d, SDL_Renderer *render) { 402void init_Text(iText *d, SDL_Renderer *render) {
@@ -400,7 +404,7 @@ void init_Text(iText *d, SDL_Renderer *render) {
400 activeText_ = d; 404 activeText_ = d;
401 init_Array(&d->fonts, sizeof(iFont)); 405 init_Array(&d->fonts, sizeof(iFont));
402 d->contentFontSize = contentScale_Text_; 406 d->contentFontSize = contentScale_Text_;
403 d->ansiEscape = makeAnsiEscapePattern_Text(); 407 d->ansiEscape = makeAnsiEscapePattern_Text(iFalse /* no ESC */);
404 d->baseFontId = -1; 408 d->baseFontId = -1;
405 d->baseFgColorId = -1; 409 d->baseFgColorId = -1;
406 d->missingGlyphs = iFalse; 410 d->missingGlyphs = iFalse;
@@ -714,6 +718,34 @@ struct Impl_AttributedRun {
714 718
715static iColor fgColor_AttributedRun_(const iAttributedRun *d) { 719static iColor fgColor_AttributedRun_(const iAttributedRun *d) {
716 if (d->fgColor_.a) { 720 if (d->fgColor_.a) {
721 /* Ensure legibility if only the foreground color is set. */
722 if (!d->bgColor_.a) {
723 iColor fg = d->fgColor_;
724 const iHSLColor themeBg = get_HSLColor(tmBackground_ColorId);
725 const float bgLuminance = luma_Color(get_Color(tmBackground_ColorId));
726 /* TODO: Actually this should check if the FG is too close to the BG, and
727 either darken or brighten the FG. Now it only accounts for nearly black/white
728 backgrounds. */
729 if (bgLuminance < 0.1f) {
730 /* Background is dark. Lighten the foreground. */
731 iHSLColor fgHsl = hsl_Color(fg);
732 fgHsl.lum = iMax(0.2f, fgHsl.lum);
733 return rgb_HSLColor(fgHsl);
734 }
735 if (bgLuminance > 0.4f) {
736 float dim = (bgLuminance - 0.4f);
737 fg.r *= 1.0f * dim;
738 fg.g *= 1.0f * dim;
739 fg.b *= 1.0f * dim;
740 }
741 if (themeBg.sat > 0.15f && themeBg.lum >= 0.5f) {
742 iHSLColor fgHsl = hsl_Color(fg);
743 fgHsl.hue = themeBg.hue;
744 fgHsl.lum = themeBg.lum * 0.5f;
745 fg = rgb_HSLColor(fgHsl);
746 }
747 return fg;
748 }
717 return d->fgColor_; 749 return d->fgColor_;
718 } 750 }
719 if (d->attrib.fgColorId == none_ColorId) { 751 if (d->attrib.fgColorId == none_ColorId) {
@@ -1576,7 +1608,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1576 iAssert(xAdvance >= 0); 1608 iAssert(xAdvance >= 0);
1577 if (wrapMode == word_WrapTextMode) { 1609 if (wrapMode == word_WrapTextMode) {
1578 /* When word wrapping, only consider certain places breakable. */ 1610 /* When word wrapping, only consider certain places breakable. */
1579 if ((prevCh == '-' || prevCh == '/') && !isPunct_Char(ch)) { 1611 if ((prevCh == '-' || prevCh == '/' || prevCh == '\\') && !isPunct_Char(ch)) {
1580 safeBreakPos = logPos; 1612 safeBreakPos = logPos;
1581 breakAdvance = wrapAdvance; 1613 breakAdvance = wrapAdvance;
1582 breakRunIndex = runIndex; 1614 breakRunIndex = runIndex;
@@ -1977,6 +2009,7 @@ static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, iTextAttrib attrib,
1977} 2009}
1978 2010
1979iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { 2011iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) {
2012 *endPos = text.end;
1980 iWrapText wrap = { .mode = word_WrapTextMode, 2013 iWrapText wrap = { .mode = word_WrapTextMode,
1981 .text = text, 2014 .text = text,
1982 .maxWidth = width, 2015 .maxWidth = width,
@@ -1991,6 +2024,7 @@ iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **e
1991 *endPos = text.start; 2024 *endPos = text.start;
1992 return zero_I2(); 2025 return zero_I2();
1993 } 2026 }
2027 *endPos = text.end;
1994 /* "NoWrap" means words aren't wrapped; the line is broken at nearest character. */ 2028 /* "NoWrap" means words aren't wrapped; the line is broken at nearest character. */
1995 iWrapText wrap = { .mode = anyCharacter_WrapTextMode, 2029 iWrapText wrap = { .mode = anyCharacter_WrapTextMode,
1996 .text = text, 2030 .text = text,
diff --git a/src/ui/text.h b/src/ui/text.h
index a34cc9bd..e741880d 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -237,7 +237,7 @@ enum iTextBlockMode { quadrants_TextBlockMode, shading_TextBlockMode };
237iString * renderBlockChars_Text (const iBlock *fontData, int height, enum iTextBlockMode, 237iString * renderBlockChars_Text (const iBlock *fontData, int height, enum iTextBlockMode,
238 const iString *text); 238 const iString *text);
239 239
240iRegExp * makeAnsiEscapePattern_Text (void); 240iRegExp * makeAnsiEscapePattern_Text (iBool includeEscChar);
241 241
242/*-----------------------------------------------------------------------------------------------*/ 242/*-----------------------------------------------------------------------------------------------*/
243 243
diff --git a/src/ui/touch.c b/src/ui/touch.c
index 20ccf7b8..a178a913 100644
--- a/src/ui/touch.c
+++ b/src/ui/touch.c
@@ -638,11 +638,14 @@ iBool processEvent_Touch(const SDL_Event *ev) {
638 pixels.x = 0; 638 pixels.x = 0;
639 } 639 }
640#if 0 640#if 0
641 printf("%p (%s) py: %i wy: %f acc: %f edge: %d\n", 641 static uint32_t lastTime = 0;
642 printf("%u :: %p (%s) py: %i wy: %f acc: %f edge: %d\n",
643 nowTime - lastTime,
642 touch->affinity, 644 touch->affinity,
643 class_Widget(touch->affinity)->name, 645 class_Widget(touch->affinity)->name,
644 pixels.y, y_F3(amount), y_F3(touch->accum), 646 pixels.y, y_F3(amount), y_F3(touch->accum),
645 touch->edge); 647 touch->edge);
648 lastTime = nowTime;
646#endif 649#endif
647 if (pixels.x || pixels.y) { 650 if (pixels.x || pixels.y) {
648 //setFocus_Widget(NULL); 651 //setFocus_Widget(NULL);
diff --git a/src/ui/window.c b/src/ui/window.c
index af36bb22..13abc5fa 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -1001,10 +1001,11 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
1001 default: { 1001 default: {
1002 SDL_Event event = *ev; 1002 SDL_Event event = *ev;
1003 if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.unfreeze") && mw) { 1003 if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.unfreeze") && mw) {
1004 mw->isDrawFrozen = iFalse;
1005 if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) { 1004 if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) {
1005 mw->isDrawFrozen = iTrue; /* don't trigger a redraw now */
1006 SDL_ShowWindow(d->win); 1006 SDL_ShowWindow(d->win);
1007 } 1007 }
1008 mw->isDrawFrozen = iFalse;
1008 draw_MainWindow(mw); /* don't show a frame of placeholder content */ 1009 draw_MainWindow(mw); /* don't show a frame of placeholder content */
1009 postCommand_App("media.player.update"); /* in case a player needs updating */ 1010 postCommand_App("media.player.update"); /* in case a player needs updating */
1010 return iTrue; 1011 return iTrue;