]> git.armaanb.net Git - chorizo.git/blob - browser.c
Make tab position customizable
[chorizo.git] / browser.c
1 #include <limits.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <fcntl.h>
7 #include <string.h>
8
9 #include <gtk/gtk.h>
10 #include <gtk/gtkx.h>
11 #include <gdk/gdkkeysyms.h>
12 #include <gio/gio.h>
13 #include <webkit2/webkit2.h>
14 #include <JavaScriptCore/JavaScript.h>
15
16
17 static gboolean button_tablabel(GtkWidget *, GdkEvent *, gpointer);
18 static void client_destroy(GtkWidget *, gpointer);
19 static WebKitWebView *client_new(const gchar *, WebKitWebView *, gboolean);
20 static WebKitWebView *client_new_request(WebKitWebView *, WebKitNavigationAction *,
21                                          gpointer);
22 static void cooperation_setup(void);
23 static void changed_download_progress(GObject *, GParamSpec *, gpointer);
24 static void changed_load_progress(GObject *, GParamSpec *, gpointer);
25 static void changed_title(GObject *, GParamSpec *, gpointer);
26 static void changed_uri(GObject *, GParamSpec *, gpointer);
27 static gboolean crashed_web_view(WebKitWebView *, gpointer);
28 static gboolean decide_policy(WebKitWebView *, WebKitPolicyDecision *,
29                               WebKitPolicyDecisionType, gpointer);
30 static gboolean download_handle(WebKitDownload *, gchar *, gpointer);
31 static void download_handle_start(WebKitWebView *, WebKitDownload *, gpointer);
32 static void downloadmanager_cancel(GtkToolButton *, gpointer);
33 static gboolean downloadmanager_delete(GtkWidget *, gpointer);
34 static void downloadmanager_setup(void);
35 static gchar *ensure_uri_scheme(const gchar *);
36 static void external_handler_run(GSimpleAction *, GVariant *, gpointer);
37 static void grab_environment_configuration(void);
38 static void grab_feeds_finished(GObject *, GAsyncResult *, gpointer);
39 static void hover_web_view(WebKitWebView *, WebKitHitTestResult *, guint, gpointer);
40 static void icon_location(GtkEntry *, GtkEntryIconPosition, GdkEvent *, gpointer);
41 static gboolean key_common(GtkWidget *, GdkEvent *, gpointer);
42 static gboolean key_downloadmanager(GtkWidget *, GdkEvent *, gpointer);
43 static gboolean key_location(GtkWidget *, GdkEvent *, gpointer);
44 static gboolean key_web_view(GtkWidget *, GdkEvent *, gpointer);
45 static void keywords_load(void);
46 static gboolean keywords_try_search(WebKitWebView *, const gchar *);
47 static void mainwindow_setup(void);
48 static void mainwindow_title(gint);
49 static void mainwindow_title_before(GtkNotebook *, GtkWidget *, guint, gpointer);
50 static gboolean menu_web_view(WebKitWebView *, WebKitContextMenu *, GdkEvent *,
51                               WebKitHitTestResult *, gpointer);
52 static gboolean quit_if_nothing_active(void);
53 static gboolean remote_msg(GIOChannel *, GIOCondition, gpointer);
54 static void run_user_scripts(WebKitWebView *);
55 static void search(gpointer, gint);
56 static void show_web_view(WebKitWebView *, gpointer);
57 static void trust_user_certs(WebKitWebContext *);
58
59
60 struct Client
61 {
62     gchar *external_handler_uri;
63     gchar *hover_uri;
64     gchar *feed_html;
65     GtkWidget *location;
66     GtkWidget *tablabel;
67     GtkWidget *vbox;
68     GtkWidget *web_view;
69 };
70
71 struct MainWindow
72 {
73     GtkWidget *win;
74     GtkWidget *notebook;
75 } mw;
76
77 struct DownloadManager
78 {
79     GtkWidget *scroll;
80     GtkWidget *toolbar;
81     GtkWidget *win;
82 } dm;
83
84
85 static const gchar *accepted_language[2] = { NULL, NULL };
86 static gint clients = 0, downloads = 0;
87 static gboolean cooperative_alone = TRUE;
88 static gboolean cooperative_instances = TRUE;
89 static int cooperative_pipe_fp = 0;
90 static gchar *download_dir = "/var/tmp";
91 static gboolean enable_console_to_stdout = FALSE;
92 static gchar *fifo_suffix = "main";
93 static gdouble global_zoom = 1.0;
94 static gchar *history_file = NULL;
95 static gchar *home_uri = "about:blank";
96 static gboolean initial_wc_setup_done = FALSE;
97 static GHashTable *keywords = NULL;
98 static gchar *search_text = NULL;
99 static GtkPositionType tab_pos = GTK_POS_TOP;
100 static gchar *user_agent = NULL;
101
102
103 gboolean
104 button_tablabel(GtkWidget *widget, GdkEvent *event, gpointer data)
105 {
106     if (event->type == GDK_BUTTON_RELEASE)
107     {
108         switch (((GdkEventButton *)event)->button)
109         {
110             case 2:
111                 client_destroy(NULL, data);
112                 return TRUE;
113         }
114     }
115     return FALSE;
116 }
117
118 void
119 client_destroy(GtkWidget *widget, gpointer data)
120 {
121     struct Client *c = (struct Client *)data;
122     gint idx;
123
124     g_signal_handlers_disconnect_by_func(G_OBJECT(c->web_view),
125                                          changed_load_progress, c);
126
127     idx = gtk_notebook_page_num(GTK_NOTEBOOK(mw.notebook), c->vbox);
128     if (idx == -1)
129         fprintf(stderr, __NAME__": Tab index was -1, bamboozled\n");
130     else
131         gtk_notebook_remove_page(GTK_NOTEBOOK(mw.notebook), idx);
132
133     free(c);
134     clients--;
135
136     quit_if_nothing_active();
137 }
138
139 WebKitWebView *
140 client_new(const gchar *uri, WebKitWebView *related_wv, gboolean show)
141 {
142     struct Client *c;
143     WebKitWebContext *wc;
144     gchar *f;
145     GtkWidget *evbox;
146
147     if (uri != NULL && cooperative_instances && !cooperative_alone)
148     {
149         f = ensure_uri_scheme(uri);
150         write(cooperative_pipe_fp, f, strlen(f));
151         write(cooperative_pipe_fp, "\n", 1);
152         g_free(f);
153         return NULL;
154     }
155
156     c = calloc(1, sizeof(struct Client));
157     if (!c)
158     {
159         fprintf(stderr, __NAME__": fatal: calloc failed\n");
160         exit(EXIT_FAILURE);
161     }
162
163     if (related_wv == NULL)
164         c->web_view = webkit_web_view_new();
165     else
166         c->web_view = webkit_web_view_new_with_related_view(related_wv);
167     wc = webkit_web_view_get_context(WEBKIT_WEB_VIEW(c->web_view));
168
169     webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(c->web_view), global_zoom);
170     g_signal_connect(G_OBJECT(c->web_view), "notify::title",
171                      G_CALLBACK(changed_title), c);
172     g_signal_connect(G_OBJECT(c->web_view), "notify::uri",
173                      G_CALLBACK(changed_uri), c);
174     g_signal_connect(G_OBJECT(c->web_view), "notify::estimated-load-progress",
175                      G_CALLBACK(changed_load_progress), c);
176     g_signal_connect(G_OBJECT(c->web_view), "create",
177                      G_CALLBACK(client_new_request), NULL);
178     g_signal_connect(G_OBJECT(c->web_view), "context-menu",
179                      G_CALLBACK(menu_web_view), c);
180     g_signal_connect(G_OBJECT(c->web_view), "close",
181                      G_CALLBACK(client_destroy), c);
182     g_signal_connect(G_OBJECT(c->web_view), "decide-policy",
183                      G_CALLBACK(decide_policy), NULL);
184     g_signal_connect(G_OBJECT(c->web_view), "key-press-event",
185                      G_CALLBACK(key_web_view), c);
186     g_signal_connect(G_OBJECT(c->web_view), "button-release-event",
187                      G_CALLBACK(key_web_view), c);
188     g_signal_connect(G_OBJECT(c->web_view), "scroll-event",
189                      G_CALLBACK(key_web_view), c);
190     g_signal_connect(G_OBJECT(c->web_view), "mouse-target-changed",
191                      G_CALLBACK(hover_web_view), c);
192     g_signal_connect(G_OBJECT(c->web_view), "web-process-crashed",
193                      G_CALLBACK(crashed_web_view), c);
194
195     if (!initial_wc_setup_done)
196     {
197         if (accepted_language[0] != NULL)
198             webkit_web_context_set_preferred_languages(wc, accepted_language);
199
200         g_signal_connect(G_OBJECT(wc), "download-started",
201                          G_CALLBACK(download_handle_start), NULL);
202
203         trust_user_certs(wc);
204
205         initial_wc_setup_done = TRUE;
206     }
207
208     if (user_agent != NULL)
209         g_object_set(G_OBJECT(webkit_web_view_get_settings(WEBKIT_WEB_VIEW(c->web_view))),
210                      "user-agent", user_agent, NULL);
211
212     if (enable_console_to_stdout)
213         webkit_settings_set_enable_write_console_messages_to_stdout(webkit_web_view_get_settings(WEBKIT_WEB_VIEW(c->web_view)), TRUE);
214
215     webkit_settings_set_enable_developer_extras(webkit_web_view_get_settings(WEBKIT_WEB_VIEW(c->web_view)), TRUE);
216
217     c->location = gtk_entry_new();
218     g_signal_connect(G_OBJECT(c->location), "key-press-event",
219                      G_CALLBACK(key_location), c);
220     g_signal_connect(G_OBJECT(c->location), "icon-release",
221                      G_CALLBACK(icon_location), c);
222     /* XXX This is a workaround. Setting this to NULL (which is done in
223      * grab_feeds_finished() if no feed has been detected) adds a little
224      * padding left of the text. Not sure why. The point of this call
225      * right here is to have that padding right from the start. This
226      * avoids a graphical artifact. */
227     gtk_entry_set_icon_from_icon_name(GTK_ENTRY(c->location),
228                                       GTK_ENTRY_ICON_PRIMARY,
229                                       NULL);
230
231     c->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
232     gtk_box_pack_start(GTK_BOX(c->vbox), c->location, FALSE, FALSE, 0);
233     gtk_box_pack_start(GTK_BOX(c->vbox), c->web_view, TRUE, TRUE, 0);
234
235     c->tablabel = gtk_label_new(__NAME__);
236     gtk_label_set_ellipsize(GTK_LABEL(c->tablabel), PANGO_ELLIPSIZE_END);
237     gtk_label_set_width_chars(GTK_LABEL(c->tablabel), 20);
238
239     evbox = gtk_event_box_new();
240     gtk_container_add(GTK_CONTAINER(evbox), c->tablabel);
241     g_signal_connect(G_OBJECT(evbox), "button-release-event",
242                      G_CALLBACK(button_tablabel), c);
243
244     /* This only shows the event box and the label inside, nothing else.
245      * Needed because the evbox/label is "internal" to the notebook and
246      * not part of the normal "widget tree" (IIUC). */
247     gtk_widget_show_all(evbox);
248
249     gtk_notebook_append_page(GTK_NOTEBOOK(mw.notebook), c->vbox, evbox);
250     gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(mw.notebook), c->vbox, TRUE);
251
252     if (show)
253         show_web_view(NULL, c);
254     else
255         g_signal_connect(G_OBJECT(c->web_view), "ready-to-show",
256                          G_CALLBACK(show_web_view), c);
257
258     if (uri != NULL)
259     {
260         f = ensure_uri_scheme(uri);
261         webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), f);
262         g_free(f);
263     }
264
265     clients++;
266
267     return WEBKIT_WEB_VIEW(c->web_view);
268 }
269
270 WebKitWebView *
271 client_new_request(WebKitWebView *web_view,
272                    WebKitNavigationAction *navigation_action, gpointer data)
273 {
274     return client_new(NULL, web_view, FALSE);
275 }
276
277 void
278 cooperation_setup(void)
279 {
280     GIOChannel *towatch;
281     gchar *fifofilename, *fifopath;
282
283     fifofilename = g_strdup_printf("%s-%s", __NAME__".fifo", fifo_suffix);
284     fifopath = g_build_filename(g_get_user_runtime_dir(), fifofilename, NULL);
285     g_free(fifofilename);
286
287     if (!g_file_test(fifopath, G_FILE_TEST_EXISTS))
288         mkfifo(fifopath, 0600);
289
290     cooperative_pipe_fp = open(fifopath, O_WRONLY | O_NONBLOCK);
291     if (!cooperative_pipe_fp)
292     {
293         fprintf(stderr, __NAME__": Can't open FIFO at all.\n");
294     }
295     else
296     {
297         if (write(cooperative_pipe_fp, "", 0) == -1)
298         {
299             /* Could not do an empty write to the FIFO which means there's
300              * no one listening. */
301             close(cooperative_pipe_fp);
302             towatch = g_io_channel_new_file(fifopath, "r+", NULL);
303             g_io_add_watch(towatch, G_IO_IN, (GIOFunc)remote_msg, NULL);
304         }
305         else
306             cooperative_alone = FALSE;
307     }
308
309     g_free(fifopath);
310 }
311
312 void
313 changed_download_progress(GObject *obj, GParamSpec *pspec, gpointer data)
314 {
315     WebKitDownload *download = WEBKIT_DOWNLOAD(obj);
316     WebKitURIResponse *resp;
317     GtkToolItem *tb = GTK_TOOL_ITEM(data);
318     gdouble p, size_mb;
319     const gchar *uri;
320     gchar *t, *filename, *base;
321
322     p = webkit_download_get_estimated_progress(download);
323     p = p > 1 ? 1 : p;
324     p = p < 0 ? 0 : p;
325     p *= 100;
326     resp = webkit_download_get_response(download);
327     size_mb = webkit_uri_response_get_content_length(resp) / 1e6;
328
329     uri = webkit_download_get_destination(download);
330     filename = g_filename_from_uri(uri, NULL, NULL);
331     if (filename == NULL)
332     {
333         /* This really should not happen because WebKit uses that URI to
334          * write to a file... */
335         fprintf(stderr, __NAME__": Could not construct file name from URI!\n");
336         t = g_strdup_printf("%s (%.0f%% of %.1f MB)",
337                             webkit_uri_response_get_uri(resp), p, size_mb);
338     }
339     else
340     {
341         base = g_path_get_basename(filename);
342         t = g_strdup_printf("%s (%.0f%% of %.1f MB)", base, p, size_mb);
343         g_free(filename);
344         g_free(base);
345     }
346     gtk_tool_button_set_label(GTK_TOOL_BUTTON(tb), t);
347     g_free(t);
348 }
349
350 void
351 changed_load_progress(GObject *obj, GParamSpec *pspec, gpointer data)
352 {
353     struct Client *c = (struct Client *)data;
354     gdouble p;
355     gchar *grab_feeds =
356         "a = document.querySelectorAll('"
357         "    html > head > link[rel=\"alternate\"][href][type=\"application/atom+xml\"],"
358         "    html > head > link[rel=\"alternate\"][href][type=\"application/rss+xml\"]"
359         "');"
360         "if (a.length == 0)"
361         "    null;"
362         "else"
363         "{"
364         "    out = '';"
365         "    for (i = 0; i < a.length; i++)"
366         "    {"
367         "        url = encodeURIComponent(a[i].href);"
368         "        if ('title' in a[i] && a[i].title != '')"
369         "            title = encodeURIComponent(a[i].title);"
370         "        else"
371         "            title = url;"
372         "        out += '<li><a href=\"' + url + '\">' + title + '</a></li>';"
373         "    }"
374         "    out;"
375         "}";
376
377     p = webkit_web_view_get_estimated_load_progress(WEBKIT_WEB_VIEW(c->web_view));
378     if (p == 1)
379     {
380         p = 0;
381
382         /* The page has loaded fully. We now run the short JavaScript
383          * snippet above that operates on the DOM. It tries to grab all
384          * occurences of <link rel="alternate" ...>, i.e. RSS/Atom feed
385          * references. */
386         webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(c->web_view),
387                                        grab_feeds, NULL,
388                                        grab_feeds_finished, c);
389
390         run_user_scripts(WEBKIT_WEB_VIEW(c->web_view));
391     }
392     gtk_entry_set_progress_fraction(GTK_ENTRY(c->location), p);
393 }
394
395 void
396 changed_title(GObject *obj, GParamSpec *pspec, gpointer data)
397 {
398     const gchar *t, *u;
399     struct Client *c = (struct Client *)data;
400
401     u = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
402     t = webkit_web_view_get_title(WEBKIT_WEB_VIEW(c->web_view));
403
404     u = u == NULL ? __NAME__ : u;
405     u = u[0] == 0 ? __NAME__ : u;
406
407     t = t == NULL ? u : t;
408     t = t[0] == 0 ? u : t;
409
410     gtk_label_set_text(GTK_LABEL(c->tablabel), t);
411     mainwindow_title(-1);
412 }
413
414 void
415 changed_uri(GObject *obj, GParamSpec *pspec, gpointer data)
416 {
417     const gchar *t;
418     struct Client *c = (struct Client *)data;
419     FILE *fp;
420
421     t = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
422
423     /* When a web process crashes, we get a "notify::uri" signal, but we
424      * can no longer read a meaningful URI. It's just an empty string
425      * now. Not updating the location bar in this scenario is important,
426      * because we would override the "WEB PROCESS CRASHED" message. */
427     if (t != NULL && strlen(t) > 0)
428     {
429         gtk_entry_set_text(GTK_ENTRY(c->location), t);
430
431         if (history_file != NULL)
432         {
433             fp = fopen(history_file, "a");
434             if (fp != NULL)
435             {
436                 fprintf(fp, "%s\n", t);
437                 fclose(fp);
438             }
439             else
440                 perror(__NAME__": Error opening history file");
441         }
442     }
443 }
444
445 gboolean
446 crashed_web_view(WebKitWebView *web_view, gpointer data)
447 {
448     gchar *t;
449     struct Client *c = (struct Client *)data;
450
451     t = g_strdup_printf("WEB PROCESS CRASHED: %s",
452                         webkit_web_view_get_uri(WEBKIT_WEB_VIEW(web_view)));
453     gtk_entry_set_text(GTK_ENTRY(c->location), t);
454     g_free(t);
455
456     return TRUE;
457 }
458
459 gboolean
460 decide_policy(WebKitWebView *web_view, WebKitPolicyDecision *decision,
461               WebKitPolicyDecisionType type, gpointer data)
462 {
463     WebKitResponsePolicyDecision *r;
464
465     switch (type)
466     {
467         case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
468             r = WEBKIT_RESPONSE_POLICY_DECISION(decision);
469             if (!webkit_response_policy_decision_is_mime_type_supported(r))
470                 webkit_policy_decision_download(decision);
471             else
472                 webkit_policy_decision_use(decision);
473             break;
474         default:
475             /* Use whatever default there is. */
476             return FALSE;
477     }
478     return TRUE;
479 }
480
481 void
482 download_handle_finished(WebKitDownload *download, gpointer data)
483 {
484     downloads--;
485 }
486
487 void
488 download_handle_start(WebKitWebView *web_view, WebKitDownload *download,
489                       gpointer data)
490 {
491     g_signal_connect(G_OBJECT(download), "decide-destination",
492                      G_CALLBACK(download_handle), data);
493 }
494
495 gboolean
496 download_handle(WebKitDownload *download, gchar *suggested_filename, gpointer data)
497 {
498     gchar *sug_clean, *path, *path2 = NULL, *uri;
499     GtkToolItem *tb;
500     int suffix = 1;
501     size_t i;
502
503     sug_clean = g_strdup(suggested_filename);
504     for (i = 0; i < strlen(sug_clean); i++)
505         if (sug_clean[i] == G_DIR_SEPARATOR)
506             sug_clean[i] = '_';
507
508     path = g_build_filename(download_dir, sug_clean, NULL);
509     path2 = g_strdup(path);
510     while (g_file_test(path2, G_FILE_TEST_EXISTS) && suffix < 1000)
511     {
512         g_free(path2);
513
514         path2 = g_strdup_printf("%s.%d", path, suffix);
515         suffix++;
516     }
517
518     if (suffix == 1000)
519     {
520         fprintf(stderr, __NAME__": Suffix reached limit for download.\n");
521         webkit_download_cancel(download);
522     }
523     else
524     {
525         uri = g_filename_to_uri(path2, NULL, NULL);
526         webkit_download_set_destination(download, uri);
527         g_free(uri);
528
529         tb = gtk_tool_button_new(NULL, NULL);
530         gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(tb), "gtk-delete");
531         gtk_tool_button_set_label(GTK_TOOL_BUTTON(tb), sug_clean);
532         gtk_toolbar_insert(GTK_TOOLBAR(dm.toolbar), tb, 0);
533         gtk_widget_show_all(dm.win);
534
535         g_signal_connect(G_OBJECT(download), "notify::estimated-progress",
536                          G_CALLBACK(changed_download_progress), tb);
537
538         downloads++;
539         g_signal_connect(G_OBJECT(download), "finished",
540                          G_CALLBACK(download_handle_finished), NULL);
541
542         g_object_ref(download);
543         g_signal_connect(G_OBJECT(tb), "clicked",
544                          G_CALLBACK(downloadmanager_cancel), download);
545     }
546
547     g_free(sug_clean);
548     g_free(path);
549     g_free(path2);
550
551     /* Propagate -- to whom it may concern. */
552     return FALSE;
553 }
554
555 void
556 downloadmanager_cancel(GtkToolButton *tb, gpointer data)
557 {
558     WebKitDownload *download = WEBKIT_DOWNLOAD(data);
559
560     webkit_download_cancel(download);
561     g_object_unref(download);
562
563     gtk_widget_destroy(GTK_WIDGET(tb));
564 }
565
566 gboolean
567 downloadmanager_delete(GtkWidget *obj, gpointer data)
568 {
569     if (!quit_if_nothing_active())
570         gtk_widget_hide(dm.win);
571
572     return TRUE;
573 }
574
575 void
576 downloadmanager_setup(void)
577 {
578     dm.win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
579     gtk_window_set_type_hint(GTK_WINDOW(dm.win), GDK_WINDOW_TYPE_HINT_DIALOG);
580     gtk_window_set_default_size(GTK_WINDOW(dm.win), 500, 250);
581     gtk_window_set_title(GTK_WINDOW(dm.win), __NAME__" - Download Manager");
582     g_signal_connect(G_OBJECT(dm.win), "delete-event",
583                      G_CALLBACK(downloadmanager_delete), NULL);
584     g_signal_connect(G_OBJECT(dm.win), "key-press-event",
585                      G_CALLBACK(key_downloadmanager), NULL);
586
587     dm.toolbar = gtk_toolbar_new();
588     gtk_orientable_set_orientation(GTK_ORIENTABLE(dm.toolbar),
589                                    GTK_ORIENTATION_VERTICAL);
590     gtk_toolbar_set_style(GTK_TOOLBAR(dm.toolbar), GTK_TOOLBAR_BOTH_HORIZ);
591     gtk_toolbar_set_show_arrow(GTK_TOOLBAR(dm.toolbar), FALSE);
592
593     dm.scroll = gtk_scrolled_window_new(NULL, NULL);
594     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dm.scroll),
595                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
596     gtk_container_add(GTK_CONTAINER(dm.scroll), dm.toolbar);
597
598     gtk_container_add(GTK_CONTAINER(dm.win), dm.scroll);
599 }
600
601 gchar *
602 ensure_uri_scheme(const gchar *t)
603 {
604     gchar *f, *fabs;
605
606     f = g_ascii_strdown(t, -1);
607     if (!g_str_has_prefix(f, "http:") &&
608         !g_str_has_prefix(f, "https:") &&
609         !g_str_has_prefix(f, "file:") &&
610         !g_str_has_prefix(f, "about:") &&
611         !g_str_has_prefix(f, "data:"))
612     {
613         g_free(f);
614         fabs = realpath(t, NULL);
615         if (fabs != NULL)
616         {
617             f = g_strdup_printf("file://%s", fabs);
618             free(fabs);
619         }
620         else
621             f = g_strdup_printf("http://%s", t);
622         return f;
623     }
624     else
625         return g_strdup(t);
626 }
627
628 void
629 external_handler_run(GSimpleAction *simple, GVariant *param, gpointer data)
630 {
631     struct Client *c = (struct Client *)data;
632     gchar *argv[] = { "lariza-external-handler", "-u", NULL, NULL };
633     GPid pid;
634     GError *err = NULL;
635
636     (void)simple;
637     (void)param;
638
639     argv[2] = c->external_handler_uri;
640     if (!g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
641                        &pid, &err))
642     {
643         fprintf(stderr, __NAME__": Could not launch key handler: %s\n",
644                 err->message);
645         g_error_free(err);
646     }
647     else
648         g_spawn_close_pid(pid);
649 }
650
651 void
652 grab_environment_configuration(void)
653 {
654     const gchar *e;
655
656     e = g_getenv(__NAME_UPPERCASE__"_ACCEPTED_LANGUAGE");
657     if (e != NULL)
658         accepted_language[0] = g_strdup(e);
659
660     e = g_getenv(__NAME_UPPERCASE__"_DOWNLOAD_DIR");
661     if (e != NULL)
662         download_dir = g_strdup(e);
663
664     e = g_getenv(__NAME_UPPERCASE__"_ENABLE_CONSOLE_TO_STDOUT");
665     if (e != NULL)
666         enable_console_to_stdout = TRUE;
667
668     e = g_getenv(__NAME_UPPERCASE__"_FIFO_SUFFIX");
669     if (e != NULL)
670         fifo_suffix = g_strdup(e);
671
672     e = g_getenv(__NAME_UPPERCASE__"_HISTORY_FILE");
673     if (e != NULL)
674         history_file = g_strdup(e);
675
676     e = g_getenv(__NAME_UPPERCASE__"_HOME_URI");
677     if (e != NULL)
678         home_uri = g_strdup(e);
679
680     e = g_getenv(__NAME_UPPERCASE__"_TAB_POS");
681     if (e != NULL)
682     {
683         if (strcmp(e, "top") == 0)
684             tab_pos = GTK_POS_TOP;
685         if (strcmp(e, "right") == 0)
686             tab_pos = GTK_POS_RIGHT;
687         if (strcmp(e, "bottom") == 0)
688             tab_pos = GTK_POS_BOTTOM;
689         if (strcmp(e, "left") == 0)
690             tab_pos = GTK_POS_LEFT;
691     }
692
693     e = g_getenv(__NAME_UPPERCASE__"_USER_AGENT");
694     if (e != NULL)
695         user_agent = g_strdup(e);
696
697     e = g_getenv(__NAME_UPPERCASE__"_ZOOM");
698     if (e != NULL)
699         global_zoom = atof(e);
700 }
701
702 void
703 grab_feeds_finished(GObject *object, GAsyncResult *result, gpointer data)
704 {
705     struct Client *c = (struct Client *)data;
706     WebKitJavascriptResult *js_result;
707     JSCValue *value;
708     JSCException *exception;
709     GError *err = NULL;
710     gchar *str_value;
711
712     g_free(c->feed_html);
713     c->feed_html = NULL;
714
715     /* This was taken almost verbatim from the example in WebKit's
716      * documentation:
717      *
718      * https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#webkit-web-view-run-javascript-finish */
719
720     js_result = webkit_web_view_run_javascript_finish(WEBKIT_WEB_VIEW(object),
721                                                       result, &err);
722     if (!js_result)
723     {
724         fprintf(stderr, __NAME__": Error running javascript: %s\n", err->message);
725         g_error_free(err);
726         return;
727     }
728
729     value = webkit_javascript_result_get_js_value(js_result);
730     if (jsc_value_is_string(value))
731     {
732         str_value = jsc_value_to_string(value);
733         exception = jsc_context_get_exception(jsc_value_get_context(value));
734         if (exception != NULL)
735         {
736             fprintf(stderr, __NAME__": Error running javascript: %s\n",
737                     jsc_exception_get_message(exception));
738         }
739         else
740             c->feed_html = str_value;
741
742         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(c->location),
743                                           GTK_ENTRY_ICON_PRIMARY,
744                                           "application-rss+xml-symbolic");
745         gtk_entry_set_icon_activatable(GTK_ENTRY(c->location),
746                                        GTK_ENTRY_ICON_PRIMARY,
747                                        TRUE);
748     }
749     else
750     {
751         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(c->location),
752                                           GTK_ENTRY_ICON_PRIMARY,
753                                           NULL);
754     }
755
756     webkit_javascript_result_unref(js_result);
757 }
758
759 void
760 hover_web_view(WebKitWebView *web_view, WebKitHitTestResult *ht, guint modifiers,
761                gpointer data)
762 {
763     struct Client *c = (struct Client *)data;
764
765     if (!gtk_widget_is_focus(c->location))
766     {
767         if (webkit_hit_test_result_context_is_link(ht))
768         {
769             gtk_entry_set_text(GTK_ENTRY(c->location),
770                                webkit_hit_test_result_get_link_uri(ht));
771
772             if (c->hover_uri != NULL)
773                 g_free(c->hover_uri);
774             c->hover_uri = g_strdup(webkit_hit_test_result_get_link_uri(ht));
775         }
776         else
777         {
778             gtk_entry_set_text(GTK_ENTRY(c->location),
779                                webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view)));
780
781             if (c->hover_uri != NULL)
782                 g_free(c->hover_uri);
783             c->hover_uri = NULL;
784         }
785     }
786 }
787
788 void
789 icon_location(GtkEntry *entry, GtkEntryIconPosition icon_pos, GdkEvent *event,
790               gpointer data)
791 {
792     struct Client *c = (struct Client *)data;
793     gchar *d;
794     gchar *data_template =
795         "data:text/html,"
796         "<!DOCTYPE html>"
797         "<html>"
798         "    <head>"
799         "        <meta charset=\"UTF-8\">"
800         "        <title>Feeds</title>"
801         "    </head>"
802         "    <body>"
803         "        <p>Feeds found on this page:</p>"
804         "        <ul>"
805         "        %s"
806         "        </ul>"
807         "    </body>"
808         "</html>";
809
810     if (c->feed_html != NULL)
811     {
812         /* What we're actually trying to do is show a simple HTML page
813          * that lists all the feeds on the current page. The function
814          * webkit_web_view_load_html() looks like the proper way to do
815          * that. Sad thing is, it doesn't create a history entry, but
816          * instead simply replaces the content of the current page. This
817          * is not what we want.
818          *
819          * RFC 2397 [0] defines the data URI scheme [1]. We abuse this
820          * mechanism to show my custom HTML snippet *and* create a
821          * history entry.
822          *
823          * [0]: https://tools.ietf.org/html/rfc2397
824          * [1]: https://en.wikipedia.org/wiki/Data_URI_scheme */
825         d = g_strdup_printf(data_template, c->feed_html);
826         webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), d);
827         g_free(d);
828     }
829 }
830
831 gboolean
832 key_common(GtkWidget *widget, GdkEvent *event, gpointer data)
833 {
834     struct Client *c = (struct Client *)data;
835     WebKitWebContext *wc = webkit_web_view_get_context(WEBKIT_WEB_VIEW(c->web_view));
836     gchar *f;
837
838     if (event->type == GDK_KEY_PRESS)
839     {
840         if (((GdkEventKey *)event)->state & GDK_MOD1_MASK)
841         {
842             switch (((GdkEventKey *)event)->keyval)
843             {
844                 case GDK_KEY_q:  /* close window (left hand) */
845                     client_destroy(NULL, c);
846                     return TRUE;
847                 case GDK_KEY_w:  /* home (left hand) */
848                     f = ensure_uri_scheme(home_uri);
849                     webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), f);
850                     g_free(f);
851                     return TRUE;
852                 case GDK_KEY_e:  /* new tab (left hand) */
853                     f = ensure_uri_scheme(home_uri);
854                     client_new(f, NULL, TRUE);
855                     g_free(f);
856                     return TRUE;
857                 case GDK_KEY_r:  /* reload (left hand) */
858                     webkit_web_view_reload_bypass_cache(WEBKIT_WEB_VIEW(
859                                                         c->web_view));
860                     return TRUE;
861                 case GDK_KEY_d:  /* download manager (left hand) */
862                     gtk_widget_show_all(dm.win);
863                     return TRUE;
864                 case GDK_KEY_2:  /* search forward (left hand) */
865                 case GDK_KEY_n:  /* search forward (maybe both hands) */
866                     search(c, 1);
867                     return TRUE;
868                 case GDK_KEY_3:  /* search backward (left hand) */
869                     search(c, -1);
870                     return TRUE;
871                 case GDK_KEY_l:  /* location (BOTH hands) */
872                     gtk_widget_grab_focus(c->location);
873                     return TRUE;
874                 case GDK_KEY_k:  /* initiate search (BOTH hands) */
875                     gtk_widget_grab_focus(c->location);
876                     gtk_entry_set_text(GTK_ENTRY(c->location), ":/");
877                     gtk_editable_set_position(GTK_EDITABLE(c->location), -1);
878                     return TRUE;
879                 case GDK_KEY_c:  /* reload trusted certs (left hand) */
880                     trust_user_certs(wc);
881                     return TRUE;
882                 case GDK_KEY_x:  /* launch external handler (left hand) */
883                     if (c->external_handler_uri != NULL)
884                         g_free(c->external_handler_uri);
885                     c->external_handler_uri = g_strdup(
886                         webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view)));
887                     external_handler_run(NULL, NULL, c);
888                     return TRUE;
889                 case GDK_KEY_a:  /* go one tab to the left (left hand) */
890                     gtk_notebook_prev_page(GTK_NOTEBOOK(mw.notebook));
891                     return TRUE;
892                 case GDK_KEY_s:  /* go one tab to the right (left hand) */
893                     gtk_notebook_next_page(GTK_NOTEBOOK(mw.notebook));
894                     return TRUE;
895             }
896         }
897         /* navigate backward (left hand) */
898         else if (((GdkEventKey *)event)->keyval == GDK_KEY_F2)
899         {
900             webkit_web_view_go_back(WEBKIT_WEB_VIEW(c->web_view));
901             return TRUE;
902         }
903         /* navigate forward (left hand) */
904         else if (((GdkEventKey *)event)->keyval == GDK_KEY_F3)
905         {
906             webkit_web_view_go_forward(WEBKIT_WEB_VIEW(c->web_view));
907             return TRUE;
908         }
909     }
910
911     return FALSE;
912 }
913
914 gboolean
915 key_downloadmanager(GtkWidget *widget, GdkEvent *event, gpointer data)
916 {
917     if (event->type == GDK_KEY_PRESS)
918     {
919         if (((GdkEventKey *)event)->state & GDK_MOD1_MASK)
920         {
921             switch (((GdkEventKey *)event)->keyval)
922             {
923                 case GDK_KEY_d:  /* close window (left hand) */
924                 case GDK_KEY_q:
925                     downloadmanager_delete(dm.win, NULL);
926                     return TRUE;
927             }
928         }
929     }
930
931     return FALSE;
932 }
933
934 gboolean
935 key_location(GtkWidget *widget, GdkEvent *event, gpointer data)
936 {
937     struct Client *c = (struct Client *)data;
938     const gchar *t;
939     gchar *f;
940
941     if (key_common(widget, event, data))
942         return TRUE;
943
944     if (event->type == GDK_KEY_PRESS)
945     {
946         switch (((GdkEventKey *)event)->keyval)
947         {
948             case GDK_KEY_KP_Enter:
949             case GDK_KEY_Return:
950                 gtk_widget_grab_focus(c->web_view);
951                 t = gtk_entry_get_text(GTK_ENTRY(c->location));
952                 if (t != NULL && t[0] == ':' && t[1] == '/')
953                 {
954                     if (search_text != NULL)
955                         g_free(search_text);
956                     search_text = g_strdup(t + 2);
957                     search(c, 0);
958                 }
959                 else if (!keywords_try_search(WEBKIT_WEB_VIEW(c->web_view), t))
960                 {
961                     f = ensure_uri_scheme(t);
962                     webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), f);
963                     g_free(f);
964                 }
965                 return TRUE;
966             case GDK_KEY_Escape:
967                 t = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
968                 gtk_entry_set_text(GTK_ENTRY(c->location),
969                                    (t == NULL ? __NAME__ : t));
970                 return TRUE;
971         }
972     }
973
974     return FALSE;
975 }
976
977 gboolean
978 key_web_view(GtkWidget *widget, GdkEvent *event, gpointer data)
979 {
980     struct Client *c = (struct Client *)data;
981     gdouble dx, dy;
982     gfloat z;
983
984     if (key_common(widget, event, data))
985         return TRUE;
986
987     if (event->type == GDK_KEY_PRESS)
988     {
989         if (((GdkEventKey *)event)->keyval == GDK_KEY_Escape)
990         {
991             webkit_web_view_stop_loading(WEBKIT_WEB_VIEW(c->web_view));
992             gtk_entry_set_progress_fraction(GTK_ENTRY(c->location), 0);
993         }
994     }
995     else if (event->type == GDK_BUTTON_RELEASE)
996     {
997         switch (((GdkEventButton *)event)->button)
998         {
999             case 2:
1000                 if (c->hover_uri != NULL)
1001                 {
1002                     client_new(c->hover_uri, NULL, TRUE);
1003                     return TRUE;
1004                 }
1005                 break;
1006             case 8:
1007                 webkit_web_view_go_back(WEBKIT_WEB_VIEW(c->web_view));
1008                 return TRUE;
1009             case 9:
1010                 webkit_web_view_go_forward(WEBKIT_WEB_VIEW(c->web_view));
1011                 return TRUE;
1012         }
1013     }
1014     else if (event->type == GDK_SCROLL)
1015     {
1016         if (((GdkEventScroll *)event)->state & GDK_MOD1_MASK ||
1017             ((GdkEventScroll *)event)->state & GDK_CONTROL_MASK)
1018         {
1019             gdk_event_get_scroll_deltas(event, &dx, &dy);
1020             z = webkit_web_view_get_zoom_level(WEBKIT_WEB_VIEW(c->web_view));
1021             z += -dy * 0.1;
1022             z = dx != 0 ? global_zoom : z;
1023             webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(c->web_view), z);
1024             return TRUE;
1025         }
1026     }
1027
1028     return FALSE;
1029 }
1030
1031 void
1032 keywords_load(void)
1033 {
1034     GError *err = NULL;
1035     GIOChannel *channel = NULL;
1036     gchar *path = NULL, *buf = NULL;
1037     gchar **tokens = NULL;
1038
1039     keywords = g_hash_table_new(g_str_hash, g_str_equal);
1040
1041     path = g_build_filename(g_get_user_config_dir(), __NAME__, "keywordsearch",
1042                             NULL);
1043     channel = g_io_channel_new_file(path, "r", &err);
1044     if (channel != NULL)
1045     {
1046         while (g_io_channel_read_line(channel, &buf, NULL, NULL, NULL)
1047                == G_IO_STATUS_NORMAL)
1048         {
1049             g_strstrip(buf);
1050             if (buf[0] != '#')
1051             {
1052                 tokens = g_strsplit(buf, " ", 2);
1053                 if (tokens[0] != NULL && tokens[1] != NULL)
1054                     g_hash_table_insert(keywords, g_strdup(tokens[0]),
1055                                         g_strdup(tokens[1]));
1056                 g_strfreev(tokens);
1057             }
1058             g_free(buf);
1059         }
1060         g_io_channel_shutdown(channel, FALSE, NULL);
1061     }
1062     g_free(path);
1063 }
1064
1065 gboolean
1066 keywords_try_search(WebKitWebView *web_view, const gchar *t)
1067 {
1068     gboolean ret = FALSE;
1069     gchar **tokens = NULL;
1070     gchar *val = NULL, *escaped = NULL, *uri = NULL;
1071
1072     tokens = g_strsplit(t, " ", 2);
1073     if (tokens[0] != NULL && tokens[1] != NULL)
1074     {
1075         val = g_hash_table_lookup(keywords, tokens[0]);
1076         if (val != NULL)
1077         {
1078             escaped = g_uri_escape_string(tokens[1], NULL, TRUE);
1079             uri = g_strdup_printf((gchar *)val, escaped);
1080             webkit_web_view_load_uri(web_view, uri);
1081             g_free(uri);
1082             g_free(escaped);
1083             ret = TRUE;
1084         }
1085     }
1086     g_strfreev(tokens);
1087
1088     return ret;
1089 }
1090
1091 void
1092 mainwindow_setup(void)
1093 {
1094     mw.win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1095     gtk_window_set_default_size(GTK_WINDOW(mw.win), 800, 600);
1096     g_signal_connect(G_OBJECT(mw.win), "destroy", gtk_main_quit, NULL);
1097     gtk_window_set_title(GTK_WINDOW(mw.win), __NAME__);
1098
1099     mw.notebook = gtk_notebook_new();
1100     gtk_notebook_set_scrollable(GTK_NOTEBOOK(mw.notebook), TRUE);
1101     gtk_notebook_set_tab_pos(GTK_NOTEBOOK(mw.notebook), tab_pos);
1102     gtk_container_add(GTK_CONTAINER(mw.win), mw.notebook);
1103     g_signal_connect(G_OBJECT(mw.notebook), "switch-page",
1104                      G_CALLBACK(mainwindow_title_before), NULL);
1105 }
1106
1107 void
1108 mainwindow_title_before(GtkNotebook *nb, GtkWidget *p, guint idx, gpointer data)
1109 {
1110     mainwindow_title(idx);
1111 }
1112
1113 void
1114 mainwindow_title(gint idx)
1115 {
1116     GtkWidget *child, *evbox, *label;
1117     const gchar *text;
1118
1119     if (idx == -1)
1120     {
1121         idx = gtk_notebook_get_current_page(GTK_NOTEBOOK(mw.notebook));
1122         if (idx == -1)
1123             return;
1124     }
1125
1126     child = gtk_notebook_get_nth_page(GTK_NOTEBOOK(mw.notebook), idx);
1127     if (child == NULL)
1128         return;
1129
1130     evbox = gtk_notebook_get_tab_label(GTK_NOTEBOOK(mw.notebook), child);
1131     label = gtk_bin_get_child(GTK_BIN(evbox));
1132     text = gtk_label_get_text(GTK_LABEL(label));
1133     gtk_window_set_title(GTK_WINDOW(mw.win), text);
1134 }
1135
1136 gboolean
1137 menu_web_view(WebKitWebView *web_view, WebKitContextMenu *menu, GdkEvent *ev,
1138               WebKitHitTestResult *ht, gpointer data)
1139 {
1140     struct Client *c = (struct Client *)data;
1141     GSimpleAction *action = NULL;
1142     WebKitContextMenuItem *mi = NULL;
1143     const gchar *uri = NULL;
1144
1145     (void)ev;
1146
1147     if (webkit_hit_test_result_context_is_link(ht))
1148         uri = webkit_hit_test_result_get_link_uri(ht);
1149     else if (webkit_hit_test_result_context_is_image(ht))
1150         uri = webkit_hit_test_result_get_image_uri(ht);
1151     else if (webkit_hit_test_result_context_is_media(ht))
1152         uri = webkit_hit_test_result_get_media_uri(ht);
1153
1154     if (uri != NULL)
1155     {
1156         webkit_context_menu_append(menu, webkit_context_menu_item_new_separator());
1157
1158         if (c->external_handler_uri != NULL)
1159             g_free(c->external_handler_uri);
1160         c->external_handler_uri = g_strdup(uri);
1161         action = g_simple_action_new("external_handler", NULL);
1162         g_signal_connect(G_OBJECT(action), "activate",
1163                          G_CALLBACK(external_handler_run), data);
1164         mi = webkit_context_menu_item_new_from_gaction(G_ACTION(action),
1165                                                        "Open with external handler",
1166                                                        NULL);
1167         webkit_context_menu_append(menu, mi);
1168         g_object_unref(action);
1169     }
1170
1171     /* FALSE = Show the menu. (TRUE = Don't ever show it.) */
1172     return FALSE;
1173 }
1174
1175 gboolean
1176 quit_if_nothing_active(void)
1177 {
1178     if (clients == 0)
1179     {
1180         if (downloads == 0)
1181         {
1182             gtk_main_quit();
1183             return TRUE;
1184         }
1185         else
1186             gtk_widget_show_all(dm.win);
1187     }
1188
1189     return FALSE;
1190 }
1191
1192 gboolean
1193 remote_msg(GIOChannel *channel, GIOCondition condition, gpointer data)
1194 {
1195     gchar *uri = NULL;
1196
1197     g_io_channel_read_line(channel, &uri, NULL, NULL, NULL);
1198     if (uri)
1199     {
1200         g_strstrip(uri);
1201         client_new(uri, NULL, TRUE);
1202         g_free(uri);
1203     }
1204     return TRUE;
1205 }
1206
1207 void
1208 run_user_scripts(WebKitWebView *web_view)
1209 {
1210     gchar *base = NULL, *path = NULL, *contents = NULL;
1211     const gchar *entry = NULL;
1212     GDir *scriptdir = NULL;
1213
1214     base = g_build_filename(g_get_user_config_dir(), __NAME__, "user-scripts", NULL);
1215     scriptdir = g_dir_open(base, 0, NULL);
1216     if (scriptdir != NULL)
1217     {
1218         while ((entry = g_dir_read_name(scriptdir)) != NULL)
1219         {
1220             path = g_build_filename(base, entry, NULL);
1221             if (g_str_has_suffix(path, ".js"))
1222             {
1223                 if (g_file_get_contents(path, &contents, NULL, NULL))
1224                 {
1225                     webkit_web_view_run_javascript(web_view, contents, NULL, NULL, NULL);
1226                     g_free(contents);
1227                 }
1228             }
1229             g_free(path);
1230         }
1231         g_dir_close(scriptdir);
1232     }
1233
1234     g_free(base);
1235 }
1236
1237 void
1238 search(gpointer data, gint direction)
1239 {
1240     struct Client *c = (struct Client *)data;
1241     WebKitWebView *web_view = WEBKIT_WEB_VIEW(c->web_view);
1242     WebKitFindController *fc = webkit_web_view_get_find_controller(web_view);
1243
1244     if (search_text == NULL)
1245         return;
1246
1247     switch (direction)
1248     {
1249         case 0:
1250             webkit_find_controller_search(fc, search_text,
1251                                           WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE |
1252                                           WEBKIT_FIND_OPTIONS_WRAP_AROUND,
1253                                           G_MAXUINT);
1254             break;
1255         case 1:
1256             webkit_find_controller_search_next(fc);
1257             break;
1258         case -1:
1259             webkit_find_controller_search_previous(fc);
1260             break;
1261     }
1262 }
1263
1264 void
1265 show_web_view(WebKitWebView *web_view, gpointer data)
1266 {
1267     struct Client *c = (struct Client *)data;
1268     gint idx;
1269
1270     (void)web_view;
1271
1272     gtk_widget_show_all(mw.win);
1273
1274     idx = gtk_notebook_page_num(GTK_NOTEBOOK(mw.notebook), c->vbox);
1275     if (idx != -1)
1276         gtk_notebook_set_current_page(GTK_NOTEBOOK(mw.notebook), idx);
1277
1278     gtk_widget_grab_focus(c->web_view);
1279 }
1280
1281 void
1282 trust_user_certs(WebKitWebContext *wc)
1283 {
1284     GTlsCertificate *cert;
1285     const gchar *basedir, *file, *absfile;
1286     GDir *dir;
1287
1288     basedir = g_build_filename(g_get_user_config_dir(), __NAME__, "certs", NULL);
1289     dir = g_dir_open(basedir, 0, NULL);
1290     if (dir != NULL)
1291     {
1292         file = g_dir_read_name(dir);
1293         while (file != NULL)
1294         {
1295             absfile = g_build_filename(g_get_user_config_dir(), __NAME__, "certs",
1296                                        file, NULL);
1297             cert = g_tls_certificate_new_from_file(absfile, NULL);
1298             if (cert == NULL)
1299                 fprintf(stderr, __NAME__": Could not load trusted cert '%s'\n", file);
1300             else
1301                 webkit_web_context_allow_tls_certificate_for_host(wc, cert, file);
1302             file = g_dir_read_name(dir);
1303         }
1304         g_dir_close(dir);
1305     }
1306 }
1307
1308
1309 int
1310 main(int argc, char **argv)
1311 {
1312     gchar *c;
1313     int opt, i;
1314
1315     gtk_init(&argc, &argv);
1316     webkit_web_context_set_process_model(webkit_web_context_get_default(),
1317         WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
1318
1319     grab_environment_configuration();
1320
1321     while ((opt = getopt(argc, argv, "C")) != -1)
1322     {
1323         switch (opt)
1324         {
1325             case 'C':
1326                 cooperative_instances = FALSE;
1327                 break;
1328             default:
1329                 fprintf(stderr, "Usage: "__NAME__" [OPTION]... [URI]...\n");
1330                 exit(EXIT_FAILURE);
1331         }
1332     }
1333
1334     keywords_load();
1335     if (cooperative_instances)
1336         cooperation_setup();
1337     downloadmanager_setup();
1338
1339     mainwindow_setup();
1340
1341     if (!cooperative_instances || cooperative_alone)
1342     {
1343         c = g_build_filename(g_get_user_config_dir(), __NAME__, "web_extensions",
1344                              NULL);
1345         webkit_web_context_set_web_extensions_directory(
1346             webkit_web_context_get_default(), c
1347         );
1348     }
1349
1350     if (optind >= argc)
1351         client_new(home_uri, NULL, TRUE);
1352     else
1353     {
1354         for (i = optind; i < argc; i++)
1355             client_new(argv[i], NULL, TRUE);
1356     }
1357
1358     if (!cooperative_instances || cooperative_alone)
1359         gtk_main();
1360     exit(EXIT_SUCCESS);
1361 }