Add arrows on tabs when overflowing
[xxxterm.git] / xxxterm.c
blob4647704bb8b6ebcdf68a88b56736c4a4e1f5cefd
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 * inverse color browsing
21 * favs
22 * download files status
23 * multi letter commands
24 * pre and post counts for commands
25 * search on page
26 * search on engines
27 * fav icon
28 * close tab X
29 * autocompletion on various inputs
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <err.h>
35 #include <pwd.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <util.h>
40 #include <sys/queue.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
44 #include <gtk/gtk.h>
45 #include <gdk/gdkkeysyms.h>
46 #include <webkit/webkit.h>
47 #include <libsoup/soup.h>
48 #include <JavaScriptCore/JavaScript.h>
50 static char *version = "$xxxterm$";
52 #define XT_DEBUG
53 /* #define XT_DEBUG */
54 #ifdef XT_DEBUG
55 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
56 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
57 #define XT_D_MOVE 0x0001
58 #define XT_D_KEY 0x0002
59 #define XT_D_TAB 0x0004
60 #define XT_D_URL 0x0008
61 #define XT_D_CMD 0x0010
62 #define XT_D_NAV 0x0020
63 #define XT_D_DOWNLOAD 0x0040
64 #define XT_D_CONFIG 0x0080
65 u_int32_t swm_debug = 0
66 | XT_D_MOVE
67 | XT_D_KEY
68 | XT_D_TAB
69 | XT_D_URL
70 | XT_D_CMD
71 | XT_D_NAV
72 | XT_D_DOWNLOAD
73 | XT_D_CONFIG
75 #else
76 #define DPRINTF(x...)
77 #define DNPRINTF(n,x...)
78 #endif
80 #define LENGTH(x) (sizeof x / sizeof x[0])
81 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
82 ~(GDK_BUTTON1_MASK) & \
83 ~(GDK_BUTTON2_MASK) & \
84 ~(GDK_BUTTON3_MASK) & \
85 ~(GDK_BUTTON4_MASK) & \
86 ~(GDK_BUTTON5_MASK))
88 struct tab {
89 TAILQ_ENTRY(tab) entry;
90 GtkWidget *vbox;
91 GtkWidget *label;
92 GtkWidget *uri_entry;
93 GtkWidget *search_entry;
94 GtkWidget *toolbar;
95 GtkWidget *browser_win;
96 GtkWidget *cmd;
97 guint tab_id;
99 /* adjustments for browser */
100 GtkScrollbar *sb_h;
101 GtkScrollbar *sb_v;
102 GtkAdjustment *adjust_h;
103 GtkAdjustment *adjust_v;
105 /* flags */
106 int focus_wv;
107 int ctrl_click;
108 gchar *hover;
110 WebKitWebView *wv;
111 WebKitWebSettings *settings;
113 TAILQ_HEAD(tab_list, tab);
115 struct karg {
116 int i;
117 char *s;
120 /* defines */
121 #define XT_DIR (".xxxterm")
122 #define XT_CONF_FILE ("xxxterm.conf")
123 #define XT_CB_HANDLED (TRUE)
124 #define XT_CB_PASSTHROUGH (FALSE)
126 /* actions */
127 #define XT_MOVE_INVALID (0)
128 #define XT_MOVE_DOWN (1)
129 #define XT_MOVE_UP (2)
130 #define XT_MOVE_BOTTOM (3)
131 #define XT_MOVE_TOP (4)
132 #define XT_MOVE_PAGEDOWN (5)
133 #define XT_MOVE_PAGEUP (6)
134 #define XT_MOVE_LEFT (7)
135 #define XT_MOVE_FARLEFT (8)
136 #define XT_MOVE_RIGHT (9)
137 #define XT_MOVE_FARRIGHT (10)
139 #define XT_TAB_PREV (-2)
140 #define XT_TAB_NEXT (-1)
141 #define XT_TAB_INVALID (0)
142 #define XT_TAB_NEW (1)
143 #define XT_TAB_DELETE (2)
144 #define XT_TAB_DELQUIT (3)
145 #define XT_TAB_OPEN (4)
147 #define XT_NAV_INVALID (0)
148 #define XT_NAV_BACK (1)
149 #define XT_NAV_FORWARD (2)
150 #define XT_NAV_RELOAD (3)
152 #define XT_FOCUS_INVALID (0)
153 #define XT_FOCUS_URI (1)
155 /* globals */
156 extern char *__progname;
157 struct passwd *pwd;
158 GtkWidget *main_window;
159 GtkNotebook *notebook;
160 struct tab_list tabs;
162 /* settings */
163 int showtabs = 1; /* show tabs on notebook */
164 int showurl = 1; /* show url toolbar on notebook */
165 int tabless = 0; /* allow only 1 tab */
166 int ctrl_click_focus = 0; /* ctrl click gets focus */
167 int cookies_enabled = 1; /* enable cookies */
168 int read_only_cookies = 0; /* enable to not write cookies */
169 int enable_scripts = 1;
170 int enable_plugins = 1;
171 int default_font_size = 12;
173 char *home = "http://www.peereboom.us";
174 char *http_proxy = NULL;
175 SoupURI *proxy_uri = NULL;
176 char work_dir[PATH_MAX];
177 char cookie_file[PATH_MAX];
178 char download_dir[PATH_MAX];
179 SoupSession *session;
180 SoupCookieJar *cookiejar;
182 /* protos */
183 void create_new_tab(char *, int);
184 void delete_tab(struct tab *);
186 struct valid_url_types {
187 char *type;
188 } vut[] = {
189 { "http://" },
190 { "https://" },
191 { "ftp://" },
192 { "file://" },
196 valid_url_type(char *url)
198 int i;
200 for (i = 0; i < LENGTH(vut); i++)
201 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
202 return (0);
204 return (1);
207 char *
208 guess_url_type(char *url_in)
210 struct stat sb;
211 char *url_out = NULL;
213 /* XXX not sure about this heuristic */
214 if (stat(url_in, &sb) == 0) {
215 if (asprintf(&url_out, "file://%s", url_in) == -1)
216 err(1, "aprintf file");
217 } else {
218 /* guess http */
219 if (asprintf(&url_out, "http://%s", url_in) == -1)
220 err(1, "aprintf http");
223 if (url_out == NULL)
224 err(1, "asprintf pointer");
226 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
228 return (url_out);
231 #define WS "\n= \t"
232 void
233 config_parse(char *filename)
235 FILE *config;
236 char *line, *cp, *var, *val;
237 size_t len, lineno = 0;
239 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
241 if (filename == NULL)
242 return;
244 if ((config = fopen(filename, "r")) == NULL) {
245 warn("config_parse: cannot open %s", filename);
246 return;
249 for (;;) {
250 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
251 if (feof(config))
252 break;
254 cp = line;
255 cp += (long)strspn(cp, WS);
256 if (cp[0] == '\0') {
257 /* empty line */
258 free(line);
259 continue;
262 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
263 break;
265 cp += (long)strspn(cp, WS);
266 if ((val = strsep(&cp, WS)) == NULL)
267 break;
269 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
271 /* get settings */
272 if (!strcmp(var, "home"))
273 home = strdup(val);
274 else if (!strcmp(var, "ctrl_click_focus"))
275 ctrl_click_focus = atoi(val);
276 else if (!strcmp(var, "read_only_cookies"))
277 read_only_cookies = atoi(val);
278 else if (!strcmp(var, "cookies_enabled"))
279 cookies_enabled = atoi(val);
280 else if (!strcmp(var, "enable_scripts"))
281 enable_scripts = atoi(val);
282 else if (!strcmp(var, "enable_plugins"))
283 enable_plugins = atoi(val);
284 else if (!strcmp(var, "default_font_size"))
285 default_font_size = atoi(val);
286 else if (!strcmp(var, "http_proxy")) {
287 http_proxy = strdup(val);
288 if (http_proxy == NULL)
289 err(1, "http_proxy");
290 } else if (!strcmp(var, "download_dir")) {
291 if (val[0] == '~')
292 snprintf(download_dir, sizeof download_dir,
293 "%s/%s", pwd->pw_dir, &val[1]);
294 else
295 strlcpy(download_dir, val, sizeof download_dir);
296 fprintf(stderr, "download dir: %s\n", download_dir);
297 } else
298 errx(1, "invalid conf file entry: %s=%s", var, val);
300 free(line);
303 fclose(config);
306 quit(struct tab *t, struct karg *args)
308 gtk_main_quit();
310 return (1);
314 focus(struct tab *t, struct karg *args)
316 if (t == NULL)
317 errx(1, "focus");
319 if (args->i == XT_FOCUS_URI)
320 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
322 return (0);
326 help(struct tab *t, struct karg *args)
328 if (t == NULL)
329 errx(1, "help");
331 webkit_web_view_load_string(t->wv,
332 "<html><body><h1>XXXTerm</h1></body></html>",
333 NULL,
334 NULL,
335 NULL);
337 return (0);
341 navaction(struct tab *t, struct karg *args)
343 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
344 t->tab_id, args->i);
346 switch (args->i) {
347 case XT_NAV_BACK:
348 webkit_web_view_go_back(t->wv);
349 break;
350 case XT_NAV_FORWARD:
351 webkit_web_view_go_forward(t->wv);
352 break;
353 case XT_NAV_RELOAD:
354 webkit_web_view_reload(t->wv);
355 break;
357 return (XT_CB_PASSTHROUGH);
361 move(struct tab *t, struct karg *args)
363 GtkAdjustment *adjust;
364 double pi, si, pos, ps, upper, lower, max;
366 switch (args->i) {
367 case XT_MOVE_DOWN:
368 case XT_MOVE_UP:
369 case XT_MOVE_BOTTOM:
370 case XT_MOVE_TOP:
371 case XT_MOVE_PAGEDOWN:
372 case XT_MOVE_PAGEUP:
373 adjust = t->adjust_v;
374 break;
375 default:
376 adjust = t->adjust_h;
377 break;
380 pos = gtk_adjustment_get_value(adjust);
381 ps = gtk_adjustment_get_page_size(adjust);
382 upper = gtk_adjustment_get_upper(adjust);
383 lower = gtk_adjustment_get_lower(adjust);
384 si = gtk_adjustment_get_step_increment(adjust);
385 pi = gtk_adjustment_get_page_increment(adjust);
386 max = upper - ps;
388 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
389 "max %f si %f pi %f\n",
390 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
391 pos, ps, upper, lower, max, si, pi);
393 switch (args->i) {
394 case XT_MOVE_DOWN:
395 case XT_MOVE_RIGHT:
396 pos += si;
397 gtk_adjustment_set_value(adjust, MIN(pos, max));
398 break;
399 case XT_MOVE_UP:
400 case XT_MOVE_LEFT:
401 pos -= si;
402 gtk_adjustment_set_value(adjust, MAX(pos, lower));
403 break;
404 case XT_MOVE_BOTTOM:
405 case XT_MOVE_FARRIGHT:
406 gtk_adjustment_set_value(adjust, max);
407 break;
408 case XT_MOVE_TOP:
409 case XT_MOVE_FARLEFT:
410 gtk_adjustment_set_value(adjust, lower);
411 break;
412 case XT_MOVE_PAGEDOWN:
413 pos += pi;
414 gtk_adjustment_set_value(adjust, MIN(pos, max));
415 break;
416 case XT_MOVE_PAGEUP:
417 pos -= pi;
418 gtk_adjustment_set_value(adjust, MAX(pos, lower));
419 break;
420 default:
421 return (XT_CB_PASSTHROUGH);
424 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
426 return (XT_CB_HANDLED);
429 char *
430 getparams(char *cmd, char *cmp)
432 char *rv = NULL;
434 if (cmd && cmp) {
435 if (!strncmp(cmd, cmp, strlen(cmp))) {
436 rv = cmd + strlen(cmp);
437 while (*rv == ' ')
438 rv++;
439 if (strlen(rv) == 0)
440 rv = NULL;
444 return (rv);
448 tabaction(struct tab *t, struct karg *args)
450 int rv = XT_CB_HANDLED;
451 char *url = NULL, *newuri = NULL;
453 DNPRINTF(XT_D_TAB, "tabaction: %p %d %d\n", t, args->i, t->focus_wv);
455 if (t == NULL)
456 return (XT_CB_PASSTHROUGH);
458 switch (args->i) {
459 case XT_TAB_NEW:
460 if ((url = getparams(args->s, "tabnew")))
461 create_new_tab(url, 1);
462 else
463 create_new_tab(NULL, 1);
464 break;
465 case XT_TAB_DELETE:
466 delete_tab(t);
467 break;
468 case XT_TAB_DELQUIT:
469 if (gtk_notebook_get_n_pages(notebook) > 1)
470 delete_tab(t);
471 else
472 quit(t, args);
473 break;
474 case XT_TAB_OPEN:
475 if ((url = getparams(args->s, "open")) ||
476 ((url = getparams(args->s, "op"))) ||
477 ((url = getparams(args->s, "o"))))
479 else {
480 rv = XT_CB_PASSTHROUGH;
481 goto done;
484 if (valid_url_type(url)) {
485 newuri = guess_url_type(url);
486 url = newuri;
488 webkit_web_view_load_uri(t->wv, url);
489 if (newuri)
490 free(newuri);
491 break;
492 default:
493 rv = XT_CB_PASSTHROUGH;
494 goto done;
497 done:
498 if (args->s) {
499 free(args->s);
500 args->s = NULL;
503 return (rv);
507 movetab(struct tab *t, struct karg *args)
509 struct tab *tt;
510 int x;
512 DNPRINTF(XT_D_TAB, "movetab: %p %d\n", t, args->i);
514 if (t == NULL)
515 return (XT_CB_PASSTHROUGH);
517 if (args->i == XT_TAB_INVALID)
518 return (XT_CB_PASSTHROUGH);
520 if (args->i < XT_TAB_INVALID) {
521 /* next or previous tab */
522 if (TAILQ_EMPTY(&tabs))
523 return (XT_CB_PASSTHROUGH);
525 if (args->i == XT_TAB_NEXT)
526 gtk_notebook_next_page(notebook);
527 else
528 gtk_notebook_prev_page(notebook);
530 return (XT_CB_HANDLED);
533 /* jump to tab */
534 x = args->i - 1;
535 if (t->tab_id == x) {
536 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
537 return (XT_CB_HANDLED);
540 TAILQ_FOREACH(tt, &tabs, entry) {
541 if (tt->tab_id == x) {
542 gtk_notebook_set_current_page(notebook, x);
543 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
544 if (tt->focus_wv)
545 gtk_widget_grab_focus(GTK_WIDGET(tt->wv));
549 return (XT_CB_HANDLED);
553 command(struct tab *t, struct karg *args)
555 DNPRINTF(XT_D_CMD, "command:\n");
557 gtk_entry_set_text(GTK_ENTRY(t->cmd), ":");
558 gtk_widget_show(t->cmd);
559 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
560 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
562 return (XT_CB_HANDLED);
565 /* inherent to GTK not all keys will be caught at all times */
566 struct key {
567 guint mask;
568 guint modkey;
569 guint key;
570 int (*func)(struct tab *, struct karg *);
571 struct karg arg;
572 } keys[] = {
573 { GDK_SHIFT_MASK, 0, GDK_colon, command, {0} },
574 { GDK_CONTROL_MASK, 0, GDK_q, quit, {0} },
576 /* focus */
577 { 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} },
579 /* navigation */
580 { 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
581 { GDK_MOD1_MASK, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
582 { GDK_SHIFT_MASK, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
583 { GDK_MOD1_MASK, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
584 { 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} },
586 /* vertical movement */
587 { 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
588 { 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
589 { 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
590 { 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
591 { GDK_SHIFT_MASK, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
592 { 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
593 { 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
594 { 0, GDK_g, GDK_g, move, {.i = XT_MOVE_TOP} }, /* XXX make this work */
595 { 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
596 { 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
597 { 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
598 /* horizontal movement */
599 { 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
600 { 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
601 { 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
602 { 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
603 { GDK_SHIFT_MASK, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
604 { 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
606 /* tabs */
607 { GDK_CONTROL_MASK, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
608 { GDK_CONTROL_MASK, 0, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
609 { GDK_CONTROL_MASK, 0, GDK_1, movetab, {.i = 1} },
610 { GDK_CONTROL_MASK, 0, GDK_2, movetab, {.i = 2} },
611 { GDK_CONTROL_MASK, 0, GDK_3, movetab, {.i = 3} },
612 { GDK_CONTROL_MASK, 0, GDK_4, movetab, {.i = 4} },
613 { GDK_CONTROL_MASK, 0, GDK_5, movetab, {.i = 5} },
614 { GDK_CONTROL_MASK, 0, GDK_6, movetab, {.i = 6} },
615 { GDK_CONTROL_MASK, 0, GDK_7, movetab, {.i = 7} },
616 { GDK_CONTROL_MASK, 0, GDK_8, movetab, {.i = 8} },
617 { GDK_CONTROL_MASK, 0, GDK_9, movetab, {.i = 9} },
618 { GDK_CONTROL_MASK, 0, GDK_0, movetab, {.i = 10} },
621 struct cmd {
622 char *cmd;
623 int params;
624 int (*func)(struct tab *, struct karg *);
625 struct karg arg;
626 } cmds[] = {
627 { "q!", 0, quit, {0} },
628 { "qa", 0, quit, {0} },
629 { "qa!", 0, quit, {0} },
630 { "help", 0, help, {0} },
632 /* tabs */
633 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
634 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
635 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
636 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
637 { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} },
638 { "tabe", 1, tabaction, {.i = XT_TAB_NEW} },
639 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
640 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} },
641 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
642 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
643 /* XXX add count to these commands and add tabl and friends */
644 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
645 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
646 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
647 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
650 void
651 focus_uri_entry_cb(GtkWidget* w, GtkDirectionType direction, struct tab *t)
653 DNPRINTF(XT_D_URL, "focus_uri_entry_cb: tab %d focus_wv %d\n",
654 t->tab_id, t->focus_wv);
656 if (t == NULL)
657 errx(1, "focus_uri_entry_cb");
659 /* focus on wv instead */
660 if (t->focus_wv)
661 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
664 void
665 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
667 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
668 char *newuri = NULL;
670 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
672 if (t == NULL)
673 errx(1, "activate_uri_entry_cb");
675 if (uri == NULL)
676 errx(1, "uri");
678 if (valid_url_type((char *)uri)) {
679 newuri = guess_url_type((char *)uri);
680 uri = newuri;
683 webkit_web_view_load_uri(t->wv, uri);
684 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
686 if (newuri)
687 free(newuri);
690 void
691 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
693 WebKitWebFrame *frame;
694 const gchar *uri;
696 if (t == NULL)
697 errx(1, "notify_load_status_cb");
699 switch (webkit_web_view_get_load_status(wview)) {
700 case WEBKIT_LOAD_COMMITTED:
701 frame = webkit_web_view_get_main_frame(wview);
702 uri = webkit_web_frame_get_uri(frame);
703 if (uri)
704 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
705 t->focus_wv = 1;
707 /* take focus if we are visible */
708 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
709 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
710 break;
711 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
712 uri = webkit_web_view_get_title(wview);
713 if (uri == NULL) {
714 frame = webkit_web_view_get_main_frame(wview);
715 uri = webkit_web_frame_get_uri(frame);
717 gtk_label_set_text(GTK_LABEL(t->label), uri);
718 break;
719 case WEBKIT_LOAD_PROVISIONAL:
720 case WEBKIT_LOAD_FINISHED:
721 case WEBKIT_LOAD_FAILED:
722 default:
723 break;
728 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
729 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
730 WebKitWebPolicyDecision *pd, struct tab *t)
732 char *uri;
734 if (t == NULL)
735 errx(1, "webview_npd_cb");
737 DNPRINTF(XT_D_NAV, "webview_npd_cb: %s\n",
738 webkit_network_request_get_uri(request));
740 if (t->ctrl_click) {
741 uri = (char *)webkit_network_request_get_uri(request);
742 create_new_tab(uri, ctrl_click_focus);
743 t->ctrl_click = 0;
744 webkit_web_policy_decision_ignore(pd);
746 return (TRUE); /* we made the decission */
749 return (FALSE);
753 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
755 /* we can not eat the event without throwing gtk off so defer it */
757 /* catch ctrl click */
758 if (e->type == GDK_BUTTON_RELEASE &&
759 CLEAN(e->state) == GDK_CONTROL_MASK)
760 t->ctrl_click = 1;
761 else
762 t->ctrl_click = 0;
764 return (XT_CB_PASSTHROUGH);
768 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
769 WebKitNetworkRequest *request, char *mime_type,
770 WebKitWebPolicyDecision *decision, struct tab *t)
772 if (t == NULL)
773 errx(1, "webview_mimetype_cb");
775 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
776 t->tab_id, mime_type);
778 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
779 webkit_web_policy_decision_download(decision);
780 return (TRUE);
783 return (FALSE);
787 webview_download_cb(WebKitWebView *wv, WebKitDownload *download, struct tab *t)
789 const gchar *filename;
790 char *uri = NULL;
792 if (download == NULL || t == NULL)
793 errx(1, "webview_download_cb: invalid pointers");
795 filename = webkit_download_get_suggested_filename(download);
796 if (filename == NULL)
797 return (FALSE); /* abort download */
799 if (asprintf(&uri, "file://%s/%s", download_dir, filename) == -1)
800 err(1, "aprintf uri");
802 DNPRINTF(XT_D_DOWNLOAD, "webview_download_cb: tab %d filename %s "
803 "local %s\n",
804 t->tab_id, filename, uri);
806 webkit_download_set_destination_uri(download, uri);
808 if (uri)
809 free(uri);
811 webkit_download_start(download);
813 return (TRUE); /* start download */
816 void
817 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
819 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
821 if (t == NULL)
822 errx(1, "webview_hover_cb");
824 if (uri) {
825 if (t->hover) {
826 free(t->hover);
827 t->hover = NULL;
829 t->hover = strdup(uri);
830 } else if (t->hover) {
831 free(t->hover);
832 t->hover = NULL;
837 webview_keypress_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
839 int i;
841 /* don't use w directly; use t->whatever instead */
843 if (t == NULL)
844 errx(1, "webview_keypress_cb");
846 DNPRINTF(XT_D_KEY, "webview_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
847 e->keyval, e->state, t);
849 for (i = 0; i < LENGTH(keys); i++)
850 if (e->keyval == keys[i].key && CLEAN(e->state) ==
851 keys[i].mask) {
852 keys[i].func(t, &keys[i].arg);
853 return (XT_CB_HANDLED);
856 return (XT_CB_PASSTHROUGH);
860 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
862 int rv = XT_CB_HANDLED;
863 const gchar *c = gtk_entry_get_text(w);
865 if (t == NULL)
866 errx(1, "cmd_keypress_cb");
868 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
869 e->keyval, e->state, t);
871 /* sanity */
872 if (c == NULL)
873 e->keyval = GDK_Escape;
874 else if (c[0] != ':')
875 e->keyval = GDK_Escape;
877 switch (e->keyval) {
878 case GDK_BackSpace:
879 if (strcmp(c, ":"))
880 break;
881 /* FALLTHROUGH */
882 case GDK_Escape:
883 gtk_widget_hide(t->cmd);
884 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
885 goto done;
888 rv = XT_CB_PASSTHROUGH;
889 done:
890 return (rv);
894 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
896 if (t == NULL)
897 errx(1, "cmd_focusout_cb");
899 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d focus_wv %d\n",
900 t->tab_id, t->focus_wv);
902 /* abort command when losing focus */
903 gtk_widget_hide(t->cmd);
904 if (t->focus_wv)
905 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
906 else
907 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
909 return (XT_CB_PASSTHROUGH);
912 void
913 cmd_activate_cb(GtkEntry *entry, struct tab *t)
915 int i;
916 char *s;
917 const gchar *c = gtk_entry_get_text(entry);
919 if (t == NULL)
920 errx(1, "cmd_activate_cb");
922 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
924 /* sanity */
925 if (c == NULL)
926 goto done;
927 else if (c[0] != ':')
928 goto done;
929 if (strlen(c) < 2)
930 goto done;
931 s = (char *)&c[1];
933 for (i = 0; i < LENGTH(cmds); i++)
934 if (cmds[i].params) {
935 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
936 cmds[i].arg.s = strdup(s);
937 cmds[i].func(t, &cmds[i].arg);
939 } else {
940 if (!strcmp(s, cmds[i].cmd))
941 cmds[i].func(t, &cmds[i].arg);
944 done:
945 gtk_widget_hide(t->cmd);
948 GtkWidget *
949 create_browser(struct tab *t)
951 GtkWidget *w;
953 if (t == NULL)
954 errx(1, "create_browser");
956 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
957 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
958 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
959 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
961 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
962 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
963 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
965 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
966 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
968 g_signal_connect(t->wv, "notify::load-status",
969 G_CALLBACK(notify_load_status_cb), t);
971 return (w);
974 GtkWidget *
975 create_window(void)
977 GtkWidget *w;
979 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
980 gtk_window_set_default_size(GTK_WINDOW(w), 800, 600);
981 gtk_widget_set_name(w, "xxxterm");
982 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
984 return (w);
987 GtkWidget *
988 create_toolbar(struct tab *t)
990 GtkWidget *toolbar = gtk_toolbar_new();
991 GtkToolItem *i;
993 #if GTK_CHECK_VERSION(2,15,0)
994 gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar),
995 GTK_ORIENTATION_HORIZONTAL);
996 #else
997 gtk_toolbar_set_orientation(GTK_TOOLBAR(toolbar),
998 GTK_ORIENTATION_HORIZONTAL);
999 #endif
1000 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH_HORIZ);
1002 i = gtk_tool_item_new();
1003 gtk_tool_item_set_expand(i, TRUE);
1004 t->uri_entry = gtk_entry_new();
1005 gtk_container_add(GTK_CONTAINER(i), t->uri_entry);
1006 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
1007 G_CALLBACK(activate_uri_entry_cb), t);
1008 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), i, -1);
1010 return (toolbar);
1013 void
1014 delete_tab(struct tab *t)
1016 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
1018 if (t == NULL)
1019 return;
1021 TAILQ_REMOVE(&tabs, t, entry);
1022 if (TAILQ_EMPTY(&tabs))
1023 create_new_tab(NULL, 1);
1025 gtk_widget_destroy(t->vbox);
1026 g_free(t);
1029 void
1030 setup_webkit(struct tab *t)
1032 gchar *strval;
1033 gchar *ua;
1035 /* XXX this can't be called over and over; fix it */
1036 t->settings = webkit_web_settings_new();
1037 g_object_get((GObject *)t->settings, "user-agent", &strval, NULL);
1038 if (strval == NULL) {
1039 warnx("setup_webkit: can't get user-agent property");
1040 return;
1043 if (asprintf(&ua, "%s %s+", strval, version) == -1)
1044 err(1, "aprintf user-agent");
1046 g_object_set((GObject *)t->settings,
1047 "user-agent", ua, NULL);
1048 g_object_set((GObject *)t->settings,
1049 "enable-scripts", enable_scripts, NULL);
1050 g_object_set((GObject *)t->settings,
1051 "enable-plugins", enable_plugins, NULL);
1052 g_object_set((GObject *)t->settings,
1053 "default-font-size", default_font_size, NULL);
1055 webkit_web_view_set_settings(t->wv, t->settings);
1057 g_free (strval);
1058 free(ua);
1061 void
1062 create_new_tab(char *title, int focus)
1064 struct tab *t;
1065 int load = 1;
1066 char *newuri = NULL;
1068 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
1070 if (tabless && !TAILQ_EMPTY(&tabs)) {
1071 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
1072 return;
1075 t = g_malloc0(sizeof *t);
1076 TAILQ_INSERT_TAIL(&tabs, t, entry);
1078 if (title == NULL) {
1079 title = "(untitled)";
1080 load = 0;
1081 } else {
1082 if (valid_url_type(title)) {
1083 newuri = guess_url_type(title);
1084 title = newuri;
1088 t->vbox = gtk_vbox_new(FALSE, 0);
1090 /* label for tab */
1091 t->label = gtk_label_new(title);
1092 gtk_widget_set_size_request(t->label, 100, -1);
1094 /* toolbar */
1095 t->toolbar = create_toolbar(t);
1096 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
1098 /* browser */
1099 t->browser_win = create_browser(t);
1100 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
1101 setup_webkit(t);
1103 /* command entry */
1104 t->cmd = gtk_entry_new();
1105 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
1106 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
1107 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
1109 /* and show it all */
1110 gtk_widget_show_all(t->vbox);
1111 t->tab_id = gtk_notebook_append_page(notebook, t->vbox,
1112 t->label);
1114 g_object_connect((GObject*)t->cmd,
1115 "signal::key-press-event", (GCallback)cmd_keypress_cb, t,
1116 "signal::focus-out-event", (GCallback)cmd_focusout_cb, t,
1117 "signal::activate", (GCallback)cmd_activate_cb, t,
1118 NULL);
1120 g_object_connect((GObject*)t->wv,
1121 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
1122 /* "signal::hovering-over-link", (GCallback)webview_hover_cb, t, */
1123 "signal::download-requested", (GCallback)webview_download_cb, t,
1124 "signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, t,
1125 "signal::navigation-policy-decision-requested", (GCallback)webview_npd_cb, t,
1126 "signal::event", (GCallback)webview_event_cb, t,
1127 NULL);
1129 /* hijack the unused keys as if we were the browser */
1130 g_object_connect((GObject*)t->toolbar,
1131 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
1132 NULL);
1134 g_signal_connect(G_OBJECT(t->uri_entry), "focus",
1135 G_CALLBACK(focus_uri_entry_cb), t);
1137 /* hide stuff */
1138 gtk_widget_hide(t->cmd);
1139 if (showurl == 0)
1140 gtk_widget_hide(t->toolbar);
1142 if (focus) {
1143 gtk_notebook_set_current_page(notebook, t->tab_id);
1144 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
1145 t->tab_id);
1148 if (load)
1149 webkit_web_view_load_uri(t->wv, title);
1150 else
1151 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
1153 if (newuri)
1154 free(newuri);
1157 void
1158 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
1159 gpointer *udata)
1161 struct tab *t;
1163 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
1165 TAILQ_FOREACH(t, &tabs, entry) {
1166 if (t->tab_id == pn) {
1167 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
1168 "%d\n", pn);
1169 gtk_widget_hide(t->cmd);
1174 void
1175 create_canvas(void)
1177 GtkWidget *vbox;
1179 vbox = gtk_vbox_new(FALSE, 0);
1180 notebook = GTK_NOTEBOOK(gtk_notebook_new());
1181 if (showtabs == 0)
1182 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
1183 gtk_notebook_set_scrollable(notebook, TRUE);
1185 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
1187 g_object_connect((GObject*)notebook,
1188 "signal::switch-page", (GCallback)notebook_switchpage_cb, NULL,
1189 NULL);
1191 main_window = create_window();
1192 gtk_container_add(GTK_CONTAINER(main_window), vbox);
1193 gtk_widget_show_all(main_window);
1196 void
1197 setup_cookies(void)
1199 if (cookiejar) {
1200 soup_session_remove_feature(session,
1201 (SoupSessionFeature*)cookiejar);
1202 g_object_unref(cookiejar);
1203 cookiejar = NULL;
1206 if (cookies_enabled == 0)
1207 return;
1209 cookiejar = soup_cookie_jar_text_new(cookie_file, read_only_cookies);
1210 soup_session_add_feature(session, (SoupSessionFeature*)cookiejar);
1213 void
1214 setup_proxy(char *uri)
1216 if (proxy_uri) {
1217 g_object_set(session, "proxy_uri", NULL, NULL);
1218 soup_uri_free(proxy_uri);
1219 proxy_uri = NULL;
1221 if (http_proxy) {
1222 free(http_proxy);
1223 http_proxy = strdup(uri);
1224 if (http_proxy == NULL)
1225 err(1, "http_proxy");
1228 if (uri == NULL)
1229 return;
1231 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
1232 proxy_uri = soup_uri_new(uri);
1233 if (proxy_uri)
1234 g_object_set(session, "proxy-uri", proxy_uri, NULL);
1237 void
1238 usage(void)
1240 fprintf(stderr,
1241 "%s [-STVt][-f file] url ...\n", __progname);
1242 exit(0);
1246 main(int argc, char *argv[])
1248 struct stat sb;
1249 int c, focus = 1;
1250 char conf[PATH_MAX] = { '\0' };
1251 char *env_proxy = NULL;
1253 while ((c = getopt(argc, argv, "STVf:t")) != -1) {
1254 switch (c) {
1255 case 'S':
1256 showurl = 0;
1257 break;
1258 case 'T':
1259 showtabs = 0;
1260 break;
1261 case 'V':
1262 errx(0 , "Version: %s", version);
1263 break;
1264 case 'f':
1265 strlcpy(conf, optarg, sizeof(conf));
1266 break;
1267 case 't':
1268 tabless = 1;
1269 break;
1270 default:
1271 usage();
1272 /* NOTREACHED */
1275 argc -= optind;
1276 argv += optind;
1278 TAILQ_INIT(&tabs);
1280 /* prepare gtk */
1281 gtk_init(&argc, &argv);
1282 if (!g_thread_supported())
1283 g_thread_init(NULL);
1285 pwd = getpwuid(getuid());
1286 if (pwd == NULL)
1287 errx(1, "invalid user %d", getuid());
1289 /* set download dir */
1290 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
1292 /* read config file */
1293 if (strlen(conf) == 0)
1294 snprintf(conf, sizeof conf, "%s/.%s",
1295 pwd->pw_dir, XT_CONF_FILE);
1296 config_parse(conf);
1298 if (stat(download_dir, &sb))
1299 errx(1, "must specify a valid download_dir");
1301 /* working directory */
1302 snprintf(work_dir, sizeof work_dir, "%s/%s", pwd->pw_dir, XT_DIR);
1303 if (stat(work_dir, &sb)) {
1304 if (mkdir(work_dir, S_IRWXU) == -1)
1305 err(1, "mkdir");
1306 } else if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
1307 warnx("fixing invalid permissions on %s", work_dir);
1308 if (chmod(work_dir, S_IRWXU) == -1)
1309 err(1, "chmod");
1312 /* cookies */
1313 session = webkit_get_default_session();
1314 snprintf(cookie_file, sizeof cookie_file, "%s/cookies.txt", work_dir);
1315 setup_cookies();
1317 /* proxy */
1318 env_proxy = getenv("http_proxy");
1319 if (env_proxy) {
1320 http_proxy = strdup(env_proxy);
1321 if (http_proxy == NULL)
1322 err(1, "http_proxy");
1324 setup_proxy(http_proxy);
1326 create_canvas();
1328 while (argc) {
1329 create_new_tab(argv[0], focus);
1330 focus = 0;
1332 argc--;
1333 argv++;
1335 if (focus == 1)
1336 create_new_tab(home, 1);
1338 gtk_main();
1340 return (0);