This fixes the clicking on my wifes site.
[xxxterm.git] / xxxterm.c
blob53b34e0a9096e2eb1dd359bcf20af8ad997310ff
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 * - add favicon
23 * - add bookmark
24 * download files status
25 * multi letter commands
26 * pre and post counts for commands
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 GtkToolItem *backward;
98 GtkToolItem *forward;
99 GtkToolItem *stop;
100 guint tab_id;
102 /* adjustments for browser */
103 GtkScrollbar *sb_h;
104 GtkScrollbar *sb_v;
105 GtkAdjustment *adjust_h;
106 GtkAdjustment *adjust_v;
108 /* flags */
109 int focus_wv;
110 int ctrl_click;
111 gchar *hover;
113 /* search */
114 char *search_text;
115 int search_forward;
116 WebKitWebView *wv;
117 WebKitWebSettings *settings;
119 TAILQ_HEAD(tab_list, tab);
121 struct karg {
122 int i;
123 char *s;
126 /* defines */
127 #define XT_DIR (".xxxterm")
128 #define XT_CONF_FILE ("xxxterm.conf")
129 #define XT_FAVS_FILE ("favorites")
130 #define XT_CB_HANDLED (TRUE)
131 #define XT_CB_PASSTHROUGH (FALSE)
133 /* actions */
134 #define XT_MOVE_INVALID (0)
135 #define XT_MOVE_DOWN (1)
136 #define XT_MOVE_UP (2)
137 #define XT_MOVE_BOTTOM (3)
138 #define XT_MOVE_TOP (4)
139 #define XT_MOVE_PAGEDOWN (5)
140 #define XT_MOVE_PAGEUP (6)
141 #define XT_MOVE_LEFT (7)
142 #define XT_MOVE_FARLEFT (8)
143 #define XT_MOVE_RIGHT (9)
144 #define XT_MOVE_FARRIGHT (10)
146 #define XT_TAB_PREV (-2)
147 #define XT_TAB_NEXT (-1)
148 #define XT_TAB_INVALID (0)
149 #define XT_TAB_NEW (1)
150 #define XT_TAB_DELETE (2)
151 #define XT_TAB_DELQUIT (3)
152 #define XT_TAB_OPEN (4)
154 #define XT_NAV_INVALID (0)
155 #define XT_NAV_BACK (1)
156 #define XT_NAV_FORWARD (2)
157 #define XT_NAV_RELOAD (3)
159 #define XT_FOCUS_INVALID (0)
160 #define XT_FOCUS_URI (1)
162 #define XT_SEARCH_INVALID (0)
163 #define XT_SEARCH_NEXT (1)
164 #define XT_SEARCH_PREV (2)
166 /* globals */
167 extern char *__progname;
168 struct passwd *pwd;
169 GtkWidget *main_window;
170 GtkNotebook *notebook;
171 struct tab_list tabs;
173 /* settings */
174 int showtabs = 1; /* show tabs on notebook */
175 int showurl = 1; /* show url toolbar on notebook */
176 int tabless = 0; /* allow only 1 tab */
177 int ctrl_click_focus = 0; /* ctrl click gets focus */
178 int cookies_enabled = 1; /* enable cookies */
179 int read_only_cookies = 0; /* enable to not write cookies */
180 int enable_scripts = 1;
181 int enable_plugins = 1;
182 int default_font_size = 12;
183 int fancy_bar = 1; /* fancy toolbar */
185 char *home = "http://www.peereboom.us";
186 char *search_string = NULL;
187 char *http_proxy = NULL;
188 SoupURI *proxy_uri = NULL;
189 char work_dir[PATH_MAX];
190 char cookie_file[PATH_MAX];
191 char download_dir[PATH_MAX];
192 SoupSession *session;
193 SoupCookieJar *cookiejar;
195 /* protos */
196 void create_new_tab(char *, int);
197 void delete_tab(struct tab *);
198 void adjustfont_webkit(struct tab *, int);
200 struct valid_url_types {
201 char *type;
202 } vut[] = {
203 { "http://" },
204 { "https://" },
205 { "ftp://" },
206 { "file://" },
210 valid_url_type(char *url)
212 int i;
214 for (i = 0; i < LENGTH(vut); i++)
215 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
216 return (0);
218 return (1);
221 char *
222 guess_url_type(char *url_in)
224 struct stat sb;
225 char *url_out = NULL;
227 /* XXX not sure about this heuristic */
228 if (stat(url_in, &sb) == 0) {
229 if (asprintf(&url_out, "file://%s", url_in) == -1)
230 err(1, "aprintf file");
231 } else {
232 /* guess http */
233 if (asprintf(&url_out, "http://%s", url_in) == -1)
234 err(1, "aprintf http");
237 if (url_out == NULL)
238 err(1, "asprintf pointer");
240 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
242 return (url_out);
245 #define WS "\n= \t"
246 void
247 config_parse(char *filename)
249 FILE *config;
250 char *line, *cp, *var, *val;
251 size_t len, lineno = 0;
253 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
255 if (filename == NULL)
256 return;
258 if ((config = fopen(filename, "r")) == NULL) {
259 warn("config_parse: cannot open %s", filename);
260 return;
263 for (;;) {
264 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
265 if (feof(config))
266 break;
268 cp = line;
269 cp += (long)strspn(cp, WS);
270 if (cp[0] == '\0') {
271 /* empty line */
272 free(line);
273 continue;
276 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
277 break;
279 cp += (long)strspn(cp, WS);
281 if ((val = strsep(&cp, "\0")) == NULL)
282 break;
284 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
286 /* get settings */
287 if (!strcmp(var, "home"))
288 home = strdup(val);
289 else if (!strcmp(var, "ctrl_click_focus"))
290 ctrl_click_focus = atoi(val);
291 else if (!strcmp(var, "read_only_cookies"))
292 read_only_cookies = atoi(val);
293 else if (!strcmp(var, "cookies_enabled"))
294 cookies_enabled = atoi(val);
295 else if (!strcmp(var, "enable_scripts"))
296 enable_scripts = atoi(val);
297 else if (!strcmp(var, "enable_plugins"))
298 enable_plugins = atoi(val);
299 else if (!strcmp(var, "default_font_size"))
300 default_font_size = atoi(val);
301 else if (!strcmp(var, "fancy_bar"))
302 fancy_bar = atoi(val);
303 else if (!strcmp(var, "http_proxy")) {
304 http_proxy = strdup(val);
305 if (http_proxy == NULL)
306 err(1, "http_proxy");
307 } else if (!strcmp(var, "search_string")) {
308 search_string = strdup(val);
309 if (search_string == NULL)
310 err(1, "search_string");
311 } else if (!strcmp(var, "download_dir")) {
312 if (val[0] == '~')
313 snprintf(download_dir, sizeof download_dir,
314 "%s/%s", pwd->pw_dir, &val[1]);
315 else
316 strlcpy(download_dir, val, sizeof download_dir);
317 } else
318 errx(1, "invalid conf file entry: %s=%s", var, val);
320 free(line);
323 fclose(config);
326 quit(struct tab *t, struct karg *args)
328 gtk_main_quit();
330 return (1);
334 focus(struct tab *t, struct karg *args)
336 if (t == NULL)
337 errx(1, "focus");
339 if (args->i == XT_FOCUS_URI)
340 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
342 return (0);
346 help(struct tab *t, struct karg *args)
348 if (t == NULL)
349 errx(1, "help");
351 webkit_web_view_load_string(t->wv,
352 "<html><body><h1>XXXTerm</h1></body></html>",
353 NULL,
354 NULL,
355 NULL);
357 return (0);
361 favorites(struct tab *t, struct karg *args)
363 char file[PATH_MAX];
364 FILE *f, *h;
365 char *uri = NULL, *title = NULL;
366 size_t len, lineno = 0;
367 int i, failed = 0;
369 if (t == NULL)
370 errx(1, "help");
372 /* XXX run a digest over the favorites file instead of always generating it */
374 /* open favorites */
375 snprintf(file, sizeof file, "%s/%s/%s",
376 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
377 if ((f = fopen(file, "r")) == NULL) {
378 warn("favorites");
379 return (1);
382 /* open favorites html */
383 snprintf(file, sizeof file, "%s/%s/%s.html",
384 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
385 if ((h = fopen(file, "w+")) == NULL) {
386 warn("favorites.html");
387 return (1);
390 fprintf(h, "<html><body>Favorites:<p>\n<ol>\n");
392 for (i = 1;;) {
393 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
394 if (feof(f))
395 break;
396 if (strlen(title) == 0)
397 continue;
399 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
400 if (feof(f)) {
401 failed = 1;
402 break;
405 fprintf(h, "<li><a href=\"%s\">%s</a><br>\n", uri, title);
407 free(uri);
408 uri = NULL;
409 free(title);
410 title = NULL;
411 i++;
414 if (uri)
415 free(uri);
416 if (title)
417 free(title);
419 fprintf(h, "</ol></body></html>");
420 fclose(f);
421 fclose(h);
423 if (failed) {
424 webkit_web_view_load_string(t->wv,
425 "<html><body>Invalid favorites file</body></html>",
426 NULL,
427 NULL,
428 NULL);
429 } else {
430 snprintf(file, sizeof file, "file://%s/%s/%s.html",
431 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
432 webkit_web_view_load_uri(t->wv, file);
435 return (0);
439 navaction(struct tab *t, struct karg *args)
441 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
442 t->tab_id, args->i);
444 switch (args->i) {
445 case XT_NAV_BACK:
446 webkit_web_view_go_back(t->wv);
447 break;
448 case XT_NAV_FORWARD:
449 webkit_web_view_go_forward(t->wv);
450 break;
451 case XT_NAV_RELOAD:
452 webkit_web_view_reload(t->wv);
453 break;
455 return (XT_CB_PASSTHROUGH);
459 move(struct tab *t, struct karg *args)
461 GtkAdjustment *adjust;
462 double pi, si, pos, ps, upper, lower, max;
464 switch (args->i) {
465 case XT_MOVE_DOWN:
466 case XT_MOVE_UP:
467 case XT_MOVE_BOTTOM:
468 case XT_MOVE_TOP:
469 case XT_MOVE_PAGEDOWN:
470 case XT_MOVE_PAGEUP:
471 adjust = t->adjust_v;
472 break;
473 default:
474 adjust = t->adjust_h;
475 break;
478 pos = gtk_adjustment_get_value(adjust);
479 ps = gtk_adjustment_get_page_size(adjust);
480 upper = gtk_adjustment_get_upper(adjust);
481 lower = gtk_adjustment_get_lower(adjust);
482 si = gtk_adjustment_get_step_increment(adjust);
483 pi = gtk_adjustment_get_page_increment(adjust);
484 max = upper - ps;
486 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
487 "max %f si %f pi %f\n",
488 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
489 pos, ps, upper, lower, max, si, pi);
491 switch (args->i) {
492 case XT_MOVE_DOWN:
493 case XT_MOVE_RIGHT:
494 pos += si;
495 gtk_adjustment_set_value(adjust, MIN(pos, max));
496 break;
497 case XT_MOVE_UP:
498 case XT_MOVE_LEFT:
499 pos -= si;
500 gtk_adjustment_set_value(adjust, MAX(pos, lower));
501 break;
502 case XT_MOVE_BOTTOM:
503 case XT_MOVE_FARRIGHT:
504 gtk_adjustment_set_value(adjust, max);
505 break;
506 case XT_MOVE_TOP:
507 case XT_MOVE_FARLEFT:
508 gtk_adjustment_set_value(adjust, lower);
509 break;
510 case XT_MOVE_PAGEDOWN:
511 pos += pi;
512 gtk_adjustment_set_value(adjust, MIN(pos, max));
513 break;
514 case XT_MOVE_PAGEUP:
515 pos -= pi;
516 gtk_adjustment_set_value(adjust, MAX(pos, lower));
517 break;
518 default:
519 return (XT_CB_PASSTHROUGH);
522 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
524 return (XT_CB_HANDLED);
527 char *
528 getparams(char *cmd, char *cmp)
530 char *rv = NULL;
532 if (cmd && cmp) {
533 if (!strncmp(cmd, cmp, strlen(cmp))) {
534 rv = cmd + strlen(cmp);
535 while (*rv == ' ')
536 rv++;
537 if (strlen(rv) == 0)
538 rv = NULL;
542 return (rv);
546 tabaction(struct tab *t, struct karg *args)
548 int rv = XT_CB_HANDLED;
549 char *url = NULL, *newuri = NULL;
551 DNPRINTF(XT_D_TAB, "tabaction: %p %d %d\n", t, args->i, t->focus_wv);
553 if (t == NULL)
554 return (XT_CB_PASSTHROUGH);
556 switch (args->i) {
557 case XT_TAB_NEW:
558 if ((url = getparams(args->s, "tabnew")))
559 create_new_tab(url, 1);
560 else
561 create_new_tab(NULL, 1);
562 break;
563 case XT_TAB_DELETE:
564 delete_tab(t);
565 break;
566 case XT_TAB_DELQUIT:
567 if (gtk_notebook_get_n_pages(notebook) > 1)
568 delete_tab(t);
569 else
570 quit(t, args);
571 break;
572 case XT_TAB_OPEN:
573 if ((url = getparams(args->s, "open")) ||
574 ((url = getparams(args->s, "op"))) ||
575 ((url = getparams(args->s, "o"))))
577 else {
578 rv = XT_CB_PASSTHROUGH;
579 goto done;
582 if (valid_url_type(url)) {
583 newuri = guess_url_type(url);
584 url = newuri;
586 webkit_web_view_load_uri(t->wv, url);
587 if (newuri)
588 free(newuri);
589 break;
590 default:
591 rv = XT_CB_PASSTHROUGH;
592 goto done;
595 done:
596 if (args->s) {
597 free(args->s);
598 args->s = NULL;
601 return (rv);
605 resizetab(struct tab *t, struct karg *args)
607 if (t == NULL || args == NULL)
608 errx(1, "resizetab");
610 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
611 t->tab_id, args->i);
613 if (t == NULL)
614 return (XT_CB_PASSTHROUGH);
616 adjustfont_webkit(t, args->i);
618 return (XT_CB_HANDLED);
622 movetab(struct tab *t, struct karg *args)
624 struct tab *tt;
625 int x;
627 if (t == NULL || args == NULL)
628 return (XT_CB_PASSTHROUGH);
630 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
631 t->tab_id, args->i);
633 if (args->i == XT_TAB_INVALID)
634 return (XT_CB_PASSTHROUGH);
636 if (args->i < XT_TAB_INVALID) {
637 /* next or previous tab */
638 if (TAILQ_EMPTY(&tabs))
639 return (XT_CB_PASSTHROUGH);
641 if (args->i == XT_TAB_NEXT)
642 gtk_notebook_next_page(notebook);
643 else
644 gtk_notebook_prev_page(notebook);
646 return (XT_CB_HANDLED);
649 /* jump to tab */
650 x = args->i - 1;
651 if (t->tab_id == x) {
652 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
653 return (XT_CB_HANDLED);
656 TAILQ_FOREACH(tt, &tabs, entry) {
657 if (tt->tab_id == x) {
658 gtk_notebook_set_current_page(notebook, x);
659 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
660 if (tt->focus_wv)
661 gtk_widget_grab_focus(GTK_WIDGET(tt->wv));
665 return (XT_CB_HANDLED);
669 command(struct tab *t, struct karg *args)
671 char *s = NULL;
672 GdkColor color;
674 if (t == NULL || args == NULL)
675 errx(1, "command");
677 if (args->i == '/')
678 s = "/";
679 else if (args->i == '?')
680 s = "?";
681 else if (args->i == ':')
682 s = ":";
683 else {
684 warnx("invalid command %c\n", args->i);
685 return (XT_CB_PASSTHROUGH);
688 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
690 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
691 gdk_color_parse("white", &color);
692 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
693 gtk_widget_show(t->cmd);
694 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
695 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
697 return (XT_CB_HANDLED);
701 search(struct tab *t, struct karg *args)
703 gboolean d;
705 if (t == NULL || args == NULL)
706 errx(1, "search");
707 if (t->search_text == NULL)
708 return (XT_CB_PASSTHROUGH);
710 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
711 t->tab_id, args->i, t->search_forward, t->search_text);
713 switch (args->i) {
714 case XT_SEARCH_NEXT:
715 d = t->search_forward;
716 break;
717 case XT_SEARCH_PREV:
718 d = !t->search_forward;
719 break;
720 default:
721 return (XT_CB_PASSTHROUGH);
724 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
726 return (XT_CB_HANDLED);
729 /* inherent to GTK not all keys will be caught at all times */
730 struct key {
731 guint mask;
732 guint modkey;
733 guint key;
734 int (*func)(struct tab *, struct karg *);
735 struct karg arg;
736 } keys[] = {
737 { 0, 0, GDK_slash, command, {.i = '/'} },
738 { GDK_SHIFT_MASK, 0, GDK_question, command, {.i = '?'} },
739 { GDK_SHIFT_MASK, 0, GDK_colon, command, {.i = ':'} },
740 { GDK_CONTROL_MASK, 0, GDK_q, quit, {0} },
742 /* search */
743 { 0, 0, GDK_n, search, {.i = XT_SEARCH_NEXT} },
744 { GDK_SHIFT_MASK, 0, GDK_N, search, {.i = XT_SEARCH_PREV} },
746 /* focus */
747 { 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} },
749 /* navigation */
750 { 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
751 { GDK_MOD1_MASK, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
752 { GDK_SHIFT_MASK, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
753 { GDK_MOD1_MASK, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
754 { 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} },
755 { GDK_CONTROL_MASK, 0, GDK_r, navaction, {.i = XT_NAV_RELOAD} },
756 { GDK_CONTROL_MASK, 0, GDK_l, navaction, {.i = XT_NAV_RELOAD} },
758 /* vertical movement */
759 { 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
760 { 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
761 { 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
762 { 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
763 { GDK_SHIFT_MASK, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
764 { 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
765 { 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
766 { 0, GDK_g, GDK_g, move, {.i = XT_MOVE_TOP} }, /* XXX make this work */
767 { 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
768 { GDK_CONTROL_MASK, 0, GDK_f, move, {.i = XT_MOVE_PAGEDOWN} },
769 { 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
770 { 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
771 { GDK_CONTROL_MASK, 0, GDK_b, move, {.i = XT_MOVE_PAGEUP} },
772 /* horizontal movement */
773 { 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
774 { 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
775 { 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
776 { 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
777 { GDK_SHIFT_MASK, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
778 { 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
780 /* tabs */
781 { GDK_CONTROL_MASK, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
782 { GDK_CONTROL_MASK, 0, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
783 { GDK_CONTROL_MASK, 0, GDK_1, movetab, {.i = 1} },
784 { GDK_CONTROL_MASK, 0, GDK_2, movetab, {.i = 2} },
785 { GDK_CONTROL_MASK, 0, GDK_3, movetab, {.i = 3} },
786 { GDK_CONTROL_MASK, 0, GDK_4, movetab, {.i = 4} },
787 { GDK_CONTROL_MASK, 0, GDK_5, movetab, {.i = 5} },
788 { GDK_CONTROL_MASK, 0, GDK_6, movetab, {.i = 6} },
789 { GDK_CONTROL_MASK, 0, GDK_7, movetab, {.i = 7} },
790 { GDK_CONTROL_MASK, 0, GDK_8, movetab, {.i = 8} },
791 { GDK_CONTROL_MASK, 0, GDK_9, movetab, {.i = 9} },
792 { GDK_CONTROL_MASK, 0, GDK_0, movetab, {.i = 10} },
793 { GDK_CONTROL_MASK, 0, GDK_minus, resizetab, {.i = -1} },
794 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_plus, resizetab, {.i = 1} },
795 { GDK_CONTROL_MASK, 0, GDK_equal, resizetab, {.i = 1} },
798 struct cmd {
799 char *cmd;
800 int params;
801 int (*func)(struct tab *, struct karg *);
802 struct karg arg;
803 } cmds[] = {
804 { "q!", 0, quit, {0} },
805 { "qa", 0, quit, {0} },
806 { "qa!", 0, quit, {0} },
807 { "help", 0, help, {0} },
808 { "fav", 0, favorites, {0} },
810 /* tabs */
811 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
812 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
813 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
814 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
815 { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} },
816 { "tabe", 1, tabaction, {.i = XT_TAB_NEW} },
817 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
818 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} },
819 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
820 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
821 /* XXX add count to these commands and add tabl and friends */
822 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
823 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
824 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
825 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
828 void
829 focus_uri_entry_cb(GtkWidget* w, GtkDirectionType direction, struct tab *t)
831 DNPRINTF(XT_D_URL, "focus_uri_entry_cb: tab %d focus_wv %d\n",
832 t->tab_id, t->focus_wv);
834 if (t == NULL)
835 errx(1, "focus_uri_entry_cb");
837 /* focus on wv instead */
838 if (t->focus_wv)
839 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
842 void
843 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
845 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
846 char *newuri = NULL;
848 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
850 if (t == NULL)
851 errx(1, "activate_uri_entry_cb");
853 if (uri == NULL)
854 errx(1, "uri");
856 if (valid_url_type((char *)uri)) {
857 newuri = guess_url_type((char *)uri);
858 uri = newuri;
861 webkit_web_view_load_uri(t->wv, uri);
862 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
864 if (newuri)
865 free(newuri);
868 void
869 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
871 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
872 char *newuri = NULL;
874 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
876 if (t == NULL)
877 errx(1, "activate_search_entry_cb");
879 if (search_string == NULL) {
880 warnx("no search_string");
881 return;
884 if (asprintf(&newuri, search_string, search) == -1)
885 err(1, "activate_search_entry_cb");
887 webkit_web_view_load_uri(t->wv, newuri);
888 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
890 if (newuri)
891 free(newuri);
894 void
895 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
897 WebKitWebFrame *frame;
898 const gchar *uri;
900 if (t == NULL)
901 errx(1, "notify_load_status_cb");
903 switch (webkit_web_view_get_load_status(wview)) {
904 case WEBKIT_LOAD_COMMITTED:
905 frame = webkit_web_view_get_main_frame(wview);
906 uri = webkit_web_frame_get_uri(frame);
907 if (uri)
908 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
910 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
911 t->focus_wv = 1;
913 /* take focus if we are visible */
914 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
915 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
916 break;
917 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
918 uri = webkit_web_view_get_title(wview);
919 if (uri == NULL) {
920 frame = webkit_web_view_get_main_frame(wview);
921 uri = webkit_web_frame_get_uri(frame);
923 gtk_label_set_text(GTK_LABEL(t->label), uri);
924 break;
925 case WEBKIT_LOAD_PROVISIONAL:
926 case WEBKIT_LOAD_FINISHED:
927 #if WEBKIT_CHECK_VERSION(1, 1, 18)
928 case WEBKIT_LOAD_FAILED:
929 #endif
930 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
931 default:
932 break;
935 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
936 webkit_web_view_can_go_back(wview));
938 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
939 webkit_web_view_can_go_forward(wview));
943 webview_nw_cb(WebKitWebView *wv, WebKitWebFrame *wf,
944 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
945 WebKitWebPolicyDecision *pd, struct tab *t)
947 if (t == NULL)
948 errx(1, "webview_nw_cb");
950 DNPRINTF(XT_D_NAV, "webview_nw_cb: %s\n",
951 webkit_network_request_get_uri(request));
953 return (FALSE); /* open in current tab */
957 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
958 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
959 WebKitWebPolicyDecision *pd, struct tab *t)
961 char *uri;
963 if (t == NULL)
964 errx(1, "webview_npd_cb");
966 DNPRINTF(XT_D_NAV, "webview_npd_cb: %s\n",
967 webkit_network_request_get_uri(request));
969 if (t->ctrl_click) {
970 uri = (char *)webkit_network_request_get_uri(request);
971 create_new_tab(uri, ctrl_click_focus);
972 t->ctrl_click = 0;
973 webkit_web_policy_decision_ignore(pd);
975 return (TRUE); /* we made the decission */
978 return (FALSE);
982 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
984 /* we can not eat the event without throwing gtk off so defer it */
986 /* catch ctrl click */
987 if (e->type == GDK_BUTTON_RELEASE &&
988 CLEAN(e->state) == GDK_CONTROL_MASK)
989 t->ctrl_click = 1;
990 else
991 t->ctrl_click = 0;
993 return (XT_CB_PASSTHROUGH);
997 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
998 WebKitNetworkRequest *request, char *mime_type,
999 WebKitWebPolicyDecision *decision, struct tab *t)
1001 if (t == NULL)
1002 errx(1, "webview_mimetype_cb");
1004 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
1005 t->tab_id, mime_type);
1007 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
1008 webkit_web_policy_decision_download(decision);
1009 return (TRUE);
1012 return (FALSE);
1016 webview_download_cb(WebKitWebView *wv, WebKitDownload *download, struct tab *t)
1018 const gchar *filename;
1019 char *uri = NULL;
1021 if (download == NULL || t == NULL)
1022 errx(1, "webview_download_cb: invalid pointers");
1024 filename = webkit_download_get_suggested_filename(download);
1025 if (filename == NULL)
1026 return (FALSE); /* abort download */
1028 if (asprintf(&uri, "file://%s/%s", download_dir, filename) == -1)
1029 err(1, "aprintf uri");
1031 DNPRINTF(XT_D_DOWNLOAD, "webview_download_cb: tab %d filename %s "
1032 "local %s\n",
1033 t->tab_id, filename, uri);
1035 webkit_download_set_destination_uri(download, uri);
1037 if (uri)
1038 free(uri);
1040 webkit_download_start(download);
1042 return (TRUE); /* start download */
1045 /* XXX currently unused */
1046 void
1047 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
1049 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
1051 if (t == NULL)
1052 errx(1, "webview_hover_cb");
1054 if (uri) {
1055 if (t->hover) {
1056 free(t->hover);
1057 t->hover = NULL;
1059 t->hover = strdup(uri);
1060 } else if (t->hover) {
1061 free(t->hover);
1062 t->hover = NULL;
1067 webview_keypress_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
1069 int i;
1071 /* don't use w directly; use t->whatever instead */
1073 if (t == NULL)
1074 errx(1, "webview_keypress_cb");
1076 DNPRINTF(XT_D_KEY, "webview_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
1077 e->keyval, e->state, t);
1079 for (i = 0; i < LENGTH(keys); i++)
1080 if (e->keyval == keys[i].key && CLEAN(e->state) ==
1081 keys[i].mask) {
1082 keys[i].func(t, &keys[i].arg);
1083 return (XT_CB_HANDLED);
1086 return (XT_CB_PASSTHROUGH);
1090 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
1092 const gchar *c = gtk_entry_get_text(w);
1093 GdkColor color;
1094 int forward = TRUE;
1096 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
1097 e->keyval, e->state, t);
1099 if (t == NULL)
1100 errx(1, "cmd_keyrelease_cb");
1102 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
1103 e->keyval, e->state, t);
1105 if (c[0] == ':')
1106 goto done;
1107 if (strlen(c) == 1)
1108 goto done;
1110 if (c[0] == '/')
1111 forward = TRUE;
1112 else if (c[0] == '?')
1113 forward = FALSE;
1114 else
1115 goto done;
1117 /* search */
1118 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
1119 FALSE) {
1120 /* not found, mark red */
1121 gdk_color_parse("red", &color);
1122 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
1123 /* unmark and remove selection */
1124 webkit_web_view_unmark_text_matches(t->wv);
1125 /* my kingdom for a way to unselect text in webview */
1126 } else {
1127 /* found, highlight all */
1128 webkit_web_view_unmark_text_matches(t->wv);
1129 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
1130 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
1131 gdk_color_parse("white", &color);
1132 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
1134 done:
1135 return (XT_CB_PASSTHROUGH);
1139 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
1141 int rv = XT_CB_HANDLED;
1142 const gchar *c = gtk_entry_get_text(w);
1144 if (t == NULL)
1145 errx(1, "cmd_keypress_cb");
1147 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
1148 e->keyval, e->state, t);
1150 /* sanity */
1151 if (c == NULL)
1152 e->keyval = GDK_Escape;
1153 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
1154 e->keyval = GDK_Escape;
1156 switch (e->keyval) {
1157 case GDK_BackSpace:
1158 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
1159 break;
1160 /* FALLTHROUGH */
1161 case GDK_Escape:
1162 gtk_widget_hide(t->cmd);
1163 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1164 goto done;
1167 rv = XT_CB_PASSTHROUGH;
1168 done:
1169 return (rv);
1173 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
1175 if (t == NULL)
1176 errx(1, "cmd_focusout_cb");
1178 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d focus_wv %d\n",
1179 t->tab_id, t->focus_wv);
1181 /* abort command when losing focus */
1182 gtk_widget_hide(t->cmd);
1183 if (t->focus_wv)
1184 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1185 else
1186 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
1188 return (XT_CB_PASSTHROUGH);
1191 void
1192 cmd_activate_cb(GtkEntry *entry, struct tab *t)
1194 int i;
1195 char *s;
1196 const gchar *c = gtk_entry_get_text(entry);
1198 if (t == NULL)
1199 errx(1, "cmd_activate_cb");
1201 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
1203 /* sanity */
1204 if (c == NULL)
1205 goto done;
1206 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
1207 goto done;
1208 if (strlen(c) < 2)
1209 goto done;
1210 s = (char *)&c[1];
1212 if (c[0] == '/' || c[0] == '?') {
1213 if (t->search_text) {
1214 free(t->search_text);
1215 t->search_text = NULL;
1218 t->search_text = strdup(s);
1219 if (t->search_text == NULL)
1220 err(1, "search_text");
1222 t->search_forward = c[0] == '/';
1224 goto done;
1227 for (i = 0; i < LENGTH(cmds); i++)
1228 if (cmds[i].params) {
1229 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
1230 cmds[i].arg.s = strdup(s);
1231 cmds[i].func(t, &cmds[i].arg);
1233 } else {
1234 if (!strcmp(s, cmds[i].cmd))
1235 cmds[i].func(t, &cmds[i].arg);
1238 done:
1239 gtk_widget_hide(t->cmd);
1242 void
1243 backward_cb(GtkWidget *w, struct tab *t)
1245 if (t == NULL)
1246 errx(1, "backward_cb");
1248 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
1250 webkit_web_view_go_back(t->wv);
1253 void
1254 forward_cb(GtkWidget *w, struct tab *t)
1256 if (t == NULL)
1257 errx(1, "forward_cb");
1259 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
1261 webkit_web_view_go_forward(t->wv);
1264 void
1265 stop_cb(GtkWidget *w, struct tab *t)
1267 WebKitWebFrame *frame;
1269 if (t == NULL)
1270 errx(1, "stop_cb");
1272 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
1274 frame = webkit_web_view_get_main_frame(t->wv);
1275 if (frame == NULL) {
1276 warnx("stop_cb: no frame");
1277 return;
1280 webkit_web_frame_stop_loading(frame);
1283 GtkWidget *
1284 create_browser(struct tab *t)
1286 GtkWidget *w;
1288 if (t == NULL)
1289 errx(1, "create_browser");
1291 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
1292 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
1293 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
1294 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
1296 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
1297 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
1298 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1300 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
1301 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
1303 g_signal_connect(t->wv, "notify::load-status",
1304 G_CALLBACK(notify_load_status_cb), t);
1306 return (w);
1309 GtkWidget *
1310 create_window(void)
1312 GtkWidget *w;
1314 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1315 gtk_window_set_default_size(GTK_WINDOW(w), 800, 600);
1316 gtk_widget_set_name(w, "xxxterm");
1317 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
1319 return (w);
1322 GtkWidget *
1323 create_toolbar(struct tab *t)
1325 GtkWidget *toolbar = gtk_toolbar_new();
1326 GtkToolItem *i;
1328 #if GTK_CHECK_VERSION(2,15,0)
1329 gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar),
1330 GTK_ORIENTATION_HORIZONTAL);
1331 #else
1332 gtk_toolbar_set_orientation(GTK_TOOLBAR(toolbar),
1333 GTK_ORIENTATION_HORIZONTAL);
1334 #endif
1335 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH_HORIZ);
1337 if (fancy_bar) {
1338 /* backward button */
1339 t->backward = gtk_tool_button_new_from_stock(GTK_STOCK_GO_BACK);
1340 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), FALSE);
1341 g_signal_connect(G_OBJECT(t->backward), "clicked",
1342 G_CALLBACK(backward_cb), t);
1343 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), t->backward, -1);
1345 /* forward button */
1346 t->forward =
1347 gtk_tool_button_new_from_stock(GTK_STOCK_GO_FORWARD);
1348 gtk_widget_set_sensitive(GTK_WIDGET(t->forward), FALSE);
1349 g_signal_connect(G_OBJECT(t->forward), "clicked",
1350 G_CALLBACK(forward_cb), t);
1351 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), t->forward, -1);
1353 /* stop button */
1354 t->stop = gtk_tool_button_new_from_stock(GTK_STOCK_STOP);
1355 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
1356 g_signal_connect(G_OBJECT(t->stop), "clicked",
1357 G_CALLBACK(stop_cb), t);
1358 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), t->stop, -1);
1361 /* uri entry */
1362 i = gtk_tool_item_new();
1363 gtk_tool_item_set_expand(i, TRUE);
1364 t->uri_entry = gtk_entry_new();
1365 gtk_container_add(GTK_CONTAINER(i), t->uri_entry);
1366 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
1367 G_CALLBACK(activate_uri_entry_cb), t);
1368 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), i, -1);
1370 /* search entry */
1371 if (fancy_bar && search_string) {
1372 i = gtk_tool_item_new();
1373 t->search_entry = gtk_entry_new();
1374 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
1375 gtk_container_add(GTK_CONTAINER(i), t->search_entry);
1376 g_signal_connect(G_OBJECT(t->search_entry), "activate",
1377 G_CALLBACK(activate_search_entry_cb), t);
1378 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), i, -1);
1381 return (toolbar);
1384 void
1385 delete_tab(struct tab *t)
1387 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
1389 if (t == NULL)
1390 return;
1392 TAILQ_REMOVE(&tabs, t, entry);
1393 if (TAILQ_EMPTY(&tabs))
1394 create_new_tab(NULL, 1);
1396 gtk_widget_destroy(t->vbox);
1397 g_free(t);
1400 void
1401 setup_webkit(struct tab *t)
1403 gchar *strval;
1404 gchar *ua;
1406 /* XXX this can't be called over and over; fix it */
1407 t->settings = webkit_web_settings_new();
1408 g_object_get((GObject *)t->settings, "user-agent", &strval, NULL);
1409 if (strval == NULL) {
1410 warnx("setup_webkit: can't get user-agent property");
1411 return;
1414 if (asprintf(&ua, "%s %s+", strval, version) == -1)
1415 err(1, "aprintf user-agent");
1417 g_object_set((GObject *)t->settings,
1418 "user-agent", ua, NULL);
1419 g_object_set((GObject *)t->settings,
1420 "enable-scripts", enable_scripts, NULL);
1421 g_object_set((GObject *)t->settings,
1422 "enable-plugins", enable_plugins, NULL);
1423 adjustfont_webkit(t, 0);
1425 webkit_web_view_set_settings(t->wv, t->settings);
1427 g_free (strval);
1428 free(ua);
1431 void
1432 adjustfont_webkit(struct tab *t, int adjust)
1434 if (t == NULL)
1435 errx(1, "adjustfont_webkit");
1437 default_font_size += adjust;
1438 g_object_set((GObject *)t->settings, "default-font-size",
1439 default_font_size, NULL);
1440 g_object_get((GObject *)t->settings, "default-font-size",
1441 &default_font_size, NULL);
1444 void
1445 create_new_tab(char *title, int focus)
1447 struct tab *t;
1448 int load = 1;
1449 char *newuri = NULL;
1451 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
1453 if (tabless && !TAILQ_EMPTY(&tabs)) {
1454 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
1455 return;
1458 t = g_malloc0(sizeof *t);
1459 TAILQ_INSERT_TAIL(&tabs, t, entry);
1461 if (title == NULL) {
1462 title = "(untitled)";
1463 load = 0;
1464 } else {
1465 if (valid_url_type(title)) {
1466 newuri = guess_url_type(title);
1467 title = newuri;
1471 t->vbox = gtk_vbox_new(FALSE, 0);
1473 /* label for tab */
1474 t->label = gtk_label_new(title);
1475 gtk_widget_set_size_request(t->label, 100, -1);
1477 /* toolbar */
1478 t->toolbar = create_toolbar(t);
1479 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
1481 /* browser */
1482 t->browser_win = create_browser(t);
1483 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
1484 setup_webkit(t);
1486 /* command entry */
1487 t->cmd = gtk_entry_new();
1488 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
1489 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
1490 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
1492 /* and show it all */
1493 gtk_widget_show_all(t->vbox);
1494 t->tab_id = gtk_notebook_append_page(notebook, t->vbox,
1495 t->label);
1497 g_object_connect((GObject*)t->cmd,
1498 "signal::key-press-event", (GCallback)cmd_keypress_cb, t,
1499 "signal::key-release-event", (GCallback)cmd_keyrelease_cb, t,
1500 "signal::focus-out-event", (GCallback)cmd_focusout_cb, t,
1501 "signal::activate", (GCallback)cmd_activate_cb, t,
1502 NULL);
1504 g_object_connect((GObject*)t->wv,
1505 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
1506 /* "signal::hovering-over-link", (GCallback)webview_hover_cb, t, */
1507 "signal::download-requested", (GCallback)webview_download_cb, t,
1508 "signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, t,
1509 "signal::navigation-policy-decision-requested", (GCallback)webview_npd_cb, t,
1510 "signal::new-window-policy-decision-requested", (GCallback)webview_nw_cb, t,
1511 "signal::event", (GCallback)webview_event_cb, t,
1512 NULL);
1514 /* hijack the unused keys as if we were the browser */
1515 g_object_connect((GObject*)t->toolbar,
1516 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
1517 NULL);
1519 g_signal_connect(G_OBJECT(t->uri_entry), "focus",
1520 G_CALLBACK(focus_uri_entry_cb), t);
1522 /* hide stuff */
1523 gtk_widget_hide(t->cmd);
1524 if (showurl == 0)
1525 gtk_widget_hide(t->toolbar);
1527 if (focus) {
1528 gtk_notebook_set_current_page(notebook, t->tab_id);
1529 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
1530 t->tab_id);
1533 if (load)
1534 webkit_web_view_load_uri(t->wv, title);
1535 else
1536 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
1538 if (newuri)
1539 free(newuri);
1542 void
1543 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
1544 gpointer *udata)
1546 struct tab *t;
1548 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
1550 TAILQ_FOREACH(t, &tabs, entry) {
1551 if (t->tab_id == pn) {
1552 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
1553 "%d\n", pn);
1554 gtk_widget_hide(t->cmd);
1559 void
1560 create_canvas(void)
1562 GtkWidget *vbox;
1564 vbox = gtk_vbox_new(FALSE, 0);
1565 notebook = GTK_NOTEBOOK(gtk_notebook_new());
1566 if (showtabs == 0)
1567 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
1568 gtk_notebook_set_scrollable(notebook, TRUE);
1570 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
1572 g_object_connect((GObject*)notebook,
1573 "signal::switch-page", (GCallback)notebook_switchpage_cb, NULL,
1574 NULL);
1576 main_window = create_window();
1577 gtk_container_add(GTK_CONTAINER(main_window), vbox);
1578 gtk_widget_show_all(main_window);
1581 void
1582 setup_cookies(void)
1584 if (cookiejar) {
1585 soup_session_remove_feature(session,
1586 (SoupSessionFeature*)cookiejar);
1587 g_object_unref(cookiejar);
1588 cookiejar = NULL;
1591 if (cookies_enabled == 0)
1592 return;
1594 cookiejar = soup_cookie_jar_text_new(cookie_file, read_only_cookies);
1595 soup_session_add_feature(session, (SoupSessionFeature*)cookiejar);
1598 void
1599 setup_proxy(char *uri)
1601 if (proxy_uri) {
1602 g_object_set(session, "proxy_uri", NULL, NULL);
1603 soup_uri_free(proxy_uri);
1604 proxy_uri = NULL;
1606 if (http_proxy) {
1607 free(http_proxy);
1608 http_proxy = strdup(uri);
1609 if (http_proxy == NULL)
1610 err(1, "http_proxy");
1613 if (uri == NULL)
1614 return;
1616 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
1617 proxy_uri = soup_uri_new(uri);
1618 if (proxy_uri)
1619 g_object_set(session, "proxy-uri", proxy_uri, NULL);
1622 void
1623 usage(void)
1625 fprintf(stderr,
1626 "%s [-STVt][-f file] url ...\n", __progname);
1627 exit(0);
1631 main(int argc, char *argv[])
1633 struct stat sb;
1634 int c, focus = 1;
1635 char conf[PATH_MAX] = { '\0' };
1636 char *env_proxy = NULL;
1637 FILE *f = NULL;
1639 while ((c = getopt(argc, argv, "STVf:t")) != -1) {
1640 switch (c) {
1641 case 'S':
1642 showurl = 0;
1643 break;
1644 case 'T':
1645 showtabs = 0;
1646 break;
1647 case 'V':
1648 errx(0 , "Version: %s", version);
1649 break;
1650 case 'f':
1651 strlcpy(conf, optarg, sizeof(conf));
1652 break;
1653 case 't':
1654 tabless = 1;
1655 break;
1656 default:
1657 usage();
1658 /* NOTREACHED */
1661 argc -= optind;
1662 argv += optind;
1664 TAILQ_INIT(&tabs);
1666 /* prepare gtk */
1667 gtk_init(&argc, &argv);
1668 if (!g_thread_supported())
1669 g_thread_init(NULL);
1671 pwd = getpwuid(getuid());
1672 if (pwd == NULL)
1673 errx(1, "invalid user %d", getuid());
1675 /* set download dir */
1676 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
1678 /* read config file */
1679 if (strlen(conf) == 0)
1680 snprintf(conf, sizeof conf, "%s/.%s",
1681 pwd->pw_dir, XT_CONF_FILE);
1682 config_parse(conf);
1684 /* download dir */
1685 if (stat(download_dir, &sb))
1686 errx(1, "must specify a valid download_dir");
1687 if (S_ISDIR(sb.st_mode) == 0)
1688 errx(1, "%s not a dir", download_dir);
1689 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
1690 warnx("fixing invalid permissions on %s", download_dir);
1691 if (chmod(download_dir, S_IRWXU) == -1)
1692 err(1, "chmod");
1695 /* working directory */
1696 snprintf(work_dir, sizeof work_dir, "%s/%s", pwd->pw_dir, XT_DIR);
1697 if (stat(work_dir, &sb)) {
1698 if (mkdir(work_dir, S_IRWXU) == -1)
1699 err(1, "mkdir");
1701 if (S_ISDIR(sb.st_mode) == 0)
1702 errx(1, "%s not a dir", work_dir);
1703 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
1704 warnx("fixing invalid permissions on %s", work_dir);
1705 if (chmod(work_dir, S_IRWXU) == -1)
1706 err(1, "chmod");
1709 /* favorites file */
1710 snprintf(work_dir, sizeof work_dir, "%s/%s/%s",
1711 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
1712 if (stat(work_dir, &sb)) {
1713 warnx("favorites file doesn't exist, creating it");
1714 if ((f = fopen(work_dir, "w")) == NULL)
1715 err(1, "favorites");
1716 fclose(f);
1719 /* cookies */
1720 session = webkit_get_default_session();
1721 snprintf(cookie_file, sizeof cookie_file, "%s/cookies.txt", work_dir);
1722 setup_cookies();
1724 /* proxy */
1725 env_proxy = getenv("http_proxy");
1726 if (env_proxy) {
1727 http_proxy = strdup(env_proxy);
1728 if (http_proxy == NULL)
1729 err(1, "http_proxy");
1731 setup_proxy(http_proxy);
1733 create_canvas();
1735 while (argc) {
1736 create_new_tab(argv[0], focus);
1737 focus = 0;
1739 argc--;
1740 argv++;
1742 if (focus == 1)
1743 create_new_tab(home, 1);
1745 gtk_main();
1747 return (0);