remove some comments
[xxxterm.git] / xxxterm.c
blobb950526e1a01227309cb2740731e3a6ba6638c75
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")
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")
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 set_status(struct tab *t, gchar *s, int status)
761 gchar *type = NULL;
763 if (s == NULL)
764 return;
766 switch (status) {
767 case XT_STATUS_LOADING:
768 type = g_strdup_printf("Loading: %s", s);
769 s = type;
770 break;
771 case XT_STATUS_LINK:
772 type = g_strdup_printf("Link: %s", s);
773 if (!t->status)
774 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
775 s = type;
776 break;
777 case XT_STATUS_URI:
778 type = g_strdup_printf("%s", s);
779 if (!t->status) {
780 t->status = g_strdup(type);
782 s = type;
783 if (!t->status)
784 t->status = g_strdup(s);
785 break;
786 case XT_STATUS_NOTHING:
787 /* FALL THROUGH */
788 default:
789 break;
791 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
792 if (type)
793 g_free(type);
796 void
797 hide_oops(struct tab *t)
799 gtk_widget_hide(t->oops);
802 void
803 hide_cmd(struct tab *t)
805 gtk_widget_hide(t->cmd);
808 void
809 show_cmd(struct tab *t)
811 gtk_widget_hide(t->oops);
812 gtk_widget_show(t->cmd);
815 void
816 show_oops(struct tab *t, const char *fmt, ...)
818 va_list ap;
819 char *msg;
821 if (fmt == NULL)
822 return;
824 va_start(ap, fmt);
825 if (vasprintf(&msg, fmt, ap) == -1)
826 errx(1, "show_oops failed");
827 va_end(ap);
829 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
830 gtk_widget_hide(t->cmd);
831 gtk_widget_show(t->oops);
834 /* XXX collapse with show_oops */
835 void
836 show_oops_s(const char *fmt, ...)
838 va_list ap;
839 char *msg;
840 struct tab *ti, *t = NULL;
842 if (fmt == NULL)
843 return;
845 TAILQ_FOREACH(ti, &tabs, entry)
846 if (ti->tab_id == gtk_notebook_current_page(notebook)) {
847 t = ti;
848 break;
850 if (t == NULL)
851 return;
853 va_start(ap, fmt);
854 if (vasprintf(&msg, fmt, ap) == -1)
855 errx(1, "show_oops_s failed");
856 va_end(ap);
858 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
859 gtk_widget_hide(t->cmd);
860 gtk_widget_show(t->oops);
863 char *
864 get_as_string(struct settings *s)
866 char *r = NULL;
868 if (s == NULL)
869 return (NULL);
871 if (s->s) {
872 if (s->s->get)
873 r = s->s->get(s);
874 else
875 warnx("get_as_string skip %s\n", s->name);
876 } else if (s->type == XT_S_INT)
877 r = g_strdup_printf("%d", *s->ival);
878 else if (s->type == XT_S_STR)
879 r = g_strdup(*s->sval);
880 else
881 r = g_strdup_printf("INVALID TYPE");
883 return (r);
886 void
887 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
889 int i;
890 char *s;
892 for (i = 0; i < LENGTH(rs); i++) {
893 if (rs[i].s && rs[i].s->walk)
894 rs[i].s->walk(&rs[i], cb, cb_args);
895 else {
896 s = get_as_string(&rs[i]);
897 cb(&rs[i], s, cb_args);
898 g_free(s);
904 set_browser_mode(struct settings *s, char *val)
906 if (!strcmp(val, "whitelist")) {
907 browser_mode = XT_COOKIE_WHITELIST;
908 allow_volatile_cookies = 0;
909 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
910 cookies_enabled = 1;
911 enable_cookie_whitelist = 1;
912 read_only_cookies = 0;
913 save_rejected_cookies = 0;
914 session_timeout = 3600;
915 enable_scripts = 0;
916 enable_js_whitelist = 1;
917 } else if (!strcmp(val, "normal")) {
918 browser_mode = XT_COOKIE_NORMAL;
919 allow_volatile_cookies = 0;
920 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
921 cookies_enabled = 1;
922 enable_cookie_whitelist = 0;
923 read_only_cookies = 0;
924 save_rejected_cookies = 0;
925 session_timeout = 3600;
926 enable_scripts = 1;
927 enable_js_whitelist = 0;
928 } else
929 return (1);
931 return (0);
934 char *
935 get_browser_mode(struct settings *s)
937 char *r = NULL;
939 if (browser_mode == XT_COOKIE_WHITELIST)
940 r = g_strdup("whitelist");
941 else if (browser_mode == XT_COOKIE_NORMAL)
942 r = g_strdup("normal");
943 else
944 return (NULL);
946 return (r);
950 set_cookie_policy(struct settings *s, char *val)
952 if (!strcmp(val, "no3rdparty"))
953 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
954 else if (!strcmp(val, "accept"))
955 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
956 else if (!strcmp(val, "reject"))
957 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
958 else
959 return (1);
961 return (0);
964 char *
965 get_cookie_policy(struct settings *s)
967 char *r = NULL;
969 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
970 r = g_strdup("no3rdparty");
971 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
972 r = g_strdup("accept");
973 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
974 r = g_strdup("reject");
975 else
976 return (NULL);
978 return (r);
981 char *
982 get_download_dir(struct settings *s)
984 if (download_dir[0] == '\0')
985 return (0);
986 return (g_strdup(download_dir));
990 set_download_dir(struct settings *s, char *val)
992 if (val[0] == '~')
993 snprintf(download_dir, sizeof download_dir, "%s/%s",
994 pwd->pw_dir, &val[1]);
995 else
996 strlcpy(download_dir, val, sizeof download_dir);
998 return (0);
1002 * Session IDs.
1003 * We use these to prevent people putting xxxt:// URLs on
1004 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1006 #define XT_XTP_SES_KEY_SZ 8
1007 #define XT_XTP_SES_KEY_HEX_FMT \
1008 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1009 char *dl_session_key; /* downloads */
1010 char *hl_session_key; /* history list */
1011 char *cl_session_key; /* cookie list */
1012 char *fl_session_key; /* favorites list */
1014 char work_dir[PATH_MAX];
1015 char certs_dir[PATH_MAX];
1016 char cache_dir[PATH_MAX];
1017 char sessions_dir[PATH_MAX];
1018 char cookie_file[PATH_MAX];
1019 SoupURI *proxy_uri = NULL;
1020 SoupSession *session;
1021 SoupCookieJar *s_cookiejar;
1022 SoupCookieJar *p_cookiejar;
1023 char rc_fname[PATH_MAX];
1025 struct mime_type_list mtl;
1026 struct alias_list aliases;
1028 /* protos */
1029 void create_new_tab(char *, struct undo *, int);
1030 void delete_tab(struct tab *);
1031 void adjustfont_webkit(struct tab *, int);
1032 int run_script(struct tab *, char *);
1033 int download_rb_cmp(struct download *, struct download *);
1036 history_rb_cmp(struct history *h1, struct history *h2)
1038 return (strcmp(h1->uri, h2->uri));
1040 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1043 domain_rb_cmp(struct domain *d1, struct domain *d2)
1045 return (strcmp(d1->d, d2->d));
1047 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1049 char *
1050 get_work_dir(struct settings *s)
1052 if (work_dir[0] == '\0')
1053 return (0);
1054 return (g_strdup(work_dir));
1058 set_work_dir(struct settings *s, char *val)
1060 if (val[0] == '~')
1061 snprintf(work_dir, sizeof work_dir, "%s/%s",
1062 pwd->pw_dir, &val[1]);
1063 else
1064 strlcpy(work_dir, val, sizeof work_dir);
1066 return (0);
1070 * generate a session key to secure xtp commands.
1071 * pass in a ptr to the key in question and it will
1072 * be modified in place.
1074 void
1075 generate_xtp_session_key(char **key)
1077 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1079 /* free old key */
1080 if (*key)
1081 g_free(*key);
1083 /* make a new one */
1084 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1085 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1086 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1087 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1089 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1093 * validate a xtp session key.
1094 * return 1 if OK
1097 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1099 if (strcmp(trusted, untrusted) != 0) {
1100 show_oops(t, "%s: xtp session key mismatch possible spoof",
1101 __func__);
1102 return (0);
1105 return (1);
1109 download_rb_cmp(struct download *e1, struct download *e2)
1111 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1113 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1115 struct valid_url_types {
1116 char *type;
1117 } vut[] = {
1118 { "http://" },
1119 { "https://" },
1120 { "ftp://" },
1121 { "file://" },
1122 { XT_XTP_STR },
1126 valid_url_type(char *url)
1128 int i;
1130 for (i = 0; i < LENGTH(vut); i++)
1131 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1132 return (0);
1134 return (1);
1137 void
1138 print_cookie(char *msg, SoupCookie *c)
1140 if (c == NULL)
1141 return;
1143 if (msg)
1144 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1145 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1146 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1147 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1148 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1149 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1150 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1151 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1152 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1153 DNPRINTF(XT_D_COOKIE, "====================================\n");
1156 void
1157 walk_alias(struct settings *s,
1158 void (*cb)(struct settings *, char *, void *), void *cb_args)
1160 struct alias *a;
1161 char *str;
1163 if (s == NULL || cb == NULL) {
1164 show_oops_s("walk_alias invalid parameters");
1165 return;
1168 TAILQ_FOREACH(a, &aliases, entry) {
1169 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1170 cb(s, str, cb_args);
1171 g_free(str);
1175 char *
1176 match_alias(char *url_in)
1178 struct alias *a;
1179 char *arg;
1180 char *url_out = NULL, *search, *enc_arg;
1182 search = g_strdup(url_in);
1183 arg = search;
1184 if (strsep(&arg, " \t") == NULL) {
1185 show_oops_s("match_alias: NULL URL");
1186 goto done;
1189 TAILQ_FOREACH(a, &aliases, entry) {
1190 if (!strcmp(search, a->a_name))
1191 break;
1194 if (a != NULL) {
1195 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1196 a->a_name);
1197 if (arg != NULL) {
1198 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1199 url_out = g_strdup_printf(a->a_uri, enc_arg);
1200 g_free(enc_arg);
1201 } else
1202 url_out = g_strdup(a->a_uri);
1204 done:
1205 g_free(search);
1206 return (url_out);
1209 char *
1210 guess_url_type(char *url_in)
1212 struct stat sb;
1213 char *url_out = NULL, *enc_search = NULL;
1215 url_out = match_alias(url_in);
1216 if (url_out != NULL)
1217 return (url_out);
1219 if (guess_search) {
1221 * If there is no dot nor slash in the string and it isn't a
1222 * path to a local file and doesn't resolves to an IP, assume
1223 * that the user wants to search for the string.
1226 if (strchr(url_in, '.') == NULL &&
1227 strchr(url_in, '/') == NULL &&
1228 stat(url_in, &sb) != 0 &&
1229 gethostbyname(url_in) == NULL) {
1231 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1232 url_out = g_strdup_printf(search_string, enc_search);
1233 g_free(enc_search);
1234 return (url_out);
1238 /* XXX not sure about this heuristic */
1239 if (stat(url_in, &sb) == 0)
1240 url_out = g_strdup_printf("file://%s", url_in);
1241 else
1242 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1244 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1246 return (url_out);
1249 void
1250 load_uri(struct tab *t, gchar *uri)
1252 struct karg args;
1253 gchar *newuri = NULL;
1254 int i;
1256 if (uri == NULL)
1257 return;
1259 /* Strip leading spaces. */
1260 while(*uri && isspace(*uri))
1261 uri++;
1263 if (strlen(uri) == 0) {
1264 blank(t, NULL);
1265 return;
1268 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1269 for (i = 0; i < LENGTH(about_list); i++)
1270 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1271 bzero(&args, sizeof args);
1272 about_list[i].func(t, &args);
1273 return;
1275 show_oops(t, "invalid about page");
1276 return;
1279 if (valid_url_type(uri)) {
1280 newuri = guess_url_type(uri);
1281 uri = newuri;
1284 set_status(t, (char *)uri, XT_STATUS_LOADING);
1285 webkit_web_view_load_uri(t->wv, uri);
1287 if (newuri)
1288 g_free(newuri);
1291 const gchar *
1292 get_uri(WebKitWebView *wv)
1294 WebKitWebFrame *frame;
1295 const gchar *uri;
1297 frame = webkit_web_view_get_main_frame(wv);
1298 uri = webkit_web_frame_get_uri(frame);
1300 if (uri && strlen(uri) > 0)
1301 return (uri);
1302 else
1303 return (NULL);
1307 add_alias(struct settings *s, char *line)
1309 char *l, *alias;
1310 struct alias *a = NULL;
1312 if (s == NULL || line == NULL) {
1313 show_oops_s("add_alias invalid parameters");
1314 return (1);
1317 l = line;
1318 a = g_malloc(sizeof(*a));
1320 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1321 show_oops_s("add_alias: incomplete alias definition");
1322 goto bad;
1324 if (strlen(alias) == 0 || strlen(l) == 0) {
1325 show_oops_s("add_alias: invalid alias definition");
1326 goto bad;
1329 a->a_name = g_strdup(alias);
1330 a->a_uri = g_strdup(l);
1332 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1334 TAILQ_INSERT_TAIL(&aliases, a, entry);
1336 return (0);
1337 bad:
1338 if (a)
1339 g_free(a);
1340 return (1);
1344 add_mime_type(struct settings *s, char *line)
1346 char *mime_type;
1347 char *l;
1348 struct mime_type *m = NULL;
1350 /* XXX this could be smarter */
1352 if (line == NULL) {
1353 show_oops_s("add_mime_type invalid parameters");
1354 return (1);
1357 l = line;
1358 m = g_malloc(sizeof(*m));
1360 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1361 show_oops_s("add_mime_type: invalid mime_type");
1362 goto bad;
1364 if (mime_type[strlen(mime_type) - 1] == '*') {
1365 mime_type[strlen(mime_type) - 1] = '\0';
1366 m->mt_default = 1;
1367 } else
1368 m->mt_default = 0;
1370 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1371 show_oops_s("add_mime_type: invalid mime_type");
1372 goto bad;
1375 m->mt_type = g_strdup(mime_type);
1376 m->mt_action = g_strdup(l);
1378 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1379 m->mt_type, m->mt_action, m->mt_default);
1381 TAILQ_INSERT_TAIL(&mtl, m, entry);
1383 return (0);
1384 bad:
1385 if (m)
1386 g_free(m);
1387 return (1);
1390 struct mime_type *
1391 find_mime_type(char *mime_type)
1393 struct mime_type *m, *def = NULL, *rv = NULL;
1395 TAILQ_FOREACH(m, &mtl, entry) {
1396 if (m->mt_default &&
1397 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1398 def = m;
1400 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1401 rv = m;
1402 break;
1406 if (rv == NULL)
1407 rv = def;
1409 return (rv);
1412 void
1413 walk_mime_type(struct settings *s,
1414 void (*cb)(struct settings *, char *, void *), void *cb_args)
1416 struct mime_type *m;
1417 char *str;
1419 if (s == NULL || cb == NULL)
1420 show_oops_s("walk_mime_type invalid parameters");
1422 TAILQ_FOREACH(m, &mtl, entry) {
1423 str = g_strdup_printf("%s%s --> %s",
1424 m->mt_type,
1425 m->mt_default ? "*" : "",
1426 m->mt_action);
1427 cb(s, str, cb_args);
1428 g_free(str);
1432 void
1433 wl_add(char *str, struct domain_list *wl, int handy)
1435 struct domain *d;
1436 int add_dot = 0;
1438 if (str == NULL || wl == NULL)
1439 return;
1440 if (strlen(str) < 2)
1441 return;
1443 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1445 /* treat *.moo.com the same as .moo.com */
1446 if (str[0] == '*' && str[1] == '.')
1447 str = &str[1];
1448 else if (str[0] == '.')
1449 str = &str[0];
1450 else
1451 add_dot = 1;
1453 d = g_malloc(sizeof *d);
1454 if (add_dot)
1455 d->d = g_strdup_printf(".%s", str);
1456 else
1457 d->d = g_strdup(str);
1458 d->handy = handy;
1460 if (RB_INSERT(domain_list, wl, d))
1461 goto unwind;
1463 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1464 return;
1465 unwind:
1466 if (d) {
1467 if (d->d)
1468 g_free(d->d);
1469 g_free(d);
1474 add_cookie_wl(struct settings *s, char *entry)
1476 wl_add(entry, &c_wl, 1);
1477 return (0);
1480 void
1481 walk_cookie_wl(struct settings *s,
1482 void (*cb)(struct settings *, char *, void *), void *cb_args)
1484 struct domain *d;
1486 if (s == NULL || cb == NULL) {
1487 show_oops_s("walk_cookie_wl invalid parameters");
1488 return;
1491 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1492 cb(s, d->d, cb_args);
1495 void
1496 walk_js_wl(struct settings *s,
1497 void (*cb)(struct settings *, char *, void *), void *cb_args)
1499 struct domain *d;
1501 if (s == NULL || cb == NULL) {
1502 show_oops_s("walk_js_wl invalid parameters");
1503 return;
1506 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1507 cb(s, d->d, cb_args);
1511 add_js_wl(struct settings *s, char *entry)
1513 wl_add(entry, &js_wl, 1 /* persistent */);
1514 return (0);
1517 struct domain *
1518 wl_find(const gchar *search, struct domain_list *wl)
1520 int i;
1521 struct domain *d = NULL, dfind;
1522 gchar *s = NULL;
1524 if (search == NULL || wl == NULL)
1525 return (NULL);
1526 if (strlen(search) < 2)
1527 return (NULL);
1529 if (search[0] != '.')
1530 s = g_strdup_printf(".%s", search);
1531 else
1532 s = g_strdup(search);
1534 for (i = strlen(s) - 1; i >= 0; i--) {
1535 if (s[i] == '.') {
1536 dfind.d = &s[i];
1537 d = RB_FIND(domain_list, wl, &dfind);
1538 if (d)
1539 goto done;
1543 done:
1544 if (s)
1545 g_free(s);
1547 return (d);
1550 struct domain *
1551 wl_find_uri(const gchar *s, struct domain_list *wl)
1553 int i;
1554 char *ss;
1555 struct domain *r;
1557 if (s == NULL || wl == NULL)
1558 return (NULL);
1560 if (!strncmp(s, "http://", strlen("http://")))
1561 s = &s[strlen("http://")];
1562 else if (!strncmp(s, "https://", strlen("https://")))
1563 s = &s[strlen("https://")];
1565 if (strlen(s) < 2)
1566 return (NULL);
1568 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1569 /* chop string at first slash */
1570 if (s[i] == '/' || s[i] == '\0') {
1571 ss = g_strdup(s);
1572 ss[i] = '\0';
1573 r = wl_find(ss, wl);
1574 g_free(ss);
1575 return (r);
1578 return (NULL);
1581 char *
1582 get_toplevel_domain(char *domain)
1584 char *s;
1585 int found = 0;
1587 if (domain == NULL)
1588 return (NULL);
1589 if (strlen(domain) < 2)
1590 return (NULL);
1592 s = &domain[strlen(domain) - 1];
1593 while (s != domain) {
1594 if (*s == '.') {
1595 found++;
1596 if (found == 2)
1597 return (s);
1599 s--;
1602 if (found)
1603 return (domain);
1605 return (NULL);
1609 settings_add(char *var, char *val)
1611 int i, rv, *p;
1612 char **s;
1614 /* get settings */
1615 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1616 if (strcmp(var, rs[i].name))
1617 continue;
1619 if (rs[i].s) {
1620 if (rs[i].s->set(&rs[i], val))
1621 errx(1, "invalid value for %s", var);
1622 rv = 1;
1623 break;
1624 } else
1625 switch (rs[i].type) {
1626 case XT_S_INT:
1627 p = rs[i].ival;
1628 *p = atoi(val);
1629 rv = 1;
1630 break;
1631 case XT_S_STR:
1632 s = rs[i].sval;
1633 if (s == NULL)
1634 errx(1, "invalid sval for %s",
1635 rs[i].name);
1636 if (*s)
1637 g_free(*s);
1638 *s = g_strdup(val);
1639 rv = 1;
1640 break;
1641 case XT_S_INVALID:
1642 default:
1643 errx(1, "invalid type for %s", var);
1645 break;
1647 return (rv);
1650 #define WS "\n= \t"
1651 void
1652 config_parse(char *filename, int runtime)
1654 FILE *config, *f;
1655 char *line, *cp, *var, *val;
1656 size_t len, lineno = 0;
1657 int handled;
1658 char file[PATH_MAX];
1659 struct stat sb;
1661 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1663 if (filename == NULL)
1664 return;
1666 if (runtime && runtime_settings[0] != '\0') {
1667 snprintf(file, sizeof file, "%s/%s",
1668 work_dir, runtime_settings);
1669 if (stat(file, &sb)) {
1670 warnx("runtime file doesn't exist, creating it");
1671 if ((f = fopen(file, "w")) == NULL)
1672 err(1, "runtime");
1673 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1674 fclose(f);
1676 } else
1677 strlcpy(file, filename, sizeof file);
1679 if ((config = fopen(file, "r")) == NULL) {
1680 warn("config_parse: cannot open %s", filename);
1681 return;
1684 for (;;) {
1685 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1686 if (feof(config) || ferror(config))
1687 break;
1689 cp = line;
1690 cp += (long)strspn(cp, WS);
1691 if (cp[0] == '\0') {
1692 /* empty line */
1693 free(line);
1694 continue;
1697 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1698 errx(1, "invalid config file entry: %s", line);
1700 cp += (long)strspn(cp, WS);
1702 if ((val = strsep(&cp, "\0")) == NULL)
1703 break;
1705 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1706 handled = settings_add(var, val);
1707 if (handled == 0)
1708 errx(1, "invalid conf file entry: %s=%s", var, val);
1710 free(line);
1713 fclose(config);
1716 char *
1717 js_ref_to_string(JSContextRef context, JSValueRef ref)
1719 char *s = NULL;
1720 size_t l;
1721 JSStringRef jsref;
1723 jsref = JSValueToStringCopy(context, ref, NULL);
1724 if (jsref == NULL)
1725 return (NULL);
1727 l = JSStringGetMaximumUTF8CStringSize(jsref);
1728 s = g_malloc(l);
1729 if (s)
1730 JSStringGetUTF8CString(jsref, s, l);
1731 JSStringRelease(jsref);
1733 return (s);
1736 void
1737 disable_hints(struct tab *t)
1739 bzero(t->hint_buf, sizeof t->hint_buf);
1740 bzero(t->hint_num, sizeof t->hint_num);
1741 run_script(t, "vimprobable_clear()");
1742 t->hints_on = 0;
1743 t->hint_mode = XT_HINT_NONE;
1746 void
1747 enable_hints(struct tab *t)
1749 bzero(t->hint_buf, sizeof t->hint_buf);
1750 run_script(t, "vimprobable_show_hints()");
1751 t->hints_on = 1;
1752 t->hint_mode = XT_HINT_NONE;
1755 #define XT_JS_OPEN ("open;")
1756 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1757 #define XT_JS_FIRE ("fire;")
1758 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1759 #define XT_JS_FOUND ("found;")
1760 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1763 run_script(struct tab *t, char *s)
1765 JSGlobalContextRef ctx;
1766 WebKitWebFrame *frame;
1767 JSStringRef str;
1768 JSValueRef val, exception;
1769 char *es, buf[128];
1771 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1772 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1774 frame = webkit_web_view_get_main_frame(t->wv);
1775 ctx = webkit_web_frame_get_global_context(frame);
1777 str = JSStringCreateWithUTF8CString(s);
1778 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1779 NULL, 0, &exception);
1780 JSStringRelease(str);
1782 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1783 if (val == NULL) {
1784 es = js_ref_to_string(ctx, exception);
1785 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1786 g_free(es);
1787 return (1);
1788 } else {
1789 es = js_ref_to_string(ctx, val);
1790 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1792 /* handle return value right here */
1793 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1794 disable_hints(t);
1795 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1798 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1799 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1800 &es[XT_JS_FIRE_LEN]);
1801 run_script(t, buf);
1802 disable_hints(t);
1805 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1806 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1807 disable_hints(t);
1810 g_free(es);
1813 return (0);
1817 hint(struct tab *t, struct karg *args)
1820 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1822 if (t->hints_on == 0)
1823 enable_hints(t);
1824 else
1825 disable_hints(t);
1827 return (0);
1831 * Doesn't work fully, due to the following bug:
1832 * https://bugs.webkit.org/show_bug.cgi?id=51747
1835 restore_global_history(void)
1837 char file[PATH_MAX];
1838 FILE *f;
1839 struct history *h;
1840 gchar *uri;
1841 gchar *title;
1843 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
1845 if ((f = fopen(file, "r")) == NULL) {
1846 warnx("%s: fopen", __func__);
1847 return (1);
1850 for (;;) {
1851 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1852 if (feof(f) || ferror(f))
1853 break;
1855 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1856 if (feof(f) || ferror(f)) {
1857 free(uri);
1858 warnx("%s: broken history file\n", __func__);
1859 return (1);
1862 if (uri && strlen(uri) && title && strlen(title)) {
1863 webkit_web_history_item_new_with_data(uri, title);
1864 h = g_malloc(sizeof(struct history));
1865 h->uri = g_strdup(uri);
1866 h->title = g_strdup(title);
1867 RB_INSERT(history_list, &hl, h);
1868 } else {
1869 warnx("%s: failed to restore history\n", __func__);
1870 free(uri);
1871 free(title);
1872 return (1);
1875 free(uri);
1876 free(title);
1877 uri = NULL;
1878 title = NULL;
1881 return (0);
1885 save_global_history_to_disk(struct tab *t)
1887 char file[PATH_MAX];
1888 FILE *f;
1889 struct history *h;
1891 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
1893 if ((f = fopen(file, "w")) == NULL) {
1894 show_oops(t, "%s: global history file: %s",
1895 __func__, strerror(errno));
1896 return (1);
1899 RB_FOREACH_REVERSE(h, history_list, &hl) {
1900 if (h->uri && h->title)
1901 fprintf(f, "%s\n%s\n", h->uri, h->title);
1904 fclose(f);
1906 return (0);
1910 quit(struct tab *t, struct karg *args)
1912 if (save_global_history)
1913 save_global_history_to_disk(t);
1915 gtk_main_quit();
1917 return (1);
1921 open_tabs(struct tab *t, struct karg *a)
1923 char file[PATH_MAX];
1924 FILE *f = NULL;
1925 char *uri = NULL;
1926 int rv = 1;
1927 struct tab *ti, *tt;
1929 if (a == NULL)
1930 goto done;
1932 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
1933 if ((f = fopen(file, "r")) == NULL)
1934 goto done;
1936 ti = TAILQ_LAST(&tabs, tab_list);
1938 for (;;) {
1939 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1940 if (feof(f) || ferror(f))
1941 break;
1943 /* retrieve session name */
1944 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
1945 strlcpy(named_session,
1946 &uri[strlen(XT_SAVE_SESSION_ID)],
1947 sizeof named_session);
1948 continue;
1951 if (uri && strlen(uri))
1952 create_new_tab(uri, NULL, 1);
1954 free(uri);
1955 uri = NULL;
1958 /* close open tabs */
1959 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
1960 for (;;) {
1961 tt = TAILQ_FIRST(&tabs);
1962 if (tt != ti) {
1963 delete_tab(tt);
1964 continue;
1966 delete_tab(tt);
1967 break;
1971 rv = 0;
1972 done:
1973 if (f)
1974 fclose(f);
1976 return (rv);
1980 restore_saved_tabs(void)
1982 char file[PATH_MAX];
1983 int unlink_file = 0;
1984 struct stat sb;
1985 struct karg a;
1986 int rv = 0;
1988 snprintf(file, sizeof file, "%s/%s",
1989 sessions_dir, XT_RESTART_TABS_FILE);
1990 if (stat(file, &sb) == -1)
1991 a.s = XT_SAVED_TABS_FILE;
1992 else {
1993 unlink_file = 1;
1994 a.s = XT_RESTART_TABS_FILE;
1997 a.i = XT_SES_DONOTHING;
1998 rv = open_tabs(NULL, &a);
2000 if (unlink_file)
2001 unlink(file);
2003 return (rv);
2007 save_tabs(struct tab *t, struct karg *a)
2009 char file[PATH_MAX];
2010 FILE *f;
2011 struct tab *ti;
2012 const gchar *uri;
2013 int len = 0, i;
2014 const gchar **arr = NULL;
2016 if (a == NULL)
2017 return (1);
2018 if (a->s == NULL)
2019 snprintf(file, sizeof file, "%s/%s",
2020 sessions_dir, named_session);
2021 else
2022 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2024 if ((f = fopen(file, "w")) == NULL) {
2025 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2026 return (1);
2029 /* save session name */
2030 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2032 /* save tabs, in the order they are arranged in the notebook */
2033 TAILQ_FOREACH(ti, &tabs, entry)
2034 len++;
2036 arr = g_malloc0(len * sizeof(gchar *));
2038 TAILQ_FOREACH(ti, &tabs, entry) {
2039 if ((uri = get_uri(ti->wv)) != NULL)
2040 arr[gtk_notebook_page_num(notebook, ti->vbox)] = uri;
2043 for (i = 0; i < len; i++)
2044 if (arr[i])
2045 fprintf(f, "%s\n", arr[i]);
2047 g_free(arr);
2048 fclose(f);
2050 return (0);
2054 save_tabs_and_quit(struct tab *t, struct karg *args)
2056 struct karg a;
2058 a.s = NULL;
2059 save_tabs(t, &a);
2060 quit(t, NULL);
2062 return (1);
2066 yank_uri(struct tab *t, struct karg *args)
2068 const gchar *uri;
2069 GtkClipboard *clipboard;
2071 if ((uri = get_uri(t->wv)) == NULL)
2072 return (1);
2074 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2075 gtk_clipboard_set_text(clipboard, uri, -1);
2077 return (0);
2080 struct paste_args {
2081 struct tab *t;
2082 int i;
2085 void
2086 paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
2088 struct paste_args *pap;
2090 if (data == NULL || text == NULL || !strlen(text))
2091 return;
2093 pap = (struct paste_args *)data;
2095 switch(pap->i) {
2096 case XT_PASTE_CURRENT_TAB:
2097 load_uri(pap->t, (gchar *)text);
2098 break;
2099 case XT_PASTE_NEW_TAB:
2100 create_new_tab((gchar *)text, NULL, 1);
2101 break;
2104 g_free(pap);
2108 paste_uri(struct tab *t, struct karg *args)
2110 GtkClipboard *clipboard;
2111 struct paste_args *pap;
2113 pap = g_malloc(sizeof(struct paste_args));
2115 pap->t = t;
2116 pap->i = args->i;
2118 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2119 gtk_clipboard_request_text(clipboard, paste_uri_cb, pap);
2121 return (0);
2124 char *
2125 find_domain(const gchar *s, int add_dot)
2127 int i;
2128 char *r = NULL, *ss = NULL;
2130 if (s == NULL)
2131 return (NULL);
2133 if (!strncmp(s, "http://", strlen("http://")))
2134 s = &s[strlen("http://")];
2135 else if (!strncmp(s, "https://", strlen("https://")))
2136 s = &s[strlen("https://")];
2138 if (strlen(s) < 2)
2139 return (NULL);
2141 ss = g_strdup(s);
2142 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2143 /* chop string at first slash */
2144 if (ss[i] == '/' || ss[i] == '\0') {
2145 ss[i] = '\0';
2146 if (add_dot)
2147 r = g_strdup_printf(".%s", ss);
2148 else
2149 r = g_strdup(ss);
2150 break;
2152 g_free(ss);
2154 return (r);
2158 toggle_cwl(struct tab *t, struct karg *args)
2160 struct domain *d;
2161 const gchar *uri;
2162 char *dom = NULL, *dom_toggle = NULL;
2163 int es;
2165 if (args == NULL)
2166 return (1);
2168 uri = get_uri(t->wv);
2169 dom = find_domain(uri, 1);
2170 d = wl_find(dom, &c_wl);
2172 if (d == NULL)
2173 es = 0;
2174 else
2175 es = 1;
2177 if (args->i & XT_WL_TOGGLE)
2178 es = !es;
2179 else if ((args->i & XT_WL_ENABLE) && es != 1)
2180 es = 1;
2181 else if ((args->i & XT_WL_DISABLE) && es != 0)
2182 es = 0;
2184 if (args->i & XT_WL_TOPLEVEL)
2185 dom_toggle = get_toplevel_domain(dom);
2186 else
2187 dom_toggle = dom;
2189 if (es)
2190 /* enable cookies for domain */
2191 wl_add(dom_toggle, &c_wl, 0);
2192 else
2193 /* disable cookies for domain */
2194 RB_REMOVE(domain_list, &c_wl, d);
2196 webkit_web_view_reload(t->wv);
2198 g_free(dom);
2199 return (0);
2203 toggle_js(struct tab *t, struct karg *args)
2205 int es;
2206 const gchar *uri;
2207 struct domain *d;
2208 char *dom = NULL, *dom_toggle = NULL;
2210 if (args == NULL)
2211 return (1);
2213 g_object_get(G_OBJECT(t->settings),
2214 "enable-scripts", &es, (char *)NULL);
2215 if (args->i & XT_WL_TOGGLE)
2216 es = !es;
2217 else if ((args->i & XT_WL_ENABLE) && es != 1)
2218 es = 1;
2219 else if ((args->i & XT_WL_DISABLE) && es != 0)
2220 es = 0;
2221 else
2222 return (1);
2224 uri = get_uri(t->wv);
2225 dom = find_domain(uri, 1);
2227 if (uri == NULL || dom == NULL) {
2228 show_oops(t, "Can't toggle domain in JavaScript white list");
2229 goto done;
2232 if (args->i & XT_WL_TOPLEVEL)
2233 dom_toggle = get_toplevel_domain(dom);
2234 else
2235 dom_toggle = dom;
2237 if (es) {
2238 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2239 wl_add(dom_toggle, &js_wl, 0 /* session */);
2240 } else {
2241 d = wl_find(dom_toggle, &js_wl);
2242 if (d)
2243 RB_REMOVE(domain_list, &js_wl, d);
2244 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2246 g_object_set(G_OBJECT(t->settings),
2247 "enable-scripts", es, (char *)NULL);
2248 webkit_web_view_set_settings(t->wv, t->settings);
2249 webkit_web_view_reload(t->wv);
2250 done:
2251 if (dom)
2252 g_free(dom);
2253 return (0);
2256 void
2257 js_toggle_cb(GtkWidget *w, struct tab *t)
2259 struct karg a;
2261 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
2262 toggle_js(t, &a);
2266 toggle_src(struct tab *t, struct karg *args)
2268 gboolean mode;
2270 if (t == NULL)
2271 return (0);
2273 mode = webkit_web_view_get_view_source_mode(t->wv);
2274 webkit_web_view_set_view_source_mode(t->wv, !mode);
2275 webkit_web_view_reload(t->wv);
2277 return (0);
2280 void
2281 focus_webview(struct tab *t)
2283 if (t == NULL)
2284 return;
2286 /* only grab focus if we are visible */
2287 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2288 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2292 focus(struct tab *t, struct karg *args)
2294 if (t == NULL || args == NULL)
2295 return (1);
2297 if (show_url == 0)
2298 return (0);
2300 if (args->i == XT_FOCUS_URI)
2301 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2302 else if (args->i == XT_FOCUS_SEARCH)
2303 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2305 return (0);
2309 stats(struct tab *t, struct karg *args)
2311 char *stats, *s, line[64 * 1024];
2312 uint64_t line_count = 0;
2313 FILE *r_cookie_f;
2315 if (t == NULL)
2316 show_oops_s("stats invalid parameters");
2318 line[0] = '\0';
2319 if (save_rejected_cookies) {
2320 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2321 for (;;) {
2322 s = fgets(line, sizeof line, r_cookie_f);
2323 if (s == NULL || feof(r_cookie_f) ||
2324 ferror(r_cookie_f))
2325 break;
2326 line_count++;
2328 fclose(r_cookie_f);
2329 snprintf(line, sizeof line,
2330 "<br>Cookies blocked(*) total: %llu", line_count);
2331 } else
2332 show_oops(t, "Can't open blocked cookies file: %s",
2333 strerror(errno));
2336 stats = g_strdup_printf(XT_DOCTYPE
2337 "<html>"
2338 "<head>"
2339 "<title>Statistics</title>"
2340 "</head>"
2341 "<h1>Statistics</h1>"
2342 "<body>"
2343 "Cookies blocked(*) this session: %llu"
2344 "%s"
2345 "<p><small><b>*</b> results vary based on settings"
2346 "</body>"
2347 "</html>",
2348 blocked_cookies,
2349 line);
2351 load_webkit_string(t, stats, XT_URI_ABOUT_STATS);
2352 g_free(stats);
2354 return (0);
2358 blank(struct tab *t, struct karg *args)
2360 if (t == NULL)
2361 show_oops_s("about invalid parameters");
2363 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2365 return (0);
2368 about(struct tab *t, struct karg *args)
2370 char *about;
2372 if (t == NULL)
2373 show_oops_s("about invalid parameters");
2375 about = g_strdup_printf(XT_DOCTYPE
2376 "<html>"
2377 "<head>"
2378 "<title>About</title>"
2379 "</head>"
2380 "<h1>About</h1>"
2381 "<body>"
2382 "<b>Version: %s</b><p>"
2383 "Authors:"
2384 "<ul>"
2385 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2386 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2387 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2388 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2389 "</ul>"
2390 "Copyrights and licenses can be found on the XXXterm "
2391 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
2392 "</body>"
2393 "</html>",
2394 version
2397 load_webkit_string(t, about, XT_URI_ABOUT_ABOUT);
2398 g_free(about);
2400 return (0);
2404 help(struct tab *t, struct karg *args)
2406 char *help;
2408 if (t == NULL)
2409 show_oops_s("help invalid parameters");
2411 help = XT_DOCTYPE
2412 "<html>"
2413 "<head>"
2414 "<title>XXXterm</title>"
2415 "<meta http-equiv=\"REFRESH\" content=\"0;"
2416 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2417 "</head>"
2418 "<body>"
2419 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2420 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2421 "cgi-bin/man-cgi?xxxterm</a>"
2422 "</body>"
2423 "</html>"
2426 load_webkit_string(t, help, XT_URI_ABOUT_HELP);
2428 return (0);
2432 * update all favorite tabs apart from one. Pass NULL if
2433 * you want to update all.
2435 void
2436 update_favorite_tabs(struct tab *apart_from)
2438 struct tab *t;
2439 if (!updating_fl_tabs) {
2440 updating_fl_tabs = 1; /* stop infinite recursion */
2441 TAILQ_FOREACH(t, &tabs, entry)
2442 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2443 && (t != apart_from))
2444 xtp_page_fl(t, NULL);
2445 updating_fl_tabs = 0;
2449 /* show a list of favorites (bookmarks) */
2451 xtp_page_fl(struct tab *t, struct karg *args)
2453 char file[PATH_MAX];
2454 FILE *f;
2455 char *uri = NULL, *title = NULL;
2456 size_t len, lineno = 0;
2457 int i, failed = 0;
2458 char *header, *body, *tmp, *html = NULL;
2460 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2462 if (t == NULL)
2463 warn("%s: bad param", __func__);
2465 /* mark tab as favorite list */
2466 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2468 /* new session key */
2469 if (!updating_fl_tabs)
2470 generate_xtp_session_key(&fl_session_key);
2472 /* open favorites */
2473 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2474 if ((f = fopen(file, "r")) == NULL) {
2475 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2476 return (1);
2479 /* header */
2480 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
2481 "<title>Favorites</title>\n"
2482 "%s"
2483 "</head>"
2484 "<h1>Favorites</h1>\n",
2485 XT_PAGE_STYLE);
2487 /* body */
2488 body = g_strdup_printf("<div align='center'><table><tr>"
2489 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
2490 "<th style='width: 15%%'>Remove</th></tr>\n");
2492 for (i = 1;;) {
2493 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2494 if (feof(f) || ferror(f))
2495 break;
2496 if (len == 0) {
2497 free(title);
2498 title = NULL;
2499 continue;
2502 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2503 if (feof(f) || ferror(f)) {
2504 show_oops(t, "favorites file corrupt");
2505 failed = 1;
2506 break;
2509 tmp = body;
2510 body = g_strdup_printf("%s<tr>"
2511 "<td>%d</td>"
2512 "<td><a href='%s'>%s</a></td>"
2513 "<td style='text-align: center'>"
2514 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2515 "</tr>\n",
2516 body, i, uri, title,
2517 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2519 g_free(tmp);
2521 free(uri);
2522 uri = NULL;
2523 free(title);
2524 title = NULL;
2525 i++;
2527 fclose(f);
2529 /* if none, say so */
2530 if (i == 1) {
2531 tmp = body;
2532 body = g_strdup_printf("%s<tr>"
2533 "<td colspan='3' style='text-align: center'>"
2534 "No favorites - To add one use the 'favadd' command."
2535 "</td></tr>", body);
2536 g_free(tmp);
2539 if (uri)
2540 free(uri);
2541 if (title)
2542 free(title);
2544 /* render */
2545 if (!failed) {
2546 html = g_strdup_printf("%s%s</table></div></html>",
2547 header, body);
2548 load_webkit_string(t, html, XT_URI_ABOUT_FAVORITES);
2551 update_favorite_tabs(t);
2553 if (header)
2554 g_free(header);
2555 if (body)
2556 g_free(body);
2557 if (html)
2558 g_free(html);
2560 return (failed);
2563 char *
2564 getparams(char *cmd, char *cmp)
2566 char *rv = NULL;
2568 if (cmd && cmp) {
2569 if (!strncmp(cmd, cmp, strlen(cmp))) {
2570 rv = cmd + strlen(cmp);
2571 while (*rv == ' ')
2572 rv++;
2573 if (strlen(rv) == 0)
2574 rv = NULL;
2578 return (rv);
2581 void
2582 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2583 size_t cert_count, char *title)
2585 gnutls_datum_t cinfo;
2586 char *tmp, *header, *body, *footer;
2587 int i;
2589 header = g_strdup_printf("<title>%s</title><html><body>", title);
2590 footer = g_strdup("</body></html>");
2591 body = g_strdup("");
2593 for (i = 0; i < cert_count; i++) {
2594 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2595 &cinfo))
2596 return;
2598 tmp = body;
2599 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2600 body, i, cinfo.data);
2601 gnutls_free(cinfo.data);
2602 g_free(tmp);
2605 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2606 g_free(header);
2607 g_free(body);
2608 g_free(footer);
2609 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2610 g_free(tmp);
2614 ca_cmd(struct tab *t, struct karg *args)
2616 FILE *f = NULL;
2617 int rv = 1, certs = 0, certs_read;
2618 struct stat sb;
2619 gnutls_datum dt;
2620 gnutls_x509_crt_t *c = NULL;
2621 char *certs_buf = NULL, *s;
2623 /* yeah yeah stat race */
2624 if (stat(ssl_ca_file, &sb)) {
2625 show_oops(t, "no CA file: %s", ssl_ca_file);
2626 goto done;
2629 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2630 show_oops(t, "Can't open CA file: %s", strerror(errno));
2631 return (1);
2634 certs_buf = g_malloc(sb.st_size + 1);
2635 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2636 show_oops(t, "Can't read CA file: %s", strerror(errno));
2637 goto done;
2639 certs_buf[sb.st_size] = '\0';
2641 s = certs_buf;
2642 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2643 certs++;
2644 s += strlen("BEGIN CERTIFICATE");
2647 bzero(&dt, sizeof dt);
2648 dt.data = certs_buf;
2649 dt.size = sb.st_size;
2650 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2651 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
2652 GNUTLS_X509_FMT_PEM, 0);
2653 if (certs_read <= 0) {
2654 show_oops(t, "No cert(s) available");
2655 goto done;
2657 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2658 done:
2659 if (c)
2660 g_free(c);
2661 if (certs_buf)
2662 g_free(certs_buf);
2663 if (f)
2664 fclose(f);
2666 return (rv);
2670 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2672 SoupURI *su = NULL;
2673 struct addrinfo hints, *res = NULL, *ai;
2674 int s = -1, on;
2675 char port[8];
2677 if (uri && !g_str_has_prefix(uri, "https://"))
2678 goto done;
2680 su = soup_uri_new(uri);
2681 if (su == NULL)
2682 goto done;
2683 if (!SOUP_URI_VALID_FOR_HTTP(su))
2684 goto done;
2686 snprintf(port, sizeof port, "%d", su->port);
2687 bzero(&hints, sizeof(struct addrinfo));
2688 hints.ai_flags = AI_CANONNAME;
2689 hints.ai_family = AF_UNSPEC;
2690 hints.ai_socktype = SOCK_STREAM;
2692 if (getaddrinfo(su->host, port, &hints, &res))
2693 goto done;
2695 for (ai = res; ai; ai = ai->ai_next) {
2696 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2697 continue;
2699 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2700 if (s < 0)
2701 goto done;
2702 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2703 sizeof(on)) == -1)
2704 goto done;
2706 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2707 goto done;
2710 if (domain)
2711 strlcpy(domain, su->host, domain_sz);
2712 done:
2713 if (su)
2714 soup_uri_free(su);
2715 if (res)
2716 freeaddrinfo(res);
2718 return (s);
2722 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2724 if (gsession)
2725 gnutls_deinit(gsession);
2726 if (xcred)
2727 gnutls_certificate_free_credentials(xcred);
2729 return (0);
2733 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2734 gnutls_certificate_credentials_t *xc)
2736 gnutls_certificate_credentials_t xcred;
2737 gnutls_session_t gsession;
2738 int rv = 1;
2740 if (gs == NULL || xc == NULL)
2741 goto done;
2743 *gs = NULL;
2744 *xc = NULL;
2746 gnutls_certificate_allocate_credentials(&xcred);
2747 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2748 GNUTLS_X509_FMT_PEM);
2749 gnutls_init(&gsession, GNUTLS_CLIENT);
2750 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2751 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2752 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2753 if ((rv = gnutls_handshake(gsession)) < 0) {
2754 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2756 gnutls_error_is_fatal(rv),
2757 gnutls_strerror_name(rv));
2758 stop_tls(gsession, xcred);
2759 goto done;
2762 gnutls_credentials_type_t cred;
2763 cred = gnutls_auth_get_type(gsession);
2764 if (cred != GNUTLS_CRD_CERTIFICATE) {
2765 stop_tls(gsession, xcred);
2766 goto done;
2769 *gs = gsession;
2770 *xc = xcred;
2771 rv = 0;
2772 done:
2773 return (rv);
2777 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2778 size_t *cert_count)
2780 unsigned int len;
2781 const gnutls_datum_t *cl;
2782 gnutls_x509_crt_t *all_certs;
2783 int i, rv = 1;
2785 if (certs == NULL || cert_count == NULL)
2786 goto done;
2787 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2788 goto done;
2789 cl = gnutls_certificate_get_peers(gsession, &len);
2790 if (len == 0)
2791 goto done;
2793 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2794 for (i = 0; i < len; i++) {
2795 gnutls_x509_crt_init(&all_certs[i]);
2796 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2797 GNUTLS_X509_FMT_PEM < 0)) {
2798 g_free(all_certs);
2799 goto done;
2803 *certs = all_certs;
2804 *cert_count = len;
2805 rv = 0;
2806 done:
2807 return (rv);
2810 void
2811 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2813 int i;
2815 for (i = 0; i < cert_count; i++)
2816 gnutls_x509_crt_deinit(certs[i]);
2817 g_free(certs);
2820 void
2821 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2822 size_t cert_count, char *domain)
2824 size_t cert_buf_sz;
2825 char cert_buf[64 * 1024], file[PATH_MAX];
2826 int i;
2827 FILE *f;
2828 GdkColor color;
2830 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2831 return;
2833 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2834 if ((f = fopen(file, "w")) == NULL) {
2835 show_oops(t, "Can't create cert file %s %s",
2836 file, strerror(errno));
2837 return;
2840 for (i = 0; i < cert_count; i++) {
2841 cert_buf_sz = sizeof cert_buf;
2842 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2843 cert_buf, &cert_buf_sz)) {
2844 show_oops(t, "gnutls_x509_crt_export failed");
2845 goto done;
2847 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
2848 show_oops(t, "Can't write certs: %s", strerror(errno));
2849 goto done;
2853 /* not the best spot but oh well */
2854 gdk_color_parse("lightblue", &color);
2855 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
2856 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
2857 gdk_color_parse("black", &color);
2858 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
2859 done:
2860 fclose(f);
2864 load_compare_cert(struct tab *t, struct karg *args)
2866 const gchar *uri;
2867 char domain[8182], file[PATH_MAX];
2868 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
2869 int s = -1, rv = 1, i;
2870 size_t cert_count;
2871 FILE *f = NULL;
2872 size_t cert_buf_sz;
2873 gnutls_session_t gsession;
2874 gnutls_x509_crt_t *certs;
2875 gnutls_certificate_credentials_t xcred;
2877 if (t == NULL)
2878 return (1);
2880 if ((uri = get_uri(t->wv)) == NULL)
2881 return (1);
2883 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
2884 return (1);
2886 /* go ssl/tls */
2887 if (start_tls(t, s, &gsession, &xcred)) {
2888 show_oops(t, "Start TLS failed");
2889 goto done;
2892 /* get certs */
2893 if (get_connection_certs(gsession, &certs, &cert_count)) {
2894 show_oops(t, "Can't get connection certificates");
2895 goto done;
2898 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2899 if ((f = fopen(file, "r")) == NULL)
2900 goto freeit;
2902 for (i = 0; i < cert_count; i++) {
2903 cert_buf_sz = sizeof cert_buf;
2904 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2905 cert_buf, &cert_buf_sz)) {
2906 goto freeit;
2908 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
2909 rv = -1; /* critical */
2910 goto freeit;
2912 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
2913 rv = -1; /* critical */
2914 goto freeit;
2918 rv = 0;
2919 freeit:
2920 if (f)
2921 fclose(f);
2922 free_connection_certs(certs, cert_count);
2923 done:
2924 /* we close the socket first for speed */
2925 if (s != -1)
2926 close(s);
2927 stop_tls(gsession, xcred);
2929 return (rv);
2933 cert_cmd(struct tab *t, struct karg *args)
2935 const gchar *uri;
2936 char *action, domain[8182];
2937 int s = -1;
2938 size_t cert_count;
2939 gnutls_session_t gsession;
2940 gnutls_x509_crt_t *certs;
2941 gnutls_certificate_credentials_t xcred;
2943 if (t == NULL)
2944 return (1);
2946 if ((action = getparams(args->s, "cert")))
2948 else
2949 action = "show";
2951 if ((uri = get_uri(t->wv)) == NULL) {
2952 show_oops(t, "Invalid URI");
2953 return (1);
2956 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
2957 show_oops(t, "Invalid certidicate URI: %s", uri);
2958 return (1);
2961 /* go ssl/tls */
2962 if (start_tls(t, s, &gsession, &xcred)) {
2963 show_oops(t, "Start TLS failed");
2964 goto done;
2967 /* get certs */
2968 if (get_connection_certs(gsession, &certs, &cert_count)) {
2969 show_oops(t, "get_connection_certs failed");
2970 goto done;
2973 if (!strcmp(action, "show"))
2974 show_certs(t, certs, cert_count, "Certificate Chain");
2975 else if (!strcmp(action, "save"))
2976 save_certs(t, certs, cert_count, domain);
2977 else
2978 show_oops(t, "Invalid command: %s", action);
2980 free_connection_certs(certs, cert_count);
2981 done:
2982 /* we close the socket first for speed */
2983 if (s != -1)
2984 close(s);
2985 stop_tls(gsession, xcred);
2987 return (0);
2991 remove_cookie(int index)
2993 int i, rv = 1;
2994 GSList *cf;
2995 SoupCookie *c;
2997 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
2999 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3001 for (i = 1; cf; cf = cf->next, i++) {
3002 if (i != index)
3003 continue;
3004 c = cf->data;
3005 print_cookie("remove cookie", c);
3006 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3007 rv = 0;
3008 break;
3011 soup_cookies_free(cf);
3013 return (rv);
3017 wl_show(struct tab *t, char *args, char *title, struct domain_list *wl)
3019 struct domain *d;
3020 char *tmp, *header, *body, *footer;
3021 int p_js = 0, s_js = 0;
3023 /* we set this to indicate we want to manually do navaction */
3024 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
3026 if (g_str_has_prefix(args, "show a") ||
3027 !strcmp(args, "show")) {
3028 /* show all */
3029 p_js = 1;
3030 s_js = 1;
3031 } else if (g_str_has_prefix(args, "show p")) {
3032 /* show persistent */
3033 p_js = 1;
3034 } else if (g_str_has_prefix(args, "show s")) {
3035 /* show session */
3036 s_js = 1;
3037 } else
3038 return (1);
3040 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
3041 title, title);
3042 footer = g_strdup("</body></html>");
3043 body = g_strdup("");
3045 /* p list */
3046 if (p_js) {
3047 tmp = body;
3048 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3049 g_free(tmp);
3050 RB_FOREACH(d, domain_list, wl) {
3051 if (d->handy == 0)
3052 continue;
3053 tmp = body;
3054 body = g_strdup_printf("%s%s<br>", body, d->d);
3055 g_free(tmp);
3059 /* s list */
3060 if (s_js) {
3061 tmp = body;
3062 body = g_strdup_printf("%s<h2>Session</h2>", body);
3063 g_free(tmp);
3064 RB_FOREACH(d, domain_list, wl) {
3065 if (d->handy == 1)
3066 continue;
3067 tmp = body;
3068 body = g_strdup_printf("%s%s<br>", body, d->d);
3069 g_free(tmp);
3073 tmp = g_strdup_printf("%s%s%s", header, body, footer);
3074 g_free(header);
3075 g_free(body);
3076 g_free(footer);
3077 if (wl == &js_wl)
3078 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3079 else
3080 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3081 g_free(tmp);
3082 return (0);
3086 wl_save(struct tab *t, struct karg *args, int js)
3088 char file[PATH_MAX];
3089 FILE *f;
3090 char *line = NULL, *lt = NULL;
3091 size_t linelen;
3092 const gchar *uri;
3093 char *dom = NULL, *dom_save = NULL;
3094 struct karg a;
3095 struct domain *d;
3096 GSList *cf;
3097 SoupCookie *ci, *c;
3098 int flags;
3100 if (t == NULL || args == NULL)
3101 return (1);
3103 if (runtime_settings[0] == '\0')
3104 return (1);
3106 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3107 if ((f = fopen(file, "r+")) == NULL)
3108 return (1);
3110 uri = get_uri(t->wv);
3111 dom = find_domain(uri, 1);
3112 if (uri == NULL || dom == NULL) {
3113 show_oops(t, "Can't add domain to %s white list",
3114 js ? "JavaScript" : "cookie");
3115 goto done;
3118 if (g_str_has_prefix(args->s, "save d")) {
3119 /* save domain */
3120 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3121 show_oops(t, "invalid domain: %s", dom);
3122 goto done;
3124 flags = XT_WL_TOPLEVEL;
3125 } else if (g_str_has_prefix(args->s, "save f") ||
3126 !strcmp(args->s, "save")) {
3127 /* save fqdn */
3128 dom_save = dom;
3129 flags = XT_WL_FQDN;
3130 } else {
3131 show_oops(t, "invalid command: %s", args->s);
3132 goto done;
3135 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3137 while (!feof(f)) {
3138 line = fparseln(f, &linelen, NULL, NULL, 0);
3139 if (line == NULL)
3140 continue;
3141 if (!strcmp(line, lt))
3142 goto done;
3143 free(line);
3144 line = NULL;
3147 fprintf(f, "%s\n", lt);
3149 a.i = XT_WL_ENABLE;
3150 a.i |= flags;
3151 if (js) {
3152 d = wl_find(dom_save, &js_wl);
3153 if (!d) {
3154 settings_add("js_wl", dom_save);
3155 d = wl_find(dom_save, &js_wl);
3157 toggle_js(t, &a);
3158 } else {
3159 d = wl_find(dom_save, &c_wl);
3160 if (!d) {
3161 settings_add("cookie_wl", dom_save);
3162 d = wl_find(dom_save, &c_wl);
3164 toggle_cwl(t, &a);
3166 /* find and add to persistent jar */
3167 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3168 for (;cf; cf = cf->next) {
3169 ci = cf->data;
3170 if (!strcmp(dom_save, ci->domain) ||
3171 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3172 c = soup_cookie_copy(ci);
3173 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3176 soup_cookies_free(cf);
3178 if (d)
3179 d->handy = 1;
3181 done:
3182 if (line)
3183 free(line);
3184 if (dom)
3185 g_free(dom);
3186 if (lt)
3187 g_free(lt);
3188 fclose(f);
3190 return (0);
3194 js_show_wl(struct tab *t, struct karg *args)
3196 wl_show(t, "show all", "JavaScript White List", &js_wl);
3198 return (0);
3202 cookie_show_wl(struct tab *t, struct karg *args)
3204 wl_show(t, "show all", "Cookie White List", &c_wl);
3206 return (0);
3210 cookie_cmd(struct tab *t, struct karg *args)
3212 char *cmd;
3213 struct karg a;
3215 if ((cmd = getparams(args->s, "cookie")))
3217 else
3218 cmd = "show all";
3221 if (g_str_has_prefix(cmd, "show")) {
3222 wl_show(t, cmd, "Cookie White List", &c_wl);
3223 } else if (g_str_has_prefix(cmd, "save")) {
3224 a.s = cmd;
3225 wl_save(t, &a, 0);
3226 } else if (g_str_has_prefix(cmd, "toggle")) {
3227 a.i = XT_WL_TOGGLE;
3228 if (g_str_has_prefix(cmd, "toggle d"))
3229 a.i |= XT_WL_TOPLEVEL;
3230 else
3231 a.i |= XT_WL_FQDN;
3232 toggle_cwl(t, &a);
3233 } else if (g_str_has_prefix(cmd, "delete")) {
3234 show_oops(t, "'cookie delete' currently unimplemented");
3235 } else
3236 show_oops(t, "unknown cookie command: %s", cmd);
3238 return (0);
3242 js_cmd(struct tab *t, struct karg *args)
3244 char *cmd;
3245 struct karg a;
3247 if ((cmd = getparams(args->s, "js")))
3249 else
3250 cmd = "show all";
3252 if (g_str_has_prefix(cmd, "show")) {
3253 wl_show(t, cmd, "JavaScript White List", &js_wl);
3254 } else if (g_str_has_prefix(cmd, "save")) {
3255 a.s = cmd;
3256 wl_save(t, &a, 1);
3257 } else if (g_str_has_prefix(cmd, "toggle")) {
3258 a.i = XT_WL_TOGGLE;
3259 if (g_str_has_prefix(cmd, "toggle d"))
3260 a.i |= XT_WL_TOPLEVEL;
3261 else
3262 a.i |= XT_WL_FQDN;
3263 toggle_js(t, &a);
3264 } else if (g_str_has_prefix(cmd, "delete")) {
3265 show_oops(t, "'js delete' currently unimplemented");
3266 } else
3267 show_oops(t, "unknown js command: %s", cmd);
3269 return (0);
3273 add_favorite(struct tab *t, struct karg *args)
3275 char file[PATH_MAX];
3276 FILE *f;
3277 char *line = NULL;
3278 size_t urilen, linelen;
3279 const gchar *uri, *title;
3281 if (t == NULL)
3282 return (1);
3284 /* don't allow adding of xtp pages to favorites */
3285 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3286 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3287 return (1);
3290 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3291 if ((f = fopen(file, "r+")) == NULL) {
3292 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3293 return (1);
3296 title = webkit_web_view_get_title(t->wv);
3297 uri = get_uri(t->wv);
3299 if (title == NULL)
3300 title = uri;
3302 if (title == NULL || uri == NULL) {
3303 show_oops(t, "can't add page to favorites");
3304 goto done;
3307 urilen = strlen(uri);
3309 for (;;) {
3310 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3311 if (feof(f) || ferror(f))
3312 break;
3314 if (linelen == urilen && !strcmp(line, uri))
3315 goto done;
3317 free(line);
3318 line = NULL;
3321 fprintf(f, "\n%s\n%s", title, uri);
3322 done:
3323 if (line)
3324 free(line);
3325 fclose(f);
3327 update_favorite_tabs(NULL);
3329 return (0);
3333 navaction(struct tab *t, struct karg *args)
3335 WebKitWebHistoryItem *item;
3337 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3338 t->tab_id, args->i);
3340 if (t->item) {
3341 if (args->i == XT_NAV_BACK)
3342 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3343 else
3344 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3345 if (item == NULL)
3346 return (XT_CB_PASSTHROUGH);
3347 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3348 t->item = NULL;
3349 return (XT_CB_PASSTHROUGH);
3352 switch (args->i) {
3353 case XT_NAV_BACK:
3354 webkit_web_view_go_back(t->wv);
3355 break;
3356 case XT_NAV_FORWARD:
3357 webkit_web_view_go_forward(t->wv);
3358 break;
3359 case XT_NAV_RELOAD:
3360 webkit_web_view_reload(t->wv);
3361 break;
3362 case XT_NAV_RELOAD_CACHE:
3363 webkit_web_view_reload_bypass_cache(t->wv);
3364 break;
3366 return (XT_CB_PASSTHROUGH);
3370 move(struct tab *t, struct karg *args)
3372 GtkAdjustment *adjust;
3373 double pi, si, pos, ps, upper, lower, max;
3375 switch (args->i) {
3376 case XT_MOVE_DOWN:
3377 case XT_MOVE_UP:
3378 case XT_MOVE_BOTTOM:
3379 case XT_MOVE_TOP:
3380 case XT_MOVE_PAGEDOWN:
3381 case XT_MOVE_PAGEUP:
3382 case XT_MOVE_HALFDOWN:
3383 case XT_MOVE_HALFUP:
3384 adjust = t->adjust_v;
3385 break;
3386 default:
3387 adjust = t->adjust_h;
3388 break;
3391 pos = gtk_adjustment_get_value(adjust);
3392 ps = gtk_adjustment_get_page_size(adjust);
3393 upper = gtk_adjustment_get_upper(adjust);
3394 lower = gtk_adjustment_get_lower(adjust);
3395 si = gtk_adjustment_get_step_increment(adjust);
3396 pi = gtk_adjustment_get_page_increment(adjust);
3397 max = upper - ps;
3399 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3400 "max %f si %f pi %f\n",
3401 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3402 pos, ps, upper, lower, max, si, pi);
3404 switch (args->i) {
3405 case XT_MOVE_DOWN:
3406 case XT_MOVE_RIGHT:
3407 pos += si;
3408 gtk_adjustment_set_value(adjust, MIN(pos, max));
3409 break;
3410 case XT_MOVE_UP:
3411 case XT_MOVE_LEFT:
3412 pos -= si;
3413 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3414 break;
3415 case XT_MOVE_BOTTOM:
3416 case XT_MOVE_FARRIGHT:
3417 gtk_adjustment_set_value(adjust, max);
3418 break;
3419 case XT_MOVE_TOP:
3420 case XT_MOVE_FARLEFT:
3421 gtk_adjustment_set_value(adjust, lower);
3422 break;
3423 case XT_MOVE_PAGEDOWN:
3424 pos += pi;
3425 gtk_adjustment_set_value(adjust, MIN(pos, max));
3426 break;
3427 case XT_MOVE_PAGEUP:
3428 pos -= pi;
3429 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3430 break;
3431 case XT_MOVE_HALFDOWN:
3432 pos += pi / 2;
3433 gtk_adjustment_set_value(adjust, MIN(pos, max));
3434 break;
3435 case XT_MOVE_HALFUP:
3436 pos -= pi / 2;
3437 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3438 break;
3439 default:
3440 return (XT_CB_PASSTHROUGH);
3443 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3445 return (XT_CB_HANDLED);
3448 void
3449 url_set_visibility(void)
3451 struct tab *t;
3453 TAILQ_FOREACH(t, &tabs, entry) {
3454 if (show_url == 0) {
3455 gtk_widget_hide(t->toolbar);
3456 focus_webview(t);
3457 } else
3458 gtk_widget_show(t->toolbar);
3462 void
3463 notebook_tab_set_visibility(GtkNotebook *notebook)
3465 if (show_tabs == 0)
3466 gtk_notebook_set_show_tabs(notebook, FALSE);
3467 else
3468 gtk_notebook_set_show_tabs(notebook, TRUE);
3471 void
3472 statusbar_set_visibility(void)
3474 struct tab *t;
3476 TAILQ_FOREACH(t, &tabs, entry) {
3477 if (show_statusbar == 0) {
3478 gtk_widget_hide(t->statusbar);
3479 focus_webview(t);
3480 } else
3481 gtk_widget_show(t->statusbar);
3485 void
3486 url_set(struct tab *t, int enable_url_entry)
3488 GdkPixbuf *pixbuf;
3489 int progress;
3491 show_url = enable_url_entry;
3493 if (enable_url_entry) {
3494 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3495 GTK_ENTRY_ICON_PRIMARY, NULL);
3496 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3497 } else {
3498 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3499 GTK_ENTRY_ICON_PRIMARY);
3500 progress =
3501 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3502 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3503 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3504 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3505 progress);
3510 fullscreen(struct tab *t, struct karg *args)
3512 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3514 if (t == NULL)
3515 return (XT_CB_PASSTHROUGH);
3517 if (show_url == 0) {
3518 url_set(t, 1);
3519 show_tabs = 1;
3520 } else {
3521 url_set(t, 0);
3522 show_tabs = 0;
3525 url_set_visibility();
3526 notebook_tab_set_visibility(notebook);
3528 return (XT_CB_HANDLED);
3532 statusaction(struct tab *t, struct karg *args)
3534 int rv = XT_CB_HANDLED;
3536 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3538 if (t == NULL)
3539 return (XT_CB_PASSTHROUGH);
3541 switch (args->i) {
3542 case XT_STATUSBAR_SHOW:
3543 if (show_statusbar == 0) {
3544 show_statusbar = 1;
3545 statusbar_set_visibility();
3547 break;
3548 case XT_STATUSBAR_HIDE:
3549 if (show_statusbar == 1) {
3550 show_statusbar = 0;
3551 statusbar_set_visibility();
3553 break;
3555 return (rv);
3559 urlaction(struct tab *t, struct karg *args)
3561 int rv = XT_CB_HANDLED;
3563 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3565 if (t == NULL)
3566 return (XT_CB_PASSTHROUGH);
3568 switch (args->i) {
3569 case XT_URL_SHOW:
3570 if (show_url == 0) {
3571 url_set(t, 1);
3572 url_set_visibility();
3574 break;
3575 case XT_URL_HIDE:
3576 if (show_url == 1) {
3577 url_set(t, 0);
3578 url_set_visibility();
3580 break;
3582 return (rv);
3586 tabaction(struct tab *t, struct karg *args)
3588 int rv = XT_CB_HANDLED;
3589 char *url = NULL;
3590 struct undo *u;
3592 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3594 if (t == NULL)
3595 return (XT_CB_PASSTHROUGH);
3597 switch (args->i) {
3598 case XT_TAB_NEW:
3599 if ((url = getparams(args->s, "tabnew")))
3600 create_new_tab(url, NULL, 1);
3601 else
3602 create_new_tab(NULL, NULL, 1);
3603 break;
3604 case XT_TAB_DELETE:
3605 delete_tab(t);
3606 break;
3607 case XT_TAB_DELQUIT:
3608 if (gtk_notebook_get_n_pages(notebook) > 1)
3609 delete_tab(t);
3610 else
3611 quit(t, args);
3612 break;
3613 case XT_TAB_OPEN:
3614 if ((url = getparams(args->s, "open")) ||
3615 ((url = getparams(args->s, "op"))) ||
3616 ((url = getparams(args->s, "o"))))
3618 else {
3619 rv = XT_CB_PASSTHROUGH;
3620 goto done;
3622 load_uri(t, url);
3623 break;
3624 case XT_TAB_SHOW:
3625 if (show_tabs == 0) {
3626 show_tabs = 1;
3627 notebook_tab_set_visibility(notebook);
3629 break;
3630 case XT_TAB_HIDE:
3631 if (show_tabs == 1) {
3632 show_tabs = 0;
3633 notebook_tab_set_visibility(notebook);
3635 break;
3636 case XT_TAB_UNDO_CLOSE:
3637 if (undo_count == 0) {
3638 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3639 goto done;
3640 } else {
3641 undo_count--;
3642 u = TAILQ_FIRST(&undos);
3643 create_new_tab(u->uri, u, 1);
3645 TAILQ_REMOVE(&undos, u, entry);
3646 g_free(u->uri);
3647 /* u->history is freed in create_new_tab() */
3648 g_free(u);
3650 break;
3651 default:
3652 rv = XT_CB_PASSTHROUGH;
3653 goto done;
3656 done:
3657 if (args->s) {
3658 g_free(args->s);
3659 args->s = NULL;
3662 return (rv);
3666 resizetab(struct tab *t, struct karg *args)
3668 if (t == NULL || args == NULL) {
3669 show_oops_s("resizetab invalid parameters");
3670 return (XT_CB_PASSTHROUGH);
3673 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3674 t->tab_id, args->i);
3676 adjustfont_webkit(t, args->i);
3678 return (XT_CB_HANDLED);
3682 movetab(struct tab *t, struct karg *args)
3684 struct tab *tt;
3685 int x;
3687 if (t == NULL || args == NULL) {
3688 show_oops_s("movetab invalid parameters");
3689 return (XT_CB_PASSTHROUGH);
3692 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3693 t->tab_id, args->i);
3695 if (args->i == XT_TAB_INVALID)
3696 return (XT_CB_PASSTHROUGH);
3698 if (args->i < XT_TAB_INVALID) {
3699 /* next or previous tab */
3700 if (TAILQ_EMPTY(&tabs))
3701 return (XT_CB_PASSTHROUGH);
3703 switch (args->i) {
3704 case XT_TAB_NEXT:
3705 /* if at the last page, loop around to the first */
3706 if (gtk_notebook_get_current_page(notebook) ==
3707 gtk_notebook_get_n_pages(notebook) - 1)
3708 gtk_notebook_set_current_page(notebook, 0);
3709 else
3710 gtk_notebook_next_page(notebook);
3711 break;
3712 case XT_TAB_PREV:
3713 /* if at the first page, loop around to the last */
3714 if (gtk_notebook_current_page(notebook) == 0)
3715 gtk_notebook_set_current_page(notebook,
3716 gtk_notebook_get_n_pages(notebook) - 1);
3717 else
3718 gtk_notebook_prev_page(notebook);
3719 break;
3720 case XT_TAB_FIRST:
3721 gtk_notebook_set_current_page(notebook, 0);
3722 break;
3723 case XT_TAB_LAST:
3724 gtk_notebook_set_current_page(notebook, -1);
3725 break;
3726 default:
3727 return (XT_CB_PASSTHROUGH);
3730 return (XT_CB_HANDLED);
3733 /* jump to tab */
3734 x = args->i - 1;
3735 if (t->tab_id == x) {
3736 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3737 return (XT_CB_HANDLED);
3740 TAILQ_FOREACH(tt, &tabs, entry) {
3741 if (tt->tab_id == x) {
3742 gtk_notebook_set_current_page(notebook, x);
3743 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
3744 if (tt->focus_wv)
3745 focus_webview(tt);
3749 return (XT_CB_HANDLED);
3753 command(struct tab *t, struct karg *args)
3755 char *s = NULL, *ss = NULL;
3756 GdkColor color;
3757 const gchar *uri;
3759 if (t == NULL || args == NULL) {
3760 show_oops_s("command invalid parameters");
3761 return (XT_CB_PASSTHROUGH);
3764 switch (args->i) {
3765 case '/':
3766 s = "/";
3767 break;
3768 case '?':
3769 s = "?";
3770 break;
3771 case ':':
3772 s = ":";
3773 break;
3774 case XT_CMD_OPEN:
3775 s = ":open ";
3776 break;
3777 case XT_CMD_TABNEW:
3778 s = ":tabnew ";
3779 break;
3780 case XT_CMD_OPEN_CURRENT:
3781 s = ":open ";
3782 /* FALL THROUGH */
3783 case XT_CMD_TABNEW_CURRENT:
3784 if (!s) /* FALL THROUGH? */
3785 s = ":tabnew ";
3786 if ((uri = get_uri(t->wv)) != NULL) {
3787 ss = g_strdup_printf("%s%s", s, uri);
3788 s = ss;
3790 break;
3791 default:
3792 show_oops(t, "command: invalid opcode %d", args->i);
3793 return (XT_CB_PASSTHROUGH);
3796 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3798 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3799 gdk_color_parse("white", &color);
3800 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3801 show_cmd(t);
3802 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3803 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3805 if (ss)
3806 g_free(ss);
3808 return (XT_CB_HANDLED);
3812 * Return a new string with a download row (in html)
3813 * appended. Old string is freed.
3815 char *
3816 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3819 WebKitDownloadStatus stat;
3820 char *status_html = NULL, *cmd_html = NULL, *new_html;
3821 gdouble progress;
3822 char cur_sz[FMT_SCALED_STRSIZE];
3823 char tot_sz[FMT_SCALED_STRSIZE];
3824 char *xtp_prefix;
3826 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3828 /* All actions wil take this form:
3829 * xxxt://class/seskey
3831 xtp_prefix = g_strdup_printf("%s%d/%s/",
3832 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3834 stat = webkit_download_get_status(dl->download);
3836 switch (stat) {
3837 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3838 status_html = g_strdup_printf("Finished");
3839 cmd_html = g_strdup_printf(
3840 "<a href='%s%d/%d'>Remove</a>",
3841 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3842 break;
3843 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3844 /* gather size info */
3845 progress = 100 * webkit_download_get_progress(dl->download);
3847 fmt_scaled(
3848 webkit_download_get_current_size(dl->download), cur_sz);
3849 fmt_scaled(
3850 webkit_download_get_total_size(dl->download), tot_sz);
3852 status_html = g_strdup_printf(
3853 "<div style='width: 100%%' align='center'>"
3854 "<div class='progress-outer'>"
3855 "<div class='progress-inner' style='width: %.2f%%'>"
3856 "</div></div></div>"
3857 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3858 progress, cur_sz, tot_sz, progress);
3860 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3861 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3863 break;
3864 /* LLL */
3865 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3866 status_html = g_strdup_printf("Cancelled");
3867 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3868 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3869 break;
3870 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3871 status_html = g_strdup_printf("Error!");
3872 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3873 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3874 break;
3875 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3876 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3877 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3878 status_html = g_strdup_printf("Starting");
3879 break;
3880 default:
3881 show_oops(t, "%s: unknown download status", __func__);
3884 new_html = g_strdup_printf(
3885 "%s\n<tr><td>%s</td><td>%s</td>"
3886 "<td style='text-align:center'>%s</td></tr>\n",
3887 html, basename(webkit_download_get_uri(dl->download)),
3888 status_html, cmd_html);
3889 g_free(html);
3891 if (status_html)
3892 g_free(status_html);
3894 if (cmd_html)
3895 g_free(cmd_html);
3897 g_free(xtp_prefix);
3899 return new_html;
3903 * update all download tabs apart from one. Pass NULL if
3904 * you want to update all.
3906 void
3907 update_download_tabs(struct tab *apart_from)
3909 struct tab *t;
3910 if (!updating_dl_tabs) {
3911 updating_dl_tabs = 1; /* stop infinite recursion */
3912 TAILQ_FOREACH(t, &tabs, entry)
3913 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
3914 && (t != apart_from))
3915 xtp_page_dl(t, NULL);
3916 updating_dl_tabs = 0;
3921 * update all cookie tabs apart from one. Pass NULL if
3922 * you want to update all.
3924 void
3925 update_cookie_tabs(struct tab *apart_from)
3927 struct tab *t;
3928 if (!updating_cl_tabs) {
3929 updating_cl_tabs = 1; /* stop infinite recursion */
3930 TAILQ_FOREACH(t, &tabs, entry)
3931 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
3932 && (t != apart_from))
3933 xtp_page_cl(t, NULL);
3934 updating_cl_tabs = 0;
3939 * update all history tabs apart from one. Pass NULL if
3940 * you want to update all.
3942 void
3943 update_history_tabs(struct tab *apart_from)
3945 struct tab *t;
3947 if (!updating_hl_tabs) {
3948 updating_hl_tabs = 1; /* stop infinite recursion */
3949 TAILQ_FOREACH(t, &tabs, entry)
3950 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
3951 && (t != apart_from))
3952 xtp_page_hl(t, NULL);
3953 updating_hl_tabs = 0;
3957 /* cookie management XTP page */
3959 xtp_page_cl(struct tab *t, struct karg *args)
3961 char *header, *body, *footer, *page, *tmp;
3962 int i = 1; /* all ids start 1 */
3963 GSList *sc, *pc, *pc_start;
3964 SoupCookie *c;
3965 char *type, *table_headers;
3966 char *last_domain = strdup("");
3968 DNPRINTF(XT_D_CMD, "%s", __func__);
3970 if (t == NULL) {
3971 show_oops_s("%s invalid parameters", __func__);
3972 return (1);
3974 /* mark this tab as cookie jar */
3975 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
3977 /* Generate a new session key */
3978 if (!updating_cl_tabs)
3979 generate_xtp_session_key(&cl_session_key);
3981 /* header */
3982 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3983 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
3984 "</head><body><h1>Cookie Jar</h1>\n");
3986 /* table headers */
3987 table_headers = g_strdup_printf("<div align='center'><table><tr>"
3988 "<th>Type</th>"
3989 "<th>Name</th>"
3990 "<th>Value</th>"
3991 "<th>Path</th>"
3992 "<th>Expires</th>"
3993 "<th>Secure</th>"
3994 "<th>HTTP<br />only</th>"
3995 "<th>Rm</th></tr>\n");
3997 sc = soup_cookie_jar_all_cookies(s_cookiejar);
3998 pc = soup_cookie_jar_all_cookies(p_cookiejar);
3999 pc_start = pc;
4001 body = NULL;
4002 for (; sc; sc = sc->next) {
4003 c = sc->data;
4005 if (strcmp(last_domain, c->domain) != 0) {
4006 /* new domain */
4007 free(last_domain);
4008 last_domain = strdup(c->domain);
4010 if (body != NULL) {
4011 tmp = body;
4012 body = g_strdup_printf("%s</table></div>"
4013 "<h2>%s</h2>%s\n",
4014 body, c->domain, table_headers);
4015 g_free(tmp);
4016 } else {
4017 /* first domain */
4018 body = g_strdup_printf("<h2>%s</h2>%s\n",
4019 c->domain, table_headers);
4023 type = "Session";
4024 for (pc = pc_start; pc; pc = pc->next)
4025 if (soup_cookie_equal(pc->data, c)) {
4026 type = "Session + Persistent";
4027 break;
4030 tmp = body;
4031 body = g_strdup_printf(
4032 "%s\n<tr>"
4033 "<td style='width: text-align: center'>%s</td>"
4034 "<td style='width: 1px'>%s</td>"
4035 "<td style='width=70%%;overflow: visible'>"
4036 " <textarea rows='4'>%s</textarea>"
4037 "</td>"
4038 "<td>%s</td>"
4039 "<td>%s</td>"
4040 "<td style='width: 1px; text-align: center'>%d</td>"
4041 "<td style='width: 1px; text-align: center'>%d</td>"
4042 "<td style='width: 1px; text-align: center'>"
4043 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4044 body,
4045 type,
4046 c->name,
4047 c->value,
4048 c->path,
4049 c->expires ?
4050 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4051 c->secure,
4052 c->http_only,
4054 XT_XTP_STR,
4055 XT_XTP_CL,
4056 cl_session_key,
4057 XT_XTP_CL_REMOVE,
4061 g_free(tmp);
4062 i++;
4065 soup_cookies_free(sc);
4066 soup_cookies_free(pc);
4068 /* small message if there are none */
4069 if (i == 1) {
4070 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4071 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4074 /* footer */
4075 footer = g_strdup_printf("</table></div></body></html>");
4077 page = g_strdup_printf("%s%s%s", header, body, footer);
4079 g_free(header);
4080 g_free(body);
4081 g_free(footer);
4082 g_free(table_headers);
4083 g_free(last_domain);
4085 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4086 update_cookie_tabs(t);
4088 g_free(page);
4090 return (0);
4094 xtp_page_hl(struct tab *t, struct karg *args)
4096 char *header, *body, *footer, *page, *tmp;
4097 struct history *h;
4098 int i = 1; /* all ids start 1 */
4100 DNPRINTF(XT_D_CMD, "%s", __func__);
4102 if (t == NULL) {
4103 show_oops_s("%s invalid parameters", __func__);
4104 return (1);
4107 /* mark this tab as history manager */
4108 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
4110 /* Generate a new session key */
4111 if (!updating_hl_tabs)
4112 generate_xtp_session_key(&hl_session_key);
4114 /* header */
4115 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
4116 "<title>History</title>\n"
4117 "%s"
4118 "</head>"
4119 "<h1>History</h1>\n",
4120 XT_PAGE_STYLE);
4122 /* body */
4123 body = g_strdup_printf("<div align='center'><table><tr>"
4124 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
4126 RB_FOREACH_REVERSE(h, history_list, &hl) {
4127 tmp = body;
4128 body = g_strdup_printf(
4129 "%s\n<tr>"
4130 "<td><a href='%s'>%s</a></td>"
4131 "<td>%s</td>"
4132 "<td style='text-align: center'>"
4133 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4134 body, h->uri, h->uri, h->title,
4135 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4136 XT_XTP_HL_REMOVE, i);
4138 g_free(tmp);
4139 i++;
4142 /* small message if there are none */
4143 if (i == 1) {
4144 tmp = body;
4145 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4146 "colspan='3'>No History</td></tr>\n", body);
4147 g_free(tmp);
4150 /* footer */
4151 footer = g_strdup_printf("</table></div></body></html>");
4153 page = g_strdup_printf("%s%s%s", header, body, footer);
4156 * update all history manager tabs as the xtp session
4157 * key has now changed. No need to update the current tab.
4158 * Already did that above.
4160 update_history_tabs(t);
4162 g_free(header);
4163 g_free(body);
4164 g_free(footer);
4166 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4167 g_free(page);
4169 return (0);
4173 * Generate a web page detailing the status of any downloads
4176 xtp_page_dl(struct tab *t, struct karg *args)
4178 struct download *dl;
4179 char *header, *body, *footer, *page, *tmp;
4180 char *ref;
4181 int n_dl = 1;
4183 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4185 if (t == NULL) {
4186 show_oops_s("%s invalid parameters", __func__);
4187 return (1);
4189 /* mark as a download manager tab */
4190 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
4193 * Generate a new session key for next page instance.
4194 * This only happens for the top level call to xtp_page_dl()
4195 * in which case updating_dl_tabs is 0.
4197 if (!updating_dl_tabs)
4198 generate_xtp_session_key(&dl_session_key);
4200 /* header - with refresh so as to update */
4201 if (refresh_interval >= 1)
4202 ref = g_strdup_printf(
4203 "<meta http-equiv='refresh' content='%u"
4204 ";url=%s%d/%s/%d' />\n",
4205 refresh_interval,
4206 XT_XTP_STR,
4207 XT_XTP_DL,
4208 dl_session_key,
4209 XT_XTP_DL_LIST);
4210 else
4211 ref = g_strdup("");
4214 header = g_strdup_printf(
4215 "%s\n<head>"
4216 "<title>Downloads</title>\n%s%s</head>\n",
4217 XT_DOCTYPE XT_HTML_TAG,
4218 ref,
4219 XT_PAGE_STYLE);
4221 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
4222 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4223 "</p><table><tr><th style='width: 60%%'>"
4224 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4225 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4227 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4228 body = xtp_page_dl_row(t, body, dl);
4229 n_dl++;
4232 /* message if no downloads in list */
4233 if (n_dl == 1) {
4234 tmp = body;
4235 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4236 " style='text-align: center'>"
4237 "No downloads</td></tr>\n", body);
4238 g_free(tmp);
4241 /* footer */
4242 footer = g_strdup_printf("</table></div></body></html>");
4244 page = g_strdup_printf("%s%s%s", header, body, footer);
4248 * update all download manager tabs as the xtp session
4249 * key has now changed. No need to update the current tab.
4250 * Already did that above.
4252 update_download_tabs(t);
4254 g_free(ref);
4255 g_free(header);
4256 g_free(body);
4257 g_free(footer);
4259 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4260 g_free(page);
4262 return (0);
4266 search(struct tab *t, struct karg *args)
4268 gboolean d;
4270 if (t == NULL || args == NULL) {
4271 show_oops_s("search invalid parameters");
4272 return (1);
4274 if (t->search_text == NULL) {
4275 if (global_search == NULL)
4276 return (XT_CB_PASSTHROUGH);
4277 else {
4278 t->search_text = g_strdup(global_search);
4279 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4280 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4284 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4285 t->tab_id, args->i, t->search_forward, t->search_text);
4287 switch (args->i) {
4288 case XT_SEARCH_NEXT:
4289 d = t->search_forward;
4290 break;
4291 case XT_SEARCH_PREV:
4292 d = !t->search_forward;
4293 break;
4294 default:
4295 return (XT_CB_PASSTHROUGH);
4298 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4300 return (XT_CB_HANDLED);
4303 struct settings_args {
4304 char **body;
4305 int i;
4308 void
4309 print_setting(struct settings *s, char *val, void *cb_args)
4311 char *tmp, *color;
4312 struct settings_args *sa = cb_args;
4314 if (sa == NULL)
4315 return;
4317 if (s->flags & XT_SF_RUNTIME)
4318 color = "#22cc22";
4319 else
4320 color = "#cccccc";
4322 tmp = *sa->body;
4323 *sa->body = g_strdup_printf(
4324 "%s\n<tr>"
4325 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
4326 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
4327 *sa->body,
4328 color,
4329 s->name,
4330 color,
4333 g_free(tmp);
4334 sa->i++;
4338 set(struct tab *t, struct karg *args)
4340 char *header, *body, *footer, *page, *tmp, *pars;
4341 int i = 1;
4342 struct settings_args sa;
4344 if ((pars = getparams(args->s, "set")) == NULL) {
4345 bzero(&sa, sizeof sa);
4346 sa.body = &body;
4348 /* header */
4349 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4350 "\n<head><title>Settings</title>\n"
4351 "</head><body><h1>Settings</h1>\n");
4353 /* body */
4354 body = g_strdup_printf("<div align='center'><table><tr>"
4355 "<th align='left'>Setting</th>"
4356 "<th align='left'>Value</th></tr>\n");
4358 settings_walk(print_setting, &sa);
4359 i = sa.i;
4361 /* small message if there are none */
4362 if (i == 1) {
4363 tmp = body;
4364 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4365 "colspan='2'>No settings</td></tr>\n", body);
4366 g_free(tmp);
4369 /* footer */
4370 footer = g_strdup_printf("</table></div></body></html>");
4372 page = g_strdup_printf("%s%s%s", header, body, footer);
4374 g_free(header);
4375 g_free(body);
4376 g_free(footer);
4378 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4379 } else
4380 show_oops(t, "Invalid command: %s", pars);
4382 return (XT_CB_PASSTHROUGH);
4386 session_save(struct tab *t, char *filename, char **ret)
4388 struct karg a;
4389 char *f = filename;
4390 int rv = 1;
4392 f += strlen("save");
4393 while (*f == ' ' && *f != '\0')
4394 f++;
4395 if (strlen(f) == 0)
4396 goto done;
4398 *ret = f;
4399 if (f[0] == '.' || f[0] == '/')
4400 goto done;
4402 a.s = f;
4403 if (save_tabs(t, &a))
4404 goto done;
4405 strlcpy(named_session, f, sizeof named_session);
4407 rv = 0;
4408 done:
4409 return (rv);
4413 session_open(struct tab *t, char *filename, char **ret)
4415 struct karg a;
4416 char *f = filename;
4417 int rv = 1;
4419 f += strlen("open");
4420 while (*f == ' ' && *f != '\0')
4421 f++;
4422 if (strlen(f) == 0)
4423 goto done;
4425 *ret = f;
4426 if (f[0] == '.' || f[0] == '/')
4427 goto done;
4429 a.s = f;
4430 a.i = XT_SES_CLOSETABS;
4431 if (open_tabs(t, &a))
4432 goto done;
4434 strlcpy(named_session, f, sizeof named_session);
4436 rv = 0;
4437 done:
4438 return (rv);
4442 session_delete(struct tab *t, char *filename, char **ret)
4444 char file[PATH_MAX];
4445 char *f = filename;
4446 int rv = 1;
4448 f += strlen("delete");
4449 while (*f == ' ' && *f != '\0')
4450 f++;
4451 if (strlen(f) == 0)
4452 goto done;
4454 *ret = f;
4455 if (f[0] == '.' || f[0] == '/')
4456 goto done;
4458 snprintf(file, sizeof file, "%s/%s", sessions_dir, f);
4459 if (unlink(file))
4460 goto done;
4462 if (!strcmp(f, named_session))
4463 strlcpy(named_session, XT_SAVED_TABS_FILE,
4464 sizeof named_session);
4466 rv = 0;
4467 done:
4468 return (rv);
4472 session_cmd(struct tab *t, struct karg *args)
4474 char *action = NULL;
4475 char *filename = NULL;
4477 if (t == NULL)
4478 return (1);
4480 if ((action = getparams(args->s, "session")))
4482 else
4483 action = "show";
4485 if (!strcmp(action, "show"))
4486 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4487 XT_SAVED_TABS_FILE : named_session);
4488 else if (g_str_has_prefix(action, "save ")) {
4489 if (session_save(t, action, &filename)) {
4490 show_oops(t, "Can't save session: %s",
4491 filename ? filename : "INVALID");
4492 goto done;
4494 } else if (g_str_has_prefix(action, "open ")) {
4495 if (session_open(t, action, &filename)) {
4496 show_oops(t, "Can't open session: %s",
4497 filename ? filename : "INVALID");
4498 goto done;
4500 } else if (g_str_has_prefix(action, "delete ")) {
4501 if (session_delete(t, action, &filename)) {
4502 show_oops(t, "Can't delete session: %s",
4503 filename ? filename : "INVALID");
4504 goto done;
4506 } else
4507 show_oops(t, "Invalid command: %s", action);
4508 done:
4509 return (XT_CB_PASSTHROUGH);
4513 * Make a hardcopy of the page
4516 print_page(struct tab *t, struct karg *args)
4518 WebKitWebFrame *frame;
4519 GtkPageSetup *ps;
4520 GtkPrintOperation *op;
4521 GtkPrintOperationAction action;
4522 GtkPrintOperationResult print_res;
4523 GError *g_err = NULL;
4524 int marg_l, marg_r, marg_t, marg_b;
4526 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4528 ps = gtk_page_setup_new();
4529 op = gtk_print_operation_new();
4530 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4531 frame = webkit_web_view_get_main_frame(t->wv);
4533 /* the default margins are too small, so we will bump them */
4534 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4535 XT_PRINT_EXTRA_MARGIN;
4536 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4537 XT_PRINT_EXTRA_MARGIN;
4538 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4539 XT_PRINT_EXTRA_MARGIN;
4540 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4541 XT_PRINT_EXTRA_MARGIN;
4543 /* set margins */
4544 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4545 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4546 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4547 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4549 gtk_print_operation_set_default_page_setup(op, ps);
4551 /* this appears to free 'op' and 'ps' */
4552 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4554 /* check it worked */
4555 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4556 show_oops_s("can't print: %s", g_err->message);
4557 g_error_free (g_err);
4558 return (1);
4561 return (0);
4565 go_home(struct tab *t, struct karg *args)
4567 load_uri(t, home);
4568 return (0);
4572 restart(struct tab *t, struct karg *args)
4574 struct karg a;
4576 a.s = XT_RESTART_TABS_FILE;
4577 save_tabs(t, &a);
4578 execvp(start_argv[0], start_argv);
4579 /* NOTREACHED */
4581 return (0);
4584 #define CTRL GDK_CONTROL_MASK
4585 #define MOD1 GDK_MOD1_MASK
4586 #define SHFT GDK_SHIFT_MASK
4588 /* inherent to GTK not all keys will be caught at all times */
4589 /* XXX sort key bindings */
4590 struct key_binding {
4591 char *name;
4592 guint mask;
4593 guint use_in_entry;
4594 guint key;
4595 int (*func)(struct tab *, struct karg *);
4596 struct karg arg;
4597 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4598 } keys[] = {
4599 { "cookiejar", MOD1, 0, GDK_j, xtp_page_cl, {0} },
4600 { "downloadmgr", MOD1, 0, GDK_d, xtp_page_dl, {0} },
4601 { "history", MOD1, 0, GDK_h, xtp_page_hl, {0} },
4602 { "print", CTRL, 0, GDK_p, print_page, {0}},
4603 { NULL, 0, 0, GDK_slash, command, {.i = '/'} },
4604 { NULL, 0, 0, GDK_question, command, {.i = '?'} },
4605 { NULL, 0, 0, GDK_colon, command, {.i = ':'} },
4606 { "quit", CTRL, 0, GDK_q, quit, {0} },
4607 { "restart", MOD1, 0, GDK_q, restart, {0} },
4608 { "togglejs", CTRL, 0, GDK_j, toggle_js, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
4609 { "togglecookie", MOD1, 0, GDK_c, toggle_cwl, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
4610 { "togglesrc", CTRL, 0, GDK_s, toggle_src, {0} },
4611 { "yankuri", 0, 0, GDK_y, yank_uri, {0} },
4612 { "pasteuricur", 0, 0, GDK_p, paste_uri, {.i = XT_PASTE_CURRENT_TAB} },
4613 { "pasteurinew", 0, 0, GDK_P, paste_uri, {.i = XT_PASTE_NEW_TAB} },
4615 /* search */
4616 { "searchnext", 0, 0, GDK_n, search, {.i = XT_SEARCH_NEXT} },
4617 { "searchprev", 0, 0, GDK_N, search, {.i = XT_SEARCH_PREV} },
4619 /* focus */
4620 { "focusaddress", 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} },
4621 { "focussearch", 0, 0, GDK_F7, focus, {.i = XT_FOCUS_SEARCH} },
4623 /* command aliases (handy when -S flag is used) */
4624 { NULL, 0, 0, GDK_F9, command, {.i = XT_CMD_OPEN} },
4625 { NULL, 0, 0, GDK_F10, command, {.i = XT_CMD_OPEN_CURRENT} },
4626 { NULL, 0, 0, GDK_F11, command, {.i = XT_CMD_TABNEW} },
4627 { NULL, 0, 0, GDK_F12, command, {.i = XT_CMD_TABNEW_CURRENT} },
4629 /* hinting */
4630 { "hinting", 0, 0, GDK_f, hint, {.i = 0} },
4632 /* navigation */
4633 { "goback", 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
4634 { "goback", MOD1, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
4635 { "goforward", SHFT, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
4636 { "goforward", MOD1, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
4637 { "reload", 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} },
4638 { "reload", CTRL, 0, GDK_r, navaction, {.i = XT_NAV_RELOAD} },
4639 { "reloadforce", CTRL, 0, GDK_R, navaction, {.i = XT_NAV_RELOAD_CACHE} },
4640 { "reload" , CTRL, 0, GDK_l, navaction, {.i = XT_NAV_RELOAD} },
4641 { "favorites", MOD1, 1, GDK_f, xtp_page_fl, {0} },
4643 /* vertical movement */
4644 { "scrolldown", 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
4645 { "scrolldown", 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
4646 { "scrollup", 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
4647 { "scrollup", 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
4648 { "scrollbottom", 0, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
4649 { "scrollbottom", 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
4650 { "scrolltop", 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
4651 { "scrolltop", 0, 0, GDK_g, move, {.i = XT_MOVE_TOP} },
4652 { "scrollpagedown", 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
4653 { "scrollpagedown", CTRL, 0, GDK_f, move, {.i = XT_MOVE_PAGEDOWN} },
4654 { "scrollhalfdown", CTRL, 0, GDK_d, move, {.i = XT_MOVE_HALFDOWN} },
4655 { "scrollpagedown", 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
4656 { "scrollpageup", 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
4657 { "scrollpageup", CTRL, 0, GDK_b, move, {.i = XT_MOVE_PAGEUP} },
4658 { "scrollhalfup", CTRL, 0, GDK_u, move, {.i = XT_MOVE_HALFUP} },
4659 /* horizontal movement */
4660 { "scrollright", 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
4661 { "scrollright", 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
4662 { "scrollleft", 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
4663 { "scrollleft", 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
4664 { "scrollfarright", 0, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
4665 { "scrollfarleft", 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
4667 /* tabs */
4668 { "tabnew", CTRL, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
4669 { "tabclose", CTRL, 1, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
4670 { "tabundoclose", 0, 0, GDK_U, tabaction, {.i = XT_TAB_UNDO_CLOSE} },
4671 { "tabgoto1", CTRL, 0, GDK_1, movetab, {.i = 1} },
4672 { "tabgoto2", CTRL, 0, GDK_2, movetab, {.i = 2} },
4673 { "tabgoto3", CTRL, 0, GDK_3, movetab, {.i = 3} },
4674 { "tabgoto4", CTRL, 0, GDK_4, movetab, {.i = 4} },
4675 { "tabgoto5", CTRL, 0, GDK_5, movetab, {.i = 5} },
4676 { "tabgoto6", CTRL, 0, GDK_6, movetab, {.i = 6} },
4677 { "tabgoto7", CTRL, 0, GDK_7, movetab, {.i = 7} },
4678 { "tabgoto8", CTRL, 0, GDK_8, movetab, {.i = 8} },
4679 { "tabgoto9", CTRL, 0, GDK_9, movetab, {.i = 9} },
4680 { "tabgoto10", CTRL, 0, GDK_0, movetab, {.i = 10} },
4681 { "tabgotofirst", CTRL, 0, GDK_less, movetab, {.i = XT_TAB_FIRST} },
4682 { "tabgotolast", CTRL, 0, GDK_greater, movetab, {.i = XT_TAB_LAST} },
4683 { "tabgotoprev", CTRL, 0, GDK_Left, movetab, {.i = XT_TAB_PREV} },
4684 { "tabgotonext", CTRL, 0, GDK_Right, movetab, {.i = XT_TAB_NEXT} },
4685 { "focusout", CTRL, 0, GDK_minus, resizetab, {.i = -1} },
4686 { "focusin", CTRL, 0, GDK_plus, resizetab, {.i = 1} },
4687 { "focusin", CTRL, 0, GDK_equal, resizetab, {.i = 1} },
4689 TAILQ_HEAD(keybinding_list, key_binding);
4691 void
4692 walk_kb(struct settings *s,
4693 void (*cb)(struct settings *, char *, void *), void *cb_args)
4695 struct key_binding *k;
4696 char str[1024];
4698 if (s == NULL || cb == NULL) {
4699 show_oops_s("walk_kb invalid parameters");
4700 return;
4703 TAILQ_FOREACH(k, &kbl, entry) {
4704 if (k->name == NULL)
4705 continue;
4706 str[0] = '\0';
4708 /* sanity */
4709 if (gdk_keyval_name(k->key) == NULL)
4710 continue;
4712 strlcat(str, k->name, sizeof str);
4713 strlcat(str, ",", sizeof str);
4715 if (k->mask & GDK_SHIFT_MASK)
4716 strlcat(str, "S-", sizeof str);
4717 if (k->mask & GDK_CONTROL_MASK)
4718 strlcat(str, "C-", sizeof str);
4719 if (k->mask & GDK_MOD1_MASK)
4720 strlcat(str, "M1-", sizeof str);
4721 if (k->mask & GDK_MOD2_MASK)
4722 strlcat(str, "M2-", sizeof str);
4723 if (k->mask & GDK_MOD3_MASK)
4724 strlcat(str, "M3-", sizeof str);
4725 if (k->mask & GDK_MOD4_MASK)
4726 strlcat(str, "M4-", sizeof str);
4727 if (k->mask & GDK_MOD5_MASK)
4728 strlcat(str, "M5-", sizeof str);
4730 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4731 cb(s, str, cb_args);
4734 void
4735 init_keybindings(void)
4737 int i;
4738 struct key_binding *k;
4740 for (i = 0; i < LENGTH(keys); i++) {
4741 k = g_malloc0(sizeof *k);
4742 k->name = keys[i].name;
4743 k->mask = keys[i].mask;
4744 k->use_in_entry = keys[i].use_in_entry;
4745 k->key = keys[i].key;
4746 k->func = keys[i].func;
4747 bcopy(&keys[i].arg, &k->arg, sizeof k->arg);
4748 TAILQ_INSERT_HEAD(&kbl, k, entry);
4750 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4751 k->name ? k->name : "unnamed key");
4755 void
4756 keybinding_clearall(void)
4758 struct key_binding *k, *next;
4760 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4761 next = TAILQ_NEXT(k, entry);
4762 if (k->name == NULL)
4763 continue;
4765 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4766 k->name ? k->name : "unnamed key");
4767 TAILQ_REMOVE(&kbl, k, entry);
4768 g_free(k);
4773 keybinding_add(char *kb, char *value, struct key_binding *orig)
4775 struct key_binding *k;
4776 guint keyval, mask = 0;
4777 int i;
4779 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s %s\n", kb, value, orig->name);
4781 if (orig == NULL)
4782 return (1);
4783 if (strcmp(kb, orig->name))
4784 return (1);
4786 /* find modifier keys */
4787 if (strstr(value, "S-"))
4788 mask |= GDK_SHIFT_MASK;
4789 if (strstr(value, "C-"))
4790 mask |= GDK_CONTROL_MASK;
4791 if (strstr(value, "M1-"))
4792 mask |= GDK_MOD1_MASK;
4793 if (strstr(value, "M2-"))
4794 mask |= GDK_MOD2_MASK;
4795 if (strstr(value, "M3-"))
4796 mask |= GDK_MOD3_MASK;
4797 if (strstr(value, "M4-"))
4798 mask |= GDK_MOD4_MASK;
4799 if (strstr(value, "M5-"))
4800 mask |= GDK_MOD5_MASK;
4802 /* find keyname */
4803 for (i = strlen(value) - 1; i > 0; i--)
4804 if (value[i] == '-')
4805 value = &value[i + 1];
4807 /* validate keyname */
4808 keyval = gdk_keyval_from_name(value);
4809 if (keyval == GDK_VoidSymbol) {
4810 warnx("invalid keybinding name %s", value);
4811 return (1);
4813 /* must run this test too, gtk+ doesn't handle 10 for example */
4814 if (gdk_keyval_name(keyval) == NULL) {
4815 warnx("invalid keybinding name %s", value);
4816 return (1);
4819 /* make sure it isn't a dupe */
4820 TAILQ_FOREACH(k, &kbl, entry)
4821 if (k->key == keyval && k->mask == mask) {
4822 warnx("duplicate keybinding for %s", value);
4823 return (1);
4826 /* add keyname */
4827 k = g_malloc0(sizeof *k);
4828 k->name = orig->name;
4829 k->mask = mask;
4830 k->use_in_entry = orig->use_in_entry;
4831 k->key = keyval;
4832 k->func = orig->func;
4833 bcopy(&orig->arg, &k->arg, sizeof k->arg);
4835 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
4836 k->name,
4837 k->mask,
4838 k->use_in_entry,
4839 k->key);
4840 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
4841 k->name, gdk_keyval_name(keyval));
4843 TAILQ_INSERT_HEAD(&kbl, k, entry);
4845 return (0);
4849 add_kb(struct settings *s, char *entry)
4851 int i;
4852 char *kb, *value;
4854 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
4856 /* clearall is special */
4857 if (!strcmp(entry, "clearall")) {
4858 keybinding_clearall();
4859 return (0);
4862 kb = strstr(entry, ",");
4863 if (kb == NULL)
4864 return (1);
4865 *kb = '\0';
4866 value = kb + 1;
4868 /* make sure it is a valid keybinding */
4869 for (i = 0; i < LENGTH(keys); i++)
4870 if (keys[i].name && !strcmp(entry, keys[i].name)) {
4871 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s 0x%x %d 0x%x\n",
4872 keys[i].name,
4873 keys[i].mask,
4874 keys[i].use_in_entry,
4875 keys[i].key);
4877 return (keybinding_add(entry, value, &keys[i]));
4880 return (1);
4883 struct cmd {
4884 char *cmd;
4885 int params;
4886 int (*func)(struct tab *, struct karg *);
4887 struct karg arg;
4888 } cmds[] = {
4889 { "q!", 0, quit, {0} },
4890 { "qa", 0, quit, {0} },
4891 { "qa!", 0, quit, {0} },
4892 { "w", 0, save_tabs, {0} },
4893 { "wq", 0, save_tabs_and_quit, {0} },
4894 { "wq!", 0, save_tabs_and_quit, {0} },
4895 { "help", 0, help, {0} },
4896 { "about", 0, about, {0} },
4897 { "stats", 0, stats, {0} },
4898 { "version", 0, about, {0} },
4899 { "cookies", 0, xtp_page_cl, {0} },
4900 { "fav", 0, xtp_page_fl, {0} },
4901 { "favadd", 0, add_favorite, {0} },
4902 { "js", 2, js_cmd, {0} },
4903 { "cookie", 2, cookie_cmd, {0} },
4904 { "cert", 1, cert_cmd, {0} },
4905 { "ca", 0, ca_cmd, {0} },
4906 { "dl", 0, xtp_page_dl, {0} },
4907 { "h", 0, xtp_page_hl, {0} },
4908 { "hist", 0, xtp_page_hl, {0} },
4909 { "history", 0, xtp_page_hl, {0} },
4910 { "home", 0, go_home, {0} },
4911 { "restart", 0, restart, {0} },
4912 { "urlhide", 0, urlaction, {.i = XT_URL_HIDE} },
4913 { "urlh", 0, urlaction, {.i = XT_URL_HIDE} },
4914 { "urlshow", 0, urlaction, {.i = XT_URL_SHOW} },
4915 { "urls", 0, urlaction, {.i = XT_URL_SHOW} },
4916 { "statushide", 0, statusaction, {.i = XT_STATUSBAR_HIDE} },
4917 { "statush", 0, statusaction, {.i = XT_STATUSBAR_HIDE} },
4918 { "statusshow", 0, statusaction, {.i = XT_STATUSBAR_SHOW} },
4919 { "statuss", 0, statusaction, {.i = XT_STATUSBAR_SHOW} },
4921 { "1", 0, move, {.i = XT_MOVE_TOP} },
4922 { "print", 0, print_page, {0} },
4924 /* tabs */
4925 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
4926 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
4927 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
4928 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
4929 { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} },
4930 { "tabe", 1, tabaction, {.i = XT_TAB_NEW} },
4931 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
4932 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} },
4933 { "tabshow", 1, tabaction, {.i = XT_TAB_SHOW} },
4934 { "tabs", 1, tabaction, {.i = XT_TAB_SHOW} },
4935 { "tabhide", 1, tabaction, {.i = XT_TAB_HIDE} },
4936 { "tabh", 1, tabaction, {.i = XT_TAB_HIDE} },
4937 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4938 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4939 /* XXX add count to these commands */
4940 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST} },
4941 { "tabfir", 0, movetab, {.i = XT_TAB_FIRST} },
4942 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST} },
4943 { "tabr", 0, movetab, {.i = XT_TAB_FIRST} },
4944 { "tablast", 0, movetab, {.i = XT_TAB_LAST} },
4945 { "tabl", 0, movetab, {.i = XT_TAB_LAST} },
4946 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
4947 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
4948 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
4949 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
4951 /* settings */
4952 { "set", 1, set, {0} },
4953 { "fullscreen", 0, fullscreen, {0} },
4954 { "f", 0, fullscreen, {0} },
4956 /* sessions */
4957 { "session", 1, session_cmd, {0} },
4960 gboolean
4961 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
4963 hide_oops(t);
4965 return (FALSE);
4968 gboolean
4969 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
4971 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
4973 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
4974 delete_tab(t);
4976 return (FALSE);
4980 * cancel, remove, etc. downloads
4982 void
4983 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
4985 struct download find, *d;
4987 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
4989 /* some commands require a valid download id */
4990 if (cmd != XT_XTP_DL_LIST) {
4991 /* lookup download in question */
4992 find.id = id;
4993 d = RB_FIND(download_list, &downloads, &find);
4995 if (d == NULL) {
4996 show_oops(t, "%s: no such download", __func__);
4997 return;
5001 /* decide what to do */
5002 switch (cmd) {
5003 case XT_XTP_DL_CANCEL:
5004 webkit_download_cancel(d->download);
5005 break;
5006 case XT_XTP_DL_REMOVE:
5007 webkit_download_cancel(d->download); /* just incase */
5008 g_object_unref(d->download);
5009 RB_REMOVE(download_list, &downloads, d);
5010 break;
5011 case XT_XTP_DL_LIST:
5012 /* Nothing */
5013 break;
5014 default:
5015 show_oops(t, "%s: unknown command", __func__);
5016 break;
5018 xtp_page_dl(t, NULL);
5022 * Actions on history, only does one thing for now, but
5023 * we provide the function for future actions
5025 void
5026 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5028 struct history *h, *next;
5029 int i = 1;
5031 switch (cmd) {
5032 case XT_XTP_HL_REMOVE:
5033 /* walk backwards, as listed in reverse */
5034 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5035 next = RB_PREV(history_list, &hl, h);
5036 if (id == i) {
5037 RB_REMOVE(history_list, &hl, h);
5038 g_free((gpointer) h->title);
5039 g_free((gpointer) h->uri);
5040 g_free(h);
5041 break;
5043 i++;
5045 break;
5046 case XT_XTP_HL_LIST:
5047 /* Nothing - just xtp_page_hl() below */
5048 break;
5049 default:
5050 show_oops(t, "%s: unknown command", __func__);
5051 break;
5054 xtp_page_hl(t, NULL);
5057 /* remove a favorite */
5058 void
5059 remove_favorite(struct tab *t, int index)
5061 char file[PATH_MAX], *title, *uri;
5062 char *new_favs, *tmp;
5063 FILE *f;
5064 int i;
5065 size_t len, lineno;
5067 /* open favorites */
5068 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5070 if ((f = fopen(file, "r")) == NULL) {
5071 show_oops(t, "%s: can't open favorites: %s",
5072 __func__, strerror(errno));
5073 return;
5076 /* build a string which will become the new favroites file */
5077 new_favs = g_strdup_printf("%s", "");
5079 for (i = 1;;) {
5080 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5081 if (feof(f) || ferror(f))
5082 break;
5083 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5084 if (len == 0) {
5085 free(title);
5086 title = NULL;
5087 continue;
5090 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5091 if (feof(f) || ferror(f)) {
5092 show_oops(t, "%s: can't parse favorites %s",
5093 __func__, strerror(errno));
5094 goto clean;
5098 /* as long as this isn't the one we are deleting add to file */
5099 if (i != index) {
5100 tmp = new_favs;
5101 new_favs = g_strdup_printf("%s%s\n%s\n",
5102 new_favs, title, uri);
5103 g_free(tmp);
5106 free(uri);
5107 uri = NULL;
5108 free(title);
5109 title = NULL;
5110 i++;
5112 fclose(f);
5114 /* write back new favorites file */
5115 if ((f = fopen(file, "w")) == NULL) {
5116 show_oops(t, "%s: can't open favorites: %s",
5117 __func__, strerror(errno));
5118 goto clean;
5121 fwrite(new_favs, strlen(new_favs), 1, f);
5122 fclose(f);
5124 clean:
5125 if (uri)
5126 free(uri);
5127 if (title)
5128 free(title);
5130 g_free(new_favs);
5133 void
5134 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5136 switch (cmd) {
5137 case XT_XTP_FL_LIST:
5138 /* nothing, just the below call to xtp_page_fl() */
5139 break;
5140 case XT_XTP_FL_REMOVE:
5141 remove_favorite(t, arg);
5142 break;
5143 default:
5144 show_oops(t, "%s: invalid favorites command", __func__);
5145 break;
5148 xtp_page_fl(t, NULL);
5151 void
5152 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5154 switch (cmd) {
5155 case XT_XTP_CL_LIST:
5156 /* nothing, just xtp_page_cl() */
5157 break;
5158 case XT_XTP_CL_REMOVE:
5159 remove_cookie(arg);
5160 break;
5161 default:
5162 show_oops(t, "%s: unknown cookie xtp command", __func__);
5163 break;
5166 xtp_page_cl(t, NULL);
5169 /* link an XTP class to it's session key and handler function */
5170 struct xtp_despatch {
5171 uint8_t xtp_class;
5172 char **session_key;
5173 void (*handle_func)(struct tab *, uint8_t, int);
5176 struct xtp_despatch xtp_despatches[] = {
5177 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5178 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5179 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5180 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5181 { NULL, NULL, NULL }
5185 * is the url xtp protocol? (xxxt://)
5186 * if so, parse and despatch correct bahvior
5189 parse_xtp_url(struct tab *t, const char *url)
5191 char *dup = NULL, *p, *last;
5192 uint8_t n_tokens = 0;
5193 char *tokens[4] = {NULL, NULL, NULL, ""};
5194 struct xtp_despatch *dsp, *dsp_match = NULL;
5195 uint8_t req_class;
5198 * tokens array meaning:
5199 * tokens[0] = class
5200 * tokens[1] = session key
5201 * tokens[2] = action
5202 * tokens[3] = optional argument
5205 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5207 /*xtp tab meaning is normal unless proven special */
5208 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5210 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5211 return 0;
5213 dup = g_strdup(url + strlen(XT_XTP_STR));
5215 /* split out the url */
5216 for ((p = strtok_r(dup, "/", &last)); p;
5217 (p = strtok_r(NULL, "/", &last))) {
5218 if (n_tokens < 4)
5219 tokens[n_tokens++] = p;
5222 /* should be atleast three fields 'class/seskey/command/arg' */
5223 if (n_tokens < 3)
5224 goto clean;
5226 dsp = xtp_despatches;
5227 req_class = atoi(tokens[0]);
5228 while (dsp->xtp_class != NULL) {
5229 if (dsp->xtp_class == req_class) {
5230 dsp_match = dsp;
5231 break;
5233 dsp++;
5236 /* did we find one atall? */
5237 if (dsp_match == NULL) {
5238 show_oops(t, "%s: no matching xtp despatch found", __func__);
5239 goto clean;
5242 /* check session key and call despatch function */
5243 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5244 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5247 clean:
5248 if (dup)
5249 g_free(dup);
5251 return 1;
5256 void
5257 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5259 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5261 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5263 if (t == NULL) {
5264 show_oops_s("activate_uri_entry_cb invalid parameters");
5265 return;
5268 if (uri == NULL) {
5269 show_oops(t, "activate_uri_entry_cb no uri");
5270 return;
5273 uri += strspn(uri, "\t ");
5275 /* if xxxt:// treat specially */
5276 if (!parse_xtp_url(t, uri)) {
5277 load_uri(t, (gchar *)uri);
5278 focus_webview(t);
5282 void
5283 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5285 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5286 char *newuri = NULL;
5287 gchar *enc_search;
5289 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5291 if (t == NULL) {
5292 show_oops_s("activate_search_entry_cb invalid parameters");
5293 return;
5296 if (search_string == NULL) {
5297 show_oops(t, "no search_string");
5298 return;
5301 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5302 newuri = g_strdup_printf(search_string, enc_search);
5303 g_free(enc_search);
5305 webkit_web_view_load_uri(t->wv, newuri);
5306 focus_webview(t);
5308 if (newuri)
5309 g_free(newuri);
5312 void
5313 check_and_set_js(const gchar *uri, struct tab *t)
5315 struct domain *d = NULL;
5316 int es = 0;
5318 if (uri == NULL || t == NULL)
5319 return;
5321 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5322 es = 0;
5323 else
5324 es = 1;
5326 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5327 es ? "enable" : "disable", uri);
5329 g_object_set(G_OBJECT(t->settings),
5330 "enable-scripts", es, (char *)NULL);
5331 webkit_web_view_set_settings(t->wv, t->settings);
5333 button_set_stockid(t->js_toggle,
5334 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5337 void
5338 show_ca_status(struct tab *t, const char *uri)
5340 WebKitWebFrame *frame;
5341 WebKitWebDataSource *source;
5342 WebKitNetworkRequest *request;
5343 SoupMessage *message;
5344 GdkColor color;
5345 gchar *col_str = "white";
5346 int r;
5348 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5349 ssl_strict_certs, ssl_ca_file, uri);
5351 if (uri == NULL)
5352 goto done;
5353 if (ssl_ca_file == NULL) {
5354 if (g_str_has_prefix(uri, "http://"))
5355 goto done;
5356 if (g_str_has_prefix(uri, "https://")) {
5357 col_str = "red";
5358 goto done;
5360 return;
5362 if (g_str_has_prefix(uri, "http://") ||
5363 !g_str_has_prefix(uri, "https://"))
5364 goto done;
5366 frame = webkit_web_view_get_main_frame(t->wv);
5367 source = webkit_web_frame_get_data_source(frame);
5368 request = webkit_web_data_source_get_request(source);
5369 message = webkit_network_request_get_message(request);
5371 if (message && (soup_message_get_flags(message) &
5372 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5373 col_str = "green";
5374 goto done;
5375 } else {
5376 r = load_compare_cert(t, NULL);
5377 if (r == 0)
5378 col_str = "lightblue";
5379 else if (r == 1)
5380 col_str = "yellow";
5381 else
5382 col_str = "red";
5383 goto done;
5385 done:
5386 if (col_str) {
5387 gdk_color_parse(col_str, &color);
5388 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5390 if (!strcmp(col_str, "white")) {
5391 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5392 &color);
5393 gdk_color_parse("black", &color);
5394 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5395 &color);
5396 } else {
5397 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5398 &color);
5399 gdk_color_parse("black", &color);
5400 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5401 &color);
5406 void
5407 free_favicon(struct tab *t)
5409 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p pix %p\n",
5410 __func__, t->icon_download, t->icon_request, t->icon_pixbuf);
5412 if (t->icon_request)
5413 g_object_unref(t->icon_request);
5414 if (t->icon_pixbuf)
5415 g_object_unref(t->icon_pixbuf);
5416 if (t->icon_dest_uri)
5417 g_free(t->icon_dest_uri);
5419 t->icon_pixbuf = NULL;
5420 t->icon_request = NULL;
5421 t->icon_dest_uri = NULL;
5424 void
5425 xt_icon_from_name(struct tab *t, gchar *name)
5427 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5428 GTK_ENTRY_ICON_PRIMARY, "text-html");
5429 if (show_url == 0) {
5430 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5431 GTK_ENTRY_ICON_PRIMARY, "text-html");
5432 } else {
5433 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5434 GTK_ENTRY_ICON_PRIMARY, NULL);
5436 return;
5439 void
5440 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pixbuf)
5442 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5443 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5444 if (show_url == 0) {
5445 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5446 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5447 } else {
5448 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5449 GTK_ENTRY_ICON_PRIMARY, NULL);
5453 void
5454 abort_favicon_download(struct tab *t)
5456 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5458 if (t->icon_download)
5459 webkit_download_cancel(t->icon_download);
5460 else
5461 free_favicon(t);
5463 xt_icon_from_name(t, "text-html");
5466 void
5467 set_favicon_from_file(struct tab *t, char *file)
5469 gint width, height;
5470 GdkPixbuf *pixbuf, *scaled;
5471 struct stat sb;
5473 if (t == NULL || file == NULL)
5474 return;
5475 if (t->icon_pixbuf) {
5476 DNPRINTF(XT_D_DOWNLOAD, "%s: icon already set\n", __func__);
5477 return;
5480 if (g_str_has_prefix(file, "file://"))
5481 file += strlen("file://");
5482 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5484 if (!stat(file, &sb)) {
5485 if (sb.st_size == 0) {
5486 /* corrupt icon so trash it */
5487 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5488 __func__, file);
5489 unlink(file);
5490 /* no need to set icon to default here */
5491 return;
5495 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
5496 if (pixbuf == NULL) {
5497 xt_icon_from_name(t, "text-html");
5498 return;
5501 g_object_get(pixbuf, "width", &width, "height", &height,
5502 (char *)NULL);
5503 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
5504 __func__, t->tab_id, width, height);
5506 if (width > 16 || height > 16) {
5507 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5508 GDK_INTERP_BILINEAR);
5509 g_object_unref(pixbuf);
5510 } else
5511 scaled = pixbuf;
5513 if (scaled == NULL) {
5514 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5515 GDK_INTERP_BILINEAR);
5516 return;
5519 t->icon_pixbuf = scaled;
5520 xt_icon_from_pixbuf(t, t->icon_pixbuf);
5523 void
5524 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5525 struct tab *t)
5527 WebKitDownloadStatus status = webkit_download_get_status(download);
5529 if (t == NULL)
5530 return;
5532 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5533 __func__, t->tab_id, status);
5535 switch (status) {
5536 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5537 /* -1 */
5538 t->icon_download = NULL;
5539 free_favicon(t);
5540 break;
5541 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5542 /* 0 */
5543 break;
5544 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5545 /* 1 */
5546 break;
5547 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5548 /* 2 */
5549 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5550 __func__, t->tab_id);
5551 t->icon_download = NULL;
5552 free_favicon(t);
5553 break;
5554 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5555 /* 3 */
5556 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5557 __func__, t->icon_dest_uri);
5558 set_favicon_from_file(t, t->icon_dest_uri);
5559 /* these will be freed post callback */
5560 t->icon_request = NULL;
5561 t->icon_download = NULL;
5562 break;
5563 default:
5564 break;
5568 void
5569 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5571 gchar *name_hash, file[PATH_MAX];
5572 struct stat sb;
5574 DNPRINTF(XT_D_DOWNLOAD, "notify_icon_loaded_cb %s\n", uri);
5576 if (uri == NULL || t == NULL)
5577 return;
5579 if (t->icon_request) {
5580 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5581 return;
5584 /* check to see if we got the icon in cache */
5585 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5586 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5587 g_free(name_hash);
5589 if (!stat(file, &sb)) {
5590 if (sb.st_size > 0) {
5591 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5592 __func__, file);
5593 set_favicon_from_file(t, file);
5594 return;
5597 /* corrupt icon so trash it */
5598 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5599 __func__, file);
5600 unlink(file);
5603 /* create download for icon */
5604 t->icon_request = webkit_network_request_new(uri);
5605 if (t->icon_request == NULL) {
5606 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5607 __func__, uri);
5608 return;
5611 t->icon_download = webkit_download_new(t->icon_request);
5613 /* we have to free icon_dest_uri later */
5614 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5615 webkit_download_set_destination_uri(t->icon_download,
5616 t->icon_dest_uri);
5618 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5619 G_CALLBACK(favicon_download_status_changed_cb), t);
5621 webkit_download_start(t->icon_download);
5624 void
5625 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5627 const gchar *set = NULL, *uri = NULL, *title = NULL;
5628 struct history *h, find;
5629 int add = 0;
5630 const gchar *s_loading;
5631 struct karg a;
5633 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d\n",
5634 webkit_web_view_get_load_status(wview));
5636 if (t == NULL) {
5637 show_oops_s("notify_load_status_cb invalid paramters");
5638 return;
5641 switch (webkit_web_view_get_load_status(wview)) {
5642 case WEBKIT_LOAD_PROVISIONAL:
5643 /* 0 */
5644 abort_favicon_download(t);
5645 #if GTK_CHECK_VERSION(2, 20, 0)
5646 gtk_widget_show(t->spinner);
5647 gtk_spinner_start(GTK_SPINNER(t->spinner));
5648 #endif
5649 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5651 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5653 break;
5655 case WEBKIT_LOAD_COMMITTED:
5656 /* 1 */
5657 if ((uri = get_uri(wview)) != NULL) {
5658 if (strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
5659 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5661 if (t->status) {
5662 g_free(t->status);
5663 t->status = NULL;
5665 set_status(t, (char *)uri, XT_STATUS_LOADING);
5668 /* check if js white listing is enabled */
5669 if (enable_js_whitelist) {
5670 uri = get_uri(wview);
5671 check_and_set_js(uri, t);
5674 show_ca_status(t, uri);
5676 /* we know enough to autosave the session */
5677 if (session_autosave) {
5678 a.s = NULL;
5679 save_tabs(t, &a);
5681 break;
5683 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5684 /* 3 */
5685 title = webkit_web_view_get_title(wview);
5686 uri = get_uri(wview);
5687 if (title)
5688 set = title;
5689 else if (uri)
5690 set = uri;
5691 else
5692 break;
5694 gtk_label_set_text(GTK_LABEL(t->label), set);
5695 gtk_window_set_title(GTK_WINDOW(main_window), set);
5697 if (uri) {
5698 if (!strncmp(uri, "http://", strlen("http://")) ||
5699 !strncmp(uri, "https://", strlen("https://")) ||
5700 !strncmp(uri, "file://", strlen("file://")))
5701 add = 1;
5702 if (add == 0)
5703 break;
5704 find.uri = uri;
5705 h = RB_FIND(history_list, &hl, &find);
5706 if (h)
5707 break;
5709 h = g_malloc(sizeof *h);
5710 h->uri = g_strdup(uri);
5711 if (title)
5712 h->title = g_strdup(title);
5713 else
5714 h->title = g_strdup(uri);
5715 RB_INSERT(history_list, &hl, h);
5716 update_history_tabs(NULL);
5719 break;
5721 case WEBKIT_LOAD_FINISHED:
5722 /* 2 */
5723 uri = get_uri(wview);
5724 set_status(t, (char *)uri, XT_STATUS_URI);
5725 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5726 case WEBKIT_LOAD_FAILED:
5727 /* 4 */
5728 #endif
5729 #if GTK_CHECK_VERSION(2, 20, 0)
5730 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5731 gtk_widget_hide(t->spinner);
5732 #endif
5733 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
5734 if (s_loading && !strcmp(s_loading, "Loading"))
5735 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5736 default:
5737 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
5738 break;
5741 if (t->item)
5742 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
5743 else
5744 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
5745 webkit_web_view_can_go_back(wview));
5747 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
5748 webkit_web_view_can_go_forward(wview));
5750 /* take focus if we are visible */
5751 t->focus_wv = 1;
5752 focus_webview(t);
5755 void
5756 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5758 run_script(t, JS_HINTING);
5761 void
5762 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
5764 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
5765 progress == 100 ? 0 : (double)progress / 100);
5766 if (show_url == 0) {
5767 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
5768 progress == 100 ? 0 : (double)progress / 100);
5773 webview_nw_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5774 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5775 WebKitWebPolicyDecision *pd, struct tab *t)
5777 char *uri;
5779 if (t == NULL) {
5780 show_oops_s("webview_nw_cb invalid paramters");
5781 return (FALSE);
5784 DNPRINTF(XT_D_NAV, "webview_nw_cb: %s\n",
5785 webkit_network_request_get_uri(request));
5787 /* open in current tab */
5788 uri = (char *)webkit_network_request_get_uri(request);
5789 webkit_web_view_load_uri(t->wv, uri);
5790 webkit_web_policy_decision_ignore(pd);
5792 return (TRUE); /* we made the decission */
5796 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5797 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5798 WebKitWebPolicyDecision *pd, struct tab *t)
5800 char *uri;
5802 if (t == NULL) {
5803 show_oops_s("webview_npd_cb invalid parameters");
5804 return (FALSE);
5807 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
5808 t->ctrl_click,
5809 webkit_network_request_get_uri(request));
5811 uri = (char *)webkit_network_request_get_uri(request);
5813 if ((!parse_xtp_url(t, uri) && (t->ctrl_click))) {
5814 t->ctrl_click = 0;
5815 create_new_tab(uri, NULL, ctrl_click_focus);
5816 webkit_web_policy_decision_ignore(pd);
5817 return (TRUE); /* we made the decission */
5820 webkit_web_policy_decision_use(pd);
5821 return (TRUE); /* we made the decission */
5824 WebKitWebView *
5825 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5827 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
5828 webkit_web_view_get_uri(wv));
5830 return (wv);
5834 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
5836 /* we can not eat the event without throwing gtk off so defer it */
5838 /* catch middle click */
5839 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
5840 t->ctrl_click = 1;
5841 goto done;
5844 /* catch ctrl click */
5845 if (e->type == GDK_BUTTON_RELEASE &&
5846 CLEAN(e->state) == GDK_CONTROL_MASK)
5847 t->ctrl_click = 1;
5848 else
5849 t->ctrl_click = 0;
5850 done:
5851 return (XT_CB_PASSTHROUGH);
5855 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
5857 struct mime_type *m;
5859 m = find_mime_type(mime_type);
5860 if (m == NULL)
5861 return (1);
5863 switch (fork()) {
5864 case -1:
5865 show_oops(t, "can't fork mime handler");
5866 /* NOTREACHED */
5867 case 0:
5868 break;
5869 default:
5870 return (0);
5873 /* child */
5874 execlp(m->mt_action, m->mt_action,
5875 webkit_network_request_get_uri(request), (void *)NULL);
5877 _exit(0);
5879 /* NOTREACHED */
5880 return (0);
5884 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
5885 WebKitNetworkRequest *request, char *mime_type,
5886 WebKitWebPolicyDecision *decision, struct tab *t)
5888 if (t == NULL) {
5889 show_oops_s("webview_mimetype_cb invalid parameters");
5890 return (FALSE);
5893 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
5894 t->tab_id, mime_type);
5896 if (run_mimehandler(t, mime_type, request) == 0) {
5897 webkit_web_policy_decision_ignore(decision);
5898 focus_webview(t);
5899 return (TRUE);
5902 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
5903 webkit_web_policy_decision_download(decision);
5904 return (TRUE);
5907 return (FALSE);
5911 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
5912 struct tab *t)
5914 const gchar *filename;
5915 char *uri = NULL;
5916 struct download *download_entry;
5917 int ret = TRUE;
5919 if (wk_download == NULL || t == NULL) {
5920 show_oops_s("%s invalid parameters", __func__);
5921 return (FALSE);
5924 filename = webkit_download_get_suggested_filename(wk_download);
5925 if (filename == NULL)
5926 return (FALSE); /* abort download */
5928 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
5930 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
5931 "local %s\n", __func__, t->tab_id, filename, uri);
5933 webkit_download_set_destination_uri(wk_download, uri);
5935 if (webkit_download_get_status(wk_download) ==
5936 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5937 show_oops(t, "%s: download failed to start", __func__);
5938 ret = FALSE;
5939 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
5940 } else {
5941 download_entry = g_malloc(sizeof(struct download));
5942 download_entry->download = wk_download;
5943 download_entry->tab = t;
5944 download_entry->id = next_download_id++;
5945 RB_INSERT(download_list, &downloads, download_entry);
5946 /* get from history */
5947 g_object_ref(wk_download);
5948 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
5951 if (uri)
5952 g_free(uri);
5954 /* sync other download manager tabs */
5955 update_download_tabs(NULL);
5958 * NOTE: never redirect/render the current tab before this
5959 * function returns. This will cause the download to never start.
5961 return (ret); /* start download */
5964 void
5965 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
5967 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
5969 if (t == NULL) {
5970 show_oops_s("webview_hover_cb");
5971 return;
5974 if (uri)
5975 set_status(t, uri, XT_STATUS_LINK);
5976 else {
5977 if (t->status)
5978 set_status(t, t->status, XT_STATUS_NOTHING);
5983 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
5985 char s[2], buf[128];
5986 const char *errstr = NULL;
5987 long long link;
5989 /* don't use w directly; use t->whatever instead */
5991 if (t == NULL) {
5992 show_oops_s("wv_keypress_after_cb");
5993 return (XT_CB_PASSTHROUGH);
5996 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
5997 e->keyval, e->state, t);
5999 if (t->hints_on) {
6000 /* ESC */
6001 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6002 disable_hints(t);
6003 return (XT_CB_HANDLED);
6006 /* RETURN */
6007 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6008 link = strtonum(t->hint_num, 1, 1000, &errstr);
6009 if (errstr) {
6010 /* we have a string */
6011 } else {
6012 /* we have a number */
6013 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6014 t->hint_num);
6015 run_script(t, buf);
6017 disable_hints(t);
6020 /* BACKSPACE */
6021 /* XXX unfuck this */
6022 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6023 if (t->hint_mode == XT_HINT_NUMERICAL) {
6024 /* last input was numerical */
6025 int l;
6026 l = strlen(t->hint_num);
6027 if (l > 0) {
6028 l--;
6029 if (l == 0) {
6030 disable_hints(t);
6031 enable_hints(t);
6032 } else {
6033 t->hint_num[l] = '\0';
6034 goto num;
6037 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6038 /* last input was alphanumerical */
6039 int l;
6040 l = strlen(t->hint_buf);
6041 if (l > 0) {
6042 l--;
6043 if (l == 0) {
6044 disable_hints(t);
6045 enable_hints(t);
6046 } else {
6047 t->hint_buf[l] = '\0';
6048 goto anum;
6051 } else {
6052 /* bogus */
6053 disable_hints(t);
6057 /* numerical input */
6058 if (CLEAN(e->state) == 0 &&
6059 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6060 snprintf(s, sizeof s, "%c", e->keyval);
6061 strlcat(t->hint_num, s, sizeof t->hint_num);
6062 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6063 t->hint_num);
6064 num:
6065 link = strtonum(t->hint_num, 1, 1000, &errstr);
6066 if (errstr) {
6067 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6068 disable_hints(t);
6069 } else {
6070 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6071 t->hint_num);
6072 t->hint_mode = XT_HINT_NUMERICAL;
6073 run_script(t, buf);
6076 /* empty the counter buffer */
6077 bzero(t->hint_buf, sizeof t->hint_buf);
6078 return (XT_CB_HANDLED);
6081 /* alphanumerical input */
6082 if (
6083 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6084 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6085 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6086 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6087 snprintf(s, sizeof s, "%c", e->keyval);
6088 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6089 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6090 t->hint_buf);
6091 anum:
6092 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6093 run_script(t, buf);
6095 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6096 t->hint_buf);
6097 t->hint_mode = XT_HINT_ALPHANUM;
6098 run_script(t, buf);
6100 /* empty the counter buffer */
6101 bzero(t->hint_num, sizeof t->hint_num);
6102 return (XT_CB_HANDLED);
6105 return (XT_CB_HANDLED);
6108 struct key_binding *k;
6109 TAILQ_FOREACH(k, &kbl, entry)
6110 if (e->keyval == k->key) {
6111 if (k->mask == 0) {
6112 if ((e->state & (CTRL | MOD1)) == 0) {
6113 k->func(t, &k->arg);
6114 return (XT_CB_HANDLED);
6117 else if ((e->state & k->mask) == k->mask) {
6118 k->func(t, &k->arg);
6119 return (XT_CB_HANDLED);
6123 return (XT_CB_PASSTHROUGH);
6127 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6129 hide_oops(t);
6131 return (XT_CB_PASSTHROUGH);
6135 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6137 const gchar *c = gtk_entry_get_text(w);
6138 GdkColor color;
6139 int forward = TRUE;
6141 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6142 e->keyval, e->state, t);
6144 if (t == NULL) {
6145 show_oops_s("cmd_keyrelease_cb invalid parameters");
6146 return (XT_CB_PASSTHROUGH);
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 (c[0] == ':')
6153 goto done;
6154 if (strlen(c) == 1) {
6155 webkit_web_view_unmark_text_matches(t->wv);
6156 goto done;
6159 if (c[0] == '/')
6160 forward = TRUE;
6161 else if (c[0] == '?')
6162 forward = FALSE;
6163 else
6164 goto done;
6166 /* search */
6167 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6168 FALSE) {
6169 /* not found, mark red */
6170 gdk_color_parse("red", &color);
6171 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6172 /* unmark and remove selection */
6173 webkit_web_view_unmark_text_matches(t->wv);
6174 /* my kingdom for a way to unselect text in webview */
6175 } else {
6176 /* found, highlight all */
6177 webkit_web_view_unmark_text_matches(t->wv);
6178 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6179 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6180 gdk_color_parse("white", &color);
6181 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6183 done:
6184 return (XT_CB_PASSTHROUGH);
6187 #if 0
6189 cmd_complete(struct tab *t, char *s)
6191 int i;
6192 GtkEntry *w = GTK_ENTRY(t->cmd);
6194 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", s);
6196 for (i = 0; i < LENGTH(cmds); i++) {
6197 if (!strncasecmp(cmds[i].cmd, s, strlen(s))) {
6198 fprintf(stderr, "match %s %d\n", cmds[i].cmd, strcasecmp(cmds[i].cmd, s));
6199 #if 0
6200 gtk_entry_set_text(w, ":");
6201 gtk_entry_append_text(w, cmds[i].cmd);
6202 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6203 #endif
6207 return (0);
6209 #endif
6212 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6214 if (t == NULL) {
6215 show_oops_s("entry_key_cb invalid parameters");
6216 return (XT_CB_PASSTHROUGH);
6219 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6220 e->keyval, e->state, t);
6222 hide_oops(t);
6224 if (e->keyval == GDK_Escape) {
6225 /* don't use focus_webview(t) because we want to type :cmds */
6226 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6229 struct key_binding *k;
6230 TAILQ_FOREACH(k, &kbl, entry)
6231 if (e->keyval == k->key && k->use_in_entry) {
6232 if (k->mask == 0) {
6233 if ((e->state & (CTRL | MOD1)) == 0) {
6234 k->func(t, &k->arg);
6235 return (XT_CB_HANDLED);
6238 else if ((e->state & k->mask) == k->mask) {
6239 k->func(t, &k->arg);
6240 return (XT_CB_HANDLED);
6244 return (XT_CB_PASSTHROUGH);
6248 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6250 int rv = XT_CB_HANDLED;
6251 const gchar *c = gtk_entry_get_text(w);
6253 if (t == NULL) {
6254 show_oops_s("cmd_keypress_cb parameters");
6255 return (XT_CB_PASSTHROUGH);
6258 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6259 e->keyval, e->state, t);
6261 /* sanity */
6262 if (c == NULL)
6263 e->keyval = GDK_Escape;
6264 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6265 e->keyval = GDK_Escape;
6267 switch (e->keyval) {
6268 #if 0
6269 case GDK_Tab:
6270 if (c[0] != ':')
6271 goto done;
6273 if (strchr (c, ' ')) {
6274 /* par completion */
6275 fprintf(stderr, "completeme par\n");
6276 goto done;
6279 cmd_complete(t, (char *)&c[1]);
6281 goto done;
6282 #endif
6283 case GDK_BackSpace:
6284 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
6285 break;
6286 /* FALLTHROUGH */
6287 case GDK_Escape:
6288 hide_cmd(t);
6289 focus_webview(t);
6291 /* cancel search */
6292 if (c[0] == '/' || c[0] == '?')
6293 webkit_web_view_unmark_text_matches(t->wv);
6294 goto done;
6297 rv = XT_CB_PASSTHROUGH;
6298 done:
6299 return (rv);
6303 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
6305 if (t == NULL) {
6306 show_oops_s("cmd_focusout_cb invalid parameters");
6307 return (XT_CB_PASSTHROUGH);
6309 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
6311 hide_cmd(t);
6312 hide_oops(t);
6314 if (show_url == 0 || t->focus_wv)
6315 focus_webview(t);
6316 else
6317 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6319 return (XT_CB_PASSTHROUGH);
6322 void
6323 cmd_activate_cb(GtkEntry *entry, struct tab *t)
6325 int i;
6326 char *s;
6327 const gchar *c = gtk_entry_get_text(entry);
6329 if (t == NULL) {
6330 show_oops_s("cmd_activate_cb invalid parameters");
6331 return;
6334 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
6336 /* sanity */
6337 if (c == NULL)
6338 goto done;
6339 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6340 goto done;
6341 if (strlen(c) < 2)
6342 goto done;
6343 s = (char *)&c[1];
6345 if (c[0] == '/' || c[0] == '?') {
6346 if (t->search_text) {
6347 g_free(t->search_text);
6348 t->search_text = NULL;
6351 t->search_text = g_strdup(s);
6352 if (global_search)
6353 g_free(global_search);
6354 global_search = g_strdup(s);
6355 t->search_forward = c[0] == '/';
6357 goto done;
6360 for (i = 0; i < LENGTH(cmds); i++)
6361 if (cmds[i].params) {
6362 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
6363 cmds[i].arg.s = g_strdup(s);
6364 goto execute_command;
6366 } else {
6367 if (!strcmp(s, cmds[i].cmd))
6368 goto execute_command;
6370 show_oops(t, "Invalid command: %s", s);
6371 done:
6372 hide_cmd(t);
6373 return;
6375 execute_command:
6376 hide_cmd(t);
6377 cmds[i].func(t, &cmds[i].arg);
6379 void
6380 backward_cb(GtkWidget *w, struct tab *t)
6382 struct karg a;
6384 if (t == NULL) {
6385 show_oops_s("backward_cb invalid parameters");
6386 return;
6389 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
6391 a.i = XT_NAV_BACK;
6392 navaction(t, &a);
6395 void
6396 forward_cb(GtkWidget *w, struct tab *t)
6398 struct karg a;
6400 if (t == NULL) {
6401 show_oops_s("forward_cb invalid parameters");
6402 return;
6405 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
6407 a.i = XT_NAV_FORWARD;
6408 navaction(t, &a);
6411 void
6412 stop_cb(GtkWidget *w, struct tab *t)
6414 WebKitWebFrame *frame;
6416 if (t == NULL) {
6417 show_oops_s("stop_cb invalid parameters");
6418 return;
6421 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
6423 frame = webkit_web_view_get_main_frame(t->wv);
6424 if (frame == NULL) {
6425 show_oops(t, "stop_cb: no frame");
6426 return;
6429 webkit_web_frame_stop_loading(frame);
6430 abort_favicon_download(t);
6433 void
6434 setup_webkit(struct tab *t)
6436 g_object_set(G_OBJECT(t->settings),
6437 "user-agent", t->user_agent, (char *)NULL);
6438 g_object_set(G_OBJECT(t->settings),
6439 "enable-scripts", enable_scripts, (char *)NULL);
6440 g_object_set(G_OBJECT(t->settings),
6441 "enable-plugins", enable_plugins, (char *)NULL);
6442 adjustfont_webkit(t, XT_FONT_SET);
6444 webkit_web_view_set_settings(t->wv, t->settings);
6447 GtkWidget *
6448 create_browser(struct tab *t)
6450 GtkWidget *w;
6451 gchar *strval;
6453 if (t == NULL) {
6454 show_oops_s("create_browser invalid parameters");
6455 return (NULL);
6458 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
6459 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
6460 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
6461 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
6463 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
6464 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
6465 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
6467 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
6468 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
6470 /* set defaults */
6471 t->settings = webkit_web_settings_new();
6473 if (user_agent == NULL) {
6474 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
6475 (char *)NULL);
6476 t->user_agent = g_strdup_printf("%s %s+", strval, version);
6477 g_free(strval);
6478 } else {
6479 t->user_agent = g_strdup(user_agent);
6482 setup_webkit(t);
6484 return (w);
6487 GtkWidget *
6488 create_window(void)
6490 GtkWidget *w;
6492 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
6493 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
6494 gtk_widget_set_name(w, "xxxterm");
6495 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
6496 g_signal_connect(G_OBJECT(w), "delete_event",
6497 G_CALLBACK (gtk_main_quit), NULL);
6499 return (w);
6502 GtkWidget *
6503 create_toolbar(struct tab *t)
6505 GtkWidget *toolbar = NULL, *b, *eb1;
6507 b = gtk_hbox_new(FALSE, 0);
6508 toolbar = b;
6509 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
6511 if (fancy_bar) {
6512 /* backward button */
6513 t->backward = create_button("GoBack", GTK_STOCK_GO_BACK, 0);
6514 gtk_widget_set_sensitive(t->backward, FALSE);
6515 g_signal_connect(G_OBJECT(t->backward), "clicked",
6516 G_CALLBACK(backward_cb), t);
6517 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
6519 /* forward button */
6520 t->forward = create_button("GoForward",GTK_STOCK_GO_FORWARD, 0);
6521 gtk_widget_set_sensitive(t->forward, FALSE);
6522 g_signal_connect(G_OBJECT(t->forward), "clicked",
6523 G_CALLBACK(forward_cb), t);
6524 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
6525 FALSE, 0);
6527 /* stop button */
6528 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
6529 gtk_widget_set_sensitive(t->stop, FALSE);
6530 g_signal_connect(G_OBJECT(t->stop), "clicked",
6531 G_CALLBACK(stop_cb), t);
6532 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
6533 FALSE, 0);
6535 /* JS button */
6536 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
6537 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
6538 gtk_widget_set_sensitive(t->js_toggle, TRUE);
6539 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
6540 G_CALLBACK(js_toggle_cb), t);
6541 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
6544 t->uri_entry = gtk_entry_new();
6545 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
6546 G_CALLBACK(activate_uri_entry_cb), t);
6547 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
6548 G_CALLBACK(entry_key_cb), t);
6549 eb1 = gtk_hbox_new(FALSE, 0);
6550 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
6551 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
6552 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
6554 /* search entry */
6555 if (fancy_bar && search_string) {
6556 GtkWidget *eb2;
6557 t->search_entry = gtk_entry_new();
6558 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
6559 g_signal_connect(G_OBJECT(t->search_entry), "activate",
6560 G_CALLBACK(activate_search_entry_cb), t);
6561 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
6562 G_CALLBACK(entry_key_cb), t);
6563 gtk_widget_set_size_request(t->search_entry, -1, -1);
6564 eb2 = gtk_hbox_new(FALSE, 0);
6565 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
6566 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
6568 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
6570 return (toolbar);
6573 void
6574 recalc_tabs(void)
6576 struct tab *t;
6578 TAILQ_FOREACH(t, &tabs, entry)
6579 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
6583 undo_close_tab_save(struct tab *t)
6585 int m, n;
6586 const gchar *uri;
6587 struct undo *u1, *u2;
6588 GList *items;
6589 WebKitWebHistoryItem *item;
6591 if ((uri = get_uri(t->wv)) == NULL)
6592 return (1);
6594 u1 = g_malloc0(sizeof(struct undo));
6595 u1->uri = g_strdup(uri);
6597 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
6599 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
6600 n = webkit_web_back_forward_list_get_back_length(t->bfl);
6601 u1->back = n;
6603 /* forward history */
6604 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
6606 while (items) {
6607 item = items->data;
6608 u1->history = g_list_prepend(u1->history,
6609 webkit_web_history_item_copy(item));
6610 items = g_list_next(items);
6613 /* current item */
6614 if (m) {
6615 item = webkit_web_back_forward_list_get_current_item(t->bfl);
6616 u1->history = g_list_prepend(u1->history,
6617 webkit_web_history_item_copy(item));
6620 /* back history */
6621 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
6623 while (items) {
6624 item = items->data;
6625 u1->history = g_list_prepend(u1->history,
6626 webkit_web_history_item_copy(item));
6627 items = g_list_next(items);
6630 TAILQ_INSERT_HEAD(&undos, u1, entry);
6632 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
6633 u2 = TAILQ_LAST(&undos, undo_tailq);
6634 TAILQ_REMOVE(&undos, u2, entry);
6635 g_free(u2->uri);
6636 g_list_free(u2->history);
6637 g_free(u2);
6638 } else
6639 undo_count++;
6641 return (0);
6644 void
6645 delete_tab(struct tab *t)
6647 struct karg a;
6649 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
6651 if (t == NULL)
6652 return;
6654 TAILQ_REMOVE(&tabs, t, entry);
6656 /* halt all webkit activity */
6657 abort_favicon_download(t);
6658 webkit_web_view_stop_loading(t->wv);
6659 undo_close_tab_save(t);
6661 gtk_widget_destroy(t->vbox);
6662 g_free(t->user_agent);
6663 g_free(t);
6665 recalc_tabs();
6666 if (TAILQ_EMPTY(&tabs))
6667 create_new_tab(NULL, NULL, 1);
6670 /* recreate session */
6671 if (session_autosave) {
6672 a.s = NULL;
6673 save_tabs(t, &a);
6677 void
6678 adjustfont_webkit(struct tab *t, int adjust)
6680 if (t == NULL) {
6681 show_oops_s("adjustfont_webkit invalid parameters");
6682 return;
6685 if (adjust == XT_FONT_SET)
6686 t->font_size = default_font_size;
6688 t->font_size += adjust;
6689 g_object_set(G_OBJECT(t->settings), "default-font-size",
6690 t->font_size, (char *)NULL);
6691 g_object_get(G_OBJECT(t->settings), "default-font-size",
6692 &t->font_size, (char *)NULL);
6695 void
6696 append_tab(struct tab *t)
6698 if (t == NULL)
6699 return;
6701 TAILQ_INSERT_TAIL(&tabs, t, entry);
6702 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
6705 void
6706 create_new_tab(char *title, struct undo *u, int focus)
6708 struct tab *t, *tt;
6709 int load = 1, id, notfound;
6710 GtkWidget *b, *bb;
6711 WebKitWebHistoryItem *item;
6712 GList *items;
6713 GdkColor color;
6714 PangoFontDescription *fd = NULL;
6716 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
6718 if (tabless && !TAILQ_EMPTY(&tabs)) {
6719 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
6720 return;
6723 t = g_malloc0(sizeof *t);
6725 if (title == NULL) {
6726 title = "(untitled)";
6727 load = 0;
6730 t->vbox = gtk_vbox_new(FALSE, 0);
6732 /* label + button for tab */
6733 b = gtk_hbox_new(FALSE, 0);
6734 t->tab_content = b;
6736 #if GTK_CHECK_VERSION(2, 20, 0)
6737 t->spinner = gtk_spinner_new ();
6738 #endif
6739 t->label = gtk_label_new(title);
6740 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
6741 gtk_widget_set_size_request(t->label, 100, 0);
6742 gtk_widget_set_size_request(b, 130, 0);
6744 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
6745 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
6746 #if GTK_CHECK_VERSION(2, 20, 0)
6747 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
6748 #endif
6750 /* toolbar */
6751 t->toolbar = create_toolbar(t);
6752 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
6754 /* browser */
6755 t->browser_win = create_browser(t);
6756 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
6758 /* oops message for user feedback */
6759 t->oops = gtk_entry_new();
6760 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
6761 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
6762 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
6763 gdk_color_parse("red", &color);
6764 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
6765 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
6767 /* command entry */
6768 t->cmd = gtk_entry_new();
6769 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
6770 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
6771 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
6773 /* status bar */
6774 t->statusbar = gtk_entry_new();
6775 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
6776 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
6777 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
6778 gdk_color_parse("black", &color);
6779 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
6780 gdk_color_parse("white", &color);
6781 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
6782 fd = GTK_WIDGET(t->statusbar)->style->font_desc;
6783 pango_font_description_set_weight(fd, PANGO_WEIGHT_SEMIBOLD);
6784 pango_font_description_set_absolute_size(fd, 10 * PANGO_SCALE); /* 10 px font */
6785 gtk_widget_modify_font(t->statusbar, fd);
6786 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
6788 /* xtp meaning is normal by default */
6789 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6791 /* set empty favicon */
6792 xt_icon_from_name(t, "text-html");
6794 /* and show it all */
6795 gtk_widget_show_all(b);
6796 gtk_widget_show_all(t->vbox);
6798 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
6799 append_tab(t);
6800 else {
6801 notfound = 1;
6802 id = gtk_notebook_get_current_page(notebook);
6803 TAILQ_FOREACH(tt, &tabs, entry) {
6804 if (tt->tab_id == id) {
6805 notfound = 0;
6806 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
6807 gtk_notebook_insert_page(notebook, t->vbox, b,
6808 id + 1);
6809 recalc_tabs();
6810 break;
6813 if (notfound)
6814 append_tab(t);
6817 #if GTK_CHECK_VERSION(2, 20, 0)
6818 /* turn spinner off if we are a new tab without uri */
6819 if (!load) {
6820 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6821 gtk_widget_hide(t->spinner);
6823 #endif
6824 /* make notebook tabs reorderable */
6825 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
6827 g_object_connect(G_OBJECT(t->cmd),
6828 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
6829 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
6830 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
6831 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
6832 (char *)NULL);
6834 /* reuse wv_button_cb to hide oops */
6835 g_object_connect(G_OBJECT(t->oops),
6836 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
6837 (char *)NULL);
6839 g_object_connect(G_OBJECT(t->wv),
6840 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
6841 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
6842 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
6843 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
6844 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
6845 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
6846 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_nw_cb), t,
6847 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
6848 "signal::event", G_CALLBACK(webview_event_cb), t,
6849 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
6850 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
6851 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6852 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
6853 #endif
6854 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
6855 (char *)NULL);
6856 g_signal_connect(t->wv,
6857 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
6859 /* hijack the unused keys as if we were the browser */
6860 g_object_connect(G_OBJECT(t->toolbar),
6861 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
6862 (char *)NULL);
6864 g_signal_connect(G_OBJECT(bb), "button_press_event",
6865 G_CALLBACK(tab_close_cb), t);
6867 /* hide stuff */
6868 hide_cmd(t);
6869 hide_oops(t);
6870 url_set_visibility();
6871 statusbar_set_visibility();
6873 if (focus) {
6874 gtk_notebook_set_current_page(notebook, t->tab_id);
6875 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
6876 t->tab_id);
6879 if (load) {
6880 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
6881 load_uri(t, title);
6882 } else {
6883 if (show_url == 1)
6884 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6885 else
6886 focus_webview(t);
6889 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
6890 /* restore the tab's history */
6891 if (u && u->history) {
6892 items = u->history;
6893 while (items) {
6894 item = items->data;
6895 webkit_web_back_forward_list_add_item(t->bfl, item);
6896 items = g_list_next(items);
6899 item = g_list_nth_data(u->history, u->back);
6900 if (item)
6901 webkit_web_view_go_to_back_forward_item(t->wv, item);
6903 g_list_free(items);
6904 g_list_free(u->history);
6905 } else
6906 webkit_web_back_forward_list_clear(t->bfl);
6909 void
6910 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
6911 gpointer *udata)
6913 struct tab *t;
6914 const gchar *uri;
6916 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
6918 TAILQ_FOREACH(t, &tabs, entry) {
6919 if (t->tab_id == pn) {
6920 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
6921 "%d\n", pn);
6923 uri = webkit_web_view_get_title(t->wv);
6924 if (uri == NULL)
6925 uri = XT_NAME;
6926 gtk_window_set_title(GTK_WINDOW(main_window), uri);
6928 hide_cmd(t);
6929 hide_oops(t);
6931 if (t->focus_wv)
6932 focus_webview(t);
6937 void
6938 menuitem_response(struct tab *t)
6940 gtk_notebook_set_current_page(notebook, t->tab_id);
6943 gboolean
6944 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
6946 GtkWidget *menu, *menu_items;
6947 GdkEventButton *bevent;
6948 const gchar *uri;
6949 struct tab *ti;
6951 if (event->type == GDK_BUTTON_PRESS) {
6952 bevent = (GdkEventButton *) event;
6953 menu = gtk_menu_new();
6955 TAILQ_FOREACH(ti, &tabs, entry) {
6956 if ((uri = get_uri(ti->wv)) == NULL)
6957 /* XXX make sure there is something to print */
6958 /* XXX add gui pages in here to look purdy */
6959 uri = "(untitled)";
6960 menu_items = gtk_menu_item_new_with_label(uri);
6961 gtk_menu_append(GTK_MENU (menu), menu_items);
6962 gtk_widget_show(menu_items);
6964 gtk_signal_connect_object(GTK_OBJECT(menu_items),
6965 "activate", GTK_SIGNAL_FUNC(menuitem_response),
6966 (gpointer)ti);
6969 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
6970 bevent->button, bevent->time);
6972 /* unref object so it'll free itself when popped down */
6973 g_object_ref_sink(menu);
6974 g_object_unref(menu);
6976 return (TRUE /* eat event */);
6979 return (FALSE /* propagate */);
6983 icon_size_map(int icon_size)
6985 if (icon_size <= GTK_ICON_SIZE_INVALID ||
6986 icon_size > GTK_ICON_SIZE_DIALOG)
6987 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
6989 return (icon_size);
6992 GtkWidget *
6993 create_button(char *name, char *stockid, int size)
6995 GtkWidget *button, *image;
6996 gchar *rcstring;
6997 int gtk_icon_size;
6998 rcstring = g_strdup_printf(
6999 "style \"%s-style\"\n"
7000 "{\n"
7001 " GtkWidget::focus-padding = 0\n"
7002 " GtkWidget::focus-line-width = 0\n"
7003 " xthickness = 0\n"
7004 " ythickness = 0\n"
7005 "}\n"
7006 "widget \"*.%s\" style \"%s-style\"",name,name,name);
7007 gtk_rc_parse_string(rcstring);
7008 g_free(rcstring);
7009 button = gtk_button_new();
7010 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
7011 gtk_icon_size = icon_size_map(size?size:icon_size);
7013 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
7014 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7015 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
7016 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
7017 gtk_widget_set_name(button, name);
7018 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
7019 gtk_widget_set_tooltip_text(button, name);
7021 return button;
7024 void
7025 button_set_stockid(GtkWidget *button, char *stockid)
7027 GtkWidget *image;
7028 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
7029 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7030 gtk_button_set_image(GTK_BUTTON(button), image);
7033 void
7034 create_canvas(void)
7036 GtkWidget *vbox;
7037 GList *l = NULL;
7038 GdkPixbuf *pb;
7039 char file[PATH_MAX];
7040 int i;
7042 vbox = gtk_vbox_new(FALSE, 0);
7043 gtk_box_set_spacing(GTK_BOX(vbox), 0);
7044 notebook = GTK_NOTEBOOK(gtk_notebook_new());
7045 gtk_notebook_set_tab_hborder(notebook, 0);
7046 gtk_notebook_set_tab_vborder(notebook, 0);
7047 gtk_notebook_set_scrollable(notebook, TRUE);
7048 notebook_tab_set_visibility(notebook);
7049 gtk_notebook_set_show_border(notebook, FALSE);
7050 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
7052 abtn = gtk_button_new();
7053 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
7054 gtk_widget_set_size_request(arrow, -1, -1);
7055 gtk_container_add(GTK_CONTAINER(abtn), arrow);
7056 gtk_widget_set_size_request(abtn, -1, 20);
7057 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
7059 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
7060 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
7061 gtk_widget_set_size_request(vbox, -1, -1);
7063 g_object_connect(G_OBJECT(notebook),
7064 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
7065 (char *)NULL);
7066 g_signal_connect(G_OBJECT(abtn), "button_press_event",
7067 G_CALLBACK(arrow_cb), NULL);
7069 main_window = create_window();
7070 gtk_container_add(GTK_CONTAINER(main_window), vbox);
7071 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
7073 /* icons */
7074 for (i = 0; i < LENGTH(icons); i++) {
7075 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
7076 pb = gdk_pixbuf_new_from_file(file, NULL);
7077 l = g_list_append(l, pb);
7079 gtk_window_set_default_icon_list(l);
7081 gtk_widget_show_all(abtn);
7082 gtk_widget_show_all(main_window);
7085 void
7086 set_hook(void **hook, char *name)
7088 if (hook == NULL)
7089 errx(1, "set_hook");
7091 if (*hook == NULL) {
7092 *hook = dlsym(RTLD_NEXT, name);
7093 if (*hook == NULL)
7094 errx(1, "can't hook %s", name);
7098 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7099 gboolean
7100 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
7102 g_return_val_if_fail(cookie1, FALSE);
7103 g_return_val_if_fail(cookie2, FALSE);
7105 return (!strcmp (cookie1->name, cookie2->name) &&
7106 !strcmp (cookie1->value, cookie2->value) &&
7107 !strcmp (cookie1->path, cookie2->path) &&
7108 !strcmp (cookie1->domain, cookie2->domain));
7111 void
7112 transfer_cookies(void)
7114 GSList *cf;
7115 SoupCookie *sc, *pc;
7117 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7119 for (;cf; cf = cf->next) {
7120 pc = cf->data;
7121 sc = soup_cookie_copy(pc);
7122 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
7125 soup_cookies_free(cf);
7128 void
7129 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
7131 GSList *cf;
7132 SoupCookie *ci;
7134 print_cookie("soup_cookie_jar_delete_cookie", c);
7136 if (cookies_enabled == 0)
7137 return;
7139 if (jar == NULL || c == NULL)
7140 return;
7142 /* find and remove from persistent jar */
7143 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7145 for (;cf; cf = cf->next) {
7146 ci = cf->data;
7147 if (soup_cookie_equal(ci, c)) {
7148 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
7149 break;
7153 soup_cookies_free(cf);
7155 /* delete from session jar */
7156 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
7159 void
7160 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
7162 struct domain *d = NULL;
7163 SoupCookie *c;
7164 FILE *r_cookie_f;
7166 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
7167 jar, p_cookiejar, s_cookiejar);
7169 if (cookies_enabled == 0)
7170 return;
7172 /* see if we are up and running */
7173 if (p_cookiejar == NULL) {
7174 _soup_cookie_jar_add_cookie(jar, cookie);
7175 return;
7177 /* disallow p_cookiejar adds, shouldn't happen */
7178 if (jar == p_cookiejar)
7179 return;
7181 if (enable_cookie_whitelist &&
7182 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
7183 blocked_cookies++;
7184 DNPRINTF(XT_D_COOKIE,
7185 "soup_cookie_jar_add_cookie: reject %s\n",
7186 cookie->domain);
7187 if (save_rejected_cookies) {
7188 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
7189 show_oops_s("can't open reject cookie file");
7190 return;
7192 fseek(r_cookie_f, 0, SEEK_END);
7193 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7194 cookie->http_only ? "#HttpOnly_" : "",
7195 cookie->domain,
7196 *cookie->domain == '.' ? "TRUE" : "FALSE",
7197 cookie->path,
7198 cookie->secure ? "TRUE" : "FALSE",
7199 cookie->expires ?
7200 (gulong)soup_date_to_time_t(cookie->expires) :
7202 cookie->name,
7203 cookie->value);
7204 fflush(r_cookie_f);
7205 fclose(r_cookie_f);
7207 if (!allow_volatile_cookies)
7208 return;
7211 if (cookie->expires == NULL && session_timeout) {
7212 soup_cookie_set_expires(cookie,
7213 soup_date_new_from_now(session_timeout));
7214 print_cookie("modified add cookie", cookie);
7217 /* see if we are white listed for persistence */
7218 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
7219 /* add to persistent jar */
7220 c = soup_cookie_copy(cookie);
7221 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
7222 _soup_cookie_jar_add_cookie(p_cookiejar, c);
7225 /* add to session jar */
7226 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
7227 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
7230 void
7231 setup_cookies(void)
7233 char file[PATH_MAX];
7235 set_hook((void *)&_soup_cookie_jar_add_cookie,
7236 "soup_cookie_jar_add_cookie");
7237 set_hook((void *)&_soup_cookie_jar_delete_cookie,
7238 "soup_cookie_jar_delete_cookie");
7240 if (cookies_enabled == 0)
7241 return;
7244 * the following code is intricate due to overriding several libsoup
7245 * functions.
7246 * do not alter order of these operations.
7249 /* rejected cookies */
7250 if (save_rejected_cookies)
7251 snprintf(rc_fname, sizeof file, "%s/rejected.txt", work_dir);
7253 /* persistent cookies */
7254 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
7255 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
7257 /* session cookies */
7258 s_cookiejar = soup_cookie_jar_new();
7259 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
7260 cookie_policy, (void *)NULL);
7261 transfer_cookies();
7263 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
7266 void
7267 setup_proxy(char *uri)
7269 if (proxy_uri) {
7270 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
7271 soup_uri_free(proxy_uri);
7272 proxy_uri = NULL;
7274 if (http_proxy) {
7275 if (http_proxy != uri) {
7276 g_free(http_proxy);
7277 http_proxy = NULL;
7281 if (uri) {
7282 http_proxy = g_strdup(uri);
7283 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
7284 proxy_uri = soup_uri_new(http_proxy);
7285 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
7290 send_url_to_socket(char *url)
7292 int s, len, rv = -1;
7293 struct sockaddr_un sa;
7295 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7296 warnx("send_url_to_socket: socket");
7297 return (-1);
7300 sa.sun_family = AF_UNIX;
7301 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7302 work_dir, XT_SOCKET_FILE);
7303 len = SUN_LEN(&sa);
7305 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7306 warnx("send_url_to_socket: connect");
7307 goto done;
7310 if (send(s, url, strlen(url) + 1, 0) == -1) {
7311 warnx("send_url_to_socket: send");
7312 goto done;
7314 done:
7315 close(s);
7316 return (rv);
7319 void
7320 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
7322 int s, n;
7323 char str[XT_MAX_URL_LENGTH];
7324 socklen_t t = sizeof(struct sockaddr_un);
7325 struct sockaddr_un sa;
7326 struct passwd *p;
7327 uid_t uid;
7328 gid_t gid;
7330 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
7331 warn("socket_watcher: accept");
7332 return;
7335 if (getpeereid(s, &uid, &gid) == -1) {
7336 warn("socket_watcher: getpeereid");
7337 return;
7339 if (uid != getuid() || gid != getgid()) {
7340 warnx("socket_watcher: unauthorized user");
7341 return;
7344 p = getpwuid(uid);
7345 if (p == NULL) {
7346 warnx("socket_watcher: not a valid user");
7347 return;
7350 n = recv(s, str, sizeof(str), 0);
7351 if (n <= 0)
7352 return;
7354 create_new_tab(str, NULL, 1);
7358 is_running(void)
7360 int s, len, rv = 1;
7361 struct sockaddr_un sa;
7363 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7364 warn("is_running: socket");
7365 return (-1);
7368 sa.sun_family = AF_UNIX;
7369 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7370 work_dir, XT_SOCKET_FILE);
7371 len = SUN_LEN(&sa);
7373 /* connect to see if there is a listener */
7374 if (connect(s, (struct sockaddr *)&sa, len) == -1)
7375 rv = 0; /* not running */
7376 else
7377 rv = 1; /* already running */
7379 close(s);
7381 return (rv);
7385 build_socket(void)
7387 int s, len;
7388 struct sockaddr_un sa;
7390 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7391 warn("build_socket: socket");
7392 return (-1);
7395 sa.sun_family = AF_UNIX;
7396 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7397 work_dir, XT_SOCKET_FILE);
7398 len = SUN_LEN(&sa);
7400 /* connect to see if there is a listener */
7401 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7402 /* no listener so we will */
7403 unlink(sa.sun_path);
7405 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
7406 warn("build_socket: bind");
7407 goto done;
7410 if (listen(s, 1) == -1) {
7411 warn("build_socket: listen");
7412 goto done;
7415 return (s);
7418 done:
7419 close(s);
7420 return (-1);
7423 void
7424 usage(void)
7426 fprintf(stderr,
7427 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
7428 exit(0);
7432 main(int argc, char *argv[])
7434 struct stat sb;
7435 int c, s, optn = 0, focus = 1;
7436 char conf[PATH_MAX] = { '\0' };
7437 char file[PATH_MAX];
7438 char *env_proxy = NULL;
7439 FILE *f = NULL;
7440 struct karg a;
7441 struct sigaction sact;
7443 start_argv = argv;
7445 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
7447 while ((c = getopt(argc, argv, "STVf:s:tn")) != -1) {
7448 switch (c) {
7449 case 'S':
7450 show_url = 0;
7451 break;
7452 case 'T':
7453 show_tabs = 0;
7454 break;
7455 case 'V':
7456 errx(0 , "Version: %s", version);
7457 break;
7458 case 'f':
7459 strlcpy(conf, optarg, sizeof(conf));
7460 break;
7461 case 's':
7462 strlcpy(named_session, optarg, sizeof(named_session));
7463 break;
7464 case 't':
7465 tabless = 1;
7466 break;
7467 case 'n':
7468 optn = 1;
7469 break;
7470 default:
7471 usage();
7472 /* NOTREACHED */
7475 argc -= optind;
7476 argv += optind;
7478 RB_INIT(&hl);
7479 RB_INIT(&js_wl);
7480 RB_INIT(&downloads);
7482 TAILQ_INIT(&tabs);
7483 TAILQ_INIT(&mtl);
7484 TAILQ_INIT(&aliases);
7485 TAILQ_INIT(&undos);
7486 TAILQ_INIT(&kbl);
7488 init_keybindings();
7490 gnutls_global_init();
7492 /* generate session keys for xtp pages */
7493 generate_xtp_session_key(&dl_session_key);
7494 generate_xtp_session_key(&hl_session_key);
7495 generate_xtp_session_key(&cl_session_key);
7496 generate_xtp_session_key(&fl_session_key);
7498 /* prepare gtk */
7499 gtk_init(&argc, &argv);
7500 if (!g_thread_supported())
7501 g_thread_init(NULL);
7503 /* signals */
7504 bzero(&sact, sizeof(sact));
7505 sigemptyset(&sact.sa_mask);
7506 sact.sa_handler = sigchild;
7507 sact.sa_flags = SA_NOCLDSTOP;
7508 sigaction(SIGCHLD, &sact, NULL);
7510 /* set download dir */
7511 pwd = getpwuid(getuid());
7512 if (pwd == NULL)
7513 errx(1, "invalid user %d", getuid());
7514 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
7516 /* set default string settings */
7517 home = g_strdup("http://www.peereboom.us");
7518 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
7519 resource_dir = g_strdup("/usr/local/share/xxxterm/");
7520 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
7522 /* read config file */
7523 if (strlen(conf) == 0)
7524 snprintf(conf, sizeof conf, "%s/.%s",
7525 pwd->pw_dir, XT_CONF_FILE);
7526 config_parse(conf, 0);
7528 /* working directory */
7529 if (strlen(work_dir) == 0)
7530 snprintf(work_dir, sizeof work_dir, "%s/%s",
7531 pwd->pw_dir, XT_DIR);
7532 if (stat(work_dir, &sb)) {
7533 if (mkdir(work_dir, S_IRWXU) == -1)
7534 err(1, "mkdir work_dir");
7535 if (stat(work_dir, &sb))
7536 err(1, "stat work_dir");
7538 if (S_ISDIR(sb.st_mode) == 0)
7539 errx(1, "%s not a dir", work_dir);
7540 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7541 warnx("fixing invalid permissions on %s", work_dir);
7542 if (chmod(work_dir, S_IRWXU) == -1)
7543 err(1, "chmod");
7546 /* icon cache dir */
7547 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
7548 if (stat(cache_dir, &sb)) {
7549 if (mkdir(cache_dir, S_IRWXU) == -1)
7550 err(1, "mkdir cache_dir");
7551 if (stat(cache_dir, &sb))
7552 err(1, "stat cache_dir");
7554 if (S_ISDIR(sb.st_mode) == 0)
7555 errx(1, "%s not a dir", cache_dir);
7556 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7557 warnx("fixing invalid permissions on %s", cache_dir);
7558 if (chmod(cache_dir, S_IRWXU) == -1)
7559 err(1, "chmod");
7562 /* certs dir */
7563 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
7564 if (stat(certs_dir, &sb)) {
7565 if (mkdir(certs_dir, S_IRWXU) == -1)
7566 err(1, "mkdir certs_dir");
7567 if (stat(certs_dir, &sb))
7568 err(1, "stat certs_dir");
7570 if (S_ISDIR(sb.st_mode) == 0)
7571 errx(1, "%s not a dir", certs_dir);
7572 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7573 warnx("fixing invalid permissions on %s", certs_dir);
7574 if (chmod(certs_dir, S_IRWXU) == -1)
7575 err(1, "chmod");
7578 /* sessions dir */
7579 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
7580 work_dir, XT_SESSIONS_DIR);
7581 if (stat(sessions_dir, &sb)) {
7582 if (mkdir(sessions_dir, S_IRWXU) == -1)
7583 err(1, "mkdir sessions_dir");
7584 if (stat(sessions_dir, &sb))
7585 err(1, "stat sessions_dir");
7587 if (S_ISDIR(sb.st_mode) == 0)
7588 errx(1, "%s not a dir", sessions_dir);
7589 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7590 warnx("fixing invalid permissions on %s", sessions_dir);
7591 if (chmod(sessions_dir, S_IRWXU) == -1)
7592 err(1, "chmod");
7594 /* runtime settings that can override config file */
7595 if (runtime_settings[0] != '\0')
7596 config_parse(runtime_settings, 1);
7598 /* download dir */
7599 if (!strcmp(download_dir, pwd->pw_dir))
7600 strlcat(download_dir, "/downloads", sizeof download_dir);
7601 if (stat(download_dir, &sb)) {
7602 if (mkdir(download_dir, S_IRWXU) == -1)
7603 err(1, "mkdir download_dir");
7604 if (stat(download_dir, &sb))
7605 err(1, "stat download_dir");
7607 if (S_ISDIR(sb.st_mode) == 0)
7608 errx(1, "%s not a dir", download_dir);
7609 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7610 warnx("fixing invalid permissions on %s", download_dir);
7611 if (chmod(download_dir, S_IRWXU) == -1)
7612 err(1, "chmod");
7615 /* favorites file */
7616 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
7617 if (stat(file, &sb)) {
7618 warnx("favorites file doesn't exist, creating it");
7619 if ((f = fopen(file, "w")) == NULL)
7620 err(1, "favorites");
7621 fclose(f);
7624 /* cookies */
7625 session = webkit_get_default_session();
7626 setup_cookies();
7628 /* certs */
7629 if (ssl_ca_file) {
7630 if (stat(ssl_ca_file, &sb)) {
7631 warn("no CA file: %s", ssl_ca_file);
7632 g_free(ssl_ca_file);
7633 ssl_ca_file = NULL;
7634 } else
7635 g_object_set(session,
7636 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
7637 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
7638 (void *)NULL);
7641 /* proxy */
7642 env_proxy = getenv("http_proxy");
7643 if (env_proxy)
7644 setup_proxy(env_proxy);
7645 else
7646 setup_proxy(http_proxy);
7648 /* see if there is already an xxxterm running */
7649 if (single_instance && is_running()) {
7650 optn = 1;
7651 warnx("already running");
7654 if (optn) {
7655 while (argc) {
7656 send_url_to_socket(argv[0]);
7658 argc--;
7659 argv++;
7661 exit(0);
7664 /* go graphical */
7665 create_canvas();
7667 if (save_global_history)
7668 restore_global_history();
7670 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
7671 restore_saved_tabs();
7672 else {
7673 a.s = named_session;
7674 a.i = XT_SES_DONOTHING;
7675 open_tabs(NULL, &a);
7678 while (argc) {
7679 create_new_tab(argv[0], NULL, focus);
7680 focus = 0;
7682 argc--;
7683 argv++;
7686 if (TAILQ_EMPTY(&tabs))
7687 create_new_tab(home, NULL, 1);
7689 if (enable_socket)
7690 if ((s = build_socket()) != -1)
7691 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
7693 gtk_main();
7695 gnutls_global_deinit();
7697 return (0);