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