fix online man page
[xxxterm.git] / xxxterm.c
blob4e1bd51f53d0556b551e69da5a62b487a00e5d0e
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 <util.h>
40 #include <pthread.h>
41 #include <dlfcn.h>
42 #include <errno.h>
44 #include <sys/types.h>
45 #if defined(__linux__)
46 #include "linux/util.h"
47 #include "linux/tree.h"
48 #elif defined(__FreeBSD__)
49 #include <libutil.h>
50 #include "freebsd/util.h"
51 #include <sys/tree.h>
52 #else /* OpenBSD */
53 #include <util.h>
54 #include <sys/tree.h>
55 #endif
56 #include <sys/queue.h>
57 #include <sys/stat.h>
58 #include <sys/socket.h>
59 #include <sys/un.h>
61 #include <gtk/gtk.h>
62 #include <gdk/gdkkeysyms.h>
63 #include <webkit/webkit.h>
64 #include <libsoup/soup.h>
65 #include <gnutls/gnutls.h>
66 #include <JavaScriptCore/JavaScript.h>
67 #include <gnutls/x509.h>
69 #include "javascript.h"
72 javascript.h borrowed from vimprobable2 under the following license:
74 Copyright (c) 2009 Leon Winter
75 Copyright (c) 2009 Hannes Schueller
76 Copyright (c) 2009 Matto Fransen
78 Permission is hereby granted, free of charge, to any person obtaining a copy
79 of this software and associated documentation files (the "Software"), to deal
80 in the Software without restriction, including without limitation the rights
81 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
82 copies of the Software, and to permit persons to whom the Software is
83 furnished to do so, subject to the following conditions:
85 The above copyright notice and this permission notice shall be included in
86 all copies or substantial portions of the Software.
88 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
89 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
90 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
91 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
92 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
93 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
94 THE SOFTWARE.
97 static char *version = "$xxxterm$";
99 /* hooked functions */
100 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
101 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
102 SoupCookie *);
104 /*#define XT_DEBUG*/
105 #ifdef XT_DEBUG
106 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
107 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
108 #define XT_D_MOVE 0x0001
109 #define XT_D_KEY 0x0002
110 #define XT_D_TAB 0x0004
111 #define XT_D_URL 0x0008
112 #define XT_D_CMD 0x0010
113 #define XT_D_NAV 0x0020
114 #define XT_D_DOWNLOAD 0x0040
115 #define XT_D_CONFIG 0x0080
116 #define XT_D_JS 0x0100
117 #define XT_D_FAVORITE 0x0200
118 #define XT_D_PRINTING 0x0400
119 #define XT_D_COOKIE 0x0800
120 u_int32_t swm_debug = 0
121 | XT_D_MOVE
122 | XT_D_KEY
123 | XT_D_TAB
124 | XT_D_URL
125 | XT_D_CMD
126 | XT_D_NAV
127 | XT_D_DOWNLOAD
128 | XT_D_CONFIG
129 | XT_D_JS
130 | XT_D_FAVORITE
131 | XT_D_PRINTING
132 | XT_D_COOKIE
134 #else
135 #define DPRINTF(x...)
136 #define DNPRINTF(n,x...)
137 #endif
139 #define LENGTH(x) (sizeof x / sizeof x[0])
140 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
141 ~(GDK_BUTTON1_MASK) & \
142 ~(GDK_BUTTON2_MASK) & \
143 ~(GDK_BUTTON3_MASK) & \
144 ~(GDK_BUTTON4_MASK) & \
145 ~(GDK_BUTTON5_MASK))
147 char *icons[] = {
148 "xxxtermicon16.png",
149 "xxxtermicon32.png",
150 "xxxtermicon48.png",
151 "xxxtermicon64.png",
152 "xxxtermicon128.png"
155 struct tab {
156 TAILQ_ENTRY(tab) entry;
157 GtkWidget *vbox;
158 GtkWidget *tab_content;
159 GtkWidget *label;
160 GtkWidget *spinner;
161 GtkWidget *uri_entry;
162 GtkWidget *search_entry;
163 GtkWidget *toolbar;
164 GtkWidget *browser_win;
165 GtkWidget *cmd;
166 GtkWidget *oops;
167 GtkWidget *backward;
168 GtkWidget *forward;
169 GtkWidget *stop;
170 GtkWidget *js_toggle;
171 guint tab_id;
172 WebKitWebView *wv;
174 WebKitWebHistoryItem *item;
175 WebKitWebBackForwardList *bfl;
177 /* favicon */
178 WebKitNetworkRequest *icon_request;
179 WebKitDownload *icon_download;
180 GdkPixbuf *icon_pixbuf;
181 gchar *icon_dest_uri;
183 /* adjustments for browser */
184 GtkScrollbar *sb_h;
185 GtkScrollbar *sb_v;
186 GtkAdjustment *adjust_h;
187 GtkAdjustment *adjust_v;
189 /* flags */
190 int focus_wv;
191 int ctrl_click;
192 gchar *hover;
193 int xtp_meaning; /* identifies dls/favorites */
195 /* hints */
196 int hints_on;
197 int hint_mode;
198 #define XT_HINT_NONE (0)
199 #define XT_HINT_NUMERICAL (1)
200 #define XT_HINT_ALPHANUM (2)
201 char hint_buf[128];
202 char hint_num[128];
204 /* search */
205 char *search_text;
206 int search_forward;
208 /* settings */
209 WebKitWebSettings *settings;
210 int font_size;
211 gchar *user_agent;
213 TAILQ_HEAD(tab_list, tab);
215 struct history {
216 RB_ENTRY(history) entry;
217 const gchar *uri;
218 const gchar *title;
220 RB_HEAD(history_list, history);
222 struct download {
223 RB_ENTRY(download) entry;
224 int id;
225 WebKitDownload *download;
226 struct tab *tab;
228 RB_HEAD(download_list, download);
230 struct domain {
231 RB_ENTRY(domain) entry;
232 gchar *d;
233 int handy; /* app use */
235 RB_HEAD(domain_list, domain);
237 struct undo {
238 TAILQ_ENTRY(undo) entry;
239 gchar *uri;
240 GList *history;
241 int back; /* Keeps track of how many back
242 * history items there are. */
244 TAILQ_HEAD(undo_tailq, undo);
246 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
247 int next_download_id = 1;
249 struct karg {
250 int i;
251 char *s;
254 /* defines */
255 #define XT_NAME ("XXXTerm")
256 #define XT_DIR (".xxxterm")
257 #define XT_CACHE_DIR ("cache")
258 #define XT_CERT_DIR ("certs/")
259 #define XT_SESSIONS_DIR ("sessions/")
260 #define XT_CONF_FILE ("xxxterm.conf")
261 #define XT_FAVS_FILE ("favorites")
262 #define XT_SAVED_TABS_FILE ("main_session")
263 #define XT_RESTART_TABS_FILE ("restart_tabs")
264 #define XT_SOCKET_FILE ("socket")
265 #define XT_HISTORY_FILE ("history")
266 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
267 #define XT_CB_HANDLED (TRUE)
268 #define XT_CB_PASSTHROUGH (FALSE)
269 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
270 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>"
271 #define XT_DLMAN_REFRESH "10"
272 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
273 "td {overflow: hidden;}\n" \
274 "th {background-color: #cccccc}" \
275 "table {width: 90%%; border: 1px black" \
276 " solid; table-layout: fixed}\n</style>\n\n"
277 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
278 #define XT_MAX_UNDO_CLOSE_TAB (32)
280 /* file sizes */
281 #define SZ_KB ((uint64_t) 1024)
282 #define SZ_MB (SZ_KB * SZ_KB)
283 #define SZ_GB (SZ_KB * SZ_KB * SZ_KB)
284 #define SZ_TB (SZ_KB * SZ_KB * SZ_KB * SZ_KB)
287 * xxxterm "protocol" (xtp)
288 * We use this for managing stuff like downloads and favorites. They
289 * make magical HTML pages in memory which have xxxt:// links in order
290 * to communicate with xxxterm's internals. These links take the format:
291 * xxxt://class/session_key/action/arg
293 * Don't begin xtp class/actions as 0. atoi returns that on error.
295 * Typically we have not put addition of items in this framework, as
296 * adding items is either done via an ex-command or via a keybinding instead.
299 #define XT_XTP_STR "xxxt://"
301 /* XTP classes (xxxt://<class>) */
302 #define XT_XTP_DL 1 /* downloads */
303 #define XT_XTP_HL 2 /* history */
304 #define XT_XTP_CL 3 /* cookies */
305 #define XT_XTP_FL 4 /* favorites */
307 /* XTP download actions */
308 #define XT_XTP_DL_LIST 1
309 #define XT_XTP_DL_CANCEL 2
310 #define XT_XTP_DL_REMOVE 3
312 /* XTP history actions */
313 #define XT_XTP_HL_LIST 1
314 #define XT_XTP_HL_REMOVE 2
316 /* XTP cookie actions */
317 #define XT_XTP_CL_LIST 1
318 #define XT_XTP_CL_REMOVE 2
320 /* XTP cookie actions */
321 #define XT_XTP_FL_LIST 1
322 #define XT_XTP_FL_REMOVE 2
324 /* xtp tab meanings - identifies which tabs have xtp pages in */
325 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
326 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
327 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
328 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
329 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
331 /* actions */
332 #define XT_MOVE_INVALID (0)
333 #define XT_MOVE_DOWN (1)
334 #define XT_MOVE_UP (2)
335 #define XT_MOVE_BOTTOM (3)
336 #define XT_MOVE_TOP (4)
337 #define XT_MOVE_PAGEDOWN (5)
338 #define XT_MOVE_PAGEUP (6)
339 #define XT_MOVE_HALFDOWN (7)
340 #define XT_MOVE_HALFUP (8)
341 #define XT_MOVE_LEFT (9)
342 #define XT_MOVE_FARLEFT (10)
343 #define XT_MOVE_RIGHT (11)
344 #define XT_MOVE_FARRIGHT (12)
346 #define XT_TAB_LAST (-4)
347 #define XT_TAB_FIRST (-3)
348 #define XT_TAB_PREV (-2)
349 #define XT_TAB_NEXT (-1)
350 #define XT_TAB_INVALID (0)
351 #define XT_TAB_NEW (1)
352 #define XT_TAB_DELETE (2)
353 #define XT_TAB_DELQUIT (3)
354 #define XT_TAB_OPEN (4)
355 #define XT_TAB_UNDO_CLOSE (5)
356 #define XT_TAB_SHOW (6)
357 #define XT_TAB_HIDE (7)
359 #define XT_NAV_INVALID (0)
360 #define XT_NAV_BACK (1)
361 #define XT_NAV_FORWARD (2)
362 #define XT_NAV_RELOAD (3)
363 #define XT_NAV_RELOAD_CACHE (4)
365 #define XT_FOCUS_INVALID (0)
366 #define XT_FOCUS_URI (1)
367 #define XT_FOCUS_SEARCH (2)
369 #define XT_SEARCH_INVALID (0)
370 #define XT_SEARCH_NEXT (1)
371 #define XT_SEARCH_PREV (2)
373 #define XT_PASTE_CURRENT_TAB (0)
374 #define XT_PASTE_NEW_TAB (1)
376 #define XT_FONT_SET (0)
378 #define XT_URL_SHOW (1)
379 #define XT_URL_HIDE (2)
381 #define XT_WL_TOGGLE (1<<0)
382 #define XT_WL_ENABLE (1<<1)
383 #define XT_WL_DISABLE (1<<2)
384 #define XT_WL_FQDN (1<<3) /* default */
385 #define XT_WL_TOPLEVEL (1<<4)
387 #define XT_CMD_OPEN (0)
388 #define XT_CMD_OPEN_CURRENT (1)
389 #define XT_CMD_TABNEW (2)
390 #define XT_CMD_TABNEW_CURRENT (3)
392 #define XT_SES_DONOTHING (0)
393 #define XT_SES_CLOSETABS (1)
395 /* mime types */
396 struct mime_type {
397 char *mt_type;
398 char *mt_action;
399 int mt_default;
400 TAILQ_ENTRY(mime_type) entry;
402 TAILQ_HEAD(mime_type_list, mime_type);
404 /* uri aliases */
405 struct alias {
406 char *a_name;
407 char *a_uri;
408 TAILQ_ENTRY(alias) entry;
410 TAILQ_HEAD(alias_list, alias);
412 /* settings that require restart */
413 int show_tabs = 1; /* show tabs on notebook */
414 int show_url = 1; /* show url toolbar on notebook */
415 int tabless = 0; /* allow only 1 tab */
416 int enable_socket = 0;
417 int single_instance = 0; /* only allow one xxxterm to run */
418 int fancy_bar = 1; /* fancy toolbar */
420 /* runtime settings */
421 int ctrl_click_focus = 0; /* ctrl click gets focus */
422 int cookies_enabled = 1; /* enable cookies */
423 int read_only_cookies = 0; /* enable to not write cookies */
424 int enable_scripts = 0;
425 int enable_plugins = 0;
426 int default_font_size = 12;
427 int window_height = 768;
428 int window_width = 1024;
429 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
430 unsigned refresh_interval = 10; /* download refresh interval */
431 int enable_cookie_whitelist = 1;
432 int enable_js_whitelist = 1;
433 time_t session_timeout = 3600; /* cookie session timeout */
434 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
435 char *ssl_ca_file = NULL;
436 char *resource_dir = NULL;
437 gboolean ssl_strict_certs = FALSE;
438 int append_next = 1; /* append tab after current tab */
439 char *home = NULL;
440 char *search_string = NULL;
441 char *http_proxy = NULL;
442 char download_dir[PATH_MAX];
443 char runtime_settings[PATH_MAX]; /* override of settings */
444 int allow_volatile_cookies = 0;
445 int save_global_history = 0; /* save global history to disk */
446 char *user_agent = NULL;
447 int save_rejected_cookies = 0;
448 time_t session_autosave = 0;
450 struct settings;
451 int set_download_dir(struct settings *, char *);
452 int set_runtime_dir(struct settings *, char *);
453 int set_cookie_policy(struct settings *, char *);
454 int add_alias(struct settings *, char *);
455 int add_mime_type(struct settings *, char *);
456 int add_cookie_wl(struct settings *, char *);
457 int add_js_wl(struct settings *, char *);
458 void button_set_stockid(GtkWidget *, char *);
459 GtkWidget * create_button(char *, char *, int);
461 char *get_cookie_policy(struct settings *);
463 char *get_download_dir(struct settings *);
464 char *get_runtime_dir(struct settings *);
466 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
467 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
468 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
469 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
471 struct special {
472 int (*set)(struct settings *, char *);
473 char *(*get)(struct settings *);
474 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
477 struct special s_cookie = {
478 set_cookie_policy,
479 get_cookie_policy,
480 NULL
483 struct special s_alias = {
484 add_alias,
485 NULL,
486 walk_alias
489 struct special s_mime = {
490 add_mime_type,
491 NULL,
492 walk_mime_type
495 struct special s_js = {
496 add_js_wl,
497 NULL,
498 walk_js_wl
501 struct special s_cookie_wl = {
502 add_cookie_wl,
503 NULL,
504 walk_cookie_wl
507 struct special s_download_dir = {
508 set_download_dir,
509 get_download_dir,
510 NULL
513 struct settings {
514 char *name;
515 int type;
516 #define XT_S_INVALID (0)
517 #define XT_S_INT (1)
518 #define XT_S_STR (2)
519 uint32_t flags;
520 #define XT_SF_RESTART (1<<0)
521 #define XT_SF_RUNTIME (1<<1)
522 int *ival;
523 char **sval;
524 struct special *s;
525 } rs[] = {
526 { "append_next", XT_S_INT, 0 , &append_next, NULL, NULL },
527 { "allow_volatile_cookies", XT_S_INT, 0 , &allow_volatile_cookies, NULL, NULL },
528 { "cookies_enabled", XT_S_INT, 0 , &cookies_enabled, NULL, NULL },
529 { "cookie_policy", XT_S_INT, 0 , NULL, NULL, &s_cookie },
530 { "ctrl_click_focus", XT_S_INT, 0 , &ctrl_click_focus, NULL, NULL },
531 { "default_font_size", XT_S_INT, 0 , &default_font_size, NULL, NULL },
532 { "download_dir", XT_S_STR, 0 , NULL, NULL, &s_download_dir },
533 { "enable_cookie_whitelist", XT_S_INT, 0 , &enable_cookie_whitelist, NULL, NULL },
534 { "enable_js_whitelist", XT_S_INT, 0 , &enable_js_whitelist, NULL, NULL },
535 { "enable_plugins", XT_S_INT, 0 , &enable_plugins, NULL, NULL },
536 { "enable_scripts", XT_S_INT, 0 , &enable_scripts, NULL, NULL },
537 { "enable_socket", XT_S_INT, XT_SF_RESTART , &enable_socket, NULL, NULL },
538 { "fancy_bar", XT_S_INT, XT_SF_RESTART , &fancy_bar, NULL, NULL },
539 { "home", XT_S_STR, 0 , NULL, &home, NULL },
540 { "http_proxy", XT_S_STR, 0 , NULL, &http_proxy, NULL },
541 { "icon_size", XT_S_INT, 0 , &icon_size, NULL, NULL },
542 { "read_only_cookies", XT_S_INT, 0 , &read_only_cookies, NULL, NULL },
543 { "refresh_interval", XT_S_INT, 0 , &refresh_interval, NULL, NULL },
544 { "resource_dir", XT_S_STR, 0 , NULL, &resource_dir, NULL },
545 { "search_string", XT_S_STR, 0 , NULL, &search_string, NULL },
546 { "save_global_history", XT_S_INT, XT_SF_RESTART , &save_global_history, NULL, NULL },
547 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART , &save_rejected_cookies, NULL, NULL },
548 { "session_timeout", XT_S_INT, 0 , &session_timeout, NULL, NULL },
549 { "session_autosave", XT_S_INT, 0 , &session_autosave, NULL, NULL },
550 { "single_instance", XT_S_INT, XT_SF_RESTART , &single_instance, NULL, NULL },
551 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
552 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
553 { "ssl_ca_file", XT_S_STR, 0 , NULL, &ssl_ca_file, NULL },
554 { "ssl_strict_certs", XT_S_INT, 0 , &ssl_strict_certs, NULL, NULL },
555 { "user_agent", XT_S_STR, 0 , NULL, &user_agent, NULL },
556 { "window_height", XT_S_INT, 0 , &window_height, NULL, NULL },
557 { "window_width", XT_S_INT, 0 , &window_width, NULL, NULL },
559 /* runtime settings */
560 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
561 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
562 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
563 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
566 /* globals */
567 extern char *__progname;
568 char **start_argv;
569 struct passwd *pwd;
570 GtkWidget *main_window;
571 GtkNotebook *notebook;
572 GtkWidget *arrow, *abtn;
573 struct tab_list tabs;
574 struct history_list hl;
575 struct download_list downloads;
576 struct domain_list c_wl;
577 struct domain_list js_wl;
578 struct undo_tailq undos;
579 int undo_count;
580 int updating_dl_tabs = 0;
581 int updating_hl_tabs = 0;
582 int updating_cl_tabs = 0;
583 int updating_fl_tabs = 0;
584 char *global_search;
585 uint64_t blocked_cookies = 0;
586 char named_session[PATH_MAX];
587 void update_favicon(struct tab *);
588 int icon_size_map(int);
590 void
591 load_webkit_string(struct tab *t, const char *str)
593 /* we set this to indicate we want to manually do navaction */
594 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
595 webkit_web_view_load_string(t->wv, str, NULL, NULL, NULL);
598 void
599 hide_oops(struct tab *t)
601 gtk_widget_hide(t->oops);
604 void
605 hide_cmd(struct tab *t)
607 gtk_widget_hide(t->cmd);
610 void
611 show_cmd(struct tab *t)
613 gtk_widget_hide(t->oops);
614 gtk_widget_show(t->cmd);
617 void
618 show_oops(struct tab *t, const char *fmt, ...)
620 va_list ap;
621 char *msg;
623 if (fmt == NULL)
624 return;
626 va_start(ap, fmt);
627 if (vasprintf(&msg, fmt, ap) == -1)
628 errx(1, "moo");
629 va_end(ap);
631 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
632 gtk_widget_hide(t->cmd);
633 gtk_widget_show(t->oops);
635 char *
636 get_as_string(struct settings *s)
638 char *r = NULL;
640 if (s == NULL)
641 return (NULL);
643 if (s->s) {
644 if (s->s->get)
645 r = s->s->get(s);
646 else
647 warnx("get_as_string skip %s\n", s->name);
648 } else if (s->type == XT_S_INT)
649 r = g_strdup_printf("%d", *s->ival);
650 else if (s->type == XT_S_STR)
651 r = g_strdup(*s->sval);
652 else
653 r = g_strdup_printf("INVALID TYPE");
655 return (r);
658 void
659 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
661 int i;
662 char *s;
664 for (i = 0; i < LENGTH(rs); i++) {
665 if (rs[i].s && rs[i].s->walk)
666 rs[i].s->walk(&rs[i], cb, cb_args);
667 else {
668 s = get_as_string(&rs[i]);
669 cb(&rs[i], s, cb_args);
670 g_free(s);
676 set_cookie_policy(struct settings *s, char *val)
678 if (!strcmp(val, "no3rdparty"))
679 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
680 else if (!strcmp(val, "accept"))
681 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
682 else if (!strcmp(val, "reject"))
683 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
684 else
685 return (1);
687 return (0);
690 char *
691 get_cookie_policy(struct settings *s)
693 char *r = NULL;
695 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
696 r = g_strdup("no3rdparty");
697 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
698 r = g_strdup("accept");
699 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
700 r = g_strdup("reject");
701 else
702 return (NULL);
704 return (r);
707 char *
708 get_download_dir(struct settings *s)
710 if (download_dir[0] == '\0')
711 return (0);
712 return (g_strdup(download_dir));
716 set_download_dir(struct settings *s, char *val)
718 if (val[0] == '~')
719 snprintf(download_dir, sizeof download_dir, "%s/%s",
720 pwd->pw_dir, &val[1]);
721 else
722 strlcpy(download_dir, val, sizeof download_dir);
724 return (0);
728 * Session IDs.
729 * We use these to prevent people putting xxxt:// URLs on
730 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
732 #define XT_XTP_SES_KEY_SZ 8
733 #define XT_XTP_SES_KEY_HEX_FMT \
734 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
735 char *dl_session_key; /* downloads */
736 char *hl_session_key; /* history list */
737 char *cl_session_key; /* cookie list */
738 char *fl_session_key; /* favorites list */
740 char work_dir[PATH_MAX];
741 char certs_dir[PATH_MAX];
742 char cache_dir[PATH_MAX];
743 char sessions_dir[PATH_MAX];
744 char cookie_file[PATH_MAX];
745 SoupURI *proxy_uri = NULL;
746 SoupSession *session;
747 SoupCookieJar *s_cookiejar;
748 SoupCookieJar *p_cookiejar;
749 char rc_fname[PATH_MAX];
751 struct mime_type_list mtl;
752 struct alias_list aliases;
754 /* protos */
755 void create_new_tab(char *, struct undo *, int);
756 void delete_tab(struct tab *);
757 void adjustfont_webkit(struct tab *, int);
758 int run_script(struct tab *, char *);
759 int download_rb_cmp(struct download *, struct download *);
760 int xtp_page_hl(struct tab *t, struct karg *args);
761 int xtp_page_dl(struct tab *t, struct karg *args);
762 int xtp_page_cl(struct tab *t, struct karg *args);
763 int xtp_page_fl(struct tab *t, struct karg *args);
766 history_rb_cmp(struct history *h1, struct history *h2)
768 return (strcmp(h1->uri, h2->uri));
770 RB_GENERATE(history_list, history, entry, history_rb_cmp);
773 domain_rb_cmp(struct domain *d1, struct domain *d2)
775 return (strcmp(d1->d, d2->d));
777 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
780 * generate a session key to secure xtp commands.
781 * pass in a ptr to the key in question and it will
782 * be modified in place.
784 void
785 generate_xtp_session_key(char **key)
787 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
789 /* free old key */
790 if (*key)
791 g_free(*key);
793 /* make a new one */
794 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
795 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
796 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
797 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
799 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
803 * validate a xtp session key.
804 * return 1 if OK
807 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
809 if (strcmp(trusted, untrusted) != 0) {
810 show_oops(t, "%s: xtp session key mismatch possible spoof",
811 __func__);
812 return (0);
815 return (1);
819 download_rb_cmp(struct download *e1, struct download *e2)
821 return (e1->id < e2->id ? -1 : e1->id > e2->id);
823 RB_GENERATE(download_list, download, entry, download_rb_cmp);
825 struct valid_url_types {
826 char *type;
827 } vut[] = {
828 { "http://" },
829 { "https://" },
830 { "ftp://" },
831 { "file://" },
832 { XT_XTP_STR },
836 valid_url_type(char *url)
838 int i;
840 for (i = 0; i < LENGTH(vut); i++)
841 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
842 return (0);
844 return (1);
847 void
848 print_cookie(char *msg, SoupCookie *c)
850 if (c == NULL)
851 return;
853 if (msg)
854 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
855 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
856 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
857 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
858 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
859 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
860 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
861 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
862 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
863 DNPRINTF(XT_D_COOKIE, "====================================\n");
866 void
867 walk_alias(struct settings *s,
868 void (*cb)(struct settings *, char *, void *), void *cb_args)
870 struct alias *a;
871 char *str;
873 if (s == NULL || cb == NULL)
874 errx(1, "walk_alias");
876 TAILQ_FOREACH(a, &aliases, entry) {
877 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
878 cb(s, str, cb_args);
879 g_free(str);
883 char *
884 match_alias(char *url_in)
886 struct alias *a;
887 char *arg;
888 char *url_out = NULL, *search;
890 search = g_strdup(url_in);
891 arg = search;
892 if (strsep(&arg, " \t") == NULL)
893 errx(1, "match_alias: NULL URL");
895 TAILQ_FOREACH(a, &aliases, entry) {
896 if (!strcmp(search, a->a_name))
897 break;
900 if (a != NULL) {
901 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
902 a->a_name);
903 if (arg != NULL)
904 url_out = g_strdup_printf(a->a_uri, arg);
905 else
906 url_out = g_strdup(a->a_uri);
909 g_free(search);
911 return (url_out);
914 char *
915 guess_url_type(char *url_in)
917 struct stat sb;
918 char *url_out = NULL;
920 url_out = match_alias(url_in);
921 if (url_out != NULL)
922 return (url_out);
924 /* XXX not sure about this heuristic */
925 if (stat(url_in, &sb) == 0)
926 url_out = g_strdup_printf("file://%s", url_in);
927 else
928 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
930 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
932 return (url_out);
936 add_alias(struct settings *s, char *line)
938 char *l, *alias;
939 struct alias *a;
941 if (line == NULL)
942 errx(1, "add_alias");
943 l = line;
945 a = g_malloc(sizeof(*a));
947 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL)
948 errx(1, "add_alias: incomplete alias definition");
950 if (strlen(alias) == 0 || strlen(l) == 0)
951 errx(1, "add_alias: invalid alias definition");
953 a->a_name = g_strdup(alias);
954 a->a_uri = g_strdup(l);
956 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
958 TAILQ_INSERT_TAIL(&aliases, a, entry);
960 return (0);
964 add_mime_type(struct settings *s, char *line)
966 char *mime_type;
967 char *l = NULL;
968 struct mime_type *m;
970 /* XXX this could be smarter */
972 if (line == NULL)
973 errx(1, "add_mime_type");
974 l = line;
976 m = g_malloc(sizeof(*m));
978 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL)
979 errx(1, "add_mime_type: invalid mime_type");
981 if (mime_type[strlen(mime_type) - 1] == '*') {
982 mime_type[strlen(mime_type) - 1] = '\0';
983 m->mt_default = 1;
984 } else
985 m->mt_default = 0;
987 if (strlen(mime_type) == 0 || strlen(l) == 0)
988 errx(1, "add_mime_type: invalid mime_type");
990 m->mt_type = g_strdup(mime_type);
991 m->mt_action = g_strdup(l);
993 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
994 m->mt_type, m->mt_action, m->mt_default);
996 TAILQ_INSERT_TAIL(&mtl, m, entry);
998 return (0);
1001 struct mime_type *
1002 find_mime_type(char *mime_type)
1004 struct mime_type *m, *def = NULL, *rv = NULL;
1006 TAILQ_FOREACH(m, &mtl, entry) {
1007 if (m->mt_default &&
1008 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1009 def = m;
1011 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1012 rv = m;
1013 break;
1017 if (rv == NULL)
1018 rv = def;
1020 return (rv);
1023 void
1024 walk_mime_type(struct settings *s,
1025 void (*cb)(struct settings *, char *, void *), void *cb_args)
1027 struct mime_type *m;
1028 char *str;
1030 if (s == NULL || cb == NULL)
1031 errx(1, "walk_mime_type");
1033 TAILQ_FOREACH(m, &mtl, entry) {
1034 str = g_strdup_printf("%s%s --> %s",
1035 m->mt_type,
1036 m->mt_default ? "*" : "",
1037 m->mt_action);
1038 cb(s, str, cb_args);
1039 g_free(str);
1043 void
1044 wl_add(char *str, struct domain_list *wl, int handy)
1046 struct domain *d;
1047 int add_dot = 0;
1049 if (str == NULL || wl == NULL)
1050 return;
1051 if (strlen(str) < 2)
1052 return;
1054 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1056 /* treat *.moo.com the same as .moo.com */
1057 if (str[0] == '*' && str[1] == '.')
1058 str = &str[1];
1059 else if (str[0] == '.')
1060 str = &str[0];
1061 else
1062 add_dot = 1;
1064 d = g_malloc(sizeof *d);
1065 if (add_dot)
1066 d->d = g_strdup_printf(".%s", str);
1067 else
1068 d->d = g_strdup(str);
1069 d->handy = handy;
1071 if (RB_INSERT(domain_list, wl, d))
1072 goto unwind;
1074 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1075 return;
1076 unwind:
1077 if (d) {
1078 if (d->d)
1079 g_free(d->d);
1080 g_free(d);
1085 add_cookie_wl(struct settings *s, char *entry)
1087 wl_add(entry, &c_wl, 1);
1088 return (0);
1091 void
1092 walk_cookie_wl(struct settings *s,
1093 void (*cb)(struct settings *, char *, void *), void *cb_args)
1095 struct domain *d;
1097 if (s == NULL || cb == NULL)
1098 errx(1, "walk_cookie_wl");
1100 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1101 cb(s, d->d, cb_args);
1104 void
1105 walk_js_wl(struct settings *s,
1106 void (*cb)(struct settings *, char *, void *), void *cb_args)
1108 struct domain *d;
1110 if (s == NULL || cb == NULL)
1111 errx(1, "walk_js_wl");
1113 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1114 cb(s, d->d, cb_args);
1118 add_js_wl(struct settings *s, char *entry)
1120 wl_add(entry, &js_wl, 1 /* persistent */);
1121 return (0);
1124 struct domain *
1125 wl_find(const gchar *search, struct domain_list *wl)
1127 int i;
1128 struct domain *d = NULL, dfind;
1129 gchar *s = NULL;
1131 if (search == NULL || wl == NULL)
1132 return (NULL);
1133 if (strlen(search) < 2)
1134 return (NULL);
1136 if (search[0] != '.')
1137 s = g_strdup_printf(".%s", search);
1138 else
1139 s = g_strdup(search);
1141 for (i = strlen(s) - 1; i >= 0; i--) {
1142 if (s[i] == '.') {
1143 dfind.d = &s[i];
1144 d = RB_FIND(domain_list, wl, &dfind);
1145 if (d)
1146 goto done;
1150 done:
1151 if (s)
1152 g_free(s);
1154 return (d);
1157 struct domain *
1158 wl_find_uri(const gchar *s, struct domain_list *wl)
1160 int i;
1161 char *ss;
1162 struct domain *r;
1164 if (s == NULL || wl == NULL)
1165 return (NULL);
1167 if (!strncmp(s, "http://", strlen("http://")))
1168 s = &s[strlen("http://")];
1169 else if (!strncmp(s, "https://", strlen("https://")))
1170 s = &s[strlen("https://")];
1172 if (strlen(s) < 2)
1173 return (NULL);
1175 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1176 /* chop string at first slash */
1177 if (s[i] == '/' || s[i] == '\0') {
1178 ss = g_strdup(s);
1179 ss[i] = '\0';
1180 r = wl_find(ss, wl);
1181 g_free(ss);
1182 return (r);
1185 return (NULL);
1188 char *
1189 get_toplevel_domain(char *domain)
1191 char *s;
1192 int found = 0;
1194 if (domain == NULL)
1195 return (NULL);
1196 if (strlen(domain) < 2)
1197 return (NULL);
1199 s = &domain[strlen(domain) - 1];
1200 while (s != domain) {
1201 if (*s == '.') {
1202 found++;
1203 if (found == 2)
1204 return (s);
1206 s--;
1209 if (found)
1210 return (domain);
1212 return (NULL);
1215 #define WS "\n= \t"
1216 void
1217 config_parse(char *filename, int runtime)
1219 FILE *config, *f;
1220 char *line, *cp, *var, *val;
1221 size_t len, lineno = 0;
1222 int i, handled, *p;
1223 char **s, file[PATH_MAX];
1224 struct stat sb;
1226 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1228 if (filename == NULL)
1229 return;
1231 if (runtime && runtime_settings[0] != '\0') {
1232 snprintf(file, sizeof file, "%s/%s",
1233 work_dir, runtime_settings);
1234 if (stat(file, &sb)) {
1235 warnx("runtime file doesn't exist, creating it");
1236 if ((f = fopen(file, "w")) == NULL)
1237 err(1, "runtime");
1238 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1239 fclose(f);
1241 } else
1242 strlcpy(file, filename, sizeof file);
1244 if ((config = fopen(file, "r")) == NULL) {
1245 warn("config_parse: cannot open %s", filename);
1246 return;
1249 for (;;) {
1250 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1251 if (feof(config) || ferror(config))
1252 break;
1254 cp = line;
1255 cp += (long)strspn(cp, WS);
1256 if (cp[0] == '\0') {
1257 /* empty line */
1258 free(line);
1259 continue;
1262 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1263 break;
1265 cp += (long)strspn(cp, WS);
1267 if ((val = strsep(&cp, "\0")) == NULL)
1268 break;
1270 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1272 /* get settings */
1273 for (i = 0, handled = 0; i < LENGTH(rs); i++) {
1274 if (strcmp(var, rs[i].name))
1275 continue;
1277 if (rs[i].s) {
1278 if (rs[i].s->set(&rs[i], val))
1279 errx(1, "invalid value for %s", var);
1280 handled = 1;
1281 break;
1282 } else
1283 switch (rs[i].type) {
1284 case XT_S_INT:
1285 p = rs[i].ival;
1286 *p = atoi(val);
1287 handled = 1;
1288 break;
1289 case XT_S_STR:
1290 s = rs[i].sval;
1291 if (s == NULL)
1292 errx(1, "invalid sval for %s",
1293 rs[i].name);
1294 if (*s)
1295 g_free(*s);
1296 *s = g_strdup(val);
1297 handled = 1;
1298 break;
1299 case XT_S_INVALID:
1300 default:
1301 errx(1, "invalid type for %s", var);
1303 break;
1305 if (handled == 0)
1306 errx(1, "invalid conf file entry: %s=%s", var, val);
1308 free(line);
1311 fclose(config);
1314 char *
1315 js_ref_to_string(JSContextRef context, JSValueRef ref)
1317 char *s = NULL;
1318 size_t l;
1319 JSStringRef jsref;
1321 jsref = JSValueToStringCopy(context, ref, NULL);
1322 if (jsref == NULL)
1323 return (NULL);
1325 l = JSStringGetMaximumUTF8CStringSize(jsref);
1326 s = g_malloc(l);
1327 if (s)
1328 JSStringGetUTF8CString(jsref, s, l);
1329 JSStringRelease(jsref);
1331 return (s);
1334 void
1335 disable_hints(struct tab *t)
1337 bzero(t->hint_buf, sizeof t->hint_buf);
1338 bzero(t->hint_num, sizeof t->hint_num);
1339 run_script(t, "vimprobable_clear()");
1340 t->hints_on = 0;
1341 t->hint_mode = XT_HINT_NONE;
1344 void
1345 enable_hints(struct tab *t)
1347 bzero(t->hint_buf, sizeof t->hint_buf);
1348 run_script(t, "vimprobable_show_hints()");
1349 t->hints_on = 1;
1350 t->hint_mode = XT_HINT_NONE;
1353 #define XT_JS_OPEN ("open;")
1354 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1355 #define XT_JS_FIRE ("fire;")
1356 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1357 #define XT_JS_FOUND ("found;")
1358 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1361 run_script(struct tab *t, char *s)
1363 JSGlobalContextRef ctx;
1364 WebKitWebFrame *frame;
1365 JSStringRef str;
1366 JSValueRef val, exception;
1367 char *es, buf[128];
1369 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1370 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1372 frame = webkit_web_view_get_main_frame(t->wv);
1373 ctx = webkit_web_frame_get_global_context(frame);
1375 str = JSStringCreateWithUTF8CString(s);
1376 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1377 NULL, 0, &exception);
1378 JSStringRelease(str);
1380 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1381 if (val == NULL) {
1382 es = js_ref_to_string(ctx, exception);
1383 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1384 g_free(es);
1385 return (1);
1386 } else {
1387 es = js_ref_to_string(ctx, val);
1388 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1390 /* handle return value right here */
1391 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1392 disable_hints(t);
1393 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1396 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1397 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1398 &es[XT_JS_FIRE_LEN]);
1399 run_script(t, buf);
1400 disable_hints(t);
1403 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1404 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1405 disable_hints(t);
1408 g_free(es);
1411 return (0);
1415 hint(struct tab *t, struct karg *args)
1418 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1420 if (t->hints_on == 0)
1421 enable_hints(t);
1422 else
1423 disable_hints(t);
1425 return (0);
1428 /* Doesn't work fully, due to the following bug:
1429 * https://bugs.webkit.org/show_bug.cgi?id=51747
1432 restore_global_history(void)
1434 char file[PATH_MAX];
1435 FILE *f;
1436 struct history *h;
1437 gchar *uri;
1438 gchar *title;
1440 snprintf(file, sizeof file, "%s/%s/%s",
1441 pwd->pw_dir, XT_DIR, XT_HISTORY_FILE);
1443 if ((f = fopen(file, "r")) == NULL) {
1444 warnx("%s: fopen", __func__);
1445 return (1);
1448 for (;;) {
1449 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1450 if (feof(f) || ferror(f))
1451 break;
1453 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1454 if (feof(f) || ferror(f)) {
1455 free(uri);
1456 warnx("%s: broken history file\n", __func__);
1457 return (1);
1460 if (uri && strlen(uri) && title && strlen(title)) {
1461 webkit_web_history_item_new_with_data(uri, title);
1462 h = g_malloc(sizeof(struct history));
1463 h->uri = g_strdup(uri);
1464 h->title = g_strdup(title);
1465 RB_INSERT(history_list, &hl, h);
1466 } else {
1467 warnx("%s: failed to restore history\n", __func__);
1468 free(uri);
1469 free(title);
1470 return (1);
1473 free(uri);
1474 free(title);
1475 uri = NULL;
1476 title = NULL;
1479 return (0);
1483 save_global_history_to_disk(struct tab *t)
1485 char file[PATH_MAX];
1486 FILE *f;
1487 struct history *h;
1489 snprintf(file, sizeof file, "%s/%s/%s",
1490 pwd->pw_dir, XT_DIR, XT_HISTORY_FILE);
1492 if ((f = fopen(file, "w")) == NULL) {
1493 show_oops(t, "%s: global history file: %s",
1494 __func__, strerror(errno));
1495 return (1);
1498 RB_FOREACH_REVERSE(h, history_list, &hl) {
1499 if (h->uri && h->title)
1500 fprintf(f, "%s\n%s\n", h->uri, h->title);
1503 fclose(f);
1505 return (0);
1509 quit(struct tab *t, struct karg *args)
1511 if (save_global_history)
1512 save_global_history_to_disk(t);
1514 gtk_main_quit();
1516 return (1);
1520 open_tabs(struct tab *t, struct karg *a)
1522 char file[PATH_MAX];
1523 FILE *f = NULL;
1524 char *uri = NULL;
1525 int rv = 1;
1526 struct tab *ti, *tt;
1528 if (a == NULL)
1529 goto done;
1531 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
1532 if ((f = fopen(file, "r")) == NULL)
1533 goto done;
1535 ti = TAILQ_LAST(&tabs, tab_list);
1537 for (;;) {
1538 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1539 if (feof(f) || ferror(f))
1540 break;
1542 /* retrieve session name */
1543 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
1544 strlcpy(named_session,
1545 &uri[strlen(XT_SAVE_SESSION_ID)],
1546 sizeof named_session);
1547 continue;
1550 if (uri && strlen(uri))
1551 create_new_tab(uri, NULL, 1);
1553 free(uri);
1554 uri = NULL;
1557 /* close open tabs */
1558 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
1559 for (;;) {
1560 tt = TAILQ_FIRST(&tabs);
1561 if (tt != ti) {
1562 delete_tab(tt);
1563 continue;
1565 delete_tab(tt);
1566 break;
1570 rv = 0;
1571 done:
1572 if (f)
1573 fclose(f);
1575 return (rv);
1579 restore_saved_tabs(void)
1581 char file[PATH_MAX];
1582 int unlink_file = 0;
1583 struct stat sb;
1584 struct karg a;
1585 int rv = 0;
1587 snprintf(file, sizeof file, "%s/%s",
1588 sessions_dir, XT_RESTART_TABS_FILE);
1589 if (stat(file, &sb) == -1)
1590 a.s = XT_SAVED_TABS_FILE;
1591 else {
1592 unlink_file = 1;
1593 a.s = XT_RESTART_TABS_FILE;
1596 a.i = XT_SES_DONOTHING;
1597 rv = open_tabs(NULL, &a);
1599 if (unlink_file)
1600 unlink(file);
1602 return (rv);
1606 save_tabs(struct tab *t, struct karg *a)
1608 char file[PATH_MAX];
1609 FILE *f;
1610 struct tab *ti;
1611 WebKitWebFrame *frame;
1612 const gchar *uri;
1613 int len = 0, i;
1614 gchar **arr = NULL;
1616 if (a == NULL)
1617 return (1);
1618 if (a->s == NULL)
1619 snprintf(file, sizeof file, "%s/%s",
1620 sessions_dir, named_session);
1621 else
1622 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
1624 if ((f = fopen(file, "w")) == NULL) {
1625 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
1626 return (1);
1629 /* save session name */
1630 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
1632 /* save tabs, in the order they are arranged in the notebook */
1633 TAILQ_FOREACH(ti, &tabs, entry)
1634 len++;
1636 arr = g_malloc0(len * sizeof(gchar *));
1638 TAILQ_FOREACH(ti, &tabs, entry) {
1639 frame = webkit_web_view_get_main_frame(ti->wv);
1640 uri = webkit_web_frame_get_uri(frame);
1641 if (uri && strlen(uri) > 0)
1642 arr[gtk_notebook_page_num(notebook, ti->vbox)] = (gchar *)uri;
1645 for (i = 0; i < len; i++)
1646 if (arr[i])
1647 fprintf(f, "%s\n", arr[i]);
1649 g_free(arr);
1650 fclose(f);
1652 return (0);
1656 save_tabs_and_quit(struct tab *t, struct karg *args)
1658 struct karg a;
1660 a.s = NULL;
1661 save_tabs(t, &a);
1662 quit(t, NULL);
1664 return (1);
1668 yank_uri(struct tab *t, struct karg *args)
1670 WebKitWebFrame *frame;
1671 const gchar *uri;
1672 GtkClipboard *clipboard;
1674 frame = webkit_web_view_get_main_frame(t->wv);
1675 uri = webkit_web_frame_get_uri(frame);
1676 if (!uri)
1677 return (1);
1679 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1680 gtk_clipboard_set_text(clipboard, uri, -1);
1682 return (0);
1685 struct paste_args {
1686 struct tab *t;
1687 int i;
1690 void
1691 paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
1693 struct paste_args *pap;
1695 if (data == NULL)
1696 return;
1698 pap = (struct paste_args *)data;
1700 switch(pap->i) {
1701 case XT_PASTE_CURRENT_TAB:
1702 webkit_web_view_load_uri(pap->t->wv, text);
1703 break;
1704 case XT_PASTE_NEW_TAB:
1705 create_new_tab((char *)text, NULL, 1);
1706 break;
1709 g_free(pap);
1713 paste_uri(struct tab *t, struct karg *args)
1715 GtkClipboard *clipboard;
1716 struct paste_args *pap;
1718 pap = g_malloc(sizeof(struct paste_args));
1720 pap->t = t;
1721 pap->i = args->i;
1723 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1724 gtk_clipboard_request_text(clipboard, paste_uri_cb, pap);
1726 return (0);
1729 char *
1730 find_domain(const char *s, int add_dot)
1732 int i;
1733 char *r = NULL, *ss = NULL;
1735 if (s == NULL)
1736 return (NULL);
1738 if (!strncmp(s, "http://", strlen("http://")))
1739 s = &s[strlen("http://")];
1740 else if (!strncmp(s, "https://", strlen("https://")))
1741 s = &s[strlen("https://")];
1743 if (strlen(s) < 2)
1744 return (NULL);
1746 ss = g_strdup(s);
1747 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
1748 /* chop string at first slash */
1749 if (ss[i] == '/' || ss[i] == '\0') {
1750 ss[i] = '\0';
1751 if (add_dot)
1752 r = g_strdup_printf(".%s", ss);
1753 else
1754 r = g_strdup(ss);
1755 break;
1757 g_free(ss);
1759 return (r);
1763 toggle_cwl(struct tab *t, struct karg *args)
1765 WebKitWebFrame *frame;
1766 struct domain *d;
1767 char *uri;
1768 char *dom = NULL, *dom_toggle = NULL;
1769 int es;
1771 if (args == NULL)
1772 return (0);
1774 frame = webkit_web_view_get_main_frame(t->wv);
1775 uri = (char *)webkit_web_frame_get_uri(frame);
1776 dom = find_domain(uri, 1);
1777 d = wl_find(dom, &c_wl);
1778 if (d == NULL)
1779 es = 0;
1780 else
1781 es = 1;
1783 if (args->i & XT_WL_TOGGLE)
1784 es = !es;
1785 else if ((args->i & XT_WL_ENABLE) && es != 1)
1786 es = 1;
1787 else if ((args->i & XT_WL_DISABLE) && es != 0)
1788 es = 0;
1790 if (args->i & XT_WL_TOPLEVEL)
1791 dom_toggle = get_toplevel_domain(dom);
1792 else
1793 dom_toggle = dom;
1795 if (es)
1796 /* enable cookies for domain */
1797 wl_add(dom_toggle, &c_wl, 0);
1798 else
1799 /* disable cookies for domain */
1800 RB_REMOVE(domain_list, &c_wl, d);
1802 webkit_web_view_reload(t->wv);
1804 g_free(dom);
1805 return (0);
1809 toggle_js(struct tab *t, struct karg *args)
1811 int es;
1812 WebKitWebFrame *frame;
1813 const gchar *uri;
1814 struct domain *d;
1815 char *dom = NULL, *dom_toggle = NULL;
1817 if (args == NULL)
1818 return (0);
1820 g_object_get((GObject *)t->settings,
1821 "enable-scripts", &es, (char *)NULL);
1822 if (args->i & XT_WL_TOGGLE)
1823 es = !es;
1824 else if ((args->i & XT_WL_ENABLE) && es != 1)
1825 es = 1;
1826 else if ((args->i & XT_WL_DISABLE) && es != 0)
1827 es = 0;
1828 else
1829 return (0);
1831 frame = webkit_web_view_get_main_frame(t->wv);
1832 uri = (char *)webkit_web_frame_get_uri(frame);
1833 dom = find_domain(uri, 1);
1834 if (uri == NULL || dom == NULL) {
1835 show_oops(t, "Can't toggle domain in JavaScript white list");
1836 goto done;
1839 if (args->i & XT_WL_TOPLEVEL)
1840 dom_toggle = get_toplevel_domain(dom);
1841 else
1842 dom_toggle = dom;
1844 if (es) {
1845 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
1846 wl_add(dom_toggle, &js_wl, 0 /* session */);
1847 } else {
1848 d = wl_find(dom_toggle, &js_wl);
1849 if (d)
1850 RB_REMOVE(domain_list, &js_wl, d);
1851 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
1853 g_object_set((GObject *)t->settings,
1854 "enable-scripts", es, (char *)NULL);
1855 webkit_web_view_set_settings(t->wv, t->settings);
1856 webkit_web_view_reload(t->wv);
1857 done:
1858 if (dom)
1859 g_free(dom);
1860 return (0);
1863 void
1864 js_toggle_cb(GtkWidget *w, struct tab *t)
1866 struct karg a;
1868 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
1869 toggle_js(t, &a);
1873 toggle_src(struct tab *t, struct karg *args)
1875 gboolean mode;
1877 if (t == NULL)
1878 return (0);
1880 mode = webkit_web_view_get_view_source_mode(t->wv);
1881 webkit_web_view_set_view_source_mode(t->wv, !mode);
1882 webkit_web_view_reload(t->wv);
1884 return (0);
1888 focus(struct tab *t, struct karg *args)
1890 if (t == NULL || args == NULL)
1891 return (1);
1893 if (show_url == 0)
1894 return (0);
1896 if (args->i == XT_FOCUS_URI)
1897 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
1898 else if (args->i == XT_FOCUS_SEARCH)
1899 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
1901 return (0);
1905 stats(struct tab *t, struct karg *args)
1907 char *stats, *s, line[64 * 1024];
1908 uint64_t line_count = 0;
1909 FILE *r_cookie_f;
1911 if (t == NULL)
1912 errx(1, "stats");
1914 line[0] = '\0';
1915 if (save_rejected_cookies) {
1916 if ((r_cookie_f = fopen(rc_fname, "r"))) {
1917 for (;;) {
1918 s = fgets(line, sizeof line, r_cookie_f);
1919 if (s == NULL || feof(r_cookie_f) ||
1920 ferror(r_cookie_f))
1921 break;
1922 line_count++;
1924 fclose(r_cookie_f);
1925 snprintf(line, sizeof line,
1926 "<br>Cookies blocked(*) total: %llu", line_count);
1927 } else
1928 show_oops(t, "Can't open blocked cookies file: %s",
1929 strerror(errno));
1932 stats = g_strdup_printf(XT_DOCTYPE
1933 "<html>"
1934 "<head>"
1935 "<title>Statistics</title>"
1936 "</head>"
1937 "<h1>Statistics</h1>"
1938 "<body>"
1939 "Cookies blocked(*) this session: %llu"
1940 "%s"
1941 "<p><small><b>*</b> results vary based on settings"
1942 "</body>"
1943 "</html>",
1944 blocked_cookies,
1945 line);
1947 load_webkit_string(t, stats);
1948 g_free(stats);
1950 return (0);
1954 about(struct tab *t, struct karg *args)
1956 char *about;
1958 if (t == NULL)
1959 errx(1, "about");
1961 about = g_strdup_printf(XT_DOCTYPE
1962 "<html>"
1963 "<head>"
1964 "<title>About</title>"
1965 "</head>"
1966 "<h1>About</h1>"
1967 "<body>"
1968 "<b>Version: %s</b><p>"
1969 "Authors:"
1970 "<ul>"
1971 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
1972 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
1973 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
1974 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
1975 "</ul>"
1976 "Copyrights and licenses can be found on the XXXterm "
1977 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
1978 "</body>"
1979 "</html>",
1980 version
1983 load_webkit_string(t, about);
1984 g_free(about);
1986 return (0);
1990 help(struct tab *t, struct karg *args)
1992 char *help;
1994 if (t == NULL)
1995 errx(1, "help");
1997 help = XT_DOCTYPE
1998 "<html>"
1999 "<head>"
2000 "<title>XXXterm</title>"
2001 "<meta http-equiv=\"REFRESH\" content=\"0;"
2002 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2003 "</head>"
2004 "<body>"
2005 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2006 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2007 "cgi-bin/man-cgi?xxxterm</a>"
2008 "</body>"
2009 "</html>"
2012 load_webkit_string(t, help);
2014 return (0);
2018 * update all favorite tabs apart from one. Pass NULL if
2019 * you want to update all.
2021 void
2022 update_favorite_tabs(struct tab *apart_from)
2024 struct tab *t;
2025 if (!updating_fl_tabs) {
2026 updating_fl_tabs = 1; /* stop infinite recursion */
2027 TAILQ_FOREACH(t, &tabs, entry)
2028 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2029 && (t != apart_from))
2030 xtp_page_fl(t, NULL);
2031 updating_fl_tabs = 0;
2035 /* show a list of favorites (bookmarks) */
2037 xtp_page_fl(struct tab *t, struct karg *args)
2039 char file[PATH_MAX];
2040 FILE *f;
2041 char *uri = NULL, *title = NULL;
2042 size_t len, lineno = 0;
2043 int i, failed = 0;
2044 char *header, *body, *tmp, *html = NULL;
2046 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2048 if (t == NULL)
2049 warn("%s: bad param", __func__);
2051 /* mark tab as favorite list */
2052 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2054 /* new session key */
2055 if (!updating_fl_tabs)
2056 generate_xtp_session_key(&fl_session_key);
2058 /* open favorites */
2059 snprintf(file, sizeof file, "%s/%s/%s",
2060 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
2061 if ((f = fopen(file, "r")) == NULL) {
2062 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2063 return (1);
2066 /* header */
2067 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
2068 "<title>Favorites</title>\n"
2069 "%s"
2070 "</head>"
2071 "<h1>Favorites</h1>\n",
2072 XT_PAGE_STYLE);
2074 /* body */
2075 body = g_strdup_printf("<div align='center'><table><tr>"
2076 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
2077 "<th style='width: 15%%'>Remove</th></tr>\n");
2079 for (i = 1;;) {
2080 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2081 if (feof(f) || ferror(f))
2082 break;
2083 if (len == 0) {
2084 free(title);
2085 title = NULL;
2086 continue;
2089 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2090 if (feof(f) || ferror(f)) {
2091 errx(0, "%s: can't parse favorites\n",
2092 __func__);
2093 failed = 1;
2094 break;
2097 tmp = body;
2098 body = g_strdup_printf("%s<tr>"
2099 "<td>%d</td>"
2100 "<td><a href='%s'>%s</a></td>"
2101 "<td style='text-align: center'>"
2102 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2103 "</tr>\n",
2104 body, i, uri, title,
2105 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2107 g_free(tmp);
2109 free(uri);
2110 uri = NULL;
2111 free(title);
2112 title = NULL;
2113 i++;
2115 fclose(f);
2117 /* if none, say so */
2118 if (i == 1) {
2119 tmp = body;
2120 body = g_strdup_printf("%s<tr>"
2121 "<td colspan='3' style='text-align: center'>"
2122 "No favorites - To add one use the 'favadd' command."
2123 "</td></tr>", body);
2124 g_free(tmp);
2127 if (uri)
2128 free(uri);
2129 if (title)
2130 free(title);
2132 /* render */
2133 if (!failed) {
2134 html = g_strdup_printf("%s%s</table></div></html>",
2135 header, body);
2136 load_webkit_string(t, html);
2139 update_favorite_tabs(t);
2141 if (header)
2142 g_free(header);
2143 if (body)
2144 g_free(body);
2145 if (html)
2146 g_free(html);
2148 return (failed);
2151 char *
2152 getparams(char *cmd, char *cmp)
2154 char *rv = NULL;
2156 if (cmd && cmp) {
2157 if (!strncmp(cmd, cmp, strlen(cmp))) {
2158 rv = cmd + strlen(cmp);
2159 while (*rv == ' ')
2160 rv++;
2161 if (strlen(rv) == 0)
2162 rv = NULL;
2166 return (rv);
2169 void
2170 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2171 size_t cert_count, char *title)
2173 gnutls_datum_t cinfo;
2174 char *tmp, *header, *body, *footer;
2175 int i;
2177 header = g_strdup_printf("<title>%s</title><html><body>", title);
2178 footer = g_strdup("</body></html>");
2179 body = g_strdup("");
2181 for (i = 0; i < cert_count; i++) {
2182 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2183 &cinfo))
2184 return;
2186 tmp = body;
2187 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2188 body, i, cinfo.data);
2189 gnutls_free(cinfo.data);
2190 g_free(tmp);
2193 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2194 g_free(header);
2195 g_free(body);
2196 g_free(footer);
2197 load_webkit_string(t, tmp);
2198 g_free(tmp);
2202 ca_cmd(struct tab *t, struct karg *args)
2204 FILE *f = NULL;
2205 int rv = 1, certs = 0, certs_read;
2206 struct stat sb;
2207 gnutls_datum dt;
2208 gnutls_x509_crt_t *c = NULL;
2209 char *certs_buf = NULL, *s;
2211 /* yeah yeah stat race */
2212 if (stat(ssl_ca_file, &sb)) {
2213 show_oops(t, "no CA file: %s", ssl_ca_file);
2214 goto done;
2217 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2218 show_oops(t, "Can't open CA file: %s", strerror(errno));
2219 return (1);
2222 certs_buf = g_malloc(sb.st_size + 1);
2223 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2224 show_oops(t, "Can't read CA file: %s", strerror(errno));
2225 goto done;
2227 certs_buf[sb.st_size] = '\0';
2229 s = certs_buf;
2230 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2231 certs++;
2232 s += strlen("BEGIN CERTIFICATE");
2235 bzero(&dt, sizeof dt);
2236 dt.data = certs_buf;
2237 dt.size = sb.st_size;
2238 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2239 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt, GNUTLS_X509_FMT_PEM, 0);
2240 if (certs_read <= 0) {
2241 show_oops(t, "No cert(s) available");
2242 goto done;
2244 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2245 done:
2246 if (c)
2247 g_free(c);
2248 if (certs_buf)
2249 g_free(certs_buf);
2250 if (f)
2251 fclose(f);
2253 return (rv);
2257 connect_socket_from_uri(char *uri, char *domain, size_t domain_sz)
2259 SoupURI *su = NULL;
2260 struct addrinfo hints, *res = NULL, *ai;
2261 int s = -1, on;
2262 char port[8];
2264 if (uri && !g_str_has_prefix(uri, "https://"))
2265 goto done;
2267 su = soup_uri_new(uri);
2268 if (su == NULL)
2269 goto done;
2270 if (!SOUP_URI_VALID_FOR_HTTP(su))
2271 goto done;
2273 snprintf(port, sizeof port, "%d", su->port);
2274 bzero(&hints, sizeof(struct addrinfo));
2275 hints.ai_flags = AI_CANONNAME;
2276 hints.ai_family = AF_UNSPEC;
2277 hints.ai_socktype = SOCK_STREAM;
2279 if (getaddrinfo(su->host, port, &hints, &res))
2280 goto done;
2282 for (ai = res; ai; ai = ai->ai_next) {
2283 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2284 continue;
2286 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2287 if (s < 0)
2288 goto done;
2289 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2290 sizeof(on)) == -1)
2291 goto done;
2293 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2294 goto done;
2297 if (domain)
2298 strlcpy(domain, su->host, domain_sz);
2299 done:
2300 if (su)
2301 soup_uri_free(su);
2302 if (res)
2303 freeaddrinfo(res);
2305 return (s);
2309 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2311 if (gsession)
2312 gnutls_deinit(gsession);
2313 if (xcred)
2314 gnutls_certificate_free_credentials(xcred);
2316 return (0);
2320 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2321 gnutls_certificate_credentials_t *xc)
2323 gnutls_certificate_credentials_t xcred;
2324 gnutls_session_t gsession;
2325 int rv = 1;
2327 if (gs == NULL || xc == NULL)
2328 goto done;
2330 *gs = NULL;
2331 *xc = NULL;
2333 gnutls_certificate_allocate_credentials(&xcred);
2334 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2335 GNUTLS_X509_FMT_PEM);
2336 gnutls_init(&gsession, GNUTLS_CLIENT);
2337 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2338 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2339 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2340 if ((rv = gnutls_handshake(gsession)) < 0) {
2341 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2343 gnutls_error_is_fatal(rv),
2344 gnutls_strerror_name(rv));
2345 stop_tls(gsession, xcred);
2346 goto done;
2349 gnutls_credentials_type_t cred;
2350 cred = gnutls_auth_get_type(gsession);
2351 if (cred != GNUTLS_CRD_CERTIFICATE) {
2352 stop_tls(gsession, xcred);
2353 goto done;
2356 *gs = gsession;
2357 *xc = xcred;
2358 rv = 0;
2359 done:
2360 return (rv);
2364 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2365 size_t *cert_count)
2367 unsigned int len;
2368 const gnutls_datum_t *cl;
2369 gnutls_x509_crt_t *all_certs;
2370 int i, rv = 1;
2372 if (certs == NULL || cert_count == NULL)
2373 goto done;
2374 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2375 goto done;
2376 cl = gnutls_certificate_get_peers(gsession, &len);
2377 if (len == 0)
2378 goto done;
2380 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2381 for (i = 0; i < len; i++) {
2382 gnutls_x509_crt_init(&all_certs[i]);
2383 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2384 GNUTLS_X509_FMT_PEM < 0)) {
2385 g_free(all_certs);
2386 goto done;
2390 *certs = all_certs;
2391 *cert_count = len;
2392 rv = 0;
2393 done:
2394 return (rv);
2397 void
2398 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2400 int i;
2402 for (i = 0; i < cert_count; i++)
2403 gnutls_x509_crt_deinit(certs[i]);
2404 g_free(certs);
2407 void
2408 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2409 size_t cert_count, char *domain)
2411 size_t cert_buf_sz;
2412 char cert_buf[64 * 1024], file[PATH_MAX];
2413 int i;
2414 FILE *f;
2415 GdkColor color;
2417 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2418 return;
2420 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2421 if ((f = fopen(file, "w")) == NULL) {
2422 show_oops(t, "Can't create cert file %s %s",
2423 file, strerror(errno));
2424 return;
2427 for (i = 0; i < cert_count; i++) {
2428 cert_buf_sz = sizeof cert_buf;
2429 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2430 cert_buf, &cert_buf_sz)) {
2431 show_oops(t, "gnutls_x509_crt_export failed");
2432 goto done;
2434 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
2435 show_oops(t, "Can't write certs: %s", strerror(errno));
2436 goto done;
2440 /* not the best spot but oh well */
2441 gdk_color_parse("lightblue", &color);
2442 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
2443 done:
2444 fclose(f);
2448 load_compare_cert(struct tab *t, struct karg *args)
2450 WebKitWebFrame *frame;
2451 char *uri, domain[8182], file[PATH_MAX];
2452 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
2453 int s = -1, rv = 1, i;
2454 size_t cert_count;
2455 FILE *f = NULL;
2456 size_t cert_buf_sz;
2457 gnutls_session_t gsession;
2458 gnutls_x509_crt_t *certs;
2459 gnutls_certificate_credentials_t xcred;
2461 if (t == NULL)
2462 return (1);
2464 frame = webkit_web_view_get_main_frame(t->wv);
2465 uri = (char *)webkit_web_frame_get_uri(frame);
2466 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
2467 return (1);
2469 /* go ssl/tls */
2470 if (start_tls(t, s, &gsession, &xcred)) {
2471 show_oops(t, "Start TLS failed");
2472 goto done;
2475 /* get certs */
2476 if (get_connection_certs(gsession, &certs, &cert_count)) {
2477 show_oops(t, "Can't get connection certificates");
2478 goto done;
2481 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2482 if ((f = fopen(file, "r")) == NULL)
2483 goto freeit;
2485 for (i = 0; i < cert_count; i++) {
2486 cert_buf_sz = sizeof cert_buf;
2487 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2488 cert_buf, &cert_buf_sz)) {
2489 goto freeit;
2491 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
2492 rv = -1; /* critical */
2493 goto freeit;
2495 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
2496 rv = -1; /* critical */
2497 goto freeit;
2501 rv = 0;
2502 freeit:
2503 if (f)
2504 fclose(f);
2505 free_connection_certs(certs, cert_count);
2506 done:
2507 /* we close the socket first for speed */
2508 if (s != -1)
2509 close(s);
2510 stop_tls(gsession, xcred);
2512 return (rv);
2516 cert_cmd(struct tab *t, struct karg *args)
2518 WebKitWebFrame *frame;
2519 char *uri, *action, domain[8182];
2520 int s = -1;
2521 size_t cert_count;
2522 gnutls_session_t gsession;
2523 gnutls_x509_crt_t *certs;
2524 gnutls_certificate_credentials_t xcred;
2526 if (t == NULL)
2527 return (1);
2529 if ((action = getparams(args->s, "cert")))
2531 else
2532 action = "show";
2534 frame = webkit_web_view_get_main_frame(t->wv);
2535 uri = (char *)webkit_web_frame_get_uri(frame);
2536 if (uri && strlen(uri) == 0) {
2537 show_oops(t, "Invalid URI");
2538 return (1);
2540 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
2541 show_oops(t, "Invalid certidicate URI: %s", uri);
2542 return (1);
2545 /* go ssl/tls */
2546 if (start_tls(t, s, &gsession, &xcred)) {
2547 show_oops(t, "Start TLS failed");
2548 goto done;
2551 /* get certs */
2552 if (get_connection_certs(gsession, &certs, &cert_count)) {
2553 show_oops(t, "get_connection_certs failed");
2554 goto done;
2557 if (!strcmp(action, "show"))
2558 show_certs(t, certs, cert_count, "Certificate Chain");
2559 else if (!strcmp(action, "save"))
2560 save_certs(t, certs, cert_count, domain);
2561 else
2562 show_oops(t, "Invalid command: %s", action);
2564 free_connection_certs(certs, cert_count);
2565 done:
2566 /* we close the socket first for speed */
2567 if (s != -1)
2568 close(s);
2569 stop_tls(gsession, xcred);
2571 return (0);
2575 remove_cookie(int index)
2577 int i, rv = 1;
2578 GSList *cf;
2579 SoupCookie *c;
2581 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
2583 cf = soup_cookie_jar_all_cookies(s_cookiejar);
2585 for (i = 1; cf; cf = cf->next, i++) {
2586 if (i != index)
2587 continue;
2588 c = cf->data;
2589 print_cookie("remove cookie", c);
2590 soup_cookie_jar_delete_cookie(s_cookiejar, c);
2591 rv = 0;
2592 break;
2595 soup_cookies_free(cf);
2597 return (rv);
2601 wl_show(struct tab *t, char *args, char *title, struct domain_list *wl)
2603 struct domain *d;
2604 char *tmp, *header, *body, *footer;
2605 int p_js = 0, s_js = 0;
2607 /* we set this to indicate we want to manually do navaction */
2608 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
2610 if (g_str_has_prefix(args, "show a") ||
2611 !strcmp(args, "show")) {
2612 /* show all */
2613 p_js = 1;
2614 s_js = 1;
2615 } else if (g_str_has_prefix(args, "show p")) {
2616 /* show persistent */
2617 p_js = 1;
2618 } else if (g_str_has_prefix(args, "show s")) {
2619 /* show session */
2620 s_js = 1;
2621 } else
2622 return (1);
2624 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
2625 title, title);
2626 footer = g_strdup("</body></html>");
2627 body = g_strdup("");
2629 /* p list */
2630 if (p_js) {
2631 tmp = body;
2632 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
2633 g_free(tmp);
2634 RB_FOREACH(d, domain_list, wl) {
2635 if (d->handy == 0)
2636 continue;
2637 tmp = body;
2638 body = g_strdup_printf("%s%s<br>", body, d->d);
2639 g_free(tmp);
2643 /* s list */
2644 if (s_js) {
2645 tmp = body;
2646 body = g_strdup_printf("%s<h2>Session</h2>", body);
2647 g_free(tmp);
2648 RB_FOREACH(d, domain_list, wl) {
2649 if (d->handy == 1)
2650 continue;
2651 tmp = body;
2652 body = g_strdup_printf("%s%s", body, d->d);
2653 g_free(tmp);
2657 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2658 g_free(header);
2659 g_free(body);
2660 g_free(footer);
2661 load_webkit_string(t, tmp);
2662 g_free(tmp);
2663 return (0);
2667 wl_save(struct tab *t, struct karg *args, int js)
2669 char file[PATH_MAX];
2670 FILE *f;
2671 char *line = NULL, *lt = NULL;
2672 size_t linelen;
2673 WebKitWebFrame *frame;
2674 char *dom = NULL, *uri, *dom_save = NULL;
2675 struct karg a;
2676 struct domain *d;
2677 GSList *cf;
2678 SoupCookie *ci, *c;
2679 int flags;
2681 if (t == NULL || args == NULL)
2682 return (1);
2684 if (runtime_settings[0] == '\0')
2685 return (1);
2687 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
2688 if ((f = fopen(file, "r+")) == NULL)
2689 return (1);
2691 frame = webkit_web_view_get_main_frame(t->wv);
2692 uri = (char *)webkit_web_frame_get_uri(frame);
2693 dom = find_domain(uri, 1);
2694 if (uri == NULL || dom == NULL) {
2695 show_oops(t, "Can't add domain to %s white list",
2696 js ? "JavaScript" : "cookie");
2697 goto done;
2700 if (g_str_has_prefix(args->s, "save d")) {
2701 /* save domain */
2702 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
2703 show_oops(t, "invalid domain: %s", dom);
2704 goto done;
2706 flags = XT_WL_TOPLEVEL;
2707 } else if (g_str_has_prefix(args->s, "save f") ||
2708 !strcmp(args->s, "save")) {
2709 /* save fqdn */
2710 dom_save = dom;
2711 flags = XT_WL_FQDN;
2712 } else {
2713 show_oops(t, "invalid command: %s", args->s);
2714 goto done;
2717 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
2719 while (!feof(f)) {
2720 line = fparseln(f, &linelen, NULL, NULL, 0);
2721 if (line == NULL)
2722 continue;
2723 if (!strcmp(line, lt))
2724 goto done;
2725 free(line);
2726 line = NULL;
2729 fprintf(f, "%s\n", lt);
2731 a.i = XT_WL_ENABLE;
2732 a.i |= flags;
2733 if (js) {
2734 d = wl_find(dom_save, &js_wl);
2735 toggle_js(t, &a);
2736 } else {
2737 d = wl_find(dom_save, &c_wl);
2738 toggle_cwl(t, &a);
2740 /* find and add to persistent jar */
2741 cf = soup_cookie_jar_all_cookies(s_cookiejar);
2742 for (;cf; cf = cf->next) {
2743 ci = cf->data;
2744 if (!strcmp(dom_save, ci->domain) ||
2745 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
2746 c = soup_cookie_copy(ci);
2747 _soup_cookie_jar_add_cookie(p_cookiejar, c);
2750 soup_cookies_free(cf);
2752 if (d)
2753 d->handy = 1;
2755 done:
2756 if (line)
2757 free(line);
2758 if (dom)
2759 g_free(dom);
2760 if (lt)
2761 g_free(lt);
2762 fclose(f);
2764 return (0);
2768 cookie_cmd(struct tab *t, struct karg *args)
2770 char *cmd;
2771 struct karg a;
2773 if ((cmd = getparams(args->s, "cookie")))
2775 else
2776 cmd = "show all";
2779 if (g_str_has_prefix(cmd, "show")) {
2780 wl_show(t, cmd, "Cookie White List", &c_wl);
2781 } else if (g_str_has_prefix(cmd, "save")) {
2782 a.s = cmd;
2783 wl_save(t, &a, 0);
2784 } else if (g_str_has_prefix(cmd, "toggle")) {
2785 a.i = XT_WL_TOGGLE;
2786 if (g_str_has_prefix(cmd, "toggle d"))
2787 a.i |= XT_WL_TOPLEVEL;
2788 else
2789 a.i |= XT_WL_FQDN;
2790 toggle_cwl(t, &a);
2791 } else if (g_str_has_prefix(cmd, "delete")) {
2792 show_oops(t, "'cookie delete' currently unimplemented");
2793 } else
2794 show_oops(t, "unknown cookie command: %s", cmd);
2796 return (0);
2800 js_cmd(struct tab *t, struct karg *args)
2802 char *cmd;
2803 struct karg a;
2805 if ((cmd = getparams(args->s, "js")))
2807 else
2808 cmd = "show all";
2810 if (g_str_has_prefix(cmd, "show")) {
2811 wl_show(t, cmd, "JavaScript White List", &js_wl);
2812 } else if (g_str_has_prefix(cmd, "save")) {
2813 a.s = cmd;
2814 wl_save(t, &a, 1);
2815 } else if (g_str_has_prefix(cmd, "toggle")) {
2816 a.i = XT_WL_TOGGLE;
2817 if (g_str_has_prefix(cmd, "toggle d"))
2818 a.i |= XT_WL_TOPLEVEL;
2819 else
2820 a.i |= XT_WL_FQDN;
2821 toggle_js(t, &a);
2822 } else if (g_str_has_prefix(cmd, "delete")) {
2823 show_oops(t, "'js delete' currently unimplemented");
2824 } else
2825 show_oops(t, "unknown js command: %s", cmd);
2827 return (0);
2831 add_favorite(struct tab *t, struct karg *args)
2833 char file[PATH_MAX];
2834 FILE *f;
2835 char *line = NULL;
2836 size_t urilen, linelen;
2837 WebKitWebFrame *frame;
2838 const gchar *uri, *title;
2840 if (t == NULL)
2841 return (1);
2843 /* don't allow adding of xtp pages to favorites */
2844 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
2845 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
2846 return (1);
2849 snprintf(file, sizeof file, "%s/%s/%s",
2850 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
2851 if ((f = fopen(file, "r+")) == NULL) {
2852 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2853 return (1);
2856 title = webkit_web_view_get_title(t->wv);
2857 frame = webkit_web_view_get_main_frame(t->wv);
2858 uri = webkit_web_frame_get_uri(frame);
2859 if (title == NULL)
2860 title = uri;
2862 if (title == NULL || uri == NULL) {
2863 show_oops(t, "can't add page to favorites");
2864 goto done;
2867 urilen = strlen(uri);
2869 while (!feof(f)) {
2870 line = fparseln(f, &linelen, NULL, NULL, 0);
2871 if (linelen == urilen && !strcmp(line, uri))
2872 goto done;
2873 free(line);
2874 line = NULL;
2877 fprintf(f, "\n%s\n%s", title, uri);
2878 done:
2879 if (line)
2880 free(line);
2881 fclose(f);
2883 update_favorite_tabs(NULL);
2885 return (0);
2889 navaction(struct tab *t, struct karg *args)
2891 WebKitWebHistoryItem *item;
2893 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
2894 t->tab_id, args->i);
2896 if (t->item) {
2897 if (args->i == XT_NAV_BACK)
2898 item = webkit_web_back_forward_list_get_current_item(t->bfl);
2899 else
2900 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
2901 if (item == NULL)
2902 return (XT_CB_PASSTHROUGH);;
2903 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
2904 t->item = NULL;
2905 return (XT_CB_PASSTHROUGH);
2908 switch (args->i) {
2909 case XT_NAV_BACK:
2910 webkit_web_view_go_back(t->wv);
2911 break;
2912 case XT_NAV_FORWARD:
2913 webkit_web_view_go_forward(t->wv);
2914 break;
2915 case XT_NAV_RELOAD:
2916 webkit_web_view_reload(t->wv);
2917 break;
2918 case XT_NAV_RELOAD_CACHE:
2919 webkit_web_view_reload_bypass_cache(t->wv);
2920 break;
2922 return (XT_CB_PASSTHROUGH);
2926 move(struct tab *t, struct karg *args)
2928 GtkAdjustment *adjust;
2929 double pi, si, pos, ps, upper, lower, max;
2931 switch (args->i) {
2932 case XT_MOVE_DOWN:
2933 case XT_MOVE_UP:
2934 case XT_MOVE_BOTTOM:
2935 case XT_MOVE_TOP:
2936 case XT_MOVE_PAGEDOWN:
2937 case XT_MOVE_PAGEUP:
2938 case XT_MOVE_HALFDOWN:
2939 case XT_MOVE_HALFUP:
2940 adjust = t->adjust_v;
2941 break;
2942 default:
2943 adjust = t->adjust_h;
2944 break;
2947 pos = gtk_adjustment_get_value(adjust);
2948 ps = gtk_adjustment_get_page_size(adjust);
2949 upper = gtk_adjustment_get_upper(adjust);
2950 lower = gtk_adjustment_get_lower(adjust);
2951 si = gtk_adjustment_get_step_increment(adjust);
2952 pi = gtk_adjustment_get_page_increment(adjust);
2953 max = upper - ps;
2955 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
2956 "max %f si %f pi %f\n",
2957 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
2958 pos, ps, upper, lower, max, si, pi);
2960 switch (args->i) {
2961 case XT_MOVE_DOWN:
2962 case XT_MOVE_RIGHT:
2963 pos += si;
2964 gtk_adjustment_set_value(adjust, MIN(pos, max));
2965 break;
2966 case XT_MOVE_UP:
2967 case XT_MOVE_LEFT:
2968 pos -= si;
2969 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2970 break;
2971 case XT_MOVE_BOTTOM:
2972 case XT_MOVE_FARRIGHT:
2973 gtk_adjustment_set_value(adjust, max);
2974 break;
2975 case XT_MOVE_TOP:
2976 case XT_MOVE_FARLEFT:
2977 gtk_adjustment_set_value(adjust, lower);
2978 break;
2979 case XT_MOVE_PAGEDOWN:
2980 pos += pi;
2981 gtk_adjustment_set_value(adjust, MIN(pos, max));
2982 break;
2983 case XT_MOVE_PAGEUP:
2984 pos -= pi;
2985 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2986 break;
2987 case XT_MOVE_HALFDOWN:
2988 pos += pi / 2;
2989 gtk_adjustment_set_value(adjust, MIN(pos, max));
2990 break;
2991 case XT_MOVE_HALFUP:
2992 pos -= pi / 2;
2993 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2994 break;
2995 default:
2996 return (XT_CB_PASSTHROUGH);
2999 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3001 return (XT_CB_HANDLED);
3004 void
3005 url_set_visibility(void)
3007 struct tab *t;
3009 TAILQ_FOREACH(t, &tabs, entry) {
3010 if (show_url == 0) {
3011 gtk_widget_hide(t->toolbar);
3012 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
3013 } else
3014 gtk_widget_show(t->toolbar);
3018 void
3019 notebook_tab_set_visibility(GtkNotebook *notebook)
3021 if (show_tabs == 0)
3022 gtk_notebook_set_show_tabs(notebook, FALSE);
3023 else
3024 gtk_notebook_set_show_tabs(notebook, TRUE);
3028 fullscreen(struct tab *t, struct karg *args)
3030 DNPRINTF(XT_D_TAB, "urlaction: %p %d %d\n", t, args->i, t->focus_wv);
3032 if (t == NULL)
3033 return (XT_CB_PASSTHROUGH);
3035 if (show_url == 0)
3036 show_url = show_tabs = 1;
3037 else
3038 show_url = show_tabs = 0;
3040 url_set_visibility();
3041 notebook_tab_set_visibility(notebook);
3043 return (XT_CB_HANDLED);
3047 urlaction(struct tab *t, struct karg *args)
3049 int rv = XT_CB_HANDLED;
3051 DNPRINTF(XT_D_TAB, "urlaction: %p %d %d\n", t, args->i, t->focus_wv);
3053 if (t == NULL)
3054 return (XT_CB_PASSTHROUGH);
3056 switch (args->i) {
3057 case XT_URL_SHOW:
3058 if (show_url == 0) {
3059 show_url = 1;
3060 url_set_visibility();
3062 break;
3063 case XT_URL_HIDE:
3064 if (show_url == 1) {
3065 show_url = 0;
3066 url_set_visibility();
3068 break;
3070 return (rv);
3074 tabaction(struct tab *t, struct karg *args)
3076 int rv = XT_CB_HANDLED;
3077 char *url = NULL, *newuri = NULL;
3078 struct undo *u;
3080 DNPRINTF(XT_D_TAB, "tabaction: %p %d %d\n", t, args->i, t->focus_wv);
3082 if (t == NULL)
3083 return (XT_CB_PASSTHROUGH);
3085 switch (args->i) {
3086 case XT_TAB_NEW:
3087 if ((url = getparams(args->s, "tabnew")))
3088 create_new_tab(url, NULL, 1);
3089 else
3090 create_new_tab(NULL, NULL, 1);
3091 break;
3092 case XT_TAB_DELETE:
3093 delete_tab(t);
3094 break;
3095 case XT_TAB_DELQUIT:
3096 if (gtk_notebook_get_n_pages(notebook) > 1)
3097 delete_tab(t);
3098 else
3099 quit(t, args);
3100 break;
3101 case XT_TAB_OPEN:
3102 if ((url = getparams(args->s, "open")) ||
3103 ((url = getparams(args->s, "op"))) ||
3104 ((url = getparams(args->s, "o"))))
3106 else {
3107 rv = XT_CB_PASSTHROUGH;
3108 goto done;
3111 if (valid_url_type(url)) {
3112 newuri = guess_url_type(url);
3113 url = newuri;
3115 webkit_web_view_load_uri(t->wv, url);
3116 if (newuri)
3117 g_free(newuri);
3118 break;
3119 case XT_TAB_SHOW:
3120 if (show_tabs == 0) {
3121 show_tabs = 1;
3122 notebook_tab_set_visibility(notebook);
3124 break;
3125 case XT_TAB_HIDE:
3126 if (show_tabs == 1) {
3127 show_tabs = 0;
3128 notebook_tab_set_visibility(notebook);
3130 break;
3131 case XT_TAB_UNDO_CLOSE:
3132 if (undo_count == 0) {
3133 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3134 goto done;
3135 } else {
3136 undo_count--;
3137 u = TAILQ_FIRST(&undos);
3138 create_new_tab(u->uri, u, 1);
3140 TAILQ_REMOVE(&undos, u, entry);
3141 g_free(u->uri);
3142 /* u->history is freed in create_new_tab() */
3143 g_free(u);
3145 break;
3146 default:
3147 rv = XT_CB_PASSTHROUGH;
3148 goto done;
3151 done:
3152 if (args->s) {
3153 g_free(args->s);
3154 args->s = NULL;
3157 return (rv);
3161 resizetab(struct tab *t, struct karg *args)
3163 if (t == NULL || args == NULL)
3164 errx(1, "resizetab");
3166 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3167 t->tab_id, args->i);
3169 adjustfont_webkit(t, args->i);
3171 return (XT_CB_HANDLED);
3175 movetab(struct tab *t, struct karg *args)
3177 struct tab *tt;
3178 int x;
3180 if (t == NULL || args == NULL)
3181 errx(1, "movetab");
3183 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3184 t->tab_id, args->i);
3186 if (args->i == XT_TAB_INVALID)
3187 return (XT_CB_PASSTHROUGH);
3189 if (args->i < XT_TAB_INVALID) {
3190 /* next or previous tab */
3191 if (TAILQ_EMPTY(&tabs))
3192 return (XT_CB_PASSTHROUGH);
3194 switch (args->i) {
3195 case XT_TAB_NEXT:
3196 /* if at the last page, loop around to the first */
3197 if (gtk_notebook_get_current_page(notebook) ==
3198 gtk_notebook_get_n_pages(notebook) - 1)
3199 gtk_notebook_set_current_page(notebook, 0);
3200 else
3201 gtk_notebook_next_page(notebook);
3202 break;
3203 case XT_TAB_PREV:
3204 /* if at the first page, loop around to the last */
3205 if (gtk_notebook_current_page(notebook) == 0)
3206 gtk_notebook_set_current_page(notebook,
3207 gtk_notebook_get_n_pages(notebook) - 1);
3208 else
3209 gtk_notebook_prev_page(notebook);
3210 break;
3211 case XT_TAB_FIRST:
3212 gtk_notebook_set_current_page(notebook, 0);
3213 break;
3214 case XT_TAB_LAST:
3215 gtk_notebook_set_current_page(notebook, -1);
3216 break;
3217 default:
3218 return (XT_CB_PASSTHROUGH);
3221 return (XT_CB_HANDLED);
3224 /* jump to tab */
3225 x = args->i - 1;
3226 if (t->tab_id == x) {
3227 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3228 return (XT_CB_HANDLED);
3231 TAILQ_FOREACH(tt, &tabs, entry) {
3232 if (tt->tab_id == x) {
3233 gtk_notebook_set_current_page(notebook, x);
3234 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
3235 if (tt->focus_wv)
3236 gtk_widget_grab_focus(GTK_WIDGET(tt->wv));
3240 return (XT_CB_HANDLED);
3244 command(struct tab *t, struct karg *args)
3246 WebKitWebFrame *frame;
3247 char *s = NULL, *ss = NULL;
3248 GdkColor color;
3249 const gchar *uri;
3251 if (t == NULL || args == NULL)
3252 errx(1, "command");
3254 switch (args->i) {
3255 case '/':
3256 s = "/";
3257 break;
3258 case '?':
3259 s = "?";
3260 break;
3261 case ':':
3262 s = ":";
3263 break;
3264 case XT_CMD_OPEN:
3265 s = ":open ";
3266 break;
3267 case XT_CMD_TABNEW:
3268 s = ":tabnew ";
3269 break;
3270 case XT_CMD_OPEN_CURRENT:
3271 s = ":open ";
3272 /* FALL THROUGH */
3273 case XT_CMD_TABNEW_CURRENT:
3274 if (!s) /* FALL THROUGH? */
3275 s = ":tabnew ";
3276 frame = webkit_web_view_get_main_frame(t->wv);
3277 uri = webkit_web_frame_get_uri(frame);
3278 if (uri && strlen(uri)) {
3279 ss = g_strdup_printf("%s%s", s, uri);
3280 s = ss;
3282 break;
3283 default:
3284 show_oops(t, "command: invalid opcode %d", args->i);
3285 return (XT_CB_PASSTHROUGH);
3288 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3290 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3291 gdk_color_parse("white", &color);
3292 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3293 show_cmd(t);
3294 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3295 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3297 if (ss)
3298 g_free(ss);
3300 return (XT_CB_HANDLED);
3304 * Return a new string with a download row (in html)
3305 * appended. Old string is freed.
3307 char *
3308 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3311 WebKitDownloadStatus stat;
3312 char *status_html = NULL, *cmd_html = NULL, *new_html;
3313 gdouble progress;
3314 char cur_sz[FMT_SCALED_STRSIZE];
3315 char tot_sz[FMT_SCALED_STRSIZE];
3316 char *xtp_prefix;
3318 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3320 /* All actions wil take this form:
3321 * xxxt://class/seskey
3323 xtp_prefix = g_strdup_printf("%s%d/%s/",
3324 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3326 stat = webkit_download_get_status(dl->download);
3328 switch (stat) {
3329 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3330 status_html = g_strdup_printf("Finished");
3331 cmd_html = g_strdup_printf(
3332 "<a href='%s%d/%d'>Remove</a>",
3333 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3334 break;
3335 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3336 /* gather size info */
3337 progress = 100 * webkit_download_get_progress(dl->download);
3339 fmt_scaled(
3340 webkit_download_get_current_size(dl->download), cur_sz);
3341 fmt_scaled(
3342 webkit_download_get_total_size(dl->download), tot_sz);
3344 status_html = g_strdup_printf("%s of %s (%.2f%%)", cur_sz,
3345 tot_sz, progress);
3346 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3347 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3349 break;
3350 /* LLL */
3351 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3352 status_html = g_strdup_printf("Cancelled");
3353 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3354 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3355 break;
3356 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3357 status_html = g_strdup_printf("Error!");
3358 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3359 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3360 break;
3361 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3362 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3363 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3364 status_html = g_strdup_printf("Starting");
3365 break;
3366 default:
3367 show_oops(t, "%s: unknown download status", __func__);
3370 new_html = g_strdup_printf(
3371 "%s\n<tr><td>%s</td><td>%s</td>"
3372 "<td style='text-align:center'>%s</td></tr>\n",
3373 html, webkit_download_get_uri(dl->download),
3374 status_html, cmd_html);
3375 g_free(html);
3377 if (status_html)
3378 g_free(status_html);
3380 if (cmd_html)
3381 g_free(cmd_html);
3383 g_free(xtp_prefix);
3385 return new_html;
3389 * update all download tabs apart from one. Pass NULL if
3390 * you want to update all.
3392 void
3393 update_download_tabs(struct tab *apart_from)
3395 struct tab *t;
3396 if (!updating_dl_tabs) {
3397 updating_dl_tabs = 1; /* stop infinite recursion */
3398 TAILQ_FOREACH(t, &tabs, entry)
3399 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
3400 && (t != apart_from))
3401 xtp_page_dl(t, NULL);
3402 updating_dl_tabs = 0;
3407 * update all cookie tabs apart from one. Pass NULL if
3408 * you want to update all.
3410 void
3411 update_cookie_tabs(struct tab *apart_from)
3413 struct tab *t;
3414 if (!updating_cl_tabs) {
3415 updating_cl_tabs = 1; /* stop infinite recursion */
3416 TAILQ_FOREACH(t, &tabs, entry)
3417 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
3418 && (t != apart_from))
3419 xtp_page_cl(t, NULL);
3420 updating_cl_tabs = 0;
3425 * update all history tabs apart from one. Pass NULL if
3426 * you want to update all.
3428 void
3429 update_history_tabs(struct tab *apart_from)
3431 struct tab *t;
3433 if (!updating_hl_tabs) {
3434 updating_hl_tabs = 1; /* stop infinite recursion */
3435 TAILQ_FOREACH(t, &tabs, entry)
3436 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
3437 && (t != apart_from))
3438 xtp_page_hl(t, NULL);
3439 updating_hl_tabs = 0;
3443 /* cookie management XTP page */
3445 xtp_page_cl(struct tab *t, struct karg *args)
3447 char *header, *body, *footer, *page, *tmp;
3448 int i = 1; /* all ids start 1 */
3449 GSList *sc, *pc, *pc_start;
3450 SoupCookie *c;
3451 char *type;
3453 DNPRINTF(XT_D_CMD, "%s", __func__);
3455 if (t == NULL)
3456 errx(1, "%s: null tab", __func__);
3458 /* mark this tab as cookie jar */
3459 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
3461 /* Generate a new session key */
3462 if (!updating_cl_tabs)
3463 generate_xtp_session_key(&cl_session_key);
3465 /* header */
3466 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3467 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
3468 "</head><body><h1>Cookie Jar</h1>\n");
3470 /* body */
3471 body = g_strdup_printf("<div align='center'><table><tr>"
3472 "<th>Type</th>"
3473 "<th>Name</th>"
3474 "<th>Value</th>"
3475 "<th>Domain</th>"
3476 "<th>Path</th>"
3477 "<th>Expires</th>"
3478 "<th>Secure</th>"
3479 "<th>HTTP_only</th>"
3480 "<th>Remove</th></tr>\n");
3482 sc = soup_cookie_jar_all_cookies(s_cookiejar);
3483 pc = soup_cookie_jar_all_cookies(p_cookiejar);
3484 pc_start = pc;
3486 for (; sc; sc = sc->next) {
3487 c = sc->data;
3489 type = "Session";
3490 for (pc = pc_start; pc; pc = pc->next)
3491 if (soup_cookie_equal(pc->data, c)) {
3492 type = "Session + Persistent";
3493 break;
3496 tmp = body;
3497 body = g_strdup_printf(
3498 "%s\n<tr>"
3499 "<td style='width: 3%%; text-align: center'>%s</td>"
3500 "<td style='width: 10%%; word-break: break-all'>%s</td>"
3501 "<td style='width: 20%%; word-break: break-all'>%s</td>"
3502 "<td style='width: 10%%; word-break: break-all'>%s</td>"
3503 "<td style='width: 8%%; word-break: break-all'>%s</td>"
3504 "<td style='width: 12%%; word-break: break-all'>%s</td>"
3505 "<td style='width: 3%%; text-align: center'>%d</td>"
3506 "<td style='width: 3%%; text-align: center'>%d</td>"
3507 "<td style='width: 3%%; text-align: center'>"
3508 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
3509 body,
3510 type,
3511 c->name,
3512 c->value,
3513 c->domain,
3514 c->path,
3515 c->expires ?
3516 soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "",
3517 c->secure,
3518 c->http_only,
3520 XT_XTP_STR,
3521 XT_XTP_CL,
3522 cl_session_key,
3523 XT_XTP_CL_REMOVE,
3527 g_free(tmp);
3528 i++;
3531 soup_cookies_free(sc);
3532 soup_cookies_free(pc);
3534 /* small message if there are none */
3535 if (i == 1) {
3536 tmp = body;
3537 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3538 "colspan='8'>No Cookies</td></tr>\n", body);
3539 g_free(tmp);
3542 /* footer */
3543 footer = g_strdup_printf("</table></div></body></html>");
3545 page = g_strdup_printf("%s%s%s", header, body, footer);
3547 g_free(header);
3548 g_free(body);
3549 g_free(footer);
3551 load_webkit_string(t, page);
3552 update_cookie_tabs(t);
3554 g_free(page);
3556 return (0);
3560 xtp_page_hl(struct tab *t, struct karg *args)
3562 char *header, *body, *footer, *page, *tmp;
3563 struct history *h;
3564 int i = 1; /* all ids start 1 */
3566 DNPRINTF(XT_D_CMD, "%s", __func__);
3568 if (t == NULL)
3569 errx(1, "%s: null tab", __func__);
3571 /* mark this tab as history manager */
3572 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
3574 /* Generate a new session key */
3575 if (!updating_hl_tabs)
3576 generate_xtp_session_key(&hl_session_key);
3578 /* header */
3579 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
3580 "<title>History</title>\n"
3581 "%s"
3582 "</head>"
3583 "<h1>History</h1>\n",
3584 XT_PAGE_STYLE);
3586 /* body */
3587 body = g_strdup_printf("<div align='center'><table><tr>"
3588 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
3590 RB_FOREACH_REVERSE(h, history_list, &hl) {
3591 tmp = body;
3592 body = g_strdup_printf(
3593 "%s\n<tr>"
3594 "<td><a href='%s'>%s</a></td>"
3595 "<td>%s</td>"
3596 "<td style='text-align: center'>"
3597 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
3598 body, h->uri, h->uri, h->title,
3599 XT_XTP_STR, XT_XTP_HL, hl_session_key,
3600 XT_XTP_HL_REMOVE, i);
3602 g_free(tmp);
3603 i++;
3606 /* small message if there are none */
3607 if (i == 1) {
3608 tmp = body;
3609 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3610 "colspan='3'>No History</td></tr>\n", body);
3611 g_free(tmp);
3614 /* footer */
3615 footer = g_strdup_printf("</table></div></body></html>");
3617 page = g_strdup_printf("%s%s%s", header, body, footer);
3620 * update all history manager tabs as the xtp session
3621 * key has now changed. No need to update the current tab.
3622 * Already did that above.
3624 update_history_tabs(t);
3626 g_free(header);
3627 g_free(body);
3628 g_free(footer);
3630 load_webkit_string(t, page);
3631 g_free(page);
3633 return (0);
3637 * Generate a web page detailing the status of any downloads
3640 xtp_page_dl(struct tab *t, struct karg *args)
3642 struct download *dl;
3643 char *header, *body, *footer, *page, *tmp;
3644 char *ref;
3645 int n_dl = 1;
3647 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
3649 if (t == NULL)
3650 errx(1, "%s: null tab", __func__);
3652 /* mark as a download manager tab */
3653 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
3656 * Generate a new session key for next page instance.
3657 * This only happens for the top level call to xtp_page_dl()
3658 * in which case updating_dl_tabs is 0.
3660 if (!updating_dl_tabs)
3661 generate_xtp_session_key(&dl_session_key);
3663 /* header - with refresh so as to update */
3664 if (refresh_interval >= 1)
3665 ref = g_strdup_printf(
3666 "<meta http-equiv='refresh' content='%u"
3667 ";url=%s%d/%s/%d' />\n",
3668 refresh_interval,
3669 XT_XTP_STR,
3670 XT_XTP_DL,
3671 dl_session_key,
3672 XT_XTP_DL_LIST);
3673 else
3674 ref = g_strdup("");
3677 header = g_strdup_printf(
3678 "%s\n<head>"
3679 "<title>Downloads</title>\n%s%s</head>\n",
3680 XT_DOCTYPE XT_HTML_TAG,
3681 ref,
3682 XT_PAGE_STYLE);
3684 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
3685 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
3686 "</p><table><tr><th style='width: 60%%'>"
3687 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
3688 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
3690 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
3691 body = xtp_page_dl_row(t, body, dl);
3692 n_dl++;
3695 /* message if no downloads in list */
3696 if (n_dl == 1) {
3697 tmp = body;
3698 body = g_strdup_printf("%s\n<tr><td colspan='3'"
3699 " style='text-align: center'>"
3700 "No downloads</td></tr>\n", body);
3701 g_free(tmp);
3704 /* footer */
3705 footer = g_strdup_printf("</table></div></body></html>");
3707 page = g_strdup_printf("%s%s%s", header, body, footer);
3711 * update all download manager tabs as the xtp session
3712 * key has now changed. No need to update the current tab.
3713 * Already did that above.
3715 update_download_tabs(t);
3717 g_free(ref);
3718 g_free(header);
3719 g_free(body);
3720 g_free(footer);
3722 load_webkit_string(t, page);
3723 g_free(page);
3725 return (0);
3729 search(struct tab *t, struct karg *args)
3731 gboolean d;
3733 if (t == NULL || args == NULL)
3734 errx(1, "search");
3735 if (t->search_text == NULL) {
3736 if (global_search == NULL)
3737 return (XT_CB_PASSTHROUGH);
3738 else {
3739 t->search_text = g_strdup(global_search);
3740 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
3741 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
3745 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
3746 t->tab_id, args->i, t->search_forward, t->search_text);
3748 switch (args->i) {
3749 case XT_SEARCH_NEXT:
3750 d = t->search_forward;
3751 break;
3752 case XT_SEARCH_PREV:
3753 d = !t->search_forward;
3754 break;
3755 default:
3756 return (XT_CB_PASSTHROUGH);
3759 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
3761 return (XT_CB_HANDLED);
3764 struct settings_args {
3765 char **body;
3766 int i;
3769 void
3770 print_setting(struct settings *s, char *val, void *cb_args)
3772 char *tmp, *color;
3773 struct settings_args *sa = cb_args;
3775 if (sa == NULL)
3776 return;
3778 if (s->flags & XT_SF_RUNTIME)
3779 color = "#22cc22";
3780 else
3781 color = "#cccccc";
3783 tmp = *sa->body;
3784 *sa->body = g_strdup_printf(
3785 "%s\n<tr>"
3786 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
3787 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
3788 *sa->body,
3789 color,
3790 s->name,
3791 color,
3794 g_free(tmp);
3795 sa->i++;
3799 set(struct tab *t, struct karg *args)
3801 char *header, *body, *footer, *page, *tmp, *pars;
3802 int i = 1;
3803 struct settings_args sa;
3805 if ((pars = getparams(args->s, "set")) == NULL) {
3806 bzero(&sa, sizeof sa);
3807 sa.body = &body;
3809 /* header */
3810 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3811 "\n<head><title>Settings</title>\n"
3812 "</head><body><h1>Settings</h1>\n");
3814 /* body */
3815 body = g_strdup_printf("<div align='center'><table><tr>"
3816 "<th align='left'>Setting</th>"
3817 "<th align='left'>Value</th></tr>\n");
3819 settings_walk(print_setting, &sa);
3820 i = sa.i;
3822 /* small message if there are none */
3823 if (i == 1) {
3824 tmp = body;
3825 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3826 "colspan='2'>No settings</td></tr>\n", body);
3827 g_free(tmp);
3830 /* footer */
3831 footer = g_strdup_printf("</table></div></body></html>");
3833 page = g_strdup_printf("%s%s%s", header, body, footer);
3835 g_free(header);
3836 g_free(body);
3837 g_free(footer);
3839 load_webkit_string(t, page);
3840 } else
3841 show_oops(t, "Invalid command: %s", pars);
3843 return (XT_CB_PASSTHROUGH);
3847 session_save(struct tab *t, char *filename, char **ret)
3849 struct karg a;
3850 char *f = filename;
3851 int rv = 1;
3853 f += strlen("save");
3854 while (*f == ' ' && *f != '\0')
3855 f++;
3856 if (strlen(f) == 0)
3857 goto done;
3859 *ret = f;
3860 if (f[0] == '.' || f[0] == '/')
3861 goto done;
3863 a.s = f;
3864 if (save_tabs(t, &a))
3865 goto done;
3866 strlcpy(named_session, f, sizeof named_session);
3868 rv = 0;
3869 done:
3870 return (rv);
3874 session_open(struct tab *t, char *filename, char **ret)
3876 struct karg a;
3877 char *f = filename;
3878 int rv = 1;
3880 f += strlen("open");
3881 while (*f == ' ' && *f != '\0')
3882 f++;
3883 if (strlen(f) == 0)
3884 goto done;
3886 *ret = f;
3887 if (f[0] == '.' || f[0] == '/')
3888 goto done;
3890 a.s = f;
3891 a.i = XT_SES_CLOSETABS;
3892 if (open_tabs(t, &a))
3893 goto done;
3895 strlcpy(named_session, f, sizeof named_session);
3897 rv = 0;
3898 done:
3899 return (rv);
3903 session_delete(struct tab *t, char *filename, char **ret)
3905 char file[PATH_MAX];
3906 char *f = filename;
3907 int rv = 1;
3909 f += strlen("delete");
3910 while (*f == ' ' && *f != '\0')
3911 f++;
3912 if (strlen(f) == 0)
3913 goto done;
3915 *ret = f;
3916 if (f[0] == '.' || f[0] == '/')
3917 goto done;
3919 snprintf(file, sizeof file, "%s/%s", sessions_dir, f);
3920 if (unlink(file))
3921 goto done;
3923 if (!strcmp(f, named_session))
3924 strlcpy(named_session, XT_SAVED_TABS_FILE,
3925 sizeof named_session);
3927 rv = 0;
3928 done:
3929 return (rv);
3933 session_cmd(struct tab *t, struct karg *args)
3935 char *action = NULL;
3936 char *filename = NULL;
3938 if (t == NULL)
3939 return (1);
3941 if ((action = getparams(args->s, "session")))
3943 else
3944 action = "show";
3946 if (!strcmp(action, "show"))
3947 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
3948 XT_SAVED_TABS_FILE : named_session);
3949 else if (g_str_has_prefix(action, "save ")) {
3950 if (session_save(t, action, &filename)) {
3951 show_oops(t, "Can't save session: %s",
3952 filename ? filename : "INVALID");
3953 goto done;
3955 } else if (g_str_has_prefix(action, "open ")) {
3956 if (session_open(t, action, &filename)) {
3957 show_oops(t, "Can't open session: %s",
3958 filename ? filename : "INVALID");
3959 goto done;
3961 } else if (g_str_has_prefix(action, "delete ")) {
3962 if (session_delete(t, action, &filename)) {
3963 show_oops(t, "Can't delete session: %s",
3964 filename ? filename : "INVALID");
3965 goto done;
3967 } else
3968 show_oops(t, "Invalid command: %s", action);
3969 done:
3970 return (XT_CB_PASSTHROUGH);
3974 * Make a hardcopy of the page
3977 print_page(struct tab *t, struct karg *args)
3979 WebKitWebFrame *frame;
3981 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
3984 * for now we just call the GTK print box,
3985 * but later we might decide to hook in a command.
3987 frame = webkit_web_view_get_main_frame(t->wv);
3988 webkit_web_frame_print(frame);
3990 return (0);
3994 go_home(struct tab *t, struct karg *args)
3996 char *newuri;
3998 newuri = guess_url_type((char *)home);
3999 webkit_web_view_load_uri(t->wv, newuri);
4000 free(newuri);
4002 return (0);
4006 restart(struct tab *t, struct karg *args)
4008 struct karg a;
4010 a.s = XT_RESTART_TABS_FILE;
4011 save_tabs(t, &a);
4012 execvp(start_argv[0], start_argv);
4013 /* NOTREACHED */
4015 return (0);
4018 /* inherent to GTK not all keys will be caught at all times */
4019 /* XXX sort key bindings */
4020 struct key_bindings {
4021 guint mask;
4022 guint use_in_entry;
4023 guint key;
4024 int (*func)(struct tab *, struct karg *);
4025 struct karg arg;
4026 } keys[] = {
4027 { GDK_MOD1_MASK, 0, GDK_d, xtp_page_dl, {0} },
4028 { GDK_MOD1_MASK, 0, GDK_h, xtp_page_hl, {0} },
4029 { GDK_CONTROL_MASK, 0, GDK_p, print_page, {0}},
4030 { 0, 0, GDK_slash, command, {.i = '/'} },
4031 { GDK_SHIFT_MASK, 0, GDK_question, command, {.i = '?'} },
4032 { GDK_SHIFT_MASK, 0, GDK_colon, command, {.i = ':'} },
4033 { GDK_CONTROL_MASK, 0, GDK_q, quit, {0} },
4034 { GDK_MOD1_MASK, 0, GDK_q, restart, {0} },
4035 { GDK_CONTROL_MASK, 0, GDK_j, toggle_js, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
4036 { GDK_MOD1_MASK, 0, GDK_c, toggle_cwl, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
4037 { GDK_CONTROL_MASK, 0, GDK_s, toggle_src, {0} },
4038 { 0, 0, GDK_y, yank_uri, {0} },
4039 { 0, 0, GDK_p, paste_uri, {.i = XT_PASTE_CURRENT_TAB} },
4040 { GDK_SHIFT_MASK, 0, GDK_P, paste_uri, {.i = XT_PASTE_NEW_TAB} },
4042 /* search */
4043 { 0, 0, GDK_n, search, {.i = XT_SEARCH_NEXT} },
4044 { GDK_SHIFT_MASK, 0, GDK_N, search, {.i = XT_SEARCH_PREV} },
4046 /* focus */
4047 { 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} },
4048 { 0, 0, GDK_F7, focus, {.i = XT_FOCUS_SEARCH} },
4050 /* command aliases (handy when -S flag is used) */
4051 { 0, 0, GDK_F9, command, {.i = XT_CMD_OPEN} },
4052 { 0, 0, GDK_F10, command, {.i = XT_CMD_OPEN_CURRENT} },
4053 { 0, 0, GDK_F11, command, {.i = XT_CMD_TABNEW} },
4054 { 0, 0, GDK_F12, command, {.i = XT_CMD_TABNEW_CURRENT} },
4056 /* hinting */
4057 { 0, 0, GDK_f, hint, {.i = 0} },
4059 /* navigation */
4060 { 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
4061 { GDK_MOD1_MASK, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
4062 { GDK_SHIFT_MASK, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
4063 { GDK_MOD1_MASK, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
4064 { 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} },
4065 { GDK_CONTROL_MASK, 0, GDK_r, navaction, {.i = XT_NAV_RELOAD} },
4066 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_R, navaction, {.i = XT_NAV_RELOAD_CACHE} },
4067 { GDK_CONTROL_MASK, 0, GDK_l, navaction, {.i = XT_NAV_RELOAD} },
4068 { GDK_MOD1_MASK, 1, GDK_f, xtp_page_fl, {0} },
4070 /* vertical movement */
4071 { 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
4072 { 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
4073 { 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
4074 { 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
4075 { GDK_SHIFT_MASK, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
4076 { 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
4077 { 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
4078 { 0, 0, GDK_g, move, {.i = XT_MOVE_TOP} }, /* XXX make this work */
4079 { 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
4080 { GDK_CONTROL_MASK, 0, GDK_f, move, {.i = XT_MOVE_PAGEDOWN} },
4081 { GDK_CONTROL_MASK, 0, GDK_d, move, {.i = XT_MOVE_HALFDOWN} },
4082 { 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
4083 { 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
4084 { GDK_CONTROL_MASK, 0, GDK_b, move, {.i = XT_MOVE_PAGEUP} },
4085 { GDK_CONTROL_MASK, 0, GDK_u, move, {.i = XT_MOVE_HALFUP} },
4086 /* horizontal movement */
4087 { 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
4088 { 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
4089 { 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
4090 { 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
4091 { GDK_SHIFT_MASK, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
4092 { 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
4094 /* tabs */
4095 { GDK_CONTROL_MASK, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
4096 { GDK_CONTROL_MASK, 1, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
4097 { GDK_SHIFT_MASK, 0, GDK_U, tabaction, {.i = XT_TAB_UNDO_CLOSE} },
4098 { GDK_CONTROL_MASK, 0, GDK_1, movetab, {.i = 1} },
4099 { GDK_CONTROL_MASK, 0, GDK_2, movetab, {.i = 2} },
4100 { GDK_CONTROL_MASK, 0, GDK_3, movetab, {.i = 3} },
4101 { GDK_CONTROL_MASK, 0, GDK_4, movetab, {.i = 4} },
4102 { GDK_CONTROL_MASK, 0, GDK_5, movetab, {.i = 5} },
4103 { GDK_CONTROL_MASK, 0, GDK_6, movetab, {.i = 6} },
4104 { GDK_CONTROL_MASK, 0, GDK_7, movetab, {.i = 7} },
4105 { GDK_CONTROL_MASK, 0, GDK_8, movetab, {.i = 8} },
4106 { GDK_CONTROL_MASK, 0, GDK_9, movetab, {.i = 9} },
4107 { GDK_CONTROL_MASK, 0, GDK_0, movetab, {.i = 10} },
4108 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_less, movetab, {.i = XT_TAB_FIRST} },
4109 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_greater, movetab, {.i = XT_TAB_LAST} },
4110 { GDK_CONTROL_MASK, 0, GDK_Left, movetab, {.i = XT_TAB_PREV} },
4111 { GDK_CONTROL_MASK, 0, GDK_Right, movetab, {.i = XT_TAB_NEXT} },
4112 { GDK_CONTROL_MASK, 0, GDK_minus, resizetab, {.i = -1} },
4113 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_plus, resizetab, {.i = 1} },
4114 { GDK_CONTROL_MASK, 0, GDK_equal, resizetab, {.i = 1} },
4117 struct cmd {
4118 char *cmd;
4119 int params;
4120 int (*func)(struct tab *, struct karg *);
4121 struct karg arg;
4122 } cmds[] = {
4123 { "q!", 0, quit, {0} },
4124 { "qa", 0, quit, {0} },
4125 { "qa!", 0, quit, {0} },
4126 { "w", 0, save_tabs, {0} },
4127 { "wq", 0, save_tabs_and_quit, {0} },
4128 { "wq!", 0, save_tabs_and_quit, {0} },
4129 { "help", 0, help, {0} },
4130 { "about", 0, about, {0} },
4131 { "stats", 0, stats, {0} },
4132 { "version", 0, about, {0} },
4133 { "cookies", 0, xtp_page_cl, {0} },
4134 { "fav", 0, xtp_page_fl, {0} },
4135 { "favadd", 0, add_favorite, {0} },
4136 { "js", 2, js_cmd, {0} },
4137 { "cookie", 2, cookie_cmd, {0} },
4138 { "cert", 1, cert_cmd, {0} },
4139 { "ca", 0, ca_cmd, {0} },
4140 { "dl" , 0, xtp_page_dl, {0} },
4141 { "h" , 0, xtp_page_hl, {0} },
4142 { "hist" , 0, xtp_page_hl, {0} },
4143 { "history" , 0, xtp_page_hl, {0} },
4144 { "home" , 0, go_home, {0} },
4145 { "restart" , 0, restart, {0} },
4146 { "urlhide", 0, urlaction, {.i = XT_URL_HIDE} },
4147 { "urlh", 0, urlaction, {.i = XT_URL_HIDE} },
4148 { "urlshow", 0, urlaction, {.i = XT_URL_SHOW} },
4149 { "urls", 0, urlaction, {.i = XT_URL_SHOW} },
4151 { "1", 0, move, {.i = XT_MOVE_TOP} },
4152 { "print", 0, print_page, {0} },
4154 /* tabs */
4155 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
4156 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
4157 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
4158 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
4159 { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} },
4160 { "tabe", 1, tabaction, {.i = XT_TAB_NEW} },
4161 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
4162 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} },
4163 { "tabshow", 1, tabaction, {.i = XT_TAB_SHOW} },
4164 { "tabs", 1, tabaction, {.i = XT_TAB_SHOW} },
4165 { "tabhide", 1, tabaction, {.i = XT_TAB_HIDE} },
4166 { "tabh", 1, tabaction, {.i = XT_TAB_HIDE} },
4167 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4168 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4169 /* XXX add count to these commands */
4170 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST} },
4171 { "tabfir", 0, movetab, {.i = XT_TAB_FIRST} },
4172 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST} },
4173 { "tabr", 0, movetab, {.i = XT_TAB_FIRST} },
4174 { "tablast", 0, movetab, {.i = XT_TAB_LAST} },
4175 { "tabl", 0, movetab, {.i = XT_TAB_LAST} },
4176 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
4177 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
4178 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
4179 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
4181 /* settings */
4182 { "set", 1, set, {0} },
4183 { "fullscreen", 0, fullscreen, {0} },
4184 { "f", 0, fullscreen, {0} },
4186 /* sessions */
4187 { "session", 1, session_cmd, {0} },
4190 gboolean
4191 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
4193 hide_oops(t);
4195 return (FALSE);
4198 gboolean
4199 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
4201 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
4203 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
4204 delete_tab(t);
4206 return (FALSE);
4210 * cancel, remove, etc. downloads
4212 void
4213 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
4215 struct download find, *d;
4217 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
4219 /* some commands require a valid download id */
4220 if (cmd != XT_XTP_DL_LIST) {
4221 /* lookup download in question */
4222 find.id = id;
4223 d = RB_FIND(download_list, &downloads, &find);
4225 if (d == NULL) {
4226 show_oops(t, "%s: no such download", __func__);
4227 return;
4231 /* decide what to do */
4232 switch (cmd) {
4233 case XT_XTP_DL_CANCEL:
4234 webkit_download_cancel(d->download);
4235 break;
4236 case XT_XTP_DL_REMOVE:
4237 webkit_download_cancel(d->download); /* just incase */
4238 g_object_unref(d->download);
4239 RB_REMOVE(download_list, &downloads, d);
4240 break;
4241 case XT_XTP_DL_LIST:
4242 /* Nothing */
4243 break;
4244 default:
4245 show_oops(t, "%s: unknown command", __func__);
4246 break;
4248 xtp_page_dl(t, NULL);
4252 * Actions on history, only does one thing for now, but
4253 * we provide the function for future actions
4255 void
4256 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
4258 struct history *h, *next;
4259 int i = 1;
4261 switch (cmd) {
4262 case XT_XTP_HL_REMOVE:
4263 /* walk backwards, as listed in reverse */
4264 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
4265 next = RB_PREV(history_list, &hl, h);
4266 if (id == i) {
4267 RB_REMOVE(history_list, &hl, h);
4268 g_free((gpointer) h->title);
4269 g_free((gpointer) h->uri);
4270 g_free(h);
4271 break;
4273 i++;
4275 break;
4276 case XT_XTP_HL_LIST:
4277 /* Nothing - just xtp_page_hl() below */
4278 break;
4279 default:
4280 show_oops(t, "%s: unknown command", __func__);
4281 break;
4284 xtp_page_hl(t, NULL);
4287 /* remove a favorite */
4288 void
4289 remove_favorite(struct tab *t, int index)
4291 char file[PATH_MAX], *title, *uri;
4292 char *new_favs, *tmp;
4293 FILE *f;
4294 int i;
4295 size_t len, lineno;
4297 /* open favorites */
4298 snprintf(file, sizeof file, "%s/%s/%s",
4299 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
4301 if ((f = fopen(file, "r")) == NULL) {
4302 show_oops(t, "%s: can't open favorites: %s",
4303 __func__, strerror(errno));
4304 return;
4307 /* build a string which will become the new favroites file */
4308 new_favs = g_strdup_printf("%s", "");
4310 for (i = 1;;) {
4311 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
4312 if (feof(f) || ferror(f))
4313 break;
4314 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
4315 if (len == 0) {
4316 free(title);
4317 title = NULL;
4318 continue;
4321 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
4322 if (feof(f) || ferror(f)) {
4323 show_oops(t, "%s: can't parse favorites %s",
4324 __func__, strerror(errno));
4325 goto clean;
4329 /* as long as this isn't the one we are deleting add to file */
4330 if (i != index) {
4331 tmp = new_favs;
4332 new_favs = g_strdup_printf("%s%s\n%s\n",
4333 new_favs, title, uri);
4334 g_free(tmp);
4337 free(uri);
4338 uri = NULL;
4339 free(title);
4340 title = NULL;
4341 i++;
4343 fclose(f);
4345 /* write back new favorites file */
4346 if ((f = fopen(file, "w")) == NULL) {
4347 show_oops(t, "%s: can't open favorites: %s",
4348 __func__, strerror(errno));
4349 goto clean;
4352 fwrite(new_favs, strlen(new_favs), 1, f);
4353 fclose(f);
4355 clean:
4356 if (uri)
4357 free(uri);
4358 if (title)
4359 free(title);
4361 g_free(new_favs);
4364 void
4365 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
4367 switch (cmd) {
4368 case XT_XTP_FL_LIST:
4369 /* nothing, just the below call to xtp_page_fl() */
4370 break;
4371 case XT_XTP_FL_REMOVE:
4372 remove_favorite(t, arg);
4373 break;
4374 default:
4375 show_oops(t, "%s: invalid favorites command", __func__);
4376 break;
4379 xtp_page_fl(t, NULL);
4382 void
4383 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
4385 switch (cmd) {
4386 case XT_XTP_CL_LIST:
4387 /* nothing, just xtp_page_cl() */
4388 break;
4389 case XT_XTP_CL_REMOVE:
4390 remove_cookie(arg);
4391 break;
4392 default:
4393 show_oops(t, "%s: unknown cookie xtp command", __func__);
4394 break;
4397 xtp_page_cl(t, NULL);
4400 /* link an XTP class to it's session key and handler function */
4401 struct xtp_despatch {
4402 uint8_t xtp_class;
4403 char **session_key;
4404 void (*handle_func)(struct tab *, uint8_t, int);
4407 struct xtp_despatch xtp_despatches[] = {
4408 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
4409 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
4410 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
4411 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
4412 { NULL, NULL, NULL }
4416 * is the url xtp protocol? (xxxt://)
4417 * if so, parse and despatch correct bahvior
4420 parse_xtp_url(struct tab *t, const char *url)
4422 char *dup = NULL, *p, *last;
4423 uint8_t n_tokens = 0;
4424 char *tokens[4] = {NULL, NULL, NULL, ""};
4425 struct xtp_despatch *dsp, *dsp_match = NULL;
4426 uint8_t req_class;
4429 * tokens array meaning:
4430 * tokens[0] = class
4431 * tokens[1] = session key
4432 * tokens[2] = action
4433 * tokens[3] = optional argument
4436 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
4438 /*xtp tab meaning is normal unless proven special */
4439 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4441 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
4442 return 0;
4444 dup = g_strdup(url + strlen(XT_XTP_STR));
4446 /* split out the url */
4447 for ((p = strtok_r(dup, "/", &last)); p;
4448 (p = strtok_r(NULL, "/", &last))) {
4449 if (n_tokens < 4)
4450 tokens[n_tokens++] = p;
4453 /* should be atleast three fields 'class/seskey/command/arg' */
4454 if (n_tokens < 3)
4455 goto clean;
4457 dsp = xtp_despatches;
4458 req_class = atoi(tokens[0]);
4459 while (dsp->xtp_class != NULL) {
4460 if (dsp->xtp_class == req_class) {
4461 dsp_match = dsp;
4462 break;
4464 dsp++;
4467 /* did we find one atall? */
4468 if (dsp_match == NULL) {
4469 show_oops(t, "%s: no matching xtp despatch found", __func__);
4470 goto clean;
4473 /* check session key and call despatch function */
4474 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
4475 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
4478 clean:
4479 if (dup)
4480 g_free(dup);
4482 return 1;
4487 void
4488 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
4490 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
4491 char *newuri = NULL;
4493 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
4495 if (t == NULL)
4496 errx(1, "activate_uri_entry_cb");
4498 if (uri == NULL)
4499 errx(1, "uri");
4501 uri += strspn(uri, "\t ");
4503 /* if xxxt:// treat specially */
4504 if (!parse_xtp_url(t, uri)) {
4505 if (valid_url_type((char *)uri)) {
4506 newuri = guess_url_type((char *)uri);
4507 uri = newuri;
4510 webkit_web_view_load_uri(t->wv, uri);
4511 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4514 if (newuri)
4515 g_free(newuri);
4518 void
4519 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
4521 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
4522 char *newuri = NULL;
4524 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
4526 if (t == NULL)
4527 errx(1, "activate_search_entry_cb");
4529 if (search_string == NULL) {
4530 show_oops(t, "no search_string");
4531 return;
4534 newuri = g_strdup_printf(search_string, search);
4536 webkit_web_view_load_uri(t->wv, newuri);
4537 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4539 if (newuri)
4540 g_free(newuri);
4543 void
4544 check_and_set_js(gchar *uri, struct tab *t)
4546 struct domain *d = NULL;
4547 int es = 0;
4549 if (uri == NULL || t == NULL)
4550 return;
4552 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
4553 es = 0;
4554 else
4555 es = 1;
4557 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
4558 es ? "enable" : "disable", uri);
4560 g_object_set((GObject *)t->settings,
4561 "enable-scripts", es, (char *)NULL);
4562 webkit_web_view_set_settings(t->wv, t->settings);
4564 button_set_stockid(t->js_toggle,
4565 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
4568 void
4569 show_ca_status(struct tab *t, const char *uri)
4571 WebKitWebFrame *frame;
4572 WebKitWebDataSource *source;
4573 WebKitNetworkRequest *request;
4574 SoupMessage *message;
4575 GdkColor color;
4576 gchar *col_str = "white";
4577 int r;
4579 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
4580 ssl_strict_certs, ssl_ca_file, uri);
4582 if (uri == NULL)
4583 goto done;
4584 if (ssl_ca_file == NULL) {
4585 if (g_str_has_prefix(uri, "http://"))
4586 goto done;
4587 if (g_str_has_prefix(uri, "https://")) {
4588 col_str = "red";
4589 goto done;
4591 return;
4593 if (g_str_has_prefix(uri, "http://") ||
4594 !g_str_has_prefix(uri, "https://"))
4595 goto done;
4597 frame = webkit_web_view_get_main_frame(t->wv);
4598 source = webkit_web_frame_get_data_source(frame);
4599 request = webkit_web_data_source_get_request(source);
4600 message = webkit_network_request_get_message(request);
4602 if (message && (soup_message_get_flags(message) &
4603 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
4604 col_str = "green";
4605 goto done;
4606 } else {
4607 r = load_compare_cert(t, NULL);
4608 if (r == 0)
4609 col_str = "lightblue";
4610 else if (r == 1)
4611 col_str = "yellow";
4612 else
4613 col_str = "red";
4614 goto done;
4616 done:
4617 if (col_str) {
4618 gdk_color_parse(col_str, &color);
4619 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
4623 void
4624 free_favicon(struct tab *t)
4626 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p pix %p\n",
4627 __func__, t->icon_download, t->icon_request, t->icon_pixbuf);
4629 if (t->icon_request)
4630 g_object_unref(t->icon_request);
4631 if (t->icon_pixbuf)
4632 g_object_unref(t->icon_pixbuf);
4633 if (t->icon_dest_uri)
4634 g_free(t->icon_dest_uri);
4636 t->icon_pixbuf = NULL;
4637 t->icon_request = NULL;
4638 t->icon_dest_uri = NULL;
4641 void
4642 abort_favicon_download(struct tab *t)
4644 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
4646 if (t->icon_download)
4647 webkit_download_cancel(t->icon_download);
4648 else
4649 free_favicon(t);
4651 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
4652 GTK_ENTRY_ICON_PRIMARY, "text-html");
4655 void
4656 set_favicon_from_file(struct tab *t, char *file)
4658 gint width, height;
4659 GdkPixbuf *pixbuf, *scaled;
4660 struct stat sb;
4662 if (t == NULL || file == NULL)
4663 return;
4664 if (t->icon_pixbuf) {
4665 DNPRINTF(XT_D_DOWNLOAD, "%s: icon already set\n", __func__);
4666 return;
4669 if (g_str_has_prefix(file, "file://"))
4670 file += strlen("file://");
4671 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
4673 if (!stat(file, &sb)) {
4674 if (sb.st_size == 0) {
4675 /* corrupt icon so trash it */
4676 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
4677 __func__, file);
4678 unlink(file);
4679 /* no need to set icon to default here */
4680 return;
4684 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
4685 if (pixbuf == NULL) {
4686 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
4687 GTK_ENTRY_ICON_PRIMARY, "text-html");
4688 return;
4691 g_object_get(pixbuf, "width", &width, "height", &height,
4692 (char *)NULL);
4693 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
4694 __func__, t->tab_id, width, height);
4696 if (width > 16 || height > 16) {
4697 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
4698 GDK_INTERP_BILINEAR);
4699 g_object_unref(pixbuf);
4700 } else
4701 scaled = pixbuf;
4703 if (scaled == NULL) {
4704 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
4705 GDK_INTERP_BILINEAR);
4706 return;
4709 t->icon_pixbuf = scaled;
4710 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
4711 GTK_ENTRY_ICON_PRIMARY, t->icon_pixbuf);
4714 void
4715 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
4716 struct tab *t)
4718 WebKitDownloadStatus status = webkit_download_get_status(download);
4720 if (t == NULL)
4721 return;
4723 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
4724 __func__, t->tab_id, status);
4726 switch (status) {
4727 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4728 /* -1 */
4729 t->icon_download = NULL;
4730 free_favicon(t);
4731 break;
4732 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4733 /* 0 */
4734 break;
4735 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4736 /* 1 */
4737 break;
4738 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4739 /* 2 */
4740 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
4741 __func__, t->tab_id);
4742 t->icon_download = NULL;
4743 free_favicon(t);
4744 break;
4745 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4746 /* 3 */
4747 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
4748 __func__, t->icon_dest_uri);
4749 set_favicon_from_file(t, t->icon_dest_uri);
4750 /* these will be freed post callback */
4751 t->icon_request = NULL;
4752 t->icon_download = NULL;
4753 break;
4754 default:
4755 break;
4759 void
4760 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
4762 gchar *name_hash, file[PATH_MAX];
4763 struct stat sb;
4765 DNPRINTF(XT_D_DOWNLOAD, "notify_icon_loaded_cb %s\n", uri);
4767 if (uri == NULL || t == NULL)
4768 return;
4770 if (t->icon_request) {
4771 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
4772 return;
4775 /* check to see if we got the icon in cache */
4776 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
4777 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
4778 g_free(name_hash);
4780 if (!stat(file, &sb)) {
4781 if (sb.st_size > 0) {
4782 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
4783 __func__, file);
4784 set_favicon_from_file(t, file);
4785 return;
4788 /* corrupt icon so trash it */
4789 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
4790 __func__, file);
4791 unlink(file);
4794 /* create download for icon */
4795 t->icon_request = webkit_network_request_new(uri);
4796 if (t->icon_request == NULL) {
4797 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
4798 __func__, uri);
4799 return;
4802 t->icon_download = webkit_download_new(t->icon_request);
4804 /* we have to free icon_dest_uri later */
4805 t->icon_dest_uri = g_strdup_printf("file://%s", file);
4806 webkit_download_set_destination_uri(t->icon_download,
4807 t->icon_dest_uri);
4809 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
4810 G_CALLBACK(favicon_download_status_changed_cb), t);
4812 webkit_download_start(t->icon_download);
4815 void
4816 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
4818 WebKitWebFrame *frame;
4819 const gchar *set = NULL, *uri = NULL, *title = NULL;
4820 struct history *h, find;
4821 int add = 0;
4822 const gchar *s_loading;
4823 struct karg a;
4825 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d\n",
4826 webkit_web_view_get_load_status(wview));
4828 if (t == NULL)
4829 errx(1, "notify_load_status_cb");
4831 switch (webkit_web_view_get_load_status(wview)) {
4832 case WEBKIT_LOAD_PROVISIONAL:
4833 abort_favicon_download(t);
4834 #if GTK_CHECK_VERSION(2, 20, 0)
4835 gtk_widget_show(t->spinner);
4836 gtk_spinner_start(GTK_SPINNER(t->spinner));
4837 #endif
4838 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
4840 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
4841 t->focus_wv = 1;
4843 /* take focus if we are visible */
4844 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
4845 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4847 break;
4849 case WEBKIT_LOAD_COMMITTED:
4850 frame = webkit_web_view_get_main_frame(wview);
4851 uri = webkit_web_frame_get_uri(frame);
4852 if (uri)
4853 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
4855 /* check if js white listing is enabled */
4856 if (enable_js_whitelist) {
4857 frame = webkit_web_view_get_main_frame(wview);
4858 uri = webkit_web_frame_get_uri(frame);
4859 check_and_set_js((gchar *)uri, t);
4862 show_ca_status(t, uri);
4864 /* we know enough to autosave the session */
4865 if (session_autosave) {
4866 a.s = NULL;
4867 save_tabs(t, &a);
4869 break;
4871 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
4872 title = webkit_web_view_get_title(wview);
4873 frame = webkit_web_view_get_main_frame(wview);
4874 uri = webkit_web_frame_get_uri(frame);
4875 if (title)
4876 set = title;
4877 else if (uri)
4878 set = uri;
4879 else
4880 break;
4882 gtk_label_set_text(GTK_LABEL(t->label), set);
4883 gtk_window_set_title(GTK_WINDOW(main_window), set);
4885 if (uri) {
4886 if (!strncmp(uri, "http://", strlen("http://")) ||
4887 !strncmp(uri, "https://", strlen("https://")) ||
4888 !strncmp(uri, "file://", strlen("file://")))
4889 add = 1;
4890 if (add == 0)
4891 break;
4892 find.uri = uri;
4893 h = RB_FIND(history_list, &hl, &find);
4894 if (h)
4895 break;
4897 h = g_malloc(sizeof *h);
4898 h->uri = g_strdup(uri);
4899 if (title)
4900 h->title = g_strdup(title);
4901 else
4902 h->title = g_strdup(uri);
4903 RB_INSERT(history_list, &hl, h);
4904 update_history_tabs(NULL);
4907 break;
4909 case WEBKIT_LOAD_FINISHED:
4910 #if WEBKIT_CHECK_VERSION(1, 1, 18)
4911 case WEBKIT_LOAD_FAILED:
4912 #endif
4913 #if GTK_CHECK_VERSION(2, 20, 0)
4914 gtk_spinner_stop(GTK_SPINNER(t->spinner));
4915 gtk_widget_hide(t->spinner);
4916 #endif
4917 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
4918 if (s_loading && !strcmp(s_loading, "Loading"))
4919 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
4920 default:
4921 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
4922 break;
4925 if (t->item)
4926 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
4927 else
4928 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
4929 webkit_web_view_can_go_back(wview));
4931 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
4932 webkit_web_view_can_go_forward(wview));
4935 void
4936 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
4938 run_script(t, JS_HINTING);
4941 void
4942 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
4944 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
4945 progress == 100 ? 0 : (double)progress / 100);
4949 webview_nw_cb(WebKitWebView *wv, WebKitWebFrame *wf,
4950 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
4951 WebKitWebPolicyDecision *pd, struct tab *t)
4953 char *uri;
4955 if (t == NULL)
4956 errx(1, "webview_nw_cb");
4958 DNPRINTF(XT_D_NAV, "webview_nw_cb: %s\n",
4959 webkit_network_request_get_uri(request));
4961 /* open in current tab */
4962 uri = (char *)webkit_network_request_get_uri(request);
4963 webkit_web_view_load_uri(t->wv, uri);
4964 webkit_web_policy_decision_ignore(pd);
4966 return (TRUE); /* we made the decission */
4970 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
4971 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
4972 WebKitWebPolicyDecision *pd, struct tab *t)
4974 char *uri;
4976 if (t == NULL)
4977 errx(1, "webview_npd_cb");
4979 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
4980 t->ctrl_click,
4981 webkit_network_request_get_uri(request));
4983 uri = (char *)webkit_network_request_get_uri(request);
4985 if ((!parse_xtp_url(t, uri) && (t->ctrl_click))) {
4986 t->ctrl_click = 0;
4987 create_new_tab(uri, NULL, ctrl_click_focus);
4988 webkit_web_policy_decision_ignore(pd);
4989 return (TRUE); /* we made the decission */
4992 webkit_web_policy_decision_use(pd);
4993 return (TRUE); /* we made the decission */
4996 WebKitWebView *
4997 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
4999 if (t == NULL)
5000 errx(1, "webview_cwv_cb");
5002 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
5003 webkit_web_view_get_uri(wv));
5005 return (wv);
5009 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
5011 /* we can not eat the event without throwing gtk off so defer it */
5013 /* catch middle click */
5014 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
5015 t->ctrl_click = 1;
5016 goto done;
5019 /* catch ctrl click */
5020 if (e->type == GDK_BUTTON_RELEASE &&
5021 CLEAN(e->state) == GDK_CONTROL_MASK)
5022 t->ctrl_click = 1;
5023 else
5024 t->ctrl_click = 0;
5025 done:
5026 return (XT_CB_PASSTHROUGH);
5030 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
5032 struct mime_type *m;
5034 m = find_mime_type(mime_type);
5035 if (m == NULL)
5036 return (1);
5038 switch (fork()) {
5039 case -1:
5040 err(1, "fork");
5041 /* NOTREACHED */
5042 case 0:
5043 break;
5044 default:
5045 return (0);
5048 /* child */
5049 execlp(m->mt_action, m->mt_action,
5050 webkit_network_request_get_uri(request), (void *)NULL);
5052 _exit(0);
5054 /* NOTREACHED */
5055 return (0);
5059 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
5060 WebKitNetworkRequest *request, char *mime_type,
5061 WebKitWebPolicyDecision *decision, struct tab *t)
5063 if (t == NULL)
5064 errx(1, "webview_mimetype_cb");
5066 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
5067 t->tab_id, mime_type);
5069 if (run_mimehandler(t, mime_type, request) == 0) {
5070 webkit_web_policy_decision_ignore(decision);
5071 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5072 return (TRUE);
5075 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
5076 webkit_web_policy_decision_download(decision);
5077 return (TRUE);
5080 return (FALSE);
5084 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download, struct tab *t)
5086 const gchar *filename;
5087 char *uri = NULL;
5088 struct download *download_entry;
5089 int ret = TRUE;
5091 if (wk_download == NULL || t == NULL)
5092 errx(1, "%s: invalid pointers", __func__);
5094 filename = webkit_download_get_suggested_filename(wk_download);
5095 if (filename == NULL)
5096 return (FALSE); /* abort download */
5098 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
5100 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
5101 "local %s\n", __func__, t->tab_id, filename, uri);
5103 webkit_download_set_destination_uri(wk_download, uri);
5105 if (webkit_download_get_status(wk_download) ==
5106 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5107 show_oops(t, "%s: download failed to start", __func__);
5108 ret = FALSE;
5109 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
5110 } else {
5111 download_entry = g_malloc(sizeof(struct download));
5112 download_entry->download = wk_download;
5113 download_entry->tab = t;
5114 download_entry->id = next_download_id++;
5115 RB_INSERT(download_list, &downloads, download_entry);
5116 /* get from history */
5117 g_object_ref(wk_download);
5118 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
5121 if (uri)
5122 g_free(uri);
5124 /* sync other download manager tabs */
5125 update_download_tabs(NULL);
5128 * NOTE: never redirect/render the current tab before this
5129 * function returns. This will cause the download to never start.
5131 return (ret); /* start download */
5134 /* XXX currently unused */
5135 void
5136 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
5138 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
5140 if (t == NULL)
5141 errx(1, "webview_hover_cb");
5143 if (uri) {
5144 if (t->hover) {
5145 g_free(t->hover);
5146 t->hover = NULL;
5148 t->hover = g_strdup(uri);
5149 } else if (t->hover) {
5150 g_free(t->hover);
5151 t->hover = NULL;
5156 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
5158 int i;
5159 char s[2], buf[128];
5160 const char *errstr = NULL;
5161 long long link;
5163 /* don't use w directly; use t->whatever instead */
5165 if (t == NULL)
5166 errx(1, "wv_keypress_after_cb");
5168 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
5169 e->keyval, e->state, t);
5171 if (t->hints_on) {
5172 /* ESC */
5173 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
5174 disable_hints(t);
5175 return (XT_CB_HANDLED);
5178 /* RETURN */
5179 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
5180 link = strtonum(t->hint_num, 1, 1000, &errstr);
5181 if (errstr) {
5182 /* we have a string */
5183 } else {
5184 /* we have a number */
5185 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
5186 t->hint_num);
5187 run_script(t, buf);
5189 disable_hints(t);
5192 /* BACKSPACE */
5193 /* XXX unfuck this */
5194 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
5195 if (t->hint_mode == XT_HINT_NUMERICAL) {
5196 /* last input was numerical */
5197 int l;
5198 l = strlen(t->hint_num);
5199 if (l > 0) {
5200 l--;
5201 if (l == 0) {
5202 disable_hints(t);
5203 enable_hints(t);
5204 } else {
5205 t->hint_num[l] = '\0';
5206 goto num;
5209 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
5210 /* last input was alphanumerical */
5211 int l;
5212 l = strlen(t->hint_buf);
5213 if (l > 0) {
5214 l--;
5215 if (l == 0) {
5216 disable_hints(t);
5217 enable_hints(t);
5218 } else {
5219 t->hint_buf[l] = '\0';
5220 goto anum;
5223 } else {
5224 /* bogus */
5225 disable_hints(t);
5229 /* numerical input */
5230 if (CLEAN(e->state) == 0 &&
5231 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
5232 snprintf(s, sizeof s, "%c", e->keyval);
5233 strlcat(t->hint_num, s, sizeof t->hint_num);
5234 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
5235 t->hint_num);
5236 num:
5237 link = strtonum(t->hint_num, 1, 1000, &errstr);
5238 if (errstr) {
5239 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
5240 disable_hints(t);
5241 } else {
5242 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
5243 t->hint_num);
5244 t->hint_mode = XT_HINT_NUMERICAL;
5245 run_script(t, buf);
5248 /* empty the counter buffer */
5249 bzero(t->hint_buf, sizeof t->hint_buf);
5250 return (XT_CB_HANDLED);
5253 /* alphanumerical input */
5254 if (
5255 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
5256 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
5257 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
5258 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
5259 snprintf(s, sizeof s, "%c", e->keyval);
5260 strlcat(t->hint_buf, s, sizeof t->hint_buf);
5261 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
5262 t->hint_buf);
5263 anum:
5264 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
5265 run_script(t, buf);
5267 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
5268 t->hint_buf);
5269 t->hint_mode = XT_HINT_ALPHANUM;
5270 run_script(t, buf);
5272 /* empty the counter buffer */
5273 bzero(t->hint_num, sizeof t->hint_num);
5274 return (XT_CB_HANDLED);
5277 return (XT_CB_HANDLED);
5280 for (i = 0; i < LENGTH(keys); i++)
5281 if (e->keyval == keys[i].key && CLEAN(e->state) ==
5282 keys[i].mask) {
5283 keys[i].func(t, &keys[i].arg);
5284 return (XT_CB_HANDLED);
5288 return (XT_CB_PASSTHROUGH);
5292 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5294 hide_oops(t);
5296 return (XT_CB_PASSTHROUGH);
5300 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5302 const gchar *c = gtk_entry_get_text(w);
5303 GdkColor color;
5304 int forward = TRUE;
5306 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
5307 e->keyval, e->state, t);
5309 if (t == NULL)
5310 errx(1, "cmd_keyrelease_cb");
5312 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
5313 e->keyval, e->state, t);
5315 if (c[0] == ':')
5316 goto done;
5317 if (strlen(c) == 1) {
5318 webkit_web_view_unmark_text_matches(t->wv);
5319 goto done;
5322 if (c[0] == '/')
5323 forward = TRUE;
5324 else if (c[0] == '?')
5325 forward = FALSE;
5326 else
5327 goto done;
5329 /* search */
5330 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
5331 FALSE) {
5332 /* not found, mark red */
5333 gdk_color_parse("red", &color);
5334 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
5335 /* unmark and remove selection */
5336 webkit_web_view_unmark_text_matches(t->wv);
5337 /* my kingdom for a way to unselect text in webview */
5338 } else {
5339 /* found, highlight all */
5340 webkit_web_view_unmark_text_matches(t->wv);
5341 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
5342 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
5343 gdk_color_parse("white", &color);
5344 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
5346 done:
5347 return (XT_CB_PASSTHROUGH);
5350 #if 0
5352 cmd_complete(struct tab *t, char *s)
5354 int i;
5355 GtkEntry *w = GTK_ENTRY(t->cmd);
5357 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", s);
5359 for (i = 0; i < LENGTH(cmds); i++) {
5360 if (!strncasecmp(cmds[i].cmd, s, strlen(s))) {
5361 fprintf(stderr, "match %s %d\n", cmds[i].cmd, strcasecmp(cmds[i].cmd, s));
5362 #if 0
5363 gtk_entry_set_text(w, ":");
5364 gtk_entry_append_text(w, cmds[i].cmd);
5365 gtk_editable_set_position(GTK_EDITABLE(w), -1);
5366 #endif
5370 return (0);
5372 #endif
5375 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5377 int i;
5379 if (t == NULL)
5380 errx(1, "entry_key_cb");
5382 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
5383 e->keyval, e->state, t);
5385 hide_oops(t);
5387 if (e->keyval == GDK_Escape)
5388 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5390 for (i = 0; i < LENGTH(keys); i++)
5391 if (e->keyval == keys[i].key &&
5392 CLEAN(e->state) == keys[i].mask &&
5393 keys[i].use_in_entry) {
5394 keys[i].func(t, &keys[i].arg);
5395 return (XT_CB_HANDLED);
5398 return (XT_CB_PASSTHROUGH);
5402 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5404 int rv = XT_CB_HANDLED;
5405 const gchar *c = gtk_entry_get_text(w);
5407 if (t == NULL)
5408 errx(1, "cmd_keypress_cb");
5410 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
5411 e->keyval, e->state, t);
5413 /* sanity */
5414 if (c == NULL)
5415 e->keyval = GDK_Escape;
5416 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
5417 e->keyval = GDK_Escape;
5419 switch (e->keyval) {
5420 #if 0
5421 case GDK_Tab:
5422 if (c[0] != ':')
5423 goto done;
5425 if (strchr (c, ' ')) {
5426 /* par completion */
5427 fprintf(stderr, "completeme par\n");
5428 goto done;
5431 cmd_complete(t, (char *)&c[1]);
5433 goto done;
5434 #endif
5435 case GDK_BackSpace:
5436 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
5437 break;
5438 /* FALLTHROUGH */
5439 case GDK_Escape:
5440 hide_cmd(t);
5441 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5443 /* cancel search */
5444 if (c[0] == '/' || c[0] == '?')
5445 webkit_web_view_unmark_text_matches(t->wv);
5446 goto done;
5449 rv = XT_CB_PASSTHROUGH;
5450 done:
5451 return (rv);
5455 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
5457 if (t == NULL)
5458 errx(1, "cmd_focusout_cb");
5460 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d focus_wv %d\n",
5461 t->tab_id, t->focus_wv);
5463 hide_cmd(t);
5464 hide_oops(t);
5466 if (show_url == 0 || t->focus_wv)
5467 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5468 else
5469 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
5471 return (XT_CB_PASSTHROUGH);
5474 void
5475 cmd_activate_cb(GtkEntry *entry, struct tab *t)
5477 int i;
5478 char *s;
5479 const gchar *c = gtk_entry_get_text(entry);
5481 if (t == NULL)
5482 errx(1, "cmd_activate_cb");
5484 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
5486 /* sanity */
5487 if (c == NULL)
5488 goto done;
5489 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
5490 goto done;
5491 if (strlen(c) < 2)
5492 goto done;
5493 s = (char *)&c[1];
5495 if (c[0] == '/' || c[0] == '?') {
5496 if (t->search_text) {
5497 g_free(t->search_text);
5498 t->search_text = NULL;
5501 t->search_text = g_strdup(s);
5502 if (global_search)
5503 g_free(global_search);
5504 global_search = g_strdup(s);
5505 t->search_forward = c[0] == '/';
5507 goto done;
5510 for (i = 0; i < LENGTH(cmds); i++)
5511 if (cmds[i].params) {
5512 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
5513 cmds[i].arg.s = g_strdup(s);
5514 goto execute_command;
5516 } else {
5517 if (!strcmp(s, cmds[i].cmd))
5518 goto execute_command;
5520 show_oops(t, "Invalid command: %s", s);
5521 done:
5522 hide_cmd(t);
5523 return;
5525 execute_command:
5526 hide_cmd(t);
5527 cmds[i].func(t, &cmds[i].arg);
5529 void
5530 backward_cb(GtkWidget *w, struct tab *t)
5532 struct karg a;
5534 if (t == NULL)
5535 errx(1, "backward_cb");
5537 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
5539 a.i = XT_NAV_BACK;
5540 navaction(t, &a);
5543 void
5544 forward_cb(GtkWidget *w, struct tab *t)
5546 struct karg a;
5548 if (t == NULL)
5549 errx(1, "forward_cb");
5551 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
5553 a.i = XT_NAV_FORWARD;
5554 navaction(t, &a);
5557 void
5558 stop_cb(GtkWidget *w, struct tab *t)
5560 WebKitWebFrame *frame;
5562 if (t == NULL)
5563 errx(1, "stop_cb");
5565 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
5567 frame = webkit_web_view_get_main_frame(t->wv);
5568 if (frame == NULL) {
5569 show_oops(t, "stop_cb: no frame");
5570 return;
5573 webkit_web_frame_stop_loading(frame);
5574 abort_favicon_download(t);
5577 void
5578 setup_webkit(struct tab *t)
5580 g_object_set((GObject *)t->settings,
5581 "user-agent", t->user_agent, (char *)NULL);
5582 g_object_set((GObject *)t->settings,
5583 "enable-scripts", enable_scripts, (char *)NULL);
5584 g_object_set((GObject *)t->settings,
5585 "enable-plugins", enable_plugins, (char *)NULL);
5586 adjustfont_webkit(t, XT_FONT_SET);
5588 webkit_web_view_set_settings(t->wv, t->settings);
5591 GtkWidget *
5592 create_browser(struct tab *t)
5594 GtkWidget *w;
5595 gchar *strval;
5597 if (t == NULL)
5598 errx(1, "create_browser");
5600 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
5601 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
5602 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
5603 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
5605 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
5606 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
5607 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
5609 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
5610 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
5612 /* set defaults */
5613 t->settings = webkit_web_settings_new();
5615 if (user_agent == NULL) {
5616 g_object_get((GObject *)t->settings, "user-agent", &strval,
5617 (char *)NULL);
5618 t->user_agent = g_strdup_printf("%s %s+", strval, version);
5619 g_free(strval);
5620 } else {
5621 t->user_agent = g_strdup(user_agent);
5624 setup_webkit(t);
5626 return (w);
5629 GtkWidget *
5630 create_window(void)
5632 GtkWidget *w;
5634 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
5635 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
5636 gtk_widget_set_name(w, "xxxterm");
5637 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
5638 g_signal_connect(G_OBJECT(w), "delete_event",
5639 G_CALLBACK (gtk_main_quit), NULL);
5641 return (w);
5644 GtkWidget *
5645 create_toolbar(struct tab *t)
5647 GtkWidget *toolbar = NULL, *b, *eb1;
5649 b = gtk_hbox_new(FALSE, 0);
5650 toolbar = b;
5651 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
5653 if (fancy_bar) {
5654 /* backward button */
5655 t->backward = create_button("GoBack", GTK_STOCK_GO_BACK, 0);
5656 gtk_widget_set_sensitive(t->backward, FALSE);
5657 g_signal_connect(G_OBJECT(t->backward), "clicked",
5658 G_CALLBACK(backward_cb), t);
5659 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
5661 /* forward button */
5662 t->forward = create_button("GoForward",GTK_STOCK_GO_FORWARD, 0);
5663 gtk_widget_set_sensitive(t->forward, FALSE);
5664 g_signal_connect(G_OBJECT(t->forward), "clicked",
5665 G_CALLBACK(forward_cb), t);
5666 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
5667 FALSE, 0);
5669 /* stop button */
5670 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
5671 gtk_widget_set_sensitive(t->stop, FALSE);
5672 g_signal_connect(G_OBJECT(t->stop), "clicked",
5673 G_CALLBACK(stop_cb), t);
5674 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
5675 FALSE, 0);
5677 /* JS button */
5678 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
5679 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
5680 gtk_widget_set_sensitive(t->js_toggle, TRUE);
5681 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
5682 G_CALLBACK(js_toggle_cb), t);
5683 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
5686 t->uri_entry = gtk_entry_new();
5687 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
5688 G_CALLBACK(activate_uri_entry_cb), t);
5689 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
5690 (GCallback)entry_key_cb, t);
5691 eb1 = gtk_hbox_new(FALSE, 0);
5692 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
5693 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
5694 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
5696 /* set empty favicon */
5697 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5698 GTK_ENTRY_ICON_PRIMARY, "text-html");
5700 /* search entry */
5701 if (fancy_bar && search_string) {
5702 GtkWidget *eb2;
5703 t->search_entry = gtk_entry_new();
5704 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
5705 g_signal_connect(G_OBJECT(t->search_entry), "activate",
5706 G_CALLBACK(activate_search_entry_cb), t);
5707 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
5708 (GCallback)entry_key_cb, t);
5709 gtk_widget_set_size_request(t->search_entry, -1, -1);
5710 eb2 = gtk_hbox_new(FALSE, 0);
5711 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
5712 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
5714 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
5716 return (toolbar);
5719 void
5720 recalc_tabs(void)
5722 struct tab *t;
5724 TAILQ_FOREACH(t, &tabs, entry)
5725 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
5729 undo_close_tab_save(struct tab *t)
5731 int m, n;
5732 const gchar *uri;
5733 struct undo *u1, *u2;
5734 WebKitWebFrame *frame;
5735 GList *items;
5736 WebKitWebHistoryItem *item;
5738 frame = webkit_web_view_get_main_frame(t->wv);
5739 uri = webkit_web_frame_get_uri(frame);
5741 if (uri && !strlen(uri))
5742 return (1);
5744 u1 = g_malloc0(sizeof(struct undo));
5745 u1->uri = g_strdup(uri);
5747 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
5749 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
5750 n = webkit_web_back_forward_list_get_back_length(t->bfl);
5751 u1->back = n;
5753 /* forward history */
5754 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
5756 while (items) {
5757 item = items->data;
5758 u1->history = g_list_prepend(u1->history,
5759 webkit_web_history_item_copy(item));
5760 items = g_list_next(items);
5763 /* current item */
5764 if (m) {
5765 item = webkit_web_back_forward_list_get_current_item(t->bfl);
5766 u1->history = g_list_prepend(u1->history,
5767 webkit_web_history_item_copy(item));
5770 /* back history */
5771 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
5773 while (items) {
5774 item = items->data;
5775 u1->history = g_list_prepend(u1->history,
5776 webkit_web_history_item_copy(item));
5777 items = g_list_next(items);
5780 TAILQ_INSERT_HEAD(&undos, u1, entry);
5782 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
5783 u2 = TAILQ_LAST(&undos, undo_tailq);
5784 TAILQ_REMOVE(&undos, u2, entry);
5785 g_free(u2->uri);
5786 g_list_free(u2->history);
5787 g_free(u2);
5788 } else
5789 undo_count++;
5791 return (0);
5794 void
5795 delete_tab(struct tab *t)
5797 struct karg a;
5799 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
5801 if (t == NULL)
5802 return;
5804 TAILQ_REMOVE(&tabs, t, entry);
5806 /* halt all webkit activity */
5807 abort_favicon_download(t);
5808 webkit_web_view_stop_loading(t->wv);
5809 undo_close_tab_save(t);
5811 gtk_widget_destroy(t->vbox);
5812 g_free(t->user_agent);
5813 g_free(t);
5815 recalc_tabs();
5816 if (TAILQ_EMPTY(&tabs))
5817 create_new_tab(NULL, NULL, 1);
5820 /* recreate session */
5821 if (session_autosave) {
5822 a.s = NULL;
5823 save_tabs(t, &a);
5827 void
5828 adjustfont_webkit(struct tab *t, int adjust)
5830 if (t == NULL)
5831 errx(1, "adjustfont_webkit");
5833 if (adjust == XT_FONT_SET)
5834 t->font_size = default_font_size;
5836 t->font_size += adjust;
5837 g_object_set((GObject *)t->settings, "default-font-size",
5838 t->font_size, (char *)NULL);
5839 g_object_get((GObject *)t->settings, "default-font-size",
5840 &t->font_size, (char *)NULL);
5843 void
5844 append_tab(struct tab *t)
5846 if (t == NULL)
5847 return;
5849 TAILQ_INSERT_TAIL(&tabs, t, entry);
5850 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
5853 void
5854 create_new_tab(char *title, struct undo *u, int focus)
5856 struct tab *t, *tt;
5857 int load = 1, id, notfound;
5858 char *newuri = NULL;
5859 GtkWidget *b, *bb;
5860 WebKitWebHistoryItem *item;
5861 GList *items;
5862 GdkColor color;
5864 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
5866 if (tabless && !TAILQ_EMPTY(&tabs)) {
5867 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
5868 return;
5871 t = g_malloc0(sizeof *t);
5873 if (title == NULL) {
5874 title = "(untitled)";
5875 load = 0;
5876 } else {
5877 if (valid_url_type(title)) {
5878 newuri = guess_url_type(title);
5879 title = newuri;
5883 t->vbox = gtk_vbox_new(FALSE, 0);
5885 /* label + button for tab */
5886 b = gtk_hbox_new(FALSE, 0);
5887 t->tab_content = b;
5889 #if GTK_CHECK_VERSION(2, 20, 0)
5890 t->spinner = gtk_spinner_new ();
5891 #endif
5892 t->label = gtk_label_new(title);
5893 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
5894 gtk_widget_set_size_request(t->label, 100, 0);
5895 gtk_widget_set_size_request(b, 130, 0);
5897 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
5898 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
5899 #if GTK_CHECK_VERSION(2, 20, 0)
5900 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
5901 #endif
5903 /* toolbar */
5904 t->toolbar = create_toolbar(t);
5905 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
5907 /* browser */
5908 t->browser_win = create_browser(t);
5909 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
5911 /* oops message for user feedback */
5912 t->oops = gtk_entry_new();
5913 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
5914 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
5915 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
5916 gdk_color_parse("red", &color);
5917 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
5918 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
5920 /* command entry */
5921 t->cmd = gtk_entry_new();
5922 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
5923 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
5924 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
5926 /* xtp meaning is normal by default */
5927 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5929 /* and show it all */
5930 gtk_widget_show_all(b);
5931 gtk_widget_show_all(t->vbox);
5933 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
5934 append_tab(t);
5935 else {
5936 notfound = 1;
5937 id = gtk_notebook_get_current_page(notebook);
5938 TAILQ_FOREACH(tt, &tabs, entry) {
5939 if (tt->tab_id == id) {
5940 notfound = 0;
5941 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
5942 gtk_notebook_insert_page(notebook, t->vbox, b,
5943 id + 1);
5944 recalc_tabs();
5945 break;
5948 if (notfound)
5949 append_tab(t);
5952 #if GTK_CHECK_VERSION(2, 20, 0)
5953 /* turn spinner off if we are a new tab without uri */
5954 if (!load) {
5955 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5956 gtk_widget_hide(t->spinner);
5958 #endif
5959 /* make notebook tabs reorderable */
5960 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
5962 g_object_connect((GObject*)t->cmd,
5963 "signal::key-press-event", (GCallback)cmd_keypress_cb, t,
5964 "signal::key-release-event", (GCallback)cmd_keyrelease_cb, t,
5965 "signal::focus-out-event", (GCallback)cmd_focusout_cb, t,
5966 "signal::activate", (GCallback)cmd_activate_cb, t,
5967 (char *)NULL);
5969 /* reuse wv_button_cb to hide oops */
5970 g_object_connect((GObject*)t->oops,
5971 "signal::button_press_event", (GCallback)wv_button_cb, t,
5972 (char *)NULL);
5974 g_object_connect((GObject*)t->wv,
5975 "signal::key-press-event", (GCallback)wv_keypress_cb, t,
5976 "signal-after::key-press-event", (GCallback)wv_keypress_after_cb, t,
5977 /* "signal::hovering-over-link", (GCallback)webview_hover_cb, t, */
5978 "signal::download-requested", (GCallback)webview_download_cb, t,
5979 "signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, t,
5980 "signal::navigation-policy-decision-requested", (GCallback)webview_npd_cb, t,
5981 "signal::new-window-policy-decision-requested", (GCallback)webview_nw_cb, t,
5982 "signal::create-web-view", (GCallback)webview_cwv_cb, t,
5983 "signal::event", (GCallback)webview_event_cb, t,
5984 "signal::load-finished", (GCallback)webview_load_finished_cb, t,
5985 "signal::load-progress-changed", (GCallback)webview_progress_changed_cb, t,
5986 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5987 "signal::icon-loaded", (GCallback)notify_icon_loaded_cb, t,
5988 #endif
5989 "signal::button_press_event", (GCallback)wv_button_cb, t,
5990 (char *)NULL);
5991 g_signal_connect(t->wv,
5992 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
5994 /* hijack the unused keys as if we were the browser */
5995 g_object_connect((GObject*)t->toolbar,
5996 "signal-after::key-press-event", (GCallback)wv_keypress_after_cb, t,
5997 (char *)NULL);
5999 g_signal_connect(G_OBJECT(bb), "button_press_event",
6000 G_CALLBACK(tab_close_cb), t);
6002 /* hide stuff */
6003 hide_cmd(t);
6004 hide_oops(t);
6005 url_set_visibility();
6007 if (focus) {
6008 gtk_notebook_set_current_page(notebook, t->tab_id);
6009 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
6010 t->tab_id);
6013 if (load)
6014 webkit_web_view_load_uri(t->wv, title);
6015 else {
6016 if (show_url == 1)
6017 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6018 else
6019 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6022 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
6023 /* restore the tab's history */
6024 if (u && u->history) {
6025 items = u->history;
6026 while (items) {
6027 item = items->data;
6028 webkit_web_back_forward_list_add_item(t->bfl, item);
6029 items = g_list_next(items);
6032 item = g_list_nth_data(u->history, u->back);
6033 if (item)
6034 webkit_web_view_go_to_back_forward_item(t->wv, item);
6036 g_list_free(items);
6037 g_list_free(u->history);
6040 if (newuri)
6041 g_free(newuri);
6044 void
6045 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
6046 gpointer *udata)
6048 struct tab *t;
6049 const gchar *uri;
6051 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
6053 TAILQ_FOREACH(t, &tabs, entry) {
6054 if (t->tab_id == pn) {
6055 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
6056 "%d\n", pn);
6058 uri = webkit_web_view_get_title(t->wv);
6059 if (uri == NULL)
6060 uri = XT_NAME;
6061 gtk_window_set_title(GTK_WINDOW(main_window), uri);
6063 hide_cmd(t);
6064 hide_oops(t);
6066 if (t->focus_wv)
6067 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6072 void
6073 menuitem_response(struct tab *t)
6075 gtk_notebook_set_current_page(notebook, t->tab_id);
6078 gboolean
6079 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
6081 GtkWidget *menu, *menu_items;
6082 GdkEventButton *bevent;
6083 WebKitWebFrame *frame;
6084 const gchar *uri;
6085 struct tab *ti;
6087 if (event->type == GDK_BUTTON_PRESS) {
6088 bevent = (GdkEventButton *) event;
6089 menu = gtk_menu_new();
6091 TAILQ_FOREACH(ti, &tabs, entry) {
6092 frame = webkit_web_view_get_main_frame(ti->wv);
6093 uri = webkit_web_frame_get_uri(frame);
6094 /* XXX make sure there is something to print */
6095 /* XXX add gui pages in here to look purdy */
6096 if (uri == NULL)
6097 uri = "(untitled)";
6098 if (strlen(uri) == 0)
6099 uri = "(untitled)";
6100 menu_items = gtk_menu_item_new_with_label(uri);
6101 gtk_menu_append(GTK_MENU (menu), menu_items);
6102 gtk_widget_show(menu_items);
6104 gtk_signal_connect_object(GTK_OBJECT(menu_items),
6105 "activate", GTK_SIGNAL_FUNC(menuitem_response),
6106 (gpointer)ti);
6109 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
6110 bevent->button, bevent->time);
6112 /* unref object so it'll free itself when popped down */
6113 g_object_ref_sink(menu);
6114 g_object_unref(menu);
6116 return (TRUE /* eat event */);
6119 return (FALSE /* propagate */);
6123 icon_size_map(int icon_size)
6125 if (icon_size <= GTK_ICON_SIZE_INVALID ||
6126 icon_size > GTK_ICON_SIZE_DIALOG)
6127 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
6129 return (icon_size);
6132 GtkWidget *
6133 create_button(char *name, char *stockid, int size)
6135 GtkWidget *button, *image;
6136 gchar *rcstring;
6137 int gtk_icon_size;
6138 rcstring = g_strdup_printf(
6139 "style \"%s-style\"\n"
6140 "{\n"
6141 " GtkWidget::focus-padding = 0\n"
6142 " GtkWidget::focus-line-width = 0\n"
6143 " xthickness = 0\n"
6144 " ythickness = 0\n"
6145 "}\n"
6146 "widget \"*.%s\" style \"%s-style\"",name,name,name);
6147 gtk_rc_parse_string(rcstring);
6148 g_free(rcstring);
6149 button = gtk_button_new();
6150 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
6151 gtk_icon_size = icon_size_map(size?size:icon_size);
6153 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
6154 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
6155 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
6156 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
6157 gtk_widget_set_name(button, name);
6158 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
6159 gtk_widget_set_tooltip_text(button, name);
6161 return button;
6164 void
6165 button_set_stockid(GtkWidget *button, char *stockid)
6167 GtkWidget *image;
6168 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
6169 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
6170 gtk_button_set_image(GTK_BUTTON(button), image);
6173 void
6174 create_canvas(void)
6176 GtkWidget *vbox;
6177 GList *l = NULL;
6178 GdkPixbuf *pb;
6179 char file[PATH_MAX];
6180 int i;
6182 vbox = gtk_vbox_new(FALSE, 0);
6183 gtk_box_set_spacing(GTK_BOX(vbox), 0);
6184 notebook = GTK_NOTEBOOK(gtk_notebook_new());
6185 gtk_notebook_set_tab_hborder(notebook, 0);
6186 gtk_notebook_set_tab_vborder(notebook, 0);
6187 gtk_notebook_set_scrollable(notebook, TRUE);
6188 notebook_tab_set_visibility(notebook);
6189 gtk_notebook_set_show_border(notebook, FALSE);
6190 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
6192 abtn = gtk_button_new();
6193 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
6194 gtk_widget_set_size_request(arrow, -1, -1);
6195 gtk_container_add(GTK_CONTAINER(abtn), arrow);
6196 gtk_widget_set_size_request(abtn, -1, 20);
6197 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
6199 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
6200 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
6201 gtk_widget_set_size_request(vbox, -1, -1);
6203 g_object_connect((GObject*)notebook,
6204 "signal::switch-page", (GCallback)notebook_switchpage_cb, NULL,
6205 (char *)NULL);
6206 g_signal_connect(G_OBJECT(abtn), "button_press_event",
6207 G_CALLBACK(arrow_cb), NULL);
6209 main_window = create_window();
6210 gtk_container_add(GTK_CONTAINER(main_window), vbox);
6211 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6213 /* icons */
6214 for (i = 0; i < LENGTH(icons); i++) {
6215 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
6216 pb = gdk_pixbuf_new_from_file(file, NULL);
6217 l = g_list_append(l, pb);
6219 gtk_window_set_default_icon_list(l);
6221 gtk_widget_show_all(abtn);
6222 gtk_widget_show_all(main_window);
6225 void
6226 set_hook(void **hook, char *name)
6228 if (hook == NULL)
6229 errx(1, "set_hook");
6231 if (*hook == NULL) {
6232 *hook = dlsym(RTLD_NEXT, name);
6233 if (*hook == NULL)
6234 errx(1, "can't hook %s", name);
6238 /* override libsoup soup_cookie_equal because it doesn't look at domain */
6239 gboolean
6240 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
6242 g_return_val_if_fail(cookie1, FALSE);
6243 g_return_val_if_fail(cookie2, FALSE);
6245 return (!strcmp (cookie1->name, cookie2->name) &&
6246 !strcmp (cookie1->value, cookie2->value) &&
6247 !strcmp (cookie1->path, cookie2->path) &&
6248 !strcmp (cookie1->domain, cookie2->domain));
6251 void
6252 transfer_cookies(void)
6254 GSList *cf;
6255 SoupCookie *sc, *pc;
6257 cf = soup_cookie_jar_all_cookies(p_cookiejar);
6259 for (;cf; cf = cf->next) {
6260 pc = cf->data;
6261 sc = soup_cookie_copy(pc);
6262 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
6265 soup_cookies_free(cf);
6268 void
6269 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
6271 GSList *cf;
6272 SoupCookie *ci;
6274 print_cookie("soup_cookie_jar_delete_cookie", c);
6276 if (cookies_enabled == 0)
6277 return;
6279 if (jar == NULL || c == NULL)
6280 return;
6282 /* find and remove from persistent jar */
6283 cf = soup_cookie_jar_all_cookies(p_cookiejar);
6285 for (;cf; cf = cf->next) {
6286 ci = cf->data;
6287 if (soup_cookie_equal(ci, c)) {
6288 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
6289 break;
6293 soup_cookies_free(cf);
6295 /* delete from session jar */
6296 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
6299 void
6300 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
6302 struct domain *d;
6303 SoupCookie *c;
6304 FILE *r_cookie_f;
6306 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
6307 jar, p_cookiejar, s_cookiejar);
6309 if (cookies_enabled == 0)
6310 return;
6312 /* see if we are up and running */
6313 if (p_cookiejar == NULL) {
6314 _soup_cookie_jar_add_cookie(jar, cookie);
6315 return;
6317 /* disallow p_cookiejar adds, shouldn't happen */
6318 if (jar == p_cookiejar)
6319 return;
6321 if ((d = wl_find(cookie->domain, &c_wl)) == NULL) {
6322 blocked_cookies++;
6323 DNPRINTF(XT_D_COOKIE,
6324 "soup_cookie_jar_add_cookie: reject %s\n",
6325 cookie->domain);
6326 if (save_rejected_cookies) {
6327 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL)
6328 err(1, "reject cookie file");
6329 fseek(r_cookie_f, 0, SEEK_END);
6330 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
6331 cookie->http_only ? "#HttpOnly_" : "",
6332 cookie->domain,
6333 *cookie->domain == '.' ? "TRUE" : "FALSE",
6334 cookie->path,
6335 cookie->secure ? "TRUE" : "FALSE",
6336 cookie->expires ?
6337 (gulong)soup_date_to_time_t(cookie->expires) :
6339 cookie->name,
6340 cookie->value);
6341 fflush(r_cookie_f);
6342 fclose(r_cookie_f);
6344 if (!allow_volatile_cookies)
6345 return;
6348 if (cookie->expires == NULL && session_timeout) {
6349 soup_cookie_set_expires(cookie,
6350 soup_date_new_from_now(session_timeout));
6351 print_cookie("modified add cookie", cookie);
6354 /* see if we are white listed for persistence */
6355 if (d && d->handy) {
6356 /* add to persistent jar */
6357 c = soup_cookie_copy(cookie);
6358 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
6359 _soup_cookie_jar_add_cookie(p_cookiejar, c);
6362 /* add to session jar */
6363 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
6364 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
6367 void
6368 setup_cookies(void)
6370 char file[PATH_MAX];
6372 set_hook((void *)&_soup_cookie_jar_add_cookie,
6373 "soup_cookie_jar_add_cookie");
6374 set_hook((void *)&_soup_cookie_jar_delete_cookie,
6375 "soup_cookie_jar_delete_cookie");
6377 if (cookies_enabled == 0)
6378 return;
6381 * the following code is intricate due to overriding several libsoup
6382 * functions.
6383 * do not alter order of these operations.
6386 /* rejected cookies */
6387 if (save_rejected_cookies)
6388 snprintf(rc_fname, sizeof file, "%s/rejected.txt", work_dir);
6390 /* persistent cookies */
6391 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
6392 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
6394 /* session cookies */
6395 s_cookiejar = soup_cookie_jar_new();
6396 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
6397 cookie_policy, (void *)NULL);
6398 transfer_cookies();
6400 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
6403 void
6404 setup_proxy(char *uri)
6406 if (proxy_uri) {
6407 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
6408 soup_uri_free(proxy_uri);
6409 proxy_uri = NULL;
6411 if (http_proxy) {
6412 if (http_proxy != uri) {
6413 g_free(http_proxy);
6414 http_proxy = NULL;
6418 if (uri) {
6419 http_proxy = g_strdup(uri);
6420 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
6421 proxy_uri = soup_uri_new(http_proxy);
6422 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
6427 send_url_to_socket(char *url)
6429 int s, len, rv = -1;
6430 struct sockaddr_un sa;
6432 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
6433 warnx("send_url_to_socket: socket");
6434 return (-1);
6437 sa.sun_family = AF_UNIX;
6438 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
6439 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
6440 len = SUN_LEN(&sa);
6442 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
6443 warnx("send_url_to_socket: connect");
6444 goto done;
6447 if (send(s, url, strlen(url) + 1, 0) == -1) {
6448 warnx("send_url_to_socket: send");
6449 goto done;
6451 done:
6452 close(s);
6453 return (rv);
6456 void
6457 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
6459 int s, n;
6460 char str[XT_MAX_URL_LENGTH];
6461 socklen_t t = sizeof(struct sockaddr_un);
6462 struct sockaddr_un sa;
6463 struct passwd *p;
6464 uid_t uid;
6465 gid_t gid;
6467 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
6468 warn("socket_watcher: accept");
6469 return;
6472 if (getpeereid(s, &uid, &gid) == -1) {
6473 warn("socket_watcher: getpeereid");
6474 return;
6476 if (uid != getuid() || gid != getgid()) {
6477 warnx("socket_watcher: unauthorized user");
6478 return;
6481 p = getpwuid(uid);
6482 if (p == NULL) {
6483 warnx("socket_watcher: not a valid user");
6484 return;
6487 n = recv(s, str, sizeof(str), 0);
6488 if (n <= 0)
6489 return;
6491 create_new_tab(str, NULL, 1);
6495 is_running(void)
6497 int s, len, rv = 1;
6498 struct sockaddr_un sa;
6500 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
6501 warn("is_running: socket");
6502 return (-1);
6505 sa.sun_family = AF_UNIX;
6506 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
6507 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
6508 len = SUN_LEN(&sa);
6510 /* connect to see if there is a listener */
6511 if (connect(s, (struct sockaddr *)&sa, len) == -1)
6512 rv = 0; /* not running */
6513 else
6514 rv = 1; /* already running */
6516 close(s);
6518 return (rv);
6522 build_socket(void)
6524 int s, len;
6525 struct sockaddr_un sa;
6527 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
6528 warn("build_socket: socket");
6529 return (-1);
6532 sa.sun_family = AF_UNIX;
6533 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
6534 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
6535 len = SUN_LEN(&sa);
6537 /* connect to see if there is a listener */
6538 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
6539 /* no listener so we will */
6540 unlink(sa.sun_path);
6542 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
6543 warn("build_socket: bind");
6544 goto done;
6547 if (listen(s, 1) == -1) {
6548 warn("build_socket: listen");
6549 goto done;
6552 return (s);
6555 done:
6556 close(s);
6557 return (-1);
6560 void
6561 usage(void)
6563 fprintf(stderr,
6564 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
6565 exit(0);
6569 main(int argc, char *argv[])
6571 struct stat sb;
6572 int c, s, optn = 0, focus = 1;
6573 char conf[PATH_MAX] = { '\0' };
6574 char file[PATH_MAX];
6575 char *env_proxy = NULL;
6576 FILE *f = NULL;
6577 struct karg a;
6579 start_argv = argv;
6581 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
6583 while ((c = getopt(argc, argv, "STVf:s:tn")) != -1) {
6584 switch (c) {
6585 case 'S':
6586 show_url = 0;
6587 break;
6588 case 'T':
6589 show_tabs = 0;
6590 break;
6591 case 'V':
6592 errx(0 , "Version: %s", version);
6593 break;
6594 case 'f':
6595 strlcpy(conf, optarg, sizeof(conf));
6596 break;
6597 case 's':
6598 strlcpy(named_session, optarg, sizeof(named_session));
6599 break;
6600 case 't':
6601 tabless = 1;
6602 break;
6603 case 'n':
6604 optn = 1;
6605 break;
6606 default:
6607 usage();
6608 /* NOTREACHED */
6611 argc -= optind;
6612 argv += optind;
6614 TAILQ_INIT(&tabs);
6615 RB_INIT(&hl);
6616 RB_INIT(&js_wl);
6617 RB_INIT(&downloads);
6618 TAILQ_INIT(&mtl);
6619 TAILQ_INIT(&aliases);
6620 TAILQ_INIT(&undos);
6622 gnutls_global_init();
6624 /* generate session keys for xtp pages */
6625 generate_xtp_session_key(&dl_session_key);
6626 generate_xtp_session_key(&hl_session_key);
6627 generate_xtp_session_key(&cl_session_key);
6628 generate_xtp_session_key(&fl_session_key);
6630 /* prepare gtk */
6631 gtk_init(&argc, &argv);
6632 if (!g_thread_supported())
6633 g_thread_init(NULL);
6635 pwd = getpwuid(getuid());
6636 if (pwd == NULL)
6637 errx(1, "invalid user %d", getuid());
6639 /* set download dir */
6640 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
6642 /* set default string settings */
6643 home = g_strdup("http://www.peereboom.us");
6644 resource_dir = g_strdup("/usr/local/share/xxxterm/");
6645 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
6647 /* read config file */
6648 if (strlen(conf) == 0)
6649 snprintf(conf, sizeof conf, "%s/.%s",
6650 pwd->pw_dir, XT_CONF_FILE);
6651 config_parse(conf, 0);
6653 /* working directory */
6654 snprintf(work_dir, sizeof work_dir, "%s/%s", pwd->pw_dir, XT_DIR);
6655 if (stat(work_dir, &sb)) {
6656 if (mkdir(work_dir, S_IRWXU) == -1)
6657 err(1, "mkdir work_dir");
6658 if (stat(work_dir, &sb))
6659 err(1, "stat work_dir");
6661 if (S_ISDIR(sb.st_mode) == 0)
6662 errx(1, "%s not a dir", work_dir);
6663 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6664 warnx("fixing invalid permissions on %s", work_dir);
6665 if (chmod(work_dir, S_IRWXU) == -1)
6666 err(1, "chmod");
6669 /* icon cache dir */
6670 snprintf(cache_dir, sizeof cache_dir, "%s/%s/%s",
6671 pwd->pw_dir, XT_DIR, XT_CACHE_DIR);
6672 if (stat(cache_dir, &sb)) {
6673 if (mkdir(cache_dir, S_IRWXU) == -1)
6674 err(1, "mkdir cache_dir");
6675 if (stat(cache_dir, &sb))
6676 err(1, "stat cache_dir");
6678 if (S_ISDIR(sb.st_mode) == 0)
6679 errx(1, "%s not a dir", cache_dir);
6680 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6681 warnx("fixing invalid permissions on %s", cache_dir);
6682 if (chmod(cache_dir, S_IRWXU) == -1)
6683 err(1, "chmod");
6686 /* certs dir */
6687 snprintf(certs_dir, sizeof certs_dir, "%s/%s/%s",
6688 pwd->pw_dir, XT_DIR, XT_CERT_DIR);
6689 if (stat(certs_dir, &sb)) {
6690 if (mkdir(certs_dir, S_IRWXU) == -1)
6691 err(1, "mkdir certs_dir");
6692 if (stat(certs_dir, &sb))
6693 err(1, "stat certs_dir");
6695 if (S_ISDIR(sb.st_mode) == 0)
6696 errx(1, "%s not a dir", certs_dir);
6697 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6698 warnx("fixing invalid permissions on %s", certs_dir);
6699 if (chmod(certs_dir, S_IRWXU) == -1)
6700 err(1, "chmod");
6703 /* sessions dir */
6704 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s/%s",
6705 pwd->pw_dir, XT_DIR, XT_SESSIONS_DIR);
6706 if (stat(sessions_dir, &sb)) {
6707 if (mkdir(sessions_dir, S_IRWXU) == -1)
6708 err(1, "mkdir sessions_dir");
6709 if (stat(sessions_dir, &sb))
6710 err(1, "stat sessions_dir");
6712 if (S_ISDIR(sb.st_mode) == 0)
6713 errx(1, "%s not a dir", sessions_dir);
6714 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6715 warnx("fixing invalid permissions on %s", sessions_dir);
6716 if (chmod(sessions_dir, S_IRWXU) == -1)
6717 err(1, "chmod");
6719 /* runtime settings that can override config file */
6720 if (runtime_settings[0] != '\0')
6721 config_parse(runtime_settings, 1);
6723 /* download dir */
6724 if (!strcmp(download_dir, pwd->pw_dir))
6725 strlcat(download_dir, "/downloads", sizeof download_dir);
6726 if (stat(download_dir, &sb)) {
6727 if (mkdir(download_dir, S_IRWXU) == -1)
6728 err(1, "mkdir download_dir");
6729 if (stat(download_dir, &sb))
6730 err(1, "stat download_dir");
6732 if (S_ISDIR(sb.st_mode) == 0)
6733 errx(1, "%s not a dir", download_dir);
6734 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6735 warnx("fixing invalid permissions on %s", download_dir);
6736 if (chmod(download_dir, S_IRWXU) == -1)
6737 err(1, "chmod");
6740 /* favorites file */
6741 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
6742 if (stat(file, &sb)) {
6743 warnx("favorites file doesn't exist, creating it");
6744 if ((f = fopen(file, "w")) == NULL)
6745 err(1, "favorites");
6746 fclose(f);
6749 /* cookies */
6750 session = webkit_get_default_session();
6751 setup_cookies();
6753 /* certs */
6754 if (ssl_ca_file) {
6755 if (stat(ssl_ca_file, &sb)) {
6756 warn("no CA file: %s", ssl_ca_file);
6757 g_free(ssl_ca_file);
6758 ssl_ca_file = NULL;
6759 } else
6760 g_object_set(session,
6761 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
6762 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
6763 (void *)NULL);
6766 /* proxy */
6767 env_proxy = getenv("http_proxy");
6768 if (env_proxy)
6769 setup_proxy(env_proxy);
6770 else
6771 setup_proxy(http_proxy);
6773 /* see if there is already an xxxterm running */
6774 if (single_instance && is_running()) {
6775 optn = 1;
6776 warnx("already running");
6779 if (optn) {
6780 while (argc) {
6781 send_url_to_socket(argv[0]);
6783 argc--;
6784 argv++;
6786 exit(0);
6789 /* go graphical */
6790 create_canvas();
6792 if (save_global_history)
6793 restore_global_history();
6795 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
6796 restore_saved_tabs();
6797 else {
6798 a.s = named_session;
6799 a.i = XT_SES_DONOTHING;
6800 open_tabs(NULL, &a);
6803 while (argc) {
6804 create_new_tab(argv[0], NULL, focus);
6805 focus = 0;
6807 argc--;
6808 argv++;
6811 if (TAILQ_EMPTY(&tabs))
6812 create_new_tab(home, NULL, 1);
6814 if (enable_socket)
6815 if ((s = build_socket()) != -1)
6816 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
6818 gtk_main();
6820 gnutls_global_deinit();
6822 return (0);