]> git.armaanb.net Git - chorizo.git/blob - browser.c
draft 3
[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, 0);
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         if (t != NULL && strlen(t) > 0) {
529                 set_uri(t, c);
530
531                 //No g_get_user_state_dir unfortunately
532                 gchar *state_env = getenv("XDG_STATE_DIR");
533                 gchar *state_dir = (state_env) ?
534                                                  state_env :
535                                                  g_build_filename(g_get_home_dir(),
536                                                             ".local", "state",
537                                                             "chorizo", NULL);
538
539                 gchar *history_file =
540                         g_build_filename(state_dir, "history", NULL);
541                 if (!cfg.private) {
542                         mkdirp(state_dir, 0700);
543                         fp = fopen(history_file, "a");
544                         if (fp != NULL) {
545                                 fprintf(fp, "%s\n", t);
546                                 fclose(fp);
547                         } else {
548                                 perror("chorizo: error: could not open history file");
549                         }
550                 }
551                 g_free(history_file);
552                 g_free(state_dir);
553         }
554 }
555
556 gboolean
557 crashed_web_view(WebKitWebView *web_view, gpointer data)
558 {
559         struct Client *c = (struct Client *)data;
560         GtkDialogFlags flags = GTK_DIALOG_DESTROY_WITH_PARENT;
561         GtkWidget *dialog = gtk_message_dialog_new(
562                 GTK_WINDOW(c->vbox), flags, GTK_MESSAGE_ERROR,
563                 GTK_BUTTONS_CLOSE, "ERROR: Web process %s crashed.",
564                 webkit_web_view_get_uri(WEBKIT_WEB_VIEW(web_view)),
565                 g_strerror(errno));
566         gtk_dialog_run(GTK_DIALOG(dialog));
567         gtk_widget_destroy(dialog);
568
569         return TRUE;
570 }
571 gboolean
572 decide_policy(WebKitWebView *web_view, WebKitPolicyDecision *decision,
573               WebKitPolicyDecisionType type, gpointer data)
574 {
575         WebKitResponsePolicyDecision *r;
576         switch (type) {
577         case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
578                 r = WEBKIT_RESPONSE_POLICY_DECISION(decision);
579                 if (!webkit_response_policy_decision_is_mime_type_supported(r))
580                         webkit_policy_decision_download(decision);
581                 else
582                         webkit_policy_decision_use(decision);
583                 break;
584         default:
585                 //Use whatever default there is.
586                 return FALSE;
587         }
588         return TRUE;
589 }
590
591 gchar *
592 ensure_uri_scheme(const gchar *t)
593 {
594         gchar *f, *fabs;
595         f = g_ascii_strdown(t, -1);
596         if (!g_str_has_prefix(f, "http:") && !g_str_has_prefix(f, "https:") &&
597             !g_str_has_prefix(f, "file:") && !g_str_has_prefix(f, "about:") &&
598             !g_str_has_prefix(f, "data:") && !g_str_has_prefix(f, "webkit:")) {
599                 g_free(f);
600                 fabs = realpath(t, NULL);
601                 if (fabs != NULL) {
602                         f = g_strdup_printf("file://%s", fabs);
603                         free(fabs);
604                 } else {
605                         f = g_strdup_printf("http://%s", t);
606                 }
607                 return f;
608         } else
609                 return g_strdup(t);
610 }
611
612 void
613 grab_feeds_finished(GObject *object, GAsyncResult *result, gpointer data)
614 {
615         struct Client *c = (struct Client *)data;
616         WebKitJavascriptResult *js_result;
617         JSCValue *value;
618         JSCException *exception;
619         GError *err = NULL;
620         gchar *str_value;
621         g_free(c->feed_html);
622         c->feed_html = NULL;
623
624         /*
625         * This was taken almost verbatim from the example in WebKit's
626         * documentation:
627         *
628         * https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html
629         */
630
631         js_result = webkit_web_view_run_javascript_finish(
632                 WEBKIT_WEB_VIEW(object), result, &err);
633         if (!js_result) {
634                 fprintf(stderr,
635                         "chorizo: error: error running javascript: %s\n",
636                         err->message);
637                 g_error_free(err);
638                 return;
639         }
640         value = webkit_javascript_result_get_js_value(js_result);
641         if (jsc_value_is_string(value)) {
642                 str_value = jsc_value_to_string(value);
643                 exception =
644                         jsc_context_get_exception(jsc_value_get_context(value));
645                 if (exception != NULL) {
646                         fprintf(stderr,
647                                 "chorizo: warning: error running javascript: %s\n",
648                                 jsc_exception_get_message(exception));
649                 } else {
650                         c->feed_html = str_value;
651                 }
652
653                 gtk_entry_set_icon_from_icon_name(
654                         GTK_ENTRY(c->location), GTK_ENTRY_ICON_SECONDARY,
655                         "application-rss+xml-symbolic");
656                 gtk_entry_set_icon_activatable(GTK_ENTRY(c->location),
657                                                GTK_ENTRY_ICON_SECONDARY, TRUE);
658         } else {
659                 gtk_entry_set_icon_from_icon_name(
660                         GTK_ENTRY(c->location), GTK_ENTRY_ICON_SECONDARY, NULL);
661         }
662
663         webkit_javascript_result_unref(js_result);
664 }
665
666 void
667 hover_web_view(WebKitWebView *web_view, WebKitHitTestResult *ht,
668                guint modifiers, gpointer data)
669 {
670         struct Client *c = (struct Client *)data;
671         const char *to_show;
672         g_free(c->hover_uri);
673
674         if (webkit_hit_test_result_context_is_link(ht)) {
675                 to_show = webkit_hit_test_result_get_link_uri(ht);
676                 c->hover_uri = g_strdup(to_show);
677         } else {
678                 to_show = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
679                 c->hover_uri = NULL;
680         }
681
682         if (!gtk_widget_is_focus(c->location)) set_uri(to_show, c);
683 }
684
685 void
686 icon_location(GtkEntry *entry, GtkEntryIconPosition icon_pos, GdkEvent *event,
687               gpointer data)
688 {
689         struct Client *c = (struct Client *)data;
690         gchar *d;
691         gchar *data_template = "data:text/html,"
692                                "<!DOCTYPE html>"
693                                "<html>"
694                                "    <head>"
695                                "        <meta charset=\"UTF-8\">"
696                                "        <title>Feeds</title>"
697                                "    </head>"
698                                "    <body>"
699                                "        <p>Feeds found on this page:</p>"
700                                "        <ul>"
701                                "        %s"
702                                "        </ul>"
703                                "    </body>"
704                                "</html>";
705         if (c->feed_html != NULL) {
706                 /*
707                 * What we're actually trying to do is show a simple HTML
708                 * page that lists all the feeds on the current page. The
709                 * function webkit_web_view_load_html() looks like the proper
710                 * way to do that. Sad thing is, it doesn't create a history
711                 * entry, but instead simply replaces the content of the
712                 * current page. This is not what we want.
713                 *
714                 * RFC 2397 [0] defines the data URI scheme [1]. We abuse this
715                 * mechanism to show my custom HTML snippet* and*create a
716                 * history entry.
717                 *
718                 * [0]: https://tools.ietf.org/html/rfc2397 [1]:
719                 * https://en.wikipedia.org/wiki/Data_URI_scheme
720                 */
721
722                 d = g_strdup_printf(data_template, c->feed_html);
723                 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), d);
724                 g_free(d);
725         }
726 }
727
728 void
729 init_default_web_context(void)
730 {
731         gchar *p;
732         WebKitWebContext *wc;
733         WebKitCookieManager *cm;
734         wc = (cfg.private) ? webkit_web_context_new_ephemeral() :
735                                    webkit_web_context_get_default();
736
737         p = g_build_filename(g_get_user_config_dir(), "chorizo", "adblock",
738                              NULL);
739         webkit_web_context_set_sandbox_enabled(wc, TRUE);
740         webkit_web_context_add_path_to_sandbox(wc, p, TRUE);
741         g_free(p);
742
743         WebKitProcessModel model =
744                 WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES;
745         webkit_web_context_set_process_model(wc, model);
746
747         p = g_build_filename(g_get_user_data_dir(), "chorizo", "web-extensions",
748                              NULL);
749         webkit_web_context_set_web_extensions_directory(wc, p);
750         g_free(p);
751
752         char *xdg_down = getenv("XDG_DOWNLOAD_DIR");
753         g_signal_connect(G_OBJECT(wc), "download-started",
754                          G_CALLBACK(download_start),
755                          (xdg_down) ? xdg_down : "/var/tmp");
756
757         trust_user_certs(wc);
758
759         cm = webkit_web_context_get_cookie_manager(wc);
760         webkit_cookie_manager_set_accept_policy(cm, cfg_cookie_policy);
761
762         if (!cfg.private) {
763                 webkit_web_context_set_favicon_database_directory(wc, NULL);
764
765                 gchar *fname = g_build_filename("/", g_get_user_data_dir(),
766                                                 "chorizo", "cookies.db", NULL);
767                 mkdirp(dirname(fname), 0700);
768                 WebKitCookiePersistentStorage type =
769                         WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE;
770                 webkit_cookie_manager_set_persistent_storage(cm, fname, type);
771                 g_free(fname);
772         }
773         webkit_web_context_set_spell_checking_enabled(wc, TRUE);
774 }
775
776 void
777 search(gpointer data, gint direction)
778 {
779         struct Client *c = (struct Client *)data;
780         WebKitWebView *web_view = WEBKIT_WEB_VIEW(c->web_view);
781         WebKitFindController *fc =
782                 webkit_web_view_get_find_controller(web_view);
783         if (search_text == NULL) return;
784
785         switch (direction) {
786         case 0:
787                 webkit_find_controller_search(
788                         fc, search_text,
789                         WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE |
790                                 WEBKIT_FIND_OPTIONS_WRAP_AROUND,
791                         G_MAXUINT);
792                 break;
793         case 1:
794                 webkit_find_controller_search_next(fc);
795                 break;
796         case -1:
797                 webkit_find_controller_search_previous(fc);
798                 break;
799         case 2:
800                 webkit_find_controller_search_finish(fc);
801                 break;
802         }
803 }
804
805 void
806 search_init(struct Client *c, int direction)
807 {
808         gtk_widget_grab_focus(c->location);
809         const gchar *contents = gtk_entry_get_text(GTK_ENTRY(c->location));
810         if (strcspn(contents, "s/")) {
811                 gtk_entry_set_text(GTK_ENTRY(c->location), "s/");
812                 gtk_editable_set_position(GTK_EDITABLE(c->location), -1);
813         } else {
814                 search(c, 0);
815                 search(c, -1);
816                 search(c, direction);
817         }
818 }
819 gboolean
820 key_common(GtkWidget *widget, GdkEvent *event, gpointer data)
821 {
822         struct Client *c = (struct Client *)data;
823         gdouble now;
824         if (event->type == GDK_KEY_PRESS) {
825                 if (((GdkEventKey *)event)->state & GDK_CONTROL_MASK) {
826                         const char *uri = webkit_web_view_get_uri(
827                                 WEBKIT_WEB_VIEW(c->web_view));
828                         int key = ((GdkEventKey *)event)->keyval;
829                         if (GDK_KEY_y == key) {
830                                 downloadmanager_show();
831                                 return TRUE;
832                         } else if (GDK_KEY_h == key) {
833                                 webkit_web_view_go_back(
834                                         WEBKIT_WEB_VIEW(c->web_view));
835                                 return TRUE;
836                         } else if (GDK_KEY_l == key) {
837                                 webkit_web_view_go_forward(
838                                         WEBKIT_WEB_VIEW(c->web_view));
839                                 return TRUE;
840                         } else if (GDK_KEY_o == key) {
841                                 gtk_widget_grab_focus(c->location);
842                                 return TRUE;
843                         } else if (GDK_KEY_Print == key) {
844                                 WebKitPrintOperation *operation =
845                                         webkit_print_operation_new(
846                                                 WEBKIT_WEB_VIEW(c->web_view));
847                                 GtkWidget *toplevel =
848                                         gtk_widget_get_toplevel(mw.win);
849                                 webkit_print_operation_run_dialog(
850                                         operation, GTK_WINDOW(toplevel));
851                                 return TRUE;
852                         } else if (GDK_KEY_g == key) {
853                                 search(c, 2);
854                                 gtk_widget_grab_focus(c->web_view);
855                                 gtk_editable_set_position(
856                                         GTK_EDITABLE(c->location), -1);
857                                 if (uri)
858                                         gtk_entry_set_text(
859                                                 GTK_ENTRY(c->location), uri);
860                                 webkit_web_view_run_javascript(
861                                         WEBKIT_WEB_VIEW(c->web_view),
862                                         "window.getSelection().removeAllRanges();"
863                                         "document.activeElement.blur();",
864                                         NULL, NULL, c);
865                                 return TRUE;
866                         } else if (GDK_KEY_semicolon == key) {
867                                 webkit_web_view_reload_bypass_cache(
868                                         WEBKIT_WEB_VIEW(c->web_view));
869                                 return TRUE;
870                         } else if (GDK_KEY_j == key) {
871                                 for (int i = 0; i <= cfg_scroll_lines - 1;
872                                      i++) {
873                                         event->key.keyval = GDK_KEY_Down;
874                                         gdk_event_put(event);
875                                 }
876                                 return TRUE;
877                         } else if (GDK_KEY_k == key) {
878                                 for (int i = 0; i <= cfg_scroll_lines - 1;
879                                      i++) {
880                                         event->key.keyval = GDK_KEY_Up;
881                                         gdk_event_put(event);
882                                 }
883                                 return TRUE;
884                         } else if (GDK_KEY_f == key) {
885                                 event->key.keyval = GDK_KEY_Page_Down;
886                                 gdk_event_put(event);
887                                 return TRUE;
888                         } else if (GDK_KEY_b == key) {
889                                 event->key.keyval = GDK_KEY_Page_Up;
890                                 gdk_event_put(event);
891                                 return TRUE;
892                         } else if (GDK_KEY_s == key) {
893                                 search_init(c, 1);
894                                 return TRUE;
895                         } else if (GDK_KEY_r == key) {
896                                 search_init(c, -1);
897                                 return TRUE;
898                         } else if (GDK_KEY_q == key) {
899                                 client_destroy(NULL, c);
900                                 return TRUE;
901                         } else if (GDK_KEY_1 == key) {
902                                 gtk_notebook_set_current_page(
903                                         GTK_NOTEBOOK(mw.notebook), 0);
904                                 return TRUE;
905                         } else if (GDK_KEY_2 == key) {
906                                 gtk_notebook_set_current_page(
907                                         GTK_NOTEBOOK(mw.notebook), 1);
908                                 return TRUE;
909                         } else if (GDK_KEY_3 == key) {
910                                 gtk_notebook_set_current_page(
911                                         GTK_NOTEBOOK(mw.notebook), 2);
912                                 return TRUE;
913                         } else if (GDK_KEY_4 == key) {
914                                 gtk_notebook_set_current_page(
915                                         GTK_NOTEBOOK(mw.notebook), 3);
916                                 return TRUE;
917                         } else if (GDK_KEY_5 == key) {
918                                 gtk_notebook_set_current_page(
919                                         GTK_NOTEBOOK(mw.notebook), 4);
920                                 return TRUE;
921                         } else if (GDK_KEY_6 == key) {
922                                 gtk_notebook_set_current_page(
923                                         GTK_NOTEBOOK(mw.notebook), 5);
924                                 return TRUE;
925                         } else if (GDK_KEY_7 == key) {
926                                 gtk_notebook_set_current_page(
927                                         GTK_NOTEBOOK(mw.notebook), 6);
928                                 return TRUE;
929                         } else if (GDK_KEY_8 == key) {
930                                 gtk_notebook_set_current_page(
931                                         GTK_NOTEBOOK(mw.notebook), 7);
932                                 return TRUE;
933                         } else if (GDK_KEY_9 == key) {
934                                 gtk_notebook_set_current_page(
935                                         GTK_NOTEBOOK(mw.notebook), 8);
936                                 return TRUE;
937                         } else if (GDK_KEY_u == key) {
938                                 gtk_notebook_prev_page(
939                                         GTK_NOTEBOOK(mw.notebook));
940                                 return TRUE;
941                         } else if (GDK_KEY_m == key) {
942                                 gboolean muted = webkit_web_view_get_is_muted(
943                                         WEBKIT_WEB_VIEW(c->web_view));
944                                 webkit_web_view_set_is_muted(
945                                         WEBKIT_WEB_VIEW(c->web_view), !muted);
946                                 changed_title(G_OBJECT(c->web_view), NULL, c);
947                                 return TRUE;
948                         } else if (GDK_KEY_t == key) {
949                                 client_new(cfg_home_uri, NULL, TRUE, TRUE);
950                                 return TRUE;
951                         } else if (GDK_KEY_bracketleft == key) {
952                                 if (num_closed == 0) return TRUE;
953                                 client_new(closed_tabs[num_closed - 1], NULL,
954                                            TRUE, TRUE);
955                                 num_closed--;
956                                 closed_tabs = realloc(
957                                         closed_tabs,
958                                         num_closed * sizeof(closed_tabs[0]));
959                                 if (!closed_tabs) allocfail();
960                                 return TRUE;
961                         } else if (GDK_KEY_i == key) {
962                                 gtk_notebook_next_page(
963                                         GTK_NOTEBOOK(mw.notebook));
964                                 return TRUE;
965                         } else if (GDK_KEY_p == key) {
966                                 gboolean on =
967                                         webkit_settings_get_enable_javascript(
968                                                 c->settings);
969                                 webkit_settings_set_enable_javascript(
970                                         c->settings, !on);
971                                 webkit_web_view_set_settings(
972                                         WEBKIT_WEB_VIEW(c->web_view),
973                                         c->settings);
974                                 gtk_toggle_button_set_active(
975                                         GTK_TOGGLE_BUTTON(c->jsbutton), !on);
976                         } else if (GDK_KEY_d == key) {
977                                 gtk_widget_grab_focus(c->location);
978                                 gtk_entry_set_text(GTK_ENTRY(c->location),
979                                                    "w/");
980                                 gtk_editable_set_position(
981                                         GTK_EDITABLE(c->location), -1);
982                                 return TRUE;
983                         } else if (GDK_KEY_equal == key) {
984                                 now = webkit_web_view_get_zoom_level(
985                                         WEBKIT_WEB_VIEW(c->web_view));
986                                 webkit_web_view_set_zoom_level(
987                                         WEBKIT_WEB_VIEW(c->web_view),
988                                         now + 0.1);
989                                 return TRUE;
990                         } else if (GDK_KEY_minus == key) {
991                                 now = webkit_web_view_get_zoom_level(
992                                         WEBKIT_WEB_VIEW(c->web_view));
993                                 webkit_web_view_set_zoom_level(
994                                         WEBKIT_WEB_VIEW(c->web_view),
995                                         now - 0.1);
996                                 return TRUE;
997                         } else if (GDK_KEY_0 == key) {
998                                 webkit_web_view_set_zoom_level(
999                                         WEBKIT_WEB_VIEW(c->web_view), 1);
1000                                 return TRUE;
1001                         }
1002                 }
1003         }
1004         return FALSE;
1005 }
1006 gboolean
1007 key_location(GtkWidget *widget, GdkEvent *event, gpointer data)
1008 {
1009         struct Client *c = (struct Client *)data;
1010         const gchar *t;
1011         if (key_common(widget, event, data)) return TRUE;
1012
1013         if (event->type == GDK_KEY_PRESS) {
1014                 int key = ((GdkEventKey *)event)->keyval;
1015                 if ((GDK_KEY_KP_Enter == key) || (GDK_KEY_Return == key)) {
1016                         gtk_widget_grab_focus(c->web_view);
1017                         t = gtk_entry_get_text(GTK_ENTRY(c->location));
1018                         if (t != NULL && t[0] == 's' && t[1] == '/') {
1019                                 if (search_text != NULL) g_free(search_text);
1020                                 search_text = g_strdup(t + 2);
1021                                 search(c, 0);
1022                         } else if (t != NULL && t[0] == 'w' && t[1] == '/') {
1023                                 int len = strlen(cfg_search_engine) +
1024                                           strlen(t) - 2;
1025                                 gchar *f = malloc(len + 1);
1026                                 if (!f) allocfail();
1027                                 snprintf(f, len + 1, "%s%s", cfg_search_engine,
1028                                          t + 2);
1029                                 webkit_web_view_load_uri(
1030                                         WEBKIT_WEB_VIEW(c->web_view), f);
1031                                 g_free(f);
1032                         } else {
1033                                 webkit_web_view_load_uri(
1034                                         WEBKIT_WEB_VIEW(c->web_view),
1035                                         ensure_uri_scheme(t));
1036                         }
1037                         return TRUE;
1038                 } else if (GDK_KEY_Escape == key) {
1039                         t = webkit_web_view_get_uri(
1040                                 WEBKIT_WEB_VIEW(c->web_view));
1041                         gtk_entry_set_text(GTK_ENTRY(c->location),
1042                                            (t == NULL) ? "" : t);
1043                         return TRUE;
1044                 }
1045         }
1046         return FALSE;
1047 }
1048 gboolean
1049 key_tablabel(GtkWidget *widget, GdkEvent *event, gpointer data)
1050 {
1051         GdkScrollDirection direction;
1052         if (event->type == GDK_BUTTON_RELEASE) {
1053                 switch (((GdkEventButton *)event)->button) {
1054                 case 2:
1055                         client_destroy(NULL, data);
1056                         return TRUE;
1057                 }
1058         } else if (event->type == GDK_SCROLL) {
1059                 gdk_event_get_scroll_direction(event, &direction);
1060                 switch (direction) {
1061                 case GDK_SCROLL_UP:
1062                         gtk_notebook_prev_page(GTK_NOTEBOOK(mw.notebook));
1063                         break;
1064                 case GDK_SCROLL_DOWN:
1065                         gtk_notebook_next_page(GTK_NOTEBOOK(mw.notebook));
1066                         break;
1067                 default:
1068                         break;
1069                 }
1070                 return TRUE;
1071         }
1072         return FALSE;
1073 }
1074 gboolean
1075 key_web_view(GtkWidget *widget, GdkEvent *event, gpointer data)
1076 {
1077         struct Client *c = (struct Client *)data;
1078         gdouble dx, dy;
1079         gfloat z;
1080         if (key_common(widget, event, data)) return TRUE;
1081
1082         if (event->type == GDK_KEY_PRESS) {
1083                 if (((GdkEventKey *)event)->keyval == GDK_KEY_Escape) {
1084                         webkit_web_view_stop_loading(
1085                                 WEBKIT_WEB_VIEW(c->web_view));
1086                         gtk_entry_set_progress_fraction(GTK_ENTRY(c->location),
1087                                                         0);
1088                 }
1089         } else if (event->type == GDK_BUTTON_RELEASE) {
1090                 GdkModifierType modifiers =
1091                         gtk_accelerator_get_default_mod_mask();
1092                 switch (((GdkEventButton *)event)->button) {
1093                 case 1:
1094                         if ((((GdkEventButton *)event)->state & modifiers) ==
1095                                     GDK_CONTROL_MASK &&
1096                             c->hover_uri != NULL) {
1097                                 client_new(c->hover_uri, NULL, TRUE, FALSE);
1098                                 return TRUE;
1099                         }
1100                         break;
1101                 case 8:
1102                         webkit_web_view_go_back(WEBKIT_WEB_VIEW(c->web_view));
1103                         return TRUE;
1104                 case 9:
1105                         webkit_web_view_go_forward(
1106                                 WEBKIT_WEB_VIEW(c->web_view));
1107                         return TRUE;
1108                 }
1109         } else if (event->type == GDK_SCROLL) {
1110                 event->scroll.delta_y *= cfg_scroll_lines;
1111                 if (((GdkEventScroll *)event)->state & GDK_CONTROL_MASK) {
1112                         gdk_event_get_scroll_deltas(event, &dx, &dy);
1113                         z = webkit_web_view_get_zoom_level(
1114                                 WEBKIT_WEB_VIEW(c->web_view));
1115                         z += -dy * 0.1;
1116                         z = dx != 0 ? 1 : z;
1117                         webkit_web_view_set_zoom_level(
1118                                 WEBKIT_WEB_VIEW(c->web_view), z);
1119                         return TRUE;
1120                 }
1121         }
1122         return FALSE;
1123 }
1124
1125 void
1126 mainwindow_setup(void)
1127 {
1128         mw.win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1129         gtk_window_set_default_size(GTK_WINDOW(mw.win), 800, 600);
1130         g_signal_connect(G_OBJECT(mw.win), "destroy", gtk_main_quit, NULL);
1131
1132         gchar *priv = (cfg.private) ? "-private" : "";
1133         gchar *title = malloc(strlen(priv) + 7);
1134         if (!title) allocfail();
1135         sprintf(title, "%s%s", "chorizo", priv);
1136         gtk_window_set_title(GTK_WINDOW(mw.win), title);
1137         g_free(title);
1138
1139         mw.notebook = gtk_notebook_new();
1140         gtk_notebook_set_scrollable(GTK_NOTEBOOK(mw.notebook), TRUE);
1141         gtk_container_add(GTK_CONTAINER(mw.win), mw.notebook);
1142         g_signal_connect(G_OBJECT(mw.notebook), "switch-page",
1143                          G_CALLBACK(notebook_switch_page), NULL);
1144 }
1145
1146 void
1147 mainwindow_title(gint idx)
1148 {
1149         GtkWidget *child, *widg, *tablabel;
1150         const gchar *text;
1151         child = gtk_notebook_get_nth_page(GTK_NOTEBOOK(mw.notebook), idx);
1152         if (child == NULL) return;
1153
1154         widg = gtk_notebook_get_tab_label(GTK_NOTEBOOK(mw.notebook), child);
1155         tablabel = (GtkWidget *)g_object_get_data(G_OBJECT(widg),
1156                                                   "chorizo-tab-label");
1157         text = gtk_label_get_text(GTK_LABEL(tablabel));
1158         gtk_window_set_title(GTK_WINDOW(mw.win), text);
1159 }
1160
1161 void
1162 notebook_switch_page(GtkNotebook *nb, GtkWidget *p, guint idx, gpointer data)
1163 {
1164         mainwindow_title(idx);
1165 }
1166 gboolean
1167 quit_if_nothing_active(void)
1168 {
1169         if (clients == 0) {
1170                 if (downloads == 0) {
1171                         gtk_main_quit();
1172                         return TRUE;
1173                 } else {
1174                         downloadmanager_show();
1175                 }
1176         }
1177         return FALSE;
1178 }
1179 gboolean
1180 remote_msg(GIOChannel *channel, GIOCondition condition, gpointer data)
1181 {
1182         gchar *uri = NULL;
1183         g_io_channel_read_line(channel, &uri, NULL, NULL, NULL);
1184         if (uri) {
1185                 g_strstrip(uri);
1186                 client_new(uri, NULL, TRUE, TRUE);
1187                 g_free(uri);
1188         }
1189         return TRUE;
1190 }
1191
1192 void
1193 show_web_view(WebKitWebView *web_view, gpointer data)
1194 {
1195         struct Client *c = (struct Client *)data;
1196         gint idx;
1197         (void)web_view;
1198
1199         gtk_widget_show_all(mw.win);
1200
1201         if (c->focus_new_tab) {
1202                 idx = gtk_notebook_page_num(GTK_NOTEBOOK(mw.notebook), c->vbox);
1203                 if (idx != -1)
1204                         gtk_notebook_set_current_page(GTK_NOTEBOOK(mw.notebook),
1205                                                       idx);
1206
1207                 gtk_widget_grab_focus(c->web_view);
1208         }
1209 }
1210
1211 void
1212 trust_user_certs(WebKitWebContext *wc)
1213 {
1214         GTlsCertificate *cert;
1215         gchar *basedir, *absfile;
1216         const gchar *file;
1217         GDir *dir = NULL;
1218         basedir = g_build_filename(g_get_user_data_dir(), "chorizo", "certs",
1219                                    NULL);
1220         dir = g_dir_open(basedir, 0, NULL);
1221         g_free(basedir);
1222         if (dir != NULL) {
1223                 file = g_dir_read_name(dir);
1224                 while (file != NULL) {
1225                         absfile = g_build_filename(g_get_user_data_dir(),
1226                                                    "chorizo", "certs", file,
1227                                                    NULL);
1228                         cert = g_tls_certificate_new_from_file(absfile, NULL);
1229                         g_free(absfile);
1230                         if (cert == NULL)
1231                                 fprintf(stderr,
1232                                         "chorizo: warning: could not load trusted cert: %s\n",
1233                                         file);
1234                         else
1235                                 webkit_web_context_allow_tls_certificate_for_host(
1236                                         wc, cert, file);
1237                         file = g_dir_read_name(dir);
1238                 }
1239                 g_dir_close(dir);
1240         }
1241 }
1242
1243 void
1244 version(void)
1245 {
1246         printf("%s %s\n", "chorizo", VERSION);
1247 }
1248
1249 int
1250 main(int argc, char **argv)
1251 {
1252         int opt, i;
1253
1254         //TODO:pretty this
1255         cfg.noncooperative_instances = FALSE;
1256         cfg.cooperative_alone = TRUE;
1257         closed_tabs = malloc(0);
1258         if (!closed_tabs) allocfail();
1259
1260         while ((opt = getopt(argc, argv, "cpvV")) != -1) {
1261                 switch (opt) {
1262                 case 'c':
1263                         cfg.noncooperative_instances = TRUE;
1264                         break;
1265                 case 'p':
1266                         cfg.private = TRUE;
1267                         break;
1268                 case 'v':
1269                         cfg.verbose = TRUE;
1270                         break;
1271                 case 'V':
1272                         version();
1273                         exit(0);
1274                 default:
1275                         fprintf(stderr,
1276                                 "usage: chorizo [OPTION]... [URI]...\n");
1277                         exit(EXIT_FAILURE);
1278                 }
1279         }
1280
1281         if (cfg.verbose) version();
1282
1283         gtk_init(&argc, &argv);
1284
1285         // Keep clipboard contents after program closes
1286         gtk_clipboard_store(gtk_clipboard_get_for_display(
1287                 gdk_display_get_default(), GDK_SELECTION_CLIPBOARD));
1288
1289         if (!cfg.noncooperative_instances) cooperation_setup();
1290
1291         if (cfg.noncooperative_instances || cfg.cooperative_alone)
1292                 init_default_web_context();
1293
1294         downloadmanager_setup();
1295         mainwindow_setup();
1296
1297         client_arr = malloc(sizeof(struct Client *));
1298         if (!client_arr) allocfail();
1299
1300         if (optind >= argc) {
1301                 client_new(cfg_home_uri, NULL, TRUE, TRUE);
1302         } else {
1303                 for (i = optind; i < argc; i++)
1304                         client_new(argv[i], NULL, TRUE, TRUE);
1305         }
1306
1307         if (cfg.noncooperative_instances || cfg.cooperative_alone) {
1308                 gtk_main();
1309                 remove(fifopath);
1310         }
1311         for (int i = 0; i < clients; i++) { free(&(client_arr[i])); }
1312
1313         exit(EXIT_SUCCESS);
1314 }