add work_dir
[xxxterm.git] / xxxterm.c
blob5632e420d092210e54a53a8c426770b9181edcc5
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
138 #else
139 #define DPRINTF(x...)
140 #define DNPRINTF(n,x...)
141 #endif
143 #define LENGTH(x) (sizeof x / sizeof x[0])
144 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
145 ~(GDK_BUTTON1_MASK) & \
146 ~(GDK_BUTTON2_MASK) & \
147 ~(GDK_BUTTON3_MASK) & \
148 ~(GDK_BUTTON4_MASK) & \
149 ~(GDK_BUTTON5_MASK))
151 char *icons[] = {
152 "xxxtermicon16.png",
153 "xxxtermicon32.png",
154 "xxxtermicon48.png",
155 "xxxtermicon64.png",
156 "xxxtermicon128.png"
159 struct tab {
160 TAILQ_ENTRY(tab) entry;
161 GtkWidget *vbox;
162 GtkWidget *tab_content;
163 GtkWidget *label;
164 GtkWidget *spinner;
165 GtkWidget *uri_entry;
166 GtkWidget *search_entry;
167 GtkWidget *toolbar;
168 GtkWidget *browser_win;
169 GtkWidget *statusbar;
170 GtkWidget *cmd;
171 GtkWidget *oops;
172 GtkWidget *backward;
173 GtkWidget *forward;
174 GtkWidget *stop;
175 GtkWidget *js_toggle;
176 guint tab_id;
177 WebKitWebView *wv;
179 WebKitWebHistoryItem *item;
180 WebKitWebBackForwardList *bfl;
182 /* favicon */
183 WebKitNetworkRequest *icon_request;
184 WebKitDownload *icon_download;
185 GdkPixbuf *icon_pixbuf;
186 gchar *icon_dest_uri;
188 /* adjustments for browser */
189 GtkScrollbar *sb_h;
190 GtkScrollbar *sb_v;
191 GtkAdjustment *adjust_h;
192 GtkAdjustment *adjust_v;
194 /* flags */
195 int focus_wv;
196 int ctrl_click;
197 gchar *status;
198 int xtp_meaning; /* identifies dls/favorites */
200 /* hints */
201 int hints_on;
202 int hint_mode;
203 #define XT_HINT_NONE (0)
204 #define XT_HINT_NUMERICAL (1)
205 #define XT_HINT_ALPHANUM (2)
206 char hint_buf[128];
207 char hint_num[128];
209 /* search */
210 char *search_text;
211 int search_forward;
213 /* settings */
214 WebKitWebSettings *settings;
215 int font_size;
216 gchar *user_agent;
218 TAILQ_HEAD(tab_list, tab);
220 struct history {
221 RB_ENTRY(history) entry;
222 const gchar *uri;
223 const gchar *title;
225 RB_HEAD(history_list, history);
227 struct download {
228 RB_ENTRY(download) entry;
229 int id;
230 WebKitDownload *download;
231 struct tab *tab;
233 RB_HEAD(download_list, download);
235 struct domain {
236 RB_ENTRY(domain) entry;
237 gchar *d;
238 int handy; /* app use */
240 RB_HEAD(domain_list, domain);
242 struct undo {
243 TAILQ_ENTRY(undo) entry;
244 gchar *uri;
245 GList *history;
246 int back; /* Keeps track of how many back
247 * history items there are. */
249 TAILQ_HEAD(undo_tailq, undo);
251 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
252 int next_download_id = 1;
254 struct karg {
255 int i;
256 char *s;
259 /* defines */
260 #define XT_NAME ("XXXTerm")
261 #define XT_DIR (".xxxterm")
262 #define XT_CACHE_DIR ("cache")
263 #define XT_CERT_DIR ("certs/")
264 #define XT_SESSIONS_DIR ("sessions/")
265 #define XT_CONF_FILE ("xxxterm.conf")
266 #define XT_FAVS_FILE ("favorites")
267 #define XT_SAVED_TABS_FILE ("main_session")
268 #define XT_RESTART_TABS_FILE ("restart_tabs")
269 #define XT_SOCKET_FILE ("socket")
270 #define XT_HISTORY_FILE ("history")
271 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
272 #define XT_CB_HANDLED (TRUE)
273 #define XT_CB_PASSTHROUGH (FALSE)
274 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
275 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>"
276 #define XT_DLMAN_REFRESH "10"
277 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
278 "td {overflow: hidden;" \
279 " padding: 2px 2px 2px 2px;" \
280 " border: 1px solid black}\n" \
281 "tr:hover {background: #ffff99 ;}\n" \
282 "th {background-color: #cccccc;" \
283 " border: 1px solid black}" \
284 "table {border-spacing: 0; " \
285 " width: 90%%;" \
286 " border: 1px black solid;}\n" \
287 ".progress-outer{" \
288 " border: 1px solid black;" \
289 " height: 8px;" \
290 " width: 90%%;}" \
291 ".progress-inner{" \
292 " float: left;" \
293 " height: 8px;" \
294 " background: green;}" \
295 ".dlstatus{" \
296 " font-size: small;" \
297 " text-align: center;}" \
298 "</style>\n\n"
299 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
300 #define XT_MAX_UNDO_CLOSE_TAB (32)
301 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
303 /* file sizes */
304 #define SZ_KB ((uint64_t) 1024)
305 #define SZ_MB (SZ_KB * SZ_KB)
306 #define SZ_GB (SZ_KB * SZ_KB * SZ_KB)
307 #define SZ_TB (SZ_KB * SZ_KB * SZ_KB * SZ_KB)
310 * xxxterm "protocol" (xtp)
311 * We use this for managing stuff like downloads and favorites. They
312 * make magical HTML pages in memory which have xxxt:// links in order
313 * to communicate with xxxterm's internals. These links take the format:
314 * xxxt://class/session_key/action/arg
316 * Don't begin xtp class/actions as 0. atoi returns that on error.
318 * Typically we have not put addition of items in this framework, as
319 * adding items is either done via an ex-command or via a keybinding instead.
322 #define XT_XTP_STR "xxxt://"
324 /* XTP classes (xxxt://<class>) */
325 #define XT_XTP_DL 1 /* downloads */
326 #define XT_XTP_HL 2 /* history */
327 #define XT_XTP_CL 3 /* cookies */
328 #define XT_XTP_FL 4 /* favorites */
330 /* XTP download actions */
331 #define XT_XTP_DL_LIST 1
332 #define XT_XTP_DL_CANCEL 2
333 #define XT_XTP_DL_REMOVE 3
335 /* XTP history actions */
336 #define XT_XTP_HL_LIST 1
337 #define XT_XTP_HL_REMOVE 2
339 /* XTP cookie actions */
340 #define XT_XTP_CL_LIST 1
341 #define XT_XTP_CL_REMOVE 2
343 /* XTP cookie actions */
344 #define XT_XTP_FL_LIST 1
345 #define XT_XTP_FL_REMOVE 2
347 /* xtp tab meanings - identifies which tabs have xtp pages in */
348 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
349 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
350 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
351 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
352 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
354 /* actions */
355 #define XT_MOVE_INVALID (0)
356 #define XT_MOVE_DOWN (1)
357 #define XT_MOVE_UP (2)
358 #define XT_MOVE_BOTTOM (3)
359 #define XT_MOVE_TOP (4)
360 #define XT_MOVE_PAGEDOWN (5)
361 #define XT_MOVE_PAGEUP (6)
362 #define XT_MOVE_HALFDOWN (7)
363 #define XT_MOVE_HALFUP (8)
364 #define XT_MOVE_LEFT (9)
365 #define XT_MOVE_FARLEFT (10)
366 #define XT_MOVE_RIGHT (11)
367 #define XT_MOVE_FARRIGHT (12)
369 #define XT_TAB_LAST (-4)
370 #define XT_TAB_FIRST (-3)
371 #define XT_TAB_PREV (-2)
372 #define XT_TAB_NEXT (-1)
373 #define XT_TAB_INVALID (0)
374 #define XT_TAB_NEW (1)
375 #define XT_TAB_DELETE (2)
376 #define XT_TAB_DELQUIT (3)
377 #define XT_TAB_OPEN (4)
378 #define XT_TAB_UNDO_CLOSE (5)
379 #define XT_TAB_SHOW (6)
380 #define XT_TAB_HIDE (7)
382 #define XT_NAV_INVALID (0)
383 #define XT_NAV_BACK (1)
384 #define XT_NAV_FORWARD (2)
385 #define XT_NAV_RELOAD (3)
386 #define XT_NAV_RELOAD_CACHE (4)
388 #define XT_FOCUS_INVALID (0)
389 #define XT_FOCUS_URI (1)
390 #define XT_FOCUS_SEARCH (2)
392 #define XT_SEARCH_INVALID (0)
393 #define XT_SEARCH_NEXT (1)
394 #define XT_SEARCH_PREV (2)
396 #define XT_PASTE_CURRENT_TAB (0)
397 #define XT_PASTE_NEW_TAB (1)
399 #define XT_FONT_SET (0)
401 #define XT_URL_SHOW (1)
402 #define XT_URL_HIDE (2)
404 #define XT_STATUSBAR_SHOW (1)
405 #define XT_STATUSBAR_HIDE (2)
407 #define XT_WL_TOGGLE (1<<0)
408 #define XT_WL_ENABLE (1<<1)
409 #define XT_WL_DISABLE (1<<2)
410 #define XT_WL_FQDN (1<<3) /* default */
411 #define XT_WL_TOPLEVEL (1<<4)
413 #define XT_CMD_OPEN (0)
414 #define XT_CMD_OPEN_CURRENT (1)
415 #define XT_CMD_TABNEW (2)
416 #define XT_CMD_TABNEW_CURRENT (3)
418 #define XT_STATUS_NOTHING (0)
419 #define XT_STATUS_LINK (1)
420 #define XT_STATUS_URI (2)
421 #define XT_STATUS_LOADING (3)
423 #define XT_SES_DONOTHING (0)
424 #define XT_SES_CLOSETABS (1)
426 #define XT_COOKIE_NORMAL (0)
427 #define XT_COOKIE_WHITELIST (1)
429 /* mime types */
430 struct mime_type {
431 char *mt_type;
432 char *mt_action;
433 int mt_default;
434 TAILQ_ENTRY(mime_type) entry;
436 TAILQ_HEAD(mime_type_list, mime_type);
438 /* uri aliases */
439 struct alias {
440 char *a_name;
441 char *a_uri;
442 TAILQ_ENTRY(alias) entry;
444 TAILQ_HEAD(alias_list, alias);
446 /* settings that require restart */
447 int tabless = 0; /* allow only 1 tab */
448 int enable_socket = 0;
449 int single_instance = 0; /* only allow one xxxterm to run */
450 int fancy_bar = 1; /* fancy toolbar */
451 int browser_mode = XT_COOKIE_NORMAL;
453 /* runtime settings */
454 int show_tabs = 1; /* show tabs on notebook */
455 int show_url = 1; /* show url toolbar on notebook */
456 int show_statusbar = 0; /* vimperator style status bar */
457 int ctrl_click_focus = 0; /* ctrl click gets focus */
458 int cookies_enabled = 1; /* enable cookies */
459 int read_only_cookies = 0; /* enable to not write cookies */
460 int enable_scripts = 1;
461 int enable_plugins = 0;
462 int default_font_size = 12;
463 int window_height = 768;
464 int window_width = 1024;
465 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
466 unsigned refresh_interval = 10; /* download refresh interval */
467 int enable_cookie_whitelist = 0;
468 int enable_js_whitelist = 0;
469 time_t session_timeout = 3600; /* cookie session timeout */
470 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
471 char *ssl_ca_file = NULL;
472 char *resource_dir = NULL;
473 gboolean ssl_strict_certs = FALSE;
474 int append_next = 1; /* append tab after current tab */
475 char *home = NULL;
476 char *search_string = NULL;
477 char *http_proxy = NULL;
478 char download_dir[PATH_MAX];
479 char runtime_settings[PATH_MAX]; /* override of settings */
480 int allow_volatile_cookies = 0;
481 int save_global_history = 0; /* save global history to disk */
482 char *user_agent = NULL;
483 int save_rejected_cookies = 0;
484 time_t session_autosave = 0;
485 int guess_search = 0;
487 struct settings;
488 struct key_binding;
489 int set_download_dir(struct settings *, char *);
490 int set_work_dir(struct settings *, char *);
491 int set_runtime_dir(struct settings *, char *);
492 int set_browser_mode(struct settings *, char *);
493 int set_cookie_policy(struct settings *, char *);
494 int add_alias(struct settings *, char *);
495 int add_mime_type(struct settings *, char *);
496 int add_cookie_wl(struct settings *, char *);
497 int add_js_wl(struct settings *, char *);
498 int add_kb(struct settings *, char *);
499 void button_set_stockid(GtkWidget *, char *);
500 GtkWidget * create_button(char *, char *, int);
502 char *get_browser_mode(struct settings *);
503 char *get_cookie_policy(struct settings *);
505 char *get_download_dir(struct settings *);
506 char *get_work_dir(struct settings *);
507 char *get_runtime_dir(struct settings *);
509 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
510 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
511 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
512 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
513 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
515 struct special {
516 int (*set)(struct settings *, char *);
517 char *(*get)(struct settings *);
518 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
521 struct special s_browser_mode = {
522 set_browser_mode,
523 get_browser_mode,
524 NULL
527 struct special s_cookie = {
528 set_cookie_policy,
529 get_cookie_policy,
530 NULL
533 struct special s_alias = {
534 add_alias,
535 NULL,
536 walk_alias
539 struct special s_mime = {
540 add_mime_type,
541 NULL,
542 walk_mime_type
545 struct special s_js = {
546 add_js_wl,
547 NULL,
548 walk_js_wl
551 struct special s_kb = {
552 add_kb,
553 NULL,
554 walk_kb
557 struct special s_cookie_wl = {
558 add_cookie_wl,
559 NULL,
560 walk_cookie_wl
563 struct special s_download_dir = {
564 set_download_dir,
565 get_download_dir,
566 NULL
569 struct special s_work_dir = {
570 set_work_dir,
571 get_work_dir,
572 NULL
575 struct settings {
576 char *name;
577 int type;
578 #define XT_S_INVALID (0)
579 #define XT_S_INT (1)
580 #define XT_S_STR (2)
581 uint32_t flags;
582 #define XT_SF_RESTART (1<<0)
583 #define XT_SF_RUNTIME (1<<1)
584 int *ival;
585 char **sval;
586 struct special *s;
587 } rs[] = {
588 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
589 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
590 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
591 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
592 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
593 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
594 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
595 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
596 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
597 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
598 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
599 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
600 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
601 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
602 { "home", XT_S_STR, 0, NULL, &home, NULL },
603 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
604 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
605 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
606 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
607 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
608 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
609 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
610 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
611 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
612 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
613 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
614 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
615 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
616 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
617 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
618 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
619 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
620 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
621 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
622 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
623 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
625 /* runtime settings */
626 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
627 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
628 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
629 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
630 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
633 /* globals */
634 extern char *__progname;
635 char **start_argv;
636 struct passwd *pwd;
637 GtkWidget *main_window;
638 GtkNotebook *notebook;
639 GtkWidget *arrow, *abtn;
640 struct tab_list tabs;
641 struct history_list hl;
642 struct download_list downloads;
643 struct domain_list c_wl;
644 struct domain_list js_wl;
645 struct undo_tailq undos;
646 struct keybinding_list kbl;
647 int undo_count;
648 int updating_dl_tabs = 0;
649 int updating_hl_tabs = 0;
650 int updating_cl_tabs = 0;
651 int updating_fl_tabs = 0;
652 char *global_search;
653 uint64_t blocked_cookies = 0;
654 char named_session[PATH_MAX];
655 void update_favicon(struct tab *);
656 int icon_size_map(int);
658 void
659 sigchild(int sig)
661 int saved_errno, status;
662 pid_t pid;
664 saved_errno = errno;
666 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
667 if (pid == -1) {
668 if (errno == EINTR)
669 continue;
670 if (errno != ECHILD) {
672 clog_warn("sigchild: waitpid:");
675 break;
678 if (WIFEXITED(status)) {
679 if (WEXITSTATUS(status) != 0) {
681 clog_warnx("sigchild: child exit status: %d",
682 WEXITSTATUS(status));
685 } else {
687 clog_warnx("sigchild: child is terminated abnormally");
692 errno = saved_errno;
695 void
696 load_webkit_string(struct tab *t, const char *str)
698 /* we set this to indicate we want to manually do navaction */
699 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
700 webkit_web_view_load_string(t->wv, str, NULL, NULL, NULL);
703 void
704 set_status(struct tab *t, gchar *s, int status)
706 gchar *type = NULL;
708 if (s == NULL)
709 return;
711 switch (status) {
712 case XT_STATUS_LOADING:
713 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), s);
714 type = g_strdup_printf("Loading: %s", s);
715 s = type;
716 break;
717 case XT_STATUS_LINK:
718 type = g_strdup_printf("Link: %s", s);
719 if (!t->status)
720 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
721 s = type;
722 break;
723 case XT_STATUS_URI:
724 type = g_strdup_printf("%s", s);
725 if (!t->status) {
726 t->status = g_strdup(type);
728 s = type;
729 if (!t->status)
730 t->status = g_strdup(s);
731 break;
732 case XT_STATUS_NOTHING:
733 /* FALL THROUGH */
734 default:
735 break;
737 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
738 if (type)
739 g_free(type);
742 void
743 hide_oops(struct tab *t)
745 gtk_widget_hide(t->oops);
748 void
749 hide_cmd(struct tab *t)
751 gtk_widget_hide(t->cmd);
754 void
755 show_cmd(struct tab *t)
757 gtk_widget_hide(t->oops);
758 gtk_widget_show(t->cmd);
761 void
762 show_oops(struct tab *t, const char *fmt, ...)
764 va_list ap;
765 char *msg;
767 if (fmt == NULL)
768 return;
770 va_start(ap, fmt);
771 if (vasprintf(&msg, fmt, ap) == -1)
772 errx(1, "show_oops failed");
773 va_end(ap);
775 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
776 gtk_widget_hide(t->cmd);
777 gtk_widget_show(t->oops);
780 /* XXX collapse with show_oops */
781 void
782 show_oops_s(const char *fmt, ...)
784 va_list ap;
785 char *msg;
786 struct tab *ti, *t = NULL;
788 if (fmt == NULL)
789 return;
791 TAILQ_FOREACH(ti, &tabs, entry)
792 if (ti->tab_id == gtk_notebook_current_page(notebook)) {
793 t = ti;
794 break;
796 if (t == NULL)
797 return;
799 va_start(ap, fmt);
800 if (vasprintf(&msg, fmt, ap) == -1)
801 errx(1, "show_oops_s failed");
802 va_end(ap);
804 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
805 gtk_widget_hide(t->cmd);
806 gtk_widget_show(t->oops);
809 char *
810 get_as_string(struct settings *s)
812 char *r = NULL;
814 if (s == NULL)
815 return (NULL);
817 if (s->s) {
818 if (s->s->get)
819 r = s->s->get(s);
820 else
821 warnx("get_as_string skip %s\n", s->name);
822 } else if (s->type == XT_S_INT)
823 r = g_strdup_printf("%d", *s->ival);
824 else if (s->type == XT_S_STR)
825 r = g_strdup(*s->sval);
826 else
827 r = g_strdup_printf("INVALID TYPE");
829 return (r);
832 void
833 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
835 int i;
836 char *s;
838 for (i = 0; i < LENGTH(rs); i++) {
839 if (rs[i].s && rs[i].s->walk)
840 rs[i].s->walk(&rs[i], cb, cb_args);
841 else {
842 s = get_as_string(&rs[i]);
843 cb(&rs[i], s, cb_args);
844 g_free(s);
850 set_browser_mode(struct settings *s, char *val)
852 if (!strcmp(val, "whitelist")) {
853 browser_mode = XT_COOKIE_WHITELIST;
854 allow_volatile_cookies = 0;
855 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
856 cookies_enabled = 1;
857 enable_cookie_whitelist = 1;
858 read_only_cookies = 0;
859 save_rejected_cookies = 0;
860 session_timeout = 3600;
861 enable_scripts = 0;
862 enable_js_whitelist = 1;
863 } else if (!strcmp(val, "normal")) {
864 browser_mode = XT_COOKIE_NORMAL;
865 allow_volatile_cookies = 0;
866 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
867 cookies_enabled = 1;
868 enable_cookie_whitelist = 0;
869 read_only_cookies = 0;
870 save_rejected_cookies = 0;
871 session_timeout = 3600;
872 enable_scripts = 1;
873 enable_js_whitelist = 0;
874 } else
875 return (1);
877 return (0);
880 char *
881 get_browser_mode(struct settings *s)
883 char *r = NULL;
885 if (browser_mode == XT_COOKIE_WHITELIST)
886 r = g_strdup("whitelist");
887 else if (browser_mode == XT_COOKIE_NORMAL)
888 r = g_strdup("normal");
889 else
890 return (NULL);
892 return (r);
896 set_cookie_policy(struct settings *s, char *val)
898 if (!strcmp(val, "no3rdparty"))
899 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
900 else if (!strcmp(val, "accept"))
901 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
902 else if (!strcmp(val, "reject"))
903 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
904 else
905 return (1);
907 return (0);
910 char *
911 get_cookie_policy(struct settings *s)
913 char *r = NULL;
915 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
916 r = g_strdup("no3rdparty");
917 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
918 r = g_strdup("accept");
919 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
920 r = g_strdup("reject");
921 else
922 return (NULL);
924 return (r);
927 char *
928 get_download_dir(struct settings *s)
930 if (download_dir[0] == '\0')
931 return (0);
932 return (g_strdup(download_dir));
936 set_download_dir(struct settings *s, char *val)
938 if (val[0] == '~')
939 snprintf(download_dir, sizeof download_dir, "%s/%s",
940 pwd->pw_dir, &val[1]);
941 else
942 strlcpy(download_dir, val, sizeof download_dir);
944 return (0);
948 * Session IDs.
949 * We use these to prevent people putting xxxt:// URLs on
950 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
952 #define XT_XTP_SES_KEY_SZ 8
953 #define XT_XTP_SES_KEY_HEX_FMT \
954 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
955 char *dl_session_key; /* downloads */
956 char *hl_session_key; /* history list */
957 char *cl_session_key; /* cookie list */
958 char *fl_session_key; /* favorites list */
960 char work_dir[PATH_MAX];
961 char certs_dir[PATH_MAX];
962 char cache_dir[PATH_MAX];
963 char sessions_dir[PATH_MAX];
964 char cookie_file[PATH_MAX];
965 SoupURI *proxy_uri = NULL;
966 SoupSession *session;
967 SoupCookieJar *s_cookiejar;
968 SoupCookieJar *p_cookiejar;
969 char rc_fname[PATH_MAX];
971 struct mime_type_list mtl;
972 struct alias_list aliases;
974 /* protos */
975 void create_new_tab(char *, struct undo *, int);
976 void delete_tab(struct tab *);
977 void adjustfont_webkit(struct tab *, int);
978 int run_script(struct tab *, char *);
979 int download_rb_cmp(struct download *, struct download *);
980 int xtp_page_hl(struct tab *t, struct karg *args);
981 int xtp_page_dl(struct tab *t, struct karg *args);
982 int xtp_page_cl(struct tab *t, struct karg *args);
983 int xtp_page_fl(struct tab *t, struct karg *args);
986 history_rb_cmp(struct history *h1, struct history *h2)
988 return (strcmp(h1->uri, h2->uri));
990 RB_GENERATE(history_list, history, entry, history_rb_cmp);
993 domain_rb_cmp(struct domain *d1, struct domain *d2)
995 return (strcmp(d1->d, d2->d));
997 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
999 char *
1000 get_work_dir(struct settings *s)
1002 if (work_dir[0] == '\0')
1003 return (0);
1004 return (g_strdup(work_dir));
1008 set_work_dir(struct settings *s, char *val)
1010 if (val[0] == '~')
1011 snprintf(work_dir, sizeof work_dir, "%s/%s",
1012 pwd->pw_dir, &val[1]);
1013 else
1014 strlcpy(work_dir, val, sizeof work_dir);
1016 return (0);
1020 * generate a session key to secure xtp commands.
1021 * pass in a ptr to the key in question and it will
1022 * be modified in place.
1024 void
1025 generate_xtp_session_key(char **key)
1027 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1029 /* free old key */
1030 if (*key)
1031 g_free(*key);
1033 /* make a new one */
1034 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1035 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1036 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1037 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1039 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1043 * validate a xtp session key.
1044 * return 1 if OK
1047 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1049 if (strcmp(trusted, untrusted) != 0) {
1050 show_oops(t, "%s: xtp session key mismatch possible spoof",
1051 __func__);
1052 return (0);
1055 return (1);
1059 download_rb_cmp(struct download *e1, struct download *e2)
1061 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1063 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1065 struct valid_url_types {
1066 char *type;
1067 } vut[] = {
1068 { "http://" },
1069 { "https://" },
1070 { "ftp://" },
1071 { "file://" },
1072 { XT_XTP_STR },
1076 valid_url_type(char *url)
1078 int i;
1080 for (i = 0; i < LENGTH(vut); i++)
1081 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1082 return (0);
1084 return (1);
1087 void
1088 print_cookie(char *msg, SoupCookie *c)
1090 if (c == NULL)
1091 return;
1093 if (msg)
1094 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1095 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1096 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1097 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1098 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1099 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1100 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1101 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1102 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1103 DNPRINTF(XT_D_COOKIE, "====================================\n");
1106 void
1107 walk_alias(struct settings *s,
1108 void (*cb)(struct settings *, char *, void *), void *cb_args)
1110 struct alias *a;
1111 char *str;
1113 if (s == NULL || cb == NULL) {
1114 show_oops_s("walk_alias invalid parameters");
1115 return;
1118 TAILQ_FOREACH(a, &aliases, entry) {
1119 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1120 cb(s, str, cb_args);
1121 g_free(str);
1125 char *
1126 match_alias(char *url_in)
1128 struct alias *a;
1129 char *arg;
1130 char *url_out = NULL, *search, *enc_arg;
1132 search = g_strdup(url_in);
1133 arg = search;
1134 if (strsep(&arg, " \t") == NULL) {
1135 show_oops_s("match_alias: NULL URL");
1136 goto done;
1139 TAILQ_FOREACH(a, &aliases, entry) {
1140 if (!strcmp(search, a->a_name))
1141 break;
1144 if (a != NULL) {
1145 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1146 a->a_name);
1147 if (arg != NULL) {
1148 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1149 url_out = g_strdup_printf(a->a_uri, enc_arg);
1150 g_free(enc_arg);
1151 } else
1152 url_out = g_strdup(a->a_uri);
1154 done:
1155 g_free(search);
1156 return (url_out);
1159 char *
1160 guess_url_type(char *url_in)
1162 struct stat sb;
1163 char *url_out = NULL, *enc_search = NULL;
1165 url_out = match_alias(url_in);
1166 if (url_out != NULL)
1167 return (url_out);
1169 if (guess_search) {
1171 * If there is no dot nor slash in the string and it isn't a
1172 * path to a local file and doesn't resolves to an IP, assume
1173 * that the user wants to search for the string.
1176 if (strchr(url_in, '.') == NULL &&
1177 strchr(url_in, '/') == NULL &&
1178 stat(url_in, &sb) != 0 &&
1179 gethostbyname(url_in) == NULL) {
1181 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1182 url_out = g_strdup_printf(search_string, enc_search);
1183 g_free(enc_search);
1184 return (url_out);
1188 /* XXX not sure about this heuristic */
1189 if (stat(url_in, &sb) == 0)
1190 url_out = g_strdup_printf("file://%s", url_in);
1191 else
1192 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1194 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1196 return (url_out);
1199 void
1200 load_uri(struct tab *t, gchar *uri)
1202 gchar *newuri = NULL;
1204 if (uri == NULL || !strlen(uri))
1205 return;
1207 /* Strip leading spaces. */
1208 while(*uri && isspace(*uri))
1209 uri++;
1211 if (!strlen(uri))
1212 return;
1214 if (valid_url_type(uri)) {
1215 newuri = guess_url_type(uri);
1216 uri = newuri;
1219 set_status(t, (char *)uri, XT_STATUS_LOADING);
1220 webkit_web_view_load_uri(t->wv, uri);
1222 if (newuri)
1223 g_free(newuri);
1226 const gchar *
1227 get_uri(WebKitWebView *wv)
1229 WebKitWebFrame *frame;
1230 const gchar *uri;
1232 frame = webkit_web_view_get_main_frame(wv);
1233 uri = webkit_web_frame_get_uri(frame);
1235 if (uri && strlen(uri) > 0)
1236 return (uri);
1237 else
1238 return (NULL);
1242 add_alias(struct settings *s, char *line)
1244 char *l, *alias;
1245 struct alias *a = NULL;
1247 if (s == NULL || line == NULL) {
1248 show_oops_s("add_alias invalid parameters");
1249 return (1);
1252 l = line;
1253 a = g_malloc(sizeof(*a));
1255 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1256 show_oops_s("add_alias: incomplete alias definition");
1257 goto bad;
1259 if (strlen(alias) == 0 || strlen(l) == 0) {
1260 show_oops_s("add_alias: invalid alias definition");
1261 goto bad;
1264 a->a_name = g_strdup(alias);
1265 a->a_uri = g_strdup(l);
1267 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1269 TAILQ_INSERT_TAIL(&aliases, a, entry);
1271 return (0);
1272 bad:
1273 if (a)
1274 g_free(a);
1275 return (1);
1279 add_mime_type(struct settings *s, char *line)
1281 char *mime_type;
1282 char *l;
1283 struct mime_type *m = NULL;
1285 /* XXX this could be smarter */
1287 if (line == NULL) {
1288 show_oops_s("add_mime_type invalid parameters");
1289 return (1);
1292 l = line;
1293 m = g_malloc(sizeof(*m));
1295 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1296 show_oops_s("add_mime_type: invalid mime_type");
1297 goto bad;
1299 if (mime_type[strlen(mime_type) - 1] == '*') {
1300 mime_type[strlen(mime_type) - 1] = '\0';
1301 m->mt_default = 1;
1302 } else
1303 m->mt_default = 0;
1305 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1306 show_oops_s("add_mime_type: invalid mime_type");
1307 goto bad;
1310 m->mt_type = g_strdup(mime_type);
1311 m->mt_action = g_strdup(l);
1313 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1314 m->mt_type, m->mt_action, m->mt_default);
1316 TAILQ_INSERT_TAIL(&mtl, m, entry);
1318 return (0);
1319 bad:
1320 if (m)
1321 g_free(m);
1322 return (1);
1325 struct mime_type *
1326 find_mime_type(char *mime_type)
1328 struct mime_type *m, *def = NULL, *rv = NULL;
1330 TAILQ_FOREACH(m, &mtl, entry) {
1331 if (m->mt_default &&
1332 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1333 def = m;
1335 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1336 rv = m;
1337 break;
1341 if (rv == NULL)
1342 rv = def;
1344 return (rv);
1347 void
1348 walk_mime_type(struct settings *s,
1349 void (*cb)(struct settings *, char *, void *), void *cb_args)
1351 struct mime_type *m;
1352 char *str;
1354 if (s == NULL || cb == NULL)
1355 show_oops_s("walk_mime_type invalid parameters");
1357 TAILQ_FOREACH(m, &mtl, entry) {
1358 str = g_strdup_printf("%s%s --> %s",
1359 m->mt_type,
1360 m->mt_default ? "*" : "",
1361 m->mt_action);
1362 cb(s, str, cb_args);
1363 g_free(str);
1367 void
1368 wl_add(char *str, struct domain_list *wl, int handy)
1370 struct domain *d;
1371 int add_dot = 0;
1373 if (str == NULL || wl == NULL)
1374 return;
1375 if (strlen(str) < 2)
1376 return;
1378 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1380 /* treat *.moo.com the same as .moo.com */
1381 if (str[0] == '*' && str[1] == '.')
1382 str = &str[1];
1383 else if (str[0] == '.')
1384 str = &str[0];
1385 else
1386 add_dot = 1;
1388 d = g_malloc(sizeof *d);
1389 if (add_dot)
1390 d->d = g_strdup_printf(".%s", str);
1391 else
1392 d->d = g_strdup(str);
1393 d->handy = handy;
1395 if (RB_INSERT(domain_list, wl, d))
1396 goto unwind;
1398 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1399 return;
1400 unwind:
1401 if (d) {
1402 if (d->d)
1403 g_free(d->d);
1404 g_free(d);
1409 add_cookie_wl(struct settings *s, char *entry)
1411 wl_add(entry, &c_wl, 1);
1412 return (0);
1415 void
1416 walk_cookie_wl(struct settings *s,
1417 void (*cb)(struct settings *, char *, void *), void *cb_args)
1419 struct domain *d;
1421 if (s == NULL || cb == NULL) {
1422 show_oops_s("walk_cookie_wl invalid parameters");
1423 return;
1426 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1427 cb(s, d->d, cb_args);
1430 void
1431 walk_js_wl(struct settings *s,
1432 void (*cb)(struct settings *, char *, void *), void *cb_args)
1434 struct domain *d;
1436 if (s == NULL || cb == NULL) {
1437 show_oops_s("walk_js_wl invalid parameters");
1438 return;
1441 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1442 cb(s, d->d, cb_args);
1446 add_js_wl(struct settings *s, char *entry)
1448 wl_add(entry, &js_wl, 1 /* persistent */);
1449 return (0);
1452 struct domain *
1453 wl_find(const gchar *search, struct domain_list *wl)
1455 int i;
1456 struct domain *d = NULL, dfind;
1457 gchar *s = NULL;
1459 if (search == NULL || wl == NULL)
1460 return (NULL);
1461 if (strlen(search) < 2)
1462 return (NULL);
1464 if (search[0] != '.')
1465 s = g_strdup_printf(".%s", search);
1466 else
1467 s = g_strdup(search);
1469 for (i = strlen(s) - 1; i >= 0; i--) {
1470 if (s[i] == '.') {
1471 dfind.d = &s[i];
1472 d = RB_FIND(domain_list, wl, &dfind);
1473 if (d)
1474 goto done;
1478 done:
1479 if (s)
1480 g_free(s);
1482 return (d);
1485 struct domain *
1486 wl_find_uri(const gchar *s, struct domain_list *wl)
1488 int i;
1489 char *ss;
1490 struct domain *r;
1492 if (s == NULL || wl == NULL)
1493 return (NULL);
1495 if (!strncmp(s, "http://", strlen("http://")))
1496 s = &s[strlen("http://")];
1497 else if (!strncmp(s, "https://", strlen("https://")))
1498 s = &s[strlen("https://")];
1500 if (strlen(s) < 2)
1501 return (NULL);
1503 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1504 /* chop string at first slash */
1505 if (s[i] == '/' || s[i] == '\0') {
1506 ss = g_strdup(s);
1507 ss[i] = '\0';
1508 r = wl_find(ss, wl);
1509 g_free(ss);
1510 return (r);
1513 return (NULL);
1516 char *
1517 get_toplevel_domain(char *domain)
1519 char *s;
1520 int found = 0;
1522 if (domain == NULL)
1523 return (NULL);
1524 if (strlen(domain) < 2)
1525 return (NULL);
1527 s = &domain[strlen(domain) - 1];
1528 while (s != domain) {
1529 if (*s == '.') {
1530 found++;
1531 if (found == 2)
1532 return (s);
1534 s--;
1537 if (found)
1538 return (domain);
1540 return (NULL);
1543 #define WS "\n= \t"
1544 void
1545 config_parse(char *filename, int runtime)
1547 FILE *config, *f;
1548 char *line, *cp, *var, *val;
1549 size_t len, lineno = 0;
1550 int i, handled, *p;
1551 char **s, file[PATH_MAX];
1552 struct stat sb;
1554 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1556 if (filename == NULL)
1557 return;
1559 if (runtime && runtime_settings[0] != '\0') {
1560 snprintf(file, sizeof file, "%s/%s",
1561 work_dir, runtime_settings);
1562 if (stat(file, &sb)) {
1563 warnx("runtime file doesn't exist, creating it");
1564 if ((f = fopen(file, "w")) == NULL)
1565 err(1, "runtime");
1566 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1567 fclose(f);
1569 } else
1570 strlcpy(file, filename, sizeof file);
1572 if ((config = fopen(file, "r")) == NULL) {
1573 warn("config_parse: cannot open %s", filename);
1574 return;
1577 for (;;) {
1578 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1579 if (feof(config) || ferror(config))
1580 break;
1582 cp = line;
1583 cp += (long)strspn(cp, WS);
1584 if (cp[0] == '\0') {
1585 /* empty line */
1586 free(line);
1587 continue;
1590 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1591 errx(1, "invalid config file entry: %s", line);
1593 cp += (long)strspn(cp, WS);
1595 if ((val = strsep(&cp, "\0")) == NULL)
1596 break;
1598 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1600 /* get settings */
1601 for (i = 0, handled = 0; i < LENGTH(rs); i++) {
1602 if (strcmp(var, rs[i].name))
1603 continue;
1605 if (rs[i].s) {
1606 if (rs[i].s->set(&rs[i], val))
1607 errx(1, "invalid value for %s", var);
1608 handled = 1;
1609 break;
1610 } else
1611 switch (rs[i].type) {
1612 case XT_S_INT:
1613 p = rs[i].ival;
1614 *p = atoi(val);
1615 handled = 1;
1616 break;
1617 case XT_S_STR:
1618 s = rs[i].sval;
1619 if (s == NULL)
1620 errx(1, "invalid sval for %s",
1621 rs[i].name);
1622 if (*s)
1623 g_free(*s);
1624 *s = g_strdup(val);
1625 handled = 1;
1626 break;
1627 case XT_S_INVALID:
1628 default:
1629 errx(1, "invalid type for %s", var);
1631 break;
1633 if (handled == 0)
1634 errx(1, "invalid conf file entry: %s=%s", var, val);
1636 free(line);
1639 fclose(config);
1642 char *
1643 js_ref_to_string(JSContextRef context, JSValueRef ref)
1645 char *s = NULL;
1646 size_t l;
1647 JSStringRef jsref;
1649 jsref = JSValueToStringCopy(context, ref, NULL);
1650 if (jsref == NULL)
1651 return (NULL);
1653 l = JSStringGetMaximumUTF8CStringSize(jsref);
1654 s = g_malloc(l);
1655 if (s)
1656 JSStringGetUTF8CString(jsref, s, l);
1657 JSStringRelease(jsref);
1659 return (s);
1662 void
1663 disable_hints(struct tab *t)
1665 bzero(t->hint_buf, sizeof t->hint_buf);
1666 bzero(t->hint_num, sizeof t->hint_num);
1667 run_script(t, "vimprobable_clear()");
1668 t->hints_on = 0;
1669 t->hint_mode = XT_HINT_NONE;
1672 void
1673 enable_hints(struct tab *t)
1675 bzero(t->hint_buf, sizeof t->hint_buf);
1676 run_script(t, "vimprobable_show_hints()");
1677 t->hints_on = 1;
1678 t->hint_mode = XT_HINT_NONE;
1681 #define XT_JS_OPEN ("open;")
1682 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1683 #define XT_JS_FIRE ("fire;")
1684 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1685 #define XT_JS_FOUND ("found;")
1686 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1689 run_script(struct tab *t, char *s)
1691 JSGlobalContextRef ctx;
1692 WebKitWebFrame *frame;
1693 JSStringRef str;
1694 JSValueRef val, exception;
1695 char *es, buf[128];
1697 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1698 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1700 frame = webkit_web_view_get_main_frame(t->wv);
1701 ctx = webkit_web_frame_get_global_context(frame);
1703 str = JSStringCreateWithUTF8CString(s);
1704 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1705 NULL, 0, &exception);
1706 JSStringRelease(str);
1708 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1709 if (val == NULL) {
1710 es = js_ref_to_string(ctx, exception);
1711 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1712 g_free(es);
1713 return (1);
1714 } else {
1715 es = js_ref_to_string(ctx, val);
1716 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1718 /* handle return value right here */
1719 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1720 disable_hints(t);
1721 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1724 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1725 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1726 &es[XT_JS_FIRE_LEN]);
1727 run_script(t, buf);
1728 disable_hints(t);
1731 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1732 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1733 disable_hints(t);
1736 g_free(es);
1739 return (0);
1743 hint(struct tab *t, struct karg *args)
1746 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1748 if (t->hints_on == 0)
1749 enable_hints(t);
1750 else
1751 disable_hints(t);
1753 return (0);
1757 * Doesn't work fully, due to the following bug:
1758 * https://bugs.webkit.org/show_bug.cgi?id=51747
1761 restore_global_history(void)
1763 char file[PATH_MAX];
1764 FILE *f;
1765 struct history *h;
1766 gchar *uri;
1767 gchar *title;
1769 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
1771 if ((f = fopen(file, "r")) == NULL) {
1772 warnx("%s: fopen", __func__);
1773 return (1);
1776 for (;;) {
1777 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1778 if (feof(f) || ferror(f))
1779 break;
1781 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1782 if (feof(f) || ferror(f)) {
1783 free(uri);
1784 warnx("%s: broken history file\n", __func__);
1785 return (1);
1788 if (uri && strlen(uri) && title && strlen(title)) {
1789 webkit_web_history_item_new_with_data(uri, title);
1790 h = g_malloc(sizeof(struct history));
1791 h->uri = g_strdup(uri);
1792 h->title = g_strdup(title);
1793 RB_INSERT(history_list, &hl, h);
1794 } else {
1795 warnx("%s: failed to restore history\n", __func__);
1796 free(uri);
1797 free(title);
1798 return (1);
1801 free(uri);
1802 free(title);
1803 uri = NULL;
1804 title = NULL;
1807 return (0);
1811 save_global_history_to_disk(struct tab *t)
1813 char file[PATH_MAX];
1814 FILE *f;
1815 struct history *h;
1817 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
1819 if ((f = fopen(file, "w")) == NULL) {
1820 show_oops(t, "%s: global history file: %s",
1821 __func__, strerror(errno));
1822 return (1);
1825 RB_FOREACH_REVERSE(h, history_list, &hl) {
1826 if (h->uri && h->title)
1827 fprintf(f, "%s\n%s\n", h->uri, h->title);
1830 fclose(f);
1832 return (0);
1836 quit(struct tab *t, struct karg *args)
1838 if (save_global_history)
1839 save_global_history_to_disk(t);
1841 gtk_main_quit();
1843 return (1);
1847 open_tabs(struct tab *t, struct karg *a)
1849 char file[PATH_MAX];
1850 FILE *f = NULL;
1851 char *uri = NULL;
1852 int rv = 1;
1853 struct tab *ti, *tt;
1855 if (a == NULL)
1856 goto done;
1858 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
1859 if ((f = fopen(file, "r")) == NULL)
1860 goto done;
1862 ti = TAILQ_LAST(&tabs, tab_list);
1864 for (;;) {
1865 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1866 if (feof(f) || ferror(f))
1867 break;
1869 /* retrieve session name */
1870 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
1871 strlcpy(named_session,
1872 &uri[strlen(XT_SAVE_SESSION_ID)],
1873 sizeof named_session);
1874 continue;
1877 if (uri && strlen(uri))
1878 create_new_tab(uri, NULL, 1);
1880 free(uri);
1881 uri = NULL;
1884 /* close open tabs */
1885 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
1886 for (;;) {
1887 tt = TAILQ_FIRST(&tabs);
1888 if (tt != ti) {
1889 delete_tab(tt);
1890 continue;
1892 delete_tab(tt);
1893 break;
1897 rv = 0;
1898 done:
1899 if (f)
1900 fclose(f);
1902 return (rv);
1906 restore_saved_tabs(void)
1908 char file[PATH_MAX];
1909 int unlink_file = 0;
1910 struct stat sb;
1911 struct karg a;
1912 int rv = 0;
1914 snprintf(file, sizeof file, "%s/%s",
1915 sessions_dir, XT_RESTART_TABS_FILE);
1916 if (stat(file, &sb) == -1)
1917 a.s = XT_SAVED_TABS_FILE;
1918 else {
1919 unlink_file = 1;
1920 a.s = XT_RESTART_TABS_FILE;
1923 a.i = XT_SES_DONOTHING;
1924 rv = open_tabs(NULL, &a);
1926 if (unlink_file)
1927 unlink(file);
1929 return (rv);
1933 save_tabs(struct tab *t, struct karg *a)
1935 char file[PATH_MAX];
1936 FILE *f;
1937 struct tab *ti;
1938 const gchar *uri;
1939 int len = 0, i;
1940 const gchar **arr = NULL;
1942 if (a == NULL)
1943 return (1);
1944 if (a->s == NULL)
1945 snprintf(file, sizeof file, "%s/%s",
1946 sessions_dir, named_session);
1947 else
1948 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
1950 if ((f = fopen(file, "w")) == NULL) {
1951 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
1952 return (1);
1955 /* save session name */
1956 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
1958 /* save tabs, in the order they are arranged in the notebook */
1959 TAILQ_FOREACH(ti, &tabs, entry)
1960 len++;
1962 arr = g_malloc0(len * sizeof(gchar *));
1964 TAILQ_FOREACH(ti, &tabs, entry) {
1965 if ((uri = get_uri(ti->wv)) != NULL)
1966 arr[gtk_notebook_page_num(notebook, ti->vbox)] = uri;
1969 for (i = 0; i < len; i++)
1970 if (arr[i])
1971 fprintf(f, "%s\n", arr[i]);
1973 g_free(arr);
1974 fclose(f);
1976 return (0);
1980 save_tabs_and_quit(struct tab *t, struct karg *args)
1982 struct karg a;
1984 a.s = NULL;
1985 save_tabs(t, &a);
1986 quit(t, NULL);
1988 return (1);
1992 yank_uri(struct tab *t, struct karg *args)
1994 const gchar *uri;
1995 GtkClipboard *clipboard;
1997 if ((uri = get_uri(t->wv)) == NULL)
1998 return (1);
2000 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2001 gtk_clipboard_set_text(clipboard, uri, -1);
2003 return (0);
2006 struct paste_args {
2007 struct tab *t;
2008 int i;
2011 void
2012 paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
2014 struct paste_args *pap;
2016 if (data == NULL || text == NULL || !strlen(text))
2017 return;
2019 pap = (struct paste_args *)data;
2021 switch(pap->i) {
2022 case XT_PASTE_CURRENT_TAB:
2023 load_uri(pap->t, (gchar *)text);
2024 break;
2025 case XT_PASTE_NEW_TAB:
2026 create_new_tab((gchar *)text, NULL, 1);
2027 break;
2030 g_free(pap);
2034 paste_uri(struct tab *t, struct karg *args)
2036 GtkClipboard *clipboard;
2037 struct paste_args *pap;
2039 pap = g_malloc(sizeof(struct paste_args));
2041 pap->t = t;
2042 pap->i = args->i;
2044 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2045 gtk_clipboard_request_text(clipboard, paste_uri_cb, pap);
2047 return (0);
2050 char *
2051 find_domain(const gchar *s, int add_dot)
2053 int i;
2054 char *r = NULL, *ss = NULL;
2056 if (s == NULL)
2057 return (NULL);
2059 if (!strncmp(s, "http://", strlen("http://")))
2060 s = &s[strlen("http://")];
2061 else if (!strncmp(s, "https://", strlen("https://")))
2062 s = &s[strlen("https://")];
2064 if (strlen(s) < 2)
2065 return (NULL);
2067 ss = g_strdup(s);
2068 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2069 /* chop string at first slash */
2070 if (ss[i] == '/' || ss[i] == '\0') {
2071 ss[i] = '\0';
2072 if (add_dot)
2073 r = g_strdup_printf(".%s", ss);
2074 else
2075 r = g_strdup(ss);
2076 break;
2078 g_free(ss);
2080 return (r);
2084 toggle_cwl(struct tab *t, struct karg *args)
2086 struct domain *d;
2087 const gchar *uri;
2088 char *dom = NULL, *dom_toggle = NULL;
2089 int es;
2091 if (args == NULL)
2092 return (1);
2094 if ((uri = get_uri(t->wv)) == NULL)
2095 return (1);
2097 dom = find_domain(uri, 1);
2098 d = wl_find(dom, &c_wl);
2099 if (d == NULL)
2100 es = 0;
2101 else
2102 es = 1;
2104 if (args->i & XT_WL_TOGGLE)
2105 es = !es;
2106 else if ((args->i & XT_WL_ENABLE) && es != 1)
2107 es = 1;
2108 else if ((args->i & XT_WL_DISABLE) && es != 0)
2109 es = 0;
2111 if (args->i & XT_WL_TOPLEVEL)
2112 dom_toggle = get_toplevel_domain(dom);
2113 else
2114 dom_toggle = dom;
2116 if (es)
2117 /* enable cookies for domain */
2118 wl_add(dom_toggle, &c_wl, 0);
2119 else
2120 /* disable cookies for domain */
2121 RB_REMOVE(domain_list, &c_wl, d);
2123 webkit_web_view_reload(t->wv);
2125 g_free(dom);
2126 return (0);
2130 toggle_js(struct tab *t, struct karg *args)
2132 int es;
2133 const gchar *uri;
2134 struct domain *d;
2135 char *dom = NULL, *dom_toggle = NULL;
2137 if (args == NULL)
2138 return (1);
2140 g_object_get(G_OBJECT(t->settings),
2141 "enable-scripts", &es, (char *)NULL);
2142 if (args->i & XT_WL_TOGGLE)
2143 es = !es;
2144 else if ((args->i & XT_WL_ENABLE) && es != 1)
2145 es = 1;
2146 else if ((args->i & XT_WL_DISABLE) && es != 0)
2147 es = 0;
2148 else
2149 return (1);
2151 if ((uri = get_uri(t->wv)) == NULL)
2152 return (1);
2154 dom = find_domain(uri, 1);
2155 if (uri == NULL || dom == NULL) {
2156 show_oops(t, "Can't toggle domain in JavaScript white list");
2157 goto done;
2160 if (args->i & XT_WL_TOPLEVEL)
2161 dom_toggle = get_toplevel_domain(dom);
2162 else
2163 dom_toggle = dom;
2165 if (es) {
2166 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2167 wl_add(dom_toggle, &js_wl, 0 /* session */);
2168 } else {
2169 d = wl_find(dom_toggle, &js_wl);
2170 if (d)
2171 RB_REMOVE(domain_list, &js_wl, d);
2172 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2174 g_object_set(G_OBJECT(t->settings),
2175 "enable-scripts", es, (char *)NULL);
2176 webkit_web_view_set_settings(t->wv, t->settings);
2177 webkit_web_view_reload(t->wv);
2178 done:
2179 if (dom)
2180 g_free(dom);
2181 return (0);
2184 void
2185 js_toggle_cb(GtkWidget *w, struct tab *t)
2187 struct karg a;
2189 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
2190 toggle_js(t, &a);
2194 toggle_src(struct tab *t, struct karg *args)
2196 gboolean mode;
2198 if (t == NULL)
2199 return (0);
2201 mode = webkit_web_view_get_view_source_mode(t->wv);
2202 webkit_web_view_set_view_source_mode(t->wv, !mode);
2203 webkit_web_view_reload(t->wv);
2205 return (0);
2208 void
2209 focus_webview(struct tab *t)
2211 if (t == NULL)
2212 return;
2214 /* only grab focus if we are visible */
2215 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2216 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2220 focus(struct tab *t, struct karg *args)
2222 if (t == NULL || args == NULL)
2223 return (1);
2225 if (show_url == 0)
2226 return (0);
2228 if (args->i == XT_FOCUS_URI)
2229 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2230 else if (args->i == XT_FOCUS_SEARCH)
2231 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2233 return (0);
2237 stats(struct tab *t, struct karg *args)
2239 char *stats, *s, line[64 * 1024];
2240 uint64_t line_count = 0;
2241 FILE *r_cookie_f;
2243 if (t == NULL)
2244 show_oops_s("stats invalid parameters");
2246 line[0] = '\0';
2247 if (save_rejected_cookies) {
2248 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2249 for (;;) {
2250 s = fgets(line, sizeof line, r_cookie_f);
2251 if (s == NULL || feof(r_cookie_f) ||
2252 ferror(r_cookie_f))
2253 break;
2254 line_count++;
2256 fclose(r_cookie_f);
2257 snprintf(line, sizeof line,
2258 "<br>Cookies blocked(*) total: %llu", line_count);
2259 } else
2260 show_oops(t, "Can't open blocked cookies file: %s",
2261 strerror(errno));
2264 stats = g_strdup_printf(XT_DOCTYPE
2265 "<html>"
2266 "<head>"
2267 "<title>Statistics</title>"
2268 "</head>"
2269 "<h1>Statistics</h1>"
2270 "<body>"
2271 "Cookies blocked(*) this session: %llu"
2272 "%s"
2273 "<p><small><b>*</b> results vary based on settings"
2274 "</body>"
2275 "</html>",
2276 blocked_cookies,
2277 line);
2279 load_webkit_string(t, stats);
2280 g_free(stats);
2282 return (0);
2286 about(struct tab *t, struct karg *args)
2288 char *about;
2290 if (t == NULL)
2291 show_oops_s("about invalid parameters");
2293 about = g_strdup_printf(XT_DOCTYPE
2294 "<html>"
2295 "<head>"
2296 "<title>About</title>"
2297 "</head>"
2298 "<h1>About</h1>"
2299 "<body>"
2300 "<b>Version: %s</b><p>"
2301 "Authors:"
2302 "<ul>"
2303 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2304 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2305 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2306 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2307 "</ul>"
2308 "Copyrights and licenses can be found on the XXXterm "
2309 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
2310 "</body>"
2311 "</html>",
2312 version
2315 load_webkit_string(t, about);
2316 g_free(about);
2318 return (0);
2322 help(struct tab *t, struct karg *args)
2324 char *help;
2326 if (t == NULL)
2327 show_oops_s("help invalid parameters");
2329 help = XT_DOCTYPE
2330 "<html>"
2331 "<head>"
2332 "<title>XXXterm</title>"
2333 "<meta http-equiv=\"REFRESH\" content=\"0;"
2334 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2335 "</head>"
2336 "<body>"
2337 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2338 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2339 "cgi-bin/man-cgi?xxxterm</a>"
2340 "</body>"
2341 "</html>"
2344 load_webkit_string(t, help);
2346 return (0);
2350 * update all favorite tabs apart from one. Pass NULL if
2351 * you want to update all.
2353 void
2354 update_favorite_tabs(struct tab *apart_from)
2356 struct tab *t;
2357 if (!updating_fl_tabs) {
2358 updating_fl_tabs = 1; /* stop infinite recursion */
2359 TAILQ_FOREACH(t, &tabs, entry)
2360 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2361 && (t != apart_from))
2362 xtp_page_fl(t, NULL);
2363 updating_fl_tabs = 0;
2367 /* show a list of favorites (bookmarks) */
2369 xtp_page_fl(struct tab *t, struct karg *args)
2371 char file[PATH_MAX];
2372 FILE *f;
2373 char *uri = NULL, *title = NULL;
2374 size_t len, lineno = 0;
2375 int i, failed = 0;
2376 char *header, *body, *tmp, *html = NULL;
2378 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2380 if (t == NULL)
2381 warn("%s: bad param", __func__);
2383 /* mark tab as favorite list */
2384 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2386 /* new session key */
2387 if (!updating_fl_tabs)
2388 generate_xtp_session_key(&fl_session_key);
2390 /* open favorites */
2391 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2392 if ((f = fopen(file, "r")) == NULL) {
2393 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2394 return (1);
2397 /* header */
2398 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
2399 "<title>Favorites</title>\n"
2400 "%s"
2401 "</head>"
2402 "<h1>Favorites</h1>\n",
2403 XT_PAGE_STYLE);
2405 /* body */
2406 body = g_strdup_printf("<div align='center'><table><tr>"
2407 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
2408 "<th style='width: 15%%'>Remove</th></tr>\n");
2410 for (i = 1;;) {
2411 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2412 if (feof(f) || ferror(f))
2413 break;
2414 if (len == 0) {
2415 free(title);
2416 title = NULL;
2417 continue;
2420 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2421 if (feof(f) || ferror(f)) {
2422 show_oops(t, "favorites file corrupt");
2423 failed = 1;
2424 break;
2427 tmp = body;
2428 body = g_strdup_printf("%s<tr>"
2429 "<td>%d</td>"
2430 "<td><a href='%s'>%s</a></td>"
2431 "<td style='text-align: center'>"
2432 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2433 "</tr>\n",
2434 body, i, uri, title,
2435 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2437 g_free(tmp);
2439 free(uri);
2440 uri = NULL;
2441 free(title);
2442 title = NULL;
2443 i++;
2445 fclose(f);
2447 /* if none, say so */
2448 if (i == 1) {
2449 tmp = body;
2450 body = g_strdup_printf("%s<tr>"
2451 "<td colspan='3' style='text-align: center'>"
2452 "No favorites - To add one use the 'favadd' command."
2453 "</td></tr>", body);
2454 g_free(tmp);
2457 if (uri)
2458 free(uri);
2459 if (title)
2460 free(title);
2462 /* render */
2463 if (!failed) {
2464 html = g_strdup_printf("%s%s</table></div></html>",
2465 header, body);
2466 load_webkit_string(t, html);
2469 update_favorite_tabs(t);
2471 if (header)
2472 g_free(header);
2473 if (body)
2474 g_free(body);
2475 if (html)
2476 g_free(html);
2478 return (failed);
2481 char *
2482 getparams(char *cmd, char *cmp)
2484 char *rv = NULL;
2486 if (cmd && cmp) {
2487 if (!strncmp(cmd, cmp, strlen(cmp))) {
2488 rv = cmd + strlen(cmp);
2489 while (*rv == ' ')
2490 rv++;
2491 if (strlen(rv) == 0)
2492 rv = NULL;
2496 return (rv);
2499 void
2500 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2501 size_t cert_count, char *title)
2503 gnutls_datum_t cinfo;
2504 char *tmp, *header, *body, *footer;
2505 int i;
2507 header = g_strdup_printf("<title>%s</title><html><body>", title);
2508 footer = g_strdup("</body></html>");
2509 body = g_strdup("");
2511 for (i = 0; i < cert_count; i++) {
2512 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2513 &cinfo))
2514 return;
2516 tmp = body;
2517 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2518 body, i, cinfo.data);
2519 gnutls_free(cinfo.data);
2520 g_free(tmp);
2523 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2524 g_free(header);
2525 g_free(body);
2526 g_free(footer);
2527 load_webkit_string(t, tmp);
2528 g_free(tmp);
2532 ca_cmd(struct tab *t, struct karg *args)
2534 FILE *f = NULL;
2535 int rv = 1, certs = 0, certs_read;
2536 struct stat sb;
2537 gnutls_datum dt;
2538 gnutls_x509_crt_t *c = NULL;
2539 char *certs_buf = NULL, *s;
2541 /* yeah yeah stat race */
2542 if (stat(ssl_ca_file, &sb)) {
2543 show_oops(t, "no CA file: %s", ssl_ca_file);
2544 goto done;
2547 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2548 show_oops(t, "Can't open CA file: %s", strerror(errno));
2549 return (1);
2552 certs_buf = g_malloc(sb.st_size + 1);
2553 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2554 show_oops(t, "Can't read CA file: %s", strerror(errno));
2555 goto done;
2557 certs_buf[sb.st_size] = '\0';
2559 s = certs_buf;
2560 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2561 certs++;
2562 s += strlen("BEGIN CERTIFICATE");
2565 bzero(&dt, sizeof dt);
2566 dt.data = certs_buf;
2567 dt.size = sb.st_size;
2568 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2569 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
2570 GNUTLS_X509_FMT_PEM, 0);
2571 if (certs_read <= 0) {
2572 show_oops(t, "No cert(s) available");
2573 goto done;
2575 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2576 done:
2577 if (c)
2578 g_free(c);
2579 if (certs_buf)
2580 g_free(certs_buf);
2581 if (f)
2582 fclose(f);
2584 return (rv);
2588 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2590 SoupURI *su = NULL;
2591 struct addrinfo hints, *res = NULL, *ai;
2592 int s = -1, on;
2593 char port[8];
2595 if (uri && !g_str_has_prefix(uri, "https://"))
2596 goto done;
2598 su = soup_uri_new(uri);
2599 if (su == NULL)
2600 goto done;
2601 if (!SOUP_URI_VALID_FOR_HTTP(su))
2602 goto done;
2604 snprintf(port, sizeof port, "%d", su->port);
2605 bzero(&hints, sizeof(struct addrinfo));
2606 hints.ai_flags = AI_CANONNAME;
2607 hints.ai_family = AF_UNSPEC;
2608 hints.ai_socktype = SOCK_STREAM;
2610 if (getaddrinfo(su->host, port, &hints, &res))
2611 goto done;
2613 for (ai = res; ai; ai = ai->ai_next) {
2614 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2615 continue;
2617 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2618 if (s < 0)
2619 goto done;
2620 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2621 sizeof(on)) == -1)
2622 goto done;
2624 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2625 goto done;
2628 if (domain)
2629 strlcpy(domain, su->host, domain_sz);
2630 done:
2631 if (su)
2632 soup_uri_free(su);
2633 if (res)
2634 freeaddrinfo(res);
2636 return (s);
2640 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2642 if (gsession)
2643 gnutls_deinit(gsession);
2644 if (xcred)
2645 gnutls_certificate_free_credentials(xcred);
2647 return (0);
2651 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2652 gnutls_certificate_credentials_t *xc)
2654 gnutls_certificate_credentials_t xcred;
2655 gnutls_session_t gsession;
2656 int rv = 1;
2658 if (gs == NULL || xc == NULL)
2659 goto done;
2661 *gs = NULL;
2662 *xc = NULL;
2664 gnutls_certificate_allocate_credentials(&xcred);
2665 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2666 GNUTLS_X509_FMT_PEM);
2667 gnutls_init(&gsession, GNUTLS_CLIENT);
2668 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2669 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2670 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2671 if ((rv = gnutls_handshake(gsession)) < 0) {
2672 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2674 gnutls_error_is_fatal(rv),
2675 gnutls_strerror_name(rv));
2676 stop_tls(gsession, xcred);
2677 goto done;
2680 gnutls_credentials_type_t cred;
2681 cred = gnutls_auth_get_type(gsession);
2682 if (cred != GNUTLS_CRD_CERTIFICATE) {
2683 stop_tls(gsession, xcred);
2684 goto done;
2687 *gs = gsession;
2688 *xc = xcred;
2689 rv = 0;
2690 done:
2691 return (rv);
2695 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2696 size_t *cert_count)
2698 unsigned int len;
2699 const gnutls_datum_t *cl;
2700 gnutls_x509_crt_t *all_certs;
2701 int i, rv = 1;
2703 if (certs == NULL || cert_count == NULL)
2704 goto done;
2705 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2706 goto done;
2707 cl = gnutls_certificate_get_peers(gsession, &len);
2708 if (len == 0)
2709 goto done;
2711 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2712 for (i = 0; i < len; i++) {
2713 gnutls_x509_crt_init(&all_certs[i]);
2714 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2715 GNUTLS_X509_FMT_PEM < 0)) {
2716 g_free(all_certs);
2717 goto done;
2721 *certs = all_certs;
2722 *cert_count = len;
2723 rv = 0;
2724 done:
2725 return (rv);
2728 void
2729 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2731 int i;
2733 for (i = 0; i < cert_count; i++)
2734 gnutls_x509_crt_deinit(certs[i]);
2735 g_free(certs);
2738 void
2739 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2740 size_t cert_count, char *domain)
2742 size_t cert_buf_sz;
2743 char cert_buf[64 * 1024], file[PATH_MAX];
2744 int i;
2745 FILE *f;
2746 GdkColor color;
2748 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2749 return;
2751 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2752 if ((f = fopen(file, "w")) == NULL) {
2753 show_oops(t, "Can't create cert file %s %s",
2754 file, strerror(errno));
2755 return;
2758 for (i = 0; i < cert_count; i++) {
2759 cert_buf_sz = sizeof cert_buf;
2760 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2761 cert_buf, &cert_buf_sz)) {
2762 show_oops(t, "gnutls_x509_crt_export failed");
2763 goto done;
2765 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
2766 show_oops(t, "Can't write certs: %s", strerror(errno));
2767 goto done;
2771 /* not the best spot but oh well */
2772 gdk_color_parse("lightblue", &color);
2773 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
2774 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
2775 gdk_color_parse("black", &color);
2776 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
2777 done:
2778 fclose(f);
2782 load_compare_cert(struct tab *t, struct karg *args)
2784 const gchar *uri;
2785 char domain[8182], file[PATH_MAX];
2786 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
2787 int s = -1, rv = 1, i;
2788 size_t cert_count;
2789 FILE *f = NULL;
2790 size_t cert_buf_sz;
2791 gnutls_session_t gsession;
2792 gnutls_x509_crt_t *certs;
2793 gnutls_certificate_credentials_t xcred;
2795 if (t == NULL)
2796 return (1);
2798 if ((uri = get_uri(t->wv)) == NULL)
2799 return (1);
2801 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
2802 return (1);
2804 /* go ssl/tls */
2805 if (start_tls(t, s, &gsession, &xcred)) {
2806 show_oops(t, "Start TLS failed");
2807 goto done;
2810 /* get certs */
2811 if (get_connection_certs(gsession, &certs, &cert_count)) {
2812 show_oops(t, "Can't get connection certificates");
2813 goto done;
2816 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2817 if ((f = fopen(file, "r")) == NULL)
2818 goto freeit;
2820 for (i = 0; i < cert_count; i++) {
2821 cert_buf_sz = sizeof cert_buf;
2822 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2823 cert_buf, &cert_buf_sz)) {
2824 goto freeit;
2826 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
2827 rv = -1; /* critical */
2828 goto freeit;
2830 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
2831 rv = -1; /* critical */
2832 goto freeit;
2836 rv = 0;
2837 freeit:
2838 if (f)
2839 fclose(f);
2840 free_connection_certs(certs, cert_count);
2841 done:
2842 /* we close the socket first for speed */
2843 if (s != -1)
2844 close(s);
2845 stop_tls(gsession, xcred);
2847 return (rv);
2851 cert_cmd(struct tab *t, struct karg *args)
2853 const gchar *uri;
2854 char *action, domain[8182];
2855 int s = -1;
2856 size_t cert_count;
2857 gnutls_session_t gsession;
2858 gnutls_x509_crt_t *certs;
2859 gnutls_certificate_credentials_t xcred;
2861 if (t == NULL)
2862 return (1);
2864 if ((action = getparams(args->s, "cert")))
2866 else
2867 action = "show";
2869 if ((uri = get_uri(t->wv)) == NULL) {
2870 show_oops(t, "Invalid URI");
2871 return (1);
2874 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
2875 show_oops(t, "Invalid certidicate URI: %s", uri);
2876 return (1);
2879 /* go ssl/tls */
2880 if (start_tls(t, s, &gsession, &xcred)) {
2881 show_oops(t, "Start TLS failed");
2882 goto done;
2885 /* get certs */
2886 if (get_connection_certs(gsession, &certs, &cert_count)) {
2887 show_oops(t, "get_connection_certs failed");
2888 goto done;
2891 if (!strcmp(action, "show"))
2892 show_certs(t, certs, cert_count, "Certificate Chain");
2893 else if (!strcmp(action, "save"))
2894 save_certs(t, certs, cert_count, domain);
2895 else
2896 show_oops(t, "Invalid command: %s", action);
2898 free_connection_certs(certs, cert_count);
2899 done:
2900 /* we close the socket first for speed */
2901 if (s != -1)
2902 close(s);
2903 stop_tls(gsession, xcred);
2905 return (0);
2909 remove_cookie(int index)
2911 int i, rv = 1;
2912 GSList *cf;
2913 SoupCookie *c;
2915 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
2917 cf = soup_cookie_jar_all_cookies(s_cookiejar);
2919 for (i = 1; cf; cf = cf->next, i++) {
2920 if (i != index)
2921 continue;
2922 c = cf->data;
2923 print_cookie("remove cookie", c);
2924 soup_cookie_jar_delete_cookie(s_cookiejar, c);
2925 rv = 0;
2926 break;
2929 soup_cookies_free(cf);
2931 return (rv);
2935 wl_show(struct tab *t, char *args, char *title, struct domain_list *wl)
2937 struct domain *d;
2938 char *tmp, *header, *body, *footer;
2939 int p_js = 0, s_js = 0;
2941 /* we set this to indicate we want to manually do navaction */
2942 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
2944 if (g_str_has_prefix(args, "show a") ||
2945 !strcmp(args, "show")) {
2946 /* show all */
2947 p_js = 1;
2948 s_js = 1;
2949 } else if (g_str_has_prefix(args, "show p")) {
2950 /* show persistent */
2951 p_js = 1;
2952 } else if (g_str_has_prefix(args, "show s")) {
2953 /* show session */
2954 s_js = 1;
2955 } else
2956 return (1);
2958 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
2959 title, title);
2960 footer = g_strdup("</body></html>");
2961 body = g_strdup("");
2963 /* p list */
2964 if (p_js) {
2965 tmp = body;
2966 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
2967 g_free(tmp);
2968 RB_FOREACH(d, domain_list, wl) {
2969 if (d->handy == 0)
2970 continue;
2971 tmp = body;
2972 body = g_strdup_printf("%s%s<br>", body, d->d);
2973 g_free(tmp);
2977 /* s list */
2978 if (s_js) {
2979 tmp = body;
2980 body = g_strdup_printf("%s<h2>Session</h2>", body);
2981 g_free(tmp);
2982 RB_FOREACH(d, domain_list, wl) {
2983 if (d->handy == 1)
2984 continue;
2985 tmp = body;
2986 body = g_strdup_printf("%s%s<br>", body, d->d);
2987 g_free(tmp);
2991 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2992 g_free(header);
2993 g_free(body);
2994 g_free(footer);
2995 load_webkit_string(t, tmp);
2996 g_free(tmp);
2997 return (0);
3001 wl_save(struct tab *t, struct karg *args, int js)
3003 char file[PATH_MAX];
3004 FILE *f;
3005 char *line = NULL, *lt = NULL;
3006 size_t linelen;
3007 const gchar *uri;
3008 char *dom = NULL, *dom_save = NULL;
3009 struct karg a;
3010 struct domain *d;
3011 GSList *cf;
3012 SoupCookie *ci, *c;
3013 int flags;
3015 if (t == NULL || args == NULL)
3016 return (1);
3018 if (runtime_settings[0] == '\0')
3019 return (1);
3021 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3022 if ((f = fopen(file, "r+")) == NULL)
3023 return (1);
3025 uri = get_uri(t->wv);
3026 dom = find_domain(uri, 1);
3027 if (uri == NULL || dom == NULL) {
3028 show_oops(t, "Can't add domain to %s white list",
3029 js ? "JavaScript" : "cookie");
3030 goto done;
3033 if (g_str_has_prefix(args->s, "save d")) {
3034 /* save domain */
3035 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3036 show_oops(t, "invalid domain: %s", dom);
3037 goto done;
3039 flags = XT_WL_TOPLEVEL;
3040 } else if (g_str_has_prefix(args->s, "save f") ||
3041 !strcmp(args->s, "save")) {
3042 /* save fqdn */
3043 dom_save = dom;
3044 flags = XT_WL_FQDN;
3045 } else {
3046 show_oops(t, "invalid command: %s", args->s);
3047 goto done;
3050 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3052 while (!feof(f)) {
3053 line = fparseln(f, &linelen, NULL, NULL, 0);
3054 if (line == NULL)
3055 continue;
3056 if (!strcmp(line, lt))
3057 goto done;
3058 free(line);
3059 line = NULL;
3062 fprintf(f, "%s\n", lt);
3064 a.i = XT_WL_ENABLE;
3065 a.i |= flags;
3066 if (js) {
3067 d = wl_find(dom_save, &js_wl);
3068 toggle_js(t, &a);
3069 } else {
3070 d = wl_find(dom_save, &c_wl);
3071 toggle_cwl(t, &a);
3073 /* find and add to persistent jar */
3074 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3075 for (;cf; cf = cf->next) {
3076 ci = cf->data;
3077 if (!strcmp(dom_save, ci->domain) ||
3078 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3079 c = soup_cookie_copy(ci);
3080 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3083 soup_cookies_free(cf);
3085 if (d)
3086 d->handy = 1;
3088 done:
3089 if (line)
3090 free(line);
3091 if (dom)
3092 g_free(dom);
3093 if (lt)
3094 g_free(lt);
3095 fclose(f);
3097 return (0);
3101 cookie_cmd(struct tab *t, struct karg *args)
3103 char *cmd;
3104 struct karg a;
3106 if ((cmd = getparams(args->s, "cookie")))
3108 else
3109 cmd = "show all";
3112 if (g_str_has_prefix(cmd, "show")) {
3113 wl_show(t, cmd, "Cookie White List", &c_wl);
3114 } else if (g_str_has_prefix(cmd, "save")) {
3115 a.s = cmd;
3116 wl_save(t, &a, 0);
3117 } else if (g_str_has_prefix(cmd, "toggle")) {
3118 a.i = XT_WL_TOGGLE;
3119 if (g_str_has_prefix(cmd, "toggle d"))
3120 a.i |= XT_WL_TOPLEVEL;
3121 else
3122 a.i |= XT_WL_FQDN;
3123 toggle_cwl(t, &a);
3124 } else if (g_str_has_prefix(cmd, "delete")) {
3125 show_oops(t, "'cookie delete' currently unimplemented");
3126 } else
3127 show_oops(t, "unknown cookie command: %s", cmd);
3129 return (0);
3133 js_cmd(struct tab *t, struct karg *args)
3135 char *cmd;
3136 struct karg a;
3138 if ((cmd = getparams(args->s, "js")))
3140 else
3141 cmd = "show all";
3143 if (g_str_has_prefix(cmd, "show")) {
3144 wl_show(t, cmd, "JavaScript White List", &js_wl);
3145 } else if (g_str_has_prefix(cmd, "save")) {
3146 a.s = cmd;
3147 wl_save(t, &a, 1);
3148 } else if (g_str_has_prefix(cmd, "toggle")) {
3149 a.i = XT_WL_TOGGLE;
3150 if (g_str_has_prefix(cmd, "toggle d"))
3151 a.i |= XT_WL_TOPLEVEL;
3152 else
3153 a.i |= XT_WL_FQDN;
3154 toggle_js(t, &a);
3155 } else if (g_str_has_prefix(cmd, "delete")) {
3156 show_oops(t, "'js delete' currently unimplemented");
3157 } else
3158 show_oops(t, "unknown js command: %s", cmd);
3160 return (0);
3164 add_favorite(struct tab *t, struct karg *args)
3166 char file[PATH_MAX];
3167 FILE *f;
3168 char *line = NULL;
3169 size_t urilen, linelen;
3170 const gchar *uri, *title;
3172 if (t == NULL)
3173 return (1);
3175 /* don't allow adding of xtp pages to favorites */
3176 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3177 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3178 return (1);
3181 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3182 if ((f = fopen(file, "r+")) == NULL) {
3183 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3184 return (1);
3187 title = webkit_web_view_get_title(t->wv);
3188 uri = get_uri(t->wv);
3190 if (title == NULL)
3191 title = uri;
3193 if (title == NULL || uri == NULL) {
3194 show_oops(t, "can't add page to favorites");
3195 goto done;
3198 urilen = strlen(uri);
3200 for (;;) {
3201 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3202 if (feof(f) || ferror(f))
3203 break;
3205 if (linelen == urilen && !strcmp(line, uri))
3206 goto done;
3208 free(line);
3209 line = NULL;
3212 fprintf(f, "\n%s\n%s", title, uri);
3213 done:
3214 if (line)
3215 free(line);
3216 fclose(f);
3218 update_favorite_tabs(NULL);
3220 return (0);
3224 navaction(struct tab *t, struct karg *args)
3226 WebKitWebHistoryItem *item;
3228 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3229 t->tab_id, args->i);
3231 if (t->item) {
3232 if (args->i == XT_NAV_BACK)
3233 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3234 else
3235 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3236 if (item == NULL)
3237 return (XT_CB_PASSTHROUGH);
3238 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3239 t->item = NULL;
3240 return (XT_CB_PASSTHROUGH);
3243 switch (args->i) {
3244 case XT_NAV_BACK:
3245 webkit_web_view_go_back(t->wv);
3246 break;
3247 case XT_NAV_FORWARD:
3248 webkit_web_view_go_forward(t->wv);
3249 break;
3250 case XT_NAV_RELOAD:
3251 webkit_web_view_reload(t->wv);
3252 break;
3253 case XT_NAV_RELOAD_CACHE:
3254 webkit_web_view_reload_bypass_cache(t->wv);
3255 break;
3257 return (XT_CB_PASSTHROUGH);
3261 move(struct tab *t, struct karg *args)
3263 GtkAdjustment *adjust;
3264 double pi, si, pos, ps, upper, lower, max;
3266 switch (args->i) {
3267 case XT_MOVE_DOWN:
3268 case XT_MOVE_UP:
3269 case XT_MOVE_BOTTOM:
3270 case XT_MOVE_TOP:
3271 case XT_MOVE_PAGEDOWN:
3272 case XT_MOVE_PAGEUP:
3273 case XT_MOVE_HALFDOWN:
3274 case XT_MOVE_HALFUP:
3275 adjust = t->adjust_v;
3276 break;
3277 default:
3278 adjust = t->adjust_h;
3279 break;
3282 pos = gtk_adjustment_get_value(adjust);
3283 ps = gtk_adjustment_get_page_size(adjust);
3284 upper = gtk_adjustment_get_upper(adjust);
3285 lower = gtk_adjustment_get_lower(adjust);
3286 si = gtk_adjustment_get_step_increment(adjust);
3287 pi = gtk_adjustment_get_page_increment(adjust);
3288 max = upper - ps;
3290 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3291 "max %f si %f pi %f\n",
3292 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3293 pos, ps, upper, lower, max, si, pi);
3295 switch (args->i) {
3296 case XT_MOVE_DOWN:
3297 case XT_MOVE_RIGHT:
3298 pos += si;
3299 gtk_adjustment_set_value(adjust, MIN(pos, max));
3300 break;
3301 case XT_MOVE_UP:
3302 case XT_MOVE_LEFT:
3303 pos -= si;
3304 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3305 break;
3306 case XT_MOVE_BOTTOM:
3307 case XT_MOVE_FARRIGHT:
3308 gtk_adjustment_set_value(adjust, max);
3309 break;
3310 case XT_MOVE_TOP:
3311 case XT_MOVE_FARLEFT:
3312 gtk_adjustment_set_value(adjust, lower);
3313 break;
3314 case XT_MOVE_PAGEDOWN:
3315 pos += pi;
3316 gtk_adjustment_set_value(adjust, MIN(pos, max));
3317 break;
3318 case XT_MOVE_PAGEUP:
3319 pos -= pi;
3320 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3321 break;
3322 case XT_MOVE_HALFDOWN:
3323 pos += pi / 2;
3324 gtk_adjustment_set_value(adjust, MIN(pos, max));
3325 break;
3326 case XT_MOVE_HALFUP:
3327 pos -= pi / 2;
3328 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3329 break;
3330 default:
3331 return (XT_CB_PASSTHROUGH);
3334 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3336 return (XT_CB_HANDLED);
3339 void
3340 url_set_visibility(void)
3342 struct tab *t;
3344 TAILQ_FOREACH(t, &tabs, entry) {
3345 if (show_url == 0) {
3346 gtk_widget_hide(t->toolbar);
3347 focus_webview(t);
3348 } else
3349 gtk_widget_show(t->toolbar);
3353 void
3354 notebook_tab_set_visibility(GtkNotebook *notebook)
3356 if (show_tabs == 0)
3357 gtk_notebook_set_show_tabs(notebook, FALSE);
3358 else
3359 gtk_notebook_set_show_tabs(notebook, TRUE);
3362 void
3363 statusbar_set_visibility(void)
3365 struct tab *t;
3367 TAILQ_FOREACH(t, &tabs, entry) {
3368 if (show_statusbar == 0) {
3369 gtk_widget_hide(t->statusbar);
3370 focus_webview(t);
3371 } else
3372 gtk_widget_show(t->statusbar);
3376 void
3377 url_set(struct tab *t, int enable_url_entry)
3379 GdkPixbuf *pixbuf;
3380 int progress;
3382 show_url = enable_url_entry;
3384 if (enable_url_entry) {
3385 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3386 GTK_ENTRY_ICON_PRIMARY, NULL);
3387 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3388 } else {
3389 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3390 GTK_ENTRY_ICON_PRIMARY);
3391 progress =
3392 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3393 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3394 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3395 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3396 progress);
3401 fullscreen(struct tab *t, struct karg *args)
3403 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3405 if (t == NULL)
3406 return (XT_CB_PASSTHROUGH);
3408 if (show_url == 0) {
3409 url_set(t, 1);
3410 show_tabs = 1;
3411 } else {
3412 url_set(t, 0);
3413 show_tabs = 0;
3416 url_set_visibility();
3417 notebook_tab_set_visibility(notebook);
3419 return (XT_CB_HANDLED);
3423 statusaction(struct tab *t, struct karg *args)
3425 int rv = XT_CB_HANDLED;
3427 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3429 if (t == NULL)
3430 return (XT_CB_PASSTHROUGH);
3432 switch (args->i) {
3433 case XT_STATUSBAR_SHOW:
3434 if (show_statusbar == 0) {
3435 show_statusbar = 1;
3436 statusbar_set_visibility();
3438 break;
3439 case XT_STATUSBAR_HIDE:
3440 if (show_statusbar == 1) {
3441 show_statusbar = 0;
3442 statusbar_set_visibility();
3444 break;
3446 return (rv);
3450 urlaction(struct tab *t, struct karg *args)
3452 int rv = XT_CB_HANDLED;
3454 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3456 if (t == NULL)
3457 return (XT_CB_PASSTHROUGH);
3459 switch (args->i) {
3460 case XT_URL_SHOW:
3461 if (show_url == 0) {
3462 url_set(t, 1);
3463 url_set_visibility();
3465 break;
3466 case XT_URL_HIDE:
3467 if (show_url == 1) {
3468 url_set(t, 0);
3469 url_set_visibility();
3471 break;
3473 return (rv);
3477 tabaction(struct tab *t, struct karg *args)
3479 int rv = XT_CB_HANDLED;
3480 char *url = NULL;
3481 struct undo *u;
3483 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3485 if (t == NULL)
3486 return (XT_CB_PASSTHROUGH);
3488 switch (args->i) {
3489 case XT_TAB_NEW:
3490 if ((url = getparams(args->s, "tabnew")))
3491 create_new_tab(url, NULL, 1);
3492 else
3493 create_new_tab(NULL, NULL, 1);
3494 break;
3495 case XT_TAB_DELETE:
3496 delete_tab(t);
3497 break;
3498 case XT_TAB_DELQUIT:
3499 if (gtk_notebook_get_n_pages(notebook) > 1)
3500 delete_tab(t);
3501 else
3502 quit(t, args);
3503 break;
3504 case XT_TAB_OPEN:
3505 if ((url = getparams(args->s, "open")) ||
3506 ((url = getparams(args->s, "op"))) ||
3507 ((url = getparams(args->s, "o"))))
3509 else {
3510 rv = XT_CB_PASSTHROUGH;
3511 goto done;
3513 load_uri(t, url);
3514 break;
3515 case XT_TAB_SHOW:
3516 if (show_tabs == 0) {
3517 show_tabs = 1;
3518 notebook_tab_set_visibility(notebook);
3520 break;
3521 case XT_TAB_HIDE:
3522 if (show_tabs == 1) {
3523 show_tabs = 0;
3524 notebook_tab_set_visibility(notebook);
3526 break;
3527 case XT_TAB_UNDO_CLOSE:
3528 if (undo_count == 0) {
3529 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3530 goto done;
3531 } else {
3532 undo_count--;
3533 u = TAILQ_FIRST(&undos);
3534 create_new_tab(u->uri, u, 1);
3536 TAILQ_REMOVE(&undos, u, entry);
3537 g_free(u->uri);
3538 /* u->history is freed in create_new_tab() */
3539 g_free(u);
3541 break;
3542 default:
3543 rv = XT_CB_PASSTHROUGH;
3544 goto done;
3547 done:
3548 if (args->s) {
3549 g_free(args->s);
3550 args->s = NULL;
3553 return (rv);
3557 resizetab(struct tab *t, struct karg *args)
3559 if (t == NULL || args == NULL) {
3560 show_oops_s("resizetab invalid parameters");
3561 return (XT_CB_PASSTHROUGH);
3564 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3565 t->tab_id, args->i);
3567 adjustfont_webkit(t, args->i);
3569 return (XT_CB_HANDLED);
3573 movetab(struct tab *t, struct karg *args)
3575 struct tab *tt;
3576 int x;
3578 if (t == NULL || args == NULL) {
3579 show_oops_s("movetab invalid parameters");
3580 return (XT_CB_PASSTHROUGH);
3583 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3584 t->tab_id, args->i);
3586 if (args->i == XT_TAB_INVALID)
3587 return (XT_CB_PASSTHROUGH);
3589 if (args->i < XT_TAB_INVALID) {
3590 /* next or previous tab */
3591 if (TAILQ_EMPTY(&tabs))
3592 return (XT_CB_PASSTHROUGH);
3594 switch (args->i) {
3595 case XT_TAB_NEXT:
3596 /* if at the last page, loop around to the first */
3597 if (gtk_notebook_get_current_page(notebook) ==
3598 gtk_notebook_get_n_pages(notebook) - 1)
3599 gtk_notebook_set_current_page(notebook, 0);
3600 else
3601 gtk_notebook_next_page(notebook);
3602 break;
3603 case XT_TAB_PREV:
3604 /* if at the first page, loop around to the last */
3605 if (gtk_notebook_current_page(notebook) == 0)
3606 gtk_notebook_set_current_page(notebook,
3607 gtk_notebook_get_n_pages(notebook) - 1);
3608 else
3609 gtk_notebook_prev_page(notebook);
3610 break;
3611 case XT_TAB_FIRST:
3612 gtk_notebook_set_current_page(notebook, 0);
3613 break;
3614 case XT_TAB_LAST:
3615 gtk_notebook_set_current_page(notebook, -1);
3616 break;
3617 default:
3618 return (XT_CB_PASSTHROUGH);
3621 return (XT_CB_HANDLED);
3624 /* jump to tab */
3625 x = args->i - 1;
3626 if (t->tab_id == x) {
3627 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3628 return (XT_CB_HANDLED);
3631 TAILQ_FOREACH(tt, &tabs, entry) {
3632 if (tt->tab_id == x) {
3633 gtk_notebook_set_current_page(notebook, x);
3634 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
3635 if (tt->focus_wv)
3636 focus_webview(tt);
3640 return (XT_CB_HANDLED);
3644 command(struct tab *t, struct karg *args)
3646 char *s = NULL, *ss = NULL;
3647 GdkColor color;
3648 const gchar *uri;
3650 if (t == NULL || args == NULL) {
3651 show_oops_s("command invalid parameters");
3652 return (XT_CB_PASSTHROUGH);
3655 switch (args->i) {
3656 case '/':
3657 s = "/";
3658 break;
3659 case '?':
3660 s = "?";
3661 break;
3662 case ':':
3663 s = ":";
3664 break;
3665 case XT_CMD_OPEN:
3666 s = ":open ";
3667 break;
3668 case XT_CMD_TABNEW:
3669 s = ":tabnew ";
3670 break;
3671 case XT_CMD_OPEN_CURRENT:
3672 s = ":open ";
3673 /* FALL THROUGH */
3674 case XT_CMD_TABNEW_CURRENT:
3675 if (!s) /* FALL THROUGH? */
3676 s = ":tabnew ";
3677 if ((uri = get_uri(t->wv)) != NULL) {
3678 ss = g_strdup_printf("%s%s", s, uri);
3679 s = ss;
3681 break;
3682 default:
3683 show_oops(t, "command: invalid opcode %d", args->i);
3684 return (XT_CB_PASSTHROUGH);
3687 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3689 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3690 gdk_color_parse("white", &color);
3691 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3692 show_cmd(t);
3693 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3694 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3696 if (ss)
3697 g_free(ss);
3699 return (XT_CB_HANDLED);
3703 * Return a new string with a download row (in html)
3704 * appended. Old string is freed.
3706 char *
3707 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3710 WebKitDownloadStatus stat;
3711 char *status_html = NULL, *cmd_html = NULL, *new_html;
3712 gdouble progress;
3713 char cur_sz[FMT_SCALED_STRSIZE];
3714 char tot_sz[FMT_SCALED_STRSIZE];
3715 char *xtp_prefix;
3717 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3719 /* All actions wil take this form:
3720 * xxxt://class/seskey
3722 xtp_prefix = g_strdup_printf("%s%d/%s/",
3723 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3725 stat = webkit_download_get_status(dl->download);
3727 switch (stat) {
3728 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3729 status_html = g_strdup_printf("Finished");
3730 cmd_html = g_strdup_printf(
3731 "<a href='%s%d/%d'>Remove</a>",
3732 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3733 break;
3734 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3735 /* gather size info */
3736 progress = 100 * webkit_download_get_progress(dl->download);
3738 fmt_scaled(
3739 webkit_download_get_current_size(dl->download), cur_sz);
3740 fmt_scaled(
3741 webkit_download_get_total_size(dl->download), tot_sz);
3743 status_html = g_strdup_printf(
3744 "<div style='width: 100%%' align='center'>"
3745 "<div class='progress-outer'>"
3746 "<div class='progress-inner' style='width: %.2f%%'>"
3747 "</div></div></div>"
3748 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3749 progress, cur_sz, tot_sz, progress);
3751 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3752 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3754 break;
3755 /* LLL */
3756 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3757 status_html = g_strdup_printf("Cancelled");
3758 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3759 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3760 break;
3761 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3762 status_html = g_strdup_printf("Error!");
3763 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3764 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3765 break;
3766 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3767 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3768 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3769 status_html = g_strdup_printf("Starting");
3770 break;
3771 default:
3772 show_oops(t, "%s: unknown download status", __func__);
3775 new_html = g_strdup_printf(
3776 "%s\n<tr><td>%s</td><td>%s</td>"
3777 "<td style='text-align:center'>%s</td></tr>\n",
3778 html, basename(webkit_download_get_uri(dl->download)),
3779 status_html, cmd_html);
3780 g_free(html);
3782 if (status_html)
3783 g_free(status_html);
3785 if (cmd_html)
3786 g_free(cmd_html);
3788 g_free(xtp_prefix);
3790 return new_html;
3794 * update all download tabs apart from one. Pass NULL if
3795 * you want to update all.
3797 void
3798 update_download_tabs(struct tab *apart_from)
3800 struct tab *t;
3801 if (!updating_dl_tabs) {
3802 updating_dl_tabs = 1; /* stop infinite recursion */
3803 TAILQ_FOREACH(t, &tabs, entry)
3804 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
3805 && (t != apart_from))
3806 xtp_page_dl(t, NULL);
3807 updating_dl_tabs = 0;
3812 * update all cookie tabs apart from one. Pass NULL if
3813 * you want to update all.
3815 void
3816 update_cookie_tabs(struct tab *apart_from)
3818 struct tab *t;
3819 if (!updating_cl_tabs) {
3820 updating_cl_tabs = 1; /* stop infinite recursion */
3821 TAILQ_FOREACH(t, &tabs, entry)
3822 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
3823 && (t != apart_from))
3824 xtp_page_cl(t, NULL);
3825 updating_cl_tabs = 0;
3830 * update all history tabs apart from one. Pass NULL if
3831 * you want to update all.
3833 void
3834 update_history_tabs(struct tab *apart_from)
3836 struct tab *t;
3838 if (!updating_hl_tabs) {
3839 updating_hl_tabs = 1; /* stop infinite recursion */
3840 TAILQ_FOREACH(t, &tabs, entry)
3841 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
3842 && (t != apart_from))
3843 xtp_page_hl(t, NULL);
3844 updating_hl_tabs = 0;
3848 /* cookie management XTP page */
3850 xtp_page_cl(struct tab *t, struct karg *args)
3852 char *header, *body, *footer, *page, *tmp;
3853 int i = 1; /* all ids start 1 */
3854 GSList *sc, *pc, *pc_start;
3855 SoupCookie *c;
3856 char *type, *table_headers;
3857 char *last_domain = strdup("");
3859 DNPRINTF(XT_D_CMD, "%s", __func__);
3861 if (t == NULL) {
3862 show_oops_s("%s invalid parameters", __func__);
3863 return (1);
3865 /* mark this tab as cookie jar */
3866 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
3868 /* Generate a new session key */
3869 if (!updating_cl_tabs)
3870 generate_xtp_session_key(&cl_session_key);
3872 /* header */
3873 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3874 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
3875 "</head><body><h1>Cookie Jar</h1>\n");
3877 /* table headers */
3878 table_headers = g_strdup_printf("<div align='center'><table><tr>"
3879 "<th>Type</th>"
3880 "<th>Name</th>"
3881 "<th>Value</th>"
3882 "<th>Path</th>"
3883 "<th>Expires</th>"
3884 "<th>Secure</th>"
3885 "<th>HTTP<br />only</th>"
3886 "<th>Rm</th></tr>\n");
3888 sc = soup_cookie_jar_all_cookies(s_cookiejar);
3889 pc = soup_cookie_jar_all_cookies(p_cookiejar);
3890 pc_start = pc;
3892 body = NULL;
3893 for (; sc; sc = sc->next) {
3894 c = sc->data;
3896 if (strcmp(last_domain, c->domain) != 0) {
3897 /* new domain */
3898 free(last_domain);
3899 last_domain = strdup(c->domain);
3901 if (body != NULL) {
3902 tmp = body;
3903 body = g_strdup_printf("%s</table></div>"
3904 "<h2>%s</h2>%s\n",
3905 body, c->domain, table_headers);
3906 g_free(tmp);
3907 } else {
3908 /* first domain */
3909 body = g_strdup_printf("<h2>%s</h2>%s\n",
3910 c->domain, table_headers);
3914 type = "Session";
3915 for (pc = pc_start; pc; pc = pc->next)
3916 if (soup_cookie_equal(pc->data, c)) {
3917 type = "Session + Persistent";
3918 break;
3921 tmp = body;
3922 body = g_strdup_printf(
3923 "%s\n<tr>"
3924 "<td style='width: text-align: center'>%s</td>"
3925 "<td style='width: 1px'>%s</td>"
3926 "<td style='width=70%%;overflow: visible'>"
3927 " <textarea rows='4'>%s</textarea>"
3928 "</td>"
3929 "<td>%s</td>"
3930 "<td>%s</td>"
3931 "<td style='width: 1px; text-align: center'>%d</td>"
3932 "<td style='width: 1px; text-align: center'>%d</td>"
3933 "<td style='width: 1px; text-align: center'>"
3934 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
3935 body,
3936 type,
3937 c->name,
3938 c->value,
3939 c->path,
3940 c->expires ?
3941 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
3942 c->secure,
3943 c->http_only,
3945 XT_XTP_STR,
3946 XT_XTP_CL,
3947 cl_session_key,
3948 XT_XTP_CL_REMOVE,
3952 g_free(tmp);
3953 i++;
3956 soup_cookies_free(sc);
3957 soup_cookies_free(pc);
3959 /* small message if there are none */
3960 if (i == 1) {
3961 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3962 "colspan='8'>No Cookies</td></tr>\n", table_headers);
3965 /* footer */
3966 footer = g_strdup_printf("</table></div></body></html>");
3968 page = g_strdup_printf("%s%s%s", header, body, footer);
3970 g_free(header);
3971 g_free(body);
3972 g_free(footer);
3973 g_free(table_headers);
3974 g_free(last_domain);
3976 load_webkit_string(t, page);
3977 update_cookie_tabs(t);
3979 g_free(page);
3981 return (0);
3985 xtp_page_hl(struct tab *t, struct karg *args)
3987 char *header, *body, *footer, *page, *tmp;
3988 struct history *h;
3989 int i = 1; /* all ids start 1 */
3991 DNPRINTF(XT_D_CMD, "%s", __func__);
3993 if (t == NULL) {
3994 show_oops_s("%s invalid parameters", __func__);
3995 return (1);
3998 /* mark this tab as history manager */
3999 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
4001 /* Generate a new session key */
4002 if (!updating_hl_tabs)
4003 generate_xtp_session_key(&hl_session_key);
4005 /* header */
4006 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
4007 "<title>History</title>\n"
4008 "%s"
4009 "</head>"
4010 "<h1>History</h1>\n",
4011 XT_PAGE_STYLE);
4013 /* body */
4014 body = g_strdup_printf("<div align='center'><table><tr>"
4015 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
4017 RB_FOREACH_REVERSE(h, history_list, &hl) {
4018 tmp = body;
4019 body = g_strdup_printf(
4020 "%s\n<tr>"
4021 "<td><a href='%s'>%s</a></td>"
4022 "<td>%s</td>"
4023 "<td style='text-align: center'>"
4024 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4025 body, h->uri, h->uri, h->title,
4026 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4027 XT_XTP_HL_REMOVE, i);
4029 g_free(tmp);
4030 i++;
4033 /* small message if there are none */
4034 if (i == 1) {
4035 tmp = body;
4036 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4037 "colspan='3'>No History</td></tr>\n", body);
4038 g_free(tmp);
4041 /* footer */
4042 footer = g_strdup_printf("</table></div></body></html>");
4044 page = g_strdup_printf("%s%s%s", header, body, footer);
4047 * update all history manager tabs as the xtp session
4048 * key has now changed. No need to update the current tab.
4049 * Already did that above.
4051 update_history_tabs(t);
4053 g_free(header);
4054 g_free(body);
4055 g_free(footer);
4057 load_webkit_string(t, page);
4058 g_free(page);
4060 return (0);
4064 * Generate a web page detailing the status of any downloads
4067 xtp_page_dl(struct tab *t, struct karg *args)
4069 struct download *dl;
4070 char *header, *body, *footer, *page, *tmp;
4071 char *ref;
4072 int n_dl = 1;
4074 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4076 if (t == NULL) {
4077 show_oops_s("%s invalid parameters", __func__);
4078 return (1);
4080 /* mark as a download manager tab */
4081 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
4084 * Generate a new session key for next page instance.
4085 * This only happens for the top level call to xtp_page_dl()
4086 * in which case updating_dl_tabs is 0.
4088 if (!updating_dl_tabs)
4089 generate_xtp_session_key(&dl_session_key);
4091 /* header - with refresh so as to update */
4092 if (refresh_interval >= 1)
4093 ref = g_strdup_printf(
4094 "<meta http-equiv='refresh' content='%u"
4095 ";url=%s%d/%s/%d' />\n",
4096 refresh_interval,
4097 XT_XTP_STR,
4098 XT_XTP_DL,
4099 dl_session_key,
4100 XT_XTP_DL_LIST);
4101 else
4102 ref = g_strdup("");
4105 header = g_strdup_printf(
4106 "%s\n<head>"
4107 "<title>Downloads</title>\n%s%s</head>\n",
4108 XT_DOCTYPE XT_HTML_TAG,
4109 ref,
4110 XT_PAGE_STYLE);
4112 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
4113 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4114 "</p><table><tr><th style='width: 60%%'>"
4115 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4116 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4118 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4119 body = xtp_page_dl_row(t, body, dl);
4120 n_dl++;
4123 /* message if no downloads in list */
4124 if (n_dl == 1) {
4125 tmp = body;
4126 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4127 " style='text-align: center'>"
4128 "No downloads</td></tr>\n", body);
4129 g_free(tmp);
4132 /* footer */
4133 footer = g_strdup_printf("</table></div></body></html>");
4135 page = g_strdup_printf("%s%s%s", header, body, footer);
4139 * update all download manager tabs as the xtp session
4140 * key has now changed. No need to update the current tab.
4141 * Already did that above.
4143 update_download_tabs(t);
4145 g_free(ref);
4146 g_free(header);
4147 g_free(body);
4148 g_free(footer);
4150 load_webkit_string(t, page);
4151 g_free(page);
4153 return (0);
4157 search(struct tab *t, struct karg *args)
4159 gboolean d;
4161 if (t == NULL || args == NULL) {
4162 show_oops_s("search invalid parameters");
4163 return (1);
4165 if (t->search_text == NULL) {
4166 if (global_search == NULL)
4167 return (XT_CB_PASSTHROUGH);
4168 else {
4169 t->search_text = g_strdup(global_search);
4170 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4171 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4175 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4176 t->tab_id, args->i, t->search_forward, t->search_text);
4178 switch (args->i) {
4179 case XT_SEARCH_NEXT:
4180 d = t->search_forward;
4181 break;
4182 case XT_SEARCH_PREV:
4183 d = !t->search_forward;
4184 break;
4185 default:
4186 return (XT_CB_PASSTHROUGH);
4189 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4191 return (XT_CB_HANDLED);
4194 struct settings_args {
4195 char **body;
4196 int i;
4199 void
4200 print_setting(struct settings *s, char *val, void *cb_args)
4202 char *tmp, *color;
4203 struct settings_args *sa = cb_args;
4205 if (sa == NULL)
4206 return;
4208 if (s->flags & XT_SF_RUNTIME)
4209 color = "#22cc22";
4210 else
4211 color = "#cccccc";
4213 tmp = *sa->body;
4214 *sa->body = g_strdup_printf(
4215 "%s\n<tr>"
4216 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
4217 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
4218 *sa->body,
4219 color,
4220 s->name,
4221 color,
4224 g_free(tmp);
4225 sa->i++;
4229 set(struct tab *t, struct karg *args)
4231 char *header, *body, *footer, *page, *tmp, *pars;
4232 int i = 1;
4233 struct settings_args sa;
4235 if ((pars = getparams(args->s, "set")) == NULL) {
4236 bzero(&sa, sizeof sa);
4237 sa.body = &body;
4239 /* header */
4240 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4241 "\n<head><title>Settings</title>\n"
4242 "</head><body><h1>Settings</h1>\n");
4244 /* body */
4245 body = g_strdup_printf("<div align='center'><table><tr>"
4246 "<th align='left'>Setting</th>"
4247 "<th align='left'>Value</th></tr>\n");
4249 settings_walk(print_setting, &sa);
4250 i = sa.i;
4252 /* small message if there are none */
4253 if (i == 1) {
4254 tmp = body;
4255 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4256 "colspan='2'>No settings</td></tr>\n", body);
4257 g_free(tmp);
4260 /* footer */
4261 footer = g_strdup_printf("</table></div></body></html>");
4263 page = g_strdup_printf("%s%s%s", header, body, footer);
4265 g_free(header);
4266 g_free(body);
4267 g_free(footer);
4269 load_webkit_string(t, page);
4270 } else
4271 show_oops(t, "Invalid command: %s", pars);
4273 return (XT_CB_PASSTHROUGH);
4277 session_save(struct tab *t, char *filename, char **ret)
4279 struct karg a;
4280 char *f = filename;
4281 int rv = 1;
4283 f += strlen("save");
4284 while (*f == ' ' && *f != '\0')
4285 f++;
4286 if (strlen(f) == 0)
4287 goto done;
4289 *ret = f;
4290 if (f[0] == '.' || f[0] == '/')
4291 goto done;
4293 a.s = f;
4294 if (save_tabs(t, &a))
4295 goto done;
4296 strlcpy(named_session, f, sizeof named_session);
4298 rv = 0;
4299 done:
4300 return (rv);
4304 session_open(struct tab *t, char *filename, char **ret)
4306 struct karg a;
4307 char *f = filename;
4308 int rv = 1;
4310 f += strlen("open");
4311 while (*f == ' ' && *f != '\0')
4312 f++;
4313 if (strlen(f) == 0)
4314 goto done;
4316 *ret = f;
4317 if (f[0] == '.' || f[0] == '/')
4318 goto done;
4320 a.s = f;
4321 a.i = XT_SES_CLOSETABS;
4322 if (open_tabs(t, &a))
4323 goto done;
4325 strlcpy(named_session, f, sizeof named_session);
4327 rv = 0;
4328 done:
4329 return (rv);
4333 session_delete(struct tab *t, char *filename, char **ret)
4335 char file[PATH_MAX];
4336 char *f = filename;
4337 int rv = 1;
4339 f += strlen("delete");
4340 while (*f == ' ' && *f != '\0')
4341 f++;
4342 if (strlen(f) == 0)
4343 goto done;
4345 *ret = f;
4346 if (f[0] == '.' || f[0] == '/')
4347 goto done;
4349 snprintf(file, sizeof file, "%s/%s", sessions_dir, f);
4350 if (unlink(file))
4351 goto done;
4353 if (!strcmp(f, named_session))
4354 strlcpy(named_session, XT_SAVED_TABS_FILE,
4355 sizeof named_session);
4357 rv = 0;
4358 done:
4359 return (rv);
4363 session_cmd(struct tab *t, struct karg *args)
4365 char *action = NULL;
4366 char *filename = NULL;
4368 if (t == NULL)
4369 return (1);
4371 if ((action = getparams(args->s, "session")))
4373 else
4374 action = "show";
4376 if (!strcmp(action, "show"))
4377 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4378 XT_SAVED_TABS_FILE : named_session);
4379 else if (g_str_has_prefix(action, "save ")) {
4380 if (session_save(t, action, &filename)) {
4381 show_oops(t, "Can't save session: %s",
4382 filename ? filename : "INVALID");
4383 goto done;
4385 } else if (g_str_has_prefix(action, "open ")) {
4386 if (session_open(t, action, &filename)) {
4387 show_oops(t, "Can't open session: %s",
4388 filename ? filename : "INVALID");
4389 goto done;
4391 } else if (g_str_has_prefix(action, "delete ")) {
4392 if (session_delete(t, action, &filename)) {
4393 show_oops(t, "Can't delete session: %s",
4394 filename ? filename : "INVALID");
4395 goto done;
4397 } else
4398 show_oops(t, "Invalid command: %s", action);
4399 done:
4400 return (XT_CB_PASSTHROUGH);
4404 * Make a hardcopy of the page
4407 print_page(struct tab *t, struct karg *args)
4409 WebKitWebFrame *frame;
4411 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4414 * for now we just call the GTK print box,
4415 * but later we might decide to hook in a command.
4417 frame = webkit_web_view_get_main_frame(t->wv);
4418 webkit_web_frame_print(frame);
4420 return (0);
4424 go_home(struct tab *t, struct karg *args)
4426 load_uri(t, home);
4427 return (0);
4431 restart(struct tab *t, struct karg *args)
4433 struct karg a;
4435 a.s = XT_RESTART_TABS_FILE;
4436 save_tabs(t, &a);
4437 execvp(start_argv[0], start_argv);
4438 /* NOTREACHED */
4440 return (0);
4443 #define CTRL GDK_CONTROL_MASK
4444 #define MOD1 GDK_MOD1_MASK
4445 #define SHFT GDK_SHIFT_MASK
4447 /* inherent to GTK not all keys will be caught at all times */
4448 /* XXX sort key bindings */
4449 struct key_binding {
4450 char *name;
4451 guint mask;
4452 guint use_in_entry;
4453 guint key;
4454 int (*func)(struct tab *, struct karg *);
4455 struct karg arg;
4456 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4457 } keys[] = {
4458 { "cookiejar", MOD1, 0, GDK_j, xtp_page_cl, {0} },
4459 { "downloadmgr", MOD1, 0, GDK_d, xtp_page_dl, {0} },
4460 { "history", MOD1, 0, GDK_h, xtp_page_hl, {0} },
4461 { "print", CTRL, 0, GDK_p, print_page, {0}},
4462 { NULL, 0, 0, GDK_slash, command, {.i = '/'} },
4463 { NULL, 0, 0, GDK_question, command, {.i = '?'} },
4464 { NULL, 0, 0, GDK_colon, command, {.i = ':'} },
4465 { "quit", CTRL, 0, GDK_q, quit, {0} },
4466 { "restart", MOD1, 0, GDK_q, restart, {0} },
4467 { "togglejs", CTRL, 0, GDK_j, toggle_js, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
4468 { "togglecookie", MOD1, 0, GDK_c, toggle_cwl, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
4469 { "togglesrc", CTRL, 0, GDK_s, toggle_src, {0} },
4470 { "yankuri", 0, 0, GDK_y, yank_uri, {0} },
4471 { "pasteuricur", 0, 0, GDK_p, paste_uri, {.i = XT_PASTE_CURRENT_TAB} },
4472 { "pasteurinew", 0, 0, GDK_P, paste_uri, {.i = XT_PASTE_NEW_TAB} },
4474 /* search */
4475 { "searchnext", 0, 0, GDK_n, search, {.i = XT_SEARCH_NEXT} },
4476 { "searchprev", 0, 0, GDK_N, search, {.i = XT_SEARCH_PREV} },
4478 /* focus */
4479 { "focusaddress", 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} },
4480 { "focussearch", 0, 0, GDK_F7, focus, {.i = XT_FOCUS_SEARCH} },
4482 /* command aliases (handy when -S flag is used) */
4483 { NULL, 0, 0, GDK_F9, command, {.i = XT_CMD_OPEN} },
4484 { NULL, 0, 0, GDK_F10, command, {.i = XT_CMD_OPEN_CURRENT} },
4485 { NULL, 0, 0, GDK_F11, command, {.i = XT_CMD_TABNEW} },
4486 { NULL, 0, 0, GDK_F12, command, {.i = XT_CMD_TABNEW_CURRENT} },
4488 /* hinting */
4489 { "hinting", 0, 0, GDK_f, hint, {.i = 0} },
4491 /* navigation */
4492 { "goback", 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
4493 { "goback", MOD1, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
4494 { "goforward", SHFT, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
4495 { "goforward", MOD1, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
4496 { "reload", 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} },
4497 { "reload", CTRL, 0, GDK_r, navaction, {.i = XT_NAV_RELOAD} },
4498 { "reloadforce", CTRL, 0, GDK_R, navaction, {.i = XT_NAV_RELOAD_CACHE} },
4499 { "reload" , CTRL, 0, GDK_l, navaction, {.i = XT_NAV_RELOAD} },
4500 { "favorites", MOD1, 1, GDK_f, xtp_page_fl, {0} },
4502 /* vertical movement */
4503 { "scrolldown", 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
4504 { "scrolldown", 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
4505 { "scrollup", 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
4506 { "scrollup", 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
4507 { "scrollbottom", 0, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
4508 { "scrollbottom", 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
4509 { "scrolltop", 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
4510 { "scrolltop", 0, 0, GDK_g, move, {.i = XT_MOVE_TOP} },
4511 { "scrollpagedown", 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
4512 { "scrollpagedown", CTRL, 0, GDK_f, move, {.i = XT_MOVE_PAGEDOWN} },
4513 { "scrollhalfdown", CTRL, 0, GDK_d, move, {.i = XT_MOVE_HALFDOWN} },
4514 { "scrollpagedown", 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
4515 { "scrollpageup", 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
4516 { "scrollpageup", CTRL, 0, GDK_b, move, {.i = XT_MOVE_PAGEUP} },
4517 { "scrollhalfup", CTRL, 0, GDK_u, move, {.i = XT_MOVE_HALFUP} },
4518 /* horizontal movement */
4519 { "scrollright", 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
4520 { "scrollright", 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
4521 { "scrollleft", 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
4522 { "scrollleft", 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
4523 { "scrollfarright", 0, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
4524 { "scrollfarleft", 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
4526 /* tabs */
4527 { "tabnew", CTRL, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
4528 { "tabclose", CTRL, 1, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
4529 { "tabundoclose", 0, 0, GDK_U, tabaction, {.i = XT_TAB_UNDO_CLOSE} },
4530 { "tabgoto1", CTRL, 0, GDK_1, movetab, {.i = 1} },
4531 { "tabgoto2", CTRL, 0, GDK_2, movetab, {.i = 2} },
4532 { "tabgoto3", CTRL, 0, GDK_3, movetab, {.i = 3} },
4533 { "tabgoto4", CTRL, 0, GDK_4, movetab, {.i = 4} },
4534 { "tabgoto5", CTRL, 0, GDK_5, movetab, {.i = 5} },
4535 { "tabgoto6", CTRL, 0, GDK_6, movetab, {.i = 6} },
4536 { "tabgoto7", CTRL, 0, GDK_7, movetab, {.i = 7} },
4537 { "tabgoto8", CTRL, 0, GDK_8, movetab, {.i = 8} },
4538 { "tabgoto9", CTRL, 0, GDK_9, movetab, {.i = 9} },
4539 { "tabgoto10", CTRL, 0, GDK_0, movetab, {.i = 10} },
4540 { "tabgotofirst", CTRL, 0, GDK_less, movetab, {.i = XT_TAB_FIRST} },
4541 { "tabgotolast", CTRL, 0, GDK_greater, movetab, {.i = XT_TAB_LAST} },
4542 { "tabgotoprev", CTRL, 0, GDK_Left, movetab, {.i = XT_TAB_PREV} },
4543 { "tabgotonext", CTRL, 0, GDK_Right, movetab, {.i = XT_TAB_NEXT} },
4544 { "focusout", CTRL, 0, GDK_minus, resizetab, {.i = -1} },
4545 { "focusin", CTRL, 0, GDK_plus, resizetab, {.i = 1} },
4546 { "focusin", CTRL, 0, GDK_equal, resizetab, {.i = 1} },
4548 TAILQ_HEAD(keybinding_list, key_binding);
4550 void
4551 walk_kb(struct settings *s,
4552 void (*cb)(struct settings *, char *, void *), void *cb_args)
4554 struct key_binding *k;
4555 char str[1024];
4557 if (s == NULL || cb == NULL) {
4558 show_oops_s("walk_kb invalid parameters");
4559 return;
4562 TAILQ_FOREACH(k, &kbl, entry) {
4563 if (k->name == NULL)
4564 continue;
4565 str[0] = '\0';
4567 strlcat(str, k->name, sizeof str);
4568 strlcat(str, ",", sizeof str);
4570 if (k->mask & GDK_SHIFT_MASK)
4571 strlcat(str, "S+", sizeof str);
4572 if (k->mask & GDK_CONTROL_MASK)
4573 strlcat(str, "C+", sizeof str);
4574 if (k->mask & GDK_MOD1_MASK)
4575 strlcat(str, "M1+", sizeof str);
4576 if (k->mask & GDK_MOD2_MASK)
4577 strlcat(str, "M2+", sizeof str);
4578 if (k->mask & GDK_MOD3_MASK)
4579 strlcat(str, "M3+", sizeof str);
4580 if (k->mask & GDK_MOD4_MASK)
4581 strlcat(str, "M4+", sizeof str);
4582 if (k->mask & GDK_MOD5_MASK)
4583 strlcat(str, "M5+", sizeof str);
4585 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4586 cb(s, str, cb_args);
4589 void
4590 init_keybindings(void)
4592 int i;
4593 struct key_binding *k;
4595 for (i = 0; i < LENGTH(keys); i++) {
4596 k = g_malloc0(sizeof *k);
4597 k->name = keys[i].name;
4598 k->mask = keys[i].mask;
4599 k->use_in_entry = keys[i].use_in_entry;
4600 k->key = keys[i].key;
4601 k->func = keys[i].func;
4602 bcopy(&keys[i].arg, &k->arg, sizeof k->arg);
4603 TAILQ_INSERT_HEAD(&kbl, k, entry);
4605 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4606 k->name ? k->name : "unnamed key");
4610 void
4611 keybinding_clearall(void)
4613 struct key_binding *k, *next;
4615 for (k = TAILQ_FIRST(&kbl); k != TAILQ_LAST(&kbl, keybinding_list);
4616 k = next) {
4617 next = TAILQ_NEXT(k, entry);
4618 if (k->name == NULL)
4619 continue;
4621 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4622 k->name ? k->name : "unnamed key");
4623 TAILQ_REMOVE(&kbl, k, entry);
4624 g_free(k);
4629 keybinding_add(char *kb, struct key_binding *orig)
4631 struct key_binding *k;
4632 char *name, *value, *s;
4633 guint keyval, mask = 0;
4634 int i;
4636 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s\n", kb);
4638 s = strstr(kb, ",");
4639 if (s == NULL)
4640 return (1);
4641 *s = '\0';
4643 name = kb;
4644 value = s + 1;
4646 /* find modifier keys */
4647 if (strstr(value, "S+"))
4648 mask |= GDK_SHIFT_MASK;
4649 if (strstr(value, "C+"))
4650 mask |= GDK_CONTROL_MASK;
4651 if (strstr(value, "M1+"))
4652 mask |= GDK_MOD1_MASK;
4653 if (strstr(value, "M2+"))
4654 mask |= GDK_MOD2_MASK;
4655 if (strstr(value, "M3+"))
4656 mask |= GDK_MOD3_MASK;
4657 if (strstr(value, "M4+"))
4658 mask |= GDK_MOD4_MASK;
4659 if (strstr(value, "M5+"))
4660 mask |= GDK_MOD5_MASK;
4662 /* find keyname */
4663 for (i = strlen(value) - 1; i > 0; i--)
4664 if (value[i] == '+')
4665 value = &value[i + 1];
4667 /* validate keyname */
4668 keyval = gdk_keyval_from_name(value);
4669 if (keyval == GDK_VoidSymbol) {
4670 warnx("invalid keybinding name %s", value);
4671 return (1);
4674 /* add keyname */
4675 k = g_malloc0(sizeof *k);
4676 k->name = name;
4677 k->mask = mask;
4678 k->use_in_entry = orig->use_in_entry;
4679 k->key = keyval;
4680 k->func = orig->func;
4681 bcopy(&orig->arg, &k->arg, sizeof k->arg);
4683 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
4684 k->name,
4685 k->mask,
4686 k->use_in_entry,
4687 k->key);
4688 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
4689 name, gdk_keyval_name(keyval));
4691 TAILQ_INSERT_HEAD(&kbl, k, entry);
4693 return (0);
4697 add_kb(struct settings *s, char *entry)
4699 int i;
4701 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
4703 /* clearall is special */
4704 if (!strcmp(entry, "clearall")) {
4705 keybinding_clearall();
4706 return (0);
4709 /* make sure it is a valid keybinding */
4710 for (i = 0; i < LENGTH(keys); i++)
4711 if (keys[i].name && !strncmp(entry, keys[i].name,
4712 strlen(keys[i].name))) {
4713 DNPRINTF(XT_D_KEYBINDING, "%s 0x%x %d 0x%x\n",
4714 keys[i].name,
4715 keys[i].mask,
4716 keys[i].use_in_entry,
4717 keys[i].key);
4719 return (keybinding_add(entry, &keys[i]));
4722 return (1);
4725 struct cmd {
4726 char *cmd;
4727 int params;
4728 int (*func)(struct tab *, struct karg *);
4729 struct karg arg;
4730 } cmds[] = {
4731 { "q!", 0, quit, {0} },
4732 { "qa", 0, quit, {0} },
4733 { "qa!", 0, quit, {0} },
4734 { "w", 0, save_tabs, {0} },
4735 { "wq", 0, save_tabs_and_quit, {0} },
4736 { "wq!", 0, save_tabs_and_quit, {0} },
4737 { "help", 0, help, {0} },
4738 { "about", 0, about, {0} },
4739 { "stats", 0, stats, {0} },
4740 { "version", 0, about, {0} },
4741 { "cookies", 0, xtp_page_cl, {0} },
4742 { "fav", 0, xtp_page_fl, {0} },
4743 { "favadd", 0, add_favorite, {0} },
4744 { "js", 2, js_cmd, {0} },
4745 { "cookie", 2, cookie_cmd, {0} },
4746 { "cert", 1, cert_cmd, {0} },
4747 { "ca", 0, ca_cmd, {0} },
4748 { "dl", 0, xtp_page_dl, {0} },
4749 { "h", 0, xtp_page_hl, {0} },
4750 { "hist", 0, xtp_page_hl, {0} },
4751 { "history", 0, xtp_page_hl, {0} },
4752 { "home", 0, go_home, {0} },
4753 { "restart", 0, restart, {0} },
4754 { "urlhide", 0, urlaction, {.i = XT_URL_HIDE} },
4755 { "urlh", 0, urlaction, {.i = XT_URL_HIDE} },
4756 { "urlshow", 0, urlaction, {.i = XT_URL_SHOW} },
4757 { "urls", 0, urlaction, {.i = XT_URL_SHOW} },
4758 { "statushide", 0, statusaction, {.i = XT_STATUSBAR_HIDE} },
4759 { "statush", 0, statusaction, {.i = XT_STATUSBAR_HIDE} },
4760 { "statusshow", 0, statusaction, {.i = XT_STATUSBAR_SHOW} },
4761 { "statuss", 0, statusaction, {.i = XT_STATUSBAR_SHOW} },
4763 { "1", 0, move, {.i = XT_MOVE_TOP} },
4764 { "print", 0, print_page, {0} },
4766 /* tabs */
4767 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
4768 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
4769 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
4770 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
4771 { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} },
4772 { "tabe", 1, tabaction, {.i = XT_TAB_NEW} },
4773 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
4774 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} },
4775 { "tabshow", 1, tabaction, {.i = XT_TAB_SHOW} },
4776 { "tabs", 1, tabaction, {.i = XT_TAB_SHOW} },
4777 { "tabhide", 1, tabaction, {.i = XT_TAB_HIDE} },
4778 { "tabh", 1, tabaction, {.i = XT_TAB_HIDE} },
4779 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4780 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4781 /* XXX add count to these commands */
4782 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST} },
4783 { "tabfir", 0, movetab, {.i = XT_TAB_FIRST} },
4784 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST} },
4785 { "tabr", 0, movetab, {.i = XT_TAB_FIRST} },
4786 { "tablast", 0, movetab, {.i = XT_TAB_LAST} },
4787 { "tabl", 0, movetab, {.i = XT_TAB_LAST} },
4788 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
4789 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
4790 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
4791 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
4793 /* settings */
4794 { "set", 1, set, {0} },
4795 { "fullscreen", 0, fullscreen, {0} },
4796 { "f", 0, fullscreen, {0} },
4798 /* sessions */
4799 { "session", 1, session_cmd, {0} },
4802 gboolean
4803 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
4805 hide_oops(t);
4807 return (FALSE);
4810 gboolean
4811 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
4813 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
4815 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
4816 delete_tab(t);
4818 return (FALSE);
4822 * cancel, remove, etc. downloads
4824 void
4825 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
4827 struct download find, *d;
4829 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
4831 /* some commands require a valid download id */
4832 if (cmd != XT_XTP_DL_LIST) {
4833 /* lookup download in question */
4834 find.id = id;
4835 d = RB_FIND(download_list, &downloads, &find);
4837 if (d == NULL) {
4838 show_oops(t, "%s: no such download", __func__);
4839 return;
4843 /* decide what to do */
4844 switch (cmd) {
4845 case XT_XTP_DL_CANCEL:
4846 webkit_download_cancel(d->download);
4847 break;
4848 case XT_XTP_DL_REMOVE:
4849 webkit_download_cancel(d->download); /* just incase */
4850 g_object_unref(d->download);
4851 RB_REMOVE(download_list, &downloads, d);
4852 break;
4853 case XT_XTP_DL_LIST:
4854 /* Nothing */
4855 break;
4856 default:
4857 show_oops(t, "%s: unknown command", __func__);
4858 break;
4860 xtp_page_dl(t, NULL);
4864 * Actions on history, only does one thing for now, but
4865 * we provide the function for future actions
4867 void
4868 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
4870 struct history *h, *next;
4871 int i = 1;
4873 switch (cmd) {
4874 case XT_XTP_HL_REMOVE:
4875 /* walk backwards, as listed in reverse */
4876 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
4877 next = RB_PREV(history_list, &hl, h);
4878 if (id == i) {
4879 RB_REMOVE(history_list, &hl, h);
4880 g_free((gpointer) h->title);
4881 g_free((gpointer) h->uri);
4882 g_free(h);
4883 break;
4885 i++;
4887 break;
4888 case XT_XTP_HL_LIST:
4889 /* Nothing - just xtp_page_hl() below */
4890 break;
4891 default:
4892 show_oops(t, "%s: unknown command", __func__);
4893 break;
4896 xtp_page_hl(t, NULL);
4899 /* remove a favorite */
4900 void
4901 remove_favorite(struct tab *t, int index)
4903 char file[PATH_MAX], *title, *uri;
4904 char *new_favs, *tmp;
4905 FILE *f;
4906 int i;
4907 size_t len, lineno;
4909 /* open favorites */
4910 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
4912 if ((f = fopen(file, "r")) == NULL) {
4913 show_oops(t, "%s: can't open favorites: %s",
4914 __func__, strerror(errno));
4915 return;
4918 /* build a string which will become the new favroites file */
4919 new_favs = g_strdup_printf("%s", "");
4921 for (i = 1;;) {
4922 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
4923 if (feof(f) || ferror(f))
4924 break;
4925 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
4926 if (len == 0) {
4927 free(title);
4928 title = NULL;
4929 continue;
4932 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
4933 if (feof(f) || ferror(f)) {
4934 show_oops(t, "%s: can't parse favorites %s",
4935 __func__, strerror(errno));
4936 goto clean;
4940 /* as long as this isn't the one we are deleting add to file */
4941 if (i != index) {
4942 tmp = new_favs;
4943 new_favs = g_strdup_printf("%s%s\n%s\n",
4944 new_favs, title, uri);
4945 g_free(tmp);
4948 free(uri);
4949 uri = NULL;
4950 free(title);
4951 title = NULL;
4952 i++;
4954 fclose(f);
4956 /* write back new favorites file */
4957 if ((f = fopen(file, "w")) == NULL) {
4958 show_oops(t, "%s: can't open favorites: %s",
4959 __func__, strerror(errno));
4960 goto clean;
4963 fwrite(new_favs, strlen(new_favs), 1, f);
4964 fclose(f);
4966 clean:
4967 if (uri)
4968 free(uri);
4969 if (title)
4970 free(title);
4972 g_free(new_favs);
4975 void
4976 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
4978 switch (cmd) {
4979 case XT_XTP_FL_LIST:
4980 /* nothing, just the below call to xtp_page_fl() */
4981 break;
4982 case XT_XTP_FL_REMOVE:
4983 remove_favorite(t, arg);
4984 break;
4985 default:
4986 show_oops(t, "%s: invalid favorites command", __func__);
4987 break;
4990 xtp_page_fl(t, NULL);
4993 void
4994 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
4996 switch (cmd) {
4997 case XT_XTP_CL_LIST:
4998 /* nothing, just xtp_page_cl() */
4999 break;
5000 case XT_XTP_CL_REMOVE:
5001 remove_cookie(arg);
5002 break;
5003 default:
5004 show_oops(t, "%s: unknown cookie xtp command", __func__);
5005 break;
5008 xtp_page_cl(t, NULL);
5011 /* link an XTP class to it's session key and handler function */
5012 struct xtp_despatch {
5013 uint8_t xtp_class;
5014 char **session_key;
5015 void (*handle_func)(struct tab *, uint8_t, int);
5018 struct xtp_despatch xtp_despatches[] = {
5019 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5020 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5021 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5022 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5023 { NULL, NULL, NULL }
5027 * is the url xtp protocol? (xxxt://)
5028 * if so, parse and despatch correct bahvior
5031 parse_xtp_url(struct tab *t, const char *url)
5033 char *dup = NULL, *p, *last;
5034 uint8_t n_tokens = 0;
5035 char *tokens[4] = {NULL, NULL, NULL, ""};
5036 struct xtp_despatch *dsp, *dsp_match = NULL;
5037 uint8_t req_class;
5040 * tokens array meaning:
5041 * tokens[0] = class
5042 * tokens[1] = session key
5043 * tokens[2] = action
5044 * tokens[3] = optional argument
5047 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5049 /*xtp tab meaning is normal unless proven special */
5050 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5052 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5053 return 0;
5055 dup = g_strdup(url + strlen(XT_XTP_STR));
5057 /* split out the url */
5058 for ((p = strtok_r(dup, "/", &last)); p;
5059 (p = strtok_r(NULL, "/", &last))) {
5060 if (n_tokens < 4)
5061 tokens[n_tokens++] = p;
5064 /* should be atleast three fields 'class/seskey/command/arg' */
5065 if (n_tokens < 3)
5066 goto clean;
5068 dsp = xtp_despatches;
5069 req_class = atoi(tokens[0]);
5070 while (dsp->xtp_class != NULL) {
5071 if (dsp->xtp_class == req_class) {
5072 dsp_match = dsp;
5073 break;
5075 dsp++;
5078 /* did we find one atall? */
5079 if (dsp_match == NULL) {
5080 show_oops(t, "%s: no matching xtp despatch found", __func__);
5081 goto clean;
5084 /* check session key and call despatch function */
5085 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5086 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5089 clean:
5090 if (dup)
5091 g_free(dup);
5093 return 1;
5098 void
5099 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5101 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5103 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5105 if (t == NULL) {
5106 show_oops_s("activate_uri_entry_cb invalid parameters");
5107 return;
5110 if (uri == NULL) {
5111 show_oops(t, "activate_uri_entry_cb no uri");
5112 return;
5115 uri += strspn(uri, "\t ");
5117 /* if xxxt:// treat specially */
5118 if (!parse_xtp_url(t, uri)) {
5119 load_uri(t, (gchar *)uri);
5120 focus_webview(t);
5124 void
5125 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5127 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5128 char *newuri = NULL;
5129 gchar *enc_search;
5131 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5133 if (t == NULL) {
5134 show_oops_s("activate_search_entry_cb invalid parameters");
5135 return;
5138 if (search_string == NULL) {
5139 show_oops(t, "no search_string");
5140 return;
5143 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5144 newuri = g_strdup_printf(search_string, enc_search);
5145 g_free(enc_search);
5147 webkit_web_view_load_uri(t->wv, newuri);
5148 focus_webview(t);
5150 if (newuri)
5151 g_free(newuri);
5154 void
5155 check_and_set_js(const gchar *uri, struct tab *t)
5157 struct domain *d = NULL;
5158 int es = 0;
5160 if (uri == NULL || t == NULL)
5161 return;
5163 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5164 es = 0;
5165 else
5166 es = 1;
5168 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5169 es ? "enable" : "disable", uri);
5171 g_object_set(G_OBJECT(t->settings),
5172 "enable-scripts", es, (char *)NULL);
5173 webkit_web_view_set_settings(t->wv, t->settings);
5175 button_set_stockid(t->js_toggle,
5176 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5179 void
5180 show_ca_status(struct tab *t, const char *uri)
5182 WebKitWebFrame *frame;
5183 WebKitWebDataSource *source;
5184 WebKitNetworkRequest *request;
5185 SoupMessage *message;
5186 GdkColor color;
5187 gchar *col_str = "white";
5188 int r;
5190 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5191 ssl_strict_certs, ssl_ca_file, uri);
5193 if (uri == NULL)
5194 goto done;
5195 if (ssl_ca_file == NULL) {
5196 if (g_str_has_prefix(uri, "http://"))
5197 goto done;
5198 if (g_str_has_prefix(uri, "https://")) {
5199 col_str = "red";
5200 goto done;
5202 return;
5204 if (g_str_has_prefix(uri, "http://") ||
5205 !g_str_has_prefix(uri, "https://"))
5206 goto done;
5208 frame = webkit_web_view_get_main_frame(t->wv);
5209 source = webkit_web_frame_get_data_source(frame);
5210 request = webkit_web_data_source_get_request(source);
5211 message = webkit_network_request_get_message(request);
5213 if (message && (soup_message_get_flags(message) &
5214 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5215 col_str = "green";
5216 goto done;
5217 } else {
5218 r = load_compare_cert(t, NULL);
5219 if (r == 0)
5220 col_str = "lightblue";
5221 else if (r == 1)
5222 col_str = "yellow";
5223 else
5224 col_str = "red";
5225 goto done;
5227 done:
5228 if (col_str) {
5229 gdk_color_parse(col_str, &color);
5230 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5232 if (!strcmp(col_str, "white")) {
5233 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5234 &color);
5235 gdk_color_parse("black", &color);
5236 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5237 &color);
5238 } else {
5239 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5240 &color);
5241 gdk_color_parse("black", &color);
5242 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5243 &color);
5248 void
5249 free_favicon(struct tab *t)
5251 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p pix %p\n",
5252 __func__, t->icon_download, t->icon_request, t->icon_pixbuf);
5254 if (t->icon_request)
5255 g_object_unref(t->icon_request);
5256 if (t->icon_pixbuf)
5257 g_object_unref(t->icon_pixbuf);
5258 if (t->icon_dest_uri)
5259 g_free(t->icon_dest_uri);
5261 t->icon_pixbuf = NULL;
5262 t->icon_request = NULL;
5263 t->icon_dest_uri = NULL;
5266 void
5267 xt_icon_from_name(struct tab *t, gchar *name)
5269 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5270 GTK_ENTRY_ICON_PRIMARY, "text-html");
5271 if (show_url == 0) {
5272 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5273 GTK_ENTRY_ICON_PRIMARY, "text-html");
5274 } else {
5275 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5276 GTK_ENTRY_ICON_PRIMARY, NULL);
5278 return;
5281 void
5282 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pixbuf)
5284 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5285 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5286 if (show_url == 0) {
5287 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5288 GTK_ENTRY_ICON_PRIMARY, pixbuf);
5289 } else {
5290 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5291 GTK_ENTRY_ICON_PRIMARY, NULL);
5295 void
5296 abort_favicon_download(struct tab *t)
5298 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5300 if (t->icon_download)
5301 webkit_download_cancel(t->icon_download);
5302 else
5303 free_favicon(t);
5305 xt_icon_from_name(t, "text-html");
5308 void
5309 set_favicon_from_file(struct tab *t, char *file)
5311 gint width, height;
5312 GdkPixbuf *pixbuf, *scaled;
5313 struct stat sb;
5315 if (t == NULL || file == NULL)
5316 return;
5317 if (t->icon_pixbuf) {
5318 DNPRINTF(XT_D_DOWNLOAD, "%s: icon already set\n", __func__);
5319 return;
5322 if (g_str_has_prefix(file, "file://"))
5323 file += strlen("file://");
5324 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5326 if (!stat(file, &sb)) {
5327 if (sb.st_size == 0) {
5328 /* corrupt icon so trash it */
5329 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5330 __func__, file);
5331 unlink(file);
5332 /* no need to set icon to default here */
5333 return;
5337 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
5338 if (pixbuf == NULL) {
5339 xt_icon_from_name(t, "text-html");
5340 return;
5343 g_object_get(pixbuf, "width", &width, "height", &height,
5344 (char *)NULL);
5345 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
5346 __func__, t->tab_id, width, height);
5348 if (width > 16 || height > 16) {
5349 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5350 GDK_INTERP_BILINEAR);
5351 g_object_unref(pixbuf);
5352 } else
5353 scaled = pixbuf;
5355 if (scaled == NULL) {
5356 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5357 GDK_INTERP_BILINEAR);
5358 return;
5361 t->icon_pixbuf = scaled;
5362 xt_icon_from_pixbuf(t, t->icon_pixbuf);
5365 void
5366 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5367 struct tab *t)
5369 WebKitDownloadStatus status = webkit_download_get_status(download);
5371 if (t == NULL)
5372 return;
5374 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5375 __func__, t->tab_id, status);
5377 switch (status) {
5378 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5379 /* -1 */
5380 t->icon_download = NULL;
5381 free_favicon(t);
5382 break;
5383 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5384 /* 0 */
5385 break;
5386 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5387 /* 1 */
5388 break;
5389 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5390 /* 2 */
5391 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5392 __func__, t->tab_id);
5393 t->icon_download = NULL;
5394 free_favicon(t);
5395 break;
5396 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5397 /* 3 */
5398 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5399 __func__, t->icon_dest_uri);
5400 set_favicon_from_file(t, t->icon_dest_uri);
5401 /* these will be freed post callback */
5402 t->icon_request = NULL;
5403 t->icon_download = NULL;
5404 break;
5405 default:
5406 break;
5410 void
5411 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5413 gchar *name_hash, file[PATH_MAX];
5414 struct stat sb;
5416 DNPRINTF(XT_D_DOWNLOAD, "notify_icon_loaded_cb %s\n", uri);
5418 if (uri == NULL || t == NULL)
5419 return;
5421 if (t->icon_request) {
5422 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5423 return;
5426 /* check to see if we got the icon in cache */
5427 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5428 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5429 g_free(name_hash);
5431 if (!stat(file, &sb)) {
5432 if (sb.st_size > 0) {
5433 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5434 __func__, file);
5435 set_favicon_from_file(t, file);
5436 return;
5439 /* corrupt icon so trash it */
5440 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5441 __func__, file);
5442 unlink(file);
5445 /* create download for icon */
5446 t->icon_request = webkit_network_request_new(uri);
5447 if (t->icon_request == NULL) {
5448 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5449 __func__, uri);
5450 return;
5453 t->icon_download = webkit_download_new(t->icon_request);
5455 /* we have to free icon_dest_uri later */
5456 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5457 webkit_download_set_destination_uri(t->icon_download,
5458 t->icon_dest_uri);
5460 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5461 G_CALLBACK(favicon_download_status_changed_cb), t);
5463 webkit_download_start(t->icon_download);
5466 void
5467 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5469 const gchar *set = NULL, *uri = NULL, *title = NULL;
5470 struct history *h, find;
5471 int add = 0;
5472 const gchar *s_loading;
5473 struct karg a;
5475 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d\n",
5476 webkit_web_view_get_load_status(wview));
5478 if (t == NULL) {
5479 show_oops_s("notify_load_status_cb invalid paramters");
5480 return;
5483 switch (webkit_web_view_get_load_status(wview)) {
5484 case WEBKIT_LOAD_PROVISIONAL:
5485 /* 0 */
5486 abort_favicon_download(t);
5487 #if GTK_CHECK_VERSION(2, 20, 0)
5488 gtk_widget_show(t->spinner);
5489 gtk_spinner_start(GTK_SPINNER(t->spinner));
5490 #endif
5491 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5493 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5495 break;
5497 case WEBKIT_LOAD_COMMITTED:
5498 /* 1 */
5499 if ((uri = get_uri(wview)) != NULL) {
5500 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5502 if (t->status) {
5503 g_free(t->status);
5504 t->status = NULL;
5506 set_status(t, (char *)uri, XT_STATUS_LOADING);
5509 /* check if js white listing is enabled */
5510 if (enable_js_whitelist) {
5511 uri = get_uri(wview);
5512 check_and_set_js(uri, t);
5515 show_ca_status(t, uri);
5517 /* we know enough to autosave the session */
5518 if (session_autosave) {
5519 a.s = NULL;
5520 save_tabs(t, &a);
5522 break;
5524 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5525 /* 3 */
5526 title = webkit_web_view_get_title(wview);
5527 uri = get_uri(wview);
5528 if (title)
5529 set = title;
5530 else if (uri)
5531 set = uri;
5532 else
5533 break;
5535 gtk_label_set_text(GTK_LABEL(t->label), set);
5536 gtk_window_set_title(GTK_WINDOW(main_window), set);
5538 if (uri) {
5539 if (!strncmp(uri, "http://", strlen("http://")) ||
5540 !strncmp(uri, "https://", strlen("https://")) ||
5541 !strncmp(uri, "file://", strlen("file://")))
5542 add = 1;
5543 if (add == 0)
5544 break;
5545 find.uri = uri;
5546 h = RB_FIND(history_list, &hl, &find);
5547 if (h)
5548 break;
5550 h = g_malloc(sizeof *h);
5551 h->uri = g_strdup(uri);
5552 if (title)
5553 h->title = g_strdup(title);
5554 else
5555 h->title = g_strdup(uri);
5556 RB_INSERT(history_list, &hl, h);
5557 update_history_tabs(NULL);
5560 break;
5562 case WEBKIT_LOAD_FINISHED:
5563 /* 2 */
5564 uri = get_uri(wview);
5565 set_status(t, (char *)uri, XT_STATUS_URI);
5566 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5567 case WEBKIT_LOAD_FAILED:
5568 /* 4 */
5569 #endif
5570 #if GTK_CHECK_VERSION(2, 20, 0)
5571 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5572 gtk_widget_hide(t->spinner);
5573 #endif
5574 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
5575 if (s_loading && !strcmp(s_loading, "Loading"))
5576 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5577 default:
5578 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
5579 break;
5582 if (t->item)
5583 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
5584 else
5585 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
5586 webkit_web_view_can_go_back(wview));
5588 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
5589 webkit_web_view_can_go_forward(wview));
5591 /* take focus if we are visible */
5592 t->focus_wv = 1;
5593 focus_webview(t);
5596 void
5597 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5599 run_script(t, JS_HINTING);
5602 void
5603 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
5605 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
5606 progress == 100 ? 0 : (double)progress / 100);
5607 if (show_url == 0) {
5608 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
5609 progress == 100 ? 0 : (double)progress / 100);
5614 webview_nw_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5615 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5616 WebKitWebPolicyDecision *pd, struct tab *t)
5618 char *uri;
5620 if (t == NULL) {
5621 show_oops_s("webview_nw_cb invalid paramters");
5622 return (FALSE);
5625 DNPRINTF(XT_D_NAV, "webview_nw_cb: %s\n",
5626 webkit_network_request_get_uri(request));
5628 /* open in current tab */
5629 uri = (char *)webkit_network_request_get_uri(request);
5630 webkit_web_view_load_uri(t->wv, uri);
5631 webkit_web_policy_decision_ignore(pd);
5633 return (TRUE); /* we made the decission */
5637 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5638 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5639 WebKitWebPolicyDecision *pd, struct tab *t)
5641 char *uri;
5643 if (t == NULL) {
5644 show_oops_s("webview_npd_cb invalid parameters");
5645 return (FALSE);
5648 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
5649 t->ctrl_click,
5650 webkit_network_request_get_uri(request));
5652 uri = (char *)webkit_network_request_get_uri(request);
5654 if ((!parse_xtp_url(t, uri) && (t->ctrl_click))) {
5655 t->ctrl_click = 0;
5656 create_new_tab(uri, NULL, ctrl_click_focus);
5657 webkit_web_policy_decision_ignore(pd);
5658 return (TRUE); /* we made the decission */
5661 webkit_web_policy_decision_use(pd);
5662 return (TRUE); /* we made the decission */
5665 WebKitWebView *
5666 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5668 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
5669 webkit_web_view_get_uri(wv));
5671 return (wv);
5675 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
5677 /* we can not eat the event without throwing gtk off so defer it */
5679 /* catch middle click */
5680 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
5681 t->ctrl_click = 1;
5682 goto done;
5685 /* catch ctrl click */
5686 if (e->type == GDK_BUTTON_RELEASE &&
5687 CLEAN(e->state) == GDK_CONTROL_MASK)
5688 t->ctrl_click = 1;
5689 else
5690 t->ctrl_click = 0;
5691 done:
5692 return (XT_CB_PASSTHROUGH);
5696 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
5698 struct mime_type *m;
5700 m = find_mime_type(mime_type);
5701 if (m == NULL)
5702 return (1);
5704 switch (fork()) {
5705 case -1:
5706 show_oops(t, "can't fork mime handler");
5707 /* NOTREACHED */
5708 case 0:
5709 break;
5710 default:
5711 return (0);
5714 /* child */
5715 execlp(m->mt_action, m->mt_action,
5716 webkit_network_request_get_uri(request), (void *)NULL);
5718 _exit(0);
5720 /* NOTREACHED */
5721 return (0);
5725 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
5726 WebKitNetworkRequest *request, char *mime_type,
5727 WebKitWebPolicyDecision *decision, struct tab *t)
5729 if (t == NULL) {
5730 show_oops_s("webview_mimetype_cb invalid parameters");
5731 return (FALSE);
5734 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
5735 t->tab_id, mime_type);
5737 if (run_mimehandler(t, mime_type, request) == 0) {
5738 webkit_web_policy_decision_ignore(decision);
5739 focus_webview(t);
5740 return (TRUE);
5743 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
5744 webkit_web_policy_decision_download(decision);
5745 return (TRUE);
5748 return (FALSE);
5752 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
5753 struct tab *t)
5755 const gchar *filename;
5756 char *uri = NULL;
5757 struct download *download_entry;
5758 int ret = TRUE;
5760 if (wk_download == NULL || t == NULL) {
5761 show_oops_s("%s invalid parameters", __func__);
5762 return (FALSE);
5765 filename = webkit_download_get_suggested_filename(wk_download);
5766 if (filename == NULL)
5767 return (FALSE); /* abort download */
5769 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
5771 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
5772 "local %s\n", __func__, t->tab_id, filename, uri);
5774 webkit_download_set_destination_uri(wk_download, uri);
5776 if (webkit_download_get_status(wk_download) ==
5777 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5778 show_oops(t, "%s: download failed to start", __func__);
5779 ret = FALSE;
5780 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
5781 } else {
5782 download_entry = g_malloc(sizeof(struct download));
5783 download_entry->download = wk_download;
5784 download_entry->tab = t;
5785 download_entry->id = next_download_id++;
5786 RB_INSERT(download_list, &downloads, download_entry);
5787 /* get from history */
5788 g_object_ref(wk_download);
5789 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
5792 if (uri)
5793 g_free(uri);
5795 /* sync other download manager tabs */
5796 update_download_tabs(NULL);
5799 * NOTE: never redirect/render the current tab before this
5800 * function returns. This will cause the download to never start.
5802 return (ret); /* start download */
5805 void
5806 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
5808 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
5810 if (t == NULL) {
5811 show_oops_s("webview_hover_cb");
5812 return;
5815 if (uri)
5816 set_status(t, uri, XT_STATUS_LINK);
5817 else {
5818 if (t->status)
5819 set_status(t, t->status, XT_STATUS_NOTHING);
5824 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
5826 char s[2], buf[128];
5827 const char *errstr = NULL;
5828 long long link;
5830 /* don't use w directly; use t->whatever instead */
5832 if (t == NULL) {
5833 show_oops_s("wv_keypress_after_cb");
5834 return (XT_CB_PASSTHROUGH);
5837 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
5838 e->keyval, e->state, t);
5840 if (t->hints_on) {
5841 /* ESC */
5842 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
5843 disable_hints(t);
5844 return (XT_CB_HANDLED);
5847 /* RETURN */
5848 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
5849 link = strtonum(t->hint_num, 1, 1000, &errstr);
5850 if (errstr) {
5851 /* we have a string */
5852 } else {
5853 /* we have a number */
5854 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
5855 t->hint_num);
5856 run_script(t, buf);
5858 disable_hints(t);
5861 /* BACKSPACE */
5862 /* XXX unfuck this */
5863 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
5864 if (t->hint_mode == XT_HINT_NUMERICAL) {
5865 /* last input was numerical */
5866 int l;
5867 l = strlen(t->hint_num);
5868 if (l > 0) {
5869 l--;
5870 if (l == 0) {
5871 disable_hints(t);
5872 enable_hints(t);
5873 } else {
5874 t->hint_num[l] = '\0';
5875 goto num;
5878 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
5879 /* last input was alphanumerical */
5880 int l;
5881 l = strlen(t->hint_buf);
5882 if (l > 0) {
5883 l--;
5884 if (l == 0) {
5885 disable_hints(t);
5886 enable_hints(t);
5887 } else {
5888 t->hint_buf[l] = '\0';
5889 goto anum;
5892 } else {
5893 /* bogus */
5894 disable_hints(t);
5898 /* numerical input */
5899 if (CLEAN(e->state) == 0 &&
5900 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
5901 snprintf(s, sizeof s, "%c", e->keyval);
5902 strlcat(t->hint_num, s, sizeof t->hint_num);
5903 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
5904 t->hint_num);
5905 num:
5906 link = strtonum(t->hint_num, 1, 1000, &errstr);
5907 if (errstr) {
5908 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
5909 disable_hints(t);
5910 } else {
5911 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
5912 t->hint_num);
5913 t->hint_mode = XT_HINT_NUMERICAL;
5914 run_script(t, buf);
5917 /* empty the counter buffer */
5918 bzero(t->hint_buf, sizeof t->hint_buf);
5919 return (XT_CB_HANDLED);
5922 /* alphanumerical input */
5923 if (
5924 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
5925 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
5926 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
5927 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
5928 snprintf(s, sizeof s, "%c", e->keyval);
5929 strlcat(t->hint_buf, s, sizeof t->hint_buf);
5930 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
5931 t->hint_buf);
5932 anum:
5933 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
5934 run_script(t, buf);
5936 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
5937 t->hint_buf);
5938 t->hint_mode = XT_HINT_ALPHANUM;
5939 run_script(t, buf);
5941 /* empty the counter buffer */
5942 bzero(t->hint_num, sizeof t->hint_num);
5943 return (XT_CB_HANDLED);
5946 return (XT_CB_HANDLED);
5949 struct key_binding *k;
5950 TAILQ_FOREACH(k, &kbl, entry)
5951 if (e->keyval == k->key) {
5952 if (k->mask == 0) {
5953 if ((e->state & (CTRL | MOD1)) == 0) {
5954 k->func(t, &k->arg);
5955 return (XT_CB_HANDLED);
5958 else if ((e->state & k->mask) == k->mask) {
5959 k->func(t, &k->arg);
5960 return (XT_CB_HANDLED);
5964 return (XT_CB_PASSTHROUGH);
5968 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5970 hide_oops(t);
5972 return (XT_CB_PASSTHROUGH);
5976 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5978 const gchar *c = gtk_entry_get_text(w);
5979 GdkColor color;
5980 int forward = TRUE;
5982 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
5983 e->keyval, e->state, t);
5985 if (t == NULL) {
5986 show_oops_s("cmd_keyrelease_cb invalid parameters");
5987 return (XT_CB_PASSTHROUGH);
5990 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
5991 e->keyval, e->state, t);
5993 if (c[0] == ':')
5994 goto done;
5995 if (strlen(c) == 1) {
5996 webkit_web_view_unmark_text_matches(t->wv);
5997 goto done;
6000 if (c[0] == '/')
6001 forward = TRUE;
6002 else if (c[0] == '?')
6003 forward = FALSE;
6004 else
6005 goto done;
6007 /* search */
6008 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6009 FALSE) {
6010 /* not found, mark red */
6011 gdk_color_parse("red", &color);
6012 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6013 /* unmark and remove selection */
6014 webkit_web_view_unmark_text_matches(t->wv);
6015 /* my kingdom for a way to unselect text in webview */
6016 } else {
6017 /* found, highlight all */
6018 webkit_web_view_unmark_text_matches(t->wv);
6019 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6020 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6021 gdk_color_parse("white", &color);
6022 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6024 done:
6025 return (XT_CB_PASSTHROUGH);
6028 #if 0
6030 cmd_complete(struct tab *t, char *s)
6032 int i;
6033 GtkEntry *w = GTK_ENTRY(t->cmd);
6035 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", s);
6037 for (i = 0; i < LENGTH(cmds); i++) {
6038 if (!strncasecmp(cmds[i].cmd, s, strlen(s))) {
6039 fprintf(stderr, "match %s %d\n", cmds[i].cmd, strcasecmp(cmds[i].cmd, s));
6040 #if 0
6041 gtk_entry_set_text(w, ":");
6042 gtk_entry_append_text(w, cmds[i].cmd);
6043 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6044 #endif
6048 return (0);
6050 #endif
6053 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6055 if (t == NULL) {
6056 show_oops_s("entry_key_cb invalid parameters");
6057 return (XT_CB_PASSTHROUGH);
6060 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6061 e->keyval, e->state, t);
6063 hide_oops(t);
6065 if (e->keyval == GDK_Escape) {
6066 /* don't use focus_webview(t) because we want to type :cmds */
6067 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6070 struct key_binding *k;
6071 TAILQ_FOREACH(k, &kbl, entry)
6072 if (e->keyval == k->key && k->use_in_entry) {
6073 if (k->mask == 0) {
6074 if ((e->state & (CTRL | MOD1)) == 0) {
6075 k->func(t, &k->arg);
6076 return (XT_CB_HANDLED);
6079 else if ((e->state & k->mask) == k->mask) {
6080 k->func(t, &k->arg);
6081 return (XT_CB_HANDLED);
6085 return (XT_CB_PASSTHROUGH);
6089 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6091 int rv = XT_CB_HANDLED;
6092 const gchar *c = gtk_entry_get_text(w);
6094 if (t == NULL) {
6095 show_oops_s("cmd_keypress_cb parameters");
6096 return (XT_CB_PASSTHROUGH);
6099 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6100 e->keyval, e->state, t);
6102 /* sanity */
6103 if (c == NULL)
6104 e->keyval = GDK_Escape;
6105 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6106 e->keyval = GDK_Escape;
6108 switch (e->keyval) {
6109 #if 0
6110 case GDK_Tab:
6111 if (c[0] != ':')
6112 goto done;
6114 if (strchr (c, ' ')) {
6115 /* par completion */
6116 fprintf(stderr, "completeme par\n");
6117 goto done;
6120 cmd_complete(t, (char *)&c[1]);
6122 goto done;
6123 #endif
6124 case GDK_BackSpace:
6125 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
6126 break;
6127 /* FALLTHROUGH */
6128 case GDK_Escape:
6129 hide_cmd(t);
6130 focus_webview(t);
6132 /* cancel search */
6133 if (c[0] == '/' || c[0] == '?')
6134 webkit_web_view_unmark_text_matches(t->wv);
6135 goto done;
6138 rv = XT_CB_PASSTHROUGH;
6139 done:
6140 return (rv);
6144 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
6146 if (t == NULL) {
6147 show_oops_s("cmd_focusout_cb invalid parameters");
6148 return (XT_CB_PASSTHROUGH);
6150 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
6152 hide_cmd(t);
6153 hide_oops(t);
6155 if (show_url == 0 || t->focus_wv)
6156 focus_webview(t);
6157 else
6158 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6160 return (XT_CB_PASSTHROUGH);
6163 void
6164 cmd_activate_cb(GtkEntry *entry, struct tab *t)
6166 int i;
6167 char *s;
6168 const gchar *c = gtk_entry_get_text(entry);
6170 if (t == NULL) {
6171 show_oops_s("cmd_activate_cb invalid parameters");
6172 return;
6175 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
6177 /* sanity */
6178 if (c == NULL)
6179 goto done;
6180 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6181 goto done;
6182 if (strlen(c) < 2)
6183 goto done;
6184 s = (char *)&c[1];
6186 if (c[0] == '/' || c[0] == '?') {
6187 if (t->search_text) {
6188 g_free(t->search_text);
6189 t->search_text = NULL;
6192 t->search_text = g_strdup(s);
6193 if (global_search)
6194 g_free(global_search);
6195 global_search = g_strdup(s);
6196 t->search_forward = c[0] == '/';
6198 goto done;
6201 for (i = 0; i < LENGTH(cmds); i++)
6202 if (cmds[i].params) {
6203 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
6204 cmds[i].arg.s = g_strdup(s);
6205 goto execute_command;
6207 } else {
6208 if (!strcmp(s, cmds[i].cmd))
6209 goto execute_command;
6211 show_oops(t, "Invalid command: %s", s);
6212 done:
6213 hide_cmd(t);
6214 return;
6216 execute_command:
6217 hide_cmd(t);
6218 cmds[i].func(t, &cmds[i].arg);
6220 void
6221 backward_cb(GtkWidget *w, struct tab *t)
6223 struct karg a;
6225 if (t == NULL) {
6226 show_oops_s("backward_cb invalid parameters");
6227 return;
6230 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
6232 a.i = XT_NAV_BACK;
6233 navaction(t, &a);
6236 void
6237 forward_cb(GtkWidget *w, struct tab *t)
6239 struct karg a;
6241 if (t == NULL) {
6242 show_oops_s("forward_cb invalid parameters");
6243 return;
6246 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
6248 a.i = XT_NAV_FORWARD;
6249 navaction(t, &a);
6252 void
6253 stop_cb(GtkWidget *w, struct tab *t)
6255 WebKitWebFrame *frame;
6257 if (t == NULL) {
6258 show_oops_s("stop_cb invalid parameters");
6259 return;
6262 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
6264 frame = webkit_web_view_get_main_frame(t->wv);
6265 if (frame == NULL) {
6266 show_oops(t, "stop_cb: no frame");
6267 return;
6270 webkit_web_frame_stop_loading(frame);
6271 abort_favicon_download(t);
6274 void
6275 setup_webkit(struct tab *t)
6277 g_object_set(G_OBJECT(t->settings),
6278 "user-agent", t->user_agent, (char *)NULL);
6279 g_object_set(G_OBJECT(t->settings),
6280 "enable-scripts", enable_scripts, (char *)NULL);
6281 g_object_set(G_OBJECT(t->settings),
6282 "enable-plugins", enable_plugins, (char *)NULL);
6283 adjustfont_webkit(t, XT_FONT_SET);
6285 webkit_web_view_set_settings(t->wv, t->settings);
6288 GtkWidget *
6289 create_browser(struct tab *t)
6291 GtkWidget *w;
6292 gchar *strval;
6294 if (t == NULL) {
6295 show_oops_s("create_browser invalid parameters");
6296 return (NULL);
6299 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
6300 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
6301 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
6302 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
6304 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
6305 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
6306 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
6308 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
6309 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
6311 /* set defaults */
6312 t->settings = webkit_web_settings_new();
6314 if (user_agent == NULL) {
6315 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
6316 (char *)NULL);
6317 t->user_agent = g_strdup_printf("%s %s+", strval, version);
6318 g_free(strval);
6319 } else {
6320 t->user_agent = g_strdup(user_agent);
6323 setup_webkit(t);
6325 return (w);
6328 GtkWidget *
6329 create_window(void)
6331 GtkWidget *w;
6333 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
6334 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
6335 gtk_widget_set_name(w, "xxxterm");
6336 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
6337 g_signal_connect(G_OBJECT(w), "delete_event",
6338 G_CALLBACK (gtk_main_quit), NULL);
6340 return (w);
6343 GtkWidget *
6344 create_toolbar(struct tab *t)
6346 GtkWidget *toolbar = NULL, *b, *eb1;
6348 b = gtk_hbox_new(FALSE, 0);
6349 toolbar = b;
6350 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
6352 if (fancy_bar) {
6353 /* backward button */
6354 t->backward = create_button("GoBack", GTK_STOCK_GO_BACK, 0);
6355 gtk_widget_set_sensitive(t->backward, FALSE);
6356 g_signal_connect(G_OBJECT(t->backward), "clicked",
6357 G_CALLBACK(backward_cb), t);
6358 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
6360 /* forward button */
6361 t->forward = create_button("GoForward",GTK_STOCK_GO_FORWARD, 0);
6362 gtk_widget_set_sensitive(t->forward, FALSE);
6363 g_signal_connect(G_OBJECT(t->forward), "clicked",
6364 G_CALLBACK(forward_cb), t);
6365 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
6366 FALSE, 0);
6368 /* stop button */
6369 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
6370 gtk_widget_set_sensitive(t->stop, FALSE);
6371 g_signal_connect(G_OBJECT(t->stop), "clicked",
6372 G_CALLBACK(stop_cb), t);
6373 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
6374 FALSE, 0);
6376 /* JS button */
6377 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
6378 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
6379 gtk_widget_set_sensitive(t->js_toggle, TRUE);
6380 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
6381 G_CALLBACK(js_toggle_cb), t);
6382 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
6385 t->uri_entry = gtk_entry_new();
6386 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
6387 G_CALLBACK(activate_uri_entry_cb), t);
6388 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
6389 G_CALLBACK(entry_key_cb), t);
6390 eb1 = gtk_hbox_new(FALSE, 0);
6391 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
6392 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
6393 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
6395 /* search entry */
6396 if (fancy_bar && search_string) {
6397 GtkWidget *eb2;
6398 t->search_entry = gtk_entry_new();
6399 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
6400 g_signal_connect(G_OBJECT(t->search_entry), "activate",
6401 G_CALLBACK(activate_search_entry_cb), t);
6402 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
6403 G_CALLBACK(entry_key_cb), t);
6404 gtk_widget_set_size_request(t->search_entry, -1, -1);
6405 eb2 = gtk_hbox_new(FALSE, 0);
6406 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
6407 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
6409 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
6411 return (toolbar);
6414 void
6415 recalc_tabs(void)
6417 struct tab *t;
6419 TAILQ_FOREACH(t, &tabs, entry)
6420 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
6424 undo_close_tab_save(struct tab *t)
6426 int m, n;
6427 const gchar *uri;
6428 struct undo *u1, *u2;
6429 GList *items;
6430 WebKitWebHistoryItem *item;
6432 if ((uri = get_uri(t->wv)) == NULL)
6433 return (1);
6435 u1 = g_malloc0(sizeof(struct undo));
6436 u1->uri = g_strdup(uri);
6438 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
6440 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
6441 n = webkit_web_back_forward_list_get_back_length(t->bfl);
6442 u1->back = n;
6444 /* forward history */
6445 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
6447 while (items) {
6448 item = items->data;
6449 u1->history = g_list_prepend(u1->history,
6450 webkit_web_history_item_copy(item));
6451 items = g_list_next(items);
6454 /* current item */
6455 if (m) {
6456 item = webkit_web_back_forward_list_get_current_item(t->bfl);
6457 u1->history = g_list_prepend(u1->history,
6458 webkit_web_history_item_copy(item));
6461 /* back history */
6462 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
6464 while (items) {
6465 item = items->data;
6466 u1->history = g_list_prepend(u1->history,
6467 webkit_web_history_item_copy(item));
6468 items = g_list_next(items);
6471 TAILQ_INSERT_HEAD(&undos, u1, entry);
6473 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
6474 u2 = TAILQ_LAST(&undos, undo_tailq);
6475 TAILQ_REMOVE(&undos, u2, entry);
6476 g_free(u2->uri);
6477 g_list_free(u2->history);
6478 g_free(u2);
6479 } else
6480 undo_count++;
6482 return (0);
6485 void
6486 delete_tab(struct tab *t)
6488 struct karg a;
6490 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
6492 if (t == NULL)
6493 return;
6495 TAILQ_REMOVE(&tabs, t, entry);
6497 /* halt all webkit activity */
6498 abort_favicon_download(t);
6499 webkit_web_view_stop_loading(t->wv);
6500 undo_close_tab_save(t);
6502 gtk_widget_destroy(t->vbox);
6503 g_free(t->user_agent);
6504 g_free(t);
6506 recalc_tabs();
6507 if (TAILQ_EMPTY(&tabs))
6508 create_new_tab(NULL, NULL, 1);
6511 /* recreate session */
6512 if (session_autosave) {
6513 a.s = NULL;
6514 save_tabs(t, &a);
6518 void
6519 adjustfont_webkit(struct tab *t, int adjust)
6521 if (t == NULL) {
6522 show_oops_s("adjustfont_webkit invalid parameters");
6523 return;
6526 if (adjust == XT_FONT_SET)
6527 t->font_size = default_font_size;
6529 t->font_size += adjust;
6530 g_object_set(G_OBJECT(t->settings), "default-font-size",
6531 t->font_size, (char *)NULL);
6532 g_object_get(G_OBJECT(t->settings), "default-font-size",
6533 &t->font_size, (char *)NULL);
6536 void
6537 append_tab(struct tab *t)
6539 if (t == NULL)
6540 return;
6542 TAILQ_INSERT_TAIL(&tabs, t, entry);
6543 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
6546 void
6547 create_new_tab(char *title, struct undo *u, int focus)
6549 struct tab *t, *tt;
6550 int load = 1, id, notfound;
6551 GtkWidget *b, *bb;
6552 WebKitWebHistoryItem *item;
6553 GList *items;
6554 GdkColor color;
6555 PangoFontDescription *fd = NULL;
6557 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
6559 if (tabless && !TAILQ_EMPTY(&tabs)) {
6560 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
6561 return;
6564 t = g_malloc0(sizeof *t);
6566 if (title == NULL) {
6567 title = "(untitled)";
6568 load = 0;
6571 t->vbox = gtk_vbox_new(FALSE, 0);
6573 /* label + button for tab */
6574 b = gtk_hbox_new(FALSE, 0);
6575 t->tab_content = b;
6577 #if GTK_CHECK_VERSION(2, 20, 0)
6578 t->spinner = gtk_spinner_new ();
6579 #endif
6580 t->label = gtk_label_new(title);
6581 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
6582 gtk_widget_set_size_request(t->label, 100, 0);
6583 gtk_widget_set_size_request(b, 130, 0);
6585 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
6586 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
6587 #if GTK_CHECK_VERSION(2, 20, 0)
6588 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
6589 #endif
6591 /* toolbar */
6592 t->toolbar = create_toolbar(t);
6593 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
6595 /* browser */
6596 t->browser_win = create_browser(t);
6597 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
6599 /* oops message for user feedback */
6600 t->oops = gtk_entry_new();
6601 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
6602 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
6603 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
6604 gdk_color_parse("red", &color);
6605 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
6606 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
6608 /* command entry */
6609 t->cmd = gtk_entry_new();
6610 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
6611 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
6612 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
6614 /* status bar */
6615 t->statusbar = gtk_entry_new();
6616 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
6617 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
6618 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
6619 gdk_color_parse("black", &color);
6620 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
6621 gdk_color_parse("white", &color);
6622 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
6623 fd = GTK_WIDGET(t->statusbar)->style->font_desc;
6624 pango_font_description_set_weight(fd, PANGO_WEIGHT_SEMIBOLD);
6625 pango_font_description_set_absolute_size(fd, 10 * PANGO_SCALE); /* 10 px font */
6626 gtk_widget_modify_font(t->statusbar, fd);
6627 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
6629 /* xtp meaning is normal by default */
6630 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6632 /* set empty favicon */
6633 xt_icon_from_name(t, "text-html");
6635 /* and show it all */
6636 gtk_widget_show_all(b);
6637 gtk_widget_show_all(t->vbox);
6639 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
6640 append_tab(t);
6641 else {
6642 notfound = 1;
6643 id = gtk_notebook_get_current_page(notebook);
6644 TAILQ_FOREACH(tt, &tabs, entry) {
6645 if (tt->tab_id == id) {
6646 notfound = 0;
6647 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
6648 gtk_notebook_insert_page(notebook, t->vbox, b,
6649 id + 1);
6650 recalc_tabs();
6651 break;
6654 if (notfound)
6655 append_tab(t);
6658 #if GTK_CHECK_VERSION(2, 20, 0)
6659 /* turn spinner off if we are a new tab without uri */
6660 if (!load) {
6661 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6662 gtk_widget_hide(t->spinner);
6664 #endif
6665 /* make notebook tabs reorderable */
6666 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
6668 g_object_connect(G_OBJECT(t->cmd),
6669 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
6670 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
6671 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
6672 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
6673 (char *)NULL);
6675 /* reuse wv_button_cb to hide oops */
6676 g_object_connect(G_OBJECT(t->oops),
6677 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
6678 (char *)NULL);
6680 g_object_connect(G_OBJECT(t->wv),
6681 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
6682 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
6683 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
6684 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
6685 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
6686 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
6687 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_nw_cb), t,
6688 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
6689 "signal::event", G_CALLBACK(webview_event_cb), t,
6690 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
6691 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
6692 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6693 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
6694 #endif
6695 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
6696 (char *)NULL);
6697 g_signal_connect(t->wv,
6698 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
6700 /* hijack the unused keys as if we were the browser */
6701 g_object_connect(G_OBJECT(t->toolbar),
6702 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
6703 (char *)NULL);
6705 g_signal_connect(G_OBJECT(bb), "button_press_event",
6706 G_CALLBACK(tab_close_cb), t);
6708 /* hide stuff */
6709 hide_cmd(t);
6710 hide_oops(t);
6711 url_set_visibility();
6712 statusbar_set_visibility();
6714 if (focus) {
6715 gtk_notebook_set_current_page(notebook, t->tab_id);
6716 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
6717 t->tab_id);
6720 if (load) {
6721 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
6722 load_uri(t, title);
6723 } else {
6724 if (show_url == 1)
6725 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6726 else
6727 focus_webview(t);
6730 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
6731 /* restore the tab's history */
6732 if (u && u->history) {
6733 items = u->history;
6734 while (items) {
6735 item = items->data;
6736 webkit_web_back_forward_list_add_item(t->bfl, item);
6737 items = g_list_next(items);
6740 item = g_list_nth_data(u->history, u->back);
6741 if (item)
6742 webkit_web_view_go_to_back_forward_item(t->wv, item);
6744 g_list_free(items);
6745 g_list_free(u->history);
6746 } else
6747 webkit_web_back_forward_list_clear(t->bfl);
6750 void
6751 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
6752 gpointer *udata)
6754 struct tab *t;
6755 const gchar *uri;
6757 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
6759 TAILQ_FOREACH(t, &tabs, entry) {
6760 if (t->tab_id == pn) {
6761 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
6762 "%d\n", pn);
6764 uri = webkit_web_view_get_title(t->wv);
6765 if (uri == NULL)
6766 uri = XT_NAME;
6767 gtk_window_set_title(GTK_WINDOW(main_window), uri);
6769 hide_cmd(t);
6770 hide_oops(t);
6772 if (t->focus_wv)
6773 focus_webview(t);
6778 void
6779 menuitem_response(struct tab *t)
6781 gtk_notebook_set_current_page(notebook, t->tab_id);
6784 gboolean
6785 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
6787 GtkWidget *menu, *menu_items;
6788 GdkEventButton *bevent;
6789 const gchar *uri;
6790 struct tab *ti;
6792 if (event->type == GDK_BUTTON_PRESS) {
6793 bevent = (GdkEventButton *) event;
6794 menu = gtk_menu_new();
6796 TAILQ_FOREACH(ti, &tabs, entry) {
6797 if ((uri = get_uri(ti->wv)) == NULL)
6798 /* XXX make sure there is something to print */
6799 /* XXX add gui pages in here to look purdy */
6800 uri = "(untitled)";
6801 menu_items = gtk_menu_item_new_with_label(uri);
6802 gtk_menu_append(GTK_MENU (menu), menu_items);
6803 gtk_widget_show(menu_items);
6805 gtk_signal_connect_object(GTK_OBJECT(menu_items),
6806 "activate", GTK_SIGNAL_FUNC(menuitem_response),
6807 (gpointer)ti);
6810 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
6811 bevent->button, bevent->time);
6813 /* unref object so it'll free itself when popped down */
6814 g_object_ref_sink(menu);
6815 g_object_unref(menu);
6817 return (TRUE /* eat event */);
6820 return (FALSE /* propagate */);
6824 icon_size_map(int icon_size)
6826 if (icon_size <= GTK_ICON_SIZE_INVALID ||
6827 icon_size > GTK_ICON_SIZE_DIALOG)
6828 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
6830 return (icon_size);
6833 GtkWidget *
6834 create_button(char *name, char *stockid, int size)
6836 GtkWidget *button, *image;
6837 gchar *rcstring;
6838 int gtk_icon_size;
6839 rcstring = g_strdup_printf(
6840 "style \"%s-style\"\n"
6841 "{\n"
6842 " GtkWidget::focus-padding = 0\n"
6843 " GtkWidget::focus-line-width = 0\n"
6844 " xthickness = 0\n"
6845 " ythickness = 0\n"
6846 "}\n"
6847 "widget \"*.%s\" style \"%s-style\"",name,name,name);
6848 gtk_rc_parse_string(rcstring);
6849 g_free(rcstring);
6850 button = gtk_button_new();
6851 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
6852 gtk_icon_size = icon_size_map(size?size:icon_size);
6854 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
6855 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
6856 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
6857 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
6858 gtk_widget_set_name(button, name);
6859 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
6860 gtk_widget_set_tooltip_text(button, name);
6862 return button;
6865 void
6866 button_set_stockid(GtkWidget *button, char *stockid)
6868 GtkWidget *image;
6869 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
6870 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
6871 gtk_button_set_image(GTK_BUTTON(button), image);
6874 void
6875 create_canvas(void)
6877 GtkWidget *vbox;
6878 GList *l = NULL;
6879 GdkPixbuf *pb;
6880 char file[PATH_MAX];
6881 int i;
6883 vbox = gtk_vbox_new(FALSE, 0);
6884 gtk_box_set_spacing(GTK_BOX(vbox), 0);
6885 notebook = GTK_NOTEBOOK(gtk_notebook_new());
6886 gtk_notebook_set_tab_hborder(notebook, 0);
6887 gtk_notebook_set_tab_vborder(notebook, 0);
6888 gtk_notebook_set_scrollable(notebook, TRUE);
6889 notebook_tab_set_visibility(notebook);
6890 gtk_notebook_set_show_border(notebook, FALSE);
6891 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
6893 abtn = gtk_button_new();
6894 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
6895 gtk_widget_set_size_request(arrow, -1, -1);
6896 gtk_container_add(GTK_CONTAINER(abtn), arrow);
6897 gtk_widget_set_size_request(abtn, -1, 20);
6898 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
6900 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
6901 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
6902 gtk_widget_set_size_request(vbox, -1, -1);
6904 g_object_connect(G_OBJECT(notebook),
6905 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
6906 (char *)NULL);
6907 g_signal_connect(G_OBJECT(abtn), "button_press_event",
6908 G_CALLBACK(arrow_cb), NULL);
6910 main_window = create_window();
6911 gtk_container_add(GTK_CONTAINER(main_window), vbox);
6912 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6914 /* icons */
6915 for (i = 0; i < LENGTH(icons); i++) {
6916 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
6917 pb = gdk_pixbuf_new_from_file(file, NULL);
6918 l = g_list_append(l, pb);
6920 gtk_window_set_default_icon_list(l);
6922 gtk_widget_show_all(abtn);
6923 gtk_widget_show_all(main_window);
6926 void
6927 set_hook(void **hook, char *name)
6929 if (hook == NULL)
6930 errx(1, "set_hook");
6932 if (*hook == NULL) {
6933 *hook = dlsym(RTLD_NEXT, name);
6934 if (*hook == NULL)
6935 errx(1, "can't hook %s", name);
6939 /* override libsoup soup_cookie_equal because it doesn't look at domain */
6940 gboolean
6941 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
6943 g_return_val_if_fail(cookie1, FALSE);
6944 g_return_val_if_fail(cookie2, FALSE);
6946 return (!strcmp (cookie1->name, cookie2->name) &&
6947 !strcmp (cookie1->value, cookie2->value) &&
6948 !strcmp (cookie1->path, cookie2->path) &&
6949 !strcmp (cookie1->domain, cookie2->domain));
6952 void
6953 transfer_cookies(void)
6955 GSList *cf;
6956 SoupCookie *sc, *pc;
6958 cf = soup_cookie_jar_all_cookies(p_cookiejar);
6960 for (;cf; cf = cf->next) {
6961 pc = cf->data;
6962 sc = soup_cookie_copy(pc);
6963 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
6966 soup_cookies_free(cf);
6969 void
6970 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
6972 GSList *cf;
6973 SoupCookie *ci;
6975 print_cookie("soup_cookie_jar_delete_cookie", c);
6977 if (cookies_enabled == 0)
6978 return;
6980 if (jar == NULL || c == NULL)
6981 return;
6983 /* find and remove from persistent jar */
6984 cf = soup_cookie_jar_all_cookies(p_cookiejar);
6986 for (;cf; cf = cf->next) {
6987 ci = cf->data;
6988 if (soup_cookie_equal(ci, c)) {
6989 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
6990 break;
6994 soup_cookies_free(cf);
6996 /* delete from session jar */
6997 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
7000 void
7001 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
7003 struct domain *d = NULL;
7004 SoupCookie *c;
7005 FILE *r_cookie_f;
7007 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
7008 jar, p_cookiejar, s_cookiejar);
7010 if (cookies_enabled == 0)
7011 return;
7013 /* see if we are up and running */
7014 if (p_cookiejar == NULL) {
7015 _soup_cookie_jar_add_cookie(jar, cookie);
7016 return;
7018 /* disallow p_cookiejar adds, shouldn't happen */
7019 if (jar == p_cookiejar)
7020 return;
7022 if (enable_cookie_whitelist &&
7023 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
7024 blocked_cookies++;
7025 DNPRINTF(XT_D_COOKIE,
7026 "soup_cookie_jar_add_cookie: reject %s\n",
7027 cookie->domain);
7028 if (save_rejected_cookies) {
7029 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
7030 show_oops_s("can't open reject cookie file");
7031 return;
7033 fseek(r_cookie_f, 0, SEEK_END);
7034 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7035 cookie->http_only ? "#HttpOnly_" : "",
7036 cookie->domain,
7037 *cookie->domain == '.' ? "TRUE" : "FALSE",
7038 cookie->path,
7039 cookie->secure ? "TRUE" : "FALSE",
7040 cookie->expires ?
7041 (gulong)soup_date_to_time_t(cookie->expires) :
7043 cookie->name,
7044 cookie->value);
7045 fflush(r_cookie_f);
7046 fclose(r_cookie_f);
7048 if (!allow_volatile_cookies)
7049 return;
7052 if (cookie->expires == NULL && session_timeout) {
7053 soup_cookie_set_expires(cookie,
7054 soup_date_new_from_now(session_timeout));
7055 print_cookie("modified add cookie", cookie);
7058 /* see if we are white listed for persistence */
7059 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
7060 /* add to persistent jar */
7061 c = soup_cookie_copy(cookie);
7062 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
7063 _soup_cookie_jar_add_cookie(p_cookiejar, c);
7066 /* add to session jar */
7067 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
7068 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
7071 void
7072 setup_cookies(void)
7074 char file[PATH_MAX];
7076 set_hook((void *)&_soup_cookie_jar_add_cookie,
7077 "soup_cookie_jar_add_cookie");
7078 set_hook((void *)&_soup_cookie_jar_delete_cookie,
7079 "soup_cookie_jar_delete_cookie");
7081 if (cookies_enabled == 0)
7082 return;
7085 * the following code is intricate due to overriding several libsoup
7086 * functions.
7087 * do not alter order of these operations.
7090 /* rejected cookies */
7091 if (save_rejected_cookies)
7092 snprintf(rc_fname, sizeof file, "%s/rejected.txt", work_dir);
7094 /* persistent cookies */
7095 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
7096 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
7098 /* session cookies */
7099 s_cookiejar = soup_cookie_jar_new();
7100 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
7101 cookie_policy, (void *)NULL);
7102 transfer_cookies();
7104 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
7107 void
7108 setup_proxy(char *uri)
7110 if (proxy_uri) {
7111 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
7112 soup_uri_free(proxy_uri);
7113 proxy_uri = NULL;
7115 if (http_proxy) {
7116 if (http_proxy != uri) {
7117 g_free(http_proxy);
7118 http_proxy = NULL;
7122 if (uri) {
7123 http_proxy = g_strdup(uri);
7124 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
7125 proxy_uri = soup_uri_new(http_proxy);
7126 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
7131 send_url_to_socket(char *url)
7133 int s, len, rv = -1;
7134 struct sockaddr_un sa;
7136 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7137 warnx("send_url_to_socket: socket");
7138 return (-1);
7141 sa.sun_family = AF_UNIX;
7142 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7143 work_dir, XT_SOCKET_FILE);
7144 len = SUN_LEN(&sa);
7146 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7147 warnx("send_url_to_socket: connect");
7148 goto done;
7151 if (send(s, url, strlen(url) + 1, 0) == -1) {
7152 warnx("send_url_to_socket: send");
7153 goto done;
7155 done:
7156 close(s);
7157 return (rv);
7160 void
7161 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
7163 int s, n;
7164 char str[XT_MAX_URL_LENGTH];
7165 socklen_t t = sizeof(struct sockaddr_un);
7166 struct sockaddr_un sa;
7167 struct passwd *p;
7168 uid_t uid;
7169 gid_t gid;
7171 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
7172 warn("socket_watcher: accept");
7173 return;
7176 if (getpeereid(s, &uid, &gid) == -1) {
7177 warn("socket_watcher: getpeereid");
7178 return;
7180 if (uid != getuid() || gid != getgid()) {
7181 warnx("socket_watcher: unauthorized user");
7182 return;
7185 p = getpwuid(uid);
7186 if (p == NULL) {
7187 warnx("socket_watcher: not a valid user");
7188 return;
7191 n = recv(s, str, sizeof(str), 0);
7192 if (n <= 0)
7193 return;
7195 create_new_tab(str, NULL, 1);
7199 is_running(void)
7201 int s, len, rv = 1;
7202 struct sockaddr_un sa;
7204 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7205 warn("is_running: socket");
7206 return (-1);
7209 sa.sun_family = AF_UNIX;
7210 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7211 work_dir, XT_SOCKET_FILE);
7212 len = SUN_LEN(&sa);
7214 /* connect to see if there is a listener */
7215 if (connect(s, (struct sockaddr *)&sa, len) == -1)
7216 rv = 0; /* not running */
7217 else
7218 rv = 1; /* already running */
7220 close(s);
7222 return (rv);
7226 build_socket(void)
7228 int s, len;
7229 struct sockaddr_un sa;
7231 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
7232 warn("build_socket: socket");
7233 return (-1);
7236 sa.sun_family = AF_UNIX;
7237 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
7238 work_dir, XT_SOCKET_FILE);
7239 len = SUN_LEN(&sa);
7241 /* connect to see if there is a listener */
7242 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
7243 /* no listener so we will */
7244 unlink(sa.sun_path);
7246 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
7247 warn("build_socket: bind");
7248 goto done;
7251 if (listen(s, 1) == -1) {
7252 warn("build_socket: listen");
7253 goto done;
7256 return (s);
7259 done:
7260 close(s);
7261 return (-1);
7264 void
7265 usage(void)
7267 fprintf(stderr,
7268 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
7269 exit(0);
7273 main(int argc, char *argv[])
7275 struct stat sb;
7276 int c, s, optn = 0, focus = 1;
7277 char conf[PATH_MAX] = { '\0' };
7278 char file[PATH_MAX];
7279 char *env_proxy = NULL;
7280 FILE *f = NULL;
7281 struct karg a;
7282 struct sigaction sact;
7284 start_argv = argv;
7286 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
7288 while ((c = getopt(argc, argv, "STVf:s:tn")) != -1) {
7289 switch (c) {
7290 case 'S':
7291 show_url = 0;
7292 break;
7293 case 'T':
7294 show_tabs = 0;
7295 break;
7296 case 'V':
7297 errx(0 , "Version: %s", version);
7298 break;
7299 case 'f':
7300 strlcpy(conf, optarg, sizeof(conf));
7301 break;
7302 case 's':
7303 strlcpy(named_session, optarg, sizeof(named_session));
7304 break;
7305 case 't':
7306 tabless = 1;
7307 break;
7308 case 'n':
7309 optn = 1;
7310 break;
7311 default:
7312 usage();
7313 /* NOTREACHED */
7316 argc -= optind;
7317 argv += optind;
7319 RB_INIT(&hl);
7320 RB_INIT(&js_wl);
7321 RB_INIT(&downloads);
7323 TAILQ_INIT(&tabs);
7324 TAILQ_INIT(&mtl);
7325 TAILQ_INIT(&aliases);
7326 TAILQ_INIT(&undos);
7327 TAILQ_INIT(&kbl);
7329 init_keybindings();
7331 gnutls_global_init();
7333 /* generate session keys for xtp pages */
7334 generate_xtp_session_key(&dl_session_key);
7335 generate_xtp_session_key(&hl_session_key);
7336 generate_xtp_session_key(&cl_session_key);
7337 generate_xtp_session_key(&fl_session_key);
7339 /* prepare gtk */
7340 gtk_init(&argc, &argv);
7341 if (!g_thread_supported())
7342 g_thread_init(NULL);
7344 /* signals */
7345 bzero(&sact, sizeof(sact));
7346 sigemptyset(&sact.sa_mask);
7347 sact.sa_handler = sigchild;
7348 sact.sa_flags = SA_NOCLDSTOP;
7349 sigaction(SIGCHLD, &sact, NULL);
7351 /* set download dir */
7352 pwd = getpwuid(getuid());
7353 if (pwd == NULL)
7354 errx(1, "invalid user %d", getuid());
7355 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
7357 /* set default string settings */
7358 home = g_strdup("http://www.peereboom.us");
7359 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
7360 resource_dir = g_strdup("/usr/local/share/xxxterm/");
7361 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
7363 /* read config file */
7364 if (strlen(conf) == 0)
7365 snprintf(conf, sizeof conf, "%s/.%s",
7366 pwd->pw_dir, XT_CONF_FILE);
7367 config_parse(conf, 0);
7369 /* working directory */
7370 if (strlen(work_dir) == 0)
7371 snprintf(work_dir, sizeof work_dir, "%s/%s",
7372 pwd->pw_dir, XT_DIR);
7373 if (stat(work_dir, &sb)) {
7374 if (mkdir(work_dir, S_IRWXU) == -1)
7375 err(1, "mkdir work_dir");
7376 if (stat(work_dir, &sb))
7377 err(1, "stat work_dir");
7379 if (S_ISDIR(sb.st_mode) == 0)
7380 errx(1, "%s not a dir", work_dir);
7381 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7382 warnx("fixing invalid permissions on %s", work_dir);
7383 if (chmod(work_dir, S_IRWXU) == -1)
7384 err(1, "chmod");
7387 /* icon cache dir */
7388 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
7389 if (stat(cache_dir, &sb)) {
7390 if (mkdir(cache_dir, S_IRWXU) == -1)
7391 err(1, "mkdir cache_dir");
7392 if (stat(cache_dir, &sb))
7393 err(1, "stat cache_dir");
7395 if (S_ISDIR(sb.st_mode) == 0)
7396 errx(1, "%s not a dir", cache_dir);
7397 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7398 warnx("fixing invalid permissions on %s", cache_dir);
7399 if (chmod(cache_dir, S_IRWXU) == -1)
7400 err(1, "chmod");
7403 /* certs dir */
7404 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
7405 if (stat(certs_dir, &sb)) {
7406 if (mkdir(certs_dir, S_IRWXU) == -1)
7407 err(1, "mkdir certs_dir");
7408 if (stat(certs_dir, &sb))
7409 err(1, "stat certs_dir");
7411 if (S_ISDIR(sb.st_mode) == 0)
7412 errx(1, "%s not a dir", certs_dir);
7413 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7414 warnx("fixing invalid permissions on %s", certs_dir);
7415 if (chmod(certs_dir, S_IRWXU) == -1)
7416 err(1, "chmod");
7419 /* sessions dir */
7420 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
7421 work_dir, XT_SESSIONS_DIR);
7422 if (stat(sessions_dir, &sb)) {
7423 if (mkdir(sessions_dir, S_IRWXU) == -1)
7424 err(1, "mkdir sessions_dir");
7425 if (stat(sessions_dir, &sb))
7426 err(1, "stat sessions_dir");
7428 if (S_ISDIR(sb.st_mode) == 0)
7429 errx(1, "%s not a dir", sessions_dir);
7430 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7431 warnx("fixing invalid permissions on %s", sessions_dir);
7432 if (chmod(sessions_dir, S_IRWXU) == -1)
7433 err(1, "chmod");
7435 /* runtime settings that can override config file */
7436 if (runtime_settings[0] != '\0')
7437 config_parse(runtime_settings, 1);
7439 /* download dir */
7440 if (!strcmp(download_dir, pwd->pw_dir))
7441 strlcat(download_dir, "/downloads", sizeof download_dir);
7442 if (stat(download_dir, &sb)) {
7443 if (mkdir(download_dir, S_IRWXU) == -1)
7444 err(1, "mkdir download_dir");
7445 if (stat(download_dir, &sb))
7446 err(1, "stat download_dir");
7448 if (S_ISDIR(sb.st_mode) == 0)
7449 errx(1, "%s not a dir", download_dir);
7450 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7451 warnx("fixing invalid permissions on %s", download_dir);
7452 if (chmod(download_dir, S_IRWXU) == -1)
7453 err(1, "chmod");
7456 /* favorites file */
7457 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
7458 if (stat(file, &sb)) {
7459 warnx("favorites file doesn't exist, creating it");
7460 if ((f = fopen(file, "w")) == NULL)
7461 err(1, "favorites");
7462 fclose(f);
7465 /* cookies */
7466 session = webkit_get_default_session();
7467 setup_cookies();
7469 /* certs */
7470 if (ssl_ca_file) {
7471 if (stat(ssl_ca_file, &sb)) {
7472 warn("no CA file: %s", ssl_ca_file);
7473 g_free(ssl_ca_file);
7474 ssl_ca_file = NULL;
7475 } else
7476 g_object_set(session,
7477 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
7478 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
7479 (void *)NULL);
7482 /* proxy */
7483 env_proxy = getenv("http_proxy");
7484 if (env_proxy)
7485 setup_proxy(env_proxy);
7486 else
7487 setup_proxy(http_proxy);
7489 /* see if there is already an xxxterm running */
7490 if (single_instance && is_running()) {
7491 optn = 1;
7492 warnx("already running");
7495 if (optn) {
7496 while (argc) {
7497 send_url_to_socket(argv[0]);
7499 argc--;
7500 argv++;
7502 exit(0);
7505 /* go graphical */
7506 create_canvas();
7508 if (save_global_history)
7509 restore_global_history();
7511 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
7512 restore_saved_tabs();
7513 else {
7514 a.s = named_session;
7515 a.i = XT_SES_DONOTHING;
7516 open_tabs(NULL, &a);
7519 while (argc) {
7520 create_new_tab(argv[0], NULL, focus);
7521 focus = 0;
7523 argc--;
7524 argv++;
7527 if (TAILQ_EMPTY(&tabs))
7528 create_new_tab(home, NULL, 1);
7530 if (enable_socket)
7531 if ((s = build_socket()) != -1)
7532 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
7534 gtk_main();
7536 gnutls_global_deinit();
7538 return (0);