Fix use after free and some other buglets
[xxxterm.git] / xxxterm.c
blob1b8fa83c2836d123a2926cd61c90cd1c5cd0a3b4
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 * download files status
24 * multi letter commands
25 * pre and post counts for commands
26 * fav icon
27 * close tab X
28 * autocompletion on various inputs
29 * create privacy browsing
30 * - encrypted local data
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <err.h>
36 #include <pwd.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <util.h>
41 #include <sys/queue.h>
42 #include <sys/types.h>
43 #include <sys/stat.h>
45 #include <gtk/gtk.h>
46 #include <gdk/gdkkeysyms.h>
47 #include <webkit/webkit.h>
48 #include <libsoup/soup.h>
49 #include <JavaScriptCore/JavaScript.h>
51 static char *version = "$xxxterm$";
53 #define XT_DEBUG
54 /* #define XT_DEBUG */
55 #ifdef XT_DEBUG
56 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
57 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
58 #define XT_D_MOVE 0x0001
59 #define XT_D_KEY 0x0002
60 #define XT_D_TAB 0x0004
61 #define XT_D_URL 0x0008
62 #define XT_D_CMD 0x0010
63 #define XT_D_NAV 0x0020
64 #define XT_D_DOWNLOAD 0x0040
65 #define XT_D_CONFIG 0x0080
66 u_int32_t swm_debug = 0
67 | XT_D_MOVE
68 | XT_D_KEY
69 | XT_D_TAB
70 | XT_D_URL
71 | XT_D_CMD
72 | XT_D_NAV
73 | XT_D_DOWNLOAD
74 | XT_D_CONFIG
76 #else
77 #define DPRINTF(x...)
78 #define DNPRINTF(n,x...)
79 #endif
81 #define LENGTH(x) (sizeof x / sizeof x[0])
82 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
83 ~(GDK_BUTTON1_MASK) & \
84 ~(GDK_BUTTON2_MASK) & \
85 ~(GDK_BUTTON3_MASK) & \
86 ~(GDK_BUTTON4_MASK) & \
87 ~(GDK_BUTTON5_MASK))
89 struct tab {
90 TAILQ_ENTRY(tab) entry;
91 GtkWidget *vbox;
92 GtkWidget *label;
93 GtkWidget *uri_entry;
94 GtkWidget *search_entry;
95 GtkWidget *toolbar;
96 GtkWidget *browser_win;
97 GtkWidget *cmd;
98 GtkToolItem *backward;
99 GtkToolItem *forward;
100 GtkToolItem *stop;
101 guint tab_id;
102 WebKitWebView *wv;
104 /* adjustments for browser */
105 GtkScrollbar *sb_h;
106 GtkScrollbar *sb_v;
107 GtkAdjustment *adjust_h;
108 GtkAdjustment *adjust_v;
110 /* flags */
111 int focus_wv;
112 int ctrl_click;
113 gchar *hover;
115 /* search */
116 char *search_text;
117 int search_forward;
119 /* settings */
120 WebKitWebSettings *settings;
121 int font_size;
122 gchar *user_agent;
124 TAILQ_HEAD(tab_list, tab);
126 struct karg {
127 int i;
128 char *s;
131 /* defines */
132 #define XT_NAME ("XXXTerm")
133 #define XT_DIR (".xxxterm")
134 #define XT_CONF_FILE ("xxxterm.conf")
135 #define XT_FAVS_FILE ("favorites")
136 #define XT_CB_HANDLED (TRUE)
137 #define XT_CB_PASSTHROUGH (FALSE)
139 /* actions */
140 #define XT_MOVE_INVALID (0)
141 #define XT_MOVE_DOWN (1)
142 #define XT_MOVE_UP (2)
143 #define XT_MOVE_BOTTOM (3)
144 #define XT_MOVE_TOP (4)
145 #define XT_MOVE_PAGEDOWN (5)
146 #define XT_MOVE_PAGEUP (6)
147 #define XT_MOVE_LEFT (7)
148 #define XT_MOVE_FARLEFT (8)
149 #define XT_MOVE_RIGHT (9)
150 #define XT_MOVE_FARRIGHT (10)
152 #define XT_TAB_LAST (-4)
153 #define XT_TAB_FIRST (-3)
154 #define XT_TAB_PREV (-2)
155 #define XT_TAB_NEXT (-1)
156 #define XT_TAB_INVALID (0)
157 #define XT_TAB_NEW (1)
158 #define XT_TAB_DELETE (2)
159 #define XT_TAB_DELQUIT (3)
160 #define XT_TAB_OPEN (4)
162 #define XT_NAV_INVALID (0)
163 #define XT_NAV_BACK (1)
164 #define XT_NAV_FORWARD (2)
165 #define XT_NAV_RELOAD (3)
167 #define XT_FOCUS_INVALID (0)
168 #define XT_FOCUS_URI (1)
169 #define XT_FOCUS_SEARCH (2)
171 #define XT_SEARCH_INVALID (0)
172 #define XT_SEARCH_NEXT (1)
173 #define XT_SEARCH_PREV (2)
175 #define XT_FONT_SET (0)
177 /* globals */
178 extern char *__progname;
179 struct passwd *pwd;
180 GtkWidget *main_window;
181 GtkNotebook *notebook;
182 struct tab_list tabs;
184 /* mime types */
185 struct mime_type {
186 char *mt_type;
187 char *mt_action;
188 int mt_default;
189 TAILQ_ENTRY(mime_type) entry;
191 TAILQ_HEAD(mime_type_list, mime_type);
193 /* settings */
194 int showtabs = 1; /* show tabs on notebook */
195 int showurl = 1; /* show url toolbar on notebook */
196 int tabless = 0; /* allow only 1 tab */
197 int ctrl_click_focus = 0; /* ctrl click gets focus */
198 int cookies_enabled = 1; /* enable cookies */
199 int read_only_cookies = 0; /* enable to not write cookies */
200 int enable_scripts = 0;
201 int enable_plugins = 0;
202 int default_font_size = 12;
203 int fancy_bar = 1; /* fancy toolbar */
205 char *home = "http://www.peereboom.us";
206 char *search_string = NULL;
207 char *http_proxy = NULL;
208 SoupURI *proxy_uri = NULL;
209 char work_dir[PATH_MAX];
210 char cookie_file[PATH_MAX];
211 char download_dir[PATH_MAX];
212 SoupSession *session;
213 SoupCookieJar *cookiejar;
215 struct mime_type_list mtl;
217 /* protos */
218 void create_new_tab(char *, int);
219 void delete_tab(struct tab *);
220 void adjustfont_webkit(struct tab *, int);
222 struct valid_url_types {
223 char *type;
224 } vut[] = {
225 { "http://" },
226 { "https://" },
227 { "ftp://" },
228 { "file://" },
232 valid_url_type(char *url)
234 int i;
236 for (i = 0; i < LENGTH(vut); i++)
237 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
238 return (0);
240 return (1);
243 char *
244 guess_url_type(char *url_in)
246 struct stat sb;
247 char *url_out = NULL;
249 /* XXX not sure about this heuristic */
250 if (stat(url_in, &sb) == 0) {
251 if (asprintf(&url_out, "file://%s", url_in) == -1)
252 err(1, "aprintf file");
253 } else {
254 /* guess http */
255 if (asprintf(&url_out, "http://%s", url_in) == -1)
256 err(1, "aprintf http");
259 if (url_out == NULL)
260 err(1, "asprintf pointer");
262 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
264 return (url_out);
267 void
268 add_mime_type(char *line)
270 char *mime_type;
271 char *l = NULL;
272 struct mime_type *m;
274 /* XXX this could be smarter */
276 if (line == NULL)
277 errx(1, "add_mime_type");
278 l = line;
280 m = malloc(sizeof(*m));
281 if (m == NULL)
282 err(1, "add_mime_type: malloc");
284 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL)
285 errx(1, "add_mime_type: invalid mime_type");
287 if (mime_type[strlen(mime_type) - 1] == '*') {
288 mime_type[strlen(mime_type) - 1] = '\0';
289 m->mt_default = 1;
290 } else
291 m->mt_default = 0;
293 if (strlen(mime_type) == 0 || strlen(l) == 0)
294 errx(1, "add_mime_type: invalid mime_type");
296 m->mt_type = strdup(mime_type);
297 if (m->mt_type == NULL)
298 err(1, "add_mime_type: malloc type");
300 m->mt_action = strdup(l);
301 if (m->mt_action == NULL)
302 err(1, "add_mime_type: malloc action");
304 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
305 m->mt_type, m->mt_action, m->mt_default);
307 TAILQ_INSERT_TAIL(&mtl, m, entry);
310 struct mime_type *
311 find_mime_type(char *mime_type)
313 struct mime_type *m, *def = NULL, *rv = NULL;
315 TAILQ_FOREACH(m, &mtl, entry) {
316 if (m->mt_default &&
317 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
318 def = m;
320 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
321 rv = m;
322 break;
326 if (rv == NULL)
327 rv = def;
329 return (rv);
332 #define WS "\n= \t"
333 void
334 config_parse(char *filename)
336 FILE *config;
337 char *line, *cp, *var, *val;
338 size_t len, lineno = 0;
340 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
342 TAILQ_INIT(&mtl);
344 if (filename == NULL)
345 return;
347 if ((config = fopen(filename, "r")) == NULL) {
348 warn("config_parse: cannot open %s", filename);
349 return;
352 for (;;) {
353 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
354 if (feof(config))
355 break;
357 cp = line;
358 cp += (long)strspn(cp, WS);
359 if (cp[0] == '\0') {
360 /* empty line */
361 free(line);
362 continue;
365 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
366 break;
368 cp += (long)strspn(cp, WS);
370 if ((val = strsep(&cp, "\0")) == NULL)
371 break;
373 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
375 /* get settings */
376 if (!strcmp(var, "home"))
377 home = strdup(val);
378 else if (!strcmp(var, "ctrl_click_focus"))
379 ctrl_click_focus = atoi(val);
380 else if (!strcmp(var, "read_only_cookies"))
381 read_only_cookies = atoi(val);
382 else if (!strcmp(var, "cookies_enabled"))
383 cookies_enabled = atoi(val);
384 else if (!strcmp(var, "enable_scripts"))
385 enable_scripts = atoi(val);
386 else if (!strcmp(var, "enable_plugins"))
387 enable_plugins = atoi(val);
388 else if (!strcmp(var, "default_font_size"))
389 default_font_size = atoi(val);
390 else if (!strcmp(var, "fancy_bar"))
391 fancy_bar = atoi(val);
392 else if (!strcmp(var, "mime_type"))
393 add_mime_type(val);
394 else if (!strcmp(var, "http_proxy")) {
395 if (http_proxy)
396 free(http_proxy);
397 http_proxy = strdup(val);
398 if (http_proxy == NULL)
399 err(1, "http_proxy");
400 } else if (!strcmp(var, "search_string")) {
401 if (search_string)
402 free(search_string);
403 search_string = strdup(val);
404 if (search_string == NULL)
405 err(1, "search_string");
406 } else if (!strcmp(var, "download_dir")) {
407 if (val[0] == '~')
408 snprintf(download_dir, sizeof download_dir,
409 "%s/%s", pwd->pw_dir, &val[1]);
410 else
411 strlcpy(download_dir, val, sizeof download_dir);
412 } else
413 errx(1, "invalid conf file entry: %s=%s", var, val);
415 free(line);
418 fclose(config);
421 quit(struct tab *t, struct karg *args)
423 gtk_main_quit();
425 return (1);
429 focus(struct tab *t, struct karg *args)
431 if (t == NULL || args == NULL)
432 errx(1, "focus");
434 if (args->i == XT_FOCUS_URI)
435 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
436 else if (args->i == XT_FOCUS_SEARCH)
437 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
439 return (0);
443 help(struct tab *t, struct karg *args)
445 if (t == NULL)
446 errx(1, "help");
448 webkit_web_view_load_string(t->wv,
449 "<html><body><h1>XXXTerm</h1></body></html>",
450 NULL,
451 NULL,
452 NULL);
454 return (0);
458 favorites(struct tab *t, struct karg *args)
460 char file[PATH_MAX];
461 FILE *f, *h;
462 char *uri = NULL, *title = NULL;
463 size_t len, lineno = 0;
464 int i, failed = 0;
466 if (t == NULL)
467 errx(1, "favorites");
469 /* XXX run a digest over the favorites file instead of always generating it */
471 /* open favorites */
472 snprintf(file, sizeof file, "%s/%s/%s",
473 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
474 if ((f = fopen(file, "r")) == NULL) {
475 warn("favorites");
476 return (1);
479 /* open favorites html */
480 snprintf(file, sizeof file, "%s/%s/%s.html",
481 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
482 if ((h = fopen(file, "w+")) == NULL) {
483 warn("favorites.html");
484 return (1);
487 fprintf(h, "<html><body>Favorites:<p>\n<ol>\n");
489 for (i = 1;;) {
490 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
491 if (feof(f))
492 break;
493 if (strlen(title) == 0)
494 continue;
496 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
497 if (feof(f)) {
498 failed = 1;
499 break;
502 fprintf(h, "<li><a href=\"%s\">%s</a><br>\n", uri, title);
504 free(uri);
505 uri = NULL;
506 free(title);
507 title = NULL;
508 i++;
511 if (uri)
512 free(uri);
513 if (title)
514 free(title);
516 fprintf(h, "</ol></body></html>");
517 fclose(f);
518 fclose(h);
520 if (failed) {
521 webkit_web_view_load_string(t->wv,
522 "<html><body>Invalid favorites file</body></html>",
523 NULL,
524 NULL,
525 NULL);
526 } else {
527 snprintf(file, sizeof file, "file://%s/%s/%s.html",
528 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
529 webkit_web_view_load_uri(t->wv, file);
532 return (0);
536 favadd(struct tab *t, struct karg *args)
538 char file[PATH_MAX];
539 FILE *f;
540 WebKitWebFrame *frame;
541 const gchar *uri, *title;
543 if (t == NULL)
544 errx(1, "favadd");
546 snprintf(file, sizeof file, "%s/%s/%s",
547 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
548 if ((f = fopen(file, "r+")) == NULL) {
549 warn("favorites");
550 return (1);
552 if (fseeko(f, 0, SEEK_END) == -1)
553 err(1, "fseeko");
555 title = webkit_web_view_get_title(t->wv);
556 frame = webkit_web_view_get_main_frame(t->wv);
557 uri = webkit_web_frame_get_uri(frame);
558 if (title == NULL)
559 title = uri;
561 if (title == NULL || uri == NULL) {
562 webkit_web_view_load_string(t->wv,
563 "<html><body>can't add page to favorites</body></html>",
564 NULL,
565 NULL,
566 NULL);
567 goto done;
570 fprintf(f, "\n%s\n%s", title, uri);
571 done:
572 fclose(f);
574 return (0);
578 navaction(struct tab *t, struct karg *args)
580 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
581 t->tab_id, args->i);
583 switch (args->i) {
584 case XT_NAV_BACK:
585 webkit_web_view_go_back(t->wv);
586 break;
587 case XT_NAV_FORWARD:
588 webkit_web_view_go_forward(t->wv);
589 break;
590 case XT_NAV_RELOAD:
591 webkit_web_view_reload(t->wv);
592 break;
594 return (XT_CB_PASSTHROUGH);
598 move(struct tab *t, struct karg *args)
600 GtkAdjustment *adjust;
601 double pi, si, pos, ps, upper, lower, max;
603 switch (args->i) {
604 case XT_MOVE_DOWN:
605 case XT_MOVE_UP:
606 case XT_MOVE_BOTTOM:
607 case XT_MOVE_TOP:
608 case XT_MOVE_PAGEDOWN:
609 case XT_MOVE_PAGEUP:
610 adjust = t->adjust_v;
611 break;
612 default:
613 adjust = t->adjust_h;
614 break;
617 pos = gtk_adjustment_get_value(adjust);
618 ps = gtk_adjustment_get_page_size(adjust);
619 upper = gtk_adjustment_get_upper(adjust);
620 lower = gtk_adjustment_get_lower(adjust);
621 si = gtk_adjustment_get_step_increment(adjust);
622 pi = gtk_adjustment_get_page_increment(adjust);
623 max = upper - ps;
625 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
626 "max %f si %f pi %f\n",
627 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
628 pos, ps, upper, lower, max, si, pi);
630 switch (args->i) {
631 case XT_MOVE_DOWN:
632 case XT_MOVE_RIGHT:
633 pos += si;
634 gtk_adjustment_set_value(adjust, MIN(pos, max));
635 break;
636 case XT_MOVE_UP:
637 case XT_MOVE_LEFT:
638 pos -= si;
639 gtk_adjustment_set_value(adjust, MAX(pos, lower));
640 break;
641 case XT_MOVE_BOTTOM:
642 case XT_MOVE_FARRIGHT:
643 gtk_adjustment_set_value(adjust, max);
644 break;
645 case XT_MOVE_TOP:
646 case XT_MOVE_FARLEFT:
647 gtk_adjustment_set_value(adjust, lower);
648 break;
649 case XT_MOVE_PAGEDOWN:
650 pos += pi;
651 gtk_adjustment_set_value(adjust, MIN(pos, max));
652 break;
653 case XT_MOVE_PAGEUP:
654 pos -= pi;
655 gtk_adjustment_set_value(adjust, MAX(pos, lower));
656 break;
657 default:
658 return (XT_CB_PASSTHROUGH);
661 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
663 return (XT_CB_HANDLED);
666 char *
667 getparams(char *cmd, char *cmp)
669 char *rv = NULL;
671 if (cmd && cmp) {
672 if (!strncmp(cmd, cmp, strlen(cmp))) {
673 rv = cmd + strlen(cmp);
674 while (*rv == ' ')
675 rv++;
676 if (strlen(rv) == 0)
677 rv = NULL;
681 return (rv);
685 tabaction(struct tab *t, struct karg *args)
687 int rv = XT_CB_HANDLED;
688 char *url = NULL, *newuri = NULL;
690 DNPRINTF(XT_D_TAB, "tabaction: %p %d %d\n", t, args->i, t->focus_wv);
692 if (t == NULL)
693 return (XT_CB_PASSTHROUGH);
695 switch (args->i) {
696 case XT_TAB_NEW:
697 if ((url = getparams(args->s, "tabnew")))
698 create_new_tab(url, 1);
699 else
700 create_new_tab(NULL, 1);
701 break;
702 case XT_TAB_DELETE:
703 delete_tab(t);
704 break;
705 case XT_TAB_DELQUIT:
706 if (gtk_notebook_get_n_pages(notebook) > 1)
707 delete_tab(t);
708 else
709 quit(t, args);
710 break;
711 case XT_TAB_OPEN:
712 if ((url = getparams(args->s, "open")) ||
713 ((url = getparams(args->s, "op"))) ||
714 ((url = getparams(args->s, "o"))))
716 else {
717 rv = XT_CB_PASSTHROUGH;
718 goto done;
721 if (valid_url_type(url)) {
722 newuri = guess_url_type(url);
723 url = newuri;
725 webkit_web_view_load_uri(t->wv, url);
726 if (newuri)
727 free(newuri);
728 break;
729 default:
730 rv = XT_CB_PASSTHROUGH;
731 goto done;
734 done:
735 if (args->s) {
736 free(args->s);
737 args->s = NULL;
740 return (rv);
744 resizetab(struct tab *t, struct karg *args)
746 if (t == NULL || args == NULL)
747 errx(1, "resizetab");
749 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
750 t->tab_id, args->i);
752 adjustfont_webkit(t, args->i);
754 return (XT_CB_HANDLED);
758 movetab(struct tab *t, struct karg *args)
760 struct tab *tt;
761 int x;
763 if (t == NULL || args == NULL)
764 errx(1, "movetab");
766 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
767 t->tab_id, args->i);
769 if (args->i == XT_TAB_INVALID)
770 return (XT_CB_PASSTHROUGH);
772 if (args->i < XT_TAB_INVALID) {
773 /* next or previous tab */
774 if (TAILQ_EMPTY(&tabs))
775 return (XT_CB_PASSTHROUGH);
777 switch (args->i) {
778 case XT_TAB_NEXT:
779 gtk_notebook_next_page(notebook);
780 break;
781 case XT_TAB_PREV:
782 gtk_notebook_prev_page(notebook);
783 break;
784 case XT_TAB_FIRST:
785 gtk_notebook_set_current_page(notebook, 0);
786 break;
787 case XT_TAB_LAST:
788 gtk_notebook_set_current_page(notebook, -1);
789 break;
790 default:
791 return (XT_CB_PASSTHROUGH);
794 return (XT_CB_HANDLED);
797 /* jump to tab */
798 x = args->i - 1;
799 if (t->tab_id == x) {
800 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
801 return (XT_CB_HANDLED);
804 TAILQ_FOREACH(tt, &tabs, entry) {
805 if (tt->tab_id == x) {
806 gtk_notebook_set_current_page(notebook, x);
807 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
808 if (tt->focus_wv)
809 gtk_widget_grab_focus(GTK_WIDGET(tt->wv));
813 return (XT_CB_HANDLED);
817 command(struct tab *t, struct karg *args)
819 char *s = NULL;
820 GdkColor color;
822 if (t == NULL || args == NULL)
823 errx(1, "command");
825 if (args->i == '/')
826 s = "/";
827 else if (args->i == '?')
828 s = "?";
829 else if (args->i == ':')
830 s = ":";
831 else {
832 warnx("invalid command %c\n", args->i);
833 return (XT_CB_PASSTHROUGH);
836 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
838 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
839 gdk_color_parse("white", &color);
840 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
841 gtk_widget_show(t->cmd);
842 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
843 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
845 return (XT_CB_HANDLED);
849 search(struct tab *t, struct karg *args)
851 gboolean d;
853 if (t == NULL || args == NULL)
854 errx(1, "search");
855 if (t->search_text == NULL)
856 return (XT_CB_PASSTHROUGH);
858 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
859 t->tab_id, args->i, t->search_forward, t->search_text);
861 switch (args->i) {
862 case XT_SEARCH_NEXT:
863 d = t->search_forward;
864 break;
865 case XT_SEARCH_PREV:
866 d = !t->search_forward;
867 break;
868 default:
869 return (XT_CB_PASSTHROUGH);
872 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
874 return (XT_CB_HANDLED);
878 mnprintf(char **buf, int *len, char *fmt, ...)
880 int x, old_len;
881 va_list ap;
883 va_start(ap, fmt);
885 old_len = *len;
886 x = vsnprintf(*buf, *len, fmt, ap);
887 if (x == -1)
888 err(1, "mnprintf");
889 if (old_len < x)
890 errx(1, "mnprintf: buffer overflow");
892 *buf += x;
893 *len -= x;
895 va_end(ap);
897 return (0);
901 set(struct tab *t, struct karg *args)
903 struct mime_type *m;
904 char b[16 * 1024], *s, *pars;
905 int l;
907 if (t == NULL || args == NULL)
908 errx(1, "set");
910 DNPRINTF(XT_D_CMD, "set: tab %d\n",
911 t->tab_id);
913 s = b;
914 l = sizeof b;
916 if ((pars = getparams(args->s, "set")) == NULL) {
917 mnprintf(&s, &l, "<html><body><pre>");
918 mnprintf(&s, &l, "ctrl_click_focus\t= %d<br>", ctrl_click_focus);
919 mnprintf(&s, &l, "cookies_enabled\t\t= %d<br>", cookies_enabled);
920 mnprintf(&s, &l, "default_font_size\t= %d<br>", default_font_size);
921 mnprintf(&s, &l, "enable_plugins\t\t= %d<br>", enable_plugins);
922 mnprintf(&s, &l, "enable_scripts\t\t= %d<br>", enable_scripts);
923 mnprintf(&s, &l, "fancy_bar\t\t= %d<br>", fancy_bar);
924 mnprintf(&s, &l, "home\t\t\t= %s<br>", home);
925 TAILQ_FOREACH(m, &mtl, entry) {
926 mnprintf(&s, &l, "mime_type\t\t= %s%s,%s<br>",
927 m->mt_type, m->mt_default ? "*" : "", m->mt_action);
929 mnprintf(&s, &l, "proxy_uri\t\t= %s<br>", proxy_uri);
930 mnprintf(&s, &l, "read_only_cookies\t= %d<br>", read_only_cookies);
931 mnprintf(&s, &l, "search_string\t\t= %s<br>", search_string);
932 mnprintf(&s, &l, "showurl\t\t\t= %d<br>", showurl);
933 mnprintf(&s, &l, "showtabs\t\t= %d<br>", showtabs);
934 mnprintf(&s, &l, "tabless\t\t\t= %d<br>", tabless);
935 mnprintf(&s, &l, "download_dir\t\t= %s<br>", download_dir);
936 mnprintf(&s, &l, "</pre></body></html>");
938 webkit_web_view_load_string(t->wv,
940 NULL,
941 NULL,
942 "about:config");
943 goto done;
946 /* XXX this sucks donkey balls and is a POC only */
947 int x;
948 char *e;
949 if (!strncmp(pars, "enable_scripts ", strlen("enable_scripts"))) {
950 s = pars + strlen("enable_scripts");
951 x = strtol(s, &e, 10);
952 if (s[0] == '\0' || *e != '\0')
953 webkit_web_view_load_string(t->wv,
954 "<html><body>invalid value</body></html>",
955 NULL,
956 NULL,
957 "about:error");
959 enable_scripts = x;
960 g_object_set((GObject *)t->settings,
961 "enable-scripts", enable_scripts, NULL);
962 webkit_web_view_set_settings(t->wv, t->settings);
965 done:
966 if (args->s) {
967 free(args->s);
968 args->s = NULL;
971 return (XT_CB_PASSTHROUGH);
974 /* inherent to GTK not all keys will be caught at all times */
975 struct key {
976 guint mask;
977 guint modkey;
978 guint key;
979 int (*func)(struct tab *, struct karg *);
980 struct karg arg;
981 } keys[] = {
982 { 0, 0, GDK_slash, command, {.i = '/'} },
983 { GDK_SHIFT_MASK, 0, GDK_question, command, {.i = '?'} },
984 { GDK_SHIFT_MASK, 0, GDK_colon, command, {.i = ':'} },
985 { GDK_CONTROL_MASK, 0, GDK_q, quit, {0} },
987 /* search */
988 { 0, 0, GDK_n, search, {.i = XT_SEARCH_NEXT} },
989 { GDK_SHIFT_MASK, 0, GDK_N, search, {.i = XT_SEARCH_PREV} },
991 /* focus */
992 { 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} },
993 { 0, 0, GDK_F7, focus, {.i = XT_FOCUS_SEARCH} },
995 /* navigation */
996 { 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
997 { GDK_MOD1_MASK, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
998 { GDK_SHIFT_MASK, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
999 { GDK_MOD1_MASK, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
1000 { 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} },
1001 { GDK_CONTROL_MASK, 0, GDK_r, navaction, {.i = XT_NAV_RELOAD} },
1002 { GDK_CONTROL_MASK, 0, GDK_l, navaction, {.i = XT_NAV_RELOAD} },
1004 /* vertical movement */
1005 { 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
1006 { 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
1007 { 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
1008 { 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
1009 { GDK_SHIFT_MASK, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
1010 { 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
1011 { 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
1012 { 0, GDK_g, GDK_g, move, {.i = XT_MOVE_TOP} }, /* XXX make this work */
1013 { 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
1014 { GDK_CONTROL_MASK, 0, GDK_f, move, {.i = XT_MOVE_PAGEDOWN} },
1015 { 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
1016 { 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
1017 { GDK_CONTROL_MASK, 0, GDK_b, move, {.i = XT_MOVE_PAGEUP} },
1018 /* horizontal movement */
1019 { 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
1020 { 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
1021 { 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
1022 { 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
1023 { GDK_SHIFT_MASK, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
1024 { 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
1026 /* tabs */
1027 { GDK_CONTROL_MASK, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
1028 { GDK_CONTROL_MASK, 0, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
1029 { GDK_CONTROL_MASK, 0, GDK_1, movetab, {.i = 1} },
1030 { GDK_CONTROL_MASK, 0, GDK_2, movetab, {.i = 2} },
1031 { GDK_CONTROL_MASK, 0, GDK_3, movetab, {.i = 3} },
1032 { GDK_CONTROL_MASK, 0, GDK_4, movetab, {.i = 4} },
1033 { GDK_CONTROL_MASK, 0, GDK_5, movetab, {.i = 5} },
1034 { GDK_CONTROL_MASK, 0, GDK_6, movetab, {.i = 6} },
1035 { GDK_CONTROL_MASK, 0, GDK_7, movetab, {.i = 7} },
1036 { GDK_CONTROL_MASK, 0, GDK_8, movetab, {.i = 8} },
1037 { GDK_CONTROL_MASK, 0, GDK_9, movetab, {.i = 9} },
1038 { GDK_CONTROL_MASK, 0, GDK_0, movetab, {.i = 10} },
1039 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_less, movetab, {.i = XT_TAB_FIRST} },
1040 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_greater, movetab, {.i = XT_TAB_LAST} },
1041 { GDK_CONTROL_MASK, 0, GDK_minus, resizetab, {.i = -1} },
1042 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_plus, resizetab, {.i = 1} },
1043 { GDK_CONTROL_MASK, 0, GDK_equal, resizetab, {.i = 1} },
1046 struct cmd {
1047 char *cmd;
1048 int params;
1049 int (*func)(struct tab *, struct karg *);
1050 struct karg arg;
1051 } cmds[] = {
1052 { "q!", 0, quit, {0} },
1053 { "qa", 0, quit, {0} },
1054 { "qa!", 0, quit, {0} },
1055 { "help", 0, help, {0} },
1057 /* favorites */
1058 { "fav", 0, favorites, {0} },
1059 { "favadd", 0, favadd, {0} },
1061 /* tabs */
1062 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
1063 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
1064 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
1065 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
1066 { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} },
1067 { "tabe", 1, tabaction, {.i = XT_TAB_NEW} },
1068 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
1069 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} },
1070 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
1071 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
1072 /* XXX add count to these commands */
1073 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST} },
1074 { "tabfir", 0, movetab, {.i = XT_TAB_FIRST} },
1075 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST} },
1076 { "tabr", 0, movetab, {.i = XT_TAB_FIRST} },
1077 { "tablast", 0, movetab, {.i = XT_TAB_LAST} },
1078 { "tabl", 0, movetab, {.i = XT_TAB_LAST} },
1079 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
1080 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
1081 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
1082 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
1084 /* settings */
1085 { "set", 1, set, {0} },
1088 void
1089 focus_uri_entry_cb(GtkWidget* w, GtkDirectionType direction, struct tab *t)
1091 DNPRINTF(XT_D_URL, "focus_uri_entry_cb: tab %d focus_wv %d\n",
1092 t->tab_id, t->focus_wv);
1094 if (t == NULL)
1095 errx(1, "focus_uri_entry_cb");
1097 /* focus on wv instead */
1098 if (t->focus_wv)
1099 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1102 void
1103 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
1105 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
1106 char *newuri = NULL;
1108 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
1110 if (t == NULL)
1111 errx(1, "activate_uri_entry_cb");
1113 if (uri == NULL)
1114 errx(1, "uri");
1116 if (valid_url_type((char *)uri)) {
1117 newuri = guess_url_type((char *)uri);
1118 uri = newuri;
1121 webkit_web_view_load_uri(t->wv, uri);
1122 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1124 if (newuri)
1125 free(newuri);
1128 void
1129 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
1131 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
1132 char *newuri = NULL;
1134 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
1136 if (t == NULL)
1137 errx(1, "activate_search_entry_cb");
1139 if (search_string == NULL) {
1140 warnx("no search_string");
1141 return;
1144 if (asprintf(&newuri, search_string, search) == -1)
1145 err(1, "activate_search_entry_cb");
1147 webkit_web_view_load_uri(t->wv, newuri);
1148 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1150 if (newuri)
1151 free(newuri);
1154 void
1155 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
1157 GdkColor color;
1158 WebKitWebFrame *frame;
1159 const gchar *uri;
1161 if (t == NULL)
1162 errx(1, "notify_load_status_cb");
1164 switch (webkit_web_view_get_load_status(wview)) {
1165 case WEBKIT_LOAD_COMMITTED:
1166 frame = webkit_web_view_get_main_frame(wview);
1167 uri = webkit_web_frame_get_uri(frame);
1168 if (uri)
1169 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
1171 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
1172 t->focus_wv = 1;
1174 /* take focus if we are visible */
1175 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
1176 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1178 /* color uri_entry */
1179 if (uri && !strncmp(uri, "https://", strlen("https://")))
1180 gdk_color_parse("green", &color);
1181 else
1182 gdk_color_parse("white", &color);
1183 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
1185 break;
1187 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
1188 uri = webkit_web_view_get_title(wview);
1189 if (uri == NULL) {
1190 frame = webkit_web_view_get_main_frame(wview);
1191 uri = webkit_web_frame_get_uri(frame);
1193 gtk_label_set_text(GTK_LABEL(t->label), uri);
1194 gtk_window_set_title(GTK_WINDOW(main_window), uri);
1196 break;
1198 case WEBKIT_LOAD_PROVISIONAL:
1199 case WEBKIT_LOAD_FINISHED:
1200 #if WEBKIT_CHECK_VERSION(1, 1, 18)
1201 case WEBKIT_LOAD_FAILED:
1202 #endif
1203 default:
1204 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
1205 break;
1208 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
1209 webkit_web_view_can_go_back(wview));
1211 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
1212 webkit_web_view_can_go_forward(wview));
1216 webview_nw_cb(WebKitWebView *wv, WebKitWebFrame *wf,
1217 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
1218 WebKitWebPolicyDecision *pd, struct tab *t)
1220 char *uri;
1222 if (t == NULL)
1223 errx(1, "webview_nw_cb");
1225 DNPRINTF(XT_D_NAV, "webview_nw_cb: %s\n",
1226 webkit_network_request_get_uri(request));
1228 /* open in current tab */
1229 uri = (char *)webkit_network_request_get_uri(request);
1230 webkit_web_view_load_uri(t->wv, uri);
1231 webkit_web_policy_decision_ignore(pd);
1233 return (TRUE); /* we made the decission */
1237 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
1238 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
1239 WebKitWebPolicyDecision *pd, struct tab *t)
1241 char *uri;
1243 if (t == NULL)
1244 errx(1, "webview_npd_cb");
1246 DNPRINTF(XT_D_NAV, "webview_npd_cb: %s\n",
1247 webkit_network_request_get_uri(request));
1249 uri = (char *)webkit_network_request_get_uri(request);
1250 if (t->ctrl_click) {
1251 t->ctrl_click = 0;
1252 create_new_tab(uri, ctrl_click_focus);
1253 webkit_web_policy_decision_ignore(pd);
1254 return (TRUE); /* we made the decission */
1257 webkit_web_policy_decision_use(pd);
1258 return (TRUE); /* we made the decission */
1261 WebKitWebView *
1262 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
1264 if (t == NULL)
1265 errx(1, "webview_cwv_cb");
1267 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
1268 webkit_web_view_get_uri(wv));
1270 return (wv);
1274 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
1276 /* we can not eat the event without throwing gtk off so defer it */
1278 /* catch ctrl click */
1279 if (e->type == GDK_BUTTON_RELEASE &&
1280 CLEAN(e->state) == GDK_CONTROL_MASK)
1281 t->ctrl_click = 1;
1282 else
1283 t->ctrl_click = 0;
1285 return (XT_CB_PASSTHROUGH);
1289 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
1291 struct mime_type *m;
1293 m = find_mime_type(mime_type);
1294 if (m == NULL)
1295 return (1);
1297 switch (fork()) {
1298 case -1:
1299 err(1, "fork");
1300 /* NOTREACHED */
1301 case 0:
1302 break;
1303 default:
1304 return (0);
1307 /* child */
1308 execlp(m->mt_action, m->mt_action,
1309 webkit_network_request_get_uri(request), (void *)NULL);
1311 _exit(0);
1313 /* NOTREACHED */
1314 return (0);
1318 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
1319 WebKitNetworkRequest *request, char *mime_type,
1320 WebKitWebPolicyDecision *decision, struct tab *t)
1322 if (t == NULL)
1323 errx(1, "webview_mimetype_cb");
1325 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
1326 t->tab_id, mime_type);
1328 if (run_mimehandler(t, mime_type, request) == 0) {
1329 webkit_web_policy_decision_ignore(decision);
1330 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1331 return (TRUE);
1334 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
1335 webkit_web_policy_decision_download(decision);
1336 return (TRUE);
1339 return (FALSE);
1343 webview_download_cb(WebKitWebView *wv, WebKitDownload *download, struct tab *t)
1345 const gchar *filename;
1346 char *uri = NULL;
1348 if (download == NULL || t == NULL)
1349 errx(1, "webview_download_cb: invalid pointers");
1351 filename = webkit_download_get_suggested_filename(download);
1352 if (filename == NULL)
1353 return (FALSE); /* abort download */
1355 if (asprintf(&uri, "file://%s/%s", download_dir, filename) == -1)
1356 err(1, "aprintf uri");
1358 DNPRINTF(XT_D_DOWNLOAD, "webview_download_cb: tab %d filename %s "
1359 "local %s\n",
1360 t->tab_id, filename, uri);
1362 webkit_download_set_destination_uri(download, uri);
1364 if (uri)
1365 free(uri);
1367 webkit_download_start(download);
1369 return (TRUE); /* start download */
1372 /* XXX currently unused */
1373 void
1374 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
1376 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
1378 if (t == NULL)
1379 errx(1, "webview_hover_cb");
1381 if (uri) {
1382 if (t->hover) {
1383 free(t->hover);
1384 t->hover = NULL;
1386 t->hover = strdup(uri);
1387 } else if (t->hover) {
1388 free(t->hover);
1389 t->hover = NULL;
1394 webview_keypress_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
1396 int i;
1398 /* don't use w directly; use t->whatever instead */
1400 if (t == NULL)
1401 errx(1, "webview_keypress_cb");
1403 DNPRINTF(XT_D_KEY, "webview_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
1404 e->keyval, e->state, t);
1406 for (i = 0; i < LENGTH(keys); i++)
1407 if (e->keyval == keys[i].key && CLEAN(e->state) ==
1408 keys[i].mask) {
1409 keys[i].func(t, &keys[i].arg);
1410 return (XT_CB_HANDLED);
1413 return (XT_CB_PASSTHROUGH);
1417 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
1419 const gchar *c = gtk_entry_get_text(w);
1420 GdkColor color;
1421 int forward = TRUE;
1423 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
1424 e->keyval, e->state, t);
1426 if (t == NULL)
1427 errx(1, "cmd_keyrelease_cb");
1429 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
1430 e->keyval, e->state, t);
1432 if (c[0] == ':')
1433 goto done;
1434 if (strlen(c) == 1)
1435 goto done;
1437 if (c[0] == '/')
1438 forward = TRUE;
1439 else if (c[0] == '?')
1440 forward = FALSE;
1441 else
1442 goto done;
1444 /* search */
1445 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
1446 FALSE) {
1447 /* not found, mark red */
1448 gdk_color_parse("red", &color);
1449 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
1450 /* unmark and remove selection */
1451 webkit_web_view_unmark_text_matches(t->wv);
1452 /* my kingdom for a way to unselect text in webview */
1453 } else {
1454 /* found, highlight all */
1455 webkit_web_view_unmark_text_matches(t->wv);
1456 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
1457 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
1458 gdk_color_parse("white", &color);
1459 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
1461 done:
1462 return (XT_CB_PASSTHROUGH);
1465 #if 0
1467 cmd_complete(struct tab *t, char *s)
1469 int i;
1470 GtkEntry *w = GTK_ENTRY(t->cmd);
1472 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", s);
1474 for (i = 0; i < LENGTH(cmds); i++) {
1475 if (!strncasecmp(cmds[i].cmd, s, strlen(s))) {
1476 fprintf(stderr, "match %s %d\n", cmds[i].cmd, strcasecmp(cmds[i].cmd, s));
1477 #if 0
1478 gtk_entry_set_text(w, ":");
1479 gtk_entry_append_text(w, cmds[i].cmd);
1480 gtk_editable_set_position(GTK_EDITABLE(w), -1);
1481 #endif
1485 return (0);
1487 #endif
1490 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
1492 int rv = XT_CB_HANDLED;
1493 const gchar *c = gtk_entry_get_text(w);
1495 if (t == NULL)
1496 errx(1, "cmd_keypress_cb");
1498 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
1499 e->keyval, e->state, t);
1501 /* sanity */
1502 if (c == NULL)
1503 e->keyval = GDK_Escape;
1504 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
1505 e->keyval = GDK_Escape;
1507 switch (e->keyval) {
1508 #if 0
1509 case GDK_Tab:
1510 if (c[0] != ':')
1511 goto done;
1513 if (strchr (c, ' ')) {
1514 /* par completion */
1515 fprintf(stderr, "completeme par\n");
1516 goto done;
1519 cmd_complete(t, (char *)&c[1]);
1521 goto done;
1522 #endif
1523 case GDK_BackSpace:
1524 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
1525 break;
1526 /* FALLTHROUGH */
1527 case GDK_Escape:
1528 gtk_widget_hide(t->cmd);
1529 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1530 goto done;
1533 rv = XT_CB_PASSTHROUGH;
1534 done:
1535 return (rv);
1539 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
1541 if (t == NULL)
1542 errx(1, "cmd_focusout_cb");
1544 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d focus_wv %d\n",
1545 t->tab_id, t->focus_wv);
1547 /* abort command when losing focus */
1548 gtk_widget_hide(t->cmd);
1549 if (t->focus_wv)
1550 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1551 else
1552 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
1554 return (XT_CB_PASSTHROUGH);
1557 void
1558 cmd_activate_cb(GtkEntry *entry, struct tab *t)
1560 int i;
1561 char *s;
1562 const gchar *c = gtk_entry_get_text(entry);
1564 if (t == NULL)
1565 errx(1, "cmd_activate_cb");
1567 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
1569 /* sanity */
1570 if (c == NULL)
1571 goto done;
1572 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
1573 goto done;
1574 if (strlen(c) < 2)
1575 goto done;
1576 s = (char *)&c[1];
1578 if (c[0] == '/' || c[0] == '?') {
1579 if (t->search_text) {
1580 free(t->search_text);
1581 t->search_text = NULL;
1584 t->search_text = strdup(s);
1585 if (t->search_text == NULL)
1586 err(1, "search_text");
1588 t->search_forward = c[0] == '/';
1590 goto done;
1593 for (i = 0; i < LENGTH(cmds); i++)
1594 if (cmds[i].params) {
1595 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
1596 cmds[i].arg.s = strdup(s);
1597 cmds[i].func(t, &cmds[i].arg);
1599 } else {
1600 if (!strcmp(s, cmds[i].cmd))
1601 cmds[i].func(t, &cmds[i].arg);
1604 done:
1605 gtk_widget_hide(t->cmd);
1608 void
1609 backward_cb(GtkWidget *w, struct tab *t)
1611 if (t == NULL)
1612 errx(1, "backward_cb");
1614 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
1616 webkit_web_view_go_back(t->wv);
1619 void
1620 forward_cb(GtkWidget *w, struct tab *t)
1622 if (t == NULL)
1623 errx(1, "forward_cb");
1625 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
1627 webkit_web_view_go_forward(t->wv);
1630 void
1631 stop_cb(GtkWidget *w, struct tab *t)
1633 WebKitWebFrame *frame;
1635 if (t == NULL)
1636 errx(1, "stop_cb");
1638 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
1640 frame = webkit_web_view_get_main_frame(t->wv);
1641 if (frame == NULL) {
1642 warnx("stop_cb: no frame");
1643 return;
1646 webkit_web_frame_stop_loading(frame);
1649 void
1650 setup_webkit(struct tab *t)
1652 g_object_set((GObject *)t->settings,
1653 "user-agent", t->user_agent, NULL);
1654 g_object_set((GObject *)t->settings,
1655 "enable-scripts", enable_scripts, NULL);
1656 g_object_set((GObject *)t->settings,
1657 "enable-plugins", enable_plugins, NULL);
1658 adjustfont_webkit(t, XT_FONT_SET);
1660 webkit_web_view_set_settings(t->wv, t->settings);
1663 GtkWidget *
1664 create_browser(struct tab *t)
1666 GtkWidget *w;
1667 gchar *strval;
1669 if (t == NULL)
1670 errx(1, "create_browser");
1672 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
1673 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
1674 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
1675 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
1677 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
1678 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
1679 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1681 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
1682 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
1684 g_signal_connect(t->wv, "notify::load-status",
1685 G_CALLBACK(notify_load_status_cb), t);
1687 /* set defaults */
1688 t->settings = webkit_web_settings_new();
1690 g_object_get((GObject *)t->settings, "user-agent", &strval, NULL);
1691 if (strval == NULL)
1692 errx(1, "setup_webkit: can't get user-agent property");
1694 if (asprintf(&t->user_agent, "%s %s+", strval, version) == -1)
1695 err(1, "aprintf user-agent");
1696 g_free (strval);
1698 setup_webkit(t);
1700 return (w);
1703 GtkWidget *
1704 create_window(void)
1706 GtkWidget *w;
1708 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1709 gtk_window_set_default_size(GTK_WINDOW(w), 1024, 768);
1710 gtk_widget_set_name(w, "xxxterm");
1711 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
1712 g_signal_connect(G_OBJECT(w), "delete_event",
1713 G_CALLBACK (gtk_main_quit), NULL);
1715 return (w);
1718 GtkWidget *
1719 create_toolbar(struct tab *t)
1721 GtkWidget *toolbar = gtk_toolbar_new();
1722 GtkToolItem *i;
1724 #if GTK_CHECK_VERSION(2,15,0)
1725 gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar),
1726 GTK_ORIENTATION_HORIZONTAL);
1727 #else
1728 gtk_toolbar_set_orientation(GTK_TOOLBAR(toolbar),
1729 GTK_ORIENTATION_HORIZONTAL);
1730 #endif
1731 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH_HORIZ);
1733 if (fancy_bar) {
1734 /* backward button */
1735 t->backward = gtk_tool_button_new_from_stock(GTK_STOCK_GO_BACK);
1736 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), FALSE);
1737 g_signal_connect(G_OBJECT(t->backward), "clicked",
1738 G_CALLBACK(backward_cb), t);
1739 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), t->backward, -1);
1741 /* forward button */
1742 t->forward =
1743 gtk_tool_button_new_from_stock(GTK_STOCK_GO_FORWARD);
1744 gtk_widget_set_sensitive(GTK_WIDGET(t->forward), FALSE);
1745 g_signal_connect(G_OBJECT(t->forward), "clicked",
1746 G_CALLBACK(forward_cb), t);
1747 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), t->forward, -1);
1749 /* stop button */
1750 t->stop = gtk_tool_button_new_from_stock(GTK_STOCK_STOP);
1751 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
1752 g_signal_connect(G_OBJECT(t->stop), "clicked",
1753 G_CALLBACK(stop_cb), t);
1754 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), t->stop, -1);
1757 /* uri entry */
1758 i = gtk_tool_item_new();
1759 gtk_tool_item_set_expand(i, TRUE);
1760 t->uri_entry = gtk_entry_new();
1761 gtk_container_add(GTK_CONTAINER(i), t->uri_entry);
1762 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
1763 G_CALLBACK(activate_uri_entry_cb), t);
1764 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), i, -1);
1766 /* search entry */
1767 if (fancy_bar && search_string) {
1768 i = gtk_tool_item_new();
1769 t->search_entry = gtk_entry_new();
1770 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
1771 gtk_container_add(GTK_CONTAINER(i), t->search_entry);
1772 g_signal_connect(G_OBJECT(t->search_entry), "activate",
1773 G_CALLBACK(activate_search_entry_cb), t);
1774 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), i, -1);
1777 return (toolbar);
1780 void
1781 delete_tab(struct tab *t)
1783 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
1785 if (t == NULL)
1786 return;
1788 TAILQ_REMOVE(&tabs, t, entry);
1789 if (TAILQ_EMPTY(&tabs))
1790 create_new_tab(NULL, 1);
1792 gtk_widget_destroy(t->vbox);
1794 free(t->user_agent);
1795 g_free(t);
1798 void
1799 adjustfont_webkit(struct tab *t, int adjust)
1801 if (t == NULL)
1802 errx(1, "adjustfont_webkit");
1804 if (adjust == XT_FONT_SET)
1805 t->font_size = default_font_size;
1807 t->font_size += adjust;
1808 g_object_set((GObject *)t->settings, "default-font-size",
1809 t->font_size, NULL);
1810 g_object_get((GObject *)t->settings, "default-font-size",
1811 &t->font_size, NULL);
1814 void
1815 create_new_tab(char *title, int focus)
1817 struct tab *t;
1818 int load = 1;
1819 char *newuri = NULL;
1821 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
1823 if (tabless && !TAILQ_EMPTY(&tabs)) {
1824 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
1825 return;
1828 t = g_malloc0(sizeof *t);
1829 TAILQ_INSERT_TAIL(&tabs, t, entry);
1831 if (title == NULL) {
1832 title = "(untitled)";
1833 load = 0;
1834 } else {
1835 if (valid_url_type(title)) {
1836 newuri = guess_url_type(title);
1837 title = newuri;
1841 t->vbox = gtk_vbox_new(FALSE, 0);
1843 /* label for tab */
1844 t->label = gtk_label_new(title);
1845 gtk_widget_set_size_request(t->label, 100, -1);
1847 /* toolbar */
1848 t->toolbar = create_toolbar(t);
1849 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
1851 /* browser */
1852 t->browser_win = create_browser(t);
1853 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
1855 /* command entry */
1856 t->cmd = gtk_entry_new();
1857 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
1858 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
1859 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
1861 /* and show it all */
1862 gtk_widget_show_all(t->vbox);
1863 t->tab_id = gtk_notebook_append_page(notebook, t->vbox,
1864 t->label);
1866 g_object_connect((GObject*)t->cmd,
1867 "signal::key-press-event", (GCallback)cmd_keypress_cb, t,
1868 "signal::key-release-event", (GCallback)cmd_keyrelease_cb, t,
1869 "signal::focus-out-event", (GCallback)cmd_focusout_cb, t,
1870 "signal::activate", (GCallback)cmd_activate_cb, t,
1871 NULL);
1873 g_object_connect((GObject*)t->wv,
1874 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
1875 /* "signal::hovering-over-link", (GCallback)webview_hover_cb, t, */
1876 "signal::download-requested", (GCallback)webview_download_cb, t,
1877 "signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, t,
1878 "signal::navigation-policy-decision-requested", (GCallback)webview_npd_cb, t,
1879 "signal::new-window-policy-decision-requested", (GCallback)webview_nw_cb, t,
1880 "signal::create-web-view", (GCallback)webview_cwv_cb, t,
1881 "signal::event", (GCallback)webview_event_cb, t,
1882 NULL);
1884 /* hijack the unused keys as if we were the browser */
1885 g_object_connect((GObject*)t->toolbar,
1886 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
1887 NULL);
1889 g_signal_connect(G_OBJECT(t->uri_entry), "focus",
1890 G_CALLBACK(focus_uri_entry_cb), t);
1892 /* hide stuff */
1893 gtk_widget_hide(t->cmd);
1894 if (showurl == 0)
1895 gtk_widget_hide(t->toolbar);
1897 if (focus) {
1898 gtk_notebook_set_current_page(notebook, t->tab_id);
1899 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
1900 t->tab_id);
1903 if (load)
1904 webkit_web_view_load_uri(t->wv, title);
1905 else
1906 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
1908 if (newuri)
1909 free(newuri);
1912 void
1913 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
1914 gpointer *udata)
1916 struct tab *t;
1917 const gchar *uri;
1919 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
1921 TAILQ_FOREACH(t, &tabs, entry) {
1922 if (t->tab_id == pn) {
1923 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
1924 "%d\n", pn);
1926 uri = webkit_web_view_get_title(t->wv);
1927 if (uri == NULL)
1928 uri = XT_NAME;
1929 gtk_window_set_title(GTK_WINDOW(main_window), uri);
1931 gtk_widget_hide(t->cmd);
1936 void
1937 create_canvas(void)
1939 GtkWidget *vbox;
1941 vbox = gtk_vbox_new(FALSE, 0);
1942 notebook = GTK_NOTEBOOK(gtk_notebook_new());
1943 if (showtabs == 0)
1944 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
1945 gtk_notebook_set_scrollable(notebook, TRUE);
1947 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
1949 g_object_connect((GObject*)notebook,
1950 "signal::switch-page", (GCallback)notebook_switchpage_cb, NULL,
1951 NULL);
1953 main_window = create_window();
1954 gtk_container_add(GTK_CONTAINER(main_window), vbox);
1955 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
1956 gtk_widget_show_all(main_window);
1959 void
1960 setup_cookies(void)
1962 if (cookiejar) {
1963 soup_session_remove_feature(session,
1964 (SoupSessionFeature*)cookiejar);
1965 g_object_unref(cookiejar);
1966 cookiejar = NULL;
1969 if (cookies_enabled == 0)
1970 return;
1972 cookiejar = soup_cookie_jar_text_new(cookie_file, read_only_cookies);
1973 soup_session_add_feature(session, (SoupSessionFeature*)cookiejar);
1976 void
1977 setup_proxy(char *uri)
1979 if (proxy_uri) {
1980 g_object_set(session, "proxy_uri", NULL, NULL);
1981 soup_uri_free(proxy_uri);
1982 proxy_uri = NULL;
1984 if (http_proxy) {
1985 if (http_proxy != uri) {
1986 free(http_proxy);
1987 http_proxy = NULL;
1991 if (uri) {
1992 http_proxy = strdup(uri);
1993 if (http_proxy == NULL)
1994 err(1, "setup_proxy: strdup");
1996 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
1997 proxy_uri = soup_uri_new(http_proxy);
1998 g_object_set(session, "proxy-uri", proxy_uri, NULL);
2002 void
2003 usage(void)
2005 fprintf(stderr,
2006 "%s [-STVt][-f file] url ...\n", __progname);
2007 exit(0);
2011 main(int argc, char *argv[])
2013 struct stat sb;
2014 int c, focus = 1;
2015 char conf[PATH_MAX] = { '\0' };
2016 char *env_proxy = NULL;
2017 FILE *f = NULL;
2019 while ((c = getopt(argc, argv, "STVf:t")) != -1) {
2020 switch (c) {
2021 case 'S':
2022 showurl = 0;
2023 break;
2024 case 'T':
2025 showtabs = 0;
2026 break;
2027 case 'V':
2028 errx(0 , "Version: %s", version);
2029 break;
2030 case 'f':
2031 strlcpy(conf, optarg, sizeof(conf));
2032 break;
2033 case 't':
2034 tabless = 1;
2035 break;
2036 default:
2037 usage();
2038 /* NOTREACHED */
2041 argc -= optind;
2042 argv += optind;
2044 TAILQ_INIT(&tabs);
2046 /* prepare gtk */
2047 gtk_init(&argc, &argv);
2048 if (!g_thread_supported())
2049 g_thread_init(NULL);
2051 pwd = getpwuid(getuid());
2052 if (pwd == NULL)
2053 errx(1, "invalid user %d", getuid());
2055 /* set download dir */
2056 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
2058 /* read config file */
2059 if (strlen(conf) == 0)
2060 snprintf(conf, sizeof conf, "%s/.%s",
2061 pwd->pw_dir, XT_CONF_FILE);
2062 config_parse(conf);
2064 /* download dir */
2065 if (stat(download_dir, &sb))
2066 errx(1, "must specify a valid download_dir");
2067 if (S_ISDIR(sb.st_mode) == 0)
2068 errx(1, "%s not a dir", download_dir);
2069 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
2070 warnx("fixing invalid permissions on %s", download_dir);
2071 if (chmod(download_dir, S_IRWXU) == -1)
2072 err(1, "chmod");
2075 /* working directory */
2076 snprintf(work_dir, sizeof work_dir, "%s/%s", pwd->pw_dir, XT_DIR);
2077 if (stat(work_dir, &sb)) {
2078 if (mkdir(work_dir, S_IRWXU) == -1)
2079 err(1, "mkdir");
2081 if (S_ISDIR(sb.st_mode) == 0)
2082 errx(1, "%s not a dir", work_dir);
2083 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
2084 warnx("fixing invalid permissions on %s", work_dir);
2085 if (chmod(work_dir, S_IRWXU) == -1)
2086 err(1, "chmod");
2089 /* favorites file */
2090 snprintf(work_dir, sizeof work_dir, "%s/%s/%s",
2091 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
2092 if (stat(work_dir, &sb)) {
2093 warnx("favorites file doesn't exist, creating it");
2094 if ((f = fopen(work_dir, "w")) == NULL)
2095 err(1, "favorites");
2096 fclose(f);
2099 /* cookies */
2100 session = webkit_get_default_session();
2101 snprintf(cookie_file, sizeof cookie_file, "%s/cookies.txt", work_dir);
2102 setup_cookies();
2104 /* proxy */
2105 env_proxy = getenv("http_proxy");
2106 if (env_proxy)
2107 setup_proxy(env_proxy);
2108 else
2109 setup_proxy(http_proxy);
2111 create_canvas();
2113 while (argc) {
2114 create_new_tab(argv[0], focus);
2115 focus = 0;
2117 argc--;
2118 argv++;
2120 if (focus == 1)
2121 create_new_tab(home, 1);
2123 gtk_main();
2125 return (0);