add about: support in the address bar. Now one can type like about:set
[xxxterm.git] / xxxterm.c
blobf07ce2f83f24f22cdc441a6251fa3e6a33ef0613
1 /* $xxxterm$ */
2 /*
3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 * TODO:
23 * inverse color browsing
24 * favs
25 * - store in sqlite
26 * multi letter commands
27 * pre and post counts for commands
28 * autocompletion on various inputs
29 * create privacy browsing
30 * - encrypted local data
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <err.h>
36 #include <pwd.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <pthread.h>
40 #include <dlfcn.h>
41 #include <errno.h>
42 #include <signal.h>
43 #include <libgen.h>
44 #include <ctype.h>
46 #include <sys/types.h>
47 #include <sys/wait.h>
48 #if defined(__linux__)
49 #include "linux/util.h"
50 #include "linux/tree.h"
51 #elif defined(__FreeBSD__)
52 #include <libutil.h>
53 #include "freebsd/util.h"
54 #include <sys/tree.h>
55 #else /* OpenBSD */
56 #include <util.h>
57 #include <sys/tree.h>
58 #endif
59 #include <sys/queue.h>
60 #include <sys/stat.h>
61 #include <sys/socket.h>
62 #include <sys/un.h>
64 #include <gtk/gtk.h>
65 #include <gdk/gdkkeysyms.h>
66 #include <webkit/webkit.h>
67 #include <libsoup/soup.h>
68 #include <gnutls/gnutls.h>
69 #include <JavaScriptCore/JavaScript.h>
70 #include <gnutls/x509.h>
72 #include "javascript.h"
75 javascript.h borrowed from vimprobable2 under the following license:
77 Copyright (c) 2009 Leon Winter
78 Copyright (c) 2009 Hannes Schueller
79 Copyright (c) 2009 Matto Fransen
81 Permission is hereby granted, free of charge, to any person obtaining a copy
82 of this software and associated documentation files (the "Software"), to deal
83 in the Software without restriction, including without limitation the rights
84 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
85 copies of the Software, and to permit persons to whom the Software is
86 furnished to do so, subject to the following conditions:
88 The above copyright notice and this permission notice shall be included in
89 all copies or substantial portions of the Software.
91 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
92 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
93 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
94 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
95 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
96 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
97 THE SOFTWARE.
100 static char *version = "$xxxterm$";
102 /* hooked functions */
103 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
104 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
105 SoupCookie *);
107 /*#define XT_DEBUG*/
108 #ifdef XT_DEBUG
109 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
110 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
111 #define XT_D_MOVE 0x0001
112 #define XT_D_KEY 0x0002
113 #define XT_D_TAB 0x0004
114 #define XT_D_URL 0x0008
115 #define XT_D_CMD 0x0010
116 #define XT_D_NAV 0x0020
117 #define XT_D_DOWNLOAD 0x0040
118 #define XT_D_CONFIG 0x0080
119 #define XT_D_JS 0x0100
120 #define XT_D_FAVORITE 0x0200
121 #define XT_D_PRINTING 0x0400
122 #define XT_D_COOKIE 0x0800
123 #define XT_D_KEYBINDING 0x1000
124 u_int32_t swm_debug = 0
125 | XT_D_MOVE
126 | XT_D_KEY
127 | XT_D_TAB
128 | XT_D_URL
129 | XT_D_CMD
130 | XT_D_NAV
131 | XT_D_DOWNLOAD
132 | XT_D_CONFIG
133 | XT_D_JS
134 | XT_D_FAVORITE
135 | XT_D_PRINTING
136 | XT_D_COOKIE
137 | XT_D_KEYBINDING
139 #else
140 #define DPRINTF(x...)
141 #define DNPRINTF(n,x...)
142 #endif
144 #define LENGTH(x) (sizeof x / sizeof x[0])
145 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
146 ~(GDK_BUTTON1_MASK) & \
147 ~(GDK_BUTTON2_MASK) & \
148 ~(GDK_BUTTON3_MASK) & \
149 ~(GDK_BUTTON4_MASK) & \
150 ~(GDK_BUTTON5_MASK))
152 char *icons[] = {
153 "xxxtermicon16.png",
154 "xxxtermicon32.png",
155 "xxxtermicon48.png",
156 "xxxtermicon64.png",
157 "xxxtermicon128.png"
160 struct tab {
161 TAILQ_ENTRY(tab) entry;
162 GtkWidget *vbox;
163 GtkWidget *tab_content;
164 GtkWidget *label;
165 GtkWidget *spinner;
166 GtkWidget *uri_entry;
167 GtkWidget *search_entry;
168 GtkWidget *toolbar;
169 GtkWidget *browser_win;
170 GtkWidget *statusbar;
171 GtkWidget *cmd;
172 GtkWidget *oops;
173 GtkWidget *backward;
174 GtkWidget *forward;
175 GtkWidget *stop;
176 GtkWidget *js_toggle;
177 guint tab_id;
178 WebKitWebView *wv;
180 WebKitWebHistoryItem *item;
181 WebKitWebBackForwardList *bfl;
183 /* favicon */
184 WebKitNetworkRequest *icon_request;
185 WebKitDownload *icon_download;
186 GdkPixbuf *icon_pixbuf;
187 gchar *icon_dest_uri;
189 /* adjustments for browser */
190 GtkScrollbar *sb_h;
191 GtkScrollbar *sb_v;
192 GtkAdjustment *adjust_h;
193 GtkAdjustment *adjust_v;
195 /* flags */
196 int focus_wv;
197 int ctrl_click;
198 gchar *status;
199 int xtp_meaning; /* identifies dls/favorites */
201 /* hints */
202 int hints_on;
203 int hint_mode;
204 #define XT_HINT_NONE (0)
205 #define XT_HINT_NUMERICAL (1)
206 #define XT_HINT_ALPHANUM (2)
207 char hint_buf[128];
208 char hint_num[128];
210 /* search */
211 char *search_text;
212 int search_forward;
214 /* settings */
215 WebKitWebSettings *settings;
216 int font_size;
217 gchar *user_agent;
219 TAILQ_HEAD(tab_list, tab);
221 struct history {
222 RB_ENTRY(history) entry;
223 const gchar *uri;
224 const gchar *title;
226 RB_HEAD(history_list, history);
228 struct download {
229 RB_ENTRY(download) entry;
230 int id;
231 WebKitDownload *download;
232 struct tab *tab;
234 RB_HEAD(download_list, download);
236 struct domain {
237 RB_ENTRY(domain) entry;
238 gchar *d;
239 int handy; /* app use */
241 RB_HEAD(domain_list, domain);
243 struct undo {
244 TAILQ_ENTRY(undo) entry;
245 gchar *uri;
246 GList *history;
247 int back; /* Keeps track of how many back
248 * history items there are. */
250 TAILQ_HEAD(undo_tailq, undo);
252 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
253 int next_download_id = 1;
255 struct karg {
256 int i;
257 char *s;
260 /* defines */
261 #define XT_NAME ("XXXTerm")
262 #define XT_DIR (".xxxterm")
263 #define XT_CACHE_DIR ("cache")
264 #define XT_CERT_DIR ("certs/")
265 #define XT_SESSIONS_DIR ("sessions/")
266 #define XT_CONF_FILE ("xxxterm.conf")
267 #define XT_FAVS_FILE ("favorites")
268 #define XT_SAVED_TABS_FILE ("main_session")
269 #define XT_RESTART_TABS_FILE ("restart_tabs")
270 #define XT_SOCKET_FILE ("socket")
271 #define XT_HISTORY_FILE ("history")
272 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
273 #define XT_CB_HANDLED (TRUE)
274 #define XT_CB_PASSTHROUGH (FALSE)
275 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
276 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>"
277 #define XT_DLMAN_REFRESH "10"
278 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
279 "td {overflow: hidden;" \
280 " padding: 2px 2px 2px 2px;" \
281 " border: 1px solid black}\n" \
282 "tr:hover {background: #ffff99 ;}\n" \
283 "th {background-color: #cccccc;" \
284 " border: 1px solid black}" \
285 "table {border-spacing: 0; " \
286 " width: 90%%;" \
287 " border: 1px black solid;}\n" \
288 ".progress-outer{" \
289 " border: 1px solid black;" \
290 " height: 8px;" \
291 " width: 90%%;}" \
292 ".progress-inner{" \
293 " float: left;" \
294 " height: 8px;" \
295 " background: green;}" \
296 ".dlstatus{" \
297 " font-size: small;" \
298 " text-align: center;}" \
299 "</style>\n\n"
300 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
301 #define XT_MAX_UNDO_CLOSE_TAB (32)
302 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
303 #define XT_PRINT_EXTRA_MARGIN 10
305 /* file sizes */
306 #define SZ_KB ((uint64_t) 1024)
307 #define SZ_MB (SZ_KB * SZ_KB)
308 #define SZ_GB (SZ_KB * SZ_KB * SZ_KB)
309 #define SZ_TB (SZ_KB * SZ_KB * SZ_KB * SZ_KB)
312 * xxxterm "protocol" (xtp)
313 * We use this for managing stuff like downloads and favorites. They
314 * make magical HTML pages in memory which have xxxt:// links in order
315 * to communicate with xxxterm's internals. These links take the format:
316 * xxxt://class/session_key/action/arg
318 * Don't begin xtp class/actions as 0. atoi returns that on error.
320 * Typically we have not put addition of items in this framework, as
321 * adding items is either done via an ex-command or via a keybinding instead.
324 #define XT_XTP_STR "xxxt://"
326 /* XTP classes (xxxt://<class>) */
327 #define XT_XTP_DL 1 /* downloads */
328 #define XT_XTP_HL 2 /* history */
329 #define XT_XTP_CL 3 /* cookies */
330 #define XT_XTP_FL 4 /* favorites */
332 /* XTP download actions */
333 #define XT_XTP_DL_LIST 1
334 #define XT_XTP_DL_CANCEL 2
335 #define XT_XTP_DL_REMOVE 3
337 /* XTP history actions */
338 #define XT_XTP_HL_LIST 1
339 #define XT_XTP_HL_REMOVE 2
341 /* XTP cookie actions */
342 #define XT_XTP_CL_LIST 1
343 #define XT_XTP_CL_REMOVE 2
345 /* XTP cookie actions */
346 #define XT_XTP_FL_LIST 1
347 #define XT_XTP_FL_REMOVE 2
349 /* xtp tab meanings - identifies which tabs have xtp pages in */
350 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
351 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
352 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
353 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
354 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
356 /* actions */
357 #define XT_MOVE_INVALID (0)
358 #define XT_MOVE_DOWN (1)
359 #define XT_MOVE_UP (2)
360 #define XT_MOVE_BOTTOM (3)
361 #define XT_MOVE_TOP (4)
362 #define XT_MOVE_PAGEDOWN (5)
363 #define XT_MOVE_PAGEUP (6)
364 #define XT_MOVE_HALFDOWN (7)
365 #define XT_MOVE_HALFUP (8)
366 #define XT_MOVE_LEFT (9)
367 #define XT_MOVE_FARLEFT (10)
368 #define XT_MOVE_RIGHT (11)
369 #define XT_MOVE_FARRIGHT (12)
371 #define XT_TAB_LAST (-4)
372 #define XT_TAB_FIRST (-3)
373 #define XT_TAB_PREV (-2)
374 #define XT_TAB_NEXT (-1)
375 #define XT_TAB_INVALID (0)
376 #define XT_TAB_NEW (1)
377 #define XT_TAB_DELETE (2)
378 #define XT_TAB_DELQUIT (3)
379 #define XT_TAB_OPEN (4)
380 #define XT_TAB_UNDO_CLOSE (5)
381 #define XT_TAB_SHOW (6)
382 #define XT_TAB_HIDE (7)
384 #define XT_NAV_INVALID (0)
385 #define XT_NAV_BACK (1)
386 #define XT_NAV_FORWARD (2)
387 #define XT_NAV_RELOAD (3)
388 #define XT_NAV_RELOAD_CACHE (4)
390 #define XT_FOCUS_INVALID (0)
391 #define XT_FOCUS_URI (1)
392 #define XT_FOCUS_SEARCH (2)
394 #define XT_SEARCH_INVALID (0)
395 #define XT_SEARCH_NEXT (1)
396 #define XT_SEARCH_PREV (2)
398 #define XT_PASTE_CURRENT_TAB (0)
399 #define XT_PASTE_NEW_TAB (1)
401 #define XT_FONT_SET (0)
403 #define XT_URL_SHOW (1)
404 #define XT_URL_HIDE (2)
406 #define XT_STATUSBAR_SHOW (1)
407 #define XT_STATUSBAR_HIDE (2)
409 #define XT_WL_TOGGLE (1<<0)
410 #define XT_WL_ENABLE (1<<1)
411 #define XT_WL_DISABLE (1<<2)
412 #define XT_WL_FQDN (1<<3) /* default */
413 #define XT_WL_TOPLEVEL (1<<4)
415 #define XT_CMD_OPEN (0)
416 #define XT_CMD_OPEN_CURRENT (1)
417 #define XT_CMD_TABNEW (2)
418 #define XT_CMD_TABNEW_CURRENT (3)
420 #define XT_STATUS_NOTHING (0)
421 #define XT_STATUS_LINK (1)
422 #define XT_STATUS_URI (2)
423 #define XT_STATUS_LOADING (3)
425 #define XT_SES_DONOTHING (0)
426 #define XT_SES_CLOSETABS (1)
428 #define XT_COOKIE_NORMAL (0)
429 #define XT_COOKIE_WHITELIST (1)
431 /* mime types */
432 struct mime_type {
433 char *mt_type;
434 char *mt_action;
435 int mt_default;
436 TAILQ_ENTRY(mime_type) entry;
438 TAILQ_HEAD(mime_type_list, mime_type);
440 /* uri aliases */
441 struct alias {
442 char *a_name;
443 char *a_uri;
444 TAILQ_ENTRY(alias) entry;
446 TAILQ_HEAD(alias_list, alias);
448 /* settings that require restart */
449 int tabless = 0; /* allow only 1 tab */
450 int enable_socket = 0;
451 int single_instance = 0; /* only allow one xxxterm to run */
452 int fancy_bar = 1; /* fancy toolbar */
453 int browser_mode = XT_COOKIE_NORMAL;
455 /* runtime settings */
456 int show_tabs = 1; /* show tabs on notebook */
457 int show_url = 1; /* show url toolbar on notebook */
458 int show_statusbar = 0; /* vimperator style status bar */
459 int ctrl_click_focus = 0; /* ctrl click gets focus */
460 int cookies_enabled = 1; /* enable cookies */
461 int read_only_cookies = 0; /* enable to not write cookies */
462 int enable_scripts = 1;
463 int enable_plugins = 0;
464 int default_font_size = 12;
465 int window_height = 768;
466 int window_width = 1024;
467 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
468 unsigned refresh_interval = 10; /* download refresh interval */
469 int enable_cookie_whitelist = 0;
470 int enable_js_whitelist = 0;
471 time_t session_timeout = 3600; /* cookie session timeout */
472 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
473 char *ssl_ca_file = NULL;
474 char *resource_dir = NULL;
475 gboolean ssl_strict_certs = FALSE;
476 int append_next = 1; /* append tab after current tab */
477 char *home = NULL;
478 char *search_string = NULL;
479 char *http_proxy = NULL;
480 char download_dir[PATH_MAX];
481 char runtime_settings[PATH_MAX]; /* override of settings */
482 int allow_volatile_cookies = 0;
483 int save_global_history = 0; /* save global history to disk */
484 char *user_agent = NULL;
485 int save_rejected_cookies = 0;
486 time_t session_autosave = 0;
487 int guess_search = 0;
489 struct settings;
490 struct key_binding;
491 int set_download_dir(struct settings *, char *);
492 int set_work_dir(struct settings *, char *);
493 int set_runtime_dir(struct settings *, char *);
494 int set_browser_mode(struct settings *, char *);
495 int set_cookie_policy(struct settings *, char *);
496 int add_alias(struct settings *, char *);
497 int add_mime_type(struct settings *, char *);
498 int add_cookie_wl(struct settings *, char *);
499 int add_js_wl(struct settings *, char *);
500 int add_kb(struct settings *, char *);
501 void button_set_stockid(GtkWidget *, char *);
502 GtkWidget * create_button(char *, char *, int);
504 char *get_browser_mode(struct settings *);
505 char *get_cookie_policy(struct settings *);
507 char *get_download_dir(struct settings *);
508 char *get_work_dir(struct settings *);
509 char *get_runtime_dir(struct settings *);
511 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
512 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
513 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
514 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
515 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
517 struct special {
518 int (*set)(struct settings *, char *);
519 char *(*get)(struct settings *);
520 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
523 struct special s_browser_mode = {
524 set_browser_mode,
525 get_browser_mode,
526 NULL
529 struct special s_cookie = {
530 set_cookie_policy,
531 get_cookie_policy,
532 NULL
535 struct special s_alias = {
536 add_alias,
537 NULL,
538 walk_alias
541 struct special s_mime = {
542 add_mime_type,
543 NULL,
544 walk_mime_type
547 struct special s_js = {
548 add_js_wl,
549 NULL,
550 walk_js_wl
553 struct special s_kb = {
554 add_kb,
555 NULL,
556 walk_kb
559 struct special s_cookie_wl = {
560 add_cookie_wl,
561 NULL,
562 walk_cookie_wl
565 struct special s_download_dir = {
566 set_download_dir,
567 get_download_dir,
568 NULL
571 struct special s_work_dir = {
572 set_work_dir,
573 get_work_dir,
574 NULL
577 struct settings {
578 char *name;
579 int type;
580 #define XT_S_INVALID (0)
581 #define XT_S_INT (1)
582 #define XT_S_STR (2)
583 uint32_t flags;
584 #define XT_SF_RESTART (1<<0)
585 #define XT_SF_RUNTIME (1<<1)
586 int *ival;
587 char **sval;
588 struct special *s;
589 } rs[] = {
590 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
591 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
592 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
593 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
594 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
595 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
596 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
597 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
598 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
599 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
600 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
601 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
602 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
603 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
604 { "home", XT_S_STR, 0, NULL, &home, NULL },
605 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
606 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
607 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
608 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
609 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
610 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
611 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
612 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
613 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
614 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
615 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
616 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
617 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
618 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
619 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
620 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
621 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
622 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
623 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
624 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
625 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
627 /* runtime settings */
628 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
629 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
630 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
631 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
632 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
635 int about(struct tab *, struct karg *);
636 int blank(struct tab *, struct karg *);
637 int cookie_show_wl(struct tab *, struct karg *);
638 int js_show_wl(struct tab *, struct karg *);
639 int help(struct tab *, struct karg *);
640 int set(struct tab *, struct karg *);
641 int stats(struct tab *, struct karg *);
642 int xtp_page_cl(struct tab *, struct karg *);
643 int xtp_page_dl(struct tab *, struct karg *);
644 int xtp_page_fl(struct tab *, struct karg *);
645 int xtp_page_hl(struct tab *, struct karg *);
647 #define XT_URI_ABOUT ("about:")
648 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
649 #define XT_URI_ABOUT_ABOUT ("about")
650 #define XT_URI_ABOUT_BLANK ("blank")
651 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
652 #define XT_URI_ABOUT_COOKIEWL ("cookiewl") /* NOT YET */
653 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
654 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
655 #define XT_URI_ABOUT_FAVORITES ("favorites")
656 #define XT_URI_ABOUT_HELP ("help")
657 #define XT_URI_ABOUT_HISTORY ("history")
658 #define XT_URI_ABOUT_JSWL ("jswl") /* NOT YET */
659 #define XT_URI_ABOUT_SET ("set")
660 #define XT_URI_ABOUT_STATS ("stats")
662 struct about_type {
663 char *name;
664 int (*func)(struct tab *, struct karg *);
665 } about_list[] = {
666 { XT_URI_ABOUT_ABOUT, about },
667 { XT_URI_ABOUT_BLANK, blank },
668 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
669 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
670 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
671 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
672 { XT_URI_ABOUT_HELP, help },
673 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
674 { XT_URI_ABOUT_JSWL, js_show_wl },
675 { XT_URI_ABOUT_SET, set },
676 { XT_URI_ABOUT_STATS, stats },
679 /* globals */
680 extern char *__progname;
681 char **start_argv;
682 struct passwd *pwd;
683 GtkWidget *main_window;
684 GtkNotebook *notebook;
685 GtkWidget *arrow, *abtn;
686 struct tab_list tabs;
687 struct history_list hl;
688 struct download_list downloads;
689 struct domain_list c_wl;
690 struct domain_list js_wl;
691 struct undo_tailq undos;
692 struct keybinding_list kbl;
693 int undo_count;
694 int updating_dl_tabs = 0;
695 int updating_hl_tabs = 0;
696 int updating_cl_tabs = 0;
697 int updating_fl_tabs = 0;
698 char *global_search;
699 uint64_t blocked_cookies = 0;
700 char named_session[PATH_MAX];
701 void update_favicon(struct tab *);
702 int icon_size_map(int);
704 void
705 sigchild(int sig)
707 int saved_errno, status;
708 pid_t pid;
710 saved_errno = errno;
712 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
713 if (pid == -1) {
714 if (errno == EINTR)
715 continue;
716 if (errno != ECHILD) {
718 clog_warn("sigchild: waitpid:");
721 break;
724 if (WIFEXITED(status)) {
725 if (WEXITSTATUS(status) != 0) {
727 clog_warnx("sigchild: child exit status: %d",
728 WEXITSTATUS(status));
731 } else {
733 clog_warnx("sigchild: child is terminated abnormally");
738 errno = saved_errno;
741 void
742 _load_webkit_string(struct tab *t, const char *str, gchar *title)
744 gchar *uri;
746 /* we set this to indicate we want to manually do navaction */
747 if (t->bfl)
748 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
749 webkit_web_view_load_string(t->wv, str, NULL, NULL, NULL);
751 if (title) {
752 uri = g_strdup_printf("%s%s", XT_URI_ABOUT, title);
753 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
754 g_free(uri);
758 void
759 load_webkit_string(struct tab *t, const char *str)
761 /* we set this to indicate we want to manually do navaction */
762 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
763 webkit_web_view_load_string(t->wv, str, NULL, NULL, NULL);
766 void
767 set_status(struct tab *t, gchar *s, int status)
769 gchar *type = NULL;
771 if (s == NULL)
772 return;
774 switch (status) {
775 case XT_STATUS_LOADING:
776 type = g_strdup_printf("Loading: %s", s);
777 s = type;
778 break;
779 case XT_STATUS_LINK:
780 type = g_strdup_printf("Link: %s", s);
781 if (!t->status)
782 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
783 s = type;
784 break;
785 case XT_STATUS_URI:
786 type = g_strdup_printf("%s", s);
787 if (!t->status) {
788 t->status = g_strdup(type);
790 s = type;
791 if (!t->status)
792 t->status = g_strdup(s);
793 break;
794 case XT_STATUS_NOTHING:
795 /* FALL THROUGH */
796 default:
797 break;
799 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
800 if (type)
801 g_free(type);
804 void
805 hide_oops(struct tab *t)
807 gtk_widget_hide(t->oops);
810 void
811 hide_cmd(struct tab *t)
813 gtk_widget_hide(t->cmd);
816 void
817 show_cmd(struct tab *t)
819 gtk_widget_hide(t->oops);
820 gtk_widget_show(t->cmd);
823 void
824 show_oops(struct tab *t, const char *fmt, ...)
826 va_list ap;
827 char *msg;
829 if (fmt == NULL)
830 return;
832 va_start(ap, fmt);
833 if (vasprintf(&msg, fmt, ap) == -1)
834 errx(1, "show_oops failed");
835 va_end(ap);
837 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
838 gtk_widget_hide(t->cmd);
839 gtk_widget_show(t->oops);
842 /* XXX collapse with show_oops */
843 void
844 show_oops_s(const char *fmt, ...)
846 va_list ap;
847 char *msg;
848 struct tab *ti, *t = NULL;
850 if (fmt == NULL)
851 return;
853 TAILQ_FOREACH(ti, &tabs, entry)
854 if (ti->tab_id == gtk_notebook_current_page(notebook)) {
855 t = ti;
856 break;
858 if (t == NULL)
859 return;
861 va_start(ap, fmt);
862 if (vasprintf(&msg, fmt, ap) == -1)
863 errx(1, "show_oops_s failed");
864 va_end(ap);
866 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
867 gtk_widget_hide(t->cmd);
868 gtk_widget_show(t->oops);
871 char *
872 get_as_string(struct settings *s)
874 char *r = NULL;
876 if (s == NULL)
877 return (NULL);
879 if (s->s) {
880 if (s->s->get)
881 r = s->s->get(s);
882 else
883 warnx("get_as_string skip %s\n", s->name);
884 } else if (s->type == XT_S_INT)
885 r = g_strdup_printf("%d", *s->ival);
886 else if (s->type == XT_S_STR)
887 r = g_strdup(*s->sval);
888 else
889 r = g_strdup_printf("INVALID TYPE");
891 return (r);
894 void
895 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
897 int i;
898 char *s;
900 for (i = 0; i < LENGTH(rs); i++) {
901 if (rs[i].s && rs[i].s->walk)
902 rs[i].s->walk(&rs[i], cb, cb_args);
903 else {
904 s = get_as_string(&rs[i]);
905 cb(&rs[i], s, cb_args);
906 g_free(s);
912 set_browser_mode(struct settings *s, char *val)
914 if (!strcmp(val, "whitelist")) {
915 browser_mode = XT_COOKIE_WHITELIST;
916 allow_volatile_cookies = 0;
917 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
918 cookies_enabled = 1;
919 enable_cookie_whitelist = 1;
920 read_only_cookies = 0;
921 save_rejected_cookies = 0;
922 session_timeout = 3600;
923 enable_scripts = 0;
924 enable_js_whitelist = 1;
925 } else if (!strcmp(val, "normal")) {
926 browser_mode = XT_COOKIE_NORMAL;
927 allow_volatile_cookies = 0;
928 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
929 cookies_enabled = 1;
930 enable_cookie_whitelist = 0;
931 read_only_cookies = 0;
932 save_rejected_cookies = 0;
933 session_timeout = 3600;
934 enable_scripts = 1;
935 enable_js_whitelist = 0;
936 } else
937 return (1);
939 return (0);
942 char *
943 get_browser_mode(struct settings *s)
945 char *r = NULL;
947 if (browser_mode == XT_COOKIE_WHITELIST)
948 r = g_strdup("whitelist");
949 else if (browser_mode == XT_COOKIE_NORMAL)
950 r = g_strdup("normal");
951 else
952 return (NULL);
954 return (r);
958 set_cookie_policy(struct settings *s, char *val)
960 if (!strcmp(val, "no3rdparty"))
961 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
962 else if (!strcmp(val, "accept"))
963 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
964 else if (!strcmp(val, "reject"))
965 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
966 else
967 return (1);
969 return (0);
972 char *
973 get_cookie_policy(struct settings *s)
975 char *r = NULL;
977 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
978 r = g_strdup("no3rdparty");
979 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
980 r = g_strdup("accept");
981 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
982 r = g_strdup("reject");
983 else
984 return (NULL);
986 return (r);
989 char *
990 get_download_dir(struct settings *s)
992 if (download_dir[0] == '\0')
993 return (0);
994 return (g_strdup(download_dir));
998 set_download_dir(struct settings *s, char *val)
1000 if (val[0] == '~')
1001 snprintf(download_dir, sizeof download_dir, "%s/%s",
1002 pwd->pw_dir, &val[1]);
1003 else
1004 strlcpy(download_dir, val, sizeof download_dir);
1006 return (0);
1010 * Session IDs.
1011 * We use these to prevent people putting xxxt:// URLs on
1012 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1014 #define XT_XTP_SES_KEY_SZ 8
1015 #define XT_XTP_SES_KEY_HEX_FMT \
1016 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1017 char *dl_session_key; /* downloads */
1018 char *hl_session_key; /* history list */
1019 char *cl_session_key; /* cookie list */
1020 char *fl_session_key; /* favorites list */
1022 char work_dir[PATH_MAX];
1023 char certs_dir[PATH_MAX];
1024 char cache_dir[PATH_MAX];
1025 char sessions_dir[PATH_MAX];
1026 char cookie_file[PATH_MAX];
1027 SoupURI *proxy_uri = NULL;
1028 SoupSession *session;
1029 SoupCookieJar *s_cookiejar;
1030 SoupCookieJar *p_cookiejar;
1031 char rc_fname[PATH_MAX];
1033 struct mime_type_list mtl;
1034 struct alias_list aliases;
1036 /* protos */
1037 void create_new_tab(char *, struct undo *, int);
1038 void delete_tab(struct tab *);
1039 void adjustfont_webkit(struct tab *, int);
1040 int run_script(struct tab *, char *);
1041 int download_rb_cmp(struct download *, struct download *);
1044 history_rb_cmp(struct history *h1, struct history *h2)
1046 return (strcmp(h1->uri, h2->uri));
1048 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1051 domain_rb_cmp(struct domain *d1, struct domain *d2)
1053 return (strcmp(d1->d, d2->d));
1055 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1057 char *
1058 get_work_dir(struct settings *s)
1060 if (work_dir[0] == '\0')
1061 return (0);
1062 return (g_strdup(work_dir));
1066 set_work_dir(struct settings *s, char *val)
1068 if (val[0] == '~')
1069 snprintf(work_dir, sizeof work_dir, "%s/%s",
1070 pwd->pw_dir, &val[1]);
1071 else
1072 strlcpy(work_dir, val, sizeof work_dir);
1074 return (0);
1078 * generate a session key to secure xtp commands.
1079 * pass in a ptr to the key in question and it will
1080 * be modified in place.
1082 void
1083 generate_xtp_session_key(char **key)
1085 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1087 /* free old key */
1088 if (*key)
1089 g_free(*key);
1091 /* make a new one */
1092 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1093 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1094 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1095 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1097 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1101 * validate a xtp session key.
1102 * return 1 if OK
1105 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1107 if (strcmp(trusted, untrusted) != 0) {
1108 show_oops(t, "%s: xtp session key mismatch possible spoof",
1109 __func__);
1110 return (0);
1113 return (1);
1117 download_rb_cmp(struct download *e1, struct download *e2)
1119 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1121 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1123 struct valid_url_types {
1124 char *type;
1125 } vut[] = {
1126 { "http://" },
1127 { "https://" },
1128 { "ftp://" },
1129 { "file://" },
1130 { XT_XTP_STR },
1134 valid_url_type(char *url)
1136 int i;
1138 for (i = 0; i < LENGTH(vut); i++)
1139 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1140 return (0);
1142 return (1);
1145 void
1146 print_cookie(char *msg, SoupCookie *c)
1148 if (c == NULL)
1149 return;
1151 if (msg)
1152 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1153 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1154 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1155 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1156 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1157 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1158 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1159 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1160 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1161 DNPRINTF(XT_D_COOKIE, "====================================\n");
1164 void
1165 walk_alias(struct settings *s,
1166 void (*cb)(struct settings *, char *, void *), void *cb_args)
1168 struct alias *a;
1169 char *str;
1171 if (s == NULL || cb == NULL) {
1172 show_oops_s("walk_alias invalid parameters");
1173 return;
1176 TAILQ_FOREACH(a, &aliases, entry) {
1177 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1178 cb(s, str, cb_args);
1179 g_free(str);
1183 char *
1184 match_alias(char *url_in)
1186 struct alias *a;
1187 char *arg;
1188 char *url_out = NULL, *search, *enc_arg;
1190 search = g_strdup(url_in);
1191 arg = search;
1192 if (strsep(&arg, " \t") == NULL) {
1193 show_oops_s("match_alias: NULL URL");
1194 goto done;
1197 TAILQ_FOREACH(a, &aliases, entry) {
1198 if (!strcmp(search, a->a_name))
1199 break;
1202 if (a != NULL) {
1203 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1204 a->a_name);
1205 if (arg != NULL) {
1206 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1207 url_out = g_strdup_printf(a->a_uri, enc_arg);
1208 g_free(enc_arg);
1209 } else
1210 url_out = g_strdup(a->a_uri);
1212 done:
1213 g_free(search);
1214 return (url_out);
1217 char *
1218 guess_url_type(char *url_in)
1220 struct stat sb;
1221 char *url_out = NULL, *enc_search = NULL;
1223 url_out = match_alias(url_in);
1224 if (url_out != NULL)
1225 return (url_out);
1227 if (guess_search) {
1229 * If there is no dot nor slash in the string and it isn't a
1230 * path to a local file and doesn't resolves to an IP, assume
1231 * that the user wants to search for the string.
1234 if (strchr(url_in, '.') == NULL &&
1235 strchr(url_in, '/') == NULL &&
1236 stat(url_in, &sb) != 0 &&
1237 gethostbyname(url_in) == NULL) {
1239 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1240 url_out = g_strdup_printf(search_string, enc_search);
1241 g_free(enc_search);
1242 return (url_out);
1246 /* XXX not sure about this heuristic */
1247 if (stat(url_in, &sb) == 0)
1248 url_out = g_strdup_printf("file://%s", url_in);
1249 else
1250 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1252 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1254 return (url_out);
1257 void
1258 load_uri(struct tab *t, gchar *uri)
1260 struct karg args;
1261 gchar *newuri = NULL;
1262 int i;
1264 if (uri == NULL)
1265 return;
1267 /* Strip leading spaces. */
1268 while(*uri && isspace(*uri))
1269 uri++;
1271 if (strlen(uri) == 0) {
1272 blank(t, NULL);
1273 return;
1276 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1277 for (i = 0; i < LENGTH(about_list); i++)
1278 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1279 bzero(&args, sizeof args);
1280 about_list[i].func(t, &args);
1281 return;
1283 show_oops(t, "invalid about page");
1284 return;
1287 if (valid_url_type(uri)) {
1288 newuri = guess_url_type(uri);
1289 uri = newuri;
1292 set_status(t, (char *)uri, XT_STATUS_LOADING);
1293 webkit_web_view_load_uri(t->wv, uri);
1295 if (newuri)
1296 g_free(newuri);
1299 const gchar *
1300 get_uri(WebKitWebView *wv)
1302 WebKitWebFrame *frame;
1303 const gchar *uri;
1305 frame = webkit_web_view_get_main_frame(wv);
1306 uri = webkit_web_frame_get_uri(frame);
1308 if (uri && strlen(uri) > 0)
1309 return (uri);
1310 else
1311 return (NULL);
1315 add_alias(struct settings *s, char *line)
1317 char *l, *alias;
1318 struct alias *a = NULL;
1320 if (s == NULL || line == NULL) {
1321 show_oops_s("add_alias invalid parameters");
1322 return (1);
1325 l = line;
1326 a = g_malloc(sizeof(*a));
1328 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1329 show_oops_s("add_alias: incomplete alias definition");
1330 goto bad;
1332 if (strlen(alias) == 0 || strlen(l) == 0) {
1333 show_oops_s("add_alias: invalid alias definition");
1334 goto bad;
1337 a->a_name = g_strdup(alias);
1338 a->a_uri = g_strdup(l);
1340 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1342 TAILQ_INSERT_TAIL(&aliases, a, entry);
1344 return (0);
1345 bad:
1346 if (a)
1347 g_free(a);
1348 return (1);
1352 add_mime_type(struct settings *s, char *line)
1354 char *mime_type;
1355 char *l;
1356 struct mime_type *m = NULL;
1358 /* XXX this could be smarter */
1360 if (line == NULL) {
1361 show_oops_s("add_mime_type invalid parameters");
1362 return (1);
1365 l = line;
1366 m = g_malloc(sizeof(*m));
1368 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1369 show_oops_s("add_mime_type: invalid mime_type");
1370 goto bad;
1372 if (mime_type[strlen(mime_type) - 1] == '*') {
1373 mime_type[strlen(mime_type) - 1] = '\0';
1374 m->mt_default = 1;
1375 } else
1376 m->mt_default = 0;
1378 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1379 show_oops_s("add_mime_type: invalid mime_type");
1380 goto bad;
1383 m->mt_type = g_strdup(mime_type);
1384 m->mt_action = g_strdup(l);
1386 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1387 m->mt_type, m->mt_action, m->mt_default);
1389 TAILQ_INSERT_TAIL(&mtl, m, entry);
1391 return (0);
1392 bad:
1393 if (m)
1394 g_free(m);
1395 return (1);
1398 struct mime_type *
1399 find_mime_type(char *mime_type)
1401 struct mime_type *m, *def = NULL, *rv = NULL;
1403 TAILQ_FOREACH(m, &mtl, entry) {
1404 if (m->mt_default &&
1405 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1406 def = m;
1408 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1409 rv = m;
1410 break;
1414 if (rv == NULL)
1415 rv = def;
1417 return (rv);
1420 void
1421 walk_mime_type(struct settings *s,
1422 void (*cb)(struct settings *, char *, void *), void *cb_args)
1424 struct mime_type *m;
1425 char *str;
1427 if (s == NULL || cb == NULL)
1428 show_oops_s("walk_mime_type invalid parameters");
1430 TAILQ_FOREACH(m, &mtl, entry) {
1431 str = g_strdup_printf("%s%s --> %s",
1432 m->mt_type,
1433 m->mt_default ? "*" : "",
1434 m->mt_action);
1435 cb(s, str, cb_args);
1436 g_free(str);
1440 void
1441 wl_add(char *str, struct domain_list *wl, int handy)
1443 struct domain *d;
1444 int add_dot = 0;
1446 if (str == NULL || wl == NULL)
1447 return;
1448 if (strlen(str) < 2)
1449 return;
1451 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1453 /* treat *.moo.com the same as .moo.com */
1454 if (str[0] == '*' && str[1] == '.')
1455 str = &str[1];
1456 else if (str[0] == '.')
1457 str = &str[0];
1458 else
1459 add_dot = 1;
1461 d = g_malloc(sizeof *d);
1462 if (add_dot)
1463 d->d = g_strdup_printf(".%s", str);
1464 else
1465 d->d = g_strdup(str);
1466 d->handy = handy;
1468 if (RB_INSERT(domain_list, wl, d))
1469 goto unwind;
1471 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1472 return;
1473 unwind:
1474 if (d) {
1475 if (d->d)
1476 g_free(d->d);
1477 g_free(d);
1482 add_cookie_wl(struct settings *s, char *entry)
1484 wl_add(entry, &c_wl, 1);
1485 return (0);
1488 void
1489 walk_cookie_wl(struct settings *s,
1490 void (*cb)(struct settings *, char *, void *), void *cb_args)
1492 struct domain *d;
1494 if (s == NULL || cb == NULL) {
1495 show_oops_s("walk_cookie_wl invalid parameters");
1496 return;
1499 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1500 cb(s, d->d, cb_args);
1503 void
1504 walk_js_wl(struct settings *s,
1505 void (*cb)(struct settings *, char *, void *), void *cb_args)
1507 struct domain *d;
1509 if (s == NULL || cb == NULL) {
1510 show_oops_s("walk_js_wl invalid parameters");
1511 return;
1514 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1515 cb(s, d->d, cb_args);
1519 add_js_wl(struct settings *s, char *entry)
1521 wl_add(entry, &js_wl, 1 /* persistent */);
1522 return (0);
1525 struct domain *
1526 wl_find(const gchar *search, struct domain_list *wl)
1528 int i;
1529 struct domain *d = NULL, dfind;
1530 gchar *s = NULL;
1532 if (search == NULL || wl == NULL)
1533 return (NULL);
1534 if (strlen(search) < 2)
1535 return (NULL);
1537 if (search[0] != '.')
1538 s = g_strdup_printf(".%s", search);
1539 else
1540 s = g_strdup(search);
1542 for (i = strlen(s) - 1; i >= 0; i--) {
1543 if (s[i] == '.') {
1544 dfind.d = &s[i];
1545 d = RB_FIND(domain_list, wl, &dfind);
1546 if (d)
1547 goto done;
1551 done:
1552 if (s)
1553 g_free(s);
1555 return (d);
1558 struct domain *
1559 wl_find_uri(const gchar *s, struct domain_list *wl)
1561 int i;
1562 char *ss;
1563 struct domain *r;
1565 if (s == NULL || wl == NULL)
1566 return (NULL);
1568 if (!strncmp(s, "http://", strlen("http://")))
1569 s = &s[strlen("http://")];
1570 else if (!strncmp(s, "https://", strlen("https://")))
1571 s = &s[strlen("https://")];
1573 if (strlen(s) < 2)
1574 return (NULL);
1576 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1577 /* chop string at first slash */
1578 if (s[i] == '/' || s[i] == '\0') {
1579 ss = g_strdup(s);
1580 ss[i] = '\0';
1581 r = wl_find(ss, wl);
1582 g_free(ss);
1583 return (r);
1586 return (NULL);
1589 char *
1590 get_toplevel_domain(char *domain)
1592 char *s;
1593 int found = 0;
1595 if (domain == NULL)
1596 return (NULL);
1597 if (strlen(domain) < 2)
1598 return (NULL);
1600 s = &domain[strlen(domain) - 1];
1601 while (s != domain) {
1602 if (*s == '.') {
1603 found++;
1604 if (found == 2)
1605 return (s);
1607 s--;
1610 if (found)
1611 return (domain);
1613 return (NULL);
1617 settings_add(char *var, char *val)
1619 int i, rv, *p;
1620 char **s;
1622 /* get settings */
1623 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1624 if (strcmp(var, rs[i].name))
1625 continue;
1627 if (rs[i].s) {
1628 if (rs[i].s->set(&rs[i], val))
1629 errx(1, "invalid value for %s", var);
1630 rv = 1;
1631 break;
1632 } else
1633 switch (rs[i].type) {
1634 case XT_S_INT:
1635 p = rs[i].ival;
1636 *p = atoi(val);
1637 rv = 1;
1638 break;
1639 case XT_S_STR:
1640 s = rs[i].sval;
1641 if (s == NULL)
1642 errx(1, "invalid sval for %s",
1643 rs[i].name);
1644 if (*s)
1645 g_free(*s);
1646 *s = g_strdup(val);
1647 rv = 1;
1648 break;
1649 case XT_S_INVALID:
1650 default:
1651 errx(1, "invalid type for %s", var);
1653 break;
1655 return (rv);
1658 #define WS "\n= \t"
1659 void
1660 config_parse(char *filename, int runtime)
1662 FILE *config, *f;
1663 char *line, *cp, *var, *val;
1664 size_t len, lineno = 0;
1665 int handled;
1666 char file[PATH_MAX];
1667 struct stat sb;
1669 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1671 if (filename == NULL)
1672 return;
1674 if (runtime && runtime_settings[0] != '\0') {
1675 snprintf(file, sizeof file, "%s/%s",
1676 work_dir, runtime_settings);
1677 if (stat(file, &sb)) {
1678 warnx("runtime file doesn't exist, creating it");
1679 if ((f = fopen(file, "w")) == NULL)
1680 err(1, "runtime");
1681 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1682 fclose(f);
1684 } else
1685 strlcpy(file, filename, sizeof file);
1687 if ((config = fopen(file, "r")) == NULL) {
1688 warn("config_parse: cannot open %s", filename);
1689 return;
1692 for (;;) {
1693 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1694 if (feof(config) || ferror(config))
1695 break;
1697 cp = line;
1698 cp += (long)strspn(cp, WS);
1699 if (cp[0] == '\0') {
1700 /* empty line */
1701 free(line);
1702 continue;
1705 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1706 errx(1, "invalid config file entry: %s", line);
1708 cp += (long)strspn(cp, WS);
1710 if ((val = strsep(&cp, "\0")) == NULL)
1711 break;
1713 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1714 handled = settings_add(var, val);
1715 if (handled == 0)
1716 errx(1, "invalid conf file entry: %s=%s", var, val);
1718 free(line);
1721 fclose(config);
1724 char *
1725 js_ref_to_string(JSContextRef context, JSValueRef ref)
1727 char *s = NULL;
1728 size_t l;
1729 JSStringRef jsref;
1731 jsref = JSValueToStringCopy(context, ref, NULL);
1732 if (jsref == NULL)
1733 return (NULL);
1735 l = JSStringGetMaximumUTF8CStringSize(jsref);
1736 s = g_malloc(l);
1737 if (s)
1738 JSStringGetUTF8CString(jsref, s, l);
1739 JSStringRelease(jsref);
1741 return (s);
1744 void
1745 disable_hints(struct tab *t)
1747 bzero(t->hint_buf, sizeof t->hint_buf);
1748 bzero(t->hint_num, sizeof t->hint_num);
1749 run_script(t, "vimprobable_clear()");
1750 t->hints_on = 0;
1751 t->hint_mode = XT_HINT_NONE;
1754 void
1755 enable_hints(struct tab *t)
1757 bzero(t->hint_buf, sizeof t->hint_buf);
1758 run_script(t, "vimprobable_show_hints()");
1759 t->hints_on = 1;
1760 t->hint_mode = XT_HINT_NONE;
1763 #define XT_JS_OPEN ("open;")
1764 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1765 #define XT_JS_FIRE ("fire;")
1766 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1767 #define XT_JS_FOUND ("found;")
1768 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1771 run_script(struct tab *t, char *s)
1773 JSGlobalContextRef ctx;
1774 WebKitWebFrame *frame;
1775 JSStringRef str;
1776 JSValueRef val, exception;
1777 char *es, buf[128];
1779 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1780 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1782 frame = webkit_web_view_get_main_frame(t->wv);
1783 ctx = webkit_web_frame_get_global_context(frame);
1785 str = JSStringCreateWithUTF8CString(s);
1786 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1787 NULL, 0, &exception);
1788 JSStringRelease(str);
1790 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1791 if (val == NULL) {
1792 es = js_ref_to_string(ctx, exception);
1793 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1794 g_free(es);
1795 return (1);
1796 } else {
1797 es = js_ref_to_string(ctx, val);
1798 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1800 /* handle return value right here */
1801 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1802 disable_hints(t);
1803 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1806 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1807 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1808 &es[XT_JS_FIRE_LEN]);
1809 run_script(t, buf);
1810 disable_hints(t);
1813 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1814 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1815 disable_hints(t);
1818 g_free(es);
1821 return (0);
1825 hint(struct tab *t, struct karg *args)
1828 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1830 if (t->hints_on == 0)
1831 enable_hints(t);
1832 else
1833 disable_hints(t);
1835 return (0);
1839 * Doesn't work fully, due to the following bug:
1840 * https://bugs.webkit.org/show_bug.cgi?id=51747
1843 restore_global_history(void)
1845 char file[PATH_MAX];
1846 FILE *f;
1847 struct history *h;
1848 gchar *uri;
1849 gchar *title;
1851 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
1853 if ((f = fopen(file, "r")) == NULL) {
1854 warnx("%s: fopen", __func__);
1855 return (1);
1858 for (;;) {
1859 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1860 if (feof(f) || ferror(f))
1861 break;
1863 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1864 if (feof(f) || ferror(f)) {
1865 free(uri);
1866 warnx("%s: broken history file\n", __func__);
1867 return (1);
1870 if (uri && strlen(uri) && title && strlen(title)) {
1871 webkit_web_history_item_new_with_data(uri, title);
1872 h = g_malloc(sizeof(struct history));
1873 h->uri = g_strdup(uri);
1874 h->title = g_strdup(title);
1875 RB_INSERT(history_list, &hl, h);
1876 } else {
1877 warnx("%s: failed to restore history\n", __func__);
1878 free(uri);
1879 free(title);
1880 return (1);
1883 free(uri);
1884 free(title);
1885 uri = NULL;
1886 title = NULL;
1889 return (0);
1893 save_global_history_to_disk(struct tab *t)
1895 char file[PATH_MAX];
1896 FILE *f;
1897 struct history *h;
1899 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
1901 if ((f = fopen(file, "w")) == NULL) {
1902 show_oops(t, "%s: global history file: %s",
1903 __func__, strerror(errno));
1904 return (1);
1907 RB_FOREACH_REVERSE(h, history_list, &hl) {
1908 if (h->uri && h->title)
1909 fprintf(f, "%s\n%s\n", h->uri, h->title);
1912 fclose(f);
1914 return (0);
1918 quit(struct tab *t, struct karg *args)
1920 if (save_global_history)
1921 save_global_history_to_disk(t);
1923 gtk_main_quit();
1925 return (1);
1929 open_tabs(struct tab *t, struct karg *a)
1931 char file[PATH_MAX];
1932 FILE *f = NULL;
1933 char *uri = NULL;
1934 int rv = 1;
1935 struct tab *ti, *tt;
1937 if (a == NULL)
1938 goto done;
1940 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
1941 if ((f = fopen(file, "r")) == NULL)
1942 goto done;
1944 ti = TAILQ_LAST(&tabs, tab_list);
1946 for (;;) {
1947 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1948 if (feof(f) || ferror(f))
1949 break;
1951 /* retrieve session name */
1952 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
1953 strlcpy(named_session,
1954 &uri[strlen(XT_SAVE_SESSION_ID)],
1955 sizeof named_session);
1956 continue;
1959 if (uri && strlen(uri))
1960 create_new_tab(uri, NULL, 1);
1962 free(uri);
1963 uri = NULL;
1966 /* close open tabs */
1967 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
1968 for (;;) {
1969 tt = TAILQ_FIRST(&tabs);
1970 if (tt != ti) {
1971 delete_tab(tt);
1972 continue;
1974 delete_tab(tt);
1975 break;
1979 rv = 0;
1980 done:
1981 if (f)
1982 fclose(f);
1984 return (rv);
1988 restore_saved_tabs(void)
1990 char file[PATH_MAX];
1991 int unlink_file = 0;
1992 struct stat sb;
1993 struct karg a;
1994 int rv = 0;
1996 snprintf(file, sizeof file, "%s/%s",
1997 sessions_dir, XT_RESTART_TABS_FILE);
1998 if (stat(file, &sb) == -1)
1999 a.s = XT_SAVED_TABS_FILE;
2000 else {
2001 unlink_file = 1;
2002 a.s = XT_RESTART_TABS_FILE;
2005 a.i = XT_SES_DONOTHING;
2006 rv = open_tabs(NULL, &a);
2008 if (unlink_file)
2009 unlink(file);
2011 return (rv);
2015 save_tabs(struct tab *t, struct karg *a)
2017 char file[PATH_MAX];
2018 FILE *f;
2019 struct tab *ti;
2020 const gchar *uri;
2021 int len = 0, i;
2022 const gchar **arr = NULL;
2024 if (a == NULL)
2025 return (1);
2026 if (a->s == NULL)
2027 snprintf(file, sizeof file, "%s/%s",
2028 sessions_dir, named_session);
2029 else
2030 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2032 if ((f = fopen(file, "w")) == NULL) {
2033 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2034 return (1);
2037 /* save session name */
2038 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2040 /* save tabs, in the order they are arranged in the notebook */
2041 TAILQ_FOREACH(ti, &tabs, entry)
2042 len++;
2044 arr = g_malloc0(len * sizeof(gchar *));
2046 TAILQ_FOREACH(ti, &tabs, entry) {
2047 if ((uri = get_uri(ti->wv)) != NULL)
2048 arr[gtk_notebook_page_num(notebook, ti->vbox)] = uri;
2051 for (i = 0; i < len; i++)
2052 if (arr[i])
2053 fprintf(f, "%s\n", arr[i]);
2055 g_free(arr);
2056 fclose(f);
2058 return (0);
2062 save_tabs_and_quit(struct tab *t, struct karg *args)
2064 struct karg a;
2066 a.s = NULL;
2067 save_tabs(t, &a);
2068 quit(t, NULL);
2070 return (1);
2074 yank_uri(struct tab *t, struct karg *args)
2076 const gchar *uri;
2077 GtkClipboard *clipboard;
2079 if ((uri = get_uri(t->wv)) == NULL)
2080 return (1);
2082 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2083 gtk_clipboard_set_text(clipboard, uri, -1);
2085 return (0);
2088 struct paste_args {
2089 struct tab *t;
2090 int i;
2093 void
2094 paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
2096 struct paste_args *pap;
2098 if (data == NULL || text == NULL || !strlen(text))
2099 return;
2101 pap = (struct paste_args *)data;
2103 switch(pap->i) {
2104 case XT_PASTE_CURRENT_TAB:
2105 load_uri(pap->t, (gchar *)text);
2106 break;
2107 case XT_PASTE_NEW_TAB:
2108 create_new_tab((gchar *)text, NULL, 1);
2109 break;
2112 g_free(pap);
2116 paste_uri(struct tab *t, struct karg *args)
2118 GtkClipboard *clipboard;
2119 struct paste_args *pap;
2121 pap = g_malloc(sizeof(struct paste_args));
2123 pap->t = t;
2124 pap->i = args->i;
2126 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2127 gtk_clipboard_request_text(clipboard, paste_uri_cb, pap);
2129 return (0);
2132 char *
2133 find_domain(const gchar *s, int add_dot)
2135 int i;
2136 char *r = NULL, *ss = NULL;
2138 if (s == NULL)
2139 return (NULL);
2141 if (!strncmp(s, "http://", strlen("http://")))
2142 s = &s[strlen("http://")];
2143 else if (!strncmp(s, "https://", strlen("https://")))
2144 s = &s[strlen("https://")];
2146 if (strlen(s) < 2)
2147 return (NULL);
2149 ss = g_strdup(s);
2150 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2151 /* chop string at first slash */
2152 if (ss[i] == '/' || ss[i] == '\0') {
2153 ss[i] = '\0';
2154 if (add_dot)
2155 r = g_strdup_printf(".%s", ss);
2156 else
2157 r = g_strdup(ss);
2158 break;
2160 g_free(ss);
2162 return (r);
2166 toggle_cwl(struct tab *t, struct karg *args)
2168 struct domain *d;
2169 const gchar *uri;
2170 char *dom = NULL, *dom_toggle = NULL;
2171 int es;
2173 if (args == NULL)
2174 return (1);
2176 uri = get_uri(t->wv);
2177 dom = find_domain(uri, 1);
2178 d = wl_find(dom, &c_wl);
2180 if (d == NULL)
2181 es = 0;
2182 else
2183 es = 1;
2185 if (args->i & XT_WL_TOGGLE)
2186 es = !es;
2187 else if ((args->i & XT_WL_ENABLE) && es != 1)
2188 es = 1;
2189 else if ((args->i & XT_WL_DISABLE) && es != 0)
2190 es = 0;
2192 if (args->i & XT_WL_TOPLEVEL)
2193 dom_toggle = get_toplevel_domain(dom);
2194 else
2195 dom_toggle = dom;
2197 if (es)
2198 /* enable cookies for domain */
2199 wl_add(dom_toggle, &c_wl, 0);
2200 else
2201 /* disable cookies for domain */
2202 RB_REMOVE(domain_list, &c_wl, d);
2204 webkit_web_view_reload(t->wv);
2206 g_free(dom);
2207 return (0);
2211 toggle_js(struct tab *t, struct karg *args)
2213 int es;
2214 const gchar *uri;
2215 struct domain *d;
2216 char *dom = NULL, *dom_toggle = NULL;
2218 if (args == NULL)
2219 return (1);
2221 g_object_get(G_OBJECT(t->settings),
2222 "enable-scripts", &es, (char *)NULL);
2223 if (args->i & XT_WL_TOGGLE)
2224 es = !es;
2225 else if ((args->i & XT_WL_ENABLE) && es != 1)
2226 es = 1;
2227 else if ((args->i & XT_WL_DISABLE) && es != 0)
2228 es = 0;
2229 else
2230 return (1);
2232 uri = get_uri(t->wv);
2233 dom = find_domain(uri, 1);
2235 if (uri == NULL || dom == NULL) {
2236 show_oops(t, "Can't toggle domain in JavaScript white list");
2237 goto done;
2240 if (args->i & XT_WL_TOPLEVEL)
2241 dom_toggle = get_toplevel_domain(dom);
2242 else
2243 dom_toggle = dom;
2245 if (es) {
2246 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2247 wl_add(dom_toggle, &js_wl, 0 /* session */);
2248 } else {
2249 d = wl_find(dom_toggle, &js_wl);
2250 if (d)
2251 RB_REMOVE(domain_list, &js_wl, d);
2252 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2254 g_object_set(G_OBJECT(t->settings),
2255 "enable-scripts", es, (char *)NULL);
2256 webkit_web_view_set_settings(t->wv, t->settings);
2257 webkit_web_view_reload(t->wv);
2258 done:
2259 if (dom)
2260 g_free(dom);
2261 return (0);
2264 void
2265 js_toggle_cb(GtkWidget *w, struct tab *t)
2267 struct karg a;
2269 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
2270 toggle_js(t, &a);
2274 toggle_src(struct tab *t, struct karg *args)
2276 gboolean mode;
2278 if (t == NULL)
2279 return (0);
2281 mode = webkit_web_view_get_view_source_mode(t->wv);
2282 webkit_web_view_set_view_source_mode(t->wv, !mode);
2283 webkit_web_view_reload(t->wv);
2285 return (0);
2288 void
2289 focus_webview(struct tab *t)
2291 if (t == NULL)
2292 return;
2294 /* only grab focus if we are visible */
2295 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2296 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2300 focus(struct tab *t, struct karg *args)
2302 if (t == NULL || args == NULL)
2303 return (1);
2305 if (show_url == 0)
2306 return (0);
2308 if (args->i == XT_FOCUS_URI)
2309 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2310 else if (args->i == XT_FOCUS_SEARCH)
2311 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2313 return (0);
2317 stats(struct tab *t, struct karg *args)
2319 char *stats, *s, line[64 * 1024];
2320 uint64_t line_count = 0;
2321 FILE *r_cookie_f;
2323 if (t == NULL)
2324 show_oops_s("stats invalid parameters");
2326 line[0] = '\0';
2327 if (save_rejected_cookies) {
2328 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2329 for (;;) {
2330 s = fgets(line, sizeof line, r_cookie_f);
2331 if (s == NULL || feof(r_cookie_f) ||
2332 ferror(r_cookie_f))
2333 break;
2334 line_count++;
2336 fclose(r_cookie_f);
2337 snprintf(line, sizeof line,
2338 "<br>Cookies blocked(*) total: %llu", line_count);
2339 } else
2340 show_oops(t, "Can't open blocked cookies file: %s",
2341 strerror(errno));
2344 stats = g_strdup_printf(XT_DOCTYPE
2345 "<html>"
2346 "<head>"
2347 "<title>Statistics</title>"
2348 "</head>"
2349 "<h1>Statistics</h1>"
2350 "<body>"
2351 "Cookies blocked(*) this session: %llu"
2352 "%s"
2353 "<p><small><b>*</b> results vary based on settings"
2354 "</body>"
2355 "</html>",
2356 blocked_cookies,
2357 line);
2359 _load_webkit_string(t, stats, XT_URI_ABOUT_STATS);
2360 g_free(stats);
2362 return (0);
2366 blank(struct tab *t, struct karg *args)
2368 if (t == NULL)
2369 show_oops_s("about invalid parameters");
2371 _load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2373 return (0);
2376 about(struct tab *t, struct karg *args)
2378 char *about;
2380 if (t == NULL)
2381 show_oops_s("about invalid parameters");
2383 about = g_strdup_printf(XT_DOCTYPE
2384 "<html>"
2385 "<head>"
2386 "<title>About</title>"
2387 "</head>"
2388 "<h1>About</h1>"
2389 "<body>"
2390 "<b>Version: %s</b><p>"
2391 "Authors:"
2392 "<ul>"
2393 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2394 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2395 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2396 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2397 "</ul>"
2398 "Copyrights and licenses can be found on the XXXterm "
2399 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
2400 "</body>"
2401 "</html>",
2402 version
2405 _load_webkit_string(t, about, XT_URI_ABOUT_ABOUT);
2406 g_free(about);
2408 return (0);
2412 help(struct tab *t, struct karg *args)
2414 char *help;
2416 if (t == NULL)
2417 show_oops_s("help invalid parameters");
2419 help = XT_DOCTYPE
2420 "<html>"
2421 "<head>"
2422 "<title>XXXterm</title>"
2423 "<meta http-equiv=\"REFRESH\" content=\"0;"
2424 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2425 "</head>"
2426 "<body>"
2427 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2428 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2429 "cgi-bin/man-cgi?xxxterm</a>"
2430 "</body>"
2431 "</html>"
2434 _load_webkit_string(t, help, XT_URI_ABOUT_HELP);
2436 return (0);
2440 * update all favorite tabs apart from one. Pass NULL if
2441 * you want to update all.
2443 void
2444 update_favorite_tabs(struct tab *apart_from)
2446 struct tab *t;
2447 if (!updating_fl_tabs) {
2448 updating_fl_tabs = 1; /* stop infinite recursion */
2449 TAILQ_FOREACH(t, &tabs, entry)
2450 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2451 && (t != apart_from))
2452 xtp_page_fl(t, NULL);
2453 updating_fl_tabs = 0;
2457 /* show a list of favorites (bookmarks) */
2459 xtp_page_fl(struct tab *t, struct karg *args)
2461 char file[PATH_MAX];
2462 FILE *f;
2463 char *uri = NULL, *title = NULL;
2464 size_t len, lineno = 0;
2465 int i, failed = 0;
2466 char *header, *body, *tmp, *html = NULL;
2468 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2470 if (t == NULL)
2471 warn("%s: bad param", __func__);
2473 /* mark tab as favorite list */
2474 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2476 /* new session key */
2477 if (!updating_fl_tabs)
2478 generate_xtp_session_key(&fl_session_key);
2480 /* open favorites */
2481 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2482 if ((f = fopen(file, "r")) == NULL) {
2483 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2484 return (1);
2487 /* header */
2488 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
2489 "<title>Favorites</title>\n"
2490 "%s"
2491 "</head>"
2492 "<h1>Favorites</h1>\n",
2493 XT_PAGE_STYLE);
2495 /* body */
2496 body = g_strdup_printf("<div align='center'><table><tr>"
2497 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
2498 "<th style='width: 15%%'>Remove</th></tr>\n");
2500 for (i = 1;;) {
2501 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2502 if (feof(f) || ferror(f))
2503 break;
2504 if (len == 0) {
2505 free(title);
2506 title = NULL;
2507 continue;
2510 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2511 if (feof(f) || ferror(f)) {
2512 show_oops(t, "favorites file corrupt");
2513 failed = 1;
2514 break;
2517 tmp = body;
2518 body = g_strdup_printf("%s<tr>"
2519 "<td>%d</td>"
2520 "<td><a href='%s'>%s</a></td>"
2521 "<td style='text-align: center'>"
2522 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2523 "</tr>\n",
2524 body, i, uri, title,
2525 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2527 g_free(tmp);
2529 free(uri);
2530 uri = NULL;
2531 free(title);
2532 title = NULL;
2533 i++;
2535 fclose(f);
2537 /* if none, say so */
2538 if (i == 1) {
2539 tmp = body;
2540 body = g_strdup_printf("%s<tr>"
2541 "<td colspan='3' style='text-align: center'>"
2542 "No favorites - To add one use the 'favadd' command."
2543 "</td></tr>", body);
2544 g_free(tmp);
2547 if (uri)
2548 free(uri);
2549 if (title)
2550 free(title);
2552 /* render */
2553 if (!failed) {
2554 html = g_strdup_printf("%s%s</table></div></html>",
2555 header, body);
2556 _load_webkit_string(t, html, XT_URI_ABOUT_FAVORITES);
2559 update_favorite_tabs(t);
2561 if (header)
2562 g_free(header);
2563 if (body)
2564 g_free(body);
2565 if (html)
2566 g_free(html);
2568 return (failed);
2571 char *
2572 getparams(char *cmd, char *cmp)
2574 char *rv = NULL;
2576 if (cmd && cmp) {
2577 if (!strncmp(cmd, cmp, strlen(cmp))) {
2578 rv = cmd + strlen(cmp);
2579 while (*rv == ' ')
2580 rv++;
2581 if (strlen(rv) == 0)
2582 rv = NULL;
2586 return (rv);
2589 void
2590 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2591 size_t cert_count, char *title)
2593 gnutls_datum_t cinfo;
2594 char *tmp, *header, *body, *footer;
2595 int i;
2597 header = g_strdup_printf("<title>%s</title><html><body>", title);
2598 footer = g_strdup("</body></html>");
2599 body = g_strdup("");
2601 for (i = 0; i < cert_count; i++) {
2602 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2603 &cinfo))
2604 return;
2606 tmp = body;
2607 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2608 body, i, cinfo.data);
2609 gnutls_free(cinfo.data);
2610 g_free(tmp);
2613 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2614 g_free(header);
2615 g_free(body);
2616 g_free(footer);
2617 _load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2618 g_free(tmp);
2622 ca_cmd(struct tab *t, struct karg *args)
2624 FILE *f = NULL;
2625 int rv = 1, certs = 0, certs_read;
2626 struct stat sb;
2627 gnutls_datum dt;
2628 gnutls_x509_crt_t *c = NULL;
2629 char *certs_buf = NULL, *s;
2631 /* yeah yeah stat race */
2632 if (stat(ssl_ca_file, &sb)) {
2633 show_oops(t, "no CA file: %s", ssl_ca_file);
2634 goto done;
2637 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2638 show_oops(t, "Can't open CA file: %s", strerror(errno));
2639 return (1);
2642 certs_buf = g_malloc(sb.st_size + 1);
2643 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2644 show_oops(t, "Can't read CA file: %s", strerror(errno));
2645 goto done;
2647 certs_buf[sb.st_size] = '\0';
2649 s = certs_buf;
2650 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2651 certs++;
2652 s += strlen("BEGIN CERTIFICATE");
2655 bzero(&dt, sizeof dt);
2656 dt.data = certs_buf;
2657 dt.size = sb.st_size;
2658 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2659 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
2660 GNUTLS_X509_FMT_PEM, 0);
2661 if (certs_read <= 0) {
2662 show_oops(t, "No cert(s) available");
2663 goto done;
2665 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2666 done:
2667 if (c)
2668 g_free(c);
2669 if (certs_buf)
2670 g_free(certs_buf);
2671 if (f)
2672 fclose(f);
2674 return (rv);
2678 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2680 SoupURI *su = NULL;
2681 struct addrinfo hints, *res = NULL, *ai;
2682 int s = -1, on;
2683 char port[8];
2685 if (uri && !g_str_has_prefix(uri, "https://"))
2686 goto done;
2688 su = soup_uri_new(uri);
2689 if (su == NULL)
2690 goto done;
2691 if (!SOUP_URI_VALID_FOR_HTTP(su))
2692 goto done;
2694 snprintf(port, sizeof port, "%d", su->port);
2695 bzero(&hints, sizeof(struct addrinfo));
2696 hints.ai_flags = AI_CANONNAME;
2697 hints.ai_family = AF_UNSPEC;
2698 hints.ai_socktype = SOCK_STREAM;
2700 if (getaddrinfo(su->host, port, &hints, &res))
2701 goto done;
2703 for (ai = res; ai; ai = ai->ai_next) {
2704 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2705 continue;
2707 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2708 if (s < 0)
2709 goto done;
2710 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2711 sizeof(on)) == -1)
2712 goto done;
2714 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2715 goto done;
2718 if (domain)
2719 strlcpy(domain, su->host, domain_sz);
2720 done:
2721 if (su)
2722 soup_uri_free(su);
2723 if (res)
2724 freeaddrinfo(res);
2726 return (s);
2730 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2732 if (gsession)
2733 gnutls_deinit(gsession);
2734 if (xcred)
2735 gnutls_certificate_free_credentials(xcred);
2737 return (0);
2741 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2742 gnutls_certificate_credentials_t *xc)
2744 gnutls_certificate_credentials_t xcred;
2745 gnutls_session_t gsession;
2746 int rv = 1;
2748 if (gs == NULL || xc == NULL)
2749 goto done;
2751 *gs = NULL;
2752 *xc = NULL;
2754 gnutls_certificate_allocate_credentials(&xcred);
2755 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2756 GNUTLS_X509_FMT_PEM);
2757 gnutls_init(&gsession, GNUTLS_CLIENT);
2758 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2759 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2760 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2761 if ((rv = gnutls_handshake(gsession)) < 0) {
2762 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2764 gnutls_error_is_fatal(rv),
2765 gnutls_strerror_name(rv));
2766 stop_tls(gsession, xcred);
2767 goto done;
2770 gnutls_credentials_type_t cred;
2771 cred = gnutls_auth_get_type(gsession);
2772 if (cred != GNUTLS_CRD_CERTIFICATE) {
2773 stop_tls(gsession, xcred);
2774 goto done;
2777 *gs = gsession;
2778 *xc = xcred;
2779 rv = 0;
2780 done:
2781 return (rv);
2785 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2786 size_t *cert_count)
2788 unsigned int len;
2789 const gnutls_datum_t *cl;
2790 gnutls_x509_crt_t *all_certs;
2791 int i, rv = 1;
2793 if (certs == NULL || cert_count == NULL)
2794 goto done;
2795 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2796 goto done;
2797 cl = gnutls_certificate_get_peers(gsession, &len);
2798 if (len == 0)
2799 goto done;
2801 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2802 for (i = 0; i < len; i++) {
2803 gnutls_x509_crt_init(&all_certs[i]);
2804 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2805 GNUTLS_X509_FMT_PEM < 0)) {
2806 g_free(all_certs);
2807 goto done;
2811 *certs = all_certs;
2812 *cert_count = len;
2813 rv = 0;
2814 done:
2815 return (rv);
2818 void
2819 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2821 int i;
2823 for (i = 0; i < cert_count; i++)
2824 gnutls_x509_crt_deinit(certs[i]);
2825 g_free(certs);
2828 void
2829 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2830 size_t cert_count, char *domain)
2832 size_t cert_buf_sz;
2833 char cert_buf[64 * 1024], file[PATH_MAX];
2834 int i;
2835 FILE *f;
2836 GdkColor color;
2838 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2839 return;
2841 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2842 if ((f = fopen(file, "w")) == NULL) {
2843 show_oops(t, "Can't create cert file %s %s",
2844 file, strerror(errno));
2845 return;
2848 for (i = 0; i < cert_count; i++) {
2849 cert_buf_sz = sizeof cert_buf;
2850 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2851 cert_buf, &cert_buf_sz)) {
2852 show_oops(t, "gnutls_x509_crt_export failed");
2853 goto done;
2855 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
2856 show_oops(t, "Can't write certs: %s", strerror(errno));
2857 goto done;
2861 /* not the best spot but oh well */
2862 gdk_color_parse("lightblue", &color);
2863 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
2864 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
2865 gdk_color_parse("black", &color);
2866 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
2867 done:
2868 fclose(f);
2872 load_compare_cert(struct tab *t, struct karg *args)
2874 const gchar *uri;
2875 char domain[8182], file[PATH_MAX];
2876 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
2877 int s = -1, rv = 1, i;
2878 size_t cert_count;
2879 FILE *f = NULL;
2880 size_t cert_buf_sz;
2881 gnutls_session_t gsession;
2882 gnutls_x509_crt_t *certs;
2883 gnutls_certificate_credentials_t xcred;
2885 if (t == NULL)
2886 return (1);
2888 if ((uri = get_uri(t->wv)) == NULL)
2889 return (1);
2891 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
2892 return (1);
2894 /* go ssl/tls */
2895 if (start_tls(t, s, &gsession, &xcred)) {
2896 show_oops(t, "Start TLS failed");
2897 goto done;
2900 /* get certs */
2901 if (get_connection_certs(gsession, &certs, &cert_count)) {
2902 show_oops(t, "Can't get connection certificates");
2903 goto done;
2906 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2907 if ((f = fopen(file, "r")) == NULL)
2908 goto freeit;
2910 for (i = 0; i < cert_count; i++) {
2911 cert_buf_sz = sizeof cert_buf;
2912 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2913 cert_buf, &cert_buf_sz)) {
2914 goto freeit;
2916 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
2917 rv = -1; /* critical */
2918 goto freeit;
2920 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
2921 rv = -1; /* critical */
2922 goto freeit;
2926 rv = 0;
2927 freeit:
2928 if (f)
2929 fclose(f);
2930 free_connection_certs(certs, cert_count);
2931 done:
2932 /* we close the socket first for speed */
2933 if (s != -1)
2934 close(s);
2935 stop_tls(gsession, xcred);
2937 return (rv);
2941 cert_cmd(struct tab *t, struct karg *args)
2943 const gchar *uri;
2944 char *action, domain[8182];
2945 int s = -1;
2946 size_t cert_count;
2947 gnutls_session_t gsession;
2948 gnutls_x509_crt_t *certs;
2949 gnutls_certificate_credentials_t xcred;
2951 if (t == NULL)
2952 return (1);
2954 if ((action = getparams(args->s, "cert")))
2956 else
2957 action = "show";
2959 if ((uri = get_uri(t->wv)) == NULL) {
2960 show_oops(t, "Invalid URI");
2961 return (1);
2964 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
2965 show_oops(t, "Invalid certidicate URI: %s", uri);
2966 return (1);
2969 /* go ssl/tls */
2970 if (start_tls(t, s, &gsession, &xcred)) {
2971 show_oops(t, "Start TLS failed");
2972 goto done;
2975 /* get certs */
2976 if (get_connection_certs(gsession, &certs, &cert_count)) {
2977 show_oops(t, "get_connection_certs failed");
2978 goto done;
2981 if (!strcmp(action, "show"))
2982 show_certs(t, certs, cert_count, "Certificate Chain");
2983 else if (!strcmp(action, "save"))
2984 save_certs(t, certs, cert_count, domain);
2985 else
2986 show_oops(t, "Invalid command: %s", action);
2988 free_connection_certs(certs, cert_count);
2989 done:
2990 /* we close the socket first for speed */
2991 if (s != -1)
2992 close(s);
2993 stop_tls(gsession, xcred);
2995 return (0);
2999 remove_cookie(int index)
3001 int i, rv = 1;
3002 GSList *cf;
3003 SoupCookie *c;
3005 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3007 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3009 for (i = 1; cf; cf = cf->next, i++) {
3010 if (i != index)
3011 continue;
3012 c = cf->data;
3013 print_cookie("remove cookie", c);
3014 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3015 rv = 0;
3016 break;
3019 soup_cookies_free(cf);
3021 return (rv);
3025 wl_show(struct tab *t, char *args, char *title, struct domain_list *wl)
3027 struct domain *d;
3028 char *tmp, *header, *body, *footer;
3029 int p_js = 0, s_js = 0;
3031 /* we set this to indicate we want to manually do navaction */
3032 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
3034 if (g_str_has_prefix(args, "show a") ||
3035 !strcmp(args, "show")) {
3036 /* show all */
3037 p_js = 1;
3038 s_js = 1;
3039 } else if (g_str_has_prefix(args, "show p")) {
3040 /* show persistent */
3041 p_js = 1;
3042 } else if (g_str_has_prefix(args, "show s")) {
3043 /* show session */
3044 s_js = 1;
3045 } else
3046 return (1);
3048 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
3049 title, title);
3050 footer = g_strdup("</body></html>");
3051 body = g_strdup("");
3053 /* p list */
3054 if (p_js) {
3055 tmp = body;
3056 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3057 g_free(tmp);
3058 RB_FOREACH(d, domain_list, wl) {
3059 if (d->handy == 0)
3060 continue;
3061 tmp = body;
3062 body = g_strdup_printf("%s%s<br>", body, d->d);
3063 g_free(tmp);
3067 /* s list */
3068 if (s_js) {
3069 tmp = body;
3070 body = g_strdup_printf("%s<h2>Session</h2>", body);
3071 g_free(tmp);
3072 RB_FOREACH(d, domain_list, wl) {
3073 if (d->handy == 1)
3074 continue;
3075 tmp = body;
3076 body = g_strdup_printf("%s%s<br>", body, d->d);
3077 g_free(tmp);
3081 tmp = g_strdup_printf("%s%s%s", header, body, footer);
3082 g_free(header);
3083 g_free(body);
3084 g_free(footer);
3085 if (wl == &js_wl)
3086 _load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3087 else
3088 _load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3089 g_free(tmp);
3090 return (0);
3094 wl_save(struct tab *t, struct karg *args, int js)
3096 char file[PATH_MAX];
3097 FILE *f;
3098 char *line = NULL, *lt = NULL;
3099 size_t linelen;
3100 const gchar *uri;
3101 char *dom = NULL, *dom_save = NULL;
3102 struct karg a;
3103 struct domain *d;
3104 GSList *cf;
3105 SoupCookie *ci, *c;
3106 int flags;
3108 if (t == NULL || args == NULL)
3109 return (1);
3111 if (runtime_settings[0] == '\0')
3112 return (1);
3114 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3115 if ((f = fopen(file, "r+")) == NULL)
3116 return (1);
3118 uri = get_uri(t->wv);
3119 dom = find_domain(uri, 1);
3120 if (uri == NULL || dom == NULL) {
3121 show_oops(t, "Can't add domain to %s white list",
3122 js ? "JavaScript" : "cookie");
3123 goto done;
3126 if (g_str_has_prefix(args->s, "save d")) {
3127 /* save domain */
3128 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3129 show_oops(t, "invalid domain: %s", dom);
3130 goto done;
3132 flags = XT_WL_TOPLEVEL;
3133 } else if (g_str_has_prefix(args->s, "save f") ||
3134 !strcmp(args->s, "save")) {
3135 /* save fqdn */
3136 dom_save = dom;
3137 flags = XT_WL_FQDN;
3138 } else {
3139 show_oops(t, "invalid command: %s", args->s);
3140 goto done;
3143 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3145 while (!feof(f)) {
3146 line = fparseln(f, &linelen, NULL, NULL, 0);
3147 if (line == NULL)
3148 continue;
3149 if (!strcmp(line, lt))
3150 goto done;
3151 free(line);
3152 line = NULL;
3155 fprintf(f, "%s\n", lt);
3157 a.i = XT_WL_ENABLE;
3158 a.i |= flags;
3159 if (js) {
3160 d = wl_find(dom_save, &js_wl);
3161 if (!d) {
3162 settings_add("js_wl", dom_save);
3163 d = wl_find(dom_save, &js_wl);
3165 toggle_js(t, &a);
3166 } else {
3167 d = wl_find(dom_save, &c_wl);
3168 if (!d) {
3169 settings_add("cookie_wl", dom_save);
3170 d = wl_find(dom_save, &c_wl);
3172 toggle_cwl(t, &a);
3174 /* find and add to persistent jar */
3175 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3176 for (;cf; cf = cf->next) {
3177 ci = cf->data;
3178 if (!strcmp(dom_save, ci->domain) ||
3179 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3180 c = soup_cookie_copy(ci);
3181 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3184 soup_cookies_free(cf);
3186 if (d)
3187 d->handy = 1;
3189 done:
3190 if (line)
3191 free(line);
3192 if (dom)
3193 g_free(dom);
3194 if (lt)
3195 g_free(lt);
3196 fclose(f);
3198 return (0);
3202 js_show_wl(struct tab *t, struct karg *args)
3204 wl_show(t, "show all", "JavaScript White List", &js_wl);
3206 return (0);
3210 cookie_show_wl(struct tab *t, struct karg *args)
3212 wl_show(t, "show all", "Cookie White List", &c_wl);
3214 return (0);
3218 cookie_cmd(struct tab *t, struct karg *args)
3220 char *cmd;
3221 struct karg a;
3223 if ((cmd = getparams(args->s, "cookie")))
3225 else
3226 cmd = "show all";
3229 if (g_str_has_prefix(cmd, "show")) {
3230 wl_show(t, cmd, "Cookie White List", &c_wl);
3231 } else if (g_str_has_prefix(cmd, "save")) {
3232 a.s = cmd;
3233 wl_save(t, &a, 0);
3234 } else if (g_str_has_prefix(cmd, "toggle")) {
3235 a.i = XT_WL_TOGGLE;
3236 if (g_str_has_prefix(cmd, "toggle d"))
3237 a.i |= XT_WL_TOPLEVEL;
3238 else
3239 a.i |= XT_WL_FQDN;
3240 toggle_cwl(t, &a);
3241 } else if (g_str_has_prefix(cmd, "delete")) {
3242 show_oops(t, "'cookie delete' currently unimplemented");
3243 } else
3244 show_oops(t, "unknown cookie command: %s", cmd);
3246 return (0);
3250 js_cmd(struct tab *t, struct karg *args)
3252 char *cmd;
3253 struct karg a;
3255 if ((cmd = getparams(args->s, "js")))
3257 else
3258 cmd = "show all";
3260 if (g_str_has_prefix(cmd, "show")) {
3261 wl_show(t, cmd, "JavaScript White List", &js_wl);
3262 } else if (g_str_has_prefix(cmd, "save")) {
3263 a.s = cmd;
3264 wl_save(t, &a, 1);
3265 } else if (g_str_has_prefix(cmd, "toggle")) {
3266 a.i = XT_WL_TOGGLE;
3267 if (g_str_has_prefix(cmd, "toggle d"))
3268 a.i |= XT_WL_TOPLEVEL;
3269 else
3270 a.i |= XT_WL_FQDN;
3271 toggle_js(t, &a);
3272 } else if (g_str_has_prefix(cmd, "delete")) {
3273 show_oops(t, "'js delete' currently unimplemented");
3274 } else
3275 show_oops(t, "unknown js command: %s", cmd);
3277 return (0);
3281 add_favorite(struct tab *t, struct karg *args)
3283 char file[PATH_MAX];
3284 FILE *f;
3285 char *line = NULL;
3286 size_t urilen, linelen;
3287 const gchar *uri, *title;
3289 if (t == NULL)
3290 return (1);
3292 /* don't allow adding of xtp pages to favorites */
3293 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3294 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3295 return (1);
3298 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3299 if ((f = fopen(file, "r+")) == NULL) {
3300 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3301 return (1);
3304 title = webkit_web_view_get_title(t->wv);
3305 uri = get_uri(t->wv);
3307 if (title == NULL)
3308 title = uri;
3310 if (title == NULL || uri == NULL) {
3311 show_oops(t, "can't add page to favorites");
3312 goto done;
3315 urilen = strlen(uri);
3317 for (;;) {
3318 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3319 if (feof(f) || ferror(f))
3320 break;
3322 if (linelen == urilen && !strcmp(line, uri))
3323 goto done;
3325 free(line);
3326 line = NULL;
3329 fprintf(f, "\n%s\n%s", title, uri);
3330 done:
3331 if (line)
3332 free(line);
3333 fclose(f);
3335 update_favorite_tabs(NULL);
3337 return (0);
3341 navaction(struct tab *t, struct karg *args)
3343 WebKitWebHistoryItem *item;
3345 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3346 t->tab_id, args->i);
3348 if (t->item) {
3349 if (args->i == XT_NAV_BACK)
3350 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3351 else
3352 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3353 if (item == NULL)
3354 return (XT_CB_PASSTHROUGH);
3355 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3356 t->item = NULL;
3357 return (XT_CB_PASSTHROUGH);
3360 switch (args->i) {
3361 case XT_NAV_BACK:
3362 webkit_web_view_go_back(t->wv);
3363 break;
3364 case XT_NAV_FORWARD:
3365 webkit_web_view_go_forward(t->wv);
3366 break;
3367 case XT_NAV_RELOAD:
3368 webkit_web_view_reload(t->wv);
3369 break;
3370 case XT_NAV_RELOAD_CACHE:
3371 webkit_web_view_reload_bypass_cache(t->wv);
3372 break;
3374 return (XT_CB_PASSTHROUGH);
3378 move(struct tab *t, struct karg *args)
3380 GtkAdjustment *adjust;
3381 double pi, si, pos, ps, upper, lower, max;
3383 switch (args->i) {
3384 case XT_MOVE_DOWN:
3385 case XT_MOVE_UP:
3386 case XT_MOVE_BOTTOM:
3387 case XT_MOVE_TOP:
3388 case XT_MOVE_PAGEDOWN:
3389 case XT_MOVE_PAGEUP:
3390 case XT_MOVE_HALFDOWN:
3391 case XT_MOVE_HALFUP:
3392 adjust = t->adjust_v;
3393 break;
3394 default:
3395 adjust = t->adjust_h;
3396 break;
3399 pos = gtk_adjustment_get_value(adjust);
3400 ps = gtk_adjustment_get_page_size(adjust);
3401 upper = gtk_adjustment_get_upper(adjust);
3402 lower = gtk_adjustment_get_lower(adjust);
3403 si = gtk_adjustment_get_step_increment(adjust);
3404 pi = gtk_adjustment_get_page_increment(adjust);
3405 max = upper - ps;
3407 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3408 "max %f si %f pi %f\n",
3409 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3410 pos, ps, upper, lower, max, si, pi);
3412 switch (args->i) {
3413 case XT_MOVE_DOWN:
3414 case XT_MOVE_RIGHT:
3415 pos += si;
3416 gtk_adjustment_set_value(adjust, MIN(pos, max));
3417 break;
3418 case XT_MOVE_UP:
3419 case XT_MOVE_LEFT:
3420 pos -= si;
3421 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3422 break;
3423 case XT_MOVE_BOTTOM:
3424 case XT_MOVE_FARRIGHT:
3425 gtk_adjustment_set_value(adjust, max);
3426 break;
3427 case XT_MOVE_TOP:
3428 case XT_MOVE_FARLEFT:
3429 gtk_adjustment_set_value(adjust, lower);
3430 break;
3431 case XT_MOVE_PAGEDOWN:
3432 pos += pi;
3433 gtk_adjustment_set_value(adjust, MIN(pos, max));
3434 break;
3435 case XT_MOVE_PAGEUP:
3436 pos -= pi;
3437 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3438 break;
3439 case XT_MOVE_HALFDOWN:
3440 pos += pi / 2;
3441 gtk_adjustment_set_value(adjust, MIN(pos, max));
3442 break;
3443 case XT_MOVE_HALFUP:
3444 pos -= pi / 2;
3445 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3446 break;
3447 default:
3448 return (XT_CB_PASSTHROUGH);
3451 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3453 return (XT_CB_HANDLED);
3456 void
3457 url_set_visibility(void)
3459 struct tab *t;
3461 TAILQ_FOREACH(t, &tabs, entry) {
3462 if (show_url == 0) {
3463 gtk_widget_hide(t->toolbar);
3464 focus_webview(t);
3465 } else
3466 gtk_widget_show(t->toolbar);
3470 void
3471 notebook_tab_set_visibility(GtkNotebook *notebook)
3473 if (show_tabs == 0)
3474 gtk_notebook_set_show_tabs(notebook, FALSE);
3475 else
3476 gtk_notebook_set_show_tabs(notebook, TRUE);
3479 void
3480 statusbar_set_visibility(void)
3482 struct tab *t;
3484 TAILQ_FOREACH(t, &tabs, entry) {
3485 if (show_statusbar == 0) {
3486 gtk_widget_hide(t->statusbar);
3487 focus_webview(t);
3488 } else
3489 gtk_widget_show(t->statusbar);
3493 void
3494 url_set(struct tab *t, int enable_url_entry)
3496 GdkPixbuf *pixbuf;
3497 int progress;
3499 show_url = enable_url_entry;
3501 if (enable_url_entry) {
3502 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3503 GTK_ENTRY_ICON_PRIMARY, NULL);
3504 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3505 } else {
3506 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3507 GTK_ENTRY_ICON_PRIMARY);
3508 progress =
3509 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3510 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3511 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3512 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3513 progress);
3518 fullscreen(struct tab *t, struct karg *args)
3520 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3522 if (t == NULL)
3523 return (XT_CB_PASSTHROUGH);
3525 if (show_url == 0) {
3526 url_set(t, 1);
3527 show_tabs = 1;
3528 } else {
3529 url_set(t, 0);
3530 show_tabs = 0;
3533 url_set_visibility();
3534 notebook_tab_set_visibility(notebook);
3536 return (XT_CB_HANDLED);
3540 statusaction(struct tab *t, struct karg *args)
3542 int rv = XT_CB_HANDLED;
3544 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3546 if (t == NULL)
3547 return (XT_CB_PASSTHROUGH);
3549 switch (args->i) {
3550 case XT_STATUSBAR_SHOW:
3551 if (show_statusbar == 0) {
3552 show_statusbar = 1;
3553 statusbar_set_visibility();
3555 break;
3556 case XT_STATUSBAR_HIDE:
3557 if (show_statusbar == 1) {
3558 show_statusbar = 0;
3559 statusbar_set_visibility();
3561 break;
3563 return (rv);
3567 urlaction(struct tab *t, struct karg *args)
3569 int rv = XT_CB_HANDLED;
3571 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3573 if (t == NULL)
3574 return (XT_CB_PASSTHROUGH);
3576 switch (args->i) {
3577 case XT_URL_SHOW:
3578 if (show_url == 0) {
3579 url_set(t, 1);
3580 url_set_visibility();
3582 break;
3583 case XT_URL_HIDE:
3584 if (show_url == 1) {
3585 url_set(t, 0);
3586 url_set_visibility();
3588 break;
3590 return (rv);
3594 tabaction(struct tab *t, struct karg *args)
3596 int rv = XT_CB_HANDLED;
3597 char *url = NULL;
3598 struct undo *u;
3600 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3602 if (t == NULL)
3603 return (XT_CB_PASSTHROUGH);
3605 switch (args->i) {
3606 case XT_TAB_NEW:
3607 if ((url = getparams(args->s, "tabnew")))
3608 create_new_tab(url, NULL, 1);
3609 else
3610 create_new_tab(NULL, NULL, 1);
3611 break;
3612 case XT_TAB_DELETE:
3613 delete_tab(t);
3614 break;
3615 case XT_TAB_DELQUIT:
3616 if (gtk_notebook_get_n_pages(notebook) > 1)
3617 delete_tab(t);
3618 else
3619 quit(t, args);
3620 break;
3621 case XT_TAB_OPEN:
3622 if ((url = getparams(args->s, "open")) ||
3623 ((url = getparams(args->s, "op"))) ||
3624 ((url = getparams(args->s, "o"))))
3626 else {
3627 rv = XT_CB_PASSTHROUGH;
3628 goto done;
3630 load_uri(t, url);
3631 break;
3632 case XT_TAB_SHOW:
3633 if (show_tabs == 0) {
3634 show_tabs = 1;
3635 notebook_tab_set_visibility(notebook);
3637 break;
3638 case XT_TAB_HIDE:
3639 if (show_tabs == 1) {
3640 show_tabs = 0;
3641 notebook_tab_set_visibility(notebook);
3643 break;
3644 case XT_TAB_UNDO_CLOSE:
3645 if (undo_count == 0) {
3646 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3647 goto done;
3648 } else {
3649 undo_count--;
3650 u = TAILQ_FIRST(&undos);
3651 create_new_tab(u->uri, u, 1);
3653 TAILQ_REMOVE(&undos, u, entry);
3654 g_free(u->uri);
3655 /* u->history is freed in create_new_tab() */
3656 g_free(u);
3658 break;
3659 default:
3660 rv = XT_CB_PASSTHROUGH;
3661 goto done;
3664 done:
3665 if (args->s) {
3666 g_free(args->s);
3667 args->s = NULL;
3670 return (rv);
3674 resizetab(struct tab *t, struct karg *args)
3676 if (t == NULL || args == NULL) {
3677 show_oops_s("resizetab invalid parameters");
3678 return (XT_CB_PASSTHROUGH);
3681 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3682 t->tab_id, args->i);
3684 adjustfont_webkit(t, args->i);
3686 return (XT_CB_HANDLED);
3690 movetab(struct tab *t, struct karg *args)
3692 struct tab *tt;
3693 int x;
3695 if (t == NULL || args == NULL) {
3696 show_oops_s("movetab invalid parameters");
3697 return (XT_CB_PASSTHROUGH);
3700 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3701 t->tab_id, args->i);
3703 if (args->i == XT_TAB_INVALID)
3704 return (XT_CB_PASSTHROUGH);
3706 if (args->i < XT_TAB_INVALID) {
3707 /* next or previous tab */
3708 if (TAILQ_EMPTY(&tabs))
3709 return (XT_CB_PASSTHROUGH);
3711 switch (args->i) {
3712 case XT_TAB_NEXT:
3713 /* if at the last page, loop around to the first */
3714 if (gtk_notebook_get_current_page(notebook) ==
3715 gtk_notebook_get_n_pages(notebook) - 1)
3716 gtk_notebook_set_current_page(notebook, 0);
3717 else
3718 gtk_notebook_next_page(notebook);
3719 break;
3720 case XT_TAB_PREV:
3721 /* if at the first page, loop around to the last */
3722 if (gtk_notebook_current_page(notebook) == 0)
3723 gtk_notebook_set_current_page(notebook,
3724 gtk_notebook_get_n_pages(notebook) - 1);
3725 else
3726 gtk_notebook_prev_page(notebook);
3727 break;
3728 case XT_TAB_FIRST:
3729 gtk_notebook_set_current_page(notebook, 0);
3730 break;
3731 case XT_TAB_LAST:
3732 gtk_notebook_set_current_page(notebook, -1);
3733 break;
3734 default:
3735 return (XT_CB_PASSTHROUGH);
3738 return (XT_CB_HANDLED);
3741 /* jump to tab */
3742 x = args->i - 1;
3743 if (t->tab_id == x) {
3744 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3745 return (XT_CB_HANDLED);
3748 TAILQ_FOREACH(tt, &tabs, entry) {
3749 if (tt->tab_id == x) {
3750 gtk_notebook_set_current_page(notebook, x);
3751 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
3752 if (tt->focus_wv)
3753 focus_webview(tt);
3757 return (XT_CB_HANDLED);
3761 command(struct tab *t, struct karg *args)
3763 char *s = NULL, *ss = NULL;
3764 GdkColor color;
3765 const gchar *uri;
3767 if (t == NULL || args == NULL) {
3768 show_oops_s("command invalid parameters");
3769 return (XT_CB_PASSTHROUGH);
3772 switch (args->i) {
3773 case '/':
3774 s = "/";
3775 break;
3776 case '?':
3777 s = "?";
3778 break;
3779 case ':':
3780 s = ":";
3781 break;
3782 case XT_CMD_OPEN:
3783 s = ":open ";
3784 break;
3785 case XT_CMD_TABNEW:
3786 s = ":tabnew ";
3787 break;
3788 case XT_CMD_OPEN_CURRENT:
3789 s = ":open ";
3790 /* FALL THROUGH */
3791 case XT_CMD_TABNEW_CURRENT:
3792 if (!s) /* FALL THROUGH? */
3793 s = ":tabnew ";
3794 if ((uri = get_uri(t->wv)) != NULL) {
3795 ss = g_strdup_printf("%s%s", s, uri);
3796 s = ss;
3798 break;
3799 default:
3800 show_oops(t, "command: invalid opcode %d", args->i);
3801 return (XT_CB_PASSTHROUGH);
3804 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3806 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3807 gdk_color_parse("white", &color);
3808 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3809 show_cmd(t);
3810 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3811 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3813 if (ss)
3814 g_free(ss);
3816 return (XT_CB_HANDLED);
3820 * Return a new string with a download row (in html)
3821 * appended. Old string is freed.
3823 char *
3824 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3827 WebKitDownloadStatus stat;
3828 char *status_html = NULL, *cmd_html = NULL, *new_html;
3829 gdouble progress;
3830 char cur_sz[FMT_SCALED_STRSIZE];
3831 char tot_sz[FMT_SCALED_STRSIZE];
3832 char *xtp_prefix;
3834 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3836 /* All actions wil take this form:
3837 * xxxt://class/seskey
3839 xtp_prefix = g_strdup_printf("%s%d/%s/",
3840 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3842 stat = webkit_download_get_status(dl->download);
3844 switch (stat) {
3845 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3846 status_html = g_strdup_printf("Finished");
3847 cmd_html = g_strdup_printf(
3848 "<a href='%s%d/%d'>Remove</a>",
3849 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3850 break;
3851 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3852 /* gather size info */
3853 progress = 100 * webkit_download_get_progress(dl->download);
3855 fmt_scaled(
3856 webkit_download_get_current_size(dl->download), cur_sz);
3857 fmt_scaled(
3858 webkit_download_get_total_size(dl->download), tot_sz);
3860 status_html = g_strdup_printf(
3861 "<div style='width: 100%%' align='center'>"
3862 "<div class='progress-outer'>"
3863 "<div class='progress-inner' style='width: %.2f%%'>"
3864 "</div></div></div>"
3865 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3866 progress, cur_sz, tot_sz, progress);
3868 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3869 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3871 break;
3872 /* LLL */
3873 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3874 status_html = g_strdup_printf("Cancelled");
3875 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3876 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3877 break;
3878 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3879 status_html = g_strdup_printf("Error!");
3880 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3881 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3882 break;
3883 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3884 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3885 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3886 status_html = g_strdup_printf("Starting");
3887 break;
3888 default:
3889 show_oops(t, "%s: unknown download status", __func__);
3892 new_html = g_strdup_printf(
3893 "%s\n<tr><td>%s</td><td>%s</td>"
3894 "<td style='text-align:center'>%s</td></tr>\n",
3895 html, basename(webkit_download_get_uri(dl->download)),
3896 status_html, cmd_html);
3897 g_free(html);
3899 if (status_html)
3900 g_free(status_html);
3902 if (cmd_html)
3903 g_free(cmd_html);
3905 g_free(xtp_prefix);
3907 return new_html;
3911 * update all download tabs apart from one. Pass NULL if
3912 * you want to update all.
3914 void
3915 update_download_tabs(struct tab *apart_from)
3917 struct tab *t;
3918 if (!updating_dl_tabs) {
3919 updating_dl_tabs = 1; /* stop infinite recursion */
3920 TAILQ_FOREACH(t, &tabs, entry)
3921 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
3922 && (t != apart_from))
3923 xtp_page_dl(t, NULL);
3924 updating_dl_tabs = 0;
3929 * update all cookie tabs apart from one. Pass NULL if
3930 * you want to update all.
3932 void
3933 update_cookie_tabs(struct tab *apart_from)
3935 struct tab *t;
3936 if (!updating_cl_tabs) {
3937 updating_cl_tabs = 1; /* stop infinite recursion */
3938 TAILQ_FOREACH(t, &tabs, entry)
3939 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
3940 && (t != apart_from))
3941 xtp_page_cl(t, NULL);
3942 updating_cl_tabs = 0;
3947 * update all history tabs apart from one. Pass NULL if
3948 * you want to update all.
3950 void
3951 update_history_tabs(struct tab *apart_from)
3953 struct tab *t;
3955 if (!updating_hl_tabs) {
3956 updating_hl_tabs = 1; /* stop infinite recursion */
3957 TAILQ_FOREACH(t, &tabs, entry)
3958 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
3959 && (t != apart_from))
3960 xtp_page_hl(t, NULL);
3961 updating_hl_tabs = 0;
3965 /* cookie management XTP page */
3967 xtp_page_cl(struct tab *t, struct karg *args)
3969 char *header, *body, *footer, *page, *tmp;
3970 int i = 1; /* all ids start 1 */
3971 GSList *sc, *pc, *pc_start;
3972 SoupCookie *c;
3973 char *type, *table_headers;
3974 char *last_domain = strdup("");
3976 DNPRINTF(XT_D_CMD, "%s", __func__);
3978 if (t == NULL) {
3979 show_oops_s("%s invalid parameters", __func__);
3980 return (1);
3982 /* mark this tab as cookie jar */
3983 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
3985 /* Generate a new session key */
3986 if (!updating_cl_tabs)
3987 generate_xtp_session_key(&cl_session_key);
3989 /* header */
3990 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3991 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
3992 "</head><body><h1>Cookie Jar</h1>\n");
3994 /* table headers */
3995 table_headers = g_strdup_printf("<div align='center'><table><tr>"
3996 "<th>Type</th>"
3997 "<th>Name</th>"
3998 "<th>Value</th>"
3999 "<th>Path</th>"
4000 "<th>Expires</th>"
4001 "<th>Secure</th>"
4002 "<th>HTTP<br />only</th>"
4003 "<th>Rm</th></tr>\n");
4005 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4006 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4007 pc_start = pc;
4009 body = NULL;
4010 for (; sc; sc = sc->next) {
4011 c = sc->data;
4013 if (strcmp(last_domain, c->domain) != 0) {
4014 /* new domain */
4015 free(last_domain);
4016 last_domain = strdup(c->domain);
4018 if (body != NULL) {
4019 tmp = body;
4020 body = g_strdup_printf("%s</table></div>"
4021 "<h2>%s</h2>%s\n",
4022 body, c->domain, table_headers);
4023 g_free(tmp);
4024 } else {
4025 /* first domain */
4026 body = g_strdup_printf("<h2>%s</h2>%s\n",
4027 c->domain, table_headers);
4031 type = "Session";
4032 for (pc = pc_start; pc; pc = pc->next)
4033 if (soup_cookie_equal(pc->data, c)) {
4034 type = "Session + Persistent";
4035 break;
4038 tmp = body;
4039 body = g_strdup_printf(
4040 "%s\n<tr>"
4041 "<td style='width: text-align: center'>%s</td>"
4042 "<td style='width: 1px'>%s</td>"
4043 "<td style='width=70%%;overflow: visible'>"
4044 " <textarea rows='4'>%s</textarea>"
4045 "</td>"
4046 "<td>%s</td>"
4047 "<td>%s</td>"
4048 "<td style='width: 1px; text-align: center'>%d</td>"
4049 "<td style='width: 1px; text-align: center'>%d</td>"
4050 "<td style='width: 1px; text-align: center'>"
4051 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4052 body,
4053 type,
4054 c->name,
4055 c->value,
4056 c->path,
4057 c->expires ?
4058 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4059 c->secure,
4060 c->http_only,
4062 XT_XTP_STR,
4063 XT_XTP_CL,
4064 cl_session_key,
4065 XT_XTP_CL_REMOVE,
4069 g_free(tmp);
4070 i++;
4073 soup_cookies_free(sc);
4074 soup_cookies_free(pc);
4076 /* small message if there are none */
4077 if (i == 1) {
4078 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4079 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4082 /* footer */
4083 footer = g_strdup_printf("</table></div></body></html>");
4085 page = g_strdup_printf("%s%s%s", header, body, footer);
4087 g_free(header);
4088 g_free(body);
4089 g_free(footer);
4090 g_free(table_headers);
4091 g_free(last_domain);
4093 _load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4094 update_cookie_tabs(t);
4096 g_free(page);
4098 return (0);
4102 xtp_page_hl(struct tab *t, struct karg *args)
4104 char *header, *body, *footer, *page, *tmp;
4105 struct history *h;
4106 int i = 1; /* all ids start 1 */
4108 DNPRINTF(XT_D_CMD, "%s", __func__);
4110 if (t == NULL) {
4111 show_oops_s("%s invalid parameters", __func__);
4112 return (1);
4115 /* mark this tab as history manager */
4116 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
4118 /* Generate a new session key */
4119 if (!updating_hl_tabs)
4120 generate_xtp_session_key(&hl_session_key);
4122 /* header */
4123 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
4124 "<title>History</title>\n"
4125 "%s"
4126 "</head>"
4127 "<h1>History</h1>\n",
4128 XT_PAGE_STYLE);
4130 /* body */
4131 body = g_strdup_printf("<div align='center'><table><tr>"
4132 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
4134 RB_FOREACH_REVERSE(h, history_list, &hl) {
4135 tmp = body;
4136 body = g_strdup_printf(
4137 "%s\n<tr>"
4138 "<td><a href='%s'>%s</a></td>"
4139 "<td>%s</td>"
4140 "<td style='text-align: center'>"
4141 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4142 body, h->uri, h->uri, h->title,
4143 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4144 XT_XTP_HL_REMOVE, i);
4146 g_free(tmp);
4147 i++;
4150 /* small message if there are none */
4151 if (i == 1) {
4152 tmp = body;
4153 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4154 "colspan='3'>No History</td></tr>\n", body);
4155 g_free(tmp);
4158 /* footer */
4159 footer = g_strdup_printf("</table></div></body></html>");
4161 page = g_strdup_printf("%s%s%s", header, body, footer);
4164 * update all history manager tabs as the xtp session
4165 * key has now changed. No need to update the current tab.
4166 * Already did that above.
4168 update_history_tabs(t);
4170 g_free(header);
4171 g_free(body);
4172 g_free(footer);
4174 _load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4175 g_free(page);
4177 return (0);
4181 * Generate a web page detailing the status of any downloads
4184 xtp_page_dl(struct tab *t, struct karg *args)
4186 struct download *dl;
4187 char *header, *body, *footer, *page, *tmp;
4188 char *ref;
4189 int n_dl = 1;
4191 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4193 if (t == NULL) {
4194 show_oops_s("%s invalid parameters", __func__);
4195 return (1);
4197 /* mark as a download manager tab */
4198 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
4201 * Generate a new session key for next page instance.
4202 * This only happens for the top level call to xtp_page_dl()
4203 * in which case updating_dl_tabs is 0.
4205 if (!updating_dl_tabs)
4206 generate_xtp_session_key(&dl_session_key);
4208 /* header - with refresh so as to update */
4209 if (refresh_interval >= 1)
4210 ref = g_strdup_printf(
4211 "<meta http-equiv='refresh' content='%u"
4212 ";url=%s%d/%s/%d' />\n",
4213 refresh_interval,
4214 XT_XTP_STR,
4215 XT_XTP_DL,
4216 dl_session_key,
4217 XT_XTP_DL_LIST);
4218 else
4219 ref = g_strdup("");
4222 header = g_strdup_printf(
4223 "%s\n<head>"
4224 "<title>Downloads</title>\n%s%s</head>\n",
4225 XT_DOCTYPE XT_HTML_TAG,
4226 ref,
4227 XT_PAGE_STYLE);
4229 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
4230 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4231 "</p><table><tr><th style='width: 60%%'>"
4232 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4233 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4235 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4236 body = xtp_page_dl_row(t, body, dl);
4237 n_dl++;
4240 /* message if no downloads in list */
4241 if (n_dl == 1) {
4242 tmp = body;
4243 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4244 " style='text-align: center'>"
4245 "No downloads</td></tr>\n", body);
4246 g_free(tmp);
4249 /* footer */
4250 footer = g_strdup_printf("</table></div></body></html>");
4252 page = g_strdup_printf("%s%s%s", header, body, footer);
4256 * update all download manager tabs as the xtp session
4257 * key has now changed. No need to update the current tab.
4258 * Already did that above.
4260 update_download_tabs(t);
4262 g_free(ref);
4263 g_free(header);
4264 g_free(body);
4265 g_free(footer);
4267 _load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4268 g_free(page);
4270 return (0);
4274 search(struct tab *t, struct karg *args)
4276 gboolean d;
4278 if (t == NULL || args == NULL) {
4279 show_oops_s("search invalid parameters");
4280 return (1);
4282 if (t->search_text == NULL) {
4283 if (global_search == NULL)
4284 return (XT_CB_PASSTHROUGH);
4285 else {
4286 t->search_text = g_strdup(global_search);
4287 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4288 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4292 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4293 t->tab_id, args->i, t->search_forward, t->search_text);
4295 switch (args->i) {
4296 case XT_SEARCH_NEXT:
4297 d = t->search_forward;
4298 break;
4299 case XT_SEARCH_PREV:
4300 d = !t->search_forward;
4301 break;
4302 default:
4303 return (XT_CB_PASSTHROUGH);
4306 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4308 return (XT_CB_HANDLED);
4311 struct settings_args {
4312 char **body;
4313 int i;
4316 void
4317 print_setting(struct settings *s, char *val, void *cb_args)
4319 char *tmp, *color;
4320 struct settings_args *sa = cb_args;
4322 if (sa == NULL)
4323 return;
4325 if (s->flags & XT_SF_RUNTIME)
4326 color = "#22cc22";
4327 else
4328 color = "#cccccc";
4330 tmp = *sa->body;
4331 *sa->body = g_strdup_printf(
4332 "%s\n<tr>"
4333 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
4334 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
4335 *sa->body,
4336 color,
4337 s->name,
4338 color,
4341 g_free(tmp);
4342 sa->i++;
4346 set(struct tab *t, struct karg *args)
4348 char *header, *body, *footer, *page, *tmp, *pars;
4349 int i = 1;
4350 struct settings_args sa;
4352 if ((pars = getparams(args->s, "set")) == NULL) {
4353 bzero(&sa, sizeof sa);
4354 sa.body = &body;
4356 /* header */
4357 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4358 "\n<head><title>Settings</title>\n"
4359 "</head><body><h1>Settings</h1>\n");
4361 /* body */
4362 body = g_strdup_printf("<div align='center'><table><tr>"
4363 "<th align='left'>Setting</th>"
4364 "<th align='left'>Value</th></tr>\n");
4366 settings_walk(print_setting, &sa);
4367 i = sa.i;
4369 /* small message if there are none */
4370 if (i == 1) {
4371 tmp = body;
4372 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4373 "colspan='2'>No settings</td></tr>\n", body);
4374 g_free(tmp);
4377 /* footer */
4378 footer = g_strdup_printf("</table></div></body></html>");
4380 page = g_strdup_printf("%s%s%s", header, body, footer);
4382 g_free(header);
4383 g_free(body);
4384 g_free(footer);
4386 _load_webkit_string(t, page, XT_URI_ABOUT_SET);
4387 } else
4388 show_oops(t, "Invalid command: %s", pars);
4390 return (XT_CB_PASSTHROUGH);
4394 session_save(struct tab *t, char *filename, char **ret)
4396 struct karg a;
4397 char *f = filename;
4398 int rv = 1;
4400 f += strlen("save");
4401 while (*f == ' ' && *f != '\0')
4402 f++;
4403 if (strlen(f) == 0)
4404 goto done;
4406 *ret = f;
4407 if (f[0] == '.' || f[0] == '/')
4408 goto done;
4410 a.s = f;
4411 if (save_tabs(t, &a))
4412 goto done;
4413 strlcpy(named_session, f, sizeof named_session);
4415 rv = 0;
4416 done:
4417 return (rv);
4421 session_open(struct tab *t, char *filename, char **ret)
4423 struct karg a;
4424 char *f = filename;
4425 int rv = 1;
4427 f += strlen("open");
4428 while (*f == ' ' && *f != '\0')
4429 f++;
4430 if (strlen(f) == 0)
4431 goto done;
4433 *ret = f;
4434 if (f[0] == '.' || f[0] == '/')
4435 goto done;
4437 a.s = f;
4438 a.i = XT_SES_CLOSETABS;
4439 if (open_tabs(t, &a))
4440 goto done;
4442 strlcpy(named_session, f, sizeof named_session);
4444 rv = 0;
4445 done:
4446 return (rv);
4450 session_delete(struct tab *t, char *filename, char **ret)
4452 char file[PATH_MAX];
4453 char *f = filename;
4454 int rv = 1;
4456 f += strlen("delete");
4457 while (*f == ' ' && *f != '\0')
4458 f++;
4459 if (strlen(f) == 0)
4460 goto done;
4462 *ret = f;
4463 if (f[0] == '.' || f[0] == '/')
4464 goto done;
4466 snprintf(file, sizeof file, "%s/%s", sessions_dir, f);
4467 if (unlink(file))
4468 goto done;
4470 if (!strcmp(f, named_session))
4471 strlcpy(named_session, XT_SAVED_TABS_FILE,
4472 sizeof named_session);
4474 rv = 0;
4475 done:
4476 return (rv);
4480 session_cmd(struct tab *t, struct karg *args)
4482 char *action = NULL;
4483 char *filename = NULL;
4485 if (t == NULL)
4486 return (1);
4488 if ((action = getparams(args->s, "session")))
4490 else
4491 action = "show";
4493 if (!strcmp(action, "show"))
4494 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4495 XT_SAVED_TABS_FILE : named_session);
4496 else if (g_str_has_prefix(action, "save ")) {
4497 if (session_save(t, action, &filename)) {
4498 show_oops(t, "Can't save session: %s",
4499 filename ? filename : "INVALID");
4500 goto done;
4502 } else if (g_str_has_prefix(action, "open ")) {
4503 if (session_open(t, action, &filename)) {
4504 show_oops(t, "Can't open session: %s",
4505 filename ? filename : "INVALID");
4506 goto done;
4508 } else if (g_str_has_prefix(action, "delete ")) {
4509 if (session_delete(t, action, &filename)) {
4510 show_oops(t, "Can't delete session: %s",
4511 filename ? filename : "INVALID");
4512 goto done;
4514 } else
4515 show_oops(t, "Invalid command: %s", action);
4516 done:
4517 return (XT_CB_PASSTHROUGH);
4521 * Make a hardcopy of the page
4524 print_page(struct tab *t, struct karg *args)
4526 WebKitWebFrame *frame;
4527 GtkPageSetup *ps;
4528 GtkPrintOperation *op;
4529 GtkPrintOperationAction action;
4530 GtkPrintOperationResult print_res;
4531 GError *g_err = NULL;
4532 int marg_l, marg_r, marg_t, marg_b;
4534 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4536 ps = gtk_page_setup_new();
4537 op = gtk_print_operation_new();
4538 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4539 frame = webkit_web_view_get_main_frame(t->wv);
4541 /* the default margins are too small, so we will bump them */
4542 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4543 XT_PRINT_EXTRA_MARGIN;
4544 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4545 XT_PRINT_EXTRA_MARGIN;
4546 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4547 XT_PRINT_EXTRA_MARGIN;
4548 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4549 XT_PRINT_EXTRA_MARGIN;
4551 /* set margins */
4552 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4553 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4554 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4555 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4557 gtk_print_operation_set_default_page_setup(op, ps);
4559 /* this appears to free 'op' and 'ps' */
4560 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4562 /* check it worked */
4563 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4564 show_oops_s("can't print: %s", g_err->message);
4565 g_error_free (g_err);
4566 return (1);
4569 return (0);
4573 go_home(struct tab *t, struct karg *args)
4575 load_uri(t, home);
4576 return (0);
4580 restart(struct tab *t, struct karg *args)
4582 struct karg a;
4584 a.s = XT_RESTART_TABS_FILE;
4585 save_tabs(t, &a);
4586 execvp(start_argv[0], start_argv);
4587 /* NOTREACHED */
4589 return (0);
4592 #define CTRL GDK_CONTROL_MASK
4593 #define MOD1 GDK_MOD1_MASK
4594 #define SHFT GDK_SHIFT_MASK
4596 /* inherent to GTK not all keys will be caught at all times */
4597 /* XXX sort key bindings */
4598 struct key_binding {
4599 char *name;
4600 guint mask;
4601 guint use_in_entry;
4602 guint key;
4603 int (*func)(struct tab *, struct karg *);
4604 struct karg arg;
4605 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4606 } keys[] = {
4607 { "cookiejar", MOD1, 0, GDK_j, xtp_page_cl, {0} },
4608 { "downloadmgr", MOD1, 0, GDK_d, xtp_page_dl, {0} },
4609 { "history", MOD1, 0, GDK_h, xtp_page_hl, {0} },
4610 { "print", CTRL, 0, GDK_p, print_page, {0}},
4611 { NULL, 0, 0, GDK_slash, command, {.i = '/'} },
4612 { NULL, 0, 0, GDK_question, command, {.i = '?'} },
4613 { NULL, 0, 0, GDK_colon, command, {.i = ':'} },
4614 { "quit", CTRL, 0, GDK_q, quit, {0} },
4615 { "restart", MOD1, 0, GDK_q, restart, {0} },
4616 { "togglejs", CTRL, 0, GDK_j, toggle_js, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
4617 { "togglecookie", MOD1, 0, GDK_c, toggle_cwl, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
4618 { "togglesrc", CTRL, 0, GDK_s, toggle_src, {0} },
4619 { "yankuri", 0, 0, GDK_y, yank_uri, {0} },
4620 { "pasteuricur", 0, 0, GDK_p, paste_uri, {.i = XT_PASTE_CURRENT_TAB} },
4621 { "pasteurinew", 0, 0, GDK_P, paste_uri, {.i = XT_PASTE_NEW_TAB} },
4623 /* search */
4624 { "searchnext", 0, 0, GDK_n, search, {.i = XT_SEARCH_NEXT} },
4625 { "searchprev", 0, 0, GDK_N, search, {.i = XT_SEARCH_PREV} },
4627 /* focus */
4628 { "focusaddress", 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} },
4629 { "focussearch", 0, 0, GDK_F7, focus, {.i = XT_FOCUS_SEARCH} },
4631 /* command aliases (handy when -S flag is used) */
4632 { NULL, 0, 0, GDK_F9, command, {.i = XT_CMD_OPEN} },
4633 { NULL, 0, 0, GDK_F10, command, {.i = XT_CMD_OPEN_CURRENT} },
4634 { NULL, 0, 0, GDK_F11, command, {.i = XT_CMD_TABNEW} },
4635 { NULL, 0, 0, GDK_F12, command, {.i = XT_CMD_TABNEW_CURRENT} },
4637 /* hinting */
4638 { "hinting", 0, 0, GDK_f, hint, {.i = 0} },
4640 /* navigation */
4641 { "goback", 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
4642 { "goback", MOD1, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
4643 { "goforward", SHFT, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
4644 { "goforward", MOD1, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
4645 { "reload", 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} },
4646 { "reload", CTRL, 0, GDK_r, navaction, {.i = XT_NAV_RELOAD} },
4647 { "reloadforce", CTRL, 0, GDK_R, navaction, {.i = XT_NAV_RELOAD_CACHE} },
4648 { "reload" , CTRL, 0, GDK_l, navaction, {.i = XT_NAV_RELOAD} },
4649 { "favorites", MOD1, 1, GDK_f, xtp_page_fl, {0} },
4651 /* vertical movement */
4652 { "scrolldown", 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
4653 { "scrolldown", 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
4654 { "scrollup", 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
4655 { "scrollup", 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
4656 { "scrollbottom", 0, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
4657 { "scrollbottom", 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
4658 { "scrolltop", 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
4659 { "scrolltop", 0, 0, GDK_g, move, {.i = XT_MOVE_TOP} },
4660 { "scrollpagedown", 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
4661 { "scrollpagedown", CTRL, 0, GDK_f, move, {.i = XT_MOVE_PAGEDOWN} },
4662 { "scrollhalfdown", CTRL, 0, GDK_d, move, {.i = XT_MOVE_HALFDOWN} },
4663 { "scrollpagedown", 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
4664 { "scrollpageup", 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
4665 { "scrollpageup", CTRL, 0, GDK_b, move, {.i = XT_MOVE_PAGEUP} },
4666 { "scrollhalfup", CTRL, 0, GDK_u, move, {.i = XT_MOVE_HALFUP} },
4667 /* horizontal movement */
4668 { "scrollright", 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
4669 { "scrollright", 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
4670 { "scrollleft", 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
4671 { "scrollleft", 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
4672 { "scrollfarright", 0, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
4673 { "scrollfarleft", 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
4675 /* tabs */
4676 { "tabnew", CTRL, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
4677 { "tabclose", CTRL, 1, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
4678 { "tabundoclose", 0, 0, GDK_U, tabaction, {.i = XT_TAB_UNDO_CLOSE} },
4679 { "tabgoto1", CTRL, 0, GDK_1, movetab, {.i = 1} },
4680 { "tabgoto2", CTRL, 0, GDK_2, movetab, {.i = 2} },
4681 { "tabgoto3", CTRL, 0, GDK_3, movetab, {.i = 3} },
4682 { "tabgoto4", CTRL, 0, GDK_4, movetab, {.i = 4} },
4683 { "tabgoto5", CTRL, 0, GDK_5, movetab, {.i = 5} },
4684 { "tabgoto6", CTRL, 0, GDK_6, movetab, {.i = 6} },
4685 { "tabgoto7", CTRL, 0, GDK_7, movetab, {.i = 7} },
4686 { "tabgoto8", CTRL, 0, GDK_8, movetab, {.i = 8} },
4687 { "tabgoto9", CTRL, 0, GDK_9, movetab, {.i = 9} },
4688 { "tabgoto10", CTRL, 0, GDK_0, movetab, {.i = 10} },
4689 { "tabgotofirst", CTRL, 0, GDK_less, movetab, {.i = XT_TAB_FIRST} },
4690 { "tabgotolast", CTRL, 0, GDK_greater, movetab, {.i = XT_TAB_LAST} },
4691 { "tabgotoprev", CTRL, 0, GDK_Left, movetab, {.i = XT_TAB_PREV} },
4692 { "tabgotonext", CTRL, 0, GDK_Right, movetab, {.i = XT_TAB_NEXT} },
4693 { "focusout", CTRL, 0, GDK_minus, resizetab, {.i = -1} },
4694 { "focusin", CTRL, 0, GDK_plus, resizetab, {.i = 1} },
4695 { "focusin", CTRL, 0, GDK_equal, resizetab, {.i = 1} },
4697 TAILQ_HEAD(keybinding_list, key_binding);
4699 void
4700 walk_kb(struct settings *s,
4701 void (*cb)(struct settings *, char *, void *), void *cb_args)
4703 struct key_binding *k;
4704 char str[1024];
4706 if (s == NULL || cb == NULL) {
4707 show_oops_s("walk_kb invalid parameters");
4708 return;
4711 TAILQ_FOREACH(k, &kbl, entry) {
4712 if (k->name == NULL)
4713 continue;
4714 str[0] = '\0';
4716 /* sanity */
4717 if (gdk_keyval_name(k->key) == NULL)
4718 continue;
4720 strlcat(str, k->name, sizeof str);
4721 strlcat(str, ",", sizeof str);
4723 if (k->mask & GDK_SHIFT_MASK)
4724 strlcat(str, "S-", sizeof str);
4725 if (k->mask & GDK_CONTROL_MASK)
4726 strlcat(str, "C-", sizeof str);
4727 if (k->mask & GDK_MOD1_MASK)
4728 strlcat(str, "M1-", sizeof str);
4729 if (k->mask & GDK_MOD2_MASK)
4730 strlcat(str, "M2-", sizeof str);
4731 if (k->mask & GDK_MOD3_MASK)
4732 strlcat(str, "M3-", sizeof str);
4733 if (k->mask & GDK_MOD4_MASK)
4734 strlcat(str, "M4-", sizeof str);
4735 if (k->mask & GDK_MOD5_MASK)
4736 strlcat(str, "M5-", sizeof str);
4738 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4739 cb(s, str, cb_args);
4742 void
4743 init_keybindings(void)
4745 int i;
4746 struct key_binding *k;
4748 for (i = 0; i < LENGTH(keys); i++) {
4749 k = g_malloc0(sizeof *k);
4750 k->name = keys[i].name;
4751 k->mask = keys[i].mask;
4752 k->use_in_entry = keys[i].use_in_entry;
4753 k->key = keys[i].key;
4754 k->func = keys[i].func;
4755 bcopy(&keys[i].arg, &k->arg, sizeof k->arg);
4756 TAILQ_INSERT_HEAD(&kbl, k, entry);
4758 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4759 k->name ? k->name : "unnamed key");
4763 void
4764 keybinding_clearall(void)
4766 struct key_binding *k, *next;
4768 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4769 next = TAILQ_NEXT(k, entry);
4770 if (k->name == NULL)
4771 continue;
4773 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4774 k->name ? k->name : "unnamed key");
4775 TAILQ_REMOVE(&kbl, k, entry);
4776 g_free(k);
4781 keybinding_add(char *kb, char *value, struct key_binding *orig)
4783 struct key_binding *k;
4784 guint keyval, mask = 0;
4785 int i;
4787 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s %s\n", kb, value, orig->name);
4789 if (orig == NULL)
4790 return (1);
4791 if (strcmp(kb, orig->name))
4792 return (1);
4794 /* find modifier keys */
4795 if (strstr(value, "S-"))
4796 mask |= GDK_SHIFT_MASK;
4797 if (strstr(value, "C-"))
4798 mask |= GDK_CONTROL_MASK;
4799 if (strstr(value, "M1-"))
4800 mask |= GDK_MOD1_MASK;
4801 if (strstr(value, "M2-"))
4802 mask |= GDK_MOD2_MASK;
4803 if (strstr(value, "M3-"))
4804 mask |= GDK_MOD3_MASK;
4805 if (strstr(value, "M4-"))
4806 mask |= GDK_MOD4_MASK;
4807 if (strstr(value, "M5-"))
4808 mask |= GDK_MOD5_MASK;
4810 /* find keyname */
4811 for (i = strlen(value) - 1; i > 0; i--)
4812 if (value[i] == '-')
4813 value = &value[i + 1];
4815 /* validate keyname */
4816 keyval = gdk_keyval_from_name(value);
4817 if (keyval == GDK_VoidSymbol) {
4818 warnx("invalid keybinding name %s", value);
4819 return (1);
4821 /* must run this test too, gtk+ doesn't handle 10 for example */
4822 if (gdk_keyval_name(keyval) == NULL) {
4823 warnx("invalid keybinding name %s", value);
4824 return (1);
4827 /* make sure it isn't a dupe */
4828 TAILQ_FOREACH(k, &kbl, entry)
4829 if (k->key == keyval && k->mask == mask) {
4830 warnx("duplicate keybinding for %s", value);
4831 return (1);
4834 /* add keyname */
4835 k = g_malloc0(sizeof *k);
4836 k->name = orig->name;
4837 k->mask = mask;
4838 k->use_in_entry = orig->use_in_entry;
4839 k->key = keyval;
4840 k->func = orig->func;
4841 bcopy(&orig->arg, &k->arg, sizeof k->arg);
4843 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
4844 k->name,
4845 k->mask,
4846 k->use_in_entry,
4847 k->key);
4848 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
4849 k->name, gdk_keyval_name(keyval));
4851 TAILQ_INSERT_HEAD(&kbl, k, entry);
4853 return (0);
4857 add_kb(struct settings *s, char *entry)
4859 int i;
4860 char *kb, *value;
4862 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
4864 /* clearall is special */
4865 if (!strcmp(entry, "clearall")) {
4866 keybinding_clearall();
4867 return (0);
4870 kb = strstr(entry, ",");
4871 if (kb == NULL)
4872 return (1);
4873 *kb = '\0';
4874 value = kb + 1;
4876 /* make sure it is a valid keybinding */
4877 for (i = 0; i < LENGTH(keys); i++)
4878 if (keys[i].name && !strcmp(entry, keys[i].name)) {
4879 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s 0x%x %d 0x%x\n",
4880 keys[i].name,
4881 keys[i].mask,
4882 keys[i].use_in_entry,
4883 keys[i].key);
4885 return (keybinding_add(entry, value, &keys[i]));
4888 return (1);
4891 struct cmd {
4892 char *cmd;
4893 int params;
4894 int (*func)(struct tab *, struct karg *);
4895 struct karg arg;
4896 } cmds[] = {
4897 { "q!", 0, quit, {0} },
4898 { "qa", 0, quit, {0} },
4899 { "qa!", 0, quit, {0} },
4900 { "w", 0, save_tabs, {0} },
4901 { "wq", 0, save_tabs_and_quit, {0} },
4902 { "wq!", 0, save_tabs_and_quit, {0} },
4903 { "help", 0, help, {0} },
4904 { "about", 0, about, {0} },
4905 { "stats", 0, stats, {0} },
4906 { "version", 0, about, {0} },
4907 { "cookies", 0, xtp_page_cl, {0} },
4908 { "fav", 0, xtp_page_fl, {0} },
4909 { "favadd", 0, add_favorite, {0} },
4910 { "js", 2, js_cmd, {0} },
4911 { "cookie", 2, cookie_cmd, {0} },
4912 { "cert", 1, cert_cmd, {0} },
4913 { "ca", 0, ca_cmd, {0} },
4914 { "dl", 0, xtp_page_dl, {0} },
4915 { "h", 0, xtp_page_hl, {0} },
4916 { "hist", 0, xtp_page_hl, {0} },
4917 { "history", 0, xtp_page_hl, {0} },
4918 { "home", 0, go_home, {0} },
4919 { "restart", 0, restart, {0} },
4920 { "urlhide", 0, urlaction, {.i = XT_URL_HIDE} },
4921 { "urlh", 0, urlaction, {.i = XT_URL_HIDE} },
4922 { "urlshow", 0, urlaction, {.i = XT_URL_SHOW} },
4923 { "urls", 0, urlaction, {.i = XT_URL_SHOW} },
4924 { "statushide", 0, statusaction, {.i = XT_STATUSBAR_HIDE} },
4925 { "statush", 0, statusaction, {.i = XT_STATUSBAR_HIDE} },
4926 { "statusshow", 0, statusaction, {.i = XT_STATUSBAR_SHOW} },
4927 { "statuss", 0, statusaction, {.i = XT_STATUSBAR_SHOW} },
4929 { "1", 0, move, {.i = XT_MOVE_TOP} },
4930 { "print", 0, print_page, {0} },
4932 /* tabs */
4933 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
4934 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
4935 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
4936 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
4937 { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} },
4938 { "tabe", 1, tabaction, {.i = XT_TAB_NEW} },
4939 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
4940 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} },
4941 { "tabshow", 1, tabaction, {.i = XT_TAB_SHOW} },
4942 { "tabs", 1, tabaction, {.i = XT_TAB_SHOW} },
4943 { "tabhide", 1, tabaction, {.i = XT_TAB_HIDE} },
4944 { "tabh", 1, tabaction, {.i = XT_TAB_HIDE} },
4945 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4946 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4947 /* XXX add count to these commands */
4948 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST} },
4949 { "tabfir", 0, movetab, {.i = XT_TAB_FIRST} },
4950 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST} },
4951 { "tabr", 0, movetab, {.i = XT_TAB_FIRST} },
4952 { "tablast", 0, movetab, {.i = XT_TAB_LAST} },
4953 { "tabl", 0, movetab, {.i = XT_TAB_LAST} },
4954 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
4955 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
4956 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
4957 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
4959 /* settings */
4960 { "set", 1, set, {0} },
4961 { "fullscreen", 0, fullscreen, {0} },
4962 { "f", 0, fullscreen, {0} },
4964 /* sessions */
4965 { "session", 1, session_cmd, {0} },
4968 gboolean
4969 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
4971 hide_oops(t);
4973 return (FALSE);
4976 gboolean
4977 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
4979 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
4981 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
4982 delete_tab(t);
4984 return (FALSE);
4988 * cancel, remove, etc. downloads
4990 void
4991 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
4993 struct download find, *d;
4995 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
4997 /* some commands require a valid download id */
4998 if (cmd != XT_XTP_DL_LIST) {
4999 /* lookup download in question */
5000 find.id = id;
5001 d = RB_FIND(download_list, &downloads, &find);
5003 if (d == NULL) {
5004 show_oops(t, "%s: no such download", __func__);
5005 return;
5009 /* decide what to do */
5010 switch (cmd) {
5011 case XT_XTP_DL_CANCEL:
5012 webkit_download_cancel(d->download);
5013 break;
5014 case XT_XTP_DL_REMOVE:
5015 webkit_download_cancel(d->download); /* just incase */
5016 g_object_unref(d->download);
5017 RB_REMOVE(download_list, &downloads, d);
5018 break;
5019 case XT_XTP_DL_LIST:
5020 /* Nothing */
5021 break;
5022 default:
5023 show_oops(t, "%s: unknown command", __func__);
5024 break;
5026 xtp_page_dl(t, NULL);
5030 * Actions on history, only does one thing for now, but
5031 * we provide the function for future actions
5033 void
5034 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5036 struct history *h, *next;
5037 int i = 1;
5039 switch (cmd) {
5040 case XT_XTP_HL_REMOVE:
5041 /* walk backwards, as listed in reverse */
5042 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5043 next = RB_PREV(history_list, &hl, h);
5044 if (id == i) {
5045 RB_REMOVE(history_list, &hl, h);
5046 g_free((gpointer) h->title);
5047 g_free((gpointer) h->uri);
5048 g_free(h);
5049 break;
5051 i++;
5053 break;
5054 case XT_XTP_HL_LIST:
5055 /* Nothing - just xtp_page_hl() below */
5056 break;
5057 default:
5058 show_oops(t, "%s: unknown command", __func__);
5059 break;
5062 xtp_page_hl(t, NULL);
5065 /* remove a favorite */
5066 void
5067 remove_favorite(struct tab *t, int index)
5069 char file[PATH_MAX], *title, *uri;
5070 char *new_favs, *tmp;
5071 FILE *f;
5072 int i;
5073 size_t len, lineno;
5075 /* open favorites */
5076 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5078 if ((f = fopen(file, "r")) == NULL) {
5079 show_oops(t, "%s: can't open favorites: %s",
5080 __func__, strerror(errno));
5081 return;
5084 /* build a string which will become the new favroites file */
5085 new_favs = g_strdup_printf("%s", "");
5087 for (i = 1;;) {
5088 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5089 if (feof(f) || ferror(f))
5090 break;
5091 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5092 if (len == 0) {
5093 free(title);
5094 title = NULL;
5095 continue;
5098 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5099 if (feof(f) || ferror(f)) {
5100 show_oops(t, "%s: can't parse favorites %s",
5101 __func__, strerror(errno));
5102 goto clean;
5106 /* as long as this isn't the one we are deleting add to file */
5107 if (i != index) {
5108 tmp = new_favs;
5109 new_favs = g_strdup_printf("%s%s\n%s\n",
5110 new_favs, title, uri);
5111 g_free(tmp);
5114 free(uri);
5115 uri = NULL;
5116 free(title);
5117 title = NULL;
5118 i++;
5120 fclose(f);
5122 /* write back new favorites file */
5123 if ((f = fopen(file, "w")) == NULL) {
5124 show_oops(t, "%s: can't open favorites: %s",
5125 __func__, strerror(errno));
5126 goto clean;
5129 fwrite(new_favs, strlen(new_favs), 1, f);
5130 fclose(f);
5132 clean:
5133 if (uri)
5134 free(uri);
5135 if (title)
5136 free(title);
5138 g_free(new_favs);
5141 void
5142 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5144 switch (cmd) {
5145 case XT_XTP_FL_LIST:
5146 /* nothing, just the below call to xtp_page_fl() */
5147 break;
5148 case XT_XTP_FL_REMOVE:
5149 remove_favorite(t, arg);
5150 break;
5151 default:
5152 show_oops(t, "%s: invalid favorites command", __func__);
5153 break;
5156 xtp_page_fl(t, NULL);
5159 void
5160 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5162 switch (cmd) {
5163 case XT_XTP_CL_LIST:
5164 /* nothing, just xtp_page_cl() */
5165 break;
5166 case XT_XTP_CL_REMOVE:
5167 remove_cookie(arg);
5168 break;
5169 default:
5170 show_oops(t, "%s: unknown cookie xtp command", __func__);
5171 break;
5174 xtp_page_cl(t, NULL);
5177 /* link an XTP class to it's session key and handler function */
5178 struct xtp_despatch {
5179 uint8_t xtp_class;
5180 char **session_key;
5181 void (*handle_func)(struct tab *, uint8_t, int);
5184 struct xtp_despatch xtp_despatches[] = {
5185 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5186 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5187 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5188 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5189 { NULL, NULL, NULL }
5193 * is the url xtp protocol? (xxxt://)
5194 * if so, parse and despatch correct bahvior
5197 parse_xtp_url(struct tab *t, const char *url)
5199 char *dup = NULL, *p, *last;
5200 uint8_t n_tokens = 0;
5201 char *tokens[4] = {NULL, NULL, NULL, ""};
5202 struct xtp_despatch *dsp, *dsp_match = NULL;
5203 uint8_t req_class;
5206 * tokens array meaning:
5207 * tokens[0] = class
5208 * tokens[1] = session key
5209 * tokens[2] = action
5210 * tokens[3] = optional argument
5213 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5215 /*xtp tab meaning is normal unless proven special */
5216 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5218 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5219 return 0;
5221 dup = g_strdup(url + strlen(XT_XTP_STR));
5223 /* split out the url */
5224 for ((p = strtok_r(dup, "/", &last)); p;
5225 (p = strtok_r(NULL, "/", &last))) {
5226 if (n_tokens < 4)
5227 tokens[n_tokens++] = p;
5230 /* should be atleast three fields 'class/seskey/command/arg' */
5231 if (n_tokens < 3)
5232 goto clean;
5234 dsp = xtp_despatches;
5235 req_class = atoi(tokens[0]);
5236 while (dsp->xtp_class != NULL) {
5237 if (dsp->xtp_class == req_class) {
5238 dsp_match = dsp;
5239 break;
5241 dsp++;
5244 /* did we find one atall? */
5245 if (dsp_match == NULL) {
5246 show_oops(t, "%s: no matching xtp despatch found", __func__);
5247 goto clean;
5250 /* check session key and call despatch function */
5251 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5252 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5255 clean:
5256 if (dup)
5257 g_free(dup);
5259 return 1;
5264 void
5265 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5267 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5269 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5271 if (t == NULL) {
5272 show_oops_s("activate_uri_entry_cb invalid parameters");
5273 return;
5276 if (uri == NULL) {
5277 show_oops(t, "activate_uri_entry_cb no uri");
5278 return;
5281 uri += strspn(uri, "\t ");
5283 /* if xxxt:// treat specially */
5284 if (!parse_xtp_url(t, uri)) {
5285 load_uri(t, (gchar *)uri);
5286 focus_webview(t);
5290 void
5291 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5293 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5294 char *newuri = NULL;
5295 gchar *enc_search;
5297 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5299 if (t == NULL) {
5300 show_oops_s("activate_search_entry_cb invalid parameters");
5301 return;
5304 if (search_string == NULL) {
5305 show_oops(t, "no search_string");
5306 return;
5309 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5310 newuri = g_strdup_printf(search_string, enc_search);
5311 g_free(enc_search);
5313 webkit_web_view_load_uri(t->wv, newuri);
5314 focus_webview(t);
5316 if (newuri)
5317 g_free(newuri);
5320 void
5321 check_and_set_js(const gchar *uri, struct tab *t)
5323 struct domain *d = NULL;
5324 int es = 0;
5326 if (uri == NULL || t == NULL)
5327 return;
5329 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5330 es = 0;
5331 else
5332 es = 1;
5334 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5335 es ? "enable" : "disable", uri);
5337 g_object_set(G_OBJECT(t->settings),
5338 "enable-scripts", es, (char *)NULL);
5339 webkit_web_view_set_settings(t->wv, t->settings);
5341 button_set_stockid(t->js_toggle,
5342 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5345 void
5346 show_ca_status(struct tab *t, const char *uri)
5348 WebKitWebFrame *frame;
5349 WebKitWebDataSource *source;
5350 WebKitNetworkRequest *request;
5351 SoupMessage *message;
5352 GdkColor color;
5353 gchar *col_str = "white";
5354 int r;
5356 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5357 ssl_strict_certs, ssl_ca_file, uri);
5359 if (uri == NULL)
5360 goto done;
5361 if (ssl_ca_file == NULL) {
5362 if (g_str_has_prefix(uri, "http://"))
5363 goto done;
5364 if (g_str_has_prefix(uri, "https://")) {
5365 col_str = "red";
5366 goto done;
5368 return;
5370 if (g_str_has_prefix(uri, "http://") ||
5371 !g_str_has_prefix(uri, "https://"))
5372 goto done;
5374 frame = webkit_web_view_get_main_frame(t->wv);
5375 source = webkit_web_frame_get_data_source(frame);
5376 request = webkit_web_data_source_get_request(source);
5377 message = webkit_network_request_get_message(request);
5379 if (message && (soup_message_get_flags(message) &
5380 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5381 col_str = "green";
5382 goto done;
5383 } else {
5384 r = load_compare_cert(t, NULL);
5385 if (r == 0)
5386 col_str = "lightblue";
5387 else if (r == 1)
5388 col_str = "yellow";
5389 else
5390 col_str = "red";
5391 goto done;
5393 done:
5394 if (col_str) {
5395 gdk_color_parse(col_str, &color);
5396 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5398 if (!strcmp(col_str, "white")) {
5399 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5400 &color);
5401 gdk_color_parse("black", &color);
5402 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5403 &color);
5404 } else {
5405 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5406 &color);
5407 gdk_color_parse("black", &color);
5408 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5409 &color);
5414 void
5415 free_favicon(struct tab *t)
5417 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p pix %p\n",
5418 __func__, t->icon_download, t->icon_request, t->icon_pixbuf);
5420 if (t->icon_request)
5421 g_object_unref(t->icon_request);
5422 if (t->icon_pixbuf)
5423 g_object_unref(t->icon_pixbuf);
5424 if (t->icon_dest_uri)
5425 g_free(t->icon_dest_uri);
5427 t->icon_pixbuf = NULL;
5428 t->icon_request = NULL;
5429 t->icon_dest_uri = NULL;
5432 void
5433 xt_icon_from_name(struct tab *t, gchar *name)
5435 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5436 GTK_ENTRY_ICON_PRIMARY, "text-html");
5437 if (show_url == 0) {
5438 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5439 GTK_ENTRY_ICON_PRIMARY, "text-html");
5440 } else {
5441 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5442 GTK_ENTRY_ICON_PRIMARY, NULL);
5444 return;
5447 void
5448 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pixbuf)
5450 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5451 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5452 if (show_url == 0) {
5453 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5454 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5455 } else {
5456 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5457 GTK_ENTRY_ICON_PRIMARY, NULL);
5461 void
5462 abort_favicon_download(struct tab *t)
5464 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5466 if (t->icon_download)
5467 webkit_download_cancel(t->icon_download);
5468 else
5469 free_favicon(t);
5471 xt_icon_from_name(t, "text-html");
5474 void
5475 set_favicon_from_file(struct tab *t, char *file)
5477 gint width, height;
5478 GdkPixbuf *pixbuf, *scaled;
5479 struct stat sb;
5481 if (t == NULL || file == NULL)
5482 return;
5483 if (t->icon_pixbuf) {
5484 DNPRINTF(XT_D_DOWNLOAD, "%s: icon already set\n", __func__);
5485 return;
5488 if (g_str_has_prefix(file, "file://"))
5489 file += strlen("file://");
5490 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5492 if (!stat(file, &sb)) {
5493 if (sb.st_size == 0) {
5494 /* corrupt icon so trash it */
5495 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5496 __func__, file);
5497 unlink(file);
5498 /* no need to set icon to default here */
5499 return;
5503 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
5504 if (pixbuf == NULL) {
5505 xt_icon_from_name(t, "text-html");
5506 return;
5509 g_object_get(pixbuf, "width", &width, "height", &height,
5510 (char *)NULL);
5511 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
5512 __func__, t->tab_id, width, height);
5514 if (width > 16 || height > 16) {
5515 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5516 GDK_INTERP_BILINEAR);
5517 g_object_unref(pixbuf);
5518 } else
5519 scaled = pixbuf;
5521 if (scaled == NULL) {
5522 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5523 GDK_INTERP_BILINEAR);
5524 return;
5527 t->icon_pixbuf = scaled;
5528 xt_icon_from_pixbuf(t, t->icon_pixbuf);
5531 void
5532 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5533 struct tab *t)
5535 WebKitDownloadStatus status = webkit_download_get_status(download);
5537 if (t == NULL)
5538 return;
5540 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5541 __func__, t->tab_id, status);
5543 switch (status) {
5544 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5545 /* -1 */
5546 t->icon_download = NULL;
5547 free_favicon(t);
5548 break;
5549 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5550 /* 0 */
5551 break;
5552 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5553 /* 1 */
5554 break;
5555 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5556 /* 2 */
5557 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5558 __func__, t->tab_id);
5559 t->icon_download = NULL;
5560 free_favicon(t);
5561 break;
5562 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5563 /* 3 */
5564 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5565 __func__, t->icon_dest_uri);
5566 set_favicon_from_file(t, t->icon_dest_uri);
5567 /* these will be freed post callback */
5568 t->icon_request = NULL;
5569 t->icon_download = NULL;
5570 break;
5571 default:
5572 break;
5576 void
5577 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5579 gchar *name_hash, file[PATH_MAX];
5580 struct stat sb;
5582 DNPRINTF(XT_D_DOWNLOAD, "notify_icon_loaded_cb %s\n", uri);
5584 if (uri == NULL || t == NULL)
5585 return;
5587 if (t->icon_request) {
5588 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5589 return;
5592 /* check to see if we got the icon in cache */
5593 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5594 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5595 g_free(name_hash);
5597 if (!stat(file, &sb)) {
5598 if (sb.st_size > 0) {
5599 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5600 __func__, file);
5601 set_favicon_from_file(t, file);
5602 return;
5605 /* corrupt icon so trash it */
5606 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5607 __func__, file);
5608 unlink(file);
5611 /* create download for icon */
5612 t->icon_request = webkit_network_request_new(uri);
5613 if (t->icon_request == NULL) {
5614 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5615 __func__, uri);
5616 return;
5619 t->icon_download = webkit_download_new(t->icon_request);
5621 /* we have to free icon_dest_uri later */
5622 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5623 webkit_download_set_destination_uri(t->icon_download,
5624 t->icon_dest_uri);
5626 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5627 G_CALLBACK(favicon_download_status_changed_cb), t);
5629 webkit_download_start(t->icon_download);
5632 void
5633 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5635 const gchar *set = NULL, *uri = NULL, *title = NULL;
5636 struct history *h, find;
5637 int add = 0;
5638 const gchar *s_loading;
5639 struct karg a;
5641 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d\n",
5642 webkit_web_view_get_load_status(wview));
5644 if (t == NULL) {
5645 show_oops_s("notify_load_status_cb invalid paramters");
5646 return;
5649 switch (webkit_web_view_get_load_status(wview)) {
5650 case WEBKIT_LOAD_PROVISIONAL:
5651 /* 0 */
5652 abort_favicon_download(t);
5653 #if GTK_CHECK_VERSION(2, 20, 0)
5654 gtk_widget_show(t->spinner);
5655 gtk_spinner_start(GTK_SPINNER(t->spinner));
5656 #endif
5657 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5659 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5661 break;
5663 case WEBKIT_LOAD_COMMITTED:
5664 /* 1 */
5665 if ((uri = get_uri(wview)) != NULL) {
5666 if (strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
5667 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5669 if (t->status) {
5670 g_free(t->status);
5671 t->status = NULL;
5673 set_status(t, (char *)uri, XT_STATUS_LOADING);
5676 /* check if js white listing is enabled */
5677 if (enable_js_whitelist) {
5678 uri = get_uri(wview);
5679 check_and_set_js(uri, t);
5682 show_ca_status(t, uri);
5684 /* we know enough to autosave the session */
5685 if (session_autosave) {
5686 a.s = NULL;
5687 save_tabs(t, &a);
5689 break;
5691 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5692 /* 3 */
5693 title = webkit_web_view_get_title(wview);
5694 uri = get_uri(wview);
5695 if (title)
5696 set = title;
5697 else if (uri)
5698 set = uri;
5699 else
5700 break;
5702 gtk_label_set_text(GTK_LABEL(t->label), set);
5703 gtk_window_set_title(GTK_WINDOW(main_window), set);
5705 if (uri) {
5706 if (!strncmp(uri, "http://", strlen("http://")) ||
5707 !strncmp(uri, "https://", strlen("https://")) ||
5708 !strncmp(uri, "file://", strlen("file://")))
5709 add = 1;
5710 if (add == 0)
5711 break;
5712 find.uri = uri;
5713 h = RB_FIND(history_list, &hl, &find);
5714 if (h)
5715 break;
5717 h = g_malloc(sizeof *h);
5718 h->uri = g_strdup(uri);
5719 if (title)
5720 h->title = g_strdup(title);
5721 else
5722 h->title = g_strdup(uri);
5723 RB_INSERT(history_list, &hl, h);
5724 update_history_tabs(NULL);
5727 break;
5729 case WEBKIT_LOAD_FINISHED:
5730 /* 2 */
5731 uri = get_uri(wview);
5732 set_status(t, (char *)uri, XT_STATUS_URI);
5733 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5734 case WEBKIT_LOAD_FAILED:
5735 /* 4 */
5736 #endif
5737 #if GTK_CHECK_VERSION(2, 20, 0)
5738 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5739 gtk_widget_hide(t->spinner);
5740 #endif
5741 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
5742 if (s_loading && !strcmp(s_loading, "Loading"))
5743 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5744 default:
5745 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
5746 break;
5749 if (t->item)
5750 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
5751 else
5752 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
5753 webkit_web_view_can_go_back(wview));
5755 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
5756 webkit_web_view_can_go_forward(wview));
5758 /* take focus if we are visible */
5759 t->focus_wv = 1;
5760 focus_webview(t);
5763 void
5764 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5766 run_script(t, JS_HINTING);
5769 void
5770 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
5772 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
5773 progress == 100 ? 0 : (double)progress / 100);
5774 if (show_url == 0) {
5775 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
5776 progress == 100 ? 0 : (double)progress / 100);
5781 webview_nw_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5782 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5783 WebKitWebPolicyDecision *pd, struct tab *t)
5785 char *uri;
5787 if (t == NULL) {
5788 show_oops_s("webview_nw_cb invalid paramters");
5789 return (FALSE);
5792 DNPRINTF(XT_D_NAV, "webview_nw_cb: %s\n",
5793 webkit_network_request_get_uri(request));
5795 /* open in current tab */
5796 uri = (char *)webkit_network_request_get_uri(request);
5797 webkit_web_view_load_uri(t->wv, uri);
5798 webkit_web_policy_decision_ignore(pd);
5800 return (TRUE); /* we made the decission */
5804 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5805 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5806 WebKitWebPolicyDecision *pd, struct tab *t)
5808 char *uri;
5810 if (t == NULL) {
5811 show_oops_s("webview_npd_cb invalid parameters");
5812 return (FALSE);
5815 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
5816 t->ctrl_click,
5817 webkit_network_request_get_uri(request));
5819 uri = (char *)webkit_network_request_get_uri(request);
5821 if ((!parse_xtp_url(t, uri) && (t->ctrl_click))) {
5822 t->ctrl_click = 0;
5823 create_new_tab(uri, NULL, ctrl_click_focus);
5824 webkit_web_policy_decision_ignore(pd);
5825 return (TRUE); /* we made the decission */
5828 webkit_web_policy_decision_use(pd);
5829 return (TRUE); /* we made the decission */
5832 WebKitWebView *
5833 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5835 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
5836 webkit_web_view_get_uri(wv));
5838 return (wv);
5842 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
5844 /* we can not eat the event without throwing gtk off so defer it */
5846 /* catch middle click */
5847 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
5848 t->ctrl_click = 1;
5849 goto done;
5852 /* catch ctrl click */
5853 if (e->type == GDK_BUTTON_RELEASE &&
5854 CLEAN(e->state) == GDK_CONTROL_MASK)
5855 t->ctrl_click = 1;
5856 else
5857 t->ctrl_click = 0;
5858 done:
5859 return (XT_CB_PASSTHROUGH);
5863 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
5865 struct mime_type *m;
5867 m = find_mime_type(mime_type);
5868 if (m == NULL)
5869 return (1);
5871 switch (fork()) {
5872 case -1:
5873 show_oops(t, "can't fork mime handler");
5874 /* NOTREACHED */
5875 case 0:
5876 break;
5877 default:
5878 return (0);
5881 /* child */
5882 execlp(m->mt_action, m->mt_action,
5883 webkit_network_request_get_uri(request), (void *)NULL);
5885 _exit(0);
5887 /* NOTREACHED */
5888 return (0);
5892 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
5893 WebKitNetworkRequest *request, char *mime_type,
5894 WebKitWebPolicyDecision *decision, struct tab *t)
5896 if (t == NULL) {
5897 show_oops_s("webview_mimetype_cb invalid parameters");
5898 return (FALSE);
5901 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
5902 t->tab_id, mime_type);
5904 if (run_mimehandler(t, mime_type, request) == 0) {
5905 webkit_web_policy_decision_ignore(decision);
5906 focus_webview(t);
5907 return (TRUE);
5910 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
5911 webkit_web_policy_decision_download(decision);
5912 return (TRUE);
5915 return (FALSE);
5919 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
5920 struct tab *t)
5922 const gchar *filename;
5923 char *uri = NULL;
5924 struct download *download_entry;
5925 int ret = TRUE;
5927 if (wk_download == NULL || t == NULL) {
5928 show_oops_s("%s invalid parameters", __func__);
5929 return (FALSE);
5932 filename = webkit_download_get_suggested_filename(wk_download);
5933 if (filename == NULL)
5934 return (FALSE); /* abort download */
5936 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
5938 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
5939 "local %s\n", __func__, t->tab_id, filename, uri);
5941 webkit_download_set_destination_uri(wk_download, uri);
5943 if (webkit_download_get_status(wk_download) ==
5944 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5945 show_oops(t, "%s: download failed to start", __func__);
5946 ret = FALSE;
5947 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
5948 } else {
5949 download_entry = g_malloc(sizeof(struct download));
5950 download_entry->download = wk_download;
5951 download_entry->tab = t;
5952 download_entry->id = next_download_id++;
5953 RB_INSERT(download_list, &downloads, download_entry);
5954 /* get from history */
5955 g_object_ref(wk_download);
5956 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
5959 if (uri)
5960 g_free(uri);
5962 /* sync other download manager tabs */
5963 update_download_tabs(NULL);
5966 * NOTE: never redirect/render the current tab before this
5967 * function returns. This will cause the download to never start.
5969 return (ret); /* start download */
5972 void
5973 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
5975 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
5977 if (t == NULL) {
5978 show_oops_s("webview_hover_cb");
5979 return;
5982 if (uri)
5983 set_status(t, uri, XT_STATUS_LINK);
5984 else {
5985 if (t->status)
5986 set_status(t, t->status, XT_STATUS_NOTHING);
5991 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
5993 char s[2], buf[128];
5994 const char *errstr = NULL;
5995 long long link;
5997 /* don't use w directly; use t->whatever instead */
5999 if (t == NULL) {
6000 show_oops_s("wv_keypress_after_cb");
6001 return (XT_CB_PASSTHROUGH);
6004 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6005 e->keyval, e->state, t);
6007 if (t->hints_on) {
6008 /* ESC */
6009 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6010 disable_hints(t);
6011 return (XT_CB_HANDLED);
6014 /* RETURN */
6015 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6016 link = strtonum(t->hint_num, 1, 1000, &errstr);
6017 if (errstr) {
6018 /* we have a string */
6019 } else {
6020 /* we have a number */
6021 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6022 t->hint_num);
6023 run_script(t, buf);
6025 disable_hints(t);
6028 /* BACKSPACE */
6029 /* XXX unfuck this */
6030 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6031 if (t->hint_mode == XT_HINT_NUMERICAL) {
6032 /* last input was numerical */
6033 int l;
6034 l = strlen(t->hint_num);
6035 if (l > 0) {
6036 l--;
6037 if (l == 0) {
6038 disable_hints(t);
6039 enable_hints(t);
6040 } else {
6041 t->hint_num[l] = '\0';
6042 goto num;
6045 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6046 /* last input was alphanumerical */
6047 int l;
6048 l = strlen(t->hint_buf);
6049 if (l > 0) {
6050 l--;
6051 if (l == 0) {
6052 disable_hints(t);
6053 enable_hints(t);
6054 } else {
6055 t->hint_buf[l] = '\0';
6056 goto anum;
6059 } else {
6060 /* bogus */
6061 disable_hints(t);
6065 /* numerical input */
6066 if (CLEAN(e->state) == 0 &&
6067 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6068 snprintf(s, sizeof s, "%c", e->keyval);
6069 strlcat(t->hint_num, s, sizeof t->hint_num);
6070 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6071 t->hint_num);
6072 num:
6073 link = strtonum(t->hint_num, 1, 1000, &errstr);
6074 if (errstr) {
6075 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6076 disable_hints(t);
6077 } else {
6078 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6079 t->hint_num);
6080 t->hint_mode = XT_HINT_NUMERICAL;
6081 run_script(t, buf);
6084 /* empty the counter buffer */
6085 bzero(t->hint_buf, sizeof t->hint_buf);
6086 return (XT_CB_HANDLED);
6089 /* alphanumerical input */
6090 if (
6091 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6092 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6093 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6094 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6095 snprintf(s, sizeof s, "%c", e->keyval);
6096 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6097 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6098 t->hint_buf);
6099 anum:
6100 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6101 run_script(t, buf);
6103 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6104 t->hint_buf);
6105 t->hint_mode = XT_HINT_ALPHANUM;
6106 run_script(t, buf);
6108 /* empty the counter buffer */
6109 bzero(t->hint_num, sizeof t->hint_num);
6110 return (XT_CB_HANDLED);
6113 return (XT_CB_HANDLED);
6116 struct key_binding *k;
6117 TAILQ_FOREACH(k, &kbl, entry)
6118 if (e->keyval == k->key) {
6119 if (k->mask == 0) {
6120 if ((e->state & (CTRL | MOD1)) == 0) {
6121 k->func(t, &k->arg);
6122 return (XT_CB_HANDLED);
6125 else if ((e->state & k->mask) == k->mask) {
6126 k->func(t, &k->arg);
6127 return (XT_CB_HANDLED);
6131 return (XT_CB_PASSTHROUGH);
6135 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6137 hide_oops(t);
6139 return (XT_CB_PASSTHROUGH);
6143 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6145 const gchar *c = gtk_entry_get_text(w);
6146 GdkColor color;
6147 int forward = TRUE;
6149 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6150 e->keyval, e->state, t);
6152 if (t == NULL) {
6153 show_oops_s("cmd_keyrelease_cb invalid parameters");
6154 return (XT_CB_PASSTHROUGH);
6157 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6158 e->keyval, e->state, t);
6160 if (c[0] == ':')
6161 goto done;
6162 if (strlen(c) == 1) {
6163 webkit_web_view_unmark_text_matches(t->wv);
6164 goto done;
6167 if (c[0] == '/')
6168 forward = TRUE;
6169 else if (c[0] == '?')
6170 forward = FALSE;
6171 else
6172 goto done;
6174 /* search */
6175 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6176 FALSE) {
6177 /* not found, mark red */
6178 gdk_color_parse("red", &color);
6179 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6180 /* unmark and remove selection */
6181 webkit_web_view_unmark_text_matches(t->wv);
6182 /* my kingdom for a way to unselect text in webview */
6183 } else {
6184 /* found, highlight all */
6185 webkit_web_view_unmark_text_matches(t->wv);
6186 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6187 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6188 gdk_color_parse("white", &color);
6189 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6191 done:
6192 return (XT_CB_PASSTHROUGH);
6195 #if 0
6197 cmd_complete(struct tab *t, char *s)
6199 int i;
6200 GtkEntry *w = GTK_ENTRY(t->cmd);
6202 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", s);
6204 for (i = 0; i < LENGTH(cmds); i++) {
6205 if (!strncasecmp(cmds[i].cmd, s, strlen(s))) {
6206 fprintf(stderr, "match %s %d\n", cmds[i].cmd, strcasecmp(cmds[i].cmd, s));
6207 #if 0
6208 gtk_entry_set_text(w, ":");
6209 gtk_entry_append_text(w, cmds[i].cmd);
6210 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6211 #endif
6215 return (0);
6217 #endif
6220 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6222 if (t == NULL) {
6223 show_oops_s("entry_key_cb invalid parameters");
6224 return (XT_CB_PASSTHROUGH);
6227 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6228 e->keyval, e->state, t);
6230 hide_oops(t);
6232 if (e->keyval == GDK_Escape) {
6233 /* don't use focus_webview(t) because we want to type :cmds */
6234 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6237 struct key_binding *k;
6238 TAILQ_FOREACH(k, &kbl, entry)
6239 if (e->keyval == k->key && k->use_in_entry) {
6240 if (k->mask == 0) {
6241 if ((e->state & (CTRL | MOD1)) == 0) {
6242 k->func(t, &k->arg);
6243 return (XT_CB_HANDLED);
6246 else if ((e->state & k->mask) == k->mask) {
6247 k->func(t, &k->arg);
6248 return (XT_CB_HANDLED);
6252 return (XT_CB_PASSTHROUGH);
6256 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6258 int rv = XT_CB_HANDLED;
6259 const gchar *c = gtk_entry_get_text(w);
6261 if (t == NULL) {
6262 show_oops_s("cmd_keypress_cb parameters");
6263 return (XT_CB_PASSTHROUGH);
6266 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6267 e->keyval, e->state, t);
6269 /* sanity */
6270 if (c == NULL)
6271 e->keyval = GDK_Escape;
6272 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6273 e->keyval = GDK_Escape;
6275 switch (e->keyval) {
6276 #if 0
6277 case GDK_Tab:
6278 if (c[0] != ':')
6279 goto done;
6281 if (strchr (c, ' ')) {
6282 /* par completion */
6283 fprintf(stderr, "completeme par\n");
6284 goto done;
6287 cmd_complete(t, (char *)&c[1]);
6289 goto done;
6290 #endif
6291 case GDK_BackSpace:
6292 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
6293 break;
6294 /* FALLTHROUGH */
6295 case GDK_Escape:
6296 hide_cmd(t);
6297 focus_webview(t);
6299 /* cancel search */
6300 if (c[0] == '/' || c[0] == '?')
6301 webkit_web_view_unmark_text_matches(t->wv);
6302 goto done;
6305 rv = XT_CB_PASSTHROUGH;
6306 done:
6307 return (rv);
6311 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
6313 if (t == NULL) {
6314 show_oops_s("cmd_focusout_cb invalid parameters");
6315 return (XT_CB_PASSTHROUGH);
6317 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
6319 hide_cmd(t);
6320 hide_oops(t);
6322 if (show_url == 0 || t->focus_wv)
6323 focus_webview(t);
6324 else
6325 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6327 return (XT_CB_PASSTHROUGH);
6330 void
6331 cmd_activate_cb(GtkEntry *entry, struct tab *t)
6333 int i;
6334 char *s;
6335 const gchar *c = gtk_entry_get_text(entry);
6337 if (t == NULL) {
6338 show_oops_s("cmd_activate_cb invalid parameters");
6339 return;
6342 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
6344 /* sanity */
6345 if (c == NULL)
6346 goto done;
6347 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6348 goto done;
6349 if (strlen(c) < 2)
6350 goto done;
6351 s = (char *)&c[1];
6353 if (c[0] == '/' || c[0] == '?') {
6354 if (t->search_text) {
6355 g_free(t->search_text);
6356 t->search_text = NULL;
6359 t->search_text = g_strdup(s);
6360 if (global_search)
6361 g_free(global_search);
6362 global_search = g_strdup(s);
6363 t->search_forward = c[0] == '/';
6365 goto done;
6368 for (i = 0; i < LENGTH(cmds); i++)
6369 if (cmds[i].params) {
6370 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
6371 cmds[i].arg.s = g_strdup(s);
6372 goto execute_command;
6374 } else {
6375 if (!strcmp(s, cmds[i].cmd))
6376 goto execute_command;
6378 show_oops(t, "Invalid command: %s", s);
6379 done:
6380 hide_cmd(t);
6381 return;
6383 execute_command:
6384 hide_cmd(t);
6385 cmds[i].func(t, &cmds[i].arg);
6387 void
6388 backward_cb(GtkWidget *w, struct tab *t)
6390 struct karg a;
6392 if (t == NULL) {
6393 show_oops_s("backward_cb invalid parameters");
6394 return;
6397 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
6399 a.i = XT_NAV_BACK;
6400 navaction(t, &a);
6403 void
6404 forward_cb(GtkWidget *w, struct tab *t)
6406 struct karg a;
6408 if (t == NULL) {
6409 show_oops_s("forward_cb invalid parameters");
6410 return;
6413 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
6415 a.i = XT_NAV_FORWARD;
6416 navaction(t, &a);
6419 void
6420 stop_cb(GtkWidget *w, struct tab *t)
6422 WebKitWebFrame *frame;
6424 if (t == NULL) {
6425 show_oops_s("stop_cb invalid parameters");
6426 return;
6429 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
6431 frame = webkit_web_view_get_main_frame(t->wv);
6432 if (frame == NULL) {
6433 show_oops(t, "stop_cb: no frame");
6434 return;
6437 webkit_web_frame_stop_loading(frame);
6438 abort_favicon_download(t);
6441 void
6442 setup_webkit(struct tab *t)
6444 g_object_set(G_OBJECT(t->settings),
6445 "user-agent", t->user_agent, (char *)NULL);
6446 g_object_set(G_OBJECT(t->settings),
6447 "enable-scripts", enable_scripts, (char *)NULL);
6448 g_object_set(G_OBJECT(t->settings),
6449 "enable-plugins", enable_plugins, (char *)NULL);
6450 adjustfont_webkit(t, XT_FONT_SET);
6452 webkit_web_view_set_settings(t->wv, t->settings);
6455 GtkWidget *
6456 create_browser(struct tab *t)
6458 GtkWidget *w;
6459 gchar *strval;
6461 if (t == NULL) {
6462 show_oops_s("create_browser invalid parameters");
6463 return (NULL);
6466 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
6467 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
6468 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
6469 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
6471 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
6472 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
6473 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
6475 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
6476 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
6478 /* set defaults */
6479 t->settings = webkit_web_settings_new();
6481 if (user_agent == NULL) {
6482 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
6483 (char *)NULL);
6484 t->user_agent = g_strdup_printf("%s %s+", strval, version);
6485 g_free(strval);
6486 } else {
6487 t->user_agent = g_strdup(user_agent);
6490 setup_webkit(t);
6492 return (w);
6495 GtkWidget *
6496 create_window(void)
6498 GtkWidget *w;
6500 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
6501 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
6502 gtk_widget_set_name(w, "xxxterm");
6503 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
6504 g_signal_connect(G_OBJECT(w), "delete_event",
6505 G_CALLBACK (gtk_main_quit), NULL);
6507 return (w);
6510 GtkWidget *
6511 create_toolbar(struct tab *t)
6513 GtkWidget *toolbar = NULL, *b, *eb1;
6515 b = gtk_hbox_new(FALSE, 0);
6516 toolbar = b;
6517 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
6519 if (fancy_bar) {
6520 /* backward button */
6521 t->backward = create_button("GoBack", GTK_STOCK_GO_BACK, 0);
6522 gtk_widget_set_sensitive(t->backward, FALSE);
6523 g_signal_connect(G_OBJECT(t->backward), "clicked",
6524 G_CALLBACK(backward_cb), t);
6525 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
6527 /* forward button */
6528 t->forward = create_button("GoForward",GTK_STOCK_GO_FORWARD, 0);
6529 gtk_widget_set_sensitive(t->forward, FALSE);
6530 g_signal_connect(G_OBJECT(t->forward), "clicked",
6531 G_CALLBACK(forward_cb), t);
6532 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
6533 FALSE, 0);
6535 /* stop button */
6536 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
6537 gtk_widget_set_sensitive(t->stop, FALSE);
6538 g_signal_connect(G_OBJECT(t->stop), "clicked",
6539 G_CALLBACK(stop_cb), t);
6540 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
6541 FALSE, 0);
6543 /* JS button */
6544 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
6545 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
6546 gtk_widget_set_sensitive(t->js_toggle, TRUE);
6547 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
6548 G_CALLBACK(js_toggle_cb), t);
6549 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
6552 t->uri_entry = gtk_entry_new();
6553 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
6554 G_CALLBACK(activate_uri_entry_cb), t);
6555 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
6556 G_CALLBACK(entry_key_cb), t);
6557 eb1 = gtk_hbox_new(FALSE, 0);
6558 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
6559 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
6560 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
6562 /* search entry */
6563 if (fancy_bar && search_string) {
6564 GtkWidget *eb2;
6565 t->search_entry = gtk_entry_new();
6566 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
6567 g_signal_connect(G_OBJECT(t->search_entry), "activate",
6568 G_CALLBACK(activate_search_entry_cb), t);
6569 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
6570 G_CALLBACK(entry_key_cb), t);
6571 gtk_widget_set_size_request(t->search_entry, -1, -1);
6572 eb2 = gtk_hbox_new(FALSE, 0);
6573 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
6574 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
6576 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
6578 return (toolbar);
6581 void
6582 recalc_tabs(void)
6584 struct tab *t;
6586 TAILQ_FOREACH(t, &tabs, entry)
6587 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
6591 undo_close_tab_save(struct tab *t)
6593 int m, n;
6594 const gchar *uri;
6595 struct undo *u1, *u2;
6596 GList *items;
6597 WebKitWebHistoryItem *item;
6599 if ((uri = get_uri(t->wv)) == NULL)
6600 return (1);
6602 u1 = g_malloc0(sizeof(struct undo));
6603 u1->uri = g_strdup(uri);
6605 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
6607 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
6608 n = webkit_web_back_forward_list_get_back_length(t->bfl);
6609 u1->back = n;
6611 /* forward history */
6612 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
6614 while (items) {
6615 item = items->data;
6616 u1->history = g_list_prepend(u1->history,
6617 webkit_web_history_item_copy(item));
6618 items = g_list_next(items);
6621 /* current item */
6622 if (m) {
6623 item = webkit_web_back_forward_list_get_current_item(t->bfl);
6624 u1->history = g_list_prepend(u1->history,
6625 webkit_web_history_item_copy(item));
6628 /* back history */
6629 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
6631 while (items) {
6632 item = items->data;
6633 u1->history = g_list_prepend(u1->history,
6634 webkit_web_history_item_copy(item));
6635 items = g_list_next(items);
6638 TAILQ_INSERT_HEAD(&undos, u1, entry);
6640 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
6641 u2 = TAILQ_LAST(&undos, undo_tailq);
6642 TAILQ_REMOVE(&undos, u2, entry);
6643 g_free(u2->uri);
6644 g_list_free(u2->history);
6645 g_free(u2);
6646 } else
6647 undo_count++;
6649 return (0);
6652 void
6653 delete_tab(struct tab *t)
6655 struct karg a;
6657 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
6659 if (t == NULL)
6660 return;
6662 TAILQ_REMOVE(&tabs, t, entry);
6664 /* halt all webkit activity */
6665 abort_favicon_download(t);
6666 webkit_web_view_stop_loading(t->wv);
6667 undo_close_tab_save(t);
6669 gtk_widget_destroy(t->vbox);
6670 g_free(t->user_agent);
6671 g_free(t);
6673 recalc_tabs();
6674 if (TAILQ_EMPTY(&tabs))
6675 create_new_tab(NULL, NULL, 1);
6678 /* recreate session */
6679 if (session_autosave) {
6680 a.s = NULL;
6681 save_tabs(t, &a);
6685 void
6686 adjustfont_webkit(struct tab *t, int adjust)
6688 if (t == NULL) {
6689 show_oops_s("adjustfont_webkit invalid parameters");
6690 return;
6693 if (adjust == XT_FONT_SET)
6694 t->font_size = default_font_size;
6696 t->font_size += adjust;
6697 g_object_set(G_OBJECT(t->settings), "default-font-size",
6698 t->font_size, (char *)NULL);
6699 g_object_get(G_OBJECT(t->settings), "default-font-size",
6700 &t->font_size, (char *)NULL);
6703 void
6704 append_tab(struct tab *t)
6706 if (t == NULL)
6707 return;
6709 TAILQ_INSERT_TAIL(&tabs, t, entry);
6710 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
6713 void
6714 create_new_tab(char *title, struct undo *u, int focus)
6716 struct tab *t, *tt;
6717 int load = 1, id, notfound;
6718 GtkWidget *b, *bb;
6719 WebKitWebHistoryItem *item;
6720 GList *items;
6721 GdkColor color;
6722 PangoFontDescription *fd = NULL;
6724 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
6726 if (tabless && !TAILQ_EMPTY(&tabs)) {
6727 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
6728 return;
6731 t = g_malloc0(sizeof *t);
6733 if (title == NULL) {
6734 title = "(untitled)";
6735 load = 0;
6738 t->vbox = gtk_vbox_new(FALSE, 0);
6740 /* label + button for tab */
6741 b = gtk_hbox_new(FALSE, 0);
6742 t->tab_content = b;
6744 #if GTK_CHECK_VERSION(2, 20, 0)
6745 t->spinner = gtk_spinner_new ();
6746 #endif
6747 t->label = gtk_label_new(title);
6748 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
6749 gtk_widget_set_size_request(t->label, 100, 0);
6750 gtk_widget_set_size_request(b, 130, 0);
6752 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
6753 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
6754 #if GTK_CHECK_VERSION(2, 20, 0)
6755 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
6756 #endif
6758 /* toolbar */
6759 t->toolbar = create_toolbar(t);
6760 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
6762 /* browser */
6763 t->browser_win = create_browser(t);
6764 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
6766 /* oops message for user feedback */
6767 t->oops = gtk_entry_new();
6768 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
6769 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
6770 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
6771 gdk_color_parse("red", &color);
6772 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
6773 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
6775 /* command entry */
6776 t->cmd = gtk_entry_new();
6777 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
6778 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
6779 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
6781 /* status bar */
6782 t->statusbar = gtk_entry_new();
6783 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
6784 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
6785 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
6786 gdk_color_parse("black", &color);
6787 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
6788 gdk_color_parse("white", &color);
6789 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
6790 fd = GTK_WIDGET(t->statusbar)->style->font_desc;
6791 pango_font_description_set_weight(fd, PANGO_WEIGHT_SEMIBOLD);
6792 pango_font_description_set_absolute_size(fd, 10 * PANGO_SCALE); /* 10 px font */
6793 gtk_widget_modify_font(t->statusbar, fd);
6794 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
6796 /* xtp meaning is normal by default */
6797 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6799 /* set empty favicon */
6800 xt_icon_from_name(t, "text-html");
6802 /* and show it all */
6803 gtk_widget_show_all(b);
6804 gtk_widget_show_all(t->vbox);
6806 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
6807 append_tab(t);
6808 else {
6809 notfound = 1;
6810 id = gtk_notebook_get_current_page(notebook);
6811 TAILQ_FOREACH(tt, &tabs, entry) {
6812 if (tt->tab_id == id) {
6813 notfound = 0;
6814 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
6815 gtk_notebook_insert_page(notebook, t->vbox, b,
6816 id + 1);
6817 recalc_tabs();
6818 break;
6821 if (notfound)
6822 append_tab(t);
6825 #if GTK_CHECK_VERSION(2, 20, 0)
6826 /* turn spinner off if we are a new tab without uri */
6827 if (!load) {
6828 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6829 gtk_widget_hide(t->spinner);
6831 #endif
6832 /* make notebook tabs reorderable */
6833 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
6835 g_object_connect(G_OBJECT(t->cmd),
6836 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
6837 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
6838 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
6839 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
6840 (char *)NULL);
6842 /* reuse wv_button_cb to hide oops */
6843 g_object_connect(G_OBJECT(t->oops),
6844 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
6845 (char *)NULL);
6847 g_object_connect(G_OBJECT(t->wv),
6848 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
6849 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
6850 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
6851 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
6852 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
6853 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
6854 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_nw_cb), t,
6855 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
6856 "signal::event", G_CALLBACK(webview_event_cb), t,
6857 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
6858 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
6859 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6860 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
6861 #endif
6862 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
6863 (char *)NULL);
6864 g_signal_connect(t->wv,
6865 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
6867 /* hijack the unused keys as if we were the browser */
6868 g_object_connect(G_OBJECT(t->toolbar),
6869 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
6870 (char *)NULL);
6872 g_signal_connect(G_OBJECT(bb), "button_press_event",
6873 G_CALLBACK(tab_close_cb), t);
6875 /* hide stuff */
6876 hide_cmd(t);
6877 hide_oops(t);
6878 url_set_visibility();
6879 statusbar_set_visibility();
6881 if (focus) {
6882 gtk_notebook_set_current_page(notebook, t->tab_id);
6883 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
6884 t->tab_id);
6887 if (load) {
6888 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
6889 load_uri(t, title);
6890 } else {
6891 if (show_url == 1)
6892 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6893 else
6894 focus_webview(t);
6897 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
6898 /* restore the tab's history */
6899 if (u && u->history) {
6900 items = u->history;
6901 while (items) {
6902 item = items->data;
6903 webkit_web_back_forward_list_add_item(t->bfl, item);
6904 items = g_list_next(items);
6907 item = g_list_nth_data(u->history, u->back);
6908 if (item)
6909 webkit_web_view_go_to_back_forward_item(t->wv, item);
6911 g_list_free(items);
6912 g_list_free(u->history);
6913 } else
6914 webkit_web_back_forward_list_clear(t->bfl);
6917 void
6918 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
6919 gpointer *udata)
6921 struct tab *t;
6922 const gchar *uri;
6924 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
6926 TAILQ_FOREACH(t, &tabs, entry) {
6927 if (t->tab_id == pn) {
6928 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
6929 "%d\n", pn);
6931 uri = webkit_web_view_get_title(t->wv);
6932 if (uri == NULL)
6933 uri = XT_NAME;
6934 gtk_window_set_title(GTK_WINDOW(main_window), uri);
6936 hide_cmd(t);
6937 hide_oops(t);
6939 if (t->focus_wv)
6940 focus_webview(t);
6945 void
6946 menuitem_response(struct tab *t)
6948 gtk_notebook_set_current_page(notebook, t->tab_id);
6951 gboolean
6952 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
6954 GtkWidget *menu, *menu_items;
6955 GdkEventButton *bevent;
6956 const gchar *uri;
6957 struct tab *ti;
6959 if (event->type == GDK_BUTTON_PRESS) {
6960 bevent = (GdkEventButton *) event;
6961 menu = gtk_menu_new();
6963 TAILQ_FOREACH(ti, &tabs, entry) {
6964 if ((uri = get_uri(ti->wv)) == NULL)
6965 /* XXX make sure there is something to print */
6966 /* XXX add gui pages in here to look purdy */
6967 uri = "(untitled)";
6968 menu_items = gtk_menu_item_new_with_label(uri);
6969 gtk_menu_append(GTK_MENU (menu), menu_items);
6970 gtk_widget_show(menu_items);
6972 gtk_signal_connect_object(GTK_OBJECT(menu_items),
6973 "activate", GTK_SIGNAL_FUNC(menuitem_response),
6974 (gpointer)ti);
6977 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
6978 bevent->button, bevent->time);
6980 /* unref object so it'll free itself when popped down */
6981 g_object_ref_sink(menu);
6982 g_object_unref(menu);
6984 return (TRUE /* eat event */);
6987 return (FALSE /* propagate */);
6991 icon_size_map(int icon_size)
6993 if (icon_size <= GTK_ICON_SIZE_INVALID ||
6994 icon_size > GTK_ICON_SIZE_DIALOG)
6995 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
6997 return (icon_size);
7000 GtkWidget *
7001 create_button(char *name, char *stockid, int size)
7003 GtkWidget *button, *image;
7004 gchar *rcstring;
7005 int gtk_icon_size;
7006 rcstring = g_strdup_printf(
7007 "style \"%s-style\"\n"
7008 "{\n"
7009 " GtkWidget::focus-padding = 0\n"
7010 " GtkWidget::focus-line-width = 0\n"
7011 " xthickness = 0\n"
7012 " ythickness = 0\n"
7013 "}\n"
7014 "widget \"*.%s\" style \"%s-style\"",name,name,name);
7015 gtk_rc_parse_string(rcstring);
7016 g_free(rcstring);
7017 button = gtk_button_new();
7018 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
7019 gtk_icon_size = icon_size_map(size?size:icon_size);
7021 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
7022 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7023 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
7024 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
7025 gtk_widget_set_name(button, name);
7026 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
7027 gtk_widget_set_tooltip_text(button, name);
7029 return button;
7032 void
7033 button_set_stockid(GtkWidget *button, char *stockid)
7035 GtkWidget *image;
7036 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
7037 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7038 gtk_button_set_image(GTK_BUTTON(button), image);
7041 void
7042 create_canvas(void)
7044 GtkWidget *vbox;
7045 GList *l = NULL;
7046 GdkPixbuf *pb;
7047 char file[PATH_MAX];
7048 int i;
7050 vbox = gtk_vbox_new(FALSE, 0);
7051 gtk_box_set_spacing(GTK_BOX(vbox), 0);
7052 notebook = GTK_NOTEBOOK(gtk_notebook_new());
7053 gtk_notebook_set_tab_hborder(notebook, 0);
7054 gtk_notebook_set_tab_vborder(notebook, 0);
7055 gtk_notebook_set_scrollable(notebook, TRUE);
7056 notebook_tab_set_visibility(notebook);
7057 gtk_notebook_set_show_border(notebook, FALSE);
7058 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
7060 abtn = gtk_button_new();
7061 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
7062 gtk_widget_set_size_request(arrow, -1, -1);
7063 gtk_container_add(GTK_CONTAINER(abtn), arrow);
7064 gtk_widget_set_size_request(abtn, -1, 20);
7065 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
7067 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
7068 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
7069 gtk_widget_set_size_request(vbox, -1, -1);
7071 g_object_connect(G_OBJECT(notebook),
7072 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
7073 (char *)NULL);
7074 g_signal_connect(G_OBJECT(abtn), "button_press_event",
7075 G_CALLBACK(arrow_cb), NULL);
7077 main_window = create_window();
7078 gtk_container_add(GTK_CONTAINER(main_window), vbox);
7079 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
7081 /* icons */
7082 for (i = 0; i < LENGTH(icons); i++) {
7083 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
7084 pb = gdk_pixbuf_new_from_file(file, NULL);
7085 l = g_list_append(l, pb);
7087 gtk_window_set_default_icon_list(l);
7089 gtk_widget_show_all(abtn);
7090 gtk_widget_show_all(main_window);
7093 void
7094 set_hook(void **hook, char *name)
7096 if (hook == NULL)
7097 errx(1, "set_hook");
7099 if (*hook == NULL) {
7100 *hook = dlsym(RTLD_NEXT, name);
7101 if (*hook == NULL)
7102 errx(1, "can't hook %s", name);
7106 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7107 gboolean
7108 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
7110 g_return_val_if_fail(cookie1, FALSE);
7111 g_return_val_if_fail(cookie2, FALSE);
7113 return (!strcmp (cookie1->name, cookie2->name) &&
7114 !strcmp (cookie1->value, cookie2->value) &&
7115 !strcmp (cookie1->path, cookie2->path) &&
7116 !strcmp (cookie1->domain, cookie2->domain));
7119 void
7120 transfer_cookies(void)
7122 GSList *cf;
7123 SoupCookie *sc, *pc;
7125 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7127 for (;cf; cf = cf->next) {
7128 pc = cf->data;
7129 sc = soup_cookie_copy(pc);
7130 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
7133 soup_cookies_free(cf);
7136 void
7137 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
7139 GSList *cf;
7140 SoupCookie *ci;
7142 print_cookie("soup_cookie_jar_delete_cookie", c);
7144 if (cookies_enabled == 0)
7145 return;
7147 if (jar == NULL || c == NULL)
7148 return;
7150 /* find and remove from persistent jar */
7151 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7153 for (;cf; cf = cf->next) {
7154 ci = cf->data;
7155 if (soup_cookie_equal(ci, c)) {
7156 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
7157 break;
7161 soup_cookies_free(cf);
7163 /* delete from session jar */
7164 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
7167 void
7168 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
7170 struct domain *d = NULL;
7171 SoupCookie *c;
7172 FILE *r_cookie_f;
7174 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
7175 jar, p_cookiejar, s_cookiejar);
7177 if (cookies_enabled == 0)
7178 return;
7180 /* see if we are up and running */
7181 if (p_cookiejar == NULL) {
7182 _soup_cookie_jar_add_cookie(jar, cookie);
7183 return;
7185 /* disallow p_cookiejar adds, shouldn't happen */
7186 if (jar == p_cookiejar)
7187 return;
7189 if (enable_cookie_whitelist &&
7190 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
7191 blocked_cookies++;
7192 DNPRINTF(XT_D_COOKIE,
7193 "soup_cookie_jar_add_cookie: reject %s\n",
7194 cookie->domain);
7195 if (save_rejected_cookies) {
7196 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
7197 show_oops_s("can't open reject cookie file");
7198 return;
7200 fseek(r_cookie_f, 0, SEEK_END);
7201 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7202 cookie->http_only ? "#HttpOnly_" : "",
7203 cookie->domain,
7204 *cookie->domain == '.' ? "TRUE" : "FALSE",
7205 cookie->path,
7206 cookie->secure ? "TRUE" : "FALSE",
7207 cookie->expires ?
7208 (gulong)soup_date_to_time_t(cookie->expires) :
7210 cookie->name,
7211 cookie->value);
7212 fflush(r_cookie_f);
7213 fclose(r_cookie_f);
7215 if (!allow_volatile_cookies)
7216 return;
7219 if (cookie->expires == NULL && session_timeout) {
7220 soup_cookie_set_expires(cookie,
7221 soup_date_new_from_now(session_timeout));
7222 print_cookie("modified add cookie", cookie);
7225 /* see if we are white listed for persistence */
7226 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
7227 /* add to persistent jar */
7228 c = soup_cookie_copy(cookie);
7229 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
7230 _soup_cookie_jar_add_cookie(p_cookiejar, c);
7233 /* add to session jar */
7234 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
7235 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
7238 void
7239 setup_cookies(void)
7241 char file[PATH_MAX];
7243 set_hook((void *)&_soup_cookie_jar_add_cookie,
7244 "soup_cookie_jar_add_cookie");
7245 set_hook((void *)&_soup_cookie_jar_delete_cookie,
7246 "soup_cookie_jar_delete_cookie");
7248 if (cookies_enabled == 0)
7249 return;
7252 * the following code is intricate due to overriding several libsoup
7253 * functions.
7254 * do not alter order of these operations.
7257 /* rejected cookies */
7258 if (save_rejected_cookies)
7259 snprintf(rc_fname, sizeof file, "%s/rejected.txt", work_dir);
7261 /* persistent cookies */
7262 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
7263 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
7265 /* session cookies */
7266 s_cookiejar = soup_cookie_jar_new();
7267 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
7268 cookie_policy, (void *)NULL);
7269 transfer_cookies();
7271 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
7274 void
7275 setup_proxy(char *uri)
7277 if (proxy_uri) {
7278 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
7279 soup_uri_free(proxy_uri);
7280 proxy_uri = NULL;
7282 if (http_proxy) {
7283 if (http_proxy != uri) {
7284 g_free(http_proxy);
7285 http_proxy = NULL;
7289 if (uri) {
7290 http_proxy = g_strdup(uri);
7291 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
7292 proxy_uri = soup_uri_new(http_proxy);
7293 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
7298 send_url_to_socket(char *url)
7300 int s, len, rv = -1;
7301 struct sockaddr_un sa;
7303 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7304 warnx("send_url_to_socket: socket");
7305 return (-1);
7308 sa.sun_family = AF_UNIX;
7309 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7310 work_dir, XT_SOCKET_FILE);
7311 len = SUN_LEN(&sa);
7313 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7314 warnx("send_url_to_socket: connect");
7315 goto done;
7318 if (send(s, url, strlen(url) + 1, 0) == -1) {
7319 warnx("send_url_to_socket: send");
7320 goto done;
7322 done:
7323 close(s);
7324 return (rv);
7327 void
7328 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
7330 int s, n;
7331 char str[XT_MAX_URL_LENGTH];
7332 socklen_t t = sizeof(struct sockaddr_un);
7333 struct sockaddr_un sa;
7334 struct passwd *p;
7335 uid_t uid;
7336 gid_t gid;
7338 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
7339 warn("socket_watcher: accept");
7340 return;
7343 if (getpeereid(s, &uid, &gid) == -1) {
7344 warn("socket_watcher: getpeereid");
7345 return;
7347 if (uid != getuid() || gid != getgid()) {
7348 warnx("socket_watcher: unauthorized user");
7349 return;
7352 p = getpwuid(uid);
7353 if (p == NULL) {
7354 warnx("socket_watcher: not a valid user");
7355 return;
7358 n = recv(s, str, sizeof(str), 0);
7359 if (n <= 0)
7360 return;
7362 create_new_tab(str, NULL, 1);
7366 is_running(void)
7368 int s, len, rv = 1;
7369 struct sockaddr_un sa;
7371 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7372 warn("is_running: socket");
7373 return (-1);
7376 sa.sun_family = AF_UNIX;
7377 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7378 work_dir, XT_SOCKET_FILE);
7379 len = SUN_LEN(&sa);
7381 /* connect to see if there is a listener */
7382 if (connect(s, (struct sockaddr *)&sa, len) == -1)
7383 rv = 0; /* not running */
7384 else
7385 rv = 1; /* already running */
7387 close(s);
7389 return (rv);
7393 build_socket(void)
7395 int s, len;
7396 struct sockaddr_un sa;
7398 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7399 warn("build_socket: socket");
7400 return (-1);
7403 sa.sun_family = AF_UNIX;
7404 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7405 work_dir, XT_SOCKET_FILE);
7406 len = SUN_LEN(&sa);
7408 /* connect to see if there is a listener */
7409 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7410 /* no listener so we will */
7411 unlink(sa.sun_path);
7413 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
7414 warn("build_socket: bind");
7415 goto done;
7418 if (listen(s, 1) == -1) {
7419 warn("build_socket: listen");
7420 goto done;
7423 return (s);
7426 done:
7427 close(s);
7428 return (-1);
7431 void
7432 usage(void)
7434 fprintf(stderr,
7435 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
7436 exit(0);
7440 main(int argc, char *argv[])
7442 struct stat sb;
7443 int c, s, optn = 0, focus = 1;
7444 char conf[PATH_MAX] = { '\0' };
7445 char file[PATH_MAX];
7446 char *env_proxy = NULL;
7447 FILE *f = NULL;
7448 struct karg a;
7449 struct sigaction sact;
7451 start_argv = argv;
7453 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
7455 while ((c = getopt(argc, argv, "STVf:s:tn")) != -1) {
7456 switch (c) {
7457 case 'S':
7458 show_url = 0;
7459 break;
7460 case 'T':
7461 show_tabs = 0;
7462 break;
7463 case 'V':
7464 errx(0 , "Version: %s", version);
7465 break;
7466 case 'f':
7467 strlcpy(conf, optarg, sizeof(conf));
7468 break;
7469 case 's':
7470 strlcpy(named_session, optarg, sizeof(named_session));
7471 break;
7472 case 't':
7473 tabless = 1;
7474 break;
7475 case 'n':
7476 optn = 1;
7477 break;
7478 default:
7479 usage();
7480 /* NOTREACHED */
7483 argc -= optind;
7484 argv += optind;
7486 RB_INIT(&hl);
7487 RB_INIT(&js_wl);
7488 RB_INIT(&downloads);
7490 TAILQ_INIT(&tabs);
7491 TAILQ_INIT(&mtl);
7492 TAILQ_INIT(&aliases);
7493 TAILQ_INIT(&undos);
7494 TAILQ_INIT(&kbl);
7496 init_keybindings();
7498 gnutls_global_init();
7500 /* generate session keys for xtp pages */
7501 generate_xtp_session_key(&dl_session_key);
7502 generate_xtp_session_key(&hl_session_key);
7503 generate_xtp_session_key(&cl_session_key);
7504 generate_xtp_session_key(&fl_session_key);
7506 /* prepare gtk */
7507 gtk_init(&argc, &argv);
7508 if (!g_thread_supported())
7509 g_thread_init(NULL);
7511 /* signals */
7512 bzero(&sact, sizeof(sact));
7513 sigemptyset(&sact.sa_mask);
7514 sact.sa_handler = sigchild;
7515 sact.sa_flags = SA_NOCLDSTOP;
7516 sigaction(SIGCHLD, &sact, NULL);
7518 /* set download dir */
7519 pwd = getpwuid(getuid());
7520 if (pwd == NULL)
7521 errx(1, "invalid user %d", getuid());
7522 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
7524 /* set default string settings */
7525 home = g_strdup("http://www.peereboom.us");
7526 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
7527 resource_dir = g_strdup("/usr/local/share/xxxterm/");
7528 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
7530 /* read config file */
7531 if (strlen(conf) == 0)
7532 snprintf(conf, sizeof conf, "%s/.%s",
7533 pwd->pw_dir, XT_CONF_FILE);
7534 config_parse(conf, 0);
7536 /* working directory */
7537 if (strlen(work_dir) == 0)
7538 snprintf(work_dir, sizeof work_dir, "%s/%s",
7539 pwd->pw_dir, XT_DIR);
7540 if (stat(work_dir, &sb)) {
7541 if (mkdir(work_dir, S_IRWXU) == -1)
7542 err(1, "mkdir work_dir");
7543 if (stat(work_dir, &sb))
7544 err(1, "stat work_dir");
7546 if (S_ISDIR(sb.st_mode) == 0)
7547 errx(1, "%s not a dir", work_dir);
7548 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7549 warnx("fixing invalid permissions on %s", work_dir);
7550 if (chmod(work_dir, S_IRWXU) == -1)
7551 err(1, "chmod");
7554 /* icon cache dir */
7555 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
7556 if (stat(cache_dir, &sb)) {
7557 if (mkdir(cache_dir, S_IRWXU) == -1)
7558 err(1, "mkdir cache_dir");
7559 if (stat(cache_dir, &sb))
7560 err(1, "stat cache_dir");
7562 if (S_ISDIR(sb.st_mode) == 0)
7563 errx(1, "%s not a dir", cache_dir);
7564 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7565 warnx("fixing invalid permissions on %s", cache_dir);
7566 if (chmod(cache_dir, S_IRWXU) == -1)
7567 err(1, "chmod");
7570 /* certs dir */
7571 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
7572 if (stat(certs_dir, &sb)) {
7573 if (mkdir(certs_dir, S_IRWXU) == -1)
7574 err(1, "mkdir certs_dir");
7575 if (stat(certs_dir, &sb))
7576 err(1, "stat certs_dir");
7578 if (S_ISDIR(sb.st_mode) == 0)
7579 errx(1, "%s not a dir", certs_dir);
7580 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7581 warnx("fixing invalid permissions on %s", certs_dir);
7582 if (chmod(certs_dir, S_IRWXU) == -1)
7583 err(1, "chmod");
7586 /* sessions dir */
7587 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
7588 work_dir, XT_SESSIONS_DIR);
7589 if (stat(sessions_dir, &sb)) {
7590 if (mkdir(sessions_dir, S_IRWXU) == -1)
7591 err(1, "mkdir sessions_dir");
7592 if (stat(sessions_dir, &sb))
7593 err(1, "stat sessions_dir");
7595 if (S_ISDIR(sb.st_mode) == 0)
7596 errx(1, "%s not a dir", sessions_dir);
7597 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7598 warnx("fixing invalid permissions on %s", sessions_dir);
7599 if (chmod(sessions_dir, S_IRWXU) == -1)
7600 err(1, "chmod");
7602 /* runtime settings that can override config file */
7603 if (runtime_settings[0] != '\0')
7604 config_parse(runtime_settings, 1);
7606 /* download dir */
7607 if (!strcmp(download_dir, pwd->pw_dir))
7608 strlcat(download_dir, "/downloads", sizeof download_dir);
7609 if (stat(download_dir, &sb)) {
7610 if (mkdir(download_dir, S_IRWXU) == -1)
7611 err(1, "mkdir download_dir");
7612 if (stat(download_dir, &sb))
7613 err(1, "stat download_dir");
7615 if (S_ISDIR(sb.st_mode) == 0)
7616 errx(1, "%s not a dir", download_dir);
7617 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7618 warnx("fixing invalid permissions on %s", download_dir);
7619 if (chmod(download_dir, S_IRWXU) == -1)
7620 err(1, "chmod");
7623 /* favorites file */
7624 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
7625 if (stat(file, &sb)) {
7626 warnx("favorites file doesn't exist, creating it");
7627 if ((f = fopen(file, "w")) == NULL)
7628 err(1, "favorites");
7629 fclose(f);
7632 /* cookies */
7633 session = webkit_get_default_session();
7634 setup_cookies();
7636 /* certs */
7637 if (ssl_ca_file) {
7638 if (stat(ssl_ca_file, &sb)) {
7639 warn("no CA file: %s", ssl_ca_file);
7640 g_free(ssl_ca_file);
7641 ssl_ca_file = NULL;
7642 } else
7643 g_object_set(session,
7644 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
7645 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
7646 (void *)NULL);
7649 /* proxy */
7650 env_proxy = getenv("http_proxy");
7651 if (env_proxy)
7652 setup_proxy(env_proxy);
7653 else
7654 setup_proxy(http_proxy);
7656 /* see if there is already an xxxterm running */
7657 if (single_instance && is_running()) {
7658 optn = 1;
7659 warnx("already running");
7662 if (optn) {
7663 while (argc) {
7664 send_url_to_socket(argv[0]);
7666 argc--;
7667 argv++;
7669 exit(0);
7672 /* go graphical */
7673 create_canvas();
7675 if (save_global_history)
7676 restore_global_history();
7678 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
7679 restore_saved_tabs();
7680 else {
7681 a.s = named_session;
7682 a.i = XT_SES_DONOTHING;
7683 open_tabs(NULL, &a);
7686 while (argc) {
7687 create_new_tab(argv[0], NULL, focus);
7688 focus = 0;
7690 argc--;
7691 argv++;
7694 if (TAILQ_EMPTY(&tabs))
7695 create_new_tab(home, NULL, 1);
7697 if (enable_socket)
7698 if ((s = build_socket()) != -1)
7699 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
7701 gtk_main();
7703 gnutls_global_deinit();
7705 return (0);