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