catch up to new man reality; files go in man/man
[xxxterm.git] / xxxterm.c
blob8367e55e27aba44688612087f137513f88978735
1 /* $xxxterm$ */
2 /*
3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010 Edd Barrett <vext01@gmail.com>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
7 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 * TODO:
24 * multi letter commands
25 * pre and post counts for commands
26 * autocompletion on various inputs
27 * create privacy browsing
28 * - encrypted local data
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <err.h>
34 #include <pwd.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <pthread.h>
38 #include <dlfcn.h>
39 #include <errno.h>
40 #include <signal.h>
41 #include <libgen.h>
42 #include <ctype.h>
44 #include <sys/types.h>
45 #include <sys/wait.h>
46 #if defined(__linux__)
47 #include "linux/util.h"
48 #include "linux/tree.h"
49 #elif defined(__FreeBSD__)
50 #include <libutil.h>
51 #include "freebsd/util.h"
52 #include <sys/tree.h>
53 #else /* OpenBSD */
54 #include <util.h>
55 #include <sys/tree.h>
56 #endif
57 #include <sys/queue.h>
58 #include <sys/stat.h>
59 #include <sys/socket.h>
60 #include <sys/un.h>
62 #include <gtk/gtk.h>
63 #include <gdk/gdkkeysyms.h>
65 #if GTK_CHECK_VERSION(3,0,0)
66 /* we still use GDK_* instead of GDK_KEY_* */
67 #include <gdk/gdkkeysyms-compat.h>
68 #endif
70 #include <webkit/webkit.h>
71 #include <libsoup/soup.h>
72 #include <gnutls/gnutls.h>
73 #include <JavaScriptCore/JavaScript.h>
74 #include <gnutls/x509.h>
76 #include "javascript.h"
79 javascript.h borrowed from vimprobable2 under the following license:
81 Copyright (c) 2009 Leon Winter
82 Copyright (c) 2009 Hannes Schueller
83 Copyright (c) 2009 Matto Fransen
85 Permission is hereby granted, free of charge, to any person obtaining a copy
86 of this software and associated documentation files (the "Software"), to deal
87 in the Software without restriction, including without limitation the rights
88 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
89 copies of the Software, and to permit persons to whom the Software is
90 furnished to do so, subject to the following conditions:
92 The above copyright notice and this permission notice shall be included in
93 all copies or substantial portions of the Software.
95 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
96 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
97 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
98 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
99 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
100 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
101 THE SOFTWARE.
104 static char *version = "$xxxterm$";
106 /* hooked functions */
107 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
108 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
109 SoupCookie *);
111 /*#define XT_DEBUG*/
112 #ifdef XT_DEBUG
113 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
114 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
115 #define XT_D_MOVE 0x0001
116 #define XT_D_KEY 0x0002
117 #define XT_D_TAB 0x0004
118 #define XT_D_URL 0x0008
119 #define XT_D_CMD 0x0010
120 #define XT_D_NAV 0x0020
121 #define XT_D_DOWNLOAD 0x0040
122 #define XT_D_CONFIG 0x0080
123 #define XT_D_JS 0x0100
124 #define XT_D_FAVORITE 0x0200
125 #define XT_D_PRINTING 0x0400
126 #define XT_D_COOKIE 0x0800
127 #define XT_D_KEYBINDING 0x1000
128 #define XT_D_CLIP 0x2000
129 u_int32_t swm_debug = 0
130 | XT_D_MOVE
131 | XT_D_KEY
132 | XT_D_TAB
133 | XT_D_URL
134 | XT_D_CMD
135 | XT_D_NAV
136 | XT_D_DOWNLOAD
137 | XT_D_CONFIG
138 | XT_D_JS
139 | XT_D_FAVORITE
140 | XT_D_PRINTING
141 | XT_D_COOKIE
142 | XT_D_KEYBINDING
143 | XT_D_CLIP
145 #else
146 #define DPRINTF(x...)
147 #define DNPRINTF(n,x...)
148 #endif
150 #define LENGTH(x) (sizeof x / sizeof x[0])
151 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
152 ~(GDK_BUTTON1_MASK) & \
153 ~(GDK_BUTTON2_MASK) & \
154 ~(GDK_BUTTON3_MASK) & \
155 ~(GDK_BUTTON4_MASK) & \
156 ~(GDK_BUTTON5_MASK))
158 char *icons[] = {
159 "xxxtermicon16.png",
160 "xxxtermicon32.png",
161 "xxxtermicon48.png",
162 "xxxtermicon64.png",
163 "xxxtermicon128.png"
166 struct tab {
167 TAILQ_ENTRY(tab) entry;
168 GtkWidget *vbox;
169 GtkWidget *tab_content;
170 GtkWidget *label;
171 GtkWidget *spinner;
172 GtkWidget *uri_entry;
173 GtkWidget *search_entry;
174 GtkWidget *toolbar;
175 GtkWidget *browser_win;
176 GtkWidget *statusbar;
177 GtkWidget *cmd;
178 GtkWidget *oops;
179 GtkWidget *backward;
180 GtkWidget *forward;
181 GtkWidget *stop;
182 GtkWidget *gohome;
183 GtkWidget *js_toggle;
184 GtkEntryCompletion *completion;
185 guint tab_id;
186 WebKitWebView *wv;
188 WebKitWebHistoryItem *item;
189 WebKitWebBackForwardList *bfl;
191 /* favicon */
192 WebKitNetworkRequest *icon_request;
193 WebKitDownload *icon_download;
194 gchar *icon_dest_uri;
196 /* adjustments for browser */
197 GtkScrollbar *sb_h;
198 GtkScrollbar *sb_v;
199 GtkAdjustment *adjust_h;
200 GtkAdjustment *adjust_v;
202 /* flags */
203 int focus_wv;
204 int ctrl_click;
205 gchar *status;
206 int xtp_meaning; /* identifies dls/favorites */
208 /* hints */
209 int hints_on;
210 int hint_mode;
211 #define XT_HINT_NONE (0)
212 #define XT_HINT_NUMERICAL (1)
213 #define XT_HINT_ALPHANUM (2)
214 char hint_buf[128];
215 char hint_num[128];
217 /* custom stylesheet */
218 int styled;
219 char *stylesheet;
221 /* search */
222 char *search_text;
223 int search_forward;
225 /* settings */
226 WebKitWebSettings *settings;
227 int font_size;
228 gchar *user_agent;
230 TAILQ_HEAD(tab_list, tab);
232 struct history {
233 RB_ENTRY(history) entry;
234 const gchar *uri;
235 const gchar *title;
237 RB_HEAD(history_list, history);
239 struct download {
240 RB_ENTRY(download) entry;
241 int id;
242 WebKitDownload *download;
243 struct tab *tab;
245 RB_HEAD(download_list, download);
247 struct domain {
248 RB_ENTRY(domain) entry;
249 gchar *d;
250 int handy; /* app use */
252 RB_HEAD(domain_list, domain);
254 struct undo {
255 TAILQ_ENTRY(undo) entry;
256 gchar *uri;
257 GList *history;
258 int back; /* Keeps track of how many back
259 * history items there are. */
261 TAILQ_HEAD(undo_tailq, undo);
263 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
264 int next_download_id = 1;
266 struct karg {
267 int i;
268 char *s;
269 int p;
272 /* defines */
273 #define XT_NAME ("XXXTerm")
274 #define XT_DIR (".xxxterm")
275 #define XT_CACHE_DIR ("cache")
276 #define XT_CERT_DIR ("certs/")
277 #define XT_SESSIONS_DIR ("sessions/")
278 #define XT_CONF_FILE ("xxxterm.conf")
279 #define XT_FAVS_FILE ("favorites")
280 #define XT_SAVED_TABS_FILE ("main_session")
281 #define XT_RESTART_TABS_FILE ("restart_tabs")
282 #define XT_SOCKET_FILE ("socket")
283 #define XT_HISTORY_FILE ("history")
284 #define XT_REJECT_FILE ("rejected.txt")
285 #define XT_COOKIE_FILE ("cookies.txt")
286 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
287 #define XT_CB_HANDLED (TRUE)
288 #define XT_CB_PASSTHROUGH (FALSE)
289 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
290 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
291 #define XT_DLMAN_REFRESH "10"
292 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
293 "td{overflow: hidden;" \
294 " padding: 2px 2px 2px 2px;" \
295 " border: 1px solid black;" \
296 " vertical-align:top;" \
297 " word-wrap: break-word}\n" \
298 "tr:hover{background: #ffff99}\n" \
299 "th{background-color: #cccccc;" \
300 " border: 1px solid black}\n" \
301 "table{width: 100%%;" \
302 " border: 1px black solid;" \
303 " border-collapse:collapse}\n" \
304 ".progress-outer{" \
305 "border: 1px solid black;" \
306 " height: 8px;" \
307 " width: 90%%}\n" \
308 ".progress-inner{float: left;" \
309 " height: 8px;" \
310 " background: green}\n" \
311 ".dlstatus{font-size: small;" \
312 " text-align: center}\n" \
313 "</style>\n"
314 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
315 #define XT_MAX_UNDO_CLOSE_TAB (32)
316 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
317 #define XT_PRINT_EXTRA_MARGIN 10
319 /* colors */
320 #define XT_COLOR_RED "#cc0000"
321 #define XT_COLOR_YELLOW "#ffff66"
322 #define XT_COLOR_BLUE "lightblue"
323 #define XT_COLOR_GREEN "#99ff66"
324 #define XT_COLOR_WHITE "white"
325 #define XT_COLOR_BLACK "black"
328 * xxxterm "protocol" (xtp)
329 * We use this for managing stuff like downloads and favorites. They
330 * make magical HTML pages in memory which have xxxt:// links in order
331 * to communicate with xxxterm's internals. These links take the format:
332 * xxxt://class/session_key/action/arg
334 * Don't begin xtp class/actions as 0. atoi returns that on error.
336 * Typically we have not put addition of items in this framework, as
337 * adding items is either done via an ex-command or via a keybinding instead.
340 #define XT_XTP_STR "xxxt://"
342 /* XTP classes (xxxt://<class>) */
343 #define XT_XTP_INVALID 0 /* invalid */
344 #define XT_XTP_DL 1 /* downloads */
345 #define XT_XTP_HL 2 /* history */
346 #define XT_XTP_CL 3 /* cookies */
347 #define XT_XTP_FL 4 /* favorites */
349 /* XTP download actions */
350 #define XT_XTP_DL_LIST 1
351 #define XT_XTP_DL_CANCEL 2
352 #define XT_XTP_DL_REMOVE 3
354 /* XTP history actions */
355 #define XT_XTP_HL_LIST 1
356 #define XT_XTP_HL_REMOVE 2
358 /* XTP cookie actions */
359 #define XT_XTP_CL_LIST 1
360 #define XT_XTP_CL_REMOVE 2
362 /* XTP cookie actions */
363 #define XT_XTP_FL_LIST 1
364 #define XT_XTP_FL_REMOVE 2
366 /* xtp tab meanings - identifies which tabs have xtp pages in */
367 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
368 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
369 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
370 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
371 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
373 /* actions */
374 #define XT_MOVE_INVALID (0)
375 #define XT_MOVE_DOWN (1)
376 #define XT_MOVE_UP (2)
377 #define XT_MOVE_BOTTOM (3)
378 #define XT_MOVE_TOP (4)
379 #define XT_MOVE_PAGEDOWN (5)
380 #define XT_MOVE_PAGEUP (6)
381 #define XT_MOVE_HALFDOWN (7)
382 #define XT_MOVE_HALFUP (8)
383 #define XT_MOVE_LEFT (9)
384 #define XT_MOVE_FARLEFT (10)
385 #define XT_MOVE_RIGHT (11)
386 #define XT_MOVE_FARRIGHT (12)
388 #define XT_TAB_LAST (-4)
389 #define XT_TAB_FIRST (-3)
390 #define XT_TAB_PREV (-2)
391 #define XT_TAB_NEXT (-1)
392 #define XT_TAB_INVALID (0)
393 #define XT_TAB_NEW (1)
394 #define XT_TAB_DELETE (2)
395 #define XT_TAB_DELQUIT (3)
396 #define XT_TAB_OPEN (4)
397 #define XT_TAB_UNDO_CLOSE (5)
398 #define XT_TAB_SHOW (6)
399 #define XT_TAB_HIDE (7)
401 #define XT_NAV_INVALID (0)
402 #define XT_NAV_BACK (1)
403 #define XT_NAV_FORWARD (2)
404 #define XT_NAV_RELOAD (3)
405 #define XT_NAV_RELOAD_CACHE (4)
407 #define XT_FOCUS_INVALID (0)
408 #define XT_FOCUS_URI (1)
409 #define XT_FOCUS_SEARCH (2)
411 #define XT_SEARCH_INVALID (0)
412 #define XT_SEARCH_NEXT (1)
413 #define XT_SEARCH_PREV (2)
415 #define XT_PASTE_CURRENT_TAB (0)
416 #define XT_PASTE_NEW_TAB (1)
418 #define XT_FONT_SET (0)
420 #define XT_URL_SHOW (1)
421 #define XT_URL_HIDE (2)
423 #define XT_STATUSBAR_SHOW (1)
424 #define XT_STATUSBAR_HIDE (2)
426 #define XT_WL_TOGGLE (1<<0)
427 #define XT_WL_ENABLE (1<<1)
428 #define XT_WL_DISABLE (1<<2)
429 #define XT_WL_FQDN (1<<3) /* default */
430 #define XT_WL_TOPLEVEL (1<<4)
431 #define XT_WL_PERSISTENT (1<<5)
432 #define XT_WL_SESSION (1<<6)
433 #define XT_WL_RELOAD (1<<7)
435 #define XT_SHOW (1<<7)
436 #define XT_DELETE (1<<8)
437 #define XT_SAVE (1<<9)
438 #define XT_OPEN (1<<10)
440 #define XT_CMD_OPEN (0)
441 #define XT_CMD_OPEN_CURRENT (1)
442 #define XT_CMD_TABNEW (2)
443 #define XT_CMD_TABNEW_CURRENT (3)
445 #define XT_STATUS_NOTHING (0)
446 #define XT_STATUS_LINK (1)
447 #define XT_STATUS_URI (2)
448 #define XT_STATUS_LOADING (3)
450 #define XT_SES_DONOTHING (0)
451 #define XT_SES_CLOSETABS (1)
453 #define XT_BM_NORMAL (0)
454 #define XT_BM_WHITELIST (1)
455 #define XT_BM_KIOSK (2)
457 #define XT_PREFIX (1<<0)
458 #define XT_USERARG (1<<1)
459 #define XT_URLARG (1<<2)
460 #define XT_INTARG (1<<3)
462 /* mime types */
463 struct mime_type {
464 char *mt_type;
465 char *mt_action;
466 int mt_default;
467 int mt_download;
468 TAILQ_ENTRY(mime_type) entry;
470 TAILQ_HEAD(mime_type_list, mime_type);
472 /* uri aliases */
473 struct alias {
474 char *a_name;
475 char *a_uri;
476 TAILQ_ENTRY(alias) entry;
478 TAILQ_HEAD(alias_list, alias);
480 /* settings that require restart */
481 int tabless = 0; /* allow only 1 tab */
482 int enable_socket = 0;
483 int single_instance = 0; /* only allow one xxxterm to run */
484 int fancy_bar = 1; /* fancy toolbar */
485 int browser_mode = XT_BM_NORMAL;
487 /* runtime settings */
488 int show_tabs = 1; /* show tabs on notebook */
489 int show_url = 1; /* show url toolbar on notebook */
490 int show_statusbar = 0; /* vimperator style status bar */
491 int ctrl_click_focus = 0; /* ctrl click gets focus */
492 int cookies_enabled = 1; /* enable cookies */
493 int read_only_cookies = 0; /* enable to not write cookies */
494 int enable_scripts = 1;
495 int enable_plugins = 0;
496 int default_font_size = 12;
497 gfloat default_zoom_level = 1.0;
498 int window_height = 768;
499 int window_width = 1024;
500 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
501 unsigned refresh_interval = 10; /* download refresh interval */
502 int enable_cookie_whitelist = 0;
503 int enable_js_whitelist = 0;
504 time_t session_timeout = 3600; /* cookie session timeout */
505 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
506 char *ssl_ca_file = NULL;
507 char *resource_dir = NULL;
508 gboolean ssl_strict_certs = FALSE;
509 int append_next = 1; /* append tab after current tab */
510 char *home = NULL;
511 char *search_string = NULL;
512 char *http_proxy = NULL;
513 char download_dir[PATH_MAX];
514 char runtime_settings[PATH_MAX]; /* override of settings */
515 int allow_volatile_cookies = 0;
516 int save_global_history = 0; /* save global history to disk */
517 char *user_agent = NULL;
518 int save_rejected_cookies = 0;
519 time_t session_autosave = 0;
520 int guess_search = 0;
521 int dns_prefetch = FALSE;
522 gint max_connections = 25;
523 gint max_host_connections = 5;
524 gint enable_spell_checking = 0;
525 char *spell_check_languages = NULL;
527 struct settings;
528 struct key_binding;
529 int set_download_dir(struct settings *, char *);
530 int set_work_dir(struct settings *, char *);
531 int set_runtime_dir(struct settings *, char *);
532 int set_browser_mode(struct settings *, char *);
533 int set_cookie_policy(struct settings *, char *);
534 int add_alias(struct settings *, char *);
535 int add_mime_type(struct settings *, char *);
536 int add_cookie_wl(struct settings *, char *);
537 int add_js_wl(struct settings *, char *);
538 int add_kb(struct settings *, char *);
539 void button_set_stockid(GtkWidget *, char *);
540 GtkWidget * create_button(char *, char *, int);
542 char *get_browser_mode(struct settings *);
543 char *get_cookie_policy(struct settings *);
545 char *get_download_dir(struct settings *);
546 char *get_work_dir(struct settings *);
547 char *get_runtime_dir(struct settings *);
549 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
550 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
551 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
552 void walk_kb(struct settings *, void (*)(struct settings *, char *, void *), void *);
553 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
555 void recalc_tabs(void);
557 struct special {
558 int (*set)(struct settings *, char *);
559 char *(*get)(struct settings *);
560 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
563 struct special s_browser_mode = {
564 set_browser_mode,
565 get_browser_mode,
566 NULL
569 struct special s_cookie = {
570 set_cookie_policy,
571 get_cookie_policy,
572 NULL
575 struct special s_alias = {
576 add_alias,
577 NULL,
578 walk_alias
581 struct special s_mime = {
582 add_mime_type,
583 NULL,
584 walk_mime_type
587 struct special s_js = {
588 add_js_wl,
589 NULL,
590 walk_js_wl
593 struct special s_kb = {
594 add_kb,
595 NULL,
596 walk_kb
599 struct special s_cookie_wl = {
600 add_cookie_wl,
601 NULL,
602 walk_cookie_wl
605 struct special s_download_dir = {
606 set_download_dir,
607 get_download_dir,
608 NULL
611 struct special s_work_dir = {
612 set_work_dir,
613 get_work_dir,
614 NULL
617 struct settings {
618 char *name;
619 int type;
620 #define XT_S_INVALID (0)
621 #define XT_S_INT (1)
622 #define XT_S_STR (2)
623 #define XT_S_FLOAT (3)
624 uint32_t flags;
625 #define XT_SF_RESTART (1<<0)
626 #define XT_SF_RUNTIME (1<<1)
627 int *ival;
628 char **sval;
629 struct special *s;
630 gfloat *fval;
631 } rs[] = {
632 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
633 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
634 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
635 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
636 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
637 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
638 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
639 { "default_zoom_level", XT_S_FLOAT, 0, NULL, NULL, NULL, &default_zoom_level },
640 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
641 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
642 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
643 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
644 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
645 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
646 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
647 { "home", XT_S_STR, 0, NULL, &home, NULL },
648 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
649 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
650 { "max_connections", XT_S_INT, XT_SF_RESTART,&max_connections, NULL, NULL },
651 { "max_host_connections", XT_S_INT, XT_SF_RESTART,&max_host_connections, NULL, NULL },
652 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
653 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
654 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
655 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
656 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
657 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
658 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
659 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
660 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
661 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
662 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
663 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
664 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
665 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
666 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
667 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
668 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
669 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
670 { "work_dir", XT_S_STR, 0, NULL, NULL,&s_work_dir },
671 { "enable_spell_checking", XT_S_INT, 0, &enable_spell_checking, NULL, NULL },
672 { "spell_check_languages", XT_S_STR, 0, NULL, &spell_check_languages, NULL },
674 /* runtime settings */
675 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
676 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
677 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
678 { "keybinding", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_kb },
679 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
682 int about(struct tab *, struct karg *);
683 int blank(struct tab *, struct karg *);
684 int ca_cmd(struct tab *, struct karg *);
685 int cookie_show_wl(struct tab *, struct karg *);
686 int js_show_wl(struct tab *, struct karg *);
687 int help(struct tab *, struct karg *);
688 int set(struct tab *, struct karg *);
689 int stats(struct tab *, struct karg *);
690 int marco(struct tab *, struct karg *);
691 const char * marco_message(int *);
692 int xtp_page_cl(struct tab *, struct karg *);
693 int xtp_page_dl(struct tab *, struct karg *);
694 int xtp_page_fl(struct tab *, struct karg *);
695 int xtp_page_hl(struct tab *, struct karg *);
696 void xt_icon_from_file(struct tab *, char *);
698 #define XT_URI_ABOUT ("about:")
699 #define XT_URI_ABOUT_LEN (strlen(XT_URI_ABOUT))
700 #define XT_URI_ABOUT_ABOUT ("about")
701 #define XT_URI_ABOUT_BLANK ("blank")
702 #define XT_URI_ABOUT_CERTS ("certs") /* XXX NOT YET */
703 #define XT_URI_ABOUT_COOKIEWL ("cookiewl")
704 #define XT_URI_ABOUT_COOKIEJAR ("cookiejar")
705 #define XT_URI_ABOUT_DOWNLOADS ("downloads")
706 #define XT_URI_ABOUT_FAVORITES ("favorites")
707 #define XT_URI_ABOUT_HELP ("help")
708 #define XT_URI_ABOUT_HISTORY ("history")
709 #define XT_URI_ABOUT_JSWL ("jswl")
710 #define XT_URI_ABOUT_SET ("set")
711 #define XT_URI_ABOUT_STATS ("stats")
712 #define XT_URI_ABOUT_MARCO ("marco")
714 struct about_type {
715 char *name;
716 int (*func)(struct tab *, struct karg *);
717 } about_list[] = {
718 { XT_URI_ABOUT_ABOUT, about },
719 { XT_URI_ABOUT_BLANK, blank },
720 { XT_URI_ABOUT_CERTS, ca_cmd },
721 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
722 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
723 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
724 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
725 { XT_URI_ABOUT_HELP, help },
726 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
727 { XT_URI_ABOUT_JSWL, js_show_wl },
728 { XT_URI_ABOUT_SET, set },
729 { XT_URI_ABOUT_STATS, stats },
730 { XT_URI_ABOUT_MARCO, marco },
733 /* globals */
734 extern char *__progname;
735 char **start_argv;
736 struct passwd *pwd;
737 GtkWidget *main_window;
738 GtkNotebook *notebook;
739 GtkWidget *arrow, *abtn;
740 struct tab_list tabs;
741 struct history_list hl;
742 struct download_list downloads;
743 struct domain_list c_wl;
744 struct domain_list js_wl;
745 struct undo_tailq undos;
746 struct keybinding_list kbl;
747 int undo_count;
748 int updating_dl_tabs = 0;
749 int updating_hl_tabs = 0;
750 int updating_cl_tabs = 0;
751 int updating_fl_tabs = 0;
752 char *global_search;
753 uint64_t blocked_cookies = 0;
754 char named_session[PATH_MAX];
755 int icon_size_map(int);
757 GtkListStore *completion_model;
758 void completion_add(struct tab *);
759 void completion_add_uri(const gchar *);
760 void xxx_dir(char *);
762 void
763 sigchild(int sig)
765 int saved_errno, status;
766 pid_t pid;
768 saved_errno = errno;
770 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
771 if (pid == -1) {
772 if (errno == EINTR)
773 continue;
774 if (errno != ECHILD) {
776 clog_warn("sigchild: waitpid:");
779 break;
782 if (WIFEXITED(status)) {
783 if (WEXITSTATUS(status) != 0) {
785 clog_warnx("sigchild: child exit status: %d",
786 WEXITSTATUS(status));
789 } else {
791 clog_warnx("sigchild: child is terminated abnormally");
796 errno = saved_errno;
800 is_g_object_setting(GObject *o, char *str)
802 guint n_props = 0, i;
803 GParamSpec **proplist;
805 if (! G_IS_OBJECT(o))
806 return (0);
808 proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
809 &n_props);
811 for (i=0; i < n_props; i++) {
812 if (! strcmp(proplist[i]->name, str))
813 return (1);
815 return (0);
818 gchar *
819 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
821 gchar *r;
823 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
824 "<head>\n"
825 "<title>%s</title>\n"
826 "%s"
827 "%s"
828 "</head>\n"
829 "<body>\n"
830 "<h1>%s</h1>\n"
831 "%s\n</body>\n"
832 "</html>",
833 title,
834 addstyles ? XT_PAGE_STYLE : "",
835 head,
836 title,
837 body);
839 return r;
843 * Display a web page from a HTML string in memory, rather than from a URL
845 void
846 load_webkit_string(struct tab *t, const char *str, gchar *title)
848 gchar *uri;
849 char file[PATH_MAX];
851 /* we set this to indicate we want to manually do navaction */
852 if (t->bfl)
853 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
855 webkit_web_view_load_string(t->wv, str, NULL, NULL, "");
856 #if GTK_CHECK_VERSION(2, 20, 0)
857 gtk_spinner_stop(GTK_SPINNER(t->spinner));
858 gtk_widget_hide(t->spinner);
859 #endif
861 if (title) {
862 uri = g_strdup_printf("%s%s", XT_URI_ABOUT, title);
863 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
864 g_free(uri);
866 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
867 xt_icon_from_file(t, file);
871 void
872 set_status(struct tab *t, gchar *s, int status)
874 gchar *type = NULL;
876 if (s == NULL)
877 return;
879 switch (status) {
880 case XT_STATUS_LOADING:
881 type = g_strdup_printf("Loading: %s", s);
882 s = type;
883 break;
884 case XT_STATUS_LINK:
885 type = g_strdup_printf("Link: %s", s);
886 if (!t->status)
887 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
888 s = type;
889 break;
890 case XT_STATUS_URI:
891 type = g_strdup_printf("%s", s);
892 if (!t->status) {
893 t->status = g_strdup(type);
895 s = type;
896 if (!t->status)
897 t->status = g_strdup(s);
898 break;
899 case XT_STATUS_NOTHING:
900 /* FALL THROUGH */
901 default:
902 break;
904 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
905 if (type)
906 g_free(type);
909 void
910 hide_oops(struct tab *t)
912 gtk_widget_hide(t->oops);
915 void
916 hide_cmd(struct tab *t)
918 gtk_widget_hide(t->cmd);
921 void
922 show_cmd(struct tab *t)
924 gtk_widget_hide(t->oops);
925 gtk_widget_show(t->cmd);
928 void
929 show_oops(struct tab *t, const char *fmt, ...)
931 va_list ap;
932 char *msg;
934 if (fmt == NULL)
935 return;
937 va_start(ap, fmt);
938 if (vasprintf(&msg, fmt, ap) == -1)
939 errx(1, "show_oops failed");
940 va_end(ap);
942 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
943 gtk_widget_hide(t->cmd);
944 gtk_widget_show(t->oops);
947 /* XXX collapse with show_oops */
948 void
949 show_oops_s(const char *fmt, ...)
951 va_list ap;
952 char *msg;
953 struct tab *ti, *t = NULL;
955 if (fmt == NULL)
956 return;
958 TAILQ_FOREACH(ti, &tabs, entry)
959 if (ti->tab_id == gtk_notebook_get_current_page(notebook)) {
960 t = ti;
961 break;
963 if (t == NULL)
964 return;
966 va_start(ap, fmt);
967 if (vasprintf(&msg, fmt, ap) == -1)
968 errx(1, "show_oops_s failed");
969 va_end(ap);
971 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
972 gtk_widget_hide(t->cmd);
973 gtk_widget_show(t->oops);
976 char *
977 get_as_string(struct settings *s)
979 char *r = NULL;
981 if (s == NULL)
982 return (NULL);
984 if (s->s) {
985 if (s->s->get)
986 r = s->s->get(s);
987 else
988 warnx("get_as_string skip %s\n", s->name);
989 } else if (s->type == XT_S_INT)
990 r = g_strdup_printf("%d", *s->ival);
991 else if (s->type == XT_S_STR)
992 r = g_strdup(*s->sval);
993 else if (s->type == XT_S_FLOAT)
994 r = g_strdup_printf("%f", *s->fval);
995 else
996 r = g_strdup_printf("INVALID TYPE");
998 return (r);
1001 void
1002 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
1004 int i;
1005 char *s;
1007 for (i = 0; i < LENGTH(rs); i++) {
1008 if (rs[i].s && rs[i].s->walk)
1009 rs[i].s->walk(&rs[i], cb, cb_args);
1010 else {
1011 s = get_as_string(&rs[i]);
1012 cb(&rs[i], s, cb_args);
1013 g_free(s);
1019 set_browser_mode(struct settings *s, char *val)
1021 if (!strcmp(val, "whitelist")) {
1022 browser_mode = XT_BM_WHITELIST;
1023 allow_volatile_cookies = 0;
1024 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1025 cookies_enabled = 1;
1026 enable_cookie_whitelist = 1;
1027 read_only_cookies = 0;
1028 save_rejected_cookies = 0;
1029 session_timeout = 3600;
1030 enable_scripts = 0;
1031 enable_js_whitelist = 1;
1032 } else if (!strcmp(val, "normal")) {
1033 browser_mode = XT_BM_NORMAL;
1034 allow_volatile_cookies = 0;
1035 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1036 cookies_enabled = 1;
1037 enable_cookie_whitelist = 0;
1038 read_only_cookies = 0;
1039 save_rejected_cookies = 0;
1040 session_timeout = 3600;
1041 enable_scripts = 1;
1042 enable_js_whitelist = 0;
1043 } else if (!strcmp(val, "kiosk")) {
1044 browser_mode = XT_BM_KIOSK;
1045 allow_volatile_cookies = 0;
1046 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1047 cookies_enabled = 1;
1048 enable_cookie_whitelist = 0;
1049 read_only_cookies = 0;
1050 save_rejected_cookies = 0;
1051 session_timeout = 3600;
1052 enable_scripts = 1;
1053 enable_js_whitelist = 0;
1054 show_tabs = 0;
1055 tabless = 1;
1056 } else
1057 return (1);
1059 return (0);
1062 char *
1063 get_browser_mode(struct settings *s)
1065 char *r = NULL;
1067 if (browser_mode == XT_BM_WHITELIST)
1068 r = g_strdup("whitelist");
1069 else if (browser_mode == XT_BM_NORMAL)
1070 r = g_strdup("normal");
1071 else if (browser_mode == XT_BM_KIOSK)
1072 r = g_strdup("kiosk");
1073 else
1074 return (NULL);
1076 return (r);
1080 set_cookie_policy(struct settings *s, char *val)
1082 if (!strcmp(val, "no3rdparty"))
1083 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
1084 else if (!strcmp(val, "accept"))
1085 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
1086 else if (!strcmp(val, "reject"))
1087 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
1088 else
1089 return (1);
1091 return (0);
1094 char *
1095 get_cookie_policy(struct settings *s)
1097 char *r = NULL;
1099 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
1100 r = g_strdup("no3rdparty");
1101 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
1102 r = g_strdup("accept");
1103 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
1104 r = g_strdup("reject");
1105 else
1106 return (NULL);
1108 return (r);
1111 char *
1112 get_download_dir(struct settings *s)
1114 if (download_dir[0] == '\0')
1115 return (0);
1116 return (g_strdup(download_dir));
1120 set_download_dir(struct settings *s, char *val)
1122 if (val[0] == '~')
1123 snprintf(download_dir, sizeof download_dir, "%s/%s",
1124 pwd->pw_dir, &val[1]);
1125 else
1126 strlcpy(download_dir, val, sizeof download_dir);
1128 return (0);
1132 * Session IDs.
1133 * We use these to prevent people putting xxxt:// URLs on
1134 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
1136 #define XT_XTP_SES_KEY_SZ 8
1137 #define XT_XTP_SES_KEY_HEX_FMT \
1138 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
1139 char *dl_session_key; /* downloads */
1140 char *hl_session_key; /* history list */
1141 char *cl_session_key; /* cookie list */
1142 char *fl_session_key; /* favorites list */
1144 char work_dir[PATH_MAX];
1145 char certs_dir[PATH_MAX];
1146 char cache_dir[PATH_MAX];
1147 char sessions_dir[PATH_MAX];
1148 char cookie_file[PATH_MAX];
1149 SoupURI *proxy_uri = NULL;
1150 SoupSession *session;
1151 SoupCookieJar *s_cookiejar;
1152 SoupCookieJar *p_cookiejar;
1153 char rc_fname[PATH_MAX];
1155 struct mime_type_list mtl;
1156 struct alias_list aliases;
1158 /* protos */
1159 struct tab *create_new_tab(char *, struct undo *, int, int);
1160 void delete_tab(struct tab *);
1161 void adjustfont_webkit(struct tab *, int);
1162 int run_script(struct tab *, char *);
1163 int download_rb_cmp(struct download *, struct download *);
1164 gboolean cmd_execute(struct tab *t, char *str);
1167 history_rb_cmp(struct history *h1, struct history *h2)
1169 return (strcmp(h1->uri, h2->uri));
1171 RB_GENERATE(history_list, history, entry, history_rb_cmp);
1174 domain_rb_cmp(struct domain *d1, struct domain *d2)
1176 return (strcmp(d1->d, d2->d));
1178 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
1180 char *
1181 get_work_dir(struct settings *s)
1183 if (work_dir[0] == '\0')
1184 return (0);
1185 return (g_strdup(work_dir));
1189 set_work_dir(struct settings *s, char *val)
1191 if (val[0] == '~')
1192 snprintf(work_dir, sizeof work_dir, "%s/%s",
1193 pwd->pw_dir, &val[1]);
1194 else
1195 strlcpy(work_dir, val, sizeof work_dir);
1197 return (0);
1201 * generate a session key to secure xtp commands.
1202 * pass in a ptr to the key in question and it will
1203 * be modified in place.
1205 void
1206 generate_xtp_session_key(char **key)
1208 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1210 /* free old key */
1211 if (*key)
1212 g_free(*key);
1214 /* make a new one */
1215 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1216 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1217 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1218 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1220 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1224 * validate a xtp session key.
1225 * return 1 if OK
1228 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1230 if (strcmp(trusted, untrusted) != 0) {
1231 show_oops(t, "%s: xtp session key mismatch possible spoof",
1232 __func__);
1233 return (0);
1236 return (1);
1240 download_rb_cmp(struct download *e1, struct download *e2)
1242 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1244 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1246 struct valid_url_types {
1247 char *type;
1248 } vut[] = {
1249 { "http://" },
1250 { "https://" },
1251 { "ftp://" },
1252 { "file://" },
1253 { XT_XTP_STR },
1257 valid_url_type(char *url)
1259 int i;
1261 for (i = 0; i < LENGTH(vut); i++)
1262 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1263 return (0);
1265 return (1);
1268 void
1269 print_cookie(char *msg, SoupCookie *c)
1271 if (c == NULL)
1272 return;
1274 if (msg)
1275 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1276 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1277 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1278 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1279 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1280 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1281 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1282 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1283 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1284 DNPRINTF(XT_D_COOKIE, "====================================\n");
1287 void
1288 walk_alias(struct settings *s,
1289 void (*cb)(struct settings *, char *, void *), void *cb_args)
1291 struct alias *a;
1292 char *str;
1294 if (s == NULL || cb == NULL) {
1295 show_oops_s("walk_alias invalid parameters");
1296 return;
1299 TAILQ_FOREACH(a, &aliases, entry) {
1300 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1301 cb(s, str, cb_args);
1302 g_free(str);
1306 char *
1307 match_alias(char *url_in)
1309 struct alias *a;
1310 char *arg;
1311 char *url_out = NULL, *search, *enc_arg;
1313 search = g_strdup(url_in);
1314 arg = search;
1315 if (strsep(&arg, " \t") == NULL) {
1316 show_oops_s("match_alias: NULL URL");
1317 goto done;
1320 TAILQ_FOREACH(a, &aliases, entry) {
1321 if (!strcmp(search, a->a_name))
1322 break;
1325 if (a != NULL) {
1326 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1327 a->a_name);
1328 if (arg != NULL) {
1329 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1330 url_out = g_strdup_printf(a->a_uri, enc_arg);
1331 g_free(enc_arg);
1332 } else
1333 url_out = g_strdup(a->a_uri);
1335 done:
1336 g_free(search);
1337 return (url_out);
1340 char *
1341 guess_url_type(char *url_in)
1343 struct stat sb;
1344 char *url_out = NULL, *enc_search = NULL;
1346 url_out = match_alias(url_in);
1347 if (url_out != NULL)
1348 return (url_out);
1350 if (guess_search) {
1352 * If there is no dot nor slash in the string and it isn't a
1353 * path to a local file and doesn't resolves to an IP, assume
1354 * that the user wants to search for the string.
1357 if (strchr(url_in, '.') == NULL &&
1358 strchr(url_in, '/') == NULL &&
1359 stat(url_in, &sb) != 0 &&
1360 gethostbyname(url_in) == NULL) {
1362 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1363 url_out = g_strdup_printf(search_string, enc_search);
1364 g_free(enc_search);
1365 return (url_out);
1369 /* XXX not sure about this heuristic */
1370 if (stat(url_in, &sb) == 0)
1371 url_out = g_strdup_printf("file://%s", url_in);
1372 else
1373 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1375 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1377 return (url_out);
1380 void
1381 load_uri(struct tab *t, gchar *uri)
1383 struct karg args;
1384 gchar *newuri = NULL;
1385 int i;
1387 if (uri == NULL)
1388 return;
1390 /* Strip leading spaces. */
1391 while(*uri && isspace(*uri))
1392 uri++;
1394 if (strlen(uri) == 0) {
1395 blank(t, NULL);
1396 return;
1399 if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
1400 for (i = 0; i < LENGTH(about_list); i++)
1401 if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name)) {
1402 bzero(&args, sizeof args);
1403 about_list[i].func(t, &args);
1404 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
1405 FALSE);
1406 return;
1408 show_oops(t, "invalid about page");
1409 return;
1412 if (valid_url_type(uri)) {
1413 newuri = guess_url_type(uri);
1414 uri = newuri;
1417 set_status(t, (char *)uri, XT_STATUS_LOADING);
1418 webkit_web_view_load_uri(t->wv, uri);
1420 if (newuri)
1421 g_free(newuri);
1424 const gchar *
1425 get_uri(WebKitWebView *wv)
1427 const gchar *uri;
1429 uri = webkit_web_view_get_uri(wv);
1431 if (uri && strlen(uri) > 0)
1432 return (uri);
1433 else
1434 return (NULL);
1438 add_alias(struct settings *s, char *line)
1440 char *l, *alias;
1441 struct alias *a = NULL;
1443 if (s == NULL || line == NULL) {
1444 show_oops_s("add_alias invalid parameters");
1445 return (1);
1448 l = line;
1449 a = g_malloc(sizeof(*a));
1451 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1452 show_oops_s("add_alias: incomplete alias definition");
1453 goto bad;
1455 if (strlen(alias) == 0 || strlen(l) == 0) {
1456 show_oops_s("add_alias: invalid alias definition");
1457 goto bad;
1460 a->a_name = g_strdup(alias);
1461 a->a_uri = g_strdup(l);
1463 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1465 TAILQ_INSERT_TAIL(&aliases, a, entry);
1467 return (0);
1468 bad:
1469 if (a)
1470 g_free(a);
1471 return (1);
1475 add_mime_type(struct settings *s, char *line)
1477 char *mime_type;
1478 char *l;
1479 struct mime_type *m = NULL;
1480 int downloadfirst = 0;
1482 /* XXX this could be smarter */
1484 if (line == NULL && strlen(line) == 0) {
1485 show_oops_s("add_mime_type invalid parameters");
1486 return (1);
1489 l = line;
1490 if (*l == '@') {
1491 downloadfirst = 1;
1492 l++;
1494 m = g_malloc(sizeof(*m));
1496 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1497 show_oops_s("add_mime_type: invalid mime_type");
1498 goto bad;
1500 if (mime_type[strlen(mime_type) - 1] == '*') {
1501 mime_type[strlen(mime_type) - 1] = '\0';
1502 m->mt_default = 1;
1503 } else
1504 m->mt_default = 0;
1506 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1507 show_oops_s("add_mime_type: invalid mime_type");
1508 goto bad;
1511 m->mt_type = g_strdup(mime_type);
1512 m->mt_action = g_strdup(l);
1513 m->mt_download = downloadfirst;
1515 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1516 m->mt_type, m->mt_action, m->mt_default);
1518 TAILQ_INSERT_TAIL(&mtl, m, entry);
1520 return (0);
1521 bad:
1522 if (m)
1523 g_free(m);
1524 return (1);
1527 struct mime_type *
1528 find_mime_type(char *mime_type)
1530 struct mime_type *m, *def = NULL, *rv = NULL;
1532 TAILQ_FOREACH(m, &mtl, entry) {
1533 if (m->mt_default &&
1534 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1535 def = m;
1537 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1538 rv = m;
1539 break;
1543 if (rv == NULL)
1544 rv = def;
1546 return (rv);
1549 void
1550 walk_mime_type(struct settings *s,
1551 void (*cb)(struct settings *, char *, void *), void *cb_args)
1553 struct mime_type *m;
1554 char *str;
1556 if (s == NULL || cb == NULL)
1557 show_oops_s("walk_mime_type invalid parameters");
1559 TAILQ_FOREACH(m, &mtl, entry) {
1560 str = g_strdup_printf("%s%s --> %s",
1561 m->mt_type,
1562 m->mt_default ? "*" : "",
1563 m->mt_action);
1564 cb(s, str, cb_args);
1565 g_free(str);
1569 void
1570 wl_add(char *str, struct domain_list *wl, int handy)
1572 struct domain *d;
1573 int add_dot = 0;
1575 if (str == NULL || wl == NULL || strlen(str) < 2)
1576 return;
1578 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1580 /* treat *.moo.com the same as .moo.com */
1581 if (str[0] == '*' && str[1] == '.')
1582 str = &str[1];
1583 else if (str[0] == '.')
1584 str = &str[0];
1585 else
1586 add_dot = 1;
1588 d = g_malloc(sizeof *d);
1589 if (add_dot)
1590 d->d = g_strdup_printf(".%s", str);
1591 else
1592 d->d = g_strdup(str);
1593 d->handy = handy;
1595 if (RB_INSERT(domain_list, wl, d))
1596 goto unwind;
1598 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1599 return;
1600 unwind:
1601 if (d) {
1602 if (d->d)
1603 g_free(d->d);
1604 g_free(d);
1609 add_cookie_wl(struct settings *s, char *entry)
1611 wl_add(entry, &c_wl, 1);
1612 return (0);
1615 void
1616 walk_cookie_wl(struct settings *s,
1617 void (*cb)(struct settings *, char *, void *), void *cb_args)
1619 struct domain *d;
1621 if (s == NULL || cb == NULL) {
1622 show_oops_s("walk_cookie_wl invalid parameters");
1623 return;
1626 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1627 cb(s, d->d, cb_args);
1630 void
1631 walk_js_wl(struct settings *s,
1632 void (*cb)(struct settings *, char *, void *), void *cb_args)
1634 struct domain *d;
1636 if (s == NULL || cb == NULL) {
1637 show_oops_s("walk_js_wl invalid parameters");
1638 return;
1641 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1642 cb(s, d->d, cb_args);
1646 add_js_wl(struct settings *s, char *entry)
1648 wl_add(entry, &js_wl, 1 /* persistent */);
1649 return (0);
1652 struct domain *
1653 wl_find(const gchar *search, struct domain_list *wl)
1655 int i;
1656 struct domain *d = NULL, dfind;
1657 gchar *s = NULL;
1659 if (search == NULL || wl == NULL)
1660 return (NULL);
1661 if (strlen(search) < 2)
1662 return (NULL);
1664 if (search[0] != '.')
1665 s = g_strdup_printf(".%s", search);
1666 else
1667 s = g_strdup(search);
1669 for (i = strlen(s) - 1; i >= 0; i--) {
1670 if (s[i] == '.') {
1671 dfind.d = &s[i];
1672 d = RB_FIND(domain_list, wl, &dfind);
1673 if (d)
1674 goto done;
1678 done:
1679 if (s)
1680 g_free(s);
1682 return (d);
1685 struct domain *
1686 wl_find_uri(const gchar *s, struct domain_list *wl)
1688 int i;
1689 char *ss;
1690 struct domain *r;
1692 if (s == NULL || wl == NULL)
1693 return (NULL);
1695 if (!strncmp(s, "http://", strlen("http://")))
1696 s = &s[strlen("http://")];
1697 else if (!strncmp(s, "https://", strlen("https://")))
1698 s = &s[strlen("https://")];
1700 if (strlen(s) < 2)
1701 return (NULL);
1703 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1704 /* chop string at first slash */
1705 if (s[i] == '/' || s[i] == '\0') {
1706 ss = g_strdup(s);
1707 ss[i] = '\0';
1708 r = wl_find(ss, wl);
1709 g_free(ss);
1710 return (r);
1713 return (NULL);
1716 char *
1717 get_toplevel_domain(char *domain)
1719 char *s;
1720 int found = 0;
1722 if (domain == NULL)
1723 return (NULL);
1724 if (strlen(domain) < 2)
1725 return (NULL);
1727 s = &domain[strlen(domain) - 1];
1728 while (s != domain) {
1729 if (*s == '.') {
1730 found++;
1731 if (found == 2)
1732 return (s);
1734 s--;
1737 if (found)
1738 return (domain);
1740 return (NULL);
1744 settings_add(char *var, char *val)
1746 int i, rv, *p;
1747 gfloat *f;
1748 char **s;
1750 /* get settings */
1751 for (i = 0, rv = 0; i < LENGTH(rs); i++) {
1752 if (strcmp(var, rs[i].name))
1753 continue;
1755 if (rs[i].s) {
1756 if (rs[i].s->set(&rs[i], val))
1757 errx(1, "invalid value for %s: %s", var, val);
1758 rv = 1;
1759 break;
1760 } else
1761 switch (rs[i].type) {
1762 case XT_S_INT:
1763 p = rs[i].ival;
1764 *p = atoi(val);
1765 rv = 1;
1766 break;
1767 case XT_S_STR:
1768 s = rs[i].sval;
1769 if (s == NULL)
1770 errx(1, "invalid sval for %s",
1771 rs[i].name);
1772 if (*s)
1773 g_free(*s);
1774 *s = g_strdup(val);
1775 rv = 1;
1776 break;
1777 case XT_S_FLOAT:
1778 f = rs[i].fval;
1779 *f = atof(val);
1780 rv = 1;
1781 break;
1782 case XT_S_INVALID:
1783 default:
1784 errx(1, "invalid type for %s", var);
1786 break;
1788 return (rv);
1791 #define WS "\n= \t"
1792 void
1793 config_parse(char *filename, int runtime)
1795 FILE *config, *f;
1796 char *line, *cp, *var, *val;
1797 size_t len, lineno = 0;
1798 int handled;
1799 char file[PATH_MAX];
1800 struct stat sb;
1802 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1804 if (filename == NULL)
1805 return;
1807 if (runtime && runtime_settings[0] != '\0') {
1808 snprintf(file, sizeof file, "%s/%s",
1809 work_dir, runtime_settings);
1810 if (stat(file, &sb)) {
1811 warnx("runtime file doesn't exist, creating it");
1812 if ((f = fopen(file, "w")) == NULL)
1813 err(1, "runtime");
1814 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1815 fclose(f);
1817 } else
1818 strlcpy(file, filename, sizeof file);
1820 if ((config = fopen(file, "r")) == NULL) {
1821 warn("config_parse: cannot open %s", filename);
1822 return;
1825 for (;;) {
1826 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1827 if (feof(config) || ferror(config))
1828 break;
1830 cp = line;
1831 cp += (long)strspn(cp, WS);
1832 if (cp[0] == '\0') {
1833 /* empty line */
1834 free(line);
1835 continue;
1838 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1839 errx(1, "invalid config file entry: %s", line);
1841 cp += (long)strspn(cp, WS);
1843 if ((val = strsep(&cp, "\0")) == NULL)
1844 break;
1846 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1847 handled = settings_add(var, val);
1848 if (handled == 0)
1849 errx(1, "invalid conf file entry: %s=%s", var, val);
1851 free(line);
1854 fclose(config);
1857 char *
1858 js_ref_to_string(JSContextRef context, JSValueRef ref)
1860 char *s = NULL;
1861 size_t l;
1862 JSStringRef jsref;
1864 jsref = JSValueToStringCopy(context, ref, NULL);
1865 if (jsref == NULL)
1866 return (NULL);
1868 l = JSStringGetMaximumUTF8CStringSize(jsref);
1869 s = g_malloc(l);
1870 if (s)
1871 JSStringGetUTF8CString(jsref, s, l);
1872 JSStringRelease(jsref);
1874 return (s);
1877 void
1878 disable_hints(struct tab *t)
1880 bzero(t->hint_buf, sizeof t->hint_buf);
1881 bzero(t->hint_num, sizeof t->hint_num);
1882 run_script(t, "vimprobable_clear()");
1883 t->hints_on = 0;
1884 t->hint_mode = XT_HINT_NONE;
1887 void
1888 enable_hints(struct tab *t)
1890 bzero(t->hint_buf, sizeof t->hint_buf);
1891 run_script(t, "vimprobable_show_hints()");
1892 t->hints_on = 1;
1893 t->hint_mode = XT_HINT_NONE;
1896 #define XT_JS_OPEN ("open;")
1897 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1898 #define XT_JS_FIRE ("fire;")
1899 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1900 #define XT_JS_FOUND ("found;")
1901 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1904 run_script(struct tab *t, char *s)
1906 JSGlobalContextRef ctx;
1907 WebKitWebFrame *frame;
1908 JSStringRef str;
1909 JSValueRef val, exception;
1910 char *es, buf[128];
1912 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1913 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1915 frame = webkit_web_view_get_main_frame(t->wv);
1916 ctx = webkit_web_frame_get_global_context(frame);
1918 str = JSStringCreateWithUTF8CString(s);
1919 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1920 NULL, 0, &exception);
1921 JSStringRelease(str);
1923 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1924 if (val == NULL) {
1925 es = js_ref_to_string(ctx, exception);
1926 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1927 g_free(es);
1928 return (1);
1929 } else {
1930 es = js_ref_to_string(ctx, val);
1931 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1933 /* handle return value right here */
1934 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1935 disable_hints(t);
1936 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1939 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1940 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1941 &es[XT_JS_FIRE_LEN]);
1942 run_script(t, buf);
1943 disable_hints(t);
1946 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1947 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1948 disable_hints(t);
1951 g_free(es);
1954 return (0);
1958 hint(struct tab *t, struct karg *args)
1961 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1963 if (t->hints_on == 0)
1964 enable_hints(t);
1965 else
1966 disable_hints(t);
1968 return (0);
1971 void
1972 apply_style(struct tab *t)
1974 g_object_set(G_OBJECT(t->settings),
1975 "user-stylesheet-uri", t->stylesheet, (char *)NULL);
1979 userstyle(struct tab *t, struct karg *args)
1981 DNPRINTF(XT_D_JS, "userstyle: tab %d\n", t->tab_id);
1983 if (t->styled) {
1984 t->styled = 0;
1985 g_object_set(G_OBJECT(t->settings),
1986 "user-stylesheet-uri", NULL, (char *)NULL);
1987 } else {
1988 t->styled = 1;
1989 apply_style(t);
1991 return (0);
1995 * Doesn't work fully, due to the following bug:
1996 * https://bugs.webkit.org/show_bug.cgi?id=51747
1999 restore_global_history(void)
2001 char file[PATH_MAX];
2002 FILE *f;
2003 struct history *h;
2004 gchar *uri;
2005 gchar *title;
2006 const char delim[3] = {'\\', '\\', '\0'};
2008 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2010 if ((f = fopen(file, "r")) == NULL) {
2011 warnx("%s: fopen", __func__);
2012 return (1);
2015 for (;;) {
2016 if ((uri = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2017 if (feof(f) || ferror(f))
2018 break;
2020 if ((title = fparseln(f, NULL, NULL, delim, 0)) == NULL)
2021 if (feof(f) || ferror(f)) {
2022 free(uri);
2023 warnx("%s: broken history file\n", __func__);
2024 return (1);
2027 if (uri && strlen(uri) && title && strlen(title)) {
2028 webkit_web_history_item_new_with_data(uri, title);
2029 h = g_malloc(sizeof(struct history));
2030 h->uri = g_strdup(uri);
2031 h->title = g_strdup(title);
2032 RB_INSERT(history_list, &hl, h);
2033 completion_add_uri(h->uri);
2034 } else {
2035 warnx("%s: failed to restore history\n", __func__);
2036 free(uri);
2037 free(title);
2038 return (1);
2041 free(uri);
2042 free(title);
2043 uri = NULL;
2044 title = NULL;
2047 return (0);
2051 save_global_history_to_disk(struct tab *t)
2053 char file[PATH_MAX];
2054 FILE *f;
2055 struct history *h;
2057 snprintf(file, sizeof file, "%s/%s", work_dir, XT_HISTORY_FILE);
2059 if ((f = fopen(file, "w")) == NULL) {
2060 show_oops(t, "%s: global history file: %s",
2061 __func__, strerror(errno));
2062 return (1);
2065 RB_FOREACH_REVERSE(h, history_list, &hl) {
2066 if (h->uri && h->title)
2067 fprintf(f, "%s\n%s\n", h->uri, h->title);
2070 fclose(f);
2072 return (0);
2076 quit(struct tab *t, struct karg *args)
2078 if (save_global_history)
2079 save_global_history_to_disk(t);
2081 gtk_main_quit();
2083 return (1);
2087 open_tabs(struct tab *t, struct karg *a)
2089 char file[PATH_MAX];
2090 FILE *f = NULL;
2091 char *uri = NULL;
2092 int rv = 1;
2093 struct tab *ti, *tt;
2095 if (a == NULL)
2096 goto done;
2098 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2099 if ((f = fopen(file, "r")) == NULL)
2100 goto done;
2102 ti = TAILQ_LAST(&tabs, tab_list);
2104 for (;;) {
2105 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
2106 if (feof(f) || ferror(f))
2107 break;
2109 /* retrieve session name */
2110 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
2111 strlcpy(named_session,
2112 &uri[strlen(XT_SAVE_SESSION_ID)],
2113 sizeof named_session);
2114 continue;
2117 if (uri && strlen(uri))
2118 create_new_tab(uri, NULL, 1, -1);
2120 free(uri);
2121 uri = NULL;
2124 /* close open tabs */
2125 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
2126 for (;;) {
2127 tt = TAILQ_FIRST(&tabs);
2128 if (tt != ti) {
2129 delete_tab(tt);
2130 continue;
2132 delete_tab(tt);
2133 break;
2137 rv = 0;
2138 done:
2139 if (f)
2140 fclose(f);
2142 return (rv);
2146 restore_saved_tabs(void)
2148 char file[PATH_MAX];
2149 int unlink_file = 0;
2150 struct stat sb;
2151 struct karg a;
2152 int rv = 0;
2154 snprintf(file, sizeof file, "%s/%s",
2155 sessions_dir, XT_RESTART_TABS_FILE);
2156 if (stat(file, &sb) == -1)
2157 a.s = XT_SAVED_TABS_FILE;
2158 else {
2159 unlink_file = 1;
2160 a.s = XT_RESTART_TABS_FILE;
2163 a.i = XT_SES_DONOTHING;
2164 rv = open_tabs(NULL, &a);
2166 if (unlink_file)
2167 unlink(file);
2169 return (rv);
2173 save_tabs(struct tab *t, struct karg *a)
2175 char file[PATH_MAX];
2176 FILE *f;
2177 struct tab *ti;
2178 const gchar *uri;
2179 int len = 0, i;
2180 const gchar **arr = NULL;
2182 if (a == NULL)
2183 return (1);
2184 if (a->s == NULL)
2185 snprintf(file, sizeof file, "%s/%s",
2186 sessions_dir, named_session);
2187 else
2188 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
2190 if ((f = fopen(file, "w")) == NULL) {
2191 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
2192 return (1);
2195 /* save session name */
2196 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
2198 /* save tabs, in the order they are arranged in the notebook */
2199 TAILQ_FOREACH(ti, &tabs, entry)
2200 len++;
2202 arr = g_malloc0(len * sizeof(gchar *));
2204 TAILQ_FOREACH(ti, &tabs, entry) {
2205 if ((uri = get_uri(ti->wv)) != NULL)
2206 arr[gtk_notebook_page_num(notebook, ti->vbox)] = uri;
2209 for (i = 0; i < len; i++)
2210 if (arr[i])
2211 fprintf(f, "%s\n", arr[i]);
2213 g_free(arr);
2214 /* try and make sure this gets to disk NOW. XXX Backup first? */
2215 if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
2216 show_oops(t, "May not have managed to save session: %s",
2217 strerror(errno));
2220 fclose(f);
2222 return (0);
2226 save_tabs_and_quit(struct tab *t, struct karg *args)
2228 struct karg a;
2230 a.s = NULL;
2231 save_tabs(t, &a);
2232 quit(t, NULL);
2234 return (1);
2238 yank_uri(struct tab *t, struct karg *args)
2240 const gchar *uri;
2241 GtkClipboard *clipboard;
2243 if ((uri = get_uri(t->wv)) == NULL)
2244 return (1);
2246 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2247 gtk_clipboard_set_text(clipboard, uri, -1);
2249 return (0);
2253 paste_uri(struct tab *t, struct karg *args)
2255 GtkClipboard *clipboard;
2256 GdkAtom atom = gdk_atom_intern("CUT_BUFFER0", FALSE);
2257 gint len;
2258 gchar *p = NULL, *uri;
2260 /* try primary clipboard first */
2261 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2262 p = gtk_clipboard_wait_for_text(clipboard);
2264 /* if it failed get whatever text is in cut_buffer0 */
2265 if (p == NULL)
2266 if (gdk_property_get(gdk_get_default_root_window(),
2267 atom,
2268 gdk_atom_intern("STRING", FALSE),
2270 65536 /* picked out of my butt */,
2271 FALSE,
2272 NULL,
2273 NULL,
2274 &len,
2275 (guchar **)&p)) {
2276 /* yes sir, we need to NUL the string */
2277 p[len] = '\0';
2280 if (p) {
2281 uri = p;
2282 while(*uri && isspace(*uri))
2283 uri++;
2284 if (strlen(uri) == 0) {
2285 show_oops(t, "empty paste buffer");
2286 goto done;
2288 if (valid_url_type(uri)) {
2289 /* we can be clever and paste this in search box */
2290 show_oops(t, "not a valid URL");
2291 goto done;
2294 if (args->i == XT_PASTE_CURRENT_TAB)
2295 load_uri(t, uri);
2296 else if (args->i == XT_PASTE_NEW_TAB)
2297 create_new_tab(uri, NULL, 1, -1);
2300 done:
2301 if (p)
2302 g_free(p);
2304 return (0);
2307 char *
2308 find_domain(const gchar *s, int add_dot)
2310 int i;
2311 char *r = NULL, *ss = NULL;
2313 if (s == NULL)
2314 return (NULL);
2316 if (!strncmp(s, "http://", strlen("http://")))
2317 s = &s[strlen("http://")];
2318 else if (!strncmp(s, "https://", strlen("https://")))
2319 s = &s[strlen("https://")];
2321 if (strlen(s) < 2)
2322 return (NULL);
2324 ss = g_strdup(s);
2325 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2326 /* chop string at first slash */
2327 if (ss[i] == '/' || ss[i] == '\0') {
2328 ss[i] = '\0';
2329 if (add_dot)
2330 r = g_strdup_printf(".%s", ss);
2331 else
2332 r = g_strdup(ss);
2333 break;
2335 g_free(ss);
2337 return (r);
2341 toggle_cwl(struct tab *t, struct karg *args)
2343 struct domain *d;
2344 const gchar *uri;
2345 char *dom = NULL, *dom_toggle = NULL;
2346 int es;
2348 if (args == NULL)
2349 return (1);
2351 uri = get_uri(t->wv);
2352 dom = find_domain(uri, 1);
2353 d = wl_find(dom, &c_wl);
2355 if (d == NULL)
2356 es = 0;
2357 else
2358 es = 1;
2360 if (args->i & XT_WL_TOGGLE)
2361 es = !es;
2362 else if ((args->i & XT_WL_ENABLE) && es != 1)
2363 es = 1;
2364 else if ((args->i & XT_WL_DISABLE) && es != 0)
2365 es = 0;
2367 if (args->i & XT_WL_TOPLEVEL)
2368 dom_toggle = get_toplevel_domain(dom);
2369 else
2370 dom_toggle = dom;
2372 if (es)
2373 /* enable cookies for domain */
2374 wl_add(dom_toggle, &c_wl, 0);
2375 else
2376 /* disable cookies for domain */
2377 RB_REMOVE(domain_list, &c_wl, d);
2379 if (args->i & XT_WL_RELOAD)
2380 webkit_web_view_reload(t->wv);
2382 g_free(dom);
2383 return (0);
2387 toggle_js(struct tab *t, struct karg *args)
2389 int es;
2390 const gchar *uri;
2391 struct domain *d;
2392 char *dom = NULL, *dom_toggle = NULL;
2394 if (args == NULL)
2395 return (1);
2397 g_object_get(G_OBJECT(t->settings),
2398 "enable-scripts", &es, (char *)NULL);
2399 if (args->i & XT_WL_TOGGLE)
2400 es = !es;
2401 else if ((args->i & XT_WL_ENABLE) && es != 1)
2402 es = 1;
2403 else if ((args->i & XT_WL_DISABLE) && es != 0)
2404 es = 0;
2405 else
2406 return (1);
2408 uri = get_uri(t->wv);
2409 dom = find_domain(uri, 1);
2411 if (uri == NULL || dom == NULL) {
2412 show_oops(t, "Can't toggle domain in JavaScript white list");
2413 goto done;
2416 if (args->i & XT_WL_TOPLEVEL)
2417 dom_toggle = get_toplevel_domain(dom);
2418 else
2419 dom_toggle = dom;
2421 if (es) {
2422 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2423 wl_add(dom_toggle, &js_wl, 0 /* session */);
2424 } else {
2425 d = wl_find(dom_toggle, &js_wl);
2426 if (d)
2427 RB_REMOVE(domain_list, &js_wl, d);
2428 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2430 g_object_set(G_OBJECT(t->settings),
2431 "enable-scripts", es, (char *)NULL);
2432 g_object_set(G_OBJECT(t->settings),
2433 "javascript-can-open-windows-automatically", es, (char *)NULL);
2434 webkit_web_view_set_settings(t->wv, t->settings);
2436 if (args->i & XT_WL_RELOAD)
2437 webkit_web_view_reload(t->wv);
2438 done:
2439 if (dom)
2440 g_free(dom);
2441 return (0);
2444 void
2445 js_toggle_cb(GtkWidget *w, struct tab *t)
2447 struct karg a;
2449 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL;
2450 toggle_cwl(t, &a);
2452 a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
2453 toggle_js(t, &a);
2457 toggle_src(struct tab *t, struct karg *args)
2459 gboolean mode;
2461 if (t == NULL)
2462 return (0);
2464 mode = webkit_web_view_get_view_source_mode(t->wv);
2465 webkit_web_view_set_view_source_mode(t->wv, !mode);
2466 webkit_web_view_reload(t->wv);
2468 return (0);
2471 void
2472 focus_webview(struct tab *t)
2474 if (t == NULL)
2475 return;
2477 /* only grab focus if we are visible */
2478 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2479 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2483 focus(struct tab *t, struct karg *args)
2485 if (t == NULL || args == NULL)
2486 return (1);
2488 if (show_url == 0)
2489 return (0);
2491 if (args->i == XT_FOCUS_URI)
2492 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2493 else if (args->i == XT_FOCUS_SEARCH)
2494 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2496 return (0);
2500 stats(struct tab *t, struct karg *args)
2502 char *page, *body, *s, line[64 * 1024];
2503 uint64_t line_count = 0;
2504 FILE *r_cookie_f;
2506 if (t == NULL)
2507 show_oops_s("stats invalid parameters");
2509 line[0] = '\0';
2510 if (save_rejected_cookies) {
2511 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2512 for (;;) {
2513 s = fgets(line, sizeof line, r_cookie_f);
2514 if (s == NULL || feof(r_cookie_f) ||
2515 ferror(r_cookie_f))
2516 break;
2517 line_count++;
2519 fclose(r_cookie_f);
2520 snprintf(line, sizeof line,
2521 "<br/>Cookies blocked(*) total: %llu", line_count);
2522 } else
2523 show_oops(t, "Can't open blocked cookies file: %s",
2524 strerror(errno));
2527 body = g_strdup_printf(
2528 "Cookies blocked(*) this session: %llu"
2529 "%s"
2530 "<p><small><b>*</b> results vary based on settings</small></p>",
2531 blocked_cookies,
2532 line);
2534 page = get_html_page("Statistics", body, "", 0);
2535 g_free(body);
2537 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
2538 g_free(page);
2540 return (0);
2544 marco(struct tab *t, struct karg *args)
2546 char *page, line[64 * 1024];
2547 int len;
2549 if (t == NULL)
2550 show_oops_s("marco invalid parameters");
2552 line[0] = '\0';
2553 snprintf(line, sizeof line, "%s", marco_message(&len));
2555 page = get_html_page("Marco Sez...", line, "", 0);
2557 load_webkit_string(t, page, XT_URI_ABOUT_MARCO);
2558 g_free(page);
2560 return (0);
2564 blank(struct tab *t, struct karg *args)
2566 if (t == NULL)
2567 show_oops_s("blank invalid parameters");
2569 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
2571 return (0);
2574 about(struct tab *t, struct karg *args)
2576 char *page, *body;
2578 if (t == NULL)
2579 show_oops_s("about invalid parameters");
2581 body = g_strdup_printf("<b>Version: %s</b><p>"
2582 "Authors:"
2583 "<ul>"
2584 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2585 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2586 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2587 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2588 "<li>Raphael Graf &lt;r@undefined.ch&gt; </li>"
2589 "</ul>"
2590 "Copyrights and licenses can be found on the XXXterm "
2591 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>",
2592 version
2595 page = get_html_page("About", body, "", 0);
2596 g_free(body);
2598 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
2599 g_free(page);
2601 return (0);
2605 help(struct tab *t, struct karg *args)
2607 char *page, *head, *body;
2609 if (t == NULL)
2610 show_oops_s("help invalid parameters");
2612 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
2613 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2614 "</head>\n";
2615 body = "XXXterm man page <a href=\"http://opensource.conformal.com/"
2616 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2617 "cgi-bin/man-cgi?xxxterm</a>";
2619 page = get_html_page("XXXterm", body, head, FALSE);
2621 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
2622 g_free(page);
2624 return (0);
2628 * update all favorite tabs apart from one. Pass NULL if
2629 * you want to update all.
2631 void
2632 update_favorite_tabs(struct tab *apart_from)
2634 struct tab *t;
2635 if (!updating_fl_tabs) {
2636 updating_fl_tabs = 1; /* stop infinite recursion */
2637 TAILQ_FOREACH(t, &tabs, entry)
2638 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2639 && (t != apart_from))
2640 xtp_page_fl(t, NULL);
2641 updating_fl_tabs = 0;
2645 /* show a list of favorites (bookmarks) */
2647 xtp_page_fl(struct tab *t, struct karg *args)
2649 char file[PATH_MAX];
2650 FILE *f;
2651 char *uri = NULL, *title = NULL;
2652 size_t len, lineno = 0;
2653 int i, failed = 0;
2654 char *body, *tmp, *page = NULL;
2655 const char delim[3] = {'\\', '\\', '\0'};
2657 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2659 if (t == NULL)
2660 warn("%s: bad param", __func__);
2662 /* mark tab as favorite list */
2663 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2665 /* new session key */
2666 if (!updating_fl_tabs)
2667 generate_xtp_session_key(&fl_session_key);
2669 /* open favorites */
2670 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
2671 if ((f = fopen(file, "r")) == NULL) {
2672 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2673 return (1);
2676 /* body */
2677 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
2678 "<th style='width: 40px'>&#35;</th><th>Link</th>"
2679 "<th style='width: 40px'>Rm</th></tr>\n");
2681 for (i = 1;;) {
2682 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2683 if (feof(f) || ferror(f))
2684 break;
2685 if (len == 0) {
2686 free(title);
2687 title = NULL;
2688 continue;
2691 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
2692 if (feof(f) || ferror(f)) {
2693 show_oops(t, "favorites file corrupt");
2694 failed = 1;
2695 break;
2698 tmp = body;
2699 body = g_strdup_printf("%s<tr>"
2700 "<td>%d</td>"
2701 "<td><a href='%s'>%s</a></td>"
2702 "<td style='text-align: center'>"
2703 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2704 "</tr>\n",
2705 body, i, uri, title,
2706 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2708 g_free(tmp);
2710 free(uri);
2711 uri = NULL;
2712 free(title);
2713 title = NULL;
2714 i++;
2716 fclose(f);
2718 /* if none, say so */
2719 if (i == 1) {
2720 tmp = body;
2721 body = g_strdup_printf("%s<tr>"
2722 "<td colspan='3' style='text-align: center'>"
2723 "No favorites - To add one use the 'favadd' command."
2724 "</td></tr>", body);
2725 g_free(tmp);
2728 tmp = body;
2729 body = g_strdup_printf("%s</table>", body);
2730 g_free(tmp);
2732 if (uri)
2733 free(uri);
2734 if (title)
2735 free(title);
2737 /* render */
2738 if (!failed) {
2739 page = get_html_page("Favorites", body, "", 1);
2740 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
2741 g_free(page);
2744 update_favorite_tabs(t);
2746 if (body)
2747 g_free(body);
2749 return (failed);
2752 void
2753 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2754 size_t cert_count, char *title)
2756 gnutls_datum_t cinfo;
2757 char *tmp, *body;
2758 int i;
2760 body = g_strdup("");
2762 for (i = 0; i < cert_count; i++) {
2763 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2764 &cinfo))
2765 return;
2767 tmp = body;
2768 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2769 body, i, cinfo.data);
2770 gnutls_free(cinfo.data);
2771 g_free(tmp);
2774 tmp = get_html_page(title, body, "", 0);
2775 g_free(body);
2777 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
2778 g_free(tmp);
2782 ca_cmd(struct tab *t, struct karg *args)
2784 FILE *f = NULL;
2785 int rv = 1, certs = 0, certs_read;
2786 struct stat sb;
2787 gnutls_datum dt;
2788 gnutls_x509_crt_t *c = NULL;
2789 char *certs_buf = NULL, *s;
2791 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2792 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
2793 return (1);
2796 if (fstat(fileno(f), &sb) == -1) {
2797 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
2798 goto done;
2801 certs_buf = g_malloc(sb.st_size + 1);
2802 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2803 show_oops(t, "Can't read CA file: %s", strerror(errno));
2804 goto done;
2806 certs_buf[sb.st_size] = '\0';
2808 s = certs_buf;
2809 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2810 certs++;
2811 s += strlen("BEGIN CERTIFICATE");
2814 bzero(&dt, sizeof dt);
2815 dt.data = certs_buf;
2816 dt.size = sb.st_size;
2817 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2818 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
2819 GNUTLS_X509_FMT_PEM, 0);
2820 if (certs_read <= 0) {
2821 show_oops(t, "No cert(s) available");
2822 goto done;
2824 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2825 done:
2826 if (c)
2827 g_free(c);
2828 if (certs_buf)
2829 g_free(certs_buf);
2830 if (f)
2831 fclose(f);
2833 return (rv);
2837 connect_socket_from_uri(const gchar *uri, char *domain, size_t domain_sz)
2839 SoupURI *su = NULL;
2840 struct addrinfo hints, *res = NULL, *ai;
2841 int s = -1, on;
2842 char port[8];
2844 if (uri && !g_str_has_prefix(uri, "https://"))
2845 goto done;
2847 su = soup_uri_new(uri);
2848 if (su == NULL)
2849 goto done;
2850 if (!SOUP_URI_VALID_FOR_HTTP(su))
2851 goto done;
2853 snprintf(port, sizeof port, "%d", su->port);
2854 bzero(&hints, sizeof(struct addrinfo));
2855 hints.ai_flags = AI_CANONNAME;
2856 hints.ai_family = AF_UNSPEC;
2857 hints.ai_socktype = SOCK_STREAM;
2859 if (getaddrinfo(su->host, port, &hints, &res))
2860 goto done;
2862 for (ai = res; ai; ai = ai->ai_next) {
2863 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2864 continue;
2866 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2867 if (s < 0)
2868 goto done;
2869 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2870 sizeof(on)) == -1)
2871 goto done;
2873 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2874 goto done;
2877 if (domain)
2878 strlcpy(domain, su->host, domain_sz);
2879 done:
2880 if (su)
2881 soup_uri_free(su);
2882 if (res)
2883 freeaddrinfo(res);
2885 return (s);
2889 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2891 if (gsession)
2892 gnutls_deinit(gsession);
2893 if (xcred)
2894 gnutls_certificate_free_credentials(xcred);
2896 return (0);
2900 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2901 gnutls_certificate_credentials_t *xc)
2903 gnutls_certificate_credentials_t xcred;
2904 gnutls_session_t gsession;
2905 int rv = 1;
2907 if (gs == NULL || xc == NULL)
2908 goto done;
2910 *gs = NULL;
2911 *xc = NULL;
2913 gnutls_certificate_allocate_credentials(&xcred);
2914 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2915 GNUTLS_X509_FMT_PEM);
2916 gnutls_init(&gsession, GNUTLS_CLIENT);
2917 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2918 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2919 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2920 if ((rv = gnutls_handshake(gsession)) < 0) {
2921 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2923 gnutls_error_is_fatal(rv),
2924 gnutls_strerror_name(rv));
2925 stop_tls(gsession, xcred);
2926 goto done;
2929 gnutls_credentials_type_t cred;
2930 cred = gnutls_auth_get_type(gsession);
2931 if (cred != GNUTLS_CRD_CERTIFICATE) {
2932 stop_tls(gsession, xcred);
2933 goto done;
2936 *gs = gsession;
2937 *xc = xcred;
2938 rv = 0;
2939 done:
2940 return (rv);
2944 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2945 size_t *cert_count)
2947 unsigned int len;
2948 const gnutls_datum_t *cl;
2949 gnutls_x509_crt_t *all_certs;
2950 int i, rv = 1;
2952 if (certs == NULL || cert_count == NULL)
2953 goto done;
2954 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2955 goto done;
2956 cl = gnutls_certificate_get_peers(gsession, &len);
2957 if (len == 0)
2958 goto done;
2960 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2961 for (i = 0; i < len; i++) {
2962 gnutls_x509_crt_init(&all_certs[i]);
2963 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2964 GNUTLS_X509_FMT_PEM < 0)) {
2965 g_free(all_certs);
2966 goto done;
2970 *certs = all_certs;
2971 *cert_count = len;
2972 rv = 0;
2973 done:
2974 return (rv);
2977 void
2978 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2980 int i;
2982 for (i = 0; i < cert_count; i++)
2983 gnutls_x509_crt_deinit(certs[i]);
2984 g_free(certs);
2987 void
2988 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2989 size_t cert_count, char *domain)
2991 size_t cert_buf_sz;
2992 char cert_buf[64 * 1024], file[PATH_MAX];
2993 int i;
2994 FILE *f;
2995 GdkColor color;
2997 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2998 return;
3000 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3001 if ((f = fopen(file, "w")) == NULL) {
3002 show_oops(t, "Can't create cert file %s %s",
3003 file, strerror(errno));
3004 return;
3007 for (i = 0; i < cert_count; i++) {
3008 cert_buf_sz = sizeof cert_buf;
3009 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3010 cert_buf, &cert_buf_sz)) {
3011 show_oops(t, "gnutls_x509_crt_export failed");
3012 goto done;
3014 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
3015 show_oops(t, "Can't write certs: %s", strerror(errno));
3016 goto done;
3020 /* not the best spot but oh well */
3021 gdk_color_parse("lightblue", &color);
3022 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
3023 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
3024 gdk_color_parse(XT_COLOR_BLACK, &color);
3025 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
3026 done:
3027 fclose(f);
3031 load_compare_cert(struct tab *t, struct karg *args)
3033 const gchar *uri;
3034 char domain[8182], file[PATH_MAX];
3035 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
3036 int s = -1, rv = 1, i;
3037 size_t cert_count;
3038 FILE *f = NULL;
3039 size_t cert_buf_sz;
3040 gnutls_session_t gsession;
3041 gnutls_x509_crt_t *certs;
3042 gnutls_certificate_credentials_t xcred;
3044 if (t == NULL)
3045 return (1);
3047 if ((uri = get_uri(t->wv)) == NULL)
3048 return (1);
3050 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
3051 return (1);
3053 /* go ssl/tls */
3054 if (start_tls(t, s, &gsession, &xcred)) {
3055 show_oops(t, "Start TLS failed");
3056 goto done;
3059 /* get certs */
3060 if (get_connection_certs(gsession, &certs, &cert_count)) {
3061 show_oops(t, "Can't get connection certificates");
3062 goto done;
3065 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
3066 if ((f = fopen(file, "r")) == NULL)
3067 goto freeit;
3069 for (i = 0; i < cert_count; i++) {
3070 cert_buf_sz = sizeof cert_buf;
3071 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
3072 cert_buf, &cert_buf_sz)) {
3073 goto freeit;
3075 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
3076 rv = -1; /* critical */
3077 goto freeit;
3079 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
3080 rv = -1; /* critical */
3081 goto freeit;
3085 rv = 0;
3086 freeit:
3087 if (f)
3088 fclose(f);
3089 free_connection_certs(certs, cert_count);
3090 done:
3091 /* we close the socket first for speed */
3092 if (s != -1)
3093 close(s);
3094 stop_tls(gsession, xcred);
3096 return (rv);
3100 cert_cmd(struct tab *t, struct karg *args)
3102 const gchar *uri;
3103 char domain[8182];
3104 int s = -1;
3105 size_t cert_count;
3106 gnutls_session_t gsession;
3107 gnutls_x509_crt_t *certs;
3108 gnutls_certificate_credentials_t xcred;
3110 if (t == NULL)
3111 return (1);
3113 if (ssl_ca_file == NULL) {
3114 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
3115 return (1);
3118 if ((uri = get_uri(t->wv)) == NULL) {
3119 show_oops(t, "Invalid URI");
3120 return (1);
3123 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
3124 show_oops(t, "Invalid certificate URI: %s", uri);
3125 return (1);
3128 /* go ssl/tls */
3129 if (start_tls(t, s, &gsession, &xcred)) {
3130 show_oops(t, "Start TLS failed");
3131 goto done;
3134 /* get certs */
3135 if (get_connection_certs(gsession, &certs, &cert_count)) {
3136 show_oops(t, "get_connection_certs failed");
3137 goto done;
3140 if (args->i & XT_SHOW)
3141 show_certs(t, certs, cert_count, "Certificate Chain");
3142 else if (args->i & XT_SAVE)
3143 save_certs(t, certs, cert_count, domain);
3145 free_connection_certs(certs, cert_count);
3146 done:
3147 /* we close the socket first for speed */
3148 if (s != -1)
3149 close(s);
3150 stop_tls(gsession, xcred);
3152 return (0);
3156 remove_cookie(int index)
3158 int i, rv = 1;
3159 GSList *cf;
3160 SoupCookie *c;
3162 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
3164 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3166 for (i = 1; cf; cf = cf->next, i++) {
3167 if (i != index)
3168 continue;
3169 c = cf->data;
3170 print_cookie("remove cookie", c);
3171 soup_cookie_jar_delete_cookie(s_cookiejar, c);
3172 rv = 0;
3173 break;
3176 soup_cookies_free(cf);
3178 return (rv);
3182 wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
3184 struct domain *d;
3185 char *tmp, *body;
3187 body = g_strdup("");
3189 /* p list */
3190 if (args->i & XT_WL_PERSISTENT) {
3191 tmp = body;
3192 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
3193 g_free(tmp);
3194 RB_FOREACH(d, domain_list, wl) {
3195 if (d->handy == 0)
3196 continue;
3197 tmp = body;
3198 body = g_strdup_printf("%s%s<br/>", body, d->d);
3199 g_free(tmp);
3203 /* s list */
3204 if (args->i & XT_WL_SESSION) {
3205 tmp = body;
3206 body = g_strdup_printf("%s<h2>Session</h2>", body);
3207 g_free(tmp);
3208 RB_FOREACH(d, domain_list, wl) {
3209 if (d->handy == 1)
3210 continue;
3211 tmp = body;
3212 body = g_strdup_printf("%s%s<br/>", body, d->d);
3213 g_free(tmp);
3217 tmp = get_html_page(title, body, "", 0);
3218 g_free(body);
3219 if (wl == &js_wl)
3220 load_webkit_string(t, tmp, XT_URI_ABOUT_JSWL);
3221 else
3222 load_webkit_string(t, tmp, XT_URI_ABOUT_COOKIEWL);
3223 g_free(tmp);
3224 return (0);
3228 wl_save(struct tab *t, struct karg *args, int js)
3230 char file[PATH_MAX];
3231 FILE *f;
3232 char *line = NULL, *lt = NULL;
3233 size_t linelen;
3234 const gchar *uri;
3235 char *dom = NULL, *dom_save = NULL;
3236 struct karg a;
3237 struct domain *d;
3238 GSList *cf;
3239 SoupCookie *ci, *c;
3241 if (t == NULL || args == NULL)
3242 return (1);
3244 if (runtime_settings[0] == '\0')
3245 return (1);
3247 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
3248 if ((f = fopen(file, "r+")) == NULL)
3249 return (1);
3251 uri = get_uri(t->wv);
3252 dom = find_domain(uri, 1);
3253 if (uri == NULL || dom == NULL) {
3254 show_oops(t, "Can't add domain to %s white list",
3255 js ? "JavaScript" : "cookie");
3256 goto done;
3259 if (args->i & XT_WL_TOPLEVEL) {
3260 /* save domain */
3261 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
3262 show_oops(t, "invalid domain: %s", dom);
3263 goto done;
3265 } else if (args->i & XT_WL_FQDN) {
3266 /* save fqdn */
3267 dom_save = dom;
3268 } else
3269 goto done;
3271 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
3273 while (!feof(f)) {
3274 line = fparseln(f, &linelen, NULL, NULL, 0);
3275 if (line == NULL)
3276 continue;
3277 if (!strcmp(line, lt))
3278 goto done;
3279 free(line);
3280 line = NULL;
3283 fprintf(f, "%s\n", lt);
3285 a.i = XT_WL_ENABLE;
3286 a.i |= args->i;
3287 if (js) {
3288 d = wl_find(dom_save, &js_wl);
3289 if (!d) {
3290 settings_add("js_wl", dom_save);
3291 d = wl_find(dom_save, &js_wl);
3293 toggle_js(t, &a);
3294 } else {
3295 d = wl_find(dom_save, &c_wl);
3296 if (!d) {
3297 settings_add("cookie_wl", dom_save);
3298 d = wl_find(dom_save, &c_wl);
3300 toggle_cwl(t, &a);
3302 /* find and add to persistent jar */
3303 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3304 for (;cf; cf = cf->next) {
3305 ci = cf->data;
3306 if (!strcmp(dom_save, ci->domain) ||
3307 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3308 c = soup_cookie_copy(ci);
3309 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3312 soup_cookies_free(cf);
3314 if (d)
3315 d->handy = 1;
3317 done:
3318 if (line)
3319 free(line);
3320 if (dom)
3321 g_free(dom);
3322 if (lt)
3323 g_free(lt);
3324 fclose(f);
3326 return (0);
3330 js_show_wl(struct tab *t, struct karg *args)
3332 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3333 wl_show(t, args, "JavaScript White List", &js_wl);
3335 return (0);
3339 cookie_show_wl(struct tab *t, struct karg *args)
3341 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
3342 wl_show(t, args, "Cookie White List", &c_wl);
3344 return (0);
3348 cookie_cmd(struct tab *t, struct karg *args)
3350 if (args->i & XT_SHOW)
3351 wl_show(t, args, "Cookie White List", &c_wl);
3352 else if (args->i & XT_WL_TOGGLE) {
3353 args->i |= XT_WL_RELOAD;
3354 toggle_cwl(t, args);
3355 } else if (args->i & XT_SAVE) {
3356 args->i |= XT_WL_RELOAD;
3357 wl_save(t, args, 0);
3358 } else if (args->i & XT_DELETE)
3359 show_oops(t, "'cookie delete' currently unimplemented");
3361 return (0);
3365 js_cmd(struct tab *t, struct karg *args)
3367 if (args->i & XT_SHOW)
3368 wl_show(t, args, "JavaScript White List", &js_wl);
3369 else if (args->i & XT_SAVE) {
3370 args->i |= XT_WL_RELOAD;
3371 wl_save(t, args, 1);
3372 } else if (args->i & XT_WL_TOGGLE) {
3373 args->i |= XT_WL_RELOAD;
3374 toggle_js(t, args);
3375 } else if (args->i & XT_DELETE)
3376 show_oops(t, "'js delete' currently unimplemented");
3378 return (0);
3382 toplevel_cmd(struct tab *t, struct karg *args)
3384 js_toggle_cb(t->js_toggle, t);
3386 return (0);
3390 add_favorite(struct tab *t, struct karg *args)
3392 char file[PATH_MAX];
3393 FILE *f;
3394 char *line = NULL;
3395 size_t urilen, linelen;
3396 const gchar *uri, *title;
3398 if (t == NULL)
3399 return (1);
3401 /* don't allow adding of xtp pages to favorites */
3402 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3403 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3404 return (1);
3407 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
3408 if ((f = fopen(file, "r+")) == NULL) {
3409 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3410 return (1);
3413 title = webkit_web_view_get_title(t->wv);
3414 uri = get_uri(t->wv);
3416 if (title == NULL)
3417 title = uri;
3419 if (title == NULL || uri == NULL) {
3420 show_oops(t, "can't add page to favorites");
3421 goto done;
3424 urilen = strlen(uri);
3426 for (;;) {
3427 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
3428 if (feof(f) || ferror(f))
3429 break;
3431 if (linelen == urilen && !strcmp(line, uri))
3432 goto done;
3434 free(line);
3435 line = NULL;
3438 fprintf(f, "\n%s\n%s", title, uri);
3439 done:
3440 if (line)
3441 free(line);
3442 fclose(f);
3444 update_favorite_tabs(NULL);
3446 return (0);
3450 navaction(struct tab *t, struct karg *args)
3452 WebKitWebHistoryItem *item;
3454 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3455 t->tab_id, args->i);
3457 if (t->item) {
3458 if (args->i == XT_NAV_BACK)
3459 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3460 else
3461 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3462 if (item == NULL)
3463 return (XT_CB_PASSTHROUGH);
3464 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3465 t->item = NULL;
3466 return (XT_CB_PASSTHROUGH);
3469 switch (args->i) {
3470 case XT_NAV_BACK:
3471 webkit_web_view_go_back(t->wv);
3472 break;
3473 case XT_NAV_FORWARD:
3474 webkit_web_view_go_forward(t->wv);
3475 break;
3476 case XT_NAV_RELOAD:
3477 webkit_web_view_reload(t->wv);
3478 break;
3479 case XT_NAV_RELOAD_CACHE:
3480 webkit_web_view_reload_bypass_cache(t->wv);
3481 break;
3483 return (XT_CB_PASSTHROUGH);
3487 move(struct tab *t, struct karg *args)
3489 GtkAdjustment *adjust;
3490 double pi, si, pos, ps, upper, lower, max;
3492 switch (args->i) {
3493 case XT_MOVE_DOWN:
3494 case XT_MOVE_UP:
3495 case XT_MOVE_BOTTOM:
3496 case XT_MOVE_TOP:
3497 case XT_MOVE_PAGEDOWN:
3498 case XT_MOVE_PAGEUP:
3499 case XT_MOVE_HALFDOWN:
3500 case XT_MOVE_HALFUP:
3501 adjust = t->adjust_v;
3502 break;
3503 default:
3504 adjust = t->adjust_h;
3505 break;
3508 pos = gtk_adjustment_get_value(adjust);
3509 ps = gtk_adjustment_get_page_size(adjust);
3510 upper = gtk_adjustment_get_upper(adjust);
3511 lower = gtk_adjustment_get_lower(adjust);
3512 si = gtk_adjustment_get_step_increment(adjust);
3513 pi = gtk_adjustment_get_page_increment(adjust);
3514 max = upper - ps;
3516 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3517 "max %f si %f pi %f\n",
3518 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3519 pos, ps, upper, lower, max, si, pi);
3521 switch (args->i) {
3522 case XT_MOVE_DOWN:
3523 case XT_MOVE_RIGHT:
3524 pos += si;
3525 gtk_adjustment_set_value(adjust, MIN(pos, max));
3526 break;
3527 case XT_MOVE_UP:
3528 case XT_MOVE_LEFT:
3529 pos -= si;
3530 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3531 break;
3532 case XT_MOVE_BOTTOM:
3533 case XT_MOVE_FARRIGHT:
3534 gtk_adjustment_set_value(adjust, max);
3535 break;
3536 case XT_MOVE_TOP:
3537 case XT_MOVE_FARLEFT:
3538 gtk_adjustment_set_value(adjust, lower);
3539 break;
3540 case XT_MOVE_PAGEDOWN:
3541 pos += pi;
3542 gtk_adjustment_set_value(adjust, MIN(pos, max));
3543 break;
3544 case XT_MOVE_PAGEUP:
3545 pos -= pi;
3546 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3547 break;
3548 case XT_MOVE_HALFDOWN:
3549 pos += pi / 2;
3550 gtk_adjustment_set_value(adjust, MIN(pos, max));
3551 break;
3552 case XT_MOVE_HALFUP:
3553 pos -= pi / 2;
3554 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3555 break;
3556 default:
3557 return (XT_CB_PASSTHROUGH);
3560 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3562 return (XT_CB_HANDLED);
3565 void
3566 url_set_visibility(void)
3568 struct tab *t;
3570 TAILQ_FOREACH(t, &tabs, entry) {
3571 if (show_url == 0) {
3572 gtk_widget_hide(t->toolbar);
3573 focus_webview(t);
3574 } else
3575 gtk_widget_show(t->toolbar);
3579 void
3580 notebook_tab_set_visibility(GtkNotebook *notebook)
3582 if (show_tabs == 0)
3583 gtk_notebook_set_show_tabs(notebook, FALSE);
3584 else
3585 gtk_notebook_set_show_tabs(notebook, TRUE);
3588 void
3589 statusbar_set_visibility(void)
3591 struct tab *t;
3593 TAILQ_FOREACH(t, &tabs, entry) {
3594 if (show_statusbar == 0) {
3595 gtk_widget_hide(t->statusbar);
3596 focus_webview(t);
3597 } else
3598 gtk_widget_show(t->statusbar);
3602 void
3603 url_set(struct tab *t, int enable_url_entry)
3605 GdkPixbuf *pixbuf;
3606 int progress;
3608 show_url = enable_url_entry;
3610 if (enable_url_entry) {
3611 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
3612 GTK_ENTRY_ICON_PRIMARY, NULL);
3613 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar), 0);
3614 } else {
3615 pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
3616 GTK_ENTRY_ICON_PRIMARY);
3617 progress =
3618 gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
3619 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
3620 GTK_ENTRY_ICON_PRIMARY, pixbuf);
3621 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
3622 progress);
3627 fullscreen(struct tab *t, struct karg *args)
3629 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3631 if (t == NULL)
3632 return (XT_CB_PASSTHROUGH);
3634 if (show_url == 0) {
3635 url_set(t, 1);
3636 show_tabs = 1;
3637 } else {
3638 url_set(t, 0);
3639 show_tabs = 0;
3642 url_set_visibility();
3643 notebook_tab_set_visibility(notebook);
3645 return (XT_CB_HANDLED);
3649 statusaction(struct tab *t, struct karg *args)
3651 int rv = XT_CB_HANDLED;
3653 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3655 if (t == NULL)
3656 return (XT_CB_PASSTHROUGH);
3658 switch (args->i) {
3659 case XT_STATUSBAR_SHOW:
3660 if (show_statusbar == 0) {
3661 show_statusbar = 1;
3662 statusbar_set_visibility();
3664 break;
3665 case XT_STATUSBAR_HIDE:
3666 if (show_statusbar == 1) {
3667 show_statusbar = 0;
3668 statusbar_set_visibility();
3670 break;
3672 return (rv);
3676 urlaction(struct tab *t, struct karg *args)
3678 int rv = XT_CB_HANDLED;
3680 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3682 if (t == NULL)
3683 return (XT_CB_PASSTHROUGH);
3685 switch (args->i) {
3686 case XT_URL_SHOW:
3687 if (show_url == 0) {
3688 url_set(t, 1);
3689 url_set_visibility();
3691 break;
3692 case XT_URL_HIDE:
3693 if (show_url == 1) {
3694 url_set(t, 0);
3695 url_set_visibility();
3697 break;
3699 return (rv);
3703 tabaction(struct tab *t, struct karg *args)
3705 int rv = XT_CB_HANDLED;
3706 char *url = args->s;
3707 struct undo *u;
3708 struct tab *tt;
3710 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3712 if (t == NULL)
3713 return (XT_CB_PASSTHROUGH);
3715 switch (args->i) {
3716 case XT_TAB_NEW:
3717 if (strlen(url) > 0)
3718 create_new_tab(url, NULL, 1, args->p);
3719 else
3720 create_new_tab(NULL, NULL, 1, args->p);
3721 break;
3722 case XT_TAB_DELETE:
3723 if (args->p < 0)
3724 delete_tab(t);
3725 else
3726 TAILQ_FOREACH(tt, &tabs, entry)
3727 if (tt->tab_id == args->p - 1) {
3728 delete_tab(tt);
3729 recalc_tabs();
3730 break;
3732 break;
3733 case XT_TAB_DELQUIT:
3734 if (gtk_notebook_get_n_pages(notebook) > 1)
3735 delete_tab(t);
3736 else
3737 quit(t, args);
3738 break;
3739 case XT_TAB_OPEN:
3740 if (strlen(url) > 0)
3742 else {
3743 rv = XT_CB_PASSTHROUGH;
3744 goto done;
3746 load_uri(t, url);
3747 break;
3748 case XT_TAB_SHOW:
3749 if (show_tabs == 0) {
3750 show_tabs = 1;
3751 notebook_tab_set_visibility(notebook);
3753 break;
3754 case XT_TAB_HIDE:
3755 if (show_tabs == 1) {
3756 show_tabs = 0;
3757 notebook_tab_set_visibility(notebook);
3759 break;
3760 case XT_TAB_UNDO_CLOSE:
3761 if (undo_count == 0) {
3762 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3763 goto done;
3764 } else {
3765 undo_count--;
3766 u = TAILQ_FIRST(&undos);
3767 create_new_tab(u->uri, u, 1, -1);
3769 TAILQ_REMOVE(&undos, u, entry);
3770 g_free(u->uri);
3771 /* u->history is freed in create_new_tab() */
3772 g_free(u);
3774 break;
3775 default:
3776 rv = XT_CB_PASSTHROUGH;
3777 goto done;
3780 done:
3781 if (args->s) {
3782 g_free(args->s);
3783 args->s = NULL;
3786 return (rv);
3790 resizetab(struct tab *t, struct karg *args)
3792 if (t == NULL || args == NULL) {
3793 show_oops_s("resizetab invalid parameters");
3794 return (XT_CB_PASSTHROUGH);
3797 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3798 t->tab_id, args->i);
3800 adjustfont_webkit(t, args->i);
3802 return (XT_CB_HANDLED);
3806 movetab(struct tab *t, struct karg *args)
3808 int n, dest;
3810 if (t == NULL || args == NULL) {
3811 show_oops_s("movetab invalid parameters");
3812 return (XT_CB_PASSTHROUGH);
3815 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3816 t->tab_id, args->i);
3818 if (args->i >= XT_TAB_INVALID)
3819 return (XT_CB_PASSTHROUGH);
3821 if (TAILQ_EMPTY(&tabs))
3822 return (XT_CB_PASSTHROUGH);
3824 n = gtk_notebook_get_n_pages(notebook);
3825 dest = gtk_notebook_get_current_page(notebook);
3827 switch (args->i) {
3828 case XT_TAB_NEXT:
3829 if (args->p < 0)
3830 dest = dest == n - 1 ? 0 : dest + 1;
3831 else
3832 dest = args->p - 1;
3834 break;
3835 case XT_TAB_PREV:
3836 if (args->p < 0)
3837 dest -= 1;
3838 else
3839 dest -= args->p % n;
3841 if (dest < 0)
3842 dest += n;
3844 break;
3845 case XT_TAB_FIRST:
3846 dest = 0;
3847 break;
3848 case XT_TAB_LAST:
3849 dest = n - 1;
3850 break;
3851 default:
3852 return (XT_CB_PASSTHROUGH);
3855 if (dest < 0 || dest >= n)
3856 return (XT_CB_PASSTHROUGH);
3857 if (t->tab_id == dest) {
3858 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3859 return (XT_CB_HANDLED);
3862 gtk_notebook_set_current_page(notebook, dest);
3864 return (XT_CB_HANDLED);
3867 int cmd_prefix = 0;
3870 command(struct tab *t, struct karg *args)
3872 char *s = NULL, *ss = NULL;
3873 GdkColor color;
3874 const gchar *uri;
3876 if (t == NULL || args == NULL) {
3877 show_oops_s("command invalid parameters");
3878 return (XT_CB_PASSTHROUGH);
3881 switch (args->i) {
3882 case '/':
3883 s = "/";
3884 break;
3885 case '?':
3886 s = "?";
3887 break;
3888 case ':':
3889 if (cmd_prefix == 0)
3890 s = ":";
3891 else {
3892 ss = g_strdup_printf(":%d", cmd_prefix);
3893 s = ss;
3894 cmd_prefix = 0;
3896 break;
3897 case XT_CMD_OPEN:
3898 s = ":open ";
3899 break;
3900 case XT_CMD_TABNEW:
3901 s = ":tabnew ";
3902 break;
3903 case XT_CMD_OPEN_CURRENT:
3904 s = ":open ";
3905 /* FALL THROUGH */
3906 case XT_CMD_TABNEW_CURRENT:
3907 if (!s) /* FALL THROUGH? */
3908 s = ":tabnew ";
3909 if ((uri = get_uri(t->wv)) != NULL) {
3910 ss = g_strdup_printf("%s%s", s, uri);
3911 s = ss;
3913 break;
3914 default:
3915 show_oops(t, "command: invalid opcode %d", args->i);
3916 return (XT_CB_PASSTHROUGH);
3919 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3921 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3922 gdk_color_parse(XT_COLOR_WHITE, &color);
3923 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3924 show_cmd(t);
3925 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3926 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3928 if (ss)
3929 g_free(ss);
3931 return (XT_CB_HANDLED);
3935 * Return a new string with a download row (in html)
3936 * appended. Old string is freed.
3938 char *
3939 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3942 WebKitDownloadStatus stat;
3943 char *status_html = NULL, *cmd_html = NULL, *new_html;
3944 gdouble progress;
3945 char cur_sz[FMT_SCALED_STRSIZE];
3946 char tot_sz[FMT_SCALED_STRSIZE];
3947 char *xtp_prefix;
3949 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3951 /* All actions wil take this form:
3952 * xxxt://class/seskey
3954 xtp_prefix = g_strdup_printf("%s%d/%s/",
3955 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3957 stat = webkit_download_get_status(dl->download);
3959 switch (stat) {
3960 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3961 status_html = g_strdup_printf("Finished");
3962 cmd_html = g_strdup_printf(
3963 "<a href='%s%d/%d'>Remove</a>",
3964 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3965 break;
3966 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3967 /* gather size info */
3968 progress = 100 * webkit_download_get_progress(dl->download);
3970 fmt_scaled(
3971 webkit_download_get_current_size(dl->download), cur_sz);
3972 fmt_scaled(
3973 webkit_download_get_total_size(dl->download), tot_sz);
3975 status_html = g_strdup_printf(
3976 "<div style='width: 100%%' align='center'>"
3977 "<div class='progress-outer'>"
3978 "<div class='progress-inner' style='width: %.2f%%'>"
3979 "</div></div></div>"
3980 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3981 progress, cur_sz, tot_sz, progress);
3983 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3984 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3986 break;
3987 /* LLL */
3988 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3989 status_html = g_strdup_printf("Cancelled");
3990 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3991 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3992 break;
3993 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3994 status_html = g_strdup_printf("Error!");
3995 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3996 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3997 break;
3998 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3999 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
4000 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
4001 status_html = g_strdup_printf("Starting");
4002 break;
4003 default:
4004 show_oops(t, "%s: unknown download status", __func__);
4007 new_html = g_strdup_printf(
4008 "%s\n<tr><td>%s</td><td>%s</td>"
4009 "<td style='text-align:center'>%s</td></tr>\n",
4010 html, basename(webkit_download_get_destination_uri(dl->download)),
4011 status_html, cmd_html);
4012 g_free(html);
4014 if (status_html)
4015 g_free(status_html);
4017 if (cmd_html)
4018 g_free(cmd_html);
4020 g_free(xtp_prefix);
4022 return new_html;
4026 * update all download tabs apart from one. Pass NULL if
4027 * you want to update all.
4029 void
4030 update_download_tabs(struct tab *apart_from)
4032 struct tab *t;
4033 if (!updating_dl_tabs) {
4034 updating_dl_tabs = 1; /* stop infinite recursion */
4035 TAILQ_FOREACH(t, &tabs, entry)
4036 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
4037 && (t != apart_from))
4038 xtp_page_dl(t, NULL);
4039 updating_dl_tabs = 0;
4044 * update all cookie tabs apart from one. Pass NULL if
4045 * you want to update all.
4047 void
4048 update_cookie_tabs(struct tab *apart_from)
4050 struct tab *t;
4051 if (!updating_cl_tabs) {
4052 updating_cl_tabs = 1; /* stop infinite recursion */
4053 TAILQ_FOREACH(t, &tabs, entry)
4054 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
4055 && (t != apart_from))
4056 xtp_page_cl(t, NULL);
4057 updating_cl_tabs = 0;
4062 * update all history tabs apart from one. Pass NULL if
4063 * you want to update all.
4065 void
4066 update_history_tabs(struct tab *apart_from)
4068 struct tab *t;
4070 if (!updating_hl_tabs) {
4071 updating_hl_tabs = 1; /* stop infinite recursion */
4072 TAILQ_FOREACH(t, &tabs, entry)
4073 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
4074 && (t != apart_from))
4075 xtp_page_hl(t, NULL);
4076 updating_hl_tabs = 0;
4080 /* cookie management XTP page */
4082 xtp_page_cl(struct tab *t, struct karg *args)
4084 char *body, *page, *tmp;
4085 int i = 1; /* all ids start 1 */
4086 GSList *sc, *pc, *pc_start;
4087 SoupCookie *c;
4088 char *type, *table_headers;
4089 char *last_domain = strdup("");
4091 DNPRINTF(XT_D_CMD, "%s", __func__);
4093 if (t == NULL) {
4094 show_oops_s("%s invalid parameters", __func__);
4095 return (1);
4097 /* mark this tab as cookie jar */
4098 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
4100 /* Generate a new session key */
4101 if (!updating_cl_tabs)
4102 generate_xtp_session_key(&cl_session_key);
4104 /* table headers */
4105 table_headers = g_strdup_printf("<table><tr>"
4106 "<th>Type</th>"
4107 "<th>Name</th>"
4108 "<th style='width:200px'>Value</th>"
4109 "<th>Path</th>"
4110 "<th>Expires</th>"
4111 "<th>Secure</th>"
4112 "<th>HTTP<br />only</th>"
4113 "<th style='width:40px'>Rm</th></tr>\n");
4115 sc = soup_cookie_jar_all_cookies(s_cookiejar);
4116 pc = soup_cookie_jar_all_cookies(p_cookiejar);
4117 pc_start = pc;
4119 body = NULL;
4120 for (; sc; sc = sc->next) {
4121 c = sc->data;
4123 if (strcmp(last_domain, c->domain) != 0) {
4124 /* new domain */
4125 free(last_domain);
4126 last_domain = strdup(c->domain);
4128 if (body != NULL) {
4129 tmp = body;
4130 body = g_strdup_printf("%s</table>"
4131 "<h2>%s</h2>%s\n",
4132 body, c->domain, table_headers);
4133 g_free(tmp);
4134 } else {
4135 /* first domain */
4136 body = g_strdup_printf("<h2>%s</h2>%s\n",
4137 c->domain, table_headers);
4141 type = "Session";
4142 for (pc = pc_start; pc; pc = pc->next)
4143 if (soup_cookie_equal(pc->data, c)) {
4144 type = "Session + Persistent";
4145 break;
4148 tmp = body;
4149 body = g_strdup_printf(
4150 "%s\n<tr>"
4151 "<td>%s</td>"
4152 "<td style='word-wrap:normal'>%s</td>"
4153 "<td>"
4154 " <textarea rows='4'>%s</textarea>"
4155 "</td>"
4156 "<td>%s</td>"
4157 "<td>%s</td>"
4158 "<td>%d</td>"
4159 "<td>%d</td>"
4160 "<td style='text-align:center'>"
4161 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4162 body,
4163 type,
4164 c->name,
4165 c->value,
4166 c->path,
4167 c->expires ?
4168 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
4169 c->secure,
4170 c->http_only,
4172 XT_XTP_STR,
4173 XT_XTP_CL,
4174 cl_session_key,
4175 XT_XTP_CL_REMOVE,
4179 g_free(tmp);
4180 i++;
4183 soup_cookies_free(sc);
4184 soup_cookies_free(pc);
4186 /* small message if there are none */
4187 if (i == 1) {
4188 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4189 "colspan='8'>No Cookies</td></tr>\n", table_headers);
4191 tmp = body;
4192 body = g_strdup_printf("%s</table>", body);
4193 g_free(tmp);
4195 page = get_html_page("Cookie Jar", body, "", TRUE);
4196 g_free(body);
4197 g_free(table_headers);
4198 g_free(last_domain);
4200 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
4201 update_cookie_tabs(t);
4203 g_free(page);
4205 return (0);
4209 xtp_page_hl(struct tab *t, struct karg *args)
4211 char *body, *page, *tmp;
4212 struct history *h;
4213 int i = 1; /* all ids start 1 */
4215 DNPRINTF(XT_D_CMD, "%s", __func__);
4217 if (t == NULL) {
4218 show_oops_s("%s invalid parameters", __func__);
4219 return (1);
4222 /* mark this tab as history manager */
4223 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
4225 /* Generate a new session key */
4226 if (!updating_hl_tabs)
4227 generate_xtp_session_key(&hl_session_key);
4229 /* body */
4230 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
4231 "<th>URI</th><th>Title</th><th style='width: 40px'>Rm</th></tr>\n");
4233 RB_FOREACH_REVERSE(h, history_list, &hl) {
4234 tmp = body;
4235 body = g_strdup_printf(
4236 "%s\n<tr>"
4237 "<td><a href='%s'>%s</a></td>"
4238 "<td>%s</td>"
4239 "<td style='text-align: center'>"
4240 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
4241 body, h->uri, h->uri, h->title,
4242 XT_XTP_STR, XT_XTP_HL, hl_session_key,
4243 XT_XTP_HL_REMOVE, i);
4245 g_free(tmp);
4246 i++;
4249 /* small message if there are none */
4250 if (i == 1) {
4251 tmp = body;
4252 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4253 "colspan='3'>No History</td></tr>\n", body);
4254 g_free(tmp);
4257 tmp = body;
4258 body = g_strdup_printf("%s</table>", body);
4259 g_free(tmp);
4261 page = get_html_page("History", body, "", TRUE);
4262 g_free(body);
4265 * update all history manager tabs as the xtp session
4266 * key has now changed. No need to update the current tab.
4267 * Already did that above.
4269 update_history_tabs(t);
4271 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
4272 g_free(page);
4274 return (0);
4278 * Generate a web page detailing the status of any downloads
4281 xtp_page_dl(struct tab *t, struct karg *args)
4283 struct download *dl;
4284 char *body, *page, *tmp;
4285 char *ref;
4286 int n_dl = 1;
4288 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
4290 if (t == NULL) {
4291 show_oops_s("%s invalid parameters", __func__);
4292 return (1);
4294 /* mark as a download manager tab */
4295 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
4298 * Generate a new session key for next page instance.
4299 * This only happens for the top level call to xtp_page_dl()
4300 * in which case updating_dl_tabs is 0.
4302 if (!updating_dl_tabs)
4303 generate_xtp_session_key(&dl_session_key);
4305 /* header - with refresh so as to update */
4306 if (refresh_interval >= 1)
4307 ref = g_strdup_printf(
4308 "<meta http-equiv='refresh' content='%u"
4309 ";url=%s%d/%s/%d' />\n",
4310 refresh_interval,
4311 XT_XTP_STR,
4312 XT_XTP_DL,
4313 dl_session_key,
4314 XT_XTP_DL_LIST);
4315 else
4316 ref = g_strdup("");
4318 body = g_strdup_printf("<div align='center'>"
4319 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4320 "</p><table><tr><th style='width: 60%%'>"
4321 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4322 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4324 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4325 body = xtp_page_dl_row(t, body, dl);
4326 n_dl++;
4329 /* message if no downloads in list */
4330 if (n_dl == 1) {
4331 tmp = body;
4332 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4333 " style='text-align: center'>"
4334 "No downloads</td></tr>\n", body);
4335 g_free(tmp);
4338 tmp = body;
4339 body = g_strdup_printf("%s</table></div>", body);
4340 g_free(tmp);
4342 page = get_html_page("Downloads", body, ref, 1);
4343 g_free(ref);
4344 g_free(body);
4347 * update all download manager tabs as the xtp session
4348 * key has now changed. No need to update the current tab.
4349 * Already did that above.
4351 update_download_tabs(t);
4353 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
4354 g_free(page);
4356 return (0);
4360 search(struct tab *t, struct karg *args)
4362 gboolean d;
4364 if (t == NULL || args == NULL) {
4365 show_oops_s("search invalid parameters");
4366 return (1);
4368 if (t->search_text == NULL) {
4369 if (global_search == NULL)
4370 return (XT_CB_PASSTHROUGH);
4371 else {
4372 t->search_text = g_strdup(global_search);
4373 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4374 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4378 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4379 t->tab_id, args->i, t->search_forward, t->search_text);
4381 switch (args->i) {
4382 case XT_SEARCH_NEXT:
4383 d = t->search_forward;
4384 break;
4385 case XT_SEARCH_PREV:
4386 d = !t->search_forward;
4387 break;
4388 default:
4389 return (XT_CB_PASSTHROUGH);
4392 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4394 return (XT_CB_HANDLED);
4397 struct settings_args {
4398 char **body;
4399 int i;
4402 void
4403 print_setting(struct settings *s, char *val, void *cb_args)
4405 char *tmp, *color;
4406 struct settings_args *sa = cb_args;
4408 if (sa == NULL)
4409 return;
4411 if (s->flags & XT_SF_RUNTIME)
4412 color = "#22cc22";
4413 else
4414 color = "#cccccc";
4416 tmp = *sa->body;
4417 *sa->body = g_strdup_printf(
4418 "%s\n<tr>"
4419 "<td style='background-color: %s; width: 10%%;word-break:break-all'>%s</td>"
4420 "<td style='background-color: %s; width: 20%%;word-break:break-all'>%s</td>",
4421 *sa->body,
4422 color,
4423 s->name,
4424 color,
4427 g_free(tmp);
4428 sa->i++;
4432 set(struct tab *t, struct karg *args)
4434 char *body, *page, *tmp;
4435 int i = 1;
4436 struct settings_args sa;
4438 bzero(&sa, sizeof sa);
4439 sa.body = &body;
4441 /* body */
4442 body = g_strdup_printf("<div align='center'><table><tr>"
4443 "<th align='left'>Setting</th>"
4444 "<th align='left'>Value</th></tr>\n");
4446 settings_walk(print_setting, &sa);
4447 i = sa.i;
4449 /* small message if there are none */
4450 if (i == 1) {
4451 tmp = body;
4452 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4453 "colspan='2'>No settings</td></tr>\n", body);
4454 g_free(tmp);
4457 tmp = body;
4458 body = g_strdup_printf("%s</table></div>", body);
4459 g_free(tmp);
4461 page = get_html_page("Settings", body, "", 0);
4463 g_free(body);
4465 load_webkit_string(t, page, XT_URI_ABOUT_SET);
4467 g_free(page);
4469 return (XT_CB_PASSTHROUGH);
4473 session_save(struct tab *t, char *filename)
4475 struct karg a;
4476 int rv = 1;
4478 if (strlen(filename) == 0)
4479 goto done;
4481 if (filename[0] == '.' || filename[0] == '/')
4482 goto done;
4484 a.s = filename;
4485 if (save_tabs(t, &a))
4486 goto done;
4487 strlcpy(named_session, filename, sizeof named_session);
4489 rv = 0;
4490 done:
4491 return (rv);
4495 session_open(struct tab *t, char *filename)
4497 struct karg a;
4498 int rv = 1;
4500 if (strlen(filename) == 0)
4501 goto done;
4503 if (filename[0] == '.' || filename[0] == '/')
4504 goto done;
4506 a.s = filename;
4507 a.i = XT_SES_CLOSETABS;
4508 if (open_tabs(t, &a))
4509 goto done;
4511 strlcpy(named_session, filename, sizeof named_session);
4513 rv = 0;
4514 done:
4515 return (rv);
4519 session_delete(struct tab *t, char *filename)
4521 char file[PATH_MAX];
4522 int rv = 1;
4524 if (strlen(filename) == 0)
4525 goto done;
4527 if (filename[0] == '.' || filename[0] == '/')
4528 goto done;
4530 snprintf(file, sizeof file, "%s/%s", sessions_dir, filename);
4531 if (unlink(file))
4532 goto done;
4534 if (!strcmp(filename, named_session))
4535 strlcpy(named_session, XT_SAVED_TABS_FILE,
4536 sizeof named_session);
4538 rv = 0;
4539 done:
4540 return (rv);
4544 session_cmd(struct tab *t, struct karg *args)
4546 char *filename = args->s;
4548 if (t == NULL)
4549 return (1);
4551 if (args->i & XT_SHOW)
4552 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4553 XT_SAVED_TABS_FILE : named_session);
4554 else if (args->i & XT_SAVE) {
4555 if (session_save(t, filename)) {
4556 show_oops(t, "Can't save session: %s",
4557 filename ? filename : "INVALID");
4558 goto done;
4560 } else if (args->i & XT_OPEN) {
4561 if (session_open(t, filename)) {
4562 show_oops(t, "Can't open session: %s",
4563 filename ? filename : "INVALID");
4564 goto done;
4566 } else if (args->i & XT_DELETE) {
4567 if (session_delete(t, filename)) {
4568 show_oops(t, "Can't delete session: %s",
4569 filename ? filename : "INVALID");
4570 goto done;
4573 done:
4574 return (XT_CB_PASSTHROUGH);
4578 * Make a hardcopy of the page
4581 print_page(struct tab *t, struct karg *args)
4583 WebKitWebFrame *frame;
4584 GtkPageSetup *ps;
4585 GtkPrintOperation *op;
4586 GtkPrintOperationAction action;
4587 GtkPrintOperationResult print_res;
4588 GError *g_err = NULL;
4589 int marg_l, marg_r, marg_t, marg_b;
4591 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4593 ps = gtk_page_setup_new();
4594 op = gtk_print_operation_new();
4595 action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
4596 frame = webkit_web_view_get_main_frame(t->wv);
4598 /* the default margins are too small, so we will bump them */
4599 marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
4600 XT_PRINT_EXTRA_MARGIN;
4601 marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
4602 XT_PRINT_EXTRA_MARGIN;
4603 marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
4604 XT_PRINT_EXTRA_MARGIN;
4605 marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
4606 XT_PRINT_EXTRA_MARGIN;
4608 /* set margins */
4609 gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
4610 gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
4611 gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
4612 gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
4614 gtk_print_operation_set_default_page_setup(op, ps);
4616 /* this appears to free 'op' and 'ps' */
4617 print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
4619 /* check it worked */
4620 if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
4621 show_oops_s("can't print: %s", g_err->message);
4622 g_error_free (g_err);
4623 return (1);
4626 return (0);
4630 go_home(struct tab *t, struct karg *args)
4632 load_uri(t, home);
4633 return (0);
4637 restart(struct tab *t, struct karg *args)
4639 struct karg a;
4641 a.s = XT_RESTART_TABS_FILE;
4642 save_tabs(t, &a);
4643 execvp(start_argv[0], start_argv);
4644 /* NOTREACHED */
4646 return (0);
4649 #define CTRL GDK_CONTROL_MASK
4650 #define MOD1 GDK_MOD1_MASK
4651 #define SHFT GDK_SHIFT_MASK
4653 /* inherent to GTK not all keys will be caught at all times */
4654 /* XXX sort key bindings */
4655 struct key_binding {
4656 char *cmd;
4657 guint mask;
4658 guint use_in_entry;
4659 guint key;
4660 TAILQ_ENTRY(key_binding) entry; /* in bss so no need to init */
4661 } keys[] = {
4662 { "cookiejar", MOD1, 0, GDK_j },
4663 { "downloadmgr", MOD1, 0, GDK_d },
4664 { "history", MOD1, 0, GDK_h },
4665 { "print", CTRL, 0, GDK_p },
4666 { "search", 0, 0, GDK_slash },
4667 { "searchb", 0, 0, GDK_question },
4668 { "command", 0, 0, GDK_colon },
4669 { "qa", CTRL, 0, GDK_q },
4670 { "restart", MOD1, 0, GDK_q },
4671 { "js toggle", CTRL, 0, GDK_j },
4672 { "cookie toggle", MOD1, 0, GDK_c },
4673 { "togglesrc", CTRL, 0, GDK_s },
4674 { "yankuri", 0, 0, GDK_y },
4675 { "pasteuricur", 0, 0, GDK_p },
4676 { "pasteurinew", 0, 0, GDK_P },
4677 { "toplevel toggle", 0, 0, GDK_F4 },
4678 { "help", 0, 0, GDK_F1 },
4680 /* search */
4681 { "searchnext", 0, 0, GDK_n },
4682 { "searchprevious", 0, 0, GDK_N },
4684 /* focus */
4685 { "focusaddress", 0, 0, GDK_F6 },
4686 { "focussearch", 0, 0, GDK_F7 },
4688 /* hinting */
4689 { "hinting", 0, 0, GDK_f },
4691 /* custom stylesheet */
4692 { "userstyle", 0, 0, GDK_i },
4694 /* navigation */
4695 { "goback", 0, 0, GDK_BackSpace },
4696 { "goback", MOD1, 0, GDK_Left },
4697 { "goforward", SHFT, 0, GDK_BackSpace },
4698 { "goforward", MOD1, 0, GDK_Right },
4699 { "reload", 0, 0, GDK_F5 },
4700 { "reload", CTRL, 0, GDK_r },
4701 { "reloadforce", CTRL, 0, GDK_R },
4702 { "reload", CTRL, 0, GDK_l },
4703 { "favorites", MOD1, 1, GDK_f },
4705 /* vertical movement */
4706 { "scrolldown", 0, 0, GDK_j },
4707 { "scrolldown", 0, 0, GDK_Down },
4708 { "scrollup", 0, 0, GDK_Up },
4709 { "scrollup", 0, 0, GDK_k },
4710 { "scrollbottom", 0, 0, GDK_G },
4711 { "scrollbottom", 0, 0, GDK_End },
4712 { "scrolltop", 0, 0, GDK_Home },
4713 { "scrolltop", 0, 0, GDK_g },
4714 { "scrollpagedown", 0, 0, GDK_space },
4715 { "scrollpagedown", CTRL, 0, GDK_f },
4716 { "scrollhalfdown", CTRL, 0, GDK_d },
4717 { "scrollpagedown", 0, 0, GDK_Page_Down },
4718 { "scrollpageup", 0, 0, GDK_Page_Up },
4719 { "scrollpageup", CTRL, 0, GDK_b },
4720 { "scrollhalfup", CTRL, 0, GDK_u },
4721 /* horizontal movement */
4722 { "scrollright", 0, 0, GDK_l },
4723 { "scrollright", 0, 0, GDK_Right },
4724 { "scrollleft", 0, 0, GDK_Left },
4725 { "scrollleft", 0, 0, GDK_h },
4726 { "scrollfarright", 0, 0, GDK_dollar },
4727 { "scrollfarleft", 0, 0, GDK_0 },
4729 /* tabs */
4730 { "tabnew", CTRL, 0, GDK_t },
4731 { "999tabnew", CTRL, 0, GDK_T },
4732 { "tabclose", CTRL, 1, GDK_w },
4733 { "tabundoclose", 0, 0, GDK_U },
4734 { "tabnext 1", CTRL, 0, GDK_1 },
4735 { "tabnext 2", CTRL, 0, GDK_2 },
4736 { "tabnext 3", CTRL, 0, GDK_3 },
4737 { "tabnext 4", CTRL, 0, GDK_4 },
4738 { "tabnext 5", CTRL, 0, GDK_5 },
4739 { "tabnext 6", CTRL, 0, GDK_6 },
4740 { "tabnext 7", CTRL, 0, GDK_7 },
4741 { "tabnext 8", CTRL, 0, GDK_8 },
4742 { "tabnext 9", CTRL, 0, GDK_9 },
4743 { "tabnext 10", CTRL, 0, GDK_0 },
4744 { "tabfirst", CTRL, 0, GDK_less },
4745 { "tablast", CTRL, 0, GDK_greater },
4746 { "tabprevious", CTRL, 0, GDK_Left },
4747 { "tabnext", CTRL, 0, GDK_Right },
4748 { "focusout", CTRL, 0, GDK_minus },
4749 { "focusin", CTRL, 0, GDK_plus },
4750 { "focusin", CTRL, 0, GDK_equal },
4752 /* command aliases (handy when -S flag is used) */
4753 { "promptopen", 0, 0, GDK_F9 },
4754 { "promptopencurrent", 0, 0, GDK_F10 },
4755 { "prompttabnew", 0, 0, GDK_F11 },
4756 { "prompttabnewcurrent",0, 0, GDK_F12 },
4758 TAILQ_HEAD(keybinding_list, key_binding);
4760 void
4761 walk_kb(struct settings *s,
4762 void (*cb)(struct settings *, char *, void *), void *cb_args)
4764 struct key_binding *k;
4765 char str[1024];
4767 if (s == NULL || cb == NULL) {
4768 show_oops_s("walk_kb invalid parameters");
4769 return;
4772 TAILQ_FOREACH(k, &kbl, entry) {
4773 if (k->cmd == NULL)
4774 continue;
4775 str[0] = '\0';
4777 /* sanity */
4778 if (gdk_keyval_name(k->key) == NULL)
4779 continue;
4781 strlcat(str, k->cmd, sizeof str);
4782 strlcat(str, ",", sizeof str);
4784 if (k->mask & GDK_SHIFT_MASK)
4785 strlcat(str, "S-", sizeof str);
4786 if (k->mask & GDK_CONTROL_MASK)
4787 strlcat(str, "C-", sizeof str);
4788 if (k->mask & GDK_MOD1_MASK)
4789 strlcat(str, "M1-", sizeof str);
4790 if (k->mask & GDK_MOD2_MASK)
4791 strlcat(str, "M2-", sizeof str);
4792 if (k->mask & GDK_MOD3_MASK)
4793 strlcat(str, "M3-", sizeof str);
4794 if (k->mask & GDK_MOD4_MASK)
4795 strlcat(str, "M4-", sizeof str);
4796 if (k->mask & GDK_MOD5_MASK)
4797 strlcat(str, "M5-", sizeof str);
4799 strlcat(str, gdk_keyval_name(k->key), sizeof str);
4800 cb(s, str, cb_args);
4804 void
4805 init_keybindings(void)
4807 int i;
4808 struct key_binding *k;
4810 for (i = 0; i < LENGTH(keys); i++) {
4811 k = g_malloc0(sizeof *k);
4812 k->cmd = keys[i].cmd;
4813 k->mask = keys[i].mask;
4814 k->use_in_entry = keys[i].use_in_entry;
4815 k->key = keys[i].key;
4816 TAILQ_INSERT_HEAD(&kbl, k, entry);
4818 DNPRINTF(XT_D_KEYBINDING, "init_keybindings: added: %s\n",
4819 k->cmd ? k->cmd : "unnamed key");
4823 void
4824 keybinding_clearall(void)
4826 struct key_binding *k, *next;
4828 for (k = TAILQ_FIRST(&kbl); k; k = next) {
4829 next = TAILQ_NEXT(k, entry);
4830 if (k->cmd == NULL)
4831 continue;
4833 DNPRINTF(XT_D_KEYBINDING, "keybinding_clearall: %s\n",
4834 k->cmd ? k->cmd : "unnamed key");
4835 TAILQ_REMOVE(&kbl, k, entry);
4836 g_free(k);
4841 keybinding_add(char *cmd, char *key, int use_in_entry)
4843 struct key_binding *k;
4844 guint keyval, mask = 0;
4845 int i;
4847 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s %s\n", cmd, key);
4849 /* Keys which are to be used in entry have been prefixed with an
4850 * exclamation mark. */
4851 if (use_in_entry)
4852 key++;
4854 /* find modifier keys */
4855 if (strstr(key, "S-"))
4856 mask |= GDK_SHIFT_MASK;
4857 if (strstr(key, "C-"))
4858 mask |= GDK_CONTROL_MASK;
4859 if (strstr(key, "M1-"))
4860 mask |= GDK_MOD1_MASK;
4861 if (strstr(key, "M2-"))
4862 mask |= GDK_MOD2_MASK;
4863 if (strstr(key, "M3-"))
4864 mask |= GDK_MOD3_MASK;
4865 if (strstr(key, "M4-"))
4866 mask |= GDK_MOD4_MASK;
4867 if (strstr(key, "M5-"))
4868 mask |= GDK_MOD5_MASK;
4870 /* find keyname */
4871 for (i = strlen(key) - 1; i > 0; i--)
4872 if (key[i] == '-')
4873 key = &key[i + 1];
4875 /* validate keyname */
4876 keyval = gdk_keyval_from_name(key);
4877 if (keyval == GDK_VoidSymbol) {
4878 warnx("invalid keybinding name %s", key);
4879 return (1);
4881 /* must run this test too, gtk+ doesn't handle 10 for example */
4882 if (gdk_keyval_name(keyval) == NULL) {
4883 warnx("invalid keybinding name %s", key);
4884 return (1);
4887 /* Remove eventual dupes. */
4888 TAILQ_FOREACH(k, &kbl, entry)
4889 if (k->key == keyval && k->mask == mask) {
4890 TAILQ_REMOVE(&kbl, k, entry);
4891 g_free(k);
4892 break;
4895 /* add keyname */
4896 k = g_malloc0(sizeof *k);
4897 k->cmd = g_strdup(cmd);
4898 k->mask = mask;
4899 k->use_in_entry = use_in_entry;
4900 k->key = keyval;
4902 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: %s 0x%x %d 0x%x\n",
4903 k->cmd,
4904 k->mask,
4905 k->use_in_entry,
4906 k->key);
4907 DNPRINTF(XT_D_KEYBINDING, "keybinding_add: adding: %s %s\n",
4908 k->cmd, gdk_keyval_name(keyval));
4910 TAILQ_INSERT_HEAD(&kbl, k, entry);
4912 return (0);
4916 add_kb(struct settings *s, char *entry)
4918 char *kb, *key;
4920 DNPRINTF(XT_D_KEYBINDING, "add_kb: %s\n", entry);
4922 /* clearall is special */
4923 if (!strcmp(entry, "clearall")) {
4924 keybinding_clearall();
4925 return (0);
4928 kb = strstr(entry, ",");
4929 if (kb == NULL)
4930 return (1);
4931 *kb = '\0';
4932 key = kb + 1;
4934 return (keybinding_add(entry, key, key[0] == '!'));
4937 struct cmd {
4938 char *cmd;
4939 int level;
4940 int (*func)(struct tab *, struct karg *);
4941 int arg;
4942 int type;
4943 } cmds[] = {
4944 { "command", 0, command, ':', 0 },
4945 { "search", 0, command, '/', 0 },
4946 { "searchb", 0, command, '?', 0 },
4947 { "togglesrc", 0, toggle_src, 0, 0 },
4949 /* yanking and pasting */
4950 { "yankuri", 0, yank_uri, 0, 0 },
4951 /* XXX: pasteuri{cur,new} do not work from the cmd_entry? */
4952 { "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
4953 { "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
4955 /* search */
4956 { "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
4957 { "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
4959 /* focus */
4960 { "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
4961 { "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
4963 /* hinting */
4964 { "hinting", 0, hint, 0, 0 },
4966 /* custom stylesheet */
4967 { "userstyle", 0, userstyle, 0, 0 },
4969 /* navigation */
4970 { "goback", 0, navaction, XT_NAV_BACK, 0 },
4971 { "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
4972 { "reload", 0, navaction, XT_NAV_RELOAD, 0 },
4973 { "reloadforce", 0, navaction, XT_NAV_RELOAD_CACHE, 0 },
4975 /* vertical movement */
4976 { "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
4977 { "scrollup", 0, move, XT_MOVE_UP, 0 },
4978 { "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
4979 { "scrolltop", 0, move, XT_MOVE_TOP, 0 },
4980 { "1", 0, move, XT_MOVE_TOP, 0 },
4981 { "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
4982 { "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
4983 { "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
4984 { "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
4985 /* horizontal movement */
4986 { "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
4987 { "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
4988 { "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
4989 { "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
4992 { "favorites", 0, xtp_page_fl, 0, 0 },
4993 { "fav", 0, xtp_page_fl, 0, 0 },
4994 { "favadd", 0, add_favorite, 0, 0 },
4996 { "qall", 0, quit, 0, 0 },
4997 { "quitall", 0, quit, 0, 0 },
4998 { "w", 0, save_tabs, 0, 0 },
4999 { "wq", 0, save_tabs_and_quit, 0, 0 },
5000 { "help", 0, help, 0, 0 },
5001 { "about", 0, about, 0, 0 },
5002 { "stats", 0, stats, 0, 0 },
5003 { "version", 0, about, 0, 0 },
5005 /* js command */
5006 { "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5007 { "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5008 { "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5009 { "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5010 { "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5011 { "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5012 { "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5013 { "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5014 { "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5015 { "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5016 { "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5018 /* cookie command */
5019 { "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5020 { "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5021 { "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
5022 { "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
5023 { "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5024 { "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
5025 { "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
5026 { "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
5027 { "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5028 { "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
5029 { "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
5031 /* toplevel (domain) command */
5032 { "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5033 { "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
5035 /* cookie jar */
5036 { "cookiejar", 0, xtp_page_cl, 0, 0 },
5038 /* cert command */
5039 { "cert", 0, cert_cmd, XT_SHOW, 0 },
5040 { "save", 1, cert_cmd, XT_SAVE, 0 },
5041 { "show", 1, cert_cmd, XT_SHOW, 0 },
5043 { "ca", 0, ca_cmd, 0, 0 },
5044 { "downloadmgr", 0, xtp_page_dl, 0, 0 },
5045 { "dl", 0, xtp_page_dl, 0, 0 },
5046 { "h", 0, xtp_page_hl, 0, 0 },
5047 { "history", 0, xtp_page_hl, 0, 0 },
5048 { "home", 0, go_home, 0, 0 },
5049 { "restart", 0, restart, 0, 0 },
5050 { "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
5051 { "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
5052 { "statushide", 0, statusaction, XT_STATUSBAR_HIDE, 0 },
5053 { "statusshow", 0, statusaction, XT_STATUSBAR_SHOW, 0 },
5055 { "print", 0, print_page, 0, 0 },
5057 /* tabs */
5058 { "focusin", 0, resizetab, 1, 0 },
5059 { "focusout", 0, resizetab, -1, 0 },
5060 { "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
5061 { "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
5062 { "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
5063 { "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
5064 { "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5065 { "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
5066 { "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
5067 { "tablast", 0, movetab, XT_TAB_LAST, 0 },
5068 { "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
5069 { "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
5070 { "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
5071 { "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
5072 { "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
5073 { "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
5075 /* command aliases (handy when -S flag is used) */
5076 { "promptopen", 0, command, XT_CMD_OPEN, 0 },
5077 { "promptopencurrent", 0, command, XT_CMD_OPEN_CURRENT, 0 },
5078 { "prompttabnew", 0, command, XT_CMD_TABNEW, 0 },
5079 { "prompttabnewcurrent",0, command, XT_CMD_TABNEW_CURRENT, 0 },
5081 /* settings */
5082 { "set", 0, set, 0, 0 },
5083 { "fullscreen", 0, fullscreen, 0, 0 },
5084 { "f", 0, fullscreen, 0, 0 },
5086 /* sessions */
5087 { "session", 0, session_cmd, XT_SHOW, 0 },
5088 { "delete", 1, session_cmd, XT_DELETE, XT_USERARG },
5089 { "open", 1, session_cmd, XT_OPEN, XT_USERARG },
5090 { "save", 1, session_cmd, XT_SAVE, XT_USERARG },
5091 { "show", 1, session_cmd, XT_SHOW, 0 },
5094 struct {
5095 int index;
5096 int len;
5097 gchar *list[256];
5098 } cmd_status = {-1, 0};
5100 gboolean
5101 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5103 struct karg a;
5105 hide_oops(t);
5107 if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
5108 /* go backward */
5109 a.i = XT_NAV_BACK;
5110 navaction(t, &a);
5112 return (TRUE);
5113 } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
5114 /* go forward */
5115 a.i = XT_NAV_FORWARD;
5116 navaction(t, &a);
5118 return (TRUE);
5121 return (FALSE);
5124 gboolean
5125 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
5127 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
5129 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
5130 delete_tab(t);
5132 return (FALSE);
5136 * cancel, remove, etc. downloads
5138 void
5139 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
5141 struct download find, *d = NULL;
5143 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
5145 /* some commands require a valid download id */
5146 if (cmd != XT_XTP_DL_LIST) {
5147 /* lookup download in question */
5148 find.id = id;
5149 d = RB_FIND(download_list, &downloads, &find);
5151 if (d == NULL) {
5152 show_oops(t, "%s: no such download", __func__);
5153 return;
5157 /* decide what to do */
5158 switch (cmd) {
5159 case XT_XTP_DL_CANCEL:
5160 webkit_download_cancel(d->download);
5161 break;
5162 case XT_XTP_DL_REMOVE:
5163 webkit_download_cancel(d->download); /* just incase */
5164 g_object_unref(d->download);
5165 RB_REMOVE(download_list, &downloads, d);
5166 break;
5167 case XT_XTP_DL_LIST:
5168 /* Nothing */
5169 break;
5170 default:
5171 show_oops(t, "%s: unknown command", __func__);
5172 break;
5174 xtp_page_dl(t, NULL);
5178 * Actions on history, only does one thing for now, but
5179 * we provide the function for future actions
5181 void
5182 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
5184 struct history *h, *next;
5185 int i = 1;
5187 switch (cmd) {
5188 case XT_XTP_HL_REMOVE:
5189 /* walk backwards, as listed in reverse */
5190 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
5191 next = RB_PREV(history_list, &hl, h);
5192 if (id == i) {
5193 RB_REMOVE(history_list, &hl, h);
5194 g_free((gpointer) h->title);
5195 g_free((gpointer) h->uri);
5196 g_free(h);
5197 break;
5199 i++;
5201 break;
5202 case XT_XTP_HL_LIST:
5203 /* Nothing - just xtp_page_hl() below */
5204 break;
5205 default:
5206 show_oops(t, "%s: unknown command", __func__);
5207 break;
5210 xtp_page_hl(t, NULL);
5213 /* remove a favorite */
5214 void
5215 remove_favorite(struct tab *t, int index)
5217 char file[PATH_MAX], *title, *uri = NULL;
5218 char *new_favs, *tmp;
5219 FILE *f;
5220 int i;
5221 size_t len, lineno;
5223 /* open favorites */
5224 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5226 if ((f = fopen(file, "r")) == NULL) {
5227 show_oops(t, "%s: can't open favorites: %s",
5228 __func__, strerror(errno));
5229 return;
5232 /* build a string which will become the new favroites file */
5233 new_favs = g_strdup("");
5235 for (i = 1;;) {
5236 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
5237 if (feof(f) || ferror(f))
5238 break;
5239 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
5240 if (len == 0) {
5241 free(title);
5242 title = NULL;
5243 continue;
5246 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
5247 if (feof(f) || ferror(f)) {
5248 show_oops(t, "%s: can't parse favorites %s",
5249 __func__, strerror(errno));
5250 goto clean;
5254 /* as long as this isn't the one we are deleting add to file */
5255 if (i != index) {
5256 tmp = new_favs;
5257 new_favs = g_strdup_printf("%s%s\n%s\n",
5258 new_favs, title, uri);
5259 g_free(tmp);
5262 free(uri);
5263 uri = NULL;
5264 free(title);
5265 title = NULL;
5266 i++;
5268 fclose(f);
5270 /* write back new favorites file */
5271 if ((f = fopen(file, "w")) == NULL) {
5272 show_oops(t, "%s: can't open favorites: %s",
5273 __func__, strerror(errno));
5274 goto clean;
5277 fwrite(new_favs, strlen(new_favs), 1, f);
5278 fclose(f);
5280 clean:
5281 if (uri)
5282 free(uri);
5283 if (title)
5284 free(title);
5286 g_free(new_favs);
5289 void
5290 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
5292 switch (cmd) {
5293 case XT_XTP_FL_LIST:
5294 /* nothing, just the below call to xtp_page_fl() */
5295 break;
5296 case XT_XTP_FL_REMOVE:
5297 remove_favorite(t, arg);
5298 break;
5299 default:
5300 show_oops(t, "%s: invalid favorites command", __func__);
5301 break;
5304 xtp_page_fl(t, NULL);
5307 void
5308 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
5310 switch (cmd) {
5311 case XT_XTP_CL_LIST:
5312 /* nothing, just xtp_page_cl() */
5313 break;
5314 case XT_XTP_CL_REMOVE:
5315 remove_cookie(arg);
5316 break;
5317 default:
5318 show_oops(t, "%s: unknown cookie xtp command", __func__);
5319 break;
5322 xtp_page_cl(t, NULL);
5325 /* link an XTP class to it's session key and handler function */
5326 struct xtp_despatch {
5327 uint8_t xtp_class;
5328 char **session_key;
5329 void (*handle_func)(struct tab *, uint8_t, int);
5332 struct xtp_despatch xtp_despatches[] = {
5333 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
5334 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
5335 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
5336 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
5337 { XT_XTP_INVALID, NULL, NULL }
5341 * is the url xtp protocol? (xxxt://)
5342 * if so, parse and despatch correct bahvior
5345 parse_xtp_url(struct tab *t, const char *url)
5347 char *dup = NULL, *p, *last;
5348 uint8_t n_tokens = 0;
5349 char *tokens[4] = {NULL, NULL, NULL, ""};
5350 struct xtp_despatch *dsp, *dsp_match = NULL;
5351 uint8_t req_class;
5352 int ret = FALSE;
5355 * tokens array meaning:
5356 * tokens[0] = class
5357 * tokens[1] = session key
5358 * tokens[2] = action
5359 * tokens[3] = optional argument
5362 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
5364 /*xtp tab meaning is normal unless proven special */
5365 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5367 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
5368 goto clean;
5370 dup = g_strdup(url + strlen(XT_XTP_STR));
5372 /* split out the url */
5373 for ((p = strtok_r(dup, "/", &last)); p;
5374 (p = strtok_r(NULL, "/", &last))) {
5375 if (n_tokens < 4)
5376 tokens[n_tokens++] = p;
5379 /* should be atleast three fields 'class/seskey/command/arg' */
5380 if (n_tokens < 3)
5381 goto clean;
5383 dsp = xtp_despatches;
5384 req_class = atoi(tokens[0]);
5385 while (dsp->xtp_class) {
5386 if (dsp->xtp_class == req_class) {
5387 dsp_match = dsp;
5388 break;
5390 dsp++;
5393 /* did we find one atall? */
5394 if (dsp_match == NULL) {
5395 show_oops(t, "%s: no matching xtp despatch found", __func__);
5396 goto clean;
5399 /* check session key and call despatch function */
5400 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
5401 ret = TRUE; /* all is well, this was a valid xtp request */
5402 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
5405 clean:
5406 if (dup)
5407 g_free(dup);
5409 return (ret);
5414 void
5415 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
5417 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
5419 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
5421 if (t == NULL) {
5422 show_oops_s("activate_uri_entry_cb invalid parameters");
5423 return;
5426 if (uri == NULL) {
5427 show_oops(t, "activate_uri_entry_cb no uri");
5428 return;
5431 uri += strspn(uri, "\t ");
5433 /* if xxxt:// treat specially */
5434 if (parse_xtp_url(t, uri))
5435 return;
5437 /* otherwise continue to load page normally */
5438 load_uri(t, (gchar *)uri);
5439 focus_webview(t);
5442 void
5443 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
5445 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
5446 char *newuri = NULL;
5447 gchar *enc_search;
5449 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
5451 if (t == NULL) {
5452 show_oops_s("activate_search_entry_cb invalid parameters");
5453 return;
5456 if (search_string == NULL) {
5457 show_oops(t, "no search_string");
5458 return;
5461 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
5462 newuri = g_strdup_printf(search_string, enc_search);
5463 g_free(enc_search);
5465 webkit_web_view_load_uri(t->wv, newuri);
5466 focus_webview(t);
5468 if (newuri)
5469 g_free(newuri);
5472 void
5473 check_and_set_js(const gchar *uri, struct tab *t)
5475 struct domain *d = NULL;
5476 int es = 0;
5478 if (uri == NULL || t == NULL)
5479 return;
5481 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
5482 es = 0;
5483 else
5484 es = 1;
5486 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
5487 es ? "enable" : "disable", uri);
5489 g_object_set(G_OBJECT(t->settings),
5490 "enable-scripts", es, (char *)NULL);
5491 g_object_set(G_OBJECT(t->settings),
5492 "javascript-can-open-windows-automatically", es, (char *)NULL);
5493 webkit_web_view_set_settings(t->wv, t->settings);
5495 button_set_stockid(t->js_toggle,
5496 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
5499 void
5500 show_ca_status(struct tab *t, const char *uri)
5502 WebKitWebFrame *frame;
5503 WebKitWebDataSource *source;
5504 WebKitNetworkRequest *request;
5505 SoupMessage *message;
5506 GdkColor color;
5507 gchar *col_str = XT_COLOR_WHITE;
5508 int r;
5510 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
5511 ssl_strict_certs, ssl_ca_file, uri);
5513 if (uri == NULL)
5514 goto done;
5515 if (ssl_ca_file == NULL) {
5516 if (g_str_has_prefix(uri, "http://"))
5517 goto done;
5518 if (g_str_has_prefix(uri, "https://")) {
5519 col_str = XT_COLOR_RED;
5520 goto done;
5522 return;
5524 if (g_str_has_prefix(uri, "http://") ||
5525 !g_str_has_prefix(uri, "https://"))
5526 goto done;
5528 frame = webkit_web_view_get_main_frame(t->wv);
5529 source = webkit_web_frame_get_data_source(frame);
5530 request = webkit_web_data_source_get_request(source);
5531 message = webkit_network_request_get_message(request);
5533 if (message && (soup_message_get_flags(message) &
5534 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
5535 col_str = XT_COLOR_GREEN;
5536 goto done;
5537 } else {
5538 r = load_compare_cert(t, NULL);
5539 if (r == 0)
5540 col_str = XT_COLOR_BLUE;
5541 else if (r == 1)
5542 col_str = XT_COLOR_YELLOW;
5543 else
5544 col_str = XT_COLOR_RED;
5545 goto done;
5547 done:
5548 if (col_str) {
5549 gdk_color_parse(col_str, &color);
5550 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
5552 if (!strcmp(col_str, XT_COLOR_WHITE)) {
5553 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5554 &color);
5555 gdk_color_parse(XT_COLOR_BLACK, &color);
5556 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5557 &color);
5558 } else {
5559 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
5560 &color);
5561 gdk_color_parse(XT_COLOR_BLACK, &color);
5562 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
5563 &color);
5568 void
5569 free_favicon(struct tab *t)
5571 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
5572 __func__, t->icon_download, t->icon_request);
5574 if (t->icon_request)
5575 g_object_unref(t->icon_request);
5576 if (t->icon_dest_uri)
5577 g_free(t->icon_dest_uri);
5579 t->icon_request = NULL;
5580 t->icon_dest_uri = NULL;
5583 void
5584 xt_icon_from_name(struct tab *t, gchar *name)
5586 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5587 GTK_ENTRY_ICON_PRIMARY, "text-html");
5588 if (show_url == 0)
5589 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5590 GTK_ENTRY_ICON_PRIMARY, "text-html");
5591 else
5592 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5593 GTK_ENTRY_ICON_PRIMARY, NULL);
5596 void
5597 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
5599 GdkPixbuf *pb_scaled;
5601 if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
5602 pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16, GDK_INTERP_BILINEAR);
5603 else
5604 pb_scaled = pb;
5606 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5607 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5608 if (show_url == 0)
5609 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->statusbar),
5610 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
5611 else
5612 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->statusbar),
5613 GTK_ENTRY_ICON_PRIMARY, NULL);
5615 if (pb_scaled != pb)
5616 g_object_unref(pb_scaled);
5619 void
5620 xt_icon_from_file(struct tab *t, char *file)
5622 GdkPixbuf *pb;
5624 if (g_str_has_prefix(file, "file://"))
5625 file += strlen("file://");
5627 pb = gdk_pixbuf_new_from_file(file, NULL);
5628 if (pb) {
5629 xt_icon_from_pixbuf(t, pb);
5630 g_object_unref(pb);
5631 } else
5632 xt_icon_from_name(t, "text-html");
5635 gboolean
5636 is_valid_icon(char *file)
5638 gboolean valid = 0;
5639 const char *mime_type;
5640 GFileInfo *fi;
5641 GFile *gf;
5643 gf = g_file_new_for_path(file);
5644 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
5645 NULL, NULL);
5646 mime_type = g_file_info_get_content_type(fi);
5647 valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
5648 g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
5649 g_strcmp0(mime_type, "image/png") == 0 ||
5650 g_strcmp0(mime_type, "image/gif") == 0 ||
5651 g_strcmp0(mime_type, "application/octet-stream") == 0;
5652 g_object_unref(fi);
5653 g_object_unref(gf);
5655 return (valid);
5658 void
5659 set_favicon_from_file(struct tab *t, char *file)
5661 struct stat sb;
5663 if (t == NULL || file == NULL)
5664 return;
5666 if (g_str_has_prefix(file, "file://"))
5667 file += strlen("file://");
5668 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5670 if (!stat(file, &sb)) {
5671 if (sb.st_size == 0 || !is_valid_icon(file)) {
5672 /* corrupt icon so trash it */
5673 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5674 __func__, file);
5675 unlink(file);
5676 /* no need to set icon to default here */
5677 return;
5680 xt_icon_from_file(t, file);
5683 void
5684 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5685 WebKitWebView *wv)
5687 WebKitDownloadStatus status = webkit_download_get_status(download);
5688 struct tab *tt = NULL, *t = NULL;
5691 * find the webview instead of passing in the tab as it could have been
5692 * deleted from underneath us.
5694 TAILQ_FOREACH(tt, &tabs, entry) {
5695 if (tt->wv == wv) {
5696 t = tt;
5697 break;
5700 if (t == NULL)
5701 return;
5703 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5704 __func__, t->tab_id, status);
5706 switch (status) {
5707 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5708 /* -1 */
5709 t->icon_download = NULL;
5710 free_favicon(t);
5711 break;
5712 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5713 /* 0 */
5714 break;
5715 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5716 /* 1 */
5717 break;
5718 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5719 /* 2 */
5720 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5721 __func__, t->tab_id);
5722 t->icon_download = NULL;
5723 free_favicon(t);
5724 break;
5725 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5726 /* 3 */
5728 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5729 __func__, t->icon_dest_uri);
5730 set_favicon_from_file(t, t->icon_dest_uri);
5731 /* these will be freed post callback */
5732 t->icon_request = NULL;
5733 t->icon_download = NULL;
5734 break;
5735 default:
5736 break;
5740 void
5741 abort_favicon_download(struct tab *t)
5743 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
5745 if (!WEBKIT_CHECK_VERSION(1, 4, 0)) {
5746 if (t->icon_download) {
5747 g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
5748 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5749 webkit_download_cancel(t->icon_download);
5750 t->icon_download = NULL;
5751 } else
5752 free_favicon(t);
5755 xt_icon_from_name(t, "text-html");
5758 void
5759 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5761 GdkPixbuf *pb;
5762 gchar *name_hash, file[PATH_MAX];
5763 struct stat sb;
5765 DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
5767 if (uri == NULL || t == NULL)
5768 return;
5770 if (WEBKIT_CHECK_VERSION(1, 4, 0)) {
5771 /* take icon from WebKitIconDatabase */
5772 pb = webkit_web_view_get_icon_pixbuf(wv);
5773 if (pb) {
5774 xt_icon_from_pixbuf(t, pb);
5775 g_object_unref(pb);
5776 } else
5777 xt_icon_from_name(t, "text-html");
5779 } else if (WEBKIT_CHECK_VERSION(1, 1, 18)) {
5780 /* download icon to cache dir */
5781 if (t->icon_request) {
5782 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5783 return;
5786 /* check to see if we got the icon in cache */
5787 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5788 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5789 g_free(name_hash);
5791 if (!stat(file, &sb)) {
5792 if (sb.st_size > 0) {
5793 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5794 __func__, file);
5795 set_favicon_from_file(t, file);
5796 return;
5799 /* corrupt icon so trash it */
5800 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5801 __func__, file);
5802 unlink(file);
5805 /* create download for icon */
5806 t->icon_request = webkit_network_request_new(uri);
5807 if (t->icon_request == NULL) {
5808 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5809 __func__, uri);
5810 return;
5813 t->icon_download = webkit_download_new(t->icon_request);
5814 if (t->icon_download == NULL) {
5815 fprintf(stderr, "%s: icon_download", __func__);
5816 return;
5819 /* we have to free icon_dest_uri later */
5820 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5821 webkit_download_set_destination_uri(t->icon_download,
5822 t->icon_dest_uri);
5824 if (webkit_download_get_status(t->icon_download) ==
5825 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5826 fprintf(stderr, "%s: download failed to start", __func__);
5827 g_object_unref(t->icon_request);
5828 g_free(t->icon_dest_uri);
5829 t->icon_request = NULL;
5830 t->icon_dest_uri = NULL;
5831 return;
5834 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5835 G_CALLBACK(favicon_download_status_changed_cb), t->wv);
5837 webkit_download_start(t->icon_download);
5841 void
5842 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5844 const gchar *set = NULL, *uri = NULL, *title = NULL;
5845 struct history *h, find;
5846 const gchar *s_loading;
5847 struct karg a;
5849 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d %s\n",
5850 webkit_web_view_get_load_status(wview), get_uri(wview) ? get_uri(wview) : "NOTHING");
5852 if (t == NULL) {
5853 show_oops_s("notify_load_status_cb invalid paramters");
5854 return;
5857 switch (webkit_web_view_get_load_status(wview)) {
5858 case WEBKIT_LOAD_PROVISIONAL:
5859 /* 0 */
5860 abort_favicon_download(t);
5861 #if GTK_CHECK_VERSION(2, 20, 0)
5862 gtk_widget_show(t->spinner);
5863 gtk_spinner_start(GTK_SPINNER(t->spinner));
5864 #endif
5865 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5867 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5869 /* take focus if we are visible */
5870 focus_webview(t);
5871 t->focus_wv = 1;
5873 break;
5875 case WEBKIT_LOAD_COMMITTED:
5876 /* 1 */
5877 if ((uri = get_uri(wview)) != NULL) {
5878 if (strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
5879 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5881 if (t->status) {
5882 g_free(t->status);
5883 t->status = NULL;
5885 set_status(t, (char *)uri, XT_STATUS_LOADING);
5888 /* check if js white listing is enabled */
5889 if (enable_js_whitelist) {
5890 uri = get_uri(wview);
5891 check_and_set_js(uri, t);
5894 if (t->styled)
5895 apply_style(t);
5897 show_ca_status(t, uri);
5899 /* we know enough to autosave the session */
5900 if (session_autosave) {
5901 a.s = NULL;
5902 save_tabs(t, &a);
5904 break;
5906 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5907 /* 3 */
5908 break;
5910 case WEBKIT_LOAD_FINISHED:
5911 /* 2 */
5912 uri = get_uri(wview);
5913 if (uri == NULL)
5914 return;
5916 if (!strncmp(uri, "http://", strlen("http://")) ||
5917 !strncmp(uri, "https://", strlen("https://")) ||
5918 !strncmp(uri, "file://", strlen("file://"))) {
5919 find.uri = uri;
5920 h = RB_FIND(history_list, &hl, &find);
5921 if (!h) {
5922 title = webkit_web_view_get_title(wview);
5923 set = title ? title: uri;
5924 h = g_malloc(sizeof *h);
5925 h->uri = g_strdup(uri);
5926 h->title = g_strdup(set);
5927 RB_INSERT(history_list, &hl, h);
5928 completion_add_uri(h->uri);
5929 update_history_tabs(NULL);
5933 set_status(t, (char *)uri, XT_STATUS_URI);
5934 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5935 case WEBKIT_LOAD_FAILED:
5936 /* 4 */
5937 #endif
5938 #if GTK_CHECK_VERSION(2, 20, 0)
5939 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5940 gtk_widget_hide(t->spinner);
5941 #endif
5942 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
5943 if (s_loading && !strcmp(s_loading, "Loading"))
5944 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5945 default:
5946 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
5947 break;
5950 if (t->item)
5951 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
5952 else
5953 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
5954 webkit_web_view_can_go_back(wview));
5956 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
5957 webkit_web_view_can_go_forward(wview));
5960 void
5961 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5963 const gchar *set = NULL, *title = NULL;
5965 title = webkit_web_view_get_title(wview);
5966 set = title ? title: get_uri(wview);
5967 if (set) {
5968 gtk_label_set_text(GTK_LABEL(t->label), set);
5969 gtk_window_set_title(GTK_WINDOW(main_window), set);
5970 } else {
5971 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5972 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
5976 void
5977 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5979 run_script(t, JS_HINTING);
5982 void
5983 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
5985 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
5986 progress == 100 ? 0 : (double)progress / 100);
5987 if (show_url == 0) {
5988 gtk_entry_set_progress_fraction(GTK_ENTRY(t->statusbar),
5989 progress == 100 ? 0 : (double)progress / 100);
5994 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5995 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5996 WebKitWebPolicyDecision *pd, struct tab *t)
5998 char *uri;
5999 WebKitWebNavigationReason reason;
6000 struct domain *d = NULL;
6002 if (t == NULL) {
6003 show_oops_s("webview_npd_cb invalid parameters");
6004 return (FALSE);
6007 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
6008 t->ctrl_click,
6009 webkit_network_request_get_uri(request));
6011 uri = (char *)webkit_network_request_get_uri(request);
6013 /* if this is an xtp url, we don't load anything else */
6014 if (parse_xtp_url(t, uri))
6015 return (TRUE);
6017 if (t->ctrl_click) {
6018 t->ctrl_click = 0;
6019 create_new_tab(uri, NULL, ctrl_click_focus, -1);
6020 webkit_web_policy_decision_ignore(pd);
6021 return (TRUE); /* we made the decission */
6025 * This is a little hairy but it comes down to this:
6026 * when we run in whitelist mode we have to assist the browser in
6027 * opening the URL that it would have opened in a new tab.
6029 reason = webkit_web_navigation_action_get_reason(na);
6030 if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
6031 if (enable_scripts == 0 && enable_cookie_whitelist == 1)
6032 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6033 load_uri(t, uri);
6035 webkit_web_policy_decision_use(pd);
6036 return (TRUE); /* we made the decission */
6039 return (FALSE);
6042 WebKitWebView *
6043 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
6045 struct tab *tt;
6046 struct domain *d = NULL;
6047 const gchar *uri;
6048 WebKitWebView *webview = NULL;
6050 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
6051 webkit_web_view_get_uri(wv));
6053 if (tabless) {
6054 /* open in current tab */
6055 webview = t->wv;
6056 } else if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6057 uri = webkit_web_view_get_uri(wv);
6058 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6059 return (NULL);
6061 tt = create_new_tab(NULL, NULL, 1, -1);
6062 webview = tt->wv;
6063 } else if (enable_scripts == 1) {
6064 tt = create_new_tab(NULL, NULL, 1, -1);
6065 webview = tt->wv;
6068 return (webview);
6071 gboolean
6072 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
6074 const gchar *uri;
6075 struct domain *d = NULL;
6077 DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
6079 if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
6080 uri = webkit_web_view_get_uri(wv);
6081 if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
6082 return (FALSE);
6084 delete_tab(t);
6085 } else if (enable_scripts == 1)
6086 delete_tab(t);
6088 return (TRUE);
6092 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
6094 /* we can not eat the event without throwing gtk off so defer it */
6096 /* catch middle click */
6097 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
6098 t->ctrl_click = 1;
6099 goto done;
6102 /* catch ctrl click */
6103 if (e->type == GDK_BUTTON_RELEASE &&
6104 CLEAN(e->state) == GDK_CONTROL_MASK)
6105 t->ctrl_click = 1;
6106 else
6107 t->ctrl_click = 0;
6108 done:
6109 return (XT_CB_PASSTHROUGH);
6113 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
6115 struct mime_type *m;
6117 m = find_mime_type(mime_type);
6118 if (m == NULL)
6119 return (1);
6120 if (m->mt_download)
6121 return (1);
6123 switch (fork()) {
6124 case -1:
6125 show_oops(t, "can't fork mime handler");
6126 /* NOTREACHED */
6127 case 0:
6128 break;
6129 default:
6130 return (0);
6133 /* child */
6134 execlp(m->mt_action, m->mt_action,
6135 webkit_network_request_get_uri(request), (void *)NULL);
6137 _exit(0);
6139 /* NOTREACHED */
6140 return (0);
6143 const gchar *
6144 get_mime_type(char *file)
6146 const char *mime_type;
6147 GFileInfo *fi;
6148 GFile *gf;
6150 if (g_str_has_prefix(file, "file://"))
6151 file += strlen("file://");
6153 gf = g_file_new_for_path(file);
6154 fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
6155 NULL, NULL);
6156 mime_type = g_file_info_get_content_type(fi);
6157 g_object_unref(fi);
6158 g_object_unref(gf);
6160 return (mime_type);
6164 run_download_mimehandler(char *mime_type, char *file)
6166 struct mime_type *m;
6168 m = find_mime_type(mime_type);
6169 if (m == NULL)
6170 return (1);
6172 switch (fork()) {
6173 case -1:
6174 show_oops_s("can't fork download mime handler");
6175 /* NOTREACHED */
6176 case 0:
6177 break;
6178 default:
6179 return (0);
6182 /* child */
6183 if (g_str_has_prefix(file, "file://"))
6184 file += strlen("file://");
6185 execlp(m->mt_action, m->mt_action, file, (void *)NULL);
6187 _exit(0);
6189 /* NOTREACHED */
6190 return (0);
6193 void
6194 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
6195 WebKitWebView *wv)
6197 WebKitDownloadStatus status;
6198 const gchar *file = NULL, *mime = NULL;
6200 if (download == NULL)
6201 return;
6202 status = webkit_download_get_status(download);
6203 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
6204 return;
6206 file = webkit_download_get_destination_uri(download);
6207 if (file == NULL)
6208 return;
6209 mime = get_mime_type((char *)file);
6210 if (mime == NULL)
6211 return;
6213 run_download_mimehandler((char *)mime, (char *)file);
6217 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
6218 WebKitNetworkRequest *request, char *mime_type,
6219 WebKitWebPolicyDecision *decision, struct tab *t)
6221 if (t == NULL) {
6222 show_oops_s("webview_mimetype_cb invalid parameters");
6223 return (FALSE);
6226 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
6227 t->tab_id, mime_type);
6229 if (run_mimehandler(t, mime_type, request) == 0) {
6230 webkit_web_policy_decision_ignore(decision);
6231 focus_webview(t);
6232 return (TRUE);
6235 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
6236 webkit_web_policy_decision_download(decision);
6237 return (TRUE);
6240 return (FALSE);
6244 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
6245 struct tab *t)
6247 const gchar *filename;
6248 char *uri = NULL;
6249 struct download *download_entry;
6250 int ret = TRUE;
6252 if (wk_download == NULL || t == NULL) {
6253 show_oops_s("%s invalid parameters", __func__);
6254 return (FALSE);
6257 filename = webkit_download_get_suggested_filename(wk_download);
6258 if (filename == NULL)
6259 return (FALSE); /* abort download */
6261 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
6263 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
6264 "local %s\n", __func__, t->tab_id, filename, uri);
6266 webkit_download_set_destination_uri(wk_download, uri);
6268 if (webkit_download_get_status(wk_download) ==
6269 WEBKIT_DOWNLOAD_STATUS_ERROR) {
6270 show_oops(t, "%s: download failed to start", __func__);
6271 ret = FALSE;
6272 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
6273 } else {
6274 /* connect "download first" mime handler */
6275 g_signal_connect(G_OBJECT(wk_download), "notify::status",
6276 G_CALLBACK(download_status_changed_cb), NULL);
6278 download_entry = g_malloc(sizeof(struct download));
6279 download_entry->download = wk_download;
6280 download_entry->tab = t;
6281 download_entry->id = next_download_id++;
6282 RB_INSERT(download_list, &downloads, download_entry);
6283 /* get from history */
6284 g_object_ref(wk_download);
6285 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
6286 show_oops(t, "Download of '%s' started...",
6287 basename(webkit_download_get_destination_uri(wk_download)));
6290 if (uri)
6291 g_free(uri);
6293 /* sync other download manager tabs */
6294 update_download_tabs(NULL);
6297 * NOTE: never redirect/render the current tab before this
6298 * function returns. This will cause the download to never start.
6300 return (ret); /* start download */
6303 void
6304 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
6306 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
6308 if (t == NULL) {
6309 show_oops_s("webview_hover_cb");
6310 return;
6313 if (uri)
6314 set_status(t, uri, XT_STATUS_LINK);
6315 else {
6316 if (t->status)
6317 set_status(t, t->status, XT_STATUS_NOTHING);
6321 gboolean
6322 handle_keypress(struct tab *t, GdkEventKey *e, int entry)
6324 struct key_binding *k;
6326 TAILQ_FOREACH(k, &kbl, entry)
6327 if (e->keyval == k->key && (entry ? k->use_in_entry : 1)) {
6328 if (k->mask == 0) {
6329 if ((e->state & (CTRL | MOD1)) == 0)
6330 return (cmd_execute(t, k->cmd));
6331 } else if ((e->state & k->mask) == k->mask) {
6332 return (cmd_execute(t, k->cmd));
6336 return (XT_CB_PASSTHROUGH);
6340 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
6342 char s[2], buf[128];
6343 const char *errstr = NULL;
6344 long long link;
6346 /* don't use w directly; use t->whatever instead */
6348 if (t == NULL) {
6349 show_oops_s("wv_keypress_after_cb");
6350 return (XT_CB_PASSTHROUGH);
6353 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
6354 e->keyval, e->state, t);
6356 if (t->hints_on) {
6357 /* ESC */
6358 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
6359 disable_hints(t);
6360 return (XT_CB_HANDLED);
6363 /* RETURN */
6364 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
6365 link = strtonum(t->hint_num, 1, 1000, &errstr);
6366 if (errstr) {
6367 /* we have a string */
6368 } else {
6369 /* we have a number */
6370 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
6371 t->hint_num);
6372 run_script(t, buf);
6374 disable_hints(t);
6377 /* BACKSPACE */
6378 /* XXX unfuck this */
6379 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
6380 if (t->hint_mode == XT_HINT_NUMERICAL) {
6381 /* last input was numerical */
6382 int l;
6383 l = strlen(t->hint_num);
6384 if (l > 0) {
6385 l--;
6386 if (l == 0) {
6387 disable_hints(t);
6388 enable_hints(t);
6389 } else {
6390 t->hint_num[l] = '\0';
6391 goto num;
6394 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
6395 /* last input was alphanumerical */
6396 int l;
6397 l = strlen(t->hint_buf);
6398 if (l > 0) {
6399 l--;
6400 if (l == 0) {
6401 disable_hints(t);
6402 enable_hints(t);
6403 } else {
6404 t->hint_buf[l] = '\0';
6405 goto anum;
6408 } else {
6409 /* bogus */
6410 disable_hints(t);
6414 /* numerical input */
6415 if (CLEAN(e->state) == 0 &&
6416 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
6417 snprintf(s, sizeof s, "%c", e->keyval);
6418 strlcat(t->hint_num, s, sizeof t->hint_num);
6419 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
6420 t->hint_num);
6421 num:
6422 link = strtonum(t->hint_num, 1, 1000, &errstr);
6423 if (errstr) {
6424 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
6425 disable_hints(t);
6426 } else {
6427 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
6428 t->hint_num);
6429 t->hint_mode = XT_HINT_NUMERICAL;
6430 run_script(t, buf);
6433 /* empty the counter buffer */
6434 bzero(t->hint_buf, sizeof t->hint_buf);
6435 return (XT_CB_HANDLED);
6438 /* alphanumerical input */
6439 if (
6440 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
6441 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
6442 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
6443 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
6444 snprintf(s, sizeof s, "%c", e->keyval);
6445 strlcat(t->hint_buf, s, sizeof t->hint_buf);
6446 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
6447 t->hint_buf);
6448 anum:
6449 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
6450 run_script(t, buf);
6452 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
6453 t->hint_buf);
6454 t->hint_mode = XT_HINT_ALPHANUM;
6455 run_script(t, buf);
6457 /* empty the counter buffer */
6458 bzero(t->hint_num, sizeof t->hint_num);
6459 return (XT_CB_HANDLED);
6462 return (XT_CB_HANDLED);
6463 } else {
6464 /* prefix input*/
6465 snprintf(s, sizeof s, "%c", e->keyval);
6466 if (CLEAN(e->state) == 0 && isdigit(s[0])) {
6467 cmd_prefix = 10 * cmd_prefix + atoi(s);
6471 return (handle_keypress(t, e, 0));
6475 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6477 hide_oops(t);
6479 return (XT_CB_PASSTHROUGH);
6483 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6485 const gchar *c = gtk_entry_get_text(w);
6486 GdkColor color;
6487 int forward = TRUE;
6489 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6490 e->keyval, e->state, t);
6492 if (t == NULL) {
6493 show_oops_s("cmd_keyrelease_cb invalid parameters");
6494 return (XT_CB_PASSTHROUGH);
6497 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
6498 e->keyval, e->state, t);
6500 if (c[0] == ':')
6501 goto done;
6502 if (strlen(c) == 1) {
6503 webkit_web_view_unmark_text_matches(t->wv);
6504 goto done;
6507 if (c[0] == '/')
6508 forward = TRUE;
6509 else if (c[0] == '?')
6510 forward = FALSE;
6511 else
6512 goto done;
6514 /* search */
6515 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
6516 FALSE) {
6517 /* not found, mark red */
6518 gdk_color_parse(XT_COLOR_RED, &color);
6519 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6520 /* unmark and remove selection */
6521 webkit_web_view_unmark_text_matches(t->wv);
6522 /* my kingdom for a way to unselect text in webview */
6523 } else {
6524 /* found, highlight all */
6525 webkit_web_view_unmark_text_matches(t->wv);
6526 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
6527 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
6528 gdk_color_parse(XT_COLOR_WHITE, &color);
6529 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
6531 done:
6532 return (XT_CB_PASSTHROUGH);
6535 gboolean
6536 match_uri(const gchar *uri, const gchar *key) {
6537 gchar *voffset;
6538 size_t len;
6539 gboolean match = FALSE;
6541 len = strlen(key);
6543 if (!strncmp(key, uri, len))
6544 match = TRUE;
6545 else {
6546 voffset = strstr(uri, "/") + 2;
6547 if (!strncmp(key, voffset, len))
6548 match = TRUE;
6549 else if (g_str_has_prefix(voffset, "www.")) {
6550 voffset = voffset + strlen("www.");
6551 if (!strncmp(key, voffset, len))
6552 match = TRUE;
6556 return (match);
6559 void
6560 cmd_getlist(int id, char *key)
6562 int i, dep, c = 0;
6563 struct history *h;
6565 if (id >= 0 && (cmds[id].type & XT_URLARG)) {
6566 RB_FOREACH_REVERSE(h, history_list, &hl)
6567 if (match_uri(h->uri, key)) {
6568 cmd_status.list[c] = (char *)h->uri;
6569 if (++c > 255)
6570 break;
6573 cmd_status.len = c;
6574 return;
6577 dep = (id == -1) ? 0 : cmds[id].level + 1;
6579 for (i = id + 1; i < LENGTH(cmds); i++) {
6580 if(cmds[i].level < dep)
6581 break;
6582 if (cmds[i].level == dep && !strncmp(key, cmds[i].cmd, strlen(key)))
6583 cmd_status.list[c++] = cmds[i].cmd;
6587 cmd_status.len = c;
6590 char *
6591 cmd_getnext(int dir)
6593 cmd_status.index += dir;
6595 if (cmd_status.index < 0)
6596 cmd_status.index = cmd_status.len - 1;
6597 else if (cmd_status.index >= cmd_status.len)
6598 cmd_status.index = 0;
6600 return cmd_status.list[cmd_status.index];
6604 cmd_tokenize(char *s, char *tokens[])
6606 int i = 0;
6607 char *tok, *last;
6608 size_t len = strlen(s);
6609 bool blank = len == 0 || (len > 0 && s[len-1] == ' ');
6611 for (tok = strtok_r(s, " ", &last); tok && i < 3; tok = strtok_r(NULL, " ", &last), i++)
6612 tokens[i] = tok;
6614 if (blank && i < 3)
6615 tokens[i++] = "";
6617 return (i);
6620 void
6621 cmd_complete(struct tab *t, char *str, int dir)
6623 GtkEntry *w = GTK_ENTRY(t->cmd);
6624 int i, j, levels, c = 0, dep = 0, parent = -1, matchcount = 0;
6625 char *tok, *match, *s = g_strdup(str);
6626 char *tokens[3];
6627 char res[XT_MAX_URL_LENGTH + 32] = ":";
6628 char *sc = s;
6630 DNPRINTF(XT_D_CMD, "%s: complete %s\n", __func__, str);
6632 /* copy prefix*/
6633 for (i = 0; isdigit(s[i]); i++)
6634 res[i + 1] = s[i];
6636 for (; isspace(s[i]); i++)
6637 res[i + 1] = s[i];
6639 s += i;
6641 levels = cmd_tokenize(s, tokens);
6643 for (i = 0; i < levels - 1; i++) {
6644 tok = tokens[i];
6645 matchcount = 0;
6646 for (j = c; j < LENGTH(cmds); j++) {
6647 if (cmds[j].level < dep)
6648 break;
6649 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, strlen(tok))) {
6650 matchcount++;
6651 c = j + 1;
6652 if (strlen(tok) == strlen(cmds[j].cmd)) {
6653 matchcount = 1;
6654 break;
6659 if (matchcount == 1) {
6660 strlcat(res, tok, sizeof res);
6661 strlcat(res, " ", sizeof res);
6662 dep++;
6663 } else {
6664 g_free(sc);
6665 return;
6668 parent = c - 1;
6671 if (cmd_status.index == -1)
6672 cmd_getlist(parent, tokens[i]);
6674 if (cmd_status.len > 0) {
6675 match = cmd_getnext(dir);
6676 strlcat(res, match, sizeof res);
6677 gtk_entry_set_text(w, res);
6678 gtk_editable_set_position(GTK_EDITABLE(w), -1);
6681 g_free(sc);
6684 gboolean
6685 cmd_execute(struct tab *t, char *str)
6687 struct cmd *cmd = NULL;
6688 char *tok, *last, *s = g_strdup(str), *sc, prefixstr[4];
6689 int j, len, c = 0, dep = 0, matchcount = 0, prefix = -1;
6690 struct karg arg = {0, NULL, -1};
6691 int rv = XT_CB_PASSTHROUGH;
6693 sc = s;
6695 /* copy prefix*/
6696 for (j = 0; j<3 && isdigit(s[j]); j++)
6697 prefixstr[j]=s[j];
6699 prefixstr[j]='\0';
6701 s += j;
6702 while (isspace(s[0]))
6703 s++;
6705 if (strlen(s) > 0 && strlen(prefixstr) > 0)
6706 prefix = atoi(prefixstr);
6707 else
6708 s = sc;
6710 for (tok = strtok_r(s, " ", &last); tok;
6711 tok = strtok_r(NULL, " ", &last)) {
6712 matchcount = 0;
6713 for (j = c; j < LENGTH(cmds); j++) {
6714 if (cmds[j].level < dep)
6715 break;
6716 len = (tok[strlen(tok) - 1] == '!') ? strlen(tok) - 1: strlen(tok);
6717 if (cmds[j].level == dep && !strncmp(tok, cmds[j].cmd, len)) {
6718 matchcount++;
6719 c = j + 1;
6720 cmd = &cmds[j];
6721 if (len == strlen(cmds[j].cmd)) {
6722 matchcount = 1;
6723 break;
6727 if (matchcount == 1) {
6728 if (cmd->type > 0)
6729 goto execute_cmd;
6730 dep++;
6731 } else {
6732 show_oops(t, "Invalid command: %s", str);
6733 goto done;
6736 execute_cmd:
6737 arg.i = cmd->arg;
6739 if (prefix != -1)
6740 arg.p = prefix;
6741 else if (cmd_prefix > 0)
6742 arg.p = cmd_prefix;
6744 if (j > 0 && !(cmd->type & XT_PREFIX) && arg.p > -1) {
6745 show_oops(t, "No prefix allowed: %s", str);
6746 goto done;
6748 if (cmd->type > 1)
6749 arg.s = last ? g_strdup(last) : g_strdup("");
6750 if (cmd->type & XT_INTARG && last && strlen(last) > 0) {
6751 arg.p = atoi(arg.s);
6752 if (arg.p <= 0) {
6753 if (arg.s[0]=='0')
6754 show_oops(t, "Zero count");
6755 else
6756 show_oops(t, "Trailing characters");
6757 goto done;
6761 DNPRINTF(XT_D_CMD, "%s: prefix %d arg %s\n", __func__, arg.p, arg.s);
6763 cmd->func(t, &arg);
6765 rv = XT_CB_HANDLED;
6766 done:
6767 if (j > 0)
6768 cmd_prefix = 0;
6769 g_free(sc);
6770 if (arg.s)
6771 g_free(arg.s);
6773 return (rv);
6777 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6779 if (t == NULL) {
6780 show_oops_s("entry_key_cb invalid parameters");
6781 return (XT_CB_PASSTHROUGH);
6784 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
6785 e->keyval, e->state, t);
6787 hide_oops(t);
6789 if (e->keyval == GDK_Escape) {
6790 /* don't use focus_webview(t) because we want to type :cmds */
6791 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
6794 return (handle_keypress(t, e, 1));
6798 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
6800 int rv = XT_CB_HANDLED;
6801 const gchar *c = gtk_entry_get_text(w);
6803 if (t == NULL) {
6804 show_oops_s("cmd_keypress_cb parameters");
6805 return (XT_CB_PASSTHROUGH);
6808 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
6809 e->keyval, e->state, t);
6811 /* sanity */
6812 if (c == NULL)
6813 e->keyval = GDK_Escape;
6814 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6815 e->keyval = GDK_Escape;
6817 if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L && e->keyval != GDK_ISO_Left_Tab)
6818 cmd_status.index = -1;
6820 switch (e->keyval) {
6821 case GDK_Tab:
6822 if (c[0] == ':')
6823 cmd_complete(t, (char *)&c[1], 1);
6824 goto done;
6825 case GDK_ISO_Left_Tab:
6826 if (c[0] == ':')
6827 cmd_complete(t, (char *)&c[1], -1);
6829 goto done;
6830 case GDK_BackSpace:
6831 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
6832 break;
6833 /* FALLTHROUGH */
6834 case GDK_Escape:
6835 hide_cmd(t);
6836 focus_webview(t);
6838 /* cancel search */
6839 if (c[0] == '/' || c[0] == '?')
6840 webkit_web_view_unmark_text_matches(t->wv);
6841 goto done;
6844 rv = XT_CB_PASSTHROUGH;
6845 done:
6846 return (rv);
6850 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
6852 if (t == NULL) {
6853 show_oops_s("cmd_focusout_cb invalid parameters");
6854 return (XT_CB_PASSTHROUGH);
6856 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
6858 hide_cmd(t);
6859 hide_oops(t);
6861 if (show_url == 0 || t->focus_wv)
6862 focus_webview(t);
6863 else
6864 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6866 return (XT_CB_PASSTHROUGH);
6869 void
6870 cmd_activate_cb(GtkEntry *entry, struct tab *t)
6872 char *s;
6873 const gchar *c = gtk_entry_get_text(entry);
6875 if (t == NULL) {
6876 show_oops_s("cmd_activate_cb invalid parameters");
6877 return;
6880 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
6882 hide_cmd(t);
6884 /* sanity */
6885 if (c == NULL)
6886 goto done;
6887 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
6888 goto done;
6889 if (strlen(c) < 2)
6890 goto done;
6891 s = (char *)&c[1];
6893 if (c[0] == '/' || c[0] == '?') {
6894 if (t->search_text) {
6895 g_free(t->search_text);
6896 t->search_text = NULL;
6899 t->search_text = g_strdup(s);
6900 if (global_search)
6901 g_free(global_search);
6902 global_search = g_strdup(s);
6903 t->search_forward = c[0] == '/';
6905 goto done;
6908 cmd_execute(t, s);
6910 done:
6911 return;
6914 void
6915 backward_cb(GtkWidget *w, struct tab *t)
6917 struct karg a;
6919 if (t == NULL) {
6920 show_oops_s("backward_cb invalid parameters");
6921 return;
6924 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
6926 a.i = XT_NAV_BACK;
6927 navaction(t, &a);
6930 void
6931 forward_cb(GtkWidget *w, struct tab *t)
6933 struct karg a;
6935 if (t == NULL) {
6936 show_oops_s("forward_cb invalid parameters");
6937 return;
6940 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
6942 a.i = XT_NAV_FORWARD;
6943 navaction(t, &a);
6946 void
6947 home_cb(GtkWidget *w, struct tab *t)
6949 if (t == NULL) {
6950 show_oops_s("home_cb invalid parameters");
6951 return;
6954 DNPRINTF(XT_D_NAV, "home_cb: tab %d\n", t->tab_id);
6956 load_uri(t, home);
6959 void
6960 stop_cb(GtkWidget *w, struct tab *t)
6962 WebKitWebFrame *frame;
6964 if (t == NULL) {
6965 show_oops_s("stop_cb invalid parameters");
6966 return;
6969 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
6971 frame = webkit_web_view_get_main_frame(t->wv);
6972 if (frame == NULL) {
6973 show_oops(t, "stop_cb: no frame");
6974 return;
6977 webkit_web_frame_stop_loading(frame);
6978 abort_favicon_download(t);
6981 void
6982 setup_webkit(struct tab *t)
6984 if (is_g_object_setting(G_OBJECT(t->settings), "enable-dns-prefetching"))
6985 g_object_set(G_OBJECT(t->settings), "enable-dns-prefetching",
6986 FALSE, (char *)NULL);
6987 else
6988 warnx("webkit does not have \"enable-dns-prefetching\" property");
6989 g_object_set(G_OBJECT(t->settings),
6990 "user-agent", t->user_agent, (char *)NULL);
6991 g_object_set(G_OBJECT(t->settings),
6992 "enable-scripts", enable_scripts, (char *)NULL);
6993 g_object_set(G_OBJECT(t->settings),
6994 "enable-plugins", enable_plugins, (char *)NULL);
6995 g_object_set(G_OBJECT(t->settings),
6996 "javascript-can-open-windows-automatically", enable_scripts, (char *)NULL);
6997 g_object_set(G_OBJECT(t->settings),
6998 "enable_spell_checking", enable_spell_checking, (char *)NULL);
6999 g_object_set(G_OBJECT(t->settings),
7000 "spell_checking_languages", spell_check_languages, (char *)NULL);
7001 g_object_set(G_OBJECT(t->wv),
7002 "full-content-zoom", TRUE, (char *)NULL);
7003 adjustfont_webkit(t, XT_FONT_SET);
7005 webkit_web_view_set_settings(t->wv, t->settings);
7008 GtkWidget *
7009 create_browser(struct tab *t)
7011 GtkWidget *w;
7012 gchar *strval;
7014 if (t == NULL) {
7015 show_oops_s("create_browser invalid parameters");
7016 return (NULL);
7019 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
7020 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
7021 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
7022 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
7024 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
7025 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
7026 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
7028 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
7029 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
7031 /* set defaults */
7032 t->settings = webkit_web_settings_new();
7034 if (user_agent == NULL) {
7035 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
7036 (char *)NULL);
7037 t->user_agent = g_strdup_printf("%s %s+", strval, version);
7038 g_free(strval);
7039 } else
7040 t->user_agent = g_strdup(user_agent);
7042 t->stylesheet = g_strdup_printf("file://%s/style.css", resource_dir);
7044 setup_webkit(t);
7046 return (w);
7049 GtkWidget *
7050 create_window(void)
7052 GtkWidget *w;
7054 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
7055 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
7056 gtk_widget_set_name(w, "xxxterm");
7057 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
7058 g_signal_connect(G_OBJECT(w), "delete_event",
7059 G_CALLBACK (gtk_main_quit), NULL);
7061 return (w);
7064 GtkWidget *
7065 create_kiosk_toolbar(struct tab *t)
7067 GtkWidget *toolbar = NULL, *b;
7069 b = gtk_hbox_new(FALSE, 0);
7070 toolbar = b;
7071 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7073 /* backward button */
7074 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7075 gtk_widget_set_sensitive(t->backward, FALSE);
7076 g_signal_connect(G_OBJECT(t->backward), "clicked",
7077 G_CALLBACK(backward_cb), t);
7078 gtk_box_pack_start(GTK_BOX(b), t->backward, TRUE, TRUE, 0);
7080 /* forward button */
7081 t->forward = create_button("Forward", GTK_STOCK_GO_FORWARD, 0);
7082 gtk_widget_set_sensitive(t->forward, FALSE);
7083 g_signal_connect(G_OBJECT(t->forward), "clicked",
7084 G_CALLBACK(forward_cb), t);
7085 gtk_box_pack_start(GTK_BOX(b), t->forward, TRUE, TRUE, 0);
7087 /* home button */
7088 t->gohome = create_button("Home", GTK_STOCK_HOME, 0);
7089 gtk_widget_set_sensitive(t->gohome, true);
7090 g_signal_connect(G_OBJECT(t->gohome), "clicked",
7091 G_CALLBACK(home_cb), t);
7092 gtk_box_pack_start(GTK_BOX(b), t->gohome, TRUE, TRUE, 0);
7094 /* create widgets but don't use them */
7095 t->uri_entry = gtk_entry_new();
7096 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7097 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7098 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7100 return (toolbar);
7103 GtkWidget *
7104 create_toolbar(struct tab *t)
7106 GtkWidget *toolbar = NULL, *b, *eb1;
7108 b = gtk_hbox_new(FALSE, 0);
7109 toolbar = b;
7110 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
7112 if (fancy_bar) {
7113 /* backward button */
7114 t->backward = create_button("Back", GTK_STOCK_GO_BACK, 0);
7115 gtk_widget_set_sensitive(t->backward, FALSE);
7116 g_signal_connect(G_OBJECT(t->backward), "clicked",
7117 G_CALLBACK(backward_cb), t);
7118 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
7120 /* forward button */
7121 t->forward = create_button("Forward",GTK_STOCK_GO_FORWARD, 0);
7122 gtk_widget_set_sensitive(t->forward, FALSE);
7123 g_signal_connect(G_OBJECT(t->forward), "clicked",
7124 G_CALLBACK(forward_cb), t);
7125 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
7126 FALSE, 0);
7128 /* stop button */
7129 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
7130 gtk_widget_set_sensitive(t->stop, FALSE);
7131 g_signal_connect(G_OBJECT(t->stop), "clicked",
7132 G_CALLBACK(stop_cb), t);
7133 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
7134 FALSE, 0);
7136 /* JS button */
7137 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
7138 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
7139 gtk_widget_set_sensitive(t->js_toggle, TRUE);
7140 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
7141 G_CALLBACK(js_toggle_cb), t);
7142 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
7145 t->uri_entry = gtk_entry_new();
7146 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
7147 G_CALLBACK(activate_uri_entry_cb), t);
7148 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
7149 G_CALLBACK(entry_key_cb), t);
7150 completion_add(t);
7151 eb1 = gtk_hbox_new(FALSE, 0);
7152 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
7153 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
7154 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
7156 /* search entry */
7157 if (fancy_bar && search_string) {
7158 GtkWidget *eb2;
7159 t->search_entry = gtk_entry_new();
7160 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
7161 g_signal_connect(G_OBJECT(t->search_entry), "activate",
7162 G_CALLBACK(activate_search_entry_cb), t);
7163 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
7164 G_CALLBACK(entry_key_cb), t);
7165 gtk_widget_set_size_request(t->search_entry, -1, -1);
7166 eb2 = gtk_hbox_new(FALSE, 0);
7167 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
7168 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
7170 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
7172 return (toolbar);
7175 void
7176 recalc_tabs(void)
7178 struct tab *t;
7180 TAILQ_FOREACH(t, &tabs, entry)
7181 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
7185 undo_close_tab_save(struct tab *t)
7187 int m, n;
7188 const gchar *uri;
7189 struct undo *u1, *u2;
7190 GList *items;
7191 WebKitWebHistoryItem *item;
7193 if ((uri = get_uri(t->wv)) == NULL)
7194 return (1);
7196 u1 = g_malloc0(sizeof(struct undo));
7197 u1->uri = g_strdup(uri);
7199 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7201 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
7202 n = webkit_web_back_forward_list_get_back_length(t->bfl);
7203 u1->back = n;
7205 /* forward history */
7206 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
7208 while (items) {
7209 item = items->data;
7210 u1->history = g_list_prepend(u1->history,
7211 webkit_web_history_item_copy(item));
7212 items = g_list_next(items);
7215 /* current item */
7216 if (m) {
7217 item = webkit_web_back_forward_list_get_current_item(t->bfl);
7218 u1->history = g_list_prepend(u1->history,
7219 webkit_web_history_item_copy(item));
7222 /* back history */
7223 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
7225 while (items) {
7226 item = items->data;
7227 u1->history = g_list_prepend(u1->history,
7228 webkit_web_history_item_copy(item));
7229 items = g_list_next(items);
7232 TAILQ_INSERT_HEAD(&undos, u1, entry);
7234 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
7235 u2 = TAILQ_LAST(&undos, undo_tailq);
7236 TAILQ_REMOVE(&undos, u2, entry);
7237 g_free(u2->uri);
7238 g_list_free(u2->history);
7239 g_free(u2);
7240 } else
7241 undo_count++;
7243 return (0);
7246 void
7247 delete_tab(struct tab *t)
7249 struct karg a;
7251 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
7253 if (t == NULL)
7254 return;
7256 TAILQ_REMOVE(&tabs, t, entry);
7258 /* halt all webkit activity */
7259 abort_favicon_download(t);
7260 webkit_web_view_stop_loading(t->wv);
7261 undo_close_tab_save(t);
7263 if (browser_mode == XT_BM_KIOSK) {
7264 gtk_widget_destroy(t->uri_entry);
7265 gtk_widget_destroy(t->stop);
7266 gtk_widget_destroy(t->js_toggle);
7269 gtk_widget_destroy(t->vbox);
7270 g_free(t->user_agent);
7271 g_free(t->stylesheet);
7272 g_free(t);
7274 if (TAILQ_EMPTY(&tabs)) {
7275 if (browser_mode == XT_BM_KIOSK)
7276 create_new_tab(home, NULL, 1, -1);
7277 else
7278 create_new_tab(NULL, NULL, 1, -1);
7281 /* recreate session */
7282 if (session_autosave) {
7283 a.s = NULL;
7284 save_tabs(t, &a);
7288 void
7289 adjustfont_webkit(struct tab *t, int adjust)
7291 gfloat zoom;
7293 if (t == NULL) {
7294 show_oops_s("adjustfont_webkit invalid parameters");
7295 return;
7298 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7299 if (adjust == XT_FONT_SET) {
7300 t->font_size = default_font_size;
7301 zoom = default_zoom_level;
7302 t->font_size += adjust;
7303 g_object_set(G_OBJECT(t->settings), "default-font-size",
7304 t->font_size, (char *)NULL);
7305 g_object_get(G_OBJECT(t->settings), "default-font-size",
7306 &t->font_size, (char *)NULL);
7307 } else {
7308 t->font_size += adjust;
7309 zoom += adjust/25.0;
7310 if (zoom < 0.0) {
7311 zoom = 0.04;
7314 g_object_set(G_OBJECT(t->wv), "zoom-level", zoom, (char *)NULL);
7315 g_object_get(G_OBJECT(t->wv), "zoom-level", &zoom, (char *)NULL);
7318 void
7319 append_tab(struct tab *t)
7321 if (t == NULL)
7322 return;
7324 TAILQ_INSERT_TAIL(&tabs, t, entry);
7325 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
7328 struct tab *
7329 create_new_tab(char *title, struct undo *u, int focus, int position)
7331 struct tab *t;
7332 int load = 1, id;
7333 GtkWidget *b, *bb;
7334 WebKitWebHistoryItem *item;
7335 GList *items;
7336 GdkColor color;
7338 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
7340 if (tabless && !TAILQ_EMPTY(&tabs)) {
7341 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
7342 return (NULL);
7345 t = g_malloc0(sizeof *t);
7347 if (title == NULL) {
7348 title = "(untitled)";
7349 load = 0;
7352 t->vbox = gtk_vbox_new(FALSE, 0);
7354 /* label + button for tab */
7355 b = gtk_hbox_new(FALSE, 0);
7356 t->tab_content = b;
7358 #if GTK_CHECK_VERSION(2, 20, 0)
7359 t->spinner = gtk_spinner_new ();
7360 #endif
7361 t->label = gtk_label_new(title);
7362 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
7363 gtk_widget_set_size_request(t->label, 100, 0);
7364 gtk_label_set_max_width_chars(GTK_LABEL(t->label), 20);
7365 gtk_label_set_ellipsize(GTK_LABEL(t->label), PANGO_ELLIPSIZE_END);
7366 gtk_widget_set_size_request(b, 130, 0);
7368 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
7369 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
7370 #if GTK_CHECK_VERSION(2, 20, 0)
7371 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
7372 #endif
7374 /* toolbar */
7375 if (browser_mode == XT_BM_KIOSK)
7376 t->toolbar = create_kiosk_toolbar(t);
7377 else
7378 t->toolbar = create_toolbar(t);
7380 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
7382 /* browser */
7383 t->browser_win = create_browser(t);
7384 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
7386 /* oops message for user feedback */
7387 t->oops = gtk_entry_new();
7388 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
7389 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
7390 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
7391 gdk_color_parse(XT_COLOR_RED, &color);
7392 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
7393 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
7395 /* command entry */
7396 t->cmd = gtk_entry_new();
7397 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
7398 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
7399 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
7401 /* status bar */
7402 t->statusbar = gtk_entry_new();
7403 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
7404 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
7405 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
7406 gdk_color_parse(XT_COLOR_BLACK, &color);
7407 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
7408 gdk_color_parse(XT_COLOR_WHITE, &color);
7409 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
7410 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
7412 /* xtp meaning is normal by default */
7413 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
7415 /* set empty favicon */
7416 xt_icon_from_name(t, "text-html");
7418 /* and show it all */
7419 gtk_widget_show_all(b);
7420 gtk_widget_show_all(t->vbox);
7422 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
7423 append_tab(t);
7424 else {
7425 id = position >= 0 ? position: gtk_notebook_get_current_page(notebook) + 1;
7426 if (id > gtk_notebook_get_n_pages(notebook))
7427 append_tab(t);
7428 else {
7429 TAILQ_INSERT_TAIL(&tabs, t, entry);
7430 gtk_notebook_insert_page(notebook, t->vbox, b, id);
7431 recalc_tabs();
7435 #if GTK_CHECK_VERSION(2, 20, 0)
7436 /* turn spinner off if we are a new tab without uri */
7437 if (!load) {
7438 gtk_spinner_stop(GTK_SPINNER(t->spinner));
7439 gtk_widget_hide(t->spinner);
7441 #endif
7442 /* make notebook tabs reorderable */
7443 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
7445 g_object_connect(G_OBJECT(t->cmd),
7446 "signal::key-press-event", G_CALLBACK(cmd_keypress_cb), t,
7447 "signal::key-release-event", G_CALLBACK(cmd_keyrelease_cb), t,
7448 "signal::focus-out-event", G_CALLBACK(cmd_focusout_cb), t,
7449 "signal::activate", G_CALLBACK(cmd_activate_cb), t,
7450 (char *)NULL);
7452 /* reuse wv_button_cb to hide oops */
7453 g_object_connect(G_OBJECT(t->oops),
7454 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7455 (char *)NULL);
7457 g_object_connect(G_OBJECT(t->wv),
7458 "signal::key-press-event", G_CALLBACK(wv_keypress_cb), t,
7459 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7460 "signal::hovering-over-link", G_CALLBACK(webview_hover_cb), t,
7461 "signal::download-requested", G_CALLBACK(webview_download_cb), t,
7462 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
7463 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7464 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
7465 "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
7466 "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
7467 "signal::event", G_CALLBACK(webview_event_cb), t,
7468 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), t,
7469 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), t,
7470 "signal::icon-loaded", G_CALLBACK(notify_icon_loaded_cb), t,
7471 "signal::button_press_event", G_CALLBACK(wv_button_cb), t,
7472 (char *)NULL);
7473 g_signal_connect(t->wv,
7474 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
7475 g_signal_connect(t->wv,
7476 "notify::title", G_CALLBACK(notify_title_cb), t);
7478 /* hijack the unused keys as if we were the browser */
7479 g_object_connect(G_OBJECT(t->toolbar),
7480 "signal-after::key-press-event", G_CALLBACK(wv_keypress_after_cb), t,
7481 (char *)NULL);
7483 g_signal_connect(G_OBJECT(bb), "button_press_event",
7484 G_CALLBACK(tab_close_cb), t);
7486 /* hide stuff */
7487 hide_cmd(t);
7488 hide_oops(t);
7489 url_set_visibility();
7490 statusbar_set_visibility();
7492 if (focus) {
7493 gtk_notebook_set_current_page(notebook, t->tab_id);
7494 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
7495 t->tab_id);
7498 if (load) {
7499 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), title);
7500 load_uri(t, title);
7501 } else {
7502 if (show_url == 1)
7503 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
7504 else
7505 focus_webview(t);
7508 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
7509 /* restore the tab's history */
7510 if (u && u->history) {
7511 items = u->history;
7512 while (items) {
7513 item = items->data;
7514 webkit_web_back_forward_list_add_item(t->bfl, item);
7515 items = g_list_next(items);
7518 item = g_list_nth_data(u->history, u->back);
7519 if (item)
7520 webkit_web_view_go_to_back_forward_item(t->wv, item);
7522 g_list_free(items);
7523 g_list_free(u->history);
7524 } else
7525 webkit_web_back_forward_list_clear(t->bfl);
7527 return (t);
7530 void
7531 notebook_switchpage_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
7532 gpointer *udata)
7534 struct tab *t;
7535 const gchar *uri;
7537 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
7539 if (gtk_notebook_get_current_page(notebook) == -1)
7540 recalc_tabs();
7542 TAILQ_FOREACH(t, &tabs, entry) {
7543 if (t->tab_id == pn) {
7544 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
7545 "%d\n", pn);
7547 uri = webkit_web_view_get_title(t->wv);
7548 if (uri == NULL)
7549 uri = XT_NAME;
7550 gtk_window_set_title(GTK_WINDOW(main_window), uri);
7552 hide_cmd(t);
7553 hide_oops(t);
7555 if (t->focus_wv) {
7556 /* can't use focus_webview here */
7557 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
7563 void
7564 notebook_pagereordered_cb(GtkNotebook *nb, GtkWidget *nbp, guint pn,
7565 gpointer *udata)
7567 recalc_tabs();
7570 void
7571 menuitem_response(struct tab *t)
7573 gtk_notebook_set_current_page(notebook, t->tab_id);
7576 gboolean
7577 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
7579 GtkWidget *menu, *menu_items;
7580 GdkEventButton *bevent;
7581 const gchar *uri;
7582 struct tab *ti;
7584 if (event->type == GDK_BUTTON_PRESS) {
7585 bevent = (GdkEventButton *) event;
7586 menu = gtk_menu_new();
7588 TAILQ_FOREACH(ti, &tabs, entry) {
7589 if ((uri = get_uri(ti->wv)) == NULL)
7590 /* XXX make sure there is something to print */
7591 /* XXX add gui pages in here to look purdy */
7592 uri = "(untitled)";
7593 menu_items = gtk_menu_item_new_with_label(uri);
7594 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);
7595 gtk_widget_show(menu_items);
7597 g_signal_connect_swapped((menu_items),
7598 "activate", G_CALLBACK(menuitem_response),
7599 (gpointer)ti);
7602 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
7603 bevent->button, bevent->time);
7605 /* unref object so it'll free itself when popped down */
7606 #if !GTK_CHECK_VERSION(3, 0, 0)
7607 /* XXX does not need unref with gtk+3? */
7608 g_object_ref_sink(menu);
7609 g_object_unref(menu);
7610 #endif
7612 return (TRUE /* eat event */);
7615 return (FALSE /* propagate */);
7619 icon_size_map(int icon_size)
7621 if (icon_size <= GTK_ICON_SIZE_INVALID ||
7622 icon_size > GTK_ICON_SIZE_DIALOG)
7623 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
7625 return (icon_size);
7628 GtkWidget *
7629 create_button(char *name, char *stockid, int size)
7631 GtkWidget *button, *image;
7632 gchar *rcstring;
7633 int gtk_icon_size;
7635 rcstring = g_strdup_printf(
7636 "style \"%s-style\"\n"
7637 "{\n"
7638 " GtkWidget::focus-padding = 0\n"
7639 " GtkWidget::focus-line-width = 0\n"
7640 " xthickness = 0\n"
7641 " ythickness = 0\n"
7642 "}\n"
7643 "widget \"*.%s\" style \"%s-style\"", name, name, name);
7644 gtk_rc_parse_string(rcstring);
7645 g_free(rcstring);
7646 button = gtk_button_new();
7647 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
7648 gtk_icon_size = icon_size_map(size ? size : icon_size);
7650 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
7651 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7652 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
7653 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
7654 gtk_widget_set_name(button, name);
7655 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
7657 return (button);
7660 void
7661 button_set_stockid(GtkWidget *button, char *stockid)
7663 GtkWidget *image;
7665 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
7666 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
7667 gtk_button_set_image(GTK_BUTTON(button), image);
7670 void
7671 clipb_primary_cb(GtkClipboard *primary, GdkEvent *event, gpointer notused)
7673 GtkClipboard *clipboard;
7674 gchar *p = NULL, *s = NULL;
7677 * This code is very aggressive!
7678 * It basically ensures that the primary and regular clipboard are
7679 * always set the same. This obviously messes with standard X protocol
7680 * but those clowns should have come up with something better.
7683 /* XXX make this setting? */
7684 clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
7685 p = gtk_clipboard_wait_for_text(primary);
7686 if (p == NULL) {
7687 DNPRINTF(XT_D_CLIP, "primary cleaned\n");
7688 p = gtk_clipboard_wait_for_text(clipboard);
7689 if (p)
7690 gtk_clipboard_set_text(primary, p, -1);
7691 } else {
7692 DNPRINTF(XT_D_CLIP, "primary got selection\n");
7693 s = gtk_clipboard_wait_for_text(clipboard);
7694 if (s) {
7696 * if s and p are the same the string was set by
7697 * clipb_clipboard_cb so do nothing in that case
7698 * to prevent endless loop
7700 if (!strcmp(s, p))
7701 goto done;
7703 gtk_clipboard_set_text(clipboard, p, -1);
7705 done:
7706 if (p)
7707 g_free(p);
7708 if (s)
7709 g_free(s);
7712 void
7713 clipb_clipboard_cb(GtkClipboard *clipboard, GdkEvent *event, gpointer notused)
7715 GtkClipboard *primary;
7716 gchar *p = NULL, *s = NULL;
7718 DNPRINTF(XT_D_CLIP, "clipboard got content\n");
7720 primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7721 p = gtk_clipboard_wait_for_text(clipboard);
7722 if (p) {
7723 s = gtk_clipboard_wait_for_text(primary);
7724 if (s) {
7726 * if s and p are the same the string was set by
7727 * clipb_primary_cb so do nothing in that case
7728 * to prevent endless loop and deselection of text
7730 if (!strcmp(s, p))
7731 goto done;
7733 gtk_clipboard_set_text(primary, p, -1);
7735 done:
7736 if (p)
7737 g_free(p);
7738 if (s)
7739 g_free(s);
7742 void
7743 create_canvas(void)
7745 GtkWidget *vbox;
7746 GList *l = NULL;
7747 GdkPixbuf *pb;
7748 char file[PATH_MAX];
7749 int i;
7751 vbox = gtk_vbox_new(FALSE, 0);
7752 gtk_box_set_spacing(GTK_BOX(vbox), 0);
7753 notebook = GTK_NOTEBOOK(gtk_notebook_new());
7754 #if !GTK_CHECK_VERSION(3, 0, 0)
7755 /* XXX seems to be needed with gtk+2 */
7756 gtk_notebook_set_tab_hborder(notebook, 0);
7757 gtk_notebook_set_tab_vborder(notebook, 0);
7758 #endif
7759 gtk_notebook_set_scrollable(notebook, TRUE);
7760 notebook_tab_set_visibility(notebook);
7761 gtk_notebook_set_show_border(notebook, FALSE);
7762 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
7764 abtn = gtk_button_new();
7765 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
7766 gtk_widget_set_size_request(arrow, -1, -1);
7767 gtk_container_add(GTK_CONTAINER(abtn), arrow);
7768 gtk_widget_set_size_request(abtn, -1, 20);
7770 #if GTK_CHECK_VERSION(2, 20, 0)
7771 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
7772 #endif
7773 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
7774 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
7775 gtk_widget_set_size_request(vbox, -1, -1);
7777 g_object_connect(G_OBJECT(notebook),
7778 "signal::switch-page", G_CALLBACK(notebook_switchpage_cb), NULL,
7779 (char *)NULL);
7780 g_object_connect(G_OBJECT(notebook),
7781 "signal::page-reordered", G_CALLBACK(notebook_pagereordered_cb), NULL,
7782 (char *)NULL);
7783 g_signal_connect(G_OBJECT(abtn), "button_press_event",
7784 G_CALLBACK(arrow_cb), NULL);
7786 main_window = create_window();
7787 gtk_container_add(GTK_CONTAINER(main_window), vbox);
7788 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
7790 /* icons */
7791 for (i = 0; i < LENGTH(icons); i++) {
7792 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
7793 pb = gdk_pixbuf_new_from_file(file, NULL);
7794 l = g_list_append(l, pb);
7796 gtk_window_set_default_icon_list(l);
7798 /* clipboard */
7799 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_PRIMARY)),
7800 "owner-change", G_CALLBACK(clipb_primary_cb), NULL);
7801 g_signal_connect(G_OBJECT(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)),
7802 "owner-change", G_CALLBACK(clipb_clipboard_cb), NULL);
7804 gtk_widget_show_all(abtn);
7805 gtk_widget_show_all(main_window);
7808 void
7809 set_hook(void **hook, char *name)
7811 if (hook == NULL)
7812 errx(1, "set_hook");
7814 if (*hook == NULL) {
7815 *hook = dlsym(RTLD_NEXT, name);
7816 if (*hook == NULL)
7817 errx(1, "can't hook %s", name);
7821 /* override libsoup soup_cookie_equal because it doesn't look at domain */
7822 gboolean
7823 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
7825 g_return_val_if_fail(cookie1, FALSE);
7826 g_return_val_if_fail(cookie2, FALSE);
7828 return (!strcmp (cookie1->name, cookie2->name) &&
7829 !strcmp (cookie1->value, cookie2->value) &&
7830 !strcmp (cookie1->path, cookie2->path) &&
7831 !strcmp (cookie1->domain, cookie2->domain));
7834 void
7835 transfer_cookies(void)
7837 GSList *cf;
7838 SoupCookie *sc, *pc;
7840 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7842 for (;cf; cf = cf->next) {
7843 pc = cf->data;
7844 sc = soup_cookie_copy(pc);
7845 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
7848 soup_cookies_free(cf);
7851 void
7852 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
7854 GSList *cf;
7855 SoupCookie *ci;
7857 print_cookie("soup_cookie_jar_delete_cookie", c);
7859 if (cookies_enabled == 0)
7860 return;
7862 if (jar == NULL || c == NULL)
7863 return;
7865 /* find and remove from persistent jar */
7866 cf = soup_cookie_jar_all_cookies(p_cookiejar);
7868 for (;cf; cf = cf->next) {
7869 ci = cf->data;
7870 if (soup_cookie_equal(ci, c)) {
7871 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
7872 break;
7876 soup_cookies_free(cf);
7878 /* delete from session jar */
7879 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
7882 void
7883 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
7885 struct domain *d = NULL;
7886 SoupCookie *c;
7887 FILE *r_cookie_f;
7889 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
7890 jar, p_cookiejar, s_cookiejar);
7892 if (cookies_enabled == 0)
7893 return;
7895 /* see if we are up and running */
7896 if (p_cookiejar == NULL) {
7897 _soup_cookie_jar_add_cookie(jar, cookie);
7898 return;
7900 /* disallow p_cookiejar adds, shouldn't happen */
7901 if (jar == p_cookiejar)
7902 return;
7904 /* sanity */
7905 if (jar == NULL || cookie == NULL)
7906 return;
7908 if (enable_cookie_whitelist &&
7909 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
7910 blocked_cookies++;
7911 DNPRINTF(XT_D_COOKIE,
7912 "soup_cookie_jar_add_cookie: reject %s\n",
7913 cookie->domain);
7914 if (save_rejected_cookies) {
7915 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL) {
7916 show_oops_s("can't open reject cookie file");
7917 return;
7919 fseek(r_cookie_f, 0, SEEK_END);
7920 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
7921 cookie->http_only ? "#HttpOnly_" : "",
7922 cookie->domain,
7923 *cookie->domain == '.' ? "TRUE" : "FALSE",
7924 cookie->path,
7925 cookie->secure ? "TRUE" : "FALSE",
7926 cookie->expires ?
7927 (gulong)soup_date_to_time_t(cookie->expires) :
7929 cookie->name,
7930 cookie->value);
7931 fflush(r_cookie_f);
7932 fclose(r_cookie_f);
7934 if (!allow_volatile_cookies)
7935 return;
7938 if (cookie->expires == NULL && session_timeout) {
7939 soup_cookie_set_expires(cookie,
7940 soup_date_new_from_now(session_timeout));
7941 print_cookie("modified add cookie", cookie);
7944 /* see if we are white listed for persistence */
7945 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
7946 /* add to persistent jar */
7947 c = soup_cookie_copy(cookie);
7948 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
7949 _soup_cookie_jar_add_cookie(p_cookiejar, c);
7952 /* add to session jar */
7953 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
7954 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
7957 void
7958 setup_cookies(void)
7960 char file[PATH_MAX];
7962 set_hook((void *)&_soup_cookie_jar_add_cookie,
7963 "soup_cookie_jar_add_cookie");
7964 set_hook((void *)&_soup_cookie_jar_delete_cookie,
7965 "soup_cookie_jar_delete_cookie");
7967 if (cookies_enabled == 0)
7968 return;
7971 * the following code is intricate due to overriding several libsoup
7972 * functions.
7973 * do not alter order of these operations.
7976 /* rejected cookies */
7977 if (save_rejected_cookies)
7978 snprintf(rc_fname, sizeof file, "%s/%s", work_dir, XT_REJECT_FILE);
7980 /* persistent cookies */
7981 snprintf(file, sizeof file, "%s/%s", work_dir, XT_COOKIE_FILE);
7982 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
7984 /* session cookies */
7985 s_cookiejar = soup_cookie_jar_new();
7986 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
7987 cookie_policy, (void *)NULL);
7988 transfer_cookies();
7990 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
7993 void
7994 setup_proxy(char *uri)
7996 if (proxy_uri) {
7997 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
7998 soup_uri_free(proxy_uri);
7999 proxy_uri = NULL;
8001 if (http_proxy) {
8002 if (http_proxy != uri) {
8003 g_free(http_proxy);
8004 http_proxy = NULL;
8008 if (uri) {
8009 http_proxy = g_strdup(uri);
8010 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
8011 proxy_uri = soup_uri_new(http_proxy);
8012 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
8017 send_cmd_to_socket(char *cmd)
8019 int s, len, rv = 1;
8020 struct sockaddr_un sa;
8022 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8023 warnx("%s: socket", __func__);
8024 return (rv);
8027 sa.sun_family = AF_UNIX;
8028 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8029 work_dir, XT_SOCKET_FILE);
8030 len = SUN_LEN(&sa);
8032 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8033 warnx("%s: connect", __func__);
8034 goto done;
8037 if (send(s, cmd, strlen(cmd) + 1, 0) == -1) {
8038 warnx("%s: send", __func__);
8039 goto done;
8042 rv = 0;
8043 done:
8044 close(s);
8045 return (rv);
8048 gboolean
8049 socket_watcher(GIOChannel *source, GIOCondition condition, gpointer data)
8051 int s, n;
8052 char str[XT_MAX_URL_LENGTH];
8053 socklen_t t = sizeof(struct sockaddr_un);
8054 struct sockaddr_un sa;
8055 struct passwd *p;
8056 uid_t uid;
8057 gid_t gid;
8058 struct tab *tt;
8059 gint fd = g_io_channel_unix_get_fd(source);
8061 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
8062 warn("accept");
8063 return (FALSE);
8066 if (getpeereid(s, &uid, &gid) == -1) {
8067 warn("getpeereid");
8068 return (FALSE);
8070 if (uid != getuid() || gid != getgid()) {
8071 warnx("unauthorized user");
8072 return (FALSE);
8075 p = getpwuid(uid);
8076 if (p == NULL) {
8077 warnx("not a valid user");
8078 return (FALSE);
8081 n = recv(s, str, sizeof(str), 0);
8082 if (n <= 0)
8083 return (FALSE);
8085 tt = TAILQ_LAST(&tabs, tab_list);
8086 cmd_execute(tt, str);
8087 return (TRUE);
8091 is_running(void)
8093 int s, len, rv = 1;
8094 struct sockaddr_un sa;
8096 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8097 warn("is_running: socket");
8098 return (-1);
8101 sa.sun_family = AF_UNIX;
8102 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8103 work_dir, XT_SOCKET_FILE);
8104 len = SUN_LEN(&sa);
8106 /* connect to see if there is a listener */
8107 if (connect(s, (struct sockaddr *)&sa, len) == -1)
8108 rv = 0; /* not running */
8109 else
8110 rv = 1; /* already running */
8112 close(s);
8114 return (rv);
8118 build_socket(void)
8120 int s, len;
8121 struct sockaddr_un sa;
8123 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
8124 warn("build_socket: socket");
8125 return (-1);
8128 sa.sun_family = AF_UNIX;
8129 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s",
8130 work_dir, XT_SOCKET_FILE);
8131 len = SUN_LEN(&sa);
8133 /* connect to see if there is a listener */
8134 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
8135 /* no listener so we will */
8136 unlink(sa.sun_path);
8138 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
8139 warn("build_socket: bind");
8140 goto done;
8143 if (listen(s, 1) == -1) {
8144 warn("build_socket: listen");
8145 goto done;
8148 return (s);
8151 done:
8152 close(s);
8153 return (-1);
8156 gboolean
8157 completion_select_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8158 GtkTreeIter *iter, struct tab *t)
8160 gchar *value;
8162 gtk_tree_model_get(model, iter, 0, &value, -1);
8163 load_uri(t, value);
8164 g_free(value);
8166 return (FALSE);
8169 gboolean
8170 completion_hover_cb(GtkEntryCompletion *widget, GtkTreeModel *model,
8171 GtkTreeIter *iter, struct tab *t)
8173 gchar *value;
8175 gtk_tree_model_get(model, iter, 0, &value, -1);
8176 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), value);
8177 gtk_editable_set_position(GTK_EDITABLE(t->uri_entry), -1);
8178 g_free(value);
8180 return (TRUE);
8183 void
8184 completion_add_uri(const gchar *uri)
8186 GtkTreeIter iter;
8188 /* add uri to list_store */
8189 gtk_list_store_append(completion_model, &iter);
8190 gtk_list_store_set(completion_model, &iter, 0, uri, -1);
8193 gboolean
8194 completion_match(GtkEntryCompletion *completion, const gchar *key,
8195 GtkTreeIter *iter, gpointer user_data)
8197 gchar *value;
8198 gboolean match = FALSE;
8200 gtk_tree_model_get(GTK_TREE_MODEL(completion_model), iter, 0, &value,
8201 -1);
8203 if (value == NULL)
8204 return FALSE;
8206 match = match_uri(value, key);
8208 g_free(value);
8209 return (match);
8212 void
8213 completion_add(struct tab *t)
8215 /* enable completion for tab */
8216 t->completion = gtk_entry_completion_new();
8217 gtk_entry_completion_set_text_column(t->completion, 0);
8218 gtk_entry_set_completion(GTK_ENTRY(t->uri_entry), t->completion);
8219 gtk_entry_completion_set_model(t->completion,
8220 GTK_TREE_MODEL(completion_model));
8221 gtk_entry_completion_set_match_func(t->completion, completion_match,
8222 NULL, NULL);
8223 gtk_entry_completion_set_minimum_key_length(t->completion, 1);
8224 gtk_entry_completion_set_inline_selection(t->completion, TRUE);
8225 g_signal_connect(G_OBJECT (t->completion), "match-selected",
8226 G_CALLBACK(completion_select_cb), t);
8227 g_signal_connect(G_OBJECT (t->completion), "cursor-on-match",
8228 G_CALLBACK(completion_hover_cb), t);
8231 void
8232 xxx_dir(char *dir)
8234 struct stat sb;
8236 if (stat(dir, &sb)) {
8237 if (mkdir(dir, S_IRWXU) == -1)
8238 err(1, "mkdir %s", dir);
8239 if (stat(dir, &sb))
8240 err(1, "stat %s", dir);
8242 if (S_ISDIR(sb.st_mode) == 0)
8243 errx(1, "%s not a dir", dir);
8244 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
8245 warnx("fixing invalid permissions on %s", dir);
8246 if (chmod(dir, S_IRWXU) == -1)
8247 err(1, "chmod %s", dir);
8251 void
8252 usage(void)
8254 fprintf(stderr,
8255 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
8256 exit(0);
8261 main(int argc, char *argv[])
8263 struct stat sb;
8264 int c, s, optn = 0, opte = 0, focus = 1;
8265 char conf[PATH_MAX] = { '\0' };
8266 char file[PATH_MAX];
8267 char *env_proxy = NULL;
8268 FILE *f = NULL;
8269 struct karg a;
8270 struct sigaction sact;
8271 gchar *priority = g_strdup("NORMAL");
8272 GIOChannel *channel;
8274 start_argv = argv;
8276 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
8278 while ((c = getopt(argc, argv, "STVf:s:tne")) != -1) {
8279 switch (c) {
8280 case 'S':
8281 show_url = 0;
8282 break;
8283 case 'T':
8284 show_tabs = 0;
8285 break;
8286 case 'V':
8287 errx(0 , "Version: %s", version);
8288 break;
8289 case 'f':
8290 strlcpy(conf, optarg, sizeof(conf));
8291 break;
8292 case 's':
8293 strlcpy(named_session, optarg, sizeof(named_session));
8294 break;
8295 case 't':
8296 tabless = 1;
8297 break;
8298 case 'n':
8299 optn = 1;
8300 break;
8301 case 'e':
8302 opte = 1;
8303 break;
8304 default:
8305 usage();
8306 /* NOTREACHED */
8309 argc -= optind;
8310 argv += optind;
8312 RB_INIT(&hl);
8313 RB_INIT(&js_wl);
8314 RB_INIT(&downloads);
8316 TAILQ_INIT(&tabs);
8317 TAILQ_INIT(&mtl);
8318 TAILQ_INIT(&aliases);
8319 TAILQ_INIT(&undos);
8320 TAILQ_INIT(&kbl);
8322 init_keybindings();
8324 gnutls_global_init();
8326 /* generate session keys for xtp pages */
8327 generate_xtp_session_key(&dl_session_key);
8328 generate_xtp_session_key(&hl_session_key);
8329 generate_xtp_session_key(&cl_session_key);
8330 generate_xtp_session_key(&fl_session_key);
8332 /* prepare gtk */
8333 gtk_init(&argc, &argv);
8334 if (!g_thread_supported())
8335 g_thread_init(NULL);
8337 /* signals */
8338 bzero(&sact, sizeof(sact));
8339 sigemptyset(&sact.sa_mask);
8340 sact.sa_handler = sigchild;
8341 sact.sa_flags = SA_NOCLDSTOP;
8342 sigaction(SIGCHLD, &sact, NULL);
8344 /* set download dir */
8345 pwd = getpwuid(getuid());
8346 if (pwd == NULL)
8347 errx(1, "invalid user %d", getuid());
8348 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
8350 /* set default string settings */
8351 home = g_strdup("https://www.cyphertite.com");
8352 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
8353 resource_dir = g_strdup("/usr/local/share/xxxterm/");
8354 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
8356 /* read config file */
8357 if (strlen(conf) == 0)
8358 snprintf(conf, sizeof conf, "%s/.%s",
8359 pwd->pw_dir, XT_CONF_FILE);
8360 config_parse(conf, 0);
8362 /* working directory */
8363 if (strlen(work_dir) == 0)
8364 snprintf(work_dir, sizeof work_dir, "%s/%s",
8365 pwd->pw_dir, XT_DIR);
8366 xxx_dir(work_dir);
8368 /* icon cache dir */
8369 snprintf(cache_dir, sizeof cache_dir, "%s/%s", work_dir, XT_CACHE_DIR);
8370 xxx_dir(cache_dir);
8372 /* certs dir */
8373 snprintf(certs_dir, sizeof certs_dir, "%s/%s", work_dir, XT_CERT_DIR);
8374 xxx_dir(certs_dir);
8376 /* sessions dir */
8377 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s",
8378 work_dir, XT_SESSIONS_DIR);
8379 xxx_dir(sessions_dir);
8381 /* runtime settings that can override config file */
8382 if (runtime_settings[0] != '\0')
8383 config_parse(runtime_settings, 1);
8385 /* download dir */
8386 if (!strcmp(download_dir, pwd->pw_dir))
8387 strlcat(download_dir, "/downloads", sizeof download_dir);
8388 xxx_dir(download_dir);
8390 /* favorites file */
8391 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
8392 if (stat(file, &sb)) {
8393 warnx("favorites file doesn't exist, creating it");
8394 if ((f = fopen(file, "w")) == NULL)
8395 err(1, "favorites");
8396 fclose(f);
8399 /* cookies */
8400 session = webkit_get_default_session();
8401 /* XXX ssl-priority property not quite available yet */
8402 if (is_g_object_setting(G_OBJECT(session), "ssl-priority"))
8403 g_object_set(G_OBJECT(session), "ssl-priority", priority,
8404 (char *)NULL);
8405 else
8406 warnx("session does not have \"ssl-priority\" property");
8407 setup_cookies();
8409 /* certs */
8410 if (ssl_ca_file) {
8411 if (stat(ssl_ca_file, &sb)) {
8412 warnx("no CA file: %s", ssl_ca_file);
8413 g_free(ssl_ca_file);
8414 ssl_ca_file = NULL;
8415 } else
8416 g_object_set(session,
8417 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
8418 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
8419 (void *)NULL);
8422 /* proxy */
8423 env_proxy = getenv("http_proxy");
8424 if (env_proxy)
8425 setup_proxy(env_proxy);
8426 else
8427 setup_proxy(http_proxy);
8429 if (opte) {
8430 send_cmd_to_socket(argv[0]);
8431 exit(0);
8434 /* set some connection parameters */
8435 g_object_set(session, "max-conns", max_connections, (char *)NULL);
8436 g_object_set(session, "max-conns-per-host", max_host_connections,
8437 (char *)NULL);
8439 /* see if there is already an xxxterm running */
8440 if (single_instance && is_running()) {
8441 optn = 1;
8442 warnx("already running");
8445 char *cmd = NULL;
8446 if (optn) {
8447 while (argc) {
8448 cmd = g_strdup_printf("%s %s", "tabnew", argv[0]);
8449 send_cmd_to_socket(cmd);
8450 if (cmd)
8451 g_free(cmd);
8453 argc--;
8454 argv++;
8456 exit(0);
8459 /* uri completion */
8460 completion_model = gtk_list_store_new(1, G_TYPE_STRING);
8462 /* go graphical */
8463 create_canvas();
8465 if (save_global_history)
8466 restore_global_history();
8468 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
8469 restore_saved_tabs();
8470 else {
8471 a.s = named_session;
8472 a.i = XT_SES_DONOTHING;
8473 open_tabs(NULL, &a);
8476 while (argc) {
8477 create_new_tab(argv[0], NULL, focus, -1);
8478 focus = 0;
8480 argc--;
8481 argv++;
8484 if (TAILQ_EMPTY(&tabs))
8485 create_new_tab(home, NULL, 1, -1);
8487 if (enable_socket)
8488 if ((s = build_socket()) != -1) {
8489 channel = g_io_channel_unix_new(s);
8490 g_io_add_watch(channel, G_IO_IN, socket_watcher, NULL);
8493 gtk_main();
8495 gnutls_global_deinit();
8497 return (0);