]> git.armaanb.net Git - chorizo.git/blob - browser.c
6c4e849a79b655ec64a831f0d3dd347fe8851a4a
[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
15
16 static void client_destroy(GtkWidget *, gpointer);
17 static gboolean client_destroy_request(WebKitWebView *, gpointer);
18 static WebKitWebView *client_new(const gchar *, WebKitWebView *, gboolean);
19 static WebKitWebView *client_new_request(WebKitWebView *, WebKitNavigationAction *,
20                                          gpointer);
21 static void cooperation_setup(void);
22 static void changed_download_progress(GObject *, GParamSpec *, gpointer);
23 static void changed_load_progress(GObject *, GParamSpec *, gpointer);
24 static void changed_title(GObject *, GParamSpec *, gpointer);
25 static void changed_uri(GObject *, GParamSpec *, gpointer);
26 static gboolean crashed_web_view(WebKitWebView *, gpointer);
27 static gboolean decide_policy(WebKitWebView *, WebKitPolicyDecision *,
28                               WebKitPolicyDecisionType, gpointer);
29 static gboolean download_handle(WebKitDownload *, gchar *, gpointer);
30 static void download_handle_start(WebKitWebView *, WebKitDownload *, gpointer);
31 static void downloadmanager_cancel(GtkToolButton *, gpointer data);
32 static void downloadmanager_setup(void);
33 static gchar *ensure_uri_scheme(const gchar *);
34 static void external_handler_run(GtkAction *, gpointer);
35 static void grab_environment_configuration(void);
36 static void hover_web_view(WebKitWebView *, WebKitHitTestResult *, guint, gpointer);
37 static gboolean key_common(GtkWidget *, GdkEvent *, gpointer);
38 static gboolean key_downloadmanager(GtkWidget *, GdkEvent *, gpointer);
39 static gboolean key_location(GtkWidget *, GdkEvent *, gpointer);
40 static gboolean key_web_view(GtkWidget *, GdkEvent *, gpointer);
41 static void keywords_load(void);
42 static gboolean keywords_try_search(WebKitWebView *, const gchar *);
43 static gboolean menu_web_view(WebKitWebView *, WebKitContextMenu *, GdkEvent *,
44                               WebKitHitTestResult *, gpointer);
45 static gboolean remote_msg(GIOChannel *, GIOCondition, gpointer);
46 static void search(gpointer, gint);
47 static void show_web_view(WebKitWebView *, gpointer);
48 static Window tabbed_launch(void);
49 static void trust_user_certs(WebKitWebContext *);
50
51
52 struct Client
53 {
54     gchar *external_handler_uri;
55     gchar *hover_uri;
56     GtkWidget *location;
57     GtkWidget *vbox;
58     GtkWidget *web_view;
59     GtkWidget *win;
60 };
61
62 struct DownloadManager
63 {
64     GtkWidget *scroll;
65     GtkWidget *toolbar;
66     GtkWidget *win;
67 } dm;
68
69
70 static const gchar *accepted_language[2] = { NULL, NULL };
71 static gint clients = 0;
72 static gboolean cooperative_alone = TRUE;
73 static gboolean cooperative_instances = TRUE;
74 static int cooperative_pipe_fp = 0;
75 static gchar *download_dir = "/var/tmp";
76 static Window embed = 0;
77 static gchar *fifo_suffix = "main";
78 static gdouble global_zoom = 1.0;
79 static gchar *history_file = NULL;
80 static gchar *home_uri = "about:blank";
81 static gboolean initial_wc_setup_done = FALSE;
82 static GHashTable *keywords = NULL;
83 static gchar *search_text = NULL;
84 static gboolean tabbed_automagic = TRUE;
85 static gchar *user_agent = NULL;
86
87
88 void
89 client_destroy(GtkWidget *obj, gpointer data)
90 {
91     struct Client *c = (struct Client *)data;
92
93     g_signal_handlers_disconnect_by_func(G_OBJECT(c->web_view),
94                                          changed_load_progress, c);
95
96     free(c);
97     clients--;
98
99     if (clients == 0)
100         gtk_main_quit();
101 }
102
103 gboolean
104 client_destroy_request(WebKitWebView *web_view, gpointer data)
105 {
106     struct Client *c = (struct Client *)data;
107
108     gtk_widget_destroy(c->win);
109
110     return TRUE;
111 }
112
113 WebKitWebView *
114 client_new(const gchar *uri, WebKitWebView *related_wv, gboolean show)
115 {
116     struct Client *c;
117     WebKitWebContext *wc;
118     gchar *f;
119
120     if (uri != NULL && cooperative_instances && !cooperative_alone)
121     {
122         f = ensure_uri_scheme(uri);
123         write(cooperative_pipe_fp, f, strlen(f));
124         write(cooperative_pipe_fp, "\n", 1);
125         g_free(f);
126         return NULL;
127     }
128
129     c = calloc(1, sizeof(struct Client));
130     if (!c)
131     {
132         fprintf(stderr, __NAME__": fatal: calloc failed\n");
133         exit(EXIT_FAILURE);
134     }
135
136     if (embed != 0)
137     {
138         c->win = gtk_plug_new(embed);
139         if (!gtk_plug_get_embedded(GTK_PLUG(c->win)))
140         {
141             fprintf(stderr, __NAME__": Can't plug-in to XID %ld.\n", embed);
142             gtk_widget_destroy(c->win);
143             c->win = NULL;
144             embed = 0;
145         }
146     }
147
148     if (c->win == NULL)
149         c->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
150
151     gtk_window_set_default_size(GTK_WINDOW(c->win), 800, 600);
152
153     g_signal_connect(G_OBJECT(c->win), "destroy", G_CALLBACK(client_destroy), c);
154     gtk_window_set_title(GTK_WINDOW(c->win), __NAME__);
155
156     if (related_wv == NULL)
157         c->web_view = webkit_web_view_new();
158     else
159         c->web_view = webkit_web_view_new_with_related_view(related_wv);
160     wc = webkit_web_view_get_context(WEBKIT_WEB_VIEW(c->web_view));
161
162     webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(c->web_view), global_zoom);
163     g_signal_connect(G_OBJECT(c->web_view), "notify::title",
164                      G_CALLBACK(changed_title), c);
165     g_signal_connect(G_OBJECT(c->web_view), "notify::uri",
166                      G_CALLBACK(changed_uri), c);
167     g_signal_connect(G_OBJECT(c->web_view), "notify::estimated-load-progress",
168                      G_CALLBACK(changed_load_progress), c);
169     g_signal_connect(G_OBJECT(c->web_view), "create",
170                      G_CALLBACK(client_new_request), NULL);
171     g_signal_connect(G_OBJECT(c->web_view), "context-menu",
172                      G_CALLBACK(menu_web_view), c);
173     g_signal_connect(G_OBJECT(c->web_view), "close",
174                      G_CALLBACK(client_destroy_request), c);
175     g_signal_connect(G_OBJECT(c->web_view), "decide-policy",
176                      G_CALLBACK(decide_policy), NULL);
177     g_signal_connect(G_OBJECT(c->web_view), "key-press-event",
178                      G_CALLBACK(key_web_view), c);
179     g_signal_connect(G_OBJECT(c->web_view), "button-press-event",
180                      G_CALLBACK(key_web_view), c);
181     g_signal_connect(G_OBJECT(c->web_view), "scroll-event",
182                      G_CALLBACK(key_web_view), c);
183     g_signal_connect(G_OBJECT(c->web_view), "mouse-target-changed",
184                      G_CALLBACK(hover_web_view), c);
185     g_signal_connect(G_OBJECT(c->web_view), "web-process-crashed",
186                      G_CALLBACK(crashed_web_view), c);
187
188     if (!initial_wc_setup_done)
189     {
190         if (accepted_language[0] != NULL)
191             webkit_web_context_set_preferred_languages(wc, accepted_language);
192
193         g_signal_connect(G_OBJECT(wc), "download-started",
194                          G_CALLBACK(download_handle_start), NULL);
195
196         trust_user_certs(wc);
197
198         initial_wc_setup_done = TRUE;
199     }
200
201     if (user_agent != NULL)
202         g_object_set(G_OBJECT(webkit_web_view_get_settings(WEBKIT_WEB_VIEW(c->web_view))),
203                      "user-agent", user_agent, NULL);
204
205     c->location = gtk_entry_new();
206     g_signal_connect(G_OBJECT(c->location), "key-press-event",
207                      G_CALLBACK(key_location), c);
208
209     c->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
210     gtk_box_pack_start(GTK_BOX(c->vbox), c->location, FALSE, FALSE, 0);
211     gtk_box_pack_start(GTK_BOX(c->vbox), c->web_view, TRUE, TRUE, 0);
212
213     gtk_container_add(GTK_CONTAINER(c->win), c->vbox);
214
215     if (show)
216         show_web_view(NULL, c);
217     else
218         g_signal_connect(G_OBJECT(c->web_view), "ready-to-show",
219                          G_CALLBACK(show_web_view), c);
220
221     if (uri != NULL)
222     {
223         f = ensure_uri_scheme(uri);
224         webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), f);
225         g_free(f);
226     }
227
228     clients++;
229
230     return WEBKIT_WEB_VIEW(c->web_view);
231 }
232
233 WebKitWebView *
234 client_new_request(WebKitWebView *web_view,
235                    WebKitNavigationAction *navigation_action, gpointer data)
236 {
237     return client_new(NULL, web_view, FALSE);
238 }
239
240 void
241 cooperation_setup(void)
242 {
243     GIOChannel *towatch;
244     gchar *fifofilename, *fifopath;
245
246     fifofilename = g_strdup_printf("%s-%s", __NAME__".fifo", fifo_suffix);
247     fifopath = g_build_filename(g_get_user_runtime_dir(), fifofilename, NULL);
248     g_free(fifofilename);
249
250     if (!g_file_test(fifopath, G_FILE_TEST_EXISTS))
251         mkfifo(fifopath, 0600);
252
253     cooperative_pipe_fp = open(fifopath, O_WRONLY | O_NONBLOCK);
254     if (!cooperative_pipe_fp)
255     {
256         fprintf(stderr, __NAME__": Can't open FIFO at all.\n");
257     }
258     else
259     {
260         if (write(cooperative_pipe_fp, "", 0) == -1)
261         {
262             /* Could not do an empty write to the FIFO which means there's
263              * no one listening. */
264             close(cooperative_pipe_fp);
265             towatch = g_io_channel_new_file(fifopath, "r+", NULL);
266             g_io_add_watch(towatch, G_IO_IN, (GIOFunc)remote_msg, NULL);
267         }
268         else
269             cooperative_alone = FALSE;
270     }
271
272     g_free(fifopath);
273 }
274
275 void
276 changed_download_progress(GObject *obj, GParamSpec *pspec, gpointer data)
277 {
278     WebKitDownload *download = WEBKIT_DOWNLOAD(obj);
279     WebKitURIResponse *resp;
280     GtkToolItem *tb = GTK_TOOL_ITEM(data);
281     gdouble p, size_mb;
282     const gchar *uri;
283     gchar *t, *filename, *base;
284
285     p = webkit_download_get_estimated_progress(download);
286     p = p > 1 ? 1 : p;
287     p = p < 0 ? 0 : p;
288     p *= 100;
289     resp = webkit_download_get_response(download);
290     size_mb = webkit_uri_response_get_content_length(resp) / 1e6;
291
292     uri = webkit_download_get_destination(download);
293     filename = g_filename_from_uri(uri, NULL, NULL);
294     if (filename == NULL)
295     {
296         /* This really should not happen because WebKit uses that URI to
297          * write to a file... */
298         fprintf(stderr, __NAME__": Could not construct file name from URI!\n");
299         t = g_strdup_printf("%s (%.0f%% of %.1f MB)",
300                             webkit_uri_response_get_uri(resp), p, size_mb);
301     }
302     else
303     {
304         base = g_path_get_basename(filename);
305         t = g_strdup_printf("%s (%.0f%% of %.1f MB)", base, p, size_mb);
306         g_free(filename);
307         g_free(base);
308     }
309     gtk_tool_button_set_label(GTK_TOOL_BUTTON(tb), t);
310     g_free(t);
311 }
312
313 void
314 changed_load_progress(GObject *obj, GParamSpec *pspec, gpointer data)
315 {
316     struct Client *c = (struct Client *)data;
317     gdouble p;
318
319     p = webkit_web_view_get_estimated_load_progress(WEBKIT_WEB_VIEW(c->web_view));
320     gtk_entry_set_progress_fraction(GTK_ENTRY(c->location), p);
321 }
322
323 void
324 changed_title(GObject *obj, GParamSpec *pspec, gpointer data)
325 {
326     const gchar *t, *u;
327     struct Client *c = (struct Client *)data;
328
329     u = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
330     t = webkit_web_view_get_title(WEBKIT_WEB_VIEW(c->web_view));
331
332     u = u == NULL ? __NAME__ : u;
333     u = u[0] == 0 ? __NAME__ : u;
334
335     t = t == NULL ? u : t;
336     t = t[0] == 0 ? u : t;
337
338     gtk_window_set_title(GTK_WINDOW(c->win), t);
339 }
340
341 void
342 changed_uri(GObject *obj, GParamSpec *pspec, gpointer data)
343 {
344     const gchar *t;
345     struct Client *c = (struct Client *)data;
346     FILE *fp;
347
348     t = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
349
350     /* When a web process crashes, we get a "notify::uri" signal, but we
351      * can no longer read a meaningful URI. It's just an empty string
352      * now. Not updating the location bar in this scenario is important,
353      * because we would override the "WEB PROCESS CRASHED" message. */
354     if (t != NULL && strlen(t) > 0)
355     {
356         gtk_entry_set_text(GTK_ENTRY(c->location), t);
357
358         if (history_file != NULL)
359         {
360             fp = fopen(history_file, "a");
361             if (fp != NULL)
362             {
363                 fprintf(fp, "%s\n", t);
364                 fclose(fp);
365             }
366             else
367                 perror(__NAME__": Error opening history file");
368         }
369     }
370 }
371
372 gboolean
373 crashed_web_view(WebKitWebView *web_view, gpointer data)
374 {
375     gchar *t;
376     struct Client *c = (struct Client *)data;
377
378     t = g_strdup_printf("WEB PROCESS CRASHED: %s",
379                         webkit_web_view_get_uri(WEBKIT_WEB_VIEW(web_view)));
380     gtk_entry_set_text(GTK_ENTRY(c->location), t);
381     g_free(t);
382
383     return TRUE;
384 }
385
386 gboolean
387 decide_policy(WebKitWebView *web_view, WebKitPolicyDecision *decision,
388               WebKitPolicyDecisionType type, gpointer data)
389 {
390     WebKitResponsePolicyDecision *r;
391
392     switch (type)
393     {
394         case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
395             r = WEBKIT_RESPONSE_POLICY_DECISION(decision);
396             if (!webkit_response_policy_decision_is_mime_type_supported(r))
397                 webkit_policy_decision_download(decision);
398             else
399                 webkit_policy_decision_use(decision);
400             break;
401         default:
402             /* Use whatever default there is. */
403             return FALSE;
404     }
405     return TRUE;
406 }
407
408 void
409 download_handle_start(WebKitWebView *web_view, WebKitDownload *download,
410                       gpointer data)
411 {
412     g_signal_connect(G_OBJECT(download), "decide-destination",
413                      G_CALLBACK(download_handle), data);
414 }
415
416 gboolean
417 download_handle(WebKitDownload *download, gchar *suggested_filename, gpointer data)
418 {
419     gchar *sug_clean, *path, *path2 = NULL, *uri;
420     GtkToolItem *tb;
421     int suffix = 1;
422     size_t i;
423
424     sug_clean = g_strdup(suggested_filename);
425     for (i = 0; i < strlen(sug_clean); i++)
426         if (sug_clean[i] == G_DIR_SEPARATOR)
427             sug_clean[i] = '_';
428
429     path = g_build_filename(download_dir, sug_clean, NULL);
430     path2 = g_strdup(path);
431     while (g_file_test(path2, G_FILE_TEST_EXISTS) && suffix < 1000)
432     {
433         g_free(path2);
434
435         path2 = g_strdup_printf("%s.%d", path, suffix);
436         suffix++;
437     }
438
439     if (suffix == 1000)
440     {
441         fprintf(stderr, __NAME__": Suffix reached limit for download.\n");
442         webkit_download_cancel(download);
443     }
444     else
445     {
446         uri = g_filename_to_uri(path2, NULL, NULL);
447         webkit_download_set_destination(download, uri);
448         g_free(uri);
449
450         tb = gtk_tool_button_new(NULL, NULL);
451         gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(tb), "gtk-delete");
452         gtk_tool_button_set_label(GTK_TOOL_BUTTON(tb), sug_clean);
453         gtk_toolbar_insert(GTK_TOOLBAR(dm.toolbar), tb, 0);
454         gtk_widget_show_all(dm.win);
455
456         g_signal_connect(G_OBJECT(download), "notify::estimated-progress",
457                          G_CALLBACK(changed_download_progress), tb);
458
459         g_object_ref(download);
460         g_signal_connect(G_OBJECT(tb), "clicked",
461                          G_CALLBACK(downloadmanager_cancel), download);
462     }
463
464     g_free(sug_clean);
465     g_free(path);
466     g_free(path2);
467
468     /* Propagate -- to whom it may concern. */
469     return FALSE;
470 }
471
472 void
473 downloadmanager_cancel(GtkToolButton *tb, gpointer data)
474 {
475     WebKitDownload *download = WEBKIT_DOWNLOAD(data);
476
477     webkit_download_cancel(download);
478     g_object_unref(download);
479
480     gtk_widget_destroy(GTK_WIDGET(tb));
481 }
482
483 void
484 downloadmanager_setup(void)
485 {
486     dm.win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
487     gtk_window_set_type_hint(GTK_WINDOW(dm.win), GDK_WINDOW_TYPE_HINT_DIALOG);
488     gtk_window_set_default_size(GTK_WINDOW(dm.win), 500, 250);
489     gtk_window_set_title(GTK_WINDOW(dm.win), __NAME__" - Download Manager");
490     g_signal_connect(G_OBJECT(dm.win), "delete-event",
491                      G_CALLBACK(gtk_widget_hide_on_delete), NULL);
492     g_signal_connect(G_OBJECT(dm.win), "key-press-event",
493                      G_CALLBACK(key_downloadmanager), NULL);
494
495     dm.toolbar = gtk_toolbar_new();
496     gtk_orientable_set_orientation(GTK_ORIENTABLE(dm.toolbar),
497                                    GTK_ORIENTATION_VERTICAL);
498     gtk_toolbar_set_style(GTK_TOOLBAR(dm.toolbar), GTK_TOOLBAR_BOTH_HORIZ);
499     gtk_toolbar_set_show_arrow(GTK_TOOLBAR(dm.toolbar), FALSE);
500
501     dm.scroll = gtk_scrolled_window_new(NULL, NULL);
502     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dm.scroll),
503                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
504     gtk_container_add(GTK_CONTAINER(dm.scroll), dm.toolbar);
505
506     gtk_container_add(GTK_CONTAINER(dm.win), dm.scroll);
507 }
508
509 gchar *
510 ensure_uri_scheme(const gchar *t)
511 {
512     gchar *f, *fabs;
513
514     f = g_ascii_strdown(t, -1);
515     if (!g_str_has_prefix(f, "http:") &&
516         !g_str_has_prefix(f, "https:") &&
517         !g_str_has_prefix(f, "file:") &&
518         !g_str_has_prefix(f, "about:"))
519     {
520         g_free(f);
521         fabs = realpath(t, NULL);
522         if (fabs != NULL)
523         {
524             f = g_strdup_printf("file://%s", fabs);
525             free(fabs);
526         }
527         else
528             f = g_strdup_printf("http://%s", t);
529         return f;
530     }
531     else
532         return g_strdup(t);
533 }
534
535 void
536 external_handler_run(GtkAction *action, gpointer data)
537 {
538     struct Client *c = (struct Client *)data;
539     gchar *argv[] = { "lariza-external-handler", "-u", NULL, NULL };
540     GPid pid;
541     GError *err = NULL;
542
543     (void)action;
544
545     argv[2] = c->external_handler_uri;
546     if (!g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
547                        &pid, &err))
548     {
549         fprintf(stderr, __NAME__": Could not launch key handler: %s\n",
550                 err->message);
551         g_error_free(err);
552     }
553     else
554         g_spawn_close_pid(pid);
555 }
556
557 void
558 grab_environment_configuration(void)
559 {
560     const gchar *e;
561
562     e = g_getenv(__NAME_UPPERCASE__"_ACCEPTED_LANGUAGE");
563     if (e != NULL)
564         accepted_language[0] = g_strdup(e);
565
566     e = g_getenv(__NAME_UPPERCASE__"_DOWNLOAD_DIR");
567     if (e != NULL)
568         download_dir = g_strdup(e);
569
570     e = g_getenv(__NAME_UPPERCASE__"_FIFO_SUFFIX");
571     if (e != NULL)
572         fifo_suffix = g_strdup(e);
573
574     e = g_getenv(__NAME_UPPERCASE__"_HISTORY_FILE");
575     if (e != NULL)
576         history_file = g_strdup(e);
577
578     e = g_getenv(__NAME_UPPERCASE__"_HOME_URI");
579     if (e != NULL)
580         home_uri = g_strdup(e);
581
582     e = g_getenv(__NAME_UPPERCASE__"_USER_AGENT");
583     if (e != NULL)
584         user_agent = g_strdup(e);
585
586     e = g_getenv(__NAME_UPPERCASE__"_ZOOM");
587     if (e != NULL)
588         global_zoom = atof(e);
589 }
590
591 void
592 hover_web_view(WebKitWebView *web_view, WebKitHitTestResult *ht, guint modifiers,
593                gpointer data)
594 {
595     struct Client *c = (struct Client *)data;
596
597     if (!gtk_widget_is_focus(c->location))
598     {
599         if (webkit_hit_test_result_context_is_link(ht))
600         {
601             gtk_entry_set_text(GTK_ENTRY(c->location),
602                                webkit_hit_test_result_get_link_uri(ht));
603
604             if (c->hover_uri != NULL)
605                 g_free(c->hover_uri);
606             c->hover_uri = g_strdup(webkit_hit_test_result_get_link_uri(ht));
607         }
608         else
609         {
610             gtk_entry_set_text(GTK_ENTRY(c->location),
611                                webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view)));
612
613             if (c->hover_uri != NULL)
614                 g_free(c->hover_uri);
615             c->hover_uri = NULL;
616         }
617     }
618 }
619
620 gboolean
621 key_common(GtkWidget *widget, GdkEvent *event, gpointer data)
622 {
623     struct Client *c = (struct Client *)data;
624     WebKitWebContext *wc = webkit_web_view_get_context(WEBKIT_WEB_VIEW(c->web_view));
625     gchar *f;
626
627     if (event->type == GDK_KEY_PRESS)
628     {
629         if (((GdkEventKey *)event)->state & GDK_MOD1_MASK)
630         {
631             switch (((GdkEventKey *)event)->keyval)
632             {
633                 case GDK_KEY_q:  /* close window (left hand) */
634                     gtk_widget_destroy(c->win);
635                     return TRUE;
636                 case GDK_KEY_w:  /* home (left hand) */
637                     f = ensure_uri_scheme(home_uri);
638                     webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), f);
639                     g_free(f);
640                     return TRUE;
641                 case GDK_KEY_e:  /* new tab (left hand) */
642                     f = ensure_uri_scheme(home_uri);
643                     client_new(f, NULL, TRUE);
644                     g_free(f);
645                     return TRUE;
646                 case GDK_KEY_r:  /* reload (left hand) */
647                     webkit_web_view_reload_bypass_cache(WEBKIT_WEB_VIEW(
648                                                         c->web_view));
649                     return TRUE;
650                 case GDK_KEY_d:  /* download manager (left hand) */
651                     gtk_widget_show_all(dm.win);
652                     return TRUE;
653                 case GDK_KEY_2:  /* search forward (left hand) */
654                 case GDK_KEY_n:  /* search forward (maybe both hands) */
655                     search(c, 1);
656                     return TRUE;
657                 case GDK_KEY_3:  /* search backward (left hand) */
658                     search(c, -1);
659                     return TRUE;
660                 case GDK_KEY_l:  /* location (BOTH hands) */
661                     gtk_widget_grab_focus(c->location);
662                     return TRUE;
663                 case GDK_KEY_k:  /* initiate search (BOTH hands) */
664                     gtk_widget_grab_focus(c->location);
665                     gtk_entry_set_text(GTK_ENTRY(c->location), ":/");
666                     gtk_editable_set_position(GTK_EDITABLE(c->location), -1);
667                     return TRUE;
668                 case GDK_KEY_c:  /* reload trusted certs (left hand) */
669                     trust_user_certs(wc);
670                     return TRUE;
671                 case GDK_KEY_x:  /* launch external handler (left hand) */
672                     if (c->external_handler_uri != NULL)
673                         g_free(c->external_handler_uri);
674                     c->external_handler_uri = g_strdup(
675                         webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view)));
676                     external_handler_run(NULL, c);
677                     return TRUE;
678             }
679         }
680         /* navigate backward (left hand) */
681         else if (((GdkEventKey *)event)->keyval == GDK_KEY_F2)
682         {
683             webkit_web_view_go_back(WEBKIT_WEB_VIEW(c->web_view));
684             return TRUE;
685         }
686         /* navigate forward (left hand) */
687         else if (((GdkEventKey *)event)->keyval == GDK_KEY_F3)
688         {
689             webkit_web_view_go_forward(WEBKIT_WEB_VIEW(c->web_view));
690             return TRUE;
691         }
692     }
693
694     return FALSE;
695 }
696
697 gboolean
698 key_downloadmanager(GtkWidget *widget, GdkEvent *event, gpointer data)
699 {
700     if (event->type == GDK_KEY_PRESS)
701     {
702         if (((GdkEventKey *)event)->state & GDK_MOD1_MASK)
703         {
704             switch (((GdkEventKey *)event)->keyval)
705             {
706                 case GDK_KEY_d:  /* close window (left hand) */
707                     gtk_widget_hide(dm.win);
708                     return TRUE;
709             }
710         }
711     }
712
713     return FALSE;
714 }
715
716 gboolean
717 key_location(GtkWidget *widget, GdkEvent *event, gpointer data)
718 {
719     struct Client *c = (struct Client *)data;
720     const gchar *t;
721     gchar *f;
722
723     if (key_common(widget, event, data))
724         return TRUE;
725
726     if (event->type == GDK_KEY_PRESS)
727     {
728         switch (((GdkEventKey *)event)->keyval)
729         {
730             case GDK_KEY_KP_Enter:
731             case GDK_KEY_Return:
732                 gtk_widget_grab_focus(c->web_view);
733                 t = gtk_entry_get_text(GTK_ENTRY(c->location));
734                 if (t != NULL && t[0] == ':' && t[1] == '/')
735                 {
736                     if (search_text != NULL)
737                         g_free(search_text);
738                     search_text = g_strdup(t + 2);  /* XXX whacky */
739                     search(c, 0);
740                 }
741                 else if (!keywords_try_search(WEBKIT_WEB_VIEW(c->web_view), t))
742                 {
743                     f = ensure_uri_scheme(t);
744                     webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), f);
745                     g_free(f);
746                 }
747                 return TRUE;
748             case GDK_KEY_Escape:
749                 t = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
750                 gtk_entry_set_text(GTK_ENTRY(c->location),
751                                    (t == NULL ? __NAME__ : t));
752                 return TRUE;
753         }
754     }
755
756     return FALSE;
757 }
758
759 gboolean
760 key_web_view(GtkWidget *widget, GdkEvent *event, gpointer data)
761 {
762     struct Client *c = (struct Client *)data;
763     gdouble dx, dy;
764     gfloat z;
765
766     if (key_common(widget, event, data))
767         return TRUE;
768
769     if (event->type == GDK_KEY_PRESS)
770     {
771         if (((GdkEventKey *)event)->keyval == GDK_KEY_Escape)
772         {
773             webkit_web_view_stop_loading(WEBKIT_WEB_VIEW(c->web_view));
774             gtk_entry_set_progress_fraction(GTK_ENTRY(c->location), 0);
775         }
776     }
777     else if (event->type == GDK_BUTTON_PRESS)
778     {
779         switch (((GdkEventButton *)event)->button)
780         {
781             case 2:
782                 if (c->hover_uri != NULL)
783                 {
784                     client_new(c->hover_uri, NULL, TRUE);
785                     return TRUE;
786                 }
787                 break;
788             case 8:
789                 webkit_web_view_go_back(WEBKIT_WEB_VIEW(c->web_view));
790                 return TRUE;
791             case 9:
792                 webkit_web_view_go_forward(WEBKIT_WEB_VIEW(c->web_view));
793                 return TRUE;
794         }
795     }
796     else if (event->type == GDK_SCROLL)
797     {
798         if (((GdkEventScroll *)event)->state & GDK_MOD1_MASK ||
799             ((GdkEventScroll *)event)->state & GDK_CONTROL_MASK)
800         {
801             gdk_event_get_scroll_deltas(event, &dx, &dy);
802             z = webkit_web_view_get_zoom_level(WEBKIT_WEB_VIEW(c->web_view));
803             z += -dy * 0.1;
804             z = dx != 0 ? global_zoom : z;
805             webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(c->web_view), z);
806             return TRUE;
807         }
808     }
809
810     return FALSE;
811 }
812
813 void
814 keywords_load(void)
815 {
816     GError *err = NULL;
817     GIOChannel *channel = NULL;
818     gchar *path = NULL, *buf = NULL;
819     gchar **tokens = NULL;
820
821     keywords = g_hash_table_new(g_str_hash, g_str_equal);
822
823     path = g_build_filename(g_get_user_config_dir(), __NAME__, "keywordsearch",
824                             NULL);
825     channel = g_io_channel_new_file(path, "r", &err);
826     if (channel != NULL)
827     {
828         while (g_io_channel_read_line(channel, &buf, NULL, NULL, NULL)
829                == G_IO_STATUS_NORMAL)
830         {
831             g_strstrip(buf);
832             if (buf[0] != '#')
833             {
834                 tokens = g_strsplit(buf, " ", 2);
835                 if (tokens[0] != NULL && tokens[1] != NULL)
836                     g_hash_table_insert(keywords, g_strdup(tokens[0]),
837                                         g_strdup(tokens[1]));
838                 g_strfreev(tokens);
839             }
840             g_free(buf);
841         }
842         g_io_channel_shutdown(channel, FALSE, NULL);
843     }
844     g_free(path);
845 }
846
847 gboolean
848 keywords_try_search(WebKitWebView *web_view, const gchar *t)
849 {
850     gboolean ret = FALSE;
851     gchar **tokens = NULL;
852     gchar *val = NULL, *uri = NULL;
853
854     tokens = g_strsplit(t, " ", 2);
855     if (tokens[0] != NULL && tokens[1] != NULL)
856     {
857         val = g_hash_table_lookup(keywords, tokens[0]);
858         if (val != NULL)
859         {
860             uri = g_strdup_printf((gchar *)val, tokens[1]);
861             webkit_web_view_load_uri(web_view, uri);
862             g_free(uri);
863             ret = TRUE;
864         }
865     }
866     g_strfreev(tokens);
867
868     return ret;
869 }
870
871 gboolean
872 menu_web_view(WebKitWebView *web_view, WebKitContextMenu *menu, GdkEvent *ev,
873               WebKitHitTestResult *ht, gpointer data)
874 {
875     struct Client *c = (struct Client *)data;
876     GtkAction *action = NULL;
877     WebKitContextMenuItem *mi = NULL;
878     const gchar *uri = NULL;
879
880     (void)ev;
881
882     if (webkit_hit_test_result_context_is_link(ht))
883         uri = webkit_hit_test_result_get_link_uri(ht);
884     else if (webkit_hit_test_result_context_is_image(ht))
885         uri = webkit_hit_test_result_get_image_uri(ht);
886     else if (webkit_hit_test_result_context_is_media(ht))
887         uri = webkit_hit_test_result_get_media_uri(ht);
888
889     if (uri != NULL)
890     {
891         webkit_context_menu_append(menu, webkit_context_menu_item_new_separator());
892
893         if (c->external_handler_uri != NULL)
894             g_free(c->external_handler_uri);
895         c->external_handler_uri = g_strdup(uri);
896         action = gtk_action_new("external_handler", "Open with external handler",
897                                 NULL, NULL);
898         g_signal_connect(G_OBJECT(action), "activate",
899                          G_CALLBACK(external_handler_run), data);
900         mi = webkit_context_menu_item_new(action);
901         webkit_context_menu_append(menu, mi);
902     }
903
904     /* FALSE = Show the menu. (TRUE = Don't ever show it.) */
905     return FALSE;
906 }
907
908 gboolean
909 remote_msg(GIOChannel *channel, GIOCondition condition, gpointer data)
910 {
911     gchar *uri = NULL;
912
913     g_io_channel_read_line(channel, &uri, NULL, NULL, NULL);
914     if (uri)
915     {
916         g_strstrip(uri);
917         client_new(uri, NULL, TRUE);
918         g_free(uri);
919     }
920     return TRUE;
921 }
922
923 void
924 search(gpointer data, gint direction)
925 {
926     struct Client *c = (struct Client *)data;
927     WebKitWebView *web_view = WEBKIT_WEB_VIEW(c->web_view);
928     WebKitFindController *fc = webkit_web_view_get_find_controller(web_view);
929
930     if (search_text == NULL)
931         return;
932
933     switch (direction)
934     {
935         case 0:
936             webkit_find_controller_search(fc, search_text,
937                                           WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE |
938                                           WEBKIT_FIND_OPTIONS_WRAP_AROUND,
939                                           G_MAXUINT);
940             break;
941         case 1:
942             webkit_find_controller_search_next(fc);
943             break;
944         case -1:
945             webkit_find_controller_search_previous(fc);
946             break;
947     }
948 }
949
950 void
951 show_web_view(WebKitWebView *web_view, gpointer data)
952 {
953     struct Client *c = (struct Client *)data;
954
955     (void)web_view;
956
957     gtk_widget_grab_focus(c->web_view);
958     gtk_widget_show_all(c->win);
959 }
960
961 Window
962 tabbed_launch(void)
963 {
964     gint tabbed_stdout;
965     GIOChannel *tabbed_stdout_channel;
966     GError *err = NULL;
967     gchar *output = NULL;
968     char *argv[] = { "tabbed", "-c", "-d", "-p", "s1", "-n", __NAME__, NULL };
969     Window plug_into;
970
971     if (!g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL,
972                                   NULL, NULL, NULL, &tabbed_stdout, NULL,
973                                   &err))
974     {
975         fprintf(stderr, __NAME__": Could not launch tabbed: %s\n", err->message);
976         g_error_free(err);
977         return 0;
978     }
979
980     tabbed_stdout_channel = g_io_channel_unix_new(tabbed_stdout);
981     if (tabbed_stdout_channel == NULL)
982     {
983         fprintf(stderr, __NAME__": Could open tabbed's stdout\n");
984         return 0;
985     }
986     g_io_channel_read_line(tabbed_stdout_channel, &output, NULL, NULL, NULL);
987     g_io_channel_shutdown(tabbed_stdout_channel, FALSE, NULL);
988     if (output == NULL)
989     {
990         fprintf(stderr, __NAME__": Could not read XID from tabbed\n");
991         return 0;
992     }
993     g_strstrip(output);
994     plug_into = strtol(output, NULL, 16);
995     g_free(output);
996     if (plug_into == 0)
997         fprintf(stderr, __NAME__": The XID from tabbed is 0\n");
998     return plug_into;
999 }
1000
1001 void
1002 trust_user_certs(WebKitWebContext *wc)
1003 {
1004     GTlsCertificate *cert;
1005     const gchar *basedir, *file, *absfile;
1006     GDir *dir;
1007
1008     basedir = g_build_filename(g_get_user_config_dir(), __NAME__, "certs", NULL);
1009     dir = g_dir_open(basedir, 0, NULL);
1010     if (dir != NULL)
1011     {
1012         file = g_dir_read_name(dir);
1013         while (file != NULL)
1014         {
1015             absfile = g_build_filename(g_get_user_config_dir(), __NAME__, "certs",
1016                                        file, NULL);
1017             cert = g_tls_certificate_new_from_file(absfile, NULL);
1018             if (cert == NULL)
1019                 fprintf(stderr, __NAME__": Could not load trusted cert '%s'\n", file);
1020             else
1021                 webkit_web_context_allow_tls_certificate_for_host(wc, cert, file);
1022             file = g_dir_read_name(dir);
1023         }
1024         g_dir_close(dir);
1025     }
1026 }
1027
1028
1029 int
1030 main(int argc, char **argv)
1031 {
1032     gchar *c;
1033     int opt, i;
1034
1035     gtk_init(&argc, &argv);
1036     webkit_web_context_set_process_model(webkit_web_context_get_default(),
1037         WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
1038
1039     grab_environment_configuration();
1040
1041     while ((opt = getopt(argc, argv, "e:CT")) != -1)
1042     {
1043         switch (opt)
1044         {
1045             case 'e':
1046                 embed = atol(optarg);
1047                 tabbed_automagic = FALSE;
1048                 break;
1049             case 'C':
1050                 cooperative_instances = FALSE;
1051                 break;
1052             case 'T':
1053                 tabbed_automagic = FALSE;
1054                 break;
1055             default:
1056                 fprintf(stderr, "Usage: "__NAME__" [OPTION]... [URI]...\n");
1057                 exit(EXIT_FAILURE);
1058         }
1059     }
1060
1061     keywords_load();
1062     if (cooperative_instances)
1063         cooperation_setup();
1064     downloadmanager_setup();
1065
1066     if (tabbed_automagic && !(cooperative_instances && !cooperative_alone))
1067         embed = tabbed_launch();
1068
1069     if (!cooperative_instances || cooperative_alone)
1070     {
1071         c = g_build_filename(g_get_user_config_dir(), __NAME__, "web_extensions",
1072                              NULL);
1073         webkit_web_context_set_web_extensions_directory(
1074             webkit_web_context_get_default(), c
1075         );
1076     }
1077
1078     if (optind >= argc)
1079         client_new(home_uri, NULL, TRUE);
1080     else
1081     {
1082         for (i = optind; i < argc; i++)
1083             client_new(argv[i], NULL, TRUE);
1084     }
1085
1086     if (!cooperative_instances || cooperative_alone)
1087         gtk_main();
1088     exit(EXIT_SUCCESS);
1089 }