2 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
3 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
4 * Copyright (c) 2010, 2011, 2012 Edd Barrett <vext01@gmail.com>
5 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
6 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
7 * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
8 * Copyright (c) 2012 Josh Rickmar <jrick@devio.us>
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
26 char *version
= XOMBRERO_VERSION
;
29 uint32_t swm_debug
= 0
52 GCRY_THREAD_OPTION_PTHREAD_IMPL
;
64 TAILQ_ENTRY(session
) entry
;
67 TAILQ_HEAD(session_list
, session
);
70 TAILQ_ENTRY(undo
) entry
;
73 int back
; /* Keeps track of how many back
74 * history items there are. */
76 TAILQ_HEAD(undo_tailq
, undo
);
78 struct command_entry
{
80 TAILQ_ENTRY(command_entry
) entry
;
82 TAILQ_HEAD(command_list
, command_entry
);
85 #define XT_CACHE_DIR ("cache")
86 #define XT_CERT_DIR ("certs")
87 #define XT_CERT_CACHE_DIR ("certs_cache")
88 #define XT_JS_DIR ("js")
89 #define XT_SESSIONS_DIR ("sessions")
90 #define XT_TEMP_DIR ("tmp")
91 #define XT_QMARKS_FILE ("quickmarks")
92 #define XT_SAVED_TABS_FILE ("main_session")
93 #define XT_RESTART_TABS_FILE ("restart_tabs")
94 #define XT_SOCKET_FILE ("socket")
95 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
96 #define XT_SEARCH_FILE ("search_history")
97 #define XT_COMMAND_FILE ("command_history")
98 #define XT_DLMAN_REFRESH "10"
99 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
100 #define XT_MAX_UNDO_CLOSE_TAB (32)
101 #define XT_PRINT_EXTRA_MARGIN 10
102 #define XT_URL_REGEX ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+\\.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
103 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
106 #define XT_COLOR_RED "#cc0000"
107 #define XT_COLOR_YELLOW "#ffff66"
108 #define XT_COLOR_BLUE "lightblue"
109 #define XT_COLOR_GREEN "#99ff66"
110 #define XT_COLOR_WHITE "white"
111 #define XT_COLOR_BLACK "black"
113 #define XT_COLOR_CT_BACKGROUND "#000000"
114 #define XT_COLOR_CT_INACTIVE "#dddddd"
115 #define XT_COLOR_CT_ACTIVE "#bbbb00"
116 #define XT_COLOR_CT_SEPARATOR "#555555"
118 #define XT_COLOR_SB_SEPARATOR "#555555"
120 /* CSS element names */
121 #define XT_CSS_NORMAL ""
122 #define XT_CSS_RED "red"
123 #define XT_CSS_YELLOW "yellow"
124 #define XT_CSS_GREEN "green"
125 #define XT_CSS_BLUE "blue"
126 #define XT_CSS_HIDDEN "hidden"
127 #define XT_CSS_ACTIVE "active"
129 #define XT_PROTO_DELIM "://"
132 #define XT_MOVE_INVALID (0)
133 #define XT_MOVE_DOWN (1)
134 #define XT_MOVE_UP (2)
135 #define XT_MOVE_BOTTOM (3)
136 #define XT_MOVE_TOP (4)
137 #define XT_MOVE_PAGEDOWN (5)
138 #define XT_MOVE_PAGEUP (6)
139 #define XT_MOVE_HALFDOWN (7)
140 #define XT_MOVE_HALFUP (8)
141 #define XT_MOVE_LEFT (9)
142 #define XT_MOVE_FARLEFT (10)
143 #define XT_MOVE_RIGHT (11)
144 #define XT_MOVE_FARRIGHT (12)
145 #define XT_MOVE_PERCENT (13)
146 #define XT_MOVE_CENTER (14)
148 #define XT_QMARK_SET (0)
149 #define XT_QMARK_OPEN (1)
150 #define XT_QMARK_TAB (2)
152 #define XT_MARK_SET (0)
153 #define XT_MARK_GOTO (1)
155 #define XT_GO_UP_ROOT (999)
157 #define XT_NAV_INVALID (0)
158 #define XT_NAV_BACK (1)
159 #define XT_NAV_FORWARD (2)
160 #define XT_NAV_RELOAD (3)
161 #define XT_NAV_STOP (4)
163 #define XT_FOCUS_INVALID (0)
164 #define XT_FOCUS_URI (1)
165 #define XT_FOCUS_SEARCH (2)
167 #define XT_SEARCH_INVALID (0)
168 #define XT_SEARCH_NEXT (1)
169 #define XT_SEARCH_PREV (2)
171 #define XT_PASTE_CURRENT_TAB (0)
172 #define XT_PASTE_NEW_TAB (1)
174 #define XT_ZOOM_IN (-1)
175 #define XT_ZOOM_OUT (-2)
176 #define XT_ZOOM_NORMAL (100)
178 #define XT_SES_DONOTHING (0)
179 #define XT_SES_CLOSETABS (1)
181 #define XT_PREFIX (1<<0)
182 #define XT_USERARG (1<<1)
183 #define XT_URLARG (1<<2)
184 #define XT_INTARG (1<<3)
185 #define XT_SESSARG (1<<4)
186 #define XT_SETARG (1<<5)
188 #define XT_HINT_NEWTAB (1<<0)
190 #define XT_BUFCMD_SZ (8)
192 #define XT_EJS_SHOW (1<<0)
194 GtkWidget
* create_button(char *, char *, int);
196 void recalc_tabs(void);
197 void recolor_compact_tabs(void);
198 void set_current_tab(int page_num
);
199 gboolean
update_statusbar_position(GtkAdjustment
*, gpointer
);
200 void marks_clear(struct tab
*t
);
203 extern char *__progname
;
204 char * const *start_argv
;
206 GtkWidget
*main_window
;
207 GtkNotebook
*notebook
;
209 GtkWidget
*tab_bar_box
;
210 GtkWidget
*arrow
, *abtn
;
211 GdkEvent
*fevent
= NULL
;
212 struct tab_list tabs
;
213 struct history_list hl
;
214 int hl_purge_count
= 0;
215 struct session_list sessions
;
217 struct wl_list js_wl
;
218 struct wl_list pl_wl
;
219 struct wl_list force_https
;
221 struct strict_transport_tree st_tree
;
222 struct undo_tailq undos
;
223 struct keybinding_list kbl
;
225 struct user_agent_list ua_list
;
226 struct http_accept_list ha_list
;
227 struct domain_id_list di_list
;
228 struct cmd_alias_list cal
;
229 struct custom_uri_list cul
;
230 struct command_list chl
;
231 struct command_list shl
;
232 struct command_entry
*history_at
;
233 struct command_entry
*search_at
;
234 struct secviolation_list svl
;
235 struct set_reject_list srl
;
237 int cmd_history_count
= 0;
238 int search_history_count
= 0;
240 uint64_t blocked_cookies
= 0;
241 char named_session
[PATH_MAX
];
242 GtkListStore
*completion_model
;
243 GtkListStore
*buffers_store
;
246 char *qmarks
[XT_NOQMARKS
];
247 int btn_down
; /* M1 down in any wv */
248 regex_t url_re
; /* guess_search regex */
250 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
251 int next_download_id
= 1;
253 void xxx_dir(char *);
254 int icon_size_map(int);
255 void activate_uri_entry_cb(GtkWidget
*, struct tab
*);
258 history_delete(struct command_list
*l
, int *counter
)
260 struct command_entry
*c
;
262 if (l
== NULL
|| counter
== NULL
)
265 c
= TAILQ_LAST(l
, command_list
);
269 TAILQ_REMOVE(l
, c
, entry
);
276 history_add(struct command_list
*list
, char *file
, char *l
, int *counter
)
278 struct command_entry
*c
;
281 if (list
== NULL
|| l
== NULL
|| counter
== NULL
)
284 /* don't add the same line */
285 c
= TAILQ_FIRST(list
);
287 if (!strcmp(c
->line
+ 1 /* skip space */, l
))
290 c
= g_malloc0(sizeof *c
);
291 c
->line
= g_strdup_printf(" %s", l
);
294 TAILQ_INSERT_HEAD(list
, c
, entry
);
297 history_delete(list
, counter
);
299 if (history_autosave
&& file
) {
300 f
= fopen(file
, "w");
302 show_oops(NULL
, "couldn't write history %s", file
);
306 TAILQ_FOREACH_REVERSE(c
, list
, command_list
, entry
) {
308 fprintf(f
, "%s\n", c
->line
);
316 history_read(struct command_list
*list
, char *file
, int *counter
)
319 char *s
, line
[65536];
321 if (list
== NULL
|| file
== NULL
)
324 f
= fopen(file
, "r");
326 startpage_add("couldn't open history file %s", file
);
331 s
= fgets(line
, sizeof line
, f
);
332 if (s
== NULL
|| feof(f
) || ferror(f
))
334 if ((s
= strchr(line
, '\n')) == NULL
) {
335 startpage_add("invalid history file %s", file
);
341 history_add(list
, NULL
, line
+ 1, counter
);
349 /* marks array storage. */
353 if (i
< 0 || i
>= XT_NOMARKS
)
364 if ((ret
= strchr(XT_MARKS
, m
)) != NULL
)
365 return ret
- XT_MARKS
;
370 /* quickmarks array storage. */
374 if (i
< 0 || i
>= XT_NOQMARKS
)
385 if ((ret
= strchr(XT_QMARKS
, m
)) != NULL
)
386 return ret
- XT_QMARKS
;
392 is_g_object_setting(GObject
*o
, char *str
)
394 guint n_props
= 0, i
;
395 GParamSpec
**proplist
;
401 proplist
= g_object_class_list_properties(G_OBJECT_GET_CLASS(o
),
404 for (i
= 0; i
< n_props
; i
++) {
405 if (! strcmp(proplist
[i
]->name
, str
)) {
416 get_current_tab(void)
420 TAILQ_FOREACH(t
, &tabs
, entry
) {
421 if (t
->tab_id
== gtk_notebook_get_current_page(notebook
))
425 warnx("%s: no current tab", __func__
);
431 set_ssl_ca_file(struct settings
*s
, char *file
)
435 if (file
== NULL
|| strlen(file
) == 0)
437 if (stat(file
, &sb
)) {
438 warnx("no CA file: %s", file
);
441 expand_tilde(ssl_ca_file
, sizeof ssl_ca_file
, file
);
442 g_object_set(session
,
443 SOUP_SESSION_SSL_CA_FILE
, ssl_ca_file
,
444 SOUP_SESSION_SSL_STRICT
, ssl_strict_certs
,
450 set_status(struct tab
*t
, gchar
*fmt
, ...)
457 status
= g_strdup_vprintf(fmt
, ap
);
459 gtk_entry_set_text(GTK_ENTRY(t
->sbe
.uri
), status
);
463 else if (strcmp(t
->status
, status
)) {
474 hide_cmd(struct tab
*t
)
476 DNPRINTF(XT_D_CMD
, "%s: tab %d\n", __func__
, t
->tab_id
);
478 history_at
= NULL
; /* just in case */
479 search_at
= NULL
; /* just in case */
480 gtk_widget_set_can_focus(t
->cmd
, FALSE
);
481 gtk_widget_hide(t
->cmd
);
485 show_cmd(struct tab
*t
, const char *s
)
487 DNPRINTF(XT_D_CMD
, "%s: tab %d\n", __func__
, t
->tab_id
);
489 /* without this you can't middle click in t->cmd to paste */
490 gtk_entry_set_text(GTK_ENTRY(t
->cmd
), "");
494 gtk_widget_hide(t
->oops
);
495 gtk_widget_set_can_focus(t
->cmd
, TRUE
);
496 gtk_widget_show(t
->cmd
);
498 gtk_widget_grab_focus(GTK_WIDGET(t
->cmd
));
499 #if GTK_CHECK_VERSION(3, 0, 0)
500 gtk_widget_set_name(t
->cmd
, XT_CSS_NORMAL
);
502 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
,
503 &t
->default_style
->base
[GTK_STATE_NORMAL
]);
505 gtk_entry_set_text(GTK_ENTRY(t
->cmd
), s
);
506 gtk_editable_set_position(GTK_EDITABLE(t
->cmd
), -1);
510 hide_buffers(struct tab
*t
)
512 gtk_widget_hide(t
->buffers
);
513 gtk_widget_set_can_focus(t
->buffers
, FALSE
);
514 gtk_list_store_clear(buffers_store
);
525 sort_tabs_by_page_num(struct tab
***stabs
)
530 num_tabs
= gtk_notebook_get_n_pages(notebook
);
532 *stabs
= g_malloc0(num_tabs
* sizeof(struct tab
*));
534 TAILQ_FOREACH(t
, &tabs
, entry
)
535 (*stabs
)[gtk_notebook_page_num(notebook
, t
->vbox
)] = t
;
541 buffers_make_list(void)
544 const gchar
*title
= NULL
;
546 struct tab
**stabs
= NULL
;
548 num_tabs
= sort_tabs_by_page_num(&stabs
);
550 for (i
= 0; i
< num_tabs
; i
++)
552 gtk_list_store_append(buffers_store
, &iter
);
553 title
= get_title(stabs
[i
], FALSE
);
554 gtk_list_store_set(buffers_store
, &iter
,
555 COL_ID
, i
+ 1, /* Enumerate the tabs starting from 1
557 COL_FAVICON
, gtk_image_get_pixbuf
558 (GTK_IMAGE(stabs
[i
]->tab_elems
.favicon
)),
567 show_buffers(struct tab
*t
)
570 GtkTreeSelection
*sel
;
574 if (gtk_widget_get_visible(GTK_WIDGET(t
->buffers
)))
579 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(t
->buffers
));
580 index
= gtk_notebook_get_current_page(notebook
);
581 path
= gtk_tree_path_new_from_indices(index
, -1);
582 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store
), &iter
, path
))
583 gtk_tree_selection_select_iter(sel
, &iter
);
584 gtk_tree_path_free(path
);
586 gtk_widget_show(t
->buffers
);
587 gtk_widget_set_can_focus(t
->buffers
, TRUE
);
588 gtk_widget_grab_focus(GTK_WIDGET(t
->buffers
));
592 toggle_buffers(struct tab
*t
)
594 if (gtk_widget_get_visible(t
->buffers
))
601 buffers(struct tab
*t
, struct karg
*args
)
609 set_scrollbar_visibility(struct tab
*t
, int visible
)
611 #if GTK_CHECK_VERSION(3, 0, 0)
612 GtkWidget
*h_scrollbar
, *v_scrollbar
;
614 h_scrollbar
= gtk_scrolled_window_get_hscrollbar(
615 GTK_SCROLLED_WINDOW(t
->browser_win
));
616 v_scrollbar
= gtk_scrolled_window_get_vscrollbar(
617 GTK_SCROLLED_WINDOW(t
->browser_win
));
620 gtk_widget_set_name(h_scrollbar
, XT_CSS_HIDDEN
);
621 gtk_widget_set_name(v_scrollbar
, XT_CSS_HIDDEN
);
623 gtk_widget_set_name(h_scrollbar
, "");
624 gtk_widget_set_name(v_scrollbar
, "");
629 return (visible
== 0);
634 hide_oops(struct tab
*t
)
636 gtk_widget_hide(t
->oops
);
640 show_oops(struct tab
*at
, const char *fmt
, ...)
644 struct tab
*t
= NULL
;
650 if ((t
= get_current_tab()) == NULL
)
656 if ((msg
= g_strdup_vprintf(fmt
, ap
)) == NULL
)
657 errx(1, "show_oops failed");
660 gtk_entry_set_text(GTK_ENTRY(t
->oops
), msg
);
661 gtk_widget_hide(t
->cmd
);
662 gtk_widget_show(t
->oops
);
668 char work_dir
[PATH_MAX
];
669 char certs_dir
[PATH_MAX
];
670 char certs_cache_dir
[PATH_MAX
];
671 char js_dir
[PATH_MAX
];
672 char cache_dir
[PATH_MAX
];
673 char sessions_dir
[PATH_MAX
];
674 char temp_dir
[PATH_MAX
];
675 char cookie_file
[PATH_MAX
];
676 char *strict_transport_file
= NULL
;
677 SoupSession
*session
;
678 SoupCookieJar
*s_cookiejar
;
679 SoupCookieJar
*p_cookiejar
;
680 char rc_fname
[PATH_MAX
];
682 struct mime_type_list mtl
;
683 struct alias_list aliases
;
686 struct tab
*create_new_tab(char *, struct undo
*, int, int);
687 void delete_tab(struct tab
*);
688 void setzoom_webkit(struct tab
*, int);
689 int download_rb_cmp(struct download
*, struct download
*);
690 gboolean
cmd_execute(struct tab
*t
, char *str
);
693 history_rb_cmp(struct history
*h1
, struct history
*h2
)
695 return (strcmp(h1
->uri
, h2
->uri
));
697 RB_GENERATE(history_list
, history
, entry
, history_rb_cmp
);
700 download_rb_cmp(struct download
*e1
, struct download
*e2
)
702 return (e1
->id
< e2
->id
? -1 : e1
->id
> e2
->id
);
704 RB_GENERATE(download_list
, download
, entry
, download_rb_cmp
);
707 secviolation_rb_cmp(struct secviolation
*s1
, struct secviolation
*s2
)
709 return (s1
->xtp_arg
< s2
->xtp_arg
? -1 : s1
->xtp_arg
> s2
->xtp_arg
);
711 RB_GENERATE(secviolation_list
, secviolation
, entry
, secviolation_rb_cmp
);
714 user_agent_rb_cmp(struct user_agent
*ua1
, struct user_agent
*ua2
)
716 return (ua1
->id
< ua2
->id
? -1 : ua1
->id
> ua2
->id
);
718 RB_GENERATE(user_agent_list
, user_agent
, entry
, user_agent_rb_cmp
);
721 http_accept_rb_cmp(struct http_accept
*ha1
, struct http_accept
*ha2
)
723 return (ha1
->id
< ha2
->id
? -1 : ha1
->id
> ha2
->id
);
725 RB_GENERATE(http_accept_list
, http_accept
, entry
, http_accept_rb_cmp
);
728 domain_id_rb_cmp(struct domain_id
*d1
, struct domain_id
*d2
)
730 return (strcmp(d1
->domain
, d2
->domain
));
732 RB_GENERATE(domain_id_list
, domain_id
, entry
, domain_id_rb_cmp
);
734 struct valid_url_types
{
746 valid_url_type(char *url
)
750 for (i
= 0; i
< LENGTH(vut
); i
++)
751 if (!strncasecmp(vut
[i
].type
, url
, strlen(vut
[i
].type
)))
758 match_alias(char *url_in
)
762 char *url_out
= NULL
, *search
, *enc_arg
;
765 search
= g_strdup(url_in
);
767 if (strsep(&arg
, " \t") == NULL
) {
768 show_oops(NULL
, "match_alias: NULL URL");
772 TAILQ_FOREACH(a
, &aliases
, entry
) {
773 if (!strcmp(search
, a
->a_name
))
778 DNPRINTF(XT_D_URL
, "match_alias: matched alias %s\n",
782 enc_arg
= soup_uri_encode(arg
, XT_RESERVED_CHARS
);
783 sv
= g_strsplit(a
->a_uri
, "%s", 2);
785 url_out
= g_strjoinv(enc_arg
, sv
);
787 url_out
= g_strjoinv("", sv
);
797 guess_url_type(char *url_in
)
800 char cwd
[PATH_MAX
] = {0};
801 char *url_out
= NULL
, *enc_search
= NULL
;
807 /* substitute aliases */
808 url_out
= match_alias(url_in
);
812 /* see if we are an about page */
813 if (!strncmp(url_in
, XT_URI_ABOUT
, XT_URI_ABOUT_LEN
))
814 for (i
= 0; i
< about_list_size(); i
++)
815 if (!strcmp(&url_in
[XT_URI_ABOUT_LEN
],
816 about_list
[i
].name
)) {
817 url_out
= g_strdup(url_in
);
821 if (guess_search
&& url_regex
&&
822 !(g_str_has_prefix(url_in
, "http://") ||
823 g_str_has_prefix(url_in
, "https://"))) {
824 if (regexec(&url_re
, url_in
, 0, NULL
, 0)) {
825 /* invalid URI so search instead */
826 enc_search
= soup_uri_encode(url_in
, XT_RESERVED_CHARS
);
827 sv
= g_strsplit(search_string
, "%s", 2);
828 url_out
= g_strjoinv(enc_search
, sv
);
835 /* XXX not sure about this heuristic */
836 if (stat(url_in
, &sb
) == 0) {
837 if (url_in
[0] == '/')
838 url_out
= g_filename_to_uri(url_in
, NULL
, NULL
);
840 if (getcwd(cwd
, PATH_MAX
) != NULL
) {
841 path
= g_strdup_printf("%s" PS
"%s", cwd
,
843 url_out
= g_filename_to_uri(path
, NULL
, NULL
);
848 url_out
= g_strdup_printf("http://%s", url_in
); /* guess http */
850 DNPRINTF(XT_D_URL
, "guess_url_type: guessed %s\n", url_out
);
856 set_normal_tab_meaning(struct tab
*t
)
861 t
->xtp_meaning
= XT_XTP_TAB_MEANING_NORMAL
;
862 if (t
->session_key
!= NULL
) {
863 g_free(t
->session_key
);
864 t
->session_key
= NULL
;
869 load_uri(struct tab
*t
, gchar
*uri
)
872 gchar
*newuri
= NULL
;
878 /* Strip leading spaces. */
879 while (*uri
&& isspace(*uri
))
882 if (strlen(uri
) == 0) {
887 set_normal_tab_meaning(t
);
889 if (valid_url_type(uri
)) {
890 newuri
= guess_url_type(uri
);
894 if (!strncmp(uri
, XT_URI_ABOUT
, XT_URI_ABOUT_LEN
)) {
895 for (i
= 0; i
< about_list_size(); i
++)
896 if (!strcmp(&uri
[XT_URI_ABOUT_LEN
], about_list
[i
].name
) &&
897 about_list
[i
].func
!= NULL
) {
898 bzero(&args
, sizeof args
);
899 about_list
[i
].func(t
, &args
);
900 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
),
904 show_oops(t
, "invalid about page");
908 set_status(t
, "Loading: %s", (char *)uri
);
910 webkit_web_view_load_uri(t
->wv
, uri
);
917 get_uri(struct tab
*t
)
919 const gchar
*uri
= NULL
;
921 if (webkit_web_view_get_load_status(t
->wv
) == WEBKIT_LOAD_FAILED
&&
922 !t
->download_requested
)
924 if (t
->xtp_meaning
== XT_XTP_TAB_MEANING_NORMAL
)
925 uri
= webkit_web_view_get_uri(t
->wv
);
927 /* use tmp_uri to make sure it is g_freed */
930 t
->tmp_uri
= g_strdup_printf("%s%s", XT_URI_ABOUT
,
931 about_list
[t
->xtp_meaning
].name
);
938 get_title(struct tab
*t
, bool window
)
940 const gchar
*set
= NULL
, *title
= NULL
;
941 WebKitLoadStatus status
= webkit_web_view_get_load_status(t
->wv
);
943 if (status
== WEBKIT_LOAD_PROVISIONAL
||
944 (status
== WEBKIT_LOAD_FAILED
&& !t
->download_requested
) ||
945 t
->xtp_meaning
== XT_XTP_TAB_MEANING_BL
)
948 title
= webkit_web_view_get_title(t
->wv
);
949 if ((set
= title
? title
: get_uri(t
)))
953 set
= window
? XT_NAME
: "(untitled)";
959 find_mime_type(char *mime_type
)
961 struct mime_type
*m
, *def
= NULL
, *rv
= NULL
;
963 TAILQ_FOREACH(m
, &mtl
, entry
) {
965 !strncmp(mime_type
, m
->mt_type
, strlen(m
->mt_type
)))
968 if (m
->mt_default
== 0 && !strcmp(mime_type
, m
->mt_type
)) {
981 * This only escapes the & and < characters, as per the discussion found here:
982 * http://lists.apple.com/archives/Webkitsdk-dev/2007/May/msg00056.html
985 html_escape(const char *val
)
993 sv
= g_strsplit(val
, "&", -1);
994 s
= g_strjoinv("&", sv
);
997 sv
= g_strsplit(val
, "<", -1);
998 s
= g_strjoinv("<", sv
);
1005 wl_find_uri(const gchar
*s
, struct wl_list
*wl
)
1011 if (s
== NULL
|| wl
== NULL
)
1014 if (!strncmp(s
, "http://", strlen("http://")))
1015 s
= &s
[strlen("http://")];
1016 else if (!strncmp(s
, "https://", strlen("https://")))
1017 s
= &s
[strlen("https://")];
1022 for (i
= 0; i
< strlen(s
) + 1 /* yes er need this */; i
++)
1023 /* chop string at first slash */
1024 if (s
[i
] == '/' || s
[i
] == ':' || s
[i
] == '\0') {
1027 w
= wl_find(ss
, wl
);
1036 js_ref_to_string(JSContextRef context
, JSValueRef ref
)
1042 jsref
= JSValueToStringCopy(context
, ref
, NULL
);
1046 l
= JSStringGetMaximumUTF8CStringSize(jsref
);
1049 JSStringGetUTF8CString(jsref
, s
, l
);
1050 JSStringRelease(jsref
);
1055 #define XT_JS_DONE ("done;")
1056 #define XT_JS_DONE_LEN (strlen(XT_JS_DONE))
1057 #define XT_JS_INSERT ("insert;")
1058 #define XT_JS_INSERT_LEN (strlen(XT_JS_INSERT))
1061 run_script(struct tab
*t
, char *s
)
1063 JSGlobalContextRef ctx
;
1064 WebKitWebFrame
*frame
;
1066 JSValueRef val
, exception
;
1069 DNPRINTF(XT_D_JS
, "%s: tab %d %s\n", __func__
,
1070 t
->tab_id
, s
== (char *)JS_HINTING
? "JS_HINTING" : s
);
1072 frame
= webkit_web_view_get_main_frame(t
->wv
);
1073 ctx
= webkit_web_frame_get_global_context(frame
);
1075 str
= JSStringCreateWithUTF8CString(s
);
1076 val
= JSEvaluateScript(ctx
, str
, JSContextGetGlobalObject(ctx
),
1077 NULL
, 0, &exception
);
1078 JSStringRelease(str
);
1080 DNPRINTF(XT_D_JS
, "%s: val %p\n", __func__
, val
);
1082 es
= js_ref_to_string(ctx
, exception
);
1084 DNPRINTF(XT_D_JS
, "%s: exception %s\n", __func__
, es
);
1089 es
= js_ref_to_string(ctx
, val
);
1092 if (!strncmp(es
, XT_JS_DONE
, XT_JS_DONE_LEN
))
1094 if (!strncmp(es
, XT_JS_INSERT
, XT_JS_INSERT_LEN
))
1098 DNPRINTF(XT_D_JS
, "%s: val %s\n", __func__
, es
);
1107 run_script_locked(struct tab
*t
, char *s
)
1111 gdk_threads_enter();
1113 rv
= run_script(t
, s
);
1116 gdk_threads_leave();
1122 enable_hints(struct tab
*t
)
1124 DNPRINTF(XT_D_JS
, "%s: tab %d\n", __func__
, t
->tab_id
);
1127 run_script(t
, "hints.createHints('', 'F');");
1129 run_script(t
, "hints.createHints('', 'f');");
1130 t
->mode
= XT_MODE_HINT
;
1134 disable_hints(struct tab
*t
)
1136 DNPRINTF(XT_D_JS
, "%s: tab %d\n", __func__
, t
->tab_id
);
1138 run_script(t
, "hints.clearHints();");
1139 t
->mode
= XT_MODE_COMMAND
;
1144 passthrough(struct tab
*t
, struct karg
*args
)
1146 t
->mode
= XT_MODE_PASSTHROUGH
;
1151 modurl(struct tab
*t
, struct karg
*args
)
1153 const gchar
*uri
= NULL
;
1156 /* XXX kind of a bad hack, but oh well */
1157 if (gtk_widget_has_focus(t
->uri_entry
)) {
1158 if ((uri
= gtk_entry_get_text(GTK_ENTRY(t
->uri_entry
))) &&
1160 u
= g_strdup_printf("www.%s.com", uri
);
1161 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), u
);
1163 activate_uri_entry_cb(t
->uri_entry
, t
);
1170 hint(struct tab
*t
, struct karg
*args
)
1173 DNPRINTF(XT_D_JS
, "hint: tab %d args %d\n", t
->tab_id
, args
->i
);
1175 if (t
->mode
== XT_MODE_HINT
) {
1176 if (args
->i
== XT_HINT_NEWTAB
)
1186 apply_style(struct tab
*t
)
1189 g_object_set(G_OBJECT(t
->settings
),
1190 "user-stylesheet-uri", t
->stylesheet
, (char *)NULL
);
1194 remove_style(struct tab
*t
)
1197 g_object_set(G_OBJECT(t
->settings
),
1198 "user-stylesheet-uri", NULL
, (char *)NULL
);
1202 userstyle_cmd(struct tab
*t
, struct karg
*args
)
1204 char script
[PATH_MAX
] = {'\0'};
1208 DNPRINTF(XT_D_JS
, "userstyle_cmd: tab %d\n", t
->tab_id
);
1210 if (args
->s
!= NULL
&& strlen(args
->s
)) {
1211 expand_tilde(script
, sizeof script
, args
->s
);
1212 script_uri
= g_filename_to_uri(script
, NULL
, NULL
);
1214 script_uri
= g_strdup(userstyle
);
1216 if (script_uri
== NULL
)
1220 case XT_STYLE_CURRENT_TAB
:
1221 if (t
->styled
&& !strcmp(script_uri
, t
->stylesheet
))
1225 g_free(t
->stylesheet
);
1226 t
->stylesheet
= g_strdup(script_uri
);
1230 case XT_STYLE_GLOBAL
:
1231 if (userstyle_global
&& !strcmp(script_uri
, t
->stylesheet
)) {
1232 userstyle_global
= 0;
1233 TAILQ_FOREACH(tt
, &tabs
, entry
)
1236 userstyle_global
= 1;
1238 /* need to save this stylesheet for new tabs */
1241 stylesheet
= g_strdup(script_uri
);
1243 TAILQ_FOREACH(tt
, &tabs
, entry
) {
1245 g_free(tt
->stylesheet
);
1246 tt
->stylesheet
= g_strdup(script_uri
);
1259 quit(struct tab
*t
, struct karg
*args
)
1261 if (save_global_history
)
1262 save_global_history_to_disk(t
);
1270 restore_sessions_list(void)
1273 struct dirent
*dp
= NULL
;
1277 sdir
= opendir(sessions_dir
);
1279 while ((dp
= readdir(sdir
)) != NULL
) {
1280 #if defined __MINGW32__
1281 reg
= 1; /* windows only has regular files */
1283 reg
= dp
->d_type
== DT_REG
;
1286 s
= g_malloc(sizeof(struct session
));
1287 s
->name
= g_strdup(dp
->d_name
);
1288 TAILQ_INSERT_TAIL(&sessions
, s
, entry
);
1296 open_tabs(struct tab
*t
, struct karg
*a
)
1298 char file
[PATH_MAX
];
1302 struct tab
*ti
, *tt
;
1307 ti
= TAILQ_LAST(&tabs
, tab_list
);
1309 snprintf(file
, sizeof file
, "%s" PS
"%s", sessions_dir
, a
->s
);
1310 if ((f
= fopen(file
, "r")) == NULL
)
1314 if ((uri
= fparseln(f
, NULL
, NULL
, "\0\0\0", 0)) == NULL
)
1315 if (feof(f
) || ferror(f
))
1318 /* retrieve session name */
1319 if (uri
&& g_str_has_prefix(uri
, XT_SAVE_SESSION_ID
)) {
1320 strlcpy(named_session
,
1321 &uri
[strlen(XT_SAVE_SESSION_ID
)],
1322 sizeof named_session
);
1326 if (uri
&& strlen(uri
))
1327 create_new_tab(uri
, NULL
, 1, -1);
1333 /* close open tabs */
1334 if (a
->i
== XT_SES_CLOSETABS
&& ti
!= NULL
) {
1336 tt
= TAILQ_FIRST(&tabs
);
1357 restore_saved_tabs(void)
1359 char file
[PATH_MAX
];
1360 int unlink_file
= 0;
1365 snprintf(file
, sizeof file
, "%s" PS
"%s",
1366 sessions_dir
, XT_RESTART_TABS_FILE
);
1367 if (stat(file
, &sb
) == -1)
1368 a
.s
= XT_SAVED_TABS_FILE
;
1371 a
.s
= XT_RESTART_TABS_FILE
;
1374 a
.i
= XT_SES_DONOTHING
;
1375 rv
= open_tabs(NULL
, &a
);
1384 save_tabs(struct tab
*t
, struct karg
*a
)
1386 char file
[PATH_MAX
];
1388 int num_tabs
= 0, i
;
1389 struct tab
**stabs
= NULL
;
1391 /* tab may be null here */
1396 snprintf(file
, sizeof file
, "%s" PS
"%s",
1397 sessions_dir
, named_session
);
1399 snprintf(file
, sizeof file
, "%s" PS
"%s", sessions_dir
, a
->s
);
1401 if ((f
= fopen(file
, "w")) == NULL
) {
1402 show_oops(t
, "Can't open save_tabs file: %s", strerror(errno
));
1406 /* save session name */
1407 fprintf(f
, "%s%s\n", XT_SAVE_SESSION_ID
, named_session
);
1409 /* Save tabs, in the order they are arranged in the notebook. */
1410 num_tabs
= sort_tabs_by_page_num(&stabs
);
1412 for (i
= 0; i
< num_tabs
; i
++)
1414 if (get_uri(stabs
[i
]) != NULL
)
1415 fprintf(f
, "%s\n", get_uri(stabs
[i
]));
1416 else if (gtk_entry_get_text(GTK_ENTRY(
1417 stabs
[i
]->uri_entry
)))
1418 fprintf(f
, "%s\n", gtk_entry_get_text(GTK_ENTRY(
1419 stabs
[i
]->uri_entry
)));
1424 /* try and make sure this gets to disk NOW. XXX Backup first? */
1425 if (fflush(f
) != 0 || fsync(fileno(f
)) != 0) {
1426 show_oops(t
, "May not have managed to save session: %s",
1436 save_tabs_and_quit(struct tab
*t
, struct karg
*args
)
1448 expand_tilde(char *path
, size_t len
, const char *s
)
1452 char user
[LOGIN_NAME_MAX
];
1455 if (path
== NULL
|| s
== NULL
)
1456 errx(1, "expand_tilde");
1459 strlcpy(path
, sc
, len
);
1464 for (i
= 0; s
[i
] != PSC
&& s
[i
] != '\0'; ++i
)
1469 pwd
= strlen(user
) == 0 ? getpwuid(getuid()) : getpwnam(user
);
1471 strlcpy(path
, sc
, len
);
1473 snprintf(path
, len
, "%s%s", pwd
->pw_dir
, s
);
1477 run_page_script(struct tab
*t
, struct karg
*args
)
1480 char *tmp
, script
[PATH_MAX
];
1483 tmp
= args
->s
!= NULL
&& strlen(args
->s
) > 0 ? args
->s
: default_script
;
1484 if (tmp
[0] == '\0') {
1485 show_oops(t
, "no script specified");
1489 if ((uri
= get_uri(t
)) == NULL
) {
1490 show_oops(t
, "tab is empty, not running script");
1494 expand_tilde(script
, sizeof script
, tmp
);
1497 sv
[1] = (char *)uri
;
1499 if (!g_spawn_async(NULL
, sv
, NULL
, G_SPAWN_SEARCH_PATH
, NULL
, NULL
,
1501 show_oops(t
, "%s: could not spawn process: %s %s", __func__
,
1505 show_oops(t
, "running: %s %s", sv
[0], sv
[1]);
1511 yank_uri(struct tab
*t
, struct karg
*args
)
1514 GtkClipboard
*clipboard
, *primary
;
1516 if ((uri
= get_uri(t
)) == NULL
)
1519 primary
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
1520 clipboard
= gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
);
1521 gtk_clipboard_set_text(primary
, uri
, -1);
1522 gtk_clipboard_set_text(clipboard
, uri
, -1);
1528 paste_uri(struct tab
*t
, struct karg
*args
)
1530 GtkClipboard
*clipboard
, *primary
;
1531 gchar
*c
= NULL
, *p
= NULL
, *uri
;
1534 clipboard
= gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
);
1535 primary
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
1536 c
= gtk_clipboard_wait_for_text(clipboard
);
1537 p
= gtk_clipboard_wait_for_text(primary
);
1541 /* Windows try clipboard first */
1544 /* UNIX try primary first */
1547 /* replace all newlines with spaces */
1548 for (i
= 0; uri
[i
] != '\0'; ++i
)
1552 while (*uri
&& isspace(*uri
))
1554 if (strlen(uri
) == 0) {
1555 show_oops(t
, "empty paste buffer");
1558 if (guess_search
== 0 && valid_url_type(uri
)) {
1559 /* we can be clever and paste this in search box */
1560 show_oops(t
, "not a valid URL");
1564 if (args
->i
== XT_PASTE_CURRENT_TAB
)
1566 else if (args
->i
== XT_PASTE_NEW_TAB
)
1567 create_new_tab(uri
, NULL
, 1, -1);
1580 js_toggle_cb(GtkWidget
*w
, struct tab
*t
)
1585 g_object_get(G_OBJECT(t
->settings
),
1586 "enable-scripts", &es
, (char *)NULL
);
1591 set
= XT_WL_DISABLE
;
1593 a
.i
= set
| XT_WL_TOPLEVEL
;
1596 a
.i
= set
| XT_WL_TOPLEVEL
;
1599 a
.i
= XT_WL_TOGGLE
| XT_WL_TOPLEVEL
| XT_WL_RELOAD
;
1604 proxy_toggle_cb(GtkWidget
*w
, struct tab
*t
)
1606 struct karg args
= {0};
1608 args
.i
= XT_PRXY_TOGGLE
;
1609 proxy_cmd(t
, &args
);
1613 toggle_src(struct tab
*t
, struct karg
*args
)
1620 mode
= webkit_web_view_get_view_source_mode(t
->wv
);
1621 webkit_web_view_set_view_source_mode(t
->wv
, !mode
);
1622 webkit_web_view_reload(t
->wv
);
1628 focus_webview(struct tab
*t
)
1633 /* only grab focus if we are visible */
1634 if (gtk_notebook_get_current_page(notebook
) == t
->tab_id
)
1635 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
1639 focus(struct tab
*t
, struct karg
*args
)
1641 if (t
== NULL
|| args
== NULL
)
1647 if (args
->i
== XT_FOCUS_URI
)
1648 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
1649 else if (args
->i
== XT_FOCUS_SEARCH
)
1650 gtk_widget_grab_focus(GTK_WIDGET(t
->search_entry
));
1656 connect_socket_from_uri(const gchar
*uri
, const gchar
**error_str
, char *domain
,
1660 struct addrinfo hints
, *res
= NULL
, *ai
;
1661 int rv
= -1, s
= -1, on
, error
;
1663 static gchar myerror
[256]; /* this is not thread safe */
1666 *error_str
= myerror
;
1667 if (uri
&& !g_str_has_prefix(uri
, "https://")) {
1668 *error_str
= "invalid URI";
1672 su
= soup_uri_new(uri
);
1674 *error_str
= "invalid soup URI";
1677 if (!SOUP_URI_VALID_FOR_HTTP(su
)) {
1678 *error_str
= "invalid HTTPS URI";
1682 snprintf(port
, sizeof port
, "%d", su
->port
);
1683 bzero(&hints
, sizeof(struct addrinfo
));
1684 hints
.ai_flags
= AI_CANONNAME
;
1685 hints
.ai_family
= AF_UNSPEC
;
1686 hints
.ai_socktype
= SOCK_STREAM
;
1688 if ((error
= getaddrinfo(su
->host
, port
, &hints
, &res
))) {
1689 snprintf(myerror
, sizeof myerror
, "getaddrinfo failed: %s",
1690 gai_strerror(errno
));
1694 for (ai
= res
; ai
; ai
= ai
->ai_next
) {
1700 if (ai
->ai_family
!= AF_INET
&& ai
->ai_family
!= AF_INET6
)
1702 s
= socket(ai
->ai_family
, ai
->ai_socktype
, ai
->ai_protocol
);
1705 if (setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, &on
,
1708 if (connect(s
, ai
->ai_addr
, ai
->ai_addrlen
) == 0)
1712 snprintf(myerror
, sizeof myerror
,
1713 "could not obtain certificates from: %s",
1719 strlcpy(domain
, su
->host
, domain_sz
);
1726 if (rv
== -1 && s
!= -1)
1734 custom_gnutls_push(void *s
, const void *buf
, size_t len
)
1736 return send((size_t)s
, buf
, len
, 0);
1740 custom_gnutls_pull(void *s
, void *buf
, size_t len
)
1742 return recv((size_t)s
, buf
, len
, 0);
1747 stop_tls(gnutls_session_t gsession
, gnutls_certificate_credentials_t xcred
)
1750 gnutls_deinit(gsession
);
1752 gnutls_certificate_free_credentials(xcred
);
1758 start_tls(const gchar
**error_str
, int s
, gnutls_session_t
*gs
,
1759 gnutls_certificate_credentials_t
*xc
)
1761 gnutls_certificate_credentials_t xcred
;
1762 gnutls_session_t gsession
;
1764 static gchar myerror
[1024]; /* this is not thread safe */
1766 if (gs
== NULL
|| xc
== NULL
)
1773 gnutls_certificate_allocate_credentials(&xcred
);
1774 gnutls_certificate_set_x509_trust_file(xcred
, ssl_ca_file
,
1775 GNUTLS_X509_FMT_PEM
);
1777 gnutls_init(&gsession
, GNUTLS_CLIENT
);
1778 gnutls_priority_set_direct(gsession
, "PERFORMANCE", NULL
);
1779 gnutls_credentials_set(gsession
, GNUTLS_CRD_CERTIFICATE
, xcred
);
1780 gnutls_transport_set_ptr(gsession
, (gnutls_transport_ptr_t
)(long)s
);
1782 /* sockets on windows don't use file descriptors */
1783 gnutls_transport_set_push_function(gsession
, custom_gnutls_push
);
1784 gnutls_transport_set_pull_function(gsession
, custom_gnutls_pull
);
1786 if ((rv
= gnutls_handshake(gsession
)) < 0) {
1787 snprintf(myerror
, sizeof myerror
,
1788 "gnutls_handshake failed %d fatal %d %s",
1790 gnutls_error_is_fatal(rv
),
1791 #if LIBGNUTLS_VERSION_MAJOR >= 2 && LIBGNUTLS_VERSION_MINOR >= 6
1792 gnutls_strerror_name(rv
));
1794 "GNUTLS version is too old to provide human readable error");
1796 stop_tls(gsession
, xcred
);
1800 gnutls_credentials_type_t cred
;
1801 cred
= gnutls_auth_get_type(gsession
);
1802 if (cred
!= GNUTLS_CRD_CERTIFICATE
) {
1803 snprintf(myerror
, sizeof myerror
,
1804 "gnutls_auth_get_type failed %d",
1806 stop_tls(gsession
, xcred
);
1814 *error_str
= myerror
;
1819 get_connection_certs(gnutls_session_t gsession
, gnutls_x509_crt_t
**certs
,
1823 const gnutls_datum_t
*cl
;
1824 gnutls_x509_crt_t
*all_certs
;
1827 if (certs
== NULL
|| cert_count
== NULL
)
1829 if (gnutls_certificate_type_get(gsession
) != GNUTLS_CRT_X509
)
1831 cl
= gnutls_certificate_get_peers(gsession
, &len
);
1835 all_certs
= g_malloc(sizeof(gnutls_x509_crt_t
) * len
);
1836 for (i
= 0; i
< len
; i
++) {
1837 gnutls_x509_crt_init(&all_certs
[i
]);
1838 if (gnutls_x509_crt_import(all_certs
[i
], &cl
[i
],
1839 GNUTLS_X509_FMT_PEM
< 0)) {
1853 free_connection_certs(gnutls_x509_crt_t
*certs
, size_t cert_count
)
1857 for (i
= 0; i
< cert_count
; i
++)
1858 gnutls_x509_crt_deinit(certs
[i
]);
1862 #if GTK_CHECK_VERSION(3, 0, 0)
1864 statusbar_modify_attr(struct tab
*t
, const char *css_name
)
1866 gtk_widget_set_name(t
->sbe
.ebox
, css_name
);
1870 statusbar_modify_attr(struct tab
*t
, const char *text
, const char *base
)
1872 GdkColor c_text
, c_base
;
1874 gdk_color_parse(text
, &c_text
);
1875 gdk_color_parse(base
, &c_base
);
1877 gtk_widget_modify_bg(t
->sbe
.ebox
, GTK_STATE_NORMAL
, &c_base
);
1878 gtk_widget_modify_base(t
->sbe
.uri
, GTK_STATE_NORMAL
, &c_base
);
1879 gtk_widget_modify_text(t
->sbe
.uri
, GTK_STATE_NORMAL
, &c_text
);
1884 save_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
1885 size_t cert_count
, const char *domain
, const char *dir
)
1888 char cert_buf
[64 * 1024], file
[PATH_MAX
];
1892 if (t
== NULL
|| certs
== NULL
|| cert_count
<= 0 || domain
== NULL
)
1895 snprintf(file
, sizeof file
, "%s" PS
"%s", dir
, domain
);
1896 if ((f
= fopen(file
, "w")) == NULL
) {
1897 show_oops(t
, "Can't create cert file %s %s",
1898 file
, strerror(errno
));
1902 for (i
= 0; i
< cert_count
; i
++) {
1903 cert_buf_sz
= sizeof cert_buf
;
1904 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
1905 cert_buf
, &cert_buf_sz
)) {
1906 show_oops(t
, "gnutls_x509_crt_export failed");
1909 if (fwrite(cert_buf
, cert_buf_sz
, 1, f
) != 1) {
1910 show_oops(t
, "Can't write certs: %s", strerror(errno
));
1927 load_compare_cert(const gchar
*uri
, const gchar
**error_str
, const char *dir
)
1929 char domain
[8182], file
[PATH_MAX
];
1930 char cert_buf
[64 * 1024], r_cert_buf
[64 * 1024];
1932 unsigned int error
= 0;
1934 size_t cert_buf_sz
, cert_count
;
1935 enum cert_trust rv
= CERT_UNTRUSTED
;
1936 static gchar serr
[80]; /* this isn't thread safe */
1937 gnutls_session_t gsession
;
1938 gnutls_x509_crt_t
*certs
;
1939 gnutls_certificate_credentials_t xcred
;
1941 DNPRINTF(XT_D_URL
, "%s: %s\n", __func__
, uri
);
1945 if ((s
= connect_socket_from_uri(uri
, error_str
, domain
,
1946 sizeof domain
)) == -1)
1949 DNPRINTF(XT_D_URL
, "%s: fd %d\n", __func__
, s
);
1952 if (start_tls(error_str
, s
, &gsession
, &xcred
))
1954 DNPRINTF(XT_D_URL
, "%s: got tls\n", __func__
);
1956 /* verify certs in case cert file doesn't exist */
1957 if (gnutls_certificate_verify_peers2(gsession
, &error
) !=
1959 *error_str
= "Invalid certificates";
1964 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
1965 *error_str
= "Can't get connection certificates";
1969 snprintf(file
, sizeof file
, "%s" PS
"%s", dir
, domain
);
1970 if ((f
= fopen(file
, "r")) == NULL
) {
1976 for (i
= 0; i
< cert_count
; i
++) {
1977 cert_buf_sz
= sizeof cert_buf
;
1978 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
1979 cert_buf
, &cert_buf_sz
)) {
1982 if (fread(r_cert_buf
, cert_buf_sz
, 1, f
) != 1 && !feof(f
)) {
1983 rv
= CERT_BAD
; /* critical */
1986 if (bcmp(r_cert_buf
, cert_buf
, cert_buf_sz
)) {
1987 rv
= CERT_BAD
; /* critical */
1996 free_connection_certs(certs
, cert_count
);
1998 /* we close the socket first for speed */
2002 /* only complain if we didn't save it locally */
2003 if (strlen(ssl_ca_file
) != 0 && error
&& rv
!= CERT_LOCAL
) {
2004 strlcpy(serr
, "Certificate exception(s): ", sizeof serr
);
2005 if (error
& GNUTLS_CERT_INVALID
)
2006 strlcat(serr
, "invalid, ", sizeof serr
);
2007 if (error
& GNUTLS_CERT_REVOKED
)
2008 strlcat(serr
, "revoked, ", sizeof serr
);
2009 if (error
& GNUTLS_CERT_SIGNER_NOT_FOUND
)
2010 strlcat(serr
, "signer not found, ", sizeof serr
);
2011 if (error
& GNUTLS_CERT_SIGNER_NOT_CA
)
2012 strlcat(serr
, "not signed by CA, ", sizeof serr
);
2013 if (error
& GNUTLS_CERT_INSECURE_ALGORITHM
)
2014 strlcat(serr
, "insecure algorithm, ", sizeof serr
);
2015 #if LIBGNUTLS_VERSION_MAJOR >= 2 && LIBGNUTLS_VERSION_MINOR >= 6
2016 if (error
& GNUTLS_CERT_NOT_ACTIVATED
)
2017 strlcat(serr
, "not activated, ", sizeof serr
);
2018 if (error
& GNUTLS_CERT_EXPIRED
)
2019 strlcat(serr
, "expired, ", sizeof serr
);
2021 for (i
= strlen(serr
) - 1; i
> 0; i
--)
2022 if (serr
[i
] == ',') {
2029 stop_tls(gsession
, xcred
);
2035 get_local_cert_chain(const char *uri
, size_t *ncerts
, const char **error_str
,
2039 unsigned char cert_buf
[64 * 1024] = {0};
2040 gnutls_datum_t data
;
2041 unsigned int len
= UINT_MAX
;
2043 char file
[PATH_MAX
];
2045 gnutls_x509_crt_t
*certs
;
2047 if ((su
= soup_uri_new(uri
)) == NULL
) {
2048 *error_str
= "Invalid URI";
2052 snprintf(file
, sizeof file
, "%s" PS
"%s", dir
, su
->host
);
2053 if ((f
= fopen(file
, "r")) == NULL
) {
2054 *error_str
= "Could not read local cert";
2058 bytes_read
= fread(cert_buf
, sizeof *cert_buf
, sizeof cert_buf
, f
);
2059 if (bytes_read
== 0) {
2060 *error_str
= "Could not read local cert";
2064 data
.data
= cert_buf
;
2065 data
.size
= bytes_read
;
2066 certs
= g_malloc(sizeof *certs
);
2068 if (gnutls_x509_crt_list_import(certs
, &len
, &data
,
2069 GNUTLS_X509_FMT_PEM
, 0) < 0) {
2070 *error_str
= "Error reading local cert chain";
2080 cert_cmd(struct tab
*t
, struct karg
*args
)
2082 const gchar
*uri
, *error_str
= NULL
;
2086 gnutls_session_t gsession
;
2087 gnutls_x509_crt_t
*certs
;
2088 gnutls_certificate_credentials_t xcred
;
2089 #if !GTK_CHECK_VERSION(3, 0, 0)
2096 if (args
->s
!= NULL
)
2098 else if ((uri
= get_uri(t
)) == NULL
) {
2099 show_oops(t
, "Invalid URI");
2104 * if we're only showing the local certs, don't open a socket and get
2107 if (args
->i
& XT_SHOW
&& args
->i
& XT_CACHE
) {
2108 certs
= get_local_cert_chain(uri
, &cert_count
, &error_str
,
2110 if (error_str
== NULL
) {
2111 show_certs(t
, certs
, cert_count
, "Certificate Chain");
2112 free_connection_certs(certs
, cert_count
);
2114 show_oops(t
, "%s", error_str
);
2120 if ((s
= connect_socket_from_uri(uri
, &error_str
, domain
,
2121 sizeof domain
)) == -1) {
2122 show_oops(t
, "%s", error_str
);
2127 if (start_tls(&error_str
, s
, &gsession
, &xcred
))
2131 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
2132 show_oops(t
, "get_connection_certs failed");
2136 if (args
->i
& XT_SHOW
)
2137 show_certs(t
, certs
, cert_count
, "Certificate Chain");
2138 else if (args
->i
& XT_SAVE
) {
2139 save_certs(t
, certs
, cert_count
, domain
, certs_dir
);
2140 #if GTK_CHECK_VERSION(3, 0, 0)
2141 gtk_widget_set_name(t
->uri_entry
, XT_CSS_BLUE
);
2142 statusbar_modify_attr(t
, XT_CSS_BLUE
);
2144 gdk_color_parse(XT_COLOR_BLUE
, &color
);
2145 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
2146 statusbar_modify_attr(t
, XT_COLOR_BLACK
, XT_COLOR_BLUE
);
2148 } else if (args
->i
& XT_CACHE
)
2149 save_certs(t
, certs
, cert_count
, domain
, certs_cache_dir
);
2151 free_connection_certs(certs
, cert_count
);
2153 /* we close the socket first for speed */
2156 stop_tls(gsession
, xcred
);
2157 if (error_str
&& strlen(error_str
))
2158 show_oops(t
, "%s", error_str
);
2163 * args must be allocated dynamically as the thread that added this function
2164 * to the idle loop no longer exists
2167 warn_cert_cache_differs_idle(struct karg
*args
)
2170 show_oops(NULL
, "%s: invalid parameters", __func__
);
2171 /* return 0 to not re-add function to the idle loop */
2174 xtp_page_sv((struct tab
*)args
->ptr
, args
);
2180 check_cert_changes(struct tab
*t
, const char *uri
)
2182 SoupURI
*soupuri
= NULL
;
2183 struct karg args
= {0};
2184 struct wl_entry
*w
= NULL
;
2185 const char *errstr
= NULL
;
2188 if (!(warn_cert_changes
&& g_str_has_prefix(uri
, "https://")))
2191 switch (load_compare_cert(uri
, &errstr
, certs_cache_dir
)) {
2193 /* The cached certificate is identical */
2195 case CERT_TRUSTED
: /* FALLTHROUGH */
2196 case CERT_UNTRUSTED
:
2197 /* cache new certificate */
2202 if ((soupuri
= soup_uri_new(uri
)) == NULL
||
2203 soupuri
->host
== NULL
)
2205 if ((w
= wl_find(soupuri
->host
, &svil
)) != NULL
)
2207 t
->xtp_meaning
= XT_XTP_TAB_MEANING_SV
;
2208 argsp
= g_malloc0(sizeof(struct karg
));
2209 argsp
->s
= g_strdup((char *)uri
);
2210 argsp
->ptr
= (void *)t
;
2211 g_idle_add((GSourceFunc
)warn_cert_cache_differs_idle
, argsp
);
2216 soup_uri_free(soupuri
);
2221 remove_cookie(int index
)
2227 DNPRINTF(XT_D_COOKIE
, "remove_cookie: %d\n", index
);
2229 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
2231 for (i
= 1; cf
; cf
= cf
->next
, i
++) {
2235 print_cookie("remove cookie", c
);
2236 soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
2241 soup_cookies_free(cf
);
2247 remove_cookie_domain(int domain_id
)
2249 int domain_count
, rv
= 1;
2254 DNPRINTF(XT_D_COOKIE
, "remove_cookie_domain: %d\n", domain_id
);
2257 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
2259 for (domain_count
= 0; cf
; cf
= cf
->next
) {
2262 if (strcmp(last_domain
, c
->domain
) != 0) {
2264 last_domain
= c
->domain
;
2267 if (domain_count
< domain_id
)
2269 else if (domain_count
> domain_id
)
2272 print_cookie("remove cookie", c
);
2273 soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
2277 soup_cookies_free(cf
);
2289 DNPRINTF(XT_D_COOKIE
, "remove_cookie_all\n");
2291 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
2293 for (; cf
; cf
= cf
->next
) {
2296 print_cookie("remove cookie", c
);
2297 soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
2301 soup_cookies_free(cf
);
2307 toplevel_cmd(struct tab
*t
, struct karg
*args
)
2309 js_toggle_cb(t
->js_toggle
, t
);
2315 can_go_back_for_real(struct tab
*t
)
2318 WebKitWebHistoryItem
*item
;
2324 if (t
->item
!= NULL
)
2327 /* rely on webkit to make sure we can go backward when on an about page */
2329 if (uri
== NULL
|| g_str_has_prefix(uri
, "about:") ||
2330 g_str_has_prefix(uri
, "xxxt://"))
2331 return (webkit_web_view_can_go_back(t
->wv
));
2333 /* the back/forward list is stupid so help determine if we can go back */
2334 for (i
= 0, item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
2336 i
--, item
= webkit_web_back_forward_list_get_nth_item(t
->bfl
, i
)) {
2337 if (strcmp(webkit_web_history_item_get_uri(item
), uri
))
2345 can_go_forward_for_real(struct tab
*t
)
2348 WebKitWebHistoryItem
*item
;
2354 /* rely on webkit to make sure we can go forward when on an about page */
2356 if (uri
== NULL
|| g_str_has_prefix(uri
, "about:") ||
2357 g_str_has_prefix(uri
, "xxxt://"))
2358 return (webkit_web_view_can_go_forward(t
->wv
));
2360 /* the back/forwars list is stupid so help selecting a different item */
2361 for (i
= 0, item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
2363 i
++, item
= webkit_web_back_forward_list_get_nth_item(t
->bfl
, i
)) {
2364 if (strcmp(webkit_web_history_item_get_uri(item
), uri
))
2372 go_back_for_real(struct tab
*t
)
2375 WebKitWebHistoryItem
*item
;
2382 if (uri
== NULL
|| g_str_has_prefix(uri
, "about:") ||
2383 g_str_has_prefix(uri
, "xxxt://")) {
2384 webkit_web_view_go_back(t
->wv
);
2387 /* the back/forwars list is stupid so help selecting a different item */
2388 for (i
= 0, item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
2390 i
--, item
= webkit_web_back_forward_list_get_nth_item(t
->bfl
, i
)) {
2391 if (strcmp(webkit_web_history_item_get_uri(item
), uri
)) {
2392 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
2399 go_forward_for_real(struct tab
*t
)
2402 WebKitWebHistoryItem
*item
;
2409 if (uri
== NULL
|| g_str_has_prefix(uri
, "about:") ||
2410 g_str_has_prefix(uri
, "xxxt://")) {
2411 webkit_web_view_go_forward(t
->wv
);
2414 /* the back/forwars list is stupid so help selecting a different item */
2415 for (i
= 0, item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
2417 i
++, item
= webkit_web_back_forward_list_get_nth_item(t
->bfl
, i
)) {
2418 if (strcmp(webkit_web_history_item_get_uri(item
), uri
)) {
2419 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
2426 navaction(struct tab
*t
, struct karg
*args
)
2428 WebKitWebHistoryItem
*item
= NULL
;
2429 WebKitWebFrame
*frame
;
2431 DNPRINTF(XT_D_NAV
, "navaction: tab %d opcode %d\n",
2432 t
->tab_id
, args
->i
);
2435 set_normal_tab_meaning(t
);
2437 if (args
->i
== XT_NAV_BACK
)
2438 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
2440 item
= webkit_web_back_forward_list_get_forward_item(t
->bfl
);
2447 return (XT_CB_PASSTHROUGH
);
2448 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
2450 return (XT_CB_PASSTHROUGH
);
2453 go_back_for_real(t
);
2455 case XT_NAV_FORWARD
:
2458 return (XT_CB_PASSTHROUGH
);
2459 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
2461 return (XT_CB_PASSTHROUGH
);
2464 go_forward_for_real(t
);
2467 frame
= webkit_web_view_get_main_frame(t
->wv
);
2468 webkit_web_frame_reload(frame
);
2471 frame
= webkit_web_view_get_main_frame(t
->wv
);
2472 webkit_web_frame_stop_loading(frame
);
2475 return (XT_CB_PASSTHROUGH
);
2479 move(struct tab
*t
, struct karg
*args
)
2481 GtkAdjustment
*adjust
;
2482 double pi
, si
, pos
, ps
, upper
, lower
, max
;
2488 case XT_MOVE_BOTTOM
:
2490 case XT_MOVE_PAGEDOWN
:
2491 case XT_MOVE_PAGEUP
:
2492 case XT_MOVE_HALFDOWN
:
2493 case XT_MOVE_HALFUP
:
2494 case XT_MOVE_PERCENT
:
2495 case XT_MOVE_CENTER
:
2496 adjust
= t
->adjust_v
;
2499 adjust
= t
->adjust_h
;
2503 pos
= gtk_adjustment_get_value(adjust
);
2504 ps
= gtk_adjustment_get_page_size(adjust
);
2505 upper
= gtk_adjustment_get_upper(adjust
);
2506 lower
= gtk_adjustment_get_lower(adjust
);
2507 si
= gtk_adjustment_get_step_increment(adjust
);
2508 pi
= gtk_adjustment_get_page_increment(adjust
);
2511 DNPRINTF(XT_D_MOVE
, "move: opcode %d %s pos %f ps %f upper %f lower %f "
2512 "max %f si %f pi %f\n",
2513 args
->i
, adjust
== t
->adjust_h
? "horizontal" : "vertical",
2514 pos
, ps
, upper
, lower
, max
, si
, pi
);
2520 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
2525 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
2527 case XT_MOVE_BOTTOM
:
2528 case XT_MOVE_FARRIGHT
:
2529 t
->mark
[marktoindex('\'')] = gtk_adjustment_get_value(t
->adjust_v
);
2530 gtk_adjustment_set_value(adjust
, max
);
2533 case XT_MOVE_FARLEFT
:
2534 t
->mark
[marktoindex('\'')] = gtk_adjustment_get_value(t
->adjust_v
);
2535 gtk_adjustment_set_value(adjust
, lower
);
2537 case XT_MOVE_PAGEDOWN
:
2539 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
2541 case XT_MOVE_PAGEUP
:
2543 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
2545 case XT_MOVE_HALFDOWN
:
2547 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
2549 case XT_MOVE_HALFUP
:
2551 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
2553 case XT_MOVE_CENTER
:
2554 t
->mark
[marktoindex('\'')] = gtk_adjustment_get_value(t
->adjust_v
);
2555 args
->s
= g_strdup("50.0");
2557 case XT_MOVE_PERCENT
:
2558 t
->mark
[marktoindex('\'')] = gtk_adjustment_get_value(t
->adjust_v
);
2559 percent
= atoi(args
->s
) / 100.0;
2560 pos
= max
* percent
;
2561 if (pos
< 0.0 || pos
> max
)
2563 gtk_adjustment_set_value(adjust
, pos
);
2566 return (XT_CB_PASSTHROUGH
);
2569 DNPRINTF(XT_D_MOVE
, "move: new pos %f %f\n", pos
, MIN(pos
, max
));
2571 return (XT_CB_HANDLED
);
2575 url_set_visibility(void)
2579 TAILQ_FOREACH(t
, &tabs
, entry
)
2580 if (show_url
== 0) {
2581 gtk_widget_hide(t
->toolbar
);
2584 gtk_widget_show(t
->toolbar
);
2588 notebook_tab_set_visibility(void)
2590 if (show_tabs
== 0) {
2591 gtk_widget_hide(tab_bar
);
2592 gtk_notebook_set_show_tabs(notebook
, FALSE
);
2594 if (tab_style
== XT_TABS_NORMAL
) {
2595 gtk_widget_hide(tab_bar
);
2596 gtk_notebook_set_show_tabs(notebook
, TRUE
);
2597 } else if (tab_style
== XT_TABS_COMPACT
) {
2598 gtk_widget_show(tab_bar
);
2599 gtk_notebook_set_show_tabs(notebook
, FALSE
);
2605 statusbar_set_visibility(void)
2609 TAILQ_FOREACH(t
, &tabs
, entry
){
2610 if (show_statusbar
== 0)
2611 gtk_widget_hide(t
->statusbar
);
2613 gtk_widget_show(t
->statusbar
);
2620 url_set(struct tab
*t
, int enable_url_entry
)
2625 show_url
= enable_url_entry
;
2627 if (enable_url_entry
) {
2628 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->sbe
.uri
),
2629 GTK_ENTRY_ICON_PRIMARY
, NULL
);
2630 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->sbe
.uri
), 0);
2632 pixbuf
= gtk_entry_get_icon_pixbuf(GTK_ENTRY(t
->uri_entry
),
2633 GTK_ENTRY_ICON_PRIMARY
);
2635 gtk_entry_get_progress_fraction(GTK_ENTRY(t
->uri_entry
));
2636 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->sbe
.uri
),
2637 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
2638 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->sbe
.uri
),
2644 fullscreen(struct tab
*t
, struct karg
*args
)
2646 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
2649 return (XT_CB_PASSTHROUGH
);
2651 if (show_url
== 0) {
2659 url_set_visibility();
2660 notebook_tab_set_visibility();
2662 return (XT_CB_HANDLED
);
2666 statustoggle(struct tab
*t
, struct karg
*args
)
2668 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
2670 if (show_statusbar
== 1) {
2672 statusbar_set_visibility();
2673 } else if (show_statusbar
== 0) {
2675 statusbar_set_visibility();
2677 return (XT_CB_HANDLED
);
2681 urlaction(struct tab
*t
, struct karg
*args
)
2683 int rv
= XT_CB_HANDLED
;
2685 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
2688 return (XT_CB_PASSTHROUGH
);
2692 if (show_url
== 0) {
2694 url_set_visibility();
2698 if (show_url
== 1) {
2700 url_set_visibility();
2708 tabaction(struct tab
*t
, struct karg
*args
)
2710 int rv
= XT_CB_HANDLED
;
2711 char *url
= args
->s
;
2713 struct tab
*tt
, *tv
;
2715 DNPRINTF(XT_D_TAB
, "tabaction: %p %d\n", t
, args
->i
);
2718 return (XT_CB_PASSTHROUGH
);
2722 if (strlen(url
) > 0)
2723 create_new_tab(url
, NULL
, 1, args
->precount
);
2725 create_new_tab(NULL
, NULL
, 1, args
->precount
);
2728 if (args
->precount
< 0)
2731 TAILQ_FOREACH(tt
, &tabs
, entry
)
2732 if (tt
->tab_id
== args
->precount
- 1) {
2737 case XT_TAB_DELQUIT
:
2738 if (gtk_notebook_get_n_pages(notebook
) > 1)
2744 TAILQ_FOREACH_SAFE(tt
, &tabs
, entry
, tv
)
2749 if (strlen(url
) > 0)
2752 rv
= XT_CB_PASSTHROUGH
;
2758 if (show_tabs
== 0) {
2760 notebook_tab_set_visibility();
2764 if (show_tabs
== 1) {
2766 notebook_tab_set_visibility();
2769 case XT_TAB_NEXTSTYLE
:
2770 if (tab_style
== XT_TABS_NORMAL
) {
2771 tab_style
= XT_TABS_COMPACT
;
2772 recolor_compact_tabs();
2775 tab_style
= XT_TABS_NORMAL
;
2776 notebook_tab_set_visibility();
2778 case XT_TAB_UNDO_CLOSE
:
2779 if (undo_count
== 0) {
2780 DNPRINTF(XT_D_TAB
, "%s: no tabs to undo close",
2785 u
= TAILQ_FIRST(&undos
);
2786 create_new_tab(u
->uri
, u
, 1, -1);
2788 TAILQ_REMOVE(&undos
, u
, entry
);
2790 /* u->history is freed in create_new_tab() */
2794 case XT_TAB_LOAD_IMAGES
:
2796 if (!auto_load_images
) {
2798 /* Enable auto-load images (this will load all
2799 * previously unloaded images). */
2800 g_object_set(G_OBJECT(t
->settings
),
2801 "auto-load-images", TRUE
, (char *)NULL
);
2802 webkit_web_view_set_settings(t
->wv
, t
->settings
);
2804 webkit_web_view_reload(t
->wv
);
2806 /* Webkit triggers an event when we change the setting,
2807 * so we can't disable the auto-loading at once.
2809 * Unfortunately, webkit does not tell us when it's done.
2810 * Instead, we wait until the next request, and then
2811 * disable autoloading again.
2813 t
->load_images
= TRUE
;
2817 rv
= XT_CB_PASSTHROUGH
;
2831 resizetab(struct tab
*t
, struct karg
*args
)
2833 if (t
== NULL
|| args
== NULL
) {
2834 show_oops(NULL
, "resizetab invalid parameters");
2835 return (XT_CB_PASSTHROUGH
);
2838 DNPRINTF(XT_D_TAB
, "resizetab: tab %d %d\n",
2839 t
->tab_id
, args
->i
);
2841 setzoom_webkit(t
, args
->i
);
2843 return (XT_CB_HANDLED
);
2847 movetab(struct tab
*t
, struct karg
*args
)
2851 if (t
== NULL
|| args
== NULL
) {
2852 show_oops(NULL
, "movetab invalid parameters");
2853 return (XT_CB_PASSTHROUGH
);
2856 DNPRINTF(XT_D_TAB
, "movetab: tab %d opcode %d\n",
2857 t
->tab_id
, args
->i
);
2859 if (args
->i
>= XT_TAB_INVALID
)
2860 return (XT_CB_PASSTHROUGH
);
2862 if (TAILQ_EMPTY(&tabs
))
2863 return (XT_CB_PASSTHROUGH
);
2865 n
= gtk_notebook_get_n_pages(notebook
);
2866 dest
= gtk_notebook_get_current_page(notebook
);
2870 if (args
->precount
< 0)
2871 dest
= dest
== n
- 1 ? 0 : dest
+ 1;
2873 dest
= args
->precount
- 1;
2877 if (args
->precount
< 0)
2880 dest
-= args
->precount
% n
;
2893 return (XT_CB_PASSTHROUGH
);
2896 if (dest
< 0 || dest
>= n
)
2897 return (XT_CB_PASSTHROUGH
);
2898 if (t
->tab_id
== dest
) {
2899 DNPRINTF(XT_D_TAB
, "movetab: do nothing\n");
2900 return (XT_CB_HANDLED
);
2903 set_current_tab(dest
);
2905 return (XT_CB_HANDLED
);
2912 const char *(*f
)(struct tab
*);
2914 { "<uri>", get_uri
},
2918 command(struct tab
*t
, struct karg
*args
)
2920 struct karg a
= {0};
2921 int i
, cmd_setup
= 0;
2922 char *s
= NULL
, *sp
= NULL
, *sl
= NULL
;
2925 if (t
== NULL
|| args
== NULL
) {
2926 show_oops(NULL
, "command invalid parameters");
2927 return (XT_CB_PASSTHROUGH
);
2938 if (cmd_prefix
== 0) {
2939 if (args
->s
!= NULL
&& strlen(args
->s
) != 0) {
2940 sp
= g_strdup_printf(":%s", args
->s
);
2945 sp
= g_strdup_printf(":%d", cmd_prefix
);
2955 for (i
= 0; i
< LENGTH(subs
); ++i
) {
2956 sv
= g_strsplit(sl
, subs
[i
].s
, -1);
2959 sl
= g_strjoinv(subs
[i
].f(t
), sv
);
2965 t
->mode
= XT_MODE_HINT
;
2969 * js code will auto fire() if a single link is visible,
2970 * causing the focus-out-event cb function to be called. Setup
2971 * the cmd _before_ triggering hinting code so the cmd can get
2972 * killed by the cb in this case.
2979 t
->mode
= XT_MODE_HINT
;
2980 a
.i
= XT_HINT_NEWTAB
;
2987 show_oops(t
, "command: invalid opcode %d", args
->i
);
2988 return (XT_CB_PASSTHROUGH
);
2991 DNPRINTF(XT_D_CMD
, "%s: tab %d type %s\n", __func__
, t
->tab_id
, s
);
3001 return (XT_CB_HANDLED
);
3005 search(struct tab
*t
, struct karg
*args
)
3009 if (t
== NULL
|| args
== NULL
) {
3010 show_oops(NULL
, "search invalid parameters");
3015 case XT_SEARCH_NEXT
:
3016 d
= t
->search_forward
;
3018 case XT_SEARCH_PREV
:
3019 d
= !t
->search_forward
;
3022 return (XT_CB_PASSTHROUGH
);
3025 if (t
->search_text
== NULL
) {
3026 if (global_search
== NULL
)
3027 return (XT_CB_PASSTHROUGH
);
3029 d
= t
->search_forward
= TRUE
;
3030 t
->search_text
= g_strdup(global_search
);
3031 webkit_web_view_mark_text_matches(t
->wv
, global_search
, FALSE
, 0);
3032 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
3036 DNPRINTF(XT_D_CMD
, "search: tab %d opc %d forw %d text %s\n",
3037 t
->tab_id
, args
->i
, t
->search_forward
, t
->search_text
);
3039 webkit_web_view_search_text(t
->wv
, t
->search_text
, FALSE
, d
, TRUE
);
3041 return (XT_CB_HANDLED
);
3045 session_save(struct tab
*t
, char *filename
)
3051 if (strlen(filename
) == 0)
3054 if (filename
[0] == '.' || filename
[0] == '/')
3058 if (save_tabs(t
, &a
))
3060 strlcpy(named_session
, filename
, sizeof named_session
);
3062 /* add the new session to the list of sessions */
3063 s
= g_malloc(sizeof(struct session
));
3064 s
->name
= g_strdup(filename
);
3065 TAILQ_INSERT_TAIL(&sessions
, s
, entry
);
3073 session_open(struct tab
*t
, char *filename
)
3078 if (strlen(filename
) == 0)
3081 if (filename
[0] == '.' || filename
[0] == '/')
3085 a
.i
= XT_SES_CLOSETABS
;
3086 if (open_tabs(t
, &a
))
3089 strlcpy(named_session
, filename
, sizeof named_session
);
3097 session_delete(struct tab
*t
, char *filename
)
3099 char file
[PATH_MAX
];
3103 if (strlen(filename
) == 0)
3106 if (filename
[0] == '.' || filename
[0] == '/')
3109 snprintf(file
, sizeof file
, "%s" PS
"%s", sessions_dir
, filename
);
3113 if (!strcmp(filename
, named_session
))
3114 strlcpy(named_session
, XT_SAVED_TABS_FILE
,
3115 sizeof named_session
);
3117 /* remove session from sessions list */
3118 TAILQ_FOREACH(s
, &sessions
, entry
) {
3119 if (!strcmp(s
->name
, filename
))
3124 TAILQ_REMOVE(&sessions
, s
, entry
);
3125 g_free((gpointer
) s
->name
);
3134 session_cmd(struct tab
*t
, struct karg
*args
)
3136 char *filename
= args
->s
;
3141 if (args
->i
& XT_SHOW
)
3142 show_oops(t
, "Current session: %s", named_session
[0] == '\0' ?
3143 XT_SAVED_TABS_FILE
: named_session
);
3144 else if (args
->i
& XT_SAVE
) {
3145 if (session_save(t
, filename
)) {
3146 show_oops(t
, "Can't save session: %s",
3147 filename
? filename
: "INVALID");
3150 } else if (args
->i
& XT_OPEN
) {
3151 if (session_open(t
, filename
)) {
3152 show_oops(t
, "Can't open session: %s",
3153 filename
? filename
: "INVALID");
3156 } else if (args
->i
& XT_DELETE
) {
3157 if (session_delete(t
, filename
)) {
3158 show_oops(t
, "Can't delete session: %s",
3159 filename
? filename
: "INVALID");
3164 return (XT_CB_PASSTHROUGH
);
3168 script_cmd(struct tab
*t
, struct karg
*args
)
3177 if ((f
= fopen(args
->s
, "r")) == NULL
) {
3178 show_oops(t
, "Can't open script file: %s", args
->s
);
3182 if (fstat(fileno(f
), &sb
) == -1) {
3183 show_oops(t
, "Can't stat script file: %s", args
->s
);
3187 buf
= g_malloc0(sb
.st_size
+ 1);
3188 if (fread(buf
, 1, sb
.st_size
, f
) != sb
.st_size
) {
3189 show_oops(t
, "Can't read script file: %s", args
->s
);
3193 DNPRINTF(XT_D_JS
, "%s: about to run script\n", __func__
);
3202 return (XT_CB_PASSTHROUGH
);
3206 * Make a hardcopy of the page
3209 print_page(struct tab
*t
, struct karg
*args
)
3211 WebKitWebFrame
*frame
;
3213 GtkPrintOperation
*op
;
3214 GtkPrintOperationAction action
;
3215 GtkPrintOperationResult print_res
;
3216 GError
*g_err
= NULL
;
3217 int marg_l
, marg_r
, marg_t
, marg_b
;
3219 DNPRINTF(XT_D_PRINTING
, "%s:", __func__
);
3221 ps
= gtk_page_setup_new();
3222 op
= gtk_print_operation_new();
3223 action
= GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG
;
3224 frame
= webkit_web_view_get_main_frame(t
->wv
);
3226 /* the default margins are too small, so we will bump them */
3227 marg_l
= gtk_page_setup_get_left_margin(ps
, GTK_UNIT_MM
) +
3228 XT_PRINT_EXTRA_MARGIN
;
3229 marg_r
= gtk_page_setup_get_right_margin(ps
, GTK_UNIT_MM
) +
3230 XT_PRINT_EXTRA_MARGIN
;
3231 marg_t
= gtk_page_setup_get_top_margin(ps
, GTK_UNIT_MM
) +
3232 XT_PRINT_EXTRA_MARGIN
;
3233 marg_b
= gtk_page_setup_get_bottom_margin(ps
, GTK_UNIT_MM
) +
3234 XT_PRINT_EXTRA_MARGIN
;
3237 gtk_page_setup_set_left_margin(ps
, marg_l
, GTK_UNIT_MM
);
3238 gtk_page_setup_set_right_margin(ps
, marg_r
, GTK_UNIT_MM
);
3239 gtk_page_setup_set_top_margin(ps
, marg_t
, GTK_UNIT_MM
);
3240 gtk_page_setup_set_bottom_margin(ps
, marg_b
, GTK_UNIT_MM
);
3242 gtk_print_operation_set_default_page_setup(op
, ps
);
3244 /* this appears to free 'op' and 'ps' */
3245 print_res
= webkit_web_frame_print_full(frame
, op
, action
, &g_err
);
3247 /* check it worked */
3248 if (print_res
== GTK_PRINT_OPERATION_RESULT_ERROR
) {
3249 show_oops(NULL
, "can't print: %s", g_err
->message
);
3250 g_error_free (g_err
);
3258 go_home(struct tab
*t
, struct karg
*args
)
3265 set_encoding(struct tab
*t
, struct karg
*args
)
3269 if (args
->s
&& strlen(g_strstrip(args
->s
)) == 0) {
3270 e
= webkit_web_view_get_custom_encoding(t
->wv
);
3272 e
= webkit_web_view_get_encoding(t
->wv
);
3273 show_oops(t
, "encoding: %s", e
? e
: "N/A");
3275 webkit_web_view_set_custom_encoding(t
->wv
, args
->s
);
3281 restart(struct tab
*t
, struct karg
*args
)
3285 a
.s
= XT_RESTART_TABS_FILE
;
3287 execvp(start_argv
[0], start_argv
);
3293 char *http_proxy_save
; /* not a setting, used to toggle */
3296 proxy_cmd(struct tab
*t
, struct karg
*args
)
3300 DNPRINTF(XT_D_CMD
, "%s: tab %d\n", __func__
, t
->tab_id
);
3307 TAILQ_FOREACH(tt
, &tabs
, entry
)
3308 gtk_widget_show(t
->proxy_toggle
);
3309 if (http_proxy_save
)
3310 g_free(http_proxy_save
);
3311 http_proxy_save
= g_strdup(http_proxy
);
3314 if (args
->i
& XT_PRXY_SHOW
) {
3316 show_oops(t
, "http_proxy = %s", http_proxy
);
3318 show_oops(t
, "proxy is currently disabled");
3319 } else if (args
->i
& XT_PRXY_TOGGLE
) {
3320 if (http_proxy_save
== NULL
&& http_proxy
== NULL
) {
3321 show_oops(t
, "can't toggle proxy");
3324 TAILQ_FOREACH(tt
, &tabs
, entry
)
3325 gtk_widget_show(t
->proxy_toggle
);
3328 button_set_file(t
->proxy_toggle
, "tordisabled.ico");
3329 show_oops(t
, "http proxy disabled");
3331 setup_proxy(http_proxy_save
);
3332 button_set_file(t
->proxy_toggle
, "torenabled.ico");
3333 show_oops(t
, "http_proxy = %s", http_proxy
);
3337 return (XT_CB_PASSTHROUGH
);
3342 int (*func
)(struct tab
*, struct karg
*);
3346 { "command_mode", 0, command_mode
, XT_MODE_COMMAND
, 0 },
3347 { "insert_mode", 0, command_mode
, XT_MODE_INSERT
, 0 },
3348 { "command", 0, command
, ':', 0 },
3349 { "search", 0, command
, '/', 0 },
3350 { "searchb", 0, command
, '?', 0 },
3351 { "hinting", 0, command
, '.', 0 },
3352 { "hinting_newtab", 0, command
, ',', 0 },
3353 { "togglesrc", 0, toggle_src
, 0, 0 },
3354 { "editsrc", 0, edit_src
, 0, 0 },
3355 { "editelement", 0, edit_element
, 0, 0 },
3356 { "passthrough", 0, passthrough
, 0, 0 },
3357 { "modurl", 0, modurl
, 0, 0 },
3359 /* yanking and pasting */
3360 { "yankuri", 0, yank_uri
, 0, 0 },
3361 { "pasteuricur", 0, paste_uri
, XT_PASTE_CURRENT_TAB
, 0 },
3362 { "pasteurinew", 0, paste_uri
, XT_PASTE_NEW_TAB
, 0 },
3365 { "searchnext", 0, search
, XT_SEARCH_NEXT
, 0 },
3366 { "searchprevious", 0, search
, XT_SEARCH_PREV
, 0 },
3369 { "focusaddress", 0, focus
, XT_FOCUS_URI
, 0 },
3370 { "focussearch", 0, focus
, XT_FOCUS_SEARCH
, 0 },
3373 { "hinting", 0, hint
, 0, 0 },
3374 { "hinting_newtab", 0, hint
, XT_HINT_NEWTAB
, 0 },
3376 /* custom stylesheet */
3377 { "userstyle", 0, userstyle_cmd
, XT_STYLE_CURRENT_TAB
, XT_USERARG
},
3378 { "userstyle_global", 0, userstyle_cmd
, XT_STYLE_GLOBAL
, XT_USERARG
},
3381 { "goback", 0, navaction
, XT_NAV_BACK
, 0 },
3382 { "goforward", 0, navaction
, XT_NAV_FORWARD
, 0 },
3383 { "reload", 0, navaction
, XT_NAV_RELOAD
, 0 },
3384 { "stop", 0, navaction
, XT_NAV_STOP
, 0 },
3386 /* vertical movement */
3387 { "scrolldown", 0, move
, XT_MOVE_DOWN
, 0 },
3388 { "scrollup", 0, move
, XT_MOVE_UP
, 0 },
3389 { "scrollbottom", 0, move
, XT_MOVE_BOTTOM
, 0 },
3390 { "scrolltop", 0, move
, XT_MOVE_TOP
, 0 },
3391 { "1", 0, move
, XT_MOVE_TOP
, 0 },
3392 { "scrollhalfdown", 0, move
, XT_MOVE_HALFDOWN
, 0 },
3393 { "scrollhalfup", 0, move
, XT_MOVE_HALFUP
, 0 },
3394 { "scrollpagedown", 0, move
, XT_MOVE_PAGEDOWN
, 0 },
3395 { "scrollpageup", 0, move
, XT_MOVE_PAGEUP
, 0 },
3396 /* horizontal movement */
3397 { "scrollright", 0, move
, XT_MOVE_RIGHT
, 0 },
3398 { "scrollleft", 0, move
, XT_MOVE_LEFT
, 0 },
3399 { "scrollfarright", 0, move
, XT_MOVE_FARRIGHT
, 0 },
3400 { "scrollfarleft", 0, move
, XT_MOVE_FARLEFT
, 0 },
3402 { "favorites", 0, xtp_page_fl
, XT_SHOW
, 0 },
3403 { "fav", 0, xtp_page_fl
, XT_SHOW
, 0 },
3404 { "favedit", 0, xtp_page_fl
, XT_SHOW
|XT_DELETE
, 0 },
3405 { "favadd", 0, add_favorite
, 0, 0 },
3407 { "qall", 0, quit
, 0, 0 },
3408 { "quitall", 0, quit
, 0, 0 },
3409 { "w", 0, save_tabs
, 0, 0 },
3410 { "wq", 0, save_tabs_and_quit
, 0, 0 },
3411 { "help", 0, help
, 0, 0 },
3412 { "about", 0, xtp_page_ab
, 0, 0 },
3413 { "stats", 0, stats
, 0, 0 },
3414 { "version", 0, xtp_page_ab
, 0, 0 },
3417 { "js", 0, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3418 { "save", 1, js_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
3419 { "domain", 2, js_cmd
, XT_SAVE
| XT_WL_TOPLEVEL
, 0 },
3420 { "fqdn", 2, js_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
3421 { "show", 1, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3422 { "all", 2, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3423 { "persistent", 2, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
, 0 },
3424 { "session", 2, js_cmd
, XT_SHOW
| XT_WL_SESSION
, 0 },
3425 { "toggle", 1, js_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
3426 { "domain", 2, js_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
, 0 },
3427 { "fqdn", 2, js_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
3429 /* cookie command */
3430 { "cookie", 0, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3431 { "save", 1, cookie_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
3432 { "domain", 2, cookie_cmd
, XT_SAVE
| XT_WL_TOPLEVEL
, 0 },
3433 { "fqdn", 2, cookie_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
3434 { "show", 1, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3435 { "all", 2, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3436 { "persistent", 2, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
, 0 },
3437 { "session", 2, cookie_cmd
, XT_SHOW
| XT_WL_SESSION
, 0 },
3438 { "toggle", 1, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
3439 { "domain", 2, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
, 0 },
3440 { "fqdn", 2, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
3441 { "purge", 1, cookie_cmd
, XT_DELETE
, 0 },
3443 /* plugin command */
3444 { "plugin", 0, pl_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3445 { "save", 1, pl_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
3446 { "domain", 2, pl_cmd
, XT_SAVE
| XT_WL_TOPLEVEL
, 0 },
3447 { "fqdn", 2, pl_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
3448 { "show", 1, pl_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3449 { "all", 2, pl_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3450 { "persistent", 2, pl_cmd
, XT_SHOW
| XT_WL_PERSISTENT
, 0 },
3451 { "session", 2, pl_cmd
, XT_SHOW
| XT_WL_SESSION
, 0 },
3452 { "toggle", 1, pl_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
3453 { "domain", 2, pl_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
, 0 },
3454 { "fqdn", 2, pl_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
3457 { "https", 0, https_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3458 { "save", 1, https_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
3459 { "domain", 2, https_cmd
, XT_SAVE
| XT_WL_TOPLEVEL
, 0 },
3460 { "fqdn", 2, https_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
3461 { "show", 1, https_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3462 { "all", 2, https_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3463 { "persistent", 2, https_cmd
, XT_SHOW
| XT_WL_PERSISTENT
, 0 },
3464 { "session", 2, https_cmd
, XT_SHOW
| XT_WL_SESSION
, 0 },
3465 { "toggle", 1, https_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
3466 { "domain", 2, https_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
, 0 },
3467 { "fqdn", 2, https_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
3469 /* toplevel (domain) command */
3470 { "toplevel", 0, toplevel_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
| XT_WL_RELOAD
, 0 },
3471 { "toggle", 1, toplevel_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
| XT_WL_RELOAD
, 0 },
3474 { "cookiejar", 0, xtp_page_cl
, 0, 0 },
3477 { "cert", 0, cert_cmd
, XT_SHOW
, 0 },
3478 { "save", 1, cert_cmd
, XT_SAVE
, 0 },
3479 { "show", 1, cert_cmd
, XT_SHOW
, 0 },
3481 { "ca", 0, ca_cmd
, 0, 0 },
3482 { "downloadmgr", 0, xtp_page_dl
, 0, 0 },
3483 { "dl", 0, xtp_page_dl
, 0, 0 },
3484 { "h", 0, xtp_page_hl
, 0, 0 },
3485 { "history", 0, xtp_page_hl
, 0, 0 },
3486 { "home", 0, go_home
, 0, 0 },
3487 { "restart", 0, restart
, 0, 0 },
3488 { "urlhide", 0, urlaction
, XT_URL_HIDE
, 0 },
3489 { "urlshow", 0, urlaction
, XT_URL_SHOW
, 0 },
3490 { "statustoggle", 0, statustoggle
, 0, 0 },
3491 { "run_script", 0, run_page_script
, 0, XT_USERARG
},
3493 { "print", 0, print_page
, 0, 0 },
3496 { "focusin", 0, resizetab
, XT_ZOOM_IN
, 0 },
3497 { "focusout", 0, resizetab
, XT_ZOOM_OUT
, 0 },
3498 { "focusreset", 0, resizetab
, XT_ZOOM_NORMAL
, 0 },
3499 { "q", 0, tabaction
, XT_TAB_DELQUIT
, 0 },
3500 { "quit", 0, tabaction
, XT_TAB_DELQUIT
, 0 },
3501 { "open", 0, tabaction
, XT_TAB_OPEN
, XT_URLARG
},
3502 { "tabclose", 0, tabaction
, XT_TAB_DELETE
, XT_PREFIX
| XT_INTARG
},
3503 { "tabedit", 0, tabaction
, XT_TAB_NEW
, XT_PREFIX
| XT_URLARG
},
3504 { "tabfirst", 0, movetab
, XT_TAB_FIRST
, 0 },
3505 { "tabhide", 0, tabaction
, XT_TAB_HIDE
, 0 },
3506 { "tablast", 0, movetab
, XT_TAB_LAST
, 0 },
3507 { "tabnew", 0, tabaction
, XT_TAB_NEW
, XT_PREFIX
| XT_URLARG
},
3508 { "tabnext", 0, movetab
, XT_TAB_NEXT
, XT_PREFIX
| XT_INTARG
},
3509 { "tabnextstyle", 0, tabaction
, XT_TAB_NEXTSTYLE
, 0 },
3510 { "tabonly", 0, tabaction
, XT_TAB_ONLY
, 0 },
3511 { "tabprevious", 0, movetab
, XT_TAB_PREV
, XT_PREFIX
| XT_INTARG
},
3512 { "tabrewind", 0, movetab
, XT_TAB_FIRST
, 0 },
3513 { "tabshow", 0, tabaction
, XT_TAB_SHOW
, 0 },
3514 { "tabs", 0, buffers
, 0, 0 },
3515 { "tabundoclose", 0, tabaction
, XT_TAB_UNDO_CLOSE
, 0 },
3516 { "buffers", 0, buffers
, 0, 0 },
3517 { "ls", 0, buffers
, 0, 0 },
3518 { "encoding", 0, set_encoding
, 0, XT_USERARG
},
3519 { "loadimages", 0, tabaction
, XT_TAB_LOAD_IMAGES
, 0 },
3522 { "set", 0, set
, 0, XT_SETARG
},
3523 { "runtime", 0, xtp_page_rt
, 0, 0 },
3525 { "fullscreen", 0, fullscreen
, 0, 0 },
3526 { "f", 0, fullscreen
, 0, 0 },
3529 { "session", 0, session_cmd
, XT_SHOW
, 0 },
3530 { "delete", 1, session_cmd
, XT_DELETE
, XT_SESSARG
},
3531 { "open", 1, session_cmd
, XT_OPEN
, XT_SESSARG
},
3532 { "save", 1, session_cmd
, XT_SAVE
, XT_USERARG
},
3533 { "show", 1, session_cmd
, XT_SHOW
, 0 },
3535 /* external javascript */
3536 { "script", 0, script_cmd
, XT_EJS_SHOW
, XT_USERARG
},
3539 { "inspector", 0, inspector_cmd
, XT_INS_SHOW
, 0 },
3540 { "show", 1, inspector_cmd
, XT_INS_SHOW
, 0 },
3541 { "hide", 1, inspector_cmd
, XT_INS_HIDE
, 0 },
3544 { "proxy", 0, proxy_cmd
, XT_PRXY_SHOW
, 0 },
3545 { "show", 1, proxy_cmd
, XT_PRXY_SHOW
, 0 },
3546 { "toggle", 1, proxy_cmd
, XT_PRXY_TOGGLE
, 0 },
3553 } cmd_status
= {-1, 0};
3556 wv_release_button_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
3559 if (e
->type
== GDK_BUTTON_RELEASE
&& e
->button
== 1)
3566 wv_button_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
3569 WebKitHitTestResult
*hit_test_result
;
3572 hit_test_result
= webkit_web_view_get_hit_test_result(t
->wv
, e
);
3573 g_object_get(hit_test_result
, "context", &context
, NULL
);
3578 if (context
& WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE
)
3579 t
->mode
= XT_MODE_INSERT
;
3581 t
->mode
= XT_MODE_COMMAND
;
3583 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 1)
3585 else if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 8 /* btn 4 */) {
3591 } else if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 9 /* btn 5 */) {
3593 a
.i
= XT_NAV_FORWARD
;
3603 tab_close_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
3605 DNPRINTF(XT_D_TAB
, "tab_close_cb: tab %d\n", t
->tab_id
);
3607 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 1)
3614 parse_custom_uri(struct tab
*t
, const char *uri
)
3616 struct custom_uri
*u
;
3620 TAILQ_FOREACH(u
, &cul
, entry
) {
3621 if (strncmp(uri
, u
->uri
, strlen(u
->uri
)))
3626 sv
[1] = (char *)uri
;
3628 if (!g_spawn_async(NULL
, sv
, NULL
, G_SPAWN_SEARCH_PATH
, NULL
,
3630 show_oops(t
, "%s: could not spawn process", __func__
);
3637 activate_uri_entry_cb(GtkWidget
* entry
, struct tab
*t
)
3639 const gchar
*uri
= gtk_entry_get_text(GTK_ENTRY(entry
));
3641 DNPRINTF(XT_D_URL
, "activate_uri_entry_cb: %s\n", uri
);
3644 show_oops(NULL
, "activate_uri_entry_cb invalid parameters");
3649 show_oops(t
, "activate_uri_entry_cb no uri");
3653 uri
+= strspn(uri
, "\t ");
3655 if (parse_custom_uri(t
, uri
))
3658 /* otherwise continue to load page normally */
3659 load_uri(t
, (gchar
*)uri
);
3664 activate_search_entry_cb(GtkWidget
* entry
, struct tab
*t
)
3666 const gchar
*search
= gtk_entry_get_text(GTK_ENTRY(entry
));
3667 char *newuri
= NULL
;
3671 DNPRINTF(XT_D_URL
, "activate_search_entry_cb: %s\n", search
);
3674 show_oops(NULL
, "activate_search_entry_cb invalid parameters");
3678 if (search_string
== NULL
|| strlen(search_string
) == 0) {
3679 show_oops(t
, "no search_string");
3683 set_normal_tab_meaning(t
);
3685 enc_search
= soup_uri_encode(search
, XT_RESERVED_CHARS
);
3686 sv
= g_strsplit(search_string
, "%s", 2);
3687 newuri
= g_strjoinv(enc_search
, sv
);
3692 load_uri(t
, newuri
);
3700 check_and_set_cookie(const gchar
*uri
, struct tab
*t
)
3702 struct wl_entry
*w
= NULL
;
3705 if (uri
== NULL
|| t
== NULL
)
3708 if ((w
= wl_find_uri(uri
, &c_wl
)) == NULL
)
3713 DNPRINTF(XT_D_COOKIE
, "check_and_set_cookie: %s %s\n",
3714 es
? "enable" : "disable", uri
);
3716 g_object_set(G_OBJECT(t
->settings
),
3717 "enable-html5-local-storage", es
, (char *)NULL
);
3718 webkit_web_view_set_settings(t
->wv
, t
->settings
);
3722 check_and_set_js(const gchar
*uri
, struct tab
*t
)
3724 struct wl_entry
*w
= NULL
;
3727 if (uri
== NULL
|| t
== NULL
)
3730 if ((w
= wl_find_uri(uri
, &js_wl
)) == NULL
)
3735 DNPRINTF(XT_D_JS
, "check_and_set_js: %s %s\n",
3736 es
? "enable" : "disable", uri
);
3738 g_object_set(G_OBJECT(t
->settings
),
3739 "enable-scripts", es
, (char *)NULL
);
3740 g_object_set(G_OBJECT(t
->settings
),
3741 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
3742 webkit_web_view_set_settings(t
->wv
, t
->settings
);
3744 button_set_stockid(t
->js_toggle
,
3745 es
? GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
);
3749 check_and_set_pl(const gchar
*uri
, struct tab
*t
)
3751 struct wl_entry
*w
= NULL
;
3754 if (uri
== NULL
|| t
== NULL
)
3757 if ((w
= wl_find_uri(uri
, &pl_wl
)) == NULL
)
3762 DNPRINTF(XT_D_JS
, "check_and_set_pl: %s %s\n",
3763 es
? "enable" : "disable", uri
);
3765 g_object_set(G_OBJECT(t
->settings
),
3766 "enable-plugins", es
, (char *)NULL
);
3767 webkit_web_view_set_settings(t
->wv
, t
->settings
);
3770 #if GTK_CHECK_VERSION(3, 0, 0)
3771 /* A lot of this can be removed when gtk2 is dropped on the floor */
3773 get_css_name(const char *col_str
)
3777 if (!strcmp(col_str
, XT_COLOR_WHITE
))
3778 name
= g_strdup(XT_CSS_NORMAL
);
3779 else if (!strcmp(col_str
, XT_COLOR_RED
))
3780 name
= g_strdup(XT_CSS_RED
);
3781 else if (!strcmp(col_str
, XT_COLOR_YELLOW
))
3782 name
= g_strdup(XT_CSS_YELLOW
);
3783 else if (!strcmp(col_str
, XT_COLOR_GREEN
))
3784 name
= g_strdup(XT_CSS_GREEN
);
3785 else if (!strcmp(col_str
, XT_COLOR_BLUE
))
3786 name
= g_strdup(XT_CSS_BLUE
);
3792 check_certs(gpointer p
)
3794 struct tab
*tt
, *t
= p
;
3795 gchar
*col_str
= XT_COLOR_WHITE
;
3796 const gchar
*uri
, *u
= NULL
, *error_str
= NULL
;
3797 #if GTK_CHECK_VERSION(3, 0, 0)
3805 gdk_threads_enter();
3807 DNPRINTF(XT_D_URL
, "%s:\n", __func__
);
3809 /* make sure t still exists */
3812 TAILQ_FOREACH(tt
, &tabs
, entry
)
3818 if ((uri
= get_uri(t
)) == NULL
)
3824 gdk_threads_leave();
3827 col_str
= XT_COLOR_YELLOW
;
3828 switch (load_compare_cert(u
, &error_str
, certs_dir
)) {
3830 col_str
= XT_COLOR_BLUE
;
3833 col_str
= (strlen(ssl_ca_file
) == 0) ? XT_COLOR_RED
3836 case CERT_UNTRUSTED
:
3837 col_str
= (strlen(ssl_ca_file
) == 0) ? XT_COLOR_RED
3841 col_str
= XT_COLOR_RED
;
3846 gdk_threads_enter();
3848 /* make sure t isn't deleted */
3849 TAILQ_FOREACH(tt
, &tabs
, entry
)
3856 /* test to see if the user navigated away and canceled the thread */
3857 if (t
->thread
!= g_thread_self())
3859 if ((uri
= get_uri(t
)) == NULL
) {
3863 if (strcmp(uri
, u
)) {
3864 /* make sure we are still the same url */
3871 if (!strcmp(col_str
, XT_COLOR_WHITE
)) {
3872 #if GTK_CHECK_VERSION(3, 0, 0)
3873 gtk_widget_set_name(t
->uri_entry
, XT_CSS_NORMAL
);
3874 statusbar_modify_attr(t
, XT_CSS_NORMAL
);
3876 text
= gdk_color_to_string(
3877 &t
->default_style
->text
[GTK_STATE_NORMAL
]);
3878 base
= gdk_color_to_string(
3879 &t
->default_style
->base
[GTK_STATE_NORMAL
]);
3880 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
,
3881 &t
->default_style
->base
[GTK_STATE_NORMAL
]);
3882 statusbar_modify_attr(t
, text
, base
);
3887 #if GTK_CHECK_VERSION(3, 0, 0)
3888 name
= get_css_name(col_str
);
3889 gtk_widget_set_name(t
->uri_entry
, name
);
3890 statusbar_modify_attr(t
, name
);
3893 gdk_color_parse(col_str
, &color
);
3894 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
3895 statusbar_modify_attr(t
, XT_COLOR_BLACK
, col_str
);
3899 if (error_str
&& error_str
[0] != '\0')
3900 show_oops(t
, "%s", error_str
);
3902 check_cert_changes(t
, u
);
3908 /* t is invalid at this point */
3910 g_free((gpointer
)u
);
3913 gdk_threads_leave();
3918 show_ca_status(struct tab
*t
, const char *uri
)
3920 gchar
*col_str
= XT_COLOR_WHITE
;
3921 #if GTK_CHECK_VERSION(3, 0, 0)
3928 DNPRINTF(XT_D_URL
, "show_ca_status: %d %s %s\n",
3929 ssl_strict_certs
, ssl_ca_file
, uri
);
3934 if (uri
== NULL
|| g_str_has_prefix(uri
, "http://") ||
3935 !g_str_has_prefix(uri
, "https://"))
3940 * It is not necessary to see if the thread is already running.
3941 * If the thread is in progress setting it to something else aborts it
3945 /* thread the coloring of the address bar */
3946 t
->thread
= g_thread_create((GThreadFunc
)check_certs
, t
, TRUE
, NULL
);
3954 if (!strcmp(col_str
, XT_COLOR_WHITE
)) {
3955 #if GTK_CHECK_VERSION(3, 0, 0)
3956 gtk_widget_set_name(t
->uri_entry
, XT_CSS_NORMAL
);
3957 statusbar_modify_attr(t
, XT_CSS_NORMAL
);
3959 text
= gdk_color_to_string(
3960 &t
->default_style
->text
[GTK_STATE_NORMAL
]);
3961 base
= gdk_color_to_string(
3962 &t
->default_style
->base
[GTK_STATE_NORMAL
]);
3963 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
,
3964 &t
->default_style
->base
[GTK_STATE_NORMAL
]);
3965 statusbar_modify_attr(t
, text
, base
);
3970 #if GTK_CHECK_VERSION(3, 0, 0)
3971 name
= get_css_name(col_str
);
3972 gtk_widget_set_name(t
->uri_entry
, name
);
3973 statusbar_modify_attr(t
, name
);
3976 gdk_color_parse(col_str
, &color
);
3977 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
3978 statusbar_modify_attr(t
, XT_COLOR_BLACK
, col_str
);
3985 free_favicon(struct tab
*t
)
3987 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p req %p\n",
3988 __func__
, t
->icon_download
, t
->icon_request
);
3990 if (t
->icon_request
)
3991 g_object_unref(t
->icon_request
);
3992 if (t
->icon_dest_uri
)
3993 g_free(t
->icon_dest_uri
);
3995 t
->icon_request
= NULL
;
3996 t
->icon_dest_uri
= NULL
;
4000 xt_icon_from_name(struct tab
*t
, gchar
*name
)
4002 if (!enable_favicon_entry
)
4005 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->uri_entry
),
4006 GTK_ENTRY_ICON_PRIMARY
, "text-html");
4008 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->sbe
.uri
),
4009 GTK_ENTRY_ICON_PRIMARY
, "text-html");
4011 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->sbe
.uri
),
4012 GTK_ENTRY_ICON_PRIMARY
, NULL
);
4016 xt_icon_from_pixbuf(struct tab
*t
, GdkPixbuf
*pb
)
4018 GdkPixbuf
*pb_scaled
;
4020 if (gdk_pixbuf_get_width(pb
) > 16 || gdk_pixbuf_get_height(pb
) > 16)
4021 pb_scaled
= gdk_pixbuf_scale_simple(pb
, 16, 16,
4022 GDK_INTERP_BILINEAR
);
4026 if (enable_favicon_entry
) {
4029 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->uri_entry
),
4030 GTK_ENTRY_ICON_PRIMARY
, pb_scaled
);
4033 if (show_url
== 0) {
4034 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->sbe
.uri
),
4035 GTK_ENTRY_ICON_PRIMARY
, pb_scaled
);
4037 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->sbe
.uri
),
4038 GTK_ENTRY_ICON_PRIMARY
, NULL
);
4041 /* XXX: Only supports the minimal tabs atm. */
4042 if (enable_favicon_tabs
)
4043 gtk_image_set_from_pixbuf(GTK_IMAGE(t
->tab_elems
.favicon
),
4046 if (pb_scaled
!= pb
)
4047 g_object_unref(pb_scaled
);
4051 xt_icon_from_file(struct tab
*t
, char *uri
)
4056 if (g_str_has_prefix(uri
, "file://"))
4057 file
= g_filename_from_uri(uri
, NULL
, NULL
);
4059 file
= g_strdup(uri
);
4064 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
4066 xt_icon_from_pixbuf(t
, pb
);
4069 xt_icon_from_name(t
, "text-html");
4075 is_valid_icon(char *file
)
4078 const char *mime_type
;
4082 gf
= g_file_new_for_path(file
);
4083 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
4085 mime_type
= g_file_info_get_content_type(fi
);
4086 valid
= g_strcmp0(mime_type
, "image/x-ico") == 0 ||
4087 g_strcmp0(mime_type
, "image/vnd.microsoft.icon") == 0 ||
4088 g_strcmp0(mime_type
, "image/png") == 0 ||
4089 g_strcmp0(mime_type
, "image/gif") == 0 ||
4090 g_strcmp0(mime_type
, "application/octet-stream") == 0;
4098 set_favicon_from_file(struct tab
*t
, char *uri
)
4103 if (t
== NULL
|| uri
== NULL
)
4106 if (g_str_has_prefix(uri
, "file://"))
4107 file
= g_filename_from_uri(uri
, NULL
, NULL
);
4109 file
= g_strdup(uri
);
4114 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading %s\n", __func__
, file
);
4116 if (!stat(file
, &sb
)) {
4117 if (sb
.st_size
== 0 || !is_valid_icon(file
)) {
4118 /* corrupt icon so trash it */
4119 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
4122 /* no need to set icon to default here */
4126 xt_icon_from_file(t
, file
);
4132 favicon_download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
4135 WebKitDownloadStatus status
= webkit_download_get_status(download
);
4136 struct tab
*tt
= NULL
, *t
= NULL
;
4139 * find the webview instead of passing in the tab as it could have been
4140 * deleted from underneath us.
4142 TAILQ_FOREACH(tt
, &tabs
, entry
) {
4151 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d status %d\n",
4152 __func__
, t
->tab_id
, status
);
4155 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
4157 t
->icon_download
= NULL
;
4160 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
4163 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
4166 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
4168 DNPRINTF(XT_D_DOWNLOAD
, "%s: freeing favicon %d\n",
4169 __func__
, t
->tab_id
);
4170 t
->icon_download
= NULL
;
4173 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
4176 DNPRINTF(XT_D_DOWNLOAD
, "%s: setting icon to %s\n",
4177 __func__
, t
->icon_dest_uri
);
4178 set_favicon_from_file(t
, t
->icon_dest_uri
);
4179 /* these will be freed post callback */
4180 t
->icon_request
= NULL
;
4181 t
->icon_download
= NULL
;
4189 abort_favicon_download(struct tab
*t
)
4191 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p\n", __func__
, t
->icon_download
);
4193 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
4194 if (t
->icon_download
) {
4195 g_signal_handlers_disconnect_by_func(G_OBJECT(t
->icon_download
),
4196 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
4197 webkit_download_cancel(t
->icon_download
);
4198 t
->icon_download
= NULL
;
4203 xt_icon_from_name(t
, "text-html");
4207 notify_icon_loaded_cb(WebKitWebView
*wv
, gchar
*uri
, struct tab
*t
)
4209 DNPRINTF(XT_D_DOWNLOAD
, "%s %s\n", __func__
, uri
);
4211 if (uri
== NULL
|| t
== NULL
)
4214 #if WEBKIT_CHECK_VERSION(1, 4, 0)
4215 /* take icon from WebKitIconDatabase */
4216 GdkPixbuf
*pb
= NULL
;
4218 /* webkit_web_view_get_icon_pixbuf is depreciated in 1.8 */
4219 #if WEBKIT_CHECK_VERSION(1, 8, 0)
4221 * If the page was not loaded (for example, via ssl_strict_certs), do
4222 * not attempt to get the webview's pixbuf. This prevents a CRITICAL
4225 if (wv
&& webkit_web_view_get_uri(wv
))
4226 pb
= webkit_web_view_try_get_favicon_pixbuf(wv
, 0, 0);
4228 if (wv
&& webkit_web_view_get_uri(wv
))
4229 pb
= webkit_web_view_get_icon_pixbuf(wv
);
4232 xt_icon_from_pixbuf(t
, pb
);
4235 xt_icon_from_name(t
, "text-html");
4236 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
4237 /* download icon to cache dir */
4238 gchar
*name_hash
, file
[PATH_MAX
];
4241 if (t
->icon_request
) {
4242 DNPRINTF(XT_D_DOWNLOAD
, "%s: download in progress\n", __func__
);
4246 /* check to see if we got the icon in cache */
4247 name_hash
= g_compute_checksum_for_string(G_CHECKSUM_SHA256
, uri
, -1);
4248 snprintf(file
, sizeof file
, "%s" PS
"%s.ico", cache_dir
, name_hash
);
4251 if (!stat(file
, &sb
)) {
4252 if (sb
.st_size
> 0) {
4253 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading from cache %s\n",
4255 set_favicon_from_file(t
, file
);
4259 /* corrupt icon so trash it */
4260 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
4265 /* create download for icon */
4266 t
->icon_request
= webkit_network_request_new(uri
);
4267 if (t
->icon_request
== NULL
) {
4268 DNPRINTF(XT_D_DOWNLOAD
, "%s: invalid uri %s\n",
4273 t
->icon_download
= webkit_download_new(t
->icon_request
);
4274 if (t
->icon_download
== NULL
)
4277 /* we have to free icon_dest_uri later */
4278 if ((t
->icon_dest_uri
= g_filename_to_uri(file
, NULL
, NULL
)) == NULL
)
4280 webkit_download_set_destination_uri(t
->icon_download
,
4283 if (webkit_download_get_status(t
->icon_download
) ==
4284 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
4285 g_object_unref(t
->icon_request
);
4286 g_free(t
->icon_dest_uri
);
4287 t
->icon_request
= NULL
;
4288 t
->icon_dest_uri
= NULL
;
4292 g_signal_connect(G_OBJECT(t
->icon_download
), "notify::status",
4293 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
4295 webkit_download_start(t
->icon_download
);
4300 notify_load_status_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
4302 const gchar
*uri
= NULL
;
4303 struct history
*h
, find
;
4305 gchar
*tmp_uri
= NULL
;
4306 #if !GTK_CHECK_VERSION(3, 0, 0)
4310 DNPRINTF(XT_D_URL
, "notify_load_status_cb: %d %s\n",
4311 webkit_web_view_get_load_status(wview
),
4312 get_uri(t
) ? get_uri(t
) : "NOTHING");
4315 show_oops(NULL
, "notify_load_status_cb invalid parameters");
4319 switch (webkit_web_view_get_load_status(wview
)) {
4320 case WEBKIT_LOAD_PROVISIONAL
:
4322 abort_favicon_download(t
);
4323 #if GTK_CHECK_VERSION(2, 20, 0)
4324 gtk_widget_show(t
->spinner
);
4325 gtk_spinner_start(GTK_SPINNER(t
->spinner
));
4327 t
->download_requested
= 0;
4329 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), TRUE
);
4331 /* assume we are a new address */
4332 #if GTK_CHECK_VERSION(3, 0, 0)
4333 gtk_widget_set_name(t
->uri_entry
, XT_CSS_NORMAL
);
4334 statusbar_modify_attr(t
, XT_CSS_NORMAL
);
4336 text
= gdk_color_to_string(
4337 &t
->default_style
->text
[GTK_STATE_NORMAL
]);
4338 base
= gdk_color_to_string(
4339 &t
->default_style
->base
[GTK_STATE_NORMAL
]);
4340 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
,
4341 &t
->default_style
->base
[GTK_STATE_NORMAL
]);
4342 statusbar_modify_attr(t
, text
, base
);
4347 /* DOM is changing, unreference the previous focused element */
4348 #if WEBKIT_CHECK_VERSION(1, 5, 0)
4350 g_object_unref(t
->active
);
4352 if (t
->active_text
) {
4353 g_free(t
->active_text
);
4354 t
->active_text
= NULL
;
4358 /* take focus if we are visible */
4364 /* kill color thread */
4369 case WEBKIT_LOAD_COMMITTED
:
4374 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
4380 set_status(t
, "Loading: %s", (char *)uri
);
4382 /* clear t->item, except if we're switching to an about: page */
4383 if (t
->item
&& !g_str_has_prefix(uri
, "xxxt://") &&
4384 !g_str_has_prefix(uri
, "about:")) {
4385 g_object_unref(t
->item
);
4389 /* check if js white listing is enabled */
4390 if (enable_plugin_whitelist
)
4391 check_and_set_pl(uri
, t
);
4392 if (enable_cookie_whitelist
)
4393 check_and_set_cookie(uri
, t
);
4394 if (enable_js_whitelist
)
4395 check_and_set_js(uri
, t
);
4401 /* we know enough to autosave the session */
4402 if (session_autosave
) {
4407 show_ca_status(t
, uri
);
4408 run_script(t
, JS_HINTING
);
4409 if (enable_autoscroll
)
4410 run_script(t
, JS_AUTOSCROLL
);
4413 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT
:
4415 if (color_visited_uris
) {
4416 color_visited(t
, color_visited_helper());
4419 * This colors the links you middle-click (open in new
4420 * tab) in the current tab.
4422 if (t
->tab_id
!= gtk_notebook_get_current_page(notebook
) &&
4423 (uri
= get_uri(t
)) != NULL
)
4424 color_visited(get_current_tab(),
4425 g_strdup_printf("{'%s' : 'dummy'}", uri
));
4429 case WEBKIT_LOAD_FINISHED
:
4431 if ((uri
= get_uri(t
)) == NULL
)
4434 * js_autorun calls get_uri which frees t->tmp_uri if on an
4435 * "about:" page. On "about:" pages, uri points to t->tmp_uri.
4436 * I.e. we will use freed memory. Prevent that.
4438 tmp_uri
= g_strdup(uri
);
4440 /* autorun some js if enabled */
4445 if (!strncmp(tmp_uri
, "http://", strlen("http://")) ||
4446 !strncmp(tmp_uri
, "https://", strlen("https://")) ||
4447 !strncmp(tmp_uri
, "file://", strlen("file://"))) {
4448 find
.uri
= (gchar
*)tmp_uri
;
4449 h
= RB_FIND(history_list
, &hl
, &find
);
4451 insert_history_item(tmp_uri
,
4452 get_title(t
, FALSE
), time(NULL
));
4454 h
->time
= time(NULL
);
4457 if (statusbar_style
== XT_STATUSBAR_URL
)
4458 set_status(t
, "%s", (char *)tmp_uri
);
4460 set_status(t
, "%s", (char *)get_title(t
, FALSE
));
4461 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), FALSE
);
4462 #if GTK_CHECK_VERSION(2, 20, 0)
4463 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
4464 gtk_widget_hide(t
->spinner
);
4469 #if WEBKIT_CHECK_VERSION(1, 1, 18)
4470 case WEBKIT_LOAD_FAILED
:
4472 if (!t
->download_requested
) {
4473 gtk_label_set_text(GTK_LABEL(t
->label
),
4474 get_title(t
, FALSE
));
4475 gtk_label_set_text(GTK_LABEL(t
->tab_elems
.label
),
4476 get_title(t
, FALSE
));
4477 set_status(t
, "%s", (char *)get_title(t
, FALSE
));
4478 gtk_window_set_title(GTK_WINDOW(main_window
),
4479 get_title(t
, TRUE
));
4485 #if GTK_CHECK_VERSION(2, 20, 0)
4486 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
4487 gtk_widget_hide(t
->spinner
);
4489 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), FALSE
);
4493 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
),
4494 can_go_back_for_real(t
));
4496 gtk_widget_set_sensitive(GTK_WIDGET(t
->forward
),
4497 can_go_forward_for_real(t
));
4501 notify_title_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
4503 const gchar
*title
= NULL
, *win_title
= NULL
;
4505 title
= get_title(t
, FALSE
);
4506 win_title
= get_title(t
, TRUE
);
4508 gtk_label_set_text(GTK_LABEL(t
->label
), title
);
4509 gtk_label_set_text(GTK_LABEL(t
->tab_elems
.label
), title
);
4512 if (win_title
&& t
->tab_id
== gtk_notebook_get_current_page(notebook
))
4513 gtk_window_set_title(GTK_WINDOW(main_window
), win_title
);
4517 get_domain(const gchar
*host
)
4522 /* handle silly domains like .co.uk */
4524 if ((x
= strlen(host
)) <= 6)
4525 return (g_strdup(host
));
4527 if (host
[x
- 3] == '.' && host
[x
- 6] == '.') {
4533 return (g_strdup(&host
[x
+ 1]));
4537 p
= g_strrstr(host
, ".");
4539 return (g_strdup(""));
4545 return (g_strdup(p
+ 1));
4547 return (g_strdup(host
));
4551 js_autorun(struct tab
*t
)
4555 size_t got_default
= 0, got_host
= 0;
4557 char deff
[PATH_MAX
], hostf
[PATH_MAX
];
4558 char *js
= NULL
, *jsat
, *domain
= NULL
;
4559 FILE *deffile
= NULL
, *hostfile
= NULL
;
4561 if (enable_js_autorun
== 0)
4566 !(g_str_has_prefix(uri
, "http://") ||
4567 g_str_has_prefix(uri
, "https://")))
4570 su
= soup_uri_new(uri
);
4573 if (!SOUP_URI_VALID_FOR_HTTP(su
))
4576 DNPRINTF(XT_D_JS
, "%s: host: %s domain: %s\n", __func__
,
4578 domain
= get_domain(su
->host
);
4580 snprintf(deff
, sizeof deff
, "%s" PS
"default.js", js_dir
);
4581 if ((deffile
= fopen(deff
, "r")) != NULL
) {
4582 if (fstat(fileno(deffile
), &sb
) == -1) {
4583 show_oops(t
, "can't stat default JS file");
4586 got_default
= sb
.st_size
;
4589 /* try host first followed by domain */
4590 snprintf(hostf
, sizeof hostf
, "%s" PS
"%s.js", js_dir
, su
->host
);
4591 DNPRINTF(XT_D_JS
, "trying file: %s\n", hostf
);
4592 if ((hostfile
= fopen(hostf
, "r")) == NULL
) {
4593 snprintf(hostf
, sizeof hostf
, "%s" PS
"%s.js", js_dir
, domain
);
4594 DNPRINTF(XT_D_JS
, "trying file: %s\n", hostf
);
4595 if ((hostfile
= fopen(hostf
, "r")) == NULL
)
4598 DNPRINTF(XT_D_JS
, "file: %s\n", hostf
);
4599 if (fstat(fileno(hostfile
), &sb
) == -1) {
4600 show_oops(t
, "can't stat %s JS file", hostf
);
4603 got_host
= sb
.st_size
;
4606 if (got_default
+ got_host
== 0)
4609 js
= g_malloc0(got_default
+ got_host
+ 1);
4613 if (fread(js
, got_default
, 1, deffile
) != 1) {
4614 show_oops(t
, "default file read error");
4617 jsat
= js
+ got_default
;
4621 if (fread(jsat
, got_host
, 1, hostfile
) != 1) {
4622 show_oops(t
, "host file read error");
4627 DNPRINTF(XT_D_JS
, "%s: about to run script\n", __func__
);
4628 run_script_locked(t
, js
);
4644 webview_progress_changed_cb(WebKitWebView
*wv
, GParamSpec
*pspec
, struct tab
*t
)
4648 progress
= webkit_web_view_get_progress(wv
);
4649 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->sbe
.uri
),
4650 progress
== 1.0 ? 0 : progress
);
4651 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->uri_entry
),
4652 progress
== 1.0 ? 0 : progress
);
4654 update_statusbar_position(NULL
, NULL
);
4658 strict_transport_rb_cmp(struct strict_transport
*a
, struct strict_transport
*b
)
4663 /* compare strings from the end */
4664 l1
= strlen(a
->host
);
4665 l2
= strlen(b
->host
);
4669 for (; *p1
== *p2
&& p1
> a
->host
&& p2
> b
->host
;
4674 * Check if we need to do pattern expansion,
4675 * or if we're just keeping the tree in order
4677 if (a
->flags
& XT_STS_FLAGS_EXPAND
&&
4678 b
->flags
& XT_STS_FLAGS_INCLUDE_SUBDOMAINS
) {
4679 /* Check if we're matching the
4680 * 'host.xyz' part in '*.host.xyz'
4682 if (p2
== b
->host
&& (p1
== a
->host
|| *(p1
-1) == '.')) {
4687 if (p1
== a
->host
&& p2
== b
->host
)
4701 RB_GENERATE(strict_transport_tree
, strict_transport
, entry
,
4702 strict_transport_rb_cmp
);
4705 strict_transport_add(const char *domain
, time_t timeout
, int subdomains
)
4707 struct strict_transport
*d
, find
;
4711 if (enable_strict_transport
== FALSE
)
4714 DPRINTF("strict_transport_add(%s,%" PRIi64
",%d)\n", domain
,
4715 (uint64_t)timeout
, subdomains
);
4721 find
.host
= (char *)domain
;
4723 d
= RB_FIND(strict_transport_tree
, &st_tree
, &find
);
4727 /* check if update is needed */
4728 if (d
->timeout
== timeout
&&
4729 (d
->flags
& XT_STS_FLAGS_INCLUDE_SUBDOMAINS
) == subdomains
)
4732 d
->timeout
= timeout
;
4734 d
->flags
|= XT_STS_FLAGS_INCLUDE_SUBDOMAINS
;
4736 /* We're still initializing */
4737 if (strict_transport_file
== NULL
)
4740 if ((f
= fopen(strict_transport_file
, "w")) == NULL
) {
4742 "can't open strict-transport rules file");
4746 fprintf(f
, "# Generated file - do not update unless you know "
4747 "what you're doing\n");
4748 RB_FOREACH(d
, strict_transport_tree
, &st_tree
) {
4749 if (d
->timeout
< now
)
4751 fprintf(f
, "%s\t%" PRIi64
"\t%d\n", d
->host
,
4752 (uint64_t)d
->timeout
,
4753 d
->flags
& XT_STS_FLAGS_INCLUDE_SUBDOMAINS
);
4757 d
= g_malloc(sizeof *d
);
4758 d
->host
= g_strdup(domain
);
4759 d
->timeout
= timeout
;
4761 d
->flags
= XT_STS_FLAGS_INCLUDE_SUBDOMAINS
;
4764 RB_INSERT(strict_transport_tree
, &st_tree
, d
);
4766 /* We're still initializing */
4767 if (strict_transport_file
== NULL
)
4770 if ((f
= fopen(strict_transport_file
, "a+")) == NULL
) {
4772 "can't open strict-transport rules file");
4776 fseek(f
, 0, SEEK_END
);
4777 fprintf(f
,"%s\t%" PRIi64
"\t%d\n", d
->host
, (uint64_t)timeout
,
4785 strict_transport_check(const char *host
)
4787 static struct strict_transport
*d
= NULL
;
4788 struct strict_transport find
;
4790 if (enable_strict_transport
== FALSE
)
4793 find
.host
= (char *)host
;
4795 /* match for domains that include subdomains */
4796 find
.flags
= XT_STS_FLAGS_EXPAND
;
4798 /* First, check if we're already at the right node */
4799 if (d
!= NULL
&& strict_transport_rb_cmp(&find
, d
) == 0) {
4803 d
= RB_FIND(strict_transport_tree
, &st_tree
, &find
);
4811 strict_transport_init()
4813 char file
[PATH_MAX
];
4819 time_t timeout
, now
;
4822 snprintf(file
, sizeof file
, "%s" PS
"%s", work_dir
, XT_STS_FILE
);
4823 if ((f
= fopen(file
, "r")) == NULL
) {
4824 strict_transport_file
= g_strdup(file
);
4835 if ((rule
= fparseln(f
, &len
, NULL
, delim
, 0)) == NULL
) {
4836 if (!feof(f
) || ferror(f
))
4842 /* get second entry */
4843 if ((ptr
= strpbrk(rule
, " \t")) == NULL
)
4847 timeout
= atoi(ptr
);
4849 /* get third entry */
4850 if ((ptr
= strpbrk(ptr
, " \t")) == NULL
)
4854 subdomains
= atoi(ptr
);
4857 strict_transport_add(rule
, timeout
, subdomains
);
4862 strict_transport_file
= g_strdup(file
);
4866 startpage_add("strict-transport rules file ('%s') is corrupt", file
);
4874 force_https_check(const char *uri
)
4876 struct wl_entry
*w
= NULL
;
4881 if ((w
= wl_find_uri(uri
, &force_https
)) == NULL
)
4888 strict_transport_security_cb(SoupMessage
*msg
, gpointer data
)
4894 int subdomains
= FALSE
;
4899 sts
= soup_message_headers_get_one(msg
->response_headers
,
4900 "Strict-Transport-Security");
4901 uri
= soup_message_get_uri(msg
);
4903 if (sts
== NULL
|| uri
== NULL
)
4906 if ((ptr
= strcasestr(sts
, "max-age="))) {
4907 ptr
+= strlen("max-age=");
4908 timeout
= atoll(ptr
);
4910 return; /* malformed header - max-age must be included */
4912 if ((ptr
= strcasestr(sts
, "includeSubDomains")))
4915 strict_transport_add(uri
->host
, timeout
+ time(NULL
), subdomains
);
4919 session_rq_cb(SoupSession
*s
, SoupMessage
*msg
, SoupSocket
*socket
,
4929 if (s
== NULL
|| msg
== NULL
)
4932 if (enable_strict_transport
) {
4933 soup_message_add_header_handler(msg
, "finished",
4934 "Strict-Transport-Security",
4935 G_CALLBACK(strict_transport_security_cb
), NULL
);
4938 if (referer_mode
== XT_REFERER_ALWAYS
)
4941 /* Check if referer is set - and what the user requested for referers */
4942 ref
= soup_message_headers_get_one(msg
->request_headers
, "Referer");
4944 DNPRINTF(XT_D_NAV
, "session_rq_cb: Referer: %s\n", ref
);
4945 switch (referer_mode
) {
4946 case XT_REFERER_NEVER
:
4947 DNPRINTF(XT_D_NAV
, "session_rq_cb: removing referer\n");
4948 soup_message_headers_remove(msg
->request_headers
,
4951 case XT_REFERER_SAME_DOMAIN
:
4952 ref_uri
= soup_uri_new(ref
);
4953 dest
= soup_message_get_uri(msg
);
4955 ref_suffix
= tld_get_suffix(ref_uri
->host
);
4956 dest_suffix
= tld_get_suffix(dest
->host
);
4958 if (dest
&& ref_suffix
&& dest_suffix
&&
4959 strcmp(ref_suffix
, dest_suffix
) != 0) {
4960 soup_message_headers_remove(msg
->request_headers
,
4962 DNPRINTF(XT_D_NAV
, "session_rq_cb: removing "
4963 "referer (not same domain) (suffixes: %s - %s)\n",
4964 ref_suffix
, dest_suffix
);
4966 soup_uri_free(ref_uri
);
4968 case XT_REFERER_SAME_FQDN
:
4969 ref_uri
= soup_uri_new(ref
);
4970 dest
= soup_message_get_uri(msg
);
4971 if (dest
&& strcmp(ref_uri
->host
, dest
->host
) != 0) {
4972 soup_message_headers_remove(msg
->request_headers
,
4974 DNPRINTF(XT_D_NAV
, "session_rq_cb: removing "
4975 "referer (not same fqdn) (should be %s)\n",
4978 soup_uri_free(ref_uri
);
4980 case XT_REFERER_CUSTOM
:
4981 DNPRINTF(XT_D_NAV
, "session_rq_cb: setting referer "
4982 "to %s\n", referer_custom
);
4983 soup_message_headers_replace(msg
->request_headers
,
4984 "Referer", referer_custom
);
4991 webview_npd_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
,
4992 WebKitNetworkRequest
*request
, WebKitWebNavigationAction
*na
,
4993 WebKitWebPolicyDecision
*pd
, struct tab
*t
)
4995 WebKitWebNavigationReason reason
;
4999 show_oops(NULL
, "webview_npd_cb invalid parameters");
5003 DNPRINTF(XT_D_NAV
, "webview_npd_cb: ctrl_click %d %s\n",
5005 webkit_network_request_get_uri(request
));
5007 uri
= (char *)webkit_network_request_get_uri(request
);
5009 if (!auto_load_images
&& t
->load_images
) {
5011 /* Disable autoloading of images, now that we're done loading
5013 g_object_set(G_OBJECT(t
->settings
),
5014 "auto-load-images", FALSE
, (char *)NULL
);
5015 webkit_web_view_set_settings(t
->wv
, t
->settings
);
5017 t
->load_images
= FALSE
;
5020 /* If this is an xtp url, we don't load anything else. */
5021 if (parse_xtp_url(t
, uri
)) {
5022 webkit_web_policy_decision_ignore(pd
);
5026 if (parse_custom_uri(t
, uri
)) {
5027 webkit_web_policy_decision_ignore(pd
);
5031 if (valid_url_type(uri
)) {
5032 show_oops(t
, "Stopping attempt to load an invalid URI (possible"
5033 " bait and switch attack)");
5034 webkit_web_policy_decision_ignore(pd
);
5038 if ((t
->mode
== XT_MODE_HINT
&& t
->new_tab
) || t
->ctrl_click
) {
5040 create_new_tab(uri
, NULL
, ctrl_click_focus
, -1);
5041 webkit_web_policy_decision_ignore(pd
);
5042 return (TRUE
); /* we made the decission */
5046 * This is a little hairy but it comes down to this:
5047 * when we run in whitelist mode we have to assist the browser in
5048 * opening the URL that it would have opened in a new tab.
5050 reason
= webkit_web_navigation_action_get_reason(na
);
5051 if (reason
== WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED
) {
5052 set_normal_tab_meaning(t
);
5053 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1)
5055 webkit_web_policy_decision_use(pd
);
5056 return (TRUE
); /* we made the decision */
5063 webview_rrs_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, WebKitWebResource
*res
,
5064 WebKitNetworkRequest
*request
, WebKitNetworkResponse
*response
,
5067 SoupMessage
*msg
= NULL
;
5068 SoupURI
*uri
= NULL
;
5069 struct http_accept ha_find
, *ha
= NULL
;
5070 struct user_agent ua_find
, *ua
= NULL
;
5071 struct domain_id di_find
, *di
= NULL
;
5074 msg
= webkit_network_request_get_message(request
);
5077 uri
= soup_message_get_uri(msg
);
5080 uri_s
= soup_uri_to_string(uri
, FALSE
);
5082 if (strcmp(uri
->scheme
, SOUP_URI_SCHEME_HTTP
) == 0) {
5083 if (strict_transport_check(uri
->host
) ||
5084 force_https_check(uri_s
)) {
5085 DNPRINTF(XT_D_NAV
, "webview_rrs_cb: force https for %s\n",
5087 soup_uri_set_scheme(uri
, SOUP_URI_SCHEME_HTTPS
);
5092 soup_message_headers_append(msg
->request_headers
, "DNT", "1");
5095 * Check if resources on this domain have been loaded before. If
5096 * not, add the current tab's http-accept and user-agent id's to a
5097 * new domain_id and insert into the RB tree. Use these http headers
5098 * for all resources loaded from this domain for the lifetime of the
5101 if ((di_find
.domain
= uri
->host
) == NULL
)
5103 if ((di
= RB_FIND(domain_id_list
, &di_list
, &di_find
)) == NULL
) {
5104 di
= g_malloc(sizeof *di
);
5105 di
->domain
= g_strdup(uri
->host
);
5106 di
->ua_id
= t
->user_agent_id
++;
5107 di
->ha_id
= t
->http_accept_id
++;
5108 RB_INSERT(domain_id_list
, &di_list
, di
);
5110 ua_find
.id
= t
->user_agent_id
;
5111 ua
= RB_FIND(user_agent_list
, &ua_list
, &ua_find
);
5113 t
->user_agent_id
= 0;
5115 ha_find
.id
= t
->http_accept_id
;
5116 ha
= RB_FIND(http_accept_list
, &ha_list
, &ha_find
);
5118 t
->http_accept_id
= 0;
5121 ua_find
.id
= di
->ua_id
;
5122 ua
= RB_FIND(user_agent_list
, &ua_list
, &ua_find
);
5123 ha_find
.id
= di
->ha_id
;
5124 ha
= RB_FIND(http_accept_list
, &ha_list
, &ha_find
);
5127 soup_message_headers_replace(msg
->request_headers
,
5128 "User-Agent", ua
->value
);
5130 soup_message_headers_replace(msg
->request_headers
,
5131 "Accept", ha
->value
);
5139 webview_cwv_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
5142 struct wl_entry
*w
= NULL
;
5144 WebKitWebView
*webview
= NULL
;
5147 DNPRINTF(XT_D_NAV
, "webview_cwv_cb: %s\n",
5148 webkit_web_view_get_uri(wv
));
5151 /* open in current tab */
5153 } else if (enable_scripts
== 0 && enable_js_whitelist
== 1) {
5154 uri
= webkit_web_view_get_uri(wv
);
5155 if (uri
&& (w
= wl_find_uri(uri
, &js_wl
)) == NULL
)
5158 if (t
->ctrl_click
) {
5159 x
= ctrl_click_focus
;
5162 tt
= create_new_tab(NULL
, NULL
, x
, -1);
5164 } else if (enable_scripts
== 1) {
5165 if (t
->ctrl_click
) {
5166 x
= ctrl_click_focus
;
5169 tt
= create_new_tab(NULL
, NULL
, x
, -1);
5177 webview_closewv_cb(WebKitWebView
*wv
, struct tab
*t
)
5180 struct wl_entry
*w
= NULL
;
5182 DNPRINTF(XT_D_NAV
, "webview_close_cb: %d\n", t
->tab_id
);
5184 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
5185 uri
= webkit_web_view_get_uri(wv
);
5186 if (uri
&& (w
= wl_find_uri(uri
, &js_wl
)) == NULL
)
5190 } else if (enable_scripts
== 1)
5197 webview_event_cb(GtkWidget
*w
, GdkEventButton
*e
, struct tab
*t
)
5199 /* we can not eat the event without throwing gtk off so defer it */
5201 /* catch middle click */
5202 if (e
->type
== GDK_BUTTON_RELEASE
&& e
->button
== 2) {
5207 /* catch ctrl click */
5208 if (e
->type
== GDK_BUTTON_RELEASE
&&
5209 CLEAN(e
->state
) == GDK_CONTROL_MASK
)
5214 return (XT_CB_PASSTHROUGH
);
5218 run_mimehandler(struct tab
*t
, char *mime_type
, WebKitNetworkRequest
*request
)
5220 struct mime_type
*m
;
5222 GError
*gerr
= NULL
;
5224 m
= find_mime_type(mime_type
);
5230 sv
[0] = m
->mt_action
;
5231 sv
[1] = (char *)webkit_network_request_get_uri(request
);
5234 /* ignore donothing from example config */
5235 if (m
->mt_action
&& !strcmp(m
->mt_action
, "donothing"))
5238 if (!g_spawn_async(NULL
, sv
, NULL
, G_SPAWN_SEARCH_PATH
, NULL
, NULL
,
5240 show_oops(t
, "%s: could not spawn process (%s)", __func__
,
5241 gerr
? gerr
->message
: "N/A");
5246 get_mime_type(const char *uri
)
5251 char *mime_type
= NULL
;
5255 show_oops(NULL
, "%s: invalid parameters", __func__
);
5259 if (g_str_has_prefix(uri
, "file://"))
5260 file
= g_filename_from_uri(uri
, NULL
, NULL
);
5262 file
= g_strdup(uri
);
5267 gf
= g_file_new_for_path(file
);
5268 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
5270 if ((m
= g_file_info_get_content_type(fi
)) != NULL
)
5271 mime_type
= g_strdup(m
);
5280 run_download_mimehandler(char *mime_type
, char *file
)
5282 struct mime_type
*m
;
5285 m
= find_mime_type(mime_type
);
5289 sv
[0] = m
->mt_action
;
5292 if (!g_spawn_async(NULL
, sv
, NULL
, G_SPAWN_SEARCH_PATH
, NULL
, NULL
,
5294 show_oops(NULL
, "%s: could not spawn process: %s %s", __func__
,
5302 download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
5305 WebKitDownloadStatus status
;
5310 if (download
== NULL
)
5312 status
= webkit_download_get_status(download
);
5313 if (status
!= WEBKIT_DOWNLOAD_STATUS_FINISHED
)
5316 if (download_notifications
)
5317 show_oops(NULL
, "Download of '%s' finished",
5318 basename((char *)webkit_download_get_destination_uri(download
)));
5319 uri
= webkit_download_get_destination_uri(download
);
5322 mime
= get_mime_type(uri
);
5326 if (g_str_has_prefix(uri
, "file://"))
5327 file
= g_filename_from_uri(uri
, NULL
, NULL
);
5329 file
= g_strdup(uri
);
5334 run_download_mimehandler((char *)mime
, file
);
5341 webview_mimetype_cb(WebKitWebView
*wv
, WebKitWebFrame
*frame
,
5342 WebKitNetworkRequest
*request
, char *mime_type
,
5343 WebKitWebPolicyDecision
*decision
, struct tab
*t
)
5346 show_oops(NULL
, "webview_mimetype_cb invalid parameters");
5350 DNPRINTF(XT_D_DOWNLOAD
, "webview_mimetype_cb: tab %d mime %s\n",
5351 t
->tab_id
, mime_type
);
5353 if (run_mimehandler(t
, mime_type
, request
) == 0) {
5354 webkit_web_policy_decision_ignore(decision
);
5359 if (webkit_web_view_can_show_mime_type(wv
, mime_type
) == FALSE
) {
5360 webkit_web_policy_decision_download(decision
);
5368 download_start(struct tab
*t
, struct download
*d
, int flag
)
5370 WebKitNetworkRequest
*req
;
5372 const gchar
*suggested_name
;
5373 gchar
*filename
= NULL
;
5379 if (d
== NULL
|| t
== NULL
) {
5380 show_oops(NULL
, "%s invalid parameters", __func__
);
5384 suggested_name
= webkit_download_get_suggested_filename(d
->download
);
5385 if (suggested_name
== NULL
)
5386 return (FALSE
); /* abort download */
5397 filename
= g_strdup_printf("%d%s", i
, suggested_name
);
5400 /* XXX using urls doesn't work properly in windows? */
5401 uri
= g_strdup_printf("%s\\%s", download_dir
, i
?
5402 filename
: suggested_name
);
5404 path
= g_strdup_printf("%s" PS
"%s", download_dir
, i
?
5405 filename
: suggested_name
);
5406 if ((uri
= g_filename_to_uri(path
, NULL
, NULL
)) == NULL
)
5411 } while (!stat(uri
, &sb
));
5413 } while (!stat(path
, &sb
));
5416 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d filename %s "
5417 "local %s\n", __func__
, t
->tab_id
, filename
, uri
);
5419 /* if we're restarting the download, or starting
5420 * it after doing something else, we need to recreate
5421 * the download request.
5423 if (flag
== XT_DL_RESTART
) {
5424 req
= webkit_network_request_new(webkit_download_get_uri(d
->download
));
5425 webkit_download_cancel(d
->download
);
5426 g_object_unref(d
->download
);
5427 d
->download
= webkit_download_new(req
);
5430 webkit_download_set_destination_uri(d
->download
, uri
);
5432 if (webkit_download_get_status(d
->download
) ==
5433 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
5434 show_oops(t
, "%s: download failed to start", __func__
);
5436 show_oops(t
, "Download Failed");
5438 /* connect "download first" mime handler */
5439 g_signal_connect(G_OBJECT(d
->download
), "notify::status",
5440 G_CALLBACK(download_status_changed_cb
), NULL
);
5442 /* get from history */
5443 g_object_ref(d
->download
);
5444 show_oops(t
, "Download of '%s' started...",
5445 basename((char *)webkit_download_get_destination_uri(d
->download
)));
5448 if (flag
!= XT_DL_START
)
5449 webkit_download_start(d
->download
);
5451 DNPRINTF(XT_D_DOWNLOAD
, "download status : %d",
5452 webkit_download_get_status(d
->download
));
5454 /* sync other download manager tabs */
5455 update_download_tabs(NULL
);
5468 download_ask_cb(struct tab
*t
, GdkEventKey
*e
, gpointer data
)
5470 struct download
*d
= data
;
5474 t
->mode_cb_data
= NULL
;
5477 e
->keyval
= GDK_Escape
;
5478 return (XT_CB_PASSTHROUGH
);
5481 DPRINTF("download_ask_cb: User pressed %c\n", e
->keyval
);
5482 if (e
->keyval
== 'y' || e
->keyval
== 'Y' || e
->keyval
== GDK_Return
)
5483 /* We need to do a RESTART, because we're not calling from
5484 * webview_download_cb
5486 download_start(t
, d
, XT_DL_RESTART
);
5488 /* for all other keyvals, we just let the download be */
5489 e
->keyval
= GDK_Escape
;
5490 return (XT_CB_HANDLED
);
5494 download_ask(struct tab
*t
, struct download
*d
)
5496 const gchar
*suggested_name
;
5498 suggested_name
= webkit_download_get_suggested_filename(d
->download
);
5499 if (suggested_name
== NULL
)
5500 return (FALSE
); /* abort download */
5502 show_oops(t
, "download file %s [y/n] ?", suggested_name
);
5503 t
->mode_cb
= download_ask_cb
;
5504 t
->mode_cb_data
= d
;
5510 webview_download_cb(WebKitWebView
*wv
, WebKitDownload
*wk_download
,
5513 const gchar
*suggested_name
;
5514 struct download
*download_entry
;
5517 if (wk_download
== NULL
|| t
== NULL
) {
5518 show_oops(NULL
, "%s invalid parameters", __func__
);
5522 suggested_name
= webkit_download_get_suggested_filename(wk_download
);
5523 if (suggested_name
== NULL
)
5524 return (FALSE
); /* abort download */
5526 download_entry
= g_malloc(sizeof(struct download
));
5527 download_entry
->download
= wk_download
;
5528 download_entry
->tab
= t
;
5529 download_entry
->id
= next_download_id
++;
5530 RB_INSERT(download_list
, &downloads
, download_entry
);
5531 t
->download_requested
= 1;
5533 if (download_mode
== XT_DM_START
)
5534 ret
= download_start(t
, download_entry
, XT_DL_START
);
5535 else if (download_mode
== XT_DM_ASK
)
5536 ret
= download_ask(t
, download_entry
);
5537 else if (download_mode
== XT_DM_ADD
)
5538 show_oops(t
, "added %s to download manager",
5541 /* sync other download manager tabs */
5542 update_download_tabs(NULL
);
5545 * NOTE: never redirect/render the current tab before this
5546 * function returns. This will cause the download to never start.
5548 return (ret
); /* start download */
5552 webview_hover_cb(WebKitWebView
*wv
, gchar
*title
, gchar
*uri
, struct tab
*t
)
5554 DNPRINTF(XT_D_KEY
, "webview_hover_cb: %s %s\n", title
, uri
);
5557 show_oops(NULL
, "webview_hover_cb");
5562 set_status(t
, "Link: %s", uri
);
5564 if (statusbar_style
== XT_STATUSBAR_URL
) {
5565 const gchar
*page_uri
;
5567 if ((page_uri
= get_uri(t
)) != NULL
)
5568 set_status(t
, "%s", page_uri
);
5570 set_status(t
, "%s", (char *)get_title(t
, FALSE
));
5575 mark(struct tab
*t
, struct karg
*arg
)
5582 if ((index
= marktoindex(mark
)) == -1)
5585 if (arg
->i
== XT_MARK_SET
)
5586 t
->mark
[index
] = gtk_adjustment_get_value(t
->adjust_v
);
5587 else if (arg
->i
== XT_MARK_GOTO
) {
5588 if (t
->mark
[index
] == XT_INVALID_MARK
) {
5589 show_oops(t
, "mark '%c' does not exist", mark
);
5592 /* XXX t->mark[index] can be bigger than the maximum if ajax or
5593 something changes the document size */
5594 pos
= gtk_adjustment_get_value(t
->adjust_v
);
5595 gtk_adjustment_set_value(t
->adjust_v
, t
->mark
[index
]);
5596 t
->mark
[marktoindex('\'')] = pos
;
5603 marks_clear(struct tab
*t
)
5607 for (i
= 0; i
< LENGTH(t
->mark
); i
++)
5608 t
->mark
[i
] = XT_INVALID_MARK
;
5614 char file
[PATH_MAX
];
5615 char *line
= NULL
, *p
;
5620 snprintf(file
, sizeof file
, "%s" PS
"%s", work_dir
, XT_QMARKS_FILE
);
5621 if ((f
= fopen(file
, "r+")) == NULL
) {
5622 show_oops(NULL
, "Can't open quickmarks file: %s", strerror(errno
));
5626 for (i
= 1; ; i
++) {
5627 if ((line
= fparseln(f
, &linelen
, NULL
, NULL
, 0)) == NULL
)
5629 if (strlen(line
) == 0 || line
[0] == '#') {
5635 p
= strtok(line
, " \t");
5637 if (p
== NULL
|| strlen(p
) != 1 ||
5638 (index
= qmarktoindex(*p
)) == -1) {
5639 warnx("corrupt quickmarks file, line %d", i
);
5643 p
= strtok(NULL
, " \t");
5644 if (qmarks
[index
] != NULL
)
5645 g_free(qmarks
[index
]);
5646 qmarks
[index
] = g_strdup(p
);
5657 char file
[PATH_MAX
];
5661 snprintf(file
, sizeof file
, "%s" PS
"%s", work_dir
, XT_QMARKS_FILE
);
5662 if ((f
= fopen(file
, "r+")) == NULL
) {
5663 show_oops(NULL
, "Can't open quickmarks file: %s", strerror(errno
));
5667 for (i
= 0; i
< XT_NOQMARKS
; i
++)
5668 if (qmarks
[i
] != NULL
)
5669 fprintf(f
, "%c %s\n", indextoqmark(i
), qmarks
[i
]);
5677 qmark(struct tab
*t
, struct karg
*arg
)
5682 mark
= arg
->s
[strlen(arg
->s
)-1];
5683 index
= qmarktoindex(mark
);
5689 if (qmarks
[index
] != NULL
) {
5690 g_free(qmarks
[index
]);
5691 qmarks
[index
] = NULL
;
5694 qmarks_load(); /* sync if multiple instances */
5695 qmarks
[index
] = g_strdup(get_uri(t
));
5699 if (qmarks
[index
] != NULL
)
5700 load_uri(t
, qmarks
[index
]);
5702 show_oops(t
, "quickmark \"%c\" does not exist",
5708 if (qmarks
[index
] != NULL
)
5709 create_new_tab(qmarks
[index
], NULL
, 1, -1);
5711 show_oops(t
, "quickmark \"%c\" does not exist",
5722 go_up(struct tab
*t
, struct karg
*args
)
5730 if (args
->i
== XT_GO_UP_ROOT
)
5731 levels
= XT_GO_UP_ROOT
;
5732 else if ((levels
= atoi(args
->s
)) == 0)
5735 uri
= g_strdup(get_uri(t
));
5739 if ((tmp
= strstr(uri
, XT_PROTO_DELIM
)) == NULL
)
5742 tmp
+= strlen(XT_PROTO_DELIM
);
5744 /* it makes no sense to strip the last slash from ".../dir/", skip it */
5745 lastidx
= strlen(tmp
) - 1;
5747 if (tmp
[lastidx
] == '/')
5748 tmp
[lastidx
] = '\0';
5752 p
= strrchr(tmp
, '/');
5753 if (p
== tmp
) { /* Are we at the root of a file://-path? */
5756 } else if (p
!= NULL
)
5769 gototab(struct tab
*t
, struct karg
*args
)
5772 struct karg arg
= {0, NULL
, -1};
5774 tab
= atoi(args
->s
);
5777 arg
.i
= XT_TAB_NEXT
;
5789 zoom_amount(struct tab
*t
, struct karg
*arg
)
5791 struct karg narg
= {0, NULL
, -1};
5793 narg
.i
= atoi(arg
->s
);
5794 resizetab(t
, &narg
);
5800 flip_colon(struct tab
*t
, struct karg
*arg
)
5802 struct karg narg
= {0, NULL
, -1};
5805 if (t
== NULL
|| arg
== NULL
)
5808 p
= strstr(arg
->s
, ":");
5820 /* buffer commands receive the regex that triggered them in arg.s */
5821 char bcmd
[XT_BUFCMD_SZ
];
5825 #define XT_PRE_NO (0)
5826 #define XT_PRE_YES (1)
5827 #define XT_PRE_MAYBE (2)
5829 int (*func
)(struct tab
*, struct karg
*);
5833 { "^[0-9]*gu$", XT_PRE_MAYBE
, "gu", go_up
, 0 },
5834 { "^gU$", XT_PRE_NO
, "gU", go_up
, XT_GO_UP_ROOT
},
5835 { "^gg$", XT_PRE_NO
, "gg", move
, XT_MOVE_TOP
},
5836 { "^gG$", XT_PRE_NO
, "gG", move
, XT_MOVE_BOTTOM
},
5837 { "^[0-9]+%$", XT_PRE_YES
, "%", move
, XT_MOVE_PERCENT
},
5838 { "^zz$", XT_PRE_NO
, "zz", move
, XT_MOVE_CENTER
},
5839 { "^gh$", XT_PRE_NO
, "gh", go_home
, 0 },
5840 { "^m[a-zA-Z0-9]$", XT_PRE_NO
, "m", mark
, XT_MARK_SET
},
5841 { "^['][a-zA-Z0-9']$", XT_PRE_NO
, "'", mark
, XT_MARK_GOTO
},
5842 { "^[0-9]+t$", XT_PRE_YES
, "t", gototab
, 0 },
5843 { "^g0$", XT_PRE_YES
, "g0", movetab
, XT_TAB_FIRST
},
5844 { "^g[$]$", XT_PRE_YES
, "g$", movetab
, XT_TAB_LAST
},
5845 { "^[0-9]*gt$", XT_PRE_YES
, "t", movetab
, XT_TAB_NEXT
},
5846 { "^[0-9]*gT$", XT_PRE_YES
, "T", movetab
, XT_TAB_PREV
},
5847 { "^M[a-zA-Z0-9]$", XT_PRE_NO
, "M", qmark
, XT_QMARK_SET
},
5848 { "^go[a-zA-Z0-9]$", XT_PRE_NO
, "go", qmark
, XT_QMARK_OPEN
},
5849 { "^gn[a-zA-Z0-9]$", XT_PRE_NO
, "gn", qmark
, XT_QMARK_TAB
},
5850 { "^ZR$", XT_PRE_NO
, "ZR", restart
, 0 },
5851 { "^ZZ$", XT_PRE_NO
, "ZZ", quit
, 0 },
5852 { "^zi$", XT_PRE_NO
, "zi", resizetab
, XT_ZOOM_IN
},
5853 { "^zo$", XT_PRE_NO
, "zo", resizetab
, XT_ZOOM_OUT
},
5854 { "^z0$", XT_PRE_NO
, "z0", resizetab
, XT_ZOOM_NORMAL
},
5855 { "^[0-9]+Z$", XT_PRE_YES
, "Z", zoom_amount
, 0 },
5856 { "^[0-9]+:$", XT_PRE_YES
, ":", flip_colon
, 0 },
5860 buffercmd_init(void)
5864 for (i
= 0; i
< LENGTH(buffercmds
); i
++)
5865 if (regcomp(&buffercmds
[i
].cregex
, buffercmds
[i
].regex
,
5866 REG_EXTENDED
| REG_NOSUB
))
5867 startpage_add("invalid buffercmd regex %s",
5868 buffercmds
[i
].regex
);
5872 buffercmd_abort(struct tab
*t
)
5879 DNPRINTF(XT_D_BUFFERCMD
, "%s: clearing buffer\n", __func__
);
5881 for (i
= 0; i
< LENGTH(bcmd
); i
++)
5884 cmd_prefix
= 0; /* clear prefix for non-buffer commands */
5885 gtk_label_set_text(GTK_LABEL(t
->sbe
.buffercmd
), bcmd
);
5889 buffercmd_execute(struct tab
*t
, struct buffercmd
*cmd
)
5891 struct karg arg
= {0, NULL
, -1};
5894 arg
.s
= g_strdup(bcmd
);
5896 DNPRINTF(XT_D_BUFFERCMD
, "buffercmd_execute: buffer \"%s\" "
5897 "matches regex \"%s\", executing\n", bcmd
, cmd
->regex
);
5907 buffercmd_addkey(struct tab
*t
, guint keyval
)
5910 char s
[XT_BUFCMD_SZ
];
5912 if (gtk_widget_get_visible(GTK_WIDGET(t
->buffers
))) {
5914 return (XT_CB_PASSTHROUGH
);
5917 if (keyval
== GDK_Escape
) {
5919 return (XT_CB_HANDLED
);
5922 /* key with modifier or non-ascii character */
5923 if (!isascii(keyval
)) {
5925 * XXX this looks wrong but fixes some sites like
5926 * http://www.seslisozluk.com/
5927 * that eat a shift or ctrl and end putting default focus in js
5928 * instead of ignoring the keystroke
5929 * so instead of return (XT_CB_PASSTHROUGH); eat the key
5931 return (XT_CB_HANDLED
);
5934 DNPRINTF(XT_D_BUFFERCMD
, "buffercmd_addkey: adding key \"%c\" "
5935 "to buffer \"%s\"\n", keyval
, bcmd
);
5937 for (i
= 0; i
< LENGTH(bcmd
); i
++)
5938 if (bcmd
[i
] == '\0') {
5943 /* buffer full, ignore input */
5944 if (i
>= LENGTH(bcmd
) -1) {
5945 DNPRINTF(XT_D_BUFFERCMD
, "buffercmd_addkey: buffer full\n");
5947 return (XT_CB_HANDLED
);
5950 gtk_label_set_text(GTK_LABEL(t
->sbe
.buffercmd
), bcmd
);
5952 /* find exact match */
5953 for (i
= 0; i
< LENGTH(buffercmds
); i
++)
5954 if (regexec(&buffercmds
[i
].cregex
, bcmd
,
5955 (size_t) 0, NULL
, 0) == 0) {
5956 buffercmd_execute(t
, &buffercmds
[i
]);
5960 /* find non exact matches to see if we need to abort ot not */
5961 for (i
= 0, match
= 0; i
< LENGTH(buffercmds
); i
++) {
5962 DNPRINTF(XT_D_BUFFERCMD
, "trying: %s\n", bcmd
);
5965 if (buffercmds
[i
].precount
== XT_PRE_MAYBE
) {
5966 if (isdigit(bcmd
[0])) {
5967 if (sscanf(bcmd
, "%d%s", &c
, s
) == 0)
5971 if (sscanf(bcmd
, "%s", s
) == 0)
5974 } else if (buffercmds
[i
].precount
== XT_PRE_YES
) {
5975 if (sscanf(bcmd
, "%d%s", &c
, s
) == 0)
5978 if (sscanf(bcmd
, "%s", s
) == 0)
5981 if (c
== -1 && buffercmds
[i
].precount
)
5983 if (!strncmp(s
, buffercmds
[i
].cmd
, strlen(s
)))
5986 DNPRINTF(XT_D_BUFFERCMD
, "got[%d] %d <%s>: %d %s\n",
5987 i
, match
, buffercmds
[i
].cmd
, c
, s
);
5990 DNPRINTF(XT_D_BUFFERCMD
, "aborting: %s\n", bcmd
);
5995 return (XT_CB_HANDLED
);
5999 * XXX we were seeing a bunch of focus issues with the toplevel
6000 * main_window losing its is-active and has-toplevel-focus properties.
6001 * This is the most correct and portable solution we could come up with
6002 * without relying on calling internal GTK functions (which we
6003 * couldn't link to in Linux).
6005 #if GTK_CHECK_VERSION(3, 0, 0)
6007 fake_focus_in(GtkWidget
*w
)
6009 if (fevent
== NULL
) {
6010 fevent
= gdk_event_new(GDK_FOCUS_CHANGE
);
6011 fevent
->focus_change
.window
=
6012 gtk_widget_get_window(main_window
);
6013 fevent
->focus_change
.type
= GDK_FOCUS_CHANGE
;
6014 fevent
->focus_change
.in
= TRUE
;
6016 gtk_widget_send_focus_change(main_window
, fevent
);
6021 handle_keypress(struct tab
*t
, GdkEventKey
*e
, int entry
)
6024 struct key_binding
*k
;
6027 * This sometimes gets randomly unset for whatever reason in GTK3.
6028 * If we're handling a keypress, the main window's is-active propery
6029 * *must* be true, or else many things will break.
6031 #if GTK_CHECK_VERSION(3, 0, 0)
6032 fake_focus_in(main_window
);
6035 /* handle keybindings if buffercmd is empty.
6036 if not empty, allow commands like C-n */
6037 if (bcmd
[0] == '\0' || ((e
->state
& (CTRL
| MOD1
)) != 0))
6038 TAILQ_FOREACH(k
, &kbl
, entry
)
6039 if (e
->keyval
== k
->key
6040 && (entry
? k
->use_in_entry
: 1)) {
6041 /* when we are edditing eat ctrl/mod keys */
6042 if (edit_mode
== XT_EM_VI
&&
6043 t
->mode
== XT_MODE_INSERT
&&
6044 (e
->state
& CTRL
|| e
->state
& MOD1
))
6045 return (XT_CB_PASSTHROUGH
);
6048 if ((e
->state
& (CTRL
| MOD1
)) == 0)
6050 } else if ((e
->state
& k
->mask
) == k
->mask
) {
6055 if (!entry
&& ((e
->state
& (CTRL
| MOD1
)) == 0))
6056 return (buffercmd_addkey(t
, e
->keyval
));
6058 return (XT_CB_PASSTHROUGH
);
6061 if (k
->cmd
[0] == ':') {
6063 args
.s
= &k
->cmd
[1];
6064 return (command(t
, &args
));
6066 return (cmd_execute(t
, k
->cmd
));
6070 wv_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6074 /* don't use w directly; use t->whatever instead */
6077 show_oops(NULL
, "wv_keypress_cb");
6078 return (XT_CB_PASSTHROUGH
);
6084 return (t
->mode_cb(t
, e
, t
->mode_cb_data
));
6086 DNPRINTF(XT_D_KEY
, "wv_keypress_cb: mode %d keyval 0x%x mask "
6087 "0x%x tab %d\n", t
->mode
, e
->keyval
, e
->state
, t
->tab_id
);
6089 /* Hide buffers, if they are visible, with escape. */
6090 if (gtk_widget_get_visible(GTK_WIDGET(t
->buffers
)) &&
6091 CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Escape
) {
6092 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
6094 return (XT_CB_HANDLED
);
6097 if ((CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Tab
) ||
6098 (CLEAN(e
->state
) == SHFT
&& e
->keyval
== GDK_Tab
))
6099 /* something focussy is about to happen */
6100 return (XT_CB_PASSTHROUGH
);
6102 /* check if we are some sort of text input thing in the dom */
6103 input_check_mode(t
);
6105 if (t
->mode
== XT_MODE_PASSTHROUGH
) {
6106 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Escape
)
6107 t
->mode
= XT_MODE_COMMAND
;
6108 return (XT_CB_PASSTHROUGH
);
6109 } else if (t
->mode
== XT_MODE_COMMAND
|| t
->mode
== XT_MODE_HINT
) {
6111 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6112 if (CLEAN(e
->state
) == 0 && isdigit(s
[0]))
6113 cmd_prefix
= 10 * cmd_prefix
+ atoi(s
);
6114 return (handle_keypress(t
, e
, 0));
6117 return (handle_keypress(t
, e
, 1));
6121 return (XT_CB_PASSTHROUGH
);
6125 hint_continue(struct tab
*t
)
6127 const gchar
*c
= gtk_entry_get_text(GTK_ENTRY(t
->cmd
));
6129 const gchar
*errstr
= NULL
;
6133 if (!(c
[0] == '.' || c
[0] == ','))
6135 if (strlen(c
) == 1) {
6136 /* XXX should not happen */
6141 if (isdigit(c
[1])) {
6143 i
= strtonum(&c
[1], 1, 4096, &errstr
);
6145 show_oops(t
, "invalid numerical hint %s", &c
[1]);
6148 s
= g_strdup_printf("hints.updateHints(%d);", i
);
6152 /* alphanumeric input */
6153 s
= g_strdup_printf("hints.createHints('%s', '%c');",
6154 &c
[1], c
[0] == '.' ? 'f' : 'F');
6165 search_continue(struct tab
*t
)
6167 const gchar
*c
= gtk_entry_get_text(GTK_ENTRY(t
->cmd
));
6168 gboolean rv
= FALSE
;
6170 if (c
[0] == ':' || c
[0] == '.' || c
[0] == ',')
6172 if (strlen(c
) == 1) {
6173 webkit_web_view_unmark_text_matches(t
->wv
);
6178 t
->search_forward
= TRUE
;
6179 else if (c
[0] == '?')
6180 t
->search_forward
= FALSE
;
6190 search_cb(struct tab
*t
)
6192 const gchar
*c
= gtk_entry_get_text(GTK_ENTRY(t
->cmd
));
6193 #if !GTK_CHECK_VERSION(3, 0, 0)
6197 if (search_continue(t
) == FALSE
)
6201 if (webkit_web_view_search_text(t
->wv
, &c
[1], FALSE
, t
->search_forward
,
6203 /* not found, mark red */
6204 #if GTK_CHECK_VERSION(3, 0, 0)
6205 gtk_widget_set_name(t
->cmd
, XT_CSS_RED
);
6207 gdk_color_parse(XT_COLOR_RED
, &color
);
6208 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6210 /* unmark and remove selection */
6211 webkit_web_view_unmark_text_matches(t
->wv
);
6212 /* my kingdom for a way to unselect text in webview */
6214 /* found, highlight all */
6215 webkit_web_view_unmark_text_matches(t
->wv
);
6216 webkit_web_view_mark_text_matches(t
->wv
, &c
[1], FALSE
, 0);
6217 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
6218 #if GTK_CHECK_VERSION(3, 0, 0)
6219 gtk_widget_set_name(t
->cmd
, XT_CSS_NORMAL
);
6221 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
,
6222 &t
->default_style
->base
[GTK_STATE_NORMAL
]);
6231 cmd_keyrelease_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6233 const gchar
*c
= gtk_entry_get_text(w
);
6236 show_oops(NULL
, "cmd_keyrelease_cb invalid parameters");
6237 return (XT_CB_PASSTHROUGH
);
6240 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x tab %d\n",
6241 e
->keyval
, e
->state
, t
->tab_id
);
6244 if (!(e
->keyval
== GDK_Tab
|| e
->keyval
== GDK_ISO_Left_Tab
)) {
6245 if (hint_continue(t
) == FALSE
)
6250 if (search_continue(t
) == FALSE
)
6253 /* if search length is > 4 then no longer play timeout games */
6254 if (strlen(c
) > 4) {
6256 g_source_remove(t
->search_id
);
6263 /* reestablish a new timer if the user types fast */
6265 g_source_remove(t
->search_id
);
6266 t
->search_id
= g_timeout_add(250, (GSourceFunc
)search_cb
, (gpointer
)t
);
6269 return (XT_CB_PASSTHROUGH
);
6273 match_uri(const gchar
*uri
, const gchar
*key
) {
6276 gboolean match
= FALSE
;
6280 if (!strncmp(key
, uri
, len
))
6283 voffset
= strstr(uri
, "/") + 2;
6284 if (!strncmp(key
, voffset
, len
))
6286 else if (g_str_has_prefix(voffset
, "www.")) {
6287 voffset
= voffset
+ strlen("www.");
6288 if (!strncmp(key
, voffset
, len
))
6297 match_session(const gchar
*name
, const gchar
*key
) {
6300 sub
= strcasestr(name
, key
);
6306 cmd_getlist(int id
, char *key
)
6313 if (cmds
[id
].type
& XT_URLARG
) {
6314 RB_FOREACH_REVERSE(h
, history_list
, &hl
)
6315 if (match_uri(h
->uri
, key
)) {
6316 cmd_status
.list
[c
] = (char *)h
->uri
;
6322 } else if (cmds
[id
].type
& XT_SESSARG
) {
6323 TAILQ_FOREACH(s
, &sessions
, entry
)
6324 if (match_session(s
->name
, key
)) {
6325 cmd_status
.list
[c
] = (char *)s
->name
;
6331 } else if (cmds
[id
].type
& XT_SETARG
) {
6332 for (i
= 0; i
< get_settings_size(); i
++)
6333 if (!strncmp(key
, get_setting_name(i
),
6335 cmd_status
.list
[c
++] =
6336 get_setting_name(i
);
6342 dep
= (id
== -1) ? 0 : cmds
[id
].level
+ 1;
6344 for (i
= id
+ 1; i
< LENGTH(cmds
); i
++) {
6345 if (cmds
[i
].level
< dep
)
6347 if (cmds
[i
].level
== dep
&& !strncmp(key
, cmds
[i
].cmd
,
6348 strlen(key
)) && !isdigit(cmds
[i
].cmd
[0]))
6349 cmd_status
.list
[c
++] = cmds
[i
].cmd
;
6357 cmd_getnext(int dir
)
6359 cmd_status
.index
+= dir
;
6361 if (cmd_status
.index
< 0)
6362 cmd_status
.index
= cmd_status
.len
- 1;
6363 else if (cmd_status
.index
>= cmd_status
.len
)
6364 cmd_status
.index
= 0;
6366 return cmd_status
.list
[cmd_status
.index
];
6370 cmd_tokenize(char *s
, char *tokens
[])
6373 char *tok
, *last
= NULL
;
6374 size_t len
= strlen(s
);
6377 blank
= len
== 0 || (len
> 0 && s
[len
- 1] == ' ');
6378 for (tok
= strtok_r(s
, " ", &last
); tok
&& i
< 3;
6379 tok
= strtok_r(NULL
, " ", &last
), i
++)
6389 cmd_complete(struct tab
*t
, char *str
, int dir
)
6391 GtkEntry
*w
= GTK_ENTRY(t
->cmd
);
6392 int i
, j
, levels
, c
= 0, dep
= 0, parent
= -1;
6394 char *tok
, *match
, *s
= g_strdup(str
);
6396 char res
[XT_MAX_URL_LENGTH
+ 32] = ":";
6399 DNPRINTF(XT_D_CMD
, "%s: complete %s\n", __func__
, str
);
6402 for (i
= 0; isdigit(s
[i
]); i
++)
6405 for (; isspace(s
[i
]); i
++)
6410 levels
= cmd_tokenize(s
, tokens
);
6412 for (i
= 0; i
< levels
- 1; i
++) {
6415 for (j
= c
; j
< LENGTH(cmds
); j
++) {
6416 if (cmds
[j
].level
< dep
)
6418 if (cmds
[j
].level
== dep
&& !strncmp(tok
, cmds
[j
].cmd
,
6422 if (strlen(tok
) == strlen(cmds
[j
].cmd
)) {
6429 if (matchcount
== 1) {
6430 strlcat(res
, tok
, sizeof res
);
6431 strlcat(res
, " ", sizeof res
);
6441 if (cmd_status
.index
== -1)
6442 cmd_getlist(parent
, tokens
[i
]);
6444 if (cmd_status
.len
> 0) {
6445 match
= cmd_getnext(dir
);
6446 strlcat(res
, match
, sizeof res
);
6447 gtk_entry_set_text(w
, res
);
6448 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6455 parse_prefix_and_alias(const char *str
, int *prefix
)
6457 struct cmd_alias
*c
;
6458 char *s
= g_strdup(str
), *sc
;
6463 if (isdigit(s
[0])) {
6464 sscanf(s
, "%d", prefix
);
6465 while (isdigit(s
[0]) || isspace(s
[0]))
6469 TAILQ_FOREACH(c
, &cal
, entry
) {
6470 if (strncmp(s
, c
->alias
, strlen(c
->alias
)))
6473 if (strlen(s
) == strlen(c
->alias
)) {
6475 return (g_strdup(c
->cmd
));
6478 if (!isspace(s
[strlen(c
->alias
)]))
6481 s
= g_strdup_printf("%s %s", c
->cmd
, &s
[strlen(c
->alias
) + 1]);
6491 cmd_execute(struct tab
*t
, char *str
)
6493 struct cmd
*cmd
= NULL
;
6494 char *tok
, *last
= NULL
, *s
= str
;
6495 int j
= 0, len
, c
= 0, dep
= 0, matchcount
= 0;
6496 int prefix
= -1, rv
= XT_CB_PASSTHROUGH
;
6497 struct karg arg
= {0, NULL
, -1};
6499 s
= parse_prefix_and_alias(s
, &prefix
);
6501 for (tok
= strtok_r(s
, " ", &last
); tok
;
6502 tok
= strtok_r(NULL
, " ", &last
)) {
6504 for (j
= c
; j
< LENGTH(cmds
); j
++) {
6505 if (cmds
[j
].level
< dep
)
6507 len
= (tok
[strlen(tok
) - 1] == '!') ? strlen(tok
) - 1 :
6509 if (cmds
[j
].level
== dep
&&
6510 !strncmp(tok
, cmds
[j
].cmd
, len
)) {
6514 if (len
== strlen(cmds
[j
].cmd
)) {
6520 if (matchcount
== 1) {
6525 show_oops(t
, "Invalid command: %s", str
);
6531 show_oops(t
, "Empty command");
6537 arg
.precount
= prefix
;
6538 else if (cmd_prefix
> 0)
6539 arg
.precount
= cmd_prefix
;
6541 if (j
> 0 && !(cmd
->type
& XT_PREFIX
) && arg
.precount
> -1) {
6542 show_oops(t
, "No prefix allowed: %s", str
);
6546 arg
.s
= last
? g_strdup(last
) : g_strdup("");
6547 if (cmd
->type
& XT_INTARG
&& last
&& strlen(last
) > 0) {
6548 if (arg
.s
== NULL
) {
6549 show_oops(t
, "Invalid command");
6552 arg
.precount
= atoi(arg
.s
);
6553 if (arg
.precount
<= 0) {
6554 if (arg
.s
[0] == '0')
6555 show_oops(t
, "Zero count");
6557 show_oops(t
, "Trailing characters");
6562 DNPRINTF(XT_D_CMD
, "%s: prefix %d arg %s\n",
6563 __func__
, arg
.precount
, arg
.s
);
6579 save_runtime_setting(const char *name
, const char *val
)
6585 char file
[PATH_MAX
];
6586 char delim
[3] = { '\0', '\0', '\0' };
6587 char *line
, *lt
, *start
;
6588 char *contents
, *tmp
;
6590 if (runtime_settings
== NULL
|| strlen(runtime_settings
) == 0)
6593 snprintf(file
, sizeof file
, "%s" PS
"%s", work_dir
, runtime_settings
);
6594 if (stat(file
, &sb
) || (f
= fopen(file
, "r+")) == NULL
)
6596 lt
= g_strdup_printf("%s=%s", name
, val
);
6597 contents
= g_strdup("");
6599 line
= fparseln(f
, &linelen
, NULL
, delim
, 0);
6600 if (line
== NULL
|| linelen
== 0)
6603 start
= g_strdup_printf("%s=", name
);
6604 if (strstr(line
, start
) == NULL
)
6605 contents
= g_strdup_printf("%s%s\n", contents
, line
);
6608 contents
= g_strdup_printf("%s%s\n", contents
, lt
);
6617 contents
= g_strdup_printf("%s%s\n", contents
, lt
);
6620 if ((f
= freopen(file
, "w", f
)) == NULL
)
6633 entry_focus_cb(GtkWidget
*w
, GdkEvent e
, struct tab
*t
)
6636 * This sometimes gets randomly unset for whatever reason in GTK3,
6637 * causing a GtkEntry's text cursor becomes invisible. When we focus
6638 * a GtkEntry, be sure to manually reset the main window's is-active
6639 * property so the cursor is shown correctly.
6641 #if GTK_CHECK_VERSION(3, 0, 0)
6642 fake_focus_in(main_window
);
6644 return (XT_CB_PASSTHROUGH
);
6648 entry_key_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6651 show_oops(NULL
, "entry_key_cb invalid parameters");
6652 return (XT_CB_PASSTHROUGH
);
6655 DNPRINTF(XT_D_CMD
, "entry_key_cb: keyval 0x%x mask 0x%x tab %d\n",
6656 e
->keyval
, e
->state
, t
->tab_id
);
6660 if (e
->keyval
== GDK_Escape
) {
6661 /* don't use focus_webview(t) because we want to type :cmds */
6662 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
6665 return (handle_keypress(t
, e
, 1));
6668 struct command_entry
*
6669 history_prev(struct command_list
*l
, struct command_entry
*at
)
6672 at
= TAILQ_LAST(l
, command_list
);
6674 at
= TAILQ_PREV(at
, command_list
, entry
);
6676 at
= TAILQ_LAST(l
, command_list
);
6682 struct command_entry
*
6683 history_next(struct command_list
*l
, struct command_entry
*at
)
6686 at
= TAILQ_FIRST(l
);
6688 at
= TAILQ_NEXT(at
, entry
);
6690 at
= TAILQ_FIRST(l
);
6697 cmd_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6699 int rv
= XT_CB_HANDLED
;
6700 const gchar
*c
= gtk_entry_get_text(w
);
6704 show_oops(NULL
, "cmd_keypress_cb parameters");
6705 return (XT_CB_PASSTHROUGH
);
6708 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: keyval 0x%x mask 0x%x tab %d\n",
6709 e
->keyval
, e
->state
, t
->tab_id
);
6713 e
->keyval
= GDK_Escape
;
6714 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?' ||
6715 c
[0] == '.' || c
[0] == ','))
6716 e
->keyval
= GDK_Escape
;
6718 if (e
->keyval
!= GDK_Tab
&& e
->keyval
!= GDK_Shift_L
&&
6719 e
->keyval
!= GDK_ISO_Left_Tab
)
6720 cmd_status
.index
= -1;
6722 switch (e
->keyval
) {
6725 cmd_complete(t
, (char *)&c
[1], 1);
6726 else if (c
[0] == '.' || c
[0] == ',')
6727 run_script(t
, "hints.focusNextHint();");
6729 case GDK_ISO_Left_Tab
:
6731 cmd_complete(t
, (char *)&c
[1], -1);
6732 else if (c
[0] == '.' || c
[0] == ',')
6733 run_script(t
, "hints.focusPreviousHint();");
6737 if ((search_at
= history_next(&shl
, search_at
))) {
6738 search_at
->line
[0] = c
[0];
6739 gtk_entry_set_text(w
, search_at
->line
);
6740 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6742 } else if (c
[0] == '/') {
6743 if ((search_at
= history_prev(&shl
, search_at
))) {
6744 search_at
->line
[0] = c
[0];
6745 gtk_entry_set_text(w
, search_at
->line
);
6746 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6748 } else if (c
[0] == ':') {
6749 if ((history_at
= history_prev(&chl
, history_at
))) {
6750 history_at
->line
[0] = c
[0];
6751 gtk_entry_set_text(w
, history_at
->line
);
6752 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6758 if ((search_at
= history_next(&shl
, search_at
))) {
6759 search_at
->line
[0] = c
[0];
6760 gtk_entry_set_text(w
, search_at
->line
);
6761 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6763 } else if (c
[0] == '?') {
6764 if ((search_at
= history_prev(&shl
, search_at
))) {
6765 search_at
->line
[0] = c
[0];
6766 gtk_entry_set_text(w
, search_at
->line
);
6767 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6769 } if (c
[0] == ':') {
6770 if ((history_at
= history_next(&chl
, history_at
))) {
6771 history_at
->line
[0] = c
[0];
6772 gtk_entry_set_text(w
, history_at
->line
);
6773 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6778 if (!(!strcmp(c
, ":") || !strcmp(c
, "/") || !strcmp(c
, "?") ||
6779 !strcmp(c
, ".") || !strcmp(c
, ","))) {
6780 /* see if we are doing hinting and reset it */
6781 if (c
[0] == '.' || c
[0] == ',') {
6782 /* recreate hints */
6783 s
= g_strdup_printf("hints.createHints('', "
6784 "'%c');", c
[0] == '.' ? 'f' : 'F');
6796 if (c
!= NULL
&& (c
[0] == '/' || c
[0] == '?'))
6797 webkit_web_view_unmark_text_matches(t
->wv
);
6799 /* no need to cancel hints */
6803 rv
= XT_CB_PASSTHROUGH
;
6809 wv_popup_activ_cb(GtkMenuItem
*menu
, struct tab
*t
)
6811 GtkAction
*a
= NULL
;
6812 GtkClipboard
*clipboard
, *primary
;
6813 const gchar
*name
, *uri
;
6815 a
= gtk_activatable_get_related_action(GTK_ACTIVATABLE(menu
));
6818 name
= gtk_action_get_name(a
);
6820 DNPRINTF(XT_D_CMD
, "wv_popup_activ_cb: tab %d action %s\n",
6824 * context-menu-action-3 copy link location
6825 * context-menu-action-7 copy image address
6826 * context-menu-action-2030 copy video link location
6830 if ((g_strcmp0(name
, "context-menu-action-3") == 0) ||
6831 (g_strcmp0(name
, "context-menu-action-7") == 0) ||
6832 (g_strcmp0(name
, "context-menu-action-2030") == 0)) {
6833 clipboard
= gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
);
6834 primary
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
6835 uri
= gtk_clipboard_wait_for_text(clipboard
);
6838 gtk_clipboard_set_text(primary
, uri
, -1);
6843 wv_popup_cb(WebKitWebView
*wview
, GtkMenu
*menu
, struct tab
*t
)
6847 DNPRINTF(XT_D_CMD
, "wv_popup_cb: tab %d\n", t
->tab_id
);
6849 items
= gtk_container_get_children(GTK_CONTAINER(menu
));
6850 for (l
= items
; l
; l
= l
->next
)
6851 g_signal_connect(l
->data
, "activate",
6852 G_CALLBACK(wv_popup_activ_cb
), t
);
6857 cmd_popup_cb(GtkEntry
*entry
, GtkMenu
*menu
, struct tab
*t
)
6859 /* popup menu enabled */
6864 cmd_focusout_cb(GtkWidget
*w
, GdkEventFocus
*e
, struct tab
*t
)
6867 show_oops(NULL
, "cmd_focusout_cb invalid parameters");
6868 return (XT_CB_PASSTHROUGH
);
6871 DNPRINTF(XT_D_CMD
, "cmd_focusout_cb: tab %d popup %d\n",
6872 t
->tab_id
, t
->popup
);
6874 /* if popup is enabled don't lose focus */
6877 return (XT_CB_PASSTHROUGH
);
6884 return (XT_CB_PASSTHROUGH
);
6888 cmd_hide_cb(GtkWidget
*w
, struct tab
*t
)
6891 show_oops(NULL
, "%s: invalid parameters", __func__
);
6895 if (show_url
== 0 || t
->focus_wv
)
6898 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
6902 cmd_activate_cb(GtkEntry
*entry
, struct tab
*t
)
6905 const gchar
*c
= gtk_entry_get_text(entry
);
6908 show_oops(NULL
, "cmd_activate_cb invalid parameters");
6912 DNPRINTF(XT_D_CMD
, "cmd_activate_cb: tab %d %s\n", t
->tab_id
, c
);
6917 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?' ||
6918 c
[0] == '.' || c
[0] == ','))
6924 if (c
[0] == '/' || c
[0] == '?') {
6925 /* see if there is a timer pending */
6927 g_source_remove(t
->search_id
);
6932 if (t
->search_text
) {
6933 g_free(t
->search_text
);
6934 t
->search_text
= NULL
;
6937 t
->search_text
= g_strdup(s
);
6939 g_free(global_search
);
6940 global_search
= g_strdup(s
);
6941 t
->search_forward
= c
[0] == '/';
6943 history_add(&shl
, search_file
, s
, &search_history_count
);
6944 } else if (c
[0] == '.' || c
[0] == ',') {
6945 run_script(t
, "hints.fire();");
6946 /* XXX history for link following? */
6947 } else if (c
[0] == ':') {
6948 history_add(&chl
, command_file
, s
, &cmd_history_count
);
6949 /* can't call hide_cmd after cmd_execute */
6960 backward_cb(GtkWidget
*w
, struct tab
*t
)
6965 show_oops(NULL
, "backward_cb invalid parameters");
6969 DNPRINTF(XT_D_NAV
, "backward_cb: tab %d\n", t
->tab_id
);
6976 forward_cb(GtkWidget
*w
, struct tab
*t
)
6981 show_oops(NULL
, "forward_cb invalid parameters");
6985 DNPRINTF(XT_D_NAV
, "forward_cb: tab %d\n", t
->tab_id
);
6987 a
.i
= XT_NAV_FORWARD
;
6992 home_cb(GtkWidget
*w
, struct tab
*t
)
6995 show_oops(NULL
, "home_cb invalid parameters");
6999 DNPRINTF(XT_D_NAV
, "home_cb: tab %d\n", t
->tab_id
);
7005 stop_cb(GtkWidget
*w
, struct tab
*t
)
7007 WebKitWebFrame
*frame
;
7010 show_oops(NULL
, "stop_cb invalid parameters");
7014 DNPRINTF(XT_D_NAV
, "stop_cb: tab %d\n", t
->tab_id
);
7016 frame
= webkit_web_view_get_main_frame(t
->wv
);
7017 if (frame
== NULL
) {
7018 show_oops(t
, "stop_cb: no frame");
7022 webkit_web_frame_stop_loading(frame
);
7023 abort_favicon_download(t
);
7027 setup_webkit(struct tab
*t
)
7029 if (is_g_object_setting(G_OBJECT(t
->settings
), "enable-dns-prefetching"))
7030 g_object_set(G_OBJECT(t
->settings
), "enable-dns-prefetching",
7031 FALSE
, (char *)NULL
);
7033 warnx("webkit does not have \"enable-dns-prefetching\" property");
7034 g_object_set(G_OBJECT(t
->settings
),
7035 "enable-scripts", enable_scripts
, (char *)NULL
);
7036 g_object_set(G_OBJECT(t
->settings
),
7037 "enable-plugins", enable_plugins
, (char *)NULL
);
7038 g_object_set(G_OBJECT(t
->settings
),
7039 "javascript-can-open-windows-automatically", enable_scripts
,
7041 g_object_set(G_OBJECT(t
->settings
),
7042 "enable-html5-database", FALSE
, (char *)NULL
);
7043 g_object_set(G_OBJECT(t
->settings
),
7044 "enable-html5-local-storage", enable_localstorage
, (char *)NULL
);
7045 g_object_set(G_OBJECT(t
->settings
),
7046 "enable_spell_checking", enable_spell_checking
, (char *)NULL
);
7047 g_object_set(G_OBJECT(t
->settings
),
7048 "spell_checking_languages", spell_check_languages
, (char *)NULL
);
7049 g_object_set(G_OBJECT(t
->settings
),
7050 "enable-developer-extras", TRUE
, (char *)NULL
);
7051 g_object_set(G_OBJECT(t
->wv
),
7052 "full-content-zoom", TRUE
, (char *)NULL
);
7053 g_object_set(G_OBJECT(t
->settings
),
7054 "auto-load-images", auto_load_images
, (char *)NULL
);
7055 if (is_g_object_setting(G_OBJECT(t
->settings
),
7056 "enable-display-of-insecure-content"))
7057 g_object_set(G_OBJECT(t
->settings
),
7058 "enable-display-of-insecure-content",
7059 allow_insecure_content
, (char *)NULL
);
7060 if (is_g_object_setting(G_OBJECT(t
->settings
),
7061 "enable-running-of-insecure-content"))
7062 g_object_set(G_OBJECT(t
->settings
),
7063 "enable-running-of-insecure-content",
7064 allow_insecure_scripts
, (char *)NULL
);
7066 webkit_web_view_set_settings(t
->wv
, t
->settings
);
7070 update_statusbar_position(GtkAdjustment
* adjustment
, gpointer data
)
7072 struct tab
*ti
, *t
= NULL
;
7073 gdouble view_size
, value
, max
;
7076 TAILQ_FOREACH(ti
, &tabs
, entry
)
7077 if (ti
->tab_id
== gtk_notebook_get_current_page(notebook
)) {
7085 if (adjustment
== NULL
)
7086 adjustment
= gtk_scrolled_window_get_vadjustment(
7087 GTK_SCROLLED_WINDOW(t
->browser_win
));
7089 view_size
= gtk_adjustment_get_page_size(adjustment
);
7090 value
= gtk_adjustment_get_value(adjustment
);
7091 max
= gtk_adjustment_get_upper(adjustment
) - view_size
;
7094 position
= g_strdup("All");
7095 else if (value
== max
)
7096 position
= g_strdup("Bot");
7097 else if (value
== 0)
7098 position
= g_strdup("Top");
7100 position
= g_strdup_printf("%d%%", (int) ((value
/ max
) * 100));
7102 gtk_label_set_text(GTK_LABEL(t
->sbe
.position
), position
);
7109 create_window(const gchar
*name
)
7113 w
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
7114 if (window_maximize
)
7115 gtk_window_maximize(GTK_WINDOW(w
));
7117 gtk_window_set_default_size(GTK_WINDOW(w
), window_width
, window_height
);
7118 gtk_widget_set_name(w
, name
);
7119 gtk_window_set_wmclass(GTK_WINDOW(w
), name
, "Xombrero");
7125 create_browser(struct tab
*t
)
7128 GtkAdjustment
*adjustment
;
7131 show_oops(NULL
, "create_browser invalid parameters");
7135 w
= gtk_scrolled_window_new(NULL
, NULL
);
7136 gtk_widget_set_can_focus(w
, FALSE
);
7137 t
->adjust_h
= gtk_scrolled_window_get_hadjustment(
7138 GTK_SCROLLED_WINDOW(w
));
7139 t
->adjust_v
= gtk_scrolled_window_get_vadjustment(
7140 GTK_SCROLLED_WINDOW(w
));
7141 #if !GTK_CHECK_VERSION(3, 0, 0)
7142 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w
),
7143 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
7147 t
->wv
= WEBKIT_WEB_VIEW(webkit_web_view_new());
7148 gtk_container_add(GTK_CONTAINER(w
), GTK_WIDGET(t
->wv
));
7151 t
->settings
= webkit_web_settings_new();
7153 g_object_set(t
->settings
, "default-encoding", encoding
, (char *)NULL
);
7155 t
->stylesheet
= g_strdup(stylesheet
);
7156 t
->load_images
= auto_load_images
;
7159 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w
));
7160 g_signal_connect(G_OBJECT(adjustment
), "value-changed",
7161 G_CALLBACK(update_statusbar_position
), NULL
);
7170 create_kiosk_toolbar(struct tab
*t
)
7172 GtkWidget
*toolbar
= NULL
, *b
;
7174 #if GTK_CHECK_VERSION(3, 0, 0)
7175 b
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
7176 gtk_widget_set_name(GTK_WIDGET(b
), "toolbar");
7178 b
= gtk_hbox_new(FALSE
, 0);
7181 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
7183 /* backward button */
7184 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7185 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7186 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7187 G_CALLBACK(backward_cb
), t
);
7188 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, TRUE
, TRUE
, 0);
7190 /* forward button */
7191 t
->forward
= create_button("Forward", GTK_STOCK_GO_FORWARD
, 0);
7192 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7193 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7194 G_CALLBACK(forward_cb
), t
);
7195 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, TRUE
, TRUE
, 0);
7198 t
->gohome
= create_button("Home", GTK_STOCK_HOME
, 0);
7199 gtk_widget_set_sensitive(t
->gohome
, true);
7200 g_signal_connect(G_OBJECT(t
->gohome
), "clicked",
7201 G_CALLBACK(home_cb
), t
);
7202 gtk_box_pack_start(GTK_BOX(b
), t
->gohome
, TRUE
, TRUE
, 0);
7204 /* create widgets but don't use them */
7205 t
->uri_entry
= gtk_entry_new();
7206 g_signal_connect(G_OBJECT(t
->uri_entry
), "focus-in-event",
7207 G_CALLBACK(entry_focus_cb
), t
);
7208 #if !GTK_CHECK_VERSION(3, 0, 0)
7209 t
->default_style
= gtk_rc_get_style(t
->uri_entry
);
7211 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7212 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7213 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7219 create_toolbar(struct tab
*t
)
7221 GtkWidget
*toolbar
= NULL
, *b
;
7223 #if GTK_CHECK_VERSION(3, 0, 0)
7224 b
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
7225 gtk_widget_set_name(GTK_WIDGET(b
), "toolbar");
7227 b
= gtk_hbox_new(FALSE
, 0);
7230 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
7232 /* backward button */
7233 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7234 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7235 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7236 G_CALLBACK(backward_cb
), t
);
7237 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, FALSE
, FALSE
, 0);
7239 /* forward button */
7240 t
->forward
= create_button("Forward",GTK_STOCK_GO_FORWARD
, 0);
7241 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7242 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7243 G_CALLBACK(forward_cb
), t
);
7244 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, FALSE
, FALSE
, 0);
7247 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7248 gtk_widget_set_sensitive(t
->stop
, FALSE
);
7249 g_signal_connect(G_OBJECT(t
->stop
), "clicked", G_CALLBACK(stop_cb
), t
);
7250 gtk_box_pack_start(GTK_BOX(b
), t
->stop
, FALSE
, FALSE
, 0);
7253 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7254 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7255 gtk_widget_set_sensitive(t
->js_toggle
, TRUE
);
7256 g_signal_connect(G_OBJECT(t
->js_toggle
), "clicked",
7257 G_CALLBACK(js_toggle_cb
), t
);
7258 gtk_box_pack_start(GTK_BOX(b
), t
->js_toggle
, FALSE
, FALSE
, 0);
7260 /* toggle proxy button */
7261 t
->proxy_toggle
= create_button("Proxy-Toggle", proxy_uri
?
7262 GTK_STOCK_CONNECT
: GTK_STOCK_DISCONNECT
, 0);
7263 /* override icons */
7265 button_set_file(t
->proxy_toggle
, "torenabled.ico");
7267 button_set_file(t
->proxy_toggle
, "tordisabled.ico");
7268 gtk_widget_set_sensitive(t
->proxy_toggle
, TRUE
);
7269 g_signal_connect(G_OBJECT(t
->proxy_toggle
), "clicked",
7270 G_CALLBACK(proxy_toggle_cb
), t
);
7271 gtk_box_pack_start(GTK_BOX(b
), t
->proxy_toggle
, FALSE
, FALSE
, 0);
7273 t
->uri_entry
= gtk_entry_new();
7274 g_signal_connect(G_OBJECT(t
->uri_entry
), "activate",
7275 G_CALLBACK(activate_uri_entry_cb
), t
);
7276 g_signal_connect(G_OBJECT(t
->uri_entry
), "key-press-event",
7277 G_CALLBACK(entry_key_cb
), t
);
7278 g_signal_connect(G_OBJECT(t
->uri_entry
), "focus-in-event",
7279 G_CALLBACK(entry_focus_cb
), t
);
7281 gtk_box_pack_start(GTK_BOX(b
), t
->uri_entry
, TRUE
, TRUE
, 0);
7284 t
->search_entry
= gtk_entry_new();
7285 gtk_entry_set_width_chars(GTK_ENTRY(t
->search_entry
), 30);
7286 g_signal_connect(G_OBJECT(t
->search_entry
), "activate",
7287 G_CALLBACK(activate_search_entry_cb
), t
);
7288 g_signal_connect(G_OBJECT(t
->search_entry
), "key-press-event",
7289 G_CALLBACK(entry_key_cb
), t
);
7290 g_signal_connect(G_OBJECT(t
->search_entry
), "focus-in-event",
7291 G_CALLBACK(entry_focus_cb
), t
);
7292 gtk_box_pack_start(GTK_BOX(b
), t
->search_entry
, FALSE
, FALSE
, 0);
7294 #if !GTK_CHECK_VERSION(3, 0, 0)
7295 t
->default_style
= gtk_rc_get_style(t
->uri_entry
);
7302 create_buffers(struct tab
*t
)
7304 GtkCellRenderer
*renderer
;
7307 view
= gtk_tree_view_new();
7309 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view
), FALSE
);
7311 renderer
= gtk_cell_renderer_text_new();
7312 gtk_tree_view_insert_column_with_attributes
7313 (GTK_TREE_VIEW(view
), -1, "Id", renderer
, "text", COL_ID
, (char *)NULL
);
7315 renderer
= gtk_cell_renderer_pixbuf_new();
7316 gtk_tree_view_insert_column_with_attributes
7317 (GTK_TREE_VIEW(view
), -1, "Favicon", renderer
, "pixbuf", COL_FAVICON
,
7320 renderer
= gtk_cell_renderer_text_new();
7321 gtk_tree_view_insert_column_with_attributes
7322 (GTK_TREE_VIEW(view
), -1, "Title", renderer
, "text", COL_TITLE
,
7325 gtk_tree_view_set_model
7326 (GTK_TREE_VIEW(view
), GTK_TREE_MODEL(buffers_store
));
7332 row_activated_cb(GtkTreeView
*view
, GtkTreePath
*path
,
7333 GtkTreeViewColumn
*col
, struct tab
*t
)
7338 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
7340 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store
), &iter
,
7343 (GTK_TREE_MODEL(buffers_store
), &iter
, COL_ID
, &id
, -1);
7344 set_current_tab(id
- 1);
7350 /* after tab reordering/creation/removal */
7356 TAILQ_FOREACH(t
, &tabs
, entry
)
7357 t
->tab_id
= gtk_notebook_page_num(notebook
, t
->vbox
);
7361 update_statusbar_tabs(struct tab
*t
)
7363 int tab_id
, max_tab_id
;
7369 tab_id
= gtk_notebook_get_current_page(notebook
);
7371 max_tab_id
= gtk_notebook_get_n_pages(notebook
);
7372 snprintf(s
, sizeof s
, "%d/%d", tab_id
+ 1, max_tab_id
);
7375 t
= get_current_tab();
7378 gtk_label_set_text(GTK_LABEL(t
->sbe
.tabs
), s
);
7381 /* after active tab change */
7383 recolor_compact_tabs(void)
7387 #if !GTK_CHECK_VERSION(3, 0, 0)
7388 GdkColor color_active
, color_inactive
;
7390 gdk_color_parse(XT_COLOR_CT_ACTIVE
, &color_active
);
7391 gdk_color_parse(XT_COLOR_CT_INACTIVE
, &color_inactive
);
7393 curid
= gtk_notebook_get_current_page(notebook
);
7395 TAILQ_FOREACH(t
, &tabs
, entry
) {
7396 #if GTK_CHECK_VERSION(3, 0, 0)
7397 if (t
->tab_id
== curid
)
7398 gtk_widget_set_name(t
->tab_elems
.label
, XT_CSS_ACTIVE
);
7400 gtk_widget_set_name(t
->tab_elems
.label
, "");
7402 if (t
->tab_id
== curid
)
7403 gtk_widget_modify_fg(t
->tab_elems
.label
,
7404 GTK_STATE_NORMAL
, &color_active
);
7406 gtk_widget_modify_fg(t
->tab_elems
.label
,
7407 GTK_STATE_NORMAL
, &color_inactive
);
7413 set_current_tab(int page_num
)
7415 buffercmd_abort(get_current_tab());
7416 gtk_notebook_set_current_page(notebook
, page_num
);
7417 recolor_compact_tabs();
7418 update_statusbar_tabs(NULL
);
7422 undo_close_tab_save(struct tab
*t
)
7426 struct undo
*u1
, *u2
;
7428 WebKitWebHistoryItem
*item
;
7430 if ((uri
= get_uri(t
)) == NULL
)
7433 u1
= g_malloc0(sizeof(struct undo
));
7434 u1
->uri
= g_strdup(uri
);
7436 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
7438 m
= webkit_web_back_forward_list_get_forward_length(t
->bfl
);
7439 n
= webkit_web_back_forward_list_get_back_length(t
->bfl
);
7442 /* forward history */
7443 items
= webkit_web_back_forward_list_get_forward_list_with_limit(t
->bfl
, m
);
7447 u1
->history
= g_list_prepend(u1
->history
,
7448 webkit_web_history_item_copy(item
));
7449 items
= g_list_next(items
);
7454 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
7455 u1
->history
= g_list_prepend(u1
->history
,
7456 webkit_web_history_item_copy(item
));
7460 items
= webkit_web_back_forward_list_get_back_list_with_limit(t
->bfl
, n
);
7464 u1
->history
= g_list_prepend(u1
->history
,
7465 webkit_web_history_item_copy(item
));
7466 items
= g_list_next(items
);
7469 TAILQ_INSERT_HEAD(&undos
, u1
, entry
);
7471 if (undo_count
> XT_MAX_UNDO_CLOSE_TAB
) {
7472 u2
= TAILQ_LAST(&undos
, undo_tailq
);
7473 TAILQ_REMOVE(&undos
, u2
, entry
);
7475 g_list_free(u2
->history
);
7484 delete_tab(struct tab
*t
)
7488 DNPRINTF(XT_D_TAB
, "delete_tab: %p\n", t
);
7494 * no need to join thread here because it won't access t on completion
7497 TAILQ_REMOVE(&tabs
, t
, entry
);
7500 /* Halt all webkit activity. */
7501 abort_favicon_download(t
);
7502 webkit_web_view_stop_loading(t
->wv
);
7504 /* Save the tab, so we can undo the close. */
7505 undo_close_tab_save(t
);
7509 g_source_remove(t
->search_id
);
7513 g_free(t
->session_key
);
7516 bzero(&a
, sizeof a
);
7518 inspector_cmd(t
, &a
);
7520 if (browser_mode
== XT_BM_KIOSK
) {
7521 gtk_widget_destroy(t
->uri_entry
);
7522 gtk_widget_destroy(t
->stop
);
7523 gtk_widget_destroy(t
->js_toggle
);
7526 g_object_unref(t
->completion
);
7528 g_object_unref(t
->item
);
7530 gtk_widget_destroy(t
->tab_elems
.eventbox
);
7531 gtk_widget_destroy(t
->vbox
);
7533 g_free(t
->stylesheet
);
7539 if (TAILQ_EMPTY(&tabs
)) {
7540 if (browser_mode
== XT_BM_KIOSK
)
7541 create_new_tab(home
, NULL
, 1, -1);
7543 create_new_tab(NULL
, NULL
, 1, -1);
7546 /* recreate session */
7547 if (session_autosave
) {
7548 bzero(&a
, sizeof a
);
7550 save_tabs(NULL
, &a
);
7554 recolor_compact_tabs();
7558 update_statusbar_zoom(struct tab
*t
)
7561 char s
[16] = { '\0' };
7563 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7564 if ((zoom
<= 0.99 || zoom
>= 1.01))
7565 snprintf(s
, sizeof s
, "%d%%", (int)(zoom
* 100));
7566 gtk_label_set_text(GTK_LABEL(t
->sbe
.zoom
), s
);
7570 setzoom_webkit(struct tab
*t
, int adjust
)
7572 #define XT_ZOOMPERCENT 0.04
7577 show_oops(NULL
, "setzoom_webkit invalid parameters");
7581 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7582 if (adjust
== XT_ZOOM_IN
)
7583 zoom
+= XT_ZOOMPERCENT
;
7584 else if (adjust
== XT_ZOOM_OUT
)
7585 zoom
-= XT_ZOOMPERCENT
;
7586 else if (adjust
> 0)
7587 zoom
= default_zoom_level
+ adjust
/ 100.0 - 1.0;
7589 show_oops(t
, "setzoom_webkit invalid zoom value");
7593 if (zoom
< XT_ZOOMPERCENT
)
7594 zoom
= XT_ZOOMPERCENT
;
7595 g_object_set(G_OBJECT(t
->wv
), "zoom-level", zoom
, (char *)NULL
);
7596 update_statusbar_zoom(t
);
7600 tab_clicked_cb(GtkWidget
*widget
, GdkEventButton
*event
, gpointer data
)
7602 struct tab
*t
= (struct tab
*) data
;
7604 DNPRINTF(XT_D_TAB
, "tab_clicked_cb: tab: %d\n", t
->tab_id
);
7606 switch (event
->button
) {
7608 set_current_tab(t
->tab_id
);
7619 append_tab(struct tab
*t
)
7624 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
7625 t
->tab_id
= gtk_notebook_append_page(notebook
, t
->vbox
, t
->tab_content
);
7633 sbe
= gtk_label_new(NULL
);
7634 gtk_widget_set_can_focus(GTK_WIDGET(sbe
), FALSE
);
7635 gtk_widget_modify_font(GTK_WIDGET(sbe
), statusbar_font
);
7640 add_sbe(GtkWidget
*box
, char flag
, int *used
, GtkWidget
*sbe
)
7642 if (box
== NULL
|| used
== NULL
|| sbe
== NULL
) {
7643 DPRINTF("%s: invalid parameters", __func__
);
7648 warnx("flag \"%c\" specified more than "
7649 "once in statusbar_elems\n", flag
);
7653 gtk_box_pack_start(GTK_BOX(box
), sbe
, FALSE
, FALSE
, 0);
7660 statusbar_create(struct tab
*t
)
7662 GtkWidget
*box
; /* container for statusbar elems */
7665 int sbe_P
= 0, sbe_B
= 0, sbe_Z
= 0, sbe_T
= 0,
7667 #if !GTK_CHECK_VERSION(3, 0, 0)
7672 DPRINTF("%s: invalid parameters", __func__
);
7676 #if GTK_CHECK_VERSION(3, 0, 0)
7677 t
->statusbar
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
7678 box
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
7679 gtk_widget_set_name(GTK_WIDGET(t
->statusbar
), "statusbar");
7681 t
->statusbar
= gtk_hbox_new(FALSE
, 0);
7682 box
= gtk_hbox_new(FALSE
, 0);
7684 t
->sbe
.ebox
= gtk_event_box_new();
7686 gtk_widget_set_can_focus(GTK_WIDGET(t
->statusbar
), FALSE
);
7687 gtk_widget_set_can_focus(GTK_WIDGET(t
->sbe
.ebox
), FALSE
);
7688 gtk_widget_set_can_focus(GTK_WIDGET(box
), FALSE
);
7690 gtk_box_set_spacing(GTK_BOX(box
), 10);
7691 gtk_box_pack_start(GTK_BOX(t
->statusbar
), t
->sbe
.ebox
, TRUE
, TRUE
, 0);
7692 gtk_container_add(GTK_CONTAINER(t
->sbe
.ebox
), box
);
7694 /* create these widgets only if specified in statusbar_elems */
7695 t
->sbe
.uri
= gtk_entry_new();
7696 gtk_widget_set_can_focus(GTK_WIDGET(t
->sbe
.uri
), FALSE
);
7697 gtk_widget_modify_font(GTK_WIDGET(t
->sbe
.uri
), statusbar_font
);
7698 #if !GTK_CHECK_VERSION(3, 0, 0)
7699 gtk_entry_set_inner_border(GTK_ENTRY(t
->sbe
.uri
), NULL
);
7700 gtk_entry_set_has_frame(GTK_ENTRY(t
->sbe
.uri
), FALSE
);
7702 t
->sbe
.position
= create_sbe();
7703 t
->sbe
.zoom
= create_sbe();
7704 t
->sbe
.buffercmd
= create_sbe();
7705 t
->sbe
.tabs
= create_sbe();
7706 t
->sbe
.proxy
= create_sbe();
7708 #if GTK_CHECK_VERSION(3, 0, 0)
7709 statusbar_modify_attr(t
, XT_CSS_NORMAL
);
7711 statusbar_modify_attr(t
, XT_COLOR_WHITE
, XT_COLOR_BLACK
);
7714 gtk_box_pack_start(GTK_BOX(box
), t
->sbe
.uri
, TRUE
, TRUE
, 0);
7717 * gtk widgets cannot be added to a box twice. The sbe_* variables
7720 for (p
= statusbar_elems
; *p
!= '\0'; p
++) {
7723 #if GTK_CHECK_VERSION(3, 0, 0)
7724 sep
= gtk_separator_new(GTK_ORIENTATION_VERTICAL
);
7726 sep
= gtk_vseparator_new();
7727 gdk_color_parse(XT_COLOR_SB_SEPARATOR
, &color
);
7728 gtk_widget_modify_bg(sep
, GTK_STATE_NORMAL
, &color
);
7730 gtk_box_pack_start(GTK_BOX(box
), sep
, FALSE
, FALSE
, 0);
7733 add_sbe(box
, *p
, &sbe_P
, t
->sbe
.position
);
7736 add_sbe(box
, *p
, &sbe_B
, t
->sbe
.buffercmd
);
7739 add_sbe(box
, *p
, &sbe_Z
, t
->sbe
.zoom
);
7742 add_sbe(box
, *p
, &sbe_T
, t
->sbe
.tabs
);
7745 if (add_sbe(box
, *p
, &sbe_p
, t
->sbe
.proxy
) == 0)
7748 GTK_ENTRY(t
->sbe
.proxy
), "proxy");
7751 warnx("illegal flag \"%c\" in statusbar_elems\n", *p
);
7756 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->statusbar
, FALSE
, FALSE
, 0);
7762 create_new_tab(char *title
, struct undo
*u
, int focus
, int position
)
7767 WebKitWebHistoryItem
*item
;
7770 #if !GTK_CHECK_VERSION(3, 0, 0)
7774 DNPRINTF(XT_D_TAB
, "create_new_tab: title %s focus %d\n", title
, focus
);
7776 if (tabless
&& !TAILQ_EMPTY(&tabs
)) {
7777 if (single_instance
) {
7779 "create_new_tab: new tab rejected\n");
7782 sv
[0] = start_argv
[0];
7784 sv
[2] = (char *)NULL
;
7785 if (!g_spawn_async(NULL
, sv
, NULL
, G_SPAWN_SEARCH_PATH
,
7786 NULL
, NULL
, NULL
, NULL
))
7787 show_oops(NULL
, "%s: could not spawn process",
7792 t
= g_malloc0(sizeof *t
);
7794 if (title
== NULL
) {
7795 title
= "(untitled)";
7799 #if GTK_CHECK_VERSION(3, 0, 0)
7800 t
->vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
7801 b
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
7802 gtk_widget_set_name(t
->vbox
, "vbox");
7804 t
->vbox
= gtk_vbox_new(FALSE
, 0);
7805 b
= gtk_hbox_new(FALSE
, 0);
7807 gtk_widget_set_can_focus(t
->vbox
, FALSE
);
7809 /* label + button for tab */
7811 gtk_widget_set_can_focus(t
->tab_content
, FALSE
);
7813 t
->user_agent_id
= 0;
7814 t
->http_accept_id
= 0;
7816 #if WEBKIT_CHECK_VERSION(1, 5, 0)
7820 #if GTK_CHECK_VERSION(2, 20, 0)
7821 t
->spinner
= gtk_spinner_new();
7823 t
->label
= gtk_label_new(title
);
7824 bb
= create_button("Close", GTK_STOCK_CLOSE
, 1);
7825 gtk_label_set_max_width_chars(GTK_LABEL(t
->label
), 20);
7826 gtk_label_set_ellipsize(GTK_LABEL(t
->label
), PANGO_ELLIPSIZE_END
);
7827 gtk_label_set_line_wrap(GTK_LABEL(t
->label
), FALSE
);
7828 gtk_widget_set_size_request(b
, 130, 0);
7831 * this is a total hack and most likely breaks with other styles but
7832 * is necessary so the text doesn't bounce around when the spinner is
7835 #if GTK_CHECK_VERSION(3, 0, 0)
7836 gtk_widget_set_size_request(t
->label
, 95, 0);
7838 gtk_widget_set_size_request(t
->label
, 100, 0);
7841 gtk_box_pack_start(GTK_BOX(b
), bb
, FALSE
, FALSE
, 0);
7842 gtk_box_pack_start(GTK_BOX(b
), t
->label
, FALSE
, FALSE
, 0);
7843 #if GTK_CHECK_VERSION(2, 20, 0)
7844 gtk_box_pack_end(GTK_BOX(b
), t
->spinner
, FALSE
, FALSE
, 0);
7848 if (browser_mode
== XT_BM_KIOSK
) {
7849 t
->toolbar
= create_kiosk_toolbar(t
);
7850 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->toolbar
, FALSE
, FALSE
,
7853 t
->toolbar
= create_toolbar(t
);
7854 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->toolbar
, FALSE
, FALSE
,
7862 t
->browser_win
= create_browser(t
);
7863 set_scrollbar_visibility(t
, show_scrollbars
);
7864 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->browser_win
, TRUE
, TRUE
, 0);
7866 /* oops message for user feedback */
7867 t
->oops
= gtk_entry_new();
7868 gtk_entry_set_inner_border(GTK_ENTRY(t
->oops
), NULL
);
7869 gtk_entry_set_has_frame(GTK_ENTRY(t
->oops
), FALSE
);
7870 gtk_widget_set_can_focus(GTK_WIDGET(t
->oops
), FALSE
);
7871 #if GTK_CHECK_VERSION(3, 0, 0)
7872 gtk_widget_set_name(t
->oops
, XT_CSS_RED
);
7874 gdk_color_parse(XT_COLOR_RED
, &color
);
7875 gtk_widget_modify_base(t
->oops
, GTK_STATE_NORMAL
, &color
);
7877 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->oops
, FALSE
, FALSE
, 0);
7878 gtk_widget_modify_font(GTK_WIDGET(t
->oops
), oops_font
);
7881 t
->cmd
= gtk_entry_new();
7882 g_signal_connect(G_OBJECT(t
->cmd
), "focus-in-event",
7883 G_CALLBACK(entry_focus_cb
), t
);
7884 gtk_entry_set_inner_border(GTK_ENTRY(t
->cmd
), NULL
);
7885 gtk_entry_set_has_frame(GTK_ENTRY(t
->cmd
), FALSE
);
7886 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->cmd
, FALSE
, FALSE
, 0);
7887 gtk_widget_modify_font(GTK_WIDGET(t
->cmd
), cmd_font
);
7890 statusbar_create(t
);
7893 t
->buffers
= create_buffers(t
);
7894 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->buffers
, FALSE
, FALSE
, 0);
7896 /* xtp meaning is normal by default */
7897 set_normal_tab_meaning(t
);
7899 /* set empty favicon */
7900 xt_icon_from_name(t
, "text-html");
7902 /* and show it all */
7903 gtk_widget_show_all(b
);
7904 gtk_widget_show_all(t
->vbox
);
7907 gtk_widget_hide(t
->backward
);
7908 gtk_widget_hide(t
->forward
);
7909 gtk_widget_hide(t
->stop
);
7910 gtk_widget_hide(t
->js_toggle
);
7912 if (!fancy_bar
|| (search_string
== NULL
|| strlen(search_string
) == 0))
7913 gtk_widget_hide(t
->search_entry
);
7914 if (http_proxy
== NULL
&& http_proxy_save
== NULL
)
7915 gtk_widget_hide(t
->proxy_toggle
);
7917 /* compact tab bar */
7918 t
->tab_elems
.label
= gtk_label_new(title
);
7919 t
->tab_elems
.favicon
= gtk_image_new();
7921 t
->tab_elems
.eventbox
= gtk_event_box_new();
7922 gtk_widget_set_name(t
->tab_elems
.eventbox
, "compact_tab");
7923 #if GTK_CHECK_VERSION(3, 0, 0)
7924 t
->tab_elems
.box
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
7926 gtk_label_set_ellipsize(GTK_LABEL(t
->tab_elems
.label
),
7927 PANGO_ELLIPSIZE_END
);
7928 gtk_widget_override_font(t
->tab_elems
.label
, tabbar_font
);
7929 gtk_widget_set_halign(t
->tab_elems
.label
, GTK_ALIGN_START
);
7930 gtk_widget_set_valign(t
->tab_elems
.label
, GTK_ALIGN_START
);
7932 t
->tab_elems
.box
= gtk_hbox_new(FALSE
, 0);
7934 gtk_label_set_width_chars(GTK_LABEL(t
->tab_elems
.label
), 1);
7935 gtk_misc_set_alignment(GTK_MISC(t
->tab_elems
.label
), 0.0, 0.0);
7936 gtk_misc_set_padding(GTK_MISC(t
->tab_elems
.label
), 4.0, 4.0);
7937 gtk_widget_modify_font(GTK_WIDGET(t
->tab_elems
.label
), tabbar_font
);
7939 gdk_color_parse(XT_COLOR_CT_BACKGROUND
, &color
);
7940 gtk_widget_modify_bg(t
->tab_elems
.eventbox
, GTK_STATE_NORMAL
, &color
);
7941 gdk_color_parse(XT_COLOR_CT_INACTIVE
, &color
);
7942 gtk_widget_modify_fg(t
->tab_elems
.label
, GTK_STATE_NORMAL
, &color
);
7945 gtk_box_pack_start(GTK_BOX(t
->tab_elems
.box
), t
->tab_elems
.favicon
, FALSE
,
7947 gtk_box_pack_start(GTK_BOX(t
->tab_elems
.box
), t
->tab_elems
.label
, TRUE
,
7949 gtk_container_add(GTK_CONTAINER(t
->tab_elems
.eventbox
),
7952 gtk_box_pack_start(GTK_BOX(tab_bar_box
), t
->tab_elems
.eventbox
, TRUE
,
7954 gtk_widget_show_all(t
->tab_elems
.eventbox
);
7956 if (append_next
== 0 || gtk_notebook_get_n_pages(notebook
) == 0)
7959 id
= position
>= 0 ? position
:
7960 gtk_notebook_get_current_page(notebook
) + 1;
7961 if (id
> gtk_notebook_get_n_pages(notebook
))
7964 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
7965 gtk_notebook_insert_page(notebook
, t
->vbox
, b
, id
);
7966 gtk_box_reorder_child(GTK_BOX(tab_bar_box
),
7967 t
->tab_elems
.eventbox
, id
);
7972 #if GTK_CHECK_VERSION(2, 20, 0)
7973 /* turn spinner off if we are a new tab without uri */
7975 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
7976 gtk_widget_hide(t
->spinner
);
7979 /* make notebook tabs reorderable */
7980 gtk_notebook_set_tab_reorderable(notebook
, t
->vbox
, TRUE
);
7982 /* compact tabs clickable */
7983 g_signal_connect(G_OBJECT(t
->tab_elems
.eventbox
),
7984 "button_press_event", G_CALLBACK(tab_clicked_cb
), t
);
7986 g_object_connect(G_OBJECT(t
->cmd
),
7987 "signal::button-release-event", G_CALLBACK(cmd_keyrelease_cb
), t
,
7988 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb
), t
,
7989 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb
), t
,
7990 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb
), t
,
7991 "signal::activate", G_CALLBACK(cmd_activate_cb
), t
,
7992 "signal::populate-popup", G_CALLBACK(cmd_popup_cb
), t
,
7993 "signal::hide", G_CALLBACK(cmd_hide_cb
), t
,
7996 /* reuse wv_button_cb to hide oops */
7997 g_object_connect(G_OBJECT(t
->oops
),
7998 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
8001 g_signal_connect(t
->buffers
,
8002 "row-activated", G_CALLBACK(row_activated_cb
), t
);
8003 g_object_connect(G_OBJECT(t
->buffers
),
8004 "signal::key-press-event", G_CALLBACK(wv_keypress_cb
), t
, (char *)NULL
);
8006 g_object_connect(G_OBJECT(t
->wv
),
8007 "signal::key-press-event", G_CALLBACK(wv_keypress_cb
), t
,
8008 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb
), t
,
8009 "signal::download-requested", G_CALLBACK(webview_download_cb
), t
,
8010 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb
), t
,
8011 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
8012 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
8013 "signal::resource-request-starting", G_CALLBACK(webview_rrs_cb
), t
,
8014 "signal::create-web-view", G_CALLBACK(webview_cwv_cb
), t
,
8015 "signal::close-web-view", G_CALLBACK(webview_closewv_cb
), t
,
8016 "signal::event", G_CALLBACK(webview_event_cb
), t
,
8017 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb
), t
,
8018 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
8019 "signal::button_release_event", G_CALLBACK(wv_release_button_cb
), t
,
8020 "signal::populate-popup", G_CALLBACK(wv_popup_cb
), t
,
8022 g_signal_connect(t
->wv
,
8023 "notify::load-status", G_CALLBACK(notify_load_status_cb
), t
);
8024 g_signal_connect(t
->wv
,
8025 "notify::title", G_CALLBACK(notify_title_cb
), t
);
8026 t
->progress_handle
= g_signal_connect(t
->wv
,
8027 "notify::progress", G_CALLBACK(webview_progress_changed_cb
), t
);
8029 /* hijack the unused keys as if we were the browser */
8030 //g_object_connect(G_OBJECT(t->toolbar),
8031 // "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8034 g_signal_connect(G_OBJECT(bb
), "button_press_event",
8035 G_CALLBACK(tab_close_cb
), t
);
8038 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
8039 /* restore the tab's history */
8040 if (u
&& u
->history
) {
8044 webkit_web_back_forward_list_add_item(t
->bfl
, item
);
8045 items
= g_list_next(items
);
8048 item
= g_list_nth_data(u
->history
, u
->back
);
8050 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
8053 g_list_free(u
->history
);
8055 webkit_web_back_forward_list_clear(t
->bfl
);
8057 /* check and show url and statusbar */
8058 url_set_visibility();
8059 statusbar_set_visibility();
8062 set_current_tab(t
->tab_id
);
8063 DNPRINTF(XT_D_TAB
, "create_new_tab: going to tab: %d\n",
8067 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), title
);
8071 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
8078 if (userstyle_global
)
8081 recolor_compact_tabs();
8082 setzoom_webkit(t
, XT_ZOOM_NORMAL
);
8087 notebook_switchpage_cb(GtkNotebook
*nb
, GtkWidget
*nbp
, guint pn
,
8093 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: tab: %d\n", pn
);
8095 if (gtk_notebook_get_current_page(notebook
) == -1)
8098 TAILQ_FOREACH(t
, &tabs
, entry
) {
8099 if (t
->tab_id
== pn
) {
8100 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: going to "
8103 uri
= get_title(t
, TRUE
);
8104 gtk_window_set_title(GTK_WINDOW(main_window
), uri
);
8111 /* can't use focus_webview here */
8112 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
8114 update_statusbar_tabs(t
);
8121 notebook_pagereordered_cb(GtkNotebook
*nb
, GtkWidget
*nbp
, guint pn
,
8124 struct tab
*t
= NULL
, *tt
;
8128 TAILQ_FOREACH(tt
, &tabs
, entry
)
8129 if (tt
->tab_id
== pn
) {
8135 DNPRINTF(XT_D_TAB
, "page_reordered_cb: tab: %d\n", t
->tab_id
);
8137 gtk_box_reorder_child(GTK_BOX(tab_bar_box
), t
->tab_elems
.eventbox
,
8140 update_statusbar_tabs(t
);
8144 menuitem_response(struct tab
*t
)
8146 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
8150 destroy_menu(GtkMenuShell
*m
, void *notused
)
8152 gtk_widget_destroy(GTK_WIDGET(m
));
8153 return (XT_CB_PASSTHROUGH
);
8157 arrow_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer user_data
)
8159 GtkWidget
*menu
= NULL
, *menu_items
;
8160 GdkEventButton
*bevent
;
8161 struct tab
**stabs
= NULL
;
8165 if (event
->type
== GDK_BUTTON_PRESS
) {
8166 bevent
= (GdkEventButton
*) event
;
8167 menu
= gtk_menu_new();
8169 num_tabs
= sort_tabs_by_page_num(&stabs
);
8170 for (i
= 0; i
< num_tabs
; ++i
) {
8171 if (stabs
[i
] == NULL
)
8173 if ((uri
= get_uri(stabs
[i
])) == NULL
)
8174 /* XXX make sure there is something to print */
8175 /* XXX add gui pages in here to look purdy */
8177 menu_items
= gtk_menu_item_new_with_label(uri
);
8178 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menu_items
);
8179 gtk_widget_show(menu_items
);
8181 g_signal_connect_swapped(menu_items
,
8182 "activate", G_CALLBACK(menuitem_response
),
8183 (gpointer
)stabs
[i
]);
8187 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
8188 bevent
->button
, bevent
->time
);
8190 g_object_connect(G_OBJECT(menu
),
8191 "signal::selection-done", G_CALLBACK(destroy_menu
), NULL
,
8194 return (TRUE
/* eat event */);
8197 return (FALSE
/* propagate */);
8201 icon_size_map(int iconsz
)
8203 if (iconsz
<= GTK_ICON_SIZE_INVALID
||
8204 iconsz
> GTK_ICON_SIZE_DIALOG
)
8205 return (GTK_ICON_SIZE_SMALL_TOOLBAR
);
8211 create_button(char *name
, char *stockid
, int size
)
8213 GtkWidget
*button
, *image
;
8215 #if !GTK_CHECK_VERSION(3, 0, 0)
8219 #if !GTK_CHECK_VERSION(3, 0, 0)
8220 newstyle
= g_strdup_printf(
8221 "style \"%s-style\"\n"
8223 " GtkWidget::focus-padding = 0\n"
8224 " GtkWidget::focus-line-width = 0\n"
8228 "widget \"*.%s\" style \"%s-style\"", name
, name
, name
);
8229 gtk_rc_parse_string(newstyle
);
8232 button
= gtk_button_new();
8233 gtk_widget_set_can_focus(button
, FALSE
);
8234 gtk_button_set_focus_on_click(GTK_BUTTON(button
), FALSE
);
8235 gtk_icon_size
= icon_size_map(size
? size
: icon_size
);
8237 image
= gtk_image_new_from_stock(stockid
, gtk_icon_size
);
8238 gtk_container_set_border_width(GTK_CONTAINER(button
), 1);
8239 gtk_container_add(GTK_CONTAINER(button
), GTK_WIDGET(image
));
8240 gtk_widget_set_name(button
, name
);
8241 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
8247 button_set_file(GtkWidget
*button
, char *filename
)
8250 char file
[PATH_MAX
];
8252 snprintf(file
, sizeof file
, "%s" PS
"%s", resource_dir
, filename
);
8253 image
= gtk_image_new_from_file(file
);
8254 gtk_button_set_image(GTK_BUTTON(button
), image
);
8258 button_set_stockid(GtkWidget
*button
, char *stockid
)
8262 image
= gtk_image_new_from_stock(stockid
, icon_size_map(icon_size
));
8263 gtk_button_set_image(GTK_BUTTON(button
), image
);
8272 char file
[PATH_MAX
];
8274 #if !GTK_CHECK_VERSION(3, 0, 0)
8278 #if GTK_CHECK_VERSION(3, 0, 0)
8279 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
8281 vbox
= gtk_vbox_new(FALSE
, 0);
8283 gtk_box_set_spacing(GTK_BOX(vbox
), 0);
8284 gtk_widget_set_can_focus(vbox
, FALSE
);
8285 notebook
= GTK_NOTEBOOK(gtk_notebook_new());
8286 #if !GTK_CHECK_VERSION(3, 0, 0)
8287 /* XXX seems to be needed with gtk+2 */
8288 g_object_set(G_OBJECT(notebook
), "tab-border", 0, NULL
);
8290 gtk_notebook_set_scrollable(notebook
, TRUE
);
8291 gtk_notebook_set_show_border(notebook
, FALSE
);
8292 gtk_widget_set_can_focus(GTK_WIDGET(notebook
), FALSE
);
8294 abtn
= gtk_button_new();
8295 gtk_widget_set_can_focus(abtn
, FALSE
);
8296 arrow
= gtk_arrow_new(GTK_ARROW_DOWN
, GTK_SHADOW_NONE
);
8297 gtk_widget_set_name(abtn
, "Arrow");
8298 gtk_container_add(GTK_CONTAINER(abtn
), arrow
);
8299 gtk_widget_set_size_request(abtn
, -1, 20);
8301 #if GTK_CHECK_VERSION(2, 20, 0)
8302 gtk_notebook_set_action_widget(notebook
, abtn
, GTK_PACK_END
);
8304 /* compact tab bar */
8305 tab_bar
= gtk_event_box_new();
8306 #if GTK_CHECK_VERSION(3, 0, 0)
8307 gtk_widget_set_name(tab_bar
, "tab_bar");
8308 tab_bar_box
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
8310 gdk_color_parse(XT_COLOR_CT_SEPARATOR
, &color
);
8311 gtk_widget_modify_bg(tab_bar
, GTK_STATE_NORMAL
, &color
);
8312 tab_bar_box
= gtk_hbox_new(TRUE
, 0);
8314 gtk_container_add(GTK_CONTAINER(tab_bar
), tab_bar_box
);
8315 gtk_box_set_homogeneous(GTK_BOX(tab_bar_box
), TRUE
);
8316 gtk_box_set_spacing(GTK_BOX(tab_bar_box
), 2);
8318 gtk_box_pack_start(GTK_BOX(vbox
), tab_bar
, FALSE
, FALSE
, 0);
8319 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(notebook
), TRUE
, TRUE
, 0);
8321 g_object_connect(G_OBJECT(notebook
),
8322 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb
), NULL
,
8324 g_object_connect(G_OBJECT(notebook
),
8325 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb
),
8326 NULL
, (char *)NULL
);
8327 g_signal_connect(G_OBJECT(abtn
), "button_press_event",
8328 G_CALLBACK(arrow_cb
), NULL
);
8330 main_window
= create_window("xombrero");
8331 gtk_container_add(GTK_CONTAINER(main_window
), vbox
);
8332 g_signal_connect(G_OBJECT(main_window
), "delete_event",
8333 G_CALLBACK(gtk_main_quit
), NULL
);
8334 #if GTK_CHECK_VERSION(3, 0, 0)
8335 gtk_window_set_has_resize_grip(GTK_WINDOW(main_window
), FALSE
);
8339 for (i
= 0; i
< LENGTH(icons
); i
++) {
8340 snprintf(file
, sizeof file
, "%s" PS
"%s", resource_dir
, icons
[i
]);
8341 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
8342 l
= g_list_append(l
, pb
);
8344 gtk_window_set_default_icon_list(l
);
8346 gtk_widget_show_all(abtn
);
8347 gtk_widget_show_all(main_window
);
8348 notebook_tab_set_visibility();
8351 #ifndef XT_SOCKET_DISABLE
8353 send_cmd_to_socket(char *cmd
)
8356 struct sockaddr_un sa
;
8358 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
8359 warnx("%s: socket", __func__
);
8363 sa
.sun_family
= AF_UNIX
;
8364 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s" PS
"%s",
8365 work_dir
, XT_SOCKET_FILE
);
8368 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8369 warnx("%s: connect", __func__
);
8373 if (send(s
, cmd
, strlen(cmd
) + 1, 0) == -1) {
8374 warnx("%s: send", __func__
);
8385 socket_watcher(GIOChannel
*source
, GIOCondition condition
, gpointer data
)
8388 char str
[XT_MAX_URL_LENGTH
];
8389 socklen_t t
= sizeof(struct sockaddr_un
);
8390 struct sockaddr_un sa
;
8395 gint fd
= g_io_channel_unix_get_fd(source
);
8397 if ((s
= accept(fd
, (struct sockaddr
*)&sa
, &t
)) == -1) {
8402 if (getpeereid(s
, &uid
, &gid
) == -1) {
8406 if (uid
!= getuid() || gid
!= getgid()) {
8407 warnx("unauthorized user");
8413 warnx("not a valid user");
8417 n
= recv(s
, str
, sizeof(str
), 0);
8421 tt
= get_current_tab();
8422 cmd_execute(tt
, str
);
8430 struct sockaddr_un sa
;
8432 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
8433 warn("is_running: socket");
8437 sa
.sun_family
= AF_UNIX
;
8438 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s" PS
"%s",
8439 work_dir
, XT_SOCKET_FILE
);
8442 /* connect to see if there is a listener */
8443 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1)
8444 rv
= 0; /* not running */
8446 rv
= 1; /* already running */
8457 struct sockaddr_un sa
;
8459 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
8460 warn("build_socket: socket");
8464 sa
.sun_family
= AF_UNIX
;
8465 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s" PS
"%s",
8466 work_dir
, XT_SOCKET_FILE
);
8469 /* connect to see if there is a listener */
8470 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8471 /* no listener so we will */
8472 unlink(sa
.sun_path
);
8474 if (bind(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8475 warn("build_socket: bind");
8479 if (listen(s
, 1) == -1) {
8480 warn("build_socket: listen");
8498 if (stat(dir
, &sb
)) {
8499 #if defined __MINGW32__
8500 if (mkdir(dir
) == -1)
8502 if (mkdir(dir
, S_IRWXU
) == -1)
8504 err(1, "mkdir %s", dir
);
8506 err(1, "stat %s", dir
);
8508 if (S_ISDIR(sb
.st_mode
) == 0)
8509 errx(1, "%s not a dir", dir
);
8510 #if !defined __MINGW32__
8511 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8512 warnx("fixing invalid permissions on %s", dir
);
8513 if (chmod(dir
, S_IRWXU
) == -1)
8514 err(1, "chmod %s", dir
);
8523 "%s [-nSTVt][-f file][-s session] url ...\n", __progname
);
8527 GStaticRecMutex my_gdk_mtx
= G_STATIC_REC_MUTEX_INIT
;
8528 volatile int mtx_depth
;
8532 * The linux flash plugin violates the gdk locking mechanism.
8533 * Work around the issue by using a recursive mutex with some match applied
8534 * to see if we hit a buggy condition.
8536 * The following code is painful so just don't read it. It really doesn't
8537 * make much sense but seems to work.
8546 g_static_rec_mutex_lock(&my_gdk_mtx
);
8547 if (my_gdk_mtx
.depth
<= 0) {
8551 g_static_rec_mutex_lock(&my_gdk_mtx
);
8553 } else if (my_gdk_mtx
.depth
!= 1) {
8558 g_static_rec_mutex_unlock(&my_gdk_mtx
);
8559 } while (my_gdk_mtx
.depth
> 1);
8565 if (mtx_complain
== 0) {
8566 DNPRINTF(XT_D_MTX
, "buggy mutex implementation detected(%s), "
8567 "work around implemented", s
);
8579 if (my_gdk_mtx
.depth
<= 0) {
8584 } else if (my_gdk_mtx
.depth
!= 1) {
8588 g_static_rec_mutex_unlock_full(&my_gdk_mtx
);
8591 g_static_rec_mutex_unlock(&my_gdk_mtx
);
8595 if (mtx_complain
== 0) {
8596 DNPRINTF(XT_D_MTX
, "buggy mutex implementation detected(%s), "
8597 "work around implemented", s
);
8602 #if GTK_CHECK_VERSION(3, 0, 0)
8606 GtkCssProvider
*provider
;
8607 GdkDisplay
*display
;
8610 char path
[PATH_MAX
];
8611 #if defined __MINGW32__
8612 GtkCssProvider
*windows_hacks
;
8615 provider
= gtk_css_provider_new();
8616 display
= gdk_display_get_default();
8617 screen
= gdk_display_get_default_screen(display
);
8618 snprintf(path
, sizeof path
, "%s" PS
"%s", resource_dir
, XT_CSS_FILE
);
8619 file
= g_file_new_for_path(path
);
8620 gtk_css_provider_load_from_file(provider
, file
, NULL
);
8621 gtk_style_context_add_provider_for_screen(screen
,
8622 GTK_STYLE_PROVIDER(provider
),
8623 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
8624 #if defined __MINGW32__
8625 windows_hacks
= gtk_css_provider_new();
8626 gtk_css_provider_load_from_data(windows_hacks
,
8628 " border-width: 0px;\n"
8630 gtk_style_context_add_provider_for_screen(screen
,
8631 GTK_STYLE_PROVIDER(windows_hacks
),
8632 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
8634 g_object_unref(file
);
8641 startpage_add("<b>Welcome to xombrero %s!</b><p>", version
);
8642 startpage_add("Details at "
8643 "<a href=https://opensource.conformal.com/wiki/xombrero>xombrero "
8644 "wiki page</a><p>");
8648 main(int argc
, char **argv
)
8651 int c
, optn
= 0, opte
= 0, focus
= 1;
8652 char conf
[PATH_MAX
] = { '\0' };
8653 char file
[PATH_MAX
];
8654 char sodversion
[32];
8655 char *env_proxy
= NULL
;
8660 start_argv
= (char * const *)argv
;
8667 #if !defined __MINGW32__
8668 /* http://web.archiveorange.com/archive/v/UsPjxkX5PsaXBIoOjqxf */
8671 /* http://developer.gnome.org/gdk/stable/gdk-Threads.html */
8672 g_thread_init(NULL
);
8673 gdk_threads_set_lock_functions(mtx_lock
, mtx_unlock
);
8675 gdk_threads_enter();
8677 /* http://www.gnupg.org/documentation/manuals/gcrypt/Multi_002dThreading.html */
8678 gcry_control (GCRYCTL_SET_THREAD_CBS
, &gcry_threads_pthread
);
8680 gtk_init(&argc
, &argv
);
8682 gnutls_global_init();
8684 strlcpy(named_session
, XT_SAVED_TABS_FILE
, sizeof named_session
);
8687 RB_INIT(&downloads
);
8694 TAILQ_INIT(&sessions
);
8697 TAILQ_INIT(&aliases
);
8708 TAILQ_INIT(&force_https
);
8711 #ifndef XT_RESOURCE_LIMITS_DISABLE
8715 GIOChannel
*channel
;
8717 /* fiddle with ulimits */
8718 if (getrlimit(RLIMIT_NOFILE
, &rlp
) == -1)
8721 /* just use them all */
8722 rlp
.rlim_cur
= rlp
.rlim_max
;
8723 if (setrlimit(RLIMIT_NOFILE
, &rlp
) == -1)
8725 if (getrlimit(RLIMIT_NOFILE
, &rlp
) == -1)
8727 else if (rlp
.rlim_cur
< 1024)
8728 startpage_add("%s requires at least 1024 "
8729 "(2048 recommended) file " "descriptors, "
8730 "currently it has up to %d available",
8731 __progname
, rlp
.rlim_cur
);
8735 while ((c
= getopt(argc
, argv
, "STVf:s:tne")) != -1) {
8744 #ifdef XOMBRERO_BUILDSTR
8745 errx(0 , "Version: %s Build: %s",
8746 version
, XOMBRERO_BUILDSTR
);
8748 errx(0 , "Version: %s", version
);
8752 strlcpy(conf
, optarg
, sizeof(conf
));
8755 strlcpy(named_session
, optarg
, sizeof(named_session
));
8776 pwd
= getpwuid(getuid());
8778 errx(1, "invalid user %d", getuid());
8780 /* set download dir */
8781 if (strlen(download_dir
) == 0)
8782 strlcpy(download_dir
, pwd
->pw_dir
, sizeof download_dir
);
8784 /* compile buffer command regexes */
8787 /* set default dynamic string settings */
8788 home
= g_strdup(XT_DS_HOME
);
8789 search_string
= g_strdup(XT_DS_SEARCH_STRING
);
8790 strlcpy(runtime_settings
, "runtime", sizeof runtime_settings
);
8791 cmd_font_name
= g_strdup(XT_DS_CMD_FONT_NAME
);
8792 oops_font_name
= g_strdup(XT_DS_OOPS_FONT_NAME
);
8793 statusbar_font_name
= g_strdup(XT_DS_STATUSBAR_FONT_NAME
);
8794 tabbar_font_name
= g_strdup(XT_DS_TABBAR_FONT_NAME
);
8795 statusbar_elems
= g_strdup("BP");
8796 spell_check_languages
= g_strdup(XT_DS_SPELL_CHECK_LANGUAGES
);
8797 encoding
= g_strdup(XT_DS_ENCODING
);
8798 spell_check_languages
= g_strdup(XT_DS_SPELL_CHECK_LANGUAGES
);
8799 path
= g_strdup_printf("%s" PS
"style.css", resource_dir
);
8800 userstyle
= g_filename_to_uri(path
, NULL
, NULL
);
8802 stylesheet
= g_strdup(userstyle
);
8804 /* set statically allocated (struct special) settings */
8805 if (strlen(default_script
) == 0)
8806 expand_tilde(default_script
, sizeof default_script
,
8807 XT_DS_DEFAULT_SCRIPT
);
8808 if (strlen(ssl_ca_file
) == 0)
8809 expand_tilde(ssl_ca_file
, sizeof ssl_ca_file
,
8813 session
= webkit_get_default_session();
8815 /* read config file */
8816 if (strlen(conf
) == 0)
8817 snprintf(conf
, sizeof conf
, "%s" PS
".%s",
8818 pwd
->pw_dir
, XT_CONF_FILE
);
8819 config_parse(conf
, 0);
8821 /* read preloaded HSTS list */
8822 if (preload_strict_transport
) {
8823 snprintf(conf
, sizeof conf
, "%s" PS
"%s",
8824 resource_dir
, XT_HSTS_PRELOAD_FILE
);
8825 config_parse(conf
, 0);
8828 /* check whether to read in a crapton of additional http headers */
8829 if (anonymize_headers
) {
8830 snprintf(conf
, sizeof conf
, "%s" PS
"%s",
8831 resource_dir
, XT_USER_AGENT_FILE
);
8832 config_parse(conf
, 0);
8833 snprintf(conf
, sizeof conf
, "%s" PS
"%s",
8834 resource_dir
, XT_HTTP_ACCEPT_FILE
);
8835 config_parse(conf
, 0);
8839 cmd_font
= pango_font_description_from_string(cmd_font_name
);
8840 oops_font
= pango_font_description_from_string(oops_font_name
);
8841 statusbar_font
= pango_font_description_from_string(statusbar_font_name
);
8842 tabbar_font
= pango_font_description_from_string(tabbar_font_name
);
8844 /* working directory */
8845 if (strlen(work_dir
) == 0)
8846 snprintf(work_dir
, sizeof work_dir
, "%s" PS
"%s",
8847 pwd
->pw_dir
, XT_DIR
);
8850 /* icon cache dir */
8851 snprintf(cache_dir
, sizeof cache_dir
, "%s" PS
"%s", work_dir
, XT_CACHE_DIR
);
8855 snprintf(certs_dir
, sizeof certs_dir
, "%s" PS
"%s", work_dir
, XT_CERT_DIR
);
8858 /* cert changes dir */
8859 snprintf(certs_cache_dir
, sizeof certs_cache_dir
, "%s" PS
"%s",
8860 work_dir
, XT_CERT_CACHE_DIR
);
8861 xxx_dir(certs_cache_dir
);
8864 snprintf(sessions_dir
, sizeof sessions_dir
, "%s" PS
"%s",
8865 work_dir
, XT_SESSIONS_DIR
);
8866 xxx_dir(sessions_dir
);
8869 snprintf(js_dir
, sizeof js_dir
, "%s" PS
"%s", work_dir
, XT_JS_DIR
);
8873 snprintf(temp_dir
, sizeof temp_dir
, "%s" PS
"%s", work_dir
, XT_TEMP_DIR
);
8876 /* runtime settings that can override config file */
8877 if (runtime_settings
[0] != '\0')
8878 config_parse(runtime_settings
, 1);
8881 if (!strcmp(download_dir
, pwd
->pw_dir
))
8882 strlcat(download_dir
, PS
"downloads", sizeof download_dir
);
8883 xxx_dir(download_dir
);
8885 /* first start file */
8886 snprintf(file
, sizeof file
, "%s" PS
"%s", work_dir
, XT_SOD_FILE
);
8887 if (stat(file
, &sb
)) {
8888 warnx("start of day file doesn't exist, creating it");
8889 if ((f
= fopen(file
, "w")) == NULL
)
8890 err(1, "startofday");
8891 if (fputs(version
, f
) == EOF
)
8898 if ((f
= fopen(file
, "r+")) == NULL
)
8899 err(1, "startofday");
8900 if (fgets(sodversion
, sizeof sodversion
, f
) == NULL
)
8902 sodversion
[strcspn(sodversion
, "\n")] = '\0';
8903 if (strcmp(version
, sodversion
)) {
8904 if ((f
= freopen(file
, "w", f
)) == NULL
)
8905 err(1, "startofday");
8906 if (fputs(version
, f
) == EOF
)
8909 /* upgrade, say something smart */
8915 /* favorites file */
8916 snprintf(file
, sizeof file
, "%s" PS
"%s", work_dir
, XT_FAVS_FILE
);
8917 if (stat(file
, &sb
)) {
8918 warnx("favorites file doesn't exist, creating it");
8919 if ((f
= fopen(file
, "w")) == NULL
)
8920 err(1, "favorites");
8924 /* quickmarks file */
8925 snprintf(file
, sizeof file
, "%s" PS
"%s", work_dir
, XT_QMARKS_FILE
);
8926 if (stat(file
, &sb
)) {
8927 warnx("quickmarks file doesn't exist, creating it");
8928 if ((f
= fopen(file
, "w")) == NULL
)
8929 err(1, "quickmarks");
8933 /* search history */
8934 if (history_autosave
) {
8935 snprintf(search_file
, sizeof search_file
, "%s" PS
"%s",
8936 work_dir
, XT_SEARCH_FILE
);
8937 if (stat(search_file
, &sb
)) {
8938 warnx("search history file doesn't exist, creating it");
8939 if ((f
= fopen(search_file
, "w")) == NULL
)
8940 err(1, "search_history");
8943 history_read(&shl
, search_file
, &search_history_count
);
8946 /* command history */
8947 if (history_autosave
) {
8948 snprintf(command_file
, sizeof command_file
, "%s" PS
"%s",
8949 work_dir
, XT_COMMAND_FILE
);
8950 if (stat(command_file
, &sb
)) {
8951 warnx("command history file doesn't exist, creating it");
8952 if ((f
= fopen(command_file
, "w")) == NULL
)
8953 err(1, "command_history");
8956 history_read(&chl
, command_file
, &cmd_history_count
);
8962 /* guess_search regex */
8963 if (url_regex
== NULL
)
8964 url_regex
= g_strdup(XT_URL_REGEX
);
8966 if (regcomp(&url_re
, url_regex
, REG_EXTENDED
| REG_NOSUB
))
8967 startpage_add("invalid url regex %s", url_regex
);
8970 env_proxy
= getenv("http_proxy");
8972 setup_proxy(env_proxy
);
8974 env_proxy
= getenv("HTTP_PROXY");
8976 setup_proxy(env_proxy
);
8978 setup_proxy(http_proxy
);
8981 /* the user can optionally have the proxy disabled at startup */
8982 if ((http_proxy_starts_enabled
== 0) && (http_proxy
!= NULL
)) {
8983 http_proxy_save
= g_strdup(http_proxy
);
8987 #ifndef XT_SOCKET_DISABLE
8989 send_cmd_to_socket(argv
[0]);
8993 opte
= opte
; /* shut mingw up */
8995 /* set some connection parameters */
8996 g_object_set(session
, "max-conns", max_connections
, (char *)NULL
);
8997 g_object_set(session
, "max-conns-per-host", max_host_connections
,
8999 g_object_set(session
, SOUP_SESSION_SSL_STRICT
, ssl_strict_certs
,
9002 g_signal_connect(session
, "request-queued", G_CALLBACK(session_rq_cb
),
9005 #ifndef XT_SOCKET_DISABLE
9006 /* see if there is already a xombrero running */
9007 if (single_instance
&& is_running()) {
9009 warnx("already running");
9014 cmd
= g_strdup_printf("%s %s", "tabnew", argv
[0]);
9015 send_cmd_to_socket(cmd
);
9025 optn
= optn
; /* shut mingw up */
9030 if (enable_strict_transport
)
9031 strict_transport_init();
9033 /* uri completion */
9034 completion_model
= gtk_list_store_new(1, G_TYPE_STRING
);
9037 buffers_store
= gtk_list_store_new
9038 (NUM_COLS
, G_TYPE_UINT
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
);
9044 notebook_tab_set_visibility();
9046 if (save_global_history
)
9047 restore_global_history();
9049 /* restore session list */
9050 restore_sessions_list();
9052 if (!strcmp(named_session
, XT_SAVED_TABS_FILE
))
9053 restore_saved_tabs();
9055 a
.s
= named_session
;
9056 a
.i
= XT_SES_DONOTHING
;
9057 open_tabs(NULL
, &a
);
9060 /* see if we have an exception */
9061 if (!TAILQ_EMPTY(&spl
)) {
9062 create_new_tab("about:startpage", NULL
, focus
, -1);
9067 create_new_tab(argv
[0], NULL
, focus
, -1);
9074 if (TAILQ_EMPTY(&tabs
))
9075 create_new_tab(home
, NULL
, 1, -1);
9076 #ifndef XT_SOCKET_DISABLE
9078 if ((s
= build_socket()) != -1) {
9079 channel
= g_io_channel_unix_new(s
);
9080 g_io_add_watch(channel
, G_IO_IN
, socket_watcher
, NULL
);
9084 #if GTK_CHECK_VERSION(3, 0, 0)
9092 gdk_threads_leave();
9093 g_static_rec_mutex_unlock_full(&my_gdk_mtx
); /* just in case */
9096 gnutls_global_deinit();