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 toggle_src(struct tab
*t
, struct karg
*args
)
1611 mode
= webkit_web_view_get_view_source_mode(t
->wv
);
1612 webkit_web_view_set_view_source_mode(t
->wv
, !mode
);
1613 webkit_web_view_reload(t
->wv
);
1619 focus_webview(struct tab
*t
)
1624 /* only grab focus if we are visible */
1625 if (gtk_notebook_get_current_page(notebook
) == t
->tab_id
)
1626 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
1630 focus(struct tab
*t
, struct karg
*args
)
1632 if (t
== NULL
|| args
== NULL
)
1638 if (args
->i
== XT_FOCUS_URI
)
1639 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
1640 else if (args
->i
== XT_FOCUS_SEARCH
)
1641 gtk_widget_grab_focus(GTK_WIDGET(t
->search_entry
));
1647 connect_socket_from_uri(const gchar
*uri
, const gchar
**error_str
, char *domain
,
1651 struct addrinfo hints
, *res
= NULL
, *ai
;
1652 int rv
= -1, s
= -1, on
, error
;
1654 static gchar myerror
[256]; /* this is not thread safe */
1657 *error_str
= myerror
;
1658 if (uri
&& !g_str_has_prefix(uri
, "https://")) {
1659 *error_str
= "invalid URI";
1663 su
= soup_uri_new(uri
);
1665 *error_str
= "invalid soup URI";
1668 if (!SOUP_URI_VALID_FOR_HTTP(su
)) {
1669 *error_str
= "invalid HTTPS URI";
1673 snprintf(port
, sizeof port
, "%d", su
->port
);
1674 bzero(&hints
, sizeof(struct addrinfo
));
1675 hints
.ai_flags
= AI_CANONNAME
;
1676 hints
.ai_family
= AF_UNSPEC
;
1677 hints
.ai_socktype
= SOCK_STREAM
;
1679 if ((error
= getaddrinfo(su
->host
, port
, &hints
, &res
))) {
1680 snprintf(myerror
, sizeof myerror
, "getaddrinfo failed: %s",
1681 gai_strerror(errno
));
1685 for (ai
= res
; ai
; ai
= ai
->ai_next
) {
1691 if (ai
->ai_family
!= AF_INET
&& ai
->ai_family
!= AF_INET6
)
1693 s
= socket(ai
->ai_family
, ai
->ai_socktype
, ai
->ai_protocol
);
1696 if (setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, &on
,
1699 if (connect(s
, ai
->ai_addr
, ai
->ai_addrlen
) == 0)
1703 snprintf(myerror
, sizeof myerror
,
1704 "could not obtain certificates from: %s",
1710 strlcpy(domain
, su
->host
, domain_sz
);
1717 if (rv
== -1 && s
!= -1)
1725 custom_gnutls_push(void *s
, const void *buf
, size_t len
)
1727 return send((size_t)s
, buf
, len
, 0);
1731 custom_gnutls_pull(void *s
, void *buf
, size_t len
)
1733 return recv((size_t)s
, buf
, len
, 0);
1738 stop_tls(gnutls_session_t gsession
, gnutls_certificate_credentials_t xcred
)
1741 gnutls_deinit(gsession
);
1743 gnutls_certificate_free_credentials(xcred
);
1749 start_tls(const gchar
**error_str
, int s
, gnutls_session_t
*gs
,
1750 gnutls_certificate_credentials_t
*xc
)
1752 gnutls_certificate_credentials_t xcred
;
1753 gnutls_session_t gsession
;
1755 static gchar myerror
[1024]; /* this is not thread safe */
1757 if (gs
== NULL
|| xc
== NULL
)
1764 gnutls_certificate_allocate_credentials(&xcred
);
1765 gnutls_certificate_set_x509_trust_file(xcred
, ssl_ca_file
,
1766 GNUTLS_X509_FMT_PEM
);
1768 gnutls_init(&gsession
, GNUTLS_CLIENT
);
1769 gnutls_priority_set_direct(gsession
, "PERFORMANCE", NULL
);
1770 gnutls_credentials_set(gsession
, GNUTLS_CRD_CERTIFICATE
, xcred
);
1771 gnutls_transport_set_ptr(gsession
, (gnutls_transport_ptr_t
)(long)s
);
1773 /* sockets on windows don't use file descriptors */
1774 gnutls_transport_set_push_function(gsession
, custom_gnutls_push
);
1775 gnutls_transport_set_pull_function(gsession
, custom_gnutls_pull
);
1777 if ((rv
= gnutls_handshake(gsession
)) < 0) {
1778 snprintf(myerror
, sizeof myerror
,
1779 "gnutls_handshake failed %d fatal %d %s",
1781 gnutls_error_is_fatal(rv
),
1782 #if LIBGNUTLS_VERSION_MAJOR >= 2 && LIBGNUTLS_VERSION_MINOR >= 6
1783 gnutls_strerror_name(rv
));
1785 "GNUTLS version is too old to provide human readable error");
1787 stop_tls(gsession
, xcred
);
1791 gnutls_credentials_type_t cred
;
1792 cred
= gnutls_auth_get_type(gsession
);
1793 if (cred
!= GNUTLS_CRD_CERTIFICATE
) {
1794 snprintf(myerror
, sizeof myerror
,
1795 "gnutls_auth_get_type failed %d",
1797 stop_tls(gsession
, xcred
);
1805 *error_str
= myerror
;
1810 get_connection_certs(gnutls_session_t gsession
, gnutls_x509_crt_t
**certs
,
1814 const gnutls_datum_t
*cl
;
1815 gnutls_x509_crt_t
*all_certs
;
1818 if (certs
== NULL
|| cert_count
== NULL
)
1820 if (gnutls_certificate_type_get(gsession
) != GNUTLS_CRT_X509
)
1822 cl
= gnutls_certificate_get_peers(gsession
, &len
);
1826 all_certs
= g_malloc(sizeof(gnutls_x509_crt_t
) * len
);
1827 for (i
= 0; i
< len
; i
++) {
1828 gnutls_x509_crt_init(&all_certs
[i
]);
1829 if (gnutls_x509_crt_import(all_certs
[i
], &cl
[i
],
1830 GNUTLS_X509_FMT_PEM
< 0)) {
1844 free_connection_certs(gnutls_x509_crt_t
*certs
, size_t cert_count
)
1848 for (i
= 0; i
< cert_count
; i
++)
1849 gnutls_x509_crt_deinit(certs
[i
]);
1853 #if GTK_CHECK_VERSION(3, 0, 0)
1855 statusbar_modify_attr(struct tab
*t
, const char *css_name
)
1857 gtk_widget_set_name(t
->sbe
.ebox
, css_name
);
1861 statusbar_modify_attr(struct tab
*t
, const char *text
, const char *base
)
1863 GdkColor c_text
, c_base
;
1865 gdk_color_parse(text
, &c_text
);
1866 gdk_color_parse(base
, &c_base
);
1868 gtk_widget_modify_bg(t
->sbe
.ebox
, GTK_STATE_NORMAL
, &c_base
);
1869 gtk_widget_modify_base(t
->sbe
.uri
, GTK_STATE_NORMAL
, &c_base
);
1870 gtk_widget_modify_text(t
->sbe
.uri
, GTK_STATE_NORMAL
, &c_text
);
1875 save_certs(struct tab
*t
, gnutls_x509_crt_t
*certs
,
1876 size_t cert_count
, const char *domain
, const char *dir
)
1879 char cert_buf
[64 * 1024], file
[PATH_MAX
];
1883 if (t
== NULL
|| certs
== NULL
|| cert_count
<= 0 || domain
== NULL
)
1886 snprintf(file
, sizeof file
, "%s" PS
"%s", dir
, domain
);
1887 if ((f
= fopen(file
, "w")) == NULL
) {
1888 show_oops(t
, "Can't create cert file %s %s",
1889 file
, strerror(errno
));
1893 for (i
= 0; i
< cert_count
; i
++) {
1894 cert_buf_sz
= sizeof cert_buf
;
1895 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
1896 cert_buf
, &cert_buf_sz
)) {
1897 show_oops(t
, "gnutls_x509_crt_export failed");
1900 if (fwrite(cert_buf
, cert_buf_sz
, 1, f
) != 1) {
1901 show_oops(t
, "Can't write certs: %s", strerror(errno
));
1918 load_compare_cert(const gchar
*uri
, const gchar
**error_str
, const char *dir
)
1920 char domain
[8182], file
[PATH_MAX
];
1921 char cert_buf
[64 * 1024], r_cert_buf
[64 * 1024];
1923 unsigned int error
= 0;
1925 size_t cert_buf_sz
, cert_count
;
1926 enum cert_trust rv
= CERT_UNTRUSTED
;
1927 static gchar serr
[80]; /* this isn't thread safe */
1928 gnutls_session_t gsession
;
1929 gnutls_x509_crt_t
*certs
;
1930 gnutls_certificate_credentials_t xcred
;
1932 DNPRINTF(XT_D_URL
, "%s: %s\n", __func__
, uri
);
1936 if ((s
= connect_socket_from_uri(uri
, error_str
, domain
,
1937 sizeof domain
)) == -1)
1940 DNPRINTF(XT_D_URL
, "%s: fd %d\n", __func__
, s
);
1943 if (start_tls(error_str
, s
, &gsession
, &xcred
))
1945 DNPRINTF(XT_D_URL
, "%s: got tls\n", __func__
);
1947 /* verify certs in case cert file doesn't exist */
1948 if (gnutls_certificate_verify_peers2(gsession
, &error
) !=
1950 *error_str
= "Invalid certificates";
1955 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
1956 *error_str
= "Can't get connection certificates";
1960 snprintf(file
, sizeof file
, "%s" PS
"%s", dir
, domain
);
1961 if ((f
= fopen(file
, "r")) == NULL
) {
1967 for (i
= 0; i
< cert_count
; i
++) {
1968 cert_buf_sz
= sizeof cert_buf
;
1969 if (gnutls_x509_crt_export(certs
[i
], GNUTLS_X509_FMT_PEM
,
1970 cert_buf
, &cert_buf_sz
)) {
1973 if (fread(r_cert_buf
, cert_buf_sz
, 1, f
) != 1 && !feof(f
)) {
1974 rv
= CERT_BAD
; /* critical */
1977 if (bcmp(r_cert_buf
, cert_buf
, cert_buf_sz
)) {
1978 rv
= CERT_BAD
; /* critical */
1987 free_connection_certs(certs
, cert_count
);
1989 /* we close the socket first for speed */
1993 /* only complain if we didn't save it locally */
1994 if (strlen(ssl_ca_file
) != 0 && error
&& rv
!= CERT_LOCAL
) {
1995 strlcpy(serr
, "Certificate exception(s): ", sizeof serr
);
1996 if (error
& GNUTLS_CERT_INVALID
)
1997 strlcat(serr
, "invalid, ", sizeof serr
);
1998 if (error
& GNUTLS_CERT_REVOKED
)
1999 strlcat(serr
, "revoked, ", sizeof serr
);
2000 if (error
& GNUTLS_CERT_SIGNER_NOT_FOUND
)
2001 strlcat(serr
, "signer not found, ", sizeof serr
);
2002 if (error
& GNUTLS_CERT_SIGNER_NOT_CA
)
2003 strlcat(serr
, "not signed by CA, ", sizeof serr
);
2004 if (error
& GNUTLS_CERT_INSECURE_ALGORITHM
)
2005 strlcat(serr
, "insecure algorithm, ", sizeof serr
);
2006 #if LIBGNUTLS_VERSION_MAJOR >= 2 && LIBGNUTLS_VERSION_MINOR >= 6
2007 if (error
& GNUTLS_CERT_NOT_ACTIVATED
)
2008 strlcat(serr
, "not activated, ", sizeof serr
);
2009 if (error
& GNUTLS_CERT_EXPIRED
)
2010 strlcat(serr
, "expired, ", sizeof serr
);
2012 for (i
= strlen(serr
) - 1; i
> 0; i
--)
2013 if (serr
[i
] == ',') {
2020 stop_tls(gsession
, xcred
);
2026 get_local_cert_chain(const char *uri
, size_t *ncerts
, const char **error_str
,
2030 unsigned char cert_buf
[64 * 1024] = {0};
2031 gnutls_datum_t data
;
2032 unsigned int len
= UINT_MAX
;
2034 char file
[PATH_MAX
];
2036 gnutls_x509_crt_t
*certs
;
2038 if ((su
= soup_uri_new(uri
)) == NULL
) {
2039 *error_str
= "Invalid URI";
2043 snprintf(file
, sizeof file
, "%s" PS
"%s", dir
, su
->host
);
2044 if ((f
= fopen(file
, "r")) == NULL
) {
2045 *error_str
= "Could not read local cert";
2049 bytes_read
= fread(cert_buf
, sizeof *cert_buf
, sizeof cert_buf
, f
);
2050 if (bytes_read
== 0) {
2051 *error_str
= "Could not read local cert";
2055 data
.data
= cert_buf
;
2056 data
.size
= bytes_read
;
2057 certs
= g_malloc(sizeof *certs
);
2059 if (gnutls_x509_crt_list_import(certs
, &len
, &data
,
2060 GNUTLS_X509_FMT_PEM
, 0) < 0) {
2061 *error_str
= "Error reading local cert chain";
2071 cert_cmd(struct tab
*t
, struct karg
*args
)
2073 const gchar
*uri
, *error_str
= NULL
;
2077 gnutls_session_t gsession
;
2078 gnutls_x509_crt_t
*certs
;
2079 gnutls_certificate_credentials_t xcred
;
2080 #if !GTK_CHECK_VERSION(3, 0, 0)
2087 if (args
->s
!= NULL
)
2089 else if ((uri
= get_uri(t
)) == NULL
) {
2090 show_oops(t
, "Invalid URI");
2095 * if we're only showing the local certs, don't open a socket and get
2098 if (args
->i
& XT_SHOW
&& args
->i
& XT_CACHE
) {
2099 certs
= get_local_cert_chain(uri
, &cert_count
, &error_str
,
2101 if (error_str
== NULL
) {
2102 show_certs(t
, certs
, cert_count
, "Certificate Chain");
2103 free_connection_certs(certs
, cert_count
);
2105 show_oops(t
, "%s", error_str
);
2111 if ((s
= connect_socket_from_uri(uri
, &error_str
, domain
,
2112 sizeof domain
)) == -1) {
2113 show_oops(t
, "%s", error_str
);
2118 if (start_tls(&error_str
, s
, &gsession
, &xcred
))
2122 if (get_connection_certs(gsession
, &certs
, &cert_count
)) {
2123 show_oops(t
, "get_connection_certs failed");
2127 if (args
->i
& XT_SHOW
)
2128 show_certs(t
, certs
, cert_count
, "Certificate Chain");
2129 else if (args
->i
& XT_SAVE
) {
2130 save_certs(t
, certs
, cert_count
, domain
, certs_dir
);
2131 #if GTK_CHECK_VERSION(3, 0, 0)
2132 gtk_widget_set_name(t
->uri_entry
, XT_CSS_BLUE
);
2133 statusbar_modify_attr(t
, XT_CSS_BLUE
);
2135 gdk_color_parse(XT_COLOR_BLUE
, &color
);
2136 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
2137 statusbar_modify_attr(t
, XT_COLOR_BLACK
, XT_COLOR_BLUE
);
2139 } else if (args
->i
& XT_CACHE
)
2140 save_certs(t
, certs
, cert_count
, domain
, certs_cache_dir
);
2142 free_connection_certs(certs
, cert_count
);
2144 /* we close the socket first for speed */
2147 stop_tls(gsession
, xcred
);
2148 if (error_str
&& strlen(error_str
))
2149 show_oops(t
, "%s", error_str
);
2154 * args must be allocated dynamically as the thread that added this function
2155 * to the idle loop no longer exists
2158 warn_cert_cache_differs_idle(struct karg
*args
)
2161 show_oops(NULL
, "%s: invalid parameters", __func__
);
2162 /* return 0 to not re-add function to the idle loop */
2165 xtp_page_sv((struct tab
*)args
->ptr
, args
);
2171 check_cert_changes(struct tab
*t
, const char *uri
)
2173 SoupURI
*soupuri
= NULL
;
2174 struct karg args
= {0};
2175 struct wl_entry
*w
= NULL
;
2176 const char *errstr
= NULL
;
2179 if (!(warn_cert_changes
&& g_str_has_prefix(uri
, "https://")))
2182 switch (load_compare_cert(uri
, &errstr
, certs_cache_dir
)) {
2184 /* The cached certificate is identical */
2186 case CERT_TRUSTED
: /* FALLTHROUGH */
2187 case CERT_UNTRUSTED
:
2188 /* cache new certificate */
2193 if ((soupuri
= soup_uri_new(uri
)) == NULL
||
2194 soupuri
->host
== NULL
)
2196 if ((w
= wl_find(soupuri
->host
, &svil
)) != NULL
)
2198 t
->xtp_meaning
= XT_XTP_TAB_MEANING_SV
;
2199 argsp
= g_malloc0(sizeof(struct karg
));
2200 argsp
->s
= g_strdup((char *)uri
);
2201 argsp
->ptr
= (void *)t
;
2202 g_idle_add((GSourceFunc
)warn_cert_cache_differs_idle
, argsp
);
2207 soup_uri_free(soupuri
);
2212 remove_cookie(int index
)
2218 DNPRINTF(XT_D_COOKIE
, "remove_cookie: %d\n", index
);
2220 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
2222 for (i
= 1; cf
; cf
= cf
->next
, i
++) {
2226 print_cookie("remove cookie", c
);
2227 soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
2232 soup_cookies_free(cf
);
2238 remove_cookie_domain(int domain_id
)
2240 int domain_count
, rv
= 1;
2245 DNPRINTF(XT_D_COOKIE
, "remove_cookie_domain: %d\n", domain_id
);
2248 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
2250 for (domain_count
= 0; cf
; cf
= cf
->next
) {
2253 if (strcmp(last_domain
, c
->domain
) != 0) {
2255 last_domain
= c
->domain
;
2258 if (domain_count
< domain_id
)
2260 else if (domain_count
> domain_id
)
2263 print_cookie("remove cookie", c
);
2264 soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
2268 soup_cookies_free(cf
);
2280 DNPRINTF(XT_D_COOKIE
, "remove_cookie_all\n");
2282 cf
= soup_cookie_jar_all_cookies(s_cookiejar
);
2284 for (; cf
; cf
= cf
->next
) {
2287 print_cookie("remove cookie", c
);
2288 soup_cookie_jar_delete_cookie(s_cookiejar
, c
);
2292 soup_cookies_free(cf
);
2298 toplevel_cmd(struct tab
*t
, struct karg
*args
)
2300 js_toggle_cb(t
->js_toggle
, t
);
2306 can_go_back_for_real(struct tab
*t
)
2309 WebKitWebHistoryItem
*item
;
2315 if (t
->item
!= NULL
)
2318 /* rely on webkit to make sure we can go backward when on an about page */
2320 if (uri
== NULL
|| g_str_has_prefix(uri
, "about:") ||
2321 g_str_has_prefix(uri
, "xxxt://"))
2322 return (webkit_web_view_can_go_back(t
->wv
));
2324 /* the back/forward list is stupid so help determine if we can go back */
2325 for (i
= 0, item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
2327 i
--, item
= webkit_web_back_forward_list_get_nth_item(t
->bfl
, i
)) {
2328 if (strcmp(webkit_web_history_item_get_uri(item
), uri
))
2336 can_go_forward_for_real(struct tab
*t
)
2339 WebKitWebHistoryItem
*item
;
2345 /* rely on webkit to make sure we can go forward when on an about page */
2347 if (uri
== NULL
|| g_str_has_prefix(uri
, "about:") ||
2348 g_str_has_prefix(uri
, "xxxt://"))
2349 return (webkit_web_view_can_go_forward(t
->wv
));
2351 /* the back/forwars list is stupid so help selecting a different item */
2352 for (i
= 0, item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
2354 i
++, item
= webkit_web_back_forward_list_get_nth_item(t
->bfl
, i
)) {
2355 if (strcmp(webkit_web_history_item_get_uri(item
), uri
))
2363 go_back_for_real(struct tab
*t
)
2366 WebKitWebHistoryItem
*item
;
2373 if (uri
== NULL
|| g_str_has_prefix(uri
, "about:") ||
2374 g_str_has_prefix(uri
, "xxxt://")) {
2375 webkit_web_view_go_back(t
->wv
);
2378 /* the back/forwars list is stupid so help selecting a different item */
2379 for (i
= 0, item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
2381 i
--, item
= webkit_web_back_forward_list_get_nth_item(t
->bfl
, i
)) {
2382 if (strcmp(webkit_web_history_item_get_uri(item
), uri
)) {
2383 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
2390 go_forward_for_real(struct tab
*t
)
2393 WebKitWebHistoryItem
*item
;
2400 if (uri
== NULL
|| g_str_has_prefix(uri
, "about:") ||
2401 g_str_has_prefix(uri
, "xxxt://")) {
2402 webkit_web_view_go_forward(t
->wv
);
2405 /* the back/forwars list is stupid so help selecting a different item */
2406 for (i
= 0, item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
2408 i
++, item
= webkit_web_back_forward_list_get_nth_item(t
->bfl
, i
)) {
2409 if (strcmp(webkit_web_history_item_get_uri(item
), uri
)) {
2410 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
2417 navaction(struct tab
*t
, struct karg
*args
)
2419 WebKitWebHistoryItem
*item
= NULL
;
2420 WebKitWebFrame
*frame
;
2422 DNPRINTF(XT_D_NAV
, "navaction: tab %d opcode %d\n",
2423 t
->tab_id
, args
->i
);
2426 set_normal_tab_meaning(t
);
2428 if (args
->i
== XT_NAV_BACK
)
2429 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
2431 item
= webkit_web_back_forward_list_get_forward_item(t
->bfl
);
2438 return (XT_CB_PASSTHROUGH
);
2439 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
2441 return (XT_CB_PASSTHROUGH
);
2444 go_back_for_real(t
);
2446 case XT_NAV_FORWARD
:
2449 return (XT_CB_PASSTHROUGH
);
2450 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
2452 return (XT_CB_PASSTHROUGH
);
2455 go_forward_for_real(t
);
2458 frame
= webkit_web_view_get_main_frame(t
->wv
);
2459 webkit_web_frame_reload(frame
);
2462 frame
= webkit_web_view_get_main_frame(t
->wv
);
2463 webkit_web_frame_stop_loading(frame
);
2466 return (XT_CB_PASSTHROUGH
);
2470 move(struct tab
*t
, struct karg
*args
)
2472 GtkAdjustment
*adjust
;
2473 double pi
, si
, pos
, ps
, upper
, lower
, max
;
2479 case XT_MOVE_BOTTOM
:
2481 case XT_MOVE_PAGEDOWN
:
2482 case XT_MOVE_PAGEUP
:
2483 case XT_MOVE_HALFDOWN
:
2484 case XT_MOVE_HALFUP
:
2485 case XT_MOVE_PERCENT
:
2486 case XT_MOVE_CENTER
:
2487 adjust
= t
->adjust_v
;
2490 adjust
= t
->adjust_h
;
2494 pos
= gtk_adjustment_get_value(adjust
);
2495 ps
= gtk_adjustment_get_page_size(adjust
);
2496 upper
= gtk_adjustment_get_upper(adjust
);
2497 lower
= gtk_adjustment_get_lower(adjust
);
2498 si
= gtk_adjustment_get_step_increment(adjust
);
2499 pi
= gtk_adjustment_get_page_increment(adjust
);
2502 DNPRINTF(XT_D_MOVE
, "move: opcode %d %s pos %f ps %f upper %f lower %f "
2503 "max %f si %f pi %f\n",
2504 args
->i
, adjust
== t
->adjust_h
? "horizontal" : "vertical",
2505 pos
, ps
, upper
, lower
, max
, si
, pi
);
2511 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
2516 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
2518 case XT_MOVE_BOTTOM
:
2519 case XT_MOVE_FARRIGHT
:
2520 t
->mark
[marktoindex('\'')] = gtk_adjustment_get_value(t
->adjust_v
);
2521 gtk_adjustment_set_value(adjust
, max
);
2524 case XT_MOVE_FARLEFT
:
2525 t
->mark
[marktoindex('\'')] = gtk_adjustment_get_value(t
->adjust_v
);
2526 gtk_adjustment_set_value(adjust
, lower
);
2528 case XT_MOVE_PAGEDOWN
:
2530 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
2532 case XT_MOVE_PAGEUP
:
2534 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
2536 case XT_MOVE_HALFDOWN
:
2538 gtk_adjustment_set_value(adjust
, MIN(pos
, max
));
2540 case XT_MOVE_HALFUP
:
2542 gtk_adjustment_set_value(adjust
, MAX(pos
, lower
));
2544 case XT_MOVE_CENTER
:
2545 t
->mark
[marktoindex('\'')] = gtk_adjustment_get_value(t
->adjust_v
);
2546 args
->s
= g_strdup("50.0");
2548 case XT_MOVE_PERCENT
:
2549 t
->mark
[marktoindex('\'')] = gtk_adjustment_get_value(t
->adjust_v
);
2550 percent
= atoi(args
->s
) / 100.0;
2551 pos
= max
* percent
;
2552 if (pos
< 0.0 || pos
> max
)
2554 gtk_adjustment_set_value(adjust
, pos
);
2557 return (XT_CB_PASSTHROUGH
);
2560 DNPRINTF(XT_D_MOVE
, "move: new pos %f %f\n", pos
, MIN(pos
, max
));
2562 return (XT_CB_HANDLED
);
2566 url_set_visibility(void)
2570 TAILQ_FOREACH(t
, &tabs
, entry
)
2571 if (show_url
== 0) {
2572 gtk_widget_hide(t
->toolbar
);
2575 gtk_widget_show(t
->toolbar
);
2579 notebook_tab_set_visibility(void)
2581 if (show_tabs
== 0) {
2582 gtk_widget_hide(tab_bar
);
2583 gtk_notebook_set_show_tabs(notebook
, FALSE
);
2585 if (tab_style
== XT_TABS_NORMAL
) {
2586 gtk_widget_hide(tab_bar
);
2587 gtk_notebook_set_show_tabs(notebook
, TRUE
);
2588 } else if (tab_style
== XT_TABS_COMPACT
) {
2589 gtk_widget_show(tab_bar
);
2590 gtk_notebook_set_show_tabs(notebook
, FALSE
);
2596 statusbar_set_visibility(void)
2600 TAILQ_FOREACH(t
, &tabs
, entry
){
2601 if (show_statusbar
== 0)
2602 gtk_widget_hide(t
->statusbar
);
2604 gtk_widget_show(t
->statusbar
);
2611 url_set(struct tab
*t
, int enable_url_entry
)
2616 show_url
= enable_url_entry
;
2618 if (enable_url_entry
) {
2619 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->sbe
.uri
),
2620 GTK_ENTRY_ICON_PRIMARY
, NULL
);
2621 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->sbe
.uri
), 0);
2623 pixbuf
= gtk_entry_get_icon_pixbuf(GTK_ENTRY(t
->uri_entry
),
2624 GTK_ENTRY_ICON_PRIMARY
);
2626 gtk_entry_get_progress_fraction(GTK_ENTRY(t
->uri_entry
));
2627 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->sbe
.uri
),
2628 GTK_ENTRY_ICON_PRIMARY
, pixbuf
);
2629 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->sbe
.uri
),
2635 fullscreen(struct tab
*t
, struct karg
*args
)
2637 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
2640 return (XT_CB_PASSTHROUGH
);
2642 if (show_url
== 0) {
2650 url_set_visibility();
2651 notebook_tab_set_visibility();
2653 return (XT_CB_HANDLED
);
2657 statustoggle(struct tab
*t
, struct karg
*args
)
2659 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
2661 if (show_statusbar
== 1) {
2663 statusbar_set_visibility();
2664 } else if (show_statusbar
== 0) {
2666 statusbar_set_visibility();
2668 return (XT_CB_HANDLED
);
2672 urlaction(struct tab
*t
, struct karg
*args
)
2674 int rv
= XT_CB_HANDLED
;
2676 DNPRINTF(XT_D_TAB
, "%s: %p %d\n", __func__
, t
, args
->i
);
2679 return (XT_CB_PASSTHROUGH
);
2683 if (show_url
== 0) {
2685 url_set_visibility();
2689 if (show_url
== 1) {
2691 url_set_visibility();
2699 tabaction(struct tab
*t
, struct karg
*args
)
2701 int rv
= XT_CB_HANDLED
;
2702 char *url
= args
->s
;
2704 struct tab
*tt
, *tv
;
2706 DNPRINTF(XT_D_TAB
, "tabaction: %p %d\n", t
, args
->i
);
2709 return (XT_CB_PASSTHROUGH
);
2713 if (strlen(url
) > 0)
2714 create_new_tab(url
, NULL
, 1, args
->precount
);
2716 create_new_tab(NULL
, NULL
, 1, args
->precount
);
2719 if (args
->precount
< 0)
2722 TAILQ_FOREACH(tt
, &tabs
, entry
)
2723 if (tt
->tab_id
== args
->precount
- 1) {
2728 case XT_TAB_DELQUIT
:
2729 if (gtk_notebook_get_n_pages(notebook
) > 1)
2735 TAILQ_FOREACH_SAFE(tt
, &tabs
, entry
, tv
)
2740 if (strlen(url
) > 0)
2743 rv
= XT_CB_PASSTHROUGH
;
2749 if (show_tabs
== 0) {
2751 notebook_tab_set_visibility();
2755 if (show_tabs
== 1) {
2757 notebook_tab_set_visibility();
2760 case XT_TAB_NEXTSTYLE
:
2761 if (tab_style
== XT_TABS_NORMAL
) {
2762 tab_style
= XT_TABS_COMPACT
;
2763 recolor_compact_tabs();
2766 tab_style
= XT_TABS_NORMAL
;
2767 notebook_tab_set_visibility();
2769 case XT_TAB_UNDO_CLOSE
:
2770 if (undo_count
== 0) {
2771 DNPRINTF(XT_D_TAB
, "%s: no tabs to undo close",
2776 u
= TAILQ_FIRST(&undos
);
2777 create_new_tab(u
->uri
, u
, 1, -1);
2779 TAILQ_REMOVE(&undos
, u
, entry
);
2781 /* u->history is freed in create_new_tab() */
2785 case XT_TAB_LOAD_IMAGES
:
2787 if (!auto_load_images
) {
2789 /* Enable auto-load images (this will load all
2790 * previously unloaded images). */
2791 g_object_set(G_OBJECT(t
->settings
),
2792 "auto-load-images", TRUE
, (char *)NULL
);
2793 webkit_web_view_set_settings(t
->wv
, t
->settings
);
2795 webkit_web_view_reload(t
->wv
);
2797 /* Webkit triggers an event when we change the setting,
2798 * so we can't disable the auto-loading at once.
2800 * Unfortunately, webkit does not tell us when it's done.
2801 * Instead, we wait until the next request, and then
2802 * disable autoloading again.
2804 t
->load_images
= TRUE
;
2808 rv
= XT_CB_PASSTHROUGH
;
2822 resizetab(struct tab
*t
, struct karg
*args
)
2824 if (t
== NULL
|| args
== NULL
) {
2825 show_oops(NULL
, "resizetab invalid parameters");
2826 return (XT_CB_PASSTHROUGH
);
2829 DNPRINTF(XT_D_TAB
, "resizetab: tab %d %d\n",
2830 t
->tab_id
, args
->i
);
2832 setzoom_webkit(t
, args
->i
);
2834 return (XT_CB_HANDLED
);
2838 movetab(struct tab
*t
, struct karg
*args
)
2842 if (t
== NULL
|| args
== NULL
) {
2843 show_oops(NULL
, "movetab invalid parameters");
2844 return (XT_CB_PASSTHROUGH
);
2847 DNPRINTF(XT_D_TAB
, "movetab: tab %d opcode %d\n",
2848 t
->tab_id
, args
->i
);
2850 if (args
->i
>= XT_TAB_INVALID
)
2851 return (XT_CB_PASSTHROUGH
);
2853 if (TAILQ_EMPTY(&tabs
))
2854 return (XT_CB_PASSTHROUGH
);
2856 n
= gtk_notebook_get_n_pages(notebook
);
2857 dest
= gtk_notebook_get_current_page(notebook
);
2861 if (args
->precount
< 0)
2862 dest
= dest
== n
- 1 ? 0 : dest
+ 1;
2864 dest
= args
->precount
- 1;
2868 if (args
->precount
< 0)
2871 dest
-= args
->precount
% n
;
2884 return (XT_CB_PASSTHROUGH
);
2887 if (dest
< 0 || dest
>= n
)
2888 return (XT_CB_PASSTHROUGH
);
2889 if (t
->tab_id
== dest
) {
2890 DNPRINTF(XT_D_TAB
, "movetab: do nothing\n");
2891 return (XT_CB_HANDLED
);
2894 set_current_tab(dest
);
2896 return (XT_CB_HANDLED
);
2903 const char *(*f
)(struct tab
*);
2905 { "<uri>", get_uri
},
2909 command(struct tab
*t
, struct karg
*args
)
2911 struct karg a
= {0};
2912 int i
, cmd_setup
= 0;
2913 char *s
= NULL
, *sp
= NULL
, *sl
= NULL
;
2916 if (t
== NULL
|| args
== NULL
) {
2917 show_oops(NULL
, "command invalid parameters");
2918 return (XT_CB_PASSTHROUGH
);
2929 if (cmd_prefix
== 0) {
2930 if (args
->s
!= NULL
&& strlen(args
->s
) != 0) {
2931 sp
= g_strdup_printf(":%s", args
->s
);
2936 sp
= g_strdup_printf(":%d", cmd_prefix
);
2946 for (i
= 0; i
< LENGTH(subs
); ++i
) {
2947 sv
= g_strsplit(sl
, subs
[i
].s
, -1);
2950 sl
= g_strjoinv(subs
[i
].f(t
), sv
);
2956 t
->mode
= XT_MODE_HINT
;
2960 * js code will auto fire() if a single link is visible,
2961 * causing the focus-out-event cb function to be called. Setup
2962 * the cmd _before_ triggering hinting code so the cmd can get
2963 * killed by the cb in this case.
2970 t
->mode
= XT_MODE_HINT
;
2971 a
.i
= XT_HINT_NEWTAB
;
2978 show_oops(t
, "command: invalid opcode %d", args
->i
);
2979 return (XT_CB_PASSTHROUGH
);
2982 DNPRINTF(XT_D_CMD
, "%s: tab %d type %s\n", __func__
, t
->tab_id
, s
);
2992 return (XT_CB_HANDLED
);
2996 search(struct tab
*t
, struct karg
*args
)
3000 if (t
== NULL
|| args
== NULL
) {
3001 show_oops(NULL
, "search invalid parameters");
3006 case XT_SEARCH_NEXT
:
3007 d
= t
->search_forward
;
3009 case XT_SEARCH_PREV
:
3010 d
= !t
->search_forward
;
3013 return (XT_CB_PASSTHROUGH
);
3016 if (t
->search_text
== NULL
) {
3017 if (global_search
== NULL
)
3018 return (XT_CB_PASSTHROUGH
);
3020 d
= t
->search_forward
= TRUE
;
3021 t
->search_text
= g_strdup(global_search
);
3022 webkit_web_view_mark_text_matches(t
->wv
, global_search
, FALSE
, 0);
3023 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
3027 DNPRINTF(XT_D_CMD
, "search: tab %d opc %d forw %d text %s\n",
3028 t
->tab_id
, args
->i
, t
->search_forward
, t
->search_text
);
3030 webkit_web_view_search_text(t
->wv
, t
->search_text
, FALSE
, d
, TRUE
);
3032 return (XT_CB_HANDLED
);
3036 session_save(struct tab
*t
, char *filename
)
3042 if (strlen(filename
) == 0)
3045 if (filename
[0] == '.' || filename
[0] == '/')
3049 if (save_tabs(t
, &a
))
3051 strlcpy(named_session
, filename
, sizeof named_session
);
3053 /* add the new session to the list of sessions */
3054 s
= g_malloc(sizeof(struct session
));
3055 s
->name
= g_strdup(filename
);
3056 TAILQ_INSERT_TAIL(&sessions
, s
, entry
);
3064 session_open(struct tab
*t
, char *filename
)
3069 if (strlen(filename
) == 0)
3072 if (filename
[0] == '.' || filename
[0] == '/')
3076 a
.i
= XT_SES_CLOSETABS
;
3077 if (open_tabs(t
, &a
))
3080 strlcpy(named_session
, filename
, sizeof named_session
);
3088 session_delete(struct tab
*t
, char *filename
)
3090 char file
[PATH_MAX
];
3094 if (strlen(filename
) == 0)
3097 if (filename
[0] == '.' || filename
[0] == '/')
3100 snprintf(file
, sizeof file
, "%s" PS
"%s", sessions_dir
, filename
);
3104 if (!strcmp(filename
, named_session
))
3105 strlcpy(named_session
, XT_SAVED_TABS_FILE
,
3106 sizeof named_session
);
3108 /* remove session from sessions list */
3109 TAILQ_FOREACH(s
, &sessions
, entry
) {
3110 if (!strcmp(s
->name
, filename
))
3115 TAILQ_REMOVE(&sessions
, s
, entry
);
3116 g_free((gpointer
) s
->name
);
3125 session_cmd(struct tab
*t
, struct karg
*args
)
3127 char *filename
= args
->s
;
3132 if (args
->i
& XT_SHOW
)
3133 show_oops(t
, "Current session: %s", named_session
[0] == '\0' ?
3134 XT_SAVED_TABS_FILE
: named_session
);
3135 else if (args
->i
& XT_SAVE
) {
3136 if (session_save(t
, filename
)) {
3137 show_oops(t
, "Can't save session: %s",
3138 filename
? filename
: "INVALID");
3141 } else if (args
->i
& XT_OPEN
) {
3142 if (session_open(t
, filename
)) {
3143 show_oops(t
, "Can't open session: %s",
3144 filename
? filename
: "INVALID");
3147 } else if (args
->i
& XT_DELETE
) {
3148 if (session_delete(t
, filename
)) {
3149 show_oops(t
, "Can't delete session: %s",
3150 filename
? filename
: "INVALID");
3155 return (XT_CB_PASSTHROUGH
);
3159 script_cmd(struct tab
*t
, struct karg
*args
)
3168 if ((f
= fopen(args
->s
, "r")) == NULL
) {
3169 show_oops(t
, "Can't open script file: %s", args
->s
);
3173 if (fstat(fileno(f
), &sb
) == -1) {
3174 show_oops(t
, "Can't stat script file: %s", args
->s
);
3178 buf
= g_malloc0(sb
.st_size
+ 1);
3179 if (fread(buf
, 1, sb
.st_size
, f
) != sb
.st_size
) {
3180 show_oops(t
, "Can't read script file: %s", args
->s
);
3184 DNPRINTF(XT_D_JS
, "%s: about to run script\n", __func__
);
3193 return (XT_CB_PASSTHROUGH
);
3197 * Make a hardcopy of the page
3200 print_page(struct tab
*t
, struct karg
*args
)
3202 WebKitWebFrame
*frame
;
3204 GtkPrintOperation
*op
;
3205 GtkPrintOperationAction action
;
3206 GtkPrintOperationResult print_res
;
3207 GError
*g_err
= NULL
;
3208 int marg_l
, marg_r
, marg_t
, marg_b
;
3210 DNPRINTF(XT_D_PRINTING
, "%s:", __func__
);
3212 ps
= gtk_page_setup_new();
3213 op
= gtk_print_operation_new();
3214 action
= GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG
;
3215 frame
= webkit_web_view_get_main_frame(t
->wv
);
3217 /* the default margins are too small, so we will bump them */
3218 marg_l
= gtk_page_setup_get_left_margin(ps
, GTK_UNIT_MM
) +
3219 XT_PRINT_EXTRA_MARGIN
;
3220 marg_r
= gtk_page_setup_get_right_margin(ps
, GTK_UNIT_MM
) +
3221 XT_PRINT_EXTRA_MARGIN
;
3222 marg_t
= gtk_page_setup_get_top_margin(ps
, GTK_UNIT_MM
) +
3223 XT_PRINT_EXTRA_MARGIN
;
3224 marg_b
= gtk_page_setup_get_bottom_margin(ps
, GTK_UNIT_MM
) +
3225 XT_PRINT_EXTRA_MARGIN
;
3228 gtk_page_setup_set_left_margin(ps
, marg_l
, GTK_UNIT_MM
);
3229 gtk_page_setup_set_right_margin(ps
, marg_r
, GTK_UNIT_MM
);
3230 gtk_page_setup_set_top_margin(ps
, marg_t
, GTK_UNIT_MM
);
3231 gtk_page_setup_set_bottom_margin(ps
, marg_b
, GTK_UNIT_MM
);
3233 gtk_print_operation_set_default_page_setup(op
, ps
);
3235 /* this appears to free 'op' and 'ps' */
3236 print_res
= webkit_web_frame_print_full(frame
, op
, action
, &g_err
);
3238 /* check it worked */
3239 if (print_res
== GTK_PRINT_OPERATION_RESULT_ERROR
) {
3240 show_oops(NULL
, "can't print: %s", g_err
->message
);
3241 g_error_free (g_err
);
3249 go_home(struct tab
*t
, struct karg
*args
)
3256 set_encoding(struct tab
*t
, struct karg
*args
)
3260 if (args
->s
&& strlen(g_strstrip(args
->s
)) == 0) {
3261 e
= webkit_web_view_get_custom_encoding(t
->wv
);
3263 e
= webkit_web_view_get_encoding(t
->wv
);
3264 show_oops(t
, "encoding: %s", e
? e
: "N/A");
3266 webkit_web_view_set_custom_encoding(t
->wv
, args
->s
);
3272 restart(struct tab
*t
, struct karg
*args
)
3276 a
.s
= XT_RESTART_TABS_FILE
;
3278 execvp(start_argv
[0], start_argv
);
3284 char *http_proxy_save
; /* not a setting, used to toggle */
3287 proxy_cmd(struct tab
*t
, struct karg
*args
)
3289 DNPRINTF(XT_D_CMD
, "%s: tab %d\n", __func__
, t
->tab_id
);
3296 if (http_proxy_save
)
3297 g_free(http_proxy_save
);
3298 http_proxy_save
= g_strdup(http_proxy
);
3301 if (args
->i
& XT_PRXY_SHOW
) {
3303 show_oops(t
, "http_proxy = %s", http_proxy
);
3305 show_oops(t
, "proxy is currently disabled");
3306 } else if (args
->i
& XT_PRXY_TOGGLE
) {
3307 if (http_proxy_save
== NULL
&& http_proxy
== NULL
) {
3308 show_oops(t
, "can't toggle proxy");
3313 show_oops(t
, "http proxy disabled");
3315 setup_proxy(http_proxy_save
);
3316 show_oops(t
, "http_proxy = %s", http_proxy
);
3320 return (XT_CB_PASSTHROUGH
);
3325 int (*func
)(struct tab
*, struct karg
*);
3329 { "command_mode", 0, command_mode
, XT_MODE_COMMAND
, 0 },
3330 { "insert_mode", 0, command_mode
, XT_MODE_INSERT
, 0 },
3331 { "command", 0, command
, ':', 0 },
3332 { "search", 0, command
, '/', 0 },
3333 { "searchb", 0, command
, '?', 0 },
3334 { "hinting", 0, command
, '.', 0 },
3335 { "hinting_newtab", 0, command
, ',', 0 },
3336 { "togglesrc", 0, toggle_src
, 0, 0 },
3337 { "editsrc", 0, edit_src
, 0, 0 },
3338 { "editelement", 0, edit_element
, 0, 0 },
3339 { "passthrough", 0, passthrough
, 0, 0 },
3340 { "modurl", 0, modurl
, 0, 0 },
3342 /* yanking and pasting */
3343 { "yankuri", 0, yank_uri
, 0, 0 },
3344 { "pasteuricur", 0, paste_uri
, XT_PASTE_CURRENT_TAB
, 0 },
3345 { "pasteurinew", 0, paste_uri
, XT_PASTE_NEW_TAB
, 0 },
3348 { "searchnext", 0, search
, XT_SEARCH_NEXT
, 0 },
3349 { "searchprevious", 0, search
, XT_SEARCH_PREV
, 0 },
3352 { "focusaddress", 0, focus
, XT_FOCUS_URI
, 0 },
3353 { "focussearch", 0, focus
, XT_FOCUS_SEARCH
, 0 },
3356 { "hinting", 0, hint
, 0, 0 },
3357 { "hinting_newtab", 0, hint
, XT_HINT_NEWTAB
, 0 },
3359 /* custom stylesheet */
3360 { "userstyle", 0, userstyle_cmd
, XT_STYLE_CURRENT_TAB
, XT_USERARG
},
3361 { "userstyle_global", 0, userstyle_cmd
, XT_STYLE_GLOBAL
, XT_USERARG
},
3364 { "goback", 0, navaction
, XT_NAV_BACK
, 0 },
3365 { "goforward", 0, navaction
, XT_NAV_FORWARD
, 0 },
3366 { "reload", 0, navaction
, XT_NAV_RELOAD
, 0 },
3367 { "stop", 0, navaction
, XT_NAV_STOP
, 0 },
3369 /* vertical movement */
3370 { "scrolldown", 0, move
, XT_MOVE_DOWN
, 0 },
3371 { "scrollup", 0, move
, XT_MOVE_UP
, 0 },
3372 { "scrollbottom", 0, move
, XT_MOVE_BOTTOM
, 0 },
3373 { "scrolltop", 0, move
, XT_MOVE_TOP
, 0 },
3374 { "1", 0, move
, XT_MOVE_TOP
, 0 },
3375 { "scrollhalfdown", 0, move
, XT_MOVE_HALFDOWN
, 0 },
3376 { "scrollhalfup", 0, move
, XT_MOVE_HALFUP
, 0 },
3377 { "scrollpagedown", 0, move
, XT_MOVE_PAGEDOWN
, 0 },
3378 { "scrollpageup", 0, move
, XT_MOVE_PAGEUP
, 0 },
3379 /* horizontal movement */
3380 { "scrollright", 0, move
, XT_MOVE_RIGHT
, 0 },
3381 { "scrollleft", 0, move
, XT_MOVE_LEFT
, 0 },
3382 { "scrollfarright", 0, move
, XT_MOVE_FARRIGHT
, 0 },
3383 { "scrollfarleft", 0, move
, XT_MOVE_FARLEFT
, 0 },
3385 { "favorites", 0, xtp_page_fl
, XT_SHOW
, 0 },
3386 { "fav", 0, xtp_page_fl
, XT_SHOW
, 0 },
3387 { "favedit", 0, xtp_page_fl
, XT_SHOW
|XT_DELETE
, 0 },
3388 { "favadd", 0, add_favorite
, 0, 0 },
3390 { "qall", 0, quit
, 0, 0 },
3391 { "quitall", 0, quit
, 0, 0 },
3392 { "w", 0, save_tabs
, 0, 0 },
3393 { "wq", 0, save_tabs_and_quit
, 0, 0 },
3394 { "help", 0, help
, 0, 0 },
3395 { "about", 0, xtp_page_ab
, 0, 0 },
3396 { "stats", 0, stats
, 0, 0 },
3397 { "version", 0, xtp_page_ab
, 0, 0 },
3400 { "js", 0, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3401 { "save", 1, js_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
3402 { "domain", 2, js_cmd
, XT_SAVE
| XT_WL_TOPLEVEL
, 0 },
3403 { "fqdn", 2, js_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
3404 { "show", 1, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3405 { "all", 2, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3406 { "persistent", 2, js_cmd
, XT_SHOW
| XT_WL_PERSISTENT
, 0 },
3407 { "session", 2, js_cmd
, XT_SHOW
| XT_WL_SESSION
, 0 },
3408 { "toggle", 1, js_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
3409 { "domain", 2, js_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
, 0 },
3410 { "fqdn", 2, js_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
3412 /* cookie command */
3413 { "cookie", 0, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3414 { "save", 1, cookie_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
3415 { "domain", 2, cookie_cmd
, XT_SAVE
| XT_WL_TOPLEVEL
, 0 },
3416 { "fqdn", 2, cookie_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
3417 { "show", 1, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3418 { "all", 2, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3419 { "persistent", 2, cookie_cmd
, XT_SHOW
| XT_WL_PERSISTENT
, 0 },
3420 { "session", 2, cookie_cmd
, XT_SHOW
| XT_WL_SESSION
, 0 },
3421 { "toggle", 1, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
3422 { "domain", 2, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
, 0 },
3423 { "fqdn", 2, cookie_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
3424 { "purge", 1, cookie_cmd
, XT_DELETE
, 0 },
3426 /* plugin command */
3427 { "plugin", 0, pl_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3428 { "save", 1, pl_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
3429 { "domain", 2, pl_cmd
, XT_SAVE
| XT_WL_TOPLEVEL
, 0 },
3430 { "fqdn", 2, pl_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
3431 { "show", 1, pl_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3432 { "all", 2, pl_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3433 { "persistent", 2, pl_cmd
, XT_SHOW
| XT_WL_PERSISTENT
, 0 },
3434 { "session", 2, pl_cmd
, XT_SHOW
| XT_WL_SESSION
, 0 },
3435 { "toggle", 1, pl_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
3436 { "domain", 2, pl_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
, 0 },
3437 { "fqdn", 2, pl_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
3440 { "https", 0, https_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3441 { "save", 1, https_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
3442 { "domain", 2, https_cmd
, XT_SAVE
| XT_WL_TOPLEVEL
, 0 },
3443 { "fqdn", 2, https_cmd
, XT_SAVE
| XT_WL_FQDN
, 0 },
3444 { "show", 1, https_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3445 { "all", 2, https_cmd
, XT_SHOW
| XT_WL_PERSISTENT
| XT_WL_SESSION
, 0 },
3446 { "persistent", 2, https_cmd
, XT_SHOW
| XT_WL_PERSISTENT
, 0 },
3447 { "session", 2, https_cmd
, XT_SHOW
| XT_WL_SESSION
, 0 },
3448 { "toggle", 1, https_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
3449 { "domain", 2, https_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
, 0 },
3450 { "fqdn", 2, https_cmd
, XT_WL_TOGGLE
| XT_WL_FQDN
, 0 },
3452 /* toplevel (domain) command */
3453 { "toplevel", 0, toplevel_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
| XT_WL_RELOAD
, 0 },
3454 { "toggle", 1, toplevel_cmd
, XT_WL_TOGGLE
| XT_WL_TOPLEVEL
| XT_WL_RELOAD
, 0 },
3457 { "cookiejar", 0, xtp_page_cl
, 0, 0 },
3460 { "cert", 0, cert_cmd
, XT_SHOW
, 0 },
3461 { "save", 1, cert_cmd
, XT_SAVE
, 0 },
3462 { "show", 1, cert_cmd
, XT_SHOW
, 0 },
3464 { "ca", 0, ca_cmd
, 0, 0 },
3465 { "downloadmgr", 0, xtp_page_dl
, 0, 0 },
3466 { "dl", 0, xtp_page_dl
, 0, 0 },
3467 { "h", 0, xtp_page_hl
, 0, 0 },
3468 { "history", 0, xtp_page_hl
, 0, 0 },
3469 { "home", 0, go_home
, 0, 0 },
3470 { "restart", 0, restart
, 0, 0 },
3471 { "urlhide", 0, urlaction
, XT_URL_HIDE
, 0 },
3472 { "urlshow", 0, urlaction
, XT_URL_SHOW
, 0 },
3473 { "statustoggle", 0, statustoggle
, 0, 0 },
3474 { "run_script", 0, run_page_script
, 0, XT_USERARG
},
3476 { "print", 0, print_page
, 0, 0 },
3479 { "focusin", 0, resizetab
, XT_ZOOM_IN
, 0 },
3480 { "focusout", 0, resizetab
, XT_ZOOM_OUT
, 0 },
3481 { "focusreset", 0, resizetab
, XT_ZOOM_NORMAL
, 0 },
3482 { "q", 0, tabaction
, XT_TAB_DELQUIT
, 0 },
3483 { "quit", 0, tabaction
, XT_TAB_DELQUIT
, 0 },
3484 { "open", 0, tabaction
, XT_TAB_OPEN
, XT_URLARG
},
3485 { "tabclose", 0, tabaction
, XT_TAB_DELETE
, XT_PREFIX
| XT_INTARG
},
3486 { "tabedit", 0, tabaction
, XT_TAB_NEW
, XT_PREFIX
| XT_URLARG
},
3487 { "tabfirst", 0, movetab
, XT_TAB_FIRST
, 0 },
3488 { "tabhide", 0, tabaction
, XT_TAB_HIDE
, 0 },
3489 { "tablast", 0, movetab
, XT_TAB_LAST
, 0 },
3490 { "tabnew", 0, tabaction
, XT_TAB_NEW
, XT_PREFIX
| XT_URLARG
},
3491 { "tabnext", 0, movetab
, XT_TAB_NEXT
, XT_PREFIX
| XT_INTARG
},
3492 { "tabnextstyle", 0, tabaction
, XT_TAB_NEXTSTYLE
, 0 },
3493 { "tabonly", 0, tabaction
, XT_TAB_ONLY
, 0 },
3494 { "tabprevious", 0, movetab
, XT_TAB_PREV
, XT_PREFIX
| XT_INTARG
},
3495 { "tabrewind", 0, movetab
, XT_TAB_FIRST
, 0 },
3496 { "tabshow", 0, tabaction
, XT_TAB_SHOW
, 0 },
3497 { "tabs", 0, buffers
, 0, 0 },
3498 { "tabundoclose", 0, tabaction
, XT_TAB_UNDO_CLOSE
, 0 },
3499 { "buffers", 0, buffers
, 0, 0 },
3500 { "ls", 0, buffers
, 0, 0 },
3501 { "encoding", 0, set_encoding
, 0, XT_USERARG
},
3502 { "loadimages", 0, tabaction
, XT_TAB_LOAD_IMAGES
, 0 },
3505 { "set", 0, set
, 0, XT_SETARG
},
3506 { "runtime", 0, xtp_page_rt
, 0, 0 },
3508 { "fullscreen", 0, fullscreen
, 0, 0 },
3509 { "f", 0, fullscreen
, 0, 0 },
3512 { "session", 0, session_cmd
, XT_SHOW
, 0 },
3513 { "delete", 1, session_cmd
, XT_DELETE
, XT_SESSARG
},
3514 { "open", 1, session_cmd
, XT_OPEN
, XT_SESSARG
},
3515 { "save", 1, session_cmd
, XT_SAVE
, XT_USERARG
},
3516 { "show", 1, session_cmd
, XT_SHOW
, 0 },
3518 /* external javascript */
3519 { "script", 0, script_cmd
, XT_EJS_SHOW
, XT_USERARG
},
3522 { "inspector", 0, inspector_cmd
, XT_INS_SHOW
, 0 },
3523 { "show", 1, inspector_cmd
, XT_INS_SHOW
, 0 },
3524 { "hide", 1, inspector_cmd
, XT_INS_HIDE
, 0 },
3527 { "proxy", 0, proxy_cmd
, XT_PRXY_SHOW
, 0 },
3528 { "show", 1, proxy_cmd
, XT_PRXY_SHOW
, 0 },
3529 { "toggle", 1, proxy_cmd
, XT_PRXY_TOGGLE
, 0 },
3536 } cmd_status
= {-1, 0};
3539 wv_release_button_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
3542 if (e
->type
== GDK_BUTTON_RELEASE
&& e
->button
== 1)
3549 wv_button_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
3552 WebKitHitTestResult
*hit_test_result
;
3555 hit_test_result
= webkit_web_view_get_hit_test_result(t
->wv
, e
);
3556 g_object_get(hit_test_result
, "context", &context
, NULL
);
3561 if (context
& WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE
)
3562 t
->mode
= XT_MODE_INSERT
;
3564 t
->mode
= XT_MODE_COMMAND
;
3566 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 1)
3568 else if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 8 /* btn 4 */) {
3574 } else if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 9 /* btn 5 */) {
3576 a
.i
= XT_NAV_FORWARD
;
3586 tab_close_cb(GtkWidget
*btn
, GdkEventButton
*e
, struct tab
*t
)
3588 DNPRINTF(XT_D_TAB
, "tab_close_cb: tab %d\n", t
->tab_id
);
3590 if (e
->type
== GDK_BUTTON_PRESS
&& e
->button
== 1)
3597 parse_custom_uri(struct tab
*t
, const char *uri
)
3599 struct custom_uri
*u
;
3603 TAILQ_FOREACH(u
, &cul
, entry
) {
3604 if (strncmp(uri
, u
->uri
, strlen(u
->uri
)))
3609 sv
[1] = (char *)uri
;
3611 if (!g_spawn_async(NULL
, sv
, NULL
, G_SPAWN_SEARCH_PATH
, NULL
,
3613 show_oops(t
, "%s: could not spawn process", __func__
);
3620 activate_uri_entry_cb(GtkWidget
* entry
, struct tab
*t
)
3622 const gchar
*uri
= gtk_entry_get_text(GTK_ENTRY(entry
));
3624 DNPRINTF(XT_D_URL
, "activate_uri_entry_cb: %s\n", uri
);
3627 show_oops(NULL
, "activate_uri_entry_cb invalid parameters");
3632 show_oops(t
, "activate_uri_entry_cb no uri");
3636 uri
+= strspn(uri
, "\t ");
3638 if (parse_custom_uri(t
, uri
))
3641 /* otherwise continue to load page normally */
3642 load_uri(t
, (gchar
*)uri
);
3647 activate_search_entry_cb(GtkWidget
* entry
, struct tab
*t
)
3649 const gchar
*search
= gtk_entry_get_text(GTK_ENTRY(entry
));
3650 char *newuri
= NULL
;
3654 DNPRINTF(XT_D_URL
, "activate_search_entry_cb: %s\n", search
);
3657 show_oops(NULL
, "activate_search_entry_cb invalid parameters");
3661 if (search_string
== NULL
|| strlen(search_string
) == 0) {
3662 show_oops(t
, "no search_string");
3666 set_normal_tab_meaning(t
);
3668 enc_search
= soup_uri_encode(search
, XT_RESERVED_CHARS
);
3669 sv
= g_strsplit(search_string
, "%s", 2);
3670 newuri
= g_strjoinv(enc_search
, sv
);
3675 load_uri(t
, newuri
);
3683 check_and_set_cookie(const gchar
*uri
, struct tab
*t
)
3685 struct wl_entry
*w
= NULL
;
3688 if (uri
== NULL
|| t
== NULL
)
3691 if ((w
= wl_find_uri(uri
, &c_wl
)) == NULL
)
3696 DNPRINTF(XT_D_COOKIE
, "check_and_set_cookie: %s %s\n",
3697 es
? "enable" : "disable", uri
);
3699 g_object_set(G_OBJECT(t
->settings
),
3700 "enable-html5-local-storage", es
, (char *)NULL
);
3701 webkit_web_view_set_settings(t
->wv
, t
->settings
);
3705 check_and_set_js(const gchar
*uri
, struct tab
*t
)
3707 struct wl_entry
*w
= NULL
;
3710 if (uri
== NULL
|| t
== NULL
)
3713 if ((w
= wl_find_uri(uri
, &js_wl
)) == NULL
)
3718 DNPRINTF(XT_D_JS
, "check_and_set_js: %s %s\n",
3719 es
? "enable" : "disable", uri
);
3721 g_object_set(G_OBJECT(t
->settings
),
3722 "enable-scripts", es
, (char *)NULL
);
3723 g_object_set(G_OBJECT(t
->settings
),
3724 "javascript-can-open-windows-automatically", es
, (char *)NULL
);
3725 webkit_web_view_set_settings(t
->wv
, t
->settings
);
3727 button_set_stockid(t
->js_toggle
,
3728 es
? GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
);
3732 check_and_set_pl(const gchar
*uri
, struct tab
*t
)
3734 struct wl_entry
*w
= NULL
;
3737 if (uri
== NULL
|| t
== NULL
)
3740 if ((w
= wl_find_uri(uri
, &pl_wl
)) == NULL
)
3745 DNPRINTF(XT_D_JS
, "check_and_set_pl: %s %s\n",
3746 es
? "enable" : "disable", uri
);
3748 g_object_set(G_OBJECT(t
->settings
),
3749 "enable-plugins", es
, (char *)NULL
);
3750 webkit_web_view_set_settings(t
->wv
, t
->settings
);
3753 #if GTK_CHECK_VERSION(3, 0, 0)
3754 /* A lot of this can be removed when gtk2 is dropped on the floor */
3756 get_css_name(const char *col_str
)
3760 if (!strcmp(col_str
, XT_COLOR_WHITE
))
3761 name
= g_strdup(XT_CSS_NORMAL
);
3762 else if (!strcmp(col_str
, XT_COLOR_RED
))
3763 name
= g_strdup(XT_CSS_RED
);
3764 else if (!strcmp(col_str
, XT_COLOR_YELLOW
))
3765 name
= g_strdup(XT_CSS_YELLOW
);
3766 else if (!strcmp(col_str
, XT_COLOR_GREEN
))
3767 name
= g_strdup(XT_CSS_GREEN
);
3768 else if (!strcmp(col_str
, XT_COLOR_BLUE
))
3769 name
= g_strdup(XT_CSS_BLUE
);
3775 check_certs(gpointer p
)
3777 struct tab
*tt
, *t
= p
;
3778 gchar
*col_str
= XT_COLOR_WHITE
;
3779 const gchar
*uri
, *u
= NULL
, *error_str
= NULL
;
3780 #if GTK_CHECK_VERSION(3, 0, 0)
3788 gdk_threads_enter();
3790 DNPRINTF(XT_D_URL
, "%s:\n", __func__
);
3792 /* make sure t still exists */
3795 TAILQ_FOREACH(tt
, &tabs
, entry
)
3801 if ((uri
= get_uri(t
)) == NULL
)
3807 gdk_threads_leave();
3810 col_str
= XT_COLOR_YELLOW
;
3811 switch (load_compare_cert(u
, &error_str
, certs_dir
)) {
3813 col_str
= XT_COLOR_BLUE
;
3816 col_str
= (strlen(ssl_ca_file
) == 0) ? XT_COLOR_RED
3819 case CERT_UNTRUSTED
:
3820 col_str
= (strlen(ssl_ca_file
) == 0) ? XT_COLOR_RED
3824 col_str
= XT_COLOR_RED
;
3829 gdk_threads_enter();
3831 /* make sure t isn't deleted */
3832 TAILQ_FOREACH(tt
, &tabs
, entry
)
3839 /* test to see if the user navigated away and canceled the thread */
3840 if (t
->thread
!= g_thread_self())
3842 if ((uri
= get_uri(t
)) == NULL
) {
3846 if (strcmp(uri
, u
)) {
3847 /* make sure we are still the same url */
3854 if (!strcmp(col_str
, XT_COLOR_WHITE
)) {
3855 #if GTK_CHECK_VERSION(3, 0, 0)
3856 gtk_widget_set_name(t
->uri_entry
, XT_CSS_NORMAL
);
3857 statusbar_modify_attr(t
, XT_CSS_NORMAL
);
3859 text
= gdk_color_to_string(
3860 &t
->default_style
->text
[GTK_STATE_NORMAL
]);
3861 base
= gdk_color_to_string(
3862 &t
->default_style
->base
[GTK_STATE_NORMAL
]);
3863 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
,
3864 &t
->default_style
->base
[GTK_STATE_NORMAL
]);
3865 statusbar_modify_attr(t
, text
, base
);
3870 #if GTK_CHECK_VERSION(3, 0, 0)
3871 name
= get_css_name(col_str
);
3872 gtk_widget_set_name(t
->uri_entry
, name
);
3873 statusbar_modify_attr(t
, name
);
3876 gdk_color_parse(col_str
, &color
);
3877 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
3878 statusbar_modify_attr(t
, XT_COLOR_BLACK
, col_str
);
3882 if (error_str
&& error_str
[0] != '\0')
3883 show_oops(t
, "%s", error_str
);
3885 check_cert_changes(t
, u
);
3891 /* t is invalid at this point */
3893 g_free((gpointer
)u
);
3896 gdk_threads_leave();
3901 show_ca_status(struct tab
*t
, const char *uri
)
3903 gchar
*col_str
= XT_COLOR_WHITE
;
3904 #if GTK_CHECK_VERSION(3, 0, 0)
3911 DNPRINTF(XT_D_URL
, "show_ca_status: %d %s %s\n",
3912 ssl_strict_certs
, ssl_ca_file
, uri
);
3917 if (uri
== NULL
|| g_str_has_prefix(uri
, "http://") ||
3918 !g_str_has_prefix(uri
, "https://"))
3923 * It is not necessary to see if the thread is already running.
3924 * If the thread is in progress setting it to something else aborts it
3928 /* thread the coloring of the address bar */
3929 t
->thread
= g_thread_create((GThreadFunc
)check_certs
, t
, TRUE
, NULL
);
3937 if (!strcmp(col_str
, XT_COLOR_WHITE
)) {
3938 #if GTK_CHECK_VERSION(3, 0, 0)
3939 gtk_widget_set_name(t
->uri_entry
, XT_CSS_NORMAL
);
3940 statusbar_modify_attr(t
, XT_CSS_NORMAL
);
3942 text
= gdk_color_to_string(
3943 &t
->default_style
->text
[GTK_STATE_NORMAL
]);
3944 base
= gdk_color_to_string(
3945 &t
->default_style
->base
[GTK_STATE_NORMAL
]);
3946 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
,
3947 &t
->default_style
->base
[GTK_STATE_NORMAL
]);
3948 statusbar_modify_attr(t
, text
, base
);
3953 #if GTK_CHECK_VERSION(3, 0, 0)
3954 name
= get_css_name(col_str
);
3955 gtk_widget_set_name(t
->uri_entry
, name
);
3956 statusbar_modify_attr(t
, name
);
3959 gdk_color_parse(col_str
, &color
);
3960 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
, &color
);
3961 statusbar_modify_attr(t
, XT_COLOR_BLACK
, col_str
);
3968 free_favicon(struct tab
*t
)
3970 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p req %p\n",
3971 __func__
, t
->icon_download
, t
->icon_request
);
3973 if (t
->icon_request
)
3974 g_object_unref(t
->icon_request
);
3975 if (t
->icon_dest_uri
)
3976 g_free(t
->icon_dest_uri
);
3978 t
->icon_request
= NULL
;
3979 t
->icon_dest_uri
= NULL
;
3983 xt_icon_from_name(struct tab
*t
, gchar
*name
)
3985 if (!enable_favicon_entry
)
3988 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->uri_entry
),
3989 GTK_ENTRY_ICON_PRIMARY
, "text-html");
3991 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->sbe
.uri
),
3992 GTK_ENTRY_ICON_PRIMARY
, "text-html");
3994 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->sbe
.uri
),
3995 GTK_ENTRY_ICON_PRIMARY
, NULL
);
3999 xt_icon_from_pixbuf(struct tab
*t
, GdkPixbuf
*pb
)
4001 GdkPixbuf
*pb_scaled
;
4003 if (gdk_pixbuf_get_width(pb
) > 16 || gdk_pixbuf_get_height(pb
) > 16)
4004 pb_scaled
= gdk_pixbuf_scale_simple(pb
, 16, 16,
4005 GDK_INTERP_BILINEAR
);
4009 if (enable_favicon_entry
) {
4012 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->uri_entry
),
4013 GTK_ENTRY_ICON_PRIMARY
, pb_scaled
);
4016 if (show_url
== 0) {
4017 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t
->sbe
.uri
),
4018 GTK_ENTRY_ICON_PRIMARY
, pb_scaled
);
4020 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t
->sbe
.uri
),
4021 GTK_ENTRY_ICON_PRIMARY
, NULL
);
4024 /* XXX: Only supports the minimal tabs atm. */
4025 if (enable_favicon_tabs
)
4026 gtk_image_set_from_pixbuf(GTK_IMAGE(t
->tab_elems
.favicon
),
4029 if (pb_scaled
!= pb
)
4030 g_object_unref(pb_scaled
);
4034 xt_icon_from_file(struct tab
*t
, char *uri
)
4039 if (g_str_has_prefix(uri
, "file://"))
4040 file
= g_filename_from_uri(uri
, NULL
, NULL
);
4042 file
= g_strdup(uri
);
4047 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
4049 xt_icon_from_pixbuf(t
, pb
);
4052 xt_icon_from_name(t
, "text-html");
4058 is_valid_icon(char *file
)
4061 const char *mime_type
;
4065 gf
= g_file_new_for_path(file
);
4066 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
4068 mime_type
= g_file_info_get_content_type(fi
);
4069 valid
= g_strcmp0(mime_type
, "image/x-ico") == 0 ||
4070 g_strcmp0(mime_type
, "image/vnd.microsoft.icon") == 0 ||
4071 g_strcmp0(mime_type
, "image/png") == 0 ||
4072 g_strcmp0(mime_type
, "image/gif") == 0 ||
4073 g_strcmp0(mime_type
, "application/octet-stream") == 0;
4081 set_favicon_from_file(struct tab
*t
, char *uri
)
4086 if (t
== NULL
|| uri
== NULL
)
4089 if (g_str_has_prefix(uri
, "file://"))
4090 file
= g_filename_from_uri(uri
, NULL
, NULL
);
4092 file
= g_strdup(uri
);
4097 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading %s\n", __func__
, file
);
4099 if (!stat(file
, &sb
)) {
4100 if (sb
.st_size
== 0 || !is_valid_icon(file
)) {
4101 /* corrupt icon so trash it */
4102 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
4105 /* no need to set icon to default here */
4109 xt_icon_from_file(t
, file
);
4115 favicon_download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
4118 WebKitDownloadStatus status
= webkit_download_get_status(download
);
4119 struct tab
*tt
= NULL
, *t
= NULL
;
4122 * find the webview instead of passing in the tab as it could have been
4123 * deleted from underneath us.
4125 TAILQ_FOREACH(tt
, &tabs
, entry
) {
4134 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d status %d\n",
4135 __func__
, t
->tab_id
, status
);
4138 case WEBKIT_DOWNLOAD_STATUS_ERROR
:
4140 t
->icon_download
= NULL
;
4143 case WEBKIT_DOWNLOAD_STATUS_CREATED
:
4146 case WEBKIT_DOWNLOAD_STATUS_STARTED
:
4149 case WEBKIT_DOWNLOAD_STATUS_CANCELLED
:
4151 DNPRINTF(XT_D_DOWNLOAD
, "%s: freeing favicon %d\n",
4152 __func__
, t
->tab_id
);
4153 t
->icon_download
= NULL
;
4156 case WEBKIT_DOWNLOAD_STATUS_FINISHED
:
4159 DNPRINTF(XT_D_DOWNLOAD
, "%s: setting icon to %s\n",
4160 __func__
, t
->icon_dest_uri
);
4161 set_favicon_from_file(t
, t
->icon_dest_uri
);
4162 /* these will be freed post callback */
4163 t
->icon_request
= NULL
;
4164 t
->icon_download
= NULL
;
4172 abort_favicon_download(struct tab
*t
)
4174 DNPRINTF(XT_D_DOWNLOAD
, "%s: down %p\n", __func__
, t
->icon_download
);
4176 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
4177 if (t
->icon_download
) {
4178 g_signal_handlers_disconnect_by_func(G_OBJECT(t
->icon_download
),
4179 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
4180 webkit_download_cancel(t
->icon_download
);
4181 t
->icon_download
= NULL
;
4186 xt_icon_from_name(t
, "text-html");
4190 notify_icon_loaded_cb(WebKitWebView
*wv
, gchar
*uri
, struct tab
*t
)
4192 DNPRINTF(XT_D_DOWNLOAD
, "%s %s\n", __func__
, uri
);
4194 if (uri
== NULL
|| t
== NULL
)
4197 #if WEBKIT_CHECK_VERSION(1, 4, 0)
4198 /* take icon from WebKitIconDatabase */
4199 GdkPixbuf
*pb
= NULL
;
4201 /* webkit_web_view_get_icon_pixbuf is depreciated in 1.8 */
4202 #if WEBKIT_CHECK_VERSION(1, 8, 0)
4204 * If the page was not loaded (for example, via ssl_strict_certs), do
4205 * not attempt to get the webview's pixbuf. This prevents a CRITICAL
4208 if (wv
&& webkit_web_view_get_uri(wv
))
4209 pb
= webkit_web_view_try_get_favicon_pixbuf(wv
, 0, 0);
4211 if (wv
&& webkit_web_view_get_uri(wv
))
4212 pb
= webkit_web_view_get_icon_pixbuf(wv
);
4215 xt_icon_from_pixbuf(t
, pb
);
4218 xt_icon_from_name(t
, "text-html");
4219 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
4220 /* download icon to cache dir */
4221 gchar
*name_hash
, file
[PATH_MAX
];
4224 if (t
->icon_request
) {
4225 DNPRINTF(XT_D_DOWNLOAD
, "%s: download in progress\n", __func__
);
4229 /* check to see if we got the icon in cache */
4230 name_hash
= g_compute_checksum_for_string(G_CHECKSUM_SHA256
, uri
, -1);
4231 snprintf(file
, sizeof file
, "%s" PS
"%s.ico", cache_dir
, name_hash
);
4234 if (!stat(file
, &sb
)) {
4235 if (sb
.st_size
> 0) {
4236 DNPRINTF(XT_D_DOWNLOAD
, "%s: loading from cache %s\n",
4238 set_favicon_from_file(t
, file
);
4242 /* corrupt icon so trash it */
4243 DNPRINTF(XT_D_DOWNLOAD
, "%s: corrupt icon %s\n",
4248 /* create download for icon */
4249 t
->icon_request
= webkit_network_request_new(uri
);
4250 if (t
->icon_request
== NULL
) {
4251 DNPRINTF(XT_D_DOWNLOAD
, "%s: invalid uri %s\n",
4256 t
->icon_download
= webkit_download_new(t
->icon_request
);
4257 if (t
->icon_download
== NULL
)
4260 /* we have to free icon_dest_uri later */
4261 if ((t
->icon_dest_uri
= g_filename_to_uri(file
, NULL
, NULL
)) == NULL
)
4263 webkit_download_set_destination_uri(t
->icon_download
,
4266 if (webkit_download_get_status(t
->icon_download
) ==
4267 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
4268 g_object_unref(t
->icon_request
);
4269 g_free(t
->icon_dest_uri
);
4270 t
->icon_request
= NULL
;
4271 t
->icon_dest_uri
= NULL
;
4275 g_signal_connect(G_OBJECT(t
->icon_download
), "notify::status",
4276 G_CALLBACK(favicon_download_status_changed_cb
), t
->wv
);
4278 webkit_download_start(t
->icon_download
);
4283 notify_load_status_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
4285 const gchar
*uri
= NULL
;
4286 struct history
*h
, find
;
4288 gchar
*tmp_uri
= NULL
;
4289 #if !GTK_CHECK_VERSION(3, 0, 0)
4293 DNPRINTF(XT_D_URL
, "notify_load_status_cb: %d %s\n",
4294 webkit_web_view_get_load_status(wview
),
4295 get_uri(t
) ? get_uri(t
) : "NOTHING");
4298 show_oops(NULL
, "notify_load_status_cb invalid parameters");
4302 switch (webkit_web_view_get_load_status(wview
)) {
4303 case WEBKIT_LOAD_PROVISIONAL
:
4305 abort_favicon_download(t
);
4306 #if GTK_CHECK_VERSION(2, 20, 0)
4307 gtk_widget_show(t
->spinner
);
4308 gtk_spinner_start(GTK_SPINNER(t
->spinner
));
4310 t
->download_requested
= 0;
4312 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), TRUE
);
4314 /* assume we are a new address */
4315 #if GTK_CHECK_VERSION(3, 0, 0)
4316 gtk_widget_set_name(t
->uri_entry
, XT_CSS_NORMAL
);
4317 statusbar_modify_attr(t
, XT_CSS_NORMAL
);
4319 text
= gdk_color_to_string(
4320 &t
->default_style
->text
[GTK_STATE_NORMAL
]);
4321 base
= gdk_color_to_string(
4322 &t
->default_style
->base
[GTK_STATE_NORMAL
]);
4323 gtk_widget_modify_base(t
->uri_entry
, GTK_STATE_NORMAL
,
4324 &t
->default_style
->base
[GTK_STATE_NORMAL
]);
4325 statusbar_modify_attr(t
, text
, base
);
4330 /* DOM is changing, unreference the previous focused element */
4331 #if WEBKIT_CHECK_VERSION(1, 5, 0)
4333 g_object_unref(t
->active
);
4335 if (t
->active_text
) {
4336 g_free(t
->active_text
);
4337 t
->active_text
= NULL
;
4341 /* take focus if we are visible */
4347 /* kill color thread */
4352 case WEBKIT_LOAD_COMMITTED
:
4357 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), uri
);
4363 set_status(t
, "Loading: %s", (char *)uri
);
4365 /* clear t->item, except if we're switching to an about: page */
4366 if (t
->item
&& !g_str_has_prefix(uri
, "xxxt://") &&
4367 !g_str_has_prefix(uri
, "about:")) {
4368 g_object_unref(t
->item
);
4372 /* check if js white listing is enabled */
4373 if (enable_plugin_whitelist
)
4374 check_and_set_pl(uri
, t
);
4375 if (enable_cookie_whitelist
)
4376 check_and_set_cookie(uri
, t
);
4377 if (enable_js_whitelist
)
4378 check_and_set_js(uri
, t
);
4384 /* we know enough to autosave the session */
4385 if (session_autosave
) {
4390 show_ca_status(t
, uri
);
4391 run_script(t
, JS_HINTING
);
4392 if (enable_autoscroll
)
4393 run_script(t
, JS_AUTOSCROLL
);
4396 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT
:
4398 if (color_visited_uris
) {
4399 color_visited(t
, color_visited_helper());
4402 * This colors the links you middle-click (open in new
4403 * tab) in the current tab.
4405 if (t
->tab_id
!= gtk_notebook_get_current_page(notebook
) &&
4406 (uri
= get_uri(t
)) != NULL
)
4407 color_visited(get_current_tab(),
4408 g_strdup_printf("{'%s' : 'dummy'}", uri
));
4412 case WEBKIT_LOAD_FINISHED
:
4414 if ((uri
= get_uri(t
)) == NULL
)
4417 * js_autorun calls get_uri which frees t->tmp_uri if on an
4418 * "about:" page. On "about:" pages, uri points to t->tmp_uri.
4419 * I.e. we will use freed memory. Prevent that.
4421 tmp_uri
= g_strdup(uri
);
4423 /* autorun some js if enabled */
4428 if (!strncmp(tmp_uri
, "http://", strlen("http://")) ||
4429 !strncmp(tmp_uri
, "https://", strlen("https://")) ||
4430 !strncmp(tmp_uri
, "file://", strlen("file://"))) {
4431 find
.uri
= (gchar
*)tmp_uri
;
4432 h
= RB_FIND(history_list
, &hl
, &find
);
4434 insert_history_item(tmp_uri
,
4435 get_title(t
, FALSE
), time(NULL
));
4437 h
->time
= time(NULL
);
4440 if (statusbar_style
== XT_STATUSBAR_URL
)
4441 set_status(t
, "%s", (char *)tmp_uri
);
4443 set_status(t
, "%s", (char *)get_title(t
, FALSE
));
4444 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), FALSE
);
4445 #if GTK_CHECK_VERSION(2, 20, 0)
4446 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
4447 gtk_widget_hide(t
->spinner
);
4452 #if WEBKIT_CHECK_VERSION(1, 1, 18)
4453 case WEBKIT_LOAD_FAILED
:
4455 if (!t
->download_requested
) {
4456 gtk_label_set_text(GTK_LABEL(t
->label
),
4457 get_title(t
, FALSE
));
4458 gtk_label_set_text(GTK_LABEL(t
->tab_elems
.label
),
4459 get_title(t
, FALSE
));
4460 set_status(t
, "%s", (char *)get_title(t
, FALSE
));
4461 gtk_window_set_title(GTK_WINDOW(main_window
),
4462 get_title(t
, TRUE
));
4468 #if GTK_CHECK_VERSION(2, 20, 0)
4469 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
4470 gtk_widget_hide(t
->spinner
);
4472 gtk_widget_set_sensitive(GTK_WIDGET(t
->stop
), FALSE
);
4476 gtk_widget_set_sensitive(GTK_WIDGET(t
->backward
),
4477 can_go_back_for_real(t
));
4479 gtk_widget_set_sensitive(GTK_WIDGET(t
->forward
),
4480 can_go_forward_for_real(t
));
4484 notify_title_cb(WebKitWebView
* wview
, GParamSpec
* pspec
, struct tab
*t
)
4486 const gchar
*title
= NULL
, *win_title
= NULL
;
4488 title
= get_title(t
, FALSE
);
4489 win_title
= get_title(t
, TRUE
);
4491 gtk_label_set_text(GTK_LABEL(t
->label
), title
);
4492 gtk_label_set_text(GTK_LABEL(t
->tab_elems
.label
), title
);
4495 if (win_title
&& t
->tab_id
== gtk_notebook_get_current_page(notebook
))
4496 gtk_window_set_title(GTK_WINDOW(main_window
), win_title
);
4500 get_domain(const gchar
*host
)
4505 /* handle silly domains like .co.uk */
4507 if ((x
= strlen(host
)) <= 6)
4508 return (g_strdup(host
));
4510 if (host
[x
- 3] == '.' && host
[x
- 6] == '.') {
4516 return (g_strdup(&host
[x
+ 1]));
4520 p
= g_strrstr(host
, ".");
4522 return (g_strdup(""));
4528 return (g_strdup(p
+ 1));
4530 return (g_strdup(host
));
4534 js_autorun(struct tab
*t
)
4538 size_t got_default
= 0, got_host
= 0;
4540 char deff
[PATH_MAX
], hostf
[PATH_MAX
];
4541 char *js
= NULL
, *jsat
, *domain
= NULL
;
4542 FILE *deffile
= NULL
, *hostfile
= NULL
;
4544 if (enable_js_autorun
== 0)
4549 !(g_str_has_prefix(uri
, "http://") ||
4550 g_str_has_prefix(uri
, "https://")))
4553 su
= soup_uri_new(uri
);
4556 if (!SOUP_URI_VALID_FOR_HTTP(su
))
4559 DNPRINTF(XT_D_JS
, "%s: host: %s domain: %s\n", __func__
,
4561 domain
= get_domain(su
->host
);
4563 snprintf(deff
, sizeof deff
, "%s" PS
"default.js", js_dir
);
4564 if ((deffile
= fopen(deff
, "r")) != NULL
) {
4565 if (fstat(fileno(deffile
), &sb
) == -1) {
4566 show_oops(t
, "can't stat default JS file");
4569 got_default
= sb
.st_size
;
4572 /* try host first followed by domain */
4573 snprintf(hostf
, sizeof hostf
, "%s" PS
"%s.js", js_dir
, su
->host
);
4574 DNPRINTF(XT_D_JS
, "trying file: %s\n", hostf
);
4575 if ((hostfile
= fopen(hostf
, "r")) == NULL
) {
4576 snprintf(hostf
, sizeof hostf
, "%s" PS
"%s.js", js_dir
, domain
);
4577 DNPRINTF(XT_D_JS
, "trying file: %s\n", hostf
);
4578 if ((hostfile
= fopen(hostf
, "r")) == NULL
)
4581 DNPRINTF(XT_D_JS
, "file: %s\n", hostf
);
4582 if (fstat(fileno(hostfile
), &sb
) == -1) {
4583 show_oops(t
, "can't stat %s JS file", hostf
);
4586 got_host
= sb
.st_size
;
4589 if (got_default
+ got_host
== 0)
4592 js
= g_malloc0(got_default
+ got_host
+ 1);
4596 if (fread(js
, got_default
, 1, deffile
) != 1) {
4597 show_oops(t
, "default file read error");
4600 jsat
= js
+ got_default
;
4604 if (fread(jsat
, got_host
, 1, hostfile
) != 1) {
4605 show_oops(t
, "host file read error");
4610 DNPRINTF(XT_D_JS
, "%s: about to run script\n", __func__
);
4611 run_script_locked(t
, js
);
4627 webview_progress_changed_cb(WebKitWebView
*wv
, GParamSpec
*pspec
, struct tab
*t
)
4631 progress
= webkit_web_view_get_progress(wv
);
4632 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->sbe
.uri
),
4633 progress
== 1.0 ? 0 : progress
);
4634 gtk_entry_set_progress_fraction(GTK_ENTRY(t
->uri_entry
),
4635 progress
== 1.0 ? 0 : progress
);
4637 update_statusbar_position(NULL
, NULL
);
4641 strict_transport_rb_cmp(struct strict_transport
*a
, struct strict_transport
*b
)
4646 /* compare strings from the end */
4647 l1
= strlen(a
->host
);
4648 l2
= strlen(b
->host
);
4652 for (; *p1
== *p2
&& p1
> a
->host
&& p2
> b
->host
;
4657 * Check if we need to do pattern expansion,
4658 * or if we're just keeping the tree in order
4660 if (a
->flags
& XT_STS_FLAGS_EXPAND
&&
4661 b
->flags
& XT_STS_FLAGS_INCLUDE_SUBDOMAINS
) {
4662 /* Check if we're matching the
4663 * 'host.xyz' part in '*.host.xyz'
4665 if (p2
== b
->host
&& (p1
== a
->host
|| *(p1
-1) == '.')) {
4670 if (p1
== a
->host
&& p2
== b
->host
)
4684 RB_GENERATE(strict_transport_tree
, strict_transport
, entry
,
4685 strict_transport_rb_cmp
);
4688 strict_transport_add(const char *domain
, time_t timeout
, int subdomains
)
4690 struct strict_transport
*d
, find
;
4694 if (enable_strict_transport
== FALSE
)
4697 DPRINTF("strict_transport_add(%s,%" PRIi64
",%d)\n", domain
,
4698 (uint64_t)timeout
, subdomains
);
4704 find
.host
= (char *)domain
;
4706 d
= RB_FIND(strict_transport_tree
, &st_tree
, &find
);
4710 /* check if update is needed */
4711 if (d
->timeout
== timeout
&&
4712 (d
->flags
& XT_STS_FLAGS_INCLUDE_SUBDOMAINS
) == subdomains
)
4715 d
->timeout
= timeout
;
4717 d
->flags
|= XT_STS_FLAGS_INCLUDE_SUBDOMAINS
;
4719 /* We're still initializing */
4720 if (strict_transport_file
== NULL
)
4723 if ((f
= fopen(strict_transport_file
, "w")) == NULL
) {
4725 "can't open strict-transport rules file");
4729 fprintf(f
, "# Generated file - do not update unless you know "
4730 "what you're doing\n");
4731 RB_FOREACH(d
, strict_transport_tree
, &st_tree
) {
4732 if (d
->timeout
< now
)
4734 fprintf(f
, "%s\t%" PRIi64
"\t%d\n", d
->host
,
4735 (uint64_t)d
->timeout
,
4736 d
->flags
& XT_STS_FLAGS_INCLUDE_SUBDOMAINS
);
4740 d
= g_malloc(sizeof *d
);
4741 d
->host
= g_strdup(domain
);
4742 d
->timeout
= timeout
;
4744 d
->flags
= XT_STS_FLAGS_INCLUDE_SUBDOMAINS
;
4747 RB_INSERT(strict_transport_tree
, &st_tree
, d
);
4749 /* We're still initializing */
4750 if (strict_transport_file
== NULL
)
4753 if ((f
= fopen(strict_transport_file
, "a+")) == NULL
) {
4755 "can't open strict-transport rules file");
4759 fseek(f
, 0, SEEK_END
);
4760 fprintf(f
,"%s\t%" PRIi64
"\t%d\n", d
->host
, (uint64_t)timeout
,
4768 strict_transport_check(const char *host
)
4770 static struct strict_transport
*d
= NULL
;
4771 struct strict_transport find
;
4773 if (enable_strict_transport
== FALSE
)
4776 find
.host
= (char *)host
;
4778 /* match for domains that include subdomains */
4779 find
.flags
= XT_STS_FLAGS_EXPAND
;
4781 /* First, check if we're already at the right node */
4782 if (d
!= NULL
&& strict_transport_rb_cmp(&find
, d
) == 0) {
4786 d
= RB_FIND(strict_transport_tree
, &st_tree
, &find
);
4794 strict_transport_init()
4796 char file
[PATH_MAX
];
4802 time_t timeout
, now
;
4805 snprintf(file
, sizeof file
, "%s" PS
"%s", work_dir
, XT_STS_FILE
);
4806 if ((f
= fopen(file
, "r")) == NULL
) {
4807 strict_transport_file
= g_strdup(file
);
4818 if ((rule
= fparseln(f
, &len
, NULL
, delim
, 0)) == NULL
) {
4819 if (!feof(f
) || ferror(f
))
4825 /* get second entry */
4826 if ((ptr
= strpbrk(rule
, " \t")) == NULL
)
4830 timeout
= atoi(ptr
);
4832 /* get third entry */
4833 if ((ptr
= strpbrk(ptr
, " \t")) == NULL
)
4837 subdomains
= atoi(ptr
);
4840 strict_transport_add(rule
, timeout
, subdomains
);
4845 strict_transport_file
= g_strdup(file
);
4849 startpage_add("strict-transport rules file ('%s') is corrupt", file
);
4857 force_https_check(const char *uri
)
4859 struct wl_entry
*w
= NULL
;
4864 if ((w
= wl_find_uri(uri
, &force_https
)) == NULL
)
4871 strict_transport_security_cb(SoupMessage
*msg
, gpointer data
)
4877 int subdomains
= FALSE
;
4882 sts
= soup_message_headers_get_one(msg
->response_headers
,
4883 "Strict-Transport-Security");
4884 uri
= soup_message_get_uri(msg
);
4886 if (sts
== NULL
|| uri
== NULL
)
4889 if ((ptr
= strcasestr(sts
, "max-age="))) {
4890 ptr
+= strlen("max-age=");
4891 timeout
= atoll(ptr
);
4893 return; /* malformed header - max-age must be included */
4895 if ((ptr
= strcasestr(sts
, "includeSubDomains")))
4898 strict_transport_add(uri
->host
, timeout
+ time(NULL
), subdomains
);
4902 session_rq_cb(SoupSession
*s
, SoupMessage
*msg
, SoupSocket
*socket
,
4912 if (s
== NULL
|| msg
== NULL
)
4915 if (enable_strict_transport
) {
4916 soup_message_add_header_handler(msg
, "finished",
4917 "Strict-Transport-Security",
4918 G_CALLBACK(strict_transport_security_cb
), NULL
);
4921 if (referer_mode
== XT_REFERER_ALWAYS
)
4924 /* Check if referer is set - and what the user requested for referers */
4925 ref
= soup_message_headers_get_one(msg
->request_headers
, "Referer");
4927 DNPRINTF(XT_D_NAV
, "session_rq_cb: Referer: %s\n", ref
);
4928 switch (referer_mode
) {
4929 case XT_REFERER_NEVER
:
4930 DNPRINTF(XT_D_NAV
, "session_rq_cb: removing referer\n");
4931 soup_message_headers_remove(msg
->request_headers
,
4934 case XT_REFERER_SAME_DOMAIN
:
4935 ref_uri
= soup_uri_new(ref
);
4936 dest
= soup_message_get_uri(msg
);
4938 ref_suffix
= tld_get_suffix(ref_uri
->host
);
4939 dest_suffix
= tld_get_suffix(dest
->host
);
4941 if (dest
&& ref_suffix
&& dest_suffix
&&
4942 strcmp(ref_suffix
, dest_suffix
) != 0) {
4943 soup_message_headers_remove(msg
->request_headers
,
4945 DNPRINTF(XT_D_NAV
, "session_rq_cb: removing "
4946 "referer (not same domain) (suffixes: %s - %s)\n",
4947 ref_suffix
, dest_suffix
);
4949 soup_uri_free(ref_uri
);
4951 case XT_REFERER_SAME_FQDN
:
4952 ref_uri
= soup_uri_new(ref
);
4953 dest
= soup_message_get_uri(msg
);
4954 if (dest
&& strcmp(ref_uri
->host
, dest
->host
) != 0) {
4955 soup_message_headers_remove(msg
->request_headers
,
4957 DNPRINTF(XT_D_NAV
, "session_rq_cb: removing "
4958 "referer (not same fqdn) (should be %s)\n",
4961 soup_uri_free(ref_uri
);
4963 case XT_REFERER_CUSTOM
:
4964 DNPRINTF(XT_D_NAV
, "session_rq_cb: setting referer "
4965 "to %s\n", referer_custom
);
4966 soup_message_headers_replace(msg
->request_headers
,
4967 "Referer", referer_custom
);
4974 webview_npd_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
,
4975 WebKitNetworkRequest
*request
, WebKitWebNavigationAction
*na
,
4976 WebKitWebPolicyDecision
*pd
, struct tab
*t
)
4978 WebKitWebNavigationReason reason
;
4982 show_oops(NULL
, "webview_npd_cb invalid parameters");
4986 DNPRINTF(XT_D_NAV
, "webview_npd_cb: ctrl_click %d %s\n",
4988 webkit_network_request_get_uri(request
));
4990 uri
= (char *)webkit_network_request_get_uri(request
);
4992 if (!auto_load_images
&& t
->load_images
) {
4994 /* Disable autoloading of images, now that we're done loading
4996 g_object_set(G_OBJECT(t
->settings
),
4997 "auto-load-images", FALSE
, (char *)NULL
);
4998 webkit_web_view_set_settings(t
->wv
, t
->settings
);
5000 t
->load_images
= FALSE
;
5003 /* If this is an xtp url, we don't load anything else. */
5004 if (parse_xtp_url(t
, uri
)) {
5005 webkit_web_policy_decision_ignore(pd
);
5009 if (parse_custom_uri(t
, uri
)) {
5010 webkit_web_policy_decision_ignore(pd
);
5014 if (valid_url_type(uri
)) {
5015 show_oops(t
, "Stopping attempt to load an invalid URI (possible"
5016 " bait and switch attack)");
5017 webkit_web_policy_decision_ignore(pd
);
5021 if ((t
->mode
== XT_MODE_HINT
&& t
->new_tab
) || t
->ctrl_click
) {
5023 create_new_tab(uri
, NULL
, ctrl_click_focus
, -1);
5024 webkit_web_policy_decision_ignore(pd
);
5025 return (TRUE
); /* we made the decission */
5029 * This is a little hairy but it comes down to this:
5030 * when we run in whitelist mode we have to assist the browser in
5031 * opening the URL that it would have opened in a new tab.
5033 reason
= webkit_web_navigation_action_get_reason(na
);
5034 if (reason
== WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED
) {
5035 set_normal_tab_meaning(t
);
5036 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1)
5038 webkit_web_policy_decision_use(pd
);
5039 return (TRUE
); /* we made the decision */
5046 webview_rrs_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, WebKitWebResource
*res
,
5047 WebKitNetworkRequest
*request
, WebKitNetworkResponse
*response
,
5050 SoupMessage
*msg
= NULL
;
5051 SoupURI
*uri
= NULL
;
5052 struct http_accept ha_find
, *ha
= NULL
;
5053 struct user_agent ua_find
, *ua
= NULL
;
5054 struct domain_id di_find
, *di
= NULL
;
5057 msg
= webkit_network_request_get_message(request
);
5060 uri
= soup_message_get_uri(msg
);
5063 uri_s
= soup_uri_to_string(uri
, FALSE
);
5065 if (strcmp(uri
->scheme
, SOUP_URI_SCHEME_HTTP
) == 0) {
5066 if (strict_transport_check(uri
->host
) ||
5067 force_https_check(uri_s
)) {
5068 DNPRINTF(XT_D_NAV
, "webview_rrs_cb: force https for %s\n",
5070 soup_uri_set_scheme(uri
, SOUP_URI_SCHEME_HTTPS
);
5075 soup_message_headers_append(msg
->request_headers
, "DNT", "1");
5078 * Check if resources on this domain have been loaded before. If
5079 * not, add the current tab's http-accept and user-agent id's to a
5080 * new domain_id and insert into the RB tree. Use these http headers
5081 * for all resources loaded from this domain for the lifetime of the
5084 if ((di_find
.domain
= uri
->host
) == NULL
)
5086 if ((di
= RB_FIND(domain_id_list
, &di_list
, &di_find
)) == NULL
) {
5087 di
= g_malloc(sizeof *di
);
5088 di
->domain
= g_strdup(uri
->host
);
5089 di
->ua_id
= t
->user_agent_id
++;
5090 di
->ha_id
= t
->http_accept_id
++;
5091 RB_INSERT(domain_id_list
, &di_list
, di
);
5093 ua_find
.id
= t
->user_agent_id
;
5094 ua
= RB_FIND(user_agent_list
, &ua_list
, &ua_find
);
5096 t
->user_agent_id
= 0;
5098 ha_find
.id
= t
->http_accept_id
;
5099 ha
= RB_FIND(http_accept_list
, &ha_list
, &ha_find
);
5101 t
->http_accept_id
= 0;
5104 ua_find
.id
= di
->ua_id
;
5105 ua
= RB_FIND(user_agent_list
, &ua_list
, &ua_find
);
5106 ha_find
.id
= di
->ha_id
;
5107 ha
= RB_FIND(http_accept_list
, &ha_list
, &ha_find
);
5110 soup_message_headers_replace(msg
->request_headers
,
5111 "User-Agent", ua
->value
);
5113 soup_message_headers_replace(msg
->request_headers
,
5114 "Accept", ha
->value
);
5122 webview_cwv_cb(WebKitWebView
*wv
, WebKitWebFrame
*wf
, struct tab
*t
)
5125 struct wl_entry
*w
= NULL
;
5127 WebKitWebView
*webview
= NULL
;
5130 DNPRINTF(XT_D_NAV
, "webview_cwv_cb: %s\n",
5131 webkit_web_view_get_uri(wv
));
5134 /* open in current tab */
5136 } else if (enable_scripts
== 0 && enable_js_whitelist
== 1) {
5137 uri
= webkit_web_view_get_uri(wv
);
5138 if (uri
&& (w
= wl_find_uri(uri
, &js_wl
)) == NULL
)
5141 if (t
->ctrl_click
) {
5142 x
= ctrl_click_focus
;
5145 tt
= create_new_tab(NULL
, NULL
, x
, -1);
5147 } else if (enable_scripts
== 1) {
5148 if (t
->ctrl_click
) {
5149 x
= ctrl_click_focus
;
5152 tt
= create_new_tab(NULL
, NULL
, x
, -1);
5160 webview_closewv_cb(WebKitWebView
*wv
, struct tab
*t
)
5163 struct wl_entry
*w
= NULL
;
5165 DNPRINTF(XT_D_NAV
, "webview_close_cb: %d\n", t
->tab_id
);
5167 if (enable_scripts
== 0 && enable_cookie_whitelist
== 1) {
5168 uri
= webkit_web_view_get_uri(wv
);
5169 if (uri
&& (w
= wl_find_uri(uri
, &js_wl
)) == NULL
)
5173 } else if (enable_scripts
== 1)
5180 webview_event_cb(GtkWidget
*w
, GdkEventButton
*e
, struct tab
*t
)
5182 /* we can not eat the event without throwing gtk off so defer it */
5184 /* catch middle click */
5185 if (e
->type
== GDK_BUTTON_RELEASE
&& e
->button
== 2) {
5190 /* catch ctrl click */
5191 if (e
->type
== GDK_BUTTON_RELEASE
&&
5192 CLEAN(e
->state
) == GDK_CONTROL_MASK
)
5197 return (XT_CB_PASSTHROUGH
);
5201 run_mimehandler(struct tab
*t
, char *mime_type
, WebKitNetworkRequest
*request
)
5203 struct mime_type
*m
;
5205 GError
*gerr
= NULL
;
5207 m
= find_mime_type(mime_type
);
5213 sv
[0] = m
->mt_action
;
5214 sv
[1] = (char *)webkit_network_request_get_uri(request
);
5217 /* ignore donothing from example config */
5218 if (m
->mt_action
&& !strcmp(m
->mt_action
, "donothing"))
5221 if (!g_spawn_async(NULL
, sv
, NULL
, G_SPAWN_SEARCH_PATH
, NULL
, NULL
,
5223 show_oops(t
, "%s: could not spawn process (%s)", __func__
,
5224 gerr
? gerr
->message
: "N/A");
5229 get_mime_type(const char *uri
)
5234 char *mime_type
= NULL
;
5238 show_oops(NULL
, "%s: invalid parameters", __func__
);
5242 if (g_str_has_prefix(uri
, "file://"))
5243 file
= g_filename_from_uri(uri
, NULL
, NULL
);
5245 file
= g_strdup(uri
);
5250 gf
= g_file_new_for_path(file
);
5251 fi
= g_file_query_info(gf
, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
, 0,
5253 if ((m
= g_file_info_get_content_type(fi
)) != NULL
)
5254 mime_type
= g_strdup(m
);
5263 run_download_mimehandler(char *mime_type
, char *file
)
5265 struct mime_type
*m
;
5268 m
= find_mime_type(mime_type
);
5272 sv
[0] = m
->mt_action
;
5275 if (!g_spawn_async(NULL
, sv
, NULL
, G_SPAWN_SEARCH_PATH
, NULL
, NULL
,
5277 show_oops(NULL
, "%s: could not spawn process: %s %s", __func__
,
5285 download_status_changed_cb(WebKitDownload
*download
, GParamSpec
*spec
,
5288 WebKitDownloadStatus status
;
5293 if (download
== NULL
)
5295 status
= webkit_download_get_status(download
);
5296 if (status
!= WEBKIT_DOWNLOAD_STATUS_FINISHED
)
5299 if (download_notifications
)
5300 show_oops(NULL
, "Download of '%s' finished",
5301 basename((char *)webkit_download_get_destination_uri(download
)));
5302 uri
= webkit_download_get_destination_uri(download
);
5305 mime
= get_mime_type(uri
);
5309 if (g_str_has_prefix(uri
, "file://"))
5310 file
= g_filename_from_uri(uri
, NULL
, NULL
);
5312 file
= g_strdup(uri
);
5317 run_download_mimehandler((char *)mime
, file
);
5324 webview_mimetype_cb(WebKitWebView
*wv
, WebKitWebFrame
*frame
,
5325 WebKitNetworkRequest
*request
, char *mime_type
,
5326 WebKitWebPolicyDecision
*decision
, struct tab
*t
)
5329 show_oops(NULL
, "webview_mimetype_cb invalid parameters");
5333 DNPRINTF(XT_D_DOWNLOAD
, "webview_mimetype_cb: tab %d mime %s\n",
5334 t
->tab_id
, mime_type
);
5336 if (run_mimehandler(t
, mime_type
, request
) == 0) {
5337 webkit_web_policy_decision_ignore(decision
);
5342 if (webkit_web_view_can_show_mime_type(wv
, mime_type
) == FALSE
) {
5343 webkit_web_policy_decision_download(decision
);
5351 download_start(struct tab
*t
, struct download
*d
, int flag
)
5353 WebKitNetworkRequest
*req
;
5355 const gchar
*suggested_name
;
5356 gchar
*filename
= NULL
;
5362 if (d
== NULL
|| t
== NULL
) {
5363 show_oops(NULL
, "%s invalid parameters", __func__
);
5367 suggested_name
= webkit_download_get_suggested_filename(d
->download
);
5368 if (suggested_name
== NULL
)
5369 return (FALSE
); /* abort download */
5380 filename
= g_strdup_printf("%d%s", i
, suggested_name
);
5383 /* XXX using urls doesn't work properly in windows? */
5384 uri
= g_strdup_printf("%s\\%s", download_dir
, i
?
5385 filename
: suggested_name
);
5387 path
= g_strdup_printf("%s" PS
"%s", download_dir
, i
?
5388 filename
: suggested_name
);
5389 if ((uri
= g_filename_to_uri(path
, NULL
, NULL
)) == NULL
)
5394 } while (!stat(uri
, &sb
));
5396 } while (!stat(path
, &sb
));
5399 DNPRINTF(XT_D_DOWNLOAD
, "%s: tab %d filename %s "
5400 "local %s\n", __func__
, t
->tab_id
, filename
, uri
);
5402 /* if we're restarting the download, or starting
5403 * it after doing something else, we need to recreate
5404 * the download request.
5406 if (flag
== XT_DL_RESTART
) {
5407 req
= webkit_network_request_new(webkit_download_get_uri(d
->download
));
5408 webkit_download_cancel(d
->download
);
5409 g_object_unref(d
->download
);
5410 d
->download
= webkit_download_new(req
);
5413 webkit_download_set_destination_uri(d
->download
, uri
);
5415 if (webkit_download_get_status(d
->download
) ==
5416 WEBKIT_DOWNLOAD_STATUS_ERROR
) {
5417 show_oops(t
, "%s: download failed to start", __func__
);
5419 show_oops(t
, "Download Failed");
5421 /* connect "download first" mime handler */
5422 g_signal_connect(G_OBJECT(d
->download
), "notify::status",
5423 G_CALLBACK(download_status_changed_cb
), NULL
);
5425 /* get from history */
5426 g_object_ref(d
->download
);
5427 show_oops(t
, "Download of '%s' started...",
5428 basename((char *)webkit_download_get_destination_uri(d
->download
)));
5431 if (flag
!= XT_DL_START
)
5432 webkit_download_start(d
->download
);
5434 DNPRINTF(XT_D_DOWNLOAD
, "download status : %d",
5435 webkit_download_get_status(d
->download
));
5437 /* sync other download manager tabs */
5438 update_download_tabs(NULL
);
5451 download_ask_cb(struct tab
*t
, GdkEventKey
*e
, gpointer data
)
5453 struct download
*d
= data
;
5457 t
->mode_cb_data
= NULL
;
5460 e
->keyval
= GDK_Escape
;
5461 return (XT_CB_PASSTHROUGH
);
5464 DPRINTF("download_ask_cb: User pressed %c\n", e
->keyval
);
5465 if (e
->keyval
== 'y' || e
->keyval
== 'Y' || e
->keyval
== GDK_Return
)
5466 /* We need to do a RESTART, because we're not calling from
5467 * webview_download_cb
5469 download_start(t
, d
, XT_DL_RESTART
);
5471 /* for all other keyvals, we just let the download be */
5472 e
->keyval
= GDK_Escape
;
5473 return (XT_CB_HANDLED
);
5477 download_ask(struct tab
*t
, struct download
*d
)
5479 const gchar
*suggested_name
;
5481 suggested_name
= webkit_download_get_suggested_filename(d
->download
);
5482 if (suggested_name
== NULL
)
5483 return (FALSE
); /* abort download */
5485 show_oops(t
, "download file %s [y/n] ?", suggested_name
);
5486 t
->mode_cb
= download_ask_cb
;
5487 t
->mode_cb_data
= d
;
5493 webview_download_cb(WebKitWebView
*wv
, WebKitDownload
*wk_download
,
5496 const gchar
*suggested_name
;
5497 struct download
*download_entry
;
5500 if (wk_download
== NULL
|| t
== NULL
) {
5501 show_oops(NULL
, "%s invalid parameters", __func__
);
5505 suggested_name
= webkit_download_get_suggested_filename(wk_download
);
5506 if (suggested_name
== NULL
)
5507 return (FALSE
); /* abort download */
5509 download_entry
= g_malloc(sizeof(struct download
));
5510 download_entry
->download
= wk_download
;
5511 download_entry
->tab
= t
;
5512 download_entry
->id
= next_download_id
++;
5513 RB_INSERT(download_list
, &downloads
, download_entry
);
5514 t
->download_requested
= 1;
5516 if (download_mode
== XT_DM_START
)
5517 ret
= download_start(t
, download_entry
, XT_DL_START
);
5518 else if (download_mode
== XT_DM_ASK
)
5519 ret
= download_ask(t
, download_entry
);
5520 else if (download_mode
== XT_DM_ADD
)
5521 show_oops(t
, "added %s to download manager",
5524 /* sync other download manager tabs */
5525 update_download_tabs(NULL
);
5528 * NOTE: never redirect/render the current tab before this
5529 * function returns. This will cause the download to never start.
5531 return (ret
); /* start download */
5535 webview_hover_cb(WebKitWebView
*wv
, gchar
*title
, gchar
*uri
, struct tab
*t
)
5537 DNPRINTF(XT_D_KEY
, "webview_hover_cb: %s %s\n", title
, uri
);
5540 show_oops(NULL
, "webview_hover_cb");
5545 set_status(t
, "Link: %s", uri
);
5547 if (statusbar_style
== XT_STATUSBAR_URL
) {
5548 const gchar
*page_uri
;
5550 if ((page_uri
= get_uri(t
)) != NULL
)
5551 set_status(t
, "%s", page_uri
);
5553 set_status(t
, "%s", (char *)get_title(t
, FALSE
));
5558 mark(struct tab
*t
, struct karg
*arg
)
5565 if ((index
= marktoindex(mark
)) == -1)
5568 if (arg
->i
== XT_MARK_SET
)
5569 t
->mark
[index
] = gtk_adjustment_get_value(t
->adjust_v
);
5570 else if (arg
->i
== XT_MARK_GOTO
) {
5571 if (t
->mark
[index
] == XT_INVALID_MARK
) {
5572 show_oops(t
, "mark '%c' does not exist", mark
);
5575 /* XXX t->mark[index] can be bigger than the maximum if ajax or
5576 something changes the document size */
5577 pos
= gtk_adjustment_get_value(t
->adjust_v
);
5578 gtk_adjustment_set_value(t
->adjust_v
, t
->mark
[index
]);
5579 t
->mark
[marktoindex('\'')] = pos
;
5586 marks_clear(struct tab
*t
)
5590 for (i
= 0; i
< LENGTH(t
->mark
); i
++)
5591 t
->mark
[i
] = XT_INVALID_MARK
;
5597 char file
[PATH_MAX
];
5598 char *line
= NULL
, *p
;
5603 snprintf(file
, sizeof file
, "%s" PS
"%s", work_dir
, XT_QMARKS_FILE
);
5604 if ((f
= fopen(file
, "r+")) == NULL
) {
5605 show_oops(NULL
, "Can't open quickmarks file: %s", strerror(errno
));
5609 for (i
= 1; ; i
++) {
5610 if ((line
= fparseln(f
, &linelen
, NULL
, NULL
, 0)) == NULL
)
5612 if (strlen(line
) == 0 || line
[0] == '#') {
5618 p
= strtok(line
, " \t");
5620 if (p
== NULL
|| strlen(p
) != 1 ||
5621 (index
= qmarktoindex(*p
)) == -1) {
5622 warnx("corrupt quickmarks file, line %d", i
);
5626 p
= strtok(NULL
, " \t");
5627 if (qmarks
[index
] != NULL
)
5628 g_free(qmarks
[index
]);
5629 qmarks
[index
] = g_strdup(p
);
5640 char file
[PATH_MAX
];
5644 snprintf(file
, sizeof file
, "%s" PS
"%s", work_dir
, XT_QMARKS_FILE
);
5645 if ((f
= fopen(file
, "r+")) == NULL
) {
5646 show_oops(NULL
, "Can't open quickmarks file: %s", strerror(errno
));
5650 for (i
= 0; i
< XT_NOQMARKS
; i
++)
5651 if (qmarks
[i
] != NULL
)
5652 fprintf(f
, "%c %s\n", indextoqmark(i
), qmarks
[i
]);
5660 qmark(struct tab
*t
, struct karg
*arg
)
5665 mark
= arg
->s
[strlen(arg
->s
)-1];
5666 index
= qmarktoindex(mark
);
5672 if (qmarks
[index
] != NULL
) {
5673 g_free(qmarks
[index
]);
5674 qmarks
[index
] = NULL
;
5677 qmarks_load(); /* sync if multiple instances */
5678 qmarks
[index
] = g_strdup(get_uri(t
));
5682 if (qmarks
[index
] != NULL
)
5683 load_uri(t
, qmarks
[index
]);
5685 show_oops(t
, "quickmark \"%c\" does not exist",
5691 if (qmarks
[index
] != NULL
)
5692 create_new_tab(qmarks
[index
], NULL
, 1, -1);
5694 show_oops(t
, "quickmark \"%c\" does not exist",
5705 go_up(struct tab
*t
, struct karg
*args
)
5713 if (args
->i
== XT_GO_UP_ROOT
)
5714 levels
= XT_GO_UP_ROOT
;
5715 else if ((levels
= atoi(args
->s
)) == 0)
5718 uri
= g_strdup(get_uri(t
));
5722 if ((tmp
= strstr(uri
, XT_PROTO_DELIM
)) == NULL
)
5725 tmp
+= strlen(XT_PROTO_DELIM
);
5727 /* it makes no sense to strip the last slash from ".../dir/", skip it */
5728 lastidx
= strlen(tmp
) - 1;
5730 if (tmp
[lastidx
] == '/')
5731 tmp
[lastidx
] = '\0';
5735 p
= strrchr(tmp
, '/');
5736 if (p
== tmp
) { /* Are we at the root of a file://-path? */
5739 } else if (p
!= NULL
)
5752 gototab(struct tab
*t
, struct karg
*args
)
5755 struct karg arg
= {0, NULL
, -1};
5757 tab
= atoi(args
->s
);
5760 arg
.i
= XT_TAB_NEXT
;
5772 zoom_amount(struct tab
*t
, struct karg
*arg
)
5774 struct karg narg
= {0, NULL
, -1};
5776 narg
.i
= atoi(arg
->s
);
5777 resizetab(t
, &narg
);
5783 flip_colon(struct tab
*t
, struct karg
*arg
)
5785 struct karg narg
= {0, NULL
, -1};
5788 if (t
== NULL
|| arg
== NULL
)
5791 p
= strstr(arg
->s
, ":");
5803 /* buffer commands receive the regex that triggered them in arg.s */
5804 char bcmd
[XT_BUFCMD_SZ
];
5808 #define XT_PRE_NO (0)
5809 #define XT_PRE_YES (1)
5810 #define XT_PRE_MAYBE (2)
5812 int (*func
)(struct tab
*, struct karg
*);
5816 { "^[0-9]*gu$", XT_PRE_MAYBE
, "gu", go_up
, 0 },
5817 { "^gU$", XT_PRE_NO
, "gU", go_up
, XT_GO_UP_ROOT
},
5818 { "^gg$", XT_PRE_NO
, "gg", move
, XT_MOVE_TOP
},
5819 { "^gG$", XT_PRE_NO
, "gG", move
, XT_MOVE_BOTTOM
},
5820 { "^[0-9]+%$", XT_PRE_YES
, "%", move
, XT_MOVE_PERCENT
},
5821 { "^zz$", XT_PRE_NO
, "zz", move
, XT_MOVE_CENTER
},
5822 { "^gh$", XT_PRE_NO
, "gh", go_home
, 0 },
5823 { "^m[a-zA-Z0-9]$", XT_PRE_NO
, "m", mark
, XT_MARK_SET
},
5824 { "^['][a-zA-Z0-9']$", XT_PRE_NO
, "'", mark
, XT_MARK_GOTO
},
5825 { "^[0-9]+t$", XT_PRE_YES
, "t", gototab
, 0 },
5826 { "^g0$", XT_PRE_YES
, "g0", movetab
, XT_TAB_FIRST
},
5827 { "^g[$]$", XT_PRE_YES
, "g$", movetab
, XT_TAB_LAST
},
5828 { "^[0-9]*gt$", XT_PRE_YES
, "t", movetab
, XT_TAB_NEXT
},
5829 { "^[0-9]*gT$", XT_PRE_YES
, "T", movetab
, XT_TAB_PREV
},
5830 { "^M[a-zA-Z0-9]$", XT_PRE_NO
, "M", qmark
, XT_QMARK_SET
},
5831 { "^go[a-zA-Z0-9]$", XT_PRE_NO
, "go", qmark
, XT_QMARK_OPEN
},
5832 { "^gn[a-zA-Z0-9]$", XT_PRE_NO
, "gn", qmark
, XT_QMARK_TAB
},
5833 { "^ZR$", XT_PRE_NO
, "ZR", restart
, 0 },
5834 { "^ZZ$", XT_PRE_NO
, "ZZ", quit
, 0 },
5835 { "^zi$", XT_PRE_NO
, "zi", resizetab
, XT_ZOOM_IN
},
5836 { "^zo$", XT_PRE_NO
, "zo", resizetab
, XT_ZOOM_OUT
},
5837 { "^z0$", XT_PRE_NO
, "z0", resizetab
, XT_ZOOM_NORMAL
},
5838 { "^[0-9]+Z$", XT_PRE_YES
, "Z", zoom_amount
, 0 },
5839 { "^[0-9]+:$", XT_PRE_YES
, ":", flip_colon
, 0 },
5843 buffercmd_init(void)
5847 for (i
= 0; i
< LENGTH(buffercmds
); i
++)
5848 if (regcomp(&buffercmds
[i
].cregex
, buffercmds
[i
].regex
,
5849 REG_EXTENDED
| REG_NOSUB
))
5850 startpage_add("invalid buffercmd regex %s",
5851 buffercmds
[i
].regex
);
5855 buffercmd_abort(struct tab
*t
)
5862 DNPRINTF(XT_D_BUFFERCMD
, "%s: clearing buffer\n", __func__
);
5864 for (i
= 0; i
< LENGTH(bcmd
); i
++)
5867 cmd_prefix
= 0; /* clear prefix for non-buffer commands */
5868 gtk_label_set_text(GTK_LABEL(t
->sbe
.buffercmd
), bcmd
);
5872 buffercmd_execute(struct tab
*t
, struct buffercmd
*cmd
)
5874 struct karg arg
= {0, NULL
, -1};
5877 arg
.s
= g_strdup(bcmd
);
5879 DNPRINTF(XT_D_BUFFERCMD
, "buffercmd_execute: buffer \"%s\" "
5880 "matches regex \"%s\", executing\n", bcmd
, cmd
->regex
);
5890 buffercmd_addkey(struct tab
*t
, guint keyval
)
5893 char s
[XT_BUFCMD_SZ
];
5895 if (gtk_widget_get_visible(GTK_WIDGET(t
->buffers
))) {
5897 return (XT_CB_PASSTHROUGH
);
5900 if (keyval
== GDK_Escape
) {
5902 return (XT_CB_HANDLED
);
5905 /* key with modifier or non-ascii character */
5906 if (!isascii(keyval
)) {
5908 * XXX this looks wrong but fixes some sites like
5909 * http://www.seslisozluk.com/
5910 * that eat a shift or ctrl and end putting default focus in js
5911 * instead of ignoring the keystroke
5912 * so instead of return (XT_CB_PASSTHROUGH); eat the key
5914 return (XT_CB_HANDLED
);
5917 DNPRINTF(XT_D_BUFFERCMD
, "buffercmd_addkey: adding key \"%c\" "
5918 "to buffer \"%s\"\n", keyval
, bcmd
);
5920 for (i
= 0; i
< LENGTH(bcmd
); i
++)
5921 if (bcmd
[i
] == '\0') {
5926 /* buffer full, ignore input */
5927 if (i
>= LENGTH(bcmd
) -1) {
5928 DNPRINTF(XT_D_BUFFERCMD
, "buffercmd_addkey: buffer full\n");
5930 return (XT_CB_HANDLED
);
5933 gtk_label_set_text(GTK_LABEL(t
->sbe
.buffercmd
), bcmd
);
5935 /* find exact match */
5936 for (i
= 0; i
< LENGTH(buffercmds
); i
++)
5937 if (regexec(&buffercmds
[i
].cregex
, bcmd
,
5938 (size_t) 0, NULL
, 0) == 0) {
5939 buffercmd_execute(t
, &buffercmds
[i
]);
5943 /* find non exact matches to see if we need to abort ot not */
5944 for (i
= 0, match
= 0; i
< LENGTH(buffercmds
); i
++) {
5945 DNPRINTF(XT_D_BUFFERCMD
, "trying: %s\n", bcmd
);
5948 if (buffercmds
[i
].precount
== XT_PRE_MAYBE
) {
5949 if (isdigit(bcmd
[0])) {
5950 if (sscanf(bcmd
, "%d%s", &c
, s
) == 0)
5954 if (sscanf(bcmd
, "%s", s
) == 0)
5957 } else if (buffercmds
[i
].precount
== XT_PRE_YES
) {
5958 if (sscanf(bcmd
, "%d%s", &c
, s
) == 0)
5961 if (sscanf(bcmd
, "%s", s
) == 0)
5964 if (c
== -1 && buffercmds
[i
].precount
)
5966 if (!strncmp(s
, buffercmds
[i
].cmd
, strlen(s
)))
5969 DNPRINTF(XT_D_BUFFERCMD
, "got[%d] %d <%s>: %d %s\n",
5970 i
, match
, buffercmds
[i
].cmd
, c
, s
);
5973 DNPRINTF(XT_D_BUFFERCMD
, "aborting: %s\n", bcmd
);
5978 return (XT_CB_HANDLED
);
5982 * XXX we were seeing a bunch of focus issues with the toplevel
5983 * main_window losing its is-active and has-toplevel-focus properties.
5984 * This is the most correct and portable solution we could come up with
5985 * without relying on calling internal GTK functions (which we
5986 * couldn't link to in Linux).
5988 #if GTK_CHECK_VERSION(3, 0, 0)
5990 fake_focus_in(GtkWidget
*w
)
5992 if (fevent
== NULL
) {
5993 fevent
= gdk_event_new(GDK_FOCUS_CHANGE
);
5994 fevent
->focus_change
.window
=
5995 gtk_widget_get_window(main_window
);
5996 fevent
->focus_change
.type
= GDK_FOCUS_CHANGE
;
5997 fevent
->focus_change
.in
= TRUE
;
5999 gtk_widget_send_focus_change(main_window
, fevent
);
6004 handle_keypress(struct tab
*t
, GdkEventKey
*e
, int entry
)
6007 struct key_binding
*k
;
6010 * This sometimes gets randomly unset for whatever reason in GTK3.
6011 * If we're handling a keypress, the main window's is-active propery
6012 * *must* be true, or else many things will break.
6014 #if GTK_CHECK_VERSION(3, 0, 0)
6015 fake_focus_in(main_window
);
6018 /* handle keybindings if buffercmd is empty.
6019 if not empty, allow commands like C-n */
6020 if (bcmd
[0] == '\0' || ((e
->state
& (CTRL
| MOD1
)) != 0))
6021 TAILQ_FOREACH(k
, &kbl
, entry
)
6022 if (e
->keyval
== k
->key
6023 && (entry
? k
->use_in_entry
: 1)) {
6024 /* when we are edditing eat ctrl/mod keys */
6025 if (edit_mode
== XT_EM_VI
&&
6026 t
->mode
== XT_MODE_INSERT
&&
6027 (e
->state
& CTRL
|| e
->state
& MOD1
))
6028 return (XT_CB_PASSTHROUGH
);
6031 if ((e
->state
& (CTRL
| MOD1
)) == 0)
6033 } else if ((e
->state
& k
->mask
) == k
->mask
) {
6038 if (!entry
&& ((e
->state
& (CTRL
| MOD1
)) == 0))
6039 return (buffercmd_addkey(t
, e
->keyval
));
6041 return (XT_CB_PASSTHROUGH
);
6044 if (k
->cmd
[0] == ':') {
6046 args
.s
= &k
->cmd
[1];
6047 return (command(t
, &args
));
6049 return (cmd_execute(t
, k
->cmd
));
6053 wv_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6057 /* don't use w directly; use t->whatever instead */
6060 show_oops(NULL
, "wv_keypress_cb");
6061 return (XT_CB_PASSTHROUGH
);
6067 return (t
->mode_cb(t
, e
, t
->mode_cb_data
));
6069 DNPRINTF(XT_D_KEY
, "wv_keypress_cb: mode %d keyval 0x%x mask "
6070 "0x%x tab %d\n", t
->mode
, e
->keyval
, e
->state
, t
->tab_id
);
6072 /* Hide buffers, if they are visible, with escape. */
6073 if (gtk_widget_get_visible(GTK_WIDGET(t
->buffers
)) &&
6074 CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Escape
) {
6075 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
6077 return (XT_CB_HANDLED
);
6080 if ((CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Tab
) ||
6081 (CLEAN(e
->state
) == SHFT
&& e
->keyval
== GDK_Tab
))
6082 /* something focussy is about to happen */
6083 return (XT_CB_PASSTHROUGH
);
6085 /* check if we are some sort of text input thing in the dom */
6086 input_check_mode(t
);
6088 if (t
->mode
== XT_MODE_PASSTHROUGH
) {
6089 if (CLEAN(e
->state
) == 0 && e
->keyval
== GDK_Escape
)
6090 t
->mode
= XT_MODE_COMMAND
;
6091 return (XT_CB_PASSTHROUGH
);
6092 } else if (t
->mode
== XT_MODE_COMMAND
|| t
->mode
== XT_MODE_HINT
) {
6094 snprintf(s
, sizeof s
, "%c", e
->keyval
);
6095 if (CLEAN(e
->state
) == 0 && isdigit(s
[0]))
6096 cmd_prefix
= 10 * cmd_prefix
+ atoi(s
);
6097 return (handle_keypress(t
, e
, 0));
6100 return (handle_keypress(t
, e
, 1));
6104 return (XT_CB_PASSTHROUGH
);
6108 hint_continue(struct tab
*t
)
6110 const gchar
*c
= gtk_entry_get_text(GTK_ENTRY(t
->cmd
));
6112 const gchar
*errstr
= NULL
;
6116 if (!(c
[0] == '.' || c
[0] == ','))
6118 if (strlen(c
) == 1) {
6119 /* XXX should not happen */
6124 if (isdigit(c
[1])) {
6126 i
= strtonum(&c
[1], 1, 4096, &errstr
);
6128 show_oops(t
, "invalid numerical hint %s", &c
[1]);
6131 s
= g_strdup_printf("hints.updateHints(%d);", i
);
6135 /* alphanumeric input */
6136 s
= g_strdup_printf("hints.createHints('%s', '%c');",
6137 &c
[1], c
[0] == '.' ? 'f' : 'F');
6148 search_continue(struct tab
*t
)
6150 const gchar
*c
= gtk_entry_get_text(GTK_ENTRY(t
->cmd
));
6151 gboolean rv
= FALSE
;
6153 if (c
[0] == ':' || c
[0] == '.' || c
[0] == ',')
6155 if (strlen(c
) == 1) {
6156 webkit_web_view_unmark_text_matches(t
->wv
);
6161 t
->search_forward
= TRUE
;
6162 else if (c
[0] == '?')
6163 t
->search_forward
= FALSE
;
6173 search_cb(struct tab
*t
)
6175 const gchar
*c
= gtk_entry_get_text(GTK_ENTRY(t
->cmd
));
6176 #if !GTK_CHECK_VERSION(3, 0, 0)
6180 if (search_continue(t
) == FALSE
)
6184 if (webkit_web_view_search_text(t
->wv
, &c
[1], FALSE
, t
->search_forward
,
6186 /* not found, mark red */
6187 #if GTK_CHECK_VERSION(3, 0, 0)
6188 gtk_widget_set_name(t
->cmd
, XT_CSS_RED
);
6190 gdk_color_parse(XT_COLOR_RED
, &color
);
6191 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
, &color
);
6193 /* unmark and remove selection */
6194 webkit_web_view_unmark_text_matches(t
->wv
);
6195 /* my kingdom for a way to unselect text in webview */
6197 /* found, highlight all */
6198 webkit_web_view_unmark_text_matches(t
->wv
);
6199 webkit_web_view_mark_text_matches(t
->wv
, &c
[1], FALSE
, 0);
6200 webkit_web_view_set_highlight_text_matches(t
->wv
, TRUE
);
6201 #if GTK_CHECK_VERSION(3, 0, 0)
6202 gtk_widget_set_name(t
->cmd
, XT_CSS_NORMAL
);
6204 gtk_widget_modify_base(t
->cmd
, GTK_STATE_NORMAL
,
6205 &t
->default_style
->base
[GTK_STATE_NORMAL
]);
6214 cmd_keyrelease_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6216 const gchar
*c
= gtk_entry_get_text(w
);
6219 show_oops(NULL
, "cmd_keyrelease_cb invalid parameters");
6220 return (XT_CB_PASSTHROUGH
);
6223 DNPRINTF(XT_D_CMD
, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x tab %d\n",
6224 e
->keyval
, e
->state
, t
->tab_id
);
6227 if (!(e
->keyval
== GDK_Tab
|| e
->keyval
== GDK_ISO_Left_Tab
)) {
6228 if (hint_continue(t
) == FALSE
)
6233 if (search_continue(t
) == FALSE
)
6236 /* if search length is > 4 then no longer play timeout games */
6237 if (strlen(c
) > 4) {
6239 g_source_remove(t
->search_id
);
6246 /* reestablish a new timer if the user types fast */
6248 g_source_remove(t
->search_id
);
6249 t
->search_id
= g_timeout_add(250, (GSourceFunc
)search_cb
, (gpointer
)t
);
6252 return (XT_CB_PASSTHROUGH
);
6256 match_uri(const gchar
*uri
, const gchar
*key
) {
6259 gboolean match
= FALSE
;
6263 if (!strncmp(key
, uri
, len
))
6266 voffset
= strstr(uri
, "/") + 2;
6267 if (!strncmp(key
, voffset
, len
))
6269 else if (g_str_has_prefix(voffset
, "www.")) {
6270 voffset
= voffset
+ strlen("www.");
6271 if (!strncmp(key
, voffset
, len
))
6280 match_session(const gchar
*name
, const gchar
*key
) {
6283 sub
= strcasestr(name
, key
);
6289 cmd_getlist(int id
, char *key
)
6296 if (cmds
[id
].type
& XT_URLARG
) {
6297 RB_FOREACH_REVERSE(h
, history_list
, &hl
)
6298 if (match_uri(h
->uri
, key
)) {
6299 cmd_status
.list
[c
] = (char *)h
->uri
;
6305 } else if (cmds
[id
].type
& XT_SESSARG
) {
6306 TAILQ_FOREACH(s
, &sessions
, entry
)
6307 if (match_session(s
->name
, key
)) {
6308 cmd_status
.list
[c
] = (char *)s
->name
;
6314 } else if (cmds
[id
].type
& XT_SETARG
) {
6315 for (i
= 0; i
< get_settings_size(); i
++)
6316 if (!strncmp(key
, get_setting_name(i
),
6318 cmd_status
.list
[c
++] =
6319 get_setting_name(i
);
6325 dep
= (id
== -1) ? 0 : cmds
[id
].level
+ 1;
6327 for (i
= id
+ 1; i
< LENGTH(cmds
); i
++) {
6328 if (cmds
[i
].level
< dep
)
6330 if (cmds
[i
].level
== dep
&& !strncmp(key
, cmds
[i
].cmd
,
6331 strlen(key
)) && !isdigit(cmds
[i
].cmd
[0]))
6332 cmd_status
.list
[c
++] = cmds
[i
].cmd
;
6340 cmd_getnext(int dir
)
6342 cmd_status
.index
+= dir
;
6344 if (cmd_status
.index
< 0)
6345 cmd_status
.index
= cmd_status
.len
- 1;
6346 else if (cmd_status
.index
>= cmd_status
.len
)
6347 cmd_status
.index
= 0;
6349 return cmd_status
.list
[cmd_status
.index
];
6353 cmd_tokenize(char *s
, char *tokens
[])
6356 char *tok
, *last
= NULL
;
6357 size_t len
= strlen(s
);
6360 blank
= len
== 0 || (len
> 0 && s
[len
- 1] == ' ');
6361 for (tok
= strtok_r(s
, " ", &last
); tok
&& i
< 3;
6362 tok
= strtok_r(NULL
, " ", &last
), i
++)
6372 cmd_complete(struct tab
*t
, char *str
, int dir
)
6374 GtkEntry
*w
= GTK_ENTRY(t
->cmd
);
6375 int i
, j
, levels
, c
= 0, dep
= 0, parent
= -1;
6377 char *tok
, *match
, *s
= g_strdup(str
);
6379 char res
[XT_MAX_URL_LENGTH
+ 32] = ":";
6382 DNPRINTF(XT_D_CMD
, "%s: complete %s\n", __func__
, str
);
6385 for (i
= 0; isdigit(s
[i
]); i
++)
6388 for (; isspace(s
[i
]); i
++)
6393 levels
= cmd_tokenize(s
, tokens
);
6395 for (i
= 0; i
< levels
- 1; i
++) {
6398 for (j
= c
; j
< LENGTH(cmds
); j
++) {
6399 if (cmds
[j
].level
< dep
)
6401 if (cmds
[j
].level
== dep
&& !strncmp(tok
, cmds
[j
].cmd
,
6405 if (strlen(tok
) == strlen(cmds
[j
].cmd
)) {
6412 if (matchcount
== 1) {
6413 strlcat(res
, tok
, sizeof res
);
6414 strlcat(res
, " ", sizeof res
);
6424 if (cmd_status
.index
== -1)
6425 cmd_getlist(parent
, tokens
[i
]);
6427 if (cmd_status
.len
> 0) {
6428 match
= cmd_getnext(dir
);
6429 strlcat(res
, match
, sizeof res
);
6430 gtk_entry_set_text(w
, res
);
6431 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6438 parse_prefix_and_alias(const char *str
, int *prefix
)
6440 struct cmd_alias
*c
;
6441 char *s
= g_strdup(str
), *sc
;
6446 if (isdigit(s
[0])) {
6447 sscanf(s
, "%d", prefix
);
6448 while (isdigit(s
[0]) || isspace(s
[0]))
6452 TAILQ_FOREACH(c
, &cal
, entry
) {
6453 if (strncmp(s
, c
->alias
, strlen(c
->alias
)))
6456 if (strlen(s
) == strlen(c
->alias
)) {
6458 return (g_strdup(c
->cmd
));
6461 if (!isspace(s
[strlen(c
->alias
)]))
6464 s
= g_strdup_printf("%s %s", c
->cmd
, &s
[strlen(c
->alias
) + 1]);
6474 cmd_execute(struct tab
*t
, char *str
)
6476 struct cmd
*cmd
= NULL
;
6477 char *tok
, *last
= NULL
, *s
= str
;
6478 int j
= 0, len
, c
= 0, dep
= 0, matchcount
= 0;
6479 int prefix
= -1, rv
= XT_CB_PASSTHROUGH
;
6480 struct karg arg
= {0, NULL
, -1};
6482 s
= parse_prefix_and_alias(s
, &prefix
);
6484 for (tok
= strtok_r(s
, " ", &last
); tok
;
6485 tok
= strtok_r(NULL
, " ", &last
)) {
6487 for (j
= c
; j
< LENGTH(cmds
); j
++) {
6488 if (cmds
[j
].level
< dep
)
6490 len
= (tok
[strlen(tok
) - 1] == '!') ? strlen(tok
) - 1 :
6492 if (cmds
[j
].level
== dep
&&
6493 !strncmp(tok
, cmds
[j
].cmd
, len
)) {
6497 if (len
== strlen(cmds
[j
].cmd
)) {
6503 if (matchcount
== 1) {
6508 show_oops(t
, "Invalid command: %s", str
);
6514 show_oops(t
, "Empty command");
6520 arg
.precount
= prefix
;
6521 else if (cmd_prefix
> 0)
6522 arg
.precount
= cmd_prefix
;
6524 if (j
> 0 && !(cmd
->type
& XT_PREFIX
) && arg
.precount
> -1) {
6525 show_oops(t
, "No prefix allowed: %s", str
);
6529 arg
.s
= last
? g_strdup(last
) : g_strdup("");
6530 if (cmd
->type
& XT_INTARG
&& last
&& strlen(last
) > 0) {
6531 if (arg
.s
== NULL
) {
6532 show_oops(t
, "Invalid command");
6535 arg
.precount
= atoi(arg
.s
);
6536 if (arg
.precount
<= 0) {
6537 if (arg
.s
[0] == '0')
6538 show_oops(t
, "Zero count");
6540 show_oops(t
, "Trailing characters");
6545 DNPRINTF(XT_D_CMD
, "%s: prefix %d arg %s\n",
6546 __func__
, arg
.precount
, arg
.s
);
6562 save_runtime_setting(const char *name
, const char *val
)
6568 char file
[PATH_MAX
];
6569 char delim
[3] = { '\0', '\0', '\0' };
6570 char *line
, *lt
, *start
;
6571 char *contents
, *tmp
;
6573 if (runtime_settings
== NULL
|| strlen(runtime_settings
) == 0)
6576 snprintf(file
, sizeof file
, "%s" PS
"%s", work_dir
, runtime_settings
);
6577 if (stat(file
, &sb
) || (f
= fopen(file
, "r+")) == NULL
)
6579 lt
= g_strdup_printf("%s=%s", name
, val
);
6580 contents
= g_strdup("");
6582 line
= fparseln(f
, &linelen
, NULL
, delim
, 0);
6583 if (line
== NULL
|| linelen
== 0)
6586 start
= g_strdup_printf("%s=", name
);
6587 if (strstr(line
, start
) == NULL
)
6588 contents
= g_strdup_printf("%s%s\n", contents
, line
);
6591 contents
= g_strdup_printf("%s%s\n", contents
, lt
);
6600 contents
= g_strdup_printf("%s%s\n", contents
, lt
);
6603 if ((f
= freopen(file
, "w", f
)) == NULL
)
6616 entry_focus_cb(GtkWidget
*w
, GdkEvent e
, struct tab
*t
)
6619 * This sometimes gets randomly unset for whatever reason in GTK3,
6620 * causing a GtkEntry's text cursor becomes invisible. When we focus
6621 * a GtkEntry, be sure to manually reset the main window's is-active
6622 * property so the cursor is shown correctly.
6624 #if GTK_CHECK_VERSION(3, 0, 0)
6625 fake_focus_in(main_window
);
6627 return (XT_CB_PASSTHROUGH
);
6631 entry_key_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6634 show_oops(NULL
, "entry_key_cb invalid parameters");
6635 return (XT_CB_PASSTHROUGH
);
6638 DNPRINTF(XT_D_CMD
, "entry_key_cb: keyval 0x%x mask 0x%x tab %d\n",
6639 e
->keyval
, e
->state
, t
->tab_id
);
6643 if (e
->keyval
== GDK_Escape
) {
6644 /* don't use focus_webview(t) because we want to type :cmds */
6645 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
6648 return (handle_keypress(t
, e
, 1));
6651 struct command_entry
*
6652 history_prev(struct command_list
*l
, struct command_entry
*at
)
6655 at
= TAILQ_LAST(l
, command_list
);
6657 at
= TAILQ_PREV(at
, command_list
, entry
);
6659 at
= TAILQ_LAST(l
, command_list
);
6665 struct command_entry
*
6666 history_next(struct command_list
*l
, struct command_entry
*at
)
6669 at
= TAILQ_FIRST(l
);
6671 at
= TAILQ_NEXT(at
, entry
);
6673 at
= TAILQ_FIRST(l
);
6680 cmd_keypress_cb(GtkEntry
*w
, GdkEventKey
*e
, struct tab
*t
)
6682 int rv
= XT_CB_HANDLED
;
6683 const gchar
*c
= gtk_entry_get_text(w
);
6687 show_oops(NULL
, "cmd_keypress_cb parameters");
6688 return (XT_CB_PASSTHROUGH
);
6691 DNPRINTF(XT_D_CMD
, "cmd_keypress_cb: keyval 0x%x mask 0x%x tab %d\n",
6692 e
->keyval
, e
->state
, t
->tab_id
);
6696 e
->keyval
= GDK_Escape
;
6697 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?' ||
6698 c
[0] == '.' || c
[0] == ','))
6699 e
->keyval
= GDK_Escape
;
6701 if (e
->keyval
!= GDK_Tab
&& e
->keyval
!= GDK_Shift_L
&&
6702 e
->keyval
!= GDK_ISO_Left_Tab
)
6703 cmd_status
.index
= -1;
6705 switch (e
->keyval
) {
6708 cmd_complete(t
, (char *)&c
[1], 1);
6709 else if (c
[0] == '.' || c
[0] == ',')
6710 run_script(t
, "hints.focusNextHint();");
6712 case GDK_ISO_Left_Tab
:
6714 cmd_complete(t
, (char *)&c
[1], -1);
6715 else if (c
[0] == '.' || c
[0] == ',')
6716 run_script(t
, "hints.focusPreviousHint();");
6720 if ((search_at
= history_next(&shl
, search_at
))) {
6721 search_at
->line
[0] = c
[0];
6722 gtk_entry_set_text(w
, search_at
->line
);
6723 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6725 } else if (c
[0] == '/') {
6726 if ((search_at
= history_prev(&shl
, search_at
))) {
6727 search_at
->line
[0] = c
[0];
6728 gtk_entry_set_text(w
, search_at
->line
);
6729 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6731 } else if (c
[0] == ':') {
6732 if ((history_at
= history_prev(&chl
, history_at
))) {
6733 history_at
->line
[0] = c
[0];
6734 gtk_entry_set_text(w
, history_at
->line
);
6735 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6741 if ((search_at
= history_next(&shl
, search_at
))) {
6742 search_at
->line
[0] = c
[0];
6743 gtk_entry_set_text(w
, search_at
->line
);
6744 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6746 } else if (c
[0] == '?') {
6747 if ((search_at
= history_prev(&shl
, search_at
))) {
6748 search_at
->line
[0] = c
[0];
6749 gtk_entry_set_text(w
, search_at
->line
);
6750 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6752 } if (c
[0] == ':') {
6753 if ((history_at
= history_next(&chl
, history_at
))) {
6754 history_at
->line
[0] = c
[0];
6755 gtk_entry_set_text(w
, history_at
->line
);
6756 gtk_editable_set_position(GTK_EDITABLE(w
), -1);
6761 if (!(!strcmp(c
, ":") || !strcmp(c
, "/") || !strcmp(c
, "?") ||
6762 !strcmp(c
, ".") || !strcmp(c
, ","))) {
6763 /* see if we are doing hinting and reset it */
6764 if (c
[0] == '.' || c
[0] == ',') {
6765 /* recreate hints */
6766 s
= g_strdup_printf("hints.createHints('', "
6767 "'%c');", c
[0] == '.' ? 'f' : 'F');
6779 if (c
!= NULL
&& (c
[0] == '/' || c
[0] == '?'))
6780 webkit_web_view_unmark_text_matches(t
->wv
);
6782 /* no need to cancel hints */
6786 rv
= XT_CB_PASSTHROUGH
;
6792 wv_popup_activ_cb(GtkMenuItem
*menu
, struct tab
*t
)
6794 GtkAction
*a
= NULL
;
6795 GtkClipboard
*clipboard
, *primary
;
6796 const gchar
*name
, *uri
;
6798 a
= gtk_activatable_get_related_action(GTK_ACTIVATABLE(menu
));
6801 name
= gtk_action_get_name(a
);
6803 DNPRINTF(XT_D_CMD
, "wv_popup_activ_cb: tab %d action %s\n",
6807 * context-menu-action-3 copy link location
6808 * context-menu-action-7 copy image address
6809 * context-menu-action-2030 copy video link location
6813 if ((g_strcmp0(name
, "context-menu-action-3") == 0) ||
6814 (g_strcmp0(name
, "context-menu-action-7") == 0) ||
6815 (g_strcmp0(name
, "context-menu-action-2030") == 0)) {
6816 clipboard
= gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
);
6817 primary
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
6818 uri
= gtk_clipboard_wait_for_text(clipboard
);
6821 gtk_clipboard_set_text(primary
, uri
, -1);
6826 wv_popup_cb(WebKitWebView
*wview
, GtkMenu
*menu
, struct tab
*t
)
6830 DNPRINTF(XT_D_CMD
, "wv_popup_cb: tab %d\n", t
->tab_id
);
6832 items
= gtk_container_get_children(GTK_CONTAINER(menu
));
6833 for (l
= items
; l
; l
= l
->next
)
6834 g_signal_connect(l
->data
, "activate",
6835 G_CALLBACK(wv_popup_activ_cb
), t
);
6840 cmd_popup_cb(GtkEntry
*entry
, GtkMenu
*menu
, struct tab
*t
)
6842 /* popup menu enabled */
6847 cmd_focusout_cb(GtkWidget
*w
, GdkEventFocus
*e
, struct tab
*t
)
6850 show_oops(NULL
, "cmd_focusout_cb invalid parameters");
6851 return (XT_CB_PASSTHROUGH
);
6854 DNPRINTF(XT_D_CMD
, "cmd_focusout_cb: tab %d popup %d\n",
6855 t
->tab_id
, t
->popup
);
6857 /* if popup is enabled don't lose focus */
6860 return (XT_CB_PASSTHROUGH
);
6867 return (XT_CB_PASSTHROUGH
);
6871 cmd_hide_cb(GtkWidget
*w
, struct tab
*t
)
6874 show_oops(NULL
, "%s: invalid parameters", __func__
);
6878 if (show_url
== 0 || t
->focus_wv
)
6881 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
6885 cmd_activate_cb(GtkEntry
*entry
, struct tab
*t
)
6888 const gchar
*c
= gtk_entry_get_text(entry
);
6891 show_oops(NULL
, "cmd_activate_cb invalid parameters");
6895 DNPRINTF(XT_D_CMD
, "cmd_activate_cb: tab %d %s\n", t
->tab_id
, c
);
6900 else if (!(c
[0] == ':' || c
[0] == '/' || c
[0] == '?' ||
6901 c
[0] == '.' || c
[0] == ','))
6907 if (c
[0] == '/' || c
[0] == '?') {
6908 /* see if there is a timer pending */
6910 g_source_remove(t
->search_id
);
6915 if (t
->search_text
) {
6916 g_free(t
->search_text
);
6917 t
->search_text
= NULL
;
6920 t
->search_text
= g_strdup(s
);
6922 g_free(global_search
);
6923 global_search
= g_strdup(s
);
6924 t
->search_forward
= c
[0] == '/';
6926 history_add(&shl
, search_file
, s
, &search_history_count
);
6927 } else if (c
[0] == '.' || c
[0] == ',') {
6928 run_script(t
, "hints.fire();");
6929 /* XXX history for link following? */
6930 } else if (c
[0] == ':') {
6931 history_add(&chl
, command_file
, s
, &cmd_history_count
);
6932 /* can't call hide_cmd after cmd_execute */
6943 backward_cb(GtkWidget
*w
, struct tab
*t
)
6948 show_oops(NULL
, "backward_cb invalid parameters");
6952 DNPRINTF(XT_D_NAV
, "backward_cb: tab %d\n", t
->tab_id
);
6959 forward_cb(GtkWidget
*w
, struct tab
*t
)
6964 show_oops(NULL
, "forward_cb invalid parameters");
6968 DNPRINTF(XT_D_NAV
, "forward_cb: tab %d\n", t
->tab_id
);
6970 a
.i
= XT_NAV_FORWARD
;
6975 home_cb(GtkWidget
*w
, struct tab
*t
)
6978 show_oops(NULL
, "home_cb invalid parameters");
6982 DNPRINTF(XT_D_NAV
, "home_cb: tab %d\n", t
->tab_id
);
6988 stop_cb(GtkWidget
*w
, struct tab
*t
)
6990 WebKitWebFrame
*frame
;
6993 show_oops(NULL
, "stop_cb invalid parameters");
6997 DNPRINTF(XT_D_NAV
, "stop_cb: tab %d\n", t
->tab_id
);
6999 frame
= webkit_web_view_get_main_frame(t
->wv
);
7000 if (frame
== NULL
) {
7001 show_oops(t
, "stop_cb: no frame");
7005 webkit_web_frame_stop_loading(frame
);
7006 abort_favicon_download(t
);
7010 setup_webkit(struct tab
*t
)
7012 if (is_g_object_setting(G_OBJECT(t
->settings
), "enable-dns-prefetching"))
7013 g_object_set(G_OBJECT(t
->settings
), "enable-dns-prefetching",
7014 FALSE
, (char *)NULL
);
7016 warnx("webkit does not have \"enable-dns-prefetching\" property");
7017 g_object_set(G_OBJECT(t
->settings
),
7018 "enable-scripts", enable_scripts
, (char *)NULL
);
7019 g_object_set(G_OBJECT(t
->settings
),
7020 "enable-plugins", enable_plugins
, (char *)NULL
);
7021 g_object_set(G_OBJECT(t
->settings
),
7022 "javascript-can-open-windows-automatically", enable_scripts
,
7024 g_object_set(G_OBJECT(t
->settings
),
7025 "enable-html5-database", FALSE
, (char *)NULL
);
7026 g_object_set(G_OBJECT(t
->settings
),
7027 "enable-html5-local-storage", enable_localstorage
, (char *)NULL
);
7028 g_object_set(G_OBJECT(t
->settings
),
7029 "enable_spell_checking", enable_spell_checking
, (char *)NULL
);
7030 g_object_set(G_OBJECT(t
->settings
),
7031 "spell_checking_languages", spell_check_languages
, (char *)NULL
);
7032 g_object_set(G_OBJECT(t
->settings
),
7033 "enable-developer-extras", TRUE
, (char *)NULL
);
7034 g_object_set(G_OBJECT(t
->wv
),
7035 "full-content-zoom", TRUE
, (char *)NULL
);
7036 g_object_set(G_OBJECT(t
->settings
),
7037 "auto-load-images", auto_load_images
, (char *)NULL
);
7038 if (is_g_object_setting(G_OBJECT(t
->settings
),
7039 "enable-display-of-insecure-content"))
7040 g_object_set(G_OBJECT(t
->settings
),
7041 "enable-display-of-insecure-content",
7042 allow_insecure_content
, (char *)NULL
);
7043 if (is_g_object_setting(G_OBJECT(t
->settings
),
7044 "enable-running-of-insecure-content"))
7045 g_object_set(G_OBJECT(t
->settings
),
7046 "enable-running-of-insecure-content",
7047 allow_insecure_scripts
, (char *)NULL
);
7049 webkit_web_view_set_settings(t
->wv
, t
->settings
);
7053 update_statusbar_position(GtkAdjustment
* adjustment
, gpointer data
)
7055 struct tab
*ti
, *t
= NULL
;
7056 gdouble view_size
, value
, max
;
7059 TAILQ_FOREACH(ti
, &tabs
, entry
)
7060 if (ti
->tab_id
== gtk_notebook_get_current_page(notebook
)) {
7068 if (adjustment
== NULL
)
7069 adjustment
= gtk_scrolled_window_get_vadjustment(
7070 GTK_SCROLLED_WINDOW(t
->browser_win
));
7072 view_size
= gtk_adjustment_get_page_size(adjustment
);
7073 value
= gtk_adjustment_get_value(adjustment
);
7074 max
= gtk_adjustment_get_upper(adjustment
) - view_size
;
7077 position
= g_strdup("All");
7078 else if (value
== max
)
7079 position
= g_strdup("Bot");
7080 else if (value
== 0)
7081 position
= g_strdup("Top");
7083 position
= g_strdup_printf("%d%%", (int) ((value
/ max
) * 100));
7085 gtk_label_set_text(GTK_LABEL(t
->sbe
.position
), position
);
7092 create_window(const gchar
*name
)
7096 w
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
7097 if (window_maximize
)
7098 gtk_window_maximize(GTK_WINDOW(w
));
7100 gtk_window_set_default_size(GTK_WINDOW(w
), window_width
, window_height
);
7101 gtk_widget_set_name(w
, name
);
7102 gtk_window_set_wmclass(GTK_WINDOW(w
), name
, "Xombrero");
7108 create_browser(struct tab
*t
)
7111 GtkAdjustment
*adjustment
;
7114 show_oops(NULL
, "create_browser invalid parameters");
7118 w
= gtk_scrolled_window_new(NULL
, NULL
);
7119 gtk_widget_set_can_focus(w
, FALSE
);
7120 t
->adjust_h
= gtk_scrolled_window_get_hadjustment(
7121 GTK_SCROLLED_WINDOW(w
));
7122 t
->adjust_v
= gtk_scrolled_window_get_vadjustment(
7123 GTK_SCROLLED_WINDOW(w
));
7124 #if !GTK_CHECK_VERSION(3, 0, 0)
7125 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w
),
7126 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
7130 t
->wv
= WEBKIT_WEB_VIEW(webkit_web_view_new());
7131 gtk_container_add(GTK_CONTAINER(w
), GTK_WIDGET(t
->wv
));
7134 t
->settings
= webkit_web_settings_new();
7136 g_object_set(t
->settings
, "default-encoding", encoding
, (char *)NULL
);
7138 t
->stylesheet
= g_strdup(stylesheet
);
7139 t
->load_images
= auto_load_images
;
7142 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w
));
7143 g_signal_connect(G_OBJECT(adjustment
), "value-changed",
7144 G_CALLBACK(update_statusbar_position
), NULL
);
7153 create_kiosk_toolbar(struct tab
*t
)
7155 GtkWidget
*toolbar
= NULL
, *b
;
7157 #if GTK_CHECK_VERSION(3, 0, 0)
7158 b
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
7159 gtk_widget_set_name(GTK_WIDGET(b
), "toolbar");
7161 b
= gtk_hbox_new(FALSE
, 0);
7164 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
7166 /* backward button */
7167 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7168 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7169 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7170 G_CALLBACK(backward_cb
), t
);
7171 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, TRUE
, TRUE
, 0);
7173 /* forward button */
7174 t
->forward
= create_button("Forward", GTK_STOCK_GO_FORWARD
, 0);
7175 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7176 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7177 G_CALLBACK(forward_cb
), t
);
7178 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, TRUE
, TRUE
, 0);
7181 t
->gohome
= create_button("Home", GTK_STOCK_HOME
, 0);
7182 gtk_widget_set_sensitive(t
->gohome
, true);
7183 g_signal_connect(G_OBJECT(t
->gohome
), "clicked",
7184 G_CALLBACK(home_cb
), t
);
7185 gtk_box_pack_start(GTK_BOX(b
), t
->gohome
, TRUE
, TRUE
, 0);
7187 /* create widgets but don't use them */
7188 t
->uri_entry
= gtk_entry_new();
7189 g_signal_connect(G_OBJECT(t
->uri_entry
), "focus-in-event",
7190 G_CALLBACK(entry_focus_cb
), t
);
7191 #if !GTK_CHECK_VERSION(3, 0, 0)
7192 t
->default_style
= gtk_rc_get_style(t
->uri_entry
);
7194 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7195 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7196 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7202 create_toolbar(struct tab
*t
)
7204 GtkWidget
*toolbar
= NULL
, *b
;
7206 #if GTK_CHECK_VERSION(3, 0, 0)
7207 b
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
7208 gtk_widget_set_name(GTK_WIDGET(b
), "toolbar");
7210 b
= gtk_hbox_new(FALSE
, 0);
7213 gtk_container_set_border_width(GTK_CONTAINER(toolbar
), 0);
7215 /* backward button */
7216 t
->backward
= create_button("Back", GTK_STOCK_GO_BACK
, 0);
7217 gtk_widget_set_sensitive(t
->backward
, FALSE
);
7218 g_signal_connect(G_OBJECT(t
->backward
), "clicked",
7219 G_CALLBACK(backward_cb
), t
);
7220 gtk_box_pack_start(GTK_BOX(b
), t
->backward
, FALSE
, FALSE
, 0);
7222 /* forward button */
7223 t
->forward
= create_button("Forward",GTK_STOCK_GO_FORWARD
, 0);
7224 gtk_widget_set_sensitive(t
->forward
, FALSE
);
7225 g_signal_connect(G_OBJECT(t
->forward
), "clicked",
7226 G_CALLBACK(forward_cb
), t
);
7227 gtk_box_pack_start(GTK_BOX(b
), t
->forward
, FALSE
, FALSE
, 0);
7230 t
->stop
= create_button("Stop", GTK_STOCK_STOP
, 0);
7231 gtk_widget_set_sensitive(t
->stop
, FALSE
);
7232 g_signal_connect(G_OBJECT(t
->stop
), "clicked", G_CALLBACK(stop_cb
), t
);
7233 gtk_box_pack_start(GTK_BOX(b
), t
->stop
, FALSE
, FALSE
, 0);
7236 t
->js_toggle
= create_button("JS-Toggle", enable_scripts
?
7237 GTK_STOCK_MEDIA_PLAY
: GTK_STOCK_MEDIA_PAUSE
, 0);
7238 gtk_widget_set_sensitive(t
->js_toggle
, TRUE
);
7239 g_signal_connect(G_OBJECT(t
->js_toggle
), "clicked",
7240 G_CALLBACK(js_toggle_cb
), t
);
7241 gtk_box_pack_start(GTK_BOX(b
), t
->js_toggle
, FALSE
, FALSE
, 0);
7243 t
->uri_entry
= gtk_entry_new();
7244 g_signal_connect(G_OBJECT(t
->uri_entry
), "activate",
7245 G_CALLBACK(activate_uri_entry_cb
), t
);
7246 g_signal_connect(G_OBJECT(t
->uri_entry
), "key-press-event",
7247 G_CALLBACK(entry_key_cb
), t
);
7248 g_signal_connect(G_OBJECT(t
->uri_entry
), "focus-in-event",
7249 G_CALLBACK(entry_focus_cb
), t
);
7251 gtk_box_pack_start(GTK_BOX(b
), t
->uri_entry
, TRUE
, TRUE
, 0);
7254 t
->search_entry
= gtk_entry_new();
7255 gtk_entry_set_width_chars(GTK_ENTRY(t
->search_entry
), 30);
7256 g_signal_connect(G_OBJECT(t
->search_entry
), "activate",
7257 G_CALLBACK(activate_search_entry_cb
), t
);
7258 g_signal_connect(G_OBJECT(t
->search_entry
), "key-press-event",
7259 G_CALLBACK(entry_key_cb
), t
);
7260 g_signal_connect(G_OBJECT(t
->search_entry
), "focus-in-event",
7261 G_CALLBACK(entry_focus_cb
), t
);
7262 gtk_box_pack_start(GTK_BOX(b
), t
->search_entry
, FALSE
, FALSE
, 0);
7264 #if !GTK_CHECK_VERSION(3, 0, 0)
7265 t
->default_style
= gtk_rc_get_style(t
->uri_entry
);
7272 create_buffers(struct tab
*t
)
7274 GtkCellRenderer
*renderer
;
7277 view
= gtk_tree_view_new();
7279 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view
), FALSE
);
7281 renderer
= gtk_cell_renderer_text_new();
7282 gtk_tree_view_insert_column_with_attributes
7283 (GTK_TREE_VIEW(view
), -1, "Id", renderer
, "text", COL_ID
, (char *)NULL
);
7285 renderer
= gtk_cell_renderer_pixbuf_new();
7286 gtk_tree_view_insert_column_with_attributes
7287 (GTK_TREE_VIEW(view
), -1, "Favicon", renderer
, "pixbuf", COL_FAVICON
,
7290 renderer
= gtk_cell_renderer_text_new();
7291 gtk_tree_view_insert_column_with_attributes
7292 (GTK_TREE_VIEW(view
), -1, "Title", renderer
, "text", COL_TITLE
,
7295 gtk_tree_view_set_model
7296 (GTK_TREE_VIEW(view
), GTK_TREE_MODEL(buffers_store
));
7302 row_activated_cb(GtkTreeView
*view
, GtkTreePath
*path
,
7303 GtkTreeViewColumn
*col
, struct tab
*t
)
7308 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
7310 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store
), &iter
,
7313 (GTK_TREE_MODEL(buffers_store
), &iter
, COL_ID
, &id
, -1);
7314 set_current_tab(id
- 1);
7320 /* after tab reordering/creation/removal */
7326 TAILQ_FOREACH(t
, &tabs
, entry
)
7327 t
->tab_id
= gtk_notebook_page_num(notebook
, t
->vbox
);
7331 update_statusbar_tabs(struct tab
*t
)
7333 int tab_id
, max_tab_id
;
7339 tab_id
= gtk_notebook_get_current_page(notebook
);
7341 max_tab_id
= gtk_notebook_get_n_pages(notebook
);
7342 snprintf(s
, sizeof s
, "%d/%d", tab_id
+ 1, max_tab_id
);
7345 t
= get_current_tab();
7348 gtk_label_set_text(GTK_LABEL(t
->sbe
.tabs
), s
);
7351 /* after active tab change */
7353 recolor_compact_tabs(void)
7357 #if !GTK_CHECK_VERSION(3, 0, 0)
7358 GdkColor color_active
, color_inactive
;
7360 gdk_color_parse(XT_COLOR_CT_ACTIVE
, &color_active
);
7361 gdk_color_parse(XT_COLOR_CT_INACTIVE
, &color_inactive
);
7363 curid
= gtk_notebook_get_current_page(notebook
);
7365 TAILQ_FOREACH(t
, &tabs
, entry
) {
7366 #if GTK_CHECK_VERSION(3, 0, 0)
7367 if (t
->tab_id
== curid
)
7368 gtk_widget_set_name(t
->tab_elems
.label
, XT_CSS_ACTIVE
);
7370 gtk_widget_set_name(t
->tab_elems
.label
, "");
7372 if (t
->tab_id
== curid
)
7373 gtk_widget_modify_fg(t
->tab_elems
.label
,
7374 GTK_STATE_NORMAL
, &color_active
);
7376 gtk_widget_modify_fg(t
->tab_elems
.label
,
7377 GTK_STATE_NORMAL
, &color_inactive
);
7383 set_current_tab(int page_num
)
7385 buffercmd_abort(get_current_tab());
7386 gtk_notebook_set_current_page(notebook
, page_num
);
7387 recolor_compact_tabs();
7388 update_statusbar_tabs(NULL
);
7392 undo_close_tab_save(struct tab
*t
)
7396 struct undo
*u1
, *u2
;
7398 WebKitWebHistoryItem
*item
;
7400 if ((uri
= get_uri(t
)) == NULL
)
7403 u1
= g_malloc0(sizeof(struct undo
));
7404 u1
->uri
= g_strdup(uri
);
7406 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
7408 m
= webkit_web_back_forward_list_get_forward_length(t
->bfl
);
7409 n
= webkit_web_back_forward_list_get_back_length(t
->bfl
);
7412 /* forward history */
7413 items
= webkit_web_back_forward_list_get_forward_list_with_limit(t
->bfl
, m
);
7417 u1
->history
= g_list_prepend(u1
->history
,
7418 webkit_web_history_item_copy(item
));
7419 items
= g_list_next(items
);
7424 item
= webkit_web_back_forward_list_get_current_item(t
->bfl
);
7425 u1
->history
= g_list_prepend(u1
->history
,
7426 webkit_web_history_item_copy(item
));
7430 items
= webkit_web_back_forward_list_get_back_list_with_limit(t
->bfl
, n
);
7434 u1
->history
= g_list_prepend(u1
->history
,
7435 webkit_web_history_item_copy(item
));
7436 items
= g_list_next(items
);
7439 TAILQ_INSERT_HEAD(&undos
, u1
, entry
);
7441 if (undo_count
> XT_MAX_UNDO_CLOSE_TAB
) {
7442 u2
= TAILQ_LAST(&undos
, undo_tailq
);
7443 TAILQ_REMOVE(&undos
, u2
, entry
);
7445 g_list_free(u2
->history
);
7454 delete_tab(struct tab
*t
)
7458 DNPRINTF(XT_D_TAB
, "delete_tab: %p\n", t
);
7464 * no need to join thread here because it won't access t on completion
7467 TAILQ_REMOVE(&tabs
, t
, entry
);
7470 /* Halt all webkit activity. */
7471 abort_favicon_download(t
);
7472 webkit_web_view_stop_loading(t
->wv
);
7474 /* Save the tab, so we can undo the close. */
7475 undo_close_tab_save(t
);
7479 g_source_remove(t
->search_id
);
7483 g_free(t
->session_key
);
7486 bzero(&a
, sizeof a
);
7488 inspector_cmd(t
, &a
);
7490 if (browser_mode
== XT_BM_KIOSK
) {
7491 gtk_widget_destroy(t
->uri_entry
);
7492 gtk_widget_destroy(t
->stop
);
7493 gtk_widget_destroy(t
->js_toggle
);
7496 g_object_unref(t
->completion
);
7498 g_object_unref(t
->item
);
7500 gtk_widget_destroy(t
->tab_elems
.eventbox
);
7501 gtk_widget_destroy(t
->vbox
);
7503 g_free(t
->stylesheet
);
7509 if (TAILQ_EMPTY(&tabs
)) {
7510 if (browser_mode
== XT_BM_KIOSK
)
7511 create_new_tab(home
, NULL
, 1, -1);
7513 create_new_tab(NULL
, NULL
, 1, -1);
7516 /* recreate session */
7517 if (session_autosave
) {
7518 bzero(&a
, sizeof a
);
7520 save_tabs(NULL
, &a
);
7524 recolor_compact_tabs();
7528 update_statusbar_zoom(struct tab
*t
)
7531 char s
[16] = { '\0' };
7533 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7534 if ((zoom
<= 0.99 || zoom
>= 1.01))
7535 snprintf(s
, sizeof s
, "%d%%", (int)(zoom
* 100));
7536 gtk_label_set_text(GTK_LABEL(t
->sbe
.zoom
), s
);
7540 setzoom_webkit(struct tab
*t
, int adjust
)
7542 #define XT_ZOOMPERCENT 0.04
7547 show_oops(NULL
, "setzoom_webkit invalid parameters");
7551 g_object_get(G_OBJECT(t
->wv
), "zoom-level", &zoom
, (char *)NULL
);
7552 if (adjust
== XT_ZOOM_IN
)
7553 zoom
+= XT_ZOOMPERCENT
;
7554 else if (adjust
== XT_ZOOM_OUT
)
7555 zoom
-= XT_ZOOMPERCENT
;
7556 else if (adjust
> 0)
7557 zoom
= default_zoom_level
+ adjust
/ 100.0 - 1.0;
7559 show_oops(t
, "setzoom_webkit invalid zoom value");
7563 if (zoom
< XT_ZOOMPERCENT
)
7564 zoom
= XT_ZOOMPERCENT
;
7565 g_object_set(G_OBJECT(t
->wv
), "zoom-level", zoom
, (char *)NULL
);
7566 update_statusbar_zoom(t
);
7570 tab_clicked_cb(GtkWidget
*widget
, GdkEventButton
*event
, gpointer data
)
7572 struct tab
*t
= (struct tab
*) data
;
7574 DNPRINTF(XT_D_TAB
, "tab_clicked_cb: tab: %d\n", t
->tab_id
);
7576 switch (event
->button
) {
7578 set_current_tab(t
->tab_id
);
7589 append_tab(struct tab
*t
)
7594 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
7595 t
->tab_id
= gtk_notebook_append_page(notebook
, t
->vbox
, t
->tab_content
);
7603 sbe
= gtk_label_new(NULL
);
7604 gtk_widget_set_can_focus(GTK_WIDGET(sbe
), FALSE
);
7605 gtk_widget_modify_font(GTK_WIDGET(sbe
), statusbar_font
);
7610 add_sbe(GtkWidget
*box
, char flag
, int *used
, GtkWidget
*sbe
)
7612 if (box
== NULL
|| used
== NULL
|| sbe
== NULL
) {
7613 DPRINTF("%s: invalid parameters", __func__
);
7618 warnx("flag \"%c\" specified more than "
7619 "once in statusbar_elems\n", flag
);
7623 gtk_box_pack_start(GTK_BOX(box
), sbe
, FALSE
, FALSE
, 0);
7630 statusbar_create(struct tab
*t
)
7632 GtkWidget
*box
; /* container for statusbar elems */
7635 int sbe_P
= 0, sbe_B
= 0, sbe_Z
= 0, sbe_T
= 0,
7637 #if !GTK_CHECK_VERSION(3, 0, 0)
7642 DPRINTF("%s: invalid parameters", __func__
);
7646 #if GTK_CHECK_VERSION(3, 0, 0)
7647 t
->statusbar
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
7648 box
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
7649 gtk_widget_set_name(GTK_WIDGET(t
->statusbar
), "statusbar");
7651 t
->statusbar
= gtk_hbox_new(FALSE
, 0);
7652 box
= gtk_hbox_new(FALSE
, 0);
7654 t
->sbe
.ebox
= gtk_event_box_new();
7656 gtk_widget_set_can_focus(GTK_WIDGET(t
->statusbar
), FALSE
);
7657 gtk_widget_set_can_focus(GTK_WIDGET(t
->sbe
.ebox
), FALSE
);
7658 gtk_widget_set_can_focus(GTK_WIDGET(box
), FALSE
);
7660 gtk_box_set_spacing(GTK_BOX(box
), 10);
7661 gtk_box_pack_start(GTK_BOX(t
->statusbar
), t
->sbe
.ebox
, TRUE
, TRUE
, 0);
7662 gtk_container_add(GTK_CONTAINER(t
->sbe
.ebox
), box
);
7664 /* create these widgets only if specified in statusbar_elems */
7665 t
->sbe
.uri
= gtk_entry_new();
7666 gtk_widget_set_can_focus(GTK_WIDGET(t
->sbe
.uri
), FALSE
);
7667 gtk_widget_modify_font(GTK_WIDGET(t
->sbe
.uri
), statusbar_font
);
7668 #if !GTK_CHECK_VERSION(3, 0, 0)
7669 gtk_entry_set_inner_border(GTK_ENTRY(t
->sbe
.uri
), NULL
);
7670 gtk_entry_set_has_frame(GTK_ENTRY(t
->sbe
.uri
), FALSE
);
7672 t
->sbe
.position
= create_sbe();
7673 t
->sbe
.zoom
= create_sbe();
7674 t
->sbe
.buffercmd
= create_sbe();
7675 t
->sbe
.tabs
= create_sbe();
7676 t
->sbe
.proxy
= create_sbe();
7678 #if GTK_CHECK_VERSION(3, 0, 0)
7679 statusbar_modify_attr(t
, XT_CSS_NORMAL
);
7681 statusbar_modify_attr(t
, XT_COLOR_WHITE
, XT_COLOR_BLACK
);
7684 gtk_box_pack_start(GTK_BOX(box
), t
->sbe
.uri
, TRUE
, TRUE
, 0);
7687 * gtk widgets cannot be added to a box twice. The sbe_* variables
7690 for (p
= statusbar_elems
; *p
!= '\0'; p
++) {
7693 #if GTK_CHECK_VERSION(3, 0, 0)
7694 sep
= gtk_separator_new(GTK_ORIENTATION_VERTICAL
);
7696 sep
= gtk_vseparator_new();
7697 gdk_color_parse(XT_COLOR_SB_SEPARATOR
, &color
);
7698 gtk_widget_modify_bg(sep
, GTK_STATE_NORMAL
, &color
);
7700 gtk_box_pack_start(GTK_BOX(box
), sep
, FALSE
, FALSE
, 0);
7703 add_sbe(box
, *p
, &sbe_P
, t
->sbe
.position
);
7706 add_sbe(box
, *p
, &sbe_B
, t
->sbe
.buffercmd
);
7709 add_sbe(box
, *p
, &sbe_Z
, t
->sbe
.zoom
);
7712 add_sbe(box
, *p
, &sbe_T
, t
->sbe
.tabs
);
7715 if (add_sbe(box
, *p
, &sbe_p
, t
->sbe
.proxy
) == 0)
7718 GTK_ENTRY(t
->sbe
.proxy
), "proxy");
7721 warnx("illegal flag \"%c\" in statusbar_elems\n", *p
);
7726 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->statusbar
, FALSE
, FALSE
, 0);
7732 create_new_tab(char *title
, struct undo
*u
, int focus
, int position
)
7737 WebKitWebHistoryItem
*item
;
7740 #if !GTK_CHECK_VERSION(3, 0, 0)
7744 DNPRINTF(XT_D_TAB
, "create_new_tab: title %s focus %d\n", title
, focus
);
7746 if (tabless
&& !TAILQ_EMPTY(&tabs
)) {
7747 if (single_instance
) {
7749 "create_new_tab: new tab rejected\n");
7752 sv
[0] = start_argv
[0];
7754 sv
[2] = (char *)NULL
;
7755 if (!g_spawn_async(NULL
, sv
, NULL
, G_SPAWN_SEARCH_PATH
,
7756 NULL
, NULL
, NULL
, NULL
))
7757 show_oops(NULL
, "%s: could not spawn process",
7762 t
= g_malloc0(sizeof *t
);
7764 if (title
== NULL
) {
7765 title
= "(untitled)";
7769 #if GTK_CHECK_VERSION(3, 0, 0)
7770 t
->vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
7771 b
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
7772 gtk_widget_set_name(t
->vbox
, "vbox");
7774 t
->vbox
= gtk_vbox_new(FALSE
, 0);
7775 b
= gtk_hbox_new(FALSE
, 0);
7777 gtk_widget_set_can_focus(t
->vbox
, FALSE
);
7779 /* label + button for tab */
7781 gtk_widget_set_can_focus(t
->tab_content
, FALSE
);
7783 t
->user_agent_id
= 0;
7784 t
->http_accept_id
= 0;
7786 #if WEBKIT_CHECK_VERSION(1, 5, 0)
7790 #if GTK_CHECK_VERSION(2, 20, 0)
7791 t
->spinner
= gtk_spinner_new();
7793 t
->label
= gtk_label_new(title
);
7794 bb
= create_button("Close", GTK_STOCK_CLOSE
, 1);
7795 gtk_label_set_max_width_chars(GTK_LABEL(t
->label
), 20);
7796 gtk_label_set_ellipsize(GTK_LABEL(t
->label
), PANGO_ELLIPSIZE_END
);
7797 gtk_label_set_line_wrap(GTK_LABEL(t
->label
), FALSE
);
7798 gtk_widget_set_size_request(b
, 130, 0);
7801 * this is a total hack and most likely breaks with other styles but
7802 * is necessary so the text doesn't bounce around when the spinner is
7805 #if GTK_CHECK_VERSION(3, 0, 0)
7806 gtk_widget_set_size_request(t
->label
, 95, 0);
7808 gtk_widget_set_size_request(t
->label
, 100, 0);
7811 gtk_box_pack_start(GTK_BOX(b
), bb
, FALSE
, FALSE
, 0);
7812 gtk_box_pack_start(GTK_BOX(b
), t
->label
, FALSE
, FALSE
, 0);
7813 #if GTK_CHECK_VERSION(2, 20, 0)
7814 gtk_box_pack_end(GTK_BOX(b
), t
->spinner
, FALSE
, FALSE
, 0);
7818 if (browser_mode
== XT_BM_KIOSK
) {
7819 t
->toolbar
= create_kiosk_toolbar(t
);
7820 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->toolbar
, FALSE
, FALSE
,
7823 t
->toolbar
= create_toolbar(t
);
7824 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->toolbar
, FALSE
, FALSE
,
7832 t
->browser_win
= create_browser(t
);
7833 set_scrollbar_visibility(t
, show_scrollbars
);
7834 gtk_box_pack_start(GTK_BOX(t
->vbox
), t
->browser_win
, TRUE
, TRUE
, 0);
7836 /* oops message for user feedback */
7837 t
->oops
= gtk_entry_new();
7838 gtk_entry_set_inner_border(GTK_ENTRY(t
->oops
), NULL
);
7839 gtk_entry_set_has_frame(GTK_ENTRY(t
->oops
), FALSE
);
7840 gtk_widget_set_can_focus(GTK_WIDGET(t
->oops
), FALSE
);
7841 #if GTK_CHECK_VERSION(3, 0, 0)
7842 gtk_widget_set_name(t
->oops
, XT_CSS_RED
);
7844 gdk_color_parse(XT_COLOR_RED
, &color
);
7845 gtk_widget_modify_base(t
->oops
, GTK_STATE_NORMAL
, &color
);
7847 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->oops
, FALSE
, FALSE
, 0);
7848 gtk_widget_modify_font(GTK_WIDGET(t
->oops
), oops_font
);
7851 t
->cmd
= gtk_entry_new();
7852 g_signal_connect(G_OBJECT(t
->cmd
), "focus-in-event",
7853 G_CALLBACK(entry_focus_cb
), t
);
7854 gtk_entry_set_inner_border(GTK_ENTRY(t
->cmd
), NULL
);
7855 gtk_entry_set_has_frame(GTK_ENTRY(t
->cmd
), FALSE
);
7856 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->cmd
, FALSE
, FALSE
, 0);
7857 gtk_widget_modify_font(GTK_WIDGET(t
->cmd
), cmd_font
);
7860 statusbar_create(t
);
7863 t
->buffers
= create_buffers(t
);
7864 gtk_box_pack_end(GTK_BOX(t
->vbox
), t
->buffers
, FALSE
, FALSE
, 0);
7866 /* xtp meaning is normal by default */
7867 set_normal_tab_meaning(t
);
7869 /* set empty favicon */
7870 xt_icon_from_name(t
, "text-html");
7872 /* and show it all */
7873 gtk_widget_show_all(b
);
7874 gtk_widget_show_all(t
->vbox
);
7877 gtk_widget_hide(t
->backward
);
7878 gtk_widget_hide(t
->forward
);
7879 gtk_widget_hide(t
->stop
);
7880 gtk_widget_hide(t
->js_toggle
);
7882 if (!fancy_bar
|| (search_string
== NULL
|| strlen(search_string
) == 0))
7883 gtk_widget_hide(t
->search_entry
);
7885 /* compact tab bar */
7886 t
->tab_elems
.label
= gtk_label_new(title
);
7887 t
->tab_elems
.favicon
= gtk_image_new();
7889 t
->tab_elems
.eventbox
= gtk_event_box_new();
7890 gtk_widget_set_name(t
->tab_elems
.eventbox
, "compact_tab");
7891 #if GTK_CHECK_VERSION(3, 0, 0)
7892 t
->tab_elems
.box
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
7894 gtk_label_set_ellipsize(GTK_LABEL(t
->tab_elems
.label
),
7895 PANGO_ELLIPSIZE_END
);
7896 gtk_widget_override_font(t
->tab_elems
.label
, tabbar_font
);
7897 gtk_widget_set_halign(t
->tab_elems
.label
, GTK_ALIGN_START
);
7898 gtk_widget_set_valign(t
->tab_elems
.label
, GTK_ALIGN_START
);
7900 t
->tab_elems
.box
= gtk_hbox_new(FALSE
, 0);
7902 gtk_label_set_width_chars(GTK_LABEL(t
->tab_elems
.label
), 1);
7903 gtk_misc_set_alignment(GTK_MISC(t
->tab_elems
.label
), 0.0, 0.0);
7904 gtk_misc_set_padding(GTK_MISC(t
->tab_elems
.label
), 4.0, 4.0);
7905 gtk_widget_modify_font(GTK_WIDGET(t
->tab_elems
.label
), tabbar_font
);
7907 gdk_color_parse(XT_COLOR_CT_BACKGROUND
, &color
);
7908 gtk_widget_modify_bg(t
->tab_elems
.eventbox
, GTK_STATE_NORMAL
, &color
);
7909 gdk_color_parse(XT_COLOR_CT_INACTIVE
, &color
);
7910 gtk_widget_modify_fg(t
->tab_elems
.label
, GTK_STATE_NORMAL
, &color
);
7913 gtk_box_pack_start(GTK_BOX(t
->tab_elems
.box
), t
->tab_elems
.favicon
, FALSE
,
7915 gtk_box_pack_start(GTK_BOX(t
->tab_elems
.box
), t
->tab_elems
.label
, TRUE
,
7917 gtk_container_add(GTK_CONTAINER(t
->tab_elems
.eventbox
),
7920 gtk_box_pack_start(GTK_BOX(tab_bar_box
), t
->tab_elems
.eventbox
, TRUE
,
7922 gtk_widget_show_all(t
->tab_elems
.eventbox
);
7924 if (append_next
== 0 || gtk_notebook_get_n_pages(notebook
) == 0)
7927 id
= position
>= 0 ? position
:
7928 gtk_notebook_get_current_page(notebook
) + 1;
7929 if (id
> gtk_notebook_get_n_pages(notebook
))
7932 TAILQ_INSERT_TAIL(&tabs
, t
, entry
);
7933 gtk_notebook_insert_page(notebook
, t
->vbox
, b
, id
);
7934 gtk_box_reorder_child(GTK_BOX(tab_bar_box
),
7935 t
->tab_elems
.eventbox
, id
);
7940 #if GTK_CHECK_VERSION(2, 20, 0)
7941 /* turn spinner off if we are a new tab without uri */
7943 gtk_spinner_stop(GTK_SPINNER(t
->spinner
));
7944 gtk_widget_hide(t
->spinner
);
7947 /* make notebook tabs reorderable */
7948 gtk_notebook_set_tab_reorderable(notebook
, t
->vbox
, TRUE
);
7950 /* compact tabs clickable */
7951 g_signal_connect(G_OBJECT(t
->tab_elems
.eventbox
),
7952 "button_press_event", G_CALLBACK(tab_clicked_cb
), t
);
7954 g_object_connect(G_OBJECT(t
->cmd
),
7955 "signal::button-release-event", G_CALLBACK(cmd_keyrelease_cb
), t
,
7956 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb
), t
,
7957 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb
), t
,
7958 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb
), t
,
7959 "signal::activate", G_CALLBACK(cmd_activate_cb
), t
,
7960 "signal::populate-popup", G_CALLBACK(cmd_popup_cb
), t
,
7961 "signal::hide", G_CALLBACK(cmd_hide_cb
), t
,
7964 /* reuse wv_button_cb to hide oops */
7965 g_object_connect(G_OBJECT(t
->oops
),
7966 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
7969 g_signal_connect(t
->buffers
,
7970 "row-activated", G_CALLBACK(row_activated_cb
), t
);
7971 g_object_connect(G_OBJECT(t
->buffers
),
7972 "signal::key-press-event", G_CALLBACK(wv_keypress_cb
), t
, (char *)NULL
);
7974 g_object_connect(G_OBJECT(t
->wv
),
7975 "signal::key-press-event", G_CALLBACK(wv_keypress_cb
), t
,
7976 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb
), t
,
7977 "signal::download-requested", G_CALLBACK(webview_download_cb
), t
,
7978 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb
), t
,
7979 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
7980 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb
), t
,
7981 "signal::resource-request-starting", G_CALLBACK(webview_rrs_cb
), t
,
7982 "signal::create-web-view", G_CALLBACK(webview_cwv_cb
), t
,
7983 "signal::close-web-view", G_CALLBACK(webview_closewv_cb
), t
,
7984 "signal::event", G_CALLBACK(webview_event_cb
), t
,
7985 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb
), t
,
7986 "signal::button_press_event", G_CALLBACK(wv_button_cb
), t
,
7987 "signal::button_release_event", G_CALLBACK(wv_release_button_cb
), t
,
7988 "signal::populate-popup", G_CALLBACK(wv_popup_cb
), t
,
7990 g_signal_connect(t
->wv
,
7991 "notify::load-status", G_CALLBACK(notify_load_status_cb
), t
);
7992 g_signal_connect(t
->wv
,
7993 "notify::title", G_CALLBACK(notify_title_cb
), t
);
7994 t
->progress_handle
= g_signal_connect(t
->wv
,
7995 "notify::progress", G_CALLBACK(webview_progress_changed_cb
), t
);
7997 /* hijack the unused keys as if we were the browser */
7998 //g_object_connect(G_OBJECT(t->toolbar),
7999 // "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8002 g_signal_connect(G_OBJECT(bb
), "button_press_event",
8003 G_CALLBACK(tab_close_cb
), t
);
8006 t
->bfl
= webkit_web_view_get_back_forward_list(t
->wv
);
8007 /* restore the tab's history */
8008 if (u
&& u
->history
) {
8012 webkit_web_back_forward_list_add_item(t
->bfl
, item
);
8013 items
= g_list_next(items
);
8016 item
= g_list_nth_data(u
->history
, u
->back
);
8018 webkit_web_view_go_to_back_forward_item(t
->wv
, item
);
8021 g_list_free(u
->history
);
8023 webkit_web_back_forward_list_clear(t
->bfl
);
8025 /* check and show url and statusbar */
8026 url_set_visibility();
8027 statusbar_set_visibility();
8030 set_current_tab(t
->tab_id
);
8031 DNPRINTF(XT_D_TAB
, "create_new_tab: going to tab: %d\n",
8035 gtk_entry_set_text(GTK_ENTRY(t
->uri_entry
), title
);
8039 gtk_widget_grab_focus(GTK_WIDGET(t
->uri_entry
));
8046 if (userstyle_global
)
8049 recolor_compact_tabs();
8050 setzoom_webkit(t
, XT_ZOOM_NORMAL
);
8055 notebook_switchpage_cb(GtkNotebook
*nb
, GtkWidget
*nbp
, guint pn
,
8061 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: tab: %d\n", pn
);
8063 if (gtk_notebook_get_current_page(notebook
) == -1)
8066 TAILQ_FOREACH(t
, &tabs
, entry
) {
8067 if (t
->tab_id
== pn
) {
8068 DNPRINTF(XT_D_TAB
, "notebook_switchpage_cb: going to "
8071 uri
= get_title(t
, TRUE
);
8072 gtk_window_set_title(GTK_WINDOW(main_window
), uri
);
8079 /* can't use focus_webview here */
8080 gtk_widget_grab_focus(GTK_WIDGET(t
->wv
));
8082 update_statusbar_tabs(t
);
8089 notebook_pagereordered_cb(GtkNotebook
*nb
, GtkWidget
*nbp
, guint pn
,
8092 struct tab
*t
= NULL
, *tt
;
8096 TAILQ_FOREACH(tt
, &tabs
, entry
)
8097 if (tt
->tab_id
== pn
) {
8103 DNPRINTF(XT_D_TAB
, "page_reordered_cb: tab: %d\n", t
->tab_id
);
8105 gtk_box_reorder_child(GTK_BOX(tab_bar_box
), t
->tab_elems
.eventbox
,
8108 update_statusbar_tabs(t
);
8112 menuitem_response(struct tab
*t
)
8114 gtk_notebook_set_current_page(notebook
, t
->tab_id
);
8118 destroy_menu(GtkMenuShell
*m
, void *notused
)
8120 gtk_widget_destroy(GTK_WIDGET(m
));
8121 return (XT_CB_PASSTHROUGH
);
8125 arrow_cb(GtkWidget
*w
, GdkEventButton
*event
, gpointer user_data
)
8127 GtkWidget
*menu
= NULL
, *menu_items
;
8128 GdkEventButton
*bevent
;
8129 struct tab
**stabs
= NULL
;
8133 if (event
->type
== GDK_BUTTON_PRESS
) {
8134 bevent
= (GdkEventButton
*) event
;
8135 menu
= gtk_menu_new();
8137 num_tabs
= sort_tabs_by_page_num(&stabs
);
8138 for (i
= 0; i
< num_tabs
; ++i
) {
8139 if (stabs
[i
] == NULL
)
8141 if ((uri
= get_uri(stabs
[i
])) == NULL
)
8142 /* XXX make sure there is something to print */
8143 /* XXX add gui pages in here to look purdy */
8145 menu_items
= gtk_menu_item_new_with_label(uri
);
8146 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menu_items
);
8147 gtk_widget_show(menu_items
);
8149 g_signal_connect_swapped(menu_items
,
8150 "activate", G_CALLBACK(menuitem_response
),
8151 (gpointer
)stabs
[i
]);
8155 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
8156 bevent
->button
, bevent
->time
);
8158 g_object_connect(G_OBJECT(menu
),
8159 "signal::selection-done", G_CALLBACK(destroy_menu
), NULL
,
8162 return (TRUE
/* eat event */);
8165 return (FALSE
/* propagate */);
8169 icon_size_map(int iconsz
)
8171 if (iconsz
<= GTK_ICON_SIZE_INVALID
||
8172 iconsz
> GTK_ICON_SIZE_DIALOG
)
8173 return (GTK_ICON_SIZE_SMALL_TOOLBAR
);
8179 create_button(char *name
, char *stockid
, int size
)
8181 GtkWidget
*button
, *image
;
8183 #if !GTK_CHECK_VERSION(3, 0, 0)
8187 #if !GTK_CHECK_VERSION(3, 0, 0)
8188 newstyle
= g_strdup_printf(
8189 "style \"%s-style\"\n"
8191 " GtkWidget::focus-padding = 0\n"
8192 " GtkWidget::focus-line-width = 0\n"
8196 "widget \"*.%s\" style \"%s-style\"", name
, name
, name
);
8197 gtk_rc_parse_string(newstyle
);
8200 button
= gtk_button_new();
8201 gtk_widget_set_can_focus(button
, FALSE
);
8202 gtk_button_set_focus_on_click(GTK_BUTTON(button
), FALSE
);
8203 gtk_icon_size
= icon_size_map(size
? size
: icon_size
);
8205 image
= gtk_image_new_from_stock(stockid
, gtk_icon_size
);
8206 gtk_container_set_border_width(GTK_CONTAINER(button
), 1);
8207 gtk_container_add(GTK_CONTAINER(button
), GTK_WIDGET(image
));
8208 gtk_widget_set_name(button
, name
);
8209 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
8215 button_set_stockid(GtkWidget
*button
, char *stockid
)
8219 image
= gtk_image_new_from_stock(stockid
, icon_size_map(icon_size
));
8220 gtk_button_set_image(GTK_BUTTON(button
), image
);
8229 char file
[PATH_MAX
];
8231 #if !GTK_CHECK_VERSION(3, 0, 0)
8235 #if GTK_CHECK_VERSION(3, 0, 0)
8236 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
8238 vbox
= gtk_vbox_new(FALSE
, 0);
8240 gtk_box_set_spacing(GTK_BOX(vbox
), 0);
8241 gtk_widget_set_can_focus(vbox
, FALSE
);
8242 notebook
= GTK_NOTEBOOK(gtk_notebook_new());
8243 #if !GTK_CHECK_VERSION(3, 0, 0)
8244 /* XXX seems to be needed with gtk+2 */
8245 g_object_set(G_OBJECT(notebook
), "tab-border", 0, NULL
);
8247 gtk_notebook_set_scrollable(notebook
, TRUE
);
8248 gtk_notebook_set_show_border(notebook
, FALSE
);
8249 gtk_widget_set_can_focus(GTK_WIDGET(notebook
), FALSE
);
8251 abtn
= gtk_button_new();
8252 gtk_widget_set_can_focus(abtn
, FALSE
);
8253 arrow
= gtk_arrow_new(GTK_ARROW_DOWN
, GTK_SHADOW_NONE
);
8254 gtk_widget_set_name(abtn
, "Arrow");
8255 gtk_container_add(GTK_CONTAINER(abtn
), arrow
);
8256 gtk_widget_set_size_request(abtn
, -1, 20);
8258 #if GTK_CHECK_VERSION(2, 20, 0)
8259 gtk_notebook_set_action_widget(notebook
, abtn
, GTK_PACK_END
);
8261 /* compact tab bar */
8262 tab_bar
= gtk_event_box_new();
8263 #if GTK_CHECK_VERSION(3, 0, 0)
8264 gtk_widget_set_name(tab_bar
, "tab_bar");
8265 tab_bar_box
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
8267 gdk_color_parse(XT_COLOR_CT_SEPARATOR
, &color
);
8268 gtk_widget_modify_bg(tab_bar
, GTK_STATE_NORMAL
, &color
);
8269 tab_bar_box
= gtk_hbox_new(TRUE
, 0);
8271 gtk_container_add(GTK_CONTAINER(tab_bar
), tab_bar_box
);
8272 gtk_box_set_homogeneous(GTK_BOX(tab_bar_box
), TRUE
);
8273 gtk_box_set_spacing(GTK_BOX(tab_bar_box
), 2);
8275 gtk_box_pack_start(GTK_BOX(vbox
), tab_bar
, FALSE
, FALSE
, 0);
8276 gtk_box_pack_start(GTK_BOX(vbox
), GTK_WIDGET(notebook
), TRUE
, TRUE
, 0);
8278 g_object_connect(G_OBJECT(notebook
),
8279 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb
), NULL
,
8281 g_object_connect(G_OBJECT(notebook
),
8282 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb
),
8283 NULL
, (char *)NULL
);
8284 g_signal_connect(G_OBJECT(abtn
), "button_press_event",
8285 G_CALLBACK(arrow_cb
), NULL
);
8287 main_window
= create_window("xombrero");
8288 gtk_container_add(GTK_CONTAINER(main_window
), vbox
);
8289 g_signal_connect(G_OBJECT(main_window
), "delete_event",
8290 G_CALLBACK(gtk_main_quit
), NULL
);
8291 #if GTK_CHECK_VERSION(3, 0, 0)
8292 gtk_window_set_has_resize_grip(GTK_WINDOW(main_window
), FALSE
);
8296 for (i
= 0; i
< LENGTH(icons
); i
++) {
8297 snprintf(file
, sizeof file
, "%s" PS
"%s", resource_dir
, icons
[i
]);
8298 pb
= gdk_pixbuf_new_from_file(file
, NULL
);
8299 l
= g_list_append(l
, pb
);
8301 gtk_window_set_default_icon_list(l
);
8303 gtk_widget_show_all(abtn
);
8304 gtk_widget_show_all(main_window
);
8305 notebook_tab_set_visibility();
8308 #ifndef XT_SOCKET_DISABLE
8310 send_cmd_to_socket(char *cmd
)
8313 struct sockaddr_un sa
;
8315 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
8316 warnx("%s: socket", __func__
);
8320 sa
.sun_family
= AF_UNIX
;
8321 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s" PS
"%s",
8322 work_dir
, XT_SOCKET_FILE
);
8325 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8326 warnx("%s: connect", __func__
);
8330 if (send(s
, cmd
, strlen(cmd
) + 1, 0) == -1) {
8331 warnx("%s: send", __func__
);
8342 socket_watcher(GIOChannel
*source
, GIOCondition condition
, gpointer data
)
8345 char str
[XT_MAX_URL_LENGTH
];
8346 socklen_t t
= sizeof(struct sockaddr_un
);
8347 struct sockaddr_un sa
;
8352 gint fd
= g_io_channel_unix_get_fd(source
);
8354 if ((s
= accept(fd
, (struct sockaddr
*)&sa
, &t
)) == -1) {
8359 if (getpeereid(s
, &uid
, &gid
) == -1) {
8363 if (uid
!= getuid() || gid
!= getgid()) {
8364 warnx("unauthorized user");
8370 warnx("not a valid user");
8374 n
= recv(s
, str
, sizeof(str
), 0);
8378 tt
= get_current_tab();
8379 cmd_execute(tt
, str
);
8387 struct sockaddr_un sa
;
8389 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
8390 warn("is_running: socket");
8394 sa
.sun_family
= AF_UNIX
;
8395 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s" PS
"%s",
8396 work_dir
, XT_SOCKET_FILE
);
8399 /* connect to see if there is a listener */
8400 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1)
8401 rv
= 0; /* not running */
8403 rv
= 1; /* already running */
8414 struct sockaddr_un sa
;
8416 if ((s
= socket(AF_UNIX
, SOCK_STREAM
, 0)) == -1) {
8417 warn("build_socket: socket");
8421 sa
.sun_family
= AF_UNIX
;
8422 snprintf(sa
.sun_path
, sizeof(sa
.sun_path
), "%s" PS
"%s",
8423 work_dir
, XT_SOCKET_FILE
);
8426 /* connect to see if there is a listener */
8427 if (connect(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8428 /* no listener so we will */
8429 unlink(sa
.sun_path
);
8431 if (bind(s
, (struct sockaddr
*)&sa
, len
) == -1) {
8432 warn("build_socket: bind");
8436 if (listen(s
, 1) == -1) {
8437 warn("build_socket: listen");
8455 if (stat(dir
, &sb
)) {
8456 #if defined __MINGW32__
8457 if (mkdir(dir
) == -1)
8459 if (mkdir(dir
, S_IRWXU
) == -1)
8461 err(1, "mkdir %s", dir
);
8463 err(1, "stat %s", dir
);
8465 if (S_ISDIR(sb
.st_mode
) == 0)
8466 errx(1, "%s not a dir", dir
);
8467 #if !defined __MINGW32__
8468 if (((sb
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
))) != S_IRWXU
) {
8469 warnx("fixing invalid permissions on %s", dir
);
8470 if (chmod(dir
, S_IRWXU
) == -1)
8471 err(1, "chmod %s", dir
);
8480 "%s [-nSTVt][-f file][-s session] url ...\n", __progname
);
8484 GStaticRecMutex my_gdk_mtx
= G_STATIC_REC_MUTEX_INIT
;
8485 volatile int mtx_depth
;
8489 * The linux flash plugin violates the gdk locking mechanism.
8490 * Work around the issue by using a recursive mutex with some match applied
8491 * to see if we hit a buggy condition.
8493 * The following code is painful so just don't read it. It really doesn't
8494 * make much sense but seems to work.
8503 g_static_rec_mutex_lock(&my_gdk_mtx
);
8504 if (my_gdk_mtx
.depth
<= 0) {
8508 g_static_rec_mutex_lock(&my_gdk_mtx
);
8510 } else if (my_gdk_mtx
.depth
!= 1) {
8515 g_static_rec_mutex_unlock(&my_gdk_mtx
);
8516 } while (my_gdk_mtx
.depth
> 1);
8522 if (mtx_complain
== 0) {
8523 DNPRINTF(XT_D_MTX
, "buggy mutex implementation detected(%s), "
8524 "work around implemented", s
);
8536 if (my_gdk_mtx
.depth
<= 0) {
8541 } else if (my_gdk_mtx
.depth
!= 1) {
8545 g_static_rec_mutex_unlock_full(&my_gdk_mtx
);
8548 g_static_rec_mutex_unlock(&my_gdk_mtx
);
8552 if (mtx_complain
== 0) {
8553 DNPRINTF(XT_D_MTX
, "buggy mutex implementation detected(%s), "
8554 "work around implemented", s
);
8559 #if GTK_CHECK_VERSION(3, 0, 0)
8563 GtkCssProvider
*provider
;
8564 GdkDisplay
*display
;
8567 char path
[PATH_MAX
];
8568 #if defined __MINGW32__
8569 GtkCssProvider
*windows_hacks
;
8572 provider
= gtk_css_provider_new();
8573 display
= gdk_display_get_default();
8574 screen
= gdk_display_get_default_screen(display
);
8575 snprintf(path
, sizeof path
, "%s" PS
"%s", resource_dir
, XT_CSS_FILE
);
8576 file
= g_file_new_for_path(path
);
8577 gtk_css_provider_load_from_file(provider
, file
, NULL
);
8578 gtk_style_context_add_provider_for_screen(screen
,
8579 GTK_STYLE_PROVIDER(provider
),
8580 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
8581 #if defined __MINGW32__
8582 windows_hacks
= gtk_css_provider_new();
8583 gtk_css_provider_load_from_data(windows_hacks
,
8585 " border-width: 0px;\n"
8587 gtk_style_context_add_provider_for_screen(screen
,
8588 GTK_STYLE_PROVIDER(windows_hacks
),
8589 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
8591 g_object_unref(file
);
8598 startpage_add("<b>Welcome to xombrero %s!</b><p>", version
);
8599 startpage_add("Details at "
8600 "<a href=https://opensource.conformal.com/wiki/xombrero>xombrero "
8601 "wiki page</a><p>");
8605 main(int argc
, char **argv
)
8608 int c
, optn
= 0, opte
= 0, focus
= 1;
8609 char conf
[PATH_MAX
] = { '\0' };
8610 char file
[PATH_MAX
];
8611 char sodversion
[32];
8612 char *env_proxy
= NULL
;
8617 start_argv
= (char * const *)argv
;
8624 #if !defined __MINGW32__
8625 /* http://web.archiveorange.com/archive/v/UsPjxkX5PsaXBIoOjqxf */
8628 /* http://developer.gnome.org/gdk/stable/gdk-Threads.html */
8629 g_thread_init(NULL
);
8630 gdk_threads_set_lock_functions(mtx_lock
, mtx_unlock
);
8632 gdk_threads_enter();
8634 /* http://www.gnupg.org/documentation/manuals/gcrypt/Multi_002dThreading.html */
8635 gcry_control (GCRYCTL_SET_THREAD_CBS
, &gcry_threads_pthread
);
8637 gtk_init(&argc
, &argv
);
8639 gnutls_global_init();
8641 strlcpy(named_session
, XT_SAVED_TABS_FILE
, sizeof named_session
);
8644 RB_INIT(&downloads
);
8651 TAILQ_INIT(&sessions
);
8654 TAILQ_INIT(&aliases
);
8664 TAILQ_INIT(&force_https
);
8667 #ifndef XT_RESOURCE_LIMITS_DISABLE
8671 GIOChannel
*channel
;
8673 /* fiddle with ulimits */
8674 if (getrlimit(RLIMIT_NOFILE
, &rlp
) == -1)
8677 /* just use them all */
8678 rlp
.rlim_cur
= rlp
.rlim_max
;
8679 if (setrlimit(RLIMIT_NOFILE
, &rlp
) == -1)
8681 if (getrlimit(RLIMIT_NOFILE
, &rlp
) == -1)
8683 else if (rlp
.rlim_cur
< 1024)
8684 startpage_add("%s requires at least 1024 "
8685 "(2048 recommended) file " "descriptors, "
8686 "currently it has up to %d available",
8687 __progname
, rlp
.rlim_cur
);
8691 while ((c
= getopt(argc
, argv
, "STVf:s:tne")) != -1) {
8700 #ifdef XOMBRERO_BUILDSTR
8701 errx(0 , "Version: %s Build: %s",
8702 version
, XOMBRERO_BUILDSTR
);
8704 errx(0 , "Version: %s", version
);
8708 strlcpy(conf
, optarg
, sizeof(conf
));
8711 strlcpy(named_session
, optarg
, sizeof(named_session
));
8732 pwd
= getpwuid(getuid());
8734 errx(1, "invalid user %d", getuid());
8736 /* set download dir */
8737 if (strlen(download_dir
) == 0)
8738 strlcpy(download_dir
, pwd
->pw_dir
, sizeof download_dir
);
8740 /* compile buffer command regexes */
8743 /* set default dynamic string settings */
8744 home
= g_strdup(XT_DS_HOME
);
8745 search_string
= g_strdup(XT_DS_SEARCH_STRING
);
8746 strlcpy(runtime_settings
, "runtime", sizeof runtime_settings
);
8747 cmd_font_name
= g_strdup(XT_DS_CMD_FONT_NAME
);
8748 oops_font_name
= g_strdup(XT_DS_OOPS_FONT_NAME
);
8749 statusbar_font_name
= g_strdup(XT_DS_STATUSBAR_FONT_NAME
);
8750 tabbar_font_name
= g_strdup(XT_DS_TABBAR_FONT_NAME
);
8751 statusbar_elems
= g_strdup("BP");
8752 spell_check_languages
= g_strdup(XT_DS_SPELL_CHECK_LANGUAGES
);
8753 encoding
= g_strdup(XT_DS_ENCODING
);
8754 spell_check_languages
= g_strdup(XT_DS_SPELL_CHECK_LANGUAGES
);
8755 path
= g_strdup_printf("%s" PS
"style.css", resource_dir
);
8756 userstyle
= g_filename_to_uri(path
, NULL
, NULL
);
8758 stylesheet
= g_strdup(userstyle
);
8760 /* set statically allocated (struct special) settings */
8761 if (strlen(default_script
) == 0)
8762 expand_tilde(default_script
, sizeof default_script
,
8763 XT_DS_DEFAULT_SCRIPT
);
8764 if (strlen(ssl_ca_file
) == 0)
8765 expand_tilde(ssl_ca_file
, sizeof ssl_ca_file
,
8769 session
= webkit_get_default_session();
8771 /* read config file */
8772 if (strlen(conf
) == 0)
8773 snprintf(conf
, sizeof conf
, "%s" PS
".%s",
8774 pwd
->pw_dir
, XT_CONF_FILE
);
8775 config_parse(conf
, 0);
8777 /* read preloaded HSTS list */
8778 if (preload_strict_transport
) {
8779 snprintf(conf
, sizeof conf
, "%s" PS
"%s",
8780 resource_dir
, XT_HSTS_PRELOAD_FILE
);
8781 config_parse(conf
, 0);
8784 /* check whether to read in a crapton of additional http headers */
8785 if (anonymize_headers
) {
8786 snprintf(conf
, sizeof conf
, "%s" PS
"%s",
8787 resource_dir
, XT_USER_AGENT_FILE
);
8788 config_parse(conf
, 0);
8789 snprintf(conf
, sizeof conf
, "%s" PS
"%s",
8790 resource_dir
, XT_HTTP_ACCEPT_FILE
);
8791 config_parse(conf
, 0);
8795 cmd_font
= pango_font_description_from_string(cmd_font_name
);
8796 oops_font
= pango_font_description_from_string(oops_font_name
);
8797 statusbar_font
= pango_font_description_from_string(statusbar_font_name
);
8798 tabbar_font
= pango_font_description_from_string(tabbar_font_name
);
8800 /* working directory */
8801 if (strlen(work_dir
) == 0)
8802 snprintf(work_dir
, sizeof work_dir
, "%s" PS
"%s",
8803 pwd
->pw_dir
, XT_DIR
);
8806 /* icon cache dir */
8807 snprintf(cache_dir
, sizeof cache_dir
, "%s" PS
"%s", work_dir
, XT_CACHE_DIR
);
8811 snprintf(certs_dir
, sizeof certs_dir
, "%s" PS
"%s", work_dir
, XT_CERT_DIR
);
8814 /* cert changes dir */
8815 snprintf(certs_cache_dir
, sizeof certs_cache_dir
, "%s" PS
"%s",
8816 work_dir
, XT_CERT_CACHE_DIR
);
8817 xxx_dir(certs_cache_dir
);
8820 snprintf(sessions_dir
, sizeof sessions_dir
, "%s" PS
"%s",
8821 work_dir
, XT_SESSIONS_DIR
);
8822 xxx_dir(sessions_dir
);
8825 snprintf(js_dir
, sizeof js_dir
, "%s" PS
"%s", work_dir
, XT_JS_DIR
);
8829 snprintf(temp_dir
, sizeof temp_dir
, "%s" PS
"%s", work_dir
, XT_TEMP_DIR
);
8832 /* runtime settings that can override config file */
8833 if (runtime_settings
[0] != '\0')
8834 config_parse(runtime_settings
, 1);
8837 if (!strcmp(download_dir
, pwd
->pw_dir
))
8838 strlcat(download_dir
, PS
"downloads", sizeof download_dir
);
8839 xxx_dir(download_dir
);
8841 /* first start file */
8842 snprintf(file
, sizeof file
, "%s" PS
"%s", work_dir
, XT_SOD_FILE
);
8843 if (stat(file
, &sb
)) {
8844 warnx("start of day file doesn't exist, creating it");
8845 if ((f
= fopen(file
, "w")) == NULL
)
8846 err(1, "startofday");
8847 if (fputs(version
, f
) == EOF
)
8854 if ((f
= fopen(file
, "r+")) == NULL
)
8855 err(1, "startofday");
8856 if (fgets(sodversion
, sizeof sodversion
, f
) == NULL
)
8858 sodversion
[strcspn(sodversion
, "\n")] = '\0';
8859 if (strcmp(version
, sodversion
)) {
8860 if ((f
= freopen(file
, "w", f
)) == NULL
)
8861 err(1, "startofday");
8862 if (fputs(version
, f
) == EOF
)
8865 /* upgrade, say something smart */
8871 /* favorites file */
8872 snprintf(file
, sizeof file
, "%s" PS
"%s", work_dir
, XT_FAVS_FILE
);
8873 if (stat(file
, &sb
)) {
8874 warnx("favorites file doesn't exist, creating it");
8875 if ((f
= fopen(file
, "w")) == NULL
)
8876 err(1, "favorites");
8880 /* quickmarks file */
8881 snprintf(file
, sizeof file
, "%s" PS
"%s", work_dir
, XT_QMARKS_FILE
);
8882 if (stat(file
, &sb
)) {
8883 warnx("quickmarks file doesn't exist, creating it");
8884 if ((f
= fopen(file
, "w")) == NULL
)
8885 err(1, "quickmarks");
8889 /* search history */
8890 if (history_autosave
) {
8891 snprintf(search_file
, sizeof search_file
, "%s" PS
"%s",
8892 work_dir
, XT_SEARCH_FILE
);
8893 if (stat(search_file
, &sb
)) {
8894 warnx("search history file doesn't exist, creating it");
8895 if ((f
= fopen(search_file
, "w")) == NULL
)
8896 err(1, "search_history");
8899 history_read(&shl
, search_file
, &search_history_count
);
8902 /* command history */
8903 if (history_autosave
) {
8904 snprintf(command_file
, sizeof command_file
, "%s" PS
"%s",
8905 work_dir
, XT_COMMAND_FILE
);
8906 if (stat(command_file
, &sb
)) {
8907 warnx("command history file doesn't exist, creating it");
8908 if ((f
= fopen(command_file
, "w")) == NULL
)
8909 err(1, "command_history");
8912 history_read(&chl
, command_file
, &cmd_history_count
);
8918 /* guess_search regex */
8919 if (url_regex
== NULL
)
8920 url_regex
= g_strdup(XT_URL_REGEX
);
8922 if (regcomp(&url_re
, url_regex
, REG_EXTENDED
| REG_NOSUB
))
8923 startpage_add("invalid url regex %s", url_regex
);
8926 env_proxy
= getenv("http_proxy");
8928 setup_proxy(env_proxy
);
8930 env_proxy
= getenv("HTTP_PROXY");
8932 setup_proxy(env_proxy
);
8934 setup_proxy(http_proxy
);
8937 /* the user can optionally have the proxy disabled at startup */
8938 if ((http_proxy_starts_enabled
== 0) && (http_proxy
!= NULL
)) {
8939 http_proxy_save
= g_strdup(http_proxy
);
8943 #ifndef XT_SOCKET_DISABLE
8945 send_cmd_to_socket(argv
[0]);
8949 opte
= opte
; /* shut mingw up */
8951 /* set some connection parameters */
8952 g_object_set(session
, "max-conns", max_connections
, (char *)NULL
);
8953 g_object_set(session
, "max-conns-per-host", max_host_connections
,
8955 g_object_set(session
, SOUP_SESSION_SSL_STRICT
, ssl_strict_certs
,
8958 g_signal_connect(session
, "request-queued", G_CALLBACK(session_rq_cb
),
8961 #ifndef XT_SOCKET_DISABLE
8962 /* see if there is already a xombrero running */
8963 if (single_instance
&& is_running()) {
8965 warnx("already running");
8970 cmd
= g_strdup_printf("%s %s", "tabnew", argv
[0]);
8971 send_cmd_to_socket(cmd
);
8981 optn
= optn
; /* shut mingw up */
8986 if (enable_strict_transport
)
8987 strict_transport_init();
8989 /* uri completion */
8990 completion_model
= gtk_list_store_new(1, G_TYPE_STRING
);
8993 buffers_store
= gtk_list_store_new
8994 (NUM_COLS
, G_TYPE_UINT
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
);
9000 notebook_tab_set_visibility();
9002 if (save_global_history
)
9003 restore_global_history();
9005 /* restore session list */
9006 restore_sessions_list();
9008 if (!strcmp(named_session
, XT_SAVED_TABS_FILE
))
9009 restore_saved_tabs();
9011 a
.s
= named_session
;
9012 a
.i
= XT_SES_DONOTHING
;
9013 open_tabs(NULL
, &a
);
9016 /* see if we have an exception */
9017 if (!TAILQ_EMPTY(&spl
)) {
9018 create_new_tab("about:startpage", NULL
, focus
, -1);
9023 create_new_tab(argv
[0], NULL
, focus
, -1);
9030 if (TAILQ_EMPTY(&tabs
))
9031 create_new_tab(home
, NULL
, 1, -1);
9032 #ifndef XT_SOCKET_DISABLE
9034 if ((s
= build_socket()) != -1) {
9035 channel
= g_io_channel_unix_new(s
);
9036 g_io_add_watch(channel
, G_IO_IN
, socket_watcher
, NULL
);
9040 #if GTK_CHECK_VERSION(3, 0, 0)
9048 gdk_threads_leave();
9049 g_static_rec_mutex_unlock_full(&my_gdk_mtx
); /* just in case */
9052 gnutls_global_deinit();