]> git.armaanb.net Git - chorizo.git/blob - src/browser.c
Implement private mode
[chorizo.git] / src / browser.c
1 #include <fcntl.h>
2 #include <sys/stat.h>
3 #include <webkit2/webkit2.h>
4
5 #include "downloads.h"
6
7 void client_destroy(GtkWidget *, gpointer);
8 WebKitWebView *client_new(const gchar *, WebKitWebView *, gboolean, gboolean);
9 WebKitWebView *client_new_request(WebKitWebView *, WebKitNavigationAction *,
10                                   gpointer);
11 void cooperation_setup(void);
12 void changed_load_progress(GObject *, GParamSpec *, gpointer);
13 void changed_favicon(GObject *, GParamSpec *, gpointer);
14 void changed_title(GObject *, GParamSpec *, gpointer);
15 void changed_uri(GObject *, GParamSpec *, gpointer);
16 gboolean crashed_web_view(WebKitWebView *, gpointer);
17 gboolean decide_policy(WebKitWebView *, WebKitPolicyDecision *,
18                        WebKitPolicyDecisionType, gpointer);
19 gchar *ensure_uri_scheme(const gchar *);
20 void grab_environment_configuration(void);
21 void grab_feeds_finished(GObject *, GAsyncResult *, gpointer);
22 void hover_web_view(WebKitWebView *, WebKitHitTestResult *, guint, gpointer);
23 void icon_location(GtkEntry *, GtkEntryIconPosition, GdkEvent *, gpointer);
24 void init_default_web_context(void);
25 gboolean key_common(GtkWidget *, GdkEvent *, gpointer);
26 gboolean key_location(GtkWidget *, GdkEvent *, gpointer);
27 gboolean key_tablabel(GtkWidget *, GdkEvent *, gpointer);
28 gboolean key_web_view(GtkWidget *, GdkEvent *, gpointer);
29 void mainwindow_setup(void);
30 void mainwindow_title(gint);
31 void notebook_switch_page(GtkNotebook *, GtkWidget *, guint, gpointer);
32 gboolean remote_msg(GIOChannel *, GIOCondition, gpointer);
33 void run_user_scripts(WebKitWebView *);
34 void search(gpointer, gint);
35 void show_web_view(WebKitWebView *, gpointer);
36 void trust_user_certs(WebKitWebContext *);
37 GKeyFile *get_ini(void);
38 GKeyFile *config;
39
40 struct Client {
41     gchar *external_handler_uri;
42     gchar *feed_html;
43     gchar *hover_uri;
44     GtkWidget *location;
45     GtkWidget *tabicon;
46     GtkWidget *tablabel;
47     GtkWidget *vbox;
48     GtkWidget *web_view;
49     gboolean focus_new_tab;
50 };
51
52 struct Configuration {
53     WebKitCookieAcceptPolicy cookie_policy;
54     const gchar *accepted_language[2];
55     gboolean cooperative_alone;
56     gboolean cooperative_instances;
57     gboolean enable_console_to_stdout;
58     gboolean javascript_disabled;
59     gboolean private;
60     gboolean spellcheck_disabled;
61     gchar *download_dir;
62     gchar *fifo_suffix;
63     gchar *history_file;
64     gchar *home_uri;
65     gchar *search_engine;
66     gchar *spellcheck_language;
67     gchar *user_agent;
68     gdouble global_zoom;
69     gint scroll_lines;
70     gint tab_width_chars;
71 } cfg;
72
73 struct MainWindow {
74     GtkWidget *win;
75     GtkWidget *notebook;
76 } mw;
77
78 gint clients = 0;
79 int cooperative_pipe_fp = 0;
80 gchar *search_text;
81
82 void
83 client_destroy(GtkWidget *widget, gpointer data) {
84     struct Client *c = (struct Client *)data;
85     gint idx;
86
87     g_signal_handlers_disconnect_by_func(G_OBJECT(c->web_view),
88                                          changed_load_progress, c);
89
90     idx = gtk_notebook_page_num(GTK_NOTEBOOK(mw.notebook), c->vbox);
91     if (idx == -1)
92         fprintf(stderr, __NAME__ ": Tab index was -1, bamboozled\n");
93     else
94         gtk_notebook_remove_page(GTK_NOTEBOOK(mw.notebook), idx);
95
96     free(c);
97     clients--;
98
99     quit_if_nothing_active();
100 }
101
102 WebKitWebView *
103 client_new(const gchar *uri, WebKitWebView *related_wv, gboolean show,
104            gboolean focus_tab) {
105     struct Client *c;
106     gchar *f;
107     GtkWidget *evbox, *tabbox;
108
109     if (uri != NULL && cfg.cooperative_instances && !cfg.cooperative_alone) {
110         f = ensure_uri_scheme(uri);
111         write(cooperative_pipe_fp, f, strlen(f));
112         write(cooperative_pipe_fp, "\n", 1);
113         g_free(f);
114         return NULL;
115     }
116
117     c = calloc(1, sizeof(struct Client));
118     if (!c) {
119         fprintf(stderr, __NAME__ ": fatal: calloc failed\n");
120         exit(EXIT_FAILURE);
121     }
122
123     c->focus_new_tab = focus_tab;
124
125     if (related_wv == NULL)
126         c->web_view = webkit_web_view_new();
127     else
128         c->web_view = webkit_web_view_new_with_related_view(related_wv);
129
130     webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(c->web_view),
131                                    cfg.global_zoom);
132     WebKitSettings *settings =
133         webkit_web_view_get_settings(WEBKIT_WEB_VIEW(c->web_view));
134     webkit_settings_set_enable_javascript(settings, !cfg.javascript_disabled);
135     g_signal_connect(G_OBJECT(c->web_view), "notify::favicon",
136                      G_CALLBACK(changed_favicon), c);
137     g_signal_connect(G_OBJECT(c->web_view), "notify::title",
138                      G_CALLBACK(changed_title), c);
139     g_signal_connect(G_OBJECT(c->web_view), "notify::uri",
140                      G_CALLBACK(changed_uri), c);
141     g_signal_connect(G_OBJECT(c->web_view), "notify::estimated-load-progress",
142                      G_CALLBACK(changed_load_progress), c);
143     g_signal_connect(G_OBJECT(c->web_view), "create",
144                      G_CALLBACK(client_new_request), NULL);
145     g_signal_connect(G_OBJECT(c->web_view), "close", G_CALLBACK(client_destroy),
146                      c);
147     g_signal_connect(G_OBJECT(c->web_view), "decide-policy",
148                      G_CALLBACK(decide_policy), NULL);
149     g_signal_connect(G_OBJECT(c->web_view), "key-press-event",
150                      G_CALLBACK(key_web_view), c);
151     g_signal_connect(G_OBJECT(c->web_view), "button-release-event",
152                      G_CALLBACK(key_web_view), c);
153     g_signal_connect(G_OBJECT(c->web_view), "scroll-event",
154                      G_CALLBACK(key_web_view), c);
155     g_signal_connect(G_OBJECT(c->web_view), "mouse-target-changed",
156                      G_CALLBACK(hover_web_view), c);
157     g_signal_connect(G_OBJECT(c->web_view), "web-process-crashed",
158                      G_CALLBACK(crashed_web_view), c);
159
160     if (cfg.user_agent != NULL) {
161         g_object_set(settings, "user-agent", cfg.user_agent, NULL);
162     }
163
164     if (cfg.enable_console_to_stdout)
165         webkit_settings_set_enable_write_console_messages_to_stdout(settings,
166                                                                     TRUE);
167
168     webkit_settings_set_enable_developer_extras(settings, TRUE);
169
170     GtkWidget *locbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
171     c->location = gtk_entry_new();
172     gtk_box_pack_start(GTK_BOX(locbox), c->location, TRUE, TRUE, 5);
173     if (cfg.private) {
174         GtkWidget *privindicator = gtk_label_new("Private mode");
175         gtk_widget_set_tooltip_text(
176             privindicator, "You are in private mode. No history, caches, or "
177                            "cookies will be saved beyond this session.");
178         gtk_box_pack_end(GTK_BOX(locbox), privindicator, FALSE, FALSE, 0);
179     }
180
181     g_signal_connect(G_OBJECT(c->location), "key-press-event",
182                      G_CALLBACK(key_location), c);
183     g_signal_connect(G_OBJECT(c->location), "icon-release",
184                      G_CALLBACK(icon_location), c);
185     /* XXX This is a workaround. Setting this to NULL (which is done in
186      * grab_feeds_finished() if no feed has been detected) adds a little
187      * padding left of the text. Not sure why. The point of this call
188      * right here is to have that padding right from the start. This
189      * avoids a graphical artifact. */
190     gtk_entry_set_icon_from_icon_name(GTK_ENTRY(c->location),
191                                       GTK_ENTRY_ICON_PRIMARY, NULL);
192
193     c->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
194     gtk_box_pack_start(GTK_BOX(c->vbox), locbox, FALSE, FALSE, 0);
195     gtk_box_pack_start(GTK_BOX(c->vbox), c->web_view, TRUE, TRUE, 0);
196     gtk_container_set_focus_child(GTK_CONTAINER(c->vbox), c->web_view);
197
198     c->tabicon =
199         gtk_image_new_from_icon_name("text-html", GTK_ICON_SIZE_SMALL_TOOLBAR);
200
201     c->tablabel = gtk_label_new(__NAME__);
202     gtk_label_set_ellipsize(GTK_LABEL(c->tablabel), PANGO_ELLIPSIZE_END);
203     gtk_label_set_width_chars(GTK_LABEL(c->tablabel), cfg.tab_width_chars);
204     gtk_widget_set_has_tooltip(c->tablabel, TRUE);
205
206     /* XXX I don't own a HiDPI screen, so I don't know if scale_factor
207      * does the right thing. */
208     tabbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL,
209                          5 * gtk_widget_get_scale_factor(mw.win));
210     gtk_box_pack_start(GTK_BOX(tabbox), c->tabicon, FALSE, FALSE, 0);
211     gtk_box_pack_start(GTK_BOX(tabbox), c->tablabel, TRUE, TRUE, 0);
212
213     evbox = gtk_event_box_new();
214     gtk_container_add(GTK_CONTAINER(evbox), tabbox);
215     g_signal_connect(G_OBJECT(evbox), "button-release-event",
216                      G_CALLBACK(key_tablabel), c);
217
218     gtk_widget_add_events(evbox, GDK_SCROLL_MASK);
219     g_signal_connect(G_OBJECT(evbox), "scroll-event", G_CALLBACK(key_tablabel),
220                      c);
221
222     /* For easy access, store a reference to our label. */
223     g_object_set_data(G_OBJECT(evbox), "chorizo-tab-label", c->tablabel);
224
225     /* This only shows the event box and the label inside, nothing else.
226      * Needed because the evbox/label is "internal" to the notebook and
227      * not part of the normal "widget tree" (IIUC). */
228     gtk_widget_show_all(evbox);
229
230     int page = gtk_notebook_get_current_page(GTK_NOTEBOOK(mw.notebook)) + 1;
231     gtk_notebook_insert_page(GTK_NOTEBOOK(mw.notebook), c->vbox, evbox, page);
232     gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(mw.notebook), c->vbox, TRUE);
233
234     if (show)
235         show_web_view(NULL, c);
236     else
237         g_signal_connect(G_OBJECT(c->web_view), "ready-to-show",
238                          G_CALLBACK(show_web_view), c);
239
240     if (uri != NULL) {
241         f = ensure_uri_scheme(uri);
242         webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), f);
243         g_free(f);
244     }
245
246     clients++;
247
248     return WEBKIT_WEB_VIEW(c->web_view);
249 }
250
251 WebKitWebView *
252 client_new_request(WebKitWebView *web_view,
253                    WebKitNavigationAction *navigation_action, gpointer data) {
254     return client_new(NULL, web_view, FALSE, FALSE);
255 }
256
257 void
258 cooperation_setup(void) {
259     GIOChannel *towatch;
260     gchar *fifofilename, *fifopath;
261
262     gchar *priv = (cfg.private) ? "-private" : "";
263     fifofilename =
264         g_strdup_printf("%s%s%s-%s", __NAME__, priv, ".fifo", cfg.fifo_suffix);
265     fifopath = g_build_filename(g_get_user_runtime_dir(), fifofilename, NULL);
266     g_free(fifofilename);
267
268     if (!g_file_test(fifopath, G_FILE_TEST_EXISTS))
269         mkfifo(fifopath, 0600);
270
271     cooperative_pipe_fp = open(fifopath, O_WRONLY | O_NONBLOCK);
272     if (!cooperative_pipe_fp) {
273         fprintf(stderr, __NAME__ ": Can't open FIFO at all.\n");
274     } else {
275         if (write(cooperative_pipe_fp, "", 0) == -1) {
276             /* Could not do an empty write to the FIFO which means
277       there's no one listening. */
278             close(cooperative_pipe_fp);
279             towatch = g_io_channel_new_file(fifopath, "r+", NULL);
280             g_io_add_watch(towatch, G_IO_IN, (GIOFunc)remote_msg, NULL);
281         } else {
282             cfg.cooperative_alone = FALSE;
283         }
284     }
285
286     g_free(fifopath);
287 }
288
289 void
290 changed_load_progress(GObject *obj, GParamSpec *pspec, gpointer data) {
291     struct Client *c = (struct Client *)data;
292     gdouble p;
293     gchar *grab_feeds =
294         "a = document.querySelectorAll('"
295         "    html > head > "
296         "link[rel=\"alternate\"][href][type=\"application/atom+xml\"],"
297         "    html > head > "
298         "link[rel=\"alternate\"][href][type=\"application/rss+xml\"]"
299         "');"
300         "if (a.length == 0)"
301         "    null;"
302         "else {"
303         "    out = '';"
304         "    for (i = 0; i < a.length; i++) {"
305         "        url = encodeURIComponent(a[i].href);"
306         "        if ('title' in a[i] && a[i].title != '')"
307         "            title = encodeURIComponent(a[i].title);"
308         "        else"
309         "            title = url;"
310         "        out += '<li><a href=\"' + url + '\">' + title + "
311         "'</a></li>';"
312         "    }"
313         "    out;"
314         "}";
315
316     p = webkit_web_view_get_estimated_load_progress(
317         WEBKIT_WEB_VIEW(c->web_view));
318     if (p == 1) {
319         p = 0;
320
321         /* The page has loaded fully. We now run the short JavaScript
322          * snippet above that operates on the DOM. It tries to grab all
323          * occurences of <link rel="alternate" ...>, i.e. RSS/Atom feed
324          * references. */
325         webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(c->web_view), grab_feeds,
326                                        NULL, grab_feeds_finished, c);
327
328         run_user_scripts(WEBKIT_WEB_VIEW(c->web_view));
329     }
330     gtk_entry_set_progress_fraction(GTK_ENTRY(c->location), p);
331 }
332
333 void
334 changed_favicon(GObject *obj, GParamSpec *pspec, gpointer data) {
335     struct Client *c = (struct Client *)data;
336     cairo_surface_t *f;
337     int w, h, w_should, h_should;
338     GdkPixbuf *pb, *pb_scaled;
339
340     f = webkit_web_view_get_favicon(WEBKIT_WEB_VIEW(c->web_view));
341     if (f == NULL) {
342         gtk_image_set_from_icon_name(GTK_IMAGE(c->tabicon), "text-html",
343                                      GTK_ICON_SIZE_SMALL_TOOLBAR);
344     } else {
345         w = cairo_image_surface_get_width(f);
346         h = cairo_image_surface_get_height(f);
347         pb = gdk_pixbuf_get_from_surface(f, 0, 0, w, h);
348         if (pb != NULL) {
349             w_should = 16 * gtk_widget_get_scale_factor(c->tabicon);
350             h_should = 16 * gtk_widget_get_scale_factor(c->tabicon);
351             pb_scaled = gdk_pixbuf_scale_simple(pb, w_should, h_should,
352                                                 GDK_INTERP_BILINEAR);
353             gtk_image_set_from_pixbuf(GTK_IMAGE(c->tabicon), pb_scaled);
354
355             g_object_unref(pb_scaled);
356             g_object_unref(pb);
357         }
358     }
359 }
360
361 void
362 changed_title(GObject *obj, GParamSpec *pspec, gpointer data) {
363     const gchar *t, *u;
364     struct Client *c = (struct Client *)data;
365
366     u = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
367     t = webkit_web_view_get_title(WEBKIT_WEB_VIEW(c->web_view));
368
369     u = u == NULL ? __NAME__ : u;
370     u = u[0] == 0 ? __NAME__ : u;
371
372     t = t == NULL ? u : t;
373     t = t[0] == 0 ? u : t;
374
375     gchar *name = malloc(strlen(t) + 4);
376     gboolean mute = webkit_web_view_get_is_muted(WEBKIT_WEB_VIEW(c->web_view));
377     gchar *muted = (mute) ? "[m] " : "";
378     sprintf(name, "%s%s", muted, t);
379
380     gtk_label_set_text(GTK_LABEL(c->tablabel), name);
381     g_free(name);
382     gtk_widget_set_tooltip_text(c->tablabel, t);
383     mainwindow_title(gtk_notebook_get_current_page(GTK_NOTEBOOK(mw.notebook)));
384 }
385
386 void
387 changed_uri(GObject *obj, GParamSpec *pspec, gpointer data) {
388     const gchar *t;
389     struct Client *c = (struct Client *)data;
390     FILE *fp;
391
392     t = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
393
394     /* When a web process crashes, we get a "notify::uri" signal, but we
395      * can no longer read a meaningful URI. It's just an empty string
396      * now. Not updating the location bar in this scenario is important,
397      * because we would override the "WEB PROCESS CRASHED" message. */
398     if (t != NULL && strlen(t) > 0) {
399         gtk_entry_set_text(GTK_ENTRY(c->location), t);
400
401         if (cfg.history_file != NULL && !cfg.private) {
402             fp = fopen(cfg.history_file, "a");
403             if (fp != NULL) {
404                 fprintf(fp, "%s\n", t);
405                 fclose(fp);
406             } else {
407                 perror(__NAME__ ": Error opening history file");
408             }
409         }
410     }
411 }
412
413 gboolean
414 crashed_web_view(WebKitWebView *web_view, gpointer data) {
415     gchar *t;
416     struct Client *c = (struct Client *)data;
417
418     t = g_strdup_printf("WEB PROCESS CRASHED: %s",
419                         webkit_web_view_get_uri(WEBKIT_WEB_VIEW(web_view)));
420     gtk_entry_set_text(GTK_ENTRY(c->location), t);
421     g_free(t);
422
423     return TRUE;
424 }
425
426 gboolean
427 decide_policy(WebKitWebView *web_view, WebKitPolicyDecision *decision,
428               WebKitPolicyDecisionType type, gpointer data) {
429     WebKitResponsePolicyDecision *r;
430
431     switch (type) {
432     case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
433         r = WEBKIT_RESPONSE_POLICY_DECISION(decision);
434         if (!webkit_response_policy_decision_is_mime_type_supported(r))
435             webkit_policy_decision_download(decision);
436         else
437             webkit_policy_decision_use(decision);
438         break;
439     default:
440         /* Use whatever default there is. */
441         return FALSE;
442     }
443     return TRUE;
444 }
445
446 gchar *
447 ensure_uri_scheme(const gchar *t) {
448     gchar *f, *fabs;
449
450     f = g_ascii_strdown(t, -1);
451     if (!g_str_has_prefix(f, "http:") && !g_str_has_prefix(f, "https:") &&
452         !g_str_has_prefix(f, "file:") && !g_str_has_prefix(f, "about:") &&
453         !g_str_has_prefix(f, "data:") && !g_str_has_prefix(f, "webkit:")) {
454         g_free(f);
455         fabs = realpath(t, NULL);
456         if (fabs != NULL) {
457             f = g_strdup_printf("file://%s", fabs);
458             free(fabs);
459         } else {
460             f = g_strdup_printf("http://%s", t);
461         }
462         return f;
463     } else
464         return g_strdup(t);
465 }
466
467 void
468 get_config(void) {
469     cfg.accepted_language[0] = NULL;
470     cfg.accepted_language[1] = NULL;
471     cfg.cooperative_alone = TRUE;
472     cfg.cooperative_alone = TRUE;
473     cfg.fifo_suffix = "main";
474
475     const char *e = g_getenv(__NAME_UPPERCASE__ "_FIFO_SUFFIX");
476     if (e != NULL)
477         cfg.fifo_suffix = g_strdup(e);
478
479     char *xdg_down = getenv("XDG_DOWNLOAD_DIR");
480     cfg.download_dir = (xdg_down) ? xdg_down : "/var/tmp";
481
482     config = get_ini();
483     cfg.accepted_language[0] =
484         g_key_file_get_string(config, "browser", "accepted_language", NULL);
485     cfg.history_file =
486         g_key_file_get_string(config, "browser", "history_file", NULL);
487     cfg.home_uri = g_key_file_get_string(config, "browser", "homepage", NULL);
488     cfg.home_uri = (cfg.home_uri) ? cfg.home_uri : "about:blank";
489     cfg.enable_console_to_stdout =
490         g_key_file_get_boolean(config, "browser", "console_to_stdout", NULL);
491     cfg.user_agent =
492         g_key_file_get_string(config, "browser", "user_agent", NULL);
493     cfg.javascript_disabled =
494         g_key_file_get_boolean(config, "browser", "javascript_disabled", NULL);
495     char *input_cookie_policy =
496         g_key_file_get_string(config, "browser", "cookie_policy", NULL);
497     cfg.cookie_policy = WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY;
498     if (input_cookie_policy) {
499         if (strcmp(input_cookie_policy, "all") == 0) {
500             cfg.cookie_policy = WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS;
501         } else if (strcmp(input_cookie_policy, "none") == 0) {
502             cfg.cookie_policy = WEBKIT_COOKIE_POLICY_ACCEPT_NEVER;
503         }
504     }
505
506     cfg.tab_width_chars =
507         g_key_file_get_integer(config, "ui", "tab_width", NULL);
508     cfg.tab_width_chars = (cfg.tab_width_chars) ? cfg.tab_width_chars : 20;
509     cfg.global_zoom = g_key_file_get_double(config, "ui", "zoom_level", NULL);
510     cfg.global_zoom = (cfg.global_zoom) ? cfg.global_zoom : 1.0;
511     cfg.search_engine =
512         g_key_file_get_string(config, "ui", "search_engine", NULL);
513     cfg.search_engine =
514         (cfg.search_engine) ? cfg.search_engine : "https://duckduckgo.com?q=";
515     cfg.spellcheck_disabled =
516         g_key_file_get_boolean(config, "ui", "spellcheck_disabled", NULL);
517     cfg.spellcheck_language =
518         g_key_file_get_string(config, "ui", "spellcheck_language", NULL);
519     cfg.spellcheck_language =
520         (cfg.spellcheck_language) ? cfg.spellcheck_language : "en_US";
521     cfg.scroll_lines =
522         g_key_file_get_integer(config, "ui", "scroll_lines", NULL);
523     cfg.scroll_lines = (cfg.scroll_lines) ? cfg.scroll_lines : 3;
524 }
525
526 void
527 grab_feeds_finished(GObject *object, GAsyncResult *result, gpointer data) {
528     struct Client *c = (struct Client *)data;
529     WebKitJavascriptResult *js_result;
530     JSCValue *value;
531     JSCException *exception;
532     GError *err = NULL;
533     gchar *str_value;
534
535     g_free(c->feed_html);
536     c->feed_html = NULL;
537
538     /* This was taken almost verbatim from the example in WebKit's
539      * documentation:
540      *
541      * https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html */
542
543     js_result = webkit_web_view_run_javascript_finish(WEBKIT_WEB_VIEW(object),
544                                                       result, &err);
545     if (!js_result) {
546         fprintf(stderr, __NAME__ ": Error running javascript: %s\n",
547                 err->message);
548         g_error_free(err);
549         return;
550     }
551
552     value = webkit_javascript_result_get_js_value(js_result);
553     if (jsc_value_is_string(value)) {
554         str_value = jsc_value_to_string(value);
555         exception = jsc_context_get_exception(jsc_value_get_context(value));
556         if (exception != NULL) {
557             fprintf(stderr, __NAME__ ": Error running javascript: %s\n",
558                     jsc_exception_get_message(exception));
559         } else {
560             c->feed_html = str_value;
561         }
562
563         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(c->location),
564                                           GTK_ENTRY_ICON_PRIMARY,
565                                           "application-rss+xml-symbolic");
566         gtk_entry_set_icon_activatable(GTK_ENTRY(c->location),
567                                        GTK_ENTRY_ICON_PRIMARY, TRUE);
568     } else {
569         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(c->location),
570                                           GTK_ENTRY_ICON_PRIMARY, NULL);
571     }
572
573     webkit_javascript_result_unref(js_result);
574 }
575
576 void
577 hover_web_view(WebKitWebView *web_view, WebKitHitTestResult *ht,
578                guint modifiers, gpointer data) {
579     struct Client *c = (struct Client *)data;
580     const char *to_show;
581
582     g_free(c->hover_uri);
583
584     if (webkit_hit_test_result_context_is_link(ht)) {
585         to_show = webkit_hit_test_result_get_link_uri(ht);
586         c->hover_uri = g_strdup(to_show);
587     } else {
588         to_show = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
589         c->hover_uri = NULL;
590     }
591
592     if (!gtk_widget_is_focus(c->location))
593         gtk_entry_set_text(GTK_ENTRY(c->location), to_show);
594 }
595
596 void
597 icon_location(GtkEntry *entry, GtkEntryIconPosition icon_pos, GdkEvent *event,
598               gpointer data) {
599     struct Client *c = (struct Client *)data;
600     gchar *d;
601     gchar *data_template = "data:text/html,"
602                            "<!DOCTYPE html>"
603                            "<html>"
604                            "    <head>"
605                            "        <meta charset=\"UTF-8\">"
606                            "        <title>Feeds</title>"
607                            "    </head>"
608                            "    <body>"
609                            "        <p>Feeds found on this page:</p>"
610                            "        <ul>"
611                            "        %s"
612                            "        </ul>"
613                            "    </body>"
614                            "</html>";
615
616     if (c->feed_html != NULL) {
617         /* What we're actually trying to do is show a simple HTML page
618          * that lists all the feeds on the current page. The function
619          * webkit_web_view_load_html() looks like the proper way to do
620          * that. Sad thing is, it doesn't create a history entry, but
621          * instead simply replaces the content of the current page. This
622          * is not what we want.
623          *
624          * RFC 2397 [0] defines the data URI scheme [1]. We abuse this
625          * mechanism to show my custom HTML snippet *and* create a
626          * history entry.
627          *
628          * [0]: https://tools.ietf.org/html/rfc2397
629          * [1]: https://en.wikipedia.org/wiki/Data_URI_scheme */
630         d = g_strdup_printf(data_template, c->feed_html);
631         webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), d);
632         g_free(d);
633     }
634 }
635
636 void
637 init_default_web_context(void) {
638     gchar *p;
639     WebKitWebContext *wc;
640     WebKitCookieManager *cm;
641
642     wc = (cfg.private) ? webkit_web_context_new_ephemeral()
643                        : webkit_web_context_get_default();
644
645     p = g_build_filename(g_get_user_config_dir(), __NAME__, "adblock", NULL);
646     webkit_web_context_set_sandbox_enabled(wc, TRUE);
647     webkit_web_context_add_path_to_sandbox(wc, p, TRUE);
648     g_free(p);
649
650     WebKitProcessModel model =
651         WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES;
652     webkit_web_context_set_process_model(wc, model);
653
654     p = g_build_filename(g_get_user_data_dir(), __NAME__, "web_extensions",
655                          NULL);
656     webkit_web_context_set_web_extensions_directory(wc, p);
657     g_free(p);
658
659     if (cfg.accepted_language[0] != NULL)
660         webkit_web_context_set_preferred_languages(wc, cfg.accepted_language);
661
662     g_signal_connect(G_OBJECT(wc), "download-started",
663                      G_CALLBACK(download_start), &cfg);
664
665     trust_user_certs(wc);
666
667     cm = webkit_web_context_get_cookie_manager(wc);
668     webkit_cookie_manager_set_accept_policy(cm, cfg.cookie_policy);
669
670     if (!cfg.private) {
671         webkit_web_context_set_favicon_database_directory(wc, NULL);
672
673         gchar *fname = g_build_filename("/", g_get_user_cache_dir(), __NAME__,
674                                         "cookies.db", NULL);
675         WebKitCookiePersistentStorage type =
676             WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE;
677         webkit_cookie_manager_set_persistent_storage(cm, fname, type);
678     }
679
680     const gchar *const languages[2] = {(const gchar *)cfg.spellcheck_language,
681                                        NULL};
682     webkit_web_context_set_spell_checking_languages(wc, languages);
683     webkit_web_context_set_spell_checking_enabled(wc, !cfg.spellcheck_disabled);
684 }
685
686 void
687 search(gpointer data, gint direction) {
688     struct Client *c = (struct Client *)data;
689     WebKitWebView *web_view = WEBKIT_WEB_VIEW(c->web_view);
690     WebKitFindController *fc = webkit_web_view_get_find_controller(web_view);
691
692     if (search_text == NULL)
693         return;
694
695     switch (direction) {
696     case 0:
697         webkit_find_controller_search(fc, search_text,
698                                       WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE |
699                                           WEBKIT_FIND_OPTIONS_WRAP_AROUND,
700                                       G_MAXUINT);
701         break;
702     case 1:
703         webkit_find_controller_search_next(fc);
704         break;
705     case -1:
706         webkit_find_controller_search_previous(fc);
707         break;
708     case 2:
709         webkit_find_controller_search_finish(fc);
710         break;
711     }
712 }
713
714 void
715 search_init(struct Client *c, int direction) {
716     gtk_widget_grab_focus(c->location);
717     const gchar *contents = gtk_entry_get_text(GTK_ENTRY(c->location));
718     if (strcspn(contents, "s/")) {
719         gtk_entry_set_text(GTK_ENTRY(c->location), "s/");
720     }
721     gtk_editable_set_position(GTK_EDITABLE(c->location), -1);
722     search(c, 0);
723     search(c, -1);
724     search(c, direction);
725 }
726
727 int
728 def_key(char *key, unsigned int def) {
729     char *conf = g_key_file_get_string(config, "keybindings", key, NULL);
730     return (conf) ? gdk_keyval_from_name((conf) ? conf : NULL) : def;
731 }
732
733 gboolean
734 key_common(GtkWidget *widget, GdkEvent *event, gpointer data) {
735     struct Client *c = (struct Client *)data;
736     gdouble now;
737     gchar *f;
738
739     if (event->type == GDK_KEY_PRESS) {
740         if (((GdkEventKey *)event)->state & GDK_CONTROL_MASK) {
741             WebKitSettings *settings =
742                 webkit_web_view_get_settings(WEBKIT_WEB_VIEW(c->web_view));
743             gboolean js = webkit_settings_get_enable_javascript(settings);
744             int key = ((GdkEventKey *)event)->keyval;
745             if (def_key("download_manager", GDK_KEY_y) == key) {
746                 downloadmanager_show();
747                 return TRUE;
748             } else if (def_key("history_back", GDK_KEY_h) == key) {
749                 webkit_web_view_go_back(WEBKIT_WEB_VIEW(c->web_view));
750                 return TRUE;
751             } else if (def_key("history_forwards", GDK_KEY_l) == key) {
752                 webkit_web_view_go_forward(WEBKIT_WEB_VIEW(c->web_view));
753                 return TRUE;
754             } else if (def_key("location", GDK_KEY_t) == key) {
755                 gtk_widget_grab_focus(c->location);
756                 const char *uri =
757                     webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
758                 const char *goal = (uri) ? uri : "https://";
759                 gtk_entry_set_text(GTK_ENTRY(c->location), goal);
760                 gtk_editable_set_position(GTK_EDITABLE(c->location), -1);
761                 return TRUE;
762             } else if (def_key("print", GDK_KEY_Print) == key) {
763                 WebKitPrintOperation *operation =
764                     webkit_print_operation_new(WEBKIT_WEB_VIEW(c->web_view));
765                 GtkWidget *toplevel = gtk_widget_get_toplevel(mw.win);
766                 webkit_print_operation_run_dialog(operation,
767                                                   GTK_WINDOW(toplevel));
768                 return TRUE;
769             } else if (def_key("quit", GDK_KEY_g) == key) {
770                 search(c, 2);
771                 gtk_widget_grab_focus(c->web_view);
772                 const gchar *uri =
773                     webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
774                 gtk_entry_set_text(GTK_ENTRY(c->location), uri);
775                 gtk_editable_set_position(GTK_EDITABLE(c->location), -1);
776                 webkit_web_view_run_javascript(
777                     WEBKIT_WEB_VIEW(c->web_view),
778                     "window.getSelection().removeAllRanges();"
779                     "document.activeElement.blur();",
780                     NULL, NULL, c);
781                 return TRUE;
782             } else if (def_key("reload", GDK_KEY_e) == key) {
783                 webkit_web_view_reload_bypass_cache(
784                     WEBKIT_WEB_VIEW(c->web_view));
785                 return TRUE;
786             } else if (def_key("scroll_line_down", GDK_KEY_j) == key) {
787                 for (int i = 0; i <= cfg.scroll_lines - 1; i++) {
788                     event->key.keyval = GDK_KEY_Down;
789                     gdk_event_put(event);
790                 }
791                 return TRUE;
792             } else if (def_key("scroll_line_up", GDK_KEY_k) == key) {
793                 for (int i = 0; i <= cfg.scroll_lines - 1; i++) {
794                     event->key.keyval = GDK_KEY_Up;
795                     gdk_event_put(event);
796                 }
797                 return TRUE;
798             } else if (def_key("scroll_page_down", GDK_KEY_f) == key) {
799                 event->key.keyval = GDK_KEY_Page_Down;
800                 gdk_event_put(event);
801                 return TRUE;
802             } else if (def_key("scroll_page_up", GDK_KEY_b) == key) {
803                 event->key.keyval = GDK_KEY_Page_Up;
804                 gdk_event_put(event);
805                 return TRUE;
806             } else if (def_key("search_forwards", GDK_KEY_s) == key) {
807                 search_init(c, 1);
808                 return TRUE;
809             } else if (def_key("search_backwards", GDK_KEY_r) == key) {
810                 search_init(c, -1);
811                 return TRUE;
812             } else if (def_key("tab_close", GDK_KEY_q) == key) {
813                 client_destroy(NULL, c);
814                 return TRUE;
815             } else if (def_key("tab_switch_1", GDK_KEY_1) == key) {
816                 gtk_notebook_set_current_page(GTK_NOTEBOOK(mw.notebook), 0);
817                 return TRUE;
818             } else if (def_key("tab_switch_2", GDK_KEY_2) == key) {
819                 gtk_notebook_set_current_page(GTK_NOTEBOOK(mw.notebook), 1);
820                 return TRUE;
821             } else if (def_key("tab_switch_3", GDK_KEY_3) == key) {
822                 gtk_notebook_set_current_page(GTK_NOTEBOOK(mw.notebook), 2);
823                 return TRUE;
824             } else if (def_key("tab_switch_4", GDK_KEY_4) == key) {
825                 gtk_notebook_set_current_page(GTK_NOTEBOOK(mw.notebook), 3);
826                 return TRUE;
827             } else if (def_key("tab_switch_5", GDK_KEY_5) == key) {
828                 gtk_notebook_set_current_page(GTK_NOTEBOOK(mw.notebook), 4);
829                 return TRUE;
830             } else if (def_key("tab_switch_6", GDK_KEY_6) == key) {
831                 gtk_notebook_set_current_page(GTK_NOTEBOOK(mw.notebook), 5);
832                 return TRUE;
833             } else if (def_key("tab_switch_7", GDK_KEY_7) == key) {
834                 gtk_notebook_set_current_page(GTK_NOTEBOOK(mw.notebook), 6);
835                 return TRUE;
836             } else if (def_key("tab_switch_8", GDK_KEY_8) == key) {
837                 gtk_notebook_set_current_page(GTK_NOTEBOOK(mw.notebook), 7);
838                 return TRUE;
839             } else if (def_key("tab_switch_9", GDK_KEY_9) == key) {
840                 gtk_notebook_set_current_page(GTK_NOTEBOOK(mw.notebook), 8);
841                 return TRUE;
842             } else if (def_key("tab_previous", GDK_KEY_u) == key) {
843                 gtk_notebook_prev_page(GTK_NOTEBOOK(mw.notebook));
844                 return TRUE;
845             } else if (def_key("tab_mute", GDK_KEY_m) == key) {
846                 gboolean muted =
847                     webkit_web_view_get_is_muted(WEBKIT_WEB_VIEW(c->web_view));
848                 webkit_web_view_set_is_muted(WEBKIT_WEB_VIEW(c->web_view),
849                                              !muted);
850                 changed_title(G_OBJECT(c->web_view), NULL, c);
851                 return TRUE;
852             } else if (def_key("tab_new", GDK_KEY_w) == key) {
853                 f = ensure_uri_scheme(cfg.home_uri);
854                 client_new(f, NULL, TRUE, TRUE);
855                 g_free(f);
856                 return TRUE;
857             } else if (def_key("tab_next", GDK_KEY_i) == key) {
858                 gtk_notebook_next_page(GTK_NOTEBOOK(mw.notebook));
859                 return TRUE;
860             } else if (def_key("toggle_js", GDK_KEY_o) == key) {
861                 webkit_settings_set_enable_javascript(settings,
862                                                       (js) ? FALSE : TRUE);
863                 webkit_web_view_set_settings(WEBKIT_WEB_VIEW(c->web_view),
864                                              settings);
865                 return TRUE;
866             } else if (def_key("web_search", GDK_KEY_d) == key) {
867                 gtk_widget_grab_focus(c->location);
868                 gtk_entry_set_text(GTK_ENTRY(c->location), "w/");
869                 gtk_editable_set_position(GTK_EDITABLE(c->location), -1);
870                 return TRUE;
871             } else if (def_key("zoom_in", GDK_KEY_equal) == key) {
872                 now = webkit_web_view_get_zoom_level(
873                     WEBKIT_WEB_VIEW(c->web_view));
874                 webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(c->web_view),
875                                                now + 0.1);
876                 return TRUE;
877             } else if (def_key("zoom_out", GDK_KEY_minus) == key) {
878                 now = webkit_web_view_get_zoom_level(
879                     WEBKIT_WEB_VIEW(c->web_view));
880                 webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(c->web_view),
881                                                now - 0.1);
882                 return TRUE;
883             } else if (def_key("zoom_reset", GDK_KEY_0) == key) {
884                 webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(c->web_view),
885                                                cfg.global_zoom);
886                 return TRUE;
887             }
888         }
889     }
890     return FALSE;
891 }
892
893 gboolean
894 key_location(GtkWidget *widget, GdkEvent *event, gpointer data) {
895     struct Client *c = (struct Client *)data;
896     const gchar *t;
897     gchar *f;
898
899     if (key_common(widget, event, data))
900         return TRUE;
901
902     if (event->type == GDK_KEY_PRESS) {
903         int key = ((GdkEventKey *)event)->keyval;
904         if ((GDK_KEY_KP_Enter == key) || (GDK_KEY_Return == key)) {
905             gtk_widget_grab_focus(c->web_view);
906             t = gtk_entry_get_text(GTK_ENTRY(c->location));
907             if (t != NULL && t[0] == 's' && t[1] == '/') {
908                 if (search_text != NULL)
909                     g_free(search_text);
910                 search_text = g_strdup(t + 2);
911                 search(c, 0);
912             } else if (t != NULL && t[0] == 'w' && t[1] == '/') {
913                 const char *engine = cfg.search_engine;
914                 int len = strlen(engine) + strlen(t) - 2;
915                 char *f = (char *)malloc(len);
916                 snprintf(f, len + 1, "%s%s", engine, t + 2);
917                 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), f);
918                 free(f);
919             } else {
920                 f = ensure_uri_scheme(t);
921                 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), f);
922                 g_free(f);
923             }
924             return TRUE;
925         } else if (GDK_KEY_Escape == key) {
926             t = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
927             gtk_entry_set_text(GTK_ENTRY(c->location), (t == NULL) ? "" : t);
928             return TRUE;
929         }
930     }
931     return FALSE;
932 }
933
934 gboolean
935 key_tablabel(GtkWidget *widget, GdkEvent *event, gpointer data) {
936     GdkScrollDirection direction;
937
938     if (event->type == GDK_BUTTON_RELEASE) {
939         switch (((GdkEventButton *)event)->button) {
940         case 2:
941             client_destroy(NULL, data);
942             return TRUE;
943         }
944     } else if (event->type == GDK_SCROLL) {
945         gdk_event_get_scroll_direction(event, &direction);
946         switch (direction) {
947         case GDK_SCROLL_UP:
948             gtk_notebook_prev_page(GTK_NOTEBOOK(mw.notebook));
949             break;
950         case GDK_SCROLL_DOWN:
951             gtk_notebook_next_page(GTK_NOTEBOOK(mw.notebook));
952             break;
953         default:
954             break;
955         }
956         return TRUE;
957     }
958     return FALSE;
959 }
960
961 gboolean
962 key_web_view(GtkWidget *widget, GdkEvent *event, gpointer data) {
963     struct Client *c = (struct Client *)data;
964     gdouble dx, dy;
965     gfloat z;
966
967     if (key_common(widget, event, data))
968         return TRUE;
969
970     if (event->type == GDK_KEY_PRESS) {
971         if (((GdkEventKey *)event)->keyval == GDK_KEY_Escape) {
972             webkit_web_view_stop_loading(WEBKIT_WEB_VIEW(c->web_view));
973             gtk_entry_set_progress_fraction(GTK_ENTRY(c->location), 0);
974         }
975     } else if (event->type == GDK_BUTTON_RELEASE) {
976         switch (((GdkEventButton *)event)->button) {
977         case 2:
978             if (c->hover_uri != NULL) {
979                 client_new(c->hover_uri, NULL, TRUE, FALSE);
980                 return TRUE;
981             }
982             break;
983         case 8:
984             webkit_web_view_go_back(WEBKIT_WEB_VIEW(c->web_view));
985             return TRUE;
986         case 9:
987             webkit_web_view_go_forward(WEBKIT_WEB_VIEW(c->web_view));
988             return TRUE;
989         }
990     } else if (event->type == GDK_SCROLL) {
991         event->scroll.delta_y *= cfg.scroll_lines;
992         if (((GdkEventScroll *)event)->state & GDK_CONTROL_MASK) {
993             gdk_event_get_scroll_deltas(event, &dx, &dy);
994             z = webkit_web_view_get_zoom_level(WEBKIT_WEB_VIEW(c->web_view));
995             z += -dy * 0.1;
996             z = dx != 0 ? cfg.global_zoom : z;
997             webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(c->web_view), z);
998             return TRUE;
999         }
1000     }
1001
1002     return FALSE;
1003 }
1004
1005 void
1006 mainwindow_setup(void) {
1007     mw.win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1008     gtk_window_set_default_size(GTK_WINDOW(mw.win), 800, 600);
1009     g_signal_connect(G_OBJECT(mw.win), "destroy", gtk_main_quit, NULL);
1010
1011     gchar *priv = (cfg.private) ? "-private" : "";
1012     gchar *title = malloc(strlen(priv) + strlen(__NAME__));
1013     sprintf(title, "%s%s", __NAME__, priv);
1014     gtk_window_set_title(GTK_WINDOW(mw.win), title);
1015
1016     mw.notebook = gtk_notebook_new();
1017     gtk_notebook_set_scrollable(GTK_NOTEBOOK(mw.notebook), TRUE);
1018     gtk_container_add(GTK_CONTAINER(mw.win), mw.notebook);
1019     g_signal_connect(G_OBJECT(mw.notebook), "switch-page",
1020                      G_CALLBACK(notebook_switch_page), NULL);
1021 }
1022
1023 void
1024 mainwindow_title(gint idx) {
1025     GtkWidget *child, *widg, *tablabel;
1026     const gchar *text;
1027
1028     child = gtk_notebook_get_nth_page(GTK_NOTEBOOK(mw.notebook), idx);
1029     if (child == NULL)
1030         return;
1031
1032     widg = gtk_notebook_get_tab_label(GTK_NOTEBOOK(mw.notebook), child);
1033     tablabel =
1034         (GtkWidget *)g_object_get_data(G_OBJECT(widg), "chorizo-tab-label");
1035     text = gtk_label_get_text(GTK_LABEL(tablabel));
1036     gtk_window_set_title(GTK_WINDOW(mw.win), text);
1037 }
1038
1039 void
1040 notebook_switch_page(GtkNotebook *nb, GtkWidget *p, guint idx, gpointer data) {
1041     mainwindow_title(idx);
1042 }
1043
1044 gboolean
1045 quit_if_nothing_active(void) {
1046     if (clients == 0) {
1047         if (downloads == 0) {
1048             gtk_main_quit();
1049             return TRUE;
1050         } else {
1051             downloadmanager_show();
1052         }
1053     }
1054
1055     return FALSE;
1056 }
1057
1058 gboolean
1059 remote_msg(GIOChannel *channel, GIOCondition condition, gpointer data) {
1060     gchar *uri = NULL;
1061
1062     g_io_channel_read_line(channel, &uri, NULL, NULL, NULL);
1063     if (uri) {
1064         g_strstrip(uri);
1065         client_new(uri, NULL, TRUE, TRUE);
1066         g_free(uri);
1067     }
1068     return TRUE;
1069 }
1070
1071 void
1072 run_user_scripts(WebKitWebView *web_view) {
1073     gchar *base = NULL, *path = NULL, *contents = NULL;
1074     const gchar *entry = NULL;
1075     GDir *scriptdir = NULL;
1076
1077     base =
1078         g_build_filename(g_get_user_data_dir(), __NAME__, "user-scripts", NULL);
1079     scriptdir = g_dir_open(base, 0, NULL);
1080     if (scriptdir != NULL) {
1081         while ((entry = g_dir_read_name(scriptdir)) != NULL) {
1082             path = g_build_filename(base, entry, NULL);
1083             char *jscmd = malloc(strlen(path) + 36);
1084             sprintf(jscmd, "console.log(\"Running userscript %s\");", path);
1085             if (g_str_has_suffix(path, ".js")) {
1086                 if (g_file_get_contents(path, &contents, NULL, NULL)) {
1087                     webkit_web_view_run_javascript(web_view, jscmd, NULL, NULL,
1088                                                    NULL);
1089                     webkit_web_view_run_javascript(web_view, contents, NULL,
1090                                                    NULL, NULL);
1091                     g_free(contents);
1092                     g_free(jscmd);
1093                 }
1094             }
1095             g_free(path);
1096         }
1097         g_dir_close(scriptdir);
1098     }
1099
1100     g_free(base);
1101 }
1102
1103 void
1104 show_web_view(WebKitWebView *web_view, gpointer data) {
1105     struct Client *c = (struct Client *)data;
1106     gint idx;
1107
1108     (void)web_view;
1109
1110     gtk_widget_show_all(mw.win);
1111
1112     if (c->focus_new_tab) {
1113         idx = gtk_notebook_page_num(GTK_NOTEBOOK(mw.notebook), c->vbox);
1114         if (idx != -1)
1115             gtk_notebook_set_current_page(GTK_NOTEBOOK(mw.notebook), idx);
1116
1117         gtk_widget_grab_focus(c->web_view);
1118     }
1119 }
1120
1121 void
1122 trust_user_certs(WebKitWebContext *wc) {
1123     GTlsCertificate *cert;
1124     const gchar *basedir, *file, *absfile;
1125     GDir *dir;
1126
1127     basedir = g_build_filename(g_get_user_data_dir(), __NAME__, "certs", NULL);
1128     dir = g_dir_open(basedir, 0, NULL);
1129     if (dir != NULL) {
1130         file = g_dir_read_name(dir);
1131         while (file != NULL) {
1132             absfile = g_build_filename(g_get_user_data_dir(), __NAME__, "certs",
1133                                        file, NULL);
1134             cert = g_tls_certificate_new_from_file(absfile, NULL);
1135             if (cert == NULL)
1136                 fprintf(stderr, __NAME__ ": Could not load trusted cert '%s'\n",
1137                         file);
1138             else
1139                 webkit_web_context_allow_tls_certificate_for_host(wc, cert,
1140                                                                   file);
1141             file = g_dir_read_name(dir);
1142         }
1143         g_dir_close(dir);
1144     }
1145 }
1146
1147 GKeyFile *
1148 get_ini(void) {
1149     GKeyFileFlags flags = G_KEY_FILE_NONE;
1150     config = g_key_file_new();
1151
1152     // Load user config
1153     if (!g_key_file_load_from_file(config,
1154                                    g_build_filename(g_get_user_config_dir(),
1155                                                     __NAME__, "chorizo.ini",
1156                                                     NULL),
1157                                    flags, NULL)) {
1158         // Load global config
1159         if (!g_key_file_load_from_file(config, "/etc/chorizo.ini", flags,
1160                                        NULL)) {
1161             fprintf(stderr, "Could not load chorizo.ini");
1162         }
1163     }
1164     return config;
1165 }
1166
1167 int
1168 main(int argc, char **argv) {
1169     int opt, i;
1170
1171     while ((opt = getopt(argc, argv, "Cpv")) != -1) {
1172         switch (opt) {
1173         case 'C':
1174             cfg.cooperative_instances = FALSE;
1175             break;
1176         case 'p':
1177             cfg.private = true;
1178             break;
1179         case 'v':
1180             printf("%s %s\n", __NAME__, VERSION);
1181             exit(0);
1182         default:
1183             fprintf(stderr, "Usage: " __NAME__ " [OPTION]... [URI]...\n");
1184             exit(EXIT_FAILURE);
1185         }
1186     }
1187
1188     gtk_init(&argc, &argv);
1189     get_config();
1190
1191     if (cfg.cooperative_instances)
1192         cooperation_setup();
1193
1194     if (!cfg.cooperative_instances || cfg.cooperative_alone)
1195         init_default_web_context();
1196
1197     downloadmanager_setup();
1198     mainwindow_setup();
1199
1200     if (optind >= argc) {
1201         client_new(cfg.home_uri, NULL, TRUE, TRUE);
1202     } else {
1203         for (i = optind; i < argc; i++)
1204             client_new(argv[i], NULL, TRUE, TRUE);
1205     }
1206
1207     if (!cfg.cooperative_instances || cfg.cooperative_alone)
1208         gtk_main();
1209
1210     exit(EXIT_SUCCESS);
1211 }