Move xtp and about stuff into it's own file
[xxxterm.git] / xxxterm.c
blobfdfd7fd2a13995f6fab28b2508dc8fa2ccc9768e
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 static 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 history {
65 RB_ENTRY(history) entry;
66 const gchar *uri;
67 const gchar *title;
69 RB_HEAD(history_list, history);
71 struct session {
72 TAILQ_ENTRY(session) entry;
73 const gchar *name;
75 TAILQ_HEAD(session_list, session);
77 struct download {
78 RB_ENTRY(download) entry;
79 int id;
80 WebKitDownload *download;
81 struct tab *tab;
83 RB_HEAD(download_list, download);
85 struct domain {
86 RB_ENTRY(domain) entry;
87 gchar *d;
88 int handy; /* app use */
90 RB_HEAD(domain_list, domain);
92 struct undo {
93 TAILQ_ENTRY(undo) entry;
94 gchar *uri;
95 GList *history;
96 int back; /* Keeps track of how many back
97 * history items there are. */
99 TAILQ_HEAD(undo_tailq, undo);
101 struct sp {
102 char *line;
103 TAILQ_ENTRY(sp) entry;
105 TAILQ_HEAD(sp_list, sp);
107 struct command_entry {
108 char *line;
109 TAILQ_ENTRY(command_entry) entry;
111 TAILQ_HEAD(command_list, command_entry);
113 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
114 int next_download_id = 1;
116 /* defines */
117 #define XT_NAME ("XXXTerm")
118 #define XT_DIR (".xxxterm")
119 #define XT_CACHE_DIR ("cache")
120 #define XT_CERT_DIR ("certs/")
121 #define XT_SESSIONS_DIR ("sessions/")
122 #define XT_CONF_FILE ("xxxterm.conf")
123 #define XT_FAVS_FILE ("favorites")
124 #define XT_QMARKS_FILE ("quickmarks")
125 #define XT_SAVED_TABS_FILE ("main_session")
126 #define XT_RESTART_TABS_FILE ("restart_tabs")
127 #define XT_SOCKET_FILE ("socket")
128 #define XT_HISTORY_FILE ("history")
129 #define XT_REJECT_FILE ("rejected.txt")
130 #define XT_COOKIE_FILE ("cookies.txt")
131 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
132 #define XT_SEARCH_FILE ("search_history")
133 #define XT_COMMAND_FILE ("command_history")
134 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
135 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
136 #define XT_DLMAN_REFRESH "10"
137 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
138 "td{overflow: hidden;" \
139 " padding: 2px 2px 2px 2px;" \
140 " border: 1px solid black;" \
141 " vertical-align:top;" \
142 " word-wrap: break-word}\n" \
143 "tr:hover{background: #ffff99}\n" \
144 "th{background-color: #cccccc;" \
145 " border: 1px solid black}\n" \
146 "table{width: 100%%;" \
147 " border: 1px black solid;" \
148 " border-collapse:collapse}\n" \
149 ".progress-outer{" \
150 "border: 1px solid black;" \
151 " height: 8px;" \
152 " width: 90%%}\n" \
153 ".progress-inner{float: left;" \
154 " height: 8px;" \
155 " background: green}\n" \
156 ".dlstatus{font-size: small;" \
157 " text-align: center}\n" \
158 "</style>\n"
159 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
160 #define XT_MAX_UNDO_CLOSE_TAB (32)
161 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
162 #define XT_PRINT_EXTRA_MARGIN 10
163 #define XT_URL_REGEX ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+\\.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
164 #define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
166 /* colors */
167 #define XT_COLOR_RED "#cc0000"
168 #define XT_COLOR_YELLOW "#ffff66"
169 #define XT_COLOR_BLUE "lightblue"
170 #define XT_COLOR_GREEN "#99ff66"
171 #define XT_COLOR_WHITE "white"
172 #define XT_COLOR_BLACK "black"
174 #define XT_COLOR_CT_BACKGROUND "#000000"
175 #define XT_COLOR_CT_INACTIVE "#dddddd"
176 #define XT_COLOR_CT_ACTIVE "#bbbb00"
177 #define XT_COLOR_CT_SEPARATOR "#555555"
179 #define XT_COLOR_SB_SEPARATOR "#555555"
181 #define XT_PROTO_DELIM "://"
184 * xxxterm "protocol" (xtp)
185 * We use this for managing stuff like downloads and favorites. They
186 * make magical HTML pages in memory which have xxxt:// links in order
187 * to communicate with xxxterm's internals. These links take the format:
188 * xxxt://class/session_key/action/arg
190 * Don't begin xtp class/actions as 0. atoi returns that on error.
192 * Typically we have not put addition of items in this framework, as
193 * adding items is either done via an ex-command or via a keybinding instead.
196 #define XT_XTP_STR "xxxt://"
198 /* XTP classes (xxxt://<class>) */
199 #define XT_XTP_INVALID 0 /* invalid */
200 #define XT_XTP_DL 1 /* downloads */
201 #define XT_XTP_HL 2 /* history */
202 #define XT_XTP_CL 3 /* cookies */
203 #define XT_XTP_FL 4 /* favorites */
205 /* XTP download actions */
206 #define XT_XTP_DL_LIST 1
207 #define XT_XTP_DL_CANCEL 2
208 #define XT_XTP_DL_REMOVE 3
210 /* XTP history actions */
211 #define XT_XTP_HL_LIST 1
212 #define XT_XTP_HL_REMOVE 2
214 /* XTP cookie actions */
215 #define XT_XTP_CL_LIST 1
216 #define XT_XTP_CL_REMOVE 2
218 /* XTP cookie actions */
219 #define XT_XTP_FL_LIST 1
220 #define XT_XTP_FL_REMOVE 2
222 /* actions */
223 #define XT_MOVE_INVALID (0)
224 #define XT_MOVE_DOWN (1)
225 #define XT_MOVE_UP (2)
226 #define XT_MOVE_BOTTOM (3)
227 #define XT_MOVE_TOP (4)
228 #define XT_MOVE_PAGEDOWN (5)
229 #define XT_MOVE_PAGEUP (6)
230 #define XT_MOVE_HALFDOWN (7)
231 #define XT_MOVE_HALFUP (8)
232 #define XT_MOVE_LEFT (9)
233 #define XT_MOVE_FARLEFT (10)
234 #define XT_MOVE_RIGHT (11)
235 #define XT_MOVE_FARRIGHT (12)
236 #define XT_MOVE_PERCENT (13)
238 #define XT_QMARK_SET (0)
239 #define XT_QMARK_OPEN (1)
240 #define XT_QMARK_TAB (2)
242 #define XT_MARK_SET (0)
243 #define XT_MARK_GOTO (1)
245 #define XT_TAB_LAST (-4)
246 #define XT_TAB_FIRST (-3)
247 #define XT_TAB_PREV (-2)
248 #define XT_TAB_NEXT (-1)
249 #define XT_TAB_INVALID (0)
250 #define XT_TAB_NEW (1)
251 #define XT_TAB_DELETE (2)
252 #define XT_TAB_DELQUIT (3)
253 #define XT_TAB_OPEN (4)
254 #define XT_TAB_UNDO_CLOSE (5)
255 #define XT_TAB_SHOW (6)
256 #define XT_TAB_HIDE (7)
257 #define XT_TAB_NEXTSTYLE (8)
259 #define XT_NAV_INVALID (0)
260 #define XT_NAV_BACK (1)
261 #define XT_NAV_FORWARD (2)
262 #define XT_NAV_RELOAD (3)
264 #define XT_FOCUS_INVALID (0)
265 #define XT_FOCUS_URI (1)
266 #define XT_FOCUS_SEARCH (2)
268 #define XT_SEARCH_INVALID (0)
269 #define XT_SEARCH_NEXT (1)
270 #define XT_SEARCH_PREV (2)
272 #define XT_PASTE_CURRENT_TAB (0)
273 #define XT_PASTE_NEW_TAB (1)
275 #define XT_ZOOM_IN (-1)
276 #define XT_ZOOM_OUT (-2)
277 #define XT_ZOOM_NORMAL (100)
279 #define XT_URL_SHOW (1)
280 #define XT_URL_HIDE (2)
282 #define XT_WL_TOGGLE (1<<0)
283 #define XT_WL_ENABLE (1<<1)
284 #define XT_WL_DISABLE (1<<2)
285 #define XT_WL_FQDN (1<<3) /* default */
286 #define XT_WL_TOPLEVEL (1<<4)
287 #define XT_WL_PERSISTENT (1<<5)
288 #define XT_WL_SESSION (1<<6)
289 #define XT_WL_RELOAD (1<<7)
291 #define XT_SHOW (1<<7)
292 #define XT_DELETE (1<<8)
293 #define XT_SAVE (1<<9)
294 #define XT_OPEN (1<<10)
296 #define XT_CMD_OPEN (0)
297 #define XT_CMD_OPEN_CURRENT (1)
298 #define XT_CMD_TABNEW (2)
299 #define XT_CMD_TABNEW_CURRENT (3)
301 #define XT_STATUS_NOTHING (0)
302 #define XT_STATUS_LINK (1)
303 #define XT_STATUS_URI (2)
304 #define XT_STATUS_LOADING (3)
306 #define XT_SES_DONOTHING (0)
307 #define XT_SES_CLOSETABS (1)
309 #define XT_BM_NORMAL (0)
310 #define XT_BM_WHITELIST (1)
311 #define XT_BM_KIOSK (2)
313 #define XT_PREFIX (1<<0)
314 #define XT_USERARG (1<<1)
315 #define XT_URLARG (1<<2)
316 #define XT_INTARG (1<<3)
317 #define XT_SESSARG (1<<4)
318 #define XT_SETARG (1<<5)
320 #define XT_HINT_NEWTAB (1<<0)
322 #define XT_MODE_INSERT (0)
323 #define XT_MODE_COMMAND (1)
325 #define XT_TABS_NORMAL 0
326 #define XT_TABS_COMPACT 1
328 #define XT_BUFCMD_SZ (8)
330 #define XT_EJS_SHOW (1<<0)
332 /* mime types */
333 struct mime_type {
334 char *mt_type;
335 char *mt_action;
336 int mt_default;
337 int mt_download;
338 TAILQ_ENTRY(mime_type) entry;
340 TAILQ_HEAD(mime_type_list, mime_type);
342 /* uri aliases */
343 struct alias {
344 char *a_name;
345 char *a_uri;
346 TAILQ_ENTRY(alias) entry;
348 TAILQ_HEAD(alias_list, alias);
350 /* settings that require restart */
351 int tabless = 0; /* allow only 1 tab */
352 int enable_socket = 0;
353 int single_instance = 0; /* only allow one xxxterm to run */
354 int fancy_bar = 1; /* fancy toolbar */
355 int browser_mode = XT_BM_NORMAL;
356 int enable_localstorage = 1;
357 char *statusbar_elems = NULL;
359 /* runtime settings */
360 int show_tabs = 1; /* show tabs on notebook */
361 int tab_style = XT_TABS_NORMAL; /* tab bar style */
362 int show_url = 1; /* show url toolbar on notebook */
363 int show_statusbar = 0; /* vimperator style status bar */
364 int ctrl_click_focus = 0; /* ctrl click gets focus */
365 int cookies_enabled = 1; /* enable cookies */
366 int read_only_cookies = 0; /* enable to not write cookies */
367 int enable_scripts = 1;
368 int enable_plugins = 1;
369 gfloat default_zoom_level = 1.0;
370 char default_script[PATH_MAX];
371 int window_height = 768;
372 int window_width = 1024;
373 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
374 int refresh_interval = 10; /* download refresh interval */
375 int enable_plugin_whitelist = 0;
376 int enable_cookie_whitelist = 0;
377 int enable_js_whitelist = 0;
378 int session_timeout = 3600; /* cookie session timeout */
379 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
380 char *ssl_ca_file = NULL;
381 char *resource_dir = NULL;
382 gboolean ssl_strict_certs = FALSE;
383 int append_next = 1; /* append tab after current tab */
384 char *home = NULL;
385 char *search_string = NULL;
386 char *http_proxy = NULL;
387 char download_dir[PATH_MAX];
388 char runtime_settings[PATH_MAX]; /* override of settings */
389 int allow_volatile_cookies = 0;
390 int save_global_history = 0; /* save global history to disk */
391 char *user_agent = NULL;
392 int save_rejected_cookies = 0;
393 int session_autosave = 0;
394 int guess_search = 0;
395 int dns_prefetch = FALSE;
396 gint max_connections = 25;
397 gint max_host_connections = 5;
398 gint enable_spell_checking = 0;
399 char *spell_check_languages = NULL;
400 int xterm_workaround = 0;
401 char *url_regex = NULL;
402 int history_autosave = 0;
403 char search_file[PATH_MAX];
404 char command_file[PATH_MAX];
405 char *encoding = NULL;
406 int autofocus_onload = 0;
408 char *cmd_font_name = NULL;
409 char *oops_font_name = NULL;
410 char *statusbar_font_name = NULL;
411 char *tabbar_font_name = NULL;
412 PangoFontDescription *cmd_font;
413 PangoFontDescription *oops_font;
414 PangoFontDescription *statusbar_font;
415 PangoFontDescription *tabbar_font;
416 char *qmarks[XT_NOMARKS];
418 int btn_down; /* M1 down in any wv */
419 regex_t url_re; /* guess_search regex */
421 struct settings;
422 struct key_binding;
423 int set_browser_mode(struct settings *, char *);
424 int set_cookie_policy(struct settings *, char *);
425 int set_download_dir(struct settings *, char *);
426 int set_default_script(struct settings *, char *);
427 int set_runtime_dir(struct settings *, char *);
428 int set_tab_style(struct settings *, char *);
429 int set_work_dir(struct settings *, char *);
430 int add_alias(struct settings *, char *);
431 int add_mime_type(struct settings *, char *);
432 int add_cookie_wl(struct settings *, char *);
433 int add_js_wl(struct settings *, char *);
434 int add_pl_wl(struct settings *, char *);
435 int add_kb(struct settings *, char *);
436 void button_set_stockid(GtkWidget *, char *);
437 GtkWidget * create_button(char *, char *, int);
439 char *get_browser_mode(struct settings *);
440 char *get_cookie_policy(struct settings *);
441 char *get_download_dir(struct settings *);
442 char *get_default_script(struct settings *);
443 char *get_runtime_dir(struct settings *);
444 char *get_tab_style(struct settings *);
445 char *get_work_dir(struct settings *);
446 void startpage_add(const char *, ...);
448 void walk_alias(struct settings *, void (*)(struct settings *,
449 char *, void *), void *);
450 void walk_cookie_wl(struct settings *, void (*)(struct settings *,
451 char *, void *), void *);
452 void walk_js_wl(struct settings *, void (*)(struct settings *,
453 char *, void *), void *);
454 void walk_pl_wl(struct settings *, void (*)(struct settings *,
455 char *, void *), void *);
456 void walk_kb(struct settings *, void (*)(struct settings *, char *,
457 void *), void *);
458 void walk_mime_type(struct settings *, void (*)(struct settings *,
459 char *, void *), void *);
461 void recalc_tabs(void);
462 void recolor_compact_tabs(void);
463 void set_current_tab(int page_num);
464 gboolean update_statusbar_position(GtkAdjustment*, gpointer);
465 void marks_clear(struct tab *t);
467 int set_http_proxy(char *);
469 struct special {
470 int (*set)(struct settings *, char *);
471 char *(*get)(struct settings *);
472 void (*walk)(struct settings *,
473 void (*cb)(struct settings *, char *, void *),
474 void *);
477 struct special s_browser_mode = {
478 set_browser_mode,
479 get_browser_mode,
480 NULL
483 struct special s_cookie = {
484 set_cookie_policy,
485 get_cookie_policy,
486 NULL
489 struct special s_alias = {
490 add_alias,
491 NULL,
492 walk_alias
495 struct special s_mime = {
496 add_mime_type,
497 NULL,
498 walk_mime_type
501 struct special s_js = {
502 add_js_wl,
503 NULL,
504 walk_js_wl
507 struct special s_pl = {
508 add_pl_wl,
509 NULL,
510 walk_pl_wl
513 struct special s_kb = {
514 add_kb,
515 NULL,
516 walk_kb
519 struct special s_cookie_wl = {
520 add_cookie_wl,
521 NULL,
522 walk_cookie_wl
525 struct special s_default_script = {
526 set_default_script,
527 get_default_script,
528 NULL
531 struct special s_download_dir = {
532 set_download_dir,
533 get_download_dir,
534 NULL
537 struct special s_work_dir = {
538 set_work_dir,
539 get_work_dir,
540 NULL
543 struct special s_tab_style = {
544 set_tab_style,
545 get_tab_style,
546 NULL
549 struct settings {
550 char *name;
551 int type;
552 #define XT_S_INVALID (0)
553 #define XT_S_INT (1)
554 #define XT_S_STR (2)
555 #define XT_S_FLOAT (3)
556 uint32_t flags;
557 #define XT_SF_RESTART (1<<0)
558 #define XT_SF_RUNTIME (1<<1)
559 int *ival;
560 char **sval;
561 struct special *s;
562 gfloat *fval;
563 int (*activate)(char *);
564 } rs[] = {
565 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
566 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
567 { "autofocus_onload", XT_S_INT, 0, &autofocus_onload, NULL, NULL },
568 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
569 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
570 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
571 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
572 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
573 { "default_script", XT_S_STR, 0, NULL, NULL,&s_default_script },
574 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
575 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
576 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
577 { "enable_plugin_whitelist", XT_S_INT, 0, &enable_plugin_whitelist, NULL, NULL },
578 { "enable_localstorage", XT_S_INT, 0, &enable_localstorage, NULL, NULL },
579 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
580 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
581 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
582 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
583 { "encoding", XT_S_STR, 0, NULL, &encoding, NULL },
584 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
585 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
586 { "history_autosave", XT_S_INT, 0, &history_autosave, NULL, NULL },
587 { "home", XT_S_STR, 0, NULL, &home, NULL },
588 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL, NULL, set_http_proxy },
589 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
590 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
591 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
592 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
593 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
594 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
595 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
596 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
597 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
598 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
599 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
600 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
601 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
602 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
603 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
604 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
605 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
606 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
607 { "statusbar_elems", XT_S_STR, 0, NULL, &statusbar_elems, NULL },
608 { "tab_style", XT_S_STR, 0, NULL, NULL,&s_tab_style },
609 { "url_regex", XT_S_STR, 0, NULL, &url_regex, NULL },
610 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
611 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
612 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
613 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
614 { "xterm_workaround", XT_S_INT, 0, &xterm_workaround, NULL, NULL },
616 /* font settings */
617 { "cmd_font", XT_S_STR, 0, NULL, &cmd_font_name, NULL },
618 { "oops_font", XT_S_STR, 0, NULL, &oops_font_name, NULL },
619 { "statusbar_font", XT_S_STR, 0, NULL, &statusbar_font_name, NULL },
620 { "tabbar_font", XT_S_STR, 0, NULL, &tabbar_font_name, NULL },
622 /* runtime settings */
623 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
624 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
625 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
626 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
627 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
628 { "pl_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_pl },
631 int about(struct tab *, struct karg *);
632 int blank(struct tab *, struct karg *);
633 int ca_cmd(struct tab *, struct karg *);
634 int cookie_show_wl(struct tab *, struct karg *);
635 int js_show_wl(struct tab *, struct karg *);
636 int pl_show_wl(struct tab *, struct karg *);
637 int help(struct tab *, struct karg *);
638 int set(struct tab *, struct karg *);
639 int stats(struct tab *, struct karg *);
640 int marco(struct tab *, struct karg *);
641 int startpage(struct tab *, struct karg *);
642 const char * marco_message(int *);
643 int xtp_page_cl(struct tab *, struct karg *);
644 int xtp_page_dl(struct tab *, struct karg *);
645 int xtp_page_fl(struct tab *, struct karg *);
646 int xtp_page_hl(struct tab *, struct karg *);
647 void xt_icon_from_file(struct tab *, char *);
648 const gchar *get_uri(struct tab *);
649 const gchar *get_title(struct tab *, bool);
651 #define XT_URI_ABOUT ("about:")
652 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
653 #define XT_URI_ABOUT_ABOUT ("about")
654 #define XT_URI_ABOUT_BLANK ("blank")
655 #define XT_URI_ABOUT_CERTS ("certs")
656 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
657 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
658 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
659 #define XT_URI_ABOUT_FAVORITES ("favorites")
660 #define XT_URI_ABOUT_HELP ("help")
661 #define XT_URI_ABOUT_HISTORY ("history")
662 #define XT_URI_ABOUT_JSWL ("jswl")
663 #define XT_URI_ABOUT_PLUGINWL ("plwl")
664 #define XT_URI_ABOUT_SET ("set")
665 #define XT_URI_ABOUT_STATS ("stats")
666 #define XT_URI_ABOUT_MARCO ("marco")
667 #define XT_URI_ABOUT_STARTPAGE ("startpage")
669 struct about_type {
670 char *name;
671 int (*func)(struct tab *, struct karg *);
672 } about_list[] = {
673 { XT_URI_ABOUT_ABOUT, about },
674 { XT_URI_ABOUT_BLANK, blank },
675 { XT_URI_ABOUT_CERTS, ca_cmd },
676 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
677 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
678 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
679 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
680 { XT_URI_ABOUT_HELP, help },
681 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
682 { XT_URI_ABOUT_JSWL, js_show_wl },
683 { XT_URI_ABOUT_SET, set },
684 { XT_URI_ABOUT_STATS, stats },
685 { XT_URI_ABOUT_MARCO, marco },
686 { XT_URI_ABOUT_STARTPAGE, startpage },
687 { XT_URI_ABOUT_PLUGINWL, pl_show_wl },
690 /* xtp tab meanings - identifies which tabs have xtp pages in (corresponding to about_list indices) */
691 #define XT_XTP_TAB_MEANING_NORMAL -1 /* normal url */
692 #define XT_XTP_TAB_MEANING_BL 1 /* about:blank in this tab */
693 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
694 #define XT_XTP_TAB_MEANING_DL 5 /* download manager in this tab */
695 #define XT_XTP_TAB_MEANING_FL 6 /* favorite manager in this tab */
696 #define XT_XTP_TAB_MEANING_HL 8 /* history manager in this tab */
698 /* globals */
699 extern char *__progname;
700 char **start_argv;
701 struct passwd *pwd;
702 GtkWidget *main_window;
703 GtkNotebook *notebook;
704 GtkWidget *tab_bar;
705 GtkWidget *arrow, *abtn;
706 struct tab_list tabs;
707 struct history_list hl;
708 struct session_list sessions;
709 struct download_list downloads;
710 struct domain_list c_wl;
711 struct domain_list js_wl;
712 struct domain_list pl_wl;
713 struct undo_tailq undos;
714 struct keybinding_list kbl;
715 struct sp_list spl;
716 struct command_list chl;
717 struct command_list shl;
718 struct command_entry *history_at;
719 struct command_entry *search_at;
720 int undo_count;
721 int updating_dl_tabs = 0;
722 int updating_hl_tabs = 0;
723 int updating_cl_tabs = 0;
724 int updating_fl_tabs = 0;
725 int cmd_history_count = 0;
726 int search_history_count = 0;
727 char *global_search;
728 long long unsigned int blocked_cookies = 0;
729 char named_session[PATH_MAX];
730 GtkListStore *completion_model;
731 GtkListStore *buffers_store;
733 void xxx_dir(char *);
734 int icon_size_map(int);
735 void completion_add(struct tab *);
736 void completion_add_uri(const gchar *);
737 void show_oops(struct tab *, const char *, ...);
739 void
740 history_delete(struct command_list *l, int *counter)
742 struct command_entry *c;
744 if (l == NULL || counter == NULL)
745 return;
747 c = TAILQ_LAST(l, command_list);
748 if (c == NULL)
749 return;
751 TAILQ_REMOVE(l, c, entry);
752 *counter -= 1;
753 g_free(c->line);
754 g_free(c);
757 void
758 history_add(struct command_list *list, char *file, char *l, int *counter)
760 struct command_entry *c;
761 FILE *f;
763 if (list == NULL || l == NULL || counter == NULL)
764 return;
766 /* don't add the same line */
767 c = TAILQ_FIRST(list);
768 if (c)
769 if (!strcmp(c->line + 1 /* skip space */, l))
770 return;
772 c = g_malloc0(sizeof *c);
773 c->line = g_strdup_printf(" %s", l);
775 *counter += 1;
776 TAILQ_INSERT_HEAD(list, c, entry);
778 if (*counter > 1000)
779 history_delete(list, counter);
781 if (history_autosave && file) {
782 f = fopen(file, "w");
783 if (f == NULL) {
784 show_oops(NULL, "couldn't write history %s", file);
785 return;
788 TAILQ_FOREACH_REVERSE(c, list, command_list, entry) {
789 c->line[0] = ' ';
790 fprintf(f, "%s\n", c->line);
793 fclose(f);
798 history_read(struct command_list *list, char *file, int *counter)
800 FILE *f;
801 char *s, line[65536];
803 if (list == NULL || file == NULL)
804 return (1);
806 f = fopen(file, "r");
807 if (f == NULL) {
808 startpage_add("couldn't open history file %s", file);
809 return (1);
812 for (;;) {
813 s = fgets(line, sizeof line, f);
814 if (s == NULL || feof(f) || ferror(f))
815 break;
816 if ((s = strchr(line, '\n')) == NULL) {
817 startpage_add("invalid history file %s", file);
818 fclose(f);
819 return (1);
821 *s = '\0';
823 history_add(list, NULL, line + 1, counter);
826 fclose(f);
828 return (0);
831 /* marks and quickmarks array storage.
832 * first a-z, then A-Z, then 0-9 */
833 char
834 indextomark(int i)
836 if (i < 0)
837 return (0);
839 if (i >= 0 && i <= 'z' - 'a')
840 return 'a' + i;
842 i -= 'z' - 'a' + 1;
843 if (i >= 0 && i <= 'Z' - 'A')
844 return 'A' + i;
846 i -= 'Z' - 'A' + 1;
847 if (i >= 10)
848 return (0);
850 return i + '0';
854 marktoindex(char m)
856 int ret = 0;
858 if (m >= 'a' && m <= 'z')
859 return ret + m - 'a';
861 ret += 'z' - 'a' + 1;
862 if (m >= 'A' && m <= 'Z')
863 return ret + m - 'A';
865 ret += 'Z' - 'A' + 1;
866 if (m >= '0' && m <= '9')
867 return ret + m - '0';
869 return (-1);
873 void
874 sigchild(int sig)
876 int saved_errno, status;
877 pid_t pid;
879 saved_errno = errno;
881 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
882 if (pid == -1) {
883 if (errno == EINTR)
884 continue;
885 if (errno != ECHILD) {
887 clog_warn("sigchild: waitpid:");
890 break;
893 if (WIFEXITED(status)) {
894 if (WEXITSTATUS(status) != 0) {
896 clog_warnx("sigchild: child exit status: %d",
897 WEXITSTATUS(status));
900 } else {
902 clog_warnx("sigchild: child is terminated abnormally");
907 errno = saved_errno;
911 is_g_object_setting(GObject *o, char *str)
913 guint n_props = 0, i;
914 GParamSpec **proplist;
916 if (! G_IS_OBJECT(o))
917 return (0);
919 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
920 &n_props);
922 for (i=0; i < n_props; i++) {
923 if (! strcmp(proplist[i]->name, str))
924 return (1);
926 return (0);
929 gchar *
930 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
932 gchar *r;
934 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
935 "<head>\n"
936 "<title>%s</title>\n"
937 "%s"
938 "%s"
939 "</head>\n"
940 "<body>\n"
941 "<h1>%s</h1>\n"
942 "%s\n</body>\n"
943 "</html>",
944 title,
945 addstyles ? XT_PAGE_STYLE : "",
946 head,
947 title,
948 body);
950 return r;
954 * Display a web page from a HTML string in memory, rather than from a URL
956 void
957 load_webkit_string(struct tab *t, const char *str, gchar *title)
959 char file[PATH_MAX];
960 int i;
962 /* we set this to indicate we want to manually do navaction */
963 if (t->bfl)
964 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
966 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
967 if (title) {
968 /* set t->xtp_meaning */
969 for (i = 0; i < LENGTH(about_list); i++)
970 if (!strcmp(title, about_list[i].name)) {
971 t->xtp_meaning = i;
972 break;
975 webkit_web_view_load_string(t->wv, str, NULL, encoding,
976 "file://");
977 #if GTK_CHECK_VERSION(2, 20, 0)
978 gtk_spinner_stop(GTK_SPINNER(t->spinner));
979 gtk_widget_hide(t->spinner);
980 #endif
981 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
982 xt_icon_from_file(t, file);
986 struct tab *
987 get_current_tab(void)
989 struct tab *t;
991 TAILQ_FOREACH(t, &tabs, entry) {
992 if (t->tab_id == gtk_notebook_get_current_page(notebook))
993 return (t);
996 warnx("%s: no current tab", __func__);
998 return (NULL);
1001 void
1002 set_status(struct tab *t, gchar *s, int status)
1004 gchar *type = NULL;
1006 if (s == NULL)
1007 return;
1009 switch (status) {
1010 case XT_STATUS_LOADING:
1011 type = g_strdup_printf("Loading: %s", s);
1012 s = type;
1013 break;
1014 case XT_STATUS_LINK:
1015 type = g_strdup_printf("Link: %s", s);
1016 if (!t->status)
1017 t->status = g_strdup(gtk_entry_get_text(
1018 GTK_ENTRY(t->sbe.statusbar)));
1019 s = type;
1020 break;
1021 case XT_STATUS_URI:
1022 type = g_strdup_printf("%s", s);
1023 if (!t->status) {
1024 t->status = g_strdup(type);
1026 s = type;
1027 if (!t->status)
1028 t->status = g_strdup(s);
1029 break;
1030 case XT_STATUS_NOTHING:
1031 /* FALL THROUGH */
1032 default:
1033 break;
1035 gtk_entry_set_text(GTK_ENTRY(t->sbe.statusbar), s);
1036 if (type)
1037 g_free(type);
1040 void
1041 hide_cmd(struct tab *t)
1043 history_at = NULL; /* just in case */
1044 search_at = NULL; /* just in case */
1045 gtk_widget_hide(t->cmd);
1048 void
1049 show_cmd(struct tab *t)
1051 history_at = NULL;
1052 search_at = NULL;
1053 gtk_widget_hide(t->oops);
1054 gtk_widget_show(t->cmd);
1057 void
1058 hide_buffers(struct tab *t)
1060 gtk_widget_hide(t->buffers);
1061 gtk_list_store_clear(buffers_store);
1064 enum {
1065 COL_ID = 0,
1066 COL_TITLE,
1067 NUM_COLS
1071 sort_tabs_by_page_num(struct tab ***stabs)
1073 int num_tabs = 0;
1074 struct tab *t;
1076 num_tabs = gtk_notebook_get_n_pages(notebook);
1078 *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
1080 TAILQ_FOREACH(t, &tabs, entry)
1081 (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
1083 return (num_tabs);
1086 void
1087 buffers_make_list(void)
1089 int i, num_tabs;
1090 const gchar *title = NULL;
1091 GtkTreeIter iter;
1092 struct tab **stabs = NULL;
1094 num_tabs = sort_tabs_by_page_num(&stabs);
1096 for (i = 0; i < num_tabs; i++)
1097 if (stabs[i]) {
1098 gtk_list_store_append(buffers_store, &iter);
1099 title = get_title(stabs[i], FALSE);
1100 gtk_list_store_set(buffers_store, &iter,
1101 COL_ID, i + 1, /* Enumerate the tabs starting from 1
1102 * rather than 0. */
1103 COL_TITLE, title,
1104 -1);
1107 g_free(stabs);
1110 void
1111 show_buffers(struct tab *t)
1113 buffers_make_list();
1114 gtk_widget_show(t->buffers);
1115 gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
1118 void
1119 toggle_buffers(struct tab *t)
1121 if (gtk_widget_get_visible(t->buffers))
1122 hide_buffers(t);
1123 else
1124 show_buffers(t);
1128 buffers(struct tab *t, struct karg *args)
1130 show_buffers(t);
1132 return (0);
1135 void
1136 hide_oops(struct tab *t)
1138 gtk_widget_hide(t->oops);
1141 void
1142 show_oops(struct tab *at, const char *fmt, ...)
1144 va_list ap;
1145 char *msg = NULL;
1146 struct tab *t = NULL;
1148 if (fmt == NULL)
1149 return;
1151 if (at == NULL) {
1152 if ((t = get_current_tab()) == NULL)
1153 return;
1154 } else
1155 t = at;
1157 va_start(ap, fmt);
1158 if (vasprintf(&msg, fmt, ap) == -1)
1159 errx(1, "show_oops failed");
1160 va_end(ap);
1162 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
1163 gtk_widget_hide(t->cmd);
1164 gtk_widget_show(t->oops);
1166 if (msg)
1167 free(msg);
1170 char *
1171 get_as_string(struct settings *s)
1173 char *r = NULL;
1175 if (s == NULL)
1176 return (NULL);
1178 if (s->s) {
1179 if (s->s->get)
1180 r = s->s->get(s);
1181 else
1182 warnx("get_as_string skip %s\n", s->name);
1183 } else if (s->type == XT_S_INT)
1184 r = g_strdup_printf("%d", *s->ival);
1185 else if (s->type == XT_S_STR)
1186 r = g_strdup(*s->sval);
1187 else if (s->type == XT_S_FLOAT)
1188 r = g_strdup_printf("%f", *s->fval);
1189 else
1190 r = g_strdup_printf("INVALID TYPE");
1192 return (r);
1195 void
1196 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1198 int i;
1199 char *s;
1201 for (i = 0; i < LENGTH(rs); i++) {
1202 if (rs[i].s && rs[i].s->walk)
1203 rs[i].s->walk(&rs[i], cb, cb_args);
1204 else {
1205 s = get_as_string(&rs[i]);
1206 cb(&rs[i], s, cb_args);
1207 g_free(s);
1213 set_browser_mode(struct settings *s, char *val)
1215 if (!strcmp(val, "whitelist")) {
1216 browser_mode = XT_BM_WHITELIST;
1217 allow_volatile_cookies = 0;
1218 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1219 cookies_enabled = 1;
1220 enable_cookie_whitelist = 1;
1221 enable_plugin_whitelist = 1;
1222 enable_plugins = 0;
1223 read_only_cookies = 0;
1224 save_rejected_cookies = 0;
1225 session_timeout = 3600;
1226 enable_scripts = 0;
1227 enable_js_whitelist = 1;
1228 enable_localstorage = 0;
1229 } else if (!strcmp(val, "normal")) {
1230 browser_mode = XT_BM_NORMAL;
1231 allow_volatile_cookies = 0;
1232 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1233 cookies_enabled = 1;
1234 enable_cookie_whitelist = 0;
1235 enable_plugin_whitelist = 0;
1236 enable_plugins = 1;
1237 read_only_cookies = 0;
1238 save_rejected_cookies = 0;
1239 session_timeout = 3600;
1240 enable_scripts = 1;
1241 enable_js_whitelist = 0;
1242 enable_localstorage = 1;
1243 } else if (!strcmp(val, "kiosk")) {
1244 browser_mode = XT_BM_KIOSK;
1245 allow_volatile_cookies = 0;
1246 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1247 cookies_enabled = 1;
1248 enable_cookie_whitelist = 0;
1249 enable_plugin_whitelist = 0;
1250 enable_plugins = 1;
1251 read_only_cookies = 0;
1252 save_rejected_cookies = 0;
1253 session_timeout = 3600;
1254 enable_scripts = 1;
1255 enable_js_whitelist = 0;
1256 enable_localstorage = 1;
1257 show_tabs = 0;
1258 tabless = 1;
1259 } else
1260 return (1);
1262 return (0);
1265 char *
1266 get_browser_mode(struct settings *s)
1268 char *r = NULL;
1270 if (browser_mode == XT_BM_WHITELIST)
1271 r = g_strdup("whitelist");
1272 else if (browser_mode == XT_BM_NORMAL)
1273 r = g_strdup("normal");
1274 else if (browser_mode == XT_BM_KIOSK)
1275 r = g_strdup("kiosk");
1276 else
1277 return (NULL);
1279 return (r);
1283 set_cookie_policy(struct settings *s, char *val)
1285 if (!strcmp(val, "no3rdparty"))
1286 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1287 else if (!strcmp(val, "accept"))
1288 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1289 else if (!strcmp(val, "reject"))
1290 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1291 else
1292 return (1);
1294 return (0);
1297 char *
1298 get_cookie_policy(struct settings *s)
1300 char *r = NULL;
1302 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1303 r = g_strdup("no3rdparty");
1304 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1305 r = g_strdup("accept");
1306 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1307 r = g_strdup("reject");
1308 else
1309 return (NULL);
1311 return (r);
1314 char *
1315 get_default_script(struct settings *s)
1317 if (default_script[0] == '\0')
1318 return (0);
1319 return (g_strdup(default_script));
1323 set_default_script(struct settings *s, char *val)
1325 if (val[0] == '~')
1326 snprintf(default_script, sizeof default_script, "%s/%s",
1327 pwd->pw_dir, &val[1]);
1328 else
1329 strlcpy(default_script, val, sizeof default_script);
1331 return (0);
1334 char *
1335 get_download_dir(struct settings *s)
1337 if (download_dir[0] == '\0')
1338 return (0);
1339 return (g_strdup(download_dir));
1343 set_download_dir(struct settings *s, char *val)
1345 if (val[0] == '~')
1346 snprintf(download_dir, sizeof download_dir, "%s/%s",
1347 pwd->pw_dir, &val[1]);
1348 else
1349 strlcpy(download_dir, val, sizeof download_dir);
1351 return (0);
1355 * Session IDs.
1356 * We use these to prevent people putting xxxt:// URLs on
1357 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1359 #define XT_XTP_SES_KEY_SZ 8
1360 #define XT_XTP_SES_KEY_HEX_FMT \
1361 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1362 char *dl_session_key; /* downloads */
1363 char *hl_session_key; /* history list */
1364 char *cl_session_key; /* cookie list */
1365 char *fl_session_key; /* favorites list */
1367 char work_dir[PATH_MAX];
1368 char certs_dir[PATH_MAX];
1369 char cache_dir[PATH_MAX];
1370 char sessions_dir[PATH_MAX];
1371 char cookie_file[PATH_MAX];
1372 SoupURI *proxy_uri = NULL;
1373 SoupSession *session;
1374 SoupCookieJar *s_cookiejar;
1375 SoupCookieJar *p_cookiejar;
1376 char rc_fname[PATH_MAX];
1378 struct mime_type_list mtl;
1379 struct alias_list aliases;
1381 /* protos */
1382 struct tab *create_new_tab(char *, struct undo *, int, int);
1383 void delete_tab(struct tab *);
1384 void setzoom_webkit(struct tab *, int);
1385 int run_script(struct tab *, char *);
1386 int download_rb_cmp(struct download *, struct download *);
1387 gboolean cmd_execute(struct tab *t, char *str);
1390 history_rb_cmp(struct history *h1, struct history *h2)
1392 return (strcmp(h1->uri, h2->uri));
1394 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1397 domain_rb_cmp(struct domain *d1, struct domain *d2)
1399 return (strcmp(d1->d, d2->d));
1401 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1403 char *
1404 get_work_dir(struct settings *s)
1406 if (work_dir[0] == '\0')
1407 return (0);
1408 return (g_strdup(work_dir));
1412 set_work_dir(struct settings *s, char *val)
1414 if (val[0] == '~')
1415 snprintf(work_dir, sizeof work_dir, "%s/%s",
1416 pwd->pw_dir, &val[1]);
1417 else
1418 strlcpy(work_dir, val, sizeof work_dir);
1420 return (0);
1423 char *
1424 get_tab_style(struct settings *s)
1426 if (tab_style == XT_TABS_NORMAL)
1427 return (g_strdup("normal"));
1428 else
1429 return (g_strdup("compact"));
1433 set_tab_style(struct settings *s, char *val)
1435 if (!strcmp(val, "normal"))
1436 tab_style = XT_TABS_NORMAL;
1437 else if (!strcmp(val, "compact"))
1438 tab_style = XT_TABS_COMPACT;
1439 else
1440 return (1);
1442 return (0);
1446 * generate a session key to secure xtp commands.
1447 * pass in a ptr to the key in question and it will
1448 * be modified in place.
1450 void
1451 generate_xtp_session_key(char **key)
1453 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1455 /* free old key */
1456 if (*key)
1457 g_free(*key);
1459 /* make a new one */
1460 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1461 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1462 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1463 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1465 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1469 * validate a xtp session key.
1470 * return (1) if OK
1473 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1475 if (strcmp(trusted, untrusted) != 0) {
1476 show_oops(t, "%s: xtp session key mismatch possible spoof",
1477 __func__);
1478 return (0);
1481 return (1);
1485 download_rb_cmp(struct download *e1, struct download *e2)
1487 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1489 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1491 struct valid_url_types {
1492 char *type;
1493 } vut[] = {
1494 { "http://" },
1495 { "https://" },
1496 { "ftp://" },
1497 { "file://" },
1498 { XT_XTP_STR },
1502 valid_url_type(char *url)
1504 int i;
1506 for (i = 0; i < LENGTH(vut); i++)
1507 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1508 return (0);
1510 return (1);
1513 void
1514 print_cookie(char *msg, SoupCookie *c)
1516 if (c == NULL)
1517 return;
1519 if (msg)
1520 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1521 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1522 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1523 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1524 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1525 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1526 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1527 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1528 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1529 DNPRINTF(XT_D_COOKIE, "====================================\n");
1532 void
1533 walk_alias(struct settings *s,
1534 void (*cb)(struct settings *, char *, void *), void *cb_args)
1536 struct alias *a;
1537 char *str;
1539 if (s == NULL || cb == NULL) {
1540 show_oops(NULL, "walk_alias invalid parameters");
1541 return;
1544 TAILQ_FOREACH(a, &aliases, entry) {
1545 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1546 cb(s, str, cb_args);
1547 g_free(str);
1551 char *
1552 match_alias(char *url_in)
1554 struct alias *a;
1555 char *arg;
1556 char *url_out = NULL, *search, *enc_arg;
1558 search = g_strdup(url_in);
1559 arg = search;
1560 if (strsep(&arg, " \t") == NULL) {
1561 show_oops(NULL, "match_alias: NULL URL");
1562 goto done;
1565 TAILQ_FOREACH(a, &aliases, entry) {
1566 if (!strcmp(search, a->a_name))
1567 break;
1570 if (a != NULL) {
1571 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1572 a->a_name);
1573 if (arg != NULL) {
1574 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1575 url_out = g_strdup_printf(a->a_uri, enc_arg);
1576 g_free(enc_arg);
1577 } else
1578 url_out = g_strdup_printf(a->a_uri, "");
1580 done:
1581 g_free(search);
1582 return (url_out);
1585 char *
1586 guess_url_type(char *url_in)
1588 struct stat sb;
1589 char *url_out = NULL, *enc_search = NULL;
1590 int i;
1592 /* substitute aliases */
1593 url_out = match_alias(url_in);
1594 if (url_out != NULL)
1595 return (url_out);
1597 /* see if we are an about page */
1598 if (!strncmp(url_in, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
1599 for (i = 0; i < LENGTH(about_list); i++)
1600 if (!strcmp(&url_in[XT_URI_ABOUT_LEN],
1601 about_list[i].name)) {
1602 url_out = g_strdup(url_in);
1603 goto done;
1606 if (guess_search && url_regex &&
1607 !(g_str_has_prefix(url_in, "http://") ||
1608 g_str_has_prefix(url_in, "https://"))) {
1609 if (regexec(&url_re, url_in, 0, NULL, 0)) {
1610 /* invalid URI so search instead */
1611 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1612 url_out = g_strdup_printf(search_string, enc_search);
1613 g_free(enc_search);
1614 goto done;
1618 /* XXX not sure about this heuristic */
1619 if (stat(url_in, &sb) == 0)
1620 url_out = g_strdup_printf("file://%s", url_in);
1621 else
1622 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1623 done:
1624 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1626 return (url_out);
1629 void
1630 load_uri(struct tab *t, gchar *uri)
1632 struct karg args;
1633 gchar *newuri = NULL;
1634 int i;
1636 if (uri == NULL)
1637 return;
1639 /* Strip leading spaces. */
1640 while (*uri && isspace(*uri))
1641 uri++;
1643 if (strlen(uri) == 0) {
1644 blank(t, NULL);
1645 return;
1648 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
1650 if (valid_url_type(uri)) {
1651 newuri = guess_url_type(uri);
1652 uri = newuri;
1655 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1656 for (i = 0; i < LENGTH(about_list); i++)
1657 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1658 bzero(&args, sizeof args);
1659 about_list[i].func(t, &args);
1660 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1661 FALSE);
1662 goto done;
1664 show_oops(t, "invalid about page");
1665 goto done;
1668 set_status(t, (char *)uri, XT_STATUS_LOADING);
1669 marks_clear(t);
1670 webkit_web_view_load_uri(t->wv, uri);
1671 done:
1672 if (newuri)
1673 g_free(newuri);
1676 const gchar *
1677 get_uri(struct tab *t)
1679 const gchar *uri = NULL;
1681 if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED)
1682 return NULL;
1683 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL) {
1684 uri = webkit_web_view_get_uri(t->wv);
1685 } else {
1686 /* use tmp_uri to make sure it is g_freed */
1687 if (t->tmp_uri)
1688 g_free(t->tmp_uri);
1689 t->tmp_uri =g_strdup_printf("%s%s", XT_URI_ABOUT,
1690 about_list[t->xtp_meaning].name);
1691 uri = t->tmp_uri;
1693 return uri;
1696 const gchar *
1697 get_title(struct tab *t, bool window)
1699 const gchar *set = NULL, *title = NULL;
1700 WebKitLoadStatus status = webkit_web_view_get_load_status(t->wv);
1702 if (status == WEBKIT_LOAD_PROVISIONAL || status == WEBKIT_LOAD_FAILED ||
1703 t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
1704 goto notitle;
1706 title = webkit_web_view_get_title(t->wv);
1707 if ((set = title ? title : get_uri(t)))
1708 return set;
1710 notitle:
1711 set = window ? XT_NAME : "(untitled)";
1713 return set;
1717 add_alias(struct settings *s, char *line)
1719 char *l, *alias;
1720 struct alias *a = NULL;
1722 if (s == NULL || line == NULL) {
1723 show_oops(NULL, "add_alias invalid parameters");
1724 return (1);
1727 l = line;
1728 a = g_malloc(sizeof(*a));
1730 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1731 show_oops(NULL, "add_alias: incomplete alias definition");
1732 goto bad;
1734 if (strlen(alias) == 0 || strlen(l) == 0) {
1735 show_oops(NULL, "add_alias: invalid alias definition");
1736 goto bad;
1739 a->a_name = g_strdup(alias);
1740 a->a_uri = g_strdup(l);
1742 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1744 TAILQ_INSERT_TAIL(&aliases, a, entry);
1746 return (0);
1747 bad:
1748 if (a)
1749 g_free(a);
1750 return (1);
1754 add_mime_type(struct settings *s, char *line)
1756 char *mime_type;
1757 char *l;
1758 struct mime_type *m = NULL;
1759 int downloadfirst = 0;
1761 /* XXX this could be smarter */
1763 if (line == NULL || strlen(line) == 0) {
1764 show_oops(NULL, "add_mime_type invalid parameters");
1765 return (1);
1768 l = line;
1769 if (*l == '@') {
1770 downloadfirst = 1;
1771 l++;
1773 m = g_malloc(sizeof(*m));
1775 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1776 show_oops(NULL, "add_mime_type: invalid mime_type");
1777 goto bad;
1779 if (mime_type[strlen(mime_type) - 1] == '*') {
1780 mime_type[strlen(mime_type) - 1] = '\0';
1781 m->mt_default = 1;
1782 } else
1783 m->mt_default = 0;
1785 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1786 show_oops(NULL, "add_mime_type: invalid mime_type");
1787 goto bad;
1790 m->mt_type = g_strdup(mime_type);
1791 m->mt_action = g_strdup(l);
1792 m->mt_download = downloadfirst;
1794 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1795 m->mt_type, m->mt_action, m->mt_default);
1797 TAILQ_INSERT_TAIL(&mtl, m, entry);
1799 return (0);
1800 bad:
1801 if (m)
1802 g_free(m);
1803 return (1);
1806 struct mime_type *
1807 find_mime_type(char *mime_type)
1809 struct mime_type *m, *def = NULL, *rv = NULL;
1811 TAILQ_FOREACH(m, &mtl, entry) {
1812 if (m->mt_default &&
1813 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1814 def = m;
1816 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1817 rv = m;
1818 break;
1822 if (rv == NULL)
1823 rv = def;
1825 return (rv);
1828 void
1829 walk_mime_type(struct settings *s,
1830 void (*cb)(struct settings *, char *, void *), void *cb_args)
1832 struct mime_type *m;
1833 char *str;
1835 if (s == NULL || cb == NULL) {
1836 show_oops(NULL, "walk_mime_type invalid parameters");
1837 return;
1840 TAILQ_FOREACH(m, &mtl, entry) {
1841 str = g_strdup_printf("%s%s --> %s",
1842 m->mt_type,
1843 m->mt_default ? "*" : "",
1844 m->mt_action);
1845 cb(s, str, cb_args);
1846 g_free(str);
1850 void
1851 wl_add(char *str, struct domain_list *wl, int handy)
1853 struct domain *d;
1854 int add_dot = 0;
1855 char *p;
1857 if (str == NULL || wl == NULL || strlen(str) < 2)
1858 return;
1860 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1862 /* treat *.moo.com the same as .moo.com */
1863 if (str[0] == '*' && str[1] == '.')
1864 str = &str[1];
1865 else if (str[0] == '.')
1866 str = &str[0];
1867 else
1868 add_dot = 1;
1870 /* slice off port number */
1871 p = g_strrstr(str, ":");
1872 if (p)
1873 *p = '\0';
1875 d = g_malloc(sizeof *d);
1876 if (add_dot)
1877 d->d = g_strdup_printf(".%s", str);
1878 else
1879 d->d = g_strdup(str);
1880 d->handy = handy;
1882 if (RB_INSERT(domain_list, wl, d))
1883 goto unwind;
1885 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1886 return;
1887 unwind:
1888 if (d) {
1889 if (d->d)
1890 g_free(d->d);
1891 g_free(d);
1896 add_cookie_wl(struct settings *s, char *entry)
1898 wl_add(entry, &c_wl, 1);
1899 return (0);
1902 void
1903 walk_cookie_wl(struct settings *s,
1904 void (*cb)(struct settings *, char *, void *), void *cb_args)
1906 struct domain *d;
1908 if (s == NULL || cb == NULL) {
1909 show_oops(NULL, "walk_cookie_wl invalid parameters");
1910 return;
1913 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1914 cb(s, d->d, cb_args);
1917 void
1918 walk_js_wl(struct settings *s,
1919 void (*cb)(struct settings *, char *, void *), void *cb_args)
1921 struct domain *d;
1923 if (s == NULL || cb == NULL) {
1924 show_oops(NULL, "walk_js_wl invalid parameters");
1925 return;
1928 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1929 cb(s, d->d, cb_args);
1933 add_js_wl(struct settings *s, char *entry)
1935 wl_add(entry, &js_wl, 1 /* persistent */);
1936 return (0);
1939 void
1940 walk_pl_wl(struct settings *s,
1941 void (*cb)(struct settings *, char *, void *), void *cb_args)
1943 struct domain *d;
1945 if (s == NULL || cb == NULL) {
1946 show_oops(NULL, "walk_pl_wl invalid parameters");
1947 return;
1950 RB_FOREACH_REVERSE(d, domain_list, &pl_wl)
1951 cb(s, d->d, cb_args);
1955 add_pl_wl(struct settings *s, char *entry)
1957 wl_add(entry, &pl_wl, 1 /* persistent */);
1958 return (0);
1961 struct domain *
1962 wl_find(const gchar *search, struct domain_list *wl)
1964 int i;
1965 struct domain *d = NULL, dfind;
1966 gchar *s = NULL;
1968 if (search == NULL || wl == NULL)
1969 return (NULL);
1970 if (strlen(search) < 2)
1971 return (NULL);
1973 if (search[0] != '.')
1974 s = g_strdup_printf(".%s", search);
1975 else
1976 s = g_strdup(search);
1978 for (i = strlen(s) - 1; i >= 0; i--) {
1979 if (s[i] == '.') {
1980 dfind.d = &s[i];
1981 d = RB_FIND(domain_list, wl, &dfind);
1982 if (d)
1983 goto done;
1987 done:
1988 if (s)
1989 g_free(s);
1991 return (d);
1994 struct domain *
1995 wl_find_uri(const gchar *s, struct domain_list *wl)
1997 int i;
1998 char *ss;
1999 struct domain *r;
2001 if (s == NULL || wl == NULL)
2002 return (NULL);
2004 if (!strncmp(s, "http://", strlen("http://")))
2005 s = &s[strlen("http://")];
2006 else if (!strncmp(s, "https://", strlen("https://")))
2007 s = &s[strlen("https://")];
2009 if (strlen(s) < 2)
2010 return (NULL);
2012 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
2013 /* chop string at first slash */
2014 if (s[i] == '/' || s[i] == ':' || s[i] == '\0') {
2015 ss = g_strdup(s);
2016 ss[i] = '\0';
2017 r = wl_find(ss, wl);
2018 g_free(ss);
2019 return (r);
2022 return (NULL);
2026 settings_add(char *var, char *val)
2028 int i, rv, *p;
2029 gfloat *f;
2030 char **s;
2032 /* get settings */
2033 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
2034 if (strcmp(var, rs[i].name))
2035 continue;
2037 if (rs[i].s) {
2038 if (rs[i].s->set(&rs[i], val))
2039 errx(1, "invalid value for %s: %s", var, val);
2040 rv = 1;
2041 break;
2042 } else
2043 switch (rs[i].type) {
2044 case XT_S_INT:
2045 p = rs[i].ival;
2046 *p = atoi(val);
2047 rv = 1;
2048 break;
2049 case XT_S_STR:
2050 s = rs[i].sval;
2051 if (s == NULL)
2052 errx(1, "invalid sval for %s",
2053 rs[i].name);
2054 if (*s)
2055 g_free(*s);
2056 *s = g_strdup(val);
2057 rv = 1;
2058 break;
2059 case XT_S_FLOAT:
2060 f = rs[i].fval;
2061 *f = atof(val);
2062 rv = 1;
2063 break;
2064 case XT_S_INVALID:
2065 default:
2066 errx(1, "invalid type for %s", var);
2068 break;
2070 return (rv);
2073 #define WS "\n= \t"
2074 void
2075 config_parse(char *filename, int runtime)
2077 FILE *config, *f;
2078 char *line, *cp, *var, *val;
2079 size_t len, lineno = 0;
2080 int handled;
2081 char file[PATH_MAX];
2082 struct stat sb;
2084 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
2086 if (filename == NULL)
2087 return;
2089 if (runtime && runtime_settings[0] != '\0') {
2090 snprintf(file, sizeof file, "%s/%s",
2091 work_dir, runtime_settings);
2092 if (stat(file, &sb)) {
2093 warnx("runtime file doesn't exist, creating it");
2094 if ((f = fopen(file, "w")) == NULL)
2095 err(1, "runtime");
2096 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
2097 fclose(f);
2099 } else
2100 strlcpy(file, filename, sizeof file);
2102 if ((config = fopen(file, "r")) == NULL) {
2103 warn("config_parse: cannot open %s", filename);
2104 return;
2107 for (;;) {
2108 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
2109 if (feof(config) || ferror(config))
2110 break;
2112 cp = line;
2113 cp += (long)strspn(cp, WS);
2114 if (cp[0] == '\0') {
2115 /* empty line */
2116 free(line);
2117 continue;
2120 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
2121 startpage_add("invalid configuration file entry: %s",
2122 line);
2123 else {
2124 cp += (long)strspn(cp, WS);
2126 if ((val = strsep(&cp, "\0")) == NULL)
2127 break;
2129 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",
2130 var, val);
2131 handled = settings_add(var, val);
2133 if (handled == 0)
2134 startpage_add("invalid configuration file entry"
2135 ": %s=%s", var, val);
2138 free(line);
2141 fclose(config);
2144 char *
2145 js_ref_to_string(JSContextRef context, JSValueRef ref)
2147 char *s = NULL;
2148 size_t l;
2149 JSStringRef jsref;
2151 jsref = JSValueToStringCopy(context, ref, NULL);
2152 if (jsref == NULL)
2153 return (NULL);
2155 l = JSStringGetMaximumUTF8CStringSize(jsref);
2156 s = g_malloc(l);
2157 if (s)
2158 JSStringGetUTF8CString(jsref, s, l);
2159 JSStringRelease(jsref);
2161 return (s);
2164 void
2165 disable_hints(struct tab *t)
2167 DNPRINTF(XT_D_JS, "%s\n", __func__);
2169 run_script(t, "hints.clearHints();");
2170 t->hints_on = 0;
2171 t->new_tab = 0;
2174 void
2175 enable_hints(struct tab *t)
2177 DNPRINTF(XT_D_JS, "%s\n", __func__);
2179 run_script(t, JS_HINTING);
2181 if (t->new_tab)
2182 run_script(t, "hints.createHints('', 'F');");
2183 else
2184 run_script(t, "hints.createHints('', 'f');");
2185 t->hints_on = 1;
2188 #define XT_JS_DONE ("done;")
2189 #define XT_JS_DONE_LEN (strlen(XT_JS_DONE))
2190 #define XT_JS_INSERT ("insert;")
2191 #define XT_JS_INSERT_LEN (strlen(XT_JS_INSERT))
2194 run_script(struct tab *t, char *s)
2196 JSGlobalContextRef ctx;
2197 WebKitWebFrame *frame;
2198 JSStringRef str;
2199 JSValueRef val, exception;
2200 char *es;
2202 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
2203 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
2205 /* a bit silly but it prevents some heartburn */
2206 if (t->script_init == 0) {
2207 t->script_init = 1;
2208 run_script(t, JS_HINTING);
2211 frame = webkit_web_view_get_main_frame(t->wv);
2212 ctx = webkit_web_frame_get_global_context(frame);
2214 str = JSStringCreateWithUTF8CString(s);
2215 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
2216 NULL, 0, &exception);
2217 JSStringRelease(str);
2219 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
2220 if (val == NULL) {
2221 es = js_ref_to_string(ctx, exception);
2222 if (es) {
2223 /* show_oops(t, "script exception: %s", es); */
2224 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
2225 g_free(es);
2227 return (1);
2228 } else {
2229 es = js_ref_to_string(ctx, val);
2230 #if 0
2231 /* return values */
2232 if (!strncmp(es, XT_JS_DONE, XT_JS_DONE_LEN))
2233 ; /* do nothing */
2234 if (!strncmp(es, XT_JS_INSERT, XT_JS_INSERT_LEN))
2235 ; /* do nothing */
2236 #endif
2237 if (es) {
2238 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
2239 g_free(es);
2243 return (0);
2247 hint(struct tab *t, struct karg *args)
2250 DNPRINTF(XT_D_JS, "hint: tab %d args %d\n", t->tab_id, args->i);
2252 if (t->hints_on == 0) {
2253 if (args->i == XT_HINT_NEWTAB)
2254 t->new_tab = 1;
2255 enable_hints(t);
2256 } else
2257 disable_hints(t);
2259 return (0);
2262 void
2263 apply_style(struct tab *t)
2265 g_object_set(G_OBJECT(t->settings),
2266 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
2270 userstyle(struct tab *t, struct karg *args)
2272 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
2274 if (t->styled) {
2275 t->styled = 0;
2276 g_object_set(G_OBJECT(t->settings),
2277 "user-stylesheet-uri", NULL, (char *)NULL);
2278 } else {
2279 t->styled = 1;
2280 apply_style(t);
2282 return (0);
2286 * Doesn't work fully, due to the following bug:
2287 * https://bugs.webkit.org/show_bug.cgi?id=51747
2290 restore_global_history(void)
2292 char file[PATH_MAX];
2293 FILE *f;
2294 struct history *h;
2295 gchar *uri;
2296 gchar *title;
2297 const char delim[3] = {'\\', '\\', '\0'};
2299 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2301 if ((f = fopen(file, "r")) == NULL) {
2302 warnx("%s: fopen", __func__);
2303 return (1);
2306 for (;;) {
2307 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2308 if (feof(f) || ferror(f))
2309 break;
2311 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2312 if (feof(f) || ferror(f)) {
2313 free(uri);
2314 warnx("%s: broken history file\n", __func__);
2315 return (1);
2318 if (uri && strlen(uri) && title && strlen(title)) {
2319 webkit_web_history_item_new_with_data(uri, title);
2320 h = g_malloc(sizeof(struct history));
2321 h->uri = g_strdup(uri);
2322 h->title = g_strdup(title);
2323 RB_INSERT(history_list, &hl, h);
2324 completion_add_uri(h->uri);
2325 } else {
2326 warnx("%s: failed to restore history\n", __func__);
2327 free(uri);
2328 free(title);
2329 return (1);
2332 free(uri);
2333 free(title);
2334 uri = NULL;
2335 title = NULL;
2338 return (0);
2342 save_global_history_to_disk(struct tab *t)
2344 char file[PATH_MAX];
2345 FILE *f;
2346 struct history *h;
2348 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2350 if ((f = fopen(file, "w")) == NULL) {
2351 show_oops(t, "%s: global history file: %s",
2352 __func__, strerror(errno));
2353 return (1);
2356 RB_FOREACH_REVERSE(h, history_list, &hl) {
2357 if (h->uri && h->title)
2358 fprintf(f, "%s\n%s\n", h->uri, h->title);
2361 fclose(f);
2363 return (0);
2367 quit(struct tab *t, struct karg *args)
2369 if (save_global_history)
2370 save_global_history_to_disk(t);
2372 gtk_main_quit();
2374 return (1);
2377 void
2378 restore_sessions_list(void)
2380 DIR *sdir = NULL;
2381 struct dirent *dp = NULL;
2382 struct session *s;
2384 sdir = opendir(sessions_dir);
2385 if (sdir) {
2386 while ((dp = readdir(sdir)) != NULL)
2387 if (dp->d_type == DT_REG) {
2388 s = g_malloc(sizeof(struct session));
2389 s->name = g_strdup(dp->d_name);
2390 TAILQ_INSERT_TAIL(&sessions, s, entry);
2392 closedir(sdir);
2397 open_tabs(struct tab *t, struct karg *a)
2399 char file[PATH_MAX];
2400 FILE *f = NULL;
2401 char *uri = NULL;
2402 int rv = 1;
2403 struct tab *ti, *tt;
2405 if (a == NULL)
2406 goto done;
2408 ti = TAILQ_LAST(&tabs, tab_list);
2410 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2411 if ((f = fopen(file, "r")) == NULL)
2412 goto done;
2414 for (;;) {
2415 if ((uri = fparseln(f, NULL, NULL, "\0\0\0", 0)) == NULL)
2416 if (feof(f) || ferror(f))
2417 break;
2419 /* retrieve session name */
2420 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2421 strlcpy(named_session,
2422 &uri[strlen(XT_SAVE_SESSION_ID)],
2423 sizeof named_session);
2424 continue;
2427 if (uri && strlen(uri))
2428 create_new_tab(uri, NULL, 1, -1);
2430 free(uri);
2431 uri = NULL;
2434 /* close open tabs */
2435 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2436 for (;;) {
2437 tt = TAILQ_FIRST(&tabs);
2438 if (tt == NULL)
2439 break;
2440 if (tt != ti) {
2441 delete_tab(tt);
2442 continue;
2444 delete_tab(tt);
2445 break;
2449 rv = 0;
2450 done:
2451 if (f)
2452 fclose(f);
2454 return (rv);
2458 restore_saved_tabs(void)
2460 char file[PATH_MAX];
2461 int unlink_file = 0;
2462 struct stat sb;
2463 struct karg a;
2464 int rv = 0;
2466 snprintf(file, sizeof file, "%s/%s",
2467 sessions_dir, XT_RESTART_TABS_FILE);
2468 if (stat(file, &sb) == -1)
2469 a.s = XT_SAVED_TABS_FILE;
2470 else {
2471 unlink_file = 1;
2472 a.s = XT_RESTART_TABS_FILE;
2475 a.i = XT_SES_DONOTHING;
2476 rv = open_tabs(NULL, &a);
2478 if (unlink_file)
2479 unlink(file);
2481 return (rv);
2485 save_tabs(struct tab *t, struct karg *a)
2487 char file[PATH_MAX];
2488 FILE *f;
2489 int num_tabs = 0, i;
2490 struct tab **stabs = NULL;
2492 /* tab may be null here */
2494 if (a == NULL)
2495 return (1);
2496 if (a->s == NULL)
2497 snprintf(file, sizeof file, "%s/%s",
2498 sessions_dir, named_session);
2499 else
2500 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2502 if ((f = fopen(file, "w")) == NULL) {
2503 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2504 return (1);
2507 /* save session name */
2508 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2510 /* Save tabs, in the order they are arranged in the notebook. */
2511 num_tabs = sort_tabs_by_page_num(&stabs);
2513 for (i = 0; i < num_tabs; i++)
2514 if (stabs[i]) {
2515 if (get_uri(stabs[i]) != NULL)
2516 fprintf(f, "%s\n", get_uri(stabs[i]));
2517 else if (gtk_entry_get_text(GTK_ENTRY(
2518 stabs[i]->uri_entry)))
2519 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
2520 stabs[i]->uri_entry)));
2523 g_free(stabs);
2525 /* try and make sure this gets to disk NOW. XXX Backup first? */
2526 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2527 show_oops(t, "May not have managed to save session: %s",
2528 strerror(errno));
2531 fclose(f);
2533 return (0);
2537 save_tabs_and_quit(struct tab *t, struct karg *args)
2539 struct karg a;
2541 a.s = NULL;
2542 save_tabs(t, &a);
2543 quit(t, NULL);
2545 return (1);
2549 run_page_script(struct tab *t, struct karg *args)
2551 const gchar *uri;
2552 char *tmp, script[PATH_MAX];
2554 tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
2555 if (tmp[0] == '\0') {
2556 show_oops(t, "no script specified");
2557 return (1);
2560 if ((uri = get_uri(t)) == NULL) {
2561 show_oops(t, "tab is empty, not running script");
2562 return (1);
2565 if (tmp[0] == '~')
2566 snprintf(script, sizeof script, "%s/%s",
2567 pwd->pw_dir, &tmp[1]);
2568 else
2569 strlcpy(script, tmp, sizeof script);
2571 switch (fork()) {
2572 case -1:
2573 show_oops(t, "can't fork to run script");
2574 return (1);
2575 /* NOTREACHED */
2576 case 0:
2577 break;
2578 default:
2579 return (0);
2582 /* child */
2583 execlp(script, script, uri, (void *)NULL);
2585 _exit(0);
2587 /* NOTREACHED */
2589 return (0);
2593 yank_uri(struct tab *t, struct karg *args)
2595 const gchar *uri;
2596 GtkClipboard *clipboard;
2598 if ((uri = get_uri(t)) == NULL)
2599 return (1);
2601 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2602 gtk_clipboard_set_text(clipboard, uri, -1);
2604 return (0);
2608 paste_uri(struct tab *t, struct karg *args)
2610 GtkClipboard *clipboard;
2611 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2612 gint len;
2613 gchar *p = NULL, *uri;
2615 /* try primary clipboard first */
2616 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2617 p = gtk_clipboard_wait_for_text(clipboard);
2619 /* if it failed get whatever text is in cut_buffer0 */
2620 if (p == NULL && xterm_workaround)
2621 if (gdk_property_get(gdk_get_default_root_window(),
2622 atom,
2623 gdk_atom_intern("STRING", FALSE),
2625 1024 * 1024 /* picked out of my butt */,
2626 FALSE,
2627 NULL,
2628 NULL,
2629 &len,
2630 (guchar **)&p)) {
2631 /* yes sir, we need to NUL the string */
2632 p[len] = '\0';
2635 if (p) {
2636 uri = p;
2637 while (*uri && isspace(*uri))
2638 uri++;
2639 if (strlen(uri) == 0) {
2640 show_oops(t, "empty paste buffer");
2641 goto done;
2643 if (guess_search == 0 && valid_url_type(uri)) {
2644 /* we can be clever and paste this in search box */
2645 show_oops(t, "not a valid URL");
2646 goto done;
2649 if (args->i == XT_PASTE_CURRENT_TAB)
2650 load_uri(t, uri);
2651 else if (args->i == XT_PASTE_NEW_TAB)
2652 create_new_tab(uri, NULL, 1, -1);
2655 done:
2656 if (p)
2657 g_free(p);
2659 return (0);
2662 gchar *
2663 find_domain(const gchar *s, int toplevel)
2665 SoupURI *uri;
2666 gchar *ret, *p;
2668 if (s == NULL)
2669 return (NULL);
2671 uri = soup_uri_new(s);
2673 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri)) {
2674 return (NULL);
2677 if (toplevel && !isdigit(uri->host[strlen(uri->host) - 1])) {
2678 if ((p = strrchr(uri->host, '.')) != NULL) {
2679 while(--p >= uri->host && *p != '.');
2680 p++;
2681 } else
2682 p = uri->host;
2683 } else
2684 p = uri->host;
2686 ret = g_strdup_printf(".%s", p);
2688 soup_uri_free(uri);
2690 return (ret);
2694 toggle_cwl(struct tab *t, struct karg *args)
2696 struct domain *d;
2697 const gchar *uri;
2698 char *dom = NULL;
2699 int es;
2701 if (args == NULL)
2702 return (1);
2704 uri = get_uri(t);
2705 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2707 if (uri == NULL || dom == NULL ||
2708 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2709 show_oops(t, "Can't toggle domain in cookie white list");
2710 goto done;
2712 d = wl_find(dom, &c_wl);
2714 if (d == NULL)
2715 es = 0;
2716 else
2717 es = 1;
2719 if (args->i & XT_WL_TOGGLE)
2720 es = !es;
2721 else if ((args->i & XT_WL_ENABLE) && es != 1)
2722 es = 1;
2723 else if ((args->i & XT_WL_DISABLE) && es != 0)
2724 es = 0;
2726 if (es)
2727 /* enable cookies for domain */
2728 wl_add(dom, &c_wl, 0);
2729 else {
2730 /* disable cookies for domain */
2731 if (d)
2732 RB_REMOVE(domain_list, &c_wl, d);
2735 if (args->i & XT_WL_RELOAD)
2736 webkit_web_view_reload(t->wv);
2738 done:
2739 g_free(dom);
2740 return (0);
2744 toggle_js(struct tab *t, struct karg *args)
2746 int es;
2747 const gchar *uri;
2748 struct domain *d;
2749 char *dom = NULL;
2751 if (args == NULL)
2752 return (1);
2754 g_object_get(G_OBJECT(t->settings),
2755 "enable-scripts", &es, (char *)NULL);
2756 if (args->i & XT_WL_TOGGLE)
2757 es = !es;
2758 else if ((args->i & XT_WL_ENABLE) && es != 1)
2759 es = 1;
2760 else if ((args->i & XT_WL_DISABLE) && es != 0)
2761 es = 0;
2762 else
2763 return (1);
2765 uri = get_uri(t);
2766 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2768 if (uri == NULL || dom == NULL ||
2769 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2770 show_oops(t, "Can't toggle domain in JavaScript white list");
2771 goto done;
2774 if (es) {
2775 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2776 wl_add(dom, &js_wl, 0 /* session */);
2777 } else {
2778 d = wl_find(dom, &js_wl);
2779 if (d)
2780 RB_REMOVE(domain_list, &js_wl, d);
2781 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2783 g_object_set(G_OBJECT(t->settings),
2784 "enable-scripts", es, (char *)NULL);
2785 g_object_set(G_OBJECT(t->settings),
2786 "javascript-can-open-windows-automatically", es, (char *)NULL);
2787 webkit_web_view_set_settings(t->wv, t->settings);
2789 if (args->i & XT_WL_RELOAD)
2790 webkit_web_view_reload(t->wv);
2791 done:
2792 if (dom)
2793 g_free(dom);
2794 return (0);
2798 toggle_pl(struct tab *t, struct karg *args)
2800 int es;
2801 const gchar *uri;
2802 struct domain *d;
2803 char *dom = NULL;
2805 if (args == NULL)
2806 return (1);
2808 g_object_get(G_OBJECT(t->settings),
2809 "enable-plugins", &es, (char *)NULL);
2810 if (args->i & XT_WL_TOGGLE)
2811 es = !es;
2812 else if ((args->i & XT_WL_ENABLE) && es != 1)
2813 es = 1;
2814 else if ((args->i & XT_WL_DISABLE) && es != 0)
2815 es = 0;
2816 else
2817 return (1);
2819 uri = get_uri(t);
2820 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
2822 if (uri == NULL || dom == NULL ||
2823 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
2824 show_oops(t, "Can't toggle domain in plugins white list");
2825 goto done;
2828 if (es)
2829 wl_add(dom, &pl_wl, 0 /* session */);
2830 else {
2831 d = wl_find(dom, &pl_wl);
2832 if (d)
2833 RB_REMOVE(domain_list, &pl_wl, d);
2835 g_object_set(G_OBJECT(t->settings),
2836 "enable-plugins", es, (char *)NULL);
2837 webkit_web_view_set_settings(t->wv, t->settings);
2839 if (args->i & XT_WL_RELOAD)
2840 webkit_web_view_reload(t->wv);
2841 done:
2842 if (dom)
2843 g_free(dom);
2844 return (0);
2847 void
2848 js_toggle_cb(GtkWidget *w, struct tab *t)
2850 struct karg a;
2851 int es, set;
2853 g_object_get(G_OBJECT(t->settings),
2854 "enable-scripts", &es, (char *)NULL);
2855 es = !es;
2856 if (es)
2857 set = XT_WL_ENABLE;
2858 else
2859 set = XT_WL_DISABLE;
2861 a.i = set | XT_WL_TOPLEVEL;
2862 toggle_pl(t, &a);
2864 a.i = set | XT_WL_TOPLEVEL;
2865 toggle_cwl(t, &a);
2867 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2868 toggle_js(t, &a);
2872 toggle_src(struct tab *t, struct karg *args)
2874 gboolean mode;
2876 if (t == NULL)
2877 return (0);
2879 mode = webkit_web_view_get_view_source_mode(t->wv);
2880 webkit_web_view_set_view_source_mode(t->wv, !mode);
2881 webkit_web_view_reload(t->wv);
2883 return (0);
2886 void
2887 focus_webview(struct tab *t)
2889 if (t == NULL)
2890 return;
2892 /* only grab focus if we are visible */
2893 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2894 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2898 focus(struct tab *t, struct karg *args)
2900 if (t == NULL || args == NULL)
2901 return (1);
2903 if (show_url == 0)
2904 return (0);
2906 if (args->i == XT_FOCUS_URI)
2907 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2908 else if (args->i == XT_FOCUS_SEARCH)
2909 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2911 return (0);
2915 stats(struct tab *t, struct karg *args)
2917 char *page, *body, *s, line[64 * 1024];
2918 long long unsigned int line_count = 0;
2919 FILE *r_cookie_f;
2921 if (t == NULL)
2922 show_oops(NULL, "stats invalid parameters");
2924 line[0] = '\0';
2925 if (save_rejected_cookies) {
2926 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2927 for (;;) {
2928 s = fgets(line, sizeof line, r_cookie_f);
2929 if (s == NULL || feof(r_cookie_f) ||
2930 ferror(r_cookie_f))
2931 break;
2932 line_count++;
2934 fclose(r_cookie_f);
2935 snprintf(line, sizeof line,
2936 "<br/>Cookies blocked(*) total: %llu", line_count);
2937 } else
2938 show_oops(t, "Can't open blocked cookies file: %s",
2939 strerror(errno));
2942 body = g_strdup_printf(
2943 "Cookies blocked(*) this session: %llu"
2944 "%s"
2945 "<p><small><b>*</b> results vary based on settings</small></p>",
2946 blocked_cookies,
2947 line);
2949 page = get_html_page("Statistics", body, "", 0);
2950 g_free(body);
2952 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2953 g_free(page);
2955 return (0);
2959 marco(struct tab *t, struct karg *args)
2961 char *page, line[64 * 1024];
2962 int len;
2964 if (t == NULL)
2965 show_oops(NULL, "marco invalid parameters");
2967 line[0] = '\0';
2968 snprintf(line, sizeof line, "%s", marco_message(&len));
2970 page = get_html_page("Marco Sez...", line, "", 0);
2972 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2973 g_free(page);
2975 return (0);
2979 blank(struct tab *t, struct karg *args)
2981 if (t == NULL)
2982 show_oops(NULL, "blank invalid parameters");
2984 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2986 return (0);
2990 about(struct tab *t, struct karg *args)
2992 char *page, *body;
2994 if (t == NULL)
2995 show_oops(NULL, "about invalid parameters");
2997 body = g_strdup_printf("<b>Version: %s</b>"
2998 #ifdef XXXTERM_BUILDSTR
2999 "<br><b>Build: %s</b>"
3000 #endif
3001 "<p>"
3002 "Authors:"
3003 "<ul>"
3004 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
3005 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
3006 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
3007 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
3008 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
3009 "</ul>"
3010 "Copyrights and licenses can be found on the XXXTerm "
3011 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
3012 "</p>",
3013 #ifdef XXXTERM_BUILDSTR
3014 version, XXXTERM_BUILDSTR
3015 #else
3016 version
3017 #endif
3020 page = get_html_page("About", body, "", 0);
3021 g_free(body);
3023 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
3024 g_free(page);
3026 return (0);
3030 help(struct tab *t, struct karg *args)
3032 char *page, *head, *body;
3034 if (t == NULL)
3035 show_oops(NULL, "help invalid parameters");
3037 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
3038 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
3039 "</head>\n";
3040 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
3041 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
3042 "cgi-bin/man-cgi?xxxterm</a>";
3044 page = get_html_page(XT_NAME, body, head, FALSE);
3046 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
3047 g_free(page);
3049 return (0);
3053 startpage(struct tab *t, struct karg *args)
3055 char *page, *body, *b;
3056 struct sp *s;
3058 if (t == NULL)
3059 show_oops(NULL, "startpage invalid parameters");
3061 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
3063 TAILQ_FOREACH(s, &spl, entry) {
3064 b = body;
3065 body = g_strdup_printf("%s%s<br>", body, s->line);
3066 g_free(b);
3069 page = get_html_page("Startup Exception", body, "", 0);
3070 g_free(body);
3072 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
3073 g_free(page);
3075 return (0);
3078 void
3079 startpage_add(const char *fmt, ...)
3081 va_list ap;
3082 char *msg;
3083 struct sp *s;
3085 if (fmt == NULL)
3086 return;
3088 va_start(ap, fmt);
3089 if (vasprintf(&msg, fmt, ap) == -1)
3090 errx(1, "startpage_add failed");
3091 va_end(ap);
3093 s = g_malloc0(sizeof *s);
3094 s->line = msg;
3096 TAILQ_INSERT_TAIL(&spl, s, entry);
3100 * update all favorite tabs apart from one. Pass NULL if
3101 * you want to update all.
3103 void
3104 update_favorite_tabs(struct tab *apart_from)
3106 struct tab *t;
3107 if (!updating_fl_tabs) {
3108 updating_fl_tabs = 1; /* stop infinite recursion */
3109 TAILQ_FOREACH(t, &tabs, entry)
3110 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
3111 && (t != apart_from))
3112 xtp_page_fl(t, NULL);
3113 updating_fl_tabs = 0;
3117 /* show a list of favorites (bookmarks) */
3119 xtp_page_fl(struct tab *t, struct karg *args)
3121 char file[PATH_MAX];
3122 FILE *f;
3123 char *uri = NULL, *title = NULL;
3124 size_t len, lineno = 0;
3125 int i, failed = 0;
3126 char *body, *tmp, *page = NULL;
3127 const char delim[3] = {'\\', '\\', '\0'};
3129 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
3131 if (t == NULL)
3132 warn("%s: bad param", __func__);
3134 /* new session key */
3135 if (!updating_fl_tabs)
3136 generate_xtp_session_key(&fl_session_key);
3138 /* open favorites */
3139 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3140 if ((f = fopen(file, "r")) == NULL) {
3141 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3142 return (1);
3145 /* body */
3146 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
3147 "<th style='width: 40px'>&#35;</th><th>Link</th>"
3148 "<th style='width: 40px'>Rm</th></tr>\n");
3150 for (i = 1;;) {
3151 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3152 break;
3153 if (strlen(title) == 0 || title[0] == '#') {
3154 free(title);
3155 title = NULL;
3156 continue;
3159 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
3160 if (feof(f) || ferror(f)) {
3161 show_oops(t, "favorites file corrupt");
3162 failed = 1;
3163 break;
3166 tmp = body;
3167 body = g_strdup_printf("%s<tr>"
3168 "<td>%d</td>"
3169 "<td><a href='%s'>%s</a></td>"
3170 "<td style='text-align: center'>"
3171 "<a href='%s%d/%s/%d/%d'>X</a></td>"
3172 "</tr>\n",
3173 body, i, uri, title,
3174 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
3176 g_free(tmp);
3178 free(uri);
3179 uri = NULL;
3180 free(title);
3181 title = NULL;
3182 i++;
3184 fclose(f);
3186 /* if none, say so */
3187 if (i == 1) {
3188 tmp = body;
3189 body = g_strdup_printf("%s<tr>"
3190 "<td colspan='3' style='text-align: center'>"
3191 "No favorites - To add one use the 'favadd' command."
3192 "</td></tr>", body);
3193 g_free(tmp);
3196 tmp = body;
3197 body = g_strdup_printf("%s</table>", body);
3198 g_free(tmp);
3200 if (uri)
3201 free(uri);
3202 if (title)
3203 free(title);
3205 /* render */
3206 if (!failed) {
3207 page = get_html_page("Favorites", body, "", 1);
3208 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
3209 g_free(page);
3212 update_favorite_tabs(t);
3214 if (body)
3215 g_free(body);
3217 return (failed);
3220 void
3221 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
3222 size_t cert_count, char *title)
3224 gnutls_datum_t cinfo;
3225 char *tmp, *body;
3226 int i;
3228 body = g_strdup("");
3230 for (i = 0; i < cert_count; i++) {
3231 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
3232 &cinfo))
3233 return;
3235 tmp = body;
3236 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
3237 body, i, cinfo.data);
3238 gnutls_free(cinfo.data);
3239 g_free(tmp);
3242 tmp = get_html_page(title, body, "", 0);
3243 g_free(body);
3245 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
3246 g_free(tmp);
3250 ca_cmd(struct tab *t, struct karg *args)
3252 FILE *f = NULL;
3253 int rv = 1, certs = 0, certs_read;
3254 struct stat sb;
3255 gnutls_datum_t dt;
3256 gnutls_x509_crt_t *c = NULL;
3257 char *certs_buf = NULL, *s;
3259 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
3260 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3261 return (1);
3264 if (fstat(fileno(f), &sb) == -1) {
3265 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
3266 goto done;
3269 certs_buf = g_malloc(sb.st_size + 1);
3270 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
3271 show_oops(t, "Can't read CA file: %s", strerror(errno));
3272 goto done;
3274 certs_buf[sb.st_size] = '\0';
3276 s = certs_buf;
3277 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
3278 certs++;
3279 s += strlen("BEGIN CERTIFICATE");
3282 bzero(&dt, sizeof dt);
3283 dt.data = (unsigned char *)certs_buf;
3284 dt.size = sb.st_size;
3285 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
3286 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
3287 GNUTLS_X509_FMT_PEM, 0);
3288 if (certs_read <= 0) {
3289 show_oops(t, "No cert(s) available");
3290 goto done;
3292 show_certs(t, c, certs_read, "Certificate Authority Certificates");
3293 done:
3294 if (c)
3295 g_free(c);
3296 if (certs_buf)
3297 g_free(certs_buf);
3298 if (f)
3299 fclose(f);
3301 return (rv);
3305 connect_socket_from_uri(const gchar *uri, const gchar **error_str, char *domain,
3306 size_t domain_sz)
3308 SoupURI *su = NULL;
3309 struct addrinfo hints, *res = NULL, *ai;
3310 int rv = -1, s = -1, on, error;
3311 char port[8];
3312 static gchar myerror[256]; /* this is not thread safe */
3314 myerror[0] = '\0';
3315 *error_str = myerror;
3316 if (uri && !g_str_has_prefix(uri, "https://")) {
3317 *error_str = "invalid URI";
3318 goto done;
3321 su = soup_uri_new(uri);
3322 if (su == NULL) {
3323 *error_str = "invalid soup URI";
3324 goto done;
3326 if (!SOUP_URI_VALID_FOR_HTTP(su)) {
3327 *error_str = "invalid HTTPS URI";
3328 goto done;
3331 snprintf(port, sizeof port, "%d", su->port);
3332 bzero(&hints, sizeof(struct addrinfo));
3333 hints.ai_flags = AI_CANONNAME;
3334 hints.ai_family = AF_UNSPEC;
3335 hints.ai_socktype = SOCK_STREAM;
3337 if ((error = getaddrinfo(su->host, port, &hints, &res))) {
3338 snprintf(myerror, sizeof myerror, "getaddrinfo failed: %s",
3339 gai_strerror(errno));
3340 goto done;
3343 for (ai = res; ai; ai = ai->ai_next) {
3344 if (s != -1) {
3345 close(s);
3346 s = -1;
3349 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
3350 continue;
3351 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
3352 if (s == -1)
3353 continue;
3354 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
3355 sizeof(on)) == -1)
3356 continue;
3357 if (connect(s, ai->ai_addr, ai->ai_addrlen) == 0)
3358 break;
3360 if (s == -1) {
3361 snprintf(myerror, sizeof myerror,
3362 "could not obtain certificates from: %s",
3363 su->host);
3364 goto done;
3367 if (domain)
3368 strlcpy(domain, su->host, domain_sz);
3369 rv = s;
3370 done:
3371 if (su)
3372 soup_uri_free(su);
3373 if (res)
3374 freeaddrinfo(res);
3375 if (rv == -1 && s != -1)
3376 close(s);
3378 return (rv);
3382 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
3384 if (gsession)
3385 gnutls_deinit(gsession);
3386 if (xcred)
3387 gnutls_certificate_free_credentials(xcred);
3389 return (0);
3393 start_tls(const gchar **error_str, int s, gnutls_session_t *gs,
3394 gnutls_certificate_credentials_t *xc)
3396 gnutls_certificate_credentials_t xcred;
3397 gnutls_session_t gsession;
3398 int rv = 1;
3399 static gchar myerror[1024]; /* this is not thread safe */
3401 if (gs == NULL || xc == NULL)
3402 goto done;
3404 myerror[0] = '\0';
3405 *gs = NULL;
3406 *xc = NULL;
3408 gnutls_certificate_allocate_credentials(&xcred);
3409 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
3410 GNUTLS_X509_FMT_PEM);
3412 gnutls_init(&gsession, GNUTLS_CLIENT);
3413 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
3414 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
3415 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
3416 if ((rv = gnutls_handshake(gsession)) < 0) {
3417 snprintf(myerror, sizeof myerror,
3418 "gnutls_handshake failed %d fatal %d %s",
3420 gnutls_error_is_fatal(rv),
3421 gnutls_strerror_name(rv));
3422 stop_tls(gsession, xcred);
3423 goto done;
3426 gnutls_credentials_type_t cred;
3427 cred = gnutls_auth_get_type(gsession);
3428 if (cred != GNUTLS_CRD_CERTIFICATE) {
3429 snprintf(myerror, sizeof myerror,
3430 "gnutls_auth_get_type failed %d",
3431 (int)cred);
3432 stop_tls(gsession, xcred);
3433 goto done;
3436 *gs = gsession;
3437 *xc = xcred;
3438 rv = 0;
3439 done:
3440 *error_str = myerror;
3441 return (rv);
3445 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
3446 size_t *cert_count)
3448 unsigned int len;
3449 const gnutls_datum_t *cl;
3450 gnutls_x509_crt_t *all_certs;
3451 int i, rv = 1;
3453 if (certs == NULL || cert_count == NULL)
3454 goto done;
3455 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
3456 goto done;
3457 cl = gnutls_certificate_get_peers(gsession, &len);
3458 if (len == 0)
3459 goto done;
3461 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
3462 for (i = 0; i < len; i++) {
3463 gnutls_x509_crt_init(&all_certs[i]);
3464 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
3465 GNUTLS_X509_FMT_PEM < 0)) {
3466 g_free(all_certs);
3467 goto done;
3471 *certs = all_certs;
3472 *cert_count = len;
3473 rv = 0;
3474 done:
3475 return (rv);
3478 void
3479 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
3481 int i;
3483 for (i = 0; i < cert_count; i++)
3484 gnutls_x509_crt_deinit(certs[i]);
3485 g_free(certs);
3488 void
3489 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
3491 GdkColor c_text, c_base;
3493 gdk_color_parse(text, &c_text);
3494 gdk_color_parse(base, &c_base);
3496 gtk_widget_modify_text(t->sbe.statusbar, GTK_STATE_NORMAL, &c_text);
3497 gtk_widget_modify_text(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_text);
3498 gtk_widget_modify_text(t->sbe.zoom, GTK_STATE_NORMAL, &c_text);
3499 gtk_widget_modify_text(t->sbe.position, GTK_STATE_NORMAL, &c_text);
3501 gtk_widget_modify_base(t->sbe.statusbar, GTK_STATE_NORMAL, &c_base);
3502 gtk_widget_modify_base(t->sbe.buffercmd, GTK_STATE_NORMAL, &c_base);
3503 gtk_widget_modify_base(t->sbe.zoom, GTK_STATE_NORMAL, &c_base);
3504 gtk_widget_modify_base(t->sbe.position, GTK_STATE_NORMAL, &c_base);
3507 void
3508 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
3509 size_t cert_count, char *domain)
3511 size_t cert_buf_sz;
3512 char cert_buf[64 * 1024], file[PATH_MAX];
3513 int i;
3514 FILE *f;
3515 GdkColor color;
3517 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
3518 return;
3520 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3521 if ((f = fopen(file, "w")) == NULL) {
3522 show_oops(t, "Can't create cert file %s %s",
3523 file, strerror(errno));
3524 return;
3527 for (i = 0; i < cert_count; i++) {
3528 cert_buf_sz = sizeof cert_buf;
3529 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3530 cert_buf, &cert_buf_sz)) {
3531 show_oops(t, "gnutls_x509_crt_export failed");
3532 goto done;
3534 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3535 show_oops(t, "Can't write certs: %s", strerror(errno));
3536 goto done;
3540 /* not the best spot but oh well */
3541 gdk_color_parse("lightblue", &color);
3542 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3543 statusbar_modify_attr(t, XT_COLOR_BLACK, "lightblue");
3544 done:
3545 fclose(f);
3548 enum cert_trust {
3549 CERT_LOCAL,
3550 CERT_TRUSTED,
3551 CERT_UNTRUSTED,
3552 CERT_BAD
3555 enum cert_trust
3556 load_compare_cert(const gchar *uri, const gchar **error_str)
3558 char domain[8182], file[PATH_MAX];
3559 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3560 int s = -1, i;
3561 unsigned int error = 0;
3562 FILE *f = NULL;
3563 size_t cert_buf_sz, cert_count;
3564 enum cert_trust rv = CERT_UNTRUSTED;
3565 static gchar serr[80]; /* this isn't thread safe */
3566 gnutls_session_t gsession;
3567 gnutls_x509_crt_t *certs;
3568 gnutls_certificate_credentials_t xcred;
3570 DNPRINTF(XT_D_URL, "%s: %s\n", __func__, uri);
3572 serr[0] = '\0';
3573 *error_str = serr;
3574 if ((s = connect_socket_from_uri(uri, error_str, domain,
3575 sizeof domain)) == -1)
3576 return (rv);
3578 DNPRINTF(XT_D_URL, "%s: fd %d\n", __func__, s);
3580 /* go ssl/tls */
3581 if (start_tls(error_str, s, &gsession, &xcred))
3582 goto done;
3583 DNPRINTF(XT_D_URL, "%s: got tls\n", __func__);
3585 /* verify certs in case cert file doesn't exist */
3586 if (gnutls_certificate_verify_peers2(gsession, &error) !=
3587 GNUTLS_E_SUCCESS) {
3588 *error_str = "Invalid certificates";
3589 goto done;
3592 /* get certs */
3593 if (get_connection_certs(gsession, &certs, &cert_count)) {
3594 *error_str = "Can't get connection certificates";
3595 goto done;
3598 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3599 if ((f = fopen(file, "r")) == NULL) {
3600 if (!error)
3601 rv = CERT_TRUSTED;
3602 goto freeit;
3605 for (i = 0; i < cert_count; i++) {
3606 cert_buf_sz = sizeof cert_buf;
3607 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3608 cert_buf, &cert_buf_sz)) {
3609 goto freeit;
3611 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3612 rv = CERT_BAD; /* critical */
3613 goto freeit;
3615 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3616 rv = CERT_BAD; /* critical */
3617 goto freeit;
3619 rv = CERT_LOCAL;
3622 freeit:
3623 if (f)
3624 fclose(f);
3625 free_connection_certs(certs, cert_count);
3626 done:
3627 /* we close the socket first for speed */
3628 if (s != -1)
3629 close(s);
3631 /* only complain if we didn't save it locally */
3632 if (error && rv != CERT_LOCAL) {
3633 strlcpy(serr, "Certificate exception(s): ", sizeof serr);
3634 if (error & GNUTLS_CERT_INVALID)
3635 strlcat(serr, "invalid, ", sizeof serr);
3636 if (error & GNUTLS_CERT_REVOKED)
3637 strlcat(serr, "revoked, ", sizeof serr);
3638 if (error & GNUTLS_CERT_SIGNER_NOT_FOUND)
3639 strlcat(serr, "signer not found, ", sizeof serr);
3640 if (error & GNUTLS_CERT_SIGNER_NOT_CA)
3641 strlcat(serr, "not signed by CA, ", sizeof serr);
3642 if (error & GNUTLS_CERT_INSECURE_ALGORITHM)
3643 strlcat(serr, "insecure algorithm, ", sizeof serr);
3644 if (error & GNUTLS_CERT_NOT_ACTIVATED)
3645 strlcat(serr, "not activated, ", sizeof serr);
3646 if (error & GNUTLS_CERT_EXPIRED)
3647 strlcat(serr, "expired, ", sizeof serr);
3648 for (i = strlen(serr) - 1; i > 0; i--)
3649 if (serr[i] == ',') {
3650 serr[i] = '\0';
3651 break;
3653 *error_str = serr;
3656 stop_tls(gsession, xcred);
3658 return (rv);
3662 cert_cmd(struct tab *t, struct karg *args)
3664 const gchar *uri, *error_str = NULL;
3665 char domain[8182];
3666 int s = -1;
3667 size_t cert_count;
3668 gnutls_session_t gsession;
3669 gnutls_x509_crt_t *certs;
3670 gnutls_certificate_credentials_t xcred;
3672 if (t == NULL)
3673 return (1);
3675 if (ssl_ca_file == NULL) {
3676 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3677 return (1);
3680 if ((uri = get_uri(t)) == NULL) {
3681 show_oops(t, "Invalid URI");
3682 return (1);
3685 if ((s = connect_socket_from_uri(uri, &error_str, domain,
3686 sizeof domain)) == -1) {
3687 show_oops(t, "%s", error_str);
3688 return (1);
3691 /* go ssl/tls */
3692 if (start_tls(&error_str, s, &gsession, &xcred))
3693 goto done;
3695 /* get certs */
3696 if (get_connection_certs(gsession, &certs, &cert_count)) {
3697 show_oops(t, "get_connection_certs failed");
3698 goto done;
3701 if (args->i & XT_SHOW)
3702 show_certs(t, certs, cert_count, "Certificate Chain");
3703 else if (args->i & XT_SAVE)
3704 save_certs(t, certs, cert_count, domain);
3706 free_connection_certs(certs, cert_count);
3707 done:
3708 /* we close the socket first for speed */
3709 if (s != -1)
3710 close(s);
3711 stop_tls(gsession, xcred);
3712 if (error_str && strlen(error_str))
3713 show_oops(t, "%s", error_str);
3714 return (0);
3718 remove_cookie(int index)
3720 int i, rv = 1;
3721 GSList *cf;
3722 SoupCookie *c;
3724 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3726 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3728 for (i = 1; cf; cf = cf->next, i++) {
3729 if (i != index)
3730 continue;
3731 c = cf->data;
3732 print_cookie("remove cookie", c);
3733 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3734 rv = 0;
3735 break;
3738 soup_cookies_free(cf);
3740 return (rv);
3744 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3746 struct domain *d;
3747 char *tmp, *body;
3749 body = g_strdup("");
3751 /* p list */
3752 if (args->i & XT_WL_PERSISTENT) {
3753 tmp = body;
3754 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3755 g_free(tmp);
3756 RB_FOREACH(d, domain_list, wl) {
3757 if (d->handy == 0)
3758 continue;
3759 tmp = body;
3760 body = g_strdup_printf("%s%s<br/>", body, d->d);
3761 g_free(tmp);
3765 /* s list */
3766 if (args->i & XT_WL_SESSION) {
3767 tmp = body;
3768 body = g_strdup_printf("%s<h2>Session</h2>", body);
3769 g_free(tmp);
3770 RB_FOREACH(d, domain_list, wl) {
3771 if (d->handy == 1)
3772 continue;
3773 tmp = body;
3774 body = g_strdup_printf("%s%s<br/>", body, d->d);
3775 g_free(tmp);
3779 tmp = get_html_page(title, body, "", 0);
3780 g_free(body);
3781 if (wl == &js_wl)
3782 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3783 else if (wl == &c_wl)
3784 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3785 else
3786 load_webkit_string(t, tmp, XT_URI_ABOUT_PLUGINWL);
3787 g_free(tmp);
3788 return (0);
3791 #define XT_WL_INVALID (0)
3792 #define XT_WL_JAVASCRIPT (1)
3793 #define XT_WL_COOKIE (2)
3794 #define XT_WL_PLUGIN (3)
3796 wl_save(struct tab *t, struct karg *args, int list)
3798 char file[PATH_MAX], *lst_str = NULL;
3799 FILE *f;
3800 char *line = NULL, *lt = NULL, *dom = NULL;
3801 size_t linelen;
3802 const gchar *uri;
3803 struct karg a;
3804 struct domain *d;
3805 GSList *cf;
3806 SoupCookie *ci, *c;
3808 if (t == NULL || args == NULL)
3809 return (1);
3811 if (runtime_settings[0] == '\0')
3812 return (1);
3814 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3815 if ((f = fopen(file, "r+")) == NULL)
3816 return (1);
3818 switch (list) {
3819 case XT_WL_JAVASCRIPT:
3820 lst_str = "JavaScript";
3821 lt = g_strdup_printf("js_wl=%s", dom);
3822 break;
3823 case XT_WL_COOKIE:
3824 lst_str = "Cookie";
3825 lt = g_strdup_printf("cookie_wl=%s", dom);
3826 break;
3827 case XT_WL_PLUGIN:
3828 lst_str = "Plugin";
3829 lt = g_strdup_printf("pl_wl=%s", dom);
3830 break;
3831 default:
3832 show_oops(t, "Invalid list id: %d", list);
3833 return (1);
3836 uri = get_uri(t);
3837 dom = find_domain(uri, args->i & XT_WL_TOPLEVEL);
3838 if (uri == NULL || dom == NULL ||
3839 webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED) {
3840 show_oops(t, "Can't add domain to %s white list", lst_str);
3841 goto done;
3844 while (!feof(f)) {
3845 line = fparseln(f, &linelen, NULL, NULL, 0);
3846 if (line == NULL)
3847 continue;
3848 if (!strcmp(line, lt))
3849 goto done;
3850 free(line);
3851 line = NULL;
3854 fprintf(f, "%s\n", lt);
3856 a.i = XT_WL_ENABLE;
3857 a.i |= args->i;
3858 switch (list) {
3859 case XT_WL_JAVASCRIPT:
3860 d = wl_find(dom, &js_wl);
3861 if (!d) {
3862 settings_add("js_wl", dom);
3863 d = wl_find(dom, &js_wl);
3865 toggle_js(t, &a);
3866 break;
3868 case XT_WL_COOKIE:
3869 d = wl_find(dom, &c_wl);
3870 if (!d) {
3871 settings_add("cookie_wl", dom);
3872 d = wl_find(dom, &c_wl);
3874 toggle_cwl(t, &a);
3876 /* find and add to persistent jar */
3877 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3878 for (;cf; cf = cf->next) {
3879 ci = cf->data;
3880 if (!strcmp(dom, ci->domain) ||
3881 !strcmp(&dom[1], ci->domain)) /* deal with leading . */ {
3882 c = soup_cookie_copy(ci);
3883 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3886 soup_cookies_free(cf);
3887 break;
3889 case XT_WL_PLUGIN:
3890 d = wl_find(dom, &pl_wl);
3891 if (!d) {
3892 settings_add("pl_wl", dom);
3893 d = wl_find(dom, &pl_wl);
3895 toggle_pl(t, &a);
3896 break;
3897 default:
3898 abort(); /* can't happen */
3900 if (d)
3901 d->handy = 1;
3903 done:
3904 if (line)
3905 free(line);
3906 if (dom)
3907 g_free(dom);
3908 if (lt)
3909 g_free(lt);
3910 fclose(f);
3912 return (0);
3916 js_show_wl(struct tab *t, struct karg *args)
3918 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3919 wl_show(t, args, "JavaScript White List", &js_wl);
3921 return (0);
3925 cookie_show_wl(struct tab *t, struct karg *args)
3927 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3928 wl_show(t, args, "Cookie White List", &c_wl);
3930 return (0);
3934 cookie_cmd(struct tab *t, struct karg *args)
3936 if (args->i & XT_SHOW)
3937 wl_show(t, args, "Cookie White List", &c_wl);
3938 else if (args->i & XT_WL_TOGGLE) {
3939 args->i |= XT_WL_RELOAD;
3940 toggle_cwl(t, args);
3941 } else if (args->i & XT_SAVE) {
3942 args->i |= XT_WL_RELOAD;
3943 wl_save(t, args, XT_WL_COOKIE);
3944 } else if (args->i & XT_DELETE)
3945 show_oops(t, "'cookie delete' currently unimplemented");
3947 return (0);
3951 js_cmd(struct tab *t, struct karg *args)
3953 if (args->i & XT_SHOW)
3954 wl_show(t, args, "JavaScript White List", &js_wl);
3955 else if (args->i & XT_SAVE) {
3956 args->i |= XT_WL_RELOAD;
3957 wl_save(t, args, XT_WL_JAVASCRIPT);
3958 } else if (args->i & XT_WL_TOGGLE) {
3959 args->i |= XT_WL_RELOAD;
3960 toggle_js(t, args);
3961 } else if (args->i & XT_DELETE)
3962 show_oops(t, "'js delete' currently unimplemented");
3964 return (0);
3968 pl_show_wl(struct tab *t, struct karg *args)
3970 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3971 wl_show(t, args, "Plugin White List", &pl_wl);
3973 return (0);
3977 pl_cmd(struct tab *t, struct karg *args)
3979 if (args->i & XT_SHOW)
3980 wl_show(t, args, "Plugin White List", &pl_wl);
3981 else if (args->i & XT_SAVE) {
3982 args->i |= XT_WL_RELOAD;
3983 wl_save(t, args, XT_WL_PLUGIN);
3984 } else if (args->i & XT_WL_TOGGLE) {
3985 args->i |= XT_WL_RELOAD;
3986 toggle_pl(t, args);
3987 } else if (args->i & XT_DELETE)
3988 show_oops(t, "'plugin delete' currently unimplemented");
3990 return (0);
3994 toplevel_cmd(struct tab *t, struct karg *args)
3996 js_toggle_cb(t->js_toggle, t);
3998 return (0);
4002 add_favorite(struct tab *t, struct karg *args)
4004 char file[PATH_MAX];
4005 FILE *f;
4006 char *line = NULL;
4007 size_t urilen, linelen;
4008 const gchar *uri, *title;
4010 if (t == NULL)
4011 return (1);
4013 /* don't allow adding of xtp pages to favorites */
4014 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
4015 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
4016 return (1);
4019 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
4020 if ((f = fopen(file, "r+")) == NULL) {
4021 show_oops(t, "Can't open favorites file: %s", strerror(errno));
4022 return (1);
4025 title = get_title(t, FALSE);
4026 uri = get_uri(t);
4028 if (title == NULL || uri == NULL) {
4029 show_oops(t, "can't add page to favorites");
4030 goto done;
4033 urilen = strlen(uri);
4035 for (;;) {
4036 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
4037 if (feof(f) || ferror(f))
4038 break;
4040 if (linelen == urilen && !strcmp(line, uri))
4041 goto done;
4043 free(line);
4044 line = NULL;
4047 fprintf(f, "\n%s\n%s", title, uri);
4048 done:
4049 if (line)
4050 free(line);
4051 fclose(f);
4053 update_favorite_tabs(NULL);
4055 return (0);
4059 can_go_back_for_real(struct tab *t)
4061 int i;
4062 WebKitWebHistoryItem *item;
4063 const gchar *uri;
4065 if (t == NULL)
4066 return (FALSE);
4068 /* rely on webkit to make sure we can go backward when on an about page */
4069 uri = get_uri(t);
4070 if (uri == NULL || g_str_has_prefix(uri, "about:"))
4071 return (webkit_web_view_can_go_back(t->wv));
4073 /* the back/forwars list is stupid so help determine if we can go back */
4074 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4075 item != NULL;
4076 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4077 if (strcmp(webkit_web_history_item_get_uri(item), uri))
4078 return (TRUE);
4081 return (FALSE);
4085 can_go_forward_for_real(struct tab *t)
4087 int i;
4088 WebKitWebHistoryItem *item;
4089 const gchar *uri;
4091 if (t == NULL)
4092 return (FALSE);
4094 /* rely on webkit to make sure we can go forward when on an about page */
4095 uri = get_uri(t);
4096 if (uri == NULL || g_str_has_prefix(uri, "about:"))
4097 return (webkit_web_view_can_go_forward(t->wv));
4099 /* the back/forwars list is stupid so help selecting a different item */
4100 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4101 item != NULL;
4102 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4103 if (strcmp(webkit_web_history_item_get_uri(item), uri))
4104 return (TRUE);
4107 return (FALSE);
4110 void
4111 go_back_for_real(struct tab *t)
4113 int i;
4114 WebKitWebHistoryItem *item;
4115 const gchar *uri;
4117 if (t == NULL)
4118 return;
4120 uri = get_uri(t);
4121 if (uri == NULL) {
4122 webkit_web_view_go_back(t->wv);
4123 return;
4125 /* the back/forwars list is stupid so help selecting a different item */
4126 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4127 item != NULL;
4128 i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4129 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
4130 webkit_web_view_go_to_back_forward_item(t->wv, item);
4131 break;
4136 void
4137 go_forward_for_real(struct tab *t)
4139 int i;
4140 WebKitWebHistoryItem *item;
4141 const gchar *uri;
4143 if (t == NULL)
4144 return;
4146 uri = get_uri(t);
4147 if (uri == NULL) {
4148 webkit_web_view_go_forward(t->wv);
4149 return;
4151 /* the back/forwars list is stupid so help selecting a different item */
4152 for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
4153 item != NULL;
4154 i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
4155 if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
4156 webkit_web_view_go_to_back_forward_item(t->wv, item);
4157 break;
4163 navaction(struct tab *t, struct karg *args)
4165 WebKitWebHistoryItem *item;
4166 WebKitWebFrame *frame;
4168 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
4169 t->tab_id, args->i);
4171 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4172 if (t->item) {
4173 if (args->i == XT_NAV_BACK)
4174 item = webkit_web_back_forward_list_get_current_item(t->bfl);
4175 else
4176 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
4177 if (item == NULL)
4178 return (XT_CB_PASSTHROUGH);
4179 webkit_web_view_go_to_back_forward_item(t->wv, item);
4180 t->item = NULL;
4181 return (XT_CB_PASSTHROUGH);
4184 switch (args->i) {
4185 case XT_NAV_BACK:
4186 marks_clear(t);
4187 go_back_for_real(t);
4188 break;
4189 case XT_NAV_FORWARD:
4190 marks_clear(t);
4191 go_forward_for_real(t);
4192 break;
4193 case XT_NAV_RELOAD:
4194 frame = webkit_web_view_get_main_frame(t->wv);
4195 webkit_web_frame_reload(frame);
4196 break;
4198 return (XT_CB_PASSTHROUGH);
4202 move(struct tab *t, struct karg *args)
4204 GtkAdjustment *adjust;
4205 double pi, si, pos, ps, upper, lower, max;
4206 double percent;
4208 switch (args->i) {
4209 case XT_MOVE_DOWN:
4210 case XT_MOVE_UP:
4211 case XT_MOVE_BOTTOM:
4212 case XT_MOVE_TOP:
4213 case XT_MOVE_PAGEDOWN:
4214 case XT_MOVE_PAGEUP:
4215 case XT_MOVE_HALFDOWN:
4216 case XT_MOVE_HALFUP:
4217 case XT_MOVE_PERCENT:
4218 adjust = t->adjust_v;
4219 break;
4220 default:
4221 adjust = t->adjust_h;
4222 break;
4225 pos = gtk_adjustment_get_value(adjust);
4226 ps = gtk_adjustment_get_page_size(adjust);
4227 upper = gtk_adjustment_get_upper(adjust);
4228 lower = gtk_adjustment_get_lower(adjust);
4229 si = gtk_adjustment_get_step_increment(adjust);
4230 pi = gtk_adjustment_get_page_increment(adjust);
4231 max = upper - ps;
4233 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
4234 "max %f si %f pi %f\n",
4235 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
4236 pos, ps, upper, lower, max, si, pi);
4238 switch (args->i) {
4239 case XT_MOVE_DOWN:
4240 case XT_MOVE_RIGHT:
4241 pos += si;
4242 gtk_adjustment_set_value(adjust, MIN(pos, max));
4243 break;
4244 case XT_MOVE_UP:
4245 case XT_MOVE_LEFT:
4246 pos -= si;
4247 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4248 break;
4249 case XT_MOVE_BOTTOM:
4250 case XT_MOVE_FARRIGHT:
4251 gtk_adjustment_set_value(adjust, max);
4252 break;
4253 case XT_MOVE_TOP:
4254 case XT_MOVE_FARLEFT:
4255 gtk_adjustment_set_value(adjust, lower);
4256 break;
4257 case XT_MOVE_PAGEDOWN:
4258 pos += pi;
4259 gtk_adjustment_set_value(adjust, MIN(pos, max));
4260 break;
4261 case XT_MOVE_PAGEUP:
4262 pos -= pi;
4263 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4264 break;
4265 case XT_MOVE_HALFDOWN:
4266 pos += pi / 2;
4267 gtk_adjustment_set_value(adjust, MIN(pos, max));
4268 break;
4269 case XT_MOVE_HALFUP:
4270 pos -= pi / 2;
4271 gtk_adjustment_set_value(adjust, MAX(pos, lower));
4272 break;
4273 case XT_MOVE_PERCENT:
4274 percent = atoi(args->s) / 100.0;
4275 pos = max * percent;
4276 if (pos < 0.0 || pos > max)
4277 break;
4278 gtk_adjustment_set_value(adjust, pos);
4279 break;
4280 default:
4281 return (XT_CB_PASSTHROUGH);
4284 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
4286 return (XT_CB_HANDLED);
4289 void
4290 url_set_visibility(void)
4292 struct tab *t;
4294 TAILQ_FOREACH(t, &tabs, entry)
4295 if (show_url == 0) {
4296 gtk_widget_hide(t->toolbar);
4297 focus_webview(t);
4298 } else
4299 gtk_widget_show(t->toolbar);
4302 void
4303 notebook_tab_set_visibility(void)
4305 if (show_tabs == 0) {
4306 gtk_widget_hide(tab_bar);
4307 gtk_notebook_set_show_tabs(notebook, FALSE);
4308 } else {
4309 if (tab_style == XT_TABS_NORMAL) {
4310 gtk_widget_hide(tab_bar);
4311 gtk_notebook_set_show_tabs(notebook, TRUE);
4312 } else if (tab_style == XT_TABS_COMPACT) {
4313 gtk_widget_show(tab_bar);
4314 gtk_notebook_set_show_tabs(notebook, FALSE);
4319 void
4320 statusbar_set_visibility(void)
4322 struct tab *t;
4324 TAILQ_FOREACH(t, &tabs, entry)
4325 if (show_statusbar == 0) {
4326 gtk_widget_hide(t->statusbar_box);
4327 focus_webview(t);
4328 } else
4329 gtk_widget_show(t->statusbar_box);
4332 void
4333 url_set(struct tab *t, int enable_url_entry)
4335 GdkPixbuf *pixbuf;
4336 int progress;
4338 show_url = enable_url_entry;
4340 if (enable_url_entry) {
4341 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
4342 GTK_ENTRY_ICON_PRIMARY, NULL);
4343 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar), 0);
4344 } else {
4345 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
4346 GTK_ENTRY_ICON_PRIMARY);
4347 progress =
4348 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
4349 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
4350 GTK_ENTRY_ICON_PRIMARY, pixbuf);
4351 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
4352 progress);
4357 fullscreen(struct tab *t, struct karg *args)
4359 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4361 if (t == NULL)
4362 return (XT_CB_PASSTHROUGH);
4364 if (show_url == 0) {
4365 url_set(t, 1);
4366 show_tabs = 1;
4367 } else {
4368 url_set(t, 0);
4369 show_tabs = 0;
4372 url_set_visibility();
4373 notebook_tab_set_visibility();
4375 return (XT_CB_HANDLED);
4379 statustoggle(struct tab *t, struct karg *args)
4381 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4383 if (show_statusbar == 1) {
4384 show_statusbar = 0;
4385 statusbar_set_visibility();
4386 } else if (show_statusbar == 0) {
4387 show_statusbar = 1;
4388 statusbar_set_visibility();
4390 return (XT_CB_HANDLED);
4394 urlaction(struct tab *t, struct karg *args)
4396 int rv = XT_CB_HANDLED;
4398 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
4400 if (t == NULL)
4401 return (XT_CB_PASSTHROUGH);
4403 switch (args->i) {
4404 case XT_URL_SHOW:
4405 if (show_url == 0) {
4406 url_set(t, 1);
4407 url_set_visibility();
4409 break;
4410 case XT_URL_HIDE:
4411 if (show_url == 1) {
4412 url_set(t, 0);
4413 url_set_visibility();
4415 break;
4417 return (rv);
4421 tabaction(struct tab *t, struct karg *args)
4423 int rv = XT_CB_HANDLED;
4424 char *url = args->s;
4425 struct undo *u;
4426 struct tab *tt;
4428 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
4430 if (t == NULL)
4431 return (XT_CB_PASSTHROUGH);
4433 switch (args->i) {
4434 case XT_TAB_NEW:
4435 if (strlen(url) > 0)
4436 create_new_tab(url, NULL, 1, args->precount);
4437 else
4438 create_new_tab(NULL, NULL, 1, args->precount);
4439 break;
4440 case XT_TAB_DELETE:
4441 if (args->precount < 0)
4442 delete_tab(t);
4443 else
4444 TAILQ_FOREACH(tt, &tabs, entry)
4445 if (tt->tab_id == args->precount - 1) {
4446 delete_tab(tt);
4447 break;
4449 break;
4450 case XT_TAB_DELQUIT:
4451 if (gtk_notebook_get_n_pages(notebook) > 1)
4452 delete_tab(t);
4453 else
4454 quit(t, args);
4455 break;
4456 case XT_TAB_OPEN:
4457 if (strlen(url) > 0)
4459 else {
4460 rv = XT_CB_PASSTHROUGH;
4461 goto done;
4463 load_uri(t, url);
4464 break;
4465 case XT_TAB_SHOW:
4466 if (show_tabs == 0) {
4467 show_tabs = 1;
4468 notebook_tab_set_visibility();
4470 break;
4471 case XT_TAB_HIDE:
4472 if (show_tabs == 1) {
4473 show_tabs = 0;
4474 notebook_tab_set_visibility();
4476 break;
4477 case XT_TAB_NEXTSTYLE:
4478 if (tab_style == XT_TABS_NORMAL) {
4479 tab_style = XT_TABS_COMPACT;
4480 recolor_compact_tabs();
4482 else
4483 tab_style = XT_TABS_NORMAL;
4484 notebook_tab_set_visibility();
4485 break;
4486 case XT_TAB_UNDO_CLOSE:
4487 if (undo_count == 0) {
4488 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
4489 __func__);
4490 goto done;
4491 } else {
4492 undo_count--;
4493 u = TAILQ_FIRST(&undos);
4494 create_new_tab(u->uri, u, 1, -1);
4496 TAILQ_REMOVE(&undos, u, entry);
4497 g_free(u->uri);
4498 /* u->history is freed in create_new_tab() */
4499 g_free(u);
4501 break;
4502 default:
4503 rv = XT_CB_PASSTHROUGH;
4504 goto done;
4507 done:
4508 if (args->s) {
4509 g_free(args->s);
4510 args->s = NULL;
4513 return (rv);
4517 resizetab(struct tab *t, struct karg *args)
4519 if (t == NULL || args == NULL) {
4520 show_oops(NULL, "resizetab invalid parameters");
4521 return (XT_CB_PASSTHROUGH);
4524 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
4525 t->tab_id, args->i);
4527 setzoom_webkit(t, args->i);
4529 return (XT_CB_HANDLED);
4533 movetab(struct tab *t, struct karg *args)
4535 int n, dest;
4537 if (t == NULL || args == NULL) {
4538 show_oops(NULL, "movetab invalid parameters");
4539 return (XT_CB_PASSTHROUGH);
4542 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
4543 t->tab_id, args->i);
4545 if (args->i >= XT_TAB_INVALID)
4546 return (XT_CB_PASSTHROUGH);
4548 if (TAILQ_EMPTY(&tabs))
4549 return (XT_CB_PASSTHROUGH);
4551 n = gtk_notebook_get_n_pages(notebook);
4552 dest = gtk_notebook_get_current_page(notebook);
4554 switch (args->i) {
4555 case XT_TAB_NEXT:
4556 if (args->precount < 0)
4557 dest = dest == n - 1 ? 0 : dest + 1;
4558 else
4559 dest = args->precount - 1;
4561 break;
4562 case XT_TAB_PREV:
4563 if (args->precount < 0)
4564 dest -= 1;
4565 else
4566 dest -= args->precount % n;
4568 if (dest < 0)
4569 dest += n;
4571 break;
4572 case XT_TAB_FIRST:
4573 dest = 0;
4574 break;
4575 case XT_TAB_LAST:
4576 dest = n - 1;
4577 break;
4578 default:
4579 return (XT_CB_PASSTHROUGH);
4582 if (dest < 0 || dest >= n)
4583 return (XT_CB_PASSTHROUGH);
4584 if (t->tab_id == dest) {
4585 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
4586 return (XT_CB_HANDLED);
4589 set_current_tab(dest);
4591 return (XT_CB_HANDLED);
4594 int cmd_prefix = 0;
4597 command_mode(struct tab *t, struct karg *args)
4599 if (args->i == XT_MODE_COMMAND)
4600 run_script(t, "hints.clearFocus();");
4601 else
4602 run_script(t, "hints.focusInput();");
4603 return (XT_CB_HANDLED);
4607 command(struct tab *t, struct karg *args)
4609 char *s = NULL, *ss = NULL;
4610 GdkColor color;
4611 const gchar *uri;
4612 struct karg a;
4614 if (t == NULL || args == NULL) {
4615 show_oops(NULL, "command invalid parameters");
4616 return (XT_CB_PASSTHROUGH);
4619 switch (args->i) {
4620 case '/':
4621 s = "/";
4622 break;
4623 case '?':
4624 s = "?";
4625 break;
4626 case ':':
4627 if (cmd_prefix == 0)
4628 s = ":";
4629 else {
4630 ss = g_strdup_printf(":%d", cmd_prefix);
4631 s = ss;
4632 cmd_prefix = 0;
4634 break;
4635 case '.':
4636 bzero(&a, sizeof a);
4637 a.i = 0;
4638 hint(t, &a);
4639 s = ".";
4640 break;
4641 case ',':
4642 bzero(&a, sizeof a);
4643 a.i = XT_HINT_NEWTAB;
4644 hint(t, &a);
4645 s = ",";
4646 break;
4647 case XT_CMD_OPEN:
4648 s = ":open ";
4649 break;
4650 case XT_CMD_TABNEW:
4651 s = ":tabnew ";
4652 break;
4653 case XT_CMD_OPEN_CURRENT:
4654 s = ":open ";
4655 /* FALL THROUGH */
4656 case XT_CMD_TABNEW_CURRENT:
4657 if (!s) /* FALL THROUGH? */
4658 s = ":tabnew ";
4659 if ((uri = get_uri(t)) != NULL) {
4660 ss = g_strdup_printf("%s%s", s, uri);
4661 s = ss;
4663 break;
4664 default:
4665 show_oops(t, "command: invalid opcode %d", args->i);
4666 return (XT_CB_PASSTHROUGH);
4669 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
4671 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
4672 gdk_color_parse(XT_COLOR_WHITE, &color);
4673 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4674 show_cmd(t);
4675 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
4676 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
4678 if (ss)
4679 g_free(ss);
4681 return (XT_CB_HANDLED);
4685 * Return a new string with a download row (in html)
4686 * appended. Old string is freed.
4688 char *
4689 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
4692 WebKitDownloadStatus stat;
4693 char *status_html = NULL, *cmd_html = NULL, *new_html;
4694 gdouble progress;
4695 char cur_sz[FMT_SCALED_STRSIZE];
4696 char tot_sz[FMT_SCALED_STRSIZE];
4697 char *xtp_prefix;
4699 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
4701 /* All actions wil take this form:
4702 * xxxt://class/seskey
4704 xtp_prefix = g_strdup_printf("%s%d/%s/",
4705 XT_XTP_STR, XT_XTP_DL, dl_session_key);
4707 stat = webkit_download_get_status(dl->download);
4709 switch (stat) {
4710 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4711 status_html = g_strdup_printf("Finished");
4712 cmd_html = g_strdup_printf(
4713 "<a href='%s%d/%d'>Remove</a>",
4714 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4715 break;
4716 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4717 /* gather size info */
4718 progress = 100 * webkit_download_get_progress(dl->download);
4720 fmt_scaled(
4721 webkit_download_get_current_size(dl->download), cur_sz);
4722 fmt_scaled(
4723 webkit_download_get_total_size(dl->download), tot_sz);
4725 status_html = g_strdup_printf(
4726 "<div style='width: 100%%' align='center'>"
4727 "<div class='progress-outer'>"
4728 "<div class='progress-inner' style='width: %.2f%%'>"
4729 "</div></div></div>"
4730 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
4731 progress, cur_sz, tot_sz, progress);
4733 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4734 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4736 break;
4737 /* LLL */
4738 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4739 status_html = g_strdup_printf("Cancelled");
4740 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4741 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4742 break;
4743 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4744 status_html = g_strdup_printf("Error!");
4745 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
4746 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
4747 break;
4748 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4749 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4750 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4751 status_html = g_strdup_printf("Starting");
4752 break;
4753 default:
4754 show_oops(t, "%s: unknown download status", __func__);
4757 new_html = g_strdup_printf(
4758 "%s\n<tr><td>%s</td><td>%s</td>"
4759 "<td style='text-align:center'>%s</td></tr>\n",
4760 html, basename((char *)webkit_download_get_destination_uri(dl->download)),
4761 status_html, cmd_html);
4762 g_free(html);
4764 if (status_html)
4765 g_free(status_html);
4767 if (cmd_html)
4768 g_free(cmd_html);
4770 g_free(xtp_prefix);
4772 return new_html;
4776 * update all download tabs apart from one. Pass NULL if
4777 * you want to update all.
4779 void
4780 update_download_tabs(struct tab *apart_from)
4782 struct tab *t;
4783 if (!updating_dl_tabs) {
4784 updating_dl_tabs = 1; /* stop infinite recursion */
4785 TAILQ_FOREACH(t, &tabs, entry)
4786 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4787 && (t != apart_from))
4788 xtp_page_dl(t, NULL);
4789 updating_dl_tabs = 0;
4794 * update all cookie tabs apart from one. Pass NULL if
4795 * you want to update all.
4797 void
4798 update_cookie_tabs(struct tab *apart_from)
4800 struct tab *t;
4801 if (!updating_cl_tabs) {
4802 updating_cl_tabs = 1; /* stop infinite recursion */
4803 TAILQ_FOREACH(t, &tabs, entry)
4804 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4805 && (t != apart_from))
4806 xtp_page_cl(t, NULL);
4807 updating_cl_tabs = 0;
4812 * update all history tabs apart from one. Pass NULL if
4813 * you want to update all.
4815 void
4816 update_history_tabs(struct tab *apart_from)
4818 struct tab *t;
4820 if (!updating_hl_tabs) {
4821 updating_hl_tabs = 1; /* stop infinite recursion */
4822 TAILQ_FOREACH(t, &tabs, entry)
4823 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4824 && (t != apart_from))
4825 xtp_page_hl(t, NULL);
4826 updating_hl_tabs = 0;
4830 /* cookie management XTP page */
4832 xtp_page_cl(struct tab *t, struct karg *args)
4834 char *body, *page, *tmp;
4835 int i = 1; /* all ids start 1 */
4836 GSList *sc, *pc, *pc_start;
4837 SoupCookie *c;
4838 char *type, *table_headers, *last_domain;
4840 DNPRINTF(XT_D_CMD, "%s", __func__);
4842 if (t == NULL) {
4843 show_oops(NULL, "%s invalid parameters", __func__);
4844 return (1);
4847 /* Generate a new session key */
4848 if (!updating_cl_tabs)
4849 generate_xtp_session_key(&cl_session_key);
4851 /* table headers */
4852 table_headers = g_strdup_printf("<table><tr>"
4853 "<th>Type</th>"
4854 "<th>Name</th>"
4855 "<th style='width:200px'>Value</th>"
4856 "<th>Path</th>"
4857 "<th>Expires</th>"
4858 "<th>Secure</th>"
4859 "<th>HTTP<br />only</th>"
4860 "<th style='width:40px'>Rm</th></tr>\n");
4862 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4863 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4864 pc_start = pc;
4866 body = NULL;
4867 last_domain = strdup("");
4868 for (; sc; sc = sc->next) {
4869 c = sc->data;
4871 if (strcmp(last_domain, c->domain) != 0) {
4872 /* new domain */
4873 free(last_domain);
4874 last_domain = strdup(c->domain);
4876 if (body != NULL) {
4877 tmp = body;
4878 body = g_strdup_printf("%s</table>"
4879 "<h2>%s</h2>%s\n",
4880 body, c->domain, table_headers);
4881 g_free(tmp);
4882 } else {
4883 /* first domain */
4884 body = g_strdup_printf("<h2>%s</h2>%s\n",
4885 c->domain, table_headers);
4889 type = "Session";
4890 for (pc = pc_start; pc; pc = pc->next)
4891 if (soup_cookie_equal(pc->data, c)) {
4892 type = "Session + Persistent";
4893 break;
4896 tmp = body;
4897 body = g_strdup_printf(
4898 "%s\n<tr>"
4899 "<td>%s</td>"
4900 "<td style='word-wrap:normal'>%s</td>"
4901 "<td>"
4902 " <textarea rows='4'>%s</textarea>"
4903 "</td>"
4904 "<td>%s</td>"
4905 "<td>%s</td>"
4906 "<td>%d</td>"
4907 "<td>%d</td>"
4908 "<td style='text-align:center'>"
4909 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4910 body,
4911 type,
4912 c->name,
4913 c->value,
4914 c->path,
4915 c->expires ?
4916 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4917 c->secure,
4918 c->http_only,
4920 XT_XTP_STR,
4921 XT_XTP_CL,
4922 cl_session_key,
4923 XT_XTP_CL_REMOVE,
4927 g_free(tmp);
4928 i++;
4931 soup_cookies_free(sc);
4932 soup_cookies_free(pc);
4934 /* small message if there are none */
4935 if (i == 1) {
4936 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4937 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4939 tmp = body;
4940 body = g_strdup_printf("%s</table>", body);
4941 g_free(tmp);
4943 page = get_html_page("Cookie Jar", body, "", TRUE);
4944 g_free(body);
4945 g_free(table_headers);
4946 g_free(last_domain);
4948 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4949 update_cookie_tabs(t);
4951 g_free(page);
4953 return (0);
4957 xtp_page_hl(struct tab *t, struct karg *args)
4959 char *body, *page, *tmp;
4960 struct history *h;
4961 int i = 1; /* all ids start 1 */
4963 DNPRINTF(XT_D_CMD, "%s", __func__);
4965 if (t == NULL) {
4966 show_oops(NULL, "%s invalid parameters", __func__);
4967 return (1);
4970 /* Generate a new session key */
4971 if (!updating_hl_tabs)
4972 generate_xtp_session_key(&hl_session_key);
4974 /* body */
4975 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4976 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4978 RB_FOREACH_REVERSE(h, history_list, &hl) {
4979 tmp = body;
4980 body = g_strdup_printf(
4981 "%s\n<tr>"
4982 "<td><a href='%s'>%s</a></td>"
4983 "<td>%s</td>"
4984 "<td style='text-align: center'>"
4985 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4986 body, h->uri, h->uri, h->title,
4987 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4988 XT_XTP_HL_REMOVE, i);
4990 g_free(tmp);
4991 i++;
4994 /* small message if there are none */
4995 if (i == 1) {
4996 tmp = body;
4997 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4998 "colspan='3'>No History</td></tr>\n", body);
4999 g_free(tmp);
5002 tmp = body;
5003 body = g_strdup_printf("%s</table>", body);
5004 g_free(tmp);
5006 page = get_html_page("History", body, "", TRUE);
5007 g_free(body);
5010 * update all history manager tabs as the xtp session
5011 * key has now changed. No need to update the current tab.
5012 * Already did that above.
5014 update_history_tabs(t);
5016 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
5017 g_free(page);
5019 return (0);
5023 * Generate a web page detailing the status of any downloads
5026 xtp_page_dl(struct tab *t, struct karg *args)
5028 struct download *dl;
5029 char *body, *page, *tmp;
5030 char *ref;
5031 int n_dl = 1;
5033 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
5035 if (t == NULL) {
5036 show_oops(NULL, "%s invalid parameters", __func__);
5037 return (1);
5041 * Generate a new session key for next page instance.
5042 * This only happens for the top level call to xtp_page_dl()
5043 * in which case updating_dl_tabs is 0.
5045 if (!updating_dl_tabs)
5046 generate_xtp_session_key(&dl_session_key);
5048 /* header - with refresh so as to update */
5049 if (refresh_interval >= 1)
5050 ref = g_strdup_printf(
5051 "<meta http-equiv='refresh' content='%u"
5052 ";url=%s%d/%s/%d' />\n",
5053 refresh_interval,
5054 XT_XTP_STR,
5055 XT_XTP_DL,
5056 dl_session_key,
5057 XT_XTP_DL_LIST);
5058 else
5059 ref = g_strdup("");
5061 body = g_strdup_printf("<div align='center'>"
5062 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
5063 "</p><table><tr><th style='width: 60%%'>"
5064 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
5065 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
5067 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
5068 body = xtp_page_dl_row(t, body, dl);
5069 n_dl++;
5072 /* message if no downloads in list */
5073 if (n_dl == 1) {
5074 tmp = body;
5075 body = g_strdup_printf("%s\n<tr><td colspan='3'"
5076 " style='text-align: center'>"
5077 "No downloads</td></tr>\n", body);
5078 g_free(tmp);
5081 tmp = body;
5082 body = g_strdup_printf("%s</table></div>", body);
5083 g_free(tmp);
5085 page = get_html_page("Downloads", body, ref, 1);
5086 g_free(ref);
5087 g_free(body);
5090 * update all download manager tabs as the xtp session
5091 * key has now changed. No need to update the current tab.
5092 * Already did that above.
5094 update_download_tabs(t);
5096 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
5097 g_free(page);
5099 return (0);
5103 search(struct tab *t, struct karg *args)
5105 gboolean d;
5107 if (t == NULL || args == NULL) {
5108 show_oops(NULL, "search invalid parameters");
5109 return (1);
5112 switch (args->i) {
5113 case XT_SEARCH_NEXT:
5114 d = t->search_forward;
5115 break;
5116 case XT_SEARCH_PREV:
5117 d = !t->search_forward;
5118 break;
5119 default:
5120 return (XT_CB_PASSTHROUGH);
5123 if (t->search_text == NULL) {
5124 if (global_search == NULL)
5125 return (XT_CB_PASSTHROUGH);
5126 else {
5127 d = t->search_forward = TRUE;
5128 t->search_text = g_strdup(global_search);
5129 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
5130 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
5134 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
5135 t->tab_id, args->i, t->search_forward, t->search_text);
5137 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
5139 return (XT_CB_HANDLED);
5142 struct settings_args {
5143 char **body;
5144 int i;
5147 void
5148 print_setting(struct settings *s, char *val, void *cb_args)
5150 char *tmp, *color;
5151 struct settings_args *sa = cb_args;
5153 if (sa == NULL)
5154 return;
5156 if (s->flags & XT_SF_RUNTIME)
5157 color = "#22cc22";
5158 else
5159 color = "#cccccc";
5161 tmp = *sa->body;
5162 *sa->body = g_strdup_printf(
5163 "%s\n<tr>"
5164 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
5165 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
5166 *sa->body,
5167 color,
5168 s->name,
5169 color,
5172 g_free(tmp);
5173 sa->i++;
5177 set_show(struct tab *t, struct karg *args)
5179 char *body, *page, *tmp;
5180 int i = 1;
5181 struct settings_args sa;
5183 bzero(&sa, sizeof sa);
5184 sa.body = &body;
5186 /* body */
5187 body = g_strdup_printf("<div align='center'><table><tr>"
5188 "<th align='left'>Setting</th>"
5189 "<th align='left'>Value</th></tr>\n");
5191 settings_walk(print_setting, &sa);
5192 i = sa.i;
5194 /* small message if there are none */
5195 if (i == 1) {
5196 tmp = body;
5197 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
5198 "colspan='2'>No settings</td></tr>\n", body);
5199 g_free(tmp);
5202 tmp = body;
5203 body = g_strdup_printf("%s</table></div>", body);
5204 g_free(tmp);
5206 page = get_html_page("Settings", body, "", 0);
5208 g_free(body);
5210 load_webkit_string(t, page, XT_URI_ABOUT_SET);
5212 g_free(page);
5214 return (XT_CB_PASSTHROUGH);
5218 set(struct tab *t, struct karg *args)
5220 char *p, *val;
5221 int i;
5223 if (args == NULL || args->s == NULL)
5224 return (set_show(t, args));
5226 /* strip spaces */
5227 p = g_strstrip(args->s);
5229 if (strlen(p) == 0)
5230 return (set_show(t, args));
5232 /* we got some sort of string */
5233 val = g_strrstr(p, "=");
5234 if (val) {
5235 *val++ = '\0';
5236 val = g_strchomp(val);
5237 p = g_strchomp(p);
5239 for (i = 0; i < LENGTH(rs); i++) {
5240 if (strcmp(rs[i].name, p))
5241 continue;
5243 if (rs[i].activate) {
5244 if (rs[i].activate(val))
5245 show_oops(t, "%s invalid value %s",
5246 p, val);
5247 else
5248 show_oops(t, ":set %s = %s", p, val);
5249 goto done;
5250 } else {
5251 show_oops(t, "not a runtime option: %s", p);
5252 goto done;
5255 show_oops(t, "unknown option: %s", p);
5256 } else {
5257 p = g_strchomp(p);
5259 for (i = 0; i < LENGTH(rs); i++) {
5260 if (strcmp(rs[i].name, p))
5261 continue;
5263 /* XXX this could use some cleanup */
5264 switch (rs[i].type) {
5265 case XT_S_INT:
5266 if (rs[i].ival)
5267 show_oops(t, "%s = %d",
5268 rs[i].name, *rs[i].ival);
5269 else if (rs[i].s && rs[i].s->get)
5270 show_oops(t, "%s = %s",
5271 rs[i].name,
5272 rs[i].s->get(&rs[i]));
5273 else if (rs[i].s && rs[i].s->get == NULL)
5274 show_oops(t, "%s = ...", rs[i].name);
5275 else
5276 show_oops(t, "%s = ", rs[i].name);
5277 break;
5278 case XT_S_FLOAT:
5279 if (rs[i].fval)
5280 show_oops(t, "%s = %f",
5281 rs[i].name, *rs[i].fval);
5282 else if (rs[i].s && rs[i].s->get)
5283 show_oops(t, "%s = %s",
5284 rs[i].name,
5285 rs[i].s->get(&rs[i]));
5286 else if (rs[i].s && rs[i].s->get == NULL)
5287 show_oops(t, "%s = ...", rs[i].name);
5288 else
5289 show_oops(t, "%s = ", rs[i].name);
5290 break;
5291 case XT_S_STR:
5292 if (rs[i].sval && *rs[i].sval)
5293 show_oops(t, "%s = %s",
5294 rs[i].name, *rs[i].sval);
5295 else if (rs[i].s && rs[i].s->get)
5296 show_oops(t, "%s = %s",
5297 rs[i].name,
5298 rs[i].s->get(&rs[i]));
5299 else if (rs[i].s && rs[i].s->get == NULL)
5300 show_oops(t, "%s = ...", rs[i].name);
5301 else
5302 show_oops(t, "%s = ", rs[i].name);
5303 break;
5304 default:
5305 show_oops(t, "unknown type for %s", rs[i].name);
5306 goto done;
5309 goto done;
5311 show_oops(t, "unknown option: %s", p);
5313 done:
5314 return (XT_CB_PASSTHROUGH);
5318 session_save(struct tab *t, char *filename)
5320 struct karg a;
5321 int rv = 1;
5322 struct session *s;
5324 if (strlen(filename) == 0)
5325 goto done;
5327 if (filename[0] == '.' || filename[0] == '/')
5328 goto done;
5330 a.s = filename;
5331 if (save_tabs(t, &a))
5332 goto done;
5333 strlcpy(named_session, filename, sizeof named_session);
5335 /* add the new session to the list of sessions */
5336 s = g_malloc(sizeof(struct session));
5337 s->name = g_strdup(filename);
5338 TAILQ_INSERT_TAIL(&sessions, s, entry);
5340 rv = 0;
5341 done:
5342 return (rv);
5346 session_open(struct tab *t, char *filename)
5348 struct karg a;
5349 int rv = 1;
5351 if (strlen(filename) == 0)
5352 goto done;
5354 if (filename[0] == '.' || filename[0] == '/')
5355 goto done;
5357 a.s = filename;
5358 a.i = XT_SES_CLOSETABS;
5359 if (open_tabs(t, &a))
5360 goto done;
5362 strlcpy(named_session, filename, sizeof named_session);
5364 rv = 0;
5365 done:
5366 return (rv);
5370 session_delete(struct tab *t, char *filename)
5372 char file[PATH_MAX];
5373 int rv = 1;
5374 struct session *s;
5376 if (strlen(filename) == 0)
5377 goto done;
5379 if (filename[0] == '.' || filename[0] == '/')
5380 goto done;
5382 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
5383 if (unlink(file))
5384 goto done;
5386 if (!strcmp(filename, named_session))
5387 strlcpy(named_session, XT_SAVED_TABS_FILE,
5388 sizeof named_session);
5390 /* remove session from sessions list */
5391 TAILQ_FOREACH(s, &sessions, entry) {
5392 if (!strcmp(s->name, filename))
5393 break;
5395 if (s == NULL)
5396 goto done;
5397 TAILQ_REMOVE(&sessions, s, entry);
5398 g_free((gpointer) s->name);
5399 g_free(s);
5401 rv = 0;
5402 done:
5403 return (rv);
5407 session_cmd(struct tab *t, struct karg *args)
5409 char *filename = args->s;
5411 if (t == NULL)
5412 return (1);
5414 if (args->i & XT_SHOW)
5415 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
5416 XT_SAVED_TABS_FILE : named_session);
5417 else if (args->i & XT_SAVE) {
5418 if (session_save(t, filename)) {
5419 show_oops(t, "Can't save session: %s",
5420 filename ? filename : "INVALID");
5421 goto done;
5423 } else if (args->i & XT_OPEN) {
5424 if (session_open(t, filename)) {
5425 show_oops(t, "Can't open session: %s",
5426 filename ? filename : "INVALID");
5427 goto done;
5429 } else if (args->i & XT_DELETE) {
5430 if (session_delete(t, filename)) {
5431 show_oops(t, "Can't delete session: %s",
5432 filename ? filename : "INVALID");
5433 goto done;
5436 done:
5437 return (XT_CB_PASSTHROUGH);
5441 script_cmd(struct tab *t, struct karg *args)
5443 JSGlobalContextRef ctx;
5444 WebKitWebFrame *frame;
5445 JSStringRef str;
5446 JSValueRef val, exception;
5447 char *es;
5448 struct stat sb;
5449 FILE *f = NULL;
5450 char *buf = NULL;
5452 if (t == NULL)
5453 goto done;
5455 if ((f = fopen(args->s, "r")) == NULL) {
5456 show_oops(t, "Can't open script file: %s", args->s);
5457 goto done;
5460 if (fstat(fileno(f), &sb) == -1) {
5461 show_oops(t, "Can't stat script file: %s", args->s);
5462 goto done;
5465 buf = g_malloc0(sb.st_size + 1);
5466 if (fread(buf, 1, sb.st_size, f) != sb.st_size) {
5467 show_oops(t, "Can't read script file: %s", args->s);
5468 goto done;
5471 /* this code needs to be redone */
5472 frame = webkit_web_view_get_main_frame(t->wv);
5473 ctx = webkit_web_frame_get_global_context(frame);
5475 str = JSStringCreateWithUTF8CString(buf);
5476 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
5477 NULL, 0, &exception);
5478 JSStringRelease(str);
5480 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
5481 if (val == NULL) {
5482 es = js_ref_to_string(ctx, exception);
5483 if (es) {
5484 show_oops(t, "script exception: %s", es);
5485 g_free(es);
5487 goto done;
5488 } else {
5489 es = js_ref_to_string(ctx, val);
5490 #if 0
5491 /* return values */
5492 if (!strncmp(es, XT_JS_DONE, XT_JS_DONE_LEN))
5493 ; /* do nothing */
5494 if (!strncmp(es, XT_JS_INSERT, XT_JS_INSERT_LEN))
5495 ; /* do nothing */
5496 #endif
5497 if (es) {
5498 show_oops(t, "script complete return value: '%s'", es);
5499 g_free(es);
5500 } else
5501 show_oops(t, "script complete: without a return value");
5504 done:
5505 if (f)
5506 fclose(f);
5507 if (buf)
5508 g_free(buf);
5510 return (XT_CB_PASSTHROUGH);
5514 * Make a hardcopy of the page
5517 print_page(struct tab *t, struct karg *args)
5519 WebKitWebFrame *frame;
5520 GtkPageSetup *ps;
5521 GtkPrintOperation *op;
5522 GtkPrintOperationAction action;
5523 GtkPrintOperationResult print_res;
5524 GError *g_err = NULL;
5525 int marg_l, marg_r, marg_t, marg_b;
5527 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
5529 ps = gtk_page_setup_new();
5530 op = gtk_print_operation_new();
5531 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
5532 frame = webkit_web_view_get_main_frame(t->wv);
5534 /* the default margins are too small, so we will bump them */
5535 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
5536 XT_PRINT_EXTRA_MARGIN;
5537 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
5538 XT_PRINT_EXTRA_MARGIN;
5539 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
5540 XT_PRINT_EXTRA_MARGIN;
5541 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
5542 XT_PRINT_EXTRA_MARGIN;
5544 /* set margins */
5545 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
5546 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
5547 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
5548 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
5550 gtk_print_operation_set_default_page_setup(op, ps);
5552 /* this appears to free 'op' and 'ps' */
5553 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
5555 /* check it worked */
5556 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
5557 show_oops(NULL, "can't print: %s", g_err->message);
5558 g_error_free (g_err);
5559 return (1);
5562 return (0);
5566 go_home(struct tab *t, struct karg *args)
5568 load_uri(t, home);
5569 return (0);
5573 set_encoding(struct tab *t, struct karg *args)
5575 const gchar *e;
5577 if (args->s && strlen(g_strstrip(args->s)) == 0) {
5578 e = webkit_web_view_get_custom_encoding(t->wv);
5579 if (e == NULL)
5580 e = webkit_web_view_get_encoding(t->wv);
5581 show_oops(t, "encoding: %s", e ? e : "N/A");
5582 } else
5583 webkit_web_view_set_custom_encoding(t->wv, args->s);
5585 return (0);
5589 restart(struct tab *t, struct karg *args)
5591 struct karg a;
5593 a.s = XT_RESTART_TABS_FILE;
5594 save_tabs(t, &a);
5595 execvp(start_argv[0], start_argv);
5596 /* NOTREACHED */
5598 return (0);
5601 #define CTRL GDK_CONTROL_MASK
5602 #define MOD1 GDK_MOD1_MASK
5603 #define SHFT GDK_SHIFT_MASK
5605 /* inherent to GTK not all keys will be caught at all times */
5606 /* XXX sort key bindings */
5607 struct key_binding {
5608 char *cmd;
5609 guint mask;
5610 guint use_in_entry;
5611 guint key;
5612 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
5613 } keys[] = {
5614 { "command_mode", 0, 0, GDK_Escape },
5615 { "insert_mode", 0, 0, GDK_i },
5616 { "cookiejar", MOD1, 0, GDK_j },
5617 { "downloadmgr", MOD1, 0, GDK_d },
5618 { "history", MOD1, 0, GDK_h },
5619 { "print", CTRL, 0, GDK_p },
5620 { "search", 0, 0, GDK_slash },
5621 { "searchb", 0, 0, GDK_question },
5622 { "statustoggle", CTRL, 0, GDK_n },
5623 { "command", 0, 0, GDK_colon },
5624 { "qa", CTRL, 0, GDK_q },
5625 { "restart", MOD1, 0, GDK_q },
5626 { "js toggle", CTRL, 0, GDK_j },
5627 { "cookie toggle", MOD1, 0, GDK_c },
5628 { "togglesrc", CTRL, 0, GDK_s },
5629 { "yankuri", 0, 0, GDK_y },
5630 { "pasteuricur", 0, 0, GDK_p },
5631 { "pasteurinew", 0, 0, GDK_P },
5632 { "toplevel toggle", 0, 0, GDK_F4 },
5633 { "help", 0, 0, GDK_F1 },
5634 { "run_script", MOD1, 0, GDK_r },
5636 /* search */
5637 { "searchnext", 0, 0, GDK_n },
5638 { "searchprevious", 0, 0, GDK_N },
5640 /* focus */
5641 { "focusaddress", 0, 0, GDK_F6 },
5642 { "focussearch", 0, 0, GDK_F7 },
5644 /* hinting */
5645 { "hinting", 0, 0, GDK_f },
5646 { "hinting", 0, 0, GDK_period },
5647 { "hinting_newtab", SHFT, 0, GDK_F },
5648 { "hinting_newtab", 0, 0, GDK_comma },
5650 /* custom stylesheet */
5651 { "userstyle", 0, 0, GDK_s },
5653 /* navigation */
5654 { "goback", 0, 0, GDK_BackSpace },
5655 { "goback", MOD1, 0, GDK_Left },
5656 { "goforward", SHFT, 0, GDK_BackSpace },
5657 { "goforward", MOD1, 0, GDK_Right },
5658 { "reload", 0, 0, GDK_F5 },
5659 { "reload", CTRL, 0, GDK_r },
5660 { "reload", CTRL, 0, GDK_l },
5661 { "favorites", MOD1, 1, GDK_f },
5663 /* vertical movement */
5664 { "scrolldown", 0, 0, GDK_j },
5665 { "scrolldown", 0, 0, GDK_Down },
5666 { "scrollup", 0, 0, GDK_Up },
5667 { "scrollup", 0, 0, GDK_k },
5668 { "scrollbottom", 0, 0, GDK_G },
5669 { "scrollbottom", 0, 0, GDK_End },
5670 { "scrolltop", 0, 0, GDK_Home },
5671 { "scrollpagedown", 0, 0, GDK_space },
5672 { "scrollpagedown", CTRL, 0, GDK_f },
5673 { "scrollhalfdown", CTRL, 0, GDK_d },
5674 { "scrollpagedown", 0, 0, GDK_Page_Down },
5675 { "scrollpageup", 0, 0, GDK_Page_Up },
5676 { "scrollpageup", CTRL, 0, GDK_b },
5677 { "scrollhalfup", CTRL, 0, GDK_u },
5678 /* horizontal movement */
5679 { "scrollright", 0, 0, GDK_l },
5680 { "scrollright", 0, 0, GDK_Right },
5681 { "scrollleft", 0, 0, GDK_Left },
5682 { "scrollleft", 0, 0, GDK_h },
5683 { "scrollfarright", 0, 0, GDK_dollar },
5684 { "scrollfarleft", 0, 0, GDK_0 },
5686 /* tabs */
5687 { "tabnew", CTRL, 0, GDK_t },
5688 { "999tabnew", CTRL, 0, GDK_T },
5689 { "tabclose", CTRL, 1, GDK_w },
5690 { "tabundoclose", 0, 0, GDK_U },
5691 { "tabnext 1", CTRL, 0, GDK_1 },
5692 { "tabnext 2", CTRL, 0, GDK_2 },
5693 { "tabnext 3", CTRL, 0, GDK_3 },
5694 { "tabnext 4", CTRL, 0, GDK_4 },
5695 { "tabnext 5", CTRL, 0, GDK_5 },
5696 { "tabnext 6", CTRL, 0, GDK_6 },
5697 { "tabnext 7", CTRL, 0, GDK_7 },
5698 { "tabnext 8", CTRL, 0, GDK_8 },
5699 { "tabnext 9", CTRL, 0, GDK_9 },
5700 { "tabfirst", CTRL, 0, GDK_less },
5701 { "tablast", CTRL, 0, GDK_greater },
5702 { "tabprevious", CTRL, 0, GDK_Left },
5703 { "tabnext", CTRL, 0, GDK_Right },
5704 { "focusout", CTRL, 0, GDK_minus },
5705 { "focusin", CTRL, 0, GDK_plus },
5706 { "focusin", CTRL, 0, GDK_equal },
5707 { "focusreset", CTRL, 0, GDK_0 },
5709 /* command aliases (handy when -S flag is used) */
5710 { "promptopen", 0, 0, GDK_F9 },
5711 { "promptopencurrent", 0, 0, GDK_F10 },
5712 { "prompttabnew", 0, 0, GDK_F11 },
5713 { "prompttabnewcurrent",0, 0, GDK_F12 },
5715 TAILQ_HEAD(keybinding_list, key_binding);
5717 void
5718 walk_kb(struct settings *s,
5719 void (*cb)(struct settings *, char *, void *), void *cb_args)
5721 struct key_binding *k;
5722 char str[1024];
5724 if (s == NULL || cb == NULL) {
5725 show_oops(NULL, "walk_kb invalid parameters");
5726 return;
5729 TAILQ_FOREACH(k, &kbl, entry) {
5730 if (k->cmd == NULL)
5731 continue;
5732 str[0] = '\0';
5734 /* sanity */
5735 if (gdk_keyval_name(k->key) == NULL)
5736 continue;
5738 strlcat(str, k->cmd, sizeof str);
5739 strlcat(str, ",", sizeof str);
5741 if (k->mask & GDK_SHIFT_MASK)
5742 strlcat(str, "S-", sizeof str);
5743 if (k->mask & GDK_CONTROL_MASK)
5744 strlcat(str, "C-", sizeof str);
5745 if (k->mask & GDK_MOD1_MASK)
5746 strlcat(str, "M1-", sizeof str);
5747 if (k->mask & GDK_MOD2_MASK)
5748 strlcat(str, "M2-", sizeof str);
5749 if (k->mask & GDK_MOD3_MASK)
5750 strlcat(str, "M3-", sizeof str);
5751 if (k->mask & GDK_MOD4_MASK)
5752 strlcat(str, "M4-", sizeof str);
5753 if (k->mask & GDK_MOD5_MASK)
5754 strlcat(str, "M5-", sizeof str);
5756 strlcat(str, gdk_keyval_name(k->key), sizeof str);
5757 cb(s, str, cb_args);
5761 void
5762 init_keybindings(void)
5764 int i;
5765 struct key_binding *k;
5767 for (i = 0; i < LENGTH(keys); i++) {
5768 k = g_malloc0(sizeof *k);
5769 k->cmd = keys[i].cmd;
5770 k->mask = keys[i].mask;
5771 k->use_in_entry = keys[i].use_in_entry;
5772 k->key = keys[i].key;
5773 TAILQ_INSERT_HEAD(&kbl, k, entry);
5775 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
5776 k->cmd ? k->cmd : "unnamed key");
5780 void
5781 keybinding_clearall(void)
5783 struct key_binding *k, *next;
5785 for (k = TAILQ_FIRST(&kbl); k; k = next) {
5786 next = TAILQ_NEXT(k, entry);
5787 if (k->cmd == NULL)
5788 continue;
5790 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
5791 k->cmd ? k->cmd : "unnamed key");
5792 TAILQ_REMOVE(&kbl, k, entry);
5793 g_free(k);
5798 keybinding_add(char *cmd, char *key, int use_in_entry)
5800 struct key_binding *k;
5801 guint keyval, mask = 0;
5802 int i;
5804 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
5806 /* Keys which are to be used in entry have been prefixed with an
5807 * exclamation mark. */
5808 if (use_in_entry)
5809 key++;
5811 /* find modifier keys */
5812 if (strstr(key, "S-"))
5813 mask |= GDK_SHIFT_MASK;
5814 if (strstr(key, "C-"))
5815 mask |= GDK_CONTROL_MASK;
5816 if (strstr(key, "M1-"))
5817 mask |= GDK_MOD1_MASK;
5818 if (strstr(key, "M2-"))
5819 mask |= GDK_MOD2_MASK;
5820 if (strstr(key, "M3-"))
5821 mask |= GDK_MOD3_MASK;
5822 if (strstr(key, "M4-"))
5823 mask |= GDK_MOD4_MASK;
5824 if (strstr(key, "M5-"))
5825 mask |= GDK_MOD5_MASK;
5827 /* find keyname */
5828 for (i = strlen(key) - 1; i > 0; i--)
5829 if (key[i] == '-')
5830 key = &key[i + 1];
5832 /* validate keyname */
5833 keyval = gdk_keyval_from_name(key);
5834 if (keyval == GDK_VoidSymbol) {
5835 warnx("invalid keybinding name %s", key);
5836 return (1);
5838 /* must run this test too, gtk+ doesn't handle 10 for example */
5839 if (gdk_keyval_name(keyval) == NULL) {
5840 warnx("invalid keybinding name %s", key);
5841 return (1);
5844 /* Remove eventual dupes. */
5845 TAILQ_FOREACH(k, &kbl, entry)
5846 if (k->key == keyval && k->mask == mask) {
5847 TAILQ_REMOVE(&kbl, k, entry);
5848 g_free(k);
5849 break;
5852 /* add keyname */
5853 k = g_malloc0(sizeof *k);
5854 k->cmd = g_strdup(cmd);
5855 k->mask = mask;
5856 k->use_in_entry = use_in_entry;
5857 k->key = keyval;
5859 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
5860 k->cmd,
5861 k->mask,
5862 k->use_in_entry,
5863 k->key);
5864 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
5865 k->cmd, gdk_keyval_name(keyval));
5867 TAILQ_INSERT_HEAD(&kbl, k, entry);
5869 return (0);
5873 add_kb(struct settings *s, char *entry)
5875 char *kb, *key;
5877 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
5879 /* clearall is special */
5880 if (!strcmp(entry, "clearall")) {
5881 keybinding_clearall();
5882 return (0);
5885 kb = strstr(entry, ",");
5886 if (kb == NULL)
5887 return (1);
5888 *kb = '\0';
5889 key = kb + 1;
5891 return (keybinding_add(entry, key, key[0] == '!'));
5894 struct cmd {
5895 char *cmd;
5896 int level;
5897 int (*func)(struct tab *, struct karg *);
5898 int arg;
5899 int type;
5900 } cmds[] = {
5901 { "command_mode", 0, command_mode, XT_MODE_COMMAND, 0 },
5902 { "insert_mode", 0, command_mode, XT_MODE_INSERT, 0 },
5903 { "command", 0, command, ':', 0 },
5904 { "search", 0, command, '/', 0 },
5905 { "searchb", 0, command, '?', 0 },
5906 { "hinting", 0, command, '.', 0 },
5907 { "hinting_newtab", 0, command, ',', 0 },
5908 { "togglesrc", 0, toggle_src, 0, 0 },
5910 /* yanking and pasting */
5911 { "yankuri", 0, yank_uri, 0, 0 },
5912 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
5913 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
5914 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
5916 /* search */
5917 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
5918 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
5920 /* focus */
5921 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
5922 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
5924 /* hinting */
5925 { "hinting", 0, hint, 0, 0 },
5926 { "hinting_newtab", 0, hint, XT_HINT_NEWTAB, 0 },
5928 /* custom stylesheet */
5929 { "userstyle", 0, userstyle, 0, 0 },
5931 /* navigation */
5932 { "goback", 0, navaction, XT_NAV_BACK, 0 },
5933 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
5934 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
5936 /* vertical movement */
5937 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
5938 { "scrollup", 0, move, XT_MOVE_UP, 0 },
5939 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
5940 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
5941 { "1", 0, move, XT_MOVE_TOP, 0 },
5942 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
5943 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
5944 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
5945 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
5946 /* horizontal movement */
5947 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
5948 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
5949 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
5950 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
5952 { "favorites", 0, xtp_page_fl, 0, 0 },
5953 { "fav", 0, xtp_page_fl, 0, 0 },
5954 { "favadd", 0, add_favorite, 0, 0 },
5956 { "qall", 0, quit, 0, 0 },
5957 { "quitall", 0, quit, 0, 0 },
5958 { "w", 0, save_tabs, 0, 0 },
5959 { "wq", 0, save_tabs_and_quit, 0, 0 },
5960 { "help", 0, help, 0, 0 },
5961 { "about", 0, about, 0, 0 },
5962 { "stats", 0, stats, 0, 0 },
5963 { "version", 0, about, 0, 0 },
5965 /* js command */
5966 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5967 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5968 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5969 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5970 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5971 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5972 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5973 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5974 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5975 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5976 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5978 /* cookie command */
5979 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5980 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5981 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5982 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5983 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5984 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5985 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5986 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5987 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5988 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5989 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5991 /* plugin command */
5992 { "plugin", 0, pl_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5993 { "save", 1, pl_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5994 { "domain", 2, pl_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5995 { "fqdn", 2, pl_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5996 { "show", 1, pl_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5997 { "all", 2, pl_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5998 { "persistent", 2, pl_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5999 { "session", 2, pl_cmd, XT_SHOW | XT_WL_SESSION, 0 },
6000 { "toggle", 1, pl_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
6001 { "domain", 2, pl_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
6002 { "fqdn", 2, pl_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
6004 /* toplevel (domain) command */
6005 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
6006 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
6008 /* cookie jar */
6009 { "cookiejar", 0, xtp_page_cl, 0, 0 },
6011 /* cert command */
6012 { "cert", 0, cert_cmd, XT_SHOW, 0 },
6013 { "save", 1, cert_cmd, XT_SAVE, 0 },
6014 { "show", 1, cert_cmd, XT_SHOW, 0 },
6016 { "ca", 0, ca_cmd, 0, 0 },
6017 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
6018 { "dl", 0, xtp_page_dl, 0, 0 },
6019 { "h", 0, xtp_page_hl, 0, 0 },
6020 { "history", 0, xtp_page_hl, 0, 0 },
6021 { "home", 0, go_home, 0, 0 },
6022 { "restart", 0, restart, 0, 0 },
6023 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
6024 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
6025 { "statustoggle", 0, statustoggle, 0, 0 },
6026 { "run_script", 0, run_page_script, 0, XT_USERARG },
6028 { "print", 0, print_page, 0, 0 },
6030 /* tabs */
6031 { "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
6032 { "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
6033 { "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
6034 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
6035 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
6036 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
6037 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
6038 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
6039 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
6040 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
6041 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
6042 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
6043 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
6044 { "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
6045 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
6046 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
6047 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
6048 { "tabs", 0, buffers, 0, 0 },
6049 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
6050 { "buffers", 0, buffers, 0, 0 },
6051 { "ls", 0, buffers, 0, 0 },
6052 { "encoding", 0, set_encoding, 0, XT_USERARG },
6054 /* command aliases (handy when -S flag is used) */
6055 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
6056 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
6057 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
6058 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
6060 /* settings */
6061 { "set", 0, set, 0, XT_SETARG },
6063 { "fullscreen", 0, fullscreen, 0, 0 },
6064 { "f", 0, fullscreen, 0, 0 },
6066 /* sessions */
6067 { "session", 0, session_cmd, XT_SHOW, 0 },
6068 { "delete", 1, session_cmd, XT_DELETE, XT_SESSARG },
6069 { "open", 1, session_cmd, XT_OPEN, XT_SESSARG },
6070 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
6071 { "show", 1, session_cmd, XT_SHOW, 0 },
6073 /* external javascript */
6074 { "script", 0, script_cmd, XT_EJS_SHOW, XT_USERARG },
6076 /* inspector */
6077 { "inspector", 0, inspector_cmd, XT_INS_SHOW, 0 },
6078 { "show", 1, inspector_cmd, XT_INS_SHOW, 0 },
6079 { "hide", 1, inspector_cmd, XT_INS_HIDE, 0 },
6082 struct {
6083 int index;
6084 int len;
6085 gchar *list[256];
6086 } cmd_status = {-1, 0};
6088 gboolean
6089 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
6092 if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
6093 btn_down = 0;
6095 return (FALSE);
6098 gboolean
6099 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
6101 struct karg a;
6103 hide_oops(t);
6104 hide_buffers(t);
6106 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
6107 btn_down = 1;
6108 else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
6109 /* go backward */
6110 a.i = XT_NAV_BACK;
6111 navaction(t, &a);
6113 return (TRUE);
6114 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
6115 /* go forward */
6116 a.i = XT_NAV_FORWARD;
6117 navaction(t, &a);
6119 return (TRUE);
6122 return (FALSE);
6125 gboolean
6126 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
6128 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
6130 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
6131 delete_tab(t);
6133 return (FALSE);
6137 * cancel, remove, etc. downloads
6139 void
6140 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
6142 struct download find, *d = NULL;
6144 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
6146 /* some commands require a valid download id */
6147 if (cmd != XT_XTP_DL_LIST) {
6148 /* lookup download in question */
6149 find.id = id;
6150 d = RB_FIND(download_list, &downloads, &find);
6152 if (d == NULL) {
6153 show_oops(t, "%s: no such download", __func__);
6154 return;
6158 /* decide what to do */
6159 switch (cmd) {
6160 case XT_XTP_DL_CANCEL:
6161 webkit_download_cancel(d->download);
6162 break;
6163 case XT_XTP_DL_REMOVE:
6164 webkit_download_cancel(d->download); /* just incase */
6165 g_object_unref(d->download);
6166 RB_REMOVE(download_list, &downloads, d);
6167 break;
6168 case XT_XTP_DL_LIST:
6169 /* Nothing */
6170 break;
6171 default:
6172 show_oops(t, "%s: unknown command", __func__);
6173 break;
6175 xtp_page_dl(t, NULL);
6179 * Actions on history, only does one thing for now, but
6180 * we provide the function for future actions
6182 void
6183 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
6185 struct history *h, *next;
6186 int i = 1;
6188 switch (cmd) {
6189 case XT_XTP_HL_REMOVE:
6190 /* walk backwards, as listed in reverse */
6191 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
6192 next = RB_PREV(history_list, &hl, h);
6193 if (id == i) {
6194 RB_REMOVE(history_list, &hl, h);
6195 g_free((gpointer) h->title);
6196 g_free((gpointer) h->uri);
6197 g_free(h);
6198 break;
6200 i++;
6202 break;
6203 case XT_XTP_HL_LIST:
6204 /* Nothing - just xtp_page_hl() below */
6205 break;
6206 default:
6207 show_oops(t, "%s: unknown command", __func__);
6208 break;
6211 xtp_page_hl(t, NULL);
6214 /* remove a favorite */
6215 void
6216 remove_favorite(struct tab *t, int index)
6218 char file[PATH_MAX], *title, *uri = NULL;
6219 char *new_favs, *tmp;
6220 FILE *f;
6221 int i;
6222 size_t len, lineno;
6224 /* open favorites */
6225 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
6227 if ((f = fopen(file, "r")) == NULL) {
6228 show_oops(t, "%s: can't open favorites: %s",
6229 __func__, strerror(errno));
6230 return;
6233 /* build a string which will become the new favroites file */
6234 new_favs = g_strdup("");
6236 for (i = 1;;) {
6237 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
6238 if (feof(f) || ferror(f))
6239 break;
6240 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
6241 if (len == 0) {
6242 free(title);
6243 title = NULL;
6244 continue;
6247 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
6248 if (feof(f) || ferror(f)) {
6249 show_oops(t, "%s: can't parse favorites %s",
6250 __func__, strerror(errno));
6251 goto clean;
6255 /* as long as this isn't the one we are deleting add to file */
6256 if (i != index) {
6257 tmp = new_favs;
6258 new_favs = g_strdup_printf("%s%s\n%s\n",
6259 new_favs, title, uri);
6260 g_free(tmp);
6263 free(uri);
6264 uri = NULL;
6265 free(title);
6266 title = NULL;
6267 i++;
6269 fclose(f);
6271 /* write back new favorites file */
6272 if ((f = fopen(file, "w")) == NULL) {
6273 show_oops(t, "%s: can't open favorites: %s",
6274 __func__, strerror(errno));
6275 goto clean;
6278 if (fwrite(new_favs, strlen(new_favs), 1, f) != 1)
6279 show_oops(t, "%s: can't fwrite"); /* shut gcc up */
6280 fclose(f);
6282 clean:
6283 if (uri)
6284 free(uri);
6285 if (title)
6286 free(title);
6288 g_free(new_favs);
6291 void
6292 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
6294 switch (cmd) {
6295 case XT_XTP_FL_LIST:
6296 /* nothing, just the below call to xtp_page_fl() */
6297 break;
6298 case XT_XTP_FL_REMOVE:
6299 remove_favorite(t, arg);
6300 break;
6301 default:
6302 show_oops(t, "%s: invalid favorites command", __func__);
6303 break;
6306 xtp_page_fl(t, NULL);
6309 void
6310 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
6312 switch (cmd) {
6313 case XT_XTP_CL_LIST:
6314 /* nothing, just xtp_page_cl() */
6315 break;
6316 case XT_XTP_CL_REMOVE:
6317 remove_cookie(arg);
6318 break;
6319 default:
6320 show_oops(t, "%s: unknown cookie xtp command", __func__);
6321 break;
6324 xtp_page_cl(t, NULL);
6327 /* link an XTP class to it's session key and handler function */
6328 struct xtp_despatch {
6329 uint8_t xtp_class;
6330 char **session_key;
6331 void (*handle_func)(struct tab *, uint8_t, int);
6334 struct xtp_despatch xtp_despatches[] = {
6335 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
6336 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
6337 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
6338 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
6339 { XT_XTP_INVALID, NULL, NULL }
6343 * is the url xtp protocol? (xxxt://)
6344 * if so, parse and despatch correct bahvior
6347 parse_xtp_url(struct tab *t, const char *url)
6349 char *dup = NULL, *p, *last = NULL;
6350 uint8_t n_tokens = 0;
6351 char *tokens[4] = {NULL, NULL, NULL, ""};
6352 struct xtp_despatch *dsp, *dsp_match = NULL;
6353 uint8_t req_class;
6354 int ret = FALSE;
6357 * tokens array meaning:
6358 * tokens[0] = class
6359 * tokens[1] = session key
6360 * tokens[2] = action
6361 * tokens[3] = optional argument
6364 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
6366 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
6367 goto clean;
6369 dup = g_strdup(url + strlen(XT_XTP_STR));
6371 /* split out the url */
6372 for ((p = strtok_r(dup, "/", &last)); p;
6373 (p = strtok_r(NULL, "/", &last))) {
6374 if (n_tokens < 4)
6375 tokens[n_tokens++] = p;
6378 /* should be atleast three fields 'class/seskey/command/arg' */
6379 if (n_tokens < 3)
6380 goto clean;
6382 dsp = xtp_despatches;
6383 req_class = atoi(tokens[0]);
6384 while (dsp->xtp_class) {
6385 if (dsp->xtp_class == req_class) {
6386 dsp_match = dsp;
6387 break;
6389 dsp++;
6392 /* did we find one atall? */
6393 if (dsp_match == NULL) {
6394 show_oops(t, "%s: no matching xtp despatch found", __func__);
6395 goto clean;
6398 /* check session key and call despatch function */
6399 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
6400 ret = TRUE; /* all is well, this was a valid xtp request */
6401 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
6404 clean:
6405 if (dup)
6406 g_free(dup);
6408 return (ret);
6413 void
6414 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
6416 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
6418 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
6420 if (t == NULL) {
6421 show_oops(NULL, "activate_uri_entry_cb invalid parameters");
6422 return;
6425 if (uri == NULL) {
6426 show_oops(t, "activate_uri_entry_cb no uri");
6427 return;
6430 uri += strspn(uri, "\t ");
6432 /* if xxxt:// treat specially */
6433 if (parse_xtp_url(t, uri))
6434 return;
6436 /* otherwise continue to load page normally */
6437 load_uri(t, (gchar *)uri);
6438 focus_webview(t);
6441 void
6442 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
6444 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
6445 char *newuri = NULL;
6446 gchar *enc_search;
6448 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
6450 if (t == NULL) {
6451 show_oops(NULL, "activate_search_entry_cb invalid parameters");
6452 return;
6455 if (search_string == NULL) {
6456 show_oops(t, "no search_string");
6457 return;
6460 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6462 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
6463 newuri = g_strdup_printf(search_string, enc_search);
6464 g_free(enc_search);
6466 marks_clear(t);
6467 webkit_web_view_load_uri(t->wv, newuri);
6468 focus_webview(t);
6470 if (newuri)
6471 g_free(newuri);
6474 void
6475 check_and_set_cookie(const gchar *uri, struct tab *t)
6477 struct domain *d = NULL;
6478 int es = 0;
6480 if (uri == NULL || t == NULL)
6481 return;
6483 if ((d = wl_find_uri(uri, &c_wl)) == NULL)
6484 es = 0;
6485 else
6486 es = 1;
6488 DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
6489 es ? "enable" : "disable", uri);
6491 g_object_set(G_OBJECT(t->settings),
6492 "enable-html5-local-storage", es, (char *)NULL);
6493 webkit_web_view_set_settings(t->wv, t->settings);
6496 void
6497 check_and_set_js(const gchar *uri, struct tab *t)
6499 struct domain *d = NULL;
6500 int es = 0;
6502 if (uri == NULL || t == NULL)
6503 return;
6505 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
6506 es = 0;
6507 else
6508 es = 1;
6510 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
6511 es ? "enable" : "disable", uri);
6513 g_object_set(G_OBJECT(t->settings),
6514 "enable-scripts", es, (char *)NULL);
6515 g_object_set(G_OBJECT(t->settings),
6516 "javascript-can-open-windows-automatically", es, (char *)NULL);
6517 webkit_web_view_set_settings(t->wv, t->settings);
6519 button_set_stockid(t->js_toggle,
6520 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
6523 void
6524 check_and_set_pl(const gchar *uri, struct tab *t)
6526 struct domain *d = NULL;
6527 int es = 0;
6529 if (uri == NULL || t == NULL)
6530 return;
6532 if ((d = wl_find_uri(uri, &pl_wl)) == NULL)
6533 es = 0;
6534 else
6535 es = 1;
6537 DNPRINTF(XT_D_JS, "check_and_set_pl: %s %s\n",
6538 es ? "enable" : "disable", uri);
6540 g_object_set(G_OBJECT(t->settings),
6541 "enable-plugins", es, (char *)NULL);
6542 webkit_web_view_set_settings(t->wv, t->settings);
6545 void
6546 color_address_bar(gpointer p)
6548 GdkColor color;
6549 struct tab *tt, *t = p;
6550 gchar *col_str = XT_COLOR_WHITE;
6551 const gchar *uri, *u = NULL, *error_str = NULL;
6553 #ifdef USE_THREADS
6554 gdk_threads_enter();
6555 #endif
6556 DNPRINTF(XT_D_URL, "%s:\n", __func__);
6558 /* make sure t still exists */
6559 if (t == NULL)
6560 return;
6561 TAILQ_FOREACH(tt, &tabs, entry)
6562 if (t == tt)
6563 break;
6564 if (t != tt)
6565 goto done;
6567 if ((uri = get_uri(t)) == NULL)
6568 goto white;
6569 u = g_strdup(uri);
6571 #ifdef USE_THREADS
6572 gdk_threads_leave();
6573 #endif
6575 col_str = XT_COLOR_YELLOW;
6576 switch (load_compare_cert(u, &error_str)) {
6577 case CERT_LOCAL:
6578 col_str = XT_COLOR_BLUE;
6579 break;
6580 case CERT_TRUSTED:
6581 col_str = XT_COLOR_GREEN;
6582 break;
6583 case CERT_UNTRUSTED:
6584 col_str = XT_COLOR_YELLOW;
6585 break;
6586 case CERT_BAD:
6587 col_str = XT_COLOR_RED;
6588 break;
6591 #ifdef USE_THREADS
6592 gdk_threads_enter();
6593 #endif
6594 /* make sure t isn't deleted */
6595 TAILQ_FOREACH(tt, &tabs, entry)
6596 if (t == tt)
6597 break;
6598 if (t != tt)
6599 goto done;
6601 #ifdef USE_THREADS
6602 /* test to see if the user navigated away and canceled the thread */
6603 if (t->thread != g_thread_self())
6604 goto done;
6605 if ((uri = get_uri(t)) == NULL) {
6606 t->thread = NULL;
6607 goto done;
6609 if (strcmp(uri, u)) {
6610 /* make sure we are still the same url */
6611 t->thread = NULL;
6612 goto done;
6614 #endif
6615 white:
6616 gdk_color_parse(col_str, &color);
6617 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6619 if (!strcmp(col_str, XT_COLOR_WHITE))
6620 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6621 else
6622 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6624 if (error_str && error_str[0] != '\0')
6625 show_oops(t, "%s", error_str);
6626 #ifdef USE_THREADS
6627 t->thread = NULL;
6628 #endif
6629 done:
6630 /* t is invalid at this point */
6631 if (u)
6632 g_free((gpointer)u);
6633 #ifdef USE_THREADS
6634 gdk_threads_leave();
6635 #endif
6638 void
6639 show_ca_status(struct tab *t, const char *uri)
6641 GdkColor color;
6642 gchar *col_str = XT_COLOR_WHITE;
6644 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
6645 ssl_strict_certs, ssl_ca_file, uri);
6647 if (t == NULL)
6648 return;
6650 if (uri == NULL)
6651 goto done;
6652 if (ssl_ca_file == NULL) {
6653 if (g_str_has_prefix(uri, "http://"))
6654 goto done;
6655 if (g_str_has_prefix(uri, "https://")) {
6656 col_str = XT_COLOR_RED;
6657 goto done;
6659 return;
6661 if (g_str_has_prefix(uri, "http://") ||
6662 !g_str_has_prefix(uri, "https://"))
6663 goto done;
6664 #ifdef USE_THREADS
6666 * It is not necessary to see if the thread is already running.
6667 * If the thread is in progress setting it to something else aborts it
6668 * on the way out.
6671 /* thread the coloring of the address bar */
6672 t->thread = g_thread_create((GThreadFunc)color_address_bar, t, TRUE, NULL);
6673 #else
6674 color_address_bar(t);
6675 #endif
6676 return;
6678 done:
6679 if (col_str) {
6680 gdk_color_parse(col_str, &color);
6681 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6683 if (!strcmp(col_str, XT_COLOR_WHITE))
6684 statusbar_modify_attr(t, col_str, XT_COLOR_BLACK);
6685 else
6686 statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
6690 void
6691 free_favicon(struct tab *t)
6693 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
6694 __func__, t->icon_download, t->icon_request);
6696 if (t->icon_request)
6697 g_object_unref(t->icon_request);
6698 if (t->icon_dest_uri)
6699 g_free(t->icon_dest_uri);
6701 t->icon_request = NULL;
6702 t->icon_dest_uri = NULL;
6705 void
6706 xt_icon_from_name(struct tab *t, gchar *name)
6708 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6709 GTK_ENTRY_ICON_PRIMARY, "text-html");
6710 if (show_url == 0)
6711 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6712 GTK_ENTRY_ICON_PRIMARY, "text-html");
6713 else
6714 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6715 GTK_ENTRY_ICON_PRIMARY, NULL);
6718 void
6719 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
6721 GdkPixbuf *pb_scaled;
6723 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
6724 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
6725 GDK_INTERP_BILINEAR);
6726 else
6727 pb_scaled = pb;
6729 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
6730 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6731 if (show_url == 0)
6732 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.statusbar),
6733 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
6734 else
6735 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.statusbar),
6736 GTK_ENTRY_ICON_PRIMARY, NULL);
6738 if (pb_scaled != pb)
6739 g_object_unref(pb_scaled);
6742 void
6743 xt_icon_from_file(struct tab *t, char *file)
6745 GdkPixbuf *pb;
6747 if (g_str_has_prefix(file, "file://"))
6748 file += strlen("file://");
6750 pb = gdk_pixbuf_new_from_file(file, NULL);
6751 if (pb) {
6752 xt_icon_from_pixbuf(t, pb);
6753 g_object_unref(pb);
6754 } else
6755 xt_icon_from_name(t, "text-html");
6758 gboolean
6759 is_valid_icon(char *file)
6761 gboolean valid = 0;
6762 const char *mime_type;
6763 GFileInfo *fi;
6764 GFile *gf;
6766 gf = g_file_new_for_path(file);
6767 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6768 NULL, NULL);
6769 mime_type = g_file_info_get_content_type(fi);
6770 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
6771 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
6772 g_strcmp0(mime_type, "image/png") == 0 ||
6773 g_strcmp0(mime_type, "image/gif") == 0 ||
6774 g_strcmp0(mime_type, "application/octet-stream") == 0;
6775 g_object_unref(fi);
6776 g_object_unref(gf);
6778 return (valid);
6781 void
6782 set_favicon_from_file(struct tab *t, char *file)
6784 struct stat sb;
6786 if (t == NULL || file == NULL)
6787 return;
6789 if (g_str_has_prefix(file, "file://"))
6790 file += strlen("file://");
6791 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
6793 if (!stat(file, &sb)) {
6794 if (sb.st_size == 0 || !is_valid_icon(file)) {
6795 /* corrupt icon so trash it */
6796 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6797 __func__, file);
6798 unlink(file);
6799 /* no need to set icon to default here */
6800 return;
6803 xt_icon_from_file(t, file);
6806 void
6807 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6808 WebKitWebView *wv)
6810 WebKitDownloadStatus status = webkit_download_get_status(download);
6811 struct tab *tt = NULL, *t = NULL;
6814 * find the webview instead of passing in the tab as it could have been
6815 * deleted from underneath us.
6817 TAILQ_FOREACH(tt, &tabs, entry) {
6818 if (tt->wv == wv) {
6819 t = tt;
6820 break;
6823 if (t == NULL)
6824 return;
6826 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
6827 __func__, t->tab_id, status);
6829 switch (status) {
6830 case WEBKIT_DOWNLOAD_STATUS_ERROR:
6831 /* -1 */
6832 t->icon_download = NULL;
6833 free_favicon(t);
6834 break;
6835 case WEBKIT_DOWNLOAD_STATUS_CREATED:
6836 /* 0 */
6837 break;
6838 case WEBKIT_DOWNLOAD_STATUS_STARTED:
6839 /* 1 */
6840 break;
6841 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
6842 /* 2 */
6843 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
6844 __func__, t->tab_id);
6845 t->icon_download = NULL;
6846 free_favicon(t);
6847 break;
6848 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
6849 /* 3 */
6851 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
6852 __func__, t->icon_dest_uri);
6853 set_favicon_from_file(t, t->icon_dest_uri);
6854 /* these will be freed post callback */
6855 t->icon_request = NULL;
6856 t->icon_download = NULL;
6857 break;
6858 default:
6859 break;
6863 void
6864 abort_favicon_download(struct tab *t)
6866 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
6868 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
6869 if (t->icon_download) {
6870 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
6871 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6872 webkit_download_cancel(t->icon_download);
6873 t->icon_download = NULL;
6874 } else
6875 free_favicon(t);
6876 #endif
6878 xt_icon_from_name(t, "text-html");
6881 void
6882 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
6884 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
6886 if (uri == NULL || t == NULL)
6887 return;
6889 #if WEBKIT_CHECK_VERSION(1, 4, 0)
6890 /* take icon from WebKitIconDatabase */
6891 GdkPixbuf *pb;
6893 pb = webkit_web_view_get_icon_pixbuf(wv);
6894 if (pb) {
6895 xt_icon_from_pixbuf(t, pb);
6896 g_object_unref(pb);
6897 } else
6898 xt_icon_from_name(t, "text-html");
6899 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
6900 /* download icon to cache dir */
6901 gchar *name_hash, file[PATH_MAX];
6902 struct stat sb;
6904 if (t->icon_request) {
6905 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
6906 return;
6909 /* check to see if we got the icon in cache */
6910 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
6911 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
6912 g_free(name_hash);
6914 if (!stat(file, &sb)) {
6915 if (sb.st_size > 0) {
6916 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
6917 __func__, file);
6918 set_favicon_from_file(t, file);
6919 return;
6922 /* corrupt icon so trash it */
6923 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
6924 __func__, file);
6925 unlink(file);
6928 /* create download for icon */
6929 t->icon_request = webkit_network_request_new(uri);
6930 if (t->icon_request == NULL) {
6931 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
6932 __func__, uri);
6933 return;
6936 t->icon_download = webkit_download_new(t->icon_request);
6937 if (t->icon_download == NULL)
6938 return;
6940 /* we have to free icon_dest_uri later */
6941 t->icon_dest_uri = g_strdup_printf("file://%s", file);
6942 webkit_download_set_destination_uri(t->icon_download,
6943 t->icon_dest_uri);
6945 if (webkit_download_get_status(t->icon_download) ==
6946 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6947 g_object_unref(t->icon_request);
6948 g_free(t->icon_dest_uri);
6949 t->icon_request = NULL;
6950 t->icon_dest_uri = NULL;
6951 return;
6954 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
6955 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
6957 webkit_download_start(t->icon_download);
6958 #endif
6961 void
6962 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
6964 const gchar *uri = NULL, *title = NULL;
6965 struct history *h, find;
6966 struct karg a;
6967 GdkColor color;
6969 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
6970 webkit_web_view_get_load_status(wview),
6971 get_uri(t) ? get_uri(t) : "NOTHING");
6973 if (t == NULL) {
6974 show_oops(NULL, "notify_load_status_cb invalid parameters");
6975 return;
6978 switch (webkit_web_view_get_load_status(wview)) {
6979 case WEBKIT_LOAD_PROVISIONAL:
6980 /* 0 */
6981 abort_favicon_download(t);
6982 #if GTK_CHECK_VERSION(2, 20, 0)
6983 gtk_widget_show(t->spinner);
6984 gtk_spinner_start(GTK_SPINNER(t->spinner));
6985 #endif
6986 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
6988 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
6990 /* assume we are a new address */
6991 gdk_color_parse("white", &color);
6992 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
6993 statusbar_modify_attr(t, "white", XT_COLOR_BLACK);
6995 /* take focus if we are visible */
6996 focus_webview(t);
6997 t->focus_wv = 1;
6999 marks_clear(t);
7000 #ifdef USE_THREAD
7001 /* kill color thread */
7002 t->thread = NULL;
7003 #endif
7004 break;
7006 case WEBKIT_LOAD_COMMITTED:
7007 /* 1 */
7008 uri = get_uri(t);
7009 if (uri == NULL)
7010 return;
7011 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
7013 if (t->status) {
7014 g_free(t->status);
7015 t->status = NULL;
7017 set_status(t, (char *)uri, XT_STATUS_LOADING);
7019 /* check if js white listing is enabled */
7020 if (enable_plugin_whitelist)
7021 check_and_set_pl(uri, t);
7022 if (enable_cookie_whitelist)
7023 check_and_set_cookie(uri, t);
7024 if (enable_js_whitelist)
7025 check_and_set_js(uri, t);
7027 if (t->styled)
7028 apply_style(t);
7031 /* we know enough to autosave the session */
7032 if (session_autosave) {
7033 a.s = NULL;
7034 save_tabs(t, &a);
7037 show_ca_status(t, uri);
7038 break;
7040 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
7041 /* 3 */
7042 break;
7044 case WEBKIT_LOAD_FINISHED:
7045 /* 2 */
7046 uri = get_uri(t);
7047 if (uri == NULL)
7048 return;
7050 if (!strncmp(uri, "http://", strlen("http://")) ||
7051 !strncmp(uri, "https://", strlen("https://")) ||
7052 !strncmp(uri, "file://", strlen("file://"))) {
7053 find.uri = uri;
7054 h = RB_FIND(history_list, &hl, &find);
7055 if (!h) {
7056 title = get_title(t, FALSE);
7057 h = g_malloc(sizeof *h);
7058 h->uri = g_strdup(uri);
7059 h->title = g_strdup(title);
7060 RB_INSERT(history_list, &hl, h);
7061 completion_add_uri(h->uri);
7062 update_history_tabs(NULL);
7066 set_status(t, (char *)uri, XT_STATUS_URI);
7067 #if WEBKIT_CHECK_VERSION(1, 1, 18)
7068 case WEBKIT_LOAD_FAILED:
7069 /* 4 */
7070 #endif
7071 #if GTK_CHECK_VERSION(2, 20, 0)
7072 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7073 gtk_widget_hide(t->spinner);
7074 #endif
7075 default:
7076 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
7077 break;
7080 if (t->item)
7081 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
7082 else
7083 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
7084 can_go_back_for_real(t));
7086 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
7087 can_go_forward_for_real(t));
7090 void
7091 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
7093 const gchar *title = NULL, *win_title = NULL;
7095 title = get_title(t, FALSE);
7096 win_title = get_title(t, TRUE);
7097 gtk_label_set_text(GTK_LABEL(t->label), title);
7098 gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
7099 if (t->tab_id == gtk_notebook_get_current_page(notebook))
7100 gtk_window_set_title(GTK_WINDOW(main_window), win_title);
7103 void
7104 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
7106 run_script(t, JS_HINTING);
7107 if (autofocus_onload &&
7108 t->tab_id == gtk_notebook_get_current_page(notebook))
7109 run_script(t, "hints.focusInput();");
7110 else
7111 run_script(t, "hints.clearFocus();");
7114 void
7115 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
7117 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
7118 progress == 100 ? 0 : (double)progress / 100);
7119 if (show_url == 0) {
7120 gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.statusbar),
7121 progress == 100 ? 0 : (double)progress / 100);
7124 update_statusbar_position(NULL, NULL);
7128 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
7129 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
7130 WebKitWebPolicyDecision *pd, struct tab *t)
7132 char *uri;
7133 WebKitWebNavigationReason reason;
7134 struct domain *d = NULL;
7136 if (t == NULL) {
7137 show_oops(NULL, "webview_npd_cb invalid parameters");
7138 return (FALSE);
7141 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
7142 t->ctrl_click,
7143 webkit_network_request_get_uri(request));
7145 uri = (char *)webkit_network_request_get_uri(request);
7147 /* if this is an xtp url, we don't load anything else */
7148 if (parse_xtp_url(t, uri))
7149 return (TRUE);
7151 if ((t->hints_on && t->new_tab) || t->ctrl_click) {
7152 t->ctrl_click = 0;
7153 create_new_tab(uri, NULL, ctrl_click_focus, -1);
7154 webkit_web_policy_decision_ignore(pd);
7155 return (TRUE); /* we made the decission */
7159 * This is a little hairy but it comes down to this:
7160 * when we run in whitelist mode we have to assist the browser in
7161 * opening the URL that it would have opened in a new tab.
7163 reason = webkit_web_navigation_action_get_reason(na);
7164 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
7165 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7166 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
7167 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7168 load_uri(t, uri);
7169 webkit_web_policy_decision_use(pd);
7170 return (TRUE); /* we made the decision */
7173 return (FALSE);
7176 WebKitWebView *
7177 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
7179 struct tab *tt;
7180 struct domain *d = NULL;
7181 const gchar *uri;
7182 WebKitWebView *webview = NULL;
7184 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
7185 webkit_web_view_get_uri(wv));
7187 if (tabless) {
7188 /* open in current tab */
7189 webview = t->wv;
7190 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
7191 uri = webkit_web_view_get_uri(wv);
7192 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7193 return (NULL);
7195 tt = create_new_tab(NULL, NULL, 1, -1);
7196 webview = tt->wv;
7197 } else if (enable_scripts == 1) {
7198 tt = create_new_tab(NULL, NULL, 1, -1);
7199 webview = tt->wv;
7202 return (webview);
7205 gboolean
7206 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
7208 const gchar *uri;
7209 struct domain *d = NULL;
7211 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
7213 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
7214 uri = webkit_web_view_get_uri(wv);
7215 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
7216 return (FALSE);
7218 delete_tab(t);
7219 } else if (enable_scripts == 1)
7220 delete_tab(t);
7222 return (TRUE);
7226 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
7228 /* we can not eat the event without throwing gtk off so defer it */
7230 /* catch middle click */
7231 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
7232 t->ctrl_click = 1;
7233 goto done;
7236 /* catch ctrl click */
7237 if (e->type == GDK_BUTTON_RELEASE &&
7238 CLEAN(e->state) == GDK_CONTROL_MASK)
7239 t->ctrl_click = 1;
7240 else
7241 t->ctrl_click = 0;
7242 done:
7243 return (XT_CB_PASSTHROUGH);
7247 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
7249 struct mime_type *m;
7251 m = find_mime_type(mime_type);
7252 if (m == NULL)
7253 return (1);
7254 if (m->mt_download)
7255 return (1);
7257 switch (fork()) {
7258 case -1:
7259 show_oops(t, "can't fork mime handler");
7260 return (1);
7261 /* NOTREACHED */
7262 case 0:
7263 break;
7264 default:
7265 return (0);
7268 /* child */
7269 execlp(m->mt_action, m->mt_action,
7270 webkit_network_request_get_uri(request), (void *)NULL);
7272 _exit(0);
7274 /* NOTREACHED */
7275 return (0);
7278 char *
7279 get_mime_type(const char *file)
7281 const gchar *m;
7282 char *mime_type = NULL;
7283 GFileInfo *fi;
7284 GFile *gf;
7286 if (g_str_has_prefix(file, "file://"))
7287 file += strlen("file://");
7289 gf = g_file_new_for_path(file);
7290 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
7291 NULL, NULL);
7292 if ((m = g_file_info_get_content_type(fi)) != NULL)
7293 mime_type = g_strdup(m);
7294 g_object_unref(fi);
7295 g_object_unref(gf);
7297 return (mime_type);
7301 run_download_mimehandler(char *mime_type, char *file)
7303 struct mime_type *m;
7305 m = find_mime_type(mime_type);
7306 if (m == NULL)
7307 return (1);
7309 switch (fork()) {
7310 case -1:
7311 show_oops(NULL, "can't fork download mime handler");
7312 return (1);
7313 /* NOTREACHED */
7314 case 0:
7315 break;
7316 default:
7317 return (0);
7320 /* child */
7321 if (g_str_has_prefix(file, "file://"))
7322 file += strlen("file://");
7323 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
7325 _exit(0);
7327 /* NOTREACHED */
7328 return (0);
7331 void
7332 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
7333 WebKitWebView *wv)
7335 WebKitDownloadStatus status;
7336 const char *file = NULL;
7337 char *mime = NULL;
7339 if (download == NULL)
7340 return;
7341 status = webkit_download_get_status(download);
7342 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
7343 return;
7345 file = webkit_download_get_destination_uri(download);
7346 if (file == NULL)
7347 return;
7348 mime = get_mime_type(file);
7349 if (mime == NULL)
7350 return;
7352 run_download_mimehandler((char *)mime, (char *)file);
7353 g_free(mime);
7357 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
7358 WebKitNetworkRequest *request, char *mime_type,
7359 WebKitWebPolicyDecision *decision, struct tab *t)
7361 if (t == NULL) {
7362 show_oops(NULL, "webview_mimetype_cb invalid parameters");
7363 return (FALSE);
7366 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
7367 t->tab_id, mime_type);
7369 if (run_mimehandler(t, mime_type, request) == 0) {
7370 webkit_web_policy_decision_ignore(decision);
7371 focus_webview(t);
7372 return (TRUE);
7375 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
7376 webkit_web_policy_decision_download(decision);
7377 return (TRUE);
7380 return (FALSE);
7384 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
7385 struct tab *t)
7387 struct stat sb;
7388 const gchar *suggested_name;
7389 gchar *filename = NULL;
7390 char *uri = NULL;
7391 struct download *download_entry;
7392 int i, ret = TRUE;
7394 if (wk_download == NULL || t == NULL) {
7395 show_oops(NULL, "%s invalid parameters", __func__);
7396 return (FALSE);
7399 suggested_name = webkit_download_get_suggested_filename(wk_download);
7400 if (suggested_name == NULL)
7401 return (FALSE); /* abort download */
7403 i = 0;
7404 do {
7405 if (filename) {
7406 g_free(filename);
7407 filename = NULL;
7409 if (i) {
7410 g_free(uri);
7411 uri = NULL;
7412 filename = g_strdup_printf("%d%s", i, suggested_name);
7414 uri = g_strdup_printf("file://%s/%s", download_dir, i ?
7415 filename : suggested_name);
7416 i++;
7417 } while (!stat(uri + strlen("file://"), &sb));
7419 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
7420 "local %s\n", __func__, t->tab_id, filename, uri);
7422 webkit_download_set_destination_uri(wk_download, uri);
7424 if (webkit_download_get_status(wk_download) ==
7425 WEBKIT_DOWNLOAD_STATUS_ERROR) {
7426 show_oops(t, "%s: download failed to start", __func__);
7427 ret = FALSE;
7428 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
7429 } else {
7430 /* connect "download first" mime handler */
7431 g_signal_connect(G_OBJECT(wk_download), "notify::status",
7432 G_CALLBACK(download_status_changed_cb), NULL);
7434 download_entry = g_malloc(sizeof(struct download));
7435 download_entry->download = wk_download;
7436 download_entry->tab = t;
7437 download_entry->id = next_download_id++;
7438 RB_INSERT(download_list, &downloads, download_entry);
7439 /* get from history */
7440 g_object_ref(wk_download);
7441 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
7442 show_oops(t, "Download of '%s' started...",
7443 basename((char *)webkit_download_get_destination_uri(wk_download)));
7446 if (uri)
7447 g_free(uri);
7449 if (filename)
7450 g_free(filename);
7452 /* sync other download manager tabs */
7453 update_download_tabs(NULL);
7456 * NOTE: never redirect/render the current tab before this
7457 * function returns. This will cause the download to never start.
7459 return (ret); /* start download */
7462 void
7463 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
7465 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
7467 if (t == NULL) {
7468 show_oops(NULL, "webview_hover_cb");
7469 return;
7472 if (uri)
7473 set_status(t, uri, XT_STATUS_LINK);
7474 else {
7475 if (t->status)
7476 set_status(t, t->status, XT_STATUS_NOTHING);
7481 mark(struct tab *t, struct karg *arg)
7483 char mark;
7484 int index;
7486 mark = arg->s[1];
7487 if ((index = marktoindex(mark)) == -1)
7488 return (-1);
7490 if (arg->i == XT_MARK_SET)
7491 t->mark[index] = gtk_adjustment_get_value(t->adjust_v);
7492 else if (arg->i == XT_MARK_GOTO) {
7493 if (t->mark[index] == XT_INVALID_MARK) {
7494 show_oops(t, "mark '%c' does not exist", mark);
7495 return (-1);
7497 /* XXX t->mark[index] can be bigger than the maximum if ajax or
7498 something changes the document size */
7499 gtk_adjustment_set_value(t->adjust_v, t->mark[index]);
7502 return (0);
7505 void
7506 marks_clear(struct tab *t)
7508 int i;
7510 for (i = 0; i < LENGTH(t->mark); i++)
7511 t->mark[i] = XT_INVALID_MARK;
7515 qmarks_load(void)
7517 char file[PATH_MAX];
7518 char *line = NULL, *p;
7519 int index, i;
7520 FILE *f;
7521 size_t linelen;
7523 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7524 if ((f = fopen(file, "r+")) == NULL) {
7525 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7526 return (1);
7529 for (i = 1; ; i++) {
7530 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
7531 break;
7532 if (strlen(line) == 0 || line[0] == '#') {
7533 free(line);
7534 line = NULL;
7535 continue;
7538 p = strtok(line, " \t");
7540 if (p == NULL || strlen(p) != 1 ||
7541 (index = marktoindex(*p)) == -1) {
7542 warnx("corrupt quickmarks file, line %d", i);
7543 break;
7546 p = strtok(NULL, " \t");
7547 if (qmarks[index] != NULL)
7548 g_free(qmarks[index]);
7549 qmarks[index] = g_strdup(p);
7552 fclose(f);
7554 return (0);
7558 qmarks_save(void)
7560 char file[PATH_MAX];
7561 int i;
7562 FILE *f;
7564 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
7565 if ((f = fopen(file, "r+")) == NULL) {
7566 show_oops(NULL, "Can't open quickmarks file: %s", strerror(errno));
7567 return (1);
7570 for (i = 0; i < XT_NOMARKS; i++)
7571 if (qmarks[i] != NULL)
7572 fprintf(f, "%c %s\n", indextomark(i), qmarks[i]);
7574 fclose(f);
7576 return (0);
7580 qmark(struct tab *t, struct karg *arg)
7582 char mark;
7583 int index;
7585 mark = arg->s[strlen(arg->s)-1];
7586 index = marktoindex(mark);
7587 if (index == -1)
7588 return (-1);
7590 switch (arg->i) {
7591 case XT_QMARK_SET:
7592 if (qmarks[index] != NULL)
7593 g_free(qmarks[index]);
7595 qmarks_load(); /* sync if multiple instances */
7596 qmarks[index] = g_strdup(get_uri(t));
7597 qmarks_save();
7598 break;
7599 case XT_QMARK_OPEN:
7600 if (qmarks[index] != NULL)
7601 load_uri(t, qmarks[index]);
7602 else {
7603 show_oops(t, "quickmark \"%c\" does not exist",
7604 mark);
7605 return (-1);
7607 break;
7608 case XT_QMARK_TAB:
7609 if (qmarks[index] != NULL)
7610 create_new_tab(qmarks[index], NULL, 1, -1);
7611 else {
7612 show_oops(t, "quickmark \"%c\" does not exist",
7613 mark);
7614 return (-1);
7616 break;
7619 return (0);
7623 go_up(struct tab *t, struct karg *args)
7625 int levels;
7626 char *uri;
7627 char *tmp;
7629 levels = atoi(args->s);
7630 if (levels == 0)
7631 levels = 1;
7633 uri = g_strdup(webkit_web_view_get_uri(t->wv));
7634 if ((tmp = strstr(uri, XT_PROTO_DELIM)) == NULL)
7635 return (1);
7636 tmp += strlen(XT_PROTO_DELIM);
7638 /* if an uri starts with a slash, leave it alone (for file:///) */
7639 if (tmp[0] == '/')
7640 tmp++;
7642 while (levels--) {
7643 char *p;
7645 p = strrchr(tmp, '/');
7646 if (p != NULL)
7647 *p = '\0';
7648 else
7649 break;
7652 load_uri(t, uri);
7653 g_free(uri);
7655 return (0);
7659 gototab(struct tab *t, struct karg *args)
7661 int tab;
7662 struct karg arg = {0, NULL, -1};
7664 tab = atoi(args->s);
7666 arg.i = XT_TAB_NEXT;
7667 arg.precount = tab;
7669 movetab(t, &arg);
7671 return (0);
7675 zoom_amount(struct tab *t, struct karg *arg)
7677 struct karg narg = {0, NULL, -1};
7679 narg.i = atoi(arg->s);
7680 resizetab(t, &narg);
7682 return (0);
7686 flip_colon(struct tab *t, struct karg *arg)
7688 struct karg narg = {0, NULL, -1};
7689 char *p;
7691 if (t == NULL || arg == NULL)
7692 return (1);
7694 p = strstr(arg->s, ":");
7695 if (p == NULL)
7696 return (1);
7697 *p = '\0';
7699 narg.i = ':';
7700 narg.s = arg->s;
7701 command(t, &narg);
7703 return (0);
7706 /* buffer commands receive the regex that triggered them in arg.s */
7707 char bcmd[XT_BUFCMD_SZ];
7708 struct buffercmd {
7709 char *regex;
7710 int precount;
7711 #define XT_PRE_NO (0)
7712 #define XT_PRE_YES (1)
7713 #define XT_PRE_MAYBE (2)
7714 char *cmd;
7715 int (*func)(struct tab *, struct karg *);
7716 int arg;
7717 regex_t cregex;
7718 } buffercmds[] = {
7719 { "^[0-9]*gu$", XT_PRE_MAYBE, "gu", go_up, 0 },
7720 { "^gg$", XT_PRE_NO, "gg", move, XT_MOVE_TOP },
7721 { "^gG$", XT_PRE_NO, "gG", move, XT_MOVE_BOTTOM },
7722 { "^[0-9]+%$", XT_PRE_YES, "%", move, XT_MOVE_PERCENT },
7723 { "^gh$", XT_PRE_NO, "gh", go_home, 0 },
7724 { "^m[a-zA-Z0-9]$", XT_PRE_NO, "m", mark, XT_MARK_SET },
7725 { "^['][a-zA-Z0-9]$", XT_PRE_NO, "'", mark, XT_MARK_GOTO },
7726 { "^[0-9]+t$", XT_PRE_YES, "t", gototab, 0 },
7727 { "^M[a-zA-Z0-9]$", XT_PRE_NO, "M", qmark, XT_QMARK_SET },
7728 { "^go[a-zA-Z0-9]$", XT_PRE_NO, "go", qmark, XT_QMARK_OPEN },
7729 { "^gn[a-zA-Z0-9]$", XT_PRE_NO, "gn", qmark, XT_QMARK_TAB },
7730 { "^ZR$", XT_PRE_NO, "ZR", restart, 0 },
7731 { "^ZZ$", XT_PRE_NO, "ZZ", quit, 0 },
7732 { "^zi$", XT_PRE_NO, "zi", resizetab, XT_ZOOM_IN },
7733 { "^zo$", XT_PRE_NO, "zo", resizetab, XT_ZOOM_OUT },
7734 { "^z0$", XT_PRE_NO, "z0", resizetab, XT_ZOOM_NORMAL },
7735 { "^[0-9]+Z$", XT_PRE_YES, "Z", zoom_amount, 0 },
7736 { "^[0-9]+:$", XT_PRE_YES, ":", flip_colon, 0 },
7739 void
7740 buffercmd_init(void)
7742 int i;
7744 for (i = 0; i < LENGTH(buffercmds); i++)
7745 if (regcomp(&buffercmds[i].cregex, buffercmds[i].regex,
7746 REG_EXTENDED | REG_NOSUB))
7747 startpage_add("invalid buffercmd regex %s",
7748 buffercmds[i].regex);
7751 void
7752 buffercmd_abort(struct tab *t)
7754 int i;
7756 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_abort: clearing buffer\n");
7757 for (i = 0; i < LENGTH(bcmd); i++)
7758 bcmd[i] = '\0';
7760 cmd_prefix = 0; /* clear prefix for non-buffer commands */
7761 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7764 void
7765 buffercmd_execute(struct tab *t, struct buffercmd *cmd)
7767 struct karg arg = {0, NULL, -1};
7769 arg.i = cmd->arg;
7770 arg.s = g_strdup(bcmd);
7772 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_execute: buffer \"%s\" "
7773 "matches regex \"%s\", executing\n", bcmd, cmd->regex);
7774 cmd->func(t, &arg);
7776 if (arg.s)
7777 g_free(arg.s);
7779 buffercmd_abort(t);
7782 gboolean
7783 buffercmd_addkey(struct tab *t, guint keyval)
7785 int i, c, match ;
7786 char s[XT_BUFCMD_SZ];
7788 if (keyval == GDK_Escape) {
7789 buffercmd_abort(t);
7790 return (XT_CB_HANDLED);
7793 /* key with modifier or non-ascii character */
7794 if (!isascii(keyval))
7795 return (XT_CB_PASSTHROUGH);
7797 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: adding key \"%c\" "
7798 "to buffer \"%s\"\n", keyval, bcmd);
7800 for (i = 0; i < LENGTH(bcmd); i++)
7801 if (bcmd[i] == '\0') {
7802 bcmd[i] = keyval;
7803 break;
7806 /* buffer full, ignore input */
7807 if (i >= LENGTH(bcmd) -1) {
7808 DNPRINTF(XT_D_BUFFERCMD, "buffercmd_addkey: buffer full\n");
7809 buffercmd_abort(t);
7810 return (XT_CB_HANDLED);
7813 gtk_entry_set_text(GTK_ENTRY(t->sbe.buffercmd), bcmd);
7815 /* find exact match */
7816 for (i = 0; i < LENGTH(buffercmds); i++)
7817 if (regexec(&buffercmds[i].cregex, bcmd,
7818 (size_t) 0, NULL, 0) == 0) {
7819 buffercmd_execute(t, &buffercmds[i]);
7820 goto done;
7823 /* find non exact matches to see if we need to abort ot not */
7824 for (i = 0, match = 0; i < LENGTH(buffercmds); i++) {
7825 DNPRINTF(XT_D_BUFFERCMD, "trying: %s\n", bcmd);
7826 c = -1;
7827 s[0] = '\0';
7828 if (buffercmds[i].precount == XT_PRE_MAYBE) {
7829 if (isdigit(bcmd[0])) {
7830 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7831 continue;
7832 } else {
7833 c = 0;
7834 if (sscanf(bcmd, "%s", s) == 0)
7835 continue;
7837 } else if (buffercmds[i].precount == XT_PRE_YES) {
7838 if (sscanf(bcmd, "%d%s", &c, s) == 0)
7839 continue;
7840 } else {
7841 if (sscanf(bcmd, "%s", s) == 0)
7842 continue;
7844 if (c == -1 && buffercmds[i].precount)
7845 continue;
7846 if (!strncmp(s, buffercmds[i].cmd, strlen(s)))
7847 match++;
7849 DNPRINTF(XT_D_BUFFERCMD, "got[%d] %d <%s>: %d %s\n",
7850 i, match, buffercmds[i].cmd, c, s);
7852 if (match == 0) {
7853 DNPRINTF(XT_D_BUFFERCMD, "aborting: %s\n", bcmd);
7854 buffercmd_abort(t);
7857 done:
7858 return (XT_CB_HANDLED);
7861 gboolean
7862 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
7864 struct key_binding *k;
7866 /* handle keybindings if buffercmd is empty.
7867 if not empty, allow commands like C-n */
7868 if (bcmd[0] == '\0' || ((e->state & (CTRL | MOD1)) != 0))
7869 TAILQ_FOREACH(k, &kbl, entry)
7870 if (e->keyval == k->key
7871 && (entry ? k->use_in_entry : 1)) {
7872 if (k->mask == 0) {
7873 if ((e->state & (CTRL | MOD1)) == 0)
7874 return (cmd_execute(t, k->cmd));
7875 } else if ((e->state & k->mask) == k->mask) {
7876 return (cmd_execute(t, k->cmd));
7880 if (!entry && ((e->state & (CTRL | MOD1)) == 0))
7881 return buffercmd_addkey(t, e->keyval);
7883 return (XT_CB_PASSTHROUGH);
7887 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
7889 char s[2];
7891 /* don't use w directly; use t->whatever instead */
7893 if (t == NULL) {
7894 show_oops(NULL, "wv_keypress_after_cb");
7895 return (XT_CB_PASSTHROUGH);
7898 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
7899 e->keyval, e->state, t);
7901 if (t->hints_on) {
7902 /* XXX make sure cmd entry is enabled */
7903 return (XT_CB_HANDLED);
7904 } else {
7905 /* prefix input*/
7906 snprintf(s, sizeof s, "%c", e->keyval);
7907 if (CLEAN(e->state) == 0 && isdigit(s[0]))
7908 cmd_prefix = 10 * cmd_prefix + atoi(s);
7911 return (handle_keypress(t, e, 0));
7915 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
7917 hide_oops(t);
7919 /* Hide buffers, if they are visible, with escape. */
7920 if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)) &&
7921 CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
7922 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7923 hide_buffers(t);
7924 return (XT_CB_HANDLED);
7927 return (XT_CB_PASSTHROUGH);
7930 gboolean
7931 hint_continue(struct tab *t)
7933 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7934 char *s;
7935 const gchar *errstr = NULL;
7936 gboolean rv = TRUE;
7937 long long i;
7939 if (!(c[0] == '.' || c[0] == ','))
7940 goto done;
7941 if (strlen(c) == 1) {
7942 /* XXX should not happen */
7943 rv = TRUE;
7944 goto done;
7947 if (isdigit(c[1])) {
7948 /* numeric input */
7949 i = strtonum(&c[1], 1, 4096, &errstr);
7950 if (errstr) {
7951 show_oops(t, "invalid numerical hint %s", &c[1]);
7952 goto done;
7954 s = g_strdup_printf("hints.updateHints(%lld);", i);
7955 run_script(t, s);
7956 g_free(s);
7957 } else {
7958 /* alphanumeric input */
7959 s = g_strdup_printf("hints.createHints('%s', '%c');",
7960 &c[1], c[0] == '.' ? 'f' : 'F');
7961 run_script(t, s);
7962 g_free(s);
7965 rv = TRUE;
7966 done:
7967 return (rv);
7970 gboolean
7971 search_continue(struct tab *t)
7973 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7974 gboolean rv = FALSE;
7976 if (c[0] == ':' || c[0] == '.' || c[0] == ',')
7977 goto done;
7978 if (strlen(c) == 1) {
7979 webkit_web_view_unmark_text_matches(t->wv);
7980 goto done;
7983 if (c[0] == '/')
7984 t->search_forward = TRUE;
7985 else if (c[0] == '?')
7986 t->search_forward = FALSE;
7987 else
7988 goto done;
7990 rv = TRUE;
7991 done:
7992 return (rv);
7995 gboolean
7996 search_cb(struct tab *t)
7998 const gchar *c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
7999 GdkColor color;
8001 if (search_continue(t) == FALSE)
8002 goto done;
8004 /* search */
8005 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, t->search_forward,
8006 TRUE) == FALSE) {
8007 /* not found, mark red */
8008 gdk_color_parse(XT_COLOR_RED, &color);
8009 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
8010 /* unmark and remove selection */
8011 webkit_web_view_unmark_text_matches(t->wv);
8012 /* my kingdom for a way to unselect text in webview */
8013 } else {
8014 /* found, highlight all */
8015 webkit_web_view_unmark_text_matches(t->wv);
8016 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
8017 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
8018 gdk_color_parse(XT_COLOR_WHITE, &color);
8019 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
8021 done:
8022 t->search_id = 0;
8023 return (FALSE);
8027 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8029 const gchar *c = gtk_entry_get_text(w);
8031 if (t == NULL) {
8032 show_oops(NULL, "cmd_keyrelease_cb invalid parameters");
8033 return (XT_CB_PASSTHROUGH);
8036 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
8037 e->keyval, e->state, t);
8039 /* hinting */
8040 if (!(e->keyval == GDK_Tab || e->keyval == GDK_ISO_Left_Tab)) {
8041 if (hint_continue(t) == FALSE)
8042 goto done;
8045 /* search */
8046 if (search_continue(t) == FALSE)
8047 goto done;
8049 /* if search length is > 4 then no longer play timeout games */
8050 if (strlen(c) > 4) {
8051 if (t->search_id) {
8052 g_source_remove(t->search_id);
8053 t->search_id = 0;
8055 search_cb(t);
8056 goto done;
8059 /* reestablish a new timer if the user types fast */
8060 if (t->search_id)
8061 g_source_remove(t->search_id);
8062 t->search_id = g_timeout_add(250, (GSourceFunc)search_cb, (gpointer)t);
8064 done:
8065 return (XT_CB_PASSTHROUGH);
8068 gboolean
8069 match_uri(const gchar *uri, const gchar *key) {
8070 gchar *voffset;
8071 size_t len;
8072 gboolean match = FALSE;
8074 len = strlen(key);
8076 if (!strncmp(key, uri, len))
8077 match = TRUE;
8078 else {
8079 voffset = strstr(uri, "/") + 2;
8080 if (!strncmp(key, voffset, len))
8081 match = TRUE;
8082 else if (g_str_has_prefix(voffset, "www.")) {
8083 voffset = voffset + strlen("www.");
8084 if (!strncmp(key, voffset, len))
8085 match = TRUE;
8089 return (match);
8092 gboolean
8093 match_session(const gchar *name, const gchar *key) {
8094 char *sub;
8096 sub = strcasestr(name, key);
8098 return sub == name;
8101 void
8102 cmd_getlist(int id, char *key)
8104 int i, dep, c = 0;
8105 struct history *h;
8106 struct session *s;
8108 if (id >= 0) {
8109 if (cmds[id].type & XT_URLARG) {
8110 RB_FOREACH_REVERSE(h, history_list, &hl)
8111 if (match_uri(h->uri, key)) {
8112 cmd_status.list[c] = (char *)h->uri;
8113 if (++c > 255)
8114 break;
8116 cmd_status.len = c;
8117 return;
8118 } else if (cmds[id].type & XT_SESSARG) {
8119 TAILQ_FOREACH(s, &sessions, entry)
8120 if (match_session(s->name, key)) {
8121 cmd_status.list[c] = (char *)s->name;
8122 if (++c > 255)
8123 break;
8125 cmd_status.len = c;
8126 return;
8127 } else if (cmds[id].type & XT_SETARG) {
8128 for (i = 0; i < LENGTH(rs); i++)
8129 if(!strncmp(key, rs[i].name, strlen(key)))
8130 cmd_status.list[c++] = rs[i].name;
8131 cmd_status.len = c;
8132 return;
8136 dep = (id == -1) ? 0 : cmds[id].level + 1;
8138 for (i = id + 1; i < LENGTH(cmds); i++) {
8139 if (cmds[i].level < dep)
8140 break;
8141 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd,
8142 strlen(key)) && !isdigit(cmds[i].cmd[0]))
8143 cmd_status.list[c++] = cmds[i].cmd;
8147 cmd_status.len = c;
8150 char *
8151 cmd_getnext(int dir)
8153 cmd_status.index += dir;
8155 if (cmd_status.index < 0)
8156 cmd_status.index = cmd_status.len - 1;
8157 else if (cmd_status.index >= cmd_status.len)
8158 cmd_status.index = 0;
8160 return cmd_status.list[cmd_status.index];
8164 cmd_tokenize(char *s, char *tokens[])
8166 int i = 0;
8167 char *tok, *last = NULL;
8168 size_t len = strlen(s);
8169 bool blank;
8171 blank = len == 0 || (len > 0 && s[len - 1] == ' ');
8172 for (tok = strtok_r(s, " ", &last); tok && i < 3;
8173 tok = strtok_r(NULL, " ", &last), i++)
8174 tokens[i] = tok;
8176 if (blank && i < 3)
8177 tokens[i++] = "";
8179 return (i);
8182 void
8183 cmd_complete(struct tab *t, char *str, int dir)
8185 GtkEntry *w = GTK_ENTRY(t->cmd);
8186 int i, j, levels, c = 0, dep = 0, parent = -1;
8187 int matchcount = 0;
8188 char *tok, *match, *s = g_strdup(str);
8189 char *tokens[3];
8190 char res[XT_MAX_URL_LENGTH + 32] = ":";
8191 char *sc = s;
8193 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
8195 /* copy prefix*/
8196 for (i = 0; isdigit(s[i]); i++)
8197 res[i + 1] = s[i];
8199 for (; isspace(s[i]); i++)
8200 res[i + 1] = s[i];
8202 s += i;
8204 levels = cmd_tokenize(s, tokens);
8206 for (i = 0; i < levels - 1; i++) {
8207 tok = tokens[i];
8208 matchcount = 0;
8209 for (j = c; j < LENGTH(cmds); j++) {
8210 if (cmds[j].level < dep)
8211 break;
8212 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd,
8213 strlen(tok))) {
8214 matchcount++;
8215 c = j + 1;
8216 if (strlen(tok) == strlen(cmds[j].cmd)) {
8217 matchcount = 1;
8218 break;
8223 if (matchcount == 1) {
8224 strlcat(res, tok, sizeof res);
8225 strlcat(res, " ", sizeof res);
8226 dep++;
8227 } else {
8228 g_free(sc);
8229 return;
8232 parent = c - 1;
8235 if (cmd_status.index == -1)
8236 cmd_getlist(parent, tokens[i]);
8238 if (cmd_status.len > 0) {
8239 match = cmd_getnext(dir);
8240 strlcat(res, match, sizeof res);
8241 gtk_entry_set_text(w, res);
8242 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8245 g_free(sc);
8248 gboolean
8249 cmd_execute(struct tab *t, char *str)
8251 struct cmd *cmd = NULL;
8252 char *tok, *last = NULL, *s = g_strdup(str), *sc;
8253 char prefixstr[4];
8254 int j, len, c = 0, dep = 0, matchcount = 0;
8255 int prefix = -1, rv = XT_CB_PASSTHROUGH;
8256 struct karg arg = {0, NULL, -1};
8258 sc = s;
8260 /* copy prefix*/
8261 for (j = 0; j<3 && isdigit(s[j]); j++)
8262 prefixstr[j]=s[j];
8264 prefixstr[j]='\0';
8266 s += j;
8267 while (isspace(s[0]))
8268 s++;
8270 if (strlen(s) > 0 && strlen(prefixstr) > 0)
8271 prefix = atoi(prefixstr);
8272 else
8273 s = sc;
8275 for (tok = strtok_r(s, " ", &last); tok;
8276 tok = strtok_r(NULL, " ", &last)) {
8277 matchcount = 0;
8278 for (j = c; j < LENGTH(cmds); j++) {
8279 if (cmds[j].level < dep)
8280 break;
8281 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1 :
8282 strlen(tok);
8283 if (cmds[j].level == dep &&
8284 !strncmp(tok, cmds[j].cmd, len)) {
8285 matchcount++;
8286 c = j + 1;
8287 cmd = &cmds[j];
8288 if (len == strlen(cmds[j].cmd)) {
8289 matchcount = 1;
8290 break;
8294 if (matchcount == 1) {
8295 if (cmd->type > 0)
8296 goto execute_cmd;
8297 dep++;
8298 } else {
8299 show_oops(t, "Invalid command: %s", str);
8300 goto done;
8303 execute_cmd:
8304 if (cmd == NULL) {
8305 show_oops(t, "Empty command");
8306 goto done;
8308 arg.i = cmd->arg;
8310 if (prefix != -1)
8311 arg.precount = prefix;
8312 else if (cmd_prefix > 0)
8313 arg.precount = cmd_prefix;
8315 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.precount > -1) {
8316 show_oops(t, "No prefix allowed: %s", str);
8317 goto done;
8319 if (cmd->type > 1)
8320 arg.s = last ? g_strdup(last) : g_strdup("");
8321 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
8322 if (arg.s == NULL) {
8323 show_oops(t, "Invalid command");
8324 goto done;
8326 arg.precount = atoi(arg.s);
8327 if (arg.precount <= 0) {
8328 if (arg.s[0] == '0')
8329 show_oops(t, "Zero count");
8330 else
8331 show_oops(t, "Trailing characters");
8332 goto done;
8336 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n",
8337 __func__, arg.precount, arg.s);
8339 cmd->func(t, &arg);
8341 rv = XT_CB_HANDLED;
8342 done:
8343 if (j > 0)
8344 cmd_prefix = 0;
8345 g_free(sc);
8346 if (arg.s)
8347 g_free(arg.s);
8349 return (rv);
8353 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8355 if (t == NULL) {
8356 show_oops(NULL, "entry_key_cb invalid parameters");
8357 return (XT_CB_PASSTHROUGH);
8360 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
8361 e->keyval, e->state, t);
8363 hide_oops(t);
8365 if (e->keyval == GDK_Escape) {
8366 /* don't use focus_webview(t) because we want to type :cmds */
8367 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8370 return (handle_keypress(t, e, 1));
8373 struct command_entry *
8374 history_prev(struct command_list *l, struct command_entry *at)
8376 if (at == NULL)
8377 at = TAILQ_LAST(l, command_list);
8378 else {
8379 at = TAILQ_PREV(at, command_list, entry);
8380 if (at == NULL)
8381 at = TAILQ_LAST(l, command_list);
8384 return (at);
8387 struct command_entry *
8388 history_next(struct command_list *l, struct command_entry *at)
8390 if (at == NULL)
8391 at = TAILQ_FIRST(l);
8392 else {
8393 at = TAILQ_NEXT(at, entry);
8394 if (at == NULL)
8395 at = TAILQ_FIRST(l);
8398 return (at);
8402 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
8404 int rv = XT_CB_HANDLED;
8405 const gchar *c = gtk_entry_get_text(w);
8406 char *s;
8408 if (t == NULL) {
8409 show_oops(NULL, "cmd_keypress_cb parameters");
8410 return (XT_CB_PASSTHROUGH);
8413 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
8414 e->keyval, e->state, t);
8416 /* sanity */
8417 if (c == NULL)
8418 e->keyval = GDK_Escape;
8419 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?' ||
8420 c[0] == '.' || c[0] == ','))
8421 e->keyval = GDK_Escape;
8423 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
8424 e->keyval != GDK_ISO_Left_Tab)
8425 cmd_status.index = -1;
8427 switch (e->keyval) {
8428 case GDK_Tab:
8429 if (c[0] == ':')
8430 cmd_complete(t, (char *)&c[1], 1);
8431 else if (c[0] == '.' || c[0] == ',')
8432 run_script(t, "hints.focusNextHint();");
8433 goto done;
8434 case GDK_ISO_Left_Tab:
8435 if (c[0] == ':')
8436 cmd_complete(t, (char *)&c[1], -1);
8437 else if (c[0] == '.' || c[0] == ',')
8438 run_script(t, "hints.focusPreviousHint();");
8439 goto done;
8440 case GDK_Down:
8441 if (c[0] == '?') {
8442 if ((search_at = history_next(&shl, search_at))) {
8443 search_at->line[0] = c[0];
8444 gtk_entry_set_text(w, search_at->line);
8445 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8447 } else if (c[0] == '/') {
8448 if ((search_at = history_prev(&chl, search_at))) {
8449 search_at->line[0] = c[0];
8450 gtk_entry_set_text(w, search_at->line);
8451 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8453 } if (c[0] == ':') {
8454 if ((history_at = history_prev(&chl, history_at))) {
8455 history_at->line[0] = c[0];
8456 gtk_entry_set_text(w, history_at->line);
8457 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8460 goto done;
8461 case GDK_Up:
8462 if (c[0] == '/') {
8463 if ((search_at = history_next(&shl, search_at))) {
8464 search_at->line[0] = c[0];
8465 gtk_entry_set_text(w, search_at->line);
8466 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8468 } else if (c[0] == '?') {
8469 if ((search_at = history_prev(&chl, search_at))) {
8470 search_at->line[0] = c[0];
8471 gtk_entry_set_text(w, search_at->line);
8472 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8474 } if (c[0] == ':') {
8475 if ((history_at = history_next(&chl, history_at))) {
8476 history_at->line[0] = c[0];
8477 gtk_entry_set_text(w, history_at->line);
8478 gtk_editable_set_position(GTK_EDITABLE(w), -1);
8481 goto done;
8482 case GDK_BackSpace:
8483 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?") ||
8484 !strcmp(c, ".") || !strcmp(c, ","))) {
8485 /* see if we are doing hinting and reset it */
8486 if (c[0] == '.' || c[0] == ',') {
8487 /* recreate hints */
8488 s = g_strdup_printf("hints.createHints('', "
8489 "'%c');", c[0] == '.' ? 'f' : 'F');
8490 run_script(t, s);
8491 g_free(s);
8493 break;
8496 /* FALLTHROUGH */
8497 case GDK_Escape:
8498 hide_cmd(t);
8499 focus_webview(t);
8501 /* cancel search */
8502 if (c != NULL && (c[0] == '/' || c[0] == '?'))
8503 webkit_web_view_unmark_text_matches(t->wv);
8505 /* no need to cancel hints */
8506 goto done;
8509 rv = XT_CB_PASSTHROUGH;
8510 done:
8511 return (rv);
8514 void
8515 wv_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8517 DNPRINTF(XT_D_CMD, "wv_popup_cb: tab %d\n", t->tab_id);
8520 void
8521 cmd_popup_cb(GtkEntry *entry, GtkMenu *menu, struct tab *t)
8523 /* popup menu enabled */
8524 t->popup = 1;
8528 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
8530 if (t == NULL) {
8531 show_oops(NULL, "cmd_focusout_cb invalid parameters");
8532 return (XT_CB_PASSTHROUGH);
8535 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d popup %d\n",
8536 t->tab_id, t->popup);
8538 /* if popup is enabled don't lose focus */
8539 if (t->popup) {
8540 t->popup = 0;
8541 return (XT_CB_PASSTHROUGH);
8544 hide_cmd(t);
8545 hide_oops(t);
8546 disable_hints(t);
8548 if (show_url == 0 || t->focus_wv)
8549 focus_webview(t);
8550 else
8551 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
8553 return (XT_CB_PASSTHROUGH);
8556 void
8557 cmd_activate_cb(GtkEntry *entry, struct tab *t)
8559 char *s;
8560 const gchar *c = gtk_entry_get_text(entry);
8562 if (t == NULL) {
8563 show_oops(NULL, "cmd_activate_cb invalid parameters");
8564 return;
8567 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
8568 hide_cmd(t);
8570 /* sanity */
8571 if (c == NULL)
8572 goto done;
8573 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?' ||
8574 c[0] == '.' || c[0] == ','))
8575 goto done;
8576 if (strlen(c) < 1)
8577 goto done;
8578 s = (char *)&c[1];
8580 if (c[0] == '/' || c[0] == '?') {
8581 /* see if there is a timer pending */
8582 if (t->search_id) {
8583 g_source_remove(t->search_id);
8584 t->search_id = 0;
8585 search_cb(t);
8588 if (t->search_text) {
8589 g_free(t->search_text);
8590 t->search_text = NULL;
8593 t->search_text = g_strdup(s);
8594 if (global_search)
8595 g_free(global_search);
8596 global_search = g_strdup(s);
8597 t->search_forward = c[0] == '/';
8599 history_add(&shl, search_file, s, &search_history_count);
8600 goto done;
8601 } else if (c[0] == '.' || c[0] == ',') {
8602 run_script(t, "hints.fire();");
8603 /* XXX history for link following? */
8604 goto done;
8607 history_add(&chl, command_file, s, &cmd_history_count);
8608 cmd_execute(t, s);
8609 done:
8610 return;
8613 void
8614 backward_cb(GtkWidget *w, struct tab *t)
8616 struct karg a;
8618 if (t == NULL) {
8619 show_oops(NULL, "backward_cb invalid parameters");
8620 return;
8623 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
8625 a.i = XT_NAV_BACK;
8626 navaction(t, &a);
8629 void
8630 forward_cb(GtkWidget *w, struct tab *t)
8632 struct karg a;
8634 if (t == NULL) {
8635 show_oops(NULL, "forward_cb invalid parameters");
8636 return;
8639 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
8641 a.i = XT_NAV_FORWARD;
8642 navaction(t, &a);
8645 void
8646 home_cb(GtkWidget *w, struct tab *t)
8648 if (t == NULL) {
8649 show_oops(NULL, "home_cb invalid parameters");
8650 return;
8653 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
8655 load_uri(t, home);
8658 void
8659 stop_cb(GtkWidget *w, struct tab *t)
8661 WebKitWebFrame *frame;
8663 if (t == NULL) {
8664 show_oops(NULL, "stop_cb invalid parameters");
8665 return;
8668 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
8670 frame = webkit_web_view_get_main_frame(t->wv);
8671 if (frame == NULL) {
8672 show_oops(t, "stop_cb: no frame");
8673 return;
8676 webkit_web_frame_stop_loading(frame);
8677 abort_favicon_download(t);
8680 void
8681 setup_webkit(struct tab *t)
8683 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
8684 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
8685 FALSE, (char *)NULL);
8686 else
8687 warnx("webkit does not have \"enable-dns-prefetching\" property");
8688 g_object_set(G_OBJECT(t->settings),
8689 "user-agent", t->user_agent, (char *)NULL);
8690 g_object_set(G_OBJECT(t->settings),
8691 "enable-scripts", enable_scripts, (char *)NULL);
8692 g_object_set(G_OBJECT(t->settings),
8693 "enable-plugins", enable_plugins, (char *)NULL);
8694 g_object_set(G_OBJECT(t->settings),
8695 "javascript-can-open-windows-automatically", enable_scripts,
8696 (char *)NULL);
8697 g_object_set(G_OBJECT(t->settings),
8698 "enable-html5-database", FALSE, (char *)NULL);
8699 g_object_set(G_OBJECT(t->settings),
8700 "enable-html5-local-storage", enable_localstorage, (char *)NULL);
8701 g_object_set(G_OBJECT(t->settings),
8702 "enable_spell_checking", enable_spell_checking, (char *)NULL);
8703 g_object_set(G_OBJECT(t->settings),
8704 "spell_checking_languages", spell_check_languages, (char *)NULL);
8705 g_object_set(G_OBJECT(t->settings),
8706 "enable-developer-extras", TRUE, (char *)NULL);
8707 g_object_set(G_OBJECT(t->wv),
8708 "full-content-zoom", TRUE, (char *)NULL);
8710 webkit_web_view_set_settings(t->wv, t->settings);
8713 gboolean
8714 update_statusbar_position(GtkAdjustment* adjustment, gpointer data)
8716 struct tab *ti, *t = NULL;
8717 gdouble view_size, value, max;
8718 gchar *position;
8720 TAILQ_FOREACH(ti, &tabs, entry)
8721 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
8722 t = ti;
8723 break;
8726 if (t == NULL)
8727 return FALSE;
8729 if (adjustment == NULL)
8730 adjustment = gtk_scrolled_window_get_vadjustment(
8731 GTK_SCROLLED_WINDOW(t->browser_win));
8733 view_size = gtk_adjustment_get_page_size(adjustment);
8734 value = gtk_adjustment_get_value(adjustment);
8735 max = gtk_adjustment_get_upper(adjustment) - view_size;
8737 if (max == 0)
8738 position = g_strdup("All");
8739 else if (value == max)
8740 position = g_strdup("Bot");
8741 else if (value == 0)
8742 position = g_strdup("Top");
8743 else
8744 position = g_strdup_printf("%d%%", (int) ((value / max) * 100));
8746 gtk_entry_set_text(GTK_ENTRY(t->sbe.position), position);
8747 g_free(position);
8749 return (TRUE);
8752 GtkWidget *
8753 create_window(const gchar *name)
8755 GtkWidget *w;
8757 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
8758 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
8759 gtk_widget_set_name(w, name);
8760 gtk_window_set_wmclass(GTK_WINDOW(w), name, "XXXTerm");
8762 return (w);
8765 GtkWidget *
8766 create_browser(struct tab *t)
8768 GtkWidget *w;
8769 gchar *strval;
8770 GtkAdjustment *adjustment;
8772 if (t == NULL) {
8773 show_oops(NULL, "create_browser invalid parameters");
8774 return (NULL);
8777 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
8778 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
8779 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
8780 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
8782 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
8783 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
8784 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
8786 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
8787 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
8789 /* set defaults */
8790 t->settings = webkit_web_settings_new();
8792 g_object_set(t->settings, "default-encoding", encoding, (char *)NULL);
8794 if (user_agent == NULL) {
8795 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
8796 (char *)NULL);
8797 t->user_agent = g_strdup_printf("%s %s+", strval, version);
8798 g_free(strval);
8799 } else
8800 t->user_agent = g_strdup(user_agent);
8802 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
8804 adjustment =
8805 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w));
8806 g_signal_connect(G_OBJECT(adjustment), "value-changed",
8807 G_CALLBACK(update_statusbar_position), NULL);
8809 setup_webkit(t);
8810 setup_inspector(t);
8812 return (w);
8815 GtkWidget *
8816 create_kiosk_toolbar(struct tab *t)
8818 GtkWidget *toolbar = NULL, *b;
8820 b = gtk_hbox_new(FALSE, 0);
8821 toolbar = b;
8822 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8824 /* backward button */
8825 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8826 gtk_widget_set_sensitive(t->backward, FALSE);
8827 g_signal_connect(G_OBJECT(t->backward), "clicked",
8828 G_CALLBACK(backward_cb), t);
8829 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
8831 /* forward button */
8832 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
8833 gtk_widget_set_sensitive(t->forward, FALSE);
8834 g_signal_connect(G_OBJECT(t->forward), "clicked",
8835 G_CALLBACK(forward_cb), t);
8836 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
8838 /* home button */
8839 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
8840 gtk_widget_set_sensitive(t->gohome, true);
8841 g_signal_connect(G_OBJECT(t->gohome), "clicked",
8842 G_CALLBACK(home_cb), t);
8843 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
8845 /* create widgets but don't use them */
8846 t->uri_entry = gtk_entry_new();
8847 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8848 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8849 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8851 return (toolbar);
8854 GtkWidget *
8855 create_toolbar(struct tab *t)
8857 GtkWidget *toolbar = NULL, *b, *eb1;
8859 b = gtk_hbox_new(FALSE, 0);
8860 toolbar = b;
8861 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
8863 /* backward button */
8864 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
8865 gtk_widget_set_sensitive(t->backward, FALSE);
8866 g_signal_connect(G_OBJECT(t->backward), "clicked",
8867 G_CALLBACK(backward_cb), t);
8868 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
8870 /* forward button */
8871 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
8872 gtk_widget_set_sensitive(t->forward, FALSE);
8873 g_signal_connect(G_OBJECT(t->forward), "clicked",
8874 G_CALLBACK(forward_cb), t);
8875 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
8876 FALSE, 0);
8878 /* stop button */
8879 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
8880 gtk_widget_set_sensitive(t->stop, FALSE);
8881 g_signal_connect(G_OBJECT(t->stop), "clicked",
8882 G_CALLBACK(stop_cb), t);
8883 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
8884 FALSE, 0);
8886 /* JS button */
8887 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
8888 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
8889 gtk_widget_set_sensitive(t->js_toggle, TRUE);
8890 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
8891 G_CALLBACK(js_toggle_cb), t);
8892 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
8894 t->uri_entry = gtk_entry_new();
8895 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
8896 G_CALLBACK(activate_uri_entry_cb), t);
8897 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
8898 G_CALLBACK(entry_key_cb), t);
8899 completion_add(t);
8900 eb1 = gtk_hbox_new(FALSE, 0);
8901 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
8902 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
8903 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
8905 /* search entry */
8906 if (search_string) {
8907 GtkWidget *eb2;
8908 t->search_entry = gtk_entry_new();
8909 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
8910 g_signal_connect(G_OBJECT(t->search_entry), "activate",
8911 G_CALLBACK(activate_search_entry_cb), t);
8912 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
8913 G_CALLBACK(entry_key_cb), t);
8914 gtk_widget_set_size_request(t->search_entry, -1, -1);
8915 eb2 = gtk_hbox_new(FALSE, 0);
8916 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
8917 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
8919 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
8922 return (toolbar);
8925 GtkWidget *
8926 create_buffers(struct tab *t)
8928 GtkCellRenderer *renderer;
8929 GtkWidget *view;
8931 view = gtk_tree_view_new();
8933 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
8935 renderer = gtk_cell_renderer_text_new();
8936 gtk_tree_view_insert_column_with_attributes
8937 (GTK_TREE_VIEW(view), -1, "Id", renderer, "text", COL_ID, (char *)NULL);
8939 renderer = gtk_cell_renderer_text_new();
8940 gtk_tree_view_insert_column_with_attributes
8941 (GTK_TREE_VIEW(view), -1, "Title", renderer, "text", COL_TITLE,
8942 (char *)NULL);
8944 gtk_tree_view_set_model
8945 (GTK_TREE_VIEW(view), GTK_TREE_MODEL(buffers_store));
8947 return view;
8950 void
8951 row_activated_cb(GtkTreeView *view, GtkTreePath *path,
8952 GtkTreeViewColumn *col, struct tab *t)
8954 GtkTreeIter iter;
8955 guint id;
8957 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
8959 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter,
8960 path)) {
8961 gtk_tree_model_get
8962 (GTK_TREE_MODEL(buffers_store), &iter, COL_ID, &id, -1);
8963 set_current_tab(id - 1);
8966 hide_buffers(t);
8969 /* after tab reordering/creation/removal */
8970 void
8971 recalc_tabs(void)
8973 struct tab *t;
8974 int maxid = 0;
8976 TAILQ_FOREACH(t, &tabs, entry) {
8977 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
8978 if (t->tab_id > maxid)
8979 maxid = t->tab_id;
8981 gtk_widget_show(t->tab_elems.sep);
8984 TAILQ_FOREACH(t, &tabs, entry) {
8985 if (t->tab_id == maxid) {
8986 gtk_widget_hide(t->tab_elems.sep);
8987 break;
8992 /* after active tab change */
8993 void
8994 recolor_compact_tabs(void)
8996 struct tab *t;
8997 int curid = 0;
8998 GdkColor color;
9000 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
9001 TAILQ_FOREACH(t, &tabs, entry)
9002 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL,
9003 &color);
9005 curid = gtk_notebook_get_current_page(notebook);
9006 TAILQ_FOREACH(t, &tabs, entry)
9007 if (t->tab_id == curid) {
9008 gdk_color_parse(XT_COLOR_CT_ACTIVE, &color);
9009 gtk_widget_modify_fg(t->tab_elems.label,
9010 GTK_STATE_NORMAL, &color);
9011 break;
9015 void
9016 set_current_tab(int page_num)
9018 buffercmd_abort(get_current_tab());
9019 gtk_notebook_set_current_page(notebook, page_num);
9020 recolor_compact_tabs();
9024 undo_close_tab_save(struct tab *t)
9026 int m, n;
9027 const gchar *uri;
9028 struct undo *u1, *u2;
9029 GList *items;
9030 WebKitWebHistoryItem *item;
9032 if ((uri = get_uri(t)) == NULL)
9033 return (1);
9035 u1 = g_malloc0(sizeof(struct undo));
9036 u1->uri = g_strdup(uri);
9038 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
9040 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
9041 n = webkit_web_back_forward_list_get_back_length(t->bfl);
9042 u1->back = n;
9044 /* forward history */
9045 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
9047 while (items) {
9048 item = items->data;
9049 u1->history = g_list_prepend(u1->history,
9050 webkit_web_history_item_copy(item));
9051 items = g_list_next(items);
9054 /* current item */
9055 if (m) {
9056 item = webkit_web_back_forward_list_get_current_item(t->bfl);
9057 u1->history = g_list_prepend(u1->history,
9058 webkit_web_history_item_copy(item));
9061 /* back history */
9062 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
9064 while (items) {
9065 item = items->data;
9066 u1->history = g_list_prepend(u1->history,
9067 webkit_web_history_item_copy(item));
9068 items = g_list_next(items);
9071 TAILQ_INSERT_HEAD(&undos, u1, entry);
9073 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
9074 u2 = TAILQ_LAST(&undos, undo_tailq);
9075 TAILQ_REMOVE(&undos, u2, entry);
9076 g_free(u2->uri);
9077 g_list_free(u2->history);
9078 g_free(u2);
9079 } else
9080 undo_count++;
9082 return (0);
9085 void
9086 delete_tab(struct tab *t)
9088 struct karg a;
9090 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
9092 if (t == NULL)
9093 return;
9096 * no need to join thread here because it won't access t on completion
9099 TAILQ_REMOVE(&tabs, t, entry);
9100 buffercmd_abort(t);
9102 /* Halt all webkit activity. */
9103 abort_favicon_download(t);
9104 webkit_web_view_stop_loading(t->wv);
9106 /* Save the tab, so we can undo the close. */
9107 undo_close_tab_save(t);
9109 /* just in case */
9110 if (t->search_id)
9111 g_source_remove(t->search_id);
9113 /* inspector */
9114 bzero(&a, sizeof a);
9115 a.i = XT_INS_CLOSE;
9116 inspector_cmd(t, &a);
9118 if (browser_mode == XT_BM_KIOSK) {
9119 gtk_widget_destroy(t->uri_entry);
9120 gtk_widget_destroy(t->stop);
9121 gtk_widget_destroy(t->js_toggle);
9124 gtk_widget_destroy(t->tab_elems.eventbox);
9125 gtk_widget_destroy(t->vbox);
9127 g_free(t->user_agent);
9128 g_free(t->stylesheet);
9129 g_free(t->tmp_uri);
9130 g_free(t);
9131 t = NULL;
9133 if (TAILQ_EMPTY(&tabs)) {
9134 if (browser_mode == XT_BM_KIOSK)
9135 create_new_tab(home, NULL, 1, -1);
9136 else
9137 create_new_tab(NULL, NULL, 1, -1);
9140 /* recreate session */
9141 if (session_autosave) {
9142 bzero(&a, sizeof a);
9143 a.s = NULL;
9144 save_tabs(NULL, &a);
9147 recalc_tabs();
9148 recolor_compact_tabs();
9151 void
9152 update_statusbar_zoom(struct tab *t)
9154 gfloat zoom;
9155 char s[16] = { '\0' };
9157 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
9158 if ((zoom <= 0.99 || zoom >= 1.01))
9159 snprintf(s, sizeof s, "%d%%", (int)(zoom * 100));
9160 gtk_entry_set_text(GTK_ENTRY(t->sbe.zoom), s);
9163 void
9164 setzoom_webkit(struct tab *t, int adjust)
9166 #define XT_ZOOMPERCENT 0.04
9168 gfloat zoom;
9170 if (t == NULL) {
9171 show_oops(NULL, "setzoom_webkit invalid parameters");
9172 return;
9175 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
9176 if (adjust == XT_ZOOM_IN)
9177 zoom += XT_ZOOMPERCENT;
9178 else if (adjust == XT_ZOOM_OUT)
9179 zoom -= XT_ZOOMPERCENT;
9180 else if (adjust > 0)
9181 zoom = default_zoom_level + adjust / 100.0 - 1.0;
9182 else {
9183 show_oops(t, "setzoom_webkit invalid zoom value");
9184 return;
9187 if (zoom < XT_ZOOMPERCENT)
9188 zoom = XT_ZOOMPERCENT;
9189 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
9190 update_statusbar_zoom(t);
9193 gboolean
9194 tab_clicked_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
9196 struct tab *t = (struct tab *) data;
9198 DNPRINTF(XT_D_TAB, "tab_clicked_cb: tab: %d\n", t->tab_id);
9200 switch (event->button) {
9201 case 1:
9202 set_current_tab(t->tab_id);
9203 break;
9204 case 2:
9205 delete_tab(t);
9206 break;
9209 return TRUE;
9212 void
9213 append_tab(struct tab *t)
9215 if (t == NULL)
9216 return;
9218 TAILQ_INSERT_TAIL(&tabs, t, entry);
9219 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
9222 GtkWidget *
9223 create_sbe(int width)
9225 GtkWidget *sbe;
9227 sbe = gtk_entry_new();
9228 gtk_entry_set_inner_border(GTK_ENTRY(sbe), NULL);
9229 gtk_entry_set_has_frame(GTK_ENTRY(sbe), FALSE);
9230 gtk_widget_set_can_focus(GTK_WIDGET(sbe), FALSE);
9231 gtk_widget_modify_font(GTK_WIDGET(sbe), statusbar_font);
9232 gtk_entry_set_alignment(GTK_ENTRY(sbe), 1.0);
9233 gtk_widget_set_size_request(sbe, width, -1);
9235 return sbe;
9238 struct tab *
9239 create_new_tab(char *title, struct undo *u, int focus, int position)
9241 struct tab *t;
9242 int load = 1, id;
9243 GtkWidget *b, *bb;
9244 WebKitWebHistoryItem *item;
9245 GList *items;
9246 GdkColor color;
9247 char *p;
9248 int sbe_p = 0, sbe_b = 0,
9249 sbe_z = 0;
9251 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
9253 if (tabless && !TAILQ_EMPTY(&tabs)) {
9254 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
9255 return (NULL);
9258 t = g_malloc0(sizeof *t);
9260 if (title == NULL) {
9261 title = "(untitled)";
9262 load = 0;
9265 t->vbox = gtk_vbox_new(FALSE, 0);
9267 /* label + button for tab */
9268 b = gtk_hbox_new(FALSE, 0);
9269 t->tab_content = b;
9271 #if GTK_CHECK_VERSION(2, 20, 0)
9272 t->spinner = gtk_spinner_new();
9273 #endif
9274 t->label = gtk_label_new(title);
9275 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
9276 gtk_widget_set_size_request(t->label, 100, 0);
9277 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
9278 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
9279 gtk_widget_set_size_request(b, 130, 0);
9281 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
9282 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
9283 #if GTK_CHECK_VERSION(2, 20, 0)
9284 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
9285 #endif
9287 /* toolbar */
9288 if (browser_mode == XT_BM_KIOSK) {
9289 t->toolbar = create_kiosk_toolbar(t);
9290 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE,
9292 } else {
9293 t->toolbar = create_toolbar(t);
9294 if (fancy_bar)
9295 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE,
9296 FALSE, 0);
9299 /* marks */
9300 marks_clear(t);
9302 /* browser */
9303 t->browser_win = create_browser(t);
9304 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
9306 /* oops message for user feedback */
9307 t->oops = gtk_entry_new();
9308 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
9309 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
9310 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
9311 gdk_color_parse(XT_COLOR_RED, &color);
9312 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
9313 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
9314 gtk_widget_modify_font(GTK_WIDGET(t->oops), oops_font);
9316 /* command entry */
9317 t->cmd = gtk_entry_new();
9318 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
9319 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
9320 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
9321 gtk_widget_modify_font(GTK_WIDGET(t->cmd), cmd_font);
9323 /* status bar */
9324 t->statusbar_box = gtk_hbox_new(FALSE, 0);
9326 t->sbe.statusbar = gtk_entry_new();
9327 gtk_entry_set_inner_border(GTK_ENTRY(t->sbe.statusbar), NULL);
9328 gtk_entry_set_has_frame(GTK_ENTRY(t->sbe.statusbar), FALSE);
9329 gtk_widget_set_can_focus(GTK_WIDGET(t->sbe.statusbar), FALSE);
9330 gtk_widget_modify_font(GTK_WIDGET(t->sbe.statusbar), statusbar_font);
9332 /* create these widgets only if specified in statusbar_elems */
9334 t->sbe.position = create_sbe(40);
9335 t->sbe.zoom = create_sbe(40);
9336 t->sbe.buffercmd = create_sbe(60);
9338 statusbar_modify_attr(t, XT_COLOR_WHITE, XT_COLOR_BLACK);
9340 gtk_box_pack_start(GTK_BOX(t->statusbar_box), t->sbe.statusbar, TRUE,
9341 TRUE, FALSE);
9343 /* gtk widgets cannot be added to a box twice. sbe_* variables
9344 make sure of this */
9345 for (p = statusbar_elems; *p != '\0'; p++) {
9346 switch (*p) {
9347 case '|':
9349 GtkWidget *sep = gtk_vseparator_new();
9351 gdk_color_parse(XT_COLOR_SB_SEPARATOR, &color);
9352 gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &color);
9353 gtk_box_pack_start(GTK_BOX(t->statusbar_box), sep,
9354 FALSE, FALSE, FALSE);
9355 break;
9357 case 'P':
9358 if (sbe_p) {
9359 warnx("flag \"%c\" specified more than "
9360 "once in statusbar_elems\n", *p);
9361 break;
9363 sbe_p = 1;
9364 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9365 t->sbe.position, FALSE, FALSE, FALSE);
9366 break;
9367 case 'B':
9368 if (sbe_b) {
9369 warnx("flag \"%c\" specified more than "
9370 "once in statusbar_elems\n", *p);
9371 break;
9373 sbe_b = 1;
9374 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9375 t->sbe.buffercmd, FALSE, FALSE, FALSE);
9376 break;
9377 case 'Z':
9378 if (sbe_z) {
9379 warnx("flag \"%c\" specified more than "
9380 "once in statusbar_elems\n", *p);
9381 break;
9383 sbe_z = 1;
9384 gtk_box_pack_start(GTK_BOX(t->statusbar_box),
9385 t->sbe.zoom, FALSE, FALSE, FALSE);
9386 break;
9387 default:
9388 warnx("illegal flag \"%c\" in statusbar_elems\n", *p);
9389 break;
9393 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar_box, FALSE, FALSE, 0);
9395 /* buffer list */
9396 t->buffers = create_buffers(t);
9397 gtk_box_pack_end(GTK_BOX(t->vbox), t->buffers, FALSE, FALSE, 0);
9399 /* xtp meaning is normal by default */
9400 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
9402 /* set empty favicon */
9403 xt_icon_from_name(t, "text-html");
9405 /* and show it all */
9406 gtk_widget_show_all(b);
9407 gtk_widget_show_all(t->vbox);
9409 /* compact tab bar */
9410 t->tab_elems.label = gtk_label_new(title);
9411 gtk_label_set_width_chars(GTK_LABEL(t->tab_elems.label), 1.0);
9412 gtk_misc_set_alignment(GTK_MISC(t->tab_elems.label), 0.0, 0.0);
9413 gtk_misc_set_padding(GTK_MISC(t->tab_elems.label), 4.0, 4.0);
9414 gtk_widget_modify_font(GTK_WIDGET(t->tab_elems.label), tabbar_font);
9416 t->tab_elems.eventbox = gtk_event_box_new();
9417 t->tab_elems.box = gtk_hbox_new(FALSE, 0);
9418 t->tab_elems.sep = gtk_vseparator_new();
9420 gdk_color_parse(XT_COLOR_CT_BACKGROUND, &color);
9421 gtk_widget_modify_bg(t->tab_elems.eventbox, GTK_STATE_NORMAL, &color);
9422 gdk_color_parse(XT_COLOR_CT_INACTIVE, &color);
9423 gtk_widget_modify_fg(t->tab_elems.label, GTK_STATE_NORMAL, &color);
9424 gdk_color_parse(XT_COLOR_CT_SEPARATOR, &color);
9425 gtk_widget_modify_bg(t->tab_elems.sep, GTK_STATE_NORMAL, &color);
9427 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.label, TRUE,
9428 TRUE, 0);
9429 gtk_box_pack_start(GTK_BOX(t->tab_elems.box), t->tab_elems.sep, FALSE,
9430 FALSE, 0);
9431 gtk_container_add(GTK_CONTAINER(t->tab_elems.eventbox),
9432 t->tab_elems.box);
9434 gtk_box_pack_start(GTK_BOX(tab_bar), t->tab_elems.eventbox, TRUE,
9435 TRUE, 0);
9436 gtk_widget_show_all(t->tab_elems.eventbox);
9438 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
9439 append_tab(t);
9440 else {
9441 id = position >= 0 ? position :
9442 gtk_notebook_get_current_page(notebook) + 1;
9443 if (id > gtk_notebook_get_n_pages(notebook))
9444 append_tab(t);
9445 else {
9446 TAILQ_INSERT_TAIL(&tabs, t, entry);
9447 gtk_notebook_insert_page(notebook, t->vbox, b, id);
9448 gtk_box_reorder_child(GTK_BOX(tab_bar),
9449 t->tab_elems.eventbox, id);
9450 recalc_tabs();
9454 #if GTK_CHECK_VERSION(2, 20, 0)
9455 /* turn spinner off if we are a new tab without uri */
9456 if (!load) {
9457 gtk_spinner_stop(GTK_SPINNER(t->spinner));
9458 gtk_widget_hide(t->spinner);
9460 #endif
9461 /* make notebook tabs reorderable */
9462 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
9464 /* compact tabs clickable */
9465 g_signal_connect(G_OBJECT(t->tab_elems.eventbox),
9466 "button_press_event", G_CALLBACK(tab_clicked_cb), t);
9468 g_object_connect(G_OBJECT(t->cmd),
9469 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
9470 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
9471 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
9472 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
9473 "signal::populate-popup", G_CALLBACK(cmd_popup_cb), t,
9474 (char *)NULL);
9476 /* reuse wv_button_cb to hide oops */
9477 g_object_connect(G_OBJECT(t->oops),
9478 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9479 (char *)NULL);
9481 g_signal_connect(t->buffers,
9482 "row-activated", G_CALLBACK(row_activated_cb), t);
9483 g_object_connect(G_OBJECT(t->buffers),
9484 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t, (char *)NULL);
9486 g_object_connect(G_OBJECT(t->wv),
9487 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
9488 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9489 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
9490 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
9491 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
9492 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9493 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
9494 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
9495 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
9496 "signal::event", G_CALLBACK(webview_event_cb), t,
9497 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
9498 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
9499 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
9500 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
9501 "signal::button_release_event", G_CALLBACK(wv_release_button_cb), t,
9502 "signal::populate-popup", G_CALLBACK(wv_popup_cb), t,
9503 (char *)NULL);
9504 g_signal_connect(t->wv,
9505 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
9506 g_signal_connect(t->wv,
9507 "notify::title", G_CALLBACK(notify_title_cb), t);
9509 /* hijack the unused keys as if we were the browser */
9510 g_object_connect(G_OBJECT(t->toolbar),
9511 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
9512 (char *)NULL);
9514 g_signal_connect(G_OBJECT(bb), "button_press_event",
9515 G_CALLBACK(tab_close_cb), t);
9517 /* setup history */
9518 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
9519 /* restore the tab's history */
9520 if (u && u->history) {
9521 items = u->history;
9522 while (items) {
9523 item = items->data;
9524 webkit_web_back_forward_list_add_item(t->bfl, item);
9525 items = g_list_next(items);
9528 item = g_list_nth_data(u->history, u->back);
9529 if (item)
9530 webkit_web_view_go_to_back_forward_item(t->wv, item);
9532 g_list_free(items);
9533 g_list_free(u->history);
9534 } else
9535 webkit_web_back_forward_list_clear(t->bfl);
9537 /* hide stuff */
9538 hide_cmd(t);
9539 hide_oops(t);
9540 hide_buffers(t);
9541 url_set_visibility();
9542 statusbar_set_visibility();
9544 if (focus) {
9545 set_current_tab(t->tab_id);
9546 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
9547 t->tab_id);
9549 if (load) {
9550 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
9551 load_uri(t, title);
9552 } else {
9553 if (show_url == 1)
9554 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
9555 else
9556 focus_webview(t);
9558 } else if (load)
9559 load_uri(t, title);
9561 recolor_compact_tabs();
9562 setzoom_webkit(t, XT_ZOOM_NORMAL);
9563 return (t);
9566 void
9567 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9568 gpointer *udata)
9570 struct tab *t;
9571 const gchar *uri;
9573 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
9575 if (gtk_notebook_get_current_page(notebook) == -1)
9576 recalc_tabs();
9578 TAILQ_FOREACH(t, &tabs, entry) {
9579 if (t->tab_id == pn) {
9580 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
9581 "%d\n", pn);
9583 uri = get_title(t, TRUE);
9584 gtk_window_set_title(GTK_WINDOW(main_window), uri);
9586 hide_cmd(t);
9587 hide_oops(t);
9589 if (t->focus_wv) {
9590 /* can't use focus_webview here */
9591 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
9597 void
9598 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
9599 gpointer *udata)
9601 struct tab *t = NULL, *tt;
9603 recalc_tabs();
9605 TAILQ_FOREACH(tt, &tabs, entry)
9606 if (tt->tab_id == pn) {
9607 t = tt;
9608 break;
9610 if (t == NULL)
9611 return;
9612 DNPRINTF(XT_D_TAB, "page_reordered_cb: tab: %d\n", t->tab_id);
9614 gtk_box_reorder_child(GTK_BOX(tab_bar), t->tab_elems.eventbox,
9615 t->tab_id);
9618 void
9619 menuitem_response(struct tab *t)
9621 gtk_notebook_set_current_page(notebook, t->tab_id);
9624 gboolean
9625 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
9627 GtkWidget *menu, *menu_items;
9628 GdkEventButton *bevent;
9629 const gchar *uri;
9630 struct tab *ti;
9632 if (event->type == GDK_BUTTON_PRESS) {
9633 bevent = (GdkEventButton *) event;
9634 menu = gtk_menu_new();
9636 TAILQ_FOREACH(ti, &tabs, entry) {
9637 if ((uri = get_uri(ti)) == NULL)
9638 /* XXX make sure there is something to print */
9639 /* XXX add gui pages in here to look purdy */
9640 uri = "(untitled)";
9641 menu_items = gtk_menu_item_new_with_label(uri);
9642 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
9643 gtk_widget_show(menu_items);
9645 g_signal_connect_swapped((menu_items),
9646 "activate", G_CALLBACK(menuitem_response),
9647 (gpointer)ti);
9650 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
9651 bevent->button, bevent->time);
9653 /* unref object so it'll free itself when popped down */
9654 #if !GTK_CHECK_VERSION(3, 0, 0)
9655 /* XXX does not need unref with gtk+3? */
9656 g_object_ref_sink(menu);
9657 g_object_unref(menu);
9658 #endif
9660 return (TRUE /* eat event */);
9663 return (FALSE /* propagate */);
9667 icon_size_map(int icon_size)
9669 if (icon_size <= GTK_ICON_SIZE_INVALID ||
9670 icon_size > GTK_ICON_SIZE_DIALOG)
9671 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
9673 return (icon_size);
9676 GtkWidget *
9677 create_button(char *name, char *stockid, int size)
9679 GtkWidget *button, *image;
9680 gchar *rcstring;
9681 int gtk_icon_size;
9683 rcstring = g_strdup_printf(
9684 "style \"%s-style\"\n"
9685 "{\n"
9686 " GtkWidget::focus-padding = 0\n"
9687 " GtkWidget::focus-line-width = 0\n"
9688 " xthickness = 0\n"
9689 " ythickness = 0\n"
9690 "}\n"
9691 "widget \"*.%s\" style \"%s-style\"", name, name, name);
9692 gtk_rc_parse_string(rcstring);
9693 g_free(rcstring);
9694 button = gtk_button_new();
9695 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
9696 gtk_icon_size = icon_size_map(size ? size : icon_size);
9698 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
9699 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9700 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
9701 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
9702 gtk_widget_set_name(button, name);
9703 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
9705 return (button);
9708 void
9709 button_set_stockid(GtkWidget *button, char *stockid)
9711 GtkWidget *image;
9713 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
9714 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
9715 gtk_button_set_image(GTK_BUTTON(button), image);
9718 void
9719 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
9721 gchar *p = NULL;
9722 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
9723 gint len;
9725 if (xterm_workaround == 0)
9726 return;
9729 * xterm doesn't play nice with clipboards because it clears the
9730 * primary when clicked. We rely on primary being set to properly
9731 * handle middle mouse button clicks (paste). So when someone clears
9732 * primary copy whatever is in CUT_BUFFER0 into primary to simualte
9733 * other application behavior (as in DON'T clear primary).
9736 p = gtk_clipboard_wait_for_text(primary);
9737 if (p == NULL) {
9738 if (gdk_property_get(gdk_get_default_root_window(),
9739 atom,
9740 gdk_atom_intern("STRING", FALSE),
9742 1024 * 1024 /* picked out of my butt */,
9743 FALSE,
9744 NULL,
9745 NULL,
9746 &len,
9747 (guchar **)&p)) {
9748 /* yes sir, we need to NUL the string */
9749 p[len] = '\0';
9750 gtk_clipboard_set_text(primary, p, -1);
9754 if (p)
9755 g_free(p);
9758 void
9759 create_canvas(void)
9761 GtkWidget *vbox;
9762 GList *l = NULL;
9763 GdkPixbuf *pb;
9764 char file[PATH_MAX];
9765 int i;
9767 vbox = gtk_vbox_new(FALSE, 0);
9768 gtk_box_set_spacing(GTK_BOX(vbox), 0);
9769 notebook = GTK_NOTEBOOK(gtk_notebook_new());
9770 #if !GTK_CHECK_VERSION(3, 0, 0)
9771 /* XXX seems to be needed with gtk+2 */
9772 gtk_notebook_set_tab_hborder(notebook, 0);
9773 gtk_notebook_set_tab_vborder(notebook, 0);
9774 #endif
9775 gtk_notebook_set_scrollable(notebook, TRUE);
9776 gtk_notebook_set_show_border(notebook, FALSE);
9777 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
9779 abtn = gtk_button_new();
9780 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
9781 gtk_widget_set_size_request(arrow, -1, -1);
9782 gtk_container_add(GTK_CONTAINER(abtn), arrow);
9783 gtk_widget_set_size_request(abtn, -1, 20);
9785 #if GTK_CHECK_VERSION(2, 20, 0)
9786 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
9787 #endif
9788 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
9790 /* compact tab bar */
9791 tab_bar = gtk_hbox_new(TRUE, 0);
9793 gtk_box_pack_start(GTK_BOX(vbox), tab_bar, FALSE, FALSE, 0);
9794 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
9795 gtk_widget_set_size_request(vbox, -1, -1);
9797 g_object_connect(G_OBJECT(notebook),
9798 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
9799 (char *)NULL);
9800 g_object_connect(G_OBJECT(notebook),
9801 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb),
9802 NULL, (char *)NULL);
9803 g_signal_connect(G_OBJECT(abtn), "button_press_event",
9804 G_CALLBACK(arrow_cb), NULL);
9806 main_window = create_window("xxxterm");
9807 gtk_container_add(GTK_CONTAINER(main_window), vbox);
9808 g_signal_connect(G_OBJECT(main_window), "delete_event",
9809 G_CALLBACK(gtk_main_quit), NULL);
9811 /* icons */
9812 for (i = 0; i < LENGTH(icons); i++) {
9813 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
9814 pb = gdk_pixbuf_new_from_file(file, NULL);
9815 l = g_list_append(l, pb);
9817 gtk_window_set_default_icon_list(l);
9819 /* clipboard work around */
9820 if (xterm_workaround)
9821 g_signal_connect(
9822 G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
9823 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
9825 gtk_widget_show_all(abtn);
9826 gtk_widget_show_all(main_window);
9827 notebook_tab_set_visibility();
9830 void
9831 set_hook(void **hook, char *name)
9833 if (hook == NULL)
9834 errx(1, "set_hook");
9836 if (*hook == NULL) {
9837 *hook = dlsym(RTLD_NEXT, name);
9838 if (*hook == NULL)
9839 errx(1, "can't hook %s", name);
9843 /* override libsoup soup_cookie_equal because it doesn't look at domain */
9844 gboolean
9845 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
9847 g_return_val_if_fail(cookie1, FALSE);
9848 g_return_val_if_fail(cookie2, FALSE);
9850 return (!strcmp (cookie1->name, cookie2->name) &&
9851 !strcmp (cookie1->value, cookie2->value) &&
9852 !strcmp (cookie1->path, cookie2->path) &&
9853 !strcmp (cookie1->domain, cookie2->domain));
9856 void
9857 transfer_cookies(void)
9859 GSList *cf;
9860 SoupCookie *sc, *pc;
9862 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9864 for (;cf; cf = cf->next) {
9865 pc = cf->data;
9866 sc = soup_cookie_copy(pc);
9867 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
9870 soup_cookies_free(cf);
9873 void
9874 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
9876 GSList *cf;
9877 SoupCookie *ci;
9879 print_cookie("soup_cookie_jar_delete_cookie", c);
9881 if (cookies_enabled == 0)
9882 return;
9884 if (jar == NULL || c == NULL)
9885 return;
9887 /* find and remove from persistent jar */
9888 cf = soup_cookie_jar_all_cookies(p_cookiejar);
9890 for (;cf; cf = cf->next) {
9891 ci = cf->data;
9892 if (soup_cookie_equal(ci, c)) {
9893 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
9894 break;
9898 soup_cookies_free(cf);
9900 /* delete from session jar */
9901 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
9904 void
9905 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
9907 struct domain *d = NULL;
9908 SoupCookie *c;
9909 FILE *r_cookie_f;
9911 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
9912 jar, p_cookiejar, s_cookiejar);
9914 if (cookies_enabled == 0)
9915 return;
9917 /* see if we are up and running */
9918 if (p_cookiejar == NULL) {
9919 _soup_cookie_jar_add_cookie(jar, cookie);
9920 return;
9922 /* disallow p_cookiejar adds, shouldn't happen */
9923 if (jar == p_cookiejar)
9924 return;
9926 /* sanity */
9927 if (jar == NULL || cookie == NULL)
9928 return;
9930 if (enable_cookie_whitelist &&
9931 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
9932 blocked_cookies++;
9933 DNPRINTF(XT_D_COOKIE,
9934 "soup_cookie_jar_add_cookie: reject %s\n",
9935 cookie->domain);
9936 if (save_rejected_cookies) {
9937 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
9938 show_oops(NULL, "can't open reject cookie file");
9939 return;
9941 fseek(r_cookie_f, 0, SEEK_END);
9942 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
9943 cookie->http_only ? "#HttpOnly_" : "",
9944 cookie->domain,
9945 *cookie->domain == '.' ? "TRUE" : "FALSE",
9946 cookie->path,
9947 cookie->secure ? "TRUE" : "FALSE",
9948 cookie->expires ?
9949 (gulong)soup_date_to_time_t(cookie->expires) :
9951 cookie->name,
9952 cookie->value);
9953 fflush(r_cookie_f);
9954 fclose(r_cookie_f);
9956 if (!allow_volatile_cookies)
9957 return;
9960 if (cookie->expires == NULL && session_timeout) {
9961 soup_cookie_set_expires(cookie,
9962 soup_date_new_from_now(session_timeout));
9963 print_cookie("modified add cookie", cookie);
9966 /* see if we are white listed for persistence */
9967 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
9968 /* add to persistent jar */
9969 c = soup_cookie_copy(cookie);
9970 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
9971 _soup_cookie_jar_add_cookie(p_cookiejar, c);
9974 /* add to session jar */
9975 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
9976 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
9979 void
9980 setup_cookies(void)
9982 char file[PATH_MAX];
9984 set_hook((void *)&_soup_cookie_jar_add_cookie,
9985 "soup_cookie_jar_add_cookie");
9986 set_hook((void *)&_soup_cookie_jar_delete_cookie,
9987 "soup_cookie_jar_delete_cookie");
9989 if (cookies_enabled == 0)
9990 return;
9993 * the following code is intricate due to overriding several libsoup
9994 * functions.
9995 * do not alter order of these operations.
9998 /* rejected cookies */
9999 if (save_rejected_cookies)
10000 snprintf(rc_fname, sizeof file, "%s/%s", work_dir,
10001 XT_REJECT_FILE);
10003 /* persistent cookies */
10004 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
10005 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
10007 /* session cookies */
10008 s_cookiejar = soup_cookie_jar_new();
10009 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
10010 cookie_policy, (void *)NULL);
10011 transfer_cookies();
10013 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
10016 void
10017 setup_proxy(char *uri)
10019 if (proxy_uri) {
10020 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
10021 soup_uri_free(proxy_uri);
10022 proxy_uri = NULL;
10024 if (http_proxy) {
10025 if (http_proxy != uri) {
10026 g_free(http_proxy);
10027 http_proxy = NULL;
10031 if (uri) {
10032 http_proxy = g_strdup(uri);
10033 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
10034 proxy_uri = soup_uri_new(http_proxy);
10035 if (!(proxy_uri == NULL || !SOUP_URI_VALID_FOR_HTTP(proxy_uri)))
10036 g_object_set(session, "proxy-uri", proxy_uri,
10037 (char *)NULL);
10042 set_http_proxy(char *proxy)
10044 SoupURI *uri;
10046 if (proxy == NULL)
10047 return (1);
10049 /* see if we need to clear it instead */
10050 if (strlen(proxy) == 0) {
10051 setup_proxy(NULL);
10052 return (0);
10055 uri = soup_uri_new(proxy);
10056 if (uri == NULL || !SOUP_URI_VALID_FOR_HTTP(uri))
10057 return (1);
10059 setup_proxy(proxy);
10061 soup_uri_free(uri);
10063 return (0);
10067 send_cmd_to_socket(char *cmd)
10069 int s, len, rv = 1;
10070 struct sockaddr_un sa;
10072 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
10073 warnx("%s: socket", __func__);
10074 return (rv);
10077 sa.sun_family = AF_UNIX;
10078 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
10079 work_dir, XT_SOCKET_FILE);
10080 len = SUN_LEN(&sa);
10082 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
10083 warnx("%s: connect", __func__);
10084 goto done;
10087 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
10088 warnx("%s: send", __func__);
10089 goto done;
10092 rv = 0;
10093 done:
10094 close(s);
10095 return (rv);
10098 gboolean
10099 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
10101 int s, n;
10102 char str[XT_MAX_URL_LENGTH];
10103 socklen_t t = sizeof(struct sockaddr_un);
10104 struct sockaddr_un sa;
10105 struct passwd *p;
10106 uid_t uid;
10107 gid_t gid;
10108 struct tab *tt;
10109 gint fd = g_io_channel_unix_get_fd(source);
10111 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
10112 warn("accept");
10113 return (FALSE);
10116 if (getpeereid(s, &uid, &gid) == -1) {
10117 warn("getpeereid");
10118 return (FALSE);
10120 if (uid != getuid() || gid != getgid()) {
10121 warnx("unauthorized user");
10122 return (FALSE);
10125 p = getpwuid(uid);
10126 if (p == NULL) {
10127 warnx("not a valid user");
10128 return (FALSE);
10131 n = recv(s, str, sizeof(str), 0);
10132 if (n <= 0)
10133 return (TRUE);
10135 tt = TAILQ_LAST(&tabs, tab_list);
10136 cmd_execute(tt, str);
10137 return (TRUE);
10141 is_running(void)
10143 int s, len, rv = 1;
10144 struct sockaddr_un sa;
10146 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
10147 warn("is_running: socket");
10148 return (-1);
10151 sa.sun_family = AF_UNIX;
10152 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
10153 work_dir, XT_SOCKET_FILE);
10154 len = SUN_LEN(&sa);
10156 /* connect to see if there is a listener */
10157 if (connect(s, (struct sockaddr *)&sa, len) == -1)
10158 rv = 0; /* not running */
10159 else
10160 rv = 1; /* already running */
10162 close(s);
10164 return (rv);
10168 build_socket(void)
10170 int s, len;
10171 struct sockaddr_un sa;
10173 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
10174 warn("build_socket: socket");
10175 return (-1);
10178 sa.sun_family = AF_UNIX;
10179 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
10180 work_dir, XT_SOCKET_FILE);
10181 len = SUN_LEN(&sa);
10183 /* connect to see if there is a listener */
10184 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
10185 /* no listener so we will */
10186 unlink(sa.sun_path);
10188 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
10189 warn("build_socket: bind");
10190 goto done;
10193 if (listen(s, 1) == -1) {
10194 warn("build_socket: listen");
10195 goto done;
10198 return (s);
10201 done:
10202 close(s);
10203 return (-1);
10206 gboolean
10207 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
10208 GtkTreeIter *iter, struct tab *t)
10210 gchar *value;
10212 gtk_tree_model_get(model, iter, 0, &value, -1);
10213 load_uri(t, value);
10214 g_free(value);
10216 return (FALSE);
10219 gboolean
10220 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
10221 GtkTreeIter *iter, struct tab *t)
10223 gchar *value;
10225 gtk_tree_model_get(model, iter, 0, &value, -1);
10226 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
10227 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
10228 g_free(value);
10230 return (TRUE);
10233 void
10234 completion_add_uri(const gchar *uri)
10236 GtkTreeIter iter;
10238 /* add uri to list_store */
10239 gtk_list_store_append(completion_model, &iter);
10240 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
10243 gboolean
10244 completion_match(GtkEntryCompletion *completion, const gchar *key,
10245 GtkTreeIter *iter, gpointer user_data)
10247 gchar *value;
10248 gboolean match = FALSE;
10250 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
10251 -1);
10253 if (value == NULL)
10254 return FALSE;
10256 match = match_uri(value, key);
10258 g_free(value);
10259 return (match);
10262 void
10263 completion_add(struct tab *t)
10265 /* enable completion for tab */
10266 t->completion = gtk_entry_completion_new();
10267 gtk_entry_completion_set_text_column(t->completion, 0);
10268 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
10269 gtk_entry_completion_set_model(t->completion,
10270 GTK_TREE_MODEL(completion_model));
10271 gtk_entry_completion_set_match_func(t->completion, completion_match,
10272 NULL, NULL);
10273 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
10274 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
10275 g_signal_connect(G_OBJECT (t->completion), "match-selected",
10276 G_CALLBACK(completion_select_cb), t);
10277 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
10278 G_CALLBACK(completion_hover_cb), t);
10281 void
10282 xxx_dir(char *dir)
10284 struct stat sb;
10286 if (stat(dir, &sb)) {
10287 if (mkdir(dir, S_IRWXU) == -1)
10288 err(1, "mkdir %s", dir);
10289 if (stat(dir, &sb))
10290 err(1, "stat %s", dir);
10292 if (S_ISDIR(sb.st_mode) == 0)
10293 errx(1, "%s not a dir", dir);
10294 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
10295 warnx("fixing invalid permissions on %s", dir);
10296 if (chmod(dir, S_IRWXU) == -1)
10297 err(1, "chmod %s", dir);
10301 void
10302 usage(void)
10304 fprintf(stderr,
10305 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
10306 exit(0);
10309 GStaticRecMutex my_gdk_mtx = G_STATIC_REC_MUTEX_INIT;
10310 volatile int mtx_depth;
10311 int mtx_complain;
10314 * The linux flash plugin violates the gdk locking mechanism.
10315 * Work around the issue by using a recursive mutex with some match applied
10316 * to see if we hit a buggy condition.
10318 * The following code is painful so just don't read it. It really doesn't
10319 * make much sense but seems to work.
10321 void
10322 mtx_lock(void)
10324 g_static_rec_mutex_lock(&my_gdk_mtx);
10325 mtx_depth++;
10327 if (mtx_depth <= 0) {
10328 /* should not happen */
10329 show_oops(NULL, "negative mutex locking bug, trying to "
10330 "correct");
10331 fprintf(stderr, "negative mutex locking bug, trying to "
10332 "correct\n");
10333 g_static_rec_mutex_unlock_full(&my_gdk_mtx);
10334 g_static_rec_mutex_lock(&my_gdk_mtx);
10335 mtx_depth = 1;
10336 return;
10339 if (mtx_depth != 1) {
10340 /* decrease mutext depth to 1 */
10341 do {
10342 g_static_rec_mutex_unlock(&my_gdk_mtx);
10343 mtx_depth--;
10344 } while (mtx_depth > 1);
10348 void
10349 mtx_unlock(void)
10351 guint x;
10353 /* if mutex depth isn't 1 then something went bad */
10354 if (mtx_depth != 1) {
10355 x = g_static_rec_mutex_unlock_full(&my_gdk_mtx);
10356 if (x != 1) {
10357 /* should not happen */
10358 show_oops(NULL, "mutex unlocking bug, trying to "
10359 "correct");
10360 fprintf(stderr, "mutex unlocking bug, trying to "
10361 "correct\n");
10363 mtx_depth = 0;
10364 if (mtx_complain == 0) {
10365 show_oops(NULL, "buggy mutex implementation detected, "
10366 "work around implemented");
10367 fprintf(stderr, "buggy mutex implementation detected, "
10368 "work around implemented");
10369 mtx_complain = 1;
10371 return;
10374 mtx_depth--;
10375 g_static_rec_mutex_unlock(&my_gdk_mtx);
10379 main(int argc, char *argv[])
10381 struct stat sb;
10382 int c, s, optn = 0, opte = 0, focus = 1;
10383 char conf[PATH_MAX] = { '\0' };
10384 char file[PATH_MAX];
10385 char *env_proxy = NULL;
10386 char *cmd = NULL;
10387 FILE *f = NULL;
10388 struct karg a;
10389 struct sigaction sact;
10390 GIOChannel *channel;
10391 struct rlimit rlp;
10393 start_argv = argv;
10395 /* prepare gtk */
10396 #ifdef USE_THREADS
10397 g_thread_init(NULL);
10398 gdk_threads_set_lock_functions(mtx_lock, mtx_unlock);
10399 gdk_threads_init();
10400 gdk_threads_enter();
10402 gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
10403 #endif
10404 gtk_init(&argc, &argv);
10406 gnutls_global_init();
10408 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
10410 RB_INIT(&hl);
10411 RB_INIT(&js_wl);
10412 RB_INIT(&pl_wl);
10413 RB_INIT(&downloads);
10415 TAILQ_INIT(&sessions);
10416 TAILQ_INIT(&tabs);
10417 TAILQ_INIT(&mtl);
10418 TAILQ_INIT(&aliases);
10419 TAILQ_INIT(&undos);
10420 TAILQ_INIT(&kbl);
10421 TAILQ_INIT(&spl);
10422 TAILQ_INIT(&chl);
10423 TAILQ_INIT(&shl);
10425 /* fiddle with ulimits */
10426 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10427 warn("getrlimit");
10428 else {
10429 /* just use them all */
10430 rlp.rlim_cur = rlp.rlim_max;
10431 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
10432 warn("setrlimit");
10433 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1)
10434 warn("getrlimit");
10435 else if (rlp.rlim_cur <= 256)
10436 startpage_add("%s requires at least 256 file "
10437 "descriptors, currently it has up to %d available",
10438 __progname, rlp.rlim_cur);
10441 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
10442 switch (c) {
10443 case 'S':
10444 show_url = 0;
10445 break;
10446 case 'T':
10447 show_tabs = 0;
10448 break;
10449 case 'V':
10450 errx(0 , "Version: %s", version);
10451 break;
10452 case 'f':
10453 strlcpy(conf, optarg, sizeof(conf));
10454 break;
10455 case 's':
10456 strlcpy(named_session, optarg, sizeof(named_session));
10457 break;
10458 case 't':
10459 tabless = 1;
10460 break;
10461 case 'n':
10462 optn = 1;
10463 break;
10464 case 'e':
10465 opte = 1;
10466 break;
10467 default:
10468 usage();
10469 /* NOTREACHED */
10472 argc -= optind;
10473 argv += optind;
10475 init_keybindings();
10477 /* generate session keys for xtp pages */
10478 generate_xtp_session_key(&dl_session_key);
10479 generate_xtp_session_key(&hl_session_key);
10480 generate_xtp_session_key(&cl_session_key);
10481 generate_xtp_session_key(&fl_session_key);
10483 /* signals */
10484 bzero(&sact, sizeof(sact));
10485 sigemptyset(&sact.sa_mask);
10486 sact.sa_handler = sigchild;
10487 sact.sa_flags = SA_NOCLDSTOP;
10488 sigaction(SIGCHLD, &sact, NULL);
10490 /* set download dir */
10491 pwd = getpwuid(getuid());
10492 if (pwd == NULL)
10493 errx(1, "invalid user %d", getuid());
10494 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
10496 /* compile buffer command regexes */
10497 buffercmd_init();
10499 /* set default string settings */
10500 home = g_strdup("https://www.cyphertite.com");
10501 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
10502 resource_dir = g_strdup("/usr/local/share/xxxterm/");
10503 strlcpy(runtime_settings, "runtime", sizeof runtime_settings);
10504 cmd_font_name = g_strdup("monospace normal 9");
10505 oops_font_name = g_strdup("monospace normal 9");
10506 statusbar_font_name = g_strdup("monospace normal 9");
10507 tabbar_font_name = g_strdup("monospace normal 9");
10508 statusbar_elems = g_strdup("BP");
10509 encoding = g_strdup("ISO-8859-1");
10511 /* read config file */
10512 if (strlen(conf) == 0)
10513 snprintf(conf, sizeof conf, "%s/.%s",
10514 pwd->pw_dir, XT_CONF_FILE);
10515 config_parse(conf, 0);
10517 /* init fonts */
10518 cmd_font = pango_font_description_from_string(cmd_font_name);
10519 oops_font = pango_font_description_from_string(oops_font_name);
10520 statusbar_font = pango_font_description_from_string(statusbar_font_name);
10521 tabbar_font = pango_font_description_from_string(tabbar_font_name);
10523 /* working directory */
10524 if (strlen(work_dir) == 0)
10525 snprintf(work_dir, sizeof work_dir, "%s/%s",
10526 pwd->pw_dir, XT_DIR);
10527 xxx_dir(work_dir);
10529 /* icon cache dir */
10530 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
10531 xxx_dir(cache_dir);
10533 /* certs dir */
10534 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
10535 xxx_dir(certs_dir);
10537 /* sessions dir */
10538 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
10539 work_dir, XT_SESSIONS_DIR);
10540 xxx_dir(sessions_dir);
10542 /* runtime settings that can override config file */
10543 if (runtime_settings[0] != '\0')
10544 config_parse(runtime_settings, 1);
10546 /* download dir */
10547 if (!strcmp(download_dir, pwd->pw_dir))
10548 strlcat(download_dir, "/downloads", sizeof download_dir);
10549 xxx_dir(download_dir);
10551 /* favorites file */
10552 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
10553 if (stat(file, &sb)) {
10554 warnx("favorites file doesn't exist, creating it");
10555 if ((f = fopen(file, "w")) == NULL)
10556 err(1, "favorites");
10557 fclose(f);
10560 /* quickmarks file */
10561 snprintf(file, sizeof file, "%s/%s", work_dir, XT_QMARKS_FILE);
10562 if (stat(file, &sb)) {
10563 warnx("quickmarks file doesn't exist, creating it");
10564 if ((f = fopen(file, "w")) == NULL)
10565 err(1, "quickmarks");
10566 fclose(f);
10569 /* search history */
10570 if (history_autosave) {
10571 snprintf(search_file, sizeof search_file, "%s/%s",
10572 work_dir, XT_SEARCH_FILE);
10573 if (stat(search_file, &sb)) {
10574 warnx("search history file doesn't exist, creating it");
10575 if ((f = fopen(search_file, "w")) == NULL)
10576 err(1, "search_history");
10577 fclose(f);
10579 history_read(&shl, search_file, &search_history_count);
10582 /* command history */
10583 if (history_autosave) {
10584 snprintf(command_file, sizeof command_file, "%s/%s",
10585 work_dir, XT_COMMAND_FILE);
10586 if (stat(command_file, &sb)) {
10587 warnx("command history file doesn't exist, creating it");
10588 if ((f = fopen(command_file, "w")) == NULL)
10589 err(1, "command_history");
10590 fclose(f);
10592 history_read(&chl, command_file, &cmd_history_count);
10595 /* cookies */
10596 session = webkit_get_default_session();
10597 setup_cookies();
10599 /* certs */
10600 if (ssl_ca_file) {
10601 if (stat(ssl_ca_file, &sb)) {
10602 warnx("no CA file: %s", ssl_ca_file);
10603 g_free(ssl_ca_file);
10604 ssl_ca_file = NULL;
10605 } else
10606 g_object_set(session,
10607 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
10608 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
10609 (void *)NULL);
10612 /* guess_search regex */
10613 if (url_regex == NULL)
10614 url_regex = g_strdup(XT_URL_REGEX);
10615 if (url_regex)
10616 if (regcomp(&url_re, url_regex, REG_EXTENDED | REG_NOSUB))
10617 startpage_add("invalid url regex %s", url_regex);
10619 /* proxy */
10620 env_proxy = getenv("http_proxy");
10621 if (env_proxy)
10622 setup_proxy(env_proxy);
10623 else
10624 setup_proxy(http_proxy);
10626 if (opte) {
10627 send_cmd_to_socket(argv[0]);
10628 exit(0);
10631 /* set some connection parameters */
10632 g_object_set(session, "max-conns", max_connections, (char *)NULL);
10633 g_object_set(session, "max-conns-per-host", max_host_connections,
10634 (char *)NULL);
10636 /* see if there is already an xxxterm running */
10637 if (single_instance && is_running()) {
10638 optn = 1;
10639 warnx("already running");
10642 if (optn) {
10643 while (argc) {
10644 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
10645 send_cmd_to_socket(cmd);
10646 if (cmd)
10647 g_free(cmd);
10649 argc--;
10650 argv++;
10652 exit(0);
10655 /* uri completion */
10656 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
10658 /* buffers */
10659 buffers_store = gtk_list_store_new
10660 (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING);
10662 qmarks_load();
10664 /* go graphical */
10665 create_canvas();
10666 notebook_tab_set_visibility();
10668 if (save_global_history)
10669 restore_global_history();
10671 /* restore session list */
10672 restore_sessions_list();
10674 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
10675 restore_saved_tabs();
10676 else {
10677 a.s = named_session;
10678 a.i = XT_SES_DONOTHING;
10679 open_tabs(NULL, &a);
10682 /* see if we have an exception */
10683 if (!TAILQ_EMPTY(&spl)) {
10684 create_new_tab("about:startpage", NULL, focus, -1);
10685 focus = 0;
10688 while (argc) {
10689 create_new_tab(argv[0], NULL, focus, -1);
10690 focus = 0;
10692 argc--;
10693 argv++;
10696 if (TAILQ_EMPTY(&tabs))
10697 create_new_tab(home, NULL, 1, -1);
10699 if (enable_socket)
10700 if ((s = build_socket()) != -1) {
10701 channel = g_io_channel_unix_new(s);
10702 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
10705 gtk_main();
10707 #ifdef USE_THREADS
10708 gdk_threads_leave();
10709 g_static_rec_mutex_unlock_full(&my_gdk_mtx); /* just in case */
10710 #endif
10712 gnutls_global_deinit();
10714 if (url_regex)
10715 regfree(&url_re);
10717 return (0);