Cleanup of policy decision code
[xxxterm.git] / xxxterm.c
blobc0ed7924fdb90979e84f3a79fbb7e44f479f4031
1 /* $xxxterm$ */
2 /*
3 * Copyright (c) 2010 Marco Peereboom <marco@peereboom.us>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 * TODO:
20 * config file
21 * inverse color browsing
22 * favs
23 * download files status
24 * multi letter commands
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <err.h>
30 #include <pwd.h>
31 #include <string.h>
32 #include <unistd.h>
34 #include <sys/queue.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
38 #include <gtk/gtk.h>
39 #include <gdk/gdkkeysyms.h>
40 #include <webkit/webkit.h>
41 #include <libsoup/soup.h>
43 static char *version = "$xxxterm$";
45 #define XT_DEBUG
46 /* #define XT_DEBUG */
47 #ifdef XT_DEBUG
48 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
49 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
50 #define XT_D_MOVE 0x0001
51 #define XT_D_KEY 0x0002
52 #define XT_D_TAB 0x0004
53 #define XT_D_URL 0x0008
54 #define XT_D_CMD 0x0010
55 #define XT_D_NAV 0x0020
56 #define XT_D_DOWNLOAD 0x0040
57 u_int32_t swm_debug = 0
58 | XT_D_MOVE
59 | XT_D_KEY
60 | XT_D_TAB
61 | XT_D_URL
62 | XT_D_CMD
63 | XT_D_NAV
64 | XT_D_DOWNLOAD
66 #else
67 #define DPRINTF(x...)
68 #define DNPRINTF(n,x...)
69 #endif
71 #define LENGTH(x) (sizeof x / sizeof x[0])
72 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
73 ~(GDK_BUTTON1_MASK) & \
74 ~(GDK_BUTTON2_MASK) & \
75 ~(GDK_BUTTON3_MASK) & \
76 ~(GDK_BUTTON4_MASK) & \
77 ~(GDK_BUTTON5_MASK))
79 struct tab {
80 TAILQ_ENTRY(tab) entry;
81 GtkWidget *vbox;
82 GtkWidget *label;
83 GtkWidget *uri_entry;
84 GtkWidget *toolbar;
85 GtkWidget *browser_win;
86 GtkWidget *cmd;
87 guint tab_id;
89 /* adjustments for browser */
90 GtkScrollbar *sb_h;
91 GtkScrollbar *sb_v;
92 GtkAdjustment *adjust_h;
93 GtkAdjustment *adjust_v;
95 /* flags */
96 int focus_wv;
97 int ctrl_click;
98 gchar *hover;
100 WebKitWebView *wv;
102 TAILQ_HEAD(tab_list, tab);
104 struct karg {
105 int i;
106 char *s;
109 /* defines */
110 #define XT_CB_HANDLED (TRUE)
111 #define XT_CB_PASSTHROUGH (FALSE)
113 /* actions */
114 #define XT_MOVE_INVALID (0)
115 #define XT_MOVE_DOWN (1)
116 #define XT_MOVE_UP (2)
117 #define XT_MOVE_BOTTOM (3)
118 #define XT_MOVE_TOP (4)
119 #define XT_MOVE_PAGEDOWN (5)
120 #define XT_MOVE_PAGEUP (6)
121 #define XT_MOVE_LEFT (7)
122 #define XT_MOVE_FARLEFT (8)
123 #define XT_MOVE_RIGHT (9)
124 #define XT_MOVE_FARRIGHT (10)
126 #define XT_TAB_PREV (-2)
127 #define XT_TAB_NEXT (-1)
128 #define XT_TAB_INVALID (0)
129 #define XT_TAB_NEW (1)
130 #define XT_TAB_DELETE (2)
131 #define XT_TAB_DELQUIT (3)
132 #define XT_TAB_OPEN (4)
134 #define XT_NAV_INVALID (0)
135 #define XT_NAV_BACK (1)
136 #define XT_NAV_FORWARD (2)
138 /* globals */
139 extern char *__progname;
140 GtkWidget *main_window;
141 GtkNotebook *notebook;
142 struct tab_list tabs;
144 /* settings */
145 int showtabs = 1; /* show tabs on notebook */
146 int showurl = 1; /* show url toolbar on notebook */
147 int tabless = 0; /* allow only 1 tab */
149 /* protos */
150 void create_new_tab(char *, int);
151 void delete_tab(struct tab *);
153 struct valid_url_types {
154 char *type;
155 } vut[] = {
156 { "http://" },
157 { "https://" },
158 { "ftp://" },
159 { "file://" },
163 valid_url_type(char *url)
165 int i;
167 for (i = 0; i < LENGTH(vut); i++)
168 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
169 return (0);
171 return (1);
174 char *
175 guess_url_type(char *url_in)
177 struct stat sb;
178 char *url_out = NULL;
180 /* XXX not sure about this heuristic */
181 if (stat(url_in, &sb) == 0) {
182 if (asprintf(&url_out, "file://%s", url_in) == -1)
183 err(1, "aprintf file");
184 } else {
185 /* guess http */
186 if (asprintf(&url_out, "http://%s", url_in) == -1)
187 err(1, "aprintf http");
190 if (url_out == NULL)
191 err(1, "asprintf pointer");
193 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
195 return (url_out);
199 quit(struct tab *t, struct karg *args)
201 gtk_main_quit();
203 return (1);
207 navaction(struct tab *t, struct karg *args)
209 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
210 t->tab_id, args->i);
212 switch (args->i) {
213 case XT_NAV_BACK:
214 webkit_web_view_go_back(t->wv);
215 break;
216 case XT_NAV_FORWARD:
217 webkit_web_view_go_forward(t->wv);
218 break;
220 return (XT_CB_PASSTHROUGH);
224 move(struct tab *t, struct karg *args)
226 GtkAdjustment *adjust;
227 double pi, si, pos, ps, upper, lower, max;
229 switch (args->i) {
230 case XT_MOVE_DOWN:
231 case XT_MOVE_UP:
232 case XT_MOVE_BOTTOM:
233 case XT_MOVE_TOP:
234 case XT_MOVE_PAGEDOWN:
235 case XT_MOVE_PAGEUP:
236 adjust = t->adjust_v;
237 break;
238 default:
239 adjust = t->adjust_h;
240 break;
243 pos = gtk_adjustment_get_value(adjust);
244 ps = gtk_adjustment_get_page_size(adjust);
245 upper = gtk_adjustment_get_upper(adjust);
246 lower = gtk_adjustment_get_lower(adjust);
247 si = gtk_adjustment_get_step_increment(adjust);
248 pi = gtk_adjustment_get_page_increment(adjust);
249 max = upper - ps;
251 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
252 "max %f si %f pi %f\n",
253 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
254 pos, ps, upper, lower, max, si, pi);
256 switch (args->i) {
257 case XT_MOVE_DOWN:
258 case XT_MOVE_RIGHT:
259 pos += si;
260 gtk_adjustment_set_value(adjust, MIN(pos, max));
261 break;
262 case XT_MOVE_UP:
263 case XT_MOVE_LEFT:
264 pos -= si;
265 gtk_adjustment_set_value(adjust, MAX(pos, lower));
266 break;
267 case XT_MOVE_BOTTOM:
268 case XT_MOVE_FARRIGHT:
269 gtk_adjustment_set_value(adjust, max);
270 break;
271 case XT_MOVE_TOP:
272 case XT_MOVE_FARLEFT:
273 gtk_adjustment_set_value(adjust, lower);
274 break;
275 case XT_MOVE_PAGEDOWN:
276 pos += pi;
277 gtk_adjustment_set_value(adjust, MIN(pos, max));
278 break;
279 case XT_MOVE_PAGEUP:
280 pos -= pi;
281 gtk_adjustment_set_value(adjust, MAX(pos, lower));
282 break;
283 default:
284 return (XT_CB_PASSTHROUGH);
287 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
289 return (XT_CB_HANDLED);
292 char *
293 getparams(char *cmd, char *cmp)
295 char *rv = NULL;
297 if (cmd && cmp) {
298 if (!strncmp(cmd, cmp, strlen(cmp))) {
299 rv = cmd + strlen(cmp);
300 while (*rv == ' ')
301 rv++;
302 if (strlen(rv) == 0)
303 rv = NULL;
307 return (rv);
311 tabaction(struct tab *t, struct karg *args)
313 int rv = XT_CB_HANDLED;
314 char *url = NULL, *newuri = NULL;
316 DNPRINTF(XT_D_TAB, "tabaction: %p %d %d\n", t, args->i, t->focus_wv);
318 if (t == NULL)
319 return (XT_CB_PASSTHROUGH);
321 switch (args->i) {
322 case XT_TAB_NEW:
323 if ((url = getparams(args->s, "tabnew")))
324 create_new_tab(url, 1);
325 else
326 create_new_tab(NULL, 1);
327 break;
328 case XT_TAB_DELETE:
329 delete_tab(t);
330 break;
331 case XT_TAB_DELQUIT:
332 if (gtk_notebook_get_n_pages(notebook) > 1)
333 delete_tab(t);
334 else
335 quit(t, args);
336 break;
337 case XT_TAB_OPEN:
338 if ((url = getparams(args->s, "open")) ||
339 ((url = getparams(args->s, "op"))) ||
340 ((url = getparams(args->s, "o"))))
342 else {
343 rv = XT_CB_PASSTHROUGH;
344 goto done;
347 if (valid_url_type(url)) {
348 newuri = guess_url_type(url);
349 url = newuri;
351 webkit_web_view_load_uri(t->wv, url);
352 if (newuri)
353 free(newuri);
354 break;
355 default:
356 rv = XT_CB_PASSTHROUGH;
357 goto done;
360 done:
361 if (args->s) {
362 free(args->s);
363 args->s = NULL;
366 return (rv);
370 movetab(struct tab *t, struct karg *args)
372 struct tab *tt;
373 int x;
375 DNPRINTF(XT_D_TAB, "movetab: %p %d\n", t, args->i);
377 if (t == NULL)
378 return (XT_CB_PASSTHROUGH);
380 if (args->i == XT_TAB_INVALID)
381 return (XT_CB_PASSTHROUGH);
383 if (args->i < XT_TAB_INVALID) {
384 /* next or previous tab */
385 if (TAILQ_EMPTY(&tabs))
386 return (XT_CB_PASSTHROUGH);
388 if (args->i == XT_TAB_NEXT)
389 gtk_notebook_next_page(notebook);
390 else
391 gtk_notebook_prev_page(notebook);
393 return (XT_CB_HANDLED);
396 /* jump to tab */
397 x = args->i - 1;
398 if (t->tab_id == x) {
399 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
400 return (XT_CB_HANDLED);
403 TAILQ_FOREACH(tt, &tabs, entry) {
404 if (tt->tab_id == x) {
405 gtk_notebook_set_current_page(notebook, x);
406 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
407 if (tt->focus_wv)
408 gtk_widget_grab_focus(GTK_WIDGET(tt->wv));
412 return (XT_CB_HANDLED);
416 command(struct tab *t, struct karg *args)
418 DNPRINTF(XT_D_CMD, "command:\n");
420 gtk_entry_set_text(GTK_ENTRY(t->cmd), ":");
421 gtk_widget_show(t->cmd);
422 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
423 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
425 return (XT_CB_HANDLED);
428 /* inherent to GTK not all keys will be caught at all times */
429 struct key {
430 guint mask;
431 guint modkey;
432 guint key;
433 int (*func)(struct tab *, struct karg *);
434 struct karg arg;
435 } keys[] = {
436 { GDK_SHIFT_MASK, 0, GDK_colon, command, {0} },
437 { GDK_CONTROL_MASK, 0, GDK_q, quit, {0} },
439 /* navigation */
440 { 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
441 { GDK_MOD1_MASK, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
442 { GDK_SHIFT_MASK, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
443 { GDK_MOD1_MASK, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
445 /* vertical movement */
446 { 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
447 { 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
448 { 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
449 { 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
450 { GDK_SHIFT_MASK, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
451 { 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
452 { 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
453 { 0, GDK_g, GDK_g, move, {.i = XT_MOVE_TOP} }, /* XXX make this work */
454 { 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
455 { 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
456 { 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
457 /* horizontal movement */
458 { 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
459 { 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
460 { 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
461 { 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
462 { GDK_SHIFT_MASK, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
463 { 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
465 /* tabs */
466 { GDK_CONTROL_MASK, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
467 { GDK_CONTROL_MASK, 0, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
468 { GDK_CONTROL_MASK, 0, GDK_1, movetab, {.i = 1} },
469 { GDK_CONTROL_MASK, 0, GDK_2, movetab, {.i = 2} },
470 { GDK_CONTROL_MASK, 0, GDK_3, movetab, {.i = 3} },
471 { GDK_CONTROL_MASK, 0, GDK_4, movetab, {.i = 4} },
472 { GDK_CONTROL_MASK, 0, GDK_5, movetab, {.i = 5} },
473 { GDK_CONTROL_MASK, 0, GDK_6, movetab, {.i = 6} },
474 { GDK_CONTROL_MASK, 0, GDK_7, movetab, {.i = 7} },
475 { GDK_CONTROL_MASK, 0, GDK_8, movetab, {.i = 8} },
476 { GDK_CONTROL_MASK, 0, GDK_9, movetab, {.i = 9} },
477 { GDK_CONTROL_MASK, 0, GDK_0, movetab, {.i = 10} },
480 struct cmd {
481 char *cmd;
482 int params;
483 int (*func)(struct tab *, struct karg *);
484 struct karg arg;
485 } cmds[] = {
486 { "q!", 0, quit, {0} },
487 { "qa", 0, quit, {0} },
488 { "qa!", 0, quit, {0} },
490 /* tabs */
491 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
492 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
493 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
494 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
495 { "tabedit", 0, tabaction, {.i = XT_TAB_NEW} },
496 { "tabe", 0, tabaction, {.i = XT_TAB_NEW} },
497 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
498 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
499 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
500 /* XXX add count to these commands and add tabl and friends */
501 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
502 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
503 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
504 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
507 void
508 focus_uri_entry_cb(GtkWidget* w, GtkDirectionType direction, struct tab *t)
510 DNPRINTF(XT_D_URL, "focus_uri_entry_cb: tab %d focus_wv %d\n",
511 t->tab_id, t->focus_wv);
513 if (t == NULL)
514 errx(1, "focus_uri_entry_cb");
516 /* focus on wv instead */
517 if (t->focus_wv)
518 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
521 void
522 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
524 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
525 char *newuri = NULL;
527 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
529 if (t == NULL)
530 errx(1, "activate_uri_entry_cb");
532 if (uri == NULL)
533 errx(1, "uri");
535 if (valid_url_type((char *)uri)) {
536 newuri = guess_url_type((char *)uri);
537 uri = newuri;
540 webkit_web_view_load_uri(t->wv, uri);
541 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
543 if (newuri)
544 free(newuri);
547 void
548 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
550 WebKitWebFrame *frame;
551 const gchar *uri;
553 if (t == NULL)
554 errx(1, "notify_load_status_cb");
556 switch (webkit_web_view_get_load_status(wview)) {
557 case WEBKIT_LOAD_COMMITTED:
558 frame = webkit_web_view_get_main_frame(wview);
559 uri = webkit_web_frame_get_uri(frame);
560 if (uri)
561 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
562 t->focus_wv = 1;
564 /* take focus if we are visible */
565 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
566 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
567 break;
568 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
569 uri = webkit_web_view_get_title(wview);
570 if (uri == NULL) {
571 frame = webkit_web_view_get_main_frame(wview);
572 uri = webkit_web_frame_get_uri(frame);
574 gtk_label_set_text(GTK_LABEL(t->label), uri);
575 break;
576 case WEBKIT_LOAD_PROVISIONAL:
577 case WEBKIT_LOAD_FINISHED:
578 case WEBKIT_LOAD_FAILED:
579 default:
580 break;
585 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
586 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
587 WebKitWebPolicyDecision *pd, struct tab *t)
589 char *uri;
591 if (t == NULL)
592 errx(1, "webview_npd_cb");
594 DNPRINTF(XT_D_NAV, "webview_npd_cb: %s\n",
595 webkit_network_request_get_uri(request));
597 if (t->ctrl_click) {
598 uri = (char *)webkit_network_request_get_uri(request);
599 create_new_tab(uri, 0);
600 t->ctrl_click = 0;
601 webkit_web_policy_decision_ignore(pd);
603 return (TRUE); /* we made the decission */
606 return (FALSE);
610 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
612 /* we can not eat the event without throwing gtk off so defer it */
614 /* catch ctrl click */
615 if (e->type == GDK_BUTTON_RELEASE &&
616 CLEAN(e->state) == GDK_CONTROL_MASK)
617 t->ctrl_click = 1;
618 else
619 t->ctrl_click = 0;
621 return (XT_CB_PASSTHROUGH);
625 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
626 WebKitNetworkRequest *request, char *mime_type,
627 WebKitWebPolicyDecision *decision, struct tab *t)
629 if (t == NULL)
630 errx(1, "webview_mimetype_cb");
632 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
633 t->tab_id, mime_type);
635 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
636 webkit_web_policy_decision_download(decision);
637 return (TRUE);
640 return (FALSE);
644 webview_download_cb(WebKitWebView *wv, WebKitDownload *download, struct tab *t)
646 const gchar *filename;
647 char *uri = NULL;
648 struct passwd *pwd;
650 if (download == NULL || t == NULL)
651 errx(1, "webview_download_cb: invalid pointers");
653 filename = webkit_download_get_suggested_filename(download);
654 if (filename == NULL)
655 return (FALSE); /* abort download */
657 pwd = getpwuid(getuid());
658 if (pwd == NULL)
659 errx(1, "webview_download_cb: invalid user %d", getuid());
661 if (asprintf(&uri, "file://%s/%s", pwd->pw_dir, filename) == -1)
662 err(1, "aprintf uri");
664 DNPRINTF(XT_D_DOWNLOAD, "webview_download_cb: tab %d filename %s "
665 "local %s\n",
666 t->tab_id, filename, uri);
668 webkit_download_set_destination_uri(download, uri);
670 if (uri)
671 free(uri);
673 webkit_download_start(download);
675 return (TRUE); /* start download */
678 void
679 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
681 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
683 if (t == NULL)
684 errx(1, "webview_hover_cb");
686 if (uri) {
687 if (t->hover) {
688 free(t->hover);
689 t->hover = NULL;
691 t->hover = strdup(uri);
692 } else if (t->hover) {
693 free(t->hover);
694 t->hover = NULL;
699 webview_keypress_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
701 int i;
703 /* don't use w directly; use t->whatever instead */
705 if (t == NULL)
706 errx(1, "webview_keypress_cb");
708 DNPRINTF(XT_D_KEY, "webview_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
709 e->keyval, e->state, t);
711 for (i = 0; i < LENGTH(keys); i++)
712 if (e->keyval == keys[i].key && CLEAN(e->state) ==
713 keys[i].mask) {
714 keys[i].func(t, &keys[i].arg);
715 return (XT_CB_HANDLED);
718 return (XT_CB_PASSTHROUGH);
722 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
724 int rv = XT_CB_HANDLED;
725 const gchar *c = gtk_entry_get_text(w);
727 if (t == NULL)
728 errx(1, "cmd_keypress_cb");
730 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
731 e->keyval, e->state, t);
733 /* sanity */
734 if (c == NULL)
735 e->keyval = GDK_Escape;
736 else if (c[0] != ':')
737 e->keyval = GDK_Escape;
739 switch (e->keyval) {
740 case GDK_BackSpace:
741 if (strcmp(c, ":"))
742 break;
743 /* FALLTHROUGH */
744 case GDK_Escape:
745 gtk_widget_hide(t->cmd);
746 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
747 goto done;
750 rv = XT_CB_PASSTHROUGH;
751 done:
752 return (rv);
756 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
758 if (t == NULL)
759 errx(1, "cmd_focusout_cb");
761 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d focus_wv %d\n",
762 t->tab_id, t->focus_wv);
764 /* abort command when losing focus */
765 gtk_widget_hide(t->cmd);
766 if (t->focus_wv)
767 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
768 else
769 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
771 return (XT_CB_PASSTHROUGH);
774 void
775 cmd_activate_cb(GtkEntry *entry, struct tab *t)
777 int i;
778 char *s;
779 const gchar *c = gtk_entry_get_text(entry);
781 if (t == NULL)
782 errx(1, "cmd_activate_cb");
784 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
786 /* sanity */
787 if (c == NULL)
788 goto done;
789 else if (c[0] != ':')
790 goto done;
791 if (strlen(c) < 2)
792 goto done;
793 s = (char *)&c[1];
795 for (i = 0; i < LENGTH(cmds); i++)
796 if (cmds[i].params) {
797 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
798 cmds[i].arg.s = strdup(s);
799 cmds[i].func(t, &cmds[i].arg);
801 } else {
802 if (!strcmp(s, cmds[i].cmd))
803 cmds[i].func(t, &cmds[i].arg);
806 done:
807 gtk_widget_hide(t->cmd);
810 GtkWidget *
811 create_browser(struct tab *t)
813 GtkWidget *w;
815 if (t == NULL)
816 errx(1, "create_browser");
818 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
819 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
820 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
821 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
823 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
824 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
825 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
827 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
828 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
830 g_signal_connect(t->wv, "notify::load-status",
831 G_CALLBACK(notify_load_status_cb), t);
833 return (w);
836 GtkWidget *
837 create_window(void)
839 GtkWidget *w;
841 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
842 gtk_window_set_default_size(GTK_WINDOW(w), 800, 600);
843 gtk_widget_set_name(w, "xxxterm");
844 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
846 return (w);
849 GtkWidget *
850 create_toolbar(struct tab *t)
852 GtkWidget *toolbar = gtk_toolbar_new();
853 GtkToolItem *i;
855 #if GTK_CHECK_VERSION(2,15,0)
856 gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar),
857 GTK_ORIENTATION_HORIZONTAL);
858 #else
859 gtk_toolbar_set_orientation(GTK_TOOLBAR(toolbar),
860 GTK_ORIENTATION_HORIZONTAL);
861 #endif
862 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH_HORIZ);
864 i = gtk_tool_item_new();
865 gtk_tool_item_set_expand(i, TRUE);
866 t->uri_entry = gtk_entry_new();
867 gtk_container_add(GTK_CONTAINER(i), t->uri_entry);
868 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
869 G_CALLBACK(activate_uri_entry_cb), t);
870 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), i, -1);
872 return (toolbar);
875 void
876 delete_tab(struct tab *t)
878 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
880 if (t == NULL)
881 return;
883 TAILQ_REMOVE(&tabs, t, entry);
884 if (TAILQ_EMPTY(&tabs))
885 create_new_tab(NULL, 1);
887 gtk_widget_destroy(t->vbox);
888 g_free(t);
891 void
892 create_new_tab(char *title, int focus)
894 struct tab *t;
895 int load = 1;
896 char *newuri = NULL;
898 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
900 if (tabless && !TAILQ_EMPTY(&tabs)) {
901 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
902 return;
905 t = g_malloc0(sizeof *t);
906 TAILQ_INSERT_TAIL(&tabs, t, entry);
908 if (title == NULL) {
909 title = "(untitled)";
910 load = 0;
911 } else {
912 if (valid_url_type(title)) {
913 newuri = guess_url_type(title);
914 title = newuri;
918 t->vbox = gtk_vbox_new(FALSE, 0);
920 /* label for tab */
921 t->label = gtk_label_new(title);
922 gtk_widget_set_size_request(t->label, 100, -1);
924 /* toolbar */
925 t->toolbar = create_toolbar(t);
926 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
928 /* browser */
929 t->browser_win = create_browser(t);
930 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
932 /* command entry */
933 t->cmd = gtk_entry_new();
934 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
935 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
936 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
938 /* and show it all */
939 gtk_widget_show_all(t->vbox);
940 t->tab_id = gtk_notebook_append_page(notebook, t->vbox,
941 t->label);
943 g_object_connect((GObject*)t->cmd,
944 "signal::key-press-event", (GCallback)cmd_keypress_cb, t,
945 "signal::focus-out-event", (GCallback)cmd_focusout_cb, t,
946 "signal::activate", (GCallback)cmd_activate_cb, t,
947 NULL);
949 g_object_connect((GObject*)t->wv,
950 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
951 /* "signal::hovering-over-link", (GCallback)webview_hover_cb, t, */
952 "signal::download-requested", (GCallback)webview_download_cb, t,
953 "signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, t,
954 "signal::navigation-policy-decision-requested", (GCallback)webview_npd_cb, t,
955 "signal::event", (GCallback)webview_event_cb, t,
956 NULL);
958 /* hijack the unused keys as if we were the browser */
959 g_object_connect((GObject*)t->toolbar,
960 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
961 NULL);
963 g_signal_connect(G_OBJECT(t->uri_entry), "focus",
964 G_CALLBACK(focus_uri_entry_cb), t);
966 /* hide stuff */
967 gtk_widget_hide(t->cmd);
968 if (showurl == 0)
969 gtk_widget_hide(t->toolbar);
971 if (focus) {
972 gtk_notebook_set_current_page(notebook, t->tab_id);
973 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
974 t->tab_id);
977 if (load)
978 webkit_web_view_load_uri(t->wv, title);
979 else
980 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
982 if (newuri)
983 free(newuri);
986 void
987 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
988 gpointer *udata)
990 struct tab *t;
992 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
994 TAILQ_FOREACH(t, &tabs, entry) {
995 if (t->tab_id == pn) {
996 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
997 "%d\n", pn);
998 gtk_widget_hide(t->cmd);
1003 void
1004 create_canvas(void)
1006 GtkWidget *vbox;
1008 vbox = gtk_vbox_new(FALSE, 0);
1009 notebook = GTK_NOTEBOOK(gtk_notebook_new());
1010 if (showtabs == 0)
1011 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
1013 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
1015 g_object_connect((GObject*)notebook,
1016 "signal::switch-page", (GCallback)notebook_switchpage_cb, NULL,
1017 NULL);
1019 main_window = create_window();
1020 gtk_container_add(GTK_CONTAINER(main_window), vbox);
1021 gtk_widget_show_all(main_window);
1024 void
1025 usage(void)
1027 fprintf(stderr,
1028 "%s [-STVt] url ...\n", __progname);
1029 exit(0);
1033 main(int argc, char *argv[])
1035 int c, focus = 1;
1037 while ((c = getopt(argc, argv, "STVt")) != -1) {
1038 switch (c) {
1039 case 'S':
1040 showurl = 0;
1041 break;
1042 case 'T':
1043 showtabs = 0;
1044 break;
1045 case 'V':
1046 errx(0 , "Version: %s", version);
1047 break;
1048 case 't':
1049 tabless = 1;
1050 break;
1051 default:
1052 usage();
1053 /* NOTREACHED */
1056 argc -= optind;
1057 argv += optind;
1059 TAILQ_INIT(&tabs);
1061 /* prepare gtk */
1062 gtk_init(&argc, &argv);
1063 if (!g_thread_supported())
1064 g_thread_init(NULL);
1066 create_canvas();
1068 while (argc) {
1069 create_new_tab(argv[0], focus);
1070 focus = 0;
1072 argc--;
1073 argv++;
1075 if (focus == 1)
1076 create_new_tab("http://www.peereboom.us", 1);
1078 gtk_main();
1080 return (0);