]> git.armaanb.net Git - chorizo.git/blob - zea.c
Complete adblock
[chorizo.git] / zea.c
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 #include <gtk/gtk.h>
5 #include <gdk/gdkx.h>
6 #include <gdk/gdkkeysyms.h>
7 #include <webkit/webkit.h>
8
9
10 #define DOWNLOAD_DIR "/tmp/tmp"
11
12
13 static void zea_adblock(WebKitWebView *, WebKitWebFrame *, WebKitWebResource *,
14                         WebKitNetworkRequest *, WebKitNetworkResponse *, gpointer);
15 static void zea_destroy_client(GtkWidget *, gpointer);
16 static gboolean zea_do_download(WebKitWebView *, WebKitDownload *, gpointer);
17 static gboolean zea_download_request(WebKitWebView *, WebKitWebFrame *,
18                                      WebKitNetworkRequest *, gchar *,
19                                      WebKitWebPolicyDecision *, gpointer);
20 static void zea_load_adblock(void);
21 static void zea_load_status_changed(GObject *obj, GParamSpec *pspec,
22                                     gpointer data);
23 static gboolean zea_location_key(GtkWidget *, GdkEvent *, gpointer);
24 static void zea_new_client(const gchar *uri);
25 static gboolean zea_new_client_request(WebKitWebView *, WebKitWebFrame *,
26                                        WebKitNetworkRequest *,
27                                        WebKitWebNavigationAction *,
28                                        WebKitWebPolicyDecision *, gpointer);
29 static void zea_search(gpointer, gint);
30 static void zea_scroll(GtkAdjustment *, gint, gdouble);
31 static void zea_title_changed(GObject *, GParamSpec *, gpointer);
32 static void zea_uri_changed(GObject *, GParamSpec *, gpointer);
33 static void zea_web_view_hover(WebKitWebView *, gchar *, gchar *, gpointer);
34 static gboolean zea_web_view_key(GtkWidget *, GdkEvent *, gpointer);
35
36
37 static Window embed = 0;
38 static gint clients = 0;
39 static gdouble global_zoom = 1.0;
40 static gchar *search_text = NULL;
41 static gchar *first_uri = NULL;
42 static gboolean show_all_requests = FALSE;
43 static GSList *adblock_patterns = NULL;
44
45
46 struct Client
47 {
48         GtkWidget *win;
49         GtkWidget *vbox;
50         GtkWidget *location;
51         GtkWidget *status;
52         GtkWidget *scroll;
53         GtkWidget *web_view;
54 };
55
56
57 void
58 zea_adblock(WebKitWebView *web_view, WebKitWebFrame *frame,
59             WebKitWebResource *resource, WebKitNetworkRequest *request,
60             WebKitNetworkResponse *response, gpointer data)
61 {
62         GSList *it = adblock_patterns;
63         const gchar *uri;
64
65         (void)web_view;
66         (void)frame;
67         (void)resource;
68         (void)response;
69         (void)data;
70
71         uri = webkit_network_request_get_uri(request);
72         if (show_all_requests)
73                 fprintf(stderr, "-> %s\n", uri);
74
75         while (it)
76         {
77                 if (g_regex_match((GRegex *)(it->data), uri, 0, NULL))
78                 {
79                         webkit_network_request_set_uri(request, "about:blank");
80                         if (show_all_requests)
81                                 fprintf(stderr, "\tBLOCKED!\n");
82                         return;
83                 }
84                 it = g_slist_next(it);
85         }
86 }
87
88 void
89 zea_destroy_client(GtkWidget *obj, gpointer data)
90 {
91         struct Client *c = (struct Client *)data;
92
93         (void)obj;
94
95         webkit_web_view_stop_loading(WEBKIT_WEB_VIEW(c->web_view));
96         gtk_widget_destroy(c->web_view);
97         gtk_widget_destroy(c->scroll);
98         gtk_widget_destroy(c->status);
99         gtk_widget_destroy(c->location);
100         gtk_widget_destroy(c->vbox);
101         gtk_widget_destroy(c->win);
102         free(c);
103
104         clients--;
105         if (clients == 0)
106                 gtk_main_quit();
107 }
108
109 gboolean
110 zea_do_download(WebKitWebView *web_view, WebKitDownload *download, gpointer data)
111 {
112         const gchar *uri;
113         char id[16] = "";
114         gint ret;
115
116         (void)web_view;
117         (void)data;
118
119         uri = webkit_download_get_uri(download);
120         if (fork() == 0)
121         {
122                 chdir(DOWNLOAD_DIR);
123                 if (embed == 0)
124                         ret = execlp("xterm", "xterm", "-hold", "-e", "wget", uri, NULL);
125                 else
126                 {
127                         if (snprintf(id, 16, "%ld", embed) >= 16)
128                         {
129                                 fprintf(stderr, "zea: id for xterm embed truncated!\n");
130                                 exit(EXIT_FAILURE);
131                         }
132                         ret = execlp("xterm", "xterm", "-hold", "-into", id, "-e", "wget",
133                                      uri, NULL);
134                 }
135
136                 if (ret == -1)
137                 {
138                         fprintf(stderr, "zea: exec'ing xterm for download");
139                         perror(" failed");
140                         exit(EXIT_FAILURE);
141                 }
142         }
143
144         return FALSE;
145 }
146
147 gboolean
148 zea_download_request(WebKitWebView *web_view, WebKitWebFrame *frame,
149                      WebKitNetworkRequest *request, gchar *mime_type,
150                      WebKitWebPolicyDecision *policy_decision,
151                      gpointer data)
152 {
153         (void)frame;
154         (void)request;
155         (void)data;
156
157         if (!webkit_web_view_can_show_mime_type(web_view, mime_type))
158         {
159                 webkit_web_policy_decision_download(policy_decision);
160                 return TRUE;
161         }
162         return FALSE;
163 }
164
165 gboolean
166 zea_location_key(GtkWidget *widget, GdkEvent *event, gpointer data)
167 {
168         struct Client *c = (struct Client *)data;
169         const gchar *t;
170
171         (void)widget;
172
173         if (event->type == GDK_KEY_PRESS)
174         {
175                 if (((GdkEventKey *)event)->keyval == GDK_KEY_Return)
176                 {
177                         gtk_widget_grab_focus(c->web_view);
178                         t = gtk_entry_get_text(GTK_ENTRY(c->location));
179                         if (t != NULL && t[0] == '/')
180                         {
181                                 if (search_text != NULL)
182                                         g_free(search_text);
183                                 search_text = g_strdup(t + 1);  /* XXX whacky */
184                                 zea_search(c, 1);
185                         }
186                         else
187                                 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), t);
188                         return TRUE;
189                 }
190         }
191
192         return FALSE;
193 }
194
195 void
196 zea_new_client(const gchar *uri)
197 {
198         struct Client *c = malloc(sizeof(struct Client));
199         if (!c)
200         {
201                 fprintf(stderr, "zea: fatal: malloc failed\n");
202                 exit(EXIT_FAILURE);
203         }
204
205         if (embed == 0)
206         {
207                 c->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
208         }
209         else
210         {
211                 c->win = gtk_plug_new(embed);
212         }
213
214         /* When using Gtk2, zea only shows a white area when run in
215          * suckless' tabbed. It appears we need to set a default window size
216          * for this to work. This is not needed when using Gtk3. */
217         gtk_window_set_default_size(GTK_WINDOW(c->win), 1024, 768);
218
219         g_signal_connect(G_OBJECT(c->win), "destroy",
220                          G_CALLBACK(zea_destroy_client), c);
221         gtk_window_set_title(GTK_WINDOW(c->win), "zea");
222
223         c->web_view = webkit_web_view_new();
224         webkit_web_view_set_full_content_zoom(WEBKIT_WEB_VIEW(c->web_view), TRUE);
225         webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(c->web_view), global_zoom);
226         g_signal_connect(G_OBJECT(c->web_view), "notify::title",
227                          G_CALLBACK(zea_title_changed), c);
228         g_signal_connect(G_OBJECT(c->web_view), "notify::uri",
229                          G_CALLBACK(zea_uri_changed), c);
230         g_signal_connect(G_OBJECT(c->web_view), "notify::load-status",
231                          G_CALLBACK(zea_load_status_changed), c);
232         g_signal_connect(G_OBJECT(c->web_view),
233                          "new-window-policy-decision-requested",
234                          G_CALLBACK(zea_new_client_request), NULL);
235         g_signal_connect(G_OBJECT(c->web_view),
236                          "mime-type-policy-decision-requested",
237                          G_CALLBACK(zea_download_request), NULL);
238         g_signal_connect(G_OBJECT(c->web_view), "download-requested",
239                          G_CALLBACK(zea_do_download), NULL);
240         g_signal_connect(G_OBJECT(c->web_view), "key-press-event",
241                          G_CALLBACK(zea_web_view_key), c);
242         g_signal_connect(G_OBJECT(c->web_view), "hovering-over-link",
243                          G_CALLBACK(zea_web_view_hover), c);
244         g_signal_connect(G_OBJECT(c->web_view), "resource-request-starting",
245                          G_CALLBACK(zea_adblock), NULL);
246
247         c->scroll = gtk_scrolled_window_new(NULL, NULL);
248
249         gtk_container_add(GTK_CONTAINER(c->scroll), c->web_view);
250
251         c->location = gtk_entry_new();
252         g_signal_connect(G_OBJECT(c->location), "key-press-event",
253                          G_CALLBACK(zea_location_key), c);
254
255         c->status = gtk_statusbar_new();
256
257         c->vbox = gtk_vbox_new(FALSE, 0);
258         gtk_box_pack_start(GTK_BOX(c->vbox), c->location, FALSE, FALSE, 0);
259         gtk_container_add(GTK_CONTAINER(c->vbox), c->scroll);
260         gtk_box_pack_end(GTK_BOX(c->vbox), c->status, FALSE, FALSE, 0);
261
262         gtk_container_add(GTK_CONTAINER(c->win), c->vbox);
263
264         gtk_widget_grab_focus(c->web_view);
265         gtk_widget_show_all(c->win);
266
267         webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view), uri);
268
269         clients++;
270 }
271
272 gboolean
273 zea_new_client_request(WebKitWebView *web_view, WebKitWebFrame *frame,
274                        WebKitNetworkRequest *request,
275                        WebKitWebNavigationAction *navigation_action,
276                        WebKitWebPolicyDecision *policy_decision,
277                        gpointer user_data)
278 {
279         (void)web_view;
280         (void)frame;
281         (void)navigation_action;
282         (void)user_data;
283
284         webkit_web_policy_decision_ignore(policy_decision);
285         zea_new_client(webkit_network_request_get_uri(request));
286
287         return TRUE;
288 }
289
290 void
291 zea_search(gpointer data, gint direction)
292 {
293         struct Client *c = (struct Client *)data;
294
295         if (search_text == NULL)
296                 return;
297
298         webkit_web_view_search_text(WEBKIT_WEB_VIEW(c->web_view), search_text,
299                                     FALSE, direction == 1, TRUE);
300 }
301
302 void
303 zea_scroll(GtkAdjustment *a, gint step_type, gdouble factor)
304 {
305         gdouble new, lower, upper, step;
306         lower = gtk_adjustment_get_lower(a);
307         upper = gtk_adjustment_get_upper(a) - gtk_adjustment_get_page_size(a) + lower;
308         if (step_type == 0)
309                 step = gtk_adjustment_get_step_increment(a);
310         else
311                 step = gtk_adjustment_get_page_increment(a);
312         new = gtk_adjustment_get_value(a) + factor * step;
313         new = new < lower ? lower : new;
314         new = new > upper ? upper : new;
315         gtk_adjustment_set_value(a, new);
316 }
317
318 void
319 zea_title_changed(GObject *obj, GParamSpec *pspec, gpointer data)
320 {
321         const gchar *t;
322         struct Client *c = (struct Client *)data;
323
324         (void)obj;
325         (void)pspec;
326
327         t = webkit_web_view_get_title(WEBKIT_WEB_VIEW(c->web_view));
328         gtk_window_set_title(GTK_WINDOW(c->win), (t == NULL ? "zea" : t));
329 }
330
331 void
332 zea_uri_changed(GObject *obj, GParamSpec *pspec, gpointer data)
333 {
334         const gchar *t;
335         struct Client *c = (struct Client *)data;
336
337         (void)obj;
338         (void)pspec;
339
340         t = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(c->web_view));
341         gtk_entry_set_text(GTK_ENTRY(c->location), (t == NULL ? "zea" : t));
342 }
343
344 void
345 zea_load_adblock(void)
346 {
347         GRegex *re = NULL;
348         GError *err = NULL;
349         GIOChannel *channel = NULL;
350         gchar *path = NULL;
351         gchar *buf = NULL;
352         gsize length, term;
353
354         path = g_strdup_printf("%s/zea/adblock.black", g_get_user_config_dir());
355         channel = g_io_channel_new_file(path, "r", &err);
356         if (channel != NULL)
357         {
358                 while (g_io_channel_read_line(channel, &buf, &length, &term, &err)
359                        == G_IO_STATUS_NORMAL)
360                 {
361                         g_strstrip(buf);
362                         re = g_regex_new(buf,
363                                          G_REGEX_CASELESS | G_REGEX_OPTIMIZE,
364                                          G_REGEX_MATCH_PARTIAL, &err);
365                         if (err != NULL)
366                         {
367                                 fprintf(stderr, "zea: Could not compile regex: %s\n", buf);
368                                 g_error_free(err);
369                                 err = NULL;
370                         }
371                         adblock_patterns = g_slist_append(adblock_patterns, re);
372
373                         g_free(buf);
374                 }
375
376                 if (err != NULL)
377                         g_free(err);
378         }
379         g_free(path);
380 }
381
382 void
383 zea_load_status_changed(GObject *obj, GParamSpec *pspec, gpointer data)
384 {
385         struct Client *c = (struct Client *)data;
386
387         (void)obj;
388         (void)pspec;
389
390         if (webkit_web_view_get_load_status(WEBKIT_WEB_VIEW(c->web_view))
391             == WEBKIT_LOAD_FINISHED)
392         {
393                 gtk_statusbar_pop(GTK_STATUSBAR(c->status), 1);
394                 gtk_statusbar_push(GTK_STATUSBAR(c->status), 1, "Finished.");
395         }
396         else
397         {
398                 gtk_statusbar_pop(GTK_STATUSBAR(c->status), 1);
399                 gtk_statusbar_push(GTK_STATUSBAR(c->status), 1, "Loading...");
400         }
401 }
402
403 void
404 zea_web_view_hover(WebKitWebView *web_view, gchar *title, gchar *uri,
405                    gpointer data)
406 {
407         struct Client *c = (struct Client *)data;
408
409         (void)web_view;
410         (void)title;
411
412         gtk_statusbar_pop(GTK_STATUSBAR(c->status), 0);
413         if (uri != NULL)
414                 gtk_statusbar_push(GTK_STATUSBAR(c->status), 0, uri);
415 }
416
417 gboolean
418 zea_web_view_key(GtkWidget *widget, GdkEvent *event, gpointer data)
419 {
420         struct Client *c = (struct Client *)data;
421
422         (void)widget;
423
424         if (event->type == GDK_KEY_PRESS)
425         {
426                 if (((GdkEventKey *)event)->state & GDK_CONTROL_MASK)
427                 {
428                         if (((GdkEventKey *)event)->keyval == GDK_KEY_o)
429                         {
430                                 gtk_widget_grab_focus(c->location);
431                                 return TRUE;
432                         }
433                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_h)
434                         {
435                                 zea_scroll(gtk_scrolled_window_get_hadjustment(
436                                            GTK_SCROLLED_WINDOW(c->scroll)), 0, -1);
437                                 return TRUE;
438                         }
439                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_j)
440                         {
441                                 zea_scroll(gtk_scrolled_window_get_vadjustment(
442                                            GTK_SCROLLED_WINDOW(c->scroll)), 0, 1);
443                                 return TRUE;
444                         }
445                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_k)
446                         {
447                                 zea_scroll(gtk_scrolled_window_get_vadjustment(
448                                            GTK_SCROLLED_WINDOW(c->scroll)), 0, -1);
449                                 return TRUE;
450                         }
451                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_l)
452                         {
453                                 zea_scroll(gtk_scrolled_window_get_hadjustment(
454                                            GTK_SCROLLED_WINDOW(c->scroll)), 0, 1);
455                                 return TRUE;
456                         }
457                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_f)
458                         {
459                                 zea_scroll(gtk_scrolled_window_get_vadjustment(
460                                            GTK_SCROLLED_WINDOW(c->scroll)), 1, 0.5);
461                                 return TRUE;
462                         }
463                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_b)
464                         {
465                                 zea_scroll(gtk_scrolled_window_get_vadjustment(
466                                            GTK_SCROLLED_WINDOW(c->scroll)), 1, -0.5);
467                                 return TRUE;
468                         }
469                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_n)
470                         {
471                                 zea_search(c, 1);
472                                 return TRUE;
473                         }
474                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_p)
475                         {
476                                 zea_search(c, -1);
477                                 return TRUE;
478                         }
479                         else if (((GdkEventKey *)event)->keyval == GDK_KEY_g)
480                         {
481                                 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(c->web_view),
482                                                          first_uri);
483                                 return TRUE;
484                         }
485                 }
486                 else if (((GdkEventKey *)event)->keyval == GDK_KEY_Escape)
487                 {
488                         webkit_web_view_stop_loading(WEBKIT_WEB_VIEW(c->web_view));
489                         gtk_statusbar_pop(GTK_STATUSBAR(c->status), 1);
490                         gtk_statusbar_push(GTK_STATUSBAR(c->status), 1, "Aborted.");
491                 }
492         }
493
494         return FALSE;
495 }
496
497 int
498 main(int argc, char **argv)
499 {
500         int opt, i;
501
502         gtk_init(&argc, &argv);
503
504         while ((opt = getopt(argc, argv, "z:e:R")) != -1)
505         {
506                 switch (opt)
507                 {
508                         case 'z':
509                                 global_zoom = atof(optarg);
510                                 break;
511                         case 'e':
512                                 embed = atol(optarg);
513                                 break;
514                         case 'R':
515                                 show_all_requests = TRUE;
516                                 break;
517                 }
518         }
519
520         if (optind >= argc)
521         {
522                 fprintf(stderr, "Usage: zea [OPTIONS] <URI>\n");
523                 exit(EXIT_FAILURE);
524         }
525
526         zea_load_adblock();
527
528         first_uri = g_strdup(argv[optind]);
529         for (i = optind; i < argc; i++)
530                 zea_new_client(argv[i]);
531         gtk_main();
532         exit(EXIT_SUCCESS);
533 }