summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-01-04 12:21:33 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-01-04 12:21:33 +0200
commit4ab1e49bcd1c90a47ddccab0afa37dc0eb5e2244 (patch)
tree1c40dc5d4736304520dc6c638ce7cf107a4fed6d
parent473558b969e2521c79dc9cd0df1e04749c6185ea (diff)
Added a client certificate import dialog
Todo: Drag-and-drop; the actual identity creation step.
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/app.c15
-rw-r--r--src/ui/certimportwidget.c225
-rw-r--r--src/ui/certimportwidget.h30
-rw-r--r--src/ui/documentwidget.c4
-rw-r--r--src/ui/documentwidget.h1
-rw-r--r--src/ui/util.c57
-rw-r--r--src/ui/util.h1
-rw-r--r--src/ui/window.c8
9 files changed, 272 insertions, 71 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0e29c3fa..b6456976 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -132,6 +132,8 @@ set (SOURCES
132 # User interface: 132 # User interface:
133 src/ui/bindingswidget.c 133 src/ui/bindingswidget.c
134 src/ui/bindingswidget.h 134 src/ui/bindingswidget.h
135 src/ui/certimportwidget.c
136 src/ui/certimportwidget.h
135 src/ui/color.c 137 src/ui/color.c
136 src/ui/color.h 138 src/ui/color.h
137 src/ui/command.c 139 src/ui/command.c
diff --git a/src/app.c b/src/app.c
index be968b36..82232afa 100644
--- a/src/app.c
+++ b/src/app.c
@@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
30#include "gmdocument.h" 30#include "gmdocument.h"
31#include "gmutil.h" 31#include "gmutil.h"
32#include "history.h" 32#include "history.h"
33#include "ui/certimportwidget.h"
33#include "ui/color.h" 34#include "ui/color.h"
34#include "ui/command.h" 35#include "ui/command.h"
35#include "ui/documentwidget.h" 36#include "ui/documentwidget.h"
@@ -994,14 +995,6 @@ static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) {
994 return iFalse; 995 return iFalse;
995} 996}
996 997
997iBool handleCertificateImportCommands_(iWidget *dlg, const char *cmd) {
998 if (equal_Command(cmd, "certimport.accept") || equal_Command(cmd, "cancel")) {
999 destroy_Widget(dlg);
1000 return iTrue;
1001 }
1002 return iFalse;
1003}
1004
1005iBool willUseProxy_App(const iRangecc scheme) { 998iBool willUseProxy_App(const iRangecc scheme) {
1006 return schemeProxy_App(scheme) != NULL; 999 return schemeProxy_App(scheme) != NULL;
1007} 1000}
@@ -1412,8 +1405,10 @@ iBool handleCommand_App(const char *cmd) {
1412 return iTrue; 1405 return iTrue;
1413 } 1406 }
1414 else if (equal_Command(cmd, "ident.import")) { 1407 else if (equal_Command(cmd, "ident.import")) {
1415 iWidget *dlg = makeCertificateImport_Widget(); 1408 iCertImportWidget *imp = new_CertImportWidget();
1416 setCommandHandler_Widget(dlg, handleCertificateImportCommands_); 1409 setPageContent_CertImportWidget(imp, sourceContent_DocumentWidget(document_App()));
1410 addChild_Widget(d->window->root, iClob(imp));
1411 postRefresh_App();
1417 return iTrue; 1412 return iTrue;
1418 } 1413 }
1419 else if (equal_Command(cmd, "ident.signin")) { 1414 else if (equal_Command(cmd, "ident.signin")) {
diff --git a/src/ui/certimportwidget.c b/src/ui/certimportwidget.c
new file mode 100644
index 00000000..51743e89
--- /dev/null
+++ b/src/ui/certimportwidget.c
@@ -0,0 +1,225 @@
1/* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. Redistributions in binary form must reproduce the above copyright notice,
9 this list of conditions and the following disclaimer in the documentation
10 and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#include "certimportwidget.h"
24#include "labelwidget.h"
25#include "inputwidget.h"
26#include "color.h"
27#include "text.h"
28#include "ui/util.h"
29#include "app.h"
30
31#include <the_Foundation/tlsrequest.h>
32#include <SDL_clipboard.h>
33
34iDefineObjectConstruction(CertImportWidget)
35
36struct Impl_CertImportWidget {
37 iWidget widget;
38 iLabelWidget *info;
39 iLabelWidget *crtLabel;
40 iLabelWidget *keyLabel;
41 iInputWidget *filename;
42 iInputWidget *notes;
43 iTlsCertificate *cert;
44};
45
46static const char *infoText_ = "Paste a PEM-encoded certificate and/or private key,\n"
47 "or drop a .crt/.key file on the window.";
48
49static iBool tryImport_CertImportWidget_(iCertImportWidget *d, const iBlock *data) {
50 iBool ok = iFalse;
51 iString pem;
52 initBlock_String(&pem, data);
53 iTlsCertificate *newCert = newPemKey_TlsCertificate(&pem, &pem);
54 const iBool gotNewCrt = !isEmpty_TlsCertificate(newCert);
55 const iBool gotNewKey = hasPrivateKey_TlsCertificate(newCert);
56 if (d->cert && (gotNewCrt ^ gotNewKey)) { /* One new part? Merge with existing. */
57 const iString *crt = collect_String(pem_TlsCertificate(gotNewCrt ? newCert : d->cert));
58 const iString *key = collect_String(privateKeyPem_TlsCertificate(gotNewKey ? newCert : d->cert));
59 delete_TlsCertificate(d->cert);
60 delete_TlsCertificate(newCert);
61 d->cert = newPemKey_TlsCertificate(crt, key);
62 ok = iTrue;
63 }
64 else if (gotNewCrt || gotNewKey) {
65 delete_TlsCertificate(d->cert);
66 d->cert = newCert;
67 ok = iTrue;
68 }
69 else {
70 delete_TlsCertificate(newCert);
71 }
72 deinit_String(&pem);
73 /* Update the labels. */ {
74 if (d->cert && !isEmpty_TlsCertificate(d->cert)) {
75 setTextCStr_LabelWidget(
76 d->crtLabel,
77 format_CStr("%s%s",
78 uiTextAction_ColorEscape,
79 cstrCollect_String(subject_TlsCertificate(d->cert))));
80 setFrameColor_Widget(as_Widget(d->crtLabel), uiTextAction_ColorId);
81 }
82 else {
83 setTextCStr_LabelWidget(d->crtLabel, uiTextCaution_ColorEscape "No Certificate");
84 setFrameColor_Widget(as_Widget(d->crtLabel), uiTextCaution_ColorId);
85 }
86 if (d->cert && hasPrivateKey_TlsCertificate(d->cert)) {
87 iString *fng = collect_String(
88 hexEncode_Block(collect_Block(privateKeyFingerprint_TlsCertificate(d->cert))));
89 insertData_Block(&fng->chars, size_String(fng) / 2, "\n", 1);
90 setTextCStr_LabelWidget(
91 d->keyLabel, format_CStr("%s%s", uiTextAction_ColorEscape, cstr_String(fng)));
92 setFrameColor_Widget(as_Widget(d->keyLabel), uiTextAction_ColorId);
93 }
94 else {
95 setTextCStr_LabelWidget(d->keyLabel, uiTextCaution_ColorEscape "No Private Key");
96 setFrameColor_Widget(as_Widget(d->keyLabel), uiTextCaution_ColorId);
97 }
98 }
99 return ok;
100}
101
102void init_CertImportWidget(iCertImportWidget *d) {
103 iWidget *w = as_Widget(d);
104 init_Widget(w);
105 setId_Widget(w, "certimport");
106 d->cert = NULL;
107 /* This should behave similar to sheets. */ {
108 setPadding1_Widget(w, 3 * gap_UI);
109 setFrameColor_Widget(w, uiSeparator_ColorId);
110 setBackgroundColor_Widget(w, uiBackground_ColorId);
111 setFlags_Widget(w,
112 mouseModal_WidgetFlag | keepOnTop_WidgetFlag | arrangeVertical_WidgetFlag |
113 arrangeSize_WidgetFlag | centerHorizontal_WidgetFlag |
114 overflowScrollable_WidgetFlag,
115 iTrue);
116 }
117 addChildFlags_Widget(w,
118 iClob(new_LabelWidget(uiHeading_ColorEscape "IMPORT IDENTITY", NULL)),
119 frameless_WidgetFlag);
120 d->info = addChildFlags_Widget(w, iClob(new_LabelWidget(infoText_, NULL)), frameless_WidgetFlag);
121 addChild_Widget(w, iClob(makePadding_Widget(gap_UI)));
122 d->crtLabel = new_LabelWidget("", NULL); {
123 setFont_LabelWidget(d->crtLabel, uiContent_FontId);
124 addChildFlags_Widget(w, iClob(d->crtLabel), 0);
125 setFrameColor_Widget(as_Widget(d->crtLabel), uiTextCaution_ColorId);
126 }
127 d->keyLabel = new_LabelWidget("", NULL); {
128 setFont_LabelWidget(d->keyLabel, uiContent_FontId);
129 addChild_Widget(w, iClob(makePadding_Widget(gap_UI)));
130 addChildFlags_Widget(w, iClob(d->keyLabel), 0);
131 setFrameColor_Widget(as_Widget(d->keyLabel), uiTextCaution_ColorId);
132 }
133 addChild_Widget(w, iClob(makePadding_Widget(gap_UI)));
134 iWidget *page = new_Widget(); {
135 setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue);
136 iWidget *headings = addChildFlags_Widget(
137 page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag);
138 iWidget *values = addChildFlags_Widget(
139 page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag);
140 addChild_Widget(headings, iClob(makeHeading_Widget("Save as:")));
141 addChild_Widget(values, iClob(d->filename = new_InputWidget(0)));
142 setHint_InputWidget(d->filename, "filename (no extension)");
143 addChild_Widget(headings, iClob(makeHeading_Widget("Notes:")));
144 addChild_Widget(values, iClob(d->notes = new_InputWidget(0)));
145 setHint_InputWidget(d->notes, "e.g., site name");
146 as_Widget(d->filename)->rect.size.x = gap_UI * 70;
147 as_Widget(d->notes)->rect.size.x = gap_UI * 70;
148 }
149 addChild_Widget(w, iClob(page));
150 arrange_Widget(w);
151 setSize_Widget(as_Widget(d->crtLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12));
152 setSize_Widget(as_Widget(d->keyLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12));
153 /* Buttons. */
154 iWidget *div = new_Widget(); {
155 setFlags_Widget(div, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue);
156 addChild_Widget(div, iClob(newKeyMods_LabelWidget("Cancel", SDLK_ESCAPE, 0, "cancel")));
157 iLabelWidget *accept = addChild_Widget(
158 div,
159 iClob(newKeyMods_LabelWidget(
160 uiTextAction_ColorEscape "Import", SDLK_RETURN, KMOD_PRIMARY, "certimport.accept")));
161 setFont_LabelWidget(accept, uiLabelBold_FontId);
162 }
163 addChild_Widget(w, iClob(div));
164 arrange_Widget(w);
165}
166
167void deinit_CertImportWidget(iCertImportWidget *d) {
168 delete_TlsCertificate(d->cert);
169}
170
171static iBool isComplete_CertImportWidget_(const iCertImportWidget *d) {
172 return d->cert && !isEmpty_TlsCertificate(d->cert) && hasPrivateKey_TlsCertificate(d->cert);
173}
174
175void setPageContent_CertImportWidget(iCertImportWidget *d, const iBlock *content) {
176 if (tryImport_CertImportWidget_(d, content)) {
177 setTextCStr_LabelWidget(d->info, infoText_);
178 if (isComplete_CertImportWidget_(d)) {
179 setFocus_Widget(as_Widget(d->filename));
180 }
181 }
182 else {
183 setTextCStr_LabelWidget(
184 d->info, format_CStr("No certificate/key found on the current page.\n%s", infoText_));
185 }
186 arrange_Widget(as_Widget(d));
187}
188
189static iBool processEvent_CertImportWidget_(iCertImportWidget *d, const SDL_Event *ev) {
190 iWidget *w = as_Widget(d);
191 if (ev->type == SDL_KEYDOWN) {
192 const int key = ev->key.keysym.sym;
193 const int mods = keyMods_Sym(ev->key.keysym.mod);
194 if (key == SDLK_v && mods == KMOD_PRIMARY) {
195 if (!tryImport_CertImportWidget_(
196 d, collect_Block(newCStr_Block(SDL_GetClipboardText())))) {
197 makeMessage_Widget(uiTextCaution_ColorEscape "PASTED FROM CLIPBOARD",
198 "No certificate or private key was found.");
199 }
200 postRefresh_App();
201 return iTrue;
202 }
203 }
204 if (isCommand_Widget(w, ev, "cancel")) {
205 destroy_Widget(w);
206 return iTrue;
207 }
208 if (isCommand_Widget(w, ev, "certimport.accept")) {
209 if (d->cert) {
210 destroy_Widget(w);
211 }
212 return iTrue;
213 }
214 return processEvent_Widget(w, ev);
215}
216
217static void draw_CertImportWidget_(const iCertImportWidget *d) {
218 const iWidget *w = constAs_Widget(d);
219 draw_Widget(w);
220}
221
222iBeginDefineSubclass(CertImportWidget, Widget)
223 .processEvent = (iAny *) processEvent_CertImportWidget_,
224 .draw = (iAny *) draw_CertImportWidget_,
225iEndDefineSubclass(CertImportWidget)
diff --git a/src/ui/certimportwidget.h b/src/ui/certimportwidget.h
new file mode 100644
index 00000000..65548846
--- /dev/null
+++ b/src/ui/certimportwidget.h
@@ -0,0 +1,30 @@
1/* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. Redistributions in binary form must reproduce the above copyright notice,
9 this list of conditions and the following disclaimer in the documentation
10 and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#pragma once
24
25#include "widget.h"
26
27iDeclareWidgetClass(CertImportWidget)
28iDeclareObjectConstruction(CertImportWidget)
29
30void setPageContent_CertImportWidget (iCertImportWidget *, const iBlock *);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 1c95593b..8603f1fe 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -3219,6 +3219,10 @@ const iGmDocument *document_DocumentWidget(const iDocumentWidget *d) {
3219 return d->doc; 3219 return d->doc;
3220} 3220}
3221 3221
3222const iBlock *sourceContent_DocumentWidget(const iDocumentWidget *d) {
3223 return &d->sourceContent;
3224}
3225
3222const iString *feedTitle_DocumentWidget(const iDocumentWidget *d) { 3226const iString *feedTitle_DocumentWidget(const iDocumentWidget *d) {
3223 if (!isEmpty_String(title_GmDocument(d->doc))) { 3227 if (!isEmpty_String(title_GmDocument(d->doc))) {
3224 return title_GmDocument(d->doc); 3228 return title_GmDocument(d->doc);
diff --git a/src/ui/documentwidget.h b/src/ui/documentwidget.h
index 55c0a6ed..91337a24 100644
--- a/src/ui/documentwidget.h
+++ b/src/ui/documentwidget.h
@@ -39,6 +39,7 @@ iHistory * history_DocumentWidget (iDocumentWidget *);
39 39
40const iString * url_DocumentWidget (const iDocumentWidget *); 40const iString * url_DocumentWidget (const iDocumentWidget *);
41iBool isRequestOngoing_DocumentWidget (const iDocumentWidget *); 41iBool isRequestOngoing_DocumentWidget (const iDocumentWidget *);
42const iBlock * sourceContent_DocumentWidget (const iDocumentWidget *);
42const iGmDocument * document_DocumentWidget (const iDocumentWidget *); 43const iGmDocument * document_DocumentWidget (const iDocumentWidget *);
43const iString * bookmarkTitle_DocumentWidget (const iDocumentWidget *); 44const iString * bookmarkTitle_DocumentWidget (const iDocumentWidget *);
44const iString * feedTitle_DocumentWidget (const iDocumentWidget *); 45const iString * feedTitle_DocumentWidget (const iDocumentWidget *);
diff --git a/src/ui/util.c b/src/ui/util.c
index d8a66bab..fd297ce2 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -744,8 +744,8 @@ iBool filePathHandler_(iWidget *dlg, const char *cmd) {
744 744
745iWidget *makeSheet_Widget(const char *id) { 745iWidget *makeSheet_Widget(const char *id) {
746 iWidget *sheet = new_Widget(); 746 iWidget *sheet = new_Widget();
747 setPadding1_Widget(sheet, 3 * gap_UI);
748 setId_Widget(sheet, id); 747 setId_Widget(sheet, id);
748 setPadding1_Widget(sheet, 3 * gap_UI);
749 setFrameColor_Widget(sheet, uiSeparator_ColorId); 749 setFrameColor_Widget(sheet, uiSeparator_ColorId);
750 setBackgroundColor_Widget(sheet, uiBackground_ColorId); 750 setBackgroundColor_Widget(sheet, uiBackground_ColorId);
751 setFlags_Widget(sheet, 751 setFlags_Widget(sheet,
@@ -1442,58 +1442,3 @@ iWidget *makeIdentityCreation_Widget(void) {
1442 centerSheet_Widget(dlg); 1442 centerSheet_Widget(dlg);
1443 return dlg; 1443 return dlg;
1444} 1444}
1445
1446iWidget *makeCertificateImport_Widget(void) {
1447 iWidget *dlg = makeSheet_Widget("certimport");
1448 setId_Widget(
1449 addChildFlags_Widget(dlg,
1450 iClob(new_LabelWidget(uiHeading_ColorEscape "IMPORT CERTIFICATE", NULL)),
1451 frameless_WidgetFlag),
1452 "certimport.heading");
1453 addAction_Widget(dlg, SDLK_v, KMOD_PRIMARY, "certimport.paste");
1454 addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI)));
1455 iLabelWidget *crt = new_LabelWidget("No Certificate", NULL);
1456 setFont_LabelWidget(crt, uiContent_FontId);
1457 addChildFlags_Widget(dlg, iClob(crt), 0);
1458 setFrameColor_Widget(as_Widget(crt), uiTextDim_ColorId);
1459 iLabelWidget *key = new_LabelWidget("No Private Key", NULL);
1460 setFont_LabelWidget(key, uiContent_FontId);
1461 addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI)));
1462 addChildFlags_Widget(dlg, iClob(key), 0);
1463 setFrameColor_Widget(as_Widget(key), uiTextDim_ColorId);
1464 addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI)));
1465 iWidget *page = new_Widget(); {
1466 setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue);
1467 iWidget *headings = addChildFlags_Widget(
1468 page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag);
1469 iWidget *values = addChildFlags_Widget(
1470 page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag);
1471 addChild_Widget(headings, iClob(makeHeading_Widget("Save as:")));
1472 void *inputs[2];
1473 addChild_Widget(values, iClob(inputs[0] = new_InputWidget(0)));
1474 setHint_InputWidget(inputs[0], "filename (no extension)");
1475 addChild_Widget(headings, iClob(makeHeading_Widget("Notes:")));
1476 addChild_Widget(values, iClob(inputs[1] = new_InputWidget(0)));
1477 setHint_InputWidget(inputs[1], "e.g., site name");
1478 as_Widget(inputs[0])->rect.size.x = gap_UI * 60;
1479 as_Widget(inputs[1])->rect.size.x = gap_UI * 60;
1480 }
1481 addChild_Widget(dlg, iClob(page));
1482 arrange_Widget(dlg);
1483 setSize_Widget(as_Widget(crt), init_I2(width_Widget(dlg), gap_UI * 15));
1484 setSize_Widget(as_Widget(key), init_I2(width_Widget(dlg), gap_UI * 15));
1485 /* Buttons. */
1486 iWidget *div = new_Widget(); {
1487 setFlags_Widget(div, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue);
1488 addChild_Widget(div, iClob(newKeyMods_LabelWidget("Cancel", SDLK_ESCAPE, 0, "cancel")));
1489 iLabelWidget *accept = addChild_Widget(
1490 div,
1491 iClob(newKeyMods_LabelWidget(
1492 uiTextAction_ColorEscape "Import", SDLK_RETURN, KMOD_PRIMARY, "certimport.accept")));
1493 setFont_LabelWidget(accept, uiLabelBold_FontId);
1494 }
1495 addChild_Widget(dlg, iClob(div));
1496 addChild_Widget(get_Window()->root, iClob(dlg));
1497 centerSheet_Widget(dlg);
1498 return dlg;
1499}
diff --git a/src/ui/util.h b/src/ui/util.h
index 5b701e08..a280fedb 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -204,5 +204,4 @@ iWidget * makePreferences_Widget (void);
204iWidget * makeBookmarkEditor_Widget (void); 204iWidget * makeBookmarkEditor_Widget (void);
205iWidget * makeBookmarkCreation_Widget (const iString *url, const iString *title, iChar icon); 205iWidget * makeBookmarkCreation_Widget (const iString *url, const iString *title, iChar icon);
206iWidget * makeIdentityCreation_Widget (void); 206iWidget * makeIdentityCreation_Widget (void);
207iWidget * makeCertificateImport_Widget(void);
208iWidget * makeFeedSettings_Widget (uint32_t bookmarkId); 207iWidget * makeFeedSettings_Widget (uint32_t bookmarkId);
diff --git a/src/ui/window.c b/src/ui/window.c
index a447340b..a4734675 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -163,23 +163,23 @@ static const iMenuItem viewMenuItems_[] = {
163 163
164static iMenuItem bookmarksMenuItems_[] = { 164static iMenuItem bookmarksMenuItems_[] = {
165 { "Bookmark This Page...", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, 165 { "Bookmark This Page...", SDLK_d, KMOD_PRIMARY, "bookmark.add" },
166 { "Subscribe to This Page...", subscribeToPage_KeyModifier, "feeds.subscribe" },
166 { "---", 0, 0, NULL }, 167 { "---", 0, 0, NULL },
167 { "Import All Links on Page...", 0, 0, "bookmark.links confirm:1" }, 168 { "Import All Links on Page...", 0, 0, "bookmark.links confirm:1" },
168 { "---", 0, 0, NULL }, 169 { "---", 0, 0, NULL },
169 { "List All", 0, 0, "open url:about:bookmarks" }, 170 { "List All", 0, 0, "open url:about:bookmarks" },
170 { "List by Tag", 0, 0, "open url:about:bookmarks?tags" }, 171 { "List by Tag", 0, 0, "open url:about:bookmarks?tags" },
171 { "List by Creation Time", 0, 0, "open url:about:bookmarks?created" }, 172 { "List by Creation Time", 0, 0, "open url:about:bookmarks?created" },
172 { "Refresh Remote Sources", 0, 0, "bookmarks.reload.remote" },
173 { "---", 0, 0, NULL },
174 { "Subscribe to This Page...", subscribeToPage_KeyModifier, "feeds.subscribe" },
175 { "Show Feed Entries", 0, 0, "open url:about:feeds" }, 173 { "Show Feed Entries", 0, 0, "open url:about:feeds" },
174 { "---", 0, 0, NULL },
175 { "Refresh Remote Bookmarks", 0, 0, "bookmarks.reload.remote" },
176 { "Refresh Feeds", SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh" }, 176 { "Refresh Feeds", SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh" },
177}; 177};
178 178
179static const iMenuItem identityMenuItems_[] = { 179static const iMenuItem identityMenuItems_[] = {
180 { "New Identity...", SDLK_n, KMOD_PRIMARY | KMOD_SHIFT, "ident.new" }, 180 { "New Identity...", SDLK_n, KMOD_PRIMARY | KMOD_SHIFT, "ident.new" },
181 { "---", 0, 0, NULL }, 181 { "---", 0, 0, NULL },
182 { "Import...", 0, 0, "ident.import" }, 182 { "Import...", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" },
183}; 183};
184 184
185static const iMenuItem helpMenuItems_[] = { 185static const iMenuItem helpMenuItems_[] = {