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.
20 * inverse color browsing
22 * download files status
23 * multi letter commands
24 * pre and post counts for commands
29 * autocompletion on various inputs
40 #include <sys/queue.h>
41 #include <sys/types.h>
45 #include <gdk/gdkkeysyms.h>
46 #include <webkit/webkit.h>
47 #include <libsoup/soup.h>
48 #include <JavaScriptCore/JavaScript.h>
50 static char *version
= "$xxxterm$";
53 /* #define XT_DEBUG */
55 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
56 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
57 #define XT_D_MOVE 0x0001
58 #define XT_D_KEY 0x0002
59 #define XT_D_TAB 0x0004
60 #define XT_D_URL 0x0008
61 #define XT_D_CMD 0x0010
62 #define XT_D_NAV 0x0020
63 #define XT_D_DOWNLOAD 0x0040
64 #define XT_D_CONFIG 0x0080
65 u_int32_t swm_debug
= 0
77 #define DNPRINTF(n,x...)
80 #define LENGTH(x) (sizeof x / sizeof x[0])
81 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
82 ~(GDK_BUTTON1_MASK) & \
83 ~(GDK_BUTTON2_MASK) & \
84 ~(GDK_BUTTON3_MASK) & \
85 ~(GDK_BUTTON4_MASK) & \
89 TAILQ_ENTRY(tab
) entry
;
93 GtkWidget
*search_entry
;
95 GtkWidget
*browser_win
;
97 GtkToolItem
*backward
;
102 /* adjustments for browser */
105 GtkAdjustment
*adjust_h
;
106 GtkAdjustment
*adjust_v
;
117 WebKitWebSettings
*settings
;
119 TAILQ_HEAD(tab_list
, tab
);
127 #define XT_DIR (".xxxterm")
128 #define XT_CONF_FILE ("xxxterm.conf")
129 #define XT_CB_HANDLED (TRUE)
130 #define XT_CB_PASSTHROUGH (FALSE)
133 #define XT_MOVE_INVALID (0)
134 #define XT_MOVE_DOWN (1)
135 #define XT_MOVE_UP (2)
136 #define XT_MOVE_BOTTOM (3)
137 #define XT_MOVE_TOP (4)
138 #define XT_MOVE_PAGEDOWN (5)
139 #define XT_MOVE_PAGEUP (6)
140 #define XT_MOVE_LEFT (7)
141 #define XT_MOVE_FARLEFT (8)
142 #define XT_MOVE_RIGHT (9)
143 #define XT_MOVE_FARRIGHT (10)
145 #define XT_TAB_PREV (-2)
146 #define XT_TAB_NEXT (-1)
147 #define XT_TAB_INVALID (0)
148 #define XT_TAB_NEW (1)
149 #define XT_TAB_DELETE (2)
150 #define XT_TAB_DELQUIT (3)
151 #define XT_TAB_OPEN (4)
153 #define XT_NAV_INVALID (0)
154 #define XT_NAV_BACK (1)
155 #define XT_NAV_FORWARD (2)
156 #define XT_NAV_RELOAD (3)
158 #define XT_FOCUS_INVALID (0)
159 #define XT_FOCUS_URI (1)
161 #define XT_SEARCH_INVALID (0)
162 #define XT_SEARCH_NEXT (1)
163 #define XT_SEARCH_PREV (2)
166 extern char *__progname
;
168 GtkWidget
*main_window
;
169 GtkNotebook
*notebook
;
170 struct tab_list tabs
;
173 int showtabs
= 1; /* show tabs on notebook */
174 int showurl
= 1; /* show url toolbar on notebook */
175 int tabless
= 0; /* allow only 1 tab */
176 int ctrl_click_focus
= 0; /* ctrl click gets focus */
177 int cookies_enabled
= 1; /* enable cookies */
178 int read_only_cookies
= 0; /* enable to not write cookies */
179 int enable_scripts
= 1;
180 int enable_plugins
= 1;
181 int default_font_size
= 12;
182 int fancy_bar
= 1; /* fancy toolbar */
184 char *home
= "http://www.peereboom.us";
185 char *search_string
= NULL
;
186 char *http_proxy
= NULL
;
187 SoupURI
*proxy_uri
= NULL
;
188 char work_dir
[PATH_MAX
];
189 char cookie_file
[PATH_MAX
];
190 char download_dir
[PATH_MAX
];
191 SoupSession
*session
;
192 SoupCookieJar
*cookiejar
;
195 void create_new_tab(char *, int);
196 void delete_tab(struct tab
*);
198 struct valid_url_types
{
208 valid_url_type(char *url
)
212 for (i
= 0; i
< LENGTH(vut
); i
++)
213 if (!strncasecmp(vut
[i
].type
, url
, strlen(vut
[i
].type
)))
220 guess_url_type(char *url_in
)
223 char *url_out
= NULL
;
225 /* XXX not sure about this heuristic */
226 if (stat(url_in
, &sb
) == 0) {
227 if (asprintf(&url_out
, "file://%s", url_in
) == -1)
228 err(1, "aprintf file");
231 if (asprintf(&url_out
, "http://%s", url_in
) == -1)
232 err(1, "aprintf http");
236 err(1, "asprintf pointer");
238 DNPRINTF(XT_D_URL
, "guess_url_type: guessed %s\n", url_out
);
245 config_parse(char *filename
)
248 char *line
, *cp
, *var
, *val
;
249 size_t len
, lineno
= 0;
251 DNPRINTF(XT_D_CONFIG
, "config_parse: filename %s\n", filename
);
253 if (filename
== NULL
)
256 if ((config
= fopen(filename
, "r")) == NULL
) {
257 warn("config_parse: cannot open %s", filename
);
262 if ((line
= fparseln(config
, &len
, &lineno
, NULL
, 0)) == NULL
)
267 cp
+= (long)strspn(cp
, WS
);
274 if ((var
= strsep(&cp
, WS
)) == NULL
|| cp
== NULL
)
277 cp
+= (long)strspn(cp
, WS
);
279 if ((val
= strsep(&cp
, "\0")) == NULL
)
282 DNPRINTF(XT_D_CONFIG
, "config_parse: %s=%s\n",var
,val
);
285 if (!strcmp(var
, "home"))
287 else if (!strcmp(var
, "ctrl_click_focus"))
288 ctrl_click_focus
= atoi(val
);
289 else if (!strcmp(var
, "read_only_cookies"))
290 read_only_cookies
= atoi(val
);
291 else if (!strcmp(var
, "cookies_enabled"))
292 cookies_enabled
= atoi(val
);
293 else if (!strcmp(var
, "enable_scripts"))
294 enable_scripts
= atoi(val
);
295 else if (!strcmp(var
, "enable_plugins"))
296 enable_plugins
= atoi(val
);
297 else if (!strcmp(var
, "default_font_size"))
298 default_font_size
= atoi(val
);
299 else if (!strcmp(var
, "fancy_bar"))
300 fancy_bar
= atoi(val
);
301 else if (!strcmp(var
, "http_proxy")) {
302 http_proxy
= strdup(val
);
303 if (http_proxy
== NULL
)
304 err(1, "http_proxy");
305 } else if (!strcmp(var
, "search_string")) {
306 search_string
= strdup(val
);
307 if (search_string
== NULL
)
308 err(1, "search_string");
309 } else if (!strcmp(var
, "download_dir")) {
311 snprintf(download_dir
, sizeof download_dir
,
312 "%s/%s", pwd
->pw_dir
, &val
[1]);
314 strlcpy(download_dir
, val
, sizeof download_dir
);
315 fprintf(stderr
, "download dir: %s\n", download_dir
);
317 errx(1, "invalid conf file entry: %s=%s", var
, val
);
325 quit(struct tab
*t
, struct karg
*args
)
333 focus(struct tab
*t
, struct karg
*args
)
338 if (args
->i
== XT_FOCUS_URI
)
339 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
345 help(struct tab
*t
, struct karg
*args
)
350 webkit_web_view_load_string(t
->wv
,
351 "<html><body><h1>XXXTerm</h1></body></html>",
360 navaction(struct tab
*t
, struct karg
*args
)
362 DNPRINTF(XT_D_NAV
, "navaction: tab %d opcode %d\n",
367 webkit_web_view_go_back(t
->wv
);
370 webkit_web_view_go_forward(t
->wv
);
373 webkit_web_view_reload(t
->wv
);
376 return (XT_CB_PASSTHROUGH
);
380 move(struct tab
*t
, struct karg
*args
)
382 GtkAdjustment
*adjust
;
383 double pi
, si
, pos
, ps
, upper
, lower
, max
;
390 case XT_MOVE_PAGEDOWN
:
392 adjust
= t
->adjust_v
;
395 adjust
= t
->adjust_h
;
399 pos
= gtk_adjustment_get_value(adjust
);
400 ps
= gtk_adjustment_get_page_size(adjust
);
401 upper
= gtk_adjustment_get_upper(adjust
);
402 lower
= gtk_adjustment_get_lower(adjust
);
403 si
= gtk_adjustment_get_step_increment(adjust
);
404 pi
= gtk_adjustment_get_page_increment(adjust
);
407 DNPRINTF(XT_D_MOVE
, "move: opcode %d %s pos %f ps %f upper %f lower %f "
408 "max %f si %f pi %f\n",
409 args
->i
, adjust
== t
->adjust_h
? "horizontal" : "vertical",
410 pos
, ps
, upper
, lower
, max
, si
, pi
);
416 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
421 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
424 case XT_MOVE_FARRIGHT
:
425 gtk_adjustment_set_value(adjust
, max
);
428 case XT_MOVE_FARLEFT
:
429 gtk_adjustment_set_value(adjust
, lower
);
431 case XT_MOVE_PAGEDOWN
:
433 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
437 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
440 return (XT_CB_PASSTHROUGH
);
443 DNPRINTF(XT_D_MOVE
, "move: new pos %f %f\n", pos
, MIN(pos
, max
));
445 return (XT_CB_HANDLED
);
449 getparams(char *cmd
, char *cmp
)
454 if (!strncmp(cmd
, cmp
, strlen(cmp
))) {
455 rv
= cmd
+ strlen(cmp
);
467 tabaction(struct tab
*t
, struct karg
*args
)
469 int rv
= XT_CB_HANDLED
;
470 char *url
= NULL
, *newuri
= NULL
;
472 DNPRINTF(XT_D_TAB
, "tabaction: %p %d %d\n", t
, args
->i
, t
->focus_wv
);
475 return (XT_CB_PASSTHROUGH
);
479 if ((url
= getparams(args
->s
, "tabnew")))
480 create_new_tab(url
, 1);
482 create_new_tab(NULL
, 1);
488 if (gtk_notebook_get_n_pages(notebook
) > 1)
494 if ((url
= getparams(args
->s
, "open")) ||
495 ((url
= getparams(args
->s
, "op"))) ||
496 ((url
= getparams(args
->s
, "o"))))
499 rv
= XT_CB_PASSTHROUGH
;
503 if (valid_url_type(url
)) {
504 newuri
= guess_url_type(url
);
507 webkit_web_view_load_uri(t
->wv
, url
);
512 rv
= XT_CB_PASSTHROUGH
;
526 movetab(struct tab
*t
, struct karg
*args
)
531 DNPRINTF(XT_D_TAB
, "movetab: %p %d\n", t
, args
->i
);
534 return (XT_CB_PASSTHROUGH
);
536 if (args
->i
== XT_TAB_INVALID
)
537 return (XT_CB_PASSTHROUGH
);
539 if (args
->i
< XT_TAB_INVALID
) {
540 /* next or previous tab */
541 if (TAILQ_EMPTY(&tabs
))
542 return (XT_CB_PASSTHROUGH
);
544 if (args
->i
== XT_TAB_NEXT
)
545 gtk_notebook_next_page(notebook
);
547 gtk_notebook_prev_page(notebook
);
549 return (XT_CB_HANDLED
);
554 if (t
->tab_id
== x
) {
555 DNPRINTF(XT_D_TAB
, "movetab: do nothing\n");
556 return (XT_CB_HANDLED
);
559 TAILQ_FOREACH(tt
, &tabs
, entry
) {
560 if (tt
->tab_id
== x
) {
561 gtk_notebook_set_current_page(notebook
, x
);
562 DNPRINTF(XT_D_TAB
, "movetab: going to %d\n", x
);
564 gtk_widget_grab_focus(GTK_WIDGET(tt
->wv
));
568 return (XT_CB_HANDLED
);
572 command(struct tab
*t
, struct karg
*args
)
577 if (t
== NULL
|| args
== NULL
)
582 else if (args
->i
== ':')
585 warnx("invalid command %c\n", args
->i
);
586 return (XT_CB_PASSTHROUGH
);
589 DNPRINTF(XT_D_CMD
, "command: type %s\n", s
);
591 gtk_entry_set_text(GTK_ENTRY(t
->cmd
), s
);
592 gdk_color_parse("white", &color
);
593 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
594 gtk_widget_show(t
->cmd
);
595 gtk_widget_grab_focus(GTK_WIDGET(t
->cmd
));
596 gtk_editable_set_position(GTK_EDITABLE(t
->cmd
), -1);
598 return (XT_CB_HANDLED
);
602 search(struct tab
*t
, struct karg
*args
)
606 if (t
== NULL
|| args
== NULL
)
608 if (t
->search_text
== NULL
)
609 return (XT_CB_PASSTHROUGH
);
611 DNPRINTF(XT_D_CMD
, "search: tab %d opc %d text %s\n",
612 t
->tab_id
, args
->i
, t
->search_text
);
622 return (XT_CB_PASSTHROUGH
);
625 webkit_web_view_search_text(t
->wv
, t
->search_text
, FALSE
, d
, TRUE
);
627 return (XT_CB_HANDLED
);
630 /* inherent to GTK not all keys will be caught at all times */
635 int (*func
)(struct tab
*, struct karg
*);
638 { 0, 0, GDK_slash
, command
, {.i
= '/'} },
639 { GDK_SHIFT_MASK
, 0, GDK_colon
, command
, {.i
= ':'} },
640 { GDK_CONTROL_MASK
, 0, GDK_q
, quit
, {0} },
643 { 0, 0, GDK_n
, search
, {.i
= XT_SEARCH_NEXT
} },
644 { GDK_SHIFT_MASK
, 0, GDK_N
, search
, {.i
= XT_SEARCH_PREV
} },
647 { 0, 0, GDK_F6
, focus
, {.i
= XT_FOCUS_URI
} },
650 { 0, 0, GDK_BackSpace
, navaction
, {.i
= XT_NAV_BACK
} },
651 { GDK_MOD1_MASK
, 0, GDK_Left
, navaction
, {.i
= XT_NAV_BACK
} },
652 { GDK_SHIFT_MASK
, 0, GDK_BackSpace
, navaction
, {.i
= XT_NAV_FORWARD
} },
653 { GDK_MOD1_MASK
, 0, GDK_Right
, navaction
, {.i
= XT_NAV_FORWARD
} },
654 { 0, 0, GDK_F5
, navaction
, {.i
= XT_NAV_RELOAD
} },
656 /* vertical movement */
657 { 0, 0, GDK_j
, move
, {.i
= XT_MOVE_DOWN
} },
658 { 0, 0, GDK_Down
, move
, {.i
= XT_MOVE_DOWN
} },
659 { 0, 0, GDK_Up
, move
, {.i
= XT_MOVE_UP
} },
660 { 0, 0, GDK_k
, move
, {.i
= XT_MOVE_UP
} },
661 { GDK_SHIFT_MASK
, 0, GDK_G
, move
, {.i
= XT_MOVE_BOTTOM
} },
662 { 0, 0, GDK_End
, move
, {.i
= XT_MOVE_BOTTOM
} },
663 { 0, 0, GDK_Home
, move
, {.i
= XT_MOVE_TOP
} },
664 { 0, GDK_g
, GDK_g
, move
, {.i
= XT_MOVE_TOP
} }, /* XXX make this work */
665 { 0, 0, GDK_space
, move
, {.i
= XT_MOVE_PAGEDOWN
} },
666 { GDK_CONTROL_MASK
, 0, GDK_f
, move
, {.i
= XT_MOVE_PAGEDOWN
} },
667 { 0, 0, GDK_Page_Down
, move
, {.i
= XT_MOVE_PAGEDOWN
} },
668 { 0, 0, GDK_Page_Up
, move
, {.i
= XT_MOVE_PAGEUP
} },
669 { GDK_CONTROL_MASK
, 0, GDK_b
, move
, {.i
= XT_MOVE_PAGEUP
} },
670 /* horizontal movement */
671 { 0, 0, GDK_l
, move
, {.i
= XT_MOVE_RIGHT
} },
672 { 0, 0, GDK_Right
, move
, {.i
= XT_MOVE_RIGHT
} },
673 { 0, 0, GDK_Left
, move
, {.i
= XT_MOVE_LEFT
} },
674 { 0, 0, GDK_h
, move
, {.i
= XT_MOVE_LEFT
} },
675 { GDK_SHIFT_MASK
, 0, GDK_dollar
, move
, {.i
= XT_MOVE_FARRIGHT
} },
676 { 0, 0, GDK_0
, move
, {.i
= XT_MOVE_FARLEFT
} },
679 { GDK_CONTROL_MASK
, 0, GDK_t
, tabaction
, {.i
= XT_TAB_NEW
} },
680 { GDK_CONTROL_MASK
, 0, GDK_w
, tabaction
, {.i
= XT_TAB_DELETE
} },
681 { GDK_CONTROL_MASK
, 0, GDK_1
, movetab
, {.i
= 1} },
682 { GDK_CONTROL_MASK
, 0, GDK_2
, movetab
, {.i
= 2} },
683 { GDK_CONTROL_MASK
, 0, GDK_3
, movetab
, {.i
= 3} },
684 { GDK_CONTROL_MASK
, 0, GDK_4
, movetab
, {.i
= 4} },
685 { GDK_CONTROL_MASK
, 0, GDK_5
, movetab
, {.i
= 5} },
686 { GDK_CONTROL_MASK
, 0, GDK_6
, movetab
, {.i
= 6} },
687 { GDK_CONTROL_MASK
, 0, GDK_7
, movetab
, {.i
= 7} },
688 { GDK_CONTROL_MASK
, 0, GDK_8
, movetab
, {.i
= 8} },
689 { GDK_CONTROL_MASK
, 0, GDK_9
, movetab
, {.i
= 9} },
690 { GDK_CONTROL_MASK
, 0, GDK_0
, movetab
, {.i
= 10} },
696 int (*func
)(struct tab
*, struct karg
*);
699 { "q!", 0, quit
, {0} },
700 { "qa", 0, quit
, {0} },
701 { "qa!", 0, quit
, {0} },
702 { "help", 0, help
, {0} },
705 { "o", 1, tabaction
, {.i
= XT_TAB_OPEN
} },
706 { "op", 1, tabaction
, {.i
= XT_TAB_OPEN
} },
707 { "open", 1, tabaction
, {.i
= XT_TAB_OPEN
} },
708 { "tabnew", 1, tabaction
, {.i
= XT_TAB_NEW
} },
709 { "tabedit", 1, tabaction
, {.i
= XT_TAB_NEW
} },
710 { "tabe", 1, tabaction
, {.i
= XT_TAB_NEW
} },
711 { "tabclose", 0, tabaction
, {.i
= XT_TAB_DELETE
} },
712 { "tabc", 0, tabaction
, {.i
= XT_TAB_DELETE
} },
713 { "quit", 0, tabaction
, {.i
= XT_TAB_DELQUIT
} },
714 { "q", 0, tabaction
, {.i
= XT_TAB_DELQUIT
} },
715 /* XXX add count to these commands and add tabl and friends */
716 { "tabprevious", 0, movetab
, {.i
= XT_TAB_PREV
} },
717 { "tabp", 0, movetab
, {.i
= XT_TAB_PREV
} },
718 { "tabnext", 0, movetab
, {.i
= XT_TAB_NEXT
} },
719 { "tabn", 0, movetab
, {.i
= XT_TAB_NEXT
} },
723 focus_uri_entry_cb(GtkWidget
* w
, GtkDirectionType direction
, struct tab
*t
)
725 DNPRINTF(XT_D_URL
, "focus_uri_entry_cb: tab %d focus_wv %d\n",
726 t
->tab_id
, t
->focus_wv
);
729 errx(1, "focus_uri_entry_cb");
731 /* focus on wv instead */
733 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
737 activate_uri_entry_cb(GtkWidget
* entry
, struct tab
*t
)
739 const gchar
*uri
= gtk_entry_get_text(GTK_ENTRY(entry
));
742 DNPRINTF(XT_D_URL
, "activate_uri_entry_cb: %s\n", uri
);
745 errx(1, "activate_uri_entry_cb");
750 if (valid_url_type((char *)uri
)) {
751 newuri
= guess_url_type((char *)uri
);
755 webkit_web_view_load_uri(t
->wv
, uri
);
756 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
763 activate_search_entry_cb(GtkWidget
* entry
, struct tab
*t
)
765 const gchar
*search
= gtk_entry_get_text(GTK_ENTRY(entry
));
768 DNPRINTF(XT_D_URL
, "activate_search_entry_cb: %s\n", search
);
771 errx(1, "activate_search_entry_cb");
773 if (search_string
== NULL
) {
774 warnx("no search_string");
778 if (asprintf(&newuri
, search_string
, search
) == -1)
779 err(1, "activate_search_entry_cb");
781 webkit_web_view_load_uri(t
->wv
, newuri
);
782 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
789 notify_load_status_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
791 WebKitWebFrame
*frame
;
795 errx(1, "notify_load_status_cb");
797 switch (webkit_web_view_get_load_status(wview
)) {
798 case WEBKIT_LOAD_COMMITTED
:
799 frame
= webkit_web_view_get_main_frame(wview
);
800 uri
= webkit_web_frame_get_uri(frame
);
802 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
804 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), TRUE
);
807 /* take focus if we are visible */
808 if (gtk_notebook_get_current_page(notebook
) == t
->tab_id
)
809 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
811 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT
:
812 uri
= webkit_web_view_get_title(wview
);
814 frame
= webkit_web_view_get_main_frame(wview
);
815 uri
= webkit_web_frame_get_uri(frame
);
817 gtk_label_set_text(GTK_LABEL(t
->label
), uri
);
819 case WEBKIT_LOAD_PROVISIONAL
:
820 case WEBKIT_LOAD_FINISHED
:
821 #if WEBKIT_CHECK_VERSION(1, 1, 18)
822 case WEBKIT_LOAD_FAILED
:
824 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), FALSE
);
829 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
),
830 webkit_web_view_can_go_back(wview
));
832 gtk_widget_set_sensitive(GTK_WIDGET(t
->forward
),
833 webkit_web_view_can_go_forward(wview
));
837 webview_npd_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
,
838 WebKitNetworkRequest
*request
, WebKitWebNavigationAction
*na
,
839 WebKitWebPolicyDecision
*pd
, struct tab
*t
)
844 errx(1, "webview_npd_cb");
846 DNPRINTF(XT_D_NAV
, "webview_npd_cb: %s\n",
847 webkit_network_request_get_uri(request
));
850 uri
= (char *)webkit_network_request_get_uri(request
);
851 create_new_tab(uri
, ctrl_click_focus
);
853 webkit_web_policy_decision_ignore(pd
);
855 return (TRUE
); /* we made the decission */
862 webview_event_cb(GtkWidget
*w
, GdkEventButton
*e
, struct tab
*t
)
864 /* we can not eat the event without throwing gtk off so defer it */
866 /* catch ctrl click */
867 if (e
->type
== GDK_BUTTON_RELEASE
&&
868 CLEAN(e
->state
) == GDK_CONTROL_MASK
)
873 return (XT_CB_PASSTHROUGH
);
877 webview_mimetype_cb(WebKitWebView
*wv
, WebKitWebFrame
*frame
,
878 WebKitNetworkRequest
*request
, char *mime_type
,
879 WebKitWebPolicyDecision
*decision
, struct tab
*t
)
882 errx(1, "webview_mimetype_cb");
884 DNPRINTF(XT_D_DOWNLOAD
, "webview_mimetype_cb: tab %d mime %s\n",
885 t
->tab_id
, mime_type
);
887 if (webkit_web_view_can_show_mime_type(wv
, mime_type
) == FALSE
) {
888 webkit_web_policy_decision_download(decision
);
896 webview_download_cb(WebKitWebView
*wv
, WebKitDownload
*download
, struct tab
*t
)
898 const gchar
*filename
;
901 if (download
== NULL
|| t
== NULL
)
902 errx(1, "webview_download_cb: invalid pointers");
904 filename
= webkit_download_get_suggested_filename(download
);
905 if (filename
== NULL
)
906 return (FALSE
); /* abort download */
908 if (asprintf(&uri
, "file://%s/%s", download_dir
, filename
) == -1)
909 err(1, "aprintf uri");
911 DNPRINTF(XT_D_DOWNLOAD
, "webview_download_cb: tab %d filename %s "
913 t
->tab_id
, filename
, uri
);
915 webkit_download_set_destination_uri(download
, uri
);
920 webkit_download_start(download
);
922 return (TRUE
); /* start download */
925 /* XXX currently unused */
927 webview_hover_cb(WebKitWebView
*wv
, gchar
*title
, gchar
*uri
, struct tab
*t
)
929 DNPRINTF(XT_D_KEY
, "webview_hover_cb: %s %s\n", title
, uri
);
932 errx(1, "webview_hover_cb");
939 t
->hover
= strdup(uri
);
940 } else if (t
->hover
) {
947 webview_keypress_cb(GtkWidget
*w
, GdkEventKey
*e
, struct tab
*t
)
951 /* don't use w directly; use t->whatever instead */
954 errx(1, "webview_keypress_cb");
956 DNPRINTF(XT_D_KEY
, "webview_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
957 e
->keyval
, e
->state
, t
);
959 for (i
= 0; i
< LENGTH(keys
); i
++)
960 if (e
->keyval
== keys
[i
].key
&& CLEAN(e
->state
) ==
962 keys
[i
].func(t
, &keys
[i
].arg
);
963 return (XT_CB_HANDLED
);
966 return (XT_CB_PASSTHROUGH
);
970 cmd_keyrelease_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
972 const gchar
*c
= gtk_entry_get_text(w
);
975 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
976 e
->keyval
, e
->state
, t
);
979 errx(1, "cmd_keyrelease_cb");
981 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
982 e
->keyval
, e
->state
, t
);
990 if (webkit_web_view_search_text(t
->wv
, &c
[1], FALSE
, TRUE
, TRUE
) == FALSE
) {
991 /* not found, mark red */
992 gdk_color_parse("red", &color
);
993 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
994 /* unmark and remove selection */
995 webkit_web_view_unmark_text_matches(t
->wv
);
996 /* my kingdom for a way to unselect text in webview */
998 /* found, highlight all */
999 webkit_web_view_unmark_text_matches(t
->wv
);
1000 webkit_web_view_mark_text_matches(t
->wv
, &c
[1], FALSE
, 0);
1001 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
1002 gdk_color_parse("white", &color
);
1003 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
1006 return (XT_CB_PASSTHROUGH
);
1010 cmd_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
1012 int rv
= XT_CB_HANDLED
;
1013 const gchar
*c
= gtk_entry_get_text(w
);
1016 errx(1, "cmd_keypress_cb");
1018 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
1019 e
->keyval
, e
->state
, t
);
1023 e
->keyval
= GDK_Escape
;
1024 else if (!(c
[0] == ':' || c
[0] == '/'))
1025 e
->keyval
= GDK_Escape
;
1027 switch (e
->keyval
) {
1029 if (!(!strcmp(c
, ":") || !strcmp(c
, "/")))
1033 gtk_widget_hide(t
->cmd
);
1034 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
1038 rv
= XT_CB_PASSTHROUGH
;
1044 cmd_focusout_cb(GtkWidget
*w
, GdkEventFocus
*e
, struct tab
*t
)
1047 errx(1, "cmd_focusout_cb");
1049 DNPRINTF(XT_D_CMD
, "cmd_focusout_cb: tab %d focus_wv %d\n",
1050 t
->tab_id
, t
->focus_wv
);
1052 /* abort command when losing focus */
1053 gtk_widget_hide(t
->cmd
);
1055 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
1057 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
1059 return (XT_CB_PASSTHROUGH
);
1063 cmd_activate_cb(GtkEntry
*entry
, struct tab
*t
)
1067 const gchar
*c
= gtk_entry_get_text(entry
);
1070 errx(1, "cmd_activate_cb");
1072 DNPRINTF(XT_D_CMD
, "cmd_activate_cb: tab %d %s\n", t
->tab_id
, c
);
1077 else if (!(c
[0] == ':' || c
[0] == '/'))
1084 if (t
->search_text
) {
1085 free(t
->search_text
);
1086 t
->search_text
= NULL
;
1089 t
->search_text
= strdup(s
);
1090 if (t
->search_text
== NULL
)
1091 err(1, "search_text");
1096 for (i
= 0; i
< LENGTH(cmds
); i
++)
1097 if (cmds
[i
].params
) {
1098 if (!strncmp(s
, cmds
[i
].cmd
, strlen(cmds
[i
].cmd
))) {
1099 cmds
[i
].arg
.s
= strdup(s
);
1100 cmds
[i
].func(t
, &cmds
[i
].arg
);
1103 if (!strcmp(s
, cmds
[i
].cmd
))
1104 cmds
[i
].func(t
, &cmds
[i
].arg
);
1108 gtk_widget_hide(t
->cmd
);
1112 backward_cb(GtkWidget
*w
, struct tab
*t
)
1115 errx(1, "backward_cb");
1117 DNPRINTF(XT_D_NAV
, "backward_cb: tab %d\n", t
->tab_id
);
1119 webkit_web_view_go_back(t
->wv
);
1123 forward_cb(GtkWidget
*w
, struct tab
*t
)
1126 errx(1, "forward_cb");
1128 DNPRINTF(XT_D_NAV
, "forward_cb: tab %d\n", t
->tab_id
);
1130 webkit_web_view_go_forward(t
->wv
);
1134 stop_cb(GtkWidget
*w
, struct tab
*t
)
1136 WebKitWebFrame
*frame
;
1141 DNPRINTF(XT_D_NAV
, "stop_cb: tab %d\n", t
->tab_id
);
1143 frame
= webkit_web_view_get_main_frame(t
->wv
);
1144 if (frame
== NULL
) {
1145 warnx("stop_cb: no frame");
1149 webkit_web_frame_stop_loading(frame
);
1153 create_browser(struct tab
*t
)
1158 errx(1, "create_browser");
1160 t
->sb_h
= GTK_SCROLLBAR(gtk_hscrollbar_new(NULL
));
1161 t
->sb_v
= GTK_SCROLLBAR(gtk_vscrollbar_new(NULL
));
1162 t
->adjust_h
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_h
));
1163 t
->adjust_v
= gtk_range_get_adjustment(GTK_RANGE(t
->sb_v
));
1165 w
= gtk_scrolled_window_new(t
->adjust_h
, t
->adjust_v
);
1166 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w
),
1167 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
1169 t
->wv
= WEBKIT_WEB_VIEW(webkit_web_view_new());
1170 gtk_container_add(GTK_CONTAINER(w
), GTK_WIDGET(t
->wv
));
1172 g_signal_connect(t
->wv
, "notify::load-status",
1173 G_CALLBACK(notify_load_status_cb
), t
);
1183 w
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
1184 gtk_window_set_default_size(GTK_WINDOW(w
), 800, 600);
1185 gtk_widget_set_name(w
, "xxxterm");
1186 gtk_window_set_wmclass(GTK_WINDOW(w
), "xxxterm", "XXXTerm");
1192 create_toolbar(struct tab
*t
)
1194 GtkWidget
*toolbar
= gtk_toolbar_new();
1197 #if GTK_CHECK_VERSION(2,15,0)
1198 gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar
),
1199 GTK_ORIENTATION_HORIZONTAL
);
1201 gtk_toolbar_set_orientation(GTK_TOOLBAR(toolbar
),
1202 GTK_ORIENTATION_HORIZONTAL
);
1204 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar
), GTK_TOOLBAR_BOTH_HORIZ
);
1207 /* backward button */
1208 t
->backward
= gtk_tool_button_new_from_stock(GTK_STOCK_GO_BACK
);
1209 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
), FALSE
);
1210 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
1211 G_CALLBACK(backward_cb
), t
);
1212 gtk_toolbar_insert(GTK_TOOLBAR(toolbar
), t
->backward
, -1);
1214 /* forward button */
1216 gtk_tool_button_new_from_stock(GTK_STOCK_GO_FORWARD
);
1217 gtk_widget_set_sensitive(GTK_WIDGET(t
->forward
), FALSE
);
1218 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
1219 G_CALLBACK(forward_cb
), t
);
1220 gtk_toolbar_insert(GTK_TOOLBAR(toolbar
), t
->forward
, -1);
1223 t
->stop
= gtk_tool_button_new_from_stock(GTK_STOCK_STOP
);
1224 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), FALSE
);
1225 g_signal_connect(G_OBJECT(t
->stop
), "clicked",
1226 G_CALLBACK(stop_cb
), t
);
1227 gtk_toolbar_insert(GTK_TOOLBAR(toolbar
), t
->stop
, -1);
1231 i
= gtk_tool_item_new();
1232 gtk_tool_item_set_expand(i
, TRUE
);
1233 t
->uri_entry
= gtk_entry_new();
1234 gtk_container_add(GTK_CONTAINER(i
), t
->uri_entry
);
1235 g_signal_connect(G_OBJECT(t
->uri_entry
), "activate",
1236 G_CALLBACK(activate_uri_entry_cb
), t
);
1237 gtk_toolbar_insert(GTK_TOOLBAR(toolbar
), i
, -1);
1240 if (fancy_bar
&& search_string
) {
1241 i
= gtk_tool_item_new();
1242 t
->search_entry
= gtk_entry_new();
1243 gtk_entry_set_width_chars(GTK_ENTRY(t
->search_entry
), 30);
1244 gtk_container_add(GTK_CONTAINER(i
), t
->search_entry
);
1245 g_signal_connect(G_OBJECT(t
->search_entry
), "activate",
1246 G_CALLBACK(activate_search_entry_cb
), t
);
1247 gtk_toolbar_insert(GTK_TOOLBAR(toolbar
), i
, -1);
1254 delete_tab(struct tab
*t
)
1256 DNPRINTF(XT_D_TAB
, "delete_tab: %p\n", t
);
1261 TAILQ_REMOVE(&tabs
, t
, entry
);
1262 if (TAILQ_EMPTY(&tabs
))
1263 create_new_tab(NULL
, 1);
1265 gtk_widget_destroy(t
->vbox
);
1270 setup_webkit(struct tab
*t
)
1275 /* XXX this can't be called over and over; fix it */
1276 t
->settings
= webkit_web_settings_new();
1277 g_object_get((GObject
*)t
->settings
, "user-agent", &strval
, NULL
);
1278 if (strval
== NULL
) {
1279 warnx("setup_webkit: can't get user-agent property");
1283 if (asprintf(&ua
, "%s %s+", strval
, version
) == -1)
1284 err(1, "aprintf user-agent");
1286 g_object_set((GObject
*)t
->settings
,
1287 "user-agent", ua
, NULL
);
1288 g_object_set((GObject
*)t
->settings
,
1289 "enable-scripts", enable_scripts
, NULL
);
1290 g_object_set((GObject
*)t
->settings
,
1291 "enable-plugins", enable_plugins
, NULL
);
1292 g_object_set((GObject
*)t
->settings
,
1293 "default-font-size", default_font_size
, NULL
);
1295 webkit_web_view_set_settings(t
->wv
, t
->settings
);
1302 create_new_tab(char *title
, int focus
)
1306 char *newuri
= NULL
;
1308 DNPRINTF(XT_D_TAB
, "create_new_tab: title %s focus %d\n", title
, focus
);
1310 if (tabless
&& !TAILQ_EMPTY(&tabs
)) {
1311 DNPRINTF(XT_D_TAB
, "create_new_tab: new tab rejected\n");
1315 t
= g_malloc0(sizeof *t
);
1316 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
1318 if (title
== NULL
) {
1319 title
= "(untitled)";
1322 if (valid_url_type(title
)) {
1323 newuri
= guess_url_type(title
);
1328 t
->vbox
= gtk_vbox_new(FALSE
, 0);
1331 t
->label
= gtk_label_new(title
);
1332 gtk_widget_set_size_request(t
->label
, 100, -1);
1335 t
->toolbar
= create_toolbar(t
);
1336 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->toolbar
, FALSE
, FALSE
, 0);
1339 t
->browser_win
= create_browser(t
);
1340 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->browser_win
, TRUE
, TRUE
, 0);
1344 t
->cmd
= gtk_entry_new();
1345 gtk_entry_set_inner_border(GTK_ENTRY(t
->cmd
), NULL
);
1346 gtk_entry_set_has_frame(GTK_ENTRY(t
->cmd
), FALSE
);
1347 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->cmd
, FALSE
, FALSE
, 0);
1349 /* and show it all */
1350 gtk_widget_show_all(t
->vbox
);
1351 t
->tab_id
= gtk_notebook_append_page(notebook
, t
->vbox
,
1354 g_object_connect((GObject
*)t
->cmd
,
1355 "signal::key-press-event", (GCallback
)cmd_keypress_cb
, t
,
1356 "signal::key-release-event", (GCallback
)cmd_keyrelease_cb
, t
,
1357 "signal::focus-out-event", (GCallback
)cmd_focusout_cb
, t
,
1358 "signal::activate", (GCallback
)cmd_activate_cb
, t
,
1361 g_object_connect((GObject
*)t
->wv
,
1362 "signal-after::key-press-event", (GCallback
)webview_keypress_cb
, t
,
1363 /* "signal::hovering-over-link", (GCallback)webview_hover_cb, t, */
1364 "signal::download-requested", (GCallback
)webview_download_cb
, t
,
1365 "signal::mime-type-policy-decision-requested", (GCallback
)webview_mimetype_cb
, t
,
1366 "signal::navigation-policy-decision-requested", (GCallback
)webview_npd_cb
, t
,
1367 "signal::event", (GCallback
)webview_event_cb
, t
,
1370 /* hijack the unused keys as if we were the browser */
1371 g_object_connect((GObject
*)t
->toolbar
,
1372 "signal-after::key-press-event", (GCallback
)webview_keypress_cb
, t
,
1375 g_signal_connect(G_OBJECT(t
->uri_entry
), "focus",
1376 G_CALLBACK(focus_uri_entry_cb
), t
);
1379 gtk_widget_hide(t
->cmd
);
1381 gtk_widget_hide(t
->toolbar
);
1384 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
1385 DNPRINTF(XT_D_TAB
, "create_new_tab: going to tab: %d\n",
1390 webkit_web_view_load_uri(t
->wv
, title
);
1392 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
1399 notebook_switchpage_cb(GtkNotebook
*nb
, GtkNotebookPage
*nbp
, guint pn
,
1404 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: tab: %d\n", pn
);
1406 TAILQ_FOREACH(t
, &tabs
, entry
) {
1407 if (t
->tab_id
== pn
) {
1408 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: going to "
1410 gtk_widget_hide(t
->cmd
);
1420 vbox
= gtk_vbox_new(FALSE
, 0);
1421 notebook
= GTK_NOTEBOOK(gtk_notebook_new());
1423 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook
), FALSE
);
1424 gtk_notebook_set_scrollable(notebook
, TRUE
);
1426 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(notebook
), TRUE
, TRUE
, 0);
1428 g_object_connect((GObject
*)notebook
,
1429 "signal::switch-page", (GCallback
)notebook_switchpage_cb
, NULL
,
1432 main_window
= create_window();
1433 gtk_container_add(GTK_CONTAINER(main_window
), vbox
);
1434 gtk_widget_show_all(main_window
);
1441 soup_session_remove_feature(session
,
1442 (SoupSessionFeature
*)cookiejar
);
1443 g_object_unref(cookiejar
);
1447 if (cookies_enabled
== 0)
1450 cookiejar
= soup_cookie_jar_text_new(cookie_file
, read_only_cookies
);
1451 soup_session_add_feature(session
, (SoupSessionFeature
*)cookiejar
);
1455 setup_proxy(char *uri
)
1458 g_object_set(session
, "proxy_uri", NULL
, NULL
);
1459 soup_uri_free(proxy_uri
);
1464 http_proxy
= strdup(uri
);
1465 if (http_proxy
== NULL
)
1466 err(1, "http_proxy");
1472 DNPRINTF(XT_D_CONFIG
, "setup_proxy: %s\n", uri
);
1473 proxy_uri
= soup_uri_new(uri
);
1475 g_object_set(session
, "proxy-uri", proxy_uri
, NULL
);
1482 "%s [-STVt][-f file] url ...\n", __progname
);
1487 main(int argc
, char *argv
[])
1491 char conf
[PATH_MAX
] = { '\0' };
1492 char *env_proxy
= NULL
;
1494 while ((c
= getopt(argc
, argv
, "STVf:t")) != -1) {
1503 errx(0 , "Version: %s", version
);
1506 strlcpy(conf
, optarg
, sizeof(conf
));
1522 gtk_init(&argc
, &argv
);
1523 if (!g_thread_supported())
1524 g_thread_init(NULL
);
1526 pwd
= getpwuid(getuid());
1528 errx(1, "invalid user %d", getuid());
1530 /* set download dir */
1531 strlcpy(download_dir
, pwd
->pw_dir
, sizeof download_dir
);
1533 /* read config file */
1534 if (strlen(conf
) == 0)
1535 snprintf(conf
, sizeof conf
, "%s/.%s",
1536 pwd
->pw_dir
, XT_CONF_FILE
);
1539 if (stat(download_dir
, &sb
))
1540 errx(1, "must specify a valid download_dir");
1542 /* working directory */
1543 snprintf(work_dir
, sizeof work_dir
, "%s/%s", pwd
->pw_dir
, XT_DIR
);
1544 if (stat(work_dir
, &sb
)) {
1545 if (mkdir(work_dir
, S_IRWXU
) == -1)
1547 } else if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
1548 warnx("fixing invalid permissions on %s", work_dir
);
1549 if (chmod(work_dir
, S_IRWXU
) == -1)
1554 session
= webkit_get_default_session();
1555 snprintf(cookie_file
, sizeof cookie_file
, "%s/cookies.txt", work_dir
);
1559 env_proxy
= getenv("http_proxy");
1561 http_proxy
= strdup(env_proxy
);
1562 if (http_proxy
== NULL
)
1563 err(1, "http_proxy");
1565 setup_proxy(http_proxy
);
1570 create_new_tab(argv
[0], focus
);
1577 create_new_tab(home
, 1);