]> git.armaanb.net Git - chorizo.git/blob - browser.c
Optionally write each URI to $LARIZA_HISTORY_FILE
[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 crashed_web_view_reload(gpointer);
28 static gboolean decide_policy(WebKitWebView *, WebKitPolicyDecision *,
29                               WebKitPolicyDecisionType, gpointer);
30 static gboolean download_handle(WebKitDownload *, gchar *, gpointer);
31 static void download_handle_start(WebKitWebView *, WebKitDownload *, gpointer);
32 static void downloadmanager_cancel(GtkToolButton *, gpointer data);
33 static void downloadmanager_setup(void);
34 static gchar *ensure_uri_scheme(const gchar *);
35 static void external_handler_run(GtkAction *, gpointer);
36 static void grab_environment_configuration(void);
37 static void hover_web_view(WebKitWebView *, WebKitHitTestResult *, guint, gpointer);
38 static gboolean key_common(GtkWidget *, GdkEvent *, gpointer);
39 static gboolean key_downloadmanager(GtkWidget *, GdkEvent *, gpointer);
40 static gboolean key_location(GtkWidget *, GdkEvent *, gpointer);
41 static gboolean key_web_view(GtkWidget *, GdkEvent *, gpointer);
42 static void keywords_load(void);
43 static gboolean keywords_try_search(WebKitWebView *, const gchar *);
44 static gboolean menu_web_view(WebKitWebView *, WebKitContextMenu *, GdkEvent *,
45                               WebKitHitTestResult *, gpointer);
46 static gboolean remote_msg(GIOChannel *, GIOCondition, gpointer);
47 static void search(gpointer, gint);
48 static void show_web_view(WebKitWebView *, gpointer);
49 static Window tabbed_launch(void);
50 static void trust_user_certs(WebKitWebContext *);
51
52
53 struct Client
54 {
55     gchar *external_handler_uri;
56     gchar *hover_uri;
57     GtkWidget *location;
58     GtkWidget *vbox;
59     GtkWidget *web_view;
60     GtkWidget *win;
61 };
62
63 struct DownloadManager
64 {
65     GtkWidget *scroll;
66     GtkWidget *toolbar;
67     GtkWidget *win;
68 } dm;
69
70
71 static const gchar *accepted_language[2] = { NULL, NULL };
72 static gint clients = 0;
73 static gboolean cooperative_alone = TRUE;
74 static gboolean cooperative_instances = TRUE;
75 static int cooperative_pipe_fp = 0;
76 static int crash_autoreload_delay = 2;
77 static gchar *download_dir = "/var/tmp";
78 static Window embed = 0;
79 static gchar *fifo_suffix = "main";
80 static gdouble global_zoom = 1.0;
81 static gchar *history_file = NULL;
82 static gchar *home_uri = "about:blank";
83 static gboolean initial_wc_setup_done = FALSE;
84 static GHashTable *keywords = NULL;
85 static gchar *search_text = NULL;
86 static gboolean tabbed_automagic = TRUE;
87 static gchar *user_agent = NULL;
88
89
90 void
91 client_destroy(GtkWidget *obj, gpointer data)
92 {
93     struct Client *c = (struct Client *)data;
94
95     g_signal_handlers_disconnect_by_func(G_OBJECT(c->web_view),
96                                          changed_load_progress, c);
97
98     free(c);
99     clients--;
100
101     if (clients == 0)
102         gtk_main_quit();
103 }
104
105 gboolean
106 client_destroy_request(WebKitWebView *web_view, gpointer data)
107 {
108     struct Client *c = (struct Client *)data;
109
110     gtk_widget_destroy(c->win);
111
112     return TRUE;
113 }
114
115 WebKitWebView *
116 client_new(const gchar *uri, WebKitWebView *related_wv, gboolean show)
117 {
118     struct Client *c;
119     WebKitWebContext *wc;
120     gchar *f;
121
122     if (uri != NULL && cooperative_instances && !cooperative_alone)
123     {
124         write(cooperative_pipe_fp, uri, strlen(uri));
125         write(cooperative_pipe_fp, "\n", 1);
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     gtk_entry_set_text(GTK_ENTRY(c->location), (t == NULL ? __NAME__ : t));
350
351     if (t != NULL && history_file != NULL)
352     {
353         fp = fopen(history_file, "a");
354         if (fp != NULL)
355         {
356             fprintf(fp, "%s\n", t);
357             fclose(fp);
358         }
359         else
360             perror(__NAME__": Error opening history file");
361     }
362 }
363
364 gboolean
365 crashed_web_view(WebKitWebView *web_view, gpointer data)
366 {
367     fprintf(stderr, __NAME__": WebView crashed!\n");
368     if (crash_autoreload_delay >= 1)
369     {
370         fprintf(stderr, __NAME__": Reloading WebView in %d seconds.\n",
371                 crash_autoreload_delay);
372         g_timeout_add_seconds(crash_autoreload_delay, crashed_web_view_reload,
373                               web_view);
374     }
375
376     return TRUE;
377 }
378
379 gboolean
380 crashed_web_view_reload(gpointer data)
381 {
382     webkit_web_view_reload_bypass_cache(WEBKIT_WEB_VIEW(data));
383
384     return G_SOURCE_REMOVE;
385 }
386
387 gboolean
388 decide_policy(WebKitWebView *web_view, WebKitPolicyDecision *decision,
389               WebKitPolicyDecisionType type, gpointer data)
390 {
391     WebKitResponsePolicyDecision *r;
392
393     switch (type)
394     {
395         case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
396             r = WEBKIT_RESPONSE_POLICY_DECISION(decision);
397             if (!webkit_response_policy_decision_is_mime_type_supported(r))
398                 webkit_policy_decision_download(decision);
399             else
400                 webkit_policy_decision_use(decision);
401             break;
402         default:
403             /* Use whatever default there is. */
404             return FALSE;
405     }
406     return TRUE;
407 }
408
409 void
410 download_handle_start(WebKitWebView *web_view, WebKitDownload *download,
411                       gpointer data)
412 {
413     g_signal_connect(G_OBJECT(download), "decide-destination",
414                      G_CALLBACK(download_handle), data);
415 }
416
417 gboolean
418 download_handle(WebKitDownload *download, gchar *suggested_filename, gpointer data)
419 {
420     gchar *sug_clean, *path, *path2 = NULL, *uri;
421     GtkToolItem *tb;
422     int suffix = 1;
423     size_t i;
424
425     sug_clean = g_strdup(suggested_filename);
426     for (i = 0; i < strlen(sug_clean); i++)
427         if (sug_clean[i] == G_DIR_SEPARATOR)
428             sug_clean[i] = '_';
429
430     path = g_build_filename(download_dir, sug_clean, NULL);
431     path2 = g_strdup(path);
432     while (g_file_test(path2, G_FILE_TEST_EXISTS) && suffix < 1000)
433     {
434         g_free(path2);
435
436         path2 = g_strdup_printf("%s.%d", path, suffix);
437         suffix++;
438     }
439
440     if (suffix == 1000)
441     {
442         fprintf(stderr, __NAME__": Suffix reached limit for download.\n");
443         webkit_download_cancel(download);
444     }
445     else
446     {
447         uri = g_filename_to_uri(path2, NULL, NULL);
448         webkit_download_set_destination(download, uri);
449         g_free(uri);
450
451         tb = gtk_tool_button_new(NULL, NULL);
452         gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(tb), "gtk-delete");
453         gtk_tool_button_set_label(GTK_TOOL_BUTTON(tb), sug_clean);
454         gtk_toolbar_insert(GTK_TOOLBAR(dm.toolbar), tb, 0);
455         gtk_widget_show_all(dm.win);
456
457         g_signal_connect(G_OBJECT(download), "notify::estimated-progress",
458                          G_CALLBACK(changed_download_progress), tb);
459
460         g_object_ref(download);
461         g_signal_connect(G_OBJECT(tb), "clicked",
462                          G_CALLBACK(downloadmanager_cancel), download);
463     }
464
465     g_free(sug_clean);
466     g_free(path);
467     g_free(path2);
468
469     /* Propagate -- to whom it may concern. */
470     return FALSE;
471 }
472
473 void
474 downloadmanager_cancel(GtkToolButton *tb, gpointer data)
475 {
476     WebKitDownload *download = WEBKIT_DOWNLOAD(data);
477
478     webkit_download_cancel(download);
479     g_object_unref(download);
480
481     gtk_widget_destroy(GTK_WIDGET(tb));
482 }
483
484 void
485 downloadmanager_setup(void)
486 {
487     dm.win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
488     gtk_window_set_type_hint(GTK_WINDOW(dm.win), GDK_WINDOW_TYPE_HINT_DIALOG);
489     gtk_window_set_default_size(GTK_WINDOW(dm.win), 500, 250);
490     gtk_window_set_title(GTK_WINDOW(dm.win), __NAME__" - Download Manager");
491     g_signal_connect(G_OBJECT(dm.win), "delete-event",
492                      G_CALLBACK(gtk_widget_hide_on_delete), NULL);
493     g_signal_connect(G_OBJECT(dm.win), "key-press-event",
494                      G_CALLBACK(key_downloadmanager), NULL);
495
496     dm.toolbar = gtk_toolbar_new();
497     gtk_orientable_set_orientation(GTK_ORIENTABLE(dm.toolbar),
498                                    GTK_ORIENTATION_VERTICAL);
499     gtk_toolbar_set_style(GTK_TOOLBAR(dm.toolbar), GTK_TOOLBAR_BOTH_HORIZ);
500     gtk_toolbar_set_show_arrow(GTK_TOOLBAR(dm.toolbar), FALSE);
501
502     dm.scroll = gtk_scrolled_window_new(NULL, NULL);
503     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dm.scroll),
504                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
505     gtk_container_add(GTK_CONTAINER(dm.scroll), dm.toolbar);
506
507     gtk_container_add(GTK_CONTAINER(dm.win), dm.scroll);
508 }
509
510 gchar *
511 ensure_uri_scheme(const gchar *t)
512 {
513     gchar *f, *fabs;
514
515     f = g_ascii_strdown(t, -1);
516     if (!g_str_has_prefix(f, "http:") &&
517         !g_str_has_prefix(f, "https:") &&
518         !g_str_has_prefix(f, "file:") &&
519         !g_str_has_prefix(f, "about:"))
520     {
521         g_free(f);
522         fabs = realpath(t, NULL);
523         if (fabs != NULL)
524         {
525             f = g_strdup_printf("file://%s", fabs);
526             free(fabs);
527         }
528         else
529             f = g_strdup_printf("http://%s", t);
530         return f;
531     }
532     else
533         return g_strdup(t);
534 }
535
536 void
537 external_handler_run(GtkAction *action, gpointer data)
538 {
539     struct Client *c = (struct Client *)data;
540     gchar *argv[] = { "lariza-external-handler", "-u", NULL, NULL };
541     GPid pid;
542     GError *err = NULL;
543
544     (void)action;
545
546     argv[2] = c->external_handler_uri;
547     if (!g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
548                        &pid, &err))
549     {
550         fprintf(stderr, __NAME__": Could not launch key handler: %s\n",
551                 err->message);
552         g_error_free(err);
553     }
554     else
555         g_spawn_close_pid(pid);
556 }
557
558 void
559 grab_environment_configuration(void)
560 {
561     const gchar *e;
562
563     e = g_getenv(__NAME_UPPERCASE__"_ACCEPTED_LANGUAGE");
564     if (e != NULL)
565         accepted_language[0] = g_strdup(e);
566
567     e = g_getenv(__NAME_UPPERCASE__"_CRASH_AUTORELOAD_DELAY");
568     if (e != NULL)
569         crash_autoreload_delay = atoi(e);
570
571     e = g_getenv(__NAME_UPPERCASE__"_DOWNLOAD_DIR");
572     if (e != NULL)
573         download_dir = g_strdup(e);
574
575     e = g_getenv(__NAME_UPPERCASE__"_FIFO_SUFFIX");
576     if (e != NULL)
577         fifo_suffix = g_strdup(e);
578
579     e = g_getenv(__NAME_UPPERCASE__"_HISTORY_FILE");
580     if (e != NULL)
581         history_file = g_strdup(e);
582
583     e = g_getenv(__NAME_UPPERCASE__"_HOME_URI");
584     if (e != NULL)
585         home_uri = g_strdup(e);
586
587     e = g_getenv(__NAME_UPPERCASE__"_USER_AGENT");
588     if (e != NULL)
589         user_agent = g_strdup(e);
590
591     e = g_getenv(__NAME_UPPERCASE__"_ZOOM");
592     if (e != NULL)
593         global_zoom = atof(e);
594 }
595
596 void
597 hover_web_view(WebKitWebView *web_view, WebKitHitTestResult *ht, guint modifiers,
598                gpointer data)
599 {
600     struct Client *c = (struct Client *)data;
601
602     if (!gtk_widget_is_focus(c->location))
603     {
604         if (webkit_hit_test_result_context_is_link(ht))
605         {
606             gtk_entry_set_text(GTK_ENTRY(c->location),
607                                webkit_hit_test_result_get_link_uri(ht));
608
609             if (c->hover_uri != NULL)
610                 g_free(c->hover_uri);
611             c->hover_uri = g_strdup(webkit_hit_test_result_get_link_uri(ht));
612         }
613         else
614         {
615             gtk_entry_set_text(GTK_ENTRY(c->location),
616                                webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view)));
617
618             if (c->hover_uri != NULL)
619                 g_free(c->hover_uri);
620             c->hover_uri = NULL;
621         }
622     }
623 }
624
625 gboolean
626 key_common(GtkWidget *widget, GdkEvent *event, gpointer data)
627 {
628     struct Client *c = (struct Client *)data;
629     WebKitWebContext *wc = webkit_web_view_get_context(WEBKIT_WEB_VIEW(c->web_view));
630     gchar *f;
631
632     if (event->type == GDK_KEY_PRESS)
633     {
634         if (((GdkEventKey *)event)->state & GDK_MOD1_MASK)
635         {
636             switch (((GdkEventKey *)event)->keyval)
637             {
638                 case GDK_KEY_q:  /* close window (left hand) */
639                     gtk_widget_destroy(c->win);
640                     return TRUE;
641                 case GDK_KEY_w:  /* home (left hand) */
642                     f = ensure_uri_scheme(home_uri);
643                     webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), f);
644                     g_free(f);
645                     return TRUE;
646                 case GDK_KEY_e:  /* new tab (left hand) */
647                     f = ensure_uri_scheme(home_uri);
648                     client_new(f, NULL, TRUE);
649                     g_free(f);
650                     return TRUE;
651                 case GDK_KEY_r:  /* reload (left hand) */
652                     webkit_web_view_reload_bypass_cache(WEBKIT_WEB_VIEW(
653                                                         c->web_view));
654                     return TRUE;
655                 case GDK_KEY_d:  /* download manager (left hand) */
656                     gtk_widget_show_all(dm.win);
657                     return TRUE;
658                 case GDK_KEY_2:  /* search forward (left hand) */
659                 case GDK_KEY_n:  /* search forward (maybe both hands) */
660                     search(c, 1);
661                     return TRUE;
662                 case GDK_KEY_3:  /* search backward (left hand) */
663                     search(c, -1);
664                     return TRUE;
665                 case GDK_KEY_l:  /* location (BOTH hands) */
666                     gtk_widget_grab_focus(c->location);
667                     return TRUE;
668                 case GDK_KEY_k:  /* initiate search (BOTH hands) */
669                     gtk_widget_grab_focus(c->location);
670                     gtk_entry_set_text(GTK_ENTRY(c->location), ":/");
671                     gtk_editable_set_position(GTK_EDITABLE(c->location), -1);
672                     return TRUE;
673                 case GDK_KEY_c:  /* reload trusted certs (left hand) */
674                     trust_user_certs(wc);
675                     return TRUE;
676                 case GDK_KEY_x:  /* launch external handler (left hand) */
677                     if (c->external_handler_uri != NULL)
678                         g_free(c->external_handler_uri);
679                     c->external_handler_uri = g_strdup(
680                         webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view)));
681                     external_handler_run(NULL, c);
682                     return TRUE;
683             }
684         }
685         /* navigate backward (left hand) */
686         else if (((GdkEventKey *)event)->keyval == GDK_KEY_F2)
687         {
688             webkit_web_view_go_back(WEBKIT_WEB_VIEW(c->web_view));
689             return TRUE;
690         }
691         /* navigate forward (left hand) */
692         else if (((GdkEventKey *)event)->keyval == GDK_KEY_F3)
693         {
694             webkit_web_view_go_forward(WEBKIT_WEB_VIEW(c->web_view));
695             return TRUE;
696         }
697     }
698
699     return FALSE;
700 }
701
702 gboolean
703 key_downloadmanager(GtkWidget *widget, GdkEvent *event, gpointer data)
704 {
705     if (event->type == GDK_KEY_PRESS)
706     {
707         if (((GdkEventKey *)event)->state & GDK_MOD1_MASK)
708         {
709             switch (((GdkEventKey *)event)->keyval)
710             {
711                 case GDK_KEY_d:  /* close window (left hand) */
712                     gtk_widget_hide(dm.win);
713                     return TRUE;
714             }
715         }
716     }
717
718     return FALSE;
719 }
720
721 gboolean
722 key_location(GtkWidget *widget, GdkEvent *event, gpointer data)
723 {
724     struct Client *c = (struct Client *)data;
725     const gchar *t;
726     gchar *f;
727
728     if (key_common(widget, event, data))
729         return TRUE;
730
731     if (event->type == GDK_KEY_PRESS)
732     {
733         switch (((GdkEventKey *)event)->keyval)
734         {
735             case GDK_KEY_KP_Enter:
736             case GDK_KEY_Return:
737                 gtk_widget_grab_focus(c->web_view);
738                 t = gtk_entry_get_text(GTK_ENTRY(c->location));
739                 if (t != NULL && t[0] == ':' && t[1] == '/')
740                 {
741                     if (search_text != NULL)
742                         g_free(search_text);
743                     search_text = g_strdup(t + 2);  /* XXX whacky */
744                     search(c, 0);
745                 }
746                 else if (!keywords_try_search(WEBKIT_WEB_VIEW(c->web_view), t))
747                 {
748                     f = ensure_uri_scheme(t);
749                     webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), f);
750                     g_free(f);
751                 }
752                 return TRUE;
753             case GDK_KEY_Escape:
754                 t = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
755                 gtk_entry_set_text(GTK_ENTRY(c->location),
756                                    (t == NULL ? __NAME__ : t));
757                 return TRUE;
758         }
759     }
760
761     return FALSE;
762 }
763
764 gboolean
765 key_web_view(GtkWidget *widget, GdkEvent *event, gpointer data)
766 {
767     struct Client *c = (struct Client *)data;
768     gdouble dx, dy;
769     gfloat z;
770
771     if (key_common(widget, event, data))
772         return TRUE;
773
774     if (event->type == GDK_KEY_PRESS)
775     {
776         if (((GdkEventKey *)event)->keyval == GDK_KEY_Escape)
777         {
778             webkit_web_view_stop_loading(WEBKIT_WEB_VIEW(c->web_view));
779             gtk_entry_set_progress_fraction(GTK_ENTRY(c->location), 0);
780         }
781     }
782     else if (event->type == GDK_BUTTON_PRESS)
783     {
784         switch (((GdkEventButton *)event)->button)
785         {
786             case 2:
787                 if (c->hover_uri != NULL)
788                 {
789                     client_new(c->hover_uri, NULL, TRUE);
790                     return TRUE;
791                 }
792                 break;
793             case 8:
794                 webkit_web_view_go_back(WEBKIT_WEB_VIEW(c->web_view));
795                 return TRUE;
796             case 9:
797                 webkit_web_view_go_forward(WEBKIT_WEB_VIEW(c->web_view));
798                 return TRUE;
799         }
800     }
801     else if (event->type == GDK_SCROLL)
802     {
803         if (((GdkEventScroll *)event)->state & GDK_MOD1_MASK ||
804             ((GdkEventScroll *)event)->state & GDK_CONTROL_MASK)
805         {
806             gdk_event_get_scroll_deltas(event, &dx, &dy);
807             z = webkit_web_view_get_zoom_level(WEBKIT_WEB_VIEW(c->web_view));
808             z += -dy * 0.1;
809             z = dx != 0 ? global_zoom : z;
810             webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(c->web_view), z);
811             return TRUE;
812         }
813     }
814
815     return FALSE;
816 }
817
818 void
819 keywords_load(void)
820 {
821     GError *err = NULL;
822     GIOChannel *channel = NULL;
823     gchar *path = NULL, *buf = NULL;
824     gchar **tokens = NULL;
825
826     keywords = g_hash_table_new(g_str_hash, g_str_equal);
827
828     path = g_build_filename(g_get_user_config_dir(), __NAME__, "keywordsearch",
829                             NULL);
830     channel = g_io_channel_new_file(path, "r", &err);
831     if (channel != NULL)
832     {
833         while (g_io_channel_read_line(channel, &buf, NULL, NULL, NULL)
834                == G_IO_STATUS_NORMAL)
835         {
836             g_strstrip(buf);
837             if (buf[0] != '#')
838             {
839                 tokens = g_strsplit(buf, " ", 2);
840                 if (tokens[0] != NULL && tokens[1] != NULL)
841                     g_hash_table_insert(keywords, g_strdup(tokens[0]),
842                                         g_strdup(tokens[1]));
843                 g_strfreev(tokens);
844             }
845             g_free(buf);
846         }
847         g_io_channel_shutdown(channel, FALSE, NULL);
848     }
849     g_free(path);
850 }
851
852 gboolean
853 keywords_try_search(WebKitWebView *web_view, const gchar *t)
854 {
855     gboolean ret = FALSE;
856     gchar **tokens = NULL;
857     gchar *val = NULL, *uri = NULL;
858
859     tokens = g_strsplit(t, " ", 2);
860     if (tokens[0] != NULL && tokens[1] != NULL)
861     {
862         val = g_hash_table_lookup(keywords, tokens[0]);
863         if (val != NULL)
864         {
865             uri = g_strdup_printf((gchar *)val, tokens[1]);
866             webkit_web_view_load_uri(web_view, uri);
867             g_free(uri);
868             ret = TRUE;
869         }
870     }
871     g_strfreev(tokens);
872
873     return ret;
874 }
875
876 gboolean
877 menu_web_view(WebKitWebView *web_view, WebKitContextMenu *menu, GdkEvent *ev,
878               WebKitHitTestResult *ht, gpointer data)
879 {
880     struct Client *c = (struct Client *)data;
881     GtkAction *action = NULL;
882     WebKitContextMenuItem *mi = NULL;
883     const gchar *uri = NULL;
884
885     (void)ev;
886
887     if (webkit_hit_test_result_context_is_link(ht))
888         uri = webkit_hit_test_result_get_link_uri(ht);
889     else if (webkit_hit_test_result_context_is_image(ht))
890         uri = webkit_hit_test_result_get_image_uri(ht);
891     else if (webkit_hit_test_result_context_is_media(ht))
892         uri = webkit_hit_test_result_get_media_uri(ht);
893
894     if (uri != NULL)
895     {
896         webkit_context_menu_append(menu, webkit_context_menu_item_new_separator());
897
898         if (c->external_handler_uri != NULL)
899             g_free(c->external_handler_uri);
900         c->external_handler_uri = g_strdup(uri);
901         action = gtk_action_new("external_handler", "Open with external handler",
902                                 NULL, NULL);
903         g_signal_connect(G_OBJECT(action), "activate",
904                          G_CALLBACK(external_handler_run), data);
905         mi = webkit_context_menu_item_new(action);
906         webkit_context_menu_append(menu, mi);
907     }
908
909     /* FALSE = Show the menu. (TRUE = Don't ever show it.) */
910     return FALSE;
911 }
912
913 gboolean
914 remote_msg(GIOChannel *channel, GIOCondition condition, gpointer data)
915 {
916     gchar *uri = NULL;
917
918     g_io_channel_read_line(channel, &uri, NULL, NULL, NULL);
919     if (uri)
920     {
921         g_strstrip(uri);
922         client_new(uri, NULL, TRUE);
923         g_free(uri);
924     }
925     return TRUE;
926 }
927
928 void
929 search(gpointer data, gint direction)
930 {
931     struct Client *c = (struct Client *)data;
932     WebKitWebView *web_view = WEBKIT_WEB_VIEW(c->web_view);
933     WebKitFindController *fc = webkit_web_view_get_find_controller(web_view);
934
935     if (search_text == NULL)
936         return;
937
938     switch (direction)
939     {
940         case 0:
941             webkit_find_controller_search(fc, search_text,
942                                           WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE |
943                                           WEBKIT_FIND_OPTIONS_WRAP_AROUND,
944                                           G_MAXUINT);
945             break;
946         case 1:
947             webkit_find_controller_search_next(fc);
948             break;
949         case -1:
950             webkit_find_controller_search_previous(fc);
951             break;
952     }
953 }
954
955 void
956 show_web_view(WebKitWebView *web_view, gpointer data)
957 {
958     struct Client *c = (struct Client *)data;
959
960     (void)web_view;
961
962     gtk_widget_grab_focus(c->web_view);
963     gtk_widget_show_all(c->win);
964 }
965
966 Window
967 tabbed_launch(void)
968 {
969     gint tabbed_stdout;
970     GIOChannel *tabbed_stdout_channel;
971     GError *err = NULL;
972     gchar *output = NULL;
973     char *argv[] = { "tabbed", "-c", "-d", "-p", "s1", "-n", __NAME__, NULL };
974     Window plug_into;
975
976     if (!g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL,
977                                   NULL, NULL, NULL, &tabbed_stdout, NULL,
978                                   &err))
979     {
980         fprintf(stderr, __NAME__": Could not launch tabbed: %s\n", err->message);
981         g_error_free(err);
982         return 0;
983     }
984
985     tabbed_stdout_channel = g_io_channel_unix_new(tabbed_stdout);
986     if (tabbed_stdout_channel == NULL)
987     {
988         fprintf(stderr, __NAME__": Could open tabbed's stdout\n");
989         return 0;
990     }
991     g_io_channel_read_line(tabbed_stdout_channel, &output, NULL, NULL, NULL);
992     g_io_channel_shutdown(tabbed_stdout_channel, FALSE, NULL);
993     if (output == NULL)
994     {
995         fprintf(stderr, __NAME__": Could not read XID from tabbed\n");
996         return 0;
997     }
998     g_strstrip(output);
999     plug_into = strtol(output, NULL, 16);
1000     g_free(output);
1001     if (plug_into == 0)
1002         fprintf(stderr, __NAME__": The XID from tabbed is 0\n");
1003     return plug_into;
1004 }
1005
1006 void
1007 trust_user_certs(WebKitWebContext *wc)
1008 {
1009     GTlsCertificate *cert;
1010     const gchar *basedir, *file, *absfile;
1011     GDir *dir;
1012
1013     basedir = g_build_filename(g_get_user_config_dir(), __NAME__, "certs", NULL);
1014     dir = g_dir_open(basedir, 0, NULL);
1015     if (dir != NULL)
1016     {
1017         file = g_dir_read_name(dir);
1018         while (file != NULL)
1019         {
1020             absfile = g_build_filename(g_get_user_config_dir(), __NAME__, "certs",
1021                                        file, NULL);
1022             cert = g_tls_certificate_new_from_file(absfile, NULL);
1023             if (cert == NULL)
1024                 fprintf(stderr, __NAME__": Could not load trusted cert '%s'\n", file);
1025             else
1026                 webkit_web_context_allow_tls_certificate_for_host(wc, cert, file);
1027             file = g_dir_read_name(dir);
1028         }
1029         g_dir_close(dir);
1030     }
1031 }
1032
1033
1034 int
1035 main(int argc, char **argv)
1036 {
1037     gchar *c;
1038     int opt, i;
1039
1040     gtk_init(&argc, &argv);
1041     webkit_web_context_set_process_model(webkit_web_context_get_default(),
1042         WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
1043
1044     grab_environment_configuration();
1045
1046     while ((opt = getopt(argc, argv, "e:CT")) != -1)
1047     {
1048         switch (opt)
1049         {
1050             case 'e':
1051                 embed = atol(optarg);
1052                 tabbed_automagic = FALSE;
1053                 break;
1054             case 'C':
1055                 cooperative_instances = FALSE;
1056                 break;
1057             case 'T':
1058                 tabbed_automagic = FALSE;
1059                 break;
1060             default:
1061                 fprintf(stderr, "Usage: "__NAME__" [OPTION]... [URI]...\n");
1062                 exit(EXIT_FAILURE);
1063         }
1064     }
1065
1066     keywords_load();
1067     if (cooperative_instances)
1068         cooperation_setup();
1069     downloadmanager_setup();
1070
1071     if (tabbed_automagic && !(cooperative_instances && !cooperative_alone))
1072         embed = tabbed_launch();
1073
1074     if (!cooperative_instances || cooperative_alone)
1075     {
1076         c = g_build_filename(g_get_user_config_dir(), __NAME__, "web_extensions",
1077                              NULL);
1078         webkit_web_context_set_web_extensions_directory(
1079             webkit_web_context_get_default(), c
1080         );
1081     }
1082
1083     if (optind >= argc)
1084         client_new(home_uri, NULL, TRUE);
1085     else
1086     {
1087         for (i = optind; i < argc; i++)
1088             client_new(argv[i], NULL, TRUE);
1089     }
1090
1091     if (!cooperative_instances || cooperative_alone)
1092         gtk_main();
1093     exit(EXIT_SUCCESS);
1094 }