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