Add cookie enable and read only cookies to config file
[xxxterm.git] / xxxterm.c
blobe8c31bc529156561b0bdf15d833ef7148ea52889
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 * config file
21 * inverse color browsing
22 * favs
23 * download files status
24 * multi letter commands
25 * pre and post counts for commands
26 * cookies
27 * proxy
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <err.h>
33 #include <pwd.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <util.h>
38 #include <sys/queue.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
42 #include <gtk/gtk.h>
43 #include <gdk/gdkkeysyms.h>
44 #include <webkit/webkit.h>
45 #include <libsoup/soup.h>
46 #include <JavaScriptCore/JavaScript.h>
48 static char *version = "$xxxterm$";
50 #define XT_DEBUG
51 /* #define XT_DEBUG */
52 #ifdef XT_DEBUG
53 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
54 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
55 #define XT_D_MOVE 0x0001
56 #define XT_D_KEY 0x0002
57 #define XT_D_TAB 0x0004
58 #define XT_D_URL 0x0008
59 #define XT_D_CMD 0x0010
60 #define XT_D_NAV 0x0020
61 #define XT_D_DOWNLOAD 0x0040
62 #define XT_D_CONFIG 0x0080
63 u_int32_t swm_debug = 0
64 | XT_D_MOVE
65 | XT_D_KEY
66 | XT_D_TAB
67 | XT_D_URL
68 | XT_D_CMD
69 | XT_D_NAV
70 | XT_D_DOWNLOAD
71 | XT_D_CONFIG
73 #else
74 #define DPRINTF(x...)
75 #define DNPRINTF(n,x...)
76 #endif
78 #define LENGTH(x) (sizeof x / sizeof x[0])
79 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
80 ~(GDK_BUTTON1_MASK) & \
81 ~(GDK_BUTTON2_MASK) & \
82 ~(GDK_BUTTON3_MASK) & \
83 ~(GDK_BUTTON4_MASK) & \
84 ~(GDK_BUTTON5_MASK))
86 struct tab {
87 TAILQ_ENTRY(tab) entry;
88 GtkWidget *vbox;
89 GtkWidget *label;
90 GtkWidget *uri_entry;
91 GtkWidget *toolbar;
92 GtkWidget *browser_win;
93 GtkWidget *cmd;
94 guint tab_id;
96 /* adjustments for browser */
97 GtkScrollbar *sb_h;
98 GtkScrollbar *sb_v;
99 GtkAdjustment *adjust_h;
100 GtkAdjustment *adjust_v;
102 /* flags */
103 int focus_wv;
104 int ctrl_click;
105 gchar *hover;
107 WebKitWebView *wv;
109 TAILQ_HEAD(tab_list, tab);
111 struct karg {
112 int i;
113 char *s;
116 /* defines */
117 #define XT_DIR (".xxxterm")
118 #define XT_CONF_FILE ("xxxterm.conf")
119 #define XT_CB_HANDLED (TRUE)
120 #define XT_CB_PASSTHROUGH (FALSE)
122 /* actions */
123 #define XT_MOVE_INVALID (0)
124 #define XT_MOVE_DOWN (1)
125 #define XT_MOVE_UP (2)
126 #define XT_MOVE_BOTTOM (3)
127 #define XT_MOVE_TOP (4)
128 #define XT_MOVE_PAGEDOWN (5)
129 #define XT_MOVE_PAGEUP (6)
130 #define XT_MOVE_LEFT (7)
131 #define XT_MOVE_FARLEFT (8)
132 #define XT_MOVE_RIGHT (9)
133 #define XT_MOVE_FARRIGHT (10)
135 #define XT_TAB_PREV (-2)
136 #define XT_TAB_NEXT (-1)
137 #define XT_TAB_INVALID (0)
138 #define XT_TAB_NEW (1)
139 #define XT_TAB_DELETE (2)
140 #define XT_TAB_DELQUIT (3)
141 #define XT_TAB_OPEN (4)
143 #define XT_NAV_INVALID (0)
144 #define XT_NAV_BACK (1)
145 #define XT_NAV_FORWARD (2)
147 /* globals */
148 extern char *__progname;
149 struct passwd *pwd;
150 GtkWidget *main_window;
151 GtkNotebook *notebook;
152 struct tab_list tabs;
154 /* settings */
155 int showtabs = 1; /* show tabs on notebook */
156 int showurl = 1; /* show url toolbar on notebook */
157 int tabless = 0; /* allow only 1 tab */
158 int ctrl_click_focus = 0; /* ctrl click gets focus */
159 int cookies_enabled = 1; /* enable cookies */
160 int read_only_cookies = 0; /* enable to not write cookies */
161 char *home = "http://www.peereboom.us";
162 char work_dir[PATH_MAX];
163 char cookie_file[PATH_MAX];
164 char download_dir[PATH_MAX];
165 SoupSession *session;
166 SoupCookieJar *cookiejar;
168 /* protos */
169 void create_new_tab(char *, int);
170 void delete_tab(struct tab *);
172 struct valid_url_types {
173 char *type;
174 } vut[] = {
175 { "http://" },
176 { "https://" },
177 { "ftp://" },
178 { "file://" },
182 valid_url_type(char *url)
184 int i;
186 for (i = 0; i < LENGTH(vut); i++)
187 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
188 return (0);
190 return (1);
193 char *
194 guess_url_type(char *url_in)
196 struct stat sb;
197 char *url_out = NULL;
199 /* XXX not sure about this heuristic */
200 if (stat(url_in, &sb) == 0) {
201 if (asprintf(&url_out, "file://%s", url_in) == -1)
202 err(1, "aprintf file");
203 } else {
204 /* guess http */
205 if (asprintf(&url_out, "http://%s", url_in) == -1)
206 err(1, "aprintf http");
209 if (url_out == NULL)
210 err(1, "asprintf pointer");
212 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
214 return (url_out);
217 #define WS "\n= \t"
218 void
219 config_parse(char *filename)
221 FILE *config;
222 char *line, *cp, *var, *val;
223 size_t len, lineno = 0;
225 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
227 if (filename == NULL)
228 return;
230 if ((config = fopen(filename, "r")) == NULL) {
231 warn("config_parse: cannot open %s", filename);
232 return;
235 for (;;) {
236 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
237 if (feof(config))
238 break;
240 cp = line;
241 cp += (long)strspn(cp, WS);
242 if (cp[0] == '\0') {
243 /* empty line */
244 free(line);
245 continue;
248 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
249 break;
251 cp += (long)strspn(cp, WS);
252 if ((val = strsep(&cp, WS)) == NULL)
253 break;
255 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
257 /* get settings */
258 if (!strcmp(var, "home"))
259 home = strdup(val);
260 else if (!strcmp(var, "ctrl_click_focus"))
261 ctrl_click_focus = atoi(val);
262 else if (!strcmp(var, "read_only_cookies"))
263 read_only_cookies = atoi(val);
264 else if (!strcmp(var, "cookies_enabled"))
265 cookies_enabled = atoi(val);
266 else if (!strcmp(var, "download_dir")) {
267 if (val[0] == '~')
268 snprintf(download_dir, sizeof download_dir,
269 "%s/%s", pwd->pw_dir, &val[1]);
270 else
271 strlcpy(download_dir, val, sizeof download_dir);
272 fprintf(stderr, "download dir: %s\n", download_dir);
273 } else
274 errx(1, "invalid conf file entry: %s=%s", var, val);
276 free(line);
279 fclose(config);
282 quit(struct tab *t, struct karg *args)
284 gtk_main_quit();
286 return (1);
290 help(struct tab *t, struct karg *args)
292 if (t == NULL)
293 errx(1, "help");
295 webkit_web_view_load_string(t->wv,
296 "<html><body><h1>XXXTerm</h1></body></html>",
297 NULL,
298 NULL,
299 NULL);
301 return (0);
305 navaction(struct tab *t, struct karg *args)
307 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
308 t->tab_id, args->i);
310 switch (args->i) {
311 case XT_NAV_BACK:
312 webkit_web_view_go_back(t->wv);
313 break;
314 case XT_NAV_FORWARD:
315 webkit_web_view_go_forward(t->wv);
316 break;
318 return (XT_CB_PASSTHROUGH);
322 move(struct tab *t, struct karg *args)
324 GtkAdjustment *adjust;
325 double pi, si, pos, ps, upper, lower, max;
327 switch (args->i) {
328 case XT_MOVE_DOWN:
329 case XT_MOVE_UP:
330 case XT_MOVE_BOTTOM:
331 case XT_MOVE_TOP:
332 case XT_MOVE_PAGEDOWN:
333 case XT_MOVE_PAGEUP:
334 adjust = t->adjust_v;
335 break;
336 default:
337 adjust = t->adjust_h;
338 break;
341 pos = gtk_adjustment_get_value(adjust);
342 ps = gtk_adjustment_get_page_size(adjust);
343 upper = gtk_adjustment_get_upper(adjust);
344 lower = gtk_adjustment_get_lower(adjust);
345 si = gtk_adjustment_get_step_increment(adjust);
346 pi = gtk_adjustment_get_page_increment(adjust);
347 max = upper - ps;
349 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
350 "max %f si %f pi %f\n",
351 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
352 pos, ps, upper, lower, max, si, pi);
354 switch (args->i) {
355 case XT_MOVE_DOWN:
356 case XT_MOVE_RIGHT:
357 pos += si;
358 gtk_adjustment_set_value(adjust, MIN(pos, max));
359 break;
360 case XT_MOVE_UP:
361 case XT_MOVE_LEFT:
362 pos -= si;
363 gtk_adjustment_set_value(adjust, MAX(pos, lower));
364 break;
365 case XT_MOVE_BOTTOM:
366 case XT_MOVE_FARRIGHT:
367 gtk_adjustment_set_value(adjust, max);
368 break;
369 case XT_MOVE_TOP:
370 case XT_MOVE_FARLEFT:
371 gtk_adjustment_set_value(adjust, lower);
372 break;
373 case XT_MOVE_PAGEDOWN:
374 pos += pi;
375 gtk_adjustment_set_value(adjust, MIN(pos, max));
376 break;
377 case XT_MOVE_PAGEUP:
378 pos -= pi;
379 gtk_adjustment_set_value(adjust, MAX(pos, lower));
380 break;
381 default:
382 return (XT_CB_PASSTHROUGH);
385 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
387 return (XT_CB_HANDLED);
390 char *
391 getparams(char *cmd, char *cmp)
393 char *rv = NULL;
395 if (cmd && cmp) {
396 if (!strncmp(cmd, cmp, strlen(cmp))) {
397 rv = cmd + strlen(cmp);
398 while (*rv == ' ')
399 rv++;
400 if (strlen(rv) == 0)
401 rv = NULL;
405 return (rv);
409 tabaction(struct tab *t, struct karg *args)
411 int rv = XT_CB_HANDLED;
412 char *url = NULL, *newuri = NULL;
414 DNPRINTF(XT_D_TAB, "tabaction: %p %d %d\n", t, args->i, t->focus_wv);
416 if (t == NULL)
417 return (XT_CB_PASSTHROUGH);
419 switch (args->i) {
420 case XT_TAB_NEW:
421 if ((url = getparams(args->s, "tabnew")))
422 create_new_tab(url, 1);
423 else
424 create_new_tab(NULL, 1);
425 break;
426 case XT_TAB_DELETE:
427 delete_tab(t);
428 break;
429 case XT_TAB_DELQUIT:
430 if (gtk_notebook_get_n_pages(notebook) > 1)
431 delete_tab(t);
432 else
433 quit(t, args);
434 break;
435 case XT_TAB_OPEN:
436 if ((url = getparams(args->s, "open")) ||
437 ((url = getparams(args->s, "op"))) ||
438 ((url = getparams(args->s, "o"))))
440 else {
441 rv = XT_CB_PASSTHROUGH;
442 goto done;
445 if (valid_url_type(url)) {
446 newuri = guess_url_type(url);
447 url = newuri;
449 webkit_web_view_load_uri(t->wv, url);
450 if (newuri)
451 free(newuri);
452 break;
453 default:
454 rv = XT_CB_PASSTHROUGH;
455 goto done;
458 done:
459 if (args->s) {
460 free(args->s);
461 args->s = NULL;
464 return (rv);
468 movetab(struct tab *t, struct karg *args)
470 struct tab *tt;
471 int x;
473 DNPRINTF(XT_D_TAB, "movetab: %p %d\n", t, args->i);
475 if (t == NULL)
476 return (XT_CB_PASSTHROUGH);
478 if (args->i == XT_TAB_INVALID)
479 return (XT_CB_PASSTHROUGH);
481 if (args->i < XT_TAB_INVALID) {
482 /* next or previous tab */
483 if (TAILQ_EMPTY(&tabs))
484 return (XT_CB_PASSTHROUGH);
486 if (args->i == XT_TAB_NEXT)
487 gtk_notebook_next_page(notebook);
488 else
489 gtk_notebook_prev_page(notebook);
491 return (XT_CB_HANDLED);
494 /* jump to tab */
495 x = args->i - 1;
496 if (t->tab_id == x) {
497 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
498 return (XT_CB_HANDLED);
501 TAILQ_FOREACH(tt, &tabs, entry) {
502 if (tt->tab_id == x) {
503 gtk_notebook_set_current_page(notebook, x);
504 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
505 if (tt->focus_wv)
506 gtk_widget_grab_focus(GTK_WIDGET(tt->wv));
510 return (XT_CB_HANDLED);
514 command(struct tab *t, struct karg *args)
516 DNPRINTF(XT_D_CMD, "command:\n");
518 gtk_entry_set_text(GTK_ENTRY(t->cmd), ":");
519 gtk_widget_show(t->cmd);
520 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
521 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
523 return (XT_CB_HANDLED);
526 /* inherent to GTK not all keys will be caught at all times */
527 struct key {
528 guint mask;
529 guint modkey;
530 guint key;
531 int (*func)(struct tab *, struct karg *);
532 struct karg arg;
533 } keys[] = {
534 { GDK_SHIFT_MASK, 0, GDK_colon, command, {0} },
535 { GDK_CONTROL_MASK, 0, GDK_q, quit, {0} },
537 /* navigation */
538 { 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
539 { GDK_MOD1_MASK, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
540 { GDK_SHIFT_MASK, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
541 { GDK_MOD1_MASK, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
543 /* vertical movement */
544 { 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
545 { 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
546 { 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
547 { 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
548 { GDK_SHIFT_MASK, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
549 { 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
550 { 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
551 { 0, GDK_g, GDK_g, move, {.i = XT_MOVE_TOP} }, /* XXX make this work */
552 { 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
553 { 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
554 { 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
555 /* horizontal movement */
556 { 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
557 { 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
558 { 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
559 { 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
560 { GDK_SHIFT_MASK, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
561 { 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
563 /* tabs */
564 { GDK_CONTROL_MASK, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
565 { GDK_CONTROL_MASK, 0, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
566 { GDK_CONTROL_MASK, 0, GDK_1, movetab, {.i = 1} },
567 { GDK_CONTROL_MASK, 0, GDK_2, movetab, {.i = 2} },
568 { GDK_CONTROL_MASK, 0, GDK_3, movetab, {.i = 3} },
569 { GDK_CONTROL_MASK, 0, GDK_4, movetab, {.i = 4} },
570 { GDK_CONTROL_MASK, 0, GDK_5, movetab, {.i = 5} },
571 { GDK_CONTROL_MASK, 0, GDK_6, movetab, {.i = 6} },
572 { GDK_CONTROL_MASK, 0, GDK_7, movetab, {.i = 7} },
573 { GDK_CONTROL_MASK, 0, GDK_8, movetab, {.i = 8} },
574 { GDK_CONTROL_MASK, 0, GDK_9, movetab, {.i = 9} },
575 { GDK_CONTROL_MASK, 0, GDK_0, movetab, {.i = 10} },
578 struct cmd {
579 char *cmd;
580 int params;
581 int (*func)(struct tab *, struct karg *);
582 struct karg arg;
583 } cmds[] = {
584 { "q!", 0, quit, {0} },
585 { "qa", 0, quit, {0} },
586 { "qa!", 0, quit, {0} },
587 { "help", 0, help, {0} },
589 /* tabs */
590 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
591 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
592 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
593 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
594 { "tabedit", 0, tabaction, {.i = XT_TAB_NEW} },
595 { "tabe", 0, tabaction, {.i = XT_TAB_NEW} },
596 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
597 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
598 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
599 /* XXX add count to these commands and add tabl and friends */
600 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
601 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
602 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
603 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
606 void
607 focus_uri_entry_cb(GtkWidget* w, GtkDirectionType direction, struct tab *t)
609 DNPRINTF(XT_D_URL, "focus_uri_entry_cb: tab %d focus_wv %d\n",
610 t->tab_id, t->focus_wv);
612 if (t == NULL)
613 errx(1, "focus_uri_entry_cb");
615 /* focus on wv instead */
616 if (t->focus_wv)
617 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
620 void
621 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
623 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
624 char *newuri = NULL;
626 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
628 if (t == NULL)
629 errx(1, "activate_uri_entry_cb");
631 if (uri == NULL)
632 errx(1, "uri");
634 if (valid_url_type((char *)uri)) {
635 newuri = guess_url_type((char *)uri);
636 uri = newuri;
639 webkit_web_view_load_uri(t->wv, uri);
640 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
642 if (newuri)
643 free(newuri);
646 void
647 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
649 WebKitWebFrame *frame;
650 const gchar *uri;
652 if (t == NULL)
653 errx(1, "notify_load_status_cb");
655 switch (webkit_web_view_get_load_status(wview)) {
656 case WEBKIT_LOAD_COMMITTED:
657 frame = webkit_web_view_get_main_frame(wview);
658 uri = webkit_web_frame_get_uri(frame);
659 if (uri)
660 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
661 t->focus_wv = 1;
663 /* take focus if we are visible */
664 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
665 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
666 break;
667 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
668 uri = webkit_web_view_get_title(wview);
669 if (uri == NULL) {
670 frame = webkit_web_view_get_main_frame(wview);
671 uri = webkit_web_frame_get_uri(frame);
673 gtk_label_set_text(GTK_LABEL(t->label), uri);
674 break;
675 case WEBKIT_LOAD_PROVISIONAL:
676 case WEBKIT_LOAD_FINISHED:
677 case WEBKIT_LOAD_FAILED:
678 default:
679 break;
684 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
685 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
686 WebKitWebPolicyDecision *pd, struct tab *t)
688 char *uri;
690 if (t == NULL)
691 errx(1, "webview_npd_cb");
693 DNPRINTF(XT_D_NAV, "webview_npd_cb: %s\n",
694 webkit_network_request_get_uri(request));
696 if (t->ctrl_click) {
697 uri = (char *)webkit_network_request_get_uri(request);
698 create_new_tab(uri, ctrl_click_focus);
699 t->ctrl_click = 0;
700 webkit_web_policy_decision_ignore(pd);
702 return (TRUE); /* we made the decission */
705 return (FALSE);
709 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
711 /* we can not eat the event without throwing gtk off so defer it */
713 /* catch ctrl click */
714 if (e->type == GDK_BUTTON_RELEASE &&
715 CLEAN(e->state) == GDK_CONTROL_MASK)
716 t->ctrl_click = 1;
717 else
718 t->ctrl_click = 0;
720 return (XT_CB_PASSTHROUGH);
724 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
725 WebKitNetworkRequest *request, char *mime_type,
726 WebKitWebPolicyDecision *decision, struct tab *t)
728 if (t == NULL)
729 errx(1, "webview_mimetype_cb");
731 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
732 t->tab_id, mime_type);
734 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
735 webkit_web_policy_decision_download(decision);
736 return (TRUE);
739 return (FALSE);
743 webview_download_cb(WebKitWebView *wv, WebKitDownload *download, struct tab *t)
745 const gchar *filename;
746 char *uri = NULL;
748 if (download == NULL || t == NULL)
749 errx(1, "webview_download_cb: invalid pointers");
751 filename = webkit_download_get_suggested_filename(download);
752 if (filename == NULL)
753 return (FALSE); /* abort download */
755 if (asprintf(&uri, "file://%s/%s", download_dir, filename) == -1)
756 err(1, "aprintf uri");
758 DNPRINTF(XT_D_DOWNLOAD, "webview_download_cb: tab %d filename %s "
759 "local %s\n",
760 t->tab_id, filename, uri);
762 webkit_download_set_destination_uri(download, uri);
764 if (uri)
765 free(uri);
767 webkit_download_start(download);
769 return (TRUE); /* start download */
772 void
773 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
775 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
777 if (t == NULL)
778 errx(1, "webview_hover_cb");
780 if (uri) {
781 if (t->hover) {
782 free(t->hover);
783 t->hover = NULL;
785 t->hover = strdup(uri);
786 } else if (t->hover) {
787 free(t->hover);
788 t->hover = NULL;
793 webview_keypress_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
795 int i;
797 /* don't use w directly; use t->whatever instead */
799 if (t == NULL)
800 errx(1, "webview_keypress_cb");
802 DNPRINTF(XT_D_KEY, "webview_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
803 e->keyval, e->state, t);
805 for (i = 0; i < LENGTH(keys); i++)
806 if (e->keyval == keys[i].key && CLEAN(e->state) ==
807 keys[i].mask) {
808 keys[i].func(t, &keys[i].arg);
809 return (XT_CB_HANDLED);
812 return (XT_CB_PASSTHROUGH);
816 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
818 int rv = XT_CB_HANDLED;
819 const gchar *c = gtk_entry_get_text(w);
821 if (t == NULL)
822 errx(1, "cmd_keypress_cb");
824 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
825 e->keyval, e->state, t);
827 /* sanity */
828 if (c == NULL)
829 e->keyval = GDK_Escape;
830 else if (c[0] != ':')
831 e->keyval = GDK_Escape;
833 switch (e->keyval) {
834 case GDK_BackSpace:
835 if (strcmp(c, ":"))
836 break;
837 /* FALLTHROUGH */
838 case GDK_Escape:
839 gtk_widget_hide(t->cmd);
840 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
841 goto done;
844 rv = XT_CB_PASSTHROUGH;
845 done:
846 return (rv);
850 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
852 if (t == NULL)
853 errx(1, "cmd_focusout_cb");
855 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d focus_wv %d\n",
856 t->tab_id, t->focus_wv);
858 /* abort command when losing focus */
859 gtk_widget_hide(t->cmd);
860 if (t->focus_wv)
861 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
862 else
863 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
865 return (XT_CB_PASSTHROUGH);
868 void
869 cmd_activate_cb(GtkEntry *entry, struct tab *t)
871 int i;
872 char *s;
873 const gchar *c = gtk_entry_get_text(entry);
875 if (t == NULL)
876 errx(1, "cmd_activate_cb");
878 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
880 /* sanity */
881 if (c == NULL)
882 goto done;
883 else if (c[0] != ':')
884 goto done;
885 if (strlen(c) < 2)
886 goto done;
887 s = (char *)&c[1];
889 for (i = 0; i < LENGTH(cmds); i++)
890 if (cmds[i].params) {
891 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
892 cmds[i].arg.s = strdup(s);
893 cmds[i].func(t, &cmds[i].arg);
895 } else {
896 if (!strcmp(s, cmds[i].cmd))
897 cmds[i].func(t, &cmds[i].arg);
900 done:
901 gtk_widget_hide(t->cmd);
904 GtkWidget *
905 create_browser(struct tab *t)
907 GtkWidget *w;
909 if (t == NULL)
910 errx(1, "create_browser");
912 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
913 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
914 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
915 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
917 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
918 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
919 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
921 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
922 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
924 g_signal_connect(t->wv, "notify::load-status",
925 G_CALLBACK(notify_load_status_cb), t);
927 return (w);
930 GtkWidget *
931 create_window(void)
933 GtkWidget *w;
935 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
936 gtk_window_set_default_size(GTK_WINDOW(w), 800, 600);
937 gtk_widget_set_name(w, "xxxterm");
938 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
940 return (w);
943 GtkWidget *
944 create_toolbar(struct tab *t)
946 GtkWidget *toolbar = gtk_toolbar_new();
947 GtkToolItem *i;
949 #if GTK_CHECK_VERSION(2,15,0)
950 gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar),
951 GTK_ORIENTATION_HORIZONTAL);
952 #else
953 gtk_toolbar_set_orientation(GTK_TOOLBAR(toolbar),
954 GTK_ORIENTATION_HORIZONTAL);
955 #endif
956 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH_HORIZ);
958 i = gtk_tool_item_new();
959 gtk_tool_item_set_expand(i, TRUE);
960 t->uri_entry = gtk_entry_new();
961 gtk_container_add(GTK_CONTAINER(i), t->uri_entry);
962 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
963 G_CALLBACK(activate_uri_entry_cb), t);
964 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), i, -1);
966 return (toolbar);
969 void
970 delete_tab(struct tab *t)
972 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
974 if (t == NULL)
975 return;
977 TAILQ_REMOVE(&tabs, t, entry);
978 if (TAILQ_EMPTY(&tabs))
979 create_new_tab(NULL, 1);
981 gtk_widget_destroy(t->vbox);
982 g_free(t);
985 void
986 create_new_tab(char *title, int focus)
988 struct tab *t;
989 int load = 1;
990 char *newuri = NULL;
992 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
994 if (tabless && !TAILQ_EMPTY(&tabs)) {
995 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
996 return;
999 t = g_malloc0(sizeof *t);
1000 TAILQ_INSERT_TAIL(&tabs, t, entry);
1002 if (title == NULL) {
1003 title = "(untitled)";
1004 load = 0;
1005 } else {
1006 if (valid_url_type(title)) {
1007 newuri = guess_url_type(title);
1008 title = newuri;
1012 t->vbox = gtk_vbox_new(FALSE, 0);
1014 /* label for tab */
1015 t->label = gtk_label_new(title);
1016 gtk_widget_set_size_request(t->label, 100, -1);
1018 /* toolbar */
1019 t->toolbar = create_toolbar(t);
1020 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
1022 /* browser */
1023 t->browser_win = create_browser(t);
1024 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
1026 /* command entry */
1027 t->cmd = gtk_entry_new();
1028 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
1029 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
1030 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
1032 /* and show it all */
1033 gtk_widget_show_all(t->vbox);
1034 t->tab_id = gtk_notebook_append_page(notebook, t->vbox,
1035 t->label);
1037 g_object_connect((GObject*)t->cmd,
1038 "signal::key-press-event", (GCallback)cmd_keypress_cb, t,
1039 "signal::focus-out-event", (GCallback)cmd_focusout_cb, t,
1040 "signal::activate", (GCallback)cmd_activate_cb, t,
1041 NULL);
1043 g_object_connect((GObject*)t->wv,
1044 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
1045 /* "signal::hovering-over-link", (GCallback)webview_hover_cb, t, */
1046 "signal::download-requested", (GCallback)webview_download_cb, t,
1047 "signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, t,
1048 "signal::navigation-policy-decision-requested", (GCallback)webview_npd_cb, t,
1049 "signal::event", (GCallback)webview_event_cb, t,
1050 NULL);
1052 /* hijack the unused keys as if we were the browser */
1053 g_object_connect((GObject*)t->toolbar,
1054 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
1055 NULL);
1057 g_signal_connect(G_OBJECT(t->uri_entry), "focus",
1058 G_CALLBACK(focus_uri_entry_cb), t);
1060 /* hide stuff */
1061 gtk_widget_hide(t->cmd);
1062 if (showurl == 0)
1063 gtk_widget_hide(t->toolbar);
1065 if (focus) {
1066 gtk_notebook_set_current_page(notebook, t->tab_id);
1067 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
1068 t->tab_id);
1071 if (load)
1072 webkit_web_view_load_uri(t->wv, title);
1073 else
1074 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
1076 if (newuri)
1077 free(newuri);
1080 void
1081 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
1082 gpointer *udata)
1084 struct tab *t;
1086 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
1088 TAILQ_FOREACH(t, &tabs, entry) {
1089 if (t->tab_id == pn) {
1090 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
1091 "%d\n", pn);
1092 gtk_widget_hide(t->cmd);
1097 void
1098 create_canvas(void)
1100 GtkWidget *vbox;
1102 vbox = gtk_vbox_new(FALSE, 0);
1103 notebook = GTK_NOTEBOOK(gtk_notebook_new());
1104 if (showtabs == 0)
1105 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
1107 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
1109 g_object_connect((GObject*)notebook,
1110 "signal::switch-page", (GCallback)notebook_switchpage_cb, NULL,
1111 NULL);
1113 main_window = create_window();
1114 gtk_container_add(GTK_CONTAINER(main_window), vbox);
1115 gtk_widget_show_all(main_window);
1118 void
1119 setup_cookies(void)
1121 if (cookiejar) {
1122 soup_session_remove_feature(session,
1123 (SoupSessionFeature*)cookiejar);
1124 g_object_unref(cookiejar);
1125 cookiejar = NULL;
1128 if (cookies_enabled == 0)
1129 return;
1131 cookiejar = soup_cookie_jar_text_new(cookie_file, read_only_cookies);
1132 soup_session_add_feature(session, (SoupSessionFeature*)cookiejar);
1135 void
1136 usage(void)
1138 fprintf(stderr,
1139 "%s [-STVt][-f file] url ...\n", __progname);
1140 exit(0);
1144 main(int argc, char *argv[])
1146 struct stat sb;
1147 char conf[PATH_MAX] = { '\0' };
1148 int c, focus = 1;
1150 while ((c = getopt(argc, argv, "STVf:t")) != -1) {
1151 switch (c) {
1152 case 'S':
1153 showurl = 0;
1154 break;
1155 case 'T':
1156 showtabs = 0;
1157 break;
1158 case 'V':
1159 errx(0 , "Version: %s", version);
1160 break;
1161 case 'f':
1162 strlcpy(conf, optarg, sizeof(conf));
1163 break;
1164 case 't':
1165 tabless = 1;
1166 break;
1167 default:
1168 usage();
1169 /* NOTREACHED */
1172 argc -= optind;
1173 argv += optind;
1175 TAILQ_INIT(&tabs);
1177 /* prepare gtk */
1178 gtk_init(&argc, &argv);
1179 if (!g_thread_supported())
1180 g_thread_init(NULL);
1182 pwd = getpwuid(getuid());
1183 if (pwd == NULL)
1184 errx(1, "invalid user %d", getuid());
1186 /* set download dir */
1187 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
1189 /* read config file */
1190 if (strlen(conf) == 0)
1191 snprintf(conf, sizeof conf, "%s/.%s",
1192 pwd->pw_dir, XT_CONF_FILE);
1193 config_parse(conf);
1195 if (stat(download_dir, &sb))
1196 errx(1, "must specify a valid download_dir");
1198 /* working directory */
1199 snprintf(work_dir, sizeof work_dir, "%s/%s", pwd->pw_dir, XT_DIR);
1200 if (stat(work_dir, &sb)) {
1201 if (mkdir(work_dir, S_IRWXU) == -1)
1202 err(1, "mkdir");
1203 } else if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
1204 warnx("fixing invalid permissions on %s", work_dir);
1205 if (chmod(work_dir, S_IRWXU) == -1)
1206 err(1, "chmod");
1209 create_canvas();
1211 /* cookies */
1212 session = webkit_get_default_session();
1213 snprintf(cookie_file, sizeof cookie_file, "%s/cookies.txt", work_dir);
1214 fprintf(stderr, "cookies: %s\n", cookie_file);
1215 setup_cookies();
1217 while (argc) {
1218 create_new_tab(argv[0], focus);
1219 focus = 0;
1221 argc--;
1222 argv++;
1224 if (focus == 1)
1225 create_new_tab(home, 1);
1227 gtk_main();
1229 return (0);