/* $xxxterm$ */ /* * Copyright (c) 2010 Marco Peereboom * Copyright (c) 2010 Edd Barrett * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * TODO: * inverse color browsing * favs * - add favicon * - store in sqlite * multi letter commands * pre and post counts for commands * fav icon * autocompletion on various inputs * create privacy browsing * - encrypted local data */ #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include "linux/tree.h" #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "javascript.h" /* javascript.h borrowed from vimprobable2 under the following license: Copyright (c) 2009 Leon Winter Copyright (c) 2009 Hannes Schueller Copyright (c) 2009 Matto Fransen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ static char *version = "$xxxterm$"; /* hooked functions */ void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *); void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *, SoupCookie *); /*#define XT_DEBUG*/ #ifdef XT_DEBUG #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0) #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0) #define XT_D_MOVE 0x0001 #define XT_D_KEY 0x0002 #define XT_D_TAB 0x0004 #define XT_D_URL 0x0008 #define XT_D_CMD 0x0010 #define XT_D_NAV 0x0020 #define XT_D_DOWNLOAD 0x0040 #define XT_D_CONFIG 0x0080 #define XT_D_JS 0x0100 #define XT_D_FAVORITE 0x0200 #define XT_D_PRINTING 0x0400 #define XT_D_COOKIE 0x0800 u_int32_t swm_debug = 0 | XT_D_MOVE | XT_D_KEY | XT_D_TAB | XT_D_URL | XT_D_CMD | XT_D_NAV | XT_D_DOWNLOAD | XT_D_CONFIG | XT_D_JS | XT_D_FAVORITE | XT_D_PRINTING | XT_D_COOKIE ; #else #define DPRINTF(x...) #define DNPRINTF(n,x...) #endif #define LENGTH(x) (sizeof x / sizeof x[0]) #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \ ~(GDK_BUTTON1_MASK) & \ ~(GDK_BUTTON2_MASK) & \ ~(GDK_BUTTON3_MASK) & \ ~(GDK_BUTTON4_MASK) & \ ~(GDK_BUTTON5_MASK)) char *icons[] = { "xxxtermicon16.png", "xxxtermicon32.png", "xxxtermicon48.png", "xxxtermicon64.png", "xxxtermicon128.png" }; struct tab { TAILQ_ENTRY(tab) entry; GtkWidget *vbox; GtkWidget *tab_content; GtkWidget *label; GtkWidget *spinner; GtkWidget *uri_entry; GtkWidget *search_entry; GtkWidget *toolbar; GtkWidget *browser_win; GtkWidget *cmd; GtkToolItem *backward; GtkToolItem *forward; GtkToolItem *stop; GtkToolItem *js_toggle; guint tab_id; WebKitWebView *wv; /* adjustments for browser */ GtkScrollbar *sb_h; GtkScrollbar *sb_v; GtkAdjustment *adjust_h; GtkAdjustment *adjust_v; /* flags */ int focus_wv; int ctrl_click; gchar *hover; int xtp_meaning; /* identifies dls/favorites */ /* hints */ int hints_on; int hint_mode; #define XT_HINT_NONE (0) #define XT_HINT_NUMERICAL (1) #define XT_HINT_ALPHANUM (2) char hint_buf[128]; char hint_num[128]; /* search */ char *search_text; int search_forward; /* settings */ WebKitWebSettings *settings; int font_size; gchar *user_agent; }; TAILQ_HEAD(tab_list, tab); struct history { RB_ENTRY(history) entry; const gchar *uri; const gchar *title; }; RB_HEAD(history_list, history); struct download { RB_ENTRY(download) entry; int id; WebKitDownload *download; struct tab *tab; }; RB_HEAD(download_list, download); struct domain { RB_ENTRY(domain) entry; gchar *d; int handy; /* app use */ }; RB_HEAD(domain_list, domain); struct undo { TAILQ_ENTRY(undo) entry; gchar *uri; GList *history; int back; /* Keeps track of how many back * history items there are. */ }; TAILQ_HEAD(undo_tailq, undo); /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */ int next_download_id = 1; struct karg { int i; char *s; }; /* defines */ #define XT_NAME ("XXXTerm") #define XT_DIR (".xxxterm") #define XT_CONF_FILE ("xxxterm.conf") #define XT_FAVS_FILE ("favorites") #define XT_SAVED_TABS_FILE ("saved_tabs") #define XT_RESTART_TABS_FILE ("restart_tabs") #define XT_SOCKET_FILE ("socket") #define XT_CB_HANDLED (TRUE) #define XT_CB_PASSTHROUGH (FALSE) #define XT_DOCTYPE "" #define XT_HTML_TAG "" #define XT_DLMAN_REFRESH "10" #define XT_PAGE_STYLE "\n\n" #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */ #define XT_MAX_UNDO_CLOSE_TAB (32) /* file sizes */ #define SZ_KB ((uint64_t) 1024) #define SZ_MB (SZ_KB * SZ_KB) #define SZ_GB (SZ_KB * SZ_KB * SZ_KB) #define SZ_TB (SZ_KB * SZ_KB * SZ_KB * SZ_KB) /* * xxxterm "protocol" (xtp) * We use this for managing stuff like downloads and favorites. They * make magical HTML pages in memory which have xxxt:// links in order * to communicate with xxxterm's internals. These links take the format: * xxxt://class/session_key/action/arg * * Don't begin xtp class/actions as 0. atoi returns that on error. * * Typically we have not put addition of items in this framework, as * adding items is either done via an ex-command or via a keybinding instead. */ #define XT_XTP_STR "xxxt://" /* XTP classes (xxxt://) */ #define XT_XTP_DL 1 /* downloads */ #define XT_XTP_HL 2 /* history */ #define XT_XTP_CL 3 /* cookies */ #define XT_XTP_FL 4 /* favorites */ /* XTP download actions */ #define XT_XTP_DL_LIST 1 #define XT_XTP_DL_CANCEL 2 #define XT_XTP_DL_REMOVE 3 /* XTP history actions */ #define XT_XTP_HL_LIST 1 #define XT_XTP_HL_REMOVE 2 /* XTP cookie actions */ #define XT_XTP_CL_LIST 1 #define XT_XTP_CL_REMOVE 2 /* XTP cookie actions */ #define XT_XTP_FL_LIST 1 #define XT_XTP_FL_REMOVE 2 /* xtp tab meanings - identifies which tabs have xtp pages in */ #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */ #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */ #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */ #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */ #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */ /* actions */ #define XT_MOVE_INVALID (0) #define XT_MOVE_DOWN (1) #define XT_MOVE_UP (2) #define XT_MOVE_BOTTOM (3) #define XT_MOVE_TOP (4) #define XT_MOVE_PAGEDOWN (5) #define XT_MOVE_PAGEUP (6) #define XT_MOVE_HALFDOWN (7) #define XT_MOVE_HALFUP (8) #define XT_MOVE_LEFT (9) #define XT_MOVE_FARLEFT (10) #define XT_MOVE_RIGHT (11) #define XT_MOVE_FARRIGHT (12) #define XT_TAB_LAST (-4) #define XT_TAB_FIRST (-3) #define XT_TAB_PREV (-2) #define XT_TAB_NEXT (-1) #define XT_TAB_INVALID (0) #define XT_TAB_NEW (1) #define XT_TAB_DELETE (2) #define XT_TAB_DELQUIT (3) #define XT_TAB_OPEN (4) #define XT_TAB_UNDO_CLOSE (5) #define XT_NAV_INVALID (0) #define XT_NAV_BACK (1) #define XT_NAV_FORWARD (2) #define XT_NAV_RELOAD (3) #define XT_NAV_RELOAD_CACHE (4) #define XT_FOCUS_INVALID (0) #define XT_FOCUS_URI (1) #define XT_FOCUS_SEARCH (2) #define XT_SEARCH_INVALID (0) #define XT_SEARCH_NEXT (1) #define XT_SEARCH_PREV (2) #define XT_PASTE_CURRENT_TAB (0) #define XT_PASTE_NEW_TAB (1) #define XT_FONT_SET (0) #define XT_WL_TOGGLE (0) #define XT_WL_ENABLE (1) #define XT_WL_DISABLE (2) #define XT_CMD_OPEN (0) #define XT_CMD_OPEN_CURRENT (1) #define XT_CMD_TABNEW (2) #define XT_CMD_TABNEW_CURRENT (3) /* mime types */ struct mime_type { char *mt_type; char *mt_action; int mt_default; TAILQ_ENTRY(mime_type) entry; }; TAILQ_HEAD(mime_type_list, mime_type); /* uri aliases */ struct alias { char *a_name; char *a_uri; TAILQ_ENTRY(alias) entry; }; TAILQ_HEAD(alias_list, alias); /* settings that require restart */ int showtabs = 1; /* show tabs on notebook */ int showurl = 1; /* show url toolbar on notebook */ int tabless = 0; /* allow only 1 tab */ int enable_socket = 0; int single_instance = 0; /* only allow one xxxterm to run */ int fancy_bar = 1; /* fancy toolbar */ /* runtime settings */ int ctrl_click_focus = 0; /* ctrl click gets focus */ int cookies_enabled = 1; /* enable cookies */ int read_only_cookies = 0; /* enable to not write cookies */ int enable_scripts = 0; int enable_plugins = 0; int default_font_size = 12; unsigned refresh_interval = 10; /* download refresh interval */ int enable_cookie_whitelist = 1; int enable_js_whitelist = 1; time_t session_timeout = 3600; /* cookie session timeout */ int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY; char *ssl_ca_file = NULL; char *resource_dir = NULL; gboolean ssl_strict_certs = FALSE; int append_next = 1; /* append tab after current tab */ char *home = NULL; char *search_string = NULL; char *http_proxy = NULL; char download_dir[PATH_MAX]; char runtime_settings[PATH_MAX]; /* override of settings */ int allow_volatile_cookies = 0; struct settings; int set_download_dir(struct settings *, char *); int set_runtime_dir(struct settings *, char *); int set_cookie_policy(struct settings *, char *); int add_alias(struct settings *, char *); int add_mime_type(struct settings *, char *); int add_cookie_wl(struct settings *, char *); int add_js_wl(struct settings *, char *); char *get_cookie_policy(struct settings *); char *get_download_dir(struct settings *); char *get_runtime_dir(struct settings *); void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *); void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *); void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *); void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *); struct special { int (*set)(struct settings *, char *); char *(*get)(struct settings *); void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *); }; struct special s_cookie = { set_cookie_policy, get_cookie_policy, NULL }; struct special s_alias = { add_alias, NULL, walk_alias }; struct special s_mime = { add_mime_type, NULL, walk_mime_type }; struct special s_js = { add_js_wl, NULL, walk_js_wl }; struct special s_cookie_wl = { add_cookie_wl, NULL, walk_cookie_wl }; struct special s_download_dir = { set_download_dir, get_download_dir, NULL }; struct special s_runtime = { set_runtime_dir, get_runtime_dir, NULL }; struct settings { char *name; int type; #define XT_S_INVALID (0) #define XT_S_INT (1) #define XT_S_STR (2) uint32_t flags; #define XT_SF_RESTART (1<<0) #define XT_SF_RUNTIME (1<<1) int *ival; char **sval; struct special *s; } rs[] = { { "append_next", XT_S_INT, 0 , &append_next, NULL, NULL }, { "allow_volatile_cookies", XT_S_INT, 0 , &allow_volatile_cookies, NULL, NULL }, { "cookies_enabled", XT_S_INT, 0 , &cookies_enabled, NULL, NULL }, { "cookie_policy", XT_S_INT, 0 , NULL, NULL, &s_cookie }, { "ctrl_click_focus", XT_S_INT, 0 , &ctrl_click_focus, NULL, NULL }, { "default_font_size", XT_S_INT, 0 , &default_font_size, NULL, NULL }, { "download_dir", XT_S_STR, 0 , NULL, NULL, &s_download_dir }, { "enable_cookie_whitelist", XT_S_INT, 0 , &enable_cookie_whitelist, NULL, NULL }, { "enable_js_whitelist", XT_S_INT, 0 , &enable_js_whitelist, NULL, NULL }, { "enable_plugins", XT_S_INT, 0 , &enable_plugins, NULL, NULL }, { "enable_scripts", XT_S_INT, 0 , &enable_scripts, NULL, NULL }, { "enable_socket", XT_S_INT, XT_SF_RESTART , &enable_socket, NULL, NULL }, { "fancy_bar", XT_S_INT, XT_SF_RESTART , &fancy_bar, NULL, NULL }, { "home", XT_S_STR, 0 , NULL, &home, NULL }, { "http_proxy", XT_S_STR, 0 , NULL, &http_proxy, NULL }, { "read_only_cookies", XT_S_INT, 0 , &read_only_cookies, NULL, NULL }, { "refresh_interval", XT_S_INT, 0 , &refresh_interval, NULL, NULL }, { "resource_dir", XT_S_STR, 0 , NULL, &resource_dir, NULL }, { "runtime_settings", XT_S_STR, 0 , NULL, NULL, &s_runtime }, { "search_string", XT_S_STR, 0 , NULL, &search_string, NULL }, { "session_timeout", XT_S_INT, 0 , &session_timeout, NULL, NULL }, { "single_instance", XT_S_INT, XT_SF_RESTART , &single_instance, NULL, NULL }, { "ssl_ca_file", XT_S_STR, 0 , NULL, &ssl_ca_file, NULL }, { "ssl_strict_certs", XT_S_INT, 0 , &ssl_strict_certs, NULL, NULL }, /* runtime settings */ { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias }, { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl }, { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js }, { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime }, }; /* globals */ extern char *__progname; char **start_argv; struct passwd *pwd; GtkWidget *main_window; GtkNotebook *notebook; GtkWidget *arrow, *abtn; struct tab_list tabs; struct history_list hl; struct download_list downloads; struct domain_list c_wl; struct domain_list js_wl; struct undo_tailq undos; int undo_count; int updating_dl_tabs = 0; int updating_hl_tabs = 0; int updating_cl_tabs = 0; int updating_fl_tabs = 0; char *global_search; uint64_t blocked_cookies = 0; char * get_as_string(struct settings *s) { char *r = NULL; if (s == NULL) return (NULL); if (s->s) { if (s->s->get) r = s->s->get(s); else warnx("get_as_string skip %s\n", s->name); } else if (s->type == XT_S_INT) r = g_strdup_printf("%d", *s->ival); else if (s->type == XT_S_STR) r = g_strdup(*s->sval); else r = g_strdup_printf("INVALID TYPE"); return (r); } void settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args) { int i; char *s; for (i = 0; i < LENGTH(rs); i++) { if (rs[i].s && rs[i].s->walk) rs[i].s->walk(&rs[i], cb, cb_args); else { s = get_as_string(&rs[i]); cb(&rs[i], s, cb_args); g_free(s); } } } int set_cookie_policy(struct settings *s, char *val) { if (!strcmp(val, "no3rdparty")) cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY; else if (!strcmp(val, "accept")) cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS; else if (!strcmp(val, "reject")) cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER; else return (1); return (0); } char * get_cookie_policy(struct settings *s) { char *r = NULL; if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY) r = g_strdup("no3rdparty"); else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS) r = g_strdup("accept"); else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER) r = g_strdup("reject"); else return (NULL); return (r); } char * get_download_dir(struct settings *s) { if (download_dir[0] == '\0') return (0); return (g_strdup(download_dir)); } int set_download_dir(struct settings *s, char *val) { if (val[0] == '~') snprintf(download_dir, sizeof download_dir, "%s/%s", pwd->pw_dir, &val[1]); else strlcpy(download_dir, val, sizeof download_dir); return (0); } char * get_runtime_dir(struct settings *s) { if (runtime_settings[0] == '\0') return (0); return (g_strdup(runtime_settings)); } int set_runtime_dir(struct settings *s, char *val) { if (val[0] == '~') snprintf(runtime_settings, sizeof runtime_settings, "%s/%s", pwd->pw_dir, &val[1]); else strlcpy(runtime_settings, val, sizeof runtime_settings); return (0); } /* * Session IDs. * We use these to prevent people putting xxxt:// URLs on * websites in the wild. We generate 8 bytes and represent in hex (16 chars) */ #define XT_XTP_SES_KEY_SZ 8 #define XT_XTP_SES_KEY_HEX_FMT \ "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx" char *dl_session_key; /* downloads */ char *hl_session_key; /* history list */ char *cl_session_key; /* cookie list */ char *fl_session_key; /* favorites list */ char work_dir[PATH_MAX]; char cookie_file[PATH_MAX]; SoupURI *proxy_uri = NULL; SoupSession *session; SoupCookieJar *s_cookiejar; SoupCookieJar *p_cookiejar; struct mime_type_list mtl; struct alias_list aliases; /* protos */ void create_new_tab(char *, struct undo *, int); void delete_tab(struct tab *); void adjustfont_webkit(struct tab *, int); int run_script(struct tab *, char *); int download_rb_cmp(struct download *, struct download *); int xtp_page_hl(struct tab *t, struct karg *args); int xtp_page_dl(struct tab *t, struct karg *args); int xtp_page_cl(struct tab *t, struct karg *args); int xtp_page_fl(struct tab *t, struct karg *args); int history_rb_cmp(struct history *h1, struct history *h2) { return (strcmp(h1->uri, h2->uri)); } RB_GENERATE(history_list, history, entry, history_rb_cmp); int domain_rb_cmp(struct domain *d1, struct domain *d2) { return (strcmp(d1->d, d2->d)); } RB_GENERATE(domain_list, domain, entry, domain_rb_cmp); /* * generate a session key to secure xtp commands. * pass in a ptr to the key in question and it will * be modified in place. */ void generate_xtp_session_key(char **key) { uint8_t rand_bytes[XT_XTP_SES_KEY_SZ]; /* free old key */ if (*key) g_free(*key); /* make a new one */ arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ); *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT, rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3], rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]); DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key); } /* * validate a xtp session key. * return 1 if OK */ int validate_xtp_session_key(char *trusted, char *untrusted) { if (strcmp(trusted, untrusted) != 0) { warn("%s: xtp session key mismatch possible spoof", __func__); return (0); } return (1); } int download_rb_cmp(struct download *e1, struct download *e2) { return (e1->id < e2->id ? -1 : e1->id > e2->id); } RB_GENERATE(download_list, download, entry, download_rb_cmp); struct valid_url_types { char *type; } vut[] = { { "http://" }, { "https://" }, { "ftp://" }, { "file://" }, { XT_XTP_STR }, }; int valid_url_type(char *url) { int i; for (i = 0; i < LENGTH(vut); i++) if (!strncasecmp(vut[i].type, url, strlen(vut[i].type))) return (0); return (1); } void print_cookie(char *msg, SoupCookie *c) { if (c == NULL) return; if (msg) DNPRINTF(XT_D_COOKIE, "%s\n", msg); DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name); DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value); DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain); DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path); DNPRINTF(XT_D_COOKIE, "expires : %s\n", c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : ""); DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure); DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only); DNPRINTF(XT_D_COOKIE, "====================================\n"); } void walk_alias(struct settings *s, void (*cb)(struct settings *, char *, void *), void *cb_args) { struct alias *a; char *str; if (s == NULL || cb == NULL) errx(1, "walk_alias"); TAILQ_FOREACH(a, &aliases, entry) { str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri); cb(s, str, cb_args); g_free(str); } } char * match_alias(char *url_in) { struct alias *a; char *arg; char *url_out = NULL; arg = url_in; if (strsep(&arg, " \t") == NULL) errx(1, "match_alias: NULL URL"); TAILQ_FOREACH(a, &aliases, entry) { if (!strcmp(url_in, a->a_name)) break; } if (a != NULL) { DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n", a->a_name); if (arg != NULL) url_out = g_strdup_printf(a->a_uri, arg); else url_out = g_strdup(a->a_uri); } return (url_out); } char * guess_url_type(char *url_in) { struct stat sb; char *url_out = NULL; url_out = match_alias(url_in); if (url_out != NULL) return (url_out); /* XXX not sure about this heuristic */ if (stat(url_in, &sb) == 0) url_out = g_strdup_printf("file://%s", url_in); else url_out = g_strdup_printf("http://%s", url_in); /* guess http */ DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out); return (url_out); } int add_alias(struct settings *s, char *line) { char *l, *alias; struct alias *a; if (line == NULL) errx(1, "add_alias"); l = line; a = g_malloc(sizeof(*a)); if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) errx(1, "add_alias: incomplete alias definition"); if (strlen(alias) == 0 || strlen(l) == 0) errx(1, "add_alias: invalid alias definition"); a->a_name = g_strdup(alias); a->a_uri = g_strdup(l); DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri); TAILQ_INSERT_TAIL(&aliases, a, entry); return (0); } int add_mime_type(struct settings *s, char *line) { char *mime_type; char *l = NULL; struct mime_type *m; /* XXX this could be smarter */ if (line == NULL) errx(1, "add_mime_type"); l = line; m = g_malloc(sizeof(*m)); if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) errx(1, "add_mime_type: invalid mime_type"); if (mime_type[strlen(mime_type) - 1] == '*') { mime_type[strlen(mime_type) - 1] = '\0'; m->mt_default = 1; } else m->mt_default = 0; if (strlen(mime_type) == 0 || strlen(l) == 0) errx(1, "add_mime_type: invalid mime_type"); m->mt_type = g_strdup(mime_type); m->mt_action = g_strdup(l); DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n", m->mt_type, m->mt_action, m->mt_default); TAILQ_INSERT_TAIL(&mtl, m, entry); return (0); } struct mime_type * find_mime_type(char *mime_type) { struct mime_type *m, *def = NULL, *rv = NULL; TAILQ_FOREACH(m, &mtl, entry) { if (m->mt_default && !strncmp(mime_type, m->mt_type, strlen(m->mt_type))) def = m; if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) { rv = m; break; } } if (rv == NULL) rv = def; return (rv); } void walk_mime_type(struct settings *s, void (*cb)(struct settings *, char *, void *), void *cb_args) { struct mime_type *m; char *str; if (s == NULL || cb == NULL) errx(1, "walk_mime_type"); TAILQ_FOREACH(m, &mtl, entry) { str = g_strdup_printf("%s%s --> %s", m->mt_type, m->mt_default ? "*" : "", m->mt_action); cb(s, str, cb_args); g_free(str); } } void wl_add(char *str, struct domain_list *wl, int handy) { struct domain *d; int add_dot = 0; if (str == NULL || wl == NULL) return; if (strlen(str) < 2) return; DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str); /* treat *.moo.com the same as .moo.com */ if (str[0] == '*' && str[1] == '.') str = &str[1]; else if (str[0] == '.') str = &str[0]; else add_dot = 1; d = g_malloc(sizeof *d); if (add_dot) d->d = g_strdup_printf(".%s", str); else d->d = g_strdup(str); d->handy = handy; if (RB_INSERT(domain_list, wl, d)) goto unwind; DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d); return; unwind: if (d) { if (d->d) g_free(d->d); g_free(d); } } int add_cookie_wl(struct settings *s, char *entry) { wl_add(entry, &c_wl, 1); return (0); } void walk_cookie_wl(struct settings *s, void (*cb)(struct settings *, char *, void *), void *cb_args) { struct domain *d; if (s == NULL || cb == NULL) errx(1, "walk_cookie_wl"); RB_FOREACH_REVERSE(d, domain_list, &c_wl) cb(s, d->d, cb_args); } void walk_js_wl(struct settings *s, void (*cb)(struct settings *, char *, void *), void *cb_args) { struct domain *d; if (s == NULL || cb == NULL) errx(1, "walk_js_wl"); RB_FOREACH_REVERSE(d, domain_list, &js_wl) cb(s, d->d, cb_args); } int add_js_wl(struct settings *s, char *entry) { wl_add(entry, &js_wl, 1 /* not used */); return (0); } struct domain * wl_find(const gchar *search, struct domain_list *wl) { int i; struct domain *d = NULL, dfind; gchar *s = NULL; if (search == NULL || wl == NULL) return (NULL); if (strlen(search) < 2) return (NULL); if (search[0] != '.') s = g_strdup_printf(".%s", search); else s = g_strdup(search); for (i = strlen(s) - 1; i >= 0; i--) { if (s[i] == '.') { dfind.d = &s[i]; d = RB_FIND(domain_list, wl, &dfind); if (d) goto done; } } done: if (s) g_free(s); return (d); } struct domain * wl_find_uri(const gchar *s, struct domain_list *wl) { int i; char *ss; struct domain *r; if (s == NULL || wl == NULL) return (NULL); if (!strncmp(s, "http://", strlen("http://"))) s = &s[strlen("http://")]; else if (!strncmp(s, "https://", strlen("https://"))) s = &s[strlen("https://")]; if (strlen(s) < 2) return (NULL); for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++) /* chop string at first slash */ if (s[i] == '/' || s[i] == '\0') { ss = g_strdup(s); ss[i] = '\0'; r = wl_find(ss, wl); g_free(ss); return (r); } return (NULL); } #define WS "\n= \t" void config_parse(char *filename, int runtime) { FILE *config, *f; char *line, *cp, *var, *val; size_t len, lineno = 0; int i, handled, *p; char **s, file[PATH_MAX]; struct stat sb; DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename); if (filename == NULL) return; if (runtime && runtime_settings[0] != '\0') { snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings); if (stat(file, &sb)) { warnx("runtime file doesn't exist, creating it"); if ((f = fopen(file, "w")) == NULL) err(1, "runtime"); fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n"); fclose(f); } } else strlcpy(file, filename, sizeof file); if ((config = fopen(file, "r")) == NULL) { warn("config_parse: cannot open %s", filename); return; } for (;;) { if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL) if (feof(config) || ferror(config)) break; cp = line; cp += (long)strspn(cp, WS); if (cp[0] == '\0') { /* empty line */ free(line); continue; } if ((var = strsep(&cp, WS)) == NULL || cp == NULL) break; cp += (long)strspn(cp, WS); if ((val = strsep(&cp, "\0")) == NULL) break; DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val); /* get settings */ for (i = 0, handled = 0; i < LENGTH(rs); i++) { if (strcmp(var, rs[i].name)) continue; if (rs[i].s) { if (rs[i].s->set(&rs[i], val)) errx(1, "invalid value for %s", var); handled = 1; break; } else { switch (rs[i].type) { case XT_S_INT: p = rs[i].ival; *p = atoi(val); handled = 1; break; case XT_S_STR: s = rs[i].sval; if (s == NULL) errx(1, "invalid sval for %s", rs[i].name); if (*s) g_free(*s); *s = g_strdup(val); handled = 1; break; case XT_S_INVALID: default: errx(1, "invalid type for %s", var); } } break; } if (handled == 0) errx(1, "invalid conf file entry: %s=%s", var, val); free(line); } fclose(config); } char * js_ref_to_string(JSContextRef context, JSValueRef ref) { char *s = NULL; size_t l; JSStringRef jsref; jsref = JSValueToStringCopy(context, ref, NULL); if (jsref == NULL) return (NULL); l = JSStringGetMaximumUTF8CStringSize(jsref); s = g_malloc(l); if (s) JSStringGetUTF8CString(jsref, s, l); JSStringRelease(jsref); return (s); } void disable_hints(struct tab *t) { bzero(t->hint_buf, sizeof t->hint_buf); bzero(t->hint_num, sizeof t->hint_num); run_script(t, "vimprobable_clear()"); t->hints_on = 0; t->hint_mode = XT_HINT_NONE; } void enable_hints(struct tab *t) { bzero(t->hint_buf, sizeof t->hint_buf); run_script(t, "vimprobable_show_hints()"); t->hints_on = 1; t->hint_mode = XT_HINT_NONE; } #define XT_JS_OPEN ("open;") #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN)) #define XT_JS_FIRE ("fire;") #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE)) #define XT_JS_FOUND ("found;") #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND)) int run_script(struct tab *t, char *s) { JSGlobalContextRef ctx; WebKitWebFrame *frame; JSStringRef str; JSValueRef val, exception; char *es, buf[128]; DNPRINTF(XT_D_JS, "run_script: tab %d %s\n", t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s); frame = webkit_web_view_get_main_frame(t->wv); ctx = webkit_web_frame_get_global_context(frame); str = JSStringCreateWithUTF8CString(s); val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx), NULL, 0, &exception); JSStringRelease(str); DNPRINTF(XT_D_JS, "run_script: val %p\n", val); if (val == NULL) { es = js_ref_to_string(ctx, exception); DNPRINTF(XT_D_JS, "run_script: exception %s\n", es); g_free(es); return (1); } else { es = js_ref_to_string(ctx, val); DNPRINTF(XT_D_JS, "run_script: val %s\n", es); /* handle return value right here */ if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) { disable_hints(t); webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]); } if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) { snprintf(buf, sizeof buf, "vimprobable_fire(%s)", &es[XT_JS_FIRE_LEN]); run_script(t, buf); disable_hints(t); } if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) { if (atoi(&es[XT_JS_FOUND_LEN]) == 0) disable_hints(t); } g_free(es); } return (0); } int hint(struct tab *t, struct karg *args) { DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id); if (t->hints_on == 0) enable_hints(t); else disable_hints(t); return (0); } int quit(struct tab *t, struct karg *args) { gtk_main_quit(); return (1); } int restore_saved_tabs(void) { char file[PATH_MAX]; FILE *f; char *line = NULL; size_t linelen; int empty_saved_tabs_file = 1; int unlink_file = 0; struct stat sb; snprintf(file, sizeof file, "%s/%s/%s", pwd->pw_dir, XT_DIR, XT_RESTART_TABS_FILE); if (stat(file, &sb) == -1) snprintf(file, sizeof file, "%s/%s/%s", pwd->pw_dir, XT_DIR, XT_SAVED_TABS_FILE); else unlink_file = 1; if ((f = fopen(file, "r")) == NULL) return (empty_saved_tabs_file); while (!feof(f)) { line = fparseln(f, &linelen, NULL, NULL, 0); if (line) { create_new_tab(line, NULL, 1); empty_saved_tabs_file = 0; } free(line); line = NULL; } fclose(f); if (unlink_file) unlink(file); return (empty_saved_tabs_file); } int save_tabs(struct tab *t, struct karg *a) { char file[PATH_MAX]; FILE *f; struct tab *ti; WebKitWebFrame *frame; const gchar *uri; if (a == NULL) return (1); if (a->s == NULL) return (1); snprintf(file, sizeof file, "%s/%s/%s", pwd->pw_dir, XT_DIR, a->s); if ((f = fopen(file, "w")) == NULL) { warn("save_tabs"); return (1); } TAILQ_FOREACH(ti, &tabs, entry) { frame = webkit_web_view_get_main_frame(ti->wv); uri = webkit_web_frame_get_uri(frame); if (uri && strlen(uri) > 0) fprintf(f, "%s\n", uri); } fclose(f); return (0); } int save_tabs_and_quit(struct tab *t, struct karg *args) { struct karg a; a.s = XT_SAVED_TABS_FILE; save_tabs(t, &a); quit(t, NULL); return (1); } int yank_uri(struct tab *t, struct karg *args) { WebKitWebFrame *frame; const gchar *uri; GtkClipboard *clipboard; frame = webkit_web_view_get_main_frame(t->wv); uri = webkit_web_frame_get_uri(frame); if (!uri) return (1); clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); gtk_clipboard_set_text(clipboard, uri, -1); return (0); } struct paste_args { struct tab *t; int i; }; void paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data) { struct paste_args *pap; if (data == NULL) return; pap = (struct paste_args *)data; switch(pap->i) { case XT_PASTE_CURRENT_TAB: webkit_web_view_load_uri(pap->t->wv, text); break; case XT_PASTE_NEW_TAB: create_new_tab((char *)text, NULL, 1); break; } g_free(pap); } int paste_uri(struct tab *t, struct karg *args) { GtkClipboard *clipboard; struct paste_args *pap; pap = g_malloc(sizeof(struct paste_args)); pap->t = t; pap->i = args->i; clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); gtk_clipboard_request_text(clipboard, paste_uri_cb, pap); return (0); } char * find_domain(const char *s, int add_dot) { int i; char *r = NULL, *ss = NULL; if (s == NULL) return (NULL); if (!strncmp(s, "http://", strlen("http://"))) s = &s[strlen("http://")]; else if (!strncmp(s, "https://", strlen("https://"))) s = &s[strlen("https://")]; if (strlen(s) < 2) return (NULL); ss = g_strdup(s); for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++) /* chop string at first slash */ if (ss[i] == '/' || ss[i] == '\0') { ss[i] = '\0'; if (add_dot) r = g_strdup_printf(".%s", ss); else r = g_strdup(ss); break; } g_free(ss); return (r); } int toggle_cwl(struct tab *t, struct karg *args) { WebKitWebFrame *frame; struct domain *d; char *uri, *dom; int es; if (args == NULL) return (0); frame = webkit_web_view_get_main_frame(t->wv); uri = (char *)webkit_web_frame_get_uri(frame); dom = find_domain(uri, 1); d = wl_find(dom, &c_wl); if (d == NULL) es = 0; else es = 1; if (args->i == XT_WL_TOGGLE) es = !es; else if (args->i == XT_WL_ENABLE && es != 1) es = 1; else if (args->i == XT_WL_DISABLE && es != 0) es = 0; if (es) { /* enable cookies for domain */ wl_add(dom, &c_wl, 0); } else { /* disable cookies for domain */ RB_REMOVE(domain_list, &c_wl, d); } webkit_web_view_reload(t->wv); g_free(dom); return (0); } int toggle_js(struct tab *t, struct karg *args) { int es, i; WebKitWebFrame *frame; const gchar *s; gchar *ss; struct domain *d; if (args == NULL) return (0); g_object_get((GObject *)t->settings, "enable-scripts", &es, (char *)NULL); if (args->i == XT_WL_TOGGLE) es = !es; else if (args->i == XT_WL_ENABLE && es != 1) es = 1; else if (args->i == XT_WL_DISABLE && es != 0) es = 0; else return (0); frame = webkit_web_view_get_main_frame(t->wv); s = (gchar *)webkit_web_frame_get_uri(frame); /* this code is shared with wl_find_uri, refactor */ if (s == NULL) return (NULL); if (!strncmp(s, "http://", strlen("http://"))) s = &s[strlen("http://")]; else if (!strncmp(s, "https://", strlen("https://"))) s = &s[strlen("https://")]; if (strlen(s) < 2) return (NULL); ss = g_strdup(s); for (i = 0; i < strlen(s) + 1; i++) /* chop string at first slash */ if (ss[i] == '/' || ss[i] == '\0') { ss[i] = '\0'; if (es) { gtk_tool_button_set_stock_id( GTK_TOOL_BUTTON(t->js_toggle), GTK_STOCK_MEDIA_PLAY); wl_add(ss, &js_wl, 0 /* not used */); } else { d = wl_find(ss, &js_wl); if (d) RB_REMOVE(domain_list, &js_wl, d); gtk_tool_button_set_stock_id( GTK_TOOL_BUTTON(t->js_toggle), GTK_STOCK_MEDIA_PAUSE); } g_object_set((GObject *)t->settings, "enable-scripts", es, (char *)NULL); webkit_web_view_set_settings(t->wv, t->settings); webkit_web_view_reload(t->wv); goto done; } done: g_free(ss); return (0); } void js_toggle_cb(GtkWidget *w, struct tab *t) { struct karg a; a.i = XT_WL_TOGGLE; toggle_js(t, &a); } int toggle_src(struct tab *t, struct karg *args) { gboolean mode; if (t == NULL) return (0); mode = webkit_web_view_get_view_source_mode(t->wv); webkit_web_view_set_view_source_mode(t->wv, !mode); webkit_web_view_reload(t->wv); return (0); } int focus(struct tab *t, struct karg *args) { if (t == NULL || args == NULL) errx(1, "focus"); if (args->i == XT_FOCUS_URI) gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry)); else if (args->i == XT_FOCUS_SEARCH) gtk_widget_grab_focus(GTK_WIDGET(t->search_entry)); return (0); } int stats(struct tab *t, struct karg *args) { char *stats; if (t == NULL) errx(1, "stats"); stats = g_strdup_printf(XT_DOCTYPE "" "" "Statistics" "" "

Statistics

" "" "Cookies blocked(*) this session: %llu\n" "

* results vary based on settings" "" "", blocked_cookies ); webkit_web_view_load_string(t->wv, stats, NULL, NULL, ""); g_free(stats); return (0); } int about(struct tab *t, struct karg *args) { char *about; if (t == NULL) errx(1, "about"); about = g_strdup_printf(XT_DOCTYPE "" "" "About" "" "

About

" "" "Version: %s

" "Authors:" "

    " "
  • Marco Peereboom <marco@peereboom.us>
  • " "
  • Edd Barrett <vext01@gmail.com>
  • " "
" "Copyrights and licenses can be found on the XXXterm " "website" "" "", version ); webkit_web_view_load_string(t->wv, about, NULL, NULL, ""); g_free(about); return (0); } int help(struct tab *t, struct karg *args) { char *help; if (t == NULL) errx(1, "help"); help = XT_DOCTYPE "" "" "XXXterm" "" "" "" "XXXterm man page http://opensource.conformal.com/" "cgi-bin/man-cgi?xxxterm" "" "" ; webkit_web_view_load_string(t->wv, help, NULL, NULL, ""); return (0); } /* * update all favorite tabs apart from one. Pass NULL if * you want to update all. */ void update_favorite_tabs(struct tab *apart_from) { struct tab *t; if (!updating_fl_tabs) { updating_fl_tabs = 1; /* stop infinite recursion */ TAILQ_FOREACH(t, &tabs, entry) if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL) && (t != apart_from)) xtp_page_fl(t, NULL); updating_fl_tabs = 0; } } /* show a list of favorites (bookmarks) */ int xtp_page_fl(struct tab *t, struct karg *args) { char file[PATH_MAX]; FILE *f; char *uri = NULL, *title = NULL; size_t len, lineno = 0; int i, failed = 0; char *header, *body, *tmp, *html = NULL; DNPRINTF(XT_D_FAVORITE, "%s:", __func__); if (t == NULL) warn("%s: bad param", __func__); /* mark tab as favorite list */ t->xtp_meaning = XT_XTP_TAB_MEANING_FL; /* new session key */ if (!updating_fl_tabs) generate_xtp_session_key(&fl_session_key); /* open favorites */ snprintf(file, sizeof file, "%s/%s/%s", pwd->pw_dir, XT_DIR, XT_FAVS_FILE); if ((f = fopen(file, "r")) == NULL) { warn("favorites"); return (1); } /* header */ header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n" "Favorites\n" "%s" "" "

Favorites

\n", XT_PAGE_STYLE); /* body */ body = g_strdup_printf("
" "" "\n"); for (i = 1;;) { if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL) if (feof(f) || ferror(f)) break; if (len == 0) { free(title); title = NULL; continue; } if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) if (feof(f) || ferror(f)) { errx(0, "%s: can't parse favorites\n", __func__); failed = 1; break; } tmp = body; body = g_strdup_printf("%s" "" "" "" "\n", body, i, uri, title, XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i); g_free(tmp); free(uri); uri = NULL; free(title); title = NULL; i++; } fclose(f); /* if none, say so */ if (i == 1) { tmp = body; body = g_strdup_printf("%s" "", body); g_free(tmp); } if (uri) free(uri); if (title) free(title); /* render */ if (!failed) { html = g_strdup_printf("%s%s
#LinkRemove
%d%s" "X
" "No favorites - To add one use the 'favadd' command." "
", header, body); webkit_web_view_load_string(t->wv, html, NULL, NULL, ""); } update_favorite_tabs(t); if (header) g_free(header); if (body) g_free(body); if (html) g_free(html); return (failed); } int show_certs(struct tab *t, gnutls_session_t gs) { gnutls_datum_t cinfo; const gnutls_datum_t *cl; unsigned int len; gnutls_x509_crt_t cert; int i, rv = 0; char *tmp, *header, *body, *footer; if (gnutls_certificate_type_get(gs) != GNUTLS_CRT_X509) return (1); cl = gnutls_certificate_get_peers(gs, &len); if (len == 0) return (1); header = g_strdup("Certificate Chain"); footer = g_strdup(""); body = g_strdup(""); for (i = 0; i < len; i++) { gnutls_x509_crt_init(&cert); if (gnutls_x509_crt_import(cert, &cl[i], GNUTLS_X509_FMT_PEM < 0)) { gnutls_x509_crt_deinit(cert); rv = 1; break; } if (gnutls_x509_crt_print(cert, GNUTLS_CRT_PRINT_FULL, &cinfo)) { gnutls_x509_crt_deinit(cert); rv = 1; break; } tmp = body; body = g_strdup_printf("%s

Cert #%d

%s
", body, i, cinfo.data); g_free(tmp); gnutls_x509_crt_deinit(cert); } tmp = g_strdup_printf("%s%s%s", header, body, footer); g_free(header); g_free(body); g_free(footer); webkit_web_view_load_string(t->wv, tmp, NULL, NULL, NULL); g_free(tmp); return (rv); } int cert_cmd(struct tab *t, struct karg *args) { WebKitWebFrame *frame; SoupURI *su; char *uri, port[8]; int s = -1, on, tear_down = 0; struct addrinfo hints, *res = NULL, *ai; if (t == NULL) return (1); frame = webkit_web_view_get_main_frame(t->wv); uri = (char *)webkit_web_frame_get_uri(frame); su =soup_uri_new(uri); if (su == NULL) return (1); if (!SOUP_URI_VALID_FOR_HTTP(su)) return (1); snprintf(port, sizeof port, "%d", su->port); bzero(&hints, sizeof(struct addrinfo)); hints.ai_flags = AI_CANONNAME; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if (getaddrinfo(su->host, port, &hints, &res)) goto done; for (ai = res; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) continue; s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (s < 0) goto done; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) goto done; if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) goto done; } /* go ssl/tls */ gnutls_certificate_credentials_t xcred; gnutls_session_t gsession; int rv; tear_down = 1; gnutls_certificate_allocate_credentials(&xcred); gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file, GNUTLS_X509_FMT_PEM); gnutls_init(&gsession, GNUTLS_CLIENT); gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL); gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred); gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s); if ((rv = gnutls_handshake(gsession)) < 0) { warnx("gnutls_handshake failed %d", rv); goto done; } gnutls_credentials_type_t cred; cred = gnutls_auth_get_type(gsession); if (cred != GNUTLS_CRD_CERTIFICATE) { warnx("invalid credential type"); goto done; } show_certs(t, gsession); done: if (s != -1) close(s); if (tear_down) { gnutls_bye(gsession, GNUTLS_SHUT_RDWR); gnutls_deinit(gsession); gnutls_certificate_free_credentials(xcred); } if (su) soup_uri_free(su); if (res) freeaddrinfo(res); return (0); } int remove_cookie(int index) { int i, rv = 1; GSList *cf; SoupCookie *c; DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index); cf = soup_cookie_jar_all_cookies(s_cookiejar); for (i = 1; cf; cf = cf->next, i++) { if (i != index) continue; c = cf->data; print_cookie("remove cookie", c); soup_cookie_jar_delete_cookie(s_cookiejar, c); rv = 0; break; } soup_cookies_free(cf); return (rv); } int add_cookie(struct tab *t, struct karg *args) { char file[PATH_MAX]; FILE *f; char *line = NULL, *lt = NULL; size_t linelen; WebKitWebFrame *frame; char *dom = NULL, *uri; struct karg a; struct domain *d = NULL; GSList *cf; SoupCookie *ci, *c; if (t == NULL) return (1); if (runtime_settings[0] == '\0') return (1); snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings); if ((f = fopen(file, "r+")) == NULL) return (1); frame = webkit_web_view_get_main_frame(t->wv); uri = (char *)webkit_web_frame_get_uri(frame); dom = find_domain(uri, 1); if (uri == NULL || dom == NULL) { webkit_web_view_load_string(t->wv, "Can't add domain to Cookie white list", NULL, NULL, NULL); goto done; } lt =g_strdup_printf("cookie_wl=%s", dom); while (!feof(f)) { line = fparseln(f, &linelen, NULL, NULL, 0); if (line == NULL) continue; if (!strcmp(line, lt)) goto done; free(line); line = NULL; } fprintf(f, "%s\n", lt); a.i = XT_WL_ENABLE; toggle_cwl(t, &a); d = wl_find(dom, &c_wl); if (d) d->handy = 1; /* find and add to persistent jar */ cf = soup_cookie_jar_all_cookies(s_cookiejar); for (;cf; cf = cf->next) { ci = cf->data; if (!strcmp(dom, ci->domain) || !strcmp(&dom[1], ci->domain)) /* deal with leading . */ { c = soup_cookie_copy(ci); _soup_cookie_jar_add_cookie(p_cookiejar, c); } } soup_cookies_free(cf); done: if (line) free(line); if (dom) g_free(dom); if (lt) g_free(lt); fclose(f); return (0); } int add_js(struct tab *t, struct karg *args) { char file[PATH_MAX]; FILE *f; char *line = NULL, *lt = NULL; size_t linelen; WebKitWebFrame *frame; char *dom = NULL, *uri; struct karg a; if (t == NULL) return (1); if (runtime_settings[0] == '\0') return (1); snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings); if ((f = fopen(file, "r+")) == NULL) return (1); frame = webkit_web_view_get_main_frame(t->wv); uri = (char *)webkit_web_frame_get_uri(frame); dom = find_domain(uri, 1); if (uri == NULL || dom == NULL) { webkit_web_view_load_string(t->wv, "Can't add domain to JavaScript white list", NULL, NULL, NULL); goto done; } lt =g_strdup_printf("js_wl=%s", dom); while (!feof(f)) { line = fparseln(f, &linelen, NULL, NULL, 0); if (line == NULL) continue; if (!strcmp(line, lt)) goto done; free(line); line = NULL; } fprintf(f, "%s\n", lt); a.i = XT_WL_TOGGLE; toggle_js(t, &a); done: if (line) free(line); if (dom) g_free(dom); if (lt) g_free(lt); fclose(f); return (0); } int add_favorite(struct tab *t, struct karg *args) { char file[PATH_MAX]; FILE *f; char *line = NULL; size_t urilen, linelen; WebKitWebFrame *frame; const gchar *uri, *title; if (t == NULL) return (1); /* don't allow adding of xtp pages to favorites */ if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) { warn("%s: can't add xtp pages to favorites", __func__); return (1); } snprintf(file, sizeof file, "%s/%s/%s", pwd->pw_dir, XT_DIR, XT_FAVS_FILE); if ((f = fopen(file, "r+")) == NULL) { warn("favorites"); return (1); } title = webkit_web_view_get_title(t->wv); frame = webkit_web_view_get_main_frame(t->wv); uri = webkit_web_frame_get_uri(frame); if (title == NULL) title = uri; if (title == NULL || uri == NULL) { webkit_web_view_load_string(t->wv, "can't add page to favorites", NULL, NULL, NULL); goto done; } urilen = strlen(uri); while (!feof(f)) { line = fparseln(f, &linelen, NULL, NULL, 0); if (linelen == urilen && !strcmp(line, uri)) goto done; free(line); line = NULL; } fprintf(f, "\n%s\n%s", title, uri); done: if (line) free(line); fclose(f); update_favorite_tabs(NULL); return (0); } int navaction(struct tab *t, struct karg *args) { DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n", t->tab_id, args->i); switch (args->i) { case XT_NAV_BACK: webkit_web_view_go_back(t->wv); break; case XT_NAV_FORWARD: webkit_web_view_go_forward(t->wv); break; case XT_NAV_RELOAD: webkit_web_view_reload(t->wv); break; case XT_NAV_RELOAD_CACHE: webkit_web_view_reload_bypass_cache(t->wv); break; } return (XT_CB_PASSTHROUGH); } int move(struct tab *t, struct karg *args) { GtkAdjustment *adjust; double pi, si, pos, ps, upper, lower, max; switch (args->i) { case XT_MOVE_DOWN: case XT_MOVE_UP: case XT_MOVE_BOTTOM: case XT_MOVE_TOP: case XT_MOVE_PAGEDOWN: case XT_MOVE_PAGEUP: case XT_MOVE_HALFDOWN: case XT_MOVE_HALFUP: adjust = t->adjust_v; break; default: adjust = t->adjust_h; break; } pos = gtk_adjustment_get_value(adjust); ps = gtk_adjustment_get_page_size(adjust); upper = gtk_adjustment_get_upper(adjust); lower = gtk_adjustment_get_lower(adjust); si = gtk_adjustment_get_step_increment(adjust); pi = gtk_adjustment_get_page_increment(adjust); max = upper - ps; DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f " "max %f si %f pi %f\n", args->i, adjust == t->adjust_h ? "horizontal" : "vertical", pos, ps, upper, lower, max, si, pi); switch (args->i) { case XT_MOVE_DOWN: case XT_MOVE_RIGHT: pos += si; gtk_adjustment_set_value(adjust, MIN(pos, max)); break; case XT_MOVE_UP: case XT_MOVE_LEFT: pos -= si; gtk_adjustment_set_value(adjust, MAX(pos, lower)); break; case XT_MOVE_BOTTOM: case XT_MOVE_FARRIGHT: gtk_adjustment_set_value(adjust, max); break; case XT_MOVE_TOP: case XT_MOVE_FARLEFT: gtk_adjustment_set_value(adjust, lower); break; case XT_MOVE_PAGEDOWN: pos += pi; gtk_adjustment_set_value(adjust, MIN(pos, max)); break; case XT_MOVE_PAGEUP: pos -= pi; gtk_adjustment_set_value(adjust, MAX(pos, lower)); break; case XT_MOVE_HALFDOWN: pos += pi / 2; gtk_adjustment_set_value(adjust, MIN(pos, max)); break; case XT_MOVE_HALFUP: pos -= pi / 2; gtk_adjustment_set_value(adjust, MAX(pos, lower)); break; default: return (XT_CB_PASSTHROUGH); } DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max)); return (XT_CB_HANDLED); } char * getparams(char *cmd, char *cmp) { char *rv = NULL; if (cmd && cmp) { if (!strncmp(cmd, cmp, strlen(cmp))) { rv = cmd + strlen(cmp); while (*rv == ' ') rv++; if (strlen(rv) == 0) rv = NULL; } } return (rv); } int tabaction(struct tab *t, struct karg *args) { int rv = XT_CB_HANDLED; char *url = NULL, *newuri = NULL; struct undo *u; DNPRINTF(XT_D_TAB, "tabaction: %p %d %d\n", t, args->i, t->focus_wv); if (t == NULL) return (XT_CB_PASSTHROUGH); switch (args->i) { case XT_TAB_NEW: if ((url = getparams(args->s, "tabnew"))) create_new_tab(url, NULL, 1); else create_new_tab(NULL, NULL, 1); break; case XT_TAB_DELETE: delete_tab(t); break; case XT_TAB_DELQUIT: if (gtk_notebook_get_n_pages(notebook) > 1) delete_tab(t); else quit(t, args); break; case XT_TAB_OPEN: if ((url = getparams(args->s, "open")) || ((url = getparams(args->s, "op"))) || ((url = getparams(args->s, "o")))) ; else { rv = XT_CB_PASSTHROUGH; goto done; } if (valid_url_type(url)) { newuri = guess_url_type(url); url = newuri; } webkit_web_view_load_uri(t->wv, url); if (newuri) g_free(newuri); break; case XT_TAB_UNDO_CLOSE: if (undo_count == 0) { DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__); goto done; } else { undo_count--; u = TAILQ_FIRST(&undos); create_new_tab(u->uri, u, 1); TAILQ_REMOVE(&undos, u, entry); g_free(u->uri); /* u->history is freed in create_new_tab() */ g_free(u); } break; default: rv = XT_CB_PASSTHROUGH; goto done; } done: if (args->s) { g_free(args->s); args->s = NULL; } return (rv); } int resizetab(struct tab *t, struct karg *args) { if (t == NULL || args == NULL) errx(1, "resizetab"); DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n", t->tab_id, args->i); adjustfont_webkit(t, args->i); return (XT_CB_HANDLED); } int movetab(struct tab *t, struct karg *args) { struct tab *tt; int x; if (t == NULL || args == NULL) errx(1, "movetab"); DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n", t->tab_id, args->i); if (args->i == XT_TAB_INVALID) return (XT_CB_PASSTHROUGH); if (args->i < XT_TAB_INVALID) { /* next or previous tab */ if (TAILQ_EMPTY(&tabs)) return (XT_CB_PASSTHROUGH); switch (args->i) { case XT_TAB_NEXT: /* if at the last page, loop around to the first */ if (gtk_notebook_get_current_page(notebook) == gtk_notebook_get_n_pages(notebook) - 1) { gtk_notebook_set_current_page(notebook, 0); } else { gtk_notebook_next_page(notebook); } break; case XT_TAB_PREV: /* if at the first page, loop around to the last */ if (gtk_notebook_current_page(notebook) == 0) { gtk_notebook_set_current_page(notebook, gtk_notebook_get_n_pages(notebook) - 1); } else { gtk_notebook_prev_page(notebook); } break; case XT_TAB_FIRST: gtk_notebook_set_current_page(notebook, 0); break; case XT_TAB_LAST: gtk_notebook_set_current_page(notebook, -1); break; default: return (XT_CB_PASSTHROUGH); } return (XT_CB_HANDLED); } /* jump to tab */ x = args->i - 1; if (t->tab_id == x) { DNPRINTF(XT_D_TAB, "movetab: do nothing\n"); return (XT_CB_HANDLED); } TAILQ_FOREACH(tt, &tabs, entry) { if (tt->tab_id == x) { gtk_notebook_set_current_page(notebook, x); DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x); if (tt->focus_wv) gtk_widget_grab_focus(GTK_WIDGET(tt->wv)); } } return (XT_CB_HANDLED); } int command(struct tab *t, struct karg *args) { WebKitWebFrame *frame; char *s = NULL, *ss = NULL; GdkColor color; const gchar *uri; size_t sz; if (t == NULL || args == NULL) errx(1, "command"); switch (args->i) { case '/': s = "/"; break; case '?': s = "?"; break; case ':': s = ":"; break; case XT_CMD_OPEN: s = ":open "; break; case XT_CMD_TABNEW: s = ":tabnew "; break; case XT_CMD_OPEN_CURRENT: s = ":open "; /* FALL THROUGH */ case XT_CMD_TABNEW_CURRENT: if (!s) /* FALL THROUGH? */ s = ":tabnew "; frame = webkit_web_view_get_main_frame(t->wv); uri = webkit_web_frame_get_uri(frame); sz = sizeof(gchar) * (strlen(s) + strlen(uri)); ss = g_malloc(sz); snprintf(ss, sz, "%s%s", s, uri); s = ss; break; default: warnx("command: invalid command %c\n", args->i); return (XT_CB_PASSTHROUGH); } DNPRINTF(XT_D_CMD, "command: type %s\n", s); gtk_entry_set_text(GTK_ENTRY(t->cmd), s); gdk_color_parse("white", &color); gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color); gtk_widget_show(t->cmd); gtk_widget_grab_focus(GTK_WIDGET(t->cmd)); gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1); if (ss) g_free(ss); return (XT_CB_HANDLED); } /* * Return a new string with a download row (in html) * appended. Old string is freed. */ char * xtp_page_dl_row(char *html, struct download *dl) { WebKitDownloadStatus stat; char *status_html = NULL, *cmd_html = NULL, *new_html; gdouble progress; char cur_sz[FMT_SCALED_STRSIZE]; char tot_sz[FMT_SCALED_STRSIZE]; char *xtp_prefix; DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id); /* All actions wil take this form: * xxxt://class/seskey */ xtp_prefix = g_strdup_printf("%s%d/%s/", XT_XTP_STR, XT_XTP_DL, dl_session_key); stat = webkit_download_get_status(dl->download); switch (stat) { case WEBKIT_DOWNLOAD_STATUS_FINISHED: status_html = g_strdup_printf("Finished"); cmd_html = g_strdup_printf( "Remove", xtp_prefix, XT_XTP_DL_REMOVE, dl->id); break; case WEBKIT_DOWNLOAD_STATUS_STARTED: /* gather size info */ progress = 100 * webkit_download_get_progress(dl->download); fmt_scaled( webkit_download_get_current_size(dl->download), cur_sz); fmt_scaled( webkit_download_get_total_size(dl->download), tot_sz); status_html = g_strdup_printf("%s of %s (%.2f%%)", cur_sz, tot_sz, progress); cmd_html = g_strdup_printf("Cancel", xtp_prefix, XT_XTP_DL_CANCEL, dl->id); break; /* LLL */ case WEBKIT_DOWNLOAD_STATUS_CANCELLED: status_html = g_strdup_printf("Cancelled"); cmd_html = g_strdup_printf("Remove", xtp_prefix, XT_XTP_DL_REMOVE, dl->id); break; case WEBKIT_DOWNLOAD_STATUS_ERROR: status_html = g_strdup_printf("Error!"); cmd_html = g_strdup_printf("Remove", xtp_prefix, XT_XTP_DL_REMOVE, dl->id); break; case WEBKIT_DOWNLOAD_STATUS_CREATED: cmd_html = g_strdup_printf("Cancel", xtp_prefix, XT_XTP_DL_CANCEL, dl->id); status_html = g_strdup_printf("Starting"); break; default: warn("%s: unknown download status", __func__); }; new_html = g_strdup_printf( "%s\n%s%s" "%s\n", html, webkit_download_get_uri(dl->download), status_html, cmd_html); g_free(html); if (status_html) g_free(status_html); if (cmd_html) g_free(cmd_html); g_free(xtp_prefix); return new_html; } /* * update all download tabs apart from one. Pass NULL if * you want to update all. */ void update_download_tabs(struct tab *apart_from) { struct tab *t; if (!updating_dl_tabs) { updating_dl_tabs = 1; /* stop infinite recursion */ TAILQ_FOREACH(t, &tabs, entry) if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL) && (t != apart_from)) xtp_page_dl(t, NULL); updating_dl_tabs = 0; } } /* * update all cookie tabs apart from one. Pass NULL if * you want to update all. */ void update_cookie_tabs(struct tab *apart_from) { struct tab *t; if (!updating_cl_tabs) { updating_cl_tabs = 1; /* stop infinite recursion */ TAILQ_FOREACH(t, &tabs, entry) if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL) && (t != apart_from)) xtp_page_cl(t, NULL); updating_cl_tabs = 0; } } /* * update all history tabs apart from one. Pass NULL if * you want to update all. */ void update_history_tabs(struct tab *apart_from) { struct tab *t; if (!updating_hl_tabs) { updating_hl_tabs = 1; /* stop infinite recursion */ TAILQ_FOREACH(t, &tabs, entry) if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL) && (t != apart_from)) xtp_page_hl(t, NULL); updating_hl_tabs = 0; } } /* cookie management XTP page */ int xtp_page_cl(struct tab *t, struct karg *args) { char *header, *body, *footer, *page, *tmp; int i = 1; /* all ids start 1 */ GSList *cf; SoupCookie *c; DNPRINTF(XT_D_CMD, "%s", __func__); if (t == NULL) errx(1, "%s: null tab", __func__); /* mark this tab as cookie jar */ t->xtp_meaning = XT_XTP_TAB_MEANING_CL; /* Generate a new session key */ if (!updating_cl_tabs) generate_xtp_session_key(&cl_session_key); /* header */ header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\nCookie Jar\n" XT_PAGE_STYLE "

Cookie Jar

\n"); /* body */ body = g_strdup_printf("
" "" "" "" "" "" "" "" "\n"); cf = soup_cookie_jar_all_cookies(s_cookiejar); for (; cf; cf = cf->next) { c = cf->data; tmp = body; body = g_strdup_printf( "%s\n" "" "" "" "" "" "" "" "\n", body, c->name, c->value, c->domain, c->path, c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "", c->secure, c->http_only, XT_XTP_STR, XT_XTP_CL, cl_session_key, XT_XTP_CL_REMOVE, i ); g_free(tmp); i++; } soup_cookies_free(cf); /* small message if there are none */ if (i == 1) { tmp = body; body = g_strdup_printf("%s\n\n", body); g_free(tmp); } /* footer */ footer = g_strdup_printf("
NameValueDomainPathExpiresSecureHTTP_onlyRemove
%s%s%s%s%s%d%d" "X
No Cookies
"); page = g_strdup_printf("%s%s%s", header, body, footer); g_free(header); g_free(body); g_free(footer); webkit_web_view_load_string(t->wv, page, "text/html", "UTF-8", ""); update_cookie_tabs(t); g_free(page); return (0); } int xtp_page_hl(struct tab *t, struct karg *args) { char *header, *body, *footer, *page, *tmp; struct history *h; int i = 1; /* all ids start 1 */ DNPRINTF(XT_D_CMD, "%s", __func__); if (t == NULL) errx(1, "%s: null tab", __func__); /* mark this tab as history manager */ t->xtp_meaning = XT_XTP_TAB_MEANING_HL; /* Generate a new session key */ if (!updating_hl_tabs) generate_xtp_session_key(&hl_session_key); /* header */ header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n" "History\n" "%s" "" "

History

\n", XT_PAGE_STYLE); /* body */ body = g_strdup_printf("
" "\n"); RB_FOREACH_REVERSE(h, history_list, &hl) { tmp = body; body = g_strdup_printf( "%s\n" "" "" "\n", body, h->uri, h->uri, h->title, XT_XTP_STR, XT_XTP_HL, hl_session_key, XT_XTP_HL_REMOVE, i); g_free(tmp); i++; } /* small message if there are none */ if (i == 1) { tmp = body; body = g_strdup_printf("%s\n\n", body); g_free(tmp); } /* footer */ footer = g_strdup_printf("
URITitleRemove
%s%s" "X
No History
"); page = g_strdup_printf("%s%s%s", header, body, footer); /* * update all history manager tabs as the xtp session * key has now changed. No need to update the current tab. * Already did that above. */ update_history_tabs(t); g_free(header); g_free(body); g_free(footer); webkit_web_view_load_string(t->wv, page, "text/html", "UTF-8", ""); g_free(page); return (0); } /* * Generate a web page detailing the status of any downloads */ int xtp_page_dl(struct tab *t, struct karg *args) { struct download *dl; char *header, *body, *footer, *page, *tmp; char *ref; int n_dl = 1; DNPRINTF(XT_D_DOWNLOAD, "%s", __func__); if (t == NULL) errx(1, "%s: null tab", __func__); /* mark as a download manager tab */ t->xtp_meaning = XT_XTP_TAB_MEANING_DL; /* * Generate a new session key for next page instance. * This only happens for the top level call to xtp_page_dl() * in which case updating_dl_tabs is 0. */ if (!updating_dl_tabs) generate_xtp_session_key(&dl_session_key); /* header - with refresh so as to update */ if (refresh_interval >= 1) ref = g_strdup_printf( "\n", refresh_interval, XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST); else ref = g_strdup(""); header = g_strdup_printf( "%s\n" "Downloads\n%s%s\n", XT_DOCTYPE XT_HTML_TAG, ref, XT_PAGE_STYLE); body = g_strdup_printf("

Downloads

" "

\n\n[ Refresh Downloads ]\n" "

\n\n", XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST); RB_FOREACH_REVERSE(dl, download_list, &downloads) { body = xtp_page_dl_row(body, dl); n_dl++; } /* message if no downloads in list */ if (n_dl == 1) { tmp = body; body = g_strdup_printf("%s\n\n", body); g_free(tmp); } /* footer */ footer = g_strdup_printf("
" "FileProgressCommand
" "No downloads
"); page = g_strdup_printf("%s%s%s", header, body, footer); /* * update all download manager tabs as the xtp session * key has now changed. No need to update the current tab. * Already did that above. */ update_download_tabs(t); g_free(ref); g_free(header); g_free(body); g_free(footer); webkit_web_view_load_string(t->wv, page, "text/html", "UTF-8", ""); g_free(page); return (0); } int search(struct tab *t, struct karg *args) { gboolean d; if (t == NULL || args == NULL) errx(1, "search"); if (t->search_text == NULL) { if (global_search == NULL) return (XT_CB_PASSTHROUGH); else { t->search_text = g_strdup(global_search); webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0); webkit_web_view_set_highlight_text_matches(t->wv, TRUE); } } DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n", t->tab_id, args->i, t->search_forward, t->search_text); switch (args->i) { case XT_SEARCH_NEXT: d = t->search_forward; break; case XT_SEARCH_PREV: d = !t->search_forward; break; default: return (XT_CB_PASSTHROUGH); } webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE); return (XT_CB_HANDLED); } struct settings_args { char **body; int i; }; void print_setting(struct settings *s, char *val, void *cb_args) { char *tmp, *color; struct settings_args *sa = cb_args; if (sa == NULL) { warnx("*** %s", s->name); return; } if (s->flags & XT_SF_RUNTIME) color = "#22cc22"; else color = "#cccccc"; tmp = *sa->body; *sa->body = g_strdup_printf( "%s\n" "%s" "%s", *sa->body, color, s->name, color, val ); g_free(tmp); sa->i++; } int set(struct tab *t, struct karg *args) { char *header, *body, *footer, *page, *tmp, *pars; int i = 1; struct settings_args sa; if ((pars = getparams(args->s, "set")) == NULL) { bzero(&sa, sizeof sa); sa.body = &body; /* header */ header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\nSettings\n" "

Settings

\n"); /* body */ body = g_strdup_printf("
" "" "\n"); settings_walk(print_setting, &sa); i = sa.i; /* small message if there are none */ if (i == 1) { tmp = body; body = g_strdup_printf("%s\n\n", body); g_free(tmp); } /* footer */ footer = g_strdup_printf("
SettingValue
No settings
"); page = g_strdup_printf("%s%s%s", header, body, footer); g_free(header); g_free(body); g_free(footer); webkit_web_view_load_string(t->wv, page, "text/html", "UTF-8", ""); } else { fprintf(stderr, "pars %s\n", pars); } return (XT_CB_PASSTHROUGH); } /* * Make a hardcopy of the page */ int print_page(struct tab *t, struct karg *args) { WebKitWebFrame *frame; DNPRINTF(XT_D_PRINTING, "%s:", __func__); /* * for now we just call the GTK print box, * but later we might decide to hook in a command. */ frame = webkit_web_view_get_main_frame(t->wv); webkit_web_frame_print(frame); return (0); } int go_home(struct tab *t, struct karg *args) { char *newuri; newuri = guess_url_type((char *)home); webkit_web_view_load_uri(t->wv, newuri); free(newuri); return (0); } int restart(struct tab *t, struct karg *args) { struct karg a; a.s = XT_RESTART_TABS_FILE; save_tabs(t, &a); execvp(start_argv[0], start_argv); /* NOTREACHED */ return (0); } /* inherent to GTK not all keys will be caught at all times */ /* XXX sort key bindings */ struct key_bindings { guint mask; guint use_in_entry; guint key; int (*func)(struct tab *, struct karg *); struct karg arg; } keys[] = { { GDK_MOD1_MASK, 0, GDK_d, xtp_page_dl, {0} }, { GDK_MOD1_MASK, 0, GDK_h, xtp_page_hl, {0} }, { GDK_CONTROL_MASK, 0, GDK_p, print_page, {0}}, { 0, 0, GDK_slash, command, {.i = '/'} }, { GDK_SHIFT_MASK, 0, GDK_question, command, {.i = '?'} }, { GDK_SHIFT_MASK, 0, GDK_colon, command, {.i = ':'} }, { GDK_CONTROL_MASK, 0, GDK_q, quit, {0} }, { GDK_MOD1_MASK, 0, GDK_q, restart, {0} }, { GDK_CONTROL_MASK, 0, GDK_j, toggle_js, {.i = XT_WL_TOGGLE} }, { GDK_MOD1_MASK, 0, GDK_c, toggle_cwl, {.i = XT_WL_TOGGLE} }, { GDK_CONTROL_MASK, 0, GDK_s, toggle_src, {0} }, { 0, 0, GDK_y, yank_uri, {0} }, { 0, 0, GDK_p, paste_uri, {.i = XT_PASTE_CURRENT_TAB} }, { GDK_SHIFT_MASK, 0, GDK_P, paste_uri, {.i = XT_PASTE_NEW_TAB} }, /* search */ { 0, 0, GDK_n, search, {.i = XT_SEARCH_NEXT} }, { GDK_SHIFT_MASK, 0, GDK_N, search, {.i = XT_SEARCH_PREV} }, /* focus */ { 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} }, { 0, 0, GDK_F7, focus, {.i = XT_FOCUS_SEARCH} }, /* command aliases (handy when -S flag is used) */ { 0, 0, GDK_F9, command, {.i = XT_CMD_OPEN} }, { 0, 0, GDK_F10, command, {.i = XT_CMD_OPEN_CURRENT} }, { 0, 0, GDK_F11, command, {.i = XT_CMD_TABNEW} }, { 0, 0, GDK_F12, command, {.i = XT_CMD_TABNEW_CURRENT} }, /* hinting */ { 0, 0, GDK_f, hint, {.i = 0} }, /* navigation */ { 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} }, { GDK_MOD1_MASK, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} }, { GDK_SHIFT_MASK, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} }, { GDK_MOD1_MASK, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} }, { 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} }, { GDK_CONTROL_MASK, 0, GDK_r, navaction, {.i = XT_NAV_RELOAD} }, { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_R, navaction, {.i = XT_NAV_RELOAD_CACHE} }, { GDK_CONTROL_MASK, 0, GDK_l, navaction, {.i = XT_NAV_RELOAD} }, { GDK_MOD1_MASK, 1, GDK_f, xtp_page_fl, {0} }, /* vertical movement */ { 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} }, { 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} }, { 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} }, { 0, 0, GDK_k, move, {.i = XT_MOVE_UP} }, { GDK_SHIFT_MASK, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} }, { 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} }, { 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} }, { 0, 0, GDK_g, move, {.i = XT_MOVE_TOP} }, /* XXX make this work */ { 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} }, { GDK_CONTROL_MASK, 0, GDK_f, move, {.i = XT_MOVE_PAGEDOWN} }, { GDK_CONTROL_MASK, 0, GDK_d, move, {.i = XT_MOVE_HALFDOWN} }, { 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} }, { 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} }, { GDK_CONTROL_MASK, 0, GDK_b, move, {.i = XT_MOVE_PAGEUP} }, { GDK_CONTROL_MASK, 0, GDK_u, move, {.i = XT_MOVE_HALFUP} }, /* horizontal movement */ { 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} }, { 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} }, { 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} }, { 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} }, { GDK_SHIFT_MASK, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} }, { 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} }, /* tabs */ { GDK_CONTROL_MASK, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} }, { GDK_CONTROL_MASK, 1, GDK_w, tabaction, {.i = XT_TAB_DELETE} }, { GDK_SHIFT_MASK, 0, GDK_U, tabaction, {.i = XT_TAB_UNDO_CLOSE} }, { GDK_CONTROL_MASK, 0, GDK_1, movetab, {.i = 1} }, { GDK_CONTROL_MASK, 0, GDK_2, movetab, {.i = 2} }, { GDK_CONTROL_MASK, 0, GDK_3, movetab, {.i = 3} }, { GDK_CONTROL_MASK, 0, GDK_4, movetab, {.i = 4} }, { GDK_CONTROL_MASK, 0, GDK_5, movetab, {.i = 5} }, { GDK_CONTROL_MASK, 0, GDK_6, movetab, {.i = 6} }, { GDK_CONTROL_MASK, 0, GDK_7, movetab, {.i = 7} }, { GDK_CONTROL_MASK, 0, GDK_8, movetab, {.i = 8} }, { GDK_CONTROL_MASK, 0, GDK_9, movetab, {.i = 9} }, { GDK_CONTROL_MASK, 0, GDK_0, movetab, {.i = 10} }, { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_less, movetab, {.i = XT_TAB_FIRST} }, { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_greater, movetab, {.i = XT_TAB_LAST} }, { GDK_CONTROL_MASK, 0, GDK_Left, movetab, {.i = XT_TAB_PREV} }, { GDK_CONTROL_MASK, 0, GDK_Right, movetab, {.i = XT_TAB_NEXT} }, { GDK_CONTROL_MASK, 0, GDK_minus, resizetab, {.i = -1} }, { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_plus, resizetab, {.i = 1} }, { GDK_CONTROL_MASK, 0, GDK_equal, resizetab, {.i = 1} }, }; struct cmd { char *cmd; int params; int (*func)(struct tab *, struct karg *); struct karg arg; } cmds[] = { { "q!", 0, quit, {0} }, { "qa", 0, quit, {0} }, { "qa!", 0, quit, {0} }, { "w", 0, save_tabs, {.s = XT_SAVED_TABS_FILE} }, { "wq", 0, save_tabs_and_quit, {.s = XT_SAVED_TABS_FILE} }, { "wq!", 0, save_tabs_and_quit, {.s = XT_SAVED_TABS_FILE} }, { "help", 0, help, {0} }, { "about", 0, about, {0} }, { "stats", 0, stats, {0} }, { "version", 0, about, {0} }, { "cookies", 0, xtp_page_cl, {0} }, { "fav", 0, xtp_page_fl, {0} }, { "favadd", 0, add_favorite, {0} }, { "jsadd", 0, add_js, {0} }, { "cookieadd", 0, add_cookie, {0} }, { "cert", 0, cert_cmd, {0} }, { "dl" , 0, xtp_page_dl, {0} }, { "h" , 0, xtp_page_hl, {0} }, { "hist" , 0, xtp_page_hl, {0} }, { "history" , 0, xtp_page_hl, {0} }, { "home" , 0, go_home, {0} }, { "restart" , 0, restart, {0} }, { "1", 0, move, {.i = XT_MOVE_TOP} }, { "print", 0, print_page, {0} }, /* tabs */ { "o", 1, tabaction, {.i = XT_TAB_OPEN} }, { "op", 1, tabaction, {.i = XT_TAB_OPEN} }, { "open", 1, tabaction, {.i = XT_TAB_OPEN} }, { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} }, { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} }, { "tabe", 1, tabaction, {.i = XT_TAB_NEW} }, { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} }, { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} }, { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} }, { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} }, /* XXX add count to these commands */ { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST} }, { "tabfir", 0, movetab, {.i = XT_TAB_FIRST} }, { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST} }, { "tabr", 0, movetab, {.i = XT_TAB_FIRST} }, { "tablast", 0, movetab, {.i = XT_TAB_LAST} }, { "tabl", 0, movetab, {.i = XT_TAB_LAST} }, { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} }, { "tabp", 0, movetab, {.i = XT_TAB_PREV} }, { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} }, { "tabn", 0, movetab, {.i = XT_TAB_NEXT} }, /* settings */ { "set", 1, set, {0} }, }; gboolean tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t) { DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id); if (e->type == GDK_BUTTON_PRESS && e->button == 1) delete_tab(t); return (FALSE); } /* * cancel, remove, etc. downloads */ void xtp_handle_dl(struct tab *t, uint8_t cmd, int id) { struct download find, *d; DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id); /* some commands require a valid download id */ if (cmd != XT_XTP_DL_LIST) { /* lookup download in question */ find.id = id; d = RB_FIND(download_list, &downloads, &find); if (d == NULL) { warn("%s: no such download", __func__); return; } } /* decide what to do */ switch (cmd) { case XT_XTP_DL_CANCEL: webkit_download_cancel(d->download); break; case XT_XTP_DL_REMOVE: webkit_download_cancel(d->download); /* just incase */ g_object_unref(d->download); RB_REMOVE(download_list, &downloads, d); break; case XT_XTP_DL_LIST: /* Nothing */ break; default: warn("%s: unknown command", __func__); break; }; xtp_page_dl(t, NULL); } /* * Actions on history, only does one thing for now, but * we provide the function for future actions */ void xtp_handle_hl(struct tab *t, uint8_t cmd, int id) { struct history *h, *next; int i = 1; switch (cmd) { case XT_XTP_HL_REMOVE: /* walk backwards, as listed in reverse */ for (h = RB_MAX(history_list, &hl); h != NULL; h = next) { next = RB_PREV(history_list, &hl, h); if (id == i) { RB_REMOVE(history_list, &hl, h); g_free((gpointer) h->title); g_free((gpointer) h->uri); g_free(h); break; } i++; } break; case XT_XTP_HL_LIST: /* Nothing - just xtp_page_hl() below */ break; default: warn("%s: unknown command", __func__); break; }; xtp_page_hl(t, NULL); } /* remove a favorite */ void remove_favorite(int index) { char file[PATH_MAX], *title, *uri; char *new_favs, *tmp; FILE *f; int i; size_t len, lineno; /* open favorites */ snprintf(file, sizeof file, "%s/%s/%s", pwd->pw_dir, XT_DIR, XT_FAVS_FILE); if ((f = fopen(file, "r")) == NULL) { warn("%s: can't open favorites", __func__); return; } /* build a string which will become the new favroites file */ new_favs = g_strdup_printf("%s", ""); for (i = 1;;) { if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL) if (feof(f) || ferror(f)) break; if (len == 0) { free(title); title = NULL; continue; } if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) { if (feof(f) || ferror(f)) { warn("%s: can't parse favorites", __func__); goto clean; } } /* as long as this isn't the one we are deleting add to file */ if (i != index) { tmp = new_favs; new_favs = g_strdup_printf("%s%s\n%s\n", new_favs, title, uri); g_free(tmp); } free(uri); uri = NULL; free(title); title = NULL; i++; } fclose(f); /* write back new favorites file */ if ((f = fopen(file, "w")) == NULL) { warn("%s: can't open favorites", __func__); goto clean; } fwrite(new_favs, strlen(new_favs), 1, f); fclose(f); clean: if (uri) free(uri); if (title) free(title); g_free(new_favs); } void xtp_handle_fl(struct tab *t, uint8_t cmd, int arg) { switch (cmd) { case XT_XTP_FL_LIST: /* nothing, just the below call to xtp_page_fl() */ break; case XT_XTP_FL_REMOVE: remove_favorite(arg); break; default: warn("%s: invalid favorites command", __func__); break; }; xtp_page_fl(t, NULL); } void xtp_handle_cl(struct tab *t, uint8_t cmd, int arg) { switch (cmd) { case XT_XTP_CL_LIST: /* nothing, just xtp_page_cl() */ break; case XT_XTP_CL_REMOVE: remove_cookie(arg); break; default: warn("%s: unknown cookie xtp command", __func__); break; }; xtp_page_cl(t, NULL); } /* link an XTP class to it's session key and handler function */ struct xtp_despatch { uint8_t xtp_class; char **session_key; void (*handle_func)(struct tab *, uint8_t, int); }; struct xtp_despatch xtp_despatches[] = { { XT_XTP_DL, &dl_session_key, xtp_handle_dl }, { XT_XTP_HL, &hl_session_key, xtp_handle_hl }, { XT_XTP_FL, &fl_session_key, xtp_handle_fl }, { XT_XTP_CL, &cl_session_key, xtp_handle_cl }, { NULL, NULL, NULL } }; /* * is the url xtp protocol? (xxxt://) * if so, parse and despatch correct bahvior */ int parse_xtp_url(struct tab *t, const char *url) { char *dup = NULL, *p, *last; uint8_t n_tokens = 0; char *tokens[4] = {NULL, NULL, NULL, ""}; struct xtp_despatch *dsp, *dsp_match = NULL; uint8_t req_class; /* * tokens array meaning: * tokens[0] = class * tokens[1] = session key * tokens[2] = action * tokens[3] = optional argument */ DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url); /*xtp tab meaning is normal unless proven special */ t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL; if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR))) return 0; dup = g_strdup(url + strlen(XT_XTP_STR)); /* split out the url */ for ((p = strtok_r(dup, "/", &last)); p; (p = strtok_r(NULL, "/", &last))) { if (n_tokens < 4) tokens[n_tokens++] = p; } /* should be atleast three fields 'class/seskey/command/arg' */ if (n_tokens < 3) goto clean; dsp = xtp_despatches; req_class = atoi(tokens[0]); while (dsp->xtp_class != NULL) { if (dsp->xtp_class == req_class) { dsp_match = dsp; break; } dsp++; } /* did we find one atall? */ if (dsp_match == NULL) { warn("%s: no matching xtp despatch found", __func__); goto clean; } /* check session key and call despatch function */ if (validate_xtp_session_key(*(dsp_match->session_key), tokens[1])) { dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3])); } clean: if (dup) g_free(dup); return 1; } void activate_uri_entry_cb(GtkWidget* entry, struct tab *t) { const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry)); char *newuri = NULL; DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri); if (t == NULL) errx(1, "activate_uri_entry_cb"); if (uri == NULL) errx(1, "uri"); uri += strspn(uri, "\t "); /* if xxxt:// treat specially */ if (!parse_xtp_url(t, uri)) { if (valid_url_type((char *)uri)) { newuri = guess_url_type((char *)uri); uri = newuri; } webkit_web_view_load_uri(t->wv, uri); gtk_widget_grab_focus(GTK_WIDGET(t->wv)); } if (newuri) g_free(newuri); } void activate_search_entry_cb(GtkWidget* entry, struct tab *t) { const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry)); char *newuri = NULL; DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search); if (t == NULL) errx(1, "activate_search_entry_cb"); if (search_string == NULL) { warnx("no search_string"); return; } newuri = g_strdup_printf(search_string, search); webkit_web_view_load_uri(t->wv, newuri); gtk_widget_grab_focus(GTK_WIDGET(t->wv)); if (newuri) g_free(newuri); } void check_and_set_js(gchar *uri, struct tab *t) { struct domain *d = NULL; int es = 0; if (uri == NULL || t == NULL) return; if ((d = wl_find_uri(uri, &js_wl)) == NULL) es = 0; else es = 1; DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n", es ? "enable" : "disable", uri); g_object_set((GObject *)t->settings, "enable-scripts", es, (char *)NULL); webkit_web_view_set_settings(t->wv, t->settings); gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(t->js_toggle), es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE); } void show_ca_status(struct tab *t, const char *uri) { WebKitWebFrame *frame; WebKitWebDataSource *source; WebKitNetworkRequest *request; SoupMessage *message; GdkColor color; gchar *col_str = "white"; DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n", ssl_strict_certs, ssl_ca_file, uri); if (uri == NULL) goto done; if (ssl_ca_file == NULL) { if (g_str_has_prefix(uri, "http://")) goto done; if (g_str_has_prefix(uri, "https://")) { col_str = "red"; goto done; } return; } if (g_str_has_prefix(uri, "http://") || !g_str_has_prefix(uri, "https://")) goto done; frame = webkit_web_view_get_main_frame(t->wv); source = webkit_web_frame_get_data_source(frame); request = webkit_web_data_source_get_request(source); message = webkit_network_request_get_message(request); if (message && (soup_message_get_flags(message) & SOUP_MESSAGE_CERTIFICATE_TRUSTED)) { col_str = "green"; goto done; } else { col_str = "yellow"; goto done; } done: if (col_str) { gdk_color_parse(col_str, &color); gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color); } } void notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t) { WebKitWebFrame *frame; const gchar *set = NULL, *uri = NULL, *title = NULL; struct history *h, find; int add = 0; const gchar *s_loading; DNPRINTF(XT_D_URL, "notify_load_status_cb: %d\n", webkit_web_view_get_load_status(wview)); if (t == NULL) errx(1, "notify_load_status_cb"); switch (webkit_web_view_get_load_status(wview)) { case WEBKIT_LOAD_PROVISIONAL: #if GTK_CHECK_VERSION(2, 20, 0) gtk_widget_show(t->spinner); gtk_spinner_start(GTK_SPINNER(t->spinner)); #endif gtk_label_set_text(GTK_LABEL(t->label), "Loading"); gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE); t->focus_wv = 1; /* take focus if we are visible */ if (gtk_notebook_get_current_page(notebook) == t->tab_id) gtk_widget_grab_focus(GTK_WIDGET(t->wv)); break; case WEBKIT_LOAD_COMMITTED: frame = webkit_web_view_get_main_frame(wview); uri = webkit_web_frame_get_uri(frame); if (uri) gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri); /* check if js white listing is enabled */ if (enable_js_whitelist) { frame = webkit_web_view_get_main_frame(wview); uri = webkit_web_frame_get_uri(frame); check_and_set_js((gchar *)uri, t); } show_ca_status(t, uri); break; case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT: title = webkit_web_view_get_title(wview); frame = webkit_web_view_get_main_frame(wview); uri = webkit_web_frame_get_uri(frame); if (title) set = title; else if (uri) set = uri; else break; gtk_label_set_text(GTK_LABEL(t->label), set); gtk_window_set_title(GTK_WINDOW(main_window), set); if (uri) { if (!strncmp(uri, "http://", strlen("http://")) || !strncmp(uri, "https://", strlen("https://")) || !strncmp(uri, "file://", strlen("file://"))) add = 1; if (add == 0) break; find.uri = uri; h = RB_FIND(history_list, &hl, &find); if (h) break; h = g_malloc(sizeof *h); h->uri = g_strdup(uri); if (title) h->title = g_strdup(title); else h->title = g_strdup(uri); RB_INSERT(history_list, &hl, h); update_history_tabs(NULL); } break; case WEBKIT_LOAD_FINISHED: #if WEBKIT_CHECK_VERSION(1, 1, 18) case WEBKIT_LOAD_FAILED: #endif #if GTK_CHECK_VERSION(2, 20, 0) gtk_spinner_stop(GTK_SPINNER(t->spinner)); gtk_widget_hide(t->spinner); #endif s_loading = gtk_label_get_text(GTK_LABEL(t->label)); if (s_loading && !strcmp(s_loading, "Loading")) gtk_label_set_text(GTK_LABEL(t->label), "(untitled)"); default: gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE); break; } gtk_widget_set_sensitive(GTK_WIDGET(t->backward), webkit_web_view_can_go_back(wview)); gtk_widget_set_sensitive(GTK_WIDGET(t->forward), webkit_web_view_can_go_forward(wview)); } void webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t) { run_script(t, JS_HINTING); } void webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t) { gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry), progress == 100 ? 0 : (double)progress / 100); } int webview_nw_cb(WebKitWebView *wv, WebKitWebFrame *wf, WebKitNetworkRequest *request, WebKitWebNavigationAction *na, WebKitWebPolicyDecision *pd, struct tab *t) { char *uri; if (t == NULL) errx(1, "webview_nw_cb"); DNPRINTF(XT_D_NAV, "webview_nw_cb: %s\n", webkit_network_request_get_uri(request)); /* open in current tab */ uri = (char *)webkit_network_request_get_uri(request); webkit_web_view_load_uri(t->wv, uri); webkit_web_policy_decision_ignore(pd); return (TRUE); /* we made the decission */ } int webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf, WebKitNetworkRequest *request, WebKitWebNavigationAction *na, WebKitWebPolicyDecision *pd, struct tab *t) { char *uri; if (t == NULL) errx(1, "webview_npd_cb"); DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n", t->ctrl_click, webkit_network_request_get_uri(request)); uri = (char *)webkit_network_request_get_uri(request); if ((!parse_xtp_url(t, uri) && (t->ctrl_click))) { t->ctrl_click = 0; create_new_tab(uri, NULL, ctrl_click_focus); webkit_web_policy_decision_ignore(pd); return (TRUE); /* we made the decission */ } webkit_web_policy_decision_use(pd); return (TRUE); /* we made the decission */ } WebKitWebView * webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t) { if (t == NULL) errx(1, "webview_cwv_cb"); DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n", webkit_web_view_get_uri(wv)); return (wv); } int webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t) { /* we can not eat the event without throwing gtk off so defer it */ /* catch middle click */ if (e->type == GDK_BUTTON_RELEASE && e->button == 2) { t->ctrl_click = 1; goto done; } /* catch ctrl click */ if (e->type == GDK_BUTTON_RELEASE && CLEAN(e->state) == GDK_CONTROL_MASK) t->ctrl_click = 1; else t->ctrl_click = 0; done: return (XT_CB_PASSTHROUGH); } int run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request) { struct mime_type *m; m = find_mime_type(mime_type); if (m == NULL) return (1); switch (fork()) { case -1: err(1, "fork"); /* NOTREACHED */ case 0: break; default: return (0); } /* child */ execlp(m->mt_action, m->mt_action, webkit_network_request_get_uri(request), (void *)NULL); _exit(0); /* NOTREACHED */ return (0); } int webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame, WebKitNetworkRequest *request, char *mime_type, WebKitWebPolicyDecision *decision, struct tab *t) { if (t == NULL) errx(1, "webview_mimetype_cb"); DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n", t->tab_id, mime_type); if (run_mimehandler(t, mime_type, request) == 0) { webkit_web_policy_decision_ignore(decision); gtk_widget_grab_focus(GTK_WIDGET(t->wv)); return (TRUE); } if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) { webkit_web_policy_decision_download(decision); return (TRUE); } return (FALSE); } int webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download, struct tab *t) { const gchar *filename; char *uri = NULL; struct download *download_entry; int ret = TRUE; if (wk_download == NULL || t == NULL) errx(1, "%s: invalid pointers", __func__); filename = webkit_download_get_suggested_filename(wk_download); if (filename == NULL) return (FALSE); /* abort download */ uri = g_strdup_printf("file://%s/%s", download_dir, filename); DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s " "local %s\n", __func__, t->tab_id, filename, uri); webkit_download_set_destination_uri(wk_download, uri); if (webkit_download_get_status(wk_download) == WEBKIT_DOWNLOAD_STATUS_ERROR) { warn("%s: download failed to start", __func__); ret = FALSE; gtk_label_set_text(GTK_LABEL(t->label), "Download Failed"); } else { download_entry = g_malloc(sizeof(struct download)); download_entry->download = wk_download; download_entry->tab = t; download_entry->id = next_download_id++; RB_INSERT(download_list, &downloads, download_entry); /* get from history */ g_object_ref(wk_download); gtk_label_set_text(GTK_LABEL(t->label), "Downloading"); } if (uri) g_free(uri); /* sync other download manager tabs */ update_download_tabs(NULL); /* * NOTE: never redirect/render the current tab before this * function returns. This will cause the download to never start. */ return (ret); /* start download */ } /* XXX currently unused */ void webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t) { DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri); if (t == NULL) errx(1, "webview_hover_cb"); if (uri) { if (t->hover) { g_free(t->hover); t->hover = NULL; } t->hover = g_strdup(uri); } else if (t->hover) { g_free(t->hover); t->hover = NULL; } } int webview_keypress_cb(GtkWidget *w, GdkEventKey *e, struct tab *t) { int i; char s[2], buf[128]; const char *errstr = NULL; long long link; /* don't use w directly; use t->whatever instead */ if (t == NULL) errx(1, "webview_keypress_cb"); DNPRINTF(XT_D_KEY, "webview_keypress_cb: keyval 0x%x mask 0x%x t %p\n", e->keyval, e->state, t); if (t->hints_on) { /* ESC */ if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) { disable_hints(t); return (XT_CB_HANDLED); } /* RETURN */ if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) { link = strtonum(t->hint_num, 1, 1000, &errstr); if (errstr) { /* we have a string */ } else { /* we have a number */ snprintf(buf, sizeof buf, "vimprobable_fire(%s)", t->hint_num); run_script(t, buf); } disable_hints(t); } /* BACKSPACE */ /* XXX unfuck this */ if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) { if (t->hint_mode == XT_HINT_NUMERICAL) { /* last input was numerical */ int l; l = strlen(t->hint_num); if (l > 0) { l--; if (l == 0) { disable_hints(t); enable_hints(t); } else { t->hint_num[l] = '\0'; goto num; } } } else if (t->hint_mode == XT_HINT_ALPHANUM) { /* last input was alphanumerical */ int l; l = strlen(t->hint_buf); if (l > 0) { l--; if (l == 0) { disable_hints(t); enable_hints(t); } else { t->hint_buf[l] = '\0'; goto anum; } } } else { /* bogus */ disable_hints(t); } } /* numerical input */ if (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) { snprintf(s, sizeof s, "%c", e->keyval); strlcat(t->hint_num, s, sizeof t->hint_num); DNPRINTF(XT_D_JS, "webview_keypress_cb: numerical %s\n", t->hint_num); num: link = strtonum(t->hint_num, 1, 1000, &errstr); if (errstr) { DNPRINTF(XT_D_JS, "webview_keypress_cb: invalid link number\n"); disable_hints(t); } else { snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)", t->hint_num); t->hint_mode = XT_HINT_NUMERICAL; run_script(t, buf); } /* empty the counter buffer */ bzero(t->hint_buf, sizeof t->hint_buf); return (XT_CB_HANDLED); } /* alphanumerical input */ if ( (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) || (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) || (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) { snprintf(s, sizeof s, "%c", e->keyval); strlcat(t->hint_buf, s, sizeof t->hint_buf); DNPRINTF(XT_D_JS, "webview_keypress_cb: alphanumerical %s\n", t->hint_buf); anum: snprintf(buf, sizeof buf, "vimprobable_cleanup()"); run_script(t, buf); snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')", t->hint_buf); t->hint_mode = XT_HINT_ALPHANUM; run_script(t, buf); /* empty the counter buffer */ bzero(t->hint_num, sizeof t->hint_num); return (XT_CB_HANDLED); } return (XT_CB_HANDLED); } for (i = 0; i < LENGTH(keys); i++) if (e->keyval == keys[i].key && CLEAN(e->state) == keys[i].mask) { keys[i].func(t, &keys[i].arg); return (XT_CB_HANDLED); } return (XT_CB_PASSTHROUGH); } int cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t) { const gchar *c = gtk_entry_get_text(w); GdkColor color; int forward = TRUE; DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n", e->keyval, e->state, t); if (t == NULL) errx(1, "cmd_keyrelease_cb"); DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n", e->keyval, e->state, t); if (c[0] == ':') goto done; if (strlen(c) == 1) { webkit_web_view_unmark_text_matches(t->wv); goto done; } if (c[0] == '/') forward = TRUE; else if (c[0] == '?') forward = FALSE; else goto done; /* search */ if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) == FALSE) { /* not found, mark red */ gdk_color_parse("red", &color); gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color); /* unmark and remove selection */ webkit_web_view_unmark_text_matches(t->wv); /* my kingdom for a way to unselect text in webview */ } else { /* found, highlight all */ webkit_web_view_unmark_text_matches(t->wv); webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0); webkit_web_view_set_highlight_text_matches(t->wv, TRUE); gdk_color_parse("white", &color); gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color); } done: return (XT_CB_PASSTHROUGH); } #if 0 int cmd_complete(struct tab *t, char *s) { int i; GtkEntry *w = GTK_ENTRY(t->cmd); DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", s); for (i = 0; i < LENGTH(cmds); i++) { if (!strncasecmp(cmds[i].cmd, s, strlen(s))) { fprintf(stderr, "match %s %d\n", cmds[i].cmd, strcasecmp(cmds[i].cmd, s)); #if 0 gtk_entry_set_text(w, ":"); gtk_entry_append_text(w, cmds[i].cmd); gtk_editable_set_position(GTK_EDITABLE(w), -1); #endif } } return (0); } #endif int entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t) { int i; if (t == NULL) errx(1, "entry_key_cb"); DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n", e->keyval, e->state, t); if (e->keyval == GDK_Escape) gtk_widget_grab_focus(GTK_WIDGET(t->wv)); for (i = 0; i < LENGTH(keys); i++) if (e->keyval == keys[i].key && CLEAN(e->state) == keys[i].mask && keys[i].use_in_entry) { keys[i].func(t, &keys[i].arg); return (XT_CB_HANDLED); } return (XT_CB_PASSTHROUGH); } int cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t) { int rv = XT_CB_HANDLED; const gchar *c = gtk_entry_get_text(w); if (t == NULL) errx(1, "cmd_keypress_cb"); DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n", e->keyval, e->state, t); /* sanity */ if (c == NULL) e->keyval = GDK_Escape; else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?')) e->keyval = GDK_Escape; switch (e->keyval) { #if 0 case GDK_Tab: if (c[0] != ':') goto done; if (strchr (c, ' ')) { /* par completion */ fprintf(stderr, "completeme par\n"); goto done; } cmd_complete(t, (char *)&c[1]); goto done; #endif case GDK_BackSpace: if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?"))) break; /* FALLTHROUGH */ case GDK_Escape: gtk_widget_hide(t->cmd); gtk_widget_grab_focus(GTK_WIDGET(t->wv)); /* cancel search */ if (c[0] == '/' || c[0] == '?') webkit_web_view_unmark_text_matches(t->wv); goto done; } rv = XT_CB_PASSTHROUGH; done: return (rv); } int cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t) { if (t == NULL) errx(1, "cmd_focusout_cb"); DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d focus_wv %d\n", t->tab_id, t->focus_wv); /* abort command when losing focus */ gtk_widget_hide(t->cmd); if (t->focus_wv) gtk_widget_grab_focus(GTK_WIDGET(t->wv)); else gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry)); return (XT_CB_PASSTHROUGH); } void cmd_activate_cb(GtkEntry *entry, struct tab *t) { int i; char *s; const gchar *c = gtk_entry_get_text(entry); if (t == NULL) errx(1, "cmd_activate_cb"); DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c); /* sanity */ if (c == NULL) goto done; else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?')) goto done; if (strlen(c) < 2) goto done; s = (char *)&c[1]; if (c[0] == '/' || c[0] == '?') { if (t->search_text) { g_free(t->search_text); t->search_text = NULL; } t->search_text = g_strdup(s); if (global_search) g_free(global_search); global_search = g_strdup(s); t->search_forward = c[0] == '/'; goto done; } for (i = 0; i < LENGTH(cmds); i++) if (cmds[i].params) { if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) { cmds[i].arg.s = g_strdup(s); goto execute_command; } } else { if (!strcmp(s, cmds[i].cmd)) goto execute_command; } done: gtk_widget_hide(t->cmd); return; execute_command: gtk_widget_hide(t->cmd); cmds[i].func(t, &cmds[i].arg); } void backward_cb(GtkWidget *w, struct tab *t) { if (t == NULL) errx(1, "backward_cb"); DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id); webkit_web_view_go_back(t->wv); } void forward_cb(GtkWidget *w, struct tab *t) { if (t == NULL) errx(1, "forward_cb"); DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id); webkit_web_view_go_forward(t->wv); } void stop_cb(GtkWidget *w, struct tab *t) { WebKitWebFrame *frame; if (t == NULL) errx(1, "stop_cb"); DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id); frame = webkit_web_view_get_main_frame(t->wv); if (frame == NULL) { warnx("stop_cb: no frame"); return; } webkit_web_frame_stop_loading(frame); } void setup_webkit(struct tab *t) { g_object_set((GObject *)t->settings, "user-agent", t->user_agent, (char *)NULL); g_object_set((GObject *)t->settings, "enable-scripts", enable_scripts, (char *)NULL); g_object_set((GObject *)t->settings, "enable-plugins", enable_plugins, (char *)NULL); adjustfont_webkit(t, XT_FONT_SET); webkit_web_view_set_settings(t->wv, t->settings); } GtkWidget * create_browser(struct tab *t) { GtkWidget *w; gchar *strval; if (t == NULL) errx(1, "create_browser"); t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL)); t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL)); t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h)); t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v)); w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new()); gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv)); /* set defaults */ t->settings = webkit_web_settings_new(); g_object_get((GObject *)t->settings, "user-agent", &strval, (char *)NULL); t->user_agent = g_strdup_printf("%s %s+", strval, version); g_free(strval); setup_webkit(t); return (w); } GtkWidget * create_window(void) { GtkWidget *w; w = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_default_size(GTK_WINDOW(w), 1024, 768); gtk_widget_set_name(w, "xxxterm"); gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm"); g_signal_connect(G_OBJECT(w), "delete_event", G_CALLBACK (gtk_main_quit), NULL); return (w); } GtkWidget * create_toolbar(struct tab *t) { GtkWidget *toolbar = NULL, *b; b = gtk_hbox_new(FALSE, 0); toolbar = b; if (fancy_bar) { /* backward button */ t->backward = gtk_tool_button_new_from_stock(GTK_STOCK_GO_BACK); gtk_widget_set_sensitive(GTK_WIDGET(t->backward), FALSE); g_signal_connect(G_OBJECT(t->backward), "clicked", G_CALLBACK(backward_cb), t); gtk_widget_set_size_request(GTK_WIDGET(t->backward), -1, -1); gtk_box_pack_start(GTK_BOX(b), GTK_WIDGET(t->backward), FALSE, FALSE, 0); /* forward button */ t->forward = gtk_tool_button_new_from_stock(GTK_STOCK_GO_FORWARD); gtk_widget_set_sensitive(GTK_WIDGET(t->forward), FALSE); g_signal_connect(G_OBJECT(t->forward), "clicked", G_CALLBACK(forward_cb), t); gtk_widget_set_size_request(GTK_WIDGET(t->forward), -1, -1); gtk_box_pack_start(GTK_BOX(b), GTK_WIDGET(t->forward), FALSE, FALSE, 0); /* stop button */ t->stop = gtk_tool_button_new_from_stock(GTK_STOCK_STOP); gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE); g_signal_connect(G_OBJECT(t->stop), "clicked", G_CALLBACK(stop_cb), t); gtk_widget_set_size_request(GTK_WIDGET(t->stop), -1, -1); gtk_box_pack_start(GTK_BOX(b), GTK_WIDGET(t->stop), FALSE, FALSE, 0); /* JS button */ t->js_toggle = gtk_tool_button_new_from_stock(enable_scripts ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE); gtk_widget_set_sensitive(GTK_WIDGET(t->js_toggle), TRUE); g_signal_connect(G_OBJECT(t->js_toggle), "clicked", G_CALLBACK(js_toggle_cb), t); gtk_widget_set_size_request(GTK_WIDGET(t->js_toggle), -1, -1); gtk_box_pack_start(GTK_BOX(b), GTK_WIDGET(t->js_toggle), FALSE, FALSE, 0); } t->uri_entry = gtk_entry_new(); g_signal_connect(G_OBJECT(t->uri_entry), "activate", G_CALLBACK(activate_uri_entry_cb), t); g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event", (GCallback)entry_key_cb, t); gtk_box_pack_start(GTK_BOX(b), t->uri_entry, TRUE, TRUE, 0); gtk_widget_set_size_request(GTK_WIDGET(b), -1, 32); /* search entry */ if (fancy_bar && search_string) { t->search_entry = gtk_entry_new(); gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30); g_signal_connect(G_OBJECT(t->search_entry), "activate", G_CALLBACK(activate_search_entry_cb), t); g_signal_connect(G_OBJECT(t->search_entry), "key-press-event", (GCallback)entry_key_cb, t); gtk_widget_set_size_request(t->search_entry, -1, -1); gtk_box_pack_start(GTK_BOX(b), t->search_entry, FALSE, FALSE, 0); } return (toolbar); } void recalc_tabs(void) { struct tab *t; TAILQ_FOREACH(t, &tabs, entry) t->tab_id = gtk_notebook_page_num(notebook, t->vbox); } int undo_close_tab_save(struct tab *t) { int m, n; const gchar *uri; struct undo *u1, *u2; WebKitWebFrame *frame; WebKitWebBackForwardList *bfl; GList *items; WebKitWebHistoryItem *item; frame = webkit_web_view_get_main_frame(t->wv); uri = webkit_web_frame_get_uri(frame); if (uri && !strlen(uri)) return (1); u1 = g_malloc0(sizeof(struct undo)); u1->uri = g_strdup(uri); bfl = webkit_web_view_get_back_forward_list(t->wv); m = webkit_web_back_forward_list_get_forward_length(bfl); n = webkit_web_back_forward_list_get_back_length(bfl); u1->back = n; /* forward history */ items = webkit_web_back_forward_list_get_forward_list_with_limit(bfl, m); while (items) { item = items->data; u1->history = g_list_prepend(u1->history, webkit_web_history_item_copy(item)); items = g_list_next(items); } /* current item */ if (m) { item = webkit_web_back_forward_list_get_current_item(bfl); u1->history = g_list_prepend(u1->history, webkit_web_history_item_copy(item)); } /* back history */ items = webkit_web_back_forward_list_get_back_list_with_limit(bfl, n); while (items) { item = items->data; u1->history = g_list_prepend(u1->history, webkit_web_history_item_copy(item)); items = g_list_next(items); } TAILQ_INSERT_HEAD(&undos, u1, entry); if (undo_count > XT_MAX_UNDO_CLOSE_TAB) { u2 = TAILQ_LAST(&undos, undo_tailq); TAILQ_REMOVE(&undos, u2, entry); g_free(u2->uri); g_list_free(u2->history); g_free(u2); } else undo_count++; return (0); } void delete_tab(struct tab *t) { DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t); if (t == NULL) return; /* halt all webkit activity */ webkit_web_view_stop_loading(t->wv); undo_close_tab_save(t); TAILQ_REMOVE(&tabs, t, entry); recalc_tabs(); gtk_widget_destroy(t->vbox); g_free(t->user_agent); g_free(t); if (TAILQ_EMPTY(&tabs)) create_new_tab(NULL, NULL, 1); } void adjustfont_webkit(struct tab *t, int adjust) { if (t == NULL) errx(1, "adjustfont_webkit"); if (adjust == XT_FONT_SET) t->font_size = default_font_size; t->font_size += adjust; g_object_set((GObject *)t->settings, "default-font-size", t->font_size, (char *)NULL); g_object_get((GObject *)t->settings, "default-font-size", &t->font_size, (char *)NULL); } void append_tab(struct tab *t) { if (t == NULL) return; TAILQ_INSERT_TAIL(&tabs, t, entry); t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content); } void create_new_tab(char *title, struct undo *u, int focus) { struct tab *t, *tt; int load = 1, id, notfound; char *newuri = NULL; GtkWidget *image, *b, *bb; WebKitWebHistoryItem *item; GList *items; WebKitWebBackForwardList *bfl; DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus); if (tabless && !TAILQ_EMPTY(&tabs)) { DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n"); return; } t = g_malloc0(sizeof *t); if (title == NULL) { title = "(untitled)"; load = 0; } else { if (valid_url_type(title)) { newuri = guess_url_type(title); title = newuri; } } t->vbox = gtk_vbox_new(FALSE, 0); /* label + button for tab */ gtk_rc_parse_string( "style \"my-button-style\"\n" "{\n" " GtkWidget::focus-padding = 0\n" " GtkWidget::focus-line-width = 0\n" " xthickness = 0\n" " ythickness = 0\n" "}\n" "widget \"*.my-close-button\" style \"my-button-style\""); b = gtk_hbox_new(FALSE, 0); t->tab_content = b; #if GTK_CHECK_VERSION(2, 20, 0) t->spinner = gtk_spinner_new (); #endif t->label = gtk_label_new(title); bb = gtk_button_new(); gtk_button_set_focus_on_click(GTK_BUTTON(bb), FALSE); image = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU); gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1); gtk_container_add(GTK_CONTAINER(bb), GTK_WIDGET(image)); gtk_widget_set_name(bb, "my-close-button"); gtk_widget_set_size_request(t->label, 100, 0); gtk_widget_set_size_request(b, 130, 0); gtk_notebook_set_homogeneous_tabs(notebook, TRUE); gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0); #if GTK_CHECK_VERSION(2, 20, 0) gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0); #endif /* toolbar */ t->toolbar = create_toolbar(t); gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0); /* browser */ t->browser_win = create_browser(t); gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0); /* command entry */ t->cmd = gtk_entry_new(); gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL); gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE); gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0); /* xtp meaning is normal by default */ t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL; /* and show it all */ gtk_widget_show_all(b); gtk_widget_show_all(t->vbox); if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0) append_tab(t); else { notfound = 1; id = gtk_notebook_get_current_page(notebook); TAILQ_FOREACH(tt, &tabs, entry) { if (tt->tab_id == id) { notfound = 0; TAILQ_INSERT_AFTER(&tabs, tt, t, entry); gtk_notebook_insert_page(notebook, t->vbox, b, id + 1); recalc_tabs(); break; } } if (notfound) append_tab(t); } #if GTK_CHECK_VERSION(2, 20, 0) /* turn spinner off if we are a new tab without uri */ if (!load) { gtk_spinner_stop(GTK_SPINNER(t->spinner)); gtk_widget_hide(t->spinner); } #endif /* make notebook tabs reorderable */ gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE); g_object_connect((GObject*)t->cmd, "signal::key-press-event", (GCallback)cmd_keypress_cb, t, "signal::key-release-event", (GCallback)cmd_keyrelease_cb, t, "signal::focus-out-event", (GCallback)cmd_focusout_cb, t, "signal::activate", (GCallback)cmd_activate_cb, t, (char *)NULL); g_object_connect((GObject*)t->wv, "signal-after::key-press-event", (GCallback)webview_keypress_cb, t, /* "signal::hovering-over-link", (GCallback)webview_hover_cb, t, */ "signal::download-requested", (GCallback)webview_download_cb, t, "signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, t, "signal::navigation-policy-decision-requested", (GCallback)webview_npd_cb, t, "signal::new-window-policy-decision-requested", (GCallback)webview_nw_cb, t, "signal::create-web-view", (GCallback)webview_cwv_cb, t, "signal::event", (GCallback)webview_event_cb, t, "signal::load-finished", (GCallback)webview_load_finished_cb, t, "signal::load-progress-changed", (GCallback)webview_progress_changed_cb, t, (char *)NULL); g_signal_connect(t->wv, "notify::load-status", G_CALLBACK(notify_load_status_cb), t); /* hijack the unused keys as if we were the browser */ g_object_connect((GObject*)t->toolbar, "signal-after::key-press-event", (GCallback)webview_keypress_cb, t, (char *)NULL); g_signal_connect(G_OBJECT(bb), "button_press_event", G_CALLBACK(tab_close_cb), t); /* hide stuff */ gtk_widget_hide(t->cmd); if (showurl == 0) gtk_widget_hide(t->toolbar); if (focus) { gtk_notebook_set_current_page(notebook, t->tab_id); DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n", t->tab_id); } if (load) webkit_web_view_load_uri(t->wv, title); else gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry)); /* restore the tab's history */ if (u && u->history) { bfl = webkit_web_view_get_back_forward_list(t->wv); items = u->history; while (items) { item = items->data; webkit_web_back_forward_list_add_item(bfl, item); items = g_list_next(items); } item = g_list_nth_data(u->history, u->back); webkit_web_view_go_to_back_forward_item(t->wv, item); g_list_free(items); g_list_free(u->history); } if (newuri) g_free(newuri); } void notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn, gpointer *udata) { struct tab *t; const gchar *uri; DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn); TAILQ_FOREACH(t, &tabs, entry) { if (t->tab_id == pn) { DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to " "%d\n", pn); uri = webkit_web_view_get_title(t->wv); if (uri == NULL) uri = XT_NAME; gtk_window_set_title(GTK_WINDOW(main_window), uri); gtk_widget_hide(t->cmd); if (t->focus_wv) gtk_widget_grab_focus(GTK_WIDGET(t->wv)); } } } void menuitem_response(struct tab *t) { gtk_notebook_set_current_page(notebook, t->tab_id); } gboolean arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data) { GtkWidget *menu, *menu_items; GdkEventButton *bevent; WebKitWebFrame *frame; const gchar *uri; struct tab *ti; if (event->type == GDK_BUTTON_PRESS) { bevent = (GdkEventButton *) event; menu = gtk_menu_new(); TAILQ_FOREACH(ti, &tabs, entry) { frame = webkit_web_view_get_main_frame(ti->wv); uri = webkit_web_frame_get_uri(frame); /* XXX make sure there is something to print */ /* XXX add gui pages in here to look purdy */ if (uri == NULL) uri = "(untitled)"; if (strlen(uri) == 0) uri = "(untitled)"; menu_items = gtk_menu_item_new_with_label(uri); gtk_menu_append(GTK_MENU (menu), menu_items); gtk_widget_show(menu_items); gtk_signal_connect_object(GTK_OBJECT(menu_items), "activate", GTK_SIGNAL_FUNC(menuitem_response), (gpointer)ti); } gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, bevent->button, bevent->time); /* unref object so it'll free itself when popped down */ g_object_ref_sink(menu); g_object_unref(menu); return (TRUE /* eat event */); } return (FALSE /* propagate */); } void create_canvas(void) { GtkWidget *vbox; GList *l = NULL; GdkPixbuf *pb; char file[PATH_MAX]; int i; vbox = gtk_vbox_new(FALSE, 0); notebook = GTK_NOTEBOOK(gtk_notebook_new()); if (showtabs == 0) gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE); gtk_notebook_set_scrollable(notebook, TRUE); gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE); abtn = gtk_button_new(); arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE); gtk_widget_set_size_request(arrow, -1, 0); gtk_container_add(GTK_CONTAINER(abtn), arrow); gtk_widget_set_size_request(abtn, -1, 20); gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END); gtk_widget_set_size_request(vbox, -1, 0); gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0); g_object_connect((GObject*)notebook, "signal::switch-page", (GCallback)notebook_switchpage_cb, NULL, (char *)NULL); g_signal_connect(G_OBJECT(abtn), "button_press_event", G_CALLBACK(arrow_cb), NULL); main_window = create_window(); gtk_container_add(GTK_CONTAINER(main_window), vbox); gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME); /* icons */ for (i = 0; i < LENGTH(icons); i++) { snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]); pb = gdk_pixbuf_new_from_file(file, NULL); l = g_list_append(l, pb); } gtk_window_set_default_icon_list(l); gtk_widget_show_all(abtn); gtk_widget_show_all(main_window); } void set_hook(void **hook, char *name) { if (hook == NULL) errx(1, "set_hook"); if (*hook == NULL) { *hook = dlsym(RTLD_NEXT, name); if (*hook == NULL) errx(1, "can't hook %s", name); } } /* override libsoup soup_cookie_equal because it doesn't look at domain */ gboolean soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2) { g_return_val_if_fail(cookie1, FALSE); g_return_val_if_fail(cookie2, FALSE); return (!strcmp (cookie1->name, cookie2->name) && !strcmp (cookie1->value, cookie2->value) && !strcmp (cookie1->path, cookie2->path) && !strcmp (cookie1->domain, cookie2->domain)); } void transfer_cookies(void) { GSList *cf; SoupCookie *sc, *pc; cf = soup_cookie_jar_all_cookies(p_cookiejar); for (;cf; cf = cf->next) { pc = cf->data; sc = soup_cookie_copy(pc); _soup_cookie_jar_add_cookie(s_cookiejar, sc); } soup_cookies_free(cf); } void soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c) { GSList *cf; SoupCookie *ci; print_cookie("soup_cookie_jar_delete_cookie", c); if (jar == NULL || c == NULL) return; /* find and remove from persistent jar */ cf = soup_cookie_jar_all_cookies(p_cookiejar); for (;cf; cf = cf->next) { ci = cf->data; if (soup_cookie_equal(ci, c)) { _soup_cookie_jar_delete_cookie(p_cookiejar, ci); break; } } soup_cookies_free(cf); /* delete from session jar */ _soup_cookie_jar_delete_cookie(s_cookiejar, c); } void soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie) { struct domain *d; SoupCookie *c; DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n", jar, p_cookiejar, s_cookiejar); /* see if we are up and running */ if (p_cookiejar == NULL) { _soup_cookie_jar_add_cookie(jar, cookie); return; } /* disallow p_cookiejar adds, shouldn't happen */ if (jar == p_cookiejar) return; if ((d = wl_find(cookie->domain, &c_wl)) == NULL) { blocked_cookies++; DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: reject %s\n", cookie->domain); if (!allow_volatile_cookies) return; } if (cookie->expires == NULL && session_timeout) { soup_cookie_set_expires(cookie, soup_date_new_from_now(session_timeout)); print_cookie("modified add cookie", cookie); } /* see if we are white listed for persistence */ if (d && d->handy) { /* add to persistent jar */ c = soup_cookie_copy(cookie); print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c); _soup_cookie_jar_add_cookie(p_cookiejar, c); } /* add to session jar */ print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie); _soup_cookie_jar_add_cookie(s_cookiejar, cookie); } void setup_cookies(char *file) { if (cookies_enabled == 0) return; set_hook((void *)&_soup_cookie_jar_add_cookie, "soup_cookie_jar_add_cookie"); set_hook((void *)&_soup_cookie_jar_delete_cookie, "soup_cookie_jar_delete_cookie"); /* * the following code is intricate due to overriding several libsoup * functions. * do not alter order of these operations. */ p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies); s_cookiejar = soup_cookie_jar_new(); g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY, cookie_policy, (void *)NULL); transfer_cookies(); soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar); } void setup_proxy(char *uri) { if (proxy_uri) { g_object_set(session, "proxy_uri", NULL, (char *)NULL); soup_uri_free(proxy_uri); proxy_uri = NULL; } if (http_proxy) { if (http_proxy != uri) { g_free(http_proxy); http_proxy = NULL; } } if (uri) { http_proxy = g_strdup(uri); DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri); proxy_uri = soup_uri_new(http_proxy); g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL); } } int send_url_to_socket(char *url) { int s, len, rv = -1; struct sockaddr_un sa; pwd = getpwuid(getuid()); if (pwd == NULL) errx(1, "invalid user %d", getuid()); if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { warnx("send_url_to_socket: socket"); return (-1); } sa.sun_family = AF_UNIX; snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s", pwd->pw_dir, XT_DIR, XT_SOCKET_FILE); len = SUN_LEN(&sa); if (connect(s, (struct sockaddr *)&sa, len) == -1) { warnx("send_url_to_socket: connect"); goto done; } if (send(s, url, strlen(url) + 1, 0) == -1) { warnx("send_url_to_socket: send"); goto done; } done: close(s); return (rv); } void socket_watcher(gpointer data, gint fd, GdkInputCondition cond) { int s, n; char str[XT_MAX_URL_LENGTH]; socklen_t t = sizeof(struct sockaddr_un); struct sockaddr_un sa; struct passwd *p; uid_t uid; gid_t gid; if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) { warn("socket_watcher: accept"); return; } if (getpeereid(s, &uid, &gid) == -1) { warn("socket_watcher: getpeereid"); return; } if (uid != getuid() || gid != getgid()) { warnx("socket_watcher: unauthorized user"); return; } p = getpwuid(uid); if (p == NULL) { warnx("socket_watcher: not a valid user"); return; } n = recv(s, str, sizeof(str), 0); if (n <= 0) return; create_new_tab(str, NULL, 1); } int is_running(void) { int s, len, rv = 1; struct sockaddr_un sa; if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { warn("is_running: socket"); return (-1); } sa.sun_family = AF_UNIX; snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s", pwd->pw_dir, XT_DIR, XT_SOCKET_FILE); len = SUN_LEN(&sa); /* connect to see if there is a listener */ if (connect(s, (struct sockaddr *)&sa, len) == -1) rv = 0; /* not running */ else rv = 1; /* already running */ close(s); return (rv); } int build_socket(void) { int s, len; struct sockaddr_un sa; if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { warn("build_socket: socket"); return (-1); } sa.sun_family = AF_UNIX; snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s", pwd->pw_dir, XT_DIR, XT_SOCKET_FILE); len = SUN_LEN(&sa); /* connect to see if there is a listener */ if (connect(s, (struct sockaddr *)&sa, len) == -1) { /* no listener so we will */ unlink(sa.sun_path); if (bind(s, (struct sockaddr *)&sa, len) == -1) { warn("build_socket: bind"); goto done; } if (listen(s, 1) == -1) { warn("build_socket: listen"); goto done; } return (s); } done: close(s); return (-1); } void usage(void) { fprintf(stderr, "%s [-nSTVt][-f file] url ...\n", __progname); exit(0); } int main(int argc, char *argv[]) { struct stat sb; int c, focus = 1, s, optn = 0; char conf[PATH_MAX] = { '\0' }; char file[PATH_MAX]; char *env_proxy = NULL; FILE *f = NULL; start_argv = argv; while ((c = getopt(argc, argv, "STVf:tn")) != -1) { switch (c) { case 'S': showurl = 0; break; case 'T': showtabs = 0; break; case 'V': errx(0 , "Version: %s", version); break; case 'f': strlcpy(conf, optarg, sizeof(conf)); break; case 't': tabless = 1; break; case 'n': optn = 1; break; default: usage(); /* NOTREACHED */ } } argc -= optind; argv += optind; TAILQ_INIT(&tabs); RB_INIT(&hl); RB_INIT(&js_wl); RB_INIT(&downloads); TAILQ_INIT(&mtl); TAILQ_INIT(&aliases); TAILQ_INIT(&undos); gnutls_global_init(); /* generate session keys for xtp pages */ generate_xtp_session_key(&dl_session_key); generate_xtp_session_key(&hl_session_key); generate_xtp_session_key(&cl_session_key); generate_xtp_session_key(&fl_session_key); /* prepare gtk */ gtk_init(&argc, &argv); if (!g_thread_supported()) g_thread_init(NULL); pwd = getpwuid(getuid()); if (pwd == NULL) errx(1, "invalid user %d", getuid()); /* set download dir */ strlcpy(download_dir, pwd->pw_dir, sizeof download_dir); /* set default string settings */ home = g_strdup("http://www.peereboom.us"); resource_dir = g_strdup("/usr/local/share/xxxterm/"); /* read config file */ if (strlen(conf) == 0) snprintf(conf, sizeof conf, "%s/.%s", pwd->pw_dir, XT_CONF_FILE); config_parse(conf, 0); /* working directory */ snprintf(work_dir, sizeof work_dir, "%s/%s", pwd->pw_dir, XT_DIR); if (stat(work_dir, &sb)) { if (mkdir(work_dir, S_IRWXU) == -1) err(1, "mkdir work_dir"); if (stat(work_dir, &sb)) err(1, "stat work_dir"); } if (S_ISDIR(sb.st_mode) == 0) errx(1, "%s not a dir", work_dir); if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) { warnx("fixing invalid permissions on %s", work_dir); if (chmod(work_dir, S_IRWXU) == -1) err(1, "chmod"); } /* runtime settings that can override config file */ if (runtime_settings[0] != '\0') config_parse(runtime_settings, 1); /* download dir */ if (!strcmp(download_dir, pwd->pw_dir)) strlcat(download_dir, "/downloads", sizeof download_dir); if (stat(download_dir, &sb)) { if (mkdir(download_dir, S_IRWXU) == -1) err(1, "mkdir download_dir"); if (stat(download_dir, &sb)) err(1, "stat download_dir"); } if (S_ISDIR(sb.st_mode) == 0) errx(1, "%s not a dir", download_dir); if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) { warnx("fixing invalid permissions on %s", download_dir); if (chmod(download_dir, S_IRWXU) == -1) err(1, "chmod"); } /* favorites file */ snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE); if (stat(file, &sb)) { warnx("favorites file doesn't exist, creating it"); if ((f = fopen(file, "w")) == NULL) err(1, "favorites"); fclose(f); } /* cookies */ session = webkit_get_default_session(); snprintf(file, sizeof file, "%s/cookies.txt", work_dir); setup_cookies(file); if (ssl_ca_file) { if (stat(ssl_ca_file, &sb)) { warn("no CA file: %s", ssl_ca_file); g_free(ssl_ca_file); ssl_ca_file = NULL; } else g_object_set(session, SOUP_SESSION_SSL_CA_FILE, ssl_ca_file, SOUP_SESSION_SSL_STRICT, ssl_strict_certs, (void *)NULL); } /* proxy */ env_proxy = getenv("http_proxy"); if (env_proxy) setup_proxy(env_proxy); else setup_proxy(http_proxy); /* see if there is already an xxxterm running */ if (single_instance && is_running()) { optn = 1; warnx("already running"); } if (optn) { while (argc) { send_url_to_socket(argv[0]); argc--; argv++; } exit(0); } /* go graphical */ create_canvas(); focus = restore_saved_tabs(); while (argc) { create_new_tab(argv[0], NULL, focus); focus = 0; argc--; argv++; } if (focus == 1) create_new_tab(home, NULL, 1); if (enable_socket) if ((s = build_socket()) != -1) gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL); gtk_main(); gnutls_global_deinit(); return (0); }