damn git never does what it says it does
[xxxterm.git] / xxxterm.c
blob3da97be7975272fd1a24642e64ce3bbd90f672ce
1 /*
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 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>
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 #include "xxxterm.h"
24 char *version = XXXTERM_VERSION;
26 /* hooked functions */
27 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
28 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
29 SoupCookie *);
31 #ifdef XT_DEBUG
32 u_int32_t swm_debug = 0
33 | XT_D_MOVE
34 | XT_D_KEY
35 | XT_D_TAB
36 | XT_D_URL
37 | XT_D_CMD
38 | XT_D_NAV
39 | XT_D_DOWNLOAD
40 | XT_D_CONFIG
41 | XT_D_JS
42 | XT_D_FAVORITE
43 | XT_D_PRINTING
44 | XT_D_COOKIE
45 | XT_D_KEYBINDING
46 | XT_D_CLIP
47 | XT_D_BUFFERCMD
48 | XT_D_INSPECTOR
50 #endif
52 #ifdef USE_THREADS
53 GCRY_THREAD_OPTION_PTHREAD_IMPL;
54 #endif
56 char *icons[] = {
57 "xxxtermicon16.png",
58 "xxxtermicon32.png",
59 "xxxtermicon48.png",
60 "xxxtermicon64.png",
61 "xxxtermicon128.png"
64 struct session {
65 TAILQ_ENTRY(session) entry;
66 const gchar *name;
68 TAILQ_HEAD(session_list, session);
70 struct domain {
71 RB_ENTRY(domain) entry;
72 gchar *d;
73 int handy; /* app use */
75 RB_HEAD(domain_list, domain);
77 struct undo {
78 TAILQ_ENTRY(undo) entry;
79 gchar *uri;
80 GList *history;
81 int back; /* Keeps track of how many back
82 * history items there are. */
84 TAILQ_HEAD(undo_tailq, undo);
86 struct sp {
87 char *line;
88 TAILQ_ENTRY(sp) entry;
90 TAILQ_HEAD(sp_list, sp);
92 struct command_entry {
93 char *line;
94 TAILQ_ENTRY(command_entry) entry;
96 TAILQ_HEAD(command_list, command_entry);
98 /* defines */
99 #define XT_DIR (".xxxterm")
100 #define XT_CACHE_DIR ("cache")
101 #define XT_CERT_DIR ("certs/")
102 #define XT_SESSIONS_DIR ("sessions/")
103 #define XT_CONF_FILE ("xxxterm.conf")
104 #define XT_QMARKS_FILE ("quickmarks")
105 #define XT_SAVED_TABS_FILE ("main_session")
106 #define XT_RESTART_TABS_FILE ("restart_tabs")
107 #define XT_SOCKET_FILE ("socket")
108 #define XT_HISTORY_FILE ("history")
109 #define XT_REJECT_FILE ("rejected.txt")
110 #define XT_COOKIE_FILE ("cookies.txt")
111 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
112 #define XT_SEARCH_FILE ("search_history")
113 #define XT_COMMAND_FILE ("command_history")
114 #define XT_DLMAN_REFRESH "10"
115 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
116 #define XT_MAX_UNDO_CLOSE_TAB (32)
117 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
118 #define XT_PRINT_EXTRA_MARGIN 10
119 #define XT_URL_REGEX ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+\\.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
120 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
122 /* colors */
123 #define XT_COLOR_RED "#cc0000"
124 #define XT_COLOR_YELLOW "#ffff66"
125 #define XT_COLOR_BLUE "lightblue"
126 #define XT_COLOR_GREEN "#99ff66"
127 #define XT_COLOR_WHITE "white"
128 #define XT_COLOR_BLACK "black"
130 #define XT_COLOR_CT_BACKGROUND "#000000"
131 #define XT_COLOR_CT_INACTIVE "#dddddd"
132 #define XT_COLOR_CT_ACTIVE "#bbbb00"
133 #define XT_COLOR_CT_SEPARATOR "#555555"
135 #define XT_COLOR_SB_SEPARATOR "#555555"
137 #define XT_PROTO_DELIM "://"
139 /* actions */
140 #define XT_MOVE_INVALID (0)
141 #define XT_MOVE_DOWN (1)
142 #define XT_MOVE_UP (2)
143 #define XT_MOVE_BOTTOM (3)
144 #define XT_MOVE_TOP (4)
145 #define XT_MOVE_PAGEDOWN (5)
146 #define XT_MOVE_PAGEUP (6)
147 #define XT_MOVE_HALFDOWN (7)
148 #define XT_MOVE_HALFUP (8)
149 #define XT_MOVE_LEFT (9)
150 #define XT_MOVE_FARLEFT (10)
151 #define XT_MOVE_RIGHT (11)
152 #define XT_MOVE_FARRIGHT (12)
153 #define XT_MOVE_PERCENT (13)
155 #define XT_QMARK_SET (0)
156 #define XT_QMARK_OPEN (1)
157 #define XT_QMARK_TAB (2)
159 #define XT_MARK_SET (0)
160 #define XT_MARK_GOTO (1)
162 #define XT_TAB_LAST (-4)
163 #define XT_TAB_FIRST (-3)
164 #define XT_TAB_PREV (-2)
165 #define XT_TAB_NEXT (-1)
166 #define XT_TAB_INVALID (0)
167 #define XT_TAB_NEW (1)
168 #define XT_TAB_DELETE (2)
169 #define XT_TAB_DELQUIT (3)
170 #define XT_TAB_OPEN (4)
171 #define XT_TAB_UNDO_CLOSE (5)
172 #define XT_TAB_SHOW (6)
173 #define XT_TAB_HIDE (7)
174 #define XT_TAB_NEXTSTYLE (8)
176 #define XT_NAV_INVALID (0)
177 #define XT_NAV_BACK (1)
178 #define XT_NAV_FORWARD (2)
179 #define XT_NAV_RELOAD (3)
181 #define XT_FOCUS_INVALID (0)
182 #define XT_FOCUS_URI (1)
183 #define XT_FOCUS_SEARCH (2)
185 #define XT_SEARCH_INVALID (0)
186 #define XT_SEARCH_NEXT (1)
187 #define XT_SEARCH_PREV (2)
189 #define XT_PASTE_CURRENT_TAB (0)
190 #define XT_PASTE_NEW_TAB (1)
192 #define XT_ZOOM_IN (-1)
193 #define XT_ZOOM_OUT (-2)
194 #define XT_ZOOM_NORMAL (100)
196 #define XT_URL_SHOW (1)
197 #define XT_URL_HIDE (2)
199 #define XT_WL_TOGGLE (1<<0)
200 #define XT_WL_ENABLE (1<<1)
201 #define XT_WL_DISABLE (1<<2)
202 #define XT_WL_FQDN (1<<3) /* default */
203 #define XT_WL_TOPLEVEL (1<<4)
204 #define XT_WL_PERSISTENT (1<<5)
205 #define XT_WL_SESSION (1<<6)
206 #define XT_WL_RELOAD (1<<7)
208 #define XT_SHOW (1<<7)
209 #define XT_DELETE (1<<8)
210 #define XT_SAVE (1<<9)
211 #define XT_OPEN (1<<10)
213 #define XT_CMD_OPEN (0)
214 #define XT_CMD_OPEN_CURRENT (1)
215 #define XT_CMD_TABNEW (2)
216 #define XT_CMD_TABNEW_CURRENT (3)
218 #define XT_STATUS_NOTHING (0)
219 #define XT_STATUS_LINK (1)
220 #define XT_STATUS_URI (2)
221 #define XT_STATUS_LOADING (3)
223 #define XT_SES_DONOTHING (0)
224 #define XT_SES_CLOSETABS (1)
226 #define XT_BM_NORMAL (0)
227 #define XT_BM_WHITELIST (1)
228 #define XT_BM_KIOSK (2)
230 #define XT_PREFIX (1<<0)
231 #define XT_USERARG (1<<1)
232 #define XT_URLARG (1<<2)
233 #define XT_INTARG (1<<3)
234 #define XT_SESSARG (1<<4)
235 #define XT_SETARG (1<<5)
237 #define XT_HINT_NEWTAB (1<<0)
239 #define XT_MODE_INSERT (0)
240 #define XT_MODE_COMMAND (1)
242 #define XT_TABS_NORMAL 0
243 #define XT_TABS_COMPACT 1
245 #define XT_BUFCMD_SZ (8)
247 #define XT_EJS_SHOW (1<<0)
249 /* mime types */
250 struct mime_type {
251 char *mt_type;
252 char *mt_action;
253 int mt_default;
254 int mt_download;
255 TAILQ_ENTRY(mime_type) entry;
257 TAILQ_HEAD(mime_type_list, mime_type);
259 /* uri aliases */
260 struct alias {
261 char *a_name;
262 char *a_uri;
263 TAILQ_ENTRY(alias) entry;
265 TAILQ_HEAD(alias_list, alias);
267 /* settings that require restart */
268 int tabless = 0; /* allow only 1 tab */
269 int enable_socket = 0;
270 int single_instance = 0; /* only allow one xxxterm to run */
271 int fancy_bar = 1; /* fancy toolbar */
272 int browser_mode = XT_BM_NORMAL;
273 int enable_localstorage = 1;
274 char *statusbar_elems = NULL;
276 /* runtime settings */
277 int show_tabs = 1; /* show tabs on notebook */
278 int tab_style = XT_TABS_NORMAL; /* tab bar style */
279 int show_url = 1; /* show url toolbar on notebook */
280 int show_statusbar = 0; /* vimperator style status bar */
281 int ctrl_click_focus = 0; /* ctrl click gets focus */
282 int cookies_enabled = 1; /* enable cookies */
283 int read_only_cookies = 0; /* enable to not write cookies */
284 int enable_scripts = 1;
285 int enable_plugins = 1;
286 gfloat default_zoom_level = 1.0;
287 char default_script[PATH_MAX];
288 int window_height = 768;
289 int window_width = 1024;
290 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
291 int refresh_interval = 10; /* download refresh interval */
292 int enable_plugin_whitelist = 0;
293 int enable_cookie_whitelist = 0;
294 int enable_js_whitelist = 0;
295 int session_timeout = 3600; /* cookie session timeout */
296 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
297 char *ssl_ca_file = NULL;
298 char *resource_dir = NULL;
299 gboolean ssl_strict_certs = FALSE;
300 int append_next = 1; /* append tab after current tab */
301 char *home = NULL;
302 char *search_string = NULL;
303 char *http_proxy = NULL;
304 char download_dir[PATH_MAX];
305 char runtime_settings[PATH_MAX]; /* override of settings */
306 int allow_volatile_cookies = 0;
307 int save_global_history = 0; /* save global history to disk */
308 char *user_agent = NULL;
309 int save_rejected_cookies = 0;
310 int session_autosave = 0;
311 int guess_search = 0;
312 int dns_prefetch = FALSE;
313 gint max_connections = 25;
314 gint max_host_connections = 5;
315 gint enable_spell_checking = 0;
316 char *spell_check_languages = NULL;
317 int xterm_workaround = 0;
318 char *url_regex = NULL;
319 int history_autosave = 0;
320 char search_file[PATH_MAX];
321 char command_file[PATH_MAX];
322 char *encoding = NULL;
323 int autofocus_onload = 0;
325 char *cmd_font_name = NULL;
326 char *oops_font_name = NULL;
327 char *statusbar_font_name = NULL;
328 char *tabbar_font_name = NULL;
329 PangoFontDescription *cmd_font;
330 PangoFontDescription *oops_font;
331 PangoFontDescription *statusbar_font;
332 PangoFontDescription *tabbar_font;
333 char *qmarks[XT_NOMARKS];
335 int btn_down; /* M1 down in any wv */
336 regex_t url_re; /* guess_search regex */
338 struct settings;
339 struct key_binding;
340 int set_browser_mode(struct settings *, char *);
341 int set_cookie_policy(struct settings *, char *);
342 int set_download_dir(struct settings *, char *);
343 int set_default_script(struct settings *, char *);
344 int set_runtime_dir(struct settings *, char *);
345 int set_tab_style(struct settings *, char *);
346 int set_work_dir(struct settings *, char *);
347 int add_alias(struct settings *, char *);
348 int add_mime_type(struct settings *, char *);
349 int add_cookie_wl(struct settings *, char *);
350 int add_js_wl(struct settings *, char *);
351 int add_pl_wl(struct settings *, char *);
352 int add_kb(struct settings *, char *);
353 void button_set_stockid(GtkWidget *, char *);
354 GtkWidget * create_button(char *, char *, int);
356 char *get_browser_mode(struct settings *);
357 char *get_cookie_policy(struct settings *);
358 char *get_download_dir(struct settings *);
359 char *get_default_script(struct settings *);
360 char *get_runtime_dir(struct settings *);
361 char *get_tab_style(struct settings *);
362 char *get_work_dir(struct settings *);
363 void startpage_add(const char *, ...);
365 void walk_alias(struct settings *, void (*)(struct settings *,
366 char *, void *), void *);
367 void walk_cookie_wl(struct settings *, void (*)(struct settings *,
368 char *, void *), void *);
369 void walk_js_wl(struct settings *, void (*)(struct settings *,
370 char *, void *), void *);
371 void walk_pl_wl(struct settings *, void (*)(struct settings *,
372 char *, void *), void *);
373 void walk_kb(struct settings *, void (*)(struct settings *, char *,
374 void *), void *);
375 void walk_mime_type(struct settings *, void (*)(struct settings *,
376 char *, void *), void *);
378 void recalc_tabs(void);
379 void recolor_compact_tabs(void);
380 void set_current_tab(int page_num);
381 gboolean update_statusbar_position(GtkAdjustment*, gpointer);
382 void marks_clear(struct tab *t);
384 int set_http_proxy(char *);
386 struct special {
387 int (*set)(struct settings *, char *);
388 char *(*get)(struct settings *);
389 void (*walk)(struct settings *,
390 void (*cb)(struct settings *, char *, void *),
391 void *);
394 struct special s_browser_mode = {
395 set_browser_mode,
396 get_browser_mode,
397 NULL
400 struct special s_cookie = {
401 set_cookie_policy,
402 get_cookie_policy,
403 NULL
406 struct special s_alias = {
407 add_alias,
408 NULL,
409 walk_alias
412 struct special s_mime = {
413 add_mime_type,
414 NULL,
415 walk_mime_type
418 struct special s_js = {
419 add_js_wl,
420 NULL,
421 walk_js_wl
424 struct special s_pl = {
425 add_pl_wl,
426 NULL,
427 walk_pl_wl
430 struct special s_kb = {
431 add_kb,
432 NULL,
433 walk_kb
436 struct special s_cookie_wl = {
437 add_cookie_wl,
438 NULL,
439 walk_cookie_wl
442 struct special s_default_script = {
443 set_default_script,
444 get_default_script,
445 NULL
448 struct special s_download_dir = {
449 set_download_dir,
450 get_download_dir,
451 NULL
454 struct special s_work_dir = {
455 set_work_dir,
456 get_work_dir,
457 NULL
460 struct special s_tab_style = {
461 set_tab_style,
462 get_tab_style,
463 NULL
466 struct settings {
467 char *name;
468 int type;
469 #define XT_S_INVALID (0)
470 #define XT_S_INT (1)
471 #define XT_S_STR (2)
472 #define XT_S_FLOAT (3)
473 uint32_t flags;
474 #define XT_SF_RESTART (1<<0)
475 #define XT_SF_RUNTIME (1<<1)
476 int *ival;
477 char **sval;
478 struct special *s;
479 gfloat *fval;
480 int (*activate)(char *);
481 } rs[] = {
482 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
483 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
484 { "autofocus_onload", XT_S_INT, 0, &autofocus_onload, NULL, NULL },
485 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
486 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
487 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
488 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
489 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
490 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
491 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
492 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
493 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
494 { "enable_plugin_whitelist", XT_S_INT, 0, &enable_plugin_whitelist, NULL, NULL },
495 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
496 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
497 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
498 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
499 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
500 { "encoding", XT_S_STR, 0, NULL, &encoding, NULL },
501 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
502 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
503 { "history_autosave", XT_S_INT, 0, &history_autosave, NULL, NULL },
504 { "home", XT_S_STR, 0, NULL, &home, NULL },
505 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL, NULL, set_http_proxy },
506 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
507 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
508 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
509 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
510 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
511 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
512 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
513 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
514 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
515 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
516 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
517 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
518 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
519 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
520 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
521 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
522 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
523 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
524 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
525 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
526 { "url_regex", XT_S_STR, 0, NULL, &url_regex, NULL },
527 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
528 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
529 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
530 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
531 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
533 /* font settings */
534 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
535 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
536 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
537 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
539 /* runtime settings */
540 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
541 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
542 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
543 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
544 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
545 { "pl_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_pl },
548 const gchar *get_uri(struct tab *);
549 const gchar *get_title(struct tab *, bool);
551 /* globals */
552 extern char *__progname;
553 char **start_argv;
554 struct passwd *pwd;
555 GtkWidget *main_window;
556 GtkNotebook *notebook;
557 GtkWidget *tab_bar;
558 GtkWidget *arrow, *abtn;
559 struct tab_list tabs;
560 struct history_list hl;
561 struct session_list sessions;
562 struct domain_list c_wl;
563 struct domain_list js_wl;
564 struct domain_list pl_wl;
565 struct undo_tailq undos;
566 struct keybinding_list kbl;
567 struct sp_list spl;
568 struct command_list chl;
569 struct command_list shl;
570 struct command_entry *history_at;
571 struct command_entry *search_at;
572 int undo_count;
573 int cmd_history_count = 0;
574 int search_history_count = 0;
575 char *global_search;
576 long long unsigned int blocked_cookies = 0;
577 char named_session[PATH_MAX];
578 GtkListStore *completion_model;
579 GtkListStore *buffers_store;
581 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
582 int next_download_id = 1;
584 void xxx_dir(char *);
585 int icon_size_map(int);
586 void completion_add(struct tab *);
587 void completion_add_uri(const gchar *);
589 void
590 history_delete(struct command_list *l, int *counter)
592 struct command_entry *c;
594 if (l == NULL || counter == NULL)
595 return;
597 c = TAILQ_LAST(l, command_list);
598 if (c == NULL)
599 return;
601 TAILQ_REMOVE(l, c, entry);
602 *counter -= 1;
603 g_free(c->line);
604 g_free(c);
607 void
608 history_add(struct command_list *list, char *file, char *l, int *counter)
610 struct command_entry *c;
611 FILE *f;
613 if (list == NULL || l == NULL || counter == NULL)
614 return;
616 /* don't add the same line */
617 c = TAILQ_FIRST(list);
618 if (c)
619 if (!strcmp(c->line + 1 /* skip space */, l))
620 return;
622 c = g_malloc0(sizeof *c);
623 c->line = g_strdup_printf(" %s", l);
625 *counter += 1;
626 TAILQ_INSERT_HEAD(list, c, entry);
628 if (*counter > 1000)
629 history_delete(list, counter);
631 if (history_autosave && file) {
632 f = fopen(file, "w");
633 if (f == NULL) {
634 show_oops(NULL, "couldn't write history %s", file);
635 return;
638 TAILQ_FOREACH_REVERSE(c, list, command_list, entry) {
639 c->line[0] = ' ';
640 fprintf(f, "%s\n", c->line);
643 fclose(f);
648 history_read(struct command_list *list, char *file, int *counter)
650 FILE *f;
651 char *s, line[65536];
653 if (list == NULL || file == NULL)
654 return (1);
656 f = fopen(file, "r");
657 if (f == NULL) {
658 startpage_add("couldn't open history file %s", file);
659 return (1);
662 for (;;) {
663 s = fgets(line, sizeof line, f);
664 if (s == NULL || feof(f) || ferror(f))
665 break;
666 if ((s = strchr(line, '\n')) == NULL) {
667 startpage_add("invalid history file %s", file);
668 fclose(f);
669 return (1);
671 *s = '\0';
673 history_add(list, NULL, line + 1, counter);
676 fclose(f);
678 return (0);
681 /* marks and quickmarks array storage.
682 * first a-z, then A-Z, then 0-9 */
683 char
684 indextomark(int i)
686 if (i < 0)
687 return (0);
689 if (i >= 0 && i <= 'z' - 'a')
690 return 'a' + i;
692 i -= 'z' - 'a' + 1;
693 if (i >= 0 && i <= 'Z' - 'A')
694 return 'A' + i;
696 i -= 'Z' - 'A' + 1;
697 if (i >= 10)
698 return (0);
700 return i + '0';
704 marktoindex(char m)
706 int ret = 0;
708 if (m >= 'a' && m <= 'z')
709 return ret + m - 'a';
711 ret += 'z' - 'a' + 1;
712 if (m >= 'A' && m <= 'Z')
713 return ret + m - 'A';
715 ret += 'Z' - 'A' + 1;
716 if (m >= '0' && m <= '9')
717 return ret + m - '0';
719 return (-1);
723 void
724 sigchild(int sig)
726 int saved_errno, status;
727 pid_t pid;
729 saved_errno = errno;
731 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
732 if (pid == -1) {
733 if (errno == EINTR)
734 continue;
735 if (errno != ECHILD) {
737 clog_warn("sigchild: waitpid:");
740 break;
743 if (WIFEXITED(status)) {
744 if (WEXITSTATUS(status) != 0) {
746 clog_warnx("sigchild: child exit status: %d",
747 WEXITSTATUS(status));
750 } else {
752 clog_warnx("sigchild: child is terminated abnormally");
757 errno = saved_errno;
761 is_g_object_setting(GObject *o, char *str)
763 guint n_props = 0, i;
764 GParamSpec **proplist;
766 if (! G_IS_OBJECT(o))
767 return (0);
769 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
770 &n_props);
772 for (i=0; i < n_props; i++) {
773 if (! strcmp(proplist[i]->name, str))
774 return (1);
776 return (0);
779 struct tab *
780 get_current_tab(void)
782 struct tab *t;
784 TAILQ_FOREACH(t, &tabs, entry) {
785 if (t->tab_id == gtk_notebook_get_current_page(notebook))
786 return (t);
789 warnx("%s: no current tab", __func__);
791 return (NULL);
794 void
795 set_status(struct tab *t, gchar *s, int status)
797 gchar *type = NULL;
799 if (s == NULL)
800 return;
802 switch (status) {
803 case XT_STATUS_LOADING:
804 type = g_strdup_printf("Loading: %s", s);
805 s = type;
806 break;
807 case XT_STATUS_LINK:
808 type = g_strdup_printf("Link: %s", s);
809 if (!t->status)
810 t->status = g_strdup(gtk_entry_get_text(
811 GTK_ENTRY(t->sbe.statusbar)));
812 s = type;
813 break;
814 case XT_STATUS_URI:
815 type = g_strdup_printf("%s", s);
816 if (!t->status) {
817 t->status = g_strdup(type);
819 s = type;
820 if (!t->status)
821 t->status = g_strdup(s);
822 break;
823 case XT_STATUS_NOTHING:
824 /* FALL THROUGH */
825 default:
826 break;
828 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
829 if (type)
830 g_free(type);
833 void
834 hide_cmd(struct tab *t)
836 history_at = NULL; /* just in case */
837 search_at = NULL; /* just in case */
838 gtk_widget_hide(t->cmd);
841 void
842 show_cmd(struct tab *t)
844 history_at = NULL;
845 search_at = NULL;
846 gtk_widget_hide(t->oops);
847 gtk_widget_show(t->cmd);
850 void
851 hide_buffers(struct tab *t)
853 gtk_widget_hide(t->buffers);
854 gtk_list_store_clear(buffers_store);
857 enum {
858 COL_ID = 0,
859 COL_TITLE,
860 NUM_COLS
864 sort_tabs_by_page_num(struct tab ***stabs)
866 int num_tabs = 0;
867 struct tab *t;
869 num_tabs = gtk_notebook_get_n_pages(notebook);
871 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
873 TAILQ_FOREACH(t, &tabs, entry)
874 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
876 return (num_tabs);
879 void
880 buffers_make_list(void)
882 int i, num_tabs;
883 const gchar *title = NULL;
884 GtkTreeIter iter;
885 struct tab **stabs = NULL;
887 num_tabs = sort_tabs_by_page_num(&stabs);
889 for (i = 0; i < num_tabs; i++)
890 if (stabs[i]) {
891 gtk_list_store_append(buffers_store, &iter);
892 title = get_title(stabs[i], FALSE);
893 gtk_list_store_set(buffers_store, &iter,
894 COL_ID, i + 1, /* Enumerate the tabs starting from 1
895 * rather than 0. */
896 COL_TITLE, title,
897 -1);
900 g_free(stabs);
903 void
904 show_buffers(struct tab *t)
906 buffers_make_list();
907 gtk_widget_show(t->buffers);
908 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
911 void
912 toggle_buffers(struct tab *t)
914 if (gtk_widget_get_visible(t->buffers))
915 hide_buffers(t);
916 else
917 show_buffers(t);
921 buffers(struct tab *t, struct karg *args)
923 show_buffers(t);
925 return (0);
928 void
929 hide_oops(struct tab *t)
931 gtk_widget_hide(t->oops);
934 void
935 show_oops(struct tab *at, const char *fmt, ...)
937 va_list ap;
938 char *msg = NULL;
939 struct tab *t = NULL;
941 if (fmt == NULL)
942 return;
944 if (at == NULL) {
945 if ((t = get_current_tab()) == NULL)
946 return;
947 } else
948 t = at;
950 va_start(ap, fmt);
951 if (vasprintf(&msg, fmt, ap) == -1)
952 errx(1, "show_oops failed");
953 va_end(ap);
955 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
956 gtk_widget_hide(t->cmd);
957 gtk_widget_show(t->oops);
959 if (msg)
960 free(msg);
963 char *
964 get_as_string(struct settings *s)
966 char *r = NULL;
968 if (s == NULL)
969 return (NULL);
971 if (s->s) {
972 if (s->s->get)
973 r = s->s->get(s);
974 else
975 warnx("get_as_string skip %s\n", s->name);
976 } else if (s->type == XT_S_INT)
977 r = g_strdup_printf("%d", *s->ival);
978 else if (s->type == XT_S_STR)
979 r = g_strdup(*s->sval);
980 else if (s->type == XT_S_FLOAT)
981 r = g_strdup_printf("%f", *s->fval);
982 else
983 r = g_strdup_printf("INVALID TYPE");
985 return (r);
988 void
989 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
991 int i;
992 char *s;
994 for (i = 0; i < LENGTH(rs); i++) {
995 if (rs[i].s && rs[i].s->walk)
996 rs[i].s->walk(&rs[i], cb, cb_args);
997 else {
998 s = get_as_string(&rs[i]);
999 cb(&rs[i], s, cb_args);
1000 g_free(s);
1006 set_browser_mode(struct settings *s, char *val)
1008 if (!strcmp(val, "whitelist")) {
1009 browser_mode = XT_BM_WHITELIST;
1010 allow_volatile_cookies = 0;
1011 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1012 cookies_enabled = 1;
1013 enable_cookie_whitelist = 1;
1014 enable_plugin_whitelist = 1;
1015 enable_plugins = 0;
1016 read_only_cookies = 0;
1017 save_rejected_cookies = 0;
1018 session_timeout = 3600;
1019 enable_scripts = 0;
1020 enable_js_whitelist = 1;
1021 enable_localstorage = 0;
1022 } else if (!strcmp(val, "normal")) {
1023 browser_mode = XT_BM_NORMAL;
1024 allow_volatile_cookies = 0;
1025 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1026 cookies_enabled = 1;
1027 enable_cookie_whitelist = 0;
1028 enable_plugin_whitelist = 0;
1029 enable_plugins = 1;
1030 read_only_cookies = 0;
1031 save_rejected_cookies = 0;
1032 session_timeout = 3600;
1033 enable_scripts = 1;
1034 enable_js_whitelist = 0;
1035 enable_localstorage = 1;
1036 } else if (!strcmp(val, "kiosk")) {
1037 browser_mode = XT_BM_KIOSK;
1038 allow_volatile_cookies = 0;
1039 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1040 cookies_enabled = 1;
1041 enable_cookie_whitelist = 0;
1042 enable_plugin_whitelist = 0;
1043 enable_plugins = 1;
1044 read_only_cookies = 0;
1045 save_rejected_cookies = 0;
1046 session_timeout = 3600;
1047 enable_scripts = 1;
1048 enable_js_whitelist = 0;
1049 enable_localstorage = 1;
1050 show_tabs = 0;
1051 tabless = 1;
1052 } else
1053 return (1);
1055 return (0);
1058 char *
1059 get_browser_mode(struct settings *s)
1061 char *r = NULL;
1063 if (browser_mode == XT_BM_WHITELIST)
1064 r = g_strdup("whitelist");
1065 else if (browser_mode == XT_BM_NORMAL)
1066 r = g_strdup("normal");
1067 else if (browser_mode == XT_BM_KIOSK)
1068 r = g_strdup("kiosk");
1069 else
1070 return (NULL);
1072 return (r);
1076 set_cookie_policy(struct settings *s, char *val)
1078 if (!strcmp(val, "no3rdparty"))
1079 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1080 else if (!strcmp(val, "accept"))
1081 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1082 else if (!strcmp(val, "reject"))
1083 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1084 else
1085 return (1);
1087 return (0);
1090 char *
1091 get_cookie_policy(struct settings *s)
1093 char *r = NULL;
1095 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1096 r = g_strdup("no3rdparty");
1097 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1098 r = g_strdup("accept");
1099 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1100 r = g_strdup("reject");
1101 else
1102 return (NULL);
1104 return (r);
1107 char *
1108 get_default_script(struct settings *s)
1110 if (default_script[0] == '\0')
1111 return (0);
1112 return (g_strdup(default_script));
1116 set_default_script(struct settings *s, char *val)
1118 if (val[0] == '~')
1119 snprintf(default_script, sizeof default_script, "%s/%s",
1120 pwd->pw_dir, &val[1]);
1121 else
1122 strlcpy(default_script, val, sizeof default_script);
1124 return (0);
1127 char *
1128 get_download_dir(struct settings *s)
1130 if (download_dir[0] == '\0')
1131 return (0);
1132 return (g_strdup(download_dir));
1136 set_download_dir(struct settings *s, char *val)
1138 if (val[0] == '~')
1139 snprintf(download_dir, sizeof download_dir, "%s/%s",
1140 pwd->pw_dir, &val[1]);
1141 else
1142 strlcpy(download_dir, val, sizeof download_dir);
1144 return (0);
1147 char work_dir[PATH_MAX];
1148 char certs_dir[PATH_MAX];
1149 char cache_dir[PATH_MAX];
1150 char sessions_dir[PATH_MAX];
1151 char cookie_file[PATH_MAX];
1152 SoupURI *proxy_uri = NULL;
1153 SoupSession *session;
1154 SoupCookieJar *s_cookiejar;
1155 SoupCookieJar *p_cookiejar;
1156 char rc_fname[PATH_MAX];
1158 struct mime_type_list mtl;
1159 struct alias_list aliases;
1161 /* protos */
1162 struct tab *create_new_tab(char *, struct undo *, int, int);
1163 void delete_tab(struct tab *);
1164 void setzoom_webkit(struct tab *, int);
1165 int run_script(struct tab *, char *);
1166 int download_rb_cmp(struct download *, struct download *);
1167 gboolean cmd_execute(struct tab *t, char *str);
1170 history_rb_cmp(struct history *h1, struct history *h2)
1172 return (strcmp(h1->uri, h2->uri));
1174 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1177 domain_rb_cmp(struct domain *d1, struct domain *d2)
1179 return (strcmp(d1->d, d2->d));
1181 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1183 char *
1184 get_work_dir(struct settings *s)
1186 if (work_dir[0] == '\0')
1187 return (0);
1188 return (g_strdup(work_dir));
1192 set_work_dir(struct settings *s, char *val)
1194 if (val[0] == '~')
1195 snprintf(work_dir, sizeof work_dir, "%s/%s",
1196 pwd->pw_dir, &val[1]);
1197 else
1198 strlcpy(work_dir, val, sizeof work_dir);
1200 return (0);
1203 char *
1204 get_tab_style(struct settings *s)
1206 if (tab_style == XT_TABS_NORMAL)
1207 return (g_strdup("normal"));
1208 else
1209 return (g_strdup("compact"));
1213 set_tab_style(struct settings *s, char *val)
1215 if (!strcmp(val, "normal"))
1216 tab_style = XT_TABS_NORMAL;
1217 else if (!strcmp(val, "compact"))
1218 tab_style = XT_TABS_COMPACT;
1219 else
1220 return (1);
1222 return (0);
1226 download_rb_cmp(struct download *e1, struct download *e2)
1228 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1230 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1232 struct valid_url_types {
1233 char *type;
1234 } vut[] = {
1235 { "http://" },
1236 { "https://" },
1237 { "ftp://" },
1238 { "file://" },
1239 { XT_XTP_STR },
1243 valid_url_type(char *url)
1245 int i;
1247 for (i = 0; i < LENGTH(vut); i++)
1248 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1249 return (0);
1251 return (1);
1254 void
1255 print_cookie(char *msg, SoupCookie *c)
1257 if (c == NULL)
1258 return;
1260 if (msg)
1261 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1262 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1263 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1264 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1265 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1266 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1267 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1268 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1269 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1270 DNPRINTF(XT_D_COOKIE, "====================================\n");
1273 void
1274 walk_alias(struct settings *s,
1275 void (*cb)(struct settings *, char *, void *), void *cb_args)
1277 struct alias *a;
1278 char *str;
1280 if (s == NULL || cb == NULL) {
1281 show_oops(NULL, "walk_alias invalid parameters");
1282 return;
1285 TAILQ_FOREACH(a, &aliases, entry) {
1286 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1287 cb(s, str, cb_args);
1288 g_free(str);
1292 char *
1293 match_alias(char *url_in)
1295 struct alias *a;
1296 char *arg;
1297 char *url_out = NULL, *search, *enc_arg;
1299 search = g_strdup(url_in);
1300 arg = search;
1301 if (strsep(&arg, " \t") == NULL) {
1302 show_oops(NULL, "match_alias: NULL URL");
1303 goto done;
1306 TAILQ_FOREACH(a, &aliases, entry) {
1307 if (!strcmp(search, a->a_name))
1308 break;
1311 if (a != NULL) {
1312 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1313 a->a_name);
1314 if (arg != NULL) {
1315 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1316 url_out = g_strdup_printf(a->a_uri, enc_arg);
1317 g_free(enc_arg);
1318 } else
1319 url_out = g_strdup_printf(a->a_uri, "");
1321 done:
1322 g_free(search);
1323 return (url_out);
1326 char *
1327 guess_url_type(char *url_in)
1329 struct stat sb;
1330 char *url_out = NULL, *enc_search = NULL;
1331 int i;
1333 /* substitute aliases */
1334 url_out = match_alias(url_in);
1335 if (url_out != NULL)
1336 return (url_out);
1338 /* see if we are an about page */
1339 if (!strncmp(url_in, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
1340 for (i = 0; i < about_list_size(); i++)
1341 if (!strcmp(&url_in[XT_URI_ABOUT_LEN],
1342 about_list[i].name)) {
1343 url_out = g_strdup(url_in);
1344 goto done;
1347 if (guess_search && url_regex &&
1348 !(g_str_has_prefix(url_in, "http://") ||
1349 g_str_has_prefix(url_in, "https://"))) {
1350 if (regexec(&url_re, url_in, 0, NULL, 0)) {
1351 /* invalid URI so search instead */
1352 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1353 url_out = g_strdup_printf(search_string, enc_search);
1354 g_free(enc_search);
1355 goto done;
1359 /* XXX not sure about this heuristic */
1360 if (stat(url_in, &sb) == 0)
1361 url_out = g_strdup_printf("file://%s", url_in);
1362 else
1363 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1364 done:
1365 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1367 return (url_out);
1370 void
1371 load_uri(struct tab *t, gchar *uri)
1373 struct karg args;
1374 gchar *newuri = NULL;
1375 int i;
1377 if (uri == NULL)
1378 return;
1380 /* Strip leading spaces. */
1381 while (*uri && isspace(*uri))
1382 uri++;
1384 if (strlen(uri) == 0) {
1385 blank(t, NULL);
1386 return;
1389 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1391 if (valid_url_type(uri)) {
1392 newuri = guess_url_type(uri);
1393 uri = newuri;
1396 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1397 for (i = 0; i < about_list_size(); i++)
1398 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1399 bzero(&args, sizeof args);
1400 about_list[i].func(t, &args);
1401 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1402 FALSE);
1403 goto done;
1405 show_oops(t, "invalid about page");
1406 goto done;
1409 set_status(t, (char *)uri, XT_STATUS_LOADING);
1410 marks_clear(t);
1411 webkit_web_view_load_uri(t->wv, uri);
1412 done:
1413 if (newuri)
1414 g_free(newuri);
1417 const gchar *
1418 get_uri(struct tab *t)
1420 const gchar *uri = NULL;
1422 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1423 return NULL;
1424 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1425 uri = webkit_web_view_get_uri(t->wv);
1426 } else {
1427 /* use tmp_uri to make sure it is g_freed */
1428 if (t->tmp_uri)
1429 g_free(t->tmp_uri);
1430 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1431 about_list[t->xtp_meaning].name);
1432 uri = t->tmp_uri;
1434 return uri;
1437 const gchar *
1438 get_title(struct tab *t, bool window)
1440 const gchar *set = NULL, *title = NULL;
1441 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1443 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1444 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1445 goto notitle;
1447 title = webkit_web_view_get_title(t->wv);
1448 if ((set = title ? title : get_uri(t)))
1449 return set;
1451 notitle:
1452 set = window ? XT_NAME : "(untitled)";
1454 return set;
1458 add_alias(struct settings *s, char *line)
1460 char *l, *alias;
1461 struct alias *a = NULL;
1463 if (s == NULL || line == NULL) {
1464 show_oops(NULL, "add_alias invalid parameters");
1465 return (1);
1468 l = line;
1469 a = g_malloc(sizeof(*a));
1471 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1472 show_oops(NULL, "add_alias: incomplete alias definition");
1473 goto bad;
1475 if (strlen(alias) == 0 || strlen(l) == 0) {
1476 show_oops(NULL, "add_alias: invalid alias definition");
1477 goto bad;
1480 a->a_name = g_strdup(alias);
1481 a->a_uri = g_strdup(l);
1483 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1485 TAILQ_INSERT_TAIL(&aliases, a, entry);
1487 return (0);
1488 bad:
1489 if (a)
1490 g_free(a);
1491 return (1);
1495 add_mime_type(struct settings *s, char *line)
1497 char *mime_type;
1498 char *l;
1499 struct mime_type *m = NULL;
1500 int downloadfirst = 0;
1502 /* XXX this could be smarter */
1504 if (line == NULL || strlen(line) == 0) {
1505 show_oops(NULL, "add_mime_type invalid parameters");
1506 return (1);
1509 l = line;
1510 if (*l == '@') {
1511 downloadfirst = 1;
1512 l++;
1514 m = g_malloc(sizeof(*m));
1516 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1517 show_oops(NULL, "add_mime_type: invalid mime_type");
1518 goto bad;
1520 if (mime_type[strlen(mime_type) - 1] == '*') {
1521 mime_type[strlen(mime_type) - 1] = '\0';
1522 m->mt_default = 1;
1523 } else
1524 m->mt_default = 0;
1526 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1527 show_oops(NULL, "add_mime_type: invalid mime_type");
1528 goto bad;
1531 m->mt_type = g_strdup(mime_type);
1532 m->mt_action = g_strdup(l);
1533 m->mt_download = downloadfirst;
1535 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1536 m->mt_type, m->mt_action, m->mt_default);
1538 TAILQ_INSERT_TAIL(&mtl, m, entry);
1540 return (0);
1541 bad:
1542 if (m)
1543 g_free(m);
1544 return (1);
1547 struct mime_type *
1548 find_mime_type(char *mime_type)
1550 struct mime_type *m, *def = NULL, *rv = NULL;
1552 TAILQ_FOREACH(m, &mtl, entry) {
1553 if (m->mt_default &&
1554 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1555 def = m;
1557 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1558 rv = m;
1559 break;
1563 if (rv == NULL)
1564 rv = def;
1566 return (rv);
1569 void
1570 walk_mime_type(struct settings *s,
1571 void (*cb)(struct settings *, char *, void *), void *cb_args)
1573 struct mime_type *m;
1574 char *str;
1576 if (s == NULL || cb == NULL) {
1577 show_oops(NULL, "walk_mime_type invalid parameters");
1578 return;
1581 TAILQ_FOREACH(m, &mtl, entry) {
1582 str = g_strdup_printf("%s%s --> %s",
1583 m->mt_type,
1584 m->mt_default ? "*" : "",
1585 m->mt_action);
1586 cb(s, str, cb_args);
1587 g_free(str);
1591 void
1592 wl_add(char *str, struct domain_list *wl, int handy)
1594 struct domain *d;
1595 int add_dot = 0;
1596 char *p;
1598 if (str == NULL || wl == NULL || strlen(str) < 2)
1599 return;
1601 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1603 /* treat *.moo.com the same as .moo.com */
1604 if (str[0] == '*' && str[1] == '.')
1605 str = &str[1];
1606 else if (str[0] == '.')
1607 str = &str[0];
1608 else
1609 add_dot = 1;
1611 /* slice off port number */
1612 p = g_strrstr(str, ":");
1613 if (p)
1614 *p = '\0';
1616 d = g_malloc(sizeof *d);
1617 if (add_dot)
1618 d->d = g_strdup_printf(".%s", str);
1619 else
1620 d->d = g_strdup(str);
1621 d->handy = handy;
1623 if (RB_INSERT(domain_list, wl, d))
1624 goto unwind;
1626 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1627 return;
1628 unwind:
1629 if (d) {
1630 if (d->d)
1631 g_free(d->d);
1632 g_free(d);
1637 add_cookie_wl(struct settings *s, char *entry)
1639 wl_add(entry, &c_wl, 1);
1640 return (0);
1643 void
1644 walk_cookie_wl(struct settings *s,
1645 void (*cb)(struct settings *, char *, void *), void *cb_args)
1647 struct domain *d;
1649 if (s == NULL || cb == NULL) {
1650 show_oops(NULL, "walk_cookie_wl invalid parameters");
1651 return;
1654 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1655 cb(s, d->d, cb_args);
1658 void
1659 walk_js_wl(struct settings *s,
1660 void (*cb)(struct settings *, char *, void *), void *cb_args)
1662 struct domain *d;
1664 if (s == NULL || cb == NULL) {
1665 show_oops(NULL, "walk_js_wl invalid parameters");
1666 return;
1669 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1670 cb(s, d->d, cb_args);
1674 add_js_wl(struct settings *s, char *entry)
1676 wl_add(entry, &js_wl, 1 /* persistent */);
1677 return (0);
1680 void
1681 walk_pl_wl(struct settings *s,
1682 void (*cb)(struct settings *, char *, void *), void *cb_args)
1684 struct domain *d;
1686 if (s == NULL || cb == NULL) {
1687 show_oops(NULL, "walk_pl_wl invalid parameters");
1688 return;
1691 RB_FOREACH_REVERSE(d, domain_list, &pl_wl)
1692 cb(s, d->d, cb_args);
1696 add_pl_wl(struct settings *s, char *entry)
1698 wl_add(entry, &pl_wl, 1 /* persistent */);
1699 return (0);
1702 struct domain *
1703 wl_find(const gchar *search, struct domain_list *wl)
1705 int i;
1706 struct domain *d = NULL, dfind;
1707 gchar *s = NULL;
1709 if (search == NULL || wl == NULL)
1710 return (NULL);
1711 if (strlen(search) < 2)
1712 return (NULL);
1714 if (search[0] != '.')
1715 s = g_strdup_printf(".%s", search);
1716 else
1717 s = g_strdup(search);
1719 for (i = strlen(s) - 1; i >= 0; i--) {
1720 if (s[i] == '.') {
1721 dfind.d = &s[i];
1722 d = RB_FIND(domain_list, wl, &dfind);
1723 if (d)
1724 goto done;
1728 done:
1729 if (s)
1730 g_free(s);
1732 return (d);
1735 struct domain *
1736 wl_find_uri(const gchar *s, struct domain_list *wl)
1738 int i;
1739 char *ss;
1740 struct domain *r;
1742 if (s == NULL || wl == NULL)
1743 return (NULL);
1745 if (!strncmp(s, "http://", strlen("http://")))
1746 s = &s[strlen("http://")];
1747 else if (!strncmp(s, "https://", strlen("https://")))
1748 s = &s[strlen("https://")];
1750 if (strlen(s) < 2)
1751 return (NULL);
1753 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1754 /* chop string at first slash */
1755 if (s[i] == '/' || s[i] == ':' || s[i] == '\0') {
1756 ss = g_strdup(s);
1757 ss[i] = '\0';
1758 r = wl_find(ss, wl);
1759 g_free(ss);
1760 return (r);
1763 return (NULL);
1767 settings_add(char *var, char *val)
1769 int i, rv, *p;
1770 gfloat *f;
1771 char **s;
1773 /* get settings */
1774 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1775 if (strcmp(var, rs[i].name))
1776 continue;
1778 if (rs[i].s) {
1779 if (rs[i].s->set(&rs[i], val))
1780 errx(1, "invalid value for %s: %s", var, val);
1781 rv = 1;
1782 break;
1783 } else
1784 switch (rs[i].type) {
1785 case XT_S_INT:
1786 p = rs[i].ival;
1787 *p = atoi(val);
1788 rv = 1;
1789 break;
1790 case XT_S_STR:
1791 s = rs[i].sval;
1792 if (s == NULL)
1793 errx(1, "invalid sval for %s",
1794 rs[i].name);
1795 if (*s)
1796 g_free(*s);
1797 *s = g_strdup(val);
1798 rv = 1;
1799 break;
1800 case XT_S_FLOAT:
1801 f = rs[i].fval;
1802 *f = atof(val);
1803 rv = 1;
1804 break;
1805 case XT_S_INVALID:
1806 default:
1807 errx(1, "invalid type for %s", var);
1809 break;
1811 return (rv);
1814 #define WS "\n= \t"
1815 void
1816 config_parse(char *filename, int runtime)
1818 FILE *config, *f;
1819 char *line, *cp, *var, *val;
1820 size_t len, lineno = 0;
1821 int handled;
1822 char file[PATH_MAX];
1823 struct stat sb;
1825 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1827 if (filename == NULL)
1828 return;
1830 if (runtime && runtime_settings[0] != '\0') {
1831 snprintf(file, sizeof file, "%s/%s",
1832 work_dir, runtime_settings);
1833 if (stat(file, &sb)) {
1834 warnx("runtime file doesn't exist, creating it");
1835 if ((f = fopen(file, "w")) == NULL)
1836 err(1, "runtime");
1837 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1838 fclose(f);
1840 } else
1841 strlcpy(file, filename, sizeof file);
1843 if ((config = fopen(file, "r")) == NULL) {
1844 warn("config_parse: cannot open %s", filename);
1845 return;
1848 for (;;) {
1849 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1850 if (feof(config) || ferror(config))
1851 break;
1853 cp = line;
1854 cp += (long)strspn(cp, WS);
1855 if (cp[0] == '\0') {
1856 /* empty line */
1857 free(line);
1858 continue;
1861 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1862 startpage_add("invalid configuration file entry: %s",
1863 line);
1864 else {
1865 cp += (long)strspn(cp, WS);
1867 if ((val = strsep(&cp, "\0")) == NULL)
1868 break;
1870 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",
1871 var, val);
1872 handled = settings_add(var, val);
1874 if (handled == 0)
1875 startpage_add("invalid configuration file entry"
1876 ": %s=%s", var, val);
1879 free(line);
1882 fclose(config);
1885 char *
1886 js_ref_to_string(JSContextRef context, JSValueRef ref)
1888 char *s = NULL;
1889 size_t l;
1890 JSStringRef jsref;
1892 jsref = JSValueToStringCopy(context, ref, NULL);
1893 if (jsref == NULL)
1894 return (NULL);
1896 l = JSStringGetMaximumUTF8CStringSize(jsref);
1897 s = g_malloc(l);
1898 if (s)
1899 JSStringGetUTF8CString(jsref, s, l);
1900 JSStringRelease(jsref);
1902 return (s);
1905 void
1906 disable_hints(struct tab *t)
1908 DNPRINTF(XT_D_JS, "%s\n", __func__);
1910 run_script(t, "hints.clearHints();");
1911 t->hints_on = 0;
1912 t->new_tab = 0;
1915 void
1916 enable_hints(struct tab *t)
1918 DNPRINTF(XT_D_JS, "%s\n", __func__);
1920 run_script(t, JS_HINTING);
1922 if (t->new_tab)
1923 run_script(t, "hints.createHints('', 'F');");
1924 else
1925 run_script(t, "hints.createHints('', 'f');");
1926 t->hints_on = 1;
1929 #define XT_JS_DONE ("done;")
1930 #define XT_JS_DONE_LEN (strlen(XT_JS_DONE))
1931 #define XT_JS_INSERT ("insert;")
1932 #define XT_JS_INSERT_LEN (strlen(XT_JS_INSERT))
1935 run_script(struct tab *t, char *s)
1937 JSGlobalContextRef ctx;
1938 WebKitWebFrame *frame;
1939 JSStringRef str;
1940 JSValueRef val, exception;
1941 char *es;
1943 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1944 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1946 /* a bit silly but it prevents some heartburn */
1947 if (t->script_init == 0) {
1948 t->script_init = 1;
1949 run_script(t, JS_HINTING);
1952 frame = webkit_web_view_get_main_frame(t->wv);
1953 ctx = webkit_web_frame_get_global_context(frame);
1955 str = JSStringCreateWithUTF8CString(s);
1956 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1957 NULL, 0, &exception);
1958 JSStringRelease(str);
1960 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1961 if (val == NULL) {
1962 es = js_ref_to_string(ctx, exception);
1963 if (es) {
1964 /* show_oops(t, "script exception: %s", es); */
1965 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1966 g_free(es);
1968 return (1);
1969 } else {
1970 es = js_ref_to_string(ctx, val);
1971 #if 0
1972 /* return values */
1973 if (!strncmp(es, XT_JS_DONE, XT_JS_DONE_LEN))
1974 ; /* do nothing */
1975 if (!strncmp(es, XT_JS_INSERT, XT_JS_INSERT_LEN))
1976 ; /* do nothing */
1977 #endif
1978 if (es) {
1979 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1980 g_free(es);
1984 return (0);
1988 hint(struct tab *t, struct karg *args)
1991 DNPRINTF(XT_D_JS, "hint: tab %d args %d\n", t->tab_id, args->i);
1993 if (t->hints_on == 0) {
1994 if (args->i == XT_HINT_NEWTAB)
1995 t->new_tab = 1;
1996 enable_hints(t);
1997 } else
1998 disable_hints(t);
2000 return (0);
2003 void
2004 apply_style(struct tab *t)
2006 g_object_set(G_OBJECT(t->settings),
2007 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2011 userstyle(struct tab *t, struct karg *args)
2013 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2015 if (t->styled) {
2016 t->styled = 0;
2017 g_object_set(G_OBJECT(t->settings),
2018 "user-stylesheet-uri", NULL, (char *)NULL);
2019 } else {
2020 t->styled = 1;
2021 apply_style(t);
2023 return (0);
2027 * Doesn't work fully, due to the following bug:
2028 * https://bugs.webkit.org/show_bug.cgi?id=51747
2031 restore_global_history(void)
2033 char file[PATH_MAX];
2034 FILE *f;
2035 struct history *h;
2036 gchar *uri;
2037 gchar *title;
2038 const char delim[3] = {'\\', '\\', '\0'};
2040 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2042 if ((f = fopen(file, "r")) == NULL) {
2043 warnx("%s: fopen", __func__);
2044 return (1);
2047 for (;;) {
2048 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2049 if (feof(f) || ferror(f))
2050 break;
2052 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2053 if (feof(f) || ferror(f)) {
2054 free(uri);
2055 warnx("%s: broken history file\n", __func__);
2056 return (1);
2059 if (uri && strlen(uri) && title && strlen(title)) {
2060 webkit_web_history_item_new_with_data(uri, title);
2061 h = g_malloc(sizeof(struct history));
2062 h->uri = g_strdup(uri);
2063 h->title = g_strdup(title);
2064 RB_INSERT(history_list, &hl, h);
2065 completion_add_uri(h->uri);
2066 } else {
2067 warnx("%s: failed to restore history\n", __func__);
2068 free(uri);
2069 free(title);
2070 return (1);
2073 free(uri);
2074 free(title);
2075 uri = NULL;
2076 title = NULL;
2079 return (0);
2083 save_global_history_to_disk(struct tab *t)
2085 char file[PATH_MAX];
2086 FILE *f;
2087 struct history *h;
2089 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2091 if ((f = fopen(file, "w")) == NULL) {
2092 show_oops(t, "%s: global history file: %s",
2093 __func__, strerror(errno));
2094 return (1);
2097 RB_FOREACH_REVERSE(h, history_list, &hl) {
2098 if (h->uri && h->title)
2099 fprintf(f, "%s\n%s\n", h->uri, h->title);
2102 fclose(f);
2104 return (0);
2108 quit(struct tab *t, struct karg *args)
2110 if (save_global_history)
2111 save_global_history_to_disk(t);
2113 gtk_main_quit();
2115 return (1);
2118 void
2119 restore_sessions_list(void)
2121 DIR *sdir = NULL;
2122 struct dirent *dp = NULL;
2123 struct session *s;
2125 sdir = opendir(sessions_dir);
2126 if (sdir) {
2127 while ((dp = readdir(sdir)) != NULL)
2128 if (dp->d_type == DT_REG) {
2129 s = g_malloc(sizeof(struct session));
2130 s->name = g_strdup(dp->d_name);
2131 TAILQ_INSERT_TAIL(&sessions, s, entry);
2133 closedir(sdir);
2138 open_tabs(struct tab *t, struct karg *a)
2140 char file[PATH_MAX];
2141 FILE *f = NULL;
2142 char *uri = NULL;
2143 int rv = 1;
2144 struct tab *ti, *tt;
2146 if (a == NULL)
2147 goto done;
2149 ti = TAILQ_LAST(&tabs, tab_list);
2151 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2152 if ((f = fopen(file, "r")) == NULL)
2153 goto done;
2155 for (;;) {
2156 if ((uri = fparseln(f, NULL, NULL, "\0\0\0", 0)) == NULL)
2157 if (feof(f) || ferror(f))
2158 break;
2160 /* retrieve session name */
2161 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2162 strlcpy(named_session,
2163 &uri[strlen(XT_SAVE_SESSION_ID)],
2164 sizeof named_session);
2165 continue;
2168 if (uri && strlen(uri))
2169 create_new_tab(uri, NULL, 1, -1);
2171 free(uri);
2172 uri = NULL;
2175 /* close open tabs */
2176 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2177 for (;;) {
2178 tt = TAILQ_FIRST(&tabs);
2179 if (tt == NULL)
2180 break;
2181 if (tt != ti) {
2182 delete_tab(tt);
2183 continue;
2185 delete_tab(tt);
2186 break;
2190 rv = 0;
2191 done:
2192 if (f)
2193 fclose(f);
2195 return (rv);
2199 restore_saved_tabs(void)
2201 char file[PATH_MAX];
2202 int unlink_file = 0;
2203 struct stat sb;
2204 struct karg a;
2205 int rv = 0;
2207 snprintf(file, sizeof file, "%s/%s",
2208 sessions_dir, XT_RESTART_TABS_FILE);
2209 if (stat(file, &sb) == -1)
2210 a.s = XT_SAVED_TABS_FILE;
2211 else {
2212 unlink_file = 1;
2213 a.s = XT_RESTART_TABS_FILE;
2216 a.i = XT_SES_DONOTHING;
2217 rv = open_tabs(NULL, &a);
2219 if (unlink_file)
2220 unlink(file);
2222 return (rv);
2226 save_tabs(struct tab *t, struct karg *a)
2228 char file[PATH_MAX];
2229 FILE *f;
2230 int num_tabs = 0, i;
2231 struct tab **stabs = NULL;
2233 /* tab may be null here */
2235 if (a == NULL)
2236 return (1);
2237 if (a->s == NULL)
2238 snprintf(file, sizeof file, "%s/%s",
2239 sessions_dir, named_session);
2240 else
2241 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2243 if ((f = fopen(file, "w")) == NULL) {
2244 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2245 return (1);
2248 /* save session name */
2249 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2251 /* Save tabs, in the order they are arranged in the notebook. */
2252 num_tabs = sort_tabs_by_page_num(&stabs);
2254 for (i = 0; i < num_tabs; i++)
2255 if (stabs[i]) {
2256 if (get_uri(stabs[i]) != NULL)
2257 fprintf(f, "%s\n", get_uri(stabs[i]));
2258 else if (gtk_entry_get_text(GTK_ENTRY(
2259 stabs[i]->uri_entry)))
2260 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2261 stabs[i]->uri_entry)));
2264 g_free(stabs);
2266 /* try and make sure this gets to disk NOW. XXX Backup first? */
2267 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2268 show_oops(t, "May not have managed to save session: %s",
2269 strerror(errno));
2272 fclose(f);
2274 return (0);
2278 save_tabs_and_quit(struct tab *t, struct karg *args)
2280 struct karg a;
2282 a.s = NULL;
2283 save_tabs(t, &a);
2284 quit(t, NULL);
2286 return (1);
2290 run_page_script(struct tab *t, struct karg *args)
2292 const gchar *uri;
2293 char *tmp, script[PATH_MAX];
2295 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2296 if (tmp[0] == '\0') {
2297 show_oops(t, "no script specified");
2298 return (1);
2301 if ((uri = get_uri(t)) == NULL) {
2302 show_oops(t, "tab is empty, not running script");
2303 return (1);
2306 if (tmp[0] == '~')
2307 snprintf(script, sizeof script, "%s/%s",
2308 pwd->pw_dir, &tmp[1]);
2309 else
2310 strlcpy(script, tmp, sizeof script);
2312 switch (fork()) {
2313 case -1:
2314 show_oops(t, "can't fork to run script");
2315 return (1);
2316 /* NOTREACHED */
2317 case 0:
2318 break;
2319 default:
2320 return (0);
2323 /* child */
2324 execlp(script, script, uri, (void *)NULL);
2326 _exit(0);
2328 /* NOTREACHED */
2330 return (0);
2334 yank_uri(struct tab *t, struct karg *args)
2336 const gchar *uri;
2337 GtkClipboard *clipboard;
2339 if ((uri = get_uri(t)) == NULL)
2340 return (1);
2342 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2343 gtk_clipboard_set_text(clipboard, uri, -1);
2345 return (0);
2349 paste_uri(struct tab *t, struct karg *args)
2351 GtkClipboard *clipboard;
2352 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2353 gint len;
2354 gchar *p = NULL, *uri;
2356 /* try primary clipboard first */
2357 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2358 p = gtk_clipboard_wait_for_text(clipboard);
2360 /* if it failed get whatever text is in cut_buffer0 */
2361 if (p == NULL && xterm_workaround)
2362 if (gdk_property_get(gdk_get_default_root_window(),
2363 atom,
2364 gdk_atom_intern("STRING", FALSE),
2366 1024 * 1024 /* picked out of my butt */,
2367 FALSE,
2368 NULL,
2369 NULL,
2370 &len,
2371 (guchar **)&p)) {
2372 /* yes sir, we need to NUL the string */
2373 p[len] = '\0';
2376 if (p) {
2377 uri = p;
2378 while (*uri && isspace(*uri))
2379 uri++;
2380 if (strlen(uri) == 0) {
2381 show_oops(t, "empty paste buffer");
2382 goto done;
2384 if (guess_search == 0 && valid_url_type(uri)) {
2385 /* we can be clever and paste this in search box */
2386 show_oops(t, "not a valid URL");
2387 goto done;
2390 if (args->i == XT_PASTE_CURRENT_TAB)
2391 load_uri(t, uri);
2392 else if (args->i == XT_PASTE_NEW_TAB)
2393 create_new_tab(uri, NULL, 1, -1);
2396 done:
2397 if (p)
2398 g_free(p);
2400 return (0);
2403 gchar *
2404 find_domain(const gchar *s, int toplevel)
2406 SoupURI *uri;
2407 gchar *ret, *p;
2409 if (s == NULL)
2410 return (NULL);
2412 uri = soup_uri_new(s);
2414 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2415 return (NULL);
2418 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2419 if ((p = strrchr(uri->host, '.')) != NULL) {
2420 while(--p >= uri->host && *p != '.');
2421 p++;
2422 } else
2423 p = uri->host;
2424 } else
2425 p = uri->host;
2427 ret = g_strdup_printf(".%s", p);
2429 soup_uri_free(uri);
2431 return (ret);
2435 toggle_cwl(struct tab *t, struct karg *args)
2437 struct domain *d;
2438 const gchar *uri;
2439 char *dom = NULL;
2440 int es;
2442 if (args == NULL)
2443 return (1);
2445 uri = get_uri(t);
2446 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2448 if (uri == NULL || dom == NULL ||
2449 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2450 show_oops(t, "Can't toggle domain in cookie white list");
2451 goto done;
2453 d = wl_find(dom, &c_wl);
2455 if (d == NULL)
2456 es = 0;
2457 else
2458 es = 1;
2460 if (args->i & XT_WL_TOGGLE)
2461 es = !es;
2462 else if ((args->i & XT_WL_ENABLE) && es != 1)
2463 es = 1;
2464 else if ((args->i & XT_WL_DISABLE) && es != 0)
2465 es = 0;
2467 if (es)
2468 /* enable cookies for domain */
2469 wl_add(dom, &c_wl, 0);
2470 else {
2471 /* disable cookies for domain */
2472 if (d)
2473 RB_REMOVE(domain_list, &c_wl, d);
2476 if (args->i & XT_WL_RELOAD)
2477 webkit_web_view_reload(t->wv);
2479 done:
2480 g_free(dom);
2481 return (0);
2485 toggle_js(struct tab *t, struct karg *args)
2487 int es;
2488 const gchar *uri;
2489 struct domain *d;
2490 char *dom = NULL;
2492 if (args == NULL)
2493 return (1);
2495 g_object_get(G_OBJECT(t->settings),
2496 "enable-scripts", &es, (char *)NULL);
2497 if (args->i & XT_WL_TOGGLE)
2498 es = !es;
2499 else if ((args->i & XT_WL_ENABLE) && es != 1)
2500 es = 1;
2501 else if ((args->i & XT_WL_DISABLE) && es != 0)
2502 es = 0;
2503 else
2504 return (1);
2506 uri = get_uri(t);
2507 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2509 if (uri == NULL || dom == NULL ||
2510 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2511 show_oops(t, "Can't toggle domain in JavaScript white list");
2512 goto done;
2515 if (es) {
2516 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2517 wl_add(dom, &js_wl, 0 /* session */);
2518 } else {
2519 d = wl_find(dom, &js_wl);
2520 if (d)
2521 RB_REMOVE(domain_list, &js_wl, d);
2522 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2524 g_object_set(G_OBJECT(t->settings),
2525 "enable-scripts", es, (char *)NULL);
2526 g_object_set(G_OBJECT(t->settings),
2527 "javascript-can-open-windows-automatically", es, (char *)NULL);
2528 webkit_web_view_set_settings(t->wv, t->settings);
2530 if (args->i & XT_WL_RELOAD)
2531 webkit_web_view_reload(t->wv);
2532 done:
2533 if (dom)
2534 g_free(dom);
2535 return (0);
2539 toggle_pl(struct tab *t, struct karg *args)
2541 int es;
2542 const gchar *uri;
2543 struct domain *d;
2544 char *dom = NULL;
2546 if (args == NULL)
2547 return (1);
2549 g_object_get(G_OBJECT(t->settings),
2550 "enable-plugins", &es, (char *)NULL);
2551 if (args->i & XT_WL_TOGGLE)
2552 es = !es;
2553 else if ((args->i & XT_WL_ENABLE) && es != 1)
2554 es = 1;
2555 else if ((args->i & XT_WL_DISABLE) && es != 0)
2556 es = 0;
2557 else
2558 return (1);
2560 uri = get_uri(t);
2561 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2563 if (uri == NULL || dom == NULL ||
2564 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2565 show_oops(t, "Can't toggle domain in plugins white list");
2566 goto done;
2569 if (es)
2570 wl_add(dom, &pl_wl, 0 /* session */);
2571 else {
2572 d = wl_find(dom, &pl_wl);
2573 if (d)
2574 RB_REMOVE(domain_list, &pl_wl, d);
2576 g_object_set(G_OBJECT(t->settings),
2577 "enable-plugins", es, (char *)NULL);
2578 webkit_web_view_set_settings(t->wv, t->settings);
2580 if (args->i & XT_WL_RELOAD)
2581 webkit_web_view_reload(t->wv);
2582 done:
2583 if (dom)
2584 g_free(dom);
2585 return (0);
2588 void
2589 js_toggle_cb(GtkWidget *w, struct tab *t)
2591 struct karg a;
2592 int es, set;
2594 g_object_get(G_OBJECT(t->settings),
2595 "enable-scripts", &es, (char *)NULL);
2596 es = !es;
2597 if (es)
2598 set = XT_WL_ENABLE;
2599 else
2600 set = XT_WL_DISABLE;
2602 a.i = set | XT_WL_TOPLEVEL;
2603 toggle_pl(t, &a);
2605 a.i = set | XT_WL_TOPLEVEL;
2606 toggle_cwl(t, &a);
2608 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2609 toggle_js(t, &a);
2613 toggle_src(struct tab *t, struct karg *args)
2615 gboolean mode;
2617 if (t == NULL)
2618 return (0);
2620 mode = webkit_web_view_get_view_source_mode(t->wv);
2621 webkit_web_view_set_view_source_mode(t->wv, !mode);
2622 webkit_web_view_reload(t->wv);
2624 return (0);
2627 void
2628 focus_webview(struct tab *t)
2630 if (t == NULL)
2631 return;
2633 /* only grab focus if we are visible */
2634 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2635 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2639 focus(struct tab *t, struct karg *args)
2641 if (t == NULL || args == NULL)
2642 return (1);
2644 if (show_url == 0)
2645 return (0);
2647 if (args->i == XT_FOCUS_URI)
2648 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2649 else if (args->i == XT_FOCUS_SEARCH)
2650 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2652 return (0);
2656 startpage(struct tab *t, struct karg *args)
2658 char *page, *body, *b;
2659 struct sp *s;
2661 if (t == NULL)
2662 show_oops(NULL, "startpage invalid parameters");
2664 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
2666 TAILQ_FOREACH(s, &spl, entry) {
2667 b = body;
2668 body = g_strdup_printf("%s%s<br>", body, s->line);
2669 g_free(b);
2672 page = get_html_page("Startup Exception", body, "", 0);
2673 g_free(body);
2675 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
2676 g_free(page);
2678 return (0);
2681 void
2682 startpage_add(const char *fmt, ...)
2684 va_list ap;
2685 char *msg;
2686 struct sp *s;
2688 if (fmt == NULL)
2689 return;
2691 va_start(ap, fmt);
2692 if (vasprintf(&msg, fmt, ap) == -1)
2693 errx(1, "startpage_add failed");
2694 va_end(ap);
2696 s = g_malloc0(sizeof *s);
2697 s->line = msg;
2699 TAILQ_INSERT_TAIL(&spl, s, entry);
2702 void
2703 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2704 size_t cert_count, char *title)
2706 gnutls_datum_t cinfo;
2707 char *tmp, *body;
2708 int i;
2710 body = g_strdup("");
2712 for (i = 0; i < cert_count; i++) {
2713 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2714 &cinfo))
2715 return;
2717 tmp = body;
2718 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2719 body, i, cinfo.data);
2720 gnutls_free(cinfo.data);
2721 g_free(tmp);
2724 tmp = get_html_page(title, body, "", 0);
2725 g_free(body);
2727 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2728 g_free(tmp);
2732 ca_cmd(struct tab *t, struct karg *args)
2734 FILE *f = NULL;
2735 int rv = 1, certs = 0, certs_read;
2736 struct stat sb;
2737 gnutls_datum_t dt;
2738 gnutls_x509_crt_t *c = NULL;
2739 char *certs_buf = NULL, *s;
2741 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2742 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
2743 return (1);
2746 if (fstat(fileno(f), &sb) == -1) {
2747 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
2748 goto done;
2751 certs_buf = g_malloc(sb.st_size + 1);
2752 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2753 show_oops(t, "Can't read CA file: %s", strerror(errno));
2754 goto done;
2756 certs_buf[sb.st_size] = '\0';
2758 s = certs_buf;
2759 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2760 certs++;
2761 s += strlen("BEGIN CERTIFICATE");
2764 bzero(&dt, sizeof dt);
2765 dt.data = (unsigned char *)certs_buf;
2766 dt.size = sb.st_size;
2767 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2768 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
2769 GNUTLS_X509_FMT_PEM, 0);
2770 if (certs_read <= 0) {
2771 show_oops(t, "No cert(s) available");
2772 goto done;
2774 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2775 done:
2776 if (c)
2777 g_free(c);
2778 if (certs_buf)
2779 g_free(certs_buf);
2780 if (f)
2781 fclose(f);
2783 return (rv);
2787 connect_socket_from_uri(const gchar *uri, const gchar **error_str, char *domain,
2788 size_t domain_sz)
2790 SoupURI *su = NULL;
2791 struct addrinfo hints, *res = NULL, *ai;
2792 int rv = -1, s = -1, on, error;
2793 char port[8];
2794 static gchar myerror[256]; /* this is not thread safe */
2796 myerror[0] = '\0';
2797 *error_str = myerror;
2798 if (uri && !g_str_has_prefix(uri, "https://")) {
2799 *error_str = "invalid URI";
2800 goto done;
2803 su = soup_uri_new(uri);
2804 if (su == NULL) {
2805 *error_str = "invalid soup URI";
2806 goto done;
2808 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
2809 *error_str = "invalid HTTPS URI";
2810 goto done;
2813 snprintf(port, sizeof port, "%d", su->port);
2814 bzero(&hints, sizeof(struct addrinfo));
2815 hints.ai_flags = AI_CANONNAME;
2816 hints.ai_family = AF_UNSPEC;
2817 hints.ai_socktype = SOCK_STREAM;
2819 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
2820 snprintf(myerror, sizeof myerror, "getaddrinfo failed: %s",
2821 gai_strerror(errno));
2822 goto done;
2825 for (ai = res; ai; ai = ai->ai_next) {
2826 if (s != -1) {
2827 close(s);
2828 s = -1;
2831 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2832 continue;
2833 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2834 if (s == -1)
2835 continue;
2836 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2837 sizeof(on)) == -1)
2838 continue;
2839 if (connect(s, ai->ai_addr, ai->ai_addrlen) == 0)
2840 break;
2842 if (s == -1) {
2843 snprintf(myerror, sizeof myerror,
2844 "could not obtain certificates from: %s",
2845 su->host);
2846 goto done;
2849 if (domain)
2850 strlcpy(domain, su->host, domain_sz);
2851 rv = s;
2852 done:
2853 if (su)
2854 soup_uri_free(su);
2855 if (res)
2856 freeaddrinfo(res);
2857 if (rv == -1 && s != -1)
2858 close(s);
2860 return (rv);
2864 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2866 if (gsession)
2867 gnutls_deinit(gsession);
2868 if (xcred)
2869 gnutls_certificate_free_credentials(xcred);
2871 return (0);
2875 start_tls(const gchar **error_str, int s, gnutls_session_t *gs,
2876 gnutls_certificate_credentials_t *xc)
2878 gnutls_certificate_credentials_t xcred;
2879 gnutls_session_t gsession;
2880 int rv = 1;
2881 static gchar myerror[1024]; /* this is not thread safe */
2883 if (gs == NULL || xc == NULL)
2884 goto done;
2886 myerror[0] = '\0';
2887 *gs = NULL;
2888 *xc = NULL;
2890 gnutls_certificate_allocate_credentials(&xcred);
2891 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2892 GNUTLS_X509_FMT_PEM);
2894 gnutls_init(&gsession, GNUTLS_CLIENT);
2895 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2896 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2897 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2898 if ((rv = gnutls_handshake(gsession)) < 0) {
2899 snprintf(myerror, sizeof myerror,
2900 "gnutls_handshake failed %d fatal %d %s",
2902 gnutls_error_is_fatal(rv),
2903 gnutls_strerror_name(rv));
2904 stop_tls(gsession, xcred);
2905 goto done;
2908 gnutls_credentials_type_t cred;
2909 cred = gnutls_auth_get_type(gsession);
2910 if (cred != GNUTLS_CRD_CERTIFICATE) {
2911 snprintf(myerror, sizeof myerror,
2912 "gnutls_auth_get_type failed %d",
2913 (int)cred);
2914 stop_tls(gsession, xcred);
2915 goto done;
2918 *gs = gsession;
2919 *xc = xcred;
2920 rv = 0;
2921 done:
2922 *error_str = myerror;
2923 return (rv);
2927 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2928 size_t *cert_count)
2930 unsigned int len;
2931 const gnutls_datum_t *cl;
2932 gnutls_x509_crt_t *all_certs;
2933 int i, rv = 1;
2935 if (certs == NULL || cert_count == NULL)
2936 goto done;
2937 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2938 goto done;
2939 cl = gnutls_certificate_get_peers(gsession, &len);
2940 if (len == 0)
2941 goto done;
2943 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2944 for (i = 0; i < len; i++) {
2945 gnutls_x509_crt_init(&all_certs[i]);
2946 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2947 GNUTLS_X509_FMT_PEM < 0)) {
2948 g_free(all_certs);
2949 goto done;
2953 *certs = all_certs;
2954 *cert_count = len;
2955 rv = 0;
2956 done:
2957 return (rv);
2960 void
2961 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2963 int i;
2965 for (i = 0; i < cert_count; i++)
2966 gnutls_x509_crt_deinit(certs[i]);
2967 g_free(certs);
2970 void
2971 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
2973 GdkColor c_text, c_base;
2975 gdk_color_parse(text, &c_text);
2976 gdk_color_parse(base, &c_base);
2978 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
2979 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
2980 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
2981 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
2983 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
2984 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
2985 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
2986 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
2989 void
2990 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2991 size_t cert_count, char *domain)
2993 size_t cert_buf_sz;
2994 char cert_buf[64 * 1024], file[PATH_MAX];
2995 int i;
2996 FILE *f;
2997 GdkColor color;
2999 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3000 return;
3002 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3003 if ((f = fopen(file, "w")) == NULL) {
3004 show_oops(t, "Can't create cert file %s %s",
3005 file, strerror(errno));
3006 return;
3009 for (i = 0; i < cert_count; i++) {
3010 cert_buf_sz = sizeof cert_buf;
3011 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3012 cert_buf, &cert_buf_sz)) {
3013 show_oops(t, "gnutls_x509_crt_export failed");
3014 goto done;
3016 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3017 show_oops(t, "Can't write certs: %s", strerror(errno));
3018 goto done;
3022 /* not the best spot but oh well */
3023 gdk_color_parse("lightblue", &color);
3024 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3025 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3026 done:
3027 fclose(f);
3030 enum cert_trust {
3031 CERT_LOCAL,
3032 CERT_TRUSTED,
3033 CERT_UNTRUSTED,
3034 CERT_BAD
3037 enum cert_trust
3038 load_compare_cert(const gchar *uri, const gchar **error_str)
3040 char domain[8182], file[PATH_MAX];
3041 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3042 int s = -1, i;
3043 unsigned int error = 0;
3044 FILE *f = NULL;
3045 size_t cert_buf_sz, cert_count;
3046 enum cert_trust rv = CERT_UNTRUSTED;
3047 static gchar serr[80]; /* this isn't thread safe */
3048 gnutls_session_t gsession;
3049 gnutls_x509_crt_t *certs;
3050 gnutls_certificate_credentials_t xcred;
3052 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3054 serr[0] = '\0';
3055 *error_str = serr;
3056 if ((s = connect_socket_from_uri(uri, error_str, domain,
3057 sizeof domain)) == -1)
3058 return (rv);
3060 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3062 /* go ssl/tls */
3063 if (start_tls(error_str, s, &gsession, &xcred))
3064 goto done;
3065 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3067 /* verify certs in case cert file doesn't exist */
3068 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3069 GNUTLS_E_SUCCESS) {
3070 *error_str = "Invalid certificates";
3071 goto done;
3074 /* get certs */
3075 if (get_connection_certs(gsession, &certs, &cert_count)) {
3076 *error_str = "Can't get connection certificates";
3077 goto done;
3080 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3081 if ((f = fopen(file, "r")) == NULL) {
3082 if (!error)
3083 rv = CERT_TRUSTED;
3084 goto freeit;
3087 for (i = 0; i < cert_count; i++) {
3088 cert_buf_sz = sizeof cert_buf;
3089 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3090 cert_buf, &cert_buf_sz)) {
3091 goto freeit;
3093 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3094 rv = CERT_BAD; /* critical */
3095 goto freeit;
3097 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3098 rv = CERT_BAD; /* critical */
3099 goto freeit;
3101 rv = CERT_LOCAL;
3104 freeit:
3105 if (f)
3106 fclose(f);
3107 free_connection_certs(certs, cert_count);
3108 done:
3109 /* we close the socket first for speed */
3110 if (s != -1)
3111 close(s);
3113 /* only complain if we didn't save it locally */
3114 if (error && rv != CERT_LOCAL) {
3115 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3116 if (error & GNUTLS_CERT_INVALID)
3117 strlcat(serr, "invalid, ", sizeof serr);
3118 if (error & GNUTLS_CERT_REVOKED)
3119 strlcat(serr, "revoked, ", sizeof serr);
3120 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3121 strlcat(serr, "signer not found, ", sizeof serr);
3122 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3123 strlcat(serr, "not signed by CA, ", sizeof serr);
3124 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3125 strlcat(serr, "insecure algorithm, ", sizeof serr);
3126 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3127 strlcat(serr, "not activated, ", sizeof serr);
3128 if (error & GNUTLS_CERT_EXPIRED)
3129 strlcat(serr, "expired, ", sizeof serr);
3130 for (i = strlen(serr) - 1; i > 0; i--)
3131 if (serr[i] == ',') {
3132 serr[i] = '\0';
3133 break;
3135 *error_str = serr;
3138 stop_tls(gsession, xcred);
3140 return (rv);
3144 cert_cmd(struct tab *t, struct karg *args)
3146 const gchar *uri, *error_str = NULL;
3147 char domain[8182];
3148 int s = -1;
3149 size_t cert_count;
3150 gnutls_session_t gsession;
3151 gnutls_x509_crt_t *certs;
3152 gnutls_certificate_credentials_t xcred;
3154 if (t == NULL)
3155 return (1);
3157 if (ssl_ca_file == NULL) {
3158 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3159 return (1);
3162 if ((uri = get_uri(t)) == NULL) {
3163 show_oops(t, "Invalid URI");
3164 return (1);
3167 if ((s = connect_socket_from_uri(uri, &error_str, domain,
3168 sizeof domain)) == -1) {
3169 show_oops(t, "%s", error_str);
3170 return (1);
3173 /* go ssl/tls */
3174 if (start_tls(&error_str, s, &gsession, &xcred))
3175 goto done;
3177 /* get certs */
3178 if (get_connection_certs(gsession, &certs, &cert_count)) {
3179 show_oops(t, "get_connection_certs failed");
3180 goto done;
3183 if (args->i & XT_SHOW)
3184 show_certs(t, certs, cert_count, "Certificate Chain");
3185 else if (args->i & XT_SAVE)
3186 save_certs(t, certs, cert_count, domain);
3188 free_connection_certs(certs, cert_count);
3189 done:
3190 /* we close the socket first for speed */
3191 if (s != -1)
3192 close(s);
3193 stop_tls(gsession, xcred);
3194 if (error_str && strlen(error_str))
3195 show_oops(t, "%s", error_str);
3196 return (0);
3200 remove_cookie(int index)
3202 int i, rv = 1;
3203 GSList *cf;
3204 SoupCookie *c;
3206 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3208 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3210 for (i = 1; cf; cf = cf->next, i++) {
3211 if (i != index)
3212 continue;
3213 c = cf->data;
3214 print_cookie("remove cookie", c);
3215 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3216 rv = 0;
3217 break;
3220 soup_cookies_free(cf);
3222 return (rv);
3226 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3228 struct domain *d;
3229 char *tmp, *body;
3231 body = g_strdup("");
3233 /* p list */
3234 if (args->i & XT_WL_PERSISTENT) {
3235 tmp = body;
3236 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3237 g_free(tmp);
3238 RB_FOREACH(d, domain_list, wl) {
3239 if (d->handy == 0)
3240 continue;
3241 tmp = body;
3242 body = g_strdup_printf("%s%s<br/>", body, d->d);
3243 g_free(tmp);
3247 /* s list */
3248 if (args->i & XT_WL_SESSION) {
3249 tmp = body;
3250 body = g_strdup_printf("%s<h2>Session</h2>", body);
3251 g_free(tmp);
3252 RB_FOREACH(d, domain_list, wl) {
3253 if (d->handy == 1)
3254 continue;
3255 tmp = body;
3256 body = g_strdup_printf("%s%s<br/>", body, d->d);
3257 g_free(tmp);
3261 tmp = get_html_page(title, body, "", 0);
3262 g_free(body);
3263 if (wl == &js_wl)
3264 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3265 else if (wl == &c_wl)
3266 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3267 else
3268 load_webkit_string(t, tmp, XT_URI_ABOUT_PLUGINWL);
3269 g_free(tmp);
3270 return (0);
3273 #define XT_WL_INVALID (0)
3274 #define XT_WL_JAVASCRIPT (1)
3275 #define XT_WL_COOKIE (2)
3276 #define XT_WL_PLUGIN (3)
3278 wl_save(struct tab *t, struct karg *args, int list)
3280 char file[PATH_MAX], *lst_str = NULL;
3281 FILE *f;
3282 char *line = NULL, *lt = NULL, *dom = NULL;
3283 size_t linelen;
3284 const gchar *uri;
3285 struct karg a;
3286 struct domain *d;
3287 GSList *cf;
3288 SoupCookie *ci, *c;
3290 if (t == NULL || args == NULL)
3291 return (1);
3293 if (runtime_settings[0] == '\0')
3294 return (1);
3296 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3297 if ((f = fopen(file, "r+")) == NULL)
3298 return (1);
3300 switch (list) {
3301 case XT_WL_JAVASCRIPT:
3302 lst_str = "JavaScript";
3303 lt = g_strdup_printf("js_wl=%s", dom);
3304 break;
3305 case XT_WL_COOKIE:
3306 lst_str = "Cookie";
3307 lt = g_strdup_printf("cookie_wl=%s", dom);
3308 break;
3309 case XT_WL_PLUGIN:
3310 lst_str = "Plugin";
3311 lt = g_strdup_printf("pl_wl=%s", dom);
3312 break;
3313 default:
3314 show_oops(t, "Invalid list id: %d", list);
3315 return (1);
3318 uri = get_uri(t);
3319 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3320 if (uri == NULL || dom == NULL ||
3321 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3322 show_oops(t, "Can't add domain to %s white list", lst_str);
3323 goto done;
3326 while (!feof(f)) {
3327 line = fparseln(f, &linelen, NULL, NULL, 0);
3328 if (line == NULL)
3329 continue;
3330 if (!strcmp(line, lt))
3331 goto done;
3332 free(line);
3333 line = NULL;
3336 fprintf(f, "%s\n", lt);
3338 a.i = XT_WL_ENABLE;
3339 a.i |= args->i;
3340 switch (list) {
3341 case XT_WL_JAVASCRIPT:
3342 d = wl_find(dom, &js_wl);
3343 if (!d) {
3344 settings_add("js_wl", dom);
3345 d = wl_find(dom, &js_wl);
3347 toggle_js(t, &a);
3348 break;
3350 case XT_WL_COOKIE:
3351 d = wl_find(dom, &c_wl);
3352 if (!d) {
3353 settings_add("cookie_wl", dom);
3354 d = wl_find(dom, &c_wl);
3356 toggle_cwl(t, &a);
3358 /* find and add to persistent jar */
3359 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3360 for (;cf; cf = cf->next) {
3361 ci = cf->data;
3362 if (!strcmp(dom, ci->domain) ||
3363 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3364 c = soup_cookie_copy(ci);
3365 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3368 soup_cookies_free(cf);
3369 break;
3371 case XT_WL_PLUGIN:
3372 d = wl_find(dom, &pl_wl);
3373 if (!d) {
3374 settings_add("pl_wl", dom);
3375 d = wl_find(dom, &pl_wl);
3377 toggle_pl(t, &a);
3378 break;
3379 default:
3380 abort(); /* can't happen */
3382 if (d)
3383 d->handy = 1;
3385 done:
3386 if (line)
3387 free(line);
3388 if (dom)
3389 g_free(dom);
3390 if (lt)
3391 g_free(lt);
3392 fclose(f);
3394 return (0);
3398 js_show_wl(struct tab *t, struct karg *args)
3400 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3401 wl_show(t, args, "JavaScript White List", &js_wl);
3403 return (0);
3407 cookie_show_wl(struct tab *t, struct karg *args)
3409 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3410 wl_show(t, args, "Cookie White List", &c_wl);
3412 return (0);
3416 cookie_cmd(struct tab *t, struct karg *args)
3418 if (args->i & XT_SHOW)
3419 wl_show(t, args, "Cookie White List", &c_wl);
3420 else if (args->i & XT_WL_TOGGLE) {
3421 args->i |= XT_WL_RELOAD;
3422 toggle_cwl(t, args);
3423 } else if (args->i & XT_SAVE) {
3424 args->i |= XT_WL_RELOAD;
3425 wl_save(t, args, XT_WL_COOKIE);
3426 } else if (args->i & XT_DELETE)
3427 show_oops(t, "'cookie delete' currently unimplemented");
3429 return (0);
3433 js_cmd(struct tab *t, struct karg *args)
3435 if (args->i & XT_SHOW)
3436 wl_show(t, args, "JavaScript White List", &js_wl);
3437 else if (args->i & XT_SAVE) {
3438 args->i |= XT_WL_RELOAD;
3439 wl_save(t, args, XT_WL_JAVASCRIPT);
3440 } else if (args->i & XT_WL_TOGGLE) {
3441 args->i |= XT_WL_RELOAD;
3442 toggle_js(t, args);
3443 } else if (args->i & XT_DELETE)
3444 show_oops(t, "'js delete' currently unimplemented");
3446 return (0);
3450 pl_show_wl(struct tab *t, struct karg *args)
3452 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3453 wl_show(t, args, "Plugin White List", &pl_wl);
3455 return (0);
3459 pl_cmd(struct tab *t, struct karg *args)
3461 if (args->i & XT_SHOW)
3462 wl_show(t, args, "Plugin White List", &pl_wl);
3463 else if (args->i & XT_SAVE) {
3464 args->i |= XT_WL_RELOAD;
3465 wl_save(t, args, XT_WL_PLUGIN);
3466 } else if (args->i & XT_WL_TOGGLE) {
3467 args->i |= XT_WL_RELOAD;
3468 toggle_pl(t, args);
3469 } else if (args->i & XT_DELETE)
3470 show_oops(t, "'plugin delete' currently unimplemented");
3472 return (0);
3476 toplevel_cmd(struct tab *t, struct karg *args)
3478 js_toggle_cb(t->js_toggle, t);
3480 return (0);
3484 add_favorite(struct tab *t, struct karg *args)
3486 char file[PATH_MAX];
3487 FILE *f;
3488 char *line = NULL;
3489 size_t urilen, linelen;
3490 const gchar *uri, *title;
3492 if (t == NULL)
3493 return (1);
3495 /* don't allow adding of xtp pages to favorites */
3496 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3497 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3498 return (1);
3501 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3502 if ((f = fopen(file, "r+")) == NULL) {
3503 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3504 return (1);
3507 title = get_title(t, FALSE);
3508 uri = get_uri(t);
3510 if (title == NULL || uri == NULL) {
3511 show_oops(t, "can't add page to favorites");
3512 goto done;
3515 urilen = strlen(uri);
3517 for (;;) {
3518 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3519 if (feof(f) || ferror(f))
3520 break;
3522 if (linelen == urilen && !strcmp(line, uri))
3523 goto done;
3525 free(line);
3526 line = NULL;
3529 fprintf(f, "\n%s\n%s", title, uri);
3530 done:
3531 if (line)
3532 free(line);
3533 fclose(f);
3535 update_favorite_tabs(NULL);
3537 return (0);
3541 can_go_back_for_real(struct tab *t)
3543 int i;
3544 WebKitWebHistoryItem *item;
3545 const gchar *uri;
3547 if (t == NULL)
3548 return (FALSE);
3550 /* rely on webkit to make sure we can go backward when on an about page */
3551 uri = get_uri(t);
3552 if (uri == NULL || g_str_has_prefix(uri, "about:"))
3553 return (webkit_web_view_can_go_back(t->wv));
3555 /* the back/forwars list is stupid so help determine if we can go back */
3556 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
3557 item != NULL;
3558 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
3559 if (strcmp(webkit_web_history_item_get_uri(item), uri))
3560 return (TRUE);
3563 return (FALSE);
3567 can_go_forward_for_real(struct tab *t)
3569 int i;
3570 WebKitWebHistoryItem *item;
3571 const gchar *uri;
3573 if (t == NULL)
3574 return (FALSE);
3576 /* rely on webkit to make sure we can go forward when on an about page */
3577 uri = get_uri(t);
3578 if (uri == NULL || g_str_has_prefix(uri, "about:"))
3579 return (webkit_web_view_can_go_forward(t->wv));
3581 /* the back/forwars list is stupid so help selecting a different item */
3582 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
3583 item != NULL;
3584 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
3585 if (strcmp(webkit_web_history_item_get_uri(item), uri))
3586 return (TRUE);
3589 return (FALSE);
3592 void
3593 go_back_for_real(struct tab *t)
3595 int i;
3596 WebKitWebHistoryItem *item;
3597 const gchar *uri;
3599 if (t == NULL)
3600 return;
3602 uri = get_uri(t);
3603 if (uri == NULL) {
3604 webkit_web_view_go_back(t->wv);
3605 return;
3607 /* the back/forwars list is stupid so help selecting a different item */
3608 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
3609 item != NULL;
3610 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
3611 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
3612 webkit_web_view_go_to_back_forward_item(t->wv, item);
3613 break;
3618 void
3619 go_forward_for_real(struct tab *t)
3621 int i;
3622 WebKitWebHistoryItem *item;
3623 const gchar *uri;
3625 if (t == NULL)
3626 return;
3628 uri = get_uri(t);
3629 if (uri == NULL) {
3630 webkit_web_view_go_forward(t->wv);
3631 return;
3633 /* the back/forwars list is stupid so help selecting a different item */
3634 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
3635 item != NULL;
3636 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
3637 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
3638 webkit_web_view_go_to_back_forward_item(t->wv, item);
3639 break;
3645 navaction(struct tab *t, struct karg *args)
3647 WebKitWebHistoryItem *item;
3648 WebKitWebFrame *frame;
3650 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3651 t->tab_id, args->i);
3653 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
3654 if (t->item) {
3655 if (args->i == XT_NAV_BACK)
3656 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3657 else
3658 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3659 if (item == NULL)
3660 return (XT_CB_PASSTHROUGH);
3661 webkit_web_view_go_to_back_forward_item(t->wv, item);
3662 t->item = NULL;
3663 return (XT_CB_PASSTHROUGH);
3666 switch (args->i) {
3667 case XT_NAV_BACK:
3668 marks_clear(t);
3669 go_back_for_real(t);
3670 break;
3671 case XT_NAV_FORWARD:
3672 marks_clear(t);
3673 go_forward_for_real(t);
3674 break;
3675 case XT_NAV_RELOAD:
3676 frame = webkit_web_view_get_main_frame(t->wv);
3677 webkit_web_frame_reload(frame);
3678 break;
3680 return (XT_CB_PASSTHROUGH);
3684 move(struct tab *t, struct karg *args)
3686 GtkAdjustment *adjust;
3687 double pi, si, pos, ps, upper, lower, max;
3688 double percent;
3690 switch (args->i) {
3691 case XT_MOVE_DOWN:
3692 case XT_MOVE_UP:
3693 case XT_MOVE_BOTTOM:
3694 case XT_MOVE_TOP:
3695 case XT_MOVE_PAGEDOWN:
3696 case XT_MOVE_PAGEUP:
3697 case XT_MOVE_HALFDOWN:
3698 case XT_MOVE_HALFUP:
3699 case XT_MOVE_PERCENT:
3700 adjust = t->adjust_v;
3701 break;
3702 default:
3703 adjust = t->adjust_h;
3704 break;
3707 pos = gtk_adjustment_get_value(adjust);
3708 ps = gtk_adjustment_get_page_size(adjust);
3709 upper = gtk_adjustment_get_upper(adjust);
3710 lower = gtk_adjustment_get_lower(adjust);
3711 si = gtk_adjustment_get_step_increment(adjust);
3712 pi = gtk_adjustment_get_page_increment(adjust);
3713 max = upper - ps;
3715 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3716 "max %f si %f pi %f\n",
3717 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3718 pos, ps, upper, lower, max, si, pi);
3720 switch (args->i) {
3721 case XT_MOVE_DOWN:
3722 case XT_MOVE_RIGHT:
3723 pos += si;
3724 gtk_adjustment_set_value(adjust, MIN(pos, max));
3725 break;
3726 case XT_MOVE_UP:
3727 case XT_MOVE_LEFT:
3728 pos -= si;
3729 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3730 break;
3731 case XT_MOVE_BOTTOM:
3732 case XT_MOVE_FARRIGHT:
3733 gtk_adjustment_set_value(adjust, max);
3734 break;
3735 case XT_MOVE_TOP:
3736 case XT_MOVE_FARLEFT:
3737 gtk_adjustment_set_value(adjust, lower);
3738 break;
3739 case XT_MOVE_PAGEDOWN:
3740 pos += pi;
3741 gtk_adjustment_set_value(adjust, MIN(pos, max));
3742 break;
3743 case XT_MOVE_PAGEUP:
3744 pos -= pi;
3745 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3746 break;
3747 case XT_MOVE_HALFDOWN:
3748 pos += pi / 2;
3749 gtk_adjustment_set_value(adjust, MIN(pos, max));
3750 break;
3751 case XT_MOVE_HALFUP:
3752 pos -= pi / 2;
3753 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3754 break;
3755 case XT_MOVE_PERCENT:
3756 percent = atoi(args->s) / 100.0;
3757 pos = max * percent;
3758 if (pos < 0.0 || pos > max)
3759 break;
3760 gtk_adjustment_set_value(adjust, pos);
3761 break;
3762 default:
3763 return (XT_CB_PASSTHROUGH);
3766 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3768 return (XT_CB_HANDLED);
3771 void
3772 url_set_visibility(void)
3774 struct tab *t;
3776 TAILQ_FOREACH(t, &tabs, entry)
3777 if (show_url == 0) {
3778 gtk_widget_hide(t->toolbar);
3779 focus_webview(t);
3780 } else
3781 gtk_widget_show(t->toolbar);
3784 void
3785 notebook_tab_set_visibility(void)
3787 if (show_tabs == 0) {
3788 gtk_widget_hide(tab_bar);
3789 gtk_notebook_set_show_tabs(notebook, FALSE);
3790 } else {
3791 if (tab_style == XT_TABS_NORMAL) {
3792 gtk_widget_hide(tab_bar);
3793 gtk_notebook_set_show_tabs(notebook, TRUE);
3794 } else if (tab_style == XT_TABS_COMPACT) {
3795 gtk_widget_show(tab_bar);
3796 gtk_notebook_set_show_tabs(notebook, FALSE);
3801 void
3802 statusbar_set_visibility(void)
3804 struct tab *t;
3806 TAILQ_FOREACH(t, &tabs, entry)
3807 if (show_statusbar == 0) {
3808 gtk_widget_hide(t->statusbar_box);
3809 focus_webview(t);
3810 } else
3811 gtk_widget_show(t->statusbar_box);
3814 void
3815 url_set(struct tab *t, int enable_url_entry)
3817 GdkPixbuf *pixbuf;
3818 int progress;
3820 show_url = enable_url_entry;
3822 if (enable_url_entry) {
3823 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
3824 GTK_ENTRY_ICON_PRIMARY, NULL);
3825 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
3826 } else {
3827 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3828 GTK_ENTRY_ICON_PRIMARY);
3829 progress =
3830 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3831 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
3832 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3833 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
3834 progress);
3839 fullscreen(struct tab *t, struct karg *args)
3841 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3843 if (t == NULL)
3844 return (XT_CB_PASSTHROUGH);
3846 if (show_url == 0) {
3847 url_set(t, 1);
3848 show_tabs = 1;
3849 } else {
3850 url_set(t, 0);
3851 show_tabs = 0;
3854 url_set_visibility();
3855 notebook_tab_set_visibility();
3857 return (XT_CB_HANDLED);
3861 statustoggle(struct tab *t, struct karg *args)
3863 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3865 if (show_statusbar == 1) {
3866 show_statusbar = 0;
3867 statusbar_set_visibility();
3868 } else if (show_statusbar == 0) {
3869 show_statusbar = 1;
3870 statusbar_set_visibility();
3872 return (XT_CB_HANDLED);
3876 urlaction(struct tab *t, struct karg *args)
3878 int rv = XT_CB_HANDLED;
3880 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3882 if (t == NULL)
3883 return (XT_CB_PASSTHROUGH);
3885 switch (args->i) {
3886 case XT_URL_SHOW:
3887 if (show_url == 0) {
3888 url_set(t, 1);
3889 url_set_visibility();
3891 break;
3892 case XT_URL_HIDE:
3893 if (show_url == 1) {
3894 url_set(t, 0);
3895 url_set_visibility();
3897 break;
3899 return (rv);
3903 tabaction(struct tab *t, struct karg *args)
3905 int rv = XT_CB_HANDLED;
3906 char *url = args->s;
3907 struct undo *u;
3908 struct tab *tt;
3910 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3912 if (t == NULL)
3913 return (XT_CB_PASSTHROUGH);
3915 switch (args->i) {
3916 case XT_TAB_NEW:
3917 if (strlen(url) > 0)
3918 create_new_tab(url, NULL, 1, args->precount);
3919 else
3920 create_new_tab(NULL, NULL, 1, args->precount);
3921 break;
3922 case XT_TAB_DELETE:
3923 if (args->precount < 0)
3924 delete_tab(t);
3925 else
3926 TAILQ_FOREACH(tt, &tabs, entry)
3927 if (tt->tab_id == args->precount - 1) {
3928 delete_tab(tt);
3929 break;
3931 break;
3932 case XT_TAB_DELQUIT:
3933 if (gtk_notebook_get_n_pages(notebook) > 1)
3934 delete_tab(t);
3935 else
3936 quit(t, args);
3937 break;
3938 case XT_TAB_OPEN:
3939 if (strlen(url) > 0)
3941 else {
3942 rv = XT_CB_PASSTHROUGH;
3943 goto done;
3945 load_uri(t, url);
3946 break;
3947 case XT_TAB_SHOW:
3948 if (show_tabs == 0) {
3949 show_tabs = 1;
3950 notebook_tab_set_visibility();
3952 break;
3953 case XT_TAB_HIDE:
3954 if (show_tabs == 1) {
3955 show_tabs = 0;
3956 notebook_tab_set_visibility();
3958 break;
3959 case XT_TAB_NEXTSTYLE:
3960 if (tab_style == XT_TABS_NORMAL) {
3961 tab_style = XT_TABS_COMPACT;
3962 recolor_compact_tabs();
3964 else
3965 tab_style = XT_TABS_NORMAL;
3966 notebook_tab_set_visibility();
3967 break;
3968 case XT_TAB_UNDO_CLOSE:
3969 if (undo_count == 0) {
3970 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
3971 __func__);
3972 goto done;
3973 } else {
3974 undo_count--;
3975 u = TAILQ_FIRST(&undos);
3976 create_new_tab(u->uri, u, 1, -1);
3978 TAILQ_REMOVE(&undos, u, entry);
3979 g_free(u->uri);
3980 /* u->history is freed in create_new_tab() */
3981 g_free(u);
3983 break;
3984 default:
3985 rv = XT_CB_PASSTHROUGH;
3986 goto done;
3989 done:
3990 if (args->s) {
3991 g_free(args->s);
3992 args->s = NULL;
3995 return (rv);
3999 resizetab(struct tab *t, struct karg *args)
4001 if (t == NULL || args == NULL) {
4002 show_oops(NULL, "resizetab invalid parameters");
4003 return (XT_CB_PASSTHROUGH);
4006 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4007 t->tab_id, args->i);
4009 setzoom_webkit(t, args->i);
4011 return (XT_CB_HANDLED);
4015 movetab(struct tab *t, struct karg *args)
4017 int n, dest;
4019 if (t == NULL || args == NULL) {
4020 show_oops(NULL, "movetab invalid parameters");
4021 return (XT_CB_PASSTHROUGH);
4024 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4025 t->tab_id, args->i);
4027 if (args->i >= XT_TAB_INVALID)
4028 return (XT_CB_PASSTHROUGH);
4030 if (TAILQ_EMPTY(&tabs))
4031 return (XT_CB_PASSTHROUGH);
4033 n = gtk_notebook_get_n_pages(notebook);
4034 dest = gtk_notebook_get_current_page(notebook);
4036 switch (args->i) {
4037 case XT_TAB_NEXT:
4038 if (args->precount < 0)
4039 dest = dest == n - 1 ? 0 : dest + 1;
4040 else
4041 dest = args->precount - 1;
4043 break;
4044 case XT_TAB_PREV:
4045 if (args->precount < 0)
4046 dest -= 1;
4047 else
4048 dest -= args->precount % n;
4050 if (dest < 0)
4051 dest += n;
4053 break;
4054 case XT_TAB_FIRST:
4055 dest = 0;
4056 break;
4057 case XT_TAB_LAST:
4058 dest = n - 1;
4059 break;
4060 default:
4061 return (XT_CB_PASSTHROUGH);
4064 if (dest < 0 || dest >= n)
4065 return (XT_CB_PASSTHROUGH);
4066 if (t->tab_id == dest) {
4067 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4068 return (XT_CB_HANDLED);
4071 set_current_tab(dest);
4073 return (XT_CB_HANDLED);
4076 int cmd_prefix = 0;
4079 command_mode(struct tab *t, struct karg *args)
4081 if (args->i == XT_MODE_COMMAND)
4082 run_script(t, "hints.clearFocus();");
4083 else
4084 run_script(t, "hints.focusInput();");
4085 return (XT_CB_HANDLED);
4089 command(struct tab *t, struct karg *args)
4091 char *s = NULL, *ss = NULL;
4092 GdkColor color;
4093 const gchar *uri;
4094 struct karg a;
4096 if (t == NULL || args == NULL) {
4097 show_oops(NULL, "command invalid parameters");
4098 return (XT_CB_PASSTHROUGH);
4101 switch (args->i) {
4102 case '/':
4103 s = "/";
4104 break;
4105 case '?':
4106 s = "?";
4107 break;
4108 case ':':
4109 if (cmd_prefix == 0)
4110 s = ":";
4111 else {
4112 ss = g_strdup_printf(":%d", cmd_prefix);
4113 s = ss;
4114 cmd_prefix = 0;
4116 break;
4117 case '.':
4118 bzero(&a, sizeof a);
4119 a.i = 0;
4120 hint(t, &a);
4121 s = ".";
4122 break;
4123 case ',':
4124 bzero(&a, sizeof a);
4125 a.i = XT_HINT_NEWTAB;
4126 hint(t, &a);
4127 s = ",";
4128 break;
4129 case XT_CMD_OPEN:
4130 s = ":open ";
4131 break;
4132 case XT_CMD_TABNEW:
4133 s = ":tabnew ";
4134 break;
4135 case XT_CMD_OPEN_CURRENT:
4136 s = ":open ";
4137 /* FALL THROUGH */
4138 case XT_CMD_TABNEW_CURRENT:
4139 if (!s) /* FALL THROUGH? */
4140 s = ":tabnew ";
4141 if ((uri = get_uri(t)) != NULL) {
4142 ss = g_strdup_printf("%s%s", s, uri);
4143 s = ss;
4145 break;
4146 default:
4147 show_oops(t, "command: invalid opcode %d", args->i);
4148 return (XT_CB_PASSTHROUGH);
4151 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4153 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4154 gdk_color_parse(XT_COLOR_WHITE, &color);
4155 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4156 show_cmd(t);
4157 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4158 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4160 if (ss)
4161 g_free(ss);
4163 return (XT_CB_HANDLED);
4167 search(struct tab *t, struct karg *args)
4169 gboolean d;
4171 if (t == NULL || args == NULL) {
4172 show_oops(NULL, "search invalid parameters");
4173 return (1);
4176 switch (args->i) {
4177 case XT_SEARCH_NEXT:
4178 d = t->search_forward;
4179 break;
4180 case XT_SEARCH_PREV:
4181 d = !t->search_forward;
4182 break;
4183 default:
4184 return (XT_CB_PASSTHROUGH);
4187 if (t->search_text == NULL) {
4188 if (global_search == NULL)
4189 return (XT_CB_PASSTHROUGH);
4190 else {
4191 d = t->search_forward = TRUE;
4192 t->search_text = g_strdup(global_search);
4193 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4194 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4198 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4199 t->tab_id, args->i, t->search_forward, t->search_text);
4201 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4203 return (XT_CB_HANDLED);
4206 struct settings_args {
4207 char **body;
4208 int i;
4211 void
4212 print_setting(struct settings *s, char *val, void *cb_args)
4214 char *tmp, *color;
4215 struct settings_args *sa = cb_args;
4217 if (sa == NULL)
4218 return;
4220 if (s->flags & XT_SF_RUNTIME)
4221 color = "#22cc22";
4222 else
4223 color = "#cccccc";
4225 tmp = *sa->body;
4226 *sa->body = g_strdup_printf(
4227 "%s\n<tr>"
4228 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4229 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4230 *sa->body,
4231 color,
4232 s->name,
4233 color,
4236 g_free(tmp);
4237 sa->i++;
4241 set_show(struct tab *t, struct karg *args)
4243 char *body, *page, *tmp;
4244 int i = 1;
4245 struct settings_args sa;
4247 bzero(&sa, sizeof sa);
4248 sa.body = &body;
4250 /* body */
4251 body = g_strdup_printf("<div align='center'><table><tr>"
4252 "<th align='left'>Setting</th>"
4253 "<th align='left'>Value</th></tr>\n");
4255 settings_walk(print_setting, &sa);
4256 i = sa.i;
4258 /* small message if there are none */
4259 if (i == 1) {
4260 tmp = body;
4261 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4262 "colspan='2'>No settings</td></tr>\n", body);
4263 g_free(tmp);
4266 tmp = body;
4267 body = g_strdup_printf("%s</table></div>", body);
4268 g_free(tmp);
4270 page = get_html_page("Settings", body, "", 0);
4272 g_free(body);
4274 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4276 g_free(page);
4278 return (XT_CB_PASSTHROUGH);
4282 set(struct tab *t, struct karg *args)
4284 char *p, *val;
4285 int i;
4287 if (args == NULL || args->s == NULL)
4288 return (set_show(t, args));
4290 /* strip spaces */
4291 p = g_strstrip(args->s);
4293 if (strlen(p) == 0)
4294 return (set_show(t, args));
4296 /* we got some sort of string */
4297 val = g_strrstr(p, "=");
4298 if (val) {
4299 *val++ = '\0';
4300 val = g_strchomp(val);
4301 p = g_strchomp(p);
4303 for (i = 0; i < LENGTH(rs); i++) {
4304 if (strcmp(rs[i].name, p))
4305 continue;
4307 if (rs[i].activate) {
4308 if (rs[i].activate(val))
4309 show_oops(t, "%s invalid value %s",
4310 p, val);
4311 else
4312 show_oops(t, ":set %s = %s", p, val);
4313 goto done;
4314 } else {
4315 show_oops(t, "not a runtime option: %s", p);
4316 goto done;
4319 show_oops(t, "unknown option: %s", p);
4320 } else {
4321 p = g_strchomp(p);
4323 for (i = 0; i < LENGTH(rs); i++) {
4324 if (strcmp(rs[i].name, p))
4325 continue;
4327 /* XXX this could use some cleanup */
4328 switch (rs[i].type) {
4329 case XT_S_INT:
4330 if (rs[i].ival)
4331 show_oops(t, "%s = %d",
4332 rs[i].name, *rs[i].ival);
4333 else if (rs[i].s && rs[i].s->get)
4334 show_oops(t, "%s = %s",
4335 rs[i].name,
4336 rs[i].s->get(&rs[i]));
4337 else if (rs[i].s && rs[i].s->get == NULL)
4338 show_oops(t, "%s = ...", rs[i].name);
4339 else
4340 show_oops(t, "%s = ", rs[i].name);
4341 break;
4342 case XT_S_FLOAT:
4343 if (rs[i].fval)
4344 show_oops(t, "%s = %f",
4345 rs[i].name, *rs[i].fval);
4346 else if (rs[i].s && rs[i].s->get)
4347 show_oops(t, "%s = %s",
4348 rs[i].name,
4349 rs[i].s->get(&rs[i]));
4350 else if (rs[i].s && rs[i].s->get == NULL)
4351 show_oops(t, "%s = ...", rs[i].name);
4352 else
4353 show_oops(t, "%s = ", rs[i].name);
4354 break;
4355 case XT_S_STR:
4356 if (rs[i].sval && *rs[i].sval)
4357 show_oops(t, "%s = %s",
4358 rs[i].name, *rs[i].sval);
4359 else if (rs[i].s && rs[i].s->get)
4360 show_oops(t, "%s = %s",
4361 rs[i].name,
4362 rs[i].s->get(&rs[i]));
4363 else if (rs[i].s && rs[i].s->get == NULL)
4364 show_oops(t, "%s = ...", rs[i].name);
4365 else
4366 show_oops(t, "%s = ", rs[i].name);
4367 break;
4368 default:
4369 show_oops(t, "unknown type for %s", rs[i].name);
4370 goto done;
4373 goto done;
4375 show_oops(t, "unknown option: %s", p);
4377 done:
4378 return (XT_CB_PASSTHROUGH);
4382 session_save(struct tab *t, char *filename)
4384 struct karg a;
4385 int rv = 1;
4386 struct session *s;
4388 if (strlen(filename) == 0)
4389 goto done;
4391 if (filename[0] == '.' || filename[0] == '/')
4392 goto done;
4394 a.s = filename;
4395 if (save_tabs(t, &a))
4396 goto done;
4397 strlcpy(named_session, filename, sizeof named_session);
4399 /* add the new session to the list of sessions */
4400 s = g_malloc(sizeof(struct session));
4401 s->name = g_strdup(filename);
4402 TAILQ_INSERT_TAIL(&sessions, s, entry);
4404 rv = 0;
4405 done:
4406 return (rv);
4410 session_open(struct tab *t, char *filename)
4412 struct karg a;
4413 int rv = 1;
4415 if (strlen(filename) == 0)
4416 goto done;
4418 if (filename[0] == '.' || filename[0] == '/')
4419 goto done;
4421 a.s = filename;
4422 a.i = XT_SES_CLOSETABS;
4423 if (open_tabs(t, &a))
4424 goto done;
4426 strlcpy(named_session, filename, sizeof named_session);
4428 rv = 0;
4429 done:
4430 return (rv);
4434 session_delete(struct tab *t, char *filename)
4436 char file[PATH_MAX];
4437 int rv = 1;
4438 struct session *s;
4440 if (strlen(filename) == 0)
4441 goto done;
4443 if (filename[0] == '.' || filename[0] == '/')
4444 goto done;
4446 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4447 if (unlink(file))
4448 goto done;
4450 if (!strcmp(filename, named_session))
4451 strlcpy(named_session, XT_SAVED_TABS_FILE,
4452 sizeof named_session);
4454 /* remove session from sessions list */
4455 TAILQ_FOREACH(s, &sessions, entry) {
4456 if (!strcmp(s->name, filename))
4457 break;
4459 if (s == NULL)
4460 goto done;
4461 TAILQ_REMOVE(&sessions, s, entry);
4462 g_free((gpointer) s->name);
4463 g_free(s);
4465 rv = 0;
4466 done:
4467 return (rv);
4471 session_cmd(struct tab *t, struct karg *args)
4473 char *filename = args->s;
4475 if (t == NULL)
4476 return (1);
4478 if (args->i & XT_SHOW)
4479 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4480 XT_SAVED_TABS_FILE : named_session);
4481 else if (args->i & XT_SAVE) {
4482 if (session_save(t, filename)) {
4483 show_oops(t, "Can't save session: %s",
4484 filename ? filename : "INVALID");
4485 goto done;
4487 } else if (args->i & XT_OPEN) {
4488 if (session_open(t, filename)) {
4489 show_oops(t, "Can't open session: %s",
4490 filename ? filename : "INVALID");
4491 goto done;
4493 } else if (args->i & XT_DELETE) {
4494 if (session_delete(t, filename)) {
4495 show_oops(t, "Can't delete session: %s",
4496 filename ? filename : "INVALID");
4497 goto done;
4500 done:
4501 return (XT_CB_PASSTHROUGH);
4505 script_cmd(struct tab *t, struct karg *args)
4507 JSGlobalContextRef ctx;
4508 WebKitWebFrame *frame;
4509 JSStringRef str;
4510 JSValueRef val, exception;
4511 char *es;
4512 struct stat sb;
4513 FILE *f = NULL;
4514 char *buf = NULL;
4516 if (t == NULL)
4517 goto done;
4519 if ((f = fopen(args->s, "r")) == NULL) {
4520 show_oops(t, "Can't open script file: %s", args->s);
4521 goto done;
4524 if (fstat(fileno(f), &sb) == -1) {
4525 show_oops(t, "Can't stat script file: %s", args->s);
4526 goto done;
4529 buf = g_malloc0(sb.st_size + 1);
4530 if (fread(buf, 1, sb.st_size, f) != sb.st_size) {
4531 show_oops(t, "Can't read script file: %s", args->s);
4532 goto done;
4535 /* this code needs to be redone */
4536 frame = webkit_web_view_get_main_frame(t->wv);
4537 ctx = webkit_web_frame_get_global_context(frame);
4539 str = JSStringCreateWithUTF8CString(buf);
4540 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
4541 NULL, 0, &exception);
4542 JSStringRelease(str);
4544 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
4545 if (val == NULL) {
4546 es = js_ref_to_string(ctx, exception);
4547 if (es) {
4548 show_oops(t, "script exception: %s", es);
4549 g_free(es);
4551 goto done;
4552 } else {
4553 es = js_ref_to_string(ctx, val);
4554 #if 0
4555 /* return values */
4556 if (!strncmp(es, XT_JS_DONE, XT_JS_DONE_LEN))
4557 ; /* do nothing */
4558 if (!strncmp(es, XT_JS_INSERT, XT_JS_INSERT_LEN))
4559 ; /* do nothing */
4560 #endif
4561 if (es) {
4562 show_oops(t, "script complete return value: '%s'", es);
4563 g_free(es);
4564 } else
4565 show_oops(t, "script complete: without a return value");
4568 done:
4569 if (f)
4570 fclose(f);
4571 if (buf)
4572 g_free(buf);
4574 return (XT_CB_PASSTHROUGH);
4578 * Make a hardcopy of the page
4581 print_page(struct tab *t, struct karg *args)
4583 WebKitWebFrame *frame;
4584 GtkPageSetup *ps;
4585 GtkPrintOperation *op;
4586 GtkPrintOperationAction action;
4587 GtkPrintOperationResult print_res;
4588 GError *g_err = NULL;
4589 int marg_l, marg_r, marg_t, marg_b;
4591 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4593 ps = gtk_page_setup_new();
4594 op = gtk_print_operation_new();
4595 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4596 frame = webkit_web_view_get_main_frame(t->wv);
4598 /* the default margins are too small, so we will bump them */
4599 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4600 XT_PRINT_EXTRA_MARGIN;
4601 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4602 XT_PRINT_EXTRA_MARGIN;
4603 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4604 XT_PRINT_EXTRA_MARGIN;
4605 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4606 XT_PRINT_EXTRA_MARGIN;
4608 /* set margins */
4609 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4610 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4611 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4612 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4614 gtk_print_operation_set_default_page_setup(op, ps);
4616 /* this appears to free 'op' and 'ps' */
4617 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4619 /* check it worked */
4620 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4621 show_oops(NULL, "can't print: %s", g_err->message);
4622 g_error_free (g_err);
4623 return (1);
4626 return (0);
4630 go_home(struct tab *t, struct karg *args)
4632 load_uri(t, home);
4633 return (0);
4637 set_encoding(struct tab *t, struct karg *args)
4639 const gchar *e;
4641 if (args->s && strlen(g_strstrip(args->s)) == 0) {
4642 e = webkit_web_view_get_custom_encoding(t->wv);
4643 if (e == NULL)
4644 e = webkit_web_view_get_encoding(t->wv);
4645 show_oops(t, "encoding: %s", e ? e : "N/A");
4646 } else
4647 webkit_web_view_set_custom_encoding(t->wv, args->s);
4649 return (0);
4653 restart(struct tab *t, struct karg *args)
4655 struct karg a;
4657 a.s = XT_RESTART_TABS_FILE;
4658 save_tabs(t, &a);
4659 execvp(start_argv[0], start_argv);
4660 /* NOTREACHED */
4662 return (0);
4665 #define CTRL GDK_CONTROL_MASK
4666 #define MOD1 GDK_MOD1_MASK
4667 #define SHFT GDK_SHIFT_MASK
4669 /* inherent to GTK not all keys will be caught at all times */
4670 /* XXX sort key bindings */
4671 struct key_binding {
4672 char *cmd;
4673 guint mask;
4674 guint use_in_entry;
4675 guint key;
4676 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4677 } keys[] = {
4678 { "command_mode", 0, 0, GDK_Escape },
4679 { "insert_mode", 0, 0, GDK_i },
4680 { "cookiejar", MOD1, 0, GDK_j },
4681 { "downloadmgr", MOD1, 0, GDK_d },
4682 { "history", MOD1, 0, GDK_h },
4683 { "print", CTRL, 0, GDK_p },
4684 { "search", 0, 0, GDK_slash },
4685 { "searchb", 0, 0, GDK_question },
4686 { "statustoggle", CTRL, 0, GDK_n },
4687 { "command", 0, 0, GDK_colon },
4688 { "qa", CTRL, 0, GDK_q },
4689 { "restart", MOD1, 0, GDK_q },
4690 { "js toggle", CTRL, 0, GDK_j },
4691 { "cookie toggle", MOD1, 0, GDK_c },
4692 { "togglesrc", CTRL, 0, GDK_s },
4693 { "yankuri", 0, 0, GDK_y },
4694 { "pasteuricur", 0, 0, GDK_p },
4695 { "pasteurinew", 0, 0, GDK_P },
4696 { "toplevel toggle", 0, 0, GDK_F4 },
4697 { "help", 0, 0, GDK_F1 },
4698 { "run_script", MOD1, 0, GDK_r },
4700 /* search */
4701 { "searchnext", 0, 0, GDK_n },
4702 { "searchprevious", 0, 0, GDK_N },
4704 /* focus */
4705 { "focusaddress", 0, 0, GDK_F6 },
4706 { "focussearch", 0, 0, GDK_F7 },
4708 /* hinting */
4709 { "hinting", 0, 0, GDK_f },
4710 { "hinting", 0, 0, GDK_period },
4711 { "hinting_newtab", SHFT, 0, GDK_F },
4712 { "hinting_newtab", 0, 0, GDK_comma },
4714 /* custom stylesheet */
4715 { "userstyle", 0, 0, GDK_s },
4717 /* navigation */
4718 { "goback", 0, 0, GDK_BackSpace },
4719 { "goback", MOD1, 0, GDK_Left },
4720 { "goforward", SHFT, 0, GDK_BackSpace },
4721 { "goforward", MOD1, 0, GDK_Right },
4722 { "reload", 0, 0, GDK_F5 },
4723 { "reload", CTRL, 0, GDK_r },
4724 { "reload", CTRL, 0, GDK_l },
4725 { "favorites", MOD1, 1, GDK_f },
4727 /* vertical movement */
4728 { "scrolldown", 0, 0, GDK_j },
4729 { "scrolldown", 0, 0, GDK_Down },
4730 { "scrollup", 0, 0, GDK_Up },
4731 { "scrollup", 0, 0, GDK_k },
4732 { "scrollbottom", 0, 0, GDK_G },
4733 { "scrollbottom", 0, 0, GDK_End },
4734 { "scrolltop", 0, 0, GDK_Home },
4735 { "scrollpagedown", 0, 0, GDK_space },
4736 { "scrollpagedown", CTRL, 0, GDK_f },
4737 { "scrollhalfdown", CTRL, 0, GDK_d },
4738 { "scrollpagedown", 0, 0, GDK_Page_Down },
4739 { "scrollpageup", 0, 0, GDK_Page_Up },
4740 { "scrollpageup", CTRL, 0, GDK_b },
4741 { "scrollhalfup", CTRL, 0, GDK_u },
4742 /* horizontal movement */
4743 { "scrollright", 0, 0, GDK_l },
4744 { "scrollright", 0, 0, GDK_Right },
4745 { "scrollleft", 0, 0, GDK_Left },
4746 { "scrollleft", 0, 0, GDK_h },
4747 { "scrollfarright", 0, 0, GDK_dollar },
4748 { "scrollfarleft", 0, 0, GDK_0 },
4750 /* tabs */
4751 { "tabnew", CTRL, 0, GDK_t },
4752 { "999tabnew", CTRL, 0, GDK_T },
4753 { "tabclose", CTRL, 1, GDK_w },
4754 { "tabundoclose", 0, 0, GDK_U },
4755 { "tabnext 1", CTRL, 0, GDK_1 },
4756 { "tabnext 2", CTRL, 0, GDK_2 },
4757 { "tabnext 3", CTRL, 0, GDK_3 },
4758 { "tabnext 4", CTRL, 0, GDK_4 },
4759 { "tabnext 5", CTRL, 0, GDK_5 },
4760 { "tabnext 6", CTRL, 0, GDK_6 },
4761 { "tabnext 7", CTRL, 0, GDK_7 },
4762 { "tabnext 8", CTRL, 0, GDK_8 },
4763 { "tabnext 9", CTRL, 0, GDK_9 },
4764 { "tabfirst", CTRL, 0, GDK_less },
4765 { "tablast", CTRL, 0, GDK_greater },
4766 { "tabprevious", CTRL, 0, GDK_Left },
4767 { "tabnext", CTRL, 0, GDK_Right },
4768 { "focusout", CTRL, 0, GDK_minus },
4769 { "focusin", CTRL, 0, GDK_plus },
4770 { "focusin", CTRL, 0, GDK_equal },
4771 { "focusreset", CTRL, 0, GDK_0 },
4773 /* command aliases (handy when -S flag is used) */
4774 { "promptopen", 0, 0, GDK_F9 },
4775 { "promptopencurrent", 0, 0, GDK_F10 },
4776 { "prompttabnew", 0, 0, GDK_F11 },
4777 { "prompttabnewcurrent",0, 0, GDK_F12 },
4779 TAILQ_HEAD(keybinding_list, key_binding);
4781 void
4782 walk_kb(struct settings *s,
4783 void (*cb)(struct settings *, char *, void *), void *cb_args)
4785 struct key_binding *k;
4786 char str[1024];
4788 if (s == NULL || cb == NULL) {
4789 show_oops(NULL, "walk_kb invalid parameters");
4790 return;
4793 TAILQ_FOREACH(k, &kbl, entry) {
4794 if (k->cmd == NULL)
4795 continue;
4796 str[0] = '\0';
4798 /* sanity */
4799 if (gdk_keyval_name(k->key) == NULL)
4800 continue;
4802 strlcat(str, k->cmd, sizeof str);
4803 strlcat(str, ",", sizeof str);
4805 if (k->mask & GDK_SHIFT_MASK)
4806 strlcat(str, "S-", sizeof str);
4807 if (k->mask & GDK_CONTROL_MASK)
4808 strlcat(str, "C-", sizeof str);
4809 if (k->mask & GDK_MOD1_MASK)
4810 strlcat(str, "M1-", sizeof str);
4811 if (k->mask & GDK_MOD2_MASK)
4812 strlcat(str, "M2-", sizeof str);
4813 if (k->mask & GDK_MOD3_MASK)
4814 strlcat(str, "M3-", sizeof str);
4815 if (k->mask & GDK_MOD4_MASK)
4816 strlcat(str, "M4-", sizeof str);
4817 if (k->mask & GDK_MOD5_MASK)
4818 strlcat(str, "M5-", sizeof str);
4820 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4821 cb(s, str, cb_args);
4825 void
4826 init_keybindings(void)
4828 int i;
4829 struct key_binding *k;
4831 for (i = 0; i < LENGTH(keys); i++) {
4832 k = g_malloc0(sizeof *k);
4833 k->cmd = keys[i].cmd;
4834 k->mask = keys[i].mask;
4835 k->use_in_entry = keys[i].use_in_entry;
4836 k->key = keys[i].key;
4837 TAILQ_INSERT_HEAD(&kbl, k, entry);
4839 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4840 k->cmd ? k->cmd : "unnamed key");
4844 void
4845 keybinding_clearall(void)
4847 struct key_binding *k, *next;
4849 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4850 next = TAILQ_NEXT(k, entry);
4851 if (k->cmd == NULL)
4852 continue;
4854 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4855 k->cmd ? k->cmd : "unnamed key");
4856 TAILQ_REMOVE(&kbl, k, entry);
4857 g_free(k);
4862 keybinding_add(char *cmd, char *key, int use_in_entry)
4864 struct key_binding *k;
4865 guint keyval, mask = 0;
4866 int i;
4868 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
4870 /* Keys which are to be used in entry have been prefixed with an
4871 * exclamation mark. */
4872 if (use_in_entry)
4873 key++;
4875 /* find modifier keys */
4876 if (strstr(key, "S-"))
4877 mask |= GDK_SHIFT_MASK;
4878 if (strstr(key, "C-"))
4879 mask |= GDK_CONTROL_MASK;
4880 if (strstr(key, "M1-"))
4881 mask |= GDK_MOD1_MASK;
4882 if (strstr(key, "M2-"))
4883 mask |= GDK_MOD2_MASK;
4884 if (strstr(key, "M3-"))
4885 mask |= GDK_MOD3_MASK;
4886 if (strstr(key, "M4-"))
4887 mask |= GDK_MOD4_MASK;
4888 if (strstr(key, "M5-"))
4889 mask |= GDK_MOD5_MASK;
4891 /* find keyname */
4892 for (i = strlen(key) - 1; i > 0; i--)
4893 if (key[i] == '-')
4894 key = &key[i + 1];
4896 /* validate keyname */
4897 keyval = gdk_keyval_from_name(key);
4898 if (keyval == GDK_VoidSymbol) {
4899 warnx("invalid keybinding name %s", key);
4900 return (1);
4902 /* must run this test too, gtk+ doesn't handle 10 for example */
4903 if (gdk_keyval_name(keyval) == NULL) {
4904 warnx("invalid keybinding name %s", key);
4905 return (1);
4908 /* Remove eventual dupes. */
4909 TAILQ_FOREACH(k, &kbl, entry)
4910 if (k->key == keyval && k->mask == mask) {
4911 TAILQ_REMOVE(&kbl, k, entry);
4912 g_free(k);
4913 break;
4916 /* add keyname */
4917 k = g_malloc0(sizeof *k);
4918 k->cmd = g_strdup(cmd);
4919 k->mask = mask;
4920 k->use_in_entry = use_in_entry;
4921 k->key = keyval;
4923 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
4924 k->cmd,
4925 k->mask,
4926 k->use_in_entry,
4927 k->key);
4928 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
4929 k->cmd, gdk_keyval_name(keyval));
4931 TAILQ_INSERT_HEAD(&kbl, k, entry);
4933 return (0);
4937 add_kb(struct settings *s, char *entry)
4939 char *kb, *key;
4941 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
4943 /* clearall is special */
4944 if (!strcmp(entry, "clearall")) {
4945 keybinding_clearall();
4946 return (0);
4949 kb = strstr(entry, ",");
4950 if (kb == NULL)
4951 return (1);
4952 *kb = '\0';
4953 key = kb + 1;
4955 return (keybinding_add(entry, key, key[0] == '!'));
4958 struct cmd {
4959 char *cmd;
4960 int level;
4961 int (*func)(struct tab *, struct karg *);
4962 int arg;
4963 int type;
4964 } cmds[] = {
4965 { "command_mode", 0, command_mode, XT_MODE_COMMAND, 0 },
4966 { "insert_mode", 0, command_mode, XT_MODE_INSERT, 0 },
4967 { "command", 0, command, ':', 0 },
4968 { "search", 0, command, '/', 0 },
4969 { "searchb", 0, command, '?', 0 },
4970 { "hinting", 0, command, '.', 0 },
4971 { "hinting_newtab", 0, command, ',', 0 },
4972 { "togglesrc", 0, toggle_src, 0, 0 },
4974 /* yanking and pasting */
4975 { "yankuri", 0, yank_uri, 0, 0 },
4976 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
4977 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
4978 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
4980 /* search */
4981 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
4982 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
4984 /* focus */
4985 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
4986 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
4988 /* hinting */
4989 { "hinting", 0, hint, 0, 0 },
4990 { "hinting_newtab", 0, hint, XT_HINT_NEWTAB, 0 },
4992 /* custom stylesheet */
4993 { "userstyle", 0, userstyle, 0, 0 },
4995 /* navigation */
4996 { "goback", 0, navaction, XT_NAV_BACK, 0 },
4997 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
4998 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5000 /* vertical movement */
5001 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5002 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5003 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5004 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5005 { "1", 0, move, XT_MOVE_TOP, 0 },
5006 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5007 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5008 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5009 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5010 /* horizontal movement */
5011 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5012 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5013 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5014 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5016 { "favorites", 0, xtp_page_fl, 0, 0 },
5017 { "fav", 0, xtp_page_fl, 0, 0 },
5018 { "favadd", 0, add_favorite, 0, 0 },
5020 { "qall", 0, quit, 0, 0 },
5021 { "quitall", 0, quit, 0, 0 },
5022 { "w", 0, save_tabs, 0, 0 },
5023 { "wq", 0, save_tabs_and_quit, 0, 0 },
5024 { "help", 0, help, 0, 0 },
5025 { "about", 0, about, 0, 0 },
5026 { "stats", 0, stats, 0, 0 },
5027 { "version", 0, about, 0, 0 },
5029 /* js command */
5030 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5031 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5032 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5033 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5034 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5035 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5036 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5037 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5038 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5039 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5040 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5042 /* cookie command */
5043 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5044 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5045 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5046 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5047 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5048 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5049 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5050 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5051 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5052 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5053 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5055 /* plugin command */
5056 { "plugin", 0, pl_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5057 { "save", 1, pl_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5058 { "domain", 2, pl_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5059 { "fqdn", 2, pl_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5060 { "show", 1, pl_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5061 { "all", 2, pl_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5062 { "persistent", 2, pl_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5063 { "session", 2, pl_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5064 { "toggle", 1, pl_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5065 { "domain", 2, pl_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5066 { "fqdn", 2, pl_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5068 /* toplevel (domain) command */
5069 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5070 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5072 /* cookie jar */
5073 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5075 /* cert command */
5076 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5077 { "save", 1, cert_cmd, XT_SAVE, 0 },
5078 { "show", 1, cert_cmd, XT_SHOW, 0 },
5080 { "ca", 0, ca_cmd, 0, 0 },
5081 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5082 { "dl", 0, xtp_page_dl, 0, 0 },
5083 { "h", 0, xtp_page_hl, 0, 0 },
5084 { "history", 0, xtp_page_hl, 0, 0 },
5085 { "home", 0, go_home, 0, 0 },
5086 { "restart", 0, restart, 0, 0 },
5087 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5088 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5089 { "statustoggle", 0, statustoggle, 0, 0 },
5090 { "run_script", 0, run_page_script, 0, XT_USERARG },
5092 { "print", 0, print_page, 0, 0 },
5094 /* tabs */
5095 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
5096 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
5097 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
5098 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5099 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5100 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5101 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5102 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5103 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5104 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5105 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5106 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5107 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5108 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
5109 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5110 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5111 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5112 { "tabs", 0, buffers, 0, 0 },
5113 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5114 { "buffers", 0, buffers, 0, 0 },
5115 { "ls", 0, buffers, 0, 0 },
5116 { "encoding", 0, set_encoding, 0, XT_USERARG },
5118 /* command aliases (handy when -S flag is used) */
5119 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5120 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5121 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5122 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5124 /* settings */
5125 { "set", 0, set, 0, XT_SETARG },
5127 { "fullscreen", 0, fullscreen, 0, 0 },
5128 { "f", 0, fullscreen, 0, 0 },
5130 /* sessions */
5131 { "session", 0, session_cmd, XT_SHOW, 0 },
5132 { "delete", 1, session_cmd, XT_DELETE, XT_SESSARG },
5133 { "open", 1, session_cmd, XT_OPEN, XT_SESSARG },
5134 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5135 { "show", 1, session_cmd, XT_SHOW, 0 },
5137 /* external javascript */
5138 { "script", 0, script_cmd, XT_EJS_SHOW, XT_USERARG },
5140 /* inspector */
5141 { "inspector", 0, inspector_cmd, XT_INS_SHOW, 0 },
5142 { "show", 1, inspector_cmd, XT_INS_SHOW, 0 },
5143 { "hide", 1, inspector_cmd, XT_INS_HIDE, 0 },
5146 struct {
5147 int index;
5148 int len;
5149 gchar *list[256];
5150 } cmd_status = {-1, 0};
5152 gboolean
5153 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5156 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
5157 btn_down = 0;
5159 return (FALSE);
5162 gboolean
5163 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5165 struct karg a;
5167 hide_oops(t);
5168 hide_buffers(t);
5170 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5171 btn_down = 1;
5172 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5173 /* go backward */
5174 a.i = XT_NAV_BACK;
5175 navaction(t, &a);
5177 return (TRUE);
5178 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5179 /* go forward */
5180 a.i = XT_NAV_FORWARD;
5181 navaction(t, &a);
5183 return (TRUE);
5186 return (FALSE);
5189 gboolean
5190 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5192 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5194 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5195 delete_tab(t);
5197 return (FALSE);
5202 void
5203 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5205 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5207 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5209 if (t == NULL) {
5210 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
5211 return;
5214 if (uri == NULL) {
5215 show_oops(t, "activate_uri_entry_cb no uri");
5216 return;
5219 uri += strspn(uri, "\t ");
5221 /* if xxxt:// treat specially */
5222 if (parse_xtp_url(t, uri))
5223 return;
5225 /* otherwise continue to load page normally */
5226 load_uri(t, (gchar *)uri);
5227 focus_webview(t);
5230 void
5231 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5233 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5234 char *newuri = NULL;
5235 gchar *enc_search;
5237 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5239 if (t == NULL) {
5240 show_oops(NULL, "activate_search_entry_cb invalid parameters");
5241 return;
5244 if (search_string == NULL) {
5245 show_oops(t, "no search_string");
5246 return;
5249 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5251 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5252 newuri = g_strdup_printf(search_string, enc_search);
5253 g_free(enc_search);
5255 marks_clear(t);
5256 webkit_web_view_load_uri(t->wv, newuri);
5257 focus_webview(t);
5259 if (newuri)
5260 g_free(newuri);
5263 void
5264 check_and_set_cookie(const gchar *uri, struct tab *t)
5266 struct domain *d = NULL;
5267 int es = 0;
5269 if (uri == NULL || t == NULL)
5270 return;
5272 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
5273 es = 0;
5274 else
5275 es = 1;
5277 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
5278 es ? "enable" : "disable", uri);
5280 g_object_set(G_OBJECT(t->settings),
5281 "enable-html5-local-storage", es, (char *)NULL);
5282 webkit_web_view_set_settings(t->wv, t->settings);
5285 void
5286 check_and_set_js(const gchar *uri, struct tab *t)
5288 struct domain *d = NULL;
5289 int es = 0;
5291 if (uri == NULL || t == NULL)
5292 return;
5294 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5295 es = 0;
5296 else
5297 es = 1;
5299 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5300 es ? "enable" : "disable", uri);
5302 g_object_set(G_OBJECT(t->settings),
5303 "enable-scripts", es, (char *)NULL);
5304 g_object_set(G_OBJECT(t->settings),
5305 "javascript-can-open-windows-automatically", es, (char *)NULL);
5306 webkit_web_view_set_settings(t->wv, t->settings);
5308 button_set_stockid(t->js_toggle,
5309 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5312 void
5313 check_and_set_pl(const gchar *uri, struct tab *t)
5315 struct domain *d = NULL;
5316 int es = 0;
5318 if (uri == NULL || t == NULL)
5319 return;
5321 if ((d = wl_find_uri(uri, &pl_wl)) == NULL)
5322 es = 0;
5323 else
5324 es = 1;
5326 DNPRINTF(XT_D_JS, "check_and_set_pl: %s %s\n",
5327 es ? "enable" : "disable", uri);
5329 g_object_set(G_OBJECT(t->settings),
5330 "enable-plugins", es, (char *)NULL);
5331 webkit_web_view_set_settings(t->wv, t->settings);
5334 void
5335 color_address_bar(gpointer p)
5337 GdkColor color;
5338 struct tab *tt, *t = p;
5339 gchar *col_str = XT_COLOR_WHITE;
5340 const gchar *uri, *u = NULL, *error_str = NULL;
5342 #ifdef USE_THREADS
5343 gdk_threads_enter();
5344 #endif
5345 DNPRINTF(XT_D_URL, "%s:\n", __func__);
5347 /* make sure t still exists */
5348 if (t == NULL)
5349 return;
5350 TAILQ_FOREACH(tt, &tabs, entry)
5351 if (t == tt)
5352 break;
5353 if (t != tt)
5354 goto done;
5356 if ((uri = get_uri(t)) == NULL)
5357 goto white;
5358 u = g_strdup(uri);
5360 #ifdef USE_THREADS
5361 gdk_threads_leave();
5362 #endif
5364 col_str = XT_COLOR_YELLOW;
5365 switch (load_compare_cert(u, &error_str)) {
5366 case CERT_LOCAL:
5367 col_str = XT_COLOR_BLUE;
5368 break;
5369 case CERT_TRUSTED:
5370 col_str = XT_COLOR_GREEN;
5371 break;
5372 case CERT_UNTRUSTED:
5373 col_str = XT_COLOR_YELLOW;
5374 break;
5375 case CERT_BAD:
5376 col_str = XT_COLOR_RED;
5377 break;
5380 #ifdef USE_THREADS
5381 gdk_threads_enter();
5382 #endif
5383 /* make sure t isn't deleted */
5384 TAILQ_FOREACH(tt, &tabs, entry)
5385 if (t == tt)
5386 break;
5387 if (t != tt)
5388 goto done;
5390 #ifdef USE_THREADS
5391 /* test to see if the user navigated away and canceled the thread */
5392 if (t->thread != g_thread_self())
5393 goto done;
5394 if ((uri = get_uri(t)) == NULL) {
5395 t->thread = NULL;
5396 goto done;
5398 if (strcmp(uri, u)) {
5399 /* make sure we are still the same url */
5400 t->thread = NULL;
5401 goto done;
5403 #endif
5404 white:
5405 gdk_color_parse(col_str, &color);
5406 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5408 if (!strcmp(col_str, XT_COLOR_WHITE))
5409 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
5410 else
5411 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
5413 if (error_str && error_str[0] != '\0')
5414 show_oops(t, "%s", error_str);
5415 #ifdef USE_THREADS
5416 t->thread = NULL;
5417 #endif
5418 done:
5419 /* t is invalid at this point */
5420 if (u)
5421 g_free((gpointer)u);
5422 #ifdef USE_THREADS
5423 gdk_threads_leave();
5424 #endif
5427 void
5428 show_ca_status(struct tab *t, const char *uri)
5430 GdkColor color;
5431 gchar *col_str = XT_COLOR_WHITE;
5433 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5434 ssl_strict_certs, ssl_ca_file, uri);
5436 if (t == NULL)
5437 return;
5439 if (uri == NULL)
5440 goto done;
5441 if (ssl_ca_file == NULL) {
5442 if (g_str_has_prefix(uri, "http://"))
5443 goto done;
5444 if (g_str_has_prefix(uri, "https://")) {
5445 col_str = XT_COLOR_RED;
5446 goto done;
5448 return;
5450 if (g_str_has_prefix(uri, "http://") ||
5451 !g_str_has_prefix(uri, "https://"))
5452 goto done;
5453 #ifdef USE_THREADS
5455 * It is not necessary to see if the thread is already running.
5456 * If the thread is in progress setting it to something else aborts it
5457 * on the way out.
5460 /* thread the coloring of the address bar */
5461 t->thread = g_thread_create((GThreadFunc)color_address_bar, t, TRUE, NULL);
5462 #else
5463 color_address_bar(t);
5464 #endif
5465 return;
5467 done:
5468 if (col_str) {
5469 gdk_color_parse(col_str, &color);
5470 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5472 if (!strcmp(col_str, XT_COLOR_WHITE))
5473 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
5474 else
5475 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
5479 void
5480 free_favicon(struct tab *t)
5482 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
5483 __func__, t->icon_download, t->icon_request);
5485 if (t->icon_request)
5486 g_object_unref(t->icon_request);
5487 if (t->icon_dest_uri)
5488 g_free(t->icon_dest_uri);
5490 t->icon_request = NULL;
5491 t->icon_dest_uri = NULL;
5494 void
5495 xt_icon_from_name(struct tab *t, gchar *name)
5497 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5498 GTK_ENTRY_ICON_PRIMARY, "text-html");
5499 if (show_url == 0)
5500 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5501 GTK_ENTRY_ICON_PRIMARY, "text-html");
5502 else
5503 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5504 GTK_ENTRY_ICON_PRIMARY, NULL);
5507 void
5508 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
5510 GdkPixbuf *pb_scaled;
5512 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
5513 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
5514 GDK_INTERP_BILINEAR);
5515 else
5516 pb_scaled = pb;
5518 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5519 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5520 if (show_url == 0)
5521 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
5522 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5523 else
5524 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
5525 GTK_ENTRY_ICON_PRIMARY, NULL);
5527 if (pb_scaled != pb)
5528 g_object_unref(pb_scaled);
5531 void
5532 xt_icon_from_file(struct tab *t, char *file)
5534 GdkPixbuf *pb;
5536 if (g_str_has_prefix(file, "file://"))
5537 file += strlen("file://");
5539 pb = gdk_pixbuf_new_from_file(file, NULL);
5540 if (pb) {
5541 xt_icon_from_pixbuf(t, pb);
5542 g_object_unref(pb);
5543 } else
5544 xt_icon_from_name(t, "text-html");
5547 gboolean
5548 is_valid_icon(char *file)
5550 gboolean valid = 0;
5551 const char *mime_type;
5552 GFileInfo *fi;
5553 GFile *gf;
5555 gf = g_file_new_for_path(file);
5556 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5557 NULL, NULL);
5558 mime_type = g_file_info_get_content_type(fi);
5559 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5560 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5561 g_strcmp0(mime_type, "image/png") == 0 ||
5562 g_strcmp0(mime_type, "image/gif") == 0 ||
5563 g_strcmp0(mime_type, "application/octet-stream") == 0;
5564 g_object_unref(fi);
5565 g_object_unref(gf);
5567 return (valid);
5570 void
5571 set_favicon_from_file(struct tab *t, char *file)
5573 struct stat sb;
5575 if (t == NULL || file == NULL)
5576 return;
5578 if (g_str_has_prefix(file, "file://"))
5579 file += strlen("file://");
5580 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5582 if (!stat(file, &sb)) {
5583 if (sb.st_size == 0 || !is_valid_icon(file)) {
5584 /* corrupt icon so trash it */
5585 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5586 __func__, file);
5587 unlink(file);
5588 /* no need to set icon to default here */
5589 return;
5592 xt_icon_from_file(t, file);
5595 void
5596 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5597 WebKitWebView *wv)
5599 WebKitDownloadStatus status = webkit_download_get_status(download);
5600 struct tab *tt = NULL, *t = NULL;
5603 * find the webview instead of passing in the tab as it could have been
5604 * deleted from underneath us.
5606 TAILQ_FOREACH(tt, &tabs, entry) {
5607 if (tt->wv == wv) {
5608 t = tt;
5609 break;
5612 if (t == NULL)
5613 return;
5615 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5616 __func__, t->tab_id, status);
5618 switch (status) {
5619 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5620 /* -1 */
5621 t->icon_download = NULL;
5622 free_favicon(t);
5623 break;
5624 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5625 /* 0 */
5626 break;
5627 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5628 /* 1 */
5629 break;
5630 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5631 /* 2 */
5632 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5633 __func__, t->tab_id);
5634 t->icon_download = NULL;
5635 free_favicon(t);
5636 break;
5637 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5638 /* 3 */
5640 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5641 __func__, t->icon_dest_uri);
5642 set_favicon_from_file(t, t->icon_dest_uri);
5643 /* these will be freed post callback */
5644 t->icon_request = NULL;
5645 t->icon_download = NULL;
5646 break;
5647 default:
5648 break;
5652 void
5653 abort_favicon_download(struct tab *t)
5655 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5657 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
5658 if (t->icon_download) {
5659 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5660 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5661 webkit_download_cancel(t->icon_download);
5662 t->icon_download = NULL;
5663 } else
5664 free_favicon(t);
5665 #endif
5667 xt_icon_from_name(t, "text-html");
5670 void
5671 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5673 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
5675 if (uri == NULL || t == NULL)
5676 return;
5678 #if WEBKIT_CHECK_VERSION(1, 4, 0)
5679 /* take icon from WebKitIconDatabase */
5680 GdkPixbuf *pb;
5682 pb = webkit_web_view_get_icon_pixbuf(wv);
5683 if (pb) {
5684 xt_icon_from_pixbuf(t, pb);
5685 g_object_unref(pb);
5686 } else
5687 xt_icon_from_name(t, "text-html");
5688 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
5689 /* download icon to cache dir */
5690 gchar *name_hash, file[PATH_MAX];
5691 struct stat sb;
5693 if (t->icon_request) {
5694 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5695 return;
5698 /* check to see if we got the icon in cache */
5699 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5700 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5701 g_free(name_hash);
5703 if (!stat(file, &sb)) {
5704 if (sb.st_size > 0) {
5705 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5706 __func__, file);
5707 set_favicon_from_file(t, file);
5708 return;
5711 /* corrupt icon so trash it */
5712 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5713 __func__, file);
5714 unlink(file);
5717 /* create download for icon */
5718 t->icon_request = webkit_network_request_new(uri);
5719 if (t->icon_request == NULL) {
5720 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5721 __func__, uri);
5722 return;
5725 t->icon_download = webkit_download_new(t->icon_request);
5726 if (t->icon_download == NULL)
5727 return;
5729 /* we have to free icon_dest_uri later */
5730 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5731 webkit_download_set_destination_uri(t->icon_download,
5732 t->icon_dest_uri);
5734 if (webkit_download_get_status(t->icon_download) ==
5735 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5736 g_object_unref(t->icon_request);
5737 g_free(t->icon_dest_uri);
5738 t->icon_request = NULL;
5739 t->icon_dest_uri = NULL;
5740 return;
5743 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5744 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5746 webkit_download_start(t->icon_download);
5747 #endif
5750 void
5751 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5753 const gchar *uri = NULL, *title = NULL;
5754 struct history *h, find;
5755 struct karg a;
5756 GdkColor color;
5758 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
5759 webkit_web_view_get_load_status(wview),
5760 get_uri(t) ? get_uri(t) : "NOTHING");
5762 if (t == NULL) {
5763 show_oops(NULL, "notify_load_status_cb invalid parameters");
5764 return;
5767 switch (webkit_web_view_get_load_status(wview)) {
5768 case WEBKIT_LOAD_PROVISIONAL:
5769 /* 0 */
5770 abort_favicon_download(t);
5771 #if GTK_CHECK_VERSION(2, 20, 0)
5772 gtk_widget_show(t->spinner);
5773 gtk_spinner_start(GTK_SPINNER(t->spinner));
5774 #endif
5775 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5777 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5779 /* assume we are a new address */
5780 gdk_color_parse("white", &color);
5781 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5782 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
5784 /* take focus if we are visible */
5785 focus_webview(t);
5786 t->focus_wv = 1;
5788 marks_clear(t);
5789 #ifdef USE_THREAD
5790 /* kill color thread */
5791 t->thread = NULL;
5792 #endif
5793 break;
5795 case WEBKIT_LOAD_COMMITTED:
5796 /* 1 */
5797 uri = get_uri(t);
5798 if (uri == NULL)
5799 return;
5800 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5802 if (t->status) {
5803 g_free(t->status);
5804 t->status = NULL;
5806 set_status(t, (char *)uri, XT_STATUS_LOADING);
5808 /* check if js white listing is enabled */
5809 if (enable_plugin_whitelist)
5810 check_and_set_pl(uri, t);
5811 if (enable_cookie_whitelist)
5812 check_and_set_cookie(uri, t);
5813 if (enable_js_whitelist)
5814 check_and_set_js(uri, t);
5816 if (t->styled)
5817 apply_style(t);
5820 /* we know enough to autosave the session */
5821 if (session_autosave) {
5822 a.s = NULL;
5823 save_tabs(t, &a);
5826 show_ca_status(t, uri);
5827 break;
5829 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5830 /* 3 */
5831 break;
5833 case WEBKIT_LOAD_FINISHED:
5834 /* 2 */
5835 uri = get_uri(t);
5836 if (uri == NULL)
5837 return;
5839 if (!strncmp(uri, "http://", strlen("http://")) ||
5840 !strncmp(uri, "https://", strlen("https://")) ||
5841 !strncmp(uri, "file://", strlen("file://"))) {
5842 find.uri = uri;
5843 h = RB_FIND(history_list, &hl, &find);
5844 if (!h) {
5845 title = get_title(t, FALSE);
5846 h = g_malloc(sizeof *h);
5847 h->uri = g_strdup(uri);
5848 h->title = g_strdup(title);
5849 RB_INSERT(history_list, &hl, h);
5850 completion_add_uri(h->uri);
5851 update_history_tabs(NULL);
5855 set_status(t, (char *)uri, XT_STATUS_URI);
5856 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5857 case WEBKIT_LOAD_FAILED:
5858 /* 4 */
5859 #endif
5860 #if GTK_CHECK_VERSION(2, 20, 0)
5861 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5862 gtk_widget_hide(t->spinner);
5863 #endif
5864 default:
5865 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
5866 break;
5869 if (t->item)
5870 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
5871 else
5872 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
5873 can_go_back_for_real(t));
5875 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
5876 can_go_forward_for_real(t));
5879 void
5880 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5882 const gchar *title = NULL, *win_title = NULL;
5884 title = get_title(t, FALSE);
5885 win_title = get_title(t, TRUE);
5886 gtk_label_set_text(GTK_LABEL(t->label), title);
5887 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
5888 if (t->tab_id == gtk_notebook_get_current_page(notebook))
5889 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
5892 void
5893 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5895 run_script(t, JS_HINTING);
5896 if (autofocus_onload &&
5897 t->tab_id == gtk_notebook_get_current_page(notebook))
5898 run_script(t, "hints.focusInput();");
5899 else
5900 run_script(t, "hints.clearFocus();");
5903 void
5904 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
5906 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
5907 progress == 100 ? 0 : (double)progress / 100);
5908 if (show_url == 0) {
5909 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
5910 progress == 100 ? 0 : (double)progress / 100);
5913 update_statusbar_position(NULL, NULL);
5917 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5918 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5919 WebKitWebPolicyDecision *pd, struct tab *t)
5921 char *uri;
5922 WebKitWebNavigationReason reason;
5923 struct domain *d = NULL;
5925 if (t == NULL) {
5926 show_oops(NULL, "webview_npd_cb invalid parameters");
5927 return (FALSE);
5930 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
5931 t->ctrl_click,
5932 webkit_network_request_get_uri(request));
5934 uri = (char *)webkit_network_request_get_uri(request);
5936 /* if this is an xtp url, we don't load anything else */
5937 if (parse_xtp_url(t, uri))
5938 return (TRUE);
5940 if ((t->hints_on && t->new_tab) || t->ctrl_click) {
5941 t->ctrl_click = 0;
5942 create_new_tab(uri, NULL, ctrl_click_focus, -1);
5943 webkit_web_policy_decision_ignore(pd);
5944 return (TRUE); /* we made the decission */
5948 * This is a little hairy but it comes down to this:
5949 * when we run in whitelist mode we have to assist the browser in
5950 * opening the URL that it would have opened in a new tab.
5952 reason = webkit_web_navigation_action_get_reason(na);
5953 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
5954 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5955 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
5956 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
5957 load_uri(t, uri);
5958 webkit_web_policy_decision_use(pd);
5959 return (TRUE); /* we made the decision */
5962 return (FALSE);
5965 WebKitWebView *
5966 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5968 struct tab *tt;
5969 struct domain *d = NULL;
5970 const gchar *uri;
5971 WebKitWebView *webview = NULL;
5973 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
5974 webkit_web_view_get_uri(wv));
5976 if (tabless) {
5977 /* open in current tab */
5978 webview = t->wv;
5979 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
5980 uri = webkit_web_view_get_uri(wv);
5981 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
5982 return (NULL);
5984 tt = create_new_tab(NULL, NULL, 1, -1);
5985 webview = tt->wv;
5986 } else if (enable_scripts == 1) {
5987 tt = create_new_tab(NULL, NULL, 1, -1);
5988 webview = tt->wv;
5991 return (webview);
5994 gboolean
5995 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
5997 const gchar *uri;
5998 struct domain *d = NULL;
6000 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6002 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6003 uri = webkit_web_view_get_uri(wv);
6004 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6005 return (FALSE);
6007 delete_tab(t);
6008 } else if (enable_scripts == 1)
6009 delete_tab(t);
6011 return (TRUE);
6015 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6017 /* we can not eat the event without throwing gtk off so defer it */
6019 /* catch middle click */
6020 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6021 t->ctrl_click = 1;
6022 goto done;
6025 /* catch ctrl click */
6026 if (e->type == GDK_BUTTON_RELEASE &&
6027 CLEAN(e->state) == GDK_CONTROL_MASK)
6028 t->ctrl_click = 1;
6029 else
6030 t->ctrl_click = 0;
6031 done:
6032 return (XT_CB_PASSTHROUGH);
6036 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6038 struct mime_type *m;
6040 m = find_mime_type(mime_type);
6041 if (m == NULL)
6042 return (1);
6043 if (m->mt_download)
6044 return (1);
6046 switch (fork()) {
6047 case -1:
6048 show_oops(t, "can't fork mime handler");
6049 return (1);
6050 /* NOTREACHED */
6051 case 0:
6052 break;
6053 default:
6054 return (0);
6057 /* child */
6058 execlp(m->mt_action, m->mt_action,
6059 webkit_network_request_get_uri(request), (void *)NULL);
6061 _exit(0);
6063 /* NOTREACHED */
6064 return (0);
6067 char *
6068 get_mime_type(const char *file)
6070 const gchar *m;
6071 char *mime_type = NULL;
6072 GFileInfo *fi;
6073 GFile *gf;
6075 if (g_str_has_prefix(file, "file://"))
6076 file += strlen("file://");
6078 gf = g_file_new_for_path(file);
6079 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6080 NULL, NULL);
6081 if ((m = g_file_info_get_content_type(fi)) != NULL)
6082 mime_type = g_strdup(m);
6083 g_object_unref(fi);
6084 g_object_unref(gf);
6086 return (mime_type);
6090 run_download_mimehandler(char *mime_type, char *file)
6092 struct mime_type *m;
6094 m = find_mime_type(mime_type);
6095 if (m == NULL)
6096 return (1);
6098 switch (fork()) {
6099 case -1:
6100 show_oops(NULL, "can't fork download mime handler");
6101 return (1);
6102 /* NOTREACHED */
6103 case 0:
6104 break;
6105 default:
6106 return (0);
6109 /* child */
6110 if (g_str_has_prefix(file, "file://"))
6111 file += strlen("file://");
6112 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6114 _exit(0);
6116 /* NOTREACHED */
6117 return (0);
6120 void
6121 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6122 WebKitWebView *wv)
6124 WebKitDownloadStatus status;
6125 const char *file = NULL;
6126 char *mime = NULL;
6128 if (download == NULL)
6129 return;
6130 status = webkit_download_get_status(download);
6131 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6132 return;
6134 file = webkit_download_get_destination_uri(download);
6135 if (file == NULL)
6136 return;
6137 mime = get_mime_type(file);
6138 if (mime == NULL)
6139 return;
6141 run_download_mimehandler((char *)mime, (char *)file);
6142 g_free(mime);
6146 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6147 WebKitNetworkRequest *request, char *mime_type,
6148 WebKitWebPolicyDecision *decision, struct tab *t)
6150 if (t == NULL) {
6151 show_oops(NULL, "webview_mimetype_cb invalid parameters");
6152 return (FALSE);
6155 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6156 t->tab_id, mime_type);
6158 if (run_mimehandler(t, mime_type, request) == 0) {
6159 webkit_web_policy_decision_ignore(decision);
6160 focus_webview(t);
6161 return (TRUE);
6164 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6165 webkit_web_policy_decision_download(decision);
6166 return (TRUE);
6169 return (FALSE);
6173 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6174 struct tab *t)
6176 struct stat sb;
6177 const gchar *suggested_name;
6178 gchar *filename = NULL;
6179 char *uri = NULL;
6180 struct download *download_entry;
6181 int i, ret = TRUE;
6183 if (wk_download == NULL || t == NULL) {
6184 show_oops(NULL, "%s invalid parameters", __func__);
6185 return (FALSE);
6188 suggested_name = webkit_download_get_suggested_filename(wk_download);
6189 if (suggested_name == NULL)
6190 return (FALSE); /* abort download */
6192 i = 0;
6193 do {
6194 if (filename) {
6195 g_free(filename);
6196 filename = NULL;
6198 if (i) {
6199 g_free(uri);
6200 uri = NULL;
6201 filename = g_strdup_printf("%d%s", i, suggested_name);
6203 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
6204 filename : suggested_name);
6205 i++;
6206 } while (!stat(uri + strlen("file://"), &sb));
6208 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6209 "local %s\n", __func__, t->tab_id, filename, uri);
6211 webkit_download_set_destination_uri(wk_download, uri);
6213 if (webkit_download_get_status(wk_download) ==
6214 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6215 show_oops(t, "%s: download failed to start", __func__);
6216 ret = FALSE;
6217 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6218 } else {
6219 /* connect "download first" mime handler */
6220 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6221 G_CALLBACK(download_status_changed_cb), NULL);
6223 download_entry = g_malloc(sizeof(struct download));
6224 download_entry->download = wk_download;
6225 download_entry->tab = t;
6226 download_entry->id = next_download_id++;
6227 RB_INSERT(download_list, &downloads, download_entry);
6228 /* get from history */
6229 g_object_ref(wk_download);
6230 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6231 show_oops(t, "Download of '%s' started...",
6232 basename((char *)webkit_download_get_destination_uri(wk_download)));
6235 if (uri)
6236 g_free(uri);
6238 if (filename)
6239 g_free(filename);
6241 /* sync other download manager tabs */
6242 update_download_tabs(NULL);
6245 * NOTE: never redirect/render the current tab before this
6246 * function returns. This will cause the download to never start.
6248 return (ret); /* start download */
6251 void
6252 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6254 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6256 if (t == NULL) {
6257 show_oops(NULL, "webview_hover_cb");
6258 return;
6261 if (uri)
6262 set_status(t, uri, XT_STATUS_LINK);
6263 else {
6264 if (t->status)
6265 set_status(t, t->status, XT_STATUS_NOTHING);
6270 mark(struct tab *t, struct karg *arg)
6272 char mark;
6273 int index;
6275 mark = arg->s[1];
6276 if ((index = marktoindex(mark)) == -1)
6277 return (-1);
6279 if (arg->i == XT_MARK_SET)
6280 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
6281 else if (arg->i == XT_MARK_GOTO) {
6282 if (t->mark[index] == XT_INVALID_MARK) {
6283 show_oops(t, "mark '%c' does not exist", mark);
6284 return (-1);
6286 /* XXX t->mark[index] can be bigger than the maximum if ajax or
6287 something changes the document size */
6288 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
6291 return (0);
6294 void
6295 marks_clear(struct tab *t)
6297 int i;
6299 for (i = 0; i < LENGTH(t->mark); i++)
6300 t->mark[i] = XT_INVALID_MARK;
6304 qmarks_load(void)
6306 char file[PATH_MAX];
6307 char *line = NULL, *p;
6308 int index, i;
6309 FILE *f;
6310 size_t linelen;
6312 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
6313 if ((f = fopen(file, "r+")) == NULL) {
6314 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
6315 return (1);
6318 for (i = 1; ; i++) {
6319 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
6320 break;
6321 if (strlen(line) == 0 || line[0] == '#') {
6322 free(line);
6323 line = NULL;
6324 continue;
6327 p = strtok(line, " \t");
6329 if (p == NULL || strlen(p) != 1 ||
6330 (index = marktoindex(*p)) == -1) {
6331 warnx("corrupt quickmarks file, line %d", i);
6332 break;
6335 p = strtok(NULL, " \t");
6336 if (qmarks[index] != NULL)
6337 g_free(qmarks[index]);
6338 qmarks[index] = g_strdup(p);
6341 fclose(f);
6343 return (0);
6347 qmarks_save(void)
6349 char file[PATH_MAX];
6350 int i;
6351 FILE *f;
6353 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
6354 if ((f = fopen(file, "r+")) == NULL) {
6355 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
6356 return (1);
6359 for (i = 0; i < XT_NOMARKS; i++)
6360 if (qmarks[i] != NULL)
6361 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
6363 fclose(f);
6365 return (0);
6369 qmark(struct tab *t, struct karg *arg)
6371 char mark;
6372 int index;
6374 mark = arg->s[strlen(arg->s)-1];
6375 index = marktoindex(mark);
6376 if (index == -1)
6377 return (-1);
6379 switch (arg->i) {
6380 case XT_QMARK_SET:
6381 if (qmarks[index] != NULL)
6382 g_free(qmarks[index]);
6384 qmarks_load(); /* sync if multiple instances */
6385 qmarks[index] = g_strdup(get_uri(t));
6386 qmarks_save();
6387 break;
6388 case XT_QMARK_OPEN:
6389 if (qmarks[index] != NULL)
6390 load_uri(t, qmarks[index]);
6391 else {
6392 show_oops(t, "quickmark \"%c\" does not exist",
6393 mark);
6394 return (-1);
6396 break;
6397 case XT_QMARK_TAB:
6398 if (qmarks[index] != NULL)
6399 create_new_tab(qmarks[index], NULL, 1, -1);
6400 else {
6401 show_oops(t, "quickmark \"%c\" does not exist",
6402 mark);
6403 return (-1);
6405 break;
6408 return (0);
6412 go_up(struct tab *t, struct karg *args)
6414 int levels;
6415 char *uri;
6416 char *tmp;
6418 levels = atoi(args->s);
6419 if (levels == 0)
6420 levels = 1;
6422 uri = g_strdup(webkit_web_view_get_uri(t->wv));
6423 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
6424 return (1);
6425 tmp += strlen(XT_PROTO_DELIM);
6427 /* if an uri starts with a slash, leave it alone (for file:///) */
6428 if (tmp[0] == '/')
6429 tmp++;
6431 while (levels--) {
6432 char *p;
6434 p = strrchr(tmp, '/');
6435 if (p != NULL)
6436 *p = '\0';
6437 else
6438 break;
6441 load_uri(t, uri);
6442 g_free(uri);
6444 return (0);
6448 gototab(struct tab *t, struct karg *args)
6450 int tab;
6451 struct karg arg = {0, NULL, -1};
6453 tab = atoi(args->s);
6455 arg.i = XT_TAB_NEXT;
6456 arg.precount = tab;
6458 movetab(t, &arg);
6460 return (0);
6464 zoom_amount(struct tab *t, struct karg *arg)
6466 struct karg narg = {0, NULL, -1};
6468 narg.i = atoi(arg->s);
6469 resizetab(t, &narg);
6471 return (0);
6475 flip_colon(struct tab *t, struct karg *arg)
6477 struct karg narg = {0, NULL, -1};
6478 char *p;
6480 if (t == NULL || arg == NULL)
6481 return (1);
6483 p = strstr(arg->s, ":");
6484 if (p == NULL)
6485 return (1);
6486 *p = '\0';
6488 narg.i = ':';
6489 narg.s = arg->s;
6490 command(t, &narg);
6492 return (0);
6495 /* buffer commands receive the regex that triggered them in arg.s */
6496 char bcmd[XT_BUFCMD_SZ];
6497 struct buffercmd {
6498 char *regex;
6499 int precount;
6500 #define XT_PRE_NO (0)
6501 #define XT_PRE_YES (1)
6502 #define XT_PRE_MAYBE (2)
6503 char *cmd;
6504 int (*func)(struct tab *, struct karg *);
6505 int arg;
6506 regex_t cregex;
6507 } buffercmds[] = {
6508 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
6509 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
6510 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
6511 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
6512 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
6513 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
6514 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
6515 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
6516 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
6517 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
6518 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
6519 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
6520 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
6521 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
6522 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
6523 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
6524 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
6525 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
6528 void
6529 buffercmd_init(void)
6531 int i;
6533 for (i = 0; i < LENGTH(buffercmds); i++)
6534 if (regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
6535 REG_EXTENDED | REG_NOSUB))
6536 startpage_add("invalid buffercmd regex %s",
6537 buffercmds[i].regex);
6540 void
6541 buffercmd_abort(struct tab *t)
6543 int i;
6545 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
6546 for (i = 0; i < LENGTH(bcmd); i++)
6547 bcmd[i] = '\0';
6549 cmd_prefix = 0; /* clear prefix for non-buffer commands */
6550 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
6553 void
6554 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
6556 struct karg arg = {0, NULL, -1};
6558 arg.i = cmd->arg;
6559 arg.s = g_strdup(bcmd);
6561 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
6562 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
6563 cmd->func(t, &arg);
6565 if (arg.s)
6566 g_free(arg.s);
6568 buffercmd_abort(t);
6571 gboolean
6572 buffercmd_addkey(struct tab *t, guint keyval)
6574 int i, c, match ;
6575 char s[XT_BUFCMD_SZ];
6577 if (keyval == GDK_Escape) {
6578 buffercmd_abort(t);
6579 return (XT_CB_HANDLED);
6582 /* key with modifier or non-ascii character */
6583 if (!isascii(keyval))
6584 return (XT_CB_PASSTHROUGH);
6586 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
6587 "to buffer \"%s\"\n", keyval, bcmd);
6589 for (i = 0; i < LENGTH(bcmd); i++)
6590 if (bcmd[i] == '\0') {
6591 bcmd[i] = keyval;
6592 break;
6595 /* buffer full, ignore input */
6596 if (i >= LENGTH(bcmd) -1) {
6597 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
6598 buffercmd_abort(t);
6599 return (XT_CB_HANDLED);
6602 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
6604 /* find exact match */
6605 for (i = 0; i < LENGTH(buffercmds); i++)
6606 if (regexec(&buffercmds[i].cregex, bcmd,
6607 (size_t) 0, NULL, 0) == 0) {
6608 buffercmd_execute(t, &buffercmds[i]);
6609 goto done;
6612 /* find non exact matches to see if we need to abort ot not */
6613 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
6614 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
6615 c = -1;
6616 s[0] = '\0';
6617 if (buffercmds[i].precount == XT_PRE_MAYBE) {
6618 if (isdigit(bcmd[0])) {
6619 if (sscanf(bcmd, "%d%s", &c, s) == 0)
6620 continue;
6621 } else {
6622 c = 0;
6623 if (sscanf(bcmd, "%s", s) == 0)
6624 continue;
6626 } else if (buffercmds[i].precount == XT_PRE_YES) {
6627 if (sscanf(bcmd, "%d%s", &c, s) == 0)
6628 continue;
6629 } else {
6630 if (sscanf(bcmd, "%s", s) == 0)
6631 continue;
6633 if (c == -1 && buffercmds[i].precount)
6634 continue;
6635 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
6636 match++;
6638 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
6639 i, match, buffercmds[i].cmd, c, s);
6641 if (match == 0) {
6642 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
6643 buffercmd_abort(t);
6646 done:
6647 return (XT_CB_HANDLED);
6650 gboolean
6651 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6653 struct key_binding *k;
6655 /* handle keybindings if buffercmd is empty.
6656 if not empty, allow commands like C-n */
6657 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
6658 TAILQ_FOREACH(k, &kbl, entry)
6659 if (e->keyval == k->key
6660 && (entry ? k->use_in_entry : 1)) {
6661 if (k->mask == 0) {
6662 if ((e->state & (CTRL | MOD1)) == 0)
6663 return (cmd_execute(t, k->cmd));
6664 } else if ((e->state & k->mask) == k->mask) {
6665 return (cmd_execute(t, k->cmd));
6669 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
6670 return buffercmd_addkey(t, e->keyval);
6672 return (XT_CB_PASSTHROUGH);
6676 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6678 char s[2];
6680 /* don't use w directly; use t->whatever instead */
6682 if (t == NULL) {
6683 show_oops(NULL, "wv_keypress_after_cb");
6684 return (XT_CB_PASSTHROUGH);
6687 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6688 e->keyval, e->state, t);
6690 if (t->hints_on) {
6691 /* XXX make sure cmd entry is enabled */
6692 return (XT_CB_HANDLED);
6693 } else {
6694 /* prefix input*/
6695 snprintf(s, sizeof s, "%c", e->keyval);
6696 if (CLEAN(e->state) == 0 && isdigit(s[0]))
6697 cmd_prefix = 10 * cmd_prefix + atoi(s);
6700 return (handle_keypress(t, e, 0));
6704 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6706 hide_oops(t);
6708 /* Hide buffers, if they are visible, with escape. */
6709 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
6710 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6711 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6712 hide_buffers(t);
6713 return (XT_CB_HANDLED);
6716 return (XT_CB_PASSTHROUGH);
6719 gboolean
6720 hint_continue(struct tab *t)
6722 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
6723 char *s;
6724 const gchar *errstr = NULL;
6725 gboolean rv = TRUE;
6726 long long i;
6728 if (!(c[0] == '.' || c[0] == ','))
6729 goto done;
6730 if (strlen(c) == 1) {
6731 /* XXX should not happen */
6732 rv = TRUE;
6733 goto done;
6736 if (isdigit(c[1])) {
6737 /* numeric input */
6738 i = strtonum(&c[1], 1, 4096, &errstr);
6739 if (errstr) {
6740 show_oops(t, "invalid numerical hint %s", &c[1]);
6741 goto done;
6743 s = g_strdup_printf("hints.updateHints(%lld);", i);
6744 run_script(t, s);
6745 g_free(s);
6746 } else {
6747 /* alphanumeric input */
6748 s = g_strdup_printf("hints.createHints('%s', '%c');",
6749 &c[1], c[0] == '.' ? 'f' : 'F');
6750 run_script(t, s);
6751 g_free(s);
6754 rv = TRUE;
6755 done:
6756 return (rv);
6759 gboolean
6760 search_continue(struct tab *t)
6762 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
6763 gboolean rv = FALSE;
6765 if (c[0] == ':' || c[0] == '.' || c[0] == ',')
6766 goto done;
6767 if (strlen(c) == 1) {
6768 webkit_web_view_unmark_text_matches(t->wv);
6769 goto done;
6772 if (c[0] == '/')
6773 t->search_forward = TRUE;
6774 else if (c[0] == '?')
6775 t->search_forward = FALSE;
6776 else
6777 goto done;
6779 rv = TRUE;
6780 done:
6781 return (rv);
6784 gboolean
6785 search_cb(struct tab *t)
6787 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
6788 GdkColor color;
6790 if (search_continue(t) == FALSE)
6791 goto done;
6793 /* search */
6794 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
6795 TRUE) == FALSE) {
6796 /* not found, mark red */
6797 gdk_color_parse(XT_COLOR_RED, &color);
6798 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6799 /* unmark and remove selection */
6800 webkit_web_view_unmark_text_matches(t->wv);
6801 /* my kingdom for a way to unselect text in webview */
6802 } else {
6803 /* found, highlight all */
6804 webkit_web_view_unmark_text_matches(t->wv);
6805 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6806 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6807 gdk_color_parse(XT_COLOR_WHITE, &color);
6808 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6810 done:
6811 t->search_id = 0;
6812 return (FALSE);
6816 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6818 const gchar *c = gtk_entry_get_text(w);
6820 if (t == NULL) {
6821 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
6822 return (XT_CB_PASSTHROUGH);
6825 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6826 e->keyval, e->state, t);
6828 /* hinting */
6829 if (!(e->keyval == GDK_Tab || e->keyval == GDK_ISO_Left_Tab)) {
6830 if (hint_continue(t) == FALSE)
6831 goto done;
6834 /* search */
6835 if (search_continue(t) == FALSE)
6836 goto done;
6838 /* if search length is > 4 then no longer play timeout games */
6839 if (strlen(c) > 4) {
6840 if (t->search_id) {
6841 g_source_remove(t->search_id);
6842 t->search_id = 0;
6844 search_cb(t);
6845 goto done;
6848 /* reestablish a new timer if the user types fast */
6849 if (t->search_id)
6850 g_source_remove(t->search_id);
6851 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
6853 done:
6854 return (XT_CB_PASSTHROUGH);
6857 gboolean
6858 match_uri(const gchar *uri, const gchar *key) {
6859 gchar *voffset;
6860 size_t len;
6861 gboolean match = FALSE;
6863 len = strlen(key);
6865 if (!strncmp(key, uri, len))
6866 match = TRUE;
6867 else {
6868 voffset = strstr(uri, "/") + 2;
6869 if (!strncmp(key, voffset, len))
6870 match = TRUE;
6871 else if (g_str_has_prefix(voffset, "www.")) {
6872 voffset = voffset + strlen("www.");
6873 if (!strncmp(key, voffset, len))
6874 match = TRUE;
6878 return (match);
6881 gboolean
6882 match_session(const gchar *name, const gchar *key) {
6883 char *sub;
6885 sub = strcasestr(name, key);
6887 return sub == name;
6890 void
6891 cmd_getlist(int id, char *key)
6893 int i, dep, c = 0;
6894 struct history *h;
6895 struct session *s;
6897 if (id >= 0) {
6898 if (cmds[id].type & XT_URLARG) {
6899 RB_FOREACH_REVERSE(h, history_list, &hl)
6900 if (match_uri(h->uri, key)) {
6901 cmd_status.list[c] = (char *)h->uri;
6902 if (++c > 255)
6903 break;
6905 cmd_status.len = c;
6906 return;
6907 } else if (cmds[id].type & XT_SESSARG) {
6908 TAILQ_FOREACH(s, &sessions, entry)
6909 if (match_session(s->name, key)) {
6910 cmd_status.list[c] = (char *)s->name;
6911 if (++c > 255)
6912 break;
6914 cmd_status.len = c;
6915 return;
6916 } else if (cmds[id].type & XT_SETARG) {
6917 for (i = 0; i < LENGTH(rs); i++)
6918 if(!strncmp(key, rs[i].name, strlen(key)))
6919 cmd_status.list[c++] = rs[i].name;
6920 cmd_status.len = c;
6921 return;
6925 dep = (id == -1) ? 0 : cmds[id].level + 1;
6927 for (i = id + 1; i < LENGTH(cmds); i++) {
6928 if (cmds[i].level < dep)
6929 break;
6930 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
6931 strlen(key)) && !isdigit(cmds[i].cmd[0]))
6932 cmd_status.list[c++] = cmds[i].cmd;
6936 cmd_status.len = c;
6939 char *
6940 cmd_getnext(int dir)
6942 cmd_status.index += dir;
6944 if (cmd_status.index < 0)
6945 cmd_status.index = cmd_status.len - 1;
6946 else if (cmd_status.index >= cmd_status.len)
6947 cmd_status.index = 0;
6949 return cmd_status.list[cmd_status.index];
6953 cmd_tokenize(char *s, char *tokens[])
6955 int i = 0;
6956 char *tok, *last = NULL;
6957 size_t len = strlen(s);
6958 bool blank;
6960 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
6961 for (tok = strtok_r(s, " ", &last); tok && i < 3;
6962 tok = strtok_r(NULL, " ", &last), i++)
6963 tokens[i] = tok;
6965 if (blank && i < 3)
6966 tokens[i++] = "";
6968 return (i);
6971 void
6972 cmd_complete(struct tab *t, char *str, int dir)
6974 GtkEntry *w = GTK_ENTRY(t->cmd);
6975 int i, j, levels, c = 0, dep = 0, parent = -1;
6976 int matchcount = 0;
6977 char *tok, *match, *s = g_strdup(str);
6978 char *tokens[3];
6979 char res[XT_MAX_URL_LENGTH + 32] = ":";
6980 char *sc = s;
6982 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
6984 /* copy prefix*/
6985 for (i = 0; isdigit(s[i]); i++)
6986 res[i + 1] = s[i];
6988 for (; isspace(s[i]); i++)
6989 res[i + 1] = s[i];
6991 s += i;
6993 levels = cmd_tokenize(s, tokens);
6995 for (i = 0; i < levels - 1; i++) {
6996 tok = tokens[i];
6997 matchcount = 0;
6998 for (j = c; j < LENGTH(cmds); j++) {
6999 if (cmds[j].level < dep)
7000 break;
7001 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
7002 strlen(tok))) {
7003 matchcount++;
7004 c = j + 1;
7005 if (strlen(tok) == strlen(cmds[j].cmd)) {
7006 matchcount = 1;
7007 break;
7012 if (matchcount == 1) {
7013 strlcat(res, tok, sizeof res);
7014 strlcat(res, " ", sizeof res);
7015 dep++;
7016 } else {
7017 g_free(sc);
7018 return;
7021 parent = c - 1;
7024 if (cmd_status.index == -1)
7025 cmd_getlist(parent, tokens[i]);
7027 if (cmd_status.len > 0) {
7028 match = cmd_getnext(dir);
7029 strlcat(res, match, sizeof res);
7030 gtk_entry_set_text(w, res);
7031 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7034 g_free(sc);
7037 gboolean
7038 cmd_execute(struct tab *t, char *str)
7040 struct cmd *cmd = NULL;
7041 char *tok, *last = NULL, *s = g_strdup(str), *sc;
7042 char prefixstr[4];
7043 int j, len, c = 0, dep = 0, matchcount = 0;
7044 int prefix = -1, rv = XT_CB_PASSTHROUGH;
7045 struct karg arg = {0, NULL, -1};
7047 sc = s;
7049 /* copy prefix*/
7050 for (j = 0; j<3 && isdigit(s[j]); j++)
7051 prefixstr[j]=s[j];
7053 prefixstr[j]='\0';
7055 s += j;
7056 while (isspace(s[0]))
7057 s++;
7059 if (strlen(s) > 0 && strlen(prefixstr) > 0)
7060 prefix = atoi(prefixstr);
7061 else
7062 s = sc;
7064 for (tok = strtok_r(s, " ", &last); tok;
7065 tok = strtok_r(NULL, " ", &last)) {
7066 matchcount = 0;
7067 for (j = c; j < LENGTH(cmds); j++) {
7068 if (cmds[j].level < dep)
7069 break;
7070 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
7071 strlen(tok);
7072 if (cmds[j].level == dep &&
7073 !strncmp(tok, cmds[j].cmd, len)) {
7074 matchcount++;
7075 c = j + 1;
7076 cmd = &cmds[j];
7077 if (len == strlen(cmds[j].cmd)) {
7078 matchcount = 1;
7079 break;
7083 if (matchcount == 1) {
7084 if (cmd->type > 0)
7085 goto execute_cmd;
7086 dep++;
7087 } else {
7088 show_oops(t, "Invalid command: %s", str);
7089 goto done;
7092 execute_cmd:
7093 if (cmd == NULL) {
7094 show_oops(t, "Empty command");
7095 goto done;
7097 arg.i = cmd->arg;
7099 if (prefix != -1)
7100 arg.precount = prefix;
7101 else if (cmd_prefix > 0)
7102 arg.precount = cmd_prefix;
7104 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
7105 show_oops(t, "No prefix allowed: %s", str);
7106 goto done;
7108 if (cmd->type > 1)
7109 arg.s = last ? g_strdup(last) : g_strdup("");
7110 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
7111 if (arg.s == NULL) {
7112 show_oops(t, "Invalid command");
7113 goto done;
7115 arg.precount = atoi(arg.s);
7116 if (arg.precount <= 0) {
7117 if (arg.s[0] == '0')
7118 show_oops(t, "Zero count");
7119 else
7120 show_oops(t, "Trailing characters");
7121 goto done;
7125 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
7126 __func__, arg.precount, arg.s);
7128 cmd->func(t, &arg);
7130 rv = XT_CB_HANDLED;
7131 done:
7132 if (j > 0)
7133 cmd_prefix = 0;
7134 g_free(sc);
7135 if (arg.s)
7136 g_free(arg.s);
7138 return (rv);
7142 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7144 if (t == NULL) {
7145 show_oops(NULL, "entry_key_cb invalid parameters");
7146 return (XT_CB_PASSTHROUGH);
7149 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
7150 e->keyval, e->state, t);
7152 hide_oops(t);
7154 if (e->keyval == GDK_Escape) {
7155 /* don't use focus_webview(t) because we want to type :cmds */
7156 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7159 return (handle_keypress(t, e, 1));
7162 struct command_entry *
7163 history_prev(struct command_list *l, struct command_entry *at)
7165 if (at == NULL)
7166 at = TAILQ_LAST(l, command_list);
7167 else {
7168 at = TAILQ_PREV(at, command_list, entry);
7169 if (at == NULL)
7170 at = TAILQ_LAST(l, command_list);
7173 return (at);
7176 struct command_entry *
7177 history_next(struct command_list *l, struct command_entry *at)
7179 if (at == NULL)
7180 at = TAILQ_FIRST(l);
7181 else {
7182 at = TAILQ_NEXT(at, entry);
7183 if (at == NULL)
7184 at = TAILQ_FIRST(l);
7187 return (at);
7191 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7193 int rv = XT_CB_HANDLED;
7194 const gchar *c = gtk_entry_get_text(w);
7195 char *s;
7197 if (t == NULL) {
7198 show_oops(NULL, "cmd_keypress_cb parameters");
7199 return (XT_CB_PASSTHROUGH);
7202 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
7203 e->keyval, e->state, t);
7205 /* sanity */
7206 if (c == NULL)
7207 e->keyval = GDK_Escape;
7208 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?' ||
7209 c[0] == '.' || c[0] == ','))
7210 e->keyval = GDK_Escape;
7212 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
7213 e->keyval != GDK_ISO_Left_Tab)
7214 cmd_status.index = -1;
7216 switch (e->keyval) {
7217 case GDK_Tab:
7218 if (c[0] == ':')
7219 cmd_complete(t, (char *)&c[1], 1);
7220 else if (c[0] == '.' || c[0] == ',')
7221 run_script(t, "hints.focusNextHint();");
7222 goto done;
7223 case GDK_ISO_Left_Tab:
7224 if (c[0] == ':')
7225 cmd_complete(t, (char *)&c[1], -1);
7226 else if (c[0] == '.' || c[0] == ',')
7227 run_script(t, "hints.focusPreviousHint();");
7228 goto done;
7229 case GDK_Down:
7230 if (c[0] == '?') {
7231 if ((search_at = history_next(&shl, search_at))) {
7232 search_at->line[0] = c[0];
7233 gtk_entry_set_text(w, search_at->line);
7234 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7236 } else if (c[0] == '/') {
7237 if ((search_at = history_prev(&chl, search_at))) {
7238 search_at->line[0] = c[0];
7239 gtk_entry_set_text(w, search_at->line);
7240 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7242 } if (c[0] == ':') {
7243 if ((history_at = history_prev(&chl, history_at))) {
7244 history_at->line[0] = c[0];
7245 gtk_entry_set_text(w, history_at->line);
7246 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7249 goto done;
7250 case GDK_Up:
7251 if (c[0] == '/') {
7252 if ((search_at = history_next(&shl, search_at))) {
7253 search_at->line[0] = c[0];
7254 gtk_entry_set_text(w, search_at->line);
7255 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7257 } else if (c[0] == '?') {
7258 if ((search_at = history_prev(&chl, search_at))) {
7259 search_at->line[0] = c[0];
7260 gtk_entry_set_text(w, search_at->line);
7261 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7263 } if (c[0] == ':') {
7264 if ((history_at = history_next(&chl, history_at))) {
7265 history_at->line[0] = c[0];
7266 gtk_entry_set_text(w, history_at->line);
7267 gtk_editable_set_position(GTK_EDITABLE(w), -1);
7270 goto done;
7271 case GDK_BackSpace:
7272 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?") ||
7273 !strcmp(c, ".") || !strcmp(c, ","))) {
7274 /* see if we are doing hinting and reset it */
7275 if (c[0] == '.' || c[0] == ',') {
7276 /* recreate hints */
7277 s = g_strdup_printf("hints.createHints('', "
7278 "'%c');", c[0] == '.' ? 'f' : 'F');
7279 run_script(t, s);
7280 g_free(s);
7282 break;
7285 /* FALLTHROUGH */
7286 case GDK_Escape:
7287 hide_cmd(t);
7288 focus_webview(t);
7290 /* cancel search */
7291 if (c != NULL && (c[0] == '/' || c[0] == '?'))
7292 webkit_web_view_unmark_text_matches(t->wv);
7294 /* no need to cancel hints */
7295 goto done;
7298 rv = XT_CB_PASSTHROUGH;
7299 done:
7300 return (rv);
7303 void
7304 wv_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
7306 DNPRINTF(XT_D_CMD, "wv_popup_cb: tab %d\n", t->tab_id);
7309 void
7310 cmd_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
7312 /* popup menu enabled */
7313 t->popup = 1;
7317 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
7319 if (t == NULL) {
7320 show_oops(NULL, "cmd_focusout_cb invalid parameters");
7321 return (XT_CB_PASSTHROUGH);
7324 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d popup %d\n",
7325 t->tab_id, t->popup);
7327 /* if popup is enabled don't lose focus */
7328 if (t->popup) {
7329 t->popup = 0;
7330 return (XT_CB_PASSTHROUGH);
7333 hide_cmd(t);
7334 hide_oops(t);
7335 disable_hints(t);
7337 if (show_url == 0 || t->focus_wv)
7338 focus_webview(t);
7339 else
7340 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7342 return (XT_CB_PASSTHROUGH);
7345 void
7346 cmd_activate_cb(GtkEntry *entry, struct tab *t)
7348 char *s;
7349 const gchar *c = gtk_entry_get_text(entry);
7351 if (t == NULL) {
7352 show_oops(NULL, "cmd_activate_cb invalid parameters");
7353 return;
7356 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
7357 hide_cmd(t);
7359 /* sanity */
7360 if (c == NULL)
7361 goto done;
7362 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?' ||
7363 c[0] == '.' || c[0] == ','))
7364 goto done;
7365 if (strlen(c) < 1)
7366 goto done;
7367 s = (char *)&c[1];
7369 if (c[0] == '/' || c[0] == '?') {
7370 /* see if there is a timer pending */
7371 if (t->search_id) {
7372 g_source_remove(t->search_id);
7373 t->search_id = 0;
7374 search_cb(t);
7377 if (t->search_text) {
7378 g_free(t->search_text);
7379 t->search_text = NULL;
7382 t->search_text = g_strdup(s);
7383 if (global_search)
7384 g_free(global_search);
7385 global_search = g_strdup(s);
7386 t->search_forward = c[0] == '/';
7388 history_add(&shl, search_file, s, &search_history_count);
7389 goto done;
7390 } else if (c[0] == '.' || c[0] == ',') {
7391 run_script(t, "hints.fire();");
7392 /* XXX history for link following? */
7393 goto done;
7396 history_add(&chl, command_file, s, &cmd_history_count);
7397 cmd_execute(t, s);
7398 done:
7399 return;
7402 void
7403 backward_cb(GtkWidget *w, struct tab *t)
7405 struct karg a;
7407 if (t == NULL) {
7408 show_oops(NULL, "backward_cb invalid parameters");
7409 return;
7412 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
7414 a.i = XT_NAV_BACK;
7415 navaction(t, &a);
7418 void
7419 forward_cb(GtkWidget *w, struct tab *t)
7421 struct karg a;
7423 if (t == NULL) {
7424 show_oops(NULL, "forward_cb invalid parameters");
7425 return;
7428 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
7430 a.i = XT_NAV_FORWARD;
7431 navaction(t, &a);
7434 void
7435 home_cb(GtkWidget *w, struct tab *t)
7437 if (t == NULL) {
7438 show_oops(NULL, "home_cb invalid parameters");
7439 return;
7442 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
7444 load_uri(t, home);
7447 void
7448 stop_cb(GtkWidget *w, struct tab *t)
7450 WebKitWebFrame *frame;
7452 if (t == NULL) {
7453 show_oops(NULL, "stop_cb invalid parameters");
7454 return;
7457 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
7459 frame = webkit_web_view_get_main_frame(t->wv);
7460 if (frame == NULL) {
7461 show_oops(t, "stop_cb: no frame");
7462 return;
7465 webkit_web_frame_stop_loading(frame);
7466 abort_favicon_download(t);
7469 void
7470 setup_webkit(struct tab *t)
7472 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
7473 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
7474 FALSE, (char *)NULL);
7475 else
7476 warnx("webkit does not have \"enable-dns-prefetching\" property");
7477 g_object_set(G_OBJECT(t->settings),
7478 "user-agent", t->user_agent, (char *)NULL);
7479 g_object_set(G_OBJECT(t->settings),
7480 "enable-scripts", enable_scripts, (char *)NULL);
7481 g_object_set(G_OBJECT(t->settings),
7482 "enable-plugins", enable_plugins, (char *)NULL);
7483 g_object_set(G_OBJECT(t->settings),
7484 "javascript-can-open-windows-automatically", enable_scripts,
7485 (char *)NULL);
7486 g_object_set(G_OBJECT(t->settings),
7487 "enable-html5-database", FALSE, (char *)NULL);
7488 g_object_set(G_OBJECT(t->settings),
7489 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
7490 g_object_set(G_OBJECT(t->settings),
7491 "enable_spell_checking", enable_spell_checking, (char *)NULL);
7492 g_object_set(G_OBJECT(t->settings),
7493 "spell_checking_languages", spell_check_languages, (char *)NULL);
7494 g_object_set(G_OBJECT(t->settings),
7495 "enable-developer-extras", TRUE, (char *)NULL);
7496 g_object_set(G_OBJECT(t->wv),
7497 "full-content-zoom", TRUE, (char *)NULL);
7499 webkit_web_view_set_settings(t->wv, t->settings);
7502 gboolean
7503 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
7505 struct tab *ti, *t = NULL;
7506 gdouble view_size, value, max;
7507 gchar *position;
7509 TAILQ_FOREACH(ti, &tabs, entry)
7510 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
7511 t = ti;
7512 break;
7515 if (t == NULL)
7516 return FALSE;
7518 if (adjustment == NULL)
7519 adjustment = gtk_scrolled_window_get_vadjustment(
7520 GTK_SCROLLED_WINDOW(t->browser_win));
7522 view_size = gtk_adjustment_get_page_size(adjustment);
7523 value = gtk_adjustment_get_value(adjustment);
7524 max = gtk_adjustment_get_upper(adjustment) - view_size;
7526 if (max == 0)
7527 position = g_strdup("All");
7528 else if (value == max)
7529 position = g_strdup("Bot");
7530 else if (value == 0)
7531 position = g_strdup("Top");
7532 else
7533 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
7535 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
7536 g_free(position);
7538 return (TRUE);
7541 GtkWidget *
7542 create_window(const gchar *name)
7544 GtkWidget *w;
7546 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7547 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
7548 gtk_widget_set_name(w, name);
7549 gtk_window_set_wmclass(GTK_WINDOW(w), name, "XXXTerm");
7551 return (w);
7554 GtkWidget *
7555 create_browser(struct tab *t)
7557 GtkWidget *w;
7558 gchar *strval;
7559 GtkAdjustment *adjustment;
7561 if (t == NULL) {
7562 show_oops(NULL, "create_browser invalid parameters");
7563 return (NULL);
7566 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
7567 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
7568 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
7569 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
7571 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
7572 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
7573 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
7575 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
7576 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
7578 /* set defaults */
7579 t->settings = webkit_web_settings_new();
7581 g_object_set(t->settings, "default-encoding", encoding, (char *)NULL);
7583 if (user_agent == NULL) {
7584 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
7585 (char *)NULL);
7586 t->user_agent = g_strdup_printf("%s %s+", strval, version);
7587 g_free(strval);
7588 } else
7589 t->user_agent = g_strdup(user_agent);
7591 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
7593 adjustment =
7594 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
7595 g_signal_connect(G_OBJECT(adjustment), "value-changed",
7596 G_CALLBACK(update_statusbar_position), NULL);
7598 setup_webkit(t);
7599 setup_inspector(t);
7601 return (w);
7604 GtkWidget *
7605 create_kiosk_toolbar(struct tab *t)
7607 GtkWidget *toolbar = NULL, *b;
7609 b = gtk_hbox_new(FALSE, 0);
7610 toolbar = b;
7611 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7613 /* backward button */
7614 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7615 gtk_widget_set_sensitive(t->backward, FALSE);
7616 g_signal_connect(G_OBJECT(t->backward), "clicked",
7617 G_CALLBACK(backward_cb), t);
7618 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7620 /* forward button */
7621 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7622 gtk_widget_set_sensitive(t->forward, FALSE);
7623 g_signal_connect(G_OBJECT(t->forward), "clicked",
7624 G_CALLBACK(forward_cb), t);
7625 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7627 /* home button */
7628 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7629 gtk_widget_set_sensitive(t->gohome, true);
7630 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7631 G_CALLBACK(home_cb), t);
7632 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7634 /* create widgets but don't use them */
7635 t->uri_entry = gtk_entry_new();
7636 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7637 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7638 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7640 return (toolbar);
7643 GtkWidget *
7644 create_toolbar(struct tab *t)
7646 GtkWidget *toolbar = NULL, *b, *eb1;
7648 b = gtk_hbox_new(FALSE, 0);
7649 toolbar = b;
7650 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7652 /* backward button */
7653 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7654 gtk_widget_set_sensitive(t->backward, FALSE);
7655 g_signal_connect(G_OBJECT(t->backward), "clicked",
7656 G_CALLBACK(backward_cb), t);
7657 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7659 /* forward button */
7660 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7661 gtk_widget_set_sensitive(t->forward, FALSE);
7662 g_signal_connect(G_OBJECT(t->forward), "clicked",
7663 G_CALLBACK(forward_cb), t);
7664 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7665 FALSE, 0);
7667 /* stop button */
7668 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7669 gtk_widget_set_sensitive(t->stop, FALSE);
7670 g_signal_connect(G_OBJECT(t->stop), "clicked",
7671 G_CALLBACK(stop_cb), t);
7672 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7673 FALSE, 0);
7675 /* JS button */
7676 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7677 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7678 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7679 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7680 G_CALLBACK(js_toggle_cb), t);
7681 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7683 t->uri_entry = gtk_entry_new();
7684 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7685 G_CALLBACK(activate_uri_entry_cb), t);
7686 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7687 G_CALLBACK(entry_key_cb), t);
7688 completion_add(t);
7689 eb1 = gtk_hbox_new(FALSE, 0);
7690 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7691 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7692 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7694 /* search entry */
7695 if (search_string) {
7696 GtkWidget *eb2;
7697 t->search_entry = gtk_entry_new();
7698 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7699 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7700 G_CALLBACK(activate_search_entry_cb), t);
7701 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7702 G_CALLBACK(entry_key_cb), t);
7703 gtk_widget_set_size_request(t->search_entry, -1, -1);
7704 eb2 = gtk_hbox_new(FALSE, 0);
7705 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7706 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7708 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7711 return (toolbar);
7714 GtkWidget *
7715 create_buffers(struct tab *t)
7717 GtkCellRenderer *renderer;
7718 GtkWidget *view;
7720 view = gtk_tree_view_new();
7722 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
7724 renderer = gtk_cell_renderer_text_new();
7725 gtk_tree_view_insert_column_with_attributes
7726 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, (char *)NULL);
7728 renderer = gtk_cell_renderer_text_new();
7729 gtk_tree_view_insert_column_with_attributes
7730 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
7731 (char *)NULL);
7733 gtk_tree_view_set_model
7734 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
7736 return view;
7739 void
7740 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
7741 GtkTreeViewColumn *col, struct tab *t)
7743 GtkTreeIter iter;
7744 guint id;
7746 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7748 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
7749 path)) {
7750 gtk_tree_model_get
7751 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
7752 set_current_tab(id - 1);
7755 hide_buffers(t);
7758 /* after tab reordering/creation/removal */
7759 void
7760 recalc_tabs(void)
7762 struct tab *t;
7763 int maxid = 0;
7765 TAILQ_FOREACH(t, &tabs, entry) {
7766 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7767 if (t->tab_id > maxid)
7768 maxid = t->tab_id;
7770 gtk_widget_show(t->tab_elems.sep);
7773 TAILQ_FOREACH(t, &tabs, entry) {
7774 if (t->tab_id == maxid) {
7775 gtk_widget_hide(t->tab_elems.sep);
7776 break;
7781 /* after active tab change */
7782 void
7783 recolor_compact_tabs(void)
7785 struct tab *t;
7786 int curid = 0;
7787 GdkColor color;
7789 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
7790 TAILQ_FOREACH(t, &tabs, entry)
7791 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
7792 &color);
7794 curid = gtk_notebook_get_current_page(notebook);
7795 TAILQ_FOREACH(t, &tabs, entry)
7796 if (t->tab_id == curid) {
7797 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
7798 gtk_widget_modify_fg(t->tab_elems.label,
7799 GTK_STATE_NORMAL, &color);
7800 break;
7804 void
7805 set_current_tab(int page_num)
7807 buffercmd_abort(get_current_tab());
7808 gtk_notebook_set_current_page(notebook, page_num);
7809 recolor_compact_tabs();
7813 undo_close_tab_save(struct tab *t)
7815 int m, n;
7816 const gchar *uri;
7817 struct undo *u1, *u2;
7818 GList *items;
7819 WebKitWebHistoryItem *item;
7821 if ((uri = get_uri(t)) == NULL)
7822 return (1);
7824 u1 = g_malloc0(sizeof(struct undo));
7825 u1->uri = g_strdup(uri);
7827 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7829 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7830 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7831 u1->back = n;
7833 /* forward history */
7834 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7836 while (items) {
7837 item = items->data;
7838 u1->history = g_list_prepend(u1->history,
7839 webkit_web_history_item_copy(item));
7840 items = g_list_next(items);
7843 /* current item */
7844 if (m) {
7845 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7846 u1->history = g_list_prepend(u1->history,
7847 webkit_web_history_item_copy(item));
7850 /* back history */
7851 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7853 while (items) {
7854 item = items->data;
7855 u1->history = g_list_prepend(u1->history,
7856 webkit_web_history_item_copy(item));
7857 items = g_list_next(items);
7860 TAILQ_INSERT_HEAD(&undos, u1, entry);
7862 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7863 u2 = TAILQ_LAST(&undos, undo_tailq);
7864 TAILQ_REMOVE(&undos, u2, entry);
7865 g_free(u2->uri);
7866 g_list_free(u2->history);
7867 g_free(u2);
7868 } else
7869 undo_count++;
7871 return (0);
7874 void
7875 delete_tab(struct tab *t)
7877 struct karg a;
7879 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7881 if (t == NULL)
7882 return;
7885 * no need to join thread here because it won't access t on completion
7888 TAILQ_REMOVE(&tabs, t, entry);
7889 buffercmd_abort(t);
7891 /* Halt all webkit activity. */
7892 abort_favicon_download(t);
7893 webkit_web_view_stop_loading(t->wv);
7895 /* Save the tab, so we can undo the close. */
7896 undo_close_tab_save(t);
7898 /* just in case */
7899 if (t->search_id)
7900 g_source_remove(t->search_id);
7902 /* inspector */
7903 bzero(&a, sizeof a);
7904 a.i = XT_INS_CLOSE;
7905 inspector_cmd(t, &a);
7907 if (browser_mode == XT_BM_KIOSK) {
7908 gtk_widget_destroy(t->uri_entry);
7909 gtk_widget_destroy(t->stop);
7910 gtk_widget_destroy(t->js_toggle);
7913 gtk_widget_destroy(t->tab_elems.eventbox);
7914 gtk_widget_destroy(t->vbox);
7916 g_free(t->user_agent);
7917 g_free(t->stylesheet);
7918 g_free(t->tmp_uri);
7919 g_free(t);
7920 t = NULL;
7922 if (TAILQ_EMPTY(&tabs)) {
7923 if (browser_mode == XT_BM_KIOSK)
7924 create_new_tab(home, NULL, 1, -1);
7925 else
7926 create_new_tab(NULL, NULL, 1, -1);
7929 /* recreate session */
7930 if (session_autosave) {
7931 bzero(&a, sizeof a);
7932 a.s = NULL;
7933 save_tabs(NULL, &a);
7936 recalc_tabs();
7937 recolor_compact_tabs();
7940 void
7941 update_statusbar_zoom(struct tab *t)
7943 gfloat zoom;
7944 char s[16] = { '\0' };
7946 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7947 if ((zoom <= 0.99 || zoom >= 1.01))
7948 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
7949 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
7952 void
7953 setzoom_webkit(struct tab *t, int adjust)
7955 #define XT_ZOOMPERCENT 0.04
7957 gfloat zoom;
7959 if (t == NULL) {
7960 show_oops(NULL, "setzoom_webkit invalid parameters");
7961 return;
7964 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7965 if (adjust == XT_ZOOM_IN)
7966 zoom += XT_ZOOMPERCENT;
7967 else if (adjust == XT_ZOOM_OUT)
7968 zoom -= XT_ZOOMPERCENT;
7969 else if (adjust > 0)
7970 zoom = default_zoom_level + adjust / 100.0 - 1.0;
7971 else {
7972 show_oops(t, "setzoom_webkit invalid zoom value");
7973 return;
7976 if (zoom < XT_ZOOMPERCENT)
7977 zoom = XT_ZOOMPERCENT;
7978 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7979 update_statusbar_zoom(t);
7982 gboolean
7983 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
7985 struct tab *t = (struct tab *) data;
7987 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
7989 switch (event->button) {
7990 case 1:
7991 set_current_tab(t->tab_id);
7992 break;
7993 case 2:
7994 delete_tab(t);
7995 break;
7998 return TRUE;
8001 void
8002 append_tab(struct tab *t)
8004 if (t == NULL)
8005 return;
8007 TAILQ_INSERT_TAIL(&tabs, t, entry);
8008 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
8011 GtkWidget *
8012 create_sbe(int width)
8014 GtkWidget *sbe;
8016 sbe = gtk_entry_new();
8017 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
8018 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
8019 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
8020 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
8021 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
8022 gtk_widget_set_size_request(sbe, width, -1);
8024 return sbe;
8027 struct tab *
8028 create_new_tab(char *title, struct undo *u, int focus, int position)
8030 struct tab *t;
8031 int load = 1, id;
8032 GtkWidget *b, *bb;
8033 WebKitWebHistoryItem *item;
8034 GList *items;
8035 GdkColor color;
8036 char *p;
8037 int sbe_p = 0, sbe_b = 0,
8038 sbe_z = 0;
8040 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
8042 if (tabless && !TAILQ_EMPTY(&tabs)) {
8043 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
8044 return (NULL);
8047 t = g_malloc0(sizeof *t);
8049 if (title == NULL) {
8050 title = "(untitled)";
8051 load = 0;
8054 t->vbox = gtk_vbox_new(FALSE, 0);
8056 /* label + button for tab */
8057 b = gtk_hbox_new(FALSE, 0);
8058 t->tab_content = b;
8060 #if GTK_CHECK_VERSION(2, 20, 0)
8061 t->spinner = gtk_spinner_new();
8062 #endif
8063 t->label = gtk_label_new(title);
8064 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
8065 gtk_widget_set_size_request(t->label, 100, 0);
8066 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
8067 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
8068 gtk_widget_set_size_request(b, 130, 0);
8070 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
8071 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
8072 #if GTK_CHECK_VERSION(2, 20, 0)
8073 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
8074 #endif
8076 /* toolbar */
8077 if (browser_mode == XT_BM_KIOSK) {
8078 t->toolbar = create_kiosk_toolbar(t);
8079 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
8081 } else {
8082 t->toolbar = create_toolbar(t);
8083 if (fancy_bar)
8084 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
8085 FALSE, 0);
8088 /* marks */
8089 marks_clear(t);
8091 /* browser */
8092 t->browser_win = create_browser(t);
8093 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
8095 /* oops message for user feedback */
8096 t->oops = gtk_entry_new();
8097 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
8098 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
8099 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
8100 gdk_color_parse(XT_COLOR_RED, &color);
8101 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
8102 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
8103 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
8105 /* command entry */
8106 t->cmd = gtk_entry_new();
8107 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
8108 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
8109 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
8110 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
8112 /* status bar */
8113 t->statusbar_box = gtk_hbox_new(FALSE, 0);
8115 t->sbe.statusbar = gtk_entry_new();
8116 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
8117 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
8118 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
8119 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
8121 /* create these widgets only if specified in statusbar_elems */
8123 t->sbe.position = create_sbe(40);
8124 t->sbe.zoom = create_sbe(40);
8125 t->sbe.buffercmd = create_sbe(60);
8127 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
8129 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
8130 TRUE, FALSE);
8132 /* gtk widgets cannot be added to a box twice. sbe_* variables
8133 make sure of this */
8134 for (p = statusbar_elems; *p != '\0'; p++) {
8135 switch (*p) {
8136 case '|':
8138 GtkWidget *sep = gtk_vseparator_new();
8140 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
8141 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
8142 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
8143 FALSE, FALSE, FALSE);
8144 break;
8146 case 'P':
8147 if (sbe_p) {
8148 warnx("flag \"%c\" specified more than "
8149 "once in statusbar_elems\n", *p);
8150 break;
8152 sbe_p = 1;
8153 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8154 t->sbe.position, FALSE, FALSE, FALSE);
8155 break;
8156 case 'B':
8157 if (sbe_b) {
8158 warnx("flag \"%c\" specified more than "
8159 "once in statusbar_elems\n", *p);
8160 break;
8162 sbe_b = 1;
8163 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8164 t->sbe.buffercmd, FALSE, FALSE, FALSE);
8165 break;
8166 case 'Z':
8167 if (sbe_z) {
8168 warnx("flag \"%c\" specified more than "
8169 "once in statusbar_elems\n", *p);
8170 break;
8172 sbe_z = 1;
8173 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
8174 t->sbe.zoom, FALSE, FALSE, FALSE);
8175 break;
8176 default:
8177 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
8178 break;
8182 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
8184 /* buffer list */
8185 t->buffers = create_buffers(t);
8186 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
8188 /* xtp meaning is normal by default */
8189 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
8191 /* set empty favicon */
8192 xt_icon_from_name(t, "text-html");
8194 /* and show it all */
8195 gtk_widget_show_all(b);
8196 gtk_widget_show_all(t->vbox);
8198 /* compact tab bar */
8199 t->tab_elems.label = gtk_label_new(title);
8200 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
8201 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
8202 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
8203 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
8205 t->tab_elems.eventbox = gtk_event_box_new();
8206 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
8207 t->tab_elems.sep = gtk_vseparator_new();
8209 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
8210 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
8211 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
8212 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
8213 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
8214 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
8216 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
8217 TRUE, 0);
8218 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
8219 FALSE, 0);
8220 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
8221 t->tab_elems.box);
8223 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
8224 TRUE, 0);
8225 gtk_widget_show_all(t->tab_elems.eventbox);
8227 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
8228 append_tab(t);
8229 else {
8230 id = position >= 0 ? position :
8231 gtk_notebook_get_current_page(notebook) + 1;
8232 if (id > gtk_notebook_get_n_pages(notebook))
8233 append_tab(t);
8234 else {
8235 TAILQ_INSERT_TAIL(&tabs, t, entry);
8236 gtk_notebook_insert_page(notebook, t->vbox, b, id);
8237 gtk_box_reorder_child(GTK_BOX(tab_bar),
8238 t->tab_elems.eventbox, id);
8239 recalc_tabs();
8243 #if GTK_CHECK_VERSION(2, 20, 0)
8244 /* turn spinner off if we are a new tab without uri */
8245 if (!load) {
8246 gtk_spinner_stop(GTK_SPINNER(t->spinner));
8247 gtk_widget_hide(t->spinner);
8249 #endif
8250 /* make notebook tabs reorderable */
8251 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
8253 /* compact tabs clickable */
8254 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
8255 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
8257 g_object_connect(G_OBJECT(t->cmd),
8258 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
8259 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
8260 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
8261 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
8262 "signal::populate-popup", G_CALLBACK(cmd_popup_cb), t,
8263 (char *)NULL);
8265 /* reuse wv_button_cb to hide oops */
8266 g_object_connect(G_OBJECT(t->oops),
8267 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8268 (char *)NULL);
8270 g_signal_connect(t->buffers,
8271 "row-activated", G_CALLBACK(row_activated_cb), t);
8272 g_object_connect(G_OBJECT(t->buffers),
8273 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, (char *)NULL);
8275 g_object_connect(G_OBJECT(t->wv),
8276 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
8277 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8278 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
8279 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
8280 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
8281 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8282 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
8283 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
8284 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
8285 "signal::event", G_CALLBACK(webview_event_cb), t,
8286 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
8287 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
8288 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
8289 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
8290 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
8291 "signal::populate-popup", G_CALLBACK(wv_popup_cb), t,
8292 (char *)NULL);
8293 g_signal_connect(t->wv,
8294 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
8295 g_signal_connect(t->wv,
8296 "notify::title", G_CALLBACK(notify_title_cb), t);
8298 /* hijack the unused keys as if we were the browser */
8299 g_object_connect(G_OBJECT(t->toolbar),
8300 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
8301 (char *)NULL);
8303 g_signal_connect(G_OBJECT(bb), "button_press_event",
8304 G_CALLBACK(tab_close_cb), t);
8306 /* setup history */
8307 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
8308 /* restore the tab's history */
8309 if (u && u->history) {
8310 items = u->history;
8311 while (items) {
8312 item = items->data;
8313 webkit_web_back_forward_list_add_item(t->bfl, item);
8314 items = g_list_next(items);
8317 item = g_list_nth_data(u->history, u->back);
8318 if (item)
8319 webkit_web_view_go_to_back_forward_item(t->wv, item);
8321 g_list_free(items);
8322 g_list_free(u->history);
8323 } else
8324 webkit_web_back_forward_list_clear(t->bfl);
8326 /* hide stuff */
8327 hide_cmd(t);
8328 hide_oops(t);
8329 hide_buffers(t);
8330 url_set_visibility();
8331 statusbar_set_visibility();
8333 if (focus) {
8334 set_current_tab(t->tab_id);
8335 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
8336 t->tab_id);
8338 if (load) {
8339 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
8340 load_uri(t, title);
8341 } else {
8342 if (show_url == 1)
8343 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8344 else
8345 focus_webview(t);
8347 } else if (load)
8348 load_uri(t, title);
8350 recolor_compact_tabs();
8351 setzoom_webkit(t, XT_ZOOM_NORMAL);
8352 return (t);
8355 void
8356 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8357 gpointer *udata)
8359 struct tab *t;
8360 const gchar *uri;
8362 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
8364 if (gtk_notebook_get_current_page(notebook) == -1)
8365 recalc_tabs();
8367 TAILQ_FOREACH(t, &tabs, entry) {
8368 if (t->tab_id == pn) {
8369 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
8370 "%d\n", pn);
8372 uri = get_title(t, TRUE);
8373 gtk_window_set_title(GTK_WINDOW(main_window), uri);
8375 hide_cmd(t);
8376 hide_oops(t);
8378 if (t->focus_wv) {
8379 /* can't use focus_webview here */
8380 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8386 void
8387 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
8388 gpointer *udata)
8390 struct tab *t = NULL, *tt;
8392 recalc_tabs();
8394 TAILQ_FOREACH(tt, &tabs, entry)
8395 if (tt->tab_id == pn) {
8396 t = tt;
8397 break;
8399 if (t == NULL)
8400 return;
8401 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
8403 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
8404 t->tab_id);
8407 void
8408 menuitem_response(struct tab *t)
8410 gtk_notebook_set_current_page(notebook, t->tab_id);
8413 gboolean
8414 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
8416 GtkWidget *menu, *menu_items;
8417 GdkEventButton *bevent;
8418 const gchar *uri;
8419 struct tab *ti;
8421 if (event->type == GDK_BUTTON_PRESS) {
8422 bevent = (GdkEventButton *) event;
8423 menu = gtk_menu_new();
8425 TAILQ_FOREACH(ti, &tabs, entry) {
8426 if ((uri = get_uri(ti)) == NULL)
8427 /* XXX make sure there is something to print */
8428 /* XXX add gui pages in here to look purdy */
8429 uri = "(untitled)";
8430 menu_items = gtk_menu_item_new_with_label(uri);
8431 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
8432 gtk_widget_show(menu_items);
8434 g_signal_connect_swapped((menu_items),
8435 "activate", G_CALLBACK(menuitem_response),
8436 (gpointer)ti);
8439 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
8440 bevent->button, bevent->time);
8442 /* unref object so it'll free itself when popped down */
8443 #if !GTK_CHECK_VERSION(3, 0, 0)
8444 /* XXX does not need unref with gtk+3? */
8445 g_object_ref_sink(menu);
8446 g_object_unref(menu);
8447 #endif
8449 return (TRUE /* eat event */);
8452 return (FALSE /* propagate */);
8456 icon_size_map(int icon_size)
8458 if (icon_size <= GTK_ICON_SIZE_INVALID ||
8459 icon_size > GTK_ICON_SIZE_DIALOG)
8460 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
8462 return (icon_size);
8465 GtkWidget *
8466 create_button(char *name, char *stockid, int size)
8468 GtkWidget *button, *image;
8469 gchar *rcstring;
8470 int gtk_icon_size;
8472 rcstring = g_strdup_printf(
8473 "style \"%s-style\"\n"
8474 "{\n"
8475 " GtkWidget::focus-padding = 0\n"
8476 " GtkWidget::focus-line-width = 0\n"
8477 " xthickness = 0\n"
8478 " ythickness = 0\n"
8479 "}\n"
8480 "widget \"*.%s\" style \"%s-style\"", name, name, name);
8481 gtk_rc_parse_string(rcstring);
8482 g_free(rcstring);
8483 button = gtk_button_new();
8484 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
8485 gtk_icon_size = icon_size_map(size ? size : icon_size);
8487 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
8488 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8489 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
8490 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
8491 gtk_widget_set_name(button, name);
8492 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
8494 return (button);
8497 void
8498 button_set_stockid(GtkWidget *button, char *stockid)
8500 GtkWidget *image;
8502 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
8503 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
8504 gtk_button_set_image(GTK_BUTTON(button), image);
8507 void
8508 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
8510 gchar *p = NULL;
8511 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
8512 gint len;
8514 if (xterm_workaround == 0)
8515 return;
8518 * xterm doesn't play nice with clipboards because it clears the
8519 * primary when clicked. We rely on primary being set to properly
8520 * handle middle mouse button clicks (paste). So when someone clears
8521 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
8522 * other application behavior (as in DON'T clear primary).
8525 p = gtk_clipboard_wait_for_text(primary);
8526 if (p == NULL) {
8527 if (gdk_property_get(gdk_get_default_root_window(),
8528 atom,
8529 gdk_atom_intern("STRING", FALSE),
8531 1024 * 1024 /* picked out of my butt */,
8532 FALSE,
8533 NULL,
8534 NULL,
8535 &len,
8536 (guchar **)&p)) {
8537 /* yes sir, we need to NUL the string */
8538 p[len] = '\0';
8539 gtk_clipboard_set_text(primary, p, -1);
8543 if (p)
8544 g_free(p);
8547 void
8548 create_canvas(void)
8550 GtkWidget *vbox;
8551 GList *l = NULL;
8552 GdkPixbuf *pb;
8553 char file[PATH_MAX];
8554 int i;
8556 vbox = gtk_vbox_new(FALSE, 0);
8557 gtk_box_set_spacing(GTK_BOX(vbox), 0);
8558 notebook = GTK_NOTEBOOK(gtk_notebook_new());
8559 #if !GTK_CHECK_VERSION(3, 0, 0)
8560 /* XXX seems to be needed with gtk+2 */
8561 gtk_notebook_set_tab_hborder(notebook, 0);
8562 gtk_notebook_set_tab_vborder(notebook, 0);
8563 #endif
8564 gtk_notebook_set_scrollable(notebook, TRUE);
8565 gtk_notebook_set_show_border(notebook, FALSE);
8566 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
8568 abtn = gtk_button_new();
8569 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
8570 gtk_widget_set_size_request(arrow, -1, -1);
8571 gtk_container_add(GTK_CONTAINER(abtn), arrow);
8572 gtk_widget_set_size_request(abtn, -1, 20);
8574 #if GTK_CHECK_VERSION(2, 20, 0)
8575 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
8576 #endif
8577 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
8579 /* compact tab bar */
8580 tab_bar = gtk_hbox_new(TRUE, 0);
8582 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
8583 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
8584 gtk_widget_set_size_request(vbox, -1, -1);
8586 g_object_connect(G_OBJECT(notebook),
8587 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
8588 (char *)NULL);
8589 g_object_connect(G_OBJECT(notebook),
8590 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
8591 NULL, (char *)NULL);
8592 g_signal_connect(G_OBJECT(abtn), "button_press_event",
8593 G_CALLBACK(arrow_cb), NULL);
8595 main_window = create_window("xxxterm");
8596 gtk_container_add(GTK_CONTAINER(main_window), vbox);
8597 g_signal_connect(G_OBJECT(main_window), "delete_event",
8598 G_CALLBACK(gtk_main_quit), NULL);
8600 /* icons */
8601 for (i = 0; i < LENGTH(icons); i++) {
8602 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
8603 pb = gdk_pixbuf_new_from_file(file, NULL);
8604 l = g_list_append(l, pb);
8606 gtk_window_set_default_icon_list(l);
8608 /* clipboard work around */
8609 if (xterm_workaround)
8610 g_signal_connect(
8611 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
8612 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
8614 gtk_widget_show_all(abtn);
8615 gtk_widget_show_all(main_window);
8616 notebook_tab_set_visibility();
8619 void
8620 set_hook(void **hook, char *name)
8622 if (hook == NULL)
8623 errx(1, "set_hook");
8625 if (*hook == NULL) {
8626 *hook = dlsym(RTLD_NEXT, name);
8627 if (*hook == NULL)
8628 errx(1, "can't hook %s", name);
8632 /* override libsoup soup_cookie_equal because it doesn't look at domain */
8633 gboolean
8634 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
8636 g_return_val_if_fail(cookie1, FALSE);
8637 g_return_val_if_fail(cookie2, FALSE);
8639 return (!strcmp (cookie1->name, cookie2->name) &&
8640 !strcmp (cookie1->value, cookie2->value) &&
8641 !strcmp (cookie1->path, cookie2->path) &&
8642 !strcmp (cookie1->domain, cookie2->domain));
8645 void
8646 transfer_cookies(void)
8648 GSList *cf;
8649 SoupCookie *sc, *pc;
8651 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8653 for (;cf; cf = cf->next) {
8654 pc = cf->data;
8655 sc = soup_cookie_copy(pc);
8656 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
8659 soup_cookies_free(cf);
8662 void
8663 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
8665 GSList *cf;
8666 SoupCookie *ci;
8668 print_cookie("soup_cookie_jar_delete_cookie", c);
8670 if (cookies_enabled == 0)
8671 return;
8673 if (jar == NULL || c == NULL)
8674 return;
8676 /* find and remove from persistent jar */
8677 cf = soup_cookie_jar_all_cookies(p_cookiejar);
8679 for (;cf; cf = cf->next) {
8680 ci = cf->data;
8681 if (soup_cookie_equal(ci, c)) {
8682 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
8683 break;
8687 soup_cookies_free(cf);
8689 /* delete from session jar */
8690 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
8693 void
8694 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
8696 struct domain *d = NULL;
8697 SoupCookie *c;
8698 FILE *r_cookie_f;
8700 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
8701 jar, p_cookiejar, s_cookiejar);
8703 if (cookies_enabled == 0)
8704 return;
8706 /* see if we are up and running */
8707 if (p_cookiejar == NULL) {
8708 _soup_cookie_jar_add_cookie(jar, cookie);
8709 return;
8711 /* disallow p_cookiejar adds, shouldn't happen */
8712 if (jar == p_cookiejar)
8713 return;
8715 /* sanity */
8716 if (jar == NULL || cookie == NULL)
8717 return;
8719 if (enable_cookie_whitelist &&
8720 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
8721 blocked_cookies++;
8722 DNPRINTF(XT_D_COOKIE,
8723 "soup_cookie_jar_add_cookie: reject %s\n",
8724 cookie->domain);
8725 if (save_rejected_cookies) {
8726 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
8727 show_oops(NULL, "can't open reject cookie file");
8728 return;
8730 fseek(r_cookie_f, 0, SEEK_END);
8731 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
8732 cookie->http_only ? "#HttpOnly_" : "",
8733 cookie->domain,
8734 *cookie->domain == '.' ? "TRUE" : "FALSE",
8735 cookie->path,
8736 cookie->secure ? "TRUE" : "FALSE",
8737 cookie->expires ?
8738 (gulong)soup_date_to_time_t(cookie->expires) :
8740 cookie->name,
8741 cookie->value);
8742 fflush(r_cookie_f);
8743 fclose(r_cookie_f);
8745 if (!allow_volatile_cookies)
8746 return;
8749 if (cookie->expires == NULL && session_timeout) {
8750 soup_cookie_set_expires(cookie,
8751 soup_date_new_from_now(session_timeout));
8752 print_cookie("modified add cookie", cookie);
8755 /* see if we are white listed for persistence */
8756 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
8757 /* add to persistent jar */
8758 c = soup_cookie_copy(cookie);
8759 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
8760 _soup_cookie_jar_add_cookie(p_cookiejar, c);
8763 /* add to session jar */
8764 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
8765 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
8768 void
8769 setup_cookies(void)
8771 char file[PATH_MAX];
8773 set_hook((void *)&_soup_cookie_jar_add_cookie,
8774 "soup_cookie_jar_add_cookie");
8775 set_hook((void *)&_soup_cookie_jar_delete_cookie,
8776 "soup_cookie_jar_delete_cookie");
8778 if (cookies_enabled == 0)
8779 return;
8782 * the following code is intricate due to overriding several libsoup
8783 * functions.
8784 * do not alter order of these operations.
8787 /* rejected cookies */
8788 if (save_rejected_cookies)
8789 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
8790 XT_REJECT_FILE);
8792 /* persistent cookies */
8793 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
8794 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
8796 /* session cookies */
8797 s_cookiejar = soup_cookie_jar_new();
8798 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
8799 cookie_policy, (void *)NULL);
8800 transfer_cookies();
8802 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
8805 void
8806 setup_proxy(char *uri)
8808 if (proxy_uri) {
8809 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
8810 soup_uri_free(proxy_uri);
8811 proxy_uri = NULL;
8813 if (http_proxy) {
8814 if (http_proxy != uri) {
8815 g_free(http_proxy);
8816 http_proxy = NULL;
8820 if (uri) {
8821 http_proxy = g_strdup(uri);
8822 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
8823 proxy_uri = soup_uri_new(http_proxy);
8824 if (!(proxy_uri == NULL || !SOUP_URI_VALID_FOR_HTTP(proxy_uri)))
8825 g_object_set(session, "proxy-uri", proxy_uri,
8826 (char *)NULL);
8831 set_http_proxy(char *proxy)
8833 SoupURI *uri;
8835 if (proxy == NULL)
8836 return (1);
8838 /* see if we need to clear it instead */
8839 if (strlen(proxy) == 0) {
8840 setup_proxy(NULL);
8841 return (0);
8844 uri = soup_uri_new(proxy);
8845 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
8846 return (1);
8848 setup_proxy(proxy);
8850 soup_uri_free(uri);
8852 return (0);
8856 send_cmd_to_socket(char *cmd)
8858 int s, len, rv = 1;
8859 struct sockaddr_un sa;
8861 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8862 warnx("%s: socket", __func__);
8863 return (rv);
8866 sa.sun_family = AF_UNIX;
8867 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8868 work_dir, XT_SOCKET_FILE);
8869 len = SUN_LEN(&sa);
8871 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8872 warnx("%s: connect", __func__);
8873 goto done;
8876 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
8877 warnx("%s: send", __func__);
8878 goto done;
8881 rv = 0;
8882 done:
8883 close(s);
8884 return (rv);
8887 gboolean
8888 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
8890 int s, n;
8891 char str[XT_MAX_URL_LENGTH];
8892 socklen_t t = sizeof(struct sockaddr_un);
8893 struct sockaddr_un sa;
8894 struct passwd *p;
8895 uid_t uid;
8896 gid_t gid;
8897 struct tab *tt;
8898 gint fd = g_io_channel_unix_get_fd(source);
8900 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
8901 warn("accept");
8902 return (FALSE);
8905 if (getpeereid(s, &uid, &gid) == -1) {
8906 warn("getpeereid");
8907 return (FALSE);
8909 if (uid != getuid() || gid != getgid()) {
8910 warnx("unauthorized user");
8911 return (FALSE);
8914 p = getpwuid(uid);
8915 if (p == NULL) {
8916 warnx("not a valid user");
8917 return (FALSE);
8920 n = recv(s, str, sizeof(str), 0);
8921 if (n <= 0)
8922 return (TRUE);
8924 tt = TAILQ_LAST(&tabs, tab_list);
8925 cmd_execute(tt, str);
8926 return (TRUE);
8930 is_running(void)
8932 int s, len, rv = 1;
8933 struct sockaddr_un sa;
8935 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8936 warn("is_running: socket");
8937 return (-1);
8940 sa.sun_family = AF_UNIX;
8941 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8942 work_dir, XT_SOCKET_FILE);
8943 len = SUN_LEN(&sa);
8945 /* connect to see if there is a listener */
8946 if (connect(s, (struct sockaddr *)&sa, len) == -1)
8947 rv = 0; /* not running */
8948 else
8949 rv = 1; /* already running */
8951 close(s);
8953 return (rv);
8957 build_socket(void)
8959 int s, len;
8960 struct sockaddr_un sa;
8962 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8963 warn("build_socket: socket");
8964 return (-1);
8967 sa.sun_family = AF_UNIX;
8968 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8969 work_dir, XT_SOCKET_FILE);
8970 len = SUN_LEN(&sa);
8972 /* connect to see if there is a listener */
8973 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8974 /* no listener so we will */
8975 unlink(sa.sun_path);
8977 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
8978 warn("build_socket: bind");
8979 goto done;
8982 if (listen(s, 1) == -1) {
8983 warn("build_socket: listen");
8984 goto done;
8987 return (s);
8990 done:
8991 close(s);
8992 return (-1);
8995 gboolean
8996 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8997 GtkTreeIter *iter, struct tab *t)
8999 gchar *value;
9001 gtk_tree_model_get(model, iter, 0, &value, -1);
9002 load_uri(t, value);
9003 g_free(value);
9005 return (FALSE);
9008 gboolean
9009 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
9010 GtkTreeIter *iter, struct tab *t)
9012 gchar *value;
9014 gtk_tree_model_get(model, iter, 0, &value, -1);
9015 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
9016 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
9017 g_free(value);
9019 return (TRUE);
9022 void
9023 completion_add_uri(const gchar *uri)
9025 GtkTreeIter iter;
9027 /* add uri to list_store */
9028 gtk_list_store_append(completion_model, &iter);
9029 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
9032 gboolean
9033 completion_match(GtkEntryCompletion *completion, const gchar *key,
9034 GtkTreeIter *iter, gpointer user_data)
9036 gchar *value;
9037 gboolean match = FALSE;
9039 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
9040 -1);
9042 if (value == NULL)
9043 return FALSE;
9045 match = match_uri(value, key);
9047 g_free(value);
9048 return (match);
9051 void
9052 completion_add(struct tab *t)
9054 /* enable completion for tab */
9055 t->completion = gtk_entry_completion_new();
9056 gtk_entry_completion_set_text_column(t->completion, 0);
9057 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
9058 gtk_entry_completion_set_model(t->completion,
9059 GTK_TREE_MODEL(completion_model));
9060 gtk_entry_completion_set_match_func(t->completion, completion_match,
9061 NULL, NULL);
9062 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
9063 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
9064 g_signal_connect(G_OBJECT (t->completion), "match-selected",
9065 G_CALLBACK(completion_select_cb), t);
9066 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
9067 G_CALLBACK(completion_hover_cb), t);
9070 void
9071 xxx_dir(char *dir)
9073 struct stat sb;
9075 if (stat(dir, &sb)) {
9076 if (mkdir(dir, S_IRWXU) == -1)
9077 err(1, "mkdir %s", dir);
9078 if (stat(dir, &sb))
9079 err(1, "stat %s", dir);
9081 if (S_ISDIR(sb.st_mode) == 0)
9082 errx(1, "%s not a dir", dir);
9083 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
9084 warnx("fixing invalid permissions on %s", dir);
9085 if (chmod(dir, S_IRWXU) == -1)
9086 err(1, "chmod %s", dir);
9090 void
9091 usage(void)
9093 fprintf(stderr,
9094 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
9095 exit(0);
9098 GStaticRecMutex my_gdk_mtx = G_STATIC_REC_MUTEX_INIT;
9099 volatile int mtx_depth;
9100 int mtx_complain;
9103 * The linux flash plugin violates the gdk locking mechanism.
9104 * Work around the issue by using a recursive mutex with some match applied
9105 * to see if we hit a buggy condition.
9107 * The following code is painful so just don't read it. It really doesn't
9108 * make much sense but seems to work.
9110 void
9111 mtx_lock(void)
9113 g_static_rec_mutex_lock(&my_gdk_mtx);
9114 mtx_depth++;
9116 if (mtx_depth <= 0) {
9117 /* should not happen */
9118 show_oops(NULL, "negative mutex locking bug, trying to "
9119 "correct");
9120 fprintf(stderr, "negative mutex locking bug, trying to "
9121 "correct\n");
9122 g_static_rec_mutex_unlock_full(&my_gdk_mtx);
9123 g_static_rec_mutex_lock(&my_gdk_mtx);
9124 mtx_depth = 1;
9125 return;
9128 if (mtx_depth != 1) {
9129 /* decrease mutext depth to 1 */
9130 do {
9131 g_static_rec_mutex_unlock(&my_gdk_mtx);
9132 mtx_depth--;
9133 } while (mtx_depth > 1);
9137 void
9138 mtx_unlock(void)
9140 guint x;
9142 /* if mutex depth isn't 1 then something went bad */
9143 if (mtx_depth != 1) {
9144 x = g_static_rec_mutex_unlock_full(&my_gdk_mtx);
9145 if (x != 1) {
9146 /* should not happen */
9147 show_oops(NULL, "mutex unlocking bug, trying to "
9148 "correct");
9149 fprintf(stderr, "mutex unlocking bug, trying to "
9150 "correct\n");
9152 mtx_depth = 0;
9153 if (mtx_complain == 0) {
9154 show_oops(NULL, "buggy mutex implementation detected, "
9155 "work around implemented");
9156 fprintf(stderr, "buggy mutex implementation detected, "
9157 "work around implemented");
9158 mtx_complain = 1;
9160 return;
9163 mtx_depth--;
9164 g_static_rec_mutex_unlock(&my_gdk_mtx);
9168 main(int argc, char *argv[])
9170 struct stat sb;
9171 int c, s, optn = 0, opte = 0, focus = 1;
9172 char conf[PATH_MAX] = { '\0' };
9173 char file[PATH_MAX];
9174 char *env_proxy = NULL;
9175 char *cmd = NULL;
9176 FILE *f = NULL;
9177 struct karg a;
9178 struct sigaction sact;
9179 GIOChannel *channel;
9180 struct rlimit rlp;
9182 start_argv = argv;
9184 /* prepare gtk */
9185 #ifdef USE_THREADS
9186 g_thread_init(NULL);
9187 gdk_threads_set_lock_functions(mtx_lock, mtx_unlock);
9188 gdk_threads_init();
9189 gdk_threads_enter();
9191 gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
9192 #endif
9193 gtk_init(&argc, &argv);
9195 gnutls_global_init();
9197 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
9199 RB_INIT(&hl);
9200 RB_INIT(&js_wl);
9201 RB_INIT(&pl_wl);
9202 RB_INIT(&downloads);
9204 TAILQ_INIT(&sessions);
9205 TAILQ_INIT(&tabs);
9206 TAILQ_INIT(&mtl);
9207 TAILQ_INIT(&aliases);
9208 TAILQ_INIT(&undos);
9209 TAILQ_INIT(&kbl);
9210 TAILQ_INIT(&spl);
9211 TAILQ_INIT(&chl);
9212 TAILQ_INIT(&shl);
9214 /* fiddle with ulimits */
9215 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9216 warn("getrlimit");
9217 else {
9218 /* just use them all */
9219 rlp.rlim_cur = rlp.rlim_max;
9220 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
9221 warn("setrlimit");
9222 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
9223 warn("getrlimit");
9224 else if (rlp.rlim_cur <= 256)
9225 startpage_add("%s requires at least 256 file "
9226 "descriptors, currently it has up to %d available",
9227 __progname, rlp.rlim_cur);
9230 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
9231 switch (c) {
9232 case 'S':
9233 show_url = 0;
9234 break;
9235 case 'T':
9236 show_tabs = 0;
9237 break;
9238 case 'V':
9239 errx(0 , "Version: %s", version);
9240 break;
9241 case 'f':
9242 strlcpy(conf, optarg, sizeof(conf));
9243 break;
9244 case 's':
9245 strlcpy(named_session, optarg, sizeof(named_session));
9246 break;
9247 case 't':
9248 tabless = 1;
9249 break;
9250 case 'n':
9251 optn = 1;
9252 break;
9253 case 'e':
9254 opte = 1;
9255 break;
9256 default:
9257 usage();
9258 /* NOTREACHED */
9261 argc -= optind;
9262 argv += optind;
9264 init_keybindings();
9266 xtp_generate_keys();
9268 /* signals */
9269 bzero(&sact, sizeof(sact));
9270 sigemptyset(&sact.sa_mask);
9271 sact.sa_handler = sigchild;
9272 sact.sa_flags = SA_NOCLDSTOP;
9273 sigaction(SIGCHLD, &sact, NULL);
9275 /* set download dir */
9276 pwd = getpwuid(getuid());
9277 if (pwd == NULL)
9278 errx(1, "invalid user %d", getuid());
9279 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
9281 /* compile buffer command regexes */
9282 buffercmd_init();
9284 /* set default string settings */
9285 home = g_strdup("https://www.cyphertite.com");
9286 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
9287 resource_dir = g_strdup("/usr/local/share/xxxterm/");
9288 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
9289 cmd_font_name = g_strdup("monospace normal 9");
9290 oops_font_name = g_strdup("monospace normal 9");
9291 statusbar_font_name = g_strdup("monospace normal 9");
9292 tabbar_font_name = g_strdup("monospace normal 9");
9293 statusbar_elems = g_strdup("BP");
9294 encoding = g_strdup("ISO-8859-1");
9296 /* read config file */
9297 if (strlen(conf) == 0)
9298 snprintf(conf, sizeof conf, "%s/.%s",
9299 pwd->pw_dir, XT_CONF_FILE);
9300 config_parse(conf, 0);
9302 /* init fonts */
9303 cmd_font = pango_font_description_from_string(cmd_font_name);
9304 oops_font = pango_font_description_from_string(oops_font_name);
9305 statusbar_font = pango_font_description_from_string(statusbar_font_name);
9306 tabbar_font = pango_font_description_from_string(tabbar_font_name);
9308 /* working directory */
9309 if (strlen(work_dir) == 0)
9310 snprintf(work_dir, sizeof work_dir, "%s/%s",
9311 pwd->pw_dir, XT_DIR);
9312 xxx_dir(work_dir);
9314 /* icon cache dir */
9315 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
9316 xxx_dir(cache_dir);
9318 /* certs dir */
9319 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
9320 xxx_dir(certs_dir);
9322 /* sessions dir */
9323 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
9324 work_dir, XT_SESSIONS_DIR);
9325 xxx_dir(sessions_dir);
9327 /* runtime settings that can override config file */
9328 if (runtime_settings[0] != '\0')
9329 config_parse(runtime_settings, 1);
9331 /* download dir */
9332 if (!strcmp(download_dir, pwd->pw_dir))
9333 strlcat(download_dir, "/downloads", sizeof download_dir);
9334 xxx_dir(download_dir);
9336 /* favorites file */
9337 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
9338 if (stat(file, &sb)) {
9339 warnx("favorites file doesn't exist, creating it");
9340 if ((f = fopen(file, "w")) == NULL)
9341 err(1, "favorites");
9342 fclose(f);
9345 /* quickmarks file */
9346 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
9347 if (stat(file, &sb)) {
9348 warnx("quickmarks file doesn't exist, creating it");
9349 if ((f = fopen(file, "w")) == NULL)
9350 err(1, "quickmarks");
9351 fclose(f);
9354 /* search history */
9355 if (history_autosave) {
9356 snprintf(search_file, sizeof search_file, "%s/%s",
9357 work_dir, XT_SEARCH_FILE);
9358 if (stat(search_file, &sb)) {
9359 warnx("search history file doesn't exist, creating it");
9360 if ((f = fopen(search_file, "w")) == NULL)
9361 err(1, "search_history");
9362 fclose(f);
9364 history_read(&shl, search_file, &search_history_count);
9367 /* command history */
9368 if (history_autosave) {
9369 snprintf(command_file, sizeof command_file, "%s/%s",
9370 work_dir, XT_COMMAND_FILE);
9371 if (stat(command_file, &sb)) {
9372 warnx("command history file doesn't exist, creating it");
9373 if ((f = fopen(command_file, "w")) == NULL)
9374 err(1, "command_history");
9375 fclose(f);
9377 history_read(&chl, command_file, &cmd_history_count);
9380 /* cookies */
9381 session = webkit_get_default_session();
9382 setup_cookies();
9384 /* certs */
9385 if (ssl_ca_file) {
9386 if (stat(ssl_ca_file, &sb)) {
9387 warnx("no CA file: %s", ssl_ca_file);
9388 g_free(ssl_ca_file);
9389 ssl_ca_file = NULL;
9390 } else
9391 g_object_set(session,
9392 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
9393 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
9394 (void *)NULL);
9397 /* guess_search regex */
9398 if (url_regex == NULL)
9399 url_regex = g_strdup(XT_URL_REGEX);
9400 if (url_regex)
9401 if (regcomp(&url_re, url_regex, REG_EXTENDED | REG_NOSUB))
9402 startpage_add("invalid url regex %s", url_regex);
9404 /* proxy */
9405 env_proxy = getenv("http_proxy");
9406 if (env_proxy)
9407 setup_proxy(env_proxy);
9408 else
9409 setup_proxy(http_proxy);
9411 if (opte) {
9412 send_cmd_to_socket(argv[0]);
9413 exit(0);
9416 /* set some connection parameters */
9417 g_object_set(session, "max-conns", max_connections, (char *)NULL);
9418 g_object_set(session, "max-conns-per-host", max_host_connections,
9419 (char *)NULL);
9421 /* see if there is already an xxxterm running */
9422 if (single_instance && is_running()) {
9423 optn = 1;
9424 warnx("already running");
9427 if (optn) {
9428 while (argc) {
9429 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
9430 send_cmd_to_socket(cmd);
9431 if (cmd)
9432 g_free(cmd);
9434 argc--;
9435 argv++;
9437 exit(0);
9440 /* uri completion */
9441 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
9443 /* buffers */
9444 buffers_store = gtk_list_store_new
9445 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
9447 qmarks_load();
9449 /* go graphical */
9450 create_canvas();
9451 notebook_tab_set_visibility();
9453 if (save_global_history)
9454 restore_global_history();
9456 /* restore session list */
9457 restore_sessions_list();
9459 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
9460 restore_saved_tabs();
9461 else {
9462 a.s = named_session;
9463 a.i = XT_SES_DONOTHING;
9464 open_tabs(NULL, &a);
9467 /* see if we have an exception */
9468 if (!TAILQ_EMPTY(&spl)) {
9469 create_new_tab("about:startpage", NULL, focus, -1);
9470 focus = 0;
9473 while (argc) {
9474 create_new_tab(argv[0], NULL, focus, -1);
9475 focus = 0;
9477 argc--;
9478 argv++;
9481 if (TAILQ_EMPTY(&tabs))
9482 create_new_tab(home, NULL, 1, -1);
9484 if (enable_socket)
9485 if ((s = build_socket()) != -1) {
9486 channel = g_io_channel_unix_new(s);
9487 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
9490 gtk_main();
9492 #ifdef USE_THREADS
9493 gdk_threads_leave();
9494 g_static_rec_mutex_unlock_full(&my_gdk_mtx); /* just in case */
9495 #endif
9497 gnutls_global_deinit();
9499 if (url_regex)
9500 regfree(&url_re);
9502 return (0);