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