catch up
[xxxterm.git] / xxxterm.c
blob2aaa035c62f2935bf89e2b4fe70c59435be4e857
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_PREV (-2)
149 #define XT_TAB_NEXT (-1)
150 #define XT_TAB_INVALID (0)
151 #define XT_TAB_NEW (1)
152 #define XT_TAB_DELETE (2)
153 #define XT_TAB_DELQUIT (3)
154 #define XT_TAB_OPEN (4)
156 #define XT_NAV_INVALID (0)
157 #define XT_NAV_BACK (1)
158 #define XT_NAV_FORWARD (2)
159 #define XT_NAV_RELOAD (3)
161 #define XT_FOCUS_INVALID (0)
162 #define XT_FOCUS_URI (1)
164 #define XT_SEARCH_INVALID (0)
165 #define XT_SEARCH_NEXT (1)
166 #define XT_SEARCH_PREV (2)
168 /* globals */
169 extern char *__progname;
170 struct passwd *pwd;
171 GtkWidget *main_window;
172 GtkNotebook *notebook;
173 struct tab_list tabs;
175 /* mime types */
176 struct mime_type {
177 char *mt_type;
178 char *mt_action;
179 int mt_default;
180 TAILQ_ENTRY(mime_type) entry;
182 TAILQ_HEAD(mime_type_list, mime_type);
184 /* settings */
185 int showtabs = 1; /* show tabs on notebook */
186 int showurl = 1; /* show url toolbar on notebook */
187 int tabless = 0; /* allow only 1 tab */
188 int ctrl_click_focus = 0; /* ctrl click gets focus */
189 int cookies_enabled = 1; /* enable cookies */
190 int read_only_cookies = 0; /* enable to not write cookies */
191 int enable_scripts = 0;
192 int enable_plugins = 0;
193 int default_font_size = 12;
194 int fancy_bar = 1; /* fancy toolbar */
196 char *home = "http://www.peereboom.us";
197 char *search_string = NULL;
198 char *http_proxy = NULL;
199 SoupURI *proxy_uri = NULL;
200 char work_dir[PATH_MAX];
201 char cookie_file[PATH_MAX];
202 char download_dir[PATH_MAX];
203 SoupSession *session;
204 SoupCookieJar *cookiejar;
206 struct mime_type_list mtl;
208 /* protos */
209 void create_new_tab(char *, int);
210 void delete_tab(struct tab *);
211 void adjustfont_webkit(struct tab *, int);
213 struct valid_url_types {
214 char *type;
215 } vut[] = {
216 { "http://" },
217 { "https://" },
218 { "ftp://" },
219 { "file://" },
223 valid_url_type(char *url)
225 int i;
227 for (i = 0; i < LENGTH(vut); i++)
228 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
229 return (0);
231 return (1);
234 char *
235 guess_url_type(char *url_in)
237 struct stat sb;
238 char *url_out = NULL;
240 /* XXX not sure about this heuristic */
241 if (stat(url_in, &sb) == 0) {
242 if (asprintf(&url_out, "file://%s", url_in) == -1)
243 err(1, "aprintf file");
244 } else {
245 /* guess http */
246 if (asprintf(&url_out, "http://%s", url_in) == -1)
247 err(1, "aprintf http");
250 if (url_out == NULL)
251 err(1, "asprintf pointer");
253 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
255 return (url_out);
258 void
259 add_mime_type(char *line)
261 char *mime_type;
262 char *l = NULL;
263 struct mime_type *m;
265 /* XXX this could be smarter */
267 if (line == NULL)
268 errx(1, "add_mime_type");
269 l = line;
271 m = malloc(sizeof(*m));
272 if (m == NULL)
273 err(1, "add_mime_type: malloc");
275 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL)
276 errx(1, "add_mime_type: invalid mime_type");
278 if (mime_type[strlen(mime_type) - 1] == '*') {
279 mime_type[strlen(mime_type) - 1] = '\0';
280 m->mt_default = 1;
281 } else
282 m->mt_default = 0;
284 if (strlen(mime_type) == 0 || strlen(l) == 0)
285 errx(1, "add_mime_type: invalid mime_type");
287 m->mt_type = strdup(mime_type);
288 if (m->mt_type == NULL)
289 err(1, "add_mime_type: malloc type");
291 m->mt_action = strdup(l);
292 if (m->mt_action == NULL)
293 err(1, "add_mime_type: malloc action");
295 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
296 m->mt_type, m->mt_action, m->mt_default);
298 TAILQ_INSERT_TAIL(&mtl, m, entry);
301 struct mime_type *
302 find_mime_type(char *mime_type)
304 struct mime_type *m, *def = NULL, *rv = NULL;
306 TAILQ_FOREACH(m, &mtl, entry) {
307 if (m->mt_default &&
308 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
309 def = m;
311 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
312 rv = m;
313 break;
317 if (rv == NULL)
318 rv = def;
320 return (rv);
323 #define WS "\n= \t"
324 void
325 config_parse(char *filename)
327 FILE *config;
328 char *line, *cp, *var, *val;
329 size_t len, lineno = 0;
331 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
333 TAILQ_INIT(&mtl);
335 if (filename == NULL)
336 return;
338 if ((config = fopen(filename, "r")) == NULL) {
339 warn("config_parse: cannot open %s", filename);
340 return;
343 for (;;) {
344 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
345 if (feof(config))
346 break;
348 cp = line;
349 cp += (long)strspn(cp, WS);
350 if (cp[0] == '\0') {
351 /* empty line */
352 free(line);
353 continue;
356 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
357 break;
359 cp += (long)strspn(cp, WS);
361 if ((val = strsep(&cp, "\0")) == NULL)
362 break;
364 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
366 /* get settings */
367 if (!strcmp(var, "home"))
368 home = strdup(val);
369 else if (!strcmp(var, "ctrl_click_focus"))
370 ctrl_click_focus = atoi(val);
371 else if (!strcmp(var, "read_only_cookies"))
372 read_only_cookies = atoi(val);
373 else if (!strcmp(var, "cookies_enabled"))
374 cookies_enabled = atoi(val);
375 else if (!strcmp(var, "enable_scripts"))
376 enable_scripts = atoi(val);
377 else if (!strcmp(var, "enable_plugins"))
378 enable_plugins = atoi(val);
379 else if (!strcmp(var, "default_font_size"))
380 default_font_size = atoi(val);
381 else if (!strcmp(var, "fancy_bar"))
382 fancy_bar = atoi(val);
383 else if (!strcmp(var, "mime_type"))
384 add_mime_type(val);
385 else if (!strcmp(var, "http_proxy")) {
386 http_proxy = strdup(val);
387 if (http_proxy == NULL)
388 err(1, "http_proxy");
389 } else if (!strcmp(var, "search_string")) {
390 search_string = strdup(val);
391 if (search_string == NULL)
392 err(1, "search_string");
393 } else if (!strcmp(var, "download_dir")) {
394 if (val[0] == '~')
395 snprintf(download_dir, sizeof download_dir,
396 "%s/%s", pwd->pw_dir, &val[1]);
397 else
398 strlcpy(download_dir, val, sizeof download_dir);
399 } else
400 errx(1, "invalid conf file entry: %s=%s", var, val);
402 free(line);
405 fclose(config);
408 quit(struct tab *t, struct karg *args)
410 gtk_main_quit();
412 return (1);
416 focus(struct tab *t, struct karg *args)
418 if (t == NULL)
419 errx(1, "focus");
421 if (args->i == XT_FOCUS_URI)
422 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
424 return (0);
428 help(struct tab *t, struct karg *args)
430 if (t == NULL)
431 errx(1, "help");
433 webkit_web_view_load_string(t->wv,
434 "<html><body><h1>XXXTerm</h1></body></html>",
435 NULL,
436 NULL,
437 NULL);
439 return (0);
443 favorites(struct tab *t, struct karg *args)
445 char file[PATH_MAX];
446 FILE *f, *h;
447 char *uri = NULL, *title = NULL;
448 size_t len, lineno = 0;
449 int i, failed = 0;
451 if (t == NULL)
452 errx(1, "favorites");
454 /* XXX run a digest over the favorites file instead of always generating it */
456 /* open favorites */
457 snprintf(file, sizeof file, "%s/%s/%s",
458 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
459 if ((f = fopen(file, "r")) == NULL) {
460 warn("favorites");
461 return (1);
464 /* open favorites html */
465 snprintf(file, sizeof file, "%s/%s/%s.html",
466 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
467 if ((h = fopen(file, "w+")) == NULL) {
468 warn("favorites.html");
469 return (1);
472 fprintf(h, "<html><body>Favorites:<p>\n<ol>\n");
474 for (i = 1;;) {
475 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
476 if (feof(f))
477 break;
478 if (strlen(title) == 0)
479 continue;
481 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
482 if (feof(f)) {
483 failed = 1;
484 break;
487 fprintf(h, "<li><a href=\"%s\">%s</a><br>\n", uri, title);
489 free(uri);
490 uri = NULL;
491 free(title);
492 title = NULL;
493 i++;
496 if (uri)
497 free(uri);
498 if (title)
499 free(title);
501 fprintf(h, "</ol></body></html>");
502 fclose(f);
503 fclose(h);
505 if (failed) {
506 webkit_web_view_load_string(t->wv,
507 "<html><body>Invalid favorites file</body></html>",
508 NULL,
509 NULL,
510 NULL);
511 } else {
512 snprintf(file, sizeof file, "file://%s/%s/%s.html",
513 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
514 webkit_web_view_load_uri(t->wv, file);
517 return (0);
521 favadd(struct tab *t, struct karg *args)
523 char file[PATH_MAX];
524 FILE *f;
525 WebKitWebFrame *frame;
526 const gchar *uri, *title;
528 if (t == NULL)
529 errx(1, "favadd");
531 snprintf(file, sizeof file, "%s/%s/%s",
532 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
533 if ((f = fopen(file, "r+")) == NULL) {
534 warn("favorites");
535 return (1);
537 if (fseeko(f, 0, SEEK_END) == -1)
538 err(1, "fseeko");
540 title = webkit_web_view_get_title(t->wv);
541 frame = webkit_web_view_get_main_frame(t->wv);
542 uri = webkit_web_frame_get_uri(frame);
543 if (title == NULL)
544 title = uri;
546 if (title == NULL || uri == NULL) {
547 webkit_web_view_load_string(t->wv,
548 "<html><body>can't add page to favorites</body></html>",
549 NULL,
550 NULL,
551 NULL);
552 goto done;
555 fprintf(f, "\n%s\n%s", title, uri);
556 done:
557 fclose(f);
559 return (0);
563 navaction(struct tab *t, struct karg *args)
565 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
566 t->tab_id, args->i);
568 switch (args->i) {
569 case XT_NAV_BACK:
570 webkit_web_view_go_back(t->wv);
571 break;
572 case XT_NAV_FORWARD:
573 webkit_web_view_go_forward(t->wv);
574 break;
575 case XT_NAV_RELOAD:
576 webkit_web_view_reload(t->wv);
577 break;
579 return (XT_CB_PASSTHROUGH);
583 move(struct tab *t, struct karg *args)
585 GtkAdjustment *adjust;
586 double pi, si, pos, ps, upper, lower, max;
588 switch (args->i) {
589 case XT_MOVE_DOWN:
590 case XT_MOVE_UP:
591 case XT_MOVE_BOTTOM:
592 case XT_MOVE_TOP:
593 case XT_MOVE_PAGEDOWN:
594 case XT_MOVE_PAGEUP:
595 adjust = t->adjust_v;
596 break;
597 default:
598 adjust = t->adjust_h;
599 break;
602 pos = gtk_adjustment_get_value(adjust);
603 ps = gtk_adjustment_get_page_size(adjust);
604 upper = gtk_adjustment_get_upper(adjust);
605 lower = gtk_adjustment_get_lower(adjust);
606 si = gtk_adjustment_get_step_increment(adjust);
607 pi = gtk_adjustment_get_page_increment(adjust);
608 max = upper - ps;
610 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
611 "max %f si %f pi %f\n",
612 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
613 pos, ps, upper, lower, max, si, pi);
615 switch (args->i) {
616 case XT_MOVE_DOWN:
617 case XT_MOVE_RIGHT:
618 pos += si;
619 gtk_adjustment_set_value(adjust, MIN(pos, max));
620 break;
621 case XT_MOVE_UP:
622 case XT_MOVE_LEFT:
623 pos -= si;
624 gtk_adjustment_set_value(adjust, MAX(pos, lower));
625 break;
626 case XT_MOVE_BOTTOM:
627 case XT_MOVE_FARRIGHT:
628 gtk_adjustment_set_value(adjust, max);
629 break;
630 case XT_MOVE_TOP:
631 case XT_MOVE_FARLEFT:
632 gtk_adjustment_set_value(adjust, lower);
633 break;
634 case XT_MOVE_PAGEDOWN:
635 pos += pi;
636 gtk_adjustment_set_value(adjust, MIN(pos, max));
637 break;
638 case XT_MOVE_PAGEUP:
639 pos -= pi;
640 gtk_adjustment_set_value(adjust, MAX(pos, lower));
641 break;
642 default:
643 return (XT_CB_PASSTHROUGH);
646 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
648 return (XT_CB_HANDLED);
651 char *
652 getparams(char *cmd, char *cmp)
654 char *rv = NULL;
656 if (cmd && cmp) {
657 if (!strncmp(cmd, cmp, strlen(cmp))) {
658 rv = cmd + strlen(cmp);
659 while (*rv == ' ')
660 rv++;
661 if (strlen(rv) == 0)
662 rv = NULL;
666 return (rv);
670 tabaction(struct tab *t, struct karg *args)
672 int rv = XT_CB_HANDLED;
673 char *url = NULL, *newuri = NULL;
675 DNPRINTF(XT_D_TAB, "tabaction: %p %d %d\n", t, args->i, t->focus_wv);
677 if (t == NULL)
678 return (XT_CB_PASSTHROUGH);
680 switch (args->i) {
681 case XT_TAB_NEW:
682 if ((url = getparams(args->s, "tabnew")))
683 create_new_tab(url, 1);
684 else
685 create_new_tab(NULL, 1);
686 break;
687 case XT_TAB_DELETE:
688 delete_tab(t);
689 break;
690 case XT_TAB_DELQUIT:
691 if (gtk_notebook_get_n_pages(notebook) > 1)
692 delete_tab(t);
693 else
694 quit(t, args);
695 break;
696 case XT_TAB_OPEN:
697 if ((url = getparams(args->s, "open")) ||
698 ((url = getparams(args->s, "op"))) ||
699 ((url = getparams(args->s, "o"))))
701 else {
702 rv = XT_CB_PASSTHROUGH;
703 goto done;
706 if (valid_url_type(url)) {
707 newuri = guess_url_type(url);
708 url = newuri;
710 webkit_web_view_load_uri(t->wv, url);
711 if (newuri)
712 free(newuri);
713 break;
714 default:
715 rv = XT_CB_PASSTHROUGH;
716 goto done;
719 done:
720 if (args->s) {
721 free(args->s);
722 args->s = NULL;
725 return (rv);
729 resizetab(struct tab *t, struct karg *args)
731 if (t == NULL || args == NULL)
732 errx(1, "resizetab");
734 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
735 t->tab_id, args->i);
737 if (t == NULL)
738 return (XT_CB_PASSTHROUGH);
740 adjustfont_webkit(t, args->i);
742 return (XT_CB_HANDLED);
746 movetab(struct tab *t, struct karg *args)
748 struct tab *tt;
749 int x;
751 if (t == NULL || args == NULL)
752 return (XT_CB_PASSTHROUGH);
754 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
755 t->tab_id, args->i);
757 if (args->i == XT_TAB_INVALID)
758 return (XT_CB_PASSTHROUGH);
760 if (args->i < XT_TAB_INVALID) {
761 /* next or previous tab */
762 if (TAILQ_EMPTY(&tabs))
763 return (XT_CB_PASSTHROUGH);
765 if (args->i == XT_TAB_NEXT)
766 gtk_notebook_next_page(notebook);
767 else
768 gtk_notebook_prev_page(notebook);
770 return (XT_CB_HANDLED);
773 /* jump to tab */
774 x = args->i - 1;
775 if (t->tab_id == x) {
776 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
777 return (XT_CB_HANDLED);
780 TAILQ_FOREACH(tt, &tabs, entry) {
781 if (tt->tab_id == x) {
782 gtk_notebook_set_current_page(notebook, x);
783 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
784 if (tt->focus_wv)
785 gtk_widget_grab_focus(GTK_WIDGET(tt->wv));
789 return (XT_CB_HANDLED);
793 command(struct tab *t, struct karg *args)
795 char *s = NULL;
796 GdkColor color;
798 if (t == NULL || args == NULL)
799 errx(1, "command");
801 if (args->i == '/')
802 s = "/";
803 else if (args->i == '?')
804 s = "?";
805 else if (args->i == ':')
806 s = ":";
807 else {
808 warnx("invalid command %c\n", args->i);
809 return (XT_CB_PASSTHROUGH);
812 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
814 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
815 gdk_color_parse("white", &color);
816 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
817 gtk_widget_show(t->cmd);
818 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
819 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
821 return (XT_CB_HANDLED);
825 search(struct tab *t, struct karg *args)
827 gboolean d;
829 if (t == NULL || args == NULL)
830 errx(1, "search");
831 if (t->search_text == NULL)
832 return (XT_CB_PASSTHROUGH);
834 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
835 t->tab_id, args->i, t->search_forward, t->search_text);
837 switch (args->i) {
838 case XT_SEARCH_NEXT:
839 d = t->search_forward;
840 break;
841 case XT_SEARCH_PREV:
842 d = !t->search_forward;
843 break;
844 default:
845 return (XT_CB_PASSTHROUGH);
848 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
850 return (XT_CB_HANDLED);
853 /* inherent to GTK not all keys will be caught at all times */
854 struct key {
855 guint mask;
856 guint modkey;
857 guint key;
858 int (*func)(struct tab *, struct karg *);
859 struct karg arg;
860 } keys[] = {
861 { 0, 0, GDK_slash, command, {.i = '/'} },
862 { GDK_SHIFT_MASK, 0, GDK_question, command, {.i = '?'} },
863 { GDK_SHIFT_MASK, 0, GDK_colon, command, {.i = ':'} },
864 { GDK_CONTROL_MASK, 0, GDK_q, quit, {0} },
866 /* search */
867 { 0, 0, GDK_n, search, {.i = XT_SEARCH_NEXT} },
868 { GDK_SHIFT_MASK, 0, GDK_N, search, {.i = XT_SEARCH_PREV} },
870 /* focus */
871 { 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} },
873 /* navigation */
874 { 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
875 { GDK_MOD1_MASK, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
876 { GDK_SHIFT_MASK, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
877 { GDK_MOD1_MASK, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
878 { 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} },
879 { GDK_CONTROL_MASK, 0, GDK_r, navaction, {.i = XT_NAV_RELOAD} },
880 { GDK_CONTROL_MASK, 0, GDK_l, navaction, {.i = XT_NAV_RELOAD} },
882 /* vertical movement */
883 { 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
884 { 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
885 { 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
886 { 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
887 { GDK_SHIFT_MASK, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
888 { 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
889 { 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
890 { 0, GDK_g, GDK_g, move, {.i = XT_MOVE_TOP} }, /* XXX make this work */
891 { 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
892 { GDK_CONTROL_MASK, 0, GDK_f, move, {.i = XT_MOVE_PAGEDOWN} },
893 { 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
894 { 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
895 { GDK_CONTROL_MASK, 0, GDK_b, move, {.i = XT_MOVE_PAGEUP} },
896 /* horizontal movement */
897 { 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
898 { 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
899 { 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
900 { 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
901 { GDK_SHIFT_MASK, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
902 { 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
904 /* tabs */
905 { GDK_CONTROL_MASK, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
906 { GDK_CONTROL_MASK, 0, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
907 { GDK_CONTROL_MASK, 0, GDK_1, movetab, {.i = 1} },
908 { GDK_CONTROL_MASK, 0, GDK_2, movetab, {.i = 2} },
909 { GDK_CONTROL_MASK, 0, GDK_3, movetab, {.i = 3} },
910 { GDK_CONTROL_MASK, 0, GDK_4, movetab, {.i = 4} },
911 { GDK_CONTROL_MASK, 0, GDK_5, movetab, {.i = 5} },
912 { GDK_CONTROL_MASK, 0, GDK_6, movetab, {.i = 6} },
913 { GDK_CONTROL_MASK, 0, GDK_7, movetab, {.i = 7} },
914 { GDK_CONTROL_MASK, 0, GDK_8, movetab, {.i = 8} },
915 { GDK_CONTROL_MASK, 0, GDK_9, movetab, {.i = 9} },
916 { GDK_CONTROL_MASK, 0, GDK_0, movetab, {.i = 10} },
917 { GDK_CONTROL_MASK, 0, GDK_minus, resizetab, {.i = -1} },
918 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_plus, resizetab, {.i = 1} },
919 { GDK_CONTROL_MASK, 0, GDK_equal, resizetab, {.i = 1} },
922 struct cmd {
923 char *cmd;
924 int params;
925 int (*func)(struct tab *, struct karg *);
926 struct karg arg;
927 } cmds[] = {
928 { "q!", 0, quit, {0} },
929 { "qa", 0, quit, {0} },
930 { "qa!", 0, quit, {0} },
931 { "help", 0, help, {0} },
933 /* favorites */
934 { "fav", 0, favorites, {0} },
935 { "favadd", 0, favadd, {0} },
937 /* tabs */
938 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
939 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
940 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
941 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
942 { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} },
943 { "tabe", 1, tabaction, {.i = XT_TAB_NEW} },
944 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
945 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} },
946 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
947 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
948 /* XXX add count to these commands and add tabl and friends */
949 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
950 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
951 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
952 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
955 void
956 focus_uri_entry_cb(GtkWidget* w, GtkDirectionType direction, struct tab *t)
958 DNPRINTF(XT_D_URL, "focus_uri_entry_cb: tab %d focus_wv %d\n",
959 t->tab_id, t->focus_wv);
961 if (t == NULL)
962 errx(1, "focus_uri_entry_cb");
964 /* focus on wv instead */
965 if (t->focus_wv)
966 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
969 void
970 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
972 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
973 char *newuri = NULL;
975 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
977 if (t == NULL)
978 errx(1, "activate_uri_entry_cb");
980 if (uri == NULL)
981 errx(1, "uri");
983 if (valid_url_type((char *)uri)) {
984 newuri = guess_url_type((char *)uri);
985 uri = newuri;
988 webkit_web_view_load_uri(t->wv, uri);
989 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
991 if (newuri)
992 free(newuri);
995 void
996 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
998 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
999 char *newuri = NULL;
1001 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
1003 if (t == NULL)
1004 errx(1, "activate_search_entry_cb");
1006 if (search_string == NULL) {
1007 warnx("no search_string");
1008 return;
1011 if (asprintf(&newuri, search_string, search) == -1)
1012 err(1, "activate_search_entry_cb");
1014 webkit_web_view_load_uri(t->wv, newuri);
1015 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1017 if (newuri)
1018 free(newuri);
1021 void
1022 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
1024 WebKitWebFrame *frame;
1025 const gchar *uri;
1027 if (t == NULL)
1028 errx(1, "notify_load_status_cb");
1030 switch (webkit_web_view_get_load_status(wview)) {
1031 case WEBKIT_LOAD_COMMITTED:
1032 frame = webkit_web_view_get_main_frame(wview);
1033 uri = webkit_web_frame_get_uri(frame);
1034 if (uri)
1035 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
1037 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
1038 t->focus_wv = 1;
1040 /* take focus if we are visible */
1041 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
1042 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1043 break;
1044 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
1045 uri = webkit_web_view_get_title(wview);
1046 if (uri == NULL) {
1047 frame = webkit_web_view_get_main_frame(wview);
1048 uri = webkit_web_frame_get_uri(frame);
1050 gtk_label_set_text(GTK_LABEL(t->label), uri);
1051 gtk_window_set_title(GTK_WINDOW(main_window), uri);
1052 break;
1053 case WEBKIT_LOAD_PROVISIONAL:
1054 case WEBKIT_LOAD_FINISHED:
1055 #if WEBKIT_CHECK_VERSION(1, 1, 18)
1056 case WEBKIT_LOAD_FAILED:
1057 #endif
1058 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
1059 default:
1060 break;
1063 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
1064 webkit_web_view_can_go_back(wview));
1066 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
1067 webkit_web_view_can_go_forward(wview));
1071 webview_nw_cb(WebKitWebView *wv, WebKitWebFrame *wf,
1072 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
1073 WebKitWebPolicyDecision *pd, struct tab *t)
1075 char *uri;
1077 if (t == NULL)
1078 errx(1, "webview_nw_cb");
1080 DNPRINTF(XT_D_NAV, "webview_nw_cb: %s\n",
1081 webkit_network_request_get_uri(request));
1083 /* open in current tab */
1084 uri = (char *)webkit_network_request_get_uri(request);
1085 webkit_web_view_load_uri(t->wv, uri);
1086 webkit_web_policy_decision_ignore(pd);
1088 return (TRUE); /* we made the decission */
1092 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
1093 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
1094 WebKitWebPolicyDecision *pd, struct tab *t)
1096 char *uri;
1098 if (t == NULL)
1099 errx(1, "webview_npd_cb");
1101 DNPRINTF(XT_D_NAV, "webview_npd_cb: %s\n",
1102 webkit_network_request_get_uri(request));
1104 uri = (char *)webkit_network_request_get_uri(request);
1105 if (t->ctrl_click) {
1106 t->ctrl_click = 0;
1107 create_new_tab(uri, ctrl_click_focus);
1108 webkit_web_policy_decision_ignore(pd);
1109 return (TRUE); /* we made the decission */
1112 webkit_web_policy_decision_use(pd);
1113 return (TRUE); /* we made the decission */
1116 WebKitWebView *
1117 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
1119 if (t == NULL)
1120 errx(1, "webview_cwv_cb");
1122 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
1123 webkit_web_view_get_uri(wv));
1125 return (wv);
1129 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
1131 /* we can not eat the event without throwing gtk off so defer it */
1133 /* catch ctrl click */
1134 if (e->type == GDK_BUTTON_RELEASE &&
1135 CLEAN(e->state) == GDK_CONTROL_MASK)
1136 t->ctrl_click = 1;
1137 else
1138 t->ctrl_click = 0;
1140 return (XT_CB_PASSTHROUGH);
1144 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
1146 struct mime_type *m;
1148 m = find_mime_type(mime_type);
1149 if (m == NULL)
1150 return (1);
1152 switch (fork()) {
1153 case -1:
1154 err(1, "fork");
1155 /* NOTREACHED */
1156 case 0:
1157 break;
1158 default:
1159 return (0);
1162 /* child */
1163 execlp(m->mt_action, m->mt_action,
1164 webkit_network_request_get_uri(request), (void *)NULL);
1166 _exit(0);
1168 /* NOTREACHED */
1169 return (0);
1173 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
1174 WebKitNetworkRequest *request, char *mime_type,
1175 WebKitWebPolicyDecision *decision, struct tab *t)
1177 if (t == NULL)
1178 errx(1, "webview_mimetype_cb");
1180 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
1181 t->tab_id, mime_type);
1183 if (run_mimehandler(t, mime_type, request) == 0) {
1184 webkit_web_policy_decision_ignore(decision);
1185 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1186 return (TRUE);
1189 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
1190 webkit_web_policy_decision_download(decision);
1191 return (TRUE);
1194 return (FALSE);
1198 webview_download_cb(WebKitWebView *wv, WebKitDownload *download, struct tab *t)
1200 const gchar *filename;
1201 char *uri = NULL;
1203 if (download == NULL || t == NULL)
1204 errx(1, "webview_download_cb: invalid pointers");
1206 filename = webkit_download_get_suggested_filename(download);
1207 if (filename == NULL)
1208 return (FALSE); /* abort download */
1210 if (asprintf(&uri, "file://%s/%s", download_dir, filename) == -1)
1211 err(1, "aprintf uri");
1213 DNPRINTF(XT_D_DOWNLOAD, "webview_download_cb: tab %d filename %s "
1214 "local %s\n",
1215 t->tab_id, filename, uri);
1217 webkit_download_set_destination_uri(download, uri);
1219 if (uri)
1220 free(uri);
1222 webkit_download_start(download);
1224 return (TRUE); /* start download */
1227 /* XXX currently unused */
1228 void
1229 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
1231 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
1233 if (t == NULL)
1234 errx(1, "webview_hover_cb");
1236 if (uri) {
1237 if (t->hover) {
1238 free(t->hover);
1239 t->hover = NULL;
1241 t->hover = strdup(uri);
1242 } else if (t->hover) {
1243 free(t->hover);
1244 t->hover = NULL;
1249 webview_keypress_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
1251 int i;
1253 /* don't use w directly; use t->whatever instead */
1255 if (t == NULL)
1256 errx(1, "webview_keypress_cb");
1258 DNPRINTF(XT_D_KEY, "webview_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
1259 e->keyval, e->state, t);
1261 for (i = 0; i < LENGTH(keys); i++)
1262 if (e->keyval == keys[i].key && CLEAN(e->state) ==
1263 keys[i].mask) {
1264 keys[i].func(t, &keys[i].arg);
1265 return (XT_CB_HANDLED);
1268 return (XT_CB_PASSTHROUGH);
1272 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
1274 const gchar *c = gtk_entry_get_text(w);
1275 GdkColor color;
1276 int forward = TRUE;
1278 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
1279 e->keyval, e->state, t);
1281 if (t == NULL)
1282 errx(1, "cmd_keyrelease_cb");
1284 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
1285 e->keyval, e->state, t);
1287 if (c[0] == ':')
1288 goto done;
1289 if (strlen(c) == 1)
1290 goto done;
1292 if (c[0] == '/')
1293 forward = TRUE;
1294 else if (c[0] == '?')
1295 forward = FALSE;
1296 else
1297 goto done;
1299 /* search */
1300 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
1301 FALSE) {
1302 /* not found, mark red */
1303 gdk_color_parse("red", &color);
1304 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
1305 /* unmark and remove selection */
1306 webkit_web_view_unmark_text_matches(t->wv);
1307 /* my kingdom for a way to unselect text in webview */
1308 } else {
1309 /* found, highlight all */
1310 webkit_web_view_unmark_text_matches(t->wv);
1311 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
1312 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
1313 gdk_color_parse("white", &color);
1314 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
1316 done:
1317 return (XT_CB_PASSTHROUGH);
1321 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
1323 int rv = XT_CB_HANDLED;
1324 const gchar *c = gtk_entry_get_text(w);
1326 if (t == NULL)
1327 errx(1, "cmd_keypress_cb");
1329 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
1330 e->keyval, e->state, t);
1332 /* sanity */
1333 if (c == NULL)
1334 e->keyval = GDK_Escape;
1335 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
1336 e->keyval = GDK_Escape;
1338 switch (e->keyval) {
1339 case GDK_BackSpace:
1340 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
1341 break;
1342 /* FALLTHROUGH */
1343 case GDK_Escape:
1344 gtk_widget_hide(t->cmd);
1345 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1346 goto done;
1349 rv = XT_CB_PASSTHROUGH;
1350 done:
1351 return (rv);
1355 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
1357 if (t == NULL)
1358 errx(1, "cmd_focusout_cb");
1360 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d focus_wv %d\n",
1361 t->tab_id, t->focus_wv);
1363 /* abort command when losing focus */
1364 gtk_widget_hide(t->cmd);
1365 if (t->focus_wv)
1366 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
1367 else
1368 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
1370 return (XT_CB_PASSTHROUGH);
1373 void
1374 cmd_activate_cb(GtkEntry *entry, struct tab *t)
1376 int i;
1377 char *s;
1378 const gchar *c = gtk_entry_get_text(entry);
1380 if (t == NULL)
1381 errx(1, "cmd_activate_cb");
1383 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
1385 /* sanity */
1386 if (c == NULL)
1387 goto done;
1388 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
1389 goto done;
1390 if (strlen(c) < 2)
1391 goto done;
1392 s = (char *)&c[1];
1394 if (c[0] == '/' || c[0] == '?') {
1395 if (t->search_text) {
1396 free(t->search_text);
1397 t->search_text = NULL;
1400 t->search_text = strdup(s);
1401 if (t->search_text == NULL)
1402 err(1, "search_text");
1404 t->search_forward = c[0] == '/';
1406 goto done;
1409 for (i = 0; i < LENGTH(cmds); i++)
1410 if (cmds[i].params) {
1411 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
1412 cmds[i].arg.s = strdup(s);
1413 cmds[i].func(t, &cmds[i].arg);
1415 } else {
1416 if (!strcmp(s, cmds[i].cmd))
1417 cmds[i].func(t, &cmds[i].arg);
1420 done:
1421 gtk_widget_hide(t->cmd);
1424 void
1425 backward_cb(GtkWidget *w, struct tab *t)
1427 if (t == NULL)
1428 errx(1, "backward_cb");
1430 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
1432 webkit_web_view_go_back(t->wv);
1435 void
1436 forward_cb(GtkWidget *w, struct tab *t)
1438 if (t == NULL)
1439 errx(1, "forward_cb");
1441 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
1443 webkit_web_view_go_forward(t->wv);
1446 void
1447 stop_cb(GtkWidget *w, struct tab *t)
1449 WebKitWebFrame *frame;
1451 if (t == NULL)
1452 errx(1, "stop_cb");
1454 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
1456 frame = webkit_web_view_get_main_frame(t->wv);
1457 if (frame == NULL) {
1458 warnx("stop_cb: no frame");
1459 return;
1462 webkit_web_frame_stop_loading(frame);
1465 GtkWidget *
1466 create_browser(struct tab *t)
1468 GtkWidget *w;
1470 if (t == NULL)
1471 errx(1, "create_browser");
1473 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
1474 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
1475 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
1476 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
1478 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
1479 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
1480 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1482 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
1483 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
1485 g_signal_connect(t->wv, "notify::load-status",
1486 G_CALLBACK(notify_load_status_cb), t);
1488 return (w);
1491 GtkWidget *
1492 create_window(void)
1494 GtkWidget *w;
1496 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1497 gtk_window_set_default_size(GTK_WINDOW(w), 800, 600);
1498 gtk_widget_set_name(w, "xxxterm");
1499 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
1500 g_signal_connect(G_OBJECT(w), "delete_event",
1501 G_CALLBACK (gtk_main_quit), NULL);
1503 return (w);
1506 GtkWidget *
1507 create_toolbar(struct tab *t)
1509 GtkWidget *toolbar = gtk_toolbar_new();
1510 GtkToolItem *i;
1512 #if GTK_CHECK_VERSION(2,15,0)
1513 gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar),
1514 GTK_ORIENTATION_HORIZONTAL);
1515 #else
1516 gtk_toolbar_set_orientation(GTK_TOOLBAR(toolbar),
1517 GTK_ORIENTATION_HORIZONTAL);
1518 #endif
1519 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH_HORIZ);
1521 if (fancy_bar) {
1522 /* backward button */
1523 t->backward = gtk_tool_button_new_from_stock(GTK_STOCK_GO_BACK);
1524 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), FALSE);
1525 g_signal_connect(G_OBJECT(t->backward), "clicked",
1526 G_CALLBACK(backward_cb), t);
1527 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), t->backward, -1);
1529 /* forward button */
1530 t->forward =
1531 gtk_tool_button_new_from_stock(GTK_STOCK_GO_FORWARD);
1532 gtk_widget_set_sensitive(GTK_WIDGET(t->forward), FALSE);
1533 g_signal_connect(G_OBJECT(t->forward), "clicked",
1534 G_CALLBACK(forward_cb), t);
1535 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), t->forward, -1);
1537 /* stop button */
1538 t->stop = gtk_tool_button_new_from_stock(GTK_STOCK_STOP);
1539 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
1540 g_signal_connect(G_OBJECT(t->stop), "clicked",
1541 G_CALLBACK(stop_cb), t);
1542 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), t->stop, -1);
1545 /* uri entry */
1546 i = gtk_tool_item_new();
1547 gtk_tool_item_set_expand(i, TRUE);
1548 t->uri_entry = gtk_entry_new();
1549 gtk_container_add(GTK_CONTAINER(i), t->uri_entry);
1550 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
1551 G_CALLBACK(activate_uri_entry_cb), t);
1552 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), i, -1);
1554 /* search entry */
1555 if (fancy_bar && search_string) {
1556 i = gtk_tool_item_new();
1557 t->search_entry = gtk_entry_new();
1558 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
1559 gtk_container_add(GTK_CONTAINER(i), t->search_entry);
1560 g_signal_connect(G_OBJECT(t->search_entry), "activate",
1561 G_CALLBACK(activate_search_entry_cb), t);
1562 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), i, -1);
1565 return (toolbar);
1568 void
1569 delete_tab(struct tab *t)
1571 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
1573 if (t == NULL)
1574 return;
1576 TAILQ_REMOVE(&tabs, t, entry);
1577 if (TAILQ_EMPTY(&tabs))
1578 create_new_tab(NULL, 1);
1580 gtk_widget_destroy(t->vbox);
1581 g_free(t);
1584 void
1585 setup_webkit(struct tab *t)
1587 gchar *strval;
1588 gchar *ua;
1590 /* XXX this can't be called over and over; fix it */
1591 t->settings = webkit_web_settings_new();
1592 g_object_get((GObject *)t->settings, "user-agent", &strval, NULL);
1593 if (strval == NULL) {
1594 warnx("setup_webkit: can't get user-agent property");
1595 return;
1598 if (asprintf(&ua, "%s %s+", strval, version) == -1)
1599 err(1, "aprintf user-agent");
1601 g_object_set((GObject *)t->settings,
1602 "user-agent", ua, NULL);
1603 g_object_set((GObject *)t->settings,
1604 "enable-scripts", enable_scripts, NULL);
1605 g_object_set((GObject *)t->settings,
1606 "enable-plugins", enable_plugins, NULL);
1607 adjustfont_webkit(t, 0);
1609 webkit_web_view_set_settings(t->wv, t->settings);
1611 g_free (strval);
1612 free(ua);
1615 void
1616 adjustfont_webkit(struct tab *t, int adjust)
1618 if (t == NULL)
1619 errx(1, "adjustfont_webkit");
1621 default_font_size += adjust;
1622 g_object_set((GObject *)t->settings, "default-font-size",
1623 default_font_size, NULL);
1624 g_object_get((GObject *)t->settings, "default-font-size",
1625 &default_font_size, NULL);
1628 void
1629 create_new_tab(char *title, int focus)
1631 struct tab *t;
1632 int load = 1;
1633 char *newuri = NULL;
1635 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
1637 if (tabless && !TAILQ_EMPTY(&tabs)) {
1638 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
1639 return;
1642 t = g_malloc0(sizeof *t);
1643 TAILQ_INSERT_TAIL(&tabs, t, entry);
1645 if (title == NULL) {
1646 title = "(untitled)";
1647 load = 0;
1648 } else {
1649 if (valid_url_type(title)) {
1650 newuri = guess_url_type(title);
1651 title = newuri;
1655 t->vbox = gtk_vbox_new(FALSE, 0);
1657 /* label for tab */
1658 t->label = gtk_label_new(title);
1659 gtk_widget_set_size_request(t->label, 100, -1);
1661 /* toolbar */
1662 t->toolbar = create_toolbar(t);
1663 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
1665 /* browser */
1666 t->browser_win = create_browser(t);
1667 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
1668 setup_webkit(t);
1670 /* command entry */
1671 t->cmd = gtk_entry_new();
1672 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
1673 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
1674 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
1676 /* and show it all */
1677 gtk_widget_show_all(t->vbox);
1678 t->tab_id = gtk_notebook_append_page(notebook, t->vbox,
1679 t->label);
1681 g_object_connect((GObject*)t->cmd,
1682 "signal::key-press-event", (GCallback)cmd_keypress_cb, t,
1683 "signal::key-release-event", (GCallback)cmd_keyrelease_cb, t,
1684 "signal::focus-out-event", (GCallback)cmd_focusout_cb, t,
1685 "signal::activate", (GCallback)cmd_activate_cb, t,
1686 NULL);
1688 g_object_connect((GObject*)t->wv,
1689 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
1690 /* "signal::hovering-over-link", (GCallback)webview_hover_cb, t, */
1691 "signal::download-requested", (GCallback)webview_download_cb, t,
1692 "signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, t,
1693 "signal::navigation-policy-decision-requested", (GCallback)webview_npd_cb, t,
1694 "signal::new-window-policy-decision-requested", (GCallback)webview_nw_cb, t,
1695 "signal::create-web-view", (GCallback)webview_cwv_cb, t,
1696 "signal::event", (GCallback)webview_event_cb, t,
1697 NULL);
1699 /* hijack the unused keys as if we were the browser */
1700 g_object_connect((GObject*)t->toolbar,
1701 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
1702 NULL);
1704 g_signal_connect(G_OBJECT(t->uri_entry), "focus",
1705 G_CALLBACK(focus_uri_entry_cb), t);
1707 /* hide stuff */
1708 gtk_widget_hide(t->cmd);
1709 if (showurl == 0)
1710 gtk_widget_hide(t->toolbar);
1712 if (focus) {
1713 gtk_notebook_set_current_page(notebook, t->tab_id);
1714 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
1715 t->tab_id);
1718 if (load)
1719 webkit_web_view_load_uri(t->wv, title);
1720 else
1721 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
1723 if (newuri)
1724 free(newuri);
1727 void
1728 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
1729 gpointer *udata)
1731 struct tab *t;
1732 const gchar *uri;
1734 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
1736 TAILQ_FOREACH(t, &tabs, entry) {
1737 if (t->tab_id == pn) {
1738 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
1739 "%d\n", pn);
1741 uri = webkit_web_view_get_title(t->wv);
1742 if (uri == NULL)
1743 uri = XT_NAME;
1744 gtk_window_set_title(GTK_WINDOW(main_window), uri);
1746 gtk_widget_hide(t->cmd);
1751 void
1752 create_canvas(void)
1754 GtkWidget *vbox;
1756 vbox = gtk_vbox_new(FALSE, 0);
1757 notebook = GTK_NOTEBOOK(gtk_notebook_new());
1758 if (showtabs == 0)
1759 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
1760 gtk_notebook_set_scrollable(notebook, TRUE);
1762 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
1764 g_object_connect((GObject*)notebook,
1765 "signal::switch-page", (GCallback)notebook_switchpage_cb, NULL,
1766 NULL);
1768 main_window = create_window();
1769 gtk_container_add(GTK_CONTAINER(main_window), vbox);
1770 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
1771 gtk_widget_show_all(main_window);
1774 void
1775 setup_cookies(void)
1777 if (cookiejar) {
1778 soup_session_remove_feature(session,
1779 (SoupSessionFeature*)cookiejar);
1780 g_object_unref(cookiejar);
1781 cookiejar = NULL;
1784 if (cookies_enabled == 0)
1785 return;
1787 cookiejar = soup_cookie_jar_text_new(cookie_file, read_only_cookies);
1788 soup_session_add_feature(session, (SoupSessionFeature*)cookiejar);
1791 void
1792 setup_proxy(char *uri)
1794 if (proxy_uri) {
1795 g_object_set(session, "proxy_uri", NULL, NULL);
1796 soup_uri_free(proxy_uri);
1797 proxy_uri = NULL;
1799 if (http_proxy) {
1800 free(http_proxy);
1801 http_proxy = strdup(uri);
1802 if (http_proxy == NULL)
1803 err(1, "http_proxy");
1806 if (uri == NULL)
1807 return;
1809 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
1810 proxy_uri = soup_uri_new(uri);
1811 if (proxy_uri)
1812 g_object_set(session, "proxy-uri", proxy_uri, NULL);
1815 void
1816 usage(void)
1818 fprintf(stderr,
1819 "%s [-STVt][-f file] url ...\n", __progname);
1820 exit(0);
1824 main(int argc, char *argv[])
1826 struct stat sb;
1827 int c, focus = 1;
1828 char conf[PATH_MAX] = { '\0' };
1829 char *env_proxy = NULL;
1830 FILE *f = NULL;
1832 while ((c = getopt(argc, argv, "STVf:t")) != -1) {
1833 switch (c) {
1834 case 'S':
1835 showurl = 0;
1836 break;
1837 case 'T':
1838 showtabs = 0;
1839 break;
1840 case 'V':
1841 errx(0 , "Version: %s", version);
1842 break;
1843 case 'f':
1844 strlcpy(conf, optarg, sizeof(conf));
1845 break;
1846 case 't':
1847 tabless = 1;
1848 break;
1849 default:
1850 usage();
1851 /* NOTREACHED */
1854 argc -= optind;
1855 argv += optind;
1857 TAILQ_INIT(&tabs);
1859 /* prepare gtk */
1860 gtk_init(&argc, &argv);
1861 if (!g_thread_supported())
1862 g_thread_init(NULL);
1864 pwd = getpwuid(getuid());
1865 if (pwd == NULL)
1866 errx(1, "invalid user %d", getuid());
1868 /* set download dir */
1869 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
1871 /* read config file */
1872 if (strlen(conf) == 0)
1873 snprintf(conf, sizeof conf, "%s/.%s",
1874 pwd->pw_dir, XT_CONF_FILE);
1875 config_parse(conf);
1877 /* download dir */
1878 if (stat(download_dir, &sb))
1879 errx(1, "must specify a valid download_dir");
1880 if (S_ISDIR(sb.st_mode) == 0)
1881 errx(1, "%s not a dir", download_dir);
1882 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
1883 warnx("fixing invalid permissions on %s", download_dir);
1884 if (chmod(download_dir, S_IRWXU) == -1)
1885 err(1, "chmod");
1888 /* working directory */
1889 snprintf(work_dir, sizeof work_dir, "%s/%s", pwd->pw_dir, XT_DIR);
1890 if (stat(work_dir, &sb)) {
1891 if (mkdir(work_dir, S_IRWXU) == -1)
1892 err(1, "mkdir");
1894 if (S_ISDIR(sb.st_mode) == 0)
1895 errx(1, "%s not a dir", work_dir);
1896 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
1897 warnx("fixing invalid permissions on %s", work_dir);
1898 if (chmod(work_dir, S_IRWXU) == -1)
1899 err(1, "chmod");
1902 /* favorites file */
1903 snprintf(work_dir, sizeof work_dir, "%s/%s/%s",
1904 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
1905 if (stat(work_dir, &sb)) {
1906 warnx("favorites file doesn't exist, creating it");
1907 if ((f = fopen(work_dir, "w")) == NULL)
1908 err(1, "favorites");
1909 fclose(f);
1912 /* cookies */
1913 session = webkit_get_default_session();
1914 snprintf(cookie_file, sizeof cookie_file, "%s/cookies.txt", work_dir);
1915 setup_cookies();
1917 /* proxy */
1918 env_proxy = getenv("http_proxy");
1919 if (env_proxy) {
1920 http_proxy = strdup(env_proxy);
1921 if (http_proxy == NULL)
1922 err(1, "http_proxy");
1924 setup_proxy(http_proxy);
1926 create_canvas();
1928 while (argc) {
1929 create_new_tab(argv[0], focus);
1930 focus = 0;
1932 argc--;
1933 argv++;
1935 if (focus == 1)
1936 create_new_tab(home, 1);
1938 gtk_main();
1940 return (0);