summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-12-01 13:55:11 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-12-01 13:55:11 +0200
commitf4942e1b4da6dc1334dcdb4f2daae670bfa1f813 (patch)
tree3b0b638d4351311e8322e9c35da11078c27585c0
parentb3550138da3a669999c06da41720f2be664d5e86 (diff)
Added switching to the identity toolbar menu
One can now use the identity toolbar menu to switch between client certificates that have been used on the current site. The five latest ones are remembered.
-rw-r--r--po/en.po9
-rw-r--r--res/about/version.gmi5
-rw-r--r--res/lang/cs.binbin30899 -> 30962 bytes
-rw-r--r--res/lang/de.binbin29906 -> 29969 bytes
-rw-r--r--res/lang/en.binbin26003 -> 26066 bytes
-rw-r--r--res/lang/eo.binbin24957 -> 25020 bytes
-rw-r--r--res/lang/es.binbin29730 -> 29793 bytes
-rw-r--r--res/lang/es_MX.binbin27062 -> 27125 bytes
-rw-r--r--res/lang/fi.binbin29563 -> 29626 bytes
-rw-r--r--res/lang/fr.binbin30710 -> 30773 bytes
-rw-r--r--res/lang/gl.binbin28915 -> 28978 bytes
-rw-r--r--res/lang/hu.binbin30735 -> 30798 bytes
-rw-r--r--res/lang/ia.binbin28062 -> 28125 bytes
-rw-r--r--res/lang/ie.binbin28650 -> 28713 bytes
-rw-r--r--res/lang/isv.binbin24723 -> 24786 bytes
-rw-r--r--res/lang/pl.binbin29338 -> 29401 bytes
-rw-r--r--res/lang/ru.binbin44098 -> 44161 bytes
-rw-r--r--res/lang/sk.binbin25059 -> 25122 bytes
-rw-r--r--res/lang/sr.binbin43524 -> 43587 bytes
-rw-r--r--res/lang/tok.binbin26772 -> 26835 bytes
-rw-r--r--res/lang/tr.binbin28956 -> 29019 bytes
-rw-r--r--res/lang/uk.binbin43443 -> 43506 bytes
-rw-r--r--res/lang/zh_Hans.binbin24957 -> 25020 bytes
-rw-r--r--res/lang/zh_Hant.binbin25155 -> 25218 bytes
-rw-r--r--src/app.c19
-rw-r--r--src/gmcerts.c9
-rw-r--r--src/gmcerts.h5
-rw-r--r--src/sitespec.c81
-rw-r--r--src/sitespec.h20
-rw-r--r--src/ui/root.c89
30 files changed, 214 insertions, 23 deletions
diff --git a/po/en.po b/po/en.po
index 066c4eca..9a154c5c 100644
--- a/po/en.po
+++ b/po/en.po
@@ -321,6 +321,10 @@ msgstr "Show History"
321msgid "menu.show.identities" 321msgid "menu.show.identities"
322msgstr "Show Identities" 322msgstr "Show Identities"
323 323
324# Active identity toolbar menu.
325msgid "menu.hide.identities"
326msgstr "Hide Identities"
327""
324# Used in the View menu on macOS. Shows sidebar and switches sidebar tab. 328# Used in the View menu on macOS. Shows sidebar and switches sidebar tab.
325msgid "menu.show.outline" 329msgid "menu.show.outline"
326msgstr "Show Page Outline" 330msgstr "Show Page Outline"
@@ -629,6 +633,11 @@ msgstr "Stop Using Everywhere"
629msgid "ident.export" 633msgid "ident.export"
630msgstr "Export" 634msgstr "Export"
631 635
636# The %s represents the name of an identity.
637#, c-format
638msgid "ident.switch"
639msgstr "Switch to %s"
640
632msgid "heading.ident.use" 641msgid "heading.ident.use"
633msgstr "IDENTITY USAGE" 642msgstr "IDENTITY USAGE"
634 643
diff --git a/res/about/version.gmi b/res/about/version.gmi
index a9043b2c..7a14e79b 100644
--- a/res/about/version.gmi
+++ b/res/about/version.gmi
@@ -6,6 +6,10 @@
6``` 6```
7# Release notes 7# Release notes
8 8
9## 1.10
10New features:
11* Identity toolbar menu can be used to switch between alternate identities that have been used on the site. If you have multiple accounts on a site, this makes it more convenient to switch between them.
12
9## 1.9.2 13## 1.9.2
10* Windows: Use the correct version number for update checks. 14* Windows: Use the correct version number for update checks.
11* Shorter label for "Mark All as Read" in Feeds sidebar actions. 15* Shorter label for "Mark All as Read" in Feeds sidebar actions.
@@ -22,7 +26,6 @@
22* Fixed the New Tab button not staying at the right edge of the window, depending on how many tabs are open. 26* Fixed the New Tab button not staying at the right edge of the window, depending on how many tabs are open.
23 27
24## 1.9 28## 1.9
25
26New features: 29New features:
27* Added a toolbar button for toggling the left sidebar. 30* Added a toolbar button for toggling the left sidebar.
28* Added an unsplit button in the toolbar when in split view mode. 31* Added an unsplit button in the toolbar when in split view mode.
diff --git a/res/lang/cs.bin b/res/lang/cs.bin
index 647e1f34..d3e06c73 100644
--- a/res/lang/cs.bin
+++ b/res/lang/cs.bin
Binary files differ
diff --git a/res/lang/de.bin b/res/lang/de.bin
index b2bb35a0..b9d155a7 100644
--- a/res/lang/de.bin
+++ b/res/lang/de.bin
Binary files differ
diff --git a/res/lang/en.bin b/res/lang/en.bin
index fbf4c73c..5bb8299a 100644
--- a/res/lang/en.bin
+++ b/res/lang/en.bin
Binary files differ
diff --git a/res/lang/eo.bin b/res/lang/eo.bin
index 7156b7c8..ff1ddb01 100644
--- a/res/lang/eo.bin
+++ b/res/lang/eo.bin
Binary files differ
diff --git a/res/lang/es.bin b/res/lang/es.bin
index 1acb50d1..ba9b2343 100644
--- a/res/lang/es.bin
+++ b/res/lang/es.bin
Binary files differ
diff --git a/res/lang/es_MX.bin b/res/lang/es_MX.bin
index 078de89d..f4ddcc72 100644
--- a/res/lang/es_MX.bin
+++ b/res/lang/es_MX.bin
Binary files differ
diff --git a/res/lang/fi.bin b/res/lang/fi.bin
index ccd3e133..f73891d6 100644
--- a/res/lang/fi.bin
+++ b/res/lang/fi.bin
Binary files differ
diff --git a/res/lang/fr.bin b/res/lang/fr.bin
index 96c1148e..0851f535 100644
--- a/res/lang/fr.bin
+++ b/res/lang/fr.bin
Binary files differ
diff --git a/res/lang/gl.bin b/res/lang/gl.bin
index 4269b5ed..80215589 100644
--- a/res/lang/gl.bin
+++ b/res/lang/gl.bin
Binary files differ
diff --git a/res/lang/hu.bin b/res/lang/hu.bin
index 7b7edb50..7d7ed94e 100644
--- a/res/lang/hu.bin
+++ b/res/lang/hu.bin
Binary files differ
diff --git a/res/lang/ia.bin b/res/lang/ia.bin
index 4750b545..572015c2 100644
--- a/res/lang/ia.bin
+++ b/res/lang/ia.bin
Binary files differ
diff --git a/res/lang/ie.bin b/res/lang/ie.bin
index e4c90ef9..bb485f4e 100644
--- a/res/lang/ie.bin
+++ b/res/lang/ie.bin
Binary files differ
diff --git a/res/lang/isv.bin b/res/lang/isv.bin
index 80754fc5..f90a1e7d 100644
--- a/res/lang/isv.bin
+++ b/res/lang/isv.bin
Binary files differ
diff --git a/res/lang/pl.bin b/res/lang/pl.bin
index c0affedf..753e595b 100644
--- a/res/lang/pl.bin
+++ b/res/lang/pl.bin
Binary files differ
diff --git a/res/lang/ru.bin b/res/lang/ru.bin
index 966deaea..edcebb14 100644
--- a/res/lang/ru.bin
+++ b/res/lang/ru.bin
Binary files differ
diff --git a/res/lang/sk.bin b/res/lang/sk.bin
index deda3b69..b843a383 100644
--- a/res/lang/sk.bin
+++ b/res/lang/sk.bin
Binary files differ
diff --git a/res/lang/sr.bin b/res/lang/sr.bin
index 8d8591e6..1ef302d9 100644
--- a/res/lang/sr.bin
+++ b/res/lang/sr.bin
Binary files differ
diff --git a/res/lang/tok.bin b/res/lang/tok.bin
index 1b0c0733..6e3c7af7 100644
--- a/res/lang/tok.bin
+++ b/res/lang/tok.bin
Binary files differ
diff --git a/res/lang/tr.bin b/res/lang/tr.bin
index 60ef518d..71b9382f 100644
--- a/res/lang/tr.bin
+++ b/res/lang/tr.bin
Binary files differ
diff --git a/res/lang/uk.bin b/res/lang/uk.bin
index 2ed16909..f7040f2f 100644
--- a/res/lang/uk.bin
+++ b/res/lang/uk.bin
Binary files differ
diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin
index 3a83dd40..3ccab576 100644
--- a/res/lang/zh_Hans.bin
+++ b/res/lang/zh_Hans.bin
Binary files differ
diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin
index 7da4b273..39393417 100644
--- a/res/lang/zh_Hant.bin
+++ b/res/lang/zh_Hant.bin
Binary files differ
diff --git a/src/app.c b/src/app.c
index 9f4d60e3..540c46d8 100644
--- a/src/app.c
+++ b/src/app.c
@@ -3149,6 +3149,25 @@ iBool handleCommand_App(const char *cmd) {
3149 postCommand_App("idents.changed"); 3149 postCommand_App("idents.changed");
3150 return iTrue; 3150 return iTrue;
3151 } 3151 }
3152 else if (equal_Command(cmd, "ident.switch")) {
3153 /* This is different than "ident.signin" in that the currently used identity's activation
3154 URL is used instead of the current one. */
3155 const iString *docUrl = url_DocumentWidget(document_App());
3156 const iGmIdentity *cur = identityForUrl_GmCerts(d->certs, docUrl);
3157 iGmIdentity *dst = findIdentity_GmCerts(
3158 d->certs, collect_Block(hexDecode_Rangecc(range_Command(cmd, "fp"))));
3159 if (cur && dst && cur != dst) {
3160 iString *useUrl = copy_String(findUse_GmIdentity(cur, docUrl));
3161 if (isEmpty_String(useUrl)) {
3162 useUrl = copy_String(docUrl);
3163 }
3164 signIn_GmCerts(d->certs, dst, useUrl);
3165 postCommand_App("idents.changed");
3166 postCommand_App("navigate.reload");
3167 delete_String(useUrl);
3168 }
3169 return iTrue;
3170 }
3152 else if (equal_Command(cmd, "idents.changed")) { 3171 else if (equal_Command(cmd, "idents.changed")) {
3153 saveIdentities_GmCerts(d->certs); 3172 saveIdentities_GmCerts(d->certs);
3154 return iFalse; 3173 return iFalse;
diff --git a/src/gmcerts.c b/src/gmcerts.c
index f95fea7d..345c36e0 100644
--- a/src/gmcerts.c
+++ b/src/gmcerts.c
@@ -201,6 +201,15 @@ void clearUse_GmIdentity(iGmIdentity *d) {
201 clear_StringSet(d->useUrls); 201 clear_StringSet(d->useUrls);
202} 202}
203 203
204const iString *findUse_GmIdentity(const iGmIdentity *d, const iString *url) {
205 iConstForEach(StringSet, using, d->useUrls) {
206 if (startsWith_String(url, cstr_String(using.value))) {
207 return using.value;
208 }
209 }
210 return NULL;
211}
212
204const iString *name_GmIdentity(const iGmIdentity *d) { 213const iString *name_GmIdentity(const iGmIdentity *d) {
205 iString *name = collect_String(subject_TlsCertificate(d->cert)); 214 iString *name = collect_String(subject_TlsCertificate(d->cert));
206 if (startsWith_String(name, "CN = ")) { 215 if (startsWith_String(name, "CN = ")) {
diff --git a/src/gmcerts.h b/src/gmcerts.h
index 02a41c14..6ece1954 100644
--- a/src/gmcerts.h
+++ b/src/gmcerts.h
@@ -48,8 +48,9 @@ iBool isUsed_GmIdentity (const iGmIdentity *);
48iBool isUsedOn_GmIdentity (const iGmIdentity *, const iString *url); 48iBool isUsedOn_GmIdentity (const iGmIdentity *, const iString *url);
49iBool isUsedOnDomain_GmIdentity (const iGmIdentity *, const iRangecc domain); 49iBool isUsedOnDomain_GmIdentity (const iGmIdentity *, const iRangecc domain);
50 50
51void setUse_GmIdentity (iGmIdentity *, const iString *url, iBool use); 51void setUse_GmIdentity (iGmIdentity *, const iString *url, iBool use);
52void clearUse_GmIdentity (iGmIdentity *); 52void clearUse_GmIdentity (iGmIdentity *);
53const iString *findUse_GmIdentity (const iGmIdentity *, const iString *url);
53 54
54const iString *name_GmIdentity(const iGmIdentity *); 55const iString *name_GmIdentity(const iGmIdentity *);
55 56
diff --git a/src/sitespec.c b/src/sitespec.c
index 6f4546f0..f8b77c86 100644
--- a/src/sitespec.c
+++ b/src/sitespec.c
@@ -25,6 +25,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
25#include <the_Foundation/file.h> 25#include <the_Foundation/file.h>
26#include <the_Foundation/path.h> 26#include <the_Foundation/path.h>
27#include <the_Foundation/stringhash.h> 27#include <the_Foundation/stringhash.h>
28#include <the_Foundation/stringarray.h>
28#include <the_Foundation/toml.h> 29#include <the_Foundation/toml.h>
29 30
30iDeclareClass(SiteParams) 31iDeclareClass(SiteParams)
@@ -35,6 +36,7 @@ struct Impl_SiteParams {
35 uint16_t titanPort; 36 uint16_t titanPort;
36 iString titanIdentity; /* fingerprint */ 37 iString titanIdentity; /* fingerprint */
37 int dismissWarnings; 38 int dismissWarnings;
39 iStringArray usedIdentities; /* fingerprints; latest ones at the end */
38 /* TODO: theme seed, style settings */ 40 /* TODO: theme seed, style settings */
39}; 41};
40 42
@@ -42,12 +44,23 @@ void init_SiteParams(iSiteParams *d) {
42 d->titanPort = 0; /* undefined */ 44 d->titanPort = 0; /* undefined */
43 init_String(&d->titanIdentity); 45 init_String(&d->titanIdentity);
44 d->dismissWarnings = 0; 46 d->dismissWarnings = 0;
47 init_StringArray(&d->usedIdentities);
45} 48}
46 49
47void deinit_SiteParams(iSiteParams *d) { 50void deinit_SiteParams(iSiteParams *d) {
51 deinit_StringArray(&d->usedIdentities);
48 deinit_String(&d->titanIdentity); 52 deinit_String(&d->titanIdentity);
49} 53}
50 54
55static size_t findUsedIdentity_SiteParams_(const iSiteParams *d, const iString *fingerprint) {
56 iConstForEach(StringArray, i, &d->usedIdentities) {
57 if (equal_String(i.value, fingerprint)) {
58 return index_StringArrayConstIterator(&i);
59 }
60 }
61 return iInvalidPos;
62}
63
51iDefineClass(SiteParams) 64iDefineClass(SiteParams)
52iDefineObjectConstruction(SiteParams) 65iDefineObjectConstruction(SiteParams)
53 66
@@ -130,6 +143,12 @@ static void handleIniKeyValue_SiteSpec_(void *context, const iString *table, con
130 else if (!cmp_String(key, "dismissWarnings") && value->type == int64_TomlType) { 143 else if (!cmp_String(key, "dismissWarnings") && value->type == int64_TomlType) {
131 d->loadParams->dismissWarnings = value->value.int64; 144 d->loadParams->dismissWarnings = value->value.int64;
132 } 145 }
146 else if (!cmp_String(key, "usedIdentities") && value->type == string_TomlType) {
147 iRangecc seg = iNullRange;
148 while (nextSplit_Rangecc(range_String(value->value.string), " ", &seg)) {
149 pushBack_StringArray(&d->loadParams->usedIdentities, collectNewRange_String(seg));
150 }
151 }
133} 152}
134 153
135static iBool load_SiteSpec_(iSiteSpec *d) { 154static iBool load_SiteSpec_(iSiteSpec *d) {
@@ -151,6 +170,7 @@ static void save_SiteSpec_(iSiteSpec *d) {
151 if (open_File(f, writeOnly_FileMode | text_FileMode)) { 170 if (open_File(f, writeOnly_FileMode | text_FileMode)) {
152 iString *buf = new_String(); 171 iString *buf = new_String();
153 iConstForEach(StringHash, i, &d->sites) { 172 iConstForEach(StringHash, i, &d->sites) {
173 iBeginCollect();
154 const iBlock * key = &i.value->keyBlock; 174 const iBlock * key = &i.value->keyBlock;
155 const iSiteParams *params = i.value->object; 175 const iSiteParams *params = i.value->object;
156 format_String(buf, "[%s]\n", cstr_Block(key)); 176 format_String(buf, "[%s]\n", cstr_Block(key));
@@ -164,8 +184,15 @@ static void save_SiteSpec_(iSiteSpec *d) {
164 if (params->dismissWarnings) { 184 if (params->dismissWarnings) {
165 appendFormat_String(buf, "dismissWarnings = 0x%x\n", params->dismissWarnings); 185 appendFormat_String(buf, "dismissWarnings = 0x%x\n", params->dismissWarnings);
166 } 186 }
187 if (!isEmpty_StringArray(&params->usedIdentities)) {
188 appendFormat_String(
189 buf,
190 "usedIdentities = \"%s\"\n",
191 cstrCollect_String(joinCStr_StringArray(&params->usedIdentities, " ")));
192 }
167 appendCStr_String(buf, "\n"); 193 appendCStr_String(buf, "\n");
168 write_File(f, utf8_String(buf)); 194 write_File(f, utf8_String(buf));
195 iEndCollect();
169 } 196 }
170 delete_String(buf); 197 delete_String(buf);
171 } 198 }
@@ -188,14 +215,19 @@ void deinit_SiteSpec(void) {
188 deinit_String(&d->saveDir); 215 deinit_String(&d->saveDir);
189} 216}
190 217
191void setValue_SiteSpec(const iString *site, enum iSiteSpecKey key, int value) { 218static iSiteParams *findParams_SiteSpec_(iSiteSpec *d, const iString *site) {
192 iSiteSpec *d = &siteSpec_;
193 const iString *hashKey = collect_String(lower_String(site)); 219 const iString *hashKey = collect_String(lower_String(site));
194 iSiteParams *params = value_StringHash(&d->sites, hashKey); 220 iSiteParams *params = value_StringHash(&d->sites, hashKey);
195 if (!params) { 221 if (!params) {
196 params = new_SiteParams(); 222 params = new_SiteParams();
197 insert_StringHash(&d->sites, hashKey, params); 223 insert_StringHash(&d->sites, hashKey, params);
198 } 224 }
225 return params;
226}
227
228void setValue_SiteSpec(const iString *site, enum iSiteSpecKey key, int value) {
229 iSiteSpec *d = &siteSpec_;
230 iSiteParams *params = findParams_SiteSpec_(d, site);
199 iBool needSave = iFalse; 231 iBool needSave = iFalse;
200 switch (key) { 232 switch (key) {
201 case titanPort_SiteSpecKey: 233 case titanPort_SiteSpecKey:
@@ -216,12 +248,7 @@ void setValue_SiteSpec(const iString *site, enum iSiteSpecKey key, int value) {
216 248
217void setValueString_SiteSpec(const iString *site, enum iSiteSpecKey key, const iString *value) { 249void setValueString_SiteSpec(const iString *site, enum iSiteSpecKey key, const iString *value) {
218 iSiteSpec *d = &siteSpec_; 250 iSiteSpec *d = &siteSpec_;
219 const iString *hashKey = collect_String(lower_String(site)); 251 iSiteParams *params = findParams_SiteSpec_(d, site);
220 iSiteParams *params = value_StringHash(&d->sites, hashKey);
221 if (!params) {
222 params = new_SiteParams();
223 insert_StringHash(&d->sites, hashKey, params);
224 }
225 iBool needSave = iFalse; 252 iBool needSave = iFalse;
226 switch (key) { 253 switch (key) {
227 case titanIdentity_SiteSpecKey: 254 case titanIdentity_SiteSpecKey:
@@ -238,6 +265,44 @@ void setValueString_SiteSpec(const iString *site, enum iSiteSpecKey key, const i
238 } 265 }
239} 266}
240 267
268static void insertOrRemoveString_SiteSpec_(iSiteSpec *d, const iString *site, enum iSiteSpecKey key,
269 const iString *value, iBool doInsert) {
270 iSiteParams *params = findParams_SiteSpec_(d, site);
271 iBool needSave = iFalse;
272 switch (key) {
273 case usedIdentities_SiteSpecKey: {
274 const size_t index = findUsedIdentity_SiteParams_(params, value);
275 if (doInsert && index == iInvalidPos) {
276 pushBack_StringArray(&params->usedIdentities, value);
277 needSave = iTrue;
278 }
279 else if (!doInsert && index != iInvalidPos) {
280 remove_StringArray(&params->usedIdentities, index);
281 needSave = iTrue;
282 }
283 break;
284 }
285 default:
286 break;
287 }
288 if (needSave) {
289 save_SiteSpec_(d);
290 }
291}
292
293void insertString_SiteSpec(const iString *site, enum iSiteSpecKey key, const iString *value) {
294 insertOrRemoveString_SiteSpec_(&siteSpec_, site, key, value, iTrue);
295}
296
297void removeString_SiteSpec(const iString *site, enum iSiteSpecKey key, const iString *value) {
298 insertOrRemoveString_SiteSpec_(&siteSpec_, site, key, value, iFalse);
299}
300
301const iStringArray *strings_SiteSpec(const iString *site, enum iSiteSpecKey key) {
302 const iSiteParams *params = findParams_SiteSpec_(&siteSpec_, site);
303 return &params->usedIdentities;
304}
305
241int value_SiteSpec(const iString *site, enum iSiteSpecKey key) { 306int value_SiteSpec(const iString *site, enum iSiteSpecKey key) {
242 iSiteSpec *d = &siteSpec_; 307 iSiteSpec *d = &siteSpec_;
243 const iSiteParams *params = constValue_StringHash(&d->sites, collect_String(lower_String(site))); 308 const iSiteParams *params = constValue_StringHash(&d->sites, collect_String(lower_String(site)));
diff --git a/src/sitespec.h b/src/sitespec.h
index 5adaeb8c..11c40e3c 100644
--- a/src/sitespec.h
+++ b/src/sitespec.h
@@ -22,22 +22,26 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#pragma once 23#pragma once
24 24
25#include <the_Foundation/string.h> 25#include <the_Foundation/stringarray.h>
26 26
27iDeclareType(SiteSpec) 27iDeclareType(SiteSpec)
28 28
29enum iSiteSpecKey { 29enum iSiteSpecKey {
30 titanPort_SiteSpecKey, 30 titanPort_SiteSpecKey, /* int */
31 titanIdentity_SiteSpecKey, 31 titanIdentity_SiteSpecKey, /* String */
32 dismissWarnings_SiteSpecKey, 32 dismissWarnings_SiteSpecKey, /* int */
33 usedIdentities_SiteSpecKey, /* StringArray */
33}; 34};
34 35
35void init_SiteSpec (const char *saveDir); 36void init_SiteSpec (const char *saveDir);
36void deinit_SiteSpec (void); 37void deinit_SiteSpec (void);
37 38
38/* changes saved immediately */ 39/* changes saved immediately */
39void setValue_SiteSpec (const iString *site, enum iSiteSpecKey key, int value); 40void setValue_SiteSpec (const iString *site, enum iSiteSpecKey key, int value);
40void setValueString_SiteSpec (const iString *site, enum iSiteSpecKey key, const iString *value); 41void setValueString_SiteSpec (const iString *site, enum iSiteSpecKey key, const iString *value);
42void insertString_SiteSpec (const iString *site, enum iSiteSpecKey key, const iString *value);
43void removeString_SiteSpec (const iString *site, enum iSiteSpecKey key, const iString *value);
41 44
42int value_SiteSpec (const iString *site, enum iSiteSpecKey key); 45int value_SiteSpec (const iString *site, enum iSiteSpecKey key);
43const iString * valueString_SiteSpec (const iString *site, enum iSiteSpecKey key); 46const iString * valueString_SiteSpec (const iString *site, enum iSiteSpecKey key);
47const iStringArray *strings_SiteSpec (const iString *site, enum iSiteSpecKey key);
diff --git a/src/ui/root.c b/src/ui/root.c
index c2161d80..f06ae842 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -38,6 +38,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
38#include "../history.h" 38#include "../history.h"
39#include "../gmcerts.h" 39#include "../gmcerts.h"
40#include "../gmutil.h" 40#include "../gmutil.h"
41#include "../sitespec.h"
41#include "../visited.h" 42#include "../visited.h"
42 43
43#if defined (iPlatformMsys) 44#if defined (iPlatformMsys)
@@ -330,6 +331,66 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) {
330 openMenuFlags_Widget(menu, zero_I2(), postCommands_MenuOpenFlags | center_MenuOpenFlags); 331 openMenuFlags_Widget(menu, zero_I2(), postCommands_MenuOpenFlags | center_MenuOpenFlags);
331 return iTrue; 332 return iTrue;
332 } 333 }
334 else if (equal_Command(cmd, "identmenu.open")) {
335 iWidget *button = findWidget_Root("navbar.ident");
336 iArray items;
337 init_Array(&items, sizeof(iMenuItem));
338 /* Current identity. */
339 const iString *docUrl = url_DocumentWidget(document_App());
340 const iGmIdentity *ident = identityForUrl_GmCerts(certs_App(), docUrl);
341 const iString *fp = collect_String(hexEncode_Block(&ident->fingerprint));
342 pushBackN_Array(&items,
343 (iMenuItem[]){ { format_CStr("///" uiHeading_ColorEscape "%s",
344 ident ? cstr_String(name_GmIdentity(ident))
345 : "${menu.identity.notactive}") },
346 { "---" } },
347 2);
348 /* Alternate identities. */ {
349 const iString *site = collectNewRange_String(urlRoot_String(docUrl));
350 iBool haveAlts = iFalse;
351 iConstForEach(StringArray, i, strings_SiteSpec(site, usedIdentities_SiteSpecKey)) {
352 if (!equal_String(i.value, fp)) {
353 const iBlock *otherFp = collect_Block(hexDecode_Rangecc(range_String(i.value)));
354 const iGmIdentity *other = findIdentity_GmCerts(certs_App(), otherFp);
355 if (other) {
356 pushBack_Array(
357 &items,
358 &(iMenuItem){
359 format_CStr(translateCStr_Lang("\U0001f816 ${ident.switch}"),
360 cstr_String(name_GmIdentity(other))),
361 0,
362 0,
363 format_CStr("ident.switch fp:%s", cstr_String(i.value)) });
364 haveAlts = iTrue;
365 }
366 }
367 }
368 if (haveAlts) {
369 pushBack_Array(&items, &(iMenuItem){ "---" });
370 }
371 }
372 iSidebarWidget *sidebar = findWidget_App("sidebar");
373 pushBackN_Array(
374 &items,
375 (iMenuItem[]){
376 { add_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" },
377 { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" },
378 { "---" },
379 { isVisible_Widget(sidebar) && mode_SidebarWidget(sidebar) == identities_SidebarMode
380 ? leftHalf_Icon " ${menu.hide.identities}"
381 : leftHalf_Icon " ${menu.show.identities}",
382 0,
383 0,
384 deviceType_App() == phone_AppDeviceType ? "toolbar.showident"
385 : "sidebar.mode arg:3 toggle:1" },
386 },
387 4);
388 iWidget *menu =
389 makeMenu_Widget(button, constData_Array(&items), size_Array(&items));
390 openMenu_Widget(menu, topLeft_Rect(bounds_Widget(button)));
391 deinit_Array(&items);
392 return iTrue;
393 }
333 else if (equal_Command(cmd, "contextclick")) { 394 else if (equal_Command(cmd, "contextclick")) {
334 iBool showBarMenu = iFalse; 395 iBool showBarMenu = iFalse;
335 if (equal_Rangecc(range_Command(cmd, "id"), "buttons")) { 396 if (equal_Rangecc(range_Command(cmd, "id"), "buttons")) {
@@ -780,6 +841,26 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
780 dismissPortraitPhoneSidebars_Root(get_Root()); 841 dismissPortraitPhoneSidebars_Root(get_Root());
781 updateNavBarIdentity_(navBar); 842 updateNavBarIdentity_(navBar);
782 updateNavDirButtons_(navBar); 843 updateNavDirButtons_(navBar);
844 /* Update site-specific used identities. */ {
845 const iGmIdentity *ident =
846 identityForUrl_GmCerts(certs_App(), url_DocumentWidget(document_App()));
847 if (ident) {
848 const iString *site =
849 collectNewRange_String(urlRoot_String(canonicalUrl_String(urlStr)));
850 const iStringArray *usedIdents =
851 strings_SiteSpec(site, usedIdentities_SiteSpecKey);
852 const iString *fingerprint = collect_String(hexEncode_Block(&ident->fingerprint));
853 /* Keep this identity at the end of the list. */
854 removeString_SiteSpec(site, usedIdentities_SiteSpecKey, fingerprint);
855 insertString_SiteSpec(site, usedIdentities_SiteSpecKey, fingerprint);
856 /* Keep the list short. */
857 while (size_StringArray(usedIdents) > 5) {
858 removeString_SiteSpec(site,
859 usedIdentities_SiteSpecKey,
860 constAt_StringArray(usedIdents, 0));
861 }
862 }
863 }
783 /* Icon updates should be limited to automatically chosen icons if the user 864 /* Icon updates should be limited to automatically chosen icons if the user
784 is allowed to pick their own in the future. */ 865 is allowed to pick their own in the future. */
785 if (updateBookmarkIcon_Bookmarks(bookmarks_App(), urlStr, 866 if (updateBookmarkIcon_Bookmarks(bookmarks_App(), urlStr,
@@ -1268,10 +1349,10 @@ void createUserInterface_Root(iRoot *d) {
1268 setId_Widget(addChild_Widget(rightEmbed, iClob(makePadding_Widget(0))), "url.embedpad"); 1349 setId_Widget(addChild_Widget(rightEmbed, iClob(makePadding_Widget(0))), "url.embedpad");
1269 } 1350 }
1270 /* The active identity menu. */ { 1351 /* The active identity menu. */ {
1271 iLabelWidget *idMenu = makeMenuButton_LabelWidget( 1352 iLabelWidget *idButton = new_LabelWidget(person_Icon, "identmenu.open");
1272 "\U0001f464", identityButtonMenuItems_, iElemCount(identityButtonMenuItems_)); 1353// "\U0001f464", identityButtonMenuItems_, iElemCount(identityButtonMenuItems_));
1273 setAlignVisually_LabelWidget(idMenu, iTrue); 1354 setAlignVisually_LabelWidget(idButton, iTrue);
1274 setId_Widget(addChildFlags_Widget(navBar, iClob(idMenu), collapse_WidgetFlag), "navbar.ident"); 1355 setId_Widget(addChildFlags_Widget(navBar, iClob(idButton), collapse_WidgetFlag), "navbar.ident");
1275 } 1356 }
1276 addChildFlags_Widget(navBar, iClob(new_Widget()), expand_WidgetFlag); 1357 addChildFlags_Widget(navBar, iClob(new_Widget()), expand_WidgetFlag);
1277 setId_Widget(addChildFlags_Widget(navBar, 1358 setId_Widget(addChildFlags_Widget(navBar,