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