]> git.armaanb.net Git - chorizo.git/blob - browser.c
Refactoring: Group and unify identifiers
[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 <gdk/gdkx.h>
10 #include <gdk/gdkkeysyms.h>
11 #include <gio/gio.h>
12 #include <webkit/webkit.h>
13
14
15 #define DOWNLOAD_DIR "/tmp/tmp"
16 #define LANGUAGE "en-US"
17
18
19 static void adblock(WebKitWebView *, WebKitWebFrame *, WebKitWebResource *,
20                     WebKitNetworkRequest *, WebKitNetworkResponse *, gpointer);
21 static void adblock_load(void);
22 static void client_destroy(GtkWidget *, gpointer);
23 static void client_new(const gchar *uri);
24 static gboolean client_new_request(WebKitWebView *, WebKitWebFrame *,
25                                    WebKitNetworkRequest *,
26                                    WebKitWebNavigationAction *,
27                                    WebKitWebPolicyDecision *, gpointer);
28 static void cooperation_setup(void);
29 static void changed_load_status(GObject *obj, GParamSpec *pspec,
30                                 gpointer data);
31 static void changed_title(GObject *, GParamSpec *, gpointer);
32 static void changed_uri(GObject *, GParamSpec *, gpointer);
33 static gboolean download_request(WebKitWebView *, WebKitWebFrame *,
34                                  WebKitNetworkRequest *, gchar *,
35                                  WebKitWebPolicyDecision *, gpointer);
36 static gboolean download_wget(WebKitWebView *, WebKitDownload *, gpointer);
37 static void hover_web_view(WebKitWebView *, gchar *, gchar *, gpointer);
38 static gboolean key_location(GtkWidget *, GdkEvent *, gpointer);
39 static gboolean key_web_view(GtkWidget *, GdkEvent *, gpointer);
40 static gboolean remote_msg(GIOChannel *, GIOCondition, gpointer);
41 static void search(gpointer, gint);
42 static void scroll(GtkAdjustment *, gint, gdouble);
43 static Window tabbed_launch(void);
44 static void usage(void);
45
46
47 struct Client
48 {
49         GtkWidget *win;
50         GtkWidget *vbox;
51         GtkWidget *location;
52         GtkWidget *status;
53         GtkWidget *scroll;
54         GtkWidget *web_view;
55 };
56
57
58 static GSList *adblock_patterns = NULL;
59 static gint clients = 0;
60 static gboolean cooperative_alone = TRUE;
61 static gboolean cooperative_instances = TRUE;
62 static int cooperative_pipe_fp = 0;
63 static Window embed = 0;
64 static gchar *first_uri = NULL;
65 static gdouble global_zoom = 1.0;
66 static gboolean language_is_set = FALSE;
67 static gchar *search_text = NULL;
68 static gboolean show_all_requests = FALSE;
69 static gboolean tabbed_automagic = TRUE;
70
71
72 void
73 adblock(WebKitWebView *web_view, WebKitWebFrame *frame,
74         WebKitWebResource *resource, WebKitNetworkRequest *request,
75         WebKitNetworkResponse *response, gpointer data)
76 {
77         GSList *it = adblock_patterns;
78         const gchar *uri;
79
80         (void)web_view;
81         (void)frame;
82         (void)resource;
83         (void)response;
84         (void)data;
85
86         uri = webkit_network_request_get_uri(request);
87         if (show_all_requests)
88                 fprintf(stderr, "-> %s\n", uri);
89
90         while (it)
91         {
92                 if (g_regex_match((GRegex *)(it->data), uri, 0, NULL))
93                 {
94                         webkit_network_request_set_uri(request, "about:blank");
95                         if (show_all_requests)
96                                 fprintf(stderr, "\tBLOCKED!\n");
97                         return;
98                 }
99                 it = g_slist_next(it);
100         }
101 }
102
103 void
104 adblock_load(void)
105 {
106         GRegex *re = NULL;
107         GError *err = NULL;
108         GIOChannel *channel = NULL;
109         gchar *path = NULL;
110         gchar *buf = NULL;
111
112         path = g_build_filename(g_get_user_config_dir(), __NAME__, "adblock.black",
113                                 NULL);
114         channel = g_io_channel_new_file(path, "r", &err);
115         if (channel != NULL)
116         {
117                 while (g_io_channel_read_line(channel, &buf, NULL, NULL, NULL)
118                        == G_IO_STATUS_NORMAL)
119                 {
120                         g_strstrip(buf);
121                         re = g_regex_new(buf,
122                                          G_REGEX_CASELESS | G_REGEX_OPTIMIZE,
123                                          G_REGEX_MATCH_PARTIAL, &err);
124                         if (err != NULL)
125                         {
126                                 fprintf(stderr, __NAME__": Could not compile regex: %s\n", buf);
127                                 g_error_free(err);
128                                 err = NULL;
129                         }
130                         adblock_patterns = g_slist_append(adblock_patterns, re);
131
132                         g_free(buf);
133                 }
134         }
135         g_free(path);
136 }
137
138 void
139 client_destroy(GtkWidget *obj, gpointer data)
140 {
141         struct Client *c = (struct Client *)data;
142
143         (void)obj;
144
145         webkit_web_view_stop_loading(WEBKIT_WEB_VIEW(c->web_view));
146         gtk_widget_destroy(c->web_view);
147         gtk_widget_destroy(c->scroll);
148         gtk_widget_destroy(c->status);
149         gtk_widget_destroy(c->location);
150         gtk_widget_destroy(c->vbox);
151         gtk_widget_destroy(c->win);
152         free(c);
153
154         clients--;
155         if (clients == 0)
156                 gtk_main_quit();
157 }
158
159 void
160 client_new(const gchar *uri)
161 {
162         if (cooperative_instances && !cooperative_alone)
163         {
164                 write(cooperative_pipe_fp, uri, strlen(uri));
165                 write(cooperative_pipe_fp, "\n", 1);
166                 return;
167         }
168
169         struct Client *c = malloc(sizeof(struct Client));
170         if (!c)
171         {
172                 fprintf(stderr, __NAME__": fatal: malloc failed\n");
173                 exit(EXIT_FAILURE);
174         }
175
176         c->win = NULL;
177         if (embed != 0)
178         {
179                 c->win = gtk_plug_new(embed);
180                 if (!gtk_plug_get_embedded(GTK_PLUG(c->win)))
181                 {
182                         fprintf(stderr, __NAME__": Can't plug-in to XID %ld.\n", embed);
183                         gtk_widget_destroy(c->win);
184                         c->win = NULL;
185                         embed = 0;
186                 }
187         }
188
189         if (c->win == NULL)
190                 c->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
191
192         /* When using Gtk2, it only shows a white area when run in suckless'
193          * tabbed. It appears we need to set a default window size for this
194          * to work. This is not needed when using Gtk3. */
195         gtk_window_set_default_size(GTK_WINDOW(c->win), 1024, 768);
196
197         g_signal_connect(G_OBJECT(c->win), "destroy",
198                          G_CALLBACK(client_destroy), c);
199         gtk_window_set_title(GTK_WINDOW(c->win), __NAME__);
200
201         c->web_view = webkit_web_view_new();
202         webkit_web_view_set_full_content_zoom(WEBKIT_WEB_VIEW(c->web_view), TRUE);
203         webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(c->web_view), global_zoom);
204         g_signal_connect(G_OBJECT(c->web_view), "notify::title",
205                          G_CALLBACK(changed_title), c);
206         g_signal_connect(G_OBJECT(c->web_view), "notify::uri",
207                          G_CALLBACK(changed_uri), c);
208         g_signal_connect(G_OBJECT(c->web_view), "notify::load-status",
209                          G_CALLBACK(changed_load_status), c);
210         g_signal_connect(G_OBJECT(c->web_view),
211                          "new-window-policy-decision-requested",
212                          G_CALLBACK(client_new_request), NULL);
213         g_signal_connect(G_OBJECT(c->web_view),
214                          "mime-type-policy-decision-requested",
215                          G_CALLBACK(download_request), NULL);
216         g_signal_connect(G_OBJECT(c->web_view), "download-requested",
217                          G_CALLBACK(download_wget), NULL);
218         g_signal_connect(G_OBJECT(c->web_view), "key-press-event",
219                          G_CALLBACK(key_web_view), c);
220         g_signal_connect(G_OBJECT(c->web_view), "hovering-over-link",
221                          G_CALLBACK(hover_web_view), c);
222         g_signal_connect(G_OBJECT(c->web_view), "resource-request-starting",
223                          G_CALLBACK(adblock), NULL);
224
225         if (!language_is_set)
226         {
227                 g_object_set(webkit_get_default_session(), "accept-language", LANGUAGE,
228                              NULL);
229                 language_is_set = TRUE;
230         }
231
232         c->scroll = gtk_scrolled_window_new(NULL, NULL);
233
234         gtk_container_add(GTK_CONTAINER(c->scroll), c->web_view);
235
236         c->location = gtk_entry_new();
237         g_signal_connect(G_OBJECT(c->location), "key-press-event",
238                          G_CALLBACK(key_location), c);
239
240         c->status = gtk_statusbar_new();
241         gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(c->status), FALSE);
242
243         c->vbox = gtk_vbox_new(FALSE, 2);
244         gtk_box_pack_start(GTK_BOX(c->vbox), c->location, FALSE, FALSE, 0);
245         gtk_container_add(GTK_CONTAINER(c->vbox), c->scroll);
246         gtk_box_pack_end(GTK_BOX(c->vbox), c->status, FALSE, FALSE, 0);
247
248         gtk_container_add(GTK_CONTAINER(c->win), c->vbox);
249
250         gtk_widget_grab_focus(c->web_view);
251         gtk_widget_show_all(c->win);
252
253         webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), uri);
254
255         clients++;
256 }
257
258 gboolean
259 client_new_request(WebKitWebView *web_view, WebKitWebFrame *frame,
260                    WebKitNetworkRequest *request,
261                    WebKitWebNavigationAction *navigation_action,
262                    WebKitWebPolicyDecision *policy_decision, gpointer user_data)
263 {
264         (void)web_view;
265         (void)frame;
266         (void)navigation_action;
267         (void)user_data;
268
269         webkit_web_policy_decision_ignore(policy_decision);
270         client_new(webkit_network_request_get_uri(request));
271
272         return TRUE;
273 }
274
275 void
276 cooperation_setup(void)
277 {
278         GIOChannel *towatch;
279         gchar *fifopath;
280
281         fifopath = g_build_filename(g_get_user_runtime_dir(), __NAME__".fifo", NULL);
282
283         if (!g_file_test(fifopath, G_FILE_TEST_EXISTS))
284                 mkfifo(fifopath, 0600);
285
286         cooperative_pipe_fp = open(fifopath, O_WRONLY | O_NONBLOCK);
287         if (!cooperative_pipe_fp)
288         {
289                 fprintf(stderr, __NAME__": Can't open FIFO at all.\n");
290         }
291         else
292         {
293                 if (write(cooperative_pipe_fp, "", 0) == -1)
294                 {
295                         /* Could not do an empty write to the FIFO which means there's
296                          * no one listening. */
297                         close(cooperative_pipe_fp);
298                         towatch = g_io_channel_new_file(fifopath, "r+", NULL);
299                         g_io_add_watch(towatch, G_IO_IN, (GIOFunc)remote_msg, NULL);
300                 }
301                 else
302                         cooperative_alone = FALSE;
303         }
304
305         g_free(fifopath);
306 }
307
308 void
309 changed_load_status(GObject *obj, GParamSpec *pspec, gpointer data)
310 {
311         struct Client *c = (struct Client *)data;
312
313         (void)obj;
314         (void)pspec;
315
316         if (webkit_web_view_get_load_status(WEBKIT_WEB_VIEW(c->web_view))
317             == WEBKIT_LOAD_FINISHED)
318         {
319                 gtk_statusbar_pop(GTK_STATUSBAR(c->status), 1);
320                 gtk_statusbar_push(GTK_STATUSBAR(c->status), 1, "Finished.");
321         }
322         else
323         {
324                 gtk_statusbar_pop(GTK_STATUSBAR(c->status), 1);
325                 gtk_statusbar_push(GTK_STATUSBAR(c->status), 1, "Loading...");
326         }
327 }
328
329 void
330 changed_title(GObject *obj, GParamSpec *pspec, gpointer data)
331 {
332         const gchar *t;
333         struct Client *c = (struct Client *)data;
334
335         (void)obj;
336         (void)pspec;
337
338         t = webkit_web_view_get_title(WEBKIT_WEB_VIEW(c->web_view));
339         gtk_window_set_title(GTK_WINDOW(c->win), (t == NULL ? __NAME__ : t));
340 }
341
342 void
343 changed_uri(GObject *obj, GParamSpec *pspec, gpointer data)
344 {
345         const gchar *t;
346         struct Client *c = (struct Client *)data;
347
348         (void)obj;
349         (void)pspec;
350
351         t = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
352         gtk_entry_set_text(GTK_ENTRY(c->location), (t == NULL ? __NAME__ : t));
353 }
354
355 gboolean
356 download_request(WebKitWebView *web_view, WebKitWebFrame *frame,
357                  WebKitNetworkRequest *request, gchar *mime_type,
358                  WebKitWebPolicyDecision *policy_decision, gpointer data)
359 {
360         (void)frame;
361         (void)request;
362         (void)data;
363
364         if (!webkit_web_view_can_show_mime_type(web_view, mime_type))
365         {
366                 webkit_web_policy_decision_download(policy_decision);
367                 return TRUE;
368         }
369         return FALSE;
370 }
371
372 gboolean
373 download_wget(WebKitWebView *web_view, WebKitDownload *download, gpointer data)
374 {
375         const gchar *uri;
376         char id[16] = "";
377         gint ret;
378
379         (void)web_view;
380         (void)data;
381
382         uri = webkit_download_get_uri(download);
383         if (fork() == 0)
384         {
385                 chdir(DOWNLOAD_DIR);
386                 if (embed == 0)
387                         ret = execlp("xterm", "xterm", "-hold", "-e", "wget", uri, NULL);
388                 else
389                 {
390                         if (snprintf(id, 16, "%ld", embed) >= 16)
391                         {
392                                 fprintf(stderr, __NAME__": id for xterm embed truncated!\n");
393                                 exit(EXIT_FAILURE);
394                         }
395                         ret = execlp("xterm", "xterm", "-hold", "-into", id, "-e", "wget",
396                                      uri, NULL);
397                 }
398
399                 if (ret == -1)
400                 {
401                         fprintf(stderr, __NAME__": exec'ing xterm for download");
402                         perror(" failed");
403                         exit(EXIT_FAILURE);
404                 }
405         }
406
407         return FALSE;
408 }
409
410 void
411 hover_web_view(WebKitWebView *web_view, gchar *title, gchar *uri,
412                    gpointer data)
413 {
414         struct Client *c = (struct Client *)data;
415
416         (void)web_view;
417         (void)title;
418
419         gtk_statusbar_pop(GTK_STATUSBAR(c->status), 0);
420         if (uri != NULL)
421                 gtk_statusbar_push(GTK_STATUSBAR(c->status), 0, uri);
422 }
423
424 gboolean
425 key_location(GtkWidget *widget, GdkEvent *event, gpointer data)
426 {
427         struct Client *c = (struct Client *)data;
428         const gchar *t;
429
430         (void)widget;
431
432         if (event->type == GDK_KEY_PRESS)
433         {
434                 if (((GdkEventKey *)event)->keyval == GDK_KEY_Return)
435                 {
436                         gtk_widget_grab_focus(c->web_view);
437                         t = gtk_entry_get_text(GTK_ENTRY(c->location));
438                         if (t != NULL && t[0] == '/')
439                         {
440                                 if (search_text != NULL)
441                                         g_free(search_text);
442                                 search_text = g_strdup(t + 1);  /* XXX whacky */
443                                 search(c, 1);
444                         }
445                         else
446                                 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), t);
447                         return TRUE;
448                 }
449         }
450
451         return FALSE;
452 }
453
454 gboolean
455 key_web_view(GtkWidget *widget, GdkEvent *event, gpointer data)
456 {
457         struct Client *c = (struct Client *)data;
458
459         (void)widget;
460
461         if (event->type == GDK_KEY_PRESS)
462         {
463                 if (((GdkEventKey *)event)->state & GDK_CONTROL_MASK)
464                 {
465                         if (((GdkEventKey *)event)->keyval == GDK_KEY_o)
466                         {
467                                 gtk_widget_grab_focus(c->location);
468                                 return TRUE;
469                         }
470                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_h)
471                         {
472                                 scroll(gtk_scrolled_window_get_hadjustment(
473                                        GTK_SCROLLED_WINDOW(c->scroll)), 0, -1);
474                                 return TRUE;
475                         }
476                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_j)
477                         {
478                                 scroll(gtk_scrolled_window_get_vadjustment(
479                                        GTK_SCROLLED_WINDOW(c->scroll)), 0, 1);
480                                 return TRUE;
481                         }
482                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_k)
483                         {
484                                 scroll(gtk_scrolled_window_get_vadjustment(
485                                        GTK_SCROLLED_WINDOW(c->scroll)), 0, -1);
486                                 return TRUE;
487                         }
488                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_l)
489                         {
490                                 scroll(gtk_scrolled_window_get_hadjustment(
491                                        GTK_SCROLLED_WINDOW(c->scroll)), 0, 1);
492                                 return TRUE;
493                         }
494                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_f)
495                         {
496                                 scroll(gtk_scrolled_window_get_vadjustment(
497                                        GTK_SCROLLED_WINDOW(c->scroll)), 1, 0.5);
498                                 return TRUE;
499                         }
500                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_b)
501                         {
502                                 scroll(gtk_scrolled_window_get_vadjustment(
503                                        GTK_SCROLLED_WINDOW(c->scroll)), 1, -0.5);
504                                 return TRUE;
505                         }
506                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_n)
507                         {
508                                 search(c, 1);
509                                 return TRUE;
510                         }
511                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_p)
512                         {
513                                 search(c, -1);
514                                 return TRUE;
515                         }
516                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_g)
517                         {
518                                 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view),
519                                                          first_uri);
520                                 return TRUE;
521                         }
522                 }
523                 else if (((GdkEventKey *)event)->keyval == GDK_KEY_Escape)
524                 {
525                         webkit_web_view_stop_loading(WEBKIT_WEB_VIEW(c->web_view));
526                         gtk_statusbar_pop(GTK_STATUSBAR(c->status), 1);
527                         gtk_statusbar_push(GTK_STATUSBAR(c->status), 1, "Aborted.");
528                 }
529         }
530
531         return FALSE;
532 }
533
534 gboolean
535 remote_msg(GIOChannel *channel, GIOCondition condition, gpointer data)
536 {
537         gchar *uri = NULL;
538
539         (void)condition;
540         (void)data;
541
542         g_io_channel_read_line(channel, &uri, NULL, NULL, NULL);
543         if (uri)
544         {
545                 g_strstrip(uri);
546                 client_new(uri);
547                 g_free(uri);
548         }
549         return TRUE;
550 }
551
552 void
553 search(gpointer data, gint direction)
554 {
555         struct Client *c = (struct Client *)data;
556
557         if (search_text == NULL)
558                 return;
559
560         webkit_web_view_search_text(WEBKIT_WEB_VIEW(c->web_view), search_text,
561                                     FALSE, direction == 1, TRUE);
562 }
563
564 void
565 scroll(GtkAdjustment *a, gint step_type, gdouble factor)
566 {
567         gdouble new, lower, upper, step;
568         lower = gtk_adjustment_get_lower(a);
569         upper = gtk_adjustment_get_upper(a) - gtk_adjustment_get_page_size(a) + lower;
570         if (step_type == 0)
571                 step = gtk_adjustment_get_step_increment(a);
572         else
573                 step = gtk_adjustment_get_page_increment(a);
574         new = gtk_adjustment_get_value(a) + factor * step;
575         new = new < lower ? lower : new;
576         new = new > upper ? upper : new;
577         gtk_adjustment_set_value(a, new);
578 }
579
580 Window
581 tabbed_launch(void)
582 {
583         gint tabbed_stdout;
584         GIOChannel *tabbed_stdout_channel;
585         GError *err = NULL;
586         gchar *output = NULL;
587         char *argv[] = { "tabbed", "-c", "-d", NULL };
588         Window plug_into;
589
590         if (!g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL,
591                                       NULL, NULL, NULL, &tabbed_stdout, NULL,
592                                       &err))
593         {
594                 fprintf(stderr, __NAME__": Could not launch tabbed: %s\n", err->message);
595                 g_error_free(err);
596                 return 0;
597         }
598
599         tabbed_stdout_channel = g_io_channel_unix_new(tabbed_stdout);
600         g_io_channel_read_line(tabbed_stdout_channel, &output, NULL, NULL, NULL);
601         if (output == NULL)
602         {
603                 fprintf(stderr, __NAME__": Could not read XID from tabbed\n");
604                 return 0;
605         }
606
607         g_io_channel_shutdown(tabbed_stdout_channel, FALSE, NULL);
608
609         g_strstrip(output);
610         plug_into = strtol(output, NULL, 16);
611         g_free(output);
612         return plug_into;
613 }
614
615 void
616 usage(void)
617 {
618         fprintf(stderr, "Usage: "__NAME__" [OPTION]... <URI>...\n");
619         exit(EXIT_FAILURE);
620 }
621
622
623 int
624 main(int argc, char **argv)
625 {
626         int opt, i;
627
628         gtk_init(&argc, &argv);
629
630         while ((opt = getopt(argc, argv, "z:e:rCT")) != -1)
631         {
632                 switch (opt)
633                 {
634                         case 'z':
635                                 global_zoom = atof(optarg);
636                                 break;
637                         case 'e':
638                                 embed = atol(optarg);
639                                 tabbed_automagic = FALSE;
640                                 break;
641                         case 'r':
642                                 show_all_requests = TRUE;
643                                 break;
644                         case 'C':
645                                 cooperative_instances = FALSE;
646                                 break;
647                         case 'T':
648                                 tabbed_automagic = FALSE;
649                                 break;
650                         default:
651                                 usage();
652                 }
653         }
654
655         if (optind >= argc)
656                 usage();
657
658         adblock_load();
659         cooperation_setup();
660
661         if (tabbed_automagic && !(cooperative_instances && !cooperative_alone))
662                 embed = tabbed_launch();
663
664         first_uri = g_strdup(argv[optind]);
665         for (i = optind; i < argc; i++)
666                 client_new(argv[i]);
667         if (!cooperative_instances || cooperative_alone)
668                 gtk_main();
669         exit(EXIT_SUCCESS);
670 }