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