make set about:config
[xxxterm.git] / xxxterm.c
blob65a5e850319c5513b4d7730a6bf1051f1c2dbbf5
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;
103 /* adjustments for browser */
104 GtkScrollbar *sb_h;
105 GtkScrollbar *sb_v;
106 GtkAdjustment *adjust_h;
107 GtkAdjustment *adjust_v;
109 /* flags */
110 int focus_wv;
111 int ctrl_click;
112 gchar *hover;
114 /* search */
115 char *search_text;
116 int search_forward;
117 WebKitWebView *wv;
118 WebKitWebSettings *settings;
120 TAILQ_HEAD(tab_list, tab);
122 struct karg {
123 int i;
124 char *s;
127 /* defines */
128 #define XT_NAME ("XXXTerm")
129 #define XT_DIR (".xxxterm")
130 #define XT_CONF_FILE ("xxxterm.conf")
131 #define XT_FAVS_FILE ("favorites")
132 #define XT_CB_HANDLED (TRUE)
133 #define XT_CB_PASSTHROUGH (FALSE)
135 /* actions */
136 #define XT_MOVE_INVALID (0)
137 #define XT_MOVE_DOWN (1)
138 #define XT_MOVE_UP (2)
139 #define XT_MOVE_BOTTOM (3)
140 #define XT_MOVE_TOP (4)
141 #define XT_MOVE_PAGEDOWN (5)
142 #define XT_MOVE_PAGEUP (6)
143 #define XT_MOVE_LEFT (7)
144 #define XT_MOVE_FARLEFT (8)
145 #define XT_MOVE_RIGHT (9)
146 #define XT_MOVE_FARRIGHT (10)
148 #define XT_TAB_LAST (-4)
149 #define XT_TAB_FIRST (-3)
150 #define XT_TAB_PREV (-2)
151 #define XT_TAB_NEXT (-1)
152 #define XT_TAB_INVALID (0)
153 #define XT_TAB_NEW (1)
154 #define XT_TAB_DELETE (2)
155 #define XT_TAB_DELQUIT (3)
156 #define XT_TAB_OPEN (4)
158 #define XT_NAV_INVALID (0)
159 #define XT_NAV_BACK (1)
160 #define XT_NAV_FORWARD (2)
161 #define XT_NAV_RELOAD (3)
163 #define XT_FOCUS_INVALID (0)
164 #define XT_FOCUS_URI (1)
165 #define XT_FOCUS_SEARCH (2)
167 #define XT_SEARCH_INVALID (0)
168 #define XT_SEARCH_NEXT (1)
169 #define XT_SEARCH_PREV (2)
171 /* globals */
172 extern char *__progname;
173 struct passwd *pwd;
174 GtkWidget *main_window;
175 GtkNotebook *notebook;
176 struct tab_list tabs;
178 /* mime types */
179 struct mime_type {
180 char *mt_type;
181 char *mt_action;
182 int mt_default;
183 TAILQ_ENTRY(mime_type) entry;
185 TAILQ_HEAD(mime_type_list, mime_type);
187 /* settings */
188 int showtabs = 1; /* show tabs on notebook */
189 int showurl = 1; /* show url toolbar on notebook */
190 int tabless = 0; /* allow only 1 tab */
191 int ctrl_click_focus = 0; /* ctrl click gets focus */
192 int cookies_enabled = 1; /* enable cookies */
193 int read_only_cookies = 0; /* enable to not write cookies */
194 int enable_scripts = 0;
195 int enable_plugins = 0;
196 int default_font_size = 12;
197 int fancy_bar = 1; /* fancy toolbar */
199 char *home = "http://www.peereboom.us";
200 char *search_string = NULL;
201 char *http_proxy = NULL;
202 SoupURI *proxy_uri = NULL;
203 char work_dir[PATH_MAX];
204 char cookie_file[PATH_MAX];
205 char download_dir[PATH_MAX];
206 SoupSession *session;
207 SoupCookieJar *cookiejar;
209 struct mime_type_list mtl;
211 /* protos */
212 void create_new_tab(char *, int);
213 void delete_tab(struct tab *);
214 void adjustfont_webkit(struct tab *, int);
216 struct valid_url_types {
217 char *type;
218 } vut[] = {
219 { "http://" },
220 { "https://" },
221 { "ftp://" },
222 { "file://" },
226 valid_url_type(char *url)
228 int i;
230 for (i = 0; i < LENGTH(vut); i++)
231 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
232 return (0);
234 return (1);
237 char *
238 guess_url_type(char *url_in)
240 struct stat sb;
241 char *url_out = NULL;
243 /* XXX not sure about this heuristic */
244 if (stat(url_in, &sb) == 0) {
245 if (asprintf(&url_out, "file://%s", url_in) == -1)
246 err(1, "aprintf file");
247 } else {
248 /* guess http */
249 if (asprintf(&url_out, "http://%s", url_in) == -1)
250 err(1, "aprintf http");
253 if (url_out == NULL)
254 err(1, "asprintf pointer");
256 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
258 return (url_out);
261 void
262 add_mime_type(char *line)
264 char *mime_type;
265 char *l = NULL;
266 struct mime_type *m;
268 /* XXX this could be smarter */
270 if (line == NULL)
271 errx(1, "add_mime_type");
272 l = line;
274 m = malloc(sizeof(*m));
275 if (m == NULL)
276 err(1, "add_mime_type: malloc");
278 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL)
279 errx(1, "add_mime_type: invalid mime_type");
281 if (mime_type[strlen(mime_type) - 1] == '*') {
282 mime_type[strlen(mime_type) - 1] = '\0';
283 m->mt_default = 1;
284 } else
285 m->mt_default = 0;
287 if (strlen(mime_type) == 0 || strlen(l) == 0)
288 errx(1, "add_mime_type: invalid mime_type");
290 m->mt_type = strdup(mime_type);
291 if (m->mt_type == NULL)
292 err(1, "add_mime_type: malloc type");
294 m->mt_action = strdup(l);
295 if (m->mt_action == NULL)
296 err(1, "add_mime_type: malloc action");
298 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
299 m->mt_type, m->mt_action, m->mt_default);
301 TAILQ_INSERT_TAIL(&mtl, m, entry);
304 struct mime_type *
305 find_mime_type(char *mime_type)
307 struct mime_type *m, *def = NULL, *rv = NULL;
309 TAILQ_FOREACH(m, &mtl, entry) {
310 if (m->mt_default &&
311 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
312 def = m;
314 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
315 rv = m;
316 break;
320 if (rv == NULL)
321 rv = def;
323 return (rv);
326 #define WS "\n= \t"
327 void
328 config_parse(char *filename)
330 FILE *config;
331 char *line, *cp, *var, *val;
332 size_t len, lineno = 0;
334 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
336 TAILQ_INIT(&mtl);
338 if (filename == NULL)
339 return;
341 if ((config = fopen(filename, "r")) == NULL) {
342 warn("config_parse: cannot open %s", filename);
343 return;
346 for (;;) {
347 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
348 if (feof(config))
349 break;
351 cp = line;
352 cp += (long)strspn(cp, WS);
353 if (cp[0] == '\0') {
354 /* empty line */
355 free(line);
356 continue;
359 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
360 break;
362 cp += (long)strspn(cp, WS);
364 if ((val = strsep(&cp, "\0")) == NULL)
365 break;
367 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
369 /* get settings */
370 if (!strcmp(var, "home"))
371 home = strdup(val);
372 else if (!strcmp(var, "ctrl_click_focus"))
373 ctrl_click_focus = atoi(val);
374 else if (!strcmp(var, "read_only_cookies"))
375 read_only_cookies = atoi(val);
376 else if (!strcmp(var, "cookies_enabled"))
377 cookies_enabled = atoi(val);
378 else if (!strcmp(var, "enable_scripts"))
379 enable_scripts = atoi(val);
380 else if (!strcmp(var, "enable_plugins"))
381 enable_plugins = atoi(val);
382 else if (!strcmp(var, "default_font_size"))
383 default_font_size = atoi(val);
384 else if (!strcmp(var, "fancy_bar"))
385 fancy_bar = atoi(val);
386 else if (!strcmp(var, "mime_type"))
387 add_mime_type(val);
388 else if (!strcmp(var, "http_proxy")) {
389 http_proxy = strdup(val);
390 if (http_proxy == NULL)
391 err(1, "http_proxy");
392 } else if (!strcmp(var, "search_string")) {
393 search_string = strdup(val);
394 if (search_string == NULL)
395 err(1, "search_string");
396 } else if (!strcmp(var, "download_dir")) {
397 if (val[0] == '~')
398 snprintf(download_dir, sizeof download_dir,
399 "%s/%s", pwd->pw_dir, &val[1]);
400 else
401 strlcpy(download_dir, val, sizeof download_dir);
402 } else
403 errx(1, "invalid conf file entry: %s=%s", var, val);
405 free(line);
408 fclose(config);
411 quit(struct tab *t, struct karg *args)
413 gtk_main_quit();
415 return (1);
419 focus(struct tab *t, struct karg *args)
421 if (t == NULL || args == NULL)
422 errx(1, "focus");
424 if (args->i == XT_FOCUS_URI)
425 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
426 if (args->i == XT_FOCUS_SEARCH)
427 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
429 return (0);
433 help(struct tab *t, struct karg *args)
435 if (t == NULL)
436 errx(1, "help");
438 webkit_web_view_load_string(t->wv,
439 "<html><body><h1>XXXTerm</h1></body></html>",
440 NULL,
441 NULL,
442 NULL);
444 return (0);
448 favorites(struct tab *t, struct karg *args)
450 char file[PATH_MAX];
451 FILE *f, *h;
452 char *uri = NULL, *title = NULL;
453 size_t len, lineno = 0;
454 int i, failed = 0;
456 if (t == NULL)
457 errx(1, "favorites");
459 /* XXX run a digest over the favorites file instead of always generating it */
461 /* open favorites */
462 snprintf(file, sizeof file, "%s/%s/%s",
463 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
464 if ((f = fopen(file, "r")) == NULL) {
465 warn("favorites");
466 return (1);
469 /* open favorites html */
470 snprintf(file, sizeof file, "%s/%s/%s.html",
471 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
472 if ((h = fopen(file, "w+")) == NULL) {
473 warn("favorites.html");
474 return (1);
477 fprintf(h, "<html><body>Favorites:<p>\n<ol>\n");
479 for (i = 1;;) {
480 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
481 if (feof(f))
482 break;
483 if (strlen(title) == 0)
484 continue;
486 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
487 if (feof(f)) {
488 failed = 1;
489 break;
492 fprintf(h, "<li><a href=\"%s\">%s</a><br>\n", uri, title);
494 free(uri);
495 uri = NULL;
496 free(title);
497 title = NULL;
498 i++;
501 if (uri)
502 free(uri);
503 if (title)
504 free(title);
506 fprintf(h, "</ol></body></html>");
507 fclose(f);
508 fclose(h);
510 if (failed) {
511 webkit_web_view_load_string(t->wv,
512 "<html><body>Invalid favorites file</body></html>",
513 NULL,
514 NULL,
515 NULL);
516 } else {
517 snprintf(file, sizeof file, "file://%s/%s/%s.html",
518 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
519 webkit_web_view_load_uri(t->wv, file);
522 return (0);
526 favadd(struct tab *t, struct karg *args)
528 char file[PATH_MAX];
529 FILE *f;
530 WebKitWebFrame *frame;
531 const gchar *uri, *title;
533 if (t == NULL)
534 errx(1, "favadd");
536 snprintf(file, sizeof file, "%s/%s/%s",
537 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
538 if ((f = fopen(file, "r+")) == NULL) {
539 warn("favorites");
540 return (1);
542 if (fseeko(f, 0, SEEK_END) == -1)
543 err(1, "fseeko");
545 title = webkit_web_view_get_title(t->wv);
546 frame = webkit_web_view_get_main_frame(t->wv);
547 uri = webkit_web_frame_get_uri(frame);
548 if (title == NULL)
549 title = uri;
551 if (title == NULL || uri == NULL) {
552 webkit_web_view_load_string(t->wv,
553 "<html><body>can't add page to favorites</body></html>",
554 NULL,
555 NULL,
556 NULL);
557 goto done;
560 fprintf(f, "\n%s\n%s", title, uri);
561 done:
562 fclose(f);
564 return (0);
568 navaction(struct tab *t, struct karg *args)
570 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
571 t->tab_id, args->i);
573 switch (args->i) {
574 case XT_NAV_BACK:
575 webkit_web_view_go_back(t->wv);
576 break;
577 case XT_NAV_FORWARD:
578 webkit_web_view_go_forward(t->wv);
579 break;
580 case XT_NAV_RELOAD:
581 webkit_web_view_reload(t->wv);
582 break;
584 return (XT_CB_PASSTHROUGH);
588 move(struct tab *t, struct karg *args)
590 GtkAdjustment *adjust;
591 double pi, si, pos, ps, upper, lower, max;
593 switch (args->i) {
594 case XT_MOVE_DOWN:
595 case XT_MOVE_UP:
596 case XT_MOVE_BOTTOM:
597 case XT_MOVE_TOP:
598 case XT_MOVE_PAGEDOWN:
599 case XT_MOVE_PAGEUP:
600 adjust = t->adjust_v;
601 break;
602 default:
603 adjust = t->adjust_h;
604 break;
607 pos = gtk_adjustment_get_value(adjust);
608 ps = gtk_adjustment_get_page_size(adjust);
609 upper = gtk_adjustment_get_upper(adjust);
610 lower = gtk_adjustment_get_lower(adjust);
611 si = gtk_adjustment_get_step_increment(adjust);
612 pi = gtk_adjustment_get_page_increment(adjust);
613 max = upper - ps;
615 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
616 "max %f si %f pi %f\n",
617 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
618 pos, ps, upper, lower, max, si, pi);
620 switch (args->i) {
621 case XT_MOVE_DOWN:
622 case XT_MOVE_RIGHT:
623 pos += si;
624 gtk_adjustment_set_value(adjust, MIN(pos, max));
625 break;
626 case XT_MOVE_UP:
627 case XT_MOVE_LEFT:
628 pos -= si;
629 gtk_adjustment_set_value(adjust, MAX(pos, lower));
630 break;
631 case XT_MOVE_BOTTOM:
632 case XT_MOVE_FARRIGHT:
633 gtk_adjustment_set_value(adjust, max);
634 break;
635 case XT_MOVE_TOP:
636 case XT_MOVE_FARLEFT:
637 gtk_adjustment_set_value(adjust, lower);
638 break;
639 case XT_MOVE_PAGEDOWN:
640 pos += pi;
641 gtk_adjustment_set_value(adjust, MIN(pos, max));
642 break;
643 case XT_MOVE_PAGEUP:
644 pos -= pi;
645 gtk_adjustment_set_value(adjust, MAX(pos, lower));
646 break;
647 default:
648 return (XT_CB_PASSTHROUGH);
651 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
653 return (XT_CB_HANDLED);
656 char *
657 getparams(char *cmd, char *cmp)
659 char *rv = NULL;
661 if (cmd && cmp) {
662 if (!strncmp(cmd, cmp, strlen(cmp))) {
663 rv = cmd + strlen(cmp);
664 while (*rv == ' ')
665 rv++;
666 if (strlen(rv) == 0)
667 rv = NULL;
671 return (rv);
675 tabaction(struct tab *t, struct karg *args)
677 int rv = XT_CB_HANDLED;
678 char *url = NULL, *newuri = NULL;
680 DNPRINTF(XT_D_TAB, "tabaction: %p %d %d\n", t, args->i, t->focus_wv);
682 if (t == NULL)
683 return (XT_CB_PASSTHROUGH);
685 switch (args->i) {
686 case XT_TAB_NEW:
687 if ((url = getparams(args->s, "tabnew")))
688 create_new_tab(url, 1);
689 else
690 create_new_tab(NULL, 1);
691 break;
692 case XT_TAB_DELETE:
693 delete_tab(t);
694 break;
695 case XT_TAB_DELQUIT:
696 if (gtk_notebook_get_n_pages(notebook) > 1)
697 delete_tab(t);
698 else
699 quit(t, args);
700 break;
701 case XT_TAB_OPEN:
702 if ((url = getparams(args->s, "open")) ||
703 ((url = getparams(args->s, "op"))) ||
704 ((url = getparams(args->s, "o"))))
706 else {
707 rv = XT_CB_PASSTHROUGH;
708 goto done;
711 if (valid_url_type(url)) {
712 newuri = guess_url_type(url);
713 url = newuri;
715 webkit_web_view_load_uri(t->wv, url);
716 if (newuri)
717 free(newuri);
718 break;
719 default:
720 rv = XT_CB_PASSTHROUGH;
721 goto done;
724 done:
725 if (args->s) {
726 free(args->s);
727 args->s = NULL;
730 return (rv);
734 resizetab(struct tab *t, struct karg *args)
736 if (t == NULL || args == NULL)
737 errx(1, "resizetab");
739 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
740 t->tab_id, args->i);
742 if (t == NULL)
743 return (XT_CB_PASSTHROUGH);
745 adjustfont_webkit(t, args->i);
747 return (XT_CB_HANDLED);
751 movetab(struct tab *t, struct karg *args)
753 struct tab *tt;
754 int x;
756 if (t == NULL || args == NULL)
757 return (XT_CB_PASSTHROUGH);
759 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
760 t->tab_id, args->i);
762 if (args->i == XT_TAB_INVALID)
763 return (XT_CB_PASSTHROUGH);
765 if (args->i < XT_TAB_INVALID) {
766 /* next or previous tab */
767 if (TAILQ_EMPTY(&tabs))
768 return (XT_CB_PASSTHROUGH);
770 switch (args->i) {
771 case XT_TAB_NEXT:
772 gtk_notebook_next_page(notebook);
773 break;
774 case XT_TAB_PREV:
775 gtk_notebook_prev_page(notebook);
776 break;
777 case XT_TAB_FIRST:
778 gtk_notebook_set_current_page(notebook, 0);
779 break;
780 case XT_TAB_LAST:
781 gtk_notebook_set_current_page(notebook, -1);
782 break;
783 default:
784 return (XT_CB_PASSTHROUGH);
787 return (XT_CB_HANDLED);
790 /* jump to tab */
791 x = args->i - 1;
792 if (t->tab_id == x) {
793 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
794 return (XT_CB_HANDLED);
797 TAILQ_FOREACH(tt, &tabs, entry) {
798 if (tt->tab_id == x) {
799 gtk_notebook_set_current_page(notebook, x);
800 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
801 if (tt->focus_wv)
802 gtk_widget_grab_focus(GTK_WIDGET(tt->wv));
806 return (XT_CB_HANDLED);
810 command(struct tab *t, struct karg *args)
812 char *s = NULL;
813 GdkColor color;
815 if (t == NULL || args == NULL)
816 errx(1, "command");
818 if (args->i == '/')
819 s = "/";
820 else if (args->i == '?')
821 s = "?";
822 else if (args->i == ':')
823 s = ":";
824 else {
825 warnx("invalid command %c\n", args->i);
826 return (XT_CB_PASSTHROUGH);
829 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
831 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
832 gdk_color_parse("white", &color);
833 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
834 gtk_widget_show(t->cmd);
835 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
836 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
838 return (XT_CB_HANDLED);
842 search(struct tab *t, struct karg *args)
844 gboolean d;
846 if (t == NULL || args == NULL)
847 errx(1, "search");
848 if (t->search_text == NULL)
849 return (XT_CB_PASSTHROUGH);
851 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
852 t->tab_id, args->i, t->search_forward, t->search_text);
854 switch (args->i) {
855 case XT_SEARCH_NEXT:
856 d = t->search_forward;
857 break;
858 case XT_SEARCH_PREV:
859 d = !t->search_forward;
860 break;
861 default:
862 return (XT_CB_PASSTHROUGH);
865 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
867 return (XT_CB_HANDLED);
871 mnprintf(char **buf, int *len, char *fmt, ...)
873 int x, old_len;
874 va_list ap;
876 va_start(ap, fmt);
878 old_len = *len;
879 x = vsnprintf(*buf, *len, fmt, ap);
880 if (x == -1)
881 err(1, "mnprintf");
882 if (old_len < x)
883 errx(1, "mnprintf: buffer overflow");
885 *buf += x;
886 *len -= x;
888 va_end(ap);
890 return (0);
894 set(struct tab *t, struct karg *args)
896 struct mime_type *m;
897 char b[16 * 1024], *s;
898 int l;
900 if (t == NULL || args == NULL)
901 errx(1, "set");
903 DNPRINTF(XT_D_CMD, "set: tab %d\n",
904 t->tab_id);
906 s = b;
907 l = sizeof b;
908 mnprintf(&s, &l, "<html><body><pre>");
909 mnprintf(&s, &l, "ctrl_click_focus\t= %d<br>", ctrl_click_focus);
910 mnprintf(&s, &l, "cookies_enabled\t\t= %d<br>", cookies_enabled);
911 mnprintf(&s, &l, "default_font_size\t= %d<br>", default_font_size);
912 mnprintf(&s, &l, "enable_plugins\t\t= %d<br>", enable_plugins);
913 mnprintf(&s, &l, "enable_scripts\t\t= %d<br>", enable_scripts);
914 mnprintf(&s, &l, "fancy_bar\t\t= %d<br>", fancy_bar);
915 mnprintf(&s, &l, "home\t\t\t= %s<br>", home);
916 TAILQ_FOREACH(m, &mtl, entry) {
917 mnprintf(&s, &l, "mime_type\t\t= %s%s,%s<br>",
918 m->mt_type, m->mt_default ? "*" : "", m->mt_action);
920 mnprintf(&s, &l, "proxy_uri\t\t= %s<br>", proxy_uri);
921 mnprintf(&s, &l, "read_only_cookies\t= %d<br>", read_only_cookies);
922 mnprintf(&s, &l, "search_string\t\t= %s<br>", search_string);
923 mnprintf(&s, &l, "showurl\t\t\t= %d<br>", showurl);
924 mnprintf(&s, &l, "showtabs\t\t= %d<br>", showtabs);
925 mnprintf(&s, &l, "tabless\t\t\t= %d<br>", tabless);
926 mnprintf(&s, &l, "download_dir\t\t= %s<br>", download_dir);
927 mnprintf(&s, &l, "</pre></body></html>");
929 webkit_web_view_load_string(t->wv,
931 NULL,
932 NULL,
933 "about:config");
935 return (XT_CB_PASSTHROUGH);
938 /* inherent to GTK not all keys will be caught at all times */
939 struct key {
940 guint mask;
941 guint modkey;
942 guint key;
943 int (*func)(struct tab *, struct karg *);
944 struct karg arg;
945 } keys[] = {
946 { 0, 0, GDK_slash, command, {.i = '/'} },
947 { GDK_SHIFT_MASK, 0, GDK_question, command, {.i = '?'} },
948 { GDK_SHIFT_MASK, 0, GDK_colon, command, {.i = ':'} },
949 { GDK_CONTROL_MASK, 0, GDK_q, quit, {0} },
951 /* search */
952 { 0, 0, GDK_n, search, {.i = XT_SEARCH_NEXT} },
953 { GDK_SHIFT_MASK, 0, GDK_N, search, {.i = XT_SEARCH_PREV} },
955 /* focus */
956 { 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} },
957 { 0, 0, GDK_F7, focus, {.i = XT_FOCUS_SEARCH} },
959 /* navigation */
960 { 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
961 { GDK_MOD1_MASK, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
962 { GDK_SHIFT_MASK, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
963 { GDK_MOD1_MASK, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
964 { 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} },
965 { GDK_CONTROL_MASK, 0, GDK_r, navaction, {.i = XT_NAV_RELOAD} },
966 { GDK_CONTROL_MASK, 0, GDK_l, navaction, {.i = XT_NAV_RELOAD} },
968 /* vertical movement */
969 { 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
970 { 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
971 { 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
972 { 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
973 { GDK_SHIFT_MASK, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
974 { 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
975 { 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
976 { 0, GDK_g, GDK_g, move, {.i = XT_MOVE_TOP} }, /* XXX make this work */
977 { 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
978 { GDK_CONTROL_MASK, 0, GDK_f, move, {.i = XT_MOVE_PAGEDOWN} },
979 { 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
980 { 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
981 { GDK_CONTROL_MASK, 0, GDK_b, move, {.i = XT_MOVE_PAGEUP} },
982 /* horizontal movement */
983 { 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
984 { 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
985 { 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
986 { 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
987 { GDK_SHIFT_MASK, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
988 { 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
990 /* tabs */
991 { GDK_CONTROL_MASK, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
992 { GDK_CONTROL_MASK, 0, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
993 { GDK_CONTROL_MASK, 0, GDK_1, movetab, {.i = 1} },
994 { GDK_CONTROL_MASK, 0, GDK_2, movetab, {.i = 2} },
995 { GDK_CONTROL_MASK, 0, GDK_3, movetab, {.i = 3} },
996 { GDK_CONTROL_MASK, 0, GDK_4, movetab, {.i = 4} },
997 { GDK_CONTROL_MASK, 0, GDK_5, movetab, {.i = 5} },
998 { GDK_CONTROL_MASK, 0, GDK_6, movetab, {.i = 6} },
999 { GDK_CONTROL_MASK, 0, GDK_7, movetab, {.i = 7} },
1000 { GDK_CONTROL_MASK, 0, GDK_8, movetab, {.i = 8} },
1001 { GDK_CONTROL_MASK, 0, GDK_9, movetab, {.i = 9} },
1002 { GDK_CONTROL_MASK, 0, GDK_0, movetab, {.i = 10} },
1003 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_less, movetab, {.i = XT_TAB_FIRST} },
1004 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_greater, movetab, {.i = XT_TAB_LAST} },
1005 { GDK_CONTROL_MASK, 0, GDK_minus, resizetab, {.i = -1} },
1006 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_plus, resizetab, {.i = 1} },
1007 { GDK_CONTROL_MASK, 0, GDK_equal, resizetab, {.i = 1} },
1010 struct cmd {
1011 char *cmd;
1012 int params;
1013 int (*func)(struct tab *, struct karg *);
1014 struct karg arg;
1015 } cmds[] = {
1016 { "q!", 0, quit, {0} },
1017 { "qa", 0, quit, {0} },
1018 { "qa!", 0, quit, {0} },
1019 { "help", 0, help, {0} },
1021 /* favorites */
1022 { "fav", 0, favorites, {0} },
1023 { "favadd", 0, favadd, {0} },
1025 /* tabs */
1026 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
1027 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
1028 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
1029 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
1030 { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} },
1031 { "tabe", 1, tabaction, {.i = XT_TAB_NEW} },
1032 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
1033 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} },
1034 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
1035 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
1036 /* XXX add count to these commands and add tabl and friends */
1037 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST} },
1038 { "tabfir", 0, movetab, {.i = XT_TAB_FIRST} },
1039 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST} },
1040 { "tabr", 0, movetab, {.i = XT_TAB_FIRST} },
1041 { "tablast", 0, movetab, {.i = XT_TAB_LAST} },
1042 { "tabl", 0, movetab, {.i = XT_TAB_LAST} },
1043 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
1044 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
1045 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
1046 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
1048 /* settings */
1049 { "set", 1, set, {0} },
1052 void
1053 focus_uri_entry_cb(GtkWidget* w, GtkDirectionType direction, struct tab *t)
1055 DNPRINTF(XT_D_URL, "focus_uri_entry_cb: tab %d focus_wv %d\n",
1056 t->tab_id, t->focus_wv);
1058 if (t == NULL)
1059 errx(1, "focus_uri_entry_cb");
1061 /* focus on wv instead */
1062 if (t->focus_wv)
1063 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1066 void
1067 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
1069 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
1070 char *newuri = NULL;
1072 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
1074 if (t == NULL)
1075 errx(1, "activate_uri_entry_cb");
1077 if (uri == NULL)
1078 errx(1, "uri");
1080 if (valid_url_type((char *)uri)) {
1081 newuri = guess_url_type((char *)uri);
1082 uri = newuri;
1085 webkit_web_view_load_uri(t->wv, uri);
1086 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1088 if (newuri)
1089 free(newuri);
1092 void
1093 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
1095 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
1096 char *newuri = NULL;
1098 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
1100 if (t == NULL)
1101 errx(1, "activate_search_entry_cb");
1103 if (search_string == NULL) {
1104 warnx("no search_string");
1105 return;
1108 if (asprintf(&newuri, search_string, search) == -1)
1109 err(1, "activate_search_entry_cb");
1111 webkit_web_view_load_uri(t->wv, newuri);
1112 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1114 if (newuri)
1115 free(newuri);
1118 void
1119 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
1121 WebKitWebFrame *frame;
1122 const gchar *uri;
1124 if (t == NULL)
1125 errx(1, "notify_load_status_cb");
1127 switch (webkit_web_view_get_load_status(wview)) {
1128 case WEBKIT_LOAD_COMMITTED:
1129 frame = webkit_web_view_get_main_frame(wview);
1130 uri = webkit_web_frame_get_uri(frame);
1131 if (uri)
1132 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
1134 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
1135 t->focus_wv = 1;
1137 /* take focus if we are visible */
1138 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
1139 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1140 break;
1141 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
1142 uri = webkit_web_view_get_title(wview);
1143 if (uri == NULL) {
1144 frame = webkit_web_view_get_main_frame(wview);
1145 uri = webkit_web_frame_get_uri(frame);
1147 gtk_label_set_text(GTK_LABEL(t->label), uri);
1148 gtk_window_set_title(GTK_WINDOW(main_window), uri);
1149 break;
1150 case WEBKIT_LOAD_PROVISIONAL:
1151 case WEBKIT_LOAD_FINISHED:
1152 #if WEBKIT_CHECK_VERSION(1, 1, 18)
1153 case WEBKIT_LOAD_FAILED:
1154 #endif
1155 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
1156 default:
1157 break;
1160 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
1161 webkit_web_view_can_go_back(wview));
1163 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
1164 webkit_web_view_can_go_forward(wview));
1168 webview_nw_cb(WebKitWebView *wv, WebKitWebFrame *wf,
1169 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
1170 WebKitWebPolicyDecision *pd, struct tab *t)
1172 char *uri;
1174 if (t == NULL)
1175 errx(1, "webview_nw_cb");
1177 DNPRINTF(XT_D_NAV, "webview_nw_cb: %s\n",
1178 webkit_network_request_get_uri(request));
1180 /* open in current tab */
1181 uri = (char *)webkit_network_request_get_uri(request);
1182 webkit_web_view_load_uri(t->wv, uri);
1183 webkit_web_policy_decision_ignore(pd);
1185 return (TRUE); /* we made the decission */
1189 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
1190 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
1191 WebKitWebPolicyDecision *pd, struct tab *t)
1193 char *uri;
1195 if (t == NULL)
1196 errx(1, "webview_npd_cb");
1198 DNPRINTF(XT_D_NAV, "webview_npd_cb: %s\n",
1199 webkit_network_request_get_uri(request));
1201 uri = (char *)webkit_network_request_get_uri(request);
1202 if (t->ctrl_click) {
1203 t->ctrl_click = 0;
1204 create_new_tab(uri, ctrl_click_focus);
1205 webkit_web_policy_decision_ignore(pd);
1206 return (TRUE); /* we made the decission */
1209 webkit_web_policy_decision_use(pd);
1210 return (TRUE); /* we made the decission */
1213 WebKitWebView *
1214 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
1216 if (t == NULL)
1217 errx(1, "webview_cwv_cb");
1219 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
1220 webkit_web_view_get_uri(wv));
1222 return (wv);
1226 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
1228 /* we can not eat the event without throwing gtk off so defer it */
1230 /* catch ctrl click */
1231 if (e->type == GDK_BUTTON_RELEASE &&
1232 CLEAN(e->state) == GDK_CONTROL_MASK)
1233 t->ctrl_click = 1;
1234 else
1235 t->ctrl_click = 0;
1237 return (XT_CB_PASSTHROUGH);
1241 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
1243 struct mime_type *m;
1245 m = find_mime_type(mime_type);
1246 if (m == NULL)
1247 return (1);
1249 switch (fork()) {
1250 case -1:
1251 err(1, "fork");
1252 /* NOTREACHED */
1253 case 0:
1254 break;
1255 default:
1256 return (0);
1259 /* child */
1260 execlp(m->mt_action, m->mt_action,
1261 webkit_network_request_get_uri(request), (void *)NULL);
1263 _exit(0);
1265 /* NOTREACHED */
1266 return (0);
1270 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
1271 WebKitNetworkRequest *request, char *mime_type,
1272 WebKitWebPolicyDecision *decision, struct tab *t)
1274 if (t == NULL)
1275 errx(1, "webview_mimetype_cb");
1277 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
1278 t->tab_id, mime_type);
1280 if (run_mimehandler(t, mime_type, request) == 0) {
1281 webkit_web_policy_decision_ignore(decision);
1282 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1283 return (TRUE);
1286 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
1287 webkit_web_policy_decision_download(decision);
1288 return (TRUE);
1291 return (FALSE);
1295 webview_download_cb(WebKitWebView *wv, WebKitDownload *download, struct tab *t)
1297 const gchar *filename;
1298 char *uri = NULL;
1300 if (download == NULL || t == NULL)
1301 errx(1, "webview_download_cb: invalid pointers");
1303 filename = webkit_download_get_suggested_filename(download);
1304 if (filename == NULL)
1305 return (FALSE); /* abort download */
1307 if (asprintf(&uri, "file://%s/%s", download_dir, filename) == -1)
1308 err(1, "aprintf uri");
1310 DNPRINTF(XT_D_DOWNLOAD, "webview_download_cb: tab %d filename %s "
1311 "local %s\n",
1312 t->tab_id, filename, uri);
1314 webkit_download_set_destination_uri(download, uri);
1316 if (uri)
1317 free(uri);
1319 webkit_download_start(download);
1321 return (TRUE); /* start download */
1324 /* XXX currently unused */
1325 void
1326 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
1328 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
1330 if (t == NULL)
1331 errx(1, "webview_hover_cb");
1333 if (uri) {
1334 if (t->hover) {
1335 free(t->hover);
1336 t->hover = NULL;
1338 t->hover = strdup(uri);
1339 } else if (t->hover) {
1340 free(t->hover);
1341 t->hover = NULL;
1346 webview_keypress_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
1348 int i;
1350 /* don't use w directly; use t->whatever instead */
1352 if (t == NULL)
1353 errx(1, "webview_keypress_cb");
1355 DNPRINTF(XT_D_KEY, "webview_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
1356 e->keyval, e->state, t);
1358 for (i = 0; i < LENGTH(keys); i++)
1359 if (e->keyval == keys[i].key && CLEAN(e->state) ==
1360 keys[i].mask) {
1361 keys[i].func(t, &keys[i].arg);
1362 return (XT_CB_HANDLED);
1365 return (XT_CB_PASSTHROUGH);
1369 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
1371 const gchar *c = gtk_entry_get_text(w);
1372 GdkColor color;
1373 int forward = TRUE;
1375 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
1376 e->keyval, e->state, t);
1378 if (t == NULL)
1379 errx(1, "cmd_keyrelease_cb");
1381 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
1382 e->keyval, e->state, t);
1384 if (c[0] == ':')
1385 goto done;
1386 if (strlen(c) == 1)
1387 goto done;
1389 if (c[0] == '/')
1390 forward = TRUE;
1391 else if (c[0] == '?')
1392 forward = FALSE;
1393 else
1394 goto done;
1396 /* search */
1397 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
1398 FALSE) {
1399 /* not found, mark red */
1400 gdk_color_parse("red", &color);
1401 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
1402 /* unmark and remove selection */
1403 webkit_web_view_unmark_text_matches(t->wv);
1404 /* my kingdom for a way to unselect text in webview */
1405 } else {
1406 /* found, highlight all */
1407 webkit_web_view_unmark_text_matches(t->wv);
1408 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
1409 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
1410 gdk_color_parse("white", &color);
1411 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
1413 done:
1414 return (XT_CB_PASSTHROUGH);
1418 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
1420 int rv = XT_CB_HANDLED;
1421 const gchar *c = gtk_entry_get_text(w);
1423 if (t == NULL)
1424 errx(1, "cmd_keypress_cb");
1426 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
1427 e->keyval, e->state, t);
1429 /* sanity */
1430 if (c == NULL)
1431 e->keyval = GDK_Escape;
1432 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
1433 e->keyval = GDK_Escape;
1435 switch (e->keyval) {
1436 case GDK_BackSpace:
1437 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
1438 break;
1439 /* FALLTHROUGH */
1440 case GDK_Escape:
1441 gtk_widget_hide(t->cmd);
1442 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1443 goto done;
1446 rv = XT_CB_PASSTHROUGH;
1447 done:
1448 return (rv);
1452 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
1454 if (t == NULL)
1455 errx(1, "cmd_focusout_cb");
1457 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d focus_wv %d\n",
1458 t->tab_id, t->focus_wv);
1460 /* abort command when losing focus */
1461 gtk_widget_hide(t->cmd);
1462 if (t->focus_wv)
1463 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1464 else
1465 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
1467 return (XT_CB_PASSTHROUGH);
1470 void
1471 cmd_activate_cb(GtkEntry *entry, struct tab *t)
1473 int i;
1474 char *s;
1475 const gchar *c = gtk_entry_get_text(entry);
1477 if (t == NULL)
1478 errx(1, "cmd_activate_cb");
1480 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
1482 /* sanity */
1483 if (c == NULL)
1484 goto done;
1485 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
1486 goto done;
1487 if (strlen(c) < 2)
1488 goto done;
1489 s = (char *)&c[1];
1491 if (c[0] == '/' || c[0] == '?') {
1492 if (t->search_text) {
1493 free(t->search_text);
1494 t->search_text = NULL;
1497 t->search_text = strdup(s);
1498 if (t->search_text == NULL)
1499 err(1, "search_text");
1501 t->search_forward = c[0] == '/';
1503 goto done;
1506 for (i = 0; i < LENGTH(cmds); i++)
1507 if (cmds[i].params) {
1508 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
1509 cmds[i].arg.s = strdup(s);
1510 cmds[i].func(t, &cmds[i].arg);
1512 } else {
1513 if (!strcmp(s, cmds[i].cmd))
1514 cmds[i].func(t, &cmds[i].arg);
1517 done:
1518 gtk_widget_hide(t->cmd);
1521 void
1522 backward_cb(GtkWidget *w, struct tab *t)
1524 if (t == NULL)
1525 errx(1, "backward_cb");
1527 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
1529 webkit_web_view_go_back(t->wv);
1532 void
1533 forward_cb(GtkWidget *w, struct tab *t)
1535 if (t == NULL)
1536 errx(1, "forward_cb");
1538 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
1540 webkit_web_view_go_forward(t->wv);
1543 void
1544 stop_cb(GtkWidget *w, struct tab *t)
1546 WebKitWebFrame *frame;
1548 if (t == NULL)
1549 errx(1, "stop_cb");
1551 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
1553 frame = webkit_web_view_get_main_frame(t->wv);
1554 if (frame == NULL) {
1555 warnx("stop_cb: no frame");
1556 return;
1559 webkit_web_frame_stop_loading(frame);
1562 GtkWidget *
1563 create_browser(struct tab *t)
1565 GtkWidget *w;
1567 if (t == NULL)
1568 errx(1, "create_browser");
1570 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
1571 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
1572 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
1573 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
1575 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
1576 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
1577 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1579 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
1580 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
1582 g_signal_connect(t->wv, "notify::load-status",
1583 G_CALLBACK(notify_load_status_cb), t);
1585 return (w);
1588 GtkWidget *
1589 create_window(void)
1591 GtkWidget *w;
1593 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1594 gtk_window_set_default_size(GTK_WINDOW(w), 800, 600);
1595 gtk_widget_set_name(w, "xxxterm");
1596 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
1597 g_signal_connect(G_OBJECT(w), "delete_event",
1598 G_CALLBACK (gtk_main_quit), NULL);
1600 return (w);
1603 GtkWidget *
1604 create_toolbar(struct tab *t)
1606 GtkWidget *toolbar = gtk_toolbar_new();
1607 GtkToolItem *i;
1609 #if GTK_CHECK_VERSION(2,15,0)
1610 gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar),
1611 GTK_ORIENTATION_HORIZONTAL);
1612 #else
1613 gtk_toolbar_set_orientation(GTK_TOOLBAR(toolbar),
1614 GTK_ORIENTATION_HORIZONTAL);
1615 #endif
1616 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH_HORIZ);
1618 if (fancy_bar) {
1619 /* backward button */
1620 t->backward = gtk_tool_button_new_from_stock(GTK_STOCK_GO_BACK);
1621 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), FALSE);
1622 g_signal_connect(G_OBJECT(t->backward), "clicked",
1623 G_CALLBACK(backward_cb), t);
1624 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), t->backward, -1);
1626 /* forward button */
1627 t->forward =
1628 gtk_tool_button_new_from_stock(GTK_STOCK_GO_FORWARD);
1629 gtk_widget_set_sensitive(GTK_WIDGET(t->forward), FALSE);
1630 g_signal_connect(G_OBJECT(t->forward), "clicked",
1631 G_CALLBACK(forward_cb), t);
1632 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), t->forward, -1);
1634 /* stop button */
1635 t->stop = gtk_tool_button_new_from_stock(GTK_STOCK_STOP);
1636 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
1637 g_signal_connect(G_OBJECT(t->stop), "clicked",
1638 G_CALLBACK(stop_cb), t);
1639 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), t->stop, -1);
1642 /* uri entry */
1643 i = gtk_tool_item_new();
1644 gtk_tool_item_set_expand(i, TRUE);
1645 t->uri_entry = gtk_entry_new();
1646 gtk_container_add(GTK_CONTAINER(i), t->uri_entry);
1647 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
1648 G_CALLBACK(activate_uri_entry_cb), t);
1649 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), i, -1);
1651 /* search entry */
1652 if (fancy_bar && search_string) {
1653 i = gtk_tool_item_new();
1654 t->search_entry = gtk_entry_new();
1655 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
1656 gtk_container_add(GTK_CONTAINER(i), t->search_entry);
1657 g_signal_connect(G_OBJECT(t->search_entry), "activate",
1658 G_CALLBACK(activate_search_entry_cb), t);
1659 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), i, -1);
1662 return (toolbar);
1665 void
1666 delete_tab(struct tab *t)
1668 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
1670 if (t == NULL)
1671 return;
1673 TAILQ_REMOVE(&tabs, t, entry);
1674 if (TAILQ_EMPTY(&tabs))
1675 create_new_tab(NULL, 1);
1677 gtk_widget_destroy(t->vbox);
1678 g_free(t);
1681 void
1682 setup_webkit(struct tab *t)
1684 gchar *strval;
1685 gchar *ua;
1687 /* XXX this can't be called over and over; fix it */
1688 t->settings = webkit_web_settings_new();
1689 g_object_get((GObject *)t->settings, "user-agent", &strval, NULL);
1690 if (strval == NULL) {
1691 warnx("setup_webkit: can't get user-agent property");
1692 return;
1695 if (asprintf(&ua, "%s %s+", strval, version) == -1)
1696 err(1, "aprintf user-agent");
1698 g_object_set((GObject *)t->settings,
1699 "user-agent", ua, NULL);
1700 g_object_set((GObject *)t->settings,
1701 "enable-scripts", enable_scripts, NULL);
1702 g_object_set((GObject *)t->settings,
1703 "enable-plugins", enable_plugins, NULL);
1704 adjustfont_webkit(t, 0);
1706 webkit_web_view_set_settings(t->wv, t->settings);
1708 g_free (strval);
1709 free(ua);
1712 void
1713 adjustfont_webkit(struct tab *t, int adjust)
1715 if (t == NULL)
1716 errx(1, "adjustfont_webkit");
1718 default_font_size += adjust;
1719 g_object_set((GObject *)t->settings, "default-font-size",
1720 default_font_size, NULL);
1721 g_object_get((GObject *)t->settings, "default-font-size",
1722 &default_font_size, NULL);
1725 void
1726 create_new_tab(char *title, int focus)
1728 struct tab *t;
1729 int load = 1;
1730 char *newuri = NULL;
1732 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
1734 if (tabless && !TAILQ_EMPTY(&tabs)) {
1735 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
1736 return;
1739 t = g_malloc0(sizeof *t);
1740 TAILQ_INSERT_TAIL(&tabs, t, entry);
1742 if (title == NULL) {
1743 title = "(untitled)";
1744 load = 0;
1745 } else {
1746 if (valid_url_type(title)) {
1747 newuri = guess_url_type(title);
1748 title = newuri;
1752 t->vbox = gtk_vbox_new(FALSE, 0);
1754 /* label for tab */
1755 t->label = gtk_label_new(title);
1756 gtk_widget_set_size_request(t->label, 100, -1);
1758 /* toolbar */
1759 t->toolbar = create_toolbar(t);
1760 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
1762 /* browser */
1763 t->browser_win = create_browser(t);
1764 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
1765 setup_webkit(t);
1767 /* command entry */
1768 t->cmd = gtk_entry_new();
1769 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
1770 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
1771 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
1773 /* and show it all */
1774 gtk_widget_show_all(t->vbox);
1775 t->tab_id = gtk_notebook_append_page(notebook, t->vbox,
1776 t->label);
1778 g_object_connect((GObject*)t->cmd,
1779 "signal::key-press-event", (GCallback)cmd_keypress_cb, t,
1780 "signal::key-release-event", (GCallback)cmd_keyrelease_cb, t,
1781 "signal::focus-out-event", (GCallback)cmd_focusout_cb, t,
1782 "signal::activate", (GCallback)cmd_activate_cb, t,
1783 NULL);
1785 g_object_connect((GObject*)t->wv,
1786 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
1787 /* "signal::hovering-over-link", (GCallback)webview_hover_cb, t, */
1788 "signal::download-requested", (GCallback)webview_download_cb, t,
1789 "signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, t,
1790 "signal::navigation-policy-decision-requested", (GCallback)webview_npd_cb, t,
1791 "signal::new-window-policy-decision-requested", (GCallback)webview_nw_cb, t,
1792 "signal::create-web-view", (GCallback)webview_cwv_cb, t,
1793 "signal::event", (GCallback)webview_event_cb, t,
1794 NULL);
1796 /* hijack the unused keys as if we were the browser */
1797 g_object_connect((GObject*)t->toolbar,
1798 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
1799 NULL);
1801 g_signal_connect(G_OBJECT(t->uri_entry), "focus",
1802 G_CALLBACK(focus_uri_entry_cb), t);
1804 /* hide stuff */
1805 gtk_widget_hide(t->cmd);
1806 if (showurl == 0)
1807 gtk_widget_hide(t->toolbar);
1809 if (focus) {
1810 gtk_notebook_set_current_page(notebook, t->tab_id);
1811 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
1812 t->tab_id);
1815 if (load)
1816 webkit_web_view_load_uri(t->wv, title);
1817 else
1818 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
1820 if (newuri)
1821 free(newuri);
1824 void
1825 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
1826 gpointer *udata)
1828 struct tab *t;
1829 const gchar *uri;
1831 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
1833 TAILQ_FOREACH(t, &tabs, entry) {
1834 if (t->tab_id == pn) {
1835 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
1836 "%d\n", pn);
1838 uri = webkit_web_view_get_title(t->wv);
1839 if (uri == NULL)
1840 uri = XT_NAME;
1841 gtk_window_set_title(GTK_WINDOW(main_window), uri);
1843 gtk_widget_hide(t->cmd);
1848 void
1849 create_canvas(void)
1851 GtkWidget *vbox;
1853 vbox = gtk_vbox_new(FALSE, 0);
1854 notebook = GTK_NOTEBOOK(gtk_notebook_new());
1855 if (showtabs == 0)
1856 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
1857 gtk_notebook_set_scrollable(notebook, TRUE);
1859 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
1861 g_object_connect((GObject*)notebook,
1862 "signal::switch-page", (GCallback)notebook_switchpage_cb, NULL,
1863 NULL);
1865 main_window = create_window();
1866 gtk_container_add(GTK_CONTAINER(main_window), vbox);
1867 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
1868 gtk_widget_show_all(main_window);
1871 void
1872 setup_cookies(void)
1874 if (cookiejar) {
1875 soup_session_remove_feature(session,
1876 (SoupSessionFeature*)cookiejar);
1877 g_object_unref(cookiejar);
1878 cookiejar = NULL;
1881 if (cookies_enabled == 0)
1882 return;
1884 cookiejar = soup_cookie_jar_text_new(cookie_file, read_only_cookies);
1885 soup_session_add_feature(session, (SoupSessionFeature*)cookiejar);
1888 void
1889 setup_proxy(char *uri)
1891 if (proxy_uri) {
1892 g_object_set(session, "proxy_uri", NULL, NULL);
1893 soup_uri_free(proxy_uri);
1894 proxy_uri = NULL;
1896 if (http_proxy) {
1897 free(http_proxy);
1898 http_proxy = strdup(uri);
1899 if (http_proxy == NULL)
1900 err(1, "http_proxy");
1903 if (uri == NULL)
1904 return;
1906 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
1907 proxy_uri = soup_uri_new(uri);
1908 if (proxy_uri)
1909 g_object_set(session, "proxy-uri", proxy_uri, NULL);
1912 void
1913 usage(void)
1915 fprintf(stderr,
1916 "%s [-STVt][-f file] url ...\n", __progname);
1917 exit(0);
1921 main(int argc, char *argv[])
1923 struct stat sb;
1924 int c, focus = 1;
1925 char conf[PATH_MAX] = { '\0' };
1926 char *env_proxy = NULL;
1927 FILE *f = NULL;
1929 while ((c = getopt(argc, argv, "STVf:t")) != -1) {
1930 switch (c) {
1931 case 'S':
1932 showurl = 0;
1933 break;
1934 case 'T':
1935 showtabs = 0;
1936 break;
1937 case 'V':
1938 errx(0 , "Version: %s", version);
1939 break;
1940 case 'f':
1941 strlcpy(conf, optarg, sizeof(conf));
1942 break;
1943 case 't':
1944 tabless = 1;
1945 break;
1946 default:
1947 usage();
1948 /* NOTREACHED */
1951 argc -= optind;
1952 argv += optind;
1954 TAILQ_INIT(&tabs);
1956 /* prepare gtk */
1957 gtk_init(&argc, &argv);
1958 if (!g_thread_supported())
1959 g_thread_init(NULL);
1961 pwd = getpwuid(getuid());
1962 if (pwd == NULL)
1963 errx(1, "invalid user %d", getuid());
1965 /* set download dir */
1966 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
1968 /* read config file */
1969 if (strlen(conf) == 0)
1970 snprintf(conf, sizeof conf, "%s/.%s",
1971 pwd->pw_dir, XT_CONF_FILE);
1972 config_parse(conf);
1974 /* download dir */
1975 if (stat(download_dir, &sb))
1976 errx(1, "must specify a valid download_dir");
1977 if (S_ISDIR(sb.st_mode) == 0)
1978 errx(1, "%s not a dir", download_dir);
1979 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
1980 warnx("fixing invalid permissions on %s", download_dir);
1981 if (chmod(download_dir, S_IRWXU) == -1)
1982 err(1, "chmod");
1985 /* working directory */
1986 snprintf(work_dir, sizeof work_dir, "%s/%s", pwd->pw_dir, XT_DIR);
1987 if (stat(work_dir, &sb)) {
1988 if (mkdir(work_dir, S_IRWXU) == -1)
1989 err(1, "mkdir");
1991 if (S_ISDIR(sb.st_mode) == 0)
1992 errx(1, "%s not a dir", work_dir);
1993 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
1994 warnx("fixing invalid permissions on %s", work_dir);
1995 if (chmod(work_dir, S_IRWXU) == -1)
1996 err(1, "chmod");
1999 /* favorites file */
2000 snprintf(work_dir, sizeof work_dir, "%s/%s/%s",
2001 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
2002 if (stat(work_dir, &sb)) {
2003 warnx("favorites file doesn't exist, creating it");
2004 if ((f = fopen(work_dir, "w")) == NULL)
2005 err(1, "favorites");
2006 fclose(f);
2009 /* cookies */
2010 session = webkit_get_default_session();
2011 snprintf(cookie_file, sizeof cookie_file, "%s/cookies.txt", work_dir);
2012 setup_cookies();
2014 /* proxy */
2015 env_proxy = getenv("http_proxy");
2016 if (env_proxy) {
2017 http_proxy = strdup(env_proxy);
2018 if (http_proxy == NULL)
2019 err(1, "http_proxy");
2021 setup_proxy(http_proxy);
2023 create_canvas();
2025 while (argc) {
2026 create_new_tab(argv[0], focus);
2027 focus = 0;
2029 argc--;
2030 argv++;
2032 if (focus == 1)
2033 create_new_tab(home, 1);
2035 gtk_main();
2037 return (0);