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.
21 * inverse color browsing
23 * download files status
24 * multi letter commands
25 * pre and post counts for commands
38 #include <sys/queue.h>
39 #include <sys/types.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$";
51 /* #define 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
75 #define DNPRINTF(n,x...)
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) & \
87 TAILQ_ENTRY(tab
) entry
;
92 GtkWidget
*browser_win
;
96 /* adjustments for browser */
99 GtkAdjustment
*adjust_h
;
100 GtkAdjustment
*adjust_v
;
109 TAILQ_HEAD(tab_list
, tab
);
117 #define XT_DIR (".xxxterm")
118 #define XT_CONF_FILE ("xxxterm.conf")
119 #define XT_CB_HANDLED (TRUE)
120 #define XT_CB_PASSTHROUGH (FALSE)
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)
148 extern char *__progname
;
150 GtkWidget
*main_window
;
151 GtkNotebook
*notebook
;
152 struct tab_list tabs
;
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
;
169 void create_new_tab(char *, int);
170 void delete_tab(struct tab
*);
172 struct valid_url_types
{
182 valid_url_type(char *url
)
186 for (i
= 0; i
< LENGTH(vut
); i
++)
187 if (!strncasecmp(vut
[i
].type
, url
, strlen(vut
[i
].type
)))
194 guess_url_type(char *url_in
)
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");
205 if (asprintf(&url_out
, "http://%s", url_in
) == -1)
206 err(1, "aprintf http");
210 err(1, "asprintf pointer");
212 DNPRINTF(XT_D_URL
, "guess_url_type: guessed %s\n", url_out
);
219 config_parse(char *filename
)
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
)
230 if ((config
= fopen(filename
, "r")) == NULL
) {
231 warn("config_parse: cannot open %s", filename
);
236 if ((line
= fparseln(config
, &len
, &lineno
, NULL
, 0)) == NULL
)
241 cp
+= (long)strspn(cp
, WS
);
248 if ((var
= strsep(&cp
, WS
)) == NULL
|| cp
== NULL
)
251 cp
+= (long)strspn(cp
, WS
);
252 if ((val
= strsep(&cp
, WS
)) == NULL
)
255 DNPRINTF(XT_D_CONFIG
, "config_parse: %s=%s\n",var
,val
);
258 if (!strcmp(var
, "home"))
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")) {
268 snprintf(download_dir
, sizeof download_dir
,
269 "%s/%s", pwd
->pw_dir
, &val
[1]);
271 strlcpy(download_dir
, val
, sizeof download_dir
);
272 fprintf(stderr
, "download dir: %s\n", download_dir
);
274 errx(1, "invalid conf file entry: %s=%s", var
, val
);
282 quit(struct tab
*t
, struct karg
*args
)
290 help(struct tab
*t
, struct karg
*args
)
295 webkit_web_view_load_string(t
->wv
,
296 "<html><body><h1>XXXTerm</h1></body></html>",
305 navaction(struct tab
*t
, struct karg
*args
)
307 DNPRINTF(XT_D_NAV
, "navaction: tab %d opcode %d\n",
312 webkit_web_view_go_back(t
->wv
);
315 webkit_web_view_go_forward(t
->wv
);
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
;
332 case XT_MOVE_PAGEDOWN
:
334 adjust
= t
->adjust_v
;
337 adjust
= t
->adjust_h
;
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
);
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
);
358 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
363 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
366 case XT_MOVE_FARRIGHT
:
367 gtk_adjustment_set_value(adjust
, max
);
370 case XT_MOVE_FARLEFT
:
371 gtk_adjustment_set_value(adjust
, lower
);
373 case XT_MOVE_PAGEDOWN
:
375 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
379 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
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
);
391 getparams(char *cmd
, char *cmp
)
396 if (!strncmp(cmd
, cmp
, strlen(cmp
))) {
397 rv
= cmd
+ strlen(cmp
);
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
);
417 return (XT_CB_PASSTHROUGH
);
421 if ((url
= getparams(args
->s
, "tabnew")))
422 create_new_tab(url
, 1);
424 create_new_tab(NULL
, 1);
430 if (gtk_notebook_get_n_pages(notebook
) > 1)
436 if ((url
= getparams(args
->s
, "open")) ||
437 ((url
= getparams(args
->s
, "op"))) ||
438 ((url
= getparams(args
->s
, "o"))))
441 rv
= XT_CB_PASSTHROUGH
;
445 if (valid_url_type(url
)) {
446 newuri
= guess_url_type(url
);
449 webkit_web_view_load_uri(t
->wv
, url
);
454 rv
= XT_CB_PASSTHROUGH
;
468 movetab(struct tab
*t
, struct karg
*args
)
473 DNPRINTF(XT_D_TAB
, "movetab: %p %d\n", t
, args
->i
);
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
);
489 gtk_notebook_prev_page(notebook
);
491 return (XT_CB_HANDLED
);
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
);
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 */
531 int (*func
)(struct tab
*, struct karg
*);
534 { GDK_SHIFT_MASK
, 0, GDK_colon
, command
, {0} },
535 { GDK_CONTROL_MASK
, 0, GDK_q
, quit
, {0} },
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
} },
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} },
581 int (*func
)(struct tab
*, struct karg
*);
584 { "q!", 0, quit
, {0} },
585 { "qa", 0, quit
, {0} },
586 { "qa!", 0, quit
, {0} },
587 { "help", 0, help
, {0} },
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
} },
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
);
613 errx(1, "focus_uri_entry_cb");
615 /* focus on wv instead */
617 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
621 activate_uri_entry_cb(GtkWidget
* entry
, struct tab
*t
)
623 const gchar
*uri
= gtk_entry_get_text(GTK_ENTRY(entry
));
626 DNPRINTF(XT_D_URL
, "activate_uri_entry_cb: %s\n", uri
);
629 errx(1, "activate_uri_entry_cb");
634 if (valid_url_type((char *)uri
)) {
635 newuri
= guess_url_type((char *)uri
);
639 webkit_web_view_load_uri(t
->wv
, uri
);
640 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
647 notify_load_status_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
649 WebKitWebFrame
*frame
;
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
);
660 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
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
));
667 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT
:
668 uri
= webkit_web_view_get_title(wview
);
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
);
675 case WEBKIT_LOAD_PROVISIONAL
:
676 case WEBKIT_LOAD_FINISHED
:
677 case WEBKIT_LOAD_FAILED
:
684 webview_npd_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
,
685 WebKitNetworkRequest
*request
, WebKitWebNavigationAction
*na
,
686 WebKitWebPolicyDecision
*pd
, struct tab
*t
)
691 errx(1, "webview_npd_cb");
693 DNPRINTF(XT_D_NAV
, "webview_npd_cb: %s\n",
694 webkit_network_request_get_uri(request
));
697 uri
= (char *)webkit_network_request_get_uri(request
);
698 create_new_tab(uri
, ctrl_click_focus
);
700 webkit_web_policy_decision_ignore(pd
);
702 return (TRUE
); /* we made the decission */
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
)
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
)
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
);
743 webview_download_cb(WebKitWebView
*wv
, WebKitDownload
*download
, struct tab
*t
)
745 const gchar
*filename
;
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 "
760 t
->tab_id
, filename
, uri
);
762 webkit_download_set_destination_uri(download
, uri
);
767 webkit_download_start(download
);
769 return (TRUE
); /* start download */
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
);
778 errx(1, "webview_hover_cb");
785 t
->hover
= strdup(uri
);
786 } else if (t
->hover
) {
793 webview_keypress_cb(GtkWidget
*w
, GdkEventKey
*e
, struct tab
*t
)
797 /* don't use w directly; use t->whatever instead */
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
) ==
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
);
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
);
829 e
->keyval
= GDK_Escape
;
830 else if (c
[0] != ':')
831 e
->keyval
= GDK_Escape
;
839 gtk_widget_hide(t
->cmd
);
840 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
844 rv
= XT_CB_PASSTHROUGH
;
850 cmd_focusout_cb(GtkWidget
*w
, GdkEventFocus
*e
, struct tab
*t
)
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
);
861 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
863 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
865 return (XT_CB_PASSTHROUGH
);
869 cmd_activate_cb(GtkEntry
*entry
, struct tab
*t
)
873 const gchar
*c
= gtk_entry_get_text(entry
);
876 errx(1, "cmd_activate_cb");
878 DNPRINTF(XT_D_CMD
, "cmd_activate_cb: tab %d %s\n", t
->tab_id
, c
);
883 else if (c
[0] != ':')
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
);
896 if (!strcmp(s
, cmds
[i
].cmd
))
897 cmds
[i
].func(t
, &cmds
[i
].arg
);
901 gtk_widget_hide(t
->cmd
);
905 create_browser(struct tab
*t
)
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
);
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");
944 create_toolbar(struct tab
*t
)
946 GtkWidget
*toolbar
= gtk_toolbar_new();
949 #if GTK_CHECK_VERSION(2,15,0)
950 gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar
),
951 GTK_ORIENTATION_HORIZONTAL
);
953 gtk_toolbar_set_orientation(GTK_TOOLBAR(toolbar
),
954 GTK_ORIENTATION_HORIZONTAL
);
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);
970 delete_tab(struct tab
*t
)
972 DNPRINTF(XT_D_TAB
, "delete_tab: %p\n", t
);
977 TAILQ_REMOVE(&tabs
, t
, entry
);
978 if (TAILQ_EMPTY(&tabs
))
979 create_new_tab(NULL
, 1);
981 gtk_widget_destroy(t
->vbox
);
986 create_new_tab(char *title
, int focus
)
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");
999 t
= g_malloc0(sizeof *t
);
1000 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
1002 if (title
== NULL
) {
1003 title
= "(untitled)";
1006 if (valid_url_type(title
)) {
1007 newuri
= guess_url_type(title
);
1012 t
->vbox
= gtk_vbox_new(FALSE
, 0);
1015 t
->label
= gtk_label_new(title
);
1016 gtk_widget_set_size_request(t
->label
, 100, -1);
1019 t
->toolbar
= create_toolbar(t
);
1020 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->toolbar
, FALSE
, FALSE
, 0);
1023 t
->browser_win
= create_browser(t
);
1024 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->browser_win
, TRUE
, TRUE
, 0);
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
,
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
,
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
,
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
,
1057 g_signal_connect(G_OBJECT(t
->uri_entry
), "focus",
1058 G_CALLBACK(focus_uri_entry_cb
), t
);
1061 gtk_widget_hide(t
->cmd
);
1063 gtk_widget_hide(t
->toolbar
);
1066 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
1067 DNPRINTF(XT_D_TAB
, "create_new_tab: going to tab: %d\n",
1072 webkit_web_view_load_uri(t
->wv
, title
);
1074 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
1081 notebook_switchpage_cb(GtkNotebook
*nb
, GtkNotebookPage
*nbp
, guint pn
,
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 "
1092 gtk_widget_hide(t
->cmd
);
1102 vbox
= gtk_vbox_new(FALSE
, 0);
1103 notebook
= GTK_NOTEBOOK(gtk_notebook_new());
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
,
1113 main_window
= create_window();
1114 gtk_container_add(GTK_CONTAINER(main_window
), vbox
);
1115 gtk_widget_show_all(main_window
);
1122 soup_session_remove_feature(session
,
1123 (SoupSessionFeature
*)cookiejar
);
1124 g_object_unref(cookiejar
);
1128 if (cookies_enabled
== 0)
1131 cookiejar
= soup_cookie_jar_text_new(cookie_file
, read_only_cookies
);
1132 soup_session_add_feature(session
, (SoupSessionFeature
*)cookiejar
);
1139 "%s [-STVt][-f file] url ...\n", __progname
);
1144 main(int argc
, char *argv
[])
1147 char conf
[PATH_MAX
] = { '\0' };
1150 while ((c
= getopt(argc
, argv
, "STVf:t")) != -1) {
1159 errx(0 , "Version: %s", version
);
1162 strlcpy(conf
, optarg
, sizeof(conf
));
1178 gtk_init(&argc
, &argv
);
1179 if (!g_thread_supported())
1180 g_thread_init(NULL
);
1182 pwd
= getpwuid(getuid());
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
);
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)
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)
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
);
1218 create_new_tab(argv
[0], focus
);
1225 create_new_tab(home
, 1);