]> git.armaanb.net Git - chorizo.git/blob - browser.c
Port the most basic stuff to WebKit2
[chorizo.git] / browser.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>
6 #include <string.h>
7
8 #include <gtk/gtk.h>
9 #include <gtk/gtkx.h>
10 #include <gdk/gdkkeysyms.h>
11 #include <gio/gio.h>
12 #include <webkit2/webkit2.h>
13
14
15 #if 0
16 static void adblock(WebKitWebView *, WebKitWebFrame *, WebKitWebResource *,
17                     WebKitNetworkRequest *, WebKitNetworkResponse *, gpointer);
18 static void adblock_load(void);
19 #endif
20 static void client_destroy(GtkWidget *, gpointer);
21 static gboolean client_destroy_request(WebKitWebView *, gpointer);
22 static WebKitWebView *client_new(const gchar *);
23 static WebKitWebView *client_new_request(WebKitWebView *, WebKitNavigationAction *,
24                                          gpointer);
25 static void cooperation_setup(void);
26 static void changed_download_progress(GObject *, GParamSpec *, gpointer);
27 static void changed_load_progress(GObject *, GParamSpec *, gpointer);
28 static void changed_title(GObject *, GParamSpec *, gpointer);
29 static void changed_uri(GObject *, GParamSpec *, gpointer);
30 static gboolean download_handle(WebKitWebView *, WebKitDownload *, gpointer);
31 static gboolean download_reset_indicator(gpointer);
32 /*
33 static gboolean download_request(WebKitWebView *, WebKitWebFrame *,
34                                  WebKitNetworkRequest *, gchar *,
35                                  WebKitWebPolicyDecision *, gpointer);
36                                                                  */
37 static void downloadmanager_cancel(GtkToolButton *, gpointer data);
38 static void downloadmanager_setup(void);
39 static gchar *ensure_uri_scheme(const gchar *);
40 static void grab_environment_configuration(void);
41 static void hover_web_view(WebKitWebView *, gchar *, gchar *, gpointer);
42 static gboolean key_downloadmanager(GtkWidget *, GdkEvent *, gpointer);
43 static gboolean key_location(GtkWidget *, GdkEvent *, gpointer);
44 static gboolean key_web_view(GtkWidget *, GdkEvent *, gpointer);
45 static void keywords_load(void);
46 static gboolean keywords_try_search(WebKitWebView *, const gchar *);
47 static gboolean remote_msg(GIOChannel *, GIOCondition, gpointer);
48 static void search(gpointer, gint);
49 static Window tabbed_launch(void);
50 static void usage(void);
51
52
53 struct Client
54 {
55         GtkWidget *location;
56         GtkWidget *progress;
57         GtkWidget *scroll;
58         GtkWidget *status;
59         GtkWidget *top_box;
60         GtkWidget *vbox;
61         GtkWidget *web_view;
62         GtkWidget *win;
63 };
64
65 struct DownloadManager
66 {
67         GtkWidget *scroll;
68         GtkWidget *toolbar;
69         GtkWidget *win;
70 } dm;
71
72
73 static gchar *accepted_language = "en-US";
74 static GSList *adblock_patterns = NULL;
75 static gint clients = 0;
76 static gboolean cooperative_alone = TRUE;
77 static gboolean cooperative_instances = TRUE;
78 static int cooperative_pipe_fp = 0;
79 static gchar *download_dir = "/tmp";
80 static gint downloads_indicated = 0;
81 static Window embed = 0;
82 static gchar *fifo_suffix = "main";
83 static gdouble global_zoom = 1.0;
84 static gchar *home_uri = "about:blank";
85 static GHashTable *keywords = NULL;
86 static gboolean language_is_set = FALSE;
87 static gchar *search_text = NULL;
88 static gboolean show_all_requests = FALSE;
89 static gboolean tabbed_automagic = TRUE;
90 static gchar *user_agent = "Mozilla/5.0 (X11; U; Unix; en-US) "
91                            "AppleWebKit/537.15 (KHTML, like Gecko) "
92                            "Chrome/24.0.1295.0 "
93                            "Safari/537.15 "__NAME_CAPITALIZED__"/git";
94
95
96 #if 0
97 void
98 adblock(WebKitWebView *web_view, WebKitWebFrame *frame,
99         WebKitWebResource *resource, WebKitNetworkRequest *request,
100         WebKitNetworkResponse *response, gpointer data)
101 {
102         GSList *it = adblock_patterns;
103         const gchar *uri;
104
105         uri = webkit_network_request_get_uri(request);
106         if (show_all_requests)
107                 fprintf(stderr, "   -> %s\n", uri);
108
109         while (it)
110         {
111                 if (g_regex_match((GRegex *)(it->data), uri, 0, NULL))
112                 {
113                         webkit_network_request_set_uri(request, "about:blank");
114                         if (show_all_requests)
115                                 fprintf(stderr, "            BLOCKED!\n");
116                         return;
117                 }
118                 it = g_slist_next(it);
119         }
120 }
121
122 void
123 adblock_load(void)
124 {
125         GRegex *re = NULL;
126         GError *err = NULL;
127         GIOChannel *channel = NULL;
128         gchar *path = NULL, *buf = NULL;
129
130         path = g_build_filename(g_get_user_config_dir(), __NAME__, "adblock.black",
131                                 NULL);
132         channel = g_io_channel_new_file(path, "r", &err);
133         if (channel != NULL)
134         {
135                 while (g_io_channel_read_line(channel, &buf, NULL, NULL, NULL)
136                        == G_IO_STATUS_NORMAL)
137                 {
138                         g_strstrip(buf);
139                         if (buf[0] != '#')
140                         {
141                                 re = g_regex_new(buf,
142                                                  G_REGEX_CASELESS | G_REGEX_OPTIMIZE,
143                                                  G_REGEX_MATCH_PARTIAL, &err);
144                                 if (err != NULL)
145                                 {
146                                         fprintf(stderr, __NAME__": Could not compile regex: %s\n", buf);
147                                         g_error_free(err);
148                                         err = NULL;
149                                 }
150                                 else
151                                         adblock_patterns = g_slist_append(adblock_patterns, re);
152                         }
153                         g_free(buf);
154                 }
155                 g_io_channel_shutdown(channel, FALSE, NULL);
156         }
157         g_free(path);
158 }
159 #endif
160
161 void
162 client_destroy(GtkWidget *obj, gpointer data)
163 {
164         struct Client *c = (struct Client *)data;
165
166         g_signal_handlers_disconnect_by_func(G_OBJECT(c->web_view),
167                                              changed_load_progress, c);
168
169         free(c);
170         clients--;
171
172         if (clients == 0)
173                 gtk_main_quit();
174 }
175
176 gboolean
177 client_destroy_request(WebKitWebView *web_view, gpointer data)
178 {
179         struct Client *c = (struct Client *)data;
180
181         gtk_widget_destroy(c->win);
182
183         return TRUE;
184 }
185
186 WebKitWebView *
187 client_new(const gchar *uri)
188 {
189         struct Client *c;
190         WebKitWebContext *wc;
191         gchar *f;
192
193         if (uri != NULL && cooperative_instances && !cooperative_alone)
194         {
195                 write(cooperative_pipe_fp, uri, strlen(uri));
196                 write(cooperative_pipe_fp, "\n", 1);
197                 return NULL;
198         }
199
200         c = malloc(sizeof(struct Client));
201         if (!c)
202         {
203                 fprintf(stderr, __NAME__": fatal: malloc failed\n");
204                 exit(EXIT_FAILURE);
205         }
206
207         c->win = NULL;
208         if (embed != 0)
209         {
210                 c->win = gtk_plug_new(embed);
211                 if (!gtk_plug_get_embedded(GTK_PLUG(c->win)))
212                 {
213                         fprintf(stderr, __NAME__": Can't plug-in to XID %ld.\n", embed);
214                         gtk_widget_destroy(c->win);
215                         c->win = NULL;
216                         embed = 0;
217                 }
218         }
219
220         if (c->win == NULL)
221         {
222                 c->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
223                 gtk_window_set_wmclass(GTK_WINDOW(c->win), __NAME__, __NAME_CAPITALIZED__);
224         }
225
226         g_signal_connect(G_OBJECT(c->win), "destroy", G_CALLBACK(client_destroy), c);
227         gtk_window_set_title(GTK_WINDOW(c->win), __NAME__);
228
229         c->web_view = webkit_web_view_new();
230         wc = webkit_web_view_get_context(WEBKIT_WEB_VIEW(c->web_view));
231
232         /* XXX I really do want to enable this option. However, I get
233          * reproducible crashes with it enabled. I've seen bug reports from
234          * 2010 about this... WebKit crashes in libpixman, so maybe it's not
235          * a WebKit issue.
236          * Yeah, well. I'll turn it off for now. */
237         /*webkit_web_view_set_full_content_zoom(WEBKIT_WEB_VIEW(c->web_view), TRUE);*/
238
239         webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(c->web_view), global_zoom);
240         g_signal_connect(G_OBJECT(c->web_view), "notify::title",
241                          G_CALLBACK(changed_title), c);
242         g_signal_connect(G_OBJECT(c->web_view), "notify::uri",
243                          G_CALLBACK(changed_uri), c);
244         g_signal_connect(G_OBJECT(c->web_view), "notify::estimated-load-progress",
245                          G_CALLBACK(changed_load_progress), c);
246         g_signal_connect(G_OBJECT(c->web_view), "create",
247                          G_CALLBACK(client_new_request), NULL);
248         g_signal_connect(G_OBJECT(c->web_view), "close",
249                          G_CALLBACK(client_destroy_request), c);
250         /*
251         g_signal_connect(G_OBJECT(c->web_view),
252                          "mime-type-policy-decision-requested",
253                          G_CALLBACK(download_request), NULL);
254         */
255         g_signal_connect(G_OBJECT(wc), "download-requested",
256                          G_CALLBACK(download_handle), c);
257         g_signal_connect(G_OBJECT(c->web_view), "key-press-event",
258                          G_CALLBACK(key_web_view), c);
259         g_signal_connect(G_OBJECT(c->web_view), "button-press-event",
260                          G_CALLBACK(key_web_view), c);
261         g_signal_connect(G_OBJECT(c->web_view), "scroll-event",
262                          G_CALLBACK(key_web_view), c);
263         g_signal_connect(G_OBJECT(c->web_view), "hovering-over-link",
264                          G_CALLBACK(hover_web_view), c);
265         /*
266         g_signal_connect(G_OBJECT(c->web_view), "resource-request-starting",
267                          G_CALLBACK(adblock), NULL);
268                                          */
269
270         if (!language_is_set)
271         {
272                 /* XXX make this pretty */
273                 const gchar *languages[2];
274                 languages[0] = accepted_language;
275                 languages[1] = NULL;
276                 webkit_web_context_set_preferred_languages(wc, languages);
277                 language_is_set = TRUE;
278         }
279
280         /*
281         g_object_set(G_OBJECT(webkit_web_view_get_settings(WEBKIT_WEB_VIEW(c->web_view))),
282                      "user-agent", user_agent, NULL);
283                                  */
284
285         c->scroll = gtk_scrolled_window_new(NULL, NULL);
286
287         gtk_container_add(GTK_CONTAINER(c->scroll), c->web_view);
288
289         c->location = gtk_entry_new();
290         g_signal_connect(G_OBJECT(c->location), "key-press-event",
291                          G_CALLBACK(key_location), c);
292
293         /* XXX Progress bars don't work/look as intended anymore. Level bars
294          * are a dirty workaround (kind of). */
295         c->progress = gtk_level_bar_new();
296         gtk_level_bar_set_value(GTK_LEVEL_BAR(c->progress), 0);
297         gtk_widget_set_size_request(c->progress, 100, -1);
298
299         c->status = gtk_level_bar_new();
300         gtk_level_bar_set_value(GTK_LEVEL_BAR(c->status), 0);
301         gtk_widget_set_size_request(c->status, 20, -1);
302
303         c->top_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
304         gtk_box_pack_start(GTK_BOX(c->top_box), c->status, FALSE, FALSE, 2);
305         gtk_box_pack_start(GTK_BOX(c->top_box), c->location, TRUE, TRUE, 0);
306         gtk_box_pack_start(GTK_BOX(c->top_box), c->progress, FALSE, FALSE, 2);
307
308         c->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
309         gtk_box_pack_start(GTK_BOX(c->vbox), c->top_box, FALSE, FALSE, 2);
310         gtk_box_pack_start(GTK_BOX(c->vbox), c->scroll, TRUE, TRUE, 2);
311
312         gtk_container_add(GTK_CONTAINER(c->win), c->vbox);
313
314         gtk_widget_grab_focus(c->web_view);
315         gtk_widget_show_all(c->win);
316
317         if (uri != NULL)
318         {
319                 f = ensure_uri_scheme(uri);
320                 if (show_all_requests)
321                         fprintf(stderr, "====> %s\n", uri);
322                 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), f);
323                 g_free(f);
324         }
325
326         clients++;
327
328         return WEBKIT_WEB_VIEW(c->web_view);
329 }
330
331 WebKitWebView *
332 client_new_request(WebKitWebView *web_view,
333                    WebKitNavigationAction *navigation_action, gpointer data)
334 {
335         return client_new(NULL);
336 }
337
338 void
339 cooperation_setup(void)
340 {
341         GIOChannel *towatch;
342         gchar *fifofilename, *fifopath;
343
344         fifofilename = g_strdup_printf("%s-%s", __NAME__".fifo", fifo_suffix);
345         fifopath = g_build_filename(g_get_user_runtime_dir(), fifofilename, NULL);
346         g_free(fifofilename);
347
348         if (!g_file_test(fifopath, G_FILE_TEST_EXISTS))
349                 mkfifo(fifopath, 0600);
350
351         cooperative_pipe_fp = open(fifopath, O_WRONLY | O_NONBLOCK);
352         if (!cooperative_pipe_fp)
353         {
354                 fprintf(stderr, __NAME__": Can't open FIFO at all.\n");
355         }
356         else
357         {
358                 if (write(cooperative_pipe_fp, "", 0) == -1)
359                 {
360                         /* Could not do an empty write to the FIFO which means there's
361                          * no one listening. */
362                         close(cooperative_pipe_fp);
363                         towatch = g_io_channel_new_file(fifopath, "r+", NULL);
364                         g_io_add_watch(towatch, G_IO_IN, (GIOFunc)remote_msg, NULL);
365                 }
366                 else
367                         cooperative_alone = FALSE;
368         }
369
370         g_free(fifopath);
371 }
372
373 void
374 changed_download_progress(GObject *obj, GParamSpec *pspec, gpointer data)
375 {
376         WebKitDownload *download = WEBKIT_DOWNLOAD(obj);
377         WebKitURIResponse *resp;
378         GtkToolItem *tb = GTK_TOOL_ITEM(data);
379         gdouble p, size_mb;
380         const gchar *uri;
381         gchar *t, *filename, *base;
382
383         p = webkit_download_get_estimated_progress(download) * 100;
384         resp = webkit_download_get_response(download);
385         size_mb = webkit_uri_response_get_content_length(resp) / 1e6;
386
387         uri = webkit_download_get_destination(download);
388         filename = g_filename_from_uri(uri, NULL, NULL);
389         if (filename == NULL)
390         {
391                 /* This really should not happen because WebKit uses that URI to
392                  * write to a file... */
393                 fprintf(stderr, __NAME__": Could not construct file name from URI!\n");
394                 t = g_strdup_printf("%s (%.0f%% of %.1f MB)",
395                                     webkit_uri_response_get_uri(resp), p, size_mb);
396         }
397         else
398         {
399                 base = g_path_get_basename(filename);
400                 t = g_strdup_printf("%s (%.0f%% of %.1f MB)", base, p, size_mb);
401                 g_free(filename);
402                 g_free(base);
403         }
404         gtk_tool_button_set_label(GTK_TOOL_BUTTON(tb), t);
405         g_free(t);
406 }
407
408 void
409 changed_load_progress(GObject *obj, GParamSpec *pspec, gpointer data)
410 {
411         struct Client *c = (struct Client *)data;
412         gdouble p;
413
414         p = webkit_web_view_get_estimated_load_progress(WEBKIT_WEB_VIEW(c->web_view));
415         gtk_level_bar_set_value(GTK_LEVEL_BAR(c->progress), p);
416 }
417
418 void
419 changed_title(GObject *obj, GParamSpec *pspec, gpointer data)
420 {
421         const gchar *t;
422         struct Client *c = (struct Client *)data;
423
424         t = webkit_web_view_get_title(WEBKIT_WEB_VIEW(c->web_view));
425         gtk_window_set_title(GTK_WINDOW(c->win), (t == NULL ? __NAME__ : t));
426 }
427
428 void
429 changed_uri(GObject *obj, GParamSpec *pspec, gpointer data)
430 {
431         const gchar *t;
432         struct Client *c = (struct Client *)data;
433
434         t = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
435         gtk_entry_set_text(GTK_ENTRY(c->location), (t == NULL ? __NAME__ : t));
436 }
437
438 gboolean
439 download_handle(WebKitWebView *web_view, WebKitDownload *download, gpointer data)
440 {
441         struct Client *c = (struct Client *)data;
442         WebKitURIResponse *resp;
443         gchar *path, *path2 = NULL, *uri;
444         GtkToolItem *tb;
445         gboolean ret;
446         int suffix = 1;
447
448         resp = webkit_download_get_response(download);
449         path = g_build_filename(download_dir,
450                                 webkit_uri_response_get_suggested_filename(resp),
451                                 NULL);
452         path2 = g_strdup(path);
453         while (g_file_test(path2, G_FILE_TEST_EXISTS) && suffix < 1000)
454         {
455                 g_free(path2);
456
457                 path2 = g_strdup_printf("%s.%d", path, suffix);
458                 suffix++;
459         }
460
461         if (suffix == 1000)
462         {
463                 fprintf(stderr, __NAME__": Suffix reached limit for download.\n");
464                 ret = FALSE;
465         }
466         else
467         {
468                 uri = g_filename_to_uri(path2, NULL, NULL);
469                 webkit_download_set_destination(download, uri);
470                 ret = TRUE;
471                 g_free(uri);
472
473                 gtk_level_bar_set_value(GTK_LEVEL_BAR(c->status), 1);
474                 downloads_indicated++;
475                 g_timeout_add(500, download_reset_indicator, c);
476
477                 tb = gtk_tool_button_new_from_stock(GTK_STOCK_DELETE);
478                 gtk_tool_button_set_label(GTK_TOOL_BUTTON(tb),
479                                           webkit_uri_response_get_suggested_filename(resp));
480                 gtk_toolbar_insert(GTK_TOOLBAR(dm.toolbar), tb, 0);
481                 gtk_widget_show_all(dm.toolbar);
482
483                 g_signal_connect(G_OBJECT(download), "notify::estimated-progress",
484                                  G_CALLBACK(changed_download_progress), tb);
485
486                 g_object_ref(download);
487                 g_signal_connect(G_OBJECT(tb), "clicked",
488                                  G_CALLBACK(downloadmanager_cancel), download);
489         }
490
491         g_free(path);
492         g_free(path2);
493
494         return ret;
495 }
496
497 gboolean
498 download_reset_indicator(gpointer data)
499 {
500         struct Client *c = (struct Client *)data;
501
502         downloads_indicated--;
503         if (downloads_indicated == 0)
504                 gtk_level_bar_set_value(GTK_LEVEL_BAR(c->status), 0);
505
506         return FALSE;
507 }
508
509 /*
510 gboolean
511 download_request(WebKitWebView *web_view, WebKitWebFrame *frame,
512                  WebKitNetworkRequest *request, gchar *mime_type,
513                  WebKitWebPolicyDecision *policy_decision, gpointer data)
514 {
515         if (!webkit_web_view_can_show_mime_type(web_view, mime_type))
516         {
517                 webkit_web_policy_decision_download(policy_decision);
518                 return TRUE;
519         }
520         return FALSE;
521 }
522 */
523
524 void
525 downloadmanager_cancel(GtkToolButton *tb, gpointer data)
526 {
527         WebKitDownload *download = WEBKIT_DOWNLOAD(data);
528
529         webkit_download_cancel(download);
530         g_object_unref(download);
531
532         gtk_widget_destroy(GTK_WIDGET(tb));
533 }
534
535 void
536 downloadmanager_setup(void)
537 {
538         dm.win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
539         gtk_window_set_type_hint(GTK_WINDOW(dm.win), GDK_WINDOW_TYPE_HINT_DIALOG);
540         gtk_window_set_default_size(GTK_WINDOW(dm.win), 500, 250);
541         gtk_window_set_title(GTK_WINDOW(dm.win), __NAME__" - Download Manager");
542         g_signal_connect(G_OBJECT(dm.win), "delete-event",
543                          G_CALLBACK(gtk_widget_hide_on_delete), NULL);
544         g_signal_connect(G_OBJECT(dm.win), "key-press-event",
545                          G_CALLBACK(key_downloadmanager), NULL);
546
547         dm.toolbar = gtk_toolbar_new();
548         gtk_orientable_set_orientation(GTK_ORIENTABLE(dm.toolbar),
549                                        GTK_ORIENTATION_VERTICAL);
550         gtk_toolbar_set_style(GTK_TOOLBAR(dm.toolbar), GTK_TOOLBAR_BOTH_HORIZ);
551         gtk_toolbar_set_show_arrow(GTK_TOOLBAR(dm.toolbar), FALSE);
552
553         dm.scroll = gtk_scrolled_window_new(NULL, NULL);
554         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dm.scroll),
555                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
556         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(dm.scroll),
557                                               dm.toolbar);
558
559         gtk_container_add(GTK_CONTAINER(dm.win), dm.scroll);
560 }
561
562 gchar *
563 ensure_uri_scheme(const gchar *t)
564 {
565         gchar *f;
566
567         f = g_ascii_strdown(t, -1);
568         if (!g_str_has_prefix(f, "http:") &&
569             !g_str_has_prefix(f, "https:") &&
570             !g_str_has_prefix(f, "file:") &&
571             !g_str_has_prefix(f, "about:"))
572         {
573                 g_free(f);
574                 f = g_strdup_printf("http://%s", t);
575                 return f;
576         }
577         else
578                 return g_strdup(t);
579 }
580
581 void
582 grab_environment_configuration(void)
583 {
584         const gchar *e;
585
586         e = g_getenv(__NAME_UPPERCASE__"_ACCEPTED_LANGUAGE");
587         if (e != NULL)
588                 accepted_language = g_strdup(e);
589
590         e = g_getenv(__NAME_UPPERCASE__"_DOWNLOAD_DIR");
591         if (e != NULL)
592                 download_dir = g_strdup(e);
593
594         e = g_getenv(__NAME_UPPERCASE__"_FIFO_SUFFIX");
595         if (e != NULL)
596                 fifo_suffix = g_strdup(e);
597
598         e = g_getenv(__NAME_UPPERCASE__"_HOME_URI");
599         if (e != NULL)
600                 home_uri = g_strdup(e);
601
602         e = g_getenv(__NAME_UPPERCASE__"_USER_AGENT");
603         if (e != NULL)
604                 user_agent = g_strdup(e);
605
606         e = g_getenv(__NAME_UPPERCASE__"_ZOOM");
607         if (e != NULL)
608                 global_zoom = atof(e);
609 }
610
611 void
612 hover_web_view(WebKitWebView *web_view, gchar *title, gchar *uri, gpointer data)
613 {
614         struct Client *c = (struct Client *)data;
615
616         if (!gtk_widget_is_focus(c->location))
617         {
618                 if (uri == NULL)
619                         gtk_entry_set_text(GTK_ENTRY(c->location),
620                                            webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view)));
621                 else
622                         gtk_entry_set_text(GTK_ENTRY(c->location), uri);
623         }
624 }
625
626 gboolean
627 key_downloadmanager(GtkWidget *widget, GdkEvent *event, gpointer data)
628 {
629         if (event->type == GDK_KEY_PRESS)
630         {
631                 if (((GdkEventKey *)event)->state & GDK_MOD1_MASK)
632                 {
633                         switch (((GdkEventKey *)event)->keyval)
634                         {
635                                 case GDK_KEY_d:  /* close window (left hand) */
636                                         gtk_widget_hide(dm.win);
637                                         return TRUE;
638                         }
639                 }
640         }
641
642         return FALSE;
643 }
644
645 gboolean
646 key_location(GtkWidget *widget, GdkEvent *event, gpointer data)
647 {
648         struct Client *c = (struct Client *)data;
649         const gchar *t;
650         gchar *f;
651
652         if (event->type == GDK_KEY_PRESS)
653         {
654                 if (((GdkEventKey *)event)->state & GDK_MOD1_MASK)
655                 {
656                         switch (((GdkEventKey *)event)->keyval)
657                         {
658                                 case GDK_KEY_q:  /* close window (left hand) */
659                                         gtk_widget_destroy(c->win);
660                                         return TRUE;
661                                 case GDK_KEY_d:  /* download manager (left hand) */
662                                         gtk_widget_show_all(dm.win);
663                                         return TRUE;
664                                 case GDK_KEY_r:  /* reload (left hand) */
665                                         webkit_web_view_reload_bypass_cache(WEBKIT_WEB_VIEW(
666                                                                             c->web_view));
667                                         return TRUE;
668                                 case GDK_KEY_k:  /* initiate search (BOTH hands) */
669                                         gtk_entry_set_text(GTK_ENTRY(c->location), "/");
670                                         gtk_editable_set_position(GTK_EDITABLE(c->location), -1);
671                                         return TRUE;
672                         }
673                 }
674                 else
675                 {
676                         switch (((GdkEventKey *)event)->keyval)
677                         {
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] == '/')
682                                         {
683                                                 if (search_text != NULL)
684                                                         g_free(search_text);
685                                                 search_text = g_strdup(t + 1);  /* XXX whacky */
686                                                 search(c, 1);
687                                         }
688                                         else if (!keywords_try_search(WEBKIT_WEB_VIEW(c->web_view), t))
689                                         {
690                                                 f = ensure_uri_scheme(t);
691                                                 if (show_all_requests)
692                                                         fprintf(stderr, "====> %s\n", f);
693                                                 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), f);
694                                                 g_free(f);
695                                         }
696                                         return TRUE;
697                                 case GDK_KEY_Escape:
698                                         t = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
699                                         gtk_entry_set_text(GTK_ENTRY(c->location),
700                                                            (t == NULL ? __NAME__ : t));
701                                         return TRUE;
702                         }
703                 }
704         }
705
706         return FALSE;
707 }
708
709 gboolean
710 key_web_view(GtkWidget *widget, GdkEvent *event, gpointer data)
711 {
712         struct Client *c = (struct Client *)data;
713         WebKitHitTestResultContext ht_context;
714         WebKitHitTestResult *ht_result = NULL;
715         gchar *ht_uri = NULL, *f;
716         gfloat z;
717         gboolean b;
718
719         if (event->type == GDK_KEY_PRESS)
720         {
721                 if (((GdkEventKey *)event)->state & GDK_MOD1_MASK)
722                 {
723                         switch (((GdkEventKey *)event)->keyval)
724                         {
725                                 case GDK_KEY_q:  /* close window (left hand) */
726                                         gtk_widget_destroy(c->win);
727                                         return TRUE;
728                                 case GDK_KEY_w:  /* home (left hand) */
729                                         f = ensure_uri_scheme(home_uri);
730                                         if (show_all_requests)
731                                                 fprintf(stderr, "====> %s\n", f);
732                                         webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), f);
733                                         g_free(f);
734                                         return TRUE;
735                                 case GDK_KEY_e:  /* new tab (left hand) */
736                                         f = ensure_uri_scheme(home_uri);
737                                         if (show_all_requests)
738                                                 fprintf(stderr, "====> %s\n", f);
739                                         client_new(f);
740                                         g_free(f);
741                                         return TRUE;
742                                 case GDK_KEY_r:  /* reload (left hand) */
743                                         webkit_web_view_reload_bypass_cache(WEBKIT_WEB_VIEW(
744                                                                             c->web_view));
745                                         return TRUE;
746 #if 0
747                                 case GDK_KEY_s:  /* toggle source view (left hand) */
748                                         b = webkit_web_view_get_view_source_mode(WEBKIT_WEB_VIEW(
749                                                                                  c->web_view));
750                                         b = !b;
751                                         webkit_web_view_set_view_source_mode(WEBKIT_WEB_VIEW(
752                                                                              c->web_view), b);
753                                         webkit_web_view_reload(WEBKIT_WEB_VIEW(c->web_view));
754                                         return TRUE;
755 #endif
756                                 case GDK_KEY_d:  /* download manager (left hand) */
757                                         gtk_widget_show_all(dm.win);
758                                         return TRUE;
759                                 case GDK_KEY_2:  /* search forward (left hand) */
760                                 case GDK_KEY_n:  /* search forward (maybe both hands) */
761                                         search(c, 1);
762                                         return TRUE;
763                                 case GDK_KEY_3:  /* search backward (left hand) */
764                                         search(c, -1);
765                                         return TRUE;
766                                 case GDK_KEY_l:  /* location (BOTH hands) */
767                                         gtk_widget_grab_focus(c->location);
768                                         return TRUE;
769                                 case GDK_KEY_k:  /* initiate search (BOTH hands) */
770                                         gtk_widget_grab_focus(c->location);
771                                         gtk_entry_set_text(GTK_ENTRY(c->location), "/");
772                                         gtk_editable_set_position(GTK_EDITABLE(c->location), -1);
773                                         return TRUE;
774                         }
775                 }
776                 else if (((GdkEventKey *)event)->keyval == GDK_KEY_Escape)
777                 {
778                         webkit_web_view_stop_loading(WEBKIT_WEB_VIEW(c->web_view));
779                         gtk_level_bar_set_value(GTK_LEVEL_BAR(c->progress), 0);
780                 }
781         }
782         else if (event->type == GDK_BUTTON_PRESS)
783         {
784                 switch (((GdkEventButton *)event)->button)
785                 {
786 #if 0
787                         case 2:
788                                 ht_result = webkit_web_view_get_hit_test_result(
789                                                                    WEBKIT_WEB_VIEW(c->web_view),
790                                                                        (GdkEventButton *)event);
791                                 g_object_get(ht_result, "context", &ht_context, NULL);
792                                 if (ht_context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK)
793                                 {
794                                         g_object_get(ht_result, "link-uri", &ht_uri, NULL);
795                                         client_new(ht_uri);
796                                         g_object_unref(ht_result);
797                                         return TRUE;
798                                 }
799                                 g_object_unref(ht_result);
800                                 break;
801 #endif
802                         case 8:
803                                 webkit_web_view_go_back(WEBKIT_WEB_VIEW(c->web_view));
804                                 return TRUE;
805                         case 9:
806                                 webkit_web_view_go_forward(WEBKIT_WEB_VIEW(c->web_view));
807                                 return TRUE;
808                 }
809         }
810         else if (event->type == GDK_SCROLL)
811         {
812                 if (((GdkEventScroll *)event)->state & GDK_MOD1_MASK ||
813                     ((GdkEventScroll *)event)->state & GDK_CONTROL_MASK)
814                 {
815                         switch (((GdkEventScroll *)event)->direction)
816                         {
817                                 case GDK_SCROLL_UP:
818                                         z = webkit_web_view_get_zoom_level(WEBKIT_WEB_VIEW(c->web_view));
819                                         z += 0.1;
820                                         webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(c->web_view), z);
821                                         return TRUE;
822                                 case GDK_SCROLL_DOWN:
823                                         z = webkit_web_view_get_zoom_level(WEBKIT_WEB_VIEW(c->web_view));
824                                         z -= 0.1;
825                                         webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(c->web_view), z);
826                                         return TRUE;
827                                 default:
828                                         break;
829                         }
830                 }
831         }
832
833         return FALSE;
834 }
835
836 void
837 keywords_load(void)
838 {
839         GError *err = NULL;
840         GIOChannel *channel = NULL;
841         gchar *path = NULL, *buf = NULL;
842         gchar **tokens = NULL;
843
844         keywords = g_hash_table_new(g_str_hash, g_str_equal);
845
846         path = g_build_filename(g_get_user_config_dir(), __NAME__, "keywordsearch",
847                                 NULL);
848         channel = g_io_channel_new_file(path, "r", &err);
849         if (channel != NULL)
850         {
851                 while (g_io_channel_read_line(channel, &buf, NULL, NULL, NULL)
852                        == G_IO_STATUS_NORMAL)
853                 {
854                         g_strstrip(buf);
855                         if (buf[0] != '#')
856                         {
857                                 tokens = g_strsplit(buf, " ", 2);
858                                 if (tokens[0] != NULL && tokens[1] != NULL)
859                                         g_hash_table_insert(keywords, g_strdup(tokens[0]),
860                                                             g_strdup(tokens[1]));
861                                 g_strfreev(tokens);
862                         }
863                         g_free(buf);
864                 }
865                 g_io_channel_shutdown(channel, FALSE, NULL);
866         }
867         g_free(path);
868 }
869
870 gboolean
871 keywords_try_search(WebKitWebView *web_view, const gchar *t)
872 {
873         gboolean ret = FALSE;
874         gchar **tokens = NULL;
875         gchar *val = NULL, *uri = NULL;
876
877         tokens = g_strsplit(t, " ", 2);
878         if (tokens[0] != NULL && tokens[1] != NULL)
879         {
880                 val = g_hash_table_lookup(keywords, tokens[0]);
881                 if (val != NULL)
882                 {
883                         uri = g_strdup_printf((gchar *)val, tokens[1]);
884                         if (show_all_requests)
885                                 fprintf(stderr, "====> %s\n", uri);
886                         webkit_web_view_load_uri(web_view, uri);
887                         g_free(uri);
888                         ret = TRUE;
889                 }
890         }
891         g_strfreev(tokens);
892
893         return ret;
894 }
895
896 gboolean
897 remote_msg(GIOChannel *channel, GIOCondition condition, gpointer data)
898 {
899         gchar *uri = NULL;
900
901         g_io_channel_read_line(channel, &uri, NULL, NULL, NULL);
902         if (uri)
903         {
904                 g_strstrip(uri);
905                 client_new(uri);
906                 g_free(uri);
907         }
908         return TRUE;
909 }
910
911 void
912 search(gpointer data, gint direction)
913 {
914         struct Client *c = (struct Client *)data;
915
916         if (search_text == NULL)
917                 return;
918
919         /*
920         webkit_web_view_search_text(WEBKIT_WEB_VIEW(c->web_view), search_text,
921                                     FALSE, direction == 1, TRUE);
922                                                                 */
923 }
924
925 Window
926 tabbed_launch(void)
927 {
928         gint tabbed_stdout;
929         GIOChannel *tabbed_stdout_channel;
930         GError *err = NULL;
931         gchar *output = NULL;
932         char *argv[] = { "tabbed", "-c", "-d", "-p", "s1", "-n", __NAME__, NULL };
933         Window plug_into;
934
935         if (!g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL,
936                                       NULL, NULL, NULL, &tabbed_stdout, NULL,
937                                       &err))
938         {
939                 fprintf(stderr, __NAME__": Could not launch tabbed: %s\n", err->message);
940                 g_error_free(err);
941                 return 0;
942         }
943
944         tabbed_stdout_channel = g_io_channel_unix_new(tabbed_stdout);
945         if (tabbed_stdout_channel == NULL)
946         {
947                 fprintf(stderr, __NAME__": Could open tabbed's stdout\n");
948                 return 0;
949         }
950         g_io_channel_read_line(tabbed_stdout_channel, &output, NULL, NULL, NULL);
951         g_io_channel_shutdown(tabbed_stdout_channel, FALSE, NULL);
952         if (output == NULL)
953         {
954                 fprintf(stderr, __NAME__": Could not read XID from tabbed\n");
955                 return 0;
956         }
957         g_strstrip(output);
958         plug_into = strtol(output, NULL, 16);
959         g_free(output);
960         if (plug_into == 0)
961                 fprintf(stderr, __NAME__": The XID from tabbed is 0\n");
962         return plug_into;
963 }
964
965 void
966 usage(void)
967 {
968         fprintf(stderr, "Usage: "__NAME__" [OPTION]... [URI]...\n");
969         exit(EXIT_FAILURE);
970 }
971
972
973 int
974 main(int argc, char **argv)
975 {
976         int opt, i;
977
978         gtk_init(&argc, &argv);
979
980         grab_environment_configuration();
981
982         while ((opt = getopt(argc, argv, "e:rCT")) != -1)
983         {
984                 switch (opt)
985                 {
986                         case 'e':
987                                 embed = atol(optarg);
988                                 tabbed_automagic = FALSE;
989                                 break;
990                         case 'r':
991                                 show_all_requests = TRUE;
992                                 break;
993                         case 'C':
994                                 cooperative_instances = FALSE;
995                                 break;
996                         case 'T':
997                                 tabbed_automagic = FALSE;
998                                 break;
999                         default:
1000                                 usage();
1001                 }
1002         }
1003
1004         /*adblock_load();*/
1005         keywords_load();
1006         cooperation_setup();
1007         downloadmanager_setup();
1008
1009         if (tabbed_automagic && !(cooperative_instances && !cooperative_alone))
1010                 embed = tabbed_launch();
1011
1012         if (optind >= argc)
1013                 client_new(home_uri);
1014         else
1015         {
1016                 for (i = optind; i < argc; i++)
1017                         client_new(argv[i]);
1018         }
1019
1020         if (!cooperative_instances || cooperative_alone)
1021                 gtk_main();
1022         exit(EXIT_SUCCESS);
1023 }